@xh/hoist 87.1.1 → 87.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.npmignore +39 -0
- package/CHANGELOG.md +108 -20
- package/admin/AdminUtils.ts +64 -0
- package/admin/App.scss +88 -0
- package/admin/columns/UserData.ts +30 -3
- package/admin/detail/RestDetailModel.ts +64 -0
- package/admin/detail/RestDetailPanel.ts +106 -0
- package/admin/tabs/activity/tracking/ActivityTrackingModel.ts +27 -94
- package/admin/tabs/activity/tracking/ActivityTrackingPanel.ts +23 -60
- package/admin/tabs/activity/tracking/detail/ActivityDetailView.ts +1 -2
- package/admin/tabs/general/alertBanner/AlertBannerPanel.ts +2 -13
- package/admin/tabs/general/config/ConfigDetailPanel.ts +72 -0
- package/admin/tabs/general/config/ConfigPanel.ts +41 -53
- package/admin/tabs/general/config/ConfigPanelModel.ts +3 -0
- package/admin/tabs/general/config/ConfigValue.scss +14 -0
- package/admin/tabs/general/config/ConfigValue.ts +104 -65
- package/admin/tabs/userData/jsonblob/JsonBlobDetailPanel.ts +93 -0
- package/admin/tabs/userData/jsonblob/JsonBlobModel.ts +4 -0
- package/admin/tabs/userData/jsonblob/JsonBlobPanel.ts +2 -0
- package/admin/tabs/userData/prefs/UserPreferenceDetailPanel.ts +62 -0
- package/admin/tabs/userData/prefs/UserPreferenceModel.ts +10 -3
- package/admin/tabs/userData/prefs/UserPreferencePanel.ts +3 -1
- package/admin/tabs/userData/roles/details/RoleDetails.scss +2 -19
- package/admin/tabs/userData/roles/details/RoleDetails.ts +13 -13
- package/appcontainer/AboutDialogModel.ts +1 -0
- package/appcontainer/AppContainerModel.ts +3 -0
- package/appcontainer/AppStateModel.ts +1 -0
- package/appcontainer/BannerSourceModel.ts +1 -0
- package/appcontainer/ChangelogDialogModel.ts +1 -0
- package/appcontainer/ColChooserOptionsModel.ts +1 -0
- package/appcontainer/ExceptionDialogModel.ts +1 -0
- package/appcontainer/FeedbackDialogModel.ts +1 -0
- package/appcontainer/ImpersonationBarModel.ts +1 -0
- package/appcontainer/MessageSourceModel.ts +1 -0
- package/appcontainer/OptionsDialogModel.ts +1 -0
- package/appcontainer/PageStateModel.ts +1 -0
- package/appcontainer/RouterModel.ts +2 -0
- package/appcontainer/SizingModeModel.ts +1 -0
- package/appcontainer/ThemeModel.ts +1 -0
- package/appcontainer/ToastSourceModel.ts +1 -0
- package/appcontainer/UserAgentModel.ts +1 -0
- package/appcontainer/ViewportSizeModel.ts +1 -0
- package/build/types/admin/AdminUtils.d.ts +29 -0
- package/build/types/admin/columns/UserData.d.ts +3 -0
- package/build/types/admin/detail/RestDetailModel.d.ts +20 -0
- package/build/types/admin/detail/RestDetailPanel.d.ts +34 -0
- package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +5 -17
- package/build/types/admin/tabs/general/config/ConfigDetailPanel.d.ts +7 -0
- package/build/types/admin/tabs/general/config/ConfigValue.d.ts +10 -4
- package/build/types/admin/tabs/userData/jsonblob/JsonBlobDetailPanel.d.ts +3 -0
- package/build/types/admin/tabs/userData/prefs/UserPreferenceDetailPanel.d.ts +3 -0
- package/build/types/admin/tabs/userData/prefs/UserPreferenceModel.d.ts +3 -0
- package/build/types/appcontainer/AboutDialogModel.d.ts +1 -0
- package/build/types/appcontainer/AppContainerModel.d.ts +1 -0
- package/build/types/appcontainer/AppStateModel.d.ts +1 -0
- package/build/types/appcontainer/BannerSourceModel.d.ts +1 -0
- package/build/types/appcontainer/ChangelogDialogModel.d.ts +1 -0
- package/build/types/appcontainer/ColChooserOptionsModel.d.ts +1 -0
- package/build/types/appcontainer/ExceptionDialogModel.d.ts +1 -0
- package/build/types/appcontainer/FeedbackDialogModel.d.ts +1 -0
- package/build/types/appcontainer/ImpersonationBarModel.d.ts +1 -0
- package/build/types/appcontainer/MessageSourceModel.d.ts +1 -0
- package/build/types/appcontainer/OptionsDialogModel.d.ts +1 -0
- package/build/types/appcontainer/PageStateModel.d.ts +1 -0
- package/build/types/appcontainer/RouterModel.d.ts +1 -0
- package/build/types/appcontainer/SizingModeModel.d.ts +1 -0
- package/build/types/appcontainer/ThemeModel.d.ts +1 -0
- package/build/types/appcontainer/ToastSourceModel.d.ts +1 -0
- package/build/types/appcontainer/UserAgentModel.d.ts +1 -0
- package/build/types/appcontainer/ViewportSizeModel.d.ts +1 -0
- package/build/types/cmp/ag-grid/AgGridModel.d.ts +3 -1
- package/build/types/cmp/card/Card.d.ts +2 -2
- package/build/types/cmp/chart/ChartModel.d.ts +2 -0
- package/build/types/cmp/dataview/DataViewModel.d.ts +2 -0
- package/build/types/cmp/daterange/DateRangePickerModel.d.ts +266 -0
- package/build/types/cmp/daterange/DateRangePresets.d.ts +26 -0
- package/build/types/cmp/daterange/DateRangeUtils.d.ts +96 -0
- package/build/types/cmp/daterange/Types.d.ts +152 -0
- package/build/types/cmp/daterange/index.d.ts +4 -0
- package/build/types/cmp/filter/FilterChooserModel.d.ts +3 -1
- package/build/types/cmp/form/FormModel.d.ts +3 -1
- package/build/types/cmp/grid/GridModel.d.ts +2 -0
- package/build/types/cmp/grid/Types.d.ts +2 -0
- package/build/types/cmp/grid/filter/GridFilterModel.d.ts +1 -1
- package/build/types/cmp/grid/impl/ColumnWidthCalculator.d.ts +1 -1
- package/build/types/cmp/grouping/GroupingChooserModel.d.ts +3 -1
- package/build/types/cmp/input/HoistInputProps.d.ts +49 -2
- package/build/types/cmp/tab/TabContainerModel.d.ts +3 -1
- package/build/types/cmp/tab/TabModel.d.ts +3 -1
- package/build/types/cmp/treemap/TreeMapModel.d.ts +2 -0
- package/build/types/cmp/viewmanager/ViewManagerModel.d.ts +2 -0
- package/build/types/cmp/zoneGrid/ZoneGridModel.d.ts +2 -0
- package/build/types/core/HoistBase.d.ts +7 -0
- package/build/types/core/HoistProps.d.ts +26 -1
- package/build/types/core/model/RootRefreshContextModel.d.ts +1 -0
- package/build/types/core/types/Telemetry.d.ts +1 -1
- package/build/types/data/Store.d.ts +3 -1
- package/build/types/data/StoreSelectionModel.d.ts +3 -1
- package/build/types/data/cube/Cube.d.ts +5 -2
- package/build/types/data/cube/View.d.ts +2 -0
- package/build/types/desktop/cmp/appOption/AutoRefreshAppOption.d.ts +3 -0
- package/build/types/desktop/cmp/appOption/ThemeAppOption.d.ts +3 -0
- package/build/types/desktop/cmp/button/Button.d.ts +2 -2
- package/build/types/desktop/cmp/button/ButtonGroup.d.ts +2 -2
- package/build/types/desktop/cmp/dash/DashConfig.d.ts +2 -0
- package/build/types/desktop/cmp/dash/DashViewModel.d.ts +3 -1
- package/build/types/desktop/cmp/dash/canvas/DashCanvasModel.d.ts +1 -1
- package/build/types/desktop/cmp/dash/container/DashContainerModel.d.ts +1 -1
- package/build/types/desktop/cmp/daterange/DateRangePicker.d.ts +66 -0
- package/build/types/desktop/cmp/daterange/impl/CustomTab.d.ts +3 -0
- package/build/types/desktop/cmp/daterange/impl/DateRangePickerLocalModel.d.ts +121 -0
- package/build/types/desktop/cmp/daterange/impl/PeriodTab.d.ts +2 -0
- package/build/types/desktop/cmp/daterange/impl/PresetsTab.d.ts +3 -0
- package/build/types/desktop/cmp/daterange/impl/RelativeTab.d.ts +3 -0
- package/build/types/desktop/cmp/daterange/impl/TabUtils.d.ts +21 -0
- package/build/types/desktop/cmp/daterange/index.d.ts +2 -0
- package/build/types/desktop/cmp/dock/DockContainerModel.d.ts +3 -1
- package/build/types/desktop/cmp/dock/DockViewModel.d.ts +3 -1
- package/build/types/desktop/cmp/grouping/GroupingChooser.d.ts +4 -1
- package/build/types/desktop/cmp/input/IntentInput.d.ts +38 -0
- package/build/types/desktop/cmp/input/NumberInput.d.ts +2 -2
- package/build/types/desktop/cmp/input/SegmentedControl.d.ts +25 -4
- package/build/types/desktop/cmp/input/TextArea.d.ts +2 -2
- package/build/types/desktop/cmp/input/TextInput.d.ts +2 -2
- package/build/types/desktop/cmp/input/index.d.ts +1 -0
- package/build/types/desktop/cmp/leftrightchooser/LeftRightChooserModel.d.ts +3 -1
- package/build/types/desktop/cmp/panel/PanelModel.d.ts +3 -1
- package/build/types/desktop/cmp/rest/RestGridModel.d.ts +1 -1
- package/build/types/desktop/cmp/tab/impl/TabContainer.d.ts +1 -1
- package/build/types/desktop/hooks/UseHotkeys.d.ts +4 -3
- package/build/types/icon/Icon.d.ts +3 -0
- package/build/types/inspector/impl/InspectorUtils.d.ts +3 -0
- package/build/types/inspector/instances/InstancesModel.d.ts +4 -0
- package/build/types/kit/react-day-picker/index.d.ts +3 -3
- package/build/types/mobile/cmp/button/Button.d.ts +2 -2
- package/build/types/mobile/cmp/input/NumberInput.d.ts +2 -2
- package/build/types/mobile/cmp/input/SegmentedControl.d.ts +18 -4
- package/build/types/mobile/cmp/input/TextArea.d.ts +2 -2
- package/build/types/mobile/cmp/input/TextInput.d.ts +2 -2
- package/build/types/mobile/cmp/navigator/NavigatorModel.d.ts +8 -2
- package/build/types/mobile/cmp/navigator/PageModel.d.ts +3 -1
- package/build/types/mobile/cmp/tab/impl/TabContainer.d.ts +1 -1
- package/build/types/svc/InspectorService.d.ts +1 -0
- package/build/types/utils/js/LangUtils.d.ts +24 -5
- package/cmp/README.md +1 -0
- package/cmp/ag-grid/AgGridModel.ts +5 -0
- package/cmp/badge/Badge.ts +5 -3
- package/cmp/card/Card.ts +4 -3
- package/cmp/chart/ChartModel.ts +11 -1
- package/cmp/clock/Clock.ts +1 -1
- package/cmp/dataview/DataViewModel.ts +7 -1
- package/cmp/daterange/DateRangePickerModel.ts +628 -0
- package/cmp/daterange/DateRangePresets.ts +274 -0
- package/cmp/daterange/DateRangeUtils.ts +557 -0
- package/cmp/daterange/README.md +434 -0
- package/cmp/daterange/Types.ts +203 -0
- package/cmp/daterange/index.ts +10 -0
- package/cmp/filter/FilterChooserModel.ts +7 -4
- package/cmp/form/FormModel.ts +6 -0
- package/cmp/form/README.md +1 -0
- package/cmp/grid/GridModel.ts +24 -4
- package/cmp/grid/Types.ts +3 -0
- package/cmp/grid/filter/GridFilterModel.ts +3 -1
- package/cmp/grid/impl/ColumnWidthCalculator.ts +5 -10
- package/cmp/grouping/GroupingChooserModel.ts +6 -1
- package/cmp/input/HoistInputProps.ts +50 -2
- package/cmp/input/README.md +42 -0
- package/cmp/layout/Box.ts +3 -2
- package/cmp/tab/TabContainerModel.ts +9 -1
- package/cmp/tab/TabModel.ts +5 -0
- package/cmp/treemap/SplitTreeMapModel.ts +12 -2
- package/cmp/treemap/TreeMapModel.ts +6 -1
- package/cmp/viewmanager/ViewManagerModel.ts +6 -1
- package/cmp/zoneGrid/ZoneGridModel.ts +7 -0
- package/core/HoistBase.ts +11 -0
- package/core/HoistProps.ts +27 -0
- package/core/README.md +46 -0
- package/core/impl/InstallServices.ts +5 -1
- package/core/model/RootRefreshContextModel.ts +2 -0
- package/core/types/Telemetry.ts +1 -1
- package/data/README.md +3 -1
- package/data/Store.ts +7 -0
- package/data/StoreSelectionModel.ts +5 -1
- package/data/cube/Cube.ts +12 -3
- package/data/cube/View.ts +5 -1
- package/desktop/README.md +2 -0
- package/desktop/appcontainer/LoginPanel.ts +2 -0
- package/desktop/cmp/button/Button.scss +71 -42
- package/desktop/cmp/button/Button.ts +10 -1
- package/desktop/cmp/button/ButtonGroup.ts +4 -1
- package/desktop/cmp/button/grid/ColChooserButton.ts +9 -1
- package/desktop/cmp/button/zoneGrid/ZoneMapperButton.ts +1 -0
- package/desktop/cmp/dash/DashConfig.ts +3 -0
- package/desktop/cmp/dash/DashViewModel.ts +14 -1
- package/desktop/cmp/dash/canvas/DashCanvasModel.ts +3 -1
- package/desktop/cmp/dash/container/DashContainerModel.ts +3 -1
- package/desktop/cmp/daterange/DateRangePicker.scss +577 -0
- package/desktop/cmp/daterange/DateRangePicker.ts +357 -0
- package/desktop/cmp/daterange/impl/CustomTab.ts +165 -0
- package/desktop/cmp/daterange/impl/DateRangePickerLocalModel.ts +482 -0
- package/desktop/cmp/daterange/impl/PeriodTab.ts +138 -0
- package/desktop/cmp/daterange/impl/PresetsTab.ts +58 -0
- package/desktop/cmp/daterange/impl/RelativeTab.ts +108 -0
- package/desktop/cmp/daterange/impl/TabUtils.ts +34 -0
- package/desktop/cmp/daterange/index.ts +8 -0
- package/desktop/cmp/dock/DockContainerModel.ts +6 -1
- package/desktop/cmp/dock/DockViewModel.ts +6 -1
- package/desktop/cmp/filechooser/FileChooser.ts +4 -0
- package/desktop/cmp/form/FormField.ts +1 -0
- package/desktop/cmp/grid/editors/DateEditor.ts +11 -2
- package/desktop/cmp/grouping/GroupingChooser.ts +8 -2
- package/desktop/cmp/input/Checkbox.ts +1 -0
- package/desktop/cmp/input/CodeInput.ts +1 -0
- package/desktop/cmp/input/DateInput.scss +1 -1
- package/desktop/cmp/input/DateInput.ts +1 -0
- package/desktop/cmp/input/IntentInput.scss +202 -0
- package/desktop/cmp/input/IntentInput.ts +213 -0
- package/desktop/cmp/input/NumberInput.ts +11 -2
- package/desktop/cmp/input/Picker.ts +1 -0
- package/desktop/cmp/input/RadioInput.ts +2 -1
- package/desktop/cmp/input/SegmentedControl.scss +57 -5
- package/desktop/cmp/input/SegmentedControl.ts +66 -11
- package/desktop/cmp/input/Select.ts +9 -7
- package/desktop/cmp/input/Slider.ts +2 -0
- package/desktop/cmp/input/SwitchInput.ts +1 -0
- package/desktop/cmp/input/TextArea.ts +11 -2
- package/desktop/cmp/input/TextInput.ts +11 -2
- package/desktop/cmp/input/index.ts +1 -0
- package/desktop/cmp/leftrightchooser/LeftRightChooserModel.ts +5 -0
- package/desktop/cmp/panel/Panel.ts +8 -5
- package/desktop/cmp/panel/PanelModel.ts +8 -0
- package/desktop/cmp/panel/impl/ResizeContainer.ts +2 -1
- package/desktop/cmp/rest/RestGridModel.ts +4 -0
- package/desktop/cmp/tab/impl/TabContainer.ts +2 -0
- package/desktop/cmp/toolbar/Toolbar.scss +20 -6
- package/desktop/hooks/UseHotkeys.ts +41 -12
- package/docs/README.md +2 -0
- package/docs/build-and-publish.md +23 -7
- package/docs/doc-registry.json +8 -0
- package/docs/version-compatibility.md +2 -0
- package/icon/Icon.ts +9 -0
- package/icon/index.ts +24 -0
- package/inspector/README.md +8 -2
- package/inspector/impl/InspectorUtils.ts +12 -0
- package/inspector/instances/DiagnosticsModel.ts +3 -2
- package/inspector/instances/InstancesModel.ts +81 -14
- package/inspector/instances/InstancesPanel.ts +6 -0
- package/kit/react-day-picker/index.ts +3 -3
- package/mobile/appcontainer/LoginPanel.ts +2 -0
- package/mobile/cmp/button/Button.ts +16 -3
- package/mobile/cmp/form/FormField.ts +1 -0
- package/mobile/cmp/input/Checkbox.ts +1 -0
- package/mobile/cmp/input/DateInput.ts +1 -0
- package/mobile/cmp/input/Label.ts +13 -8
- package/mobile/cmp/input/NumberInput.ts +11 -2
- package/mobile/cmp/input/SearchInput.ts +1 -0
- package/mobile/cmp/input/SegmentedControl.scss +36 -6
- package/mobile/cmp/input/SegmentedControl.ts +37 -11
- package/mobile/cmp/input/Select.ts +2 -0
- package/mobile/cmp/input/SwitchInput.ts +1 -0
- package/mobile/cmp/input/TextArea.ts +11 -2
- package/mobile/cmp/input/TextInput.ts +11 -2
- package/mobile/cmp/navigator/NavigatorModel.ts +20 -8
- package/mobile/cmp/navigator/PageModel.ts +6 -1
- package/mobile/cmp/tab/impl/TabContainer.ts +9 -1
- package/package.json +19 -19
- package/styles/vars.scss +32 -2
- package/svc/InspectorService.ts +2 -0
- package/svc/TraceService.ts +5 -2
- package/utils/js/LangUtils.ts +37 -5
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,95 @@
|
|
|
12
12
|
3. Plain ASCII punctuation only. Use " - " for in-sentence breaks, never an em dash.
|
|
13
13
|
-->
|
|
14
14
|
|
|
15
|
+
## 87.3.0 - 2026-09-10
|
|
16
|
+
|
|
17
|
+
### 🐞 Bug Fixes
|
|
18
|
+
|
|
19
|
+
* Fixed `SegmentedControl` clipping an option's label when `equalSegmentWidths` divided the tray
|
|
20
|
+
too narrowly - options now hold their own content width, sharing equally only where there is room.
|
|
21
|
+
* Fixed `useHotkeys()` throwing a React hook-count error when its arguments changed across
|
|
22
|
+
renders - a collapsible `Panel` given `hotkeys` no longer crashes when first expanded. Hotkeys
|
|
23
|
+
may now also be changed after the first render.
|
|
24
|
+
* Fixed mobile `Navigator` back-navigation leaving the page stack and the route permanently out of
|
|
25
|
+
sync when a stale `allowSlidePrev` caused Swiper to silently skip the transition.
|
|
26
|
+
* Fixed `RadioInput` and `Checkbox` options overhanging the top of the `Toolbar` - they
|
|
27
|
+
are now centered within the compact item height, sharing one rule with `SwitchInput`.
|
|
28
|
+
* Fixed an unlabeled `Slider` (`labelRenderer: false`) sitting high in a compact `Toolbar`.
|
|
29
|
+
* Fixed `SegmentedControl` overflowing a compact `Toolbar` - it now takes its compact sizing from
|
|
30
|
+
the toolbar, without needing its own `compact: true`.
|
|
31
|
+
* Fixed `dateEditor` crashing when opening its picker on a column backed by a `localDate` field -
|
|
32
|
+
the editor now defaults its `valueType` from the Store field type.
|
|
33
|
+
|
|
34
|
+
## 87.2.0 - 2026-09-08
|
|
35
|
+
|
|
36
|
+
### 🎁 New Features
|
|
37
|
+
|
|
38
|
+
* Added `DateRangePicker` (`desktop/cmp/daterange`), a compact control for selecting a period as a
|
|
39
|
+
preset (Today, MTD, Prev 30 Days, ...), a relative lookback, a calendar month or year, or a custom
|
|
40
|
+
date range. Its `DateRangePickerModel` persists the selection as JSON and resolves it to date
|
|
41
|
+
ranges and `FieldFilterSpec`s.
|
|
42
|
+
* Added `IntentInput` (desktop), a compact input for selecting a Hoist `Intent`.
|
|
43
|
+
* Added `SegmentedControl.showOptionDividers` and `SegmentedControl.showTrayBackground` (desktop and
|
|
44
|
+
mobile) to provide more structure by default to the input control with easy options to customize.
|
|
45
|
+
* Added `SegmentedControl.equalSegmentWidths` to the desktop control, matching the existing mobile
|
|
46
|
+
prop. Defaults to `true`, so a filled control now divides its width into equal segments rather
|
|
47
|
+
than sizing each option to its own label.
|
|
48
|
+
* Added `HoistBase.xhName`, an optional developer-facing name for an instance, shown in log output,
|
|
49
|
+
trace spans (new `xh.name` tag), and the Inspector. Set it on any Hoist model config to tell peers
|
|
50
|
+
of the same class apart - instances log as `ClassName [xhName]`, or `ClassName [id]` when unnamed.
|
|
51
|
+
Hoist names services, `XH.appModel`, and models created by a named parent automatically.
|
|
52
|
+
* Added read-only detail panels to the Admin Console's Config, User Preferences, and JSON Blobs
|
|
53
|
+
tabs. The Config panel shows every view of a config's value - resolved, instance override,
|
|
54
|
+
database, and typedClass defaults - and renders notes as Markdown. Editing is now confined to the
|
|
55
|
+
grid's editor: double-click no longer opens a view-only dialog for read-only admins.
|
|
56
|
+
* Added `domAttrs`, a prop for applying arbitrary `data-*` and `aria-*` attributes to the primary
|
|
57
|
+
DOM element a component renders - the same element that receives `data-testid` from `testId`.
|
|
58
|
+
Supported by `Box` and the layout components built on it (and therefore `Panel`, `Toolbar`, and
|
|
59
|
+
similar containers), `Button`, `ButtonGroup`, `Card`, `Badge`, `FormField`, and the desktop and
|
|
60
|
+
mobile inputs.
|
|
61
|
+
* Added `enablePasswordManagers` to `TextInput`, `TextArea`, and `NumberInput` (desktop and mobile).
|
|
62
|
+
Defaults to `false`, applying `data-1p-ignore`, `data-lpignore`, and `data-bwignore` so password
|
|
63
|
+
managers stop offering saved logins on ordinary data-entry fields. Apps with hand-rolled login
|
|
64
|
+
forms should set it `true` on their credential inputs - Hoist's own `LoginPanel` already does.
|
|
65
|
+
|
|
66
|
+
### 🐞 Bug Fixes
|
|
67
|
+
|
|
68
|
+
* Fixed `testId` being silently dropped by desktop `Slider`, desktop `FileChooser`, and mobile
|
|
69
|
+
`Label` - all accepted the prop but never emitted a `data-testid` attribute.
|
|
70
|
+
* Fixed icon misalignment in desktop `DateInput` when a `leftIcon` is specified.
|
|
71
|
+
* Fixed `SegmentedControl.fill: false` leaving an empty run of tray to the right of its options -
|
|
72
|
+
the control now sizes to its options.
|
|
73
|
+
* Fixed desktop `SegmentedControl` keeping the last clicked segment highlighted after its bound
|
|
74
|
+
value was cleared programmatically - the control now renders no selection, with `aria-checked`
|
|
75
|
+
cleared, whenever its value matches no option.
|
|
76
|
+
* Fixed desktop `Select` not reliably scrolling the selected option into view when opening its
|
|
77
|
+
menu - a regression from the v86 react-select upgrade. Selects with `enableFilter: false` never
|
|
78
|
+
scrolled; others did so intermittently.
|
|
79
|
+
* `Store` and `Cube` now throw a clear error at construction when given fields with duplicate
|
|
80
|
+
names. Previously such a `Cube` failed later with a cryptic `Cannot redefine property` error when
|
|
81
|
+
creating a `View` that exposes leaves.
|
|
82
|
+
|
|
83
|
+
### ✨ Styles
|
|
84
|
+
|
|
85
|
+
* Desktop `Button` active styling now also keys on Blueprint's `bp6-active` class, so any button
|
|
86
|
+
that opens an *uncontrolled* popover renders in its `active` state while that popover is showing,
|
|
87
|
+
with no per-component code. This brings Hoist's own menu triggers into line (`AppMenuButton`,
|
|
88
|
+
`ExpandToLevelButton`, `DashCanvasAddViewButton`, `DashContainerMenuButton`, and the `Toolbar`,
|
|
89
|
+
`TabSwitcher`, and `DashCanvas` overflow menus) and applies to app buttons as well. A button that
|
|
90
|
+
needs full control of its appearance can opt out by taking ownership of its popover's `isOpen`.
|
|
91
|
+
* `SegmentedControl.outlined` now adds a border to the tray without also removing its background,
|
|
92
|
+
and defaults to `true`. Pair with `showTrayBackground: false` for the previous appearance.
|
|
93
|
+
* Restyled the `SegmentedControl` tray to draw its background from `--xh-bg-alt` in both themes,
|
|
94
|
+
replacing a bespoke blue-grey mix that read heavier than the surrounding theme.
|
|
95
|
+
* Added `--xh-date-range-picker-*` CSS variables for the new `DateRangePicker`.
|
|
96
|
+
* The desktop `GroupingChooser` trigger button now renders `outlined` when in button mode
|
|
97
|
+
(`styleButtonAsInput: false`), matching the `ViewManager` trigger.
|
|
98
|
+
|
|
99
|
+
### 📚 Libraries
|
|
100
|
+
|
|
101
|
+
* @daypicker/react `added @ 10.0` (replaces react-day-picker under its new package name)
|
|
102
|
+
* react-day-picker `removed`
|
|
103
|
+
|
|
15
104
|
## 87.1.1 - 2026-09-02
|
|
16
105
|
|
|
17
106
|
### 🐞 Bug Fixes
|
|
@@ -37,12 +126,11 @@
|
|
|
37
126
|
|
|
38
127
|
* Upgraded `react-dropzone` to v20, which drops its UMD build and ships as an ESM + CJS package with
|
|
39
128
|
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
129
|
* Extended the package `sideEffects` declaration to cover the vendored golden-layout implementation
|
|
44
130
|
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
|
|
131
|
+
kit, golden-layout kit, and persist. ~~Required by the tree-shaking in hoist-dev-utils v15.~~
|
|
132
|
+
(Tree-shaking reverted in dev-utils 15.0.1 - take 15.0.1+, see
|
|
133
|
+
[#4640](https://github.com/xh/hoist-react/issues/4640).)
|
|
46
134
|
* Restructured `PersistenceProvider` provider registration to remove a base/subclass import cycle
|
|
47
135
|
that breaks under tree-shaking bundlers' re-export optimization. No API change.
|
|
48
136
|
|
|
@@ -133,11 +221,11 @@ and performance optimizations, grouped by topic below.
|
|
|
133
221
|
|
|
134
222
|
#### Grid - Data Update Timing
|
|
135
223
|
|
|
136
|
-
* Grids now always apply `Store` data changes to ag-Grid in a fresh macrotask - pending UI
|
|
137
|
-
|
|
138
|
-
requirement: grid reads after a data change were already subject to a minimal async debounce
|
|
139
|
-
|
|
140
|
-
|
|
224
|
+
* Grids now always apply `Store` data changes to ag-Grid in a fresh macrotask - pending UI updates
|
|
225
|
+
(e.g. load masks) paint first, and rapid changes coalesce. This strengthens an existing
|
|
226
|
+
requirement: grid reads after a data change were already subject to a minimal async debounce and
|
|
227
|
+
should already route through `GridModel.whenReadyAsync()`, which now provides a hard guarantee
|
|
228
|
+
that all store data has been applied to ag-Grid.
|
|
141
229
|
* Grids now pace update-driven re-sorts and managed autosizes off their own measured cost, instead
|
|
142
230
|
of re-running them every tick. Managed autosize still runs immediately on a `Store` load or filter
|
|
143
231
|
change. Tune via the `deferredSortFactor` and `deferredAutosizeFactor` experimental flags.
|
|
@@ -242,13 +330,13 @@ columns.
|
|
|
242
330
|
scrolled to the `top`, `middle`, or `bottom` of the viewport, instead of scrolling only the
|
|
243
331
|
minimum amount required.
|
|
244
332
|
* 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
|
-
|
|
247
|
-
|
|
248
|
-
|
|
333
|
+
transactions that provably cannot affect row order, grouping, or tree structure now skip ag-Grid's
|
|
334
|
+
model refresh entirely, and transactions that would re-order rows apply their cell values
|
|
335
|
+
immediately, with row order restored by a managed, idle-scheduled re-sort. New records still sort
|
|
336
|
+
into place on arrival.
|
|
249
337
|
* 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
|
-
|
|
338
|
+
value-only update touched. Cube `View`s supply this automatically, extending the no-re-sort Grid
|
|
339
|
+
fast path to view-connected stores. See the data package README for details.
|
|
252
340
|
|
|
253
341
|
#### Admin Console
|
|
254
342
|
|
|
@@ -293,8 +381,8 @@ columns.
|
|
|
293
381
|
stores, and adds a Diagnostics panel - a live readout of the data-pipeline `diagnostics`
|
|
294
382
|
published by selected Stores, Cubes, Cube Views, and GridModels, with controls to reset counters
|
|
295
383
|
and stream ops to the devtools console.
|
|
296
|
-
* Added `XH.getCubes()` and `XH.getViews()` to enumerate all active `Cube` and `View` instances,
|
|
297
|
-
|
|
384
|
+
* Added `XH.getCubes()` and `XH.getViews()` to enumerate all active `Cube` and `View` instances, now
|
|
385
|
+
registered with Hoist's instance registry.
|
|
298
386
|
* Added `useComposedRefs` - a hook variant of `composeRefs` that manages identity via `useCallback`
|
|
299
387
|
and forwards React 19 ref-callback cleanups. Prefer it when composing refs within a component
|
|
300
388
|
render function.
|
|
@@ -358,9 +446,9 @@ columns.
|
|
|
358
446
|
* Removed the deprecated webpack-only `~` prefix from bare-module SCSS imports and the `inter-ui`
|
|
359
447
|
font-path URL in framework styles. Modern sass-loader and css-loader resolve the same package
|
|
360
448
|
paths without it, and the prefix breaks under other bundlers.
|
|
361
|
-
* Replaced `GridExperimentalFlags.deltaSort` with `deltaSortRatio` - Hoist now manages ag-Grid
|
|
362
|
-
|
|
363
|
-
|
|
449
|
+
* Replaced `GridExperimentalFlags.deltaSort` with `deltaSortRatio` - Hoist now manages ag-Grid delta
|
|
450
|
+
sorting automatically, using it for re-sorts touching fewer than this percentage of rows (default
|
|
451
|
+
50). See the Grid transaction handling entry under New Features.
|
|
364
452
|
* Added `GridExperimentalFlags.deferredSortFactor` to tune the pacing of the managed re-sort on
|
|
365
453
|
updating grids - a re-sort costing E ms defers the next for `E * factor` (default 4). Set 0 to
|
|
366
454
|
disable deferral and re-sort synchronously on every change.
|
package/admin/AdminUtils.ts
CHANGED
|
@@ -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:
|
|
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
|
+
});
|