@toolbox-web/grid 1.26.1 → 1.27.0

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.
Files changed (114) hide show
  1. package/all.js +2 -2
  2. package/all.js.map +1 -1
  3. package/index.js +1 -1
  4. package/index.js.map +1 -1
  5. package/lib/core/grid.d.ts +64 -1
  6. package/lib/core/internal/diagnostics.d.ts +4 -3
  7. package/lib/core/internal/row-manager.d.ts +3 -1
  8. package/lib/core/plugin/base-plugin.d.ts +2 -2
  9. package/lib/core/types.d.ts +59 -3
  10. package/lib/features/registry.js.map +1 -1
  11. package/lib/plugins/clipboard/ClipboardPlugin.d.ts +1 -1
  12. package/lib/plugins/clipboard/index.js.map +1 -1
  13. package/lib/plugins/column-virtualization/index.js.map +1 -1
  14. package/lib/plugins/context-menu/ContextMenuPlugin.d.ts +24 -5
  15. package/lib/plugins/context-menu/index.js.map +1 -1
  16. package/lib/plugins/editing/EditingPlugin.d.ts +1 -1
  17. package/lib/plugins/editing/index.js.map +1 -1
  18. package/lib/plugins/export/ExportPlugin.d.ts +1 -1
  19. package/lib/plugins/export/index.js.map +1 -1
  20. package/lib/plugins/filtering/index.d.ts +2 -2
  21. package/lib/plugins/filtering/index.js +1 -1
  22. package/lib/plugins/filtering/index.js.map +1 -1
  23. package/lib/plugins/grouping-columns/GroupingColumnsPlugin.d.ts +2 -2
  24. package/lib/plugins/grouping-columns/grouping-columns.d.ts +18 -3
  25. package/lib/plugins/grouping-columns/index.d.ts +0 -1
  26. package/lib/plugins/grouping-columns/index.js +1 -1
  27. package/lib/plugins/grouping-columns/index.js.map +1 -1
  28. package/lib/plugins/grouping-columns/types.d.ts +1 -7
  29. package/lib/plugins/grouping-rows/index.d.ts +2 -1
  30. package/lib/plugins/grouping-rows/index.js +1 -1
  31. package/lib/plugins/grouping-rows/index.js.map +1 -1
  32. package/lib/plugins/master-detail/MasterDetailPlugin.d.ts +2 -0
  33. package/lib/plugins/master-detail/index.js +1 -1
  34. package/lib/plugins/master-detail/index.js.map +1 -1
  35. package/lib/plugins/master-detail/types.d.ts +20 -1
  36. package/lib/plugins/multi-sort/index.js.map +1 -1
  37. package/lib/plugins/pinned-columns/PinnedColumnsPlugin.d.ts +8 -1
  38. package/lib/plugins/pinned-columns/index.js +1 -1
  39. package/lib/plugins/pinned-columns/index.js.map +1 -1
  40. package/lib/plugins/pinned-columns/pinned-columns.d.ts +11 -1
  41. package/lib/plugins/pinned-rows/index.d.ts +1 -1
  42. package/lib/plugins/pinned-rows/index.js +1 -1
  43. package/lib/plugins/pinned-rows/index.js.map +1 -1
  44. package/lib/plugins/pinned-rows/types.d.ts +10 -3
  45. package/lib/plugins/pivot/PivotPlugin.d.ts +107 -1
  46. package/lib/plugins/pivot/index.d.ts +2 -1
  47. package/lib/plugins/pivot/index.js +1 -1
  48. package/lib/plugins/pivot/index.js.map +1 -1
  49. package/lib/plugins/print/index.js.map +1 -1
  50. package/lib/plugins/print/types.d.ts +0 -3
  51. package/lib/plugins/reorder-columns/ReorderPlugin.d.ts +19 -2
  52. package/lib/plugins/reorder-columns/index.js +1 -1
  53. package/lib/plugins/reorder-columns/index.js.map +1 -1
  54. package/lib/plugins/reorder-rows/index.d.ts +1 -1
  55. package/lib/plugins/reorder-rows/index.js.map +1 -1
  56. package/lib/plugins/responsive/ResponsivePlugin.d.ts +1 -1
  57. package/lib/plugins/responsive/index.js +1 -1
  58. package/lib/plugins/responsive/index.js.map +1 -1
  59. package/lib/plugins/selection/SelectionPlugin.d.ts +1 -1
  60. package/lib/plugins/selection/index.js +1 -1
  61. package/lib/plugins/selection/index.js.map +1 -1
  62. package/lib/plugins/selection/types.d.ts +3 -3
  63. package/lib/plugins/server-side/ServerSidePlugin.d.ts +6 -1
  64. package/lib/plugins/server-side/index.js +1 -1
  65. package/lib/plugins/server-side/index.js.map +1 -1
  66. package/lib/plugins/tree/TreePlugin.d.ts +116 -0
  67. package/lib/plugins/tree/index.d.ts +1 -1
  68. package/lib/plugins/tree/index.js +1 -1
  69. package/lib/plugins/tree/index.js.map +1 -1
  70. package/lib/plugins/tree/types.d.ts +16 -1
  71. package/lib/plugins/undo-redo/UndoRedoPlugin.d.ts +1 -1
  72. package/lib/plugins/undo-redo/index.js.map +1 -1
  73. package/lib/plugins/undo-redo/types.d.ts +15 -3
  74. package/lib/plugins/visibility/VisibilityPlugin.d.ts +18 -5
  75. package/lib/plugins/visibility/index.js +1 -1
  76. package/lib/plugins/visibility/index.js.map +1 -1
  77. package/package.json +1 -1
  78. package/public.d.ts +2 -4
  79. package/themes/dg-theme-material.css +16 -4
  80. package/umd/grid.all.umd.js +1 -1
  81. package/umd/grid.all.umd.js.map +1 -1
  82. package/umd/grid.umd.js +1 -1
  83. package/umd/grid.umd.js.map +1 -1
  84. package/umd/plugins/clipboard.umd.js.map +1 -1
  85. package/umd/plugins/context-menu.umd.js.map +1 -1
  86. package/umd/plugins/editing.umd.js.map +1 -1
  87. package/umd/plugins/export.umd.js.map +1 -1
  88. package/umd/plugins/filtering.umd.js +1 -1
  89. package/umd/plugins/filtering.umd.js.map +1 -1
  90. package/umd/plugins/grouping-columns.umd.js +1 -1
  91. package/umd/plugins/grouping-columns.umd.js.map +1 -1
  92. package/umd/plugins/grouping-rows.umd.js +1 -1
  93. package/umd/plugins/grouping-rows.umd.js.map +1 -1
  94. package/umd/plugins/master-detail.umd.js +1 -1
  95. package/umd/plugins/master-detail.umd.js.map +1 -1
  96. package/umd/plugins/pinned-columns.umd.js +1 -1
  97. package/umd/plugins/pinned-columns.umd.js.map +1 -1
  98. package/umd/plugins/pinned-rows.umd.js +1 -1
  99. package/umd/plugins/pinned-rows.umd.js.map +1 -1
  100. package/umd/plugins/pivot.umd.js +1 -1
  101. package/umd/plugins/pivot.umd.js.map +1 -1
  102. package/umd/plugins/reorder-columns.umd.js +1 -1
  103. package/umd/plugins/reorder-columns.umd.js.map +1 -1
  104. package/umd/plugins/responsive.umd.js +1 -1
  105. package/umd/plugins/responsive.umd.js.map +1 -1
  106. package/umd/plugins/selection.umd.js +1 -1
  107. package/umd/plugins/selection.umd.js.map +1 -1
  108. package/umd/plugins/server-side.umd.js +1 -1
  109. package/umd/plugins/server-side.umd.js.map +1 -1
  110. package/umd/plugins/tree.umd.js +1 -1
  111. package/umd/plugins/tree.umd.js.map +1 -1
  112. package/umd/plugins/undo-redo.umd.js.map +1 -1
  113. package/umd/plugins/visibility.umd.js +1 -1
  114. package/umd/plugins/visibility.umd.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"editing.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/editing/editors.ts","../../../../../libs/grid/src/lib/plugins/editing/internal/cell-validation.ts","../../../../../libs/grid/src/lib/plugins/editing/internal/dirty-tracking.ts","../../../../../libs/grid/src/lib/plugins/editing/internal/dirty-tracking-manager.ts","../../../../../libs/grid/src/lib/plugins/editing/internal/helpers.ts","../../../../../libs/grid/src/lib/plugins/editing/internal/editor-injection.ts","../../../../../libs/grid/src/lib/plugins/editing/EditingPlugin.ts"],"sourcesContent":["/**\n * Default Editors Module\n *\n * Provides built-in editor factories for different column types.\n * Each editor type has its own factory function for consistency and readability.\n *\n * IMPORTANT: Editor factories should NOT call focus() on elements - they are called\n * before the element is appended to the DOM. The calling code (beginBulkEdit,\n * inlineEnterEdit) is responsible for focusing the correct editor after insertion.\n */\n\nimport type { ColumnConfig, ColumnEditorContext } from '../../core/types';\nimport type { DateEditorParams, NumberEditorParams, SelectEditorParams, TextEditorParams } from './types';\n\n// ============================================================================\n// Type Aliases\n// ============================================================================\n\n/** Option shape used by select editor (matches column.options) */\ntype ColumnOption = { label: string; value: unknown };\n\n/** Column with any row type (used for editor factories) */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyColumn = ColumnConfig<any>;\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/** Resolve column.options (handles both array and function forms) */\nfunction resolveOptions(column: AnyColumn): ColumnOption[] {\n const raw = column.options;\n if (!raw) return [];\n return typeof raw === 'function' ? raw() : raw;\n}\n\n/** Format a Date as YYYY-MM-DD string */\nfunction toISODate(date: Date): string {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, '0');\n const d = String(date.getDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\n// ============================================================================\n// Editor Factories\n// ============================================================================\n\n/** Creates a number input editor */\nfunction createNumberEditor(column: AnyColumn): (ctx: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as NumberEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'number';\n input.value = ctx.value != null ? String(ctx.value) : '';\n\n if (params?.min !== undefined) input.min = String(params.min);\n if (params?.max !== undefined) input.max = String(params.max);\n if (params?.step !== undefined) input.step = String(params.step);\n if (params?.placeholder) input.placeholder = params.placeholder;\n\n const commit = () => {\n if (input.value === '') {\n if (column.nullable) {\n ctx.commit(null);\n } else {\n // Non-nullable: fall back to min value or 0\n ctx.commit(params?.min ?? 0);\n }\n } else {\n ctx.commit(Number(input.value));\n }\n };\n input.addEventListener('blur', commit);\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') commit();\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return input;\n };\n}\n\n/** Creates a checkbox editor for boolean values */\nfunction createBooleanEditor(): (ctx: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const input = document.createElement('input');\n input.type = 'checkbox';\n input.checked = !!ctx.value;\n input.addEventListener('change', () => ctx.commit(input.checked));\n return input;\n };\n}\n\n/** Creates a date input editor */\nfunction createDateEditor(column: AnyColumn): (ctx: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as DateEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'date';\n\n // Set initial value - handle both Date objects and string dates\n if (ctx.value instanceof Date) {\n input.valueAsDate = ctx.value;\n } else if (typeof ctx.value === 'string' && ctx.value) {\n // String date like \"2019-10-09\" - set directly as value\n input.value = ctx.value.split('T')[0]; // Handle ISO strings too\n }\n if (params?.min) input.min = params.min;\n if (params?.max) input.max = params.max;\n if (params?.placeholder) input.placeholder = params.placeholder;\n\n // Commit function preserves original type (string vs Date)\n const commit = () => {\n if (!input.value) {\n if (column.nullable) {\n ctx.commit(null);\n } else {\n // Non-nullable: use configured default or today\n const fallback = params?.default;\n if (typeof ctx.value === 'string' || typeof fallback === 'string') {\n // String mode: use default string or today as YYYY-MM-DD\n ctx.commit(typeof fallback === 'string' ? fallback : toISODate(fallback ?? new Date()));\n } else {\n // Date object mode\n ctx.commit(fallback instanceof Date ? fallback : new Date());\n }\n }\n return;\n }\n if (typeof ctx.value === 'string') {\n // Original was string, return string in YYYY-MM-DD format\n ctx.commit(input.value);\n } else {\n ctx.commit(input.valueAsDate);\n }\n };\n\n input.addEventListener('change', commit);\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return input;\n };\n}\n\n/** Sentinel value used internally to identify the nullable \"(Blank)\" option */\nconst NULLABLE_BLANK_VALUE = '__tbw_null__';\n\n/** Creates a select dropdown editor */\nfunction createSelectEditor(column: AnyColumn): (ctx: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as SelectEditorParams | undefined;\n const select = document.createElement('select');\n if (column.multi) select.multiple = true;\n\n // Add blank option: nullable columns always get one, otherwise respect includeEmpty\n const showBlank = column.nullable || params?.includeEmpty;\n if (showBlank) {\n const emptyOpt = document.createElement('option');\n emptyOpt.value = column.nullable ? NULLABLE_BLANK_VALUE : '';\n emptyOpt.textContent = column.nullable ? (params?.emptyLabel ?? '(Blank)') : (params?.emptyLabel ?? '');\n if (ctx.value == null) emptyOpt.selected = true;\n select.appendChild(emptyOpt);\n }\n\n // Populate options from column.options\n const options = resolveOptions(column);\n options.forEach((opt) => {\n const o = document.createElement('option');\n o.value = String(opt.value);\n o.textContent = opt.label;\n if (column.multi && Array.isArray(ctx.value) && ctx.value.includes(opt.value)) {\n o.selected = true;\n } else if (!column.multi && ctx.value === opt.value) {\n o.selected = true;\n }\n select.appendChild(o);\n });\n\n const commit = () => {\n if (column.multi) {\n const values = Array.from(select.selectedOptions).map((o) => o.value);\n ctx.commit(values);\n } else if (column.nullable && select.value === NULLABLE_BLANK_VALUE) {\n ctx.commit(null);\n } else {\n ctx.commit(select.value);\n }\n };\n\n select.addEventListener('change', commit);\n select.addEventListener('blur', commit);\n select.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return select;\n };\n}\n\n/** Creates a text input editor (default) */\nfunction createTextEditor(column: AnyColumn): (ctx: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as TextEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'text';\n input.value = ctx.value != null ? String(ctx.value) : '';\n\n if (params?.maxLength !== undefined) input.maxLength = params.maxLength;\n if (params?.pattern) input.pattern = params.pattern;\n if (params?.placeholder) input.placeholder = params.placeholder;\n\n // Commit function preserves original type when possible\n const commit = () => {\n const inputVal = input.value;\n\n // Empty input handling\n if (inputVal === '') {\n if (column.nullable) {\n ctx.commit(null);\n } else {\n // Non-nullable: commit empty string so the field is never null\n ctx.commit('');\n }\n return;\n }\n\n // Preserve values with characters that <input> can't represent (newlines, etc.).\n // If stripping those characters produces the same string, the user didn't change anything.\n if (typeof ctx.value === 'string' && inputVal === ctx.value.replace(/[\\n\\r]/g, '')) {\n return;\n }\n // Preserve numeric type for custom column types (e.g., currency)\n if (typeof ctx.value === 'number') {\n ctx.commit(Number(inputVal));\n } else {\n ctx.commit(inputVal);\n }\n };\n\n input.addEventListener('blur', commit);\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') commit();\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return input;\n };\n}\n\n// ============================================================================\n// Main Entry Point\n// ============================================================================\n\n/**\n * Returns a default editor factory function for the given column type.\n * Each editor handles commit on blur/Enter, and cancel on Escape.\n *\n * Note: Focus is NOT called here - the calling code handles focusing after DOM insertion.\n */\nexport function defaultEditorFor(column: AnyColumn): (ctx: ColumnEditorContext) => HTMLElement | string {\n switch (column.type) {\n case 'number':\n return createNumberEditor(column);\n case 'boolean':\n return createBooleanEditor();\n case 'date':\n return createDateEditor(column);\n case 'select':\n return createSelectEditor(column);\n default:\n return createTextEditor(column);\n }\n}\n\n// ============================================================================\n// Utility Export (used by EditingPlugin)\n// ============================================================================\n\n/**\n * Get the typed value from an input element based on its type, column config, and original value.\n * Preserves the type of the original value (e.g., numeric currency values stay as numbers,\n * string dates stay as strings, null/undefined for empty fields).\n */\nexport function getInputValue(\n input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n column?: AnyColumn,\n originalValue?: unknown,\n): unknown {\n if (input instanceof HTMLInputElement) {\n if (input.type === 'checkbox') return input.checked;\n if (input.type === 'number') {\n if (input.value === '') {\n if (column?.nullable) return null;\n const params = column?.editorParams as NumberEditorParams | undefined;\n return params?.min ?? 0;\n }\n return Number(input.value);\n }\n if (input.type === 'date') {\n if (!input.value) {\n if (column?.nullable) return null;\n // Non-nullable: preserve original or fall back to today\n if (typeof originalValue === 'string') return originalValue || new Date().toISOString().slice(0, 10);\n return (originalValue as Date) ?? new Date();\n }\n // Preserve original type: if original was a string, return string (YYYY-MM-DD format)\n if (typeof originalValue === 'string') {\n return input.value; // input.value is already in YYYY-MM-DD format\n }\n return input.valueAsDate;\n }\n // For text inputs, check if original value was a number to preserve type\n if (typeof originalValue === 'number') {\n if (input.value === '') {\n if (column?.nullable) return null;\n const params = column?.editorParams as NumberEditorParams | undefined;\n return params?.min ?? 0;\n }\n return Number(input.value);\n }\n // Nullable text: empty → null; non-nullable: empty → ''\n if (input.value === '' && (originalValue === null || originalValue === undefined)) {\n return column?.nullable ? null : '';\n }\n // Preserve values with characters <input> can't represent (newlines, etc.)\n if (typeof originalValue === 'string' && input.value === originalValue.replace(/[\\n\\r]/g, '')) {\n return originalValue;\n }\n return input.value;\n }\n // For textarea/select, check column type OR original value type\n if (column?.type === 'number' && input.value !== '') {\n return Number(input.value);\n }\n // Preserve numeric type for custom column types (e.g., currency)\n if (typeof originalValue === 'number' && input.value !== '') {\n return Number(input.value);\n }\n // Nullable: empty → null; non-nullable: empty → ''\n if ((originalValue === null || originalValue === undefined) && input.value === '') {\n return column?.nullable ? null : '';\n }\n return input.value;\n}\n","/**\n * Cell Validation Manager\n *\n * Manages invalid-cell state for the Editing Plugin.\n * Extracted from EditingPlugin to reduce the main plugin file size.\n *\n * The manager owns the `Map<rowId, Map<field, message>>` state and exposes\n * read/write methods. DOM synchronization is handled via an injected callback.\n *\n * @internal\n */\n\n/** Callback to sync the data-invalid attribute on a cell element in the DOM. */\nexport type SyncInvalidAttributeFn = (rowId: string, field: string, invalid: boolean) => void;\n\n/**\n * Manages validation state for grid cells.\n *\n * Tracks which cells are marked invalid and their validation messages.\n * The DOM side-effect (adding/removing `data-invalid` attribute) is delegated\n * to the `syncAttribute` callback provided at construction time.\n */\nexport class CellValidationManager {\n /** Invalid cell tracking: Map<rowId, Map<field, message>> */\n readonly #cells = new Map<string, Map<string, string>>();\n\n /** Callback to sync DOM attributes when validation state changes */\n readonly #syncAttribute: SyncInvalidAttributeFn;\n\n constructor(syncAttribute: SyncInvalidAttributeFn) {\n this.#syncAttribute = syncAttribute;\n }\n\n // #region Write Operations\n\n /**\n * Mark a cell as invalid with an optional validation message.\n *\n * @param rowId - The row ID (from getRowId)\n * @param field - The field name\n * @param message - Optional validation message (for tooltips or display)\n */\n setInvalid(rowId: string, field: string, message = ''): void {\n let rowInvalids = this.#cells.get(rowId);\n if (!rowInvalids) {\n rowInvalids = new Map();\n this.#cells.set(rowId, rowInvalids);\n }\n rowInvalids.set(field, message);\n this.#syncAttribute(rowId, field, true);\n }\n\n /**\n * Clear the invalid state for a specific cell.\n *\n * @param rowId - The row ID (from getRowId)\n * @param field - The field name\n */\n clearInvalid(rowId: string, field: string): void {\n const rowInvalids = this.#cells.get(rowId);\n if (rowInvalids) {\n rowInvalids.delete(field);\n if (rowInvalids.size === 0) {\n this.#cells.delete(rowId);\n }\n }\n this.#syncAttribute(rowId, field, false);\n }\n\n /**\n * Clear all invalid cells for a specific row.\n *\n * @param rowId - The row ID (from getRowId)\n */\n clearRowInvalid(rowId: string): void {\n const rowInvalids = this.#cells.get(rowId);\n if (rowInvalids) {\n const fields = Array.from(rowInvalids.keys());\n this.#cells.delete(rowId);\n fields.forEach((field) => this.#syncAttribute(rowId, field, false));\n }\n }\n\n /**\n * Clear all invalid cell states across all rows.\n */\n clearAllInvalid(): void {\n const entries = Array.from(this.#cells.entries());\n this.#cells.clear();\n entries.forEach(([rowId, fields]) => {\n fields.forEach((_, field) => this.#syncAttribute(rowId, field, false));\n });\n }\n\n // #endregion\n\n // #region Read Operations\n\n /**\n * Check if a specific cell is marked as invalid.\n *\n * @param rowId - The row ID (from getRowId)\n * @param field - The field name\n * @returns True if the cell is marked as invalid\n */\n isCellInvalid(rowId: string, field: string): boolean {\n return this.#cells.get(rowId)?.has(field) ?? false;\n }\n\n /**\n * Get the validation message for an invalid cell.\n *\n * @param rowId - The row ID (from getRowId)\n * @param field - The field name\n * @returns The validation message, or undefined if cell is valid\n */\n getInvalidMessage(rowId: string, field: string): string | undefined {\n return this.#cells.get(rowId)?.get(field);\n }\n\n /**\n * Check if a row has any invalid cells.\n *\n * @param rowId - The row ID (from getRowId)\n * @returns True if the row has at least one invalid cell\n */\n hasInvalidCells(rowId: string): boolean {\n const rowInvalids = this.#cells.get(rowId);\n return rowInvalids ? rowInvalids.size > 0 : false;\n }\n\n /**\n * Get all invalid fields for a row.\n *\n * @param rowId - The row ID (from getRowId)\n * @returns Map of field names to validation messages\n */\n getInvalidFields(rowId: string): Map<string, string> {\n return new Map(this.#cells.get(rowId) ?? []);\n }\n\n // #endregion\n}\n","/**\n * Pure functions for dirty tracking baseline management.\n *\n * Extracted from EditingPlugin to keep the plugin under the 2,000 line target.\n * All functions are stateless — the caller owns the baseline `Map`.\n *\n * @internal\n */\n\n// #region Types\n\n/**\n * Detail for `dirty-change` custom events.\n */\nexport interface DirtyChangeDetail<T = unknown> {\n /** Row ID (from getRowId) */\n rowId: string;\n /** Current row data */\n row: T;\n /** Baseline (original) row data, or undefined for newly inserted rows */\n original: T | undefined;\n /**\n * Transition type:\n * - `'modified'` — row differs from baseline\n * - `'new'` — row was inserted via `insertRow()` and has no baseline\n * - `'reverted'` — row was reverted to baseline via `revertRow()`\n * - `'pristine'` — row was explicitly marked pristine via `markAsPristine()`\n */\n type: 'modified' | 'new' | 'reverted' | 'pristine';\n}\n\n/**\n * Result of getDirtyRows(): each entry has the row ID, original (baseline),\n * and current data.\n */\nexport interface DirtyRowEntry<T = unknown> {\n id: string;\n /** The original (baseline) row data, or `undefined` for newly inserted rows. */\n original: T | undefined;\n current: T;\n}\n\n/**\n * Detail for `baselines-captured` custom events.\n *\n * Emitted after the render pipeline completes when new baseline snapshots\n * were captured during `processRows`.\n */\nexport interface BaselinesCapturedDetail {\n /** Total number of tracked baselines (not just newly captured). */\n count: number;\n}\n\n// #endregion\n\n// #region Baseline Capture\n\n/**\n * Capture baselines for rows not already tracked (first-write-wins).\n *\n * Uses `structuredClone` for deep copy so nested objects cannot be mutated\n * through shared references.\n *\n * @param baselines - Map of rowId → deep-cloned baseline row data\n * @param rows - Rows to snapshot\n * @param getRowId - Function to resolve row ID (may throw; exceptions are swallowed)\n */\nexport function captureBaselines<T>(\n baselines: Map<string, T>,\n rows: readonly T[],\n getRowId: (row: T) => string | undefined,\n): void {\n for (const row of rows) {\n try {\n const id = getRowId(row);\n if (id != null && !baselines.has(id)) {\n baselines.set(id, structuredClone(row));\n }\n } catch {\n // Row has no resolvable ID — skip\n }\n }\n}\n\n// #endregion\n\n// #region Dirty Detection\n\n/**\n * Check whether a row's current data differs from its baseline.\n *\n * Uses deep property comparison so that `structuredClone`'d baselines\n * with nested objects/arrays/Dates compare correctly.\n */\nexport function isRowDirty<T>(baselines: Map<string, T>, rowId: string, currentRow: T): boolean {\n const baseline = baselines.get(rowId);\n if (!baseline) return false; // No baseline → row is \"new\" or untracked\n return !deepEqual(baseline, currentRow);\n}\n\n/**\n * Check whether a single cell (field) differs from its baseline value.\n *\n * Returns `false` when no baseline exists for the row.\n */\nexport function isCellDirty<T>(baselines: Map<string, T>, rowId: string, currentRow: T, field: string): boolean {\n const baseline = baselines.get(rowId);\n if (!baseline) return false;\n const baselineValue = (baseline as Record<string, unknown>)[field];\n const currentValue = (currentRow as Record<string, unknown>)[field];\n return !deepEqual(baselineValue, currentValue);\n}\n\n/**\n * Deep comparison of two values. Handles primitives, plain objects, arrays,\n * and Dates — the value types produced by `structuredClone` on row data.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a == null || b == null) return false;\n if (typeof a !== typeof b) return false;\n\n // Date comparison\n if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();\n\n // Array comparison\n if (Array.isArray(a)) {\n if (!Array.isArray(b) || a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n\n // Object comparison\n if (typeof a === 'object') {\n const aObj = a as Record<string, unknown>;\n const bObj = b as Record<string, unknown>;\n const keysA = Object.keys(aObj);\n const keysB = Object.keys(bObj);\n if (keysA.length !== keysB.length) return false;\n for (const key of keysA) {\n if (!deepEqual(aObj[key], bObj[key])) return false;\n }\n return true;\n }\n\n return false;\n}\n\n// #endregion\n\n// #region State Transitions\n\n/**\n * Mark a row as pristine: re-snapshot baseline from current data.\n *\n * After calling this, `isRowDirty` returns `false` for the row (until it\n * is edited again).\n */\nexport function markPristine<T>(baselines: Map<string, T>, rowId: string, currentRow: T): void {\n baselines.set(rowId, structuredClone(currentRow));\n}\n\n/**\n * Get the original (baseline) row data as a deep clone.\n *\n * Returns `undefined` if no baseline exists (e.g. newly inserted row).\n */\nexport function getOriginalRow<T>(baselines: Map<string, T>, rowId: string): T | undefined {\n const baseline = baselines.get(rowId);\n return baseline ? structuredClone(baseline) : undefined;\n}\n\n/**\n * Revert a row to its baseline values by mutating the current row in-place.\n *\n * Returns `true` if a baseline existed and the row was reverted, `false` otherwise.\n */\nexport function revertToBaseline<T>(baselines: Map<string, T>, rowId: string, currentRow: T): boolean {\n const baseline = baselines.get(rowId);\n if (!baseline) return false;\n const baselineObj = baseline as Record<string, unknown>;\n const currentObj = currentRow as Record<string, unknown>;\n for (const key of Object.keys(baselineObj)) {\n currentObj[key] = baselineObj[key];\n }\n return true;\n}\n\n// #endregion\n","/**\n * Stateful manager for dirty tracking in the EditingPlugin.\n *\n * Owns all dirty-tracking-related state (baselines, changed/new/committed sets)\n * and exposes high-level methods that coordinate pure functions from\n * `dirty-tracking.ts`.\n *\n * The EditingPlugin delegates all dirty tracking operations to this manager,\n * keeping the plugin class focused on orchestration (row resolution, event\n * emission, render scheduling).\n *\n * @internal\n */\n\nimport {\n captureBaselines,\n getOriginalRow,\n isCellDirty,\n isRowDirty,\n markPristine,\n revertToBaseline,\n type DirtyRowEntry,\n} from './dirty-tracking';\n\n// #region Types\n\n/**\n * Callback that resolves a row object by its ID.\n * Returns `undefined` when the row is not found.\n */\nexport type RowResolver<T> = (rowId: string) => T | undefined;\n\n// #endregion\n\n// #region DirtyTrackingManager\n\nexport class DirtyTrackingManager<T> {\n // --- Owned state ---\n\n /** Baseline snapshots: rowId → deep-cloned original row data (first-write-wins) */\n readonly baselines = new Map<string, T>();\n\n /** Whether new baselines were captured during the current processRows cycle */\n private baselinesWereCaptured = false;\n\n /** Row IDs inserted via `insertRow()` (no baseline available) */\n readonly newRowIds = new Set<string>();\n\n /** Row IDs that have been modified (edit committed) */\n readonly changedRowIds = new Set<string>();\n\n /** Row IDs whose edit session was committed (gates `tbw-row-dirty` CSS class) */\n readonly committedDirtyRowIds = new Set<string>();\n\n // --- Lifecycle ---\n\n /** Reset all dirty tracking state (called from detach / resetChangedRows). */\n clear(): void {\n this.changedRowIds.clear();\n this.committedDirtyRowIds.clear();\n this.baselines.clear();\n this.newRowIds.clear();\n this.baselinesWereCaptured = false;\n }\n\n // --- Baseline capture (called from processRows) ---\n\n /**\n * Capture baselines for rows not already tracked (first-write-wins).\n * Sets the `baselinesWereCaptured` flag when new rows are snapshotted.\n */\n capture(rows: readonly T[], getRowId: (r: T) => string | undefined): void {\n const sizeBefore = this.baselines.size;\n captureBaselines(this.baselines, rows, getRowId);\n if (this.baselines.size > sizeBefore) {\n this.baselinesWereCaptured = true;\n }\n }\n\n /**\n * Drain the baselines-captured flag (called from afterRender).\n * Returns `null` when no new captures occurred, or the baseline count.\n */\n drainCapturedFlag(): number | null {\n if (!this.baselinesWereCaptured) return null;\n this.baselinesWereCaptured = false;\n return this.baselines.size;\n }\n\n // --- Row dirty queries ---\n\n /** Check if a specific row differs from its baseline. */\n isRowDirty(rowId: string, row: T): boolean {\n return isRowDirty(this.baselines, rowId, row);\n }\n\n /** Check if ANY row is dirty (requires row resolver for baseline iteration). */\n hasAnyDirty(resolveRow: RowResolver<T>): boolean {\n if (this.newRowIds.size > 0) return true;\n for (const [rowId] of this.baselines) {\n const row = resolveRow(rowId);\n if (row && isRowDirty(this.baselines, rowId, row)) return true;\n }\n return false;\n }\n\n // --- Cell dirty queries ---\n\n /** Check if a single cell differs from its baseline value. */\n isCellDirty(rowId: string, row: T, field: string): boolean {\n return isCellDirty(this.baselines, rowId, row, field);\n }\n\n // --- Render-time helpers ---\n\n /**\n * Get the dirty-state tuple needed by afterRowRender to toggle CSS classes.\n *\n * `isCommittedDirty` requires BOTH committed status AND actual data divergence\n * from baseline (handles undo: after CTRL+Z, row is no longer visually dirty).\n */\n getRowDirtyState(rowId: string, row: T): { isNew: boolean; isCommittedDirty: boolean; hasBaseline: boolean } {\n const isNew = this.newRowIds.has(rowId);\n const isCommittedDirty = !isNew && this.committedDirtyRowIds.has(rowId) && isRowDirty(this.baselines, rowId, row);\n return { isNew, isCommittedDirty, hasBaseline: this.baselines.has(rowId) };\n }\n\n // --- Mark operations ---\n\n /** Re-snapshot baseline from current data and remove from all sets. */\n markPristine(rowId: string, row: T): void {\n markPristine(this.baselines, rowId, row);\n this.newRowIds.delete(rowId);\n this.changedRowIds.delete(rowId);\n this.committedDirtyRowIds.delete(rowId);\n }\n\n /** Mark a row as newly inserted (no baseline). */\n markNew(rowId: string): void {\n this.newRowIds.add(rowId);\n this.committedDirtyRowIds.add(rowId);\n }\n\n /** Mark a row as dirty (external mutation). */\n markDirty(rowId: string): void {\n this.changedRowIds.add(rowId);\n this.committedDirtyRowIds.add(rowId);\n }\n\n /** Re-snapshot all baselines and clear all tracking sets. */\n markAllPristine(resolveRow: RowResolver<T>): void {\n for (const [rowId] of this.baselines) {\n const row = resolveRow(rowId);\n if (row) markPristine(this.baselines, rowId, row);\n }\n this.newRowIds.clear();\n this.changedRowIds.clear();\n this.committedDirtyRowIds.clear();\n }\n\n // --- Original row access ---\n\n /** Get deep-cloned baseline (undefined for new/untracked rows). */\n getOriginalRow(rowId: string): T | undefined {\n return getOriginalRow<T>(this.baselines, rowId);\n }\n\n /** Lightweight check whether a baseline exists (no cloning). */\n hasBaseline(rowId: string): boolean {\n return this.baselines.has(rowId);\n }\n\n // --- Aggregate queries ---\n\n /** Get all dirty rows with original + current data. Requires a row resolver. */\n getDirtyRows(resolveRow: RowResolver<T>): DirtyRowEntry<T>[] {\n const result: DirtyRowEntry<T>[] = [];\n for (const [rowId, baseline] of this.baselines) {\n const row = resolveRow(rowId);\n if (row && isRowDirty(this.baselines, rowId, row)) {\n result.push({ id: rowId, original: structuredClone(baseline), current: row });\n }\n }\n for (const newId of this.newRowIds) {\n const row = resolveRow(newId);\n if (row) {\n result.push({ id: newId, original: undefined, current: row });\n }\n }\n return result;\n }\n\n /** Get IDs of all dirty rows. Requires a row resolver. */\n getDirtyRowIds(resolveRow: RowResolver<T>): string[] {\n const ids: string[] = [];\n for (const [rowId] of this.baselines) {\n const row = resolveRow(rowId);\n if (row && isRowDirty(this.baselines, rowId, row)) ids.push(rowId);\n }\n for (const newId of this.newRowIds) ids.push(newId);\n return ids;\n }\n\n // --- Revert operations ---\n\n /**\n * Revert a row to its baseline values (mutates row in-place).\n * Returns `true` when baseline existed and row was reverted.\n */\n revertRow(rowId: string, row: T): boolean {\n const reverted = revertToBaseline(this.baselines, rowId, row);\n if (reverted) {\n this.changedRowIds.delete(rowId);\n this.committedDirtyRowIds.delete(rowId);\n }\n return reverted;\n }\n\n /** Revert all rows to baseline values. Requires a row resolver. */\n revertAll(resolveRow: RowResolver<T>): void {\n for (const [rowId] of this.baselines) {\n const row = resolveRow(rowId);\n if (row) revertToBaseline(this.baselines, rowId, row);\n }\n this.changedRowIds.clear();\n this.committedDirtyRowIds.clear();\n }\n\n // --- Changed row queries ---\n\n /** Resolve changed row IDs to row objects. */\n getChangedRows(resolveRow: RowResolver<T>): T[] {\n const rows: T[] = [];\n for (const id of this.changedRowIds) {\n const row = resolveRow(id);\n if (row) rows.push(row);\n }\n return rows;\n }\n\n /** Get a copy of changed row IDs. */\n getChangedRowIds(): string[] {\n return Array.from(this.changedRowIds);\n }\n\n /** Check if a row has been marked as changed. */\n isRowChanged(rowId: string): boolean {\n return this.changedRowIds.has(rowId);\n }\n}\n\n// #endregion\n","/**\n * Pure helper functions for the Editing Plugin.\n *\n * Extracted from EditingPlugin to reduce the main plugin file size.\n * All functions are stateless — they have no `this` access.\n *\n * @internal\n */\n\nimport type {\n ColumnConfig,\n ColumnEditorSpec,\n ColumnInternal,\n InternalGrid,\n RowElementInternal,\n} from '../../../core/types';\nimport { getInputValue } from '../editors';\nimport type { EditingConfig } from '../types';\n\n// #region Constants\n\n/**\n * CSS selector for focusable editor elements inside a cell.\n * Duplicated from core/internal/rows to avoid cross-boundary imports\n * that Vite externalises to `@toolbox-web/grid` during plugin bundling.\n *\n * @internal\n */\nexport const FOCUSABLE_EDITOR_SELECTOR =\n 'input,select,textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[tabindex]:not([tabindex=\"-1\"])';\n\n// #endregion\n\n// #region Editor Resolution\n\n/**\n * Resolves the editor for a column using the priority chain:\n * 1. Column-level (`column.editor`)\n * 2. Light DOM template (`__editorTemplate` → returns 'template')\n * 3. Grid-level (`gridConfig.typeDefaults[column.type]`)\n * 4. App-level (framework adapter's `getTypeDefault`)\n * 5. Returns undefined (caller uses built-in defaultEditorFor)\n */\nexport function resolveEditor<TRow>(\n grid: InternalGrid<TRow>,\n col: ColumnInternal<TRow>,\n): ColumnEditorSpec<TRow, unknown> | 'template' | undefined {\n // 1. Column-level editor (highest priority)\n if (col.editor) return col.editor;\n\n // 2. Light DOM template\n const tplHolder = col.__editorTemplate;\n if (tplHolder) return 'template';\n\n // No type specified - no type defaults to check\n if (!col.type) return undefined;\n\n // 3. Grid-level typeDefaults (access via effectiveConfig)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const gridTypeDefaults = (grid as any).effectiveConfig?.typeDefaults;\n if (gridTypeDefaults?.[col.type]?.editor) {\n return gridTypeDefaults[col.type].editor as ColumnEditorSpec<TRow, unknown>;\n }\n\n // 4. App-level registry (via framework adapter)\n const adapter = grid.__frameworkAdapter;\n if (adapter?.getTypeDefault) {\n const appDefault = adapter.getTypeDefault<TRow>(col.type);\n if (appDefault?.editor) {\n return appDefault.editor as ColumnEditorSpec<TRow, unknown>;\n }\n }\n\n // 5. No custom editor - caller uses built-in defaultEditorFor\n return undefined;\n}\n\n// #endregion\n\n// #region Property Key Safety\n\n/**\n * Returns true if the given property key is safe to use on a plain object.\n */\nexport function isSafePropertyKey(key: unknown): key is string {\n if (typeof key !== 'string') return false;\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') return false;\n return true;\n}\n\n// #endregion\n\n// #region Row Element State\n\n/**\n * Increment the editing cell count on a row element.\n */\nexport function incrementEditingCount(rowEl: RowElementInternal): void {\n const count = (rowEl.__editingCellCount ?? 0) + 1;\n rowEl.__editingCellCount = count;\n rowEl.setAttribute('data-has-editing', '');\n}\n\n/**\n * Clear all editing state from a row element.\n */\nexport function clearEditingState(rowEl: RowElementInternal): void {\n rowEl.__editingCellCount = 0;\n rowEl.removeAttribute('data-has-editing');\n}\n\n// #endregion\n\n// #region No-op Helpers\n\n/**\n * No-op updateRow function for rows without IDs.\n * Extracted to a named function to satisfy eslint no-empty-function.\n */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport function noopUpdateRow(_changes: unknown): void {\n // Row has no ID - cannot update\n}\n\n// #endregion\n\n// #region Editor Input Wiring\n\n/**\n * Auto-wire commit/cancel lifecycle for input elements in string-returned editors.\n */\nexport function wireEditorInputs(\n editorHost: HTMLElement,\n column: ColumnConfig<unknown>,\n commit: (value: unknown) => void,\n originalValue?: unknown,\n): void {\n const input = editorHost.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (!input) return;\n\n input.addEventListener('blur', () => {\n commit(getInputValue(input, column, originalValue));\n });\n\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.addEventListener('change', () => commit(input.checked));\n } else if (input instanceof HTMLSelectElement) {\n input.addEventListener('change', () => commit(getInputValue(input, column, originalValue)));\n }\n}\n\n// #endregion\n\n// #region Edit Guard\n\n/**\n * Returns `true` when the configured `onBeforeEditClose` callback vetoes\n * the close (i.e. returns `false`). Use as a one-liner guard:\n *\n * ```ts\n * if (shouldPreventEditClose(config, e)) return;\n * ```\n */\nexport function shouldPreventEditClose(config: EditingConfig, event: MouseEvent | KeyboardEvent): boolean {\n return config.onBeforeEditClose?.(event) === false;\n}\n\n// #endregion\n\n// #region Row Comparison\n\n/**\n * Shallow-compare a snapshot against the current row to detect changes.\n * Returns `true` if any own-property value differs between the two objects.\n */\nexport function hasRowChanged<T>(snapshot: T | undefined, current: T): boolean {\n if (!snapshot) return false;\n const snapshotObj = snapshot as Record<string, unknown>;\n const currentObj = current as Record<string, unknown>;\n const allKeys = new Set([...Object.keys(snapshotObj), ...Object.keys(currentObj)]);\n for (const key of allKeys) {\n if (snapshotObj[key] !== currentObj[key]) return true;\n }\n return false;\n}\n\n// #endregion\n","/**\n * Editor injection logic for the Editing Plugin.\n *\n * Extracted from EditingPlugin to reduce the main file size.\n * Contains the DOM-heavy editor creation and template rendering,\n * while the plugin retains state management and event emission.\n *\n * @internal\n */\n\nimport { EDITOR_MOUNT_ERROR, warnDiagnostic } from '../../../core/internal/diagnostics';\nimport type { ColumnConfig, ColumnInternal, GridHost, RowElementInternal } from '../../../core/types';\nimport { defaultEditorFor, getInputValue } from '../editors';\nimport type { EditingConfig, EditorContext } from '../types';\nimport {\n FOCUSABLE_EDITOR_SELECTOR,\n incrementEditingCount,\n isSafePropertyKey,\n noopUpdateRow,\n resolveEditor,\n shouldPreventEditClose,\n wireEditorInputs,\n} from './helpers';\n\n// #region Types\n\n/**\n * Dependencies injected by the EditingPlugin so the extraction\n * can call back into plugin-owned state and methods.\n */\nexport interface EditorInjectionDeps<T> {\n /** Internal grid reference (also serves as HTMLElement). */\n grid: GridHost<T>;\n /** Whether the grid is in always-editing \"grid\" mode. */\n isGridMode: boolean;\n /** Plugin configuration. */\n config: EditingConfig;\n /** Set of cells currently in edit mode (\"rowIndex:colIndex\"). */\n editingCells: Set<string>;\n /** Value-change callbacks keyed by \"rowIndex:field\". */\n editorValueCallbacks: Map<string, (newValue: unknown) => void>;\n /** Returns `true` when an edit session is active (#activeEditRow !== -1). */\n isEditSessionActive: () => boolean;\n /** Commit a single cell value change. */\n commitCellValue: (rowIndex: number, column: ColumnConfig<T>, newValue: unknown, rowData: T) => void;\n /** Exit editing for a row (commit or revert). */\n exitRowEdit: (rowIndex: number, revert: boolean) => void;\n}\n\n// #endregion\n\n// #region Editor Injection\n\n/**\n * Inject an editor into a cell element.\n *\n * Handles the full editor lifecycle: creates the editor host, resolves\n * the editor spec (template / custom-element / factory / component),\n * wires commit/cancel callbacks, and registers value-change listeners.\n */\nexport function injectEditor<T>(\n deps: EditorInjectionDeps<T>,\n rowData: T,\n rowIndex: number,\n column: ColumnConfig<T>,\n colIndex: number,\n cell: HTMLElement,\n skipFocus: boolean,\n parentRowEl?: HTMLElement,\n): void {\n if (!column.editable) return;\n if (cell.classList.contains('editing')) return;\n\n const { grid, isGridMode, config, editingCells, editorValueCallbacks } = deps;\n\n // Get row ID for updateRow helper (may not exist)\n let rowId: string | undefined;\n try {\n rowId = grid.getRowId?.(rowData);\n } catch {\n // Row has no ID\n }\n\n // Create updateRow helper for cascade updates (noop if row has no ID)\n const updateRow: (changes: Partial<T>) => void = rowId\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (changes) => (grid as any).updateRow(rowId!, changes as Record<string, unknown>, 'cascade')\n : noopUpdateRow;\n\n const originalValue = isSafePropertyKey(column.field)\n ? (rowData as Record<string, unknown>)[column.field]\n : undefined;\n\n cell.classList.add('editing');\n editingCells.add(`${rowIndex}:${colIndex}`);\n\n // Use explicit parentRowEl when cell is in a DocumentFragment (not yet in DOM),\n // otherwise fall back to cell.parentElement for cells already attached to a row.\n const rowEl = (parentRowEl ?? cell.parentElement) as RowElementInternal | null;\n if (rowEl) incrementEditingCount(rowEl);\n\n let editFinalized = false;\n const commit = (newValue: unknown) => {\n // In grid mode, always allow commits (we're always editing)\n // In row mode, only allow commits if we're in an active edit session\n if (editFinalized || (!isGridMode && !deps.isEditSessionActive())) return;\n // Resolve row and index fresh at commit time.\n // With a row ID we use _getRowEntry for O(1) lookup — this is resilient\n // against _rows being replaced (e.g. Angular directive effect).\n // Without a row ID we fall back to the captured rowData reference.\n // Using _rows[rowIndex] without an ID is unsafe: the index may be stale\n // after _rows replacement, which would commit to the WRONG row.\n const entry = rowId ? grid._getRowEntry(rowId) : undefined;\n const currentRowData = (entry?.row ?? rowData) as T;\n const currentIndex = entry?.index ?? rowIndex;\n deps.commitCellValue(currentIndex, column, newValue, currentRowData);\n };\n const cancel = () => {\n editFinalized = true;\n if (isSafePropertyKey(column.field)) {\n // Same ID-first / captured-rowData fallback as commit — see comment above.\n const entry = rowId ? grid._getRowEntry(rowId) : undefined;\n const currentRowData = (entry?.row ?? rowData) as T;\n (currentRowData as Record<string, unknown>)[column.field] = originalValue;\n }\n };\n\n const editorHost = document.createElement('div');\n editorHost.className = 'tbw-editor-host';\n cell.innerHTML = '';\n cell.appendChild(editorHost);\n\n // Keydown handler for Enter/Escape\n editorHost.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Enter') {\n // In grid mode, Enter just commits without exiting\n if (isGridMode) {\n e.stopPropagation();\n e.preventDefault();\n // Get current value and commit\n const input = editorHost.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (input) {\n commit(getInputValue(input, column as ColumnConfig<unknown>, originalValue));\n }\n return;\n }\n if (shouldPreventEditClose(config, e)) return;\n e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n deps.exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n // In grid mode, Escape doesn't exit edit mode\n if (isGridMode) {\n e.stopPropagation();\n e.preventDefault();\n return;\n }\n if (shouldPreventEditClose(config, e)) return;\n e.stopPropagation();\n e.preventDefault();\n cancel();\n deps.exitRowEdit(rowIndex, true);\n }\n });\n\n const colInternal = column as ColumnInternal<T>;\n const tplHolder = colInternal.__editorTemplate;\n // Resolve editor using priority chain: column → template → typeDefaults → adapter → built-in\n const editorSpec = resolveEditor(grid, colInternal) ?? defaultEditorFor(column);\n const value = originalValue;\n\n // Value-change callback registration.\n // Editors call onValueChange(cb) to receive pushes when the underlying row\n // is mutated externally (e.g., via updateRow from another cell's commit).\n // Multiple callbacks can be registered (user + auto-wire).\n const callbackKey = `${rowIndex}:${column.field}`;\n const callbacks: Array<(newValue: unknown) => void> = [];\n editorValueCallbacks.set(callbackKey, (newVal) => {\n for (const cb of callbacks) cb(newVal);\n });\n const onValueChange = (cb: (newValue: unknown) => void) => {\n callbacks.push(cb);\n };\n\n if (editorSpec === 'template' && tplHolder) {\n renderTemplateEditor(deps, editorHost, colInternal, rowData, originalValue, commit, cancel, skipFocus, rowIndex);\n // Auto-update built-in template editors when value changes externally.\n // Skip non-primitive values (arrays, objects) — framework adapters manage\n // those via the cell-cancel event. String() on arrays produces comma-separated junk.\n onValueChange((newVal) => {\n if (newVal != null && typeof newVal === 'object') return;\n const input = editorHost.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(\n 'input,textarea,select',\n );\n if (input) {\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.checked = !!newVal;\n } else {\n input.value = String(newVal ?? '');\n }\n }\n });\n } else if (typeof editorSpec === 'string') {\n const el = document.createElement(editorSpec) as HTMLElement & { value?: unknown };\n el.value = value;\n el.addEventListener('change', () => commit(el.value));\n // Auto-update custom element editors when value changes externally\n onValueChange((newVal) => {\n el.value = newVal;\n });\n editorHost.appendChild(el);\n if (!skipFocus) {\n queueMicrotask(() => {\n const focusable = editorHost.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n focusable?.focus({ preventScroll: true });\n });\n }\n } else if (typeof editorSpec === 'function') {\n const ctx: EditorContext<T> = {\n row: rowData,\n rowId: rowId ?? '',\n value,\n field: column.field,\n column,\n commit,\n cancel,\n updateRow,\n onValueChange,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const produced = (editorSpec as any)(ctx);\n if (typeof produced === 'string') {\n editorHost.innerHTML = produced;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n wireEditorInputs(editorHost, column as any, commit, originalValue);\n // Auto-update wired inputs when value changes externally\n onValueChange((newVal) => {\n const input = editorHost.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(\n 'input,textarea,select',\n );\n if (input) {\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.checked = !!newVal;\n } else {\n input.value = String(newVal ?? '');\n }\n }\n });\n } else if (produced instanceof Node) {\n editorHost.appendChild(produced);\n const isSimpleInput =\n produced instanceof HTMLInputElement ||\n produced instanceof HTMLSelectElement ||\n produced instanceof HTMLTextAreaElement;\n if (!isSimpleInput) {\n cell.setAttribute('data-editor-managed', '');\n } else {\n // Auto-update simple inputs returned by factory functions\n onValueChange((newVal) => {\n if (produced instanceof HTMLInputElement && produced.type === 'checkbox') {\n produced.checked = !!newVal;\n } else {\n (produced as HTMLInputElement).value = String(newVal ?? '');\n }\n });\n }\n } else if (!produced && editorHost.hasChildNodes()) {\n // Factory returned void but mounted content into the editor host\n // (e.g. Angular/React/Vue adapter component editor). Mark the cell\n // as externally managed so the native commit loop in #exitRowEdit\n // does not read raw input values from framework editor DOM.\n cell.setAttribute('data-editor-managed', '');\n }\n if (!skipFocus) {\n queueMicrotask(() => {\n const focusable = editorHost.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n focusable?.focus({ preventScroll: true });\n });\n }\n } else if (editorSpec && typeof editorSpec === 'object') {\n const placeholder = document.createElement('div');\n placeholder.setAttribute('data-external-editor', '');\n placeholder.setAttribute('data-field', column.field);\n editorHost.appendChild(placeholder);\n cell.setAttribute('data-editor-managed', '');\n const context: EditorContext<T> = {\n row: rowData,\n rowId: rowId ?? '',\n value,\n field: column.field,\n column,\n commit,\n cancel,\n updateRow,\n onValueChange,\n };\n if (editorSpec.mount) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n editorSpec.mount({ placeholder, context: context as any, spec: editorSpec });\n } catch (e) {\n warnDiagnostic(\n EDITOR_MOUNT_ERROR,\n `External editor mount error for column '${column.field}': ${e}`,\n deps.grid.id,\n );\n }\n } else {\n grid.dispatchEvent(\n new CustomEvent('mount-external-editor', { detail: { placeholder, spec: editorSpec, context } }),\n );\n }\n }\n}\n\n// #endregion\n\n// #region Template Editor\n\n/**\n * Render a template-based editor inside an editor host element.\n */\nfunction renderTemplateEditor<T>(\n deps: Pick<EditorInjectionDeps<T>, 'config' | 'exitRowEdit'>,\n editorHost: HTMLElement,\n column: ColumnInternal<T>,\n rowData: T,\n originalValue: unknown,\n commit: (value: unknown) => void,\n cancel: () => void,\n skipFocus: boolean,\n rowIndex: number,\n): void {\n const tplHolder = column.__editorTemplate;\n if (!tplHolder) return;\n\n const clone = tplHolder.cloneNode(true) as HTMLElement;\n const compiledEditor = column.__compiledEditor;\n\n if (compiledEditor) {\n clone.innerHTML = compiledEditor({\n row: rowData,\n value: originalValue,\n field: column.field,\n column,\n commit,\n cancel,\n });\n } else {\n clone.querySelectorAll<HTMLElement>('*').forEach((node) => {\n if (node.childNodes.length === 1 && node.firstChild?.nodeType === Node.TEXT_NODE) {\n node.textContent =\n node.textContent\n ?.replace(/{{\\s*value\\s*}}/g, originalValue == null ? '' : String(originalValue))\n .replace(/{{\\s*row\\.([a-zA-Z0-9_]+)\\s*}}/g, (_m, g: string) => {\n if (!isSafePropertyKey(g)) return '';\n const v = (rowData as Record<string, unknown>)[g];\n return v == null ? '' : String(v);\n }) || '';\n }\n });\n }\n\n const input = clone.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(\n 'input,textarea,select',\n );\n if (input) {\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.checked = !!originalValue;\n } else {\n input.value = String(originalValue ?? '');\n }\n\n let editFinalized = false;\n input.addEventListener('blur', () => {\n if (editFinalized) return;\n commit(getInputValue(input, column, originalValue));\n });\n input.addEventListener('keydown', (evt) => {\n const e = evt as KeyboardEvent;\n if (e.key === 'Enter') {\n if (shouldPreventEditClose(deps.config, e)) return;\n e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n commit(getInputValue(input, column, originalValue));\n deps.exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n if (shouldPreventEditClose(deps.config, e)) return;\n e.stopPropagation();\n e.preventDefault();\n cancel();\n deps.exitRowEdit(rowIndex, true);\n }\n });\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.addEventListener('change', () => commit(input.checked));\n }\n if (!skipFocus) {\n setTimeout(() => input.focus({ preventScroll: true }), 0);\n }\n }\n editorHost.appendChild(clone);\n}\n\n// #endregion\n","/**\n * Editing Plugin\n *\n * Provides complete editing functionality for tbw-grid.\n * This plugin is FULLY SELF-CONTAINED - the grid has ZERO editing knowledge.\n *\n * The plugin:\n * - Owns all editing state (active cell, snapshots, changed rows)\n * - Uses event distribution (onCellClick, onKeyDown) to handle edit lifecycle\n * - Uses afterRender() hook to inject editors into cells\n * - Uses processColumns() to augment columns with editing metadata\n * - Emits its own events (cell-commit, row-commit, changed-rows-reset)\n *\n * Without this plugin, the grid cannot edit. With this plugin, editing\n * is fully functional without any core changes.\n */\n\nimport { ensureCellVisible } from '../../core/internal/keyboard';\nimport type {\n AfterCellRenderContext,\n AfterRowRenderContext,\n PluginManifest,\n PluginQuery,\n} from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin, type CellClickEvent, type GridElement } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, GridHost, InternalGrid, RowElementInternal } from '../../core/types';\nimport styles from './editing.css?inline';\nimport { getInputValue } from './editors';\nimport { CellValidationManager } from './internal/cell-validation';\nimport { type BaselinesCapturedDetail, type DirtyChangeDetail, type DirtyRowEntry } from './internal/dirty-tracking';\nimport { DirtyTrackingManager } from './internal/dirty-tracking-manager';\nimport { type EditorInjectionDeps, injectEditor as injectEditorImpl } from './internal/editor-injection';\nimport {\n clearEditingState,\n FOCUSABLE_EDITOR_SELECTOR,\n hasRowChanged,\n isSafePropertyKey,\n noopUpdateRow,\n shouldPreventEditClose,\n} from './internal/helpers';\nimport type {\n BeforeEditCloseDetail,\n CellCancelDetail,\n CellCommitDetail,\n ChangedRowsResetDetail,\n EditCloseDetail,\n EditingConfig,\n EditOpenDetail,\n RowCommitDetail,\n} from './types';\n\n// ============================================================================\n// EditingPlugin\n// ============================================================================\n\n/**\n * Editing Plugin for tbw-grid\n *\n * Enables inline cell editing in the grid. Provides built-in editors for common data types\n * and supports custom editor functions for specialized input scenarios.\n *\n * ## Why Opt-In?\n *\n * Editing is delivered as a plugin rather than built into the core grid:\n *\n * - **Smaller bundle** — Apps that only display data don't pay for editing code\n * - **Clear intent** — Explicit plugin registration makes editing capability obvious\n * - **Runtime validation** — Using `editable: true` without the plugin throws a helpful error\n *\n * ## Installation\n *\n * ```ts\n * import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';\n * ```\n *\n * ## Edit Triggers\n *\n * Configure how editing is triggered with the `editOn` option:\n *\n * | Value | Behavior |\n * |-------|----------|\n * | `'click'` | Single click enters edit mode (default) |\n * | `'dblclick'` | Double-click enters edit mode |\n *\n * ## Keyboard Shortcuts\n *\n * | Key | Action |\n * |-----|--------|\n * | `Enter` | Commit edit and move down |\n * | `Tab` | Commit edit and move right |\n * | `Escape` | Cancel edit, restore original value |\n * | `Arrow Keys` | Navigate between cells (when not editing) |\n *\n * @example Basic editing with double-click trigger\n * ```ts\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', editable: true },\n * { field: 'price', type: 'number', editable: true },\n * { field: 'active', type: 'boolean', editable: true },\n * ],\n * plugins: [new EditingPlugin({ editOn: 'dblclick' })],\n * };\n *\n * grid.on('cell-commit', ({ field, oldValue, newValue }) => {\n * console.log(`${field}: ${oldValue} → ${newValue}`);\n * });\n * ```\n *\n * @example Custom editor function\n * ```ts\n * columns: [\n * {\n * field: 'status',\n * editable: true,\n * editor: (ctx) => {\n * const select = document.createElement('select');\n * ['pending', 'active', 'completed'].forEach(opt => {\n * const option = document.createElement('option');\n * option.value = opt;\n * option.textContent = opt;\n * option.selected = ctx.value === opt;\n * select.appendChild(option);\n * });\n * select.addEventListener('change', () => ctx.commit(select.value));\n * return select;\n * },\n * },\n * ]\n * ```\n *\n * @see {@link EditingConfig} for configuration options\n * @see {@link EditorContext} for custom editor context\n * @see [Live Demos](?path=/docs/grid-plugins-editing--docs) for interactive examples\n */\nexport class EditingPlugin<T = unknown> extends BaseGridPlugin<EditingConfig> {\n /**\n * Plugin manifest - declares owned properties for configuration validation.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n ownedProperties: [\n {\n property: 'editable',\n level: 'column',\n description: 'the \"editable\" column property',\n isUsed: (v) => v === true || typeof v === 'function',\n },\n {\n property: 'editor',\n level: 'column',\n description: 'the \"editor\" column property',\n },\n {\n property: 'editorParams',\n level: 'column',\n description: 'the \"editorParams\" column property',\n },\n {\n property: 'nullable',\n level: 'column',\n description: 'the \"nullable\" column property (allows null values)',\n },\n ],\n events: [\n {\n type: 'cell-edit-committed',\n description: 'Emitted when a cell edit is committed (for plugin-to-plugin coordination)',\n },\n ],\n queries: [\n {\n type: 'isEditing',\n description: 'Returns whether any cell is currently being edited',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'editing';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<EditingConfig> {\n return {\n mode: 'row',\n editOn: 'click',\n };\n }\n\n /**\n * Whether the grid is in 'grid' mode (all cells always editable).\n */\n get #isGridMode(): boolean {\n return this.config.mode === 'grid';\n }\n\n /**\n * Resolve whether a given cell is editable.\n *\n * Resolution order:\n * 1. `gridConfig.rowEditable(row)` — row-level gate (if provided). Returns `false` → not editable.\n * 2. `column.editable` — `true`, `false`, or `(row) => boolean`.\n *\n * @returns `true` when the cell should be editable, `false` otherwise.\n */\n #isCellEditable(column: ColumnConfig<T>, row: T): boolean {\n // Row-level gate\n const rowEditable = this.#internalGrid.effectiveConfig?.rowEditable;\n if (rowEditable && !rowEditable(row as any)) return false;\n\n // Column-level check\n const { editable } = column;\n if (typeof editable === 'function') return editable(row);\n return editable === true;\n }\n\n /**\n * Check whether a column has ANY editability configured (static `true` or a\n * function). This is used for quick checks where no specific row is available\n * (e.g. \"does this row have any potentially-editable columns?\").\n */\n #hasEditableConfig(column: ColumnConfig<T>): boolean {\n return column.editable === true || typeof column.editable === 'function';\n }\n\n // #region Editing State (fully owned by plugin)\n\n /** Currently active edit row index, or -1 if not editing */\n #activeEditRow = -1;\n\n /** Row ID of the currently active edit row (stable across _rows replacement) */\n #activeEditRowId: string | undefined;\n\n /** Reference to the row object at edit-open time. Used as fallback in\n * #exitRowEdit when no row ID is available (prevents stale-index access). */\n #activeEditRowRef: T | undefined;\n\n /** Currently active edit column index, or -1 if not editing */\n #activeEditCol = -1;\n\n /** Snapshots of row data before editing started */\n #rowEditSnapshots = new Map<number, T>();\n\n /** Set of cells currently in edit mode: \"rowIndex:colIndex\" */\n #editingCells = new Set<string>();\n\n /**\n * Value-change callbacks for active editors.\n * Keyed by \"rowIndex:field\" → callback that pushes updated values to the editor.\n * Populated during #injectEditor, cleaned up when editors are removed.\n */\n #editorValueCallbacks = new Map<string, (newValue: unknown) => void>();\n\n /** Flag to restore focus after next render (used when exiting edit mode) */\n #pendingFocusRestore = false;\n\n /** Row index pending animation after render, or -1 if none */\n #pendingRowAnimation = -1;\n\n /**\n * Cell validation manager — handles invalid-cell state tracking and DOM sync.\n * Initialized lazily in `attach()` since the sync callback needs grid access.\n */\n #validation!: CellValidationManager;\n\n /**\n * In grid mode, tracks whether an input field is currently focused.\n * When true: arrow keys work within input (edit mode).\n * When false: arrow keys navigate between cells (navigation mode).\n * Escape switches to navigation mode, Enter switches to edit mode.\n */\n #gridModeInputFocused = false;\n\n /**\n * In grid mode, when true, prevents inputs from auto-focusing.\n * This is set when Escape is pressed (navigation mode) and cleared\n * when Enter is pressed or user explicitly clicks an input.\n */\n #gridModeEditLocked = false;\n\n /**\n * When true, only a single cell is being edited (triggered by F2 or `beginCellEdit`).\n * Tab and Arrow keys commit and close the editor instead of navigating to adjacent cells.\n */\n #singleCellEdit = false;\n\n /**\n * In grid mode, snapshot of the focused cell's value when the editor first\n * receives focus. Used to revert on Escape (cell-level cancel).\n */\n #gridModeCellSnapshot: { rowIndex: number; colIndex: number; field: string; value: unknown } | null = null;\n\n // --- Dirty Tracking State (delegated to DirtyTrackingManager) ---\n\n /** Manages all dirty tracking state: baselines, changed/new/committed sets. */\n readonly #dirty = new DirtyTrackingManager<T>();\n\n // --- Editor Injection Deps (cached for #injectEditor delegation) ---\n\n /** Dependency bag created once in `attach()` and reused by every `#injectEditor` call. */\n #editorDeps!: EditorInjectionDeps<T>;\n\n /**\n * Typed accessor for `InternalGrid` — avoids repeating `as unknown as` at every call site.\n * Safe because `DataGridElement` implements `InternalGrid` at runtime.\n */\n get #internalGrid(): GridHost<T> {\n return this.grid as unknown as GridHost<T>;\n }\n\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n\n const signal = this.disconnectSignal;\n const internalGrid = this.#internalGrid;\n\n // Initialize cell validation manager with DOM sync callback\n this.#validation = new CellValidationManager((rowId, field, invalid) => {\n this.#syncInvalidCellAttribute(rowId, field, invalid);\n });\n\n // Initialize editor injection deps (cached for all #injectEditor calls)\n this.#editorDeps = {\n grid: internalGrid,\n isGridMode: this.#isGridMode,\n config: this.config,\n editingCells: this.#editingCells,\n editorValueCallbacks: this.#editorValueCallbacks,\n isEditSessionActive: () => this.#activeEditRow !== -1,\n commitCellValue: (ri, col, val, row) => this.#commitCellValue(ri, col, val, row),\n exitRowEdit: (ri, revert) => this.#exitRowEdit(ri, revert),\n };\n\n // Inject editing state and methods onto grid for backward compatibility\n internalGrid._activeEditRows = -1;\n internalGrid._rowEditSnapshots = new Map();\n\n // Inject changedRows getter\n Object.defineProperty(grid, 'changedRows', {\n get: () => this.changedRows,\n configurable: true,\n });\n\n // Inject changedRowIds getter (new ID-based API)\n Object.defineProperty(grid, 'changedRowIds', {\n get: () => this.changedRowIds,\n configurable: true,\n });\n\n // Inject raw Set for O(1) lookup in the render hot path\n Object.defineProperty(grid, '_changedRowIdSet', {\n get: () => this.#dirty.changedRowIds,\n configurable: true,\n });\n\n // Inject resetChangedRows method\n (grid as any).resetChangedRows = (silent?: boolean) => this.resetChangedRows(silent);\n\n // Inject beginBulkEdit method (for backward compatibility)\n (grid as any).beginBulkEdit = (rowIndex: number, field?: string) => {\n if (field) {\n this.beginCellEdit(rowIndex, field);\n }\n // If no field specified, we can't start editing without a specific cell\n };\n\n // Document-level Escape to cancel editing (only in 'row' mode)\n document.addEventListener(\n 'keydown',\n (e: KeyboardEvent) => {\n // In grid mode, Escape doesn't exit edit mode\n if (this.#isGridMode) return;\n if (e.key === 'Escape' && this.#activeEditRow !== -1) {\n if (shouldPreventEditClose(this.config, e)) return;\n this.#exitRowEdit(this.#activeEditRow, true);\n }\n },\n { capture: true, signal },\n );\n\n // Click outside to commit editing (only in 'row' mode)\n // Use queueMicrotask to allow pending change events to fire first.\n // This is important for Angular/React editors where the (change) event\n // fires after mousedown but before mouseup/click.\n document.addEventListener(\n 'mousedown',\n (e: MouseEvent) => {\n // In grid mode, clicking outside doesn't exit edit mode\n if (this.#isGridMode) return;\n if (this.#activeEditRow === -1) return;\n const rowEl = internalGrid.findRenderedRowElement?.(this.#activeEditRow);\n if (!rowEl) return;\n const path = (e.composedPath && e.composedPath()) || [];\n if (path.includes(rowEl)) return;\n\n // Check if click is inside a registered external focus container\n // (e.g., overlays, datepickers, dropdowns at <body> level).\n // Only check targets OUTSIDE the grid — clicks on other rows inside\n // the grid should still commit the active edit row.\n const target = e.target as Node | null;\n if (target && !this.gridElement.contains(target) && this.grid.containsFocus?.(target)) {\n return;\n }\n\n if (shouldPreventEditClose(this.config, e)) return;\n\n // Delay exit to allow pending change/commit events to fire\n queueMicrotask(() => {\n if (this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, false);\n }\n });\n },\n { signal },\n );\n\n // Focus trap: when enabled, prevent focus from leaving the grid\n // while a row is being edited. If focus moves outside the grid\n // (and its registered external containers), reclaim it.\n if (this.config.focusTrap) {\n this.gridElement.addEventListener(\n 'focusout',\n (e: FocusEvent) => {\n // Only trap in row mode when actively editing\n if (this.#isGridMode) return;\n if (this.#activeEditRow === -1) return;\n\n const related = e.relatedTarget as Node | null;\n // If focus is going to an external container, that's fine\n if (related && this.grid.containsFocus?.(related)) return;\n // If focus is going to another element inside the grid, allow it\n if (related && this.gridElement.contains(related)) return;\n\n // Focus left the grid entirely — reclaim it\n queueMicrotask(() => {\n // Re-check in case editing was committed in the meantime\n if (this.#activeEditRow === -1) return;\n this.#focusCurrentCellEditor();\n });\n },\n { signal },\n );\n }\n\n // Listen for external row mutations to push updated values to active editors.\n // When field A commits and sets field B via updateRow(), field B's editor\n // (if open) must reflect the new value.\n this.gridElement.addEventListener(\n 'cell-change',\n (e: Event) => {\n const detail = (e as CustomEvent).detail as {\n rowIndex: number;\n field: string;\n newValue: unknown;\n source: string;\n };\n // Only push updates from cascade/api sources — not from the editor's own commit\n if (detail.source === 'user') return;\n const key = `${detail.rowIndex}:${detail.field}`;\n const cb = this.#editorValueCallbacks.get(key);\n if (cb) cb(detail.newValue);\n },\n { signal },\n );\n\n // --- Dirty tracking: listen for undo/redo events to re-evaluate dirty state ---\n if (this.config.dirtyTracking) {\n const handleUndoRedo = (e: Event) => {\n const detail = (e as CustomEvent).detail as { action?: { rowIndex: number; field: string } };\n const action = detail?.action;\n if (!action) return;\n const row = this.rows[action.rowIndex] as T | undefined;\n if (!row) return;\n const rowId = this.grid.getRowId(row);\n if (!rowId) return;\n const dirty = this.#dirty.isRowDirty(rowId, row);\n this.emit<DirtyChangeDetail<T>>('dirty-change', {\n rowId,\n row,\n original: this.#dirty.getOriginalRow(rowId),\n type: dirty ? 'modified' : 'pristine',\n });\n };\n this.gridElement.addEventListener('undo', handleUndoRedo, { signal });\n this.gridElement.addEventListener('redo', handleUndoRedo, { signal });\n\n // Listen for row-inserted events to auto-mark new rows for dirty tracking\n this.on('row-inserted', (detail: { row: T; index: number }) => {\n const rowId = this.grid.getRowId(detail.row);\n if (rowId != null) {\n this.markAsNew(String(rowId));\n }\n });\n }\n\n // In grid mode, request a full render to trigger afterCellRender hooks\n if (this.#isGridMode) {\n internalGrid._isGridEditMode = true;\n this.gridElement.classList.add('tbw-grid-mode');\n this.requestRender();\n\n // Track focus/blur on inputs to maintain navigation vs edit mode state\n this.gridElement.addEventListener(\n 'focusin',\n (e: FocusEvent) => {\n const target = e.target as HTMLElement;\n // Ignore focus on the grid element itself — it has tabindex=0 so it\n // matches FOCUSABLE_EDITOR_SELECTOR, but blurring + re-focusing it\n // would cause infinite recursion.\n if (target === this.gridElement) return;\n if (target.matches(FOCUSABLE_EDITOR_SELECTOR)) {\n // If edit is locked (navigation mode), blur the input immediately\n if (this.#gridModeEditLocked) {\n target.blur();\n this.gridElement.focus();\n return;\n }\n\n // Snapshot cell value on initial focus or when moving to a different cell.\n // This allows Escape to revert the cell to its pre-edit value.\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n const snap = this.#gridModeCellSnapshot;\n if (!snap || snap.rowIndex !== focusRow || snap.colIndex !== focusCol) {\n const column = internalGrid._visibleColumns?.[focusCol];\n const rowData = internalGrid._rows?.[focusRow];\n if (column?.field && rowData) {\n const field = column.field as string;\n this.#gridModeCellSnapshot = {\n rowIndex: focusRow,\n colIndex: focusCol,\n field,\n value: (rowData as Record<string, unknown>)[field],\n };\n }\n }\n\n this.#gridModeInputFocused = true;\n }\n },\n { signal },\n );\n\n this.gridElement.addEventListener(\n 'focusout',\n (e: FocusEvent) => {\n const related = e.relatedTarget as HTMLElement | null;\n // Only clear if focus went outside grid (and external containers) or to a non-input element\n if (\n !related ||\n (!this.gridElement.contains(related) && !this.grid.containsFocus?.(related)) ||\n !related.matches(FOCUSABLE_EDITOR_SELECTOR)\n ) {\n this.#gridModeInputFocused = false;\n // Clear cell snapshot when leaving edit mode normally (not via Escape)\n this.#gridModeCellSnapshot = null;\n }\n },\n { signal },\n );\n\n // Handle Escape key directly on the grid element (capture phase)\n // This ensures we intercept Escape even when focus is inside an input\n this.gridElement.addEventListener(\n 'keydown',\n (e: KeyboardEvent) => {\n if (e.key === 'Escape' && this.#gridModeInputFocused) {\n // Check onBeforeEditClose to let overlays close first.\n if (shouldPreventEditClose(this.config, e)) {\n // Overlay is open — schedule deferred nav-mode transition\n // after the overlay tears down.\n queueMicrotask(() => {\n if (this.#gridModeInputFocused) {\n this.#revertGridModeCellEdit();\n const activeEl = document.activeElement as HTMLElement;\n if (activeEl && this.gridElement.contains(activeEl)) {\n activeEl.blur();\n this.gridElement.focus();\n }\n this.#gridModeInputFocused = false;\n this.#gridModeEditLocked = true;\n }\n });\n return;\n }\n // Revert cell value to pre-edit snapshot\n this.#revertGridModeCellEdit();\n\n const activeEl = document.activeElement as HTMLElement;\n if (activeEl && this.gridElement.contains(activeEl)) {\n activeEl.blur();\n // Move focus to the grid container so arrow keys work\n this.gridElement.focus();\n }\n this.#gridModeInputFocused = false;\n this.#gridModeEditLocked = true; // Lock edit mode until Enter/click\n e.preventDefault();\n e.stopPropagation();\n }\n },\n { capture: true, signal },\n );\n\n // Handle click on inputs - unlock edit mode when user explicitly clicks\n this.gridElement.addEventListener(\n 'mousedown',\n (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n if (target.matches(FOCUSABLE_EDITOR_SELECTOR)) {\n this.#gridModeEditLocked = false; // User clicked input - allow edit\n }\n },\n { signal },\n );\n }\n }\n\n /** @internal */\n override detach(): void {\n const internalGrid = this.#internalGrid;\n internalGrid._isGridEditMode = false;\n this.gridElement.classList.remove('tbw-grid-mode');\n this.#activeEditRow = -1;\n this.#activeEditRowId = undefined;\n this.#activeEditRowRef = undefined;\n this.#activeEditCol = -1;\n this.#rowEditSnapshots.clear();\n this.#dirty.clear();\n this.#editingCells.clear();\n this.#editorValueCallbacks.clear();\n this.#gridModeInputFocused = false;\n this.#gridModeEditLocked = false;\n this.#gridModeCellSnapshot = null;\n this.#singleCellEdit = false;\n super.detach();\n }\n\n /**\n * Handle plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'isEditing') {\n // In grid mode, we're always editing\n return this.#isGridMode || this.#activeEditRow !== -1;\n }\n return undefined;\n }\n\n // #endregion\n\n // #region Event Handlers (event distribution)\n\n /**\n * Handle cell clicks - start editing if configured for click mode.\n * Both click and dblclick events come through this handler.\n * Starts row-based editing (all editable cells in the row get editors).\n * @internal\n */\n override onCellClick(event: CellClickEvent): boolean | void {\n // In grid mode, all cells are already editable - no need to trigger row edit\n if (this.#isGridMode) return false;\n\n const internalGrid = this.#internalGrid;\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n\n // Check if editing is disabled\n if (editOn === false || editOn === 'manual') return false;\n\n // Check if this is click or dblclick mode\n if (editOn !== 'click' && editOn !== 'dblclick') return false;\n\n // Check if the event type matches the edit mode\n const isDoubleClick = event.originalEvent.type === 'dblclick';\n if (editOn === 'click' && isDoubleClick) return false; // In click mode, only handle single clicks\n if (editOn === 'dblclick' && !isDoubleClick) return false; // In dblclick mode, only handle double clicks\n\n const { rowIndex } = event;\n\n // Check if any column in the row is potentially editable\n const hasEditableColumn = internalGrid._columns?.some((col) => this.#hasEditableConfig(col as ColumnConfig<T>));\n if (!hasEditableColumn) return false;\n\n // Row-level gate\n const rowData = internalGrid._rows[rowIndex];\n const rowEditable = internalGrid.effectiveConfig?.rowEditable;\n if (rowData && rowEditable && !rowEditable(rowData as any)) return false;\n\n // Start row-based editing (all editable cells get editors)\n event.originalEvent.stopPropagation();\n this.beginBulkEdit(rowIndex);\n return true; // Handled\n }\n\n /**\n * Handle keyboard events for edit lifecycle.\n * @internal\n */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n const internalGrid = this.#internalGrid;\n\n // Escape: cancel current edit (row mode) or exit edit mode (grid mode)\n if (event.key === 'Escape') {\n // In grid mode: revert cell, blur input, enter navigation mode.\n // NOTE: No onBeforeEditClose check here — the capture-phase handler\n // is the authoritative overlay guard. If the event reaches this\n // bubble-phase handler, either:\n // a) the capture handler already bailed (overlay was open) and the\n // template handler (e.g. select.close()) addressed it, or\n // b) no overlay was open.\n // In both cases we should transition to navigation mode.\n if (this.#isGridMode && this.#gridModeInputFocused) {\n // Revert cell value to pre-edit snapshot\n this.#revertGridModeCellEdit();\n\n const activeEl = document.activeElement as HTMLElement;\n if (activeEl && this.gridElement.contains(activeEl)) {\n activeEl.blur();\n // Move focus to the grid so arrow keys navigate cells, not the editor\n this.gridElement.focus();\n }\n this.#gridModeInputFocused = false;\n this.#gridModeEditLocked = true; // Lock until Enter/click re-enables editing\n // Update focus styling\n this.requestAfterRender();\n return true;\n }\n\n // In row mode: cancel edit\n if (this.#activeEditRow !== -1 && !this.#isGridMode) {\n if (shouldPreventEditClose(this.config, event)) return true;\n this.#exitRowEdit(this.#activeEditRow, true);\n return true;\n }\n }\n\n // Arrow keys in grid mode when not editing input: navigate cells\n if (\n this.#isGridMode &&\n !this.#gridModeInputFocused &&\n (event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'ArrowLeft' || event.key === 'ArrowRight')\n ) {\n // Let the grid's default keyboard navigation handle this\n return false;\n }\n\n // Arrow Up/Down in grid mode when input is focused: let the editor handle it\n // (e.g., ArrowDown opens autocomplete/datepicker overlays, ArrowUp/Down navigates options)\n if (this.#isGridMode && this.#gridModeInputFocused && (event.key === 'ArrowUp' || event.key === 'ArrowDown')) {\n return true; // Handled: block grid navigation, let event reach editor\n }\n\n // Arrow Up/Down while editing: commit and exit edit mode, move to adjacent row (only in 'row' mode)\n if ((event.key === 'ArrowUp' || event.key === 'ArrowDown') && this.#activeEditRow !== -1 && !this.#isGridMode) {\n if (shouldPreventEditClose(this.config, event)) return true;\n\n const maxRow = internalGrid._rows.length - 1;\n const currentRow = this.#activeEditRow;\n\n // Commit the current edit\n this.#exitRowEdit(currentRow, false);\n\n // Move focus to adjacent row (same column)\n if (event.key === 'ArrowDown') {\n internalGrid._focusRow = Math.min(maxRow, internalGrid._focusRow + 1);\n } else {\n internalGrid._focusRow = Math.max(0, internalGrid._focusRow - 1);\n }\n\n event.preventDefault();\n // Ensure the focused cell is scrolled into view\n ensureCellVisible(internalGrid);\n // Request render to update focus styling\n this.requestAfterRender();\n return true;\n }\n\n // Tab/Shift+Tab while editing: move to next/prev editable cell\n if (event.key === 'Tab' && (this.#activeEditRow !== -1 || this.#isGridMode)) {\n event.preventDefault();\n\n // In single-cell edit mode (F2), commit and close instead of navigating\n if (this.#singleCellEdit) {\n this.#exitRowEdit(this.#activeEditRow, false);\n return true;\n }\n\n const forward = !event.shiftKey;\n this.#handleTabNavigation(forward);\n return true;\n }\n\n // Space: toggle boolean cells (only when not in edit mode - let editors handle their own space)\n if (event.key === ' ' || event.key === 'Spacebar') {\n // If we're in row edit mode, let the event pass through to the editor (e.g., checkbox)\n if (this.#activeEditRow !== -1) {\n return false;\n }\n\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n if (focusRow >= 0 && focusCol >= 0) {\n const column = internalGrid._visibleColumns[focusCol];\n const rowData = internalGrid._rows[focusRow];\n if (\n column &&\n rowData &&\n this.#isCellEditable(column as ColumnConfig<T>, rowData as T) &&\n column.type === 'boolean'\n ) {\n const field = column.field;\n if (isSafePropertyKey(field)) {\n const currentValue = (rowData as Record<string, unknown>)[field];\n const newValue = !currentValue;\n this.#commitCellValue(focusRow, column, newValue, rowData);\n event.preventDefault();\n // Re-render to update the UI\n this.requestRender();\n return true;\n }\n }\n }\n // Space on non-boolean cell - don't block keyboard navigation\n return false;\n }\n\n // Enter (unmodified): start row edit, commit, or enter edit mode in grid mode\n if (event.key === 'Enter' && !event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) {\n // In grid mode when not editing: focus the current cell's input\n if (this.#isGridMode && !this.#gridModeInputFocused) {\n this.#focusCurrentCellEditor();\n return true;\n }\n\n if (this.#activeEditRow !== -1) {\n if (shouldPreventEditClose(this.config, event)) return true;\n // Already editing - let cell handlers deal with it\n return false;\n }\n\n // Start row-based editing (not just the focused cell)\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n if (editOn === false || editOn === 'manual') return false;\n\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n if (focusRow >= 0) {\n // Check if ANY column in the row is potentially editable\n const hasEditableColumn = internalGrid._columns?.some((col) => this.#hasEditableConfig(col as ColumnConfig<T>));\n // Row-level gate\n const rowData = internalGrid._rows[focusRow];\n const rowEditable = internalGrid.effectiveConfig?.rowEditable;\n const rowBlocked = rowData && rowEditable && !rowEditable(rowData as any);\n if (hasEditableColumn && !rowBlocked) {\n // Emit cell-activate event BEFORE starting edit\n // This ensures consumers always get the activation event\n const column = internalGrid._visibleColumns[focusCol];\n const row = internalGrid._rows[focusRow];\n const field = column?.field ?? '';\n const value = field && row ? (row as Record<string, unknown>)[field] : undefined;\n const cellEl = this.gridElement.querySelector(`[data-row=\"${focusRow}\"][data-col=\"${focusCol}\"]`) as\n | HTMLElement\n | undefined;\n\n const activateEvent = new CustomEvent('cell-activate', {\n cancelable: true,\n bubbles: true,\n detail: {\n rowIndex: focusRow,\n colIndex: focusCol,\n field,\n value,\n row,\n cellEl,\n trigger: 'keyboard' as const,\n originalEvent: event,\n },\n });\n this.gridElement.dispatchEvent(activateEvent);\n\n // Also emit deprecated activate-cell for backwards compatibility\n const legacyEvent = new CustomEvent('activate-cell', {\n cancelable: true,\n bubbles: true,\n detail: { row: focusRow, col: focusCol },\n });\n this.gridElement.dispatchEvent(legacyEvent);\n\n // If consumer canceled the activation, don't start editing\n if (activateEvent.defaultPrevented || legacyEvent.defaultPrevented) {\n event.preventDefault();\n return true;\n }\n\n this.beginBulkEdit(focusRow);\n return true;\n }\n }\n // No editable columns - don't block keyboard navigation\n return false;\n }\n\n // F2: begin single-cell edit on the focused cell\n if (event.key === 'F2') {\n if (this.#activeEditRow !== -1 || this.#isGridMode) return false;\n\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n if (editOn === false) return false;\n\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n if (focusRow >= 0 && focusCol >= 0) {\n const column = internalGrid._visibleColumns[focusCol];\n const rowData = internalGrid._rows[focusRow];\n if (column && rowData && this.#isCellEditable(column as ColumnConfig<T>, rowData as T) && column.field) {\n event.preventDefault();\n this.beginCellEdit(focusRow, column.field);\n return true;\n }\n }\n return false;\n }\n\n // Don't block other keyboard events\n return false;\n }\n\n // #endregion\n\n // #region Render Hooks\n\n /**\n * Process columns to merge type-level editorParams with column-level.\n * Column-level params take precedence.\n * @internal\n */\n override processColumns(columns: ColumnConfig<T>[]): ColumnConfig<T>[] {\n const internalGrid = this.#internalGrid;\n const typeDefaults = (internalGrid as any).effectiveConfig?.typeDefaults;\n const adapter = internalGrid.__frameworkAdapter;\n\n // If no type defaults configured anywhere, skip processing\n if (!typeDefaults && !adapter?.getTypeDefault) return columns;\n\n return columns.map((col) => {\n if (!col.type) return col;\n\n // Get type-level editorParams\n let typeEditorParams: Record<string, unknown> | undefined;\n\n // Check grid-level typeDefaults first\n if (typeDefaults?.[col.type]?.editorParams) {\n typeEditorParams = typeDefaults[col.type].editorParams;\n }\n\n // Then check app-level (adapter) typeDefaults\n if (!typeEditorParams && adapter?.getTypeDefault) {\n const appDefault = adapter.getTypeDefault<T>(col.type);\n if (appDefault?.editorParams) {\n typeEditorParams = appDefault.editorParams;\n }\n }\n\n // No type-level params to merge\n if (!typeEditorParams) return col;\n\n // Merge: type-level as base, column-level wins on conflicts\n return {\n ...col,\n editorParams: { ...typeEditorParams, ...col.editorParams },\n };\n });\n }\n\n /**\n * Stabilize the actively edited row across `rows` array replacements and\n * capture dirty tracking baselines.\n *\n * **Editing stability:** When the consumer reassigns `grid.rows` while\n * editing, the full pipeline (sort, filter, group) runs on the new data.\n * This hook finds the edited row in the new array by ID and swaps in the\n * in-progress row reference (`#activeEditRowRef`) so editors survive.\n *\n * **Dirty tracking baselines:** When `dirtyTracking` is enabled, captures\n * a `structuredClone` snapshot of each row on first appearance\n * (first-write-wins). This prevents Angular's feedback loop from\n * overwriting baselines.\n *\n * @internal Plugin API — part of the render pipeline\n */\n override processRows(rows: readonly T[]): T[] {\n const internalGrid = this.#internalGrid;\n\n // --- Dirty tracking: capture baselines (first-write-wins) ---\n if (this.config.dirtyTracking && internalGrid.getRowId) {\n this.#dirty.capture(rows, (r) => {\n try {\n return internalGrid.getRowId?.(r);\n } catch {\n return undefined;\n }\n });\n }\n\n // --- Editing stability: swap in the in-progress row ---\n if (this.#activeEditRow === -1 || this.#isGridMode) return rows as T[];\n\n const editRowId = this.#activeEditRowId;\n const editRowRef = this.#activeEditRowRef;\n\n // Without a stable row ID we cannot match across array replacements\n if (!editRowId || !editRowRef) return rows as T[];\n\n const result = [...rows] as T[];\n\n // Find the edited row's new position by ID\n let newIndex = -1;\n for (let i = 0; i < result.length; i++) {\n try {\n if (internalGrid.getRowId?.(result[i]) === editRowId) {\n newIndex = i;\n break;\n }\n } catch {\n // Row has no ID — skip\n }\n }\n\n if (newIndex === -1) {\n // Row was deleted server-side — close the editor.\n // Cannot close synchronously during the processRows pipeline;\n // schedule for after the current render cycle completes.\n setTimeout(() => this.cancelActiveRowEdit(), 0);\n return result;\n }\n\n // Swap in the in-progress row data to preserve editor state\n result[newIndex] = editRowRef;\n\n // Update index-keyed state if the position changed (due to sort/filter)\n if (this.#activeEditRow !== newIndex) {\n this.#migrateEditRowIndex(this.#activeEditRow, newIndex);\n }\n\n return result;\n }\n\n /**\n * After render, reapply editors to cells in edit mode.\n * This handles virtualization - when a row scrolls back into view,\n * we need to re-inject the editor.\n * @internal\n */\n override afterRender(): void {\n const internalGrid = this.#internalGrid;\n\n // --- Editing stability: verify active edit row index ---\n // After processRows, subsequent plugins (filtering, grouping) may have\n // shifted row indices. Verify the index is still correct and fix if needed\n // before re-injecting editors.\n if (this.#activeEditRow !== -1 && this.#activeEditRowRef && !this.#isGridMode) {\n if (internalGrid._rows[this.#activeEditRow] !== this.#activeEditRowRef) {\n const newIndex = (internalGrid._rows as T[]).indexOf(this.#activeEditRowRef);\n if (newIndex !== -1) {\n this.#migrateEditRowIndex(this.#activeEditRow, newIndex);\n } else {\n // Row no longer in rendered set (filtered out or deleted)\n setTimeout(() => this.cancelActiveRowEdit(), 0);\n return;\n }\n }\n }\n\n // Restore focus after exiting edit mode\n if (this.#pendingFocusRestore) {\n this.#pendingFocusRestore = false;\n this.#restoreCellFocus(internalGrid);\n }\n\n // Animate the row after render completes (so the row element exists)\n if (this.#pendingRowAnimation !== -1) {\n const rowIndex = this.#pendingRowAnimation;\n this.#pendingRowAnimation = -1;\n internalGrid.animateRow?.(rowIndex, 'change');\n }\n\n // Emit baselines-captured event when new baselines were captured this cycle.\n // Emitted post-render so consumers can safely read grid.rows, query the DOM,\n // or call getOriginalRow() in their handler.\n const capturedCount = this.#dirty.drainCapturedFlag();\n if (capturedCount != null) {\n this.emit<BaselinesCapturedDetail>('baselines-captured', {\n count: capturedCount,\n });\n }\n\n // In 'grid' mode, editors are injected via afterCellRender hook during render\n if (this.#isGridMode) return;\n\n if (this.#editingCells.size === 0) return;\n\n // Re-inject editors for any editing cells that are visible\n for (const cellKey of this.#editingCells) {\n const [rowStr, colStr] = cellKey.split(':');\n const rowIndex = parseInt(rowStr, 10);\n const colIndex = parseInt(colStr, 10);\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) continue;\n\n const cellEl = rowEl.querySelector(`.cell[data-col=\"${colIndex}\"]`) as HTMLElement | null;\n if (!cellEl || cellEl.classList.contains('editing')) continue;\n\n // Cell is visible but not in editing mode - reinject editor\n const rowData = internalGrid._rows[rowIndex];\n const column = internalGrid._visibleColumns[colIndex];\n if (rowData && column) {\n this.#injectEditor(rowData, rowIndex, column, colIndex, cellEl, true);\n }\n }\n }\n\n /**\n * Hook called after each cell is rendered.\n * In grid mode, injects editors into editable cells during render (no DOM queries needed).\n * @internal\n */\n override afterCellRender(context: AfterCellRenderContext): void {\n const { row, rowIndex, column, colIndex, cellElement } = context;\n\n const editable = this.#isCellEditable(column as ColumnConfig<T>, row as T);\n\n // Set aria-readonly on non-editable cells so screen readers can distinguish\n if (!editable) {\n cellElement.setAttribute('aria-readonly', 'true');\n // In grid mode, tear down stale editors on cells that are no longer editable\n // (e.g., after virtualization recycles a row element for different row data)\n if (this.#isGridMode && cellElement.classList.contains('editing')) {\n cellElement.classList.remove('editing');\n const value = (row as Record<string, unknown>)[(column as ColumnConfig<T>).field as string];\n cellElement.textContent = value == null ? '' : String(value);\n }\n } else {\n cellElement.removeAttribute('aria-readonly');\n }\n\n // Only inject editors in grid mode\n if (!this.#isGridMode) return;\n\n // Skip non-editable cells\n if (!editable) return;\n\n // Skip if already has editor\n if (cellElement.classList.contains('editing')) return;\n\n // Inject editor (don't track in editingCells - we're always editing in grid mode)\n // Pass rowElement so incrementEditingCount works even when cell is in a DocumentFragment\n this.#injectEditor(row as T, rowIndex, column as ColumnConfig<T>, colIndex, cellElement, true, context.rowElement);\n }\n\n /**\n * Apply dirty-tracking CSS classes to each rendered row.\n *\n * - `tbw-cell-dirty` on individual cells whose value differs from baseline\n * (applied on cell-commit, visible during editing)\n * - `tbw-row-dirty` on the row element only after the row edit session is\n * committed (edit-close without cancel)\n * - `tbw-row-new` when a row was inserted via `insertRow()` with no baseline\n *\n * Only active when `dirtyTracking: true`.\n *\n * @internal Plugin API\n */\n override afterRowRender(context: AfterRowRenderContext): void {\n if (!this.config.dirtyTracking) return;\n\n const internalGrid = this.#internalGrid;\n const rowId = internalGrid.getRowId?.(context.row as T);\n if (!rowId) return;\n\n const { isNew, isCommittedDirty, hasBaseline } = this.#dirty.getRowDirtyState(rowId, context.row as T);\n\n const el = context.rowElement;\n\n // Row-level classes (tbw-row-dirty only after row-commit AND data differs)\n el.classList.toggle('tbw-row-dirty', isCommittedDirty);\n el.classList.toggle('tbw-row-new', isNew);\n\n // Cell-level classes (tbw-cell-dirty on individual cells with changed values)\n // Only run the per-cell loop when the row has a baseline — avoids\n // querySelectorAll on the hot path for rows without dirty tracking state.\n if (hasBaseline) {\n const cells = el.querySelectorAll('.cell[data-field]');\n for (let i = 0; i < cells.length; i++) {\n const cell = cells[i] as HTMLElement;\n const field = cell.getAttribute('data-field');\n if (field) {\n cell.classList.toggle('tbw-cell-dirty', this.#dirty.isCellDirty(rowId, context.row as T, field));\n }\n }\n } else {\n // Clean up stale tbw-cell-dirty classes on recycled row elements\n // that previously displayed a dirty row but now show a pristine one.\n const dirtyCells = el.querySelectorAll('.tbw-cell-dirty');\n for (let i = 0; i < dirtyCells.length; i++) {\n dirtyCells[i].classList.remove('tbw-cell-dirty');\n }\n }\n }\n\n /**\n * On scroll render, reapply editors to recycled cells.\n * @internal\n */\n override onScrollRender(): void {\n this.afterRender();\n }\n\n // #endregion\n\n // #region Public API\n\n /**\n * Get all rows that have been modified.\n * Uses ID-based lookup for stability when rows are reordered.\n */\n get changedRows(): T[] {\n return this.#dirty.getChangedRows((id) => this.grid.getRow(id) as T | undefined);\n }\n\n /**\n * Get IDs of all modified rows.\n */\n get changedRowIds(): string[] {\n return this.#dirty.getChangedRowIds();\n }\n\n /**\n * Get the currently active edit row index, or -1 if not editing.\n */\n get activeEditRow(): number {\n return this.#activeEditRow;\n }\n\n /**\n * Get the currently active edit column index, or -1 if not editing.\n */\n get activeEditCol(): number {\n return this.#activeEditCol;\n }\n\n /**\n * Check if a specific row is currently being edited.\n */\n isRowEditing(rowIndex: number): boolean {\n return this.#activeEditRow === rowIndex;\n }\n\n /**\n * Check if a specific cell is currently being edited.\n */\n isCellEditing(rowIndex: number, colIndex: number): boolean {\n return this.#editingCells.has(`${rowIndex}:${colIndex}`);\n }\n\n /**\n * Check if a specific row has been modified.\n * @param rowIndex - Row index to check (will be converted to ID internally)\n */\n isRowChanged(rowIndex: number): boolean {\n const internalGrid = this.#internalGrid;\n const row = internalGrid._rows[rowIndex];\n if (!row) return false;\n try {\n const rowId = internalGrid.getRowId?.(row);\n return rowId ? this.#dirty.isRowChanged(rowId) : false;\n } catch {\n return false;\n }\n }\n\n /**\n * Check if a row with the given ID has been modified.\n * @param rowId - Row ID to check\n */\n isRowChangedById(rowId: string): boolean {\n return this.#dirty.isRowChanged(rowId);\n }\n\n // #region Dirty Tracking API (delegated to DirtyTrackingManager)\n\n /** Check if a row differs from its baseline. Requires `dirtyTracking: true`. */\n isDirty(rowId: string): boolean {\n if (!this.config.dirtyTracking) return false;\n if (this.#dirty.newRowIds.has(rowId)) return true;\n const row = this.grid.getRow(rowId) as T | undefined;\n if (!row) return false;\n return this.#dirty.isRowDirty(rowId, row);\n }\n\n /** Inverse of `isDirty`. */\n isPristine(rowId: string): boolean {\n return !this.isDirty(rowId);\n }\n\n /** Whether any row in the grid is dirty. */\n get dirty(): boolean {\n if (!this.config.dirtyTracking) return false;\n const internalGrid = this.#internalGrid;\n return this.#dirty.hasAnyDirty((rowId) => internalGrid._getRowEntry(rowId)?.row as T | undefined);\n }\n\n /** Whether all rows are pristine. */\n get pristine(): boolean {\n return !this.dirty;\n }\n\n /** Re-snapshot baseline from current data (call after a successful save). */\n markAsPristine(rowId: string): void {\n if (!this.config.dirtyTracking) return;\n const row = this.grid.getRow(rowId) as T | undefined;\n if (!row) return;\n this.#dirty.markPristine(rowId, row);\n this.emit<DirtyChangeDetail<T>>('dirty-change', {\n rowId,\n row,\n original: row, // after mark-pristine, original === current\n type: 'pristine',\n });\n }\n\n /** Mark a row as new (auto-called by `insertRow()` when dirty tracking is on). */\n markAsNew(rowId: string): void {\n if (!this.config.dirtyTracking) return;\n this.#dirty.markNew(rowId);\n const row = this.grid.getRow(rowId) as T | undefined;\n this.emit<DirtyChangeDetail<T>>('dirty-change', {\n rowId,\n row: row as T,\n original: undefined,\n type: 'new',\n });\n }\n\n /** Mark a row as dirty after an external mutation that bypassed the editing pipeline. */\n markAsDirty(rowId: string): void {\n if (!this.config.dirtyTracking) return;\n const row = this.grid.getRow(rowId) as T | undefined;\n if (!row) return;\n this.#dirty.markDirty(rowId);\n this.emit<DirtyChangeDetail<T>>('dirty-change', {\n rowId,\n row,\n original: this.#dirty.getOriginalRow(rowId),\n type: 'modified',\n });\n }\n\n /** Mark all tracked rows as pristine (call after a batch save). */\n markAllPristine(): void {\n if (!this.config.dirtyTracking) return;\n const internalGrid = this.#internalGrid;\n this.#dirty.markAllPristine((rowId) => internalGrid._getRowEntry(rowId)?.row as T | undefined);\n }\n\n /** Get a deep clone of the original (baseline) row data. Returns `undefined` for new rows. */\n getOriginalRow(rowId: string): T | undefined {\n if (!this.config.dirtyTracking) return undefined;\n return this.#dirty.getOriginalRow(rowId);\n }\n\n /** Lightweight check for whether a baseline exists (no cloning). */\n hasBaseline(rowId: string): boolean {\n if (!this.config.dirtyTracking) return false;\n return this.#dirty.hasBaseline(rowId);\n }\n\n /** Get all dirty rows with their original and current data. */\n getDirtyRows(): DirtyRowEntry<T>[] {\n if (!this.config.dirtyTracking) return [];\n const internalGrid = this.#internalGrid;\n return this.#dirty.getDirtyRows((rowId) => internalGrid._getRowEntry(rowId)?.row as T | undefined);\n }\n\n /**\n * Get IDs of all dirty rows.\n */\n get dirtyRowIds(): string[] {\n if (!this.config.dirtyTracking) return [];\n const internalGrid = this.#internalGrid;\n return this.#dirty.getDirtyRowIds((rowId) => internalGrid._getRowEntry(rowId)?.row as T | undefined);\n }\n\n /**\n * Revert a row to its baseline values (mutates the current row in-place).\n * Triggers a re-render.\n *\n * @param rowId - Row ID (from `getRowId`)\n */\n revertRow(rowId: string): void {\n if (!this.config.dirtyTracking) return;\n const row = this.grid.getRow(rowId) as T | undefined;\n if (!row) return;\n if (this.#dirty.revertRow(rowId, row)) {\n this.emit<DirtyChangeDetail<T>>('dirty-change', {\n rowId,\n row,\n original: this.#dirty.getOriginalRow(rowId),\n type: 'reverted',\n });\n this.requestRender();\n }\n }\n\n /**\n * Revert all dirty rows to their baseline values and re-render.\n */\n revertAll(): void {\n if (!this.config.dirtyTracking) return;\n const internalGrid = this.#internalGrid;\n this.#dirty.revertAll((rowId) => internalGrid._getRowEntry(rowId)?.row as T | undefined);\n this.requestRender();\n }\n\n // #endregion\n\n // #region Cell Validation (delegated to CellValidationManager)\n\n /**\n * Mark a cell as invalid with an optional validation message.\n * Invalid cells are marked with a `data-invalid` attribute for styling.\n *\n * @param rowId - The row ID (from getRowId)\n * @param field - The field name\n * @param message - Optional validation message (for tooltips or display)\n *\n * @example\n * ```typescript\n * // In cell-commit handler:\n * grid.on('cell-commit', (detail, e) => {\n * if (detail.field === 'email' && !isValidEmail(detail.value)) {\n * detail.setInvalid('Invalid email format');\n * }\n * });\n *\n * // Or programmatically:\n * editingPlugin.setInvalid('row-123', 'email', 'Invalid email format');\n * ```\n */\n setInvalid(rowId: string, field: string, message = ''): void {\n this.#validation.setInvalid(rowId, field, message);\n }\n\n /** Clear the invalid state for a specific cell. */\n clearInvalid(rowId: string, field: string): void {\n this.#validation.clearInvalid(rowId, field);\n }\n\n /** Clear all invalid cells for a specific row. */\n clearRowInvalid(rowId: string): void {\n this.#validation.clearRowInvalid(rowId);\n }\n\n /** Clear all invalid cell states across all rows. */\n clearAllInvalid(): void {\n this.#validation.clearAllInvalid();\n }\n\n /** Check if a specific cell is marked as invalid. */\n isCellInvalid(rowId: string, field: string): boolean {\n return this.#validation.isCellInvalid(rowId, field);\n }\n\n /** Get the validation message for an invalid cell. */\n getInvalidMessage(rowId: string, field: string): string | undefined {\n return this.#validation.getInvalidMessage(rowId, field);\n }\n\n /** Check if a row has any invalid cells. */\n hasInvalidCells(rowId: string): boolean {\n return this.#validation.hasInvalidCells(rowId);\n }\n\n /** Get all invalid fields for a row. */\n getInvalidFields(rowId: string): Map<string, string> {\n return this.#validation.getInvalidFields(rowId);\n }\n\n // #endregion\n\n /**\n * Reset all change tracking.\n * @param silent - If true, suppresses the `changed-rows-reset` event\n * @fires changed-rows-reset - Emitted when tracking is reset (unless silent)\n */\n resetChangedRows(silent?: boolean): void {\n const rows = this.changedRows;\n const ids = this.changedRowIds;\n this.#dirty.changedRowIds.clear();\n this.#dirty.committedDirtyRowIds.clear();\n this.#syncGridEditState();\n\n if (!silent) {\n this.emit<ChangedRowsResetDetail<T>>('changed-rows-reset', { rows, ids });\n }\n\n // Clear visual indicators\n const internalGrid = this.#internalGrid;\n internalGrid._rowPool?.forEach((r) => r.classList.remove('changed'));\n }\n\n /**\n * Programmatically begin editing a cell.\n * @param rowIndex - Index of the row to edit\n * @param field - Field name of the column to edit\n * @fires cell-commit - Emitted when the cell value is committed (on blur or Enter)\n */\n beginCellEdit(rowIndex: number, field: string): void {\n const internalGrid = this.#internalGrid;\n const colIndex = internalGrid._visibleColumns.findIndex((c) => c.field === field);\n if (colIndex === -1) return;\n\n const column = internalGrid._visibleColumns[colIndex];\n const rowData = internalGrid._rows[rowIndex];\n if (!column || !rowData || !this.#isCellEditable(column as ColumnConfig<T>, rowData as T)) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${colIndex}\"]`) as HTMLElement | null;\n if (!cellEl) return;\n\n this.#singleCellEdit = true;\n this.#beginCellEdit(rowIndex, colIndex, cellEl);\n }\n\n /**\n * Programmatically begin editing all editable cells in a row.\n * @param rowIndex - Index of the row to edit\n * @fires cell-commit - Emitted for each cell value that is committed\n * @fires row-commit - Emitted when focus leaves the row\n */\n beginBulkEdit(rowIndex: number): void {\n const internalGrid = this.#internalGrid;\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n if (editOn === false) return;\n\n const hasEditableColumn = internalGrid._columns?.some((col) => this.#hasEditableConfig(col as ColumnConfig<T>));\n if (!hasEditableColumn) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) return;\n\n // Row-level gate\n const rowData = internalGrid._rows[rowIndex];\n const rowEditable = internalGrid.effectiveConfig?.rowEditable;\n if (rowData && rowEditable && !rowEditable(rowData as any)) return;\n\n // Bulk edit clears single-cell mode\n this.#singleCellEdit = false;\n\n // Start row edit\n this.#startRowEdit(rowIndex, rowData);\n\n // Enter edit mode on all editable cells\n Array.from(rowEl.children).forEach((cell, i) => {\n const col = internalGrid._visibleColumns[i];\n if (col && this.#isCellEditable(col as ColumnConfig<T>, rowData as T)) {\n const cellEl = cell as HTMLElement;\n if (!cellEl.classList.contains('editing')) {\n this.#injectEditor(rowData, rowIndex, col, i, cellEl, true);\n }\n }\n });\n\n // Focus the first editable cell\n setTimeout(() => {\n let targetCell = rowEl.querySelector(`.cell[data-col=\"${internalGrid._focusCol}\"]`);\n if (!targetCell?.classList.contains('editing')) {\n targetCell = rowEl.querySelector('.cell.editing');\n }\n if (targetCell?.classList.contains('editing')) {\n const editor = (targetCell as HTMLElement).querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n try {\n editor?.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n }\n }, 0);\n }\n\n /**\n * Commit the currently active row edit.\n * @fires row-commit - Emitted after the row edit is committed\n */\n commitActiveRowEdit(): void {\n if (this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, false);\n }\n }\n\n /**\n * Cancel the currently active row edit.\n */\n cancelActiveRowEdit(): void {\n if (this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, true);\n }\n }\n\n // #endregion\n\n // #region Internal Methods\n\n /**\n * Sync the data-invalid attribute on a cell element.\n * Used as the DOM callback for CellValidationManager.\n */\n #syncInvalidCellAttribute(rowId: string, field: string, invalid: boolean): void {\n const internalGrid = this.#internalGrid;\n const colIndex = internalGrid._visibleColumns?.findIndex((c) => c.field === field);\n if (colIndex === -1 || colIndex === undefined) return;\n\n // Find the row element by rowId\n const rows = internalGrid._rows;\n const rowIndex = rows?.findIndex((r) => {\n try {\n return internalGrid.getRowId?.(r) === rowId;\n } catch {\n return false;\n }\n });\n if (rowIndex === -1 || rowIndex === undefined) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${colIndex}\"]`) as HTMLElement | null;\n if (!cellEl) return;\n\n if (invalid) {\n cellEl.setAttribute('data-invalid', 'true');\n const message = this.#validation.getInvalidMessage(rowId, field);\n if (message) {\n cellEl.setAttribute('title', message);\n }\n } else {\n cellEl.removeAttribute('data-invalid');\n cellEl.removeAttribute('title');\n }\n }\n\n /**\n * Migrate all index-keyed editing state when the active edit row moves to\n * a different position in `_rows` (e.g. after sort, filter, or new data push).\n *\n * Updates: `#activeEditRow`, `#editingCells`, `#rowEditSnapshots`,\n * `#editorValueCallbacks`, and syncs `_activeEditRows` on the grid.\n */\n #migrateEditRowIndex(oldIndex: number, newIndex: number): void {\n this.#activeEditRow = newIndex;\n\n // Migrate #editingCells keys (\"rowIndex:colIndex\")\n const migratedCells = new Set<string>();\n const prefix = `${oldIndex}:`;\n for (const cellKey of this.#editingCells) {\n if (cellKey.startsWith(prefix)) {\n migratedCells.add(`${newIndex}:${cellKey.substring(prefix.length)}`);\n } else {\n migratedCells.add(cellKey);\n }\n }\n this.#editingCells.clear();\n for (const key of migratedCells) {\n this.#editingCells.add(key);\n }\n\n // Migrate #rowEditSnapshots key\n const snapshot = this.#rowEditSnapshots.get(oldIndex);\n if (snapshot !== undefined) {\n this.#rowEditSnapshots.delete(oldIndex);\n this.#rowEditSnapshots.set(newIndex, snapshot);\n }\n\n // Migrate #editorValueCallbacks keys (\"rowIndex:field\")\n const updates: [string, (newValue: unknown) => void][] = [];\n for (const [key, cb] of this.#editorValueCallbacks) {\n if (key.startsWith(prefix)) {\n updates.push([`${newIndex}:${key.substring(prefix.length)}`, cb]);\n this.#editorValueCallbacks.delete(key);\n }\n }\n for (const [key, cb] of updates) {\n this.#editorValueCallbacks.set(key, cb);\n }\n\n // Sync the grid's rendering state so rows.ts checks the correct index\n this.#syncGridEditState();\n }\n\n /**\n * Begin editing a single cell.\n */\n #beginCellEdit(rowIndex: number, colIndex: number, cellEl: HTMLElement): void {\n const internalGrid = this.#internalGrid;\n const rowData = internalGrid._rows[rowIndex];\n const column = internalGrid._visibleColumns[colIndex];\n\n if (!rowData || !column || !this.#isCellEditable(column as ColumnConfig<T>, rowData as T)) return;\n if (cellEl.classList.contains('editing')) return;\n\n // Start row edit if not already\n if (this.#activeEditRow !== rowIndex) {\n this.#startRowEdit(rowIndex, rowData);\n }\n\n this.#activeEditCol = colIndex;\n this.#injectEditor(rowData, rowIndex, column, colIndex, cellEl, false);\n }\n\n /**\n * Revert the focused cell's value from the snapshot taken when the editor\n * first received focus, then emit a `cell-cancel` event so framework\n * adapters (e.g., GridFormArray) can revert their FormControls.\n *\n * Called by the grid-mode Escape handlers (both capture and bubble phase).\n */\n #revertGridModeCellEdit(): void {\n const snapshot = this.#gridModeCellSnapshot;\n if (!snapshot) return;\n\n const internalGrid = this.#internalGrid;\n const rowData = internalGrid._rows?.[snapshot.rowIndex];\n if (rowData) {\n (rowData as Record<string, unknown>)[snapshot.field] = snapshot.value;\n }\n\n // Push the reverted value to the editor's input element so that the\n // subsequent blur-commit (triggered by activeEl.blur()) reads the\n // reverted value instead of the user's typed text. This makes the\n // blur-commit a no-op (oldValue === newValue) and prevents it from\n // overwriting the revert.\n const callbackKey = `${snapshot.rowIndex}:${snapshot.field}`;\n const cb = this.#editorValueCallbacks.get(callbackKey);\n if (cb) cb(snapshot.value);\n\n // Notify framework adapters to revert FormControls\n this.emit<CellCancelDetail>('cell-cancel', {\n rowIndex: snapshot.rowIndex,\n colIndex: snapshot.colIndex,\n field: snapshot.field,\n previousValue: snapshot.value,\n });\n\n // After reverting, clean up changedRowIds so the built-in `.changed` class\n // toggle in rows.ts doesn't fight with consumer's rowClass callback.\n // changedRowIds.add() is unconditional during commit, so cleanup must also\n // be unconditional — otherwise the Set retains stale IDs when dirtyTracking\n // is disabled and the built-in toggle re-adds `.changed` on every render.\n if (rowData) {\n let rowId: string | undefined;\n try {\n rowId = this.grid.getRowId(rowData);\n } catch {\n // Row has no ID\n }\n if (rowId) {\n if (this.config.dirtyTracking) {\n // With dirty tracking, only remove if the row has no other dirty cells\n if (!this.#dirty.isRowDirty(rowId, rowData as T)) {\n this.#dirty.changedRowIds.delete(rowId);\n this.emit<DirtyChangeDetail<T>>('dirty-change', {\n rowId,\n row: rowData as T,\n original: this.#dirty.getOriginalRow(rowId),\n type: 'reverted',\n });\n }\n } else {\n // Without dirty tracking there's no baseline to compare, so always\n // remove — the consumer manages their own change state via rowClass.\n this.#dirty.changedRowIds.delete(rowId);\n }\n }\n }\n\n // Always re-render after revert so rowClass callbacks re-evaluate\n // (e.g., consumer may toggle a 'changed' class based on local state\n // updated in response to the cell-cancel event above).\n this.requestRender();\n\n this.#gridModeCellSnapshot = null;\n }\n\n /**\n * Focus the editor input in the currently focused cell (grid mode only).\n * Used when pressing Enter to enter edit mode from navigation mode.\n */\n #focusCurrentCellEditor(): void {\n const internalGrid = this.#internalGrid;\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n\n if (focusRow < 0 || focusCol < 0) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(focusRow);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${focusCol}\"]`) as HTMLElement | null;\n\n if (cellEl?.classList.contains('editing')) {\n const editor = cellEl.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n if (editor) {\n this.#gridModeEditLocked = false; // Unlock edit mode - user pressed Enter\n editor.focus();\n this.#gridModeInputFocused = true;\n // Select all text in text inputs for quick replacement\n if (editor instanceof HTMLInputElement && (editor.type === 'text' || editor.type === 'number')) {\n editor.select();\n }\n }\n }\n }\n\n /**\n * Handle Tab/Shift+Tab navigation while editing.\n * Moves to next/previous editable cell, staying in edit mode.\n * Wraps to next/previous row when reaching row boundaries.\n */\n #handleTabNavigation(forward: boolean): void {\n const internalGrid = this.#internalGrid;\n const rows = internalGrid._rows;\n // In grid mode, use focusRow since there's no active edit row\n const currentRow = this.#isGridMode ? internalGrid._focusRow : this.#activeEditRow;\n const currentRowData = rows[currentRow] as T;\n\n // Get editable column indices for the CURRENT row\n const editableCols = internalGrid._visibleColumns\n .map((c, i) => (currentRowData && this.#isCellEditable(c as ColumnConfig<T>, currentRowData) ? i : -1))\n .filter((i) => i >= 0);\n if (editableCols.length === 0) return;\n\n const currentIdx = editableCols.indexOf(internalGrid._focusCol);\n const nextIdx = currentIdx + (forward ? 1 : -1);\n\n // Can move within same row?\n if (nextIdx >= 0 && nextIdx < editableCols.length) {\n internalGrid._focusCol = editableCols[nextIdx];\n const rowEl = internalGrid.findRenderedRowElement?.(currentRow);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${editableCols[nextIdx]}\"]`) as HTMLElement | null;\n if (cellEl?.classList.contains('editing')) {\n const editor = cellEl.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n editor?.focus({ preventScroll: true });\n }\n ensureCellVisible(internalGrid, { forceHorizontalScroll: true });\n return;\n }\n\n // Can move to adjacent row?\n const nextRow = currentRow + (forward ? 1 : -1);\n if (nextRow >= 0 && nextRow < rows.length) {\n const nextRowData = rows[nextRow] as T;\n // Compute editable columns for the next row\n const nextEditableCols = internalGrid._visibleColumns\n .map((c, i) => (nextRowData && this.#isCellEditable(c as ColumnConfig<T>, nextRowData) ? i : -1))\n .filter((i) => i >= 0);\n if (nextEditableCols.length === 0) return; // Next row has no editable cells\n\n // In grid mode, just move focus (all rows are always editable)\n if (this.#isGridMode) {\n internalGrid._focusRow = nextRow;\n internalGrid._focusCol = forward ? nextEditableCols[0] : nextEditableCols[nextEditableCols.length - 1];\n ensureCellVisible(internalGrid, { forceHorizontalScroll: true });\n // Focus the editor in the new cell after render\n this.requestAfterRender();\n setTimeout(() => {\n const rowEl = internalGrid.findRenderedRowElement?.(nextRow);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${internalGrid._focusCol}\"]`) as HTMLElement | null;\n if (cellEl?.classList.contains('editing')) {\n const editor = cellEl.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n editor?.focus({ preventScroll: true });\n }\n }, 0);\n } else {\n // In row mode, commit current row and enter next row\n this.#exitRowEdit(currentRow, false);\n internalGrid._focusRow = nextRow;\n internalGrid._focusCol = forward ? nextEditableCols[0] : nextEditableCols[nextEditableCols.length - 1];\n this.beginBulkEdit(nextRow);\n ensureCellVisible(internalGrid, { forceHorizontalScroll: true });\n }\n }\n // else: at boundary - stay put\n }\n\n /**\n * Sync the internal grid state with the plugin's editing state.\n */\n #syncGridEditState(): void {\n const internalGrid = this.#internalGrid;\n internalGrid._activeEditRows = this.#activeEditRow;\n internalGrid._rowEditSnapshots = this.#rowEditSnapshots;\n }\n\n /**\n * Snapshot original row data and mark as editing.\n */\n #startRowEdit(rowIndex: number, rowData: T): void {\n if (this.#activeEditRow !== rowIndex) {\n // Commit the previous row before starting a new one\n if (this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, false);\n }\n this.#rowEditSnapshots.set(rowIndex, { ...rowData });\n this.#activeEditRow = rowIndex;\n this.#activeEditRowRef = rowData;\n\n // Store stable row ID for resilience against _rows replacement during editing\n const internalGrid = this.#internalGrid;\n try {\n this.#activeEditRowId = internalGrid.getRowId?.(rowData) ?? undefined;\n } catch {\n this.#activeEditRowId = undefined;\n }\n\n this.#syncGridEditState();\n\n // Emit edit-open event (row mode only)\n if (!this.#isGridMode) {\n this.emit<EditOpenDetail<T>>('edit-open', {\n rowIndex,\n rowId: this.#activeEditRowId ?? '',\n row: rowData,\n });\n }\n }\n }\n\n /**\n * Exit editing for a row.\n */\n #exitRowEdit(rowIndex: number, revert: boolean): void {\n if (this.#activeEditRow !== rowIndex) return;\n\n const internalGrid = this.#internalGrid;\n const snapshot = this.#rowEditSnapshots.get(rowIndex);\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n\n // Resolve the row being edited using the stored row ID.\n // The _rows array may have been replaced (e.g. Angular pushing new rows\n // via directive effect) since editing started, so _rows[rowIndex] could\n // point to a completely different row. The ID map is always up-to-date.\n // Without an ID we fall back to the stored row reference from edit-open\n // (#activeEditRowRef) — safer than _rows[rowIndex] which may be stale.\n let rowId = this.#activeEditRowId;\n const entry = rowId ? internalGrid._getRowEntry(rowId) : undefined;\n const current = entry?.row ?? this.#activeEditRowRef ?? internalGrid._rows[rowIndex];\n\n if (!rowId && current) {\n try {\n rowId = internalGrid.getRowId?.(current);\n } catch {\n // Row has no ID - skip ID-based tracking\n }\n }\n\n // Collect and commit values from active editors before re-rendering\n if (!revert && rowEl && current) {\n const editingCells = rowEl.querySelectorAll('.cell.editing');\n editingCells.forEach((cell) => {\n const colIndex = Number((cell as HTMLElement).getAttribute('data-col'));\n if (isNaN(colIndex)) return;\n const col = internalGrid._visibleColumns[colIndex];\n if (!col) return;\n\n // Skip cells with externally-managed editors (framework adapters like Angular/React/Vue).\n // These editors handle their own commits via the commit() callback - we should NOT\n // try to read values from their DOM inputs (which may contain formatted display values).\n if ((cell as HTMLElement).hasAttribute('data-editor-managed')) {\n return;\n }\n\n const input = cell.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (input) {\n const field = col.field as keyof T;\n const originalValue = current[field];\n const val = getInputValue(input, col, originalValue);\n if (originalValue !== val) {\n this.#commitCellValue(rowIndex, col, val, current);\n }\n }\n });\n }\n\n // Flush managed editors (framework adapters) before clearing state.\n // At this point the commit() callback is still active, so editors can\n // synchronously commit their pending values in response to this event.\n if (!revert && !this.#isGridMode && current) {\n this.emit<BeforeEditCloseDetail<T>>('before-edit-close', {\n rowIndex,\n rowId: rowId ?? '',\n row: current,\n });\n }\n\n // Revert if requested\n if (revert && snapshot && current) {\n Object.keys(snapshot as object).forEach((k) => {\n (current as Record<string, unknown>)[k] = (snapshot as Record<string, unknown>)[k];\n });\n if (rowId) {\n this.#dirty.changedRowIds.delete(rowId);\n this.#dirty.committedDirtyRowIds.delete(rowId);\n this.clearRowInvalid(rowId);\n }\n } else if (!revert && current) {\n // Compare snapshot vs current to detect if changes were made during THIS edit session\n const changedThisSession = hasRowChanged(snapshot, current);\n\n // Check if this row has any cumulative changes (via ID tracking)\n // Fall back to session-based detection when no row ID is available\n const changed = rowId ? this.#dirty.changedRowIds.has(rowId) : changedThisSession;\n\n // Emit cancelable row-commit event\n const cancelled = this.emitCancelable<RowCommitDetail<T>>('row-commit', {\n rowIndex,\n rowId: rowId ?? '',\n row: current,\n oldValue: snapshot,\n newValue: current,\n changed,\n changedRows: this.changedRows,\n changedRowIds: this.changedRowIds,\n });\n\n // If consumer called preventDefault(), revert the row\n if (cancelled && snapshot) {\n Object.keys(snapshot as object).forEach((k) => {\n (current as Record<string, unknown>)[k] = (snapshot as Record<string, unknown>)[k];\n });\n if (rowId) {\n this.#dirty.changedRowIds.delete(rowId);\n this.#dirty.committedDirtyRowIds.delete(rowId);\n this.clearRowInvalid(rowId);\n }\n } else if (!cancelled) {\n // Mark row as committed-dirty if it has actual changes vs baseline\n if (rowId && this.config.dirtyTracking) {\n if (this.#dirty.isRowDirty(rowId, current)) {\n this.#dirty.committedDirtyRowIds.add(rowId);\n } else {\n this.#dirty.committedDirtyRowIds.delete(rowId);\n }\n }\n\n if (changedThisSession && this.isAnimationEnabled) {\n // Animate the row only if changes were made during this edit session\n // (deferred to afterRender so the row element exists after re-render)\n this.#pendingRowAnimation = rowIndex;\n }\n }\n }\n\n // Clear editing state\n this.#rowEditSnapshots.delete(rowIndex);\n this.#activeEditRow = -1;\n this.#activeEditRowId = undefined;\n this.#activeEditRowRef = undefined;\n this.#activeEditCol = -1;\n this.#singleCellEdit = false;\n this.#syncGridEditState();\n\n // Remove all editing cells for this row.\n // Note: these keys use the rowIndex captured at edit-open time. Even if _rows\n // was replaced and the row moved to a different index, the keys still match\n // what was inserted during this edit session (same captured rowIndex).\n for (const cellKey of this.#editingCells) {\n if (cellKey.startsWith(`${rowIndex}:`)) {\n this.#editingCells.delete(cellKey);\n }\n }\n // Remove value-change callbacks for this row (same captured-index rationale)\n for (const callbackKey of this.#editorValueCallbacks.keys()) {\n if (callbackKey.startsWith(`${rowIndex}:`)) {\n this.#editorValueCallbacks.delete(callbackKey);\n }\n }\n\n // Mark that focus should be restored after the upcoming render completes.\n // This must be set BEFORE refreshVirtualWindow because it calls afterRender()\n // synchronously, which reads this flag.\n this.#pendingFocusRestore = true;\n\n // Re-render the row to remove editors\n if (rowEl) {\n // Remove editing class and re-render cells\n rowEl.querySelectorAll('.cell.editing').forEach((cell) => {\n cell.classList.remove('editing');\n clearEditingState(cell.parentElement as RowElementInternal);\n });\n\n // Refresh the virtual window to restore cell content WITHOUT rebuilding\n // the row model. requestRender() would trigger processRows (ROWS phase)\n // which re-sorts — causing the edited row to jump to a new position and\n // disappear from view. refreshVirtualWindow re-renders visible cells from\n // the current _rows order, keeping the row in place until the user\n // explicitly sorts again or new data arrives.\n internalGrid.refreshVirtualWindow(true);\n } else {\n // Row not visible - restore focus immediately (no render will happen)\n this.#restoreCellFocus(internalGrid);\n this.#pendingFocusRestore = false;\n }\n\n // Emit edit-close event (row mode only, fires for both commit and revert)\n if (!this.#isGridMode && current) {\n this.emit<EditCloseDetail<T>>('edit-close', {\n rowIndex,\n rowId: rowId ?? '',\n row: current,\n reverted: revert,\n });\n }\n }\n\n /**\n * Commit a single cell value change.\n * Uses ID-based change tracking for stability when rows are reordered.\n */\n #commitCellValue(rowIndex: number, column: ColumnConfig<T>, newValue: unknown, rowData: T): void {\n const field = column.field;\n if (!isSafePropertyKey(field)) return;\n const oldValue = (rowData as Record<string, unknown>)[field];\n if (oldValue === newValue) return;\n\n const internalGrid = this.#internalGrid;\n\n // Get row ID for change tracking (may not exist if getRowId not configured)\n let rowId: string | undefined;\n try {\n rowId = this.grid.getRowId(rowData);\n } catch {\n // Row has no ID - will still work but won't be tracked in changedRowIds\n }\n\n const firstTime = rowId ? !this.#dirty.changedRowIds.has(rowId) : true;\n\n // Create updateRow helper for cascade updates (noop if row has no ID)\n const updateRow: (changes: Partial<T>) => void = rowId\n ? (changes) => this.grid.updateRow(rowId!, changes as Record<string, unknown>, 'cascade')\n : noopUpdateRow;\n\n // Track whether setInvalid was called during event handling\n let invalidWasSet = false;\n\n // Create setInvalid callback for validation (noop if row has no ID)\n const setInvalid = rowId\n ? (message?: string) => {\n invalidWasSet = true;\n this.setInvalid(rowId!, field, message ?? '');\n }\n : () => {}; // eslint-disable-line @typescript-eslint/no-empty-function\n\n // Emit cancelable event BEFORE applying the value\n const cancelled = this.emitCancelable<CellCommitDetail<T>>('cell-commit', {\n row: rowData,\n rowId: rowId ?? '',\n field,\n oldValue,\n value: newValue,\n rowIndex,\n changedRows: this.changedRows,\n changedRowIds: this.changedRowIds,\n firstTimeForRow: firstTime,\n updateRow,\n setInvalid,\n });\n\n // If consumer called preventDefault(), abort the commit\n if (cancelled) return;\n\n // Clear any previous invalid state for this cell ONLY if setInvalid wasn't called\n // (if setInvalid was called, the handler wants it to remain invalid)\n if (rowId && !invalidWasSet && this.isCellInvalid(rowId, field)) {\n this.clearInvalid(rowId, field);\n }\n\n // Apply the value and mark row as changed\n (rowData as Record<string, unknown>)[field] = newValue;\n if (rowId) {\n this.#dirty.changedRowIds.add(rowId);\n }\n this.#syncGridEditState();\n\n // Emit dirty-change event if dirty tracking is enabled\n if (this.config.dirtyTracking && rowId) {\n const dirty = this.#dirty.isRowDirty(rowId, rowData);\n this.emit<DirtyChangeDetail<T>>('dirty-change', {\n rowId,\n row: rowData,\n original: this.#dirty.getOriginalRow(rowId),\n type: dirty ? 'modified' : 'pristine',\n });\n }\n\n // Notify other plugins (e.g., UndoRedoPlugin) about the committed edit\n this.emitPluginEvent('cell-edit-committed', {\n rowIndex,\n field,\n oldValue,\n newValue,\n });\n\n // Mark the row visually as changed (animation happens when row edit closes)\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (rowEl) {\n rowEl.classList.add('changed');\n }\n }\n\n /**\n * Inject an editor into a cell.\n * Delegates to the extracted `editor-injection` module.\n */\n #injectEditor(\n rowData: T,\n rowIndex: number,\n column: ColumnConfig<T>,\n colIndex: number,\n cell: HTMLElement,\n skipFocus: boolean,\n parentRowEl?: HTMLElement,\n ): void {\n injectEditorImpl(this.#editorDeps, rowData, rowIndex, column, colIndex, cell, skipFocus, parentRowEl);\n }\n\n /**\n * Restore focus to cell after exiting edit mode.\n */\n #restoreCellFocus(internalGrid: InternalGrid<T>): void {\n queueMicrotask(() => {\n try {\n const rowIdx = internalGrid._focusRow;\n const colIdx = internalGrid._focusCol;\n const rowEl = internalGrid.findRenderedRowElement?.(rowIdx);\n if (rowEl) {\n Array.from(internalGrid._bodyEl.querySelectorAll('.cell-focus')).forEach((el) =>\n el.classList.remove('cell-focus'),\n );\n const cell = rowEl.querySelector(`.cell[data-row=\"${rowIdx}\"][data-col=\"${colIdx}\"]`) as HTMLElement | null;\n if (cell) {\n cell.classList.add('cell-focus');\n cell.setAttribute('aria-selected', 'true');\n if (!cell.hasAttribute('tabindex')) cell.setAttribute('tabindex', '-1');\n cell.focus({ preventScroll: true });\n }\n }\n } catch {\n /* empty */\n }\n });\n }\n\n // #endregion\n}\n"],"names":["createDateEditor","column","ctx","params","editorParams","input","document","createElement","type","value","Date","valueAsDate","split","min","max","placeholder","addEventListener","date","commit","nullable","fallback","default","getFullYear","String","getMonth","padStart","getDate","e","key","cancel","NULLABLE_BLANK_VALUE","defaultEditorFor","step","Number","createNumberEditor","checked","select","multi","multiple","includeEmpty","emptyOpt","textContent","emptyLabel","selected","appendChild","options","raw","resolveOptions","forEach","opt","o","label","Array","isArray","includes","values","from","selectedOptions","map","createSelectEditor","maxLength","pattern","inputVal","replace","createTextEditor","getInputValue","originalValue","HTMLInputElement","toISOString","slice","CellValidationManager","cells","Map","syncAttribute","constructor","this","setInvalid","rowId","field","message","rowInvalids","get","set","clearInvalid","delete","size","clearRowInvalid","fields","keys","clearAllInvalid","entries","clear","_","isCellInvalid","has","getInvalidMessage","hasInvalidCells","getInvalidFields","isRowDirty","baselines","currentRow","baseline","deepEqual","a","b","getTime","length","i","aObj","bObj","keysA","Object","keysB","markPristine","structuredClone","revertToBaseline","baselineObj","currentObj","DirtyTrackingManager","baselinesWereCaptured","newRowIds","Set","changedRowIds","committedDirtyRowIds","capture","rows","getRowId","sizeBefore","row","id","captureBaselines","drainCapturedFlag","hasAnyDirty","resolveRow","isCellDirty","getRowDirtyState","isNew","isCommittedDirty","hasBaseline","markNew","add","markDirty","markAllPristine","getOriginalRow","getDirtyRows","result","push","original","current","newId","getDirtyRowIds","ids","revertRow","reverted","revertAll","getChangedRows","getChangedRowIds","isRowChanged","FOCUSABLE_EDITOR_SELECTOR","isSafePropertyKey","noopUpdateRow","_changes","shouldPreventEditClose","config","event","onBeforeEditClose","injectEditor","deps","rowData","rowIndex","colIndex","cell","skipFocus","parentRowEl","editable","classList","contains","grid","isGridMode","editingCells","editorValueCallbacks","updateRow","changes","rowEl","parentElement","count","__editingCellCount","setAttribute","editFinalized","newValue","isEditSessionActive","entry","_getRowEntry","currentRowData","currentIndex","index","commitCellValue","editorHost","className","innerHTML","stopPropagation","preventDefault","querySelector","exitRowEdit","colInternal","tplHolder","__editorTemplate","editorSpec","col","editor","gridTypeDefaults","effectiveConfig","typeDefaults","adapter","__frameworkAdapter","getTypeDefault","appDefault","resolveEditor","callbackKey","callbacks","newVal","cb","onValueChange","clone","cloneNode","compiledEditor","__compiledEditor","querySelectorAll","node","childNodes","firstChild","nodeType","Node","TEXT_NODE","_m","g","v","evt","setTimeout","focus","preventScroll","renderTemplateEditor","el","queueMicrotask","focusable","produced","HTMLSelectElement","wireEditorInputs","HTMLTextAreaElement","hasChildNodes","context","mount","spec","warnDiagnostic","EDITOR_MOUNT_ERROR","dispatchEvent","CustomEvent","detail","EditingPlugin","BaseGridPlugin","static","ownedProperties","property","level","description","isUsed","events","queries","name","styles","defaultConfig","mode","editOn","isCellEditable","rowEditable","internalGrid","hasEditableConfig","activeEditRow","activeEditRowId","activeEditRowRef","activeEditCol","rowEditSnapshots","pendingFocusRestore","pendingRowAnimation","validation","gridModeInputFocused","gridModeEditLocked","singleCellEdit","gridModeCellSnapshot","dirty","editorDeps","attach","super","signal","disconnectSignal","invalid","syncInvalidCellAttribute","ri","val","revert","_activeEditRows","_rowEditSnapshots","defineProperty","changedRows","configurable","resetChangedRows","silent","beginBulkEdit","beginCellEdit","findRenderedRowElement","composedPath","target","gridElement","containsFocus","focusTrap","related","relatedTarget","focusCurrentCellEditor","source","dirtyTracking","handleUndoRedo","action","emit","on","markAsNew","_isGridEditMode","requestRender","matches","blur","focusRow","_focusRow","focusCol","_focusCol","snap","_visibleColumns","_rows","revertGridModeCellEdit","activeEl","activeElement","detach","remove","handleQuery","query","onCellClick","isDoubleClick","originalEvent","hasEditableColumn","_columns","some","onKeyDown","requestAfterRender","maxRow","Math","ensureCellVisible","forward","shiftKey","handleTabNavigation","ctrlKey","altKey","metaKey","rowBlocked","cellEl","activateEvent","cancelable","bubbles","trigger","legacyEvent","defaultPrevented","processColumns","columns","typeEditorParams","processRows","r","editRowId","editRowRef","newIndex","cancelActiveRowEdit","migrateEditRowIndex","afterRender","indexOf","restoreCellFocus","animateRow","capturedCount","cellKey","rowStr","colStr","parseInt","afterCellRender","cellElement","removeAttribute","rowElement","afterRowRender","toggle","getAttribute","dirtyCells","onScrollRender","getRow","isRowEditing","isCellEditing","isRowChangedById","isDirty","isPristine","pristine","markAsPristine","markAsDirty","dirtyRowIds","syncGridEditState","_rowPool","findIndex","c","startRowEdit","children","targetCell","commitActiveRowEdit","oldIndex","migratedCells","prefix","startsWith","substring","snapshot","updates","previousValue","editableCols","filter","nextIdx","forceHorizontalScroll","nextRow","nextRowData","nextEditableCols","isNaN","hasAttribute","k","changedThisSession","snapshotObj","allKeys","hasRowChanged","changed","cancelled","emitCancelable","oldValue","isAnimationEnabled","clearEditingState","refreshVirtualWindow","firstTime","invalidWasSet","firstTimeForRow","emitPluginEvent","injectEditorImpl","rowIdx","colIdx","_bodyEl"],"mappings":"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"}
1
+ {"version":3,"file":"editing.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/editing/editors.ts","../../../../../libs/grid/src/lib/plugins/editing/internal/cell-validation.ts","../../../../../libs/grid/src/lib/plugins/editing/internal/dirty-tracking.ts","../../../../../libs/grid/src/lib/plugins/editing/internal/dirty-tracking-manager.ts","../../../../../libs/grid/src/lib/plugins/editing/internal/helpers.ts","../../../../../libs/grid/src/lib/plugins/editing/internal/editor-injection.ts","../../../../../libs/grid/src/lib/plugins/editing/EditingPlugin.ts"],"sourcesContent":["/**\n * Default Editors Module\n *\n * Provides built-in editor factories for different column types.\n * Each editor type has its own factory function for consistency and readability.\n *\n * IMPORTANT: Editor factories should NOT call focus() on elements - they are called\n * before the element is appended to the DOM. The calling code (beginBulkEdit,\n * inlineEnterEdit) is responsible for focusing the correct editor after insertion.\n */\n\nimport type { ColumnConfig, ColumnEditorContext } from '../../core/types';\nimport type { DateEditorParams, NumberEditorParams, SelectEditorParams, TextEditorParams } from './types';\n\n// ============================================================================\n// Type Aliases\n// ============================================================================\n\n/** Option shape used by select editor (matches column.options) */\ntype ColumnOption = { label: string; value: unknown };\n\n/** Column with any row type (used for editor factories) */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyColumn = ColumnConfig<any>;\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/** Resolve column.options (handles both array and function forms) */\nfunction resolveOptions(column: AnyColumn): ColumnOption[] {\n const raw = column.options;\n if (!raw) return [];\n return typeof raw === 'function' ? raw() : raw;\n}\n\n/** Format a Date as YYYY-MM-DD string */\nfunction toISODate(date: Date): string {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, '0');\n const d = String(date.getDate()).padStart(2, '0');\n return `${y}-${m}-${d}`;\n}\n\n// ============================================================================\n// Editor Factories\n// ============================================================================\n\n/** Creates a number input editor */\nfunction createNumberEditor(column: AnyColumn): (ctx: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as NumberEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'number';\n input.value = ctx.value != null ? String(ctx.value) : '';\n\n if (params?.min !== undefined) input.min = String(params.min);\n if (params?.max !== undefined) input.max = String(params.max);\n if (params?.step !== undefined) input.step = String(params.step);\n if (params?.placeholder) input.placeholder = params.placeholder;\n\n const commit = () => {\n if (input.value === '') {\n if (column.nullable) {\n ctx.commit(null);\n } else {\n // Non-nullable: fall back to min value or 0\n ctx.commit(params?.min ?? 0);\n }\n } else {\n ctx.commit(Number(input.value));\n }\n };\n input.addEventListener('blur', commit);\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') commit();\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return input;\n };\n}\n\n/** Creates a checkbox editor for boolean values */\nfunction createBooleanEditor(): (ctx: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const input = document.createElement('input');\n input.type = 'checkbox';\n input.checked = !!ctx.value;\n input.addEventListener('change', () => ctx.commit(input.checked));\n return input;\n };\n}\n\n/** Creates a date input editor */\nfunction createDateEditor(column: AnyColumn): (ctx: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as DateEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'date';\n\n // Set initial value - handle both Date objects and string dates\n if (ctx.value instanceof Date) {\n input.valueAsDate = ctx.value;\n } else if (typeof ctx.value === 'string' && ctx.value) {\n // String date like \"2019-10-09\" - set directly as value\n input.value = ctx.value.split('T')[0]; // Handle ISO strings too\n }\n if (params?.min) input.min = params.min;\n if (params?.max) input.max = params.max;\n if (params?.placeholder) input.placeholder = params.placeholder;\n\n // Commit function preserves original type (string vs Date)\n const commit = () => {\n if (!input.value) {\n if (column.nullable) {\n ctx.commit(null);\n } else {\n // Non-nullable: use configured default or today\n const fallback = params?.default;\n if (typeof ctx.value === 'string' || typeof fallback === 'string') {\n // String mode: use default string or today as YYYY-MM-DD\n ctx.commit(typeof fallback === 'string' ? fallback : toISODate(fallback ?? new Date()));\n } else {\n // Date object mode\n ctx.commit(fallback instanceof Date ? fallback : new Date());\n }\n }\n return;\n }\n if (typeof ctx.value === 'string') {\n // Original was string, return string in YYYY-MM-DD format\n ctx.commit(input.value);\n } else {\n ctx.commit(input.valueAsDate);\n }\n };\n\n input.addEventListener('change', commit);\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return input;\n };\n}\n\n/** Sentinel value used internally to identify the nullable \"(Blank)\" option */\nconst NULLABLE_BLANK_VALUE = '__tbw_null__';\n\n/** Creates a select dropdown editor */\nfunction createSelectEditor(column: AnyColumn): (ctx: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as SelectEditorParams | undefined;\n const select = document.createElement('select');\n if (column.multi) select.multiple = true;\n\n // Add blank option: nullable columns always get one, otherwise respect includeEmpty\n const showBlank = column.nullable || params?.includeEmpty;\n if (showBlank) {\n const emptyOpt = document.createElement('option');\n emptyOpt.value = column.nullable ? NULLABLE_BLANK_VALUE : '';\n emptyOpt.textContent = column.nullable ? (params?.emptyLabel ?? '(Blank)') : (params?.emptyLabel ?? '');\n if (ctx.value == null) emptyOpt.selected = true;\n select.appendChild(emptyOpt);\n }\n\n // Populate options from column.options\n const options = resolveOptions(column);\n options.forEach((opt) => {\n const o = document.createElement('option');\n o.value = String(opt.value);\n o.textContent = opt.label;\n if (column.multi && Array.isArray(ctx.value) && ctx.value.includes(opt.value)) {\n o.selected = true;\n } else if (!column.multi && ctx.value === opt.value) {\n o.selected = true;\n }\n select.appendChild(o);\n });\n\n const commit = () => {\n if (column.multi) {\n const values = Array.from(select.selectedOptions).map((o) => o.value);\n ctx.commit(values);\n } else if (column.nullable && select.value === NULLABLE_BLANK_VALUE) {\n ctx.commit(null);\n } else {\n ctx.commit(select.value);\n }\n };\n\n select.addEventListener('change', commit);\n select.addEventListener('blur', commit);\n select.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return select;\n };\n}\n\n/** Creates a text input editor (default) */\nfunction createTextEditor(column: AnyColumn): (ctx: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as TextEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'text';\n input.value = ctx.value != null ? String(ctx.value) : '';\n\n if (params?.maxLength !== undefined) input.maxLength = params.maxLength;\n if (params?.pattern) input.pattern = params.pattern;\n if (params?.placeholder) input.placeholder = params.placeholder;\n\n // Commit function preserves original type when possible\n const commit = () => {\n const inputVal = input.value;\n\n // Empty input handling\n if (inputVal === '') {\n if (column.nullable) {\n ctx.commit(null);\n } else {\n // Non-nullable: commit empty string so the field is never null\n ctx.commit('');\n }\n return;\n }\n\n // Preserve values with characters that <input> can't represent (newlines, etc.).\n // If stripping those characters produces the same string, the user didn't change anything.\n if (typeof ctx.value === 'string' && inputVal === ctx.value.replace(/[\\n\\r]/g, '')) {\n return;\n }\n // Preserve numeric type for custom column types (e.g., currency)\n if (typeof ctx.value === 'number') {\n ctx.commit(Number(inputVal));\n } else {\n ctx.commit(inputVal);\n }\n };\n\n input.addEventListener('blur', commit);\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') commit();\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return input;\n };\n}\n\n// ============================================================================\n// Main Entry Point\n// ============================================================================\n\n/**\n * Returns a default editor factory function for the given column type.\n * Each editor handles commit on blur/Enter, and cancel on Escape.\n *\n * Note: Focus is NOT called here - the calling code handles focusing after DOM insertion.\n */\nexport function defaultEditorFor(column: AnyColumn): (ctx: ColumnEditorContext) => HTMLElement | string {\n switch (column.type) {\n case 'number':\n return createNumberEditor(column);\n case 'boolean':\n return createBooleanEditor();\n case 'date':\n return createDateEditor(column);\n case 'select':\n return createSelectEditor(column);\n default:\n return createTextEditor(column);\n }\n}\n\n// ============================================================================\n// Utility Export (used by EditingPlugin)\n// ============================================================================\n\n/**\n * Get the typed value from an input element based on its type, column config, and original value.\n * Preserves the type of the original value (e.g., numeric currency values stay as numbers,\n * string dates stay as strings, null/undefined for empty fields).\n */\nexport function getInputValue(\n input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n column?: AnyColumn,\n originalValue?: unknown,\n): unknown {\n if (input instanceof HTMLInputElement) {\n if (input.type === 'checkbox') return input.checked;\n if (input.type === 'number') {\n if (input.value === '') {\n if (column?.nullable) return null;\n const params = column?.editorParams as NumberEditorParams | undefined;\n return params?.min ?? 0;\n }\n return Number(input.value);\n }\n if (input.type === 'date') {\n if (!input.value) {\n if (column?.nullable) return null;\n // Non-nullable: preserve original or fall back to today\n if (typeof originalValue === 'string') return originalValue || new Date().toISOString().slice(0, 10);\n return (originalValue as Date) ?? new Date();\n }\n // Preserve original type: if original was a string, return string (YYYY-MM-DD format)\n if (typeof originalValue === 'string') {\n return input.value; // input.value is already in YYYY-MM-DD format\n }\n return input.valueAsDate;\n }\n // For text inputs, check if original value was a number to preserve type\n if (typeof originalValue === 'number') {\n if (input.value === '') {\n if (column?.nullable) return null;\n const params = column?.editorParams as NumberEditorParams | undefined;\n return params?.min ?? 0;\n }\n return Number(input.value);\n }\n // Nullable text: empty → null; non-nullable: empty → ''\n if (input.value === '' && (originalValue === null || originalValue === undefined)) {\n return column?.nullable ? null : '';\n }\n // Preserve values with characters <input> can't represent (newlines, etc.)\n if (typeof originalValue === 'string' && input.value === originalValue.replace(/[\\n\\r]/g, '')) {\n return originalValue;\n }\n return input.value;\n }\n // For textarea/select, check column type OR original value type\n if (column?.type === 'number' && input.value !== '') {\n return Number(input.value);\n }\n // Preserve numeric type for custom column types (e.g., currency)\n if (typeof originalValue === 'number' && input.value !== '') {\n return Number(input.value);\n }\n // Nullable: empty → null; non-nullable: empty → ''\n if ((originalValue === null || originalValue === undefined) && input.value === '') {\n return column?.nullable ? null : '';\n }\n return input.value;\n}\n","/**\n * Cell Validation Manager\n *\n * Manages invalid-cell state for the Editing Plugin.\n * Extracted from EditingPlugin to reduce the main plugin file size.\n *\n * The manager owns the `Map<rowId, Map<field, message>>` state and exposes\n * read/write methods. DOM synchronization is handled via an injected callback.\n *\n * @internal\n */\n\n/** Callback to sync the data-invalid attribute on a cell element in the DOM. */\nexport type SyncInvalidAttributeFn = (rowId: string, field: string, invalid: boolean) => void;\n\n/**\n * Manages validation state for grid cells.\n *\n * Tracks which cells are marked invalid and their validation messages.\n * The DOM side-effect (adding/removing `data-invalid` attribute) is delegated\n * to the `syncAttribute` callback provided at construction time.\n */\nexport class CellValidationManager {\n /** Invalid cell tracking: Map<rowId, Map<field, message>> */\n readonly #cells = new Map<string, Map<string, string>>();\n\n /** Callback to sync DOM attributes when validation state changes */\n readonly #syncAttribute: SyncInvalidAttributeFn;\n\n constructor(syncAttribute: SyncInvalidAttributeFn) {\n this.#syncAttribute = syncAttribute;\n }\n\n // #region Write Operations\n\n /**\n * Mark a cell as invalid with an optional validation message.\n *\n * @param rowId - The row ID (from getRowId)\n * @param field - The field name\n * @param message - Optional validation message (for tooltips or display)\n */\n setInvalid(rowId: string, field: string, message = ''): void {\n let rowInvalids = this.#cells.get(rowId);\n if (!rowInvalids) {\n rowInvalids = new Map();\n this.#cells.set(rowId, rowInvalids);\n }\n rowInvalids.set(field, message);\n this.#syncAttribute(rowId, field, true);\n }\n\n /**\n * Clear the invalid state for a specific cell.\n *\n * @param rowId - The row ID (from getRowId)\n * @param field - The field name\n */\n clearInvalid(rowId: string, field: string): void {\n const rowInvalids = this.#cells.get(rowId);\n if (rowInvalids) {\n rowInvalids.delete(field);\n if (rowInvalids.size === 0) {\n this.#cells.delete(rowId);\n }\n }\n this.#syncAttribute(rowId, field, false);\n }\n\n /**\n * Clear all invalid cells for a specific row.\n *\n * @param rowId - The row ID (from getRowId)\n */\n clearRowInvalid(rowId: string): void {\n const rowInvalids = this.#cells.get(rowId);\n if (rowInvalids) {\n const fields = Array.from(rowInvalids.keys());\n this.#cells.delete(rowId);\n fields.forEach((field) => this.#syncAttribute(rowId, field, false));\n }\n }\n\n /**\n * Clear all invalid cell states across all rows.\n */\n clearAllInvalid(): void {\n const entries = Array.from(this.#cells.entries());\n this.#cells.clear();\n entries.forEach(([rowId, fields]) => {\n fields.forEach((_, field) => this.#syncAttribute(rowId, field, false));\n });\n }\n\n // #endregion\n\n // #region Read Operations\n\n /**\n * Check if a specific cell is marked as invalid.\n *\n * @param rowId - The row ID (from getRowId)\n * @param field - The field name\n * @returns True if the cell is marked as invalid\n */\n isCellInvalid(rowId: string, field: string): boolean {\n return this.#cells.get(rowId)?.has(field) ?? false;\n }\n\n /**\n * Get the validation message for an invalid cell.\n *\n * @param rowId - The row ID (from getRowId)\n * @param field - The field name\n * @returns The validation message, or undefined if cell is valid\n */\n getInvalidMessage(rowId: string, field: string): string | undefined {\n return this.#cells.get(rowId)?.get(field);\n }\n\n /**\n * Check if a row has any invalid cells.\n *\n * @param rowId - The row ID (from getRowId)\n * @returns True if the row has at least one invalid cell\n */\n hasInvalidCells(rowId: string): boolean {\n const rowInvalids = this.#cells.get(rowId);\n return rowInvalids ? rowInvalids.size > 0 : false;\n }\n\n /**\n * Get all invalid fields for a row.\n *\n * @param rowId - The row ID (from getRowId)\n * @returns Map of field names to validation messages\n */\n getInvalidFields(rowId: string): Map<string, string> {\n return new Map(this.#cells.get(rowId) ?? []);\n }\n\n // #endregion\n}\n","/**\n * Pure functions for dirty tracking baseline management.\n *\n * Extracted from EditingPlugin to keep the plugin under the 2,000 line target.\n * All functions are stateless — the caller owns the baseline `Map`.\n *\n * @internal\n */\n\n// #region Types\n\n/**\n * Detail for `dirty-change` custom events.\n */\nexport interface DirtyChangeDetail<T = unknown> {\n /** Row ID (from getRowId) */\n rowId: string;\n /** Current row data */\n row: T;\n /** Baseline (original) row data, or undefined for newly inserted rows */\n original: T | undefined;\n /**\n * Transition type:\n * - `'modified'` — row differs from baseline\n * - `'new'` — row was inserted via `insertRow()` and has no baseline\n * - `'reverted'` — row was reverted to baseline via `revertRow()`\n * - `'pristine'` — row was explicitly marked pristine via `markAsPristine()`\n */\n type: 'modified' | 'new' | 'reverted' | 'pristine';\n}\n\n/**\n * Result of getDirtyRows(): each entry has the row ID, original (baseline),\n * and current data.\n */\nexport interface DirtyRowEntry<T = unknown> {\n id: string;\n /** The original (baseline) row data, or `undefined` for newly inserted rows. */\n original: T | undefined;\n current: T;\n}\n\n/**\n * Detail for `baselines-captured` custom events.\n *\n * Emitted after the render pipeline completes when new baseline snapshots\n * were captured during `processRows`.\n */\nexport interface BaselinesCapturedDetail {\n /** Total number of tracked baselines (not just newly captured). */\n count: number;\n}\n\n// #endregion\n\n// #region Baseline Capture\n\n/**\n * Capture baselines for rows not already tracked (first-write-wins).\n *\n * Uses `structuredClone` for deep copy so nested objects cannot be mutated\n * through shared references.\n *\n * @param baselines - Map of rowId → deep-cloned baseline row data\n * @param rows - Rows to snapshot\n * @param getRowId - Function to resolve row ID (may throw; exceptions are swallowed)\n */\nexport function captureBaselines<T>(\n baselines: Map<string, T>,\n rows: readonly T[],\n getRowId: (row: T) => string | undefined,\n): void {\n for (const row of rows) {\n try {\n const id = getRowId(row);\n if (id != null && !baselines.has(id)) {\n baselines.set(id, structuredClone(row));\n }\n } catch {\n // Row has no resolvable ID — skip\n }\n }\n}\n\n// #endregion\n\n// #region Dirty Detection\n\n/**\n * Check whether a row's current data differs from its baseline.\n *\n * Uses deep property comparison so that `structuredClone`'d baselines\n * with nested objects/arrays/Dates compare correctly.\n */\nexport function isRowDirty<T>(baselines: Map<string, T>, rowId: string, currentRow: T): boolean {\n const baseline = baselines.get(rowId);\n if (!baseline) return false; // No baseline → row is \"new\" or untracked\n return !deepEqual(baseline, currentRow);\n}\n\n/**\n * Check whether a single cell (field) differs from its baseline value.\n *\n * Returns `false` when no baseline exists for the row.\n */\nexport function isCellDirty<T>(baselines: Map<string, T>, rowId: string, currentRow: T, field: string): boolean {\n const baseline = baselines.get(rowId);\n if (!baseline) return false;\n const baselineValue = (baseline as Record<string, unknown>)[field];\n const currentValue = (currentRow as Record<string, unknown>)[field];\n return !deepEqual(baselineValue, currentValue);\n}\n\n/**\n * Deep comparison of two values. Handles primitives, plain objects, arrays,\n * and Dates — the value types produced by `structuredClone` on row data.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a == null || b == null) return false;\n if (typeof a !== typeof b) return false;\n\n // Date comparison\n if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();\n\n // Array comparison\n if (Array.isArray(a)) {\n if (!Array.isArray(b) || a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false;\n }\n return true;\n }\n\n // Object comparison\n if (typeof a === 'object') {\n const aObj = a as Record<string, unknown>;\n const bObj = b as Record<string, unknown>;\n const keysA = Object.keys(aObj);\n const keysB = Object.keys(bObj);\n if (keysA.length !== keysB.length) return false;\n for (const key of keysA) {\n if (!deepEqual(aObj[key], bObj[key])) return false;\n }\n return true;\n }\n\n return false;\n}\n\n// #endregion\n\n// #region State Transitions\n\n/**\n * Mark a row as pristine: re-snapshot baseline from current data.\n *\n * After calling this, `isRowDirty` returns `false` for the row (until it\n * is edited again).\n */\nexport function markPristine<T>(baselines: Map<string, T>, rowId: string, currentRow: T): void {\n baselines.set(rowId, structuredClone(currentRow));\n}\n\n/**\n * Get the original (baseline) row data as a deep clone.\n *\n * Returns `undefined` if no baseline exists (e.g. newly inserted row).\n */\nexport function getOriginalRow<T>(baselines: Map<string, T>, rowId: string): T | undefined {\n const baseline = baselines.get(rowId);\n return baseline ? structuredClone(baseline) : undefined;\n}\n\n/**\n * Revert a row to its baseline values by mutating the current row in-place.\n *\n * Returns `true` if a baseline existed and the row was reverted, `false` otherwise.\n */\nexport function revertToBaseline<T>(baselines: Map<string, T>, rowId: string, currentRow: T): boolean {\n const baseline = baselines.get(rowId);\n if (!baseline) return false;\n const baselineObj = baseline as Record<string, unknown>;\n const currentObj = currentRow as Record<string, unknown>;\n for (const key of Object.keys(baselineObj)) {\n currentObj[key] = baselineObj[key];\n }\n return true;\n}\n\n// #endregion\n","/**\n * Stateful manager for dirty tracking in the EditingPlugin.\n *\n * Owns all dirty-tracking-related state (baselines, changed/new/committed sets)\n * and exposes high-level methods that coordinate pure functions from\n * `dirty-tracking.ts`.\n *\n * The EditingPlugin delegates all dirty tracking operations to this manager,\n * keeping the plugin class focused on orchestration (row resolution, event\n * emission, render scheduling).\n *\n * @internal\n */\n\nimport {\n captureBaselines,\n getOriginalRow,\n isCellDirty,\n isRowDirty,\n markPristine,\n revertToBaseline,\n type DirtyRowEntry,\n} from './dirty-tracking';\n\n// #region Types\n\n/**\n * Callback that resolves a row object by its ID.\n * Returns `undefined` when the row is not found.\n */\nexport type RowResolver<T> = (rowId: string) => T | undefined;\n\n// #endregion\n\n// #region DirtyTrackingManager\n\nexport class DirtyTrackingManager<T> {\n // --- Owned state ---\n\n /** Baseline snapshots: rowId → deep-cloned original row data (first-write-wins) */\n readonly baselines = new Map<string, T>();\n\n /** Whether new baselines were captured during the current processRows cycle */\n private baselinesWereCaptured = false;\n\n /** Row IDs inserted via `insertRow()` (no baseline available) */\n readonly newRowIds = new Set<string>();\n\n /** Row IDs that have been modified (edit committed) */\n readonly changedRowIds = new Set<string>();\n\n /** Row IDs whose edit session was committed (gates `tbw-row-dirty` CSS class) */\n readonly committedDirtyRowIds = new Set<string>();\n\n // --- Lifecycle ---\n\n /** Reset all dirty tracking state (called from detach / resetChangedRows). */\n clear(): void {\n this.changedRowIds.clear();\n this.committedDirtyRowIds.clear();\n this.baselines.clear();\n this.newRowIds.clear();\n this.baselinesWereCaptured = false;\n }\n\n // --- Baseline capture (called from processRows) ---\n\n /**\n * Capture baselines for rows not already tracked (first-write-wins).\n * Sets the `baselinesWereCaptured` flag when new rows are snapshotted.\n */\n capture(rows: readonly T[], getRowId: (r: T) => string | undefined): void {\n const sizeBefore = this.baselines.size;\n captureBaselines(this.baselines, rows, getRowId);\n if (this.baselines.size > sizeBefore) {\n this.baselinesWereCaptured = true;\n }\n }\n\n /**\n * Drain the baselines-captured flag (called from afterRender).\n * Returns `null` when no new captures occurred, or the baseline count.\n */\n drainCapturedFlag(): number | null {\n if (!this.baselinesWereCaptured) return null;\n this.baselinesWereCaptured = false;\n return this.baselines.size;\n }\n\n // --- Row dirty queries ---\n\n /** Check if a specific row differs from its baseline. */\n isRowDirty(rowId: string, row: T): boolean {\n return isRowDirty(this.baselines, rowId, row);\n }\n\n /** Check if ANY row is dirty (requires row resolver for baseline iteration). */\n hasAnyDirty(resolveRow: RowResolver<T>): boolean {\n if (this.newRowIds.size > 0) return true;\n for (const [rowId] of this.baselines) {\n const row = resolveRow(rowId);\n if (row && isRowDirty(this.baselines, rowId, row)) return true;\n }\n return false;\n }\n\n // --- Cell dirty queries ---\n\n /** Check if a single cell differs from its baseline value. */\n isCellDirty(rowId: string, row: T, field: string): boolean {\n return isCellDirty(this.baselines, rowId, row, field);\n }\n\n // --- Render-time helpers ---\n\n /**\n * Get the dirty-state tuple needed by afterRowRender to toggle CSS classes.\n *\n * `isCommittedDirty` requires BOTH committed status AND actual data divergence\n * from baseline (handles undo: after CTRL+Z, row is no longer visually dirty).\n */\n getRowDirtyState(rowId: string, row: T): { isNew: boolean; isCommittedDirty: boolean; hasBaseline: boolean } {\n const isNew = this.newRowIds.has(rowId);\n const isCommittedDirty = !isNew && this.committedDirtyRowIds.has(rowId) && isRowDirty(this.baselines, rowId, row);\n return { isNew, isCommittedDirty, hasBaseline: this.baselines.has(rowId) };\n }\n\n // --- Mark operations ---\n\n /** Re-snapshot baseline from current data and remove from all sets. */\n markPristine(rowId: string, row: T): void {\n markPristine(this.baselines, rowId, row);\n this.newRowIds.delete(rowId);\n this.changedRowIds.delete(rowId);\n this.committedDirtyRowIds.delete(rowId);\n }\n\n /** Mark a row as newly inserted (no baseline). */\n markNew(rowId: string): void {\n this.newRowIds.add(rowId);\n this.committedDirtyRowIds.add(rowId);\n }\n\n /** Mark a row as dirty (external mutation). */\n markDirty(rowId: string): void {\n this.changedRowIds.add(rowId);\n this.committedDirtyRowIds.add(rowId);\n }\n\n /** Re-snapshot all baselines and clear all tracking sets. */\n markAllPristine(resolveRow: RowResolver<T>): void {\n for (const [rowId] of this.baselines) {\n const row = resolveRow(rowId);\n if (row) markPristine(this.baselines, rowId, row);\n }\n this.newRowIds.clear();\n this.changedRowIds.clear();\n this.committedDirtyRowIds.clear();\n }\n\n // --- Original row access ---\n\n /** Get deep-cloned baseline (undefined for new/untracked rows). */\n getOriginalRow(rowId: string): T | undefined {\n return getOriginalRow<T>(this.baselines, rowId);\n }\n\n /** Lightweight check whether a baseline exists (no cloning). */\n hasBaseline(rowId: string): boolean {\n return this.baselines.has(rowId);\n }\n\n // --- Aggregate queries ---\n\n /** Get all dirty rows with original + current data. Requires a row resolver. */\n getDirtyRows(resolveRow: RowResolver<T>): DirtyRowEntry<T>[] {\n const result: DirtyRowEntry<T>[] = [];\n for (const [rowId, baseline] of this.baselines) {\n const row = resolveRow(rowId);\n if (row && isRowDirty(this.baselines, rowId, row)) {\n result.push({ id: rowId, original: structuredClone(baseline), current: row });\n }\n }\n for (const newId of this.newRowIds) {\n const row = resolveRow(newId);\n if (row) {\n result.push({ id: newId, original: undefined, current: row });\n }\n }\n return result;\n }\n\n /** Get IDs of all dirty rows. Requires a row resolver. */\n getDirtyRowIds(resolveRow: RowResolver<T>): string[] {\n const ids: string[] = [];\n for (const [rowId] of this.baselines) {\n const row = resolveRow(rowId);\n if (row && isRowDirty(this.baselines, rowId, row)) ids.push(rowId);\n }\n for (const newId of this.newRowIds) ids.push(newId);\n return ids;\n }\n\n // --- Revert operations ---\n\n /**\n * Revert a row to its baseline values (mutates row in-place).\n * Returns `true` when baseline existed and row was reverted.\n */\n revertRow(rowId: string, row: T): boolean {\n const reverted = revertToBaseline(this.baselines, rowId, row);\n if (reverted) {\n this.changedRowIds.delete(rowId);\n this.committedDirtyRowIds.delete(rowId);\n }\n return reverted;\n }\n\n /** Revert all rows to baseline values. Requires a row resolver. */\n revertAll(resolveRow: RowResolver<T>): void {\n for (const [rowId] of this.baselines) {\n const row = resolveRow(rowId);\n if (row) revertToBaseline(this.baselines, rowId, row);\n }\n this.changedRowIds.clear();\n this.committedDirtyRowIds.clear();\n }\n\n // --- Changed row queries ---\n\n /** Resolve changed row IDs to row objects. */\n getChangedRows(resolveRow: RowResolver<T>): T[] {\n const rows: T[] = [];\n for (const id of this.changedRowIds) {\n const row = resolveRow(id);\n if (row) rows.push(row);\n }\n return rows;\n }\n\n /** Get a copy of changed row IDs. */\n getChangedRowIds(): string[] {\n return Array.from(this.changedRowIds);\n }\n\n /** Check if a row has been marked as changed. */\n isRowChanged(rowId: string): boolean {\n return this.changedRowIds.has(rowId);\n }\n}\n\n// #endregion\n","/**\n * Pure helper functions for the Editing Plugin.\n *\n * Extracted from EditingPlugin to reduce the main plugin file size.\n * All functions are stateless — they have no `this` access.\n *\n * @internal\n */\n\nimport type {\n ColumnConfig,\n ColumnEditorSpec,\n ColumnInternal,\n InternalGrid,\n RowElementInternal,\n} from '../../../core/types';\nimport { getInputValue } from '../editors';\nimport type { EditingConfig } from '../types';\n\n// #region Constants\n\n/**\n * CSS selector for focusable editor elements inside a cell.\n * Duplicated from core/internal/rows to avoid cross-boundary imports\n * that Vite externalises to `@toolbox-web/grid` during plugin bundling.\n *\n * @internal\n */\nexport const FOCUSABLE_EDITOR_SELECTOR =\n 'input,select,textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[tabindex]:not([tabindex=\"-1\"])';\n\n// #endregion\n\n// #region Editor Resolution\n\n/**\n * Resolves the editor for a column using the priority chain:\n * 1. Column-level (`column.editor`)\n * 2. Light DOM template (`__editorTemplate` → returns 'template')\n * 3. Grid-level (`gridConfig.typeDefaults[column.type]`)\n * 4. App-level (framework adapter's `getTypeDefault`)\n * 5. Returns undefined (caller uses built-in defaultEditorFor)\n */\nexport function resolveEditor<TRow>(\n grid: InternalGrid<TRow>,\n col: ColumnInternal<TRow>,\n): ColumnEditorSpec<TRow, unknown> | 'template' | undefined {\n // 1. Column-level editor (highest priority)\n if (col.editor) return col.editor;\n\n // 2. Light DOM template\n const tplHolder = col.__editorTemplate;\n if (tplHolder) return 'template';\n\n // No type specified - no type defaults to check\n if (!col.type) return undefined;\n\n // 3. Grid-level typeDefaults (access via effectiveConfig)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const gridTypeDefaults = (grid as any).effectiveConfig?.typeDefaults;\n if (gridTypeDefaults?.[col.type]?.editor) {\n return gridTypeDefaults[col.type].editor as ColumnEditorSpec<TRow, unknown>;\n }\n\n // 4. App-level registry (via framework adapter)\n const adapter = grid.__frameworkAdapter;\n if (adapter?.getTypeDefault) {\n const appDefault = adapter.getTypeDefault<TRow>(col.type);\n if (appDefault?.editor) {\n return appDefault.editor as ColumnEditorSpec<TRow, unknown>;\n }\n }\n\n // 5. No custom editor - caller uses built-in defaultEditorFor\n return undefined;\n}\n\n// #endregion\n\n// #region Property Key Safety\n\n/**\n * Returns true if the given property key is safe to use on a plain object.\n */\nexport function isSafePropertyKey(key: unknown): key is string {\n if (typeof key !== 'string') return false;\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') return false;\n return true;\n}\n\n// #endregion\n\n// #region Row Element State\n\n/**\n * Increment the editing cell count on a row element.\n */\nexport function incrementEditingCount(rowEl: RowElementInternal): void {\n const count = (rowEl.__editingCellCount ?? 0) + 1;\n rowEl.__editingCellCount = count;\n rowEl.setAttribute('data-has-editing', '');\n}\n\n/**\n * Clear all editing state from a row element.\n */\nexport function clearEditingState(rowEl: RowElementInternal): void {\n rowEl.__editingCellCount = 0;\n rowEl.removeAttribute('data-has-editing');\n}\n\n// #endregion\n\n// #region No-op Helpers\n\n/**\n * No-op updateRow function for rows without IDs.\n * Extracted to a named function to satisfy eslint no-empty-function.\n */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nexport function noopUpdateRow(_changes: unknown): void {\n // Row has no ID - cannot update\n}\n\n// #endregion\n\n// #region Editor Input Wiring\n\n/**\n * Auto-wire commit/cancel lifecycle for input elements in string-returned editors.\n */\nexport function wireEditorInputs(\n editorHost: HTMLElement,\n column: ColumnConfig<unknown>,\n commit: (value: unknown) => void,\n originalValue?: unknown,\n): void {\n const input = editorHost.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (!input) return;\n\n input.addEventListener('blur', () => {\n commit(getInputValue(input, column, originalValue));\n });\n\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.addEventListener('change', () => commit(input.checked));\n } else if (input instanceof HTMLSelectElement) {\n input.addEventListener('change', () => commit(getInputValue(input, column, originalValue)));\n }\n}\n\n// #endregion\n\n// #region Edit Guard\n\n/**\n * Returns `true` when the configured `onBeforeEditClose` callback vetoes\n * the close (i.e. returns `false`). Use as a one-liner guard:\n *\n * ```ts\n * if (shouldPreventEditClose(config, e)) return;\n * ```\n */\nexport function shouldPreventEditClose(config: EditingConfig, event: MouseEvent | KeyboardEvent): boolean {\n return config.onBeforeEditClose?.(event) === false;\n}\n\n// #endregion\n\n// #region Row Comparison\n\n/**\n * Shallow-compare a snapshot against the current row to detect changes.\n * Returns `true` if any own-property value differs between the two objects.\n */\nexport function hasRowChanged<T>(snapshot: T | undefined, current: T): boolean {\n if (!snapshot) return false;\n const snapshotObj = snapshot as Record<string, unknown>;\n const currentObj = current as Record<string, unknown>;\n const allKeys = new Set([...Object.keys(snapshotObj), ...Object.keys(currentObj)]);\n for (const key of allKeys) {\n if (snapshotObj[key] !== currentObj[key]) return true;\n }\n return false;\n}\n\n// #endregion\n","/**\n * Editor injection logic for the Editing Plugin.\n *\n * Extracted from EditingPlugin to reduce the main file size.\n * Contains the DOM-heavy editor creation and template rendering,\n * while the plugin retains state management and event emission.\n *\n * @internal\n */\n\nimport { EDITOR_MOUNT_ERROR, warnDiagnostic } from '../../../core/internal/diagnostics';\nimport type { ColumnConfig, ColumnInternal, GridHost, RowElementInternal } from '../../../core/types';\nimport { defaultEditorFor, getInputValue } from '../editors';\nimport type { EditingConfig, EditorContext } from '../types';\nimport {\n FOCUSABLE_EDITOR_SELECTOR,\n incrementEditingCount,\n isSafePropertyKey,\n noopUpdateRow,\n resolveEditor,\n shouldPreventEditClose,\n wireEditorInputs,\n} from './helpers';\n\n// #region Types\n\n/**\n * Dependencies injected by the EditingPlugin so the extraction\n * can call back into plugin-owned state and methods.\n */\nexport interface EditorInjectionDeps<T> {\n /** Internal grid reference (also serves as HTMLElement). */\n grid: GridHost<T>;\n /** Whether the grid is in always-editing \"grid\" mode. */\n isGridMode: boolean;\n /** Plugin configuration. */\n config: EditingConfig;\n /** Set of cells currently in edit mode (\"rowIndex:colIndex\"). */\n editingCells: Set<string>;\n /** Value-change callbacks keyed by \"rowIndex:field\". */\n editorValueCallbacks: Map<string, (newValue: unknown) => void>;\n /** Returns `true` when an edit session is active (#activeEditRow !== -1). */\n isEditSessionActive: () => boolean;\n /** Commit a single cell value change. */\n commitCellValue: (rowIndex: number, column: ColumnConfig<T>, newValue: unknown, rowData: T) => void;\n /** Exit editing for a row (commit or revert). */\n exitRowEdit: (rowIndex: number, revert: boolean) => void;\n}\n\n// #endregion\n\n// #region Editor Injection\n\n/**\n * Inject an editor into a cell element.\n *\n * Handles the full editor lifecycle: creates the editor host, resolves\n * the editor spec (template / custom-element / factory / component),\n * wires commit/cancel callbacks, and registers value-change listeners.\n */\nexport function injectEditor<T>(\n deps: EditorInjectionDeps<T>,\n rowData: T,\n rowIndex: number,\n column: ColumnConfig<T>,\n colIndex: number,\n cell: HTMLElement,\n skipFocus: boolean,\n parentRowEl?: HTMLElement,\n): void {\n if (!column.editable) return;\n if (cell.classList.contains('editing')) return;\n\n const { grid, isGridMode, config, editingCells, editorValueCallbacks } = deps;\n\n // Get row ID for updateRow helper (may not exist)\n let rowId: string | undefined;\n try {\n rowId = grid.getRowId?.(rowData);\n } catch {\n // Row has no ID\n }\n\n // Create updateRow helper for cascade updates (noop if row has no ID)\n const updateRow: (changes: Partial<T>) => void = rowId\n ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (changes) => (grid as any).updateRow(rowId!, changes as Record<string, unknown>, 'cascade')\n : noopUpdateRow;\n\n const originalValue = isSafePropertyKey(column.field)\n ? (rowData as Record<string, unknown>)[column.field]\n : undefined;\n\n cell.classList.add('editing');\n editingCells.add(`${rowIndex}:${colIndex}`);\n\n // Use explicit parentRowEl when cell is in a DocumentFragment (not yet in DOM),\n // otherwise fall back to cell.parentElement for cells already attached to a row.\n const rowEl = (parentRowEl ?? cell.parentElement) as RowElementInternal | null;\n if (rowEl) incrementEditingCount(rowEl);\n\n let editFinalized = false;\n const commit = (newValue: unknown) => {\n // In grid mode, always allow commits (we're always editing)\n // In row mode, only allow commits if we're in an active edit session\n if (editFinalized || (!isGridMode && !deps.isEditSessionActive())) return;\n // Resolve row and index fresh at commit time.\n // With a row ID we use _getRowEntry for O(1) lookup — this is resilient\n // against _rows being replaced (e.g. Angular directive effect).\n // Without a row ID we fall back to the captured rowData reference.\n // Using _rows[rowIndex] without an ID is unsafe: the index may be stale\n // after _rows replacement, which would commit to the WRONG row.\n const entry = rowId ? grid._getRowEntry(rowId) : undefined;\n const currentRowData = (entry?.row ?? rowData) as T;\n const currentIndex = entry?.index ?? rowIndex;\n deps.commitCellValue(currentIndex, column, newValue, currentRowData);\n };\n const cancel = () => {\n editFinalized = true;\n if (isSafePropertyKey(column.field)) {\n // Same ID-first / captured-rowData fallback as commit — see comment above.\n const entry = rowId ? grid._getRowEntry(rowId) : undefined;\n const currentRowData = (entry?.row ?? rowData) as T;\n (currentRowData as Record<string, unknown>)[column.field] = originalValue;\n }\n };\n\n const editorHost = document.createElement('div');\n editorHost.className = 'tbw-editor-host';\n cell.innerHTML = '';\n cell.appendChild(editorHost);\n\n // Keydown handler for Enter/Escape\n editorHost.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Enter') {\n // In grid mode, Enter just commits without exiting\n if (isGridMode) {\n e.stopPropagation();\n e.preventDefault();\n // Get current value and commit\n const input = editorHost.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (input) {\n commit(getInputValue(input, column as ColumnConfig<unknown>, originalValue));\n }\n return;\n }\n if (shouldPreventEditClose(config, e)) return;\n e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n deps.exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n // In grid mode, Escape doesn't exit edit mode\n if (isGridMode) {\n e.stopPropagation();\n e.preventDefault();\n return;\n }\n if (shouldPreventEditClose(config, e)) return;\n e.stopPropagation();\n e.preventDefault();\n cancel();\n deps.exitRowEdit(rowIndex, true);\n }\n });\n\n const colInternal = column as ColumnInternal<T>;\n const tplHolder = colInternal.__editorTemplate;\n // Resolve editor using priority chain: column → template → typeDefaults → adapter → built-in\n const editorSpec = resolveEditor(grid, colInternal) ?? defaultEditorFor(column);\n const value = originalValue;\n\n // Value-change callback registration.\n // Editors call onValueChange(cb) to receive pushes when the underlying row\n // is mutated externally (e.g., via updateRow from another cell's commit).\n // Multiple callbacks can be registered (user + auto-wire).\n const callbackKey = `${rowIndex}:${column.field}`;\n const callbacks: Array<(newValue: unknown) => void> = [];\n editorValueCallbacks.set(callbackKey, (newVal) => {\n for (const cb of callbacks) cb(newVal);\n });\n const onValueChange = (cb: (newValue: unknown) => void) => {\n callbacks.push(cb);\n };\n\n if (editorSpec === 'template' && tplHolder) {\n renderTemplateEditor(deps, editorHost, colInternal, rowData, originalValue, commit, cancel, skipFocus, rowIndex);\n // Auto-update built-in template editors when value changes externally.\n // Skip non-primitive values (arrays, objects) — framework adapters manage\n // those via the cell-cancel event. String() on arrays produces comma-separated junk.\n onValueChange((newVal) => {\n if (newVal != null && typeof newVal === 'object') return;\n const input = editorHost.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(\n 'input,textarea,select',\n );\n if (input) {\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.checked = !!newVal;\n } else {\n input.value = String(newVal ?? '');\n }\n }\n });\n } else if (typeof editorSpec === 'string') {\n const el = document.createElement(editorSpec) as HTMLElement & { value?: unknown };\n el.value = value;\n el.addEventListener('change', () => commit(el.value));\n // Auto-update custom element editors when value changes externally\n onValueChange((newVal) => {\n el.value = newVal;\n });\n editorHost.appendChild(el);\n if (!skipFocus) {\n queueMicrotask(() => {\n const focusable = editorHost.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n focusable?.focus({ preventScroll: true });\n });\n }\n } else if (typeof editorSpec === 'function') {\n const ctx: EditorContext<T> = {\n row: rowData,\n rowId: rowId ?? '',\n value,\n field: column.field,\n column,\n commit,\n cancel,\n updateRow,\n onValueChange,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const produced = (editorSpec as any)(ctx);\n if (typeof produced === 'string') {\n editorHost.innerHTML = produced;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n wireEditorInputs(editorHost, column as any, commit, originalValue);\n // Auto-update wired inputs when value changes externally\n onValueChange((newVal) => {\n const input = editorHost.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(\n 'input,textarea,select',\n );\n if (input) {\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.checked = !!newVal;\n } else {\n input.value = String(newVal ?? '');\n }\n }\n });\n } else if (produced instanceof Node) {\n editorHost.appendChild(produced);\n const isSimpleInput =\n produced instanceof HTMLInputElement ||\n produced instanceof HTMLSelectElement ||\n produced instanceof HTMLTextAreaElement;\n if (!isSimpleInput) {\n cell.setAttribute('data-editor-managed', '');\n } else {\n // Auto-update simple inputs returned by factory functions\n onValueChange((newVal) => {\n if (produced instanceof HTMLInputElement && produced.type === 'checkbox') {\n produced.checked = !!newVal;\n } else {\n (produced as HTMLInputElement).value = String(newVal ?? '');\n }\n });\n }\n } else if (!produced && editorHost.hasChildNodes()) {\n // Factory returned void but mounted content into the editor host\n // (e.g. Angular/React/Vue adapter component editor). Mark the cell\n // as externally managed so the native commit loop in #exitRowEdit\n // does not read raw input values from framework editor DOM.\n cell.setAttribute('data-editor-managed', '');\n }\n if (!skipFocus) {\n queueMicrotask(() => {\n const focusable = editorHost.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n focusable?.focus({ preventScroll: true });\n });\n }\n } else if (editorSpec && typeof editorSpec === 'object') {\n const placeholder = document.createElement('div');\n placeholder.setAttribute('data-external-editor', '');\n placeholder.setAttribute('data-field', column.field);\n editorHost.appendChild(placeholder);\n cell.setAttribute('data-editor-managed', '');\n const context: EditorContext<T> = {\n row: rowData,\n rowId: rowId ?? '',\n value,\n field: column.field,\n column,\n commit,\n cancel,\n updateRow,\n onValueChange,\n };\n if (editorSpec.mount) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n editorSpec.mount({ placeholder, context: context as any, spec: editorSpec });\n } catch (e) {\n warnDiagnostic(\n EDITOR_MOUNT_ERROR,\n `External editor mount error for column '${column.field}': ${e}`,\n deps.grid.id,\n );\n }\n } else {\n grid.dispatchEvent(\n new CustomEvent('mount-external-editor', { detail: { placeholder, spec: editorSpec, context } }),\n );\n }\n }\n}\n\n// #endregion\n\n// #region Template Editor\n\n/**\n * Render a template-based editor inside an editor host element.\n */\nfunction renderTemplateEditor<T>(\n deps: Pick<EditorInjectionDeps<T>, 'config' | 'exitRowEdit'>,\n editorHost: HTMLElement,\n column: ColumnInternal<T>,\n rowData: T,\n originalValue: unknown,\n commit: (value: unknown) => void,\n cancel: () => void,\n skipFocus: boolean,\n rowIndex: number,\n): void {\n const tplHolder = column.__editorTemplate;\n if (!tplHolder) return;\n\n const clone = tplHolder.cloneNode(true) as HTMLElement;\n const compiledEditor = column.__compiledEditor;\n\n if (compiledEditor) {\n clone.innerHTML = compiledEditor({\n row: rowData,\n value: originalValue,\n field: column.field,\n column,\n commit,\n cancel,\n });\n } else {\n clone.querySelectorAll<HTMLElement>('*').forEach((node) => {\n if (node.childNodes.length === 1 && node.firstChild?.nodeType === Node.TEXT_NODE) {\n node.textContent =\n node.textContent\n ?.replace(/{{\\s*value\\s*}}/g, originalValue == null ? '' : String(originalValue))\n .replace(/{{\\s*row\\.([a-zA-Z0-9_]+)\\s*}}/g, (_m, g: string) => {\n if (!isSafePropertyKey(g)) return '';\n const v = (rowData as Record<string, unknown>)[g];\n return v == null ? '' : String(v);\n }) || '';\n }\n });\n }\n\n const input = clone.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(\n 'input,textarea,select',\n );\n if (input) {\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.checked = !!originalValue;\n } else {\n input.value = String(originalValue ?? '');\n }\n\n let editFinalized = false;\n input.addEventListener('blur', () => {\n if (editFinalized) return;\n commit(getInputValue(input, column, originalValue));\n });\n input.addEventListener('keydown', (evt) => {\n const e = evt as KeyboardEvent;\n if (e.key === 'Enter') {\n if (shouldPreventEditClose(deps.config, e)) return;\n e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n commit(getInputValue(input, column, originalValue));\n deps.exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n if (shouldPreventEditClose(deps.config, e)) return;\n e.stopPropagation();\n e.preventDefault();\n cancel();\n deps.exitRowEdit(rowIndex, true);\n }\n });\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.addEventListener('change', () => commit(input.checked));\n }\n if (!skipFocus) {\n setTimeout(() => input.focus({ preventScroll: true }), 0);\n }\n }\n editorHost.appendChild(clone);\n}\n\n// #endregion\n","/**\n * Editing Plugin\n *\n * Provides complete editing functionality for tbw-grid.\n * This plugin is FULLY SELF-CONTAINED - the grid has ZERO editing knowledge.\n *\n * The plugin:\n * - Owns all editing state (active cell, snapshots, changed rows)\n * - Uses event distribution (onCellClick, onKeyDown) to handle edit lifecycle\n * - Uses afterRender() hook to inject editors into cells\n * - Uses processColumns() to augment columns with editing metadata\n * - Emits its own events (cell-commit, row-commit, changed-rows-reset)\n *\n * Without this plugin, the grid cannot edit. With this plugin, editing\n * is fully functional without any core changes.\n */\n\nimport { ensureCellVisible } from '../../core/internal/keyboard';\nimport type {\n AfterCellRenderContext,\n AfterRowRenderContext,\n PluginManifest,\n PluginQuery,\n} from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin, type CellClickEvent, type GridElement } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, GridHost, InternalGrid, RowElementInternal } from '../../core/types';\nimport styles from './editing.css?inline';\nimport { getInputValue } from './editors';\nimport { CellValidationManager } from './internal/cell-validation';\nimport { type BaselinesCapturedDetail, type DirtyChangeDetail, type DirtyRowEntry } from './internal/dirty-tracking';\nimport { DirtyTrackingManager } from './internal/dirty-tracking-manager';\nimport { type EditorInjectionDeps, injectEditor as injectEditorImpl } from './internal/editor-injection';\nimport {\n clearEditingState,\n FOCUSABLE_EDITOR_SELECTOR,\n hasRowChanged,\n isSafePropertyKey,\n noopUpdateRow,\n shouldPreventEditClose,\n} from './internal/helpers';\nimport type {\n BeforeEditCloseDetail,\n CellCancelDetail,\n CellCommitDetail,\n ChangedRowsResetDetail,\n EditCloseDetail,\n EditingConfig,\n EditOpenDetail,\n RowCommitDetail,\n} from './types';\n\n// ============================================================================\n// EditingPlugin\n// ============================================================================\n\n/**\n * Editing Plugin for tbw-grid\n *\n * Enables inline cell editing in the grid. Provides built-in editors for common data types\n * and supports custom editor functions for specialized input scenarios.\n *\n * ## Why Opt-In?\n *\n * Editing is delivered as a plugin rather than built into the core grid:\n *\n * - **Smaller bundle** — Apps that only display data don't pay for editing code\n * - **Clear intent** — Explicit plugin registration makes editing capability obvious\n * - **Runtime validation** — Using `editable: true` without the plugin throws a helpful error\n *\n * ## Installation\n *\n * ```ts\n * import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';\n * ```\n *\n * ## Edit Triggers\n *\n * Configure how editing is triggered with the `editOn` option:\n *\n * | Value | Behavior |\n * |-------|----------|\n * | `'click'` | Single click enters edit mode (default) |\n * | `'dblclick'` | Double-click enters edit mode |\n *\n * ## Keyboard Shortcuts\n *\n * | Key | Action |\n * |-----|--------|\n * | `Enter` | Commit edit and move down |\n * | `Tab` | Commit edit and move right |\n * | `Escape` | Cancel edit, restore original value |\n * | `Arrow Keys` | Navigate between cells (when not editing) |\n *\n * @example Basic editing with double-click trigger\n * ```ts\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', editable: true },\n * { field: 'price', type: 'number', editable: true },\n * { field: 'active', type: 'boolean', editable: true },\n * ],\n * plugins: [new EditingPlugin({ editOn: 'dblclick' })],\n * };\n *\n * grid.on('cell-commit', ({ field, oldValue, newValue }) => {\n * console.log(`${field}: ${oldValue} → ${newValue}`);\n * });\n * ```\n *\n * @example Custom editor function\n * ```ts\n * columns: [\n * {\n * field: 'status',\n * editable: true,\n * editor: (ctx) => {\n * const select = document.createElement('select');\n * ['pending', 'active', 'completed'].forEach(opt => {\n * const option = document.createElement('option');\n * option.value = opt;\n * option.textContent = opt;\n * option.selected = ctx.value === opt;\n * select.appendChild(option);\n * });\n * select.addEventListener('change', () => ctx.commit(select.value));\n * return select;\n * },\n * },\n * ]\n * ```\n *\n * @see {@link EditingConfig} for configuration options\n * @see {@link EditorContext} for custom editor context\n * @see {@link EditingConfig} for interactive examples in the docs site\n */\nexport class EditingPlugin<T = unknown> extends BaseGridPlugin<EditingConfig> {\n /**\n * Plugin manifest - declares owned properties for configuration validation.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n ownedProperties: [\n {\n property: 'editable',\n level: 'column',\n description: 'the \"editable\" column property',\n isUsed: (v) => v === true || typeof v === 'function',\n },\n {\n property: 'editor',\n level: 'column',\n description: 'the \"editor\" column property',\n },\n {\n property: 'editorParams',\n level: 'column',\n description: 'the \"editorParams\" column property',\n },\n {\n property: 'nullable',\n level: 'column',\n description: 'the \"nullable\" column property (allows null values)',\n },\n ],\n events: [\n {\n type: 'cell-edit-committed',\n description: 'Emitted when a cell edit is committed (for plugin-to-plugin coordination)',\n },\n ],\n queries: [\n {\n type: 'isEditing',\n description: 'Returns whether any cell is currently being edited',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'editing';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<EditingConfig> {\n return {\n mode: 'row',\n editOn: 'click',\n };\n }\n\n /**\n * Whether the grid is in 'grid' mode (all cells always editable).\n */\n get #isGridMode(): boolean {\n return this.config.mode === 'grid';\n }\n\n /**\n * Resolve whether a given cell is editable.\n *\n * Resolution order:\n * 1. `gridConfig.rowEditable(row)` — row-level gate (if provided). Returns `false` → not editable.\n * 2. `column.editable` — `true`, `false`, or `(row) => boolean`.\n *\n * @returns `true` when the cell should be editable, `false` otherwise.\n */\n #isCellEditable(column: ColumnConfig<T>, row: T): boolean {\n // Row-level gate\n const rowEditable = this.#internalGrid.effectiveConfig?.rowEditable;\n if (rowEditable && !rowEditable(row as any)) return false;\n\n // Column-level check\n const { editable } = column;\n if (typeof editable === 'function') return editable(row);\n return editable === true;\n }\n\n /**\n * Check whether a column has ANY editability configured (static `true` or a\n * function). This is used for quick checks where no specific row is available\n * (e.g. \"does this row have any potentially-editable columns?\").\n */\n #hasEditableConfig(column: ColumnConfig<T>): boolean {\n return column.editable === true || typeof column.editable === 'function';\n }\n\n // #region Editing State (fully owned by plugin)\n\n /** Currently active edit row index, or -1 if not editing */\n #activeEditRow = -1;\n\n /** Row ID of the currently active edit row (stable across _rows replacement) */\n #activeEditRowId: string | undefined;\n\n /** Reference to the row object at edit-open time. Used as fallback in\n * #exitRowEdit when no row ID is available (prevents stale-index access). */\n #activeEditRowRef: T | undefined;\n\n /** Currently active edit column index, or -1 if not editing */\n #activeEditCol = -1;\n\n /** Snapshots of row data before editing started */\n #rowEditSnapshots = new Map<number, T>();\n\n /** Set of cells currently in edit mode: \"rowIndex:colIndex\" */\n #editingCells = new Set<string>();\n\n /**\n * Value-change callbacks for active editors.\n * Keyed by \"rowIndex:field\" → callback that pushes updated values to the editor.\n * Populated during #injectEditor, cleaned up when editors are removed.\n */\n #editorValueCallbacks = new Map<string, (newValue: unknown) => void>();\n\n /** Flag to restore focus after next render (used when exiting edit mode) */\n #pendingFocusRestore = false;\n\n /** Row index pending animation after render, or -1 if none */\n #pendingRowAnimation = -1;\n\n /**\n * Cell validation manager — handles invalid-cell state tracking and DOM sync.\n * Initialized lazily in `attach()` since the sync callback needs grid access.\n */\n #validation!: CellValidationManager;\n\n /**\n * In grid mode, tracks whether an input field is currently focused.\n * When true: arrow keys work within input (edit mode).\n * When false: arrow keys navigate between cells (navigation mode).\n * Escape switches to navigation mode, Enter switches to edit mode.\n */\n #gridModeInputFocused = false;\n\n /**\n * In grid mode, when true, prevents inputs from auto-focusing.\n * This is set when Escape is pressed (navigation mode) and cleared\n * when Enter is pressed or user explicitly clicks an input.\n */\n #gridModeEditLocked = false;\n\n /**\n * When true, only a single cell is being edited (triggered by F2 or `beginCellEdit`).\n * Tab and Arrow keys commit and close the editor instead of navigating to adjacent cells.\n */\n #singleCellEdit = false;\n\n /**\n * In grid mode, snapshot of the focused cell's value when the editor first\n * receives focus. Used to revert on Escape (cell-level cancel).\n */\n #gridModeCellSnapshot: { rowIndex: number; colIndex: number; field: string; value: unknown } | null = null;\n\n // --- Dirty Tracking State (delegated to DirtyTrackingManager) ---\n\n /** Manages all dirty tracking state: baselines, changed/new/committed sets. */\n readonly #dirty = new DirtyTrackingManager<T>();\n\n // --- Editor Injection Deps (cached for #injectEditor delegation) ---\n\n /** Dependency bag created once in `attach()` and reused by every `#injectEditor` call. */\n #editorDeps!: EditorInjectionDeps<T>;\n\n /**\n * Typed accessor for `InternalGrid` — avoids repeating `as unknown as` at every call site.\n * Safe because `DataGridElement` implements `InternalGrid` at runtime.\n */\n get #internalGrid(): GridHost<T> {\n return this.grid as unknown as GridHost<T>;\n }\n\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n\n const signal = this.disconnectSignal;\n const internalGrid = this.#internalGrid;\n\n // Initialize cell validation manager with DOM sync callback\n this.#validation = new CellValidationManager((rowId, field, invalid) => {\n this.#syncInvalidCellAttribute(rowId, field, invalid);\n });\n\n // Initialize editor injection deps (cached for all #injectEditor calls)\n this.#editorDeps = {\n grid: internalGrid,\n isGridMode: this.#isGridMode,\n config: this.config,\n editingCells: this.#editingCells,\n editorValueCallbacks: this.#editorValueCallbacks,\n isEditSessionActive: () => this.#activeEditRow !== -1,\n commitCellValue: (ri, col, val, row) => this.#commitCellValue(ri, col, val, row),\n exitRowEdit: (ri, revert) => this.#exitRowEdit(ri, revert),\n };\n\n // Inject editing state and methods onto grid for backward compatibility\n internalGrid._activeEditRows = -1;\n internalGrid._rowEditSnapshots = new Map();\n\n // Inject changedRows getter\n Object.defineProperty(grid, 'changedRows', {\n get: () => this.changedRows,\n configurable: true,\n });\n\n // Inject changedRowIds getter (new ID-based API)\n Object.defineProperty(grid, 'changedRowIds', {\n get: () => this.changedRowIds,\n configurable: true,\n });\n\n // Inject raw Set for O(1) lookup in the render hot path\n Object.defineProperty(grid, '_changedRowIdSet', {\n get: () => this.#dirty.changedRowIds,\n configurable: true,\n });\n\n // Inject resetChangedRows method\n (grid as any).resetChangedRows = (silent?: boolean) => this.resetChangedRows(silent);\n\n // Inject beginBulkEdit method (for backward compatibility)\n (grid as any).beginBulkEdit = (rowIndex: number, field?: string) => {\n if (field) {\n this.beginCellEdit(rowIndex, field);\n }\n // If no field specified, we can't start editing without a specific cell\n };\n\n // Document-level Escape to cancel editing (only in 'row' mode)\n document.addEventListener(\n 'keydown',\n (e: KeyboardEvent) => {\n // In grid mode, Escape doesn't exit edit mode\n if (this.#isGridMode) return;\n if (e.key === 'Escape' && this.#activeEditRow !== -1) {\n if (shouldPreventEditClose(this.config, e)) return;\n this.#exitRowEdit(this.#activeEditRow, true);\n }\n },\n { capture: true, signal },\n );\n\n // Click outside to commit editing (only in 'row' mode)\n // Use queueMicrotask to allow pending change events to fire first.\n // This is important for Angular/React editors where the (change) event\n // fires after mousedown but before mouseup/click.\n document.addEventListener(\n 'mousedown',\n (e: MouseEvent) => {\n // In grid mode, clicking outside doesn't exit edit mode\n if (this.#isGridMode) return;\n if (this.#activeEditRow === -1) return;\n const rowEl = internalGrid.findRenderedRowElement?.(this.#activeEditRow);\n if (!rowEl) return;\n const path = (e.composedPath && e.composedPath()) || [];\n if (path.includes(rowEl)) return;\n\n // Check if click is inside a registered external focus container\n // (e.g., overlays, datepickers, dropdowns at <body> level).\n // Only check targets OUTSIDE the grid — clicks on other rows inside\n // the grid should still commit the active edit row.\n const target = e.target as Node | null;\n if (target && !this.gridElement.contains(target) && this.grid.containsFocus?.(target)) {\n return;\n }\n\n if (shouldPreventEditClose(this.config, e)) return;\n\n // Delay exit to allow pending change/commit events to fire\n queueMicrotask(() => {\n if (this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, false);\n }\n });\n },\n { signal },\n );\n\n // Focus trap: when enabled, prevent focus from leaving the grid\n // while a row is being edited. If focus moves outside the grid\n // (and its registered external containers), reclaim it.\n if (this.config.focusTrap) {\n this.gridElement.addEventListener(\n 'focusout',\n (e: FocusEvent) => {\n // Only trap in row mode when actively editing\n if (this.#isGridMode) return;\n if (this.#activeEditRow === -1) return;\n\n const related = e.relatedTarget as Node | null;\n // If focus is going to an external container, that's fine\n if (related && this.grid.containsFocus?.(related)) return;\n // If focus is going to another element inside the grid, allow it\n if (related && this.gridElement.contains(related)) return;\n\n // Focus left the grid entirely — reclaim it\n queueMicrotask(() => {\n // Re-check in case editing was committed in the meantime\n if (this.#activeEditRow === -1) return;\n this.#focusCurrentCellEditor();\n });\n },\n { signal },\n );\n }\n\n // Listen for external row mutations to push updated values to active editors.\n // When field A commits and sets field B via updateRow(), field B's editor\n // (if open) must reflect the new value.\n this.gridElement.addEventListener(\n 'cell-change',\n (e: Event) => {\n const detail = (e as CustomEvent).detail as {\n rowIndex: number;\n field: string;\n newValue: unknown;\n source: string;\n };\n // Only push updates from cascade/api sources — not from the editor's own commit\n if (detail.source === 'user') return;\n const key = `${detail.rowIndex}:${detail.field}`;\n const cb = this.#editorValueCallbacks.get(key);\n if (cb) cb(detail.newValue);\n },\n { signal },\n );\n\n // --- Dirty tracking: listen for undo/redo events to re-evaluate dirty state ---\n if (this.config.dirtyTracking) {\n const handleUndoRedo = (e: Event) => {\n const detail = (e as CustomEvent).detail as { action?: { rowIndex: number; field: string } };\n const action = detail?.action;\n if (!action) return;\n const row = this.rows[action.rowIndex] as T | undefined;\n if (!row) return;\n const rowId = this.grid.getRowId(row);\n if (!rowId) return;\n const dirty = this.#dirty.isRowDirty(rowId, row);\n this.emit<DirtyChangeDetail<T>>('dirty-change', {\n rowId,\n row,\n original: this.#dirty.getOriginalRow(rowId),\n type: dirty ? 'modified' : 'pristine',\n });\n };\n this.gridElement.addEventListener('undo', handleUndoRedo, { signal });\n this.gridElement.addEventListener('redo', handleUndoRedo, { signal });\n\n // Listen for row-inserted events to auto-mark new rows for dirty tracking\n this.on('row-inserted', (detail: { row: T; index: number }) => {\n const rowId = this.grid.getRowId(detail.row);\n if (rowId != null) {\n this.markAsNew(String(rowId));\n }\n });\n }\n\n // In grid mode, request a full render to trigger afterCellRender hooks\n if (this.#isGridMode) {\n internalGrid._isGridEditMode = true;\n this.gridElement.classList.add('tbw-grid-mode');\n this.requestRender();\n\n // Track focus/blur on inputs to maintain navigation vs edit mode state\n this.gridElement.addEventListener(\n 'focusin',\n (e: FocusEvent) => {\n const target = e.target as HTMLElement;\n // Ignore focus on the grid element itself — it has tabindex=0 so it\n // matches FOCUSABLE_EDITOR_SELECTOR, but blurring + re-focusing it\n // would cause infinite recursion.\n if (target === this.gridElement) return;\n if (target.matches(FOCUSABLE_EDITOR_SELECTOR)) {\n // If edit is locked (navigation mode), blur the input immediately\n if (this.#gridModeEditLocked) {\n target.blur();\n this.gridElement.focus();\n return;\n }\n\n // Snapshot cell value on initial focus or when moving to a different cell.\n // This allows Escape to revert the cell to its pre-edit value.\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n const snap = this.#gridModeCellSnapshot;\n if (!snap || snap.rowIndex !== focusRow || snap.colIndex !== focusCol) {\n const column = internalGrid._visibleColumns?.[focusCol];\n const rowData = internalGrid._rows?.[focusRow];\n if (column?.field && rowData) {\n const field = column.field as string;\n this.#gridModeCellSnapshot = {\n rowIndex: focusRow,\n colIndex: focusCol,\n field,\n value: (rowData as Record<string, unknown>)[field],\n };\n }\n }\n\n this.#gridModeInputFocused = true;\n }\n },\n { signal },\n );\n\n this.gridElement.addEventListener(\n 'focusout',\n (e: FocusEvent) => {\n const related = e.relatedTarget as HTMLElement | null;\n // Only clear if focus went outside grid (and external containers) or to a non-input element\n if (\n !related ||\n (!this.gridElement.contains(related) && !this.grid.containsFocus?.(related)) ||\n !related.matches(FOCUSABLE_EDITOR_SELECTOR)\n ) {\n this.#gridModeInputFocused = false;\n // Clear cell snapshot when leaving edit mode normally (not via Escape)\n this.#gridModeCellSnapshot = null;\n }\n },\n { signal },\n );\n\n // Handle Escape key directly on the grid element (capture phase)\n // This ensures we intercept Escape even when focus is inside an input\n this.gridElement.addEventListener(\n 'keydown',\n (e: KeyboardEvent) => {\n if (e.key === 'Escape' && this.#gridModeInputFocused) {\n // Check onBeforeEditClose to let overlays close first.\n if (shouldPreventEditClose(this.config, e)) {\n // Overlay is open — schedule deferred nav-mode transition\n // after the overlay tears down.\n queueMicrotask(() => {\n if (this.#gridModeInputFocused) {\n this.#revertGridModeCellEdit();\n const activeEl = document.activeElement as HTMLElement;\n if (activeEl && this.gridElement.contains(activeEl)) {\n activeEl.blur();\n this.gridElement.focus();\n }\n this.#gridModeInputFocused = false;\n this.#gridModeEditLocked = true;\n }\n });\n return;\n }\n // Revert cell value to pre-edit snapshot\n this.#revertGridModeCellEdit();\n\n const activeEl = document.activeElement as HTMLElement;\n if (activeEl && this.gridElement.contains(activeEl)) {\n activeEl.blur();\n // Move focus to the grid container so arrow keys work\n this.gridElement.focus();\n }\n this.#gridModeInputFocused = false;\n this.#gridModeEditLocked = true; // Lock edit mode until Enter/click\n e.preventDefault();\n e.stopPropagation();\n }\n },\n { capture: true, signal },\n );\n\n // Handle click on inputs - unlock edit mode when user explicitly clicks\n this.gridElement.addEventListener(\n 'mousedown',\n (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n if (target.matches(FOCUSABLE_EDITOR_SELECTOR)) {\n this.#gridModeEditLocked = false; // User clicked input - allow edit\n }\n },\n { signal },\n );\n }\n }\n\n /** @internal */\n override detach(): void {\n const internalGrid = this.#internalGrid;\n internalGrid._isGridEditMode = false;\n this.gridElement.classList.remove('tbw-grid-mode');\n this.#activeEditRow = -1;\n this.#activeEditRowId = undefined;\n this.#activeEditRowRef = undefined;\n this.#activeEditCol = -1;\n this.#rowEditSnapshots.clear();\n this.#dirty.clear();\n this.#editingCells.clear();\n this.#editorValueCallbacks.clear();\n this.#gridModeInputFocused = false;\n this.#gridModeEditLocked = false;\n this.#gridModeCellSnapshot = null;\n this.#singleCellEdit = false;\n super.detach();\n }\n\n /**\n * Handle plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'isEditing') {\n // In grid mode, we're always editing\n return this.#isGridMode || this.#activeEditRow !== -1;\n }\n return undefined;\n }\n\n // #endregion\n\n // #region Event Handlers (event distribution)\n\n /**\n * Handle cell clicks - start editing if configured for click mode.\n * Both click and dblclick events come through this handler.\n * Starts row-based editing (all editable cells in the row get editors).\n * @internal\n */\n override onCellClick(event: CellClickEvent): boolean | void {\n // In grid mode, all cells are already editable - no need to trigger row edit\n if (this.#isGridMode) return false;\n\n const internalGrid = this.#internalGrid;\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n\n // Check if editing is disabled\n if (editOn === false || editOn === 'manual') return false;\n\n // Check if this is click or dblclick mode\n if (editOn !== 'click' && editOn !== 'dblclick') return false;\n\n // Check if the event type matches the edit mode\n const isDoubleClick = event.originalEvent.type === 'dblclick';\n if (editOn === 'click' && isDoubleClick) return false; // In click mode, only handle single clicks\n if (editOn === 'dblclick' && !isDoubleClick) return false; // In dblclick mode, only handle double clicks\n\n const { rowIndex } = event;\n\n // Check if any column in the row is potentially editable\n const hasEditableColumn = internalGrid._columns?.some((col) => this.#hasEditableConfig(col as ColumnConfig<T>));\n if (!hasEditableColumn) return false;\n\n // Row-level gate\n const rowData = internalGrid._rows[rowIndex];\n const rowEditable = internalGrid.effectiveConfig?.rowEditable;\n if (rowData && rowEditable && !rowEditable(rowData as any)) return false;\n\n // Start row-based editing (all editable cells get editors)\n event.originalEvent.stopPropagation();\n this.beginBulkEdit(rowIndex);\n return true; // Handled\n }\n\n /**\n * Handle keyboard events for edit lifecycle.\n * @internal\n */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n const internalGrid = this.#internalGrid;\n\n // Escape: cancel current edit (row mode) or exit edit mode (grid mode)\n if (event.key === 'Escape') {\n // In grid mode: revert cell, blur input, enter navigation mode.\n // NOTE: No onBeforeEditClose check here — the capture-phase handler\n // is the authoritative overlay guard. If the event reaches this\n // bubble-phase handler, either:\n // a) the capture handler already bailed (overlay was open) and the\n // template handler (e.g. select.close()) addressed it, or\n // b) no overlay was open.\n // In both cases we should transition to navigation mode.\n if (this.#isGridMode && this.#gridModeInputFocused) {\n // Revert cell value to pre-edit snapshot\n this.#revertGridModeCellEdit();\n\n const activeEl = document.activeElement as HTMLElement;\n if (activeEl && this.gridElement.contains(activeEl)) {\n activeEl.blur();\n // Move focus to the grid so arrow keys navigate cells, not the editor\n this.gridElement.focus();\n }\n this.#gridModeInputFocused = false;\n this.#gridModeEditLocked = true; // Lock until Enter/click re-enables editing\n // Update focus styling\n this.requestAfterRender();\n return true;\n }\n\n // In row mode: cancel edit\n if (this.#activeEditRow !== -1 && !this.#isGridMode) {\n if (shouldPreventEditClose(this.config, event)) return true;\n this.#exitRowEdit(this.#activeEditRow, true);\n return true;\n }\n }\n\n // Arrow keys in grid mode when not editing input: navigate cells\n if (\n this.#isGridMode &&\n !this.#gridModeInputFocused &&\n (event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'ArrowLeft' || event.key === 'ArrowRight')\n ) {\n // Let the grid's default keyboard navigation handle this\n return false;\n }\n\n // Arrow Up/Down in grid mode when input is focused: let the editor handle it\n // (e.g., ArrowDown opens autocomplete/datepicker overlays, ArrowUp/Down navigates options)\n if (this.#isGridMode && this.#gridModeInputFocused && (event.key === 'ArrowUp' || event.key === 'ArrowDown')) {\n return true; // Handled: block grid navigation, let event reach editor\n }\n\n // Arrow Up/Down while editing: commit and exit edit mode, move to adjacent row (only in 'row' mode)\n if ((event.key === 'ArrowUp' || event.key === 'ArrowDown') && this.#activeEditRow !== -1 && !this.#isGridMode) {\n if (shouldPreventEditClose(this.config, event)) return true;\n\n const maxRow = internalGrid._rows.length - 1;\n const currentRow = this.#activeEditRow;\n\n // Commit the current edit\n this.#exitRowEdit(currentRow, false);\n\n // Move focus to adjacent row (same column)\n if (event.key === 'ArrowDown') {\n internalGrid._focusRow = Math.min(maxRow, internalGrid._focusRow + 1);\n } else {\n internalGrid._focusRow = Math.max(0, internalGrid._focusRow - 1);\n }\n\n event.preventDefault();\n // Ensure the focused cell is scrolled into view\n ensureCellVisible(internalGrid);\n // Request render to update focus styling\n this.requestAfterRender();\n return true;\n }\n\n // Tab/Shift+Tab while editing: move to next/prev editable cell\n if (event.key === 'Tab' && (this.#activeEditRow !== -1 || this.#isGridMode)) {\n event.preventDefault();\n\n // In single-cell edit mode (F2), commit and close instead of navigating\n if (this.#singleCellEdit) {\n this.#exitRowEdit(this.#activeEditRow, false);\n return true;\n }\n\n const forward = !event.shiftKey;\n this.#handleTabNavigation(forward);\n return true;\n }\n\n // Space: toggle boolean cells (only when not in edit mode - let editors handle their own space)\n if (event.key === ' ' || event.key === 'Spacebar') {\n // If we're in row edit mode, let the event pass through to the editor (e.g., checkbox)\n if (this.#activeEditRow !== -1) {\n return false;\n }\n\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n if (focusRow >= 0 && focusCol >= 0) {\n const column = internalGrid._visibleColumns[focusCol];\n const rowData = internalGrid._rows[focusRow];\n if (\n column &&\n rowData &&\n this.#isCellEditable(column as ColumnConfig<T>, rowData as T) &&\n column.type === 'boolean'\n ) {\n const field = column.field;\n if (isSafePropertyKey(field)) {\n const currentValue = (rowData as Record<string, unknown>)[field];\n const newValue = !currentValue;\n this.#commitCellValue(focusRow, column, newValue, rowData);\n event.preventDefault();\n // Re-render to update the UI\n this.requestRender();\n return true;\n }\n }\n }\n // Space on non-boolean cell - don't block keyboard navigation\n return false;\n }\n\n // Enter (unmodified): start row edit, commit, or enter edit mode in grid mode\n if (event.key === 'Enter' && !event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) {\n // In grid mode when not editing: focus the current cell's input\n if (this.#isGridMode && !this.#gridModeInputFocused) {\n this.#focusCurrentCellEditor();\n return true;\n }\n\n if (this.#activeEditRow !== -1) {\n if (shouldPreventEditClose(this.config, event)) return true;\n // Already editing - let cell handlers deal with it\n return false;\n }\n\n // Start row-based editing (not just the focused cell)\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n if (editOn === false || editOn === 'manual') return false;\n\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n if (focusRow >= 0) {\n // Check if ANY column in the row is potentially editable\n const hasEditableColumn = internalGrid._columns?.some((col) => this.#hasEditableConfig(col as ColumnConfig<T>));\n // Row-level gate\n const rowData = internalGrid._rows[focusRow];\n const rowEditable = internalGrid.effectiveConfig?.rowEditable;\n const rowBlocked = rowData && rowEditable && !rowEditable(rowData as any);\n if (hasEditableColumn && !rowBlocked) {\n // Emit cell-activate event BEFORE starting edit\n // This ensures consumers always get the activation event\n const column = internalGrid._visibleColumns[focusCol];\n const row = internalGrid._rows[focusRow];\n const field = column?.field ?? '';\n const value = field && row ? (row as Record<string, unknown>)[field] : undefined;\n const cellEl = this.gridElement.querySelector(`[data-row=\"${focusRow}\"][data-col=\"${focusCol}\"]`) as\n | HTMLElement\n | undefined;\n\n const activateEvent = new CustomEvent('cell-activate', {\n cancelable: true,\n bubbles: true,\n detail: {\n rowIndex: focusRow,\n colIndex: focusCol,\n field,\n value,\n row,\n cellEl,\n trigger: 'keyboard' as const,\n originalEvent: event,\n },\n });\n this.gridElement.dispatchEvent(activateEvent);\n\n // Also emit deprecated activate-cell for backwards compatibility\n const legacyEvent = new CustomEvent('activate-cell', {\n cancelable: true,\n bubbles: true,\n detail: { row: focusRow, col: focusCol },\n });\n this.gridElement.dispatchEvent(legacyEvent);\n\n // If consumer canceled the activation, don't start editing\n if (activateEvent.defaultPrevented || legacyEvent.defaultPrevented) {\n event.preventDefault();\n return true;\n }\n\n this.beginBulkEdit(focusRow);\n return true;\n }\n }\n // No editable columns - don't block keyboard navigation\n return false;\n }\n\n // F2: begin single-cell edit on the focused cell\n if (event.key === 'F2') {\n if (this.#activeEditRow !== -1 || this.#isGridMode) return false;\n\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n if (editOn === false) return false;\n\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n if (focusRow >= 0 && focusCol >= 0) {\n const column = internalGrid._visibleColumns[focusCol];\n const rowData = internalGrid._rows[focusRow];\n if (column && rowData && this.#isCellEditable(column as ColumnConfig<T>, rowData as T) && column.field) {\n event.preventDefault();\n this.beginCellEdit(focusRow, column.field);\n return true;\n }\n }\n return false;\n }\n\n // Don't block other keyboard events\n return false;\n }\n\n // #endregion\n\n // #region Render Hooks\n\n /**\n * Process columns to merge type-level editorParams with column-level.\n * Column-level params take precedence.\n * @internal\n */\n override processColumns(columns: ColumnConfig<T>[]): ColumnConfig<T>[] {\n const internalGrid = this.#internalGrid;\n const typeDefaults = (internalGrid as any).effectiveConfig?.typeDefaults;\n const adapter = internalGrid.__frameworkAdapter;\n\n // If no type defaults configured anywhere, skip processing\n if (!typeDefaults && !adapter?.getTypeDefault) return columns;\n\n return columns.map((col) => {\n if (!col.type) return col;\n\n // Get type-level editorParams\n let typeEditorParams: Record<string, unknown> | undefined;\n\n // Check grid-level typeDefaults first\n if (typeDefaults?.[col.type]?.editorParams) {\n typeEditorParams = typeDefaults[col.type].editorParams;\n }\n\n // Then check app-level (adapter) typeDefaults\n if (!typeEditorParams && adapter?.getTypeDefault) {\n const appDefault = adapter.getTypeDefault<T>(col.type);\n if (appDefault?.editorParams) {\n typeEditorParams = appDefault.editorParams;\n }\n }\n\n // No type-level params to merge\n if (!typeEditorParams) return col;\n\n // Merge: type-level as base, column-level wins on conflicts\n return {\n ...col,\n editorParams: { ...typeEditorParams, ...col.editorParams },\n };\n });\n }\n\n /**\n * Stabilize the actively edited row across `rows` array replacements and\n * capture dirty tracking baselines.\n *\n * **Editing stability:** When the consumer reassigns `grid.rows` while\n * editing, the full pipeline (sort, filter, group) runs on the new data.\n * This hook finds the edited row in the new array by ID and swaps in the\n * in-progress row reference (`#activeEditRowRef`) so editors survive.\n *\n * **Dirty tracking baselines:** When `dirtyTracking` is enabled, captures\n * a `structuredClone` snapshot of each row on first appearance\n * (first-write-wins). This prevents Angular's feedback loop from\n * overwriting baselines.\n *\n * @internal Plugin API — part of the render pipeline\n */\n override processRows(rows: readonly T[]): T[] {\n const internalGrid = this.#internalGrid;\n\n // --- Dirty tracking: capture baselines (first-write-wins) ---\n if (this.config.dirtyTracking && internalGrid.getRowId) {\n this.#dirty.capture(rows, (r) => {\n try {\n return internalGrid.getRowId?.(r);\n } catch {\n return undefined;\n }\n });\n }\n\n // --- Editing stability: swap in the in-progress row ---\n if (this.#activeEditRow === -1 || this.#isGridMode) return rows as T[];\n\n const editRowId = this.#activeEditRowId;\n const editRowRef = this.#activeEditRowRef;\n\n // Without a stable row ID we cannot match across array replacements\n if (!editRowId || !editRowRef) return rows as T[];\n\n const result = [...rows] as T[];\n\n // Find the edited row's new position by ID\n let newIndex = -1;\n for (let i = 0; i < result.length; i++) {\n try {\n if (internalGrid.getRowId?.(result[i]) === editRowId) {\n newIndex = i;\n break;\n }\n } catch {\n // Row has no ID — skip\n }\n }\n\n if (newIndex === -1) {\n // Row was deleted server-side — close the editor.\n // Cannot close synchronously during the processRows pipeline;\n // schedule for after the current render cycle completes.\n setTimeout(() => this.cancelActiveRowEdit(), 0);\n return result;\n }\n\n // Swap in the in-progress row data to preserve editor state\n result[newIndex] = editRowRef;\n\n // Update index-keyed state if the position changed (due to sort/filter)\n if (this.#activeEditRow !== newIndex) {\n this.#migrateEditRowIndex(this.#activeEditRow, newIndex);\n }\n\n return result;\n }\n\n /**\n * After render, reapply editors to cells in edit mode.\n * This handles virtualization - when a row scrolls back into view,\n * we need to re-inject the editor.\n * @internal\n */\n override afterRender(): void {\n const internalGrid = this.#internalGrid;\n\n // --- Editing stability: verify active edit row index ---\n // After processRows, subsequent plugins (filtering, grouping) may have\n // shifted row indices. Verify the index is still correct and fix if needed\n // before re-injecting editors.\n if (this.#activeEditRow !== -1 && this.#activeEditRowRef && !this.#isGridMode) {\n if (internalGrid._rows[this.#activeEditRow] !== this.#activeEditRowRef) {\n const newIndex = (internalGrid._rows as T[]).indexOf(this.#activeEditRowRef);\n if (newIndex !== -1) {\n this.#migrateEditRowIndex(this.#activeEditRow, newIndex);\n } else {\n // Row no longer in rendered set (filtered out or deleted)\n setTimeout(() => this.cancelActiveRowEdit(), 0);\n return;\n }\n }\n }\n\n // Restore focus after exiting edit mode\n if (this.#pendingFocusRestore) {\n this.#pendingFocusRestore = false;\n this.#restoreCellFocus(internalGrid);\n }\n\n // Animate the row after render completes (so the row element exists)\n if (this.#pendingRowAnimation !== -1) {\n const rowIndex = this.#pendingRowAnimation;\n this.#pendingRowAnimation = -1;\n internalGrid.animateRow?.(rowIndex, 'change');\n }\n\n // Emit baselines-captured event when new baselines were captured this cycle.\n // Emitted post-render so consumers can safely read grid.rows, query the DOM,\n // or call getOriginalRow() in their handler.\n const capturedCount = this.#dirty.drainCapturedFlag();\n if (capturedCount != null) {\n this.emit<BaselinesCapturedDetail>('baselines-captured', {\n count: capturedCount,\n });\n }\n\n // In 'grid' mode, editors are injected via afterCellRender hook during render\n if (this.#isGridMode) return;\n\n if (this.#editingCells.size === 0) return;\n\n // Re-inject editors for any editing cells that are visible\n for (const cellKey of this.#editingCells) {\n const [rowStr, colStr] = cellKey.split(':');\n const rowIndex = parseInt(rowStr, 10);\n const colIndex = parseInt(colStr, 10);\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) continue;\n\n const cellEl = rowEl.querySelector(`.cell[data-col=\"${colIndex}\"]`) as HTMLElement | null;\n if (!cellEl || cellEl.classList.contains('editing')) continue;\n\n // Cell is visible but not in editing mode - reinject editor\n const rowData = internalGrid._rows[rowIndex];\n const column = internalGrid._visibleColumns[colIndex];\n if (rowData && column) {\n this.#injectEditor(rowData, rowIndex, column, colIndex, cellEl, true);\n }\n }\n }\n\n /**\n * Hook called after each cell is rendered.\n * In grid mode, injects editors into editable cells during render (no DOM queries needed).\n * @internal\n */\n override afterCellRender(context: AfterCellRenderContext): void {\n const { row, rowIndex, column, colIndex, cellElement } = context;\n\n const editable = this.#isCellEditable(column as ColumnConfig<T>, row as T);\n\n // Set aria-readonly on non-editable cells so screen readers can distinguish\n if (!editable) {\n cellElement.setAttribute('aria-readonly', 'true');\n // In grid mode, tear down stale editors on cells that are no longer editable\n // (e.g., after virtualization recycles a row element for different row data)\n if (this.#isGridMode && cellElement.classList.contains('editing')) {\n cellElement.classList.remove('editing');\n const value = (row as Record<string, unknown>)[(column as ColumnConfig<T>).field as string];\n cellElement.textContent = value == null ? '' : String(value);\n }\n } else {\n cellElement.removeAttribute('aria-readonly');\n }\n\n // Only inject editors in grid mode\n if (!this.#isGridMode) return;\n\n // Skip non-editable cells\n if (!editable) return;\n\n // Skip if already has editor\n if (cellElement.classList.contains('editing')) return;\n\n // Inject editor (don't track in editingCells - we're always editing in grid mode)\n // Pass rowElement so incrementEditingCount works even when cell is in a DocumentFragment\n this.#injectEditor(row as T, rowIndex, column as ColumnConfig<T>, colIndex, cellElement, true, context.rowElement);\n }\n\n /**\n * Apply dirty-tracking CSS classes to each rendered row.\n *\n * - `tbw-cell-dirty` on individual cells whose value differs from baseline\n * (applied on cell-commit, visible during editing)\n * - `tbw-row-dirty` on the row element only after the row edit session is\n * committed (edit-close without cancel)\n * - `tbw-row-new` when a row was inserted via `insertRow()` with no baseline\n *\n * Only active when `dirtyTracking: true`.\n *\n * @internal Plugin API\n */\n override afterRowRender(context: AfterRowRenderContext): void {\n if (!this.config.dirtyTracking) return;\n\n const internalGrid = this.#internalGrid;\n const rowId = internalGrid.getRowId?.(context.row as T);\n if (!rowId) return;\n\n const { isNew, isCommittedDirty, hasBaseline } = this.#dirty.getRowDirtyState(rowId, context.row as T);\n\n const el = context.rowElement;\n\n // Row-level classes (tbw-row-dirty only after row-commit AND data differs)\n el.classList.toggle('tbw-row-dirty', isCommittedDirty);\n el.classList.toggle('tbw-row-new', isNew);\n\n // Cell-level classes (tbw-cell-dirty on individual cells with changed values)\n // Only run the per-cell loop when the row has a baseline — avoids\n // querySelectorAll on the hot path for rows without dirty tracking state.\n if (hasBaseline) {\n const cells = el.querySelectorAll('.cell[data-field]');\n for (let i = 0; i < cells.length; i++) {\n const cell = cells[i] as HTMLElement;\n const field = cell.getAttribute('data-field');\n if (field) {\n cell.classList.toggle('tbw-cell-dirty', this.#dirty.isCellDirty(rowId, context.row as T, field));\n }\n }\n } else {\n // Clean up stale tbw-cell-dirty classes on recycled row elements\n // that previously displayed a dirty row but now show a pristine one.\n const dirtyCells = el.querySelectorAll('.tbw-cell-dirty');\n for (let i = 0; i < dirtyCells.length; i++) {\n dirtyCells[i].classList.remove('tbw-cell-dirty');\n }\n }\n }\n\n /**\n * On scroll render, reapply editors to recycled cells.\n * @internal\n */\n override onScrollRender(): void {\n this.afterRender();\n }\n\n // #endregion\n\n // #region Public API\n\n /**\n * Get all rows that have been modified.\n * Uses ID-based lookup for stability when rows are reordered.\n */\n get changedRows(): T[] {\n return this.#dirty.getChangedRows((id) => this.grid.getRow(id) as T | undefined);\n }\n\n /**\n * Get IDs of all modified rows.\n */\n get changedRowIds(): string[] {\n return this.#dirty.getChangedRowIds();\n }\n\n /**\n * Get the currently active edit row index, or -1 if not editing.\n */\n get activeEditRow(): number {\n return this.#activeEditRow;\n }\n\n /**\n * Get the currently active edit column index, or -1 if not editing.\n */\n get activeEditCol(): number {\n return this.#activeEditCol;\n }\n\n /**\n * Check if a specific row is currently being edited.\n */\n isRowEditing(rowIndex: number): boolean {\n return this.#activeEditRow === rowIndex;\n }\n\n /**\n * Check if a specific cell is currently being edited.\n */\n isCellEditing(rowIndex: number, colIndex: number): boolean {\n return this.#editingCells.has(`${rowIndex}:${colIndex}`);\n }\n\n /**\n * Check if a specific row has been modified.\n * @param rowIndex - Row index to check (will be converted to ID internally)\n */\n isRowChanged(rowIndex: number): boolean {\n const internalGrid = this.#internalGrid;\n const row = internalGrid._rows[rowIndex];\n if (!row) return false;\n try {\n const rowId = internalGrid.getRowId?.(row);\n return rowId ? this.#dirty.isRowChanged(rowId) : false;\n } catch {\n return false;\n }\n }\n\n /**\n * Check if a row with the given ID has been modified.\n * @param rowId - Row ID to check\n */\n isRowChangedById(rowId: string): boolean {\n return this.#dirty.isRowChanged(rowId);\n }\n\n // #region Dirty Tracking API (delegated to DirtyTrackingManager)\n\n /** Check if a row differs from its baseline. Requires `dirtyTracking: true`. */\n isDirty(rowId: string): boolean {\n if (!this.config.dirtyTracking) return false;\n if (this.#dirty.newRowIds.has(rowId)) return true;\n const row = this.grid.getRow(rowId) as T | undefined;\n if (!row) return false;\n return this.#dirty.isRowDirty(rowId, row);\n }\n\n /** Inverse of `isDirty`. */\n isPristine(rowId: string): boolean {\n return !this.isDirty(rowId);\n }\n\n /** Whether any row in the grid is dirty. */\n get dirty(): boolean {\n if (!this.config.dirtyTracking) return false;\n const internalGrid = this.#internalGrid;\n return this.#dirty.hasAnyDirty((rowId) => internalGrid._getRowEntry(rowId)?.row as T | undefined);\n }\n\n /** Whether all rows are pristine. */\n get pristine(): boolean {\n return !this.dirty;\n }\n\n /** Re-snapshot baseline from current data (call after a successful save). */\n markAsPristine(rowId: string): void {\n if (!this.config.dirtyTracking) return;\n const row = this.grid.getRow(rowId) as T | undefined;\n if (!row) return;\n this.#dirty.markPristine(rowId, row);\n this.emit<DirtyChangeDetail<T>>('dirty-change', {\n rowId,\n row,\n original: row, // after mark-pristine, original === current\n type: 'pristine',\n });\n }\n\n /** Mark a row as new (auto-called by `insertRow()` when dirty tracking is on). */\n markAsNew(rowId: string): void {\n if (!this.config.dirtyTracking) return;\n this.#dirty.markNew(rowId);\n const row = this.grid.getRow(rowId) as T | undefined;\n this.emit<DirtyChangeDetail<T>>('dirty-change', {\n rowId,\n row: row as T,\n original: undefined,\n type: 'new',\n });\n }\n\n /** Mark a row as dirty after an external mutation that bypassed the editing pipeline. */\n markAsDirty(rowId: string): void {\n if (!this.config.dirtyTracking) return;\n const row = this.grid.getRow(rowId) as T | undefined;\n if (!row) return;\n this.#dirty.markDirty(rowId);\n this.emit<DirtyChangeDetail<T>>('dirty-change', {\n rowId,\n row,\n original: this.#dirty.getOriginalRow(rowId),\n type: 'modified',\n });\n }\n\n /** Mark all tracked rows as pristine (call after a batch save). */\n markAllPristine(): void {\n if (!this.config.dirtyTracking) return;\n const internalGrid = this.#internalGrid;\n this.#dirty.markAllPristine((rowId) => internalGrid._getRowEntry(rowId)?.row as T | undefined);\n }\n\n /** Get a deep clone of the original (baseline) row data. Returns `undefined` for new rows. */\n getOriginalRow(rowId: string): T | undefined {\n if (!this.config.dirtyTracking) return undefined;\n return this.#dirty.getOriginalRow(rowId);\n }\n\n /** Lightweight check for whether a baseline exists (no cloning). */\n hasBaseline(rowId: string): boolean {\n if (!this.config.dirtyTracking) return false;\n return this.#dirty.hasBaseline(rowId);\n }\n\n /** Get all dirty rows with their original and current data. */\n getDirtyRows(): DirtyRowEntry<T>[] {\n if (!this.config.dirtyTracking) return [];\n const internalGrid = this.#internalGrid;\n return this.#dirty.getDirtyRows((rowId) => internalGrid._getRowEntry(rowId)?.row as T | undefined);\n }\n\n /**\n * Get IDs of all dirty rows.\n */\n get dirtyRowIds(): string[] {\n if (!this.config.dirtyTracking) return [];\n const internalGrid = this.#internalGrid;\n return this.#dirty.getDirtyRowIds((rowId) => internalGrid._getRowEntry(rowId)?.row as T | undefined);\n }\n\n /**\n * Revert a row to its baseline values (mutates the current row in-place).\n * Triggers a re-render.\n *\n * @param rowId - Row ID (from `getRowId`)\n */\n revertRow(rowId: string): void {\n if (!this.config.dirtyTracking) return;\n const row = this.grid.getRow(rowId) as T | undefined;\n if (!row) return;\n if (this.#dirty.revertRow(rowId, row)) {\n this.emit<DirtyChangeDetail<T>>('dirty-change', {\n rowId,\n row,\n original: this.#dirty.getOriginalRow(rowId),\n type: 'reverted',\n });\n this.requestRender();\n }\n }\n\n /**\n * Revert all dirty rows to their baseline values and re-render.\n */\n revertAll(): void {\n if (!this.config.dirtyTracking) return;\n const internalGrid = this.#internalGrid;\n this.#dirty.revertAll((rowId) => internalGrid._getRowEntry(rowId)?.row as T | undefined);\n this.requestRender();\n }\n\n // #endregion\n\n // #region Cell Validation (delegated to CellValidationManager)\n\n /**\n * Mark a cell as invalid with an optional validation message.\n * Invalid cells are marked with a `data-invalid` attribute for styling.\n *\n * @param rowId - The row ID (from getRowId)\n * @param field - The field name\n * @param message - Optional validation message (for tooltips or display)\n *\n * @example\n * ```typescript\n * // In cell-commit handler:\n * grid.on('cell-commit', (detail, e) => {\n * if (detail.field === 'email' && !isValidEmail(detail.value)) {\n * detail.setInvalid('Invalid email format');\n * }\n * });\n *\n * // Or programmatically:\n * editingPlugin.setInvalid('row-123', 'email', 'Invalid email format');\n * ```\n */\n setInvalid(rowId: string, field: string, message = ''): void {\n this.#validation.setInvalid(rowId, field, message);\n }\n\n /** Clear the invalid state for a specific cell. */\n clearInvalid(rowId: string, field: string): void {\n this.#validation.clearInvalid(rowId, field);\n }\n\n /** Clear all invalid cells for a specific row. */\n clearRowInvalid(rowId: string): void {\n this.#validation.clearRowInvalid(rowId);\n }\n\n /** Clear all invalid cell states across all rows. */\n clearAllInvalid(): void {\n this.#validation.clearAllInvalid();\n }\n\n /** Check if a specific cell is marked as invalid. */\n isCellInvalid(rowId: string, field: string): boolean {\n return this.#validation.isCellInvalid(rowId, field);\n }\n\n /** Get the validation message for an invalid cell. */\n getInvalidMessage(rowId: string, field: string): string | undefined {\n return this.#validation.getInvalidMessage(rowId, field);\n }\n\n /** Check if a row has any invalid cells. */\n hasInvalidCells(rowId: string): boolean {\n return this.#validation.hasInvalidCells(rowId);\n }\n\n /** Get all invalid fields for a row. */\n getInvalidFields(rowId: string): Map<string, string> {\n return this.#validation.getInvalidFields(rowId);\n }\n\n // #endregion\n\n /**\n * Reset all change tracking.\n * @param silent - If true, suppresses the `changed-rows-reset` event\n * @fires changed-rows-reset - Emitted when tracking is reset (unless silent)\n */\n resetChangedRows(silent?: boolean): void {\n const rows = this.changedRows;\n const ids = this.changedRowIds;\n this.#dirty.changedRowIds.clear();\n this.#dirty.committedDirtyRowIds.clear();\n this.#syncGridEditState();\n\n if (!silent) {\n this.emit<ChangedRowsResetDetail<T>>('changed-rows-reset', { rows, ids });\n }\n\n // Clear visual indicators\n const internalGrid = this.#internalGrid;\n internalGrid._rowPool?.forEach((r) => r.classList.remove('changed'));\n }\n\n /**\n * Programmatically begin editing a cell.\n * @param rowIndex - Index of the row to edit\n * @param field - Field name of the column to edit\n * @fires cell-commit - Emitted when the cell value is committed (on blur or Enter)\n */\n beginCellEdit(rowIndex: number, field: string): void {\n const internalGrid = this.#internalGrid;\n const colIndex = internalGrid._visibleColumns.findIndex((c) => c.field === field);\n if (colIndex === -1) return;\n\n const column = internalGrid._visibleColumns[colIndex];\n const rowData = internalGrid._rows[rowIndex];\n if (!column || !rowData || !this.#isCellEditable(column as ColumnConfig<T>, rowData as T)) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${colIndex}\"]`) as HTMLElement | null;\n if (!cellEl) return;\n\n this.#singleCellEdit = true;\n this.#beginCellEdit(rowIndex, colIndex, cellEl);\n }\n\n /**\n * Programmatically begin editing all editable cells in a row.\n * @param rowIndex - Index of the row to edit\n * @fires cell-commit - Emitted for each cell value that is committed\n * @fires row-commit - Emitted when focus leaves the row\n */\n beginBulkEdit(rowIndex: number): void {\n const internalGrid = this.#internalGrid;\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n if (editOn === false) return;\n\n const hasEditableColumn = internalGrid._columns?.some((col) => this.#hasEditableConfig(col as ColumnConfig<T>));\n if (!hasEditableColumn) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) return;\n\n // Row-level gate\n const rowData = internalGrid._rows[rowIndex];\n const rowEditable = internalGrid.effectiveConfig?.rowEditable;\n if (rowData && rowEditable && !rowEditable(rowData as any)) return;\n\n // Bulk edit clears single-cell mode\n this.#singleCellEdit = false;\n\n // Start row edit\n this.#startRowEdit(rowIndex, rowData);\n\n // Enter edit mode on all editable cells\n Array.from(rowEl.children).forEach((cell, i) => {\n const col = internalGrid._visibleColumns[i];\n if (col && this.#isCellEditable(col as ColumnConfig<T>, rowData as T)) {\n const cellEl = cell as HTMLElement;\n if (!cellEl.classList.contains('editing')) {\n this.#injectEditor(rowData, rowIndex, col, i, cellEl, true);\n }\n }\n });\n\n // Focus the first editable cell\n setTimeout(() => {\n let targetCell = rowEl.querySelector(`.cell[data-col=\"${internalGrid._focusCol}\"]`);\n if (!targetCell?.classList.contains('editing')) {\n targetCell = rowEl.querySelector('.cell.editing');\n }\n if (targetCell?.classList.contains('editing')) {\n const editor = (targetCell as HTMLElement).querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n try {\n editor?.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n }\n }, 0);\n }\n\n /**\n * Commit the currently active row edit.\n * @fires row-commit - Emitted after the row edit is committed\n */\n commitActiveRowEdit(): void {\n if (this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, false);\n }\n }\n\n /**\n * Cancel the currently active row edit.\n */\n cancelActiveRowEdit(): void {\n if (this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, true);\n }\n }\n\n // #endregion\n\n // #region Internal Methods\n\n /**\n * Sync the data-invalid attribute on a cell element.\n * Used as the DOM callback for CellValidationManager.\n */\n #syncInvalidCellAttribute(rowId: string, field: string, invalid: boolean): void {\n const internalGrid = this.#internalGrid;\n const colIndex = internalGrid._visibleColumns?.findIndex((c) => c.field === field);\n if (colIndex === -1 || colIndex === undefined) return;\n\n // Find the row element by rowId\n const rows = internalGrid._rows;\n const rowIndex = rows?.findIndex((r) => {\n try {\n return internalGrid.getRowId?.(r) === rowId;\n } catch {\n return false;\n }\n });\n if (rowIndex === -1 || rowIndex === undefined) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${colIndex}\"]`) as HTMLElement | null;\n if (!cellEl) return;\n\n if (invalid) {\n cellEl.setAttribute('data-invalid', 'true');\n const message = this.#validation.getInvalidMessage(rowId, field);\n if (message) {\n cellEl.setAttribute('title', message);\n }\n } else {\n cellEl.removeAttribute('data-invalid');\n cellEl.removeAttribute('title');\n }\n }\n\n /**\n * Migrate all index-keyed editing state when the active edit row moves to\n * a different position in `_rows` (e.g. after sort, filter, or new data push).\n *\n * Updates: `#activeEditRow`, `#editingCells`, `#rowEditSnapshots`,\n * `#editorValueCallbacks`, and syncs `_activeEditRows` on the grid.\n */\n #migrateEditRowIndex(oldIndex: number, newIndex: number): void {\n this.#activeEditRow = newIndex;\n\n // Migrate #editingCells keys (\"rowIndex:colIndex\")\n const migratedCells = new Set<string>();\n const prefix = `${oldIndex}:`;\n for (const cellKey of this.#editingCells) {\n if (cellKey.startsWith(prefix)) {\n migratedCells.add(`${newIndex}:${cellKey.substring(prefix.length)}`);\n } else {\n migratedCells.add(cellKey);\n }\n }\n this.#editingCells.clear();\n for (const key of migratedCells) {\n this.#editingCells.add(key);\n }\n\n // Migrate #rowEditSnapshots key\n const snapshot = this.#rowEditSnapshots.get(oldIndex);\n if (snapshot !== undefined) {\n this.#rowEditSnapshots.delete(oldIndex);\n this.#rowEditSnapshots.set(newIndex, snapshot);\n }\n\n // Migrate #editorValueCallbacks keys (\"rowIndex:field\")\n const updates: [string, (newValue: unknown) => void][] = [];\n for (const [key, cb] of this.#editorValueCallbacks) {\n if (key.startsWith(prefix)) {\n updates.push([`${newIndex}:${key.substring(prefix.length)}`, cb]);\n this.#editorValueCallbacks.delete(key);\n }\n }\n for (const [key, cb] of updates) {\n this.#editorValueCallbacks.set(key, cb);\n }\n\n // Sync the grid's rendering state so rows.ts checks the correct index\n this.#syncGridEditState();\n }\n\n /**\n * Begin editing a single cell.\n */\n #beginCellEdit(rowIndex: number, colIndex: number, cellEl: HTMLElement): void {\n const internalGrid = this.#internalGrid;\n const rowData = internalGrid._rows[rowIndex];\n const column = internalGrid._visibleColumns[colIndex];\n\n if (!rowData || !column || !this.#isCellEditable(column as ColumnConfig<T>, rowData as T)) return;\n if (cellEl.classList.contains('editing')) return;\n\n // Start row edit if not already\n if (this.#activeEditRow !== rowIndex) {\n this.#startRowEdit(rowIndex, rowData);\n }\n\n this.#activeEditCol = colIndex;\n this.#injectEditor(rowData, rowIndex, column, colIndex, cellEl, false);\n }\n\n /**\n * Revert the focused cell's value from the snapshot taken when the editor\n * first received focus, then emit a `cell-cancel` event so framework\n * adapters (e.g., GridFormArray) can revert their FormControls.\n *\n * Called by the grid-mode Escape handlers (both capture and bubble phase).\n */\n #revertGridModeCellEdit(): void {\n const snapshot = this.#gridModeCellSnapshot;\n if (!snapshot) return;\n\n const internalGrid = this.#internalGrid;\n const rowData = internalGrid._rows?.[snapshot.rowIndex];\n if (rowData) {\n (rowData as Record<string, unknown>)[snapshot.field] = snapshot.value;\n }\n\n // Push the reverted value to the editor's input element so that the\n // subsequent blur-commit (triggered by activeEl.blur()) reads the\n // reverted value instead of the user's typed text. This makes the\n // blur-commit a no-op (oldValue === newValue) and prevents it from\n // overwriting the revert.\n const callbackKey = `${snapshot.rowIndex}:${snapshot.field}`;\n const cb = this.#editorValueCallbacks.get(callbackKey);\n if (cb) cb(snapshot.value);\n\n // Notify framework adapters to revert FormControls\n this.emit<CellCancelDetail>('cell-cancel', {\n rowIndex: snapshot.rowIndex,\n colIndex: snapshot.colIndex,\n field: snapshot.field,\n previousValue: snapshot.value,\n });\n\n // After reverting, clean up changedRowIds so the built-in `.changed` class\n // toggle in rows.ts doesn't fight with consumer's rowClass callback.\n // changedRowIds.add() is unconditional during commit, so cleanup must also\n // be unconditional — otherwise the Set retains stale IDs when dirtyTracking\n // is disabled and the built-in toggle re-adds `.changed` on every render.\n if (rowData) {\n let rowId: string | undefined;\n try {\n rowId = this.grid.getRowId(rowData);\n } catch {\n // Row has no ID\n }\n if (rowId) {\n if (this.config.dirtyTracking) {\n // With dirty tracking, only remove if the row has no other dirty cells\n if (!this.#dirty.isRowDirty(rowId, rowData as T)) {\n this.#dirty.changedRowIds.delete(rowId);\n this.emit<DirtyChangeDetail<T>>('dirty-change', {\n rowId,\n row: rowData as T,\n original: this.#dirty.getOriginalRow(rowId),\n type: 'reverted',\n });\n }\n } else {\n // Without dirty tracking there's no baseline to compare, so always\n // remove — the consumer manages their own change state via rowClass.\n this.#dirty.changedRowIds.delete(rowId);\n }\n }\n }\n\n // Always re-render after revert so rowClass callbacks re-evaluate\n // (e.g., consumer may toggle a 'changed' class based on local state\n // updated in response to the cell-cancel event above).\n this.requestRender();\n\n this.#gridModeCellSnapshot = null;\n }\n\n /**\n * Focus the editor input in the currently focused cell (grid mode only).\n * Used when pressing Enter to enter edit mode from navigation mode.\n */\n #focusCurrentCellEditor(): void {\n const internalGrid = this.#internalGrid;\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n\n if (focusRow < 0 || focusCol < 0) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(focusRow);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${focusCol}\"]`) as HTMLElement | null;\n\n if (cellEl?.classList.contains('editing')) {\n const editor = cellEl.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n if (editor) {\n this.#gridModeEditLocked = false; // Unlock edit mode - user pressed Enter\n editor.focus();\n this.#gridModeInputFocused = true;\n // Select all text in text inputs for quick replacement\n if (editor instanceof HTMLInputElement && (editor.type === 'text' || editor.type === 'number')) {\n editor.select();\n }\n }\n }\n }\n\n /**\n * Handle Tab/Shift+Tab navigation while editing.\n * Moves to next/previous editable cell, staying in edit mode.\n * Wraps to next/previous row when reaching row boundaries.\n */\n #handleTabNavigation(forward: boolean): void {\n const internalGrid = this.#internalGrid;\n const rows = internalGrid._rows;\n // In grid mode, use focusRow since there's no active edit row\n const currentRow = this.#isGridMode ? internalGrid._focusRow : this.#activeEditRow;\n const currentRowData = rows[currentRow] as T;\n\n // Get editable column indices for the CURRENT row\n const editableCols = internalGrid._visibleColumns\n .map((c, i) => (currentRowData && this.#isCellEditable(c as ColumnConfig<T>, currentRowData) ? i : -1))\n .filter((i) => i >= 0);\n if (editableCols.length === 0) return;\n\n const currentIdx = editableCols.indexOf(internalGrid._focusCol);\n const nextIdx = currentIdx + (forward ? 1 : -1);\n\n // Can move within same row?\n if (nextIdx >= 0 && nextIdx < editableCols.length) {\n internalGrid._focusCol = editableCols[nextIdx];\n const rowEl = internalGrid.findRenderedRowElement?.(currentRow);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${editableCols[nextIdx]}\"]`) as HTMLElement | null;\n if (cellEl?.classList.contains('editing')) {\n const editor = cellEl.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n editor?.focus({ preventScroll: true });\n }\n ensureCellVisible(internalGrid, { forceHorizontalScroll: true });\n return;\n }\n\n // Can move to adjacent row?\n const nextRow = currentRow + (forward ? 1 : -1);\n if (nextRow >= 0 && nextRow < rows.length) {\n const nextRowData = rows[nextRow] as T;\n // Compute editable columns for the next row\n const nextEditableCols = internalGrid._visibleColumns\n .map((c, i) => (nextRowData && this.#isCellEditable(c as ColumnConfig<T>, nextRowData) ? i : -1))\n .filter((i) => i >= 0);\n if (nextEditableCols.length === 0) return; // Next row has no editable cells\n\n // In grid mode, just move focus (all rows are always editable)\n if (this.#isGridMode) {\n internalGrid._focusRow = nextRow;\n internalGrid._focusCol = forward ? nextEditableCols[0] : nextEditableCols[nextEditableCols.length - 1];\n ensureCellVisible(internalGrid, { forceHorizontalScroll: true });\n // Focus the editor in the new cell after render\n this.requestAfterRender();\n setTimeout(() => {\n const rowEl = internalGrid.findRenderedRowElement?.(nextRow);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${internalGrid._focusCol}\"]`) as HTMLElement | null;\n if (cellEl?.classList.contains('editing')) {\n const editor = cellEl.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n editor?.focus({ preventScroll: true });\n }\n }, 0);\n } else {\n // In row mode, commit current row and enter next row\n this.#exitRowEdit(currentRow, false);\n internalGrid._focusRow = nextRow;\n internalGrid._focusCol = forward ? nextEditableCols[0] : nextEditableCols[nextEditableCols.length - 1];\n this.beginBulkEdit(nextRow);\n ensureCellVisible(internalGrid, { forceHorizontalScroll: true });\n }\n }\n // else: at boundary - stay put\n }\n\n /**\n * Sync the internal grid state with the plugin's editing state.\n */\n #syncGridEditState(): void {\n const internalGrid = this.#internalGrid;\n internalGrid._activeEditRows = this.#activeEditRow;\n internalGrid._rowEditSnapshots = this.#rowEditSnapshots;\n }\n\n /**\n * Snapshot original row data and mark as editing.\n */\n #startRowEdit(rowIndex: number, rowData: T): void {\n if (this.#activeEditRow !== rowIndex) {\n // Commit the previous row before starting a new one\n if (this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, false);\n }\n this.#rowEditSnapshots.set(rowIndex, { ...rowData });\n this.#activeEditRow = rowIndex;\n this.#activeEditRowRef = rowData;\n\n // Store stable row ID for resilience against _rows replacement during editing\n const internalGrid = this.#internalGrid;\n try {\n this.#activeEditRowId = internalGrid.getRowId?.(rowData) ?? undefined;\n } catch {\n this.#activeEditRowId = undefined;\n }\n\n this.#syncGridEditState();\n\n // Emit edit-open event (row mode only)\n if (!this.#isGridMode) {\n this.emit<EditOpenDetail<T>>('edit-open', {\n rowIndex,\n rowId: this.#activeEditRowId ?? '',\n row: rowData,\n });\n }\n }\n }\n\n /**\n * Exit editing for a row.\n */\n #exitRowEdit(rowIndex: number, revert: boolean): void {\n if (this.#activeEditRow !== rowIndex) return;\n\n const internalGrid = this.#internalGrid;\n const snapshot = this.#rowEditSnapshots.get(rowIndex);\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n\n // Resolve the row being edited using the stored row ID.\n // The _rows array may have been replaced (e.g. Angular pushing new rows\n // via directive effect) since editing started, so _rows[rowIndex] could\n // point to a completely different row. The ID map is always up-to-date.\n // Without an ID we fall back to the stored row reference from edit-open\n // (#activeEditRowRef) — safer than _rows[rowIndex] which may be stale.\n let rowId = this.#activeEditRowId;\n const entry = rowId ? internalGrid._getRowEntry(rowId) : undefined;\n const current = entry?.row ?? this.#activeEditRowRef ?? internalGrid._rows[rowIndex];\n\n if (!rowId && current) {\n try {\n rowId = internalGrid.getRowId?.(current);\n } catch {\n // Row has no ID - skip ID-based tracking\n }\n }\n\n // Collect and commit values from active editors before re-rendering\n if (!revert && rowEl && current) {\n const editingCells = rowEl.querySelectorAll('.cell.editing');\n editingCells.forEach((cell) => {\n const colIndex = Number((cell as HTMLElement).getAttribute('data-col'));\n if (isNaN(colIndex)) return;\n const col = internalGrid._visibleColumns[colIndex];\n if (!col) return;\n\n // Skip cells with externally-managed editors (framework adapters like Angular/React/Vue).\n // These editors handle their own commits via the commit() callback - we should NOT\n // try to read values from their DOM inputs (which may contain formatted display values).\n if ((cell as HTMLElement).hasAttribute('data-editor-managed')) {\n return;\n }\n\n const input = cell.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (input) {\n const field = col.field as keyof T;\n const originalValue = current[field];\n const val = getInputValue(input, col, originalValue);\n if (originalValue !== val) {\n this.#commitCellValue(rowIndex, col, val, current);\n }\n }\n });\n }\n\n // Flush managed editors (framework adapters) before clearing state.\n // At this point the commit() callback is still active, so editors can\n // synchronously commit their pending values in response to this event.\n if (!revert && !this.#isGridMode && current) {\n this.emit<BeforeEditCloseDetail<T>>('before-edit-close', {\n rowIndex,\n rowId: rowId ?? '',\n row: current,\n });\n }\n\n // Revert if requested\n if (revert && snapshot && current) {\n Object.keys(snapshot as object).forEach((k) => {\n (current as Record<string, unknown>)[k] = (snapshot as Record<string, unknown>)[k];\n });\n if (rowId) {\n this.#dirty.changedRowIds.delete(rowId);\n this.#dirty.committedDirtyRowIds.delete(rowId);\n this.clearRowInvalid(rowId);\n }\n } else if (!revert && current) {\n // Compare snapshot vs current to detect if changes were made during THIS edit session\n const changedThisSession = hasRowChanged(snapshot, current);\n\n // Check if this row has any cumulative changes (via ID tracking)\n // Fall back to session-based detection when no row ID is available\n const changed = rowId ? this.#dirty.changedRowIds.has(rowId) : changedThisSession;\n\n // Emit cancelable row-commit event\n const cancelled = this.emitCancelable<RowCommitDetail<T>>('row-commit', {\n rowIndex,\n rowId: rowId ?? '',\n row: current,\n oldValue: snapshot,\n newValue: current,\n changed,\n changedRows: this.changedRows,\n changedRowIds: this.changedRowIds,\n });\n\n // If consumer called preventDefault(), revert the row\n if (cancelled && snapshot) {\n Object.keys(snapshot as object).forEach((k) => {\n (current as Record<string, unknown>)[k] = (snapshot as Record<string, unknown>)[k];\n });\n if (rowId) {\n this.#dirty.changedRowIds.delete(rowId);\n this.#dirty.committedDirtyRowIds.delete(rowId);\n this.clearRowInvalid(rowId);\n }\n } else if (!cancelled) {\n // Mark row as committed-dirty if it has actual changes vs baseline\n if (rowId && this.config.dirtyTracking) {\n if (this.#dirty.isRowDirty(rowId, current)) {\n this.#dirty.committedDirtyRowIds.add(rowId);\n } else {\n this.#dirty.committedDirtyRowIds.delete(rowId);\n }\n }\n\n if (changedThisSession && this.isAnimationEnabled) {\n // Animate the row only if changes were made during this edit session\n // (deferred to afterRender so the row element exists after re-render)\n this.#pendingRowAnimation = rowIndex;\n }\n }\n }\n\n // Clear editing state\n this.#rowEditSnapshots.delete(rowIndex);\n this.#activeEditRow = -1;\n this.#activeEditRowId = undefined;\n this.#activeEditRowRef = undefined;\n this.#activeEditCol = -1;\n this.#singleCellEdit = false;\n this.#syncGridEditState();\n\n // Remove all editing cells for this row.\n // Note: these keys use the rowIndex captured at edit-open time. Even if _rows\n // was replaced and the row moved to a different index, the keys still match\n // what was inserted during this edit session (same captured rowIndex).\n for (const cellKey of this.#editingCells) {\n if (cellKey.startsWith(`${rowIndex}:`)) {\n this.#editingCells.delete(cellKey);\n }\n }\n // Remove value-change callbacks for this row (same captured-index rationale)\n for (const callbackKey of this.#editorValueCallbacks.keys()) {\n if (callbackKey.startsWith(`${rowIndex}:`)) {\n this.#editorValueCallbacks.delete(callbackKey);\n }\n }\n\n // Mark that focus should be restored after the upcoming render completes.\n // This must be set BEFORE refreshVirtualWindow because it calls afterRender()\n // synchronously, which reads this flag.\n this.#pendingFocusRestore = true;\n\n // Re-render the row to remove editors\n if (rowEl) {\n // Remove editing class and re-render cells\n rowEl.querySelectorAll('.cell.editing').forEach((cell) => {\n cell.classList.remove('editing');\n clearEditingState(cell.parentElement as RowElementInternal);\n });\n\n // Refresh the virtual window to restore cell content WITHOUT rebuilding\n // the row model. requestRender() would trigger processRows (ROWS phase)\n // which re-sorts — causing the edited row to jump to a new position and\n // disappear from view. refreshVirtualWindow re-renders visible cells from\n // the current _rows order, keeping the row in place until the user\n // explicitly sorts again or new data arrives.\n internalGrid.refreshVirtualWindow(true);\n } else {\n // Row not visible - restore focus immediately (no render will happen)\n this.#restoreCellFocus(internalGrid);\n this.#pendingFocusRestore = false;\n }\n\n // Emit edit-close event (row mode only, fires for both commit and revert)\n if (!this.#isGridMode && current) {\n this.emit<EditCloseDetail<T>>('edit-close', {\n rowIndex,\n rowId: rowId ?? '',\n row: current,\n reverted: revert,\n });\n }\n }\n\n /**\n * Commit a single cell value change.\n * Uses ID-based change tracking for stability when rows are reordered.\n */\n #commitCellValue(rowIndex: number, column: ColumnConfig<T>, newValue: unknown, rowData: T): void {\n const field = column.field;\n if (!isSafePropertyKey(field)) return;\n const oldValue = (rowData as Record<string, unknown>)[field];\n if (oldValue === newValue) return;\n\n const internalGrid = this.#internalGrid;\n\n // Get row ID for change tracking (may not exist if getRowId not configured)\n let rowId: string | undefined;\n try {\n rowId = this.grid.getRowId(rowData);\n } catch {\n // Row has no ID - will still work but won't be tracked in changedRowIds\n }\n\n const firstTime = rowId ? !this.#dirty.changedRowIds.has(rowId) : true;\n\n // Create updateRow helper for cascade updates (noop if row has no ID)\n const updateRow: (changes: Partial<T>) => void = rowId\n ? (changes) => this.grid.updateRow(rowId!, changes as Record<string, unknown>, 'cascade')\n : noopUpdateRow;\n\n // Track whether setInvalid was called during event handling\n let invalidWasSet = false;\n\n // Create setInvalid callback for validation (noop if row has no ID)\n const setInvalid = rowId\n ? (message?: string) => {\n invalidWasSet = true;\n this.setInvalid(rowId!, field, message ?? '');\n }\n : () => {}; // eslint-disable-line @typescript-eslint/no-empty-function\n\n // Emit cancelable event BEFORE applying the value\n const cancelled = this.emitCancelable<CellCommitDetail<T>>('cell-commit', {\n row: rowData,\n rowId: rowId ?? '',\n field,\n oldValue,\n value: newValue,\n rowIndex,\n changedRows: this.changedRows,\n changedRowIds: this.changedRowIds,\n firstTimeForRow: firstTime,\n updateRow,\n setInvalid,\n });\n\n // If consumer called preventDefault(), abort the commit\n if (cancelled) return;\n\n // Clear any previous invalid state for this cell ONLY if setInvalid wasn't called\n // (if setInvalid was called, the handler wants it to remain invalid)\n if (rowId && !invalidWasSet && this.isCellInvalid(rowId, field)) {\n this.clearInvalid(rowId, field);\n }\n\n // Apply the value and mark row as changed\n (rowData as Record<string, unknown>)[field] = newValue;\n if (rowId) {\n this.#dirty.changedRowIds.add(rowId);\n }\n this.#syncGridEditState();\n\n // Emit dirty-change event if dirty tracking is enabled\n if (this.config.dirtyTracking && rowId) {\n const dirty = this.#dirty.isRowDirty(rowId, rowData);\n this.emit<DirtyChangeDetail<T>>('dirty-change', {\n rowId,\n row: rowData,\n original: this.#dirty.getOriginalRow(rowId),\n type: dirty ? 'modified' : 'pristine',\n });\n }\n\n // Notify other plugins (e.g., UndoRedoPlugin) about the committed edit\n this.emitPluginEvent('cell-edit-committed', {\n rowIndex,\n field,\n oldValue,\n newValue,\n });\n\n // Mark the row visually as changed (animation happens when row edit closes)\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (rowEl) {\n rowEl.classList.add('changed');\n }\n }\n\n /**\n * Inject an editor into a cell.\n * Delegates to the extracted `editor-injection` module.\n */\n #injectEditor(\n rowData: T,\n rowIndex: number,\n column: ColumnConfig<T>,\n colIndex: number,\n cell: HTMLElement,\n skipFocus: boolean,\n parentRowEl?: HTMLElement,\n ): void {\n injectEditorImpl(this.#editorDeps, rowData, rowIndex, column, colIndex, cell, skipFocus, parentRowEl);\n }\n\n /**\n * Restore focus to cell after exiting edit mode.\n */\n #restoreCellFocus(internalGrid: InternalGrid<T>): void {\n queueMicrotask(() => {\n try {\n const rowIdx = internalGrid._focusRow;\n const colIdx = internalGrid._focusCol;\n const rowEl = internalGrid.findRenderedRowElement?.(rowIdx);\n if (rowEl) {\n Array.from(internalGrid._bodyEl.querySelectorAll('.cell-focus')).forEach((el) =>\n el.classList.remove('cell-focus'),\n );\n const cell = rowEl.querySelector(`.cell[data-row=\"${rowIdx}\"][data-col=\"${colIdx}\"]`) as HTMLElement | null;\n if (cell) {\n cell.classList.add('cell-focus');\n cell.setAttribute('aria-selected', 'true');\n if (!cell.hasAttribute('tabindex')) cell.setAttribute('tabindex', '-1');\n cell.focus({ preventScroll: true });\n }\n }\n } catch {\n /* empty */\n }\n });\n }\n\n // #endregion\n}\n"],"names":["createDateEditor","column","ctx","params","editorParams","input","document","createElement","type","value","Date","valueAsDate","split","min","max","placeholder","addEventListener","date","commit","nullable","fallback","default","getFullYear","String","getMonth","padStart","getDate","e","key","cancel","NULLABLE_BLANK_VALUE","defaultEditorFor","step","Number","createNumberEditor","checked","select","multi","multiple","includeEmpty","emptyOpt","textContent","emptyLabel","selected","appendChild","options","raw","resolveOptions","forEach","opt","o","label","Array","isArray","includes","values","from","selectedOptions","map","createSelectEditor","maxLength","pattern","inputVal","replace","createTextEditor","getInputValue","originalValue","HTMLInputElement","toISOString","slice","CellValidationManager","cells","Map","syncAttribute","constructor","this","setInvalid","rowId","field","message","rowInvalids","get","set","clearInvalid","delete","size","clearRowInvalid","fields","keys","clearAllInvalid","entries","clear","_","isCellInvalid","has","getInvalidMessage","hasInvalidCells","getInvalidFields","isRowDirty","baselines","currentRow","baseline","deepEqual","a","b","getTime","length","i","aObj","bObj","keysA","Object","keysB","markPristine","structuredClone","revertToBaseline","baselineObj","currentObj","DirtyTrackingManager","baselinesWereCaptured","newRowIds","Set","changedRowIds","committedDirtyRowIds","capture","rows","getRowId","sizeBefore","row","id","captureBaselines","drainCapturedFlag","hasAnyDirty","resolveRow","isCellDirty","getRowDirtyState","isNew","isCommittedDirty","hasBaseline","markNew","add","markDirty","markAllPristine","getOriginalRow","getDirtyRows","result","push","original","current","newId","getDirtyRowIds","ids","revertRow","reverted","revertAll","getChangedRows","getChangedRowIds","isRowChanged","FOCUSABLE_EDITOR_SELECTOR","isSafePropertyKey","noopUpdateRow","_changes","shouldPreventEditClose","config","event","onBeforeEditClose","injectEditor","deps","rowData","rowIndex","colIndex","cell","skipFocus","parentRowEl","editable","classList","contains","grid","isGridMode","editingCells","editorValueCallbacks","updateRow","changes","rowEl","parentElement","count","__editingCellCount","setAttribute","editFinalized","newValue","isEditSessionActive","entry","_getRowEntry","currentRowData","currentIndex","index","commitCellValue","editorHost","className","innerHTML","stopPropagation","preventDefault","querySelector","exitRowEdit","colInternal","tplHolder","__editorTemplate","editorSpec","col","editor","gridTypeDefaults","effectiveConfig","typeDefaults","adapter","__frameworkAdapter","getTypeDefault","appDefault","resolveEditor","callbackKey","callbacks","newVal","cb","onValueChange","clone","cloneNode","compiledEditor","__compiledEditor","querySelectorAll","node","childNodes","firstChild","nodeType","Node","TEXT_NODE","_m","g","v","evt","setTimeout","focus","preventScroll","renderTemplateEditor","el","queueMicrotask","focusable","produced","HTMLSelectElement","wireEditorInputs","HTMLTextAreaElement","hasChildNodes","context","mount","spec","warnDiagnostic","EDITOR_MOUNT_ERROR","dispatchEvent","CustomEvent","detail","EditingPlugin","BaseGridPlugin","static","ownedProperties","property","level","description","isUsed","events","queries","name","styles","defaultConfig","mode","editOn","isCellEditable","rowEditable","internalGrid","hasEditableConfig","activeEditRow","activeEditRowId","activeEditRowRef","activeEditCol","rowEditSnapshots","pendingFocusRestore","pendingRowAnimation","validation","gridModeInputFocused","gridModeEditLocked","singleCellEdit","gridModeCellSnapshot","dirty","editorDeps","attach","super","signal","disconnectSignal","invalid","syncInvalidCellAttribute","ri","val","revert","_activeEditRows","_rowEditSnapshots","defineProperty","changedRows","configurable","resetChangedRows","silent","beginBulkEdit","beginCellEdit","findRenderedRowElement","composedPath","target","gridElement","containsFocus","focusTrap","related","relatedTarget","focusCurrentCellEditor","source","dirtyTracking","handleUndoRedo","action","emit","on","markAsNew","_isGridEditMode","requestRender","matches","blur","focusRow","_focusRow","focusCol","_focusCol","snap","_visibleColumns","_rows","revertGridModeCellEdit","activeEl","activeElement","detach","remove","handleQuery","query","onCellClick","isDoubleClick","originalEvent","hasEditableColumn","_columns","some","onKeyDown","requestAfterRender","maxRow","Math","ensureCellVisible","forward","shiftKey","handleTabNavigation","ctrlKey","altKey","metaKey","rowBlocked","cellEl","activateEvent","cancelable","bubbles","trigger","legacyEvent","defaultPrevented","processColumns","columns","typeEditorParams","processRows","r","editRowId","editRowRef","newIndex","cancelActiveRowEdit","migrateEditRowIndex","afterRender","indexOf","restoreCellFocus","animateRow","capturedCount","cellKey","rowStr","colStr","parseInt","afterCellRender","cellElement","removeAttribute","rowElement","afterRowRender","toggle","getAttribute","dirtyCells","onScrollRender","getRow","isRowEditing","isCellEditing","isRowChangedById","isDirty","isPristine","pristine","markAsPristine","markAsDirty","dirtyRowIds","syncGridEditState","_rowPool","findIndex","c","startRowEdit","children","targetCell","commitActiveRowEdit","oldIndex","migratedCells","prefix","startsWith","substring","snapshot","updates","previousValue","editableCols","filter","nextIdx","forceHorizontalScroll","nextRow","nextRowData","nextEditableCols","isNaN","hasAttribute","k","changedThisSession","snapshotObj","allKeys","hasRowChanged","changed","cancelled","emitCancelable","oldValue","isAnimationEnabled","clearEditingState","refreshVirtualWindow","firstTime","invalidWasSet","firstTimeForRow","emitPluginEvent","injectEditorImpl","rowIdx","colIdx","_bodyEl"],"mappings":"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"}