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