@deephaven/iris-grid 1.22.0 → 1.22.1-alpha-pivot-builder.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.
Files changed (51) hide show
  1. package/README.md +284 -1
  2. package/dist/CommonTypes.d.ts +62 -2
  3. package/dist/CommonTypes.d.ts.map +1 -1
  4. package/dist/CommonTypes.js.map +1 -1
  5. package/dist/IrisGrid.d.ts +85 -2
  6. package/dist/IrisGrid.d.ts.map +1 -1
  7. package/dist/IrisGrid.js +248 -68
  8. package/dist/IrisGrid.js.map +1 -1
  9. package/dist/IrisGridModel.d.ts +30 -1
  10. package/dist/IrisGridModel.d.ts.map +1 -1
  11. package/dist/IrisGridModel.js +36 -1
  12. package/dist/IrisGridModel.js.map +1 -1
  13. package/dist/IrisGridModelWidgetProps.d.ts +26 -0
  14. package/dist/IrisGridModelWidgetProps.d.ts.map +1 -0
  15. package/dist/IrisGridModelWidgetProps.js +2 -0
  16. package/dist/IrisGridModelWidgetProps.js.map +1 -0
  17. package/dist/IrisGridProxyModel.d.ts.map +1 -1
  18. package/dist/IrisGridProxyModel.js +34 -2
  19. package/dist/IrisGridProxyModel.js.map +1 -1
  20. package/dist/IrisGridTextCellRenderer.d.ts.map +1 -1
  21. package/dist/IrisGridTextCellRenderer.js +1 -1
  22. package/dist/IrisGridTextCellRenderer.js.map +1 -1
  23. package/dist/IrisGridUtils.d.ts +25 -2
  24. package/dist/IrisGridUtils.d.ts.map +1 -1
  25. package/dist/IrisGridUtils.js +99 -42
  26. package/dist/IrisGridUtils.js.map +1 -1
  27. package/dist/LazyIrisGrid.d.ts +1 -1
  28. package/dist/RemoteComponentModules.d.ts +12 -0
  29. package/dist/RemoteComponentModules.d.ts.map +1 -0
  30. package/dist/RemoteComponentModules.js +16 -0
  31. package/dist/RemoteComponentModules.js.map +1 -0
  32. package/dist/index.d.ts +2 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +2 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/sidebar/IrisGridTableOptionsWidgetProps.d.ts +22 -0
  37. package/dist/sidebar/IrisGridTableOptionsWidgetProps.d.ts.map +1 -0
  38. package/dist/sidebar/IrisGridTableOptionsWidgetProps.js +2 -0
  39. package/dist/sidebar/IrisGridTableOptionsWidgetProps.js.map +1 -0
  40. package/dist/sidebar/OptionType.d.ts +8 -0
  41. package/dist/sidebar/OptionType.d.ts.map +1 -1
  42. package/dist/sidebar/OptionType.js +7 -0
  43. package/dist/sidebar/OptionType.js.map +1 -1
  44. package/dist/sidebar/PluginTableOptionsErrorBoundary.d.ts +30 -0
  45. package/dist/sidebar/PluginTableOptionsErrorBoundary.d.ts.map +1 -0
  46. package/dist/sidebar/PluginTableOptionsErrorBoundary.js +60 -0
  47. package/dist/sidebar/PluginTableOptionsErrorBoundary.js.map +1 -0
  48. package/dist/sidebar/index.d.ts +3 -2
  49. package/dist/sidebar/index.d.ts.map +1 -1
  50. package/dist/sidebar/index.js.map +1 -1
  51. package/package.json +16 -16
package/dist/IrisGrid.js CHANGED
@@ -42,6 +42,7 @@ import IrisGridModel from "./IrisGridModel.js";
42
42
  import { isPartitionedGridModel } from "./PartitionedGridModel.js";
43
43
  import IrisGridPartitionSelector from "./IrisGridPartitionSelector.js";
44
44
  import SelectDistinctBuilder from "./sidebar/SelectDistinctBuilder.js";
45
+ import PluginTableOptionsErrorBoundary from "./sidebar/PluginTableOptionsErrorBoundary.js";
45
46
  import AdvancedSettingsType from "./sidebar/AdvancedSettingsType.js";
46
47
  import AdvancedSettingsMenu from "./sidebar/AdvancedSettingsMenu.js";
47
48
  import SHORTCUTS from "./IrisGridShortcuts.js";
@@ -49,7 +50,6 @@ import ConditionalFormattingMenu from "./sidebar/conditional-formatting/Conditio
49
50
  import ConditionalFormatEditor from "./sidebar/conditional-formatting/ConditionalFormatEditor.js";
50
51
  import IrisGridCellOverflowModal from "./IrisGridCellOverflowModal.js";
51
52
  import GotoRow from "./GotoRow.js";
52
- import AggregationOperation from "./sidebar/aggregations/AggregationOperation.js";
53
53
  import { IrisGridThemeContext } from "./IrisGridThemeProvider.js";
54
54
  import { isMissingPartitionError } from "./MissingPartitionError.js";
55
55
  import { NoPastePermissionModal } from "./NoPastePermissionModal.js";
@@ -80,6 +80,17 @@ function isEmptyConfig(_ref) {
80
80
  } = _ref;
81
81
  return advancedFilters.size === 0 && aggregationSettings.aggregations.length === 0 && customColumns.length === 0 && quickFilters.size === 0 && !reverse && rollupConfig == null && searchFilter == null && selectDistinctColumns.length === 0 && sorts.length === 0;
82
82
  }
83
+
84
+ /**
85
+ * The subset of `IrisGridProps` that overrides how the grid presents its
86
+ * model: theme, canvas renderer, extra mouse handlers, and the metric
87
+ * calculator factory. Hosts that render `<IrisGrid>` on behalf of a plugin
88
+ * (e.g. `GridWidgetPlugin`) accept this as a single passthrough bag so they
89
+ * don't need to know each view concern by name, and plugins build it from
90
+ * their own hooks. Kept as a `Pick` (not `Partial<IrisGridProps>`) so it can
91
+ * never clobber structural props like `model` or `ref`.
92
+ */
93
+
83
94
  class IrisGrid extends Component {
84
95
  constructor(props) {
85
96
  var _model$layoutHints, _model$columns$0$name, _model$columns$;
@@ -117,6 +128,13 @@ class IrisGrid extends Component {
117
128
  _defineProperty(this, "tableUtils", void 0);
118
129
  _defineProperty(this, "keyHandlers", void 0);
119
130
  _defineProperty(this, "mouseHandlers", void 0);
131
+ /**
132
+ * The metric calculator factory most recently used to instantiate the
133
+ * calculator currently stored in state. Used by `maybeRebuildMetricCalculator`
134
+ * (called from `componentDidUpdate` when the `getMetricCalculator` prop
135
+ * changes) to detect when a different factory is supplied and rebuild.
136
+ */
137
+ _defineProperty(this, "lastMetricCalculatorFactory", void 0);
120
138
  _defineProperty(this, "slideTransitionRef", /*#__PURE__*/React.createRef());
121
139
  _defineProperty(this, "bottomTransitionRef", /*#__PURE__*/React.createRef());
122
140
  _defineProperty(this, "getAdvancedMenuOpenedHandler", memoize(column => this.handleAdvancedMenuOpened.bind(this, column), {
@@ -146,63 +164,72 @@ class IrisGrid extends Component {
146
164
  optionItems.push({
147
165
  type: OptionType.CHART_BUILDER,
148
166
  title: 'Chart Builder',
149
- icon: dhGraphLineUp
167
+ icon: dhGraphLineUp,
168
+ order: 100
150
169
  });
151
170
  }
152
171
  if (isOrganizeColumnsAvailable) {
153
172
  optionItems.push({
154
173
  type: OptionType.VISIBILITY_ORDERING_BUILDER,
155
174
  title: 'Organize Columns',
156
- icon: dhEye
175
+ icon: dhEye,
176
+ order: 200
157
177
  });
158
178
  }
159
179
  if (isFormatColumnsAvailable) {
160
180
  optionItems.push({
161
181
  type: OptionType.CONDITIONAL_FORMATTING,
162
182
  title: 'Conditional Formatting',
163
- icon: vsEdit
183
+ icon: vsEdit,
184
+ order: 300
164
185
  });
165
186
  }
166
187
  if (isCustomColumnsAvailable) {
167
188
  optionItems.push({
168
189
  type: OptionType.CUSTOM_COLUMN_BUILDER,
169
190
  title: 'Custom Columns',
170
- icon: vsSplitHorizontal
191
+ icon: vsSplitHorizontal,
192
+ order: 400
171
193
  });
172
194
  }
173
195
  if (isRollupAvailable) {
174
196
  optionItems.push({
175
197
  type: OptionType.ROLLUP_ROWS,
176
198
  title: 'Rollup Rows',
177
- icon: dhTriangleDownSquare
199
+ icon: dhTriangleDownSquare,
200
+ order: 500
178
201
  });
179
202
  }
180
203
  if (isTotalsAvailable) {
181
204
  optionItems.push({
182
205
  type: OptionType.AGGREGATIONS,
183
206
  title: 'Aggregate Columns',
184
- icon: vsSymbolOperator
207
+ icon: vsSymbolOperator,
208
+ order: 600
185
209
  });
186
210
  }
187
211
  if (isSelectDistinctAvailable) {
188
212
  optionItems.push({
189
213
  type: OptionType.SELECT_DISTINCT,
190
214
  title: 'Select Distinct Values',
191
- icon: vsRuby
215
+ icon: vsRuby,
216
+ order: 700
192
217
  });
193
218
  }
194
219
  if (isExportAvailable && canDownloadCsv) {
195
220
  optionItems.push({
196
221
  type: OptionType.TABLE_EXPORTER,
197
222
  title: 'Download CSV',
198
- icon: vsCloudDownload
223
+ icon: vsCloudDownload,
224
+ order: 800
199
225
  });
200
226
  }
201
227
  if (hasAdvancedSettings) {
202
228
  optionItems.push({
203
229
  type: OptionType.ADVANCED_SETTINGS,
204
230
  title: 'Advanced Settings',
205
- icon: vsTools
231
+ icon: vsTools,
232
+ order: 900
206
233
  });
207
234
  }
208
235
  optionItems.push({
@@ -211,7 +238,8 @@ class IrisGrid extends Component {
211
238
  subtitle: toggleFilterBarAction.shortcut.getDisplayText(),
212
239
  icon: vsFilter,
213
240
  isOn: isFilterBarShown,
214
- onChange: toggleFilterBarAction.action
241
+ onChange: toggleFilterBarAction.action,
242
+ order: 1000
215
243
  });
216
244
  if (canToggleSearch) {
217
245
  optionItems.push({
@@ -220,7 +248,8 @@ class IrisGrid extends Component {
220
248
  subtitle: toggleSearchBarAction.shortcut.getDisplayText(),
221
249
  icon: vsSearch,
222
250
  isOn: showSearchBar,
223
- onChange: toggleSearchBarAction.action
251
+ onChange: toggleSearchBarAction.action,
252
+ order: 1100
224
253
  });
225
254
  }
226
255
  optionItems.push({
@@ -229,48 +258,80 @@ class IrisGrid extends Component {
229
258
  subtitle: toggleGotoRowAction.shortcut.getDisplayText(),
230
259
  icon: vsReply,
231
260
  isOn: showGotoRow,
232
- onChange: toggleGotoRowAction.action
261
+ onChange: toggleGotoRowAction.action,
262
+ order: 1200
233
263
  });
234
264
  return Object.freeze(optionItems);
235
265
  }, {
236
266
  max: 1
237
267
  }));
268
+ /**
269
+ * Apply the `transformTableOptions` transform (if any) to the
270
+ * default option list.
271
+ * Catches exceptions so a buggy plugin can't break the grid,
272
+ * and warns about duplicate `type` collisions.
273
+ */
274
+ _defineProperty(this, "getCachedTransformedOptionItems", memoize((items, transformTableOptions) => {
275
+ if (transformTableOptions == null) {
276
+ return items;
277
+ }
278
+ var transformedItems;
279
+ try {
280
+ transformedItems = transformTableOptions(items);
281
+ } catch (err) {
282
+ log.error('transformTableOptions threw an error; falling back to defaults.', err);
283
+ return items;
284
+ }
285
+ // Stably sort by ascending `order`. Items without an `order` sink to
286
+ // the end of the menu (default `Infinity`), while items with an `order`
287
+ // are positioned by their weight. Decorate-sort-undecorate guarantees
288
+ // stability regardless of the engine's sort implementation.
289
+ var sortedItems = transformedItems.map((item, index) => ({
290
+ item,
291
+ index
292
+ })).sort((a, b) => {
293
+ var _a$item$order, _b$item$order;
294
+ return ((_a$item$order = a.item.order) !== null && _a$item$order !== void 0 ? _a$item$order : Infinity) - ((_b$item$order = b.item.order) !== null && _b$item$order !== void 0 ? _b$item$order : Infinity) || a.index - b.index;
295
+ }).map(_ref2 => {
296
+ var {
297
+ item
298
+ } = _ref2;
299
+ return item;
300
+ });
301
+ var keys = new Set();
302
+ for (var i = 0; i < sortedItems.length; i += 1) {
303
+ var key = String(sortedItems[i].type);
304
+ if (keys.has(key)) {
305
+ log.warn("transformTableOptions produced duplicate type \"".concat(key, "\"; ") + 'only the first entry will be accessible from the menu.');
306
+ break;
307
+ }
308
+ keys.add(key);
309
+ }
310
+ return Object.freeze(sortedItems);
311
+ }, {
312
+ max: 1
313
+ }));
238
314
  _defineProperty(this, "getCachedHiddenColumns", memoize((metricCalculator, userColumnWidths) => IrisGridUtils.getHiddenColumns(new Map([...metricCalculator.initialColumnWidths, ...userColumnWidths])), {
239
315
  max: 1
240
316
  }));
241
317
  _defineProperty(this, "getAggregationMap", memoize((columns, aggregations) => {
242
318
  var aggregationMap = {};
243
- aggregations.forEach(_ref2 => {
319
+ aggregations.forEach(_ref3 => {
244
320
  var {
245
321
  operation,
246
322
  selected,
247
323
  invert
248
- } = _ref2;
324
+ } = _ref3;
249
325
  aggregationMap[operation] = AggregationUtils.getOperationColumnNames(columns, operation, selected, invert);
250
326
  });
251
327
  return aggregationMap;
252
328
  }, {
253
329
  max: 1
254
330
  }));
255
- _defineProperty(this, "getOperationMap", memoize((columns, aggregations) => {
256
- var operationMap = {};
257
- aggregations.filter(a => !AggregationUtils.isRollupOperation(a.operation)).forEach(_ref3 => {
258
- var {
259
- operation,
260
- selected,
261
- invert
262
- } = _ref3;
263
- AggregationUtils.getOperationColumnNames(columns, operation, selected, invert).forEach(name => {
264
- var _operationMap$name;
265
- var newOperations = [...((_operationMap$name = operationMap[name]) !== null && _operationMap$name !== void 0 ? _operationMap$name : []), operation];
266
- operationMap[name] = Object.freeze(newOperations);
267
- });
268
- });
269
- return operationMap;
270
- }, {
331
+ _defineProperty(this, "getOperationMap", memoize((columns, aggregations) => IrisGridUtils.getOperationMap(columns, aggregations), {
271
332
  max: 1
272
333
  }));
273
- _defineProperty(this, "getOperationOrder", memoize(aggregations => aggregations.map(a => a.operation).filter(o => !AggregationUtils.isRollupOperation(o)), {
334
+ _defineProperty(this, "getOperationOrder", memoize(aggregations => IrisGridUtils.getOperationOrder(aggregations), {
274
335
  max: 1
275
336
  }));
276
337
  _defineProperty(this, "getCachedFormatColumns", memoize((dh, columns, rules) => getFormatColumns(dh, columns, rules), {
@@ -298,28 +359,7 @@ class IrisGrid extends Component {
298
359
  _defineProperty(this, "getModelRollupConfig", memoize((originalColumns, config, aggregationSettings) => IrisGridUtils.getModelRollupConfig(originalColumns, config, aggregationSettings), {
299
360
  max: 1
300
361
  }));
301
- _defineProperty(this, "getModelTotalsConfig", memoize((columns, config, aggregationSettings) => {
302
- var _config$columns$lengt, _config$columns;
303
- if (((_config$columns$lengt = config === null || config === void 0 || (_config$columns = config.columns) === null || _config$columns === void 0 ? void 0 : _config$columns.length) !== null && _config$columns$lengt !== void 0 ? _config$columns$lengt : 0) > 0) {
304
- // If we've got rollups, then aggregations are applied as part of that...
305
- return null;
306
- }
307
-
308
- // Filter out aggregations without any columns actually selected
309
- var aggregations = aggregationSettings.aggregations.filter(agg => agg.selected.length > 0 || agg.invert);
310
- if (aggregations.length === 0) {
311
- // We don't actually have any aggregations set, don't bother
312
- return null;
313
- }
314
- var operationMap = this.getOperationMap(columns, aggregations);
315
- var operationOrder = this.getOperationOrder(aggregations);
316
- return {
317
- operationMap,
318
- operationOrder,
319
- showOnTop: aggregationSettings.showOnTop,
320
- defaultOperation: AggregationOperation.SKIP
321
- };
322
- }, {
362
+ _defineProperty(this, "getModelTotalsConfig", memoize((columns, config, aggregationSettings) => IrisGridUtils.getModelTotalsConfig(columns, config, aggregationSettings), {
323
363
  max: 1
324
364
  }));
325
365
  _defineProperty(this, "getCachedStateOverride", memoize((model, theme, hoverSelectColumn, isFilterBarShown, isSelectingColumn, loadingScrimProgress, quickFilters, advancedFilters, sorts, reverse, rollupConfig, isMenuShown) => ({
@@ -372,7 +412,7 @@ class IrisGrid extends Component {
372
412
  _defineProperty(this, "getCachedKeyHandlers", memoize(keyHandlers => [...keyHandlers, ...this.keyHandlers].sort((a, b) => a.order - b.order), {
373
413
  max: 1
374
414
  }));
375
- _defineProperty(this, "getCachedMouseHandlers", memoize(mouseHandlers => [...mouseHandlers, ...this.mouseHandlers].map(handler => typeof handler === 'function' ? handler(this) : handler), {
415
+ _defineProperty(this, "getCachedMouseHandlers", memoize(mouseHandlersProp => [...mouseHandlersProp, ...this.mouseHandlers].map(handler => typeof handler === 'function' ? handler(this) : handler), {
376
416
  max: 1
377
417
  }));
378
418
  _defineProperty(this, "getCachedRenderer", memoize(rendererProp => rendererProp !== null && rendererProp !== void 0 ? rendererProp : new IrisGridRenderer(), {
@@ -571,11 +611,14 @@ class IrisGrid extends Component {
571
611
  this.handleMenuSelect = this.handleMenuSelect.bind(this);
572
612
  this.handleMenuBack = this.handleMenuBack.bind(this);
573
613
  this.handleRequestFailed = this.handleRequestFailed.bind(this);
614
+ this.handlePending = this.handlePending.bind(this);
615
+ this.handlePendingCleared = this.handlePendingCleared.bind(this);
574
616
  this.handleSelectionChanged = this.handleSelectionChanged.bind(this);
575
617
  this.handleMovedColumnsChanged = this.handleMovedColumnsChanged.bind(this);
576
618
  this.handleHeaderGroupsChanged = this.handleHeaderGroupsChanged.bind(this);
577
619
  this.handleUpdate = this.handleUpdate.bind(this);
578
620
  this.handleTableChanged = this.handleTableChanged.bind(this);
621
+ this.handleModelChanged = this.handleModelChanged.bind(this);
579
622
  this.handleTooltipRef = this.handleTooltipRef.bind(this);
580
623
  this.handleViewChanged = this.handleViewChanged.bind(this);
581
624
  this.handleFormatSelection = this.handleFormatSelection.bind(this);
@@ -702,23 +745,27 @@ class IrisGrid extends Component {
702
745
  dh: _dh
703
746
  } = _model;
704
747
  var _keyHandlers = [new CopyCellKeyHandler(this), new ReverseKeyHandler(this), new ClearFilterKeyHandler(this)];
705
- var _mouseHandlers = [new IrisGridCellOverflowMouseHandler(this), new IrisGridRowTreeMouseHandler(this), new IrisGridTokenMouseHandler(this), new IrisGridColumnSelectMouseHandler(this), new IrisGridColumnTooltipMouseHandler(this), new IrisGridSortMouseHandler(this), new IrisGridFilterMouseHandler(this), new IrisGridContextMenuHandler(this, _dh), new IrisGridDataSelectMouseHandler(this), new PendingMouseHandler(this), new IrisGridPartitionedTableMouseHandler(this), ...(canCopy ? [new IrisGridCopyCellMouseHandler(this)] : [])];
748
+ var mouseHandlers = [new IrisGridCellOverflowMouseHandler(this), new IrisGridRowTreeMouseHandler(this), new IrisGridTokenMouseHandler(this), new IrisGridColumnSelectMouseHandler(this), new IrisGridColumnTooltipMouseHandler(this), new IrisGridSortMouseHandler(this), new IrisGridFilterMouseHandler(this), new IrisGridContextMenuHandler(this, _dh), new IrisGridDataSelectMouseHandler(this), new PendingMouseHandler(this), new IrisGridPartitionedTableMouseHandler(this), ...(canCopy ? [new IrisGridCopyCellMouseHandler(this)] : [])];
706
749
  if (canCopy) {
707
750
  _keyHandlers.push(new CopyKeyHandler(this));
708
751
  }
709
752
  var _movedColumns = movedColumnsProp.length > 0 ? movedColumnsProp : _model.initialMovedColumns;
710
753
  var movedRows = movedRowsProp.length > 0 ? movedRowsProp : _model.initialMovedRows;
711
- var _metricCalculator = getMetricCalculator({
754
+ var metricCalculatorFactory = getMetricCalculator;
755
+ var _metricCalculator = metricCalculatorFactory({
712
756
  userColumnWidths: new Map(_userColumnWidths),
713
757
  userColumnWidthsByName: userColumnWidthsByName != null ? new Map(userColumnWidthsByName) : undefined,
714
758
  userRowHeights: new Map(userRowHeights),
715
759
  movedColumns: _movedColumns,
716
760
  initialColumnWidths: new Map(_model === null || _model === void 0 || (_model$layoutHints = _model.layoutHints) === null || _model$layoutHints === void 0 || (_model$layoutHints = _model$layoutHints.hiddenColumns) === null || _model$layoutHints === void 0 ? void 0 : _model$layoutHints.map(name => [_model.getColumnIndexByName(name), 0]))
717
761
  });
762
+ // Remember the factory we used so we can detect a model-driven swap
763
+ // (e.g. pivot-builder's proxy swapping its inner model) on COLUMNS_CHANGED.
764
+ this.lastMetricCalculatorFactory = metricCalculatorFactory;
718
765
  var searchColumns = _selectedSearchColumns !== null && _selectedSearchColumns !== void 0 ? _selectedSearchColumns : [];
719
766
  var _searchFilter = CrossColumnSearch.createSearchFilter(_dh, _searchValue, searchColumns, _model.columns, _invertSearchColumns);
720
767
  this.tableUtils = new TableUtils(_dh);
721
- this.mouseHandlers = _mouseHandlers;
768
+ this.mouseHandlers = mouseHandlers;
722
769
  this.keyHandlers = _keyHandlers;
723
770
  this.state = {
724
771
  isFilterBarShown: _isFilterBarShown,
@@ -823,12 +870,24 @@ class IrisGrid extends Component {
823
870
  settings,
824
871
  model,
825
872
  customFilters,
826
- sorts
873
+ sorts,
874
+ getMetricCalculator
827
875
  } = this.props;
828
876
  if (model !== prevProps.model) {
829
877
  this.stopListening(prevProps.model);
830
878
  this.startListening(model);
831
879
  }
880
+
881
+ // The renderer and mouse handlers flow through memoized getters in
882
+ // render(), so a `renderer` / `mouseHandlers` prop change naturally
883
+ // propagates on the next render. The metric calculator, however, lives in
884
+ // component state, so mirror that path when the `getMetricCalculator` prop
885
+ // changes (e.g. the pivot-builder middleware swapping the pivot factory in
886
+ // or out) so the calculator stays in sync. Moved columns are not touched
887
+ // here — a model swap resets them via `handleModelChanged`.
888
+ if (getMetricCalculator !== prevProps.getMetricCalculator) {
889
+ this.maybeRebuildMetricCalculator();
890
+ }
832
891
  var changedInputFilters = inputFilters !== prevProps.inputFilters ? inputFilters.filter(inputFilter => !prevProps.inputFilters.includes(inputFilter)) : [];
833
892
  if (changedInputFilters.length > 0) {
834
893
  var _advancedSettings$get;
@@ -1764,18 +1823,24 @@ class IrisGrid extends Component {
1764
1823
  startListening(model) {
1765
1824
  model.addEventListener(IrisGridModel.EVENT.UPDATED, this.handleUpdate);
1766
1825
  model.addEventListener(IrisGridModel.EVENT.REQUEST_FAILED, this.handleRequestFailed);
1826
+ model.addEventListener(IrisGridModel.EVENT.PENDING, this.handlePending);
1827
+ model.addEventListener(IrisGridModel.EVENT.PENDING_CLEARED, this.handlePendingCleared);
1767
1828
  model.addEventListener(IrisGridModel.EVENT.COLUMNS_CHANGED, this.handleCustomColumnsChanged);
1768
1829
  model.addEventListener(IrisGridModel.EVENT.PENDING_DATA_UPDATED, this.handlePendingDataUpdated);
1769
1830
  model.addEventListener(IrisGridModel.EVENT.VIEWPORT_UPDATED, this.handleViewportUpdated);
1770
1831
  model.addEventListener(IrisGridModel.EVENT.TABLE_CHANGED, this.handleTableChanged);
1832
+ model.addEventListener(IrisGridModel.EVENT.MODEL_CHANGED, this.handleModelChanged);
1771
1833
  }
1772
1834
  stopListening(model) {
1773
1835
  model.removeEventListener(IrisGridModel.EVENT.UPDATED, this.handleUpdate);
1774
1836
  model.removeEventListener(IrisGridModel.EVENT.REQUEST_FAILED, this.handleRequestFailed);
1837
+ model.removeEventListener(IrisGridModel.EVENT.PENDING, this.handlePending);
1838
+ model.removeEventListener(IrisGridModel.EVENT.PENDING_CLEARED, this.handlePendingCleared);
1775
1839
  model.removeEventListener(IrisGridModel.EVENT.COLUMNS_CHANGED, this.handleCustomColumnsChanged);
1776
1840
  model.removeEventListener(IrisGridModel.EVENT.PENDING_DATA_UPDATED, this.handlePendingDataUpdated);
1777
1841
  model.removeEventListener(IrisGridModel.EVENT.VIEWPORT_UPDATED, this.handleViewportUpdated);
1778
1842
  model.removeEventListener(IrisGridModel.EVENT.TABLE_CHANGED, this.handleTableChanged);
1843
+ model.removeEventListener(IrisGridModel.EVENT.MODEL_CHANGED, this.handleModelChanged);
1779
1844
  }
1780
1845
  focus() {
1781
1846
  var _this$grid3;
@@ -2619,6 +2684,37 @@ class IrisGrid extends Component {
2619
2684
  onError(new Error("Error displaying table: ".concat(error)));
2620
2685
  }
2621
2686
  }
2687
+
2688
+ /**
2689
+ * Raise the loading scrim in response to a model-driven `PENDING` event. The
2690
+ * model is the start signal, mirroring how `UPDATED`/`COLUMNS_CHANGED` are
2691
+ * already the model-driven stop signal. Idempotent: the first message within
2692
+ * a commit wins (the `loadingScrimStartTime == null` guard collapses multiple
2693
+ * pending operations into a single scrim).
2694
+ */
2695
+ handlePending(event) {
2696
+ var {
2697
+ detail
2698
+ } = event;
2699
+ var {
2700
+ text,
2701
+ options
2702
+ } = detail !== null && detail !== void 0 ? detail : {};
2703
+ if (this.loadingScrimStartTime == null) {
2704
+ this.startLoading(text !== null && text !== void 0 ? text : 'Loading...', _objectSpread({
2705
+ loadingCancelShown: false
2706
+ }, options));
2707
+ }
2708
+ }
2709
+
2710
+ /**
2711
+ * Clear the loading scrim in response to a model-driven `PENDING_CLEARED`
2712
+ * event. Only needed for operations that do not naturally end in
2713
+ * `UPDATED`/`COLUMNS_CHANGED`/`REQUEST_FAILED`.
2714
+ */
2715
+ handlePendingCleared() {
2716
+ this.stopLoading();
2717
+ }
2622
2718
  handleUpdate() {
2623
2719
  var _this$grid18;
2624
2720
  log.debug2('Received model update');
@@ -2656,10 +2752,6 @@ class IrisGrid extends Component {
2656
2752
  var {
2657
2753
  model
2658
2754
  } = this.props;
2659
- // movedColumns reset triggers metricCalculator update in the Grid component
2660
- this.setState({
2661
- movedColumns: model.initialMovedColumns
2662
- });
2663
2755
  // For partitioned tables, we want to rebuild filters on table change to ensure filters are applied to the new partition
2664
2756
  var {
2665
2757
  partitionConfig
@@ -2668,6 +2760,24 @@ class IrisGrid extends Component {
2668
2760
  this.rebuildFilters();
2669
2761
  }
2670
2762
  }
2763
+
2764
+ /**
2765
+ * Handle an inner-model swap on a proxy model (`MODEL_CHANGED`). The previous
2766
+ * model's `movedColumns` reference indices that may not exist in the new
2767
+ * model (e.g. a pivot exposes a different column set), so reset them to the
2768
+ * new model's initial order. The metric calculator is rebuilt separately when
2769
+ * the `getMetricCalculator` prop changes (see `componentDidUpdate`); a calc
2770
+ * whose seed `movedColumns` are now stale self-heals because `getMetrics`
2771
+ * reconciles against the grid's current `movedColumns` at draw time.
2772
+ */
2773
+ handleModelChanged() {
2774
+ var {
2775
+ model
2776
+ } = this.props;
2777
+ this.setState({
2778
+ movedColumns: model.initialMovedColumns
2779
+ });
2780
+ }
2671
2781
  handleViewChanged(metrics) {
2672
2782
  var _this$grid$state, _this$grid19;
2673
2783
  var {
@@ -2896,6 +3006,56 @@ class IrisGrid extends Component {
2896
3006
  this.loadTableState();
2897
3007
  }
2898
3008
  }
3009
+
3010
+ /**
3011
+ * Rebuild the metric calculator when the `getMetricCalculator` prop swaps for
3012
+ * a different factory (e.g. entering or leaving a pivot, where the
3013
+ * pivot-builder middleware flips the prop). The renderer and mouse handlers
3014
+ * are recomputed via their memoized getters on the next render and do not
3015
+ * need explicit handling here.
3016
+ *
3017
+ * User column-widths / row-heights from the previous calculator are not
3018
+ * carried over: a factory swap means the column set has effectively changed,
3019
+ * so the stored sizes wouldn't map to anything meaningful.
3020
+ *
3021
+ * Moved columns are NOT reset here — that is owned by `handleModelChanged`
3022
+ * (the `MODEL_CHANGED` event) so that a plain prop swap against the same
3023
+ * model preserves the user's layout. The new calculator is seeded with the
3024
+ * current moved columns; `getMetrics` reconciles against the grid's live
3025
+ * `movedColumns` at draw time, so a later reset stays consistent.
3026
+ */
3027
+ maybeRebuildMetricCalculator() {
3028
+ var _model$layoutHints4;
3029
+ var {
3030
+ getMetricCalculator
3031
+ } = this.props;
3032
+ var factory = getMetricCalculator;
3033
+ if (factory === this.lastMetricCalculatorFactory) return;
3034
+ var {
3035
+ model
3036
+ } = this.props;
3037
+ var {
3038
+ movedColumns
3039
+ } = this.state;
3040
+ var next = factory({
3041
+ userColumnWidths: new Map(),
3042
+ userRowHeights: new Map(),
3043
+ movedColumns,
3044
+ initialColumnWidths: new Map(model === null || model === void 0 || (_model$layoutHints4 = model.layoutHints) === null || _model$layoutHints4 === void 0 || (_model$layoutHints4 = _model$layoutHints4.hiddenColumns) === null || _model$layoutHints4 === void 0 ? void 0 : _model$layoutHints4.map(name => [model.getColumnIndexByName(name), 0]))
3045
+ });
3046
+ this.lastMetricCalculatorFactory = factory;
3047
+ log.debug('Swapping metric calculator', next);
3048
+ // Also push the new calculator onto the Grid synchronously so any
3049
+ // immediately-following read of `Grid.metricCalculator` (before React has
3050
+ // committed the setState below) uses the new instance against the new
3051
+ // model rather than invoking the old calculator and potentially throwing.
3052
+ if (this.grid != null) {
3053
+ this.grid.metricCalculator = next;
3054
+ }
3055
+ this.setState({
3056
+ metricCalculator: next
3057
+ });
3058
+ }
2899
3059
  handlePendingCommitClicked() {
2900
3060
  return this.commitPending();
2901
3061
  }
@@ -3672,7 +3832,8 @@ class IrisGrid extends Component {
3672
3832
  advancedSettings,
3673
3833
  onAdvancedSettingsChange,
3674
3834
  canDownloadCsv,
3675
- onCreateChart
3835
+ onCreateChart,
3836
+ transformTableOptions
3676
3837
  } = this.props;
3677
3838
  var {
3678
3839
  metricCalculator,
@@ -3953,7 +4114,8 @@ class IrisGrid extends Component {
3953
4114
  if (_loop3()) continue;
3954
4115
  }
3955
4116
  }
3956
- var optionItems = this.getCachedOptionItems(onCreateChart !== undefined && model.isChartBuilderAvailable, model.isCustomColumnsAvailable, model.isFormatColumnsAvailable, model.isOrganizeColumnsAvailable, model.isRollupAvailable, model.isTotalsAvailable || isRollup, model.isSelectDistinctAvailable, model.isExportAvailable, this.toggleFilterBarAction, this.toggleSearchBarAction, this.toggleGotoRowAction, isFilterBarShown, showSearchBar, canDownloadCsv, this.isTableSearchAvailable(), isGotoShown, advancedSettings.size > 0);
4117
+ var defaultOptionItems = this.getCachedOptionItems(onCreateChart !== undefined && model.isChartBuilderAvailable, model.isCustomColumnsAvailable, model.isFormatColumnsAvailable, model.isOrganizeColumnsAvailable, model.isRollupAvailable, model.isTotalsAvailable || isRollup, model.isSelectDistinctAvailable, model.isExportAvailable, this.toggleFilterBarAction, this.toggleSearchBarAction, this.toggleGotoRowAction, isFilterBarShown, showSearchBar, canDownloadCsv, this.isTableSearchAvailable(), isGotoShown, advancedSettings.size > 0);
4118
+ var optionItems = this.getCachedTransformedOptionItems(defaultOptionItems, transformTableOptions);
3957
4119
  var hiddenColumns = this.getCachedHiddenColumns(metricCalculator, userColumnWidths);
3958
4120
  var openOptionsStack = openOptions.map(option => {
3959
4121
  switch (option.type) {
@@ -4048,7 +4210,25 @@ class IrisGrid extends Component {
4048
4210
  onChange: onAdvancedSettingsChange
4049
4211
  });
4050
4212
  default:
4051
- throw Error("Unexpected option type ".concat(option.type));
4213
+ {
4214
+ // Plugin-contributed items render their own page via
4215
+ // `configPage`. The page is isolated inside an error
4216
+ // boundary so a throwing plugin doesn't tear down the
4217
+ // entire grid subtree. Built-in items that hit the default
4218
+ // case indicate a programmer error (unhandled enum case).
4219
+ var PluginPage = option.configPage;
4220
+ if (PluginPage == null) {
4221
+ throw Error("Unexpected option type ".concat(option.type));
4222
+ }
4223
+ return /*#__PURE__*/_jsx(PluginTableOptionsErrorBoundary, {
4224
+ itemType: String(option.type),
4225
+ onBack: this.handleMenuBack,
4226
+ children: /*#__PURE__*/_jsx(PluginPage, {
4227
+ model: model,
4228
+ onBack: this.handleMenuBack
4229
+ })
4230
+ }, String(option.type));
4231
+ }
4052
4232
  }
4053
4233
  });
4054
4234
  return /*#__PURE__*/_jsxs("div", {
@@ -4238,7 +4418,7 @@ class IrisGrid extends Component {
4238
4418
  unmountOnExit: true,
4239
4419
  children: /*#__PURE__*/_jsx("div", {
4240
4420
  className: "table-sidebar",
4241
- children: /*#__PURE__*/_jsxs(Stack, {
4421
+ children: /*#__PURE__*/_jsx(Stack, {
4242
4422
  children: [/*#__PURE__*/_jsx(Page, {
4243
4423
  title: "Table Options",
4244
4424
  onClose: this.handleMenuClose,
@@ -4246,7 +4426,7 @@ class IrisGrid extends Component {
4246
4426
  onSelect: i => this.handleMenuSelect(optionItems[i]),
4247
4427
  items: optionItems
4248
4428
  })
4249
- }), openOptionsStack.map((option, i) => /*#__PURE__*/_jsx(Page, {
4429
+ }, "table-options"), ...openOptionsStack.map((option, i) => /*#__PURE__*/_jsx(Page, {
4250
4430
  title: openOptions[i].title,
4251
4431
  onBack: this.handleMenuBack,
4252
4432
  onClose: this.handleMenuClose,