{"version":3,"file":"PlayheadArea.mjs","names":[],"sources":["../../../src/components/playhead/PlayheadArea.tsx"],"sourcesContent":["import { consumeTimelineDoubleTap } from '#react/components/interactions/tapState';\nimport { useTimelineEngine } from '#react/hooks/core/useTimelineEngine';\nimport type { TimelineEngine } from '@techsquidtv/canvas-timeline-core';\nimport { toSeconds } from '@techsquidtv/canvas-timeline-utils';\nimport type { RationalTime } from '@techsquidtv/canvas-timeline-utils';\nimport React, { useRef } from 'react';\n/**\n * Props for the interactive playhead scrub area.\n */\nexport interface PlayheadAreaProps {\n  /** Additional class names applied to the absolute-positioned scrub area. */\n  className?: string;\n  /** Custom handler for double-click or double-tap marker behavior at a timeline time. */\n  onDoubleClick?: (\n    time: RationalTime,\n    engine: TimelineEngine,\n    e: React.PointerEvent | React.MouseEvent\n  ) => void;\n}\n\nexport const PlayheadArea = React.forwardRef<\n  HTMLDivElement,\n  PlayheadAreaProps & Omit<React.HTMLAttributes<HTMLDivElement>, 'onDoubleClick'>\n>(({ className = '', onDoubleClick, ...props }, forwardedRef) => {\n  const engine = useTimelineEngine();\n  const internalRef = useRef<HTMLDivElement>(null);\n\n  const ref = React.useCallback(\n    (node: HTMLDivElement | null) => {\n      internalRef.current = node;\n      if (typeof forwardedRef === 'function') {\n        forwardedRef(node);\n      } else if (forwardedRef) {\n        forwardedRef.current = node;\n      }\n    },\n    [forwardedRef]\n  );\n\n  const triggerDoubleClick = (e: React.PointerEvent | React.MouseEvent, x: number) => {\n    const time = engine.pixelToTime(x);\n    if (onDoubleClick) {\n      onDoubleClick(time, engine, e);\n    } else {\n      const clickRadiusSec = 10 / engine.zoomScale; // 10 pixels in seconds\n      const clickedMarker = engine.markers.find(\n        (m) => Math.abs(toSeconds(m.time) - toSeconds(time)) <= clickRadiusSec\n      );\n\n      if (clickedMarker) {\n        engine.removeMarker(clickedMarker.id);\n      } else {\n        const label = `M${engine.markers.length + 1}`;\n        engine.addMarker(time, label);\n      }\n    }\n  };\n\n  const handlePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {\n    if (e.pointerType !== 'touch' && e.button !== 0) {\n      return;\n    }\n    e.stopPropagation();\n\n    if (consumeTimelineDoubleTap(e)) {\n      const rect = internalRef.current?.getBoundingClientRect();\n      if (rect) {\n        const x = e.clientX - rect.left;\n        triggerDoubleClick(e, x);\n      }\n      e.preventDefault();\n      return;\n    }\n\n    const rect = internalRef.current?.getBoundingClientRect();\n    if (!rect) {\n      return;\n    }\n\n    const target = e.currentTarget;\n    engine.pause();\n    target.setPointerCapture(e.pointerId);\n    e.preventDefault();\n\n    const rectLeft = rect.left;\n\n    const updatePlayhead = (clientX: number) => {\n      const x = clientX - rectLeft;\n      const newTime = engine.pixelToTime(x);\n      engine.updatePlayhead(newTime);\n    };\n\n    updatePlayhead(e.clientX);\n\n    const onPointerMove = (ev: PointerEvent) => {\n      updatePlayhead(ev.clientX);\n    };\n\n    const onPointerUp = (ev: PointerEvent) => {\n      try {\n        target.releasePointerCapture(ev.pointerId);\n      } catch {\n        // Ignore\n      }\n      target.removeEventListener('pointermove', onPointerMove);\n      target.removeEventListener('pointerup', onPointerUp);\n      target.removeEventListener('pointercancel', onPointerUp);\n      engine.settle();\n    };\n\n    target.addEventListener('pointermove', onPointerMove);\n    target.addEventListener('pointerup', onPointerUp);\n    target.addEventListener('pointercancel', onPointerUp);\n  };\n\n  return (\n    <div\n      ref={ref}\n      className={`timeline-playhead-area absolute top-0 left-0 w-full h-8 cursor-text z-30 pointer-events-auto select-none touch-none ${className}`}\n      onPointerDown={handlePointerDown}\n      title=\"Scrub timeline / Double click or double tap to add marker\"\n      {...props}\n    />\n  );\n});\n\nPlayheadArea.displayName = 'Timeline.PlayheadArea';\n"],"mappings":";;;;;;AAoBA,MAAa,eAAe,MAAM,YAG/B,EAAE,YAAY,IAAI,eAAe,GAAG,SAAS,iBAAiB;CAC/D,MAAM,SAAS,kBAAkB;CACjC,MAAM,cAAc,OAAuB,IAAI;CAE/C,MAAM,MAAM,MAAM,aACf,SAAgC;EAC/B,YAAY,UAAU;EACtB,IAAI,OAAO,iBAAiB,YAC1B,aAAa,IAAI;OACZ,IAAI,cACT,aAAa,UAAU;CAE3B,GACA,CAAC,YAAY,CACf;CAEA,MAAM,sBAAsB,GAA0C,MAAc;EAClF,MAAM,OAAO,OAAO,YAAY,CAAC;EACjC,IAAI,eACF,cAAc,MAAM,QAAQ,CAAC;OACxB;GACL,MAAM,iBAAiB,KAAK,OAAO;GACnC,MAAM,gBAAgB,OAAO,QAAQ,MAClC,MAAM,KAAK,IAAI,UAAU,EAAE,IAAI,IAAI,UAAU,IAAI,CAAC,KAAK,cAC1D;GAEA,IAAI,eACF,OAAO,aAAa,cAAc,EAAE;QAC/B;IACL,MAAM,QAAQ,IAAI,OAAO,QAAQ,SAAS;IAC1C,OAAO,UAAU,MAAM,KAAK;GAC9B;EACF;CACF;CAEA,MAAM,qBAAqB,MAA0C;EACnE,IAAI,EAAE,gBAAgB,WAAW,EAAE,WAAW,GAC5C;EAEF,EAAE,gBAAgB;EAElB,IAAI,yBAAyB,CAAC,GAAG;GAC/B,MAAM,OAAO,YAAY,SAAS,sBAAsB;GACxD,IAAI,MAAM;IACR,MAAM,IAAI,EAAE,UAAU,KAAK;IAC3B,mBAAmB,GAAG,CAAC;GACzB;GACA,EAAE,eAAe;GACjB;EACF;EAEA,MAAM,OAAO,YAAY,SAAS,sBAAsB;EACxD,IAAI,CAAC,MACH;EAGF,MAAM,SAAS,EAAE;EACjB,OAAO,MAAM;EACb,OAAO,kBAAkB,EAAE,SAAS;EACpC,EAAE,eAAe;EAEjB,MAAM,WAAW,KAAK;EAEtB,MAAM,kBAAkB,YAAoB;GAC1C,MAAM,IAAI,UAAU;GACpB,MAAM,UAAU,OAAO,YAAY,CAAC;GACpC,OAAO,eAAe,OAAO;EAC/B;EAEA,eAAe,EAAE,OAAO;EAExB,MAAM,iBAAiB,OAAqB;GAC1C,eAAe,GAAG,OAAO;EAC3B;EAEA,MAAM,eAAe,OAAqB;GACxC,IAAI;IACF,OAAO,sBAAsB,GAAG,SAAS;GAC3C,QAAQ,CAER;GACA,OAAO,oBAAoB,eAAe,aAAa;GACvD,OAAO,oBAAoB,aAAa,WAAW;GACnD,OAAO,oBAAoB,iBAAiB,WAAW;GACvD,OAAO,OAAO;EAChB;EAEA,OAAO,iBAAiB,eAAe,aAAa;EACpD,OAAO,iBAAiB,aAAa,WAAW;EAChD,OAAO,iBAAiB,iBAAiB,WAAW;CACtD;CAEA,OACE,oBAAC,OAAD;EACO;EACL,WAAW,uHAAuH;EAClI,eAAe;EACf,OAAM;EACN,GAAI;CACL,CAAA;AAEL,CAAC;AAED,aAAa,cAAc"}