{"version":3,"file":"TimeGrabber.mjs","names":[],"sources":["../../../src/components/playhead/TimeGrabber.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState } from 'react';\nimport type { TimelineEngine } from '@techsquidtv/canvas-timeline-core';\nimport { addRational, fromSeconds, type RationalTime } from '@techsquidtv/canvas-timeline-utils';\nimport { useTimelineTimePosition, type TimelineTimePositionEvent } from '#react/hooks';\n\nconst DEFAULT_POSITION_EVENTS: TimeGrabberProps['positionEvents'] = ['render'];\n\n/** Render-prop payload for custom draggable time grabber children. */\nexport interface TimeGrabberRenderProps {\n  /** Whether the grabber is actively being dragged. */\n  dragging: boolean;\n  /** Current time represented by the grabber. */\n  time: RationalTime;\n  /** Timeline engine that owns drag updates. */\n  engine: TimelineEngine;\n}\n\n/** Custom node or render prop used inside a draggable time grabber. */\nexport type TimeGrabberChildren =\n  | React.ReactNode\n  | ((props: TimeGrabberRenderProps) => React.ReactNode);\n\nexport interface TimeGrabberProps extends Omit<\n  React.HTMLAttributes<HTMLDivElement>,\n  'children' | 'onDragStart' | 'onDragEnd'\n> {\n  /** Additional class names applied to the positioned grabber shell. */\n  className?: string;\n  /** Timeline engine used for positioning and drag updates. */\n  engine: TimelineEngine;\n  /** Resolves the latest represented time for imperative event-driven updates. */\n  getTime?: () => RationalTime;\n  /** Optional classes added to the default grabber affordance parts. */\n  partClassNames?: {\n    highlight?: string;\n    line?: string;\n    handle?: string;\n  };\n  /** Current time represented by this grabber. */\n  time: RationalTime;\n  /** Event names that should re-sync DOM position without a React render. */\n  positionEvents?: TimelineTimePositionEvent[];\n  /** Called when a drag starts after pointer capture is acquired. */\n  onDragStart?: () => void;\n  /** Called with each dragged time. */\n  onDragTimeChange: (time: RationalTime) => void;\n  /** Called when dragging ends after the engine settles. */\n  onDragEnd?: () => void;\n  /** Custom composable children node or render prop function. */\n  children?: TimeGrabberChildren;\n}\n\nexport const TimeGrabber = React.forwardRef<HTMLDivElement, TimeGrabberProps>(\n  (\n    {\n      children,\n      className = '',\n      engine,\n      getTime,\n      onDragEnd,\n      onDragStart,\n      onDragTimeChange,\n      onPointerDown,\n      partClassNames,\n      positionEvents = DEFAULT_POSITION_EVENTS,\n      style,\n      time,\n      ...props\n    },\n    forwardedRef\n  ) => {\n    const [dragging, setDragging] = useState(false);\n    const activeDragRef = useRef<{\n      pointerId: number;\n      target: HTMLElement;\n      move: (ev: PointerEvent) => void;\n      up: (ev: PointerEvent) => void;\n    } | null>(null);\n    const position = useTimelineTimePosition<HTMLDivElement>({\n      engine,\n      getTime,\n      positionEvents,\n      time,\n    });\n    const ref = React.useCallback(\n      (node: HTMLDivElement | null) => {\n        position.ref.current = node;\n        if (typeof forwardedRef === 'function') {\n          forwardedRef(node);\n        } else if (forwardedRef) {\n          forwardedRef.current = node;\n        }\n      },\n      [forwardedRef, position.ref]\n    );\n\n    useEffect(() => {\n      return () => {\n        if (activeDragRef.current) {\n          activeDragRef.current.target.removeEventListener(\n            'pointermove',\n            activeDragRef.current.move\n          );\n          activeDragRef.current.target.removeEventListener('pointerup', activeDragRef.current.up);\n          activeDragRef.current.target.removeEventListener(\n            'pointercancel',\n            activeDragRef.current.up\n          );\n          try {\n            activeDragRef.current.target.releasePointerCapture(activeDragRef.current.pointerId);\n          } catch {\n            // Ignore browsers or test environments that already released capture.\n          }\n          document.body.classList.remove('is-dragging-timeline-grabber');\n        }\n      };\n    }, []);\n\n    const handlePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {\n      onPointerDown?.(e);\n      if (e.defaultPrevented || (e.pointerType !== 'touch' && e.button !== 0)) {\n        return;\n      }\n\n      e.stopPropagation();\n      const target = e.currentTarget;\n      target.setPointerCapture(e.pointerId);\n      setDragging(true);\n      document.body.classList.add('is-dragging-timeline-grabber');\n      onDragStart?.();\n\n      const startX = e.clientX;\n      const startTime = getTime?.() ?? time;\n\n      const onPointerMove = (ev: PointerEvent) => {\n        const deltaX = ev.clientX - startX;\n        const deltaTimeSec = deltaX / engine.zoomScale;\n        const deltaRt = fromSeconds(deltaTimeSec, startTime.r);\n        onDragTimeChange(addRational(startTime, deltaRt));\n      };\n\n      const onPointerUp = (ev: PointerEvent) => {\n        try {\n          target.releasePointerCapture(ev.pointerId);\n        } catch {\n          // Ignore browsers or test environments that already released capture.\n        }\n        target.removeEventListener('pointermove', onPointerMove);\n        target.removeEventListener('pointerup', onPointerUp);\n        target.removeEventListener('pointercancel', onPointerUp);\n        activeDragRef.current = null;\n        document.body.classList.remove('is-dragging-timeline-grabber');\n        engine.settle();\n        setDragging(false);\n        onDragEnd?.();\n      };\n\n      activeDragRef.current = {\n        pointerId: e.pointerId,\n        target,\n        move: onPointerMove,\n        up: onPointerUp,\n      };\n\n      target.addEventListener('pointermove', onPointerMove);\n      target.addEventListener('pointerup', onPointerUp);\n      target.addEventListener('pointercancel', onPointerUp);\n    };\n\n    const renderContent = () => {\n      const resolvedTime = getTime?.() ?? time;\n      if (children !== undefined) {\n        return typeof children === 'function'\n          ? children({ dragging, time: resolvedTime, engine })\n          : children;\n      }\n\n      return (\n        <div\n          className={['timeline-time-grabber-highlight', partClassNames?.highlight]\n            .filter(Boolean)\n            .join(' ')}\n        >\n          <div\n            className={['timeline-time-grabber-line', partClassNames?.line]\n              .filter(Boolean)\n              .join(' ')}\n          />\n          <div\n            className={['timeline-time-grabber-handle', partClassNames?.handle]\n              .filter(Boolean)\n              .join(' ')}\n          />\n        </div>\n      );\n    };\n\n    return (\n      <div\n        ref={ref}\n        className={['timeline-time-grabber', dragging ? 'is-dragging' : '', className]\n          .filter(Boolean)\n          .join(' ')}\n        style={{\n          transform: 'translateX(0px)',\n          willChange: 'transform',\n          ...style,\n        }}\n        onPointerDown={handlePointerDown}\n        {...props}\n      >\n        {renderContent()}\n      </div>\n    );\n  }\n);\n\nTimeGrabber.displayName = 'Timeline.TimeGrabber';\n"],"mappings":"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"}