@toolbox-web/grid 1.1.2 → 1.3.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.
- package/README.md +80 -22
- package/all.d.ts +1 -0
- package/all.d.ts.map +1 -1
- package/all.js +557 -365
- package/all.js.map +1 -1
- package/index.d.ts +1 -1
- package/index.d.ts.map +1 -1
- package/index.js +903 -769
- package/index.js.map +1 -1
- package/lib/core/grid.d.ts +102 -3
- package/lib/core/grid.d.ts.map +1 -1
- package/lib/core/internal/row-animation.d.ts +37 -0
- package/lib/core/internal/row-animation.d.ts.map +1 -0
- package/lib/core/internal/rows.d.ts.map +1 -1
- package/lib/core/internal/shell.d.ts.map +1 -1
- package/lib/core/plugin/base-plugin.d.ts +65 -3
- package/lib/core/plugin/base-plugin.d.ts.map +1 -1
- package/lib/core/plugin/index.d.ts +1 -1
- package/lib/core/plugin/index.d.ts.map +1 -1
- package/lib/core/plugin/plugin-manager.d.ts +25 -1
- package/lib/core/plugin/plugin-manager.d.ts.map +1 -1
- package/lib/core/plugin/types.d.ts +62 -0
- package/lib/core/plugin/types.d.ts.map +1 -1
- package/lib/core/types.d.ts +64 -1
- package/lib/core/types.d.ts.map +1 -1
- package/lib/plugins/clipboard/index.js +73 -69
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/clipboard/types.d.ts +1 -0
- package/lib/plugins/clipboard/types.d.ts.map +1 -1
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
- package/lib/plugins/editing/index.js +69 -40
- package/lib/plugins/editing/index.js.map +1 -1
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/MasterDetailPlugin.d.ts.map +1 -1
- package/lib/plugins/master-detail/index.js +14 -12
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/reorder/index.js.map +1 -1
- package/lib/plugins/responsive/index.js.map +1 -1
- package/lib/plugins/row-reorder/RowReorderPlugin.d.ts +155 -0
- package/lib/plugins/row-reorder/RowReorderPlugin.d.ts.map +1 -0
- package/lib/plugins/row-reorder/index.d.ts +9 -0
- package/lib/plugins/row-reorder/index.d.ts.map +1 -0
- package/lib/plugins/row-reorder/index.js +597 -0
- package/lib/plugins/row-reorder/index.js.map +1 -0
- package/lib/plugins/row-reorder/types.d.ts +80 -0
- package/lib/plugins/row-reorder/types.d.ts.map +1 -0
- package/lib/plugins/selection/SelectionPlugin.d.ts +13 -0
- package/lib/plugins/selection/SelectionPlugin.d.ts.map +1 -1
- package/lib/plugins/selection/index.d.ts +1 -1
- package/lib/plugins/selection/index.d.ts.map +1 -1
- package/lib/plugins/selection/index.js +95 -64
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/selection/types.d.ts +50 -6
- package/lib/plugins/selection/types.d.ts.map +1 -1
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/index.js.map +1 -1
- package/package.json +21 -4
- package/public.d.ts +15 -2
- package/public.d.ts.map +1 -1
- package/umd/grid.all.umd.js +23 -23
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +15 -15
- package/umd/grid.umd.js.map +1 -1
- package/umd/plugins/clipboard.umd.js +5 -5
- package/umd/plugins/clipboard.umd.js.map +1 -1
- package/umd/plugins/editing.umd.js +1 -1
- package/umd/plugins/editing.umd.js.map +1 -1
- package/umd/plugins/master-detail.umd.js +1 -1
- package/umd/plugins/master-detail.umd.js.map +1 -1
- package/umd/plugins/row-reorder.umd.js +2 -0
- package/umd/plugins/row-reorder.umd.js.map +1 -0
- package/umd/plugins/selection.umd.js +2 -2
- package/umd/plugins/selection.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/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, EditorContext } 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// ============================================================================\n// Editor Factories\n// ============================================================================\n\n/** Creates a number input editor */\nfunction createNumberEditor(column: AnyColumn): (ctx: EditorContext) => 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 = () => ctx.commit(input.value === '' ? null : Number(input.value));\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: EditorContext) => 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: EditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as DateEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'date';\n\n if (ctx.value instanceof Date) input.valueAsDate = ctx.value;\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 input.addEventListener('change', () => ctx.commit(input.valueAsDate));\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return input;\n };\n}\n\n/** Creates a select dropdown editor */\nfunction createSelectEditor(column: AnyColumn): (ctx: EditorContext) => 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 empty option if requested\n if (params?.includeEmpty) {\n const emptyOpt = document.createElement('option');\n emptyOpt.value = '';\n emptyOpt.textContent = params.emptyLabel ?? '';\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 {\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: EditorContext) => 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 input.addEventListener('blur', () => ctx.commit(input.value));\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') ctx.commit(input.value);\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: EditorContext) => 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 * Gets the current value from an input element, with type coercion based on column type.\n */\nexport function getInputValue(\n input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n col: AnyColumn,\n): unknown {\n if (input instanceof HTMLSelectElement) {\n if (col.multi) {\n return Array.from(input.selectedOptions).map((o) => o.value);\n }\n return input.value;\n }\n if (input instanceof HTMLInputElement) {\n if (input.type === 'checkbox') return input.checked;\n if (input.type === 'number') return input.value === '' ? null : Number(input.value);\n if (input.type === 'date') return input.valueAsDate;\n }\n return input.value;\n}\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 type { PluginManifest } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin, type CellClickEvent, type GridElement } from '../../core/plugin/base-plugin';\nimport type {\n ColumnConfig,\n ColumnEditorSpec,\n ColumnInternal,\n InternalGrid,\n RowElementInternal,\n} from '../../core/types';\nimport styles from './editing.css?inline';\nimport { defaultEditorFor } from './editors';\nimport type { CellCommitDetail, ChangedRowsResetDetail, EditingConfig, EditorContext, RowCommitDetail } from './types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * CSS selector for focusable editor elements within a cell.\n */\nexport const FOCUSABLE_EDITOR_SELECTOR =\n 'input,select,textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[tabindex]:not([tabindex=\"-1\"])';\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\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 */\nfunction 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 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/**\n * Returns true if the given property key is safe to use on a plain object.\n */\nfunction 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/**\n * Check if a row element has any cells in editing mode.\n */\nexport function hasEditingCells(rowEl: RowElementInternal): boolean {\n return (rowEl.__editingCellCount ?? 0) > 0;\n}\n\n/**\n * Increment the editing cell count on a row element.\n */\nfunction 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/**\n * Get the typed value from an input element based on its type and column config.\n */\nfunction getInputValue(\n input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n column?: ColumnConfig<any>,\n): unknown {\n if (input instanceof HTMLInputElement) {\n if (input.type === 'checkbox') return input.checked;\n if (input.type === 'number') return input.value === '' ? null : Number(input.value);\n if (input.type === 'date') return input.valueAsDate;\n return input.value;\n }\n if (column?.type === 'number' && input.value !== '') {\n return Number(input.value);\n }\n return input.value;\n}\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\nfunction noopUpdateRow(_changes: unknown): void {\n // Row has no ID - cannot update\n}\n\n/**\n * Auto-wire commit/cancel lifecycle for input elements in string-returned editors.\n */\nfunction wireEditorInputs(\n editorHost: HTMLElement,\n column: ColumnConfig<unknown>,\n commit: (value: unknown) => void,\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));\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)));\n }\n}\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.addEventListener('cell-commit', (e) => {\n * const { field, oldValue, newValue } = e.detail;\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,\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 };\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 editOn: 'click',\n };\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 /** 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 row IDs that have been modified (ID-based for stability) */\n #changedRowIds = new Set<string>();\n\n /** Set of cells currently in edit mode: \"rowIndex:colIndex\" */\n #editingCells = new Set<string>();\n\n /** Flag to restore focus after next render (used when exiting edit mode) */\n #pendingFocusRestore = false;\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 = grid as unknown as InternalGrid<T>;\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 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\n document.addEventListener(\n 'keydown',\n (e: KeyboardEvent) => {\n if (e.key === 'Escape' && this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, true);\n }\n },\n { capture: true, signal },\n );\n\n // Click outside to commit editing\n document.addEventListener(\n 'mousedown',\n (e: MouseEvent) => {\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 this.#exitRowEdit(this.#activeEditRow, false);\n },\n { signal },\n );\n }\n\n /** @internal */\n override detach(): void {\n this.#activeEditRow = -1;\n this.#activeEditCol = -1;\n this.#rowEditSnapshots.clear();\n this.#changedRowIds.clear();\n this.#editingCells.clear();\n super.detach();\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 const internalGrid = this.grid as unknown as InternalGrid<T>;\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 editable\n const hasEditableColumn = internalGrid._columns?.some((col) => col.editable);\n if (!hasEditableColumn) 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.grid as unknown as InternalGrid<T>;\n\n // Escape: cancel current edit\n if (event.key === 'Escape' && this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, true);\n return true;\n }\n\n // Space: toggle boolean cells\n if (event.key === ' ' || event.key === 'Spacebar') {\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?.editable && column.type === 'boolean' && rowData) {\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: start row edit or commit\n if (event.key === 'Enter' && !event.shiftKey) {\n if (this.#activeEditRow !== -1) {\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 editable\n const hasEditableColumn = internalGrid._columns?.some((col) => col.editable);\n if (hasEditableColumn) {\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 // 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.grid as unknown as InternalGrid<T>;\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 * 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.grid as unknown as InternalGrid<T>;\n\n // Restore focus after exiting edit mode\n if (this.#pendingFocusRestore) {\n this.#pendingFocusRestore = false;\n this.#restoreCellFocus(internalGrid);\n }\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 * 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 const rows: T[] = [];\n for (const id of this.#changedRowIds) {\n const row = this.grid.getRow(id) as T | undefined;\n if (row) rows.push(row);\n }\n return rows;\n }\n\n /**\n * Get IDs of all modified rows.\n */\n get changedRowIds(): string[] {\n return Array.from(this.#changedRowIds);\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.grid as unknown as InternalGrid<T>;\n const row = internalGrid._rows[rowIndex];\n if (!row) return false;\n try {\n const rowId = internalGrid.getRowId?.(row);\n return rowId ? this.#changedRowIds.has(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.#changedRowIds.has(rowId);\n }\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.#changedRowIds.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.grid as unknown as InternalGrid<T>;\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.grid as unknown as InternalGrid<T>;\n const colIndex = internalGrid._visibleColumns.findIndex((c) => c.field === field);\n if (colIndex === -1) return;\n\n const column = internalGrid._visibleColumns[colIndex];\n if (!column?.editable) 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.#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.grid as unknown as InternalGrid<T>;\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n if (editOn === false) return;\n\n const hasEditableColumn = internalGrid._columns?.some((col) => col.editable);\n if (!hasEditableColumn) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) return;\n\n // Start row edit\n const rowData = internalGrid._rows[rowIndex];\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?.editable) {\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 * Begin editing a single cell.\n */\n #beginCellEdit(rowIndex: number, colIndex: number, cellEl: HTMLElement): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const rowData = internalGrid._rows[rowIndex];\n const column = internalGrid._visibleColumns[colIndex];\n\n if (!rowData || !column?.editable) 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 * Sync the internal grid state with the plugin's editing state.\n */\n #syncGridEditState(): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\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 this.#rowEditSnapshots.set(rowIndex, { ...rowData });\n this.#activeEditRow = rowIndex;\n this.#syncGridEditState();\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.grid as unknown as InternalGrid<T>;\n const snapshot = this.#rowEditSnapshots.get(rowIndex);\n const current = internalGrid._rows[rowIndex];\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n\n // Get row ID for change tracking\n let rowId: string | undefined;\n if (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 const input = cell.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (input) {\n const val = getInputValue(input, col);\n if (current[col.field as keyof T] !== val) {\n this.#commitCellValue(rowIndex, col, val, current);\n }\n }\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.#changedRowIds.delete(rowId);\n }\n } else if (!revert && current) {\n const changed = rowId ? this.#changedRowIds.has(rowId) : false;\n this.emit<RowCommitDetail<T>>('row-commit', {\n rowIndex,\n rowId: rowId ?? '',\n row: current,\n changed,\n changedRows: this.changedRows,\n changedRowIds: this.changedRowIds,\n });\n }\n\n // Clear editing state\n this.#rowEditSnapshots.delete(rowIndex);\n this.#activeEditRow = -1;\n this.#activeEditCol = -1;\n this.#syncGridEditState();\n\n // Remove all editing cells for this row\n for (const cellKey of this.#editingCells) {\n if (cellKey.startsWith(`${rowIndex}:`)) {\n this.#editingCells.delete(cellKey);\n }\n }\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 // Request grid re-render to restore cell content\n this.requestRender();\n }\n\n // Mark that focus should be restored after render completes\n this.#pendingFocusRestore = true;\n\n // If no render was scheduled (row not visible), restore focus immediately\n if (!rowEl) {\n this.#restoreCellFocus(internalGrid);\n this.#pendingFocusRestore = false;\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.grid as unknown as InternalGrid<T>;\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.#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 // 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 });\n\n // If consumer called preventDefault(), abort the commit\n if (cancelled) return;\n\n // Apply the value and mark row as changed\n (rowData as Record<string, unknown>)[field] = newValue;\n if (rowId) {\n this.#changedRowIds.add(rowId);\n }\n this.#syncGridEditState();\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (rowEl) rowEl.classList.add('changed');\n }\n\n /**\n * Inject an editor into a cell.\n */\n #injectEditor(\n rowData: T,\n rowIndex: number,\n column: ColumnConfig<T>,\n colIndex: number,\n cell: HTMLElement,\n skipFocus: boolean,\n ): void {\n if (!column.editable) return;\n if (cell.classList.contains('editing')) return;\n\n // Get row ID for updateRow helper (may not exist)\n let rowId: string | undefined;\n try {\n rowId = this.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 ? (changes) => this.grid.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 this.#editingCells.add(`${rowIndex}:${colIndex}`);\n\n const rowEl = cell.parentElement as RowElementInternal | null;\n if (rowEl) incrementEditingCount(rowEl);\n\n let editFinalized = false;\n const commit = (newValue: unknown) => {\n if (editFinalized || this.#activeEditRow === -1) return;\n this.#commitCellValue(rowIndex, column, newValue, rowData);\n };\n const cancel = () => {\n editFinalized = true;\n if (isSafePropertyKey(column.field)) {\n (rowData 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 e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n this.#exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n e.stopPropagation();\n e.preventDefault();\n cancel();\n this.#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(this.grid as unknown as InternalGrid<T>, colInternal) ?? defaultEditorFor(column);\n const value = originalValue;\n\n if (editorSpec === 'template' && tplHolder) {\n this.#renderTemplateEditor(editorHost, colInternal, rowData, originalValue, commit, cancel, skipFocus, rowIndex);\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 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 };\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);\n } else if (produced instanceof Node) {\n editorHost.appendChild(produced);\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 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 };\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 console.warn(`[tbw-grid] External editor mount error for column '${column.field}':`, e);\n }\n } else {\n (this.grid as unknown as HTMLElement).dispatchEvent(\n new CustomEvent('mount-external-editor', { detail: { placeholder, spec: editorSpec, context } }),\n );\n }\n }\n }\n\n /**\n * Render a template-based editor.\n */\n #renderTemplateEditor(\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));\n });\n input.addEventListener('keydown', (evt) => {\n const e = evt as KeyboardEvent;\n if (e.key === 'Enter') {\n e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n commit(getInputValue(input, column));\n this.#exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n e.stopPropagation();\n e.preventDefault();\n cancel();\n this.#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 /**\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":["resolveOptions","column","raw","createNumberEditor","ctx","params","input","commit","e","createBooleanEditor","createDateEditor","createSelectEditor","select","emptyOpt","opt","o","values","createTextEditor","defaultEditorFor","FOCUSABLE_EDITOR_SELECTOR","resolveEditor","grid","col","gridTypeDefaults","adapter","appDefault","isSafePropertyKey","key","incrementEditingCount","rowEl","count","clearEditingState","getInputValue","noopUpdateRow","_changes","wireEditorInputs","editorHost","EditingPlugin","BaseGridPlugin","v","styles","#activeEditRow","#activeEditCol","#rowEditSnapshots","#changedRowIds","#editingCells","#pendingFocusRestore","signal","internalGrid","silent","rowIndex","field","#exitRowEdit","event","editOn","isDoubleClick","focusRow","focusCol","rowData","newValue","#commitCellValue","row","value","cellEl","activateEvent","legacyEvent","columns","typeDefaults","typeEditorParams","#restoreCellFocus","cellKey","rowStr","colStr","colIndex","#injectEditor","rows","id","rowId","ids","#syncGridEditState","r","c","#beginCellEdit","#startRowEdit","cell","i","targetCell","editor","revert","snapshot","current","val","k","changed","oldValue","firstTime","updateRow","changes","skipFocus","originalValue","editFinalized","cancel","colInternal","tplHolder","editorSpec","#renderTemplateEditor","el","produced","placeholder","context","clone","compiledEditor","node","_m","g","evt","rowIdx","colIdx"],"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/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, EditorContext } 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// ============================================================================\n// Editor Factories\n// ============================================================================\n\n/** Creates a number input editor */\nfunction createNumberEditor(column: AnyColumn): (ctx: EditorContext) => 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 = () => ctx.commit(input.value === '' ? null : Number(input.value));\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: EditorContext) => 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: EditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as DateEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'date';\n\n if (ctx.value instanceof Date) input.valueAsDate = ctx.value;\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 input.addEventListener('change', () => ctx.commit(input.valueAsDate));\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return input;\n };\n}\n\n/** Creates a select dropdown editor */\nfunction createSelectEditor(column: AnyColumn): (ctx: EditorContext) => 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 empty option if requested\n if (params?.includeEmpty) {\n const emptyOpt = document.createElement('option');\n emptyOpt.value = '';\n emptyOpt.textContent = params.emptyLabel ?? '';\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 {\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: EditorContext) => 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 input.addEventListener('blur', () => ctx.commit(input.value));\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') ctx.commit(input.value);\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: EditorContext) => 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 * Gets the current value from an input element, with type coercion based on column type.\n */\nexport function getInputValue(\n input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n col: AnyColumn,\n): unknown {\n if (input instanceof HTMLSelectElement) {\n if (col.multi) {\n return Array.from(input.selectedOptions).map((o) => o.value);\n }\n return input.value;\n }\n if (input instanceof HTMLInputElement) {\n if (input.type === 'checkbox') return input.checked;\n if (input.type === 'number') return input.value === '' ? null : Number(input.value);\n if (input.type === 'date') return input.valueAsDate;\n }\n return input.value;\n}\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 { animateRowElement } from '../../core/internal/row-animation';\nimport type { PluginManifest } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin, type CellClickEvent, type GridElement } from '../../core/plugin/base-plugin';\nimport type {\n ColumnConfig,\n ColumnEditorSpec,\n ColumnInternal,\n InternalGrid,\n RowElementInternal,\n} from '../../core/types';\nimport styles from './editing.css?inline';\nimport { defaultEditorFor } from './editors';\nimport type { CellCommitDetail, ChangedRowsResetDetail, EditingConfig, EditorContext, RowCommitDetail } from './types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * CSS selector for focusable editor elements within a cell.\n */\nexport const FOCUSABLE_EDITOR_SELECTOR =\n 'input,select,textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[tabindex]:not([tabindex=\"-1\"])';\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\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 */\nfunction 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 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/**\n * Returns true if the given property key is safe to use on a plain object.\n */\nfunction 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/**\n * Check if a row element has any cells in editing mode.\n */\nexport function hasEditingCells(rowEl: RowElementInternal): boolean {\n return (rowEl.__editingCellCount ?? 0) > 0;\n}\n\n/**\n * Increment the editing cell count on a row element.\n */\nfunction 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/**\n * Get the typed value from an input element based on its type and column config.\n */\nfunction getInputValue(\n input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n column?: ColumnConfig<any>,\n): unknown {\n if (input instanceof HTMLInputElement) {\n if (input.type === 'checkbox') return input.checked;\n if (input.type === 'number') return input.value === '' ? null : Number(input.value);\n if (input.type === 'date') return input.valueAsDate;\n return input.value;\n }\n if (column?.type === 'number' && input.value !== '') {\n return Number(input.value);\n }\n return input.value;\n}\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\nfunction noopUpdateRow(_changes: unknown): void {\n // Row has no ID - cannot update\n}\n\n/**\n * Auto-wire commit/cancel lifecycle for input elements in string-returned editors.\n */\nfunction wireEditorInputs(\n editorHost: HTMLElement,\n column: ColumnConfig<unknown>,\n commit: (value: unknown) => void,\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));\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)));\n }\n}\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.addEventListener('cell-commit', (e) => {\n * const { field, oldValue, newValue } = e.detail;\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,\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 };\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 editOn: 'click',\n };\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 /** 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 row IDs that have been modified (ID-based for stability) */\n #changedRowIds = new Set<string>();\n\n /** Set of cells currently in edit mode: \"rowIndex:colIndex\" */\n #editingCells = new Set<string>();\n\n /** Flag to restore focus after next render (used when exiting edit mode) */\n #pendingFocusRestore = false;\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 = grid as unknown as InternalGrid<T>;\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 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\n document.addEventListener(\n 'keydown',\n (e: KeyboardEvent) => {\n if (e.key === 'Escape' && this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, true);\n }\n },\n { capture: true, signal },\n );\n\n // Click outside to commit editing\n document.addEventListener(\n 'mousedown',\n (e: MouseEvent) => {\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 this.#exitRowEdit(this.#activeEditRow, false);\n },\n { signal },\n );\n }\n\n /** @internal */\n override detach(): void {\n this.#activeEditRow = -1;\n this.#activeEditCol = -1;\n this.#rowEditSnapshots.clear();\n this.#changedRowIds.clear();\n this.#editingCells.clear();\n super.detach();\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 const internalGrid = this.grid as unknown as InternalGrid<T>;\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 editable\n const hasEditableColumn = internalGrid._columns?.some((col) => col.editable);\n if (!hasEditableColumn) 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.grid as unknown as InternalGrid<T>;\n\n // Escape: cancel current edit\n if (event.key === 'Escape' && this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, true);\n return true;\n }\n\n // Space: toggle boolean cells\n if (event.key === ' ' || event.key === 'Spacebar') {\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?.editable && column.type === 'boolean' && rowData) {\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: start row edit or commit\n if (event.key === 'Enter' && !event.shiftKey) {\n if (this.#activeEditRow !== -1) {\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 editable\n const hasEditableColumn = internalGrid._columns?.some((col) => col.editable);\n if (hasEditableColumn) {\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 // 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.grid as unknown as InternalGrid<T>;\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 * 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.grid as unknown as InternalGrid<T>;\n\n // Restore focus after exiting edit mode\n if (this.#pendingFocusRestore) {\n this.#pendingFocusRestore = false;\n this.#restoreCellFocus(internalGrid);\n }\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 * 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 const rows: T[] = [];\n for (const id of this.#changedRowIds) {\n const row = this.grid.getRow(id) as T | undefined;\n if (row) rows.push(row);\n }\n return rows;\n }\n\n /**\n * Get IDs of all modified rows.\n */\n get changedRowIds(): string[] {\n return Array.from(this.#changedRowIds);\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.grid as unknown as InternalGrid<T>;\n const row = internalGrid._rows[rowIndex];\n if (!row) return false;\n try {\n const rowId = internalGrid.getRowId?.(row);\n return rowId ? this.#changedRowIds.has(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.#changedRowIds.has(rowId);\n }\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.#changedRowIds.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.grid as unknown as InternalGrid<T>;\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.grid as unknown as InternalGrid<T>;\n const colIndex = internalGrid._visibleColumns.findIndex((c) => c.field === field);\n if (colIndex === -1) return;\n\n const column = internalGrid._visibleColumns[colIndex];\n if (!column?.editable) 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.#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.grid as unknown as InternalGrid<T>;\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n if (editOn === false) return;\n\n const hasEditableColumn = internalGrid._columns?.some((col) => col.editable);\n if (!hasEditableColumn) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) return;\n\n // Start row edit\n const rowData = internalGrid._rows[rowIndex];\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?.editable) {\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 * Begin editing a single cell.\n */\n #beginCellEdit(rowIndex: number, colIndex: number, cellEl: HTMLElement): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const rowData = internalGrid._rows[rowIndex];\n const column = internalGrid._visibleColumns[colIndex];\n\n if (!rowData || !column?.editable) 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 * Sync the internal grid state with the plugin's editing state.\n */\n #syncGridEditState(): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\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 this.#rowEditSnapshots.set(rowIndex, { ...rowData });\n this.#activeEditRow = rowIndex;\n this.#syncGridEditState();\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.grid as unknown as InternalGrid<T>;\n const snapshot = this.#rowEditSnapshots.get(rowIndex);\n const current = internalGrid._rows[rowIndex];\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n\n // Get row ID for change tracking\n let rowId: string | undefined;\n if (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 const input = cell.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (input) {\n const val = getInputValue(input, col);\n if (current[col.field as keyof T] !== val) {\n this.#commitCellValue(rowIndex, col, val, current);\n }\n }\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.#changedRowIds.delete(rowId);\n }\n } else if (!revert && current) {\n const changed = rowId ? this.#changedRowIds.has(rowId) : false;\n this.emit<RowCommitDetail<T>>('row-commit', {\n rowIndex,\n rowId: rowId ?? '',\n row: current,\n changed,\n changedRows: this.changedRows,\n changedRowIds: this.changedRowIds,\n });\n\n // Animate the row if changes were committed\n if (changed && this.isAnimationEnabled) {\n internalGrid.animateRow?.(rowIndex, 'change');\n }\n }\n\n // Clear editing state\n this.#rowEditSnapshots.delete(rowIndex);\n this.#activeEditRow = -1;\n this.#activeEditCol = -1;\n this.#syncGridEditState();\n\n // Remove all editing cells for this row\n for (const cellKey of this.#editingCells) {\n if (cellKey.startsWith(`${rowIndex}:`)) {\n this.#editingCells.delete(cellKey);\n }\n }\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 // Request grid re-render to restore cell content\n this.requestRender();\n }\n\n // Mark that focus should be restored after render completes\n this.#pendingFocusRestore = true;\n\n // If no render was scheduled (row not visible), restore focus immediately\n if (!rowEl) {\n this.#restoreCellFocus(internalGrid);\n this.#pendingFocusRestore = false;\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.grid as unknown as InternalGrid<T>;\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.#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 // 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 });\n\n // If consumer called preventDefault(), abort the commit\n if (cancelled) return;\n\n // Apply the value and mark row as changed\n (rowData as Record<string, unknown>)[field] = newValue;\n if (rowId) {\n this.#changedRowIds.add(rowId);\n }\n this.#syncGridEditState();\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (rowEl) {\n rowEl.classList.add('changed');\n // Trigger row change animation (respects animation.mode config)\n animateRowElement(rowEl, 'change');\n }\n }\n\n /**\n * Inject an editor into a cell.\n */\n #injectEditor(\n rowData: T,\n rowIndex: number,\n column: ColumnConfig<T>,\n colIndex: number,\n cell: HTMLElement,\n skipFocus: boolean,\n ): void {\n if (!column.editable) return;\n if (cell.classList.contains('editing')) return;\n\n // Get row ID for updateRow helper (may not exist)\n let rowId: string | undefined;\n try {\n rowId = this.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 ? (changes) => this.grid.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 this.#editingCells.add(`${rowIndex}:${colIndex}`);\n\n const rowEl = cell.parentElement as RowElementInternal | null;\n if (rowEl) incrementEditingCount(rowEl);\n\n let editFinalized = false;\n const commit = (newValue: unknown) => {\n if (editFinalized || this.#activeEditRow === -1) return;\n this.#commitCellValue(rowIndex, column, newValue, rowData);\n };\n const cancel = () => {\n editFinalized = true;\n if (isSafePropertyKey(column.field)) {\n (rowData 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 e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n this.#exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n e.stopPropagation();\n e.preventDefault();\n cancel();\n this.#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(this.grid as unknown as InternalGrid<T>, colInternal) ?? defaultEditorFor(column);\n const value = originalValue;\n\n if (editorSpec === 'template' && tplHolder) {\n this.#renderTemplateEditor(editorHost, colInternal, rowData, originalValue, commit, cancel, skipFocus, rowIndex);\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 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 };\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);\n } else if (produced instanceof Node) {\n editorHost.appendChild(produced);\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 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 };\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 console.warn(`[tbw-grid] External editor mount error for column '${column.field}':`, e);\n }\n } else {\n (this.grid as unknown as HTMLElement).dispatchEvent(\n new CustomEvent('mount-external-editor', { detail: { placeholder, spec: editorSpec, context } }),\n );\n }\n }\n }\n\n /**\n * Render a template-based editor.\n */\n #renderTemplateEditor(\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));\n });\n input.addEventListener('keydown', (evt) => {\n const e = evt as KeyboardEvent;\n if (e.key === 'Enter') {\n e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n commit(getInputValue(input, column));\n this.#exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n e.stopPropagation();\n e.preventDefault();\n cancel();\n this.#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 /**\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":["resolveOptions","column","raw","createNumberEditor","ctx","params","input","commit","e","createBooleanEditor","createDateEditor","createSelectEditor","select","emptyOpt","opt","o","values","createTextEditor","defaultEditorFor","FOCUSABLE_EDITOR_SELECTOR","resolveEditor","grid","col","gridTypeDefaults","adapter","appDefault","isSafePropertyKey","key","incrementEditingCount","rowEl","count","clearEditingState","getInputValue","noopUpdateRow","_changes","wireEditorInputs","editorHost","EditingPlugin","BaseGridPlugin","v","styles","#activeEditRow","#activeEditCol","#rowEditSnapshots","#changedRowIds","#editingCells","#pendingFocusRestore","signal","internalGrid","silent","rowIndex","field","#exitRowEdit","event","editOn","isDoubleClick","focusRow","focusCol","rowData","newValue","#commitCellValue","row","value","cellEl","activateEvent","legacyEvent","columns","typeDefaults","typeEditorParams","#restoreCellFocus","cellKey","rowStr","colStr","colIndex","#injectEditor","rows","id","rowId","ids","#syncGridEditState","r","c","#beginCellEdit","#startRowEdit","cell","i","targetCell","editor","revert","snapshot","current","val","k","changed","oldValue","firstTime","updateRow","changes","animateRowElement","skipFocus","originalValue","editFinalized","cancel","colInternal","tplHolder","editorSpec","#renderTemplateEditor","el","produced","placeholder","context","clone","compiledEditor","node","_m","g","evt","rowIdx","colIdx"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
(function(c,g){typeof exports=="object"&&typeof module<"u"?g(exports,require("../../core/internal/sanitize"),require("../../core/plugin/base-plugin"),require("../../core/plugin/expander-column")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/sanitize","../../core/plugin/base-plugin","../../core/plugin/expander-column"],g):(c=typeof globalThis<"u"?globalThis:c||self,g(c.TbwGridPlugin_masterDetail={},c.TbwGrid,c.TbwGrid,c.TbwGrid))})(this,(function(c,g,m,f){"use strict";function w(u,e){const t=new Set(u);return t.has(e)?t.delete(e):t.add(e),t}function x(u,e){const t=new Set(u);return t.add(e),t}function b(u,e){const t=new Set(u);return t.delete(e),t}function R(u,e){return u.has(e)}function E(u,e,t,
|
|
1
|
+
(function(c,g){typeof exports=="object"&&typeof module<"u"?g(exports,require("../../core/internal/sanitize"),require("../../core/plugin/base-plugin"),require("../../core/plugin/expander-column")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/sanitize","../../core/plugin/base-plugin","../../core/plugin/expander-column"],g):(c=typeof globalThis<"u"?globalThis:c||self,g(c.TbwGridPlugin_masterDetail={},c.TbwGrid,c.TbwGrid,c.TbwGrid))})(this,(function(c,g,m,f){"use strict";function w(u,e){const t=new Set(u);return t.has(e)?t.delete(e):t.add(e),t}function x(u,e){const t=new Set(u);return t.add(e),t}function b(u,e){const t=new Set(u);return t.delete(e),t}function R(u,e){return u.has(e)}function E(u,e,t,n){const a=document.createElement("div");a.className="master-detail-row",a.setAttribute("data-detail-for",String(e)),a.setAttribute("role","row");const r=document.createElement("div");r.className="master-detail-cell",r.setAttribute("role","cell"),r.style.gridColumn=`1 / ${n+1}`;const o=t(u,e);return typeof o=="string"?r.innerHTML=o:o instanceof HTMLElement&&r.appendChild(o),a.appendChild(r),a}const y="@layer tbw-plugins{tbw-grid .cell[data-field=__tbw_expander]{border-right:none!important;padding:0;display:flex;align-items:center;justify-content:center}tbw-grid .header-row .cell[data-field=__tbw_expander]{display:none}tbw-grid .header-row .cell[data-field=__tbw_expander]+.cell{grid-column:1 / 3}tbw-grid .master-detail-expander{display:flex;align-items:center;justify-content:center;width:100%;height:100%}tbw-grid .master-detail-toggle{cursor:pointer;opacity:.7;-webkit-user-select:none;user-select:none;display:inline-flex;align-items:center;justify-content:center}tbw-grid .master-detail-toggle:hover{opacity:1}tbw-grid .master-detail-row{grid-column:1 / -1;display:grid;background:var(--tbw-master-detail-bg, var(--tbw-color-row-alt));border-bottom:1px solid var(--tbw-master-detail-border, var(--tbw-color-border));overflow:hidden}tbw-grid .master-detail-cell{padding:var(--tbw-detail-padding, var(--tbw-spacing-xl, 1rem));overflow:auto}tbw-grid .master-detail-row.tbw-expanding{animation:tbw-detail-expand var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}tbw-grid .master-detail-row.tbw-collapsing{animation:tbw-detail-collapse var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-detail-expand{0%{opacity:0;max-height:0;padding-top:0;padding-bottom:0}to{opacity:1;max-height:var(--tbw-detail-max-height, 31.25rem);padding-top:var(--tbw-detail-padding, var(--tbw-spacing-xl, 1rem));padding-bottom:var(--tbw-detail-padding, var(--tbw-spacing-xl, 1rem))}}@keyframes tbw-detail-collapse{0%{opacity:1;max-height:var(--tbw-detail-max-height, 31.25rem)}to{opacity:0;max-height:0}}}";class p extends m.BaseGridPlugin{name="masterDetail";styles=y;get defaultConfig(){return{detailHeight:"auto",expandOnRowClick:!1,collapseOnClickOutside:!1,animation:"slide"}}attach(e){super.attach(e),this.parseLightDomDetail()}parseLightDomDetail(){const e=this.grid;if(!e||typeof e.querySelector!="function")return;const t=e.querySelector("tbw-grid-detail");if(!t)return;const n=e;if(n.__frameworkAdapter?.parseDetailElement){const h=n.__frameworkAdapter.parseDetailElement(t);if(h){this.config={...this.config,detailRenderer:h};return}}const a=t.getAttribute("animation"),r=t.getAttribute("show-expand-column"),o=t.getAttribute("expand-on-row-click"),s=t.getAttribute("collapse-on-click-outside"),i=t.getAttribute("height"),d={};a!==null&&(d.animation=a==="false"?!1:a),r!==null&&(d.showExpandColumn=r!=="false"),o!==null&&(d.expandOnRowClick=o==="true"),s!==null&&(d.collapseOnClickOutside=s==="true"),i!==null&&(d.detailHeight=i==="auto"?"auto":parseInt(i,10));const l=t.innerHTML.trim();l&&!this.config.detailRenderer&&(d.detailRenderer=(h,A)=>{const v=g.evalTemplateString(l,{value:h,row:h});return g.sanitizeHTML(v)}),Object.keys(d).length>0&&(this.config={...this.config,...d})}get animationStyle(){return this.isAnimationEnabled?this.config.animation??"slide":!1}animateExpand(e){!this.isAnimationEnabled||this.animationStyle===!1||(e.classList.add("tbw-expanding"),e.addEventListener("animationend",()=>{e.classList.remove("tbw-expanding")},{once:!0}))}animateCollapse(e,t){if(!this.isAnimationEnabled||this.animationStyle===!1){t();return}e.classList.add("tbw-collapsing");const n=()=>{e.classList.remove("tbw-collapsing"),t()};e.addEventListener("animationend",n,{once:!0}),setTimeout(n,this.animationDuration+50)}expandedRows=new Set;detailElements=new Map;static DEFAULT_DETAIL_HEIGHT=150;getDetailHeight(e){const t=this.detailElements.get(e);return t?t.offsetHeight:typeof this.config?.detailHeight=="number"?this.config.detailHeight:p.DEFAULT_DETAIL_HEIGHT}toggleAndEmit(e,t){this.expandedRows=w(this.expandedRows,e),this.emit("detail-expand",{rowIndex:t,row:e,expanded:this.expandedRows.has(e)}),this.requestRender()}detach(){this.expandedRows.clear(),this.detailElements.clear()}processColumns(e){if(!(this.config.showExpandColumn===!0||this.config.showExpandColumn!==!1&&!!this.config.detailRenderer))return[...e];const n=[...e];if(f.findExpanderColumn(n))return n;const r=f.createExpanderColumnConfig(this.name);return r.viewRenderer=o=>{const{row:s}=o,i=this.expandedRows.has(s),d=document.createElement("span");d.className="master-detail-expander expander-cell";const l=document.createElement("span");return l.className=`master-detail-toggle${i?" expanded":""}`,this.setIcon(l,this.resolveIcon(i?"collapse":"expand")),l.setAttribute("role","button"),l.setAttribute("tabindex","0"),l.setAttribute("aria-expanded",String(i)),l.setAttribute("aria-label",i?"Collapse details":"Expand details"),d.appendChild(l),d},[r,...n]}onRowClick(e){if(!(!this.config.expandOnRowClick||!this.config.detailRenderer))return this.toggleAndEmit(e.row,e.rowIndex),!1}onCellClick(e){if(e.originalEvent?.target?.classList.contains("master-detail-toggle"))return this.toggleAndEmit(e.row,e.rowIndex),!0;this.expandedRows.size>0&&queueMicrotask(()=>this.#e())}onKeyDown(e){if(e.key!==" ")return;const t=this.grid._focusCol,n=this.grid._focusRow,a=this.columns[t];if(!a||!f.isExpanderColumn(a))return;const r=this.rows[n];if(r)return e.preventDefault(),this.toggleAndEmit(r,n),this.requestRenderWithFocus(),!0}afterRender(){this.#e()}onScrollRender(){!this.config.detailRenderer||this.expandedRows.size===0||this.#e()}#e(){if(!this.config.detailRenderer)return;const e=this.gridElement?.querySelector(".rows");if(!e)return;const t=new Map,n=e.querySelectorAll(".data-grid-row"),a=this.columns.length;for(const o of n){const s=o.querySelector(".cell[data-row]"),i=s?parseInt(s.getAttribute("data-row")??"-1",10):-1;i>=0&&t.set(i,o)}const r=e.querySelectorAll(".master-detail-row");for(const o of r){const s=parseInt(o.getAttribute("data-detail-for")??"-1",10),i=s>=0?this.rows[s]:void 0,d=i&&this.expandedRows.has(i),l=t.has(s);(!d||!l)&&(o.remove(),i&&this.detailElements.delete(i))}for(const[o,s]of t){const i=this.rows[o];if(!i||!this.expandedRows.has(i))continue;const d=this.detailElements.get(i);if(d){d.previousElementSibling!==s&&s.after(d);continue}const l=E(i,o,this.config.detailRenderer,a);typeof this.config.detailHeight=="number"&&(l.style.height=`${this.config.detailHeight}px`),s.after(l),this.detailElements.set(i,l),this.animateExpand(l)}}getExtraHeight(){let e=0;for(const t of this.expandedRows)e+=this.getDetailHeight(t);return e}getExtraHeightBefore(e){let t=0;for(const n of this.expandedRows){const a=this.rows.indexOf(n);a>=0&&a<e&&(t+=this.getDetailHeight(n))}return t}adjustVirtualStart(e,t,n){if(this.expandedRows.size===0)return e;const a=[];for(const s of this.expandedRows){const i=this.rows.indexOf(s);i>=0&&a.push({index:i,row:s})}a.sort((s,i)=>s.index-i.index);let r=e,o=0;for(const{index:s,row:i}of a){const d=s*n+o,l=this.getDetailHeight(i),h=d+n+l;o+=l,!(s>=e)&&h>t&&s<r&&(r=s)}return r}expand(e){const t=this.rows[e];t&&(this.expandedRows=x(this.expandedRows,t),this.requestRender())}collapse(e){const t=this.rows[e];t&&(this.expandedRows=b(this.expandedRows,t),this.requestRender())}toggle(e){const t=this.rows[e];t&&(this.expandedRows=w(this.expandedRows,t),this.requestRender())}isExpanded(e){const t=this.rows[e];return t?R(this.expandedRows,t):!1}expandAll(){for(const e of this.rows)this.expandedRows.add(e);this.requestRender()}collapseAll(){this.expandedRows.clear(),this.requestRender()}getExpandedRows(){const e=[];for(const t of this.expandedRows){const n=this.rows.indexOf(t);n>=0&&e.push(n)}return e}getDetailElement(e){const t=this.rows[e];return t?this.detailElements.get(t):void 0}refreshDetailRenderer(){const e=this.config.detailRenderer;if(this.config={...this.config,detailRenderer:void 0},this.parseLightDomDetail(),!this.config.detailRenderer&&e&&(this.config={...this.config,detailRenderer:e}),this.config.detailRenderer){const t=this.grid;typeof t.refreshColumns=="function"?t.refreshColumns():this.requestRender()}}}c.MasterDetailPlugin=p,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})}));
|
|
2
2
|
//# sourceMappingURL=master-detail.umd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"master-detail.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/master-detail/master-detail.ts","../../../../../libs/grid/src/lib/plugins/master-detail/MasterDetailPlugin.ts"],"sourcesContent":["/**\n * Master/Detail Core Logic\n *\n * Pure functions for managing detail row expansion state.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n// Uses `any` for maximum flexibility with user-defined row types.\n\n/**\n * Toggle the expansion state of a detail row.\n * Returns a new Set with the updated state.\n */\nexport function toggleDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n if (newExpanded.has(row)) {\n newExpanded.delete(row);\n } else {\n newExpanded.add(row);\n }\n return newExpanded;\n}\n\n/**\n * Expand a detail row.\n * Returns a new Set with the row added.\n */\nexport function expandDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n newExpanded.add(row);\n return newExpanded;\n}\n\n/**\n * Collapse a detail row.\n * Returns a new Set with the row removed.\n */\nexport function collapseDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n newExpanded.delete(row);\n return newExpanded;\n}\n\n/**\n * Check if a detail row is expanded.\n */\nexport function isDetailExpanded(expandedRows: Set<object>, row: object): boolean {\n return expandedRows.has(row);\n}\n\n/**\n * Create a detail element for a given row.\n * The element spans all columns and contains the rendered content.\n */\nexport function createDetailElement(\n row: any,\n rowIndex: number,\n renderer: (row: any, rowIndex: number) => HTMLElement | string,\n columnCount: number\n): HTMLElement {\n const detailRow = document.createElement('div');\n detailRow.className = 'master-detail-row';\n detailRow.setAttribute('data-detail-for', String(rowIndex));\n detailRow.setAttribute('role', 'row');\n\n const detailCell = document.createElement('div');\n detailCell.className = 'master-detail-cell';\n detailCell.setAttribute('role', 'cell');\n detailCell.style.gridColumn = `1 / ${columnCount + 1}`;\n\n const content = renderer(row, rowIndex);\n if (typeof content === 'string') {\n detailCell.innerHTML = content;\n } else if (content instanceof HTMLElement) {\n detailCell.appendChild(content);\n }\n\n detailRow.appendChild(detailCell);\n return detailRow;\n}\n","/**\n * Master/Detail Plugin (Class-based)\n *\n * Enables expandable detail rows showing additional content for each row.\n * Animation style is plugin-configured; respects grid-level animation.mode.\n */\n\nimport { evalTemplateString, sanitizeHTML } from '../../core/internal/sanitize';\nimport { BaseGridPlugin, CellClickEvent, GridElement, RowClickEvent } from '../../core/plugin/base-plugin';\nimport { createExpanderColumnConfig, findExpanderColumn, isExpanderColumn } from '../../core/plugin/expander-column';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n collapseDetailRow,\n createDetailElement,\n expandDetailRow,\n isDetailExpanded,\n toggleDetailRow,\n} from './master-detail';\nimport styles from './master-detail.css?inline';\nimport type { DetailExpandDetail, ExpandCollapseAnimation, MasterDetailConfig } from './types';\n\n/**\n * Master-Detail Plugin for tbw-grid\n *\n * Creates expandable detail rows that reveal additional content beneath each master row.\n * Perfect for order/line-item UIs, employee/department views, or any scenario where\n * you need to show related data without navigating away.\n *\n * ## Installation\n *\n * ```ts\n * import { MasterDetailPlugin } from '@toolbox-web/grid/plugins/master-detail';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `detailRenderer` | `(row) => HTMLElement \\| string` | required | Render function for detail content |\n * | `expandOnRowClick` | `boolean` | `false` | Expand when clicking the row |\n * | `detailHeight` | `number \\| 'auto'` | `'auto'` | Fixed height or auto-size |\n * | `collapseOnClickOutside` | `boolean` | `false` | Collapse when clicking outside |\n * | `showExpandColumn` | `boolean` | `true` | Show expand/collapse column |\n * | `animation` | `false \\| 'slide' \\| 'fade'` | `'slide'` | Animation style |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `expandRow` | `(rowIndex) => void` | Expand a specific row |\n * | `collapseRow` | `(rowIndex) => void` | Collapse a specific row |\n * | `toggleRow` | `(rowIndex) => void` | Toggle row expansion |\n * | `expandAll` | `() => void` | Expand all rows |\n * | `collapseAll` | `() => void` | Collapse all rows |\n * | `isRowExpanded` | `(rowIndex) => boolean` | Check if row is expanded |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-master-detail-bg` | `var(--tbw-color-row-alt)` | Detail row background |\n * | `--tbw-master-detail-border` | `var(--tbw-color-border)` | Detail row border |\n * | `--tbw-detail-padding` | `1em` | Detail content padding |\n * | `--tbw-animation-duration` | `200ms` | Expand/collapse animation |\n *\n * @example Basic Master-Detail with HTML Template\n * ```ts\n * import '@toolbox-web/grid';\n * import { MasterDetailPlugin } from '@toolbox-web/grid/plugins/master-detail';\n *\n * grid.gridConfig = {\n * columns: [\n * { field: 'orderId', header: 'Order ID' },\n * { field: 'customer', header: 'Customer' },\n * { field: 'total', header: 'Total', type: 'currency' },\n * ],\n * plugins: [\n * new MasterDetailPlugin({\n * detailRenderer: (row) => `\n * <div class=\"order-details\">\n * <h4>Order Items</h4>\n * <ul>${row.items.map(i => `<li>${i.name} - $${i.price}</li>`).join('')}</ul>\n * </div>\n * `,\n * }),\n * ],\n * };\n * ```\n *\n * @example Nested Grid in Detail\n * ```ts\n * new MasterDetailPlugin({\n * detailRenderer: (row) => {\n * const childGrid = document.createElement('tbw-grid');\n * childGrid.style.height = '200px';\n * childGrid.gridConfig = { columns: [...] };\n * childGrid.rows = row.items || [];\n * return childGrid;\n * },\n * })\n * ```\n *\n * @see {@link MasterDetailConfig} for all configuration options\n * @see {@link DetailExpandDetail} for expand/collapse event details\n *\n * @internal Extends BaseGridPlugin\n */\nexport class MasterDetailPlugin extends BaseGridPlugin<MasterDetailConfig> {\n /** @internal */\n readonly name = 'masterDetail';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<MasterDetailConfig> {\n return {\n detailHeight: 'auto',\n expandOnRowClick: false,\n collapseOnClickOutside: false,\n showExpandColumn: true,\n animation: 'slide', // Plugin's own default\n };\n }\n\n // #region Light DOM Parsing\n\n /**\n * Called when plugin is attached to the grid.\n * Parses light DOM for `<tbw-grid-detail>` elements to configure detail templates.\n * @internal\n */\n override attach(grid: GridElement): void {\n super.attach(grid);\n this.parseLightDomDetail();\n }\n\n /**\n * Parse `<tbw-grid-detail>` elements from the grid's light DOM.\n *\n * Allows declarative configuration:\n * ```html\n * <tbw-grid [rows]=\"data\">\n * <tbw-grid-detail>\n * <div class=\"detail-content\">\n * <p>Name: {{ row.name }}</p>\n * <p>Email: {{ row.email }}</p>\n * </div>\n * </tbw-grid-detail>\n * </tbw-grid>\n * ```\n *\n * Attributes:\n * - `animation`: 'slide' | 'fade' | 'false' (default: 'slide')\n * - `show-expand-column`: 'true' | 'false' (default: 'true')\n * - `expand-on-row-click`: 'true' | 'false' (default: 'false')\n * - `collapse-on-click-outside`: 'true' | 'false' (default: 'false')\n * - `height`: number (pixels) or 'auto' (default: 'auto')\n */\n private parseLightDomDetail(): void {\n const gridEl = this.grid as unknown as Element;\n if (!gridEl || typeof gridEl.querySelector !== 'function') return;\n\n const detailEl = gridEl.querySelector('tbw-grid-detail');\n if (!detailEl) return;\n\n // Check if a framework adapter wants to handle this element\n // (e.g., Angular adapter intercepts for ng-template rendering)\n const gridWithAdapter = gridEl as unknown as {\n __frameworkAdapter?: {\n parseDetailElement?: (el: Element) => ((row: any, rowIndex: number) => HTMLElement | string) | undefined;\n };\n };\n if (gridWithAdapter.__frameworkAdapter?.parseDetailElement) {\n const adapterRenderer = gridWithAdapter.__frameworkAdapter.parseDetailElement(detailEl);\n if (adapterRenderer) {\n this.config = { ...this.config, detailRenderer: adapterRenderer };\n return;\n }\n }\n\n // Parse attributes for configuration\n const animation = detailEl.getAttribute('animation');\n const showExpandColumn = detailEl.getAttribute('show-expand-column');\n const expandOnRowClick = detailEl.getAttribute('expand-on-row-click');\n const collapseOnClickOutside = detailEl.getAttribute('collapse-on-click-outside');\n const heightAttr = detailEl.getAttribute('height');\n\n const configUpdates: Partial<MasterDetailConfig> = {};\n\n if (animation !== null) {\n configUpdates.animation = animation === 'false' ? false : (animation as 'slide' | 'fade');\n }\n if (showExpandColumn !== null) {\n configUpdates.showExpandColumn = showExpandColumn !== 'false';\n }\n if (expandOnRowClick !== null) {\n configUpdates.expandOnRowClick = expandOnRowClick === 'true';\n }\n if (collapseOnClickOutside !== null) {\n configUpdates.collapseOnClickOutside = collapseOnClickOutside === 'true';\n }\n if (heightAttr !== null) {\n configUpdates.detailHeight = heightAttr === 'auto' ? 'auto' : parseInt(heightAttr, 10);\n }\n\n // Get template content from innerHTML\n const templateHTML = detailEl.innerHTML.trim();\n if (templateHTML && !this.config.detailRenderer) {\n // Create a template-based renderer using the inner HTML\n configUpdates.detailRenderer = (row: any, _rowIndex: number): string => {\n // Evaluate template expressions like {{ row.field }}\n const evaluated = evalTemplateString(templateHTML, { value: row, row });\n // Sanitize the result to prevent XSS\n return sanitizeHTML(evaluated);\n };\n }\n\n // Merge updates into config\n if (Object.keys(configUpdates).length > 0) {\n this.config = { ...this.config, ...configUpdates };\n }\n }\n\n // #endregion\n\n // #region Animation Helpers\n\n /**\n * Get expand/collapse animation style from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide';\n }\n\n /**\n * Apply expand animation to a detail element.\n */\n private animateExpand(detailEl: HTMLElement): void {\n if (!this.isAnimationEnabled || this.animationStyle === false) return;\n\n detailEl.classList.add('tbw-expanding');\n detailEl.addEventListener(\n 'animationend',\n () => {\n detailEl.classList.remove('tbw-expanding');\n },\n { once: true },\n );\n }\n\n /**\n * Apply collapse animation to a detail element and remove after animation.\n */\n private animateCollapse(detailEl: HTMLElement, onComplete: () => void): void {\n if (!this.isAnimationEnabled || this.animationStyle === false) {\n onComplete();\n return;\n }\n\n detailEl.classList.add('tbw-collapsing');\n const cleanup = () => {\n detailEl.classList.remove('tbw-collapsing');\n onComplete();\n };\n detailEl.addEventListener('animationend', cleanup, { once: true });\n // Fallback timeout in case animation doesn't fire\n setTimeout(cleanup, this.animationDuration + 50);\n }\n\n // #endregion\n\n // #region Internal State\n private expandedRows: Set<any> = new Set();\n private detailElements: Map<any, HTMLElement> = new Map();\n\n /** Default height for detail rows when not configured */\n private static readonly DEFAULT_DETAIL_HEIGHT = 150;\n\n /**\n * Get the estimated height for a detail row.\n */\n private getDetailHeight(row: any): number {\n const detailEl = this.detailElements.get(row);\n if (detailEl) return detailEl.offsetHeight;\n return typeof this.config?.detailHeight === 'number'\n ? this.config.detailHeight\n : MasterDetailPlugin.DEFAULT_DETAIL_HEIGHT;\n }\n\n /**\n * Toggle a row's detail and emit event.\n */\n private toggleAndEmit(row: any, rowIndex: number): void {\n this.expandedRows = toggleDetailRow(this.expandedRows, row as object);\n this.emit<DetailExpandDetail>('detail-expand', {\n rowIndex,\n row: row as Record<string, unknown>,\n expanded: this.expandedRows.has(row as object),\n });\n this.requestRender();\n }\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.expandedRows.clear();\n this.detailElements.clear();\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (!this.config.detailRenderer || this.config.showExpandColumn === false) {\n return [...columns];\n }\n\n const cols = [...columns];\n\n // Check if expander column already exists (from this or another plugin)\n const existingExpander = findExpanderColumn(cols);\n if (existingExpander) {\n // Another plugin already added an expander column - don't add duplicate\n // Our expand logic will be handled via onCellClick on the expander column\n return cols;\n }\n\n // Create dedicated expander column that stays fixed at position 0\n const expanderCol = createExpanderColumnConfig(this.name);\n expanderCol.viewRenderer = (renderCtx) => {\n const { row } = renderCtx;\n const isExpanded = this.expandedRows.has(row as object);\n\n const container = document.createElement('span');\n container.className = 'master-detail-expander expander-cell';\n\n // Expand/collapse toggle icon\n const toggle = document.createElement('span');\n toggle.className = `master-detail-toggle${isExpanded ? ' expanded' : ''}`;\n // Use grid-level icons (fall back to defaults)\n this.setIcon(toggle, this.resolveIcon(isExpanded ? 'collapse' : 'expand'));\n // role=\"button\" is required for aria-expanded to be valid\n toggle.setAttribute('role', 'button');\n toggle.setAttribute('tabindex', '0');\n toggle.setAttribute('aria-expanded', String(isExpanded));\n toggle.setAttribute('aria-label', isExpanded ? 'Collapse details' : 'Expand details');\n container.appendChild(toggle);\n\n return container;\n };\n\n // Prepend expander column to ensure it's always first\n return [expanderCol, ...cols];\n }\n\n /** @internal */\n override onRowClick(event: RowClickEvent): boolean | void {\n if (!this.config.expandOnRowClick || !this.config.detailRenderer) return;\n this.toggleAndEmit(event.row, event.rowIndex);\n return false;\n }\n\n /** @internal */\n override onCellClick(event: CellClickEvent): boolean | void {\n // Handle click on master-detail toggle icon (same pattern as TreePlugin)\n const target = event.originalEvent?.target as HTMLElement;\n if (target?.classList.contains('master-detail-toggle')) {\n this.toggleAndEmit(event.row, event.rowIndex);\n return true; // Prevent default handling\n }\n\n // Sync detail rows after cell click triggers refreshVirtualWindow\n // This runs in microtask to ensure DOM updates are complete\n if (this.expandedRows.size > 0) {\n queueMicrotask(() => this.#syncDetailRows());\n }\n return; // Don't prevent default\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion when focus is on the expander column\n if (event.key !== ' ') return;\n\n const focusCol = this.grid._focusCol;\n const focusRow = this.grid._focusRow;\n const column = this.columns[focusCol];\n\n // Only handle SPACE on expander column\n if (!column || !isExpanderColumn(column)) return;\n\n const row = this.rows[focusRow];\n if (!row) return;\n\n event.preventDefault();\n this.toggleAndEmit(row, focusRow);\n\n // Restore focus styling after render completes via render pipeline\n this.requestRenderWithFocus();\n return true;\n }\n\n /** @internal */\n override afterRender(): void {\n this.#syncDetailRows();\n }\n\n /**\n * Called on scroll to sync detail elements with visible rows.\n * Removes details for rows that scrolled out of view and reattaches for visible rows.\n * @internal\n */\n override onScrollRender(): void {\n if (!this.config.detailRenderer || this.expandedRows.size === 0) return;\n // Full sync needed on scroll to clean up orphaned details\n this.#syncDetailRows();\n }\n\n /**\n * Full sync of detail rows - cleans up stale elements and creates new ones.\n * Detail rows are inserted as siblings AFTER their master row to survive row rebuilds.\n */\n #syncDetailRows(): void {\n if (!this.config.detailRenderer) return;\n\n const body = this.gridElement?.querySelector('.rows');\n if (!body) return;\n\n // Build a map of row index -> row element for visible rows\n const visibleRowMap = new Map<number, Element>();\n const dataRows = body.querySelectorAll('.data-grid-row');\n const columnCount = this.columns.length;\n\n for (const rowEl of dataRows) {\n const firstCell = rowEl.querySelector('.cell[data-row]');\n const rowIndex = firstCell ? parseInt(firstCell.getAttribute('data-row') ?? '-1', 10) : -1;\n if (rowIndex >= 0) {\n visibleRowMap.set(rowIndex, rowEl);\n }\n }\n\n // Remove detail rows whose parent row is no longer visible or no longer expanded\n const existingDetails = body.querySelectorAll('.master-detail-row');\n for (const detailEl of existingDetails) {\n const forIndex = parseInt(detailEl.getAttribute('data-detail-for') ?? '-1', 10);\n const row = forIndex >= 0 ? this.rows[forIndex] : undefined;\n const isStillExpanded = row && this.expandedRows.has(row);\n const isRowVisible = visibleRowMap.has(forIndex);\n\n // Remove detail if not expanded or if parent row scrolled out\n if (!isStillExpanded || !isRowVisible) {\n detailEl.remove();\n if (row) this.detailElements.delete(row);\n }\n }\n\n // Insert detail rows for expanded rows that are visible\n for (const [rowIndex, rowEl] of visibleRowMap) {\n const row = this.rows[rowIndex];\n if (!row || !this.expandedRows.has(row)) continue;\n\n // Check if detail already exists for this row\n const existingDetail = this.detailElements.get(row);\n if (existingDetail) {\n // Ensure it's positioned correctly (as next sibling of row element)\n if (existingDetail.previousElementSibling !== rowEl) {\n rowEl.after(existingDetail);\n }\n continue;\n }\n\n // Create new detail element\n const detailEl = createDetailElement(row, rowIndex, this.config.detailRenderer, columnCount);\n\n if (typeof this.config.detailHeight === 'number') {\n detailEl.style.height = `${this.config.detailHeight}px`;\n }\n\n // Insert as sibling after the row element (not as child)\n rowEl.after(detailEl);\n this.detailElements.set(row, detailEl);\n\n // Apply expand animation\n this.animateExpand(detailEl);\n }\n }\n\n /**\n * Return total extra height from all expanded detail rows.\n * Used by grid virtualization to adjust scrollbar height.\n */\n override getExtraHeight(): number {\n let totalHeight = 0;\n for (const row of this.expandedRows) {\n totalHeight += this.getDetailHeight(row);\n }\n return totalHeight;\n }\n\n /**\n * Return extra height that appears before a given row index.\n * This is the sum of heights of all expanded details whose parent row is before the given index.\n */\n override getExtraHeightBefore(beforeRowIndex: number): number {\n let totalHeight = 0;\n for (const row of this.expandedRows) {\n const rowIndex = this.rows.indexOf(row);\n // Include detail if it's for a row before the given index\n if (rowIndex >= 0 && rowIndex < beforeRowIndex) {\n totalHeight += this.getDetailHeight(row);\n }\n }\n return totalHeight;\n }\n\n /**\n * Adjust the virtualization start index to keep expanded row visible while its detail is visible.\n * This ensures the detail scrolls smoothly out of view instead of disappearing abruptly.\n */\n override adjustVirtualStart(start: number, scrollTop: number, rowHeight: number): number {\n if (this.expandedRows.size === 0) return start;\n\n // Build sorted list of expanded row indices for cumulative height calculation\n const expandedIndices: Array<{ index: number; row: any }> = [];\n for (const row of this.expandedRows) {\n const index = this.rows.indexOf(row);\n if (index >= 0) {\n expandedIndices.push({ index, row });\n }\n }\n expandedIndices.sort((a, b) => a.index - b.index);\n\n let minStart = start;\n\n // Calculate actual scroll position for each expanded row,\n // accounting for cumulative detail heights before it\n let cumulativeExtraHeight = 0;\n\n for (const { index: rowIndex, row } of expandedIndices) {\n // Actual position includes all detail heights before this row\n const actualRowTop = rowIndex * rowHeight + cumulativeExtraHeight;\n const detailHeight = this.getDetailHeight(row);\n const actualDetailBottom = actualRowTop + rowHeight + detailHeight;\n\n // Update cumulative height for next iteration\n cumulativeExtraHeight += detailHeight;\n\n // Skip rows that are at or after the calculated start\n if (rowIndex >= start) continue;\n\n // If any part of the detail is still visible (below the scroll position),\n // we need to keep the parent row in the render range\n if (actualDetailBottom > scrollTop) {\n if (rowIndex < minStart) {\n minStart = rowIndex;\n }\n }\n }\n\n return minStart;\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Expand the detail row at the given index.\n * @param rowIndex - Index of the row to expand\n */\n expand(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = expandDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Collapse the detail row at the given index.\n * @param rowIndex - Index of the row to collapse\n */\n collapse(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = collapseDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Toggle the detail row at the given index.\n * @param rowIndex - Index of the row to toggle\n */\n toggle(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = toggleDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Check if the detail row at the given index is expanded.\n * @param rowIndex - Index of the row to check\n * @returns Whether the detail row is expanded\n */\n isExpanded(rowIndex: number): boolean {\n const row = this.rows[rowIndex];\n return row ? isDetailExpanded(this.expandedRows, row) : false;\n }\n\n /**\n * Expand all detail rows.\n */\n expandAll(): void {\n for (const row of this.rows) {\n this.expandedRows.add(row);\n }\n this.requestRender();\n }\n\n /**\n * Collapse all detail rows.\n */\n collapseAll(): void {\n this.expandedRows.clear();\n this.requestRender();\n }\n\n /**\n * Get the indices of all expanded rows.\n * @returns Array of row indices that are expanded\n */\n getExpandedRows(): number[] {\n const indices: number[] = [];\n for (const row of this.expandedRows) {\n const idx = this.rows.indexOf(row);\n if (idx >= 0) indices.push(idx);\n }\n return indices;\n }\n\n /**\n * Get the detail element for a specific row.\n * @param rowIndex - Index of the row\n * @returns The detail HTMLElement or undefined\n */\n getDetailElement(rowIndex: number): HTMLElement | undefined {\n const row = this.rows[rowIndex];\n return row ? this.detailElements.get(row) : undefined;\n }\n\n /**\n * Re-parse light DOM to refresh the detail renderer.\n * Call this after framework templates are registered (e.g., Angular ngAfterContentInit).\n *\n * This allows frameworks to register templates asynchronously and then\n * update the plugin's detailRenderer.\n */\n refreshDetailRenderer(): void {\n // Force re-parse by temporarily clearing the renderer\n const currentRenderer = this.config.detailRenderer;\n this.config = { ...this.config, detailRenderer: undefined };\n this.parseLightDomDetail();\n\n // If no new renderer was found, restore the original\n if (!this.config.detailRenderer && currentRenderer) {\n this.config = { ...this.config, detailRenderer: currentRenderer };\n }\n\n // Request a COLUMNS phase re-render so processColumns runs again with the new detailRenderer\n // This ensures the expand toggle is added to the first column.\n // Must use refreshColumns() (COLUMNS phase) not requestRender() (ROWS phase)\n // because processColumns only runs at COLUMNS phase or higher.\n if (this.config.detailRenderer) {\n const grid = this.grid as unknown as { refreshColumns?: () => void };\n if (typeof grid.refreshColumns === 'function') {\n grid.refreshColumns();\n } else {\n // Fallback to requestRender if refreshColumns not available\n this.requestRender();\n }\n }\n }\n // #endregion\n}\n"],"names":["toggleDetailRow","expandedRows","row","newExpanded","expandDetailRow","collapseDetailRow","isDetailExpanded","createDetailElement","rowIndex","renderer","columnCount","detailRow","detailCell","content","MasterDetailPlugin","BaseGridPlugin","styles","grid","gridEl","detailEl","gridWithAdapter","adapterRenderer","animation","showExpandColumn","expandOnRowClick","collapseOnClickOutside","heightAttr","configUpdates","templateHTML","_rowIndex","evaluated","evalTemplateString","sanitizeHTML","onComplete","cleanup","columns","cols","findExpanderColumn","expanderCol","createExpanderColumnConfig","renderCtx","isExpanded","container","toggle","event","#syncDetailRows","focusCol","focusRow","column","isExpanderColumn","body","visibleRowMap","dataRows","rowEl","firstCell","existingDetails","forIndex","isStillExpanded","isRowVisible","existingDetail","totalHeight","beforeRowIndex","start","scrollTop","rowHeight","expandedIndices","index","a","b","minStart","cumulativeExtraHeight","actualRowTop","detailHeight","actualDetailBottom","indices","idx","currentRenderer"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"master-detail.umd.js","sources":["../../../../../libs/grid/src/lib/plugins/master-detail/master-detail.ts","../../../../../libs/grid/src/lib/plugins/master-detail/MasterDetailPlugin.ts"],"sourcesContent":["/**\n * Master/Detail Core Logic\n *\n * Pure functions for managing detail row expansion state.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n// Uses `any` for maximum flexibility with user-defined row types.\n\n/**\n * Toggle the expansion state of a detail row.\n * Returns a new Set with the updated state.\n */\nexport function toggleDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n if (newExpanded.has(row)) {\n newExpanded.delete(row);\n } else {\n newExpanded.add(row);\n }\n return newExpanded;\n}\n\n/**\n * Expand a detail row.\n * Returns a new Set with the row added.\n */\nexport function expandDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n newExpanded.add(row);\n return newExpanded;\n}\n\n/**\n * Collapse a detail row.\n * Returns a new Set with the row removed.\n */\nexport function collapseDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n newExpanded.delete(row);\n return newExpanded;\n}\n\n/**\n * Check if a detail row is expanded.\n */\nexport function isDetailExpanded(expandedRows: Set<object>, row: object): boolean {\n return expandedRows.has(row);\n}\n\n/**\n * Create a detail element for a given row.\n * The element spans all columns and contains the rendered content.\n */\nexport function createDetailElement(\n row: any,\n rowIndex: number,\n renderer: (row: any, rowIndex: number) => HTMLElement | string,\n columnCount: number\n): HTMLElement {\n const detailRow = document.createElement('div');\n detailRow.className = 'master-detail-row';\n detailRow.setAttribute('data-detail-for', String(rowIndex));\n detailRow.setAttribute('role', 'row');\n\n const detailCell = document.createElement('div');\n detailCell.className = 'master-detail-cell';\n detailCell.setAttribute('role', 'cell');\n detailCell.style.gridColumn = `1 / ${columnCount + 1}`;\n\n const content = renderer(row, rowIndex);\n if (typeof content === 'string') {\n detailCell.innerHTML = content;\n } else if (content instanceof HTMLElement) {\n detailCell.appendChild(content);\n }\n\n detailRow.appendChild(detailCell);\n return detailRow;\n}\n","/**\n * Master/Detail Plugin (Class-based)\n *\n * Enables expandable detail rows showing additional content for each row.\n * Animation style is plugin-configured; respects grid-level animation.mode.\n */\n\nimport { evalTemplateString, sanitizeHTML } from '../../core/internal/sanitize';\nimport { BaseGridPlugin, CellClickEvent, GridElement, RowClickEvent } from '../../core/plugin/base-plugin';\nimport { createExpanderColumnConfig, findExpanderColumn, isExpanderColumn } from '../../core/plugin/expander-column';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n collapseDetailRow,\n createDetailElement,\n expandDetailRow,\n isDetailExpanded,\n toggleDetailRow,\n} from './master-detail';\nimport styles from './master-detail.css?inline';\nimport type { DetailExpandDetail, ExpandCollapseAnimation, MasterDetailConfig } from './types';\n\n/**\n * Master-Detail Plugin for tbw-grid\n *\n * Creates expandable detail rows that reveal additional content beneath each master row.\n * Perfect for order/line-item UIs, employee/department views, or any scenario where\n * you need to show related data without navigating away.\n *\n * ## Installation\n *\n * ```ts\n * import { MasterDetailPlugin } from '@toolbox-web/grid/plugins/master-detail';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `detailRenderer` | `(row) => HTMLElement \\| string` | required | Render function for detail content |\n * | `expandOnRowClick` | `boolean` | `false` | Expand when clicking the row |\n * | `detailHeight` | `number \\| 'auto'` | `'auto'` | Fixed height or auto-size |\n * | `collapseOnClickOutside` | `boolean` | `false` | Collapse when clicking outside |\n * | `showExpandColumn` | `boolean` | `true` | Show expand/collapse column |\n * | `animation` | `false \\| 'slide' \\| 'fade'` | `'slide'` | Animation style |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `expandRow` | `(rowIndex) => void` | Expand a specific row |\n * | `collapseRow` | `(rowIndex) => void` | Collapse a specific row |\n * | `toggleRow` | `(rowIndex) => void` | Toggle row expansion |\n * | `expandAll` | `() => void` | Expand all rows |\n * | `collapseAll` | `() => void` | Collapse all rows |\n * | `isRowExpanded` | `(rowIndex) => boolean` | Check if row is expanded |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-master-detail-bg` | `var(--tbw-color-row-alt)` | Detail row background |\n * | `--tbw-master-detail-border` | `var(--tbw-color-border)` | Detail row border |\n * | `--tbw-detail-padding` | `1em` | Detail content padding |\n * | `--tbw-animation-duration` | `200ms` | Expand/collapse animation |\n *\n * @example Basic Master-Detail with HTML Template\n * ```ts\n * import '@toolbox-web/grid';\n * import { MasterDetailPlugin } from '@toolbox-web/grid/plugins/master-detail';\n *\n * grid.gridConfig = {\n * columns: [\n * { field: 'orderId', header: 'Order ID' },\n * { field: 'customer', header: 'Customer' },\n * { field: 'total', header: 'Total', type: 'currency' },\n * ],\n * plugins: [\n * new MasterDetailPlugin({\n * detailRenderer: (row) => `\n * <div class=\"order-details\">\n * <h4>Order Items</h4>\n * <ul>${row.items.map(i => `<li>${i.name} - $${i.price}</li>`).join('')}</ul>\n * </div>\n * `,\n * }),\n * ],\n * };\n * ```\n *\n * @example Nested Grid in Detail\n * ```ts\n * new MasterDetailPlugin({\n * detailRenderer: (row) => {\n * const childGrid = document.createElement('tbw-grid');\n * childGrid.style.height = '200px';\n * childGrid.gridConfig = { columns: [...] };\n * childGrid.rows = row.items || [];\n * return childGrid;\n * },\n * })\n * ```\n *\n * @see {@link MasterDetailConfig} for all configuration options\n * @see {@link DetailExpandDetail} for expand/collapse event details\n *\n * @internal Extends BaseGridPlugin\n */\nexport class MasterDetailPlugin extends BaseGridPlugin<MasterDetailConfig> {\n /** @internal */\n readonly name = 'masterDetail';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<MasterDetailConfig> {\n return {\n detailHeight: 'auto',\n expandOnRowClick: false,\n collapseOnClickOutside: false,\n // Note: showExpandColumn is intentionally NOT defaulted here.\n // If undefined, processColumns() adds expander only when detailRenderer is provided.\n // Set to true for framework adapters that register renderers asynchronously.\n animation: 'slide', // Plugin's own default\n };\n }\n\n // #region Light DOM Parsing\n\n /**\n * Called when plugin is attached to the grid.\n * Parses light DOM for `<tbw-grid-detail>` elements to configure detail templates.\n * @internal\n */\n override attach(grid: GridElement): void {\n super.attach(grid);\n this.parseLightDomDetail();\n }\n\n /**\n * Parse `<tbw-grid-detail>` elements from the grid's light DOM.\n *\n * Allows declarative configuration:\n * ```html\n * <tbw-grid [rows]=\"data\">\n * <tbw-grid-detail>\n * <div class=\"detail-content\">\n * <p>Name: {{ row.name }}</p>\n * <p>Email: {{ row.email }}</p>\n * </div>\n * </tbw-grid-detail>\n * </tbw-grid>\n * ```\n *\n * Attributes:\n * - `animation`: 'slide' | 'fade' | 'false' (default: 'slide')\n * - `show-expand-column`: 'true' | 'false' (default: 'true')\n * - `expand-on-row-click`: 'true' | 'false' (default: 'false')\n * - `collapse-on-click-outside`: 'true' | 'false' (default: 'false')\n * - `height`: number (pixels) or 'auto' (default: 'auto')\n */\n private parseLightDomDetail(): void {\n const gridEl = this.grid as unknown as Element;\n if (!gridEl || typeof gridEl.querySelector !== 'function') return;\n\n const detailEl = gridEl.querySelector('tbw-grid-detail');\n if (!detailEl) return;\n\n // Check if a framework adapter wants to handle this element\n // (e.g., Angular adapter intercepts for ng-template rendering)\n const gridWithAdapter = gridEl as unknown as {\n __frameworkAdapter?: {\n parseDetailElement?: (el: Element) => ((row: any, rowIndex: number) => HTMLElement | string) | undefined;\n };\n };\n if (gridWithAdapter.__frameworkAdapter?.parseDetailElement) {\n const adapterRenderer = gridWithAdapter.__frameworkAdapter.parseDetailElement(detailEl);\n if (adapterRenderer) {\n this.config = { ...this.config, detailRenderer: adapterRenderer };\n return;\n }\n }\n\n // Parse attributes for configuration\n const animation = detailEl.getAttribute('animation');\n const showExpandColumn = detailEl.getAttribute('show-expand-column');\n const expandOnRowClick = detailEl.getAttribute('expand-on-row-click');\n const collapseOnClickOutside = detailEl.getAttribute('collapse-on-click-outside');\n const heightAttr = detailEl.getAttribute('height');\n\n const configUpdates: Partial<MasterDetailConfig> = {};\n\n if (animation !== null) {\n configUpdates.animation = animation === 'false' ? false : (animation as 'slide' | 'fade');\n }\n if (showExpandColumn !== null) {\n configUpdates.showExpandColumn = showExpandColumn !== 'false';\n }\n if (expandOnRowClick !== null) {\n configUpdates.expandOnRowClick = expandOnRowClick === 'true';\n }\n if (collapseOnClickOutside !== null) {\n configUpdates.collapseOnClickOutside = collapseOnClickOutside === 'true';\n }\n if (heightAttr !== null) {\n configUpdates.detailHeight = heightAttr === 'auto' ? 'auto' : parseInt(heightAttr, 10);\n }\n\n // Get template content from innerHTML\n const templateHTML = detailEl.innerHTML.trim();\n if (templateHTML && !this.config.detailRenderer) {\n // Create a template-based renderer using the inner HTML\n configUpdates.detailRenderer = (row: any, _rowIndex: number): string => {\n // Evaluate template expressions like {{ row.field }}\n const evaluated = evalTemplateString(templateHTML, { value: row, row });\n // Sanitize the result to prevent XSS\n return sanitizeHTML(evaluated);\n };\n }\n\n // Merge updates into config\n if (Object.keys(configUpdates).length > 0) {\n this.config = { ...this.config, ...configUpdates };\n }\n }\n\n // #endregion\n\n // #region Animation Helpers\n\n /**\n * Get expand/collapse animation style from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide';\n }\n\n /**\n * Apply expand animation to a detail element.\n */\n private animateExpand(detailEl: HTMLElement): void {\n if (!this.isAnimationEnabled || this.animationStyle === false) return;\n\n detailEl.classList.add('tbw-expanding');\n detailEl.addEventListener(\n 'animationend',\n () => {\n detailEl.classList.remove('tbw-expanding');\n },\n { once: true },\n );\n }\n\n /**\n * Apply collapse animation to a detail element and remove after animation.\n */\n private animateCollapse(detailEl: HTMLElement, onComplete: () => void): void {\n if (!this.isAnimationEnabled || this.animationStyle === false) {\n onComplete();\n return;\n }\n\n detailEl.classList.add('tbw-collapsing');\n const cleanup = () => {\n detailEl.classList.remove('tbw-collapsing');\n onComplete();\n };\n detailEl.addEventListener('animationend', cleanup, { once: true });\n // Fallback timeout in case animation doesn't fire\n setTimeout(cleanup, this.animationDuration + 50);\n }\n\n // #endregion\n\n // #region Internal State\n private expandedRows: Set<any> = new Set();\n private detailElements: Map<any, HTMLElement> = new Map();\n\n /** Default height for detail rows when not configured */\n private static readonly DEFAULT_DETAIL_HEIGHT = 150;\n\n /**\n * Get the estimated height for a detail row.\n */\n private getDetailHeight(row: any): number {\n const detailEl = this.detailElements.get(row);\n if (detailEl) return detailEl.offsetHeight;\n return typeof this.config?.detailHeight === 'number'\n ? this.config.detailHeight\n : MasterDetailPlugin.DEFAULT_DETAIL_HEIGHT;\n }\n\n /**\n * Toggle a row's detail and emit event.\n */\n private toggleAndEmit(row: any, rowIndex: number): void {\n this.expandedRows = toggleDetailRow(this.expandedRows, row as object);\n this.emit<DetailExpandDetail>('detail-expand', {\n rowIndex,\n row: row as Record<string, unknown>,\n expanded: this.expandedRows.has(row as object),\n });\n this.requestRender();\n }\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.expandedRows.clear();\n this.detailElements.clear();\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n // Determine whether to add the expander column:\n // 1. If showExpandColumn === false: never add (explicit opt-out)\n // 2. If showExpandColumn === true: always add (explicit opt-in, for framework adapters)\n // 3. If showExpandColumn is undefined: add only if detailRenderer is provided\n //\n // This supports React/Angular adapters which register renderers asynchronously via light DOM.\n // They must set showExpandColumn: true to get the column immediately, avoiding layout shift.\n const shouldAddExpander =\n this.config.showExpandColumn === true || (this.config.showExpandColumn !== false && !!this.config.detailRenderer);\n\n if (!shouldAddExpander) {\n return [...columns];\n }\n\n const cols = [...columns];\n\n // Check if expander column already exists (from this or another plugin)\n const existingExpander = findExpanderColumn(cols);\n if (existingExpander) {\n // Another plugin already added an expander column - don't add duplicate\n // Our expand logic will be handled via onCellClick on the expander column\n return cols;\n }\n\n // Create dedicated expander column that stays fixed at position 0\n const expanderCol = createExpanderColumnConfig(this.name);\n expanderCol.viewRenderer = (renderCtx) => {\n const { row } = renderCtx;\n const isExpanded = this.expandedRows.has(row as object);\n\n const container = document.createElement('span');\n container.className = 'master-detail-expander expander-cell';\n\n // Expand/collapse toggle icon\n const toggle = document.createElement('span');\n toggle.className = `master-detail-toggle${isExpanded ? ' expanded' : ''}`;\n // Use grid-level icons (fall back to defaults)\n this.setIcon(toggle, this.resolveIcon(isExpanded ? 'collapse' : 'expand'));\n // role=\"button\" is required for aria-expanded to be valid\n toggle.setAttribute('role', 'button');\n toggle.setAttribute('tabindex', '0');\n toggle.setAttribute('aria-expanded', String(isExpanded));\n toggle.setAttribute('aria-label', isExpanded ? 'Collapse details' : 'Expand details');\n container.appendChild(toggle);\n\n return container;\n };\n\n // Prepend expander column to ensure it's always first\n return [expanderCol, ...cols];\n }\n\n /** @internal */\n override onRowClick(event: RowClickEvent): boolean | void {\n if (!this.config.expandOnRowClick || !this.config.detailRenderer) return;\n this.toggleAndEmit(event.row, event.rowIndex);\n return false;\n }\n\n /** @internal */\n override onCellClick(event: CellClickEvent): boolean | void {\n // Handle click on master-detail toggle icon (same pattern as TreePlugin)\n const target = event.originalEvent?.target as HTMLElement;\n if (target?.classList.contains('master-detail-toggle')) {\n this.toggleAndEmit(event.row, event.rowIndex);\n return true; // Prevent default handling\n }\n\n // Sync detail rows after cell click triggers refreshVirtualWindow\n // This runs in microtask to ensure DOM updates are complete\n if (this.expandedRows.size > 0) {\n queueMicrotask(() => this.#syncDetailRows());\n }\n return; // Don't prevent default\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion when focus is on the expander column\n if (event.key !== ' ') return;\n\n const focusCol = this.grid._focusCol;\n const focusRow = this.grid._focusRow;\n const column = this.columns[focusCol];\n\n // Only handle SPACE on expander column\n if (!column || !isExpanderColumn(column)) return;\n\n const row = this.rows[focusRow];\n if (!row) return;\n\n event.preventDefault();\n this.toggleAndEmit(row, focusRow);\n\n // Restore focus styling after render completes via render pipeline\n this.requestRenderWithFocus();\n return true;\n }\n\n /** @internal */\n override afterRender(): void {\n this.#syncDetailRows();\n }\n\n /**\n * Called on scroll to sync detail elements with visible rows.\n * Removes details for rows that scrolled out of view and reattaches for visible rows.\n * @internal\n */\n override onScrollRender(): void {\n if (!this.config.detailRenderer || this.expandedRows.size === 0) return;\n // Full sync needed on scroll to clean up orphaned details\n this.#syncDetailRows();\n }\n\n /**\n * Full sync of detail rows - cleans up stale elements and creates new ones.\n * Detail rows are inserted as siblings AFTER their master row to survive row rebuilds.\n */\n #syncDetailRows(): void {\n if (!this.config.detailRenderer) return;\n\n const body = this.gridElement?.querySelector('.rows');\n if (!body) return;\n\n // Build a map of row index -> row element for visible rows\n const visibleRowMap = new Map<number, Element>();\n const dataRows = body.querySelectorAll('.data-grid-row');\n const columnCount = this.columns.length;\n\n for (const rowEl of dataRows) {\n const firstCell = rowEl.querySelector('.cell[data-row]');\n const rowIndex = firstCell ? parseInt(firstCell.getAttribute('data-row') ?? '-1', 10) : -1;\n if (rowIndex >= 0) {\n visibleRowMap.set(rowIndex, rowEl);\n }\n }\n\n // Remove detail rows whose parent row is no longer visible or no longer expanded\n const existingDetails = body.querySelectorAll('.master-detail-row');\n for (const detailEl of existingDetails) {\n const forIndex = parseInt(detailEl.getAttribute('data-detail-for') ?? '-1', 10);\n const row = forIndex >= 0 ? this.rows[forIndex] : undefined;\n const isStillExpanded = row && this.expandedRows.has(row);\n const isRowVisible = visibleRowMap.has(forIndex);\n\n // Remove detail if not expanded or if parent row scrolled out\n if (!isStillExpanded || !isRowVisible) {\n detailEl.remove();\n if (row) this.detailElements.delete(row);\n }\n }\n\n // Insert detail rows for expanded rows that are visible\n for (const [rowIndex, rowEl] of visibleRowMap) {\n const row = this.rows[rowIndex];\n if (!row || !this.expandedRows.has(row)) continue;\n\n // Check if detail already exists for this row\n const existingDetail = this.detailElements.get(row);\n if (existingDetail) {\n // Ensure it's positioned correctly (as next sibling of row element)\n if (existingDetail.previousElementSibling !== rowEl) {\n rowEl.after(existingDetail);\n }\n continue;\n }\n\n // Create new detail element\n const detailEl = createDetailElement(row, rowIndex, this.config.detailRenderer, columnCount);\n\n if (typeof this.config.detailHeight === 'number') {\n detailEl.style.height = `${this.config.detailHeight}px`;\n }\n\n // Insert as sibling after the row element (not as child)\n rowEl.after(detailEl);\n this.detailElements.set(row, detailEl);\n\n // Apply expand animation\n this.animateExpand(detailEl);\n }\n }\n\n /**\n * Return total extra height from all expanded detail rows.\n * Used by grid virtualization to adjust scrollbar height.\n */\n override getExtraHeight(): number {\n let totalHeight = 0;\n for (const row of this.expandedRows) {\n totalHeight += this.getDetailHeight(row);\n }\n return totalHeight;\n }\n\n /**\n * Return extra height that appears before a given row index.\n * This is the sum of heights of all expanded details whose parent row is before the given index.\n */\n override getExtraHeightBefore(beforeRowIndex: number): number {\n let totalHeight = 0;\n for (const row of this.expandedRows) {\n const rowIndex = this.rows.indexOf(row);\n // Include detail if it's for a row before the given index\n if (rowIndex >= 0 && rowIndex < beforeRowIndex) {\n totalHeight += this.getDetailHeight(row);\n }\n }\n return totalHeight;\n }\n\n /**\n * Adjust the virtualization start index to keep expanded row visible while its detail is visible.\n * This ensures the detail scrolls smoothly out of view instead of disappearing abruptly.\n */\n override adjustVirtualStart(start: number, scrollTop: number, rowHeight: number): number {\n if (this.expandedRows.size === 0) return start;\n\n // Build sorted list of expanded row indices for cumulative height calculation\n const expandedIndices: Array<{ index: number; row: any }> = [];\n for (const row of this.expandedRows) {\n const index = this.rows.indexOf(row);\n if (index >= 0) {\n expandedIndices.push({ index, row });\n }\n }\n expandedIndices.sort((a, b) => a.index - b.index);\n\n let minStart = start;\n\n // Calculate actual scroll position for each expanded row,\n // accounting for cumulative detail heights before it\n let cumulativeExtraHeight = 0;\n\n for (const { index: rowIndex, row } of expandedIndices) {\n // Actual position includes all detail heights before this row\n const actualRowTop = rowIndex * rowHeight + cumulativeExtraHeight;\n const detailHeight = this.getDetailHeight(row);\n const actualDetailBottom = actualRowTop + rowHeight + detailHeight;\n\n // Update cumulative height for next iteration\n cumulativeExtraHeight += detailHeight;\n\n // Skip rows that are at or after the calculated start\n if (rowIndex >= start) continue;\n\n // If any part of the detail is still visible (below the scroll position),\n // we need to keep the parent row in the render range\n if (actualDetailBottom > scrollTop) {\n if (rowIndex < minStart) {\n minStart = rowIndex;\n }\n }\n }\n\n return minStart;\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Expand the detail row at the given index.\n * @param rowIndex - Index of the row to expand\n */\n expand(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = expandDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Collapse the detail row at the given index.\n * @param rowIndex - Index of the row to collapse\n */\n collapse(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = collapseDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Toggle the detail row at the given index.\n * @param rowIndex - Index of the row to toggle\n */\n toggle(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = toggleDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Check if the detail row at the given index is expanded.\n * @param rowIndex - Index of the row to check\n * @returns Whether the detail row is expanded\n */\n isExpanded(rowIndex: number): boolean {\n const row = this.rows[rowIndex];\n return row ? isDetailExpanded(this.expandedRows, row) : false;\n }\n\n /**\n * Expand all detail rows.\n */\n expandAll(): void {\n for (const row of this.rows) {\n this.expandedRows.add(row);\n }\n this.requestRender();\n }\n\n /**\n * Collapse all detail rows.\n */\n collapseAll(): void {\n this.expandedRows.clear();\n this.requestRender();\n }\n\n /**\n * Get the indices of all expanded rows.\n * @returns Array of row indices that are expanded\n */\n getExpandedRows(): number[] {\n const indices: number[] = [];\n for (const row of this.expandedRows) {\n const idx = this.rows.indexOf(row);\n if (idx >= 0) indices.push(idx);\n }\n return indices;\n }\n\n /**\n * Get the detail element for a specific row.\n * @param rowIndex - Index of the row\n * @returns The detail HTMLElement or undefined\n */\n getDetailElement(rowIndex: number): HTMLElement | undefined {\n const row = this.rows[rowIndex];\n return row ? this.detailElements.get(row) : undefined;\n }\n\n /**\n * Re-parse light DOM to refresh the detail renderer.\n * Call this after framework templates are registered (e.g., Angular ngAfterContentInit).\n *\n * This allows frameworks to register templates asynchronously and then\n * update the plugin's detailRenderer.\n */\n refreshDetailRenderer(): void {\n // Force re-parse by temporarily clearing the renderer\n const currentRenderer = this.config.detailRenderer;\n this.config = { ...this.config, detailRenderer: undefined };\n this.parseLightDomDetail();\n\n // If no new renderer was found, restore the original\n if (!this.config.detailRenderer && currentRenderer) {\n this.config = { ...this.config, detailRenderer: currentRenderer };\n }\n\n // Request a COLUMNS phase re-render so processColumns runs again with the new detailRenderer\n // This ensures the expand toggle is added to the first column.\n // Must use refreshColumns() (COLUMNS phase) not requestRender() (ROWS phase)\n // because processColumns only runs at COLUMNS phase or higher.\n if (this.config.detailRenderer) {\n const grid = this.grid as unknown as { refreshColumns?: () => void };\n if (typeof grid.refreshColumns === 'function') {\n grid.refreshColumns();\n } else {\n // Fallback to requestRender if refreshColumns not available\n this.requestRender();\n }\n }\n }\n // #endregion\n}\n"],"names":["toggleDetailRow","expandedRows","row","newExpanded","expandDetailRow","collapseDetailRow","isDetailExpanded","createDetailElement","rowIndex","renderer","columnCount","detailRow","detailCell","content","MasterDetailPlugin","BaseGridPlugin","styles","grid","gridEl","detailEl","gridWithAdapter","adapterRenderer","animation","showExpandColumn","expandOnRowClick","collapseOnClickOutside","heightAttr","configUpdates","templateHTML","_rowIndex","evaluated","evalTemplateString","sanitizeHTML","onComplete","cleanup","columns","cols","findExpanderColumn","expanderCol","createExpanderColumnConfig","renderCtx","isExpanded","container","toggle","event","#syncDetailRows","focusCol","focusRow","column","isExpanderColumn","body","visibleRowMap","dataRows","rowEl","firstCell","existingDetails","forIndex","isStillExpanded","isRowVisible","existingDetail","totalHeight","beforeRowIndex","start","scrollTop","rowHeight","expandedIndices","index","a","b","minStart","cumulativeExtraHeight","actualRowTop","detailHeight","actualDetailBottom","indices","idx","currentRenderer"],"mappings":"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"}
|