@xh/hoist 86.0.1 → 86.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/CHANGELOG.md +104 -0
- package/admin/AdminJsonDisplay.ts +31 -0
- package/admin/App.scss +3 -11
- package/admin/jsonsearch/impl/JsonSearchImplModel.ts +4 -4
- package/admin/tabs/cluster/instances/connpool/ConnPoolMonitorPanel.ts +3 -11
- package/admin/tabs/cluster/instances/services/DetailsPanel.ts +2 -13
- package/admin/tabs/cluster/objects/DetailPanel.ts +3 -12
- package/admin/tabs/userData/roles/RoleModel.ts +1 -19
- package/appcontainer/RouterModel.ts +2 -0
- package/appcontainer/ThemeModel.ts +41 -0
- package/appcontainer/login/LoginPanelModel.ts +6 -2
- package/build/types/admin/AdminJsonDisplay.d.ts +7 -0
- package/build/types/admin/jsonsearch/impl/JsonSearchImplModel.d.ts +2 -2
- package/build/types/appcontainer/RouterModel.d.ts +1 -0
- package/build/types/appcontainer/ThemeModel.d.ts +9 -0
- package/build/types/cmp/grid/Grid.d.ts +6 -3
- package/build/types/cmp/grid/GridModel.d.ts +11 -0
- package/build/types/cmp/grid/filter/GridFilterFieldSpec.d.ts +24 -6
- package/build/types/cmp/input/SegmentedControlOption.d.ts +44 -0
- package/build/types/cmp/input/index.d.ts +1 -0
- package/build/types/core/AppSpec.d.ts +8 -2
- package/build/types/core/HoistAuthModel.d.ts +6 -3
- package/build/types/data/filter/BaseFilterFieldSpec.d.ts +7 -1
- package/build/types/data/filter/FieldFilter.d.ts +2 -0
- package/build/types/desktop/cmp/button/grid/ExpandToLevelButton.d.ts +2 -1
- package/build/types/desktop/cmp/filechooser/FileChooserModel.d.ts +1 -1
- package/build/types/desktop/cmp/input/CodeInput.d.ts +7 -0
- package/build/types/desktop/cmp/input/NumberInput.d.ts +1 -1
- package/build/types/desktop/cmp/input/SegmentedControl.d.ts +1 -41
- package/build/types/desktop/cmp/input/impl/CalcWindowedMenuWidth.d.ts +20 -0
- package/build/types/desktop/cmp/rest/impl/RestFormModel.d.ts +1 -1
- package/build/types/format/FormatNumber.d.ts +11 -3
- package/build/types/icon/Icon.d.ts +5 -3
- package/build/types/mobile/cmp/appOption/SizingModeAppOption.d.ts +3 -3
- package/build/types/mobile/cmp/appOption/ThemeAppOption.d.ts +3 -3
- package/build/types/mobile/cmp/button/grid/ExpandToLevelButton.d.ts +2 -1
- package/build/types/mobile/cmp/input/NumberInput.d.ts +1 -1
- package/build/types/mobile/cmp/input/SegmentedControl.d.ts +47 -0
- package/build/types/mobile/cmp/input/SwitchInput.d.ts +1 -1
- package/build/types/mobile/cmp/input/TextInput.d.ts +3 -0
- package/build/types/mobile/cmp/input/index.d.ts +1 -0
- package/build/types/svc/JsonBlobService.d.ts +7 -7
- package/cmp/error/ErrorMessage.scss +2 -0
- package/cmp/grid/Grid.ts +17 -0
- package/cmp/grid/GridModel.ts +17 -5
- package/cmp/grid/filter/GridFilterFieldSpec.ts +33 -7
- package/cmp/grid/impl/GridHScrollbar.ts +10 -3
- package/cmp/grid/impl/MenuSupport.ts +2 -4
- package/cmp/input/SegmentedControlOption.ts +61 -0
- package/cmp/input/index.ts +1 -0
- package/core/AppSpec.ts +9 -1
- package/core/HoistAuthModel.ts +13 -6
- package/data/Field.ts +3 -1
- package/data/filter/BaseFilterFieldSpec.ts +16 -2
- package/data/filter/FieldFilter.ts +90 -21
- package/desktop/appcontainer/LoginPanel.ts +5 -3
- package/desktop/cmp/button/grid/ExpandToLevelButton.ts +4 -5
- package/desktop/cmp/filechooser/FileChooserModel.ts +1 -1
- package/desktop/cmp/grid/editors/NumberEditor.ts +16 -17
- package/desktop/cmp/grid/editors/SelectEditor.ts +15 -10
- package/desktop/cmp/grid/impl/filter/GridFilterDialog.ts +2 -2
- package/desktop/cmp/grid/impl/filter/headerfilter/HeaderFilterModel.ts +9 -4
- package/desktop/cmp/grid/impl/filter/headerfilter/custom/CustomRowModel.ts +2 -2
- package/desktop/cmp/grid/impl/filter/headerfilter/values/ValuesTabModel.ts +46 -7
- package/desktop/cmp/input/CheckboxButton.ts +21 -1
- package/desktop/cmp/input/CodeInput.ts +20 -3
- package/desktop/cmp/input/NumberInput.ts +1 -1
- package/desktop/cmp/input/SegmentedControl.ts +8 -51
- package/desktop/cmp/input/Select.ts +13 -2
- package/desktop/cmp/input/impl/CalcWindowedMenuWidth.ts +106 -0
- package/desktop/cmp/rest/impl/RestFormField.ts +1 -1
- package/desktop/cmp/tab/Tabs.scss +78 -27
- package/desktop/cmp/tab/dynamic/DynamicTabSwitcher.ts +7 -3
- package/docs/error-handling.md +9 -0
- package/format/FormatNumber.ts +69 -32
- package/icon/Icon.scss +13 -0
- package/icon/Icon.ts +7 -3
- package/icon/impl/IconHtml.ts +1 -1
- package/icon/index.ts +8 -0
- package/mcp/data/ts-registry.ts +1 -1
- package/mobile/appcontainer/LoginPanel.scss +53 -11
- package/mobile/appcontainer/LoginPanel.ts +69 -44
- package/mobile/cmp/appOption/SizingModeAppOption.ts +5 -16
- package/mobile/cmp/appOption/ThemeAppOption.ts +8 -25
- package/mobile/cmp/button/grid/ExpandToLevelButton.ts +4 -5
- package/mobile/cmp/error/impl/ErrorMessage.ts +4 -8
- package/mobile/cmp/input/CheckboxButton.ts +18 -1
- package/mobile/cmp/input/NumberInput.ts +1 -1
- package/mobile/cmp/input/SegmentedControl.scss +126 -0
- package/mobile/cmp/input/SegmentedControl.ts +210 -0
- package/mobile/cmp/input/SwitchInput.ts +1 -1
- package/mobile/cmp/input/TextInput.scss +7 -0
- package/mobile/cmp/input/TextInput.ts +11 -1
- package/mobile/cmp/input/index.ts +1 -0
- package/mobile/cmp/tab/impl/Tabs.scss +8 -0
- package/mobile/cmp/toolbar/Toolbar.scss +8 -0
- package/package.json +7 -7
- package/styles/vars.scss +21 -1
- package/svc/JsonBlobService.ts +21 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,105 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 86.2.0 - 2026-06-25
|
|
4
|
+
|
|
5
|
+
### 🎁 New Features
|
|
6
|
+
|
|
7
|
+
* `CodeInput` / `JsonInput` now auto-format content for display via their configured `formatter`,
|
|
8
|
+
controlled by a new `autoFormat` prop. On editable inputs, content is tidied automatically on blur
|
|
9
|
+
(never mid-edit) - users get clean, consistently-formatted JSON without reaching for the format
|
|
10
|
+
button. For `readonly` inputs it defaults to true, so applications can bind directly to raw source
|
|
11
|
+
values and drop their pre-formatting logic, simplifying call sites substantially.
|
|
12
|
+
* Grid column filter specs now support a `sortValue` config, letting the Values tab of the filter
|
|
13
|
+
dialog sort its entries the same way the underlying grid column sorts them. When not provided,
|
|
14
|
+
the column's own `sortValue` is used.
|
|
15
|
+
* `GridModel.levelLabels` now accepts a partial array covering only the top levels of a tree or
|
|
16
|
+
grouped grid. The "Expand to..." menu and `ExpandToLevelButton` offer one entry per labelled
|
|
17
|
+
level, so deeper, unlabelled levels (e.g. system-managed) are no longer required and are omitted
|
|
18
|
+
as expand-to targets - previously a too-short array disabled the feature entirely.
|
|
19
|
+
* Added a `lossless` option to `fmtQuantity` to compact values to millions / billions units only
|
|
20
|
+
when doing so loses no precision, rendering the full value otherwise (e.g. `7,100,100` stays
|
|
21
|
+
`7,100,100` rather than collapsing to `7.10m`).
|
|
22
|
+
|
|
23
|
+
### 🐞 Bug Fixes
|
|
24
|
+
|
|
25
|
+
* Fixed grid `NumberEditor` to allow starting an edit by typing `-`, `+`, or `.` (e.g. to enter a
|
|
26
|
+
negative or decimal value), while reliably rejecting other non-numeric keypresses.
|
|
27
|
+
* `fmtNumber` now treats a `precision` of `null` as full, unrestricted precision rather than
|
|
28
|
+
`'auto'`, aligning a `NumberInput` with `precision: null` so its blurred display matches its
|
|
29
|
+
focused and committed (full-precision) value.
|
|
30
|
+
* Fixed inline grid editing not ending when clicking empty grid space to the right of the last
|
|
31
|
+
column or below the last row - such clicks now commit the active edit.
|
|
32
|
+
* Fixed `Icon.placeholder()` to render with the correct width.
|
|
33
|
+
* `CheckboxButton` no longer leaks `HoistInputProps` into underlying HTML `<button>` element.
|
|
34
|
+
* Fixed `Select` (and `SelectEditor`) dropdown menu sizing: windowed menus now auto-size to their
|
|
35
|
+
option labels instead of the control/cell width, and an explicit `menuWidth` is respected rather
|
|
36
|
+
than overridden by content auto-sizing.
|
|
37
|
+
|
|
38
|
+
### ⚙️ Typescript API Adjustments
|
|
39
|
+
|
|
40
|
+
* `GridFilterFieldSpec.renderer` is now typed as a pure value transform (`GridFilterRenderer`),
|
|
41
|
+
rather than a `ColumnRenderer`. This more accurately represents the existing run-time limitation
|
|
42
|
+
that a complex column renderer would throw.
|
|
43
|
+
|
|
44
|
+
### ✨ Styles
|
|
45
|
+
|
|
46
|
+
* Vertical (left/right) `TabContainer` switchers now render a modern rounded-pill treatment by
|
|
47
|
+
default, customizable via new `--xh-tab-switcher-vertical-*` CSS variables.
|
|
48
|
+
|
|
49
|
+
## 86.1.0 - 2026-06-22
|
|
50
|
+
|
|
51
|
+
### 🎁 New Features
|
|
52
|
+
|
|
53
|
+
* Added a mobile `SegmentedControl` input - the mobile counterpart to the desktop component, with a
|
|
54
|
+
matching `options`-driven API for selecting a single value from a set of mutually exclusive
|
|
55
|
+
choices. Built on Hoist's mobile `Button` (no Blueprint dependency).
|
|
56
|
+
* The shared `SegmentedControlOption` option types now export from `@xh/hoist/cmp/input` rather
|
|
57
|
+
than the desktop `SegmentedControl` module; update any direct type imports.
|
|
58
|
+
* Added a `leftIcon` prop to mobile `TextInput`.
|
|
59
|
+
* `FilterChooser` and grid column filters on a timestamp (`date`) field now filter by calendar day,
|
|
60
|
+
comparing against full-day bounds for range and equality operators rather than midnight - e.g.
|
|
61
|
+
`> 2023-05-31` excludes the 31st and `= 2023-05-31` matches any time that day. Filter specs now
|
|
62
|
+
default a `date` source field to `fieldType: 'localDate'`; set `fieldType: 'date'` for exact
|
|
63
|
+
timestamps. Applications using workarounds to provide similar behavior may be able to unwind that
|
|
64
|
+
behavior and rely on Hoist default behavior.
|
|
65
|
+
|
|
66
|
+
### 🐞 Bug Fixes
|
|
67
|
+
|
|
68
|
+
* Fixed the "Is blank" / "Is not blank" grid column filters for `tags`-typed fields - empty tag
|
|
69
|
+
arrays now correctly match "Is blank", and such filters are edited on the Custom tab rather than
|
|
70
|
+
producing a phantom blank entry in the Values list.
|
|
71
|
+
* Fixed an issue where `Grid` column headers could fall out of sync with body content during
|
|
72
|
+
horizontal scrolling when both `enableFullWidthScroll` and `useVirtualColumns` were enabled.
|
|
73
|
+
* Updated `DynamicTabSwitcher` to properly apply `testId` passed down by `TabContainer`.
|
|
74
|
+
* Ensure publication of `router5-plugin-browser` TS module augmentation.
|
|
75
|
+
* Set an explicit `%` unit on the `flex-basis: 0` of `TabContainer`'s flex shorthand to ensure
|
|
76
|
+
that the `0` is not interpreted as a `0px` basis and that the container sizes as expected.
|
|
77
|
+
* ⚠️Apps that upgrade to `hoist-dev-utils v13.x` and use `flex: 1 1 0` or `flex-basis: 0` should
|
|
78
|
+
verify that their flex layouts continue to work as expected and add an explicit unit if
|
|
79
|
+
not (e.g. `flex: 1 1 0%` or `flex-basis: 0%`).
|
|
80
|
+
|
|
81
|
+
### ⚙️ Technical
|
|
82
|
+
|
|
83
|
+
* `JsonBlobService` and `HoistAuthModel` now accept an optional `CallContextLike` argument on all of
|
|
84
|
+
their public methods, allowing callers to nest their fetches within an existing trace.
|
|
85
|
+
|
|
86
|
+
### ✨ Styles
|
|
87
|
+
|
|
88
|
+
* Mobile `Button` now defaults to a more touch-friendly height (40px, up from 28px) and rounder
|
|
89
|
+
corners for buttons in body content. Toolbar buttons derive their height from the toolbar size so
|
|
90
|
+
a single token drives both, landing slightly taller than before (34px, up from 28px).
|
|
91
|
+
* `SegmentedControl` adopts the same standard height for a consistent control row.
|
|
92
|
+
* ⚠️ Mobile buttons outside toolbars are now taller - verify body / form / panel layouts that
|
|
93
|
+
pair tightly with button dimensions.
|
|
94
|
+
* Mobile tab content (`TabContainer`) now takes the themed app background, so content reads
|
|
95
|
+
correctly in dark mode rather than showing through to Onsen's light default page background.
|
|
96
|
+
* Applied the active theme's `color-scheme` onto `html` and added a `theme-color` meta tag matching
|
|
97
|
+
the active theme's app-bar color, so browser chrome and overscroll / safe-area regions might
|
|
98
|
+
better match the theme.
|
|
99
|
+
|
|
100
|
+
### 📚 Libraries
|
|
101
|
+
|
|
102
|
+
* @azure/msal-browser `5.13 → 5.14`
|
|
3
103
|
|
|
4
104
|
## 86.0.1 - 2026-06-16
|
|
5
105
|
|
|
@@ -14,6 +114,10 @@
|
|
|
14
114
|
the page is hidden or unloaded, reacting to `XH.pageState` and issuing the flush via
|
|
15
115
|
`fetch({keepalive: true})` (replacing the less reliable `beforeunload` + normal-fetch approach).
|
|
16
116
|
|
|
117
|
+
### 📚 Libraries
|
|
118
|
+
|
|
119
|
+
* @azure/msal-browser `5.11 → 5.13`
|
|
120
|
+
* mobx `6.15 → 6.16`
|
|
17
121
|
|
|
18
122
|
## 86.0.0 - 2026-06-12
|
|
19
123
|
|
|
@@ -0,0 +1,31 @@
|
|
|
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 {hoistCmp} from '@xh/hoist/core';
|
|
8
|
+
import {jsonInput, JsonInputProps} from '@xh/hoist/desktop/cmp/input';
|
|
9
|
+
import {fmtJson, timestampReplacer} from '@xh/hoist/format';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Readonly `jsonInput` preconfigured for Admin Console stat / config displays - sized to fill its
|
|
13
|
+
* container, without line numbers or a fullscreen button, and rendering timestamp-suffixed fields
|
|
14
|
+
* as readable dates. Pass the data via `value` (any object) or `bind`, plus any additional
|
|
15
|
+
* `jsonInput` props (e.g. `enableSearch`) to extend or override the defaults.
|
|
16
|
+
*/
|
|
17
|
+
export const adminJsonDisplay = hoistCmp.factory<JsonInputProps>({
|
|
18
|
+
displayName: 'AdminJsonDisplay',
|
|
19
|
+
render(props) {
|
|
20
|
+
return jsonInput({
|
|
21
|
+
readonly: true,
|
|
22
|
+
flex: 1,
|
|
23
|
+
width: '100%',
|
|
24
|
+
height: '100%',
|
|
25
|
+
showFullscreenButton: false,
|
|
26
|
+
lineNumbers: false,
|
|
27
|
+
formatter: v => fmtJson(v, {replacer: timestampReplacer()}),
|
|
28
|
+
...props
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
});
|
package/admin/App.scss
CHANGED
|
@@ -6,19 +6,11 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
.xh-admin-app {
|
|
9
|
+
// The admin console uses left side-tab rails throughout - give them a consistent minimum width.
|
|
10
|
+
// All other vertical-switcher styling is inherited from the framework default.
|
|
9
11
|
.xh-tab-switcher--left,
|
|
10
12
|
.xh-tab-switcher--right {
|
|
11
|
-
min-width: 160px;
|
|
12
|
-
background-color: var(--xh-grid-bg-odd);
|
|
13
|
-
border-right: var(--xh-border-solid);
|
|
14
|
-
|
|
15
|
-
.bp6-tab {
|
|
16
|
-
border-radius: 0 !important;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
.xh-tab-switcher__tab {
|
|
20
|
-
line-height: 40px;
|
|
21
|
-
}
|
|
13
|
+
--xh-tab-switcher-vertical-min-width: 160px;
|
|
22
14
|
}
|
|
23
15
|
|
|
24
16
|
&__editor-dialog {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {GridConfig, GridModel} from '@xh/hoist/cmp/grid';
|
|
9
|
-
import {HoistModel, managed, TaskObserver, XH} from '@xh/hoist/core';
|
|
9
|
+
import {HoistModel, managed, PlainObject, TaskObserver, XH} from '@xh/hoist/core';
|
|
10
10
|
import {action, bindable, makeObservable, observable} from '@xh/hoist/mobx';
|
|
11
11
|
import {pluralize} from '@xh/hoist/utils/js';
|
|
12
12
|
import {isEmpty, zipWith} from 'lodash';
|
|
@@ -32,7 +32,7 @@ export class JsonSearchImplModel extends HoistModel {
|
|
|
32
32
|
@bindable path: string = '';
|
|
33
33
|
@bindable readerContentType: 'document' | 'matches' = 'matches';
|
|
34
34
|
@bindable pathFormat: 'XPath' | 'JSONPath' = 'XPath';
|
|
35
|
-
@bindable readerContent: string = '';
|
|
35
|
+
@bindable readerContent: string | PlainObject[] = '';
|
|
36
36
|
@bindable matchingNodeCount: number = 0;
|
|
37
37
|
|
|
38
38
|
get subjectName(): string {
|
|
@@ -137,7 +137,7 @@ export class JsonSearchImplModel extends HoistModel {
|
|
|
137
137
|
const {json} = this.selectedRecord.data;
|
|
138
138
|
|
|
139
139
|
if (this.readerContentType === 'document') {
|
|
140
|
-
this.readerContent =
|
|
140
|
+
this.readerContent = json;
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
143
143
|
|
|
@@ -162,7 +162,7 @@ export class JsonSearchImplModel extends HoistModel {
|
|
|
162
162
|
value
|
|
163
163
|
};
|
|
164
164
|
});
|
|
165
|
-
this.readerContent =
|
|
165
|
+
this.readerContent = nodes;
|
|
166
166
|
});
|
|
167
167
|
}
|
|
168
168
|
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
+
import {adminJsonDisplay} from '@xh/hoist/admin/AdminJsonDisplay';
|
|
7
8
|
import {AppModel} from '@xh/hoist/admin/AppModel';
|
|
8
9
|
import {ConnPoolMonitorModel} from '@xh/hoist/admin/tabs/cluster/instances/connpool/ConnPoolMonitorModel';
|
|
9
10
|
import {chart} from '@xh/hoist/cmp/chart';
|
|
@@ -12,7 +13,6 @@ import {filler, hframe, vframe} from '@xh/hoist/cmp/layout';
|
|
|
12
13
|
import {creates, hoistCmp} from '@xh/hoist/core';
|
|
13
14
|
import {button, exportButton} from '@xh/hoist/desktop/cmp/button';
|
|
14
15
|
import {errorMessage} from '@xh/hoist/cmp/error';
|
|
15
|
-
import {jsonInput} from '@xh/hoist/desktop/cmp/input';
|
|
16
16
|
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
17
17
|
import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
|
|
18
18
|
import {Icon} from '@xh/hoist/icon';
|
|
@@ -74,7 +74,7 @@ const bbar = hoistCmp.factory<ConnPoolMonitorModel>({
|
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
const poolConfigPanel = hoistCmp.factory<ConnPoolMonitorModel>({
|
|
77
|
-
render(
|
|
77
|
+
render() {
|
|
78
78
|
return panel({
|
|
79
79
|
title: 'Pool Configuration',
|
|
80
80
|
icon: Icon.info(),
|
|
@@ -84,15 +84,7 @@ const poolConfigPanel = hoistCmp.factory<ConnPoolMonitorModel>({
|
|
|
84
84
|
defaultSize: 450,
|
|
85
85
|
defaultCollapsed: true
|
|
86
86
|
},
|
|
87
|
-
item:
|
|
88
|
-
readonly: true,
|
|
89
|
-
height: '100%',
|
|
90
|
-
width: '100%',
|
|
91
|
-
enableSearch: true,
|
|
92
|
-
showFullscreenButton: false,
|
|
93
|
-
lineNumbers: false,
|
|
94
|
-
value: JSON.stringify(model.poolConfiguration, null, 2)
|
|
95
|
-
})
|
|
87
|
+
item: adminJsonDisplay({enableSearch: true, bind: 'poolConfiguration'})
|
|
96
88
|
});
|
|
97
89
|
}
|
|
98
90
|
});
|
|
@@ -4,15 +4,14 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
+
import {adminJsonDisplay} from '@xh/hoist/admin/AdminJsonDisplay';
|
|
7
8
|
import {DetailsModel} from '@xh/hoist/admin/tabs/cluster/instances/services/DetailsModel';
|
|
8
9
|
import {placeholder} from '@xh/hoist/cmp/layout';
|
|
9
10
|
import {creates, hoistCmp, XH} from '@xh/hoist/core';
|
|
10
11
|
import {errorMessage} from '@xh/hoist/cmp/error';
|
|
11
12
|
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
12
|
-
import {jsonInput} from '@xh/hoist/desktop/cmp/input';
|
|
13
13
|
import {Icon} from '@xh/hoist/icon';
|
|
14
14
|
import {isEmpty} from 'lodash';
|
|
15
|
-
import {fmtJson, timestampReplacer} from '@xh/hoist/format';
|
|
16
15
|
|
|
17
16
|
export const detailsPanel = hoistCmp.factory({
|
|
18
17
|
model: creates(DetailsModel),
|
|
@@ -50,16 +49,6 @@ const stats = hoistCmp.factory<DetailsModel>({
|
|
|
50
49
|
? []
|
|
51
50
|
: [Icon.questionCircle(), 'This service does not report any admin stats.'])
|
|
52
51
|
)
|
|
53
|
-
: panel(
|
|
54
|
-
jsonInput({
|
|
55
|
-
readonly: true,
|
|
56
|
-
width: '100%',
|
|
57
|
-
height: '100%',
|
|
58
|
-
enableSearch: true,
|
|
59
|
-
showFullscreenButton: false,
|
|
60
|
-
lineNumbers: false,
|
|
61
|
-
value: fmtJson(stats, {replacer: timestampReplacer()})
|
|
62
|
-
})
|
|
63
|
-
);
|
|
52
|
+
: panel(adminJsonDisplay({enableSearch: true, bind: 'stats'}));
|
|
64
53
|
}
|
|
65
54
|
});
|
|
@@ -4,21 +4,20 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
+
import {adminJsonDisplay} from '@xh/hoist/admin/AdminJsonDisplay';
|
|
7
8
|
import {grid} from '@xh/hoist/cmp/grid';
|
|
8
9
|
import {placeholder} from '@xh/hoist/cmp/layout';
|
|
9
10
|
import {creates, hoistCmp} from '@xh/hoist/core';
|
|
10
|
-
import {jsonInput} from '@xh/hoist/desktop/cmp/input';
|
|
11
11
|
import {panel} from '@xh/hoist/desktop/cmp/panel';
|
|
12
12
|
import {Icon} from '@xh/hoist/icon';
|
|
13
13
|
import {DetailModel} from './DetailModel';
|
|
14
14
|
import './ClusterObjects.scss';
|
|
15
|
-
import {fmtJson, timestampReplacer} from '@xh/hoist/format';
|
|
16
15
|
|
|
17
16
|
export const detailPanel = hoistCmp.factory({
|
|
18
17
|
model: creates(DetailModel),
|
|
19
18
|
|
|
20
19
|
render({model}) {
|
|
21
|
-
const {instanceName,
|
|
20
|
+
const {instanceName, objectName, objectType} = model;
|
|
22
21
|
if (!objectName) return placeholder(Icon.grip(), 'Select an object to view details...');
|
|
23
22
|
|
|
24
23
|
return panel({
|
|
@@ -36,15 +35,7 @@ export const detailPanel = hoistCmp.factory({
|
|
|
36
35
|
defaultSize: '80%',
|
|
37
36
|
collapsible: false
|
|
38
37
|
},
|
|
39
|
-
item:
|
|
40
|
-
readonly: true,
|
|
41
|
-
flex: 1,
|
|
42
|
-
width: '100%',
|
|
43
|
-
height: '100%',
|
|
44
|
-
showFullscreenButton: false,
|
|
45
|
-
lineNumbers: false,
|
|
46
|
-
value: fmtJson(selectedAdminStats, {replacer: timestampReplacer()})
|
|
47
|
-
})
|
|
38
|
+
item: adminJsonDisplay({bind: 'selectedAdminStats'})
|
|
48
39
|
})
|
|
49
40
|
]
|
|
50
41
|
});
|
|
@@ -12,12 +12,10 @@ import {fragment, p} from '@xh/hoist/cmp/layout';
|
|
|
12
12
|
import {CallContext, HoistModel, LoadSpec, managed, XH} from '@xh/hoist/core';
|
|
13
13
|
import {RecordActionSpec} from '@xh/hoist/data';
|
|
14
14
|
import {actionCol, calcActionColWidth} from '@xh/hoist/desktop/cmp/grid';
|
|
15
|
-
import {fmtDate} from '@xh/hoist/format';
|
|
16
15
|
import {Icon} from '@xh/hoist/icon';
|
|
17
16
|
import {action, bindable, makeObservable, observable, runInAction} from '@xh/hoist/mobx';
|
|
18
17
|
import {wait} from '@xh/hoist/promise';
|
|
19
18
|
import {compact, groupBy, mapValues} from 'lodash';
|
|
20
|
-
import moment from 'moment/moment';
|
|
21
19
|
import {RoleEditorModel} from './editor/RoleEditorModel';
|
|
22
20
|
import {HoistRole, RoleModuleConfig} from './Types';
|
|
23
21
|
|
|
@@ -397,23 +395,7 @@ export class RoleModel extends HoistModel {
|
|
|
397
395
|
config.directoryGroupsSupported && 'effectiveDirectoryGroupNames',
|
|
398
396
|
'effectiveRoleNames',
|
|
399
397
|
'lastUpdatedBy',
|
|
400
|
-
|
|
401
|
-
field: 'lastUpdated',
|
|
402
|
-
example: 'YYYY-MM-DD',
|
|
403
|
-
valueParser: (v, op) => {
|
|
404
|
-
let ret = moment(v, ['YYYY-MM-DD', 'YYYYMMDD'], true);
|
|
405
|
-
if (!ret.isValid()) return null;
|
|
406
|
-
|
|
407
|
-
// Note special handling for '>' & '<=' queries.
|
|
408
|
-
if (['>', '<='].includes(op)) {
|
|
409
|
-
ret = moment(ret).endOf('day');
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
return ret.toDate();
|
|
413
|
-
},
|
|
414
|
-
valueRenderer: v => fmtDate(v),
|
|
415
|
-
ops: ['>', '>=', '<', '<=']
|
|
416
|
-
}
|
|
398
|
+
'lastUpdated'
|
|
417
399
|
]),
|
|
418
400
|
persistWith: {...RoleModel.PERSIST_WITH, path: 'mainFilterChooser'}
|
|
419
401
|
});
|
|
@@ -10,6 +10,8 @@ import {mergeDeep} from '@xh/hoist/utils/js';
|
|
|
10
10
|
import {isOmitted} from '@xh/hoist/utils/impl';
|
|
11
11
|
import {createRouter, Router, State} from 'router5';
|
|
12
12
|
import browserPlugin from 'router5-plugin-browser';
|
|
13
|
+
// Required so downstream consumers pick up Router TS augmentation (buildUrl, etc.).
|
|
14
|
+
import 'router5-plugin-browser';
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Top-level model for managing application routing in Hoist.
|
|
@@ -31,7 +31,14 @@ export class ThemeModel extends HoistModel {
|
|
|
31
31
|
const classList = document.body.classList;
|
|
32
32
|
classList.toggle('xh-dark', value);
|
|
33
33
|
classList.toggle('bp6-dark', value);
|
|
34
|
+
|
|
35
|
+
// Set color-scheme on the document root (<html>) so browser chrome and overscroll / safe-area
|
|
36
|
+
// regions match the theme - the theme class only reaches <body>, leaving the root light.
|
|
37
|
+
// See https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme
|
|
38
|
+
document.documentElement.style.colorScheme = value ? 'dark' : 'light';
|
|
39
|
+
|
|
34
40
|
this.darkTheme = value;
|
|
41
|
+
this.syncThemeColorMeta();
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
@action
|
|
@@ -64,4 +71,38 @@ export class ThemeModel extends HoistModel {
|
|
|
64
71
|
}
|
|
65
72
|
});
|
|
66
73
|
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Sync the `<meta name="theme-color">` tag to the active theme's app-bar color, creating it if
|
|
77
|
+
* needed. This tints browser chrome to match the app - e.g. Android Chrome's status bar and task
|
|
78
|
+
* switcher, and desktop installed PWAs. Hoist's theme is independent of the OS color scheme, so
|
|
79
|
+
* the tag's content must be updated on each theme change rather than relying on the static
|
|
80
|
+
* `media="(prefers-color-scheme)"` form. Note Safari 26+ ignores `theme-color`, instead deriving
|
|
81
|
+
* its chrome color from the page background (see the companion `color-scheme` handling above).
|
|
82
|
+
*/
|
|
83
|
+
private syncThemeColorMeta() {
|
|
84
|
+
// Resolve --xh-appbar-bg (a chain of var() fallbacks) to a concrete color via a throwaway
|
|
85
|
+
// probe - reading computed `color` fully substitutes the var chain.
|
|
86
|
+
const probe = document.createElement('div');
|
|
87
|
+
probe.style.cssText = 'display: none; color: var(--xh-appbar-bg)';
|
|
88
|
+
document.body.appendChild(probe);
|
|
89
|
+
const color = window.getComputedStyle(probe).color;
|
|
90
|
+
probe.remove();
|
|
91
|
+
|
|
92
|
+
// Bail if the color failed to resolve (e.g. styles not yet applied) rather than write a
|
|
93
|
+
// bad value - the next theme change will sync it.
|
|
94
|
+
if (!color) return;
|
|
95
|
+
|
|
96
|
+
// Maintain our own tag, marked so we can find it again on subsequent theme changes. If an
|
|
97
|
+
// app has hand-authored any theme-color tag, defer to it entirely.
|
|
98
|
+
let meta = document.querySelector('meta[name=theme-color][data-xh-managed]');
|
|
99
|
+
if (!meta) {
|
|
100
|
+
if (document.querySelector('meta[name=theme-color]')) return;
|
|
101
|
+
meta = document.createElement('meta');
|
|
102
|
+
meta.setAttribute('name', 'theme-color');
|
|
103
|
+
meta.setAttribute('data-xh-managed', '');
|
|
104
|
+
document.head.appendChild(meta);
|
|
105
|
+
}
|
|
106
|
+
meta.setAttribute('content', color);
|
|
107
|
+
}
|
|
67
108
|
}
|
|
@@ -34,8 +34,12 @@ export class LoginPanelModel extends HoistModel {
|
|
|
34
34
|
// Debounce to defend against double-click fast enough to get through masking + button disable.
|
|
35
35
|
@debounced(300)
|
|
36
36
|
async submitAsync() {
|
|
37
|
-
const {username, password, loginTask, isValid} = this;
|
|
38
|
-
if (
|
|
37
|
+
const {username, password, loginTask, isValid, loginInProgress} = this;
|
|
38
|
+
if (loginInProgress) return;
|
|
39
|
+
if (!isValid) {
|
|
40
|
+
this.warning = 'Please enter a username and password.';
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
39
43
|
|
|
40
44
|
try {
|
|
41
45
|
this.loginInProgress = true;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Readonly `jsonInput` preconfigured for Admin Console stat / config displays - sized to fill its
|
|
3
|
+
* container, without line numbers or a fullscreen button, and rendering timestamp-suffixed fields
|
|
4
|
+
* as readable dates. Pass the data via `value` (any object) or `bind`, plus any additional
|
|
5
|
+
* `jsonInput` props (e.g. `enableSearch`) to extend or override the defaults.
|
|
6
|
+
*/
|
|
7
|
+
export declare const adminJsonDisplay: import("@xh/hoist/core").ElementFactory<import("@xh/hoist/desktop/cmp/input").CodeInputProps>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GridConfig, GridModel } from '@xh/hoist/cmp/grid';
|
|
2
|
-
import { HoistModel, TaskObserver } from '@xh/hoist/core';
|
|
2
|
+
import { HoistModel, PlainObject, TaskObserver } from '@xh/hoist/core';
|
|
3
3
|
/**
|
|
4
4
|
* @internal
|
|
5
5
|
*/
|
|
@@ -16,7 +16,7 @@ export declare class JsonSearchImplModel extends HoistModel {
|
|
|
16
16
|
path: string;
|
|
17
17
|
readerContentType: 'document' | 'matches';
|
|
18
18
|
pathFormat: 'XPath' | 'JSONPath';
|
|
19
|
-
readerContent: string;
|
|
19
|
+
readerContent: string | PlainObject[];
|
|
20
20
|
matchingNodeCount: number;
|
|
21
21
|
get subjectName(): string;
|
|
22
22
|
get docSearchUrl(): string;
|
|
@@ -10,4 +10,13 @@ export declare class ThemeModel extends HoistModel {
|
|
|
10
10
|
setDarkTheme(value: boolean): void;
|
|
11
11
|
setTheme(value: Theme, persist?: boolean): void;
|
|
12
12
|
init(): void;
|
|
13
|
+
/**
|
|
14
|
+
* Sync the `<meta name="theme-color">` tag to the active theme's app-bar color, creating it if
|
|
15
|
+
* needed. This tints browser chrome to match the app - e.g. Android Chrome's status bar and task
|
|
16
|
+
* switcher, and desktop installed PWAs. Hoist's theme is independent of the OS color scheme, so
|
|
17
|
+
* the tag's content must be updated on each theme change rather than relying on the static
|
|
18
|
+
* `media="(prefers-color-scheme)"` form. Note Safari 26+ ignores `theme-color`, instead deriving
|
|
19
|
+
* its chrome color from the page background (see the companion `color-scheme` handling above).
|
|
20
|
+
*/
|
|
21
|
+
private syncThemeColorMeta;
|
|
13
22
|
}
|
|
@@ -3,6 +3,7 @@ import { ColumnState } from '@xh/hoist/cmp/grid';
|
|
|
3
3
|
import { HoistModel, HoistProps, LayoutProps, PlainObject, ReactionSpec, TestSupportProps } from '@xh/hoist/core';
|
|
4
4
|
import { RecordSet } from '@xh/hoist/data/impl/RecordSet';
|
|
5
5
|
import type { ColDef, ColGroupDef, GetContextMenuItemsParams, GridOptions, GridReadyEvent, ProcessCellForExportParams } from '@xh/hoist/kit/ag-grid';
|
|
6
|
+
import { type MouseEvent } from 'react';
|
|
6
7
|
import './Grid.scss';
|
|
7
8
|
import { GridModel } from './GridModel';
|
|
8
9
|
export interface GridProps<M extends GridModel = GridModel> extends HoistProps<M>, LayoutProps, TestSupportProps {
|
|
@@ -41,6 +42,7 @@ export interface GridProps<M extends GridModel = GridModel> extends HoistProps<M
|
|
|
41
42
|
export declare const Grid: import("react").FC<GridProps<GridModel>>, grid: import("@xh/hoist/core").ElementFactory<GridProps<GridModel>>;
|
|
42
43
|
export declare class GridLocalModel extends HoistModel {
|
|
43
44
|
xhImpl: boolean;
|
|
45
|
+
private static EMPTY_SPACE_SELECTOR;
|
|
44
46
|
private model;
|
|
45
47
|
agOptions: GridOptions;
|
|
46
48
|
viewRef: import("react").RefObject<HTMLElement> & import("react").RefCallback<HTMLElement>;
|
|
@@ -55,7 +57,7 @@ export declare class GridLocalModel extends HoistModel {
|
|
|
55
57
|
getColumnDefs(): Array<ColDef | ColGroupDef>;
|
|
56
58
|
getContextMenuItems: (params: GetContextMenuItemsParams) => (string | import("ag-grid-community").MenuItemDef<any, any>)[];
|
|
57
59
|
dataReaction(): {
|
|
58
|
-
track: () => (boolean | import("../../data").StoreRecord[] | RecordSet
|
|
60
|
+
track: () => (boolean | import("@xh/hoist/core").VSide | import("../../data").StoreRecord[] | RecordSet)[];
|
|
59
61
|
run: () => void;
|
|
60
62
|
};
|
|
61
63
|
selectionReaction(): {
|
|
@@ -63,7 +65,7 @@ export declare class GridLocalModel extends HoistModel {
|
|
|
63
65
|
run: () => void;
|
|
64
66
|
};
|
|
65
67
|
sortReaction(): {
|
|
66
|
-
track: () => (import("@xh/hoist/cmp/grid").GridSorter[]
|
|
68
|
+
track: () => (GridApi<any> | import("@xh/hoist/cmp/grid").GridSorter[])[];
|
|
67
69
|
run: ([agApi, sortBy]: [any, any]) => void;
|
|
68
70
|
};
|
|
69
71
|
groupReaction(): {
|
|
@@ -83,7 +85,7 @@ export declare class GridLocalModel extends HoistModel {
|
|
|
83
85
|
get useScrollOptimization(): boolean;
|
|
84
86
|
applyScrollOptimization(): void;
|
|
85
87
|
columnsReaction(): {
|
|
86
|
-
track: () => (import("@xh/hoist/cmp/grid").ColumnOrGroup[]
|
|
88
|
+
track: () => (GridApi<any> | import("@xh/hoist/cmp/grid").ColumnOrGroup[])[];
|
|
87
89
|
run: ([api]: [any]) => void;
|
|
88
90
|
};
|
|
89
91
|
columnStateReaction(): ReactionSpec<[GridApi, ColumnState[]]>;
|
|
@@ -129,6 +131,7 @@ export declare class GridLocalModel extends HoistModel {
|
|
|
129
131
|
writeFilterState(filterState: any): void;
|
|
130
132
|
processCellForClipboard: ({ value, node, column }: ProcessCellForExportParams) => any;
|
|
131
133
|
navigateToNextCell: (agParams: any) => import("ag-grid-community").CellPosition;
|
|
134
|
+
onViewMouseDown: (evt: MouseEvent) => void;
|
|
132
135
|
onCellMouseDown: (evt: any) => void;
|
|
133
136
|
onKeyDown: (evt: any) => void;
|
|
134
137
|
onRowClicked: (evt: any) => void;
|
|
@@ -155,6 +155,10 @@ export interface GridConfig {
|
|
|
155
155
|
* expand/collapse options in the default context menu will be enhanced to allow users to
|
|
156
156
|
* expand/collapse to a specific level. See {@link GroupingChooserModel.valueDisplayNames}
|
|
157
157
|
* for a convenient getter that will satisfy this API when a GroupingChooser is in play.
|
|
158
|
+
*
|
|
159
|
+
* Labels are matched to levels top-down and need not cover the full depth of the grid - provide
|
|
160
|
+
* a partial array to label only the top levels (e.g. when deeper levels should not be
|
|
161
|
+
* expand-to targets). Deeper, unlabelled levels are omitted from the menu.
|
|
158
162
|
*/
|
|
159
163
|
levelLabels?: Thunkable<string[]>;
|
|
160
164
|
/**
|
|
@@ -501,8 +505,15 @@ export declare class GridModel extends HoistModel {
|
|
|
501
505
|
expandToLevel(level: number): void;
|
|
502
506
|
/**
|
|
503
507
|
* Get the resolved level labels for the current state of the grid.
|
|
508
|
+
* An over-long array is truncated to the current `maxDepth`.
|
|
504
509
|
*/
|
|
505
510
|
get resolvedLevelLabels(): string[];
|
|
511
|
+
/**
|
|
512
|
+
* True if the given `resolvedLevelLabels` index is the grid's current expand level - used to
|
|
513
|
+
* mark the active item in the "Expand to..." menu. The deepest labelled level counts as current
|
|
514
|
+
* whenever the grid is expanded to or beyond it.
|
|
515
|
+
*/
|
|
516
|
+
isCurrentExpandLevel(idx: number): boolean;
|
|
506
517
|
/**
|
|
507
518
|
* Sort this grid.
|
|
508
519
|
* This method is a no-op if provided any sorters without a corresponding column.
|
|
@@ -1,17 +1,34 @@
|
|
|
1
|
-
import { ColumnRenderer } from '@xh/hoist/cmp/grid';
|
|
2
1
|
import { HoistInputProps } from '@xh/hoist/cmp/input';
|
|
3
2
|
import { PlainObject } from '@xh/hoist/core';
|
|
4
3
|
import { FieldFilterOperator } from '@xh/hoist/data';
|
|
5
4
|
import { BaseFilterFieldSpec, BaseFilterFieldSpecConfig } from '@xh/hoist/data/filter/BaseFilterFieldSpec';
|
|
5
|
+
import { ReactNode } from 'react';
|
|
6
6
|
import { GridFilterModel } from './GridFilterModel';
|
|
7
|
+
/**
|
|
8
|
+
* Produces the display content for an entry in the Values tab of a column filter. Must be a pure
|
|
9
|
+
* transform of the value - the values list carries no source record, and there is no column or
|
|
10
|
+
* grid context to reach for (unlike a Column's `renderer`, which runs against the source grid).
|
|
11
|
+
*/
|
|
12
|
+
export type GridFilterRenderer = (value: any) => ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* Produces the value to sort by for an entry in the Values tab of a column filter. Must be a pure
|
|
15
|
+
* transform of the value - the values list carries no source record, and there is no column or
|
|
16
|
+
* grid context to reach for (unlike a Column's `sortValue`, which runs against the source grid).
|
|
17
|
+
*/
|
|
18
|
+
export type GridFilterSortValueFn = (value: any) => any;
|
|
7
19
|
export interface GridFilterFieldSpecConfig extends BaseFilterFieldSpecConfig {
|
|
8
20
|
/** GridFilterModel instance owning this fieldSpec. */
|
|
9
21
|
filterModel?: GridFilterModel;
|
|
10
22
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
23
|
+
* Pure function producing the display content for each entry in the values filter display. If
|
|
24
|
+
* not provided, the Column's renderer is used where it can be applied to a bare value.
|
|
25
|
+
*/
|
|
26
|
+
renderer?: GridFilterRenderer;
|
|
27
|
+
/**
|
|
28
|
+
* Pure function producing the value to sort each entry of the values filter display by. If not
|
|
29
|
+
* provided, the Column's sortValue is used where it can be applied to a bare value.
|
|
13
30
|
*/
|
|
14
|
-
|
|
31
|
+
sortValue?: GridFilterSortValueFn;
|
|
15
32
|
/**
|
|
16
33
|
* Props to pass through to the HoistInput components used on the custom filter tab.
|
|
17
34
|
* Note that the HoistInput component used is decided by fieldType.
|
|
@@ -26,12 +43,13 @@ export interface GridFilterFieldSpecConfig extends BaseFilterFieldSpecConfig {
|
|
|
26
43
|
*/
|
|
27
44
|
export declare class GridFilterFieldSpec extends BaseFilterFieldSpec {
|
|
28
45
|
filterModel: GridFilterModel;
|
|
29
|
-
renderer:
|
|
46
|
+
renderer: GridFilterRenderer;
|
|
47
|
+
sortValue: GridFilterSortValueFn;
|
|
30
48
|
inputProps: PlainObject;
|
|
31
49
|
defaultOp: FieldFilterOperator;
|
|
32
50
|
/** Total number of unique values for this field in the source, regardless of other filters. */
|
|
33
51
|
allValuesCount: number;
|
|
34
|
-
constructor({ filterModel, renderer, inputProps, defaultOp, ...rest }: GridFilterFieldSpecConfig);
|
|
52
|
+
constructor({ filterModel, renderer, sortValue, inputProps, defaultOp, ...rest }: GridFilterFieldSpecConfig);
|
|
35
53
|
getUniqueValue(value: unknown): unknown;
|
|
36
54
|
loadValuesFromSource(): void;
|
|
37
55
|
private cleanFilter;
|