@xh/hoist 70.0.0-SNAPSHOT.1731083521069 → 70.0.0-SNAPSHOT.1731623470295

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.
Files changed (95) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/build/types/cmp/filter/FilterChooserModel.d.ts +17 -12
  3. package/build/types/cmp/grid/GridModel.d.ts +5 -9
  4. package/build/types/cmp/grid/Types.d.ts +7 -19
  5. package/build/types/cmp/grid/columns/Column.d.ts +0 -1
  6. package/build/types/cmp/grid/impl/InitPersist.d.ts +7 -0
  7. package/build/types/cmp/grouping/GroupingChooserModel.d.ts +6 -8
  8. package/build/types/cmp/tab/TabContainerModel.d.ts +10 -4
  9. package/build/types/cmp/zoneGrid/Types.d.ts +6 -6
  10. package/build/types/cmp/zoneGrid/ZoneGridModel.d.ts +0 -2
  11. package/build/types/cmp/zoneGrid/impl/InitPersist.d.ts +7 -0
  12. package/build/types/core/HoistBase.d.ts +1 -1
  13. package/build/types/core/persist/CustomProvider.d.ts +5 -6
  14. package/build/types/core/persist/DashViewProvider.d.ts +6 -6
  15. package/build/types/core/persist/LocalStorageProvider.d.ts +4 -5
  16. package/build/types/core/persist/PersistOptions.d.ts +5 -4
  17. package/build/types/core/persist/Persistable.d.ts +14 -0
  18. package/build/types/core/persist/PersistenceProvider.d.ts +47 -34
  19. package/build/types/core/persist/PrefProvider.d.ts +5 -5
  20. package/build/types/core/persist/index.d.ts +2 -0
  21. package/build/types/core/persist/viewmanager/Types.d.ts +46 -0
  22. package/build/types/core/persist/viewmanager/ViewManagerModel.d.ts +149 -0
  23. package/build/types/core/persist/viewmanager/ViewManagerProvider.d.ts +10 -0
  24. package/build/types/core/persist/viewmanager/impl/ManageDialogModel.d.ts +30 -0
  25. package/build/types/core/persist/viewmanager/impl/SaveDialogModel.d.ts +23 -0
  26. package/build/types/core/persist/viewmanager/index.d.ts +2 -0
  27. package/build/types/desktop/cmp/button/ColAutosizeButton.d.ts +1 -1
  28. package/build/types/desktop/cmp/dash/DashConfig.d.ts +3 -1
  29. package/build/types/desktop/cmp/dash/DashModel.d.ts +1 -2
  30. package/build/types/desktop/cmp/dash/DashViewSpec.d.ts +1 -1
  31. package/build/types/desktop/cmp/dash/canvas/DashCanvasModel.d.ts +10 -2
  32. package/build/types/desktop/cmp/dash/container/DashContainerModel.d.ts +26 -10
  33. package/build/types/desktop/cmp/dash/container/impl/DashContainerUtils.d.ts +4 -2
  34. package/build/types/desktop/cmp/panel/PanelModel.d.ts +8 -4
  35. package/build/types/desktop/cmp/viewmanager/ViewManager.d.ts +22 -0
  36. package/build/types/desktop/cmp/viewmanager/cmp/ManageDialog.d.ts +6 -0
  37. package/build/types/desktop/cmp/viewmanager/cmp/SaveDialog.d.ts +2 -0
  38. package/build/types/desktop/cmp/viewmanager/index.d.ts +3 -0
  39. package/build/types/kit/blueprint/Wrappers.d.ts +1 -1
  40. package/build/types/mobile/cmp/button/ColAutosizeButton.d.ts +1 -1
  41. package/build/types/promise/Promise.d.ts +6 -5
  42. package/build/types/svc/GridAutosizeService.d.ts +2 -5
  43. package/build/types/svc/JsonBlobService.d.ts +45 -24
  44. package/cmp/filter/FilterChooserModel.ts +142 -125
  45. package/cmp/grid/Grid.ts +2 -10
  46. package/cmp/grid/GridModel.ts +18 -31
  47. package/cmp/grid/Types.ts +7 -21
  48. package/cmp/grid/columns/Column.ts +0 -1
  49. package/cmp/grid/impl/InitPersist.ts +71 -0
  50. package/cmp/grouping/GroupingChooserModel.ts +48 -57
  51. package/cmp/tab/TabContainerModel.ts +22 -36
  52. package/cmp/zoneGrid/Types.ts +6 -6
  53. package/cmp/zoneGrid/ZoneGridModel.ts +2 -7
  54. package/cmp/zoneGrid/impl/InitPersist.ts +70 -0
  55. package/core/HoistBase.ts +14 -22
  56. package/core/HoistBaseDecorators.ts +26 -28
  57. package/core/persist/CustomProvider.ts +7 -10
  58. package/core/persist/DashViewProvider.ts +8 -10
  59. package/core/persist/LocalStorageProvider.ts +9 -12
  60. package/core/persist/PersistOptions.ts +6 -4
  61. package/core/persist/Persistable.ts +23 -0
  62. package/core/persist/PersistenceProvider.ts +159 -79
  63. package/core/persist/PrefProvider.ts +9 -12
  64. package/core/persist/index.ts +2 -0
  65. package/core/persist/viewmanager/Types.ts +51 -0
  66. package/core/persist/viewmanager/ViewManagerModel.ts +515 -0
  67. package/core/persist/viewmanager/ViewManagerProvider.ts +51 -0
  68. package/core/persist/viewmanager/impl/ManageDialogModel.ts +274 -0
  69. package/core/persist/viewmanager/impl/SaveDialogModel.ts +112 -0
  70. package/core/persist/viewmanager/index.ts +2 -0
  71. package/desktop/cmp/button/ColAutosizeButton.ts +1 -1
  72. package/desktop/cmp/dash/DashConfig.ts +3 -1
  73. package/desktop/cmp/dash/DashModel.ts +1 -2
  74. package/desktop/cmp/dash/DashViewSpec.ts +1 -1
  75. package/desktop/cmp/dash/canvas/DashCanvasModel.ts +31 -30
  76. package/desktop/cmp/dash/container/DashContainerModel.ts +68 -43
  77. package/desktop/cmp/dash/container/impl/DashContainerUtils.ts +13 -4
  78. package/desktop/cmp/leftrightchooser/LeftRightChooserFilter.ts +1 -1
  79. package/desktop/cmp/panel/PanelModel.ts +33 -53
  80. package/desktop/cmp/store/impl/StoreFilterField.ts +1 -1
  81. package/desktop/cmp/viewmanager/ViewManager.scss +58 -0
  82. package/desktop/cmp/viewmanager/ViewManager.ts +274 -0
  83. package/desktop/cmp/viewmanager/cmp/ManageDialog.ts +197 -0
  84. package/desktop/cmp/viewmanager/cmp/SaveDialog.ts +89 -0
  85. package/desktop/cmp/viewmanager/index.ts +3 -0
  86. package/mobile/cmp/button/ColAutosizeButton.ts +1 -1
  87. package/package.json +1 -1
  88. package/promise/Promise.ts +6 -7
  89. package/svc/GridAutosizeService.ts +73 -36
  90. package/svc/JsonBlobService.ts +64 -31
  91. package/tsconfig.tsbuildinfo +1 -1
  92. package/build/types/cmp/grid/impl/GridPersistenceModel.d.ts +0 -41
  93. package/build/types/cmp/zoneGrid/impl/ZoneGridPersistenceModel.d.ts +0 -39
  94. package/cmp/grid/impl/GridPersistenceModel.ts +0 -174
  95. package/cmp/zoneGrid/impl/ZoneGridPersistenceModel.ts +0 -149
@@ -1,35 +1,56 @@
1
- import { HoistService } from '@xh/hoist/core';
1
+ import { HoistService, LoadSpec, PlainObject } from '@xh/hoist/core';
2
+ export interface JsonBlob {
3
+ /** Either null for private blobs or special token "*" for globally shared blobs. */
4
+ acl: string;
5
+ /** True if this blob has been archived (soft-deleted). */
6
+ archived: boolean;
7
+ /** Timestamp indicating when this blob was archived, or special value `0` if not archived. */
8
+ archivedDate: number;
9
+ dateCreated: number;
10
+ description: string;
11
+ /** @internal database ID for this blob. Favor token instead. */
12
+ id: number;
13
+ lastUpdated: number;
14
+ lastUpdatedBy: string;
15
+ /** Optional application-specific metadata. */
16
+ meta: PlainObject | unknown[];
17
+ name: string;
18
+ /** Username of the blob's creator / owner. */
19
+ owner: string;
20
+ /** Primary unique identifier for getting, updating, and archiving blobs. */
21
+ token: string;
22
+ /**
23
+ * Application defined type for this blob. Used as a discriminator when querying blobs related
24
+ * to a particular use-case within an application.
25
+ */
26
+ type: string;
27
+ /**
28
+ * Current JSON value of this blob - its contents or data. Will be undefined for blob stubs
29
+ * returned by `listAsync` if `includeValue` was false.
30
+ */
31
+ value?: any;
32
+ }
2
33
  /**
3
34
  * Service to read and set chunks of user-specific JSON persisted via Hoist Core's JSONBlob class.
35
+ *
36
+ * This service is intended as a general, lightweight utility for persisting small bundles of
37
+ * unstructured data that might not warrant a full-blown domain object, but which still need to be
38
+ * persisted back to the database.
4
39
  */
5
40
  export declare class JsonBlobService extends HoistService {
6
41
  static instance: JsonBlobService;
7
- getAsync(token: any): Promise<any>;
8
- /**
9
- * Return the list of blobs visible to the current user.
10
- *
11
- * @param type - reference key for which type of data to list.
12
- * @param includeValue - true to include the full value string for each blob.
13
- */
14
- listAsync({ type, includeValue }: {
42
+ /** Retrieve a single JSONBlob by its unique token. */
43
+ getAsync(token: string): Promise<JsonBlob>;
44
+ /** Retrieve all blobs of a particular type that are visible to the current user. */
45
+ listAsync({ type, includeValue, loadSpec }: {
15
46
  type: string;
16
47
  includeValue?: boolean;
48
+ loadSpec?: LoadSpec;
17
49
  }): Promise<any>;
18
50
  /** Persist a new JSONBlob back to the server. */
19
- createAsync({ type, name, value, meta, description }: {
20
- type: string;
21
- name: string;
22
- description?: string;
23
- value: any;
24
- meta?: any;
25
- }): Promise<any>;
26
- /** Modify an existing JSONBlob, as identified by its unique token. */
27
- updateAsync(token: string, { name, value, meta, description }: {
28
- name?: string;
29
- value?: any;
30
- meta?: any;
31
- description?: string;
32
- }): Promise<any>;
51
+ createAsync({ acl, description, type, meta, name, value }: Partial<JsonBlob>): Promise<JsonBlob>;
52
+ /** Modify mutable properties of an existing JSONBlob, as identified by its unique token. */
53
+ updateAsync(token: string, { acl, description, meta, name, value }: Partial<JsonBlob>): Promise<JsonBlob>;
33
54
  /** Archive (soft-delete) an existing JSONBlob, as identified by its unique token. */
34
- archiveAsync(token: string): Promise<any>;
55
+ archiveAsync(token: string): Promise<JsonBlob>;
35
56
  }
@@ -7,10 +7,11 @@
7
7
  import {
8
8
  HoistModel,
9
9
  managed,
10
+ PersistableState,
10
11
  PersistenceProvider,
11
12
  PersistOptions,
12
- PlainObject,
13
13
  TaskObserver,
14
+ Thunkable,
14
15
  XH
15
16
  } from '@xh/hoist/core';
16
17
  import {
@@ -26,9 +27,8 @@ import {
26
27
  import {CompoundFilterSpec, FieldFilterSpec, FilterLike} from '@xh/hoist/data/filter/Types';
27
28
  import {action, makeObservable, observable} from '@xh/hoist/mobx';
28
29
  import {wait} from '@xh/hoist/promise';
29
- import {throwIf, withDefault} from '@xh/hoist/utils/js';
30
+ import {executeIfFunction, throwIf, withDefault} from '@xh/hoist/utils/js';
30
31
  import {createObservableRef} from '@xh/hoist/utils/react';
31
- import {ReactNode} from 'react';
32
32
  import {
33
33
  cloneDeep,
34
34
  compact,
@@ -39,13 +39,14 @@ import {
39
39
  isArray,
40
40
  isEmpty,
41
41
  isFinite,
42
- isFunction,
42
+ isObject,
43
43
  isString,
44
44
  partition,
45
45
  sortBy,
46
46
  uniq,
47
47
  uniqBy
48
48
  } from 'lodash';
49
+ import {ReactNode} from 'react';
49
50
 
50
51
  import {FilterChooserFieldSpec, FilterChooserFieldSpecConfig} from './FilterChooserFieldSpec';
51
52
  import {compoundFilterOption, fieldFilterOption, FilterChooserOption} from './impl/Option';
@@ -80,12 +81,12 @@ export interface FilterChooserConfig {
80
81
  * to produce the same. Note that FilterChooser currently can only edit and create a flat collection
81
82
  * of FieldFilters, to be 'AND'ed together.
82
83
  */
83
- initialValue?: FilterChooserFilterLike | (() => FilterChooserFilterLike);
84
+ initialValue?: Thunkable<FilterChooserFilterLike>;
84
85
 
85
86
  /**
86
87
  * Initial favorites as an array of filter configurations, or a function to produce such an array.
87
88
  */
88
- initialFavorites?: FilterChooserFilterLike[] | (() => FilterChooserFilterLike[]);
89
+ initialFavorites?: Thunkable<FilterChooserFilterLike[]>;
89
90
 
90
91
  /**
91
92
  * true to offer all field suggestions when the control is focused with an empty query,
@@ -131,9 +132,6 @@ export class FilterChooserModel extends HoistModel {
131
132
  maxTags: number;
132
133
  maxResults: number;
133
134
  introHelpText: ReactNode;
134
-
135
- @managed provider: PersistenceProvider;
136
- persistValue: boolean = false;
137
135
  persistFavorites: boolean = false;
138
136
 
139
137
  /** Tracks execution of filtering operation on bound object.*/
@@ -174,39 +172,12 @@ export class FilterChooserModel extends HoistModel {
174
172
  this.introHelpText = withDefault(introHelpText, this.getDefaultIntroHelpText());
175
173
  this.queryEngine = new QueryEngine(this);
176
174
 
177
- let value = isFunction(initialValue) ? initialValue() : initialValue,
178
- favorites = (isFunction(initialFavorites) ? initialFavorites() : initialFavorites).map(
179
- f => parseFilter(f)
180
- );
181
-
182
- // Read state from provider -- fail gently
183
- if (persistWith) {
184
- try {
185
- this.provider = PersistenceProvider.create({path: 'filterChooser', ...persistWith});
186
- this.persistValue = persistWith.persistValue ?? true;
187
- this.persistFavorites = persistWith.persistFavorites ?? true;
188
-
189
- const state = this.provider.read();
190
- if (this.persistValue && state?.value) {
191
- value = state.value;
192
- }
193
- if (this.persistFavorites && state?.favorites) {
194
- favorites = state.favorites.map(f => parseFilter(f));
195
- }
175
+ this.setValueInternal(executeIfFunction(initialValue), false);
176
+ this.setFavorites(executeIfFunction(initialFavorites));
196
177
 
197
- this.addReaction({
198
- track: () => this.persistState,
199
- run: state => this.provider.write(state)
200
- });
201
- } catch (e) {
202
- this.logError(e);
203
- XH.safeDestroy(this.provider);
204
- this.provider = null;
205
- }
206
- }
178
+ if (persistWith) this.initPersist(persistWith);
207
179
 
208
- this.setValue(value);
209
- this.setFavorites(favorites);
180
+ this.updateSelectValueAndBind();
210
181
 
211
182
  if (bind) {
212
183
  this.addReaction({
@@ -231,73 +202,8 @@ export class FilterChooserModel extends HoistModel {
231
202
  *
232
203
  * Any other Filter is unsupported and will cause the control to show a placeholder error.
233
204
  */
234
- @action
235
205
  setValue(rawValue: FilterLike) {
236
- const {bind, maxTags} = this;
237
- try {
238
- const value = parseFilter(rawValue);
239
- if (this.value?.equals(value)) return;
240
-
241
- // 1) Ensure FilterChooser can handle the requested value.
242
- const isValid = this.validateFilter(value),
243
- displayFilters = isValid ? this.toDisplayFilters(value) : null;
244
-
245
- this.unsupportedFilter = !isValid || (maxTags && displayFilters.length > maxTags);
246
- if (this.unsupportedFilter) {
247
- this.value = null;
248
- this.selectOptions = null;
249
- this.selectValue = null;
250
- return;
251
- }
252
-
253
- // 2) Main path - filter has been validated as supported, set internal value.
254
- this.logDebug('Setting value', value);
255
- this.value = value as FilterChooserFilter;
256
-
257
- // 3) Set props on select input needed to display
258
- // Build list of options, used for displaying tags. We combine the needed
259
- // options for the current filter tags with any previous ones to ensure
260
- // tags are rendered correctly throughout the transition.
261
- const newOptions = displayFilters.map(f => this.createFilterOption(f)),
262
- previousOptions = this.selectOptions ?? [],
263
- options = uniqBy([...newOptions, ...previousOptions], 'value');
264
-
265
- this.selectOptions = !isEmpty(options) ? options : null;
266
-
267
- // 4) Do the next steps asynchronously for UI responsiveness and to ensure the component
268
- // is ready to render the tags correctly (after selectOptions set above).
269
- wait()
270
- .thenAction(() => {
271
- // No-op if we've already re-entered this method by the time this async routine runs.
272
- if (this.value !== value) {
273
- return;
274
- }
275
-
276
- this.selectValue = sortBy(
277
- displayFilters.map(f => JSON.stringify(f)),
278
- f => {
279
- const idx = this.selectValue?.indexOf(f);
280
- return isFinite(idx) && idx > -1 ? idx : displayFilters.length;
281
- }
282
- );
283
-
284
- // 5) Round-trip value to bound filter
285
- if (bind) {
286
- const filter = withFilterByTypes(bind.filter, value, [
287
- 'FieldFilter',
288
- 'CompoundFilter'
289
- ]);
290
- bind.setFilter(filter);
291
- }
292
- })
293
- .linkTo(this.filterTask);
294
- } catch (e) {
295
- this.logError('Failed to set value', e);
296
- this.value = null;
297
- this.selectOptions = null;
298
- this.selectValue = null;
299
- this.unsupportedFilter = true;
300
- }
206
+ this.setValueInternal(rawValue, true);
301
207
  }
302
208
 
303
209
  //---------------------------
@@ -422,8 +328,8 @@ export class FilterChooserModel extends HoistModel {
422
328
  }
423
329
 
424
330
  @action
425
- setFavorites(favorites: Filter[]) {
426
- this.favorites = favorites.filter(this.validateFilter.bind(this));
331
+ setFavorites(favorites: FilterChooserFilterLike[]) {
332
+ this.favorites = favorites.map(parseFilter).filter(this.validateFilter.bind(this));
427
333
  }
428
334
 
429
335
  @action
@@ -445,16 +351,6 @@ export class FilterChooserModel extends HoistModel {
445
351
  return !!this.findFavorite(filter);
446
352
  }
447
353
 
448
- //-------------------------
449
- // Persistence handling
450
- //-------------------------
451
- get persistState() {
452
- const ret: PlainObject = {};
453
- if (this.persistValue) ret.value = this.value;
454
- if (this.persistFavorites) ret.favorites = this.favorites;
455
- return ret;
456
- }
457
-
458
354
  //--------------------------------
459
355
  // FilterChooserFieldSpec handling
460
356
  //--------------------------------
@@ -507,22 +403,143 @@ export class FilterChooserModel extends HoistModel {
507
403
  getDefaultIntroHelpText(): string {
508
404
  return 'Select or enter a field name (below) or begin typing to match available field values.';
509
405
  }
406
+
407
+ // -------------------------------
408
+ // Implementation
409
+ // -------------------------------
410
+ private initPersist({
411
+ persistValue = true,
412
+ persistFavorites = true,
413
+ path = 'filterChooser',
414
+ ...rootPersistWith
415
+ }: FilterChooserPersistOptions) {
416
+ if (persistValue) {
417
+ const status = {initialized: false},
418
+ persistWith = isObject(persistValue) ? persistValue : rootPersistWith;
419
+ PersistenceProvider.create({
420
+ persistOptions: {
421
+ path: `${path}.value`,
422
+ ...persistWith
423
+ },
424
+ target: {
425
+ getPersistableState: () => new PersistableState(this.value?.toJSON() ?? null),
426
+ setPersistableState: ({value}) =>
427
+ this.setValueInternal(value, status.initialized)
428
+ },
429
+ owner: this
430
+ });
431
+ status.initialized = true;
432
+ }
433
+
434
+ if (persistFavorites) {
435
+ const persistWith = isObject(persistFavorites) ? persistFavorites : rootPersistWith,
436
+ provider = PersistenceProvider.create({
437
+ persistOptions: {
438
+ path: `${path}.favorites`,
439
+ ...persistWith
440
+ },
441
+ target: {
442
+ getPersistableState: () =>
443
+ new PersistableState(this.favorites.map(f => f.toJSON())),
444
+ setPersistableState: ({value}) => this.setFavorites(value)
445
+ },
446
+ owner: this
447
+ });
448
+ if (provider) this.persistFavorites = true;
449
+ }
450
+ }
451
+
452
+ @action
453
+ private setValueInternal(rawValue: FilterLike, updateSelectValueAndBind: boolean) {
454
+ const {maxTags} = this;
455
+ try {
456
+ const value = parseFilter(rawValue);
457
+ if (this.value?.equals(value)) return;
458
+
459
+ // 1) Ensure FilterChooser can handle the requested value.
460
+ const isValid = this.validateFilter(value),
461
+ displayFilters = isValid ? this.toDisplayFilters(value) : null;
462
+
463
+ this.unsupportedFilter = !isValid || (maxTags && displayFilters.length > maxTags);
464
+ if (this.unsupportedFilter) {
465
+ this.value = null;
466
+ this.selectOptions = null;
467
+ this.selectValue = null;
468
+ return;
469
+ }
470
+
471
+ // 2) Main path - filter has been validated as supported, set internal value.
472
+ this.logDebug('Setting value', value);
473
+ this.value = value as FilterChooserFilter;
474
+
475
+ // 3) Set props on select input needed to display
476
+ // Build list of options, used for displaying tags. We combine the needed
477
+ // options for the current filter tags with any previous ones to ensure
478
+ // tags are rendered correctly throughout the transition.
479
+ const newOptions = displayFilters.map(f => this.createFilterOption(f)),
480
+ previousOptions = this.selectOptions ?? [],
481
+ options = uniqBy([...newOptions, ...previousOptions], 'value');
482
+
483
+ this.selectOptions = !isEmpty(options) ? options : null;
484
+
485
+ if (updateSelectValueAndBind) this.updateSelectValueAndBind(displayFilters);
486
+ } catch (e) {
487
+ this.logError('Failed to set value', e);
488
+ this.value = null;
489
+ this.selectOptions = null;
490
+ this.selectValue = null;
491
+ this.unsupportedFilter = true;
492
+ }
493
+ }
494
+
495
+ /**
496
+ * Update the select value and bind the filter to the bound model. Runs asynchronously after
497
+ * selectOptions are set to ensure the component is ready to render the tags correctly.
498
+ */
499
+ private updateSelectValueAndBind(displayFilters = this.toDisplayFilters(this.value)) {
500
+ const {bind, value} = this;
501
+ wait()
502
+ .thenAction(() => {
503
+ // No-op if we've already re-entered this method by the time this async routine runs.
504
+ if (this.value !== value) {
505
+ return;
506
+ }
507
+
508
+ this.selectValue = sortBy(
509
+ displayFilters.map(f => JSON.stringify(f)),
510
+ f => {
511
+ const idx = this.selectValue?.indexOf(f);
512
+ return isFinite(idx) && idx > -1 ? idx : displayFilters.length;
513
+ }
514
+ );
515
+
516
+ // Round-trip value to bound filter
517
+ if (bind) {
518
+ const filter = withFilterByTypes(bind.filter, value, [
519
+ 'FieldFilter',
520
+ 'CompoundFilter'
521
+ ]);
522
+ bind.setFilter(filter);
523
+ }
524
+ })
525
+ .linkTo(this.filterTask);
526
+ }
510
527
  }
511
528
 
512
529
  interface FilterChooserPersistOptions extends PersistOptions {
513
- /** True (default) to save value to state. */
514
- persistValue?: boolean;
530
+ /** True (default) to include value or provide value-specific PersistOptions. */
531
+ persistValue?: boolean | PersistOptions;
515
532
 
516
- /** True (default) to include favorites. */
517
- persistFavorites?: boolean;
533
+ /** True (default) to include favorites or provide favorites-specific PersistOptions. */
534
+ persistFavorites?: boolean | PersistOptions;
518
535
  }
519
536
 
520
537
  /** A variant of {@link Filter} that excludes FunctionFilter (unsupported by FilterChooser). */
521
538
  export type FilterChooserFilter = CompoundFilter | FieldFilter;
539
+ export type FilterChooserFilterSpec = CompoundFilterSpec | FieldFilterSpec;
522
540
 
523
541
  /** A variant of {@link FilterLike} that excludes FunctionFilters and FilterTestFn. */
524
542
  export type FilterChooserFilterLike =
525
- | Filter
526
- | CompoundFilterSpec
527
- | FieldFilterSpec
543
+ | FilterChooserFilter
544
+ | FilterChooserFilterSpec
528
545
  | FilterChooserFilterLike[];
package/cmp/grid/Grid.ts CHANGED
@@ -681,16 +681,8 @@ export class GridLocalModel extends HoistModel {
681
681
  }
682
682
 
683
683
  if (model.autosizeOptions.mode === 'managed') {
684
- // If sizingMode different to autosizeState, autosize all columns...
685
- if (model.autosizeState.sizingMode !== model.sizingMode) {
686
- model.autosizeAsync();
687
- } else {
688
- // ...otherwise, only autosize columns that are not manually sized
689
- const columns = model.columnState
690
- .filter(it => !it.manuallySized)
691
- .map(it => it.colId);
692
- model.autosizeAsync({columns});
693
- }
684
+ const columns = model.columnState.filter(it => !it.manuallySized).map(it => it.colId);
685
+ model.autosizeAsync({columns});
694
686
  }
695
687
 
696
688
  model.noteAgExpandStateChange();
@@ -95,10 +95,9 @@ import {ReactNode} from 'react';
95
95
  import {GridAutosizeOptions} from './GridAutosizeOptions';
96
96
  import {GridContextMenuSpec} from './GridContextMenu';
97
97
  import {GridSorter, GridSorterLike} from './GridSorter';
98
- import {GridPersistenceModel} from './impl/GridPersistenceModel';
98
+ import {initPersist} from './impl/InitPersist';
99
99
  import {managedRenderer} from './impl/Utils';
100
100
  import {
101
- AutosizeState,
102
101
  ColChooserConfig,
103
102
  ColumnState,
104
103
  GridModelPersistOptions,
@@ -405,10 +404,13 @@ export class GridModel extends HoistModel {
405
404
  @observable.ref columns: Array<ColumnGroup | Column> = [];
406
405
  @observable.ref columnState: ColumnState[] = [];
407
406
  @observable.ref expandState: any = {};
408
- @observable.ref autosizeState: AutosizeState = {};
409
407
  @observable.ref sortBy: GridSorter[] = [];
410
408
  @observable.ref groupBy: string[] = null;
411
409
 
410
+ get persistableColumnState(): ColumnState[] {
411
+ return this.cleanColumnState(this.columnState);
412
+ }
413
+
412
414
  @bindable showSummary: boolean | VSide = false;
413
415
  @bindable.ref emptyText: ReactNode;
414
416
  @bindable treeStyle: TreeStyle;
@@ -446,7 +448,6 @@ export class GridModel extends HoistModel {
446
448
  ];
447
449
 
448
450
  private _defaultState; // initial state provided to ctor - powers restoreDefaults().
449
- @managed persistenceModel: GridPersistenceModel;
450
451
 
451
452
  /**
452
453
  * Is autosizing enabled on this grid?
@@ -599,7 +600,7 @@ export class GridModel extends HoistModel {
599
600
  this.colChooserModel = this.parseChooserModel(colChooserModel);
600
601
  this.selModel = this.parseSelModel(selModel);
601
602
  this.filterModel = this.parseFilterModel(filterModel);
602
- this.persistenceModel = persistWith ? new GridPersistenceModel(this, persistWith) : null;
603
+ if (persistWith) initPersist(this, persistWith);
603
604
  this.experimental = this.parseExperimental(experimental);
604
605
  this.onKeyDown = onKeyDown;
605
606
  this.onRowClicked = onRowClicked;
@@ -649,7 +650,6 @@ export class GridModel extends HoistModel {
649
650
  this.setGroupBy(groupBy);
650
651
 
651
652
  this.filterModel?.clear();
652
- this.persistenceModel?.clear();
653
653
 
654
654
  if (this.autosizeOptions.mode === 'managed') {
655
655
  await this.autosizeAsync();
@@ -1112,13 +1112,6 @@ export class GridModel extends HoistModel {
1112
1112
  }
1113
1113
  }
1114
1114
 
1115
- @action
1116
- setAutosizeState(autosizeState) {
1117
- if (!equal(this.autosizeState, autosizeState)) {
1118
- this.autosizeState = deepFreeze(autosizeState);
1119
- }
1120
- }
1121
-
1122
1115
  noteColumnManuallySized(colId, width) {
1123
1116
  const col = this.findColumn(this.columns, colId);
1124
1117
  if (!width || !col || col.flex) return;
@@ -1126,12 +1119,6 @@ export class GridModel extends HoistModel {
1126
1119
  this.applyColumnStateChanges(colStateChanges);
1127
1120
  }
1128
1121
 
1129
- noteColumnsAutosized(colIds) {
1130
- const colStateChanges = castArray(colIds).map(colId => ({colId, manuallySized: false}));
1131
- this.applyColumnStateChanges(colStateChanges);
1132
- this.setAutosizeState({sizingMode: this.sizingMode});
1133
- }
1134
-
1135
1122
  /**
1136
1123
  * This method will update the current column definition if it has changed.
1137
1124
  * Throws an exception if any of the columns provided in colStateChanges are not
@@ -1291,11 +1278,11 @@ export class GridModel extends HoistModel {
1291
1278
  * columns are also ignored unless {@link GridAutosizeOptions.includeHiddenColumns} has been
1292
1279
  * set to true.
1293
1280
  *
1294
- * @param options - optional overrides of this model's configured {@link autosizeOptions}.
1281
+ * @param overrideOpts - optional overrides of this model's {@link GridAutosizeOptions}.
1295
1282
  */
1296
1283
  @logWithDebug
1297
- async autosizeAsync(options: GridAutosizeOptions = {}) {
1298
- options = {...this.autosizeOptions, ...options};
1284
+ async autosizeAsync(overrideOpts: Omit<GridAutosizeOptions, 'mode'> = {}) {
1285
+ const options = {...this.autosizeOptions, ...overrideOpts};
1299
1286
 
1300
1287
  if (options.mode === 'disabled') {
1301
1288
  return;
@@ -1305,16 +1292,16 @@ export class GridModel extends HoistModel {
1305
1292
  const {columns} = options;
1306
1293
  if (columns) options.fillMode = 'none'; // Fill makes sense only for the entire set.
1307
1294
 
1308
- let colIds,
1309
- includeColFn = col => true;
1295
+ let colIds: string[],
1296
+ includeColFn = (_: Column) => true;
1310
1297
  if (isFunction(columns)) {
1311
- includeColFn = columns as (col) => boolean;
1298
+ includeColFn = columns as (col: Column) => boolean;
1312
1299
  colIds = this.columnState.map(it => it.colId);
1313
1300
  } else {
1314
- colIds = columns ?? this.columnState.map(it => it.colId);
1301
+ colIds = columns ? castArray(columns) : this.columnState.map(it => it.colId);
1315
1302
  }
1316
1303
 
1317
- colIds = castArray(colIds).filter(id => {
1304
+ colIds = colIds.filter(id => {
1318
1305
  if (!options.includeHiddenColumns && !this.isColumnVisible(id)) return false;
1319
1306
  const col = this.getColumn(id);
1320
1307
  return col && col.autosizable && !col.flex && includeColFn(col);
@@ -1327,7 +1314,7 @@ export class GridModel extends HoistModel {
1327
1314
 
1328
1315
  /**
1329
1316
  * Begin an inline editing session.
1330
- * @param recOrId - StoreRecord/ID to edit. If unspecified, the first selected StoreRecord
1317
+ * @param record - StoreRecord/ID to edit. If unspecified, the first selected StoreRecord
1331
1318
  * will be used, if any, or the first overall StoreRecord in the grid.
1332
1319
  * @param colId - ID of column on which to start editing. If unspecified, the first
1333
1320
  * editable column will be used.
@@ -1472,7 +1459,7 @@ export class GridModel extends HoistModel {
1472
1459
  return new Column(config, this);
1473
1460
  }
1474
1461
 
1475
- private async autosizeColsInternalAsync(colIds, options) {
1462
+ private async autosizeColsInternalAsync(colIds: string[], options: GridAutosizeOptions) {
1476
1463
  await this.whenReadyAsync();
1477
1464
  if (!this.isReady) return;
1478
1465
 
@@ -1485,7 +1472,6 @@ export class GridModel extends HoistModel {
1485
1472
 
1486
1473
  try {
1487
1474
  await XH.gridAutosizeService.autosizeAsync(this, colIds, options);
1488
- this.noteColumnsAutosized(colIds);
1489
1475
  } finally {
1490
1476
  if (showMask) {
1491
1477
  await wait();
@@ -1616,7 +1602,8 @@ export class GridModel extends HoistModel {
1616
1602
  // Remove the width from any non-resizable column - we don't want to track those widths as
1617
1603
  // they are set programmatically (e.g. fixed / action columns), and saved state should not
1618
1604
  // conflict with any code-level updates to their widths.
1619
- if (!col.resizable) state = omit(state, 'width');
1605
+ if (!col.resizable || !state.manuallySized) state = omit(state, 'width');
1606
+ state = {...state, manuallySized: state.manuallySized ?? false};
1620
1607
 
1621
1608
  // Remove all metadata other than the id and the hidden state from hidden columns, to save
1622
1609
  // on space when storing user configs with large amounts of hidden fields.
package/cmp/grid/Types.ts CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  import {IRowNode} from '@xh/hoist/kit/ag-grid';
9
9
  import {GridFilterFieldSpecConfig} from '@xh/hoist/cmp/grid/filter/GridFilterFieldSpec';
10
- import {HSide, PersistOptions, SizingMode, Some} from '@xh/hoist/core';
10
+ import {HSide, PersistOptions, Some} from '@xh/hoist/core';
11
11
  import {Store, StoreRecord, View} from '@xh/hoist/data';
12
12
  import {ReactElement, ReactNode} from 'react';
13
13
  import {Column} from './columns/Column';
@@ -35,11 +35,6 @@ export interface ColumnState {
35
35
  pinned?: HSide;
36
36
  }
37
37
 
38
- export interface AutosizeState {
39
- /** Sizing mode used last time the columns were autosized. */
40
- sizingMode?: SizingMode;
41
- }
42
-
43
38
  /**
44
39
  * Comparator for custom grid group sorting, provided to GridModel.
45
40
  * @param groupAVal - first group value to be compared.
@@ -79,21 +74,12 @@ export type RowClassFn = (record: StoreRecord) => Some<string>;
79
74
  export type RowClassRuleFn = (agParams: RowClassParams) => boolean;
80
75
 
81
76
  export interface GridModelPersistOptions extends PersistOptions {
82
- /** True to include column information (default true) */
83
- persistColumns?: boolean;
84
- /** True to include grouping information (default true) */
85
- persistGrouping?: boolean;
86
- /** True to include sorting information (default true) */
87
- persistSort?: boolean;
88
-
89
- /**
90
- * Key to be used to identify location of legacy grid state from LocalStorage.
91
- * This key will identify the pre-v35 location for grid state, and will be used
92
- * as an initial source of grid state after an upgrade to v35.0.0 or greater.
93
- * Defaults to the new value of 'key'. If no legacy state is available at this
94
- * location, the key is ignored.
95
- */
96
- legacyStateKey?: string;
77
+ /** True (default) to include column state or provide column-specific PersistOptions. */
78
+ persistColumns?: boolean | PersistOptions;
79
+ /** True (default) to include grouping state or provide grouping-specific PersistOptions. */
80
+ persistGrouping?: boolean | PersistOptions;
81
+ /** True (default) to include sort state or provide sort-specific PersistOptions. */
82
+ persistSort?: boolean | PersistOptions;
97
83
  }
98
84
 
99
85
  export interface GridFilterModelConfig {
@@ -478,7 +478,6 @@ export class Column {
478
478
  actions?: Array<RecordActionSpec | RecordAction>;
479
479
  actionsShowOnHoverOnly?: boolean;
480
480
  fieldSpec: FieldSpec;
481
- manuallySized: boolean;
482
481
  omit: Thunkable<boolean>;
483
482
 
484
483
  gridModel: GridModel;