{"version":3,"file":"useKeyframePointer.mjs","names":[],"sources":["../../../src/components/interactions/useKeyframePointer.ts"],"sourcesContent":["import { useEffect, useRef, useLayoutEffect } from 'react';\nimport type { RefObject } from 'react';\n\ninterface ViewportPoint {\n  x: number;\n  y: number;\n}\ninterface KeyframePointerOptions<Target> {\n  root: RefObject<HTMLDivElement | null>;\n  rulerHeight: number;\n  priority?: number;\n  hitTest: (point: ViewportPoint, event: PointerEvent) => Target | null;\n  hover: (target: Target | null) => void;\n  start: (target: Target, point: ViewportPoint, event: PointerEvent) => boolean;\n  move: (point: ViewportPoint, event: PointerEvent) => void;\n  end: (cancelled: boolean) => void;\n}\n\ninterface PointerLayer {\n  priority: number;\n  active: boolean;\n  hits: (event: PointerEvent) => boolean;\n}\nconst pointerLayers = new WeakMap<Element, Set<PointerLayer>>();\n\n/** One captured pointer, cached drag bounds, and fallback listeners only when capture fails. */\nexport function useKeyframePointer<Target>(options: KeyframePointerOptions<Target>) {\n  const latest = useRef(options);\n  useLayoutEffect(() => {\n    latest.current = options;\n  });\n  const cancelRef = useRef<(() => void) | null>(null);\n  useEffect(() => {\n    const root = options.root.current;\n    const parent = root?.parentElement;\n    if (!root || !parent) {\n      return;\n    }\n    let active: { pointerId: number; bounds: DOMRect } | null = null;\n    let disposeFallback: (() => void) | null = null;\n    const point = (event: PointerEvent, bounds: DOMRect): ViewportPoint => ({\n      x: event.clientX - bounds.left,\n      y: event.clientY - bounds.top + latest.current.rulerHeight,\n    });\n    const layers = pointerLayers.get(parent) ?? new Set<PointerLayer>();\n    pointerLayers.set(parent, layers);\n    const layer: PointerLayer = {\n      priority: options.priority ?? 0,\n      active: false,\n      hits: (event) =>\n        latest.current.hitTest(point(event, root.getBoundingClientRect()), event) !== null,\n    };\n    layers.add(layer);\n    const isClaimed = (event: PointerEvent) => {\n      for (const candidate of layers) {\n        if (candidate.active || (candidate.priority > layer.priority && candidate.hits(event))) {\n          return true;\n        }\n      }\n      return false;\n    };\n    const finish = (cancelled: boolean) => {\n      if (!active) {\n        return;\n      }\n      const id = active.pointerId;\n      active = null;\n      layer.active = false;\n      disposeFallback?.();\n      disposeFallback = null;\n      latest.current.end(cancelled);\n      try {\n        root.releasePointerCapture(id);\n      } catch {\n        /* Already released. */\n      }\n    };\n    cancelRef.current = () => finish(true);\n    const move = (event: PointerEvent) => {\n      if (active) {\n        if (event.pointerId !== active.pointerId) {\n          return;\n        }\n        latest.current.move(point(event, active.bounds), event);\n        event.stopImmediatePropagation();\n      } else if (!event.defaultPrevented) {\n        latest.current.hover(\n          isClaimed(event)\n            ? null\n            : latest.current.hitTest(point(event, root.getBoundingClientRect()), event)\n        );\n      }\n    };\n    const end = (event: PointerEvent) => {\n      if (active?.pointerId !== event.pointerId) {\n        return;\n      }\n      finish(event.type !== 'pointerup');\n      event.stopImmediatePropagation();\n    };\n    const down = (event: PointerEvent) => {\n      if (\n        active ||\n        event.defaultPrevented ||\n        isClaimed(event) ||\n        (event.button !== 0 && event.pointerType !== 'touch')\n      ) {\n        return;\n      }\n      const bounds = root.getBoundingClientRect();\n      const position = point(event, bounds);\n      const hit = latest.current.hitTest(position, event);\n      if (!hit) {\n        return;\n      }\n      event.preventDefault();\n      event.stopImmediatePropagation();\n      root.focus({ preventScroll: true });\n      if (!latest.current.start(hit, position, event)) {\n        return;\n      }\n      active = { pointerId: event.pointerId, bounds };\n      layer.active = true;\n      try {\n        root.setPointerCapture(event.pointerId);\n      } catch {\n        const doc = root.ownerDocument;\n        doc.addEventListener('pointermove', move);\n        doc.addEventListener('pointerup', end);\n        doc.addEventListener('pointercancel', end);\n        disposeFallback = () => {\n          doc.removeEventListener('pointermove', move);\n          doc.removeEventListener('pointerup', end);\n          doc.removeEventListener('pointercancel', end);\n        };\n      }\n    };\n    // Capture on the stage lets blank areas pass through to clip/playhead layers.\n    parent.addEventListener('pointerdown', down, true);\n    parent.addEventListener('pointermove', move, true);\n    parent.addEventListener('pointerup', end, true);\n    parent.addEventListener('pointercancel', end, true);\n    root.addEventListener('lostpointercapture', end);\n    return () => {\n      finish(true);\n      layers.delete(layer);\n      if (layers.size === 0) {\n        pointerLayers.delete(parent);\n      }\n      parent.removeEventListener('pointerdown', down, true);\n      parent.removeEventListener('pointermove', move, true);\n      parent.removeEventListener('pointerup', end, true);\n      parent.removeEventListener('pointercancel', end, true);\n      root.removeEventListener('lostpointercapture', end);\n      cancelRef.current = null;\n    };\n  }, [options.root, options.priority]);\n  return () => cancelRef.current?.();\n}\n"],"mappings":"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"}