import type { GridRowId, GridValidRowModel, GridColDef, GridCellCoordinates } from '@mui/x-data-grid-pro'; import type { GridStateColDef } from '@mui/x-data-grid/internals'; import type { FormulaExcelExportLayout, ExcelFormulaCell } from "./gridFormulaExcelExport.mjs"; import type { FormulaBoundDependencies, FormulaCellKey, FormulaErrorCode, FormulaFunctionArg, FormulaFunctionContext, FormulaFunctionDefinition, FormulaFunctionRegistry, FormulaParseResult, FormulaParser, FormulaPositionContext, FormulaResult, FormulaValidationIssue, FormulaValidationResult } from "./engine/index.mjs"; /** * The outcome of evaluating one formula cell. */ export type GridFormulaResult = FormulaResult; /** * Error codes produced by formula parsing and evaluation, rendered as the cell content. */ export type GridFormulaErrorCode = FormulaErrorCode; /** * Serialized `${id}\u0000${field}` cell key used in formula caches. * Always created through the engine helpers — never derive the format manually. */ export type GridFormulaCellKey = FormulaCellKey; export type GridFormulaValidationResult = FormulaValidationResult; export type GridFormulaValidationIssue = FormulaValidationIssue; /** * Definition of a function callable from formulas. * The `apply` implementation only receives engine values — never the grid API. */ export type GridFormulaFunctionDefinition = FormulaFunctionDefinition; export type GridFormulaFunctionContext = FormulaFunctionContext; export type GridFormulaFunctionArg = FormulaFunctionArg; /** * Evaluated formula results, keyed by row id and field. * Membership in this lookup is what masks the raw `=` source from the rest of * the grid — a formula evaluating to `null` still has an entry. */ export type GridFormulaLookup = { [rowId: GridRowId]: { [field: string]: GridFormulaResult; }; }; /** * The cell whose formula our built-in editor is currently editing. */ export interface GridFormulaActiveEdit { id: GridRowId; field: string; /** * A draft formula source being edited OUTSIDE the cell's edit state — typed * into the formula bar while the cell stays in view mode. When set, reference * highlighting derives from this text instead of the cell's edit state. */ draft?: string; } export interface GridFormulaState { lookup: GridFormulaLookup; /** * The cell whose formula references are highlighted, or `null`. Set when our * formula editor renders and cleared on `cellEditStop`; it outlives the * editing cell being virtualized out (so the in-grid reference overlay * persists) and is never set for a column with a custom editor. */ activeEdit: GridFormulaActiveEdit | null; } /** * One scanned formula cell. `parse` is `null` for `'=` escaped literals, * which evaluate to their unescaped string without entering the graph. */ export interface GridFormulaCellRecord { id: GridRowId; field: string; source: string; parse: FormulaParseResult | null; dependencies: FormulaBoundDependencies | null; /** * `true` when the formula contains positional selectors, `RANGE_REF` or * `COLUMN_VALUES` — its dependencies were resolved against a position * context and must rebind when that context changes. */ usesPositionContext: boolean; result: GridFormulaResult; } /** * The slices of one record's range dependencies that read a given field. * Stored per dependent in `rangeDependentsByField` — interval records, * never exploded per-cell edges. */ export interface GridFormulaRangeDependency { /** * Bounded `RANGE_REF` slices: rows `fromIndex..toIndex` (1-based, inclusive) * of the field in the position context's row order. */ intervals: { fromIndex: number; toIndex: number; }[]; /** * `true` when the record reads the whole column (`COLUMN_VALUES`). */ wholeColumn: boolean; } export interface GridFormulaCellEditStartInfo { id: GridRowId; field: string; /** * `true` when the edit started by typing/deleting/pasting — the edit value * was intentionally replaced and must not be re-seeded with the source. */ replaceValue: boolean; /** * `true` when the edit started by typing `=` — the user is entering a * formula, so the formula text editor renders even on a plain cell. */ startedWithEquals: boolean; } export interface GridFormulaInternalCache { /** * Interning parser: identical sources share one parse result. */ parser: FormulaParser; registry: FormulaFunctionRegistry; /** * The `formulaFunctions` prop value `registry` was built from, * compared by reference to detect prop changes. */ registrySource: Record; records: Map; /** * Reverse dependency edges: for a cell key, the formula cells that read it. */ dependents: Map>; /** * Formula cells depending on any cell of a row, keyed by stringified row id. * Lets row additions/removals dirty their dependents in O(dependents). */ dependentsByRowId: Map>; /** * Formula records grouped by the field they live in. This is what expands * an interval dependency into graph edges: only the formula cells of the * field can participate in cycles or require ordered recomputation — * raw cells never do. */ recordsByField: Map>; /** * Keys of the records with `usesPositionContext` — the set a rebind pass * re-binds when the position context changes. */ positionDependentKeys: Set; /** * Reverse range-dependency tier: for each field, the formula cells whose * `RANGE_REF`/`COLUMN_VALUES` dependencies read it, as interval records. * A change to cell `(id, field)` dirties the dependents whose interval * contains the row's position (or any whole-column dependent). */ rangeDependentsByField: Map>; /** * The position-context snapshot records are currently bound against. * Built lazily (only when a position-dependent formula exists) and * replaced by rebind passes; `null` means "build on first need". */ positionContext: FormulaPositionContext | null; /** * The exact row order behind `positionContext` — compared on rebind events * to skip rebinding when positions did not actually change. */ positionContextRowIds: GridRowId[] | null; /** * The exact visible-field order behind `positionContext`. */ positionContextFields: string[] | null; /** * Monotonic counter stamped into each built position context. */ positionContextVersion: number; /** * Guards the post-pass re-grouping trigger: the row-tree rebuild it fires * cascades into another formula pass, which must not fire it again. */ suppressRegroupTrigger: boolean; /** * Last value resolved for each raw (non-formula) dependency cell, * keyed by stringified row id then field. Compared on row change to decide * whether dependents must recompute. */ trackedValues: Map>; /** * Rows lookup snapshot from the last pass — rows are replaced immutably, * so a reference diff finds the changed/added/removed ids. */ lastRowIdToModelLookup: Record | null; /** * Fields with `allowFormulas` at the last pass. */ formulaFields: string[]; /** * `field → valueGetter` of every column at the last pass. Evaluation reads * raw dependencies through column definitions, so adding/removing a column * or changing a `valueGetter` must trigger a re-evaluation even when the * `allowFormulas` field set is unchanged. */ lastColumnsSignature: Map; lastCellEditStart: GridFormulaCellEditStartInfo | null; /** * The A1 value last seeded into the editor and the canonical source it came * from (A1 notation only). Lets the commit parser detect an unchanged edit * and restore the stored canonical instead of re-freezing relative references * against a possibly re-sorted view. It must survive `cellEditStop` — the * commit's value setter runs again after that event — so it is overwritten or * invalidated at the next editor mount instead (`GridFormulaEditCell`). */ lastA1Seed: { id: GridRowId; field: string; display: string; canonical: string; } | null; /** * Position of the first cell of the current clipboard paste (A1 notation * only). Subsequent pasted cells offset their relative references by their * distance from this origin — the Excel fill adjustment. Armed on * `clipboardPasteStart`, consumed lazily by the first pasted cell. */ pasteOrigin: { rowPosition: number | undefined; columnPosition: number | undefined; } | null; /** * "Focus-safe" formula elements of THIS grid instance: formula-bar roots and * suggestion-popup panels. Both can live outside the grid root (a portaled * bar, the body-portaled popup), so DOM containment cannot scope them — the * `canUpdateFocus` veto and the editor's focus handling recognize them * through this registry instead. Registered through callback refs, so * registration follows the element lifecycle. */ focusSafeElements: Set; /** * The text currently typed in a formula editing surface (the cell editor or * the bar's edit-mode mirror) together with the edit-state value it parsed * to. The column's parser is lossy mid-edit — on a number column `-` parses * to `null` (never `NaN`, matching a native number input's `badInput` * report), `0.50` to `0.5`, and deleting the leading `=` of a formula leaves * text that does not parse at all — so the surfaces render this text instead * of the parsed value while the draft's value still matches the edit state * exactly. Shared through the cache so the cell editor and the bar mirror * always display the same text. Cleared when the cell leaves edit mode. */ plainEditDraft: { id: GridRowId; field: string; text: string; value: unknown; } | null; /** * Live mirror of the formula-editor session (engaged flag + caret offset + * the grown floating-surface box), written by the focused editor on every * user interaction. When virtualization remounts the editing cell (the * edited row left the render window), the fresh editor instance resumes from * it instead of snapping the caret to the end — reproducing the identical * surface box, since neither growth ratchet shrinks mid-edit. Cleared on * `cellEditStop`. */ editorSession: { id: GridRowId; field: string; engaged: boolean; caret: number | null; surfaceWidth: number | null; /** * The growth bound captured when the surface first grew. Carried across * remounts so a remounted editor never re-measures it at the current scroll * position — a scroll-skewed re-measure could shrink the restored box, the * exact wobble the grow-only ratchet forbids. */ surfaceClamp: number | null; /** * Whether the editor has switched to wrapped (multi-line) mode — the width * ratchet reached its clamp and the formula still did not fit on one line. * Monotonic for the life of the session. */ surfaceWrapped: boolean; /** The ratcheted surface height once wrapped. */ surfaceHeight: number | null; /** * The vertical growth bound captured when the surface first wrapped. Same * measure-once rule as `surfaceClamp`. */ surfaceHeightClamp: number | null; /** * Whether the surface grows upward (its block-end welded to the row) because * the row had no room below. Decided once, when the surface first wrapped. */ surfaceFlipped: boolean; /** * The largest box the content asked for before clamping, on each axis. A grid * resized smaller pulls the box in; these put it back when the grid is resized * larger again. */ surfaceWidthHighWater: number | null; surfaceHeightHighWater: number | null; /** * The viewport size the two clamps were last valid for. The clamps follow a * grid resize by the DELTA against this, never by re-measuring — a re-measure * would fold in the scroll position and could shrink the box for a reason * unrelated to the resize. */ surfaceClampBasis: { width: number; height: number; } | null; } | null; } export interface GridFormulaPrivateApi { /** * Stores a formula as the cell's row-data value and re-evaluates. * @param {GridRowId} id The row id. * @param {string} field The column field. Must have `allowFormulas` enabled. * @param {string} formula The formula source, starting with `=`. */ setCellFormula: (id: GridRowId, field: string, formula: string) => void; /** * Returns the formula source stored in the cell's row data, * or `null` when the cell does not hold a formula. * @param {GridRowId} id The row id. * @param {string} field The column field. * @returns {string | null} The formula source, including the leading `=`. */ getCellFormula: (id: GridRowId, field: string) => string | null; /** * Returns the evaluated result of a formula cell, * or `null` when the cell does not hold a formula. * @param {GridRowId} id The row id. * @param {string} field The column field. * @returns {GridFormulaResult | null} The evaluation result. */ getCellFormulaResult: (id: GridRowId, field: string) => GridFormulaResult | null; /** * Statically validates a formula source against the current function registry. * Validation is informative — invalid formulas can still be committed. * @param {string} formula The formula source, with or without the leading `=`. * @returns {GridFormulaValidationResult} The validation result. */ validateCellFormula: (formula: string) => GridFormulaValidationResult; /** * Discards every formula cache, re-evaluates all formulas from scratch and * refreshes the features that consume formula values (aggregation, row * spanning, row grouping). Escape hatch for in-place row mutations the grid * cannot observe. */ reevaluateFormulas: () => void; /** * Sets (or clears with `null`) the cell whose formula references are * highlighted in the editor and outlined in the grid. * @param {GridFormulaActiveEdit | null} cell The cell being edited, or `null` to clear. */ setFormulaActiveEdit: (cell: GridFormulaActiveEdit | null) => void; /** * Computes the value to fill into `targetCell` when the fill handle or a * paste repeats `sourceCell`: a live formula has its relative references * shifted by the source→target positional delta (Excel fill semantics). * Returns `null` when the source is not a live formula or the target column * does not accept formulas — the caller copies the evaluated value instead. * Seam for the cell selection fill: registered by the injected formula * feature, absent otherwise. * @param {GridCellCoordinates} sourceCell The cell the fill originates from. * @param {GridCellCoordinates} targetCell The cell being filled. * @returns {string | null} The adjusted canonical formula source, or `null`. */ getFilledFormulaSource: (sourceCell: GridCellCoordinates, targetCell: GridCellCoordinates) => string | null; /** * Builds the layout that re-anchors live formulas to the exported Excel * sheet's A1 coordinates, or `null` when no exported column allows formulas. * Seam for the Excel export: registered by the injected formula feature, * absent otherwise. * @param {GridStateColDef[]} columns The exported columns, in export order. * @param {GridRowId[]} rowIds The exported row ids, in export order. * @param {object} options The header rows the export writes above the data. * @param {boolean} options.includeHeaders Whether the column-header row is written. * @param {boolean} options.includeColumnGroupsHeaders Whether column-group header rows are written. * @returns {FormulaExcelExportLayout | null} The layout, or `null`. */ createFormulaExcelExportLayout: (columns: GridStateColDef[], rowIds: GridRowId[], options: { includeHeaders: boolean; includeColumnGroupsHeaders: boolean; }) => FormulaExcelExportLayout | null; /** * Resolves the live Excel formula (with its cached result) for one exported * cell against the export layout, or `null` when the cell does not hold a * live formula. Seam for the Excel export: registered by the injected * formula feature, absent otherwise. * @param {FormulaExcelExportLayout} layout The layout built by `createFormulaExcelExportLayout`. * @param {GridRowId} id The row id. * @param {string} field The column field. * @returns {ExcelFormulaCell | null} The Excel formula cell, or `null`. */ getCellExcelFormula: (layout: FormulaExcelExportLayout, id: GridRowId, field: string) => ExcelFormulaCell | null; }