@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.
- package/.npmignore +39 -0
- package/CHANGELOG.md +99 -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/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/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 +3 -1
- 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/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/RecordSortUtils.ts +10 -2
- 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/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 +45 -2
- 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/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 +6 -1
- 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,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
|
-
|
|
131
|
-
requirement: grid reads after a data change were already subject to a minimal async debounce
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
356
|
-
|
|
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.
|
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
|
+
});
|