@xh/hoist 86.1.0 → 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 +46 -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/build/types/admin/AdminJsonDisplay.d.ts +7 -0
- package/build/types/admin/jsonsearch/impl/JsonSearchImplModel.d.ts +2 -2
- 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/desktop/cmp/button/grid/ExpandToLevelButton.d.ts +2 -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/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 +4 -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/cmp/grid/Grid.ts +17 -0
- package/cmp/grid/GridModel.ts +17 -5
- package/cmp/grid/filter/GridFilterFieldSpec.ts +28 -5
- package/cmp/grid/impl/MenuSupport.ts +2 -4
- package/desktop/cmp/button/grid/ExpandToLevelButton.ts +4 -5
- 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/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/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 +72 -25
- package/format/FormatNumber.ts +69 -32
- package/icon/Icon.scss +13 -0
- package/icon/Icon.ts +4 -3
- package/icon/impl/IconHtml.ts +1 -1
- package/mcp/data/ts-registry.ts +1 -1
- package/mobile/cmp/button/grid/ExpandToLevelButton.ts +4 -5
- package/mobile/cmp/input/CheckboxButton.ts +18 -1
- package/mobile/cmp/input/NumberInput.ts +1 -1
- package/package.json +6 -6
- package/styles/vars.scss +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
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
|
+
|
|
3
49
|
## 86.1.0 - 2026-06-22
|
|
4
50
|
|
|
5
51
|
### 🎁 New Features
|
|
@@ -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
|
});
|
|
@@ -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;
|
|
@@ -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;
|
|
@@ -13,6 +13,7 @@ export interface ExpandToLevelButtonProps extends Omit<ButtonProps, 'title'> {
|
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* A menu button to expand a multi-level grouped or tree grid out to a desired level.
|
|
16
|
-
* Requires {@link GridConfig.levelLabels} to be configured on the bound GridModel
|
|
16
|
+
* Requires {@link GridConfig.levelLabels} to be configured on the bound GridModel - the menu offers
|
|
17
|
+
* one entry per labelled level, so a partial array will limit the available expand-to targets.
|
|
17
18
|
*/
|
|
18
19
|
export declare const ExpandToLevelButton: import("react").FC<ExpandToLevelButtonProps>, expandToLevelButton: import("@xh/hoist/core").ElementFactory<ExpandToLevelButtonProps>;
|
|
@@ -4,6 +4,13 @@ import './CodeInput.scss';
|
|
|
4
4
|
export interface CodeInputProps extends HoistProps, HoistInputProps, LayoutProps {
|
|
5
5
|
/** True to focus the control on render. */
|
|
6
6
|
autoFocus?: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* True to automatically format content for display using the configured `formatter`.
|
|
9
|
+
* Defaults to true for `readonly` inputs - set false to opt out. May also be enabled on
|
|
10
|
+
* editable inputs, in which case content is formatted on blur (never mid-edit, so user
|
|
11
|
+
* edits and cursor position are preserved while typing).
|
|
12
|
+
*/
|
|
13
|
+
autoFormat?: boolean;
|
|
7
14
|
/** False to not commit on every change/keystroke, default true. */
|
|
8
15
|
commitOnChange?: boolean;
|
|
9
16
|
/**
|
|
@@ -36,7 +36,7 @@ export interface NumberInputProps extends HoistProps, LayoutProps, StyleProps, H
|
|
|
36
36
|
onKeyDown?: KeyboardEventHandler<HTMLInputElement>;
|
|
37
37
|
/** Text to display when control is empty. */
|
|
38
38
|
placeholder?: string;
|
|
39
|
-
/** Max decimal precision of the value, defaults to 4. */
|
|
39
|
+
/** Max decimal precision of the value, defaults to 4. Set to null for full, unrestricted precision. */
|
|
40
40
|
precision?: NumericPrecision;
|
|
41
41
|
/** Element to display inline on the right side of the input. */
|
|
42
42
|
rightElement?: ReactNode;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Compute an explicit pixel width for a windowed (virtualized) `Select` menu.
|
|
4
|
+
*
|
|
5
|
+
* Windowed menus can't auto-size via CSS - react-window absolutely-positions rows at width:100%,
|
|
6
|
+
* so option content never widens the menu and it collapses to the control width. This restores
|
|
7
|
+
* content-based sizing using Canvas + off-screen DOM, the same render-and-measure approach as the
|
|
8
|
+
* grid's `ColumnWidthCalculator`: render each option's actual menu markup (via the configured
|
|
9
|
+
* `optionRenderer`, if any), canvas-estimate its width to pick the widest candidates, then measure
|
|
10
|
+
* only those candidates' displayed width in a hidden probe - so custom `optionRenderer`s (icons,
|
|
11
|
+
* multi-element content, etc.) size correctly. Unlike the grid (which may autosize many columns
|
|
12
|
+
* over thousands of records), this renders one menu's options once per options change.
|
|
13
|
+
*
|
|
14
|
+
* @param options - normalized (possibly grouped) Select options.
|
|
15
|
+
* @param renderOption - renders an option to its menu `ReactNode` (e.g. model.formatOptionLabel).
|
|
16
|
+
* @param portal - shared menu portal div, used to host probes so menu CSS (font, padding) applies.
|
|
17
|
+
* @returns explicit menu width in px, or null to fall back to the control width.
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export declare function calcWindowedMenuWidth(options: any[], renderOption: (opt: any) => ReactNode, portal: HTMLElement): number;
|
|
@@ -20,7 +20,7 @@ export declare class RestFormModel extends HoistModel {
|
|
|
20
20
|
del?: import("react").ReactNode | ((recs: import("@xh/hoist/data").StoreRecord[]) => import("react").ReactNode);
|
|
21
21
|
edit?: import("react").ReactNode | ((recs: import("@xh/hoist/data").StoreRecord[]) => import("react").ReactNode);
|
|
22
22
|
};
|
|
23
|
-
get actions(): (import("@xh/hoist/data").
|
|
23
|
+
get actions(): (import("@xh/hoist/data").RecordAction | import("@xh/hoist/data").RecordActionSpec)[];
|
|
24
24
|
get editors(): RestGridEditor[];
|
|
25
25
|
get gridModel(): import("../../../../cmp/grid").GridModel;
|
|
26
26
|
get store(): import("@xh/hoist/desktop/cmp/rest").RestStore;
|
|
@@ -33,8 +33,9 @@ export interface NumberFormatOptions extends Omit<FormatOptions<number>, 'toolti
|
|
|
33
33
|
*/
|
|
34
34
|
omitFourDigitComma?: boolean;
|
|
35
35
|
/**
|
|
36
|
-
* Desired number of decimal places,
|
|
37
|
-
* automatically based on the scale of the value
|
|
36
|
+
* Desired number of decimal places, 'auto' (default) to adjust the displayed precision
|
|
37
|
+
* automatically based on the scale of the value, or null for full, unrestricted precision
|
|
38
|
+
* (capped at the max supported precision, with trailing zeros trimmed).
|
|
38
39
|
*/
|
|
39
40
|
precision?: Precision;
|
|
40
41
|
/** Prefix to prepend to value (between the number and its sign). */
|
|
@@ -65,13 +66,20 @@ export interface NumberFormatOptions extends Omit<FormatOptions<number>, 'toolti
|
|
|
65
66
|
*
|
|
66
67
|
* e.g. `{precision:4, zeroPad:2}` will format `1.2` → "1.20" and `1.234` → "1.234"
|
|
67
68
|
*
|
|
68
|
-
* Default is true if a fixed precision is set, false if precision is 'auto'.
|
|
69
|
+
* Default is true if a fixed precision is set, false if precision is 'auto' or null (full).
|
|
69
70
|
*/
|
|
70
71
|
zeroPad?: ZeroPad;
|
|
71
72
|
}
|
|
72
73
|
export interface QuantityFormatOptions extends NumberFormatOptions {
|
|
74
|
+
/** True to compact values \>= 1 million into units of millions (m). Default true. */
|
|
73
75
|
useMillions?: boolean;
|
|
76
|
+
/** True to compact values \>= 1 billion into units of billions (b). Default true. */
|
|
74
77
|
useBillions?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* True to compact to m/b units only when no precision is lost, else render the full value.
|
|
80
|
+
* Default false.
|
|
81
|
+
*/
|
|
82
|
+
lossless?: boolean;
|
|
75
83
|
}
|
|
76
84
|
/** Config for pos/neg/neutral color classes. */
|
|
77
85
|
export interface ColorSpec {
|
|
@@ -3,6 +3,7 @@ import { FontAwesomeIconProps } from '@fortawesome/react-fontawesome';
|
|
|
3
3
|
import { HoistProps, Intent, Thunkable } from '@xh/hoist/core';
|
|
4
4
|
import { ReactElement } from 'react';
|
|
5
5
|
import { SetRequired } from 'type-fest';
|
|
6
|
+
import './Icon.scss';
|
|
6
7
|
export interface IconProps extends HoistProps, Partial<Omit<FontAwesomeIconProps, 'ref'>> {
|
|
7
8
|
/** Name of the icon in FontAwesome. */
|
|
8
9
|
iconName?: IconName;
|
|
@@ -333,9 +334,9 @@ export declare const Icon: {
|
|
|
333
334
|
filename: string;
|
|
334
335
|
}): any;
|
|
335
336
|
/**
|
|
336
|
-
* Returns an empty div
|
|
337
|
-
* where an icon might otherwise go - e.g. to align a series of menu items,
|
|
338
|
-
* not have an icon but others do.
|
|
337
|
+
* Returns an empty div sized to occupy the width of a standard icon. Can be used to take up
|
|
338
|
+
* room in a layout where an icon might otherwise go - e.g. to align a series of menu items,
|
|
339
|
+
* where some items do not have an icon but others do.
|
|
339
340
|
*/
|
|
340
341
|
placeholder(opts?: IconProps): any;
|
|
341
342
|
};
|
|
@@ -10,6 +10,7 @@ export interface ExpandToLevelButtonProps extends MenuButtonProps {
|
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* A menu button to expand a multi-level grouped or tree grid out to a desired level.
|
|
13
|
-
* Requires {@link GridConfig.levelLabels} to be configured on the bound GridModel
|
|
13
|
+
* Requires {@link GridConfig.levelLabels} to be configured on the bound GridModel - the menu offers
|
|
14
|
+
* one entry per labelled level, so a partial array will limit the available expand-to targets.
|
|
14
15
|
*/
|
|
15
16
|
export declare const ExpandToLevelButton: import("react").FC<ExpandToLevelButtonProps>, expandToLevelButton: import("@xh/hoist/core").ElementFactory<ExpandToLevelButtonProps>;
|
|
@@ -26,7 +26,7 @@ export interface NumberInputProps extends HoistProps, HoistInputProps, StyleProp
|
|
|
26
26
|
modifier?: string;
|
|
27
27
|
/** Text to display when control is empty. */
|
|
28
28
|
placeholder?: string;
|
|
29
|
-
/** Max decimal precision of the value, defaults to 4. */
|
|
29
|
+
/** Max decimal precision of the value, defaults to 4. Set to null for full, unrestricted precision. */
|
|
30
30
|
precision?: NumericPrecision;
|
|
31
31
|
/**
|
|
32
32
|
* Scale factor to apply when converting between the internal and external value. Useful for
|
package/cmp/grid/Grid.ts
CHANGED
|
@@ -48,6 +48,7 @@ import {consumeEvent, isDisplayed, logWithDebug} from '@xh/hoist/utils/js';
|
|
|
48
48
|
import {composeRefs, createObservableRef, getLayoutProps} from '@xh/hoist/utils/react';
|
|
49
49
|
import classNames from 'classnames';
|
|
50
50
|
import {compact, debounce, isBoolean, isEmpty, isEqual, isNil, max, maxBy, merge} from 'lodash';
|
|
51
|
+
import {type MouseEvent} from 'react';
|
|
51
52
|
import './Grid.scss';
|
|
52
53
|
import {GridModel} from './GridModel';
|
|
53
54
|
import {columnGroupHeader} from './impl/ColumnGroupHeader';
|
|
@@ -135,6 +136,7 @@ export const [Grid, grid] = hoistCmp.withFactory<GridProps>({
|
|
|
135
136
|
],
|
|
136
137
|
testId,
|
|
137
138
|
onKeyDown: impl.onKeyDown,
|
|
139
|
+
onMouseDown: impl.onViewMouseDown,
|
|
138
140
|
ref: composeRefs(impl.viewRef, model.viewRef, ref)
|
|
139
141
|
}),
|
|
140
142
|
colChooserModel ? platformColChooser({model: colChooserModel}) : null,
|
|
@@ -151,6 +153,10 @@ export const [Grid, grid] = hoistCmp.withFactory<GridProps>({
|
|
|
151
153
|
export class GridLocalModel extends HoistModel {
|
|
152
154
|
override xhImpl = true;
|
|
153
155
|
|
|
156
|
+
// Structural "empty" grid space.
|
|
157
|
+
private static EMPTY_SPACE_SELECTOR =
|
|
158
|
+
'.ag-body-viewport, .ag-center-cols-viewport, .ag-center-cols-container, .ag-row';
|
|
159
|
+
|
|
154
160
|
@lookup(GridModel)
|
|
155
161
|
private model: GridModel;
|
|
156
162
|
agOptions: GridOptions;
|
|
@@ -844,6 +850,17 @@ export class GridLocalModel extends HoistModel {
|
|
|
844
850
|
return this.rowKeyNavSupport?.navigateToNextCell(agParams);
|
|
845
851
|
};
|
|
846
852
|
|
|
853
|
+
// `stopEditingWhenCellsLoseFocus` doesn't fire on clicks in empty grid space (focus stays in
|
|
854
|
+
// the grid), so commit the active edit on those clicks ourselves. Require exact match on empty
|
|
855
|
+
// space to avoid interfering with cell editors.
|
|
856
|
+
onViewMouseDown = (evt: MouseEvent) => {
|
|
857
|
+
const {model} = this,
|
|
858
|
+
target = evt.target as HTMLElement;
|
|
859
|
+
if (model.isEditing && target.matches(GridLocalModel.EMPTY_SPACE_SELECTOR)) {
|
|
860
|
+
model.agApi?.stopEditing();
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
|
|
847
864
|
onCellMouseDown = evt => {
|
|
848
865
|
const {model} = this;
|
|
849
866
|
if (model.highlightRowOnClick) {
|