@xh/hoist 75.0.0-SNAPSHOT.1752854198386 → 75.0.0-SNAPSHOT.1752860174147

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 CHANGED
@@ -4,6 +4,12 @@
4
4
 
5
5
  ### 🎁 New Features
6
6
 
7
+ * New property `GridModel.expandToLevel` governs the expansion state of tree and grouped grids.
8
+ Replaces the use of the `agOptions.groupDefaultExpanded` on the component.
9
+ * Most recently chosen level is persistable with other grid state.
10
+ * Default grid context menu has a menu item to trigger bulk expand/collapse to particular depth
11
+ level for multi-level (e.g. depth > 2) grids. To enable, implement the `GridModel.levelLabels`
12
+ property for your grid.
7
13
  * Added new `GroupingChooserModel.sortDimensions` config - can be set to false to respect the order
8
14
  in which dimensions are provided to the model.
9
15
 
@@ -429,6 +429,8 @@ export class ActivityTrackingModel extends HoistModel implements ActivityDetailP
429
429
  exportOptions: {filename: exportFilename('activity-summary')},
430
430
  emptyText: 'No activity reported...',
431
431
  sortBy: ['cubeLabel'],
432
+ expandToLevel: 1,
433
+ levelLabels: () => ['Total', ...this.groupingChooserModel.getValueDisplayNames()],
432
434
  columns: [
433
435
  {
434
436
  field: {
@@ -165,10 +165,7 @@ const aggregateView = hoistCmp.factory<ActivityTrackingModel>(({model}) => {
165
165
  ]
166
166
  }),
167
167
  items: [
168
- grid({
169
- flex: 1,
170
- agOptions: {groupDefaultExpanded: 1}
171
- }),
168
+ grid({flex: 1}),
172
169
  div({
173
170
  className: 'xh-admin-activity-panel__max-rows-alert',
174
171
  items: [
@@ -43,6 +43,7 @@ export class ClusterObjectsModel extends HoistModel {
43
43
  @managed gridModel = new GridModel({
44
44
  selModel: 'multiple',
45
45
  treeMode: true,
46
+ expandToLevel: 2,
46
47
  autosizeOptions: {mode: 'managed', includeCollapsedChildren: true},
47
48
  enableExport: true,
48
49
  exportOptions: {filename: exportFilenameWithDate('cluster-objects'), columns: 'ALL'},
@@ -33,7 +33,7 @@ export const clusterObjectsPanel = hoistCmp.factory({
33
33
  defaultSize: 475,
34
34
  collapsible: false
35
35
  },
36
- item: grid({agOptions: {groupDefaultExpanded: 2}})
36
+ item: grid()
37
37
  }),
38
38
  detailPanel()
39
39
  ),
@@ -12,7 +12,7 @@ export type GridContextMenuItemLike = RecordActionLike | GridContextMenuToken |
12
12
  * `autosizeColumns` - autosize columns to fit their contents.
13
13
  * `copyCell` - copy cell value to clipboard.
14
14
  * `colChooser` - display column chooser for a grid.
15
- * `expandCollapseAll` - expand/collapse all parent rows on grouped or tree grid.
15
+ * `expandCollapse` - expand/collapse parent rows on grouped or tree grid.
16
16
  * `export` - export grid data to excel via Hoist's server-side export capabilities.
17
17
  * `exportExcel` - alias for `export`.
18
18
  * `exportCsv` - export to CSV via Hoist's server-side export capabilities.
@@ -21,7 +21,7 @@ export type GridContextMenuItemLike = RecordActionLike | GridContextMenuToken |
21
21
  * `restoreDefaults` - restore column, sorting, and grouping configs and clear any
22
22
  * persistent grid state. See {@link GridModel.restoreDefaults}
23
23
  */
24
- export type GridContextMenuToken = 'autosizeColumns' | 'copyCell' | 'colChooser' | 'expandCollapseAll' | 'export' | 'exportExcel' | 'exportCsv' | 'exportLocal' | 'filter' | 'restoreDefaults';
24
+ export type GridContextMenuToken = 'autosizeColumns' | 'copyCell' | 'colChooser' | 'expandCollapseAll' | 'expandCollapse' | 'export' | 'exportExcel' | 'exportCsv' | 'exportLocal' | 'filter' | 'restoreDefaults';
25
25
  /**
26
26
  * Specification for a GridContextMenu. Either a list of items, or a function to produce one.
27
27
  */
@@ -2,7 +2,7 @@ import { CellClickedEvent, CellContextMenuEvent, CellDoubleClickedEvent, ColumnS
2
2
  import { AgGridModel } from '@xh/hoist/cmp/ag-grid';
3
3
  import { Column, ColumnGroup, ColumnGroupSpec, ColumnSpec, GridAutosizeMode, GridFilterModelConfig, GridGroupSortFn, TreeStyle } from '@xh/hoist/cmp/grid';
4
4
  import { GridFilterModel } from '@xh/hoist/cmp/grid/filter/GridFilterModel';
5
- import { Awaitable, HoistModel, HSide, PlainObject, SizingMode, Some, TaskObserver, VSide } from '@xh/hoist/core';
5
+ import { Awaitable, HoistModel, HSide, PlainObject, SizingMode, Some, TaskObserver, Thunkable, VSide } from '@xh/hoist/core';
6
6
  import { Store, StoreConfig, StoreRecord, StoreRecordId, StoreRecordOrId, StoreSelectionConfig, StoreSelectionModel, StoreTransaction } from '@xh/hoist/data';
7
7
  import { ExportOptions } from '@xh/hoist/svc/GridExportService';
8
8
  import { ReactNode } from 'react';
@@ -54,6 +54,11 @@ export interface GridConfig {
54
54
  sortBy?: Some<GridSorterLike>;
55
55
  /** Column ID(s) by which to do full-width grouping. */
56
56
  groupBy?: Some<string>;
57
+ /**
58
+ * Depth level to expand to on initial load. 0 = all collapsed, 1 = top level expanded, etc.
59
+ * Defaults to 0 for tree grids (i.e. treeMode = true), 1 for standard grouped grids.
60
+ */
61
+ expandToLevel?: number;
57
62
  /** True (default) to show a count of group member rows within each full-width group row. */
58
63
  showGroupRowCounts?: boolean;
59
64
  /** Size of text in grid. If undefined, will default and bind to `XH.sizingMode`. */
@@ -134,6 +139,12 @@ export interface GridConfig {
134
139
  * triggered via a long press (aka tap and hold) on mobile devices.
135
140
  */
136
141
  onCellContextMenu?: (e: CellContextMenuEvent) => void;
142
+ /**
143
+ * Array of labels (or a function returning one) that describes the individual depth
144
+ * levels in a tree or grouped grid. If provided, will be used to construct expand/collapse
145
+ * options in the default context menu.
146
+ */
147
+ levelLabels?: Thunkable<string[]>;
137
148
  /**
138
149
  * Number of clicks required to expand / collapse a parent row in a tree grid. Defaults
139
150
  * to 2 for desktop, 1 for mobile. Any other value prevents clicks on row body from
@@ -240,6 +251,7 @@ export declare class GridModel extends HoistModel {
240
251
  onCellClicked: (e: CellClickedEvent) => void;
241
252
  onCellDoubleClicked: (e: CellDoubleClickedEvent) => void;
242
253
  onCellContextMenu: (e: CellContextMenuEvent) => void;
254
+ levelLabels: Thunkable<string[]>;
243
255
  appData: PlainObject;
244
256
  filterModel: GridFilterModel;
245
257
  agGridModel: AgGridModel;
@@ -248,6 +260,7 @@ export declare class GridModel extends HoistModel {
248
260
  expandState: any;
249
261
  sortBy: GridSorter[];
250
262
  groupBy: string[];
263
+ expandToLevel: number;
251
264
  get persistableColumnState(): ColumnState[];
252
265
  showSummary: boolean | VSide;
253
266
  emptyText: ReactNode;
@@ -271,6 +284,7 @@ export declare class GridModel extends HoistModel {
271
284
  * To disable autosizing, set autosizeOptions.mode to GridAutosizeMode.DISABLED.
272
285
  */
273
286
  get autosizeEnabled(): boolean;
287
+ get maxDepth(): number;
274
288
  /** Tracks execution of filtering operations.*/
275
289
  filterTask: TaskObserver;
276
290
  /** Tracks execution of autosize operations. */
@@ -319,10 +333,15 @@ export declare class GridModel extends HoistModel {
319
333
  * will not change the selection if there is already a selection, which is what applications
320
334
  * typically want to do when loading/reloading a grid.
321
335
  *
322
- * @param opts - set key 'ensureVisible' to true to make selection visible if it is within a
323
- * collapsed node or outside of the visible scroll window. Default true.
336
+ * @param opts -
337
+ * expandParentGroups - set to true to expand nodes to allow selection when the
338
+ * first selectable node is in a collapsed group. Default true.
339
+ * ensureVisible - set to to true to scroll to the selected row if it is outside of the
340
+ * visible scroll window. Default true.
341
+ *
324
342
  */
325
343
  selectFirstAsync(opts?: {
344
+ expandParentGroups?: boolean;
326
345
  ensureVisible?: boolean;
327
346
  }): Promise<void>;
328
347
  /**
@@ -414,6 +433,8 @@ export declare class GridModel extends HoistModel {
414
433
  expandAll(): void;
415
434
  /** Collapse all parent rows in grouped or tree grid. */
416
435
  collapseAll(): void;
436
+ /** Expand all parent rows in grouped or tree grid to the specified level. */
437
+ setExpandToLevel(level: number): void;
417
438
  /**
418
439
  * Sort this grid.
419
440
  * This method is a no-op if provided any sorters without a corresponding column.
@@ -53,6 +53,8 @@ export interface GridModelPersistOptions extends PersistOptions {
53
53
  persistGrouping?: boolean | PersistOptions;
54
54
  /** True (default) to include sort state or provide sort-specific PersistOptions. */
55
55
  persistSort?: boolean | PersistOptions;
56
+ /** True (default) to include expanded level state or provide expanded level-specific PersistOptions. */
57
+ persistExpandToLevel?: boolean | PersistOptions;
56
58
  }
57
59
  export interface GridFilterModelConfig {
58
60
  /**
@@ -4,4 +4,4 @@ import { GridModelPersistOptions } from '../Types';
4
4
  * Initialize persistence for a {@link GridModel} by applying its `persistWith` config.
5
5
  * @internal
6
6
  */
7
- export declare function initPersist(gridModel: GridModel, { persistColumns, persistGrouping, persistSort, path, ...rootPersistWith }: GridModelPersistOptions): void;
7
+ export declare function initPersist(gridModel: GridModel, { persistColumns, persistGrouping, persistSort, persistExpandToLevel, path, ...rootPersistWith }: GridModelPersistOptions): void;
@@ -78,6 +78,7 @@ export declare class GroupingChooserModel extends HoistModel {
78
78
  commitPendingValueAndClose(): void;
79
79
  validateValue(value: string[]): boolean;
80
80
  getValueLabel(value: string[]): string;
81
+ getValueDisplayNames(): string[];
81
82
  getDimDisplayName(dimName: string): string;
82
83
  onDragEnd(result: any): void;
83
84
  get favoritesOptions(): {
@@ -1,6 +1,6 @@
1
1
  import { CellClickedEvent, CellContextMenuEvent, CellDoubleClickedEvent, RowClickedEvent, RowDoubleClickedEvent } from '@ag-grid-community/core';
2
2
  import { ColumnRenderer, ColumnSpec, GridContextMenuSpec, GridGroupSortFn, GridModel, GridSorter, GridSorterLike, GroupRowRenderer, RowClassFn, RowClassRuleFn, TreeStyle } from '@xh/hoist/cmp/grid';
3
- import { Awaitable, HoistModel, LoadSpec, PlainObject, Some, VSide } from '@xh/hoist/core';
3
+ import { Awaitable, HoistModel, LoadSpec, PlainObject, Some, Thunkable, VSide } from '@xh/hoist/core';
4
4
  import { RecordAction, Store, StoreConfig, StoreRecordOrId, StoreSelectionConfig, StoreSelectionModel, StoreTransaction } from '@xh/hoist/data';
5
5
  import { ReactNode } from 'react';
6
6
  import { ZoneMapperConfig, ZoneMapperModel } from './impl/ZoneMapperModel';
@@ -72,6 +72,8 @@ export interface ZoneGridConfig {
72
72
  sortBy?: GridSorterLike;
73
73
  /** Column ID(s) by which to do full-width grouping. */
74
74
  groupBy?: Some<string>;
75
+ /** Group level to expand to on initial load. 0 = all collapsed, 1 = only top level expanded. */
76
+ expandToLevel?: number;
75
77
  /** True (default) to show a count of group member rows within each full-width group row. */
76
78
  showGroupRowCounts?: boolean;
77
79
  /** True to highlight the currently hovered row. */
@@ -138,6 +140,12 @@ export interface ZoneGridConfig {
138
140
  * triggered via a long press (aka tap and hold) on mobile devices.
139
141
  */
140
142
  onCellContextMenu?: (e: CellContextMenuEvent) => void;
143
+ /**
144
+ * Array of labels (or a function returning one) that describes the individual depth
145
+ * levels in a tree or grouped grid. If provided, will be used to construct expand/collapse
146
+ * options in the default context menu.
147
+ */
148
+ levelLabels?: Thunkable<string[]>;
141
149
  /**
142
150
  * Number of clicks required to expand / collapse a parent row in a tree grid. Defaults
143
151
  * to 2 for desktop, 1 for mobile. Any other value prevents clicks on row body from
@@ -18,7 +18,7 @@ export interface RecordActionSpec extends TestSupportProps {
18
18
  /** Function called on action execution. */
19
19
  actionFn?: (data: ActionFnData) => void;
20
20
  /** Function called prior to showing this item. */
21
- displayFn?: (data: DisplayFnData) => PlainObject;
21
+ displayFn?: (data: ActionFnData) => RecordActionSpec;
22
22
  /** Sub-actions for this action. */
23
23
  items?: RecordActionLike[];
24
24
  /** True to disable this item. */
@@ -79,7 +79,7 @@ export declare class RecordAction {
79
79
  className: string;
80
80
  tooltip: string;
81
81
  actionFn: (data: ActionFnData) => void;
82
- displayFn: (data: DisplayFnData) => PlainObject;
82
+ displayFn: (data: ActionFnData) => PlainObject;
83
83
  items: Array<RecordAction | string>;
84
84
  disabled: boolean;
85
85
  hidden: boolean;
@@ -108,8 +108,3 @@ export declare class RecordAction {
108
108
  call({ record, selectedRecords, gridModel, column, ...rest }: ActionFnData): void;
109
109
  private meetsRecordRequirement;
110
110
  }
111
- interface DisplayFnData extends ActionFnData {
112
- /** Default display config for the action */
113
- defaultConfig?: PlainObject;
114
- }
115
- export {};
package/cmp/grid/Grid.ts CHANGED
@@ -246,7 +246,7 @@ export class GridLocalModel extends HoistModel {
246
246
  navigateToNextCell: this.navigateToNextCell,
247
247
  processCellForClipboard: this.processCellForClipboard,
248
248
  initialGroupOrderComparator: model.groupSortFn ? this.groupSortComparator : undefined,
249
- groupDefaultExpanded: 1,
249
+ groupDefaultExpanded: model.expandToLevel,
250
250
  groupDisplayType: 'groupRows',
251
251
  groupRowRendererParams: {
252
252
  innerRenderer: model.groupRowRenderer,
@@ -285,7 +285,6 @@ export class GridLocalModel extends HoistModel {
285
285
  if (model.treeMode) {
286
286
  ret = {
287
287
  ...ret,
288
- groupDefaultExpanded: 0,
289
288
  groupDisplayType: 'custom',
290
289
  treeData: true,
291
290
  getDataPath: this.getDataPath
@@ -20,7 +20,7 @@ export type GridContextMenuItemLike = RecordActionLike | GridContextMenuToken |
20
20
  * `autosizeColumns` - autosize columns to fit their contents.
21
21
  * `copyCell` - copy cell value to clipboard.
22
22
  * `colChooser` - display column chooser for a grid.
23
- * `expandCollapseAll` - expand/collapse all parent rows on grouped or tree grid.
23
+ * `expandCollapse` - expand/collapse parent rows on grouped or tree grid.
24
24
  * `export` - export grid data to excel via Hoist's server-side export capabilities.
25
25
  * `exportExcel` - alias for `export`.
26
26
  * `exportCsv` - export to CSV via Hoist's server-side export capabilities.
@@ -34,6 +34,7 @@ export type GridContextMenuToken =
34
34
  | 'copyCell'
35
35
  | 'colChooser'
36
36
  | 'expandCollapseAll'
37
+ | 'expandCollapse'
37
38
  | 'export'
38
39
  | 'exportExcel'
39
40
  | 'exportCsv'
@@ -36,6 +36,7 @@ import {
36
36
  SizingMode,
37
37
  Some,
38
38
  TaskObserver,
39
+ Thunkable,
39
40
  VSide,
40
41
  XH
41
42
  } from '@xh/hoist/core';
@@ -167,6 +168,12 @@ export interface GridConfig {
167
168
  /** Column ID(s) by which to do full-width grouping. */
168
169
  groupBy?: Some<string>;
169
170
 
171
+ /**
172
+ * Depth level to expand to on initial load. 0 = all collapsed, 1 = top level expanded, etc.
173
+ * Defaults to 0 for tree grids (i.e. treeMode = true), 1 for standard grouped grids.
174
+ */
175
+ expandToLevel?: number;
176
+
170
177
  /** True (default) to show a count of group member rows within each full-width group row. */
171
178
  showGroupRowCounts?: boolean;
172
179
 
@@ -272,6 +279,13 @@ export interface GridConfig {
272
279
  */
273
280
  onCellContextMenu?: (e: CellContextMenuEvent) => void;
274
281
 
282
+ /**
283
+ * Array of labels (or a function returning one) that describes the individual depth
284
+ * levels in a tree or grouped grid. If provided, will be used to construct expand/collapse
285
+ * options in the default context menu.
286
+ */
287
+ levelLabels?: Thunkable<string[]>;
288
+
275
289
  /**
276
290
  * Number of clicks required to expand / collapse a parent row in a tree grid. Defaults
277
291
  * to 2 for desktop, 1 for mobile. Any other value prevents clicks on row body from
@@ -397,6 +411,7 @@ export class GridModel extends HoistModel {
397
411
  onCellClicked: (e: CellClickedEvent) => void;
398
412
  onCellDoubleClicked: (e: CellDoubleClickedEvent) => void;
399
413
  onCellContextMenu: (e: CellContextMenuEvent) => void;
414
+ levelLabels: Thunkable<string[]>;
400
415
  appData: PlainObject;
401
416
 
402
417
  @managed filterModel: GridFilterModel;
@@ -410,6 +425,7 @@ export class GridModel extends HoistModel {
410
425
  @observable.ref expandState: any = {};
411
426
  @observable.ref sortBy: GridSorter[] = [];
412
427
  @observable.ref groupBy: string[] = null;
428
+ @observable expandToLevel: number = 0;
413
429
 
414
430
  get persistableColumnState(): ColumnState[] {
415
431
  return this.cleanColumnState(this.columnState);
@@ -440,7 +456,7 @@ export class GridModel extends HoistModel {
440
456
  'copyWithHeaders',
441
457
  'copyCell',
442
458
  '-',
443
- 'expandCollapseAll',
459
+ 'expandCollapse',
444
460
  '-',
445
461
  'exportExcel',
446
462
  'exportCsv',
@@ -461,6 +477,11 @@ export class GridModel extends HoistModel {
461
477
  return this.autosizeOptions.mode !== 'disabled';
462
478
  }
463
479
 
480
+ get maxDepth(): number {
481
+ const {groupBy, store, treeMode} = this;
482
+ return treeMode ? store.maxDepth : groupBy ? groupBy.length : 0;
483
+ }
484
+
464
485
  /** Tracks execution of filtering operations.*/
465
486
  @managed filterTask = TaskObserver.trackAll();
466
487
 
@@ -518,6 +539,8 @@ export class GridModel extends HoistModel {
518
539
  restoreDefaultsWarning = GridModel.DEFAULT_RESTORE_DEFAULTS_WARNING,
519
540
  fullRowEditing = false,
520
541
  clicksToEdit = 2,
542
+ expandToLevel = treeMode ? 0 : 1,
543
+ levelLabels,
521
544
  highlightRowOnClick = XH.isMobileApp,
522
545
  experimental,
523
546
  appData,
@@ -532,7 +555,6 @@ export class GridModel extends HoistModel {
532
555
  this.treeMode = treeMode;
533
556
  this.treeStyle = treeStyle;
534
557
  this.showSummary = showSummary;
535
-
536
558
  this.emptyText = emptyText;
537
559
  this.hideEmptyTextBeforeLoad = hideEmptyTextBeforeLoad;
538
560
  this.headerMenuDisplay = headerMenuDisplay;
@@ -565,6 +587,8 @@ export class GridModel extends HoistModel {
565
587
  this.clicksToExpand = clicksToExpand;
566
588
  this.clicksToEdit = clicksToEdit;
567
589
  this.highlightRowOnClick = highlightRowOnClick;
590
+ this.expandToLevel = expandToLevel;
591
+ this.levelLabels = levelLabels;
568
592
 
569
593
  throwIf(
570
594
  autosizeOptions.fillMode &&
@@ -618,6 +642,13 @@ export class GridModel extends HoistModel {
618
642
  debounce: 500
619
643
  });
620
644
 
645
+ this.addReaction({
646
+ track: () => [this.expandToLevel, this.isReady],
647
+ run: () => {
648
+ this.agApi?.setGridOption('groupDefaultExpanded', this.expandToLevel);
649
+ }
650
+ });
651
+
621
652
  if (!isEmpty(rest)) {
622
653
  const keys = keysIn(rest);
623
654
  throw XH.exception(
@@ -725,21 +756,29 @@ export class GridModel extends HoistModel {
725
756
  * will not change the selection if there is already a selection, which is what applications
726
757
  * typically want to do when loading/reloading a grid.
727
758
  *
728
- * @param opts - set key 'ensureVisible' to true to make selection visible if it is within a
729
- * collapsed node or outside of the visible scroll window. Default true.
759
+ * @param opts -
760
+ * expandParentGroups - set to true to expand nodes to allow selection when the
761
+ * first selectable node is in a collapsed group. Default true.
762
+ * ensureVisible - set to to true to scroll to the selected row if it is outside of the
763
+ * visible scroll window. Default true.
764
+ *
730
765
  */
731
- async selectFirstAsync(opts?: {ensureVisible?: boolean}) {
732
- const {ensureVisible = true} = opts ?? {};
766
+ async selectFirstAsync(opts?: {expandParentGroups?: boolean; ensureVisible?: boolean}) {
767
+ const {expandParentGroups = true, ensureVisible = true} = opts ?? {};
733
768
  await this.whenReadyAsync();
734
769
  if (!this.isReady) return;
735
770
 
736
- // Get first displayed row with data - i.e. backed by a record, not a full-width group row.
771
+ // Get first visible row with data - i.e. backed by a record, not a full-width group row.
737
772
  const {selModel} = this,
738
- id = this.agGridModel.getFirstSelectableRowNode()?.data.id;
739
-
740
- if (id != null) {
741
- selModel.select(id);
742
- if (ensureVisible) await this.ensureSelectionVisibleAsync();
773
+ row = this.agGridModel.getFirstSelectableRowNode();
774
+
775
+ // If displayed select it -- we never want to auto-expand parent rows
776
+ if (row && (expandParentGroups || row.displayed)) {
777
+ const id = row.data.id;
778
+ if (id != null) {
779
+ selModel.select(id);
780
+ if (ensureVisible) await this.ensureSelectionVisibleAsync();
781
+ }
743
782
  }
744
783
  }
745
784
 
@@ -749,7 +788,7 @@ export class GridModel extends HoistModel {
749
788
  * This method delegates to {@link selectFirstAsync}.
750
789
  */
751
790
  async preSelectFirstAsync() {
752
- if (!this.hasSelection) return this.selectFirstAsync();
791
+ if (!this.hasSelection) return this.selectFirstAsync({expandParentGroups: false});
753
792
  }
754
793
 
755
794
  /** Deselect all rows. */
@@ -988,20 +1027,18 @@ export class GridModel extends HoistModel {
988
1027
 
989
1028
  /** Expand all parent rows in grouped or tree grid. (Note, this is recursive for trees!) */
990
1029
  expandAll() {
991
- const {agApi} = this;
992
- if (agApi) {
993
- agApi.expandAll();
994
- this.noteAgExpandStateChange();
995
- }
1030
+ this.setExpandToLevel(this.maxDepth);
996
1031
  }
997
1032
 
998
1033
  /** Collapse all parent rows in grouped or tree grid. */
999
1034
  collapseAll() {
1000
- const {agApi} = this;
1001
- if (agApi) {
1002
- agApi.collapseAll();
1003
- this.noteAgExpandStateChange();
1004
- }
1035
+ this.setExpandToLevel(0);
1036
+ }
1037
+
1038
+ /** Expand all parent rows in grouped or tree grid to the specified level. */
1039
+ @action
1040
+ setExpandToLevel(level: number) {
1041
+ this.expandToLevel = level;
1005
1042
  }
1006
1043
 
1007
1044
  /**
package/cmp/grid/Types.ts CHANGED
@@ -81,6 +81,8 @@ export interface GridModelPersistOptions extends PersistOptions {
81
81
  persistGrouping?: boolean | PersistOptions;
82
82
  /** True (default) to include sort state or provide sort-specific PersistOptions. */
83
83
  persistSort?: boolean | PersistOptions;
84
+ /** True (default) to include expanded level state or provide expanded level-specific PersistOptions. */
85
+ persistExpandToLevel?: boolean | PersistOptions;
84
86
  }
85
87
 
86
88
  export interface GridFilterModelConfig {
@@ -19,6 +19,7 @@ export function initPersist(
19
19
  persistColumns = true,
20
20
  persistGrouping = true,
21
21
  persistSort = true,
22
+ persistExpandToLevel = true,
22
23
  path = 'grid',
23
24
  ...rootPersistWith
24
25
  }: GridModelPersistOptions
@@ -75,6 +76,23 @@ export function initPersist(
75
76
  owner: gridModel
76
77
  });
77
78
  }
79
+
80
+ if (persistExpandToLevel) {
81
+ const persistWith = isObject(persistExpandToLevel)
82
+ ? PersistenceProvider.mergePersistOptions(rootPersistWith, persistExpandToLevel)
83
+ : rootPersistWith;
84
+ PersistenceProvider.create({
85
+ persistOptions: {
86
+ path: `${path}.expandToLevel`,
87
+ ...persistWith
88
+ },
89
+ target: {
90
+ getPersistableState: () => new PersistableState(gridModel.expandToLevel),
91
+ setPersistableState: ({value}) => gridModel.setExpandToLevel(value)
92
+ },
93
+ owner: gridModel
94
+ });
95
+ }
78
96
  }
79
97
 
80
98
  class PersistableColumnState extends PersistableState<ColumnState[]> {
@@ -9,6 +9,7 @@ import {Column, GridModel} from '@xh/hoist/cmp/grid';
9
9
  import {RecordAction, Store, StoreRecord} from '@xh/hoist/data';
10
10
  import {convertIconToHtml, Icon} from '@xh/hoist/icon';
11
11
  import {filterConsecutiveMenuSeparators} from '@xh/hoist/utils/impl';
12
+ import {executeIfFunction} from '@xh/hoist/utils/js';
12
13
  import copy from 'clipboard-copy';
13
14
  import {isEmpty, isFunction, isNil, isString, uniq} from 'lodash';
14
15
  import {isValidElement} from 'react';
@@ -136,21 +137,9 @@ function replaceHoistToken(token: string, gridModel: GridModel): Some<RecordActi
136
137
  hidden: !gridModel?.colChooserModel,
137
138
  actionFn: () => (gridModel.colChooserModel as any)?.open()
138
139
  });
139
- case 'expandCollapseAll':
140
- return [
141
- new RecordAction({
142
- text: 'Expand All',
143
- icon: Icon.groupRowExpanded(),
144
- hidden: !gridModel || (!gridModel.treeMode && isEmpty(gridModel.groupBy)),
145
- actionFn: () => gridModel.expandAll()
146
- }),
147
- new RecordAction({
148
- text: 'Collapse All',
149
- icon: Icon.groupRowCollapsed(),
150
- hidden: !gridModel || (!gridModel.treeMode && isEmpty(gridModel.groupBy)),
151
- actionFn: () => gridModel.collapseAll()
152
- })
153
- ];
140
+ case 'expandCollapseAll': // For backward compatibility
141
+ case 'expandCollapse':
142
+ return createExpandCollapseItem(gridModel);
154
143
  case 'export':
155
144
  case 'exportExcel':
156
145
  return new RecordAction({
@@ -270,3 +259,53 @@ function replaceHoistToken(token: string, gridModel: GridModel): Some<RecordActi
270
259
  return token;
271
260
  }
272
261
  }
262
+
263
+ function createExpandCollapseItem(gridModel: GridModel): RecordAction[] {
264
+ if (!gridModel || gridModel.maxDepth === 0) return null;
265
+
266
+ return [
267
+ new RecordAction({
268
+ text: 'Expand All',
269
+ icon: Icon.groupRowExpanded(),
270
+ actionFn: () => gridModel.expandAll()
271
+ }),
272
+ new RecordAction({
273
+ text: 'Collapse All',
274
+ icon: Icon.groupRowCollapsed(),
275
+ actionFn: () => gridModel.collapseAll()
276
+ }),
277
+ levelExpandAction(gridModel)
278
+ ];
279
+ }
280
+
281
+ function levelExpandAction(gridModel: GridModel): RecordAction {
282
+ return new RecordAction({
283
+ text: 'Expand to ...',
284
+ displayFn: () => {
285
+ // Don't show for degenerate shallow grid models, or if we don't have labels
286
+ const {maxDepth, expandToLevel} = gridModel;
287
+ if (maxDepth <= 1) return {hidden: true};
288
+
289
+ const levelLabels = executeIfFunction(gridModel.levelLabels);
290
+ if (!levelLabels || levelLabels.length < maxDepth + 1) {
291
+ gridModel.logDebug(
292
+ '"levelLabels" not provided or of insufficient length. No menu items shown.'
293
+ );
294
+ return {hidden: true};
295
+ }
296
+
297
+ const items = levelLabels.map((label, idx) => {
298
+ const isCurrLevel =
299
+ expandToLevel === idx ||
300
+ (expandToLevel > maxDepth && idx === levelLabels.length - 1);
301
+
302
+ return {
303
+ icon: isCurrLevel ? Icon.check() : null,
304
+ text: label,
305
+ actionFn: () => gridModel.setExpandToLevel(idx)
306
+ };
307
+ });
308
+ return {items};
309
+ }
310
+ });
311
+ }
@@ -274,6 +274,10 @@ export class GroupingChooserModel extends HoistModel {
274
274
  return value.map(dimName => this.getDimDisplayName(dimName)).join(' › ');
275
275
  }
276
276
 
277
+ getValueDisplayNames(): string[] {
278
+ return this.value.map(dimName => this.getDimDisplayName(dimName));
279
+ }
280
+
277
281
  getDimDisplayName(dimName: string) {
278
282
  return this.dimensions[dimName]?.displayName ?? dimName;
279
283
  }
@@ -39,6 +39,7 @@ import {
39
39
  managed,
40
40
  PlainObject,
41
41
  Some,
42
+ Thunkable,
42
43
  VSide,
43
44
  XH
44
45
  } from '@xh/hoist/core';
@@ -146,6 +147,9 @@ export interface ZoneGridConfig {
146
147
  /** Column ID(s) by which to do full-width grouping. */
147
148
  groupBy?: Some<string>;
148
149
 
150
+ /** Group level to expand to on initial load. 0 = all collapsed, 1 = only top level expanded. */
151
+ expandToLevel?: number;
152
+
149
153
  /** True (default) to show a count of group member rows within each full-width group row. */
150
154
  showGroupRowCounts?: boolean;
151
155
 
@@ -231,6 +235,13 @@ export interface ZoneGridConfig {
231
235
  */
232
236
  onCellContextMenu?: (e: CellContextMenuEvent) => void;
233
237
 
238
+ /**
239
+ * Array of labels (or a function returning one) that describes the individual depth
240
+ * levels in a tree or grouped grid. If provided, will be used to construct expand/collapse
241
+ * options in the default context menu.
242
+ */
243
+ levelLabels?: Thunkable<string[]>;
244
+
234
245
  /**
235
246
  * Number of clicks required to expand / collapse a parent row in a tree grid. Defaults
236
247
  * to 2 for desktop, 1 for mobile. Any other value prevents clicks on row body from
@@ -419,7 +430,7 @@ export class ZoneGridModel extends HoistModel {
419
430
  'copyWithHeaders',
420
431
  'copyCell',
421
432
  '-',
422
- 'expandCollapseAll',
433
+ 'expandCollapse',
423
434
  '-',
424
435
  'restoreDefaults',
425
436
  '-',