{"version":3,"file":"useTimelineInOutRangeControl.mjs","names":[],"sources":["../../../src/hooks/viewport/useTimelineInOutRangeControl.ts"],"sourcesContent":["import { useTimelineViewportBounds } from '#react/hooks/viewport/useTimelineViewportBounds';\nimport { formatTimelineRangeValue, formatTimelineTimeValue } from '#react/accessibility';\nimport type { TimelineControlCommitDetails } from '#react/hooks/core/timelineControlEvents';\nimport { useTimelineEngine } from '#react/hooks/core/useTimelineEngine';\nimport { useTimelineSelector } from '#react/hooks/core/useTimelineSelector';\nimport { clamp, fromSeconds, toSeconds } from '@techsquidtv/canvas-timeline-utils';\nimport { useCallback, useMemo, useRef } from 'react';\n/**\n * Options for adapting timeline In/Out points to a range control.\n */\nexport interface TimelineInOutRangeControlOptions {\n  /** Minimum timeline time in seconds. Defaults to 0. */\n  min?: number;\n  /** Maximum timeline time in seconds. Defaults to timeline duration or 100. */\n  max?: number;\n  /** Whether pointer-driven changes should snap to timeline targets. */\n  snap?: boolean;\n  /** Slider step in seconds for consumers using slider primitives. */\n  step?: number;\n  /** Accessible label for the in-point thumb. */\n  inLabel?: string;\n  /** Accessible label for the out-point thumb. */\n  outLabel?: string;\n  /**\n   * Called after the hook applies a committed range value to the engine.\n   *\n   * When the underlying range control provides commit details, Canvas Timeline\n   * forwards them unchanged as the second argument and does not read their\n   * fields.\n   */\n  onValueCommitted?: (\n    value: readonly number[],\n    eventDetails?: TimelineControlCommitDetails\n  ) => void;\n}\n\n/**\n * Change metadata emitted by Base UI range slider interactions.\n */\nexport interface RangeChangeDetails {\n  /** Index of the thumb that produced the value change. */\n  activeThumbIndex?: number;\n  /** Base UI interaction reason for the value change. */\n  reason?: string;\n}\n\n/**\n * Adapts timeline In/Out points to a Base UI-compatible range slider.\n *\n * The hook keeps loop or export boundaries in sync with `TimelineEngine`\n * `inPoint` and `outPoint` values while returning semantic labels and\n * `aria-valuetext` strings for each thumb. `Timeline.RangeSelector.Root` uses\n * this hook internally; consumers can call it directly when composing their own\n * Base UI slider surface.\n *\n * @param options - Optional bounds, step size, thumb labels, and commit callback.\n * @returns Current In/Out values in seconds, formatted range text, engine commands, and props for range slider root and thumbs.\n *\n * @example\n * ```tsx\n * const range = useTimelineInOutRangeControl();\n *\n * return (\n *   <Slider.Root {...range.rootProps}>\n *     <Slider.Control>\n *       <Slider.Track>\n *         <Slider.Indicator />\n *         <Slider.Thumb index={0} {...range.inThumbProps} />\n *         <Slider.Thumb index={1} {...range.outThumbProps} />\n *       </Slider.Track>\n *     </Slider.Control>\n *   </Slider.Root>\n * );\n * ```\n */\nexport function useTimelineInOutRangeControl(options: TimelineInOutRangeControlOptions = {}) {\n  const {\n    inLabel,\n    max: optionMax,\n    min: optionMin,\n    onValueCommitted,\n    outLabel,\n    snap = false,\n    step,\n  } = options;\n  const engine = useTimelineEngine();\n  const bounds = useTimelineViewportBounds();\n  const state = useTimelineSelector((state) => ({\n    inPoint: state.inPoint,\n    outPoint: state.outPoint,\n  }));\n  const preparedSnapIndexRef = useRef<number | null>(null);\n  const control = useMemo(() => {\n    const min = optionMin ?? 0;\n    const max = optionMax ?? toSeconds(bounds.maxContentTime);\n    const value: [number, number] = [\n      state.inPoint ? toSeconds(state.inPoint) : min,\n      state.outPoint ? toSeconds(state.outPoint) : max,\n    ];\n\n    return {\n      inValueText: formatTimelineTimeValue(value[0]),\n      max,\n      min,\n      outValueText: formatTimelineTimeValue(value[1]),\n      step: step ?? 0.01,\n      value,\n      valueText: formatTimelineRangeValue(value[0], value[1], { includeDuration: true }),\n    };\n  }, [optionMax, optionMin, bounds.maxContentTime, state.inPoint, state.outPoint, step]);\n\n  const setValue = useCallback(\n    (nextValue: number[], eventDetails?: RangeChangeDetails) => {\n      const activeThumbIndex = eventDetails?.activeThumbIndex;\n      const shouldSnap =\n        snap && (eventDetails?.reason === 'drag' || eventDetails?.reason === 'track-press');\n\n      if (!shouldSnap) {\n        preparedSnapIndexRef.current = null;\n      } else if (preparedSnapIndexRef.current !== (activeThumbIndex ?? -1)) {\n        engine.prepareSnapping({\n          ignoreInPoint: activeThumbIndex === 0,\n          ignoreOutPoint: activeThumbIndex === 1,\n        });\n        preparedSnapIndexRef.current = activeThumbIndex ?? -1;\n      }\n\n      const nextIn =\n        nextValue[0] === undefined ? undefined : clamp(nextValue[0], control.min, control.max);\n      const nextOut =\n        nextValue[1] === undefined ? undefined : clamp(nextValue[1], control.min, control.max);\n\n      if (\n        !shouldSnap &&\n        nextIn !== undefined &&\n        nextOut !== undefined &&\n        nextIn !== control.value[0] &&\n        nextOut !== control.value[1]\n      ) {\n        engine.setInOutRange(fromSeconds(nextIn), fromSeconds(nextOut));\n        return;\n      }\n\n      if (nextIn !== undefined && nextIn !== control.value[0]) {\n        engine.setInPoint(fromSeconds(nextIn), shouldSnap && activeThumbIndex === 0);\n      }\n      if (nextOut !== undefined && nextOut !== control.value[1]) {\n        engine.setOutPoint(fromSeconds(nextOut), shouldSnap && activeThumbIndex === 1);\n      }\n    },\n    [control.max, control.min, control.value, engine, snap]\n  );\n\n  const clear = useCallback(() => {\n    engine.clearInOutPoints();\n  }, [engine]);\n\n  const commit = useCallback(\n    (nextValue = control.value) => {\n      setValue([...nextValue]);\n      engine.settle();\n    },\n    [control.value, engine, setValue]\n  );\n\n  return useMemo(\n    () => ({\n      /** Maximum timeline boundary time in seconds. */\n      max: control.max,\n      /** Minimum timeline boundary time in seconds. */\n      min: control.min,\n      /** Slider step in seconds. */\n      step: control.step,\n      /** Current `[inPoint, outPoint]` values in seconds. */\n      value: control.value,\n      /** Formatted range summary for assistive technology. */\n      valueText: control.valueText,\n      /** Formatted in-point value for assistive technology. */\n      inValueText: control.inValueText,\n      /** Formatted out-point value for assistive technology. */\n      outValueText: control.outValueText,\n      /** Updates In/Out points from second values without settling history. */\n      setValue,\n      /** Clears both In and Out points. */\n      clear,\n      /** Updates In/Out points and settles the engine interaction. */\n      commit,\n      /** Formats a timeline second value for `aria-valuetext`. */\n      getAriaValueText: (nextValue: number) => formatTimelineTimeValue(nextValue),\n      /** Props for a Base UI `Slider.Root`. */\n      rootProps: {\n        min: control.min,\n        max: control.max,\n        step: control.step,\n        value: control.value,\n        onValueChange: setValue,\n        onValueCommitted: (\n          values: readonly number[],\n          eventDetails?: TimelineControlCommitDetails\n        ) => {\n          preparedSnapIndexRef.current = null;\n          engine.settle();\n          onValueCommitted?.(values, eventDetails);\n        },\n      },\n      /** Props for the Base UI thumb controlling the In point. */\n      inThumbProps: {\n        'aria-label': inLabel ?? 'In point',\n        'aria-valuetext': control.inValueText,\n      },\n      /** Props for the Base UI thumb controlling the Out point. */\n      outThumbProps: {\n        'aria-label': outLabel ?? 'Out point',\n        'aria-valuetext': control.outValueText,\n      },\n    }),\n    [clear, commit, control, engine, inLabel, onValueCommitted, outLabel, setValue]\n  );\n}\n"],"mappings":"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"}