@toolbox-web/grid 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -22
- package/all.js +619 -571
- package/all.js.map +1 -1
- package/index.js +362 -302
- package/index.js.map +1 -1
- package/lib/core/grid.d.ts +64 -1
- 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/types.d.ts +17 -0
- package/lib/core/types.d.ts.map +1 -1
- package/lib/plugins/clipboard/index.js +82 -76
- 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 +43 -41
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/index.js +24 -22
- 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 +83 -52
- package/lib/plugins/editing/index.js.map +1 -1
- package/lib/plugins/export/index.js +22 -20
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/FilteringPlugin.d.ts +11 -1
- package/lib/plugins/filtering/FilteringPlugin.d.ts.map +1 -1
- package/lib/plugins/filtering/index.js +160 -125
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/index.js +20 -18
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-rows/index.js +66 -64
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/index.js +51 -49
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/multi-sort/index.js +17 -15
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/index.js +24 -22
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-rows/index.js +25 -23
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pivot/index.js +49 -47
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/reorder/index.js +24 -22
- package/lib/plugins/reorder/index.js.map +1 -1
- package/lib/plugins/responsive/index.js +19 -17
- package/lib/plugins/responsive/index.js.map +1 -1
- package/lib/plugins/row-reorder/index.js +38 -36
- package/lib/plugins/row-reorder/index.js.map +1 -1
- 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 +118 -85
- 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 +34 -32
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/tree/index.js +25 -23
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/undo-redo/index.js +22 -20
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/index.js +21 -19
- package/lib/plugins/visibility/index.js.map +1 -1
- package/package.json +21 -4
- package/public.d.ts +1 -1
- package/public.d.ts.map +1 -1
- package/umd/grid.all.umd.js +19 -19
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +9 -9
- 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/filtering.umd.js +1 -1
- package/umd/plugins/filtering.umd.js.map +1 -1
- 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(g,C){typeof exports=="object"&&typeof module<"u"?C(exports,require("../../core/internal/virtualization"),require("../../core/plugin/base-plugin"),require("../../core/plugin/expander-column")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/virtualization","../../core/plugin/base-plugin","../../core/plugin/expander-column"],C):(g=typeof globalThis<"u"?globalThis:g||self,C(g.TbwGridPlugin_filtering={},g.TbwGrid,g.TbwGrid,g.TbwGrid))})(this,(function(g,C,M,z){"use strict";function _(v,e,t=!1){const r=v[e.field];if(e.operator==="blank")return r==null||r==="";if(e.operator==="notBlank")return r!=null&&r!=="";if(r==null)return!1;const n=String(r),l=t?n:n.toLowerCase(),a=t?String(e.value):String(e.value).toLowerCase();switch(e.operator){case"contains":return l.includes(a);case"notContains":return!l.includes(a);case"equals":return l===a;case"notEquals":return l!==a;case"startsWith":return l.startsWith(a);case"endsWith":return l.endsWith(a);case"lessThan":return Number(r)<Number(e.value);case"lessThanOrEqual":return Number(r)<=Number(e.value);case"greaterThan":return Number(r)>Number(e.value);case"greaterThanOrEqual":return Number(r)>=Number(e.value);case"between":return Number(r)>=Number(e.value)&&Number(r)<=Number(e.valueTo);case"in":return Array.isArray(e.value)&&e.value.includes(r);case"notIn":return Array.isArray(e.value)&&!e.value.includes(r);default:return!0}}function q(v,e,t=!1){return e.length?v.filter(r=>e.every(n=>_(r,n,t))):v}function B(v){return JSON.stringify(v.map(e=>({field:e.field,operator:e.operator,value:e.value,valueTo:e.valueTo})))}function I(v,e){const t=new Set;for(const r of v){const n=r[e];n!=null&&t.add(n)}return[...t].sort((r,n)=>typeof r=="number"&&typeof n=="number"?r-n:String(r).localeCompare(String(n)))}const Y='@layer tbw-plugins{tbw-grid .tbw-quick-filter-input{flex:1;max-width:300px;height:var(--tbw-input-height, 1.75rem);padding:var(--tbw-input-padding, 0 .5rem);border:1px solid var(--tbw-color-border);border-radius:var(--tbw-border-radius);background:var(--tbw-color-bg);color:var(--tbw-color-fg);font-size:var(--tbw-font-size-sm, .8125rem)}tbw-grid .tbw-quick-filter-input:focus{outline:none;border-color:var(--tbw-color-accent)}tbw-grid .header-cell.filtered:before{content:"";position:absolute;top:var(--tbw-spacing-xs, .25rem);right:var(--tbw-spacing-xs, .25rem);width:var(--tbw-indicator-size, .375rem);height:var(--tbw-indicator-size, .375rem);background:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));border-radius:50%}tbw-grid .tbw-filter-btn{display:inline-flex;align-items:center;justify-content:center;background:transparent;border:none;cursor:pointer;padding:2px;margin-left:var(--tbw-spacing-xs, .25rem);opacity:.4;transition:opacity .15s;color:inherit;vertical-align:middle}tbw-grid .tbw-filter-btn:hover,tbw-grid .tbw-filter-btn.active{opacity:1}tbw-grid .tbw-filter-btn.active{color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6))}}',G="@layer tbw-plugins{.tbw-filter-panel{position:fixed;background:var(--tbw-filter-panel-bg, var(--tbw-color-panel-bg, light-dark(#eeeeee, #222222)));color:var(--tbw-filter-panel-fg, var(--tbw-color-fg, light-dark(#222222, #eeeeee)));border:1px solid var(--tbw-filter-panel-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-filter-panel-radius, var(--tbw-border-radius, .25rem));box-shadow:0 4px 16px var(--tbw-filter-panel-shadow, var(--tbw-color-shadow, light-dark(rgba(0, 0, 0, .1), rgba(0, 0, 0, .3))));padding:var(--tbw-panel-padding, var(--tbw-spacing-lg, .75rem));z-index:10000;min-width:200px;max-width:280px;max-height:350px;display:flex;flex-direction:column;font-family:var(--tbw-font-family, system-ui, sans-serif);font-size:var(--tbw-font-size, .8125rem);transform-origin:top center}.tbw-filter-panel.tbw-filter-panel-above{transform-origin:bottom center}.tbw-filter-panel.tbw-filter-panel-animated{animation:tbw-filter-panel-enter var(--tbw-animation-duration, .15s) var(--tbw-animation-easing, ease-out)}.tbw-filter-panel.tbw-filter-panel-above.tbw-filter-panel-animated{animation:tbw-filter-panel-enter-above var(--tbw-animation-duration, .15s) var(--tbw-animation-easing, ease-out)}@keyframes tbw-filter-panel-enter{0%{opacity:0;transform:scaleY(.3) translateY(-10px)}to{opacity:1;transform:scaleY(1) translateY(0)}}@keyframes tbw-filter-panel-enter-above{0%{opacity:0;transform:scaleY(.3) translateY(10px)}to{opacity:1;transform:scaleY(1) translateY(0)}}@supports (anchor-name: --test){.tbw-filter-panel{position-anchor:--tbw-filter-anchor;top:anchor(bottom);left:anchor(left);margin-top:4px;position-try-fallbacks:flip-inline,flip-block,flip-block flip-inline}}.tbw-filter-search{margin-bottom:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem))}.tbw-filter-search-input{width:100%;padding:var(--tbw-button-padding, .375rem .625rem);background:var(--tbw-filter-input-bg, var(--tbw-color-bg, transparent));color:inherit;border:1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-filter-input-radius, var(--tbw-border-radius, .25rem));font-size:inherit;box-sizing:border-box}.tbw-filter-search-input:focus{outline:none;border-color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));box-shadow:0 0 0 2px rgba(from var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6)) r g b / 15%)}.tbw-filter-actions{display:flex;padding:var(--tbw-button-padding-sm, .25rem .125rem);margin-bottom:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));border-bottom:1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)))}.tbw-filter-action-btn{background:transparent;border:none;color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));cursor:pointer;font-size:var(--tbw-font-size-xs, .75rem);padding:2px 0}.tbw-filter-action-btn:hover{text-decoration:underline}.tbw-filter-values{flex:1;overflow-y:auto;margin-bottom:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));max-height:180px;position:relative}.tbw-filter-values-spacer{width:1px}.tbw-filter-values-content{position:absolute;top:0;left:0;right:0}.tbw-filter-value-item{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));padding:var(--tbw-button-padding-sm, .25rem .125rem);cursor:pointer;border-radius:3px}.tbw-filter-value-item:hover{background:var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)))}.tbw-filter-checkbox{margin:0;cursor:pointer;accent-color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6))}.tbw-filter-no-match{color:var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));padding:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem)) 0;text-align:center;font-style:italic}.tbw-filter-buttons{display:flex;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));padding-top:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));border-top:1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)))}.tbw-filter-apply-btn{flex:1;padding:var(--tbw-button-padding, .375rem .75rem);background:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));color:var(--tbw-filter-accent-fg, var(--tbw-color-accent-fg, light-dark(#ffffff, #000000)));border:none;border-radius:var(--tbw-border-radius, .25rem);cursor:pointer;font-size:var(--tbw-font-size-sm, .8125rem)}.tbw-filter-apply-btn:hover{filter:brightness(.9)}.tbw-filter-clear-btn{flex:1;padding:var(--tbw-button-padding, .375rem .75rem);background:transparent;color:var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));border:1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-border-radius, .25rem);cursor:pointer;font-size:var(--tbw-font-size-sm, .8125rem)}.tbw-filter-clear-btn:hover{background:var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)))}}";class p extends M.BaseGridPlugin{name="filtering";styles=Y;get defaultConfig(){return{debounceMs:300,caseSensitive:!1,trimInput:!0,useWorker:!0}}filters=new Map;cachedResult=null;cacheKey=null;openPanelField=null;panelElement=null;panelAnchorElement=null;searchText=new Map;excludedValues=new Map;panelAbortController=null;globalStylesInjected=!1;static LIST_ITEM_HEIGHT=28;static LIST_OVERSCAN=3;static LIST_BYPASS_THRESHOLD=50;syncExcludedValues(e,t){t?t.type==="set"&&t.operator==="notIn"&&Array.isArray(t.value)?this.excludedValues.set(e,new Set(t.value)):t.type==="set"&&this.excludedValues.delete(e):this.excludedValues.delete(e)}attach(e){super.attach(e),this.injectGlobalStyles()}detach(){this.filters.clear(),this.cachedResult=null,this.cacheKey=null,this.openPanelField=null,this.panelElement&&(this.panelElement.remove(),this.panelElement=null),this.searchText.clear(),this.excludedValues.clear(),this.panelAbortController?.abort(),this.panelAbortController=null}processRows(e){const t=[...this.filters.values()];if(!t.length)return[...e];if(this.config.filterHandler)return this.cachedResult?this.cachedResult:[...e];const r=B(t);if(this.cacheKey===r&&this.cachedResult)return this.cachedResult;const n=q([...e],t,this.config.caseSensitive);return this.cachedResult=n,this.cacheKey=r,n}afterRender(){const e=this.gridElement;if(!e)return;e.querySelectorAll('[part~="header-cell"]').forEach(r=>{const n=r.getAttribute("data-col");if(n===null)return;const l=this.visibleColumns[parseInt(n,10)];if(!l||l.filterable===!1||z.isUtilityColumn(l))return;const a=l.field;if(!a)return;const c=this.filters.has(a);let d=r.querySelector(".tbw-filter-btn");if(d){d.classList.toggle("active",c),r.classList.toggle("filtered",c);return}d=document.createElement("button"),d.className="tbw-filter-btn",d.setAttribute("aria-label",`Filter ${l.header??a}`),d.innerHTML='<svg viewBox="0 0 16 16" width="12" height="12"><path fill="currentColor" d="M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/></svg>',c&&(d.classList.add("active"),r.classList.add("filtered")),d.addEventListener("click",b=>{b.stopPropagation(),this.toggleFilterPanel(a,l,d)});const h=r.querySelector(".resize-handle");h?r.insertBefore(d,h):r.appendChild(d)})}setFilter(e,t){if(t===null)this.filters.delete(e),this.syncExcludedValues(e,null);else{const r={...t,field:e};this.filters.set(e,r),this.syncExcludedValues(e,r)}this.cachedResult=null,this.cacheKey=null,this.emit("filter-change",{filters:[...this.filters.values()],filteredRowCount:0}),this.requestRender()}getFilter(e){return this.filters.get(e)}getFilters(){return[...this.filters.values()]}getFilterModel(){return this.getFilters()}setFilterModel(e){this.filters.clear(),this.excludedValues.clear();for(const t of e)this.filters.set(t.field,t),this.syncExcludedValues(t.field,t);this.cachedResult=null,this.cacheKey=null,this.emit("filter-change",{filters:[...this.filters.values()],filteredRowCount:0}),this.requestRender()}clearAllFilters(){this.filters.clear(),this.excludedValues.clear(),this.searchText.clear(),this.applyFiltersInternal()}clearFieldFilter(e){this.filters.delete(e),this.excludedValues.delete(e),this.searchText.delete(e),this.applyFiltersInternal()}isFieldFiltered(e){return this.filters.has(e)}getFilteredRowCount(){return this.cachedResult?.length??this.rows.length}getActiveFilters(){return this.getFilters()}getUniqueValues(e){return I(this.sourceRows,e)}injectGlobalStyles(){if(this.globalStylesInjected)return;if(document.getElementById("tbw-filter-panel-styles")){this.globalStylesInjected=!0;return}const e=document.createElement("style");e.id="tbw-filter-panel-styles",e.textContent=G,document.head.appendChild(e),this.globalStylesInjected=!0}toggleFilterPanel(e,t,r){if(this.openPanelField===e){this.closeFilterPanel();return}this.closeFilterPanel();const n=document.createElement("div");if(n.className="tbw-filter-panel",this.isAnimationEnabled&&n.classList.add("tbw-filter-panel-animated"),this.panelElement=n,this.openPanelField=e,this.config.valuesHandler){n.innerHTML='<div class="tbw-filter-loading">Loading...</div>',document.body.appendChild(n),this.positionPanel(n,r),this.setupPanelCloseHandler(n,r),this.config.valuesHandler(e,t).then(a=>{this.openPanelField!==e||!this.panelElement||(n.innerHTML="",this.renderPanelContent(e,t,n,a))});return}const l=I(this.sourceRows,e);this.renderPanelContent(e,t,n,l),document.body.appendChild(n),this.positionPanel(n,r),this.setupPanelCloseHandler(n,r)}renderPanelContent(e,t,r,n){let l=this.excludedValues.get(e);l||(l=new Set,this.excludedValues.set(e,l));const a=this.searchText.get(e)??"",c={field:e,column:t,uniqueValues:n,excludedValues:l,searchText:a,applySetFilter:h=>{this.applySetFilter(e,h),this.closeFilterPanel()},applyTextFilter:(h,b,S)=>{this.applyTextFilter(e,h,b,S),this.closeFilterPanel()},clearFilter:()=>{this.clearFieldFilter(e),this.closeFilterPanel()},closePanel:()=>this.closeFilterPanel()};let d=!1;this.config.filterPanelRenderer&&(this.config.filterPanelRenderer(r,c),d=r.children.length>0),d||this.renderDefaultFilterPanel(r,c,n,l)}setupPanelCloseHandler(e,t){this.panelAbortController=new AbortController,setTimeout(()=>{document.addEventListener("click",r=>{!e.contains(r.target)&&r.target!==t&&this.closeFilterPanel()},{signal:this.panelAbortController?.signal})},0)}closeFilterPanel(){const e=this.panelElement;e&&(e.remove(),this.panelElement=null),this.panelAnchorElement&&(this.panelAnchorElement.style.anchorName="",this.panelAnchorElement=null),this.openPanelField=null,this.panelAbortController?.abort(),this.panelAbortController=null}static supportsAnchorPositioning=null;static checkAnchorPositioningSupport(){return p.supportsAnchorPositioning===null&&(p.supportsAnchorPositioning=CSS.supports("anchor-name","--test")),p.supportsAnchorPositioning}positionPanel(e,t){const n=t.closest(".cell")??t;if(n.style.anchorName="--tbw-filter-anchor",this.panelAnchorElement=n,p.checkAnchorPositioningSupport()){requestAnimationFrame(()=>{const a=e.getBoundingClientRect(),c=n.getBoundingClientRect();a.top<c.top&&e.classList.add("tbw-filter-panel-above")});return}const l=n.getBoundingClientRect();e.style.position="fixed",e.style.top=`${l.bottom+4}px`,e.style.left=`${l.left}px`,requestAnimationFrame(()=>{const a=e.getBoundingClientRect();a.right>window.innerWidth-8&&(e.style.left=`${l.right-a.width}px`),a.bottom>window.innerHeight-8&&(e.style.top=`${l.top-a.height-4}px`,e.classList.add("tbw-filter-panel-above"))})}renderDefaultFilterPanel(e,t,r,n){const{field:l}=t,a=document.createElement("div");a.className="tbw-filter-search";const c=document.createElement("input");c.type="text",c.placeholder="Search...",c.className="tbw-filter-search-input",c.value=this.searchText.get(l)??"",a.appendChild(c),e.appendChild(a);const d=document.createElement("div");d.className="tbw-filter-actions";const h=document.createElement("label");h.className="tbw-filter-value-item",h.style.padding="0",h.style.margin="0";const b=document.createElement("input");b.type="checkbox",b.className="tbw-filter-checkbox";const S=document.createElement("span");S.textContent="Select All",h.appendChild(b),h.appendChild(S),d.appendChild(h);const A=()=>{const i=[...x.values()],u=i.every(s=>s),w=i.every(s=>!s);b.checked=u,b.indeterminate=!u&&!w};b.addEventListener("change",()=>{const i=b.checked;for(const u of x.keys())x.set(u,i);A(),L()}),e.appendChild(d);const y=document.createElement("div");y.className="tbw-filter-values";const T=document.createElement("div");T.className="tbw-filter-values-spacer",y.appendChild(T);const m=document.createElement("div");m.className="tbw-filter-values-content",y.appendChild(m);const x=new Map;r.forEach(i=>{const u=i==null?"__null__":String(i);x.set(u,!n.has(i))}),A();let E=[];const P=(i,u)=>{const w=i==null?"(Blank)":String(i),s=i==null?"__null__":String(i),o=document.createElement("label");o.className="tbw-filter-value-item",o.style.position="absolute",o.style.top=`${u*p.LIST_ITEM_HEIGHT}px`,o.style.left="0",o.style.right="0",o.style.height=`${p.LIST_ITEM_HEIGHT}px`,o.style.boxSizing="border-box";const f=document.createElement("input");f.type="checkbox",f.className="tbw-filter-checkbox",f.checked=x.get(s)??!0,f.dataset.value=s,f.addEventListener("change",()=>{x.set(s,f.checked),A()});const V=document.createElement("span");return V.textContent=w,o.appendChild(f),o.appendChild(V),o},L=()=>{const i=E.length,u=y.clientHeight,w=y.scrollTop;if(T.style.height=`${i*p.LIST_ITEM_HEIGHT}px`,C.shouldBypassVirtualization(i,p.LIST_BYPASS_THRESHOLD/3)){m.innerHTML="",m.style.transform="translateY(0px)",E.forEach((o,f)=>{m.appendChild(P(o,f))});return}const s=C.computeVirtualWindow({totalRows:i,viewportHeight:u,scrollTop:w,rowHeight:p.LIST_ITEM_HEIGHT,overscan:p.LIST_OVERSCAN});m.style.transform=`translateY(${s.offsetY}px)`,m.innerHTML="";for(let o=s.start;o<s.end;o++)m.appendChild(P(E[o],o-s.start))},N=i=>{const u=this.config.caseSensitive??!1,w=u?i:i.toLowerCase();if(E=r.filter(s=>{const o=s==null?"(Blank)":String(s),f=u?o:o.toLowerCase();return!i||f.includes(w)}),E.length===0){T.style.height="0px",m.innerHTML="";const s=document.createElement("div");s.className="tbw-filter-no-match",s.textContent="No matching values",m.appendChild(s);return}L()};y.addEventListener("scroll",()=>{E.length>0&&L()},{passive:!0}),N(c.value),e.appendChild(y);let H;c.addEventListener("input",()=>{clearTimeout(H),H=setTimeout(()=>{this.searchText.set(l,c.value),N(c.value)},this.config.debounceMs??150)});const k=document.createElement("div");k.className="tbw-filter-buttons";const F=document.createElement("button");F.className="tbw-filter-apply-btn",F.textContent="Apply",F.addEventListener("click",()=>{const i=[];for(const[u,w]of x)if(!w)if(u==="__null__")i.push(null);else{const s=r.find(o=>String(o)===u);i.push(s!==void 0?s:u)}t.applySetFilter(i)}),k.appendChild(F);const R=document.createElement("button");R.className="tbw-filter-clear-btn",R.textContent="Clear Filter",R.addEventListener("click",()=>{t.clearFilter()}),k.appendChild(R),e.appendChild(k)}applySetFilter(e,t){this.excludedValues.set(e,new Set(t)),t.length===0?this.filters.delete(e):this.filters.set(e,{field:e,type:"set",operator:"notIn",value:t}),this.applyFiltersInternal()}applyTextFilter(e,t,r,n){this.filters.set(e,{field:e,type:"text",operator:t,value:r,valueTo:n}),this.applyFiltersInternal()}applyFiltersInternal(){this.cachedResult=null,this.cacheKey=null;const e=[...this.filters.values()];if(this.config.filterHandler){const t=this.grid;t.setAttribute("aria-busy","true");const r=this.config.filterHandler(e,this.sourceRows),n=l=>{t.removeAttribute("aria-busy"),this.cachedResult=l,this.grid.rows=l,this.emit("filter-change",{filters:e,filteredRowCount:l.length}),this.requestRender()};r&&typeof r.then=="function"?r.then(n):n(r);return}this.emit("filter-change",{filters:e,filteredRowCount:0}),this.requestRender()}getColumnState(e){const t=this.filters.get(e);if(t)return{filter:{type:t.type,operator:t.operator,value:t.value,valueTo:t.valueTo}}}applyColumnState(e,t){if(!t.filter){this.filters.delete(e);return}const r={field:e,type:t.filter.type,operator:t.filter.operator,value:t.filter.value,valueTo:t.filter.valueTo};this.filters.set(e,r),this.cachedResult=null,this.cacheKey=null}}g.FilteringPlugin=p,Object.defineProperty(g,Symbol.toStringTag,{value:"Module"})}));
|
|
1
|
+
(function(m,E){typeof exports=="object"&&typeof module<"u"?E(exports,require("../../core/internal/virtualization"),require("../../core/plugin/base-plugin"),require("../../core/plugin/expander-column")):typeof define=="function"&&define.amd?define(["exports","../../core/internal/virtualization","../../core/plugin/base-plugin","../../core/plugin/expander-column"],E):(m=typeof globalThis<"u"?globalThis:m||self,E(m.TbwGridPlugin_filtering={},m.TbwGrid,m.TbwGrid,m.TbwGrid))})(this,(function(m,E,M,q){"use strict";function _(y,e,t=!1){const r=y[e.field];if(e.operator==="blank")return r==null||r==="";if(e.operator==="notBlank")return r!=null&&r!=="";if(r==null)return!1;const n=String(r),i=t?n:n.toLowerCase(),l=t?String(e.value):String(e.value).toLowerCase();switch(e.operator){case"contains":return i.includes(l);case"notContains":return!i.includes(l);case"equals":return i===l;case"notEquals":return i!==l;case"startsWith":return i.startsWith(l);case"endsWith":return i.endsWith(l);case"lessThan":return Number(r)<Number(e.value);case"lessThanOrEqual":return Number(r)<=Number(e.value);case"greaterThan":return Number(r)>Number(e.value);case"greaterThanOrEqual":return Number(r)>=Number(e.value);case"between":return Number(r)>=Number(e.value)&&Number(r)<=Number(e.valueTo);case"in":return Array.isArray(e.value)&&e.value.includes(r);case"notIn":return Array.isArray(e.value)&&!e.value.includes(r);default:return!0}}function B(y,e,t=!1){return e.length?y.filter(r=>e.every(n=>_(r,n,t))):y}function Y(y){return JSON.stringify(y.map(e=>({field:e.field,operator:e.operator,value:e.value,valueTo:e.valueTo})))}function P(y,e){const t=new Set;for(const r of y){const n=r[e];n!=null&&t.add(n)}return[...t].sort((r,n)=>typeof r=="number"&&typeof n=="number"?r-n:String(r).localeCompare(String(n)))}const G='@layer tbw-plugins{tbw-grid .tbw-quick-filter-input{flex:1;max-width:300px;height:var(--tbw-input-height, 1.75rem);padding:var(--tbw-input-padding, 0 .5rem);border:1px solid var(--tbw-color-border);border-radius:var(--tbw-border-radius);background:var(--tbw-color-bg);color:var(--tbw-color-fg);font-size:var(--tbw-font-size-sm, .8125rem)}tbw-grid .tbw-quick-filter-input:focus{outline:none;border-color:var(--tbw-color-accent)}tbw-grid .header-cell.filtered:before{content:"";position:absolute;top:var(--tbw-spacing-xs, .25rem);right:var(--tbw-spacing-xs, .25rem);width:var(--tbw-indicator-size, .375rem);height:var(--tbw-indicator-size, .375rem);background:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));border-radius:50%}tbw-grid .tbw-filter-btn{display:var(--tbw-filter-btn-display, inline-flex);visibility:var(--tbw-filter-btn-visibility, visible);align-items:center;justify-content:center;background:transparent;border:none;cursor:pointer;padding:2px;margin-left:var(--tbw-spacing-xs, .25rem);opacity:.4;transition:opacity .15s,visibility 0s;color:inherit;vertical-align:middle}tbw-grid .tbw-filter-btn:hover,tbw-grid .tbw-filter-btn.active{opacity:1;visibility:visible}tbw-grid .tbw-filter-btn.active{color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6))}}',K="@layer tbw-plugins{.tbw-filter-panel{position:fixed;background:var(--tbw-filter-panel-bg, var(--tbw-color-panel-bg, light-dark(#eeeeee, #222222)));color:var(--tbw-filter-panel-fg, var(--tbw-color-fg, light-dark(#222222, #eeeeee)));border:1px solid var(--tbw-filter-panel-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-filter-panel-radius, var(--tbw-border-radius, .25rem));box-shadow:0 4px 16px var(--tbw-filter-panel-shadow, var(--tbw-color-shadow, light-dark(rgba(0, 0, 0, .1), rgba(0, 0, 0, .3))));padding:var(--tbw-panel-padding, var(--tbw-spacing-lg, .75rem));z-index:10000;min-width:200px;max-width:280px;max-height:350px;display:flex;flex-direction:column;font-family:var(--tbw-font-family, system-ui, sans-serif);font-size:var(--tbw-font-size, .8125rem);transform-origin:top center}.tbw-filter-panel.tbw-filter-panel-above{transform-origin:bottom center}.tbw-filter-panel.tbw-filter-panel-animated{animation:tbw-filter-panel-enter var(--tbw-animation-duration, .15s) var(--tbw-animation-easing, ease-out)}.tbw-filter-panel.tbw-filter-panel-above.tbw-filter-panel-animated{animation:tbw-filter-panel-enter-above var(--tbw-animation-duration, .15s) var(--tbw-animation-easing, ease-out)}@keyframes tbw-filter-panel-enter{0%{opacity:0;transform:scaleY(.3) translateY(-10px)}to{opacity:1;transform:scaleY(1) translateY(0)}}@keyframes tbw-filter-panel-enter-above{0%{opacity:0;transform:scaleY(.3) translateY(10px)}to{opacity:1;transform:scaleY(1) translateY(0)}}@supports (anchor-name: --test){.tbw-filter-panel{position-anchor:--tbw-filter-anchor;top:anchor(bottom);left:anchor(left);margin-top:4px;position-try-fallbacks:flip-inline,flip-block,flip-block flip-inline}}.tbw-filter-search{margin-bottom:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem))}.tbw-filter-search-input{width:100%;padding:var(--tbw-filter-search-padding, var(--tbw-spacing-sm, .375rem) var(--tbw-spacing-md, .5rem));background:var(--tbw-filter-input-bg, var(--tbw-color-bg, transparent));color:inherit;border:1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-filter-input-radius, var(--tbw-border-radius, .25rem));font-size:inherit;box-sizing:border-box}.tbw-filter-search-input:focus{outline:none;border-color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));box-shadow:0 0 0 2px rgba(from var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6)) r g b / 15%)}.tbw-filter-actions{display:flex;padding:var(--tbw-button-padding-sm, .25rem .125rem);margin-bottom:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));border-bottom:1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)))}.tbw-filter-action-btn{background:transparent;border:none;color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));cursor:pointer;font-size:var(--tbw-font-size-xs, .75rem);padding:2px 0}.tbw-filter-action-btn:hover{text-decoration:underline}.tbw-filter-values{flex:1;overflow-y:auto;margin-bottom:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));max-height:180px;position:relative}.tbw-filter-values-spacer{width:1px}.tbw-filter-values-content{position:absolute;top:0;left:0;right:0}.tbw-filter-value-item{display:flex;align-items:center;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));padding:var(--tbw-button-padding-sm, .25rem .125rem);cursor:pointer;border-radius:3px;min-height:var(--tbw-filter-item-height, 28px)}.tbw-filter-value-item:hover{background:var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)))}.tbw-filter-checkbox{margin:0;cursor:pointer;accent-color:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6))}.tbw-filter-no-match{color:var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));padding:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem)) 0;text-align:center;font-style:italic}.tbw-filter-buttons{display:flex;gap:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));padding-top:var(--tbw-panel-gap, var(--tbw-spacing-md, .5rem));border-top:1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)))}.tbw-filter-apply-btn{flex:1;padding:var(--tbw-filter-btn-padding, var(--tbw-button-padding, .375rem .75rem));background:var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));color:var(--tbw-filter-accent-fg, var(--tbw-color-accent-fg, light-dark(#ffffff, #000000)));border:none;border-radius:var(--tbw-border-radius, .25rem);cursor:pointer;font-size:var(--tbw-font-size-sm, .8125rem);font-weight:var(--tbw-filter-btn-font-weight, 500);min-height:var(--tbw-filter-btn-min-height, auto)}.tbw-filter-apply-btn:hover{filter:brightness(.9)}.tbw-filter-clear-btn{flex:1;padding:var(--tbw-filter-btn-padding, var(--tbw-button-padding, .375rem .75rem));background:transparent;color:var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));border:1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));border-radius:var(--tbw-border-radius, .25rem);cursor:pointer;font-size:var(--tbw-font-size-sm, .8125rem);font-weight:var(--tbw-filter-btn-font-weight, 500);min-height:var(--tbw-filter-btn-min-height, auto)}.tbw-filter-clear-btn:hover{background:var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)))}}";class g extends M.BaseGridPlugin{name="filtering";styles=G;get defaultConfig(){return{debounceMs:300,caseSensitive:!1,trimInput:!0,useWorker:!0}}filters=new Map;cachedResult=null;cacheKey=null;openPanelField=null;panelElement=null;panelAnchorElement=null;searchText=new Map;excludedValues=new Map;panelAbortController=null;globalStylesInjected=!1;static DEFAULT_LIST_ITEM_HEIGHT=28;static LIST_OVERSCAN=3;static LIST_BYPASS_THRESHOLD=50;getListItemHeight(){if(this.panelElement){const e=getComputedStyle(this.panelElement).getPropertyValue("--tbw-filter-item-height");if(e&&e.trim()){const t=parseFloat(e);if(!isNaN(t)&&t>0)return t}}return g.DEFAULT_LIST_ITEM_HEIGHT}syncExcludedValues(e,t){t?t.type==="set"&&t.operator==="notIn"&&Array.isArray(t.value)?this.excludedValues.set(e,new Set(t.value)):t.type==="set"&&this.excludedValues.delete(e):this.excludedValues.delete(e)}attach(e){super.attach(e),this.injectGlobalStyles()}detach(){this.filters.clear(),this.cachedResult=null,this.cacheKey=null,this.openPanelField=null,this.panelElement&&(this.panelElement.remove(),this.panelElement=null),this.searchText.clear(),this.excludedValues.clear(),this.panelAbortController?.abort(),this.panelAbortController=null}processRows(e){const t=[...this.filters.values()];if(!t.length)return[...e];if(this.config.filterHandler)return this.cachedResult?this.cachedResult:[...e];const r=Y(t);if(this.cacheKey===r&&this.cachedResult)return this.cachedResult;const n=B([...e],t,this.config.caseSensitive);return this.cachedResult=n,this.cacheKey=r,n}afterRender(){const e=this.gridElement;if(!e)return;e.querySelectorAll('[part~="header-cell"]').forEach(r=>{const n=r.getAttribute("data-col");if(n===null)return;const i=this.visibleColumns[parseInt(n,10)];if(!i||i.filterable===!1||q.isUtilityColumn(i))return;const l=i.field;if(!l)return;const u=this.filters.has(l);let a=r.querySelector(".tbw-filter-btn");if(a){const h=a.classList.contains("active");if(a.classList.toggle("active",u),r.classList.toggle("filtered",u),h!==u){const k=u?"filterActive":"filter";this.setIcon(a,this.resolveIcon(k))}return}a=document.createElement("button"),a.className="tbw-filter-btn",a.setAttribute("aria-label",`Filter ${i.header??l}`);const w=u?"filterActive":"filter";this.setIcon(a,this.resolveIcon(w)),u&&(a.classList.add("active"),r.classList.add("filtered")),a.addEventListener("click",h=>{h.stopPropagation(),this.toggleFilterPanel(l,i,a)});const p=r.querySelector(".resize-handle");p?r.insertBefore(a,p):r.appendChild(a)})}setFilter(e,t){if(t===null)this.filters.delete(e),this.syncExcludedValues(e,null);else{const r={...t,field:e};this.filters.set(e,r),this.syncExcludedValues(e,r)}this.cachedResult=null,this.cacheKey=null,this.emit("filter-change",{filters:[...this.filters.values()],filteredRowCount:0}),this.requestRender()}getFilter(e){return this.filters.get(e)}getFilters(){return[...this.filters.values()]}getFilterModel(){return this.getFilters()}setFilterModel(e){this.filters.clear(),this.excludedValues.clear();for(const t of e)this.filters.set(t.field,t),this.syncExcludedValues(t.field,t);this.cachedResult=null,this.cacheKey=null,this.emit("filter-change",{filters:[...this.filters.values()],filteredRowCount:0}),this.requestRender()}clearAllFilters(){this.filters.clear(),this.excludedValues.clear(),this.searchText.clear(),this.applyFiltersInternal()}clearFieldFilter(e){this.filters.delete(e),this.excludedValues.delete(e),this.searchText.delete(e),this.applyFiltersInternal()}isFieldFiltered(e){return this.filters.has(e)}getFilteredRowCount(){return this.cachedResult?.length??this.rows.length}getActiveFilters(){return this.getFilters()}getUniqueValues(e){return P(this.sourceRows,e)}copyGridThemeContext(e){const t=this.gridElement;if(!t)return;for(const n of t.classList)n.startsWith("tbw-")||n==="selecting"||e.classList.add(n);const r=t.dataset.theme;r&&(e.dataset.theme=r)}injectGlobalStyles(){if(this.globalStylesInjected)return;if(document.getElementById("tbw-filter-panel-styles")){this.globalStylesInjected=!0;return}const e=document.createElement("style");e.id="tbw-filter-panel-styles",e.textContent=K,document.head.appendChild(e),this.globalStylesInjected=!0}toggleFilterPanel(e,t,r){if(this.openPanelField===e){this.closeFilterPanel();return}this.closeFilterPanel();const n=document.createElement("div");if(n.className="tbw-filter-panel",this.copyGridThemeContext(n),this.isAnimationEnabled&&n.classList.add("tbw-filter-panel-animated"),this.panelElement=n,this.openPanelField=e,this.config.valuesHandler){n.innerHTML='<div class="tbw-filter-loading">Loading...</div>',document.body.appendChild(n),this.positionPanel(n,r),this.setupPanelCloseHandler(n,r),this.config.valuesHandler(e,t).then(l=>{this.openPanelField!==e||!this.panelElement||(n.innerHTML="",this.renderPanelContent(e,t,n,l))});return}const i=P(this.sourceRows,e);this.renderPanelContent(e,t,n,i),document.body.appendChild(n),this.positionPanel(n,r),this.setupPanelCloseHandler(n,r)}renderPanelContent(e,t,r,n){let i=this.excludedValues.get(e);i||(i=new Set,this.excludedValues.set(e,i));const l=this.searchText.get(e)??"",u={field:e,column:t,uniqueValues:n,excludedValues:i,searchText:l,applySetFilter:w=>{this.applySetFilter(e,w),this.closeFilterPanel()},applyTextFilter:(w,p,h)=>{this.applyTextFilter(e,w,p,h),this.closeFilterPanel()},clearFilter:()=>{this.clearFieldFilter(e),this.closeFilterPanel()},closePanel:()=>this.closeFilterPanel()};let a=!1;this.config.filterPanelRenderer&&(this.config.filterPanelRenderer(r,u),a=r.children.length>0),a||this.renderDefaultFilterPanel(r,u,n,i)}setupPanelCloseHandler(e,t){this.panelAbortController=new AbortController,setTimeout(()=>{document.addEventListener("click",r=>{!e.contains(r.target)&&r.target!==t&&this.closeFilterPanel()},{signal:this.panelAbortController?.signal})},0)}closeFilterPanel(){const e=this.panelElement;e&&(e.remove(),this.panelElement=null),this.panelAnchorElement&&(this.panelAnchorElement.style.anchorName="",this.panelAnchorElement=null),this.openPanelField=null,this.panelAbortController?.abort(),this.panelAbortController=null}static supportsAnchorPositioning=null;static checkAnchorPositioningSupport(){return g.supportsAnchorPositioning===null&&(g.supportsAnchorPositioning=CSS.supports("anchor-name","--test")),g.supportsAnchorPositioning}positionPanel(e,t){const n=t.closest(".cell")??t;if(n.style.anchorName="--tbw-filter-anchor",this.panelAnchorElement=n,g.checkAnchorPositioningSupport()){requestAnimationFrame(()=>{const l=e.getBoundingClientRect(),u=n.getBoundingClientRect();l.top<u.top&&e.classList.add("tbw-filter-panel-above")});return}const i=n.getBoundingClientRect();e.style.position="fixed",e.style.top=`${i.bottom+4}px`,e.style.left=`${i.left}px`,requestAnimationFrame(()=>{const l=e.getBoundingClientRect();l.right>window.innerWidth-8&&(e.style.left=`${i.right-l.width}px`),l.bottom>window.innerHeight-8&&(e.style.top=`${i.top-l.height-4}px`,e.classList.add("tbw-filter-panel-above"))})}renderDefaultFilterPanel(e,t,r,n){const{field:i}=t,l=this.getListItemHeight(),u=document.createElement("div");u.className="tbw-filter-search";const a=document.createElement("input");a.type="text",a.placeholder="Search...",a.className="tbw-filter-search-input",a.value=this.searchText.get(i)??"",u.appendChild(a),e.appendChild(u);const w=document.createElement("div");w.className="tbw-filter-actions";const p=document.createElement("label");p.className="tbw-filter-value-item",p.style.padding="0",p.style.margin="0";const h=document.createElement("input");h.type="checkbox",h.className="tbw-filter-checkbox";const k=document.createElement("span");k.textContent="Select All",p.appendChild(h),p.appendChild(k),w.appendChild(p);const L=()=>{const s=[...C.values()],d=s.every(o=>o),v=s.every(o=>!o);h.checked=d,h.indeterminate=!d&&!v};h.addEventListener("change",()=>{const s=h.checked;for(const d of C.keys())C.set(d,s);L(),N()}),e.appendChild(w);const x=document.createElement("div");x.className="tbw-filter-values";const T=document.createElement("div");T.className="tbw-filter-values-spacer",x.appendChild(T);const f=document.createElement("div");f.className="tbw-filter-values-content",x.appendChild(f);const C=new Map;r.forEach(s=>{const d=s==null?"__null__":String(s);C.set(d,!n.has(s))}),L();let S=[];const I=(s,d)=>{const v=s==null?"(Blank)":String(s),o=s==null?"__null__":String(s),c=document.createElement("label");c.className="tbw-filter-value-item",c.style.position="absolute",c.style.top=`${d*l}px`,c.style.left="0",c.style.right="0",c.style.height=`${l}px`,c.style.boxSizing="border-box";const b=document.createElement("input");b.type="checkbox",b.className="tbw-filter-checkbox",b.checked=C.get(o)??!0,b.dataset.value=o,b.addEventListener("change",()=>{C.set(o,b.checked),L()});const z=document.createElement("span");return z.textContent=v,c.appendChild(b),c.appendChild(z),c},N=()=>{const s=S.length,d=x.clientHeight,v=x.scrollTop;if(T.style.height=`${s*l}px`,E.shouldBypassVirtualization(s,g.LIST_BYPASS_THRESHOLD/3)){f.innerHTML="",f.style.transform="translateY(0px)",S.forEach((c,b)=>{f.appendChild(I(c,b))});return}const o=E.computeVirtualWindow({totalRows:s,viewportHeight:d,scrollTop:v,rowHeight:l,overscan:g.LIST_OVERSCAN});f.style.transform=`translateY(${o.offsetY}px)`,f.innerHTML="";for(let c=o.start;c<o.end;c++)f.appendChild(I(S[c],c-o.start))},V=s=>{const d=this.config.caseSensitive??!1,v=d?s:s.toLowerCase();if(S=r.filter(o=>{const c=o==null?"(Blank)":String(o),b=d?c:c.toLowerCase();return!s||b.includes(v)}),S.length===0){T.style.height="0px",f.innerHTML="";const o=document.createElement("div");o.className="tbw-filter-no-match",o.textContent="No matching values",f.appendChild(o);return}N()};x.addEventListener("scroll",()=>{S.length>0&&N()},{passive:!0}),V(a.value),e.appendChild(x);let H;a.addEventListener("input",()=>{clearTimeout(H),H=setTimeout(()=>{this.searchText.set(i,a.value),V(a.value)},this.config.debounceMs??150)});const F=document.createElement("div");F.className="tbw-filter-buttons";const A=document.createElement("button");A.className="tbw-filter-apply-btn",A.textContent="Apply",A.addEventListener("click",()=>{const s=[];for(const[d,v]of C)if(!v)if(d==="__null__")s.push(null);else{const o=r.find(c=>String(c)===d);s.push(o!==void 0?o:d)}t.applySetFilter(s)}),F.appendChild(A);const R=document.createElement("button");R.className="tbw-filter-clear-btn",R.textContent="Clear Filter",R.addEventListener("click",()=>{t.clearFilter()}),F.appendChild(R),e.appendChild(F)}applySetFilter(e,t){this.excludedValues.set(e,new Set(t)),t.length===0?this.filters.delete(e):this.filters.set(e,{field:e,type:"set",operator:"notIn",value:t}),this.applyFiltersInternal()}applyTextFilter(e,t,r,n){this.filters.set(e,{field:e,type:"text",operator:t,value:r,valueTo:n}),this.applyFiltersInternal()}applyFiltersInternal(){this.cachedResult=null,this.cacheKey=null;const e=[...this.filters.values()];if(this.config.filterHandler){const t=this.grid;t.setAttribute("aria-busy","true");const r=this.config.filterHandler(e,this.sourceRows),n=i=>{t.removeAttribute("aria-busy"),this.cachedResult=i,this.grid.rows=i,this.emit("filter-change",{filters:e,filteredRowCount:i.length}),this.requestRender()};r&&typeof r.then=="function"?r.then(n):n(r);return}this.emit("filter-change",{filters:e,filteredRowCount:0}),this.requestRender()}getColumnState(e){const t=this.filters.get(e);if(t)return{filter:{type:t.type,operator:t.operator,value:t.value,valueTo:t.valueTo}}}applyColumnState(e,t){if(!t.filter){this.filters.delete(e);return}const r={field:e,type:t.filter.type,operator:t.filter.operator,value:t.filter.value,valueTo:t.filter.valueTo};this.filters.set(e,r),this.cachedResult=null,this.cacheKey=null}}m.FilteringPlugin=g,Object.defineProperty(m,Symbol.toStringTag,{value:"Module"})}));
|
|
2
2
|
//# sourceMappingURL=filtering.umd.js.map
|