@xh/hoist 89.0.0 → 89.0.1
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 +25 -5
- package/appcontainer/MessageModel.ts +11 -10
- package/appcontainer/README.md +6 -2
- package/build/types/appcontainer/MessageModel.d.ts +4 -1
- package/build/types/core/XH.d.ts +7 -6
- package/cmp/grid/Grid.ts +1 -1
- package/cmp/grid/GridModel.ts +8 -3
- package/core/XH.ts +7 -6
- package/data/README.md +3 -2
- package/desktop/cmp/dash/canvas/impl/DashCanvasView.ts +49 -52
- package/docs/upgrade-notes/v89-upgrade-notes.md +94 -1
- package/package.json +1 -1
- package/test-support/setup.ts +8 -0
package/CHANGELOG.md
CHANGED
|
@@ -12,9 +12,26 @@
|
|
|
12
12
|
3. Plain ASCII punctuation only. Use " - " for in-sentence breaks, never an em dash.
|
|
13
13
|
-->
|
|
14
14
|
|
|
15
|
+
## 89.0.1 - 2026-10-14
|
|
16
|
+
|
|
17
|
+
### 🐞 Bug Fixes
|
|
18
|
+
|
|
19
|
+
* `GridModel.setColumnState()` now drops repeated `colId` entries from saved column state, keeping
|
|
20
|
+
the first. Saved state with a repeated column made the v87 column chooser silently error and break.
|
|
21
|
+
* Fixed desktop `DashCanvas` failing to render in v89.0.0.
|
|
22
|
+
* Fixed `XH.message()`, `XH.confirm()`, and `XH.prompt()` resolving to `null` when a route change
|
|
23
|
+
or other forced close shut the dialog. Callers that tested `!== false` ran the confirmed action.
|
|
24
|
+
A dialog closed without a choice now resolves to `false`, the same as a cancel. A dismiss with
|
|
25
|
+
`cancelOnDismiss: false` also resolves to `false` - the flag now only skips `onCancel`.
|
|
26
|
+
|
|
27
|
+
### ⚙️ Technical
|
|
28
|
+
|
|
29
|
+
* The unit test kit now stubs `ResizeObserver`, which jsdom lacks, so app specs can render
|
|
30
|
+
components that track their own size.
|
|
31
|
+
|
|
15
32
|
## 89.0.0 - 2026-10-13
|
|
16
33
|
|
|
17
|
-
### 💥 Breaking Changes (upgrade difficulty: 🟢 LOW - filter specs, app option presets, icon listing, grid defaults, badge spacing, column library config)
|
|
34
|
+
### 💥 Breaking Changes (upgrade difficulty: 🟢 LOW - filter specs, app option presets, icon listing, grid defaults, grid options, badge spacing, column library config)
|
|
18
35
|
|
|
19
36
|
See [`docs/upgrade-notes/v89-upgrade-notes.md`](docs/upgrade-notes/v89-upgrade-notes.md) for
|
|
20
37
|
detailed, step-by-step upgrade instructions with before/after code examples.
|
|
@@ -35,6 +52,9 @@ detailed, step-by-step upgrade instructions with before/after code examples.
|
|
|
35
52
|
`span('Users ', badge(n))`, or with a `gap` on an enclosing `hbox`.
|
|
36
53
|
* Removed `ColLibraryConfig.autoExpandOnFilter`. The Column Library now expands every group with a
|
|
37
54
|
match while a search is active. Remove the key from any `columnLibrary` config.
|
|
55
|
+
* `Grid` now ignores `agOptions.groupDefaultExpanded`. Set `GridModel.expandLevel` instead.
|
|
56
|
+
* Removed `GridExperimentalFlags.disableScrollOptimization`. The post-load row height pass it
|
|
57
|
+
disabled now runs only for grids whose rows vary in height. Remove the key from grid configs.
|
|
38
58
|
|
|
39
59
|
### 🎁 New Features
|
|
40
60
|
|
|
@@ -123,7 +143,8 @@ detailed, step-by-step upgrade instructions with before/after code examples.
|
|
|
123
143
|
* Fixed `@sharePendingPromise` running a method twice when it threw synchronously.
|
|
124
144
|
* Fixed a cancelled `Timer` keeping its heartbeat alive, and a `Timer` with `delay: true` and a
|
|
125
145
|
disabled interval scheduling a negative timeout.
|
|
126
|
-
*
|
|
146
|
+
* `FetchService` now sends every `PUT` without a `Content-Type` as form-encoded, as for `POST`.
|
|
147
|
+
A `PUT` with `params` sent `text/plain`, so servers ignored the params.
|
|
127
148
|
* Fixed errors reported by `ExceptionHandler` omitting the type and number of a failed load.
|
|
128
149
|
Arrays in these reports, such as the stack trace, are also no longer sent as objects.
|
|
129
150
|
* Fixed `XH.reloadApp()` re-encoding spaces in existing query params as `+`, and leaving its
|
|
@@ -155,8 +176,6 @@ detailed, step-by-step upgrade instructions with before/after code examples.
|
|
|
155
176
|
* `AgGrid` now publishes its row and header heights to ag-Grid as `--ag-row-height` and
|
|
156
177
|
`--ag-header-height`. `Grid` and `AgGrid` pass ag-Grid a `getRowHeight` function only when row
|
|
157
178
|
heights vary, so uniform grids skip per-row height calls and the post-load row height pass.
|
|
158
|
-
* Removed `GridExperimentalFlags.disableScrollOptimization`. The post-load row height pass now
|
|
159
|
-
runs only for grids whose rows vary in height, where it prevents rows shifting on scroll.
|
|
160
179
|
* `Column` tooltips no longer mount for rows without a record, or for editable cells with no
|
|
161
180
|
validation results, and the tooltip component is a plain function component.
|
|
162
181
|
* `Grid` now passes AG Grid only the pinned rows that changed. A summary row update redraws the
|
|
@@ -178,7 +197,8 @@ detailed, step-by-step upgrade instructions with before/after code examples.
|
|
|
178
197
|
spurious one-time re-render.
|
|
179
198
|
* `Store` now patches small changes by default (`experimental.maxPatchRatio` of 0.1). Records added
|
|
180
199
|
by a change append rather than taking their source position. This may affect the sort ordering
|
|
181
|
-
of unsorted grids when data changes.
|
|
200
|
+
of unsorted grids when data changes. Filters that read state outside the record must call
|
|
201
|
+
`Store.refreshFilter()` when that state changes.
|
|
182
202
|
* `Field` now skips DOMPurify for strings with no `<` when `enableXssProtection` is set. Such
|
|
183
203
|
strings hold no markup, so large loads skip one DOMPurify call per string value.
|
|
184
204
|
* `Field` now parses every unparseable `date` value to one shared Invalid Date, preventing an
|
|
@@ -38,7 +38,7 @@ export class MessageModel extends HoistModel {
|
|
|
38
38
|
dismissable;
|
|
39
39
|
cancelOnDismiss;
|
|
40
40
|
|
|
41
|
-
// Promise settled when the message closes - with the user's choice, or
|
|
41
|
+
// Promise settled when the message closes - with the user's choice, or false if forced closed
|
|
42
42
|
result;
|
|
43
43
|
_resolver;
|
|
44
44
|
|
|
@@ -152,7 +152,8 @@ export class MessageModel extends HoistModel {
|
|
|
152
152
|
const {formModel} = this;
|
|
153
153
|
if (formModel) {
|
|
154
154
|
await formModel.validateAsync();
|
|
155
|
-
|
|
155
|
+
// Message may have been force closed while validating - its result is already false.
|
|
156
|
+
if (!this.isOpen || !formModel.isValid) return;
|
|
156
157
|
const data = formModel.getData();
|
|
157
158
|
if (formModel.getField('value')) {
|
|
158
159
|
resolvedVal = data.value;
|
|
@@ -169,22 +170,22 @@ export class MessageModel extends HoistModel {
|
|
|
169
170
|
@action
|
|
170
171
|
doCancel() {
|
|
171
172
|
this.onCancel?.();
|
|
172
|
-
this.close(
|
|
173
|
+
this.close();
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
@action
|
|
176
177
|
doEscape() {
|
|
177
178
|
if (!this.dismissable) return;
|
|
178
|
-
|
|
179
|
-
this.doCancel();
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
this.close();
|
|
179
|
+
this.cancelOnDismiss ? this.doCancel() : this.close();
|
|
183
180
|
}
|
|
184
181
|
|
|
185
|
-
/**
|
|
182
|
+
/**
|
|
183
|
+
* Close the message, settling `result` with the given value. Defaults to false, so a close
|
|
184
|
+
* the user did not choose reads as a cancel to every caller - see `XH.message()`.
|
|
185
|
+
*/
|
|
186
186
|
@action
|
|
187
|
-
close(result: unknown =
|
|
187
|
+
close(result: unknown = false) {
|
|
188
|
+
if (!this.isOpen) return;
|
|
188
189
|
this._resolver(result);
|
|
189
190
|
this.isOpen = false;
|
|
190
191
|
}
|
package/appcontainer/README.md
CHANGED
|
@@ -84,6 +84,9 @@ methods return a `Promise` that resolves when the user responds, making them ide
|
|
|
84
84
|
The generic modal dialog API — all other message methods delegate to it. Returns
|
|
85
85
|
`Promise<boolean>` that resolves to `true` on confirm, `false` on cancel.
|
|
86
86
|
|
|
87
|
+
A message that the user dismisses, or that closes without a choice, also resolves to `false`. A
|
|
88
|
+
route change, a later message with the same `messageKey`, or a destroy closes a message this way.
|
|
89
|
+
|
|
87
90
|
```typescript
|
|
88
91
|
const result = await XH.message({
|
|
89
92
|
title: 'Export Complete',
|
|
@@ -153,7 +156,8 @@ const confirmed = await XH.confirm({
|
|
|
153
156
|
### `XH.prompt(config)`
|
|
154
157
|
|
|
155
158
|
An input collection dialog with a default `TextInput`, "OK" and "Cancel" buttons. Returns
|
|
156
|
-
`Promise<value | false>` — the input value on confirm, `false` on cancel.
|
|
159
|
+
`Promise<value | false>` — the input value on confirm, `false` on cancel. If an empty input is a
|
|
160
|
+
valid answer, test the result with `!== false`, because `null` and `''` are confirmed values.
|
|
157
161
|
|
|
158
162
|
The default TextInput comes pre-configured with `autoFocus`, `selectOnFocus`, and
|
|
159
163
|
enter-to-confirm (desktop only). Custom inputs must set their own props.
|
|
@@ -222,7 +226,7 @@ XH.alert({
|
|
|
222
226
|
| `onConfirm` | `() => void` | Callback fired on confirm (in addition to Promise resolution) |
|
|
223
227
|
| `onCancel` | `() => void` | Callback fired on cancel (in addition to Promise resolution) |
|
|
224
228
|
| `dismissable` | `boolean` | Whether Escape or clicking outside closes the dialog. Defaults to `true` when cancel button is present |
|
|
225
|
-
| `cancelOnDismiss` | `boolean` | Whether dismissing
|
|
229
|
+
| `cancelOnDismiss` | `boolean` | Whether dismissing calls `onCancel`. The result is `false` either way. Default `true` |
|
|
226
230
|
|
|
227
231
|
**MessageSpecInput** (for `XH.prompt()` config):
|
|
228
232
|
|
|
@@ -41,7 +41,10 @@ export declare class MessageModel extends HoistModel {
|
|
|
41
41
|
doConfirmAsync(): Promise<void>;
|
|
42
42
|
doCancel(): void;
|
|
43
43
|
doEscape(): void;
|
|
44
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Close the message, settling `result` with the given value. Defaults to false, so a close
|
|
46
|
+
* the user did not choose reads as a cancel to every caller - see `XH.message()`.
|
|
47
|
+
*/
|
|
45
48
|
close(result?: unknown): void;
|
|
46
49
|
destroy(): void;
|
|
47
50
|
private parseButtonProps;
|
package/build/types/core/XH.d.ts
CHANGED
|
@@ -335,10 +335,10 @@ export declare class XHApi {
|
|
|
335
335
|
* this method will resolve immediately to their previously saved response, without showing
|
|
336
336
|
* a dialog. This also applies to the `alert`, `confirm`, and `prompt` variants below.
|
|
337
337
|
*
|
|
338
|
-
* @returns true if user confirms,
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
338
|
+
* @returns true if the user confirms, or the input value if an input is provided. Resolves
|
|
339
|
+
* false for any other outcome - a cancel, a dismiss, or a close without a choice, e.g. by a
|
|
340
|
+
* route change or a later message with the same `messageKey`. With an input, test the result
|
|
341
|
+
* with `!== false`, as null and an empty string are valid confirmed values.
|
|
342
342
|
*/
|
|
343
343
|
message<T = unknown>(config: MessageSpec): Promise<T | boolean>;
|
|
344
344
|
/**
|
|
@@ -348,7 +348,7 @@ export declare class XHApi {
|
|
|
348
348
|
alert(config: MessageSpec): Promise<boolean>;
|
|
349
349
|
/**
|
|
350
350
|
* Show a modal 'confirm' dialog with message and default 'OK'/'Cancel' buttons.
|
|
351
|
-
* @returns true if user confirms, false
|
|
351
|
+
* @returns true if user confirms, false otherwise. See {@link message} for details.
|
|
352
352
|
*/
|
|
353
353
|
confirm(config: MessageSpec): Promise<boolean>;
|
|
354
354
|
/**
|
|
@@ -360,7 +360,8 @@ export declare class XHApi {
|
|
|
360
360
|
* 3. onKeyDown handler to confirm on <enter> (same as clicking 'OK') (desktop only)
|
|
361
361
|
* Applications may also provide a custom HoistInput, in which all props must be set.
|
|
362
362
|
*
|
|
363
|
-
* @returns value of input if user confirms, false
|
|
363
|
+
* @returns value of input if user confirms, false otherwise. See {@link message} for details.
|
|
364
|
+
* Test the result with `!== false`, as null and an empty string are valid confirmed values.
|
|
364
365
|
*/
|
|
365
366
|
prompt<T = unknown>(config: MessageSpec): Promise<T | false>;
|
|
366
367
|
/**
|
package/cmp/grid/Grid.ts
CHANGED
|
@@ -242,7 +242,7 @@ export class GridLocalModel extends HoistModel {
|
|
|
242
242
|
this.agOptions = merge(this.createDefaultAgOptions(), this.componentProps.agOptions || {});
|
|
243
243
|
warnIf(
|
|
244
244
|
this.componentProps.agOptions?.groupDefaultExpanded != null,
|
|
245
|
-
'agOptions.groupDefaultExpanded is
|
|
245
|
+
'agOptions.groupDefaultExpanded is ignored. Set GridModel.expandLevel instead.'
|
|
246
246
|
);
|
|
247
247
|
this.rowHeightSupport = new RowHeightSupport(this);
|
|
248
248
|
this.transactionMgr = new GridTransactionManager(this.model);
|
package/cmp/grid/GridModel.ts
CHANGED
|
@@ -115,7 +115,8 @@ import {
|
|
|
115
115
|
omit,
|
|
116
116
|
pick,
|
|
117
117
|
pull,
|
|
118
|
-
take
|
|
118
|
+
take,
|
|
119
|
+
uniqBy
|
|
119
120
|
} from 'lodash';
|
|
120
121
|
import {createRef, ReactNode, RefObject} from 'react';
|
|
121
122
|
import {GridAutosizeOptions} from './GridAutosizeOptions';
|
|
@@ -2024,8 +2025,12 @@ export class GridModel extends HoistModel {
|
|
|
2024
2025
|
const gridCols = this.getLeafColumns();
|
|
2025
2026
|
|
|
2026
2027
|
// REMOVE any state columns that are no longer found in the grid. These were likely saved
|
|
2027
|
-
// under a prior release of the app and have since been removed from the code.
|
|
2028
|
-
|
|
2028
|
+
// under a prior release of the app and have since been removed from the code. Also
|
|
2029
|
+
// REMOVE any repeated columns, keeping the first.
|
|
2030
|
+
let ret = uniqBy<any>(
|
|
2031
|
+
columnState.filter(({colId}) => this.getColumn(colId)),
|
|
2032
|
+
'colId'
|
|
2033
|
+
);
|
|
2029
2034
|
|
|
2030
2035
|
// ADD any grid columns that are not found in state. These are newly added to the code.
|
|
2031
2036
|
// Insert these columns in position based on the index at which they are defined.
|
package/core/XH.ts
CHANGED
|
@@ -671,10 +671,10 @@ export class XHApi {
|
|
|
671
671
|
* this method will resolve immediately to their previously saved response, without showing
|
|
672
672
|
* a dialog. This also applies to the `alert`, `confirm`, and `prompt` variants below.
|
|
673
673
|
*
|
|
674
|
-
* @returns true if user confirms,
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
*
|
|
674
|
+
* @returns true if the user confirms, or the input value if an input is provided. Resolves
|
|
675
|
+
* false for any other outcome - a cancel, a dismiss, or a close without a choice, e.g. by a
|
|
676
|
+
* route change or a later message with the same `messageKey`. With an input, test the result
|
|
677
|
+
* with `!== false`, as null and an empty string are valid confirmed values.
|
|
678
678
|
*/
|
|
679
679
|
message<T = unknown>(config: MessageSpec): Promise<T | boolean> {
|
|
680
680
|
return this.acm.messageSourceModel.message(config);
|
|
@@ -690,7 +690,7 @@ export class XHApi {
|
|
|
690
690
|
|
|
691
691
|
/**
|
|
692
692
|
* Show a modal 'confirm' dialog with message and default 'OK'/'Cancel' buttons.
|
|
693
|
-
* @returns true if user confirms, false
|
|
693
|
+
* @returns true if user confirms, false otherwise. See {@link message} for details.
|
|
694
694
|
*/
|
|
695
695
|
confirm(config: MessageSpec): Promise<boolean> {
|
|
696
696
|
return this.acm.messageSourceModel.confirm(config);
|
|
@@ -705,7 +705,8 @@ export class XHApi {
|
|
|
705
705
|
* 3. onKeyDown handler to confirm on <enter> (same as clicking 'OK') (desktop only)
|
|
706
706
|
* Applications may also provide a custom HoistInput, in which all props must be set.
|
|
707
707
|
*
|
|
708
|
-
* @returns value of input if user confirms, false
|
|
708
|
+
* @returns value of input if user confirms, false otherwise. See {@link message} for details.
|
|
709
|
+
* Test the result with `!== false`, as null and an empty string are valid confirmed values.
|
|
709
710
|
*/
|
|
710
711
|
prompt<T = unknown>(config: MessageSpec): Promise<T | false> {
|
|
711
712
|
return this.acm.messageSourceModel.prompt(config);
|
package/data/README.md
CHANGED
|
@@ -270,8 +270,9 @@ store.count; // Filtered record count
|
|
|
270
270
|
store.allCount; // Total record count
|
|
271
271
|
```
|
|
272
272
|
|
|
273
|
-
Store re-filters automatically whenever record data changes
|
|
274
|
-
|
|
273
|
+
Store re-filters automatically whenever record data changes, but a patched change (the default for
|
|
274
|
+
small changes) re-tests only the records it touches. Call `refreshFilter()` when the state behind a
|
|
275
|
+
`FunctionFilter` changes without any change to the records themselves.
|
|
275
276
|
|
|
276
277
|
### Observable Properties
|
|
277
278
|
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
7
|
import {isEmpty} from 'lodash';
|
|
8
|
-
import {ReactElement} from 'react';
|
|
9
8
|
import {box} from '@xh/hoist/cmp/layout';
|
|
10
|
-
import {hoistCmp, uses} from '@xh/hoist/core';
|
|
11
|
-
import {
|
|
9
|
+
import {hoistCmp, type MenuItemLike, uses} from '@xh/hoist/core';
|
|
10
|
+
import {contextMenu} from '@xh/hoist/desktop/cmp/contextmenu';
|
|
12
11
|
import {createViewMenuItems} from '@xh/hoist/desktop/cmp/dash/canvas/impl/utils';
|
|
12
|
+
import {parseMenuItems} from '@xh/hoist/desktop/cmp/menu/impl/MenuItems';
|
|
13
13
|
import {elementFromContent, useOnResize} from '@xh/hoist/utils/react';
|
|
14
14
|
import {Icon} from '@xh/hoist/icon';
|
|
15
15
|
import {popover, Position} from '@xh/hoist/kit/blueprint';
|
|
@@ -72,55 +72,52 @@ const headerMenu = hoistCmp.factory<DashCanvasViewModel>(({model}) => {
|
|
|
72
72
|
viewId: id,
|
|
73
73
|
replaceExisting: true
|
|
74
74
|
}),
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
]
|
|
119
|
-
}) as ReactElement;
|
|
75
|
+
menuItems: MenuItemLike[] = [
|
|
76
|
+
{
|
|
77
|
+
text: 'Add',
|
|
78
|
+
icon: Icon.add(),
|
|
79
|
+
items: addMenuItems,
|
|
80
|
+
hidden: contentLocked || isEmpty(addMenuItems)
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
text: 'Remove',
|
|
84
|
+
icon: Icon.cross(),
|
|
85
|
+
hidden: !viewSpec.allowRemove || contentLocked,
|
|
86
|
+
actionFn: () => containerModel.removeView(id)
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
text: 'Rename',
|
|
90
|
+
icon: Icon.edit(),
|
|
91
|
+
hidden: !viewSpec.allowRename || renameLocked,
|
|
92
|
+
actionFn: () => containerModel.renameView(id)
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
text: 'Replace',
|
|
96
|
+
icon: Icon.transaction(),
|
|
97
|
+
items: replaceMenuItems,
|
|
98
|
+
hidden: !viewSpec.allowRemove || contentLocked || isEmpty(replaceMenuItems)
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
text: 'Duplicate',
|
|
102
|
+
icon: Icon.copy(),
|
|
103
|
+
hidden: !allowDuplicate || contentLocked || viewSpec.unique,
|
|
104
|
+
actionFn: () =>
|
|
105
|
+
containerModel
|
|
106
|
+
.addViewInternal(viewSpec.id, {
|
|
107
|
+
layout: getDuplicateLayout(positionParams, model),
|
|
108
|
+
state: viewState,
|
|
109
|
+
title
|
|
110
|
+
})
|
|
111
|
+
.ensureVisible()
|
|
112
|
+
},
|
|
113
|
+
'-',
|
|
114
|
+
...(model.extraMenuItems ?? []),
|
|
115
|
+
'-',
|
|
116
|
+
...(containerModel.extraMenuItems ?? [])
|
|
117
|
+
];
|
|
120
118
|
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
if (!content) return null;
|
|
119
|
+
// Skip the button if every item is hidden.
|
|
120
|
+
if (isEmpty(parseMenuItems(menuItems))) return null;
|
|
124
121
|
|
|
125
122
|
return popover({
|
|
126
123
|
position: Position.BOTTOM,
|
|
@@ -128,7 +125,7 @@ const headerMenu = hoistCmp.factory<DashCanvasViewModel>(({model}) => {
|
|
|
128
125
|
item: button({
|
|
129
126
|
icon: Icon.ellipsisVertical()
|
|
130
127
|
}),
|
|
131
|
-
content
|
|
128
|
+
content: contextMenu({menuItems})
|
|
132
129
|
});
|
|
133
130
|
});
|
|
134
131
|
|
|
@@ -20,7 +20,8 @@ the move to TypeScript 7 is strongly recommended.
|
|
|
20
20
|
- **Icons added via `library.add()` (recommended)** - Register them with `Icon.register()` so
|
|
21
21
|
`IconPicker` offers them - see Step 8.
|
|
22
22
|
- **Grid rendering defaults (review)** - Column virtualisation is now on, and columns without a
|
|
23
|
-
`renderer` render plain text with no wrapper span
|
|
23
|
+
`renderer` render plain text with no wrapper span. React element values in those columns need a
|
|
24
|
+
`renderer` - see Step 9.
|
|
24
25
|
- **Badge spacing (required if used)** - `Badge` no longer has a 5px left margin. Badges that
|
|
25
26
|
follow text need a space or a container `gap` - see Step 10.
|
|
26
27
|
- **Column Library config (required if used)** - `ColLibraryConfig.autoExpandOnFilter` is gone.
|
|
@@ -28,6 +29,10 @@ the move to TypeScript 7 is strongly recommended.
|
|
|
28
29
|
- **App option presets (required if used)** - Desktop `themeAppOption()` and
|
|
29
30
|
`sizingModeAppOption()` now render a `RadioCardInput`. Apps passing `SegmentedControl` props via
|
|
30
31
|
`inputProps` should add `previewCards: false` - see Step 12.
|
|
32
|
+
- **`agOptions.groupDefaultExpanded` (required if used)** - `Grid` now ignores it. Set
|
|
33
|
+
`GridModel.expandLevel` instead - see Step 13.
|
|
34
|
+
- **`disableScrollOptimization` flag (required if used)** - `GridExperimentalFlags` no longer has
|
|
35
|
+
this flag. An app that sets it fails to compile - see Step 14.
|
|
31
36
|
|
|
32
37
|
Apps can also stay on TypeScript 5.9. Hoist v89 type-checks under 5.9, 6, and 7, so the TypeScript
|
|
33
38
|
move does not have to ship with the Hoist bump. We still recommend that you take both together.
|
|
@@ -440,6 +445,44 @@ After:
|
|
|
440
445
|
}
|
|
441
446
|
```
|
|
442
447
|
|
|
448
|
+
**Element values need a renderer.** ag-Grid writes a plain cell's value as text, so a React
|
|
449
|
+
element value now shows as `[object Object]`. The usual source is a `getValueFn` that calls a Hoist
|
|
450
|
+
formatter. `fmtNumber` and its variants return an element for options such as `label`, `ledger`,
|
|
451
|
+
`colorSpec` and `tooltip`. Have the `getValueFn` return the raw value, and format it in a
|
|
452
|
+
`renderer`. Sorting, filtering and export also read the raw value.
|
|
453
|
+
|
|
454
|
+
**Find affected columns:**
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
grep -rn "getValueFn" client-app/src/
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Before:
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
{
|
|
464
|
+
field: 'elapsed',
|
|
465
|
+
getValueFn: ({record}) => {
|
|
466
|
+
const {startTime, endTime} = record.data;
|
|
467
|
+
if (!startTime || !endTime) return '-';
|
|
468
|
+
return fmtNumber((endTime - startTime) / 1000, {precision: 2, label: 's'});
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
After:
|
|
474
|
+
|
|
475
|
+
```typescript
|
|
476
|
+
{
|
|
477
|
+
field: 'elapsed',
|
|
478
|
+
getValueFn: ({record}) => {
|
|
479
|
+
const {startTime, endTime} = record.data;
|
|
480
|
+
return startTime && endTime ? (endTime - startTime) / 1000 : null;
|
|
481
|
+
},
|
|
482
|
+
renderer: numberRenderer({precision: 2, label: 's', nullDisplay: '-'})
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
443
486
|
### 10. Space Badges from Preceding Text
|
|
444
487
|
|
|
445
488
|
`Badge` now renders inline with no outer margin, like a badge in most component libraries. The old
|
|
@@ -520,6 +563,47 @@ After:
|
|
|
520
563
|
themeAppOption({previewCards: false, inputProps: {outlined: true}})
|
|
521
564
|
```
|
|
522
565
|
|
|
566
|
+
### 13. Replace `agOptions.groupDefaultExpanded` with `expandLevel`
|
|
567
|
+
|
|
568
|
+
`Grid` now opens group rows with ag-Grid's `isGroupOpenByDefault` callback, driven by
|
|
569
|
+
`GridModel.expandLevel`. ag-Grid checks that callback before `groupDefaultExpanded`, so a grid
|
|
570
|
+
that sets `groupDefaultExpanded` in `agOptions` now ignores it. Such a grid logs a console warning
|
|
571
|
+
and opens only one level, the default `expandLevel` for grouped grids.
|
|
572
|
+
|
|
573
|
+
**Find affected code:**
|
|
574
|
+
|
|
575
|
+
```bash
|
|
576
|
+
grep -rn "groupDefaultExpanded" client-app/src/
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
Move the value to `expandLevel` on the `GridModel`.
|
|
580
|
+
|
|
581
|
+
Before:
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
grid({agOptions: {groupDefaultExpanded: 2}})
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
After:
|
|
588
|
+
|
|
589
|
+
```typescript
|
|
590
|
+
new GridModel({groupBy: ['region', 'desk'], expandLevel: 2, ...})
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### 14. Remove `GridExperimentalFlags.disableScrollOptimization`
|
|
594
|
+
|
|
595
|
+
The post-load row height pass this flag disabled now runs only for grids whose rows vary in height,
|
|
596
|
+
so the flag is gone. An app that still sets it fails to compile.
|
|
597
|
+
|
|
598
|
+
**Find affected code:**
|
|
599
|
+
|
|
600
|
+
```bash
|
|
601
|
+
grep -rn "disableScrollOptimization" client-app/src/
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
Remove the key from the grid's `experimental` config, and from any `xhGridExperimental` soft
|
|
605
|
+
config.
|
|
606
|
+
|
|
523
607
|
## Other Changes to Review
|
|
524
608
|
|
|
525
609
|
These need no code change in most apps.
|
|
@@ -532,6 +616,14 @@ These need no code change in most apps.
|
|
|
532
616
|
source position. Grids with no
|
|
533
617
|
sort may show new rows in a different order. Set a sort, or set `maxPatchRatio: 0` in the
|
|
534
618
|
store's `experimental` config to restore the old behavior.
|
|
619
|
+
- **Filters that read outside state need `refreshFilter()`.** A patched change re-tests only the
|
|
620
|
+
records it touches. A `FunctionFilter` that reads state other than the record, such as a model
|
|
621
|
+
property, no longer re-runs on the other records when data changes. Call
|
|
622
|
+
`store.refreshFilter()` when that state changes. v88 hid a missing call by re-filtering every
|
|
623
|
+
record on each change.
|
|
624
|
+
- **`PUT` requests default to a form-encoded body.** `FetchService` now labels every `PUT` without
|
|
625
|
+
an explicit `Content-Type` as `application/x-www-form-urlencoded`, as it does for `POST`. v88
|
|
626
|
+
sent `text/plain`. A `PUT` that sends a raw `body` should set its own `Content-Type`.
|
|
535
627
|
|
|
536
628
|
## Verification Checklist
|
|
537
629
|
|
|
@@ -553,6 +645,7 @@ After completing all steps:
|
|
|
553
645
|
- [ ] No `columnLibrary` config sets `autoExpandOnFilter`.
|
|
554
646
|
- [ ] The Options dialog shows theme and sizing choices as intended - cards, or a segmented
|
|
555
647
|
control where `previewCards: false` is set.
|
|
648
|
+
- [ ] No grid sets `agOptions.groupDefaultExpanded`, and grouped grids open to the intended level.
|
|
556
649
|
|
|
557
650
|
## Reference
|
|
558
651
|
|
package/package.json
CHANGED
package/test-support/setup.ts
CHANGED
|
@@ -48,6 +48,14 @@ if (!window.screen.orientation) {
|
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// jsdom has no ResizeObserver, which the useOnResize hook creates. jsdom has no layout either, so
|
|
52
|
+
// an element never resizes - observe nothing.
|
|
53
|
+
globalThis.ResizeObserver ??= class {
|
|
54
|
+
observe() {}
|
|
55
|
+
unobserve() {}
|
|
56
|
+
disconnect() {}
|
|
57
|
+
};
|
|
58
|
+
|
|
51
59
|
// Onsen UI, loaded by the mobile platform, throws "Invalid state" at import unless the root
|
|
52
60
|
// element's computed style lists a transition property. jsdom lists only the properties set on it.
|
|
53
61
|
document.documentElement.style.transitionDuration = '0s';
|