{"version":3,"file":"useTimelineTimePosition.mjs","names":[],"sources":["../../../src/hooks/viewport/useTimelineTimePosition.ts"],"sourcesContent":["import type { TimelineEngine } from '@techsquidtv/canvas-timeline-core';\nimport type { RationalTime } from '@techsquidtv/canvas-timeline-utils';\nimport React from 'react';\n/** Engine events that should refresh an imperatively positioned timeline element. */\nexport type TimelineTimePositionEvent =\n  | 'render'\n  | 'playhead:scrub'\n  | 'state:inOut'\n  | 'state:settled'\n  | 'history:change';\n\n/**\n * Options for `useTimelineTimePosition`.\n *\n * @remarks\n *\n * Use this primitive for a small number of DOM affordances that should move\n * with timeline time without causing React renders on every scroll, scrub, or\n * playback tick. The engine converts time to viewport X coordinates; the hook\n * writes the transform directly to the referenced element.\n *\n * @see {@link https://canvastimeline.com/docs/react-hooks | React editor hooks}\n */\nexport interface UseTimelineTimePositionOptions {\n  /** Timeline engine that converts time to viewport-space pixels. */\n  engine: TimelineEngine;\n  /** Current time represented by the positioned element. */\n  time: RationalTime;\n  /** Optional resolver for imperative event-driven updates. */\n  getTime?: () => RationalTime;\n  /** Event names that should re-sync DOM position without a React render. */\n  positionEvents?: TimelineTimePositionEvent[];\n}\n\n/**\n * Result returned by `useTimelineTimePosition`.\n *\n * @template T - HTMLElement type that receives the viewport-space transform.\n */\nexport interface UseTimelineTimePositionResult<T extends HTMLElement> {\n  /** Ref for the element that should be translated in viewport coordinates. */\n  ref: React.RefObject<T | null>;\n  /** Immediately re-syncs the element transform to the latest time. */\n  updatePosition: () => void;\n}\n\nconst DEFAULT_POSITION_EVENTS: TimelineTimePositionEvent[] = ['render'];\n\nfunction getPositionEventsKey(positionEvents: TimelineTimePositionEvent[]) {\n  return positionEvents.join('\\0');\n}\n\nfunction parsePositionEventsKey(positionEventsKey: string): TimelineTimePositionEvent[] {\n  return positionEventsKey === ''\n    ? []\n    : (positionEventsKey.split('\\0') as TimelineTimePositionEvent[]);\n}\n\n/**\n * Imperatively positions a low-count DOM affordance at a timeline time.\n *\n * The returned transform is viewport-space: `seconds * zoomScale - scrollLeft`.\n * Use it for standalone affordances such as the playhead grabber. Range\n * controls that already live inside a transformed timeline overlay should use\n * their local control primitive positioning instead.\n *\n * @param options - Timeline engine, time value, and events that should refresh the transform.\n * @returns Ref and imperative updater for the positioned element.\n * @template T - HTMLElement type that receives the viewport-space transform.\n *\n * @example\n * ```tsx\n * import type { RationalTime } from '@techsquidtv/canvas-timeline-utils';\n * import { useTimeline, useTimelineTimePosition } from '@techsquidtv/canvas-timeline-react';\n *\n * export function MarkerHead({ markerTime }: { markerTime: RationalTime }) {\n *   const engine = useTimelineEngine();\n *   const position = useTimelineTimePosition<HTMLDivElement>({\n *     engine,\n *     time: markerTime,\n *     positionEvents: ['render', 'state:settled'],\n *   });\n *\n *   return <div ref={position.ref} className=\"marker-head\" />;\n * }\n * ```\n *\n * @see {@link https://canvastimeline.com/docs/react-hooks | React editor hooks}\n */\nexport function useTimelineTimePosition<T extends HTMLElement>({\n  engine,\n  getTime,\n  positionEvents = DEFAULT_POSITION_EVENTS,\n  time,\n}: UseTimelineTimePositionOptions): UseTimelineTimePositionResult<T> {\n  const internalRef = React.useRef<T | null>(null);\n  const positionEventsKey = React.useMemo(\n    () => getPositionEventsKey(positionEvents),\n    [positionEvents]\n  );\n  const stablePositionEvents = React.useMemo(\n    () => parsePositionEventsKey(positionEventsKey),\n    [positionEventsKey]\n  );\n\n  const updatePosition = React.useCallback(() => {\n    if (!internalRef.current) {\n      return;\n    }\n    const x = engine.timeToPixel(getTime?.() ?? time);\n    internalRef.current.style.transform = `translateX(${x}px)`;\n  }, [engine, getTime, time]);\n\n  React.useEffect(() => {\n    updatePosition();\n    const unsubscribers = stablePositionEvents.map((eventName) =>\n      engine.on(eventName, updatePosition)\n    );\n    return () => {\n      for (const unsubscribe of unsubscribers) {\n        unsubscribe();\n      }\n    };\n  }, [engine, stablePositionEvents, updatePosition]);\n\n  return { ref: internalRef, updatePosition };\n}\n"],"mappings":";;AA8CA,MAAM,0BAAuD,CAAC,QAAQ;AAEtE,SAAS,qBAAqB,gBAA6C;CACzE,OAAO,eAAe,KAAK,IAAI;AACjC;AAEA,SAAS,uBAAuB,mBAAwD;CACtF,OAAO,sBAAsB,KACzB,CAAC,IACA,kBAAkB,MAAM,IAAI;AACnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,SAAgB,wBAA+C,EAC7D,QACA,SACA,iBAAiB,yBACjB,QACmE;CACnE,MAAM,cAAc,MAAM,OAAiB,IAAI;CAC/C,MAAM,oBAAoB,MAAM,cACxB,qBAAqB,cAAc,GACzC,CAAC,cAAc,CACjB;CACA,MAAM,uBAAuB,MAAM,cAC3B,uBAAuB,iBAAiB,GAC9C,CAAC,iBAAiB,CACpB;CAEA,MAAM,iBAAiB,MAAM,kBAAkB;EAC7C,IAAI,CAAC,YAAY,SACf;EAEF,MAAM,IAAI,OAAO,YAAY,UAAU,KAAK,IAAI;EAChD,YAAY,QAAQ,MAAM,YAAY,cAAc,EAAE;CACxD,GAAG;EAAC;EAAQ;EAAS;CAAI,CAAC;CAE1B,MAAM,gBAAgB;EACpB,eAAe;EACf,MAAM,gBAAgB,qBAAqB,KAAK,cAC9C,OAAO,GAAG,WAAW,cAAc,CACrC;EACA,aAAa;GACX,KAAK,MAAM,eAAe,eACxB,YAAY;EAEhB;CACF,GAAG;EAAC;EAAQ;EAAsB;CAAc,CAAC;CAEjD,OAAO;EAAE,KAAK;EAAa;CAAe;AAC5C"}