{"version":3,"file":"useTimelineViewportRangeControl.mjs","names":[],"sources":["../../../src/hooks/viewport/useTimelineViewportRangeControl.ts"],"sourcesContent":["import {\n  formatTimelineRangeValue,\n  formatTimelineTimeValue,\n  type TimelineTimeValueFormatOptions,\n} from '#react/accessibility';\nimport { useTimelineEngine } from '#react/hooks/core/useTimelineEngine';\nimport { useTimelineViewport } from '#react/hooks/viewport/useTimelineViewport';\nimport { useRangeScrollbar } from '#react/rangeScrollbar';\nimport type {\n  RangeScrollbarAriaValueTextDetails,\n  RangeScrollbarRootProps,\n  RangeScrollbarValue,\n  RangeScrollbarValueChangeDetails,\n  UseRangeScrollbarResult,\n} from '#react/rangeScrollbar';\nimport { clamp, toSeconds } from '@techsquidtv/canvas-timeline-utils';\nimport { useCallback, useMemo } from 'react';\nconst DEFAULT_MIN_VIEW_DURATION_SECONDS = 0.1;\nconst KEYBOARD_NUDGE_PX = 40;\nconst KEYBOARD_PAGE_NUDGE_RATIO = 0.8;\n\n/**\n * Options for adapting a generic range scrollbar to the timeline viewport.\n */\nexport interface TimelineViewportRangeControlOptions extends TimelineTimeValueFormatOptions {\n  /** Smallest allowed visible timeline duration in seconds. Defaults to 0.1. */\n  minSpan?: number;\n}\n\n/**\n * Controlled props required by `RangeScrollbar.Root` for a timeline viewport.\n */\nexport type TimelineViewportRangeControlRootProps = Pick<\n  RangeScrollbarRootProps,\n  'keyboardPageStep' | 'keyboardStep' | 'max' | 'min' | 'minSpan' | 'onValueChange' | 'value'\n> &\n  Required<Pick<RangeScrollbarRootProps, 'getAriaValueText'>>;\n\n/**\n * State and control props for rendering a timeline viewport scrollbar.\n */\nexport interface UseTimelineViewportRangeControlResult {\n  /** Formatted visible range and duration. */\n  valueText: string;\n  /** Formatted visible start time. */\n  startValueText: string;\n  /** Formatted visible end time. */\n  endValueText: string;\n  /** Generic range scrollbar state derived from timeline viewport state. */\n  range: UseRangeScrollbarResult;\n  /** Props that wire `RangeScrollbar.Root` to timeline scroll and zoom state. */\n  rootProps: TimelineViewportRangeControlRootProps;\n  /** Full timeline duration represented by the scrollbar domain, in seconds. */\n  totalDurationSeconds: number;\n  /** Visible viewport start time in seconds. */\n  viewStartSeconds: number;\n  /** Visible viewport end time in seconds. */\n  viewEndSeconds: number;\n  /** Visible viewport duration in seconds. */\n  viewDurationSeconds: number;\n  /** Measured timeline viewport width in pixels. */\n  viewportWidth: number;\n  /** Current timeline zoom scale in pixels per second. */\n  zoomScale: number;\n  /** Current horizontal timeline scroll offset in pixels. */\n  scrollLeft: number;\n  /** Handles controlled range changes by panning or zooming the timeline engine. */\n  onValueChange: (value: RangeScrollbarValue, details: RangeScrollbarValueChangeDetails) => void;\n}\n\n/**\n * Adapts generic range scrollbar state to the current timeline viewport.\n *\n * The hook derives a controlled `{ start, end }` value from timeline\n * `scrollLeft`, `zoomScale`, `viewportWidth`, and content duration. Thumb\n * changes pan the engine, while handle changes update zoom and then restore the\n * requested range start.\n *\n * @param options - Timeline viewport range options.\n * @returns Timeline viewport metrics plus `RangeScrollbar.Root` control props.\n *\n * @example\n * ```tsx\n * const viewportScrollbar = useTimelineViewportRangeControl();\n *\n * return (\n *   <RangeScrollbar.Root {...viewportScrollbar.rootProps}>\n *     <RangeScrollbar.Thumb>\n *       <RangeScrollbar.Handle side=\"start\" />\n *       <RangeScrollbar.Handle side=\"end\" />\n *     </RangeScrollbar.Thumb>\n *   </RangeScrollbar.Root>\n * );\n * ```\n */\nexport function useTimelineViewportRangeControl(\n  options: TimelineViewportRangeControlOptions = {}\n): UseTimelineViewportRangeControlResult {\n  const engine = useTimelineEngine();\n  const viewport = useTimelineViewport();\n  const metrics = useMemo(() => {\n    const viewportWidth = viewport.viewportWidth;\n    const maxZoomSpan =\n      Number.isFinite(engine.maxZoomScale) && engine.maxZoomScale > 0\n        ? viewportWidth / engine.maxZoomScale\n        : 0;\n    const minSpan = Math.max(options.minSpan ?? DEFAULT_MIN_VIEW_DURATION_SECONDS, maxZoomSpan);\n    const zoomScale = viewport.zoomScale;\n    const scrollLeft = viewport.scrollLeft;\n    const totalDurationSeconds = Math.max(minSpan, toSeconds(viewport.maxContentTime));\n    const rawViewStart = scrollLeft / zoomScale;\n    const rawViewDuration = viewportWidth / zoomScale;\n    const viewDurationSeconds = Math.min(totalDurationSeconds, rawViewDuration);\n    const maxViewStart = Math.max(0, totalDurationSeconds - viewDurationSeconds);\n    const viewStartSeconds = clamp(rawViewStart, 0, maxViewStart);\n    const viewEndSeconds = viewStartSeconds + viewDurationSeconds;\n    const value = { start: viewStartSeconds, end: viewEndSeconds };\n\n    return {\n      minSpan,\n      scrollLeft,\n      totalDurationSeconds,\n      value,\n      viewDurationSeconds,\n      viewEndSeconds,\n      viewStartSeconds,\n      viewportWidth,\n      zoomScale,\n    };\n  }, [\n    engine.maxZoomScale,\n    options.minSpan,\n    viewport.maxContentTime,\n    viewport.scrollLeft,\n    viewport.viewportWidth,\n    viewport.zoomScale,\n  ]);\n\n  const onValueChange = useCallback(\n    (nextValue: RangeScrollbarValue, details: RangeScrollbarValueChangeDetails) => {\n      if (details.reason === 'thumb-drag' || details.reason === 'thumb-keyboard') {\n        engine.setScrollLeft(nextValue.start * metrics.zoomScale);\n        return;\n      }\n\n      const nextDuration = Math.max(metrics.minSpan, nextValue.end - nextValue.start);\n      engine.setZoomScale(metrics.viewportWidth / nextDuration);\n      const appliedZoomScale = engine.getState().zoomScale;\n      engine.setScrollLeft(nextValue.start * appliedZoomScale);\n    },\n    [engine, metrics.minSpan, metrics.viewportWidth, metrics.zoomScale]\n  );\n\n  const range = useRangeScrollbar({\n    min: 0,\n    max: metrics.totalDurationSeconds,\n    value: metrics.value,\n    minSpan: metrics.minSpan,\n    onValueChange,\n  });\n\n  const formatOptions = { precision: options.precision };\n  const valueText = formatTimelineRangeValue(metrics.viewStartSeconds, metrics.viewEndSeconds, {\n    ...formatOptions,\n    includeDuration: true,\n  });\n  return {\n    valueText,\n    startValueText: formatTimelineTimeValue(metrics.viewStartSeconds, formatOptions),\n    endValueText: formatTimelineTimeValue(metrics.viewEndSeconds, formatOptions),\n    range,\n    rootProps: {\n      getAriaValueText: (value: number, details: RangeScrollbarAriaValueTextDetails) =>\n        details.part === 'thumb' ? valueText : formatTimelineTimeValue(value, formatOptions),\n      min: 0,\n      max: metrics.totalDurationSeconds,\n      value: metrics.value,\n      minSpan: metrics.minSpan,\n      keyboardStep: KEYBOARD_NUDGE_PX / metrics.zoomScale,\n      keyboardPageStep: (metrics.viewportWidth * KEYBOARD_PAGE_NUDGE_RATIO) / metrics.zoomScale,\n      onValueChange,\n    },\n    totalDurationSeconds: metrics.totalDurationSeconds,\n    viewStartSeconds: metrics.viewStartSeconds,\n    viewEndSeconds: metrics.viewEndSeconds,\n    viewDurationSeconds: metrics.viewDurationSeconds,\n    viewportWidth: metrics.viewportWidth,\n    zoomScale: metrics.zoomScale,\n    scrollLeft: metrics.scrollLeft,\n    onValueChange,\n  };\n}\n"],"mappings":";;;;;;;AAiBA,MAAM,oCAAoC;AAC1C,MAAM,oBAAoB;AAC1B,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;AA4ElC,SAAgB,gCACd,UAA+C,CAAC,GACT;CACvC,MAAM,SAAS,kBAAkB;CACjC,MAAM,WAAW,oBAAoB;CACrC,MAAM,UAAU,cAAc;EAC5B,MAAM,gBAAgB,SAAS;EAC/B,MAAM,cACJ,OAAO,SAAS,OAAO,YAAY,KAAK,OAAO,eAAe,IAC1D,gBAAgB,OAAO,eACvB;EACN,MAAM,UAAU,KAAK,IAAI,QAAQ,WAAW,mCAAmC,WAAW;EAC1F,MAAM,YAAY,SAAS;EAC3B,MAAM,aAAa,SAAS;EAC5B,MAAM,uBAAuB,KAAK,IAAI,SAAS,UAAU,SAAS,cAAc,CAAC;EACjF,MAAM,eAAe,aAAa;EAClC,MAAM,kBAAkB,gBAAgB;EACxC,MAAM,sBAAsB,KAAK,IAAI,sBAAsB,eAAe;EAC1E,MAAM,eAAe,KAAK,IAAI,GAAG,uBAAuB,mBAAmB;EAC3E,MAAM,mBAAmB,MAAM,cAAc,GAAG,YAAY;EAC5D,MAAM,iBAAiB,mBAAmB;EAG1C,OAAO;GACL;GACA;GACA;GACA,OAAA;IANc,OAAO;IAAkB,KAAK;GAMxC;GACJ;GACA;GACA;GACA;GACA;EACF;CACF,GAAG;EACD,OAAO;EACP,QAAQ;EACR,SAAS;EACT,SAAS;EACT,SAAS;EACT,SAAS;CACX,CAAC;CAED,MAAM,gBAAgB,aACnB,WAAgC,YAA8C;EAC7E,IAAI,QAAQ,WAAW,gBAAgB,QAAQ,WAAW,kBAAkB;GAC1E,OAAO,cAAc,UAAU,QAAQ,QAAQ,SAAS;GACxD;EACF;EAEA,MAAM,eAAe,KAAK,IAAI,QAAQ,SAAS,UAAU,MAAM,UAAU,KAAK;EAC9E,OAAO,aAAa,QAAQ,gBAAgB,YAAY;EACxD,MAAM,mBAAmB,OAAO,SAAS,CAAC,CAAC;EAC3C,OAAO,cAAc,UAAU,QAAQ,gBAAgB;CACzD,GACA;EAAC;EAAQ,QAAQ;EAAS,QAAQ;EAAe,QAAQ;CAAS,CACpE;CAEA,MAAM,QAAQ,kBAAkB;EAC9B,KAAK;EACL,KAAK,QAAQ;EACb,OAAO,QAAQ;EACf,SAAS,QAAQ;EACjB;CACF,CAAC;CAED,MAAM,gBAAgB,EAAE,WAAW,QAAQ,UAAU;CACrD,MAAM,YAAY,yBAAyB,QAAQ,kBAAkB,QAAQ,gBAAgB;EAC3F,GAAG;EACH,iBAAiB;CACnB,CAAC;CACD,OAAO;EACL;EACA,gBAAgB,wBAAwB,QAAQ,kBAAkB,aAAa;EAC/E,cAAc,wBAAwB,QAAQ,gBAAgB,aAAa;EAC3E;EACA,WAAW;GACT,mBAAmB,OAAe,YAChC,QAAQ,SAAS,UAAU,YAAY,wBAAwB,OAAO,aAAa;GACrF,KAAK;GACL,KAAK,QAAQ;GACb,OAAO,QAAQ;GACf,SAAS,QAAQ;GACjB,cAAc,oBAAoB,QAAQ;GAC1C,kBAAmB,QAAQ,gBAAgB,4BAA6B,QAAQ;GAChF;EACF;EACA,sBAAsB,QAAQ;EAC9B,kBAAkB,QAAQ;EAC1B,gBAAgB,QAAQ;EACxB,qBAAqB,QAAQ;EAC7B,eAAe,QAAQ;EACvB,WAAW,QAAQ;EACnB,YAAY,QAAQ;EACpB;CACF;AACF"}