{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "use-spring",
  "type": "registry:lib",
  "title": "useSpring",
  "description": "A spring over named numbers, driven onto a callback rather than through state, that carries velocity across every retarget.",
  "files": [
    {
      "path": "registry/lib/use-spring.ts",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nexport interface SpringOptions {\n  /**\n   * Seconds to settle, roughly. A spring has no fixed end, but this is the\n   * number you would have reached for with a duration.\n   */\n  duration?: number\n  /**\n   * Overshoot. `0` arrives and stops, above `0` passes the target and comes\n   * back, below `0` eases in slow. Keep it under `0.3` for anything that is\n   * not meant to be playful.\n   */\n  bounce?: number\n  /** Below this, in target units, the spring is called finished. */\n  epsilon?: number\n}\n\ntype Values = Record<string, number>\n\n/** Fixed step, so the motion is the same on a 60Hz screen and a 120Hz one. */\nconst STEP = 1 / 240\n/** A tab left in the background hands back one enormous frame. Ignore it. */\nconst MAX_FRAME = 0.064\n\n/**\n * Apple's parameterisation: say how long and how bouncy, rather than picking\n * mass, stiffness and damping and discovering what they add up to.\n */\nfunction coefficients({ duration = 0.4, bounce = 0 }: SpringOptions) {\n  const omega = (2 * Math.PI) / duration\n  const damping =\n    bounce >= 0\n      ? (4 * Math.PI * (1 - bounce)) / duration\n      : (4 * Math.PI) / (duration * (1 + bounce))\n\n  return { stiffness: omega * omega, damping }\n}\n\n/**\n * A spring over a set of named numbers, driven straight onto a callback rather\n * than through state. A frame that re-renders is a frame that can drop.\n *\n * The point of a spring over a curve is interruption. A CSS animation retargeted\n * halfway through restarts from a standstill, which is the moment it stops\n * feeling physical. This carries velocity across every retarget, so a value\n * already travelling keeps travelling.\n */\nexport function useSpring(\n  onFrame: (values: Values) => void,\n  options: SpringOptions = {}\n) {\n  const { epsilon = 0.0005 } = options\n  const { stiffness, damping } = coefficients(options)\n\n  const current = React.useRef<Values>({})\n  const velocity = React.useRef<Values>({})\n  const target = React.useRef<Values>({})\n  const frame = React.useRef(0)\n  const clock = React.useRef(0)\n\n  // Read through refs so the loop never closes over a stale callback and never\n  // has to be torn down and rebuilt when one changes.\n  const frameRef = React.useRef(onFrame)\n  const springRef = React.useRef({ stiffness, damping, epsilon })\n  React.useEffect(() => {\n    frameRef.current = onFrame\n    springRef.current = { stiffness, damping, epsilon }\n  })\n\n  const stop = React.useCallback(() => {\n    if (frame.current) cancelAnimationFrame(frame.current)\n    frame.current = 0\n  }, [])\n\n  const tick = React.useCallback((now: number) => {\n    const elapsed = Math.min((now - clock.current) / 1000, MAX_FRAME)\n    clock.current = now\n\n    const { stiffness: k, damping: c, epsilon: rest } = springRef.current\n    let moving = false\n\n    for (const key of Object.keys(target.current)) {\n      const to = target.current[key]\n      let x = current.current[key] ?? to\n      let v = velocity.current[key] ?? 0\n\n      // Sub-stepped: one big step at a low frame rate overshoots into\n      // oscillation, and a stiff spring diverges outright.\n      for (let t = 0; t < elapsed; t += STEP) {\n        const step = Math.min(STEP, elapsed - t)\n        v += (-k * (x - to) - c * v) * step\n        x += v * step\n      }\n\n      if (Math.abs(x - to) < rest && Math.abs(v) < rest) {\n        x = to\n        v = 0\n      } else {\n        moving = true\n      }\n\n      current.current[key] = x\n      velocity.current[key] = v\n    }\n\n    frameRef.current({ ...current.current })\n\n    if (moving) {\n      frame.current = requestAnimationFrame(tick)\n    } else {\n      frame.current = 0\n    }\n  }, [])\n\n  const run = React.useCallback(() => {\n    if (frame.current) return\n    clock.current = performance.now()\n    frame.current = requestAnimationFrame(tick)\n  }, [tick])\n\n  /** Retarget. Whatever is already moving keeps its velocity. */\n  const to = React.useCallback(\n    (next: Values) => {\n      target.current = { ...target.current, ...next }\n      for (const key of Object.keys(next)) {\n        if (current.current[key] === undefined) {\n          current.current[key] = next[key]\n          velocity.current[key] = 0\n        }\n      }\n      run()\n    },\n    [run]\n  )\n\n  /**\n   * Put the values somewhere directly. Pass `carry` to keep them moving as\n   * they land there. That is what a remeasure needs, since the numbers change\n   * meaning but the motion they describe does not.\n   */\n  const set = React.useCallback(\n    (next: Values, carry?: Values) => {\n      stop()\n      current.current = { ...current.current, ...next }\n      if (!carry) target.current = { ...target.current, ...next }\n      for (const key of Object.keys(next)) {\n        velocity.current[key] = carry?.[key] ?? 0\n      }\n      frameRef.current({ ...current.current })\n    },\n    [stop]\n  )\n\n  /** Where each value is right now, mid flight included. */\n  const peek = React.useCallback(() => ({ ...current.current }), [])\n  /** How fast each value is travelling, for carrying across a remeasure. */\n  const speed = React.useCallback(() => ({ ...velocity.current }), [])\n\n  React.useEffect(() => stop, [stop])\n\n  return { to, set, peek, speed, stop }\n}\n",
      "type": "registry:lib"
    }
  ]
}