diff --git a/packages/core/src/core/ui/slider/slider-core.ts b/packages/core/src/core/ui/slider/slider-core.ts index 95a9acf1..cc81afbc 100644 --- a/packages/core/src/core/ui/slider/slider-core.ts +++ b/packages/core/src/core/ui/slider/slider-core.ts @@ -155,6 +155,12 @@ export class SliderCore { return roundToStep(clamp(raw, min, max), step, min); } + /** Convert percent to a clamped value without applying step rounding. */ + rawValueFromPercent(percent: number): number { + const { min, max } = this.#props; + return clamp(min + (percent / 100) * (max - min), min, max); + } + percentFromValue(value: number): number { const { min, max } = this.#props; if (max === min) return 0; diff --git a/packages/core/src/core/ui/slider/tests/slider-core.test.ts b/packages/core/src/core/ui/slider/tests/slider-core.test.ts index 8ce0c7ae..262b92a3 100644 --- a/packages/core/src/core/ui/slider/tests/slider-core.test.ts +++ b/packages/core/src/core/ui/slider/tests/slider-core.test.ts @@ -203,6 +203,25 @@ describe('SliderCore', () => { }); }); + describe('rawValueFromPercent', () => { + it('converts percent to value without step rounding', () => { + const core = new SliderCore({ step: 5 }); + expect(core.rawValueFromPercent(53)).toBe(53); + expect(core.rawValueFromPercent(47)).toBe(47); + }); + + it('clamps to range', () => { + const core = new SliderCore(); + expect(core.rawValueFromPercent(-10)).toBe(0); + expect(core.rawValueFromPercent(110)).toBe(100); + }); + + it('respects custom min/max', () => { + const core = new SliderCore({ min: 10, max: 20 }); + expect(core.rawValueFromPercent(50)).toBe(15); + }); + }); + describe('percentFromValue', () => { it('returns 0 at min', () => { const core = new SliderCore(); diff --git a/packages/html/src/ui/time-slider/time-slider-element.ts b/packages/html/src/ui/time-slider/time-slider-element.ts index d75d6247..93122b22 100644 --- a/packages/html/src/ui/time-slider/time-slider-element.ts +++ b/packages/html/src/ui/time-slider/time-slider-element.ts @@ -70,7 +70,7 @@ export class TimeSliderElement extends MediaElement { getLargeStepPercent: () => this.#core.getLargeStepPercent(), onValueCommit: (percent) => { const media = this.#timeState.value; - if (media) media.seek(this.#core.valueFromPercent(percent)); + if (media) media.seek(this.#core.rawValueFromPercent(percent)); }, commitThrottle: this.commitThrottle, onDragStart: () => { diff --git a/packages/react/src/ui/time-slider/time-slider-root.tsx b/packages/react/src/ui/time-slider/time-slider-root.tsx index 16614ebe..50a15f04 100644 --- a/packages/react/src/ui/time-slider/time-slider-root.tsx +++ b/packages/react/src/ui/time-slider/time-slider-root.tsx @@ -77,7 +77,7 @@ export const TimeSliderRoot = forwardRef( getCSSVars: getTimeSliderCSSVars, onValueCommit: (percent) => { const media = mediaRef.current; - if (media) media.seek(core.valueFromPercent(percent)); + if (media) media.seek(core.rawValueFromPercent(percent)); }, onDragStart, onDragEnd,