@adaptabletools/adaptable 23.0.22 → 23.1.0-canary.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/package.json +3 -2
  2. package/src/AdaptableInterfaces/IAdaptable.d.ts +37 -1
  3. package/src/AdaptableOptions/ColumnMenuOptions.d.ts +1 -1
  4. package/src/AdaptableOptions/ContextMenuOptions.d.ts +1 -1
  5. package/src/AdaptableOptions/FilterOptions.d.ts +22 -6
  6. package/src/Api/Internal/FormatColumnInternalApi.d.ts +12 -2
  7. package/src/Api/Internal/FormatColumnInternalApi.js +4 -0
  8. package/src/Redux/Store/AdaptableStore.js +20 -6
  9. package/src/Strategy/LayoutModule.js +4 -1
  10. package/src/Utilities/Helpers/StyledColumns/SparklineStyleHelper.d.ts +2 -0
  11. package/src/Utilities/Helpers/StyledColumns/SparklineStyleHelper.js +13 -10
  12. package/src/Utilities/Hooks/useAdaptableOptions.d.ts +1 -1
  13. package/src/Utilities/Services/AnnotationsService.js +8 -8
  14. package/src/View/Components/ColumnFilter/components/FloatingFilterValues.d.ts +3 -9
  15. package/src/View/Components/ColumnFilter/components/FloatingFilterValues.js +10 -6
  16. package/src/View/Components/PredicateEditor/PredicateEditor.js +8 -4
  17. package/src/View/StyledColumn/Wizard/StyledColumnSparklineSettingsSection.js +18 -8
  18. package/src/View/Wizard/OnePageAdaptableWizard.d.ts +2 -2
  19. package/src/View/Wizard/OnePageWizards.d.ts +1 -1
  20. package/src/agGrid/AdaptableAgGrid.d.ts +71 -10
  21. package/src/agGrid/AdaptableAgGrid.js +125 -88
  22. package/src/agGrid/AgGridAdapter.d.ts +33 -1
  23. package/src/agGrid/AgGridAdapter.js +91 -0
  24. package/src/agGrid/AgGridColumnAdapter.d.ts +68 -1
  25. package/src/agGrid/AgGridColumnAdapter.js +191 -73
  26. package/src/agGrid/AgGridMenuAdapter.d.ts +8 -2
  27. package/src/agGrid/AgGridMenuAdapter.js +17 -4
  28. package/src/agGrid/AgGridModulesAdapter.js +6 -1
  29. package/src/agGrid/AgGridThemeAdapter.d.ts +17 -0
  30. package/src/agGrid/AgGridThemeAdapter.js +37 -6
  31. package/src/agGrid/gridRefreshScope.d.ts +20 -0
  32. package/src/agGrid/gridRefreshScope.js +39 -0
  33. package/src/components/Radio/index.d.ts +2 -2
  34. package/src/devTools/DevToolsTracks.d.ts +6 -0
  35. package/src/devTools/DevToolsTracks.js +6 -0
  36. package/src/devTools/index.d.ts +25 -1
  37. package/src/env.js +2 -2
  38. package/src/layout-manager/src/index.d.ts +9 -0
  39. package/src/layout-manager/src/index.js +38 -17
  40. package/src/parser/src/types.d.ts +3 -1
  41. package/src/types.d.ts +1 -1
  42. package/tsconfig.esm.tsbuildinfo +1 -1
@@ -2,10 +2,10 @@ import { AdaptableOptions } from '../AdaptableOptions/AdaptableOptions';
2
2
  import { ChartRef, Column, GridApi, GridOptions, IRowNode, Module, RowModelType, StatusPanelDef } from 'ag-grid-enterprise';
3
3
  import { AdaptableLogger } from './AdaptableLogger';
4
4
  import { AdaptableApi, DestroyConfig } from '../Api/AdaptableApi';
5
- import { AdaptableVariant, IAdaptable, NormalizeAdaptableStateOptions } from '../AdaptableInterfaces/IAdaptable';
5
+ import { AdaptableVariant, GridRefreshScope, IAdaptable, NormalizeAdaptableStateOptions } from '../AdaptableInterfaces/IAdaptable';
6
6
  import { EmitterCallback } from '../Utilities/Emitter';
7
7
  import { AdaptablePlugin } from '../AdaptableOptions/AdaptablePlugin';
8
- import { AgGridAdapter } from './AgGridAdapter';
8
+ import { AgGridAdapter, RowNodeEvent } from './AgGridAdapter';
9
9
  import { IDataService } from '../Utilities/Services/Interface/IDataService';
10
10
  import { AdaptableState } from '../AdaptableState/AdaptableState';
11
11
  import { IAdaptableStore } from '../Redux/Store/Interface/IAdaptableStore';
@@ -54,7 +54,6 @@ interface AdaptableInitInternalConfig<TData = any> {
54
54
  }
55
55
  export declare class AdaptableAgGrid implements IAdaptable {
56
56
  logger: AdaptableLogger;
57
- private _id;
58
57
  lifecycleState: AdaptableLifecycleState;
59
58
  variant: AdaptableVariant;
60
59
  private emitter;
@@ -94,7 +93,7 @@ export declare class AdaptableAgGrid implements IAdaptable {
94
93
  ThemeService: IThemeService;
95
94
  private LicenseService;
96
95
  private ChartingService;
97
- private rowListeners;
96
+ private isListeningToAgGridRowNodeEvents;
98
97
  private debouncedSetSelectedRows;
99
98
  private debouncedSetSelectedCells;
100
99
  private agGridListenerKeydown;
@@ -131,9 +130,6 @@ export declare class AdaptableAgGrid implements IAdaptable {
131
130
  constructor(config?: {
132
131
  getAgGridColumnApiModuleReference?: () => Module;
133
132
  });
134
- static forEachAdaptable(fn: (adaptable: AdaptableAgGrid) => void): void;
135
- private static collectInstance;
136
- private static dismissInstance;
137
133
  get isAgGridInitialising(): boolean;
138
134
  get isReady(): boolean;
139
135
  get isAvailable(): boolean;
@@ -187,6 +183,22 @@ export declare class AdaptableAgGrid implements IAdaptable {
187
183
  * Either initializes the AG Grid instance or delegates it to the framework wrappers (React/Anglar)
188
184
  */
189
185
  private initializeAgGrid;
186
+ /**
187
+ * Derives the host element of the grid (the element the framework wrapper / `createGrid()` renders
188
+ * the grid into) when `ContainerOptions.agGridContainer` is not provided.
189
+ *
190
+ * AG Grid 36+ wraps the grid in three grid-owned `div.ag-styled-root` elements which mirror the
191
+ * theme classes (`ag-theme-params-N ...`, or the host's `ag-theme-<name>` in legacy mode);
192
+ * `AgGridAdapter.getAgGridRootElement()` is the innermost one. These wrappers must be skipped:
193
+ * they are re-classed by AG Grid on every theme change, so anything AdapTable adds/removes on them
194
+ * (legacy theme switching) would be lost.
195
+ *
196
+ * Resolution order:
197
+ * 1. the closest non-`.ag-styled-root` ancestor of the grid root (the host element)
198
+ * 2. legacy theme mode only: if the host element has no `ag-theme-*` class, the closest ancestor
199
+ * which has one (AG Grid itself accepts the theme class anywhere up the tree)
200
+ */
201
+ private deriveAgGridContainerElement;
190
202
  useRowNodeLookUp(): boolean;
191
203
  private buildInitContainerContext;
192
204
  getAgGridContainerElement(): HTMLElement | null;
@@ -198,6 +210,13 @@ export declare class AdaptableAgGrid implements IAdaptable {
198
210
  private initAdaptableStore;
199
211
  private mapAdaptableStateToAgGridState;
200
212
  private addGridEventListeners;
213
+ /**
214
+ * see #local_event_service_monkey_patch (AgGridAdapter.ts)
215
+ *
216
+ * Entry point for the AG Grid RowNode `dataChanged` / `cellChanged` events, which AG Grid does not expose publicly.
217
+ * These drive Adaptable's data change detection (CellChanged events, Alerts, Flashing Cells, etc.)
218
+ */
219
+ onAgGridRowNodeEvent(event: RowNodeEvent): void;
201
220
  private shouldAutoSizeLayout;
202
221
  private autoSizeLayoutIfNeeded;
203
222
  forPlugins(callback: (plugin: AdaptablePlugin) => any): void;
@@ -228,10 +247,52 @@ export declare class AdaptableAgGrid implements IAdaptable {
228
247
  getDisplayValueFromRawValue(rowNode: IRowNode, columnId: string, rawValue: any): string | undefined;
229
248
  private getCleanValue;
230
249
  getNormalisedValueFromRawValue(rawValue: any, column: AdaptableColumn): string | number | boolean | Date | unknown;
231
- private __updateColumnModelAndRefreshGrid_already_called;
250
+ /**
251
+ * The refresh scope already applied by a column-model pass in the current microtask window
252
+ * (`null` when no pass ran yet), see `updateColumnModelAndRefreshGrid`.
253
+ */
254
+ private __columnModelRefreshDoneInThisTick;
232
255
  private __forceUpdateColumnModel;
233
- updateColumnModelAndRefreshGrid(): void;
234
- _updateColumnModelAndRefreshGridNow(): void;
256
+ /**
257
+ * #narrow_column_model_refresh
258
+ *
259
+ * Runs the column-model pass at most once per microtask window: the first call runs it synchronously
260
+ * with the requested refresh scope; a later call in the same window does NOT set up the columns again
261
+ * (the #stable_col_def_callbacks read live state anyway) but still gets any refresh it asks for that
262
+ * the first call did not perform - eg a cells-only pass followed by a request for a row redraw.
263
+ */
264
+ updateColumnModelAndRefreshGrid(refreshScope?: GridRefreshScope): void;
265
+ /**
266
+ * #column_model_refresh_perf
267
+ *
268
+ * The whole pass and each of its phases are published as DevTools performance tracks
269
+ * (`Runtime` track, see `DevToolsTracks`) when profiling is enabled (`localStorage.adaptableProfileTracks = 'true'`).
270
+ * The `Update column model & refresh grid` entry carries the number of columns set up, the number of
271
+ * colDefs AG Grid actually saw as changed (see `AgGridAdapter.DANGER_getColDefsVersion`) and the refresh
272
+ * scope applied, so a `performance.getEntriesByName()` consumer (eg the `perf/column-model-refresh`
273
+ * Playwright spec) can tell a callback-only pass from one that committed real colDef changes.
274
+ *
275
+ * @param refreshScope see #narrow_column_model_refresh; defaults to a full refresh (rows + header)
276
+ */
277
+ _updateColumnModelAndRefreshGridNow(refreshScope?: GridRefreshScope): void;
278
+ /**
279
+ * see #narrow_column_model_refresh
280
+ * Applies the given refresh scope; each operation is a DevTools `Runtime` track entry (see #column_model_refresh_perf).
281
+ */
282
+ private refreshGridAfterColumnModelUpdate;
283
+ /**
284
+ * see #narrow_column_model_refresh
285
+ * The cell-level counterpart of `redrawBody()`: re-runs the colDef callbacks on every rendered cell
286
+ * without destroying the rows, so cell focus, selection and an in-progress edit survive.
287
+ * `cellStyle` results are normalised (see `normalizeStyleForAgGrid`), so a style that no longer applies
288
+ * is cleared even though AG Grid only ever ADDS inline styles on refresh.
289
+ */
290
+ refreshRenderedCells(): void;
291
+ /**
292
+ * Re-applies the current sort, eg. after a Custom Sort was added/edited/removed at runtime.
293
+ * The column comparators are already up to date (see #stable_col_def_callbacks), AG Grid just has to sort again.
294
+ */
295
+ refreshSortIfColumnsSorted(): void;
235
296
  redrawBody(): void;
236
297
  refreshHeader(): void;
237
298
  redrawRows(rowNodes?: IRowNode[]): void;
@@ -1,7 +1,8 @@
1
1
  import { debounce } from '../Utilities/Helpers/TimingHelper';
2
- import { createGrid, LocalEventService, } from 'ag-grid-enterprise';
2
+ import { createGrid, } from 'ag-grid-enterprise';
3
3
  import { AdaptableLogger } from './AdaptableLogger';
4
4
  import { PrimaryKeyDocsLink } from '../Utilities/Constants/DocumentationLinkConstants';
5
+ import { describeGridRefreshScope, FULL_GRID_REFRESH_SCOPE, getMissingGridRefreshScope, mergeGridRefreshScopes, } from './gridRefreshScope';
5
6
  import StringExtensions from '../Utilities/Extensions/StringExtensions';
6
7
  import { applyQuickSearchFindCssVars } from '../Utilities/Helpers/QuickSearchHelper';
7
8
  import Emitter from '../Utilities/Emitter';
@@ -110,48 +111,9 @@ import { AgGridFilterAdapterFactory } from './AgGridFilterAdapter';
110
111
  import { AdaptableFilterHandler } from './AdaptableFilterHandler';
111
112
  import { AgGridFloatingFilterAdapterFactory } from './AgGridFloatingFilterAdapter';
112
113
  import { listenForDevTools } from '../setupDevTools';
113
- const LocalEventService_Prototype = LocalEventService.prototype;
114
- const LocalEventService_dispatchEvent = LocalEventService_Prototype.dispatchEvent;
115
- LocalEventService_Prototype.dispatchEvent = function (event) {
116
- const agGridApi = event.api;
117
- if (agGridApi?.isDestroyed()) {
118
- return;
119
- }
120
- LocalEventService_dispatchEvent.apply(this, arguments);
121
- if (event.type === 'cellChanged' || event.type === 'dataChanged') {
122
- const eventRowNode = event.node;
123
- if (!eventRowNode) {
124
- AdaptableLogger.consoleErrorBase(`No RowNode found in passed event, this should never happen!`, event);
125
- return;
126
- }
127
- const extractGridApiFromRowNode = (rowNode) => {
128
- const rowNodeApi = rowNode?.beans?.gridApi;
129
- if (!rowNodeApi) {
130
- AdaptableLogger.consoleErrorBase(`No GridAPI found in passed RowNode, this should never happen!`, rowNode);
131
- }
132
- return rowNodeApi;
133
- };
134
- if (eventRowNode) {
135
- AdaptableAgGrid.forEachAdaptable((adaptable) => {
136
- if (extractGridApiFromRowNode(eventRowNode) !== adaptable.agGridAdapter?.getAgGridApi(true)) {
137
- return;
138
- }
139
- if (adaptable.isDestroyed) {
140
- return;
141
- }
142
- const fn = adaptable.rowListeners ? adaptable.rowListeners[event.type] : null;
143
- if (fn) {
144
- fn(event);
145
- }
146
- });
147
- }
148
- }
149
- };
150
- const adaptableInstances = {};
151
114
  const publishTimestamp = Number(ADAPTABLE_PUBLISH_TIMESTAMP);
152
115
  export class AdaptableAgGrid {
153
116
  logger;
154
- _id;
155
117
  lifecycleState;
156
118
  variant;
157
119
  emitter;
@@ -188,7 +150,7 @@ export class AdaptableAgGrid {
188
150
  ThemeService;
189
151
  LicenseService;
190
152
  ChartingService;
191
- rowListeners;
153
+ isListeningToAgGridRowNodeEvents = false;
192
154
  debouncedSetSelectedRows;
193
155
  debouncedSetSelectedCells;
194
156
  agGridListenerKeydown;
@@ -226,18 +188,6 @@ export class AdaptableAgGrid {
226
188
  this.agGridModulesAdapter = new AgGridModulesAdapter(this);
227
189
  this.DataService = new DataService(this);
228
190
  }
229
- static forEachAdaptable(fn) {
230
- Object.keys(adaptableInstances).forEach((key) => {
231
- fn(adaptableInstances[key]);
232
- });
233
- }
234
- static collectInstance(adaptable, adaptableId) {
235
- adaptable._id = adaptableId;
236
- adaptableInstances[adaptable._id] = adaptable;
237
- }
238
- static dismissInstance(adaptable) {
239
- delete adaptableInstances[adaptable._id];
240
- }
241
191
  get isAgGridInitialising() {
242
192
  return this.lifecycleState === 'initAgGrid';
243
193
  }
@@ -330,7 +280,6 @@ export class AdaptableAgGrid {
330
280
  const adaptableId = this._rawAdaptableOptions.adaptableId;
331
281
  const initMarker = getMarker(adaptableId).track.Init.label.Init.start();
332
282
  const perfInitAdaptableAgGrid = this.logger.beginPerf(`Adaptable._initAdaptableAgGrid()`);
333
- AdaptableAgGrid.collectInstance(this, this._rawAdaptableOptions.adaptableId);
334
283
  this.variant = config.variant;
335
284
  this.initWithLazyData =
336
285
  config.gridOptions.rowData == undefined || config.gridOptions.rowData.length === 0;
@@ -669,6 +618,7 @@ export class AdaptableAgGrid {
669
618
  agGridApi.onFilterChanged();
670
619
  filteringApplied = true;
671
620
  }
621
+ agGridApi.refreshCells();
672
622
  this._emit('AdapTableFiltersApplied');
673
623
  return filteringApplied;
674
624
  }
@@ -1055,6 +1005,14 @@ export class AdaptableAgGrid {
1055
1005
  return result;
1056
1006
  };
1057
1007
  });
1008
+ this.agGridOptionsService.setGridOptionsProperty(gridOptions, 'getColumnMenuItems', (original_getColumnMenuItems) => {
1009
+ return (params) => {
1010
+ if (params.source === 'columnMenu') {
1011
+ return this.agGridMenuAdapter.buildColumnMenu(params, original_getColumnMenuItems);
1012
+ }
1013
+ return this.agGridMenuAdapter.buildColumnsToolPanelMenu(params);
1014
+ };
1015
+ });
1058
1016
  this.agGridOptionsService.setGridOptionsProperty(gridOptions, 'getMainMenuItems', (original_getMainMenuItems) => {
1059
1017
  return (params) => {
1060
1018
  return this.agGridMenuAdapter.buildColumnMenu(params, original_getMainMenuItems);
@@ -1300,8 +1258,7 @@ export class AdaptableAgGrid {
1300
1258
  this.logger.info('Initializing with lazy data (no initial rowData).');
1301
1259
  }
1302
1260
  if (!this.getAgGridContainerElement()) {
1303
- const gridRoot = this.agGridAdapter.getAgGridRootElement();
1304
- const gridContainer = gridRoot?.closest('[class*="ag-theme"]');
1261
+ const gridContainer = this.deriveAgGridContainerElement(agGridApi.getGridOption('theme') === 'legacy');
1305
1262
  if (!gridContainer) {
1306
1263
  this.logger.consoleError('Failed to derive AG Grid container from the framework wrapper. Please provide a valid container element via ContainerOptions.agGridContainer.');
1307
1264
  }
@@ -1321,6 +1278,27 @@ export class AdaptableAgGrid {
1321
1278
  const agGridApi = createGrid(agGridContainer, gridOptions, gridParams);
1322
1279
  return agGridApi;
1323
1280
  }
1281
+ deriveAgGridContainerElement(isLegacyTheme) {
1282
+ const gridRoot = this.agGridAdapter.getAgGridRootElement();
1283
+ if (!gridRoot) {
1284
+ return undefined;
1285
+ }
1286
+ let hostElement = gridRoot;
1287
+ while (hostElement && hostElement.classList?.contains('ag-styled-root')) {
1288
+ hostElement = hostElement.parentElement;
1289
+ }
1290
+ if (!hostElement) {
1291
+ return undefined;
1292
+ }
1293
+ const hasAgThemeClass = (element) => Array.from(element.classList ?? []).some((cls) => cls.indexOf('ag-theme-') === 0);
1294
+ if (isLegacyTheme && !hasAgThemeClass(hostElement)) {
1295
+ const themedAncestor = hostElement.parentElement?.closest('[class*="ag-theme-"]');
1296
+ if (themedAncestor && hasAgThemeClass(themedAncestor)) {
1297
+ return themedAncestor;
1298
+ }
1299
+ }
1300
+ return hostElement;
1301
+ }
1324
1302
  useRowNodeLookUp() {
1325
1303
  return this.agGridAdapter.getAgGridApi().getGridOption('getRowId') != undefined;
1326
1304
  }
@@ -1452,7 +1430,10 @@ export class AdaptableAgGrid {
1452
1430
  if (this.api.formatColumnApi.getActiveFormatColumns().some((fc) => {
1453
1431
  return fc.ColumnGroupScope != null;
1454
1432
  })) {
1455
- this.updateColumnModelAndRefreshGrid();
1433
+ this.updateColumnModelAndRefreshGrid({
1434
+ cells: true,
1435
+ header: this.api.formatColumnApi.internalApi.hasFormatColumnsTargetingHeader(),
1436
+ });
1456
1437
  }
1457
1438
  }));
1458
1439
  this.agGridAdapter.getAgGridApi().addEventListener('columnPivotChanged', (this.listenerPivotChanged = (params) => {
@@ -1519,25 +1500,23 @@ export class AdaptableAgGrid {
1519
1500
  };
1520
1501
  this.agGridAdapter.getAgGridApi().addGlobalListener(this.listenerGlobalChartingChanges);
1521
1502
  }
1522
- this.rowListeners = {
1523
- dataChanged: (event) => {
1524
- this.onRowDataChanged({
1525
- rowNode: event.node,
1526
- oldData: event.oldData,
1527
- newData: event.newData,
1528
- });
1529
- },
1530
- cellChanged: (event) => {
1531
- if (event.column) {
1532
- this.onCellDataChanged({
1533
- rowNode: event.node,
1534
- oldValue: event.oldValue,
1535
- newValue: event.newValue,
1536
- colId: event.column.colId,
1537
- });
1538
- }
1539
- },
1540
- };
1503
+ this.isListeningToAgGridRowNodeEvents = true;
1504
+ }
1505
+ onAgGridRowNodeEvent(event) {
1506
+ if (!this.isListeningToAgGridRowNodeEvents) {
1507
+ return;
1508
+ }
1509
+ if (event.type === 'dataChanged') {
1510
+ const { node, oldData, newData } = event;
1511
+ this.onRowDataChanged({ rowNode: node, oldData, newData });
1512
+ return;
1513
+ }
1514
+ if (event.type === 'cellChanged') {
1515
+ const { node, column, oldValue, newValue } = event;
1516
+ if (column) {
1517
+ this.onCellDataChanged({ rowNode: node, oldValue, newValue, colId: column.getColId() });
1518
+ }
1519
+ }
1541
1520
  }
1542
1521
  shouldAutoSizeLayout() {
1543
1522
  const { layoutApi } = this.api;
@@ -1908,29 +1887,87 @@ export class AdaptableAgGrid {
1908
1887
  }
1909
1888
  return rawValue;
1910
1889
  }
1911
- __updateColumnModelAndRefreshGrid_already_called = false;
1890
+ __columnModelRefreshDoneInThisTick = null;
1912
1891
  __forceUpdateColumnModel = false;
1913
- updateColumnModelAndRefreshGrid() {
1914
- if (this.__updateColumnModelAndRefreshGrid_already_called && !this.__forceUpdateColumnModel) {
1892
+ updateColumnModelAndRefreshGrid(refreshScope = FULL_GRID_REFRESH_SCOPE) {
1893
+ const alreadyDone = this.__columnModelRefreshDoneInThisTick;
1894
+ if (alreadyDone && !this.__forceUpdateColumnModel) {
1895
+ const missingScope = getMissingGridRefreshScope(alreadyDone, refreshScope);
1896
+ if (missingScope) {
1897
+ this.refreshGridAfterColumnModelUpdate(missingScope);
1898
+ this.__columnModelRefreshDoneInThisTick = mergeGridRefreshScopes(alreadyDone, missingScope);
1899
+ }
1915
1900
  return;
1916
1901
  }
1917
1902
  this.__forceUpdateColumnModel = false;
1918
1903
  try {
1919
- this._updateColumnModelAndRefreshGridNow();
1920
- this.__updateColumnModelAndRefreshGrid_already_called = true;
1904
+ this._updateColumnModelAndRefreshGridNow(refreshScope);
1905
+ this.__columnModelRefreshDoneInThisTick = alreadyDone
1906
+ ? mergeGridRefreshScopes(alreadyDone, refreshScope)
1907
+ : { ...refreshScope };
1921
1908
  }
1922
1909
  finally {
1923
1910
  queueMicrotask(() => {
1924
- this.__updateColumnModelAndRefreshGrid_already_called = false;
1911
+ this.__columnModelRefreshDoneInThisTick = null;
1925
1912
  });
1926
1913
  }
1927
1914
  }
1928
- _updateColumnModelAndRefreshGridNow() {
1929
- this.logger.info('Updating column model and refreshing grid.');
1915
+ _updateColumnModelAndRefreshGridNow(refreshScope = FULL_GRID_REFRESH_SCOPE) {
1916
+ const startTime = performance.now();
1917
+ const adaptableId = this.adaptableOptions.adaptableId;
1918
+ const runtimeTrack = () => getMarker(adaptableId).track.Runtime.label;
1919
+ const colDefsVersionBefore = this.agGridAdapter.DANGER_getColDefsVersion();
1920
+ const passMarker = runtimeTrack().UpdateColumnModel.start();
1921
+ const deriveMarker = runtimeTrack().DeriveColumnState.start();
1930
1922
  this.deriveAdaptableColumnStateFromAgGrid();
1923
+ deriveMarker.end();
1924
+ const setupColumnsMarker = runtimeTrack().SetupColumns.start();
1931
1925
  this.agGridColumnAdapter.setupColumns();
1932
- this.redrawBody();
1933
- this.refreshHeader();
1926
+ setupColumnsMarker.end();
1927
+ this.refreshGridAfterColumnModelUpdate(refreshScope);
1928
+ const colDefsVersionAfter = this.agGridAdapter.DANGER_getColDefsVersion();
1929
+ const colDefsChanged = colDefsVersionBefore != null && colDefsVersionAfter != null
1930
+ ? colDefsVersionAfter - colDefsVersionBefore
1931
+ : -1;
1932
+ const columnCount = this.api.columnApi.getColumns().length;
1933
+ const refreshScopeLabel = describeGridRefreshScope(refreshScope);
1934
+ passMarker.end({
1935
+ details: [
1936
+ { name: 'columns', value: columnCount },
1937
+ { name: 'colDefsChanged', value: colDefsChanged },
1938
+ { name: 'refresh', value: refreshScopeLabel },
1939
+ ],
1940
+ });
1941
+ this.logger.info(`Updated column model and refreshed grid in ${Math.round(performance.now() - startTime)}ms (${columnCount} columns, ${colDefsChanged} colDefs changed, refresh: ${refreshScopeLabel}).`);
1942
+ }
1943
+ refreshGridAfterColumnModelUpdate(refreshScope) {
1944
+ const runtimeTrack = getMarker(this.adaptableOptions.adaptableId).track.Runtime.label;
1945
+ if (refreshScope.rows) {
1946
+ const redrawBodyMarker = runtimeTrack.RedrawBody.start();
1947
+ this.redrawBody();
1948
+ redrawBodyMarker.end();
1949
+ }
1950
+ else if (refreshScope.cells) {
1951
+ const refreshCellsMarker = runtimeTrack.RefreshCells.start();
1952
+ this.refreshRenderedCells();
1953
+ refreshCellsMarker.end();
1954
+ }
1955
+ if (refreshScope.header) {
1956
+ const refreshHeaderMarker = runtimeTrack.RefreshHeader.start();
1957
+ this.refreshHeader();
1958
+ refreshHeaderMarker.end();
1959
+ }
1960
+ }
1961
+ refreshRenderedCells() {
1962
+ this.agGridAdapter.getAgGridApi().refreshCells({ force: true, suppressFlash: true });
1963
+ this.refreshAgGridFindIfNeeded();
1964
+ this._emit('GridRefreshed');
1965
+ }
1966
+ refreshSortIfColumnsSorted() {
1967
+ const columnSorts = this.api.layoutApi.getCurrentLayout()?.ColumnSorts;
1968
+ if (columnSorts?.length) {
1969
+ this.layoutManager.refreshSort();
1970
+ }
1934
1971
  }
1935
1972
  redrawBody() {
1936
1973
  this.redrawRows();
@@ -3415,6 +3452,7 @@ export class AdaptableAgGrid {
3415
3452
  'doesExternalFilterPass',
3416
3453
  'excelStyles',
3417
3454
  'getContextMenuItems',
3455
+ 'getColumnMenuItems',
3418
3456
  'getMainMenuItems',
3419
3457
  'getRowClass',
3420
3458
  'getRowId',
@@ -3458,13 +3496,12 @@ export class AdaptableAgGrid {
3458
3496
  this.agGridThemeAdapter = null;
3459
3497
  this.agGridModulesAdapter?.destroy();
3460
3498
  this.agGridModulesAdapter = null;
3461
- this.rowListeners = null;
3499
+ this.isListeningToAgGridRowNodeEvents = false;
3462
3500
  this.emitter.destroy();
3463
3501
  this.emitter = null;
3464
3502
  this.adaptableOptions = null;
3465
3503
  this.columnMinMaxValuesCache = null;
3466
3504
  this.lifecycleState = 'preDestroyed';
3467
- AdaptableAgGrid.dismissInstance(this);
3468
3505
  if (config?.unmount) {
3469
3506
  const abContainerElement = this.getAdaptableContainerElement();
3470
3507
  if (abContainerElement != null) {
@@ -1,9 +1,34 @@
1
- import { ColDef, ColGroupDef, Column, GridApi, GridOptions, IRowNode, ManagedGridOptionKey, ManagedGridOptions, Module } from 'ag-grid-enterprise';
1
+ import { AgEvent, ColDef, ColGroupDef, Column, GridApi, GridOptions, IRowNode, ManagedGridOptionKey, ManagedGridOptions, Module } from 'ag-grid-enterprise';
2
2
  import type { AgChartInstance, AgSparklineOptions } from 'ag-charts-types';
3
3
  import { AdaptableAgGrid } from './AdaptableAgGrid';
4
4
  import { AdaptableColumn, AdaptableColumnGroup } from '../AdaptableState/Common/AdaptableColumn';
5
5
  import { SelectedCellInfo } from '../AdaptableState/Selection/SelectedCellInfo';
6
6
  import { SelectedRowInfo } from '../AdaptableState/Selection/SelectedRowInfo';
7
+ /**
8
+ * #local_event_service_monkey_patch
9
+ *
10
+ * AG Grid does not expose the RowNode `cellChanged` and `dataChanged` events (they are dispatched only on the
11
+ * RowNode's own, lazily created, `LocalEventService`), so we override `LocalEventService.prototype.dispatchEvent`
12
+ * and hook our own data change detection into it.
13
+ *
14
+ * Since AG Grid v36, `LocalEventService` lives in the `ag-stack` package (not in `ag-grid-community`).
15
+ * Unlike a duplicated `ag-grid-community`, a duplicated `ag-stack` copy in a consumer bundle
16
+ * (different install location, CJS vs ESM, version skew) is SILENT - AG Grid keeps working,
17
+ * but a prototype patched via `import { LocalEventService } from 'ag-stack'` would never be hit
18
+ * by the grid, because the grid instantiates the class from ITS copy of the module.
19
+ *
20
+ * Because of this we don't patch the statically imported class; instead we patch the prototype
21
+ * of the `LocalEventService` instance actually used by the running grid (`beans.eventSvc.globalSvc`)
22
+ * - one patch per distinct prototype, regardless of how many copies of `ag-stack` are loaded.
23
+ *
24
+ * The patched prototype is shared by ALL grids (and all their RowNodes), so the patch identifies the
25
+ * originating grid via `rowNode.beans` and routes the event to that grid's AgGridAdapter
26
+ * (see DANGER_AG_GRID_ADAPTERS_MAP & AgGridAdapter.handleRowNodeEvent).
27
+ */
28
+ export type RowNodeEventType = 'dataChanged' | 'cellChanged';
29
+ export interface RowNodeEvent extends AgEvent<RowNodeEventType> {
30
+ node: IRowNode;
31
+ }
7
32
  export type AgGridThemeMode = 'legacy' | 'themingApi';
8
33
  export declare class AgGridAdapter {
9
34
  private _adaptableInstance;
@@ -26,6 +51,11 @@ export declare class AgGridAdapter {
26
51
  private getActivePivotColumnFilters;
27
52
  private get agGridOptionsService();
28
53
  setAgGridId(agGridId: string): void;
54
+ /**
55
+ * see #local_event_service_monkey_patch
56
+ * Called (for the grid managed by this adapter) whenever AG Grid dispatches a RowNode `cellChanged` / `dataChanged` event
57
+ */
58
+ handleRowNodeEvent(event: RowNodeEvent): void;
29
59
  setAgGridApi(gridApi: GridApi): void;
30
60
  getAgGridApi(skipLogging?: boolean): GridApi | undefined;
31
61
  monkeyPatchingGridOptionsUpdates(): void;
@@ -34,6 +64,8 @@ export declare class AgGridAdapter {
34
64
  private DANGER_getPrivateAgGridBeans;
35
65
  DANGER_getCreateSparkline(): ((options: AgSparklineOptions) => AgChartInstance<AgSparklineOptions>) | undefined;
36
66
  DANGER_getLiveGridOptions(): GridOptions<any>;
67
+ DANGER_getColDefsVersion(): number | undefined;
68
+ DANGER_invalidateSortCache(): void;
37
69
  getAgGridRootElement(): HTMLElement;
38
70
  /**
39
71
  * When AG Grid is rendered the first time, the AG GridApi is not yet set in the Adaptable context (as it's set only AFTER the grid is fully initialised)
@@ -1,4 +1,5 @@
1
1
  import { ColumnApiModule, } from 'ag-grid-enterprise';
2
+ import { AdaptableLogger } from './AdaptableLogger';
2
3
  import { ACTION_COLUMN_TYPE, CALCULATED_COLUMN_TYPE, FDC3_COLUMN_TYPE, FREE_TEXT_COLUMN_TYPE, } from '../AdaptableState/Common/AdaptableColumn';
3
4
  import { ACCESS_LEVEL_HIDDEN, ADAPTABLE_FDC3_ACTION_COLUMN_FRIENDLY_NAME, } from '../Utilities/Constants/GeneralConstants';
4
5
  import { createUuid } from '../AdaptableState/Uuid';
@@ -10,7 +11,84 @@ import { isPivotColumnTotal } from '../layout-manager/src/isPivotColumnTotal';
10
11
  import { isPivotAggTotalColumn } from '../layout-manager/src/isPivotAggTotalColumn';
11
12
  import { ONLY_AGG_FN_NAME, WEIGHTED_AVERAGE_AGG_FN_NAME, } from '../AdaptableState/Common/AggregationColumns';
12
13
  const DANGER_AG_GRID_BEANS_MAP = {};
14
+ const DANGER_AG_GRID_ADAPTERS_MAP = {};
13
15
  const getColumnApiModule = () => ColumnApiModule;
16
+ const PATCHED_LOCAL_EVENT_SERVICE_PROTOTYPES = new WeakSet();
17
+ const patchLocalEventServiceDispatchEvent = (beans) => {
18
+ const localEventServiceInstance = beans?.eventSvc?.globalSvc;
19
+ const LocalEventService_Prototype = localEventServiceInstance
20
+ ? Object.getPrototypeOf(localEventServiceInstance)
21
+ : null;
22
+ if (!LocalEventService_Prototype ||
23
+ typeof LocalEventService_Prototype.dispatchEvent !== 'function') {
24
+ AdaptableLogger.consoleErrorBase(`CRITICAL: could not get hold of AG Grid LocalEventService, data change detection will NOT work!`, beans);
25
+ return;
26
+ }
27
+ if (PATCHED_LOCAL_EVENT_SERVICE_PROTOTYPES.has(LocalEventService_Prototype)) {
28
+ return;
29
+ }
30
+ PATCHED_LOCAL_EVENT_SERVICE_PROTOTYPES.add(LocalEventService_Prototype);
31
+ const LocalEventService_dispatchEvent = LocalEventService_Prototype.dispatchEvent;
32
+ LocalEventService_Prototype.dispatchEvent = function (event) {
33
+ LocalEventService_dispatchEvent.apply(this, arguments);
34
+ if (event.type !== 'cellChanged' && event.type !== 'dataChanged') {
35
+ return;
36
+ }
37
+ const rowNodeEvent = event;
38
+ const rowNode = rowNodeEvent.node;
39
+ if (!rowNode) {
40
+ AdaptableLogger.consoleErrorBase(`No RowNode found in passed event, this should never happen!`, event);
41
+ return;
42
+ }
43
+ const gridId = rowNode.beans?.context?.getId();
44
+ if (!gridId) {
45
+ AdaptableLogger.consoleErrorBase(`No gridId found in passed RowNode, this should never happen!`, rowNode);
46
+ return;
47
+ }
48
+ DANGER_AG_GRID_ADAPTERS_MAP[gridId]?.handleRowNodeEvent(rowNodeEvent);
49
+ };
50
+ };
51
+ const patchVisibleColsPartition = (beans) => {
52
+ const visibleCols = beans?.visibleCols;
53
+ if (!visibleCols || typeof visibleCols.partitionVisibleCols !== 'function') {
54
+ console.error(`CRITICAL: could not get hold of AG Grid VisibleColsService.partitionVisibleCols, the selection column may NOT be displayed when it is the last column!`);
55
+ return;
56
+ }
57
+ const original_partitionVisibleCols = visibleCols.partitionVisibleCols;
58
+ visibleCols.partitionVisibleCols = function () {
59
+ const result = original_partitionVisibleCols.apply(this, arguments);
60
+ const leftCols = result?.leftCols ?? [];
61
+ const rightCols = result?.rightCols ?? [];
62
+ const centerCols = result?.centerCols ?? [];
63
+ if (leftCols.length + rightCols.length + centerCols.length === 0) {
64
+ return result;
65
+ }
66
+ const colModel = beans.colModel;
67
+ const selectionCol = colModel?.colsList?.find((col) => col?.colKind === 'selection');
68
+ if (!selectionCol || !selectionCol.visible) {
69
+ return result;
70
+ }
71
+ if (leftCols.includes(selectionCol) ||
72
+ rightCols.includes(selectionCol) ||
73
+ centerCols.includes(selectionCol)) {
74
+ return result;
75
+ }
76
+ const showAutoGroupAndValuesOnly = colModel.pivotMode && !colModel.showingPivotResult;
77
+ if (showAutoGroupAndValuesOnly && !(beans.selectionColSvc?.isEnabled?.() ?? false)) {
78
+ return result;
79
+ }
80
+ if (selectionCol.pinned === 'right') {
81
+ rightCols.push(selectionCol);
82
+ }
83
+ else if (selectionCol.pinned) {
84
+ leftCols.push(selectionCol);
85
+ }
86
+ else {
87
+ centerCols.push(selectionCol);
88
+ }
89
+ return result;
90
+ };
91
+ };
14
92
  export class AgGridAdapter {
15
93
  _adaptableInstance;
16
94
  DANGER_USE_GETTER_gridApi;
@@ -38,11 +116,14 @@ export class AgGridAdapter {
38
116
  console.error('CRITICAL: No gridId found in beans, this should never happen!');
39
117
  }
40
118
  DANGER_AG_GRID_BEANS_MAP[gridId] = beans;
119
+ patchLocalEventServiceDispatchEvent(beans);
120
+ patchVisibleColsPartition(beans);
41
121
  };
42
122
  }
43
123
  }
44
124
  destroy() {
45
125
  delete DANGER_AG_GRID_BEANS_MAP[this._agGridId];
126
+ delete DANGER_AG_GRID_ADAPTERS_MAP[this._agGridId];
46
127
  this.initialGridOptions = null;
47
128
  this.DANGER_gridApi_from_args = null;
48
129
  this.DANGER_USE_GETTER_gridApi = null;
@@ -86,6 +167,10 @@ export class AgGridAdapter {
86
167
  }
87
168
  setAgGridId(agGridId) {
88
169
  this._agGridId = agGridId;
170
+ DANGER_AG_GRID_ADAPTERS_MAP[agGridId] = this;
171
+ }
172
+ handleRowNodeEvent(event) {
173
+ this._adaptableInstance?.onAgGridRowNodeEvent(event);
89
174
  }
90
175
  setAgGridApi(gridApi) {
91
176
  this.DANGER_USE_GETTER_gridApi = gridApi;
@@ -223,6 +308,12 @@ export class AgGridAdapter {
223
308
  DANGER_getLiveGridOptions() {
224
309
  return this.DANGER_getPrivateAgGridBeans()?.gridOptions;
225
310
  }
311
+ DANGER_getColDefsVersion() {
312
+ return this.DANGER_getPrivateAgGridBeans()?.colModel?.colDefsVersion;
313
+ }
314
+ DANGER_invalidateSortCache() {
315
+ this.DANGER_getPrivateAgGridBeans()?.sortSvc?.invalidate();
316
+ }
226
317
  getAgGridRootElement() {
227
318
  return this.DANGER_getPrivateAgGridBeans()?.eGridDiv;
228
319
  }