diff --git a/documentation/ag-grid-docs/src/content/docs/aggregation/index.mdoc b/documentation/ag-grid-docs/src/content/docs/aggregation/index.mdoc
index dfe29024d92..d0f073add7b 100644
--- a/documentation/ag-grid-docs/src/content/docs/aggregation/index.mdoc
+++ b/documentation/ag-grid-docs/src/content/docs/aggregation/index.mdoc
@@ -47,7 +47,8 @@ After the grid is initialised aggregations can be applied / retrieved / removed
## Retrieving Aggregated Children
The method `rowNode.getAggregatedChildren(colKey)` with Client Side Row Model returns the immediate children that contribute to the aggregation
-of a group row. This is useful when implementing custom logic based on aggregated data.
+of a group row. This is useful when implementing custom logic based on aggregated data or when
+[Editing Group Rows](./grouping-edit/) to update child rows accordingly.
- For regular group columns, returns the direct children used for aggregation (respecting `suppressAggFilteredOnly`
and `groupAggFiltering` settings).
diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/group-editable-totals-custom/data.ts b/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/group-editable-totals-custom/data.ts
new file mode 100644
index 00000000000..16e57e71b63
--- /dev/null
+++ b/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/group-editable-totals-custom/data.ts
@@ -0,0 +1,43 @@
+export const getData = () => [
+ // Europe - Corporate
+ { id: 'fr-paris', region: 'Europe', segment: 'Corporate', country: 'France', amount: 30 },
+ { id: 'fr-lyon', region: 'Europe', segment: 'Corporate', country: 'France', amount: 30 },
+ { id: 'de-berlin', region: 'Europe', segment: 'Corporate', country: 'Germany', amount: 35 },
+ { id: 'de-hamburg', region: 'Europe', segment: 'Corporate', country: 'Germany', amount: 25 },
+ { id: 'es-madrid', region: 'Europe', segment: 'Corporate', country: 'Spain', amount: 28 },
+ { id: 'es-barcelona', region: 'Europe', segment: 'Corporate', country: 'Spain', amount: 32 },
+ { id: 'uk-london', region: 'Europe', segment: 'Corporate', country: 'UK', amount: 45 },
+ { id: 'uk-manchester', region: 'Europe', segment: 'Corporate', country: 'UK', amount: 35 },
+ // Europe - Enterprise
+ { id: 'it-rome', region: 'Europe', segment: 'Enterprise', country: 'Italy', amount: 40 },
+ { id: 'it-milan', region: 'Europe', segment: 'Enterprise', country: 'Italy', amount: 20 },
+ { id: 'pl-warsaw', region: 'Europe', segment: 'Enterprise', country: 'Poland', amount: 26 },
+ { id: 'pl-krakow', region: 'Europe', segment: 'Enterprise', country: 'Poland', amount: 24 },
+ { id: 'nl-amsterdam', region: 'Europe', segment: 'Enterprise', country: 'Netherlands', amount: 38 },
+ { id: 'nl-rotterdam', region: 'Europe', segment: 'Enterprise', country: 'Netherlands', amount: 32 },
+ // Americas - Corporate
+ { id: 'us-nyc', region: 'Americas', segment: 'Corporate', country: 'USA', amount: 70 },
+ { id: 'us-la', region: 'Americas', segment: 'Corporate', country: 'USA', amount: 30 },
+ { id: 'us-austin', region: 'Americas', segment: 'Corporate', country: 'USA', amount: 25 },
+ { id: 'us-chicago', region: 'Americas', segment: 'Corporate', country: 'USA', amount: 55 },
+ { id: 'ca-toronto', region: 'Americas', segment: 'Corporate', country: 'Canada', amount: 35 },
+ { id: 'ca-vancouver', region: 'Americas', segment: 'Corporate', country: 'Canada', amount: 25 },
+ // Americas - Enterprise
+ { id: 'br-sao-paulo', region: 'Americas', segment: 'Enterprise', country: 'Brazil', amount: 30 },
+ { id: 'br-rio', region: 'Americas', segment: 'Enterprise', country: 'Brazil', amount: 22 },
+ { id: 'mx-tijuana', region: 'Americas', segment: 'Enterprise', country: 'Mexico', amount: 28 },
+ { id: 'mx-guadalajara', region: 'Americas', segment: 'Enterprise', country: 'Mexico', amount: 18 },
+ { id: 'ar-buenos-aires', region: 'Americas', segment: 'Enterprise', country: 'Argentina', amount: 24 },
+ // Asia Pacific - Corporate
+ { id: 'jp-tokyo', region: 'Asia Pacific', segment: 'Corporate', country: 'Japan', amount: 65 },
+ { id: 'jp-osaka', region: 'Asia Pacific', segment: 'Corporate', country: 'Japan', amount: 45 },
+ { id: 'au-sydney', region: 'Asia Pacific', segment: 'Corporate', country: 'Australia', amount: 42 },
+ { id: 'au-melbourne', region: 'Asia Pacific', segment: 'Corporate', country: 'Australia', amount: 38 },
+ // Asia Pacific - Enterprise
+ { id: 'cn-shanghai', region: 'Asia Pacific', segment: 'Enterprise', country: 'China', amount: 80 },
+ { id: 'cn-beijing', region: 'Asia Pacific', segment: 'Enterprise', country: 'China', amount: 70 },
+ { id: 'sg-singapore', region: 'Asia Pacific', segment: 'Enterprise', country: 'Singapore', amount: 55 },
+ { id: 'kr-seoul', region: 'Asia Pacific', segment: 'Enterprise', country: 'South Korea', amount: 48 },
+ { id: 'in-mumbai', region: 'Asia Pacific', segment: 'Enterprise', country: 'India', amount: 36 },
+ { id: 'in-bangalore', region: 'Asia Pacific', segment: 'Enterprise', country: 'India', amount: 44 },
+];
diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/group-editable-totals-custom/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/group-editable-totals-custom/example.spec.ts
new file mode 100644
index 00000000000..2c0eed06460
--- /dev/null
+++ b/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/group-editable-totals-custom/example.spec.ts
@@ -0,0 +1,8 @@
+import { ensureGridReady, test, waitForGridContent } from '@utils/grid/test-utils';
+
+test.agExample(import.meta, () => {
+ test.eachFramework('Example', async ({ page }) => {
+ await ensureGridReady(page);
+ await waitForGridContent(page);
+ });
+});
diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/group-editable-totals-custom/index.html b/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/group-editable-totals-custom/index.html
new file mode 100644
index 00000000000..6c46dc75f2e
--- /dev/null
+++ b/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/group-editable-totals-custom/index.html
@@ -0,0 +1 @@
+
diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/group-editable-totals-custom/main.ts b/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/group-editable-totals-custom/main.ts
new file mode 100644
index 00000000000..165ad59e96e
--- /dev/null
+++ b/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/group-editable-totals-custom/main.ts
@@ -0,0 +1,120 @@
+import type { GridApi, GridOptions, GroupRowValueSetterFunc, ValueParserParams } from 'ag-grid-community';
+import {
+ ClientSideRowModelModule,
+ ModuleRegistry,
+ NumberFilterModule,
+ TextEditorModule,
+ ValidationModule,
+ createGrid,
+} from 'ag-grid-community';
+import { RowGroupingModule, SetFilterModule } from 'ag-grid-enterprise';
+
+import { getData } from './data';
+
+let gridApi: GridApi;
+
+interface SalesRecord {
+ id: string;
+ region: string;
+ segment: string;
+ country: string;
+ amount: number;
+}
+
+ModuleRegistry.registerModules([
+ RowGroupingModule,
+ ClientSideRowModelModule,
+ NumberFilterModule,
+ SetFilterModule,
+ TextEditorModule,
+ ...(process.env.NODE_ENV !== 'production' ? [ValidationModule] : []),
+]);
+
+// Parse input to integer
+const amountValueParser = (params: ValueParserParams): number | null => {
+ const numericValue = Number(params.newValue);
+ return Number.isFinite(numericValue) ? Math.round(numericValue) : params.oldValue ?? null;
+};
+
+/**
+ * Distributes a new group total equally among children.
+ *
+ * `aggregatedChildren` contains the immediate children used for aggregation:
+ * - For leaf groups: the data rows
+ * - For non-leaf groups: the child groups
+ *
+ * Calling `setDataValue` on a child group triggers `groupRowValueSetter` again,
+ * enabling recursive cascade through the entire group hierarchy.
+ */
+const cascadeGroupTotal: GroupRowValueSetterFunc = ({
+ column,
+ newValue,
+ eventSource,
+ aggregatedChildren,
+}) => {
+ const total = Number(newValue);
+ if (!Number.isFinite(total) || !aggregatedChildren.length) {
+ return false;
+ }
+
+ // Distribute equally among children
+ // https://en.wikipedia.org/wiki/Largest_remainder_method
+ const count = aggregatedChildren.length;
+ const base = Math.floor(total / count);
+ let remainder = Math.round(total) - base * count;
+
+ // Apply the distributed values
+ let changed = false;
+ for (const child of aggregatedChildren) {
+ let value = base;
+ if (remainder > 0) {
+ value++;
+ remainder--;
+ }
+ if (child.setDataValue(column, value, eventSource)) {
+ changed = true;
+ }
+ }
+ return changed;
+};
+
+const gridOptions: GridOptions = {
+ columnDefs: [
+ { field: 'region', rowGroup: true, hide: true },
+ { field: 'segment', rowGroup: true, hide: true, filter: 'agSetColumnFilter' },
+ { field: 'country', filter: 'agSetColumnFilter' },
+ {
+ headerName: 'Amount',
+ field: 'amount',
+ aggFunc: 'sum',
+ editable: true,
+ groupRowEditable: true,
+ filter: 'agNumberColumnFilter',
+ valueParser: amountValueParser,
+ groupRowValueSetter: cascadeGroupTotal,
+ },
+ ],
+ autoGroupColumnDef: {
+ minWidth: 260,
+ cellRendererParams: {
+ suppressCount: true,
+ },
+ },
+ defaultColDef: {
+ flex: 1,
+ sortable: true,
+ filter: true,
+ resizable: true,
+ },
+ rowData: getData(),
+ groupAggFiltering: true,
+ groupDefaultExpanded: -1,
+ animateRows: true,
+ getRowId: ({ data }) => data.id,
+};
+
+// setup the grid after the page has finished loading
+document.addEventListener('DOMContentLoaded', () => {
+ const gridDiv = document.querySelector('#myGrid')!;
+ gridApi = createGrid(gridDiv, gridOptions);
+});
diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/pivot-editable-totals-custom/data.ts b/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/pivot-editable-totals-custom/data.ts
new file mode 100644
index 00000000000..6ec603412d7
--- /dev/null
+++ b/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/pivot-editable-totals-custom/data.ts
@@ -0,0 +1,56 @@
+export const getData = (): SalesRecord[] => [
+ // Europe - Electronics
+ { id: 'eu-fr-elec', region: 'Europe', country: 'France', product: 'Electronics', amount: 120 },
+ { id: 'eu-de-elec', region: 'Europe', country: 'Germany', product: 'Electronics', amount: 150 },
+ { id: 'eu-es-elec', region: 'Europe', country: 'Spain', product: 'Electronics', amount: 80 },
+ { id: 'eu-uk-elec', region: 'Europe', country: 'UK', product: 'Electronics', amount: 140 },
+ { id: 'eu-it-elec', region: 'Europe', country: 'Italy', product: 'Electronics', amount: 95 },
+ // Europe - Clothing
+ { id: 'eu-fr-cloth', region: 'Europe', country: 'France', product: 'Clothing', amount: 60 },
+ { id: 'eu-de-cloth', region: 'Europe', country: 'Germany', product: 'Clothing', amount: 90 },
+ { id: 'eu-es-cloth', region: 'Europe', country: 'Spain', product: 'Clothing', amount: 50 },
+ { id: 'eu-uk-cloth', region: 'Europe', country: 'UK', product: 'Clothing', amount: 75 },
+ { id: 'eu-it-cloth', region: 'Europe', country: 'Italy', product: 'Clothing', amount: 85 },
+ // Europe - Food
+ { id: 'eu-fr-food', region: 'Europe', country: 'France', product: 'Food', amount: 40 },
+ { id: 'eu-de-food', region: 'Europe', country: 'Germany', product: 'Food', amount: 55 },
+ { id: 'eu-es-food', region: 'Europe', country: 'Spain', product: 'Food', amount: 35 },
+ { id: 'eu-uk-food', region: 'Europe', country: 'UK', product: 'Food', amount: 48 },
+ { id: 'eu-it-food', region: 'Europe', country: 'Italy', product: 'Food', amount: 52 },
+ // Americas - Electronics
+ { id: 'am-us-elec', region: 'Americas', country: 'USA', product: 'Electronics', amount: 200 },
+ { id: 'am-ca-elec', region: 'Americas', country: 'Canada', product: 'Electronics', amount: 100 },
+ { id: 'am-br-elec', region: 'Americas', country: 'Brazil', product: 'Electronics', amount: 70 },
+ { id: 'am-mx-elec', region: 'Americas', country: 'Mexico', product: 'Electronics', amount: 65 },
+ { id: 'am-ar-elec', region: 'Americas', country: 'Argentina', product: 'Electronics', amount: 45 },
+ // Americas - Clothing
+ { id: 'am-us-cloth', region: 'Americas', country: 'USA', product: 'Clothing', amount: 80 },
+ { id: 'am-ca-cloth', region: 'Americas', country: 'Canada', product: 'Clothing', amount: 45 },
+ { id: 'am-br-cloth', region: 'Americas', country: 'Brazil', product: 'Clothing', amount: 30 },
+ { id: 'am-mx-cloth', region: 'Americas', country: 'Mexico', product: 'Clothing', amount: 35 },
+ { id: 'am-ar-cloth', region: 'Americas', country: 'Argentina', product: 'Clothing', amount: 28 },
+ // Americas - Food
+ { id: 'am-us-food', region: 'Americas', country: 'USA', product: 'Food', amount: 60 },
+ { id: 'am-ca-food', region: 'Americas', country: 'Canada', product: 'Food', amount: 35 },
+ { id: 'am-br-food', region: 'Americas', country: 'Brazil', product: 'Food', amount: 25 },
+ { id: 'am-mx-food', region: 'Americas', country: 'Mexico', product: 'Food', amount: 32 },
+ { id: 'am-ar-food', region: 'Americas', country: 'Argentina', product: 'Food', amount: 22 },
+ // Asia Pacific - Electronics
+ { id: 'ap-jp-elec', region: 'Asia Pacific', country: 'Japan', product: 'Electronics', amount: 180 },
+ { id: 'ap-cn-elec', region: 'Asia Pacific', country: 'China', product: 'Electronics', amount: 220 },
+ { id: 'ap-kr-elec', region: 'Asia Pacific', country: 'South Korea', product: 'Electronics', amount: 160 },
+ { id: 'ap-au-elec', region: 'Asia Pacific', country: 'Australia', product: 'Electronics', amount: 85 },
+ { id: 'ap-in-elec', region: 'Asia Pacific', country: 'India', product: 'Electronics', amount: 110 },
+ // Asia Pacific - Clothing
+ { id: 'ap-jp-cloth', region: 'Asia Pacific', country: 'Japan', product: 'Clothing', amount: 70 },
+ { id: 'ap-cn-cloth', region: 'Asia Pacific', country: 'China', product: 'Clothing', amount: 120 },
+ { id: 'ap-kr-cloth', region: 'Asia Pacific', country: 'South Korea', product: 'Clothing', amount: 55 },
+ { id: 'ap-au-cloth', region: 'Asia Pacific', country: 'Australia', product: 'Clothing', amount: 40 },
+ { id: 'ap-in-cloth', region: 'Asia Pacific', country: 'India', product: 'Clothing', amount: 95 },
+ // Asia Pacific - Food
+ { id: 'ap-jp-food', region: 'Asia Pacific', country: 'Japan', product: 'Food', amount: 45 },
+ { id: 'ap-cn-food', region: 'Asia Pacific', country: 'China', product: 'Food', amount: 75 },
+ { id: 'ap-kr-food', region: 'Asia Pacific', country: 'South Korea', product: 'Food', amount: 38 },
+ { id: 'ap-au-food', region: 'Asia Pacific', country: 'Australia', product: 'Food', amount: 30 },
+ { id: 'ap-in-food', region: 'Asia Pacific', country: 'India', product: 'Food', amount: 55 },
+];
diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/pivot-editable-totals-custom/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/pivot-editable-totals-custom/example.spec.ts
new file mode 100644
index 00000000000..2c0eed06460
--- /dev/null
+++ b/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/pivot-editable-totals-custom/example.spec.ts
@@ -0,0 +1,8 @@
+import { ensureGridReady, test, waitForGridContent } from '@utils/grid/test-utils';
+
+test.agExample(import.meta, () => {
+ test.eachFramework('Example', async ({ page }) => {
+ await ensureGridReady(page);
+ await waitForGridContent(page);
+ });
+});
diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/pivot-editable-totals-custom/index.html b/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/pivot-editable-totals-custom/index.html
new file mode 100644
index 00000000000..6c46dc75f2e
--- /dev/null
+++ b/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/pivot-editable-totals-custom/index.html
@@ -0,0 +1 @@
+
diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/pivot-editable-totals-custom/main.ts b/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/pivot-editable-totals-custom/main.ts
new file mode 100644
index 00000000000..1d1ce2593c9
--- /dev/null
+++ b/documentation/ag-grid-docs/src/content/docs/grouping-edit/_examples/pivot-editable-totals-custom/main.ts
@@ -0,0 +1,122 @@
+import type { GridApi, GridOptions, GroupRowValueSetterFunc, ValueParserParams } from 'ag-grid-community';
+import {
+ ClientSideRowModelModule,
+ ModuleRegistry,
+ NumberFilterModule,
+ TextEditorModule,
+ ValidationModule,
+ createGrid,
+} from 'ag-grid-community';
+import { ColumnsToolPanelModule, PivotModule, RowGroupingModule, SideBarModule } from 'ag-grid-enterprise';
+
+import { getData } from './data';
+
+interface SalesRecord {
+ id: string;
+ region: string;
+ country: string;
+ product: string;
+ amount: number;
+}
+
+let gridApi: GridApi;
+
+ModuleRegistry.registerModules([
+ RowGroupingModule,
+ ClientSideRowModelModule,
+ NumberFilterModule,
+ TextEditorModule,
+ PivotModule,
+ SideBarModule,
+ ColumnsToolPanelModule,
+ ...(process.env.NODE_ENV !== 'production' ? [ValidationModule] : []),
+]);
+
+// Parse input to integer
+const amountValueParser = (params: ValueParserParams): number | null => {
+ const numericValue = Number(params.newValue);
+ return Number.isFinite(numericValue) ? Math.round(numericValue) : params.oldValue ?? null;
+};
+
+/**
+ * Distributes a new pivot total equally among children.
+ *
+ * In pivot mode, `aggregatedChildren` contains only rows matching the pivot keys.
+ * For example, editing the "Electronics 2024" cell returns only rows where
+ * product="Electronics" AND year=2024.
+ *
+ * `setDataValue` on leaf rows with pivot columns auto-resolves to the underlying
+ * value column. On group rows, it triggers `groupRowValueSetter` for recursive cascade.
+ */
+const cascadeGroupTotal: GroupRowValueSetterFunc = ({
+ column,
+ newValue,
+ eventSource,
+ aggregatedChildren,
+}) => {
+ const total = Number(newValue);
+ if (!Number.isFinite(total) || !aggregatedChildren.length) {
+ return false;
+ }
+
+ // Distribute equally among children
+ // https://en.wikipedia.org/wiki/Largest_remainder_method
+ const count = aggregatedChildren.length;
+ const base = Math.floor(total / count);
+ let remainder = Math.round(total) - base * count;
+
+ // Apply the distributed values
+ let changed = false;
+ for (const child of aggregatedChildren) {
+ let value = base;
+ if (remainder > 0) {
+ value++;
+ remainder--;
+ }
+ if (child.setDataValue(column, value, eventSource)) {
+ changed = true;
+ }
+ }
+ return changed;
+};
+
+const gridOptions: GridOptions = {
+ columnDefs: [
+ { field: 'region', rowGroup: true, hide: true },
+ { field: 'country', rowGroup: true, hide: true },
+ { field: 'product', pivot: true },
+ {
+ headerName: 'Amount',
+ field: 'amount',
+ aggFunc: 'sum',
+ editable: true,
+ groupRowEditable: true,
+ valueParser: amountValueParser,
+ groupRowValueSetter: cascadeGroupTotal,
+ },
+ ],
+ autoGroupColumnDef: {
+ minWidth: 200,
+ cellRendererParams: {
+ suppressCount: true,
+ },
+ },
+ defaultColDef: {
+ flex: 1,
+ minWidth: 120,
+ sortable: true,
+ filter: true,
+ resizable: true,
+ },
+ pivotMode: true,
+ sideBar: 'columns',
+ rowData: getData(),
+ groupDefaultExpanded: -1,
+ getRowId: ({ data }) => data.id,
+};
+
+// setup the grid after the page has finished loading
+document.addEventListener('DOMContentLoaded', () => {
+ const gridDiv = document.querySelector('#myGrid')!;
+ gridApi = createGrid(gridDiv, gridOptions);
+});
diff --git a/documentation/ag-grid-docs/src/content/docs/grouping-edit/index.mdoc b/documentation/ag-grid-docs/src/content/docs/grouping-edit/index.mdoc
index 3db4466d8cb..001d1ff08ca 100644
--- a/documentation/ag-grid-docs/src/content/docs/grouping-edit/index.mdoc
+++ b/documentation/ag-grid-docs/src/content/docs/grouping-edit/index.mdoc
@@ -4,7 +4,7 @@ enterprise: true
---
The grid supports editing grouped data when using the [Client-Side Row Model](./row-models/#client-side).
-This page explains how to keep the grouping hierarchy synchronised with edits.
+This page explains how to keep the grouping hierarchy synchronised with edits and how to allow group rows themselves to be editable.
## Refreshing Groups After Editing
@@ -24,3 +24,82 @@ stable IDs while rebuilding the grouping hierarchy.
See also [Read Only Edit](./value-setters/#read-only-edit) for configuring immutable grouped data or
connecting the grid with a store.
+
+## Editing Group Row Cells
+
+Set `groupRowEditable` on any column that should accept edits on group nodes.
+Provide either a boolean or a callback; callbacks only run for nodes where `rowNode.group === true`, while leaf rows continue to honour `editable`.
+
+`groupRowValueSetter` mirrors the regular `valueSetter`, but it only fires for group rows. It runs
+whenever a group row cell changes through the UI, `rowNode.setDataValue`, or another API call,
+making it the right place to cascade edits to child nodes.
+
+Return `true` from the callback to inform the grid that the value changed and refresh is needed.
+
+Most grouped rows (and filler nodes in tree data) do not own `rowNode.data`, so their column
+`valueSetter` never runs even if `groupRowEditable` is enabled. Provide a `groupRowValueSetter`
+whenever the edit needs to persist or update aggregates; only group rows that own real data objects
+run the normal value pipeline.
+
+When a column defines both `groupRowEditable` and `editable`, AG Grid only evaluates the property
+that matches the current node type, enabling separate rules for group rows and leaves.
+
+## Custom Distribution of Edits
+
+In this example, `groupRowValueSetter` is used to distribute edits on group rows to their descendant rows.
+The example uses a simple strategy of dividing the value equally among all visible children and sub groups.
+
+{% gridExampleRunner title="Custom Editable Group Totals" name="group-editable-totals-custom" exampleHeight=620 /%}
+
+### The aggregatedChildren Parameter
+
+The `groupRowValueSetter` callback receives an `aggregatedChildren` array containing the immediate children
+that contribute to the [Aggregation](./aggregation/) for the edited column. This array makes it easy to
+cascade edited values down to descendant rows without manually traversing the row hierarchy.
+
+The children returned by `aggregatedChildren` depend on the column and grid configuration:
+
+- **Regular value columns**: Returns direct children used for aggregation. This respects the
+ `suppressAggFilteredOnly` grid option and `groupAggFiltering` — when these cause aggregation to include
+ all children (not just filtered ones), `aggregatedChildren` returns all children accordingly.
+- **Pivot columns on leaf groups**: Returns only the children matching the column's pivot keys, since
+ pivot aggregation groups rows by pivot key values.
+- **Non-group rows**: Always returns an empty array — only group rows have aggregated children.
+
+Alternatively, use `rowNode.getAggregatedChildren(colKey)` anywhere in your code to retrieve the same
+children programmatically. See [Retrieving Aggregated Children](./aggregation/#retrieving-aggregated-children)
+for more details.
+
+### Cascading Edits
+
+Key points for implementing cascading edits:
+
+- Call `rowNode.setDataValue` on each child to push updated figures down. This triggers normal
+ aggregation to refresh parent totals.
+- When a child is also a group that defines its own `groupRowValueSetter`, the cascade recurses further.
+- Parent aggregates refresh automatically because child `data` changes re-run the column `aggFunc`,
+ so typing a new total into "Europe" instantly rebalances the children to reach that value.
+
+{% note %}
+The `aggregatedChildren` parameter and `rowNode.getAggregatedChildren()` method are only supported
+with the Client-Side Row Model. For other row models, `aggregatedChildren` is an empty array.
+{% /note %}
+
+## Editing Pivot Columns
+
+When using [Pivoting](./pivoting/), the `groupRowValueSetter` also works with pivot result columns.
+The key difference is that `aggregatedChildren` returns only the children matching the column's pivot keys,
+not all children of the group.
+
+{% gridExampleRunner title="Custom Editable Pivot Totals" name="pivot-editable-totals-custom" exampleHeight=500 /%}
+
+In this example:
+
+- The grid is pivoted by `product` (Electronics, Clothing, Food), grouped by `region` and `country`.
+- Double-click on any pivot column cell to edit the aggregated value.
+- When editing a pivot cell (e.g., "Electronics" column on "Europe" row), the `aggregatedChildren` array
+ contains only the European rows selling Electronics — not all European rows.
+- The edit is distributed equally among just those matching children, leaving other product categories unchanged.
+
+This behaviour ensures that edits on pivot columns affect only the data that contributes to that specific
+pivot value, making cascading edits work correctly with pivoted data.
diff --git a/documentation/ag-grid-docs/src/content/docs/pivoting/index.mdoc b/documentation/ag-grid-docs/src/content/docs/pivoting/index.mdoc
index 7df680e9f38..a6e10d7924e 100644
--- a/documentation/ag-grid-docs/src/content/docs/pivoting/index.mdoc
+++ b/documentation/ag-grid-docs/src/content/docs/pivoting/index.mdoc
@@ -112,3 +112,7 @@ The pivot state can be saved and restored as part of [Grid State](./grid-state/)
Pivoting can be controlled using the following grid API methods:
{% apiDocumentation source="grid-api/api.json" section="rowPivoting" /%}
+
+## See Also
+
+- [Editing Groups](./grouping-edit/) for editing pivot aggregated values with cascading updates to children
diff --git a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-async/_examples/rich-select-async-values/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-async/_examples/rich-select-async-values/example.spec.ts
index ed79767b830..b6cad2cb745 100644
--- a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-async/_examples/rich-select-async-values/example.spec.ts
+++ b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-async/_examples/rich-select-async-values/example.spec.ts
@@ -1,11 +1,57 @@
-import { clickAllButtons, ensureGridReady, test, waitForGridContent } from '@utils/grid/test-utils';
+import { expect, test } from '@utils/grid/test-utils';
+
+const languages = ['English', 'Spanish', 'French', 'Portuguese', '(other)'];
test.agExample(import.meta, () => {
- test.eachFramework('Example', async ({ page }) => {
- // PLACEHOLDER - MINIMAL TEST TO ENSURE GRID LOADS WITHOUT ERRORS
- await ensureGridReady(page);
- await waitForGridContent(page);
- await clickAllButtons(page);
- // END PLACEHOLDER
+ test.eachFramework('should load async values and display all options in dropdown', async ({ agIdFor, page }) => {
+ // Verify the first cell shows a valid language value
+ const cell = agIdFor.cell('0', 'language');
+ const cellText = await cell.textContent();
+ expect(languages).toContain(cellText);
+
+ // Double-click to open the rich select editor
+ await cell.dblclick();
+
+ // Wait for the popup to appear
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible();
+
+ // Wait for async values to load (1s server delay)
+ const options = popup.locator('.ag-rich-select-row');
+ await expect(options).toHaveCount(5, { timeout: 5000 });
+
+ // Verify all language options are present
+ for (const lang of languages) {
+ await expect(popup.locator('.ag-rich-select-row', { hasText: lang }).first()).toBeVisible();
+ }
+
+ await page.keyboard.press('Escape');
});
+
+ test.eachFramework(
+ 'should update cell value when selecting an option from the async dropdown',
+ async ({ agIdFor, page }) => {
+ const cell = agIdFor.cell('0', 'language');
+ const originalValue = await cell.textContent();
+
+ // Pick a different language
+ const newLanguage = languages.find((lang) => lang !== originalValue)!;
+
+ // Open the editor
+ await cell.dblclick();
+
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible();
+
+ // Wait for async options to load
+ await expect(popup.locator('.ag-rich-select-row')).toHaveCount(5, { timeout: 5000 });
+
+ // Select the new language
+ const option = popup.locator('.ag-rich-select-row', { hasText: newLanguage }).first();
+ await option.click();
+
+ // Verify the cell value updated
+ await expect(cell).toHaveText(newLanguage);
+ }
+ );
});
diff --git a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-async/_examples/rich-select-full-async-values/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-async/_examples/rich-select-full-async-values/example.spec.ts
index ed79767b830..7ca3db2f859 100644
--- a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-async/_examples/rich-select-full-async-values/example.spec.ts
+++ b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-async/_examples/rich-select-full-async-values/example.spec.ts
@@ -1,11 +1,70 @@
-import { clickAllButtons, ensureGridReady, test, waitForGridContent } from '@utils/grid/test-utils';
+import { expect, test } from '@utils/grid/test-utils';
+
+const languages = ['English', 'Spanish', 'French', 'Portuguese', '(other)'];
test.agExample(import.meta, () => {
- test.eachFramework('Example', async ({ page }) => {
- // PLACEHOLDER - MINIMAL TEST TO ENSURE GRID LOADS WITHOUT ERRORS
- await ensureGridReady(page);
- await waitForGridContent(page);
- await clickAllButtons(page);
- // END PLACEHOLDER
+ test.eachFramework('should open editor with a visible typing input', async ({ agIdFor, page }) => {
+ // Verify the first cell shows a valid language value
+ const cell = agIdFor.cell('0', 'language');
+ const cellText = await cell.textContent();
+ expect(languages).toContain(cellText);
+
+ // Double-click to open the rich select editor
+ await cell.dblclick();
+
+ // With filterListAsync + allowTyping, the text input is visible immediately
+ const editorInput = page.locator('.ag-rich-select-field-input .ag-input-field-input').first();
+ await expect(editorInput).toBeVisible();
+
+ await page.keyboard.press('Escape');
});
+
+ test.eachFramework('should show filtered async results after typing a search term', async ({ agIdFor, page }) => {
+ const cell = agIdFor.cell('0', 'language');
+
+ // Open editor
+ await cell.dblclick();
+
+ const editorInput = page.locator('.ag-rich-select-field-input .ag-input-field-input').first();
+ await expect(editorInput).toBeVisible();
+
+ // Fill with 'Sp' — only 'Spanish' matches the server-side filter
+ await editorInput.fill('Sp');
+
+ // Popup should appear once the async response resolves (debounce 300ms + server 1000ms)
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible({ timeout: 5000 });
+
+ // Only Spanish matches 'Sp'
+ await expect(popup.locator('.ag-rich-select-row')).toHaveCount(1, { timeout: 5000 });
+ await expect(popup.locator('.ag-rich-select-row', { hasText: 'Spanish' }).first()).toBeVisible();
+
+ await page.keyboard.press('Escape');
+ });
+
+ test.eachFramework(
+ 'should update cell value when selecting from the async-filtered list',
+ async ({ agIdFor, page }) => {
+ const cell = agIdFor.cell('0', 'language');
+
+ // Open editor
+ await cell.dblclick();
+
+ const editorInput = page.locator('.ag-rich-select-field-input .ag-input-field-input').first();
+ await expect(editorInput).toBeVisible();
+
+ // Filter to 'English' specifically (exact match, returns only English)
+ await editorInput.fill('English');
+
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible({ timeout: 5000 });
+
+ // Click the English option
+ const option = popup.locator('.ag-rich-select-row', { hasText: 'English' }).first();
+ await option.click();
+
+ // Verify the cell updated
+ await expect(cell).toHaveText('English');
+ }
+ );
});
diff --git a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-async/_examples/rich-select-paged-async-filtering/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-async/_examples/rich-select-paged-async-filtering/example.spec.ts
index ed79767b830..2b5534a2edc 100644
--- a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-async/_examples/rich-select-paged-async-filtering/example.spec.ts
+++ b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-async/_examples/rich-select-paged-async-filtering/example.spec.ts
@@ -1,11 +1,71 @@
-import { clickAllButtons, ensureGridReady, test, waitForGridContent } from '@utils/grid/test-utils';
+import { expect, test } from '@utils/grid/test-utils';
test.agExample(import.meta, () => {
- test.eachFramework('Example', async ({ page }) => {
- // PLACEHOLDER - MINIMAL TEST TO ENSURE GRID LOADS WITHOUT ERRORS
- await ensureGridReady(page);
- await waitForGridContent(page);
- await clickAllButtons(page);
- // END PLACEHOLDER
+ test.eachFramework('should open editor with a visible typing input', async ({ agIdFor, page }) => {
+ // Verify the first cell shows a language in the expected format
+ const cell = agIdFor.cell('0', 'language');
+ const cellText = await cell.textContent();
+ expect(cellText).toMatch(/^Language \d+$/);
+
+ // Double-click to open the rich select editor
+ await cell.dblclick();
+
+ // With filterListAsync + allowTyping, the text input is visible immediately
+ const editorInput = page.locator('.ag-rich-select-field-input .ag-input-field-input').first();
+ await expect(editorInput).toBeVisible();
+
+ await page.keyboard.press('Escape');
+ });
+
+ test.eachFramework('should show filtered paged results after typing a search term', async ({ agIdFor, page }) => {
+ const cell = agIdFor.cell('0', 'language');
+
+ // Open editor
+ await cell.dblclick();
+
+ const editorInput = page.locator('.ag-rich-select-field-input .ag-input-field-input').first();
+ await expect(editorInput).toBeVisible();
+
+ // Type '5000' — matches 'Language 5000' and 'Language 15000' in the 20,000-item dataset
+ await editorInput.fill('5000');
+
+ // Popup appears once the server responds (debounce 300ms + server 300ms)
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible({ timeout: 5000 });
+
+ // 'Language 5000' must be among the filtered results
+ await expect(popup.locator('.ag-rich-select-row', { hasText: 'Language 5000' }).first()).toBeVisible({
+ timeout: 5000,
+ });
+
+ await page.keyboard.press('Escape');
});
+
+ test.eachFramework(
+ 'should update cell value when selecting from filtered paged results',
+ async ({ agIdFor, page }) => {
+ const cell = agIdFor.cell('0', 'language');
+
+ // Open editor
+ await cell.dblclick();
+
+ const editorInput = page.locator('.ag-rich-select-field-input .ag-input-field-input').first();
+ await expect(editorInput).toBeVisible();
+
+ // Fill with exact text 'Language 5000' — this is the only 20,000-item match
+ // ('Language 15000' does not contain 'Language 5000' as a substring)
+ await editorInput.fill('Language 5000');
+
+ // Wait for filtered results
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible({ timeout: 5000 });
+
+ const option = popup.locator('.ag-rich-select-row', { hasText: 'Language 5000' }).first();
+ await expect(option).toBeVisible({ timeout: 5000 });
+ await option.click();
+
+ // Verify the cell value updated
+ await expect(cell).toHaveText('Language 5000');
+ }
+ );
});
diff --git a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-async/_examples/rich-select-paged-async-values/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-async/_examples/rich-select-paged-async-values/example.spec.ts
index ed79767b830..615930de57e 100644
--- a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-async/_examples/rich-select-paged-async-values/example.spec.ts
+++ b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-async/_examples/rich-select-paged-async-values/example.spec.ts
@@ -1,11 +1,94 @@
-import { clickAllButtons, ensureGridReady, test, waitForGridContent } from '@utils/grid/test-utils';
+import { expect, test } from '@utils/grid/test-utils';
test.agExample(import.meta, () => {
- test.eachFramework('Example', async ({ page }) => {
- // PLACEHOLDER - MINIMAL TEST TO ENSURE GRID LOADS WITHOUT ERRORS
- await ensureGridReady(page);
- await waitForGridContent(page);
- await clickAllButtons(page);
- // END PLACEHOLDER
- });
+ test.eachFramework(
+ 'should load first page of values and display languages in expected format',
+ async ({ agIdFor, page }) => {
+ // Verify the first cell shows a value in the expected format
+ const cell = agIdFor.cell('0', 'language');
+ const cellText = await cell.textContent();
+ expect(cellText).toMatch(/^Language \d+$/);
+
+ // Double-click to open the rich select editor
+ await cell.dblclick();
+
+ // Wait for the popup to appear
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible();
+
+ // Wait for the first page to load (300ms server delay)
+ const firstOption = popup.locator('.ag-rich-select-row').first();
+ await expect(firstOption).toBeVisible({ timeout: 5000 });
+
+ // Verify options follow the expected pattern
+ const firstOptionText = await firstOption.textContent();
+ expect(firstOptionText).toMatch(/^Language \d+$/);
+
+ await page.keyboard.press('Escape');
+ }
+ );
+
+ test.eachFramework(
+ 'should load the next page of values when scrolling to the bottom of the dropdown',
+ async ({ agIdFor, page }) => {
+ const cell = agIdFor.cell('0', 'language');
+ await cell.dblclick();
+
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible();
+
+ // Scroll to the top so we start from a known position and wait for top items to load
+ await popup.evaluate((el) => {
+ el.scrollTop = 0;
+ });
+ await expect(popup.locator('.ag-rich-select-row').first()).toBeVisible({ timeout: 5000 });
+
+ // Record the language number of the last visible row at the top of the list
+ const lastVisibleRowBefore = popup.locator('.ag-rich-select-row').last();
+ const textBefore = (await lastVisibleRowBefore.textContent()) ?? '';
+ const numberBefore = parseInt(/Language (\d+)/.exec(textBefore)?.[1] ?? '0');
+ expect(numberBefore).toBeGreaterThan(0);
+
+ // Scroll to the bottom of the virtual list (represents all 20,000 items) to trigger
+ // next-page loading — the load threshold is 8 rows from the end of loaded data
+ await popup.evaluate((el) => {
+ el.scrollTop = el.scrollHeight;
+ });
+
+ // After scrolling, new pages are fetched from the server (300ms delay).
+ // The last visible row should now show a higher-numbered language than before.
+ await expect(async () => {
+ const lastVisibleRowAfter = popup.locator('.ag-rich-select-row').last();
+ const textAfter = (await lastVisibleRowAfter.textContent()) ?? '';
+ const numberAfter = parseInt(/Language (\d+)/.exec(textAfter)?.[1] ?? '0');
+ expect(numberAfter).toBeGreaterThan(numberBefore);
+ }).toPass({ timeout: 5000 });
+
+ await page.keyboard.press('Escape');
+ }
+ );
+
+ test.eachFramework(
+ 'should update cell value when selecting an option from the paged dropdown',
+ async ({ agIdFor, page }) => {
+ const cell = agIdFor.cell('0', 'language');
+
+ // Open editor
+ await cell.dblclick();
+
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible();
+
+ // Wait for first page to load
+ const firstOption = popup.locator('.ag-rich-select-row').first();
+ await expect(firstOption).toBeVisible({ timeout: 5000 });
+
+ // Read the text of the first visible option (initial page position depends on current cell value)
+ const optionText = (await firstOption.textContent())!.trim();
+
+ // Click it and verify the cell updates to that value
+ await firstOption.click();
+ await expect(cell).toContainText(optionText);
+ }
+ );
});
diff --git a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-allow-typing/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-allow-typing/example.spec.ts
index ed79767b830..6ca062bf533 100644
--- a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-allow-typing/example.spec.ts
+++ b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-allow-typing/example.spec.ts
@@ -1,11 +1,71 @@
-import { clickAllButtons, ensureGridReady, test, waitForGridContent } from '@utils/grid/test-utils';
+import { expect, test } from '@utils/grid/test-utils';
test.agExample(import.meta, () => {
- test.eachFramework('Example', async ({ page }) => {
- // PLACEHOLDER - MINIMAL TEST TO ENSURE GRID LOADS WITHOUT ERRORS
- await ensureGridReady(page);
- await waitForGridContent(page);
- await clickAllButtons(page);
- // END PLACEHOLDER
+ test.eachFramework(
+ 'should display both column headers and open editor with typing input for Match column',
+ async ({ agIdFor, page }) => {
+ // Verify both column headers are visible
+ const matchHeader = page.locator('.ag-header-cell', { hasText: 'Allow Typing (Match)' });
+ await expect(matchHeader.first()).toBeVisible();
+
+ const matchAnyHeader = page.locator('.ag-header-cell', { hasText: 'Allow Typing (MatchAny)' });
+ await expect(matchAnyHeader.first()).toBeVisible();
+
+ // Double-click the first cell in the Match column to open the editor
+ const cell = agIdFor.cell('0', 'color').first();
+ await cell.dblclick();
+
+ // Verify the rich select popup list appears
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible();
+
+ // Verify the text input is visible inside the editor (proves allowTyping: true)
+ const editorInput = page.locator('.ag-rich-select-field-input .ag-input-field-input').first();
+ await expect(editorInput).toBeVisible();
+
+ // Close the editor
+ await page.keyboard.press('Escape');
+ }
+ );
+
+ test.eachFramework('should filter list differently based on search type when typing', async ({ agIdFor, page }) => {
+ // --- Test Match column (prefix search) ---
+
+ const matchCell = agIdFor.cell('0', 'color').first();
+ await matchCell.dblclick();
+
+ const matchPopup = page.locator('.ag-rich-select-list').first();
+ await expect(matchPopup).toBeVisible();
+
+ // Type 'Blue' to filter the list by prefix
+ const matchEditorInput = page.locator('.ag-rich-select-field-input .ag-input-field-input').first();
+ await matchEditorInput.fill('Blue');
+
+ // 'Blue' should appear in the filtered list (starts with 'Blue')
+ await expect(matchPopup.locator('.ag-rich-select-row', { hasText: 'Blue' }).first()).toBeVisible();
+
+ // 'AliceBlue' should NOT appear because it does not start with 'Blue' (prefix search)
+ await expect(matchPopup.locator('.ag-rich-select-row', { hasText: 'AliceBlue' })).toHaveCount(0);
+
+ // Close the editor
+ await page.keyboard.press('Escape');
+
+ // --- Test MatchAny column (substring search) ---
+
+ const matchAnyCell = agIdFor.cell('0', 'color_1').first();
+ await matchAnyCell.dblclick();
+
+ const matchAnyPopup = page.locator('.ag-rich-select-list').first();
+ await expect(matchAnyPopup).toBeVisible();
+
+ // Type 'Blue' to filter the list by substring
+ const matchAnyEditorInput = page.locator('.ag-rich-select-field-input .ag-input-field-input').first();
+ await matchAnyEditorInput.fill('Blue');
+
+ // 'AliceBlue' should appear because it contains 'Blue' (substring search)
+ await expect(matchAnyPopup.locator('.ag-rich-select-row', { hasText: 'AliceBlue' }).first()).toBeVisible();
+
+ // Close the editor
+ await page.keyboard.press('Escape');
});
});
diff --git a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-cell-renderer/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-cell-renderer/example.spec.ts
index ed79767b830..efe20ed6d67 100644
--- a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-cell-renderer/example.spec.ts
+++ b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-cell-renderer/example.spec.ts
@@ -1,11 +1,49 @@
-import { clickAllButtons, ensureGridReady, test, waitForGridContent } from '@utils/grid/test-utils';
+import { expect, test } from '@utils/grid/test-utils';
test.agExample(import.meta, () => {
- test.eachFramework('Example', async ({ page }) => {
- // PLACEHOLDER - MINIMAL TEST TO ENSURE GRID LOADS WITHOUT ERRORS
- await ensureGridReady(page);
- await waitForGridContent(page);
- await clickAllButtons(page);
- // END PLACEHOLDER
+ test.eachFramework(
+ 'should display header, valid cell content, and open editor with options',
+ async ({ agIdFor, page }) => {
+ // Verify the column header is visible with the expected text
+ const header = agIdFor.headerCell('color');
+ await expect(header).toContainText('Rich Select Editor');
+
+ // Verify the first cell displays a non-empty colour name
+ const cell = agIdFor.cell('0', 'color').first();
+ const cellText = await cell.textContent();
+ expect(cellText?.trim().length).toBeGreaterThan(0);
+
+ // Double-click the cell to open the rich select editor
+ await cell.dblclick();
+
+ // Verify the rich select popup list appears
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible();
+
+ // Verify the popup contains colour options
+ const options = popup.locator('.ag-rich-select-row');
+ await expect(options.first()).toBeVisible();
+ }
+ );
+
+ test.eachFramework('should update cell value when selecting a different option', async ({ agIdFor, page }) => {
+ const cell = agIdFor.cell('0', 'color').first();
+
+ // Double-click to open the rich select editor
+ await cell.dblclick();
+
+ // Wait for the popup to appear
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible();
+
+ // Click the first visible row in the popup (the list opens scrolled to the current
+ // selected value, so we click whatever row is at the top of the viewport)
+ const firstRow = popup.locator('.ag-rich-select-row').first();
+ await expect(firstRow).toBeVisible();
+ const optionText = (await firstRow.innerText()).trim();
+ await firstRow.click();
+
+ // Verify the cell value has been updated to the selected colour
+ await expect(cell).toContainText(optionText);
});
});
diff --git a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-complex-objects/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-complex-objects/example.spec.ts
index ed79767b830..03b86bacbbd 100644
--- a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-complex-objects/example.spec.ts
+++ b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-complex-objects/example.spec.ts
@@ -1,11 +1,83 @@
-import { clickAllButtons, ensureGridReady, test, waitForGridContent } from '@utils/grid/test-utils';
+import { expect, test } from '@utils/grid/test-utils';
test.agExample(import.meta, () => {
- test.eachFramework('Example', async ({ page }) => {
- // PLACEHOLDER - MINIMAL TEST TO ENSURE GRID LOADS WITHOUT ERRORS
- await ensureGridReady(page);
- await waitForGridContent(page);
- await clickAllButtons(page);
- // END PLACEHOLDER
+ test.eachFramework(
+ 'should display correct initial cell values for both string and complex object columns',
+ async ({ agIdFor }) => {
+ // Verify the column headers are visible with the expected text
+ const stringHeader = agIdFor.headerCell('color');
+ await expect(stringHeader).toContainText('Color (Column as String Type)');
+
+ const objectHeader = agIdFor.headerCell('detailedColor');
+ await expect(objectHeader).toContainText('Color (Column as Complex Object)');
+
+ // Verify row 0 string column shows the plain name
+ const row0Color = agIdFor.cell('0', 'color');
+ await expect(row0Color).toContainText('Pink');
+
+ // Verify row 0 complex object column shows the formatted value
+ const row0DetailedColor = agIdFor.cell('0', 'detailedColor');
+ await expect(row0DetailedColor).toContainText('Pink (#FFC0CB)');
+
+ // Verify row 2 string column shows the plain name
+ const row2Color = agIdFor.cell('2', 'color');
+ await expect(row2Color).toContainText('Blue');
+
+ // Verify row 2 complex object column shows the formatted value
+ const row2DetailedColor = agIdFor.cell('2', 'detailedColor');
+ await expect(row2DetailedColor).toContainText('Blue (#0000FF)');
+ }
+ );
+
+ test.eachFramework('should update cell value when selecting a different option', async ({ agIdFor, page }) => {
+ // --- Edit the string column (color) ---
+
+ const colorCell = agIdFor.cell('0', 'color');
+
+ // Double-click to open the rich select editor
+ await colorCell.dblclick();
+
+ // Verify the rich select popup list appears
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible();
+
+ // Verify all 4 colour options are displayed
+ const options = popup.locator('.ag-rich-select-row');
+ await expect(options).toHaveCount(4);
+
+ // Verify the option labels show the formatted name values
+ await expect(options.nth(0)).toContainText('Pink');
+ await expect(options.nth(1)).toContainText('Purple');
+ await expect(options.nth(2)).toContainText('Blue');
+ await expect(options.nth(3)).toContainText('Green');
+
+ // Click the "Green" option
+ const greenOption = popup.locator('.ag-rich-select-row', { hasText: 'Green' }).first();
+ await greenOption.click();
+
+ // Verify the cell value has been updated via parseValue (extracts just the name string)
+ await expect(colorCell).toContainText('Green');
+
+ // --- Edit the complex object column (detailedColor) ---
+
+ const detailedColorCell = agIdFor.cell('1', 'detailedColor');
+
+ // Double-click to open the rich select editor
+ await detailedColorCell.dblclick();
+
+ // Verify the rich select popup list appears
+ const objectPopup = page.locator('.ag-rich-select-list').first();
+ await expect(objectPopup).toBeVisible();
+
+ // Verify all 4 colour options are displayed
+ const objectOptions = objectPopup.locator('.ag-rich-select-row');
+ await expect(objectOptions).toHaveCount(4);
+
+ // Click the "Blue" option
+ const blueOption = objectPopup.locator('.ag-rich-select-row', { hasText: 'Blue' }).first();
+ await blueOption.click();
+
+ // Verify the cell value has been updated with the valueFormatter applied to the full complex object
+ await expect(detailedColorCell).toContainText('Blue (#0000FF)');
});
});
diff --git a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-format-values/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-format-values/example.spec.ts
index ed79767b830..bc5e20fc5c1 100644
--- a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-format-values/example.spec.ts
+++ b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-format-values/example.spec.ts
@@ -1,11 +1,56 @@
-import { clickAllButtons, ensureGridReady, test, waitForGridContent } from '@utils/grid/test-utils';
+import { expect, test } from '@utils/grid/test-utils';
+
+const languages = ['English', 'Spanish', 'French', 'Portuguese', '(other)'];
test.agExample(import.meta, () => {
- test.eachFramework('Example', async ({ page }) => {
- // PLACEHOLDER - MINIMAL TEST TO ENSURE GRID LOADS WITHOUT ERRORS
- await ensureGridReady(page);
- await waitForGridContent(page);
- await clickAllButtons(page);
- // END PLACEHOLDER
- });
+ test.eachFramework(
+ 'should display header, original-case cell values, and uppercase options in editor',
+ async ({ agIdFor, page }) => {
+ // Verify the column header is visible with the expected text
+ const header = agIdFor.headerCell('language');
+ await expect(header).toContainText('Rich Select Editor');
+
+ // Verify the first cell displays a valid language value in original case (not uppercased)
+ const cell = agIdFor.cell('0', 'language');
+ const cellText = await cell.textContent();
+ expect(languages).toContain(cellText);
+
+ // Double-click the cell to open the rich select editor
+ await cell.dblclick();
+
+ // Verify the rich select popup list appears
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible();
+
+ // Verify all 5 language options are displayed
+ const options = popup.locator('.ag-rich-select-row');
+ await expect(options).toHaveCount(5);
+
+ // Verify the formatValue callback displays items in uppercase
+ await expect(popup.locator('.ag-rich-select-row', { hasText: 'ENGLISH' }).first()).toBeVisible();
+ await expect(popup.locator('.ag-rich-select-row', { hasText: 'SPANISH' }).first()).toBeVisible();
+ await expect(popup.locator('.ag-rich-select-row', { hasText: '(OTHER)' }).first()).toBeVisible();
+ }
+ );
+
+ test.eachFramework(
+ 'should store original-case value when selecting a formatted uppercase option',
+ async ({ agIdFor, page }) => {
+ const cell = agIdFor.cell('0', 'language');
+
+ // Double-click to open the rich select editor
+ await cell.dblclick();
+
+ // Wait for the popup to appear
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible();
+
+ // Click the "FRENCH" option (displayed in uppercase by formatValue)
+ const option = popup.locator('.ag-rich-select-row', { hasText: 'FRENCH' }).first();
+ await option.click();
+
+ // Verify the cell stores and displays the original-case value, not the formatted uppercase
+ await expect(cell).toContainText('French');
+ }
+ );
});
diff --git a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-multi-select/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-multi-select/example.spec.ts
index ed79767b830..5ff7cd45ea5 100644
--- a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-multi-select/example.spec.ts
+++ b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-multi-select/example.spec.ts
@@ -1,11 +1,65 @@
-import { clickAllButtons, ensureGridReady, test, waitForGridContent } from '@utils/grid/test-utils';
+import { expect, test } from '@utils/grid/test-utils';
test.agExample(import.meta, () => {
- test.eachFramework('Example', async ({ page }) => {
- // PLACEHOLDER - MINIMAL TEST TO ENSURE GRID LOADS WITHOUT ERRORS
- await ensureGridReady(page);
- await waitForGridContent(page);
- await clickAllButtons(page);
- // END PLACEHOLDER
+ test.eachFramework(
+ 'should display header and keep multi-select editor open after clicking options',
+ async ({ agIdFor, page }) => {
+ // Verify the column header contains the expected text
+ const header = agIdFor.headerCell('colors');
+ await expect(header).toContainText('Colours');
+
+ // Double-click the first row cell to open the rich select editor
+ const cell = agIdFor.cell('0', 'colors');
+ await cell.dblclick();
+
+ // Verify the rich select popup list appears
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible();
+
+ // Click the first visible option in the popup (the list opens scrolled to the
+ // current selection, so we interact with whatever rows are in the viewport)
+ const firstOption = popup.locator('.ag-rich-select-row').first();
+ await expect(firstOption).toBeVisible();
+ await firstOption.click();
+
+ // Multi-select: popup should remain open after clicking an option
+ await expect(popup).toBeVisible();
+
+ // Click the second visible option
+ const secondOption = popup.locator('.ag-rich-select-row').nth(1);
+ await expect(secondOption).toBeVisible();
+ await secondOption.click();
+
+ // Multi-select: popup should still remain open after clicking another option
+ await expect(popup).toBeVisible();
+
+ // Press Escape to cancel editing without confirming changes
+ await page.keyboard.press('Escape');
+
+ // Verify the popup is no longer visible after pressing Escape
+ await expect(popup).not.toBeVisible();
+ }
+ );
+
+ test.eachFramework('should show selected state for options in multi-select editor', async ({ agIdFor, page }) => {
+ // Double-click the first row cell to open the rich select editor
+ const cell = agIdFor.cell('0', 'colors');
+ await cell.dblclick();
+
+ // Verify the rich select popup list appears
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible();
+
+ // Verify that at least one option has the selected class, reflecting
+ // the pre-selected colours from the initial random row data (1-4 colours per row)
+ const selectedRows = popup.locator('.ag-rich-select-row-selected');
+ const selectedCount = await selectedRows.count();
+ expect(selectedCount).toBeGreaterThanOrEqual(1);
+
+ // Press Escape to close the editor
+ await page.keyboard.press('Escape');
+
+ // Verify the popup is no longer visible
+ await expect(popup).not.toBeVisible();
});
});
diff --git a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-search-values/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-search-values/example.spec.ts
index ed79767b830..fe7735973e6 100644
--- a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-search-values/example.spec.ts
+++ b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select-customisation/_examples/rich-select-search-values/example.spec.ts
@@ -1,11 +1,37 @@
-import { clickAllButtons, ensureGridReady, test, waitForGridContent } from '@utils/grid/test-utils';
+import { expect, test } from '@utils/grid/test-utils';
test.agExample(import.meta, () => {
- test.eachFramework('Example', async ({ page }) => {
- // PLACEHOLDER - MINIMAL TEST TO ENSURE GRID LOADS WITHOUT ERRORS
- await ensureGridReady(page);
- await waitForGridContent(page);
- await clickAllButtons(page);
- // END PLACEHOLDER
+ test.eachFramework('should display all three search mode column headers', async ({ page }) => {
+ // Verify all three column headers are visible with the expected text
+ const fuzzyHeader = page.locator('.ag-header-cell', { hasText: 'Fuzzy Search' });
+ await expect(fuzzyHeader).toBeVisible();
+
+ const matchHeader = page.locator('.ag-header-cell', { hasText: 'Match Search' });
+ await expect(matchHeader).toBeVisible();
+
+ const matchAnyHeader = page.locator('.ag-header-cell', { hasText: 'Match Any Search' });
+ await expect(matchAnyHeader).toBeVisible();
+ });
+
+ test.eachFramework('should open the rich select editor popup with colour options', async ({ agIdFor, page }) => {
+ // Get the first cell in the Fuzzy Search column (col-id 'color')
+ const cell = agIdFor.cell('0', 'color').first();
+ await expect(cell).toBeVisible();
+
+ // Double-click the cell to open the rich select editor
+ await cell.dblclick();
+
+ // Verify the rich select popup list appears
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible();
+
+ // Verify the popup contains colour options
+ await expect(popup.locator('.ag-rich-select-row').first()).toBeVisible();
+
+ // Close the editor by pressing Escape
+ await page.keyboard.press('Escape');
+
+ // Verify the popup is no longer visible
+ await expect(popup).not.toBeVisible();
});
});
diff --git a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select/_examples/rich-select-editor/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select/_examples/rich-select-editor/example.spec.ts
index ed79767b830..10941f6cd48 100644
--- a/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select/_examples/rich-select-editor/example.spec.ts
+++ b/documentation/ag-grid-docs/src/content/docs/provided-cell-editors-rich-select/_examples/rich-select-editor/example.spec.ts
@@ -1,11 +1,54 @@
-import { clickAllButtons, ensureGridReady, test, waitForGridContent } from '@utils/grid/test-utils';
+import { expect, test } from '@utils/grid/test-utils';
+
+const languages = ['English', 'Spanish', 'French', 'Portuguese', '(other)'];
test.agExample(import.meta, () => {
- test.eachFramework('Example', async ({ page }) => {
- // PLACEHOLDER - MINIMAL TEST TO ENSURE GRID LOADS WITHOUT ERRORS
- await ensureGridReady(page);
- await waitForGridContent(page);
- await clickAllButtons(page);
- // END PLACEHOLDER
+ test.eachFramework(
+ 'should display header, valid cell content, and open editor with all options',
+ async ({ agIdFor, page }) => {
+ // Verify the column header is visible with the expected text
+ const header = agIdFor.headerCell('language');
+ await expect(header).toContainText('Rich Select Editor');
+
+ // Verify the first cell displays a valid language value
+ const cell = agIdFor.cell('0', 'language');
+ const cellText = await cell.textContent();
+ expect(languages).toContain(cellText);
+
+ // Double-click the cell to open the rich select editor
+ await cell.dblclick();
+
+ // Verify the rich select popup list appears
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible();
+
+ // Verify all 5 language options are displayed
+ const options = popup.locator('.ag-rich-select-row');
+ await expect(options).toHaveCount(5);
+ }
+ );
+
+ test.eachFramework('should update cell value when selecting a different option', async ({ agIdFor, page }) => {
+ const cell = agIdFor.cell('0', 'language');
+
+ // Read the current cell value
+ const originalValue = await cell.textContent();
+
+ // Pick a language that differs from the current value
+ const newLanguage = languages.find((lang) => lang !== originalValue)!;
+
+ // Double-click to open the rich select editor
+ await cell.dblclick();
+
+ // Wait for the popup to appear
+ const popup = page.locator('.ag-rich-select-list').first();
+ await expect(popup).toBeVisible();
+
+ // Click the option with the different language
+ const option = popup.locator('.ag-rich-select-row', { hasText: newLanguage }).first();
+ await option.click();
+
+ // Verify the cell value has been updated to the newly selected language
+ await expect(cell).toHaveText(newLanguage);
});
});
diff --git a/documentation/ag-grid-docs/src/content/docs/tree-data-nesting/index.mdoc b/documentation/ag-grid-docs/src/content/docs/tree-data-nesting/index.mdoc
index a82324ade10..15e8956f4f5 100644
--- a/documentation/ag-grid-docs/src/content/docs/tree-data-nesting/index.mdoc
+++ b/documentation/ag-grid-docs/src/content/docs/tree-data-nesting/index.mdoc
@@ -88,4 +88,5 @@ const gridOptions = {
};
```
-Refer to the [Aggregation](./aggregation/) page for more details.
+Refer to the [Aggregation](./aggregation/) page for more details, and [Editing Groups](./grouping-edit/)
+for editing aggregated values with cascading updates to children.
diff --git a/documentation/ag-grid-docs/src/content/docs/tree-data-paths/index.mdoc b/documentation/ag-grid-docs/src/content/docs/tree-data-paths/index.mdoc
index 60c65935ff3..71ad2d0e2f8 100644
--- a/documentation/ag-grid-docs/src/content/docs/tree-data-paths/index.mdoc
+++ b/documentation/ag-grid-docs/src/content/docs/tree-data-paths/index.mdoc
@@ -112,4 +112,6 @@ const gridOptions = {
};
```
-Refer to the [Aggregation](./aggregation/) page for more details.
+Refer to the [Aggregation](./aggregation/) page for more details, and [Editing Groups](./grouping-edit/)
+for editing aggregated values with cascading updates to children.
+
diff --git a/documentation/ag-grid-docs/src/content/docs/tree-data-row-dragging/index.mdoc b/documentation/ag-grid-docs/src/content/docs/tree-data-row-dragging/index.mdoc
index 2d24dfbc2ec..55ebacbc018 100644
--- a/documentation/ag-grid-docs/src/content/docs/tree-data-row-dragging/index.mdoc
+++ b/documentation/ag-grid-docs/src/content/docs/tree-data-row-dragging/index.mdoc
@@ -177,3 +177,4 @@ This example also demonstrates how to provide custom drop indicators using the [
## See Also
- [Aggregation](./aggregation/) for aggregating values in tree data
+- [Editing Groups](./grouping-edit/) for editing aggregated values with cascading updates to children
diff --git a/documentation/ag-grid-docs/src/content/docs/tree-data-self-referential/index.mdoc b/documentation/ag-grid-docs/src/content/docs/tree-data-self-referential/index.mdoc
index b2c931bd384..1e7db01a67d 100644
--- a/documentation/ag-grid-docs/src/content/docs/tree-data-self-referential/index.mdoc
+++ b/documentation/ag-grid-docs/src/content/docs/tree-data-self-referential/index.mdoc
@@ -98,4 +98,6 @@ const gridOptions = {
};
```
-Refer to the [Aggregation](./aggregation/) page for more details.
+Refer to the [Aggregation](./aggregation/) page for more details, and [Editing Groups](./grouping-edit/)
+for editing aggregated values with cascading updates to children.
+
diff --git a/packages/ag-grid-community/knip.json b/packages/ag-grid-community/knip.json
index 1f30a750b0b..2d57cb5eff9 100644
--- a/packages/ag-grid-community/knip.json
+++ b/packages/ag-grid-community/knip.json
@@ -2,7 +2,6 @@
"entry": ["./src/main.ts"],
"project": ["./src/**"],
"ignore": [
- "./src/entities/colDefInternal.ts",
"./src/interfaces/iAgChartOptions.ts",
"./src/theming/createTheme.ts",
"./src/theming/private-theming-api.ts",
diff --git a/packages/ag-grid-community/src/edit/editService.ts b/packages/ag-grid-community/src/edit/editService.ts
index 5bbeef0df51..a53acef6669 100644
--- a/packages/ag-grid-community/src/edit/editService.ts
+++ b/packages/ag-grid-community/src/edit/editService.ts
@@ -2,7 +2,6 @@ import { KeyCode } from '../agStack/constants/keyCode';
import type { NamedBean } from '../context/bean';
import { BeanStub } from '../context/beanStub';
import type { AgColumn } from '../entities/agColumn';
-import type { ColDefInternal } from '../entities/colDefInternal';
import { _getRowNode } from '../entities/positionUtils';
import type { RowNode } from '../entities/rowNode';
import type { AgEventType } from '../eventTypes';
@@ -840,8 +839,7 @@ export class EditService extends BeanStub implements NamedBean {
const { gos, beans } = this;
const rowNode = position.rowNode;
- const colDef: ColDefInternal = position.column.getColDef();
- if (rowNode.group && colDef.groupRowEditable == null) {
+ if (rowNode.group && position.column.getColDef().groupRowEditable == null) {
// This is a group - it could be a tree group or a grouping group...
if (gos.get('treeData')) {
// tree - allow editing of groups with data by default.
diff --git a/packages/ag-grid-community/src/edit/strategy/strategyUtils.ts b/packages/ag-grid-community/src/edit/strategy/strategyUtils.ts
index 30485c21875..acfbe5585b1 100644
--- a/packages/ag-grid-community/src/edit/strategy/strategyUtils.ts
+++ b/packages/ag-grid-community/src/edit/strategy/strategyUtils.ts
@@ -2,7 +2,6 @@ import { KeyCode } from '../../agStack/constants/keyCode';
import type { BeanCollection } from '../../context/context';
import type { AgColumn } from '../../entities/agColumn';
import type { ColDef } from '../../entities/colDef';
-import type { ColDefInternal } from '../../entities/colDefInternal';
import type { GridOptionsService } from '../../gridOptionsService';
import type { EditPosition, EditSource } from '../../interfaces/iEditService';
@@ -74,7 +73,7 @@ function existingEditing(beans: BeanCollection, editPosition: Required): boolean {
const column = editPosition.column as AgColumn;
const rowNode = editPosition.rowNode;
- const colDef = column.getColDef() as ColDefInternal;
+ const colDef = column.getColDef();
if (!rowNode) {
return existingEditing(beans, editPosition);
diff --git a/packages/ag-grid-community/src/edit/utils/editors.ts b/packages/ag-grid-community/src/edit/utils/editors.ts
index 2a732a41d48..21a63db8336 100644
--- a/packages/ag-grid-community/src/edit/utils/editors.ts
+++ b/packages/ag-grid-community/src/edit/utils/editors.ts
@@ -5,7 +5,6 @@ import { _getCellEditorDetails } from '../../components/framework/userCompUtils'
import type { BeanCollection } from '../../context/context';
import type { AgColumn } from '../../entities/agColumn';
import type { ColDef } from '../../entities/colDef';
-import type { ColDefInternal } from '../../entities/colDefInternal';
import type { CellEditingStoppedEvent } from '../../events';
import { _addGridCommonParams } from '../../gridOptionsUtils';
import type {
@@ -616,7 +615,7 @@ function _columnDefsRequireValidation(columnDefs?: ColDef[]): boolean {
return false;
}
for (let i = 0, len = columnDefs.length; i < len; ++i) {
- const colDef: ColDefInternal = columnDefs[i];
+ const colDef = columnDefs[i];
const params = colDef.cellEditorParams;
if (!params || (!colDef.editable && !colDef.groupRowEditable)) {
continue;
diff --git a/packages/ag-grid-community/src/entities/colDef.ts b/packages/ag-grid-community/src/entities/colDef.ts
index 4f525ed4954..3d1117519cc 100644
--- a/packages/ag-grid-community/src/entities/colDef.ts
+++ b/packages/ag-grid-community/src/entities/colDef.ts
@@ -393,9 +393,21 @@ export interface ColDef extends AbstractColDef;
+ /**
+ * Works like `editable`, but is evaluated only for group rows. When provided, group rows use this property instead of `editable`.
+ * Set to `true` if this column is editable, otherwise `false`. Can also be a function to have different rows editable.
+ */
+ groupRowEditable?: boolean | GroupRowEditableCallback;
+ /**
+ * Runs after a group row value changes so custom code can push edits down to descendant rows.
+ * Fires for every `setDataValue` call when defined, regardless of `groupRowEditable`.
+ * Use this to mutate descendants directly; the grid always commits the group row value afterwards.
+ */
+ groupRowValueSetter?: GroupRowValueSetterFunc;
/**
* Function or expression. Sets the value into your data for saving. Return `true` if the data changed.
*/
@@ -965,6 +977,39 @@ export interface EditableCallbackParams = (
params: EditableCallbackParams
) => boolean;
+export interface GroupRowEditableCallbackParams
+ extends ColumnFunctionCallbackParams {}
+export type GroupRowEditableCallback = (
+ params: GroupRowEditableCallbackParams
+) => boolean;
+export interface GroupRowValueSetterParams
+ extends Omit<
+ ChangedValueParams,
+ 'node' | 'data'
+ > {
+ /** Group row that triggered the callback. */
+ node: IRowNode;
+ /** Data associated with the group row. Undefined when the row does not own data. */
+ data?: TData | null;
+ /** Source string provided to `rowNode.setDataValue`. */
+ eventSource: string | undefined;
+ /** Whether the value actually changed. */
+ valueChanged: boolean;
+ /**
+ * The immediate children that contribute to the aggregation.
+ *
+ * - For leaf groups (groups containing data rows): returns the data rows.
+ * With pivot columns, only rows matching the pivot keys are included.
+ * - For non-leaf groups (groups containing other groups): returns the child groups.
+ * Use `setDataValue` on child groups to cascade recursively.
+ *
+ * **Note:** Only supported with the Client-Side Row Model.
+ */
+ aggregatedChildren: IRowNode[];
+}
+export type GroupRowValueSetterFunc = (
+ params: GroupRowValueSetterParams
+) => void | boolean | undefined;
export interface SuppressPasteCallbackParams
extends ColumnFunctionCallbackParams {}
export type SuppressPasteCallback = (
diff --git a/packages/ag-grid-community/src/entities/colDefInternal.ts b/packages/ag-grid-community/src/entities/colDefInternal.ts
deleted file mode 100644
index e7391aa80d4..00000000000
--- a/packages/ag-grid-community/src/entities/colDefInternal.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-/**
- * Internal column definition properties for features that are not yet public.
- * These types are used internally but not exported in the public API.
- * @internal
- */
-import type { GridApi } from '../api/gridApi';
-import type { Column } from '../interfaces/iColumn';
-import type { IRowNode } from '../interfaces/iRowNode';
-import type { ColDef, ColumnFunctionCallbackParams } from './colDef';
-
-/** @internal */
-export interface GroupRowEditableCallbackParams
- extends ColumnFunctionCallbackParams {}
-
-/** @internal */
-export type GroupRowEditableCallback = (
- params: GroupRowEditableCallbackParams
-) => boolean;
-
-/** @internal */
-export interface GroupRowValueSetterParams {
- /** The value before the change. */
- oldValue: TValue | null | undefined;
- /** The value after the change. */
- newValue: TValue | null | undefined;
- /** Group row that triggered the callback. */
- node: IRowNode;
- /** Data associated with the group row. Undefined when the row does not own data. */
- data?: TData | null;
- /** The column being edited. */
- column: Column;
- /** The column definition being edited. */
- colDef: ColDef;
- /** The grid api. */
- api: GridApi;
- /** Application context as set on `gridOptions.context`. */
- context: TContext;
- /** Source string provided to `rowNode.setDataValue`. */
- eventSource: string | undefined;
- /** Whether the value actually changed. */
- valueChanged: boolean;
- /**
- * The immediate children that contribute to the aggregation.
- *
- * - For leaf groups (groups containing data rows): returns the data rows.
- * With pivot columns, only rows matching the pivot keys are included.
- * - For non-leaf groups (groups containing other groups): returns the child groups.
- * Use `setDataValue` on child groups to cascade recursively.
- *
- * **Note:** Only supported with the Client-Side Row Model.
- */
- aggregatedChildren: IRowNode[];
-}
-
-/** @internal */
-export type GroupRowValueSetterFunc = (
- params: GroupRowValueSetterParams
-) => void | boolean | undefined;
-
-/**
- * Internal extension to ColDef for features not yet public.
- * Use this type internally when accessing internal properties.
- * @internal
- */
-export interface ColDefInternal extends ColDef {
- /**
- * Works like `editable`, but is evaluated only for group rows. When provided, group rows use this property instead of `editable`.
- * Set to `true` if this column is editable, otherwise `false`. Can also be a function to have different rows editable.
- * @internal
- */
- groupRowEditable?: boolean | GroupRowEditableCallback;
- /**
- * Runs after a group row value changes so custom code can push edits down to descendant rows.
- * Fires for every `setDataValue` call when defined, regardless of `groupRowEditable`.
- * Use this to mutate descendants directly; the grid always commits the group row value afterwards.
- * @internal
- */
- groupRowValueSetter?: GroupRowValueSetterFunc;
-}
diff --git a/packages/ag-grid-community/src/main.ts b/packages/ag-grid-community/src/main.ts
index 7c40b1e33f2..f169f7e5e1a 100644
--- a/packages/ag-grid-community/src/main.ts
+++ b/packages/ag-grid-community/src/main.ts
@@ -711,6 +711,10 @@ export {
ValueParserParams,
ValueSetterFunc,
ValueSetterParams,
+ GroupRowEditableCallback,
+ GroupRowEditableCallbackParams,
+ GroupRowValueSetterParams,
+ GroupRowValueSetterFunc,
} from './entities/colDef';
export {
BaseCellDataType,
diff --git a/packages/ag-grid-community/src/validation/rules/colDefValidations.ts b/packages/ag-grid-community/src/validation/rules/colDefValidations.ts
index 7fd9c877b96..9176daa8d4c 100644
--- a/packages/ag-grid-community/src/validation/rules/colDefValidations.ts
+++ b/packages/ag-grid-community/src/validation/rules/colDefValidations.ts
@@ -1,7 +1,6 @@
import type { UserComponentName } from '../../context/context';
import { _isSortDefValid, _isSortDirectionValid } from '../../entities/agColumn';
import type { AbstractColDef, ColDef, ColGroupDef, ColumnMenuTab } from '../../entities/colDef';
-import type { ColDefInternal } from '../../entities/colDefInternal';
import { _errMsg, toStringWithNullUndefined } from '../logging';
import type { Deprecations, ModuleValidation, OptionsValidator, Validations } from '../validationTypes';
import { USER_COMP_MODULES } from './userCompValidations';
@@ -40,8 +39,7 @@ export const COLUMN_DEFINITION_MOD_VALIDATIONS: ModuleValidation {
- const groupRowEditable = (rest as ColDefInternal).groupRowEditable;
+ cellEditor: ({ cellEditor, editable, groupRowEditable }: ColDef) => {
const editingEnabled = !!editable || !!groupRowEditable;
if (!editingEnabled) {
return null;
@@ -68,6 +66,12 @@ export const COLUMN_DEFINITION_MOD_VALIDATIONS: ModuleValidation {
+ if (groupRowEditable && !cellEditor) {
+ return 'TextEditor';
+ }
+ return null;
+ },
enableCellChangeFlash: 'HighlightChanges',
enablePivot: 'SharedPivot',
enableRowGroup: 'SharedRowGrouping',
@@ -256,6 +260,7 @@ const COLUMN_DEFINITION_VALIDATIONS: () => Validations = (
spanRows: {
dependencies: {
editable: { required: [false, undefined] },
+ groupRowEditable: { required: [false, undefined] },
rowDrag: { required: [false, undefined] },
colSpan: { required: [undefined] },
rowSpan: { required: [undefined] },
@@ -334,7 +339,7 @@ const COLUMN_DEFINITION_VALIDATIONS: () => Validations = (
return validations;
};
-type ColOrGroupKey = keyof ColDef | keyof ColGroupDef | keyof Partial;
+type ColOrGroupKey = keyof ColDef | keyof ColGroupDef;
const colDefPropertyMap: Record = {
headerName: undefined,
columnGroupShow: undefined,
@@ -360,7 +365,6 @@ const colDefPropertyMap: Record = {
initialAggFunc: undefined,
defaultAggFunc: undefined,
aggFunc: undefined,
- // Internal properties (not public API but still accepted)
groupRowEditable: undefined,
groupRowValueSetter: undefined,
pinned: undefined,
diff --git a/packages/ag-grid-community/src/valueService/valueService.ts b/packages/ag-grid-community/src/valueService/valueService.ts
index a7455296361..bd8301ea23b 100644
--- a/packages/ag-grid-community/src/valueService/valueService.ts
+++ b/packages/ag-grid-community/src/valueService/valueService.ts
@@ -9,13 +9,13 @@ import type { BeanCollection } from '../context/context';
import type { EditService } from '../edit/editService';
import type { AgColumn } from '../entities/agColumn';
import type {
+ ColDef,
KeyCreatorParams,
ValueFormatterParams,
ValueGetterParams,
ValueParserParams,
ValueSetterParams,
} from '../entities/colDef';
-import type { ColDefInternal } from '../entities/colDefInternal';
import type { RowNode } from '../entities/rowNode';
import type { CellValueChangedEvent } from '../events';
import { _addGridCommonParams, _isServerSideRowModel } from '../gridOptionsUtils';
@@ -418,7 +418,7 @@ export class ValueService extends BeanStub implements NamedBean {
* @returns `true` if the value has been updated, otherwise `false`.
*/
public setValue(rowNode: IRowNode, column: AgColumn, newValue: any, eventSource?: string): boolean {
- const colDef = column.getColDef() as ColDefInternal;
+ const colDef = column.getColDef();
if (!rowNode.data && this.canCreateRowNodeData(rowNode, colDef)) {
rowNode.data = {}; // enableGroupEdit allows editing group rows without data.
@@ -500,7 +500,7 @@ export class ValueService extends BeanStub implements NamedBean {
return this.finishValueChange(rowNode, column, params, eventSource);
}
- private canCreateRowNodeData(rowNode: IRowNode, colDef: ColDefInternal): boolean {
+ private canCreateRowNodeData(rowNode: IRowNode, colDef: ColDef): boolean {
if (!rowNode.group) {
return true; // not a group row
}
diff --git a/testing/behavioural/src/cell-editing/group-edit/group-edit-clipboard-paste.test.ts b/testing/behavioural/src/cell-editing/group-edit/group-edit-clipboard-paste.test.ts
index 21c81ad5ab4..4c07772bc72 100644
--- a/testing/behavioural/src/cell-editing/group-edit/group-edit-clipboard-paste.test.ts
+++ b/testing/behavioural/src/cell-editing/group-edit/group-edit-clipboard-paste.test.ts
@@ -13,7 +13,6 @@ import {
waitForEvent,
} from '../../test-utils';
import { expect } from '../../test-utils/matchers';
-import type { ColDefInternal } from './group-edit-test-utils';
describe('Group Edit: clipboard paste', () => {
const gridMgr = new TestGridsManager({
@@ -65,7 +64,7 @@ describe('Group Edit: clipboard paste', () => {
editable: true,
groupRowEditable: true,
valueSetter,
- } as ColDefInternal,
+ },
{ field: 'category', rowGroup: true, hide: true },
],
rowData: [
diff --git a/testing/behavioural/src/cell-editing/group-edit/group-edit-test-utils.ts b/testing/behavioural/src/cell-editing/group-edit/group-edit-test-utils.ts
index e90a6061a44..8a43441d7f0 100644
--- a/testing/behavioural/src/cell-editing/group-edit/group-edit-test-utils.ts
+++ b/testing/behavioural/src/cell-editing/group-edit/group-edit-test-utils.ts
@@ -4,12 +4,6 @@ import type { ColDef, GridApi, IRowNode } from 'ag-grid-community';
import { AllCommunityModule, ClientSideRowModelModule, UndoRedoEditModule } from 'ag-grid-community';
import { RowGroupingModule, SetFilterModule, TreeDataModule } from 'ag-grid-enterprise';
-import type {
- ColDefInternal,
- GroupRowEditableCallback as GroupRowEditableCallbackInternal,
- GroupRowValueSetterFunc,
- GroupRowValueSetterParams,
-} from '../../../../../packages/ag-grid-community/src/entities/colDefInternal';
import { TestGridsManager, asyncSetTimeout, waitForInput } from '../../test-utils';
import { expect } from '../../test-utils/matchers';
@@ -27,14 +21,11 @@ export const gridsManager = new TestGridsManager({
export const EDIT_MODES = ['ui', 'setDataValue'] as const;
export type EditableCallback = Exclude, boolean>;
-export type GroupRowEditableCallback = GroupRowEditableCallbackInternal;
-export type GroupRowValueSetterCallback = GroupRowValueSetterFunc;
+export type GroupRowEditableCallback = Exclude, boolean>;
+export type GroupRowValueSetterCallback = Extract, (...args: any[]) => any>;
export type ValueSetterCallback = Extract, (...args: any[]) => any>;
export type ValueParserCallback = Extract, (...args: any[]) => any>;
-// Re-export internal types for tests
-export type { ColDefInternal, GroupRowValueSetterParams };
-
function locateCellElements(api: GridApi, rowNode: IRowNode, colId: string) {
const gridDiv = TestGridsManager.getHTMLElement(api);
expect(gridDiv).not.toBeNull();
diff --git a/testing/behavioural/src/cell-editing/group-edit/group-row-editable-pinned-pivot.test.ts b/testing/behavioural/src/cell-editing/group-edit/group-row-editable-pinned-pivot.test.ts
index e08ca5efe58..05842423d63 100644
--- a/testing/behavioural/src/cell-editing/group-edit/group-row-editable-pinned-pivot.test.ts
+++ b/testing/behavioural/src/cell-editing/group-edit/group-row-editable-pinned-pivot.test.ts
@@ -5,7 +5,6 @@ import { PivotModule, RowGroupingModule } from 'ag-grid-enterprise';
import type { GridRowsOptions } from '../../test-utils';
import { GridRows, TestGridsManager, asyncSetTimeout } from '../../test-utils';
import { expect } from '../../test-utils/matchers';
-import type { ColDefInternal } from './group-edit-test-utils';
import { EDIT_MODES, cascadeGroupRowValueSetter, editCell } from './group-edit-test-utils';
interface PivotRowData {
@@ -64,7 +63,7 @@ describe('editing with pinned pivot rows', () => {
editable: true,
groupRowEditable: true,
groupRowValueSetter: cascadeGroupRowValueSetter,
- } as ColDefInternal,
+ },
],
pivotMode: true,
groupDefaultExpanded: -1,
@@ -265,7 +264,7 @@ describe('editing with pinned pivot rows', () => {
editable: true,
groupRowEditable: true,
groupRowValueSetter: cascadeGroupRowValueSetter,
- } as ColDefInternal,
+ },
],
pivotMode: true,
groupDefaultExpanded: -1,
@@ -452,7 +451,7 @@ describe('editing with pinned pivot rows', () => {
editable: true,
groupRowEditable: true,
groupRowValueSetter: cascadeGroupRowValueSetter,
- } as ColDefInternal,
+ },
],
isRowPinned: (node) => {
if (!node.group) {
diff --git a/testing/behavioural/src/cell-editing/group-edit/group-row-editable-pinned-sibling.test.ts b/testing/behavioural/src/cell-editing/group-edit/group-row-editable-pinned-sibling.test.ts
index c34c39760f4..98644507935 100644
--- a/testing/behavioural/src/cell-editing/group-edit/group-row-editable-pinned-sibling.test.ts
+++ b/testing/behavioural/src/cell-editing/group-edit/group-row-editable-pinned-sibling.test.ts
@@ -1,9 +1,8 @@
-import type { GridOptions, RowNode, ValueSetterParams } from 'ag-grid-community';
+import type { GridOptions, GroupRowValueSetterParams, RowNode, ValueSetterParams } from 'ag-grid-community';
import { ClientSideRowModelModule, PinnedRowModule, UndoRedoEditModule } from 'ag-grid-community';
import { PivotModule, RowGroupingModule, SetFilterModule } from 'ag-grid-enterprise';
import { GridRows, TestGridsManager, asyncSetTimeout } from '../../test-utils';
-import type { ColDefInternal, GroupRowValueSetterParams } from './group-edit-test-utils';
import { EDIT_MODES, cascadeGroupRowValueSetter, editCell } from './group-edit-test-utils';
/**
@@ -218,7 +217,7 @@ describe('editing with pinned sibling rows', () => {
editable: true,
groupRowEditable: true,
groupRowValueSetter: cascadeGroupRowValueSetter,
- } as ColDefInternal,
+ },
],
rowData: createGroupRowData(),
groupDefaultExpanded: -1,
@@ -307,7 +306,7 @@ describe('editing with pinned sibling rows', () => {
editable: true,
groupRowEditable: true,
groupRowValueSetter: cascadeGroupRowValueSetter,
- } as ColDefInternal,
+ },
],
rowData: createGroupRowData(),
groupDefaultExpanded: -1,
@@ -389,7 +388,7 @@ describe('editing with pinned sibling rows', () => {
editable: true,
groupRowEditable: true,
groupRowValueSetter: customValueSetter,
- } as ColDefInternal,
+ },
],
rowData: createGroupRowDataForCallback(),
groupDefaultExpanded: -1,
@@ -446,7 +445,7 @@ describe('editing with pinned sibling rows', () => {
groupRowEditable: true,
groupRowValueSetter: customValueSetter,
filter: 'agNumberColumnFilter',
- } as ColDefInternal,
+ },
],
rowData: createGroupRowDataForCallback(),
groupDefaultExpanded: -1,
@@ -509,7 +508,7 @@ describe('editing with pinned sibling rows', () => {
editable: true,
groupRowEditable: true,
groupRowValueSetter: customValueSetter,
- } as ColDefInternal,
+ },
],
rowData: createGroupRowDataForCallback(),
groupDefaultExpanded: -1,
diff --git a/testing/behavioural/src/cell-editing/group-edit/group-row-editable-pivot.test.ts b/testing/behavioural/src/cell-editing/group-edit/group-row-editable-pivot.test.ts
index 2f426393385..fbfa26a2469 100644
--- a/testing/behavioural/src/cell-editing/group-edit/group-row-editable-pivot.test.ts
+++ b/testing/behavioural/src/cell-editing/group-edit/group-row-editable-pivot.test.ts
@@ -6,7 +6,6 @@ import type { GridRowsOptions } from '../../test-utils';
import { EditEventTracker, GridRows, TestGridsManager, asyncSetTimeout } from '../../test-utils';
import { expect } from '../../test-utils/matchers';
import type {
- ColDefInternal,
GroupRowEditableCallback,
GroupRowValueSetterCallback,
ValueSetterCallback,
@@ -68,7 +67,7 @@ describe('groupRowEditable with pivot mode', () => {
editable: true,
groupRowEditable,
groupRowValueSetter,
- } as ColDefInternal,
+ },
],
pivotMode: true,
groupDefaultExpanded: -1,
@@ -148,7 +147,7 @@ describe('groupRowEditable with pivot mode', () => {
editable: true,
groupRowEditable: true,
groupRowValueSetter: cascadeGroupRowValueSetter,
- } as ColDefInternal,
+ },
],
pivotMode: true,
groupDefaultExpanded: -1,
@@ -221,7 +220,7 @@ describe('groupRowEditable with pivot mode', () => {
editable: true,
groupRowEditable: true,
groupRowValueSetter: cascadeGroupRowValueSetter,
- } as ColDefInternal,
+ },
],
pivotMode: true,
groupDefaultExpanded: -1,
@@ -299,7 +298,7 @@ describe('groupRowEditable with pivot mode', () => {
editable: true,
groupRowEditable: true,
groupRowValueSetter: cascadeGroupRowValueSetter,
- } as ColDefInternal,
+ },
],
pivotMode: true,
groupDefaultExpanded: -1,
@@ -375,7 +374,7 @@ describe('groupRowEditable with pivot mode', () => {
editable: true,
groupRowEditable,
groupRowValueSetter: cascadeGroupRowValueSetter,
- } as ColDefInternal,
+ },
],
pivotMode: true,
groupDefaultExpanded: -1,
@@ -431,7 +430,7 @@ describe('groupRowEditable with pivot mode', () => {
editable: true,
groupRowEditable,
groupRowValueSetter: cascadeGroupRowValueSetter,
- } as ColDefInternal,
+ },
],
pivotMode: true,
groupDefaultExpanded: -1,
@@ -489,7 +488,7 @@ describe('groupRowEditable with pivot mode', () => {
editable: true,
groupRowEditable: true,
groupRowValueSetter,
- } as ColDefInternal,
+ },
],
pivotMode: true,
groupDefaultExpanded: -1,
@@ -556,7 +555,7 @@ describe('groupRowEditable with pivot mode', () => {
editable: true,
groupRowEditable: true,
groupRowValueSetter,
- } as ColDefInternal,
+ },
],
pivotMode: true,
groupDefaultExpanded: -1,
@@ -647,7 +646,7 @@ describe('groupRowEditable with pivot mode', () => {
editable: true,
groupRowEditable: true,
groupRowValueSetter: cascadeGroupRowValueSetter,
- } as ColDefInternal,
+ },
],
pivotMode: true,
groupDefaultExpanded: -1,
@@ -727,7 +726,7 @@ describe('groupRowEditable with pivot mode', () => {
editable: true,
groupRowEditable: true,
groupRowValueSetter: cascadeGroupRowValueSetter,
- } as ColDefInternal,
+ },
],
pivotMode: true,
groupDefaultExpanded: -1,
@@ -820,7 +819,7 @@ describe('groupRowEditable with pivot mode', () => {
groupRowEditable: true,
valueSetter,
groupRowValueSetter,
- } as ColDefInternal,
+ },
],
pivotMode: true,
groupDefaultExpanded: -1,
@@ -970,7 +969,7 @@ describe('groupRowEditable with pivot mode', () => {
editable: true,
groupRowEditable: true,
groupRowValueSetter: cascadeGroupRowValueSetter,
- } as ColDefInternal,
+ },
],
pivotMode: true,
groupDefaultExpanded: -1,
@@ -1074,7 +1073,7 @@ describe('groupRowEditable with pivot mode', () => {
editable: true,
groupRowEditable: true,
groupRowValueSetter: cascadeGroupRowValueSetter,
- } as ColDefInternal,
+ },
],
pivotMode: true,
groupDefaultExpanded: 0,
@@ -1143,7 +1142,7 @@ describe('groupRowEditable with pivot mode', () => {
hide: true,
editable: true,
groupRowEditable: true,
- } as ColDefInternal,
+ },
],
pivotMode: true,
groupDefaultExpanded: 0,
diff --git a/testing/behavioural/src/cell-editing/group-edit/group-row-editable.test.ts b/testing/behavioural/src/cell-editing/group-edit/group-row-editable.test.ts
index dee44e513c5..2a9758d6a07 100644
--- a/testing/behavioural/src/cell-editing/group-edit/group-row-editable.test.ts
+++ b/testing/behavioural/src/cell-editing/group-edit/group-row-editable.test.ts
@@ -2,7 +2,6 @@ import type { GridOptions, ValueParserParams } from 'ag-grid-community';
import { expect } from '../../test-utils/matchers';
import type {
- ColDefInternal,
EditableCallback,
GroupRowEditableCallback,
GroupRowValueSetterCallback,
@@ -67,7 +66,7 @@ describe.each(EDIT_MODES)('groupRowEditable behaviour (%s)', (editMode) => {
editable,
groupRowEditable,
valueSetter,
- } as ColDefInternal,
+ },
{ field: 'category', rowGroup: true, hide: true },
],
rowData: [
@@ -90,7 +89,7 @@ describe.each(EDIT_MODES)('groupRowEditable behaviour (%s)', (editMode) => {
editableCalls.length = 0;
valueSetterCalls.length = 0;
const groupColumn = api.getDisplayedCenterColumns()[0]!;
- expect((groupColumn.getColDef() as ColDefInternal).groupRowEditable).toBe(groupRowEditable);
+ expect(groupColumn.getColDef().groupRowEditable).toBe(groupRowEditable);
expect(groupColumn.isCellEditable(groupRowNode!)).toBe(true);
const groupColId = groupColumn.getColId();
if (editMode === 'ui') {
@@ -191,7 +190,7 @@ describe.each(EDIT_MODES)('groupRowEditable behaviour (%s)', (editMode) => {
groupRowEditable: true,
valueParser,
valueSetter,
- } as ColDefInternal,
+ },
{ field: 'category', rowGroup: true, hide: true },
],
rowData: [
@@ -264,7 +263,7 @@ describe.each(EDIT_MODES)('groupRowEditable behaviour (%s)', (editMode) => {
editable,
groupRowEditable,
valueSetter,
- } as ColDefInternal,
+ },
],
treeData: true,
rowData: [{ id: 'mars', path: ['Solar System', 'Mars'], label: 'Mars' }],
@@ -280,7 +279,7 @@ describe.each(EDIT_MODES)('groupRowEditable behaviour (%s)', (editMode) => {
const originalFillerValue = getGroupColumnDisplayValue(fillerRowNode!);
const groupColumn = api.getDisplayedCenterColumns()[0]!;
- expect((groupColumn.getColDef() as ColDefInternal).groupRowEditable).toBe(groupRowEditable);
+ expect(groupColumn.getColDef().groupRowEditable).toBe(groupRowEditable);
groupRowEditableCalls.length = 0;
editableCalls.length = 0;
@@ -351,7 +350,7 @@ describe.each(EDIT_MODES)('groupRowEditable behaviour (%s)', (editMode) => {
editable,
groupRowEditable,
valueSetter,
- } as ColDefInternal,
+ },
],
treeData: true,
rowData,
@@ -485,7 +484,7 @@ describe.each(EDIT_MODES)('groupRowEditable behaviour (%s)', (editMode) => {
groupRowEditable: false,
valueSetter,
groupRowValueSetter,
- } as ColDefInternal,
+ },
{ field: 'category', rowGroup: true, hide: true },
],
rowData: [