{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "primitives-animate-spring",
  "title": "Spring",
  "description": "A flexible, animated spring component that attaches a draggable element to its origin with a spring line.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "@unlumen-ui/primitives-animate-slot",
    "@unlumen-ui/lib-get-strict-context",
    "@unlumen-ui/hooks-use-motion-value-state"
  ],
  "files": [
    {
      "path": "registry/primitives/animate/spring/index.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  motion,\n  useMotionValue,\n  useSpring as useMotionSpring,\n  type SpringOptions,\n  type HTMLMotionProps,\n  type MotionValue,\n} from \"motion/react\";\n\nimport { useMotionValueState } from \"@/hooks/use-motion-value-state\";\nimport { getStrictContext } from \"@/lib/get-strict-context\";\nimport { Slot, type WithAsChild } from \"@/components/unlumen-ui/primitives/animate/slot\";\n\ntype SpringPathConfig = {\n  coilCount?: number;\n  amplitudeMin?: number;\n  amplitudeMax?: number;\n  curveRatioMin?: number;\n  curveRatioMax?: number;\n  bezierOffset?: number;\n};\n\nfunction generateSpringPath(\n  x1: number,\n  y1: number,\n  x2: number,\n  y2: number,\n  pathConfig: SpringPathConfig = {},\n) {\n  const {\n    coilCount = 8,\n    amplitudeMin = 8,\n    amplitudeMax = 20,\n    curveRatioMin = 0.5,\n    curveRatioMax = 1,\n    bezierOffset = 8,\n  } = pathConfig;\n\n  const dx = x2 - x1;\n  const dy = y2 - y1;\n  const dist = Math.sqrt(dx * dx + dy * dy);\n  if (dist < 2) return `M${x1},${y1}`;\n  const d = dist / coilCount;\n  const h = Math.max(0.8, 1 - (dist - 40) / 200);\n  const amplitude = Math.max(\n    amplitudeMin,\n    Math.min(amplitudeMax, amplitudeMax * h),\n  );\n  const curveRatio =\n    dist <= 40\n      ? curveRatioMax\n      : dist <= 120\n        ? curveRatioMax - ((dist - 40) / 80) * (curveRatioMax - curveRatioMin)\n        : curveRatioMin;\n  const ux = dx / dist,\n    uy = dy / dist;\n  const perpX = -uy,\n    perpY = ux;\n\n  const path: string[] = [];\n  for (let i = 0; i < coilCount; i++) {\n    const sx = x1 + ux * (i * d);\n    const sy = y1 + uy * (i * d);\n    const ex = x1 + ux * ((i + 1) * d);\n    const ey = y1 + uy * ((i + 1) * d);\n\n    const mx = x1 + ux * ((i + 0.5) * d) + perpX * amplitude;\n    const my = y1 + uy * ((i + 0.5) * d) + perpY * amplitude;\n\n    const c1x = sx + d * curveRatio * ux;\n    const c1y = sy + d * curveRatio * uy;\n    const c2x = mx + ux * bezierOffset;\n    const c2y = my + uy * bezierOffset;\n    const c3x = mx - ux * bezierOffset;\n    const c3y = my - uy * bezierOffset;\n    const c4x = ex - d * curveRatio * ux;\n    const c4y = ey - d * curveRatio * uy;\n\n    if (i === 0) path.push(`M${sx},${sy}`);\n    else path.push(`L${sx},${sy}`);\n    path.push(`C${c1x},${c1y} ${c2x},${c2y} ${mx},${my}`);\n    path.push(`C${c3x},${c3y} ${c4x},${c4y} ${ex},${ey}`);\n  }\n  return path.join(\" \");\n}\n\ntype SpringContextType = {\n  dragElastic?: number;\n  childRef: React.RefObject<HTMLDivElement | null>;\n  springX: MotionValue<number>;\n  springY: MotionValue<number>;\n  x: MotionValue<number>;\n  y: MotionValue<number>;\n  isDragging: boolean;\n  setIsDragging: (isDragging: boolean) => void;\n  path: string;\n};\n\nconst [LocalSpringProvider, useSpring] =\n  getStrictContext<SpringContextType>(\"SpringContext\");\n\ntype SpringProviderProps = {\n  children: React.ReactNode;\n  dragElastic?: number;\n  pathConfig?: SpringPathConfig;\n  transition?: SpringOptions;\n};\n\nfunction SpringProvider({\n  dragElastic = 0.2,\n  transition = { stiffness: 200, damping: 16 },\n  pathConfig = {},\n  ...props\n}: SpringProviderProps) {\n  const x = useMotionValue(0);\n  const y = useMotionValue(0);\n\n  const springX = useMotionSpring(x, transition);\n  const springY = useMotionSpring(y, transition);\n\n  const sx = useMotionValueState(springX);\n  const sy = useMotionValueState(springY);\n\n  const childRef = React.useRef<HTMLDivElement>(null);\n\n  const [center, setCenter] = React.useState({ x: 0, y: 0 });\n  const [isDragging, setIsDragging] = React.useState(false);\n\n  React.useLayoutEffect(() => {\n    function update() {\n      if (childRef.current) {\n        const rect = childRef.current.getBoundingClientRect();\n        setCenter({\n          x: rect.left + rect.width / 2,\n          y: rect.top + rect.height / 2,\n        });\n      }\n    }\n\n    update();\n\n    window.addEventListener(\"resize\", update);\n    window.addEventListener(\"scroll\", update, true);\n\n    return () => {\n      window.removeEventListener(\"resize\", update);\n      window.removeEventListener(\"scroll\", update, true);\n    };\n  }, []);\n\n  React.useEffect(() => {\n    if (isDragging) {\n      document.body.style.cursor = \"grabbing\";\n    } else {\n      document.body.style.cursor = \"default\";\n    }\n  }, [isDragging]);\n\n  const path = generateSpringPath(\n    center.x,\n    center.y,\n    center.x + sx,\n    center.y + sy,\n    pathConfig,\n  );\n\n  return (\n    <LocalSpringProvider\n      value={{\n        springX,\n        springY,\n        x,\n        y,\n        isDragging,\n        setIsDragging,\n        dragElastic,\n        childRef,\n        path,\n      }}\n      {...props}\n    />\n  );\n}\n\ntype SpringProps = React.SVGProps<SVGSVGElement>;\n\nfunction Spring({ style, ...props }: SpringProps) {\n  const { path } = useSpring();\n\n  return (\n    <svg\n      width=\"100vw\"\n      height=\"100vh\"\n      style={{\n        position: \"fixed\",\n        inset: 0,\n        pointerEvents: \"none\",\n        ...style,\n      }}\n      {...props}\n    >\n      <path\n        d={path}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        stroke=\"currentColor\"\n        strokeWidth={2}\n        fill=\"none\"\n      />\n    </svg>\n  );\n}\n\ntype SpringElementProps = WithAsChild<\n  Omit<HTMLMotionProps<\"div\">, \"children\"> & {\n    children: React.ReactElement;\n  }\n>;\n\nfunction SpringElement({\n  ref,\n  asChild = false,\n  style,\n  ...props\n}: SpringElementProps) {\n  const {\n    childRef,\n    dragElastic,\n    isDragging,\n    setIsDragging,\n    springX,\n    springY,\n    x,\n    y,\n  } = useSpring();\n\n  React.useImperativeHandle(ref, () => childRef.current as HTMLDivElement);\n\n  const Component = asChild ? Slot : motion.div;\n\n  return (\n    <Component\n      ref={childRef}\n      style={{\n        cursor: isDragging ? \"grabbing\" : \"grab\",\n        x: springX,\n        y: springY,\n        ...style,\n      }}\n      drag\n      dragElastic={dragElastic}\n      dragMomentum={false}\n      onDragStart={() => {\n        setIsDragging(true);\n      }}\n      onDrag={(_, info) => {\n        x.set(info.offset.x);\n        y.set(info.offset.y);\n      }}\n      onDragEnd={() => {\n        x.set(0);\n        y.set(0);\n        setIsDragging(false);\n      }}\n      {...props}\n    />\n  );\n}\n\nexport {\n  SpringProvider,\n  Spring,\n  SpringElement,\n  useSpring,\n  type SpringProviderProps,\n  type SpringProps,\n  type SpringElementProps,\n  type SpringPathConfig,\n  type SpringContextType,\n};\n",
      "type": "registry:ui",
      "target": "components/unlumen-ui/primitives/animate/spring.tsx"
    }
  ],
  "type": "registry:ui"
}