@xh/hoist 87.1.1 → 87.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/.npmignore +39 -0
  2. package/CHANGELOG.md +92 -20
  3. package/admin/AdminUtils.ts +64 -0
  4. package/admin/App.scss +88 -0
  5. package/admin/columns/UserData.ts +30 -3
  6. package/admin/detail/RestDetailModel.ts +64 -0
  7. package/admin/detail/RestDetailPanel.ts +106 -0
  8. package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +27 -94
  9. package/admin/tabs/activity/tracking/ActivityTrackingPanel.ts +23 -60
  10. package/admin/tabs/activity/tracking/detail/ActivityDetailView.ts +1 -2
  11. package/admin/tabs/general/alertBanner/AlertBannerPanel.ts +2 -13
  12. package/admin/tabs/general/config/ConfigDetailPanel.ts +72 -0
  13. package/admin/tabs/general/config/ConfigPanel.ts +41 -53
  14. package/admin/tabs/general/config/ConfigPanelModel.ts +3 -0
  15. package/admin/tabs/general/config/ConfigValue.scss +14 -0
  16. package/admin/tabs/general/config/ConfigValue.ts +104 -65
  17. package/admin/tabs/userData/jsonblob/JsonBlobDetailPanel.ts +93 -0
  18. package/admin/tabs/userData/jsonblob/JsonBlobModel.ts +4 -0
  19. package/admin/tabs/userData/jsonblob/JsonBlobPanel.ts +2 -0
  20. package/admin/tabs/userData/prefs/UserPreferenceDetailPanel.ts +62 -0
  21. package/admin/tabs/userData/prefs/UserPreferenceModel.ts +10 -3
  22. package/admin/tabs/userData/prefs/UserPreferencePanel.ts +3 -1
  23. package/admin/tabs/userData/roles/details/RoleDetails.scss +2 -19
  24. package/admin/tabs/userData/roles/details/RoleDetails.ts +13 -13
  25. package/appcontainer/AboutDialogModel.ts +1 -0
  26. package/appcontainer/AppContainerModel.ts +3 -0
  27. package/appcontainer/AppStateModel.ts +1 -0
  28. package/appcontainer/BannerSourceModel.ts +1 -0
  29. package/appcontainer/ChangelogDialogModel.ts +1 -0
  30. package/appcontainer/ColChooserOptionsModel.ts +1 -0
  31. package/appcontainer/ExceptionDialogModel.ts +1 -0
  32. package/appcontainer/FeedbackDialogModel.ts +1 -0
  33. package/appcontainer/ImpersonationBarModel.ts +1 -0
  34. package/appcontainer/MessageSourceModel.ts +1 -0
  35. package/appcontainer/OptionsDialogModel.ts +1 -0
  36. package/appcontainer/PageStateModel.ts +1 -0
  37. package/appcontainer/RouterModel.ts +2 -0
  38. package/appcontainer/SizingModeModel.ts +1 -0
  39. package/appcontainer/ThemeModel.ts +1 -0
  40. package/appcontainer/ToastSourceModel.ts +1 -0
  41. package/appcontainer/UserAgentModel.ts +1 -0
  42. package/appcontainer/ViewportSizeModel.ts +1 -0
  43. package/build/types/admin/AdminUtils.d.ts +29 -0
  44. package/build/types/admin/columns/UserData.d.ts +3 -0
  45. package/build/types/admin/detail/RestDetailModel.d.ts +20 -0
  46. package/build/types/admin/detail/RestDetailPanel.d.ts +34 -0
  47. package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +5 -17
  48. package/build/types/admin/tabs/general/config/ConfigDetailPanel.d.ts +7 -0
  49. package/build/types/admin/tabs/general/config/ConfigValue.d.ts +10 -4
  50. package/build/types/admin/tabs/userData/jsonblob/JsonBlobDetailPanel.d.ts +3 -0
  51. package/build/types/admin/tabs/userData/prefs/UserPreferenceDetailPanel.d.ts +3 -0
  52. package/build/types/admin/tabs/userData/prefs/UserPreferenceModel.d.ts +3 -0
  53. package/build/types/appcontainer/AboutDialogModel.d.ts +1 -0
  54. package/build/types/appcontainer/AppContainerModel.d.ts +1 -0
  55. package/build/types/appcontainer/AppStateModel.d.ts +1 -0
  56. package/build/types/appcontainer/BannerSourceModel.d.ts +1 -0
  57. package/build/types/appcontainer/ChangelogDialogModel.d.ts +1 -0
  58. package/build/types/appcontainer/ColChooserOptionsModel.d.ts +1 -0
  59. package/build/types/appcontainer/ExceptionDialogModel.d.ts +1 -0
  60. package/build/types/appcontainer/FeedbackDialogModel.d.ts +1 -0
  61. package/build/types/appcontainer/ImpersonationBarModel.d.ts +1 -0
  62. package/build/types/appcontainer/MessageSourceModel.d.ts +1 -0
  63. package/build/types/appcontainer/OptionsDialogModel.d.ts +1 -0
  64. package/build/types/appcontainer/PageStateModel.d.ts +1 -0
  65. package/build/types/appcontainer/RouterModel.d.ts +1 -0
  66. package/build/types/appcontainer/SizingModeModel.d.ts +1 -0
  67. package/build/types/appcontainer/ThemeModel.d.ts +1 -0
  68. package/build/types/appcontainer/ToastSourceModel.d.ts +1 -0
  69. package/build/types/appcontainer/UserAgentModel.d.ts +1 -0
  70. package/build/types/appcontainer/ViewportSizeModel.d.ts +1 -0
  71. package/build/types/cmp/ag-grid/AgGridModel.d.ts +3 -1
  72. package/build/types/cmp/card/Card.d.ts +2 -2
  73. package/build/types/cmp/chart/ChartModel.d.ts +2 -0
  74. package/build/types/cmp/dataview/DataViewModel.d.ts +2 -0
  75. package/build/types/cmp/daterange/DateRangePickerModel.d.ts +266 -0
  76. package/build/types/cmp/daterange/DateRangePresets.d.ts +26 -0
  77. package/build/types/cmp/daterange/DateRangeUtils.d.ts +96 -0
  78. package/build/types/cmp/daterange/Types.d.ts +152 -0
  79. package/build/types/cmp/daterange/index.d.ts +4 -0
  80. package/build/types/cmp/filter/FilterChooserModel.d.ts +3 -1
  81. package/build/types/cmp/form/FormModel.d.ts +3 -1
  82. package/build/types/cmp/grid/GridModel.d.ts +2 -0
  83. package/build/types/cmp/grid/Types.d.ts +2 -0
  84. package/build/types/cmp/grid/filter/GridFilterModel.d.ts +1 -1
  85. package/build/types/cmp/grouping/GroupingChooserModel.d.ts +3 -1
  86. package/build/types/cmp/input/HoistInputProps.d.ts +49 -2
  87. package/build/types/cmp/tab/TabContainerModel.d.ts +3 -1
  88. package/build/types/cmp/tab/TabModel.d.ts +3 -1
  89. package/build/types/cmp/treemap/TreeMapModel.d.ts +2 -0
  90. package/build/types/cmp/viewmanager/ViewManagerModel.d.ts +2 -0
  91. package/build/types/cmp/zoneGrid/ZoneGridModel.d.ts +2 -0
  92. package/build/types/core/HoistBase.d.ts +7 -0
  93. package/build/types/core/HoistProps.d.ts +26 -1
  94. package/build/types/core/model/RootRefreshContextModel.d.ts +1 -0
  95. package/build/types/core/types/Telemetry.d.ts +1 -1
  96. package/build/types/data/Store.d.ts +3 -1
  97. package/build/types/data/StoreSelectionModel.d.ts +3 -1
  98. package/build/types/data/cube/Cube.d.ts +5 -2
  99. package/build/types/data/cube/View.d.ts +2 -0
  100. package/build/types/desktop/cmp/appOption/AutoRefreshAppOption.d.ts +3 -0
  101. package/build/types/desktop/cmp/appOption/ThemeAppOption.d.ts +3 -0
  102. package/build/types/desktop/cmp/button/Button.d.ts +2 -2
  103. package/build/types/desktop/cmp/button/ButtonGroup.d.ts +2 -2
  104. package/build/types/desktop/cmp/dash/DashConfig.d.ts +2 -0
  105. package/build/types/desktop/cmp/dash/DashViewModel.d.ts +3 -1
  106. package/build/types/desktop/cmp/dash/canvas/DashCanvasModel.d.ts +1 -1
  107. package/build/types/desktop/cmp/dash/container/DashContainerModel.d.ts +1 -1
  108. package/build/types/desktop/cmp/daterange/DateRangePicker.d.ts +66 -0
  109. package/build/types/desktop/cmp/daterange/impl/CustomTab.d.ts +3 -0
  110. package/build/types/desktop/cmp/daterange/impl/DateRangePickerLocalModel.d.ts +121 -0
  111. package/build/types/desktop/cmp/daterange/impl/PeriodTab.d.ts +2 -0
  112. package/build/types/desktop/cmp/daterange/impl/PresetsTab.d.ts +3 -0
  113. package/build/types/desktop/cmp/daterange/impl/RelativeTab.d.ts +3 -0
  114. package/build/types/desktop/cmp/daterange/impl/TabUtils.d.ts +21 -0
  115. package/build/types/desktop/cmp/daterange/index.d.ts +2 -0
  116. package/build/types/desktop/cmp/dock/DockContainerModel.d.ts +3 -1
  117. package/build/types/desktop/cmp/dock/DockViewModel.d.ts +3 -1
  118. package/build/types/desktop/cmp/grouping/GroupingChooser.d.ts +4 -1
  119. package/build/types/desktop/cmp/input/IntentInput.d.ts +38 -0
  120. package/build/types/desktop/cmp/input/NumberInput.d.ts +2 -2
  121. package/build/types/desktop/cmp/input/SegmentedControl.d.ts +25 -4
  122. package/build/types/desktop/cmp/input/TextArea.d.ts +2 -2
  123. package/build/types/desktop/cmp/input/TextInput.d.ts +2 -2
  124. package/build/types/desktop/cmp/input/index.d.ts +1 -0
  125. package/build/types/desktop/cmp/leftrightchooser/LeftRightChooserModel.d.ts +3 -1
  126. package/build/types/desktop/cmp/panel/PanelModel.d.ts +3 -1
  127. package/build/types/desktop/cmp/rest/RestGridModel.d.ts +1 -1
  128. package/build/types/desktop/cmp/tab/impl/TabContainer.d.ts +1 -1
  129. package/build/types/icon/Icon.d.ts +3 -0
  130. package/build/types/inspector/impl/InspectorUtils.d.ts +3 -0
  131. package/build/types/inspector/instances/InstancesModel.d.ts +4 -0
  132. package/build/types/kit/react-day-picker/index.d.ts +3 -3
  133. package/build/types/mobile/cmp/button/Button.d.ts +2 -2
  134. package/build/types/mobile/cmp/input/NumberInput.d.ts +2 -2
  135. package/build/types/mobile/cmp/input/SegmentedControl.d.ts +18 -4
  136. package/build/types/mobile/cmp/input/TextArea.d.ts +2 -2
  137. package/build/types/mobile/cmp/input/TextInput.d.ts +2 -2
  138. package/build/types/mobile/cmp/navigator/NavigatorModel.d.ts +3 -1
  139. package/build/types/mobile/cmp/navigator/PageModel.d.ts +3 -1
  140. package/build/types/mobile/cmp/tab/impl/TabContainer.d.ts +1 -1
  141. package/build/types/svc/InspectorService.d.ts +1 -0
  142. package/build/types/utils/js/LangUtils.d.ts +24 -5
  143. package/cmp/README.md +1 -0
  144. package/cmp/ag-grid/AgGridModel.ts +5 -0
  145. package/cmp/badge/Badge.ts +5 -3
  146. package/cmp/card/Card.ts +4 -3
  147. package/cmp/chart/ChartModel.ts +11 -1
  148. package/cmp/dataview/DataViewModel.ts +7 -1
  149. package/cmp/daterange/DateRangePickerModel.ts +628 -0
  150. package/cmp/daterange/DateRangePresets.ts +274 -0
  151. package/cmp/daterange/DateRangeUtils.ts +557 -0
  152. package/cmp/daterange/README.md +434 -0
  153. package/cmp/daterange/Types.ts +203 -0
  154. package/cmp/daterange/index.ts +10 -0
  155. package/cmp/filter/FilterChooserModel.ts +7 -4
  156. package/cmp/form/FormModel.ts +6 -0
  157. package/cmp/form/README.md +1 -0
  158. package/cmp/grid/GridModel.ts +24 -4
  159. package/cmp/grid/Types.ts +3 -0
  160. package/cmp/grid/filter/GridFilterModel.ts +3 -1
  161. package/cmp/grouping/GroupingChooserModel.ts +6 -1
  162. package/cmp/input/HoistInputProps.ts +50 -2
  163. package/cmp/input/README.md +42 -0
  164. package/cmp/layout/Box.ts +3 -2
  165. package/cmp/tab/TabContainerModel.ts +9 -1
  166. package/cmp/tab/TabModel.ts +5 -0
  167. package/cmp/treemap/SplitTreeMapModel.ts +12 -2
  168. package/cmp/treemap/TreeMapModel.ts +6 -1
  169. package/cmp/viewmanager/ViewManagerModel.ts +6 -1
  170. package/cmp/zoneGrid/ZoneGridModel.ts +7 -0
  171. package/core/HoistBase.ts +11 -0
  172. package/core/HoistProps.ts +27 -0
  173. package/core/README.md +46 -0
  174. package/core/impl/InstallServices.ts +5 -1
  175. package/core/model/RootRefreshContextModel.ts +2 -0
  176. package/core/types/Telemetry.ts +1 -1
  177. package/data/README.md +3 -1
  178. package/data/Store.ts +7 -0
  179. package/data/StoreSelectionModel.ts +5 -1
  180. package/data/cube/Cube.ts +12 -3
  181. package/data/cube/View.ts +5 -1
  182. package/desktop/README.md +2 -0
  183. package/desktop/appcontainer/LoginPanel.ts +2 -0
  184. package/desktop/cmp/button/Button.scss +71 -42
  185. package/desktop/cmp/button/Button.ts +10 -1
  186. package/desktop/cmp/button/ButtonGroup.ts +4 -1
  187. package/desktop/cmp/button/grid/ColChooserButton.ts +9 -1
  188. package/desktop/cmp/button/zoneGrid/ZoneMapperButton.ts +1 -0
  189. package/desktop/cmp/dash/DashConfig.ts +3 -0
  190. package/desktop/cmp/dash/DashViewModel.ts +14 -1
  191. package/desktop/cmp/dash/canvas/DashCanvasModel.ts +3 -1
  192. package/desktop/cmp/dash/container/DashContainerModel.ts +3 -1
  193. package/desktop/cmp/daterange/DateRangePicker.scss +577 -0
  194. package/desktop/cmp/daterange/DateRangePicker.ts +357 -0
  195. package/desktop/cmp/daterange/impl/CustomTab.ts +165 -0
  196. package/desktop/cmp/daterange/impl/DateRangePickerLocalModel.ts +482 -0
  197. package/desktop/cmp/daterange/impl/PeriodTab.ts +138 -0
  198. package/desktop/cmp/daterange/impl/PresetsTab.ts +58 -0
  199. package/desktop/cmp/daterange/impl/RelativeTab.ts +108 -0
  200. package/desktop/cmp/daterange/impl/TabUtils.ts +34 -0
  201. package/desktop/cmp/daterange/index.ts +8 -0
  202. package/desktop/cmp/dock/DockContainerModel.ts +6 -1
  203. package/desktop/cmp/dock/DockViewModel.ts +6 -1
  204. package/desktop/cmp/filechooser/FileChooser.ts +4 -0
  205. package/desktop/cmp/form/FormField.ts +1 -0
  206. package/desktop/cmp/grouping/GroupingChooser.ts +8 -2
  207. package/desktop/cmp/input/Checkbox.ts +1 -0
  208. package/desktop/cmp/input/CodeInput.ts +1 -0
  209. package/desktop/cmp/input/DateInput.scss +1 -1
  210. package/desktop/cmp/input/DateInput.ts +1 -0
  211. package/desktop/cmp/input/IntentInput.scss +202 -0
  212. package/desktop/cmp/input/IntentInput.ts +213 -0
  213. package/desktop/cmp/input/NumberInput.ts +11 -2
  214. package/desktop/cmp/input/Picker.ts +1 -0
  215. package/desktop/cmp/input/RadioInput.ts +2 -1
  216. package/desktop/cmp/input/SegmentedControl.scss +45 -2
  217. package/desktop/cmp/input/SegmentedControl.ts +66 -11
  218. package/desktop/cmp/input/Select.ts +9 -7
  219. package/desktop/cmp/input/Slider.ts +2 -0
  220. package/desktop/cmp/input/SwitchInput.ts +1 -0
  221. package/desktop/cmp/input/TextArea.ts +11 -2
  222. package/desktop/cmp/input/TextInput.ts +11 -2
  223. package/desktop/cmp/input/index.ts +1 -0
  224. package/desktop/cmp/leftrightchooser/LeftRightChooserModel.ts +5 -0
  225. package/desktop/cmp/panel/Panel.ts +8 -5
  226. package/desktop/cmp/panel/PanelModel.ts +8 -0
  227. package/desktop/cmp/panel/impl/ResizeContainer.ts +2 -1
  228. package/desktop/cmp/rest/RestGridModel.ts +4 -0
  229. package/desktop/cmp/tab/impl/TabContainer.ts +2 -0
  230. package/docs/README.md +2 -0
  231. package/docs/build-and-publish.md +23 -7
  232. package/docs/doc-registry.json +8 -0
  233. package/docs/version-compatibility.md +2 -0
  234. package/icon/Icon.ts +9 -0
  235. package/icon/index.ts +24 -0
  236. package/inspector/README.md +8 -2
  237. package/inspector/impl/InspectorUtils.ts +12 -0
  238. package/inspector/instances/DiagnosticsModel.ts +3 -2
  239. package/inspector/instances/InstancesModel.ts +81 -14
  240. package/inspector/instances/InstancesPanel.ts +6 -0
  241. package/kit/react-day-picker/index.ts +3 -3
  242. package/mobile/appcontainer/LoginPanel.ts +2 -0
  243. package/mobile/cmp/button/Button.ts +16 -3
  244. package/mobile/cmp/form/FormField.ts +1 -0
  245. package/mobile/cmp/input/Checkbox.ts +1 -0
  246. package/mobile/cmp/input/DateInput.ts +1 -0
  247. package/mobile/cmp/input/Label.ts +13 -8
  248. package/mobile/cmp/input/NumberInput.ts +11 -2
  249. package/mobile/cmp/input/SearchInput.ts +1 -0
  250. package/mobile/cmp/input/SegmentedControl.scss +36 -6
  251. package/mobile/cmp/input/SegmentedControl.ts +37 -11
  252. package/mobile/cmp/input/Select.ts +2 -0
  253. package/mobile/cmp/input/SwitchInput.ts +1 -0
  254. package/mobile/cmp/input/TextArea.ts +11 -2
  255. package/mobile/cmp/input/TextInput.ts +11 -2
  256. package/mobile/cmp/navigator/NavigatorModel.ts +6 -1
  257. package/mobile/cmp/navigator/PageModel.ts +6 -1
  258. package/mobile/cmp/tab/impl/TabContainer.ts +9 -1
  259. package/package.json +19 -19
  260. package/styles/vars.scss +32 -2
  261. package/svc/InspectorService.ts +2 -0
  262. package/svc/TraceService.ts +5 -2
  263. 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
- | FilterChooserFilter
660
- | FilterChooserFilterSpec
661
- | FilterChooserFilterLike[];
664
+ FilterChooserFilter | FilterChooserFilterSpec | FilterChooserFilterLike[];