diff --git a/.github/workflows/prod-release.yml b/.github/workflows/prod-release.yml index 91eed80ab7c..650078d6c6c 100644 --- a/.github/workflows/prod-release.yml +++ b/.github/workflows/prod-release.yml @@ -45,7 +45,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: '22' + node-version-file: package.json registry-url: 'https://registry.npmjs.org' - name: Yarn diff --git a/documentation/ag-grid-docs/public/changelog/changelog.json b/documentation/ag-grid-docs/public/changelog/changelog.json index b03acc4f3a9..bd70fa6a91a 100644 --- a/documentation/ag-grid-docs/public/changelog/changelog.json +++ b/documentation/ag-grid-docs/public/changelog/changelog.json @@ -1,4 +1,18 @@ [ + { + "key": "AG-16618", + "issueType": "Bug", + "manualEntry": true, + "summary": "Placeholder for 35.0.1", + "versions": ["35.0.1"], + "status": "Done", + "resolution": "Done", + "features": null, + "moreInformation": null, + "deprecationNotes": null, + "breakingChangesNotes": null, + "documentationUrl": null + }, { "key": "AG-16379", "issueType": "Bug", diff --git a/documentation/ag-grid-docs/public/changelog/releaseVersionNotes.json b/documentation/ag-grid-docs/public/changelog/releaseVersionNotes.json index 6518fea9881..2b2f9c2cea1 100644 --- a/documentation/ag-grid-docs/public/changelog/releaseVersionNotes.json +++ b/documentation/ag-grid-docs/public/changelog/releaseVersionNotes.json @@ -1,4 +1,8 @@ [ + { + "release version": "35.0.1", + "markdown": "/releases/35_0_1.md" + }, { "release version": "35.0.0", "markdown": "/releases/35_0_0.md" diff --git a/documentation/ag-grid-docs/public/changelog/releases/35_0_1.md b/documentation/ag-grid-docs/public/changelog/releases/35_0_1.md new file mode 100644 index 00000000000..c483cc86053 --- /dev/null +++ b/documentation/ag-grid-docs/public/changelog/releases/35_0_1.md @@ -0,0 +1,3 @@ +#### 22nd Januray 2026 - Grid v35.0.1 (Charts v13.0.1) + +See table below for changes included in this release. diff --git a/documentation/ag-grid-docs/src/content/docs/formula-editor-component/_examples/formula-editor-component-validation/example.spec.ts b/documentation/ag-grid-docs/src/content/docs/formula-editor-component/_examples/formula-editor-component-validation/example.spec.ts new file mode 100644 index 00000000000..70f89aa9d01 --- /dev/null +++ b/documentation/ag-grid-docs/src/content/docs/formula-editor-component/_examples/formula-editor-component-validation/example.spec.ts @@ -0,0 +1,10 @@ +import { ensureGridReady, test, waitForGridContent } from '@utils/grid/test-utils'; + +test.agExample(import.meta, () => { + test.eachFramework('Example', async ({ page }) => { + // PLACEHOLDER - MINIMAL TEST TO ENSURE GRID LOADS WITHOUT ERRORS + await ensureGridReady(page); + await waitForGridContent(page); + // END PLACEHOLDER + }); +}); diff --git a/documentation/ag-grid-docs/src/content/docs/formula-editor-component/_examples/formula-editor-component-validation/index.html b/documentation/ag-grid-docs/src/content/docs/formula-editor-component/_examples/formula-editor-component-validation/index.html new file mode 100644 index 00000000000..378fad58398 --- /dev/null +++ b/documentation/ag-grid-docs/src/content/docs/formula-editor-component/_examples/formula-editor-component-validation/index.html @@ -0,0 +1 @@ +
diff --git a/documentation/ag-grid-docs/src/content/docs/formula-editor-component/_examples/formula-editor-component-validation/main.ts b/documentation/ag-grid-docs/src/content/docs/formula-editor-component/_examples/formula-editor-component-validation/main.ts new file mode 100644 index 00000000000..ded44e420cd --- /dev/null +++ b/documentation/ag-grid-docs/src/content/docs/formula-editor-component/_examples/formula-editor-component-validation/main.ts @@ -0,0 +1,114 @@ +import type { ColDef, GetRowIdParams, GridApi, GridOptions, ValueFormatterParams } from 'ag-grid-community'; +import { + ClientSideRowModelModule, + ModuleRegistry, + NumberEditorModule, + TextEditorModule, + ValidationModule, + createGrid, +} from 'ag-grid-community'; +import { CellSelectionModule, FormulaModule } from 'ag-grid-enterprise'; + +ModuleRegistry.registerModules([ + CellSelectionModule, + ClientSideRowModelModule, + FormulaModule, + NumberEditorModule, + TextEditorModule, + ValidationModule, +]); + +let gridApi: GridApi; + +const valueFormatter = ({ value }: ValueFormatterParams) => { + if (typeof value === 'string' && value.startsWith('#')) { + return value; + } + const numericValue = Number(value); + return Number.isFinite(numericValue) ? `$ ${numericValue.toFixed(2)}` : String(value ?? ''); +}; +const getRowId = (params: GetRowIdParams) => String(params.data.id); + +const columnDefs: ColDef[] = [ + { field: 'item' }, + { field: 'price', valueFormatter: valueFormatter }, + { field: 'qty' }, + { + field: 'total', + allowFormula: true, + valueFormatter: valueFormatter, + cellEditorParams: { + validateFormulas: true, + }, + }, +]; + +const gridOptions: GridOptions = { + columnDefs, + getRowId, + cellSelection: { + handle: { + mode: 'fill', + }, + }, + defaultColDef: { + editable: true, + flex: 1, + }, + rowData: [ + { + id: 1, + item: 'Apples', + price: 1.2, + qty: 4, + total: '=REF(COLUMN("price"),ROW(1))*REF(COLUMN("qty"),ROW(1))', + }, + { + id: 2, + item: 'Bananas', + price: 0.5, + qty: 6, + total: '=B2*', + }, + { + id: 3, + item: 'Oranges', + price: 0.8, + qty: 3, + total: '=REF(COLUMN("price"),ROW(3))*REF(COLUMN("qty"),ROW(3))', + }, + { + id: 4, + item: 'Pears', + price: 1.4, + qty: 2, + total: '=REF(COLUMN("price"),ROW(4))*REF(COLUMN("qty"),ROW(4))', + }, + { + id: 5, + item: 'Grapes', + price: 2.1, + qty: 3, + total: '=BADFUNC(1)', + }, + { + id: 6, + item: 'Plums', + price: 1.5, + qty: 2, + total: '=REF(COLUMN("price"),ROW(6))*REF(COLUMN("qty"),ROW(6))', + }, + { + id: 7, + item: 'Strawberries', + price: 1.8, + qty: 4, + total: '=REF(COLUMN("price"),ROW(7))*REF(COLUMN("qty"),ROW(7))', + }, + ], +}; + +document.addEventListener('DOMContentLoaded', () => { + const gridDiv = document.querySelector('#myGrid')!; + gridApi = createGrid(gridDiv, gridOptions); +}); diff --git a/documentation/ag-grid-docs/src/content/docs/formula-editor-component/index.mdoc b/documentation/ag-grid-docs/src/content/docs/formula-editor-component/index.mdoc index 3e4e95ba921..14b0d1728fe 100644 --- a/documentation/ag-grid-docs/src/content/docs/formula-editor-component/index.mdoc +++ b/documentation/ag-grid-docs/src/content/docs/formula-editor-component/index.mdoc @@ -19,3 +19,24 @@ Range highlights and range handle editing require `cellSelection` to be enabled. Providing a `cellEditor` opts the column out of the Formula Cell Editor. Formulas still evaluate, but range highlighting, handles, and function autocomplete are disabled because a different editor is in use. {% gridExampleRunner title="Formula Editor Disabled" name="formula-editor-component-disabled" exampleHeight=320 /%} + +## Validation + +Invalid formulas already surface via the formula engine: the cell displays the error and shows a tooltip based on the grid's formula error state. Because of this, the Formula Cell Editor does not validate on every change by default. + +To opt into validation while editing, set `validateFormulas: true` on the editor params. Validation will also run if you provide a custom [getValidationErrors](./cell-editing-validation/#overriding-validation) callback. For more details on validation behaviour and presentation, see [Cell Editing Validation](./cell-editing-validation/). + +```js +const columnDefs = [ + { + field: 'total', + allowFormula: true, + cellEditorParams: { + validateFormulas: true, + }, + }, +]; +``` + +{% gridExampleRunner title="Formula Editor Validation" name="formula-editor-component-validation" exampleHeight=320 /%} + diff --git a/documentation/ag-grid-docs/src/content/docs/formulas/index.mdoc b/documentation/ag-grid-docs/src/content/docs/formulas/index.mdoc index 7db7c7576f6..1ca54631d77 100644 --- a/documentation/ag-grid-docs/src/content/docs/formulas/index.mdoc +++ b/documentation/ag-grid-docs/src/content/docs/formulas/index.mdoc @@ -384,4 +384,4 @@ When performing an [Excel Export](./excel-export), the grid will export the form ## Next Up -Continue to the next section: [Formula Editor Component](./formula-editor-component/). \ No newline at end of file +Continue to the next section: [Formula Editor Component](./formula-editor-component/). diff --git a/documentation/ag-grid-docs/src/content/versions/ag-grid-versions.json b/documentation/ag-grid-docs/src/content/versions/ag-grid-versions.json index 8a3f84c6928..daed884603d 100644 --- a/documentation/ag-grid-docs/src/content/versions/ag-grid-versions.json +++ b/documentation/ag-grid-docs/src/content/versions/ag-grid-versions.json @@ -1,4 +1,8 @@ [ + { + "version": "35.0.1", + "date": "22nd January 2026" + }, { "version": "35.0.0", "date": "10th December 2025", diff --git a/packages/ag-grid-community/src/edit/cellEditors/iFormulaCellEditor.ts b/packages/ag-grid-community/src/edit/cellEditors/iFormulaCellEditor.ts new file mode 100644 index 00000000000..806dca4f6b3 --- /dev/null +++ b/packages/ag-grid-community/src/edit/cellEditors/iFormulaCellEditor.ts @@ -0,0 +1,11 @@ +import type { ICellEditorParams } from '../../interfaces/iCellEditor'; + +export interface IFormulaCellEditorParams + extends ICellEditorParams { + /** + * Set to `true` to validate formulas while editing. + * If a custom `getValidationErrors` is provided, internal validation will still run. + * @default false + */ + validateFormulas?: boolean; +} diff --git a/packages/ag-grid-community/src/main.ts b/packages/ag-grid-community/src/main.ts index 20ff7587af2..97a60252f9f 100644 --- a/packages/ag-grid-community/src/main.ts +++ b/packages/ag-grid-community/src/main.ts @@ -342,6 +342,7 @@ export type { DateCellEditor } from './edit/cellEditors/dateCellEditor'; export type { DateStringCellEditor } from './edit/cellEditors/dateStringCellEditor'; export { IDateCellEditorParams } from './edit/cellEditors/iDateCellEditor'; export { IDateStringCellEditorParams } from './edit/cellEditors/iDateStringCellEditor'; +export { IFormulaCellEditorParams } from './edit/cellEditors/iFormulaCellEditor'; export { ILargeTextEditorParams } from './edit/cellEditors/iLargeTextCellEditor'; export { INumberCellEditorParams } from './edit/cellEditors/iNumberCellEditor'; export { ISelectCellEditorParams } from './edit/cellEditors/iSelectCellEditor'; diff --git a/packages/ag-grid-enterprise/src/formula/editor/formulaCellEditor.ts b/packages/ag-grid-enterprise/src/formula/editor/formulaCellEditor.ts index ffa1b9fd56c..9a6f69f00eb 100644 --- a/packages/ag-grid-enterprise/src/formula/editor/formulaCellEditor.ts +++ b/packages/ag-grid-enterprise/src/formula/editor/formulaCellEditor.ts @@ -1,9 +1,9 @@ -import type { ICellEditorParams } from 'ag-grid-community'; import { AgAbstractCellEditor, KeyCode, RefPlaceholder, _isBrowserSafari, _placeCaretAtEnd } from 'ag-grid-community'; +import type { IFormulaCellEditorParams } from 'ag-grid-community'; import { AgFormulaInputField } from '../../widgets/agFormulaInputField'; -export class FormulaCellEditor extends AgAbstractCellEditor { +export class FormulaCellEditor extends AgAbstractCellEditor { protected eEditor: AgFormulaInputField = RefPlaceholder; private focusAfterAttached = false; @@ -11,7 +11,7 @@ export class FormulaCellEditor extends AgAbstractCellEditor { super({ tag: 'div', cls: 'ag-cell-edit-wrapper' }); } - public initialiseEditor(params: ICellEditorParams): void { + public initialiseEditor(params: IFormulaCellEditorParams): void { const formulaInputField = this.createManagedBean(new AgFormulaInputField()); this.eEditor = formulaInputField; @@ -75,7 +75,7 @@ export class FormulaCellEditor extends AgAbstractCellEditor { event.stopPropagation(); } - private getStartValue(params: ICellEditorParams): string | null | undefined { + private getStartValue(params: IFormulaCellEditorParams): string | null | undefined { const { value } = params; return value?.toString() ?? value; } @@ -125,11 +125,12 @@ export class FormulaCellEditor extends AgAbstractCellEditor { const { params } = this; const value = this.eEditor.getCurrentValue(); const translate = this.getLocaleTextFunc(); - const { getValidationErrors } = params; + const { getValidationErrors, validateFormulas } = params; let internalErrors: string[] | null = null; + const shouldValidate = validateFormulas === true || !!getValidationErrors; - if (typeof value === 'string' && this.isFormulaText(value)) { + if (shouldValidate && typeof value === 'string' && this.isFormulaText(value)) { const normalised = this.beans.formula?.normaliseFormula(value, true); if (!normalised) {