{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "elastic-slider",
  "title": "ElasticSlider",
  "description": "Slider with elastic rubber-band drag and magnetic snap (by @iamncdai, MIT).",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.neon.com/r/neon-tokens.json"
  ],
  "files": [
    {
      "path": "src/components/ui/elastic-slider.tsx",
      "content": "/* oxlint-disable func-style, no-empty-function, no-shadow, no-use-before-define, react/react-compiler, max-lines-per-function, max-lines, no-magic-numbers -- vendored from chanhdai.com/r/elastic-slider.json (MIT, @iamncdai); kept diffable against upstream */\nimport {\n  useCallback,\n  useEffect,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from \"react\"\nimport {\n  animate,\n  domAnimation,\n  LazyMotion,\n  m,\n  useMotionValue,\n  useReducedMotion,\n  useTransform,\n} from \"motion/react\"\n\n// LazyMotion + `m` instead of `motion` to avoid shipping the full animation\n// graph (~30kb). (Neon UI patch on the vendored source.)\n\nimport { cn } from \"@/lib/utils\"\nimport { useControllableState } from \"@/hooks/use-controllable-state\"\n\n// Drag detection & rubber band\nconst CLICK_THRESHOLD = 3\nconst DEAD_ZONE = 32\nconst MAX_CURSOR_RANGE = 200\nconst MAX_STRETCH = 8\n\n// Layout offsets used by the \"handle dodges label/value\" calculation.\nconst HANDLE_BUFFER = 8\nconst LABEL_OFFSET = 12 + 4\nconst VALUE_OFFSET = 12 - 8\n\nfunction clamp(v: number, lo: number, hi: number) {\n  return Math.max(lo, Math.min(hi, v))\n}\n\nfunction decimalsForStep(step: number): number {\n  const s = step.toString()\n  const dot = s.indexOf(\".\")\n  return dot === -1 ? 0 : s.length - dot - 1\n}\n\nfunction roundValue(val: number, step: number): number {\n  const raw = Math.round(val / step) * step\n  return parseFloat(raw.toFixed(decimalsForStep(step)))\n}\n\n// Magnetic snap to the nearest decile when within 3.125% of it.\nfunction snapToDecile(rawValue: number, min: number, max: number): number {\n  const normalized = (rawValue - min) / (max - min)\n  const nearest = Math.round(normalized * 10) / 10\n  if (Math.abs(normalized - nearest) <= 0.03125) {\n    return min + nearest * (max - min)\n  }\n  return rawValue\n}\n\nexport type ElasticSliderProps = {\n  /** Label shown inside the track. */\n  label: string\n\n  /** Controlled value. Use together with `onValueChange` */\n  value?: number\n  /** Initial value for uncontrolled mode. Falls back to `min` */\n  defaultValue?: number\n  /** Called with the new value on drag, click, or key press. */\n  onValueChange?: (value: number) => void\n\n  /**\n   * Minimum value.\n   * @defaultValue 0 */\n  min?: number\n  /**\n   * Maximum value.\n   * @defaultValue 1 */\n  max?: number\n  /**\n   * Smallest increment.\n   * @defaultValue 0.01 */\n  step?: number\n  /** Format the displayed value. Defaults to `value.toFixed(...)` based on `step` */\n  formatValue?: (value: number) => string\n\n  className?: string\n  /** Accessible name. Falls back to `label` */\n  \"aria-label\"?: string\n}\n\nexport function ElasticSlider({\n  label,\n\n  value: valueProp,\n  defaultValue,\n  onValueChange,\n\n  min = 0,\n  max = 1,\n  step = 0.01,\n  formatValue,\n\n  className,\n  \"aria-label\": ariaLabel,\n}: ElasticSliderProps) {\n  const [value = min, setValue] = useControllableState({\n    prop: valueProp,\n    defaultProp: defaultValue ?? min,\n    onChange: onValueChange,\n  })\n\n  const shouldReduceMotion = useReducedMotion()\n\n  const wrapperRef = useRef<HTMLDivElement>(null)\n  const trackRef = useRef<HTMLDivElement>(null)\n  const labelRef = useRef<HTMLSpanElement>(null)\n  const valueRef = useRef<HTMLSpanElement>(null)\n\n  const [isInteracting, setIsInteracting] = useState(false)\n  const [isDragging, setIsDragging] = useState(false)\n  const [isHovered, setIsHovered] = useState(false)\n  /** Ring only for Tab focus or keyboard value nudges, not pointer press/drag. */\n  const [keyboardFocusRing, setKeyboardFocusRing] = useState(false)\n\n  // Pointer session state — mutable, does not trigger re-renders.\n  const pointerDownPos = useRef<{ x: number; y: number } | null>(null)\n  const pendingPointerFocusRef = useRef(false)\n  const isClickRef = useRef(true)\n  const animRef = useRef<ReturnType<typeof animate> | null>(null)\n  const wrapperRectRef = useRef<DOMRect | null>(null)\n  const scaleRef = useRef(1)\n\n  const percentage = ((value - min) / (max - min)) * 100\n  const isActive = isInteracting || isHovered\n  const displayValue = formatValue\n    ? formatValue(value)\n    : value.toFixed(decimalsForStep(step))\n\n  // Fill + handle driven by a single motion value for imperative updates.\n  const fillPercent = useMotionValue(percentage)\n  const fillWidth = useTransform(fillPercent, (pct) => `${pct}%`)\n  const handleLeft = useTransform(\n    fillPercent,\n    (pct) => `max(4px, calc(${pct}% - 8px))`\n  )\n\n  // Rubber band: widens the track and pulls it left when dragged past bounds.\n  const rubberStretch = useMotionValue(0)\n  const rubberWidth = useTransform(\n    rubberStretch,\n    (s) => `calc(100% + ${Math.abs(s)}px)`\n  )\n  const rubberX = useTransform(rubberStretch, (s) => (s < 0 ? s : 0))\n\n  const positionToValue = useCallback(\n    (clientX: number) => {\n      const rect = wrapperRectRef.current\n      if (!rect) return min\n\n      const sceneX = (clientX - rect.left) / scaleRef.current\n      const nativeWidth = wrapperRef.current?.offsetWidth ?? rect.width\n      const percent = clamp(sceneX / nativeWidth, 0, 1)\n\n      return clamp(min + percent * (max - min), min, max)\n    },\n    [min, max]\n  )\n\n  const percentFromValue = useCallback(\n    (v: number) => ((v - min) / (max - min)) * 100,\n    [min, max]\n  )\n\n  // Animate fill to a target percent, or jump instantly when the user prefers\n  // reduced motion. Position still updates — only the spring is skipped.\n  const animateFillTo = useCallback(\n    (targetPercent: number) => {\n      animRef.current?.stop()\n\n      if (shouldReduceMotion) {\n        fillPercent.jump(targetPercent)\n        animRef.current = null\n        return\n      }\n\n      animRef.current = animate(fillPercent, targetPercent, {\n        type: \"spring\",\n        stiffness: 300,\n        damping: 25,\n        mass: 0.8,\n        onComplete: () => {\n          animRef.current = null\n        },\n      })\n    },\n    [fillPercent, shouldReduceMotion]\n  )\n\n  // Sync from props when not interacting and no spring is in flight.\n  // Spring toward external value changes so programmatic updates (e.g. a\n  // keyboard shortcut cycling levels) animate instead of jumping.\n  // (Neon UI patch on the vendored source.)\n  useEffect(() => {\n    if (isInteracting || animRef.current) {\n      return\n    }\n\n    if (Math.abs(fillPercent.get() - percentage) < 0.5) {\n      fillPercent.jump(percentage)\n      return\n    }\n\n    animateFillTo(percentage)\n  }, [percentage, isInteracting, fillPercent, animateFillTo])\n\n\n  const computeRubberStretch = useCallback((clientX: number, sign: number) => {\n    const rect = wrapperRectRef.current\n    if (!rect) return 0\n\n    const distancePast = sign < 0 ? rect.left - clientX : clientX - rect.right\n    const overflow = Math.max(0, distancePast - DEAD_ZONE)\n\n    return (\n      sign * MAX_STRETCH * Math.sqrt(Math.min(overflow / MAX_CURSOR_RANGE, 1))\n    )\n  }, [])\n\n  const handlePointerDown = useCallback((e: React.PointerEvent) => {\n    e.preventDefault()\n    ;(e.target as HTMLElement).setPointerCapture(e.pointerId)\n\n    pointerDownPos.current = { x: e.clientX, y: e.clientY }\n\n    isClickRef.current = true\n\n    setIsInteracting(true)\n\n    pendingPointerFocusRef.current = true\n    setKeyboardFocusRing(false)\n\n    // Pointer interactions should move focus to the slider so subsequent\n    // keyboard input is received and focus styles match the active state.\n    trackRef.current?.focus({ preventScroll: true })\n    requestAnimationFrame(() => {\n      pendingPointerFocusRef.current = false\n    })\n\n    // Snapshot the wrapper rect so later math is immune to layout shifts.\n    const wrapper = wrapperRef.current\n    if (wrapper) {\n      const rect = wrapper.getBoundingClientRect()\n      wrapperRectRef.current = rect\n      scaleRef.current = rect.width / wrapper.offsetWidth\n    }\n  }, [])\n\n  const handlePointerMove = useCallback(\n    (e: React.PointerEvent) => {\n      if (!isInteracting || !pointerDownPos.current) return\n\n      const dx = e.clientX - pointerDownPos.current.x\n      const dy = e.clientY - pointerDownPos.current.y\n\n      if (isClickRef.current && Math.hypot(dx, dy) > CLICK_THRESHOLD) {\n        isClickRef.current = false\n        setIsDragging(true)\n      }\n\n      if (isClickRef.current) return\n\n      const rect = wrapperRectRef.current\n      if (rect && !shouldReduceMotion) {\n        if (e.clientX < rect.left) {\n          rubberStretch.jump(computeRubberStretch(e.clientX, -1))\n        } else if (e.clientX > rect.right) {\n          rubberStretch.jump(computeRubberStretch(e.clientX, 1))\n        } else {\n          rubberStretch.jump(0)\n        }\n      }\n\n      const newValue = positionToValue(e.clientX)\n      animRef.current?.stop()\n      animRef.current = null\n      fillPercent.jump(percentFromValue(newValue))\n      setValue(roundValue(newValue, step))\n    },\n    [\n      isInteracting,\n      positionToValue,\n      percentFromValue,\n      setValue,\n      step,\n      fillPercent,\n      rubberStretch,\n      computeRubberStretch,\n      shouldReduceMotion,\n    ]\n  )\n\n  const handlePointerUp = useCallback(\n    (e: React.PointerEvent) => {\n      if (!isInteracting) return\n\n      if (isClickRef.current) {\n        // Coarse sliders (≤10 positions) snap to the nearest step;\n        // continuous sliders keep the decile-magnetic behavior.\n        const rawValue = positionToValue(e.clientX)\n        const discreteSteps = (max - min) / step\n        const snapped =\n          discreteSteps <= 10\n            ? clamp(min + Math.round((rawValue - min) / step) * step, min, max)\n            : snapToDecile(rawValue, min, max)\n\n        animateFillTo(percentFromValue(snapped))\n        setValue(roundValue(snapped, step))\n      } else {\n        // Drag release: spring the fill onto the nearest step so it never\n        // rests between positions. (Neon UI patch on the vendored source.)\n        const rawValue = positionToValue(e.clientX)\n        const snapped = roundValue(clamp(rawValue, min, max), step)\n\n        animateFillTo(percentFromValue(snapped))\n        setValue(snapped)\n      }\n\n      if (!shouldReduceMotion && rubberStretch.get() !== 0) {\n        animate(rubberStretch, 0, {\n          type: \"spring\",\n          visualDuration: 0.35,\n          bounce: 0.15,\n        })\n      }\n\n      setIsInteracting(false)\n      setIsDragging(false)\n      pointerDownPos.current = null\n    },\n    [\n      isInteracting,\n      positionToValue,\n      percentFromValue,\n      setValue,\n      min,\n      max,\n      step,\n      animateFillTo,\n      rubberStretch,\n      shouldReduceMotion,\n    ]\n  )\n\n  const handleKeyDown = useCallback(\n    (e: React.KeyboardEvent) => {\n      // Shift + Arrow is a Figma-style fast nudge: jumps by 10x the step,\n      // independent of the WAI-ARIA Page step (which scales with range).\n      const arrowStep = e.shiftKey ? step * 10 : step\n\n      let next: number | null = null\n\n      switch (e.key) {\n        case \"ArrowRight\":\n        case \"ArrowUp\":\n          next = value + arrowStep\n          break\n\n        case \"ArrowLeft\":\n        case \"ArrowDown\":\n          next = value - arrowStep\n          break\n\n        case \"Home\":\n          next = min\n          break\n\n        case \"End\":\n          next = max\n          break\n\n        default:\n          return\n      }\n\n      e.preventDefault()\n\n      setKeyboardFocusRing(true)\n\n      const snapped = roundValue(clamp(next, min, max), step)\n      animateFillTo(percentFromValue(snapped))\n      setValue(snapped)\n    },\n    [value, min, max, step, animateFillTo, percentFromValue, setValue]\n  )\n\n  const handleTrackFocus = useCallback(() => {\n    if (!pendingPointerFocusRef.current) {\n      setKeyboardFocusRing(true)\n    }\n  }, [])\n\n  const handleTrackBlur = useCallback(() => {\n    setKeyboardFocusRing(false)\n  }, [])\n\n  // Measure label + value to derive \"dodge\" thresholds so the handle fades\n  // when it would overlap either text.\n  const [dodge, setDodge] = useState({ left: 38, right: 72 })\n\n  useLayoutEffect(() => {\n    const wrapper = wrapperRef.current\n    if (!wrapper) return\n\n    const measure = () => {\n      const trackWidth = wrapper.offsetWidth\n      if (trackWidth <= 0) return\n\n      const labelEl = labelRef.current\n      const valueEl = valueRef.current\n\n      const left = labelEl\n        ? ((LABEL_OFFSET + labelEl.offsetWidth + HANDLE_BUFFER) / trackWidth) *\n          100\n        : 38\n\n      const right = valueEl\n        ? ((trackWidth - VALUE_OFFSET - valueEl.offsetWidth - HANDLE_BUFFER) /\n            trackWidth) *\n          100\n        : 72\n\n      setDodge((prev) => {\n        return prev.left === left && prev.right === right\n          ? prev\n          : { left, right }\n      })\n    }\n\n    measure()\n\n    const observer = new ResizeObserver(measure)\n    observer.observe(wrapper)\n\n    if (labelRef.current) observer.observe(labelRef.current)\n    if (valueRef.current) observer.observe(valueRef.current)\n\n    return () => observer.disconnect()\n  }, [label, displayValue])\n\n  const valueDodge = percentage < dodge.left || percentage > dodge.right\n  const handleOpacity = !isActive\n    ? 0\n    : valueDodge\n      ? 0.1\n      : isDragging\n        ? 0.8\n        : 0.5\n\n  const discreteSteps = (max - min) / step\n  const hashMarkCount = discreteSteps <= 10 ? discreteSteps - 1 : 9\n\n  const hashMarkPct = (i: number) => {\n    return discreteSteps <= 10\n      ? (((i + 1) * step) / (max - min)) * 100\n      : (i + 1) * 10\n  }\n\n  return (\n    <LazyMotion features={domAnimation} strict>\n    <div\n      ref={wrapperRef}\n      data-slot=\"elastic-slider\"\n      className={cn(\n        \"[--elastic-slider-height:--spacing(9)] [--elastic-slider-radius:var(--radius-lg)]\",\n        \"[--elastic-slider-bg:var(--muted)]\",\n        \"[--elastic-slider-fill:var(--muted-foreground)]/10\",\n        \"[--elastic-slider-fill-active:var(--muted-foreground)]/20\",\n        \"[--elastic-slider-hash:var(--muted-foreground)]/30\",\n        \"[--elastic-slider-handle:var(--foreground)]\",\n        \"[--elastic-slider-label:var(--muted-foreground)]\",\n        \"[--elastic-slider-focus:var(--foreground)]\",\n        \"relative h-(--elastic-slider-height)\",\n        className\n      )}\n    >\n      <m.div\n        ref={trackRef}\n        role=\"slider\"\n        tabIndex={0}\n        data-slot=\"elastic-slider-track\"\n        data-active={isActive}\n        data-focus-visible={keyboardFocusRing}\n        aria-label={ariaLabel ?? label}\n        aria-orientation=\"horizontal\"\n        aria-valuemin={min}\n        aria-valuemax={max}\n        aria-valuenow={value}\n        aria-valuetext={displayValue}\n        className={cn(\n          \"group/elastic-slider absolute inset-0 cursor-pointer touch-none overflow-hidden rounded-(--elastic-slider-radius) bg-(--elastic-slider-bg) outline-none select-none\",\n          \"data-[focus-visible=true]:ring-2 data-[focus-visible=true]:ring-ring/50 data-[focus-visible=true]:ring-offset-1 data-[focus-visible=true]:ring-offset-background\"\n        )}\n        style={{ width: rubberWidth, x: rubberX }}\n        onPointerDown={handlePointerDown}\n        onPointerMove={handlePointerMove}\n        onPointerUp={handlePointerUp}\n        onFocus={handleTrackFocus}\n        onBlur={handleTrackBlur}\n        onKeyDown={handleKeyDown}\n        onMouseEnter={() => setIsHovered(true)}\n        onMouseLeave={() => setIsHovered(false)}\n      >\n        <div\n          data-slot=\"elastic-slider-hash-marks\"\n          aria-hidden=\"true\"\n          className=\"pointer-events-none absolute inset-0\"\n        >\n          {Array.from({ length: hashMarkCount }, (_, i) => (\n            <div\n              key={i}\n              className={cn(\n                \"absolute top-1/2 h-2 w-px -translate-x-1/2 -translate-y-1/2 rounded-full transition-colors duration-200\",\n                \"bg-transparent group-data-[active=true]/elastic-slider:bg-(--elastic-slider-hash)\"\n              )}\n              style={{ left: `${hashMarkPct(i)}%` }}\n            />\n          ))}\n        </div>\n\n        <m.div\n          data-slot=\"elastic-slider-fill\"\n          aria-hidden=\"true\"\n          className={cn(\n            \"pointer-events-none absolute inset-y-0 left-0 transition-colors\",\n            \"bg-(--elastic-slider-fill) group-data-[active=true]/elastic-slider:bg-(--elastic-slider-fill-active)\"\n          )}\n          style={{ width: fillWidth }}\n        />\n\n        <m.div\n          data-slot=\"elastic-slider-handle\"\n          aria-hidden=\"true\"\n          className=\"pointer-events-none absolute top-1/2 h-5 w-1 rounded-full bg-(--elastic-slider-handle)\"\n          style={{ left: handleLeft, y: \"-50%\" }}\n          animate={{\n            opacity: handleOpacity,\n            scaleX: isActive ? 1 : 0.25,\n            scaleY: isActive && valueDodge ? 0.75 : 1,\n          }}\n          transition={\n            shouldReduceMotion\n              ? { duration: 0 }\n              : {\n                  scaleX: {\n                    type: \"spring\",\n                    visualDuration: 0.25,\n                    bounce: 0.15,\n                  },\n                  scaleY: { type: \"spring\", visualDuration: 0.2, bounce: 0.1 },\n                  opacity: { duration: 0.15 },\n                }\n          }\n        />\n\n        <span\n          ref={labelRef}\n          data-slot=\"elastic-slider-label\"\n          aria-hidden=\"true\"\n          className=\"pointer-events-none absolute top-1/2 left-3 inline-flex -translate-y-1/2 items-center text-sm/none font-medium text-(--elastic-slider-label) transition-colors\"\n        >\n          {label}\n        </span>\n\n        <span\n          ref={valueRef}\n          data-slot=\"elastic-slider-value\"\n          aria-hidden=\"true\"\n          className={cn(\n            \"pointer-events-none absolute top-1/2 right-3 -translate-y-1/2 font-mono text-sm/none font-medium transition-colors\",\n            \"text-(--elastic-slider-label) group-data-[active=true]/elastic-slider:text-(--elastic-slider-focus)\"\n          )}\n        >\n          {displayValue}\n        </span>\n      </m.div>\n    </div>\n    </LazyMotion>\n  )\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "src/hooks/use-controllable-state.tsx",
      "content": "/* oxlint-disable func-style, no-empty-function, no-shadow, no-use-before-define, react/react-compiler, max-lines-per-function, max-lines, no-magic-numbers -- vendored from chanhdai.com/r/elastic-slider.json (MIT, @iamncdai); kept diffable against upstream */\nimport * as React from \"react\";\n\n// use-layout-effect.tsx\n// https://github.com/radix-ui/primitives/blob/main/packages/react/use-layout-effect/src/use-layout-effect.tsx\n\n/**\n * On the server, React emits a warning when calling `useLayoutEffect`.\n * This is because neither `useLayoutEffect` nor `useEffect` run on the server.\n * We use this safe version which suppresses the warning by replacing it with a noop on the server.\n *\n * See: https://reactjs.org/docs/hooks-reference.html#uselayouteffect\n */\nconst useLayoutEffect = globalThis?.document ? React.useLayoutEffect : () => {};\n\n// use-controllable-state.tsx\n// https://github.com/radix-ui/primitives/blob/main/packages/react/use-controllable-state/src/use-controllable-state.tsx\n\n// Prevent bundlers from trying to optimize the import\nconst useInsertionEffect: typeof useLayoutEffect =\n  (React as never)[\" useInsertionEffect \".trim().toString()] || useLayoutEffect;\n\ntype ChangeHandler<T> = (state: T) => void;\ntype SetStateFn<T> = React.Dispatch<React.SetStateAction<T>>;\n\ninterface UseControllableStateParams<T> {\n  prop?: T | undefined;\n  defaultProp: T;\n  onChange?: ChangeHandler<T>;\n  caller?: string;\n}\n\nexport function useControllableState<T>({\n  prop,\n  defaultProp,\n  onChange = () => {},\n  caller,\n}: UseControllableStateParams<T>): [T, SetStateFn<T>] {\n  const [uncontrolledProp, setUncontrolledProp, onChangeRef] =\n    useUncontrolledState({\n      defaultProp,\n      onChange,\n    });\n  const isControlled = prop !== undefined;\n  const value = isControlled ? prop : uncontrolledProp;\n\n  // Hooks run unconditionally so Hook order never changes between renders;\n  // only the dev-time warning itself is gated on the environment.\n  // (Neon UI patch on the vendored source.)\n  const isControlledRef = React.useRef(prop !== undefined);\n  React.useEffect(() => {\n    if (process.env.NODE_ENV !== \"production\") {\n      const wasControlled = isControlledRef.current;\n      if (wasControlled !== isControlled) {\n        const from = wasControlled ? \"controlled\" : \"uncontrolled\";\n        const to = isControlled ? \"controlled\" : \"uncontrolled\";\n        console.warn(\n          `${caller} is changing from ${from} to ${to}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`\n        );\n      }\n    }\n    isControlledRef.current = isControlled;\n  }, [isControlled, caller]);\n\n  const setValue = React.useCallback<SetStateFn<T>>(\n    (nextValue) => {\n      if (isControlled) {\n        const value = isFunction(nextValue) ? nextValue(prop) : nextValue;\n        if (value !== prop) {\n          onChangeRef.current?.(value);\n        }\n      } else {\n        // Notify the parent from the event handler instead of a useEffect,\n        // per https://react.dev/learn/you-might-not-need-an-effect — saves\n        // the extra render. (Neon UI patch on the vendored source.)\n        const value = isFunction(nextValue)\n          ? nextValue(uncontrolledProp)\n          : nextValue;\n        setUncontrolledProp(value);\n        if (value !== uncontrolledProp) {\n          onChangeRef.current?.(value);\n        }\n      }\n    },\n    [isControlled, prop, setUncontrolledProp, onChangeRef, uncontrolledProp]\n  );\n\n  return [value, setValue];\n}\n\nfunction useUncontrolledState<T>({\n  defaultProp,\n  onChange,\n}: Omit<UseControllableStateParams<T>, \"prop\">): [\n  Value: T,\n  setValue: React.Dispatch<React.SetStateAction<T>>,\n  OnChangeRef: React.RefObject<ChangeHandler<T> | undefined>,\n] {\n  const [value, setValue] = React.useState(defaultProp);\n\n  const onChangeRef = React.useRef(onChange);\n  useInsertionEffect(() => {\n    onChangeRef.current = onChange;\n  }, [onChange]);\n\n  // onChange is fired from setValue in useControllableState rather than from\n  // an effect here, so parents update in the same render pass.\n  // (Neon UI patch on the vendored source.)\n  return [value, setValue, onChangeRef];\n}\n\nfunction isFunction(value: unknown): value is (...args: never[]) => unknown {\n  return typeof value === \"function\";\n}\n",
      "type": "registry:hook"
    }
  ],
  "type": "registry:ui"
}