@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
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,79 @@
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
+
15
88
  ## 87.1.1 - 2026-09-02
16
89
 
17
90
  ### 🐞 Bug Fixes
@@ -37,12 +110,11 @@
37
110
 
38
111
  * Upgraded `react-dropzone` to v20, which drops its UMD build and ships as an ESM + CJS package with
39
112
  an `exports` map. Requires Node >= 22 to install.
40
- * Added an opt-in `enforceValueInOptions` prop to the desktop and mobile `Select`, constraining the
41
- value to the current `options` and dropping any selection no longer found there. Enforced once
42
- `options` is non-null, so pass null while options load.
43
113
  * Extended the package `sideEffects` declaration to cover the vendored golden-layout implementation
44
114
  and the barrels with registration or configuration side effects on import - icon, mobx, blueprint
45
- 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).)
46
118
  * Restructured `PersistenceProvider` provider registration to remove a base/subclass import cycle
47
119
  that breaks under tree-shaking bundlers' re-export optimization. No API change.
48
120
 
@@ -133,11 +205,11 @@ and performance optimizations, grouped by topic below.
133
205
 
134
206
  #### Grid - Data Update Timing
135
207
 
136
- * Grids now always apply `Store` data changes to ag-Grid in a fresh macrotask - pending UI
137
- updates (e.g. load masks) paint first, and rapid changes coalesce. This strengthens an existing
138
- requirement: grid reads after a data change were already subject to a minimal async debounce
139
- and should already route through `GridModel.whenReadyAsync()`, which now provides a hard
140
- 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.
141
213
  * Grids now pace update-driven re-sorts and managed autosizes off their own measured cost, instead
142
214
  of re-running them every tick. Managed autosize still runs immediately on a `Store` load or filter
143
215
  change. Tune via the `deferredSortFactor` and `deferredAutosizeFactor` experimental flags.
@@ -242,13 +314,13 @@ columns.
242
314
  scrolled to the `top`, `middle`, or `bottom` of the viewport, instead of scrolling only the
243
315
  minimum amount required.
244
316
  * Improved `Grid` data update performance with tiered ag-Grid transaction handling. Update
245
- transactions that provably cannot affect row order, grouping, or tree structure now skip
246
- ag-Grid's model refresh entirely, and transactions that would re-order rows apply their cell
247
- values immediately, with row order restored by a managed, idle-scheduled re-sort. New records
248
- 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.
249
321
  * Added `StoreTransaction.changedFields`, letting data producers assert exactly which fields a
250
- value-only update touched. Cube `View`s supply this automatically, extending the no-re-sort
251
- 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.
252
324
 
253
325
  #### Admin Console
254
326
 
@@ -293,8 +365,8 @@ columns.
293
365
  stores, and adds a Diagnostics panel - a live readout of the data-pipeline `diagnostics`
294
366
  published by selected Stores, Cubes, Cube Views, and GridModels, with controls to reset counters
295
367
  and stream ops to the devtools console.
296
- * Added `XH.getCubes()` and `XH.getViews()` to enumerate all active `Cube` and `View` instances,
297
- 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.
298
370
  * Added `useComposedRefs` - a hook variant of `composeRefs` that manages identity via `useCallback`
299
371
  and forwards React 19 ref-callback cleanups. Prefer it when composing refs within a component
300
372
  render function.
@@ -358,9 +430,9 @@ columns.
358
430
  * Removed the deprecated webpack-only `~` prefix from bare-module SCSS imports and the `inter-ui`
359
431
  font-path URL in framework styles. Modern sass-loader and css-loader resolve the same package
360
432
  paths without it, and the prefix breaks under other bundlers.
361
- * Replaced `GridExperimentalFlags.deltaSort` with `deltaSortRatio` - Hoist now manages ag-Grid
362
- delta sorting automatically, using it for re-sorts touching fewer than this percentage of rows
363
- (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.
364
436
  * Added `GridExperimentalFlags.deferredSortFactor` to tune the pacing of the managed re-sort on
365
437
  updating grids - a re-sort costing E ms defers the next for `E * factor` (default 4). Set 0 to
366
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
+ });