@mastra/playground-ui 42.0.0-alpha.8 → 42.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/CHANGELOG.md +234 -0
  2. package/dist/Card-BjFMX3pD.js +96 -0
  3. package/dist/Card-BjFMX3pD.js.map +1 -0
  4. package/dist/{Card-CHeBhO_f.cjs → Card-WU2qkiHB.cjs} +42 -9
  5. package/dist/Card-WU2qkiHB.cjs.map +1 -0
  6. package/dist/components/Breadcrumb.cjs.js +2 -2
  7. package/dist/components/Breadcrumb.cjs.js.map +1 -1
  8. package/dist/components/Breadcrumb.es.js +2 -2
  9. package/dist/components/Breadcrumb.es.js.map +1 -1
  10. package/dist/components/Card.cjs.js +2 -1
  11. package/dist/components/Card.cjs.js.map +1 -1
  12. package/dist/components/Card.es.js +1 -1
  13. package/dist/components/Command.cjs.js +1 -1
  14. package/dist/components/Command.es.js +1 -1
  15. package/dist/components/Composer.cjs.js +3 -3
  16. package/dist/components/Composer.cjs.js.map +1 -1
  17. package/dist/components/Composer.es.js +3 -3
  18. package/dist/components/Composer.es.js.map +1 -1
  19. package/dist/components/DataCodeSection.cjs.js +1 -1
  20. package/dist/components/DataCodeSection.es.js +1 -1
  21. package/dist/components/DataList.cjs.js +4 -4
  22. package/dist/components/DataList.es.js +4 -4
  23. package/dist/components/DataPanel.cjs.js +1 -1
  24. package/dist/components/DataPanel.es.js +1 -1
  25. package/dist/components/DateRangeTimeline.cjs.js +1011 -0
  26. package/dist/components/DateRangeTimeline.cjs.js.map +1 -0
  27. package/dist/components/DateRangeTimeline.d.ts +2 -0
  28. package/dist/components/DateRangeTimeline.es.js +1005 -0
  29. package/dist/components/DateRangeTimeline.es.js.map +1 -0
  30. package/dist/components/DateTimePicker.cjs.js +4 -3
  31. package/dist/components/DateTimePicker.cjs.js.map +1 -1
  32. package/dist/components/DateTimePicker.es.js +2 -1
  33. package/dist/components/DateTimePicker.es.js.map +1 -1
  34. package/dist/components/DateTimeRangePicker.cjs.js +1 -1
  35. package/dist/components/DateTimeRangePicker.es.js +1 -1
  36. package/dist/components/EnvironmentVariablesEditor.cjs.js +1 -1
  37. package/dist/components/EnvironmentVariablesEditor.es.js +1 -1
  38. package/dist/components/FormFieldBlocks.cjs.js +1 -1
  39. package/dist/components/FormFieldBlocks.es.js +1 -1
  40. package/dist/components/HorizontalBars.cjs.js +1 -1
  41. package/dist/components/HorizontalBars.es.js +1 -1
  42. package/dist/components/ListSearch.cjs.js +1 -1
  43. package/dist/components/ListSearch.es.js +1 -1
  44. package/dist/components/LogsDataList.cjs.js +2 -2
  45. package/dist/components/LogsDataList.es.js +2 -2
  46. package/dist/components/MainSidebar.cjs.js +39 -12
  47. package/dist/components/MainSidebar.cjs.js.map +1 -1
  48. package/dist/components/MainSidebar.es.js +39 -12
  49. package/dist/components/MainSidebar.es.js.map +1 -1
  50. package/dist/components/MetricsLineChart.cjs.js +1 -1
  51. package/dist/components/MetricsLineChart.es.js +1 -1
  52. package/dist/components/SankeyChart.cjs.js +346 -46
  53. package/dist/components/SankeyChart.cjs.js.map +1 -1
  54. package/dist/components/SankeyChart.es.js +348 -49
  55. package/dist/components/SankeyChart.es.js.map +1 -1
  56. package/dist/components/ScrollArea.cjs.js +1 -1
  57. package/dist/components/ScrollArea.es.js +1 -1
  58. package/dist/components/ThreadRail.cjs.js +1 -1
  59. package/dist/components/ThreadRail.es.js +1 -1
  60. package/dist/components/Toaster.cjs.js +1 -0
  61. package/dist/components/Toaster.cjs.js.map +1 -1
  62. package/dist/components/Toaster.es.js +1 -1
  63. package/dist/{data-code-section-CGy0owio.js → data-code-section-BjqDCzy3.js} +2 -2
  64. package/dist/{data-code-section-CGy0owio.js.map → data-code-section-BjqDCzy3.js.map} +1 -1
  65. package/dist/{data-code-section-D5yXGjk5.cjs → data-code-section-D0BwpqvV.cjs} +2 -2
  66. package/dist/{data-code-section-D5yXGjk5.cjs.map → data-code-section-D0BwpqvV.cjs.map} +1 -1
  67. package/dist/{data-list-BwGRWaNd.cjs → data-list-CQWBPREX.cjs} +3 -3
  68. package/dist/{data-list-BwGRWaNd.cjs.map → data-list-CQWBPREX.cjs.map} +1 -1
  69. package/dist/{data-list-By3xmGvT.js → data-list-Ca8j8X2e.js} +3 -3
  70. package/dist/{data-list-By3xmGvT.js.map → data-list-Ca8j8X2e.js.map} +1 -1
  71. package/dist/{data-list-row-wrapper-D0eATMXZ.cjs → data-list-row-wrapper-D-ieH7I9.cjs} +2 -2
  72. package/dist/{data-list-row-wrapper-D0eATMXZ.cjs.map → data-list-row-wrapper-D-ieH7I9.cjs.map} +1 -1
  73. package/dist/{data-list-row-wrapper--77eZufv.js → data-list-row-wrapper-dMY1gERX.js} +2 -2
  74. package/dist/{data-list-row-wrapper--77eZufv.js.map → data-list-row-wrapper-dMY1gERX.js.map} +1 -1
  75. package/dist/{data-list-skeleton-BGvCCBtu.cjs → data-list-skeleton-C-DkvqbS.cjs} +2 -2
  76. package/dist/{data-list-skeleton-BGvCCBtu.cjs.map → data-list-skeleton-C-DkvqbS.cjs.map} +1 -1
  77. package/dist/{data-list-skeleton-rDMLgcEJ.js → data-list-skeleton-CIxMXF73.js} +2 -2
  78. package/dist/{data-list-skeleton-rDMLgcEJ.js.map → data-list-skeleton-CIxMXF73.js.map} +1 -1
  79. package/dist/{data-list-top-cell-CT5c7UVt.cjs → data-list-top-cell-CL-FVkPX.cjs} +9 -4
  80. package/dist/data-list-top-cell-CL-FVkPX.cjs.map +1 -0
  81. package/dist/{data-list-top-cell-B5qdzDE4.js → data-list-top-cell-QZpCLDIZ.js} +9 -4
  82. package/dist/data-list-top-cell-QZpCLDIZ.js.map +1 -0
  83. package/dist/{data-panel-BELvPVPG.js → data-panel-BGpCzY5K.js} +2 -2
  84. package/dist/{data-panel-BELvPVPG.js.map → data-panel-BGpCzY5K.js.map} +1 -1
  85. package/dist/{data-panel-C6Aa1nFn.cjs → data-panel-CsonAnlK.cjs} +2 -2
  86. package/dist/{data-panel-C6Aa1nFn.cjs.map → data-panel-CsonAnlK.cjs.map} +1 -1
  87. package/dist/date-picker-BpKciwOX.js +48 -0
  88. package/dist/date-picker-BpKciwOX.js.map +1 -0
  89. package/dist/date-picker-DrWmUawi.cjs +50 -0
  90. package/dist/date-picker-DrWmUawi.cjs.map +1 -0
  91. package/dist/{date-time-range-picker-BRQx-ox5.cjs → date-time-range-picker-CF4a39P7.cjs} +5 -4
  92. package/dist/{date-time-range-picker-BRQx-ox5.cjs.map → date-time-range-picker-CF4a39P7.cjs.map} +1 -1
  93. package/dist/{date-time-range-picker-Cw4Ac_rW.js → date-time-range-picker-DDNeOyj9.js} +3 -2
  94. package/dist/{date-time-range-picker-Cw4Ac_rW.js.map → date-time-range-picker-DDNeOyj9.js.map} +1 -1
  95. package/dist/domains/logs/components/logs-list-view.cjs.js +2 -2
  96. package/dist/domains/logs/components/logs-list-view.es.js +2 -2
  97. package/dist/domains/metrics/components/date-range-selector.cjs.js +1 -1
  98. package/dist/domains/metrics/components/date-range-selector.es.js +1 -1
  99. package/dist/domains/metrics/components/latency-card-view.cjs.js +1 -1
  100. package/dist/domains/metrics/components/latency-card-view.es.js +1 -1
  101. package/dist/domains/metrics/components/memory-card-view.cjs.js +1 -1
  102. package/dist/domains/metrics/components/memory-card-view.es.js +1 -1
  103. package/dist/domains/metrics/components/model-usage-cost-card-view.cjs.js +1 -1
  104. package/dist/domains/metrics/components/model-usage-cost-card-view.es.js +1 -1
  105. package/dist/domains/metrics/components/scores-card-view.cjs.js +2 -2
  106. package/dist/domains/metrics/components/scores-card-view.es.js +2 -2
  107. package/dist/domains/metrics/components/token-usage-by-agent-card-view.cjs.js +1 -1
  108. package/dist/domains/metrics/components/token-usage-by-agent-card-view.es.js +1 -1
  109. package/dist/domains/metrics/components/token-usage-timeline-card-view.cjs.js +1 -1
  110. package/dist/domains/metrics/components/token-usage-timeline-card-view.es.js +1 -1
  111. package/dist/domains/metrics/components/traces-volume-card-view.cjs.js +1 -1
  112. package/dist/domains/metrics/components/traces-volume-card-view.es.js +1 -1
  113. package/dist/domains/traces/components/span-data-panel-view.cjs.js +1 -1
  114. package/dist/domains/traces/components/span-data-panel-view.es.js +1 -1
  115. package/dist/domains/traces/components/trace-data-panel-view.cjs.js +1 -1
  116. package/dist/domains/traces/components/trace-data-panel-view.es.js +1 -1
  117. package/dist/domains/traces/components/traces-list-view.cjs.js +2 -2
  118. package/dist/domains/traces/components/traces-list-view.es.js +2 -2
  119. package/dist/ee/signals/components/signals-empty-state.cjs.js +1 -1
  120. package/dist/ee/signals/components/signals-empty-state.es.js +1 -1
  121. package/dist/ee/topics/components/topic-trace-summary-list.cjs.js +1 -1
  122. package/dist/ee/topics/components/topic-trace-summary-list.es.js +1 -1
  123. package/dist/{horizontal-bars-DmWxvGBL.js → horizontal-bars-CHoNhl-X.js} +2 -2
  124. package/dist/{horizontal-bars-DmWxvGBL.js.map → horizontal-bars-CHoNhl-X.js.map} +1 -1
  125. package/dist/{horizontal-bars-C39TYAB0.cjs → horizontal-bars-aRhVh7mi.cjs} +2 -2
  126. package/dist/{horizontal-bars-C39TYAB0.cjs.map → horizontal-bars-aRhVh7mi.cjs.map} +1 -1
  127. package/dist/{logs-data-list-BYbBzVcC.cjs → logs-data-list-4R9mXlZ5.cjs} +3 -3
  128. package/dist/{logs-data-list-BYbBzVcC.cjs.map → logs-data-list-4R9mXlZ5.cjs.map} +1 -1
  129. package/dist/{logs-data-list-DbfZpFx8.js → logs-data-list-BXLk5vqv.js} +3 -3
  130. package/dist/{logs-data-list-DbfZpFx8.js.map → logs-data-list-BXLk5vqv.js.map} +1 -1
  131. package/dist/{metrics-line-chart-ByY_VXfb.js → metrics-line-chart-COIxay_u.js} +6 -3
  132. package/dist/metrics-line-chart-COIxay_u.js.map +1 -0
  133. package/dist/{metrics-line-chart-FvsUXcL7.cjs → metrics-line-chart-kZ1Ed_ga.cjs} +6 -3
  134. package/dist/metrics-line-chart-kZ1Ed_ga.cjs.map +1 -0
  135. package/dist/{scroll-area-C3deZ3yj.cjs → scroll-area-BIaiA4t3.cjs} +7 -5
  136. package/dist/scroll-area-BIaiA4t3.cjs.map +1 -0
  137. package/dist/{scroll-area-DcY6f0GS.js → scroll-area-h3DvBR0U.js} +7 -5
  138. package/dist/scroll-area-h3DvBR0U.js.map +1 -0
  139. package/dist/{search-field-block-DaXYck0w.js → search-field-block-CMoQ9fjl.js} +3 -2
  140. package/dist/search-field-block-CMoQ9fjl.js.map +1 -0
  141. package/dist/{search-field-block-BFY5jn1F.cjs → search-field-block-DmqZsrrI.cjs} +3 -2
  142. package/dist/search-field-block-DmqZsrrI.cjs.map +1 -0
  143. package/dist/src/ds/components/Card/Card.d.ts +15 -2
  144. package/dist/src/ds/components/Card/index.d.ts +2 -2
  145. package/dist/src/ds/components/Composer/composer.d.ts +5 -1
  146. package/dist/src/ds/components/Composer/index.d.ts +1 -1
  147. package/dist/src/ds/components/DataList/data-list-cells.d.ts +3 -1
  148. package/dist/src/ds/components/DateRangeTimeline/DateRangeBoundaryPicker.d.ts +10 -0
  149. package/dist/src/ds/components/DateRangeTimeline/DateRangeBoundaryPickers.d.ts +14 -0
  150. package/dist/src/ds/components/DateRangeTimeline/DateRangeHandle.d.ts +15 -0
  151. package/dist/src/ds/components/DateRangeTimeline/DateRangeSelection.d.ts +14 -0
  152. package/dist/src/ds/components/DateRangeTimeline/DateRangeTicks.d.ts +8 -0
  153. package/dist/src/ds/components/DateRangeTimeline/DateRangeTimeline.d.ts +9 -0
  154. package/dist/src/ds/components/DateRangeTimeline/DateRangeTrack.d.ts +13 -0
  155. package/dist/src/ds/components/DateRangeTimeline/DateRangeTrackGrid.d.ts +6 -0
  156. package/dist/src/ds/components/DateRangeTimeline/hooks/use-pointer-drag.d.ts +14 -0
  157. package/dist/src/ds/components/DateRangeTimeline/hooks/useDateRangeBoundaryLayout.d.ts +8 -0
  158. package/dist/src/ds/components/DateRangeTimeline/hooks/useDateRangeKeyboardControls.d.ts +13 -0
  159. package/dist/src/ds/components/DateRangeTimeline/hooks/useDateRangePointerGestures.d.ts +21 -0
  160. package/dist/src/ds/components/DateRangeTimeline/hooks/useDateRangeTimelineState.d.ts +22 -0
  161. package/dist/src/ds/components/DateRangeTimeline/hooks/useDateRangeWheelZoom.d.ts +9 -0
  162. package/dist/src/ds/components/DateRangeTimeline/index.d.ts +3 -0
  163. package/dist/src/ds/components/DateRangeTimeline/lib/date-range-boundary-layout.d.ts +15 -0
  164. package/dist/src/ds/components/DateRangeTimeline/lib/date-range-timeline-grid.d.ts +7 -0
  165. package/dist/src/ds/components/DateRangeTimeline/lib/date-range-timeline-interactions.d.ts +37 -0
  166. package/dist/src/ds/components/DateRangeTimeline/lib/date-range-timeline-view-model.d.ts +37 -0
  167. package/dist/src/ds/components/DateRangeTimeline/lib/date-range-timeline.d.ts +29 -0
  168. package/dist/src/ds/components/DateRangeTimeline/types.d.ts +9 -0
  169. package/dist/src/ds/components/MainSidebar/main-sidebar-nav-item-classes.d.ts +8 -1
  170. package/dist/src/ds/components/MainSidebar/main-sidebar-nav-label.d.ts +3 -3
  171. package/dist/src/ds/components/MainSidebar/main-sidebar-nav-link.d.ts +12 -1
  172. package/dist/src/ds/components/MainSidebar/main-sidebar.d.ts +1 -0
  173. package/dist/src/ds/components/MetricsLineChart/metrics-line-chart.d.ts +6 -1
  174. package/dist/src/ds/components/SankeyChart/index.d.ts +2 -2
  175. package/dist/src/ds/components/SankeyChart/sankey-chart-utils.d.ts +32 -1
  176. package/dist/src/ds/components/SankeyChart/sankey-chart.d.ts +4 -2
  177. package/dist/src/ds/components/SankeyChart/sankey-context.d.ts +6 -1
  178. package/dist/src/ds/components/SankeyChart/use-sankey-chart-measurements.d.ts +14 -0
  179. package/dist/src/ds/components/ScrollArea/scroll-area.d.ts +13 -1
  180. package/dist/src/ds/components/Toaster/index.d.ts +1 -1
  181. package/dist/style.css +154 -2
  182. package/dist/{time-picker-kYaitcDa.cjs → time-picker-Box0pdQU.cjs} +2 -46
  183. package/dist/time-picker-Box0pdQU.cjs.map +1 -0
  184. package/dist/{time-picker-Dg78gQcJ.js → time-picker-ew9aXG86.js} +4 -47
  185. package/dist/time-picker-ew9aXG86.js.map +1 -0
  186. package/dist/{traces-data-list-DKKrObgb.js → traces-data-list-DLmFHLQ6.js} +2 -2
  187. package/dist/{traces-data-list-DKKrObgb.js.map → traces-data-list-DLmFHLQ6.js.map} +1 -1
  188. package/dist/{traces-data-list-Bh6onSDp.cjs → traces-data-list-G69pEglx.cjs} +2 -2
  189. package/dist/{traces-data-list-Bh6onSDp.cjs.map → traces-data-list-G69pEglx.cjs.map} +1 -1
  190. package/package.json +8 -8
  191. package/dist/Card-BOmiCU4T.js +0 -64
  192. package/dist/Card-BOmiCU4T.js.map +0 -1
  193. package/dist/Card-CHeBhO_f.cjs.map +0 -1
  194. package/dist/data-list-top-cell-B5qdzDE4.js.map +0 -1
  195. package/dist/data-list-top-cell-CT5c7UVt.cjs.map +0 -1
  196. package/dist/metrics-line-chart-ByY_VXfb.js.map +0 -1
  197. package/dist/metrics-line-chart-FvsUXcL7.cjs.map +0 -1
  198. package/dist/scroll-area-C3deZ3yj.cjs.map +0 -1
  199. package/dist/scroll-area-DcY6f0GS.js.map +0 -1
  200. package/dist/search-field-block-BFY5jn1F.cjs.map +0 -1
  201. package/dist/search-field-block-DaXYck0w.js.map +0 -1
  202. package/dist/time-picker-Dg78gQcJ.js.map +0 -1
  203. package/dist/time-picker-kYaitcDa.cjs.map +0 -1
@@ -0,0 +1,1005 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useState, useLayoutEffect, useRef, useEffectEvent, useEffect } from 'react';
3
+ import { format, parseISO, isValid, differenceInCalendarDays, addDays } from 'date-fns';
4
+ import { CalendarDaysIcon } from 'lucide-react';
5
+ import { B as Button } from '../Button-Dh5CZ6Ir.js';
6
+ import { D as DatePicker } from '../date-picker-BpKciwOX.js';
7
+ import { P as Popover, b as PopoverTrigger, a as PopoverContent } from '../popover-DwDnnrzj.js';
8
+ import { T as Txt } from '../Txt-Df42XG5p.js';
9
+ import { c as cn } from '../cn-BakJzhuv.js';
10
+
11
+ const API_DATE_FORMAT = "yyyy-MM-dd";
12
+ function clamp(value, min, max) {
13
+ return Math.min(max, Math.max(min, value));
14
+ }
15
+ function parseDate(value) {
16
+ const date = parseISO(value);
17
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value) || !isValid(date)) return void 0;
18
+ return format(date, API_DATE_FORMAT) === value ? date : void 0;
19
+ }
20
+ function toDateRange(state) {
21
+ return {
22
+ from: format(addDays(state.origin, state.selection.from), API_DATE_FORMAT),
23
+ to: format(addDays(state.origin, state.selection.to), API_DATE_FORMAT)
24
+ };
25
+ }
26
+ function createTimelineState(value, min, max) {
27
+ const selectedFrom = parseDate(value.from) ?? min;
28
+ const selectedTo = parseDate(value.to) ?? max;
29
+ const absoluteSpan = Math.max(0, differenceInCalendarDays(max, min));
30
+ if (absoluteSpan === 0) {
31
+ return {
32
+ origin: min,
33
+ viewport: { from: 0, to: 0 },
34
+ selection: { from: 0, to: 0 }
35
+ };
36
+ }
37
+ const selectedFromIndex = clamp(differenceInCalendarDays(selectedFrom, min), 0, absoluteSpan);
38
+ const selectedToIndex = clamp(differenceInCalendarDays(selectedTo, min), 0, absoluteSpan);
39
+ return {
40
+ origin: min,
41
+ viewport: { from: 0, to: absoluteSpan },
42
+ selection: {
43
+ from: Math.min(selectedFromIndex, selectedToIndex),
44
+ to: Math.max(selectedFromIndex, selectedToIndex)
45
+ }
46
+ };
47
+ }
48
+ function zoomTimelineViewport(state, maximumIndex, factor, anchor) {
49
+ if (!Number.isFinite(factor) || factor <= 0 || factor === 1) return state;
50
+ const boundedMaximum = Math.max(0, maximumIndex);
51
+ const viewportSpan = state.viewport.to - state.viewport.from;
52
+ const selectionSpan = state.selection.to - state.selection.from;
53
+ const minimumViewportSpan = Math.max(selectionSpan, Math.min(1, boundedMaximum));
54
+ const scaledSpan = clamp(Math.round(viewportSpan * factor), minimumViewportSpan, boundedMaximum);
55
+ const nextSpan = scaledSpan === viewportSpan ? clamp(viewportSpan + (factor > 1 ? 1 : -1), minimumViewportSpan, boundedMaximum) : scaledSpan;
56
+ if (nextSpan === viewportSpan) return state;
57
+ const normalizedAnchor = Number.isFinite(anchor) ? clamp(anchor, 0, 1) : 0.5;
58
+ const anchorIndex = state.viewport.from + viewportSpan * normalizedAnchor;
59
+ const desiredFrom = Math.round(anchorIndex - nextSpan * normalizedAnchor);
60
+ const earliestFrom = Math.max(0, state.selection.to - nextSpan);
61
+ const latestFrom = Math.min(state.selection.from, boundedMaximum - nextSpan);
62
+ const from = clamp(desiredFrom, earliestFrom, latestFrom);
63
+ return {
64
+ ...state,
65
+ viewport: { from, to: from + nextSpan }
66
+ };
67
+ }
68
+ function revealTimelineSelection(viewport, selection, maximumIndex) {
69
+ const boundedMaximum = Math.max(0, maximumIndex);
70
+ const selectionFrom = clamp(Math.min(selection.from, selection.to), 0, boundedMaximum);
71
+ const selectionTo = clamp(Math.max(selection.from, selection.to), selectionFrom, boundedMaximum);
72
+ const viewportSpan = clamp(viewport.to - viewport.from, 0, boundedMaximum);
73
+ const viewportFrom = clamp(viewport.from, 0, boundedMaximum - viewportSpan);
74
+ const normalizedViewport = { from: viewportFrom, to: viewportFrom + viewportSpan };
75
+ const selectionSpan = selectionTo - selectionFrom;
76
+ const nextSpan = Math.max(viewportSpan, selectionSpan);
77
+ if (selectionFrom >= normalizedViewport.from && selectionTo <= normalizedViewport.to) {
78
+ if (normalizedViewport.from === viewport.from && normalizedViewport.to === viewport.to) {
79
+ return viewport;
80
+ }
81
+ return normalizedViewport;
82
+ }
83
+ const desiredFrom = selectionFrom < normalizedViewport.from ? selectionFrom : selectionTo - nextSpan;
84
+ const from = clamp(desiredFrom, 0, boundedMaximum - nextSpan);
85
+ return { from, to: from + nextSpan };
86
+ }
87
+ function getTimelinePosition(index, viewport) {
88
+ const viewportSpan = viewport.to - viewport.from;
89
+ if (viewportSpan === 0) return 50;
90
+ return (index - viewport.from) / viewportSpan * 100;
91
+ }
92
+ function buildTimelineTickOffsets(span) {
93
+ const boundedSpan = Math.max(0, Math.floor(span));
94
+ const tickCount = boundedSpan === 0 ? 1 : Math.min(5, boundedSpan + 1);
95
+ return Array.from(
96
+ { length: tickCount },
97
+ (_, index) => Math.round(boundedSpan * index / Math.max(1, tickCount - 1))
98
+ );
99
+ }
100
+ function buildTimelineTicks(domainFrom, domainTo) {
101
+ const span = Math.max(0, differenceInCalendarDays(domainTo, domainFrom));
102
+ const crossesYears = domainFrom.getFullYear() !== domainTo.getFullYear();
103
+ return buildTimelineTickOffsets(span).map((dayOffset) => {
104
+ const date = addDays(domainFrom, dayOffset);
105
+ const labelFormat = span > 365 || crossesYears ? "MMM yyyy" : "MMM d";
106
+ return {
107
+ label: format(date, labelFormat),
108
+ date: format(date, API_DATE_FORMAT),
109
+ position: span === 0 ? 50 : dayOffset / span * 100
110
+ };
111
+ });
112
+ }
113
+ function clampDateRangeToBounds(value, bounds) {
114
+ const min = parseDate(bounds.min);
115
+ const max = parseDate(bounds.max);
116
+ if (!min || !max) return value;
117
+ const from = parseDate(value.from) ?? min;
118
+ const to = parseDate(value.to) ?? max;
119
+ const clampedFrom = new Date(clamp(from.getTime(), min.getTime(), max.getTime()));
120
+ const clampedTo = new Date(clamp(to.getTime(), clampedFrom.getTime(), max.getTime()));
121
+ return {
122
+ from: format(clampedFrom, API_DATE_FORMAT),
123
+ to: format(clampedTo, API_DATE_FORMAT)
124
+ };
125
+ }
126
+ function getDateRangeBounds(createdAt, today) {
127
+ const max = parseDate(today) ?? /* @__PURE__ */ new Date();
128
+ const created = parseDate(createdAt) ?? max;
129
+ const min = created > max ? max : created;
130
+ return {
131
+ min: format(min, API_DATE_FORMAT),
132
+ max: format(max, API_DATE_FORMAT)
133
+ };
134
+ }
135
+ function formatDateRangeDuration(value) {
136
+ const from = parseDate(value.from);
137
+ const to = parseDate(value.to);
138
+ if (!from || !to) return "";
139
+ const days = Math.max(1, differenceInCalendarDays(to, from) + 1);
140
+ return days === 1 ? "1 day" : `${days} days`;
141
+ }
142
+ function formatDateRangeValueText(value) {
143
+ const from = parseDate(value.from);
144
+ const to = parseDate(value.to);
145
+ if (!from || !to) return `${value.from} through ${value.to}`;
146
+ return `${format(from, "MMMM d, yyyy")} through ${format(to, "MMMM d, yyyy")}`;
147
+ }
148
+
149
+ function DateRangeBoundaryPicker({ boundary, value, min, max, onSelect }) {
150
+ const [open, setOpen] = useState(false);
151
+ const isFrom = boundary === "from";
152
+ const boundaryLabel = isFrom ? "start date" : "end date";
153
+ const selectedDate = parseDate(value);
154
+ const firstDate = parseDate(min) ?? selectedDate;
155
+ const lastDate = parseDate(max) ?? selectedDate;
156
+ const formattedDate = selectedDate ? format(selectedDate, "MMM d, yyyy") : value;
157
+ function handleSelect(date) {
158
+ if (!date) return;
159
+ onSelect(boundary, format(date, "yyyy-MM-dd"));
160
+ setOpen(false);
161
+ }
162
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
163
+ /* @__PURE__ */ jsx(
164
+ PopoverTrigger,
165
+ {
166
+ render: /* @__PURE__ */ jsxs(
167
+ Button,
168
+ {
169
+ "aria-label": `Choose ${boundaryLabel}, ${formattedDate}`,
170
+ "aria-expanded": open,
171
+ size: "xs",
172
+ className: "h-11 w-full min-w-0 justify-between gap-2 overflow-hidden px-3 sm:h-8",
173
+ children: [
174
+ /* @__PURE__ */ jsx(Txt, { as: "span", variant: "ui-sm", className: "truncate text-neutral5 tabular-nums", children: formattedDate }),
175
+ /* @__PURE__ */ jsx(CalendarDaysIcon, { className: "size-3.5 shrink-0 text-neutral3", "aria-hidden": "true" })
176
+ ]
177
+ }
178
+ )
179
+ }
180
+ ),
181
+ /* @__PURE__ */ jsx(PopoverContent, { align: isFrom ? "start" : "end", className: "w-auto p-0", sideOffset: 6, children: /* @__PURE__ */ jsx(
182
+ DatePicker,
183
+ {
184
+ mode: "single",
185
+ selected: selectedDate,
186
+ defaultMonth: selectedDate,
187
+ fromMonth: firstDate,
188
+ toMonth: lastDate,
189
+ disabled: firstDate && lastDate ? { before: firstDate, after: lastDate } : void 0,
190
+ onSelect: handleSelect
191
+ }
192
+ ) })
193
+ ] });
194
+ }
195
+
196
+ const PREFERRED_PICKER_WIDTH = 160;
197
+ const PREFERRED_PICKER_GAP = 8;
198
+ function resolveDateRangeBoundaryLayout(requestedWidth, positions) {
199
+ const width = Math.max(0, requestedWidth);
200
+ const gap = Math.min(PREFERRED_PICKER_GAP, width);
201
+ const pickerWidth = Math.min(PREFERRED_PICKER_WIDTH, Math.max(0, (width - gap) / 2));
202
+ const maximumLeft = Math.max(0, width - pickerWidth);
203
+ const fromAnchor = clamp(positions.from, 0, 100) / 100 * width;
204
+ const toAnchor = clamp(positions.to, 0, 100) / 100 * width;
205
+ const preferredFromLeft = clamp(fromAnchor - pickerWidth - gap / 2, 0, maximumLeft);
206
+ const preferredToLeft = clamp(toAnchor + gap / 2, 0, maximumLeft);
207
+ const overlap = preferredFromLeft + pickerWidth + gap - preferredToLeft;
208
+ if (overlap <= 0) {
209
+ return {
210
+ from: { left: preferredFromLeft, width: pickerWidth },
211
+ to: { left: preferredToLeft, width: pickerWidth },
212
+ gap
213
+ };
214
+ }
215
+ const pairWidth = pickerWidth * 2 + gap;
216
+ const pairLeft = clamp(preferredFromLeft - overlap / 2, 0, Math.max(0, width - pairWidth));
217
+ return {
218
+ from: { left: pairLeft, width: pickerWidth },
219
+ to: { left: pairLeft + pickerWidth + gap, width: pickerWidth },
220
+ gap
221
+ };
222
+ }
223
+
224
+ function useDateRangeBoundaryLayout(containerRef, positions) {
225
+ const [containerWidth, setContainerWidth] = useState();
226
+ useLayoutEffect(() => {
227
+ const container = containerRef.current;
228
+ if (!container) return;
229
+ function updateWidth(width) {
230
+ if (!Number.isFinite(width) || width < 0) return;
231
+ setContainerWidth((current) => current === width ? current : width);
232
+ }
233
+ updateWidth(container.getBoundingClientRect().width);
234
+ if (typeof ResizeObserver === "undefined") return;
235
+ const observer = new ResizeObserver((entries) => {
236
+ const entry = entries.find(({ target }) => target === container) ?? entries[0];
237
+ const width = entry?.contentRect.width ?? container.getBoundingClientRect().width;
238
+ updateWidth(width);
239
+ });
240
+ observer.observe(container);
241
+ return () => observer.disconnect();
242
+ }, [containerRef]);
243
+ return containerWidth === void 0 ? void 0 : resolveDateRangeBoundaryLayout(containerWidth, positions);
244
+ }
245
+
246
+ const FALLBACK_PICKER_WIDTH = "clamp(0px, calc(50% - 0.25rem), 10rem)";
247
+ function DateRangeBoundaryPickers({ positions, value, min, max, onSelect }) {
248
+ const containerRef = useRef(null);
249
+ const layout = useDateRangeBoundaryLayout(containerRef, positions);
250
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "relative h-16 sm:h-12", children: [
251
+ /* @__PURE__ */ jsx(
252
+ "div",
253
+ {
254
+ className: "absolute top-0 min-w-0",
255
+ style: {
256
+ left: layout?.from.left ?? 0,
257
+ width: layout?.from.width ?? FALLBACK_PICKER_WIDTH
258
+ },
259
+ children: /* @__PURE__ */ jsx(DateRangeBoundaryPicker, { boundary: "from", value: value.from, min, max: value.to, onSelect })
260
+ }
261
+ ),
262
+ /* @__PURE__ */ jsx(
263
+ "div",
264
+ {
265
+ className: "absolute top-0 min-w-0",
266
+ style: {
267
+ left: layout?.to.left ?? "max(calc(50% + 0.25rem), calc(100% - 10rem))",
268
+ width: layout?.to.width ?? FALLBACK_PICKER_WIDTH
269
+ },
270
+ children: /* @__PURE__ */ jsx(DateRangeBoundaryPicker, { boundary: "to", value: value.to, min: value.from, max, onSelect })
271
+ }
272
+ ),
273
+ /* @__PURE__ */ jsx(
274
+ "div",
275
+ {
276
+ className: "absolute top-11 h-4 w-px -translate-x-1/2 bg-border2 transition-[left] duration-150 ease-out motion-reduce:transition-none sm:top-8",
277
+ style: { left: `${positions.from}%` },
278
+ "aria-hidden": "true"
279
+ }
280
+ ),
281
+ /* @__PURE__ */ jsx(
282
+ "div",
283
+ {
284
+ className: "absolute top-11 h-4 w-px -translate-x-1/2 bg-border2 transition-[left] duration-150 ease-out motion-reduce:transition-none sm:top-8",
285
+ style: { left: `${positions.to}%` },
286
+ "aria-hidden": "true"
287
+ }
288
+ )
289
+ ] });
290
+ }
291
+
292
+ function formatBoundaryDate(value) {
293
+ const date = parseDate(value);
294
+ return date ? format(date, "MMM d, yyyy") : value;
295
+ }
296
+ function DateRangeTicks({ ticks, min, max }) {
297
+ const firstTick = ticks[0];
298
+ const lastTick = ticks.at(-1);
299
+ if (!firstTick || !lastTick) return null;
300
+ const timelineLabel = `Visible from ${formatBoundaryDate(firstTick.date)} through ${formatBoundaryDate(lastTick.date)}. Available from ${formatBoundaryDate(min)} through ${formatBoundaryDate(max)}`;
301
+ if (firstTick.date === lastTick.date) {
302
+ const date = formatBoundaryDate(firstTick.date);
303
+ const singleLabel = firstTick.date === min && firstTick.date === max ? `Created today · ${date}` : firstTick.date === min ? `Created · ${date}` : firstTick.date === max ? `Today · ${date}` : date;
304
+ return /* @__PURE__ */ jsx("div", { role: "group", "aria-label": timelineLabel, className: "flex h-10 items-start justify-center pt-2", children: /* @__PURE__ */ jsx(Txt, { as: "span", variant: "ui-sm", className: "text-neutral3", children: singleLabel }) });
305
+ }
306
+ const startLabel = firstTick.date === min ? `Created · ${formatBoundaryDate(min)}` : firstTick.label;
307
+ const endLabel = lastTick.date === max ? `Today · ${formatBoundaryDate(max)}` : lastTick.label;
308
+ const middleTicks = ticks.slice(1, -1);
309
+ return /* @__PURE__ */ jsxs("div", { role: "group", className: "relative h-10", "aria-label": timelineLabel, children: [
310
+ /* @__PURE__ */ jsxs("div", { className: "absolute top-0 left-0 flex flex-col items-start", children: [
311
+ /* @__PURE__ */ jsx("span", { className: "mb-1 h-1.5 w-px bg-border2", "aria-hidden": "true" }),
312
+ /* @__PURE__ */ jsx(Txt, { as: "span", variant: "ui-sm", className: "whitespace-nowrap text-neutral3", children: startLabel })
313
+ ] }),
314
+ middleTicks.map((tick) => /* @__PURE__ */ jsxs(
315
+ "div",
316
+ {
317
+ className: "absolute top-0 hidden -translate-x-1/2 flex-col items-center sm:flex",
318
+ style: { left: `${tick.position}%` },
319
+ "aria-hidden": "true",
320
+ children: [
321
+ /* @__PURE__ */ jsx("span", { className: "mb-1 h-1.5 w-px bg-border2" }),
322
+ /* @__PURE__ */ jsx(Txt, { as: "span", variant: "ui-xs", className: "whitespace-nowrap text-neutral3", children: tick.label })
323
+ ]
324
+ },
325
+ tick.date
326
+ )),
327
+ /* @__PURE__ */ jsxs("div", { className: "absolute top-0 right-0 flex flex-col items-end", children: [
328
+ /* @__PURE__ */ jsx("span", { className: "mb-1 h-1.5 w-px bg-border2", "aria-hidden": "true" }),
329
+ /* @__PURE__ */ jsx(Txt, { as: "span", variant: "ui-sm", className: "whitespace-nowrap text-neutral3", children: endLabel })
330
+ ] })
331
+ ] });
332
+ }
333
+
334
+ function DateRangeHandle({
335
+ boundary,
336
+ position,
337
+ value,
338
+ valueText,
339
+ min,
340
+ max,
341
+ active,
342
+ onPointerDown,
343
+ onKeyDown
344
+ }) {
345
+ const label = boundary === "from" ? "Start date handle" : "End date handle";
346
+ const date = parseDate(valueText);
347
+ const accessibleValue = date ? format(date, "MMMM d, yyyy") : valueText;
348
+ return /* @__PURE__ */ jsx(
349
+ "div",
350
+ {
351
+ role: "slider",
352
+ tabIndex: 0,
353
+ "aria-label": label,
354
+ "aria-orientation": "horizontal",
355
+ "aria-valuemin": min,
356
+ "aria-valuemax": max,
357
+ "aria-valuenow": value,
358
+ "aria-valuetext": accessibleValue,
359
+ className: "group absolute inset-y-0 z-30 w-11 -translate-x-1/2 cursor-ew-resize touch-none outline-hidden",
360
+ style: { left: `${position}%` },
361
+ onPointerDown,
362
+ onKeyDown,
363
+ children: /* @__PURE__ */ jsx(
364
+ "span",
365
+ {
366
+ className: cn(
367
+ "absolute top-1/2 left-1/2 h-7 w-1.5 -translate-1/2 rounded-xs bg-surface3 ring-1 transition-[width,box-shadow] duration-150 group-hover:w-2 group-focus-visible:w-2 group-focus-visible:ring-2 group-focus-visible:ring-accent3 motion-reduce:transition-none",
368
+ active ? "w-2 ring-2 ring-neutral3/40" : "ring-border2"
369
+ ),
370
+ "aria-hidden": "true"
371
+ }
372
+ )
373
+ }
374
+ );
375
+ }
376
+
377
+ function DateRangeSelection({
378
+ left,
379
+ width,
380
+ duration,
381
+ active,
382
+ value,
383
+ valueText,
384
+ max,
385
+ onPointerDown,
386
+ onKeyDown
387
+ }) {
388
+ const visibleWidth = Math.max(width, 0.6);
389
+ const visibleLeft = Math.min(left, 100 - visibleWidth);
390
+ const showDuration = width >= 12;
391
+ return /* @__PURE__ */ jsx(
392
+ "div",
393
+ {
394
+ role: "slider",
395
+ tabIndex: 0,
396
+ "aria-label": "Move selected date range",
397
+ "aria-orientation": "horizontal",
398
+ "aria-valuemin": 0,
399
+ "aria-valuemax": max,
400
+ "aria-valuenow": value,
401
+ "aria-valuetext": valueText,
402
+ className: cn(
403
+ "absolute inset-y-1 z-10 flex cursor-grab touch-none items-center justify-center overflow-hidden rounded-md bg-neutral6/10 text-neutral6 select-none outline-hidden focus-visible:ring-2 focus-visible:ring-accent3 active:cursor-grabbing",
404
+ active ? "transition-none" : "transition-[left,width,background-color] duration-150 ease-out motion-reduce:transition-none"
405
+ ),
406
+ style: { left: `${visibleLeft}%`, width: `${visibleWidth}%` },
407
+ onPointerDown,
408
+ onKeyDown,
409
+ children: showDuration ? (
410
+ // TODO(ds): Txt needs a compact data-label variant.
411
+ /* @__PURE__ */ jsx(Txt, { as: "span", variant: "ui-sm", font: "mono", className: "text-neutral6 tabular-nums", children: duration })
412
+ ) : null
413
+ }
414
+ );
415
+ }
416
+
417
+ function getMarkerHeight(emphasis) {
418
+ if (emphasis === "major") return "h-5";
419
+ if (emphasis === "medium") return "h-3";
420
+ return "h-1.5";
421
+ }
422
+ function DateRangeTrackGrid({ markers }) {
423
+ const interiorMarkers = markers.filter((marker) => marker.position > 0 && marker.position < 100);
424
+ return /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0", "aria-hidden": "true", children: interiorMarkers.map((marker) => /* @__PURE__ */ jsx(
425
+ "span",
426
+ {
427
+ className: cn("absolute top-1/2 w-px -translate-1/2 bg-neutral3/15", getMarkerHeight(marker.emphasis)),
428
+ style: { left: `${marker.position}%` }
429
+ },
430
+ marker.index
431
+ )) });
432
+ }
433
+
434
+ const KEYBOARD_LARGE_STEP_DAYS = 7;
435
+ function getKeyboardDelta(key) {
436
+ if (key === "ArrowLeft" || key === "ArrowDown") return -1;
437
+ if (key === "ArrowRight" || key === "ArrowUp") return 1;
438
+ if (key === "PageDown") return -KEYBOARD_LARGE_STEP_DAYS;
439
+ if (key === "PageUp") return KEYBOARD_LARGE_STEP_DAYS;
440
+ return void 0;
441
+ }
442
+ function getTimelineInteraction(gesture) {
443
+ if (gesture.type === "brush") return { type: "selecting" };
444
+ if (gesture.type === "pan") return { type: "moving" };
445
+ return { type: "resizing", boundary: gesture.boundary };
446
+ }
447
+ function getTimelineIndexAtClientX(clientX, track, viewport) {
448
+ const viewportSpan = viewport.to - viewport.from;
449
+ if (!track || track.width <= 0 || viewportSpan <= 0) return viewport.from;
450
+ const position = clamp((clientX - track.left) / track.width, 0, 1);
451
+ return viewport.from + Math.round(position * viewportSpan);
452
+ }
453
+ function resolveTimelineGestureSelection(gesture, pointerIndex, viewport) {
454
+ const boundedPointerIndex = clamp(pointerIndex, viewport.from, viewport.to);
455
+ if (gesture.type === "brush") {
456
+ return {
457
+ from: Math.min(gesture.startIndex, boundedPointerIndex),
458
+ to: Math.max(gesture.startIndex, boundedPointerIndex)
459
+ };
460
+ }
461
+ if (gesture.type === "pan") {
462
+ const selectionSpan = gesture.originalSelection.to - gesture.originalSelection.from;
463
+ const delta = boundedPointerIndex - gesture.startIndex;
464
+ const from = clamp(gesture.originalSelection.from + delta, viewport.from, viewport.to - selectionSpan);
465
+ return { from, to: from + selectionSpan };
466
+ }
467
+ if (gesture.boundary === "from") {
468
+ return {
469
+ from: clamp(boundedPointerIndex, viewport.from, gesture.originalSelection.to),
470
+ to: gesture.originalSelection.to
471
+ };
472
+ }
473
+ return {
474
+ from: gesture.originalSelection.from,
475
+ to: clamp(boundedPointerIndex, gesture.originalSelection.from, viewport.to)
476
+ };
477
+ }
478
+ function moveTimelineSelectionFromKey(selection, key, maximumIndex) {
479
+ const span = selection.to - selection.from;
480
+ if (key === "Home") return { from: 0, to: span };
481
+ if (key === "End") return { from: maximumIndex - span, to: maximumIndex };
482
+ const delta = getKeyboardDelta(key);
483
+ if (delta === void 0) return void 0;
484
+ const from = clamp(selection.from + delta, 0, maximumIndex - span);
485
+ return { from, to: from + span };
486
+ }
487
+ function resizeTimelineSelectionFromKey(selection, boundary, key, maximumIndex) {
488
+ const { from, to } = selection;
489
+ if (key === "Home") return boundary === "from" ? { from: 0, to } : { from, to: from };
490
+ if (key === "End") {
491
+ return boundary === "from" ? { from: to, to } : { from, to: maximumIndex };
492
+ }
493
+ const delta = getKeyboardDelta(key);
494
+ if (delta === void 0) return void 0;
495
+ return boundary === "from" ? { from: clamp(from + delta, 0, to), to } : { from, to: clamp(to + delta, from, maximumIndex) };
496
+ }
497
+
498
+ function useDateRangeKeyboardControls({ selection, maximumIndex, onCommit }) {
499
+ function handleSelectionKeyDown(event) {
500
+ const nextSelection = moveTimelineSelectionFromKey(selection, event.key, maximumIndex);
501
+ if (!nextSelection) return;
502
+ event.preventDefault();
503
+ onCommit(nextSelection);
504
+ }
505
+ function handleBoundaryKeyDown(boundary, event) {
506
+ const nextSelection = resizeTimelineSelectionFromKey(selection, boundary, event.key, maximumIndex);
507
+ if (!nextSelection) return;
508
+ event.preventDefault();
509
+ onCommit(nextSelection);
510
+ }
511
+ function handleFromKeyDown(event) {
512
+ handleBoundaryKeyDown("from", event);
513
+ }
514
+ function handleToKeyDown(event) {
515
+ handleBoundaryKeyDown("to", event);
516
+ }
517
+ return {
518
+ handleSelectionKeyDown,
519
+ handleFromKeyDown,
520
+ handleToKeyDown
521
+ };
522
+ }
523
+
524
+ function usePointerDrag({ onMove, onEnd, onCancel }) {
525
+ const sessionRef = useRef(void 0);
526
+ function startPointerDrag(event, payload) {
527
+ if (sessionRef.current) return false;
528
+ const captureTarget = event.currentTarget;
529
+ captureTarget.setPointerCapture(event.pointerId);
530
+ sessionRef.current = {
531
+ pointerId: event.pointerId,
532
+ captureTarget,
533
+ payload
534
+ };
535
+ return true;
536
+ }
537
+ function getSession(event) {
538
+ const session = sessionRef.current;
539
+ if (!session || session.pointerId !== event.pointerId) return void 0;
540
+ return session;
541
+ }
542
+ function releaseSession(session) {
543
+ sessionRef.current = void 0;
544
+ if (!session.captureTarget.hasPointerCapture(session.pointerId)) return;
545
+ session.captureTarget.releasePointerCapture(session.pointerId);
546
+ }
547
+ function handlePointerMove(event) {
548
+ const session = getSession(event);
549
+ if (!session) return;
550
+ onMove(session.payload, event);
551
+ }
552
+ function handlePointerUp(event) {
553
+ const session = getSession(event);
554
+ if (!session) return;
555
+ releaseSession(session);
556
+ onEnd(session.payload, event);
557
+ }
558
+ function handlePointerCancel(event) {
559
+ const session = getSession(event);
560
+ if (!session) return;
561
+ releaseSession(session);
562
+ onCancel(session.payload, event);
563
+ }
564
+ function handleLostPointerCapture(event) {
565
+ const session = getSession(event);
566
+ if (!session) return;
567
+ sessionRef.current = void 0;
568
+ onCancel(session.payload, event);
569
+ }
570
+ return {
571
+ startPointerDrag,
572
+ handlePointerMove,
573
+ handlePointerUp,
574
+ handlePointerCancel,
575
+ handleLostPointerCapture
576
+ };
577
+ }
578
+
579
+ function useDateRangePointerGestures({
580
+ trackRef,
581
+ timeline,
582
+ onSelectionPreview,
583
+ onSelectionCommit,
584
+ onSelectionCancel
585
+ }) {
586
+ const [interaction, setInteraction] = useState();
587
+ function getPointerIndex(clientX) {
588
+ const track = trackRef.current?.getBoundingClientRect();
589
+ return getTimelineIndexAtClientX(clientX, track, timeline.viewport);
590
+ }
591
+ function previewPointerDrag(drag, event) {
592
+ const pointerIndex = getPointerIndex(event.clientX);
593
+ const selection = resolveTimelineGestureSelection(drag.gesture, pointerIndex, timeline.viewport);
594
+ drag.currentSelection = selection;
595
+ onSelectionPreview(selection);
596
+ }
597
+ function commitPointerDrag(drag, event) {
598
+ previewPointerDrag(drag, event);
599
+ setInteraction(void 0);
600
+ onSelectionCommit(drag.currentSelection);
601
+ }
602
+ function cancelPointerDrag() {
603
+ setInteraction(void 0);
604
+ onSelectionCancel();
605
+ }
606
+ const pointerDrag = usePointerDrag({
607
+ onMove: previewPointerDrag,
608
+ onEnd: commitPointerDrag,
609
+ onCancel: cancelPointerDrag
610
+ });
611
+ function beginPointerDrag(event, gesture, currentSelection) {
612
+ const started = pointerDrag.startPointerDrag(event, { gesture, currentSelection });
613
+ if (!started) return false;
614
+ setInteraction(getTimelineInteraction(gesture));
615
+ return true;
616
+ }
617
+ function startBrush(event) {
618
+ if (event.button !== 0) return;
619
+ event.preventDefault();
620
+ const startIndex = getPointerIndex(event.clientX);
621
+ const selection = { from: startIndex, to: startIndex };
622
+ const started = beginPointerDrag(
623
+ event,
624
+ {
625
+ type: "brush",
626
+ startIndex
627
+ },
628
+ selection
629
+ );
630
+ if (!started) return;
631
+ onSelectionPreview(selection);
632
+ }
633
+ function startPan(event) {
634
+ if (event.button !== 0) return;
635
+ event.preventDefault();
636
+ event.stopPropagation();
637
+ beginPointerDrag(
638
+ event,
639
+ {
640
+ type: "pan",
641
+ startIndex: getPointerIndex(event.clientX),
642
+ originalSelection: timeline.selection
643
+ },
644
+ timeline.selection
645
+ );
646
+ }
647
+ function startHandle(boundary, event) {
648
+ if (event.button !== 0) return;
649
+ event.preventDefault();
650
+ event.stopPropagation();
651
+ beginPointerDrag(
652
+ event,
653
+ {
654
+ type: "handle",
655
+ boundary,
656
+ originalSelection: timeline.selection
657
+ },
658
+ timeline.selection
659
+ );
660
+ }
661
+ function handlePointerMove(event) {
662
+ pointerDrag.handlePointerMove(event);
663
+ }
664
+ function handlePointerUp(event) {
665
+ pointerDrag.handlePointerUp(event);
666
+ }
667
+ function handlePointerCancel(event) {
668
+ pointerDrag.handlePointerCancel(event);
669
+ }
670
+ function handleLostPointerCapture(event) {
671
+ pointerDrag.handleLostPointerCapture(event);
672
+ }
673
+ return {
674
+ interaction,
675
+ startBrush,
676
+ startPan,
677
+ startHandle,
678
+ handlePointerMove,
679
+ handlePointerUp,
680
+ handlePointerCancel,
681
+ handleLostPointerCapture
682
+ };
683
+ }
684
+
685
+ const MAX_WHEEL_DELTA = 80;
686
+ const WHEEL_ZOOM_SENSITIVITY = 3e-3;
687
+ function useDateRangeWheelZoom({ rootRef, trackRef, disabled, onZoom }) {
688
+ const handleWheel = useEffectEvent((event) => {
689
+ if (!event.ctrlKey) return;
690
+ if (disabled) return;
691
+ event.preventDefault();
692
+ const trackRect = trackRef.current?.getBoundingClientRect();
693
+ const anchor = trackRect && trackRect.width > 0 ? clamp((event.clientX - trackRect.left) / trackRect.width, 0, 1) : 0.5;
694
+ const wheelDelta = clamp(event.deltaY, -MAX_WHEEL_DELTA, MAX_WHEEL_DELTA);
695
+ const factor = Math.exp(wheelDelta * WHEEL_ZOOM_SENSITIVITY);
696
+ onZoom(factor, anchor);
697
+ });
698
+ useEffect(() => {
699
+ const root = rootRef.current;
700
+ if (!root) return;
701
+ root.addEventListener("wheel", handleWheel, { passive: false });
702
+ return () => root.removeEventListener("wheel", handleWheel);
703
+ }, [rootRef]);
704
+ }
705
+
706
+ const MAX_GRID_INTERVALS = 24;
707
+ function addVisibleMarker(markerIndices, index, viewport) {
708
+ if (index >= viewport.from && index <= viewport.to) {
709
+ markerIndices.add(index);
710
+ }
711
+ }
712
+ function getMarkerEmphasis(index, majorIndices, mediumIndices) {
713
+ if (majorIndices.has(index)) return "major";
714
+ if (mediumIndices.has(index)) return "medium";
715
+ return "minor";
716
+ }
717
+ function createDateRangeGridMarkers(viewport, selection) {
718
+ const viewportSpan = Math.max(0, viewport.to - viewport.from);
719
+ if (viewportSpan === 0) {
720
+ return [{ index: viewport.from, position: 50, emphasis: "major" }];
721
+ }
722
+ const sampleStep = Math.max(1, Math.ceil(viewportSpan / MAX_GRID_INTERVALS));
723
+ const markerIndices = /* @__PURE__ */ new Set();
724
+ const majorIndices = /* @__PURE__ */ new Set();
725
+ const mediumIndices = /* @__PURE__ */ new Set();
726
+ const tickOffsets = buildTimelineTickOffsets(viewportSpan);
727
+ for (let index = viewport.from; index <= viewport.to; index += sampleStep) {
728
+ markerIndices.add(index);
729
+ }
730
+ for (let index = 0; index < tickOffsets.length; index += 1) {
731
+ const tickOffset = tickOffsets[index];
732
+ if (tickOffset === void 0) continue;
733
+ const majorIndex = viewport.from + tickOffset;
734
+ markerIndices.add(majorIndex);
735
+ majorIndices.add(majorIndex);
736
+ const nextTickOffset = tickOffsets[index + 1];
737
+ if (nextTickOffset === void 0) continue;
738
+ const mediumIndex = viewport.from + Math.round((tickOffset + nextTickOffset) / 2);
739
+ markerIndices.add(mediumIndex);
740
+ mediumIndices.add(mediumIndex);
741
+ }
742
+ addVisibleMarker(markerIndices, selection.from, viewport);
743
+ addVisibleMarker(markerIndices, selection.to, viewport);
744
+ return [...markerIndices].sort((left, right) => left - right).map((index) => ({
745
+ index,
746
+ position: getTimelinePosition(index, viewport),
747
+ emphasis: getMarkerEmphasis(index, majorIndices, mediumIndices)
748
+ }));
749
+ }
750
+
751
+ function createDateRangeBoundaryModel(state) {
752
+ const range = toDateRange(state);
753
+ const fromPosition = getTimelinePosition(state.selection.from, state.viewport);
754
+ const toPosition = getTimelinePosition(state.selection.to, state.viewport);
755
+ return {
756
+ positions: { from: fromPosition, to: toPosition },
757
+ range
758
+ };
759
+ }
760
+ function createDateRangeTrackModel(state, maximumIndex) {
761
+ const range = toDateRange(state);
762
+ const fromPosition = getTimelinePosition(state.selection.from, state.viewport);
763
+ const toPosition = getTimelinePosition(state.selection.to, state.viewport);
764
+ const span = state.selection.to - state.selection.from;
765
+ return {
766
+ handles: {
767
+ from: {
768
+ position: fromPosition,
769
+ value: state.selection.from,
770
+ valueText: range.from,
771
+ min: 0,
772
+ max: state.selection.to
773
+ },
774
+ to: {
775
+ position: toPosition,
776
+ value: state.selection.to,
777
+ valueText: range.to,
778
+ min: state.selection.from,
779
+ max: maximumIndex
780
+ }
781
+ },
782
+ selection: {
783
+ left: fromPosition,
784
+ width: Math.max(0, toPosition - fromPosition),
785
+ duration: formatDateRangeDuration(range),
786
+ value: state.selection.from,
787
+ valueText: formatDateRangeValueText(range),
788
+ max: maximumIndex - span
789
+ }
790
+ };
791
+ }
792
+ function createDateRangeAxisModel(state) {
793
+ const visibleFrom = addDays(state.origin, state.viewport.from);
794
+ const visibleTo = addDays(state.origin, state.viewport.to);
795
+ return { ticks: buildTimelineTicks(visibleFrom, visibleTo) };
796
+ }
797
+
798
+ function DateRangeTrack({
799
+ wheelTargetRef,
800
+ timeline,
801
+ maximumIndex,
802
+ onSelectionPreview,
803
+ onSelectionCommit,
804
+ onSelectionCancel,
805
+ onZoom
806
+ }) {
807
+ const trackRef = useRef(null);
808
+ const pointer = useDateRangePointerGestures({
809
+ trackRef,
810
+ timeline,
811
+ onSelectionPreview,
812
+ onSelectionCommit,
813
+ onSelectionCancel
814
+ });
815
+ const { handleSelectionKeyDown, handleFromKeyDown, handleToKeyDown } = useDateRangeKeyboardControls({
816
+ selection: timeline.selection,
817
+ maximumIndex,
818
+ onCommit: onSelectionCommit
819
+ });
820
+ useDateRangeWheelZoom({
821
+ rootRef: wheelTargetRef,
822
+ trackRef,
823
+ disabled: pointer.interaction !== void 0,
824
+ onZoom
825
+ });
826
+ const model = createDateRangeTrackModel(timeline, maximumIndex);
827
+ const gridMarkers = createDateRangeGridMarkers(timeline.viewport, timeline.selection);
828
+ function startFromHandle(event) {
829
+ pointer.startHandle("from", event);
830
+ }
831
+ function startToHandle(event) {
832
+ pointer.startHandle("to", event);
833
+ }
834
+ return /* @__PURE__ */ jsxs(
835
+ "div",
836
+ {
837
+ ref: trackRef,
838
+ role: "group",
839
+ "aria-label": "Date range selection area",
840
+ className: cn(
841
+ "relative h-12 touch-none rounded-lg bg-surface4",
842
+ pointer.interaction?.type === "selecting" ? "cursor-col-resize" : "cursor-crosshair"
843
+ ),
844
+ onPointerDown: pointer.startBrush,
845
+ onPointerMove: pointer.handlePointerMove,
846
+ onPointerUp: pointer.handlePointerUp,
847
+ onPointerCancel: pointer.handlePointerCancel,
848
+ onLostPointerCapture: pointer.handleLostPointerCapture,
849
+ children: [
850
+ /* @__PURE__ */ jsx(DateRangeTrackGrid, { markers: gridMarkers }),
851
+ /* @__PURE__ */ jsx(
852
+ DateRangeSelection,
853
+ {
854
+ left: model.selection.left,
855
+ width: model.selection.width,
856
+ duration: model.selection.duration,
857
+ active: pointer.interaction !== void 0,
858
+ value: model.selection.value,
859
+ valueText: model.selection.valueText,
860
+ max: model.selection.max,
861
+ onPointerDown: pointer.startPan,
862
+ onKeyDown: handleSelectionKeyDown
863
+ }
864
+ ),
865
+ /* @__PURE__ */ jsx(
866
+ DateRangeHandle,
867
+ {
868
+ boundary: "from",
869
+ position: model.handles.from.position,
870
+ value: model.handles.from.value,
871
+ valueText: model.handles.from.valueText,
872
+ min: model.handles.from.min,
873
+ max: model.handles.from.max,
874
+ active: pointer.interaction?.type === "resizing" && pointer.interaction.boundary === "from",
875
+ onPointerDown: startFromHandle,
876
+ onKeyDown: handleFromKeyDown
877
+ }
878
+ ),
879
+ /* @__PURE__ */ jsx(
880
+ DateRangeHandle,
881
+ {
882
+ boundary: "to",
883
+ position: model.handles.to.position,
884
+ value: model.handles.to.value,
885
+ valueText: model.handles.to.valueText,
886
+ min: model.handles.to.min,
887
+ max: model.handles.to.max,
888
+ active: pointer.interaction?.type === "resizing" && pointer.interaction.boundary === "to",
889
+ onPointerDown: startToHandle,
890
+ onKeyDown: handleToKeyDown
891
+ }
892
+ )
893
+ ]
894
+ }
895
+ );
896
+ }
897
+
898
+ function resolveTimelineBounds(min, max) {
899
+ const fallbackMax = /* @__PURE__ */ new Date();
900
+ const maxDate = parseDate(max) ?? fallbackMax;
901
+ const requestedMin = parseDate(min) ?? addDays(maxDate, -365);
902
+ const minDate = requestedMin > maxDate ? maxDate : requestedMin;
903
+ return { minDate, maxDate };
904
+ }
905
+ function useDateRangeTimelineState({ value, min, max, onCommit }) {
906
+ const { minDate, maxDate } = resolveTimelineBounds(min, max);
907
+ const committedTimeline = createTimelineState(value, minDate, maxDate);
908
+ const maximumIndex = Math.max(0, differenceInCalendarDays(maxDate, minDate));
909
+ const [viewport, setViewport] = useState(() => committedTimeline.viewport);
910
+ const [draftSelection, setDraftSelection] = useState();
911
+ const selection = draftSelection ?? committedTimeline.selection;
912
+ const state = {
913
+ ...committedTimeline,
914
+ viewport: revealTimelineSelection(viewport, selection, maximumIndex),
915
+ selection
916
+ };
917
+ function previewSelection(nextSelection) {
918
+ setDraftSelection(nextSelection);
919
+ }
920
+ function cancelSelection() {
921
+ setDraftSelection(void 0);
922
+ }
923
+ function commitSelection(nextSelection) {
924
+ setDraftSelection(void 0);
925
+ setViewport((current) => revealTimelineSelection(current, nextSelection, maximumIndex));
926
+ onCommit(toDateRange({ ...state, selection: nextSelection }));
927
+ }
928
+ function zoom(factor, anchor) {
929
+ setViewport((current) => {
930
+ const visibleViewport = revealTimelineSelection(current, state.selection, maximumIndex);
931
+ return zoomTimelineViewport({ ...state, viewport: visibleViewport }, maximumIndex, factor, anchor).viewport;
932
+ });
933
+ }
934
+ function selectDate(boundary, nextValue) {
935
+ const date = parseDate(nextValue);
936
+ if (!date) return;
937
+ const dateIndex = clamp(differenceInCalendarDays(date, state.origin), 0, maximumIndex);
938
+ const nextSelection = boundary === "from" ? {
939
+ from: clamp(dateIndex, 0, state.selection.to),
940
+ to: state.selection.to
941
+ } : {
942
+ from: state.selection.from,
943
+ to: clamp(dateIndex, state.selection.from, maximumIndex)
944
+ };
945
+ commitSelection(nextSelection);
946
+ }
947
+ return {
948
+ state,
949
+ maximumIndex,
950
+ previewSelection,
951
+ cancelSelection,
952
+ commitSelection,
953
+ zoom,
954
+ selectDate
955
+ };
956
+ }
957
+
958
+ function DateRangeTimeline({ value, min, max, onCommit }) {
959
+ const rootRef = useRef(null);
960
+ const timeline = useDateRangeTimelineState({
961
+ value,
962
+ min,
963
+ max,
964
+ onCommit
965
+ });
966
+ const boundaryModel = createDateRangeBoundaryModel(timeline.state);
967
+ const axisModel = createDateRangeAxisModel(timeline.state);
968
+ return /* @__PURE__ */ jsxs(
969
+ "div",
970
+ {
971
+ ref: rootRef,
972
+ role: "group",
973
+ "aria-label": "Date range timeline",
974
+ className: "w-full overflow-x-clip select-none [overflow-clip-margin:0.75rem]",
975
+ children: [
976
+ /* @__PURE__ */ jsx(
977
+ DateRangeBoundaryPickers,
978
+ {
979
+ positions: boundaryModel.positions,
980
+ value: boundaryModel.range,
981
+ min,
982
+ max,
983
+ onSelect: timeline.selectDate
984
+ }
985
+ ),
986
+ /* @__PURE__ */ jsx(
987
+ DateRangeTrack,
988
+ {
989
+ wheelTargetRef: rootRef,
990
+ timeline: timeline.state,
991
+ maximumIndex: timeline.maximumIndex,
992
+ onSelectionPreview: timeline.previewSelection,
993
+ onSelectionCommit: timeline.commitSelection,
994
+ onSelectionCancel: timeline.cancelSelection,
995
+ onZoom: timeline.zoom
996
+ }
997
+ ),
998
+ /* @__PURE__ */ jsx(DateRangeTicks, { ticks: axisModel.ticks, min, max })
999
+ ]
1000
+ }
1001
+ );
1002
+ }
1003
+
1004
+ export { DateRangeTimeline, clampDateRangeToBounds, getDateRangeBounds };
1005
+ //# sourceMappingURL=DateRangeTimeline.es.js.map