/** * The editing orchestrator. * * Owns the lifetime of an edit: resolve an editor for the cell, build it, mount * it, run validation on the way out, and write the value through the grid's * normal value pipeline. Every collaborator arrives through the constructor — * resolver, validation engine, host, keyboard, store, event bus — so this class * composes services rather than reaching for globals, and a test can drive it * with stubs. * * ### Why one place * The behaviours that used to be scattered across `CellEditorEngine` and * `GridCore.wireEditing` — the `editingCellId` store key, the `pg-cell--editing` * class, the commit flash, the formula delegate, Tab navigation — are all * transitions of the same state machine. Keeping them together is what makes it * possible to reason about "what happens when the user presses Escape during an * async validation". * * @packageDocumentation */ import type { ColumnDef } from '../../types/column.types'; import type { RowNode } from '../../types/row.types'; import type { GridStore } from '../../core/grid-store'; import type { EventBus } from '../../event-bus/event-bus'; import type { EditTrigger } from '../types/cell-editor.types'; import type { ValidationResult } from '../types/validation.types'; import { type EditingConfig, type ResolvedEditingConfig } from '../types/editing-config.types'; import type { EditorResolver } from '../registry/default-editor-resolver'; import type { ValidationEngine } from '../validation/validation-engine'; import type { EditorHost } from '../services/editor-host'; import type { KeyboardManager } from '../services/keyboard-manager'; import { type EditSession } from './edit-session'; /** Collaborators an {@link EditorManager} is composed from. */ export interface EditorManagerDeps { readonly store: GridStore; readonly eventBus: EventBus; readonly resolver: EditorResolver; readonly validation: ValidationEngine; readonly host: EditorHost; readonly keyboard: KeyboardManager; /** Supplies the live `GridApi`, which does not exist yet at construction time. */ readonly getApi: () => unknown; } /** A request to open an editor. */ export interface StartEditRequest { readonly rowNode: RowNode; readonly colDef: ColumnDef; readonly cellEl: HTMLElement; /** Defaults to the cell's `.pg-cell__inner`, which is where inline editors go. */ readonly innerEl?: HTMLElement; /** @default 'api' */ readonly trigger?: EditTrigger; /** The character that opened a `'type'` session. */ readonly eventKey?: string | null; /** * Value the editor opens with, when it differs from the cell's stored value. * * The one real use is a formula cell: the grid stores the *computed* result but * the editor must show the *source* (`=A1+B1`), or editing a formula would * silently replace it with its own output. */ readonly editValue?: unknown; /** * Column definition used for **editor resolution only**, when it differs from * the column being edited. * * Again, formulas: a formula-enabled number column has to open a text editor so * a leading `=` can be typed at all. The commit path deliberately keeps using * the real {@link colDef}, so the value is still parsed as a number. */ readonly resolveAs?: ColumnDef; } /** * Why a session is being closed. * * The distinction exists because "the user pressed Enter" and "the user clicked * a different cell" are different instructions, and treating them alike is what * used to leave a grid with two cells outlined at once: * * - `'explicit'` — the user asked to finish *here*: Enter, Tab, or an editor * calling `params.commit()`. They are still on the cell, so an editor may * legitimately stay open — that is what `onInvalid: 'keep-open'` is for — and * waiting for an asynchronous rule to answer is reasonable. * - `'navigate'` — the user has already left: a click on another cell, focus * moving out, a column being resized or moved. The editor must come down * *now*, whatever validation is still doing, because the cell it belongs to is * no longer the cell the user is looking at. */ export type CommitReason = 'explicit' | 'navigate'; export declare class EditorManager { private readonly deps; private session; private config; /** * Delegate that owns committing a formula on a formula-enabled column. * Registered by `GridCore` only when the Formula Engine is switched on. */ private formulaCommit; /** Moves the selection to the adjacent editable cell; registered by `GridCore`. */ private tabHandler; constructor(deps: EditorManagerDeps); /** Applies `GridOptions.editing`, filling in every documented default. */ configure(config: Partial): void; /** The configuration currently in force. */ getConfig(): ResolvedEditingConfig; /** Registers the Tab-navigation delegate. @see tabHandler */ setTabHandler(fn: (shiftKey: boolean) => void): void; /** Registers the formula-commit delegate. @see formulaCommit */ setFormulaCommitHandler(fn: (rowNode: RowNode, colDef: ColumnDef, source: string) => boolean): void; /** `true` while any cell is being edited. */ isEditing(): boolean; /** `true` when this specific cell is the one being edited. */ isCellEditing(nodeId: string, colId: string): boolean; /** The open session, or `null`. Exposed for the deprecated compatibility facade. */ getActiveSession(): EditSession | null; /** * Records a value reported by the editor. * * Advisory — {@link ICellEditor.getValue} still wins at commit. Under * `validateOn: 'change'` this also schedules a debounced validation pass so * the user sees the failure as they type rather than only when they leave. */ updateValue(value: unknown): void; /** * Opens an editor on a cell. * * Returns `false` — having changed nothing — when the grid is not editable, * when the resolver declines the column, or when the editor vetoes itself * through `isCancelBeforeStart`. An already-open session is committed first, * which is what makes clicking straight from one cell to another behave the * way a spreadsheet does. * * The editor's `init` may be asynchronous; the session is registered * immediately so a cancel arriving mid-`init` is honoured, and the mount is * abandoned if the session was superseded while awaiting. * * @returns `true` when a session was opened (or is opening, for an async editor). */ startEdit(request: StartEditRequest): boolean; /** * Mounts a successfully-initialised editor and announces the session. * * Split from {@link startEdit} so the synchronous and asynchronous `init` * paths converge on exactly one implementation. */ private finishStart; /** * Validates and writes the open editor's value. * * ### Closing is not negotiable when the user has left * On `'navigate'` the session always ends, synchronously, before this returns. * Asynchronous rules keep running and their answer is applied when it lands * (see {@link applyDeferredCommit}); a failure reverts the cell and is * reported, rather than pinning an error to an editor that is no longer on * screen. This is what stops a cell keeping its editing border for the length * of a server round trip while the cell the user actually clicked already has * the active-cell border — two outlined cells at once. * * ### On `'explicit'` the editor may stay * Enter means "finish here", so waiting for an async rule and holding an * invalid value open for correction — see `EditingConfig.onInvalid` — are both * the right answer. The user has not gone anywhere. * * @param reason - Why the session is closing. @default 'explicit' */ commit(reason?: CommitReason): void; /** * Runs the editor's own check and then the column's rules. * * Short-circuits: a column rule never runs against a value the editor itself * rejected, and an async editor check chains into the column rules so the * caller sees one settled answer either way. */ private validateForCommit; /** Applies a settled verdict to a session that is still the open one. */ private settleCommit; /** * Applies an async verdict that arrived after the editor was already taken * down, because the user navigated away mid-validation. * * A passing value is written exactly as a synchronous commit would have * written it. A failing one is not: the cell keeps what it had, the failure is * flashed and reported, and `CELL_EDIT_STOP` carries the message so an * application can react. */ private applyDeferredCommit; /** Abandons the session; the cell keeps the value it had before editing. */ cancel(): void; /** * Announces that an edit session has ended. * * The one place `CELL_EDIT_STOP` is emitted, because every caller has to obey * the same two rules: emit it once, and emit it only once the editor is * already unmounted. The grid repaints the edited cell on this event, so an * emit that races the teardown leaves the cell rendering its value twice. * * @param newValue - What the cell ended up with: the written value, or the * original one for a cancel or a rejection. * @param extra - `error` for a rejected value, `cancelled` for an abandoned * session. Omitted entirely for an ordinary successful commit. */ private emitEditStop; /** * Closes the session, committing unless `cancel` is `true`. * * The signature the legacy `CellEditorEngine.stopEditing` had, so the * compatibility facade is a straight delegation. */ stopEditing(cancel?: boolean): void; /** * Validates and writes a value without ever opening an editor. * * The path for edits whose "editor" is the rendered cell itself — a checkbox * or switch that toggles in place — and for programmatic writes that should * still behave like a user edit. It runs the identical validation, parsing, * value-setter, event and flash sequence a committed session runs, which is * what stops in-cell toggles from quietly bypassing a column's rules. * * Refuses when the grid is not editable, the column is locked or read-only, or * validation fails. * * @returns `true` when the value was written. */ commitValue(rowNode: RowNode, colDef: ColumnDef, value: unknown, cellEl?: HTMLElement): boolean; /** Releases every resource the manager owns. Called when the grid is destroyed. */ destroy(): void; /** * Runs the column's rules against a candidate value. * * Public so `GridApi.validateCell` can ask the same question without opening * an editor — one implementation, so an API check and a real commit can never * disagree. */ validateValue(rowNode: RowNode, colDef: ColumnDef, value: unknown): ValidationResult | Promise; /** Builds the context and defers to the engine. */ private runValidation; private buildValidationContext; /** Reflects a validation outcome on the open editor without closing it. */ private applyValidity; /** * Applies the configured reaction to a failed commit. * * `'keep-open'` (the default) annotates and waits — but only while the user is * still on the cell. Once they have navigated away there is nothing to keep * open: holding a rejected editor on a cell the user has left is what stranded * the grid in edit mode, so a `'navigate'` failure always closes and reverts, * and says why through the toast and the live region instead. * * `'revert'` discards the value. `'accept'` writes it anyway and leaves the * cell flagged, for flows that would rather capture bad data than block the * operator — and that stays true whichever way the user left. * * ### `CELL_EDIT_STOP` follows the session, not the failure * The event fires only on the paths that actually end the session, and always * after the editor has come down. A `'keep-open'` failure emits nothing: the * edit is still in progress, and the failure reaches the user through the * cell's red pulse, the live region and the toast instead. */ private handleInvalid; /** * Surfaces a failure on a cell whose editor has already gone: the red pulse, * the toast and the screen-reader announcement, without the `aria-invalid` * that belongs to a live control. */ private reportInvalid; /** Reads the authoritative value from the editor, falling back to the reported one. */ private readValue; /** * Writes a committed value through the grid's value pipeline. * * Split from {@link writeAndClose} because a value validated asynchronously is * written *after* its editor has already been taken down — the session is over * by the time the verdict arrives, but the write still has to happen exactly * the way a synchronous one would. * * The write goes through `setCellValue`, so a column `valueSetter` still owns * the assignment, and onto a fresh `data` object, preserving the grid's * one-new-reference-per-edit contract. * * @returns The value that was written — the formula source for a formula * cell, the parsed value otherwise. */ private writeValue; /** * Writes the committed value and ends the session. * * @param error - Set only on the `onInvalid: 'accept'` path, where a rejected * value is written deliberately and the message travels with the one event * that closing emits. */ private writeAndClose; /** Unmounts the editor and clears every trace of the session. */ private teardown; /** * Plays the fill-flash confirmation on a committed cell. * * Deferred a task and restarted by removing the class first, so two edits in * quick succession each get their own visible flash rather than the second * being swallowed by the first animation still running. */ private flashCell; /** Assembles the frozen bag handed to `ICellEditor.init`. */ private buildParams; /** * Resolves `ColumnDef.cellEditorParams`, calling the function form. * * A throwing params function degrades to `{}` rather than killing the session: * an editor with default options is far better than a cell that refuses to * open. */ private resolveEditorParams; } //# sourceMappingURL=editor-manager.d.ts.map