{"version":3,"file":"slider.mjs","names":[],"sources":["../../../../package/common/slider/slider.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState, type ReactElement } from 'react';\nimport cx from 'classnames';\nimport styles from './slider.module.pcss';\nimport type { RangeSliderProps, SingleSliderProps, SliderProps } from './slider.types';\n\n/**\n * Every prop of both variants, optional. Destructuring the union directly cannot reach the\n * range-only props, and leaving them in `...rest` spreads them onto the DOM node.\n */\ntype AnySliderProps = Omit<SingleSliderProps, 'onChange' | 'onChangeEnd' | 'value' | 'defaultValue' | 'range'> &\n  Partial<Pick<RangeSliderProps, 'minRange' | 'thumbLabels'>> & {\n    range?: boolean;\n    value?: number | [number, number];\n    defaultValue?: number | [number, number];\n    onChange?: (value: never) => void;\n    onChangeEnd?: (value: never) => void;\n  };\n\n/** Count decimal places in a step so snapped values don't accumulate float error. */\nfunction decimalsOf(step: number): number {\n  const s = String(step);\n  const dot = s.indexOf('.');\n  return dot === -1 ? 0 : s.length - dot - 1;\n}\n\nexport default function Slider(props: SliderProps): ReactElement {\n  const {\n    min = 0,\n    max = 100,\n    step = 1,\n    disabled = false,\n    marks,\n    showValue = false,\n    formatValue,\n    size,\n    tabIndex = 0,\n    className,\n    style,\n    classNames,\n    styles: slotStyles,\n    'aria-label': ariaLabel,\n    range,\n    value,\n    defaultValue,\n    onChange,\n    onChangeEnd,\n    minRange = 0,\n    thumbLabels,\n    ...rest\n  } = props as AnySliderProps;\n\n  // The union guarantees each handler matches `range`, but that link does not survive\n  // destructuring, so the pair is widened once here rather than cast at every call.\n  const emitChange = onChange as ((v: number | [number, number]) => void) | undefined;\n  const emitEnd = onChangeEnd as ((v: number | [number, number]) => void) | undefined;\n\n  const isRange = range === true;\n\n  const trackRef = useRef<HTMLDivElement>(null);\n  const rootRef = useRef<HTMLDivElement>(null);\n  const thumbRefs = useRef<(HTMLDivElement | null)[]>([]);\n  const draggingIndexRef = useRef<number | null>(null);\n  const [dragging, setDragging] = useState(false);\n\n  const decimals = decimalsOf(step);\n\n  function clamp(v: number): number {\n    return Math.min(max, Math.max(min, v));\n  }\n\n  function snap(v: number): number {\n    const snapped = Math.round((v - min) / step) * step + min;\n    return Number(clamp(snapped).toFixed(decimals));\n  }\n\n  /** Both modes run on an array internally; only the edges of the component know the difference. */\n  function toValues(raw: number | [number, number] | undefined): number[] {\n    if (isRange) {\n      const pair = (raw as [number, number] | undefined) ?? [min, max];\n      return [clamp(pair[0]), clamp(pair[1])];\n    }\n    return [clamp((raw as number | undefined) ?? min)];\n  }\n\n  /** Reshapes a stored pair or single if `range` is flipped after mount. */\n  function fitToMode(values: number[]): number[] {\n    const expected = isRange ? 2 : 1;\n    if (values.length === expected) return values;\n    return isRange ? [values[0], max] : [values[0]];\n  }\n\n  const isControlled = value !== undefined;\n  const [internal, setInternal] = useState<number[]>(() => toValues(defaultValue));\n  const current = isControlled ? toValues(value) : fitToMode(internal);\n\n  // Latest committed values, for reading inside pointer handlers without stale closures. Synced\n  // after render (never written during render) and optimistically in commit() so a drag burst\n  // dedupes before the next render lands.\n  const currentRef = useRef(current);\n  useEffect(() => {\n    currentRef.current = current;\n  }, [current]);\n\n  function payloadOf(next: number[]): number | [number, number] {\n    return isRange ? [next[0], next[1]] : next[0];\n  }\n\n  /** How far this thumb may travel: the track, narrowed by its neighbour and `minRange`. */\n  function boundsFor(index: number, values: number[]): [number, number] {\n    if (!isRange) return [min, max];\n    return index === 0 ? [min, values[1] - minRange] : [values[0] + minRange, max];\n  }\n\n  function commit(index: number, raw: number, ended: boolean) {\n    const previous = currentRef.current;\n    const [lower, upper] = boundsFor(index, previous);\n\n    // Clamped after snapping, and rounded again: a bound derived from a neighbour plus `minRange`\n    // is off the step grid and carries float error (0.3 - 0.1 is 0.19999999999999998).\n    let snapped = snap(raw);\n    if (snapped < lower) snapped = Number(Math.max(min, lower).toFixed(decimals));\n    if (snapped > upper) snapped = Number(Math.min(max, upper).toFixed(decimals));\n\n    // A keyboard step that lands on the same value still ends a gesture, but nothing changed,\n    // so only onChangeEnd fires.\n    if (snapped === previous[index]) {\n      if (ended) emitEnd?.(payloadOf(previous));\n      return;\n    }\n\n    const next = [...previous];\n    next[index] = snapped;\n    currentRef.current = next;\n    if (!isControlled) setInternal(next);\n    emitChange?.(payloadOf(next));\n    if (ended) emitEnd?.(payloadOf(next));\n  }\n\n  function valueFromClientX(clientX: number): number {\n    const track = trackRef.current;\n    if (!track) return currentRef.current[0];\n    const rect = track.getBoundingClientRect();\n    const ratio = rect.width ? (clientX - rect.left) / rect.width : 0;\n    return min + Math.min(1, Math.max(0, ratio)) * (max - min);\n  }\n\n  /**\n   * The thumb closest to the press. On a tie the press direction decides, which is what keeps the\n   * upper thumb reachable when the two sit on the same value and every distance ties.\n   */\n  function nearestThumb(target: number): number {\n    if (!isRange) return 0;\n    const [lo, hi] = currentRef.current;\n    const toLo = Math.abs(target - lo);\n    const toHi = Math.abs(target - hi);\n    if (toLo === toHi) return target > hi ? 1 : 0;\n    return toLo < toHi ? 0 : 1;\n  }\n\n  function handlePointerDown(e: React.PointerEvent<HTMLDivElement>) {\n    if (disabled) return;\n    e.preventDefault();\n    const target = valueFromClientX(e.clientX);\n    const index = nearestThumb(target);\n\n    (isRange ? thumbRefs.current[index] : rootRef.current)?.focus();\n    rootRef.current?.setPointerCapture?.(e.pointerId);\n    draggingIndexRef.current = index;\n    setDragging(true);\n    commit(index, target, false);\n  }\n\n  function handlePointerMove(e: React.PointerEvent<HTMLDivElement>) {\n    const index = draggingIndexRef.current;\n    if (index === null) return;\n    commit(index, valueFromClientX(e.clientX), false);\n  }\n\n  function handlePointerUp(e: React.PointerEvent<HTMLDivElement>) {\n    if (draggingIndexRef.current === null) return;\n    draggingIndexRef.current = null;\n    setDragging(false);\n    rootRef.current?.releasePointerCapture?.(e.pointerId);\n    emitEnd?.(payloadOf(currentRef.current));\n  }\n\n  function handleKeyDown(e: React.KeyboardEvent<HTMLDivElement>, index: number) {\n    if (disabled) return;\n    const page = step * 10;\n    const from = current[index];\n    let next: number | null = null;\n\n    switch (e.key) {\n      case 'ArrowRight':\n      case 'ArrowUp':\n        next = from + step;\n        break;\n      case 'ArrowLeft':\n      case 'ArrowDown':\n        next = from - step;\n        break;\n      case 'PageUp':\n        next = from + page;\n        break;\n      case 'PageDown':\n        next = from - page;\n        break;\n      case 'Home':\n        next = min;\n        break;\n      case 'End':\n        next = max;\n        break;\n      default:\n        return;\n    }\n    e.preventDefault();\n    commit(index, next, true);\n  }\n\n  const fractionOf = (v: number) => (max > min ? (clamp(v) - min) / (max - min) : 0);\n  const percentOf = (v: number) => `${fractionOf(v) * 100}%`;\n\n  const rangeStyle = isRange\n    ? { left: percentOf(current[0]), width: `${(fractionOf(current[1]) - fractionOf(current[0])) * 100}%` }\n    : { width: percentOf(current[0]) };\n\n  const hasMarkLabels = !!marks?.some((m) => m.label != null);\n  const textOf = (v: number) => (formatValue ? formatValue(v) : v);\n\n  function thumbAriaProps(index: number) {\n    const v = current[index];\n    const text = textOf(v);\n    const [lower, upper] = boundsFor(index, current);\n    return {\n      role: 'slider' as const,\n      'aria-valuemin': Number(Math.max(min, lower).toFixed(decimals)),\n      'aria-valuemax': Number(Math.min(max, upper).toFixed(decimals)),\n      'aria-valuenow': v,\n      'aria-valuetext': typeof text === 'string' || typeof text === 'number' ? String(text) : undefined,\n      'aria-orientation': 'horizontal' as const,\n      'aria-disabled': disabled || undefined,\n    };\n  }\n\n  // A single-thumb slider is the slider; a two-thumb one is a group of two, each its own slider.\n  const rootAria = isRange\n    ? { role: 'group' as const, 'aria-label': ariaLabel, 'aria-disabled': disabled || undefined }\n    : { ...thumbAriaProps(0), 'aria-label': ariaLabel, tabIndex: disabled ? -1 : tabIndex };\n\n  return (\n    <div\n      {...rest}\n      {...rootAria}\n      ref={rootRef}\n      className={cx(\n        styles.slider,\n        disabled && styles.sliderDisabled,\n        dragging && styles.sliderDragging,\n        hasMarkLabels && styles.sliderHasMarkLabels,\n        className,\n        classNames?.root,\n      )}\n      style={\n        { ...(size ? { '--slider-thumb-size': size } : null), ...style, ...slotStyles?.root } as React.CSSProperties\n      }\n      onPointerDown={handlePointerDown}\n      onPointerMove={handlePointerMove}\n      onPointerUp={handlePointerUp}\n      onKeyDown={isRange ? undefined : (e) => handleKeyDown(e, 0)}\n    >\n      <div ref={trackRef} className={cx(styles.sliderTrack, classNames?.track)} style={slotStyles?.track}>\n        <div className={cx(styles.sliderRange, classNames?.range)} style={{ ...rangeStyle, ...slotStyles?.range }} />\n        {marks?.map((mark) => {\n          const left = percentOf(mark.value);\n          return (\n            <React.Fragment key={mark.value}>\n              <span className={cx(styles.sliderMark, classNames?.mark)} style={{ left, ...slotStyles?.mark }} />\n              {mark.label != null && (\n                <span\n                  className={cx(styles.sliderMarkLabel, classNames?.markLabel)}\n                  style={{ left, ...slotStyles?.markLabel }}\n                >\n                  {mark.label}\n                </span>\n              )}\n            </React.Fragment>\n          );\n        })}\n        {current.map((v, index) => (\n          <div\n            key={index}\n            ref={(el) => {\n              thumbRefs.current[index] = el;\n            }}\n            className={cx(styles.sliderThumb, classNames?.thumb)}\n            style={{ left: percentOf(v), ...slotStyles?.thumb }}\n            {...(isRange\n              ? {\n                  ...thumbAriaProps(index),\n                  'aria-label': thumbLabels?.[index] ?? (index === 0 ? 'From' : 'To'),\n                  tabIndex: disabled ? -1 : tabIndex,\n                  onKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => handleKeyDown(e, index),\n                }\n              : {})}\n          >\n            {showValue && (\n              <span className={cx(styles.sliderValue, classNames?.value)} style={slotStyles?.value}>\n                {textOf(v)}\n              </span>\n            )}\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n"],"mappings":"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"}