diff --git a/.env b/.env index 6330f63e9ba..85421b83698 100644 --- a/.env +++ b/.env @@ -1,6 +1,6 @@ # Production Build -BUILD_GRID_VERSION=35.1.0-beta.20260316.108 -BUILD_CHARTS_VERSION=13.1.0-beta.20260316 +BUILD_GRID_VERSION=35.1.0-beta.20260317.135 +BUILD_CHARTS_VERSION=13.1.0-beta.20260317 ENV=local NX_BATCH_MODE=true NX_ADD_PLUGINS=false diff --git a/community-modules/locale/package.json b/community-modules/locale/package.json index 10c23da8bbd..46e8b6b86a7 100644 --- a/community-modules/locale/package.json +++ b/community-modules/locale/package.json @@ -1,6 +1,6 @@ { "name": "@ag-grid-community/locale", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "Localisation Module for AG Grid, providing translations in 31 languages.", "main": "./dist/package/main.cjs.js", "types": "./dist/types/src/main.d.ts", diff --git a/community-modules/styles/package.json b/community-modules/styles/package.json index 887d6b5b8cf..ae6060f5592 100644 --- a/community-modules/styles/package.json +++ b/community-modules/styles/package.json @@ -1,6 +1,6 @@ { "name": "@ag-grid-community/styles", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "AG Grid Styles and Themes", "main": "_index.scss", "files": [ diff --git a/documentation/ag-grid-docs/package.json b/documentation/ag-grid-docs/package.json index 1f3ecdf6a23..7c7fc94dbe3 100644 --- a/documentation/ag-grid-docs/package.json +++ b/documentation/ag-grid-docs/package.json @@ -2,7 +2,7 @@ "name": "ag-grid-docs", "description": "Documentation for AG Grid", "type": "module", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "repository": { "type": "git", "url": "https://github.com/ag-grid/ag-grid.git" @@ -53,17 +53,17 @@ "@types/react": "^18.2.47", "@types/react-dom": "^18.2.18", "@pqina/flip": "^1.8.4", - "ag-charts-angular": "13.1.0-beta.20260316", - "ag-charts-community": "13.1.0-beta.20260316", - "ag-charts-enterprise": "13.1.0-beta.20260316", - "ag-charts-types": "13.1.0-beta.20260316", - "ag-charts-react": "13.1.0-beta.20260316", - "ag-charts-vue3": "13.1.0-beta.20260316", - "ag-grid-angular": "35.1.0-beta.20260316.108", - "ag-grid-community": "35.1.0-beta.20260316.108", - "ag-grid-enterprise": "35.1.0-beta.20260316.108", - "ag-grid-react": "35.1.0-beta.20260316.108", - "ag-grid-vue3": "35.1.0-beta.20260316.108", + "ag-charts-angular": "13.1.0-beta.20260317", + "ag-charts-community": "13.1.0-beta.20260317", + "ag-charts-enterprise": "13.1.0-beta.20260317", + "ag-charts-types": "13.1.0-beta.20260317", + "ag-charts-react": "13.1.0-beta.20260317", + "ag-charts-vue3": "13.1.0-beta.20260317", + "ag-grid-angular": "35.1.0-beta.20260317.135", + "ag-grid-community": "35.1.0-beta.20260317.135", + "ag-grid-enterprise": "35.1.0-beta.20260317.135", + "ag-grid-react": "35.1.0-beta.20260317.135", + "ag-grid-vue3": "35.1.0-beta.20260317.135", "algoliasearch": "^4.18.0", "astro": "5.16.6", "cheerio": "^1.0.0", diff --git a/documentation/ag-grid-docs/src/content/api-documentation/grid-api/api.json b/documentation/ag-grid-docs/src/content/api-documentation/grid-api/api.json index 914a3bd29af..cc7fcc31d45 100644 --- a/documentation/ag-grid-docs/src/content/api-documentation/grid-api/api.json +++ b/documentation/ag-grid-docs/src/content/api-documentation/grid-api/api.json @@ -662,8 +662,24 @@ "displayName": "Row Expand and Collapse", "description": "See [Row Grouping](./grouping-opening-groups/), [Master Detail](./master-detail/) or [Tree Data](./tree-data-opening-groups/) for more information" }, - "expandAll": {}, - "collapseAll": {}, + "expandAll": { + "more": { + "name": "Expanding Groups", + "url": "./grouping-opening-groups/#api" + } + }, + "collapseAll": { + "more": { + "name": "Expanding Groups", + "url": "./grouping-opening-groups/#api" + } + }, + "resetRowGroupExpansion": { + "more": { + "name": "Reset Group Expansion", + "url": "./grouping-opening-groups/#reset-group-expansion" + } + }, "setRowNodeExpanded": { "more": { "name": "Expand to a Row", diff --git a/documentation/ag-grid-docs/src/content/api-documentation/grid-options/properties.json b/documentation/ag-grid-docs/src/content/api-documentation/grid-options/properties.json index d78e3f4bd04..57a60cd22d9 100644 --- a/documentation/ag-grid-docs/src/content/api-documentation/grid-options/properties.json +++ b/documentation/ag-grid-docs/src/content/api-documentation/grid-options/properties.json @@ -1731,8 +1731,18 @@ "url": "./server-side-model-pivoting/#supplying-pivot-result-fields-simple" } }, - "serverSideSortAllLevels": {}, - "serverSideEnableClientSideSort": {}, + "serverSideSortAllLevels": { + "more": { + "name": "Sorting All Levels", + "url": "./server-side-model-sorting/#sorting-all-levels" + } + }, + "serverSideEnableClientSideSort": { + "more": { + "name": "Client-side Sorting", + "url": "./server-side-model-sorting/#client-side-sorting" + } + }, "serverSideOnlyRefreshFilteredGroups": { "more": { "name": "Filters", diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/example.spec.ts new file mode 100644 index 00000000000..20e1e009d24 --- /dev/null +++ b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/example.spec.ts @@ -0,0 +1,34 @@ +import { expect, test } from '@utils/grid/test-utils'; + +test.agExample(import.meta, () => { + test.eachFramework('Example', async ({ page, agIdFor }) => { + const australiaId = 'row-group-country-Australia'; + const australia2004Id = 'row-group-country-Australia-year-2004'; + const unitedStatesId = 'row-group-country-United States'; + + // Initial state: isGroupOpenByDefault expands Australia > 2004 + await expect(agIdFor.autoGroupExpanded(australiaId)).toBeVisible(); + await expect(agIdFor.autoGroupExpanded(australia2004Id)).toBeVisible(); + await expect(agIdFor.autoGroupContracted(unitedStatesId)).toBeVisible(); + + // Expand All — United States should now be expanded + await page.getByRole('button', { name: 'Expand All' }).click(); + await expect(agIdFor.autoGroupExpanded(unitedStatesId)).toBeVisible(); + + // Reset to Defaults — back to isGroupOpenByDefault state + await page.getByRole('button', { name: 'Reset to Defaults' }).click(); + await expect(agIdFor.autoGroupExpanded(australiaId)).toBeVisible(); + await expect(agIdFor.autoGroupExpanded(australia2004Id)).toBeVisible(); + await expect(agIdFor.autoGroupContracted(unitedStatesId)).toBeVisible(); + + // Collapse All — everything collapsed + await page.getByRole('button', { name: 'Collapse All' }).click(); + await expect(agIdFor.autoGroupContracted(australiaId)).toBeVisible(); + + // Reset to Defaults — Australia > 2004 expanded again + await page.getByRole('button', { name: 'Reset to Defaults' }).click(); + await expect(agIdFor.autoGroupExpanded(australiaId)).toBeVisible(); + await expect(agIdFor.autoGroupExpanded(australia2004Id)).toBeVisible(); + await expect(agIdFor.autoGroupContracted(unitedStatesId)).toBeVisible(); + }); +}); diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/index.html b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/index.html new file mode 100644 index 00000000000..1bd6b185e1f --- /dev/null +++ b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/index.html @@ -0,0 +1,8 @@ +
+
+ + + +
+
+
diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/main.ts b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/main.ts new file mode 100644 index 00000000000..22017c70ac5 --- /dev/null +++ b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/main.ts @@ -0,0 +1,64 @@ +import type { GridApi, GridOptions, IsGroupOpenByDefaultParams } from 'ag-grid-community'; +import { + ClientSideRowModelApiModule, + ClientSideRowModelModule, + ModuleRegistry, + RowApiModule, + ValidationModule, + createGrid, +} from 'ag-grid-community'; +import { RowGroupingModule } from 'ag-grid-enterprise'; + +ModuleRegistry.registerModules([ + RowApiModule, + ClientSideRowModelModule, + ClientSideRowModelApiModule, + RowGroupingModule, + ...(process.env.NODE_ENV !== 'production' ? [ValidationModule] : []), +]); + +let gridApi: GridApi; + +const gridOptions: GridOptions = { + columnDefs: [ + { field: 'country', rowGroup: true }, + { field: 'year', rowGroup: true }, + { field: 'sport' }, + { field: 'athlete' }, + { field: 'total' }, + ], + defaultColDef: { + flex: 1, + minWidth: 100, + }, + autoGroupColumnDef: { + minWidth: 200, + }, + isGroupOpenByDefault: (params: IsGroupOpenByDefaultParams) => { + const route = params.rowNode.getRoute(); + const destPath = ['Australia', '2004']; + return !!route?.every((item, idx) => destPath[idx] === item); + }, +}; + +function onBtExpandAll() { + gridApi!.expandAll(); +} + +function onBtCollapseAll() { + gridApi!.collapseAll(); +} + +function onBtResetExpansion() { + gridApi!.resetRowGroupExpansion(); +} + +// setup the grid after the page has finished loading +document.addEventListener('DOMContentLoaded', function () { + const gridDiv = document.querySelector('#myGrid')!; + gridApi = createGrid(gridDiv, gridOptions); + + fetch('https://www.ag-grid.com/example-assets/olympic-winners.json') + .then((response) => response.json()) + .then((data: IOlympicData[]) => gridApi!.setGridOption('rowData', data)); +}); diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/styles.css b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/styles.css new file mode 100644 index 00000000000..072f3617e55 --- /dev/null +++ b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/_examples/reset-group-expansion/styles.css @@ -0,0 +1,10 @@ +.example-wrapper { + display: flex; + flex-direction: column; + height: 100%; +} + +#myGrid { + flex: 1 1 0px; + width: 100%; +} diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/index.mdoc b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/index.mdoc index 3ce3a88ba68..947bf5b7d55 100644 --- a/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/index.mdoc +++ b/documentation/ag-grid-docs/src/content/docs/grouping-opening-groups/index.mdoc @@ -75,7 +75,7 @@ The row group expansion state can be saved and restored as part of [Grid State]( {% /note %} The grid exposes API methods to expand or collapse groups programmatically. -{% apiDocumentation source="grid-api/api.json" section="rowExpansion" names=["setRowNodeExpanded", "expandAll", "collapseAll"] /%} +{% apiDocumentation source="grid-api/api.json" section="rowExpansion" names=["setRowNodeExpanded", "expandAll", "collapseAll", "resetRowGroupExpansion"] /%} ### Expand Row Ancestors @@ -93,3 +93,11 @@ const expandToRow = () => { } } ``` + +### Reset Group Expansion + +After users have expanded or collapsed groups, `resetRowGroupExpansion()` discards all overrides and re-evaluates each group against the configured defaults (`groupDefaultExpanded` or `isGroupOpenByDefault`). + +In the example below, the `isGroupOpenByDefault` callback expands the `Australia > 2004` path by default. Try expanding or collapsing groups, then click **Reset to Defaults** to restore the original expansion state. + +{% gridExampleRunner title="Reset Group Expansion" name="reset-group-expansion" /%} diff --git a/documentation/update-algolia-indices/package.json b/documentation/update-algolia-indices/package.json index 16a665039e5..d6896934039 100644 --- a/documentation/update-algolia-indices/package.json +++ b/documentation/update-algolia-indices/package.json @@ -1,6 +1,6 @@ { "name": "update-algolia-indices", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "Update algolia indices", "main": "src/index.ts", "type": "module", diff --git a/package.json b/package.json index ceab2c959a3..893196768a6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "license": "MIT", "scripts": { "compressVideo": "tsx external/ag-website-shared/scripts/compress-video", diff --git a/packages/ag-grid-angular/package.json b/packages/ag-grid-angular/package.json index 9308fb4c7c8..4cdf1133ee0 100644 --- a/packages/ag-grid-angular/package.json +++ b/packages/ag-grid-angular/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-angular", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "AG Grid Angular Component", "scripts": { "clean": "rimraf dist", @@ -15,7 +15,7 @@ "module": "./dist/ag-grid-angular/fesm2022/ag-grid-angular.mjs", "typings": "./dist/ag-grid-angular/index.d.ts", "dependencies": { - "ag-grid-community": "35.1.0-beta.20260316.108", + "ag-grid-community": "35.1.0-beta.20260317.135", "@angular/animations": "^18.0.7", "@angular/common": "^18.0.7", "@angular/compiler": "^18.0.7", @@ -27,7 +27,7 @@ "zone.js": "~0.15.1" }, "devDependencies": { - "ag-grid-community": "35.1.0-beta.20260316.108", + "ag-grid-community": "35.1.0-beta.20260317.135", "@angular-devkit/build-angular": "^18.0.7", "@angular/cli": "^18.0.7", "@angular/forms": "^18.0.7", diff --git a/packages/ag-grid-angular/projects/ag-grid-angular/package.json b/packages/ag-grid-angular/projects/ag-grid-angular/package.json index 221f2b96f12..17271e576c8 100644 --- a/packages/ag-grid-angular/projects/ag-grid-angular/package.json +++ b/packages/ag-grid-angular/projects/ag-grid-angular/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-angular", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "AG Grid Angular Component", "license": "MIT", "peerDependencies": { @@ -8,7 +8,7 @@ "@angular/core": ">= 18.0.0" }, "dependencies": { - "ag-grid-community": "35.1.0-beta.20260316.108", + "ag-grid-community": "35.1.0-beta.20260317.135", "tslib": "^2.3.0" }, "repository": { diff --git a/packages/ag-grid-community/package.json b/packages/ag-grid-community/package.json index 35c21f8d2e9..a5d40612bb5 100644 --- a/packages/ag-grid-community/package.json +++ b/packages/ag-grid-community/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-community", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "Advanced Data Grid / Data Table supporting Javascript / Typescript / React / Angular / Vue", "main": "./dist/package/main.cjs.js", "types": "./dist/types/src/main.d.ts", @@ -119,7 +119,7 @@ ], "homepage": "https://www.ag-grid.com/", "dependencies": { - "ag-charts-types": "13.1.0-beta.20260316" + "ag-charts-types": "13.1.0-beta.20260317" }, "devDependencies": { "web-streams-polyfill": "^3.3.2", diff --git a/packages/ag-grid-community/src/api/gridApi.ts b/packages/ag-grid-community/src/api/gridApi.ts index f5b6d21ecde..90ad0dbddb3 100644 --- a/packages/ag-grid-community/src/api/gridApi.ts +++ b/packages/ag-grid-community/src/api/gridApi.ts @@ -529,6 +529,15 @@ export interface _CsrmSsrmSharedGridApi { * @agModule `ClientSideRowModelApiModule / ServerSideRowModelApiModule` */ collapseAll(): void; + + /** + * Reset all group expansion to defaults, as determined by `groupDefaultExpanded`, + * `isGroupOpenByDefault`, or `isServerSideGroupOpenByDefault`. + * Any user-initiated expand/collapse overrides are discarded. + * + * @agModule `ClientSideRowModelApiModule / ServerSideRowModelApiModule` + */ + resetRowGroupExpansion(): void; } export interface _RowModelSharedApi { @@ -631,7 +640,7 @@ export interface _ColumnResizeApi { } export interface _ColumnMoveApi { - /** Moves the column at `fromIdex` to `toIndex`. The column is first removed, then added at the `toIndex` location, thus index locations will change to the right of the column after the removal. */ + /** Moves the column at `fromIndex` to `toIndex`. The column is first removed, then added at the `toIndex` location, thus index locations will change to the right of the column after the removal. */ moveColumnByIndex(fromIndex: number, toIndex: number): void; /** Moves columns to `toIndex`. The columns are first removed, then added at the `toIndex` location, thus index locations will change to the right of the column after the removal. */ diff --git a/packages/ag-grid-community/src/api/gridApiFunctions.ts b/packages/ag-grid-community/src/api/gridApiFunctions.ts index 49f56ac938b..afd445844a6 100644 --- a/packages/ag-grid-community/src/api/gridApiFunctions.ts +++ b/packages/ag-grid-community/src/api/gridApiFunctions.ts @@ -287,6 +287,7 @@ export const gridApiFunctionsMap: Record = ...mod<_CsrmSsrmSharedGridApi>('CsrmSsrmSharedApi', { expandAll: 0, collapseAll: 0, + resetRowGroupExpansion: 0, }), ...mod<_SsrmInfiniteSharedGridApi>('SsrmInfiniteSharedApi', { setRowCount: 0, diff --git a/packages/ag-grid-community/src/api/rowModelSharedApi.ts b/packages/ag-grid-community/src/api/rowModelSharedApi.ts index 87950571ed2..c012dbf703d 100644 --- a/packages/ag-grid-community/src/api/rowModelSharedApi.ts +++ b/packages/ag-grid-community/src/api/rowModelSharedApi.ts @@ -22,3 +22,7 @@ export function resetRowHeights(beans: BeanCollection) { } beans.rowModel?.resetRowHeights(); } + +export function resetRowGroupExpansion(beans: BeanCollection) { + beans.expansionSvc?.resetExpansion(); +} diff --git a/packages/ag-grid-community/src/api/sharedApiModule.ts b/packages/ag-grid-community/src/api/sharedApiModule.ts index aa067bd639a..25239071127 100644 --- a/packages/ag-grid-community/src/api/sharedApiModule.ts +++ b/packages/ag-grid-community/src/api/sharedApiModule.ts @@ -1,7 +1,13 @@ import type { _ModuleWithApi } from '../interfaces/iModule'; import { VERSION } from '../version'; import type { _CsrmSsrmSharedGridApi, _RowModelSharedApi, _SsrmInfiniteSharedGridApi } from './gridApi'; -import { collapseAll, expandAll, onRowHeightChanged, resetRowHeights } from './rowModelSharedApi'; +import { + collapseAll, + expandAll, + onRowHeightChanged, + resetRowGroupExpansion, + resetRowHeights, +} from './rowModelSharedApi'; import { getCacheBlockState, isLastRowIndexKnown, setRowCount } from './ssrmInfiniteSharedApi'; // these modules are not used in core, but are shared between multiple other modules @@ -12,7 +18,7 @@ import { getCacheBlockState, isLastRowIndexKnown, setRowCount } from './ssrmInfi export const CsrmSsrmSharedApiModule: _ModuleWithApi<_CsrmSsrmSharedGridApi> = { moduleName: 'CsrmSsrmSharedApi', version: VERSION, - apiFunctions: { expandAll, collapseAll }, + apiFunctions: { expandAll, collapseAll, resetRowGroupExpansion }, }; /** diff --git a/packages/ag-grid-community/src/clientSideRowModel/clientSideNodeManager.ts b/packages/ag-grid-community/src/clientSideRowModel/clientSideNodeManager.ts index adc4c0ca83f..5fbfc35ec1a 100644 --- a/packages/ag-grid-community/src/clientSideRowModel/clientSideNodeManager.ts +++ b/packages/ag-grid-community/src/clientSideRowModel/clientSideNodeManager.ts @@ -338,7 +338,6 @@ export class ClientSideNodeManager extends BeanStub { node.parent = this.rootNode; node.level = level; node.group = false; - node.expanded = false; if (sourceRowIndex != null) { node.sourceRowIndex = sourceRowIndex; } @@ -413,6 +412,7 @@ const adjustAddIndexForDataPath = (allLeafs: RowNode[], addIndex: const initRootNode = (rootNode: RowNode): RowNode => { rootNode.group = true; rootNode.level = -1; + rootNode._expanded = true; rootNode.id = 'ROOT_NODE_ID'; if (rootNode._leafs?.length !== 0) { rootNode._leafs = []; diff --git a/packages/ag-grid-community/src/context/context.ts b/packages/ag-grid-community/src/context/context.ts index 3764ad5ca7b..b6e754ebb6b 100644 --- a/packages/ag-grid-community/src/context/context.ts +++ b/packages/ag-grid-community/src/context/context.ts @@ -60,7 +60,11 @@ import type { AgGridCommon } from '../interfaces/iCommon'; import type { IContextMenuService } from '../interfaces/iContextMenu'; import type { ICsvCreator } from '../interfaces/iCsvCreator'; import type { IExcelCreator } from '../interfaces/iExcelCreator'; -import type { IExpansionService } from '../interfaces/iExpansionService'; +import type { + IExpansionService, + RowGroupBulkExpansionState, + RowGroupExpansionState, +} from '../interfaces/iExpansionService'; import type { IFindService } from '../interfaces/iFind'; import type { IFooterService } from '../interfaces/iFooterService'; import type { IFrameworkOverrides } from '../interfaces/iFrameworkOverrides'; @@ -334,7 +338,7 @@ interface CoreBeanCollection pageBounds: PageBoundsService; apiFunctionSvc: ApiFunctionService; gridDestroySvc: GridDestroyService; - expansionSvc?: IExpansionService; + expansionSvc?: IExpansionService; sideBar?: ISideBarService; ssrmTxnManager?: IServerSideTransactionManager; aggFuncSvc?: IAggFuncService; diff --git a/packages/ag-grid-community/src/entities/rowNode.ts b/packages/ag-grid-community/src/entities/rowNode.ts index f4b8848209a..64c34a75ec4 100644 --- a/packages/ag-grid-community/src/entities/rowNode.ts +++ b/packages/ag-grid-community/src/entities/rowNode.ts @@ -244,8 +244,23 @@ export class RowNode /** Server Side Row Model Only - the children are in an infinite cache. */ public childStore: IServerSideStore | null; - /** `true` if group is expanded, otherwise `false`. */ - public expanded: boolean; + /** + * Backing field for `expanded` property. + * - `true`/`false`: explicit expansion state. + * - `null`: triggers lazy evaluation — in CSRM, SSRM, getter resolves the default on first access and caches it. + * - `undefined`: uninitialized, means false. + */ + public _expanded: boolean | null | undefined = undefined; + + /** `true` if group or master row is expanded. */ + public get expanded(): boolean { + const expansionSvc = this.beans.expansionSvc; + return expansionSvc ? expansionSvc.isExpanded(this) : this.level === -1 ? true : !!this._expanded; + } + + public set expanded(value: boolean) { + this._expanded = value; + } /** If using footers, reference to the footer node for this group. */ public sibling: RowNode; diff --git a/packages/ag-grid-community/src/interfaces/iExpansionService.ts b/packages/ag-grid-community/src/interfaces/iExpansionService.ts index 623f3e9c3ce..e5f341355d2 100644 --- a/packages/ag-grid-community/src/interfaces/iExpansionService.ts +++ b/packages/ag-grid-community/src/interfaces/iExpansionService.ts @@ -26,9 +26,7 @@ export interface RowGroupBulkExpansionState { } /** @internal AG_GRID_INTERNAL - Not for public use. Can change / be removed at any time. */ -export interface IExpansionService< - T extends RowGroupExpansionState | RowGroupBulkExpansionState = RowGroupExpansionState, -> { +export interface IExpansionService { addExpandedCss(classes: string[], rowNode: RowNode): void; getRowExpandedListeners(rowCtrl: RowCtrl): { @@ -40,6 +38,7 @@ export interface IExpansionService< getExpansionState(): T; expandAll(value: boolean): void; + resetExpansion(): void; onGroupExpandedOrCollapsed(): void; @@ -47,5 +46,7 @@ export interface IExpansionService< isExpandable(rowNode: RowNode): boolean; + isExpanded(rowNode: RowNode): boolean; + setDetailsExpansionState(detailGridApi: GridApi): void; } diff --git a/packages/ag-grid-community/src/misc/state/stateService.ts b/packages/ag-grid-community/src/misc/state/stateService.ts index 9230e0413d7..cdd850ff602 100644 --- a/packages/ag-grid-community/src/misc/state/stateService.ts +++ b/packages/ag-grid-community/src/misc/state/stateService.ts @@ -49,19 +49,7 @@ export class StateService extends BeanStub implements NamedBean { ); // If user is doing a manual expand all node by node, we don't want to process one at a time. // EVENT_ROW_GROUP_OPENED is already async, so no impact of making the state async here. - private readonly onRowGroupOpenedDebounced = _debounce( - this, - () => { - if (this.beans.gos.get('ssrmExpandAllAffectsAllRows')) { - this.updateCachedState('ssrmRowGroupExpansion', this.getRowGroupExpansionState()); - this.updateCachedState('rowGroupExpansion', undefined); - } else { - this.updateCachedState('rowGroupExpansion', this.getRowGroupExpansionState() as RowGroupExpansionState); - this.updateCachedState('ssrmRowGroupExpansion', undefined); - } - }, - 0 - ); + private readonly onRowGroupOpenedDebounced = _debounce(this, () => this.updateGroupExpansionState(), 0); // similar to row expansion, want to debounce. However, selection is synchronous, so need to mark as stale in case `getState` is called. private readonly onRowSelectedDebounced = _debounce( this, @@ -225,7 +213,15 @@ export class StateService extends BeanStub implements NamedBean { columnPivotModeChanged: onUpdate('pivot'), columnRowGroupChanged: onUpdate('rowGroup'), sortChanged: onUpdate('sort'), - newColumnsLoaded: this.updateColumnAndGroupState.bind(this), + newColumnsLoaded: ({ source }) => { + this.updateColumnAndGroupState(); + // When setGridOption('columnDefs') changes row groups, columnRowGroupChanged fires + // while changeEventsDispatching=true, so CSRM defers its re-group to newColumnsLoaded. + // Re-read expansion state here so the cache reflects the post-re-group node IDs. + if (source !== 'gridInitializing' && this.isClientSideRowModel) { + this.onRowGroupOpenedDebounced(); + } + }, columnGroupOpened: () => this.updateCachedState('columnGroup', this.getColumnGroupState()), }); } @@ -249,7 +245,10 @@ export class StateService extends BeanStub implements NamedBean { if (shouldSetState('filter', filterState)) { this.setFilterState(filterState); } - if (shouldSetState('rowGroupExpansion', rowGroupExpansionState)) { + if ( + shouldSetState('rowGroupExpansion', rowGroupExpansionState) || + shouldSetState('ssrmRowGroupExpansion', ssrmRowGroupExpansion) + ) { this.setRowGroupExpansionState(ssrmRowGroupExpansion, rowGroupExpansionState, source); } if (shouldSetState('rowSelection', rowSelectionState)) { @@ -264,13 +263,7 @@ export class StateService extends BeanStub implements NamedBean { const updateCachedState = this.updateCachedState.bind(this); updateCachedState('filter', this.getFilterState()); - if (this.beans.gos.get('ssrmExpandAllAffectsAllRows')) { - updateCachedState('ssrmRowGroupExpansion', this.getRowGroupExpansionState()); - updateCachedState('rowGroupExpansion', undefined); - } else { - updateCachedState('rowGroupExpansion', this.getRowGroupExpansionState() as RowGroupExpansionState); - updateCachedState('ssrmRowGroupExpansion', undefined); - } + this.updateGroupExpansionState(); updateCachedState('rowSelection', this.getRowSelectionState()); updateCachedState('pagination', this.getPaginationState()); @@ -282,13 +275,7 @@ export class StateService extends BeanStub implements NamedBean { const updateCachedState = this.updateCachedState.bind(this); const updateRowGroupExpansionState = () => { this.updateRowGroupExpansionStateTimer = 0; - if (this.beans.gos.get('ssrmExpandAllAffectsAllRows')) { - updateCachedState('ssrmRowGroupExpansion', this.getRowGroupExpansionState()); - updateCachedState('rowGroupExpansion', undefined); - } else { - updateCachedState('rowGroupExpansion', this.getRowGroupExpansionState() as RowGroupExpansionState); - updateCachedState('ssrmRowGroupExpansion', undefined); - } + this.updateGroupExpansionState(); }; const updateFilterState = () => updateCachedState('filter', this.getFilterState()); @@ -835,13 +822,16 @@ export class StateService extends BeanStub implements NamedBean { this.beans.selectionSvc?.setSelectionState(rowSelectionState, source, source === 'api'); } - private getRowGroupExpansionState(): RowGroupExpansionState | RowGroupBulkExpansionState | undefined { - const { expansionSvc } = this.beans; - if (!expansionSvc) { - return undefined; - } + private updateGroupExpansionState(): void { + const { expansionSvc, gos } = this.beans; + const state = expansionSvc?.getExpansionState(); + const ssrmExpandAllAffectsAllRows = gos.get('ssrmExpandAllAffectsAllRows'); - return expansionSvc.getExpansionState(); + this.updateCachedState('ssrmRowGroupExpansion', ssrmExpandAllAffectsAllRows ? state : undefined); + this.updateCachedState( + 'rowGroupExpansion', + ssrmExpandAllAffectsAllRows ? undefined : (state as RowGroupExpansionState) + ); } private getRowPinningState(): RowPinningState | undefined { @@ -862,13 +852,9 @@ export class StateService extends BeanStub implements NamedBean { rowGroupExpansionState: RowGroupExpansionState | undefined, source: 'gridInitializing' | 'api' ): void { - const expansionSvc = this.beans.expansionSvc; - if (!expansionSvc) { - return; - } - - const newExpansionState = rowGroupExpansionState ?? { expandedRowGroupIds: [], collapsedRowGroupIds: [] }; - expansionSvc.setExpansionState(newExpansionState, source); + const state = ssrmRowGroupExpansionState ?? + rowGroupExpansionState ?? { expandedRowGroupIds: [], collapsedRowGroupIds: [] }; + this.beans.expansionSvc?.setExpansionState(state, source); } private updateColumnState(features: (keyof GridState)[]): void { diff --git a/packages/ag-grid-community/src/rendering/row/rowCtrl.ts b/packages/ag-grid-community/src/rendering/row/rowCtrl.ts index 2eb30ebf778..3d2515d6937 100644 --- a/packages/ag-grid-community/src/rendering/row/rowCtrl.ts +++ b/packages/ag-grid-community/src/rendering/row/rowCtrl.ts @@ -287,7 +287,7 @@ export class RowCtrl extends BeanStub { this.executeSlideAndFadeAnimations(gui); if (this.rowNode.group) { - _setAriaExpanded(gui.element, this.rowNode.expanded == true); + _setAriaExpanded(gui.element, !!this.rowNode.expanded); } this.setRowCompRowId(comp); diff --git a/packages/ag-grid-community/src/selection/selectionService.ts b/packages/ag-grid-community/src/selection/selectionService.ts index ffa82b11849..4b565c4035f 100644 --- a/packages/ag-grid-community/src/selection/selectionService.ts +++ b/packages/ag-grid-community/src/selection/selectionService.ts @@ -543,7 +543,7 @@ export class SelectionService extends BaseSelectionService implements NamedBean, return; } - if (!node.expanded && !node.footer) { + if (!node.footer && !node.expanded) { // even with groupSelectsChildren, do this recursively as only the filtered children // are considered as the current page const recursivelyAddChildren = (child: RowNode) => { diff --git a/packages/ag-grid-community/src/valueService/valueService.ts b/packages/ag-grid-community/src/valueService/valueService.ts index fa334c4c450..bbb257c8a5a 100644 --- a/packages/ag-grid-community/src/valueService/valueService.ts +++ b/packages/ag-grid-community/src/valueService/valueService.ts @@ -235,19 +235,22 @@ export class ValueService extends BeanStub implements NamedBean { // in the header when the group is open. // Result is: isOpenedGroup && !groupShowsAggData - // Check isOpenedGroup conditions: node.group && node.expanded && !node.footer && !isPivotLeaf - if (!node.group || !node.expanded || node.footer) { + // Check isOpenedGroup conditions: node.group && !node.footer && !isPivotLeaf && node.expanded + // The root node (level -1) is always expanded but should not suppress its agg data display. + if (!node.group || node.footer || node.level === -1) { + return false; + } + // groupShowsAggData = this.gos.get('groupSuppressBlankHeader') || !node.sibling + // We return true only if !groupShowsAggData, i.e., !groupSuppressBlankHeader && node.sibling + if (!node.sibling || this.gos.get('groupSuppressBlankHeader')) { return false; } // When in pivot mode, leafGroups cannot be expanded if (node.leafGroup && this.colModel.isPivotMode()) { - return false; // isPivotLeaf - not an opened group + return false; } - - // isOpenedGroup is true. Now check if groupShowsAggData. - // groupShowsAggData = this.gos.get('groupSuppressBlankHeader') || !node.sibling - // We return true only if !groupShowsAggData, i.e., !groupSuppressBlankHeader && node.sibling - return !!node.sibling && !this.gos.get('groupSuppressBlankHeader'); + // node.expanded (getter with side effects) evaluated last + return !!node.expanded; } private resolveValue( diff --git a/packages/ag-grid-community/src/version.ts b/packages/ag-grid-community/src/version.ts index 353202c0311..63095b37ec2 100644 --- a/packages/ag-grid-community/src/version.ts +++ b/packages/ag-grid-community/src/version.ts @@ -1,2 +1,2 @@ // DO NOT UPDATE MANUALLY: Generated from script during build time -export const VERSION = '35.1.0-beta.20260316.108'; +export const VERSION = '35.1.0-beta.20260317.135'; diff --git a/packages/ag-grid-enterprise/package.json b/packages/ag-grid-enterprise/package.json index 40265090674..b739aadeff5 100644 --- a/packages/ag-grid-enterprise/package.json +++ b/packages/ag-grid-enterprise/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-enterprise", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "Advanced Data Grid / Data Table supporting Javascript / Typescript / React / Angular / Vue", "main": "./dist/package/main.cjs.js", "types": "./dist/types/src/main.d.ts", @@ -113,15 +113,15 @@ ], "homepage": "https://www.ag-grid.com/", "dependencies": { - "ag-grid-community": "35.1.0-beta.20260316.108" + "ag-grid-community": "35.1.0-beta.20260317.135" }, "optionalDependencies": { - "ag-charts-community": "13.1.0-beta.20260316", - "ag-charts-enterprise": "13.1.0-beta.20260316" + "ag-charts-community": "13.1.0-beta.20260317", + "ag-charts-enterprise": "13.1.0-beta.20260317" }, "devDependencies": { - "ag-charts-community": "13.1.0-beta.20260316", - "ag-charts-enterprise": "13.1.0-beta.20260316", + "ag-charts-community": "13.1.0-beta.20260317", + "ag-charts-enterprise": "13.1.0-beta.20260317", "@types/jest": "^29.5.0", "jest": "^29.5.0", "jest-environment-jsdom": "^29.7.0", diff --git a/packages/ag-grid-enterprise/src/masterDetail/masterDetailService.ts b/packages/ag-grid-enterprise/src/masterDetail/masterDetailService.ts index b3b79bf3127..888963f3784 100644 --- a/packages/ag-grid-enterprise/src/masterDetail/masterDetailService.ts +++ b/packages/ag-grid-enterprise/src/masterDetail/masterDetailService.ts @@ -18,8 +18,6 @@ import { _observeResize, } from 'ag-grid-community'; -import { _getRowDefaultExpanded } from '../rowHierarchy/rowHierarchyUtils'; - export class MasterDetailService extends BeanStub implements NamedBean, IMasterDetailService { beanName: BeanName = 'masterDetailSvc' as const; @@ -96,15 +94,14 @@ export class MasterDetailService extends BeanStub implements NamedBean, IMasterD } } - const beans = this.beans; if (!treeData) { // Note that with treeData the initialization of the expansed state is delegated to treeGroupStrategy - if (newMaster && created) { - const level = beans.rowGroupColsSvc?.columns.length ?? 0; - row.expanded = _getRowDefaultExpanded(beans, row, level, false); - } else if (!newMaster && oldMaster) { - // if changing AWAY from master, then un-expand, otherwise next time it's shown it is expanded again - row.expanded = false; + if ( + (newMaster && created) || + // if changing AWAY from master, forget current state + (!newMaster && oldMaster) + ) { + row._expanded ??= null; } } diff --git a/packages/ag-grid-enterprise/src/rowGrouping/groupStrategy/groupStrategy.ts b/packages/ag-grid-enterprise/src/rowGrouping/groupStrategy/groupStrategy.ts index 2e9ff4eb2ef..c5437ce9213 100644 --- a/packages/ag-grid-enterprise/src/rowGrouping/groupStrategy/groupStrategy.ts +++ b/packages/ag-grid-enterprise/src/rowGrouping/groupStrategy/groupStrategy.ts @@ -1,15 +1,14 @@ import type { AgColumn, ChangedPath, - GridOptions, IRowNode, + InitialGroupOrderComparator, RefreshModelParams, _ChangedRowNodes, } from 'ag-grid-community'; import { BeanStub, RowNode, _csrmFirstLeaf, _forEachChangedGroupDepthFirst, _warn } from 'ag-grid-community'; import type { IRowGroupingStrategy } from '../../rowHierarchy/rowHierarchyUtils'; -import { _getRowDefaultExpanded } from '../../rowHierarchy/rowHierarchyUtils'; import { setRowNodeGroup } from '../rowGroupingUtils'; import type { GroupColumn } from './groupColumns'; import { groupColumnsChanged, makeGroupColumns } from './groupColumns'; @@ -82,8 +81,9 @@ export class GroupStrategy extends BeanStub implements IRowGroupingStrategy { } public execute(rootNode: RowNode, params: RefreshModelParams): void { - const changedPath = params.changedPath; - if (this.initRefresh(params)) { + const changedPath = params.changedPath!; + const refreshResult = this.initRefresh(params); + if (refreshResult !== 'skip') { const changedRowNodes = params.changedRowNodes; if (changedRowNodes) { this.handleDeltaUpdate(rootNode, changedPath, changedRowNodes, !!params.animate); @@ -137,11 +137,11 @@ export class GroupStrategy extends BeanStub implements IRowGroupingStrategy { }); } - private initRefresh(params: RefreshModelParams): boolean { + private initRefresh(params: RefreshModelParams): 'skip' | 'refresh' | 'groupColsChanged' { const { rowGroupColsSvc, colModel, gos } = this.beans; - const pivotMode = colModel.isPivotMode(); - this.pivotMode = pivotMode; - this.groupEmpty = pivotMode || !gos.get('groupAllowUnbalanced'); + + this.pivotMode = colModel.isPivotMode(); + this.groupEmpty = this.pivotMode || !gos.get('groupAllowUnbalanced'); const cols = rowGroupColsSvc?.columns; const groupCols = this.groupCols; const afterColumnsChanged = params.afterColumnsChanged; @@ -149,16 +149,19 @@ export class GroupStrategy extends BeanStub implements IRowGroupingStrategy { this.checkGroupCols = false; if (groupCols && !groupColumnsChanged(groupCols, cols)) { if (afterColumnsChanged) { - return false; // no change to grouping + return 'skip'; // no change to grouping } } else { - // Group columns changed. - params.animate = false; // if grouping columns change, we don't animate the regrouping - makeGroupColumns(cols, this.groupCols); + params.animate = false; + // If the top-level group column changed, every existing node ID will differ after + // rebuild, so no group nodes can be reused. Check before makeGroupColumns overwrites groupCols. + const topLevelChanged = groupCols[0]?.col.getId() !== cols?.[0]?.getId(); + makeGroupColumns(cols, groupCols); + return topLevelChanged ? 'refresh' : 'groupColsChanged'; } } - return true; + return 'refresh'; } private handleDeltaUpdate( @@ -223,7 +226,7 @@ export class GroupStrategy extends BeanStub implements IRowGroupingStrategy { } private orderGroups(rootNode: RowNode): void { - const initialGroupOrderComparator: GridOptions['initialGroupOrderComparator'] = + const initialGroupOrderComparator: InitialGroupOrderComparator | undefined = this.gos.getCallback('initialGroupOrderComparator'); if (!initialGroupOrderComparator) { return; @@ -408,10 +411,16 @@ export class GroupStrategy extends BeanStub implements IRowGroupingStrategy { } private shotgunResetEverything(rootNode: RowNode): void { - // groups are about to get disposed, so need to deselect any that are selected - this.beans.selectionSvc?.filterFromSelection?.((node) => !node.group); + const groupsById = this.nonLeafsById; + + // Mark all existing group nodes as stale by nulling childrenAfterGroup. + // Nodes that are reused during insertion will have childrenAfterGroup reset to []. + // Nodes still null after insertion are stale and will be destroyed. + for (const node of groupsById.values()) { + node.childrenAfterGroup = null; + node.childrenMapped = null; + } - this.nonLeafsById.clear(); // because we are not creating the root node each time, we have the logic // here to change leafGroup once. rootNode.leafGroup = !this.groupCols?.length; @@ -431,11 +440,37 @@ export class GroupStrategy extends BeanStub implements IRowGroupingStrategy { for (let i = 0, len = allLeafs.length; i < len; ++i) { this.insertOneNode(rootNode, allLeafs[i]); } + + // Destroy stale group nodes that were not reused during insertion + this.destroyStaleGroups(groupsById); + } + + /** Remove and destroy group nodes that were not reused (still have childrenAfterGroup === null) */ + private destroyStaleGroups(groupsById: Map): void { + const selectionSvc = this.beans.selectionSvc; + let nodesToDeselect: RowNode[] | undefined; + for (const [id, node] of groupsById) { + if (node.childrenAfterGroup !== null) { + continue; + } + if (selectionSvc && node.isSelected()) { + (nodesToDeselect ??= []).push(node); + } + groupsById.delete(id); + node._destroy(false); + } + if (nodesToDeselect) { + selectionSvc!.setNodesSelected({ + nodes: nodesToDeselect, + newValue: false, + source: 'rowGroupChanged', + }); + } } private insertOneNode(rootNode: RowNode, childNode: RowNode): void { let parentGroup = rootNode; - const { beans, pivotMode, groupCols, groupEmpty } = this; + const { beans, groupCols, groupEmpty } = this; const valueSvc = beans.valueSvc; if (!groupCols) { return; @@ -460,7 +495,7 @@ export class GroupStrategy extends BeanStub implements IRowGroupingStrategy { const isLeafLevel = nextLevel >= len - 1; const newGroup = this.createGroup(parentGroup, groupCol, key, nextLevel, isLeafLevel, childNode); this.addToParent(newGroup, parentGroup); - this.setExpandedInitialValue(pivotMode, newGroup); + parentGroup = newGroup; } if (!parentGroup.group) { @@ -485,37 +520,62 @@ export class GroupStrategy extends BeanStub implements IRowGroupingStrategy { const id = (parent.level >= 0 ? parent.id! + '-' : 'row-group-') + (col.getColId() + '-' + key); const groupsById = this.nonLeafsById; - let groupNode = groupsById.get(id); - if (groupNode !== undefined) { - return groupNode; // already exists - } - - groupNode = new RowNode(this.beans); - groupNode.group = true; - groupNode.parent = parent; - groupNode.field = groupCol.field ?? null; - groupNode.rowGroupColumn = col; - - groupNode.key = key; - groupNode.id = id; - - groupNode.level = level; - groupNode.leafGroup = isLeafLevel; + let node = groupsById.get(id); + let singleUse = true; + + if (node) { + if (node.childrenAfterGroup !== null) { + // Already active — just ensure lazy expansion state. + node._expanded ??= null; + return node; + } + singleUse = false; + // Reused existing group node from a shotgun reset. + // Reset children but preserve selection and expansion state. + invalidateAllLeafChildren(node); + } else { + // Brand new group node. + node = new RowNode(this.beans); + node.group = true; + node.key = key; + node.id = id; + node.aggData = null; + groupsById.set(id, node); + } + + const applyValuesToNode = (n: RowNode) => { + n.childrenAfterGroup = children; + n.childrenMapped = mapped; + n.parent = parent; + n.level = level; + n.rowGroupIndex = level; + n.leafGroup = isLeafLevel; + }; - groupNode.rowGroupIndex = level; - groupNode.childrenAfterGroup = []; - groupNode.childrenMapped = {}; + // Shared setup for both reused and new nodes. + const children: RowNode[] = []; + const mapped = {}; + applyValuesToNode(node); + node.field = groupCol.field ?? null; + node.rowGroupColumn = col; + node.groupValue = this.beans.valueSvc.getValue(col, leafNode, 'data'); + // null triggers lazy default resolution in the expanded getter. + node._expanded ??= null; - groupsById.set(id, groupNode); + if (singleUse) { + node.setAllChildrenCount(0); + node.updateHasChildren(); + return node; + } - groupNode.groupValue = leafNode && this.beans.valueSvc.getValue(col, leafNode, 'data'); + const sibling = node.sibling; + if (sibling) { + applyValuesToNode(sibling); + } - // why is this done here? we are not updating the children count as we go, - // i suspect this is updated in the filter stage - groupNode.setAllChildrenCount(0); - groupNode.updateHasChildren(); + node.dispatchRowEvent('hasChildrenChanged'); - return groupNode; + return node; } private getChildrenMappedKey(key: string, rowGroupColumn: AgColumn | null): string { @@ -523,16 +583,6 @@ export class GroupStrategy extends BeanStub implements IRowGroupingStrategy { return rowGroupColumn ? rowGroupColumn.getId() + '-' + key : key; } - private setExpandedInitialValue(pivotMode: boolean, groupNode: RowNode): void { - // if pivoting the leaf group is never expanded as we do not show leaf rows - if (pivotMode && groupNode.leafGroup) { - groupNode.expanded = false; - return; - } - - groupNode.expanded = _getRowDefaultExpanded(this.beans, groupNode, groupNode.level); - } - public onShowRowGroupColsSetChanged(): void { const { rowModel, valueSvc } = this.beans; for (const groupNode of this.nonLeafsById.values()) { diff --git a/packages/ag-grid-enterprise/src/rowGrouping/rowGroupingUtils.ts b/packages/ag-grid-enterprise/src/rowGrouping/rowGroupingUtils.ts index c3f726fa68b..cc2c51c44ee 100644 --- a/packages/ag-grid-enterprise/src/rowGrouping/rowGroupingUtils.ts +++ b/packages/ag-grid-enterprise/src/rowGrouping/rowGroupingUtils.ts @@ -34,7 +34,6 @@ export function setRowNodeGroup(rowNode: RowNode, beans: BeanCollection, group: // if we used to be a group, and no longer, then close the node if (rowNode.group && !group) { - rowNode.expanded = false; // Clear stale aggData when demoting from group to leaf. const colModel = beans.colModel; setAggData(rowNode, null, colModel); diff --git a/packages/ag-grid-enterprise/src/rowHierarchy/baseExpansionService.ts b/packages/ag-grid-enterprise/src/rowHierarchy/baseExpansionService.ts index fe7450534f4..e79df2a55fa 100644 --- a/packages/ag-grid-enterprise/src/rowHierarchy/baseExpansionService.ts +++ b/packages/ag-grid-enterprise/src/rowHierarchy/baseExpansionService.ts @@ -1,5 +1,5 @@ -import type { RowCtrl, RowGroupOpenedEvent, RowNode } from 'ag-grid-community'; -import { BeanStub, _createGlobalRowEvent, _setAriaExpanded } from 'ag-grid-community'; +import type { IsGroupOpenByDefaultParams, RowCtrl, RowGroupOpenedEvent, RowNode } from 'ag-grid-community'; +import { BeanStub, _addGridCommonParams, _createGlobalRowEvent, _setAriaExpanded } from 'ag-grid-community'; export abstract class BaseExpansionService extends BeanStub { protected abstract dispatchExpandedEvent(event: RowGroupOpenedEvent, forceSync?: boolean): void; @@ -28,7 +28,7 @@ export abstract class BaseExpansionService extends BeanStub { return; } - rowNode.expanded = expanded; + rowNode._expanded = expanded; rowNode.dispatchRowEvent('expandedChanged'); @@ -37,6 +37,27 @@ export abstract class BaseExpansionService extends BeanStub { this.dispatchExpandedEvent(event, forceSync); } + public defaultExpanded(rowNode: RowNode): boolean { + const beans = this.beans; + const gos = beans.gos; + const level = rowNode.level ?? 0; + // see AG-11476 isGroupOpenByDefault callback doesn't apply to master/detail grid + // We call isGroupOpenByDefault only for group nodes and not for master/detail leafs + const isGroupOpenByDefault = rowNode.group && gos.get('isGroupOpenByDefault'); + if (!isGroupOpenByDefault) { + const groupDefaultExpanded = gos.get('groupDefaultExpanded'); + return groupDefaultExpanded === -1 || level < groupDefaultExpanded; + } + const params = _addGridCommonParams(gos, { + rowNode, + field: rowNode.field!, + key: rowNode.key!, + level, + rowGroupColumn: rowNode.rowGroupColumn!, + }); + return !!isGroupOpenByDefault(params); + } + public isExpandable(rowNode: RowNode): boolean { if (rowNode.footer) { return false; diff --git a/packages/ag-grid-enterprise/src/rowHierarchy/clientSideExpansionService.ts b/packages/ag-grid-enterprise/src/rowHierarchy/clientSideExpansionService.ts index 008205f68e3..6e59a84a033 100644 --- a/packages/ag-grid-enterprise/src/rowHierarchy/clientSideExpansionService.ts +++ b/packages/ag-grid-enterprise/src/rowHierarchy/clientSideExpansionService.ts @@ -35,7 +35,7 @@ export class ClientSideExpansionService return; } - node.expanded = rowIdsToExpandSet.has(id); + node._expanded = rowIdsToExpandSet.has(id); }); this.onGroupExpandedOrCollapsed(); } @@ -62,6 +62,37 @@ export class ClientSideExpansionService return this.getInternalExpansionState(); } + public isExpanded(rowNode: RowNode): boolean { + // Footer nodes use their own _expanded backing field directly (copied from group at creation time). + // This preserves the snapshot semantics from when expanded was a plain field. + if (rowNode.footer) { + return !!rowNode._expanded; + } + if (!(rowNode.group || rowNode.master) || (rowNode.leafGroup && this.beans.colModel.isPivotMode())) { + return false; // Not expandable, so always return false + } + let value = rowNode._expanded; + if (value === null) { + // Lazy resolution of the default expansion state via the enterprise expansion service. + value = this.defaultExpanded(rowNode) ?? false; + rowNode._expanded = value; + } + return !!value; + } + + public resetExpansion(): void { + const { rowModel } = this.beans; + + rowModel.forEachNode((node) => { + if (!node.group && !node.master) { + return; + } + node._expanded = null; // null triggers lazy default resolution in the expanded getter + }); + + this.onGroupExpandedOrCollapsed(); + } + public expandAll(expand: boolean): void { const { gos, rowModel, colModel, eventSvc } = this.beans; const usingTreeData = gos.get('treeData'); @@ -73,7 +104,7 @@ export class ClientSideExpansionService } for (const rowNode of rowNodes) { const actionRow = () => { - rowNode.expanded = expand; + rowNode._expanded = expand; recursiveExpandOrCollapse(rowNode.childrenAfterGroup); }; diff --git a/packages/ag-grid-enterprise/src/rowHierarchy/flattenUtils.ts b/packages/ag-grid-enterprise/src/rowHierarchy/flattenUtils.ts index 97e6e832ad5..635d6db9bb5 100644 --- a/packages/ag-grid-enterprise/src/rowHierarchy/flattenUtils.ts +++ b/packages/ag-grid-enterprise/src/rowHierarchy/flattenUtils.ts @@ -57,19 +57,30 @@ export function _shouldRowBeRendered( isRemovedSingleChildrenGroup: boolean, isRemovedLowestSingleChildrenGroup: boolean | undefined ): boolean { - const isSkippedLeafNode = skipLeafNodes && !isParent; + if (skipLeafNodes && !isParent) { + return false; + } + if (isRemovedSingleChildrenGroup || isRemovedLowestSingleChildrenGroup) { + return false; + } // hide open parents means when group is open, we don't show it. we also need to make sure the // group is expandable in the first place (as leaf groups are not expandable if pivot mode is on). // the UI will never allow expanding leaf groups, however the user might via the API (or menu option 'expand all row groups') + if (!details.hideOpenParents) { + return true; + } + + if (rowNode.master || rowNode.level === -1) { + return true; + } + const neverAllowToExpand = skipLeafNodes && rowNode.leafGroup; - const isHiddenOpenParent = details.hideOpenParents && rowNode.expanded && !rowNode.master && !neverAllowToExpand; + // rowNode.expanded evaluated LAST because it has side effects + if (!neverAllowToExpand && rowNode.expanded) { + return false; + } - return ( - !isSkippedLeafNode && - !isHiddenOpenParent && - !isRemovedSingleChildrenGroup && - !isRemovedLowestSingleChildrenGroup - ); + return true; } diff --git a/packages/ag-grid-enterprise/src/rowHierarchy/rendering/groupCellRendererCtrl.ts b/packages/ag-grid-enterprise/src/rowHierarchy/rendering/groupCellRendererCtrl.ts index 4a4f34f0c9a..a598125044d 100644 --- a/packages/ag-grid-enterprise/src/rowHierarchy/rendering/groupCellRendererCtrl.ts +++ b/packages/ag-grid-enterprise/src/rowHierarchy/rendering/groupCellRendererCtrl.ts @@ -208,11 +208,11 @@ export class GroupCellRendererCtrl extends BeanStub implements IGroupCellRendere return; } - const expanded = this.displayedNode.expanded; + const expanded = !!this.displayedNode.expanded; comp.setExpandedDisplayed(expanded); comp.setContractedDisplayed(!expanded); - _setAriaExpanded(eGridCell, !!this.displayedNode.expanded); + _setAriaExpanded(eGridCell, expanded); }; const onExpandableChanged = () => { diff --git a/packages/ag-grid-enterprise/src/rowHierarchy/rowHierarchyUtils.ts b/packages/ag-grid-enterprise/src/rowHierarchy/rowHierarchyUtils.ts index b9b32ed24c8..bfd90cba121 100644 --- a/packages/ag-grid-enterprise/src/rowHierarchy/rowHierarchyUtils.ts +++ b/packages/ag-grid-enterprise/src/rowHierarchy/rowHierarchyUtils.ts @@ -1,9 +1,7 @@ import type { Bean, - BeanCollection, GridOptions, GridOptionsService, - IRowNode, NestedDataGetter, RefreshModelParams, RowNode, @@ -60,29 +58,3 @@ export function _isHiddenParent(node: RowNode, ancestor: RowNode, gos: GridOptio } return currentNode === ancestor; } - -export const _getRowDefaultExpanded = ( - beans: BeanCollection, - rowNode: IRowNode, - level: number, - group = rowNode.group -): boolean => { - const gos = beans.gos; - // see AG-11476 isGroupOpenByDefault callback doesn't apply to master/detail grid - // We call isGroupOpenByDefault only for group nodes and not for master/detail leafs - const isGroupOpenByDefault = group && gos.get('isGroupOpenByDefault'); - if (!isGroupOpenByDefault) { - const groupDefaultExpanded = gos.get('groupDefaultExpanded'); - return groupDefaultExpanded === -1 || level < groupDefaultExpanded; - } - const params = { - api: beans.gridApi, - context: beans.gridOptions.context, - rowNode, - field: rowNode.field!, - key: rowNode.key!, - level, - rowGroupColumn: rowNode.rowGroupColumn!, - }; - return isGroupOpenByDefault(params) == true; -}; diff --git a/packages/ag-grid-enterprise/src/rowHierarchy/stickyRowFeature.ts b/packages/ag-grid-enterprise/src/rowHierarchy/stickyRowFeature.ts index 36a2b4be146..abb22d53e1b 100644 --- a/packages/ag-grid-enterprise/src/rowHierarchy/stickyRowFeature.ts +++ b/packages/ag-grid-enterprise/src/rowHierarchy/stickyRowFeature.ts @@ -174,7 +174,7 @@ export class StickyRowFeature extends BeanStub implements IStickyRowFeature { return false; } const alreadySticking = newStickyRows.has(row); - return !alreadySticking && row.expanded; + return !alreadySticking && !!row.expanded; } return false; diff --git a/packages/ag-grid-enterprise/src/serverSideRowModel/blocks/blockUtils.ts b/packages/ag-grid-enterprise/src/serverSideRowModel/blocks/blockUtils.ts index 7fa476a084e..678533008d2 100644 --- a/packages/ag-grid-enterprise/src/serverSideRowModel/blocks/blockUtils.ts +++ b/packages/ag-grid-enterprise/src/serverSideRowModel/blocks/blockUtils.ts @@ -404,11 +404,11 @@ export class BlockUtils extends BeanStub implements NamedBean { public checkOpenByDefault(rowNode: RowNode): void { const expanded = !!this.expansionSvc?.isNodeExpanded(rowNode); - const oldExpanded = rowNode.expanded; + const oldExpanded = rowNode._expanded; if (!!oldExpanded !== expanded) { rowNode.setExpanded(expanded); } else if (oldExpanded === undefined) { - rowNode.expanded = expanded; // Initial state, don't fire event + rowNode._expanded = expanded; // Initial state, don't fire event } } } diff --git a/packages/ag-grid-enterprise/src/serverSideRowModel/services/serverSideExpansionService.ts b/packages/ag-grid-enterprise/src/serverSideRowModel/services/serverSideExpansionService.ts index 9a63122de4e..c347d5e0c78 100644 --- a/packages/ag-grid-enterprise/src/serverSideRowModel/services/serverSideExpansionService.ts +++ b/packages/ag-grid-enterprise/src/serverSideRowModel/services/serverSideExpansionService.ts @@ -37,9 +37,14 @@ export class ServerSideExpansionService this.storeFactory = beans.ssrmStoreFactory as StoreFactory; } + private setStrategy(strategy: ExpandStrategy | ExpandAllStrategy): void { + this.destroyBean(this.strategy as any); + this.strategy = this.createManagedBean(strategy); + } + public postConstruct(): void { const setDefaultExpand = () => { - this.strategy = this.createManagedBean(new ExpandStrategy()); + this.setStrategy(new ExpandStrategy()); }; this.addManagedEventListeners({ @@ -53,7 +58,7 @@ export class ServerSideExpansionService // reset strategy if explicitly disabled, otherwise state is fine to remain until new // select all value is set/removed if (!p.currentValue) { - this.strategy = this.createManagedBean(new ExpandStrategy()); + this.setStrategy(new ExpandStrategy()); this.updateAllNodes(); this.dispatchStateUpdatedEvent(); } @@ -67,9 +72,7 @@ export class ServerSideExpansionService const isExpandAllStrategy = this.isExpandAllStrategy(this.strategy); if (isExpandAllState !== isExpandAllStrategy) { - this.strategy = isExpandAllState - ? this.createManagedBean(new ExpandAllStrategy()) - : this.createManagedBean(new ExpandStrategy()); + this.setStrategy(isExpandAllState ? new ExpandAllStrategy() : new ExpandStrategy()); } this.strategy.setExpandedState(state as any); // cast to any, as we know the type is correct due to the previous assertion this.dispatchStateUpdatedEvent(); @@ -89,6 +92,19 @@ export class ServerSideExpansionService }); } + public isExpanded(rowNode: RowNode): boolean { + let value = rowNode._expanded; + if (value === null) { + value = this.defaultExpanded(rowNode); + rowNode._expanded = value; + } + // This could be returning undefined which is currently + // handled via coercion in SSRM. When fixing this to always + // return a boolean must validate that the undefined state is + // correctly handled. + return value as boolean; + } + public isNodeExpanded(node: RowNode): boolean { return this.strategy.isRowExpanded(node); } @@ -100,12 +116,20 @@ export class ServerSideExpansionService this.updateExpandedState(node); } + public resetExpansion(): void { + this.setStrategy(new ExpandStrategy()); + this.updateAllNodes(); + this.dispatchStateUpdatedEvent(); + } + public expandAll(expanded: boolean): void { const ssrmExpandAllAffectsAllRows = this.beans.gos.get('ssrmExpandAllAffectsAllRows'); // if allowed, swap to expand all strategy const shouldUseExpandAllStrategy = !this.isExpandAllStrategy(this.strategy) && ssrmExpandAllAffectsAllRows; - this.strategy = shouldUseExpandAllStrategy ? new ExpandAllStrategy() : this.strategy; + if (shouldUseExpandAllStrategy) { + this.setStrategy(new ExpandAllStrategy()); + } this.strategy.expandAll(expanded); this.updateAllNodes(); this.dispatchStateUpdatedEvent(); diff --git a/packages/ag-grid-enterprise/src/treeData/treeGroupStrategy.ts b/packages/ag-grid-enterprise/src/treeData/treeGroupStrategy.ts index 238cec67dab..44c0c8702f2 100644 --- a/packages/ag-grid-enterprise/src/treeData/treeGroupStrategy.ts +++ b/packages/ag-grid-enterprise/src/treeData/treeGroupStrategy.ts @@ -9,7 +9,6 @@ import { BeanStub, RowNode, _removeFromArray, _warn } from 'ag-grid-community'; import { setRowNodeGroup } from '../rowGrouping/rowGroupingUtils'; import type { IRowGroupingStrategy } from '../rowHierarchy/rowHierarchyUtils'; -import { _getRowDefaultExpanded } from '../rowHierarchy/rowHierarchyUtils'; import { fieldGetter } from './fieldAccess'; // The approach used here avoids complex incremental updates by using linear passes and a final traversal. @@ -20,7 +19,7 @@ import { fieldGetter } from './fieldAccess'; // This guarantees correct parent-child relationships without requiring sorting or post-processing. // // No new arrays are allocated for childrenAfterGroup — existing arrays are reused. -// The treeNodeFlags field encodes temporary state, child counters, and expanded status. +// The treeNodeFlags field encodes temporary state and child counters. // The treeParent field tracks hierarchy changes and supports re-parenting (e.g., drag-and-drop). // Setting a node treeParent to a desired node and then executing grouping without full reload will generate a valid tree. // @@ -34,9 +33,6 @@ const FLAG_CHANGED = 0x40000000; /** Toggling this flag is used to mark a filler node as used or already processed */ const FLAG_MARKED_FILLER = 0x20000000; -/** This is the only flag that stays in the node, to indicate that the expanded state was initialized */ -const FLAG_EXPANDED_INITIALIZED = 0x10000000; - /** Mask used to keep track of the number of children in a node */ const MASK_CHILDREN_LEN = 0x0fffffff; // This equates to 268,435,455 maximum children per parent, more than enough @@ -219,8 +215,8 @@ export class TreeGroupStrategy extends BeanStub implements IRowGrou } if (parentChanged && oldParent) { - if (oldParent.destroyed && maybeExpandFromRemovedParent(parent, oldParent)) { - parentFlags |= FLAG_EXPANDED_INITIALIZED; + if (oldParent.destroyed) { + maybeExpandFromRemovedParent(parent, oldParent); } oldParent.treeNodeFlags |= FLAG_CHANGED; } @@ -360,7 +356,7 @@ export class TreeGroupStrategy extends BeanStub implements IRowGrou const len = children?.length ?? 0; let flags = row.treeNodeFlags; - row.treeNodeFlags = flags & FLAG_EXPANDED_INITIALIZED; + row.treeNodeFlags = 0; row.level = level; // Update group state and children markers @@ -376,21 +372,16 @@ export class TreeGroupStrategy extends BeanStub implements IRowGrou const canBeExpanded = len !== 0 || row.master; if (!canBeExpanded) { - if (row.expanded) { - row.expanded = false; - } - if ((flags & FLAG_EXPANDED_INITIALIZED) !== 0) { - row.treeNodeFlags &= ~FLAG_EXPANDED_INITIALIZED; - } - } else if ((flags & FLAG_EXPANDED_INITIALIZED) === 0) { - row.treeNodeFlags |= FLAG_EXPANDED_INITIALIZED; - row.expanded = _getRowDefaultExpanded(this.beans, row, level); // Initialize the expanded state + row._expanded = undefined; + } else { + // null triggers lazy default resolution in the expanded getter + row._expanded ??= null; } if (collapsed && row.rowIndex !== null) { row.clearRowTopAndRowIndex(); // Mark row hidden if collapsed } - collapsed ||= row.expanded === false; + collapsed ||= !row.expanded; ++level; // Increment level as it is passed down to children flags &= FLAG_CHILDREN_CHANGED; @@ -861,19 +852,16 @@ type DuplicatePathsMap = Map[]>; /** * If parent is a new filler node, copy the expanded flag from old removed parent. - * Returns true if the expanded flag was copied. */ -const maybeExpandFromRemovedParent = (parent: RowNode, oldParent: RowNode): boolean => { +const maybeExpandFromRemovedParent = (parent: RowNode, oldParent: RowNode): void => { if ( - (oldParent.treeNodeFlags & FLAG_EXPANDED_INITIALIZED) !== 0 && - (parent.treeNodeFlags & FLAG_EXPANDED_INITIALIZED) === 0 && + oldParent._expanded !== undefined && + parent._expanded === undefined && parent.treeParent !== null && parent.sourceRowIndex < 0 ) { - parent.expanded = oldParent.expanded; - return true; + parent._expanded = oldParent._expanded; } - return false; }; const updateNodeKey = (node: RowNode, key: string): void => { diff --git a/packages/ag-grid-enterprise/src/version.ts b/packages/ag-grid-enterprise/src/version.ts index 353202c0311..63095b37ec2 100644 --- a/packages/ag-grid-enterprise/src/version.ts +++ b/packages/ag-grid-enterprise/src/version.ts @@ -1,2 +1,2 @@ // DO NOT UPDATE MANUALLY: Generated from script during build time -export const VERSION = '35.1.0-beta.20260316.108'; +export const VERSION = '35.1.0-beta.20260317.135'; diff --git a/packages/ag-grid-react/package.json b/packages/ag-grid-react/package.json index 5613bf37d3c..9b415d56318 100644 --- a/packages/ag-grid-react/package.json +++ b/packages/ag-grid-react/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-react", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "AG Grid React Component", "main": "./dist/package/index.cjs.js", "types": "./dist/types/src/index.d.ts", @@ -31,7 +31,7 @@ "devDependencies": { "@babel/runtime": "^7.27.1", "prop-types": "^15.6.2", - "ag-grid-community": "35.1.0-beta.20260316.108", + "ag-grid-community": "35.1.0-beta.20260317.135", "@babel/plugin-proposal-throw-expressions": "^7.27.1", "@babel/preset-typescript": "^7.27.1", "@types/react": "~18.3.26", @@ -44,7 +44,7 @@ }, "dependencies": { "prop-types": "^15.8.1", - "ag-grid-community": "35.1.0-beta.20260316.108" + "ag-grid-community": "35.1.0-beta.20260317.135" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", diff --git a/packages/ag-grid-vue3/package.json b/packages/ag-grid-vue3/package.json index 357c12fb599..7c2ea3b95a9 100644 --- a/packages/ag-grid-vue3/package.json +++ b/packages/ag-grid-vue3/package.json @@ -1,7 +1,7 @@ { "name": "ag-grid-vue3", "description": "AG Grid Vue 3 Component", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "author": "Sean Landsman ", "license": "MIT", "files": [ @@ -44,7 +44,7 @@ "build-only:watch": "vite build --watch" }, "dependencies": { - "ag-grid-community": "35.1.0-beta.20260316.108" + "ag-grid-community": "35.1.0-beta.20260317.135" }, "devDependencies": { "vue": "^3.5.0", diff --git a/plugins/ag-grid-generate-code-reference-files/package.json b/plugins/ag-grid-generate-code-reference-files/package.json index 7c4cd65b021..c16a71faac6 100644 --- a/plugins/ag-grid-generate-code-reference-files/package.json +++ b/plugins/ag-grid-generate-code-reference-files/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-generate-code-reference-files", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "private": true, "dependencies": { "ag-shared": "0.0.1", diff --git a/plugins/ag-grid-generate-example-files/package.json b/plugins/ag-grid-generate-example-files/package.json index 922c0c95128..7f5aa6d9910 100644 --- a/plugins/ag-grid-generate-example-files/package.json +++ b/plugins/ag-grid-generate-example-files/package.json @@ -1,10 +1,10 @@ { "name": "ag-grid-generate-example-files", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "private": true, "dependencies": { "ag-shared": "0.0.1", - "ag-grid-community": "35.1.0-beta.20260316.108", + "ag-grid-community": "35.1.0-beta.20260317.135", "glob": "8.0.3", "typescript": "~5.4.5", "cheerio": "^1.0.0", diff --git a/plugins/ag-grid-task-autogen/package.json b/plugins/ag-grid-task-autogen/package.json index e66597440d7..eda247f9e81 100644 --- a/plugins/ag-grid-task-autogen/package.json +++ b/plugins/ag-grid-task-autogen/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-task-autogen", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "private": true, "dependencies": { "@nx/devkit": "20.3.1", diff --git a/testing/accessibility/package.json b/testing/accessibility/package.json index d7d11a7ec0e..3de5a2db2a3 100644 --- a/testing/accessibility/package.json +++ b/testing/accessibility/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-accessibility", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "scripts": { "download-examples": "curl --retry 5 -retry-all-errors https://grid-staging.ag-grid.com/debug/all-examples.json > ./all-examples.json", "download-examples-local": "curl https://localhost:4610/debug/all-examples.json > ./all-examples.json", @@ -18,11 +18,11 @@ "@angular/platform-browser": "^19.0.0", "@angular/platform-browser-dynamic": "^19.0.0", "@angular/router": "^19.0.0", - "ag-grid-angular": "35.1.0-beta.20260316.108", - "ag-grid-community": "35.1.0-beta.20260316.108", - "ag-grid-enterprise": "35.1.0-beta.20260316.108", - "ag-charts-community": "13.1.0-beta.20260316", - "ag-charts-enterprise": "13.1.0-beta.20260316", + "ag-grid-angular": "35.1.0-beta.20260317.135", + "ag-grid-community": "35.1.0-beta.20260317.135", + "ag-grid-enterprise": "35.1.0-beta.20260317.135", + "ag-charts-community": "13.1.0-beta.20260317", + "ag-charts-enterprise": "13.1.0-beta.20260317", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" diff --git a/testing/angular-tests/package.json b/testing/angular-tests/package.json index eb0f36b9f42..c9cbaae421b 100644 --- a/testing/angular-tests/package.json +++ b/testing/angular-tests/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-angular-tests", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "private": true, "scripts": { "test:e2e": "jest --no-cache" @@ -11,8 +11,8 @@ "@angular/core": "^21.0.0", "@angular/platform-browser": "^21.0.0", "@angular/platform-browser-dynamic": "^21.0.0", - "ag-grid-angular": "35.1.0-beta.20260316.108", - "ag-grid-community": "35.1.0-beta.20260316.108", + "ag-grid-angular": "35.1.0-beta.20260317.135", + "ag-grid-community": "35.1.0-beta.20260317.135", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" diff --git a/testing/behavioural/package.json b/testing/behavioural/package.json index 020a72b1876..cd4c6009fbe 100644 --- a/testing/behavioural/package.json +++ b/testing/behavioural/package.json @@ -1,6 +1,6 @@ { "name": "ag-behavioural-testing", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "private": true, "description": "Behavioural unit testing for ag-Grid", "dependencies": { @@ -8,9 +8,9 @@ }, "type": "module", "devDependencies": { - "ag-grid-community": "35.1.0-beta.20260316.108", - "ag-grid-enterprise": "35.1.0-beta.20260316.108", - "ag-grid-react": "35.1.0-beta.20260316.108", + "ag-grid-community": "35.1.0-beta.20260317.135", + "ag-grid-enterprise": "35.1.0-beta.20260317.135", + "ag-grid-react": "35.1.0-beta.20260317.135", "@types/react": "^18.3.23", "@types/react-dom": "^18.3.7", "@testing-library/dom": "^10.4.0", diff --git a/testing/behavioural/src/cell-editing/benchmarks/getvalue.bench.ts b/testing/behavioural/src/cell-editing/benchmarks/getvalue.bench.ts new file mode 100644 index 00000000000..c5d177d685c --- /dev/null +++ b/testing/behavioural/src/cell-editing/benchmarks/getvalue.bench.ts @@ -0,0 +1,62 @@ +import { bench, describe } from 'vitest'; + +import type { GridApi, IRowNode } from 'ag-grid-community'; +import { ClientSideRowModelModule, RowApiModule } from 'ag-grid-community'; + +import { SimplePRNG, TestGridsManager } from '../../test-utils'; + +interface IData { + name: string; + id: string; +} + +function buildRandomData(numberOfRows: number): IData[] { + const prng = new SimplePRNG(0x12345678); + const result = new Array(numberOfRows); + for (let i = 0; i < numberOfRows; i++) { + result[i] = { name: prng.nextString(10), id: i.toString() }; + } + return result; +} + +describe('getValue profiling', () => { + const rowCount = 2000; + + const gridsManager = new TestGridsManager({ + benchmark: true, + modules: [ClientSideRowModelModule, RowApiModule], + }); + + const baseRowData = buildRandomData(rowCount); + const api: GridApi = gridsManager.createGrid('G', { + columnDefs: [{ field: 'name' }], + rowData: baseRowData, + getRowId: ({ data }) => data.id, + }); + + const rowNodes: IRowNode[] = []; + api.forEachNode((n) => rowNodes.push(n)); + + bench(`getDataValue`, () => { + for (let i = 0; i < rowCount; ++i) { + rowNodes[i].getDataValue('name'); + } + }); + + bench(`getCellValue`, () => { + for (let i = 0; i < rowCount; ++i) { + api.getCellValue({ rowNode: rowNodes[i], colKey: 'name', useFormatter: false }); + } + }); + + bench(`direct data.name `, () => { + let sum = 0; + for (let i = 0; i < rowCount; ++i) { + const val = (rowNodes[i] as any).data.name; + if (val) { + sum++; + } + } + return sum as any; + }); +}); diff --git a/testing/behavioural/src/drag-n-drop/grouping/managed-row-group-drag-edit-basic.test.ts b/testing/behavioural/src/drag-n-drop/grouping/managed-row-group-drag-edit-basic.test.ts index ff578733817..ed213eb369b 100644 --- a/testing/behavioural/src/drag-n-drop/grouping/managed-row-group-drag-edit-basic.test.ts +++ b/testing/behavioural/src/drag-n-drop/grouping/managed-row-group-drag-edit-basic.test.ts @@ -90,13 +90,14 @@ describe('drag refreshAfterGroupEdit multi-step interactions', () => { `; const assertIntermediateStep = async (expectedParentId: string, snapshot: string, label: string) => { - await asyncSetTimeout(0); - const rowDragMoveEvents = dispatcher.rowDragMoveEvents; - expect(rowDragMoveEvents.some((event) => event.rowsDrop?.newParent?.id === expectedParentId)).toBe(true); - const latestRowsDrop = rowDragMoveEvents[rowDragMoveEvents.length - 1]?.rowsDrop; - expect(latestRowsDrop?.allowed).toBe(true); - expect(latestRowsDrop?.moved).toBe(true); await waitFor(async () => { + const rowDragMoveEvents = dispatcher.rowDragMoveEvents; + expect(rowDragMoveEvents.some((event) => event.rowsDrop?.newParent?.id === expectedParentId)).toBe( + true + ); + const latestRowsDrop = rowDragMoveEvents[rowDragMoveEvents.length - 1]?.rowsDrop; + expect(latestRowsDrop?.allowed).toBe(true); + expect(latestRowsDrop?.moved).toBe(true); const intermediateRows = new GridRows(api, label); await intermediateRows.check(snapshot); const draggedRowElement = getRowHtmlElement(api, '2'); @@ -109,10 +110,8 @@ describe('drag refreshAfterGroupEdit multi-step interactions', () => { await dispatcher.start('2'); await waitFor(() => expect(dispatcher.getDragGhostLabel()).toBe('A2')); await dispatcher.move('3', { yOffsetPercent: 0.4 }); - await asyncSetTimeout(15); await assertIntermediateStep('row-group-group-B', intermediateHoverLeaf, 'after hover over group B leaf'); await dispatcher.move('row-group-group-C', { center: true }); - await asyncSetTimeout(8); await assertIntermediateStep('row-group-group-C', intermediateHoverLeaf, 'after hover over group C group node'); await dispatcher.move('6', { yOffsetPercent: 0.9 }); await dispatcher.finish(); diff --git a/testing/behavioural/src/grid-state/grid-state.test.ts b/testing/behavioural/src/grid-state/grid-state.test.ts index 08d8626058f..739f072bb90 100644 --- a/testing/behavioural/src/grid-state/grid-state.test.ts +++ b/testing/behavioural/src/grid-state/grid-state.test.ts @@ -1,7 +1,7 @@ -import type { GridState } from 'ag-grid-community'; +import type { GridState, IServerSideDatasource, IServerSideGetRowsParams } from 'ag-grid-community'; import { AllEnterpriseModule } from 'ag-grid-enterprise'; -import { TestGridsManager, asyncSetTimeout } from '../test-utils'; +import { TestGridsManager, asyncSetTimeout, waitForNoLoadingRows } from '../test-utils'; describe('StateService - Grid State Management', () => { const gridsManager = new TestGridsManager({ @@ -257,26 +257,30 @@ describe('StateService - Grid State Management', () => { expect(api.getState().rowSelection).toEqual(['0', '2']); }); - test('should capture row group expansion state - TODO FIX', async () => { + test('CSRM: getState captures rowGroupExpansion, setState restores it', async () => { const columnDefs = [ { field: 'id', hide: false }, { field: 'sport', hide: false, rowGroup: true }, { field: 'name', hide: false }, { field: 'age', hide: false }, ]; - const api = gridsManager.createGrid('myGrid', { + const api = gridsManager.createGrid('source', { columnDefs, rowData: defaultRowData, + getRowId: ({ data }) => data.id, }); + // Initially all groups collapsed expect(api.getState().rowGroupExpansion).toEqual({ collapsedRowGroupIds: [], expandedRowGroupIds: [], }); + expect(api.getState().ssrmRowGroupExpansion).toBeUndefined(); api.expandAll(); - expect(api.getState().rowGroupExpansion).toEqual({ + const savedState = api.getState(); + expect(savedState.rowGroupExpansion).toEqual({ collapsedRowGroupIds: [], expandedRowGroupIds: [ 'row-group-sport-Football', @@ -286,6 +290,199 @@ describe('StateService - Grid State Management', () => { 'row-group-sport-Swimming', ], }); + expect(savedState.ssrmRowGroupExpansion).toBeUndefined(); + + // Restore into a fresh grid + const api2 = gridsManager.createGrid('target', { + columnDefs, + rowData: defaultRowData, + getRowId: ({ data }) => data.id, + initialState: savedState, + }); + + expect(api2.getState().rowGroupExpansion).toEqual(savedState.rowGroupExpansion); + }); + + test('SSRM: getState captures rowGroupExpansion, setState restores it', async () => { + const datasource: IServerSideDatasource = { + getRows({ request, success }: IServerSideGetRowsParams) { + if (request.groupKeys.length === 0) { + success({ + rowData: [ + { id: 'ie', country: 'Ireland' }, + { id: 'fr', country: 'France' }, + ], + }); + } else { + success({ + rowData: [{ id: `${request.groupKeys[0]}-leaf`, country: request.groupKeys[0], medals: 5 }], + }); + } + }, + }; + + const api = gridsManager.createGrid('ssrmSource', { + columnDefs: [{ field: 'country', rowGroup: true, hide: true }, { field: 'medals' }], + rowModelType: 'serverSide', + serverSideDatasource: datasource, + getRowId: ({ data }) => data.id, + }); + + await waitForNoLoadingRows(api); + + // Without ssrmExpandAllAffectsAllRows, SSRM uses rowGroupExpansion (same as CSRM) + expect(api.getState().rowGroupExpansion).toEqual({ expandedRowGroupIds: [], collapsedRowGroupIds: [] }); + expect(api.getState().ssrmRowGroupExpansion).toBeUndefined(); + + // Expand Ireland + api.setRowNodeExpanded(api.getRowNode('ie')!, true); + await waitForNoLoadingRows(api); + + const savedState = api.getState(); + expect(savedState.rowGroupExpansion).toEqual({ + expandedRowGroupIds: ['ie'], + collapsedRowGroupIds: [], + }); + expect(savedState.ssrmRowGroupExpansion).toBeUndefined(); + + // Collapse Ireland + api.setRowNodeExpanded(api.getRowNode('ie')!, false); + expect(api.getRowNode('ie')!.expanded).toBe(false); + + // Restore the saved state + api.setState(savedState); + await waitForNoLoadingRows(api); + + expect(api.getRowNode('ie')!.expanded).toBe(true); + expect(api.getState().rowGroupExpansion).toEqual(savedState.rowGroupExpansion); + }); + + test('SSRM expandAll strategy: getState captures RowGroupBulkExpansionState, setState restores it', async () => { + const datasource: IServerSideDatasource = { + getRows({ request, success }: IServerSideGetRowsParams) { + if (request.groupKeys.length === 0) { + success({ + rowData: [ + { id: 'ie', key: 'Ireland', country: 'Ireland', group: true }, + { id: 'fr', key: 'France', country: 'France', group: true }, + ], + }); + } else { + success({ + rowData: [{ id: `${request.groupKeys[0]}-leaf`, country: request.groupKeys[0], medals: 1 }], + }); + } + }, + }; + + const gridOpts = { + columnDefs: [{ field: 'country', rowGroup: true, hide: true }, { field: 'medals' }], + rowModelType: 'serverSide' as const, + serverSideDatasource: datasource, + getRowId: ({ data }: any) => data.id, + ssrmExpandAllAffectsAllRows: true, + }; + + const api = gridsManager.createGrid('ssrmBulkSource', gridOpts); + await waitForNoLoadingRows(api); + + // Expand all — switches to ExpandAllStrategy + api.expandAll(); + await waitForNoLoadingRows(api); + + // Collapse France individually — recorded as an exception in ExpandAllStrategy + api.setRowNodeExpanded(api.getRowNode('fr')!, false); + await asyncSetTimeout(0); // allow debounced state update to flush + + const savedState = api.getState(); + expect(savedState.ssrmRowGroupExpansion).toEqual({ + expandAll: true, + invertedRowGroupIds: ['fr'], + }); + expect(savedState.rowGroupExpansion).toBeUndefined(); + + // Collapse all to reset state + api.collapseAll(); + expect(api.getRowNode('ie')!.expanded).toBe(false); + expect(api.getRowNode('fr')!.expanded).toBe(false); + + // Restore saved state — previously broken (ssrmRowGroupExpansion was ignored) + api.setState(savedState); + await waitForNoLoadingRows(api); + + // Ireland should be expanded (expandAll: true), France collapsed (in invertedRowGroupIds) + expect(api.getRowNode('ie')!.expanded).toBe(true); + expect(api.getRowNode('fr')!.expanded).toBe(false); + }); + + test('SSRM expandAll strategy: initialState with ssrmRowGroupExpansion restores on grid creation', async () => { + // Tests the gridInitializing path: ssrmRowGroupExpansion must be checked (not just + // rowGroupExpansion) when deciding whether to call setRowGroupExpansionState. + const datasource: IServerSideDatasource = { + getRows({ request, success }: IServerSideGetRowsParams) { + if (request.groupKeys.length === 0) { + success({ + rowData: [ + { id: 'ie', country: 'Ireland' }, + { id: 'fr', country: 'France' }, + ], + }); + } else { + success({ + rowData: [{ id: `${request.groupKeys[0]}-leaf`, country: request.groupKeys[0], medals: 1 }], + }); + } + }, + }; + + const gridOpts = { + columnDefs: [{ field: 'country', rowGroup: true, hide: true }, { field: 'medals' }], + rowModelType: 'serverSide' as const, + serverSideDatasource: datasource, + getRowId: ({ data }: any) => data.id, + ssrmExpandAllAffectsAllRows: true, + initialState: { + ssrmRowGroupExpansion: { expandAll: true, invertedRowGroupIds: ['fr'] }, + }, + }; + + const api = gridsManager.createGrid('ssrmBulkInit', gridOpts); + await waitForNoLoadingRows(api); + + // Ireland should be expanded (expandAll: true), France collapsed (in invertedRowGroupIds) + expect(api.getRowNode('ie')!.expanded).toBe(true); + expect(api.getRowNode('fr')!.expanded).toBe(false); + }); + + test('CSRM: getState reflects new node IDs after group column change via setGridOption', async () => { + // columnRowGroupChanged fires while changeEventsDispatching=true (before CSRM re-groups), + // so expansion state must be refreshed on newColumnsLoaded instead. + const api = gridsManager.createGrid('csrmRegroup', { + columnDefs: [ + { field: 'sport', rowGroup: true, hide: true }, + { field: 'name', rowGroup: false, hide: true }, + { field: 'age' }, + ], + rowData: defaultRowData, + getRowId: ({ data }) => data.id, + }); + + // expandAll uses the synchronous onGroupExpandedOrCollapsed path + api.expandAll(); + await asyncSetTimeout(0); + expect(api.getState().rowGroupExpansion?.expandedRowGroupIds).toContain('row-group-sport-Football'); + + // Switch grouping to name — all sport-based IDs are gone + api.setGridOption('columnDefs', [ + { field: 'sport', rowGroup: false, hide: true }, + { field: 'name', rowGroup: true, hide: true }, + { field: 'age' }, + ]); + await asyncSetTimeout(0); + + // Cached state must reflect the new name-based IDs, not stale sport-based IDs + const expansion = api.getState().rowGroupExpansion; + expect(expansion?.expandedRowGroupIds).not.toContain('row-group-sport-Football'); }); test('should capture pagination state', async () => { diff --git a/testing/behavioural/src/grouping-data/grouping-change-column.test.ts b/testing/behavioural/src/grouping-data/grouping-change-column.test.ts index b78bfd3ddfb..f0c5fbf3cc5 100644 --- a/testing/behavioural/src/grouping-data/grouping-change-column.test.ts +++ b/testing/behavioural/src/grouping-data/grouping-change-column.test.ts @@ -1,12 +1,12 @@ import type { GridOptions } from 'ag-grid-community'; -import { ClientSideRowModelModule } from 'ag-grid-community'; -import { RowGroupingModule } from 'ag-grid-enterprise'; +import { ClientSideRowModelModule, GridStateModule, RowSelectionModule } from 'ag-grid-community'; +import { PivotModule, RowGroupingModule } from 'ag-grid-enterprise'; -import { GridRows, TestGridsManager } from '../test-utils'; +import { GridRows, TestGridsManager, asyncSetTimeout } from '../test-utils'; describe('ag-grid grouping simple data', () => { const gridsManager = new TestGridsManager({ - modules: [ClientSideRowModelModule, RowGroupingModule], + modules: [ClientSideRowModelModule, GridStateModule, RowGroupingModule], }); beforeEach(() => { @@ -56,4 +56,1254 @@ describe('ag-grid grouping simple data', () => { · └── LEAF id:0 1:"bob" `); }); + + test('expanding groups then adding a deeper group column preserves expansion state', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [{ field: 'country', rowGroup: true, hide: true }, { field: 'year' }], + rowData, + getRowId: (params) => params.data.id, + }); + + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + + await new GridRows(api, 'Ireland expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:1 country:"Ireland" year:2020 + │ └── LEAF id:2 country:"Ireland" year:2021 + └─┬ LEAF_GROUP collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Flush the debounced state update (rowExpansionStateChanged is debounced with 0ms) + await asyncSetTimeout(0); + + // Expansion state debounce has settled — Ireland is in the expanded set + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: ['row-group-country-Ireland'], + collapsedRowGroupIds: [], + }); + + // Add year as a second grouping column + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ]); + + // Ireland should remain expanded; new year sub-groups follow groupDefaultExpanded (collapsed by default) + await new GridRows(api, 'after adding year as group column').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Flush the debounced state update + await asyncSetTimeout(0); + + // Ireland's ID is unchanged so it remains in the expanded set after the column change + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: ['row-group-country-Ireland'], + collapsedRowGroupIds: [], + }); + }); + + test('expanding groups then removing the deepest group column preserves expansion state', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ], + rowData, + getRowId: (params) => params.data.id, + }); + + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + + await new GridRows(api, 'Ireland and Ireland/2020 expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Flush the debounced state update + await asyncSetTimeout(0); + + // Expansion state debounce has settled — both expanded nodes are in the set + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: ['row-group-country-Ireland', 'row-group-country-Ireland-year-2020'], + collapsedRowGroupIds: [], + }); + + // Remove year from grouping - only country remains + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: false }, + ]); + + // Ireland should remain expanded; France should remain collapsed + await new GridRows(api, 'after removing year group column').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:1 country:"Ireland" year:2020 + │ └── LEAF id:2 country:"Ireland" year:2021 + └─┬ LEAF_GROUP collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Flush the debounced state update + await asyncSetTimeout(0); + + // Year sub-group nodes are gone — only Ireland remains in the expanded set + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: ['row-group-country-Ireland'], + collapsedRowGroupIds: [], + }); + }); + + test('removing the top group column resets all expansion to default', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ], + rowData, + getRowId: (params) => params.data.id, + }); + + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + + await new GridRows(api, 'Ireland and Ireland/2020 expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Flush the debounced state update + await asyncSetTimeout(0); + + // Expansion state debounce has settled — both expanded nodes are in the set + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: ['row-group-country-Ireland', 'row-group-country-Ireland-year-2020'], + collapsedRowGroupIds: [], + }); + + // Remove country (top level) from grouping — only year remains + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: false }, + { field: 'year', rowGroup: true, hide: true }, + ]); + + // All year-level IDs change (old: row-group-country-Ireland-year-2020, new: row-group-year-2020) + // so no saved IDs match — all groups fall back to groupDefaultExpanded (collapsed) + await new GridRows(api, 'after removing top group column').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP collapsed id:row-group-year-2020 ag-Grid-AutoColumn:2020 + │ ├── LEAF hidden id:1 country:"Ireland" year:2020 + │ └── LEAF hidden id:3 country:"France" year:2020 + └─┬ LEAF_GROUP collapsed id:row-group-year-2021 ag-Grid-AutoColumn:2021 + · └── LEAF hidden id:2 country:"Ireland" year:2021 + `); + + // Flush the debounced state update + await asyncSetTimeout(0); + + // All year-level IDs changed so none matched — state resets to empty + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: [], + collapsedRowGroupIds: [], + }); + }); + + test('removing a middle group column does not preserve deeper level expansion state', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020, sport: 'Football' }, + { id: '2', country: 'Ireland', year: 2020, sport: 'Rugby' }, + { id: '3', country: 'Ireland', year: 2021, sport: 'Football' }, + { id: '4', country: 'France', year: 2020, sport: 'Football' }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'sport', rowGroup: true, hide: true }, + ], + rowData, + getRowId: (params) => params.data.id, + }); + + // Expand Ireland → 2020 → Football (all three levels) + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + api.setRowNodeExpanded( + api.getRowNode('row-group-country-Ireland-year-2020-sport-Football')!, + true, + false, + true + ); + + await new GridRows(api, 'three levels expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ filler id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + │ │ │ └── LEAF id:1 country:"Ireland" year:2020 sport:"Football" + │ │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020-sport-Rugby ag-Grid-AutoColumn:"Rugby" + │ │ · └── LEAF hidden id:2 country:"Ireland" year:2020 sport:"Rugby" + │ └─┬ filler collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021-sport-Football ag-Grid-AutoColumn:"Football" + │ · · └── LEAF hidden id:3 country:"Ireland" year:2021 sport:"Football" + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ filler collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + · · · └── LEAF hidden id:4 country:"France" year:2020 sport:"Football" + `); + + // Remove the middle group column (year) + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: false }, + { field: 'sport', rowGroup: true, hide: true }, + ]); + + // Country-level expansion IS preserved (Ireland expanded, France collapsed). + // Sport-level expansion is NOT preserved because IDs encode the full ancestor path: + // before: row-group-country-Ireland-year-2020-sport-Football + // after: row-group-country-Ireland-sport-Football + // The IDs no longer match, so sport groups fall back to groupDefaultExpanded (collapsed). + await new GridRows(api, 'after removing middle group column').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-sport-Football ag-Grid-AutoColumn:"Football" + │ │ ├── LEAF hidden id:1 country:"Ireland" year:2020 sport:"Football" + │ │ └── LEAF hidden id:3 country:"Ireland" year:2021 sport:"Football" + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-sport-Rugby ag-Grid-AutoColumn:"Rugby" + │ · └── LEAF hidden id:2 country:"Ireland" year:2020 sport:"Rugby" + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-sport-Football ag-Grid-AutoColumn:"Football" + · · └── LEAF hidden id:4 country:"France" year:2020 sport:"Football" + `); + }); + + test('swapping 2nd and 3rd group columns resets expansion at those levels', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020, sport: 'Football' }, + { id: '2', country: 'Ireland', year: 2020, sport: 'Rugby' }, + { id: '3', country: 'Ireland', year: 2021, sport: 'Football' }, + { id: '4', country: 'France', year: 2020, sport: 'Football' }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroupIndex: 0, hide: true }, + { field: 'year', rowGroupIndex: 1, hide: true }, + { field: 'sport', rowGroupIndex: 2, hide: true }, + ], + rowData, + getRowId: (params) => params.data.id, + }); + + // Expand Ireland → 2020 → Football + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + api.setRowNodeExpanded( + api.getRowNode('row-group-country-Ireland-year-2020-sport-Football')!, + true, + false, + true + ); + + await new GridRows(api, 'three levels expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ filler id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + │ │ │ └── LEAF id:1 country:"Ireland" year:2020 sport:"Football" + │ │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020-sport-Rugby ag-Grid-AutoColumn:"Rugby" + │ │ · └── LEAF hidden id:2 country:"Ireland" year:2020 sport:"Rugby" + │ └─┬ filler collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021-sport-Football ag-Grid-AutoColumn:"Football" + │ · · └── LEAF hidden id:3 country:"Ireland" year:2021 sport:"Football" + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ filler collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + · · · └── LEAF hidden id:4 country:"France" year:2020 sport:"Football" + `); + + // Swap year and sport column order + api.setGridOption('columnDefs', [ + { field: 'country', rowGroupIndex: 0, hide: true }, + { field: 'year', rowGroupIndex: 2, hide: true }, + { field: 'sport', rowGroupIndex: 1, hide: true }, + ]); + + // Country-level expansion is preserved (Ireland expanded, France collapsed). + // Levels 2 and 3 reset because IDs change when column order changes: + // old: row-group-country-Ireland-year-2020-sport-Football + // new: row-group-country-Ireland-sport-Football-year-2020 + await new GridRows(api, 'after swapping year and sport').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ filler collapsed id:row-group-country-Ireland-sport-Football ag-Grid-AutoColumn:"Football" + │ │ ├─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-sport-Football-year-2020 ag-Grid-AutoColumn:2020 + │ │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 sport:"Football" + │ │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-sport-Football-year-2021 ag-Grid-AutoColumn:2021 + │ │ · └── LEAF hidden id:3 country:"Ireland" year:2021 sport:"Football" + │ └─┬ filler collapsed id:row-group-country-Ireland-sport-Rugby ag-Grid-AutoColumn:"Rugby" + │ · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-sport-Rugby-year-2020 ag-Grid-AutoColumn:2020 + │ · · └── LEAF hidden id:2 country:"Ireland" year:2020 sport:"Rugby" + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ filler collapsed hidden id:row-group-country-France-sport-Football ag-Grid-AutoColumn:"Football" + · · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-sport-Football-year-2020 ag-Grid-AutoColumn:2020 + · · · └── LEAF hidden id:4 country:"France" year:2020 sport:"Football" + `); + }); + + test('swapping group columns with isGroupOpenByDefault expands via callback at reset levels', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020, sport: 'Football' }, + { id: '2', country: 'Ireland', year: 2020, sport: 'Rugby' }, + { id: '3', country: 'Ireland', year: 2021, sport: 'Football' }, + { id: '4', country: 'France', year: 2020, sport: 'Football' }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroupIndex: 0, hide: true }, + { field: 'year', rowGroupIndex: 1, hide: true }, + { field: 'sport', rowGroupIndex: 2, hide: true }, + ], + rowData, + getRowId: (params) => params.data.id, + // Expand sport="Football" groups regardless of level + isGroupOpenByDefault: (params) => params.field === 'sport' && params.key === 'Football', + }); + + // Initially: sport=Football groups are expanded, others collapsed. + // Manually expand Ireland and Ireland/2020 as well. + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + + await new GridRows(api, 'initial state').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ filler id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + │ │ │ └── LEAF id:1 country:"Ireland" year:2020 sport:"Football" + │ │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020-sport-Rugby ag-Grid-AutoColumn:"Rugby" + │ │ · └── LEAF hidden id:2 country:"Ireland" year:2020 sport:"Rugby" + │ └─┬ filler collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └─┬ LEAF_GROUP hidden id:row-group-country-Ireland-year-2021-sport-Football ag-Grid-AutoColumn:"Football" + │ · · └── LEAF hidden id:3 country:"Ireland" year:2021 sport:"Football" + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ filler collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └─┬ LEAF_GROUP hidden id:row-group-country-France-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + · · · └── LEAF hidden id:4 country:"France" year:2020 sport:"Football" + `); + + // Swap year and sport column order + api.setGridOption('columnDefs', [ + { field: 'country', rowGroupIndex: 0, hide: true }, + { field: 'year', rowGroupIndex: 2, hide: true }, + { field: 'sport', rowGroupIndex: 1, hide: true }, + ]); + + // Country-level expansion is preserved (Ireland expanded, France collapsed). + // Levels 2 and 3 IDs change, so saved IDs don't match — but isGroupOpenByDefault + // fires for sport="Football" nodes, expanding them at their new level. + await new GridRows(api, 'after swap with isGroupOpenByDefault').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ filler id:row-group-country-Ireland-sport-Football ag-Grid-AutoColumn:"Football" + │ │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-sport-Football-year-2020 ag-Grid-AutoColumn:2020 + │ │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 sport:"Football" + │ │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-sport-Football-year-2021 ag-Grid-AutoColumn:2021 + │ │ · └── LEAF hidden id:3 country:"Ireland" year:2021 sport:"Football" + │ └─┬ filler collapsed id:row-group-country-Ireland-sport-Rugby ag-Grid-AutoColumn:"Rugby" + │ · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-sport-Rugby-year-2020 ag-Grid-AutoColumn:2020 + │ · · └── LEAF hidden id:2 country:"Ireland" year:2020 sport:"Rugby" + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ filler hidden id:row-group-country-France-sport-Football ag-Grid-AutoColumn:"Football" + · · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-sport-Football-year-2020 ag-Grid-AutoColumn:2020 + · · · └── LEAF hidden id:4 country:"France" year:2020 sport:"Football" + `); + }); + + test('groupDefaultExpanded -1 with added group column keeps all groups expanded', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [{ field: 'country', rowGroup: true, hide: true }, { field: 'year' }], + groupDefaultExpanded: -1, + rowData, + getRowId: (params) => params.data.id, + }); + + await new GridRows(api, 'initial - all countries expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:1 country:"Ireland" year:2020 + │ └── LEAF id:2 country:"Ireland" year:2021 + └─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" + · └── LEAF id:3 country:"France" year:2020 + `); + + // Add year as a second grouping column + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ]); + + // All country groups should remain expanded; new year sub-groups should also be expanded + // because groupDefaultExpanded: -1 applies to newly created group nodes + await new GridRows(api, 'after adding year as group column').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF id:3 country:"France" year:2020 + `); + }); + + test('adding a group column above existing resets all expansion to default', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [{ field: 'country' }, { field: 'year', rowGroupIndex: 0, hide: true }], + rowData, + getRowId: (params) => params.data.id, + }); + + api.setRowNodeExpanded(api.getRowNode('row-group-year-2020')!, true, false, true); + + await new GridRows(api, 'year 2020 expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-year-2020 ag-Grid-AutoColumn:2020 + │ ├── LEAF id:1 country:"Ireland" year:2020 + │ └── LEAF id:3 country:"France" year:2020 + └─┬ LEAF_GROUP collapsed id:row-group-year-2021 ag-Grid-AutoColumn:2021 + · └── LEAF hidden id:2 country:"Ireland" year:2021 + `); + + // Add country as a new top-level group column above year + api.setGridOption('columnDefs', [ + { field: 'country', rowGroupIndex: 0, hide: true }, + { field: 'year', rowGroupIndex: 1, hide: true }, + ]); + + // Year-level IDs change (old: row-group-year-2020, new: row-group-country-Ireland-year-2020) + // so no saved IDs match — all groups fall back to groupDefaultExpanded (collapsed) + await new GridRows(api, 'after adding country above year').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler collapsed id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + }); + + test('adding a group column in the middle does not preserve deeper level expansion state', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020, sport: 'Football' }, + { id: '2', country: 'Ireland', year: 2020, sport: 'Rugby' }, + { id: '3', country: 'Ireland', year: 2021, sport: 'Football' }, + { id: '4', country: 'France', year: 2020, sport: 'Football' }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroupIndex: 0, hide: true }, + { field: 'year' }, + { field: 'sport', rowGroupIndex: 1, hide: true }, + ], + rowData, + getRowId: (params) => params.data.id, + }); + + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-sport-Football')!, true, false, true); + + await new GridRows(api, 'Ireland and Ireland/Football expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-sport-Football ag-Grid-AutoColumn:"Football" + │ │ ├── LEAF id:1 country:"Ireland" year:2020 sport:"Football" + │ │ └── LEAF id:3 country:"Ireland" year:2021 sport:"Football" + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-sport-Rugby ag-Grid-AutoColumn:"Rugby" + │ · └── LEAF hidden id:2 country:"Ireland" year:2020 sport:"Rugby" + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-sport-Football ag-Grid-AutoColumn:"Football" + · · └── LEAF hidden id:4 country:"France" year:2020 sport:"Football" + `); + + // Add year as a middle group column between country and sport + api.setGridOption('columnDefs', [ + { field: 'country', rowGroupIndex: 0, hide: true }, + { field: 'year', rowGroupIndex: 1, hide: true }, + { field: 'sport', rowGroupIndex: 2, hide: true }, + ]); + + // Country-level expansion is preserved (Ireland expanded, France collapsed). + // Sport-level IDs change (old: row-group-country-Ireland-sport-Football, + // new: row-group-country-Ireland-year-2020-sport-Football) so sport expansion resets. + await new GridRows(api, 'after adding year in the middle').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ filler collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ ├─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + │ │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 sport:"Football" + │ │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2020-sport-Rugby ag-Grid-AutoColumn:"Rugby" + │ │ · └── LEAF hidden id:2 country:"Ireland" year:2020 sport:"Rugby" + │ └─┬ filler collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021-sport-Football ag-Grid-AutoColumn:"Football" + │ · · └── LEAF hidden id:3 country:"Ireland" year:2021 sport:"Football" + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ filler collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + · · · └── LEAF hidden id:4 country:"France" year:2020 sport:"Football" + `); + }); + + test('groupDefaultExpanded -1 with explicit collapse preserves collapsed state on column change', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [{ field: 'country', rowGroup: true, hide: true }, { field: 'year' }], + groupDefaultExpanded: -1, + rowData, + getRowId: (params) => params.data.id, + }); + + await new GridRows(api, 'initial - all expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:1 country:"Ireland" year:2020 + │ └── LEAF id:2 country:"Ireland" year:2021 + └─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" + · └── LEAF id:3 country:"France" year:2020 + `); + + // Explicitly collapse France + api.setRowNodeExpanded(api.getRowNode('row-group-country-France')!, false, false, true); + + await new GridRows(api, 'France collapsed').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF id:1 country:"Ireland" year:2020 + │ └── LEAF id:2 country:"Ireland" year:2021 + └─┬ LEAF_GROUP collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Add year as a second grouping column + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ]); + + // Ireland stays expanded (saved expanded ID match). France stays collapsed (saved collapsed ID match). + // New year sub-groups follow groupDefaultExpanded: -1 (expanded). + await new GridRows(api, 'after adding year — France stays collapsed').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + }); + + describe('resetRowGroupExpansion', () => { + test('resets all groups back to collapsed (default groupDefaultExpanded: 0)', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ], + rowData, + getRowId: (params) => params.data.id, + }); + + // Expand Ireland and Ireland/2020 + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + + await new GridRows(api, 'Ireland and Ireland/2020 expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Flush the debounced state update + await asyncSetTimeout(0); + + // Expansion state debounce has settled — both expanded nodes are in the set + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: ['row-group-country-Ireland', 'row-group-country-Ireland-year-2020'], + collapsedRowGroupIds: [], + }); + + api.resetRowGroupExpansion(); + + // All groups should be collapsed (groupDefaultExpanded defaults to 0) + await new GridRows(api, 'after resetRowGroupExpansion').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler collapsed id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Flush the debounced state update + await asyncSetTimeout(0); + + // Expansion state debounce settled — reset returns state to empty + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: [], + collapsedRowGroupIds: [], + }); + }); + + test('resets to groupDefaultExpanded: -1 (all expanded)', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ], + groupDefaultExpanded: -1, + rowData, + getRowId: (params) => params.data.id, + }); + + // Collapse France and Ireland/2020 + api.setRowNodeExpanded(api.getRowNode('row-group-country-France')!, false, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, false, false, true); + + await new GridRows(api, 'France collapsed, Ireland/2020 collapsed').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + api.resetRowGroupExpansion(); + + // All groups should be expanded (groupDefaultExpanded: -1) + await new GridRows(api, 'after resetRowGroupExpansion — all expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF id:3 country:"France" year:2020 + `); + }); + + test('resets to groupDefaultExpanded: 1 (first level expanded only)', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ], + groupDefaultExpanded: 1, + rowData, + getRowId: (params) => params.data.id, + }); + + // Collapse Ireland and expand Ireland/2020 (overrides both levels) + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, false, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + + await new GridRows(api, 'Ireland collapsed, Ireland/2020 expanded (override)').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler collapsed id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP hidden id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + api.resetRowGroupExpansion(); + + // Level 0 expanded, level 1 collapsed (groupDefaultExpanded: 1) + await new GridRows(api, 'after resetRowGroupExpansion — level 0 expanded, level 1 collapsed').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + }); + + test('re-evaluates isGroupOpenByDefault callback', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ], + rowData, + getRowId: (params) => params.data.id, + // Only Ireland should be expanded by default + isGroupOpenByDefault: (params) => params.key === 'Ireland', + }); + + await new GridRows(api, 'initial — Ireland expanded via callback').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Override: collapse Ireland, expand France and Ireland/2020 + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, false, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-France')!, true, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + + await new GridRows(api, 'user overrides applied').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler collapsed id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP hidden id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + api.resetRowGroupExpansion(); + + // Callback re-evaluated: Ireland expanded, France collapsed (back to initial state) + await new GridRows(api, 'after resetRowGroupExpansion — callback re-evaluated').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + }); + + test('resets preserved expansion state after column change', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManager.createGrid('myGrid', { + columnDefs: [{ field: 'country', rowGroup: true, hide: true }, { field: 'year' }], + rowData, + getRowId: (params) => params.data.id, + }); + + // Expand Ireland + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + + // Add year as group column — Ireland stays expanded (preserved) + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ]); + + // columnRowGroupChanged updates state synchronously — Ireland's ID is unchanged so it remains expanded + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: ['row-group-country-Ireland'], + collapsedRowGroupIds: [], + }); + + await new GridRows(api, 'Ireland preserved expanded after column change').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + api.resetRowGroupExpansion(); + + // Reset clears the preserved expansion — all groups return to default (collapsed) + await new GridRows(api, 'after resetRowGroupExpansion — all collapsed').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler collapsed id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Flush the debounced state update + await asyncSetTimeout(0); + + // Expansion state debounce settled — reset returns state to empty + expect(api.getState().rowGroupExpansion).toEqual({ + expandedRowGroupIds: [], + collapsedRowGroupIds: [], + }); + }); + }); + + describe('pivot mode', () => { + const pivotGridsManager = new TestGridsManager({ + modules: [ClientSideRowModelModule, GridStateModule, RowGroupingModule, PivotModule], + }); + + beforeEach(() => { + pivotGridsManager.reset(); + }); + + afterEach(() => { + pivotGridsManager.reset(); + }); + + test('pivot mode without active pivot — add deeper group column preserves non-leaf expansion, leaf groups forced collapsed', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020, sport: 'Football' }, + { id: '2', country: 'Ireland', year: 2021, sport: 'Rugby' }, + { id: '3', country: 'France', year: 2020, sport: 'Football' }, + ]; + + const api = pivotGridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ], + pivotMode: true, + rowData, + getRowId: (params) => params.data.id, + }); + + // Country is non-leaf (filler), year is LEAF_GROUP → forced collapsed in pivot mode + await new GridRows(api, 'initial — all collapsed in pivot mode').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler collapsed id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Expand Ireland (non-leaf group) + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + + await new GridRows(api, 'Ireland expanded, year leaf groups forced collapsed').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Add sport as 3rd group column + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'sport', rowGroup: true, hide: true }, + ]); + + // Ireland preserved expanded (non-leaf, snapshot match). + // Year now non-leaf → stays collapsed (snapshot match). + // Sport is new leaf group → forced collapsed in pivot mode. + await new GridRows(api, 'after adding sport — leaf groups forced collapsed').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ filler collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + │ │ · └── LEAF hidden id:1 country:"Ireland" year:2020 sport:"Football" + │ └─┬ filler collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021-sport-Rugby ag-Grid-AutoColumn:"Rugby" + │ · · └── LEAF hidden id:2 country:"Ireland" year:2021 sport:"Rugby" + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ filler collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020-sport-Football ag-Grid-AutoColumn:"Football" + · · · └── LEAF hidden id:3 country:"France" year:2020 sport:"Football" + `); + }); + + test('pivot mode with active pivot — add deeper group column preserves non-leaf expansion, leaf groups forced collapsed', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020, sport: 'Football', medals: 2 }, + { id: '2', country: 'Ireland', year: 2021, sport: 'Rugby', medals: 3 }, + { id: '3', country: 'France', year: 2020, sport: 'Football', medals: 4 }, + ]; + + const gridRowsOptions = { + forcedColumns: ['ag-Grid-AutoColumn', 'pivot_sport_Football_medals', 'pivot_sport_Rugby_medals'], + }; + + const api = pivotGridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'sport', pivot: true, hide: true }, + { field: 'medals', aggFunc: 'sum', hide: true }, + { field: 'year' }, + ], + pivotMode: true, + rowData, + getRowId: (params) => params.data.id, + }); + + // Country is LEAF_GROUP → forced collapsed in pivot mode + // Secondary pivot columns created from sport values + await new GridRows(api, 'initial — leaf groups forced collapsed with pivot', gridRowsOptions).check(` + ROOT id:ROOT_NODE_ID pivot_sport_Football_medals:6 pivot_sport_Rugby_medals:3 + ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" pivot_sport_Football_medals:2 pivot_sport_Rugby_medals:3 + │ ├── LEAF hidden id:1 pivot_sport_Football_medals:2 pivot_sport_Rugby_medals:2 + │ └── LEAF hidden id:2 pivot_sport_Football_medals:3 pivot_sport_Rugby_medals:3 + └─┬ LEAF_GROUP collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" pivot_sport_Football_medals:4 pivot_sport_Rugby_medals:null + · └── LEAF hidden id:3 pivot_sport_Football_medals:4 pivot_sport_Rugby_medals:4 + `); + + // Expand Ireland (leaf group in pivot — user override) + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, true, false, true); + + // Add year as 2nd group column — country becomes non-leaf + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + { field: 'sport', pivot: true, hide: true }, + { field: 'medals', aggFunc: 'sum', hide: true }, + ]); + + // Ireland preserved expanded (non-leaf now, snapshot match). + // Year groups are new leaf groups → forced collapsed in pivot mode. + await new GridRows(api, 'after adding year — non-leaf preserved, leaf collapsed', gridRowsOptions).check(` + ROOT id:ROOT_NODE_ID pivot_sport_Football_medals:6 pivot_sport_Rugby_medals:3 + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" pivot_sport_Football_medals:2 pivot_sport_Rugby_medals:3 + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 pivot_sport_Football_medals:2 pivot_sport_Rugby_medals:null + │ │ └── LEAF hidden id:1 pivot_sport_Football_medals:2 pivot_sport_Rugby_medals:2 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 pivot_sport_Football_medals:null pivot_sport_Rugby_medals:3 + │ · └── LEAF hidden id:2 pivot_sport_Football_medals:3 pivot_sport_Rugby_medals:3 + └─┬ filler collapsed id:row-group-country-France ag-Grid-AutoColumn:"France" pivot_sport_Football_medals:4 pivot_sport_Rugby_medals:null + · └─┬ LEAF_GROUP collapsed hidden id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 pivot_sport_Football_medals:4 pivot_sport_Rugby_medals:null + · · └── LEAF hidden id:3 pivot_sport_Football_medals:4 pivot_sport_Rugby_medals:4 + `); + }); + + test('resetRowGroupExpansion in pivot mode forces leaf groups collapsed, re-evaluates non-leaf defaults', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020, sport: 'Football' }, + { id: '2', country: 'Ireland', year: 2021, sport: 'Rugby' }, + { id: '3', country: 'France', year: 2020, sport: 'Football' }, + ]; + + const api = pivotGridsManager.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ], + pivotMode: true, + groupDefaultExpanded: -1, + rowData, + getRowId: (params) => params.data.id, + }); + + // groupDefaultExpanded: -1 expands non-leaf groups; leaf groups forced collapsed in pivot + await new GridRows(api, 'initial — non-leaf expanded, leaf forced collapsed').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + // Collapse Ireland, expand year leaf groups (user overrides) + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland')!, false, false, true); + api.setRowNodeExpanded(api.getRowNode('row-group-country-Ireland-year-2020')!, true, false, true); + + // Leaf groups in pivot mode are never expanded (getter returns false), + // so setRowNodeExpanded(true) has no visible effect on leaf groups. + await new GridRows(api, 'user overrides: Ireland collapsed, year 2020 expanded').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler collapsed id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed hidden id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + + api.resetRowGroupExpansion(); + + // Non-leaf groups re-evaluated against groupDefaultExpanded: -1 → expanded. + // Leaf groups forced collapsed in pivot mode regardless. + await new GridRows(api, 'after reset — non-leaf expanded, leaf forced collapsed').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF hidden id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP collapsed id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF hidden id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP collapsed id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF hidden id:3 country:"France" year:2020 + `); + }); + }); + + describe('selection during group column changes', () => { + const gridsManagerWithSelection = new TestGridsManager({ + modules: [ClientSideRowModelModule, GridStateModule, RowSelectionModule, RowGroupingModule], + }); + + beforeEach(() => { + gridsManagerWithSelection.reset(); + }); + + afterEach(() => { + gridsManagerWithSelection.reset(); + }); + + test('group and leaf selections are preserved when adding a deeper group column', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + { id: '4', country: 'France', year: 2021 }, + ]; + + const api = gridsManagerWithSelection.createGrid('myGrid', { + columnDefs: [{ field: 'country', rowGroup: true, hide: true }, { field: 'year' }], + rowSelection: { mode: 'multiRow' }, + groupDefaultExpanded: -1, + rowData, + getRowId: (params) => params.data.id, + }); + + // Select a group node and some leaf nodes + api.setNodesSelected({ + nodes: [api.getRowNode('row-group-country-Ireland')!, api.getRowNode('1')!, api.getRowNode('3')!], + newValue: true, + }); + + await new GridRows(api, 'initial selection').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP selected id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF selected id:1 country:"Ireland" year:2020 + │ └── LEAF id:2 country:"Ireland" year:2021 + └─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" + · ├── LEAF selected id:3 country:"France" year:2020 + · └── LEAF id:4 country:"France" year:2021 + `); + + // Add year as a second grouping column — triggers shotgunResetEverything + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ]); + + // Leaf selections (id:1, id:3) and the Ireland group selection are preserved + // because group nodes with the same ID are reused during the regroup. + await new GridRows(api, 'after adding year group column').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler selected id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF selected id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · ├─┬ LEAF_GROUP id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · │ └── LEAF selected id:3 country:"France" year:2020 + · └─┬ LEAF_GROUP id:row-group-country-France-year-2021 ag-Grid-AutoColumn:2021 + · · └── LEAF id:4 country:"France" year:2021 + `); + }); + + test('group selections are preserved for reused nodes but lost for destroyed nodes when removing a group column', async () => { + const rowData = [ + { id: '1', country: 'Ireland', year: 2020 }, + { id: '2', country: 'Ireland', year: 2021 }, + { id: '3', country: 'France', year: 2020 }, + ]; + + const api = gridsManagerWithSelection.createGrid('myGrid', { + columnDefs: [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: true, hide: true }, + ], + rowSelection: { mode: 'multiRow' }, + groupDefaultExpanded: -1, + rowData, + getRowId: (params) => params.data.id, + }); + + // Select group nodes and leaf nodes + api.setNodesSelected({ + nodes: [ + api.getRowNode('row-group-country-Ireland')!, + api.getRowNode('row-group-country-Ireland-year-2020')!, + api.getRowNode('1')!, + api.getRowNode('3')!, + ], + newValue: true, + }); + + await new GridRows(api, 'initial selection').check(` + ROOT id:ROOT_NODE_ID + ├─┬ filler selected id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├─┬ LEAF_GROUP selected id:row-group-country-Ireland-year-2020 ag-Grid-AutoColumn:2020 + │ │ └── LEAF selected id:1 country:"Ireland" year:2020 + │ └─┬ LEAF_GROUP id:row-group-country-Ireland-year-2021 ag-Grid-AutoColumn:2021 + │ · └── LEAF id:2 country:"Ireland" year:2021 + └─┬ filler id:row-group-country-France ag-Grid-AutoColumn:"France" + · └─┬ LEAF_GROUP id:row-group-country-France-year-2020 ag-Grid-AutoColumn:2020 + · · └── LEAF selected id:3 country:"France" year:2020 + `); + + // Remove year from grouping + api.setGridOption('columnDefs', [ + { field: 'country', rowGroup: true, hide: true }, + { field: 'year', rowGroup: false }, + ]); + + // Leaf selections preserved. Ireland group selection preserved (same ID reused). + // Year sub-group selections lost (those nodes are destroyed). + await new GridRows(api, 'after removing year group column').check(` + ROOT id:ROOT_NODE_ID + ├─┬ LEAF_GROUP selected id:row-group-country-Ireland ag-Grid-AutoColumn:"Ireland" + │ ├── LEAF selected id:1 country:"Ireland" year:2020 + │ └── LEAF id:2 country:"Ireland" year:2021 + └─┬ LEAF_GROUP id:row-group-country-France ag-Grid-AutoColumn:"France" + · └── LEAF selected id:3 country:"France" year:2020 + `); + }); + }); }); diff --git a/testing/behavioural/src/row-data/row-node-expanded.test.ts b/testing/behavioural/src/row-data/row-node-expanded.test.ts new file mode 100644 index 00000000000..3b739643c50 --- /dev/null +++ b/testing/behavioural/src/row-data/row-node-expanded.test.ts @@ -0,0 +1,46 @@ +import type { RowNode } from 'ag-grid-community'; +import { ClientSideRowModelModule } from 'ag-grid-community'; + +import { GridRows, TestGridsManager } from '../test-utils'; + +describe('RowNode.expanded getter without expansionSvc', () => { + const gridsManager = new TestGridsManager({ + modules: [ClientSideRowModelModule], + }); + + afterEach(() => { + gridsManager.reset(); + }); + + test('root node expanded returns true without expansionSvc', async () => { + const api = gridsManager.createGrid('G', { + columnDefs: [{ field: 'name' }], + rowData: [{ name: 'Alice' }, { name: 'Bob' }], + }); + + const gridRows = new GridRows(api, 'root node expanded'); + const rootNode = gridRows.rootRowNode!; + + expect(rootNode).toBeDefined(); + expect(rootNode.id).toBe('ROOT_NODE_ID'); + expect(rootNode.level).toBe(-1); + expect(rootNode._expanded).toBe(true); + expect(rootNode.expanded).toBe(true); + }); + + test('leaf nodes expanded returns false without expansionSvc', async () => { + const api = gridsManager.createGrid('G', { + columnDefs: [{ field: 'name' }], + rowData: [{ name: 'Alice' }], + getRowId: ({ data }) => data.name, + }); + + const leafNode = api.getRowNode('Alice')!; + + expect(leafNode).toBeDefined(); + expect(leafNode.group).toBe(false); + expect((leafNode as RowNode)._expanded).toBeUndefined(); + // Without expansionSvc, the getter falls back to `_expanded as boolean` which is undefined (falsy) + expect(leafNode.expanded).toEqual(false); + }); +}); diff --git a/testing/behavioural/src/services/server-side-expansion-service.test.ts b/testing/behavioural/src/services/server-side-expansion-service.test.ts index 1bdc8131652..c8f3612606c 100644 --- a/testing/behavioural/src/services/server-side-expansion-service.test.ts +++ b/testing/behavioural/src/services/server-side-expansion-service.test.ts @@ -30,7 +30,9 @@ describe('ServerSideExpansionService', () => { expansionService['serverSideRowModel'] = beans.serverSideRowModel as any; expansionService['eventSvc'] = beans.eventSvc; expansionService['beans'] = beans; + expansionService['createBean'] = (bean: any) => bean; expansionService['createManagedBean'] = (bean: any) => bean; + expansionService['destroyBean'] = () => undefined; expansionService['addManagedEventListeners'] = () => []; expansionService['addManagedPropertyListener'] = () => () => null; expansionService.postConstruct(); diff --git a/testing/behavioural/src/test-utils/gridRows/gridRows.ts b/testing/behavioural/src/test-utils/gridRows/gridRows.ts index abbcab8b766..2c023ae50f8 100644 --- a/testing/behavioural/src/test-utils/gridRows/gridRows.ts +++ b/testing/behavioural/src/test-utils/gridRows/gridRows.ts @@ -265,6 +265,12 @@ export class GridRows { attempt.loadErrors(); lastError = attempt.#tryCheck(diagramSnapshot); if (!lastError) { + if (i > 0) { + console.error( + `GridRows flaky check detected for "${this.label}" — passed only after retrying with delays. ` + + `Add \`await asyncSetTimeout(N)\` before this check to avoid intermittent failures.` + ); + } return this; } if (i < retryDelays.length) { @@ -273,13 +279,6 @@ export class GridRows { } } - if (attempt !== this) { - console.error( - `GridRows flaky check detected for "${this.label}" — passed only after retrying with delays. ` + - `Add \`await asyncSetTimeout(N)\` before this check to avoid intermittent failures.` - ); - } - addDiagramToError(lastError, attempt.makeDiagram(false), this.label); Error.captureStackTrace(lastError, this.check); throw lastError; diff --git a/testing/behavioural/src/test-utils/gridRows/rows-validation/gridRowsValidator.ts b/testing/behavioural/src/test-utils/gridRows/rows-validation/gridRowsValidator.ts index 61e95617f07..e74698e1464 100644 --- a/testing/behavioural/src/test-utils/gridRows/rows-validation/gridRowsValidator.ts +++ b/testing/behavioural/src/test-utils/gridRows/rows-validation/gridRowsValidator.ts @@ -54,7 +54,6 @@ export class GridRowsValidator { const rowErrors = this.errors.get(root); rowErrors.expectValueEqual('id', root.id, csrm ? 'ROOT_NODE_ID' : undefined); rowErrors.expectValueEqual('level', root.level, -1); - rowErrors.expectValueEqual('expanded', root.expanded, undefined); rowErrors.add(!!root.key && 'Root node has key ' + root.key); rowErrors.add(root.destroyed && 'Root node is destroyed'); rowErrors.add(root.rowIndex !== null && 'Root node has rowIndex ' + root.rowIndex); diff --git a/testing/behavioural/src/version.ts b/testing/behavioural/src/version.ts index 353202c0311..63095b37ec2 100644 --- a/testing/behavioural/src/version.ts +++ b/testing/behavioural/src/version.ts @@ -1,2 +1,2 @@ // DO NOT UPDATE MANUALLY: Generated from script during build time -export const VERSION = '35.1.0-beta.20260316.108'; +export const VERSION = '35.1.0-beta.20260317.135'; diff --git a/testing/csp/package.json b/testing/csp/package.json index ff01dea0b76..e0e485a8a83 100644 --- a/testing/csp/package.json +++ b/testing/csp/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-csp", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "CSP testing for AG Grid", "main": "index.js", "scripts": {}, diff --git a/testing/module-size-angular/package.json b/testing/module-size-angular/package.json index 374be546b35..3db61c92dc2 100644 --- a/testing/module-size-angular/package.json +++ b/testing/module-size-angular/package.json @@ -1,6 +1,6 @@ { "name": "ag-grid-module-size-angular", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "scripts": { "ng": "ng", "start": "ng serve", @@ -20,11 +20,11 @@ "@angular/platform-browser": "^19.0.0", "@angular/platform-browser-dynamic": "^19.0.0", "@angular/router": "^19.0.0", - "ag-grid-angular": "35.1.0-beta.20260316.108", - "ag-grid-community": "35.1.0-beta.20260316.108", - "ag-grid-enterprise": "35.1.0-beta.20260316.108", - "ag-charts-community": "13.1.0-beta.20260316", - "ag-charts-enterprise": "13.1.0-beta.20260316", + "ag-grid-angular": "35.1.0-beta.20260317.135", + "ag-grid-community": "35.1.0-beta.20260317.135", + "ag-grid-enterprise": "35.1.0-beta.20260317.135", + "ag-charts-community": "13.1.0-beta.20260317", + "ag-charts-enterprise": "13.1.0-beta.20260317", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" diff --git a/testing/module-size/package.json b/testing/module-size/package.json index bace13437cd..927574276eb 100644 --- a/testing/module-size/package.json +++ b/testing/module-size/package.json @@ -1,7 +1,7 @@ { "name": "ag-grid-module-size", "private": true, - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "scripts": { "dev": "vite", "cp-app": "cp ./src/App_Src.tsx ./src/App_AUTO.tsx", @@ -14,11 +14,11 @@ "test:e2e": "run-s \"module-combinations -- {1}\" module-validate --" }, "dependencies": { - "ag-grid-react": "35.1.0-beta.20260316.108", - "ag-grid-community": "35.1.0-beta.20260316.108", - "ag-grid-enterprise": "35.1.0-beta.20260316.108", - "ag-charts-community": "13.1.0-beta.20260316", - "ag-charts-enterprise": "13.1.0-beta.20260316", + "ag-grid-react": "35.1.0-beta.20260317.135", + "ag-grid-community": "35.1.0-beta.20260317.135", + "ag-grid-enterprise": "35.1.0-beta.20260317.135", + "ag-charts-community": "13.1.0-beta.20260317", + "ag-charts-enterprise": "13.1.0-beta.20260317", "ag-shared": "0.0.1", "react": "^18.3.1", "react-dom": "^18.3.1" diff --git a/testing/public-recipes/e2e/package.json b/testing/public-recipes/e2e/package.json index f0381527730..dfbe9b74e19 100644 --- a/testing/public-recipes/e2e/package.json +++ b/testing/public-recipes/e2e/package.json @@ -1,12 +1,12 @@ { "name": "ag-grid-public-e2e-testing-recipes", - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "description": "Public E2E testing recipes for AG Grid", "main": "index.js", "scripts": {}, "license": "MIT", "devDependencies": { - "ag-grid-community": "35.1.0-beta.20260316.108", + "ag-grid-community": "35.1.0-beta.20260317.135", "playwright": "^1.56.0", "@playwright/test": "^1.56.0", "@types/node": "^24.0.3" diff --git a/testing/vue3-tests/package.json b/testing/vue3-tests/package.json index a39251e6fd6..5838b27da1c 100644 --- a/testing/vue3-tests/package.json +++ b/testing/vue3-tests/package.json @@ -1,7 +1,7 @@ { "name": "ag-grid-vue3-tests", "private": true, - "version": "35.1.0-beta.20260316.108", + "version": "35.1.0-beta.20260317.135", "type": "module", "scripts": { "dev": "vite", @@ -15,9 +15,9 @@ "dependencies": { "vue": "^3.5.0", "vue-router": "^4.5.0", - "ag-grid-community": "35.1.0-beta.20260316.108", - "ag-grid-enterprise": "35.1.0-beta.20260316.108", - "ag-grid-vue3": "35.1.0-beta.20260316.108", + "ag-grid-community": "35.1.0-beta.20260317.135", + "ag-grid-enterprise": "35.1.0-beta.20260317.135", + "ag-grid-vue3": "35.1.0-beta.20260317.135", "decimal.js": "^10.4.3" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 12c88bf1e3e..089c86a27c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9186,61 +9186,61 @@ adm-zip@^0.5.10: resolved "http://52.50.158.57:4873/adm-zip/-/adm-zip-0.5.16.tgz#0b5e4c779f07dedea5805cdccb1147071d94a909" integrity sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ== -ag-charts-angular@13.1.0-beta.20260316: - version "13.1.0-beta.20260316" - resolved "https://registry.ag-grid.com/ag-charts-angular/-/ag-charts-angular-13.1.0-beta.20260316.tgz#9b12a7fde20fb890f91d04c707a43f172bbbd036" - integrity sha512-kmaMfU69ABo+1jCc2dRNjvfbtJCPuyV1+HeIg6idrZcI9a+w2rKz+wuhkjKbNHszshEVANpZ2czlrhRl1fFGAg== +ag-charts-angular@13.1.0-beta.20260317: + version "13.1.0-beta.20260317" + resolved "https://registry.ag-grid.com/ag-charts-angular/-/ag-charts-angular-13.1.0-beta.20260317.tgz#849515c4450b0d6813d3c0c3aaf8a62031e1812e" + integrity sha512-168ttJyl3Yd8jSZKslgJj9al2AouE9jkYdyJNC+lYjWPwUA3WghH87VJ6YwXvPqkwQAzOdeFyuDe87AiTC1aow== dependencies: - ag-charts-community "13.1.0-beta.20260316" + ag-charts-community "13.1.0-beta.20260317" tslib "^2.3.0" -ag-charts-community@13.1.0-beta.20260316: - version "13.1.0-beta.20260316" - resolved "https://registry.ag-grid.com/ag-charts-community/-/ag-charts-community-13.1.0-beta.20260316.tgz#e0da49c8d7e707ed9eca10b705723864a9cd8cc5" - integrity sha512-4NjGJCSEbLgIVQmVFJGsNCE6d+6Okn3HM+ynR03+m/V8Ut9FupoS8KDW6HCRIhR+neHMgyB8Mekv2eIwaRD1UA== +ag-charts-community@13.1.0-beta.20260317: + version "13.1.0-beta.20260317" + resolved "https://registry.ag-grid.com/ag-charts-community/-/ag-charts-community-13.1.0-beta.20260317.tgz#0d641b4d3f670b5af98d9b19dde0e0825c3f09f9" + integrity sha512-UufsabNwr8rIecxqpOkhRjP4lvoK7EKrCWGHNIhVmuW8Nmd2WvS4F2e9cema13jey4g8MsZhj+eXeuFGzE2K9g== dependencies: - ag-charts-core "13.1.0-beta.20260316" - ag-charts-locale "13.1.0-beta.20260316" - ag-charts-types "13.1.0-beta.20260316" + ag-charts-core "13.1.0-beta.20260317" + ag-charts-locale "13.1.0-beta.20260317" + ag-charts-types "13.1.0-beta.20260317" -ag-charts-core@13.1.0-beta.20260316: - version "13.1.0-beta.20260316" - resolved "https://registry.ag-grid.com/ag-charts-core/-/ag-charts-core-13.1.0-beta.20260316.tgz#4b4998f683f0cf5e25dcd25ca5b7ae07ce4c5305" - integrity sha512-mjxNwFOq7djJTg7j/VoUIOY+s0a+iCEErwsjSsmr7ZFL2wgfM97v3vqPLp8HR3rS4lfawkx5qtrNK1Dola4EWw== +ag-charts-core@13.1.0-beta.20260317: + version "13.1.0-beta.20260317" + resolved "https://registry.ag-grid.com/ag-charts-core/-/ag-charts-core-13.1.0-beta.20260317.tgz#bc88a29d8b546c587acbbef0aac7222bdb1a0ce2" + integrity sha512-rE17H78NfyfiS2MUP2QuwWwfQDy7K+VY1uIUaHZN36Lqzg6EjIGZslXyVafSCKL2PzgbrueDzN9aaAjhpUideg== dependencies: - ag-charts-types "13.1.0-beta.20260316" + ag-charts-types "13.1.0-beta.20260317" -ag-charts-enterprise@13.1.0-beta.20260316: - version "13.1.0-beta.20260316" - resolved "https://registry.ag-grid.com/ag-charts-enterprise/-/ag-charts-enterprise-13.1.0-beta.20260316.tgz#9f1a714ede7e90fc4496ee3f56bfd2729cc460fe" - integrity sha512-fPoxS9Dg4KBRpSm8o4mfHcOMkRUxS39hLCaJZDV773kyWf9filvmss7nNwNgfrCr7aGbvhR996m4wY2Jrqiq/g== +ag-charts-enterprise@13.1.0-beta.20260317: + version "13.1.0-beta.20260317" + resolved "https://registry.ag-grid.com/ag-charts-enterprise/-/ag-charts-enterprise-13.1.0-beta.20260317.tgz#04c6ce46c3fecfb274714256a200e0437a225904" + integrity sha512-EKwOnIxOllFsCDZLrumoc9sq2vfI3XY2tTtL0CGvtCCTPOBKeRaGwX3VXSal17KfMPs34v/AhPhJDXvWaI1zjA== dependencies: - ag-charts-community "13.1.0-beta.20260316" - ag-charts-core "13.1.0-beta.20260316" + ag-charts-community "13.1.0-beta.20260317" + ag-charts-core "13.1.0-beta.20260317" -ag-charts-locale@13.1.0-beta.20260316: - version "13.1.0-beta.20260316" - resolved "https://registry.ag-grid.com/ag-charts-locale/-/ag-charts-locale-13.1.0-beta.20260316.tgz#f96ed40b04adbc75db68d07369181629973845d0" - integrity sha512-i1zYD/RWuhVK+Ffd8QbuGVMnfbIZ2p9i/7JGwMYnhPNxht305MFZbIXCh1wiCJqaQYGe6kiOJJ0zn9p/nLqGvg== +ag-charts-locale@13.1.0-beta.20260317: + version "13.1.0-beta.20260317" + resolved "https://registry.ag-grid.com/ag-charts-locale/-/ag-charts-locale-13.1.0-beta.20260317.tgz#24e78e0b69dd42bcf598bd3b3e3efb0d90f8678f" + integrity sha512-sSdIwX2T/vY0+/6ESgxGIOq9f5LLRDQHYCQad2vqqE/zfRS01Lx968Gqlvy5fhQU0L2drivxz3mvxHIK4b7dMw== -ag-charts-react@13.1.0-beta.20260316: - version "13.1.0-beta.20260316" - resolved "https://registry.ag-grid.com/ag-charts-react/-/ag-charts-react-13.1.0-beta.20260316.tgz#44e864cb9a3eebbdd8dd7033931a39bb9b6340dd" - integrity sha512-wTv7M+9obU1NECIpEqQ1OBpXr4AaiSrGO9s761FYRCS1q4IkYxAXYZNCRhdEcbTlzzUcOhbXChAR95aJwcD/oA== +ag-charts-react@13.1.0-beta.20260317: + version "13.1.0-beta.20260317" + resolved "https://registry.ag-grid.com/ag-charts-react/-/ag-charts-react-13.1.0-beta.20260317.tgz#617d002caa0398588bb18f75310b906bd3a936ca" + integrity sha512-Chbg20g8ds+MwoCXyHDh7ZnLcECuGsg8tOY79yux09NRjFA177aZxMG85QyJ9wKMUo4M5N8FRQEsvcyhSGJe9w== dependencies: - ag-charts-community "13.1.0-beta.20260316" + ag-charts-community "13.1.0-beta.20260317" -ag-charts-types@13.1.0-beta.20260316: - version "13.1.0-beta.20260316" - resolved "https://registry.ag-grid.com/ag-charts-types/-/ag-charts-types-13.1.0-beta.20260316.tgz#d360bd47bc21ff977333f7eee49e1fe8197a483d" - integrity sha512-W44kNoCmpWLv9h66RtcAOGdGl746l+kM5YtgJoLs0Faq+eBy0b9woiqxQV20LeEfVaTwcWHp1zroHE5zlzeDEg== +ag-charts-types@13.1.0-beta.20260317: + version "13.1.0-beta.20260317" + resolved "https://registry.ag-grid.com/ag-charts-types/-/ag-charts-types-13.1.0-beta.20260317.tgz#34862f2c558db6dc4500e08215db834c53d6fc84" + integrity sha512-EcDingQ3VDuRtOMKsUsGNejCRvWE91VXswuAwg6z4WnAJxeFaU5nQuLoYSVE63kkgX83Ciw1epq57AKFJWJNVg== -ag-charts-vue3@13.1.0-beta.20260316: - version "13.1.0-beta.20260316" - resolved "https://registry.ag-grid.com/ag-charts-vue3/-/ag-charts-vue3-13.1.0-beta.20260316.tgz#9550c8a3b80ad5cfe305ba1bbdfc559b9b53742f" - integrity sha512-Lbem16r/o+eN25eIgQUabla1jqx46thWvsp+4+Zk9MLP6bjhvY36vl4aS3FMjGUJ6JyL5f82G8nFz3ref78t5g== +ag-charts-vue3@13.1.0-beta.20260317: + version "13.1.0-beta.20260317" + resolved "https://registry.ag-grid.com/ag-charts-vue3/-/ag-charts-vue3-13.1.0-beta.20260317.tgz#41401eb165ebf9ee3082fdee4e093ebced31cb04" + integrity sha512-prBELHLc3CtVRvmfp0fGA+1yb4nG8aYzhYSFXO7IfRD7QjyUD04J2yLUJI1mLzBuSAkWw7GqHk8XXOymjlJxnA== dependencies: - ag-charts-community "13.1.0-beta.20260316" + ag-charts-community "13.1.0-beta.20260317" agent-base@6, agent-base@^6.0.2: version "6.0.2" @@ -23029,16 +23029,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "http://52.50.158.57:4873/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "http://52.50.158.57:4873/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -23143,7 +23134,7 @@ stringify-entities@^4.0.0: character-entities-html4 "^2.0.0" character-entities-legacy "^3.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "http://52.50.158.57:4873/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -23157,13 +23148,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "http://52.50.158.57:4873/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.2" resolved "http://52.50.158.57:4873/strip-ansi/-/strip-ansi-7.1.2.tgz#132875abde678c7ea8d691533f2e7e22bb744dba" @@ -25633,7 +25617,7 @@ wordwrap@^1.0.0: resolved "http://52.50.158.57:4873/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "http://52.50.158.57:4873/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -25669,15 +25653,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "http://52.50.158.57:4873/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^9.0.0: version "9.0.2" resolved "http://52.50.158.57:4873/wrap-ansi/-/wrap-ansi-9.0.2.tgz#956832dea9494306e6d209eb871643bb873d7c98"