{"version":3,"file":"useTimelineKeyframes.mjs","names":[],"sources":["../../../src/hooks/keyframes/useTimelineKeyframes.ts"],"sourcesContent":["import {\n  timelineCommandFail,\n  timelineCommandInvalidInput,\n  timelineCommandOk,\n} from '@techsquidtv/canvas-timeline-core';\nimport type {\n  TimelineCommandResult,\n  TimelineKeyframe,\n  TimelineKeyframeReference,\n  TimelineKeyframeClipboard,\n  TimelineKeyframeEditCommand,\n  TimelineKeyframeMutationOptions,\n  TimelineKeyframePropertyId,\n  TimelineSetClipKeyframeOptions,\n  TimelineUpdateClipKeyframeOptions,\n} from '@techsquidtv/canvas-timeline-core';\nimport { useTimelineEngine } from '#react/hooks/core/useTimelineEngine';\nimport { useTimelineSelector } from '#react/hooks/core/useTimelineSelector';\nimport type { RationalTime } from '@techsquidtv/canvas-timeline-utils';\nimport { useCallback, useMemo } from 'react';\n/**\n * Options accepted by `useTimelineKeyframes`.\n *\n * @remarks\n *\n * Use these options to scope keyframe reads to one clip, one property, selected\n * clips. Use useTimelineKeyframeGeometry for live viewport-space reads.\n *\n * @see {@link useTimelineKeyframeDrag}\n * @see {@link https://canvastimeline.com/docs/keyframes | Keyframes}\n */\nexport interface UseTimelineKeyframesOptions {\n  /** Optional property filter. */\n  property?: TimelineKeyframePropertyId;\n  /** Restrict state to selected clips. */\n  selectedClipOnly?: boolean;\n  /** Optional clip id used to scope keyframe lists and commands. */\n  clipId?: string;\n}\n\n/**\n * Result returned by `useTimelineKeyframes`.\n *\n * @remarks\n *\n * The result combines settled keyframe lists, selection, and mutation commands.\n * Use it for keyframe inspectors,\n * property editors, and toolbar actions. For pointer-driven dragging, combine\n * it with {@link useTimelineKeyframeDrag}; for Bezier easing handles, combine\n * it with {@link useTimelineKeyframeTangentDrag}.\n *\n *\n * @see {@link useTimelineKeyframeTangentDrag}\n * @see {@link https://canvastimeline.com/docs/keyframes | Keyframes}\n */\nexport interface UseTimelineKeyframesResult {\n  /** Settled keyframes matching the clip, property, and selection filters. */\n  keyframes: TimelineKeyframe[];\n  /** Evaluates a keyframed property at a timeline time. */\n  getPropertyValueAtTime: (\n    clipId: string,\n    property: TimelineKeyframePropertyId,\n    time?: RationalTime\n  ) => number | undefined;\n  /** Adds or updates one keyframe by clip, property, and exact timeline time. */\n  setKeyframe: (\n    input: TimelineSetClipKeyframeOptions,\n    options?: TimelineKeyframeMutationOptions\n  ) => TimelineCommandResult<TimelineKeyframe>;\n  /** Updates one existing keyframe. */\n  updateKeyframe: (\n    input: TimelineUpdateClipKeyframeOptions,\n    options?: TimelineKeyframeMutationOptions\n  ) => TimelineCommandResult<TimelineKeyframe>;\n  /** Removes one keyframe from a clip. */\n  removeKeyframe: (\n    clipId: string,\n    keyframeId: string,\n    options?: TimelineKeyframeMutationOptions\n  ) => TimelineCommandResult<TimelineKeyframe>;\n  /** Selects keys without subscribing to live geometry. */\n  selectKeyframes: (\n    references: readonly TimelineKeyframeReference[],\n    mode?: 'replace' | 'add' | 'toggle'\n  ) => TimelineCommandResult;\n  /** Selected keys across all clips. */\n  selectedKeyframes: TimelineKeyframeReference[];\n  /** Copies the current selection independently of clip copy/paste. */\n  copyKeyframes: () => TimelineKeyframeClipboard;\n  /** Creates a paste command with preserved relative timing. */\n  createPasteCommand: (\n    clipboard: TimelineKeyframeClipboard,\n    time: RationalTime,\n    clipId?: string\n  ) => TimelineKeyframeEditCommand;\n  /** Clears keyframe selection. */\n  clearKeyframeSelection: () => TimelineCommandResult;\n}\n\n/**\n * Reads settled keyframe state and exposes canonical keyframe commands.\n *\n * @remarks\n *\n * `useTimelineKeyframes` is the main keyframe-domain hook. It reads settled keyframe state. Use useTimelineKeyframeGeometry for live overlays. Mutation commands return\n * {@link TimelineCommandResult} values and respect locked tracks.\n *\n * @param options - Optional clip, property, and selected-clip filters.\n * @returns Settled keyframe lists, selection, property evaluation, and mutation commands.\n *\n * @example\n * ```tsx\n * import { fromSeconds } from '@techsquidtv/canvas-timeline-utils';\n * import { useTimelineKeyframes } from '@techsquidtv/canvas-timeline-react';\n *\n * export function OpacityKeyframeButton({ clipId }: { clipId: string }) {\n *   const keyframes = useTimelineKeyframes({ clipId, property: 'opacity' });\n *\n *   return (\n *     <button\n *       type=\"button\"\n *       onClick={() =>\n *         keyframes.setKeyframe({\n *           clipId,\n *           property: 'opacity',\n *           time: fromSeconds(1),\n *           value: 0.5,\n *         })\n *       }\n *     >\n *       Add opacity keyframe\n *     </button>\n *   );\n * }\n * ```\n *\n * @see {@link useTimelineKeyframeDrag}\n * @see {@link useTimelineKeyframeTangentDrag}\n * @see {@link https://canvastimeline.com/demos/keyframe-opacity | Keyframe opacity demo}\n */\nexport function useTimelineKeyframes(\n  options: UseTimelineKeyframesOptions = {}\n): UseTimelineKeyframesResult {\n  const engine = useTimelineEngine();\n  const tracks = useTimelineSelector((state) => state.tracks);\n  const keyframes = useMemo(\n    () =>\n      tracks.flatMap((track) =>\n        track.clips\n          .filter(\n            (clip) =>\n              (options.clipId === undefined || clip.id === options.clipId) &&\n              (!options.selectedClipOnly || clip.selected)\n          )\n          .flatMap((clip) =>\n            (clip.keyframes ?? []).filter(\n              (key) => options.property === undefined || key.property === options.property\n            )\n          )\n      ),\n    [tracks, options.clipId, options.property, options.selectedClipOnly]\n  );\n  const selectedKeyframes = useMemo(\n    () =>\n      tracks.flatMap((track) =>\n        track.clips.flatMap((clip) =>\n          (clip.keyframes ?? [])\n            .filter((key) => key.selected)\n            .map((key) => ({ clipId: clip.id, keyframeId: key.id }))\n        )\n      ),\n    [tracks]\n  );\n  const copyKeyframes = useCallback(() => engine.keyframes.copyKeyframes(), [engine]);\n  const createPasteCommand = useCallback(\n    (clipboard: TimelineKeyframeClipboard, time: RationalTime, clipId?: string) =>\n      engine.keyframes.createPasteCommand(clipboard, time, clipId),\n    [engine]\n  );\n\n  const getPropertyValueAtTime = useCallback(\n    (targetClipId: string, targetProperty: TimelineKeyframePropertyId, time?: RationalTime) =>\n      engine.keyframes.getClipPropertyValueAtTime(targetClipId, targetProperty, time),\n    [engine]\n  );\n\n  const setKeyframe = useCallback(\n    (\n      input: TimelineSetClipKeyframeOptions,\n      mutationOptions?: TimelineKeyframeMutationOptions\n    ): TimelineCommandResult<TimelineKeyframe> => {\n      const found = engine.geometry.getClip(input.clipId);\n      let keyframe: TimelineKeyframe | null;\n      try {\n        keyframe = engine.keyframes.setClipKeyframe(input, mutationOptions);\n      } catch (setError: unknown) {\n        return timelineCommandInvalidInput(\n          'Timeline keyframe could not be created from the provided input.',\n          setError\n        );\n      }\n      if (keyframe) {\n        return timelineCommandOk(keyframe);\n      }\n      if (!found) {\n        return timelineCommandFail('not-found');\n      }\n      return found.track.locked ? timelineCommandFail('locked') : timelineCommandFail('not-found');\n    },\n    [engine]\n  );\n\n  const updateKeyframe = useCallback(\n    (\n      input: TimelineUpdateClipKeyframeOptions,\n      mutationOptions?: TimelineKeyframeMutationOptions\n    ): TimelineCommandResult<TimelineKeyframe> => {\n      const found = engine.geometry.getClip(input.clipId);\n      let keyframe: TimelineKeyframe | null;\n      try {\n        keyframe = engine.keyframes.updateClipKeyframe(input, mutationOptions);\n      } catch (updateError: unknown) {\n        return timelineCommandInvalidInput(\n          'Timeline keyframe could not be updated from the provided input.',\n          updateError\n        );\n      }\n      if (keyframe) {\n        return timelineCommandOk(keyframe);\n      }\n\n      if (!found) {\n        return timelineCommandFail('not-found');\n      }\n      return found.track.locked ? timelineCommandFail('locked') : timelineCommandFail('not-found');\n    },\n    [engine]\n  );\n\n  const removeKeyframe = useCallback(\n    (\n      targetClipId: string,\n      keyframeId: string,\n      mutationOptions?: TimelineKeyframeMutationOptions\n    ): TimelineCommandResult<TimelineKeyframe> => {\n      const found = engine.geometry.getClip(targetClipId);\n      const keyframe = found?.clip.keyframes?.find((candidate) => candidate.id === keyframeId);\n      const removed = engine.keyframes.removeClipKeyframe(\n        targetClipId,\n        keyframeId,\n        mutationOptions\n      );\n      if (removed && keyframe) {\n        const removedKeyframe: TimelineKeyframe = {\n          ...keyframe,\n          time: { ...keyframe.time },\n        };\n        if (keyframe.incoming) {\n          removedKeyframe.incoming = { ...keyframe.incoming };\n        }\n        if (keyframe.outgoing) {\n          removedKeyframe.outgoing = { ...keyframe.outgoing };\n        }\n        return timelineCommandOk(removedKeyframe);\n      }\n\n      if (!found) {\n        return timelineCommandFail('not-found');\n      }\n      return found.track.locked ? timelineCommandFail('locked') : timelineCommandFail('not-found');\n    },\n    [engine]\n  );\n\n  const selectKeyframes = useCallback(\n    (references: readonly TimelineKeyframeReference[], mode?: 'replace' | 'add' | 'toggle') =>\n      engine.keyframes.selectKeyframes(references, mode),\n    [engine]\n  );\n\n  const clearKeyframeSelection = useCallback((): TimelineCommandResult => {\n    engine.keyframes.clearKeyframeSelection();\n    return timelineCommandOk();\n  }, [engine]);\n\n  return useMemo(\n    () => ({\n      keyframes,\n      getPropertyValueAtTime,\n      setKeyframe,\n      updateKeyframe,\n      removeKeyframe,\n      selectKeyframes,\n      selectedKeyframes,\n      copyKeyframes,\n      createPasteCommand,\n      clearKeyframeSelection,\n    }),\n    [\n      clearKeyframeSelection,\n      getPropertyValueAtTime,\n      keyframes,\n      removeKeyframe,\n      selectKeyframes,\n      selectedKeyframes,\n      copyKeyframes,\n      createPasteCommand,\n      setKeyframe,\n      updateKeyframe,\n    ]\n  );\n}\n"],"mappings":"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"}