import { clampNumber, roundToStepPrecision, } from '$shared/lib/utils'; /** * Geometry/range options shared by the math helpers. */ type SliderMathOpts = { /** * Minimum value (inclusive) */ min: number; /** * Maximum value (inclusive) */ max: number; /** * Step increment */ step: number; }; /** * Snap a raw value onto the step grid, then clamp to [min, max]. * * Snapping is anchored to `min` so non-zero ranges land on valid stops. * `roundToStepPrecision` removes IEEE-754 drift from fractional steps. */ export function snapToStep(raw: number, { min, max, step }: SliderMathOpts): number { if (step <= 0) { return clampNumber(raw, min, max); } const snapped = min + Math.round((raw - min) / step) * step; return clampNumber(roundToStepPrecision(snapped, step), min, max); } /** * Convert a pointer coordinate into a slider value. * * Horizontal maps left→min, right→max. Vertical is inverted so that * up→max, matching natural slider expectations. The DOMRect is passed in * to keep this pure and unit-testable without layout. */ export function pointerToValue( point: { clientX: number; clientY: number }, rect: DOMRect, opts: SliderMathOpts & { orientation: 'horizontal' | 'vertical' }, ): number { const { min, max, orientation } = opts; const size = orientation === 'vertical' ? rect.height : rect.width; if (size <= 0) { return snapToStep(min, opts); } const ratio = orientation === 'vertical' ? (rect.bottom - point.clientY) / size : (point.clientX - rect.left) / size; return snapToStep(min + clampNumber(ratio, 0, 1) * (max - min), opts); }