@xh/hoist 87.1.0 → 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 (264) hide show
  1. package/.npmignore +39 -0
  2. package/CHANGELOG.md +99 -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/grid/impl/RecordSortUtils.ts +10 -2
  162. package/cmp/grouping/GroupingChooserModel.ts +6 -1
  163. package/cmp/input/HoistInputProps.ts +50 -2
  164. package/cmp/input/README.md +42 -0
  165. package/cmp/layout/Box.ts +3 -2
  166. package/cmp/tab/TabContainerModel.ts +9 -1
  167. package/cmp/tab/TabModel.ts +5 -0
  168. package/cmp/treemap/SplitTreeMapModel.ts +12 -2
  169. package/cmp/treemap/TreeMapModel.ts +6 -1
  170. package/cmp/viewmanager/ViewManagerModel.ts +6 -1
  171. package/cmp/zoneGrid/ZoneGridModel.ts +7 -0
  172. package/core/HoistBase.ts +11 -0
  173. package/core/HoistProps.ts +27 -0
  174. package/core/README.md +46 -0
  175. package/core/impl/InstallServices.ts +5 -1
  176. package/core/model/RootRefreshContextModel.ts +2 -0
  177. package/core/types/Telemetry.ts +1 -1
  178. package/data/README.md +3 -1
  179. package/data/Store.ts +7 -0
  180. package/data/StoreSelectionModel.ts +5 -1
  181. package/data/cube/Cube.ts +12 -3
  182. package/data/cube/View.ts +5 -1
  183. package/desktop/README.md +2 -0
  184. package/desktop/appcontainer/LoginPanel.ts +2 -0
  185. package/desktop/cmp/button/Button.scss +71 -42
  186. package/desktop/cmp/button/Button.ts +10 -1
  187. package/desktop/cmp/button/ButtonGroup.ts +4 -1
  188. package/desktop/cmp/button/grid/ColChooserButton.ts +9 -1
  189. package/desktop/cmp/button/zoneGrid/ZoneMapperButton.ts +1 -0
  190. package/desktop/cmp/dash/DashConfig.ts +3 -0
  191. package/desktop/cmp/dash/DashViewModel.ts +14 -1
  192. package/desktop/cmp/dash/canvas/DashCanvasModel.ts +3 -1
  193. package/desktop/cmp/dash/container/DashContainerModel.ts +3 -1
  194. package/desktop/cmp/daterange/DateRangePicker.scss +577 -0
  195. package/desktop/cmp/daterange/DateRangePicker.ts +357 -0
  196. package/desktop/cmp/daterange/impl/CustomTab.ts +165 -0
  197. package/desktop/cmp/daterange/impl/DateRangePickerLocalModel.ts +482 -0
  198. package/desktop/cmp/daterange/impl/PeriodTab.ts +138 -0
  199. package/desktop/cmp/daterange/impl/PresetsTab.ts +58 -0
  200. package/desktop/cmp/daterange/impl/RelativeTab.ts +108 -0
  201. package/desktop/cmp/daterange/impl/TabUtils.ts +34 -0
  202. package/desktop/cmp/daterange/index.ts +8 -0
  203. package/desktop/cmp/dock/DockContainerModel.ts +6 -1
  204. package/desktop/cmp/dock/DockViewModel.ts +6 -1
  205. package/desktop/cmp/filechooser/FileChooser.ts +4 -0
  206. package/desktop/cmp/form/FormField.ts +1 -0
  207. package/desktop/cmp/grouping/GroupingChooser.ts +8 -2
  208. package/desktop/cmp/input/Checkbox.ts +1 -0
  209. package/desktop/cmp/input/CodeInput.ts +1 -0
  210. package/desktop/cmp/input/DateInput.scss +1 -1
  211. package/desktop/cmp/input/DateInput.ts +1 -0
  212. package/desktop/cmp/input/IntentInput.scss +202 -0
  213. package/desktop/cmp/input/IntentInput.ts +213 -0
  214. package/desktop/cmp/input/NumberInput.ts +11 -2
  215. package/desktop/cmp/input/Picker.ts +1 -0
  216. package/desktop/cmp/input/RadioInput.ts +2 -1
  217. package/desktop/cmp/input/SegmentedControl.scss +45 -2
  218. package/desktop/cmp/input/SegmentedControl.ts +66 -11
  219. package/desktop/cmp/input/Select.ts +9 -7
  220. package/desktop/cmp/input/Slider.ts +2 -0
  221. package/desktop/cmp/input/SwitchInput.ts +1 -0
  222. package/desktop/cmp/input/TextArea.ts +11 -2
  223. package/desktop/cmp/input/TextInput.ts +11 -2
  224. package/desktop/cmp/input/index.ts +1 -0
  225. package/desktop/cmp/leftrightchooser/LeftRightChooserModel.ts +5 -0
  226. package/desktop/cmp/panel/Panel.ts +8 -5
  227. package/desktop/cmp/panel/PanelModel.ts +8 -0
  228. package/desktop/cmp/panel/impl/ResizeContainer.ts +2 -1
  229. package/desktop/cmp/rest/RestGridModel.ts +4 -0
  230. package/desktop/cmp/tab/impl/TabContainer.ts +2 -0
  231. package/docs/README.md +2 -0
  232. package/docs/build-and-publish.md +23 -7
  233. package/docs/doc-registry.json +8 -0
  234. package/docs/version-compatibility.md +2 -0
  235. package/icon/Icon.ts +9 -0
  236. package/icon/index.ts +24 -0
  237. package/inspector/README.md +8 -2
  238. package/inspector/impl/InspectorUtils.ts +12 -0
  239. package/inspector/instances/DiagnosticsModel.ts +3 -2
  240. package/inspector/instances/InstancesModel.ts +81 -14
  241. package/inspector/instances/InstancesPanel.ts +6 -0
  242. package/kit/react-day-picker/index.ts +3 -3
  243. package/mobile/appcontainer/LoginPanel.ts +2 -0
  244. package/mobile/cmp/button/Button.ts +16 -3
  245. package/mobile/cmp/form/FormField.ts +1 -0
  246. package/mobile/cmp/input/Checkbox.ts +1 -0
  247. package/mobile/cmp/input/DateInput.ts +1 -0
  248. package/mobile/cmp/input/Label.ts +13 -8
  249. package/mobile/cmp/input/NumberInput.ts +11 -2
  250. package/mobile/cmp/input/SearchInput.ts +1 -0
  251. package/mobile/cmp/input/SegmentedControl.scss +36 -6
  252. package/mobile/cmp/input/SegmentedControl.ts +37 -11
  253. package/mobile/cmp/input/Select.ts +2 -0
  254. package/mobile/cmp/input/SwitchInput.ts +1 -0
  255. package/mobile/cmp/input/TextArea.ts +11 -2
  256. package/mobile/cmp/input/TextInput.ts +11 -2
  257. package/mobile/cmp/navigator/NavigatorModel.ts +6 -1
  258. package/mobile/cmp/navigator/PageModel.ts +6 -1
  259. package/mobile/cmp/tab/impl/TabContainer.ts +9 -1
  260. package/package.json +19 -19
  261. package/styles/vars.scss +32 -2
  262. package/svc/InspectorService.ts +2 -0
  263. package/svc/TraceService.ts +5 -2
  264. package/utils/js/LangUtils.ts +37 -5
package/.npmignore ADDED
@@ -0,0 +1,39 @@
1
+ # Development tooling directories
2
+ .claude
3
+ .github
4
+ .husky
5
+ .idea
6
+ .planning
7
+ node_modules
8
+ scripts
9
+
10
+ # Development configuration
11
+ *.log
12
+ *.spec.ts
13
+ *.spec.d.ts
14
+ *.tgz
15
+ .DS_Store
16
+ .editorconfig
17
+ .gitignore
18
+ .mcp.json
19
+ .npmrc
20
+ .nvmrc
21
+ .prettierignore
22
+ .prettierrc.json
23
+ .stylelintrc
24
+ context7.json
25
+ eslint.config.js
26
+ pnpm-lock.yaml
27
+ pnpm-workspace.yaml
28
+ tsconfig.tsbuildinfo
29
+ webpack.config.intellij.js
30
+
31
+ # Development documentation
32
+ AGENTS.md
33
+ CLAUDE.md
34
+ CLAUDE.local.md
35
+ SECURITY.md
36
+
37
+ # Internal docs - not served by the MCP doc registry, not useful to consumers
38
+ docs/planning
39
+ docs/archive
package/CHANGELOG.md CHANGED
@@ -12,6 +12,86 @@
12
12
  3. Plain ASCII punctuation only. Use " - " for in-sentence breaks, never an em dash.
13
13
  -->
14
14
 
15
+ ## 87.2.0 - 2026-09-08
16
+
17
+ ### 🎁 New Features
18
+
19
+ * Added `DateRangePicker` (`desktop/cmp/daterange`), a compact control for selecting a period as a
20
+ preset (Today, MTD, Prev 30 Days, ...), a relative lookback, a calendar month or year, or a custom
21
+ date range. Its `DateRangePickerModel` persists the selection as JSON and resolves it to date
22
+ ranges and `FieldFilterSpec`s.
23
+ * Added `IntentInput` (desktop), a compact input for selecting a Hoist `Intent`.
24
+ * Added `SegmentedControl.showOptionDividers` and `SegmentedControl.showTrayBackground` (desktop and
25
+ mobile) to provide more structure by default to the input control with easy options to customize.
26
+ * Added `SegmentedControl.equalSegmentWidths` to the desktop control, matching the existing mobile
27
+ prop. Defaults to `true`, so a filled control now divides its width into equal segments rather
28
+ than sizing each option to its own label.
29
+ * Added `HoistBase.xhName`, an optional developer-facing name for an instance, shown in log output,
30
+ trace spans (new `xh.name` tag), and the Inspector. Set it on any Hoist model config to tell peers
31
+ of the same class apart - instances log as `ClassName [xhName]`, or `ClassName [id]` when unnamed.
32
+ Hoist names services, `XH.appModel`, and models created by a named parent automatically.
33
+ * Added Favorites to the Inspector's Instances grid - star any instance with an `xhName` to pin it,
34
+ and toggle the new `Favorites` quick filter to show only pinned instances. Favorites persist
35
+ across reloads, showing as placeholder rows when no instance is live.
36
+ * Added read-only detail panels to the Admin Console's Config, User Preferences, and JSON Blobs
37
+ tabs. The Config panel shows every view of a config's value - resolved, instance override,
38
+ database, and typedClass defaults - and renders notes as Markdown. Editing is now confined to the
39
+ grid's editor: double-click no longer opens a view-only dialog for read-only admins.
40
+ * Added `domAttrs`, a prop for applying arbitrary `data-*` and `aria-*` attributes to the primary
41
+ DOM element a component renders - the same element that receives `data-testid` from `testId`.
42
+ Supported by `Box` and the layout components built on it (and therefore `Panel`, `Toolbar`, and
43
+ similar containers), `Button`, `ButtonGroup`, `Card`, `Badge`, `FormField`, and the desktop and
44
+ mobile inputs.
45
+ * Added `enablePasswordManagers` to `TextInput`, `TextArea`, and `NumberInput` (desktop and mobile).
46
+ Defaults to `false`, applying `data-1p-ignore`, `data-lpignore`, and `data-bwignore` so password
47
+ managers stop offering saved logins on ordinary data-entry fields. Apps with hand-rolled login
48
+ forms should set it `true` on their credential inputs - Hoist's own `LoginPanel` already does.
49
+
50
+ ### 🐞 Bug Fixes
51
+
52
+ * Fixed `testId` being silently dropped by desktop `Slider`, desktop `FileChooser`, and mobile
53
+ `Label` - all accepted the prop but never emitted a `data-testid` attribute.
54
+ * Fixed icon misalignment in desktop `DateInput` when a `leftIcon` is specified.
55
+ * Fixed `SegmentedControl.fill: false` leaving an empty run of tray to the right of its options -
56
+ the control now sizes to its options.
57
+ * Fixed desktop `SegmentedControl` keeping the last clicked segment highlighted after its bound
58
+ value was cleared programmatically - the control now renders no selection, with `aria-checked`
59
+ cleared, whenever its value matches no option.
60
+ * Fixed desktop `Select` not reliably scrolling the selected option into view when opening its
61
+ menu - a regression from the v86 react-select upgrade. Selects with `enableFilter: false` never
62
+ scrolled; others did so intermittently.
63
+ * `Store` and `Cube` now throw a clear error at construction when given fields with duplicate
64
+ names. Previously such a `Cube` failed later with a cryptic `Cannot redefine property` error when
65
+ creating a `View` that exposes leaves.
66
+
67
+ ### ✨ Styles
68
+
69
+ * Desktop `Button` active styling now also keys on Blueprint's `bp6-active` class, so any button
70
+ that opens an *uncontrolled* popover renders in its `active` state while that popover is showing,
71
+ with no per-component code. This brings Hoist's own menu triggers into line (`AppMenuButton`,
72
+ `ExpandToLevelButton`, `DashCanvasAddViewButton`, `DashContainerMenuButton`, and the `Toolbar`,
73
+ `TabSwitcher`, and `DashCanvas` overflow menus) and applies to app buttons as well. A button that
74
+ needs full control of its appearance can opt out by taking ownership of its popover's `isOpen`.
75
+ * `SegmentedControl.outlined` now adds a border to the tray without also removing its background,
76
+ and defaults to `true`. Pair with `showTrayBackground: false` for the previous appearance.
77
+ * Restyled the `SegmentedControl` tray to draw its background from `--xh-bg-alt` in both themes,
78
+ replacing a bespoke blue-grey mix that read heavier than the surrounding theme.
79
+ * Added `--xh-date-range-picker-*` CSS variables for the new `DateRangePicker`.
80
+ * The desktop `GroupingChooser` trigger button now renders `outlined` when in button mode
81
+ (`styleButtonAsInput: false`), matching the `ViewManager` trigger.
82
+
83
+ ### 📚 Libraries
84
+
85
+ * @daypicker/react `added @ 10.0` (replaces react-day-picker under its new package name)
86
+ * react-day-picker `removed`
87
+
88
+ ## 87.1.1 - 2026-09-02
89
+
90
+ ### 🐞 Bug Fixes
91
+
92
+ * Fixed `GridModel.getSortedRecords()` throwing e.g. grid exports when grouped by a non-string
93
+ field. Group values are now coerced to string keys before sorting.
94
+
15
95
  ## 87.1.0 - 2026-08-28
16
96
 
17
97
  ### 🎁 New Features
@@ -30,12 +110,11 @@
30
110
 
31
111
  * Upgraded `react-dropzone` to v20, which drops its UMD build and ships as an ESM + CJS package with
32
112
  an `exports` map. Requires Node >= 22 to install.
33
- * Added an opt-in `enforceValueInOptions` prop to the desktop and mobile `Select`, constraining the
34
- value to the current `options` and dropping any selection no longer found there. Enforced once
35
- `options` is non-null, so pass null while options load.
36
113
  * Extended the package `sideEffects` declaration to cover the vendored golden-layout implementation
37
114
  and the barrels with registration or configuration side effects on import - icon, mobx, blueprint
38
- kit, golden-layout kit, and persist. Required by the tree-shaking in hoist-dev-utils v15.
115
+ kit, golden-layout kit, and persist. ~~Required by the tree-shaking in hoist-dev-utils v15.~~
116
+ (Tree-shaking reverted in dev-utils 15.0.1 - take 15.0.1+, see
117
+ [#4640](https://github.com/xh/hoist-react/issues/4640).)
39
118
  * Restructured `PersistenceProvider` provider registration to remove a base/subclass import cycle
40
119
  that breaks under tree-shaking bundlers' re-export optimization. No API change.
41
120
 
@@ -126,11 +205,11 @@ and performance optimizations, grouped by topic below.
126
205
 
127
206
  #### Grid - Data Update Timing
128
207
 
129
- * Grids now always apply `Store` data changes to ag-Grid in a fresh macrotask - pending UI
130
- updates (e.g. load masks) paint first, and rapid changes coalesce. This strengthens an existing
131
- requirement: grid reads after a data change were already subject to a minimal async debounce
132
- and should already route through `GridModel.whenReadyAsync()`, which now provides a hard
133
- guarantee that all store data has been applied to ag-Grid.
208
+ * Grids now always apply `Store` data changes to ag-Grid in a fresh macrotask - pending UI updates
209
+ (e.g. load masks) paint first, and rapid changes coalesce. This strengthens an existing
210
+ requirement: grid reads after a data change were already subject to a minimal async debounce and
211
+ should already route through `GridModel.whenReadyAsync()`, which now provides a hard guarantee
212
+ that all store data has been applied to ag-Grid.
134
213
  * Grids now pace update-driven re-sorts and managed autosizes off their own measured cost, instead
135
214
  of re-running them every tick. Managed autosize still runs immediately on a `Store` load or filter
136
215
  change. Tune via the `deferredSortFactor` and `deferredAutosizeFactor` experimental flags.
@@ -235,13 +314,13 @@ columns.
235
314
  scrolled to the `top`, `middle`, or `bottom` of the viewport, instead of scrolling only the
236
315
  minimum amount required.
237
316
  * Improved `Grid` data update performance with tiered ag-Grid transaction handling. Update
238
- transactions that provably cannot affect row order, grouping, or tree structure now skip
239
- ag-Grid's model refresh entirely, and transactions that would re-order rows apply their cell
240
- values immediately, with row order restored by a managed, idle-scheduled re-sort. New records
241
- still sort into place on arrival.
317
+ transactions that provably cannot affect row order, grouping, or tree structure now skip ag-Grid's
318
+ model refresh entirely, and transactions that would re-order rows apply their cell values
319
+ immediately, with row order restored by a managed, idle-scheduled re-sort. New records still sort
320
+ into place on arrival.
242
321
  * Added `StoreTransaction.changedFields`, letting data producers assert exactly which fields a
243
- value-only update touched. Cube `View`s supply this automatically, extending the no-re-sort
244
- Grid fast path to view-connected stores. See the data package README for details.
322
+ value-only update touched. Cube `View`s supply this automatically, extending the no-re-sort Grid
323
+ fast path to view-connected stores. See the data package README for details.
245
324
 
246
325
  #### Admin Console
247
326
 
@@ -286,8 +365,8 @@ columns.
286
365
  stores, and adds a Diagnostics panel - a live readout of the data-pipeline `diagnostics`
287
366
  published by selected Stores, Cubes, Cube Views, and GridModels, with controls to reset counters
288
367
  and stream ops to the devtools console.
289
- * Added `XH.getCubes()` and `XH.getViews()` to enumerate all active `Cube` and `View` instances,
290
- now registered with Hoist's instance registry.
368
+ * Added `XH.getCubes()` and `XH.getViews()` to enumerate all active `Cube` and `View` instances, now
369
+ registered with Hoist's instance registry.
291
370
  * Added `useComposedRefs` - a hook variant of `composeRefs` that manages identity via `useCallback`
292
371
  and forwards React 19 ref-callback cleanups. Prefer it when composing refs within a component
293
372
  render function.
@@ -351,9 +430,9 @@ columns.
351
430
  * Removed the deprecated webpack-only `~` prefix from bare-module SCSS imports and the `inter-ui`
352
431
  font-path URL in framework styles. Modern sass-loader and css-loader resolve the same package
353
432
  paths without it, and the prefix breaks under other bundlers.
354
- * Replaced `GridExperimentalFlags.deltaSort` with `deltaSortRatio` - Hoist now manages ag-Grid
355
- delta sorting automatically, using it for re-sorts touching fewer than this percentage of rows
356
- (default 50). See the Grid transaction handling entry under New Features.
433
+ * Replaced `GridExperimentalFlags.deltaSort` with `deltaSortRatio` - Hoist now manages ag-Grid delta
434
+ sorting automatically, using it for re-sorts touching fewer than this percentage of rows (default
435
+ 50). See the Grid transaction handling entry under New Features.
357
436
  * Added `GridExperimentalFlags.deferredSortFactor` to tune the pacing of the managed re-sort on
358
437
  updating grids - a re-sort costing E ms defers the next for `E * factor` (default 4). Set 0 to
359
438
  disable deferral and re-sort synchronously on every change.
@@ -5,8 +5,14 @@
5
5
  * Copyright © 2026 Extremely Heavy Industries Inc.
6
6
  */
7
7
  import {AppModel} from '@xh/hoist/admin/AppModel';
8
+ import {div, span} from '@xh/hoist/cmp/layout';
9
+ import {markdown} from '@xh/hoist/cmp/markdown';
8
10
  import {XH} from '@xh/hoist/core';
11
+ import {defaultReadonlyRenderer} from '@xh/hoist/desktop/cmp/form';
12
+ import {jsonInput} from '@xh/hoist/desktop/cmp/input';
9
13
  import {LocalDate} from '@xh/hoist/utils/datetime';
14
+ import {isString} from 'lodash';
15
+ import {ReactElement, ReactNode} from 'react';
10
16
 
11
17
  /**
12
18
  * Generate a standardized filename for an Admin module grid export, without datestamp.
@@ -26,3 +32,61 @@ export function exportFilenameWithDate(moduleName: string): () => string {
26
32
  export function getAppModel<T extends AppModel>() {
27
33
  return XH.appModel as T;
28
34
  }
35
+
36
+ /** Muted "N/A" placeholder for empty values in read-only detail forms. */
37
+ export function naSpan(): ReactNode {
38
+ return span({item: 'N/A', className: 'xh-text-color-muted'});
39
+ }
40
+
41
+ /**
42
+ * Default readonly renderer for detail forms - FormField's standard formatting (dates, numbers,
43
+ * booleans, JSON, text), or {@link naSpan} when null.
44
+ */
45
+ export function valOrNa(v: any): ReactNode {
46
+ return v != null ? defaultReadonlyRenderer(v) : naSpan();
47
+ }
48
+
49
+ /**
50
+ * Read-only JsonInput for detail displays - fixed to `height` if given, otherwise filling its
51
+ * container. Pair fill mode with the `xh-admin-readonly-form__fill` class on the enclosing
52
+ * `formFieldSet` to have it take the remaining panel height.
53
+ */
54
+ export function readonlyJsonInput(value: any, height: number = null): ReactElement {
55
+ return jsonInput({
56
+ value: isString(value) ? value : JSON.stringify(value),
57
+ readonly: true,
58
+ autoFormat: true,
59
+ enableSearch: true,
60
+ width: '100%',
61
+ ...(height != null ? {height} : {flex: 1, height: '100%'})
62
+ });
63
+ }
64
+
65
+ /**
66
+ * Readonly renderer for stored scalar values (config / preference values) - the value as literal
67
+ * text, with no number or date formatting, or {@link naSpan} when null.
68
+ */
69
+ export function rawValueRenderer(v: any): ReactNode {
70
+ return v == null ? naSpan() : v.toString();
71
+ }
72
+
73
+ /** Readonly renderer for JSON fields in detail forms - {@link readonlyJsonInput} in fill mode. */
74
+ export function jsonRenderer(v: any): ReactNode {
75
+ return v == null ? naSpan() : readonlyJsonInput(v);
76
+ }
77
+
78
+ /** Readonly renderer for boolean fields in detail forms - "Yes" / "No", or {@link naSpan} when null. */
79
+ export function yesNoRenderer(v: boolean): ReactNode {
80
+ return v == null ? naSpan() : v ? 'Yes' : 'No';
81
+ }
82
+
83
+ /**
84
+ * Readonly renderer for free-text fields in detail forms that may contain Markdown (notes,
85
+ * descriptions). Renders via {@link markdown} within a wrapper styled for compact display, or
86
+ * {@link naSpan} when null.
87
+ */
88
+ export function markdownRenderer(v: string): ReactNode {
89
+ return v == null
90
+ ? naSpan()
91
+ : div({className: 'xh-admin-readonly-form__markdown', item: markdown({content: v})});
92
+ }
package/admin/App.scss CHANGED
@@ -23,6 +23,94 @@
23
23
  background-color: var(--xh-intent-primary-darker);
24
24
  font-family: var(--xh-font-family-mono);
25
25
  }
26
+
27
+ // Value type badges - shown in grid cells and detail forms, so drop Badge's inline-text offset.
28
+ // Fixed width keeps a column of mixed types from looking ragged.
29
+ .xh-value-type-badge {
30
+ --xh-badge-font-size: 0.85em;
31
+ --xh-badge-font-weight: normal;
32
+ margin-left: 0;
33
+ width: 64px;
34
+ }
35
+ }
36
+
37
+ // Read-only detail forms within the Admin Console - stacked fields with small, muted, uppercase
38
+ // labels sitting directly above their values. Apply to the container of a read-only `form`.
39
+ // Styled entirely via the framework's form-field CSS vars - see `styles/vars.scss`.
40
+ .xh-admin-readonly-form {
41
+ --xh-form-field-margin: 0 0 var(--xh-pad-half-px) 0;
42
+ --xh-form-field-label-color: var(--xh-text-color-muted);
43
+ --xh-form-field-label-font-size: var(--xh-font-size-small-px);
44
+ --xh-form-field-label-font-weight: 600;
45
+ --xh-form-field-label-text-transform: uppercase;
46
+ --xh-form-field-readonly-label-margin: 0;
47
+
48
+ // A field set (or other child) that stretches to fill the remaining height, passing the flex
49
+ // down to a read-only code editor within. `.xh-card` renders as a <fieldset>, so make it an
50
+ // explicit flex column.
51
+ &__fill {
52
+ display: flex;
53
+ // Floor via flex-basis, not min-height - a min-height-clamped <fieldset> collapses its content.
54
+ flex: 1 0 250px;
55
+ flex-direction: column;
56
+
57
+ > .xh-card__content,
58
+ .xh-form-field,
59
+ .xh-form-field__inner,
60
+ .xh-form-field__readonly-display {
61
+ flex: 1;
62
+ min-height: 0;
63
+ }
64
+
65
+ // FormField's read-only wrapper is a block - make it (and the display within) flex columns
66
+ // so a percentage-height editor has a definite height to fill.
67
+ .xh-form-field__inner,
68
+ .xh-form-field__readonly-display {
69
+ display: flex;
70
+ flex-direction: column;
71
+ }
72
+
73
+ .xh-form-field__readonly-display {
74
+ padding: 0;
75
+ }
76
+ }
77
+
78
+ // Markdown-rendered text fields - undo the readonly display's pre-wrap (which turns
79
+ // react-markdown's inter-element newlines into blank lines) and tighten block spacing.
80
+ &__markdown {
81
+ white-space: normal;
82
+
83
+ p,
84
+ ul,
85
+ ol {
86
+ margin: 0 0 var(--xh-pad-half-px) 0;
87
+ }
88
+
89
+ ul,
90
+ ol {
91
+ padding-left: 1.5em;
92
+ }
93
+
94
+ li > p {
95
+ margin: 0;
96
+ }
97
+
98
+ > :last-child {
99
+ margin-bottom: 0;
100
+ }
101
+ }
102
+
103
+ // Frame read-only editors - they otherwise float borderless within a field set.
104
+ .xh-code-input {
105
+ border: var(--xh-border-solid);
106
+ border-radius: var(--xh-border-radius-px);
107
+ }
108
+ }
109
+
110
+ // RestDetailPanel form body.
111
+ .xh-admin-rest-detail__form {
112
+ padding: var(--xh-pad-px);
113
+ gap: var(--xh-pad-px);
26
114
  }
27
115
 
28
116
  .xh-units-label {
@@ -4,9 +4,13 @@
4
4
  *
5
5
  * Copyright © 2026 Extremely Heavy Industries Inc.
6
6
  */
7
+ import {badge} from '@xh/hoist/cmp/badge';
7
8
  import * as Col from '@xh/hoist/cmp/grid/columns';
8
- import {truncate} from 'lodash';
9
9
  import {ColumnSpec} from '@xh/hoist/cmp/grid/columns';
10
+ import {Intent} from '@xh/hoist/core';
11
+ import {Icon} from '@xh/hoist/icon';
12
+ import {truncate} from 'lodash';
13
+ import {ReactElement} from 'react';
10
14
 
11
15
  export const value: ColumnSpec = {
12
16
  field: {name: 'value', type: 'auto'},
@@ -35,8 +39,31 @@ export const valueType: ColumnSpec = {
35
39
  type: 'string',
36
40
  displayName: 'Type'
37
41
  },
38
- width: 80,
39
- align: 'center'
42
+ width: 90,
43
+ align: 'center',
44
+ renderer: v => valueTypeRenderer(v)
45
+ };
46
+
47
+ /** Renders a config value type (json, string, int, bool, pwd...) as an icon + colored badge. */
48
+ export function valueTypeRenderer(valueType: string): ReactElement {
49
+ if (!valueType) return null;
50
+ const {icon, intent} = VALUE_TYPE_BADGES[valueType] ?? {};
51
+ return badge({
52
+ item: valueType,
53
+ icon: icon?.(),
54
+ intent,
55
+ className: 'xh-value-type-badge'
56
+ });
57
+ }
58
+
59
+ const VALUE_TYPE_BADGES: Record<string, {icon: () => ReactElement; intent?: Intent}> = {
60
+ json: {icon: Icon.json, intent: 'primary'},
61
+ bool: {icon: Icon.checkSquare, intent: 'success'},
62
+ int: {icon: Icon.calculator, intent: 'warning'},
63
+ long: {icon: Icon.calculator, intent: 'warning'},
64
+ double: {icon: Icon.calculator, intent: 'warning'},
65
+ pwd: {icon: Icon.lock, intent: 'danger'},
66
+ string: {icon: Icon.list}
40
67
  };
41
68
 
42
69
  export const groupName: ColumnSpec = {
@@ -0,0 +1,64 @@
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 {FormModel} from '@xh/hoist/cmp/form';
8
+ import {HoistModel, lookup, managed} from '@xh/hoist/core';
9
+ import {StoreRecord} from '@xh/hoist/data';
10
+ import {RestGridModel} from '@xh/hoist/desktop/cmp/rest';
11
+ import {computed, makeObservable} from '@xh/hoist/mobx';
12
+
13
+ /**
14
+ * Backs a {@link restDetailPanel}. Resolves the host RestGridModel via `@lookup` - the model of an
15
+ * enclosing component must expose it as a public property - then tracks its single selection and
16
+ * mirrors the selected record into a read-only FormModel with a field for every store field.
17
+ */
18
+ export class RestDetailModel extends HoistModel {
19
+ override xhImpl = true;
20
+
21
+ @lookup(RestGridModel) gridModel: RestGridModel;
22
+
23
+ @managed formModel: FormModel;
24
+
25
+ get record(): StoreRecord {
26
+ return this.gridModel.selectedRecord;
27
+ }
28
+
29
+ @computed
30
+ get hasSelection(): boolean {
31
+ return !!this.record;
32
+ }
33
+
34
+ get readonly(): boolean {
35
+ return this.gridModel.readonly;
36
+ }
37
+
38
+ constructor() {
39
+ super();
40
+ makeObservable(this);
41
+ }
42
+
43
+ override onLinked() {
44
+ this.formModel = new FormModel({
45
+ readonly: true,
46
+ fields: this.gridModel.store.fields.map(f => ({
47
+ name: f.name,
48
+ displayName: f.displayName
49
+ }))
50
+ });
51
+
52
+ // Record identity changes on selection and on store reload (e.g. after a save), so the
53
+ // form tracks both a new selection and fresh data for the current one.
54
+ this.addReaction({
55
+ track: () => this.record,
56
+ run: rec => this.formModel.init(rec?.data ?? {}),
57
+ fireImmediately: true
58
+ });
59
+ }
60
+
61
+ edit() {
62
+ this.gridModel.editRecord(this.record);
63
+ }
64
+ }
@@ -0,0 +1,106 @@
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 {valOrNa} from '@xh/hoist/admin/AdminUtils';
8
+ import {form, FormModel} from '@xh/hoist/cmp/form';
9
+ import {filler, placeholder, vframe} from '@xh/hoist/cmp/layout';
10
+ import {creates, hoistCmp, HoistProps, PersistOptions} from '@xh/hoist/core';
11
+ import {StoreRecord} from '@xh/hoist/data';
12
+ import {button} from '@xh/hoist/desktop/cmp/button';
13
+ import {panel} from '@xh/hoist/desktop/cmp/panel';
14
+ import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
15
+ import {Icon} from '@xh/hoist/icon';
16
+ import {startCase} from 'lodash';
17
+ import {ReactElement, ReactNode} from 'react';
18
+ import {RestDetailModel} from './RestDetailModel';
19
+
20
+ export interface RestDetailPanelProps extends HoistProps<RestDetailModel> {
21
+ /** Icon for the panel header and empty-state placeholder. */
22
+ icon: ReactElement;
23
+ /** Placeholder text when no single record is selected. */
24
+ emptyText?: string;
25
+ /** Default width in pixels. */
26
+ defaultSize?: number;
27
+ /** Minimum width in pixels. */
28
+ minSize?: number;
29
+ /** Options for persisting panel size and collapsed state. */
30
+ persistWith?: PersistOptions;
31
+ /** Panel title for the selected record - defaults to its `name`. */
32
+ titleFn?: (record: StoreRecord) => ReactNode;
33
+ /**
34
+ * Renders the form body for the selected record - typically one or more `formFieldSet`s of
35
+ * `formField`s bound to the store's field names. Rendered within a read-only `form` bound to
36
+ * the model's FormModel, styled with the `xh-admin-readonly-form` class.
37
+ */
38
+ renderForm: (record: StoreRecord, formModel: FormModel) => ReactNode;
39
+ }
40
+
41
+ /**
42
+ * Read-only detail view of the record selected in a RestGrid, docked to its right. Tracks the
43
+ * grid's selection, refreshes when the grid reloads, and offers an Edit button to open the grid's
44
+ * editor dialog for the selected record.
45
+ *
46
+ * Render as a sibling of `restGrid()` within an `hframe`. The host RestGridModel is resolved via
47
+ * `@lookup`, so the model of an enclosing component must expose it as a public property.
48
+ */
49
+ export const restDetailPanel = hoistCmp.factory<RestDetailPanelProps>({
50
+ displayName: 'RestDetailPanel',
51
+ className: 'xh-admin-rest-detail',
52
+ model: creates(RestDetailModel),
53
+
54
+ render({model, className, icon, titleFn, renderForm, ...props}) {
55
+ const {
56
+ emptyText = 'Select a record to view its details.',
57
+ defaultSize = 550,
58
+ minSize = 400,
59
+ persistWith
60
+ } = props,
61
+ {hasSelection, readonly, record, formModel, gridModel} = model;
62
+
63
+ return panel({
64
+ className,
65
+ title: hasSelection
66
+ ? (titleFn?.(record) ?? record.data.name)
67
+ : `${startCase(gridModel.unit)} Detail`,
68
+ icon,
69
+ compactHeader: true,
70
+ modelConfig: {
71
+ side: 'right',
72
+ defaultSize,
73
+ minSize,
74
+ persistWith,
75
+ // Skip per-selection form work while collapsed.
76
+ renderMode: 'unmountOnHide'
77
+ },
78
+ item: hasSelection
79
+ ? form({
80
+ model: formModel,
81
+ fieldDefaults: {readonlyRenderer: valOrNa},
82
+ item: vframe({
83
+ className: 'xh-admin-rest-detail__form xh-admin-readonly-form',
84
+ // Scroll when content outgrows the panel - inline to beat the frame's
85
+ // own overflow: hidden.
86
+ overflowY: 'auto',
87
+ items: renderForm(record, formModel)
88
+ })
89
+ })
90
+ : placeholder(icon, emptyText),
91
+ bbar: toolbar({
92
+ omit: !hasSelection || readonly,
93
+ items: [
94
+ filler(),
95
+ button({
96
+ text: 'Edit',
97
+ icon: Icon.edit(),
98
+ intent: 'primary',
99
+ outlined: true,
100
+ onClick: () => model.edit()
101
+ })
102
+ ]
103
+ })
104
+ });
105
+ }
106
+ });