@toolbox-web/grid-angular 0.15.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -181,6 +181,38 @@ function injectGridUndoRedo() {
181
181
  },
182
182
  getUndoStack: () => getPlugin()?.getUndoStack() ?? [],
183
183
  getRedoStack: () => getPlugin()?.getRedoStack() ?? [],
184
+ recordEdit: (rowIndex, field, oldValue, newValue) => {
185
+ const plugin = getPlugin();
186
+ if (!plugin) {
187
+ console.warn(`[tbw-grid:undoRedo] UndoRedoPlugin not found.\n\n` +
188
+ ` → Enable undo/redo on the grid:\n` +
189
+ ` <tbw-grid [editing]="'dblclick'" [undoRedo]="true" />`);
190
+ return;
191
+ }
192
+ plugin.recordEdit(rowIndex, field, oldValue, newValue);
193
+ syncSignals();
194
+ },
195
+ beginTransaction: () => {
196
+ const plugin = getPlugin();
197
+ if (!plugin) {
198
+ console.warn(`[tbw-grid:undoRedo] UndoRedoPlugin not found.\n\n` +
199
+ ` → Enable undo/redo on the grid:\n` +
200
+ ` <tbw-grid [editing]="'dblclick'" [undoRedo]="true" />`);
201
+ return;
202
+ }
203
+ plugin.beginTransaction();
204
+ },
205
+ endTransaction: () => {
206
+ const plugin = getPlugin();
207
+ if (!plugin) {
208
+ console.warn(`[tbw-grid:undoRedo] UndoRedoPlugin not found.\n\n` +
209
+ ` → Enable undo/redo on the grid:\n` +
210
+ ` <tbw-grid [editing]="'dblclick'" [undoRedo]="true" />`);
211
+ return;
212
+ }
213
+ plugin.endTransaction();
214
+ syncSignals();
215
+ },
184
216
  };
185
217
  }
186
218
 
@@ -1 +1 @@
1
- {"version":3,"file":"toolbox-web-grid-angular-features-undo-redo.mjs","sources":["../../../../libs/grid-angular/features/undo-redo/src/index.ts","../../../../libs/grid-angular/features/undo-redo/src/toolbox-web-grid-angular-features-undo-redo.ts"],"sourcesContent":["/**\n * Undo/Redo feature for @toolbox-web/grid-angular\n *\n * Import this module to enable the `undoRedo` input on Grid directive.\n * Also exports `injectGridUndoRedo()` for programmatic undo/redo control.\n * Requires editing feature to be enabled.\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid-angular/features/editing';\n * import '@toolbox-web/grid-angular/features/undo-redo';\n *\n * <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />\n * ```\n *\n * @example Using injectGridUndoRedo\n * ```typescript\n * import { injectGridUndoRedo } from '@toolbox-web/grid-angular/features/undo-redo';\n *\n * @Component({...})\n * export class MyComponent {\n * private undoRedo = injectGridUndoRedo();\n *\n * undo() { this.undoRedo.undo(); }\n * redo() { this.undoRedo.redo(); }\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport { afterNextRender, DestroyRef, ElementRef, inject, signal, type Signal } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport { registerFeature } from '@toolbox-web/grid-angular';\nimport { UndoRedoPlugin, type EditAction } from '@toolbox-web/grid/plugins/undo-redo';\n\nregisterFeature('undoRedo', (config) => {\n if (config === true) {\n return new UndoRedoPlugin();\n }\n return new UndoRedoPlugin(config ?? undefined);\n});\n\n/**\n * Undo/Redo methods returned from injectGridUndoRedo.\n *\n * Uses lazy discovery - the grid is found on first method call, not during initialization.\n */\nexport interface UndoRedoMethods {\n /**\n * Undo the last edit action.\n * @returns The undone action, or null if nothing to undo\n */\n undo: () => EditAction | null;\n\n /**\n * Redo the last undone action.\n * @returns The redone action, or null if nothing to redo\n */\n redo: () => EditAction | null;\n\n /**\n * Reactive signal indicating whether undo is available.\n * Updates automatically when edits are made, undone, redone, or history is cleared.\n *\n * @example\n * ```typescript\n * // In template:\n * // <button [disabled]=\"!undoRedo.canUndo()\">Undo</button>\n *\n * // In computed:\n * readonly undoAvailable = computed(() => this.undoRedo.canUndo());\n * ```\n */\n canUndo: Signal<boolean>;\n\n /**\n * Reactive signal indicating whether redo is available.\n * Updates automatically when edits are made, undone, redone, or history is cleared.\n *\n * @example\n * ```typescript\n * // In template:\n * // <button [disabled]=\"!undoRedo.canRedo()\">Redo</button>\n *\n * // In computed:\n * readonly redoAvailable = computed(() => this.undoRedo.canRedo());\n * ```\n */\n canRedo: Signal<boolean>;\n\n /**\n * Clear all undo/redo history.\n */\n clearHistory: () => void;\n\n /**\n * Get a copy of the current undo stack.\n */\n getUndoStack: () => EditAction[];\n\n /**\n * Get a copy of the current redo stack.\n */\n getRedoStack: () => EditAction[];\n\n /**\n * Signal indicating if grid is ready.\n */\n isReady: Signal<boolean>;\n}\n\n/**\n * Angular inject function for programmatic undo/redo control.\n *\n * Uses **lazy grid discovery** - the grid element is found when methods are called,\n * not during initialization.\n *\n * @example\n * ```typescript\n * import { Component } from '@angular/core';\n * import { Grid } from '@toolbox-web/grid-angular';\n * import '@toolbox-web/grid-angular/features/editing';\n * import '@toolbox-web/grid-angular/features/undo-redo';\n * import { injectGridUndoRedo } from '@toolbox-web/grid-angular/features/undo-redo';\n *\n * @Component({\n * selector: 'app-my-grid',\n * imports: [Grid],\n * template: `\n * <button (click)=\"undoRedo.undo()\" [disabled]=\"!undoRedo.canUndo()\">Undo</button>\n * <button (click)=\"undoRedo.redo()\" [disabled]=\"!undoRedo.canRedo()\">Redo</button>\n * <tbw-grid [rows]=\"rows\" [editing]=\"'dblclick'\" [undoRedo]=\"true\"></tbw-grid>\n * `\n * })\n * export class MyGridComponent {\n * undoRedo = injectGridUndoRedo();\n * }\n * ```\n */\nexport function injectGridUndoRedo(): UndoRedoMethods {\n const elementRef = inject(ElementRef);\n const destroyRef = inject(DestroyRef);\n const isReady = signal(false);\n\n // Reactive undo/redo availability signals\n const canUndoSignal = signal(false);\n const canRedoSignal = signal(false);\n\n let cachedGrid: DataGridElement | null = null;\n let readyPromiseStarted = false;\n let listenerAttached = false;\n\n /**\n * Sync canUndo/canRedo signals with the current plugin state.\n */\n const syncSignals = (): void => {\n const plugin = getPlugin();\n if (plugin) {\n canUndoSignal.set(plugin.canUndo());\n canRedoSignal.set(plugin.canRedo());\n }\n };\n\n /**\n * Attach event listeners to the grid for undo/redo state changes.\n * Listens for `undo`, `redo`, and `cell-commit` DOM events.\n */\n const attachListeners = (grid: DataGridElement): void => {\n if (listenerAttached) return;\n listenerAttached = true;\n\n grid.addEventListener('undo', syncSignals);\n grid.addEventListener('redo', syncSignals);\n grid.addEventListener('cell-commit', syncSignals);\n\n destroyRef.onDestroy(() => {\n grid.removeEventListener('undo', syncSignals);\n grid.removeEventListener('redo', syncSignals);\n grid.removeEventListener('cell-commit', syncSignals);\n });\n };\n\n const getGrid = (): DataGridElement | null => {\n if (cachedGrid) return cachedGrid;\n\n const grid = elementRef.nativeElement.querySelector('tbw-grid') as DataGridElement | null;\n if (grid) {\n cachedGrid = grid;\n attachListeners(grid);\n if (!readyPromiseStarted) {\n readyPromiseStarted = true;\n grid.ready?.().then(() => isReady.set(true));\n }\n }\n return grid;\n };\n\n const getPlugin = (): UndoRedoPlugin | undefined => {\n return getGrid()?.getPlugin(UndoRedoPlugin);\n };\n\n // Eagerly discover the grid after the first render so event listeners\n // are attached and isReady updates without requiring a programmatic\n // method call. Falls back to MutationObserver for lazy-rendered content.\n afterNextRender(() => {\n const grid = getGrid();\n if (grid) {\n grid.ready?.().then(syncSignals);\n return;\n }\n\n const host = elementRef.nativeElement as HTMLElement;\n const observer = new MutationObserver(() => {\n const discovered = getGrid();\n if (discovered) {\n observer.disconnect();\n discovered.ready?.().then(syncSignals);\n }\n });\n observer.observe(host, { childList: true, subtree: true });\n\n destroyRef.onDestroy(() => observer.disconnect());\n });\n\n return {\n isReady: isReady.asReadonly(),\n canUndo: canUndoSignal.asReadonly(),\n canRedo: canRedoSignal.asReadonly(),\n\n undo: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:undoRedo] UndoRedoPlugin not found.\\n\\n` +\n ` → Enable undo/redo on the grid:\\n` +\n ` <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />`,\n );\n return null;\n }\n const result = plugin.undo();\n syncSignals();\n return result;\n },\n\n redo: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:undoRedo] UndoRedoPlugin not found.\\n\\n` +\n ` → Enable undo/redo on the grid:\\n` +\n ` <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />`,\n );\n return null;\n }\n const result = plugin.redo();\n syncSignals();\n return result;\n },\n\n clearHistory: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:undoRedo] UndoRedoPlugin not found.\\n\\n` +\n ` → Enable undo/redo on the grid:\\n` +\n ` <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />`,\n );\n return;\n }\n plugin.clearHistory();\n syncSignals();\n },\n\n getUndoStack: () => getPlugin()?.getUndoStack() ?? [],\n\n getRedoStack: () => getPlugin()?.getRedoStack() ?? [],\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"toolbox-web-grid-angular-features-undo-redo.mjs","sources":["../../../../libs/grid-angular/features/undo-redo/src/index.ts","../../../../libs/grid-angular/features/undo-redo/src/toolbox-web-grid-angular-features-undo-redo.ts"],"sourcesContent":["/**\n * Undo/Redo feature for @toolbox-web/grid-angular\n *\n * Import this module to enable the `undoRedo` input on Grid directive.\n * Also exports `injectGridUndoRedo()` for programmatic undo/redo control.\n * Requires editing feature to be enabled.\n *\n * @example\n * ```typescript\n * import '@toolbox-web/grid-angular/features/editing';\n * import '@toolbox-web/grid-angular/features/undo-redo';\n *\n * <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />\n * ```\n *\n * @example Using injectGridUndoRedo\n * ```typescript\n * import { injectGridUndoRedo } from '@toolbox-web/grid-angular/features/undo-redo';\n *\n * @Component({...})\n * export class MyComponent {\n * private undoRedo = injectGridUndoRedo();\n *\n * undo() { this.undoRedo.undo(); }\n * redo() { this.undoRedo.redo(); }\n * }\n * ```\n *\n * @packageDocumentation\n */\n\nimport { afterNextRender, DestroyRef, ElementRef, inject, signal, type Signal } from '@angular/core';\nimport type { DataGridElement } from '@toolbox-web/grid';\nimport { registerFeature } from '@toolbox-web/grid-angular';\nimport { UndoRedoPlugin, type UndoRedoAction } from '@toolbox-web/grid/plugins/undo-redo';\n\nregisterFeature('undoRedo', (config) => {\n if (config === true) {\n return new UndoRedoPlugin();\n }\n return new UndoRedoPlugin(config ?? undefined);\n});\n\n/**\n * Undo/Redo methods returned from injectGridUndoRedo.\n *\n * Uses lazy discovery - the grid is found on first method call, not during initialization.\n */\nexport interface UndoRedoMethods {\n /**\n * Undo the last edit action.\n * @returns The undone action (or compound action), or null if nothing to undo\n */\n undo: () => UndoRedoAction | null;\n\n /**\n * Redo the last undone action.\n * @returns The redone action (or compound action), or null if nothing to redo\n */\n redo: () => UndoRedoAction | null;\n\n /**\n * Reactive signal indicating whether undo is available.\n * Updates automatically when edits are made, undone, redone, or history is cleared.\n *\n * @example\n * ```typescript\n * // In template:\n * // <button [disabled]=\"!undoRedo.canUndo()\">Undo</button>\n *\n * // In computed:\n * readonly undoAvailable = computed(() => this.undoRedo.canUndo());\n * ```\n */\n canUndo: Signal<boolean>;\n\n /**\n * Reactive signal indicating whether redo is available.\n * Updates automatically when edits are made, undone, redone, or history is cleared.\n *\n * @example\n * ```typescript\n * // In template:\n * // <button [disabled]=\"!undoRedo.canRedo()\">Redo</button>\n *\n * // In computed:\n * readonly redoAvailable = computed(() => this.undoRedo.canRedo());\n * ```\n */\n canRedo: Signal<boolean>;\n\n /**\n * Clear all undo/redo history.\n */\n clearHistory: () => void;\n\n /**\n * Get a copy of the current undo stack.\n */\n getUndoStack: () => UndoRedoAction[];\n\n /**\n * Get a copy of the current redo stack.\n */\n getRedoStack: () => UndoRedoAction[];\n\n /**\n * Manually record an edit action.\n * If a transaction is active, the action is buffered; otherwise it's pushed to the undo stack.\n */\n recordEdit: (rowIndex: number, field: string, oldValue: unknown, newValue: unknown) => void;\n\n /**\n * Begin a transaction. All edits recorded until `endTransaction()` are grouped\n * into a single compound action for undo/redo.\n * @throws If a transaction is already active\n */\n beginTransaction: () => void;\n\n /**\n * End the active transaction and push the compound action to the undo stack.\n * If only one edit was recorded, it's pushed as a plain EditAction.\n * If no edits were recorded, the transaction is discarded.\n * @throws If no transaction is active\n */\n endTransaction: () => void;\n\n /**\n * Signal indicating if grid is ready.\n */\n isReady: Signal<boolean>;\n}\n\n/**\n * Angular inject function for programmatic undo/redo control.\n *\n * Uses **lazy grid discovery** - the grid element is found when methods are called,\n * not during initialization.\n *\n * @example\n * ```typescript\n * import { Component } from '@angular/core';\n * import { Grid } from '@toolbox-web/grid-angular';\n * import '@toolbox-web/grid-angular/features/editing';\n * import '@toolbox-web/grid-angular/features/undo-redo';\n * import { injectGridUndoRedo } from '@toolbox-web/grid-angular/features/undo-redo';\n *\n * @Component({\n * selector: 'app-my-grid',\n * imports: [Grid],\n * template: `\n * <button (click)=\"undoRedo.undo()\" [disabled]=\"!undoRedo.canUndo()\">Undo</button>\n * <button (click)=\"undoRedo.redo()\" [disabled]=\"!undoRedo.canRedo()\">Redo</button>\n * <tbw-grid [rows]=\"rows\" [editing]=\"'dblclick'\" [undoRedo]=\"true\"></tbw-grid>\n * `\n * })\n * export class MyGridComponent {\n * undoRedo = injectGridUndoRedo();\n * }\n * ```\n */\nexport function injectGridUndoRedo(): UndoRedoMethods {\n const elementRef = inject(ElementRef);\n const destroyRef = inject(DestroyRef);\n const isReady = signal(false);\n\n // Reactive undo/redo availability signals\n const canUndoSignal = signal(false);\n const canRedoSignal = signal(false);\n\n let cachedGrid: DataGridElement | null = null;\n let readyPromiseStarted = false;\n let listenerAttached = false;\n\n /**\n * Sync canUndo/canRedo signals with the current plugin state.\n */\n const syncSignals = (): void => {\n const plugin = getPlugin();\n if (plugin) {\n canUndoSignal.set(plugin.canUndo());\n canRedoSignal.set(plugin.canRedo());\n }\n };\n\n /**\n * Attach event listeners to the grid for undo/redo state changes.\n * Listens for `undo`, `redo`, and `cell-commit` DOM events.\n */\n const attachListeners = (grid: DataGridElement): void => {\n if (listenerAttached) return;\n listenerAttached = true;\n\n grid.addEventListener('undo', syncSignals);\n grid.addEventListener('redo', syncSignals);\n grid.addEventListener('cell-commit', syncSignals);\n\n destroyRef.onDestroy(() => {\n grid.removeEventListener('undo', syncSignals);\n grid.removeEventListener('redo', syncSignals);\n grid.removeEventListener('cell-commit', syncSignals);\n });\n };\n\n const getGrid = (): DataGridElement | null => {\n if (cachedGrid) return cachedGrid;\n\n const grid = elementRef.nativeElement.querySelector('tbw-grid') as DataGridElement | null;\n if (grid) {\n cachedGrid = grid;\n attachListeners(grid);\n if (!readyPromiseStarted) {\n readyPromiseStarted = true;\n grid.ready?.().then(() => isReady.set(true));\n }\n }\n return grid;\n };\n\n const getPlugin = (): UndoRedoPlugin | undefined => {\n return getGrid()?.getPlugin(UndoRedoPlugin);\n };\n\n // Eagerly discover the grid after the first render so event listeners\n // are attached and isReady updates without requiring a programmatic\n // method call. Falls back to MutationObserver for lazy-rendered content.\n afterNextRender(() => {\n const grid = getGrid();\n if (grid) {\n grid.ready?.().then(syncSignals);\n return;\n }\n\n const host = elementRef.nativeElement as HTMLElement;\n const observer = new MutationObserver(() => {\n const discovered = getGrid();\n if (discovered) {\n observer.disconnect();\n discovered.ready?.().then(syncSignals);\n }\n });\n observer.observe(host, { childList: true, subtree: true });\n\n destroyRef.onDestroy(() => observer.disconnect());\n });\n\n return {\n isReady: isReady.asReadonly(),\n canUndo: canUndoSignal.asReadonly(),\n canRedo: canRedoSignal.asReadonly(),\n\n undo: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:undoRedo] UndoRedoPlugin not found.\\n\\n` +\n ` → Enable undo/redo on the grid:\\n` +\n ` <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />`,\n );\n return null;\n }\n const result = plugin.undo();\n syncSignals();\n return result;\n },\n\n redo: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:undoRedo] UndoRedoPlugin not found.\\n\\n` +\n ` → Enable undo/redo on the grid:\\n` +\n ` <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />`,\n );\n return null;\n }\n const result = plugin.redo();\n syncSignals();\n return result;\n },\n\n clearHistory: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:undoRedo] UndoRedoPlugin not found.\\n\\n` +\n ` → Enable undo/redo on the grid:\\n` +\n ` <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />`,\n );\n return;\n }\n plugin.clearHistory();\n syncSignals();\n },\n\n getUndoStack: () => getPlugin()?.getUndoStack() ?? [],\n\n getRedoStack: () => getPlugin()?.getRedoStack() ?? [],\n\n recordEdit: (rowIndex: number, field: string, oldValue: unknown, newValue: unknown) => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:undoRedo] UndoRedoPlugin not found.\\n\\n` +\n ` → Enable undo/redo on the grid:\\n` +\n ` <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />`,\n );\n return;\n }\n plugin.recordEdit(rowIndex, field, oldValue, newValue);\n syncSignals();\n },\n\n beginTransaction: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:undoRedo] UndoRedoPlugin not found.\\n\\n` +\n ` → Enable undo/redo on the grid:\\n` +\n ` <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />`,\n );\n return;\n }\n plugin.beginTransaction();\n },\n\n endTransaction: () => {\n const plugin = getPlugin();\n if (!plugin) {\n console.warn(\n `[tbw-grid:undoRedo] UndoRedoPlugin not found.\\n\\n` +\n ` → Enable undo/redo on the grid:\\n` +\n ` <tbw-grid [editing]=\"'dblclick'\" [undoRedo]=\"true\" />`,\n );\n return;\n }\n plugin.endTransaction();\n syncSignals();\n },\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -347,6 +347,7 @@ class GridFormArray {
347
347
  destroyRef = inject(DestroyRef);
348
348
  elementRef = inject((ElementRef));
349
349
  cellCommitListener = null;
350
+ cellCancelListener = null;
350
351
  rowCommitListener = null;
351
352
  touchListener = null;
352
353
  valueChangesSubscription = null;
@@ -410,6 +411,12 @@ class GridFormArray {
410
411
  this.#handleCellCommit(detail);
411
412
  };
412
413
  grid.addEventListener('cell-commit', this.cellCommitListener);
414
+ // Intercept cell-cancel events to revert FormControls (grid mode Escape)
415
+ this.cellCancelListener = (e) => {
416
+ const detail = e.detail;
417
+ this.#handleCellCancel(detail);
418
+ };
419
+ grid.addEventListener('cell-cancel', this.cellCancelListener);
413
420
  // Intercept row-commit events to prevent if FormGroup is invalid
414
421
  this.rowCommitListener = (e) => {
415
422
  if (!this.syncValidation())
@@ -445,6 +452,9 @@ class GridFormArray {
445
452
  if (this.cellCommitListener) {
446
453
  grid.removeEventListener('cell-commit', this.cellCommitListener);
447
454
  }
455
+ if (this.cellCancelListener) {
456
+ grid.removeEventListener('cell-cancel', this.cellCancelListener);
457
+ }
448
458
  if (this.rowCommitListener) {
449
459
  grid.removeEventListener('row-commit', this.rowCommitListener);
450
460
  }
@@ -684,6 +694,21 @@ class GridFormArray {
684
694
  event.preventDefault();
685
695
  }
686
696
  }
697
+ /**
698
+ * Handles cell-cancel events (grid mode Escape) — reverts the FormControl
699
+ * to the value it had before the edit session began.
700
+ */
701
+ #handleCellCancel(detail) {
702
+ const { rowIndex, field, previousValue } = detail;
703
+ const rowFormGroup = this.#getRowFormGroup(rowIndex);
704
+ if (rowFormGroup) {
705
+ const control = rowFormGroup.get(field);
706
+ if (control) {
707
+ control.setValue(previousValue, { emitEvent: false });
708
+ control.markAsPristine();
709
+ }
710
+ }
711
+ }
687
712
  /**
688
713
  * Syncs a FormControl's validation state to the grid's visual invalid styling.
689
714
  */
@@ -2026,6 +2051,13 @@ class GridAdapter {
2026
2051
  // This keeps Angular component editors in sync without manual DOM patching.
2027
2052
  ctx.onValueChange?.((newVal) => {
2028
2053
  try {
2054
+ // Notify the editor so it can clear stale internal state (e.g., searchText
2055
+ // in autocomplete editors) before the value input updates. This ensures the
2056
+ // template reads fresh state during the synchronous detectChanges() below.
2057
+ const instance = componentRef.instance;
2058
+ if (typeof instance['onExternalValueChange'] === 'function') {
2059
+ instance.onExternalValueChange(newVal);
2060
+ }
2029
2061
  componentRef.setInput('value', newVal);
2030
2062
  componentRef.changeDetectorRef.detectChanges();
2031
2063
  }
@@ -2789,6 +2821,7 @@ class BaseGridEditor {
2789
2821
  // ============================================================================
2790
2822
  /**
2791
2823
  * Emits when the user commits a new value.
2824
+ * Emits `null` when a nullable field is cleared.
2792
2825
  */
2793
2826
  commit = output();
2794
2827
  /**
@@ -2920,6 +2953,22 @@ class BaseGridEditor {
2920
2953
  onEditClose() {
2921
2954
  // Default: no-op. Subclasses override.
2922
2955
  }
2956
+ /**
2957
+ * Called by the grid adapter when the cell value changes externally
2958
+ * (e.g., via `updateRow()` cascade or undo/redo).
2959
+ *
2960
+ * Override in subclasses to reset internal state (search text, selection
2961
+ * flags, etc.) so the editor displays the updated value.
2962
+ *
2963
+ * This runs **synchronously** before the value input is updated, giving
2964
+ * the editor a chance to clear stale state before the next change-detection
2965
+ * pass re-reads the template.
2966
+ *
2967
+ * @param _newVal The new cell value being pushed from the grid
2968
+ */
2969
+ onExternalValueChange(_newVal) {
2970
+ // Default: no-op. Subclasses override as needed.
2971
+ }
2923
2972
  /**
2924
2973
  * Commit a new value. Emits the commit output AND dispatches a DOM event.
2925
2974
  * The DOM event enables the grid's auto-wiring to catch the commit.
@@ -3113,10 +3162,9 @@ class BaseGridEditorCVA extends BaseGridEditor {
3113
3162
  this.cvaValue.set(value);
3114
3163
  this._onChange(value);
3115
3164
  this._onTouched();
3116
- // Update grid
3117
- if (value != null) {
3118
- this.commitValue(value);
3119
- }
3165
+ // Update grid (null is valid for nullable columns — the grid's cell-commit
3166
+ // handler and EditingPlugin will apply it correctly)
3167
+ this.commitValue(value);
3120
3168
  }
3121
3169
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.1", ngImport: i0, type: BaseGridEditorCVA, deps: null, target: i0.ɵɵFactoryTarget.Directive });
3122
3170
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.1", type: BaseGridEditorCVA, isStandalone: true, usesInheritance: true, ngImport: i0 });