From 14ce6986a922256e0f43dc9d3dfb02c274af81a9 Mon Sep 17 00:00:00 2001 From: Stephen Cooper Date: Wed, 10 Jun 2026 15:40:53 +0100 Subject: [PATCH 1/5] Fix doc links (#14025) --- .../src/content/api-documentation/grid-options/properties.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/ag-grid-docs/src/content/api-documentation/grid-options/properties.json b/documentation/ag-grid-docs/src/content/api-documentation/grid-options/properties.json index 82907331419..9643d971486 100644 --- a/documentation/ag-grid-docs/src/content/api-documentation/grid-options/properties.json +++ b/documentation/ag-grid-docs/src/content/api-documentation/grid-options/properties.json @@ -1022,7 +1022,7 @@ "processFileInput": { "more": { "name": "File Input Overlay", - "url": "./overlays-overview/#file-input-overlay" + "url": "./overlays-provided/#file-input" } } }, From b437b8ffbd65e2e8928b07cfb949734fbb3a3b76 Mon Sep 17 00:00:00 2001 From: Guilherme Lopes Date: Wed, 10 Jun 2026 11:42:09 -0300 Subject: [PATCH 2/5] AG-17480 - [Calculated Column] - Live Preview Improvements (#14024) --- .../calculated-columns-advanced/main.ts | 15 ++ .../example.spec.ts | 2 +- .../index.html | 0 .../main.ts | 17 +- .../calculated-columns-column-groups/main.ts | 176 ++++++++++++++++++ .../main.ts | 16 ++ .../main.ts | 16 ++ .../main.ts | 19 +- .../calculated-columns-row-groups/main.ts | 15 ++ .../_examples/calculated-columns/main.ts | 15 ++ .../docs/calculated-columns/index.mdoc | 20 +- .../src/interfaces/iCalculatedColumns.ts | 9 +- .../rules/gridOptionsValidations.ts | 5 +- .../calculatedColumns/calculatedColumnForm.ts | 12 +- .../calculatedColumnsService.ts | 58 +++--- .../calculated-live-preview.bench.ts | 132 +++++++++++++ .../calculated-columns-ordering.test.ts | 12 +- .../src/formulas/calculated-columns.test.ts | 72 ++++++- 18 files changed, 548 insertions(+), 63 deletions(-) rename documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/{calculated-columns-live-preview => calculated-columns-apply-mode}/example.spec.ts (79%) rename documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/{calculated-columns-live-preview => calculated-columns-apply-mode}/index.html (100%) rename documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/{calculated-columns-live-preview => calculated-columns-apply-mode}/main.ts (67%) create mode 100644 testing/behavioural/src/benchmarks/calculated-live-preview.bench.ts diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-advanced/main.ts b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-advanced/main.ts index 83661d0f181..5054e2b443d 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-advanced/main.ts +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-advanced/main.ts @@ -76,6 +76,21 @@ const rowData: SalesRow[] = [ { account: 'Pioneer Logistics', region: 'Americas', revenue: 214000, cost: 139000 }, { account: 'Apex Manufacturing', region: 'EMEA', revenue: 198000, cost: 158000 }, { account: 'Blue River Telecom', region: 'Americas', revenue: 276000, cost: 192000 }, + { account: 'Crestline Foods', region: 'EMEA', revenue: 167000, cost: 121000 }, + { account: 'Harbor Freight Co', region: 'Americas', revenue: 142000, cost: 99000 }, + { account: 'Atlas Mining', region: 'APAC', revenue: 251000, cost: 197000 }, + { account: 'Veridian Health', region: 'EMEA', revenue: 173000, cost: 128000 }, + { account: 'Quantum Software', region: 'Americas', revenue: 298000, cost: 176000 }, + { account: 'Redwood Hotels', region: 'APAC', revenue: 132000, cost: 104000 }, + { account: 'Ironbridge Steel', region: 'EMEA', revenue: 221000, cost: 183000 }, + { account: 'Lakeside Media', region: 'Americas', revenue: 96000, cost: 71000 }, + { account: 'Polar Shipping', region: 'EMEA', revenue: 188000, cost: 142000 }, + { account: 'Granite Insurance', region: 'Americas', revenue: 204000, cost: 149000 }, + { account: 'Cobalt Mining', region: 'APAC', revenue: 243000, cost: 186000 }, + { account: 'Meridian Airlines', region: 'EMEA', revenue: 312000, cost: 268000 }, + { account: 'Oakfield Farms', region: 'Americas', revenue: 87000, cost: 62000 }, + { account: 'Silverline Bank', region: 'APAC', revenue: 265000, cost: 191000 }, + { account: 'Horizon Telecom', region: 'EMEA', revenue: 154000, cost: 112000 }, ]; const gridOptions: GridOptions = { diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-live-preview/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-apply-mode/example.spec.ts similarity index 79% rename from documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-live-preview/example.spec.ts rename to documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-apply-mode/example.spec.ts index 1cf5bff8315..911024c7536 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-live-preview/example.spec.ts +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-apply-mode/example.spec.ts @@ -1,7 +1,7 @@ import { expect, test } from '@utils/grid/test-utils'; test.agExample(import.meta, () => { - test.eachFramework('live preview calculated column evaluates revenue - cost', async ({ agIdFor }) => { + test.eachFramework('deferred apply mode calculated column evaluates revenue - cost', async ({ agIdFor }) => { const profitHeader = agIdFor.headerCell('profit'); await expect(profitHeader).toContainText('Profit'); await expect(profitHeader).toHaveClass(/ag-calculated-column/); diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-live-preview/index.html b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-apply-mode/index.html similarity index 100% rename from documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-live-preview/index.html rename to documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-apply-mode/index.html diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-live-preview/main.ts b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-apply-mode/main.ts similarity index 67% rename from documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-live-preview/main.ts rename to documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-apply-mode/main.ts index 3f2daab2ec7..19941f6854e 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-live-preview/main.ts +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-apply-mode/main.ts @@ -56,6 +56,21 @@ const rowData: SalesRow[] = [ { product: 'Battery pack', revenue: 126000, cost: 101000 }, { product: 'EV charger', revenue: 92000, cost: 61000 }, { product: 'Heat pump', revenue: 168000, cost: 119000 }, + { product: 'Inverter unit', revenue: 88000, cost: 57000 }, + { product: 'Wind turbine kit', revenue: 232000, cost: 171000 }, + { product: 'Solar tile roof', revenue: 198000, cost: 144000 }, + { product: 'Power optimiser', revenue: 64000, cost: 41000 }, + { product: 'Charge controller', revenue: 53000, cost: 33000 }, + { product: 'Energy monitor', revenue: 47000, cost: 29000 }, + { product: 'Storage cabinet', revenue: 71000, cost: 52000 }, + { product: 'Microinverter', revenue: 59000, cost: 37000 }, + { product: 'Heat recovery unit', revenue: 124000, cost: 88000 }, + { product: 'Hybrid boiler', revenue: 156000, cost: 117000 }, + { product: 'Smart meter', revenue: 39000, cost: 24000 }, + { product: 'Insulation pack', revenue: 44000, cost: 27000 }, + { product: 'EV cable set', revenue: 31000, cost: 18000 }, + { product: 'Solar pump', revenue: 67000, cost: 45000 }, + { product: 'Backup generator', revenue: 173000, cost: 131000 }, ]; const gridOptions: GridOptions = { @@ -66,7 +81,7 @@ const gridOptions: GridOptions = { minWidth: 130, }, calculatedColumns: { - livePreview: true, + applyMode: 'deferred', }, }; diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-column-groups/main.ts b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-column-groups/main.ts index cdfb6b56cbf..ed982c44eb5 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-column-groups/main.ts +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-column-groups/main.ts @@ -154,6 +154,182 @@ const rowData: QuarterlyRevenueRow[] = [ q3_2026: 34000, q4_2026: 36000, }, + { + product: 'Heat pump', + q1_2025: 36000, + q2_2025: 38500, + q3_2025: 41000, + q4_2025: 43500, + q1_2026: 46000, + q2_2026: 48500, + q3_2026: 51000, + q4_2026: 53500, + }, + { + product: 'Inverter unit', + q1_2025: 21000, + q2_2025: 22500, + q3_2025: 24000, + q4_2025: 25500, + q1_2026: 27000, + q2_2026: 28500, + q3_2026: 30000, + q4_2026: 31500, + }, + { + product: 'Wind turbine kit', + q1_2025: 52000, + q2_2025: 55000, + q3_2025: 58000, + q4_2025: 61000, + q1_2026: 64000, + q2_2026: 67000, + q3_2026: 70000, + q4_2026: 73000, + }, + { + product: 'Solar tile roof', + q1_2025: 45000, + q2_2025: 47800, + q3_2025: 50600, + q4_2025: 53400, + q1_2026: 56200, + q2_2026: 59000, + q3_2026: 61800, + q4_2026: 64600, + }, + { + product: 'Power optimiser', + q1_2025: 15000, + q2_2025: 16100, + q3_2025: 17200, + q4_2025: 18300, + q1_2026: 19400, + q2_2026: 20500, + q3_2026: 21600, + q4_2026: 22700, + }, + { + product: 'Charge controller', + q1_2025: 12500, + q2_2025: 13400, + q3_2025: 14300, + q4_2025: 15200, + q1_2026: 16100, + q2_2026: 17000, + q3_2026: 17900, + q4_2026: 18800, + }, + { + product: 'Energy monitor', + q1_2025: 11000, + q2_2025: 11800, + q3_2025: 12600, + q4_2025: 13400, + q1_2026: 14200, + q2_2026: 15000, + q3_2026: 15800, + q4_2026: 16600, + }, + { + product: 'Storage cabinet', + q1_2025: 17000, + q2_2025: 18200, + q3_2025: 19400, + q4_2025: 20600, + q1_2026: 21800, + q2_2026: 23000, + q3_2026: 24200, + q4_2026: 25400, + }, + { + product: 'Microinverter', + q1_2025: 14000, + q2_2025: 15000, + q3_2025: 16000, + q4_2025: 17000, + q1_2026: 18000, + q2_2026: 19000, + q3_2026: 20000, + q4_2026: 21000, + }, + { + product: 'Heat recovery unit', + q1_2025: 28000, + q2_2025: 29900, + q3_2025: 31800, + q4_2025: 33700, + q1_2026: 35600, + q2_2026: 37500, + q3_2026: 39400, + q4_2026: 41300, + }, + { + product: 'Hybrid boiler', + q1_2025: 34000, + q2_2025: 36300, + q3_2025: 38600, + q4_2025: 40900, + q1_2026: 43200, + q2_2026: 45500, + q3_2026: 47800, + q4_2026: 50100, + }, + { + product: 'Smart meter', + q1_2025: 9000, + q2_2025: 9700, + q3_2025: 10400, + q4_2025: 11100, + q1_2026: 11800, + q2_2026: 12500, + q3_2026: 13200, + q4_2026: 13900, + }, + { + product: 'Insulation pack', + q1_2025: 10500, + q2_2025: 11250, + q3_2025: 12000, + q4_2025: 12750, + q1_2026: 13500, + q2_2026: 14250, + q3_2026: 15000, + q4_2026: 15750, + }, + { + product: 'EV cable set', + q1_2025: 7500, + q2_2025: 8050, + q3_2025: 8600, + q4_2025: 9150, + q1_2026: 9700, + q2_2026: 10250, + q3_2026: 10800, + q4_2026: 11350, + }, + { + product: 'Solar pump', + q1_2025: 16000, + q2_2025: 17150, + q3_2025: 18300, + q4_2025: 19450, + q1_2026: 20600, + q2_2026: 21750, + q3_2026: 22900, + q4_2026: 24050, + }, + { + product: 'Backup generator', + q1_2025: 40000, + q2_2025: 42600, + q3_2025: 45200, + q4_2025: 47800, + q1_2026: 50400, + q2_2026: 53000, + q3_2026: 55600, + q4_2026: 58200, + }, ]; const gridOptions: GridOptions = { diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-data-types/main.ts b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-data-types/main.ts index f717905e805..063d1eb4f9e 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-data-types/main.ts +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-data-types/main.ts @@ -44,6 +44,22 @@ const rowData: SalesRow[] = [ { product: 'Smart thermostat', revenue: 78000, cost: 52000 }, { product: 'Battery pack', revenue: 126000, cost: 101000 }, { product: 'EV charger', revenue: 92000, cost: 61000 }, + { product: 'Heat pump', revenue: 168000, cost: 119000 }, + { product: 'Inverter unit', revenue: 88000, cost: 57000 }, + { product: 'Wind turbine kit', revenue: 232000, cost: 171000 }, + { product: 'Solar tile roof', revenue: 198000, cost: 144000 }, + { product: 'Power optimiser', revenue: 64000, cost: 41000 }, + { product: 'Charge controller', revenue: 53000, cost: 33000 }, + { product: 'Energy monitor', revenue: 47000, cost: 29000 }, + { product: 'Storage cabinet', revenue: 71000, cost: 52000 }, + { product: 'Microinverter', revenue: 59000, cost: 37000 }, + { product: 'Heat recovery unit', revenue: 124000, cost: 88000 }, + { product: 'Hybrid boiler', revenue: 156000, cost: 117000 }, + { product: 'Smart meter', revenue: 39000, cost: 24000 }, + { product: 'Insulation pack', revenue: 44000, cost: 27000 }, + { product: 'EV cable set', revenue: 31000, cost: 18000 }, + { product: 'Solar pump', revenue: 67000, cost: 45000 }, + { product: 'Backup generator', revenue: 173000, cost: 131000 }, ]; const gridOptions: GridOptions = { diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-helper-lists/main.ts b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-helper-lists/main.ts index e9ef96a9743..2675b7930a8 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-helper-lists/main.ts +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-helper-lists/main.ts @@ -44,6 +44,22 @@ const rowData: SalesRow[] = [ { product: 'Smart thermostat', revenue: 78000, cost: 52000 }, { product: 'Battery pack', revenue: 126000, cost: 101000 }, { product: 'EV charger', revenue: 92000, cost: 61000 }, + { product: 'Heat pump', revenue: 168000, cost: 119000 }, + { product: 'Inverter unit', revenue: 88000, cost: 57000 }, + { product: 'Wind turbine kit', revenue: 232000, cost: 171000 }, + { product: 'Solar tile roof', revenue: 198000, cost: 144000 }, + { product: 'Power optimiser', revenue: 64000, cost: 41000 }, + { product: 'Charge controller', revenue: 53000, cost: 33000 }, + { product: 'Energy monitor', revenue: 47000, cost: 29000 }, + { product: 'Storage cabinet', revenue: 71000, cost: 52000 }, + { product: 'Microinverter', revenue: 59000, cost: 37000 }, + { product: 'Heat recovery unit', revenue: 124000, cost: 88000 }, + { product: 'Hybrid boiler', revenue: 156000, cost: 117000 }, + { product: 'Smart meter', revenue: 39000, cost: 24000 }, + { product: 'Insulation pack', revenue: 44000, cost: 27000 }, + { product: 'EV cable set', revenue: 31000, cost: 18000 }, + { product: 'Solar pump', revenue: 67000, cost: 45000 }, + { product: 'Backup generator', revenue: 173000, cost: 131000 }, ]; const gridOptions: GridOptions = { diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-highlighting/main.ts b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-highlighting/main.ts index f45d3d9635a..f4ab6064608 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-highlighting/main.ts +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-dialog-highlighting/main.ts @@ -44,6 +44,22 @@ const rowData: SalesRow[] = [ { product: 'Smart thermostat', revenue: 78000, cost: 52000 }, { product: 'Battery pack', revenue: 126000, cost: 101000 }, { product: 'EV charger', revenue: 92000, cost: 61000 }, + { product: 'Heat pump', revenue: 168000, cost: 119000 }, + { product: 'Inverter unit', revenue: 88000, cost: 57000 }, + { product: 'Wind turbine kit', revenue: 232000, cost: 171000 }, + { product: 'Solar tile roof', revenue: 198000, cost: 144000 }, + { product: 'Power optimiser', revenue: 64000, cost: 41000 }, + { product: 'Charge controller', revenue: 53000, cost: 33000 }, + { product: 'Energy monitor', revenue: 47000, cost: 29000 }, + { product: 'Storage cabinet', revenue: 71000, cost: 52000 }, + { product: 'Microinverter', revenue: 59000, cost: 37000 }, + { product: 'Heat recovery unit', revenue: 124000, cost: 88000 }, + { product: 'Hybrid boiler', revenue: 156000, cost: 117000 }, + { product: 'Smart meter', revenue: 39000, cost: 24000 }, + { product: 'Insulation pack', revenue: 44000, cost: 27000 }, + { product: 'EV cable set', revenue: 31000, cost: 18000 }, + { product: 'Solar pump', revenue: 67000, cost: 45000 }, + { product: 'Backup generator', revenue: 173000, cost: 131000 }, ]; const gridOptions: GridOptions = { @@ -56,9 +72,6 @@ const gridOptions: GridOptions = { flex: 1, minWidth: 130, }, - onFirstDataRendered: (params) => { - params.api.openCalculatedColumnDialog('profit'); - }, }; document.addEventListener('DOMContentLoaded', () => { diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-row-groups/main.ts b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-row-groups/main.ts index c096ff53e2e..2570d96a0a2 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-row-groups/main.ts +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns-row-groups/main.ts @@ -66,6 +66,21 @@ const rowData: SalesRow[] = [ { productType: 'Charging', product: 'Battery pack', revenue: 126000, cost: 101000 }, { productType: 'Charging', product: 'EV charger', revenue: 92000, cost: 61000 }, { productType: 'Heating', product: 'Heat pump', revenue: 168000, cost: 119000 }, + { productType: 'Heating', product: 'Hybrid boiler', revenue: 156000, cost: 117000 }, + { productType: 'Heating', product: 'Heat recovery unit', revenue: 124000, cost: 88000 }, + { productType: 'Storage', product: 'Storage cabinet', revenue: 71000, cost: 52000 }, + { productType: 'Storage', product: 'Lithium rack', revenue: 143000, cost: 112000 }, + { productType: 'Storage', product: 'Flow battery', revenue: 187000, cost: 149000 }, + { productType: 'Storage', product: 'Backup generator', revenue: 173000, cost: 131000 }, + { productType: 'Wind', product: 'Wind turbine kit', revenue: 232000, cost: 171000 }, + { productType: 'Wind', product: 'Micro turbine', revenue: 76000, cost: 49000 }, + { productType: 'Wind', product: 'Tower mount', revenue: 41000, cost: 26000 }, + { productType: 'Monitoring', product: 'Energy monitor', revenue: 47000, cost: 29000 }, + { productType: 'Monitoring', product: 'Smart meter', revenue: 39000, cost: 24000 }, + { productType: 'Monitoring', product: 'Grid analyser', revenue: 58000, cost: 36000 }, + { productType: 'Efficiency', product: 'Insulation pack', revenue: 44000, cost: 27000 }, + { productType: 'Efficiency', product: 'LED retrofit', revenue: 36000, cost: 21000 }, + { productType: 'Efficiency', product: 'Window film', revenue: 28000, cost: 16000 }, ]; const gridOptions: GridOptions = { diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns/main.ts b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns/main.ts index d6b828f926f..5bb232a993c 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns/main.ts +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/_examples/calculated-columns/main.ts @@ -56,6 +56,21 @@ const rowData: SalesRow[] = [ { product: 'Battery pack', revenue: 126000, cost: 101000 }, { product: 'EV charger', revenue: 92000, cost: 61000 }, { product: 'Heat pump', revenue: 168000, cost: 119000 }, + { product: 'Inverter unit', revenue: 88000, cost: 57000 }, + { product: 'Wind turbine kit', revenue: 232000, cost: 171000 }, + { product: 'Solar tile roof', revenue: 198000, cost: 144000 }, + { product: 'Power optimiser', revenue: 64000, cost: 41000 }, + { product: 'Charge controller', revenue: 53000, cost: 33000 }, + { product: 'Energy monitor', revenue: 47000, cost: 29000 }, + { product: 'Storage cabinet', revenue: 71000, cost: 52000 }, + { product: 'Microinverter', revenue: 59000, cost: 37000 }, + { product: 'Heat recovery unit', revenue: 124000, cost: 88000 }, + { product: 'Hybrid boiler', revenue: 156000, cost: 117000 }, + { product: 'Smart meter', revenue: 39000, cost: 24000 }, + { product: 'Insulation pack', revenue: 44000, cost: 27000 }, + { product: 'EV cable set', revenue: 31000, cost: 18000 }, + { product: 'Solar pump', revenue: 67000, cost: 45000 }, + { product: 'Backup generator', revenue: 173000, cost: 131000 }, ]; const gridOptions: GridOptions = { diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/index.mdoc b/documentation/ag-grid-docs/src/content/docs/calculated-columns/index.mdoc index 19d043ef93e..5beb33bbb81 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/index.mdoc +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/index.mdoc @@ -63,6 +63,8 @@ The dialog manages calculated columns inside the grid without mutating the `colu The Calculated Column dialog can be configured with the `calculatedColumns` grid option. +While a column's dialog is open, the column's header and cells are highlighted. The highlight colour can be customised with the `--ag-calculated-column-highlight-color` CSS variable. + ### dataTypes Use `dataTypes` to control which cell data types appear in the dialog type dropdown: @@ -97,7 +99,7 @@ This only controls the expression picker buttons. Use an empty array or `null` t ### suppressColumnHighlighting -Calculated columns are highlighted by default while their edit dialog is open. Use `suppressColumnHighlighting` to disable this highlight: +Use `suppressColumnHighlighting` to disable the highlight while the dialog is open: ```{% frameworkTransform=true %} const gridOptions = { @@ -108,27 +110,23 @@ const gridOptions = { }; ``` -When highlighting is not suppressed, the header and cells highlight colour can be customised with the `--ag-calculated-column-highlight-color` CSS variable. - {% gridExampleRunner title="Suppress Dialog Column Highlighting" name="calculated-columns-dialog-highlighting" /%} -### livePreview +### applyMode + +By default, dialog edits apply to the column immediately: **Add Calculated Column** creates the column and opens the dialog over it, and title, type and expression changes update the column as you type. Closing the dialog keeps the latest state; remove the column with **Remove Calculated Column**. An empty expression renders blank cells, and invalid or incomplete expressions render formula errors until fixed. -Use `livePreview` to apply dialog edits immediately instead of using Apply and Cancel buttons: +Set `applyMode: 'deferred'` to hold changes until the **Apply** button is clicked instead. In deferred mode the dialog shows Apply and Cancel buttons and validates the expression, so invalid expressions cannot be applied: ```{% frameworkTransform=true %} const gridOptions = { calculatedColumns: { - livePreview: true, + applyMode: 'deferred', }, }; ``` -With live preview enabled, **Add Calculated Column** creates the column immediately and opens the dialog for editing. Title, type and expression changes update the column live. Closing the dialog keeps the latest state; remove the column with **Remove Calculated Column**. - -An empty expression is valid in live preview and renders blank cells. Invalid or incomplete expressions are also stored while editing and render formula errors until fixed. - -{% gridExampleRunner title="Live Preview" name="calculated-columns-live-preview" /%} +{% gridExampleRunner title="Deferred Apply Mode" name="calculated-columns-apply-mode" /%} ## Advanced Calculated Columns diff --git a/packages/ag-grid-community/src/interfaces/iCalculatedColumns.ts b/packages/ag-grid-community/src/interfaces/iCalculatedColumns.ts index bbda945d109..d16c4f765f1 100644 --- a/packages/ag-grid-community/src/interfaces/iCalculatedColumns.ts +++ b/packages/ag-grid-community/src/interfaces/iCalculatedColumns.ts @@ -7,6 +7,8 @@ import type { ColumnEventType } from '../events'; export type CalculatedColumnExpressionPicker = 'columns' | 'functions' | 'operators'; +export type CalculatedColumnApplyMode = 'live' | 'deferred'; + export interface CalculatedColumnsOptions { /** * Cell data types shown in the Calculated Column dialog type selector. @@ -25,10 +27,11 @@ export interface CalculatedColumnsOptions { */ suppressColumnHighlighting?: boolean; /** - * Apply Calculated Column dialog changes immediately, without Apply or Cancel buttons. - * @default false + * When Calculated Column dialog edits are applied: `'live'` applies every change immediately; + * `'deferred'` validates the expression and applies changes via Apply and Cancel buttons. + * @default 'live' */ - livePreview?: boolean; + applyMode?: CalculatedColumnApplyMode; } export type CalculatedColumnDef = ColDef & { diff --git a/packages/ag-grid-community/src/validation/rules/gridOptionsValidations.ts b/packages/ag-grid-community/src/validation/rules/gridOptionsValidations.ts index 2b5c0994d43..362331ec0a3 100644 --- a/packages/ag-grid-community/src/validation/rules/gridOptionsValidations.ts +++ b/packages/ag-grid-community/src/validation/rules/gridOptionsValidations.ts @@ -187,7 +187,7 @@ const GRID_OPTION_VALIDATIONS: () => Validations = () => { return 'calculatedColumns should be an object.'; } - const { dataTypes, expressionPickers } = calculatedColumns; + const { dataTypes, expressionPickers, applyMode } = calculatedColumns; if (dataTypes != null) { if (!Array.isArray(dataTypes) || dataTypes.some((dataType) => typeof dataType !== 'string')) { return 'calculatedColumns.dataTypes should be an array of strings.'; @@ -202,6 +202,9 @@ const GRID_OPTION_VALIDATIONS: () => Validations = () => { return "calculatedColumns.expressionPickers should contain only 'columns', 'functions' or 'operators'."; } } + if (applyMode != null && applyMode !== 'live' && applyMode !== 'deferred') { + return "calculatedColumns.applyMode should be 'live' or 'deferred'."; + } return null; }, diff --git a/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnForm.ts b/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnForm.ts index 3938e395d09..f72367b7bc8 100644 --- a/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnForm.ts +++ b/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnForm.ts @@ -122,7 +122,7 @@ export class CalculatedColumnForm extends Component { private readonly onValidate: (draft: CalculatedColumnDraft) => string | null, private readonly onApply: (draft: CalculatedColumnDraft) => string | null, private readonly onCancel: () => void, - private readonly livePreview = false, + private readonly liveApply: boolean, private readonly onDraftChange?: (draft: CalculatedColumnDraft) => void ) { super(CalculatedColumnFormElement, [AgInputTextFieldSelector, AgSelectSelector, AgInputTextAreaSelector]); @@ -132,9 +132,11 @@ export class CalculatedColumnForm extends Component { public postConstruct(): void { this.setupFormFields(); this.setupActionButtons(); - if (!this.livePreview) { + + if (!this.liveApply) { this.setupValidationTooltip(); } + this.addFormFieldListeners(); this.setupExpressionEditor(); this.addActionListeners(); @@ -185,7 +187,7 @@ export class CalculatedColumnForm extends Component { _setDisplayed(this.eFunctions, hasFunctions); _setDisplayed(this.eOperators, hasOperators); _setDisplayed(this.eExpressionTools, hasColumns || hasFunctions || hasOperators); - _setDisplayed(this.eActions, !this.livePreview); + _setDisplayed(this.eActions, !this.liveApply); } private addFormFieldListeners(): void { @@ -196,7 +198,7 @@ export class CalculatedColumnForm extends Component { }); this.eExpression.onValueChange((value) => { this.updateDraft({ calculatedExpression: value ?? '' }); - if (!this.livePreview) { + if (!this.liveApply) { this.setExpressionError(this.onValidate(this.draft)); } this.refreshContextSuggestions(); @@ -236,7 +238,7 @@ export class CalculatedColumnForm extends Component { }); } } - if (!this.livePreview) { + if (!this.liveApply) { this.addManagedElementListeners(this.eApply, { click: () => this.setExpressionError(this.onApply(this.draft)), }); diff --git a/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnsService.ts b/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnsService.ts index 400ae248c51..ac565075f9f 100644 --- a/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnsService.ts +++ b/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnsService.ts @@ -83,7 +83,7 @@ type OpenCalculatedColumnDialog = { highlight: boolean; }; -type PendingLivePreviewUpdate = { +type PendingLiveApplyUpdate = { draft: CalculatedColumnDraft; mapper: CalculatedColumnReferenceMapper; }; @@ -112,8 +112,8 @@ export class CalculatedColumnsService extends BeanStub implements NamedBean, ICa // dispatch is skipped — the imperative caller emits its own events; declarative loads run with it at 0. private suppressValidationChecks = 0; private readonly openDialogsByColId = new Map(); - private readonly scheduledLivePreviewColIds = new Set(); - private readonly pendingLivePreviewUpdatesByColId = new Map(); + private readonly scheduledLiveApplyColIds = new Set(); + private readonly pendingLiveApplyUpdatesByColId = new Map(); // Memoised parse results keyed by expression; see getFormulaError. private readonly formulaErrorsByExpression = new Map(); @@ -269,16 +269,16 @@ export class CalculatedColumnsService extends BeanStub implements NamedBean, ICa } public openCalculatedColumnDialog(column: AgColumn | null | undefined, mode: 'add' | 'edit', focus = true): void { - const livePreview = this.isLivePreview(); + const liveApply = this.isLiveApplyMode(); if (mode === 'add') { const colId = this.createUniqueColId(); const headerName = this.getLocaleTextFunc()('calculatedColumnDefaultTitle', 'Untitled'); const draft: CalculatedColumnDraft = { colId, headerName, ...this.getDefaultDraft() }; - if (livePreview) { - // Live preview adds the column up front, then opens the dialog over it. + if (liveApply) { + // Live apply adds the column up front, then opens the dialog over it. const newColumn = this.addDynamicCalculatedColumn(draft, column); if (newColumn) { - this.showDialog(draft, () => null, newColumn, focus, true); + this.showDialog(draft, () => null, true, newColumn, focus); } return; } @@ -290,6 +290,7 @@ export class CalculatedColumnsService extends BeanStub implements NamedBean, ICa this.focusCalculatedColumn(nextDraft.colId); } }, + false, undefined, focus ); @@ -308,9 +309,9 @@ export class CalculatedColumnsService extends BeanStub implements NamedBean, ICa const { colId: _, ...update } = this.toColDef(nextDraft); this.updateCalculatedColumn(column.colId, update); }, + liveApply, column, focus, - livePreview, mapper ); } @@ -368,8 +369,8 @@ export class CalculatedColumnsService extends BeanStub implements NamedBean, ICa this.checkValidationStates(source, true); } - private isLivePreview(): boolean { - return this.gos.get('calculatedColumns')?.livePreview === true; + private isLiveApplyMode(): boolean { + return this.gos.get('calculatedColumns')?.applyMode !== 'deferred'; } public overrideFor(colDef: ColDef): ColDef | null | undefined { @@ -515,9 +516,10 @@ export class CalculatedColumnsService extends BeanStub implements NamedBean, ICa private showDialog( draft: CalculatedColumnDraft, onApply: (draft: CalculatedColumnDraft) => void, + // Always passed explicitly: the 'live'-by-default resolution lives in isLiveApplyMode(). + liveApply: boolean, columnToHighlight?: AgColumn | null, focusDialog = true, - livePreview = false, existingMapper?: CalculatedColumnReferenceMapper ): void { const openDialogState = this.openDialogsByColId.get(draft.colId); @@ -578,8 +580,8 @@ export class CalculatedColumnsService extends BeanStub implements NamedBean, ICa state.resolved = true; state.close?.(); }; - const handleDraftChange = livePreview - ? (nextDraft: CalculatedColumnDraft) => this.scheduleLivePreviewUpdate(nextDraft, mapper) + const handleDraftChange = liveApply + ? (nextDraft: CalculatedColumnDraft) => this.scheduleLiveApplyUpdate(nextDraft, mapper) : undefined; const dataTypeOptions = this.getDataTypeOptions(draft.cellDataType); @@ -593,7 +595,7 @@ export class CalculatedColumnsService extends BeanStub implements NamedBean, ICa handleValidate, handleApply, handleCancel, - livePreview, + liveApply, handleDraftChange ) ); @@ -630,10 +632,10 @@ export class CalculatedColumnsService extends BeanStub implements NamedBean, ICa } }); dialog.addDestroyFunc(() => { - if (livePreview && this.isAlive()) { - this.flushLivePreviewUpdate(draft.colId); + if (liveApply && this.isAlive()) { + this.flushLiveApplyUpdate(draft.colId); } else { - this.cancelLivePreviewUpdate(draft.colId); + this.cancelLiveApplyUpdate(draft.colId); } if (this.openDialogsByColId.get(draft.colId)?.dialog === dialog) { this.openDialogsByColId.delete(draft.colId); @@ -643,29 +645,29 @@ export class CalculatedColumnsService extends BeanStub implements NamedBean, ICa dialog.addEventListener('destroyed', () => this.destroyBean(form)); } - private scheduleLivePreviewUpdate(draft: CalculatedColumnDraft, mapper: CalculatedColumnReferenceMapper): void { + private scheduleLiveApplyUpdate(draft: CalculatedColumnDraft, mapper: CalculatedColumnReferenceMapper): void { const colId = draft.colId; - this.pendingLivePreviewUpdatesByColId.set(colId, { draft, mapper }); - if (this.scheduledLivePreviewColIds.has(colId)) { + this.pendingLiveApplyUpdatesByColId.set(colId, { draft, mapper }); + if (this.scheduledLiveApplyColIds.has(colId)) { return; } // Coalesce keystrokes into one column rebuild per frame; a cancelled update leaves the frame // scheduled but with no pending entry, so it harmlessly no-ops. - this.scheduledLivePreviewColIds.add(colId); + this.scheduledLiveApplyColIds.add(colId); _requestAnimationFrame(this.beans, () => { - this.scheduledLivePreviewColIds.delete(colId); - this.flushLivePreviewUpdate(colId); + this.scheduledLiveApplyColIds.delete(colId); + this.flushLiveApplyUpdate(colId); }); } - private flushLivePreviewUpdate(colId: string): void { - const pending = this.pendingLivePreviewUpdatesByColId.get(colId); + private flushLiveApplyUpdate(colId: string): void { + const pending = this.pendingLiveApplyUpdatesByColId.get(colId); if (pending === undefined) { return; } - this.pendingLivePreviewUpdatesByColId.delete(colId); + this.pendingLiveApplyUpdatesByColId.delete(colId); const { draft, mapper } = pending; const { colId: _, ...update } = this.toColDef({ ...draft, @@ -674,8 +676,8 @@ export class CalculatedColumnsService extends BeanStub implements NamedBean, ICa this.updateCalculatedColumn(colId, update, false); } - private cancelLivePreviewUpdate(colId: string): void { - this.pendingLivePreviewUpdatesByColId.delete(colId); + private cancelLiveApplyUpdate(colId: string): void { + this.pendingLiveApplyUpdatesByColId.delete(colId); } private getDefaultDraft(): Omit { diff --git a/testing/behavioural/src/benchmarks/calculated-live-preview.bench.ts b/testing/behavioural/src/benchmarks/calculated-live-preview.bench.ts new file mode 100644 index 00000000000..1a8997a8a6c --- /dev/null +++ b/testing/behavioural/src/benchmarks/calculated-live-preview.bench.ts @@ -0,0 +1,132 @@ +import { bench, suite } from 'vitest'; + +import type { GridApi, GridOptions } from 'ag-grid-community'; +import { ClientSideRowModelModule, RowApiModule, ValidationModule } from 'ag-grid-community'; +import { CalculatedColumnsModule, ColumnMenuModule, FormulaModule, RowGroupingModule } from 'ag-grid-enterprise'; + +import { TestGridsManager } from '../test-utils'; + +// Measures the live-preview keystroke flush WORK (rebuildCols + CSRM refreshModel + formula cache +// wipe + viewport re-evaluation). requestAnimationFrame is overridden to fire synchronously so the +// bench captures the flush body rather than the ~16ms frame wait. + +const modules = [ + ClientSideRowModelModule, + RowApiModule, + CalculatedColumnsModule, + FormulaModule, + ColumnMenuModule, + RowGroupingModule, + ValidationModule, +]; + +// jsdom has no layout: the dialog's centering reads offsetParent, so route it to parentElement +// (same polyfill the calculated-columns behavioural tests use). +Object.defineProperty(HTMLElement.prototype, 'offsetParent', { + configurable: true, + get(this: HTMLElement) { + if (this.closest('.ag-measurement-container')) { + return null; + } + return this.parentElement; + }, +}); + +// Synchronous rAF: the live-preview scheduler coalesces per frame; firing inline makes each +// keystroke's flush run synchronously inside the input event so the bench measures only the work. +window.requestAnimationFrame = ((cb: FrameRequestCallback) => { + cb(0); + return 0; +}) as typeof window.requestAnimationFrame; + +const VIEWPORT_ROWS = 30; + +const buildRows = (n: number) => { + const rows: { id: string; revenue: number; cost: number; region: string }[] = []; + for (let i = 0; i < n; ++i) { + rows.push({ id: `r${i}`, revenue: (i * 37) % 1000, cost: (i * 13) % 500, region: `R${i % 20}` }); + } + return rows; +}; + +const baseOptions = (rows: number, sortOnMargin: boolean, extraCols = 0, grouped = false): GridOptions => { + const columnDefs: GridOptions['columnDefs'] = [ + { field: 'region', rowGroup: grouped, hide: grouped }, + { field: 'revenue', aggFunc: grouped ? 'sum' : undefined }, + { field: 'cost', aggFunc: grouped ? 'sum' : undefined }, + { colId: 'profit', calculatedExpression: '[revenue] - [cost]', cellDataType: 'number' }, + { + colId: 'margin', + calculatedExpression: '[profit] / ([revenue] + 1)', + cellDataType: 'number', + sortable: true, + sort: sortOnMargin ? 'asc' : null, + }, + ]; + for (let i = 0; i < extraCols; ++i) { + columnDefs.push({ colId: `x${i}`, field: 'revenue', headerName: `x${i}` }); + } + return { + getRowId: (params) => params.data?.id, + rowData: buildRows(rows), + calculatedColumns: { applyMode: 'live' }, + columnDefs, + }; +}; + +const typeExpression = (expression: string): void => { + const input = document.querySelector('.ag-calculated-column-form textarea'); + if (!input) { + throw new Error('expression editor not found'); + } + input.value = expression; + input.dispatchEvent(new Event('input', { bubbles: true })); +}; + +// What the renderer does after a flush: re-read the visible cells of both calc columns. +const readViewport = (api: GridApi): void => { + for (let i = 0; i < VIEWPORT_ROWS; ++i) { + const rowNode = api.getDisplayedRowAtIndex(i); + if (!rowNode) { + break; + } + api.getCellValue({ rowNode, colKey: 'profit', useFormatter: false }); + api.getCellValue({ rowNode, colKey: 'margin', useFormatter: false }); + } +}; + +suite('calculated columns — live preview keystroke flush (synchronous rAF)', () => { + let gridId = 0; + const benchKeystroke = (name: string, rows: number, sortOnMargin: boolean, extraCols = 0, grouped = false) => { + const id = `LP${++gridId}`; + const gridsManager = new TestGridsManager({ benchmark: true, modules }); + let api!: GridApi; + let iter = 0; + bench( + name, + () => { + // Alternate so every flush is a real expression change on the chained `profit` column. + typeExpression(iter++ & 1 ? '[revenue] - [cost] + 1' : '[revenue] - [cost] + 2'); + readViewport(api); + }, + { + throws: true, + setup: async () => { + gridsManager.reset(); + iter = 0; + api = gridsManager.createGrid(id, baseOptions(rows, sortOnMargin, extraCols, grouped)); + api.openCalculatedColumnDialog('profit'); + await new Promise((resolve) => setTimeout(resolve, 1)); + }, + } + ); + }; + + benchKeystroke('keystroke flush — 1k rows, chained calc, no sort', 1_000, false); + benchKeystroke('keystroke flush — 10k rows, chained calc, no sort', 10_000, false); + benchKeystroke('keystroke flush — 100k rows, chained calc, no sort', 100_000, false); + benchKeystroke('keystroke flush — 10k rows, chained calc, SORTED on dependent margin', 10_000, true); + benchKeystroke('keystroke flush — 100k rows, chained calc, SORTED on dependent margin', 100_000, true); + benchKeystroke('keystroke flush — 100k rows, 200 extra cols, no sort', 100_000, false, 200); + benchKeystroke('keystroke flush — 100k rows, GROUPED region + sum aggs', 100_000, false, 0, true); +}); diff --git a/testing/behavioural/src/formulas/calculated-columns-ordering.test.ts b/testing/behavioural/src/formulas/calculated-columns-ordering.test.ts index 8adc674cd07..33268002a0d 100644 --- a/testing/behavioural/src/formulas/calculated-columns-ordering.test.ts +++ b/testing/behavioural/src/formulas/calculated-columns-ordering.test.ts @@ -164,7 +164,9 @@ describe('calculated columns - display ordering', () => { await asyncSetTimeout(1); setExpression(expression); clickDialogButton('Apply'); - await asyncSetTimeout(1); + // Wait past the live-apply animation frame so no expression flush is in flight when the + // caller starts toggling columns (under the default 'live' mode Apply is a no-op). + await asyncSetTimeout(40); const added = order(api).filter((id) => !before.has(id)); expect(added).toHaveLength(1); return added[0]; @@ -383,12 +385,11 @@ describe('calculated columns - display ordering', () => { // === Rule 2: dialog add lands immediately after the anchor leaf ============================== - test('livePreview adds immediately and updates expression and title live', async () => { + test('live apply mode (default) adds immediately and updates expression and title live', async () => { const events: { type: string; expression?: string; oldExpression?: string; newExpression?: string }[] = []; const api = createGrid('calculated-live-preview-add', { rowData: [{ id: 'r1', age: 23 }], columnDefs: [{ field: 'age' }], - calculatedColumns: { livePreview: true }, onCalculatedColumnCreated: (event) => events.push({ type: event.type, expression: event.expression }), onCalculatedColumnExpressionChanged: (event) => events.push({ @@ -432,11 +433,10 @@ describe('calculated columns - display ordering', () => { ]); }); - test('livePreview commits invalid expressions without marking the editor invalid', async () => { + test('live apply mode commits invalid expressions without marking the editor invalid', async () => { const api = createGrid('calculated-live-preview-invalid-expression', { rowData: [{ id: 'r1', age: 23 }], columnDefs: [{ field: 'age' }], - calculatedColumns: { livePreview: true }, }); enableOffsetParentPolyfill(); @@ -501,7 +501,7 @@ describe('calculated columns - display ordering', () => { setExpression('[Age] * 2'); clickDialogButton('Apply'); - await asyncSetTimeout(1); + await asyncSetTimeout(40); await new GridColumns(api, 'dialog add preserves unpinned state after calculated column add').checkColumns(` CENTER diff --git a/testing/behavioural/src/formulas/calculated-columns.test.ts b/testing/behavioural/src/formulas/calculated-columns.test.ts index a8a8ee786d0..81ae398de68 100644 --- a/testing/behavioural/src/formulas/calculated-columns.test.ts +++ b/testing/behavioural/src/formulas/calculated-columns.test.ts @@ -740,6 +740,7 @@ describe('ag-grid calculated columns', () => { test('reset column state removes dynamic calculated columns and restores provided calculated columns', async () => { const removed = vi.fn(); const api = createGrid('calculated-reset-column-state', { + calculatedColumns: { applyMode: 'deferred' }, rowData: [{ id: 'r1', revenue: 10, cost: 3 }], columnDefs: [ { field: 'revenue' }, @@ -821,6 +822,7 @@ describe('ag-grid calculated columns', () => { test('edit dialog updates calculated column cellDataType without keeping stale boolean renderer', async () => { const api = createGrid('calculated-grid-api-cell-data-type', { + calculatedColumns: { applyMode: 'deferred' }, rowData: [{ id: 'r1', revenue: 10, cost: 3 }], columnDefs: [ { field: 'revenue' }, @@ -1212,6 +1214,50 @@ describe('ag-grid calculated columns', () => { expect(api.getCellValue({ rowNode: firstRow, colKey: 'doubleProfit', useFormatter: false })).toBe(24); }); + test('live apply typing does not refetch server-side rows', async () => { + const rowData = [ + { id: 'r1', revenue: 10, cost: 3 }, + { id: 'r2', revenue: 20, cost: 8 }, + ]; + let getRowsCalls = 0; + const api = createGrid('calculated-live-apply-ssrm', { + rowModelType: 'serverSide', + serverSideDatasource: { + getRows: (params: any) => { + getRowsCalls++; + params.success({ + rowData: rowData.slice(params.request.startRow, params.request.endRow), + rowCount: rowData.length, + }); + }, + }, + columnDefs: [ + { field: 'revenue' }, + { field: 'cost' }, + { colId: 'profit', calculatedExpression: '[revenue] - [cost]', cellDataType: 'number' }, + ], + }); + await waitForFirstRow(api); + const callsAfterLoad = getRowsCalls; + expect(callsAfterLoad).toBeGreaterThan(0); + + enableOffsetParentPolyfill(); + api.openCalculatedColumnDialog('profit'); + await asyncSetTimeout(1); + + // Each keystroke flushes on an animation frame; wait past each flush. + setExpression('[revenue] - [cost] + 1'); + await asyncSetTimeout(40); + setExpression('[revenue] * [cost]'); + await asyncSetTimeout(40); + setExpression('[revenue] + [cost]'); + await asyncSetTimeout(40); + + const firstRow = api.getDisplayedRowAtIndex(0)!; + expect(api.getCellValue({ rowNode: firstRow, colKey: 'profit', useFormatter: false })).toBe(13); + expect(getRowsCalls).toBe(callsAfterLoad); + }); + test('server-side store updates invalidate calculated column caches', async () => { let rowData = [{ id: 'r1', revenue: 10, cost: 3 }]; const api = createGrid('calculated-server-side-cache', { @@ -1292,6 +1338,7 @@ describe('ag-grid calculated columns', () => { const costColId = 'server-cost-81f3431b-e4aa-4ef8-bef0'; const created = vi.fn(); const api = createGrid('calculated-dialog-references', { + calculatedColumns: { applyMode: 'deferred' }, rowData: [{ id: 'r1', revenue: 10, cost: 3 }], columnDefs: [ { field: 'revenue', colId: revenueColId, headerName: 'Revenue' }, @@ -1357,6 +1404,7 @@ describe('ag-grid calculated columns', () => { test('clearing the expression shows an empty-expression message, not the formula error', async () => { const api = createGrid('calculated-empty-expression', { + calculatedColumns: { applyMode: 'deferred' }, rowData: [{ id: 'r1', revenue: 10, cost: 3 }], columnDefs: [{ field: 'revenue' }, { field: 'cost' }], }); @@ -1420,6 +1468,7 @@ describe('ag-grid calculated columns', () => { test('dialog accepts column references in any case', async () => { const api = createGrid('calculated-dialog-case-insensitive-references', { + calculatedColumns: { applyMode: 'deferred' }, rowData: [{ id: 'r1', revenue: 10, cost: 3 }], columnDefs: [{ field: 'revenue' }, { field: 'cost' }], }); @@ -1440,6 +1489,7 @@ describe('ag-grid calculated columns', () => { test('dialog operator suggestions replace existing operators near the caret', async () => { const api = createGrid('calculated-dialog-operator-replacement', { + calculatedColumns: { applyMode: 'deferred' }, rowData: [{ id: 'r1', age: 23, medals: 8 }], columnDefs: [{ field: 'age' }, { field: 'medals' }], }); @@ -1478,6 +1528,7 @@ describe('ag-grid calculated columns', () => { test('dialog picker keeps button focus until suggestion is accepted', async () => { const api = createGrid('calculated-dialog-picker-focus', { + calculatedColumns: { applyMode: 'deferred' }, rowData: [{ id: 'r1', age: 23, medals: 8 }], columnDefs: [{ field: 'age' }, { field: 'medals' }], }); @@ -1526,6 +1577,7 @@ describe('ag-grid calculated columns', () => { }; const columnDefs: ColGroupDef[] = [year2025, year2026]; const api = createGrid('calculated-dialog-group-no-mutation', { + calculatedColumns: { applyMode: 'deferred' }, rowData: [{ id: 'r1', revenue2025: 10, cost2025: 3, revenue2026: 20, cost2026: 8 }], columnDefs, }); @@ -1573,6 +1625,7 @@ describe('ag-grid calculated columns', () => { test('dialog inserts calculated columns after generated auto group columns in visible order', async () => { const api = createGrid('calculated-dialog-auto-group-order', { + calculatedColumns: { applyMode: 'deferred' }, rowData: [{ id: 'r1', productType: 'A', revenue: 10, cost: 3 }], columnDefs: [{ field: 'productType', rowGroup: true, hide: true }, { field: 'revenue' }, { field: 'cost' }], }); @@ -1629,7 +1682,8 @@ describe('ag-grid calculated columns', () => { await asyncSetTimeout(1); setExpression('[Revenue] - [Cost]'); clickDialogButton('Apply'); - await asyncSetTimeout(1); + // Wait past the live-apply animation frame so no flush is in flight during the toggles below. + await asyncSetTimeout(40); await new GridColumns(api, 'auto-group toggle - after add').checkColumns(` CENTER ├── ag-Grid-AutoColumn "Group" width:200 @@ -1661,6 +1715,7 @@ describe('ag-grid calculated columns', () => { test('calc col anchored to the first of two auto-group cols after a grouping toggle', async () => { const api = createGrid('calculated-autogroup-toggle-multi', { + calculatedColumns: { applyMode: 'deferred' }, groupDisplayType: 'multipleColumns', rowData: [{ id: 'r1', productType: 'A', country: 'UK', revenue: 10, cost: 3 }], columnDefs: [ @@ -1719,6 +1774,7 @@ describe('ag-grid calculated columns', () => { test('dialog inserts calculated columns after the clicked generated auto group column in multiple-columns mode', async () => { const api = createGrid('calculated-dialog-multiple-auto-group-order', { + calculatedColumns: { applyMode: 'deferred' }, groupDisplayType: 'multipleColumns', rowData: [{ id: 'r1', productType: 'A', country: 'UK', revenue: 10, cost: 3 }], columnDefs: [ @@ -1779,7 +1835,8 @@ describe('ag-grid calculated columns', () => { setExpression('[Revenue] - [Cost]'); clickDialogButton('Apply'); - await asyncSetTimeout(1); + // Wait past the live-apply animation frame so no flush is in flight during the moves below. + await asyncSetTimeout(40); // Placed immediately after its anchor on creation. expect(api.getAllDisplayedColumns().map((column) => column.getColId())).toEqual([ @@ -1834,7 +1891,8 @@ describe('ag-grid calculated columns', () => { await asyncSetTimeout(1); setExpression('[Revenue] - [Cost]'); clickDialogButton('Apply'); - await asyncSetTimeout(1); + // Wait past the live-apply animation frame so each add's flush lands before the next step. + await asyncSetTimeout(40); showColumnMenu(api, 'ag-Grid-AutoColumn-country'); await asyncSetTimeout(10); @@ -1842,7 +1900,7 @@ describe('ag-grid calculated columns', () => { await asyncSetTimeout(1); setExpression('[Revenue] + [Cost]'); clickDialogButton('Apply'); - await asyncSetTimeout(1); + await asyncSetTimeout(40); // Adding the second column must not displace the first from its own anchor. expect(api.getAllDisplayedColumns().map((column) => column.getColId())).toEqual([ @@ -2123,6 +2181,7 @@ describe('ag-grid calculated columns', () => { const changed = vi.fn(); const removed = vi.fn(); const api = createGrid('calculated-ui-events', { + calculatedColumns: { applyMode: 'deferred' }, rowData: [{ id: 'r1', revenue: 10, cost: 3 }], columnDefs: [ { field: 'revenue', headerName: 'Revenue' }, @@ -2323,6 +2382,7 @@ describe('ag-grid calculated columns', () => { test('dialog type list contains the default data types only', async () => { const api = createGrid('calculated-dialog-types', { + calculatedColumns: { applyMode: 'deferred' }, rowData: [{ id: 'r1', revenue: 10, cost: 3 }], columnDefs: [{ field: 'revenue' }, { field: 'cost' }], }); @@ -2448,6 +2508,7 @@ describe('ag-grid calculated columns', () => { test('dialog validates formula syntax and function names before apply', async () => { const api = createGrid('calculated-dialog-validation', { + calculatedColumns: { applyMode: 'deferred' }, rowData: [{ id: 'r1', revenue: 10, cost: 3 }], columnDefs: [{ field: 'revenue' }, { field: 'cost' }], }); @@ -2575,6 +2636,7 @@ describe('ag-grid calculated columns', () => { test('calculated columns add calculated column classes and edit highlighting by default', async () => { const api = createGrid('calculated-column-classes', { + calculatedColumns: { applyMode: 'deferred' }, rowData: [{ id: 'r1', revenue: 10, cost: 3 }], columnDefs: [ { field: 'revenue' }, @@ -2692,6 +2754,7 @@ describe('ag-grid calculated columns', () => { test('adding a calculated column does not highlight the new column', async () => { const api = createGrid('calculated-column-add-no-highlight', { + calculatedColumns: { applyMode: 'deferred' }, rowData: [{ id: 'r1', revenue: 10, cost: 3 }], columnDefs: [{ field: 'revenue' }, { field: 'cost' }], }); @@ -2717,6 +2780,7 @@ describe('ag-grid calculated columns', () => { test('multiple open calculated column dialogs highlight each edited column', async () => { const api = createGrid('calculated-column-multi-highlight', { + calculatedColumns: { applyMode: 'deferred' }, rowData: [{ id: 'r1', revenue: 10, cost: 3 }], columnDefs: [ { field: 'revenue' }, From db992a866af4cd09784e16619992bd9865aaf214 Mon Sep 17 00:00:00 2001 From: Guilherme Lopes Date: Wed, 10 Jun 2026 12:32:14 -0300 Subject: [PATCH 3/5] AG-17408 - [Calculated Columns] - API Cleanup (#14027) --- .../api-documentation/grid-api/api.json | 15 ---- .../docs/calculated-columns/index.mdoc | 6 -- packages/ag-grid-community/src/api/gridApi.ts | 11 --- .../src/api/gridApiFunctions.ts | 5 -- .../ag-grid-community/src/main-internal.ts | 1 - .../calculatedColumns/calculatedColumnsApi.ts | 5 -- .../calculatedColumnsModule.ts | 8 +-- .../calculated-live-preview.bench.ts | 13 +++- .../src/formulas/calculated-columns.test.ts | 68 +++++-------------- 9 files changed, 30 insertions(+), 102 deletions(-) delete mode 100644 packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnsApi.ts diff --git a/documentation/ag-grid-docs/src/content/api-documentation/grid-api/api.json b/documentation/ag-grid-docs/src/content/api-documentation/grid-api/api.json index abdf235c16b..1bc4f3f1710 100644 --- a/documentation/ag-grid-docs/src/content/api-documentation/grid-api/api.json +++ b/documentation/ag-grid-docs/src/content/api-documentation/grid-api/api.json @@ -1397,20 +1397,5 @@ "url": "./notes/#api-reference" } } - }, - "calculatedColumns": { - "meta": { - "displayName": "Calculated Columns", - "page": { - "name": "Calculated Columns", - "url": "./calculated-columns/" - } - }, - "openCalculatedColumnDialog": { - "more": { - "name": "Calculated Columns API", - "url": "./calculated-columns/#api" - } - } } } diff --git a/documentation/ag-grid-docs/src/content/docs/calculated-columns/index.mdoc b/documentation/ag-grid-docs/src/content/docs/calculated-columns/index.mdoc index 5beb33bbb81..4c69a77bf35 100644 --- a/documentation/ag-grid-docs/src/content/docs/calculated-columns/index.mdoc +++ b/documentation/ag-grid-docs/src/content/docs/calculated-columns/index.mdoc @@ -189,8 +189,6 @@ api.setGridOption( ) ); -api.openCalculatedColumnDialog('profitMargin'); - api.setGridOption( 'columnDefs', (api.getColumnDefs() ?? []).filter(colDef => colDef.colId !== 'profitMargin') @@ -205,10 +203,6 @@ Calculated Column events also use stored `colId` references in their expression {% apiDocumentation source="column-properties/properties.json" section="calculatedColumns" names=["calculatedExpression"] /%} -### Grid API - -{% apiDocumentation source="grid-api/api.json" section="calculatedColumns" names=["openCalculatedColumnDialog"] /%} - ### Grid Options {% apiDocumentation source="grid-options/properties.json" section="columns" names=["calculatedColumns"] /%} diff --git a/packages/ag-grid-community/src/api/gridApi.ts b/packages/ag-grid-community/src/api/gridApi.ts index 842d3a67d39..6feabcc471e 100644 --- a/packages/ag-grid-community/src/api/gridApi.ts +++ b/packages/ag-grid-community/src/api/gridApi.ts @@ -1782,16 +1782,6 @@ export interface _FormulaGridApi { refreshFormulas(rowNode?: IRowNode | string): boolean; } -/** @internal AG_GRID_INTERNAL - Not for public use. Can change / be removed at any time. */ -export interface _CalculatedColumnsGridApi { - /** - * Open the Calculated Column dialog for an existing calculated column. - * No-op if the supplied column key does not resolve to a calculated column. - * @agModule `CalculatedColumnsModule` - */ - openCalculatedColumnDialog(column: ColKey): void; -} - /** @internal AG_GRID_INTERNAL - Not for public use. Can change / be removed at any time. */ export interface _MasterDetailGridApi { /** @@ -2050,7 +2040,6 @@ export interface GridApi _ColumnChooserGridApi, _MasterDetailGridApi, _FormulaGridApi, - _CalculatedColumnsGridApi, _ExcelExportGridApi, _ClipboardGridApi, _GridChartsGridApi, diff --git a/packages/ag-grid-community/src/api/gridApiFunctions.ts b/packages/ag-grid-community/src/api/gridApiFunctions.ts index 80f9bf9b680..761b892f2ad 100644 --- a/packages/ag-grid-community/src/api/gridApiFunctions.ts +++ b/packages/ag-grid-community/src/api/gridApiFunctions.ts @@ -5,7 +5,6 @@ import type { _AggregationGridApi, _AiToolkitGridApi, _BatchEditApi, - _CalculatedColumnsGridApi, _CellSelectionGridApi, _ClientSideRowModelGridApi, _ClipboardGridApi, @@ -378,10 +377,6 @@ export const gridApiFunctionsMap: Record = refreshFormulas: 0, }), - ...mod<_CalculatedColumnsGridApi>('CalculatedColumns', { - openCalculatedColumnDialog: 0, - }), - ...mod<_ContextMenuGridApi>('ContextMenu', { showContextMenu: 0, }), diff --git a/packages/ag-grid-community/src/main-internal.ts b/packages/ag-grid-community/src/main-internal.ts index cce64fbce03..ec8dfed88d6 100644 --- a/packages/ag-grid-community/src/main-internal.ts +++ b/packages/ag-grid-community/src/main-internal.ts @@ -9,7 +9,6 @@ export type { _AggregationGridApi, _AiToolkitGridApi, _BatchEditApi, - _CalculatedColumnsGridApi, _CellSelectionGridApi, _ClientSideRowModelGridApi, _ClipboardGridApi, diff --git a/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnsApi.ts b/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnsApi.ts deleted file mode 100644 index a44ea0117c7..00000000000 --- a/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnsApi.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { BeanCollection, ColKey } from 'ag-grid-community'; - -export function openCalculatedColumnDialog(beans: BeanCollection, column: ColKey): void { - beans.calculatedColsSvc?.openCalculatedColumnDialog(beans.colModel.getCol(column), 'edit', false); -} diff --git a/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnsModule.ts b/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnsModule.ts index 99866ee166b..345275590a8 100644 --- a/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnsModule.ts +++ b/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnsModule.ts @@ -1,22 +1,18 @@ -import type { _CalculatedColumnsGridApi, _ModuleWithApi } from 'ag-grid-community'; +import type { _ModuleWithoutApi } from 'ag-grid-community'; import { ColumnApiModule, TooltipModule, _PopupModule } from 'ag-grid-community'; import { FormulaModule } from '../formula/formulaModule'; import { VERSION } from '../version'; import calculatedColumnsCSS from './calculatedColumns.css'; -import { openCalculatedColumnDialog } from './calculatedColumnsApi'; import { CalculatedColumnsService } from './calculatedColumnsService'; /** * @feature Calculated Columns */ -export const CalculatedColumnsModule: _ModuleWithApi<_CalculatedColumnsGridApi> = { +export const CalculatedColumnsModule: _ModuleWithoutApi = { moduleName: 'CalculatedColumns', version: VERSION, beans: [CalculatedColumnsService], - apiFunctions: { - openCalculatedColumnDialog, - }, dependsOn: [FormulaModule, _PopupModule, ColumnApiModule, TooltipModule], icons: { calculatedColumnAdd: 'plus', diff --git a/testing/behavioural/src/benchmarks/calculated-live-preview.bench.ts b/testing/behavioural/src/benchmarks/calculated-live-preview.bench.ts index 1a8997a8a6c..9921ca7f1cc 100644 --- a/testing/behavioural/src/benchmarks/calculated-live-preview.bench.ts +++ b/testing/behavioural/src/benchmarks/calculated-live-preview.bench.ts @@ -1,7 +1,7 @@ import { bench, suite } from 'vitest'; import type { GridApi, GridOptions } from 'ag-grid-community'; -import { ClientSideRowModelModule, RowApiModule, ValidationModule } from 'ag-grid-community'; +import { CellApiModule, ClientSideRowModelModule, RowApiModule, ValidationModule } from 'ag-grid-community'; import { CalculatedColumnsModule, ColumnMenuModule, FormulaModule, RowGroupingModule } from 'ag-grid-enterprise'; import { TestGridsManager } from '../test-utils'; @@ -11,6 +11,7 @@ import { TestGridsManager } from '../test-utils'; // bench captures the flush body rather than the ~16ms frame wait. const modules = [ + CellApiModule, ClientSideRowModelModule, RowApiModule, CalculatedColumnsModule, @@ -115,7 +116,15 @@ suite('calculated columns — live preview keystroke flush (synchronous rAF)', ( gridsManager.reset(); iter = 0; api = gridsManager.createGrid(id, baseOptions(rows, sortOnMargin, extraCols, grouped)); - api.openCalculatedColumnDialog('profit'); + api.showColumnMenu('profit'); + await new Promise((resolve) => setTimeout(resolve, 10)); + const menuItem = Array.from(document.querySelectorAll('.ag-menu-option-text')) + .find((element) => element.textContent?.trim() === 'Edit Calculated Column') + ?.closest('.ag-menu-option'); + if (!menuItem) { + throw new Error('Edit Calculated Column menu item not found'); + } + menuItem.click(); await new Promise((resolve) => setTimeout(resolve, 1)); }, } diff --git a/testing/behavioural/src/formulas/calculated-columns.test.ts b/testing/behavioural/src/formulas/calculated-columns.test.ts index 81ae398de68..9edfb44ee12 100644 --- a/testing/behavioural/src/formulas/calculated-columns.test.ts +++ b/testing/behavioural/src/formulas/calculated-columns.test.ts @@ -216,6 +216,13 @@ describe('ag-grid calculated columns', () => { menuItem.click(); } + async function openEditDialogViaMenu(api: { showColumnMenu(colKey: string): void }, colKey: string): Promise { + showColumnMenu(api, colKey); + await asyncSetTimeout(10); + await clickColumnMenuItem('Edit Calculated Column'); + await asyncSetTimeout(1); + } + function getCalculatedColumnDialog(): HTMLElement { const dialog = document.querySelector('.ag-calculated-column-form'); expect(dialog).toBeTruthy(); @@ -767,8 +774,7 @@ describe('ag-grid calculated columns', () => { ]); const columnState = api.getColumnState(); - api.openCalculatedColumnDialog('profit'); - await asyncSetTimeout(1); + await openEditDialogViaMenu(api, 'profit'); setExpression('[Revenue] * [Cost]'); clickDialogButton('Apply'); await asyncSetTimeout(1); @@ -836,8 +842,7 @@ describe('ag-grid calculated columns', () => { }); await asyncSetTimeout(1); - api.openCalculatedColumnDialog('profitable'); - await asyncSetTimeout(1); + await openEditDialogViaMenu(api, 'profitable'); setExpression('[revenue] > [cost]'); await selectDataType('Boolean'); clickDialogButton('Apply'); @@ -845,8 +850,7 @@ describe('ag-grid calculated columns', () => { expect(api.getColumn('profitable')!.getColDef().cellRenderer).toBe('agCheckboxCellRenderer'); - api.openCalculatedColumnDialog('profitable'); - await asyncSetTimeout(1); + await openEditDialogViaMenu(api, 'profitable'); setExpression('IF([revenue] > [cost], "yes", "no")'); await selectDataType('Text'); clickDialogButton('Apply'); @@ -1241,9 +1245,7 @@ describe('ag-grid calculated columns', () => { const callsAfterLoad = getRowsCalls; expect(callsAfterLoad).toBeGreaterThan(0); - enableOffsetParentPolyfill(); - api.openCalculatedColumnDialog('profit'); - await asyncSetTimeout(1); + await openEditDialogViaMenu(api, 'profit'); // Each keystroke flushes on an animation frame; wait past each flush. setExpression('[revenue] - [cost] + 1'); @@ -2799,9 +2801,8 @@ describe('ag-grid calculated columns', () => { }); await asyncSetTimeout(1); - api.openCalculatedColumnDialog('profit'); - api.openCalculatedColumnDialog('margin'); - await asyncSetTimeout(1); + await openEditDialogViaMenu(api, 'profit'); + await openEditDialogViaMenu(api, 'margin'); const gridDiv = document.querySelector('#calculated-column-multi-highlight')!; expect(gridDiv.querySelector('[col-id="profit"].ag-header-cell')).toHaveClass( @@ -2835,43 +2836,9 @@ describe('ag-grid calculated columns', () => { clickDialogButton('Cancel'); }); - test('openCalculatedColumnDialog opens the edit dialog for an existing calculated column', async () => { - const api = createGrid('calculated-column-open-dialog-api', { - rowData: [{ id: 'r1', revenue: 10, cost: 3 }], - columnDefs: [ - { field: 'revenue' }, - { field: 'cost' }, - { - colId: 'profit', - headerName: 'Profit', - calculatedExpression: '[revenue] - [cost]', - }, - ], - }); - await asyncSetTimeout(1); - - api.openCalculatedColumnDialog('profit'); - await asyncSetTimeout(1); - - const dialog = getCalculatedColumnDialog(); - expect(dialog).toBeTruthy(); - expect(dialog.querySelector('input')!.value).toBe('Profit'); - expect(document.activeElement?.closest('.ag-dialog')).toBeNull(); - expect(document.activeElement?.closest('[col-id="profit"].ag-header-cell')).toBeNull(); - - const gridDiv = document.querySelector('#calculated-column-open-dialog-api')!; - expect(gridDiv.querySelector('[col-id="profit"].ag-header-cell')).toHaveClass( - 'ag-calculated-column-highlighted' - ); - expect(gridDiv.querySelector('[row-index="0"] [col-id="profit"]')).toHaveClass( - 'ag-calculated-column-highlighted' - ); - - clickDialogButton('Cancel'); - }); - - test('openCalculatedColumnDialog does not open duplicate dialogs for the same column', async () => { + test('edit menu does not open duplicate dialogs for the same column', async () => { const api = createGrid('calculated-column-open-dialog-once', { + calculatedColumns: { applyMode: 'deferred' }, rowData: [{ id: 'r1', revenue: 10, cost: 3 }], columnDefs: [ { field: 'revenue' }, @@ -2881,9 +2848,8 @@ describe('ag-grid calculated columns', () => { }); await asyncSetTimeout(1); - api.openCalculatedColumnDialog('profit'); - api.openCalculatedColumnDialog('profit'); - await asyncSetTimeout(1); + await openEditDialogViaMenu(api, 'profit'); + await openEditDialogViaMenu(api, 'profit'); expect(document.querySelectorAll('.ag-calculated-column-form')).toHaveLength(1); From 61d5c02d34bdb68d39a4448f94970f859b211569 Mon Sep 17 00:00:00 2001 From: seanlandsman Date: Wed, 10 Jun 2026 16:33:03 +0100 Subject: [PATCH 4/5] Security updates (#14028) --- .github/workflows/security.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 098cdc6529c..bd853f280df 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -123,7 +123,6 @@ jobs: exit 1 sast-code-scan: - needs: [determine-threshold] runs-on: ubuntu-latest steps: - name: Checkout @@ -138,7 +137,7 @@ jobs: continue-on-error: true with: snyk_token: ${{ secrets.SNYK_TOKEN }} - severity_threshold: ${{ needs.determine-threshold.outputs.severity }} + severity_threshold: high output_dir: ${{ env.OUTPUT_DIR }} report_file: ${{ env.REPORT_FILE }} From b79b6714c54daaaf3028969f778fd96c6089171e Mon Sep 17 00:00:00 2001 From: Guilherme Lopes Date: Wed, 10 Jun 2026 14:25:16 -0300 Subject: [PATCH 5/5] AG-17292 - [Calculated Columns] - UI Improvements (#14030) --- community-modules/locale/src/ar-EG.ts | 1 + community-modules/locale/src/bg-BG.ts | 1 + community-modules/locale/src/cs-CZ.ts | 1 + community-modules/locale/src/da-DK.ts | 1 + community-modules/locale/src/de-DE.ts | 1 + community-modules/locale/src/el-GR.ts | 1 + community-modules/locale/src/en-US.ts | 1 + community-modules/locale/src/es-ES.ts | 1 + community-modules/locale/src/fa-IR.ts | 1 + community-modules/locale/src/fi-FI.ts | 1 + community-modules/locale/src/fr-FR.ts | 1 + community-modules/locale/src/he-IL.ts | 1 + community-modules/locale/src/hr-HR.ts | 1 + community-modules/locale/src/hu-HU.ts | 1 + community-modules/locale/src/it-IT.ts | 1 + community-modules/locale/src/ja-JP.ts | 1 + community-modules/locale/src/ko-KR.ts | 1 + community-modules/locale/src/nb-NO.ts | 1 + community-modules/locale/src/nl-NL.ts | 1 + community-modules/locale/src/pl-PL.ts | 1 + community-modules/locale/src/pt-BR.ts | 1 + community-modules/locale/src/pt-PT.ts | 1 + community-modules/locale/src/ro-RO.ts | 1 + community-modules/locale/src/sk-SK.ts | 1 + community-modules/locale/src/sv-SE.ts | 1 + community-modules/locale/src/tr-TR.ts | 1 + community-modules/locale/src/uk-UA.ts | 1 + community-modules/locale/src/ur-PK.ts | 1 + community-modules/locale/src/vi-VN.ts | 1 + community-modules/locale/src/zh-CN.ts | 1 + community-modules/locale/src/zh-HK.ts | 1 + community-modules/locale/src/zh-TW.ts | 1 + .../base/parts/_calculated-columns.scss | 29 ++++++----- .../src/agStack/agGroupComponent.ts | 6 +++ .../calculatedColumns/calculatedColumnForm.ts | 50 ++++++++++++++----- .../calculatedColumns/calculatedColumns.css | 30 +++++------ .../calculatedColumnsService.ts | 2 +- 37 files changed, 106 insertions(+), 43 deletions(-) diff --git a/community-modules/locale/src/ar-EG.ts b/community-modules/locale/src/ar-EG.ts index f25ef515608..8831c7f74a9 100644 --- a/community-modules/locale/src/ar-EG.ts +++ b/community-modules/locale/src/ar-EG.ts @@ -829,6 +829,7 @@ export const AG_GRID_LOCALE_EG = { calculatedColumnType: 'النوع', calculatedColumnExpression: 'التعبير', calculatedColumnExpressionPlaceholder: 'اكتب هنا', + calculatedColumnExpressionToolsLabel: 'إدراج', calculatedColumnColumns: 'أعمدة', calculatedColumnFunctions: 'الدوال', calculatedColumnOperators: 'المعاملات', diff --git a/community-modules/locale/src/bg-BG.ts b/community-modules/locale/src/bg-BG.ts index 667aa2086a7..df36114a5a6 100644 --- a/community-modules/locale/src/bg-BG.ts +++ b/community-modules/locale/src/bg-BG.ts @@ -835,6 +835,7 @@ export const AG_GRID_LOCALE_BG = { calculatedColumnType: 'Тип', calculatedColumnExpression: 'Израз', calculatedColumnExpressionPlaceholder: 'Въведете тук', + calculatedColumnExpressionToolsLabel: 'Вмъкване', calculatedColumnColumns: 'Колони', calculatedColumnFunctions: 'Функции', calculatedColumnOperators: 'Оператори', diff --git a/community-modules/locale/src/cs-CZ.ts b/community-modules/locale/src/cs-CZ.ts index fb62cfe311a..a60824188e1 100644 --- a/community-modules/locale/src/cs-CZ.ts +++ b/community-modules/locale/src/cs-CZ.ts @@ -830,6 +830,7 @@ export const AG_GRID_LOCALE_CZ = { calculatedColumnType: 'Typ', calculatedColumnExpression: 'Výraz', calculatedColumnExpressionPlaceholder: 'Pište zde', + calculatedColumnExpressionToolsLabel: 'Vložit', calculatedColumnColumns: 'Sloupce', calculatedColumnFunctions: 'Funkce', calculatedColumnOperators: 'Operátory', diff --git a/community-modules/locale/src/da-DK.ts b/community-modules/locale/src/da-DK.ts index f5b54e94efa..a0096d1cba6 100644 --- a/community-modules/locale/src/da-DK.ts +++ b/community-modules/locale/src/da-DK.ts @@ -833,6 +833,7 @@ export const AG_GRID_LOCALE_DK = { calculatedColumnType: 'Type', calculatedColumnExpression: 'Udtryk', calculatedColumnExpressionPlaceholder: 'Skriv her', + calculatedColumnExpressionToolsLabel: 'Indsæt', calculatedColumnColumns: 'Kolonner', calculatedColumnFunctions: 'Funktioner', calculatedColumnOperators: 'Operatorer', diff --git a/community-modules/locale/src/de-DE.ts b/community-modules/locale/src/de-DE.ts index 15d11c1cfe7..8712cbfc204 100644 --- a/community-modules/locale/src/de-DE.ts +++ b/community-modules/locale/src/de-DE.ts @@ -837,6 +837,7 @@ export const AG_GRID_LOCALE_DE = { calculatedColumnType: 'Typ', calculatedColumnExpression: 'Ausdruck', calculatedColumnExpressionPlaceholder: 'Hier eingeben', + calculatedColumnExpressionToolsLabel: 'Einfügen', calculatedColumnColumns: 'Spalten', calculatedColumnFunctions: 'Funktionen', calculatedColumnOperators: 'Operatoren', diff --git a/community-modules/locale/src/el-GR.ts b/community-modules/locale/src/el-GR.ts index 3d73528b569..e3663276ff9 100644 --- a/community-modules/locale/src/el-GR.ts +++ b/community-modules/locale/src/el-GR.ts @@ -836,6 +836,7 @@ export const AG_GRID_LOCALE_GR = { calculatedColumnType: 'Τύπος', calculatedColumnExpression: 'Έκφραση', calculatedColumnExpressionPlaceholder: 'Πληκτρολογήστε εδώ', + calculatedColumnExpressionToolsLabel: 'Εισαγωγή', calculatedColumnColumns: 'Στήλες', calculatedColumnFunctions: 'Συναρτήσεις', calculatedColumnOperators: 'Τελεστές', diff --git a/community-modules/locale/src/en-US.ts b/community-modules/locale/src/en-US.ts index b0fa5183983..7ab87bcc03a 100644 --- a/community-modules/locale/src/en-US.ts +++ b/community-modules/locale/src/en-US.ts @@ -844,6 +844,7 @@ export const AG_GRID_LOCALE_EN = { calculatedColumnType: 'Type', calculatedColumnExpression: 'Expression', calculatedColumnExpressionPlaceholder: 'Type here', + calculatedColumnExpressionToolsLabel: 'Insert', calculatedColumnColumns: 'Columns', calculatedColumnFunctions: 'Functions', calculatedColumnOperators: 'Operators', diff --git a/community-modules/locale/src/es-ES.ts b/community-modules/locale/src/es-ES.ts index d76a751bc4f..4e5d357f941 100644 --- a/community-modules/locale/src/es-ES.ts +++ b/community-modules/locale/src/es-ES.ts @@ -835,6 +835,7 @@ export const AG_GRID_LOCALE_ES = { calculatedColumnType: 'Tipo', calculatedColumnExpression: 'Expresión', calculatedColumnExpressionPlaceholder: 'Escriba aquí', + calculatedColumnExpressionToolsLabel: 'Insertar', calculatedColumnColumns: 'Columnas', calculatedColumnFunctions: 'Funciones', calculatedColumnOperators: 'Operadores', diff --git a/community-modules/locale/src/fa-IR.ts b/community-modules/locale/src/fa-IR.ts index 55cc3582316..8adfc1889e6 100644 --- a/community-modules/locale/src/fa-IR.ts +++ b/community-modules/locale/src/fa-IR.ts @@ -832,6 +832,7 @@ export const AG_GRID_LOCALE_IR = { calculatedColumnType: 'نوع', calculatedColumnExpression: 'عبارت', calculatedColumnExpressionPlaceholder: 'اینجا بنویسید', + calculatedColumnExpressionToolsLabel: 'درج', calculatedColumnColumns: 'ستون‌ها', calculatedColumnFunctions: 'توابع', calculatedColumnOperators: 'عملگرها', diff --git a/community-modules/locale/src/fi-FI.ts b/community-modules/locale/src/fi-FI.ts index 7a767f73224..dc259b70448 100644 --- a/community-modules/locale/src/fi-FI.ts +++ b/community-modules/locale/src/fi-FI.ts @@ -833,6 +833,7 @@ export const AG_GRID_LOCALE_FI = { calculatedColumnType: 'Tyyppi', calculatedColumnExpression: 'Lauseke', calculatedColumnExpressionPlaceholder: 'Kirjoita tähän', + calculatedColumnExpressionToolsLabel: 'Lisää', calculatedColumnColumns: 'Sarakkeet', calculatedColumnFunctions: 'Funktiot', calculatedColumnOperators: 'Operaattorit', diff --git a/community-modules/locale/src/fr-FR.ts b/community-modules/locale/src/fr-FR.ts index 8ce2908beaf..372d21347c6 100644 --- a/community-modules/locale/src/fr-FR.ts +++ b/community-modules/locale/src/fr-FR.ts @@ -839,6 +839,7 @@ export const AG_GRID_LOCALE_FR = { calculatedColumnType: 'Type', calculatedColumnExpression: 'Expression', calculatedColumnExpressionPlaceholder: 'Saisissez ici', + calculatedColumnExpressionToolsLabel: 'Insérer', calculatedColumnColumns: 'Colonnes', calculatedColumnFunctions: 'Fonctions', calculatedColumnOperators: 'Opérateurs', diff --git a/community-modules/locale/src/he-IL.ts b/community-modules/locale/src/he-IL.ts index 10ad8b8fc20..aefd102b7cb 100644 --- a/community-modules/locale/src/he-IL.ts +++ b/community-modules/locale/src/he-IL.ts @@ -829,6 +829,7 @@ export const AG_GRID_LOCALE_IL = { calculatedColumnType: 'סוג', calculatedColumnExpression: 'ביטוי', calculatedColumnExpressionPlaceholder: 'הקלד כאן', + calculatedColumnExpressionToolsLabel: 'הוספה', calculatedColumnColumns: 'עמודות', calculatedColumnFunctions: 'פונקציות', calculatedColumnOperators: 'אופרטורים', diff --git a/community-modules/locale/src/hr-HR.ts b/community-modules/locale/src/hr-HR.ts index 9ab2cff3448..6ccd54db580 100644 --- a/community-modules/locale/src/hr-HR.ts +++ b/community-modules/locale/src/hr-HR.ts @@ -832,6 +832,7 @@ export const AG_GRID_LOCALE_HR = { calculatedColumnType: 'Vrsta', calculatedColumnExpression: 'Izraz', calculatedColumnExpressionPlaceholder: 'Upišite ovdje', + calculatedColumnExpressionToolsLabel: 'Umetni', calculatedColumnColumns: 'Stupci', calculatedColumnFunctions: 'Funkcije', calculatedColumnOperators: 'Operatori', diff --git a/community-modules/locale/src/hu-HU.ts b/community-modules/locale/src/hu-HU.ts index 90a032a2d56..6f6d8826bfc 100644 --- a/community-modules/locale/src/hu-HU.ts +++ b/community-modules/locale/src/hu-HU.ts @@ -837,6 +837,7 @@ export const AG_GRID_LOCALE_HU = { calculatedColumnType: 'Típus', calculatedColumnExpression: 'Kifejezés', calculatedColumnExpressionPlaceholder: 'Írjon ide', + calculatedColumnExpressionToolsLabel: 'Beszúrás', calculatedColumnColumns: 'Oszlopok', calculatedColumnFunctions: 'Függvények', calculatedColumnOperators: 'Operátorok', diff --git a/community-modules/locale/src/it-IT.ts b/community-modules/locale/src/it-IT.ts index d926f50263c..73d3216674f 100644 --- a/community-modules/locale/src/it-IT.ts +++ b/community-modules/locale/src/it-IT.ts @@ -836,6 +836,7 @@ export const AG_GRID_LOCALE_IT = { calculatedColumnType: 'Tipo', calculatedColumnExpression: 'Espressione', calculatedColumnExpressionPlaceholder: 'Digita qui', + calculatedColumnExpressionToolsLabel: 'Inserisci', calculatedColumnColumns: 'Colonne', calculatedColumnFunctions: 'Funzioni', calculatedColumnOperators: 'Operatori', diff --git a/community-modules/locale/src/ja-JP.ts b/community-modules/locale/src/ja-JP.ts index d2e3047f356..9084d943c53 100644 --- a/community-modules/locale/src/ja-JP.ts +++ b/community-modules/locale/src/ja-JP.ts @@ -830,6 +830,7 @@ export const AG_GRID_LOCALE_JP = { calculatedColumnType: '種類', calculatedColumnExpression: '式', calculatedColumnExpressionPlaceholder: 'ここに入力', + calculatedColumnExpressionToolsLabel: '挿入', calculatedColumnColumns: '列', calculatedColumnFunctions: '関数', calculatedColumnOperators: '演算子', diff --git a/community-modules/locale/src/ko-KR.ts b/community-modules/locale/src/ko-KR.ts index 803644e1829..77101469a3c 100644 --- a/community-modules/locale/src/ko-KR.ts +++ b/community-modules/locale/src/ko-KR.ts @@ -830,6 +830,7 @@ export const AG_GRID_LOCALE_KR = { calculatedColumnType: '유형', calculatedColumnExpression: '식', calculatedColumnExpressionPlaceholder: '여기에 입력', + calculatedColumnExpressionToolsLabel: '삽입', calculatedColumnColumns: '열', calculatedColumnFunctions: '함수', calculatedColumnOperators: '연산자', diff --git a/community-modules/locale/src/nb-NO.ts b/community-modules/locale/src/nb-NO.ts index f0cecd8334e..d0da9d35455 100644 --- a/community-modules/locale/src/nb-NO.ts +++ b/community-modules/locale/src/nb-NO.ts @@ -831,6 +831,7 @@ export const AG_GRID_LOCALE_NO = { calculatedColumnType: 'Type', calculatedColumnExpression: 'Uttrykk', calculatedColumnExpressionPlaceholder: 'Skriv her', + calculatedColumnExpressionToolsLabel: 'Sett inn', calculatedColumnColumns: 'Kolonner', calculatedColumnFunctions: 'Funksjoner', calculatedColumnOperators: 'Operatorer', diff --git a/community-modules/locale/src/nl-NL.ts b/community-modules/locale/src/nl-NL.ts index a35fc60ead6..a41297a03a9 100644 --- a/community-modules/locale/src/nl-NL.ts +++ b/community-modules/locale/src/nl-NL.ts @@ -832,6 +832,7 @@ export const AG_GRID_LOCALE_NL = { calculatedColumnType: 'Type', calculatedColumnExpression: 'Expressie', calculatedColumnExpressionPlaceholder: 'Typ hier', + calculatedColumnExpressionToolsLabel: 'Invoegen', calculatedColumnColumns: 'Kolommen', calculatedColumnFunctions: 'Functies', calculatedColumnOperators: 'Operatoren', diff --git a/community-modules/locale/src/pl-PL.ts b/community-modules/locale/src/pl-PL.ts index 4a5a9c3884c..a081652184c 100644 --- a/community-modules/locale/src/pl-PL.ts +++ b/community-modules/locale/src/pl-PL.ts @@ -835,6 +835,7 @@ export const AG_GRID_LOCALE_PL = { calculatedColumnType: 'Typ', calculatedColumnExpression: 'Wyrażenie', calculatedColumnExpressionPlaceholder: 'Wpisz tutaj', + calculatedColumnExpressionToolsLabel: 'Wstaw', calculatedColumnColumns: 'Kolumny', calculatedColumnFunctions: 'Funkcje', calculatedColumnOperators: 'Operatory', diff --git a/community-modules/locale/src/pt-BR.ts b/community-modules/locale/src/pt-BR.ts index 0cd89ab3b73..83a21228810 100644 --- a/community-modules/locale/src/pt-BR.ts +++ b/community-modules/locale/src/pt-BR.ts @@ -835,6 +835,7 @@ export const AG_GRID_LOCALE_BR = { calculatedColumnType: 'Tipo', calculatedColumnExpression: 'Expressão', calculatedColumnExpressionPlaceholder: 'Digite aqui', + calculatedColumnExpressionToolsLabel: 'Inserir', calculatedColumnColumns: 'Colunas', calculatedColumnFunctions: 'Funções', calculatedColumnOperators: 'Operadores', diff --git a/community-modules/locale/src/pt-PT.ts b/community-modules/locale/src/pt-PT.ts index 09a55cb4295..f8fa691dc00 100644 --- a/community-modules/locale/src/pt-PT.ts +++ b/community-modules/locale/src/pt-PT.ts @@ -834,6 +834,7 @@ export const AG_GRID_LOCALE_PT = { calculatedColumnType: 'Tipo', calculatedColumnExpression: 'Expressão', calculatedColumnExpressionPlaceholder: 'Escreva aqui', + calculatedColumnExpressionToolsLabel: 'Inserir', calculatedColumnColumns: 'Colunas', calculatedColumnFunctions: 'Funções', calculatedColumnOperators: 'Operadores', diff --git a/community-modules/locale/src/ro-RO.ts b/community-modules/locale/src/ro-RO.ts index 52fd3385252..3b90c17d885 100644 --- a/community-modules/locale/src/ro-RO.ts +++ b/community-modules/locale/src/ro-RO.ts @@ -834,6 +834,7 @@ export const AG_GRID_LOCALE_RO = { calculatedColumnType: 'Tip', calculatedColumnExpression: 'Expresie', calculatedColumnExpressionPlaceholder: 'Introduceți aici', + calculatedColumnExpressionToolsLabel: 'Inserare', calculatedColumnColumns: 'Coloane', calculatedColumnFunctions: 'Funcții', calculatedColumnOperators: 'Operatori', diff --git a/community-modules/locale/src/sk-SK.ts b/community-modules/locale/src/sk-SK.ts index cbbfe7775d6..95b300d966f 100644 --- a/community-modules/locale/src/sk-SK.ts +++ b/community-modules/locale/src/sk-SK.ts @@ -831,6 +831,7 @@ export const AG_GRID_LOCALE_SK = { calculatedColumnType: 'Typ', calculatedColumnExpression: 'Výraz', calculatedColumnExpressionPlaceholder: 'Píšte sem', + calculatedColumnExpressionToolsLabel: 'Vložiť', calculatedColumnColumns: 'Stĺpce', calculatedColumnFunctions: 'Funkcie', calculatedColumnOperators: 'Operátory', diff --git a/community-modules/locale/src/sv-SE.ts b/community-modules/locale/src/sv-SE.ts index 7e3333bc233..fd65687fc32 100644 --- a/community-modules/locale/src/sv-SE.ts +++ b/community-modules/locale/src/sv-SE.ts @@ -833,6 +833,7 @@ export const AG_GRID_LOCALE_SE = { calculatedColumnType: 'Typ', calculatedColumnExpression: 'Uttryck', calculatedColumnExpressionPlaceholder: 'Skriv här', + calculatedColumnExpressionToolsLabel: 'Infoga', calculatedColumnColumns: 'Kolumner', calculatedColumnFunctions: 'Funktioner', calculatedColumnOperators: 'Operatorer', diff --git a/community-modules/locale/src/tr-TR.ts b/community-modules/locale/src/tr-TR.ts index 6bf0e4e3048..65421f63fc8 100644 --- a/community-modules/locale/src/tr-TR.ts +++ b/community-modules/locale/src/tr-TR.ts @@ -835,6 +835,7 @@ export const AG_GRID_LOCALE_TR = { calculatedColumnType: 'Tür', calculatedColumnExpression: 'İfade', calculatedColumnExpressionPlaceholder: 'Buraya yazın', + calculatedColumnExpressionToolsLabel: 'Ekle', calculatedColumnColumns: 'Sütunlar', calculatedColumnFunctions: 'Fonksiyonlar', calculatedColumnOperators: 'Operatörler', diff --git a/community-modules/locale/src/uk-UA.ts b/community-modules/locale/src/uk-UA.ts index 6af4e80f52d..f3700783293 100644 --- a/community-modules/locale/src/uk-UA.ts +++ b/community-modules/locale/src/uk-UA.ts @@ -832,6 +832,7 @@ export const AG_GRID_LOCALE_UA = { calculatedColumnType: 'Тип', calculatedColumnExpression: 'Вираз', calculatedColumnExpressionPlaceholder: 'Введіть тут', + calculatedColumnExpressionToolsLabel: 'Вставити', calculatedColumnColumns: 'Стовпці', calculatedColumnFunctions: 'Функції', calculatedColumnOperators: 'Оператори', diff --git a/community-modules/locale/src/ur-PK.ts b/community-modules/locale/src/ur-PK.ts index 30047ef3f36..742297b84e9 100644 --- a/community-modules/locale/src/ur-PK.ts +++ b/community-modules/locale/src/ur-PK.ts @@ -830,6 +830,7 @@ export const AG_GRID_LOCALE_PK = { calculatedColumnType: 'قسم', calculatedColumnExpression: 'اظہار', calculatedColumnExpressionPlaceholder: 'یہاں لکھیں', + calculatedColumnExpressionToolsLabel: 'داخل کریں', calculatedColumnColumns: 'کالمز', calculatedColumnFunctions: 'فنکشنز', calculatedColumnOperators: 'آپریٹرز', diff --git a/community-modules/locale/src/vi-VN.ts b/community-modules/locale/src/vi-VN.ts index 93684ca8023..22899be74c1 100644 --- a/community-modules/locale/src/vi-VN.ts +++ b/community-modules/locale/src/vi-VN.ts @@ -830,6 +830,7 @@ export const AG_GRID_LOCALE_VN = { calculatedColumnType: 'Loại', calculatedColumnExpression: 'Biểu thức', calculatedColumnExpressionPlaceholder: 'Nhập tại đây', + calculatedColumnExpressionToolsLabel: 'Chèn', calculatedColumnColumns: 'Cột', calculatedColumnFunctions: 'Hàm', calculatedColumnOperators: 'Toán tử', diff --git a/community-modules/locale/src/zh-CN.ts b/community-modules/locale/src/zh-CN.ts index 47a2c45f792..db059133bab 100644 --- a/community-modules/locale/src/zh-CN.ts +++ b/community-modules/locale/src/zh-CN.ts @@ -829,6 +829,7 @@ export const AG_GRID_LOCALE_CN = { calculatedColumnType: '类型', calculatedColumnExpression: '表达式', calculatedColumnExpressionPlaceholder: '在此输入', + calculatedColumnExpressionToolsLabel: '插入', calculatedColumnColumns: '列', calculatedColumnFunctions: '函数', calculatedColumnOperators: '运算符', diff --git a/community-modules/locale/src/zh-HK.ts b/community-modules/locale/src/zh-HK.ts index e92ba24175d..2208642fd5f 100644 --- a/community-modules/locale/src/zh-HK.ts +++ b/community-modules/locale/src/zh-HK.ts @@ -829,6 +829,7 @@ export const AG_GRID_LOCALE_HK = { calculatedColumnType: '類型', calculatedColumnExpression: '運算式', calculatedColumnExpressionPlaceholder: '在此輸入', + calculatedColumnExpressionToolsLabel: '插入', calculatedColumnColumns: '欄', calculatedColumnFunctions: '函數', calculatedColumnOperators: '運算子', diff --git a/community-modules/locale/src/zh-TW.ts b/community-modules/locale/src/zh-TW.ts index 3cb29165708..34dff35506d 100644 --- a/community-modules/locale/src/zh-TW.ts +++ b/community-modules/locale/src/zh-TW.ts @@ -829,6 +829,7 @@ export const AG_GRID_LOCALE_TW = { calculatedColumnType: '類型', calculatedColumnExpression: '運算式', calculatedColumnExpressionPlaceholder: '在此輸入', + calculatedColumnExpressionToolsLabel: '插入', calculatedColumnColumns: '欄位', calculatedColumnFunctions: '函式', calculatedColumnOperators: '運算子', diff --git a/community-modules/styles/src/internal/base/parts/_calculated-columns.scss b/community-modules/styles/src/internal/base/parts/_calculated-columns.scss index 944fa14de57..a585837a4b3 100644 --- a/community-modules/styles/src/internal/base/parts/_calculated-columns.scss +++ b/community-modules/styles/src/internal/base/parts/_calculated-columns.scss @@ -14,7 +14,10 @@ .ag-calculated-column-expression-wrap { display: flex; + flex-direction: column; flex: 1 1 auto; + gap: var(--ag-grid-size); + .ag-text-area { flex: 1 1 auto; } @@ -26,6 +29,7 @@ .ag-text-area-input { height: 100%; resize: none; + padding-top: calc(var(--ag-grid-size) / 2); } } @@ -36,25 +40,20 @@ .ag-calculated-column-expression-tools { display: flex; - gap: calc(var(--ag-grid-size) / 2); - } - .ag-calculated-column-expression-tool { - flex: 1 1 0; - min-width: 0; - border: var(--ag-borders) var(--ag-border-color); - border-radius: var(--ag-border-radius); - padding: calc(var(--ag-grid-size) / 2) var(--ag-grid-size); - background: var(--ag-background-color); - color: var(--ag-foreground-color); - cursor: pointer; - font: inherit; - - &:hover { - background-color: var(--ag-row-hover-color); + .ag-group-container { + flex: 1 1 auto; + justify-content: end; + gap: var(--ag-grid-size); + flex-wrap: nowrap; + align-items: center; } } + .ag-standard-button.ag-calculated-column-expression-tool { + padding: calc(var(--ag-grid-size) / 2); + } + .ag-calculated-column-suggestion-path { display: flex; align-items: center; diff --git a/packages/ag-grid-enterprise/src/agStack/agGroupComponent.ts b/packages/ag-grid-enterprise/src/agStack/agGroupComponent.ts index 64d8f989aff..90bd2a1359a 100644 --- a/packages/ag-grid-enterprise/src/agStack/agGroupComponent.ts +++ b/packages/ag-grid-enterprise/src/agStack/agGroupComponent.ts @@ -291,6 +291,12 @@ export class AgGroupComponent< this.items = newItems; } + public setDirection(direction: GroupDirection): this { + this.eContainer.classList.toggle('ag-group-container-horizontal', direction === 'horizontal'); + this.eContainer.classList.toggle('ag-group-container-vertical', direction === 'vertical'); + return this; + } + private insertItem(item: GroupItem, prepend?: boolean) { const container = this.eContainer; const el = _isComponent(item) ? item.getGui() : item; diff --git a/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnForm.ts b/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnForm.ts index f72367b7bc8..ee3ffada692 100644 --- a/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnForm.ts +++ b/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnForm.ts @@ -19,6 +19,8 @@ import { import { AgAutocompleteList } from '../advancedFilter/autocomplete/agAutocompleteList'; import type { AutocompleteEntry } from '../advancedFilter/autocomplete/autocompleteParams'; +import { AgGroupComponentSelector } from '../agStack/agGroupComponent'; +import type { GroupComponent } from '../widgets/gridEnterpriseWidgetTypes'; import { CalculatedColumnAutocompleteRow } from './calculatedColumnAutocompleteRow'; import type { CalculatedColumnDataTypeOption, @@ -61,16 +63,30 @@ const CalculatedColumnFormElement: ElementParams = { { tag: 'div', cls: 'ag-calculated-column-expression-wrap', - children: [{ tag: 'ag-input-text-area', ref: 'eExpression' }], - }, - { - tag: 'div', - cls: 'ag-calculated-column-expression-tools', - ref: 'eExpressionTools', children: [ - { tag: 'button', ref: 'eColumns', cls: 'ag-calculated-column-expression-tool' }, - { tag: 'button', ref: 'eFunctions', cls: 'ag-calculated-column-expression-tool' }, - { tag: 'button', ref: 'eOperators', cls: 'ag-calculated-column-expression-tool' }, + { tag: 'ag-input-text-area', ref: 'eExpression' }, + { + tag: 'ag-group-component', + cls: 'ag-calculated-column-expression-tools', + ref: 'eExpressionTools', + children: [ + { + tag: 'button', + ref: 'eColumns', + cls: 'ag-button ag-standard-button ag-calculated-column-expression-tool', + }, + { + tag: 'button', + ref: 'eFunctions', + cls: 'ag-button ag-standard-button ag-calculated-column-expression-tool', + }, + { + tag: 'button', + ref: 'eOperators', + cls: 'ag-button ag-standard-button ag-calculated-column-expression-tool', + }, + ], + }, ], }, { @@ -99,7 +115,7 @@ export class CalculatedColumnForm extends Component { private readonly eApply: HTMLButtonElement = RefPlaceholder; private readonly eCancel: HTMLButtonElement = RefPlaceholder; private readonly eActions: HTMLElement = RefPlaceholder; - private readonly eExpressionTools: HTMLElement = RefPlaceholder; + private readonly eExpressionTools: GroupComponent = RefPlaceholder; private activeReplacement: { start: number; end: number } | null = null; private suggestionSource: HTMLElement | null = null; @@ -125,7 +141,12 @@ export class CalculatedColumnForm extends Component { private readonly liveApply: boolean, private readonly onDraftChange?: (draft: CalculatedColumnDraft) => void ) { - super(CalculatedColumnFormElement, [AgInputTextFieldSelector, AgSelectSelector, AgInputTextAreaSelector]); + super(CalculatedColumnFormElement, [ + AgInputTextFieldSelector, + AgSelectSelector, + AgInputTextAreaSelector, + AgGroupComponentSelector, + ]); this.expressionPickers = new Set(expressionPickers); } @@ -167,6 +188,11 @@ export class CalculatedColumnForm extends Component { .setInputPlaceholder(translate('calculatedColumnExpressionPlaceholder', 'Type here')) .setRows(3) .setValue(this.draft.calculatedExpression, true); + + this.eExpressionTools + .setDirection('horizontal') + .setTitle(translate('calculatedColumnExpressionToolsLabel', 'Insert')) + .hideOpenCloseIcons(true); } private setupActionButtons(): void { @@ -186,7 +212,7 @@ export class CalculatedColumnForm extends Component { _setDisplayed(this.eColumns, hasColumns); _setDisplayed(this.eFunctions, hasFunctions); _setDisplayed(this.eOperators, hasOperators); - _setDisplayed(this.eExpressionTools, hasColumns || hasFunctions || hasOperators); + this.eExpressionTools.setDisplayed(hasColumns || hasFunctions || hasOperators); _setDisplayed(this.eActions, !this.liveApply); } diff --git a/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumns.css b/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumns.css index c97d8a5c678..8bdd00a36ad 100644 --- a/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumns.css +++ b/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumns.css @@ -13,7 +13,9 @@ .ag-calculated-column-expression-wrap { display: flex; + flex-direction: column; flex: 1 1 auto; + gap: var(--ag-spacing); } .ag-calculated-column-expression-wrap :where(.ag-text-area) { @@ -24,9 +26,11 @@ height: 100%; } -.ag-calculated-column-expression-wrap :where(.ag-text-area-input) { +/* stylelint-disable-next-line selector-max-specificity */ +.ag-calculated-column-expression-wrap .ag-text-area-input { height: 100%; resize: none; + padding-top: calc(var(--ag-spacing) / 2); } /* stylelint-disable-next-line selector-max-specificity */ @@ -39,23 +43,19 @@ .ag-calculated-column-expression-tools { display: flex; - gap: calc(var(--ag-spacing) / 2); -} -.ag-calculated-column-expression-tool { - flex: 1 1 0; - min-width: 0; - border: var(--ag-border); - border-radius: var(--ag-border-radius); - padding: calc(var(--ag-spacing) / 2) var(--ag-spacing); - background: var(--ag-background-color); - color: var(--ag-foreground-color); - cursor: pointer; - font: inherit; + :where(.ag-group-container) { + flex: 1 1 auto; + justify-content: end; + gap: var(--ag-spacing); + flex-wrap: nowrap; + align-items: center; + } } -.ag-calculated-column-expression-tool:hover { - background-color: var(--ag-row-hover-color); +/* stylelint-disable-next-line selector-max-specificity */ +.ag-standard-button.ag-calculated-column-expression-tool { + padding: calc(var(--ag-spacing) / 2); } .ag-calculated-column-suggestion-path { diff --git a/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnsService.ts b/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnsService.ts index ac565075f9f..cdb2df1100e 100644 --- a/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnsService.ts +++ b/packages/ag-grid-enterprise/src/calculatedColumns/calculatedColumnsService.ts @@ -603,7 +603,7 @@ export class CalculatedColumnsService extends BeanStub implements NamedBean, ICa new Dialog({ title: this.getLocaleTextFunc()('calculatedColumn', 'Calculated Column'), component: form, - minWidth: 300, + minWidth: 320, width: 400, minHeight: 380, height: 380,