{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "slider",
  "type": "registry:ui",
  "registryDependencies": [],
  "dependencies": ["motion"],
  "devDependencies": [],
  "files": [
    {
      "path": "slider.tsx",
      "content": "\"use client\";\n\nimport {\n  animate,\n  type MotionValue,\n  motion,\n  useMotionValue,\n  useReducedMotion,\n  useTransform,\n} from \"motion/react\";\nimport {\n  forwardRef,\n  type KeyboardEvent as ReactKeyboardEvent,\n  type ReactNode,\n  type PointerEvent as ReactPointerEvent,\n  useCallback,\n  useEffect,\n  useId,\n  useRef,\n  useState,\n} from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst componentThemeClassName =\n  \"[--ic-background:#ffffff] [--ic-foreground:#111111] [--ic-primary:#111111] [--ic-secondary:#646b75] [--ic-surface-border:#e9edf2] [--ic-border:#e3e7ec] [--ic-card:#ffffff] [--ic-card-foreground:#111111] [--ic-muted:#f5f7fa] [--ic-muted-foreground:#6d7480] [--ic-accent:#f3f5f8] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--ic-accent-foreground:#111111] [--ic-input:#e3e7ec] [--ic-ring:rgba(17,17,17,0.16)] [--ic-destructive:#dc2626] [--ic-paper:#fcfcfd] [--ic-popover-foreground:#111111] [--ic-brand:#0ea5e9] [--ic-brand-soft:#bae6fd] [--ic-shadow-soft:0_18px_38px_-24px_rgba(15,23,42,0.35)] [--ic-chart-1:oklch(0.52_0.19_254)] [--ic-chart-2:oklch(0.74_0.11_232)] [--ic-chart-3:oklch(0.42_0.16_262)] [--ic-chart-4:oklch(0.84_0.07_228)] [--ic-chart-5:oklch(0.62_0.14_240)] [--color-background:var(--ic-background)] [--color-foreground:var(--ic-foreground)] [--color-primary:var(--ic-primary)] [--color-secondary:var(--ic-secondary)] [--color-border:var(--ic-border)] [--color-card:var(--ic-card)] [--color-card-foreground:var(--ic-card-foreground)] [--color-muted:var(--ic-muted)] [--color-muted-foreground:var(--ic-muted-foreground)] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--color-input:var(--ic-input)] [--color-ring:var(--ic-ring)] [--color-destructive:var(--ic-destructive)] [--color-paper:var(--ic-paper)] [--color-popover-foreground:var(--ic-popover-foreground)] [--color-brand:var(--ic-brand)] [--color-brand-soft:var(--ic-brand-soft)] [--color-chart-1:var(--ic-chart-1)] [--color-chart-2:var(--ic-chart-2)] [--color-chart-3:var(--ic-chart-3)] [--color-chart-4:var(--ic-chart-4)] [--color-chart-5:var(--ic-chart-5)] dark:[--ic-background:#111111] dark:[--ic-foreground:#f6f3ec] dark:[--ic-primary:#f6f3ec] dark:[--ic-secondary:#cbc6bb] dark:[--ic-surface-border:#2a2a25] dark:[--ic-border:#2b2a25] dark:[--ic-card:#111111] dark:[--ic-card-foreground:#f6f3ec] dark:[--ic-muted:#171716] dark:[--ic-muted-foreground:#9a958a] dark:[--ic-accent:#1a1a18] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] dark:[--ic-accent-foreground:#f6f3ec] dark:[--ic-input:#2b2a25] dark:[--ic-ring:rgba(246,243,236,0.18)] dark:[--ic-destructive:#f87171] dark:[--ic-paper:#171716] dark:[--ic-popover-foreground:#f6f3ec] dark:[--ic-brand:#38bdf8] dark:[--ic-brand-soft:#0c4a6e] dark:[--ic-shadow-soft:0_20px_44px_-28px_rgba(0,0,0,0.6)] dark:[--ic-chart-1:oklch(0.68_0.17_250)] dark:[--ic-chart-2:oklch(0.82_0.09_225)] dark:[--ic-chart-3:oklch(0.58_0.15_260)] dark:[--ic-chart-4:oklch(0.75_0.12_235)] dark:[--ic-chart-5:oklch(0.88_0.06_220)]\";\n\nexport interface SliderMark {\n  value: number;\n  label?: string;\n}\n\nexport type SliderSize = \"sm\" | \"md\" | \"lg\";\n\nexport interface SliderProps {\n  value?: number | [number, number];\n  defaultValue?: number | [number, number];\n  min?: number;\n  max?: number;\n  step?: number;\n  onChange?: (value: number | [number, number]) => void;\n  onValueCommit?: (value: number | [number, number]) => void;\n  showValue?: boolean;\n  valueDecimals?: number;\n  formatValue?: (value: number) => string;\n  label?: string;\n  ariaLabel?: string;\n  ariaLabelledBy?: string;\n  marks?: SliderMark[];\n  className?: string;\n  disabled?: boolean;\n  readOnly?: boolean;\n  description?: ReactNode;\n  errorMessage?: ReactNode;\n  invalid?: boolean;\n  ariaDescribedBy?: string;\n  marksInteractive?: boolean;\n  size?: SliderSize;\n  inverted?: boolean;\n  name?: string;\n  id?: string;\n  range?: boolean;\n}\n\nexport const sliderSizeClasses = {\n  sm: {\n    hitAreaHorizontal: \"h-9\",\n    thumb: \"h-3 w-3\",\n    thumbBorder: \"border\",\n    trackInactive: 3,\n    trackActive: 5,\n  },\n  md: {\n    hitAreaHorizontal: \"h-11\",\n    thumb: \"h-4 w-4\",\n    thumbBorder: \"border-[1.5px]\",\n    trackInactive: 4,\n    trackActive: 6,\n  },\n  lg: {\n    hitAreaHorizontal: \"h-14\",\n    thumb: \"h-5 w-5\",\n    thumbBorder: \"border-2\",\n    trackInactive: 5,\n    trackActive: 7,\n  },\n} as const satisfies Record<\n  SliderSize,\n  {\n    hitAreaHorizontal: string;\n    thumb: string;\n    thumbBorder: string;\n    trackInactive: number;\n    trackActive: number;\n  }\n>;\n\nfunction clamp(value: number, min: number, max: number) {\n  return Math.min(Math.max(value, min), max);\n}\n\nfunction getPrecision(value: number) {\n  if (!Number.isFinite(value)) {\n    return 0;\n  }\n\n  const normalized = value.toString().toLowerCase();\n\n  if (normalized.includes(\"e-\")) {\n    const [base, exponent] = normalized.split(\"e-\");\n    return (base.split(\".\")[1]?.length ?? 0) + Number(exponent);\n  }\n\n  return normalized.split(\".\")[1]?.length ?? 0;\n}\n\nfunction snapValue(raw: number, min: number, max: number, step: number) {\n  const safeStep = step > 0 ? step : 1;\n  const precision = Math.max(\n    getPrecision(safeStep),\n    getPrecision(min),\n    getPrecision(max)\n  );\n  const stepped = min + Math.round((raw - min) / safeStep) * safeStep;\n  return clamp(Number(stepped.toFixed(precision)), min, max);\n}\n\nfunction snapRangeValue(\n  raw: readonly [number, number] | number[],\n  min: number,\n  max: number,\n  step: number\n): [number, number] {\n  const first = snapValue(raw[0] ?? min, min, max, step);\n  const second = snapValue(raw[1] ?? max, min, max, step);\n  return first <= second ? [first, second] : [second, first];\n}\n\nfunction formatSliderDisplayValue(\n  value: number | [number, number],\n  options: {\n    formatValue?: (value: number) => string;\n    range?: boolean;\n    valueDecimals?: number;\n  }\n) {\n  const { formatValue, range = false, valueDecimals = 0 } = options;\n\n  if (range && Array.isArray(value)) {\n    const [low, high] = value;\n    const lowText = formatValue ? formatValue(low) : low.toFixed(valueDecimals);\n    const highText = formatValue\n      ? formatValue(high)\n      : high.toFixed(valueDecimals);\n    return `${lowText} – ${highText}`;\n  }\n\n  const numericValue = Array.isArray(value) ? (value[0] ?? 0) : value;\n  return formatValue\n    ? formatValue(numericValue)\n    : numericValue.toFixed(valueDecimals);\n}\n\nfunction resolveSliderAccessibleName({\n  ariaLabel,\n  ariaLabelledBy,\n  label,\n  labelId,\n}: {\n  ariaLabel?: string;\n  ariaLabelledBy?: string;\n  label?: string;\n  labelId?: string;\n}) {\n  if (ariaLabelledBy || label) {\n    return {\n      ariaLabel: undefined,\n      ariaLabelledBy: ariaLabelledBy ?? labelId,\n    };\n  }\n\n  return {\n    ariaLabel: ariaLabel ?? \"Slider\",\n    ariaLabelledBy: undefined,\n  };\n}\n\nfunction resolveSliderFieldMeta({\n  ariaDescribedByProp,\n  description,\n  errorMessage,\n  generatedId,\n  id,\n  invalid,\n}: {\n  ariaDescribedByProp?: string;\n  description?: ReactNode;\n  errorMessage?: ReactNode;\n  generatedId: string;\n  id?: string;\n  invalid?: boolean;\n}) {\n  const sliderId = id ?? generatedId;\n  const descriptionId = description ? `${sliderId}-description` : undefined;\n  const errorId = errorMessage ? `${sliderId}-error` : undefined;\n  const showInvalid = Boolean(invalid) || Boolean(errorMessage);\n\n  return {\n    describedBy: [ariaDescribedByProp, descriptionId, errorId]\n      .filter(Boolean)\n      .join(\" \"),\n    descriptionId,\n    errorId,\n    showInvalid,\n    sliderId,\n  };\n}\n\nfunction resolveBlurTarget(\n  currentTarget: EventTarget & HTMLDivElement,\n  relatedTarget: EventTarget | null\n) {\n  return !currentTarget.contains(relatedTarget as Node | null);\n}\n\nfunction isDocumentRtl() {\n  if (typeof document === \"undefined\") {\n    return false;\n  }\n\n  return document.documentElement.dir === \"rtl\";\n}\n\nfunction getPointerRatio(\n  clientX: number,\n  rect: DOMRect,\n  inverted: boolean,\n  isRtl: boolean\n) {\n  let ratio = rect.width === 0 ? 0 : (clientX - rect.left) / rect.width;\n\n  if (isRtl) {\n    ratio = 1 - ratio;\n  }\n\n  if (inverted) {\n    ratio = 1 - ratio;\n  }\n\n  return clamp(ratio, 0, 1);\n}\n\nfunction valueToProgress(\n  currentValue: number,\n  resolvedMin: number,\n  resolvedMax: number\n) {\n  const span = resolvedMax - resolvedMin;\n  return span === 0 ? 0 : ((currentValue - resolvedMin) / span) * 100;\n}\n\nfunction progressToValue(\n  progress: number,\n  resolvedMin: number,\n  resolvedMax: number,\n  safeStep: number\n) {\n  const span = resolvedMax - resolvedMin;\n  const raw = resolvedMin + (progress / 100) * span;\n  return snapValue(raw, resolvedMin, resolvedMax, safeStep);\n}\n\nfunction mergeRefs<T>(...refs: Array<React.Ref<T> | undefined>) {\n  return (node: T | null) => {\n    for (const ref of refs) {\n      if (!ref) {\n        continue;\n      }\n\n      if (typeof ref === \"function\") {\n        ref(node);\n      } else {\n        ref.current = node;\n      }\n    }\n  };\n}\n\nfunction resolveInitialSliderValue(\n  controlledValue: number | [number, number] | undefined,\n  defaultValue: number | [number, number] | undefined,\n  range: boolean,\n  resolvedMin: number,\n  resolvedMax: number,\n  safeStep: number,\n  rangeSpan: number\n) {\n  const initial = controlledValue ?? defaultValue ?? (range ? [25, 75] : 50);\n\n  if (range) {\n    const tuple = Array.isArray(initial)\n      ? initial\n      : [resolvedMin + rangeSpan * 0.25, resolvedMin + rangeSpan * 0.75];\n    return snapRangeValue(tuple, resolvedMin, resolvedMax, safeStep);\n  }\n\n  const numeric = Array.isArray(initial) ? (initial[0] ?? 50) : initial;\n  return snapValue(numeric, resolvedMin, resolvedMax, safeStep);\n}\n\nfunction resolveControlledSliderValue(\n  controlledValue: number | [number, number] | undefined,\n  internalValue: number | [number, number],\n  range: boolean,\n  resolvedMin: number,\n  resolvedMax: number,\n  safeStep: number\n) {\n  if (controlledValue === undefined) {\n    return internalValue;\n  }\n\n  if (range) {\n    return snapRangeValue(\n      Array.isArray(controlledValue)\n        ? controlledValue\n        : [controlledValue, controlledValue],\n      resolvedMin,\n      resolvedMax,\n      safeStep\n    );\n  }\n\n  return snapValue(\n    Array.isArray(controlledValue)\n      ? (controlledValue[0] ?? resolvedMin)\n      : controlledValue,\n    resolvedMin,\n    resolvedMax,\n    safeStep\n  );\n}\n\nfunction normalizeSliderNextValue(\n  nextValue: number | [number, number],\n  range: boolean,\n  resolvedMin: number,\n  resolvedMax: number,\n  safeStep: number\n) {\n  if (range) {\n    return snapRangeValue(\n      Array.isArray(nextValue) ? nextValue : [nextValue, nextValue],\n      resolvedMin,\n      resolvedMax,\n      safeStep\n    );\n  }\n\n  return snapValue(\n    Array.isArray(nextValue) ? (nextValue[0] ?? resolvedMin) : nextValue,\n    resolvedMin,\n    resolvedMax,\n    safeStep\n  );\n}\n\nfunction sliderValuesEqual(\n  current: number | [number, number],\n  normalized: number | [number, number],\n  range: boolean\n) {\n  if (range) {\n    const currentTuple = Array.isArray(current) ? current : [current, current];\n    const nextTuple = normalized as [number, number];\n    return currentTuple[0] === nextTuple[0] && currentTuple[1] === nextTuple[1];\n  }\n\n  return current === normalized;\n}\n\ntype SliderKeyboardAction =\n  | { type: \"step\"; delta: number }\n  | { type: \"min\" }\n  | { type: \"max\" };\n\nfunction getSliderKeyboardAction(\n  key: string,\n  context: {\n    inverted: boolean;\n    isRtl: boolean;\n    rangeSpan: number;\n    safeStep: number;\n  }\n): SliderKeyboardAction | null {\n  const { inverted, isRtl, rangeSpan, safeStep } = context;\n  const pageStep =\n    rangeSpan === 0 ? safeStep : Math.max(safeStep, rangeSpan / 10);\n  const increaseOnRightUp = isRtl === inverted;\n  const increaseKeys = new Set([\"ArrowRight\", \"ArrowUp\"]);\n  const decreaseKeys = new Set([\"ArrowLeft\", \"ArrowDown\"]);\n\n  if (\n    (increaseOnRightUp && increaseKeys.has(key)) ||\n    (!increaseOnRightUp && decreaseKeys.has(key))\n  ) {\n    return { type: \"step\", delta: safeStep };\n  }\n\n  if (\n    (increaseOnRightUp && decreaseKeys.has(key)) ||\n    (!increaseOnRightUp && increaseKeys.has(key))\n  ) {\n    return { type: \"step\", delta: -safeStep };\n  }\n\n  if (key === \"PageUp\") {\n    return { type: \"step\", delta: pageStep };\n  }\n\n  if (key === \"PageDown\") {\n    return { type: \"step\", delta: -pageStep };\n  }\n\n  if (key === \"Home\") {\n    return { type: \"min\" };\n  }\n\n  if (key === \"End\") {\n    return { type: \"max\" };\n  }\n\n  return null;\n}\n\nfunction applySliderKeyboardAction(\n  action: SliderKeyboardAction,\n  context: {\n    activeThumbIndex: number;\n    latestValue: number | [number, number];\n    range: boolean;\n    resolvedMax: number;\n    resolvedMin: number;\n    updateRangeThumb: (thumbIndex: number, nextThumbValue: number) => boolean;\n    updateValue: (nextValue: number | [number, number]) => boolean;\n  }\n) {\n  const {\n    activeThumbIndex,\n    latestValue,\n    range,\n    resolvedMax,\n    resolvedMin,\n    updateRangeThumb,\n    updateValue,\n  } = context;\n\n  if (action.type === \"min\") {\n    return range\n      ? updateRangeThumb(activeThumbIndex, resolvedMin)\n      : updateValue(resolvedMin);\n  }\n\n  if (action.type === \"max\") {\n    return range\n      ? updateRangeThumb(activeThumbIndex, resolvedMax)\n      : updateValue(resolvedMax);\n  }\n\n  const current = range\n    ? (latestValue as [number, number])[activeThumbIndex]\n    : (latestValue as number);\n\n  return range\n    ? updateRangeThumb(activeThumbIndex, current + action.delta)\n    : updateValue(current + action.delta);\n}\n\nfunction SliderVisualShell({\n  active,\n  activeThumbIndex,\n  fillLeftPercent,\n  fillSizePercent,\n  isDragging,\n  isHovered,\n  isRange,\n  sizeClasses,\n  thumbPercents,\n}: {\n  active: boolean;\n  activeThumbIndex: number;\n  fillLeftPercent: MotionValue<string>;\n  fillSizePercent: MotionValue<string>;\n  isDragging: boolean;\n  isHovered: boolean;\n  isRange: boolean;\n  sizeClasses: (typeof sliderSizeClasses)[SliderSize];\n  thumbPercents: MotionValue<string>[];\n}) {\n  const trackInactive = sizeClasses.trackInactive;\n  const trackActive = sizeClasses.trackActive;\n  const trackSize = active ? trackActive : trackInactive;\n  const springTransition = {\n    type: \"spring\" as const,\n    stiffness: 200,\n    damping: 28,\n    mass: 0.8,\n  };\n  const thumbSpringTransition = {\n    type: \"spring\" as const,\n    stiffness: 220,\n    damping: 22,\n    mass: 0.7,\n  };\n\n  return (\n    <div aria-hidden className=\"pointer-events-none absolute inset-0\">\n      <motion.div\n        animate={{ height: trackSize }}\n        className=\"absolute top-1/2 right-0 left-0 -translate-y-1/2 overflow-hidden rounded-full bg-foreground/15\"\n        transition={springTransition}\n      />\n\n      <motion.div\n        animate={{ height: trackSize }}\n        className=\"absolute top-1/2 left-0 origin-left -translate-y-1/2 overflow-hidden rounded-full bg-foreground\"\n        style={{ left: fillLeftPercent, width: fillSizePercent }}\n        transition={springTransition}\n      />\n\n      {thumbPercents.map((thumbPercent, index) => {\n        const isActiveThumb = isDragging && activeThumbIndex === index;\n\n        return (\n          <motion.div\n            animate={{\n              scale: isActiveThumb ? 1.15 : isHovered ? 1.08 : 1,\n            }}\n            className=\"absolute top-1/2 -translate-x-1/2 -translate-y-1/2\"\n            key={isRange ? `thumb-${index}` : \"thumb\"}\n            style={{ left: thumbPercent }}\n            transition={thumbSpringTransition}\n          >\n            <div\n              className={cn(\n                \"relative rounded-full border-foreground bg-background shadow-sm\",\n                sizeClasses.thumb,\n                sizeClasses.thumbBorder\n              )}\n            />\n          </motion.div>\n        );\n      })}\n    </div>\n  );\n}\n\nfunction SliderMarks({\n  disabled,\n  inverted,\n  isRtl,\n  marks,\n  marksInteractive,\n  onMarkActivate,\n  rangeSpan,\n  readOnly,\n  resolvedMin,\n}: {\n  disabled?: boolean;\n  inverted: boolean;\n  isRtl: boolean;\n  marks: SliderMark[];\n  marksInteractive?: boolean;\n  onMarkActivate: (value: number) => void;\n  rangeSpan: number;\n  readOnly?: boolean;\n  resolvedMin: number;\n}) {\n  if (marks.length === 0) {\n    return null;\n  }\n\n  const hasMarkLabels = marks.some((mark) => mark.label);\n  const interactive = marksInteractive && !disabled && !readOnly;\n\n  return (\n    <div\n      aria-hidden={!interactive}\n      className={cn(\n        \"relative mt-2\",\n        hasMarkLabels ? \"h-8\" : \"h-3\",\n        interactive ? \"\" : \"pointer-events-none\"\n      )}\n    >\n      {marks.map((mark) => {\n        let progress =\n          rangeSpan === 0 ? 0 : ((mark.value - resolvedMin) / rangeSpan) * 100;\n\n        if (isRtl) {\n          progress = 100 - progress;\n        }\n\n        if (inverted) {\n          progress = 100 - progress;\n        }\n\n        const positionStyle = { left: `${progress}%` };\n\n        const content = (\n          <>\n            <span className=\"mx-auto block h-2 w-px rounded-full bg-foreground/25\" />\n            {mark.label ? (\n              <span className=\"mt-1 block whitespace-nowrap font-medium text-[11px] text-muted-foreground\">\n                {mark.label}\n              </span>\n            ) : null}\n          </>\n        );\n\n        if (interactive) {\n          return (\n            <button\n              className=\"absolute top-0 -translate-x-1/2 text-center outline-none focus-visible:outline-2 focus-visible:outline-foreground/35 focus-visible:outline-offset-2\"\n              key={`${mark.value}-${mark.label ?? \"tick\"}`}\n              onClick={() => {\n                onMarkActivate(mark.value);\n              }}\n              style={positionStyle}\n              type=\"button\"\n            >\n              {content}\n            </button>\n          );\n        }\n\n        return (\n          <div\n            className=\"absolute top-0 -translate-x-1/2 text-center\"\n            key={`${mark.value}-${mark.label ?? \"tick\"}`}\n            style={positionStyle}\n          >\n            {content}\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n\nfunction SliderFieldMessages({\n  description,\n  descriptionId,\n  errorId,\n  errorMessage,\n}: {\n  description?: ReactNode;\n  descriptionId?: string;\n  errorId?: string;\n  errorMessage?: ReactNode;\n}) {\n  if (!(description || errorMessage)) {\n    return null;\n  }\n\n  return (\n    <div className=\"mt-2 space-y-1\">\n      {description ? (\n        <p\n          className=\"text-muted-foreground text-xs leading-snug\"\n          id={descriptionId}\n        >\n          {description}\n        </p>\n      ) : null}\n      {errorMessage ? (\n        <p\n          aria-live=\"polite\"\n          className=\"text-destructive text-xs leading-snug\"\n          id={errorId}\n          role=\"alert\"\n        >\n          {errorMessage}\n        </p>\n      ) : null}\n    </div>\n  );\n}\n\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: slider shell coordinates motion, range, pointer, and keyboard state in one place.\nexport const Slider = forwardRef<HTMLDivElement, SliderProps>(function Slider(\n  {\n    value: controlledValue,\n    defaultValue,\n    min = 0,\n    max = 100,\n    step = 1,\n    onChange,\n    onValueCommit,\n    showValue = true,\n    valueDecimals = 0,\n    formatValue,\n    label,\n    ariaLabel,\n    ariaLabelledBy,\n    marks,\n    className,\n    disabled = false,\n    readOnly = false,\n    description,\n    errorMessage,\n    invalid = false,\n    ariaDescribedBy,\n    marksInteractive = false,\n    size = \"md\",\n    inverted = false,\n    name,\n    id,\n    range = false,\n  },\n  ref\n) {\n  const generatedId = useId();\n  const labelId = useId();\n  const trackRef = useRef<HTMLDivElement>(null);\n  const fieldRef = useRef<HTMLDivElement>(null);\n  const pointerIdRef = useRef<number | null>(null);\n  const latestValueRef = useRef<number | [number, number]>(\n    range ? [25, 75] : 50\n  );\n  const didChangeDuringInteractionRef = useRef(false);\n  const activeThumbIndexRef = useRef(0);\n  const resolvedMin = Math.min(min, max);\n  const resolvedMax = Math.max(min, max);\n  const safeStep = step > 0 ? step : 1;\n  const safeValueDecimals = Math.max(0, valueDecimals);\n  const rangeSpan = resolvedMax - resolvedMin;\n  const sizeClasses = sliderSizeClasses[size];\n  const prefersReducedMotion = useReducedMotion() === true;\n  const [isRtl, setIsRtl] = useState(false);\n  const [internalValue, setInternalValue] = useState<number | [number, number]>(\n    () =>\n      resolveInitialSliderValue(\n        controlledValue,\n        defaultValue,\n        range,\n        resolvedMin,\n        resolvedMax,\n        safeStep,\n        rangeSpan\n      )\n  );\n  const [isDragging, setIsDragging] = useState(false);\n  const [isHovered, setIsHovered] = useState(false);\n  const [isFocused, setIsFocused] = useState(false);\n  const [activeThumbIndex, setActiveThumbIndex] = useState(0);\n\n  const fieldMeta = resolveSliderFieldMeta({\n    ariaDescribedByProp: ariaDescribedBy,\n    description,\n    errorMessage,\n    generatedId,\n    id,\n    invalid,\n  });\n  const accessibleName = resolveSliderAccessibleName({\n    ariaLabel,\n    ariaLabelledBy,\n    label,\n    labelId,\n  });\n\n  useEffect(() => {\n    setIsRtl(isDocumentRtl());\n  }, []);\n\n  useEffect(() => {\n    if (controlledValue !== undefined) {\n      return;\n    }\n\n    setInternalValue((current) => {\n      if (range) {\n        const tuple = Array.isArray(current)\n          ? current\n          : [resolvedMin, resolvedMax];\n        const next = snapRangeValue(tuple, resolvedMin, resolvedMax, safeStep);\n        return next[0] === tuple[0] && next[1] === tuple[1] ? current : next;\n      }\n\n      const numeric = Array.isArray(current)\n        ? (current[0] ?? resolvedMin)\n        : current;\n      const next = snapValue(numeric, resolvedMin, resolvedMax, safeStep);\n      return current === next ? current : next;\n    });\n  }, [controlledValue, range, resolvedMin, resolvedMax, safeStep]);\n\n  const value = resolveControlledSliderValue(\n    controlledValue,\n    internalValue,\n    range,\n    resolvedMin,\n    resolvedMax,\n    safeStep\n  );\n\n  const values: [number, number] = range\n    ? (value as [number, number])\n    : [value as number, value as number];\n  const [lowValue, highValue] = values;\n  const activeValue = range ? values[activeThumbIndex] : (value as number);\n  const lowProgress = valueToProgress(lowValue, resolvedMin, resolvedMax);\n  const highProgress = valueToProgress(highValue, resolvedMin, resolvedMax);\n  const singleProgress = range\n    ? lowProgress\n    : valueToProgress(value as number, resolvedMin, resolvedMax);\n  const fillStartProgress = range ? lowProgress : 0;\n  const fillEndProgress = range ? highProgress : singleProgress;\n\n  const lowProgressMV = useMotionValue(lowProgress);\n  const highProgressMV = useMotionValue(highProgress);\n  const singleProgressMV = useMotionValue(singleProgress);\n  const fillStartMV = useMotionValue(fillStartProgress);\n  const fillEndMV = useMotionValue(fillEndProgress);\n\n  const lowThumbPercent = useTransform(\n    lowProgressMV,\n    (progress) => `${progress}%`\n  );\n  const highThumbPercent = useTransform(\n    highProgressMV,\n    (progress) => `${progress}%`\n  );\n  const singleThumbPercent = useTransform(\n    singleProgressMV,\n    (progress) => `${progress}%`\n  );\n  const thumbPercents = range\n    ? [lowThumbPercent, highThumbPercent]\n    : [singleThumbPercent];\n\n  const fillLeftPercent = useTransform(\n    fillStartMV,\n    (progress) => `${progress}%`\n  );\n  const fillSizePercent = useTransform(\n    [fillStartMV, fillEndMV],\n    ([start, end]: number[]) => `${Math.max(0, end - start)}%`\n  );\n\n  const singleDisplayValue = useTransform(singleProgressMV, (progress) => {\n    const resolved = progressToValue(\n      progress,\n      resolvedMin,\n      resolvedMax,\n      safeStep\n    );\n\n    return formatSliderDisplayValue(resolved, {\n      formatValue,\n      valueDecimals: safeValueDecimals,\n    });\n  });\n  const rangeDisplayValue = useTransform(\n    [lowProgressMV, highProgressMV],\n    ([low, high]: number[]) => {\n      const lowResolved = progressToValue(\n        low,\n        resolvedMin,\n        resolvedMax,\n        safeStep\n      );\n      const highResolved = progressToValue(\n        high,\n        resolvedMin,\n        resolvedMax,\n        safeStep\n      );\n\n      return formatSliderDisplayValue([lowResolved, highResolved], {\n        formatValue,\n        range: true,\n        valueDecimals: safeValueDecimals,\n      });\n    }\n  );\n\n  const progressSpring = prefersReducedMotion\n    ? { duration: 0 }\n    : {\n        type: \"spring\" as const,\n        stiffness: 180,\n        damping: 26,\n        mass: 0.9,\n        restDelta: 0.001,\n      };\n\n  useEffect(() => {\n    latestValueRef.current = value;\n  }, [value]);\n\n  useEffect(() => {\n    activeThumbIndexRef.current = activeThumbIndex;\n  }, [activeThumbIndex]);\n\n  useEffect(() => {\n    const controls = [\n      animate(lowProgressMV, lowProgress, progressSpring),\n      animate(highProgressMV, highProgress, progressSpring),\n      animate(singleProgressMV, singleProgress, progressSpring),\n      animate(fillStartMV, fillStartProgress, progressSpring),\n      animate(fillEndMV, fillEndProgress, progressSpring),\n    ];\n\n    return () => {\n      for (const control of controls) {\n        control.stop();\n      }\n    };\n  }, [\n    fillEndProgress,\n    fillStartProgress,\n    highProgress,\n    highProgressMV,\n    lowProgress,\n    lowProgressMV,\n    progressSpring,\n    singleProgress,\n    singleProgressMV,\n    fillEndMV,\n    fillStartMV,\n  ]);\n\n  const formattedValueText = formatSliderDisplayValue(value, {\n    formatValue,\n    range,\n    valueDecimals: safeValueDecimals,\n  });\n\n  const updateValue = useCallback(\n    (nextValue: number | [number, number]) => {\n      if (disabled || readOnly) {\n        return false;\n      }\n\n      const normalized = normalizeSliderNextValue(\n        nextValue,\n        range,\n        resolvedMin,\n        resolvedMax,\n        safeStep\n      );\n\n      if (sliderValuesEqual(latestValueRef.current, normalized, range)) {\n        return false;\n      }\n\n      latestValueRef.current = normalized;\n\n      if (controlledValue === undefined) {\n        setInternalValue(normalized);\n      }\n\n      onChange?.(normalized);\n      return true;\n    },\n    [\n      controlledValue,\n      disabled,\n      onChange,\n      range,\n      readOnly,\n      resolvedMax,\n      resolvedMin,\n      safeStep,\n    ]\n  );\n\n  const updateRangeThumb = useCallback(\n    (thumbIndex: number, nextThumbValue: number) => {\n      const current = latestValueRef.current;\n      const tuple = Array.isArray(current) ? [...current] : [current, current];\n      tuple[thumbIndex] = snapValue(\n        nextThumbValue,\n        resolvedMin,\n        resolvedMax,\n        safeStep\n      );\n\n      if (tuple[0] > tuple[1]) {\n        if (thumbIndex === 0) {\n          tuple[0] = tuple[1];\n        } else {\n          tuple[1] = tuple[0];\n        }\n      }\n\n      return updateValue(tuple as [number, number]);\n    },\n    [resolvedMax, resolvedMin, safeStep, updateValue]\n  );\n\n  const updateFromPointer = useCallback(\n    (clientX: number, _clientY: number, thumbIndex?: number) => {\n      const track = trackRef.current;\n      if (!track) {\n        return false;\n      }\n\n      const rect = track.getBoundingClientRect();\n      const ratio = getPointerRatio(clientX, rect, inverted, isRtl);\n      const raw = resolvedMin + ratio * rangeSpan;\n      const nextValue = snapValue(raw, resolvedMin, resolvedMax, safeStep);\n\n      if (range) {\n        const index =\n          thumbIndex ??\n          (() => {\n            const current = latestValueRef.current;\n            const tuple = Array.isArray(current) ? current : [current, current];\n            const lowDistance = Math.abs(tuple[0] - nextValue);\n            const highDistance = Math.abs(tuple[1] - nextValue);\n            return lowDistance <= highDistance ? 0 : 1;\n          })();\n\n        setActiveThumbIndex(index);\n        activeThumbIndexRef.current = index;\n        return updateRangeThumb(index, nextValue);\n      }\n\n      return updateValue(nextValue);\n    },\n    [\n      inverted,\n      isRtl,\n      range,\n      rangeSpan,\n      resolvedMax,\n      resolvedMin,\n      safeStep,\n      updateRangeThumb,\n      updateValue,\n    ]\n  );\n\n  const handlePointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {\n    if (disabled || readOnly) {\n      return;\n    }\n\n    event.currentTarget.focus();\n    event.currentTarget.setPointerCapture(event.pointerId);\n    pointerIdRef.current = event.pointerId;\n    didChangeDuringInteractionRef.current = false;\n    setIsDragging(true);\n    didChangeDuringInteractionRef.current = updateFromPointer(\n      event.clientX,\n      event.clientY\n    );\n  };\n\n  const handlePointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {\n    if (\n      disabled ||\n      readOnly ||\n      !isDragging ||\n      pointerIdRef.current !== event.pointerId\n    ) {\n      return;\n    }\n\n    didChangeDuringInteractionRef.current =\n      updateFromPointer(\n        event.clientX,\n        event.clientY,\n        range ? activeThumbIndexRef.current : undefined\n      ) || didChangeDuringInteractionRef.current;\n  };\n\n  const finishPointerInteraction = (\n    event: ReactPointerEvent<HTMLDivElement>\n  ) => {\n    if (pointerIdRef.current !== event.pointerId) {\n      return;\n    }\n\n    if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n      event.currentTarget.releasePointerCapture(event.pointerId);\n    }\n\n    pointerIdRef.current = null;\n    setIsDragging(false);\n\n    if (didChangeDuringInteractionRef.current) {\n      onValueCommit?.(latestValueRef.current);\n      didChangeDuringInteractionRef.current = false;\n    }\n  };\n\n  const handleKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>) => {\n    if (disabled || readOnly) {\n      return;\n    }\n\n    const action = getSliderKeyboardAction(event.key, {\n      inverted,\n      isRtl,\n      rangeSpan,\n      safeStep,\n    });\n\n    if (!action) {\n      return;\n    }\n\n    event.preventDefault();\n\n    const changed = applySliderKeyboardAction(action, {\n      activeThumbIndex: activeThumbIndexRef.current,\n      latestValue: latestValueRef.current,\n      range,\n      resolvedMax,\n      resolvedMin,\n      updateRangeThumb,\n      updateValue,\n    });\n\n    if (changed) {\n      onValueCommit?.(latestValueRef.current);\n    }\n  };\n\n  const handleMarkActivate = useCallback(\n    (markValue: number) => {\n      if (disabled || readOnly) {\n        return;\n      }\n\n      if (range) {\n        const current = latestValueRef.current;\n        const tuple = Array.isArray(current) ? current : [current, current];\n        const lowDistance = Math.abs(tuple[0] - markValue);\n        const highDistance = Math.abs(tuple[1] - markValue);\n        const thumbIndex = lowDistance <= highDistance ? 0 : 1;\n        setActiveThumbIndex(thumbIndex);\n        activeThumbIndexRef.current = thumbIndex;\n\n        if (updateRangeThumb(thumbIndex, markValue)) {\n          onValueCommit?.(latestValueRef.current);\n        }\n\n        return;\n      }\n\n      if (updateValue(markValue)) {\n        onValueCommit?.(latestValueRef.current);\n      }\n    },\n    [disabled, onValueCommit, range, readOnly, updateRangeThumb, updateValue]\n  );\n\n  const normalizedMarks = (marks ?? [])\n    .map((mark) => ({\n      ...mark,\n      value: clamp(mark.value, resolvedMin, resolvedMax),\n    }))\n    .sort((firstMark, secondMark) => firstMark.value - secondMark.value);\n  const active = isDragging || isHovered || isFocused;\n\n  return (\n    <div\n      className={cn(\n        componentThemeClassName,\n        \"w-full select-none\",\n        disabled && \"cursor-not-allowed opacity-50\",\n        className\n      )}\n      ref={fieldRef}\n    >\n      {(label || showValue) && (\n        <div className=\"mb-3 flex items-baseline justify-between\">\n          {label ? (\n            <span\n              className=\"font-medium text-muted-foreground text-sm tracking-wide\"\n              id={labelId}\n            >\n              {label}\n            </span>\n          ) : null}\n          {showValue ? (\n            <motion.span className=\"font-semibold text-foreground text-sm tabular-nums\">\n              {range ? rangeDisplayValue : singleDisplayValue}\n            </motion.span>\n          ) : null}\n        </div>\n      )}\n\n      <div\n        className=\"relative\"\n        onBlur={(event) => {\n          if (resolveBlurTarget(event.currentTarget, event.relatedTarget)) {\n            setIsFocused(false);\n          }\n        }}\n        onFocus={() => {\n          setIsFocused(true);\n        }}\n      >\n        <div\n          aria-describedby={fieldMeta.describedBy || undefined}\n          aria-disabled={disabled || undefined}\n          aria-invalid={fieldMeta.showInvalid || undefined}\n          aria-label={accessibleName.ariaLabel}\n          aria-labelledby={accessibleName.ariaLabelledBy}\n          aria-orientation=\"horizontal\"\n          aria-readonly={readOnly || undefined}\n          aria-valuemax={resolvedMax}\n          aria-valuemin={resolvedMin}\n          aria-valuenow={activeValue}\n          aria-valuetext={formattedValueText}\n          className={cn(\n            \"relative flex w-full cursor-pointer items-center rounded-full outline-none focus-visible:outline-2 focus-visible:outline-foreground/35 focus-visible:outline-offset-4\",\n            sizeClasses.hitAreaHorizontal,\n            disabled && \"cursor-not-allowed\"\n          )}\n          id={fieldMeta.sliderId}\n          onKeyDown={handleKeyDown}\n          onMouseEnter={() => {\n            setIsHovered(true);\n          }}\n          onMouseLeave={() => {\n            setIsHovered(false);\n          }}\n          onPointerCancel={finishPointerInteraction}\n          onPointerDown={handlePointerDown}\n          onPointerMove={handlePointerMove}\n          onPointerUp={finishPointerInteraction}\n          ref={mergeRefs(ref, trackRef)}\n          role=\"slider\"\n          style={{ touchAction: \"pan-y\" }}\n          tabIndex={disabled ? -1 : 0}\n        >\n          <SliderVisualShell\n            active={active}\n            activeThumbIndex={activeThumbIndex}\n            fillLeftPercent={fillLeftPercent}\n            fillSizePercent={fillSizePercent}\n            isDragging={isDragging}\n            isHovered={isHovered}\n            isRange={range}\n            sizeClasses={sizeClasses}\n            thumbPercents={thumbPercents}\n          />\n        </div>\n\n        {name ? (\n          range ? (\n            <>\n              <input name={`${name}[0]`} type=\"hidden\" value={lowValue} />\n              <input name={`${name}[1]`} type=\"hidden\" value={highValue} />\n            </>\n          ) : (\n            <input name={name} type=\"hidden\" value={value as number} />\n          )\n        ) : null}\n\n        <SliderMarks\n          disabled={disabled}\n          inverted={inverted}\n          isRtl={isRtl}\n          marks={normalizedMarks}\n          marksInteractive={marksInteractive}\n          onMarkActivate={handleMarkActivate}\n          rangeSpan={rangeSpan}\n          readOnly={readOnly}\n          resolvedMin={resolvedMin}\n        />\n      </div>\n\n      <SliderFieldMessages\n        description={description}\n        descriptionId={fieldMeta.descriptionId}\n        errorId={fieldMeta.errorId}\n        errorMessage={errorMessage}\n      />\n    </div>\n  );\n});\n\nexport { Slider as slider };\n",
      "type": "registry:ui"
    }
  ],
  "title": "Slider",
  "description": "Range control with spring-animated track and thumb, full-width pointer drag, and optional label and live value. Motion."
}
