diff --git a/src/shared/ui/Slider/Slider.svelte b/src/shared/ui/Slider/Slider.svelte index 068e022..4cf79fd 100644 --- a/src/shared/ui/Slider/Slider.svelte +++ b/src/shared/ui/Slider/Slider.svelte @@ -87,7 +87,7 @@ const percent = $derived.by(() => { return Math.min(Math.max(((value - min) / (max - min)) * 100, 0), 100); }); -let trackEl: HTMLElement | undefined; +let trackEl: HTMLElement | undefined = $state(); let dragging = $state(false); /** @@ -101,6 +101,20 @@ function commit(raw: number): void { } } +/** + * Keep an externally-supplied value normalized to the step grid and range. + * Mirrors the bits-ui primitive's behavior so out-of-range or off-grid + * props don't desync the thumb position from aria-valuenow / the label. + * Converges in one pass: once snapped, the value equals its own snap. + */ +$effect(() => { + const normalized = snapToStep(value, { min, max, step }); + if (normalized !== value) { + value = normalized; + onValueChange?.(normalized); + } +}); + /** * Resolve a pointer event to a value using the live track rect. */ @@ -229,6 +243,7 @@ const thumbClasses = `block w-2.5 h-2.5 bg-brand aria-valuemin={min} aria-valuemax={max} aria-valuenow={value} + aria-valuetext={String(format(value))} aria-disabled={disabled ? 'true' : undefined} data-active={dragging ? '' : undefined} onkeydown={handleKeyDown} @@ -276,6 +291,7 @@ const thumbClasses = `block w-2.5 h-2.5 bg-brand aria-valuemin={min} aria-valuemax={max} aria-valuenow={value} + aria-valuetext={String(format(value))} aria-disabled={disabled ? 'true' : undefined} data-active={dragging ? '' : undefined} onkeydown={handleKeyDown}