diff --git a/src/components/Form/Settings/Slider/Slider.jsx b/src/components/Form/Settings/Slider/Slider.jsx
index 5697ef15..a2d2dc3d 100644
--- a/src/components/Form/Settings/Slider/Slider.jsx
+++ b/src/components/Form/Settings/Slider/Slider.jsx
@@ -1,10 +1,11 @@
import variables from 'config/variables';
-import { useState, useCallback, memo, useMemo } from 'react';
+import { useState, useCallback, memo, useMemo, useRef } from 'react';
import { toast } from 'react-toastify';
import ReactSlider from 'react-slider';
import { MdRefresh, MdEdit } from 'react-icons/md';
import EventBus from 'utils/eventbus';
import clsx from 'clsx';
+import debounce from 'lodash/debounce';
// Style definitions split into logical groups for better readability
const buttonStyles = {
@@ -18,17 +19,18 @@ const buttonStyles = {
const thumbStyles = {
base: [
'w-5 h-5 rounded-full cursor-pointer',
- 'absolute top-1/2 transform -translate-y-1/2',
+ 'absolute top-1/2',
'flex items-center justify-center',
+ '-translate-y-1/2', // Keep only Y transform in classes
],
colors: 'bg-gradient-to-br from-neutral-50 to-neutral-200 dark:from-white dark:to-neutral-100',
interactions: [
'hover:from-neutral-100 hover:to-neutral-300',
'dark:hover:from-white dark:hover:to-neutral-200',
'focus:outline-none focus:ring-2 focus:ring-neutral-400 dark:focus:ring-white/40',
- 'active:scale-95',
],
- effects: 'shadow-lg shadow-black/10 dark:shadow-black/25 transition-all duration-200',
+ // Remove transition-all which was causing lag
+ effects: 'shadow-lg shadow-black/10 dark:shadow-black/25',
tooltip: [
'before:content-[attr(aria-valuenow)]',
'before:absolute before:top-[-28px]',
@@ -52,9 +54,11 @@ const markStyles = {
'after:content-[attr(data-value)]',
'after:absolute after:top-5',
'after:text-xs after:opacity-85',
- 'after:transform after:-translate-x-1/2',
+ 'after:left-1/2', // Add this to position relative to center
+ 'after:transform after:-translate-x-1/2', // Center the label
'after:whitespace-nowrap after:pointer-events-none',
'after:font-medium',
+ 'after:text-center', // Ensure text is centered
'after:text-neutral-700 dark:after:text-white/85',
],
};
@@ -67,15 +71,25 @@ const MULTIPLIER_MARKS = {
};
// Update ValueDisplay component to show edit affordance
-const ValueDisplay = memo(({ value, display, onChange }) => {
+const ValueDisplay = memo(({ value, display, onChange, min, max }) => {
const [editing, setEditing] = useState(false);
const [inputValue, setInputValue] = useState(value);
+ const handleChange = (e) => {
+ // Allow typing any numeric input, validate on blur
+ const numericValue = e.target.value.replace(/[^\d.-]/g, '');
+ setInputValue(numericValue);
+ };
+
const handleBlur = () => {
setEditing(false);
const numValue = Number(inputValue);
+
+ // Only validate and clamp on blur
if (!isNaN(numValue)) {
- onChange(numValue);
+ const clampedValue = Math.min(Math.max(numValue, min), max);
+ setInputValue(clampedValue);
+ onChange(clampedValue);
} else {
setInputValue(value);
}
@@ -92,14 +106,19 @@ const ValueDisplay = memo(({ value, display, onChange }) => {
return editing ? (
setInputValue(e.target.value)}
+ onChange={handleChange}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
+ min={min} // Optional: add min/max if needed
+ max={max}
+ step="any" // Allow decimal values
className="w-20 text-sm bg-neutral-200 dark:bg-white/15 text-neutral-800 dark:text-white
px-3 py-1.5 rounded-md font-medium outline-none focus:ring-2
- focus:ring-neutral-400 dark:focus:ring-white/40"
+ focus:ring-neutral-400 dark:focus:ring-white/40
+ [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none
+ transition-colors duration-200" // Added transition
autoFocus
/>
) : (
@@ -109,9 +128,9 @@ const ValueDisplay = memo(({ value, display, onChange }) => {
text-neutral-800 dark:text-white px-3 py-1.5 rounded-md font-medium cursor-pointer
hover:bg-neutral-300 dark:hover:bg-white/20 transition-colors duration-200"
>
-
+
{value}
- {display}
+ {display}
{
+ try {
+ localStorage.setItem(props.name, newValue);
+ lastUpdateRef.current = newValue;
+
+ if (props.element && !document.querySelector(props.element)) {
+ const reminderElement = document.querySelector('.reminder-info');
+ if (reminderElement) {
+ reminderElement.style.display = 'flex';
+ localStorage.setItem('showReminder', true);
+ return;
+ }
+ }
+
+ EventBus.emit('refresh', props.category);
+ } catch (e) {
+ console.error('Error updating slider:', e);
+ }
+ },
+ [props.element, props.category],
+ );
+
+ // More efficient debounced storage update
+ const debouncedUpdate = useMemo(() => debounce(updateStorage, 150), [updateStorage]);
+
const handleChange = useCallback(
(newValue) => {
if (typeof newValue !== 'number') return;
const clampedValue = Math.min(Math.max(newValue, props.min), props.max);
- // Update state immediately for smooth UI
+ // Always update visual state immediately
setValue(clampedValue);
- // Batch heavy operations in requestAnimationFrame
- requestAnimationFrame(() => {
- try {
- localStorage.setItem(props.name, clampedValue);
-
- if (props.element && !document.querySelector(props.element)) {
- const reminderElement = document.querySelector('.reminder-info');
- if (reminderElement) {
- reminderElement.style.display = 'flex';
- localStorage.setItem('showReminder', true);
- return;
- }
- }
-
- EventBus.emit('refresh', props.category);
- } catch (e) {
- console.error('Error updating slider:', e);
- }
- });
+ // Only update storage if we're not actively dragging
+ if (!isDraggingRef.current) {
+ debouncedUpdate(clampedValue);
+ }
},
- [props.min, props.max, props.name, props.element, props.category],
+ [props.min, props.max, debouncedUpdate],
);
+ const handleDragStart = useCallback(() => {
+ isDraggingRef.current = true;
+ }, []);
+
+ const handleDragEnd = useCallback(() => {
+ isDraggingRef.current = false;
+ // Ensure final value is stored
+ if (lastUpdateRef.current !== value) {
+ updateStorage(value);
+ }
+ }, [value, updateStorage]);
+
+ // Update thumbStyles to include performance optimizations
+ const enhancedThumbStyles = {
+ ...thumbStyles,
+ performance: [
+ 'will-change-transform',
+ 'transform translate3d(0,0,0)',
+ 'backface-visibility-hidden',
+ ],
+ };
+
const resetItem = useCallback(() => {
handleChange(Number(props.default));
toast(variables.getMessage('toasts.reset'));
@@ -200,6 +256,12 @@ function SliderComponent(props) {
() => (thumbProps, state) => (
@@ -253,21 +321,25 @@ function SliderComponent(props) {