@xh/hoist 87.1.1 → 87.3.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/.npmignore +39 -0
- package/CHANGELOG.md +108 -20
- package/admin/AdminUtils.ts +64 -0
- package/admin/App.scss +88 -0
- package/admin/columns/UserData.ts +30 -3
- package/admin/detail/RestDetailModel.ts +64 -0
- package/admin/detail/RestDetailPanel.ts +106 -0
- package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +27 -94
- package/admin/tabs/activity/tracking/ActivityTrackingPanel.ts +23 -60
- package/admin/tabs/activity/tracking/detail/ActivityDetailView.ts +1 -2
- package/admin/tabs/general/alertBanner/AlertBannerPanel.ts +2 -13
- package/admin/tabs/general/config/ConfigDetailPanel.ts +72 -0
- package/admin/tabs/general/config/ConfigPanel.ts +41 -53
- package/admin/tabs/general/config/ConfigPanelModel.ts +3 -0
- package/admin/tabs/general/config/ConfigValue.scss +14 -0
- package/admin/tabs/general/config/ConfigValue.ts +104 -65
- package/admin/tabs/userData/jsonblob/JsonBlobDetailPanel.ts +93 -0
- package/admin/tabs/userData/jsonblob/JsonBlobModel.ts +4 -0
- package/admin/tabs/userData/jsonblob/JsonBlobPanel.ts +2 -0
- package/admin/tabs/userData/prefs/UserPreferenceDetailPanel.ts +62 -0
- package/admin/tabs/userData/prefs/UserPreferenceModel.ts +10 -3
- package/admin/tabs/userData/prefs/UserPreferencePanel.ts +3 -1
- package/admin/tabs/userData/roles/details/RoleDetails.scss +2 -19
- package/admin/tabs/userData/roles/details/RoleDetails.ts +13 -13
- package/appcontainer/AboutDialogModel.ts +1 -0
- package/appcontainer/AppContainerModel.ts +3 -0
- package/appcontainer/AppStateModel.ts +1 -0
- package/appcontainer/BannerSourceModel.ts +1 -0
- package/appcontainer/ChangelogDialogModel.ts +1 -0
- package/appcontainer/ColChooserOptionsModel.ts +1 -0
- package/appcontainer/ExceptionDialogModel.ts +1 -0
- package/appcontainer/FeedbackDialogModel.ts +1 -0
- package/appcontainer/ImpersonationBarModel.ts +1 -0
- package/appcontainer/MessageSourceModel.ts +1 -0
- package/appcontainer/OptionsDialogModel.ts +1 -0
- package/appcontainer/PageStateModel.ts +1 -0
- package/appcontainer/RouterModel.ts +2 -0
- package/appcontainer/SizingModeModel.ts +1 -0
- package/appcontainer/ThemeModel.ts +1 -0
- package/appcontainer/ToastSourceModel.ts +1 -0
- package/appcontainer/UserAgentModel.ts +1 -0
- package/appcontainer/ViewportSizeModel.ts +1 -0
- package/build/types/admin/AdminUtils.d.ts +29 -0
- package/build/types/admin/columns/UserData.d.ts +3 -0
- package/build/types/admin/detail/RestDetailModel.d.ts +20 -0
- package/build/types/admin/detail/RestDetailPanel.d.ts +34 -0
- package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +5 -17
- package/build/types/admin/tabs/general/config/ConfigDetailPanel.d.ts +7 -0
- package/build/types/admin/tabs/general/config/ConfigValue.d.ts +10 -4
- package/build/types/admin/tabs/userData/jsonblob/JsonBlobDetailPanel.d.ts +3 -0
- package/build/types/admin/tabs/userData/prefs/UserPreferenceDetailPanel.d.ts +3 -0
- package/build/types/admin/tabs/userData/prefs/UserPreferenceModel.d.ts +3 -0
- package/build/types/appcontainer/AboutDialogModel.d.ts +1 -0
- package/build/types/appcontainer/AppContainerModel.d.ts +1 -0
- package/build/types/appcontainer/AppStateModel.d.ts +1 -0
- package/build/types/appcontainer/BannerSourceModel.d.ts +1 -0
- package/build/types/appcontainer/ChangelogDialogModel.d.ts +1 -0
- package/build/types/appcontainer/ColChooserOptionsModel.d.ts +1 -0
- package/build/types/appcontainer/ExceptionDialogModel.d.ts +1 -0
- package/build/types/appcontainer/FeedbackDialogModel.d.ts +1 -0
- package/build/types/appcontainer/ImpersonationBarModel.d.ts +1 -0
- package/build/types/appcontainer/MessageSourceModel.d.ts +1 -0
- package/build/types/appcontainer/OptionsDialogModel.d.ts +1 -0
- package/build/types/appcontainer/PageStateModel.d.ts +1 -0
- package/build/types/appcontainer/RouterModel.d.ts +1 -0
- package/build/types/appcontainer/SizingModeModel.d.ts +1 -0
- package/build/types/appcontainer/ThemeModel.d.ts +1 -0
- package/build/types/appcontainer/ToastSourceModel.d.ts +1 -0
- package/build/types/appcontainer/UserAgentModel.d.ts +1 -0
- package/build/types/appcontainer/ViewportSizeModel.d.ts +1 -0
- package/build/types/cmp/ag-grid/AgGridModel.d.ts +3 -1
- package/build/types/cmp/card/Card.d.ts +2 -2
- package/build/types/cmp/chart/ChartModel.d.ts +2 -0
- package/build/types/cmp/dataview/DataViewModel.d.ts +2 -0
- package/build/types/cmp/daterange/DateRangePickerModel.d.ts +266 -0
- package/build/types/cmp/daterange/DateRangePresets.d.ts +26 -0
- package/build/types/cmp/daterange/DateRangeUtils.d.ts +96 -0
- package/build/types/cmp/daterange/Types.d.ts +152 -0
- package/build/types/cmp/daterange/index.d.ts +4 -0
- package/build/types/cmp/filter/FilterChooserModel.d.ts +3 -1
- package/build/types/cmp/form/FormModel.d.ts +3 -1
- package/build/types/cmp/grid/GridModel.d.ts +2 -0
- package/build/types/cmp/grid/Types.d.ts +2 -0
- package/build/types/cmp/grid/filter/GridFilterModel.d.ts +1 -1
- package/build/types/cmp/grid/impl/ColumnWidthCalculator.d.ts +1 -1
- package/build/types/cmp/grouping/GroupingChooserModel.d.ts +3 -1
- package/build/types/cmp/input/HoistInputProps.d.ts +49 -2
- package/build/types/cmp/tab/TabContainerModel.d.ts +3 -1
- package/build/types/cmp/tab/TabModel.d.ts +3 -1
- package/build/types/cmp/treemap/TreeMapModel.d.ts +2 -0
- package/build/types/cmp/viewmanager/ViewManagerModel.d.ts +2 -0
- package/build/types/cmp/zoneGrid/ZoneGridModel.d.ts +2 -0
- package/build/types/core/HoistBase.d.ts +7 -0
- package/build/types/core/HoistProps.d.ts +26 -1
- package/build/types/core/model/RootRefreshContextModel.d.ts +1 -0
- package/build/types/core/types/Telemetry.d.ts +1 -1
- package/build/types/data/Store.d.ts +3 -1
- package/build/types/data/StoreSelectionModel.d.ts +3 -1
- package/build/types/data/cube/Cube.d.ts +5 -2
- package/build/types/data/cube/View.d.ts +2 -0
- package/build/types/desktop/cmp/appOption/AutoRefreshAppOption.d.ts +3 -0
- package/build/types/desktop/cmp/appOption/ThemeAppOption.d.ts +3 -0
- package/build/types/desktop/cmp/button/Button.d.ts +2 -2
- package/build/types/desktop/cmp/button/ButtonGroup.d.ts +2 -2
- package/build/types/desktop/cmp/dash/DashConfig.d.ts +2 -0
- package/build/types/desktop/cmp/dash/DashViewModel.d.ts +3 -1
- package/build/types/desktop/cmp/dash/canvas/DashCanvasModel.d.ts +1 -1
- package/build/types/desktop/cmp/dash/container/DashContainerModel.d.ts +1 -1
- package/build/types/desktop/cmp/daterange/DateRangePicker.d.ts +66 -0
- package/build/types/desktop/cmp/daterange/impl/CustomTab.d.ts +3 -0
- package/build/types/desktop/cmp/daterange/impl/DateRangePickerLocalModel.d.ts +121 -0
- package/build/types/desktop/cmp/daterange/impl/PeriodTab.d.ts +2 -0
- package/build/types/desktop/cmp/daterange/impl/PresetsTab.d.ts +3 -0
- package/build/types/desktop/cmp/daterange/impl/RelativeTab.d.ts +3 -0
- package/build/types/desktop/cmp/daterange/impl/TabUtils.d.ts +21 -0
- package/build/types/desktop/cmp/daterange/index.d.ts +2 -0
- package/build/types/desktop/cmp/dock/DockContainerModel.d.ts +3 -1
- package/build/types/desktop/cmp/dock/DockViewModel.d.ts +3 -1
- package/build/types/desktop/cmp/grouping/GroupingChooser.d.ts +4 -1
- package/build/types/desktop/cmp/input/IntentInput.d.ts +38 -0
- package/build/types/desktop/cmp/input/NumberInput.d.ts +2 -2
- package/build/types/desktop/cmp/input/SegmentedControl.d.ts +25 -4
- package/build/types/desktop/cmp/input/TextArea.d.ts +2 -2
- package/build/types/desktop/cmp/input/TextInput.d.ts +2 -2
- package/build/types/desktop/cmp/input/index.d.ts +1 -0
- package/build/types/desktop/cmp/leftrightchooser/LeftRightChooserModel.d.ts +3 -1
- package/build/types/desktop/cmp/panel/PanelModel.d.ts +3 -1
- package/build/types/desktop/cmp/rest/RestGridModel.d.ts +1 -1
- package/build/types/desktop/cmp/tab/impl/TabContainer.d.ts +1 -1
- package/build/types/desktop/hooks/UseHotkeys.d.ts +4 -3
- package/build/types/icon/Icon.d.ts +3 -0
- package/build/types/inspector/impl/InspectorUtils.d.ts +3 -0
- package/build/types/inspector/instances/InstancesModel.d.ts +4 -0
- package/build/types/kit/react-day-picker/index.d.ts +3 -3
- package/build/types/mobile/cmp/button/Button.d.ts +2 -2
- package/build/types/mobile/cmp/input/NumberInput.d.ts +2 -2
- package/build/types/mobile/cmp/input/SegmentedControl.d.ts +18 -4
- package/build/types/mobile/cmp/input/TextArea.d.ts +2 -2
- package/build/types/mobile/cmp/input/TextInput.d.ts +2 -2
- package/build/types/mobile/cmp/navigator/NavigatorModel.d.ts +8 -2
- package/build/types/mobile/cmp/navigator/PageModel.d.ts +3 -1
- package/build/types/mobile/cmp/tab/impl/TabContainer.d.ts +1 -1
- package/build/types/svc/InspectorService.d.ts +1 -0
- package/build/types/utils/js/LangUtils.d.ts +24 -5
- package/cmp/README.md +1 -0
- package/cmp/ag-grid/AgGridModel.ts +5 -0
- package/cmp/badge/Badge.ts +5 -3
- package/cmp/card/Card.ts +4 -3
- package/cmp/chart/ChartModel.ts +11 -1
- package/cmp/clock/Clock.ts +1 -1
- package/cmp/dataview/DataViewModel.ts +7 -1
- package/cmp/daterange/DateRangePickerModel.ts +628 -0
- package/cmp/daterange/DateRangePresets.ts +274 -0
- package/cmp/daterange/DateRangeUtils.ts +557 -0
- package/cmp/daterange/README.md +434 -0
- package/cmp/daterange/Types.ts +203 -0
- package/cmp/daterange/index.ts +10 -0
- package/cmp/filter/FilterChooserModel.ts +7 -4
- package/cmp/form/FormModel.ts +6 -0
- package/cmp/form/README.md +1 -0
- package/cmp/grid/GridModel.ts +24 -4
- package/cmp/grid/Types.ts +3 -0
- package/cmp/grid/filter/GridFilterModel.ts +3 -1
- package/cmp/grid/impl/ColumnWidthCalculator.ts +5 -10
- package/cmp/grouping/GroupingChooserModel.ts +6 -1
- package/cmp/input/HoistInputProps.ts +50 -2
- package/cmp/input/README.md +42 -0
- package/cmp/layout/Box.ts +3 -2
- package/cmp/tab/TabContainerModel.ts +9 -1
- package/cmp/tab/TabModel.ts +5 -0
- package/cmp/treemap/SplitTreeMapModel.ts +12 -2
- package/cmp/treemap/TreeMapModel.ts +6 -1
- package/cmp/viewmanager/ViewManagerModel.ts +6 -1
- package/cmp/zoneGrid/ZoneGridModel.ts +7 -0
- package/core/HoistBase.ts +11 -0
- package/core/HoistProps.ts +27 -0
- package/core/README.md +46 -0
- package/core/impl/InstallServices.ts +5 -1
- package/core/model/RootRefreshContextModel.ts +2 -0
- package/core/types/Telemetry.ts +1 -1
- package/data/README.md +3 -1
- package/data/Store.ts +7 -0
- package/data/StoreSelectionModel.ts +5 -1
- package/data/cube/Cube.ts +12 -3
- package/data/cube/View.ts +5 -1
- package/desktop/README.md +2 -0
- package/desktop/appcontainer/LoginPanel.ts +2 -0
- package/desktop/cmp/button/Button.scss +71 -42
- package/desktop/cmp/button/Button.ts +10 -1
- package/desktop/cmp/button/ButtonGroup.ts +4 -1
- package/desktop/cmp/button/grid/ColChooserButton.ts +9 -1
- package/desktop/cmp/button/zoneGrid/ZoneMapperButton.ts +1 -0
- package/desktop/cmp/dash/DashConfig.ts +3 -0
- package/desktop/cmp/dash/DashViewModel.ts +14 -1
- package/desktop/cmp/dash/canvas/DashCanvasModel.ts +3 -1
- package/desktop/cmp/dash/container/DashContainerModel.ts +3 -1
- package/desktop/cmp/daterange/DateRangePicker.scss +577 -0
- package/desktop/cmp/daterange/DateRangePicker.ts +357 -0
- package/desktop/cmp/daterange/impl/CustomTab.ts +165 -0
- package/desktop/cmp/daterange/impl/DateRangePickerLocalModel.ts +482 -0
- package/desktop/cmp/daterange/impl/PeriodTab.ts +138 -0
- package/desktop/cmp/daterange/impl/PresetsTab.ts +58 -0
- package/desktop/cmp/daterange/impl/RelativeTab.ts +108 -0
- package/desktop/cmp/daterange/impl/TabUtils.ts +34 -0
- package/desktop/cmp/daterange/index.ts +8 -0
- package/desktop/cmp/dock/DockContainerModel.ts +6 -1
- package/desktop/cmp/dock/DockViewModel.ts +6 -1
- package/desktop/cmp/filechooser/FileChooser.ts +4 -0
- package/desktop/cmp/form/FormField.ts +1 -0
- package/desktop/cmp/grid/editors/DateEditor.ts +11 -2
- package/desktop/cmp/grouping/GroupingChooser.ts +8 -2
- package/desktop/cmp/input/Checkbox.ts +1 -0
- package/desktop/cmp/input/CodeInput.ts +1 -0
- package/desktop/cmp/input/DateInput.scss +1 -1
- package/desktop/cmp/input/DateInput.ts +1 -0
- package/desktop/cmp/input/IntentInput.scss +202 -0
- package/desktop/cmp/input/IntentInput.ts +213 -0
- package/desktop/cmp/input/NumberInput.ts +11 -2
- package/desktop/cmp/input/Picker.ts +1 -0
- package/desktop/cmp/input/RadioInput.ts +2 -1
- package/desktop/cmp/input/SegmentedControl.scss +57 -5
- package/desktop/cmp/input/SegmentedControl.ts +66 -11
- package/desktop/cmp/input/Select.ts +9 -7
- package/desktop/cmp/input/Slider.ts +2 -0
- package/desktop/cmp/input/SwitchInput.ts +1 -0
- package/desktop/cmp/input/TextArea.ts +11 -2
- package/desktop/cmp/input/TextInput.ts +11 -2
- package/desktop/cmp/input/index.ts +1 -0
- package/desktop/cmp/leftrightchooser/LeftRightChooserModel.ts +5 -0
- package/desktop/cmp/panel/Panel.ts +8 -5
- package/desktop/cmp/panel/PanelModel.ts +8 -0
- package/desktop/cmp/panel/impl/ResizeContainer.ts +2 -1
- package/desktop/cmp/rest/RestGridModel.ts +4 -0
- package/desktop/cmp/tab/impl/TabContainer.ts +2 -0
- package/desktop/cmp/toolbar/Toolbar.scss +20 -6
- package/desktop/hooks/UseHotkeys.ts +41 -12
- package/docs/README.md +2 -0
- package/docs/build-and-publish.md +23 -7
- package/docs/doc-registry.json +8 -0
- package/docs/version-compatibility.md +2 -0
- package/icon/Icon.ts +9 -0
- package/icon/index.ts +24 -0
- package/inspector/README.md +8 -2
- package/inspector/impl/InspectorUtils.ts +12 -0
- package/inspector/instances/DiagnosticsModel.ts +3 -2
- package/inspector/instances/InstancesModel.ts +81 -14
- package/inspector/instances/InstancesPanel.ts +6 -0
- package/kit/react-day-picker/index.ts +3 -3
- package/mobile/appcontainer/LoginPanel.ts +2 -0
- package/mobile/cmp/button/Button.ts +16 -3
- package/mobile/cmp/form/FormField.ts +1 -0
- package/mobile/cmp/input/Checkbox.ts +1 -0
- package/mobile/cmp/input/DateInput.ts +1 -0
- package/mobile/cmp/input/Label.ts +13 -8
- package/mobile/cmp/input/NumberInput.ts +11 -2
- package/mobile/cmp/input/SearchInput.ts +1 -0
- package/mobile/cmp/input/SegmentedControl.scss +36 -6
- package/mobile/cmp/input/SegmentedControl.ts +37 -11
- package/mobile/cmp/input/Select.ts +2 -0
- package/mobile/cmp/input/SwitchInput.ts +1 -0
- package/mobile/cmp/input/TextArea.ts +11 -2
- package/mobile/cmp/input/TextInput.ts +11 -2
- package/mobile/cmp/navigator/NavigatorModel.ts +20 -8
- package/mobile/cmp/navigator/PageModel.ts +6 -1
- package/mobile/cmp/tab/impl/TabContainer.ts +9 -1
- package/package.json +19 -19
- package/styles/vars.scss +32 -2
- package/svc/InspectorService.ts +2 -0
- package/svc/TraceService.ts +5 -2
- package/utils/js/LangUtils.ts +37 -5
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
# Date Range Package
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `/cmp/daterange/` package provides `DateRangePickerModel`, a model for selecting a period of
|
|
6
|
+
time and reading it back as concrete dates and filters. Its desktop UI, `DateRangePicker` in
|
|
7
|
+
`/desktop/cmp/daterange/`, is a compact toolbar control: one trigger button showing the applied
|
|
8
|
+
period and its dates, opening a popover with a tab for each way of choosing a period.
|
|
9
|
+
|
|
10
|
+
**Key features:**
|
|
11
|
+
- One value that can express a preset (MTD, Prev 30 Days, ...), a relative lookback, a calendar
|
|
12
|
+
month, quarter, or year, or a custom range of dates
|
|
13
|
+
- Resolution against a live anchor day, so a persisted "month to date" stays month to date - and
|
|
14
|
+
rolls over at midnight without app involvement
|
|
15
|
+
- A comparable prior range for every selection, for period-over-period comparisons
|
|
16
|
+
- Stepping back and forth through periods without a selection losing what it is
|
|
17
|
+
- Ready-made `FieldFilterSpec`s for filtering a Store, Cube View, or server query
|
|
18
|
+
- Built-in presets, app-defined presets, and a business-day mode for single-day navigation
|
|
19
|
+
- Plain JSON value that persists through `persistWith`, including ViewManager views
|
|
20
|
+
|
|
21
|
+
The model is cross-platform. Only the desktop component exists today.
|
|
22
|
+
|
|
23
|
+
This guide covers how the pieces fit together and how the selection resolves. For the full list
|
|
24
|
+
of configuration keys and component props with their defaults, see the JSDoc on
|
|
25
|
+
`DateRangePickerConfig` and `DateRangePickerProps` - the source of truth, and what IDE hover and
|
|
26
|
+
the Hoist symbol tools surface.
|
|
27
|
+
|
|
28
|
+
## Architecture
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
DateRangePickerModel
|
|
32
|
+
├── value: DateRangeSelection # The applied period, always normalized
|
|
33
|
+
├── anchorDay: DateRangeAnchorDay # How the anchor date is determined - live by default
|
|
34
|
+
├── anchorDate, minDate, maxDate # The dates selections resolve against
|
|
35
|
+
├── today # The reader's current day - what "Today" means
|
|
36
|
+
├── businessDayMode, isBusinessDay # Single-day handling
|
|
37
|
+
├── presets: DateRangePreset[] # Offered on the Presets tab
|
|
38
|
+
├── tabs: DateRangePickerTab[] # Offered in the popover
|
|
39
|
+
├── currentRange: LocalDateRange # Resolved {start, end}
|
|
40
|
+
├── priorRange: LocalDateRange # The comparable preceding range, or null
|
|
41
|
+
├── currentRangeFilter, priorRangeFilter # FieldFilterSpec[] on `filterField`
|
|
42
|
+
├── label, rangeLabel, displayName # Display strings
|
|
43
|
+
└── Methods:
|
|
44
|
+
├── setValue() # Apply a selection or preset token
|
|
45
|
+
├── stepRange() # Previous / next period
|
|
46
|
+
├── resolve(), parseValue() # Work with selections other than the applied one
|
|
47
|
+
└── setAnchorDay(), setMaxDate(), setPresets(), setTabs(), ...
|
|
48
|
+
|
|
49
|
+
DateRangeSelection (plain JSON, discriminated on `kind`)
|
|
50
|
+
├── {kind: 'preset', token, offset?}
|
|
51
|
+
├── {kind: 'relative', count, unit, snap, offset?}
|
|
52
|
+
├── {kind: 'month', year, month}
|
|
53
|
+
├── {kind: 'quarter', year, quarter}
|
|
54
|
+
├── {kind: 'year', year}
|
|
55
|
+
└── {kind: 'custom', start, end} # 'YYYY-MM-DD' strings
|
|
56
|
+
|
|
57
|
+
DateRangeContext # What selections resolve against
|
|
58
|
+
├── anchorDate, today, minDate, maxDate
|
|
59
|
+
├── isBusinessDay(date), businessDayMode
|
|
60
|
+
└── presets: Record<token, DateRangePreset>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Resolution is pure: `resolveDateRange(selection, context)` returns `{current, prior}`. The model
|
|
64
|
+
holds the context as observable state and exposes the resolved ranges as derived values, so they
|
|
65
|
+
update when the value, the anchor date, or the bounds change.
|
|
66
|
+
|
|
67
|
+
## DateRangePickerModel
|
|
68
|
+
|
|
69
|
+
### Basic Usage
|
|
70
|
+
|
|
71
|
+
Construct the model within an app model, render the picker bound to it, and read the resolved
|
|
72
|
+
range or filters wherever the app queries.
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import {DateRangePickerModel} from '@xh/hoist/cmp/daterange';
|
|
76
|
+
import {dateRangePicker} from '@xh/hoist/desktop/cmp/daterange';
|
|
77
|
+
|
|
78
|
+
class ReportModel extends HoistModel {
|
|
79
|
+
@managed gridModel = new GridModel({...});
|
|
80
|
+
@managed periodModel = new DateRangePickerModel({
|
|
81
|
+
filterField: 'tradeDate',
|
|
82
|
+
initialValue: 'mtd',
|
|
83
|
+
persistWith: {localStorageKey: 'reportPeriod'}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
constructor() {
|
|
87
|
+
super();
|
|
88
|
+
makeObservable(this);
|
|
89
|
+
this.addReaction({
|
|
90
|
+
track: () => this.periodModel.currentRangeFilter,
|
|
91
|
+
run: filter => this.gridModel.store.setFilter(filter),
|
|
92
|
+
fireImmediately: true
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// In the component - the picker finds the model via context lookup.
|
|
98
|
+
toolbar(dateRangePicker({showStepButtons: true}), filler(), ...)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The value and its derived ranges stay live whether or not a picker is mounted. A locked dashboard
|
|
102
|
+
widget, for example, can hide its picker but still query by period.
|
|
103
|
+
|
|
104
|
+
### Configuration
|
|
105
|
+
|
|
106
|
+
`DateRangePickerConfig` shapes what the picker offers and how it resolves. The keys most apps
|
|
107
|
+
touch:
|
|
108
|
+
|
|
109
|
+
- `tabs` and `presets` select which of the four tabs appear and which presets the Presets tab
|
|
110
|
+
lists, in order. Presets may be built-in tokens, app-defined `DateRangePreset` objects, or both.
|
|
111
|
+
- `anchorDay`, `minDate`, and `maxDate` set the dates everything resolves against - see Anchor
|
|
112
|
+
Day and Bounds below.
|
|
113
|
+
- `filterField` names the field for `currentRangeFilter` and `priorRangeFilter`.
|
|
114
|
+
- `initialValue` is the starting selection, given as a selection object or a preset token, and
|
|
115
|
+
the fallback for missing or invalid persisted state. It defaults to the first configured preset,
|
|
116
|
+
or a rolling 30 days when there are none.
|
|
117
|
+
- `persistWith` persists the value - see Persistence below.
|
|
118
|
+
|
|
119
|
+
`businessDayMode`, `commitOnChange`, `dateFormat`, `singleDayFormat`, and `isBusinessDay` round out
|
|
120
|
+
the config. All but the last can be defaulted app-wide via `DateRangePickerModel.defaults`, as can
|
|
121
|
+
`anchorDay`. The two formats split along what they show: `dateFormat` (default `YYYY-MM-DD`) for the
|
|
122
|
+
ends of a range, where the year earns its place, and `singleDayFormat` (default `ddd MMM D`) for a
|
|
123
|
+
single day, where the weekday matters more. Either may be a function of the date - e.g. to add the
|
|
124
|
+
year only when it is not the current one.
|
|
125
|
+
|
|
126
|
+
### The Selection Value
|
|
127
|
+
|
|
128
|
+
`value` is always a normalized `DateRangeSelection`. Set it with `setValue()`, which accepts a
|
|
129
|
+
selection object or a bare preset token and ignores (with a logged warning) anything that fails
|
|
130
|
+
validation: an unknown preset, an out-of-range count, year, or offset, or a malformed date.
|
|
131
|
+
|
|
132
|
+
| Kind | Shape | Resolves to |
|
|
133
|
+
|------|-------|-------------|
|
|
134
|
+
| `preset` | `{kind: 'preset', token: 'mtd'}` | Whatever the preset's resolver returns for the current context |
|
|
135
|
+
| `relative` | `{kind: 'relative', count: 6, unit: 'months', snap: false}` | A window of `count` units ending on the anchor date |
|
|
136
|
+
| `month` | `{kind: 'month', year: 2026, month: 8}` | The calendar month, clamped to `maxDate` if it falls inside |
|
|
137
|
+
| `quarter` | `{kind: 'quarter', year: 2026, quarter: 3}` | The calendar quarter, clamped likewise |
|
|
138
|
+
| `year` | `{kind: 'year', year: 2026}` | The calendar year, clamped likewise |
|
|
139
|
+
| `custom` | `{kind: 'custom', start: '2026-08-10', end: '2026-08-20'}` | Exactly those dates |
|
|
140
|
+
|
|
141
|
+
Preset and relative selections re-resolve as the anchor date moves, and carry an optional
|
|
142
|
+
`offset` when stepped from their natural range - see Stepping. Month, quarter, and year
|
|
143
|
+
selections name a fixed period, though one containing `maxDate` is clamped to it. Custom
|
|
144
|
+
selections name fixed dates. All are plain JSON, so the value round-trips through persistence
|
|
145
|
+
without custom serialization.
|
|
146
|
+
|
|
147
|
+
### Presets
|
|
148
|
+
|
|
149
|
+
Built-in presets live in `dateRangePresets`, keyed by token. Offer any subset in any order via
|
|
150
|
+
the `presets` config.
|
|
151
|
+
|
|
152
|
+
| Token | Resolves to | Label |
|
|
153
|
+
|-------|-------------|-------|
|
|
154
|
+
| `anchorDay` | The anchor date itself | `Today` when the anchor is the current day, else `As Of` |
|
|
155
|
+
| `prevDay` | The day before the anchor - the previous business day in `businessDayMode` | `Prev Day` |
|
|
156
|
+
| `wtd`, `mtd`, `qtd`, `ytd` | Start of the unit containing the anchor, through the anchor | `MTD`, ... |
|
|
157
|
+
| `prev7Days`, `prev30Days`, `prev90Days` | Rolling window ending on the anchor | `Prev 7 Days`, ... |
|
|
158
|
+
| `prev3Months`, `prev6Months`, `prev12Months` | Rolling window ending on the anchor | `Prev 3 Months`, ... |
|
|
159
|
+
| `prevWeek`, `prevMonth`, `prevQuarter`, `prevYear` | The full unit before the one containing the anchor | The period: `Aug 2026`, `Q2 2026`, `2025` |
|
|
160
|
+
| `all` | `minDate` (or unbounded) through `maxDate` | `All` |
|
|
161
|
+
|
|
162
|
+
`DEFAULT_DATE_RANGE_PRESETS` is `anchorDay`, `mtd`, `qtd`, `ytd`, `prev7Days`, `prev30Days`,
|
|
163
|
+
`prev90Days`, `prev12Months`, `prevMonth`, and `prevYear`.
|
|
164
|
+
|
|
165
|
+
Labels say "Prev" rather than "Last" because the anchor date need not be today: "Prev 7 Days" is
|
|
166
|
+
true whatever the anchor is, where "Last 7 Days" quietly claims the window ends now. Only
|
|
167
|
+
`anchorDay` reads differently by context, and when it reads `As Of` the picker shows its date
|
|
168
|
+
wherever the label would otherwise stand alone.
|
|
169
|
+
|
|
170
|
+
An app-defined preset is a `DateRangePreset`: a unique `token`, a `label` (string or function of
|
|
171
|
+
the context), an optional longer `name` for its row in the picker, a `resolve` function, optional
|
|
172
|
+
`resolvePrior` and `resolveNext` for the adjacent ranges, and an optional `shiftedLabel` for the
|
|
173
|
+
preset once stepped (see Stepping). The default adjacent ranges are the preceding and following
|
|
174
|
+
ranges of equal length in days.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
const FISCAL_YTD: DateRangePreset = {
|
|
178
|
+
token: 'fytd',
|
|
179
|
+
label: 'FYTD',
|
|
180
|
+
name: 'Fiscal Year to Date',
|
|
181
|
+
resolve: ({anchorDate}) => ({start: fiscalYearStart(anchorDate), end: anchorDate}),
|
|
182
|
+
resolvePrior: ({start, end}) => ({
|
|
183
|
+
start: start.subtract(1, 'years'),
|
|
184
|
+
end: end.subtract(1, 'years')
|
|
185
|
+
})
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
new DateRangePickerModel({presets: [FISCAL_YTD, 'qtd', 'ytd', 'prev90Days']});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Relative Lookbacks
|
|
192
|
+
|
|
193
|
+
A relative selection is `count` units ending on the anchor date. Units are `days`, `weeks`,
|
|
194
|
+
`months`, `quarters`, and `years`.
|
|
195
|
+
|
|
196
|
+
- **Rolling** (`snap: false`, the default) is exactly `count` units back from the anchor: 3 months
|
|
197
|
+
ending Sep 2 starts Jun 3.
|
|
198
|
+
- **Calendar** (`snap: true`) aligns to unit boundaries and counts the current partial unit as one:
|
|
199
|
+
3 calendar months ending Sep 2 starts Jul 1. Snap does not apply to days and is normalized to
|
|
200
|
+
`false` for that unit.
|
|
201
|
+
|
|
202
|
+
### Prior Ranges
|
|
203
|
+
|
|
204
|
+
`priorRange` is the comparable range immediately before `currentRange`, for period-over-period
|
|
205
|
+
comparison. It is `null` when the current range is unbounded.
|
|
206
|
+
|
|
207
|
+
| Selection | Prior range |
|
|
208
|
+
|-----------|-------------|
|
|
209
|
+
| Period-to-date presets (`mtd`, `ytd`, ...) | The same span one unit earlier: MTD on the 12th compares against the 1st through 12th of last month |
|
|
210
|
+
| Previous-unit presets (`prevMonth`, ...) | The unit before that |
|
|
211
|
+
| Lookbacks in weeks, months, quarters, or years | The same span `count` units earlier, matching the equivalent presets |
|
|
212
|
+
| Lookbacks in days, and `custom` | The preceding window of equal length in days |
|
|
213
|
+
| Single days in `businessDayMode` | The previous business day |
|
|
214
|
+
| `month`, `quarter`, `year` | The previous month, quarter, or year, clamped the same way if the current one is |
|
|
215
|
+
|
|
216
|
+
The same logic drives stepping, so the prior period is always one step back. An app that wants
|
|
217
|
+
the prior MTD as the *selected* value can set `{kind: 'preset', token: 'mtd', offset: -1}`.
|
|
218
|
+
|
|
219
|
+
### Filters
|
|
220
|
+
|
|
221
|
+
With `filterField` configured, `currentRangeFilter` and `priorRangeFilter` return
|
|
222
|
+
`FieldFilterSpec[]`: a `>=` filter for a bounded start and a `<=` filter for a bounded end. An
|
|
223
|
+
unbounded edge produces no filter, so the `all` preset with no `minDate` yields a single `<=`
|
|
224
|
+
filter. Pass the array anywhere a `FilterLike` is accepted, or send it to the server as part of a
|
|
225
|
+
query body. `getRangeFilter(range, field)` builds filters for any range and field.
|
|
226
|
+
|
|
227
|
+
### Stepping
|
|
228
|
+
|
|
229
|
+
`stepRange(steps)` moves the applied range by one period per step: `-1` for the previous period,
|
|
230
|
+
`1` for the next. No selection changes kind:
|
|
231
|
+
|
|
232
|
+
- **Preset and relative** selections adjust their `offset`, stepping through their own prior- and
|
|
233
|
+
next-range logic - a lookback in months steps by months, MTD steps to the prior MTD, a single
|
|
234
|
+
day steps by business day in `businessDayMode`. Negative offsets are earlier periods. Positive
|
|
235
|
+
ones are later, and reachable only when `maxDate` allows dates beyond the anchor, since the
|
|
236
|
+
natural range already ends there. The offset is omitted from the value when zero. A stepped
|
|
237
|
+
selection is still live: `mtd` at offset `-1` becomes the new prior MTD when the month turns.
|
|
238
|
+
- **Month, quarter, and year** selections step by calendar unit.
|
|
239
|
+
- **Custom** selections step by their length in days, or by business day when a single day in
|
|
240
|
+
`businessDayMode`.
|
|
241
|
+
|
|
242
|
+
Steps clamp to `minDate` and `maxDate`. `canStepBack` and `canStepForward` drive the component's
|
|
243
|
+
step buttons, and the trigger's left and right arrow keys.
|
|
244
|
+
|
|
245
|
+
Once stepped, the trigger's dates locate the range, so the label describes only its shape: a
|
|
246
|
+
rolling window reads as its length (`7 Days`, `3 Months`), a previous-unit preset as the period it
|
|
247
|
+
now covers (`Jul 2026`), and a to-date preset as its name with the signed offset (`MTD −1`,
|
|
248
|
+
`MTD +1`) - its length is set by the calendar, not the selection, so a length alone would
|
|
249
|
+
mislead. Single days read from the anchor in the T-1 idiom: `Today −1`, `Today −2`, or `As Of −1`,
|
|
250
|
+
whether the walk began from `anchorDay` or `prevDay`. App-defined presets control this via
|
|
251
|
+
`shiftedLabel`; the default appends the offset.
|
|
252
|
+
|
|
253
|
+
### Anchor Day and Bounds
|
|
254
|
+
|
|
255
|
+
`anchorDay` determines the date that relative and to-date selections resolve against, exposed as
|
|
256
|
+
`anchorDate`. It is live by default: the model re-evaluates it every few seconds, and everything
|
|
257
|
+
derived from it - ranges, labels, filters, step enablement - follows the day over as midnight
|
|
258
|
+
passes, with no app code.
|
|
259
|
+
|
|
260
|
+
| `anchorDay` | Anchor date |
|
|
261
|
+
|-------------|-------------|
|
|
262
|
+
| `'localDay'` (default) | The current day in the browser's time zone, kept current |
|
|
263
|
+
| `'appDay'` | The current day in the app's time zone (`LocalDate.currentAppDay()`), kept current |
|
|
264
|
+
| A `LocalDate` | That day, pinned. Never moves, never snapped |
|
|
265
|
+
| `() => LocalDate` | Re-evaluated every few seconds and whenever observables it reads change. Must be pure and cheap |
|
|
266
|
+
|
|
267
|
+
The function form covers as-of dates with their own rule. A desk whose day rolls to the next
|
|
268
|
+
business day at an evening cutoff, for example:
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
anchorDay: () => {
|
|
272
|
+
const now = moment(),
|
|
273
|
+
day = LocalDate.today(),
|
|
274
|
+
afterCutoff = now.hour() >= 18;
|
|
275
|
+
let ret = afterCutoff ? day.nextDay() : day;
|
|
276
|
+
while (!ret.isWeekday) ret = ret.nextDay();
|
|
277
|
+
return ret;
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The `anchorDay` preset labels itself `Today` only when the anchor date is the reader's current day
|
|
282
|
+
- the browser's, always, whatever zone the anchor is drawn from. Otherwise it reads `As Of`, and the
|
|
283
|
+
trigger shows the date. So on the Friday evening above the picker reads `As Of | 2026-09-07` until
|
|
284
|
+
Monday morning, when it reads `Today`. Likewise a user whose browser is a day ahead of an
|
|
285
|
+
`'appDay'` anchor sees `As Of` with the app's date rather than a "Today" that is not theirs.
|
|
286
|
+
|
|
287
|
+
**`businessDayMode`** is for users who think in calendar-length windows but stand on business
|
|
288
|
+
days. It has two effects and no others: a live `anchorDay` (`'localDay'` or `'appDay'`) snaps back
|
|
289
|
+
to the most recent business day per `isBusinessDay`, and single-day selections step by business
|
|
290
|
+
day. Seven days is still seven days, and a month is still a month. A pinned or computed
|
|
291
|
+
`anchorDay` is honored verbatim - a month-end that falls on a Sunday stays the 31st.
|
|
292
|
+
|
|
293
|
+
Nothing beyond `maxDate` is selectable: later months and days are disabled in the popover, and
|
|
294
|
+
month and year selections spanning `maxDate` are clamped to it, so the current year covers Jan 1
|
|
295
|
+
through the anchor. It still reads as its year, `2026` - `YTD` is the preset, which steps to the
|
|
296
|
+
same partial span a year earlier, where a year pick steps by whole years. `maxDate` defaults to
|
|
297
|
+
`anchorDate`. Set it later to allow future dates, or set `minDate` to bound the past.
|
|
298
|
+
|
|
299
|
+
### Persistence
|
|
300
|
+
|
|
301
|
+
Pass `persistWith` to persist the value under `dateRangePicker.value` by default. Set `path` to
|
|
302
|
+
disambiguate multiple pickers sharing one provider, or `persistValue: false` to skip persistence
|
|
303
|
+
while keeping the options for future aspects.
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
new DateRangePickerModel({
|
|
307
|
+
persistWith: {viewManagerModel: this.viewManagerModel, path: 'detailPeriod'}
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
A persisted value that is missing or fails validation - a preset since removed, for example -
|
|
312
|
+
falls back to `defaultValue` rather than carrying a previous value over.
|
|
313
|
+
|
|
314
|
+
### Labels
|
|
315
|
+
|
|
316
|
+
| Property | Example | Use |
|
|
317
|
+
|----------|---------|-----|
|
|
318
|
+
| `label` | `MTD`, `Prev 6 Months`, `Aug 2026`, `Custom` | The trigger |
|
|
319
|
+
| `rangeLabel` | `2026-08-01 ▸ 2026-09-02`, `Fri Sep 4` | The trigger's dates, per `dateFormat`. A single day reads as one date, per `singleDayFormat`. |
|
|
320
|
+
| `displayName` | `MTD`, `August 2026`, the dates for a custom range or `As Of` | Panel titles - the label, with months spelled out and unnamed periods as their dates |
|
|
321
|
+
|
|
322
|
+
## DateRangePicker
|
|
323
|
+
|
|
324
|
+
The desktop component. It finds its `DateRangePickerModel` via context lookup or an explicit
|
|
325
|
+
`model` prop, and supports Hoist layout props. `DateRangePickerProps` is small: `showStepButtons`
|
|
326
|
+
adds previous and next buttons wired to `stepRange()`, `styleButtonAsInput` (default true) matches
|
|
327
|
+
the trigger to `GroupingChooser`'s input styling or renders an outlined button, `intent` re-keys
|
|
328
|
+
the popover's selection accent, and `buttonProps`, `footerNote`, `popoverPosition`, and
|
|
329
|
+
`showRange` tune the rest.
|
|
330
|
+
|
|
331
|
+
### Tabs and Committing
|
|
332
|
+
|
|
333
|
+
Preset and month/year picks apply on click and close the popover. Relative and custom picks are
|
|
334
|
+
drafts until Apply, and Cancel, Escape, or a click outside discards them. With `commitOnChange`,
|
|
335
|
+
drafts apply as they change and Apply and Cancel are omitted.
|
|
336
|
+
|
|
337
|
+
A model configured with one tab renders without the rail, and its popover shrinks to fit.
|
|
338
|
+
|
|
339
|
+
### The Trigger
|
|
340
|
+
|
|
341
|
+
A trigger sized by its content never truncates. A stretched trigger (`flex: 1`, or an explicit
|
|
342
|
+
`width`) measures itself and drops the dates when it is too narrow to show them, leaving the
|
|
343
|
+
period label alone. Whenever the dates are not shown, a custom range - and the anchor day when it
|
|
344
|
+
reads `As Of` - shows its dates in place of the uninformative label. The full label and dates
|
|
345
|
+
remain available in the trigger's tooltip. With the trigger focused, the left and right arrow keys
|
|
346
|
+
step the period.
|
|
347
|
+
|
|
348
|
+
### Styling
|
|
349
|
+
|
|
350
|
+
Block classes are `xh-date-range-picker` (the control: trigger and step buttons) and
|
|
351
|
+
`xh-date-range-picker-popover`. The picker's own colors and key sizes come from
|
|
352
|
+
`--xh-date-range-picker-*` variables, declared in the `Date Range Picker` block of
|
|
353
|
+
`styles/vars.scss`, each with an unprefixed override hook. The ones an app is most likely to set:
|
|
354
|
+
|
|
355
|
+
```scss
|
|
356
|
+
body.xh-app {
|
|
357
|
+
--date-range-picker-popover-width: 720px; // default 640px
|
|
358
|
+
--date-range-picker-accent: var(--xh-intent-success); // selection accent, default primary
|
|
359
|
+
--date-range-picker-date-font-family: var(--xh-font-family); // default the mono font
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## Common Patterns
|
|
364
|
+
|
|
365
|
+
### Compare Against the Prior Period
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
get stats() {
|
|
369
|
+
const {currentRangeFilter, priorRangeFilter, priorRange} = this.periodModel;
|
|
370
|
+
return {
|
|
371
|
+
current: this.sumMatching(currentRangeFilter),
|
|
372
|
+
prior: priorRange ? this.sumMatching(priorRangeFilter) : null
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Query the Server by Period
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
@computed
|
|
381
|
+
get query() {
|
|
382
|
+
const {start, end} = this.periodModel.currentRange;
|
|
383
|
+
return {startDay: start, endDay: end, ...otherParams};
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Keep unbounded presets such as `all` out of the configured `presets` if the endpoint requires
|
|
388
|
+
both dates, or handle a `null` edge in the query.
|
|
389
|
+
|
|
390
|
+
### A Month Picker
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
new DateRangePickerModel({
|
|
394
|
+
tabs: ['period'],
|
|
395
|
+
initialValue: {kind: 'month', year: 2026, month: 1}
|
|
396
|
+
});
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### A Business-Day Desk
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
new DateRangePickerModel({
|
|
403
|
+
anchorDay: 'appDay',
|
|
404
|
+
businessDayMode: true,
|
|
405
|
+
isBusinessDay: d => d.isWeekday && !XH.holidayService.isHoliday(d),
|
|
406
|
+
presets: ['anchorDay', 'prevDay', 'wtd', 'mtd', 'qtd', 'ytd', 'prevMonth']
|
|
407
|
+
});
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
On a Saturday this resolves everything as of Friday, and the step buttons walk `anchorDay` from
|
|
411
|
+
Friday to Thursday and back.
|
|
412
|
+
|
|
413
|
+
## Common Pitfalls
|
|
414
|
+
|
|
415
|
+
- **`filterField` is required for filters.** The filter getters throw without it. Use
|
|
416
|
+
`currentRange` directly when the app builds its own query.
|
|
417
|
+
- **Invalid values are ignored, not thrown.** `setValue()` logs a warning and keeps the current
|
|
418
|
+
value. Check `validateValue()` first when the input is untrusted.
|
|
419
|
+
- **Presets are per model.** A persisted preset token that is not in the model's `presets` fails
|
|
420
|
+
validation and falls back to the default. Keep the configured list stable across versions, or
|
|
421
|
+
accept that users will see the default after a change.
|
|
422
|
+
- **`all` can be unbounded.** Without `minDate`, its start is `null`, `priorRange` is `null`, and
|
|
423
|
+
`currentRangeFilter` has one entry.
|
|
424
|
+
- **A computed `anchorDay` runs often.** It is re-evaluated every few seconds. Keep it to clock
|
|
425
|
+
reads and observable reads - derive anything expensive once, store it on an observable, and read
|
|
426
|
+
that.
|
|
427
|
+
|
|
428
|
+
## Related Packages
|
|
429
|
+
|
|
430
|
+
- [`/data/`](../../data/README.md) - `FieldFilterSpec` and applying filters to Stores
|
|
431
|
+
- [`/cmp/viewmanager/`](../viewmanager/README.md) - persisting the value within saved views
|
|
432
|
+
- [`/cmp/grouping/`](../grouping/GroupingChooserModel.ts) and [`/cmp/filter/`](../filter/FilterChooserModel.ts) -
|
|
433
|
+
sibling chooser models with the same popover-and-model pattern
|
|
434
|
+
- [`/utils/`](../../utils/README.md) - `LocalDate` and `Timer`
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file belongs to Hoist, an application development toolkit
|
|
3
|
+
* developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
|
|
4
|
+
*
|
|
5
|
+
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
|
+
*/
|
|
7
|
+
import type {LocalDate} from '@xh/hoist/utils/datetime';
|
|
8
|
+
|
|
9
|
+
/** An inclusive range of days. A null edge is unbounded. */
|
|
10
|
+
export interface LocalDateRange {
|
|
11
|
+
start: LocalDate | null;
|
|
12
|
+
end: LocalDate | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A selection resolved to its current range and the comparable range immediately before it. */
|
|
16
|
+
export interface ResolvedDateRange {
|
|
17
|
+
current: LocalDateRange;
|
|
18
|
+
/** The immediately preceding, non-overlapping range of comparable shape, or null if none. */
|
|
19
|
+
prior: LocalDateRange | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Calendar units supported by relative selections. */
|
|
23
|
+
export type DateRangeUnit = 'days' | 'weeks' | 'months' | 'quarters' | 'years';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* How the picker renders a date - a moment.js format string, or a function for formats that
|
|
27
|
+
* depend on the date itself, e.g. one that adds the year only when it is not the current year.
|
|
28
|
+
*/
|
|
29
|
+
export type DateRangeFormat = string | ((date: LocalDate) => string);
|
|
30
|
+
|
|
31
|
+
/** Tabs available within the DateRangePicker popover. */
|
|
32
|
+
export type DateRangePickerTab = 'presets' | 'relative' | 'period' | 'custom';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The day that relative and to-date selections resolve against - see the `anchorDay` config of
|
|
36
|
+
* {@link DateRangePickerModel}.
|
|
37
|
+
*
|
|
38
|
+
* - `'localDay'` - the current day in the browser's time zone, kept current as the day rolls.
|
|
39
|
+
* - `'appDay'` - the current day in the app's time zone (`LocalDate.currentAppDay()`), likewise.
|
|
40
|
+
* - A `LocalDate` - a pinned day that never moves, honored verbatim.
|
|
41
|
+
* - A function returning a `LocalDate` - re-evaluated every few seconds and whenever any
|
|
42
|
+
* observables it reads change. Must be pure and cheap. For as-of dates with their own rule, e.g.
|
|
43
|
+
* the latest loaded data date, or a day that rolls forward at an evening cutoff.
|
|
44
|
+
*/
|
|
45
|
+
export type DateRangeAnchorDay = 'localDay' | 'appDay' | LocalDate | (() => LocalDate);
|
|
46
|
+
|
|
47
|
+
/** Tokens for the presets shipped with Hoist - see {@link dateRangePresets}. */
|
|
48
|
+
export type DateRangePresetToken =
|
|
49
|
+
| 'anchorDay'
|
|
50
|
+
| 'prevDay'
|
|
51
|
+
| 'wtd'
|
|
52
|
+
| 'mtd'
|
|
53
|
+
| 'qtd'
|
|
54
|
+
| 'ytd'
|
|
55
|
+
| 'prev7Days'
|
|
56
|
+
| 'prev30Days'
|
|
57
|
+
| 'prev90Days'
|
|
58
|
+
| 'prev3Months'
|
|
59
|
+
| 'prev6Months'
|
|
60
|
+
| 'prev12Months'
|
|
61
|
+
| 'prevWeek'
|
|
62
|
+
| 'prevMonth'
|
|
63
|
+
| 'prevQuarter'
|
|
64
|
+
| 'prevYear'
|
|
65
|
+
| 'all';
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The dates a selection resolves against - the live state of a {@link DateRangePickerModel}.
|
|
69
|
+
* Passed to preset resolvers and to the resolution utilities in this package.
|
|
70
|
+
*/
|
|
71
|
+
export interface DateRangeContext {
|
|
72
|
+
/** Date that relative and to-date selections resolve against. */
|
|
73
|
+
anchorDate: LocalDate;
|
|
74
|
+
/**
|
|
75
|
+
* The current day in the browser's time zone - what "Today" means to the person looking at
|
|
76
|
+
* the screen, whatever zone the anchor date is drawn from.
|
|
77
|
+
*/
|
|
78
|
+
today: LocalDate;
|
|
79
|
+
/** Earliest selectable date, or null if unbounded. */
|
|
80
|
+
minDate: LocalDate | null;
|
|
81
|
+
/** Latest selectable date. Month, quarter and year selections spanning it are clamped to it. */
|
|
82
|
+
maxDate: LocalDate;
|
|
83
|
+
/** Whether a date is a business day - weekdays by default, or the model's `isBusinessDay`. */
|
|
84
|
+
isBusinessDay: (date: LocalDate) => boolean;
|
|
85
|
+
/** True if single-day selections step by business day - see the model config of that name. */
|
|
86
|
+
businessDayMode: boolean;
|
|
87
|
+
/** Presets available for selection, keyed by token. */
|
|
88
|
+
presets: Record<string, DateRangePreset>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* A named, one-click range offered on the picker's Presets tab. Hoist ships a set of common
|
|
93
|
+
* presets ({@link dateRangePresets}) - apps can offer a subset of those, add their own, or both
|
|
94
|
+
* via the `presets` config of {@link DateRangePickerModel}.
|
|
95
|
+
*/
|
|
96
|
+
export interface DateRangePreset {
|
|
97
|
+
/** Unique key for this preset. Persisted as the `token` of a `preset` selection. */
|
|
98
|
+
token: string;
|
|
99
|
+
|
|
100
|
+
/** Short label for the picker trigger, e.g. `MTD`. May be derived from the context. */
|
|
101
|
+
label: string | ((ctx: DateRangeContext) => string);
|
|
102
|
+
|
|
103
|
+
/** Longer name for the preset's row in the picker, e.g. `Month to Date`. Default `label`. */
|
|
104
|
+
name?: string | ((ctx: DateRangeContext) => string);
|
|
105
|
+
|
|
106
|
+
/** Resolve this preset to a concrete range. */
|
|
107
|
+
resolve: (ctx: DateRangeContext) => LocalDateRange;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Resolve the comparable prior range for a given current range. Default is the immediately
|
|
111
|
+
* preceding range of equal duration in days, or null when the current range is unbounded.
|
|
112
|
+
* Also drives stepping back: a selection at `offset` -n is this applied n times.
|
|
113
|
+
*/
|
|
114
|
+
resolvePrior?: (current: LocalDateRange, ctx: DateRangeContext) => LocalDateRange | null;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Resolve the comparable range immediately after a given current range - the mirror of
|
|
118
|
+
* `resolvePrior`, driving stepping forward when `maxDate` allows dates beyond the anchor.
|
|
119
|
+
* Default is the immediately following range of equal duration in days, or null when the
|
|
120
|
+
* current range is unbounded.
|
|
121
|
+
*/
|
|
122
|
+
resolveNext?: (current: LocalDateRange, ctx: DateRangeContext) => LocalDateRange | null;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Label for this preset once stepped to a non-zero `offset`, when the trigger's dates locate
|
|
126
|
+
* the range and the label need only describe its shape - e.g. `7 Days` for a rolling window,
|
|
127
|
+
* or the month itself for a previous-month preset. Default is the label with the signed
|
|
128
|
+
* offset appended, e.g. `MTD −1`.
|
|
129
|
+
*/
|
|
130
|
+
shiftedLabel?: (range: LocalDateRange, offset: number, ctx: DateRangeContext) => string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** A one-click preset, re-resolved against the anchor date as time passes. */
|
|
134
|
+
export interface PresetDateRangeSelection {
|
|
135
|
+
kind: 'preset';
|
|
136
|
+
/** Token of a preset configured on the owning model. */
|
|
137
|
+
token: string;
|
|
138
|
+
/**
|
|
139
|
+
* Number of periods stepped from the preset's natural range - negative for earlier periods,
|
|
140
|
+
* each applying the preset's prior-range logic once, positive for later ones (reachable only
|
|
141
|
+
* when `maxDate` allows dates beyond the anchor). Omitted when zero. See `stepRange()`.
|
|
142
|
+
*/
|
|
143
|
+
offset?: number;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** A lookback of `count` units ending on the anchor date. */
|
|
147
|
+
export interface RelativeDateRangeSelection {
|
|
148
|
+
kind: 'relative';
|
|
149
|
+
/** Number of units, from 1 to {@link MAX_RELATIVE_COUNT}. */
|
|
150
|
+
count: number;
|
|
151
|
+
unit: DateRangeUnit;
|
|
152
|
+
/**
|
|
153
|
+
* True to snap the window to calendar boundaries of `unit`, counting the current (partial)
|
|
154
|
+
* unit as one - e.g. 3 calendar months ending today covers the prior two full months plus
|
|
155
|
+
* the current month to date. False (default) for a rolling window of exactly `count` units
|
|
156
|
+
* ending on the anchor date. Has no effect for days, where each day is its own boundary.
|
|
157
|
+
*/
|
|
158
|
+
snap?: boolean;
|
|
159
|
+
/** Periods stepped from the natural window - negative back, positive forward. Omitted when zero. */
|
|
160
|
+
offset?: number;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** A calendar month, clamped to the context's `maxDate` when that date falls within it. */
|
|
164
|
+
export interface MonthDateRangeSelection {
|
|
165
|
+
kind: 'month';
|
|
166
|
+
year: number;
|
|
167
|
+
/** Month of the year, 1-12. */
|
|
168
|
+
month: number;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** A calendar quarter, clamped to the context's `maxDate` when that date falls within it. */
|
|
172
|
+
export interface QuarterDateRangeSelection {
|
|
173
|
+
kind: 'quarter';
|
|
174
|
+
year: number;
|
|
175
|
+
/** Quarter of the year, 1-4. */
|
|
176
|
+
quarter: number;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** A calendar year, clamped to the context's `maxDate` when that date falls within it. */
|
|
180
|
+
export interface YearDateRangeSelection {
|
|
181
|
+
kind: 'year';
|
|
182
|
+
year: number;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** A fixed range of specific dates, as `YYYY-MM-DD` strings so the value persists as plain JSON. */
|
|
186
|
+
export interface CustomDateRangeSelection {
|
|
187
|
+
kind: 'custom';
|
|
188
|
+
start: string;
|
|
189
|
+
end: string;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The value of a {@link DateRangePickerModel} - a user's period selection, resolved to concrete
|
|
194
|
+
* dates against a {@link DateRangeContext}. Plain JSON in all its forms, so it round-trips through
|
|
195
|
+
* persistence without custom serialization.
|
|
196
|
+
*/
|
|
197
|
+
export type DateRangeSelection =
|
|
198
|
+
| PresetDateRangeSelection
|
|
199
|
+
| RelativeDateRangeSelection
|
|
200
|
+
| MonthDateRangeSelection
|
|
201
|
+
| QuarterDateRangeSelection
|
|
202
|
+
| YearDateRangeSelection
|
|
203
|
+
| CustomDateRangeSelection;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This file belongs to Hoist, an application development toolkit
|
|
3
|
+
* developed by Extremely Heavy Industries (www.xh.io | info@xh.io)
|
|
4
|
+
*
|
|
5
|
+
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
|
+
*/
|
|
7
|
+
export * from './DateRangePickerModel';
|
|
8
|
+
export * from './DateRangePresets';
|
|
9
|
+
export * from './DateRangeUtils';
|
|
10
|
+
export * from './Types';
|
|
@@ -136,6 +136,9 @@ export interface FilterChooserConfig {
|
|
|
136
136
|
|
|
137
137
|
/** Options governing persistence. */
|
|
138
138
|
persistWith?: FilterChooserPersistOptions;
|
|
139
|
+
|
|
140
|
+
/** See {@link HoistBase.xhName}. */
|
|
141
|
+
xhName?: string;
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
/**
|
|
@@ -198,10 +201,12 @@ export class FilterChooserModel extends HoistModel {
|
|
|
198
201
|
maxTags = 100,
|
|
199
202
|
maxResults = 50,
|
|
200
203
|
persistWith,
|
|
201
|
-
introHelpText
|
|
204
|
+
introHelpText,
|
|
205
|
+
xhName = null
|
|
202
206
|
}: FilterChooserConfig = {}) {
|
|
203
207
|
super();
|
|
204
208
|
makeObservable(this);
|
|
209
|
+
this.xhName = xhName;
|
|
205
210
|
|
|
206
211
|
this.bind = bind;
|
|
207
212
|
|
|
@@ -656,6 +661,4 @@ export type FilterChooserFilterSpec = CompoundFilterSpec | FieldFilterSpec;
|
|
|
656
661
|
|
|
657
662
|
/** A variant of {@link FilterLike} that excludes FunctionFilters and FilterTestFn. */
|
|
658
663
|
export type FilterChooserFilterLike =
|
|
659
|
-
|
|
|
660
|
-
| FilterChooserFilterSpec
|
|
661
|
-
| FilterChooserFilterLike[];
|
|
664
|
+
FilterChooserFilter | FilterChooserFilterSpec | FilterChooserFilterLike[];
|