@toolbox-web/grid 1.15.0 → 1.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all.js +45 -23
- package/all.js.map +1 -1
- package/index.js +883 -865
- package/index.js.map +1 -1
- package/lib/core/internal/columns.d.ts +0 -5
- package/lib/core/internal/columns.d.ts.map +1 -1
- package/lib/core/internal/event-delegation.d.ts.map +1 -1
- package/lib/core/internal/rows.d.ts.map +1 -1
- package/lib/core/types.d.ts +12 -0
- package/lib/core/types.d.ts.map +1 -1
- package/lib/plugins/clipboard/ClipboardPlugin.d.ts.map +1 -1
- package/lib/plugins/clipboard/copy.d.ts.map +1 -1
- package/lib/plugins/clipboard/index.js +55 -55
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/index.js +59 -49
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/context-menu/menu.d.ts.map +1 -1
- package/lib/plugins/context-menu/types.d.ts +4 -4
- package/lib/plugins/context-menu/types.d.ts.map +1 -1
- package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
- package/lib/plugins/editing/index.js +228 -216
- package/lib/plugins/editing/index.js.map +1 -1
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/filter-model.d.ts.map +1 -1
- package/lib/plugins/filtering/index.js +4 -5
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/print/index.js.map +1 -1
- package/lib/plugins/reorder/index.js.map +1 -1
- package/lib/plugins/responsive/index.js.map +1 -1
- package/lib/plugins/row-reorder/index.js.map +1 -1
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/index.js.map +1 -1
- package/package.json +1 -1
- package/umd/grid.all.umd.js +26 -26
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +21 -21
- package/umd/grid.umd.js.map +1 -1
- package/umd/plugins/clipboard.umd.js +4 -4
- package/umd/plugins/clipboard.umd.js.map +1 -1
- package/umd/plugins/context-menu.umd.js +1 -1
- package/umd/plugins/context-menu.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/all.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"all.js","sources":["../../../libs/grid/src/lib/plugins/shared/data-collection.ts","../../../libs/grid/src/lib/plugins/clipboard/copy.ts","../../../libs/grid/src/lib/plugins/clipboard/paste.ts","../../../libs/grid/src/lib/plugins/clipboard/types.ts","../../../libs/grid/src/lib/plugins/clipboard/ClipboardPlugin.ts","../../../libs/grid/src/lib/plugins/column-virtualization/column-virtualization.ts","../../../libs/grid/src/lib/plugins/column-virtualization/ColumnVirtualizationPlugin.ts","../../../libs/grid/src/lib/plugins/context-menu/menu.ts","../../../libs/grid/src/lib/plugins/context-menu/ContextMenuPlugin.ts","../../../libs/grid/src/lib/plugins/editing/editors.ts","../../../libs/grid/src/lib/plugins/editing/EditingPlugin.ts","../../../libs/grid/src/lib/plugins/export/csv.ts","../../../libs/grid/src/lib/plugins/export/excel.ts","../../../libs/grid/src/lib/plugins/export/ExportPlugin.ts","../../../libs/grid/src/lib/core/plugin/expander-column.ts","../../../libs/grid/src/lib/plugins/filtering/filter-model.ts","../../../libs/grid/src/lib/plugins/filtering/FilteringPlugin.ts","../../../libs/grid/src/lib/plugins/grouping-columns/grouping-columns.ts","../../../libs/grid/src/lib/plugins/grouping-columns/GroupingColumnsPlugin.ts","../../../libs/grid/src/lib/plugins/grouping-rows/grouping-rows.ts","../../../libs/grid/src/lib/plugins/grouping-rows/GroupingRowsPlugin.ts","../../../libs/grid/src/lib/plugins/master-detail/master-detail.ts","../../../libs/grid/src/lib/plugins/master-detail/MasterDetailPlugin.ts","../../../libs/grid/src/lib/plugins/multi-sort/multi-sort.ts","../../../libs/grid/src/lib/plugins/multi-sort/MultiSortPlugin.ts","../../../libs/grid/src/lib/plugins/pinned-columns/pinned-columns.ts","../../../libs/grid/src/lib/plugins/pinned-columns/PinnedColumnsPlugin.ts","../../../libs/grid/src/lib/plugins/pinned-rows/pinned-rows.ts","../../../libs/grid/src/lib/plugins/pinned-rows/PinnedRowsPlugin.ts","../../../libs/grid/src/lib/plugins/pivot/pivot-model.ts","../../../libs/grid/src/lib/plugins/pivot/pivot-engine.ts","../../../libs/grid/src/lib/plugins/pivot/pivot-panel.ts","../../../libs/grid/src/lib/plugins/pivot/pivot-rows.ts","../../../libs/grid/src/lib/plugins/pivot/PivotPlugin.ts","../../../libs/grid/src/lib/plugins/print/print-isolated.ts","../../../libs/grid/src/lib/plugins/print/PrintPlugin.ts","../../../libs/grid/src/lib/plugins/reorder/column-drag.ts","../../../libs/grid/src/lib/plugins/reorder/ReorderPlugin.ts","../../../libs/grid/src/lib/plugins/responsive/ResponsivePlugin.ts","../../../libs/grid/src/lib/plugins/row-reorder/RowReorderPlugin.ts","../../../libs/grid/src/lib/plugins/selection/range-selection.ts","../../../libs/grid/src/lib/plugins/selection/SelectionPlugin.ts","../../../libs/grid/src/lib/plugins/server-side/datasource.ts","../../../libs/grid/src/lib/plugins/server-side/ServerSidePlugin.ts","../../../libs/grid/src/lib/plugins/tree/tree-data.ts","../../../libs/grid/src/lib/plugins/tree/tree-detect.ts","../../../libs/grid/src/lib/plugins/tree/TreePlugin.ts","../../../libs/grid/src/lib/plugins/undo-redo/history.ts","../../../libs/grid/src/lib/plugins/undo-redo/UndoRedoPlugin.ts","../../../libs/grid/src/lib/plugins/visibility/VisibilityPlugin.ts"],"sourcesContent":["/**\n * Shared Data Collection Utilities\n *\n * Pure functions for resolving columns and formatting values, shared between\n * the Clipboard and Export plugins. Each plugin bundles its own copy of this\n * module (no chunk splitting) since plugin builds inline sibling imports.\n *\n * @internal\n */\n\nimport type { ColumnConfig } from '../../core/types';\n\n/**\n * Resolve which columns to include in a data export or copy operation.\n *\n * Filters out hidden columns, utility columns (`meta.utility`), and\n * internal columns (`__`-prefixed fields). Optionally restricts to an\n * explicit set of field names.\n *\n * @param columns - All column configurations\n * @param fields - If provided, only include columns whose field is in this list\n * @param onlyVisible - When `true` (default), exclude hidden and internal columns\n * @returns Filtered column array preserving original order\n */\nexport function resolveColumns(\n columns: readonly ColumnConfig[],\n fields?: string[],\n onlyVisible = true,\n): ColumnConfig[] {\n let result = columns as ColumnConfig[];\n\n if (onlyVisible) {\n result = result.filter((c) => !c.hidden && !c.field.startsWith('__') && c.meta?.utility !== true);\n }\n\n if (fields?.length) {\n const fieldSet = new Set(fields);\n result = result.filter((c) => fieldSet.has(c.field));\n }\n\n return result;\n}\n\n/**\n * Resolve which rows to include, optionally filtered to specific indices.\n *\n * @param rows - All row data\n * @param indices - If provided, only include rows at these indices (sorted ascending)\n * @returns Filtered row array\n */\nexport function resolveRows<T>(rows: readonly T[], indices?: number[]): T[] {\n if (!indices?.length) return rows as T[];\n\n return [...indices]\n .sort((a, b) => a - b)\n .map((i) => rows[i])\n .filter((r): r is T => r != null);\n}\n\n/**\n * Format a raw cell value as a text string.\n *\n * Provides the common null / Date / object → string conversion shared by\n * both clipboard and export output builders.\n *\n * @param value - The cell value to format\n * @returns A plain-text representation of the value\n */\nexport function formatValueAsText(value: unknown): string {\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'object') return JSON.stringify(value);\n return String(value);\n}\n","/**\n * Clipboard Copy Logic\n *\n * Pure functions for copying grid data to clipboard.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ClipboardConfig } from './types';\n\n/** Parameters for building clipboard text */\nexport interface CopyParams {\n /** All grid rows */\n rows: unknown[];\n /** Column configurations */\n columns: ColumnConfig[];\n /** Selected row indices */\n selectedIndices: Set<number> | number[];\n /** Clipboard configuration */\n config: ClipboardConfig;\n}\n\n/**\n * Format a cell value for clipboard output.\n *\n * Uses custom processCell if provided, otherwise applies default formatting:\n * - null/undefined → empty string\n * - Date → ISO string\n * - Object → JSON string\n * - Other → String conversion with optional quoting\n *\n * @param value - The cell value to format\n * @param field - The field name\n * @param row - The full row object\n * @param config - Clipboard configuration\n * @returns Formatted string value\n */\nexport function formatCellValue(value: unknown, field: string, row: unknown, config: ClipboardConfig): string {\n if (config.processCell) {\n return config.processCell(value, field, row);\n }\n\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'object') return JSON.stringify(value);\n\n const str = String(value);\n const delimiter = config.delimiter ?? '\\t';\n const newline = config.newline ?? '\\n';\n\n // Quote if contains delimiter, newline, or quotes (or if quoteStrings is enabled)\n if (config.quoteStrings || str.includes(delimiter) || str.includes(newline) || str.includes('\"')) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n\n return str;\n}\n\n/**\n * Build clipboard text from selected rows and columns.\n *\n * @param params - Copy parameters including rows, columns, selection, and config\n * @returns Tab-separated (or custom delimiter) text ready for clipboard\n */\nexport function buildClipboardText(params: CopyParams): string {\n const { rows, columns, selectedIndices, config } = params;\n const delimiter = config.delimiter ?? '\\t';\n const newline = config.newline ?? '\\n';\n\n // Filter to visible columns (not hidden, not internal __ prefixed)\n const visibleColumns = columns.filter((c) => !c.hidden && !c.field.startsWith('__'));\n\n const lines: string[] = [];\n\n // Add header row if configured\n if (config.includeHeaders) {\n const headerCells = visibleColumns.map((c) => {\n const header = c.header || c.field;\n // Quote headers if they contain special characters\n if (header.includes(delimiter) || header.includes(newline) || header.includes('\"')) {\n return `\"${header.replace(/\"/g, '\"\"')}\"`;\n }\n return header;\n });\n lines.push(headerCells.join(delimiter));\n }\n\n // Convert indices to sorted array\n const indices = selectedIndices instanceof Set ? [...selectedIndices] : selectedIndices;\n const sortedIndices = [...indices].sort((a, b) => a - b);\n\n // Build data rows\n for (const idx of sortedIndices) {\n const row = rows[idx];\n if (!row) continue;\n\n const cells = visibleColumns.map((col) =>\n formatCellValue((row as Record<string, unknown>)[col.field], col.field, row, config)\n );\n lines.push(cells.join(delimiter));\n }\n\n return lines.join(newline);\n}\n\n/**\n * Copy text to the system clipboard.\n *\n * Uses the modern Clipboard API when available, with fallback\n * to execCommand for older browsers.\n *\n * @param text - The text to copy\n * @returns Promise resolving to true if successful, false otherwise\n */\nexport async function copyToClipboard(text: string): Promise<boolean> {\n try {\n await navigator.clipboard.writeText(text);\n return true;\n } catch {\n // Fallback for older browsers or when Clipboard API is not available\n const textarea = document.createElement('textarea');\n textarea.value = text;\n textarea.style.position = 'fixed';\n textarea.style.opacity = '0';\n textarea.style.pointerEvents = 'none';\n document.body.appendChild(textarea);\n textarea.select();\n const success = document.execCommand('copy');\n document.body.removeChild(textarea);\n return success;\n }\n}\n","/**\n * Clipboard Paste Logic\n *\n * Pure functions for reading and parsing clipboard data.\n */\n\nimport type { ClipboardConfig } from './types';\n\n/**\n * Parse clipboard text into a 2D array of cell values.\n *\n * Handles:\n * - Quoted values (with escaped double quotes \"\")\n * - Multi-line quoted values (newlines within quotes are preserved)\n * - Custom delimiters and newlines\n * - Empty lines (filtered out only if they contain no data)\n *\n * @param text - Raw clipboard text\n * @param config - Clipboard configuration\n * @returns 2D array where each sub-array is a row of cell values\n */\nexport function parseClipboardText(text: string, config: ClipboardConfig): string[][] {\n const delimiter = config.delimiter ?? '\\t';\n const newline = config.newline ?? '\\n';\n\n // Handle Windows CRLF line endings\n const normalizedText = text.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n\n // Parse the entire text handling quoted fields that may span multiple lines\n const rows: string[][] = [];\n let currentRow: string[] = [];\n let currentCell = '';\n let inQuotes = false;\n\n for (let i = 0; i < normalizedText.length; i++) {\n const char = normalizedText[i];\n\n if (char === '\"' && !inQuotes) {\n // Start of quoted field\n inQuotes = true;\n } else if (char === '\"' && inQuotes) {\n // Check for escaped quote (\"\")\n if (normalizedText[i + 1] === '\"') {\n currentCell += '\"';\n i++; // Skip the second quote\n } else {\n // End of quoted field\n inQuotes = false;\n }\n } else if (char === delimiter && !inQuotes) {\n // Field separator\n currentRow.push(currentCell);\n currentCell = '';\n } else if (char === newline && !inQuotes) {\n // Row separator (only if not inside quotes)\n currentRow.push(currentCell);\n currentCell = '';\n // Only add non-empty rows (at least one non-empty cell or multiple cells)\n if (currentRow.length > 1 || currentRow.some((c) => c.trim() !== '')) {\n rows.push(currentRow);\n }\n currentRow = [];\n } else {\n currentCell += char;\n }\n }\n\n // Handle the last cell and row\n currentRow.push(currentCell);\n if (currentRow.length > 1 || currentRow.some((c) => c.trim() !== '')) {\n rows.push(currentRow);\n }\n\n return rows;\n}\n\n/**\n * Read text from the system clipboard.\n *\n * Uses the modern Clipboard API. Returns empty string if\n * the API is not available or permission is denied.\n *\n * @returns Promise resolving to clipboard text or empty string\n */\nexport async function readFromClipboard(): Promise<string> {\n try {\n return await navigator.clipboard.readText();\n } catch {\n // Permission denied or API not available\n return '';\n }\n}\n","/**\n * Clipboard Plugin Types\n *\n * Type definitions for clipboard copy/paste functionality.\n */\n\nimport type { GridElement } from '../../core/plugin/base-plugin';\n\n/**\n * Custom paste handler function.\n *\n * @param detail - The parsed paste data with target and field info\n * @param grid - The grid element to update\n * @returns `false` to prevent the default paste behavior, or `void`/`true` to allow it\n *\n * @example\n * ```ts\n * // Custom handler that validates before pasting\n * new ClipboardPlugin({\n * pasteHandler: (detail, grid) => {\n * if (!detail.target) return false;\n * // Apply custom validation/transformation...\n * applyPasteData(detail, grid);\n * return false; // We handled it, skip default\n * }\n * })\n * ```\n */\nexport type PasteHandler = (detail: PasteDetail, grid: GridElement) => boolean | void;\n\n/**\n * Options for programmatic copy operations.\n *\n * Allows callers to control exactly which columns and rows are included\n * in a copy, independently of the current selection state.\n *\n * @example Copy specific columns from selected rows\n * ```ts\n * const clipboard = grid.getPlugin(ClipboardPlugin);\n * // User selected rows 0, 2, 4 via a dialog, chose columns to include\n * const text = await clipboard.copy({\n * rowIndices: [0, 2, 4],\n * columns: ['name', 'email'],\n * includeHeaders: true,\n * });\n * ```\n */\nexport interface CopyOptions {\n /** Specific column fields to include. If omitted, uses current selection or all visible columns. */\n columns?: string[];\n /** Specific row indices to copy. If omitted, uses current selection or all rows. */\n rowIndices?: number[];\n /** Include column headers in copied text. Defaults to the plugin config value. */\n includeHeaders?: boolean;\n /** Column delimiter override. Defaults to the plugin config value. */\n delimiter?: string;\n /** Row delimiter override. Defaults to the plugin config value. */\n newline?: string;\n /** Custom cell value processor for this operation. Overrides the plugin config's `processCell`. */\n processCell?: (value: unknown, field: string, row: unknown) => string;\n}\n\n/** Configuration options for the clipboard plugin */\nexport interface ClipboardConfig {\n /** Include column headers in copied text (default: false) */\n includeHeaders?: boolean;\n /** Column delimiter character (default: '\\t' for tab) */\n delimiter?: string;\n /** Row delimiter/newline character (default: '\\n') */\n newline?: string;\n /** Wrap string values with quotes (default: false) */\n quoteStrings?: boolean;\n /** Custom cell value processor for copy operations */\n processCell?: (value: unknown, field: string, row: unknown) => string;\n /**\n * Custom paste handler. By default, the plugin applies pasted data to `grid.rows`\n * starting at the target cell.\n *\n * - Set to a custom function to handle paste yourself\n * - Set to `null` to disable auto-paste (event still fires)\n * - Return `false` from handler to prevent default behavior\n *\n * @default defaultPasteHandler (auto-applies paste data)\n */\n pasteHandler?: PasteHandler | null;\n}\n\n/** Internal state managed by the clipboard plugin */\nexport interface ClipboardState {\n /** The last copied text (for reference/debugging) */\n lastCopied: string | null;\n}\n\n/** Event detail emitted after a successful copy operation */\nexport interface CopyDetail {\n /** The text that was copied to clipboard */\n text: string;\n /** Number of rows copied */\n rowCount: number;\n /** Number of columns copied */\n columnCount: number;\n}\n\n/** Target cell coordinates and bounds for paste operations */\nexport interface PasteTarget {\n /** Target row index (top-left of paste area) */\n row: number;\n /** Target column index (top-left of paste area) */\n col: number;\n /** Target column field name (for easy data mapping) */\n field: string;\n /**\n * Selection bounds that constrain the paste area.\n * If set, paste data will be clipped to fit within these bounds.\n * If null, paste expands freely from the target cell.\n */\n bounds: {\n /** End row index (inclusive) */\n endRow: number;\n /** End column index (inclusive) */\n endCol: number;\n } | null;\n}\n\n/** Event detail emitted after a paste operation */\nexport interface PasteDetail {\n /** Parsed rows from clipboard (2D array of cell values) */\n rows: string[][];\n /** Raw text that was pasted */\n text: string;\n /** The target cell where paste starts (top-left of paste area). Null if no cell is selected. */\n target: PasteTarget | null;\n /**\n * Column fields for each column in the paste range, starting from target.col.\n * Useful for mapping parsed cell values to data fields.\n * Length matches the width of the pasted data (or available columns, whichever is smaller).\n */\n fields: string[];\n}\n\n/**\n * Default paste handler that applies pasted data to grid.rows.\n *\n * This is the built-in handler used when no custom `pasteHandler` is configured.\n * It clones the rows array for immutability and applies values starting at the target cell.\n *\n * Behavior:\n * - Single cell selection: paste expands freely, adds new rows if needed\n * - Range/row selection: paste is clipped to fit within selection bounds\n * - Non-editable columns: values are skipped (column alignment preserved)\n *\n * @param detail - The parsed paste data from clipboard\n * @param grid - The grid element to update\n */\nexport function defaultPasteHandler(detail: PasteDetail, grid: GridElement): void {\n const { rows: pastedRows, target, fields } = detail;\n\n // No target = nothing to do\n if (!target) return;\n\n // Get current rows and columns from grid\n const currentRows = grid.rows as Record<string, unknown>[];\n const columns = grid.effectiveConfig.columns ?? [];\n const allFields = columns.map((col) => col.field);\n\n // Build a map of field -> editable for quick lookup\n const editableMap = new Map<string, boolean>();\n columns.forEach((col) => {\n editableMap.set(col.field, col.editable === true);\n });\n\n // Clone data for immutability\n const newRows = [...currentRows];\n\n // Calculate row bounds\n const maxPasteRow = target.bounds ? target.bounds.endRow : Infinity;\n\n // Apply pasted data starting at target cell\n pastedRows.forEach((rowData, rowOffset) => {\n const targetRowIndex = target.row + rowOffset;\n\n // Stop if we've exceeded the selection bounds\n if (targetRowIndex > maxPasteRow) return;\n\n // Only grow array if no bounds (single cell selection)\n if (!target.bounds) {\n while (targetRowIndex >= newRows.length) {\n const emptyRow: Record<string, unknown> = {};\n allFields.forEach((field) => (emptyRow[field] = ''));\n newRows.push(emptyRow);\n }\n } else if (targetRowIndex >= newRows.length) {\n // With bounds, don't paste beyond existing rows\n return;\n }\n\n // Clone the target row and apply values\n newRows[targetRowIndex] = { ...newRows[targetRowIndex] };\n rowData.forEach((cellValue, colOffset) => {\n // fields array is already constrained by bounds in ClipboardPlugin\n const field = fields[colOffset];\n if (field && editableMap.get(field)) {\n // Only paste into editable columns\n newRows[targetRowIndex][field] = cellValue;\n }\n });\n });\n\n // Update grid with new data\n grid.rows = newRows;\n}\n","/**\n * Clipboard Plugin (Class-based)\n *\n * Provides copy/paste functionality for tbw-grid.\n * Supports Ctrl+C/Cmd+C for copying and Ctrl+V/Cmd+V for pasting.\n *\n * **With Selection plugin:** Copies selected cells/rows/range\n * **Without Selection plugin:** Copies entire grid\n */\n\nimport { BaseGridPlugin, type GridElement, type PluginDependency } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport { formatValueAsText, resolveColumns, resolveRows } from '../shared/data-collection';\nimport { copyToClipboard } from './copy';\nimport { parseClipboardText, readFromClipboard } from './paste';\nimport {\n defaultPasteHandler,\n type ClipboardConfig,\n type CopyDetail,\n type CopyOptions,\n type PasteDetail,\n type PasteTarget,\n} from './types';\n\n/**\n * Clipboard Plugin for tbw-grid\n *\n * Brings familiar copy/cut/paste functionality with full keyboard shortcut support\n * (Ctrl+C, Ctrl+X, Ctrl+V). Handles single cells, multi-cell selections, and integrates\n * seamlessly with Excel and other spreadsheet applications via tab-delimited output.\n *\n * > **Optional Dependency:** Works best with SelectionPlugin for copying/pasting selected\n * > cells. Without SelectionPlugin, copies the entire grid and pastes at row 0, column 0.\n *\n * ## Installation\n *\n * ```ts\n * import { ClipboardPlugin } from '@toolbox-web/grid/plugins/clipboard';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `includeHeaders` | `boolean` | `false` | Include column headers in copied data |\n * | `delimiter` | `string` | `'\\t'` | Column delimiter (tab for Excel compatibility) |\n * | `newline` | `string` | `'\\n'` | Row delimiter |\n * | `quoteStrings` | `boolean` | `false` | Wrap string values in quotes |\n * | `processCell` | `(value, field, row) => string` | - | Custom cell value processor |\n * | `pasteHandler` | `PasteHandler \\| null` | `defaultPasteHandler` | Custom paste handler |\n *\n * ## Keyboard Shortcuts\n *\n * | Shortcut | Action |\n * |----------|--------|\n * | `Ctrl+C` / `Cmd+C` | Copy selected cells |\n * | `Ctrl+V` / `Cmd+V` | Paste into selected cells |\n * | `Ctrl+X` / `Cmd+X` | Cut selected cells |\n *\n * ## Paste Behavior by Selection Type\n *\n * | Selection Type | Paste Behavior |\n * |----------------|----------------|\n * | Single cell | Paste expands freely from that cell |\n * | Range selection | Paste is clipped to fit within the selected range |\n * | Row selection | Paste is clipped to the selected rows |\n * | No selection | Paste starts at row 0, column 0 |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `copy` | `(options?: CopyOptions) => Promise<string>` | Copy to clipboard with optional column/row control |\n * | `copyRows` | `(indices, options?) => Promise<string>` | Copy specific rows to clipboard |\n * | `paste` | `() => Promise<string[][] \\| null>` | Read and parse clipboard content |\n * | `getSelectionAsText` | `(options?: CopyOptions) => string` | Get clipboard text without writing to clipboard |\n * | `getLastCopied` | `() => { text, timestamp } \\| null` | Get info about last copy operation |\n *\n * @example Basic Usage with Excel Compatibility\n * ```ts\n * import '@toolbox-web/grid';\n * import { ClipboardPlugin } from '@toolbox-web/grid/plugins/clipboard';\n * import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';\n *\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * ],\n * plugins: [\n * new SelectionPlugin({ mode: 'range' }),\n * new ClipboardPlugin({\n * includeHeaders: true,\n * delimiter: '\\t', // Tab for Excel\n * }),\n * ],\n * };\n * ```\n *\n * @example Custom Paste Handler\n * ```ts\n * new ClipboardPlugin({\n * pasteHandler: (grid, target, data) => {\n * // Validate or transform data before applying\n * console.log('Pasting', data.length, 'rows');\n * return defaultPasteHandler(grid, target, data);\n * },\n * })\n * ```\n *\n * @see {@link ClipboardConfig} for all configuration options\n * @see {@link SelectionPlugin} for enhanced copy/paste with selection\n *\n * @internal Extends BaseGridPlugin\n */\nexport class ClipboardPlugin extends BaseGridPlugin<ClipboardConfig> {\n /**\n * Plugin dependencies - ClipboardPlugin works best with SelectionPlugin.\n *\n * Without SelectionPlugin: copies entire grid, pastes at row 0 col 0.\n * With SelectionPlugin: copies/pastes based on selection.\n */\n /** @internal */\n static override readonly dependencies: PluginDependency[] = [\n { name: 'selection', required: false, reason: 'Enables copy/paste of selected cells instead of entire grid' },\n ];\n\n /** @internal */\n readonly name = 'clipboard';\n\n /** @internal */\n protected override get defaultConfig(): Partial<ClipboardConfig> {\n return {\n includeHeaders: false,\n delimiter: '\\t',\n newline: '\\n',\n quoteStrings: false,\n };\n }\n\n // #region Internal State\n /** The last copied text (for reference/debugging) */\n private lastCopied: { text: string; timestamp: number } | null = null;\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n\n // Listen for native paste events to get clipboard data synchronously\n // This is more reliable than the async Clipboard API in iframe contexts\n // Cast to HTMLElement since GridElement is an interface\n (grid as unknown as HTMLElement).addEventListener(\n 'paste',\n (e: Event) => this.#handleNativePaste(e as ClipboardEvent),\n { signal: this.disconnectSignal },\n );\n }\n\n /** @internal */\n override detach(): void {\n this.lastCopied = null;\n }\n // #endregion\n\n // #region Event Handlers\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean {\n const isCopy = (event.ctrlKey || event.metaKey) && event.key === 'c';\n\n if (isCopy) {\n this.#handleCopy(event.target as HTMLElement);\n return true; // Prevent default browser behavior\n }\n\n // For paste, we do NOT return true - let the native paste event fire\n // so we can access clipboardData synchronously in #handleNativePaste\n return false;\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Handle copy operation from keyboard shortcut.\n *\n * For keyboard-triggered copies, respects the current selection or\n * falls back to the focused cell from the DOM.\n */\n #handleCopy(target: HTMLElement): void {\n const selection = this.#getSelection();\n\n // Selection plugin exists but nothing selected → try focused cell from DOM\n if (selection && selection.ranges.length === 0) {\n const focused = this.#getFocusedCellFromDOM(target);\n if (!focused) return;\n const col = this.columns[focused.col];\n if (!col) return;\n this.copy({ rowIndices: [focused.row], columns: [col.field] });\n return;\n }\n\n // Delegate to the public copy() method (selection or full grid)\n this.copy();\n }\n\n /**\n * Handle native paste event (preferred method - works in iframes).\n * Uses synchronous clipboardData from the native paste event.\n *\n * Flow:\n * 1. Parse clipboard text\n * 2. Build target/fields info from selection\n * 3. Emit 'paste' event (for listeners)\n * 4. Call paste handler (if configured) to apply data to grid\n *\n * Selection behavior:\n * - Single cell: paste starts at cell, expands freely\n * - Range/row: paste is clipped to fit within selection bounds\n * - No selection: paste starts at row 0, col 0\n */\n #handleNativePaste(event: ClipboardEvent): void {\n const text = event.clipboardData?.getData('text/plain');\n if (!text) return;\n\n // Prevent default to avoid pasting into contenteditable elements\n event.preventDefault();\n\n const parsed = parseClipboardText(text, this.config);\n\n // Get target cell from selection via query\n const selection = this.#getSelection();\n const firstRange = selection?.ranges?.[0];\n\n // Determine target cell and bounds\n const targetRow = firstRange?.from.row ?? 0;\n const targetCol = firstRange?.from.col ?? 0;\n\n // Check if multi-cell selection (range with different start/end)\n const isMultiCell =\n firstRange &&\n (selection?.mode === 'range' || selection?.mode === 'row') &&\n (firstRange.from.row !== firstRange.to.row || firstRange.from.col !== firstRange.to.col);\n\n const bounds = isMultiCell ? { endRow: firstRange.to.row, endCol: firstRange.to.col } : null;\n const maxCol = bounds?.endCol ?? this.columns.length - 1;\n\n // Build target info\n const column = this.columns[targetCol];\n const target: PasteTarget | null = column ? { row: targetRow, col: targetCol, field: column.field, bounds } : null;\n\n // Build field list for paste width (constrained by bounds if set)\n const fields: string[] = [];\n const pasteWidth = parsed[0]?.length ?? 0;\n for (let i = 0; i < pasteWidth && targetCol + i <= maxCol; i++) {\n const col = this.columns[targetCol + i];\n if (col && !col.hidden) {\n fields.push(col.field);\n }\n }\n\n const detail: PasteDetail = { rows: parsed, text, target, fields };\n\n // Emit the event for any listeners\n this.emit<PasteDetail>('paste', detail);\n\n // Apply paste data using the configured handler (or default)\n this.#applyPasteHandler(detail);\n }\n\n /**\n * Apply the paste handler to update grid data.\n *\n * Uses the configured `pasteHandler`, or the default handler if not specified.\n * Set `pasteHandler: null` in config to disable auto-paste.\n */\n #applyPasteHandler(detail: PasteDetail): void {\n if (!this.grid) return;\n\n const { pasteHandler } = this.config;\n\n // pasteHandler: null means explicitly disabled\n if (pasteHandler === null) return;\n\n // Use custom handler or default\n const handler = pasteHandler ?? defaultPasteHandler;\n handler(detail, this.grid);\n }\n\n /**\n * Get the current selection via Query System.\n * Returns undefined if no selection plugin is loaded or nothing is selected.\n */\n #getSelection(): SelectionQueryResult | undefined {\n const responses = this.grid?.query<SelectionQueryResult>('getSelection');\n return responses?.[0];\n }\n\n /**\n * Resolve columns and rows to include based on options and/or current selection.\n *\n * Priority for columns:\n * 1. `options.columns` (explicit field list)\n * 2. Selection range column bounds (range/cell mode only)\n * 3. All visible non-utility columns\n *\n * Priority for rows:\n * 1. `options.rowIndices` (explicit indices)\n * 2. Selection range row bounds\n * 3. All rows\n */\n #resolveData(options?: CopyOptions): { columns: ColumnConfig[]; rows: Record<string, unknown>[] } {\n const selection = this.#getSelection();\n\n // --- Columns ---\n let columns: ColumnConfig[];\n if (options?.columns) {\n // Caller specified exact fields\n columns = resolveColumns(this.columns, options.columns);\n } else if (selection?.ranges.length && selection.mode !== 'row') {\n // Range/cell selection: restrict to selection column bounds\n const range = selection.ranges[selection.ranges.length - 1];\n const minCol = Math.min(range.from.col, range.to.col);\n const maxCol = Math.max(range.from.col, range.to.col);\n columns = resolveColumns(this.columns.slice(minCol, maxCol + 1));\n } else {\n // Row selection or no selection: all visible columns\n columns = resolveColumns(this.columns);\n }\n\n // --- Rows ---\n let rows: Record<string, unknown>[];\n if (options?.rowIndices) {\n // Caller specified exact row indices\n rows = resolveRows(this.rows as Record<string, unknown>[], options.rowIndices);\n } else if (selection?.ranges.length) {\n // Selection range: extract contiguous row range\n const range = selection.ranges[selection.ranges.length - 1];\n const minRow = Math.min(range.from.row, range.to.row);\n const maxRow = Math.max(range.from.row, range.to.row);\n rows = [];\n for (let r = minRow; r <= maxRow; r++) {\n const row = this.rows[r] as Record<string, unknown> | undefined;\n if (row) rows.push(row);\n }\n } else {\n // No selection: all rows\n rows = this.rows as Record<string, unknown>[];\n }\n\n return { columns, rows };\n }\n\n /**\n * Build delimited text from resolved columns and rows.\n */\n #buildText(columns: ColumnConfig[], rows: Record<string, unknown>[], options?: CopyOptions): string {\n const delimiter = options?.delimiter ?? this.config.delimiter ?? '\\t';\n const newline = options?.newline ?? this.config.newline ?? '\\n';\n const includeHeaders = options?.includeHeaders ?? this.config.includeHeaders ?? false;\n const processCell = options?.processCell ?? this.config.processCell;\n\n const lines: string[] = [];\n\n // Header row\n if (includeHeaders) {\n lines.push(columns.map((c) => c.header || c.field).join(delimiter));\n }\n\n // Data rows\n for (const row of rows) {\n const cells = columns.map((col) => {\n const value = row[col.field];\n if (processCell) return processCell(value, col.field, row);\n return formatValueAsText(value);\n });\n lines.push(cells.join(delimiter));\n }\n\n return lines.join(newline);\n }\n\n /**\n * Get focused cell coordinates from DOM.\n * Used as fallback when SelectionPlugin has no selection.\n */\n #getFocusedCellFromDOM(target: HTMLElement): { row: number; col: number } | null {\n const cell = target.closest('[data-field-cache]') as HTMLElement | null;\n if (!cell) return null;\n\n const field = cell.dataset.fieldCache;\n const rowIndexStr = cell.dataset.row;\n if (!field || !rowIndexStr) return null;\n\n const row = parseInt(rowIndexStr, 10);\n if (isNaN(row)) return null;\n\n const col = this.columns.findIndex((c) => c.field === field);\n if (col === -1) return null;\n\n return { row, col };\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Get the text representation of the current selection (or specified data)\n * without writing to the system clipboard.\n *\n * Useful for previewing what would be copied, or for feeding the text into\n * a custom UI (e.g., a \"copy with column picker\" dialog).\n *\n * @param options - Control which columns/rows to include\n * @returns Delimited text, or empty string if nothing to copy\n *\n * @example Get text for specific columns\n * ```ts\n * const clipboard = grid.getPlugin(ClipboardPlugin);\n * const text = clipboard.getSelectionAsText({\n * columns: ['name', 'email'],\n * includeHeaders: true,\n * });\n * console.log(text);\n * // \"Name\\tEmail\\nAlice\\talice@example.com\\n...\"\n * ```\n */\n getSelectionAsText(options?: CopyOptions): string {\n const { columns, rows } = this.#resolveData(options);\n if (columns.length === 0 || rows.length === 0) return '';\n return this.#buildText(columns, rows, options);\n }\n\n /**\n * Copy data to the system clipboard.\n *\n * Without options, copies the current selection (or entire grid if no selection).\n * With options, copies exactly the specified columns and/or rows — ideal for\n * \"copy with column picker\" workflows where the user selects rows first,\n * then chooses which columns to include via a dialog.\n *\n * @param options - Control which columns/rows to include\n * @returns The copied text\n *\n * @example Copy current selection (default)\n * ```ts\n * const clipboard = grid.getPlugin(ClipboardPlugin);\n * await clipboard.copy();\n * ```\n *\n * @example Copy specific columns from specific rows\n * ```ts\n * // User selected rows in the grid, then picked columns in a dialog\n * const selectedRowIndices = [0, 3, 7];\n * const chosenColumns = ['name', 'department', 'salary'];\n * await clipboard.copy({\n * rowIndices: selectedRowIndices,\n * columns: chosenColumns,\n * includeHeaders: true,\n * });\n * ```\n */\n async copy(options?: CopyOptions): Promise<string> {\n const { columns, rows } = this.#resolveData(options);\n if (columns.length === 0 || rows.length === 0) return '';\n\n const text = this.#buildText(columns, rows, options);\n await copyToClipboard(text);\n this.lastCopied = { text, timestamp: Date.now() };\n this.emit<CopyDetail>('copy', {\n text,\n rowCount: rows.length,\n columnCount: columns.length,\n });\n return text;\n }\n\n /**\n * Copy specific rows by index to clipboard.\n *\n * Convenience wrapper around {@link copy} for row-based workflows.\n * Supports non-contiguous row indices (e.g., `[0, 3, 7]`).\n *\n * @param indices - Array of row indices to copy\n * @param options - Additional copy options (columns, headers, etc.)\n * @returns The copied text\n *\n * @example\n * ```ts\n * const clipboard = grid.getPlugin(ClipboardPlugin);\n * // Copy only rows 0 and 5, including just name and email columns\n * await clipboard.copyRows([0, 5], { columns: ['name', 'email'] });\n * ```\n */\n async copyRows(indices: number[], options?: Omit<CopyOptions, 'rowIndices'>): Promise<string> {\n if (indices.length === 0) return '';\n return this.copy({ ...options, rowIndices: indices });\n }\n\n /**\n * Read and parse clipboard content.\n * @returns Parsed 2D array of cell values, or null if clipboard is empty\n */\n async paste(): Promise<string[][] | null> {\n const text = await readFromClipboard();\n if (!text) return null;\n return parseClipboardText(text, this.config);\n }\n\n /**\n * Get the last copied text and timestamp.\n * @returns The last copied info or null\n */\n getLastCopied(): { text: string; timestamp: number } | null {\n return this.lastCopied;\n }\n // #endregion\n}\n\n// #region Internal Types\n\n/**\n * Range representation for clipboard operations.\n */\ninterface CellRange {\n from: { row: number; col: number };\n to: { row: number; col: number };\n}\n\n/**\n * Selection result returned by the Query System.\n * Matches the SelectionResult type from SelectionPlugin.\n */\ninterface SelectionQueryResult {\n mode: 'cell' | 'row' | 'range';\n ranges: CellRange[];\n anchor: { row: number; col: number } | null;\n}\n// #endregion\n\n// Re-export types\nexport type { ClipboardConfig, CopyDetail, CopyOptions, PasteDetail } from './types';\n","/**\n * Column Virtualization Core Logic\n *\n * Pure functions for horizontal column virtualization operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ColumnVirtualizationViewport } from './types';\n\n/** Default column width when not specified */\nconst DEFAULT_COLUMN_WIDTH = 100;\n\n/**\n * Parse a column width value to pixels.\n * Handles number (px) and string formats.\n *\n * @param width - The width value from column config\n * @returns Width in pixels\n */\nexport function parseColumnWidth(width: string | number | undefined): number {\n if (width === undefined || width === null) {\n return DEFAULT_COLUMN_WIDTH;\n }\n\n if (typeof width === 'number') {\n return width;\n }\n\n // Handle string values - extract numeric part\n const numeric = parseFloat(width);\n if (!isNaN(numeric)) {\n return numeric;\n }\n\n return DEFAULT_COLUMN_WIDTH;\n}\n\n/**\n * Get array of column widths in pixels.\n *\n * @param columns - Column configurations\n * @returns Array of widths in pixels\n */\nexport function getColumnWidths(columns: readonly ColumnConfig[]): number[] {\n return columns.map((col) => parseColumnWidth(col.width));\n}\n\n/**\n * Compute cumulative left offsets for each column.\n *\n * @param columns - Column configurations\n * @returns Array of left offsets in pixels\n */\nexport function computeColumnOffsets(columns: readonly ColumnConfig[]): number[] {\n const offsets: number[] = [];\n let offset = 0;\n\n for (const col of columns) {\n offsets.push(offset);\n offset += parseColumnWidth(col.width);\n }\n\n return offsets;\n}\n\n/**\n * Compute total width of all columns.\n *\n * @param columns - Column configurations\n * @returns Total width in pixels\n */\nexport function computeTotalWidth(columns: readonly ColumnConfig[]): number {\n return columns.reduce((sum, col) => sum + parseColumnWidth(col.width), 0);\n}\n\n/**\n * Find the visible column range based on scroll position.\n * Uses binary search for efficient lookup in large column sets.\n *\n * @param scrollLeft - Current horizontal scroll position\n * @param viewportWidth - Width of the visible viewport\n * @param columnOffsets - Array of column left offsets\n * @param columnWidths - Array of column widths\n * @param overscan - Number of extra columns to render on each side\n * @returns Viewport information with visible column indices\n */\nexport function getVisibleColumnRange(\n scrollLeft: number,\n viewportWidth: number,\n columnOffsets: number[],\n columnWidths: number[],\n overscan: number\n): ColumnVirtualizationViewport {\n const columnCount = columnOffsets.length;\n\n if (columnCount === 0) {\n return { startCol: 0, endCol: 0, visibleColumns: [] };\n }\n\n // Binary search for first visible column\n let startCol = binarySearchFirstVisible(scrollLeft, columnOffsets, columnWidths);\n startCol = Math.max(0, startCol - overscan);\n\n // Find last visible column (without overscan first)\n const rightEdge = scrollLeft + viewportWidth;\n let endCol = startCol;\n\n for (let i = startCol; i < columnCount; i++) {\n if (columnOffsets[i] >= rightEdge) {\n endCol = i - 1;\n break;\n }\n endCol = i;\n }\n\n // Apply overscan to end (only once)\n endCol = Math.min(columnCount - 1, endCol + overscan);\n\n // Build array of visible column indices\n const visibleColumns: number[] = [];\n for (let i = startCol; i <= endCol; i++) {\n visibleColumns.push(i);\n }\n\n return { startCol, endCol, visibleColumns };\n}\n\n/**\n * Binary search to find the first column that is visible.\n *\n * @param scrollLeft - Current scroll position\n * @param columnOffsets - Array of column offsets\n * @param columnWidths - Array of column widths\n * @returns Index of first visible column\n */\nfunction binarySearchFirstVisible(scrollLeft: number, columnOffsets: number[], columnWidths: number[]): number {\n let low = 0;\n let high = columnOffsets.length - 1;\n\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n const colRight = columnOffsets[mid] + columnWidths[mid];\n\n if (colRight <= scrollLeft) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n\n return low;\n}\n\n/**\n * Determine if column virtualization should be active.\n *\n * @param columnCount - Number of columns\n * @param threshold - Column count threshold\n * @param autoEnable - Whether auto-enable is configured\n * @returns True if virtualization should be active\n */\nexport function shouldVirtualize(columnCount: number, threshold: number, autoEnable: boolean): boolean {\n if (!autoEnable) return false;\n return columnCount > threshold;\n}\n","/**\n * Column Virtualization Plugin (Class-based)\n *\n * Provides horizontal column virtualization for grids with many columns.\n * Significantly improves rendering performance when dealing with >30 columns.\n */\n\nimport { BaseGridPlugin, ScrollEvent } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n computeColumnOffsets,\n computeTotalWidth,\n getColumnWidths,\n getVisibleColumnRange,\n shouldVirtualize,\n} from './column-virtualization';\nimport type { ColumnVirtualizationConfig } from './types';\n\n/**\n * Column Virtualization Plugin for tbw-grid\n *\n * Provides horizontal column virtualization for grids with many columns (30+).\n * Only renders visible columns plus overscan, significantly improving rendering\n * performance for wide grids.\n *\n * ## Installation\n *\n * ```ts\n * import { ColumnVirtualizationPlugin } from '@toolbox-web/grid/plugins/column-virtualization';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `autoEnable` | `boolean` | `true` | Auto-enable when column count exceeds threshold |\n * | `threshold` | `number` | `30` | Column count threshold for auto-enable |\n * | `overscan` | `number` | `3` | Extra columns to render beyond visible |\n *\n * ## Requirements\n *\n * - Grid must use `fitMode: 'fixed'`\n * - Columns must have explicit widths\n * - Grid must have fixed height\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `isEnabled` | `() => boolean` | Check if virtualization is active |\n * | `setEnabled` | `(enabled: boolean) => void` | Enable/disable virtualization |\n * | `getVisibleRange` | `() => { start, end }` | Get visible column range |\n *\n * @example Wide Grid with Column Virtualization\n * ```ts\n * import '@toolbox-web/grid';\n * import { ColumnVirtualizationPlugin } from '@toolbox-web/grid/plugins/column-virtualization';\n *\n * grid.gridConfig = {\n * columns: generateManyColumns(100), // 100 columns\n * fitMode: 'fixed', // Required\n * plugins: [\n * new ColumnVirtualizationPlugin({\n * threshold: 30, // Enable when >30 columns\n * overscan: 3, // Render 3 extra columns each side\n * }),\n * ],\n * };\n * ```\n *\n * @see {@link ColumnVirtualizationConfig} for configuration options\n *\n * @internal Extends BaseGridPlugin\n */\nexport class ColumnVirtualizationPlugin extends BaseGridPlugin<ColumnVirtualizationConfig> {\n /** @internal */\n readonly name = 'columnVirtualization';\n\n /** @internal */\n protected override get defaultConfig(): Partial<ColumnVirtualizationConfig> {\n return {\n autoEnable: true,\n threshold: 30,\n overscan: 3,\n };\n }\n\n // #region Internal State\n private isVirtualized = false;\n private startCol = 0;\n private endCol = 0;\n private scrollLeft = 0;\n private totalWidth = 0;\n private columnWidths: number[] = [];\n private columnOffsets: number[] = [];\n /** Store the original full column set for virtualization calculations */\n private originalColumns: readonly ColumnConfig[] = [];\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Initialize state from current columns\n const columns = this.columns;\n this.columnWidths = getColumnWidths(columns);\n this.columnOffsets = computeColumnOffsets(columns);\n this.totalWidth = computeTotalWidth(columns);\n this.endCol = columns.length - 1;\n }\n\n /** @internal */\n override detach(): void {\n // Clean up inline styles set by this plugin\n this.#cleanupStyles();\n\n this.columnWidths = [];\n this.columnOffsets = [];\n this.originalColumns = [];\n this.isVirtualized = false;\n this.startCol = 0;\n this.endCol = 0;\n this.scrollLeft = 0;\n this.totalWidth = 0;\n }\n\n /**\n * Remove inline styles set by this plugin for proper cleanup.\n */\n #cleanupStyles(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n const headerRow = gridEl.querySelector('.header-row') as HTMLElement | null;\n if (headerRow) {\n headerRow.style.paddingLeft = '';\n headerRow.style.minWidth = '';\n }\n\n const bodyRows = gridEl.querySelectorAll('.data-grid-row');\n bodyRows.forEach((row) => {\n (row as HTMLElement).style.paddingLeft = '';\n });\n\n const rowsContainer = gridEl.querySelector('.rows-viewport .rows') as HTMLElement | null;\n if (rowsContainer) {\n rowsContainer.style.width = '';\n }\n\n const rowsBody = gridEl.querySelector('.rows-body') as HTMLElement | null;\n if (rowsBody) {\n rowsBody.style.minWidth = '';\n }\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n // Detect if this is a new column set or just a scroll-triggered re-render.\n // We consider it \"new\" if:\n // 1. We don't have any stored yet (first time)\n // 2. Incoming has AS MANY OR MORE columns than we stored (genuine new config)\n // When virtualization is active, our output is a SUBSET (fewer columns),\n // so a scroll-triggered re-render will have fewer columns than originalColumns.\n // When virtualization is off, our output has the same count as originalColumns,\n // but any config change (e.g., pinning a column) also has the same count.\n // Using >= ensures we pick up property-only changes (like adding `pinned`).\n const isNewColumnSet = this.originalColumns.length === 0 || columns.length >= this.originalColumns.length;\n\n if (isNewColumnSet) {\n // Store the full column set\n this.originalColumns = columns;\n this.columnWidths = getColumnWidths(columns);\n this.columnOffsets = computeColumnOffsets(columns);\n this.totalWidth = computeTotalWidth(columns);\n }\n\n // Use the original (full) column set for virtualization decisions\n const fullColumns = this.originalColumns;\n const isVirtualized = shouldVirtualize(\n fullColumns.length,\n this.config.threshold ?? 30,\n this.config.autoEnable ?? true,\n );\n\n this.isVirtualized = isVirtualized ?? false;\n\n if (!isVirtualized) {\n this.startCol = 0;\n this.endCol = fullColumns.length - 1;\n return [...fullColumns];\n }\n\n // Get viewport width from grid element\n const viewportWidth = (this.grid as unknown as HTMLElement).clientWidth || 800;\n const viewport = getVisibleColumnRange(\n this.scrollLeft,\n viewportWidth,\n this.columnOffsets,\n this.columnWidths,\n this.config.overscan ?? 3,\n );\n\n this.startCol = viewport.startCol;\n this.endCol = viewport.endCol;\n\n // Return only visible columns from the ORIGINAL full set\n return viewport.visibleColumns.map((i) => fullColumns[i]);\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.isVirtualized) return;\n\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Apply left padding to offset scrolled-out columns\n const leftPadding = this.columnOffsets[this.startCol] ?? 0;\n\n const headerRow = gridEl.querySelector('.header-row') as HTMLElement | null;\n const bodyRows = gridEl.querySelectorAll('.data-grid-row');\n\n if (headerRow) {\n headerRow.style.paddingLeft = `${leftPadding}px`;\n // Set min-width on header row to enable horizontal scrolling\n headerRow.style.minWidth = `${this.totalWidth}px`;\n }\n\n bodyRows.forEach((row) => {\n (row as HTMLElement).style.paddingLeft = `${leftPadding}px`;\n });\n\n // Set total width for horizontal scrolling on the rows container\n const rowsContainer = gridEl.querySelector('.rows-viewport .rows') as HTMLElement | null;\n if (rowsContainer) {\n rowsContainer.style.width = `${this.totalWidth}px`;\n }\n\n // Also set min-width on .rows-body to ensure the scroll container knows the total scrollable width\n const rowsBody = gridEl.querySelector('.rows-body') as HTMLElement | null;\n if (rowsBody) {\n rowsBody.style.minWidth = `${this.totalWidth}px`;\n }\n }\n\n /** @internal */\n override onScroll(event: ScrollEvent): void {\n if (!this.isVirtualized) return;\n\n // Check if horizontal scroll position changed significantly\n const scrollDelta = Math.abs(event.scrollLeft - this.scrollLeft);\n if (scrollDelta < 1) return;\n\n // Update scroll position\n this.scrollLeft = event.scrollLeft;\n\n // Recalculate visible columns and request re-render\n // Must use requestColumnsRender() to trigger COLUMNS phase (processColumns hook)\n // requestRender() only requests ROWS phase which doesn't reprocess columns\n this.requestColumnsRender();\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Check if column virtualization is currently active.\n */\n getIsVirtualized(): boolean {\n return this.isVirtualized;\n }\n\n /**\n * Get the current visible column range.\n */\n getVisibleColumnRange(): { start: number; end: number } {\n return { start: this.startCol, end: this.endCol };\n }\n\n /**\n * Scroll the grid to bring a specific column into view.\n * @param columnIndex - Index of the column to scroll to\n */\n scrollToColumn(columnIndex: number): void {\n const offset = this.columnOffsets[columnIndex] ?? 0;\n const gridEl = this.grid as unknown as HTMLElement;\n // Scroll the grid element itself (it's the scroll container)\n gridEl.scrollLeft = offset;\n }\n\n /**\n * Get the left offset for a specific column.\n * @param columnIndex - Index of the column\n */\n getColumnOffset(columnIndex: number): number {\n return this.columnOffsets[columnIndex] ?? 0;\n }\n\n /**\n * Get the total width of all columns.\n */\n getTotalWidth(): number {\n return this.totalWidth;\n }\n // #endregion\n}\n","/**\n * Context Menu Rendering Logic\n *\n * Pure functions for building and positioning context menus.\n */\n\nimport type { IconValue } from '../../core/types';\nimport { DEFAULT_GRID_ICONS } from '../../core/types';\nimport type { ContextMenuItem, ContextMenuParams } from './types';\n\n/**\n * Build the visible menu items by resolving dynamic items and filtering hidden ones.\n *\n * @param items - Menu items configuration (array or factory function)\n * @param params - Context menu parameters for evaluating dynamic properties\n * @returns Filtered array of visible menu items\n */\nexport function buildMenuItems(\n items: ContextMenuItem[] | ((params: ContextMenuParams) => ContextMenuItem[]),\n params: ContextMenuParams,\n): ContextMenuItem[] {\n const menuItems = typeof items === 'function' ? items(params) : items;\n\n return menuItems.filter((item) => {\n if (item.hidden === true) return false;\n if (typeof item.hidden === 'function' && item.hidden(params)) return false;\n return true;\n });\n}\n\n/**\n * Remove consecutive, leading, and trailing separators from a menu item list.\n *\n * @param items - Array of menu items\n * @returns Cleaned array with no redundant separators\n */\nexport function collapseSeparators(items: ContextMenuItem[]): ContextMenuItem[] {\n const result: ContextMenuItem[] = [];\n for (const item of items) {\n if (item.separator) {\n // Skip if the last item is already a separator (consecutive) or list is empty (leading)\n if (result.length === 0 || result[result.length - 1].separator) continue;\n }\n result.push(item);\n }\n // Remove trailing separator\n if (result.length > 0 && result[result.length - 1].separator) {\n result.pop();\n }\n return result;\n}\n\n/**\n * Check if a menu item is disabled.\n *\n * @param item - The menu item to check\n * @param params - Context menu parameters for evaluating dynamic disabled state\n * @returns True if the item is disabled\n */\nexport function isItemDisabled(item: ContextMenuItem, params: ContextMenuParams): boolean {\n if (item.disabled === true) return true;\n if (typeof item.disabled === 'function') return item.disabled(params);\n return false;\n}\n\n/**\n * Create the menu DOM element from a list of menu items.\n *\n * @param items - Array of menu items to render\n * @param params - Context menu parameters for evaluating dynamic properties\n * @param onAction - Callback when a menu item action is triggered\n * @param submenuArrow - Optional custom submenu arrow icon\n * @returns The created menu element\n */\nexport function createMenuElement(\n items: ContextMenuItem[],\n params: ContextMenuParams,\n onAction: (item: ContextMenuItem) => void,\n submenuArrow: IconValue = DEFAULT_GRID_ICONS.submenuArrow,\n): HTMLElement {\n const menu = document.createElement('div');\n menu.className = 'tbw-context-menu';\n menu.setAttribute('role', 'menu');\n\n // Check if any non-separator item has an icon\n const hasAnyIcon = items.some((item) => !item.separator && item.icon);\n\n for (const item of items) {\n if (item.separator) {\n const separator = document.createElement('div');\n separator.className = 'tbw-context-menu-separator';\n separator.setAttribute('role', 'separator');\n menu.appendChild(separator);\n continue;\n }\n\n const menuItem = document.createElement('div');\n menuItem.className = 'tbw-context-menu-item';\n if (item.cssClass) menuItem.classList.add(item.cssClass);\n menuItem.setAttribute('role', 'menuitem');\n menuItem.setAttribute('data-id', item.id);\n\n const disabled = isItemDisabled(item, params);\n if (disabled) {\n menuItem.classList.add('disabled');\n menuItem.setAttribute('aria-disabled', 'true');\n }\n\n if (item.icon) {\n const icon = document.createElement('span');\n icon.className = 'tbw-context-menu-icon';\n icon.innerHTML = item.icon;\n menuItem.appendChild(icon);\n } else if (hasAnyIcon) {\n // Add empty placeholder to align labels when other items have icons\n const icon = document.createElement('span');\n icon.className = 'tbw-context-menu-icon';\n icon.innerHTML = ' ';\n menuItem.appendChild(icon);\n }\n\n const label = document.createElement('span');\n label.className = 'tbw-context-menu-label';\n label.textContent = item.name;\n menuItem.appendChild(label);\n\n if (item.shortcut) {\n const shortcut = document.createElement('span');\n shortcut.className = 'tbw-context-menu-shortcut';\n shortcut.textContent = item.shortcut;\n menuItem.appendChild(shortcut);\n }\n\n if (item.subMenu?.length) {\n const arrow = document.createElement('span');\n arrow.className = 'tbw-context-menu-arrow';\n // Use provided submenu arrow icon (string or HTMLElement)\n if (typeof submenuArrow === 'string') {\n arrow.innerHTML = submenuArrow;\n } else if (submenuArrow instanceof HTMLElement) {\n arrow.appendChild(submenuArrow.cloneNode(true));\n }\n menuItem.appendChild(arrow);\n\n // Add submenu on hover\n menuItem.addEventListener('mouseenter', () => {\n const existingSubMenu = menuItem.querySelector('.tbw-context-menu');\n if (existingSubMenu) return;\n if (!item.subMenu) return;\n\n const subMenuItems = buildMenuItems(item.subMenu, params);\n const subMenu = createMenuElement(subMenuItems, params, onAction, submenuArrow);\n subMenu.classList.add('tbw-context-submenu');\n subMenu.style.position = 'absolute';\n subMenu.style.left = '100%';\n subMenu.style.top = '0';\n menuItem.style.position = 'relative';\n menuItem.appendChild(subMenu);\n });\n\n menuItem.addEventListener('mouseleave', () => {\n const subMenu = menuItem.querySelector('.tbw-context-menu');\n if (subMenu) subMenu.remove();\n });\n }\n\n if (!disabled && item.action && !item.subMenu) {\n menuItem.addEventListener('click', (e) => {\n e.stopPropagation();\n onAction(item);\n });\n }\n\n menu.appendChild(menuItem);\n }\n\n return menu;\n}\n\n/**\n * Position the menu at the given viewport coordinates.\n * Menu is rendered in document.body with fixed positioning for top-layer behavior.\n *\n * @param menu - The menu element to position\n * @param x - Desired X coordinate (viewport)\n * @param y - Desired Y coordinate (viewport)\n */\nexport function positionMenu(menu: HTMLElement, x: number, y: number): void {\n // Use fixed positioning for top-layer behavior\n menu.style.position = 'fixed';\n menu.style.left = `${x}px`;\n menu.style.top = `${y}px`;\n menu.style.visibility = 'hidden';\n menu.style.zIndex = '10000';\n\n // Force layout to get dimensions\n const menuRect = menu.getBoundingClientRect();\n\n // Calculate visible area within viewport\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n\n let left = x;\n let top = y;\n\n // Check if menu overflows right edge of viewport\n if (x + menuRect.width > viewportWidth) {\n left = x - menuRect.width;\n }\n // Check if menu overflows bottom edge of viewport\n if (y + menuRect.height > viewportHeight) {\n top = y - menuRect.height;\n }\n\n // Ensure we don't go negative\n left = Math.max(0, left);\n top = Math.max(0, top);\n\n menu.style.left = `${left}px`;\n menu.style.top = `${top}px`;\n menu.style.visibility = 'visible';\n}\n","/**\n * Context Menu Plugin (Class-based)\n *\n * Provides right-click context menu functionality for tbw-grid.\n * Supports custom menu items, submenus, icons, shortcuts, and dynamic item generation.\n */\n\nimport type { PluginManifest } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport contextMenuStyles from './context-menu.css?inline';\nimport { buildMenuItems, collapseSeparators, createMenuElement, positionMenu } from './menu';\nimport type { ContextMenuConfig, ContextMenuItem, ContextMenuParams, HeaderContextMenuItem } from './types';\n\n/** Query type for collecting context menu items from plugins */\nconst QUERY_GET_CONTEXT_MENU_ITEMS = 'getContextMenuItems';\n\n/** Global click handler reference for cleanup */\nlet globalClickHandler: ((e: Event) => void) | null = null;\n/** Global keydown handler reference for cleanup */\nlet globalKeydownHandler: ((e: KeyboardEvent) => void) | null = null;\n/** Global stylesheet for context menu (injected once) */\nlet globalStyleSheet: HTMLStyleElement | null = null;\n/** Reference count for instances using global handlers */\nlet globalHandlerRefCount = 0;\n\n/** Default menu items when none are configured */\nconst defaultItems: ContextMenuItem[] = [\n {\n id: 'copy',\n name: 'Copy',\n shortcut: 'Ctrl+C',\n action: (params) => {\n const grid = (params as ContextMenuParams & { grid?: { plugins?: { clipboard?: { copy?: () => void } } } }).grid;\n grid?.plugins?.clipboard?.copy?.();\n },\n },\n { separator: true, id: 'sep1', name: '' },\n {\n id: 'export-csv',\n name: 'Export CSV',\n action: (params) => {\n const grid = (params as ContextMenuParams & { grid?: { plugins?: { export?: { exportCsv?: () => void } } } })\n .grid;\n grid?.plugins?.export?.exportCsv?.();\n },\n },\n];\n\n/**\n * Context Menu Plugin for tbw-grid\n *\n * Adds a customizable right-click menu to grid cells. Build anything from simple\n * copy/paste actions to complex nested menus with conditional visibility, icons,\n * and keyboard shortcuts.\n *\n * ## Installation\n *\n * ```ts\n * import { ContextMenuPlugin } from '@toolbox-web/grid/plugins/context-menu';\n * ```\n *\n * ## Menu Item Structure\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `id` | `string` | Unique item identifier |\n * | `name` | `string` | Display label |\n * | `icon` | `string` | Icon class or HTML |\n * | `shortcut` | `string` | Keyboard shortcut hint |\n * | `action` | `(params) => void` | Click handler |\n * | `disabled` | `boolean \\| (params) => boolean` | Disable condition |\n * | `visible` | `boolean \\| (params) => boolean` | Visibility condition |\n * | `items` | `MenuItem[]` | Submenu items |\n * | `separator` | `boolean` | Create a divider line |\n *\n * ## Menu Context (params)\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `rowIndex` | `number` | Clicked row index |\n * | `colIndex` | `number` | Clicked column index |\n * | `field` | `string` | Column field name |\n * | `value` | `any` | Cell value |\n * | `row` | `any` | Full row data |\n * | `column` | `ColumnConfig` | Column configuration |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-context-menu-bg` | `var(--tbw-color-panel-bg)` | Menu background |\n * | `--tbw-context-menu-fg` | `var(--tbw-color-fg)` | Menu text color |\n * | `--tbw-context-menu-hover` | `var(--tbw-color-row-hover)` | Item hover background |\n *\n * @example Basic Context Menu\n * ```ts\n * import '@toolbox-web/grid';\n * import { ContextMenuPlugin } from '@toolbox-web/grid/plugins/context-menu';\n *\n * grid.gridConfig = {\n * plugins: [\n * new ContextMenuPlugin({\n * items: [\n * { id: 'copy', name: 'Copy', shortcut: 'Ctrl+C', action: (ctx) => navigator.clipboard.writeText(ctx.value) },\n * { separator: true, id: 'sep1', name: '' },\n * { id: 'delete', name: 'Delete', action: (ctx) => removeRow(ctx.rowIndex) },\n * ],\n * }),\n * ],\n * };\n * ```\n *\n * @example Conditional Menu Items\n * ```ts\n * new ContextMenuPlugin({\n * items: [\n * { id: 'edit', name: 'Edit', visible: (ctx) => ctx.column.editable === true },\n * { id: 'delete', name: 'Delete', disabled: (ctx) => ctx.row.locked === true },\n * ],\n * })\n * ```\n *\n * @see {@link ContextMenuConfig} for configuration options\n * @see {@link ContextMenuItem} for menu item structure\n * @see {@link ContextMenuParams} for action callback parameters\n *\n * @internal Extends BaseGridPlugin\n */\nexport class ContextMenuPlugin extends BaseGridPlugin<ContextMenuConfig> {\n /**\n * Plugin manifest - declares queries used by this plugin.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n queries: [\n {\n type: QUERY_GET_CONTEXT_MENU_ITEMS,\n description: 'Collects context menu items from other plugins for header right-click menus',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'contextMenu';\n\n /** @internal */\n protected override get defaultConfig(): Partial<ContextMenuConfig> {\n return {\n items: defaultItems,\n };\n }\n\n // #region Internal State\n private isOpen = false;\n private position = { x: 0, y: 0 };\n private params: ContextMenuParams | null = null;\n private menuElement: HTMLElement | null = null;\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n this.installGlobalHandlers();\n globalHandlerRefCount++;\n }\n\n /** @internal */\n override detach(): void {\n if (this.menuElement) {\n this.menuElement.remove();\n this.menuElement = null;\n }\n this.isOpen = false;\n this.params = null;\n this.uninstallGlobalHandlers();\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Sync selection with the right-clicked row.\n * If the right-clicked row is already selected, keeps the multi-selection.\n * If not, selects only the right-clicked row (standard behavior in file managers / spreadsheets).\n *\n * @returns Sorted array of selected row indices after sync\n */\n private syncSelectionOnContextMenu(rowIndex: number): number[] {\n if (rowIndex < 0) return [];\n\n // Use the query system for loose coupling — no import of SelectionPlugin needed\n const selectionResult = this.grid?.query<number[]>('getSelectedRowIndices');\n const currentSelection = selectionResult?.[0];\n\n // No selection plugin loaded\n if (!currentSelection) return [rowIndex];\n\n if (currentSelection.includes(rowIndex)) {\n // Right-clicked row is already selected — preserve multi-selection\n return currentSelection;\n }\n\n // Right-clicked row is NOT selected — select only this row\n this.grid?.query('selectRows', [rowIndex]);\n return [rowIndex];\n }\n\n /**\n * CSS variables to copy from the grid element to the context menu.\n * Includes both base variables and context-menu specific overrides.\n */\n private static readonly CSS_VARS_TO_COPY = [\n // Base palette (for themes that only set base vars)\n '--tbw-color-panel-bg',\n '--tbw-color-fg',\n '--tbw-color-fg-muted',\n '--tbw-color-border',\n '--tbw-color-row-hover',\n '--tbw-color-shadow',\n '--tbw-color-danger',\n '--tbw-border-radius',\n '--tbw-font-family',\n '--tbw-font-size-sm',\n '--tbw-font-size-xs',\n '--tbw-font-size-2xs',\n '--tbw-spacing-xs',\n '--tbw-icon-size',\n '--tbw-menu-min-width',\n '--tbw-menu-item-padding',\n '--tbw-menu-item-gap',\n // Context menu specific overrides\n '--tbw-context-menu-bg',\n '--tbw-context-menu-fg',\n '--tbw-context-menu-border',\n '--tbw-context-menu-radius',\n '--tbw-context-menu-shadow',\n '--tbw-context-menu-hover',\n '--tbw-context-menu-danger',\n '--tbw-context-menu-muted',\n '--tbw-context-menu-min-width',\n '--tbw-context-menu-font-size',\n '--tbw-context-menu-font-family',\n '--tbw-context-menu-item-padding',\n '--tbw-context-menu-item-gap',\n '--tbw-context-menu-icon-size',\n '--tbw-context-menu-shortcut-size',\n '--tbw-context-menu-arrow-size',\n ];\n\n /**\n * Copy CSS custom properties from the grid element to the menu element.\n * This allows the context menu (appended to document.body) to inherit\n * theme variables set on tbw-grid.\n */\n private copyGridStyles(menuElement: HTMLElement): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n const computed = getComputedStyle(gridEl);\n const styles: string[] = [];\n\n // Copy color-scheme so light-dark() can resolve in the context menu\n const colorScheme = computed.getPropertyValue('color-scheme').trim();\n if (colorScheme) {\n styles.push(`color-scheme: ${colorScheme}`);\n }\n\n for (const varName of ContextMenuPlugin.CSS_VARS_TO_COPY) {\n const value = computed.getPropertyValue(varName).trim();\n if (value) {\n styles.push(`${varName}: ${value}`);\n }\n }\n\n if (styles.length > 0) {\n // Append to existing inline styles (don't overwrite)\n const existing = menuElement.getAttribute('style') || '';\n menuElement.setAttribute('style', existing + styles.join('; ') + ';');\n }\n }\n\n private installGlobalHandlers(): void {\n // Inject global stylesheet for context menu (once)\n // Only inject if we have valid CSS text (Vite's ?inline import)\n // When importing from source without Vite, the import is a module object, not a string\n if (\n !globalStyleSheet &&\n typeof document !== 'undefined' &&\n typeof contextMenuStyles === 'string' &&\n contextMenuStyles\n ) {\n globalStyleSheet = document.createElement('style');\n globalStyleSheet.id = 'tbw-context-menu-styles';\n globalStyleSheet.textContent = contextMenuStyles;\n document.head.appendChild(globalStyleSheet);\n }\n\n // Close menu on click outside\n if (!globalClickHandler) {\n globalClickHandler = () => {\n const menus = document.querySelectorAll('.tbw-context-menu');\n menus.forEach((menu) => menu.remove());\n };\n document.addEventListener('click', globalClickHandler);\n }\n\n // Close on escape\n if (!globalKeydownHandler) {\n globalKeydownHandler = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n const menus = document.querySelectorAll('.tbw-context-menu');\n menus.forEach((menu) => menu.remove());\n }\n };\n document.addEventListener('keydown', globalKeydownHandler);\n }\n }\n\n /**\n * Clean up global handlers when the last instance detaches.\n * Uses reference counting to ensure handlers persist while any grid uses the plugin.\n */\n private uninstallGlobalHandlers(): void {\n globalHandlerRefCount--;\n if (globalHandlerRefCount > 0) return;\n\n // Last instance - clean up all global resources\n if (globalClickHandler) {\n document.removeEventListener('click', globalClickHandler);\n globalClickHandler = null;\n }\n if (globalKeydownHandler) {\n document.removeEventListener('keydown', globalKeydownHandler);\n globalKeydownHandler = null;\n }\n if (globalStyleSheet) {\n globalStyleSheet.remove();\n globalStyleSheet = null;\n }\n }\n\n /**\n * Query all plugins for context menu items via the query system.\n * Each plugin that handles `getContextMenuItems` can return an array of HeaderContextMenuItem.\n */\n private collectPluginItems(params: ContextMenuParams): HeaderContextMenuItem[] {\n if (!this.grid) return [];\n\n const responses = this.grid.query<HeaderContextMenuItem[]>(QUERY_GET_CONTEXT_MENU_ITEMS, params);\n const items: HeaderContextMenuItem[] = [];\n\n for (const response of responses) {\n if (Array.isArray(response)) {\n items.push(...response);\n }\n }\n\n // Sort by order (default 100), then stable by insertion order\n items.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n\n // Insert separators between different order groups\n return this.insertGroupSeparators(items);\n }\n\n /**\n * Insert separators between groups of items with different order ranges.\n * Groups are defined by the tens digit (10-19, 20-29, etc.).\n */\n private insertGroupSeparators(items: HeaderContextMenuItem[]): HeaderContextMenuItem[] {\n if (items.length <= 1) return items;\n\n const result: HeaderContextMenuItem[] = [];\n let lastGroup = -1;\n\n for (const item of items) {\n if (item.separator) {\n result.push(item);\n continue;\n }\n const group = Math.floor((item.order ?? 100) / 10);\n if (lastGroup >= 0 && group !== lastGroup) {\n result.push({\n id: `__sep-${lastGroup}-${group}`,\n label: '',\n separator: true,\n action: () => {\n /* noop */\n },\n });\n }\n lastGroup = group;\n result.push(item);\n }\n\n return result;\n }\n\n /**\n * Convert plugin-contributed HeaderContextMenuItems to the internal ContextMenuItem format.\n */\n private convertPluginItems(items: HeaderContextMenuItem[]): ContextMenuItem[] {\n return items.map((item) => ({\n id: item.id,\n name: item.label,\n icon: item.icon,\n shortcut: item.shortcut,\n disabled: item.disabled ?? false,\n action: () => item.action(),\n separator: item.separator,\n cssClass: item.cssClass,\n }));\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Use querySelector instead of children[0] because light DOM children\n // (e.g. <tbw-grid-column>) are re-appended before .tbw-grid-root, making\n // children[0] point to a declarative element instead of the data container.\n const container = gridEl.querySelector('.tbw-grid-root');\n if (!container) return;\n\n // Check if handler already attached\n if (container.getAttribute('data-context-menu-bound') === 'true') return;\n container.setAttribute('data-context-menu-bound', 'true');\n\n container.addEventListener('contextmenu', (e: Event) => {\n const event = e as MouseEvent;\n event.preventDefault();\n\n const target = event.target as HTMLElement;\n const cell = target.closest('[data-row][data-col]');\n const header = target.closest('[part~=\"header-cell\"]');\n\n let params: ContextMenuParams;\n\n if (cell) {\n const rowIndex = parseInt(cell.getAttribute('data-row') ?? '-1', 10);\n const colIndex = parseInt(cell.getAttribute('data-col') ?? '-1', 10);\n const column = this.visibleColumns[colIndex];\n const row = this.rows[rowIndex];\n\n // Sync selection: if the right-clicked row is not already selected,\n // select it (clearing multi-selection). If it IS selected, keep all.\n const selectedRows = this.syncSelectionOnContextMenu(rowIndex);\n\n params = {\n row,\n rowIndex,\n column,\n columnIndex: colIndex,\n field: column?.field ?? '',\n value: row?.[column?.field as keyof typeof row] ?? null,\n isHeader: false,\n event,\n selectedRows,\n };\n } else if (header) {\n const colIndex = parseInt(header.getAttribute('data-col') ?? '-1', 10);\n const column = this.visibleColumns[colIndex];\n\n params = {\n row: null,\n rowIndex: -1,\n column,\n columnIndex: colIndex,\n field: column?.field ?? '',\n value: null,\n isHeader: true,\n event,\n selectedRows: [],\n };\n } else {\n return;\n }\n\n this.params = params;\n this.position = { x: event.clientX, y: event.clientY };\n\n // Collect plugin-contributed items via the query system\n const pluginItems = this.collectPluginItems(params);\n\n // Build configured items\n let items = buildMenuItems(this.config.items ?? defaultItems, params);\n\n // Merge plugin items with configured items\n if (pluginItems.length > 0) {\n const converted = this.convertPluginItems(pluginItems);\n if (items.length > 0 && converted.length > 0) {\n // Add separator between configured and plugin items\n items = [...items, { id: '__plugin-sep', name: '', separator: true }, ...converted];\n } else {\n items = [...items, ...converted];\n }\n }\n\n // Collapse consecutive/leading/trailing separators\n items = collapseSeparators(items);\n\n if (!items.length) return;\n\n if (this.menuElement) {\n this.menuElement.remove();\n }\n\n this.menuElement = createMenuElement(\n items,\n params,\n (item) => {\n if (item.action) {\n item.action(params);\n }\n this.menuElement?.remove();\n this.menuElement = null;\n this.isOpen = false;\n },\n this.gridIcons.submenuArrow,\n );\n\n document.body.appendChild(this.menuElement);\n this.copyGridStyles(this.menuElement);\n positionMenu(this.menuElement, event.clientX, event.clientY);\n this.isOpen = true;\n\n this.emit('context-menu-open', { params, items });\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Programmatically show the context menu at the specified position.\n * @param x - X coordinate\n * @param y - Y coordinate\n * @param params - Partial context menu parameters\n */\n showMenu(x: number, y: number, params: Partial<ContextMenuParams>): void {\n const fullParams: ContextMenuParams = {\n row: params.row ?? null,\n rowIndex: params.rowIndex ?? -1,\n column: params.column ?? null,\n columnIndex: params.columnIndex ?? -1,\n field: params.field ?? '',\n value: params.value ?? null,\n isHeader: params.isHeader ?? false,\n event: params.event ?? new MouseEvent('contextmenu'),\n selectedRows: params.selectedRows ?? [],\n };\n\n const pluginItems = this.collectPluginItems(fullParams);\n let items = buildMenuItems(this.config.items ?? defaultItems, fullParams);\n\n if (pluginItems.length > 0) {\n const converted = this.convertPluginItems(pluginItems);\n if (items.length > 0 && converted.length > 0) {\n items = [...items, { id: '__plugin-sep', name: '', separator: true }, ...converted];\n } else {\n items = [...items, ...converted];\n }\n }\n\n // Collapse consecutive/leading/trailing separators\n items = collapseSeparators(items);\n\n if (this.menuElement) {\n this.menuElement.remove();\n }\n\n this.menuElement = createMenuElement(\n items,\n fullParams,\n (item) => {\n if (item.action) item.action(fullParams);\n this.menuElement?.remove();\n this.menuElement = null;\n this.isOpen = false;\n },\n this.gridIcons.submenuArrow,\n );\n\n document.body.appendChild(this.menuElement);\n this.copyGridStyles(this.menuElement);\n positionMenu(this.menuElement, x, y);\n this.isOpen = true;\n }\n\n /**\n * Hide the context menu.\n */\n hideMenu(): void {\n if (this.menuElement) {\n this.menuElement.remove();\n this.menuElement = null;\n this.isOpen = false;\n }\n }\n\n /**\n * Check if the context menu is currently open.\n * @returns Whether the menu is open\n */\n isMenuOpen(): boolean {\n return this.isOpen;\n }\n // #endregion\n\n // Styles are injected globally via installGlobalHandlers() since menu renders in document.body\n}\n","/**\n * Default Editors Module\n *\n * Provides built-in editor factories for different column types.\n * Each editor type has its own factory function for consistency and readability.\n *\n * IMPORTANT: Editor factories should NOT call focus() on elements - they are called\n * before the element is appended to the DOM. The calling code (beginBulkEdit,\n * inlineEnterEdit) is responsible for focusing the correct editor after insertion.\n */\n\nimport type { ColumnConfig, ColumnEditorContext } from '../../core/types';\nimport type { DateEditorParams, NumberEditorParams, SelectEditorParams, TextEditorParams } from './types';\n\n// ============================================================================\n// Type Aliases\n// ============================================================================\n\n/** Option shape used by select editor (matches column.options) */\ntype ColumnOption = { label: string; value: unknown };\n\n/** Column with any row type (used for editor factories) */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyColumn = ColumnConfig<any>;\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/** Resolve column.options (handles both array and function forms) */\nfunction resolveOptions(column: AnyColumn): ColumnOption[] {\n const raw = column.options;\n if (!raw) return [];\n return typeof raw === 'function' ? raw() : raw;\n}\n\n// ============================================================================\n// Editor Factories\n// ============================================================================\n\n/** Creates a number input editor */\nfunction createNumberEditor(column: AnyColumn): (ctx: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as NumberEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'number';\n input.value = ctx.value != null ? String(ctx.value) : '';\n\n if (params?.min !== undefined) input.min = String(params.min);\n if (params?.max !== undefined) input.max = String(params.max);\n if (params?.step !== undefined) input.step = String(params.step);\n if (params?.placeholder) input.placeholder = params.placeholder;\n\n const commit = () => 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: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const input = document.createElement('input');\n input.type = 'checkbox';\n input.checked = !!ctx.value;\n input.addEventListener('change', () => ctx.commit(input.checked));\n return input;\n };\n}\n\n/** Creates a date input editor */\nfunction createDateEditor(column: AnyColumn): (ctx: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as DateEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'date';\n\n // Set initial value - handle both Date objects and string dates\n if (ctx.value instanceof Date) {\n input.valueAsDate = ctx.value;\n } else if (typeof ctx.value === 'string' && ctx.value) {\n // String date like \"2019-10-09\" - set directly as value\n input.value = ctx.value.split('T')[0]; // Handle ISO strings too\n }\n if (params?.min) input.min = params.min;\n if (params?.max) input.max = params.max;\n if (params?.placeholder) input.placeholder = params.placeholder;\n\n // Commit function preserves original type (string vs Date)\n const commit = () => {\n if (typeof ctx.value === 'string') {\n // Original was string, return string in YYYY-MM-DD format\n ctx.commit(input.value);\n } else {\n ctx.commit(input.valueAsDate);\n }\n };\n\n input.addEventListener('change', commit);\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return input;\n };\n}\n\n/** Creates a select dropdown editor */\nfunction createSelectEditor(column: AnyColumn): (ctx: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as SelectEditorParams | undefined;\n const select = document.createElement('select');\n if (column.multi) select.multiple = true;\n\n // Add 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: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as TextEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'text';\n input.value = ctx.value != null ? String(ctx.value) : '';\n\n if (params?.maxLength !== undefined) input.maxLength = params.maxLength;\n if (params?.pattern) input.pattern = params.pattern;\n if (params?.placeholder) input.placeholder = params.placeholder;\n\n // Commit function preserves original type when possible\n const commit = () => {\n const inputVal = input.value;\n // Preserve null/undefined: empty input on a null/undefined field means no change\n if ((ctx.value === null || ctx.value === undefined) && inputVal === '') {\n return;\n }\n // Preserve values with characters that <input> can't represent (newlines, etc.).\n // If stripping those characters produces the same string, the user didn't change anything.\n if (typeof ctx.value === 'string' && inputVal === ctx.value.replace(/[\\n\\r]/g, '')) {\n return;\n }\n // Preserve numeric type for custom column types (e.g., currency)\n if (typeof ctx.value === 'number' && inputVal !== '') {\n ctx.commit(Number(inputVal));\n } else {\n ctx.commit(inputVal);\n }\n };\n\n input.addEventListener('blur', commit);\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') commit();\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return input;\n };\n}\n\n// ============================================================================\n// Main Entry Point\n// ============================================================================\n\n/**\n * Returns a default editor factory function for the given column type.\n * Each editor handles commit on blur/Enter, and cancel on Escape.\n *\n * Note: Focus is NOT called here - the calling code handles focusing after DOM insertion.\n */\nexport function defaultEditorFor(column: AnyColumn): (ctx: ColumnEditorContext) => HTMLElement | string {\n switch (column.type) {\n case 'number':\n return createNumberEditor(column);\n case 'boolean':\n return createBooleanEditor();\n case 'date':\n return createDateEditor(column);\n case 'select':\n return createSelectEditor(column);\n default:\n return createTextEditor(column);\n }\n}\n\n// ============================================================================\n// Utility Export (used by EditingPlugin)\n// ============================================================================\n\n/**\n * 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 { ensureCellVisible } from '../../core/internal/keyboard';\nimport { FOCUSABLE_EDITOR_SELECTOR } from '../../core/internal/rows';\nimport type { AfterCellRenderContext, PluginManifest, PluginQuery } 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 {\n CellCommitDetail,\n ChangedRowsResetDetail,\n EditCloseDetail,\n EditingConfig,\n EditOpenDetail,\n EditorContext,\n RowCommitDetail,\n} from './types';\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, column config, and original value.\n * Preserves the type of the original value (e.g., numeric currency values stay as numbers,\n * string dates stay as strings).\n */\nfunction getInputValue(\n input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n column?: ColumnConfig<any>,\n originalValue?: unknown,\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') {\n // Preserve original type: if original was a string, return string (YYYY-MM-DD format)\n if (typeof originalValue === 'string') {\n return input.value; // input.value is already in YYYY-MM-DD format\n }\n return input.valueAsDate;\n }\n // For text inputs, check if original value was a number to preserve type\n if (typeof originalValue === 'number') {\n return input.value === '' ? null : Number(input.value);\n }\n // Preserve null/undefined: if original was null/undefined and input is empty, return original\n if ((originalValue === null || originalValue === undefined) && input.value === '') {\n return originalValue;\n }\n // Preserve values with characters <input> can't represent (newlines, etc.)\n if (typeof originalValue === 'string' && input.value === originalValue.replace(/[\\n\\r]/g, '')) {\n return originalValue;\n }\n return input.value;\n }\n // For textarea/select, check column type OR original value type\n if (column?.type === 'number' && input.value !== '') {\n return Number(input.value);\n }\n // Preserve numeric type for custom column types (e.g., currency)\n if (typeof originalValue === 'number' && input.value !== '') {\n return Number(input.value);\n }\n // Preserve null/undefined: if original was null/undefined and input is empty, return original\n if ((originalValue === null || originalValue === undefined) && input.value === '') {\n return originalValue;\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 originalValue?: unknown,\n): void {\n const input = editorHost.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (!input) return;\n\n input.addEventListener('blur', () => {\n commit(getInputValue(input, column, originalValue));\n });\n\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.addEventListener('change', () => commit(input.checked));\n } else if (input instanceof HTMLSelectElement) {\n input.addEventListener('change', () => commit(getInputValue(input, column, originalValue)));\n }\n}\n\n// ============================================================================\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 events: [\n {\n type: 'cell-edit-committed',\n description: 'Emitted when a cell edit is committed (for plugin-to-plugin coordination)',\n },\n ],\n queries: [\n {\n type: 'isEditing',\n description: 'Returns whether any cell is currently being edited',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'editing';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<EditingConfig> {\n return {\n mode: 'row',\n editOn: 'click',\n };\n }\n\n /**\n * Whether the grid is in 'grid' mode (all cells always editable).\n */\n get #isGridMode(): boolean {\n return this.config.mode === 'grid';\n }\n\n // #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 /**\n * Value-change callbacks for active editors.\n * Keyed by \"rowIndex:field\" → callback that pushes updated values to the editor.\n * Populated during #injectEditor, cleaned up when editors are removed.\n */\n #editorValueCallbacks = new Map<string, (newValue: unknown) => void>();\n\n /** Flag to restore focus after next render (used when exiting edit mode) */\n #pendingFocusRestore = false;\n\n /** Row index pending animation after render, or -1 if none */\n #pendingRowAnimation = -1;\n\n /**\n * Invalid cell tracking: Map<rowId, Map<field, message>>\n * Used for validation feedback without canceling edits.\n */\n #invalidCells = new Map<string, Map<string, string>>();\n\n /**\n * In grid mode, tracks whether an input field is currently focused.\n * When true: arrow keys work within input (edit mode).\n * When false: arrow keys navigate between cells (navigation mode).\n * Escape switches to navigation mode, Enter switches to edit mode.\n */\n #gridModeInputFocused = false;\n\n /**\n * In grid mode, when true, prevents inputs from auto-focusing.\n * This is set when Escape is pressed (navigation mode) and cleared\n * when Enter is pressed or user explicitly clicks an input.\n */\n #gridModeEditLocked = false;\n\n /**\n * When true, only a single cell is being edited (triggered by F2 or `beginCellEdit`).\n * Tab and Arrow keys commit and close the editor instead of navigating to adjacent cells.\n */\n #singleCellEdit = false;\n\n // #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 (only in 'row' mode)\n document.addEventListener(\n 'keydown',\n (e: KeyboardEvent) => {\n // In grid mode, Escape doesn't exit edit mode\n if (this.#isGridMode) return;\n if (e.key === 'Escape' && this.#activeEditRow !== -1) {\n // Allow users to prevent edit close via callback (e.g., when overlay is open)\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(e);\n if (shouldClose === false) {\n return;\n }\n }\n this.#exitRowEdit(this.#activeEditRow, true);\n }\n },\n { capture: true, signal },\n );\n\n // Click outside to commit editing (only in 'row' mode)\n // Use queueMicrotask to allow pending change events to fire first.\n // This is important for Angular/React editors where the (change) event\n // fires after mousedown but before mouseup/click.\n document.addEventListener(\n 'mousedown',\n (e: MouseEvent) => {\n // In grid mode, clicking outside doesn't exit edit mode\n if (this.#isGridMode) return;\n if (this.#activeEditRow === -1) return;\n const rowEl = internalGrid.findRenderedRowElement?.(this.#activeEditRow);\n if (!rowEl) return;\n const path = (e.composedPath && e.composedPath()) || [];\n if (path.includes(rowEl)) return;\n\n // Allow users to prevent edit close via callback (e.g., when click is inside an overlay)\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(e);\n if (shouldClose === false) {\n return;\n }\n }\n\n // Delay exit to allow pending change/commit events to fire\n queueMicrotask(() => {\n if (this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, false);\n }\n });\n },\n { signal },\n );\n\n // Listen for external row mutations to push updated values to active editors.\n // When field A commits and sets field B via updateRow(), field B's editor\n // (if open) must reflect the new value.\n this.gridElement.addEventListener(\n 'cell-change',\n (e: Event) => {\n const detail = (e as CustomEvent).detail as {\n rowIndex: number;\n field: string;\n newValue: unknown;\n source: string;\n };\n // Only push updates from cascade/api sources — not from the editor's own commit\n if (detail.source === 'user') return;\n const key = `${detail.rowIndex}:${detail.field}`;\n const cb = this.#editorValueCallbacks.get(key);\n if (cb) cb(detail.newValue);\n },\n { signal },\n );\n\n // In grid mode, request a full render to trigger afterCellRender hooks\n if (this.#isGridMode) {\n this.gridElement.classList.add('tbw-grid-mode');\n this.requestRender();\n\n // Track focus/blur on inputs to maintain navigation vs edit mode state\n this.gridElement.addEventListener(\n 'focusin',\n (e: FocusEvent) => {\n const target = e.target as HTMLElement;\n if (target.matches(FOCUSABLE_EDITOR_SELECTOR)) {\n // If edit is locked (navigation mode), blur the input immediately\n if (this.#gridModeEditLocked) {\n target.blur();\n this.gridElement.focus();\n return;\n }\n this.#gridModeInputFocused = true;\n }\n },\n { signal },\n );\n\n this.gridElement.addEventListener(\n 'focusout',\n (e: FocusEvent) => {\n const related = e.relatedTarget as HTMLElement | null;\n // Only clear if focus went outside grid or to a non-input element\n if (!related || !this.gridElement.contains(related) || !related.matches(FOCUSABLE_EDITOR_SELECTOR)) {\n this.#gridModeInputFocused = false;\n }\n },\n { signal },\n );\n\n // Handle Escape key directly on the grid element (capture phase)\n // This ensures we intercept Escape even when focus is inside an input\n this.gridElement.addEventListener(\n 'keydown',\n (e: KeyboardEvent) => {\n if (e.key === 'Escape' && this.#gridModeInputFocused) {\n const activeEl = document.activeElement as HTMLElement;\n if (activeEl && this.gridElement.contains(activeEl)) {\n activeEl.blur();\n // Move focus to the grid container so arrow keys work\n this.gridElement.focus();\n }\n this.#gridModeInputFocused = false;\n this.#gridModeEditLocked = true; // Lock edit mode until Enter/click\n e.preventDefault();\n e.stopPropagation();\n }\n },\n { capture: true, signal },\n );\n\n // Handle click on inputs - unlock edit mode when user explicitly clicks\n this.gridElement.addEventListener(\n 'mousedown',\n (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n if (target.matches(FOCUSABLE_EDITOR_SELECTOR)) {\n this.#gridModeEditLocked = false; // User clicked input - allow edit\n }\n },\n { signal },\n );\n }\n }\n\n /** @internal */\n override detach(): void {\n this.gridElement.classList.remove('tbw-grid-mode');\n this.#activeEditRow = -1;\n this.#activeEditCol = -1;\n this.#rowEditSnapshots.clear();\n this.#changedRowIds.clear();\n this.#editingCells.clear();\n this.#editorValueCallbacks.clear();\n this.#gridModeInputFocused = false;\n this.#gridModeEditLocked = false;\n this.#singleCellEdit = false;\n super.detach();\n }\n\n /**\n * Handle plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'isEditing') {\n // In grid mode, we're always editing\n return this.#isGridMode || this.#activeEditRow !== -1;\n }\n return undefined;\n }\n\n // #endregion\n\n // #region Event Handlers (event distribution)\n\n /**\n * Handle cell clicks - start editing if configured for click mode.\n * Both click and dblclick events come through this handler.\n * Starts row-based editing (all editable cells in the row get editors).\n * @internal\n */\n override onCellClick(event: CellClickEvent): boolean | void {\n // In grid mode, all cells are already editable - no need to trigger row edit\n if (this.#isGridMode) return false;\n\n const internalGrid = this.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 (row mode) or exit edit mode (grid mode)\n if (event.key === 'Escape') {\n // In grid mode: blur input to enable arrow key navigation\n if (this.#isGridMode && this.#gridModeInputFocused) {\n const activeEl = document.activeElement as HTMLElement;\n if (activeEl && this.gridElement.contains(activeEl)) {\n activeEl.blur();\n }\n this.#gridModeInputFocused = false;\n // Update focus styling\n this.requestAfterRender();\n return true;\n }\n\n // In row mode: cancel edit\n if (this.#activeEditRow !== -1 && !this.#isGridMode) {\n // Allow users to prevent edit close via callback (e.g., when overlay is open)\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(event);\n if (shouldClose === false) {\n return true; // Handled: block grid navigation, let event reach overlay\n }\n }\n this.#exitRowEdit(this.#activeEditRow, true);\n return true;\n }\n }\n\n // Arrow keys in grid mode when not editing input: navigate cells\n if (\n this.#isGridMode &&\n !this.#gridModeInputFocused &&\n (event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'ArrowLeft' || event.key === 'ArrowRight')\n ) {\n // Let the grid's default keyboard navigation handle this\n return false;\n }\n\n // Arrow Up/Down while editing: commit and exit edit mode, move to adjacent row (only in 'row' mode)\n if ((event.key === 'ArrowUp' || event.key === 'ArrowDown') && this.#activeEditRow !== -1 && !this.#isGridMode) {\n // Allow users to prevent row navigation via callback (e.g., when dropdown is open)\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(event);\n if (shouldClose === false) {\n return true; // Handled: block grid navigation, let event reach dropdown\n }\n }\n\n const maxRow = internalGrid._rows.length - 1;\n const currentRow = this.#activeEditRow;\n\n // Commit the current edit\n this.#exitRowEdit(currentRow, false);\n\n // Move focus to adjacent row (same column)\n if (event.key === 'ArrowDown') {\n internalGrid._focusRow = Math.min(maxRow, internalGrid._focusRow + 1);\n } else {\n internalGrid._focusRow = Math.max(0, internalGrid._focusRow - 1);\n }\n\n event.preventDefault();\n // Ensure the focused cell is scrolled into view\n ensureCellVisible(internalGrid);\n // Request render to update focus styling\n this.requestAfterRender();\n return true;\n }\n\n // Tab/Shift+Tab while editing: move to next/prev editable cell\n if (event.key === 'Tab' && (this.#activeEditRow !== -1 || this.#isGridMode)) {\n event.preventDefault();\n\n // In single-cell edit mode (F2), commit and close instead of navigating\n if (this.#singleCellEdit) {\n this.#exitRowEdit(this.#activeEditRow, false);\n return true;\n }\n\n const forward = !event.shiftKey;\n this.#handleTabNavigation(forward);\n return true;\n }\n\n // Space: toggle boolean cells (only when not in edit mode - let editors handle their own space)\n if (event.key === ' ' || event.key === 'Spacebar') {\n // If we're in row edit mode, let the event pass through to the editor (e.g., checkbox)\n if (this.#activeEditRow !== -1) {\n return false;\n }\n\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n if (focusRow >= 0 && focusCol >= 0) {\n const column = internalGrid._visibleColumns[focusCol];\n const rowData = internalGrid._rows[focusRow];\n if (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, commit, or enter edit mode in grid mode\n if (event.key === 'Enter' && !event.shiftKey) {\n // In grid mode when not editing: focus the current cell's input\n if (this.#isGridMode && !this.#gridModeInputFocused) {\n this.#focusCurrentCellEditor();\n return true;\n }\n\n if (this.#activeEditRow !== -1) {\n // Allow users to prevent edit close via callback (e.g., when overlay is open)\n // This lets Enter select an item in a dropdown instead of committing the row\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(event);\n if (shouldClose === false) {\n return true; // Handled: block grid navigation, let event reach overlay\n }\n }\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 // F2: begin single-cell edit on the focused cell\n if (event.key === 'F2') {\n if (this.#activeEditRow !== -1 || this.#isGridMode) return false;\n\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n if (editOn === false) return false;\n\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n if (focusRow >= 0 && focusCol >= 0) {\n const column = internalGrid._visibleColumns[focusCol];\n if (column?.editable && column.field) {\n event.preventDefault();\n this.beginCellEdit(focusRow, column.field);\n return true;\n }\n }\n return false;\n }\n\n // Don't block other keyboard events\n return false;\n }\n\n // #endregion\n\n // #region Render Hooks\n\n /**\n * Process columns to merge type-level editorParams with column-level.\n * Column-level params take precedence.\n * @internal\n */\n override processColumns(columns: ColumnConfig<T>[]): ColumnConfig<T>[] {\n const internalGrid = this.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 // Animate the row after render completes (so the row element exists)\n if (this.#pendingRowAnimation !== -1) {\n const rowIndex = this.#pendingRowAnimation;\n this.#pendingRowAnimation = -1;\n internalGrid.animateRow?.(rowIndex, 'change');\n }\n\n // In 'grid' mode, editors are injected via afterCellRender hook during render\n if (this.#isGridMode) return;\n\n if (this.#editingCells.size === 0) return;\n\n // Re-inject editors for any editing cells that are visible\n for (const cellKey of this.#editingCells) {\n const [rowStr, colStr] = cellKey.split(':');\n const rowIndex = parseInt(rowStr, 10);\n const colIndex = parseInt(colStr, 10);\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) continue;\n\n const cellEl = rowEl.querySelector(`.cell[data-col=\"${colIndex}\"]`) as HTMLElement | null;\n if (!cellEl || cellEl.classList.contains('editing')) continue;\n\n // Cell is visible but not in editing mode - reinject editor\n const rowData = internalGrid._rows[rowIndex];\n const column = internalGrid._visibleColumns[colIndex];\n if (rowData && column) {\n this.#injectEditor(rowData, rowIndex, column, colIndex, cellEl, true);\n }\n }\n }\n\n /**\n * Hook called after each cell is rendered.\n * In grid mode, injects editors into editable cells during render (no DOM queries needed).\n * @internal\n */\n override afterCellRender(context: AfterCellRenderContext): void {\n // Only inject editors in grid mode\n if (!this.#isGridMode) return;\n\n const { row, rowIndex, column, colIndex, cellElement } = context;\n\n // Skip non-editable columns\n if (!column.editable) return;\n\n // Skip if already has editor\n if (cellElement.classList.contains('editing')) return;\n\n // Inject editor (don't track in editingCells - we're always editing in grid mode)\n this.#injectEditor(row as T, rowIndex, column as ColumnConfig<T>, colIndex, cellElement, true);\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 // #region Cell Validation\n\n /**\n * Mark a cell as invalid with an optional validation message.\n * Invalid cells are marked with a `data-invalid` attribute for styling.\n *\n * @param rowId - The row ID (from getRowId)\n * @param field - The field name\n * @param message - Optional validation message (for tooltips or display)\n *\n * @example\n * ```typescript\n * // In cell-commit handler:\n * grid.addEventListener('cell-commit', (e) => {\n * if (e.detail.field === 'email' && !isValidEmail(e.detail.value)) {\n * e.detail.setInvalid('Invalid email format');\n * }\n * });\n *\n * // Or programmatically:\n * editingPlugin.setInvalid('row-123', 'email', 'Invalid email format');\n * ```\n */\n setInvalid(rowId: string, field: string, message = ''): void {\n let rowInvalids = this.#invalidCells.get(rowId);\n if (!rowInvalids) {\n rowInvalids = new Map();\n this.#invalidCells.set(rowId, rowInvalids);\n }\n rowInvalids.set(field, message);\n this.#syncInvalidCellAttribute(rowId, field, true);\n }\n\n /**\n * Clear the invalid state for a specific cell.\n *\n * @param rowId - The row ID (from getRowId)\n * @param field - The field name\n */\n clearInvalid(rowId: string, field: string): void {\n const rowInvalids = this.#invalidCells.get(rowId);\n if (rowInvalids) {\n rowInvalids.delete(field);\n if (rowInvalids.size === 0) {\n this.#invalidCells.delete(rowId);\n }\n }\n this.#syncInvalidCellAttribute(rowId, field, false);\n }\n\n /**\n * Clear all invalid cells for a specific row.\n *\n * @param rowId - The row ID (from getRowId)\n */\n clearRowInvalid(rowId: string): void {\n const rowInvalids = this.#invalidCells.get(rowId);\n if (rowInvalids) {\n const fields = Array.from(rowInvalids.keys());\n this.#invalidCells.delete(rowId);\n fields.forEach((field) => this.#syncInvalidCellAttribute(rowId, field, false));\n }\n }\n\n /**\n * Clear all invalid cell states across all rows.\n */\n clearAllInvalid(): void {\n const entries = Array.from(this.#invalidCells.entries());\n this.#invalidCells.clear();\n entries.forEach(([rowId, fields]) => {\n fields.forEach((_, field) => this.#syncInvalidCellAttribute(rowId, field, false));\n });\n }\n\n /**\n * Check if a specific cell is marked as invalid.\n *\n * @param rowId - The row ID (from getRowId)\n * @param field - The field name\n * @returns True if the cell is marked as invalid\n */\n isCellInvalid(rowId: string, field: string): boolean {\n return this.#invalidCells.get(rowId)?.has(field) ?? false;\n }\n\n /**\n * Get the validation message for an invalid cell.\n *\n * @param rowId - The row ID (from getRowId)\n * @param field - The field name\n * @returns The validation message, or undefined if cell is valid\n */\n getInvalidMessage(rowId: string, field: string): string | undefined {\n return this.#invalidCells.get(rowId)?.get(field);\n }\n\n /**\n * Check if a row has any invalid cells.\n *\n * @param rowId - The row ID (from getRowId)\n * @returns True if the row has at least one invalid cell\n */\n hasInvalidCells(rowId: string): boolean {\n const rowInvalids = this.#invalidCells.get(rowId);\n return rowInvalids ? rowInvalids.size > 0 : false;\n }\n\n /**\n * Get all invalid fields for a row.\n *\n * @param rowId - The row ID (from getRowId)\n * @returns Map of field names to validation messages\n */\n getInvalidFields(rowId: string): Map<string, string> {\n return new Map(this.#invalidCells.get(rowId) ?? []);\n }\n\n /**\n * Sync the data-invalid attribute on a cell element.\n */\n #syncInvalidCellAttribute(rowId: string, field: string, invalid: boolean): 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 || colIndex === undefined) return;\n\n // Find the row element by rowId\n const rows = internalGrid._rows;\n const rowIndex = rows?.findIndex((r) => {\n try {\n return internalGrid.getRowId?.(r) === rowId;\n } catch {\n return false;\n }\n });\n if (rowIndex === -1 || rowIndex === undefined) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${colIndex}\"]`) as HTMLElement | null;\n if (!cellEl) return;\n\n if (invalid) {\n cellEl.setAttribute('data-invalid', 'true');\n const message = this.#invalidCells.get(rowId)?.get(field);\n if (message) {\n cellEl.setAttribute('title', message);\n }\n } else {\n cellEl.removeAttribute('data-invalid');\n cellEl.removeAttribute('title');\n }\n }\n\n // #endregion\n\n /**\n * Reset all change tracking.\n * @param silent - If true, suppresses the `changed-rows-reset` event\n * @fires changed-rows-reset - Emitted when tracking is reset (unless silent)\n */\n resetChangedRows(silent?: boolean): void {\n const rows = this.changedRows;\n const ids = this.changedRowIds;\n this.#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.#singleCellEdit = true;\n this.#beginCellEdit(rowIndex, colIndex, cellEl);\n }\n\n /**\n * Programmatically begin editing all editable cells in a row.\n * @param rowIndex - Index of the row to edit\n * @fires cell-commit - Emitted for each cell value that is committed\n * @fires row-commit - Emitted when focus leaves the row\n */\n beginBulkEdit(rowIndex: number): void {\n const internalGrid = this.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 // Bulk edit clears single-cell mode\n this.#singleCellEdit = false;\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 * Focus the editor input in the currently focused cell (grid mode only).\n * Used when pressing Enter to enter edit mode from navigation mode.\n */\n #focusCurrentCellEditor(): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n\n if (focusRow < 0 || focusCol < 0) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(focusRow);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${focusCol}\"]`) as HTMLElement | null;\n\n if (cellEl?.classList.contains('editing')) {\n const editor = cellEl.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n if (editor) {\n this.#gridModeEditLocked = false; // Unlock edit mode - user pressed Enter\n editor.focus();\n this.#gridModeInputFocused = true;\n // Select all text in text inputs for quick replacement\n if (editor instanceof HTMLInputElement && (editor.type === 'text' || editor.type === 'number')) {\n editor.select();\n }\n }\n }\n }\n\n /**\n * Handle Tab/Shift+Tab navigation while editing.\n * Moves to next/previous editable cell, staying in edit mode.\n * Wraps to next/previous row when reaching row boundaries.\n */\n #handleTabNavigation(forward: boolean): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const rows = internalGrid._rows;\n // In grid mode, use focusRow since there's no active edit row\n const currentRow = this.#isGridMode ? internalGrid._focusRow : this.#activeEditRow;\n\n // Get editable column indices\n const editableCols = internalGrid._visibleColumns.map((c, i) => (c.editable ? i : -1)).filter((i) => i >= 0);\n if (editableCols.length === 0) return;\n\n const currentIdx = editableCols.indexOf(internalGrid._focusCol);\n const nextIdx = currentIdx + (forward ? 1 : -1);\n\n // Can move within same row?\n if (nextIdx >= 0 && nextIdx < editableCols.length) {\n internalGrid._focusCol = editableCols[nextIdx];\n const rowEl = internalGrid.findRenderedRowElement?.(currentRow);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${editableCols[nextIdx]}\"]`) as HTMLElement | null;\n if (cellEl?.classList.contains('editing')) {\n const editor = cellEl.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n editor?.focus({ preventScroll: true });\n }\n ensureCellVisible(internalGrid, { forceHorizontalScroll: true });\n return;\n }\n\n // Can move to adjacent row?\n const nextRow = currentRow + (forward ? 1 : -1);\n if (nextRow >= 0 && nextRow < rows.length) {\n // In grid mode, just move focus (all rows are always editable)\n if (this.#isGridMode) {\n internalGrid._focusRow = nextRow;\n internalGrid._focusCol = forward ? editableCols[0] : editableCols[editableCols.length - 1];\n ensureCellVisible(internalGrid, { forceHorizontalScroll: true });\n // Focus the editor in the new cell after render\n this.requestAfterRender();\n setTimeout(() => {\n const rowEl = internalGrid.findRenderedRowElement?.(nextRow);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${internalGrid._focusCol}\"]`) as HTMLElement | null;\n if (cellEl?.classList.contains('editing')) {\n const editor = cellEl.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n editor?.focus({ preventScroll: true });\n }\n }, 0);\n } else {\n // In row mode, commit current row and enter next row\n this.#exitRowEdit(currentRow, false);\n internalGrid._focusRow = nextRow;\n internalGrid._focusCol = forward ? editableCols[0] : editableCols[editableCols.length - 1];\n this.beginBulkEdit(nextRow);\n ensureCellVisible(internalGrid, { forceHorizontalScroll: true });\n }\n }\n // else: at boundary - stay put\n }\n\n /**\n * Sync the internal grid state with the plugin's editing state.\n */\n #syncGridEditState(): void {\n const internalGrid = this.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 // Emit edit-open event (row mode only)\n if (!this.#isGridMode) {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n let rowId = '';\n try {\n rowId = internalGrid.getRowId?.(rowData) ?? '';\n } catch {\n // Row has no ID\n }\n this.emit<EditOpenDetail<T>>('edit-open', {\n rowIndex,\n rowId,\n row: rowData,\n });\n }\n }\n }\n\n /**\n * Exit editing for a row.\n */\n #exitRowEdit(rowIndex: number, revert: boolean): void {\n if (this.#activeEditRow !== rowIndex) return;\n\n const internalGrid = this.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\n // Skip cells with externally-managed editors (framework adapters like Angular/React/Vue).\n // These editors handle their own commits via the commit() callback - we should NOT\n // try to read values from their DOM inputs (which may contain formatted display values).\n if ((cell as HTMLElement).hasAttribute('data-editor-managed')) {\n return;\n }\n\n const input = cell.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (input) {\n const field = col.field as keyof T;\n const originalValue = current[field];\n const val = getInputValue(input, col, originalValue);\n if (originalValue !== val) {\n this.#commitCellValue(rowIndex, col, val, current);\n }\n }\n });\n }\n\n // 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 this.clearRowInvalid(rowId);\n }\n } else if (!revert && current) {\n // Compare snapshot vs current to detect if changes were made during THIS edit session\n const changedThisSession = this.#hasRowChanged(snapshot, current);\n\n // Check if this row has any cumulative changes (via ID tracking)\n // Fall back to session-based detection when no row ID is available\n const changed = rowId ? this.#changedRowIds.has(rowId) : changedThisSession;\n\n // Emit cancelable row-commit event\n const cancelled = this.emitCancelable<RowCommitDetail<T>>('row-commit', {\n rowIndex,\n rowId: rowId ?? '',\n row: current,\n oldValue: snapshot,\n newValue: current,\n changed,\n changedRows: this.changedRows,\n changedRowIds: this.changedRowIds,\n });\n\n // If consumer called preventDefault(), revert the row\n if (cancelled && snapshot) {\n Object.keys(snapshot as object).forEach((k) => {\n (current as Record<string, unknown>)[k] = (snapshot as Record<string, unknown>)[k];\n });\n if (rowId) {\n this.#changedRowIds.delete(rowId);\n this.clearRowInvalid(rowId);\n }\n } else if (!cancelled && changedThisSession && this.isAnimationEnabled) {\n // Animate the row only if changes were made during this edit session\n // (deferred to afterRender so the row element exists after re-render)\n this.#pendingRowAnimation = rowIndex;\n }\n }\n\n // Clear editing state\n this.#rowEditSnapshots.delete(rowIndex);\n this.#activeEditRow = -1;\n this.#activeEditCol = -1;\n this.#singleCellEdit = false;\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 // Remove value-change callbacks for this row\n for (const callbackKey of this.#editorValueCallbacks.keys()) {\n if (callbackKey.startsWith(`${rowIndex}:`)) {\n this.#editorValueCallbacks.delete(callbackKey);\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 // Emit edit-close event (row mode only, fires for both commit and revert)\n if (!this.#isGridMode && current) {\n this.emit<EditCloseDetail<T>>('edit-close', {\n rowIndex,\n rowId: rowId ?? '',\n row: current,\n reverted: revert,\n });\n }\n }\n\n /**\n * Commit a single cell value change.\n * Uses ID-based change tracking for stability when rows are reordered.\n */\n #commitCellValue(rowIndex: number, column: ColumnConfig<T>, newValue: unknown, rowData: T): void {\n const field = column.field;\n if (!isSafePropertyKey(field)) return;\n const oldValue = (rowData as Record<string, unknown>)[field];\n if (oldValue === newValue) return;\n\n const internalGrid = this.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 // Track whether setInvalid was called during event handling\n let invalidWasSet = false;\n\n // Create setInvalid callback for validation (noop if row has no ID)\n const setInvalid = rowId\n ? (message?: string) => {\n invalidWasSet = true;\n this.setInvalid(rowId!, field, message ?? '');\n }\n : () => {}; // eslint-disable-line @typescript-eslint/no-empty-function\n\n // Emit cancelable event BEFORE applying the value\n const cancelled = this.emitCancelable<CellCommitDetail<T>>('cell-commit', {\n row: rowData,\n rowId: rowId ?? '',\n field,\n oldValue,\n value: newValue,\n rowIndex,\n changedRows: this.changedRows,\n changedRowIds: this.changedRowIds,\n firstTimeForRow: firstTime,\n updateRow,\n setInvalid,\n });\n\n // If consumer called preventDefault(), abort the commit\n if (cancelled) return;\n\n // Clear any previous invalid state for this cell ONLY if setInvalid wasn't called\n // (if setInvalid was called, the handler wants it to remain invalid)\n if (rowId && !invalidWasSet && this.isCellInvalid(rowId, field)) {\n this.clearInvalid(rowId, field);\n }\n\n // Apply the value and mark row as changed\n (rowData as Record<string, unknown>)[field] = newValue;\n if (rowId) {\n this.#changedRowIds.add(rowId);\n }\n this.#syncGridEditState();\n\n // Notify other plugins (e.g., UndoRedoPlugin) about the committed edit\n this.emitPluginEvent('cell-edit-committed', {\n rowIndex,\n field,\n oldValue,\n newValue,\n });\n\n // Mark the row visually as changed (animation happens when row edit closes)\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (rowEl) {\n rowEl.classList.add('changed');\n }\n }\n\n /**\n * Inject an editor into a cell.\n */\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 // In grid mode, always allow commits (we're always editing)\n // In row mode, only allow commits if we're in an active edit session\n if (editFinalized || (!this.#isGridMode && 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 // In grid mode, Enter just commits without exiting\n if (this.#isGridMode) {\n e.stopPropagation();\n e.preventDefault();\n // Get current value and commit\n const input = editorHost.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (input) {\n commit(getInputValue(input, column as ColumnConfig<unknown>, originalValue));\n }\n return;\n }\n // Allow users to prevent edit close via callback (e.g., when overlay is open)\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(e);\n if (shouldClose === false) {\n return; // Let the event propagate to overlay\n }\n }\n e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n this.#exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n // In grid mode, Escape doesn't exit edit mode\n if (this.#isGridMode) {\n e.stopPropagation();\n e.preventDefault();\n return;\n }\n // Allow users to prevent edit close via callback (e.g., when overlay is open)\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(e);\n if (shouldClose === false) {\n return; // Let the event propagate to overlay\n }\n }\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 // Value-change callback registration.\n // Editors call onValueChange(cb) to receive pushes when the underlying row\n // is mutated externally (e.g., via updateRow from another cell's commit).\n // Multiple callbacks can be registered (user + auto-wire).\n const callbackKey = `${rowIndex}:${column.field}`;\n const callbacks: Array<(newValue: unknown) => void> = [];\n this.#editorValueCallbacks.set(callbackKey, (newVal) => {\n for (const cb of callbacks) cb(newVal);\n });\n const onValueChange = (cb: (newValue: unknown) => void) => {\n callbacks.push(cb);\n };\n\n if (editorSpec === 'template' && tplHolder) {\n this.#renderTemplateEditor(editorHost, colInternal, rowData, originalValue, commit, cancel, skipFocus, rowIndex);\n // Auto-update built-in template editors when value changes externally\n onValueChange((newVal) => {\n const input = editorHost.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(\n 'input,textarea,select',\n );\n if (input) {\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.checked = !!newVal;\n } else {\n input.value = String(newVal ?? '');\n }\n }\n });\n } else if (typeof editorSpec === 'string') {\n const el = document.createElement(editorSpec) as HTMLElement & { value?: unknown };\n el.value = value;\n el.addEventListener('change', () => commit(el.value));\n // Auto-update custom element editors when value changes externally\n onValueChange((newVal) => {\n el.value = newVal;\n });\n editorHost.appendChild(el);\n if (!skipFocus) {\n queueMicrotask(() => {\n const focusable = editorHost.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n focusable?.focus({ preventScroll: true });\n });\n }\n } else if (typeof editorSpec === 'function') {\n const ctx: EditorContext<T> = {\n row: rowData,\n rowId: rowId ?? '',\n value,\n field: column.field,\n column,\n commit,\n cancel,\n updateRow,\n onValueChange,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const produced = (editorSpec as any)(ctx);\n if (typeof produced === 'string') {\n editorHost.innerHTML = produced;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n wireEditorInputs(editorHost, column as any, commit, originalValue);\n // Auto-update wired inputs when value changes externally\n onValueChange((newVal) => {\n const input = editorHost.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(\n 'input,textarea,select',\n );\n if (input) {\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.checked = !!newVal;\n } else {\n input.value = String(newVal ?? '');\n }\n }\n });\n } else if (produced instanceof Node) {\n editorHost.appendChild(produced);\n const isSimpleInput =\n produced instanceof HTMLInputElement ||\n produced instanceof HTMLSelectElement ||\n produced instanceof HTMLTextAreaElement;\n if (!isSimpleInput) {\n cell.setAttribute('data-editor-managed', '');\n } else {\n // Auto-update simple inputs returned by factory functions\n onValueChange((newVal) => {\n if (produced instanceof HTMLInputElement && produced.type === 'checkbox') {\n produced.checked = !!newVal;\n } else {\n (produced as HTMLInputElement).value = String(newVal ?? '');\n }\n });\n }\n }\n if (!skipFocus) {\n queueMicrotask(() => {\n const focusable = editorHost.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n focusable?.focus({ preventScroll: true });\n });\n }\n } else if (editorSpec && typeof editorSpec === 'object') {\n const placeholder = document.createElement('div');\n placeholder.setAttribute('data-external-editor', '');\n placeholder.setAttribute('data-field', column.field);\n editorHost.appendChild(placeholder);\n cell.setAttribute('data-editor-managed', '');\n const context: EditorContext<T> = {\n row: rowData,\n rowId: rowId ?? '',\n value,\n field: column.field,\n column,\n commit,\n cancel,\n updateRow,\n onValueChange,\n };\n if (editorSpec.mount) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n editorSpec.mount({ placeholder, context: context as any, spec: editorSpec });\n } catch (e) {\n 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, originalValue));\n });\n input.addEventListener('keydown', (evt) => {\n const e = evt as KeyboardEvent;\n if (e.key === 'Enter') {\n // Allow users to prevent edit close via callback (e.g., when overlay is open)\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(e);\n if (shouldClose === false) {\n return; // Let the event propagate to overlay\n }\n }\n e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n commit(getInputValue(input, column, originalValue));\n this.#exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n // Allow users to prevent edit close via callback (e.g., when overlay is open)\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(e);\n if (shouldClose === false) {\n return; // Let the event propagate to overlay\n }\n }\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 * Compare snapshot vs current row to detect if any values changed during this edit session.\n * Uses shallow comparison of all properties.\n */\n #hasRowChanged(snapshot: T | undefined, current: T): boolean {\n if (!snapshot) return false;\n\n const snapshotObj = snapshot as Record<string, unknown>;\n const currentObj = current as Record<string, unknown>;\n\n // Check all keys in both objects\n const allKeys = new Set([...Object.keys(snapshotObj), ...Object.keys(currentObj)]);\n for (const key of allKeys) {\n if (snapshotObj[key] !== currentObj[key]) {\n return true;\n }\n }\n return false;\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","/**\n * CSV Export Utilities\n *\n * Functions for building and downloading CSV content.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ExportParams } from './types';\n\n/** CSV export options */\nexport interface CsvOptions {\n /** Field delimiter (default: ',') */\n delimiter?: string;\n /** Line separator (default: '\\n') */\n newline?: string;\n /** Whether to quote strings containing special characters (default: true) */\n quoteStrings?: boolean;\n /** Add UTF-8 BOM for Excel compatibility (default: false) */\n bom?: boolean;\n}\n\n/**\n * Format a value for CSV output.\n * Handles null, Date, objects, and strings with special characters.\n */\nexport function formatCsvValue(value: any, quote = true): string {\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'object') return JSON.stringify(value);\n\n const str = String(value);\n\n // Quote if contains special characters (comma, quote, newline)\n if (quote && (str.includes(',') || str.includes('\"') || str.includes('\\n') || str.includes('\\r'))) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n\n return str;\n}\n\n/**\n * Build CSV content from rows and columns.\n */\nexport function buildCsv(rows: any[], columns: ColumnConfig[], params: ExportParams, options: CsvOptions = {}): string {\n const delimiter = options.delimiter ?? ',';\n const newline = options.newline ?? '\\n';\n const lines: string[] = [];\n\n // UTF-8 BOM for Excel compatibility\n const bom = options.bom ? '\\uFEFF' : '';\n\n // Build header row\n if (params.includeHeaders !== false) {\n const headerRow = columns.map((col) => {\n const header = col.header || col.field;\n const processed = params.processHeader ? params.processHeader(header, col.field) : header;\n return formatCsvValue(processed);\n });\n lines.push(headerRow.join(delimiter));\n }\n\n // Build data rows\n for (const row of rows) {\n const cells = columns.map((col) => {\n let value = row[col.field];\n if (params.processCell) {\n value = params.processCell(value, col.field, row);\n }\n return formatCsvValue(value);\n });\n lines.push(cells.join(delimiter));\n }\n\n return bom + lines.join(newline);\n}\n\n/**\n * Download a Blob as a file.\n */\nexport function downloadBlob(blob: Blob, fileName: string): void {\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = fileName;\n link.style.display = 'none';\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n}\n\n/**\n * Download CSV content as a file.\n */\nexport function downloadCsv(content: string, fileName: string): void {\n const blob = new Blob([content], { type: 'text/csv;charset=utf-8;' });\n downloadBlob(blob, fileName);\n}\n","/**\n * Excel Export Utilities\n *\n * Simple Excel XML format export (no external dependencies).\n * Produces XML Spreadsheet 2003 format which opens in Excel.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ExportParams } from './types';\nimport { downloadBlob } from './csv';\n\n/**\n * Escape XML special characters.\n */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Build Excel XML content from rows and columns.\n * Uses XML Spreadsheet 2003 format for broad compatibility.\n */\nexport function buildExcelXml(rows: any[], columns: ColumnConfig[], params: ExportParams): string {\n let xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<?mso-application progid=\"Excel.Sheet\"?>\n<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"\n xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\">\n<Worksheet ss:Name=\"Sheet1\">\n<Table>`;\n\n // Build header row\n if (params.includeHeaders !== false) {\n xml += '\\n<Row>';\n for (const col of columns) {\n const header = col.header || col.field;\n const processed = params.processHeader ? params.processHeader(header, col.field) : header;\n xml += `<Cell><Data ss:Type=\"String\">${escapeXml(processed)}</Data></Cell>`;\n }\n xml += '</Row>';\n }\n\n // Build data rows\n for (const row of rows) {\n xml += '\\n<Row>';\n for (const col of columns) {\n let value = row[col.field];\n if (params.processCell) {\n value = params.processCell(value, col.field, row);\n }\n\n // Determine cell type based on value\n let type: 'Number' | 'String' | 'DateTime' = 'String';\n let displayValue = '';\n\n if (value == null) {\n displayValue = '';\n } else if (typeof value === 'number' && !isNaN(value)) {\n type = 'Number';\n displayValue = String(value);\n } else if (value instanceof Date) {\n type = 'DateTime';\n displayValue = value.toISOString();\n } else {\n displayValue = escapeXml(String(value));\n }\n\n xml += `<Cell><Data ss:Type=\"${type}\">${displayValue}</Data></Cell>`;\n }\n xml += '</Row>';\n }\n\n xml += '\\n</Table>\\n</Worksheet>\\n</Workbook>';\n return xml;\n}\n\n/**\n * Download Excel XML content as a file.\n */\nexport function downloadExcel(content: string, fileName: string): void {\n const finalName = fileName.endsWith('.xls') ? fileName : `${fileName}.xls`;\n const blob = new Blob([content], {\n type: 'application/vnd.ms-excel;charset=utf-8;',\n });\n downloadBlob(blob, finalName);\n}\n","/**\n * Export Plugin (Class-based)\n *\n * Provides data export functionality for tbw-grid.\n * Supports CSV, Excel (XML), and JSON formats.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport { resolveColumns, resolveRows } from '../shared/data-collection';\nimport { buildCsv, downloadBlob, downloadCsv } from './csv';\nimport { buildExcelXml, downloadExcel } from './excel';\nimport type { ExportCompleteDetail, ExportConfig, ExportFormat, ExportParams } from './types';\n\n/** Selection plugin state interface for type safety */\ninterface SelectionPluginState {\n selected: Set<number>;\n}\n\n/**\n * Export Plugin for tbw-grid\n *\n * Lets users download grid data as CSV, Excel (XML), or JSON with a single click\n * or API call. Great for reporting, data backup, or letting users work with data\n * in Excel. Integrates with SelectionPlugin to export only selected rows.\n *\n * ## Installation\n *\n * ```ts\n * import { ExportPlugin } from '@toolbox-web/grid/plugins/export';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `fileName` | `string` | `'export'` | Base filename (without extension) |\n * | `includeHeaders` | `boolean` | `true` | Include column headers in export |\n * | `onlyVisible` | `boolean` | `true` | Export only visible columns |\n * | `onlySelected` | `boolean` | `false` | Export only selected rows (requires SelectionPlugin) |\n *\n * ## Supported Formats\n *\n * | Format | Method | Description |\n * |--------|--------|-------------|\n * | CSV | `exportToCSV()` | Comma-separated values |\n * | Excel | `exportToExcel()` | Excel XML format (.xlsx) |\n * | JSON | `exportToJSON()` | JSON array of objects |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `exportToCSV` | `(params?) => void` | Export as CSV file |\n * | `exportToExcel` | `(params?) => void` | Export as Excel file |\n * | `exportToJSON` | `(params?) => void` | Export as JSON file |\n * | `isExporting` | `() => boolean` | Check if export is in progress |\n *\n * @example Basic Export with Button\n * ```ts\n * import '@toolbox-web/grid';\n * import { ExportPlugin } from '@toolbox-web/grid/plugins/export';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * ],\n * plugins: [new ExportPlugin({ fileName: 'employees', includeHeaders: true })],\n * };\n *\n * // Trigger export via button\n * document.getElementById('export-btn').addEventListener('click', () => {\n * grid.getPlugin(ExportPlugin).exportToCSV();\n * });\n * ```\n *\n * @example Export Selected Rows Only\n * ```ts\n * import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';\n *\n * grid.gridConfig = {\n * plugins: [\n * new SelectionPlugin({ mode: 'row' }),\n * new ExportPlugin({ onlySelected: true }),\n * ],\n * };\n * ```\n *\n * @see {@link ExportConfig} for all configuration options\n * @see {@link ExportParams} for method parameters\n * @see {@link SelectionPlugin} for exporting selected rows\n *\n * @internal Extends BaseGridPlugin\n */\nexport class ExportPlugin extends BaseGridPlugin<ExportConfig> {\n /** @internal */\n readonly name = 'export';\n\n /** @internal */\n protected override get defaultConfig(): Partial<ExportConfig> {\n return {\n fileName: 'export',\n includeHeaders: true,\n onlyVisible: true,\n onlySelected: false,\n };\n }\n\n // #region Internal State\n private isExportingFlag = false;\n private lastExportInfo: { format: ExportFormat; timestamp: Date } | null = null;\n // #endregion\n\n // #region Private Methods\n\n private performExport(format: ExportFormat, params?: Partial<ExportParams>): void {\n const config = this.config;\n\n // Build full params with defaults\n const fullParams: ExportParams = {\n format,\n fileName: params?.fileName ?? config.fileName ?? 'export',\n includeHeaders: params?.includeHeaders ?? config.includeHeaders,\n processCell: params?.processCell,\n processHeader: params?.processHeader,\n columns: params?.columns,\n rowIndices: params?.rowIndices,\n };\n\n // Get columns to export (shared utility handles hidden/utility filtering)\n const columns = resolveColumns(this.columns, params?.columns, config.onlyVisible) as ColumnConfig[];\n\n // Get rows to export\n let rows: Record<string, unknown>[];\n if (params?.rowIndices) {\n rows = resolveRows(this.rows as Record<string, unknown>[], params.rowIndices);\n } else if (config.onlySelected) {\n const selectionState = this.getSelectionState();\n if (selectionState?.selected?.size) {\n rows = resolveRows(this.rows as Record<string, unknown>[], [...selectionState.selected]);\n } else {\n rows = [...this.rows] as Record<string, unknown>[];\n }\n } else {\n rows = [...this.rows] as Record<string, unknown>[];\n }\n\n this.isExportingFlag = true;\n let fileName = fullParams.fileName!;\n\n try {\n switch (format) {\n case 'csv': {\n const content = buildCsv(rows, columns, fullParams, { bom: true });\n fileName = fileName.endsWith('.csv') ? fileName : `${fileName}.csv`;\n downloadCsv(content, fileName);\n break;\n }\n\n case 'excel': {\n const content = buildExcelXml(rows, columns, fullParams);\n fileName = fileName.endsWith('.xls') ? fileName : `${fileName}.xls`;\n downloadExcel(content, fileName);\n break;\n }\n\n case 'json': {\n const jsonData = rows.map((row) => {\n const obj: Record<string, any> = {};\n for (const col of columns) {\n let value = row[col.field];\n if (fullParams.processCell) {\n value = fullParams.processCell(value, col.field, row);\n }\n obj[col.field] = value;\n }\n return obj;\n });\n const content = JSON.stringify(jsonData, null, 2);\n fileName = fileName.endsWith('.json') ? fileName : `${fileName}.json`;\n const blob = new Blob([content], { type: 'application/json' });\n downloadBlob(blob, fileName);\n break;\n }\n }\n\n this.lastExportInfo = { format, timestamp: new Date() };\n\n this.emit<ExportCompleteDetail>('export-complete', {\n format,\n fileName,\n rowCount: rows.length,\n columnCount: columns.length,\n });\n } finally {\n this.isExportingFlag = false;\n }\n }\n\n private getSelectionState(): SelectionPluginState | null {\n try {\n return (this.grid?.getPluginState?.('selection') as SelectionPluginState | null) ?? null;\n } catch {\n return null;\n }\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Export data to CSV format.\n * @param params - Optional export parameters\n */\n exportCsv(params?: Partial<ExportParams>): void {\n this.performExport('csv', params);\n }\n\n /**\n * Export data to Excel format (XML Spreadsheet).\n * @param params - Optional export parameters\n */\n exportExcel(params?: Partial<ExportParams>): void {\n this.performExport('excel', params);\n }\n\n /**\n * Export data to JSON format.\n * @param params - Optional export parameters\n */\n exportJson(params?: Partial<ExportParams>): void {\n this.performExport('json', params);\n }\n\n /**\n * Check if an export is currently in progress.\n * @returns Whether export is in progress\n */\n isExporting(): boolean {\n return this.isExportingFlag;\n }\n\n /**\n * Get information about the last export.\n * @returns Export info or null if no export has occurred\n */\n getLastExport(): { format: ExportFormat; timestamp: Date } | null {\n return this.lastExportInfo;\n }\n // #endregion\n}\n","/**\n * Shared Expander Column Utilities\n *\n * Provides a fixed expander column for plugins that need expand/collapse icons\n * (MasterDetail, Tree, RowGrouping). The column is:\n * - Always first in the grid\n * - Cannot be reordered (lockPosition: true)\n * - Has no header (empty string)\n * - Has no right border (borderless styling)\n * - Narrow width (just fits the icon)\n */\n\nimport type { ColumnConfig } from '../types';\n\n/** Special field name for the expander column */\nexport const EXPANDER_COLUMN_FIELD = '__tbw_expander';\n\n/** Default width for the expander column (pixels) */\nexport const EXPANDER_COLUMN_WIDTH = 32;\n\n/**\n * Marker interface for expander column renderers.\n * Used to detect if expander column is already present.\n */\nexport interface ExpanderColumnRenderer {\n (ctx: any): HTMLElement;\n __expanderColumn?: true;\n /** Plugin name that created this expander */\n __expanderPlugin?: string;\n}\n\n/**\n * Check if a column is an expander column.\n */\nexport function isExpanderColumn(column: ColumnConfig): boolean {\n return column.field === EXPANDER_COLUMN_FIELD;\n}\n\n/**\n * Check if a column is a utility column (excluded from selection, clipboard, etc.).\n * Utility columns are non-data columns like expander columns.\n */\nexport function isUtilityColumn(column: ColumnConfig): boolean {\n return column.meta?.utility === true;\n}\n\n/**\n * Find an existing expander column in the column array.\n */\nexport function findExpanderColumn(columns: readonly ColumnConfig[]): ColumnConfig | undefined {\n return columns.find(isExpanderColumn);\n}\n\n/**\n * Create the base expander column config.\n * Plugins should add their own renderer to customize the expand icon behavior.\n *\n * @param pluginName - Name of the plugin creating the expander (for debugging)\n * @returns Base column config for the expander column\n */\nexport function createExpanderColumnConfig(pluginName: string): ColumnConfig {\n return {\n field: EXPANDER_COLUMN_FIELD as any,\n header: '', // No header text - visually merges with next column\n width: EXPANDER_COLUMN_WIDTH,\n resizable: false,\n sortable: false,\n filterable: false, // No filter button for expander column\n meta: {\n lockPosition: true,\n suppressMovable: true,\n expanderColumn: true,\n expanderPlugin: pluginName,\n utility: true, // Marks this as a utility column (excluded from selection, clipboard, etc.)\n },\n };\n}\n\n/**\n * Create a container element for expand/collapse toggle icons.\n * Used by plugins to wrap their expand icons with consistent styling.\n *\n * @param expanded - Whether the item is currently expanded\n * @param pluginClass - CSS class prefix for the plugin (e.g., 'master-detail', 'tree')\n * @returns Container span element\n */\nexport function createExpanderContainer(expanded: boolean, pluginClass: string): HTMLSpanElement {\n const container = document.createElement('span');\n container.className = `${pluginClass}-expander expander-cell`;\n if (expanded) {\n container.classList.add('expanded');\n }\n return container;\n}\n\n/**\n * CSS styles for the expander column.\n * Plugins should include this in their styles to ensure consistent appearance.\n */\nexport const EXPANDER_COLUMN_STYLES = `\n/* Expander column data cells - always first, borderless right edge */\n.cell[data-field=\"${EXPANDER_COLUMN_FIELD}\"] {\n border-right: none !important;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n/* Expander column header - completely hidden, no content, no border, no width contribution */\n.header-row .cell[data-field=\"${EXPANDER_COLUMN_FIELD}\"] {\n visibility: hidden;\n border: none !important;\n padding: 0;\n overflow: hidden;\n}\n\n/* The column after the expander should visually extend into the expander header space */\n.header-row .cell[data-field=\"${EXPANDER_COLUMN_FIELD}\"] + .cell {\n /* Pull left to cover the hidden expander header */\n margin-left: -${EXPANDER_COLUMN_WIDTH}px;\n padding-left: calc(var(--tbw-cell-padding, 8px) + ${EXPANDER_COLUMN_WIDTH}px);\n}\n\n/* Expander cell contents */\n.expander-cell {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n cursor: pointer;\n}\n`;\n","/**\n * Filter Model Core Logic\n *\n * Pure functions for filtering operations.\n */\n\nimport type { FilterModel } from './types';\n\n/**\n * Convert a value to a comparable number.\n * Handles Date objects, numeric values, and date/ISO strings.\n */\nfunction toNumeric(value: unknown): number {\n if (value instanceof Date) return value.getTime();\n const n = Number(value);\n if (!isNaN(n)) return n;\n // Try parsing as a date string (ISO 8601, etc.)\n const d = new Date(value as string);\n return d.getTime(); // NaN if unparseable\n}\n\n/**\n * Check if a single row matches a filter condition.\n *\n * @param row - The row data object\n * @param filter - The filter to apply\n * @param caseSensitive - Whether text comparisons are case sensitive\n * @returns True if the row matches the filter\n */\nexport function matchesFilter(row: Record<string, unknown>, filter: FilterModel, caseSensitive = false): boolean {\n const rawValue = row[filter.field];\n\n // Handle blank/notBlank first - these work on null/undefined/empty\n if (filter.operator === 'blank') {\n return rawValue == null || rawValue === '';\n }\n if (filter.operator === 'notBlank') {\n return rawValue != null && rawValue !== '';\n }\n\n // Null/undefined values don't match other filters\n if (rawValue == null) return false;\n\n // Prepare values for comparison\n const stringValue = String(rawValue);\n const compareValue = caseSensitive ? stringValue : stringValue.toLowerCase();\n const filterValue = caseSensitive ? String(filter.value) : String(filter.value).toLowerCase();\n\n switch (filter.operator) {\n // Text operators\n case 'contains':\n return compareValue.includes(filterValue);\n\n case 'notContains':\n return !compareValue.includes(filterValue);\n\n case 'equals':\n return compareValue === filterValue;\n\n case 'notEquals':\n return compareValue !== filterValue;\n\n case 'startsWith':\n return compareValue.startsWith(filterValue);\n\n case 'endsWith':\n return compareValue.endsWith(filterValue);\n\n // Number/Date operators (use toNumeric for Date objects and date strings)\n case 'lessThan':\n return toNumeric(rawValue) < toNumeric(filter.value);\n\n case 'lessThanOrEqual':\n return toNumeric(rawValue) <= toNumeric(filter.value);\n\n case 'greaterThan':\n return toNumeric(rawValue) > toNumeric(filter.value);\n\n case 'greaterThanOrEqual':\n return toNumeric(rawValue) >= toNumeric(filter.value);\n\n case 'between':\n return toNumeric(rawValue) >= toNumeric(filter.value) && toNumeric(rawValue) <= toNumeric(filter.valueTo);\n\n // Set operators\n case 'in':\n return Array.isArray(filter.value) && filter.value.includes(rawValue);\n\n case 'notIn':\n return Array.isArray(filter.value) && !filter.value.includes(rawValue);\n\n default:\n return true;\n }\n}\n\n/**\n * Filter rows based on multiple filter conditions (AND logic).\n * All filters must match for a row to be included.\n *\n * @param rows - The rows to filter\n * @param filters - Array of filters to apply\n * @param caseSensitive - Whether text comparisons are case sensitive\n * @returns Filtered rows\n */\nexport function filterRows<T extends Record<string, unknown>>(\n rows: T[],\n filters: FilterModel[],\n caseSensitive = false,\n): T[] {\n if (!filters.length) return rows;\n return rows.filter((row) => filters.every((f) => matchesFilter(row, f, caseSensitive)));\n}\n\n/**\n * Compute a cache key for a set of filters.\n * Used for memoization of filter results.\n *\n * @param filters - Array of filters\n * @returns Stable string key for the filter set\n */\nexport function computeFilterCacheKey(filters: FilterModel[]): string {\n return JSON.stringify(\n filters.map((f) => ({\n field: f.field,\n operator: f.operator,\n value: f.value,\n valueTo: f.valueTo,\n })),\n );\n}\n\n/**\n * Extract unique values from a field across all rows.\n * Useful for populating \"set\" filter dropdowns.\n *\n * @param rows - The rows to extract values from\n * @param field - The field name\n * @returns Sorted array of unique non-null values\n */\nexport function getUniqueValues<T extends Record<string, unknown>>(rows: T[], field: string): unknown[] {\n const values = new Set<unknown>();\n for (const row of rows) {\n const value = row[field];\n if (value != null) {\n values.add(value);\n }\n }\n return [...values].sort((a, b) => {\n // Handle mixed types gracefully\n if (typeof a === 'number' && typeof b === 'number') {\n return a - b;\n }\n return String(a).localeCompare(String(b));\n });\n}\n","/**\n * Filtering Plugin (Class-based)\n *\n * Provides comprehensive filtering functionality for tbw-grid.\n * Supports text, number, date, set, and boolean filters with caching.\n * Includes UI with filter buttons in headers and dropdown filter panels.\n */\n\nimport { computeVirtualWindow, shouldBypassVirtualization } from '../../core/internal/virtualization';\nimport { BaseGridPlugin, type GridElement, type PluginManifest, type PluginQuery } from '../../core/plugin/base-plugin';\nimport { isUtilityColumn } from '../../core/plugin/expander-column';\nimport type { ColumnConfig, ColumnState } from '../../core/types';\nimport type { ContextMenuParams, HeaderContextMenuItem } from '../context-menu/types';\nimport { computeFilterCacheKey, filterRows, getUniqueValues } from './filter-model';\nimport styles from './filtering.css?inline';\nimport filterPanelStyles from './FilteringPlugin.css?inline';\nimport type { FilterChangeDetail, FilterConfig, FilterModel, FilterPanelParams } from './types';\n\n/**\n * Filtering Plugin for tbw-grid\n *\n * Adds column header filters with text search, dropdown options, and custom filter panels.\n * Supports both **local filtering** for small datasets and **async handlers** for server-side\n * filtering on large datasets.\n *\n * ## Installation\n *\n * ```ts\n * import { FilteringPlugin } from '@toolbox-web/grid/plugins/filtering';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `debounceMs` | `number` | `300` | Debounce delay for filter input |\n * | `caseSensitive` | `boolean` | `false` | Case-sensitive string matching |\n * | `trimInput` | `boolean` | `true` | Trim whitespace from filter input |\n * | `useWorker` | `boolean` | `true` | Use Web Worker for datasets >1000 rows |\n * | `filterPanelRenderer` | `FilterPanelRenderer` | - | Custom filter panel renderer |\n * | `valuesHandler` | `FilterValuesHandler` | - | Async handler to fetch unique filter values |\n * | `filterHandler` | `FilterHandler<TRow>` | - | Async handler to apply filters remotely |\n *\n * ## Column Configuration\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `filterable` | `boolean` | Enable filtering for this column |\n * | `filterType` | `'text' \\| 'select' \\| 'number' \\| 'date'` | Filter UI type |\n * | `filterOptions` | `unknown[]` | Predefined options for select filters |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `setFilter` | `(field, value) => void` | Set filter value for a column |\n * | `getFilters` | `() => FilterModel[]` | Get all current filters |\n * | `clearFilters` | `() => void` | Clear all filters |\n * | `clearFilter` | `(field) => void` | Clear filter for a specific column |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-filter-panel-bg` | `var(--tbw-color-panel-bg)` | Panel background |\n * | `--tbw-filter-panel-fg` | `var(--tbw-color-fg)` | Panel text color |\n * | `--tbw-filter-panel-border` | `var(--tbw-color-border)` | Panel border |\n * | `--tbw-filter-active-color` | `var(--tbw-color-accent)` | Active filter indicator |\n * | `--tbw-filter-input-bg` | `var(--tbw-color-bg)` | Input background |\n * | `--tbw-filter-input-focus` | `var(--tbw-color-accent)` | Input focus border |\n *\n * @example Basic Usage with Filterable Columns\n * ```ts\n * import '@toolbox-web/grid';\n * import { FilteringPlugin } from '@toolbox-web/grid/plugins/filtering';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name', filterable: true },\n * { field: 'status', header: 'Status', filterable: true, filterType: 'select' },\n * { field: 'email', header: 'Email', filterable: true },\n * ],\n * plugins: [new FilteringPlugin({ debounceMs: 300 })],\n * };\n * grid.rows = data;\n * ```\n *\n * @example Server-Side Filtering with Async Handlers\n * ```ts\n * new FilteringPlugin({\n * // Fetch unique values from server for filter dropdown\n * valuesHandler: async (field, column) => {\n * const response = await fetch(`/api/distinct-values?field=${field}`);\n * return response.json();\n * },\n * // Apply filters on the server\n * filterHandler: async (filters, currentRows) => {\n * const response = await fetch('/api/data', {\n * method: 'POST',\n * body: JSON.stringify({ filters }),\n * });\n * return response.json();\n * },\n * });\n * ```\n *\n * @see {@link FilterConfig} for all configuration options\n * @see {@link FilterModel} for filter data structure\n * @see {@link FilterPanelParams} for custom panel renderer parameters\n *\n * @internal Extends BaseGridPlugin\n */\nexport class FilteringPlugin extends BaseGridPlugin<FilterConfig> {\n /**\n * Plugin manifest - declares events emitted by this plugin.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n events: [\n {\n type: 'filter-applied',\n description: 'Emitted when filter criteria change. Subscribers can react to row visibility changes.',\n },\n ],\n queries: [\n {\n type: 'getContextMenuItems',\n description: 'Contributes filter-related items to the header context menu',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'filtering';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<FilterConfig> {\n return {\n debounceMs: 300,\n caseSensitive: false,\n trimInput: true,\n useWorker: true,\n };\n }\n\n // #region Helpers\n\n /**\n * Check if filtering is enabled at the grid level.\n * Grid-wide `filterable: false` disables filtering for all columns.\n */\n private isFilteringEnabled(): boolean {\n return this.grid.effectiveConfig?.filterable !== false;\n }\n\n /**\n * Check if a specific column is filterable, respecting both grid-level and column-level settings.\n */\n private isColumnFilterable(col: { filterable?: boolean; field?: string }): boolean {\n if (!this.isFilteringEnabled()) return false;\n return col.filterable !== false;\n }\n\n // #endregion\n\n // #region Internal State\n private filters: Map<string, FilterModel> = new Map();\n private cachedResult: unknown[] | null = null;\n private cacheKey: string | null = null;\n /** Spot-check of input rows for cache invalidation when upstream plugins (e.g. sort) change row order */\n private cachedInputSpot: { len: number; first: unknown; mid: unknown; last: unknown } | null = null;\n private openPanelField: string | null = null;\n private panelElement: HTMLElement | null = null;\n private panelAnchorElement: HTMLElement | null = null; // For CSS anchor positioning cleanup\n private searchText: Map<string, string> = new Map();\n private excludedValues: Map<string, Set<unknown>> = new Map();\n private panelAbortController: AbortController | null = null; // For panel-scoped listeners\n private globalStylesInjected = false;\n\n // Virtualization constants for filter value list\n private static readonly DEFAULT_LIST_ITEM_HEIGHT = 28;\n private static readonly LIST_OVERSCAN = 3;\n private static readonly LIST_BYPASS_THRESHOLD = 50; // Don't virtualize if < 50 items\n\n /**\n * Get the item height from CSS variable or fallback to default.\n * Reads --tbw-filter-item-height from the panel element.\n */\n private getListItemHeight(): number {\n if (this.panelElement) {\n const cssValue = getComputedStyle(this.panelElement).getPropertyValue('--tbw-filter-item-height');\n if (cssValue && cssValue.trim()) {\n const parsed = parseFloat(cssValue);\n if (!isNaN(parsed) && parsed > 0) {\n return parsed;\n }\n }\n }\n return FilteringPlugin.DEFAULT_LIST_ITEM_HEIGHT;\n }\n\n /**\n * Sync excludedValues map from a filter model (for set filters).\n */\n private syncExcludedValues(field: string, filter: FilterModel | null): void {\n if (!filter) {\n this.excludedValues.delete(field);\n } else if (filter.type === 'set' && filter.operator === 'notIn' && Array.isArray(filter.value)) {\n this.excludedValues.set(field, new Set(filter.value));\n } else if (filter.type === 'set') {\n // Other set operators may have different semantics; clear for safety\n this.excludedValues.delete(field);\n }\n }\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n this.injectGlobalStyles();\n }\n\n /** @internal */\n override detach(): void {\n this.filters.clear();\n this.cachedResult = null;\n this.cacheKey = null;\n this.cachedInputSpot = null;\n this.openPanelField = null;\n if (this.panelElement) {\n this.panelElement.remove();\n this.panelElement = null;\n }\n this.searchText.clear();\n this.excludedValues.clear();\n // Abort panel-scoped listeners (document click handler, etc.)\n this.panelAbortController?.abort();\n this.panelAbortController = null;\n }\n // #endregion\n\n // #region Query Handlers\n\n /**\n * Handle inter-plugin queries.\n * Contributes filter-related items to the header context menu.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'getContextMenuItems') {\n const params = query.context as ContextMenuParams;\n if (!params.isHeader) return undefined;\n\n const column = params.column as ColumnConfig;\n if (!column?.field) return undefined;\n\n // Only contribute items if filtering is enabled for this column\n if (!this.isFilteringEnabled()) return undefined;\n if (!this.isColumnFilterable(column)) return undefined;\n\n const items: HeaderContextMenuItem[] = [];\n const fieldFiltered = this.isFieldFiltered(column.field);\n const hasAnyFilter = this.filters.size > 0;\n\n if (fieldFiltered) {\n items.push({\n id: 'filtering/clear-column-filter',\n label: `Clear Filter`,\n icon: '✕',\n order: 20,\n action: () => this.clearFieldFilter(column.field),\n });\n }\n\n if (hasAnyFilter) {\n items.push({\n id: 'filtering/clear-all-filters',\n label: 'Clear All Filters',\n icon: '✕',\n order: 21,\n disabled: !hasAnyFilter,\n action: () => this.clearAllFilters(),\n });\n }\n\n return items.length > 0 ? items : undefined;\n }\n return undefined;\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processRows(rows: readonly unknown[]): unknown[] {\n const filterList = [...this.filters.values()];\n if (!filterList.length) return [...rows];\n\n // If using async filterHandler, processRows becomes a passthrough\n // Actual filtering happens in applyFiltersAsync and rows are set directly on grid\n if (this.config.filterHandler) {\n // Return cached result if available (set by async handler)\n if (this.cachedResult) return this.cachedResult;\n // Otherwise return rows as-is (filtering happens async)\n return [...rows];\n }\n\n // Check cache — also verify input rows haven't changed (e.g. due to sort)\n const newCacheKey = computeFilterCacheKey(filterList);\n const inputSpot = {\n len: rows.length,\n first: rows[0],\n mid: rows[Math.floor(rows.length / 2)],\n last: rows[rows.length - 1],\n };\n const inputUnchanged =\n this.cachedInputSpot != null &&\n inputSpot.len === this.cachedInputSpot.len &&\n inputSpot.first === this.cachedInputSpot.first &&\n inputSpot.mid === this.cachedInputSpot.mid &&\n inputSpot.last === this.cachedInputSpot.last;\n\n if (this.cacheKey === newCacheKey && this.cachedResult && inputUnchanged) {\n return this.cachedResult;\n }\n\n // Filter rows synchronously (worker support can be added later)\n const result = filterRows([...rows] as Record<string, unknown>[], filterList, this.config.caseSensitive);\n\n // Update cache\n this.cachedResult = result;\n this.cacheKey = newCacheKey;\n this.cachedInputSpot = inputSpot;\n\n return result;\n }\n\n /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Find all header cells (using part attribute, not class)\n const headerCells = gridEl.querySelectorAll('[part~=\"header-cell\"]');\n headerCells.forEach((cell) => {\n const colIndex = cell.getAttribute('data-col');\n if (colIndex === null) return;\n\n // Use visibleColumns since data-col is the index within _visibleColumns\n const col = this.visibleColumns[parseInt(colIndex, 10)] as ColumnConfig;\n if (!col || !this.isColumnFilterable(col)) return;\n\n // Skip utility columns (expander, selection checkbox, etc.)\n if (isUtilityColumn(col)) return;\n\n const field = col.field;\n if (!field) return;\n\n const hasFilter = this.filters.has(field);\n\n // Check if button already exists\n let filterBtn = cell.querySelector('.tbw-filter-btn') as HTMLElement | null;\n\n if (filterBtn) {\n // Update active state and icon of existing button\n const wasActive = filterBtn.classList.contains('active');\n filterBtn.classList.toggle('active', hasFilter);\n (cell as HTMLElement).classList.toggle('filtered', hasFilter);\n // Update icon if active state changed\n if (wasActive !== hasFilter) {\n const iconName = hasFilter ? 'filterActive' : 'filter';\n this.setIcon(filterBtn, this.resolveIcon(iconName));\n }\n return;\n }\n\n // Create filter button\n filterBtn = document.createElement('button');\n filterBtn.className = 'tbw-filter-btn';\n filterBtn.setAttribute('aria-label', `Filter ${col.header ?? field}`);\n // Use grid icons configuration\n const iconName = hasFilter ? 'filterActive' : 'filter';\n this.setIcon(filterBtn, this.resolveIcon(iconName));\n\n // Mark button as active if filter exists\n if (hasFilter) {\n filterBtn.classList.add('active');\n (cell as HTMLElement).classList.add('filtered');\n }\n\n filterBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.toggleFilterPanel(field, col, filterBtn!);\n });\n\n // Insert before resize handle to maintain order: [label, sort-indicator, filter-btn, resize-handle]\n const resizeHandle = cell.querySelector('.resize-handle');\n if (resizeHandle) {\n cell.insertBefore(filterBtn, resizeHandle);\n } else {\n cell.appendChild(filterBtn);\n }\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Set a filter on a specific field.\n * Pass null to remove the filter.\n */\n setFilter(field: string, filter: Omit<FilterModel, 'field'> | null): void {\n if (filter === null) {\n this.filters.delete(field);\n this.syncExcludedValues(field, null);\n } else {\n const fullFilter = { ...filter, field };\n this.filters.set(field, fullFilter);\n this.syncExcludedValues(field, fullFilter);\n }\n // Invalidate cache\n this.cachedResult = null;\n this.cacheKey = null;\n this.cachedInputSpot = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0, // Will be accurate after processRows\n });\n // Notify other plugins via Event Bus\n this.emitPluginEvent('filter-applied', { filters: [...this.filters.values()] });\n this.requestRender();\n }\n\n /**\n * Get the current filter for a field.\n */\n getFilter(field: string): FilterModel | undefined {\n return this.filters.get(field);\n }\n\n /**\n * Get all active filters.\n */\n getFilters(): FilterModel[] {\n return [...this.filters.values()];\n }\n\n /**\n * Alias for getFilters() to match functional API naming.\n */\n getFilterModel(): FilterModel[] {\n return this.getFilters();\n }\n\n /**\n * Set filters from an array (replaces all existing filters).\n */\n setFilterModel(filters: FilterModel[]): void {\n this.filters.clear();\n this.excludedValues.clear();\n for (const filter of filters) {\n this.filters.set(filter.field, filter);\n this.syncExcludedValues(filter.field, filter);\n }\n this.cachedResult = null;\n this.cacheKey = null;\n this.cachedInputSpot = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0,\n });\n // Notify other plugins via Event Bus\n this.emitPluginEvent('filter-applied', { filters: [...this.filters.values()] });\n this.requestRender();\n }\n\n /**\n * Clear all filters.\n */\n clearAllFilters(): void {\n this.filters.clear();\n this.excludedValues.clear();\n this.searchText.clear();\n\n this.applyFiltersInternal();\n }\n\n /**\n * Clear filter for a specific field.\n */\n clearFieldFilter(field: string): void {\n this.filters.delete(field);\n this.excludedValues.delete(field);\n this.searchText.delete(field);\n\n this.applyFiltersInternal();\n }\n\n /**\n * Check if a field has an active filter.\n */\n isFieldFiltered(field: string): boolean {\n return this.filters.has(field);\n }\n\n /**\n * Get the count of filtered rows (from cache).\n */\n getFilteredRowCount(): number {\n return this.cachedResult?.length ?? this.rows.length;\n }\n\n /**\n * Get all active filters (alias for getFilters).\n */\n getActiveFilters(): FilterModel[] {\n return this.getFilters();\n }\n\n /**\n * Get unique values for a field (for set filter dropdowns).\n * Uses sourceRows to include all values regardless of current filter.\n */\n getUniqueValues(field: string): unknown[] {\n return getUniqueValues(this.sourceRows as Record<string, unknown>[], field);\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Copy CSS classes and data attributes from grid to filter panel.\n * This ensures theme classes (e.g., .eds-theme) cascade to the panel.\n */\n private copyGridThemeContext(panel: HTMLElement): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Copy all CSS classes from grid to panel (except internal ones)\n for (const className of gridEl.classList) {\n // Skip internal classes that shouldn't be copied\n if (className.startsWith('tbw-') || className === 'selecting') continue;\n panel.classList.add(className);\n }\n\n // Copy data-theme attribute if present\n const theme = gridEl.dataset.theme;\n if (theme) {\n panel.dataset.theme = theme;\n }\n }\n\n /**\n * Inject global styles for filter panel (rendered in document.body)\n */\n private injectGlobalStyles(): void {\n if (this.globalStylesInjected) return;\n if (document.getElementById('tbw-filter-panel-styles')) {\n this.globalStylesInjected = true;\n return;\n }\n // Only inject if we have valid CSS text (Vite's ?inline import)\n // When importing from source without Vite, the import is a module object, not a string\n if (typeof filterPanelStyles !== 'string' || !filterPanelStyles) {\n this.globalStylesInjected = true;\n return;\n }\n const style = document.createElement('style');\n style.id = 'tbw-filter-panel-styles';\n style.textContent = filterPanelStyles;\n document.head.appendChild(style);\n this.globalStylesInjected = true;\n }\n\n /**\n * Toggle the filter panel for a field\n */\n private toggleFilterPanel(field: string, column: ColumnConfig, buttonEl: HTMLElement): void {\n // Close if already open\n if (this.openPanelField === field) {\n this.closeFilterPanel();\n return;\n }\n\n // Close any existing panel\n this.closeFilterPanel();\n\n // Create panel\n const panel = document.createElement('div');\n panel.className = 'tbw-filter-panel';\n // Copy theme classes from grid to panel for proper theming\n this.copyGridThemeContext(panel);\n // Add animation class if animations are enabled\n if (this.isAnimationEnabled) {\n panel.classList.add('tbw-filter-panel-animated');\n }\n this.panelElement = panel;\n this.openPanelField = field;\n\n // If using async valuesHandler, show loading state and fetch values\n if (this.config.valuesHandler) {\n panel.innerHTML = '<div class=\"tbw-filter-loading\">Loading...</div>';\n document.body.appendChild(panel);\n this.positionPanel(panel, buttonEl);\n this.setupPanelCloseHandler(panel, buttonEl);\n\n this.config.valuesHandler(field, column).then((values) => {\n // Check if panel is still open for this field\n if (this.openPanelField !== field || !this.panelElement) return;\n panel.innerHTML = '';\n this.renderPanelContent(field, column, panel, values);\n });\n return;\n }\n\n // Sync path: get unique values from local rows\n const uniqueValues = getUniqueValues(this.sourceRows as Record<string, unknown>[], field);\n\n // Position and append to body BEFORE rendering content\n // so getListItemHeight() can read CSS variables from computed styles\n document.body.appendChild(panel);\n this.positionPanel(panel, buttonEl);\n\n this.renderPanelContent(field, column, panel, uniqueValues);\n this.setupPanelCloseHandler(panel, buttonEl);\n }\n\n /**\n * Render filter panel content with given values\n */\n private renderPanelContent(field: string, column: ColumnConfig, panel: HTMLElement, uniqueValues: unknown[]): void {\n // Get current excluded values or initialize empty\n let excludedSet = this.excludedValues.get(field);\n if (!excludedSet) {\n excludedSet = new Set();\n this.excludedValues.set(field, excludedSet);\n }\n\n // Get current search text\n const currentSearchText = this.searchText.get(field) ?? '';\n\n // Create panel params for custom renderer\n const params: FilterPanelParams = {\n field,\n column,\n uniqueValues,\n excludedValues: excludedSet,\n searchText: currentSearchText,\n applySetFilter: (excluded: unknown[]) => {\n this.applySetFilter(field, excluded);\n this.closeFilterPanel();\n },\n applyTextFilter: (operator, value, valueTo) => {\n this.applyTextFilter(field, operator, value, valueTo);\n this.closeFilterPanel();\n },\n clearFilter: () => {\n this.clearFieldFilter(field);\n this.closeFilterPanel();\n },\n closePanel: () => this.closeFilterPanel(),\n };\n\n // Use custom renderer or default\n // Custom renderer can return undefined to fall back to default panel for specific columns\n // Resolution order: plugin config → typeDefaults → built-in\n let usedCustomRenderer = false;\n\n // 1. Check plugin-level filterPanelRenderer\n if (this.config.filterPanelRenderer) {\n this.config.filterPanelRenderer(panel, params);\n // If renderer added content to panel, it handled rendering\n usedCustomRenderer = panel.children.length > 0;\n }\n\n // 2. Check typeDefaults for this column's type\n if (!usedCustomRenderer && column.type) {\n const typeDefault = this.grid.effectiveConfig.typeDefaults?.[column.type];\n if (typeDefault?.filterPanelRenderer) {\n typeDefault.filterPanelRenderer(panel, params);\n usedCustomRenderer = panel.children.length > 0;\n }\n }\n\n // 3. Fall back to built-in type-specific panel renderers\n if (!usedCustomRenderer) {\n const columnType = column.type;\n if (columnType === 'number') {\n this.renderNumberFilterPanel(panel, params, uniqueValues);\n } else if (columnType === 'date') {\n this.renderDateFilterPanel(panel, params, uniqueValues);\n } else {\n this.renderDefaultFilterPanel(panel, params, uniqueValues, excludedSet);\n }\n }\n }\n\n /**\n * Setup click-outside handler to close the panel\n */\n private setupPanelCloseHandler(panel: HTMLElement, buttonEl: HTMLElement): void {\n // Create abort controller for panel-scoped listeners\n // This allows cleanup when panel closes OR when grid disconnects\n this.panelAbortController = new AbortController();\n\n // Add global click handler to close on outside click\n // Defer to next tick to avoid immediate close from the click that opened the panel\n setTimeout(() => {\n document.addEventListener(\n 'click',\n (e: MouseEvent) => {\n if (!panel.contains(e.target as Node) && e.target !== buttonEl) {\n this.closeFilterPanel();\n }\n },\n { signal: this.panelAbortController?.signal },\n );\n }, 0);\n }\n\n /**\n * Close the filter panel\n */\n private closeFilterPanel(): void {\n const panel = this.panelElement;\n if (panel) {\n panel.remove();\n this.panelElement = null;\n }\n // Clean up anchor name from header cell\n if (this.panelAnchorElement) {\n (this.panelAnchorElement.style as any).anchorName = '';\n this.panelAnchorElement = null;\n }\n this.openPanelField = null;\n // Abort panel-scoped listeners (document click handler)\n this.panelAbortController?.abort();\n this.panelAbortController = null;\n }\n\n /** Cache for CSS anchor positioning support check */\n private static supportsAnchorPositioning: boolean | null = null;\n\n /**\n * Check if browser supports CSS Anchor Positioning\n */\n private static checkAnchorPositioningSupport(): boolean {\n if (FilteringPlugin.supportsAnchorPositioning === null) {\n FilteringPlugin.supportsAnchorPositioning = CSS.supports('anchor-name', '--test');\n }\n return FilteringPlugin.supportsAnchorPositioning;\n }\n\n /**\n * Position the panel below the header cell\n * Uses CSS Anchor Positioning if supported, falls back to JS positioning\n */\n private positionPanel(panel: HTMLElement, buttonEl: HTMLElement): void {\n // Find the parent header cell\n const headerCell = buttonEl.closest('.cell') as HTMLElement | null;\n const anchorEl = headerCell ?? buttonEl;\n\n // Set anchor name on the header cell for CSS anchor positioning\n (anchorEl.style as any).anchorName = '--tbw-filter-anchor';\n this.panelAnchorElement = anchorEl; // Store for cleanup\n\n // If CSS Anchor Positioning is supported, CSS handles positioning\n // but we need to detect if it flipped above to adjust animation\n if (FilteringPlugin.checkAnchorPositioningSupport()) {\n // Check position after CSS anchor positioning takes effect\n requestAnimationFrame(() => {\n const panelRect = panel.getBoundingClientRect();\n const anchorRect = anchorEl.getBoundingClientRect();\n // If panel top is above anchor top, it flipped to above\n if (panelRect.top < anchorRect.top) {\n panel.classList.add('tbw-filter-panel-above');\n }\n });\n return;\n }\n\n // Fallback: JS-based positioning for older browsers\n const rect = anchorEl.getBoundingClientRect();\n\n panel.style.position = 'fixed';\n panel.style.top = `${rect.bottom + 4}px`;\n panel.style.left = `${rect.left}px`;\n\n // Adjust if overflows viewport edges\n requestAnimationFrame(() => {\n const panelRect = panel.getBoundingClientRect();\n\n // Check horizontal overflow - align right edge to header cell right edge\n if (panelRect.right > window.innerWidth - 8) {\n panel.style.left = `${rect.right - panelRect.width}px`;\n }\n\n // Check vertical overflow - flip to above header cell\n if (panelRect.bottom > window.innerHeight - 8) {\n panel.style.top = `${rect.top - panelRect.height - 4}px`;\n panel.classList.add('tbw-filter-panel-above');\n }\n });\n }\n\n /**\n * Render the default filter panel content\n */\n private renderDefaultFilterPanel(\n panel: HTMLElement,\n params: FilterPanelParams,\n uniqueValues: unknown[],\n excludedValues: Set<unknown>,\n ): void {\n const { field } = params;\n // Get item height from CSS variable or use default\n const itemHeight = this.getListItemHeight();\n\n // Search input\n const searchContainer = document.createElement('div');\n searchContainer.className = 'tbw-filter-search';\n\n const searchInput = document.createElement('input');\n searchInput.type = 'text';\n searchInput.placeholder = 'Search...';\n searchInput.className = 'tbw-filter-search-input';\n searchInput.value = this.searchText.get(field) ?? '';\n searchContainer.appendChild(searchInput);\n panel.appendChild(searchContainer);\n\n // Select All tristate checkbox\n const actionsRow = document.createElement('div');\n actionsRow.className = 'tbw-filter-actions';\n\n const selectAllLabel = document.createElement('label');\n selectAllLabel.className = 'tbw-filter-value-item';\n selectAllLabel.style.padding = '0';\n selectAllLabel.style.margin = '0';\n\n const selectAllCheckbox = document.createElement('input');\n selectAllCheckbox.type = 'checkbox';\n selectAllCheckbox.className = 'tbw-filter-checkbox';\n\n const selectAllText = document.createElement('span');\n selectAllText.textContent = 'Select All';\n\n selectAllLabel.appendChild(selectAllCheckbox);\n selectAllLabel.appendChild(selectAllText);\n actionsRow.appendChild(selectAllLabel);\n\n // Update tristate checkbox based on checkState\n const updateSelectAllState = () => {\n const values = [...checkState.values()];\n const allChecked = values.every((v) => v);\n const noneChecked = values.every((v) => !v);\n\n selectAllCheckbox.checked = allChecked;\n selectAllCheckbox.indeterminate = !allChecked && !noneChecked;\n };\n\n // Toggle all on click\n selectAllCheckbox.addEventListener('change', () => {\n const newState = selectAllCheckbox.checked;\n for (const key of checkState.keys()) {\n checkState.set(key, newState);\n }\n updateSelectAllState();\n renderVisibleItems();\n });\n\n panel.appendChild(actionsRow);\n\n // Values container with virtualization support\n const valuesContainer = document.createElement('div');\n valuesContainer.className = 'tbw-filter-values';\n\n // Spacer for virtual height\n const spacer = document.createElement('div');\n spacer.className = 'tbw-filter-values-spacer';\n valuesContainer.appendChild(spacer);\n\n // Content container positioned absolutely\n const contentContainer = document.createElement('div');\n contentContainer.className = 'tbw-filter-values-content';\n valuesContainer.appendChild(contentContainer);\n\n // Track current check state for values (persists across virtualizations)\n const checkState = new Map<string, boolean>();\n uniqueValues.forEach((value) => {\n const key = value == null ? '__null__' : String(value);\n checkState.set(key, !excludedValues.has(value));\n });\n\n // Initialize select all state\n updateSelectAllState();\n\n // Filtered values cache\n let filteredValues: unknown[] = [];\n\n // Create a single checkbox item element\n const createItem = (value: unknown, index: number): HTMLElement => {\n const strValue = value == null ? '(Blank)' : String(value);\n const key = value == null ? '__null__' : String(value);\n\n const item = document.createElement('label');\n item.className = 'tbw-filter-value-item';\n item.style.position = 'absolute';\n item.style.top = `calc(var(--tbw-filter-item-height, 28px) * ${index})`;\n item.style.left = '0';\n item.style.right = '0';\n item.style.boxSizing = 'border-box';\n\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.className = 'tbw-filter-checkbox';\n checkbox.checked = checkState.get(key) ?? true;\n checkbox.dataset.value = key;\n\n // Sync check state on change and update tristate checkbox\n checkbox.addEventListener('change', () => {\n checkState.set(key, checkbox.checked);\n updateSelectAllState();\n });\n\n const label = document.createElement('span');\n label.textContent = strValue;\n\n item.appendChild(checkbox);\n item.appendChild(label);\n return item;\n };\n\n // Render visible items using virtualization\n const renderVisibleItems = () => {\n const totalItems = filteredValues.length;\n const viewportHeight = valuesContainer.clientHeight;\n const scrollTop = valuesContainer.scrollTop;\n\n // Set total height for scrollbar\n spacer.style.height = `${totalItems * itemHeight}px`;\n\n // Bypass virtualization for small lists\n if (shouldBypassVirtualization(totalItems, FilteringPlugin.LIST_BYPASS_THRESHOLD / 3)) {\n contentContainer.innerHTML = '';\n contentContainer.style.transform = 'translateY(0px)';\n filteredValues.forEach((value, idx) => {\n contentContainer.appendChild(createItem(value, idx));\n });\n return;\n }\n\n // Use computeVirtualWindow for real-scroll virtualization\n const window = computeVirtualWindow({\n totalRows: totalItems,\n viewportHeight,\n scrollTop,\n rowHeight: itemHeight,\n overscan: FilteringPlugin.LIST_OVERSCAN,\n });\n\n // Position content container\n contentContainer.style.transform = `translateY(${window.offsetY}px)`;\n\n // Clear and render visible items\n contentContainer.innerHTML = '';\n for (let i = window.start; i < window.end; i++) {\n contentContainer.appendChild(createItem(filteredValues[i], i - window.start));\n }\n };\n\n // Filter and re-render values\n const renderValues = (filterText: string) => {\n const caseSensitive = this.config.caseSensitive ?? false;\n const compareFilter = caseSensitive ? filterText : filterText.toLowerCase();\n\n // Filter the unique values\n filteredValues = uniqueValues.filter((value) => {\n const strValue = value == null ? '(Blank)' : String(value);\n const compareValue = caseSensitive ? strValue : strValue.toLowerCase();\n return !filterText || compareValue.includes(compareFilter);\n });\n\n if (filteredValues.length === 0) {\n spacer.style.height = '0px';\n contentContainer.innerHTML = '';\n const noMatch = document.createElement('div');\n noMatch.className = 'tbw-filter-no-match';\n noMatch.textContent = 'No matching values';\n contentContainer.appendChild(noMatch);\n return;\n }\n\n renderVisibleItems();\n };\n\n // Scroll handler for virtualization\n valuesContainer.addEventListener(\n 'scroll',\n () => {\n if (filteredValues.length > 0) {\n renderVisibleItems();\n }\n },\n { passive: true },\n );\n\n renderValues(searchInput.value);\n panel.appendChild(valuesContainer);\n\n // Debounced search\n let debounceTimer: ReturnType<typeof setTimeout>;\n searchInput.addEventListener('input', () => {\n clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n this.searchText.set(field, searchInput.value);\n renderValues(searchInput.value);\n }, this.config.debounceMs ?? 150);\n });\n\n // Apply/Clear buttons\n const buttonRow = document.createElement('div');\n buttonRow.className = 'tbw-filter-buttons';\n\n const applyBtn = document.createElement('button');\n applyBtn.className = 'tbw-filter-apply-btn';\n applyBtn.textContent = 'Apply';\n applyBtn.addEventListener('click', () => {\n // Read from checkState map (works with virtualization)\n const excluded: unknown[] = [];\n for (const [key, isChecked] of checkState) {\n if (!isChecked) {\n if (key === '__null__') {\n excluded.push(null);\n } else {\n // Try to match original value type\n const original = uniqueValues.find((v) => String(v) === key);\n excluded.push(original !== undefined ? original : key);\n }\n }\n }\n params.applySetFilter(excluded);\n });\n buttonRow.appendChild(applyBtn);\n\n const clearBtn = document.createElement('button');\n clearBtn.className = 'tbw-filter-clear-btn';\n clearBtn.textContent = 'Clear Filter';\n clearBtn.addEventListener('click', () => {\n params.clearFilter();\n });\n buttonRow.appendChild(clearBtn);\n\n panel.appendChild(buttonRow);\n }\n\n /**\n * Render a number range filter panel with min/max inputs and slider\n */\n private renderNumberFilterPanel(panel: HTMLElement, params: FilterPanelParams, uniqueValues: unknown[]): void {\n const { field, column } = params;\n\n // Get range configuration from filterParams, editorParams, or compute from data\n const filterParams = column.filterParams;\n const editorParams = column.editorParams as { min?: number; max?: number; step?: number } | undefined;\n\n // Helper to convert to number\n const toNumber = (val: unknown, fallback: number): number => {\n if (typeof val === 'number') return val;\n if (typeof val === 'string') {\n const num = parseFloat(val);\n return isNaN(num) ? fallback : num;\n }\n return fallback;\n };\n\n // Compute min/max from data if not specified\n const numericValues = uniqueValues.filter((v) => typeof v === 'number' && !isNaN(v)) as number[];\n const dataMin = numericValues.length > 0 ? Math.min(...numericValues) : 0;\n const dataMax = numericValues.length > 0 ? Math.max(...numericValues) : 100;\n\n const min = toNumber(filterParams?.min ?? editorParams?.min, dataMin);\n const max = toNumber(filterParams?.max ?? editorParams?.max, dataMax);\n const step = filterParams?.step ?? editorParams?.step ?? 1;\n\n // Get current filter values if any\n const currentFilter = this.filters.get(field);\n let currentMin = min;\n let currentMax = max;\n if (currentFilter?.operator === 'between') {\n currentMin = toNumber(currentFilter.value, min);\n currentMax = toNumber(currentFilter.valueTo, max);\n } else if (currentFilter?.operator === 'greaterThanOrEqual') {\n currentMin = toNumber(currentFilter.value, min);\n } else if (currentFilter?.operator === 'lessThanOrEqual') {\n currentMax = toNumber(currentFilter.value, max);\n }\n\n // Range inputs container\n const rangeContainer = document.createElement('div');\n rangeContainer.className = 'tbw-filter-range-inputs';\n\n // Min input\n const minGroup = document.createElement('div');\n minGroup.className = 'tbw-filter-range-group';\n\n const minLabel = document.createElement('label');\n minLabel.textContent = 'Min';\n minLabel.className = 'tbw-filter-range-label';\n\n const minInput = document.createElement('input');\n minInput.type = 'number';\n minInput.className = 'tbw-filter-range-input';\n minInput.min = String(min);\n minInput.max = String(max);\n minInput.step = String(step);\n minInput.value = String(currentMin);\n\n minGroup.appendChild(minLabel);\n minGroup.appendChild(minInput);\n rangeContainer.appendChild(minGroup);\n\n // Separator\n const separator = document.createElement('span');\n separator.className = 'tbw-filter-range-separator';\n separator.textContent = '–';\n rangeContainer.appendChild(separator);\n\n // Max input\n const maxGroup = document.createElement('div');\n maxGroup.className = 'tbw-filter-range-group';\n\n const maxLabel = document.createElement('label');\n maxLabel.textContent = 'Max';\n maxLabel.className = 'tbw-filter-range-label';\n\n const maxInput = document.createElement('input');\n maxInput.type = 'number';\n maxInput.className = 'tbw-filter-range-input';\n maxInput.min = String(min);\n maxInput.max = String(max);\n maxInput.step = String(step);\n maxInput.value = String(currentMax);\n\n maxGroup.appendChild(maxLabel);\n maxGroup.appendChild(maxInput);\n rangeContainer.appendChild(maxGroup);\n\n panel.appendChild(rangeContainer);\n\n // Range slider (dual thumb using two range inputs)\n const sliderContainer = document.createElement('div');\n sliderContainer.className = 'tbw-filter-range-slider';\n\n const sliderTrack = document.createElement('div');\n sliderTrack.className = 'tbw-filter-range-track';\n\n const sliderFill = document.createElement('div');\n sliderFill.className = 'tbw-filter-range-fill';\n\n const minSlider = document.createElement('input');\n minSlider.type = 'range';\n minSlider.className = 'tbw-filter-range-thumb tbw-filter-range-thumb-min';\n minSlider.min = String(min);\n minSlider.max = String(max);\n minSlider.step = String(step);\n minSlider.value = String(currentMin);\n\n const maxSlider = document.createElement('input');\n maxSlider.type = 'range';\n maxSlider.className = 'tbw-filter-range-thumb tbw-filter-range-thumb-max';\n maxSlider.min = String(min);\n maxSlider.max = String(max);\n maxSlider.step = String(step);\n maxSlider.value = String(currentMax);\n\n sliderContainer.appendChild(sliderTrack);\n sliderContainer.appendChild(sliderFill);\n sliderContainer.appendChild(minSlider);\n sliderContainer.appendChild(maxSlider);\n panel.appendChild(sliderContainer);\n\n // Update fill position\n const updateFill = () => {\n const minVal = parseFloat(minSlider.value);\n const maxVal = parseFloat(maxSlider.value);\n const range = max - min;\n const leftPercent = ((minVal - min) / range) * 100;\n const rightPercent = ((maxVal - min) / range) * 100;\n sliderFill.style.left = `${leftPercent}%`;\n sliderFill.style.width = `${rightPercent - leftPercent}%`;\n };\n\n // Sync inputs with sliders\n minSlider.addEventListener('input', () => {\n const val = Math.min(parseFloat(minSlider.value), parseFloat(maxSlider.value));\n minSlider.value = String(val);\n minInput.value = String(val);\n updateFill();\n });\n\n maxSlider.addEventListener('input', () => {\n const val = Math.max(parseFloat(maxSlider.value), parseFloat(minSlider.value));\n maxSlider.value = String(val);\n maxInput.value = String(val);\n updateFill();\n });\n\n // Sync sliders with inputs\n minInput.addEventListener('input', () => {\n let val = parseFloat(minInput.value) || min;\n val = Math.max(min, Math.min(val, parseFloat(maxInput.value)));\n minSlider.value = String(val);\n updateFill();\n });\n\n maxInput.addEventListener('input', () => {\n let val = parseFloat(maxInput.value) || max;\n val = Math.min(max, Math.max(val, parseFloat(minInput.value)));\n maxSlider.value = String(val);\n updateFill();\n });\n\n // Initialize fill\n updateFill();\n\n // Apply/Clear buttons\n const buttonRow = document.createElement('div');\n buttonRow.className = 'tbw-filter-buttons';\n\n const applyBtn = document.createElement('button');\n applyBtn.className = 'tbw-filter-apply-btn';\n applyBtn.textContent = 'Apply';\n applyBtn.addEventListener('click', () => {\n const minVal = parseFloat(minInput.value);\n const maxVal = parseFloat(maxInput.value);\n params.applyTextFilter('between', minVal, maxVal);\n });\n buttonRow.appendChild(applyBtn);\n\n const clearBtn = document.createElement('button');\n clearBtn.className = 'tbw-filter-clear-btn';\n clearBtn.textContent = 'Clear Filter';\n clearBtn.addEventListener('click', () => {\n params.clearFilter();\n });\n buttonRow.appendChild(clearBtn);\n\n panel.appendChild(buttonRow);\n }\n\n /**\n * Render a date range filter panel with from/to date inputs\n */\n private renderDateFilterPanel(panel: HTMLElement, params: FilterPanelParams, uniqueValues: unknown[]): void {\n const { field, column } = params;\n\n // Get range configuration from filterParams, editorParams, or compute from data\n const filterParams = column.filterParams;\n const editorParams = column.editorParams as { min?: string; max?: string } | undefined;\n\n // Compute min/max from data if not specified\n const dateValues = uniqueValues\n .filter((v) => v instanceof Date || (typeof v === 'string' && !isNaN(Date.parse(v))))\n .map((v) => (v instanceof Date ? v : new Date(v as string)))\n .filter((d) => !isNaN(d.getTime()));\n\n const dataMin = dateValues.length > 0 ? new Date(Math.min(...dateValues.map((d) => d.getTime()))) : null;\n const dataMax = dateValues.length > 0 ? new Date(Math.max(...dateValues.map((d) => d.getTime()))) : null;\n\n // Format date for input[type=\"date\"] (YYYY-MM-DD)\n const formatDateForInput = (date: Date | null): string => {\n if (!date) return '';\n return date.toISOString().split('T')[0];\n };\n\n const parseFilterParam = (value: unknown): string => {\n if (!value) return '';\n if (typeof value === 'string') return value;\n if (typeof value === 'number') return formatDateForInput(new Date(value));\n return '';\n };\n\n const minDate =\n parseFilterParam(filterParams?.min) || parseFilterParam(editorParams?.min) || formatDateForInput(dataMin);\n const maxDate =\n parseFilterParam(filterParams?.max) || parseFilterParam(editorParams?.max) || formatDateForInput(dataMax);\n\n // Get current filter values if any\n const currentFilter = this.filters.get(field);\n let currentFrom = '';\n let currentTo = '';\n const isBlankFilter = currentFilter?.operator === 'blank';\n if (currentFilter?.operator === 'between') {\n currentFrom = parseFilterParam(currentFilter.value) || '';\n currentTo = parseFilterParam(currentFilter.valueTo) || '';\n } else if (currentFilter?.operator === 'greaterThanOrEqual') {\n currentFrom = parseFilterParam(currentFilter.value) || '';\n } else if (currentFilter?.operator === 'lessThanOrEqual') {\n currentTo = parseFilterParam(currentFilter.value) || '';\n }\n\n // Date range inputs container\n const rangeContainer = document.createElement('div');\n rangeContainer.className = 'tbw-filter-date-range';\n\n // From input\n const fromGroup = document.createElement('div');\n fromGroup.className = 'tbw-filter-date-group';\n\n const fromLabel = document.createElement('label');\n fromLabel.textContent = 'From';\n fromLabel.className = 'tbw-filter-range-label';\n\n const fromInput = document.createElement('input');\n fromInput.type = 'date';\n fromInput.className = 'tbw-filter-date-input';\n if (minDate) fromInput.min = minDate;\n if (maxDate) fromInput.max = maxDate;\n fromInput.value = currentFrom;\n\n fromGroup.appendChild(fromLabel);\n fromGroup.appendChild(fromInput);\n rangeContainer.appendChild(fromGroup);\n\n // Separator\n const separator = document.createElement('span');\n separator.className = 'tbw-filter-range-separator';\n separator.textContent = '–';\n rangeContainer.appendChild(separator);\n\n // To input\n const toGroup = document.createElement('div');\n toGroup.className = 'tbw-filter-date-group';\n\n const toLabel = document.createElement('label');\n toLabel.textContent = 'To';\n toLabel.className = 'tbw-filter-range-label';\n\n const toInput = document.createElement('input');\n toInput.type = 'date';\n toInput.className = 'tbw-filter-date-input';\n if (minDate) toInput.min = minDate;\n if (maxDate) toInput.max = maxDate;\n toInput.value = currentTo;\n\n toGroup.appendChild(toLabel);\n toGroup.appendChild(toInput);\n rangeContainer.appendChild(toGroup);\n\n panel.appendChild(rangeContainer);\n\n // \"Show only blank\" checkbox\n const blankRow = document.createElement('label');\n blankRow.className = 'tbw-filter-blank-option';\n\n const blankCheckbox = document.createElement('input');\n blankCheckbox.type = 'checkbox';\n blankCheckbox.className = 'tbw-filter-blank-checkbox';\n blankCheckbox.checked = isBlankFilter;\n\n const blankLabel = document.createTextNode('Show only blank');\n blankRow.appendChild(blankCheckbox);\n blankRow.appendChild(blankLabel);\n\n // Toggle date inputs disabled state when blank is checked\n const toggleDateInputs = (disabled: boolean): void => {\n fromInput.disabled = disabled;\n toInput.disabled = disabled;\n rangeContainer.classList.toggle('tbw-filter-disabled', disabled);\n };\n toggleDateInputs(isBlankFilter);\n\n blankCheckbox.addEventListener('change', () => {\n toggleDateInputs(blankCheckbox.checked);\n });\n\n panel.appendChild(blankRow);\n\n // Apply/Clear buttons\n const buttonRow = document.createElement('div');\n buttonRow.className = 'tbw-filter-buttons';\n\n const applyBtn = document.createElement('button');\n applyBtn.className = 'tbw-filter-apply-btn';\n applyBtn.textContent = 'Apply';\n applyBtn.addEventListener('click', () => {\n if (blankCheckbox.checked) {\n params.applyTextFilter('blank', '');\n return;\n }\n\n const from = fromInput.value;\n const to = toInput.value;\n\n if (from && to) {\n params.applyTextFilter('between', from, to);\n } else if (from) {\n params.applyTextFilter('greaterThanOrEqual', from);\n } else if (to) {\n params.applyTextFilter('lessThanOrEqual', to);\n } else {\n params.clearFilter();\n }\n });\n buttonRow.appendChild(applyBtn);\n\n const clearBtn = document.createElement('button');\n clearBtn.className = 'tbw-filter-clear-btn';\n clearBtn.textContent = 'Clear Filter';\n clearBtn.addEventListener('click', () => {\n params.clearFilter();\n });\n buttonRow.appendChild(clearBtn);\n\n panel.appendChild(buttonRow);\n }\n\n /**\n * Apply a set filter (exclude values)\n */\n private applySetFilter(field: string, excluded: unknown[]): void {\n // Store excluded values\n this.excludedValues.set(field, new Set(excluded));\n\n if (excluded.length === 0) {\n // No exclusions = no filter\n this.filters.delete(field);\n } else {\n // Create \"notIn\" filter\n this.filters.set(field, {\n field,\n type: 'set',\n operator: 'notIn',\n value: excluded,\n });\n }\n\n this.applyFiltersInternal();\n }\n\n /**\n * Apply a text/number/date filter\n */\n private applyTextFilter(\n field: string,\n operator: FilterModel['operator'],\n value: string | number,\n valueTo?: string | number,\n ): void {\n this.filters.set(field, {\n field,\n type: 'text',\n operator,\n value,\n valueTo,\n });\n\n this.applyFiltersInternal();\n }\n\n /**\n * Internal method to apply filters (sync or async based on config)\n */\n private applyFiltersInternal(): void {\n this.cachedResult = null;\n this.cacheKey = null;\n this.cachedInputSpot = null;\n\n const filterList = [...this.filters.values()];\n\n // If using async filterHandler, delegate to server\n if (this.config.filterHandler) {\n const gridEl = this.grid as unknown as Element;\n gridEl.setAttribute('aria-busy', 'true');\n\n const result = this.config.filterHandler(filterList, this.sourceRows as unknown[]);\n\n // Handle async or sync result\n const handleResult = (rows: unknown[]) => {\n gridEl.removeAttribute('aria-busy');\n this.cachedResult = rows;\n\n // Update grid rows directly for async filtering\n (this.grid as unknown as { rows: unknown[] }).rows = rows;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: filterList,\n filteredRowCount: rows.length,\n });\n // Notify other plugins via Event Bus\n this.emitPluginEvent('filter-applied', { filters: filterList });\n\n // Trigger afterRender to update filter button active state\n this.requestRender();\n };\n\n if (result && typeof (result as Promise<unknown[]>).then === 'function') {\n (result as Promise<unknown[]>).then(handleResult);\n } else {\n handleResult(result as unknown[]);\n }\n return;\n }\n\n // Sync path: emit event and re-render (processRows will handle filtering)\n this.emit<FilterChangeDetail>('filter-change', {\n filters: filterList,\n filteredRowCount: 0,\n });\n // Notify other plugins via Event Bus\n this.emitPluginEvent('filter-applied', { filters: filterList });\n this.requestRender();\n }\n // #endregion\n\n // #region Column State Hooks\n\n /**\n * Return filter state for a column if it has an active filter.\n * @internal\n */\n override getColumnState(field: string): Partial<ColumnState> | undefined {\n const filterModel = this.filters.get(field);\n if (!filterModel) return undefined;\n\n return {\n filter: {\n type: filterModel.type,\n operator: filterModel.operator,\n value: filterModel.value,\n valueTo: filterModel.valueTo,\n },\n };\n }\n\n /**\n * Apply filter state from column state.\n * @internal\n */\n override applyColumnState(field: string, state: ColumnState): void {\n // Only process if the column has filter state\n if (!state.filter) {\n this.filters.delete(field);\n return;\n }\n\n // Reconstruct the FilterModel from the stored state\n const filterModel: FilterModel = {\n field,\n type: state.filter.type,\n operator: state.filter.operator as FilterModel['operator'],\n value: state.filter.value,\n valueTo: state.filter.valueTo,\n };\n\n this.filters.set(field, filterModel);\n // Invalidate cache so filter is reapplied\n this.cachedResult = null;\n this.cacheKey = null;\n this.cachedInputSpot = null;\n }\n // #endregion\n}\n","/**\n * Column Groups Core Logic\n *\n * Pure functions for computing and managing column header groups.\n */\n\n// Import types to enable module augmentation\nimport type { ColumnConfig } from '../../core/types';\nimport './types';\nimport type { ColumnGroup, ColumnGroupInternal } from './types';\n\n/**\n * Compute column groups from column configuration.\n * Handles explicit groups (via column.group) and creates implicit groups for ungrouped columns.\n *\n * @param columns - Array of column configurations\n * @returns Array of column groups, or empty if no meaningful groups\n */\nexport function computeColumnGroups<T>(columns: ColumnConfig<T>[]): ColumnGroup<T>[] {\n if (!columns.length) return [];\n\n const explicitMap = new Map<string, ColumnGroupInternal<T>>();\n const groupsOrdered: ColumnGroupInternal<T>[] = [];\n\n // Helper to push unnamed implicit group for a run of ungrouped columns\n const pushImplicit = (startIdx: number, cols: ColumnConfig<T>[]) => {\n if (!cols.length) return;\n // Merge with previous implicit group if adjacent to reduce noise\n const prev = groupsOrdered[groupsOrdered.length - 1];\n if (prev && prev.implicit && prev.firstIndex + prev.columns.length === startIdx) {\n prev.columns.push(...cols);\n return;\n }\n groupsOrdered.push({\n id: '__implicit__' + startIdx,\n label: undefined,\n columns: cols,\n firstIndex: startIdx,\n implicit: true,\n });\n };\n\n let run: ColumnConfig<T>[] = [];\n let runStart = 0;\n\n columns.forEach((col, idx) => {\n const g = col.group;\n if (!g) {\n if (run.length === 0) runStart = idx;\n run.push(col);\n return;\n }\n // Close any pending implicit run\n if (run.length) {\n pushImplicit(runStart, run.slice());\n run = [];\n }\n const id = typeof g === 'string' ? g : g.id;\n let group = explicitMap.get(id);\n if (!group) {\n group = {\n id,\n label: typeof g === 'string' ? undefined : g.label,\n columns: [],\n firstIndex: idx,\n };\n explicitMap.set(id, group);\n groupsOrdered.push(group);\n }\n group.columns.push(col);\n });\n\n // Trailing implicit run\n if (run.length) pushImplicit(runStart, run);\n\n // If we only have a single implicit group covering all columns, treat as no groups\n if (groupsOrdered.length === 1 && groupsOrdered[0].implicit && groupsOrdered[0].columns.length === columns.length) {\n return [];\n }\n\n return groupsOrdered as ColumnGroup<T>[];\n}\n\n/**\n * Apply CSS classes to header cells based on their group membership.\n *\n * @param headerRowEl - The header row element\n * @param groups - The computed column groups\n * @param columns - The column configurations\n */\nexport function applyGroupedHeaderCellClasses(\n headerRowEl: HTMLElement | null,\n groups: ColumnGroup[],\n columns: ColumnConfig[],\n): void {\n if (!groups.length || !headerRowEl) return;\n\n const fieldToGroup = new Map<string, string>();\n for (const g of groups) {\n for (const c of g.columns) {\n if (c.field) {\n fieldToGroup.set(c.field, g.id);\n }\n }\n }\n\n const headerCells = Array.from(headerRowEl.querySelectorAll('.cell[data-field]')) as HTMLElement[];\n headerCells.forEach((cell) => {\n const f = cell.getAttribute('data-field') || '';\n const gid = fieldToGroup.get(f);\n if (gid) {\n cell.classList.add('grouped');\n if (!cell.getAttribute('data-group')) {\n cell.setAttribute('data-group', gid);\n }\n }\n });\n\n // Mark group end cells for styling\n for (const g of groups) {\n const last = g.columns[g.columns.length - 1];\n const cell = headerCells.find((c) => c.getAttribute('data-field') === last.field);\n if (cell) cell.classList.add('group-end');\n }\n}\n\n/**\n * Build the group header row element.\n *\n * @param groups - The computed column groups\n * @param columns - The column configurations (final array including any plugin-added columns)\n * @returns The group header row element, or null if no groups\n */\nexport function buildGroupHeaderRow(groups: ColumnGroup[], columns: ColumnConfig[]): HTMLElement | null {\n if (groups.length === 0) return null;\n\n const groupRow = document.createElement('div');\n groupRow.className = 'header-group-row';\n groupRow.setAttribute('role', 'row');\n\n for (const g of groups) {\n // Always compute start index from the current columns array, not stored firstIndex.\n // This accounts for plugin-added columns (e.g., expander) that weren't present\n // when the groups were initially computed during processColumns.\n const firstGroupCol = g.columns[0];\n const startIndex = firstGroupCol ? columns.findIndex((c) => c.field === firstGroupCol.field) : -1;\n if (startIndex === -1) continue; // Group columns not in final column list\n\n const isImplicit = String(g.id).startsWith('__implicit__');\n const label = isImplicit ? '' : g.label || g.id;\n\n const cell = document.createElement('div');\n cell.className = 'cell header-group-cell';\n if (isImplicit) cell.classList.add('implicit-group');\n cell.setAttribute('data-group', String(g.id));\n cell.style.gridColumn = `${startIndex + 1} / span ${g.columns.length}`;\n cell.textContent = label;\n groupRow.appendChild(cell);\n }\n\n return groupRow;\n}\n\n/**\n * Check if any columns have group configuration.\n *\n * @param columns - The column configurations\n * @returns True if at least one column has a group\n */\nexport function hasColumnGroups(columns: ColumnConfig[]): boolean {\n return columns.some((col) => col.group != null);\n}\n\n/**\n * Get group ID for a specific column.\n *\n * @param column - The column configuration\n * @returns The group ID, or undefined if not grouped\n */\nexport function getColumnGroupId(column: ColumnConfig): string | undefined {\n const g = column.group;\n if (!g) return undefined;\n return typeof g === 'string' ? g : g.id;\n}\n","/**\n * Column Groups Plugin (Class-based)\n *\n * Enables multi-level column header grouping.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { AfterCellRenderContext, PluginManifest, PluginQuery } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport type { ColumnGroupInfo } from '../visibility/types';\nimport {\n applyGroupedHeaderCellClasses,\n buildGroupHeaderRow,\n computeColumnGroups,\n hasColumnGroups,\n} from './grouping-columns';\nimport styles from './grouping-columns.css?inline';\nimport type { ColumnGroup, GroupingColumnsConfig } from './types';\n\n/**\n * Column Grouping Plugin for tbw-grid\n *\n * Enables visual grouping of columns under shared headers. Supports two approaches:\n * declarative `columnGroups` at the grid level, or inline `group` property on columns.\n *\n * ## Installation\n *\n * ```ts\n * import { GroupingColumnsPlugin } from '@toolbox-web/grid/plugins/grouping-columns';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `showGroupBorders` | `boolean` | `true` | Show borders between groups |\n * | `groupHeaderRenderer` | `function` | - | Custom renderer for group header content |\n *\n * ## Grid Config: `columnGroups`\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `id` | `string` | Unique group identifier |\n * | `header` | `string` | Display label for the group header |\n * | `children` | `string[]` | Array of column field names in this group |\n *\n * ## Column Config: `group`\n *\n * | Type | Description |\n * |------|-------------|\n * | `string` | Simple group ID (used as both id and label) |\n * | `{ id: string; label?: string }` | Group object with explicit id and optional label |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `isGroupingActive` | `() => boolean` | Check if grouping is active |\n * | `getGroups` | `() => ColumnGroup[]` | Get all computed groups |\n * | `getGroupColumns` | `(groupId) => ColumnConfig[]` | Get columns in a specific group |\n * | `refresh` | `() => void` | Force refresh of column groups |\n *\n * @example Declarative columnGroups (Recommended)\n * ```ts\n * import '@toolbox-web/grid';\n * import { GroupingColumnsPlugin } from '@toolbox-web/grid/plugins/grouping-columns';\n *\n * grid.gridConfig = {\n * columnGroups: [\n * { id: 'personal', header: 'Personal Info', children: ['firstName', 'lastName', 'email'] },\n * { id: 'work', header: 'Work Info', children: ['department', 'title', 'salary'] },\n * ],\n * columns: [\n * { field: 'firstName', header: 'First Name' },\n * { field: 'lastName', header: 'Last Name' },\n * // ...\n * ],\n * plugins: [new GroupingColumnsPlugin()],\n * };\n * ```\n *\n * @example Inline group Property\n * ```ts\n * grid.gridConfig = {\n * columns: [\n * { field: 'firstName', header: 'First Name', group: { id: 'personal', label: 'Personal Info' } },\n * { field: 'lastName', header: 'Last Name', group: 'personal' }, // string shorthand\n * ],\n * plugins: [new GroupingColumnsPlugin()],\n * };\n * ```\n *\n * @see {@link GroupingColumnsConfig} for all configuration options\n * @see {@link ColumnGroup} for the group structure\n * @see {@link ReorderPlugin} for drag-to-reorder within groups\n *\n * @internal Extends BaseGridPlugin\n */\nexport class GroupingColumnsPlugin extends BaseGridPlugin<GroupingColumnsConfig> {\n /**\n * Plugin manifest - declares owned properties for configuration validation.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n ownedProperties: [\n {\n property: 'group',\n level: 'column',\n description: 'the \"group\" column property',\n },\n {\n property: 'columnGroups',\n level: 'config',\n description: 'the \"columnGroups\" config property',\n isUsed: (v) => Array.isArray(v) && v.length > 0,\n },\n ],\n queries: [{ type: 'getColumnGrouping', description: 'Returns column group metadata for the visibility panel' }],\n };\n\n /** @internal */\n readonly name = 'groupingColumns';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<GroupingColumnsConfig> {\n return {\n showGroupBorders: true,\n lockGroupOrder: false,\n };\n }\n\n // #region Internal State\n private groups: ColumnGroup[] = [];\n private isActive = false;\n /** Fields that are the last column in a group (for group-end border class). */\n #groupEndFields = new Set<string>();\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Listen for cancelable column-move events to enforce group contiguity\n (grid as unknown as HTMLElement).addEventListener('column-move', this.#onColumnMove, {\n signal: this.disconnectSignal,\n });\n }\n\n /** @internal */\n override detach(): void {\n this.groups = [];\n this.isActive = false;\n this.#groupEndFields.clear();\n }\n\n // #region Column Move Guard\n\n /**\n * Handle the cancelable column-move event.\n * - When lockGroupOrder is enabled, prevents moves that would break group contiguity.\n * - Always refreshes #groupEndFields after a successful move so that afterCellRender\n * applies group-end borders to the correct (reordered) last column.\n */\n #onColumnMove = (e: Event): void => {\n if (!this.isActive) return;\n\n const event = e as CustomEvent<{ field: string; columnOrder: string[] }>;\n const { field, columnOrder } = event.detail;\n\n if (this.config.lockGroupOrder) {\n // Check ALL explicit groups — moving any column (grouped or not) could break contiguity\n for (const group of this.groups) {\n if (group.id.startsWith('__implicit__')) continue;\n if (!this.#isGroupContiguous(group, columnOrder)) {\n event.preventDefault();\n this.#flashHeaderCell(field);\n return;\n }\n }\n }\n\n // Recompute group-end fields based on proposed column order.\n // setColumnOrder runs synchronously after this handler returns,\n // but afterCellRender (which reads #groupEndFields) fires during\n // the subsequent refreshVirtualWindow. Precompute using the\n // proposed columnOrder so the borders are correct immediately.\n this.#recomputeGroupEndFields(columnOrder);\n };\n\n /**\n * Recompute which fields are group-end based on a column order.\n * The last field of each explicit group in the order gets the group-end class.\n */\n #recomputeGroupEndFields(columnOrder: string[]): void {\n this.#groupEndFields.clear();\n // Find the last field of each group (including implicit groups between explicit ones).\n // Skip the very last group overall — no adjacent group follows it, so no separator needed.\n const lastGroupEndField = this.#findLastGroupEndField(columnOrder);\n for (const group of this.groups) {\n const groupFields = new Set(group.columns.map((c) => c.field));\n // Walk the column order in reverse to find the last member of this group\n for (let i = columnOrder.length - 1; i >= 0; i--) {\n if (groupFields.has(columnOrder[i])) {\n const field = columnOrder[i];\n // Don't mark the last group's trailing field — nothing follows it\n if (field !== lastGroupEndField) {\n this.#groupEndFields.add(field);\n }\n break;\n }\n }\n }\n }\n\n /**\n * Find the trailing field of the last group in column order (to exclude from group-end marking).\n */\n #findLastGroupEndField(columnOrder: string[]): string | null {\n if (this.groups.length === 0) return null;\n // Determine which group contains the last field in column order\n for (let i = columnOrder.length - 1; i >= 0; i--) {\n const field = columnOrder[i];\n for (const group of this.groups) {\n if (group.columns.some((c) => c.field === field)) {\n // This group is the last in display order — find its last field\n const groupFields = new Set(group.columns.map((c) => c.field));\n for (let j = columnOrder.length - 1; j >= 0; j--) {\n if (groupFields.has(columnOrder[j])) return columnOrder[j];\n }\n }\n }\n }\n return null;\n }\n\n /**\n * Check if all columns in a group are contiguous in the proposed column order.\n */\n #isGroupContiguous(group: ColumnGroup, columnOrder: string[]): boolean {\n const indices = group.columns\n .map((c) => columnOrder.indexOf(c.field))\n .filter((i) => i !== -1)\n .sort((a, b) => a - b);\n if (indices.length <= 1) return true;\n return indices.length === indices[indices.length - 1] - indices[0] + 1;\n }\n\n /**\n * Flash the header cell with an error color to indicate a blocked move.\n */\n #flashHeaderCell(field: string): void {\n const headerCell = this.gridElement?.querySelector(\n `.header-row [part~=\"header-cell\"][data-field=\"${field}\"]`,\n ) as HTMLElement;\n if (!headerCell) return;\n\n headerCell.style.setProperty('--_flash-color', 'var(--tbw-color-error)');\n headerCell.animate(\n [{ backgroundColor: 'rgba(from var(--_flash-color) r g b / 30%)' }, { backgroundColor: 'transparent' }],\n { duration: 400, easing: 'ease-out' },\n );\n }\n // #endregion\n\n /** @internal */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'getColumnGrouping') {\n return this.#getStableColumnGrouping();\n }\n return undefined;\n }\n\n /**\n * Get stable column grouping info that includes ALL columns (visible and hidden).\n * Used by the visibility panel to maintain group structure regardless of visibility state.\n * Fields within each group are sorted by current display order.\n */\n #getStableColumnGrouping(): ColumnGroupInfo[] {\n let result: ColumnGroupInfo[];\n\n // 1. Prefer declarative columnGroups - always complete, visibility-independent\n const columnGroups = this.grid?.gridConfig?.columnGroups;\n if (columnGroups && Array.isArray(columnGroups) && columnGroups.length > 0) {\n result = columnGroups\n .filter((g) => g.children.length > 0)\n .map((g) => ({\n id: g.id,\n label: g.header,\n fields: [...g.children],\n }));\n } else if (this.isActive && this.groups.length > 0) {\n // 2. If active groups exist from processColumns, use them\n result = this.groups\n .filter((g) => !g.id.startsWith('__implicit__'))\n .map<ColumnGroupInfo>((g) => ({\n id: g.id,\n label: g.label ?? g.id,\n fields: g.columns.map((c) => c.field),\n }));\n\n // Also check hidden columns for inline group properties not in active groups\n const allCols = this.columns as ColumnConfig[];\n for (const col of allCols) {\n if ((col as any).hidden && col.group) {\n const gId = typeof col.group === 'string' ? col.group : col.group.id;\n const gLabel = typeof col.group === 'string' ? col.group : (col.group.label ?? col.group.id);\n const existing = result.find((g) => g.id === gId);\n if (existing) {\n if (!existing.fields.includes(col.field)) existing.fields.push(col.field);\n } else {\n result.push({ id: gId, label: gLabel, fields: [col.field] });\n }\n }\n }\n } else {\n // 3. Fall back: scan ALL columns (including hidden) for inline group properties\n const allCols = this.columns as ColumnConfig[];\n const groupMap = new Map<string, ColumnGroupInfo>();\n for (const col of allCols) {\n if (!col.group) continue;\n const gId = typeof col.group === 'string' ? col.group : col.group.id;\n const gLabel = typeof col.group === 'string' ? col.group : (col.group.label ?? col.group.id);\n const existing = groupMap.get(gId);\n if (existing) {\n if (!existing.fields.includes(col.field)) existing.fields.push(col.field);\n } else {\n groupMap.set(gId, { id: gId, label: gLabel, fields: [col.field] });\n }\n }\n result = Array.from(groupMap.values());\n }\n\n // Sort fields within each group by current display order so consumers\n // (e.g. the visibility panel) render columns in their reordered positions.\n const displayOrder = this.grid?.getColumnOrder();\n if (displayOrder && displayOrder.length > 0) {\n const orderIndex = new Map(displayOrder.map((f, i) => [f, i]));\n for (const group of result) {\n group.fields.sort((a, b) => (orderIndex.get(a) ?? Infinity) - (orderIndex.get(b) ?? Infinity));\n }\n }\n\n return result;\n }\n // #endregion\n\n // #region Static Detection\n\n /**\n * Auto-detect column groups from column configuration.\n * Detects both inline `column.group` properties and declarative `columnGroups` config.\n */\n static detect(rows: readonly any[], config: any): boolean {\n // Check for declarative columnGroups in config\n if (config?.columnGroups && Array.isArray(config.columnGroups) && config.columnGroups.length > 0) {\n return true;\n }\n // Check for inline group properties on columns\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasColumnGroups(columns);\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n // First, check if gridConfig.columnGroups is defined and apply to columns\n const columnGroups = this.grid?.gridConfig?.columnGroups;\n let processedColumns: ColumnConfig[];\n\n if (columnGroups && Array.isArray(columnGroups) && columnGroups.length > 0) {\n // Build a map of field -> group info from the declarative config\n const fieldToGroup = new Map<string, { id: string; label: string }>();\n for (const group of columnGroups) {\n for (const field of group.children) {\n fieldToGroup.set(field, { id: group.id, label: group.header });\n }\n }\n\n // Apply group property to columns that don't already have one\n processedColumns = columns.map((col) => {\n const groupInfo = fieldToGroup.get(col.field);\n if (groupInfo && !col.group) {\n return { ...col, group: groupInfo };\n }\n return col;\n });\n } else {\n processedColumns = [...columns];\n }\n\n // Compute groups from column definitions (now including applied groups)\n const groups = computeColumnGroups(processedColumns);\n\n if (groups.length === 0) {\n this.isActive = false;\n this.groups = [];\n return processedColumns;\n }\n\n this.isActive = true;\n this.groups = groups;\n\n // Pre-compute group-end fields for the afterCellRender hook\n this.#groupEndFields.clear();\n for (const g of groups) {\n const lastCol = g.columns[g.columns.length - 1];\n if (lastCol?.field) {\n this.#groupEndFields.add(lastCol.field);\n }\n }\n\n // Return columns with group info applied\n return processedColumns;\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.isActive) {\n // Remove any existing group header\n const header = this.gridElement?.querySelector('.header');\n const existingGroupRow = header?.querySelector('.header-group-row');\n if (existingGroupRow) existingGroupRow.remove();\n return;\n }\n\n const header = this.gridElement?.querySelector('.header');\n if (!header) return;\n\n // Remove existing group row if present\n const existingGroupRow = header.querySelector('.header-group-row');\n if (existingGroupRow) existingGroupRow.remove();\n\n // Recompute groups from visible columns only (hidden columns have no CSS grid track).\n // This also picks up any plugin-added columns (e.g. expander) that weren't present\n // during processColumns.\n const finalColumns = this.visibleColumns as ColumnConfig[];\n const groups = computeColumnGroups(finalColumns);\n if (groups.length === 0) return;\n\n // Keep #groupEndFields in sync for afterCellRender (covers scheduler-driven renders)\n this.#groupEndFields.clear();\n for (let gi = 0; gi < groups.length; gi++) {\n const g = groups[gi];\n const lastCol = g.columns[g.columns.length - 1];\n // Don't mark the last group — no adjacent group follows it\n if (lastCol?.field && gi < groups.length - 1) {\n this.#groupEndFields.add(lastCol.field);\n }\n }\n\n // Build and insert group header row\n const groupRow = buildGroupHeaderRow(groups, finalColumns);\n if (groupRow) {\n // Toggle border visibility class\n groupRow.classList.toggle('no-borders', !this.config.showGroupBorders);\n\n const headerRow = header.querySelector('.header-row');\n if (headerRow) {\n header.insertBefore(groupRow, headerRow);\n } else {\n header.appendChild(groupRow);\n }\n }\n\n // Apply classes to header cells\n const headerRow = header.querySelector('.header-row') as HTMLElement;\n if (headerRow) {\n // Toggle border visibility on header cells\n headerRow.classList.toggle('no-group-borders', !this.config.showGroupBorders);\n applyGroupedHeaderCellClasses(headerRow, groups, finalColumns);\n }\n }\n\n /**\n * Apply group-end class to individual cells during render and scroll.\n * This is more efficient than querySelectorAll in afterRender and ensures\n * cells recycled during scroll also get the class applied.\n * @internal\n */\n override afterCellRender(context: AfterCellRenderContext): void {\n if (!this.isActive || !this.config.showGroupBorders) return;\n context.cellElement.classList.toggle('group-end', this.#groupEndFields.has(context.column.field));\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Check if column groups are active.\n * @returns Whether grouping is active\n */\n isGroupingActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Get the computed column groups.\n * @returns Array of column groups\n */\n getGroups(): ColumnGroup[] {\n return this.groups;\n }\n\n /**\n * Get columns in a specific group.\n * @param groupId - The group ID to find\n * @returns Array of columns in the group\n */\n getGroupColumns(groupId: string): ColumnConfig[] {\n const group = this.groups.find((g) => g.id === groupId);\n return group ? group.columns : [];\n }\n\n /**\n * Refresh column groups (recompute from current columns).\n */\n refresh(): void {\n this.requestRender();\n }\n // #endregion\n}\n","/**\n * Row Grouping Core Logic\n *\n * Pure functions for building grouped row models and aggregations.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { DefaultExpandedValue, GroupRowModelItem, RenderRow, RowGroupingConfig } from './types';\n\n// Re-export aggregator functions from core for backward compatibility\nexport { getAggregator, listAggregators, registerAggregator, runAggregator } from '../../core/internal/aggregators';\n\ninterface GroupNode {\n key: string; // composite key\n value: any;\n depth: number;\n rows: any[];\n children: Map<string, GroupNode>;\n parent?: GroupNode;\n}\n\ninterface BuildGroupingArgs {\n rows: any[];\n config: RowGroupingConfig;\n expanded: Set<string>;\n /** Initial expanded state to apply (processed by the plugin) */\n initialExpanded?: Set<string>;\n}\n\n/**\n * Build a flattened grouping projection (collapsed by default).\n * Returns empty array when groupOn not configured or all rows ungrouped.\n *\n * @param args - The grouping arguments\n * @returns Flattened array of render rows (groups + data rows)\n */\nexport function buildGroupedRowModel({ rows, config, expanded, initialExpanded }: BuildGroupingArgs): RenderRow[] {\n const groupOn = config.groupOn;\n if (typeof groupOn !== 'function') {\n return [];\n }\n\n const root: GroupNode = { key: '__root__', value: null, depth: -1, rows: [], children: new Map() };\n\n // Build tree structure\n rows.forEach((r) => {\n let path: any = groupOn(r);\n if (path == null || path === false) path = ['__ungrouped__'];\n else if (!Array.isArray(path)) path = [path];\n\n let parent = root;\n path.forEach((rawVal: any, depthIdx: number) => {\n const seg = rawVal == null ? '∅' : String(rawVal);\n const composite = parent.key === '__root__' ? seg : parent.key + '||' + seg;\n let node = parent.children.get(seg);\n if (!node) {\n node = { key: composite, value: rawVal, depth: depthIdx, rows: [], children: new Map(), parent };\n parent.children.set(seg, node);\n }\n parent = node;\n });\n parent.rows.push(r);\n });\n\n // All ungrouped? treat as no grouping\n if (root.children.size === 1 && root.children.has('__ungrouped__')) {\n const only = root.children.get('__ungrouped__')!;\n if (only.rows.length === rows.length) return [];\n }\n\n // Merge expanded sets - use initialExpanded on first render, then expanded takes over\n const effectiveExpanded = new Set([...expanded, ...(initialExpanded ?? [])]);\n\n // Flatten tree to array\n const flat: RenderRow[] = [];\n const visit = (node: GroupNode) => {\n if (node === root) {\n node.children.forEach((c) => visit(c));\n return;\n }\n\n const isExpanded = effectiveExpanded.has(node.key);\n flat.push({\n kind: 'group',\n key: node.key,\n value: node.value,\n depth: node.depth,\n rows: node.rows,\n expanded: isExpanded,\n });\n\n if (isExpanded) {\n if (node.children.size) {\n node.children.forEach((c) => visit(c));\n } else {\n node.rows.forEach((r) => flat.push({ kind: 'data', row: r, rowIndex: rows.indexOf(r) }));\n }\n }\n };\n visit(root);\n\n return flat;\n}\n\n/**\n * Toggle expansion state for a group key.\n *\n * @param expandedKeys - Current set of expanded keys\n * @param key - The group key to toggle\n * @returns New set with toggled state\n */\nexport function toggleGroupExpansion(expandedKeys: Set<string>, key: string): Set<string> {\n const newSet = new Set(expandedKeys);\n if (newSet.has(key)) {\n newSet.delete(key);\n } else {\n newSet.add(key);\n }\n return newSet;\n}\n\n/**\n * Expand all groups.\n *\n * @param rows - The flattened render rows\n * @returns Set of all group keys\n */\nexport function expandAllGroups(rows: RenderRow[]): Set<string> {\n const keys = new Set<string>();\n for (const row of rows) {\n if (row.kind === 'group') {\n keys.add(row.key);\n }\n }\n return keys;\n}\n\n/**\n * Collapse all groups.\n *\n * @returns Empty set\n */\nexport function collapseAllGroups(): Set<string> {\n return new Set();\n}\n\n/**\n * Resolve a defaultExpanded value to a set of keys to expand.\n * This needs to be called AFTER building the group model to get all keys.\n *\n * @param value - The defaultExpanded config value\n * @param allGroupKeys - All group keys from the model\n * @returns Set of keys to expand initially\n */\nexport function resolveDefaultExpanded(value: DefaultExpandedValue, allGroupKeys: string[]): Set<string> {\n if (value === true) {\n // Expand all groups\n return new Set(allGroupKeys);\n }\n if (value === false || value == null) {\n // Collapse all groups\n return new Set();\n }\n if (typeof value === 'number') {\n // Expand group at this index\n const key = allGroupKeys[value];\n return key ? new Set([key]) : new Set();\n }\n if (typeof value === 'string') {\n // Expand group with this key\n return new Set([value]);\n }\n if (Array.isArray(value)) {\n // Expand groups with these keys\n return new Set(value);\n }\n return new Set();\n}\n\n/**\n * Get all group keys from a flattened model.\n *\n * @param rows - The flattened render rows\n * @returns Array of group keys\n */\nexport function getGroupKeys(rows: RenderRow[]): string[] {\n return rows.filter((r): r is GroupRowModelItem => r.kind === 'group').map((r) => r.key);\n}\n\n/**\n * Count total rows in a group (including nested groups).\n *\n * @param groupRow - The group row\n * @returns Total row count\n */\nexport function getGroupRowCount(groupRow: RenderRow): number {\n if (groupRow.kind !== 'group') return 0;\n return groupRow.rows.length;\n}\n","/**\n * Row Grouping Plugin (Class-based)\n *\n * Enables hierarchical row grouping with expand/collapse and aggregations.\n */\n\nimport { BaseGridPlugin, CellClickEvent, type PluginManifest, type PluginQuery } from '../../core/plugin/base-plugin';\nimport { isExpanderColumn } from '../../core/plugin/expander-column';\nimport type { RowElementInternal } from '../../core/types';\nimport {\n buildGroupedRowModel,\n collapseAllGroups,\n expandAllGroups,\n getGroupKeys,\n getGroupRowCount,\n resolveDefaultExpanded,\n runAggregator,\n toggleGroupExpansion,\n} from './grouping-rows';\nimport styles from './grouping-rows.css?inline';\nimport type {\n ExpandCollapseAnimation,\n GroupingRowsConfig,\n GroupRowModelItem,\n GroupToggleDetail,\n RenderRow,\n} from './types';\n\n/**\n * Group state information returned by getGroupState()\n */\nexport interface GroupState {\n /** Whether grouping is currently active */\n isActive: boolean;\n /** Number of expanded groups */\n expandedCount: number;\n /** Total number of groups */\n totalGroups: number;\n /** Array of expanded group keys */\n expandedKeys: string[];\n}\n\n/**\n * Row Grouping Plugin for tbw-grid\n *\n * Organizes rows into collapsible hierarchical groups. Perfect for organizing data\n * by category, department, status, or any other dimension—or even multiple dimensions\n * for nested grouping. Includes aggregation support for summarizing group data.\n *\n * ## Installation\n *\n * ```ts\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `groupOn` | `(row) => string[]` | - | Callback returning group path array |\n * | `defaultExpanded` | `boolean \\\\| number \\\\| string \\\\| string[]` | `false` | Initial expanded state |\n * | `showRowCount` | `boolean` | `true` | Show row count in group header |\n * | `indentWidth` | `number` | `20` | Indentation per level (pixels) |\n * | `fullWidth` | `boolean` | `true` | Group row spans full width |\n * | `animation` | `false \\\\| 'slide' \\\\| 'fade'` | `'slide'` | Expand/collapse animation |\n * | `accordion` | `boolean` | `false` | Only one group open at a time |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `expandGroup` | `(path: string[]) => void` | Expand a specific group |\n * | `collapseGroup` | `(path: string[]) => void` | Collapse a specific group |\n * | `expandAll` | `() => void` | Expand all groups |\n * | `collapseAll` | `() => void` | Collapse all groups |\n * | `isGroupExpanded` | `(path: string[]) => boolean` | Check if group is expanded |\n * | `getGroupState` | `() => GroupState` | Get current grouping state |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-group-indent-width` | `1.25em` | Indentation per group level |\n * | `--tbw-grouping-rows-bg` | `var(--tbw-color-panel-bg)` | Group row background |\n * | `--tbw-grouping-rows-count-color` | `var(--tbw-color-fg-muted)` | Count badge color |\n * | `--tbw-animation-duration` | `200ms` | Expand/collapse animation |\n *\n * @example Single-Level Grouping by Department\n * ```ts\n * import '@toolbox-web/grid';\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Employee' },\n * { field: 'department', header: 'Department' },\n * { field: 'salary', header: 'Salary', type: 'currency' },\n * ],\n * plugins: [\n * new GroupingRowsPlugin({\n * groupOn: (row) => [row.department],\n * showRowCount: true,\n * defaultExpanded: false,\n * }),\n * ],\n * };\n * ```\n *\n * @example Multi-Level Grouping\n * ```ts\n * new GroupingRowsPlugin({\n * groupOn: (row) => [row.region, row.department, row.team],\n * indentWidth: 24,\n * animation: 'slide',\n * })\n * ```\n *\n * @see {@link GroupingRowsConfig} for all configuration options\n * @see {@link GroupState} for the group state structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class GroupingRowsPlugin extends BaseGridPlugin<GroupingRowsConfig> {\n /**\n * Plugin manifest - declares configuration validation rules and events.\n * @internal\n */\n static override readonly manifest: PluginManifest<GroupingRowsConfig> = {\n events: [\n {\n type: 'grouping-state-change',\n description: 'Emitted when groups are expanded/collapsed. Subscribers can react to row visibility changes.',\n },\n ],\n queries: [\n {\n type: 'canMoveRow',\n description: 'Returns false for group header rows (cannot be reordered)',\n },\n ],\n configRules: [\n {\n id: 'groupingRows/accordion-defaultExpanded',\n severity: 'warn',\n message:\n `\"accordion: true\" and \"defaultExpanded\" (non-false) are used together.\\n` +\n ` → In accordion mode, only one group can be open at a time.\\n` +\n ` → Using defaultExpanded with multiple groups will collapse to one on first toggle.\\n` +\n ` → Consider using \"defaultExpanded: false\" or a single group key/index with accordion mode.`,\n check: (config) =>\n config.accordion === true &&\n config.defaultExpanded !== false &&\n config.defaultExpanded !== undefined &&\n // Allow single group expansion with accordion\n !(typeof config.defaultExpanded === 'number') &&\n !(typeof config.defaultExpanded === 'string') &&\n // Warn if true or array with multiple items\n (config.defaultExpanded === true ||\n (Array.isArray(config.defaultExpanded) && config.defaultExpanded.length > 1)),\n },\n ],\n };\n\n /** @internal */\n readonly name = 'groupingRows';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<GroupingRowsConfig> {\n return {\n defaultExpanded: false,\n showRowCount: true,\n indentWidth: 20,\n aggregators: {},\n animation: 'slide',\n accordion: false,\n };\n }\n\n // #region Internal State\n private expandedKeys: Set<string> = new Set();\n private flattenedRows: RenderRow[] = [];\n private isActive = false;\n private previousVisibleKeys = new Set<string>();\n private keysToAnimate = new Set<string>();\n /** Track if initial defaultExpanded has been applied */\n private hasAppliedDefaultExpanded = false;\n // #endregion\n\n // #region Animation\n\n /**\n * Get expand/collapse animation style from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide';\n }\n\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.expandedKeys.clear();\n this.flattenedRows = [];\n this.isActive = false;\n this.previousVisibleKeys.clear();\n this.keysToAnimate.clear();\n this.hasAppliedDefaultExpanded = false;\n }\n\n /**\n * Provide row height for group header rows.\n *\n * If `groupRowHeight` is configured, returns that value for group rows.\n * This allows the variable row height system to use known heights for\n * group headers without needing to measure them from the DOM.\n *\n * @param row - The row object (may be a group row)\n * @param _index - Index in the processed rows array (unused)\n * @returns Height in pixels for group rows, undefined for data rows\n *\n * @internal Plugin hook for variable row height support\n */\n override getRowHeight(row: unknown, _index: number): number | undefined {\n // Only provide height if groupRowHeight is configured\n if (this.config.groupRowHeight == null) return undefined;\n\n // Check if this is a group row\n if ((row as { __isGroupRow?: boolean }).__isGroupRow === true) {\n return this.config.groupRowHeight;\n }\n\n return undefined;\n }\n\n /**\n * Handle plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'canMoveRow') {\n // Group header rows cannot be reordered\n const row = query.context as { __isGroupRow?: boolean } | null | undefined;\n if (row?.__isGroupRow === true) {\n return false;\n }\n }\n return undefined;\n }\n // #endregion\n\n // #region Hooks\n\n /**\n * Auto-detect grouping configuration from grid config.\n * Called by plugin system to determine if plugin should activate.\n */\n static detect(rows: readonly any[], config: any): boolean {\n return typeof config?.groupOn === 'function' || typeof config?.enableRowGrouping === 'boolean';\n }\n\n /** @internal */\n override processRows(rows: readonly any[]): any[] {\n const config = this.config;\n\n // Check if grouping is configured\n if (typeof config.groupOn !== 'function') {\n this.isActive = false;\n this.flattenedRows = [];\n return [...rows];\n }\n\n // First build: get structure to know all group keys\n // (needed for index-based defaultExpanded)\n const initialBuild = buildGroupedRowModel({\n rows: [...rows],\n config: config,\n expanded: new Set(), // Empty to get all root groups\n });\n\n // If no grouping produced, return original rows\n if (initialBuild.length === 0) {\n this.isActive = false;\n this.flattenedRows = [];\n return [...rows];\n }\n\n // Resolve defaultExpanded on first render only\n let initialExpanded: Set<string> | undefined;\n if (!this.hasAppliedDefaultExpanded && this.expandedKeys.size === 0 && config.defaultExpanded !== false) {\n const allKeys = getGroupKeys(initialBuild);\n initialExpanded = resolveDefaultExpanded(config.defaultExpanded ?? false, allKeys);\n\n // Mark as applied and populate expandedKeys for subsequent toggles\n if (initialExpanded.size > 0) {\n this.expandedKeys = new Set(initialExpanded);\n this.hasAppliedDefaultExpanded = true;\n }\n }\n\n // Build with proper expanded state\n const grouped = buildGroupedRowModel({\n rows: [...rows],\n config: config,\n expanded: this.expandedKeys,\n initialExpanded,\n });\n\n this.isActive = true;\n this.flattenedRows = grouped;\n\n // Track which data rows are newly visible (for animation)\n this.keysToAnimate.clear();\n const currentVisibleKeys = new Set<string>();\n grouped.forEach((item, idx) => {\n if (item.kind === 'data') {\n const key = `data-${idx}`;\n currentVisibleKeys.add(key);\n if (!this.previousVisibleKeys.has(key)) {\n this.keysToAnimate.add(key);\n }\n }\n });\n this.previousVisibleKeys = currentVisibleKeys;\n\n // Return flattened rows for rendering\n // The grid will need to handle group rows specially\n return grouped.map((item) => {\n if (item.kind === 'group') {\n return {\n __isGroupRow: true,\n __groupKey: item.key,\n __groupValue: item.value,\n __groupDepth: item.depth,\n __groupRows: item.rows,\n __groupExpanded: item.expanded,\n __groupRowCount: getGroupRowCount(item),\n // Cache key for variable row height support - survives expand/collapse\n __rowCacheKey: `group:${item.key}`,\n };\n }\n return item.row;\n });\n }\n\n /** @internal */\n override onCellClick(event: CellClickEvent): boolean | void {\n const row = event.row as Record<string, unknown> | undefined;\n\n // Check if this is a group row toggle\n if (row?.__isGroupRow) {\n const target = event.originalEvent.target as HTMLElement;\n if (target?.closest('.group-toggle')) {\n this.toggle(row.__groupKey as string);\n return true; // Prevent default\n }\n }\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion on group rows\n if (event.key !== ' ') return;\n\n const focusRow = this.grid._focusRow;\n const row = this.rows[focusRow] as Record<string, unknown> | undefined;\n\n // Only handle SPACE on group rows\n if (!row?.__isGroupRow) return;\n\n event.preventDefault();\n this.toggle(row.__groupKey as string);\n\n // Restore focus styling after render completes via render pipeline\n this.requestRenderWithFocus();\n return true;\n }\n\n /**\n * Render a row. Returns true if we handled the row (group row), false otherwise.\n * @internal\n */\n override renderRow(row: any, rowEl: HTMLElement, _rowIndex: number): boolean {\n // Only handle group rows\n if (!row?.__isGroupRow) {\n return false;\n }\n\n const config = this.config;\n\n // If a custom renderer is provided, use it\n if (config.groupRowRenderer) {\n const toggleExpand = () => {\n this.toggle(row.__groupKey);\n };\n\n const result = config.groupRowRenderer({\n key: row.__groupKey,\n value: row.__groupValue,\n depth: row.__groupDepth,\n rows: row.__groupRows,\n expanded: row.__groupExpanded,\n toggleExpand,\n });\n\n if (result) {\n rowEl.className = 'data-grid-row group-row';\n (rowEl as RowElementInternal).__isCustomRow = true; // Mark for proper class reset on recycle\n rowEl.setAttribute('data-group-depth', String(row.__groupDepth));\n if (typeof result === 'string') {\n rowEl.innerHTML = result;\n } else {\n rowEl.innerHTML = '';\n rowEl.appendChild(result);\n }\n return true;\n }\n }\n\n // Helper to toggle expansion\n const handleToggle = () => {\n this.toggle(row.__groupKey);\n };\n\n // Default group row rendering - keep data-grid-row class for focus/keyboard navigation\n rowEl.className = 'data-grid-row group-row';\n (rowEl as RowElementInternal).__isCustomRow = true; // Mark for proper class reset on recycle\n rowEl.setAttribute('data-group-depth', String(row.__groupDepth));\n rowEl.setAttribute('role', 'row');\n rowEl.setAttribute('aria-expanded', String(row.__groupExpanded));\n // Use CSS variable for depth-based indentation\n rowEl.style.setProperty('--tbw-group-depth', String(row.__groupDepth || 0));\n if (config.indentWidth !== undefined) {\n rowEl.style.setProperty('--tbw-group-indent-width', `${config.indentWidth}px`);\n }\n // Clear any inline height from previous use (e.g., responsive card mode sets height: auto)\n // This ensures group rows use CSS-defined height, not stale inline styles from recycled elements\n rowEl.style.height = '';\n rowEl.innerHTML = '';\n\n const isFullWidth = config.fullWidth !== false; // default true\n\n if (isFullWidth) {\n this.renderFullWidthGroupRow(row, rowEl, handleToggle);\n } else {\n this.renderPerColumnGroupRow(row, rowEl, handleToggle);\n }\n\n return true;\n }\n\n /** @internal */\n override afterRender(): void {\n const style = this.animationStyle;\n if (style === false || this.keysToAnimate.size === 0) return;\n\n const body = this.gridElement?.querySelector('.rows');\n if (!body) return;\n\n const animClass = style === 'fade' ? 'tbw-group-fade-in' : 'tbw-group-slide-in';\n for (const rowEl of body.querySelectorAll('.data-grid-row:not(.group-row)')) {\n const cell = rowEl.querySelector('.cell[data-row]');\n const idx = cell ? parseInt(cell.getAttribute('data-row') ?? '-1', 10) : -1;\n const item = this.flattenedRows[idx];\n const key = item?.kind === 'data' ? `data-${idx}` : undefined;\n\n if (key && this.keysToAnimate.has(key)) {\n rowEl.classList.add(animClass);\n rowEl.addEventListener('animationend', () => rowEl.classList.remove(animClass), { once: true });\n }\n }\n this.keysToAnimate.clear();\n }\n // #endregion\n\n // #region Private Rendering Helpers\n\n /**\n * Create a toggle button for expanding/collapsing a group.\n */\n private createToggleButton(expanded: boolean, handleToggle: () => void): HTMLButtonElement {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = `group-toggle${expanded ? ' expanded' : ''}`;\n btn.setAttribute('aria-label', expanded ? 'Collapse group' : 'Expand group');\n this.setIcon(btn, this.resolveIcon(expanded ? 'collapse' : 'expand'));\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n handleToggle();\n });\n return btn;\n }\n\n /**\n * Get the formatted label text for a group.\n */\n private getGroupLabelText(value: unknown, depth: number, key: string): string {\n const config = this.config;\n return config.formatLabel ? config.formatLabel(value, depth, key) : String(value);\n }\n\n private renderFullWidthGroupRow(row: any, rowEl: HTMLElement, handleToggle: () => void): void {\n const config = this.config;\n const aggregators = config.aggregators ?? {};\n const groupRows = row.__groupRows ?? [];\n\n // Full-width mode: single spanning cell with toggle + label + count + aggregates\n const cell = document.createElement('div');\n cell.className = 'cell group-full';\n cell.style.gridColumn = '1 / -1';\n cell.setAttribute('role', 'gridcell');\n cell.setAttribute('data-col', '0'); // Required for focus/click delegation\n\n // Toggle button\n cell.appendChild(this.createToggleButton(row.__groupExpanded, handleToggle));\n\n // Group label\n const label = document.createElement('span');\n label.className = 'group-label';\n label.textContent = this.getGroupLabelText(row.__groupValue, row.__groupDepth || 0, row.__groupKey);\n cell.appendChild(label);\n\n // Row count\n if (config.showRowCount !== false) {\n const count = document.createElement('span');\n count.className = 'group-count';\n count.textContent = `(${row.__groupRowCount ?? row.__groupRows?.length ?? 0})`;\n cell.appendChild(count);\n }\n\n // Render aggregates if configured\n const aggregatorEntries = Object.entries(aggregators);\n if (aggregatorEntries.length > 0) {\n const aggregatesContainer = document.createElement('span');\n aggregatesContainer.className = 'group-aggregates';\n\n for (const [field, aggRef] of aggregatorEntries) {\n const col = this.columns.find((c) => c.field === field);\n const result = runAggregator(aggRef, groupRows, field, col);\n if (result != null) {\n const aggSpan = document.createElement('span');\n aggSpan.className = 'group-aggregate';\n aggSpan.setAttribute('data-field', field);\n // Use column header as label if available\n const colHeader = col?.header ?? field;\n aggSpan.textContent = `${colHeader}: ${result}`;\n aggregatesContainer.appendChild(aggSpan);\n }\n }\n\n if (aggregatesContainer.children.length > 0) {\n cell.appendChild(aggregatesContainer);\n }\n }\n\n rowEl.appendChild(cell);\n }\n\n private renderPerColumnGroupRow(row: any, rowEl: HTMLElement, handleToggle: () => void): void {\n const config = this.config;\n const aggregators = config.aggregators ?? {};\n const columns = this.columns;\n const groupRows = row.__groupRows ?? [];\n\n // Get grid template from the grid element\n const bodyEl = this.gridElement?.querySelector('.body') as HTMLElement | null;\n const gridTemplate = bodyEl?.style.gridTemplateColumns || '';\n if (gridTemplate) {\n rowEl.style.display = 'grid';\n rowEl.style.gridTemplateColumns = gridTemplate;\n }\n\n // Track whether we've rendered the toggle button yet (should be in first non-expander column)\n let toggleRendered = false;\n\n columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell group-cell';\n cell.setAttribute('data-col', String(colIdx));\n cell.setAttribute('role', 'gridcell');\n\n // Skip expander columns (they're handled by other plugins like MasterDetail/Tree)\n // but still render an empty cell to maintain grid structure\n if (isExpanderColumn(col)) {\n cell.setAttribute('data-field', col.field);\n rowEl.appendChild(cell);\n return;\n }\n\n // First non-expander column gets the toggle button + label\n if (!toggleRendered) {\n toggleRendered = true;\n cell.appendChild(this.createToggleButton(row.__groupExpanded, handleToggle));\n\n const label = document.createElement('span');\n const firstColAgg = aggregators[col.field];\n if (firstColAgg) {\n const aggResult = runAggregator(firstColAgg, groupRows, col.field, col);\n label.textContent = aggResult != null ? String(aggResult) : String(row.__groupValue);\n } else {\n label.textContent = this.getGroupLabelText(row.__groupValue, row.__groupDepth || 0, row.__groupKey);\n }\n cell.appendChild(label);\n\n if (config.showRowCount !== false) {\n const count = document.createElement('span');\n count.className = 'group-count';\n count.textContent = ` (${groupRows.length})`;\n cell.appendChild(count);\n }\n } else {\n // Other columns: run aggregator if defined\n const aggRef = aggregators[col.field];\n if (aggRef) {\n const result = runAggregator(aggRef, groupRows, col.field, col);\n cell.textContent = result != null ? String(result) : '';\n } else {\n cell.textContent = '';\n }\n }\n\n rowEl.appendChild(cell);\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Expand all groups.\n */\n expandAll(): void {\n this.expandedKeys = expandAllGroups(this.flattenedRows);\n this.emitPluginEvent('grouping-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n /**\n * Collapse all groups.\n */\n collapseAll(): void {\n this.expandedKeys = collapseAllGroups();\n this.emitPluginEvent('grouping-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n /**\n * Toggle expansion of a specific group.\n * In accordion mode, expanding a group will collapse all sibling groups.\n * @param key - The group key to toggle\n */\n toggle(key: string): void {\n const isExpanding = !this.expandedKeys.has(key);\n const config = this.config;\n\n // Find the group to get its depth for accordion mode\n const group = this.flattenedRows.find((r) => r.kind === 'group' && r.key === key) as GroupRowModelItem | undefined;\n\n // In accordion mode, collapse sibling groups when expanding\n if (config.accordion && isExpanding && group) {\n const newKeys = new Set<string>();\n // Keep only ancestors (keys that are prefixes of the current key) and the current key\n for (const existingKey of this.expandedKeys) {\n // Check if existingKey is an ancestor of the toggled key\n // Ancestors have composite keys that are prefixes of child keys (separated by '||')\n if (key.startsWith(existingKey + '||') || existingKey.startsWith(key + '||')) {\n // This is an ancestor or descendant - keep it only if ancestor\n if (key.startsWith(existingKey + '||')) {\n newKeys.add(existingKey);\n }\n } else {\n // Check depth - only keep groups at different depths\n const existingGroup = this.flattenedRows.find((r) => r.kind === 'group' && r.key === existingKey) as\n | GroupRowModelItem\n | undefined;\n if (existingGroup && existingGroup.depth !== group.depth) {\n newKeys.add(existingKey);\n }\n }\n }\n newKeys.add(key);\n this.expandedKeys = newKeys;\n } else {\n this.expandedKeys = toggleGroupExpansion(this.expandedKeys, key);\n }\n\n this.emit<GroupToggleDetail>('group-toggle', {\n key,\n expanded: this.expandedKeys.has(key),\n value: group?.value,\n depth: group?.depth ?? 0,\n });\n\n // Notify other plugins that grouping state changed (row visibility changed)\n this.emitPluginEvent('grouping-state-change', {\n expandedKeys: [...this.expandedKeys],\n });\n\n this.requestRender();\n }\n\n /**\n * Check if a specific group is expanded.\n * @param key - The group key to check\n * @returns Whether the group is expanded\n */\n isExpanded(key: string): boolean {\n return this.expandedKeys.has(key);\n }\n\n /**\n * Expand a specific group.\n * @param key - The group key to expand\n */\n expand(key: string): void {\n if (!this.expandedKeys.has(key)) {\n this.expandedKeys = new Set([...this.expandedKeys, key]);\n this.requestRender();\n }\n }\n\n /**\n * Collapse a specific group.\n * @param key - The group key to collapse\n */\n collapse(key: string): void {\n if (this.expandedKeys.has(key)) {\n const newKeys = new Set(this.expandedKeys);\n newKeys.delete(key);\n this.expandedKeys = newKeys;\n this.requestRender();\n }\n }\n\n /**\n * Get the current group state.\n * @returns Group state information\n */\n getGroupState(): GroupState {\n const groupRows = this.flattenedRows.filter((r) => r.kind === 'group');\n return {\n isActive: this.isActive,\n expandedCount: this.expandedKeys.size,\n totalGroups: groupRows.length,\n expandedKeys: [...this.expandedKeys],\n };\n }\n\n /**\n * Get the total count of visible rows (including group headers).\n * @returns Number of visible rows\n */\n getRowCount(): number {\n return this.flattenedRows.length;\n }\n\n /**\n * Refresh the grouped row model.\n * Call this after modifying groupOn or other config options.\n */\n refreshGroups(): void {\n this.requestRender();\n }\n\n /**\n * Get current expanded group keys.\n * @returns Array of expanded group keys\n */\n getExpandedGroups(): string[] {\n return [...this.expandedKeys];\n }\n\n /**\n * Get the flattened row model.\n * @returns Array of render rows (groups + data rows)\n */\n getFlattenedRows(): RenderRow[] {\n return this.flattenedRows;\n }\n\n /**\n * Check if grouping is currently active.\n * @returns Whether grouping is active\n */\n isGroupingActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Set the groupOn function dynamically.\n * @param fn - The groupOn function or undefined to disable\n */\n setGroupOn(fn: ((row: any) => any[] | any | null | false) | undefined): void {\n (this.config as GroupingRowsConfig).groupOn = fn;\n this.requestRender();\n }\n // #endregion\n}\n","/**\n * Master/Detail Core Logic\n *\n * Pure functions for managing detail row expansion state.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n// Uses `any` for maximum flexibility with user-defined row types.\n\n/**\n * Toggle the expansion state of a detail row.\n * Returns a new Set with the updated state.\n */\nexport function toggleDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n if (newExpanded.has(row)) {\n newExpanded.delete(row);\n } else {\n newExpanded.add(row);\n }\n return newExpanded;\n}\n\n/**\n * Expand a detail row.\n * Returns a new Set with the row added.\n */\nexport function expandDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n newExpanded.add(row);\n return newExpanded;\n}\n\n/**\n * Collapse a detail row.\n * Returns a new Set with the row removed.\n */\nexport function collapseDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n newExpanded.delete(row);\n return newExpanded;\n}\n\n/**\n * Check if a detail row is expanded.\n */\nexport function isDetailExpanded(expandedRows: Set<object>, row: object): boolean {\n return expandedRows.has(row);\n}\n\n/**\n * Create a detail element for a given row.\n * The element spans all columns and contains the rendered content.\n */\nexport function createDetailElement(\n row: any,\n rowIndex: number,\n renderer: (row: any, rowIndex: number) => HTMLElement | string,\n columnCount: number\n): HTMLElement {\n const detailRow = document.createElement('div');\n detailRow.className = 'master-detail-row';\n detailRow.setAttribute('data-detail-for', String(rowIndex));\n detailRow.setAttribute('role', 'row');\n\n const detailCell = document.createElement('div');\n detailCell.className = 'master-detail-cell';\n detailCell.setAttribute('role', 'cell');\n detailCell.style.gridColumn = `1 / ${columnCount + 1}`;\n\n const content = renderer(row, rowIndex);\n if (typeof content === 'string') {\n detailCell.innerHTML = content;\n } else if (content instanceof HTMLElement) {\n detailCell.appendChild(content);\n }\n\n detailRow.appendChild(detailCell);\n return detailRow;\n}\n","/**\n * Master/Detail Plugin (Class-based)\n *\n * Enables expandable detail rows showing additional content for each row.\n * Animation style is plugin-configured; respects grid-level animation.mode.\n */\n\nimport { evalTemplateString, sanitizeHTML } from '../../core/internal/sanitize';\nimport { BaseGridPlugin, CellClickEvent, GridElement, RowClickEvent } from '../../core/plugin/base-plugin';\nimport { createExpanderColumnConfig, findExpanderColumn, isExpanderColumn } from '../../core/plugin/expander-column';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n collapseDetailRow,\n createDetailElement,\n expandDetailRow,\n isDetailExpanded,\n toggleDetailRow,\n} from './master-detail';\nimport styles from './master-detail.css?inline';\nimport type { DetailExpandDetail, ExpandCollapseAnimation, MasterDetailConfig } from './types';\n\n/**\n * Master-Detail Plugin for tbw-grid\n *\n * Creates expandable detail rows that reveal additional content beneath each master row.\n * Perfect for order/line-item UIs, employee/department views, or any scenario where\n * you need to show related data without navigating away.\n *\n * ## Installation\n *\n * ```ts\n * import { MasterDetailPlugin } from '@toolbox-web/grid/plugins/master-detail';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `detailRenderer` | `(row) => HTMLElement \\| string` | required | Render function for detail content |\n * | `expandOnRowClick` | `boolean` | `false` | Expand when clicking the row |\n * | `detailHeight` | `number \\| 'auto'` | `'auto'` | Fixed height or auto-size |\n * | `collapseOnClickOutside` | `boolean` | `false` | Collapse when clicking outside |\n * | `showExpandColumn` | `boolean` | `true` | Show expand/collapse column |\n * | `animation` | `false \\| 'slide' \\| 'fade'` | `'slide'` | Animation style |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `expandRow` | `(rowIndex) => void` | Expand a specific row |\n * | `collapseRow` | `(rowIndex) => void` | Collapse a specific row |\n * | `toggleRow` | `(rowIndex) => void` | Toggle row expansion |\n * | `expandAll` | `() => void` | Expand all rows |\n * | `collapseAll` | `() => void` | Collapse all rows |\n * | `isRowExpanded` | `(rowIndex) => boolean` | Check if row is expanded |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-master-detail-bg` | `var(--tbw-color-row-alt)` | Detail row background |\n * | `--tbw-master-detail-border` | `var(--tbw-color-border)` | Detail row border |\n * | `--tbw-detail-padding` | `1em` | Detail content padding |\n * | `--tbw-animation-duration` | `200ms` | Expand/collapse animation |\n *\n * @example Basic Master-Detail with HTML Template\n * ```ts\n * import '@toolbox-web/grid';\n * import { MasterDetailPlugin } from '@toolbox-web/grid/plugins/master-detail';\n *\n * grid.gridConfig = {\n * columns: [\n * { field: 'orderId', header: 'Order ID' },\n * { field: 'customer', header: 'Customer' },\n * { field: 'total', header: 'Total', type: 'currency' },\n * ],\n * plugins: [\n * new MasterDetailPlugin({\n * detailRenderer: (row) => `\n * <div class=\"order-details\">\n * <h4>Order Items</h4>\n * <ul>${row.items.map(i => `<li>${i.name} - $${i.price}</li>`).join('')}</ul>\n * </div>\n * `,\n * }),\n * ],\n * };\n * ```\n *\n * @example Nested Grid in Detail\n * ```ts\n * new MasterDetailPlugin({\n * detailRenderer: (row) => {\n * const childGrid = document.createElement('tbw-grid');\n * childGrid.style.height = '200px';\n * childGrid.gridConfig = { columns: [...] };\n * childGrid.rows = row.items || [];\n * return childGrid;\n * },\n * })\n * ```\n *\n * @see {@link MasterDetailConfig} for all configuration options\n * @see {@link DetailExpandDetail} for expand/collapse event details\n *\n * @internal Extends BaseGridPlugin\n */\nexport class MasterDetailPlugin extends BaseGridPlugin<MasterDetailConfig> {\n /** @internal */\n readonly name = 'masterDetail';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<MasterDetailConfig> {\n return {\n detailHeight: 'auto',\n expandOnRowClick: false,\n collapseOnClickOutside: false,\n // Note: showExpandColumn is intentionally NOT defaulted here.\n // If undefined, processColumns() adds expander only when detailRenderer is provided.\n // Set to true for framework adapters that register renderers asynchronously.\n animation: 'slide', // Plugin's own default\n };\n }\n\n // #region Light DOM Parsing\n\n /**\n * Called when plugin is attached to the grid.\n * Parses light DOM for `<tbw-grid-detail>` elements to configure detail templates.\n * @internal\n */\n override attach(grid: GridElement): void {\n super.attach(grid);\n this.parseLightDomDetail();\n }\n\n /**\n * Parse `<tbw-grid-detail>` elements from the grid's light DOM.\n *\n * Allows declarative configuration:\n * ```html\n * <tbw-grid [rows]=\"data\">\n * <tbw-grid-detail>\n * <div class=\"detail-content\">\n * <p>Name: {{ row.name }}</p>\n * <p>Email: {{ row.email }}</p>\n * </div>\n * </tbw-grid-detail>\n * </tbw-grid>\n * ```\n *\n * Attributes:\n * - `animation`: 'slide' | 'fade' | 'false' (default: 'slide')\n * - `show-expand-column`: 'true' | 'false' (default: 'true')\n * - `expand-on-row-click`: 'true' | 'false' (default: 'false')\n * - `collapse-on-click-outside`: 'true' | 'false' (default: 'false')\n * - `height`: number (pixels) or 'auto' (default: 'auto')\n */\n private parseLightDomDetail(): void {\n const gridEl = this.grid as unknown as Element;\n if (!gridEl || typeof gridEl.querySelector !== 'function') return;\n\n const detailEl = gridEl.querySelector('tbw-grid-detail');\n if (!detailEl) return;\n\n // Check if a framework adapter wants to handle this element\n // (e.g., Angular adapter intercepts for ng-template rendering)\n const gridWithAdapter = gridEl as unknown as {\n __frameworkAdapter?: {\n parseDetailElement?: (el: Element) => ((row: any, rowIndex: number) => HTMLElement | string) | undefined;\n };\n };\n if (gridWithAdapter.__frameworkAdapter?.parseDetailElement) {\n const adapterRenderer = gridWithAdapter.__frameworkAdapter.parseDetailElement(detailEl);\n if (adapterRenderer) {\n this.config = { ...this.config, detailRenderer: adapterRenderer };\n return;\n }\n }\n\n // Parse attributes for configuration\n const animation = detailEl.getAttribute('animation');\n const showExpandColumn = detailEl.getAttribute('show-expand-column');\n const expandOnRowClick = detailEl.getAttribute('expand-on-row-click');\n const collapseOnClickOutside = detailEl.getAttribute('collapse-on-click-outside');\n const heightAttr = detailEl.getAttribute('height');\n\n const configUpdates: Partial<MasterDetailConfig> = {};\n\n if (animation !== null) {\n configUpdates.animation = animation === 'false' ? false : (animation as 'slide' | 'fade');\n }\n if (showExpandColumn !== null) {\n configUpdates.showExpandColumn = showExpandColumn !== 'false';\n }\n if (expandOnRowClick !== null) {\n configUpdates.expandOnRowClick = expandOnRowClick === 'true';\n }\n if (collapseOnClickOutside !== null) {\n configUpdates.collapseOnClickOutside = collapseOnClickOutside === 'true';\n }\n if (heightAttr !== null) {\n configUpdates.detailHeight = heightAttr === 'auto' ? 'auto' : parseInt(heightAttr, 10);\n }\n\n // Get template content from innerHTML\n const templateHTML = detailEl.innerHTML.trim();\n if (templateHTML && !this.config.detailRenderer) {\n // Create a template-based renderer using the inner HTML\n configUpdates.detailRenderer = (row: any, _rowIndex: number): string => {\n // Evaluate template expressions like {{ row.field }}\n const evaluated = evalTemplateString(templateHTML, { value: row, row });\n // Sanitize the result to prevent XSS\n return sanitizeHTML(evaluated);\n };\n }\n\n // Merge updates into config\n if (Object.keys(configUpdates).length > 0) {\n this.config = { ...this.config, ...configUpdates };\n }\n }\n\n // #endregion\n\n // #region Animation Helpers\n\n /**\n * Get expand/collapse animation style from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide';\n }\n\n /**\n * Apply expand animation to a detail element.\n * Returns true if animation was applied, false if skipped.\n * When animated, height measurement is deferred to animationend to avoid\n * measuring during the max-height: 0 CSS animation constraint.\n */\n private animateExpand(detailEl: HTMLElement, row?: any, rowIndex?: number): boolean {\n if (!this.isAnimationEnabled || this.animationStyle === false) return false;\n\n detailEl.classList.add('tbw-expanding');\n\n let measured = false;\n const measureOnce = () => {\n if (measured) return;\n measured = true;\n detailEl.classList.remove('tbw-expanding');\n\n // Measure height AFTER animation completes - the element now has its\n // natural height without the max-height constraint from the animation.\n if (row !== undefined && rowIndex !== undefined) {\n this.#measureAndCacheDetailHeight(detailEl, row, rowIndex);\n }\n };\n\n detailEl.addEventListener('animationend', measureOnce, { once: true });\n // Fallback timeout in case animationend doesn't fire (e.g., element detached,\n // animation removed, or framework rendering delays). Matches animateCollapse pattern.\n setTimeout(measureOnce, this.animationDuration + 50);\n return true;\n }\n\n /**\n * Apply collapse animation to a detail element and remove after animation.\n */\n private animateCollapse(detailEl: HTMLElement, onComplete: () => void): void {\n if (!this.isAnimationEnabled || this.animationStyle === false) {\n onComplete();\n return;\n }\n\n detailEl.classList.add('tbw-collapsing');\n const cleanup = () => {\n detailEl.classList.remove('tbw-collapsing');\n onComplete();\n };\n detailEl.addEventListener('animationend', cleanup, { once: true });\n // Fallback timeout in case animation doesn't fire\n setTimeout(cleanup, this.animationDuration + 50);\n }\n\n /**\n * Measure a detail element's height and update the position cache if it changed.\n * Used after layout settles (RAF) or after animation completes (animationend).\n */\n #measureAndCacheDetailHeight(detailEl: HTMLElement, row: any, rowIndex: number): void {\n if (!detailEl.isConnected) return;\n\n const height = detailEl.offsetHeight;\n if (height > 0) {\n const previousHeight = this.measuredDetailHeights.get(row);\n this.measuredDetailHeights.set(row, height);\n\n // Only invalidate if height actually changed\n // This triggers an incremental position cache update, not a full rebuild\n if (previousHeight !== height) {\n this.grid.invalidateRowHeight(rowIndex);\n }\n }\n }\n\n // #endregion\n\n // #region Internal State\n private expandedRows: Set<any> = new Set();\n private detailElements: Map<any, HTMLElement> = new Map();\n /** Cached measured heights - persists even when elements are virtualized out */\n private measuredDetailHeights: Map<any, number> = new Map();\n /** Rows that were just expanded by user action and should animate.\n * Prevents re-animation when rows scroll back into the virtual window. */\n private rowsToAnimate: Set<any> = new Set();\n\n /** Default height for detail rows when not configured */\n private static readonly DEFAULT_DETAIL_HEIGHT = 150;\n\n /**\n * Get the estimated height for a detail row.\n * Uses cached measured height when available (survives virtualization).\n * Avoids reading offsetHeight during CSS animations to prevent poisoning the cache.\n */\n private getDetailHeight(row: any): number {\n // Try DOM element first - works for tests and when element is connected\n const detailEl = this.detailElements.get(row);\n if (detailEl) {\n // Skip DOM measurement if currently animating (max-height constraint gives wrong value)\n const isAnimating = detailEl.classList.contains('tbw-expanding') || detailEl.classList.contains('tbw-collapsing');\n if (!isAnimating) {\n const height = detailEl.offsetHeight;\n if (height > 0) {\n // Cache the measurement for when this row is virtualized out\n this.measuredDetailHeights.set(row, height);\n return height;\n }\n }\n }\n\n // DOM element missing, detached, or animating - check cached measurement\n const cachedHeight = this.measuredDetailHeights.get(row);\n if (cachedHeight && cachedHeight > 0) {\n return cachedHeight;\n }\n\n // Fallback to config or default\n return typeof this.config?.detailHeight === 'number'\n ? this.config.detailHeight\n : MasterDetailPlugin.DEFAULT_DETAIL_HEIGHT;\n }\n\n /**\n * Toggle a row's detail and emit event.\n */\n private toggleAndEmit(row: any, rowIndex: number): void {\n this.expandedRows = toggleDetailRow(this.expandedRows, row as object);\n const expanded = this.expandedRows.has(row as object);\n if (expanded) {\n this.rowsToAnimate.add(row);\n }\n this.emit<DetailExpandDetail>('detail-expand', {\n rowIndex,\n row: row as Record<string, unknown>,\n expanded,\n });\n this.requestRender();\n }\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.expandedRows.clear();\n this.detailElements.clear();\n this.measuredDetailHeights.clear();\n this.rowsToAnimate.clear();\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n // Determine whether to add the expander column:\n // 1. If showExpandColumn === false: never add (explicit opt-out)\n // 2. If showExpandColumn === true: always add (explicit opt-in, for framework adapters)\n // 3. If showExpandColumn is undefined: add only if detailRenderer is provided\n //\n // This supports React/Angular adapters which register renderers asynchronously via light DOM.\n // They must set showExpandColumn: true to get the column immediately, avoiding layout shift.\n const shouldAddExpander =\n this.config.showExpandColumn === true || (this.config.showExpandColumn !== false && !!this.config.detailRenderer);\n\n if (!shouldAddExpander) {\n return [...columns];\n }\n\n const cols = [...columns];\n\n // Check if expander column already exists (from this or another plugin)\n const existingExpander = findExpanderColumn(cols);\n if (existingExpander) {\n // Another plugin already added an expander column - don't add duplicate\n // Our expand logic will be handled via onCellClick on the expander column\n return cols;\n }\n\n // Create dedicated expander column that stays fixed at position 0\n const expanderCol = createExpanderColumnConfig(this.name);\n expanderCol.viewRenderer = (renderCtx) => {\n const { row } = renderCtx;\n const isExpanded = this.expandedRows.has(row as object);\n\n const container = document.createElement('span');\n container.className = 'master-detail-expander expander-cell';\n\n // Expand/collapse toggle icon\n const toggle = document.createElement('span');\n toggle.className = `master-detail-toggle${isExpanded ? ' expanded' : ''}`;\n // Use grid-level icons (fall back to defaults)\n this.setIcon(toggle, this.resolveIcon(isExpanded ? 'collapse' : 'expand'));\n // role=\"button\" is required for aria-expanded to be valid\n toggle.setAttribute('role', 'button');\n toggle.setAttribute('tabindex', '0');\n toggle.setAttribute('aria-expanded', String(isExpanded));\n toggle.setAttribute('aria-label', isExpanded ? 'Collapse details' : 'Expand details');\n container.appendChild(toggle);\n\n return container;\n };\n\n // Prepend expander column to ensure it's always first\n return [expanderCol, ...cols];\n }\n\n /** @internal */\n override onRowClick(event: RowClickEvent): boolean | void {\n if (!this.config.expandOnRowClick || !this.config.detailRenderer) return;\n this.toggleAndEmit(event.row, event.rowIndex);\n return false;\n }\n\n /** @internal */\n override onCellClick(event: CellClickEvent): boolean | void {\n // Handle click on master-detail toggle icon (same pattern as TreePlugin)\n const target = event.originalEvent?.target as HTMLElement;\n if (target?.classList.contains('master-detail-toggle')) {\n this.toggleAndEmit(event.row, event.rowIndex);\n return true; // Prevent default handling\n }\n\n // Sync detail rows after cell click triggers refreshVirtualWindow\n // This runs in microtask to ensure DOM updates are complete\n if (this.expandedRows.size > 0) {\n queueMicrotask(() => this.#syncDetailRows());\n }\n return; // Don't prevent default\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion when focus is on the expander column\n if (event.key !== ' ') return;\n\n const focusCol = this.grid._focusCol;\n const focusRow = this.grid._focusRow;\n const column = this.columns[focusCol];\n\n // Only handle SPACE on expander column\n if (!column || !isExpanderColumn(column)) return;\n\n const row = this.rows[focusRow];\n if (!row) return;\n\n event.preventDefault();\n this.toggleAndEmit(row, focusRow);\n\n // Restore focus styling after render completes via render pipeline\n this.requestRenderWithFocus();\n return true;\n }\n\n /** @internal */\n override afterRender(): void {\n this.#syncDetailRows();\n }\n\n /**\n * Called on scroll to sync detail elements with visible rows.\n * Removes details for rows that scrolled out of view and reattaches for visible rows.\n * @internal\n */\n override onScrollRender(): void {\n if (!this.config.detailRenderer || this.expandedRows.size === 0) return;\n // Full sync needed on scroll to clean up orphaned details\n this.#syncDetailRows();\n }\n\n /**\n * Full sync of detail rows - cleans up stale elements and creates new ones.\n * Detail rows are inserted as siblings AFTER their master row to survive row rebuilds.\n *\n * PERF: Uses the grid's row pool (_rowPool) and virtual window (_virtualization.start/end)\n * to avoid querySelectorAll on every scroll frame. The pool is index-aligned with the\n * virtual window, so pool[i] corresponds to row index (start + i).\n */\n #syncDetailRows(): void {\n if (!this.config.detailRenderer) return;\n\n const body = this.gridElement?.querySelector('.rows');\n if (!body) return;\n\n // Use grid's virtualization state and row pool for O(1) lookups instead of querySelectorAll.\n // The row pool is an array of DOM elements aligned to the virtual window:\n // _rowPool[i] renders row data at index (_virtualization.start + i).\n const gridInternal = this.grid as any;\n const rowPool: HTMLElement[] | undefined = gridInternal._rowPool;\n const vStart: number = gridInternal._virtualization?.start ?? 0;\n const vEnd: number = gridInternal._virtualization?.end ?? 0;\n const columnCount = this.columns.length;\n\n // Build visible row index set from the virtual window range\n const visibleStart = vStart;\n const visibleEnd = vEnd;\n\n // Build a map of row index -> row element using the pool (O(n) where n = visible rows)\n const visibleRowMap = new Map<number, Element>();\n if (rowPool) {\n const poolLen = Math.min(rowPool.length, visibleEnd - visibleStart);\n for (let i = 0; i < poolLen; i++) {\n const rowEl = rowPool[i];\n if (rowEl.parentNode === body) {\n visibleRowMap.set(visibleStart + i, rowEl);\n }\n }\n } else {\n // Fallback: use querySelectorAll if pool is not accessible\n const dataRows = body.querySelectorAll('.data-grid-row');\n for (const rowEl of dataRows) {\n const firstCell = rowEl.querySelector('.cell[data-row]');\n const rowIndex = firstCell ? parseInt(firstCell.getAttribute('data-row') ?? '-1', 10) : -1;\n if (rowIndex >= 0) {\n visibleRowMap.set(rowIndex, rowEl);\n }\n }\n }\n\n // Remove detail rows whose parent row is no longer visible or no longer expanded.\n // Iterate the detailElements map (which we own) instead of querySelectorAll.\n for (const [row, detailEl] of this.detailElements) {\n const rowIndex = this.rows.indexOf(row);\n const isStillExpanded = this.expandedRows.has(row);\n const isRowVisible = rowIndex >= 0 && visibleRowMap.has(rowIndex);\n\n if (!isStillExpanded || !isRowVisible) {\n if (detailEl.parentNode) detailEl.remove();\n this.detailElements.delete(row);\n }\n }\n\n // Insert detail rows for expanded rows that are visible\n for (const [rowIndex, rowEl] of visibleRowMap) {\n const row = this.rows[rowIndex];\n if (!row || !this.expandedRows.has(row)) continue;\n\n // Check if detail already exists for this row\n const existingDetail = this.detailElements.get(row);\n if (existingDetail) {\n // Ensure it's positioned correctly (as next sibling of row element)\n if (existingDetail.previousElementSibling !== rowEl) {\n rowEl.after(existingDetail);\n }\n continue;\n }\n\n // Create new detail element\n const detailEl = createDetailElement(row, rowIndex, this.config.detailRenderer, columnCount);\n\n if (typeof this.config.detailHeight === 'number') {\n detailEl.style.height = `${this.config.detailHeight}px`;\n }\n\n // Insert as sibling after the row element (not as child)\n rowEl.after(detailEl);\n this.detailElements.set(row, detailEl);\n\n // Only animate if this row was just expanded by a user action (click, keyboard, API).\n // Rows re-appearing from scroll (virtualization) should not re-animate.\n const shouldAnimate = this.rowsToAnimate.has(row);\n if (shouldAnimate) {\n this.rowsToAnimate.delete(row);\n }\n\n const willAnimate = shouldAnimate && this.animateExpand(detailEl, row, rowIndex);\n\n if (!willAnimate) {\n // No animation - measure height after layout settles via RAF\n requestAnimationFrame(() => {\n this.#measureAndCacheDetailHeight(detailEl, row, rowIndex);\n });\n }\n // When animating, measurement is deferred to animationend callback\n // (inside animateExpand) to avoid measuring during max-height: 0 constraint\n }\n }\n\n /**\n * Return total extra height from all expanded detail rows.\n * Used by grid virtualization to adjust scrollbar height.\n *\n * @deprecated Use getRowHeight() instead. This hook will be removed in v3.0.\n */\n override getExtraHeight(): number {\n let totalHeight = 0;\n for (const row of this.expandedRows) {\n totalHeight += this.getDetailHeight(row);\n }\n return totalHeight;\n }\n\n /**\n * Return extra height that appears before a given row index.\n * This is the sum of heights of all expanded details whose parent row is before the given index.\n *\n * @deprecated Use getRowHeight() instead. This hook will be removed in v3.0.\n */\n override getExtraHeightBefore(beforeRowIndex: number): number {\n let totalHeight = 0;\n for (const row of this.expandedRows) {\n const rowIndex = this.rows.indexOf(row);\n // Include detail if it's for a row before the given index\n if (rowIndex >= 0 && rowIndex < beforeRowIndex) {\n totalHeight += this.getDetailHeight(row);\n }\n }\n return totalHeight;\n }\n\n /**\n * Get the height of a specific row, including any expanded detail content.\n * Always returns a height to ensure the position cache uses plugin-controlled values\n * rather than stale DOM measurements.\n *\n * @param row - The row data\n * @param _index - The row index (unused, but part of the interface)\n * @returns The row height in pixels (base height for collapsed, base + detail for expanded)\n */\n override getRowHeight(row: unknown, _index: number): number | undefined {\n const isExpanded = this.expandedRows.has(row as object);\n\n if (!isExpanded) {\n // Collapsed row - return undefined to let the grid use its measured/estimated height.\n // This ensures the position cache uses the correct row height from CSS/config.\n return undefined;\n }\n\n // Row is expanded - return base height plus detail height\n // Use grid's defaultRowHeight which reflects the actual measured/configured height\n const baseHeight = this.grid.defaultRowHeight ?? 28;\n const detailHeight = this.getDetailHeight(row);\n\n return baseHeight + detailHeight;\n }\n\n /**\n * Adjust the virtualization start index to keep expanded row visible while its detail is visible.\n * This ensures the detail scrolls smoothly out of view instead of disappearing abruptly.\n */\n override adjustVirtualStart(start: number, scrollTop: number, rowHeight: number): number {\n if (this.expandedRows.size === 0) return start;\n\n // Use position cache for accurate row positions when available (variable heights mode)\n const positionCache = (this.grid as any)?._virtualization?.positionCache as\n | Array<{ offset: number; height: number }>\n | undefined;\n\n let minStart = start;\n\n if (positionCache && positionCache.length > 0) {\n // Variable heights: use position cache for accurate offset\n for (const row of this.expandedRows) {\n const rowIndex = this.rows.indexOf(row);\n if (rowIndex < 0 || rowIndex >= start) continue;\n\n // Position cache already includes cumulative heights from all expanded details\n const detailBottom = positionCache[rowIndex].offset + positionCache[rowIndex].height;\n\n if (detailBottom > scrollTop && rowIndex < minStart) {\n minStart = rowIndex;\n }\n }\n } else {\n // Fixed heights fallback: accumulate detail heights manually\n // Build sorted list of expanded row indices for cumulative height calculation\n const expandedIndices: Array<{ index: number; row: any }> = [];\n for (const row of this.expandedRows) {\n const index = this.rows.indexOf(row);\n if (index >= 0) {\n expandedIndices.push({ index, row });\n }\n }\n expandedIndices.sort((a, b) => a.index - b.index);\n\n let cumulativeExtraHeight = 0;\n\n for (const { index: rowIndex, row } of expandedIndices) {\n const actualRowTop = rowIndex * rowHeight + cumulativeExtraHeight;\n const detailHeight = this.getDetailHeight(row);\n const actualDetailBottom = actualRowTop + rowHeight + detailHeight;\n\n cumulativeExtraHeight += detailHeight;\n\n if (rowIndex >= start) continue;\n\n if (actualDetailBottom > scrollTop && rowIndex < minStart) {\n minStart = rowIndex;\n }\n }\n }\n\n return minStart;\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Expand the detail row at the given index.\n * @param rowIndex - Index of the row to expand\n */\n expand(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.rowsToAnimate.add(row);\n this.expandedRows = expandDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Collapse the detail row at the given index.\n * @param rowIndex - Index of the row to collapse\n */\n collapse(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = collapseDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Toggle the detail row at the given index.\n * @param rowIndex - Index of the row to toggle\n */\n toggle(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = toggleDetailRow(this.expandedRows, row);\n if (this.expandedRows.has(row)) {\n this.rowsToAnimate.add(row);\n }\n this.requestRender();\n }\n }\n\n /**\n * Check if the detail row at the given index is expanded.\n * @param rowIndex - Index of the row to check\n * @returns Whether the detail row is expanded\n */\n isExpanded(rowIndex: number): boolean {\n const row = this.rows[rowIndex];\n return row ? isDetailExpanded(this.expandedRows, row) : false;\n }\n\n /**\n * Expand all detail rows.\n */\n expandAll(): void {\n for (const row of this.rows) {\n this.rowsToAnimate.add(row);\n this.expandedRows.add(row);\n }\n this.requestRender();\n }\n\n /**\n * Collapse all detail rows.\n */\n collapseAll(): void {\n this.expandedRows.clear();\n this.requestRender();\n }\n\n /**\n * Get the indices of all expanded rows.\n * @returns Array of row indices that are expanded\n */\n getExpandedRows(): number[] {\n const indices: number[] = [];\n for (const row of this.expandedRows) {\n const idx = this.rows.indexOf(row);\n if (idx >= 0) indices.push(idx);\n }\n return indices;\n }\n\n /**\n * Get the detail element for a specific row.\n * @param rowIndex - Index of the row\n * @returns The detail HTMLElement or undefined\n */\n getDetailElement(rowIndex: number): HTMLElement | undefined {\n const row = this.rows[rowIndex];\n return row ? this.detailElements.get(row) : undefined;\n }\n\n /**\n * Re-parse light DOM to refresh the detail renderer.\n * Call this after framework templates are registered (e.g., Angular ngAfterContentInit).\n *\n * This allows frameworks to register templates asynchronously and then\n * update the plugin's detailRenderer.\n */\n refreshDetailRenderer(): void {\n // Force re-parse by temporarily clearing the renderer\n const currentRenderer = this.config.detailRenderer;\n this.config = { ...this.config, detailRenderer: undefined };\n this.parseLightDomDetail();\n\n // If no new renderer was found, restore the original\n if (!this.config.detailRenderer && currentRenderer) {\n this.config = { ...this.config, detailRenderer: currentRenderer };\n }\n\n // Request a COLUMNS phase re-render so processColumns runs again with the new detailRenderer\n // This ensures the expand toggle is added to the first column.\n // Must use refreshColumns() (COLUMNS phase) not requestRender() (ROWS phase)\n // because processColumns only runs at COLUMNS phase or higher.\n if (this.config.detailRenderer) {\n const grid = this.grid as unknown as { refreshColumns?: () => void };\n if (typeof grid.refreshColumns === 'function') {\n grid.refreshColumns();\n } else {\n // Fallback to requestRender if refreshColumns not available\n this.requestRender();\n }\n }\n }\n // #endregion\n}\n","/**\n * Multi-Sort Core Logic\n *\n * Pure functions for multi-column sorting operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { SortModel } from './types';\n\n/**\n * Apply multiple sort columns to a row array.\n * Sorts are applied in order - first sort has highest priority.\n *\n * @param rows - Array of row objects to sort\n * @param sorts - Ordered array of sort configurations\n * @param columns - Column configurations (for custom comparators)\n * @returns New sorted array (does not mutate original)\n */\nexport function applySorts<TRow = unknown>(rows: TRow[], sorts: SortModel[], columns: ColumnConfig<TRow>[]): TRow[] {\n if (!sorts.length) return [...rows];\n\n return [...rows].sort((a, b) => {\n for (const sort of sorts) {\n const col = columns.find((c) => c.field === sort.field);\n const comparator = col?.sortComparator ?? defaultComparator;\n const aVal = (a as Record<string, unknown>)[sort.field];\n const bVal = (b as Record<string, unknown>)[sort.field];\n const result = comparator(aVal, bVal, a, b);\n if (result !== 0) {\n return sort.direction === 'asc' ? result : -result;\n }\n }\n return 0;\n });\n}\n\n/**\n * Default comparator for sorting values.\n * Handles nulls, numbers, dates, and strings.\n *\n * @param a - First value\n * @param b - Second value\n * @returns Comparison result (-1, 0, 1)\n */\nexport function defaultComparator(a: unknown, b: unknown): number {\n // Handle nulls/undefined - push to end\n if (a == null && b == null) return 0;\n if (a == null) return 1;\n if (b == null) return -1;\n\n // Type-aware comparison\n if (typeof a === 'number' && typeof b === 'number') {\n return a - b;\n }\n\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() - b.getTime();\n }\n\n // Boolean comparison\n if (typeof a === 'boolean' && typeof b === 'boolean') {\n return a === b ? 0 : a ? -1 : 1;\n }\n\n // String comparison (fallback)\n return String(a).localeCompare(String(b));\n}\n\n/**\n * Toggle sort state for a field.\n * With shift key: adds/toggles in multi-sort list\n * Without shift key: replaces entire sort with single column\n *\n * @param current - Current sort model\n * @param field - Field to toggle\n * @param shiftKey - Whether shift key is held (multi-sort mode)\n * @param maxColumns - Maximum columns allowed in sort\n * @returns New sort model\n */\nexport function toggleSort(current: SortModel[], field: string, shiftKey: boolean, maxColumns: number): SortModel[] {\n const existing = current.find((s) => s.field === field);\n\n if (shiftKey) {\n // Multi-sort: add/toggle in list\n if (existing) {\n if (existing.direction === 'asc') {\n // Flip to descending\n return current.map((s) => (s.field === field ? { ...s, direction: 'desc' as const } : s));\n } else {\n // Remove from sort\n return current.filter((s) => s.field !== field);\n }\n } else if (current.length < maxColumns) {\n // Add new sort column\n return [...current, { field, direction: 'asc' as const }];\n }\n // Max columns reached, return unchanged\n return current;\n } else {\n // Single sort: replace all\n if (existing?.direction === 'asc') {\n return [{ field, direction: 'desc' }];\n } else if (existing?.direction === 'desc') {\n return [];\n }\n return [{ field, direction: 'asc' }];\n }\n}\n\n/**\n * Get the sort index (1-based) for a field in the sort model.\n * Returns undefined if the field is not in the sort model.\n *\n * @param sortModel - Current sort model\n * @param field - Field to check\n * @returns 1-based index or undefined\n */\nexport function getSortIndex(sortModel: SortModel[], field: string): number | undefined {\n const index = sortModel.findIndex((s) => s.field === field);\n return index >= 0 ? index + 1 : undefined;\n}\n\n/**\n * Get the sort direction for a field in the sort model.\n *\n * @param sortModel - Current sort model\n * @param field - Field to check\n * @returns Sort direction or undefined if not sorted\n */\nexport function getSortDirection(sortModel: SortModel[], field: string): 'asc' | 'desc' | undefined {\n return sortModel.find((s) => s.field === field)?.direction;\n}\n","/**\n * Multi-Sort Plugin (Class-based)\n *\n * Provides multi-column sorting capabilities for tbw-grid.\n * Supports shift+click for adding secondary sort columns.\n */\n\nimport { BaseGridPlugin, HeaderClickEvent } from '../../core/plugin/base-plugin';\nimport type { ColumnState } from '../../core/types';\nimport { applySorts, getSortDirection, getSortIndex, toggleSort } from './multi-sort';\nimport styles from './multi-sort.css?inline';\nimport type { MultiSortConfig, SortModel } from './types';\n\n/**\n * Multi-Sort Plugin for tbw-grid\n *\n * Enables sorting by multiple columns at once—hold Shift and click additional column\n * headers to build up a sort stack. Priority badges show the sort order, so users\n * always know which column takes precedence.\n *\n * ## Installation\n *\n * ```ts\n * import { MultiSortPlugin } from '@toolbox-web/grid/plugins/multi-sort';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `maxSortColumns` | `number` | `3` | Maximum columns to sort by |\n * | `showSortIndex` | `boolean` | `true` | Show sort priority badges |\n * | `initialSort` | `SortModel[]` | - | Pre-configured sort order on load |\n *\n * ## Keyboard Shortcuts\n *\n * | Shortcut | Action |\n * |----------|--------|\n * | `Click header` | Sort by column (clears other sorts) |\n * | `Shift + Click` | Add column to multi-sort stack |\n * | `Ctrl + Click` | Toggle sort direction |\n *\n * ## Events\n *\n * | Event | Detail | Description |\n * |-------|--------|-------------|\n * | `sort-change` | `{ sortModel: SortModel[] }` | Fired when sort changes |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `setSort` | `(sortModel: SortModel[]) => void` | Set sort programmatically |\n * | `getSortModel` | `() => SortModel[]` | Get current sort model |\n * | `clearSort` | `() => void` | Clear all sorting |\n * | `addSort` | `(field, direction) => void` | Add a column to sort |\n * | `removeSort` | `(field) => void` | Remove a column from sort |\n *\n * @example Basic Multi-Column Sorting\n * ```ts\n * import '@toolbox-web/grid';\n * import { MultiSortPlugin } from '@toolbox-web/grid/plugins/multi-sort';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name', sortable: true },\n * { field: 'department', header: 'Department', sortable: true },\n * { field: 'salary', header: 'Salary', type: 'number', sortable: true },\n * ],\n * plugins: [new MultiSortPlugin({ maxSortColumns: 3, showSortIndex: true })],\n * };\n *\n * grid.addEventListener('sort-change', (e) => {\n * console.log('Active sorts:', e.detail.sortModel);\n * });\n * ```\n *\n * @example Initial Sort Configuration\n * ```ts\n * new MultiSortPlugin({\n * initialSort: [\n * { field: 'department', direction: 'asc' },\n * { field: 'salary', direction: 'desc' },\n * ],\n * })\n * ```\n *\n * @see {@link MultiSortConfig} for all configuration options\n * @see {@link SortModel} for the sort model structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class MultiSortPlugin extends BaseGridPlugin<MultiSortConfig> {\n /** @internal */\n readonly name = 'multiSort';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<MultiSortConfig> {\n return {\n maxSortColumns: 3,\n showSortIndex: true,\n };\n }\n\n // #region Internal State\n private sortModel: SortModel[] = [];\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.sortModel = [];\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processRows(rows: readonly unknown[]): unknown[] {\n if (this.sortModel.length === 0) {\n return [...rows];\n }\n return applySorts([...rows], this.sortModel, [...this.columns]);\n }\n\n /** @internal */\n override onHeaderClick(event: HeaderClickEvent): boolean {\n const column = this.columns.find((c) => c.field === event.field);\n if (!column?.sortable) return false;\n\n const shiftKey = event.originalEvent.shiftKey;\n const maxColumns = this.config.maxSortColumns ?? 3;\n\n this.sortModel = toggleSort(this.sortModel, event.field, shiftKey, maxColumns);\n\n this.emit('sort-change', { sortModel: [...this.sortModel] });\n this.requestRender();\n\n return true;\n }\n\n /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n const showIndex = this.config.showSortIndex !== false;\n\n // Update all sortable header cells with sort indicators\n const headerCells = gridEl.querySelectorAll('.header-row .cell[data-field]');\n headerCells.forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (!field) return;\n\n const sortIndex = getSortIndex(this.sortModel, field);\n const sortDir = getSortDirection(this.sortModel, field);\n\n // Remove existing sort index badge (always clean up)\n const existingBadge = cell.querySelector('.sort-index');\n existingBadge?.remove();\n\n if (sortDir) {\n // Column is sorted - remove base indicator and add our own\n const existingIndicator = cell.querySelector('[part~=\"sort-indicator\"], .sort-indicator');\n existingIndicator?.remove();\n\n cell.setAttribute('data-sort', sortDir);\n\n // Add sort arrow indicator - insert BEFORE filter button and resize handle\n // to maintain consistent order: [label, sort-indicator, sort-index, filter-btn, resize-handle]\n const indicator = document.createElement('span');\n indicator.className = 'sort-indicator';\n // Use grid-level icons (fall back to defaults)\n this.setIcon(indicator, this.resolveIcon(sortDir === 'asc' ? 'sortAsc' : 'sortDesc'));\n\n // Find insertion point: before filter button or resize handle\n const filterBtn = cell.querySelector('.tbw-filter-btn');\n const resizeHandle = cell.querySelector('.resize-handle');\n const insertBefore = filterBtn ?? resizeHandle;\n if (insertBefore) {\n cell.insertBefore(indicator, insertBefore);\n } else {\n cell.appendChild(indicator);\n }\n\n // Add sort index badge if multiple columns sorted and showSortIndex is enabled\n if (showIndex && this.sortModel.length > 1 && sortIndex !== undefined) {\n const badge = document.createElement('span');\n badge.className = 'sort-index';\n badge.textContent = String(sortIndex);\n // Insert badge right after the indicator\n if (indicator.nextSibling) {\n cell.insertBefore(badge, indicator.nextSibling);\n } else {\n cell.appendChild(badge);\n }\n }\n } else {\n cell.removeAttribute('data-sort');\n // For unsorted columns, leave the base indicator (⇅) alone\n }\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Get the current sort model.\n * @returns Copy of the current sort model\n */\n getSortModel(): SortModel[] {\n return [...this.sortModel];\n }\n\n /**\n * Set the sort model programmatically.\n * @param model - New sort model to apply\n */\n setSortModel(model: SortModel[]): void {\n this.sortModel = [...model];\n this.emit('sort-change', { sortModel: [...model] });\n this.requestRender();\n }\n\n /**\n * Clear all sorting.\n */\n clearSort(): void {\n this.sortModel = [];\n this.emit('sort-change', { sortModel: [] });\n this.requestRender();\n }\n\n /**\n * Get the sort index (1-based) for a specific field.\n * @param field - Field to check\n * @returns 1-based index or undefined if not sorted\n */\n getSortIndex(field: string): number | undefined {\n return getSortIndex(this.sortModel, field);\n }\n\n /**\n * Get the sort direction for a specific field.\n * @param field - Field to check\n * @returns Sort direction or undefined if not sorted\n */\n getSortDirection(field: string): 'asc' | 'desc' | undefined {\n return getSortDirection(this.sortModel, field);\n }\n // #endregion\n\n // #region Column State Hooks\n\n /**\n * Return sort state for a column if it's in the sort model.\n * @internal\n */\n override getColumnState(field: string): Partial<ColumnState> | undefined {\n const index = this.sortModel.findIndex((s) => s.field === field);\n if (index === -1) return undefined;\n\n const sortEntry = this.sortModel[index];\n return {\n sort: {\n direction: sortEntry.direction,\n priority: index,\n },\n };\n }\n\n /**\n * Apply sort state from column state.\n * Rebuilds the sort model from all column states.\n * @internal\n */\n override applyColumnState(field: string, state: ColumnState): void {\n // Only process if the column has sort state\n if (!state.sort) {\n // Remove this field from sortModel if it exists\n this.sortModel = this.sortModel.filter((s) => s.field !== field);\n return;\n }\n\n // Find existing entry or add new one\n const existingIndex = this.sortModel.findIndex((s) => s.field === field);\n const newEntry: SortModel = {\n field,\n direction: state.sort.direction,\n };\n\n if (existingIndex !== -1) {\n // Update existing entry\n this.sortModel[existingIndex] = newEntry;\n } else {\n // Add at the correct priority position\n this.sortModel.splice(state.sort.priority, 0, newEntry);\n }\n\n // Re-sort the model by priority to ensure correct order\n // This is handled after all columns are processed, but we maintain order here\n }\n // #endregion\n}\n","/**\n * Pinned Columns Core Logic\n *\n * Pure functions for applying pinned (sticky) column positioning.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { getDirection, resolveInlinePosition, type TextDirection } from '../../core/internal/utils';\nimport type { PinnedPosition, ResolvedPinnedPosition } from './types';\n\n// Keep deprecated imports working (StickyPosition = PinnedPosition)\ntype StickyPosition = PinnedPosition;\ntype ResolvedStickyPosition = ResolvedPinnedPosition;\n\n/**\n * Get the effective pinned position from a column, checking `pinned` first then `sticky` (deprecated).\n *\n * @param col - Column configuration object\n * @returns The pinned position, or undefined if not pinned\n */\nexport function getColumnPinned(col: any): PinnedPosition | undefined {\n return col.pinned ?? col.sticky ?? col.meta?.pinned ?? col.meta?.sticky;\n}\n\n/**\n * Resolve a pinned position to a physical position based on text direction.\n *\n * - `'left'` / `'right'` → unchanged (physical values)\n * - `'start'` → `'left'` in LTR, `'right'` in RTL\n * - `'end'` → `'right'` in LTR, `'left'` in RTL\n *\n * @param position - The pinned position (logical or physical)\n * @param direction - Text direction ('ltr' or 'rtl')\n * @returns Physical pinned position ('left' or 'right')\n */\nexport function resolveStickyPosition(position: StickyPosition, direction: TextDirection): ResolvedStickyPosition {\n return resolveInlinePosition(position, direction);\n}\n\n/**\n * Check if a column is pinned on the left (after resolving logical positions).\n */\nfunction isResolvedLeft(col: any, direction: TextDirection): boolean {\n const pinned = getColumnPinned(col);\n if (!pinned) return false;\n return resolveStickyPosition(pinned, direction) === 'left';\n}\n\n/**\n * Check if a column is pinned on the right (after resolving logical positions).\n */\nfunction isResolvedRight(col: any, direction: TextDirection): boolean {\n const pinned = getColumnPinned(col);\n if (!pinned) return false;\n return resolveStickyPosition(pinned, direction) === 'right';\n}\n\n/**\n * Get columns that should be sticky on the left.\n *\n * @param columns - Array of column configurations\n * @param direction - Text direction (default: 'ltr')\n * @returns Array of columns with sticky='left' or sticky='start' (in LTR)\n */\nexport function getLeftStickyColumns(columns: any[], direction: TextDirection = 'ltr'): any[] {\n return columns.filter((col) => isResolvedLeft(col, direction));\n}\n\n/**\n * Get columns that should be sticky on the right.\n *\n * @param columns - Array of column configurations\n * @param direction - Text direction (default: 'ltr')\n * @returns Array of columns with sticky='right' or sticky='end' (in LTR)\n */\nexport function getRightStickyColumns(columns: any[], direction: TextDirection = 'ltr'): any[] {\n return columns.filter((col) => isResolvedRight(col, direction));\n}\n\n/**\n * Check if any columns have sticky positioning.\n *\n * @param columns - Array of column configurations\n * @returns True if any column has sticky position\n */\nexport function hasStickyColumns(columns: any[]): boolean {\n return columns.some((col) => getColumnPinned(col) != null);\n}\n\n/**\n * Get the sticky position of a column.\n *\n * @param column - Column configuration\n * @returns The sticky position or null if not sticky\n */\nexport function getColumnStickyPosition(column: any): StickyPosition | null {\n return getColumnPinned(column) ?? null;\n}\n\n\n/**\n * Calculate left offsets for sticky-left columns.\n * Returns a map of field -> offset in pixels.\n *\n * @param columns - Array of column configurations (in order)\n * @param getColumnWidth - Function to get column width by field\n * @param direction - Text direction (default: 'ltr')\n * @returns Map of field to left offset\n */\nexport function calculateLeftStickyOffsets(\n columns: any[],\n getColumnWidth: (field: string) => number,\n direction: TextDirection = 'ltr',\n): Map<string, number> {\n const offsets = new Map<string, number>();\n let currentOffset = 0;\n\n for (const col of columns) {\n if (isResolvedLeft(col, direction)) {\n offsets.set(col.field, currentOffset);\n currentOffset += getColumnWidth(col.field);\n }\n }\n\n return offsets;\n}\n\n/**\n * Calculate right offsets for sticky-right columns.\n * Processes columns in reverse order.\n *\n * @param columns - Array of column configurations (in order)\n * @param getColumnWidth - Function to get column width by field\n * @param direction - Text direction (default: 'ltr')\n * @returns Map of field to right offset\n */\nexport function calculateRightStickyOffsets(\n columns: any[],\n getColumnWidth: (field: string) => number,\n direction: TextDirection = 'ltr',\n): Map<string, number> {\n const offsets = new Map<string, number>();\n let currentOffset = 0;\n\n // Process in reverse for right-sticky columns\n const reversed = [...columns].reverse();\n for (const col of reversed) {\n if (isResolvedRight(col, direction)) {\n offsets.set(col.field, currentOffset);\n currentOffset += getColumnWidth(col.field);\n }\n }\n\n return offsets;\n}\n\n/**\n * Apply sticky offsets to header and body cells.\n * This modifies the DOM elements in place.\n *\n * @param host - The grid host element (render root for DOM queries)\n * @param columns - Array of column configurations\n */\nexport function applyStickyOffsets(host: HTMLElement, columns: any[]): void {\n // With light DOM, query the host element directly\n const headerCells = Array.from(host.querySelectorAll('.header-row .cell')) as HTMLElement[];\n if (!headerCells.length) return;\n\n // Detect text direction from the host element\n const direction = getDirection(host);\n\n // Apply left sticky (includes 'start' in LTR, 'end' in RTL)\n let left = 0;\n for (const col of columns) {\n if (isResolvedLeft(col, direction)) {\n const cell = headerCells.find((c) => c.getAttribute('data-field') === col.field);\n if (cell) {\n cell.classList.add('sticky-left');\n cell.style.position = 'sticky';\n cell.style.left = left + 'px';\n // Body cells: use data-field for reliable matching (data-col indices may differ\n // between _columns and _visibleColumns due to hidden/utility columns)\n host.querySelectorAll(`.data-grid-row .cell[data-field=\"${col.field}\"]`).forEach((el) => {\n el.classList.add('sticky-left');\n (el as HTMLElement).style.position = 'sticky';\n (el as HTMLElement).style.left = left + 'px';\n });\n left += cell.offsetWidth;\n }\n }\n }\n\n // Apply right sticky (includes 'end' in LTR, 'start' in RTL) - process in reverse\n let right = 0;\n for (const col of [...columns].reverse()) {\n if (isResolvedRight(col, direction)) {\n const cell = headerCells.find((c) => c.getAttribute('data-field') === col.field);\n if (cell) {\n cell.classList.add('sticky-right');\n cell.style.position = 'sticky';\n cell.style.right = right + 'px';\n // Body cells: use data-field for reliable matching\n host.querySelectorAll(`.data-grid-row .cell[data-field=\"${col.field}\"]`).forEach((el) => {\n el.classList.add('sticky-right');\n (el as HTMLElement).style.position = 'sticky';\n (el as HTMLElement).style.right = right + 'px';\n });\n right += cell.offsetWidth;\n }\n }\n }\n}\n\n/**\n * Reorder columns so that pinned-left columns come first and pinned-right columns come last.\n * Maintains the relative order within each group (left-pinned, unpinned, right-pinned).\n *\n * @param columns - Array of column configurations (in their current order)\n * @param direction - Text direction ('ltr' or 'rtl'), used to resolve logical positions\n * @returns New array with pinned columns moved to the edges\n */\nexport function reorderColumnsForPinning(columns: readonly any[], direction: TextDirection = 'ltr'): any[] {\n const left: any[] = [];\n const middle: any[] = [];\n const right: any[] = [];\n\n for (const col of columns) {\n const pinned = getColumnPinned(col);\n if (pinned) {\n const resolved = resolveStickyPosition(pinned, direction);\n if (resolved === 'left') left.push(col);\n else right.push(col);\n } else {\n middle.push(col);\n }\n }\n\n return [...left, ...middle, ...right];\n}\n\n/**\n * Clear sticky positioning from all cells.\n *\n * @param host - The grid host element (render root for DOM queries)\n */\nexport function clearStickyOffsets(host: HTMLElement): void {\n // With light DOM, query the host element directly\n const cells = host.querySelectorAll('.sticky-left, .sticky-right');\n cells.forEach((cell) => {\n cell.classList.remove('sticky-left', 'sticky-right');\n (cell as HTMLElement).style.position = '';\n (cell as HTMLElement).style.left = '';\n (cell as HTMLElement).style.right = '';\n });\n}\n","/**\n * Pinned Columns Plugin (Class-based)\n *\n * Enables column pinning (sticky left/right positioning).\n */\n\nimport { getDirection } from '../../core/internal/utils';\nimport type { PluginManifest, PluginQuery } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport type { ContextMenuParams, HeaderContextMenuItem } from '../context-menu/types';\nimport {\n applyStickyOffsets,\n clearStickyOffsets,\n getColumnPinned,\n getLeftStickyColumns,\n getRightStickyColumns,\n hasStickyColumns,\n reorderColumnsForPinning,\n} from './pinned-columns';\nimport type { PinnedColumnsConfig, PinnedPosition } from './types';\n\n/** Query type constant for checking if a column can be moved */\nconst QUERY_CAN_MOVE_COLUMN = 'canMoveColumn';\n\n/**\n * Pinned Columns Plugin for tbw-grid\n *\n * Freezes columns to the left or right edge of the grid—essential for keeping key\n * identifiers or action buttons visible while scrolling through wide datasets. Just set\n * `pinned: 'left'` or `pinned: 'right'` on your column definitions.\n *\n * ## Installation\n *\n * ```ts\n * import { PinnedColumnsPlugin } from '@toolbox-web/grid/plugins/pinned-columns';\n * ```\n *\n * ## Column Configuration\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `pinned` | `'left' \\| 'right' \\| 'start' \\| 'end'` | Pin column to edge (logical or physical) |\n * | `meta.lockPinning` | `boolean` | `false` | Prevent user from pin/unpin via context menu |\n *\n * ### RTL Support\n *\n * Use logical values (`start`/`end`) for grids that work in both LTR and RTL layouts:\n * - `'start'` - Pins to left in LTR, right in RTL\n * - `'end'` - Pins to right in LTR, left in RTL\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-pinned-shadow` | `4px 0 8px rgba(0,0,0,0.1)` | Shadow on pinned column edge |\n * | `--tbw-pinned-border` | `var(--tbw-color-border)` | Border between pinned and scrollable |\n *\n * @example Pin ID Left and Actions Right\n * ```ts\n * import '@toolbox-web/grid';\n * import { PinnedColumnsPlugin } from '@toolbox-web/grid/plugins/pinned-columns';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID', pinned: 'left', width: 80 },\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * { field: 'department', header: 'Department' },\n * { field: 'actions', header: 'Actions', pinned: 'right', width: 120 },\n * ],\n * plugins: [new PinnedColumnsPlugin()],\n * };\n * ```\n *\n * @example RTL-Compatible Pinning\n * ```ts\n * // Same config works in LTR and RTL\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID', pinned: 'start' }, // Left in LTR, Right in RTL\n * { field: 'name', header: 'Name' },\n * { field: 'actions', header: 'Actions', pinned: 'end' }, // Right in LTR, Left in RTL\n * ],\n * plugins: [new PinnedColumnsPlugin()],\n * };\n * ```\n *\n * @see {@link PinnedColumnsConfig} for configuration options\n *\n * @internal Extends BaseGridPlugin\n */\nexport class PinnedColumnsPlugin extends BaseGridPlugin<PinnedColumnsConfig> {\n /**\n * Plugin manifest - declares owned properties and handled queries.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n ownedProperties: [\n {\n property: 'pinned',\n level: 'column',\n description: 'the \"pinned\" column property',\n isUsed: (v) => v === 'left' || v === 'right' || v === 'start' || v === 'end',\n },\n {\n property: 'sticky',\n level: 'column',\n description: 'the \"sticky\" column property (deprecated, use \"pinned\")',\n isUsed: (v) => v === 'left' || v === 'right' || v === 'start' || v === 'end',\n },\n ],\n incompatibleWith: [\n {\n name: 'groupingColumns',\n reason:\n 'Pinning reorders columns to the grid edges, but moving a column out of its column group ' +\n 'is not supported. The group header layout cannot accommodate members at different positions.',\n },\n ],\n queries: [\n {\n type: QUERY_CAN_MOVE_COLUMN,\n description: 'Prevents pinned (sticky) columns from being moved/reordered',\n },\n {\n type: 'getStickyOffsets',\n description: 'Returns the sticky offsets for left/right pinned columns',\n },\n {\n type: 'getContextMenuItems',\n description: 'Contributes pin/unpin items to the header context menu',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'pinnedColumns';\n\n /** @internal */\n protected override get defaultConfig(): Partial<PinnedColumnsConfig> {\n return {};\n }\n\n // #region Internal State\n private isApplied = false;\n private leftOffsets = new Map<string, number>();\n private rightOffsets = new Map<string, number>();\n /**\n * Snapshot of the column field order before the first context-menu pin.\n * Used to restore original positions when unpinning.\n */\n #originalColumnOrder: string[] = [];\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.leftOffsets.clear();\n this.rightOffsets.clear();\n this.isApplied = false;\n this.#originalColumnOrder = [];\n }\n // #endregion\n\n // #region Detection\n\n /**\n * Auto-detect sticky columns from column configuration.\n */\n static detect(rows: readonly unknown[], config: { columns?: ColumnConfig[] }): boolean {\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasStickyColumns(columns);\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n const cols = [...columns];\n this.isApplied = hasStickyColumns(cols);\n if (!this.isApplied) return cols;\n\n const host = this.gridElement;\n const direction = host ? getDirection(host) : 'ltr';\n return reorderColumnsForPinning(cols, direction) as ColumnConfig[];\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.isApplied) {\n return;\n }\n\n const host = this.grid as unknown as HTMLElement;\n const columns = [...this.columns];\n\n if (!hasStickyColumns(columns)) {\n clearStickyOffsets(host);\n this.isApplied = false;\n return;\n }\n\n // Apply sticky offsets after a microtask to ensure DOM is ready\n queueMicrotask(() => {\n applyStickyOffsets(host, columns);\n });\n }\n\n /**\n * Handle inter-plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n switch (query.type) {\n case QUERY_CAN_MOVE_COLUMN: {\n // Prevent pinned columns from being moved/reordered.\n // Pinned columns have fixed positions and should not be draggable.\n const column = query.context as ColumnConfig;\n if (getColumnPinned(column) != null) {\n return false;\n }\n return undefined; // Let other plugins or default behavior decide\n }\n case 'getStickyOffsets': {\n // Return the calculated sticky offsets for column virtualization\n return {\n left: Object.fromEntries(this.leftOffsets),\n right: Object.fromEntries(this.rightOffsets),\n };\n }\n case 'getContextMenuItems': {\n const params = query.context as ContextMenuParams;\n if (!params.isHeader) return undefined;\n\n const column = params.column as ColumnConfig;\n if (!column?.field) return undefined;\n\n // Don't offer pin/unpin for locked-pinning columns\n if (column.meta?.lockPinning) return undefined;\n\n // Don't offer pin/unpin when column grouping is active (incompatible)\n const groupingPlugin = this.grid?.getPluginByName('groupingColumns') as\n | { isGroupingActive(): boolean }\n | undefined;\n if (groupingPlugin?.isGroupingActive()) return undefined;\n\n const pinned = getColumnPinned(column);\n const isPinned = pinned != null;\n const items: HeaderContextMenuItem[] = [];\n\n if (isPinned) {\n items.push({\n id: 'pinned/unpin',\n label: 'Unpin Column',\n icon: '📌',\n order: 40,\n action: () => this.setPinPosition(column.field, undefined),\n });\n } else {\n items.push({\n id: 'pinned/pin-left',\n label: 'Pin Left',\n icon: '⬅',\n order: 40,\n action: () => this.setPinPosition(column.field, 'left'),\n });\n items.push({\n id: 'pinned/pin-right',\n label: 'Pin Right',\n icon: '➡',\n order: 41,\n action: () => this.setPinPosition(column.field, 'right'),\n });\n }\n\n return items;\n }\n default:\n return undefined;\n }\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Set the pin position for a column.\n * Updates the column's `pinned` property and triggers a full re-render.\n *\n * @param field - The field name of the column to pin/unpin\n * @param position - The pin position (`'left'`, `'right'`, `'start'`, `'end'`), or `undefined` to unpin\n */\n setPinPosition(field: string, position: PinnedPosition | undefined): void {\n // Read the currently-visible columns from the plugin accessor.\n // These are the post-processColumns result, which is the authoritative column set.\n const currentColumns = this.columns;\n if (!currentColumns?.length) return;\n\n const currentIndex = currentColumns.findIndex((col) => col.field === field);\n if (currentIndex === -1) return;\n\n const gridEl = this.grid as unknown as HTMLElement & { columns?: ColumnConfig[] };\n\n if (position) {\n // PINNING: snapshot original column order if this is the first context-menu pin.\n // The snapshot lets us restore columns to their original positions on unpin.\n if (this.#originalColumnOrder.length === 0) {\n this.#originalColumnOrder = currentColumns.map((c) => c.field);\n }\n\n // Set the pinned property; processColumns will reorder on next render\n const updated = currentColumns.map((col) => {\n if (col.field !== field) return col;\n const copy = { ...col };\n (copy as ColumnConfig & { pinned?: PinnedPosition }).pinned = position;\n delete (copy as ColumnConfig & { sticky?: PinnedPosition }).sticky;\n return copy;\n });\n\n gridEl.columns = updated;\n } else {\n // UNPINNING: restore column to its original position\n const col = currentColumns[currentIndex];\n const copy = { ...col };\n delete (copy as ColumnConfig & { pinned?: PinnedPosition }).pinned;\n delete (copy as ColumnConfig & { sticky?: PinnedPosition }).sticky;\n\n // Remove from current position\n const remaining = [...currentColumns];\n remaining.splice(currentIndex, 1);\n\n // Find the best insertion point using the original order snapshot\n const originalIndex = this.#originalColumnOrder.indexOf(field);\n if (originalIndex >= 0) {\n // Scan remaining non-pinned columns and find the first whose original\n // position is greater than this column's original position.\n let insertIndex = remaining.length;\n for (let i = 0; i < remaining.length; i++) {\n if (getColumnPinned(remaining[i])) continue; // skip pinned columns\n const otherOriginal = this.#originalColumnOrder.indexOf(remaining[i].field);\n if (otherOriginal > originalIndex) {\n insertIndex = i;\n break;\n }\n }\n remaining.splice(insertIndex, 0, copy);\n } else {\n // Original position unknown — keep at current index\n remaining.splice(Math.min(currentIndex, remaining.length), 0, copy);\n }\n\n // If no more pinned columns remain, clear the snapshot\n if (!remaining.some((c) => getColumnPinned(c) != null)) {\n this.#originalColumnOrder = [];\n }\n\n gridEl.columns = remaining;\n }\n }\n\n /**\n * Re-apply sticky offsets (e.g., after column resize).\n */\n refreshStickyOffsets(): void {\n const columns = [...this.columns];\n applyStickyOffsets(this.grid as unknown as HTMLElement, columns);\n }\n\n /**\n * Get columns pinned to the left (after resolving logical positions for current direction).\n */\n getLeftPinnedColumns(): ColumnConfig[] {\n const columns = [...this.columns];\n const direction = getDirection(this.grid as unknown as HTMLElement);\n return getLeftStickyColumns(columns, direction);\n }\n\n /**\n * Get columns pinned to the right (after resolving logical positions for current direction).\n */\n getRightPinnedColumns(): ColumnConfig[] {\n const columns = [...this.columns];\n const direction = getDirection(this.grid as unknown as HTMLElement);\n return getRightStickyColumns(columns, direction);\n }\n\n /**\n * Clear all sticky positioning.\n */\n clearStickyPositions(): void {\n clearStickyOffsets(this.grid as unknown as HTMLElement);\n }\n\n /**\n * Report horizontal scroll boundary offsets for pinned columns.\n * Used by keyboard navigation to ensure focused cells aren't hidden behind sticky columns.\n * @internal\n */\n override getHorizontalScrollOffsets(\n rowEl?: HTMLElement,\n focusedCell?: HTMLElement,\n ): { left: number; right: number; skipScroll?: boolean } | undefined {\n if (!this.isApplied) {\n return undefined;\n }\n\n let left = 0;\n let right = 0;\n\n if (rowEl) {\n // Calculate from rendered cells in the row\n const stickyLeftCells = rowEl.querySelectorAll('.sticky-left');\n const stickyRightCells = rowEl.querySelectorAll('.sticky-right');\n stickyLeftCells.forEach((el) => {\n left += (el as HTMLElement).offsetWidth;\n });\n stickyRightCells.forEach((el) => {\n right += (el as HTMLElement).offsetWidth;\n });\n } else {\n // Fall back to header row if no row element provided\n const host = this.grid as unknown as HTMLElement;\n const headerCells = host.querySelectorAll('.header-row .cell');\n headerCells.forEach((cell) => {\n if (cell.classList.contains('sticky-left')) {\n left += (cell as HTMLElement).offsetWidth;\n } else if (cell.classList.contains('sticky-right')) {\n right += (cell as HTMLElement).offsetWidth;\n }\n });\n }\n\n // Skip horizontal scrolling if focused cell is pinned (it's always visible)\n const skipScroll =\n focusedCell?.classList.contains('sticky-left') || focusedCell?.classList.contains('sticky-right');\n\n return { left, right, skipScroll };\n }\n // #endregion\n}\n","/**\n * Status Bar Rendering Logic\n *\n * Pure functions for creating and updating the status bar UI.\n * Includes both info bar and aggregation row rendering.\n */\n\nimport { getAggregator } from '../../core/internal/aggregators';\nimport type { ColumnConfig } from '../../core/types';\nimport type {\n AggregationRowConfig,\n AggregatorConfig,\n AggregatorDefinition,\n PinnedRowsConfig,\n PinnedRowsContext,\n PinnedRowsPanel,\n} from './types';\n\n/**\n * Check if an aggregator definition is a full config object (with aggFunc and optional formatter).\n */\nfunction isAggregatorConfig(def: AggregatorDefinition): def is AggregatorConfig {\n return typeof def === 'object' && def !== null && 'aggFunc' in def;\n}\n\n/**\n * Creates the info bar DOM element with all configured panels.\n *\n * @param config - The status bar configuration\n * @param context - The current grid context for rendering\n * @returns The complete info bar element\n */\nexport function createInfoBarElement(config: PinnedRowsConfig, context: PinnedRowsContext): HTMLElement {\n const pinnedRows = document.createElement('div');\n pinnedRows.className = 'tbw-pinned-rows';\n pinnedRows.setAttribute('role', 'presentation');\n pinnedRows.setAttribute('aria-live', 'polite');\n\n const left = document.createElement('div');\n left.className = 'tbw-pinned-rows-left';\n\n const center = document.createElement('div');\n center.className = 'tbw-pinned-rows-center';\n\n const right = document.createElement('div');\n right.className = 'tbw-pinned-rows-right';\n\n // Default panels - row count\n if (config.showRowCount !== false) {\n const rowCount = document.createElement('span');\n rowCount.className = 'tbw-status-panel tbw-status-panel-row-count';\n rowCount.textContent = `Total: ${context.totalRows} rows`;\n left.appendChild(rowCount);\n }\n\n // Filtered count panel (only shows when filter is active)\n if (config.showFilteredCount && context.filteredRows !== context.totalRows) {\n const filteredCount = document.createElement('span');\n filteredCount.className = 'tbw-status-panel tbw-status-panel-filtered-count';\n filteredCount.textContent = `Filtered: ${context.filteredRows}`;\n left.appendChild(filteredCount);\n }\n\n // Selected count panel (only shows when rows are selected)\n if (config.showSelectedCount && context.selectedRows > 0) {\n const selectedCount = document.createElement('span');\n selectedCount.className = 'tbw-status-panel tbw-status-panel-selected-count';\n selectedCount.textContent = `Selected: ${context.selectedRows}`;\n right.appendChild(selectedCount);\n }\n\n // Render custom panels\n if (config.customPanels) {\n for (const panel of config.customPanels) {\n const panelEl = renderCustomPanel(panel, context);\n switch (panel.position) {\n case 'left':\n left.appendChild(panelEl);\n break;\n case 'center':\n center.appendChild(panelEl);\n break;\n case 'right':\n right.appendChild(panelEl);\n break;\n }\n }\n }\n\n pinnedRows.appendChild(left);\n pinnedRows.appendChild(center);\n pinnedRows.appendChild(right);\n\n return pinnedRows;\n}\n\n/**\n * Creates a container for aggregation rows at top or bottom.\n *\n * @param position - 'top' or 'bottom'\n * @returns The container element\n */\nexport function createAggregationContainer(position: 'top' | 'bottom'): HTMLElement {\n const container = document.createElement('div');\n container.className = `tbw-aggregation-rows tbw-aggregation-rows-${position}`;\n // Use presentation role since aggregation rows are outside the role=\"grid\" element for layout reasons\n container.setAttribute('role', 'presentation');\n return container;\n}\n\n/**\n * Renders aggregation rows into a container.\n *\n * @param container - The container to render into\n * @param rows - Aggregation row configurations\n * @param columns - Current column configuration\n * @param dataRows - Current row data for aggregation calculations\n * @param globalFullWidth - Global fullWidth default from PinnedRowsConfig (default: false)\n */\nexport function renderAggregationRows(\n container: HTMLElement,\n rows: AggregationRowConfig[],\n columns: ColumnConfig[],\n dataRows: unknown[],\n globalFullWidth = false,\n): void {\n container.innerHTML = '';\n\n for (const rowConfig of rows) {\n const rowEl = document.createElement('div');\n rowEl.className = 'tbw-aggregation-row';\n // Use presentation role since aggregation rows are outside the role=\"grid\" element\n rowEl.setAttribute('role', 'presentation');\n if (rowConfig.id) {\n rowEl.setAttribute('data-aggregation-id', rowConfig.id);\n }\n\n // Per-row fullWidth overrides global default\n const isFullWidth = rowConfig.fullWidth ?? globalFullWidth;\n\n if (isFullWidth) {\n renderFullWidthAggregationRow(rowEl, rowConfig, columns, dataRows);\n } else {\n renderPerColumnAggregationRow(rowEl, rowConfig, columns, dataRows);\n }\n\n container.appendChild(rowEl);\n }\n}\n\n/**\n * Renders a full-width aggregation row: single spanning cell with label and inline aggregated values.\n */\nfunction renderFullWidthAggregationRow(\n rowEl: HTMLElement,\n rowConfig: AggregationRowConfig,\n columns: ColumnConfig[],\n dataRows: unknown[],\n): void {\n const cell = document.createElement('div');\n cell.className = 'tbw-aggregation-cell tbw-aggregation-cell-full';\n cell.style.gridColumn = '1 / -1';\n\n // Label (static string or dynamic function)\n const labelValue = typeof rowConfig.label === 'function' ? rowConfig.label(dataRows, columns) : rowConfig.label;\n if (labelValue) {\n const labelSpan = document.createElement('span');\n labelSpan.className = 'tbw-aggregation-label';\n labelSpan.textContent = labelValue;\n cell.appendChild(labelSpan);\n }\n\n // Inline aggregated values\n const aggregatesContainer = renderInlineAggregates(rowConfig, columns, dataRows);\n if (aggregatesContainer) {\n cell.appendChild(aggregatesContainer);\n }\n\n // If nothing was added (no label, no aggregates), ensure cell is empty but present\n rowEl.appendChild(cell);\n}\n\n/**\n * Renders per-column aggregation cells aligned to the grid template.\n */\nfunction renderPerColumnAggregationRow(\n rowEl: HTMLElement,\n rowConfig: AggregationRowConfig,\n columns: ColumnConfig[],\n dataRows: unknown[],\n): void {\n for (const col of columns) {\n const cell = document.createElement('div');\n cell.className = 'tbw-aggregation-cell';\n cell.setAttribute('data-field', col.field);\n\n const { value, formatter } = resolveAggregatedValue(rowConfig, col, dataRows);\n\n if (value != null) {\n cell.textContent = formatter ? formatter(value, col.field, col) : String(value);\n } else {\n cell.textContent = '';\n }\n rowEl.appendChild(cell);\n }\n}\n\n/**\n * Resolves the aggregated value for a single column in an aggregation row.\n * Returns the computed value and an optional formatter function.\n */\nfunction resolveAggregatedValue(\n rowConfig: AggregationRowConfig,\n col: ColumnConfig,\n dataRows: unknown[],\n): { value: unknown; formatter?: (value: unknown, field: string, column?: ColumnConfig) => string } {\n let value: unknown;\n let formatter: ((value: unknown, field: string, column?: ColumnConfig) => string) | undefined;\n\n // Check for aggregator first\n const aggDef = rowConfig.aggregators?.[col.field];\n if (aggDef) {\n if (isAggregatorConfig(aggDef)) {\n const aggFn = getAggregator(aggDef.aggFunc);\n if (aggFn) {\n value = aggFn(dataRows, col.field, col);\n }\n formatter = aggDef.formatter;\n } else {\n const aggFn = getAggregator(aggDef);\n if (aggFn) {\n value = aggFn(dataRows, col.field, col);\n }\n }\n } else if (rowConfig.cells && Object.prototype.hasOwnProperty.call(rowConfig.cells, col.field)) {\n const staticVal = rowConfig.cells[col.field];\n if (typeof staticVal === 'function') {\n value = staticVal(dataRows, col.field, col);\n } else {\n value = staticVal;\n }\n }\n\n return { value, formatter };\n}\n\n/**\n * Renders inline aggregate values for a full-width aggregation row.\n * Returns a container element with aggregate spans, or null if no aggregates are defined.\n */\nfunction renderInlineAggregates(\n rowConfig: AggregationRowConfig,\n columns: ColumnConfig[],\n dataRows: unknown[],\n): HTMLElement | null {\n // Collect fields that have aggregators or cell values\n const hasAggregators = rowConfig.aggregators && Object.keys(rowConfig.aggregators).length > 0;\n const hasCells = rowConfig.cells && Object.keys(rowConfig.cells).length > 0;\n if (!hasAggregators && !hasCells) return null;\n\n const container = document.createElement('span');\n container.className = 'tbw-aggregation-aggregates';\n\n for (const col of columns) {\n const { value, formatter } = resolveAggregatedValue(rowConfig, col, dataRows);\n if (value != null) {\n const span = document.createElement('span');\n span.className = 'tbw-aggregation-aggregate';\n span.setAttribute('data-field', col.field);\n const header = col.header ?? col.field;\n const displayValue = formatter ? formatter(value, col.field, col) : String(value);\n span.textContent = `${header}: ${displayValue}`;\n container.appendChild(span);\n }\n }\n\n return container.children.length > 0 ? container : null;\n}\n\n/**\n * Renders a custom panel element.\n *\n * @param panel - The panel definition\n * @param context - The current grid context\n * @returns The panel DOM element\n */\nfunction renderCustomPanel(panel: PinnedRowsPanel, context: PinnedRowsContext): HTMLElement {\n const panelEl = document.createElement('div');\n panelEl.className = 'tbw-status-panel tbw-status-panel-custom';\n panelEl.id = `status-panel-${panel.id}`;\n\n const content = panel.render(context);\n\n if (typeof content === 'string') {\n panelEl.innerHTML = content;\n } else {\n panelEl.appendChild(content);\n }\n\n return panelEl;\n}\n\n/**\n * Builds the status bar context from grid state and plugin states.\n *\n * @param rows - Current row data\n * @param columns - Current column configuration\n * @param grid - Grid element reference\n * @param selectionState - Optional selection plugin state\n * @param filterState - Optional filtering plugin state\n * @returns The status bar context\n */\nexport function buildContext(\n rows: unknown[],\n columns: unknown[],\n grid: HTMLElement,\n selectionState?: { selected: Set<number> } | null,\n filterState?: { cachedResult: unknown[] | null } | null,\n): PinnedRowsContext {\n return {\n totalRows: rows.length,\n filteredRows: filterState?.cachedResult?.length ?? rows.length,\n selectedRows: selectionState?.selected?.size ?? 0,\n columns: columns as PinnedRowsContext['columns'],\n rows,\n grid,\n };\n}\n\n// Keep old name as alias for backwards compatibility\nexport const createPinnedRowsElement = createInfoBarElement;\n","/**\n * Pinned Rows Plugin (Class-based)\n *\n * Adds info bars and aggregation rows to the grid.\n * - Info bar: Shows row counts, selection info, and custom panels\n * - Aggregation rows: Footer/header rows with computed values (sum, avg, etc.)\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport { buildContext, createAggregationContainer, createInfoBarElement, renderAggregationRows } from './pinned-rows';\nimport styles from './pinned-rows.css?inline';\nimport type { AggregationRowConfig, PinnedRowsConfig, PinnedRowsContext, PinnedRowsPanel } from './types';\n\n/**\n * Pinned Rows (Status Bar) Plugin for tbw-grid\n *\n * Creates fixed status bars at the top or bottom of the grid for displaying aggregations,\n * row counts, or custom content. Think of it as the \"totals row\" you'd see in a spreadsheet—\n * always visible regardless of scroll position.\n *\n * ## Installation\n *\n * ```ts\n * import { PinnedRowsPlugin } from '@toolbox-web/grid/plugins/pinned-rows';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `position` | `'top' \\| 'bottom'` | `'bottom'` | Status bar position |\n * | `showRowCount` | `boolean` | `true` | Show total row count |\n * | `showSelectedCount` | `boolean` | `true` | Show selected row count |\n * | `showFilteredCount` | `boolean` | `true` | Show filtered row count |\n * | `fullWidth` | `boolean` | `false` | Default fullWidth for aggregation rows |\n * | `aggregationRows` | `AggregationRowConfig[]` | - | Aggregation row configs |\n *\n * ## Built-in Aggregation Functions\n *\n * | Function | Description |\n * |----------|-------------|\n * | `sum` | Sum of values |\n * | `avg` | Average of values |\n * | `count` | Count of rows |\n * | `min` | Minimum value |\n * | `max` | Maximum value |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-pinned-rows-bg` | `var(--tbw-color-panel-bg)` | Status bar background |\n * | `--tbw-pinned-rows-border` | `var(--tbw-color-border)` | Status bar border |\n *\n * @example Status Bar with Aggregation\n * ```ts\n * import '@toolbox-web/grid';\n * import { PinnedRowsPlugin } from '@toolbox-web/grid/plugins/pinned-rows';\n *\n * grid.gridConfig = {\n * columns: [\n * { field: 'product', header: 'Product' },\n * { field: 'quantity', header: 'Qty', type: 'number' },\n * { field: 'price', header: 'Price', type: 'currency' },\n * ],\n * plugins: [\n * new PinnedRowsPlugin({\n * position: 'bottom',\n * showRowCount: true,\n * aggregationRows: [\n * {\n * id: 'totals',\n * aggregators: { quantity: 'sum', price: 'sum' },\n * cells: { product: 'Totals:' },\n * },\n * ],\n * }),\n * ],\n * };\n * ```\n *\n * @see {@link PinnedRowsConfig} for all configuration options\n * @see {@link AggregationRowConfig} for aggregation row structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class PinnedRowsPlugin extends BaseGridPlugin<PinnedRowsConfig> {\n /** @internal */\n readonly name = 'pinnedRows';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<PinnedRowsConfig> {\n return {\n position: 'bottom',\n showRowCount: true,\n showSelectedCount: true,\n showFilteredCount: true,\n };\n }\n\n // #region Internal State\n private infoBarElement: HTMLElement | null = null;\n private topAggregationContainer: HTMLElement | null = null;\n private bottomAggregationContainer: HTMLElement | null = null;\n private footerWrapper: HTMLElement | null = null;\n // #endregion\n\n // #region Lifecycle\n /** @internal */\n override detach(): void {\n if (this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n if (this.topAggregationContainer) {\n this.topAggregationContainer.remove();\n this.topAggregationContainer = null;\n }\n if (this.bottomAggregationContainer) {\n this.bottomAggregationContainer.remove();\n this.bottomAggregationContainer = null;\n }\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n }\n // #endregion\n\n // #region Hooks\n /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Use .tbw-scroll-area so footer is inside the horizontal scroll area,\n // otherwise fall back to .tbw-grid-content or root container\n const container =\n gridEl.querySelector('.tbw-scroll-area') ?? gridEl.querySelector('.tbw-grid-content') ?? gridEl.children[0];\n if (!container) return;\n\n // Clear orphaned element references if they were removed from the DOM\n // (e.g., by buildGridDOMIntoShadow calling replaceChildren())\n // We check if the element is still inside the container rather than isConnected,\n // because in unit tests the mock grid may not be attached to document.body\n if (this.footerWrapper && !container.contains(this.footerWrapper)) {\n this.footerWrapper = null;\n this.bottomAggregationContainer = null;\n this.infoBarElement = null;\n }\n if (this.topAggregationContainer && !container.contains(this.topAggregationContainer)) {\n this.topAggregationContainer = null;\n }\n if (this.infoBarElement && !container.contains(this.infoBarElement)) {\n this.infoBarElement = null;\n }\n\n // Build context with plugin states\n const selectionState = this.getSelectionState();\n const filterState = this.getFilterState();\n\n const context = buildContext(\n this.sourceRows as unknown[],\n this.columns as unknown[],\n this.grid as unknown as HTMLElement,\n selectionState,\n filterState,\n );\n\n // #region Handle Aggregation Rows\n const aggregationRows = this.config.aggregationRows || [];\n const topRows = aggregationRows.filter((r) => r.position === 'top');\n const bottomRows = aggregationRows.filter((r) => r.position !== 'top');\n\n // Top aggregation rows\n if (topRows.length > 0) {\n if (!this.topAggregationContainer) {\n this.topAggregationContainer = createAggregationContainer('top');\n const header = gridEl.querySelector('.header');\n if (header && header.nextSibling) {\n container.insertBefore(this.topAggregationContainer, header.nextSibling);\n } else {\n container.appendChild(this.topAggregationContainer);\n }\n }\n renderAggregationRows(\n this.topAggregationContainer,\n topRows,\n this.visibleColumns as ColumnConfig[],\n this.sourceRows as unknown[],\n this.config.fullWidth,\n );\n } else if (this.topAggregationContainer) {\n this.topAggregationContainer.remove();\n this.topAggregationContainer = null;\n }\n\n // Handle footer\n const hasInfoContent =\n this.config.showRowCount !== false ||\n (this.config.showSelectedCount && context.selectedRows > 0) ||\n (this.config.showFilteredCount && context.filteredRows !== context.totalRows) ||\n (this.config.customPanels && this.config.customPanels.length > 0);\n const hasBottomInfoBar = hasInfoContent && this.config.position !== 'top';\n const needsFooter = bottomRows.length > 0 || hasBottomInfoBar;\n\n // Handle top info bar\n if (hasInfoContent && this.config.position === 'top') {\n if (!this.infoBarElement) {\n this.infoBarElement = createInfoBarElement(this.config, context);\n container.insertBefore(this.infoBarElement, container.firstChild);\n } else {\n const newInfoBar = createInfoBarElement(this.config, context);\n this.infoBarElement.replaceWith(newInfoBar);\n this.infoBarElement = newInfoBar;\n }\n } else if (this.config.position === 'top' && this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n\n // Create/manage footer wrapper\n if (needsFooter) {\n if (!this.footerWrapper) {\n this.footerWrapper = document.createElement('div');\n this.footerWrapper.className = 'tbw-footer';\n container.appendChild(this.footerWrapper);\n }\n\n this.footerWrapper.innerHTML = '';\n\n if (bottomRows.length > 0) {\n if (!this.bottomAggregationContainer) {\n this.bottomAggregationContainer = createAggregationContainer('bottom');\n }\n this.footerWrapper.appendChild(this.bottomAggregationContainer);\n renderAggregationRows(\n this.bottomAggregationContainer,\n bottomRows,\n this.visibleColumns as ColumnConfig[],\n this.sourceRows as unknown[],\n this.config.fullWidth,\n );\n }\n\n if (hasBottomInfoBar) {\n this.infoBarElement = createInfoBarElement(this.config, context);\n this.footerWrapper.appendChild(this.infoBarElement);\n }\n } else {\n this.cleanupFooter();\n }\n // #endregion\n }\n // #endregion\n\n // #region Private Methods\n private cleanup(): void {\n if (this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n if (this.topAggregationContainer) {\n this.topAggregationContainer.remove();\n this.topAggregationContainer = null;\n }\n if (this.bottomAggregationContainer) {\n this.bottomAggregationContainer.remove();\n this.bottomAggregationContainer = null;\n }\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n }\n\n private cleanupFooter(): void {\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n if (this.bottomAggregationContainer) {\n this.bottomAggregationContainer.remove();\n this.bottomAggregationContainer = null;\n }\n if (this.infoBarElement && this.config.position !== 'top') {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n }\n\n private getSelectionState(): { selected: Set<number> } | null {\n // Try to get selection plugin state\n try {\n return (this.grid?.getPluginState?.('selection') as { selected: Set<number> } | null) ?? null;\n } catch {\n return null;\n }\n }\n\n private getFilterState(): { cachedResult: unknown[] | null } | null {\n try {\n return (this.grid?.getPluginState?.('filtering') as { cachedResult: unknown[] | null } | null) ?? null;\n } catch {\n return null;\n }\n }\n // #endregion\n\n // #region Public API\n /**\n * Refresh the status bar to reflect current grid state.\n */\n refresh(): void {\n this.requestRender();\n }\n\n /**\n * Get the current status bar context.\n * @returns The context with row counts and other info\n */\n getContext(): PinnedRowsContext {\n const selectionState = this.getSelectionState();\n const filterState = this.getFilterState();\n\n return buildContext(\n this.rows as unknown[],\n this.columns as unknown[],\n this.grid as unknown as HTMLElement,\n selectionState,\n filterState,\n );\n }\n\n /**\n * Add a custom panel to the info bar.\n * @param panel - The panel configuration to add\n */\n addPanel(panel: PinnedRowsPanel): void {\n if (!this.config.customPanels) {\n this.config.customPanels = [];\n }\n this.config.customPanels.push(panel);\n this.requestRender();\n }\n\n /**\n * Remove a custom panel by ID.\n * @param id - The panel ID to remove\n */\n removePanel(id: string): void {\n if (this.config.customPanels) {\n this.config.customPanels = this.config.customPanels.filter((p) => p.id !== id);\n this.requestRender();\n }\n }\n\n /**\n * Add an aggregation row.\n * @param row - The aggregation row configuration\n */\n addAggregationRow(row: AggregationRowConfig): void {\n if (!this.config.aggregationRows) {\n this.config.aggregationRows = [];\n }\n this.config.aggregationRows.push(row);\n this.requestRender();\n }\n\n /**\n * Remove an aggregation row by ID.\n * @param id - The aggregation row ID to remove\n */\n removeAggregationRow(id: string): void {\n if (this.config.aggregationRows) {\n this.config.aggregationRows = this.config.aggregationRows.filter((r) => r.id !== id);\n this.requestRender();\n }\n }\n // #endregion\n}\n","import { getValueAggregator } from '../../core/internal/aggregators';\nimport type { PivotConfig } from './types';\n\n// Re-export for backward compatibility within pivot plugin\nexport const getPivotAggregator = getValueAggregator;\n\nexport function validatePivotConfig(config: PivotConfig): string[] {\n const errors: string[] = [];\n\n if (!config.rowGroupFields?.length && !config.columnGroupFields?.length) {\n errors.push('At least one row or column group field is required');\n }\n\n if (!config.valueFields?.length) {\n errors.push('At least one value field is required');\n }\n\n return errors;\n}\n\nexport function createValueKey(columnValues: string[], valueField: string): string {\n return [...columnValues, valueField].join('|');\n}\n","import { createValueKey, getPivotAggregator } from './pivot-model';\nimport type { PivotConfig, PivotResult, PivotRow, PivotValueField } from './types';\n\nexport type PivotDataRow = Record<string, unknown>;\n\n/**\n * Build a hierarchical pivot result from flat data.\n * Supports multiple row group fields for nested hierarchy.\n */\nexport function buildPivot(rows: PivotDataRow[], config: PivotConfig): PivotResult {\n const rowGroupFields = config.rowGroupFields ?? [];\n const columnGroupFields = config.columnGroupFields ?? [];\n const valueFields = config.valueFields ?? [];\n\n // Get unique column combinations\n const columnKeys = getUniqueColumnKeys(rows, columnGroupFields);\n\n // Build hierarchical pivot rows\n const pivotRows = buildHierarchicalPivotRows(\n rows,\n rowGroupFields,\n columnGroupFields,\n columnKeys,\n valueFields,\n 0, // starting depth\n '', // parent key prefix\n );\n\n // Calculate grand totals\n const totals = calculateTotals(pivotRows, columnKeys, valueFields);\n const grandTotal = Object.values(totals).reduce((a, b) => a + b, 0);\n\n return {\n rows: pivotRows,\n columnKeys,\n totals,\n grandTotal,\n };\n}\n\n/**\n * Get unique column key combinations from the data.\n */\nexport function getUniqueColumnKeys(rows: PivotDataRow[], columnFields: string[]): string[] {\n if (columnFields.length === 0) return ['value'];\n\n const keys = new Set<string>();\n for (const row of rows) {\n const key = columnFields.map((f) => String(row[f] ?? '')).join('|');\n keys.add(key);\n }\n return [...keys].sort();\n}\n\n/**\n * Group rows by a single field.\n */\nexport function groupByField(rows: PivotDataRow[], field: string): Map<string, PivotDataRow[]> {\n const groups = new Map<string, PivotDataRow[]>();\n\n for (const row of rows) {\n const key = String(row[field] ?? '');\n const existing = groups.get(key);\n if (existing) {\n existing.push(row);\n } else {\n groups.set(key, [row]);\n }\n }\n\n return groups;\n}\n\n/**\n * Group rows by multiple fields (legacy flat grouping).\n */\nexport function groupByFields(rows: PivotDataRow[], fields: string[]): Map<string, PivotDataRow[]> {\n const groups = new Map<string, PivotDataRow[]>();\n\n for (const row of rows) {\n const key = fields.map((f) => String(row[f] ?? '')).join('|');\n const existing = groups.get(key);\n if (existing) {\n existing.push(row);\n } else {\n groups.set(key, [row]);\n }\n }\n\n return groups;\n}\n\n/**\n * Build hierarchical pivot rows recursively.\n * Each level of rowGroupFields creates a new depth level.\n */\nexport function buildHierarchicalPivotRows(\n rows: PivotDataRow[],\n rowGroupFields: string[],\n columnFields: string[],\n columnKeys: string[],\n valueFields: PivotValueField[],\n depth: number,\n parentKey: string,\n): PivotRow[] {\n const result: PivotRow[] = [];\n\n // If no more row group fields, we're at the leaf level - aggregate the data\n if (rowGroupFields.length === 0) {\n // This shouldn't normally happen as we need at least one grouping field\n // But handle it by creating a single aggregated row\n const values = aggregateValues(rows, columnFields, columnKeys, valueFields);\n const total = calculateRowTotal(values);\n result.push({\n rowKey: parentKey || 'all',\n rowLabel: parentKey || 'All',\n depth,\n values,\n total,\n isGroup: false,\n rowCount: rows.length,\n });\n return result;\n }\n\n // Get the current grouping field\n const currentField = rowGroupFields[0];\n const remainingFields = rowGroupFields.slice(1);\n const hasChildren = remainingFields.length > 0;\n\n // Group rows by current field\n const grouped = groupByField(rows, currentField);\n\n for (const [groupValue, groupRows] of grouped) {\n const rowKey = parentKey ? `${parentKey}|${groupValue}` : groupValue;\n\n // Aggregate values for this group (sum of all child rows)\n const values = aggregateValues(groupRows, columnFields, columnKeys, valueFields);\n const total = calculateRowTotal(values);\n\n // Build children if there are more grouping levels\n let children: PivotRow[] | undefined;\n if (hasChildren) {\n children = buildHierarchicalPivotRows(\n groupRows,\n remainingFields,\n columnFields,\n columnKeys,\n valueFields,\n depth + 1,\n rowKey,\n );\n }\n\n result.push({\n rowKey,\n rowLabel: groupValue || '(blank)',\n depth,\n values,\n total,\n isGroup: hasChildren,\n children,\n rowCount: groupRows.length,\n });\n }\n\n return result;\n}\n\n/**\n * Aggregate values for a set of rows across all column keys.\n */\nexport function aggregateValues(\n rows: PivotDataRow[],\n columnFields: string[],\n columnKeys: string[],\n valueFields: PivotValueField[],\n): Record<string, number | null> {\n const values: Record<string, number | null> = {};\n\n for (const colKey of columnKeys) {\n for (const vf of valueFields) {\n // Filter rows that match this column key\n const matchingRows =\n columnFields.length > 0\n ? rows.filter((r) => columnFields.map((f) => String(r[f] ?? '')).join('|') === colKey)\n : rows;\n\n const nums = matchingRows.map((r) => Number(r[vf.field]) || 0);\n const aggregator = getPivotAggregator(vf.aggFunc);\n const aggregatedResult = nums.length > 0 ? aggregator(nums) : null;\n\n const valueKey = createValueKey([colKey], vf.field);\n values[valueKey] = aggregatedResult;\n }\n }\n\n return values;\n}\n\n/**\n * Calculate the total for a row's values.\n */\nexport function calculateRowTotal(values: Record<string, number | null>): number {\n let sum = 0;\n for (const val of Object.values(values)) {\n sum += val ?? 0;\n }\n return sum;\n}\n\n/**\n * Legacy flat pivot row building (for backwards compatibility).\n */\nexport function buildPivotRows(\n groupedData: Map<string, PivotDataRow[]>,\n columnFields: string[],\n columnKeys: string[],\n valueFields: PivotValueField[],\n depth: number,\n): PivotRow[] {\n const result: PivotRow[] = [];\n\n for (const [rowKey, groupRows] of groupedData) {\n const values = aggregateValues(groupRows, columnFields, columnKeys, valueFields);\n const total = calculateRowTotal(values);\n\n result.push({\n rowKey,\n rowLabel: rowKey || '(blank)',\n depth,\n values,\n total,\n isGroup: false,\n rowCount: groupRows.length,\n });\n }\n\n return result;\n}\n\n/**\n * Calculate grand totals across all pivot rows.\n */\nexport function calculateTotals(\n pivotRows: PivotRow[],\n columnKeys: string[],\n valueFields: PivotValueField[],\n): Record<string, number> {\n const totals: Record<string, number> = {};\n\n // Recursively sum all rows (including nested children)\n function sumRows(rows: PivotRow[]) {\n for (const row of rows) {\n // Only count leaf rows to avoid double-counting\n if (!row.isGroup || !row.children?.length) {\n for (const colKey of columnKeys) {\n for (const vf of valueFields) {\n const valueKey = createValueKey([colKey], vf.field);\n totals[valueKey] = (totals[valueKey] ?? 0) + (row.values[valueKey] ?? 0);\n }\n }\n } else if (row.children) {\n sumRows(row.children);\n }\n }\n }\n\n sumRows(pivotRows);\n return totals;\n}\n\n/**\n * Flatten hierarchical pivot rows for rendering.\n * Respects expanded state - only includes children of expanded groups.\n */\nexport function flattenPivotRows(rows: PivotRow[], expandedKeys?: Set<string>, defaultExpanded = true): PivotRow[] {\n const result: PivotRow[] = [];\n\n function flatten(row: PivotRow) {\n result.push(row);\n\n // Check if this group is expanded\n const isExpanded = expandedKeys ? expandedKeys.has(row.rowKey) : defaultExpanded;\n\n // Only include children if expanded\n if (row.children && isExpanded) {\n for (const child of row.children) {\n flatten(child);\n }\n }\n }\n\n for (const row of rows) {\n flatten(row);\n }\n\n return result;\n}\n\n/**\n * Get all group keys from pivot rows (for expand all / collapse all).\n */\nexport function getAllGroupKeys(rows: PivotRow[]): string[] {\n const keys: string[] = [];\n\n function collectKeys(row: PivotRow) {\n if (row.isGroup) {\n keys.push(row.rowKey);\n }\n if (row.children) {\n for (const child of row.children) {\n collectKeys(child);\n }\n }\n }\n\n for (const row of rows) {\n collectKeys(row);\n }\n\n return keys;\n}\n","/**\n * Pivot Tool Panel Rendering\n *\n * Pure functions for rendering the pivot configuration panel.\n * Separated from PivotPlugin for better code organization.\n */\n\nimport type { AggFunc, PivotConfig, PivotValueField } from './types';\n\n/** All available aggregation functions */\nexport const AGG_FUNCS: AggFunc[] = ['sum', 'avg', 'count', 'min', 'max', 'first', 'last'];\n\n/** Field info for available fields */\nexport interface FieldInfo {\n field: string;\n header: string;\n}\n\n/** Callbacks for panel interactions */\nexport interface PanelCallbacks {\n onTogglePivot: (enabled: boolean) => void;\n onAddFieldToZone: (field: string, zone: 'rowGroups' | 'columnGroups') => void;\n onRemoveFieldFromZone: (field: string, zone: 'rowGroups' | 'columnGroups') => void;\n onAddValueField: (field: string, aggFunc: AggFunc) => void;\n onRemoveValueField: (field: string) => void;\n onUpdateValueAggFunc: (field: string, aggFunc: AggFunc) => void;\n onOptionChange: (option: 'showTotals' | 'showGrandTotal', value: boolean) => void;\n getAvailableFields: () => FieldInfo[];\n}\n\n/** Internal context passed to rendering functions */\ninterface RenderContext {\n config: PivotConfig;\n callbacks: PanelCallbacks;\n signal: AbortSignal;\n}\n\n/**\n * Render the complete pivot panel content.\n * Returns a cleanup function that removes all event listeners and DOM elements.\n */\nexport function renderPivotPanel(\n container: HTMLElement,\n config: PivotConfig,\n isActive: boolean,\n callbacks: PanelCallbacks,\n): () => void {\n // Create AbortController for automatic listener cleanup\n const controller = new AbortController();\n const ctx: RenderContext = { config, callbacks, signal: controller.signal };\n\n const wrapper = document.createElement('div');\n wrapper.className = 'tbw-pivot-panel';\n\n // Options section (at top, includes pivot toggle)\n wrapper.appendChild(createSection('Options', () => createOptionsPanel(isActive, ctx)));\n\n // Row Groups section\n wrapper.appendChild(createSection('Row Groups', () => createFieldZone('rowGroups', ctx)));\n\n // Column Groups section\n wrapper.appendChild(createSection('Column Groups', () => createFieldZone('columnGroups', ctx)));\n\n // Values section\n wrapper.appendChild(createSection('Values', () => createValuesZone(ctx)));\n\n // Available fields section\n wrapper.appendChild(createSection('Available Fields', () => createAvailableFieldsZone(ctx)));\n\n container.appendChild(wrapper);\n\n // Cleanup: abort all listeners, then remove DOM\n return () => {\n controller.abort();\n wrapper.remove();\n };\n}\n\n/**\n * Create a collapsible section wrapper.\n */\nfunction createSection(title: string, contentFactory: () => HTMLElement): HTMLElement {\n const section = document.createElement('div');\n section.className = 'tbw-pivot-section';\n\n const header = document.createElement('div');\n header.className = 'tbw-pivot-section-header';\n header.textContent = title;\n\n const content = document.createElement('div');\n content.className = 'tbw-pivot-section-content';\n content.appendChild(contentFactory());\n\n section.appendChild(header);\n section.appendChild(content);\n\n return section;\n}\n\n/**\n * Create a drop zone for row/column group fields.\n */\nfunction createFieldZone(zoneType: 'rowGroups' | 'columnGroups', ctx: RenderContext): HTMLElement {\n const { config, callbacks, signal } = ctx;\n const zone = document.createElement('div');\n zone.className = 'tbw-pivot-drop-zone';\n zone.setAttribute('data-zone', zoneType);\n\n const currentFields = zoneType === 'rowGroups' ? (config.rowGroupFields ?? []) : (config.columnGroupFields ?? []);\n\n if (currentFields.length === 0) {\n const placeholder = document.createElement('div');\n placeholder.className = 'tbw-pivot-placeholder';\n placeholder.textContent = 'Drag fields here or click to add';\n zone.appendChild(placeholder);\n } else {\n for (const field of currentFields) {\n zone.appendChild(createFieldChip(field, zoneType, ctx));\n }\n }\n\n // Drop handling\n zone.addEventListener(\n 'dragover',\n (e) => {\n e.preventDefault();\n zone.classList.add('drag-over');\n },\n { signal },\n );\n\n zone.addEventListener(\n 'dragleave',\n () => {\n zone.classList.remove('drag-over');\n },\n { signal },\n );\n\n zone.addEventListener(\n 'drop',\n (e) => {\n e.preventDefault();\n zone.classList.remove('drag-over');\n\n const field = e.dataTransfer?.getData('text/plain');\n if (field) {\n callbacks.onAddFieldToZone(field, zoneType);\n }\n },\n { signal },\n );\n\n return zone;\n}\n\n/**\n * Create a field chip for row/column zones.\n */\nfunction createFieldChip(field: string, zoneType: 'rowGroups' | 'columnGroups', ctx: RenderContext): HTMLElement {\n const { callbacks, signal } = ctx;\n const chip = document.createElement('div');\n chip.className = 'tbw-pivot-field-chip';\n chip.draggable = true;\n\n const fieldInfo = callbacks.getAvailableFields().find((f) => f.field === field);\n const label = document.createElement('span');\n label.className = 'tbw-pivot-chip-label';\n label.textContent = fieldInfo?.header ?? field;\n\n const removeBtn = document.createElement('button');\n removeBtn.className = 'tbw-pivot-chip-remove';\n removeBtn.innerHTML = '×';\n removeBtn.title = 'Remove field';\n removeBtn.addEventListener(\n 'click',\n (e) => {\n e.stopPropagation();\n callbacks.onRemoveFieldFromZone(field, zoneType);\n },\n { signal },\n );\n\n chip.appendChild(label);\n chip.appendChild(removeBtn);\n\n // Drag handling for reordering\n chip.addEventListener(\n 'dragstart',\n (e) => {\n e.dataTransfer?.setData('text/plain', field);\n e.dataTransfer?.setData('source-zone', zoneType);\n chip.classList.add('dragging');\n },\n { signal },\n );\n\n chip.addEventListener(\n 'dragend',\n () => {\n chip.classList.remove('dragging');\n },\n { signal },\n );\n\n return chip;\n}\n\n/**\n * Create the values zone with aggregation controls.\n */\nfunction createValuesZone(ctx: RenderContext): HTMLElement {\n const { config, callbacks, signal } = ctx;\n const zone = document.createElement('div');\n zone.className = 'tbw-pivot-drop-zone tbw-pivot-values-zone';\n zone.setAttribute('data-zone', 'values');\n\n const currentValues = config.valueFields ?? [];\n\n if (currentValues.length === 0) {\n const placeholder = document.createElement('div');\n placeholder.className = 'tbw-pivot-placeholder';\n placeholder.textContent = 'Drag numeric fields here for aggregation';\n zone.appendChild(placeholder);\n } else {\n for (const valueField of currentValues) {\n zone.appendChild(createValueChip(valueField, ctx));\n }\n }\n\n // Drop handling with signal for cleanup\n zone.addEventListener(\n 'dragover',\n (e) => {\n e.preventDefault();\n zone.classList.add('drag-over');\n },\n { signal },\n );\n\n zone.addEventListener(\n 'dragleave',\n () => {\n zone.classList.remove('drag-over');\n },\n { signal },\n );\n\n zone.addEventListener(\n 'drop',\n (e) => {\n e.preventDefault();\n zone.classList.remove('drag-over');\n const field = e.dataTransfer?.getData('text/plain');\n if (field) {\n callbacks.onAddValueField(field, 'sum');\n }\n },\n { signal },\n );\n\n return zone;\n}\n\n/**\n * Create a value chip with aggregation selector.\n */\nfunction createValueChip(valueField: PivotValueField, ctx: RenderContext): HTMLElement {\n const { callbacks, signal } = ctx;\n const chip = document.createElement('div');\n chip.className = 'tbw-pivot-field-chip tbw-pivot-value-chip';\n\n const fieldInfo = callbacks.getAvailableFields().find((f) => f.field === valueField.field);\n\n const labelWrapper = document.createElement('div');\n labelWrapper.className = 'tbw-pivot-value-label-wrapper';\n\n const label = document.createElement('span');\n label.className = 'tbw-pivot-chip-label';\n label.textContent = fieldInfo?.header ?? valueField.field;\n\n const aggSelect = document.createElement('select');\n aggSelect.className = 'tbw-pivot-agg-select';\n aggSelect.title = 'Aggregation function';\n\n for (const aggFunc of AGG_FUNCS) {\n const option = document.createElement('option');\n option.value = aggFunc;\n option.textContent = aggFunc.toUpperCase();\n option.selected = aggFunc === valueField.aggFunc;\n aggSelect.appendChild(option);\n }\n\n aggSelect.addEventListener(\n 'change',\n () => {\n callbacks.onUpdateValueAggFunc(valueField.field, aggSelect.value as AggFunc);\n },\n { signal },\n );\n\n const removeBtn = document.createElement('button');\n removeBtn.className = 'tbw-pivot-chip-remove';\n removeBtn.innerHTML = '×';\n removeBtn.title = 'Remove value field';\n removeBtn.addEventListener(\n 'click',\n (e) => {\n e.stopPropagation();\n callbacks.onRemoveValueField(valueField.field);\n },\n { signal },\n );\n\n labelWrapper.appendChild(label);\n labelWrapper.appendChild(aggSelect);\n\n chip.appendChild(labelWrapper);\n chip.appendChild(removeBtn);\n\n return chip;\n}\n\n/**\n * Create the available fields zone.\n */\nfunction createAvailableFieldsZone(ctx: RenderContext): HTMLElement {\n const { config, callbacks, signal } = ctx;\n const zone = document.createElement('div');\n zone.className = 'tbw-pivot-available-fields';\n\n const allFields = callbacks.getAvailableFields();\n const usedFields = new Set([\n ...(config.rowGroupFields ?? []),\n ...(config.columnGroupFields ?? []),\n ...(config.valueFields?.map((v) => v.field) ?? []),\n ]);\n\n // Filter to show only unused fields\n const availableFields = allFields.filter((f) => !usedFields.has(f.field));\n\n if (availableFields.length === 0) {\n const empty = document.createElement('div');\n empty.className = 'tbw-pivot-placeholder';\n empty.textContent = 'All fields are in use';\n zone.appendChild(empty);\n } else {\n for (const field of availableFields) {\n const chip = document.createElement('div');\n chip.className = 'tbw-pivot-field-chip available';\n chip.textContent = field.header;\n chip.draggable = true;\n chip.title = `Drag to add \"${field.field}\" to a zone`;\n\n chip.addEventListener(\n 'dragstart',\n (e) => {\n e.dataTransfer?.setData('text/plain', field.field);\n chip.classList.add('dragging');\n },\n { signal },\n );\n\n chip.addEventListener(\n 'dragend',\n () => {\n chip.classList.remove('dragging');\n },\n { signal },\n );\n\n zone.appendChild(chip);\n }\n }\n\n return zone;\n}\n\n/**\n * Create the options panel with pivot toggle and checkboxes for totals.\n */\nfunction createOptionsPanel(isActive: boolean, ctx: RenderContext): HTMLElement {\n const { config, callbacks, signal } = ctx;\n const panel = document.createElement('div');\n panel.className = 'tbw-pivot-options';\n\n // Pivot Mode toggle\n panel.appendChild(\n createCheckbox(\n 'Enable Pivot View',\n isActive,\n (checked) => {\n callbacks.onTogglePivot(checked);\n },\n signal,\n ),\n );\n\n // Show Totals checkbox\n panel.appendChild(\n createCheckbox(\n 'Show Row Totals',\n config.showTotals ?? true,\n (checked) => {\n callbacks.onOptionChange('showTotals', checked);\n },\n signal,\n ),\n );\n\n // Show Grand Total checkbox\n panel.appendChild(\n createCheckbox(\n 'Show Grand Total',\n config.showGrandTotal ?? true,\n (checked) => {\n callbacks.onOptionChange('showGrandTotal', checked);\n },\n signal,\n ),\n );\n\n return panel;\n}\n\n/**\n * Create a checkbox with label.\n */\nfunction createCheckbox(\n label: string,\n checked: boolean,\n onChange: (checked: boolean) => void,\n signal: AbortSignal,\n): HTMLElement {\n const wrapper = document.createElement('label');\n wrapper.className = 'tbw-pivot-checkbox';\n\n const input = document.createElement('input');\n input.type = 'checkbox';\n input.checked = checked;\n input.addEventListener('change', () => onChange(input.checked), { signal });\n\n const span = document.createElement('span');\n span.textContent = label;\n\n wrapper.appendChild(input);\n wrapper.appendChild(span);\n\n return wrapper;\n}\n","/**\n * Pivot Row Rendering\n *\n * Pure functions for rendering pivot rows (group rows, leaf rows, grand total).\n * Separated from PivotPlugin for better code organization.\n */\n\nimport type { ColumnConfig, IconValue } from '../../core/types';\n\n/** Row data with pivot metadata */\nexport interface PivotRowData {\n __pivotRowKey?: string;\n __pivotLabel?: string;\n __pivotDepth?: number;\n __pivotIndent?: number;\n __pivotExpanded?: boolean;\n __pivotHasChildren?: boolean;\n __pivotRowCount?: number;\n __pivotIsGrandTotal?: boolean;\n [key: string]: unknown;\n}\n\n/** Context for row rendering */\nexport interface RowRenderContext {\n columns: ColumnConfig[];\n rowIndex: number;\n onToggle: (key: string) => void;\n resolveIcon: (iconKey: 'expand' | 'collapse') => IconValue;\n setIcon: (element: HTMLElement, icon: IconValue) => void;\n}\n\n/**\n * Render a pivot group row (has children, can expand/collapse).\n */\nexport function renderPivotGroupRow(row: PivotRowData, rowEl: HTMLElement, ctx: RowRenderContext): boolean {\n rowEl.className = 'data-grid-row pivot-group-row';\n rowEl.setAttribute('data-pivot-depth', String(row.__pivotDepth ?? 0));\n rowEl.setAttribute('data-pivot-key', String(row.__pivotRowKey ?? ''));\n rowEl.setAttribute('role', 'row');\n // Note: aria-expanded is not set here because it's only valid in treegrid, not grid\n // The expand/collapse state is conveyed via the toggle button's aria-label\n rowEl.innerHTML = '';\n\n ctx.columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell';\n cell.setAttribute('data-col', String(colIdx));\n cell.setAttribute('data-row', String(ctx.rowIndex));\n cell.setAttribute('role', 'gridcell');\n\n if (colIdx === 0) {\n // First column: indent + toggle + label + count\n const indent = Number(row.__pivotIndent) || 0;\n cell.style.paddingLeft = `${indent}px`;\n\n // Toggle button\n const rowKey = String(row.__pivotRowKey);\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'pivot-toggle';\n btn.setAttribute('aria-label', row.__pivotExpanded ? 'Collapse group' : 'Expand group');\n ctx.setIcon(btn, ctx.resolveIcon(row.__pivotExpanded ? 'collapse' : 'expand'));\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n ctx.onToggle(rowKey);\n });\n cell.appendChild(btn);\n\n // Group label\n const label = document.createElement('span');\n label.className = 'pivot-label';\n label.textContent = String(row.__pivotLabel ?? '');\n cell.appendChild(label);\n\n // Row count\n const count = document.createElement('span');\n count.className = 'pivot-count';\n count.textContent = ` (${Number(row.__pivotRowCount) || 0})`;\n cell.appendChild(count);\n } else {\n // Other columns: render value\n const value = row[col.field];\n cell.textContent = value != null ? String(value) : '';\n }\n\n rowEl.appendChild(cell);\n });\n\n return true;\n}\n\n/**\n * Render a pivot leaf row (no children, just indentation).\n */\nexport function renderPivotLeafRow(\n row: PivotRowData,\n rowEl: HTMLElement,\n columns: ColumnConfig[],\n rowIndex: number,\n): boolean {\n rowEl.className = 'data-grid-row pivot-leaf-row';\n rowEl.setAttribute('data-pivot-depth', String(row.__pivotDepth ?? 0));\n rowEl.setAttribute('data-pivot-key', String(row.__pivotRowKey ?? ''));\n rowEl.innerHTML = '';\n\n columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell';\n cell.setAttribute('data-col', String(colIdx));\n cell.setAttribute('data-row', String(rowIndex));\n cell.setAttribute('role', 'gridcell');\n\n if (colIdx === 0) {\n // First column: indent + label (no toggle for leaves)\n const indent = Number(row.__pivotIndent) || 0;\n // Add extra indent for alignment with toggle button\n cell.style.paddingLeft = `${indent + 20}px`;\n\n const label = document.createElement('span');\n label.className = 'pivot-label';\n label.textContent = String(row.__pivotLabel ?? '');\n cell.appendChild(label);\n } else {\n // Other columns: render value\n const value = row[col.field];\n cell.textContent = value != null ? String(value) : '';\n }\n\n rowEl.appendChild(cell);\n });\n\n return true;\n}\n\n/**\n * Render the grand total row.\n */\nexport function renderPivotGrandTotalRow(row: PivotRowData, rowEl: HTMLElement, columns: ColumnConfig[]): boolean {\n rowEl.className = 'pivot-grand-total-row';\n // Use role=presentation since grand total is rendered outside the role=grid element\n rowEl.setAttribute('role', 'presentation');\n rowEl.innerHTML = '';\n\n columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell';\n cell.setAttribute('data-col', String(colIdx));\n // No role attribute - parent row has role=presentation so children don't need grid semantics\n\n if (colIdx === 0) {\n // First column: Grand Total label\n const label = document.createElement('span');\n label.className = 'pivot-label';\n label.textContent = 'Grand Total';\n cell.appendChild(label);\n } else {\n // Other columns: render totals\n const value = row[col.field];\n cell.textContent = value != null ? String(value) : '';\n }\n\n rowEl.appendChild(cell);\n });\n\n return true;\n}\n","/**\n * Pivot Plugin (Class-based)\n *\n * Provides pivot table functionality for tbw-grid.\n * Transforms flat data into grouped, aggregated pivot views.\n * Includes a tool panel for interactive pivot configuration.\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, ToolPanelDefinition } from '../../core/types';\nimport { buildPivot, flattenPivotRows, getAllGroupKeys, type PivotDataRow } from './pivot-engine';\nimport { createValueKey, validatePivotConfig } from './pivot-model';\nimport { renderPivotPanel, type FieldInfo, type PanelCallbacks } from './pivot-panel';\nimport { renderPivotGrandTotalRow, renderPivotGroupRow, renderPivotLeafRow, type PivotRowData } from './pivot-rows';\nimport type { AggFunc, ExpandCollapseAnimation, PivotConfig, PivotResult, PivotValueField } from './types';\n\n// Import CSS as inline string (Vite handles this)\nimport styles from './pivot.css?inline';\n\n/**\n * Pivot Table Plugin for tbw-grid\n *\n * Transforms flat data into a pivot table view with grouped rows, grouped columns,\n * and aggregated values. Includes an interactive tool panel for configuring\n * row groups, column groups, and value aggregations at runtime.\n *\n * ## Installation\n *\n * ```ts\n * import { PivotPlugin } from '@toolbox-web/grid/plugins/pivot';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `active` | `boolean` | `true` | Whether pivot is active on load |\n * | `rowGroupFields` | `string[]` | `[]` | Fields for row grouping |\n * | `columnGroupFields` | `string[]` | `[]` | Fields for column grouping |\n * | `valueFields` | `ValueField[]` | `[]` | Aggregation value fields |\n * | `showTotals` | `boolean` | `true` | Show row subtotals |\n * | `showGrandTotal` | `boolean` | `true` | Show grand total row |\n * | `showToolPanel` | `boolean` | `true` | Show interactive pivot panel |\n * | `defaultExpanded` | `boolean` | `true` | Groups expanded by default |\n * | `indentWidth` | `number` | `20` | Indent per depth level (px) |\n * | `animation` | `false \\| 'slide' \\| 'fade'` | `'slide'` | Expand/collapse animation |\n *\n * ## Aggregation Functions\n *\n * `sum`, `avg`, `count`, `min`, `max`, `first`, `last`\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `expandGroup` | `(path: string[]) => void` | Expand a specific group |\n * | `collapseGroup` | `(path: string[]) => void` | Collapse a specific group |\n * | `expandAll` | `() => void` | Expand all groups |\n * | `collapseAll` | `() => void` | Collapse all groups |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-pivot-group-bg` | `var(--tbw-color-row-alt)` | Group row background |\n * | `--tbw-pivot-grand-total-bg` | `var(--tbw-color-header-bg)` | Grand total row |\n *\n * @example Basic Pivot Table\n * ```ts\n * import '@toolbox-web/grid';\n * import { PivotPlugin } from '@toolbox-web/grid/plugins/pivot';\n *\n * grid.gridConfig = {\n * columns: [...],\n * plugins: [\n * new PivotPlugin({\n * rowGroupFields: ['region', 'product'],\n * columnGroupFields: ['quarter'],\n * valueFields: [{ field: 'sales', aggFunc: 'sum', header: 'Total' }],\n * }),\n * ],\n * };\n * ```\n *\n * @example Programmatic-Only (No Tool Panel)\n * ```ts\n * new PivotPlugin({\n * showToolPanel: false,\n * rowGroupFields: ['category'],\n * valueFields: [{ field: 'amount', aggFunc: 'sum' }],\n * })\n * ```\n *\n * @see {@link PivotConfig} for all configuration options\n * @see {@link PivotValueField} for value field structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class PivotPlugin extends BaseGridPlugin<PivotConfig> {\n /** @internal */\n readonly name = 'pivot';\n /** @internal */\n override readonly styles = styles;\n\n /** Tool panel ID for shell integration */\n static readonly PANEL_ID = 'pivot';\n\n /** @internal */\n protected override get defaultConfig(): Partial<PivotConfig> {\n return {\n active: true,\n showTotals: true,\n showGrandTotal: true,\n showToolPanel: true,\n animation: 'slide',\n };\n }\n\n // #region Internal State\n private isActive = false;\n private hasInitialized = false;\n private pivotResult: PivotResult | null = null;\n private fieldHeaderMap: Map<string, string> = new Map();\n private expandedKeys: Set<string> = new Set();\n private defaultExpanded = true;\n /** Tracks whether user has manually interacted with expand/collapse */\n private userHasToggledExpand = false;\n private originalColumns: Array<{ field: string; header: string }> = [];\n private panelContainer: HTMLElement | null = null;\n private grandTotalFooter: HTMLElement | null = null;\n private previousVisibleKeys = new Set<string>();\n private keysToAnimate = new Set<string>();\n\n /**\n * Check if the plugin has valid pivot configuration (at least value fields).\n */\n private hasValidPivotConfig(): boolean {\n return (this.config.valueFields?.length ?? 0) > 0;\n }\n\n /**\n * Get expand/collapse animation style from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide';\n }\n\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.isActive = false;\n this.hasInitialized = false;\n this.pivotResult = null;\n this.fieldHeaderMap.clear();\n this.originalColumns = [];\n this.panelContainer = null;\n this.cleanupGrandTotalFooter();\n this.previousVisibleKeys.clear();\n this.keysToAnimate.clear();\n this.userHasToggledExpand = false;\n }\n\n // #endregion\n\n // #region Shell Integration\n\n /** @internal */\n override getToolPanel(): ToolPanelDefinition | undefined {\n // Allow users to disable the tool panel for programmatic-only pivot\n // Check userConfig first (works before attach), then merged config\n const showToolPanel = this.config?.showToolPanel ?? this.userConfig?.showToolPanel ?? true;\n if (showToolPanel === false) {\n return undefined;\n }\n\n return {\n id: PivotPlugin.PANEL_ID,\n title: 'Pivot',\n icon: '⊞',\n tooltip: 'Configure pivot table',\n order: 90,\n render: (container) => this.renderPanel(container),\n };\n }\n\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processRows(rows: readonly unknown[]): PivotDataRow[] {\n // Auto-enable pivot if config.active is true and we have valid pivot fields\n if (!this.hasInitialized && this.config.active !== false && this.hasValidPivotConfig()) {\n this.hasInitialized = true;\n this.isActive = true;\n }\n\n if (!this.isActive) {\n return [...rows] as PivotDataRow[];\n }\n\n const errors = validatePivotConfig(this.config);\n if (errors.length > 0) {\n this.warn(`Config errors: ${errors.join(', ')}`);\n return [...rows] as PivotDataRow[];\n }\n\n this.buildFieldHeaderMap();\n this.defaultExpanded = this.config.defaultExpanded ?? true;\n\n // Build pivot first so we have the rows structure\n this.pivotResult = buildPivot(rows as PivotDataRow[], this.config);\n\n // Initialize expanded state with defaults if first build AND user hasn't manually toggled\n // This prevents re-expanding when user collapses all groups\n if (this.expandedKeys.size === 0 && this.defaultExpanded && !this.userHasToggledExpand) {\n this.expandAllKeys();\n }\n\n // Return flattened pivot rows respecting expanded state\n const indentWidth = this.config.indentWidth ?? 20;\n const flatRows: PivotDataRow[] = flattenPivotRows(\n this.pivotResult.rows,\n this.expandedKeys,\n this.defaultExpanded,\n ).map((pr) => ({\n __pivotRowKey: pr.rowKey,\n __pivotLabel: pr.rowLabel,\n __pivotDepth: pr.depth,\n __pivotIsGroup: pr.isGroup,\n __pivotHasChildren: Boolean(pr.children?.length),\n __pivotExpanded: this.expandedKeys.has(pr.rowKey),\n __pivotRowCount: pr.rowCount ?? 0,\n __pivotIndent: pr.depth * indentWidth,\n __pivotTotal: pr.total,\n ...pr.values,\n }));\n\n // Track which rows are newly visible (for animation)\n this.keysToAnimate.clear();\n const currentVisibleKeys = new Set<string>();\n for (const row of flatRows) {\n const key = row.__pivotRowKey as string;\n currentVisibleKeys.add(key);\n // Animate non-root rows that weren't previously visible\n if (!this.previousVisibleKeys.has(key) && (row.__pivotDepth as number) > 0) {\n this.keysToAnimate.add(key);\n }\n }\n this.previousVisibleKeys = currentVisibleKeys;\n\n // Grand total is rendered as a pinned footer row in afterRender,\n // not as part of the scrolling row data\n\n return flatRows;\n }\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (!this.isActive || !this.pivotResult) {\n return [...columns];\n }\n\n const pivotColumns: ColumnConfig[] = [];\n\n // Row label column\n const rowGroupHeaders = (this.config.rowGroupFields ?? []).map((f) => this.fieldHeaderMap.get(f) ?? f).join(' / ');\n pivotColumns.push({\n field: '__pivotLabel',\n header: rowGroupHeaders || 'Group',\n width: 200,\n });\n\n // Value columns for each column key\n for (const colKey of this.pivotResult.columnKeys) {\n for (const vf of this.config.valueFields ?? []) {\n const valueKey = createValueKey([colKey], vf.field);\n const valueHeader = vf.header || this.fieldHeaderMap.get(vf.field) || vf.field;\n pivotColumns.push({\n field: valueKey,\n header: `${colKey} - ${valueHeader} (${vf.aggFunc})`,\n width: 120,\n type: 'number',\n });\n }\n }\n\n // Totals column\n if (this.config.showTotals) {\n pivotColumns.push({\n field: '__pivotTotal',\n header: 'Total',\n width: 100,\n type: 'number',\n });\n }\n\n return pivotColumns;\n }\n\n /** @internal */\n override renderRow(row: Record<string, unknown>, rowEl: HTMLElement, rowIndex: number): boolean {\n const pivotRow = row as PivotRowData;\n\n // Handle pivot group row (has children)\n if (pivotRow.__pivotRowKey && pivotRow.__pivotHasChildren) {\n return renderPivotGroupRow(pivotRow, rowEl, {\n columns: this.gridColumns,\n rowIndex,\n onToggle: (key) => this.toggle(key),\n resolveIcon: (iconKey) => this.resolveIcon(iconKey),\n setIcon: (el, icon) => this.setIcon(el, icon),\n });\n }\n\n // Handle pivot leaf row (no children but in pivot mode)\n if (pivotRow.__pivotRowKey !== undefined && this.isActive) {\n return renderPivotLeafRow(pivotRow, rowEl, this.gridColumns, rowIndex);\n }\n\n // Clean up any leftover pivot styling from pooled row elements\n this.cleanupPivotStyling(rowEl);\n\n return false;\n }\n\n /**\n * Remove pivot-specific classes, attributes, and inline styles from a row element.\n * Called when pivot mode is disabled to clean up reused DOM elements.\n * Clears innerHTML so the grid's default renderer can rebuild the row.\n */\n private cleanupPivotStyling(rowEl: HTMLElement): void {\n // Check if this row was previously rendered by pivot (has pivot classes)\n const wasPivotRow =\n rowEl.classList.contains('pivot-group-row') ||\n rowEl.classList.contains('pivot-leaf-row') ||\n rowEl.classList.contains('pivot-grand-total-row');\n\n if (wasPivotRow) {\n // Remove pivot row classes and restore the default grid row class\n rowEl.classList.remove('pivot-group-row', 'pivot-leaf-row', 'pivot-grand-total-row');\n rowEl.classList.add('data-grid-row');\n\n // Remove pivot-specific attributes\n rowEl.removeAttribute('data-pivot-depth');\n\n // Clear the row content so the default renderer can rebuild it\n rowEl.innerHTML = '';\n }\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion on pivot group rows\n if (event.key !== ' ') return;\n if (!this.isActive) return;\n\n const focusRow = this.grid._focusRow;\n const row = this.rows[focusRow] as Record<string, unknown> | undefined;\n\n // Only handle SPACE on pivot group rows with children\n if (!row?.__pivotIsGroup || !row.__pivotHasChildren) return;\n\n event.preventDefault();\n this.toggle(row.__pivotRowKey as string);\n\n // Restore focus styling after render completes via render pipeline\n this.requestRenderWithFocus();\n return true;\n }\n\n /** @internal */\n override afterRender(): void {\n // Render grand total as a sticky pinned footer when pivot is active\n if (this.isActive && this.config.showGrandTotal && this.pivotResult) {\n this.renderGrandTotalFooter();\n } else {\n this.cleanupGrandTotalFooter();\n }\n\n // Apply animations to newly visible rows\n const style = this.animationStyle;\n if (style === false || this.keysToAnimate.size === 0) return;\n\n const body = this.gridElement?.querySelector('.rows');\n if (!body) return;\n\n const animClass = style === 'fade' ? 'tbw-pivot-fade-in' : 'tbw-pivot-slide-in';\n for (const rowEl of body.querySelectorAll('.pivot-group-row, .pivot-leaf-row')) {\n const key = (rowEl as HTMLElement).dataset.pivotKey;\n if (key && this.keysToAnimate.has(key)) {\n rowEl.classList.add(animClass);\n rowEl.addEventListener('animationend', () => rowEl.classList.remove(animClass), { once: true });\n }\n }\n this.keysToAnimate.clear();\n }\n\n /**\n * Render the grand total row as a sticky footer pinned to the bottom.\n */\n private renderGrandTotalFooter(): void {\n if (!this.pivotResult) return;\n\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Find the scroll container to append the footer\n const container =\n gridEl.querySelector('.tbw-scroll-area') ?? gridEl.querySelector('.tbw-grid-content') ?? gridEl.children[0];\n if (!container) return;\n\n // Create footer if it doesn't exist\n if (!this.grandTotalFooter) {\n this.grandTotalFooter = document.createElement('div');\n this.grandTotalFooter.className = 'pivot-grand-total-footer';\n container.appendChild(this.grandTotalFooter);\n }\n\n // Build the row data for grand total\n const grandTotalRow: PivotRowData = {\n __pivotRowKey: '__grandTotal',\n __pivotLabel: 'Grand Total',\n __pivotIsGrandTotal: true,\n __pivotTotal: this.pivotResult.grandTotal,\n ...this.pivotResult.totals,\n };\n\n // Render the grand total row into the footer\n renderPivotGrandTotalRow(grandTotalRow, this.grandTotalFooter, this.gridColumns);\n }\n\n /**\n * Remove the grand total footer element.\n */\n private cleanupGrandTotalFooter(): void {\n if (this.grandTotalFooter) {\n this.grandTotalFooter.remove();\n this.grandTotalFooter = null;\n }\n }\n\n // #endregion\n\n // #region Expand/Collapse API\n\n toggle(key: string): void {\n this.userHasToggledExpand = true;\n if (this.expandedKeys.has(key)) {\n this.expandedKeys.delete(key);\n } else {\n this.expandedKeys.add(key);\n }\n this.requestRender();\n }\n\n expand(key: string): void {\n this.userHasToggledExpand = true;\n this.expandedKeys.add(key);\n this.requestRender();\n }\n\n collapse(key: string): void {\n this.userHasToggledExpand = true;\n this.expandedKeys.delete(key);\n this.requestRender();\n }\n\n expandAll(): void {\n this.userHasToggledExpand = true;\n this.expandAllKeys();\n this.requestRender();\n }\n\n collapseAll(): void {\n this.userHasToggledExpand = true;\n this.expandedKeys.clear();\n this.requestRender();\n }\n\n /**\n * Add all group keys from the current pivot result to expandedKeys.\n */\n private expandAllKeys(): void {\n if (!this.pivotResult) return;\n const allKeys = getAllGroupKeys(this.pivotResult.rows);\n for (const key of allKeys) {\n this.expandedKeys.add(key);\n }\n }\n\n isExpanded(key: string): boolean {\n return this.expandedKeys.has(key);\n }\n\n // #endregion\n\n // #region Public API\n\n enablePivot(): void {\n if (this.originalColumns.length === 0) {\n this.captureOriginalColumns();\n }\n this.isActive = true;\n this.requestRender();\n }\n\n disablePivot(): void {\n this.isActive = false;\n this.pivotResult = null;\n this.requestRender();\n }\n\n isPivotActive(): boolean {\n return this.isActive;\n }\n\n getPivotResult(): PivotResult | null {\n return this.pivotResult;\n }\n\n setRowGroupFields(fields: string[]): void {\n this.config.rowGroupFields = fields;\n this.requestRender();\n }\n\n setColumnGroupFields(fields: string[]): void {\n this.config.columnGroupFields = fields;\n this.requestRender();\n }\n\n setValueFields(fields: PivotValueField[]): void {\n this.config.valueFields = fields;\n this.requestRender();\n }\n\n refresh(): void {\n this.pivotResult = null;\n this.requestRender();\n }\n\n // #endregion\n\n // #region Tool Panel API\n\n /**\n * Show the pivot tool panel.\n * Opens the tool panel and ensures this section is expanded.\n */\n showPanel(): void {\n this.grid.openToolPanel();\n // Ensure our section is expanded\n if (!this.grid.expandedToolPanelSections.includes(PivotPlugin.PANEL_ID)) {\n this.grid.toggleToolPanelSection(PivotPlugin.PANEL_ID);\n }\n }\n\n /**\n * Hide the tool panel.\n */\n hidePanel(): void {\n this.grid.closeToolPanel();\n }\n\n /**\n * Toggle the pivot tool panel section.\n */\n togglePanel(): void {\n // If tool panel is closed, open it first\n if (!this.grid.isToolPanelOpen) {\n this.grid.openToolPanel();\n }\n this.grid.toggleToolPanelSection(PivotPlugin.PANEL_ID);\n }\n\n /**\n * Check if the pivot panel section is currently expanded.\n */\n isPanelVisible(): boolean {\n return this.grid.isToolPanelOpen && this.grid.expandedToolPanelSections.includes(PivotPlugin.PANEL_ID);\n }\n\n // #endregion\n\n // #region Private Helpers\n\n private get gridColumns(): ColumnConfig[] {\n return (this.grid.columns ?? []) as ColumnConfig[];\n }\n\n /**\n * Refresh pivot and update tool panel if active.\n */\n private refreshIfActive(): void {\n if (this.isActive) this.refresh();\n this.refreshPanel();\n }\n\n private buildFieldHeaderMap(): void {\n const availableFields = this.getAvailableFields();\n this.fieldHeaderMap.clear();\n for (const field of availableFields) {\n this.fieldHeaderMap.set(field.field, field.header);\n }\n }\n\n private getAvailableFields(): FieldInfo[] {\n if (this.originalColumns.length > 0) {\n return this.originalColumns;\n }\n return this.captureOriginalColumns();\n }\n\n private captureOriginalColumns(): FieldInfo[] {\n try {\n const columns = this.grid.getAllColumns?.() ?? this.grid.columns ?? [];\n this.originalColumns = columns\n .filter((col: { field: string }) => !col.field.startsWith('__pivot'))\n .map((col: { field: string; header?: string }) => ({\n field: col.field,\n header: col.header ?? col.field,\n }));\n return this.originalColumns;\n } catch {\n return [];\n }\n }\n\n private renderPanel(container: HTMLElement): (() => void) | void {\n this.panelContainer = container;\n\n if (this.originalColumns.length === 0) {\n this.captureOriginalColumns();\n }\n\n const callbacks: PanelCallbacks = {\n onTogglePivot: (enabled) => {\n if (enabled) {\n this.enablePivot();\n } else {\n this.disablePivot();\n }\n this.refreshPanel();\n },\n onAddFieldToZone: (field, zone) => this.addFieldToZone(field, zone),\n onRemoveFieldFromZone: (field, zone) => this.removeFieldFromZone(field, zone),\n onAddValueField: (field, aggFunc) => this.addValueField(field, aggFunc),\n onRemoveValueField: (field) => this.removeValueField(field),\n onUpdateValueAggFunc: (field, aggFunc) => this.updateValueAggFunc(field, aggFunc),\n onOptionChange: (option, value) => {\n this.config[option] = value;\n if (this.isActive) this.refresh();\n },\n getAvailableFields: () => this.getAvailableFields(),\n };\n\n return renderPivotPanel(container, this.config, this.isActive, callbacks);\n }\n\n private refreshPanel(): void {\n if (!this.panelContainer) return;\n this.panelContainer.innerHTML = '';\n this.renderPanel(this.panelContainer);\n }\n\n private addFieldToZone(field: string, zoneType: 'rowGroups' | 'columnGroups'): void {\n if (zoneType === 'rowGroups') {\n const current = this.config.rowGroupFields ?? [];\n if (!current.includes(field)) {\n this.config.rowGroupFields = [...current, field];\n }\n } else {\n const current = this.config.columnGroupFields ?? [];\n if (!current.includes(field)) {\n this.config.columnGroupFields = [...current, field];\n }\n }\n\n this.removeFromOtherZones(field, zoneType);\n this.refreshIfActive();\n }\n\n private removeFieldFromZone(field: string, zoneType: 'rowGroups' | 'columnGroups'): void {\n if (zoneType === 'rowGroups') {\n this.config.rowGroupFields = (this.config.rowGroupFields ?? []).filter((f) => f !== field);\n } else {\n this.config.columnGroupFields = (this.config.columnGroupFields ?? []).filter((f) => f !== field);\n }\n\n this.refreshIfActive();\n }\n\n private removeFromOtherZones(field: string, targetZone: 'rowGroups' | 'columnGroups' | 'values'): void {\n if (targetZone !== 'rowGroups') {\n this.config.rowGroupFields = (this.config.rowGroupFields ?? []).filter((f) => f !== field);\n }\n if (targetZone !== 'columnGroups') {\n this.config.columnGroupFields = (this.config.columnGroupFields ?? []).filter((f) => f !== field);\n }\n if (targetZone !== 'values') {\n this.config.valueFields = (this.config.valueFields ?? []).filter((v) => v.field !== field);\n }\n }\n\n private addValueField(field: string, aggFunc: AggFunc): void {\n const current = this.config.valueFields ?? [];\n if (!current.some((v) => v.field === field)) {\n this.config.valueFields = [...current, { field, aggFunc }];\n }\n\n this.removeFromOtherZones(field, 'values');\n this.refreshIfActive();\n }\n\n private removeValueField(field: string): void {\n this.config.valueFields = (this.config.valueFields ?? []).filter((v) => v.field !== field);\n this.refreshIfActive();\n }\n\n private updateValueAggFunc(field: string, aggFunc: AggFunc): void {\n const valueFields = this.config.valueFields ?? [];\n const fieldIndex = valueFields.findIndex((v) => v.field === field);\n if (fieldIndex >= 0) {\n valueFields[fieldIndex] = { ...valueFields[fieldIndex], aggFunc };\n this.config.valueFields = [...valueFields];\n }\n if (this.isActive) this.refresh();\n }\n\n // #endregion\n}\n","/**\n * Utility for printing a grid in isolation by hiding all other page content.\n *\n * This approach keeps the grid in place (with virtualization disabled by PrintPlugin)\n * and uses CSS to hide everything else on the page during printing.\n */\n\nimport type { PrintOrientation } from './types';\n\nexport interface PrintIsolatedOptions {\n /** Page orientation hint */\n orientation?: PrintOrientation;\n}\n\n/** ID for the isolation stylesheet */\nconst ISOLATION_STYLE_ID = 'tbw-print-isolation-style';\n\n/**\n * Create a stylesheet that hides everything except the target grid.\n * Uses the grid's ID to target it specifically.\n */\nfunction createIsolationStylesheet(gridId: string, orientation: PrintOrientation): HTMLStyleElement {\n const style = document.createElement('style');\n style.id = ISOLATION_STYLE_ID;\n style.textContent = `\n /* Print isolation: hide everything except the target grid */\n @media print {\n /* Hide all body children by default */\n body > *:not(#${gridId}) {\n display: none !important;\n }\n\n /* But show the grid and ensure it's not hidden by ancestor rules */\n #${gridId} {\n display: block !important;\n position: static !important;\n visibility: visible !important;\n opacity: 1 !important;\n overflow: visible !important;\n height: auto !important;\n width: 100% !important;\n max-height: none !important;\n margin: 0 !important;\n padding: 0 !important;\n transform: none !important;\n }\n\n /* If grid is nested, we need to show its ancestors too */\n #${gridId},\n #${gridId} * {\n visibility: visible !important;\n }\n\n /* Walk up the DOM and show all ancestors of the grid */\n body *:has(> #${gridId}),\n body *:has(#${gridId}) {\n display: block !important;\n visibility: visible !important;\n opacity: 1 !important;\n overflow: visible !important;\n height: auto !important;\n position: static !important;\n transform: none !important;\n background: transparent !important;\n border: none !important;\n padding: 0 !important;\n margin: 0 !important;\n }\n\n /* Hide siblings of ancestors (everything that's not in the path to the grid) */\n body *:has(#${gridId}) > *:not(:has(#${gridId})):not(#${gridId}) {\n display: none !important;\n }\n\n /* Page settings */\n @page {\n size: ${orientation};\n margin: 1cm;\n }\n\n /* Ensure proper print styling */\n body {\n margin: 0 !important;\n padding: 0 !important;\n background: white !important;\n color-scheme: light !important;\n }\n }\n\n /* Screen: also apply isolation for print preview */\n @media screen {\n /* When this stylesheet is active, we're about to print */\n /* No screen-specific rules needed - isolation only applies to print */\n }\n `;\n return style;\n}\n\n/**\n * Print a grid in isolation by hiding all other page content.\n *\n * This function adds a temporary stylesheet that uses CSS to hide everything\n * on the page except the target grid during printing. The grid stays in place\n * with all its data (virtualization should be disabled separately).\n *\n * @param gridElement - The tbw-grid element to print (must have an ID)\n * @param options - Optional configuration\n * @returns Promise that resolves when the print dialog closes\n *\n * @example\n * ```typescript\n * import { printGridIsolated } from '@toolbox-web/grid/plugins/print';\n *\n * const grid = document.querySelector('tbw-grid');\n * await printGridIsolated(grid, { orientation: 'landscape' });\n * ```\n */\nexport async function printGridIsolated(gridElement: HTMLElement, options: PrintIsolatedOptions = {}): Promise<void> {\n const { orientation = 'landscape' } = options;\n\n const gridId = gridElement.id;\n\n // Warn if multiple elements share this ID (user-set IDs could collide)\n const elementsWithId = document.querySelectorAll(`#${CSS.escape(gridId)}`);\n if (elementsWithId.length > 1) {\n console.warn(\n `[tbw-grid:print] Multiple elements found with id=\"${gridId}\". ` +\n `Print isolation may not work correctly. Ensure each grid has a unique ID.`,\n );\n }\n\n // Remove any existing isolation stylesheet\n document.getElementById(ISOLATION_STYLE_ID)?.remove();\n\n // Add the isolation stylesheet\n const isolationStyle = createIsolationStylesheet(gridId, orientation);\n document.head.appendChild(isolationStyle);\n\n return new Promise((resolve) => {\n // Listen for afterprint event to cleanup\n const onAfterPrint = () => {\n window.removeEventListener('afterprint', onAfterPrint);\n // Remove isolation stylesheet\n document.getElementById(ISOLATION_STYLE_ID)?.remove();\n resolve();\n };\n window.addEventListener('afterprint', onAfterPrint);\n\n // Trigger print\n window.print();\n\n // Fallback timeout in case afterprint doesn't fire (some browsers)\n setTimeout(() => {\n window.removeEventListener('afterprint', onAfterPrint);\n document.getElementById(ISOLATION_STYLE_ID)?.remove();\n resolve();\n }, 5000);\n });\n}\n","/**\n * Print Plugin (Class-based)\n *\n * Provides print layout functionality for tbw-grid.\n * Temporarily disables virtualization to render all rows and uses\n * @media print CSS for print-optimized styling.\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { InternalGrid, ToolbarContentDefinition } from '../../core/types';\nimport { printGridIsolated } from './print-isolated';\nimport styles from './print.css?inline';\nimport type { PrintCompleteDetail, PrintConfig, PrintParams, PrintStartDetail } from './types';\n\n/**\n * Extended grid interface for PrintPlugin internal access.\n * Includes registerToolbarContent which is available on the grid class\n * but not exposed in the standard plugin API.\n */\ninterface PrintGridRef extends InternalGrid {\n registerToolbarContent?(content: ToolbarContentDefinition): void;\n unregisterToolbarContent?(contentId: string): void;\n}\n\n/** Default configuration */\nconst DEFAULT_CONFIG: Required<PrintConfig> = {\n button: false,\n orientation: 'landscape',\n warnThreshold: 500,\n maxRows: 0,\n includeTitle: true,\n includeTimestamp: true,\n title: '',\n isolate: false,\n};\n\n/**\n * Print Plugin for tbw-grid\n *\n * Enables printing the full grid content by temporarily disabling virtualization\n * and applying print-optimized styles. Handles large datasets gracefully with\n * configurable row limits.\n *\n * ## Installation\n *\n * ```ts\n * import { PrintPlugin } from '@toolbox-web/grid/plugins/print';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `button` | `boolean` | `false` | Show print button in toolbar |\n * | `orientation` | `'portrait' \\| 'landscape'` | `'landscape'` | Page orientation |\n * | `warnThreshold` | `number` | `500` | Show confirmation dialog when rows exceed this (0 = no warning) |\n * | `maxRows` | `number` | `0` | Hard limit on printed rows (0 = unlimited) |\n * | `includeTitle` | `boolean` | `true` | Include grid title in print |\n * | `includeTimestamp` | `boolean` | `true` | Include timestamp in footer |\n * | `title` | `string` | `''` | Custom print title |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `print` | `(params?) => Promise<void>` | Trigger print dialog |\n * | `isPrinting` | `() => boolean` | Check if print is in progress |\n *\n * ## Events\n *\n * | Event | Detail | Description |\n * |-------|--------|-------------|\n * | `print-start` | `PrintStartDetail` | Fired when print begins |\n * | `print-complete` | `PrintCompleteDetail` | Fired when print completes |\n *\n * @example Basic Print\n * ```ts\n * import { PrintPlugin } from '@toolbox-web/grid/plugins/print';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * plugins: [new PrintPlugin()],\n * };\n *\n * // Trigger print\n * const printPlugin = grid.getPlugin(PrintPlugin);\n * await printPlugin.print();\n * ```\n *\n * @example With Toolbar Button\n * ```ts\n * grid.gridConfig = {\n * plugins: [new PrintPlugin({ button: true, orientation: 'landscape' })],\n * };\n * ```\n *\n * @see {@link PrintConfig} for all configuration options\n */\nexport class PrintPlugin extends BaseGridPlugin<PrintConfig> {\n /** @internal */\n readonly name = 'print';\n\n /** @internal */\n override readonly version = '1.0.0';\n\n /** CSS styles for print mode */\n override readonly styles = styles;\n\n /** Current print state */\n #printing = false;\n\n /** Saved column visibility state */\n #savedHiddenColumns: Map<string, boolean> | null = null;\n\n /** Saved virtualization state */\n #savedVirtualization: { bypassThreshold: number } | null = null;\n\n /** Saved rows when maxRows limit is applied */\n #savedRows: unknown[] | null = null;\n\n /** Print header element */\n #printHeader: HTMLElement | null = null;\n\n /** Print footer element */\n #printFooter: HTMLElement | null = null;\n\n /** Applied scale factor (legacy, used for cleanup) */\n #appliedScale: number | null = null;\n\n /**\n * Get the grid typed as PrintGridRef for internal access.\n */\n get #internalGrid(): PrintGridRef {\n return this.grid as unknown as PrintGridRef;\n }\n\n /**\n * Check if print is currently in progress\n */\n isPrinting(): boolean {\n return this.#printing;\n }\n\n /**\n * Trigger the browser print dialog\n *\n * This method:\n * 1. Validates row count against maxRows limit\n * 2. Disables virtualization to render all rows\n * 3. Applies print-specific CSS classes\n * 4. Opens the browser print dialog (or isolated window if `isolate: true`)\n * 5. Restores normal state after printing\n *\n * @param params - Optional parameters to override config for this print\n * @param params.isolate - If true, prints in an isolated window containing only the grid\n * @returns Promise that resolves when print dialog closes\n */\n async print(params?: PrintParams): Promise<void> {\n if (this.#printing) {\n console.warn('[PrintPlugin] Print already in progress');\n return;\n }\n\n const grid = this.gridElement;\n if (!grid) {\n console.warn('[PrintPlugin] Grid not available');\n return;\n }\n\n const config = { ...DEFAULT_CONFIG, ...this.config, ...params };\n const rows = this.rows;\n const originalRowCount = rows.length;\n let rowCount = originalRowCount;\n let limitApplied = false;\n\n // Check if we should warn about large datasets\n if (config.warnThreshold > 0 && originalRowCount > config.warnThreshold) {\n const limitInfo =\n config.maxRows > 0 ? `\\n\\nNote: Output will be limited to ${config.maxRows.toLocaleString()} rows.` : '';\n const proceed = confirm(\n `This grid has ${originalRowCount.toLocaleString()} rows. ` +\n `Printing large datasets may cause performance issues or browser slowdowns.${limitInfo}\\n\\n` +\n `Click OK to continue, or Cancel to abort.`,\n );\n if (!proceed) {\n return;\n }\n }\n\n // Apply hard row limit if configured\n if (config.maxRows > 0 && originalRowCount > config.maxRows) {\n rowCount = config.maxRows;\n limitApplied = true;\n }\n\n this.#printing = true;\n\n // Track timing for duration reporting\n const startTime = performance.now();\n\n // Emit print-start event\n this.emit<PrintStartDetail>('print-start', {\n rowCount,\n limitApplied,\n originalRowCount,\n });\n\n try {\n // Save current virtualization state\n const internalGrid = this.#internalGrid;\n this.#savedVirtualization = {\n bypassThreshold: internalGrid._virtualization?.bypassThreshold ?? 24,\n };\n\n // Hide columns marked with printHidden\n this.#hidePrintColumns();\n\n // Apply row limit if configured\n if (limitApplied) {\n this.#savedRows = this.sourceRows;\n // Set limited rows on the grid\n (this.grid as unknown as { rows: unknown[] }).rows = this.sourceRows.slice(0, rowCount);\n // Wait for grid to process new rows\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n\n // Add print header if configured\n if (config.includeTitle || config.includeTimestamp) {\n this.#addPrintHeader(config);\n }\n\n // Disable virtualization to render all rows\n // This forces the grid to render all rows in the DOM\n await this.#disableVirtualization();\n\n // Wait for next frame to ensure DOM is updated\n await new Promise((resolve) => requestAnimationFrame(resolve));\n await new Promise((resolve) => requestAnimationFrame(resolve));\n\n // Add orientation class for @page rules\n grid.classList.add(`print-${config.orientation}`);\n\n // Wait for next frame to ensure DOM is updated\n await new Promise((resolve) => requestAnimationFrame(resolve));\n await new Promise((resolve) => requestAnimationFrame(resolve));\n\n // Trigger browser print dialog (isolated or inline)\n if (config.isolate) {\n await this.#printInIsolatedWindow(config);\n } else {\n await this.#triggerPrint();\n }\n\n // Emit print-complete event\n this.emit<PrintCompleteDetail>('print-complete', {\n success: true,\n rowCount,\n duration: Math.round(performance.now() - startTime),\n });\n } catch (error) {\n console.error('[PrintPlugin] Print failed:', error);\n this.emit<PrintCompleteDetail>('print-complete', {\n success: false,\n rowCount: 0,\n duration: Math.round(performance.now() - startTime),\n });\n } finally {\n // Restore normal state\n this.#cleanup();\n this.#printing = false;\n }\n }\n\n /**\n * Add print header with title and timestamp\n */\n #addPrintHeader(config: Required<PrintConfig>): void {\n const grid = this.gridElement;\n if (!grid) return;\n\n // Create print header\n this.#printHeader = document.createElement('div');\n this.#printHeader.className = 'tbw-print-header';\n\n // Title\n if (config.includeTitle) {\n const title = config.title || this.grid.effectiveConfig?.shell?.header?.title || 'Grid Data';\n const titleEl = document.createElement('div');\n titleEl.className = 'tbw-print-header-title';\n titleEl.textContent = title;\n this.#printHeader.appendChild(titleEl);\n }\n\n // Timestamp\n if (config.includeTimestamp) {\n const timestampEl = document.createElement('div');\n timestampEl.className = 'tbw-print-header-timestamp';\n timestampEl.textContent = `Printed: ${new Date().toLocaleString()}`;\n this.#printHeader.appendChild(timestampEl);\n }\n\n // Insert at the beginning of the grid\n grid.insertBefore(this.#printHeader, grid.firstChild);\n\n // Create print footer\n this.#printFooter = document.createElement('div');\n this.#printFooter.className = 'tbw-print-footer';\n this.#printFooter.textContent = `Page generated from ${window.location.hostname}`;\n grid.appendChild(this.#printFooter);\n }\n\n /**\n * Disable virtualization to render all rows\n */\n async #disableVirtualization(): Promise<void> {\n const internalGrid = this.#internalGrid;\n if (!internalGrid._virtualization) return;\n\n // Set bypass threshold higher than total row count to disable virtualization\n // This makes the grid render all rows (up to maxRows) instead of just visible ones\n const totalRows = this.rows.length;\n internalGrid._virtualization.bypassThreshold = totalRows + 100;\n\n // Force a full refresh to re-render with virtualization disabled\n internalGrid.refreshVirtualWindow(true);\n\n // Wait for render to complete\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n\n /**\n * Trigger the browser print dialog\n */\n async #triggerPrint(): Promise<void> {\n return new Promise((resolve) => {\n // Listen for afterprint event\n const onAfterPrint = () => {\n window.removeEventListener('afterprint', onAfterPrint);\n resolve();\n };\n window.addEventListener('afterprint', onAfterPrint);\n\n // Trigger print\n window.print();\n\n // Fallback timeout in case afterprint doesn't fire (some browsers)\n setTimeout(() => {\n // Guard against test environment teardown where window may be undefined\n if (typeof window !== 'undefined') {\n window.removeEventListener('afterprint', onAfterPrint);\n }\n resolve();\n }, 1000);\n });\n }\n\n /**\n * Print in isolation by hiding all other page content.\n * This excludes navigation, sidebars, etc. while keeping the grid in place.\n */\n async #printInIsolatedWindow(config: Required<PrintConfig>): Promise<void> {\n const grid = this.gridElement;\n if (!grid) return;\n\n await printGridIsolated(grid, {\n orientation: config.orientation,\n });\n }\n\n /**\n * Hide columns marked with printHidden: true\n */\n #hidePrintColumns(): void {\n const columns = this.columns;\n if (!columns) return;\n\n // Save current hidden state and hide print columns\n this.#savedHiddenColumns = new Map();\n\n for (const col of columns) {\n if (col.printHidden && col.field) {\n // Save current visibility state (true = visible, false = hidden)\n this.#savedHiddenColumns.set(col.field, !col.hidden);\n // Hide the column for printing\n this.grid.setColumnVisible(col.field, false);\n }\n }\n }\n\n /**\n * Restore columns that were hidden for printing\n */\n #restorePrintColumns(): void {\n if (!this.#savedHiddenColumns) return;\n\n for (const [field, wasVisible] of this.#savedHiddenColumns) {\n // Restore original visibility\n this.grid.setColumnVisible(field, wasVisible);\n }\n\n this.#savedHiddenColumns = null;\n }\n\n /**\n * Cleanup after printing\n */\n #cleanup(): void {\n const grid = this.gridElement;\n if (!grid) return;\n\n // Restore columns that were hidden for printing\n this.#restorePrintColumns();\n\n // Remove orientation classes (both original and possibly switched)\n grid.classList.remove('print-portrait', 'print-landscape');\n\n // Remove scaling transform if applied (legacy)\n if (this.#appliedScale !== null) {\n grid.style.transform = '';\n grid.style.transformOrigin = '';\n grid.style.width = '';\n this.#appliedScale = null;\n }\n\n // Remove print header/footer\n if (this.#printHeader) {\n this.#printHeader.remove();\n this.#printHeader = null;\n }\n if (this.#printFooter) {\n this.#printFooter.remove();\n this.#printFooter = null;\n }\n\n // Restore virtualization\n const internalGrid = this.#internalGrid;\n if (this.#savedVirtualization && internalGrid._virtualization) {\n internalGrid._virtualization.bypassThreshold = this.#savedVirtualization.bypassThreshold;\n internalGrid.refreshVirtualWindow(true);\n this.#savedVirtualization = null;\n }\n\n // Restore original rows if they were limited\n if (this.#savedRows !== null) {\n (this.grid as unknown as { rows: unknown[] }).rows = this.#savedRows;\n this.#savedRows = null;\n }\n }\n\n /**\n * Register toolbar button if configured\n * @internal\n */\n override afterRender(): void {\n // Register toolbar on first render when button is enabled\n if (this.config?.button && !this.#toolbarRegistered) {\n this.#registerToolbarButton();\n this.#toolbarRegistered = true;\n }\n }\n\n /** Track if toolbar button is registered */\n #toolbarRegistered = false;\n\n /**\n * Register print button in toolbar\n */\n #registerToolbarButton(): void {\n const grid = this.#internalGrid;\n\n // Register toolbar content\n grid.registerToolbarContent?.({\n id: 'print-button',\n order: 900, // High order to appear at the end\n render: (container: HTMLElement) => {\n const button = document.createElement('button');\n button.className = 'tbw-toolbar-btn tbw-print-btn';\n button.title = 'Print grid';\n button.type = 'button';\n\n // Use print icon\n const icon = this.resolveIcon('print') || '🖨️';\n this.setIcon(button, icon);\n\n button.addEventListener(\n 'click',\n () => {\n this.print();\n },\n { signal: this.disconnectSignal },\n );\n\n container.appendChild(button);\n },\n });\n }\n}\n","/**\n * Column Reordering Core Logic\n *\n * Pure functions for column drag and reordering operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\n\n/**\n * Check if a column can be moved based on its own metadata.\n * This checks column-level properties like lockPosition and suppressMovable.\n *\n * Note: For full movability checks including plugin constraints (e.g., pinned columns),\n * use `grid.query<boolean>('canMoveColumn', column)` which queries all plugins that\n * declare the 'canMoveColumn' query in their manifest.\n *\n * @param column - The column configuration to check\n * @returns True if the column can be moved based on its metadata\n */\nexport function canMoveColumn<TRow = unknown>(column: ColumnConfig<TRow>): boolean {\n // Check for lockPosition or suppressMovable properties in the column config\n const meta = column.meta ?? {};\n return meta.lockPosition !== true && meta.suppressMovable !== true;\n}\n\n/**\n * Move a column from one position to another in the order array.\n *\n * @param columns - Array of field names in current order\n * @param fromIndex - The current index of the column to move\n * @param toIndex - The target index to move the column to\n * @returns New array with updated order\n */\nexport function moveColumn(columns: string[], fromIndex: number, toIndex: number): string[] {\n if (fromIndex === toIndex) return columns;\n if (fromIndex < 0 || fromIndex >= columns.length) return columns;\n if (toIndex < 0 || toIndex > columns.length) return columns;\n\n const result = [...columns];\n const [removed] = result.splice(fromIndex, 1);\n result.splice(toIndex, 0, removed);\n return result;\n}\n\n/**\n * Calculate the drop index based on the current drag position.\n *\n * @param dragX - The current X position of the drag\n * @param headerRect - The bounding rect of the header container\n * @param columnWidths - Array of column widths in order\n * @returns The index where the column should be dropped\n */\nexport function getDropIndex(dragX: number, headerRect: DOMRect, columnWidths: number[]): number {\n let x = headerRect.left;\n\n for (let i = 0; i < columnWidths.length; i++) {\n const mid = x + columnWidths[i] / 2;\n if (dragX < mid) return i;\n x += columnWidths[i];\n }\n\n return columnWidths.length;\n}\n\n/**\n * Reorder columns according to a specified order.\n * Columns not in the order array are appended at the end.\n *\n * @param columns - Array of column configurations\n * @param order - Array of field names specifying the desired order\n * @returns New array of columns in the specified order\n */\nexport function reorderColumns<TRow = unknown>(columns: ColumnConfig<TRow>[], order: string[]): ColumnConfig<TRow>[] {\n const columnMap = new Map<string, ColumnConfig<TRow>>(columns.map((c) => [c.field as string, c]));\n const reordered: ColumnConfig<TRow>[] = [];\n\n // Add columns in specified order\n for (const field of order) {\n const col = columnMap.get(field);\n if (col) {\n reordered.push(col);\n columnMap.delete(field);\n }\n }\n\n // Add any remaining columns not in order\n for (const col of columnMap.values()) {\n reordered.push(col);\n }\n\n return reordered;\n}\n","/**\n * Column Reordering Plugin (Class-based)\n *\n * Provides drag-and-drop column reordering functionality for tbw-grid.\n * Supports keyboard and mouse interactions with visual feedback.\n * Uses FLIP animation technique for smooth column transitions.\n *\n * Animation respects grid-level animation.mode setting but style is plugin-configured.\n */\n\nimport { ensureCellVisible } from '../../core/internal/keyboard';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, InternalGrid } from '../../core/types';\nimport { canMoveColumn, moveColumn } from './column-drag';\nimport styles from './reorder.css?inline';\nimport type { ColumnMoveDetail, ReorderConfig } from './types';\n\n/**\n * Column Reorder Plugin for tbw-grid\n *\n * Lets users rearrange columns by dragging and dropping column headers. Supports smooth\n * FLIP animations, fade transitions, or instant reordering. Animation respects the\n * grid-level `animation.mode` setting.\n *\n * ## Installation\n *\n * ```ts\n * import { ReorderPlugin } from '@toolbox-web/grid/plugins/reorder';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `animation` | `false \\| 'flip' \\| 'fade'` | `'flip'` | Animation type for column moves |\n * | `animationDuration` | `number` | `200` | Animation duration in ms |\n *\n * ## Keyboard Shortcuts\n *\n * | Key | Action |\n * |-----|--------|\n * | `Alt + ←` | Move focused column left |\n * | `Alt + →` | Move focused column right |\n *\n * ## Events\n *\n * | Event | Detail | Cancelable | Description |\n * |-------|--------|------------|-------------|\n * | `column-move` | `{ field, fromIndex, toIndex, columnOrder }` | Yes | Fired when a column move is attempted |\n *\n * @example Basic Drag-and-Drop Reordering\n * ```ts\n * import '@toolbox-web/grid';\n * import { ReorderPlugin } from '@toolbox-web/grid/plugins/reorder';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID' },\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * ],\n * plugins: [new ReorderPlugin({ animation: 'flip', animationDuration: 200 })],\n * };\n *\n * // Persist column order\n * grid.addEventListener('column-move', (e) => {\n * localStorage.setItem('columnOrder', JSON.stringify(e.detail.columnOrder));\n * });\n * ```\n *\n * @example Prevent Moves That Break Group Boundaries\n * ```ts\n * grid.addEventListener('column-move', (e) => {\n * if (!isValidMoveWithinGroup(e.detail.field, e.detail.fromIndex, e.detail.toIndex)) {\n * e.preventDefault(); // Column snaps back to original position\n * }\n * });\n * ```\n *\n * @see {@link ReorderConfig} for all configuration options\n * @see {@link ColumnMoveDetail} for the event detail structure\n * @see {@link GroupingColumnsPlugin} for column group integration\n *\n * @internal Extends BaseGridPlugin\n */\nexport class ReorderPlugin extends BaseGridPlugin<ReorderConfig> {\n /** @internal */\n readonly name = 'reorder';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<ReorderConfig> {\n return {\n animation: 'flip', // Plugin's own default\n };\n }\n\n /**\n * Resolve animation type from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationType(): false | 'flip' | 'fade' {\n // Check if animations are globally disabled\n if (!this.isAnimationEnabled) return false;\n\n // Plugin config (with default from defaultConfig)\n if (this.config.animation !== undefined) return this.config.animation;\n\n return 'flip'; // Plugin default\n }\n\n /**\n * Get animation duration, allowing plugin config override.\n * Uses base class animationDuration for default.\n */\n protected override get animationDuration(): number {\n // Plugin config override\n if (this.config.animationDuration !== undefined) {\n return this.config.animationDuration;\n }\n return super.animationDuration;\n }\n\n // #region Internal State\n private isDragging = false;\n private draggedField: string | null = null;\n private draggedIndex: number | null = null;\n private dropIndex: number | null = null;\n\n /**\n * Check if a column can be moved, considering both column config and plugin queries.\n */\n private canMoveColumnWithPlugins(column: ColumnConfig | undefined): boolean {\n if (!column || !canMoveColumn(column)) return false;\n // Query plugins that respond to 'canMoveColumn' (e.g., PinnedColumnsPlugin)\n const responses = this.grid.query<boolean>('canMoveColumn', column);\n return !responses.includes(false);\n }\n\n /**\n * Clear all drag-related classes from header cells.\n */\n private clearDragClasses(): void {\n this.gridElement?.querySelectorAll('.header-row > .cell').forEach((h) => {\n h.classList.remove('dragging', 'drop-target', 'drop-before', 'drop-after');\n });\n }\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Listen for reorder requests from other plugins (e.g., VisibilityPlugin)\n // Uses disconnectSignal for automatic cleanup - no need for manual removeEventListener\n (grid as unknown as HTMLElement).addEventListener(\n 'column-reorder-request',\n (e: Event) => {\n const detail = (e as CustomEvent).detail;\n if (detail?.field && typeof detail.toIndex === 'number') {\n this.moveColumn(detail.field, detail.toIndex);\n }\n },\n { signal: this.disconnectSignal },\n );\n }\n\n /** @internal */\n override detach(): void {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n const headers = gridEl.querySelectorAll('.header-row > .cell');\n\n headers.forEach((header) => {\n const headerEl = header as HTMLElement;\n const field = headerEl.getAttribute('data-field');\n if (!field) return;\n\n const column = this.columns.find((c) => c.field === field);\n if (!this.canMoveColumnWithPlugins(column)) {\n headerEl.draggable = false;\n return;\n }\n\n headerEl.draggable = true;\n\n // Remove existing listeners to prevent duplicates\n if (headerEl.getAttribute('data-dragstart-bound')) return;\n headerEl.setAttribute('data-dragstart-bound', 'true');\n\n headerEl.addEventListener('dragstart', (e: DragEvent) => {\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.isDragging = true;\n this.draggedField = field;\n this.draggedIndex = orderIndex;\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', field);\n }\n\n headerEl.classList.add('dragging');\n });\n\n headerEl.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.clearDragClasses();\n });\n\n headerEl.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging || this.draggedField === field) return;\n\n const rect = headerEl.getBoundingClientRect();\n const midX = rect.left + rect.width / 2;\n\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.dropIndex = e.clientX < midX ? orderIndex : orderIndex + 1;\n\n headerEl.classList.add('drop-target');\n headerEl.classList.toggle('drop-before', e.clientX < midX);\n headerEl.classList.toggle('drop-after', e.clientX >= midX);\n });\n\n headerEl.addEventListener('dragleave', () => {\n headerEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n headerEl.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n const draggedField = this.draggedField;\n const draggedIndex = this.draggedIndex;\n const dropIndex = this.dropIndex;\n\n if (!this.isDragging || draggedField === null || draggedIndex === null || dropIndex === null) {\n return;\n }\n\n const effectiveToIndex = dropIndex > draggedIndex ? dropIndex - 1 : dropIndex;\n const currentOrder = this.getColumnOrder();\n const newOrder = moveColumn(currentOrder, draggedIndex, effectiveToIndex);\n\n const detail: ColumnMoveDetail = {\n field: draggedField,\n fromIndex: draggedIndex,\n toIndex: effectiveToIndex,\n columnOrder: newOrder,\n };\n\n // Emit cancelable event first - only update if not cancelled\n const cancelled = this.emitCancelable('column-move', detail);\n if (!cancelled) {\n // Update the grid's column order (with optional view transition)\n this.updateColumnOrder(newOrder);\n }\n });\n });\n }\n\n /**\n * Handle Alt+Arrow keyboard shortcuts for column reordering.\n * @internal\n */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n if (!event.altKey || (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')) {\n return;\n }\n\n const grid = this.grid as unknown as { _focusCol: number; _visibleColumns: ColumnConfig[] };\n const focusCol = grid._focusCol;\n const columns = grid._visibleColumns;\n\n if (focusCol < 0 || focusCol >= columns.length) return;\n\n const column = columns[focusCol];\n if (!this.canMoveColumnWithPlugins(column)) return;\n\n const currentOrder = this.getColumnOrder();\n const fromIndex = currentOrder.indexOf(column.field);\n if (fromIndex === -1) return;\n\n const toIndex = event.key === 'ArrowLeft' ? fromIndex - 1 : fromIndex + 1;\n\n // Check bounds\n if (toIndex < 0 || toIndex >= currentOrder.length) return;\n\n // Check if target position is allowed (e.g., not into pinned area)\n const targetColumn = columns.find((c) => c.field === currentOrder[toIndex]);\n if (!this.canMoveColumnWithPlugins(targetColumn)) return;\n\n this.moveColumn(column.field, toIndex);\n\n // Update focus to follow the moved column and refresh visual focus state\n grid._focusCol = toIndex;\n ensureCellVisible(this.grid as unknown as InternalGrid);\n\n event.preventDefault();\n event.stopPropagation();\n return true;\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Get the current column order from the grid.\n * @returns Array of field names in display order\n */\n getColumnOrder(): string[] {\n return this.grid.getColumnOrder();\n }\n\n /**\n * Move a column to a new position.\n * @param field - The field name of the column to move\n * @param toIndex - The target index\n */\n moveColumn(field: string, toIndex: number): void {\n const currentOrder = this.getColumnOrder();\n const fromIndex = currentOrder.indexOf(field);\n if (fromIndex === -1) return;\n\n const newOrder = moveColumn(currentOrder, fromIndex, toIndex);\n\n // Emit cancelable event first - only update if not cancelled\n const cancelled = this.emitCancelable<ColumnMoveDetail>('column-move', {\n field,\n fromIndex,\n toIndex,\n columnOrder: newOrder,\n });\n if (!cancelled) {\n // Update with view transition\n this.updateColumnOrder(newOrder);\n }\n }\n\n /**\n * Set a specific column order.\n * @param order - Array of field names in desired order\n */\n setColumnOrder(order: string[]): void {\n this.updateColumnOrder(order);\n }\n\n /**\n * Reset column order to the original configuration order.\n */\n resetColumnOrder(): void {\n const originalOrder = this.columns.map((c) => c.field);\n this.updateColumnOrder(originalOrder);\n }\n // #endregion\n\n // #region View Transition\n\n /**\n * Capture header cell positions before reorder.\n */\n private captureHeaderPositions(): Map<string, number> {\n const positions = new Map<string, number>();\n this.gridElement?.querySelectorAll('.header-row > .cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (field) positions.set(field, cell.getBoundingClientRect().left);\n });\n return positions;\n }\n\n /**\n * Apply FLIP animation for column reorder.\n * Uses CSS transitions - JS sets initial transform and toggles class.\n * @param oldPositions - Header positions captured before DOM change\n */\n private animateFLIP(oldPositions: Map<string, number>): void {\n const gridEl = this.gridElement;\n if (!gridEl || oldPositions.size === 0) return;\n\n // Compute deltas from header cells (stable reference points)\n const deltas = new Map<string, number>();\n gridEl.querySelectorAll('.header-row > .cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (!field) return;\n const oldLeft = oldPositions.get(field);\n if (oldLeft === undefined) return;\n const deltaX = oldLeft - cell.getBoundingClientRect().left;\n if (Math.abs(deltaX) > 1) deltas.set(field, deltaX);\n });\n\n if (deltas.size === 0) return;\n\n // Set initial transform (First → Last position offset)\n const cells: HTMLElement[] = [];\n gridEl.querySelectorAll('.cell[data-field]').forEach((cell) => {\n const deltaX = deltas.get(cell.getAttribute('data-field') ?? '');\n if (deltaX !== undefined) {\n const el = cell as HTMLElement;\n el.style.transform = `translateX(${deltaX}px)`;\n cells.push(el);\n }\n });\n\n if (cells.length === 0) return;\n\n // Force reflow then animate to final position via CSS transition\n void this.gridElement.offsetHeight;\n\n const duration = this.animationDuration;\n\n requestAnimationFrame(() => {\n cells.forEach((el) => {\n el.classList.add('flip-animating');\n el.style.transform = '';\n });\n\n // Cleanup after animation\n setTimeout(() => {\n cells.forEach((el) => {\n el.style.transform = '';\n el.classList.remove('flip-animating');\n });\n }, duration + 50);\n });\n }\n\n /**\n * Apply crossfade animation for moved columns.\n * Uses CSS keyframes - JS just toggles classes.\n */\n private animateFade(applyChange: () => void): void {\n const gridEl = this.gridElement;\n if (!gridEl) {\n applyChange();\n return;\n }\n\n // Capture old positions to detect which columns moved\n const oldPositions = this.captureHeaderPositions();\n\n // Apply the change first\n applyChange();\n\n // Find which columns changed position\n const movedFields = new Set<string>();\n gridEl.querySelectorAll('.header-row > .cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (!field) return;\n const oldLeft = oldPositions.get(field);\n if (oldLeft === undefined) return;\n const newLeft = cell.getBoundingClientRect().left;\n if (Math.abs(oldLeft - newLeft) > 1) {\n movedFields.add(field);\n }\n });\n\n if (movedFields.size === 0) return;\n\n // Add animation class to moved columns (headers + body cells)\n const cells: HTMLElement[] = [];\n gridEl.querySelectorAll('.cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (field && movedFields.has(field)) {\n const el = cell as HTMLElement;\n el.classList.add('fade-animating');\n cells.push(el);\n }\n });\n\n if (cells.length === 0) return;\n\n // Remove class after animation completes\n const duration = this.animationDuration;\n setTimeout(() => {\n cells.forEach((el) => el.classList.remove('fade-animating'));\n }, duration + 50);\n }\n\n /**\n * Update column order with configured animation.\n */\n private updateColumnOrder(newOrder: string[]): void {\n const animation = this.animationType;\n\n if (animation === 'flip' && this.gridElement) {\n const oldPositions = this.captureHeaderPositions();\n this.grid.setColumnOrder(newOrder);\n // Wait for the scheduler to process the virtual window update (RAF)\n // before running FLIP animation on the new cells\n requestAnimationFrame(() => {\n void this.gridElement.offsetHeight;\n this.animateFLIP(oldPositions);\n });\n } else if (animation === 'fade') {\n this.animateFade(() => this.grid.setColumnOrder(newOrder));\n } else {\n this.grid.setColumnOrder(newOrder);\n }\n\n this.grid.requestStateChange?.();\n }\n // #endregion\n}\n","/**\n * Responsive Plugin\n *\n * Transforms the grid from tabular layout to a card/list layout when the grid\n * width falls below a configurable breakpoint. This enables grids to work in\n * narrow containers (split-pane UIs, mobile viewports, dashboard widgets).\n *\n * ## Installation\n *\n * ```ts\n * import { ResponsivePlugin } from '@toolbox-web/grid/plugins/responsive';\n *\n * const config: GridConfig = {\n * plugins: [new ResponsivePlugin({ breakpoint: 500 })],\n * };\n * ```\n *\n * ## How It Works\n *\n * 1. ResizeObserver monitors the grid element's width\n * 2. When `width < breakpoint`, adds `data-responsive` attribute to grid\n * 3. CSS transforms cells from horizontal to vertical layout\n * 4. Each cell displays \"Header: Value\" using CSS `::before` pseudo-element\n *\n * @see [Responsive Demo](?path=/story/grid-plugins-responsive--default)\n */\n\nimport { ensureCellVisible } from '../../core/internal/keyboard';\nimport { evalTemplateString, sanitizeHTML } from '../../core/internal/sanitize';\nimport { BaseGridPlugin, type GridElement, type PluginManifest, type PluginQuery } from '../../core/plugin/base-plugin';\nimport type { InternalGrid } from '../../core/types';\nimport styles from './responsive.css?inline';\nimport type { BreakpointConfig, HiddenColumnConfig, ResponsiveChangeDetail, ResponsivePluginConfig } from './types';\n\n/**\n * Responsive Plugin for tbw-grid\n *\n * Adds automatic card layout mode when the grid width falls below a configurable\n * breakpoint. Perfect for responsive designs, split-pane UIs, and mobile viewports.\n *\n * @template T The row data type\n *\n * @example\n * ```ts\n * // Basic usage - switch to card layout below 500px\n * const config: GridConfig = {\n * plugins: [new ResponsivePlugin({ breakpoint: 500 })],\n * };\n * ```\n *\n * @example\n * ```ts\n * // Hide less important columns in card mode\n * const config: GridConfig = {\n * plugins: [\n * new ResponsivePlugin({\n * breakpoint: 600,\n * hiddenColumns: ['createdAt', 'updatedAt'],\n * }),\n * ],\n * };\n * ```\n *\n * @example\n * ```ts\n * // Custom card renderer for advanced layouts\n * const config: GridConfig = {\n * plugins: [\n * new ResponsivePlugin({\n * breakpoint: 400,\n * cardRenderer: (row) => {\n * const card = document.createElement('div');\n * card.className = 'custom-card';\n * card.innerHTML = `<strong>${row.name}</strong><br>${row.email}`;\n * return card;\n * },\n * }),\n * ],\n * };\n * ```\n */\nexport class ResponsivePlugin<T = unknown> extends BaseGridPlugin<ResponsivePluginConfig<T>> {\n readonly name = 'responsive';\n override readonly version = '1.0.0';\n override readonly styles = styles;\n\n /**\n * Plugin manifest declaring incompatibilities with other plugins.\n */\n static override readonly manifest: PluginManifest = {\n incompatibleWith: [\n {\n name: 'groupingRows',\n reason:\n 'Responsive card layout does not yet support row grouping. ' +\n 'The variable row heights (cards vs group headers) cause scroll calculation issues.',\n },\n ],\n queries: [\n {\n type: 'isCardMode',\n description: 'Returns whether the grid is currently in responsive card mode',\n },\n ],\n };\n\n #resizeObserver?: ResizeObserver;\n #isResponsive = false;\n #debounceTimer?: ReturnType<typeof setTimeout>;\n #warnedAboutMissingBreakpoint = false;\n #currentWidth = 0;\n /** Set of column fields to completely hide */\n #hiddenColumnSet: Set<string> = new Set();\n /** Set of column fields to show value only (no header label) */\n #valueOnlyColumnSet: Set<string> = new Set();\n /** Currently active breakpoint, or null if none */\n #activeBreakpoint: BreakpointConfig | null = null;\n /** Sorted breakpoints from largest to smallest */\n #sortedBreakpoints: BreakpointConfig[] = [];\n\n /**\n * Check if currently in responsive mode.\n * @returns `true` if the grid is in card layout mode\n */\n isResponsive(): boolean {\n return this.#isResponsive;\n }\n\n /**\n * Force responsive mode regardless of width.\n * Useful for testing or manual control.\n * @param enabled - Whether to enable responsive mode\n */\n setResponsive(enabled: boolean): void {\n if (enabled !== this.#isResponsive) {\n this.#isResponsive = enabled;\n this.#applyResponsiveState();\n this.emit('responsive-change', {\n isResponsive: enabled,\n width: this.#currentWidth,\n breakpoint: this.config.breakpoint ?? 0,\n } satisfies ResponsiveChangeDetail);\n }\n }\n\n /**\n * Update breakpoint dynamically.\n * @param width - New breakpoint width in pixels\n */\n setBreakpoint(width: number): void {\n this.config.breakpoint = width;\n this.#checkBreakpoint(this.#currentWidth);\n }\n\n /**\n * Set a custom card renderer.\n * This allows framework adapters to provide template-based renderers at runtime.\n * @param renderer - The card renderer function, or undefined to use default\n */\n setCardRenderer(renderer: ResponsivePluginConfig<T>['cardRenderer']): void {\n this.config.cardRenderer = renderer;\n // If already in responsive mode, trigger a re-render to apply the new renderer\n if (this.#isResponsive) {\n this.requestRender();\n }\n }\n\n /**\n * Get current grid width.\n * @returns Width of the grid element in pixels\n */\n getWidth(): number {\n return this.#currentWidth;\n }\n\n /**\n * Get the currently active breakpoint config (multi-breakpoint mode only).\n * @returns The active BreakpointConfig, or null if no breakpoint is active\n */\n getActiveBreakpoint(): BreakpointConfig | null {\n return this.#activeBreakpoint;\n }\n\n override attach(grid: GridElement): void {\n super.attach(grid);\n\n // Parse light DOM configuration first (may update this.config)\n this.#parseLightDomCard();\n\n // Build hidden column sets from config\n this.#buildHiddenColumnSets(this.config.hiddenColumns);\n\n // Sort breakpoints from largest to smallest for evaluation\n if (this.config.breakpoints?.length) {\n this.#sortedBreakpoints = [...this.config.breakpoints].sort((a, b) => b.maxWidth - a.maxWidth);\n }\n\n // Observe the grid element itself (not internal viewport)\n // This captures the container width including when shell panels open/close\n this.#resizeObserver = new ResizeObserver((entries) => {\n const width = entries[0]?.contentRect.width ?? 0;\n this.#currentWidth = width;\n\n // Debounce to avoid thrashing during resize drag\n clearTimeout(this.#debounceTimer);\n this.#debounceTimer = setTimeout(() => {\n this.#checkBreakpoint(width);\n }, this.config.debounceMs ?? 100);\n });\n\n this.#resizeObserver.observe(this.gridElement);\n }\n\n // #region Light DOM Parsing\n\n /**\n * Parse `<tbw-grid-responsive-card>` elements from the grid's light DOM.\n *\n * Allows declarative configuration:\n * ```html\n * <tbw-grid [rows]=\"data\">\n * <tbw-grid-responsive-card breakpoint=\"500\" card-row-height=\"80\">\n * <div class=\"custom-card\">\n * <strong>{{ row.name }}</strong>\n * <span>{{ row.email }}</span>\n * </div>\n * </tbw-grid-responsive-card>\n * </tbw-grid>\n * ```\n *\n * Attributes:\n * - `breakpoint`: number - Width threshold for responsive mode\n * - `card-row-height`: number | 'auto' - Card height (default: 'auto')\n * - `hidden-columns`: string - Comma-separated fields to hide\n * - `hide-header`: 'true' | 'false' - Hide header row (default: 'true')\n * - `debounce-ms`: number - Resize debounce delay (default: 100)\n */\n #parseLightDomCard(): void {\n const gridEl = this.grid as unknown as Element;\n if (!gridEl || typeof gridEl.querySelector !== 'function') return;\n\n const cardEl = gridEl.querySelector('tbw-grid-responsive-card');\n if (!cardEl) return;\n\n // Check if a framework adapter wants to handle this element\n // (e.g., React adapter intercepts for JSX rendering)\n const gridWithAdapter = gridEl as unknown as {\n __frameworkAdapter?: {\n parseResponsiveCardElement?: (el: Element) => ((row: T, rowIndex: number) => HTMLElement) | undefined;\n };\n };\n if (gridWithAdapter.__frameworkAdapter?.parseResponsiveCardElement) {\n const adapterRenderer = gridWithAdapter.__frameworkAdapter.parseResponsiveCardElement(cardEl);\n if (adapterRenderer) {\n this.config = { ...this.config, cardRenderer: adapterRenderer };\n // Continue to parse attributes even if adapter provides renderer\n }\n }\n\n // Parse attributes for configuration\n const breakpointAttr = cardEl.getAttribute('breakpoint');\n const cardRowHeightAttr = cardEl.getAttribute('card-row-height');\n const hiddenColumnsAttr = cardEl.getAttribute('hidden-columns');\n const hideHeaderAttr = cardEl.getAttribute('hide-header');\n const debounceMsAttr = cardEl.getAttribute('debounce-ms');\n\n const configUpdates: Partial<ResponsivePluginConfig<T>> = {};\n\n if (breakpointAttr !== null) {\n const breakpoint = parseInt(breakpointAttr, 10);\n if (!isNaN(breakpoint)) {\n configUpdates.breakpoint = breakpoint;\n }\n }\n\n if (cardRowHeightAttr !== null) {\n configUpdates.cardRowHeight = cardRowHeightAttr === 'auto' ? 'auto' : parseInt(cardRowHeightAttr, 10);\n }\n\n if (hiddenColumnsAttr !== null) {\n // Parse comma-separated field names\n configUpdates.hiddenColumns = hiddenColumnsAttr\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n }\n\n if (hideHeaderAttr !== null) {\n configUpdates.hideHeader = hideHeaderAttr !== 'false';\n }\n\n if (debounceMsAttr !== null) {\n const debounceMs = parseInt(debounceMsAttr, 10);\n if (!isNaN(debounceMs)) {\n configUpdates.debounceMs = debounceMs;\n }\n }\n\n // Get template content from innerHTML (only if no renderer already set)\n const templateHTML = cardEl.innerHTML.trim();\n if (templateHTML && !this.config.cardRenderer && !gridWithAdapter.__frameworkAdapter?.parseResponsiveCardElement) {\n // Create a template-based renderer using the inner HTML\n configUpdates.cardRenderer = (row: T): HTMLElement => {\n // Evaluate template expressions like {{ row.field }}\n const evaluated = evalTemplateString(templateHTML, { value: row, row: row as Record<string, unknown> });\n // Sanitize the result to prevent XSS\n const sanitized = sanitizeHTML(evaluated);\n const container = document.createElement('div');\n container.className = 'tbw-responsive-card-content';\n container.innerHTML = sanitized;\n return container;\n };\n }\n\n // Merge updates into config (light DOM values override constructor config)\n if (Object.keys(configUpdates).length > 0) {\n this.config = { ...this.config, ...configUpdates };\n }\n }\n\n // #endregion\n\n /**\n * Build the hidden and value-only column sets from config.\n */\n #buildHiddenColumnSets(hiddenColumns?: HiddenColumnConfig[]): void {\n this.#hiddenColumnSet.clear();\n this.#valueOnlyColumnSet.clear();\n\n if (!hiddenColumns) return;\n\n for (const col of hiddenColumns) {\n if (typeof col === 'string') {\n this.#hiddenColumnSet.add(col);\n } else if (col.showValue) {\n this.#valueOnlyColumnSet.add(col.field);\n } else {\n this.#hiddenColumnSet.add(col.field);\n }\n }\n }\n\n override detach(): void {\n this.#resizeObserver?.disconnect();\n this.#resizeObserver = undefined;\n clearTimeout(this.#debounceTimer);\n this.#debounceTimer = undefined;\n\n // Clean up attribute\n if (this.gridElement) {\n this.gridElement.removeAttribute('data-responsive');\n }\n\n super.detach();\n }\n\n /**\n * Handle plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'isCardMode') {\n return this.#isResponsive;\n }\n return undefined;\n }\n\n /**\n * Apply hidden and value-only columns.\n * In legacy mode (single breakpoint), only applies when in responsive mode.\n * In multi-breakpoint mode, applies whenever there's an active breakpoint.\n */\n override afterRender(): void {\n // Measure card height for virtualization calculations\n this.#measureCardHeightFromDOM();\n\n // In single breakpoint mode, only apply when responsive\n // In multi-breakpoint mode, apply when there's an active breakpoint\n const shouldApply = this.#sortedBreakpoints.length > 0 ? this.#activeBreakpoint !== null : this.#isResponsive;\n\n if (!shouldApply) {\n return;\n }\n\n const hasHiddenColumns = this.#hiddenColumnSet.size > 0;\n const hasValueOnlyColumns = this.#valueOnlyColumnSet.size > 0;\n\n if (!hasHiddenColumns && !hasValueOnlyColumns) {\n return;\n }\n\n // Mark cells for hidden columns and value-only columns\n const cells = this.gridElement.querySelectorAll('.cell[data-field]');\n for (const cell of cells) {\n const field = cell.getAttribute('data-field');\n if (!field) continue;\n\n // Apply hidden attribute\n if (this.#hiddenColumnSet.has(field)) {\n cell.setAttribute('data-responsive-hidden', '');\n cell.removeAttribute('data-responsive-value-only');\n }\n // Apply value-only attribute (shows value without header label)\n else if (this.#valueOnlyColumnSet.has(field)) {\n cell.setAttribute('data-responsive-value-only', '');\n cell.removeAttribute('data-responsive-hidden');\n }\n // Clear any previous responsive attributes\n else {\n cell.removeAttribute('data-responsive-hidden');\n cell.removeAttribute('data-responsive-value-only');\n }\n }\n }\n\n /**\n * Check if width has crossed any breakpoint threshold.\n * Handles both single breakpoint (legacy) and multi-breakpoint modes.\n */\n #checkBreakpoint(width: number): void {\n // Multi-breakpoint mode\n if (this.#sortedBreakpoints.length > 0) {\n this.#checkMultiBreakpoint(width);\n return;\n }\n\n // Legacy single breakpoint mode\n const breakpoint = this.config.breakpoint ?? 0;\n\n // Warn once if breakpoint not configured (0 means never responsive)\n if (breakpoint === 0 && !this.#warnedAboutMissingBreakpoint) {\n this.#warnedAboutMissingBreakpoint = true;\n console.warn(\n \"[tbw-grid:ResponsivePlugin] No breakpoint configured. Responsive mode is disabled. Set a breakpoint based on your grid's column count.\",\n );\n }\n\n const shouldBeResponsive = breakpoint > 0 && width < breakpoint;\n\n if (shouldBeResponsive !== this.#isResponsive) {\n this.#isResponsive = shouldBeResponsive;\n this.#applyResponsiveState();\n this.emit('responsive-change', {\n isResponsive: shouldBeResponsive,\n width,\n breakpoint,\n } satisfies ResponsiveChangeDetail);\n this.requestRender();\n }\n }\n\n /**\n * Check breakpoints in multi-breakpoint mode.\n * Evaluates breakpoints from largest to smallest, applying the first match.\n */\n #checkMultiBreakpoint(width: number): void {\n // Find the active breakpoint (first one where width <= maxWidth)\n // Since sorted largest to smallest, we find the largest matching breakpoint\n let newActiveBreakpoint: BreakpointConfig | null = null;\n\n for (const bp of this.#sortedBreakpoints) {\n if (width <= bp.maxWidth) {\n newActiveBreakpoint = bp;\n // Continue to find the most specific (smallest) matching breakpoint\n }\n }\n\n // Check if breakpoint changed\n const breakpointChanged = newActiveBreakpoint !== this.#activeBreakpoint;\n\n if (breakpointChanged) {\n this.#activeBreakpoint = newActiveBreakpoint;\n\n // Update hidden column sets from active breakpoint\n if (newActiveBreakpoint?.hiddenColumns) {\n this.#buildHiddenColumnSets(newActiveBreakpoint.hiddenColumns);\n } else {\n // Fall back to top-level hiddenColumns config\n this.#buildHiddenColumnSets(this.config.hiddenColumns);\n }\n\n // Determine if we should be in card layout\n const shouldBeResponsive = newActiveBreakpoint?.cardLayout === true;\n\n if (shouldBeResponsive !== this.#isResponsive) {\n this.#isResponsive = shouldBeResponsive;\n this.#applyResponsiveState();\n }\n\n // Emit event for any breakpoint change\n this.emit('responsive-change', {\n isResponsive: this.#isResponsive,\n width,\n breakpoint: newActiveBreakpoint?.maxWidth ?? 0,\n } satisfies ResponsiveChangeDetail);\n\n this.requestRender();\n }\n }\n\n /** Original row height before entering responsive mode, for restoration on exit */\n #originalRowHeight?: number;\n\n /**\n * Apply the responsive state to the grid element.\n * Handles scroll reset when entering responsive mode and row height restoration on exit.\n */\n #applyResponsiveState(): void {\n this.gridElement.toggleAttribute('data-responsive', this.#isResponsive);\n\n // Apply animation attribute if enabled (default: true)\n const animate = this.config.animate !== false;\n this.gridElement.toggleAttribute('data-responsive-animate', animate);\n\n // Set custom animation duration if provided\n if (this.config.animationDuration) {\n this.gridElement.style.setProperty('--tbw-responsive-duration', `${this.config.animationDuration}ms`);\n }\n\n // Cast to internal type for virtualization access\n const internalGrid = this.grid as unknown as InternalGrid;\n\n if (this.#isResponsive) {\n // Store original row height before responsive mode changes it\n if (internalGrid._virtualization) {\n this.#originalRowHeight = internalGrid._virtualization.rowHeight;\n }\n\n // Reset horizontal scroll position when entering responsive mode\n // The CSS hides overflow but doesn't reset the scroll position\n const scrollArea = this.gridElement.querySelector('.tbw-scroll-area') as HTMLElement | null;\n if (scrollArea) {\n scrollArea.scrollLeft = 0;\n }\n } else {\n // Exiting responsive mode - clean up inline styles set by renderRow\n // The rows are reused from the pool, so we need to remove the card-specific styles\n const rows = this.gridElement.querySelectorAll('.data-grid-row');\n for (const row of rows) {\n (row as HTMLElement).style.height = '';\n row.classList.remove('responsive-card');\n }\n\n // Restore original row height\n if (this.#originalRowHeight && this.#originalRowHeight > 0 && internalGrid._virtualization) {\n internalGrid._virtualization.rowHeight = this.#originalRowHeight;\n this.#originalRowHeight = undefined;\n }\n\n // Clear cached measurements so they're remeasured fresh when re-entering responsive mode\n // Without this, stale measurements cause incorrect height calculations after scrolling\n this.#measuredCardHeight = undefined;\n this.#measuredGroupRowHeight = undefined;\n this.#lastCardRowCount = undefined;\n }\n }\n\n /**\n * Custom row rendering when cardRenderer is provided and in responsive mode.\n *\n * When a cardRenderer is configured, this hook takes over row rendering to display\n * the custom card layout instead of the default cell structure.\n *\n * @param row - The row data object\n * @param rowEl - The row DOM element to render into\n * @param rowIndex - The index of the row in the data array\n * @returns `true` if rendered (prevents default), `void` for default rendering\n */\n override renderRow(row: unknown, rowEl: HTMLElement, rowIndex: number): boolean | void {\n // Only override when in responsive mode AND cardRenderer is provided\n if (!this.#isResponsive || !this.config.cardRenderer) {\n return; // Let default rendering proceed\n }\n\n // Skip group rows from GroupingRowsPlugin - they have special structure\n // and should use their own renderer\n if ((row as { __isGroupRow?: boolean }).__isGroupRow) {\n return; // Let GroupingRowsPlugin handle group row rendering\n }\n\n // Clear existing content\n rowEl.replaceChildren();\n\n // Call user's cardRenderer to get custom content\n const cardContent = this.config.cardRenderer(row as T, rowIndex);\n\n // Reset className - clears any stale classes from previous use (e.g., 'group-row' from recycled element)\n // This follows the same pattern as GroupingRowsPlugin which sets className explicitly\n rowEl.className = 'data-grid-row responsive-card';\n\n // Handle cardRowHeight\n const cardHeight = this.config.cardRowHeight ?? 'auto';\n if (cardHeight !== 'auto') {\n rowEl.style.height = `${cardHeight}px`;\n } else {\n // Remove any virtualization-set height for auto mode\n rowEl.style.height = 'auto';\n }\n\n // Append the custom card content\n rowEl.appendChild(cardContent);\n\n return true; // We handled rendering\n }\n\n /**\n * Handle keyboard navigation in responsive mode.\n *\n * In responsive mode, the visual layout is inverted:\n * - Cells are stacked vertically within each \"card\" (row)\n * - DOWN/UP visually moves within the card (between fields)\n * - Page Down/Page Up or Ctrl+Down/Up moves between cards\n *\n * For custom cardRenderers, keyboard navigation is disabled entirely\n * since the implementor controls the card content and should handle\n * navigation via their own event handlers.\n *\n * @returns `true` if the event was handled and default behavior should be prevented\n */\n override onKeyDown(e: KeyboardEvent): boolean {\n if (!this.#isResponsive) {\n return false;\n }\n\n // If custom cardRenderer is provided, disable grid's keyboard navigation\n // The implementor is responsible for their own navigation\n if (this.config.cardRenderer) {\n const navKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];\n if (navKeys.includes(e.key)) {\n // Let the event bubble - implementor can handle it\n return false;\n }\n }\n\n // Swap arrow key behavior for CSS-only responsive mode\n // In card layout, cells are stacked vertically:\n // Card 1: Card 2:\n // ID: 1 ID: 2\n // Name: Alice Name: Bob <- ArrowRight goes here\n // Dept: Eng Dept: Mkt\n // ↓ ArrowDown goes here\n //\n // ArrowDown/Up = move within card (change column/field)\n // ArrowRight/Left = move between cards (change row)\n const maxRow = this.rows.length - 1;\n const maxCol = this.visibleColumns.length - 1;\n\n switch (e.key) {\n case 'ArrowDown':\n // Move down WITHIN card (to next field/column)\n if (this.grid._focusCol < maxCol) {\n this.grid._focusCol += 1;\n e.preventDefault();\n ensureCellVisible(this.grid as unknown as InternalGrid);\n return true;\n }\n // At bottom of card - optionally move to next card's first field\n if (this.grid._focusRow < maxRow) {\n this.grid._focusRow += 1;\n this.grid._focusCol = 0;\n e.preventDefault();\n ensureCellVisible(this.grid as unknown as InternalGrid);\n return true;\n }\n break;\n\n case 'ArrowUp':\n // Move up WITHIN card (to previous field/column)\n if (this.grid._focusCol > 0) {\n this.grid._focusCol -= 1;\n e.preventDefault();\n ensureCellVisible(this.grid as unknown as InternalGrid);\n return true;\n }\n // At top of card - optionally move to previous card's last field\n if (this.grid._focusRow > 0) {\n this.grid._focusRow -= 1;\n this.grid._focusCol = maxCol;\n e.preventDefault();\n ensureCellVisible(this.grid as unknown as InternalGrid);\n return true;\n }\n break;\n\n case 'ArrowRight':\n // Move to NEXT card (same field)\n if (this.grid._focusRow < maxRow) {\n this.grid._focusRow += 1;\n e.preventDefault();\n ensureCellVisible(this.grid as unknown as InternalGrid);\n return true;\n }\n break;\n\n case 'ArrowLeft':\n // Move to PREVIOUS card (same field)\n if (this.grid._focusRow > 0) {\n this.grid._focusRow -= 1;\n e.preventDefault();\n ensureCellVisible(this.grid as unknown as InternalGrid);\n return true;\n }\n break;\n }\n\n return false;\n }\n\n // ============================================\n // Variable Height Support for Mixed Row Types\n // ============================================\n\n /** Measured card height from DOM for virtualization calculations */\n #measuredCardHeight?: number;\n\n /** Measured group row height from DOM for virtualization calculations */\n #measuredGroupRowHeight?: number;\n\n /** Last known card row count for detecting changes (e.g., group expand/collapse) */\n #lastCardRowCount?: number;\n\n /**\n * Get the effective card height for virtualization calculations.\n * Prioritizes DOM-measured height (actual rendered size) over config,\n * since content can overflow the configured height.\n */\n #getCardHeight(): number {\n // Prefer measured height - it reflects actual rendered size including overflow\n if (this.#measuredCardHeight && this.#measuredCardHeight > 0) {\n return this.#measuredCardHeight;\n }\n // Fall back to explicit config\n const configHeight = this.config.cardRowHeight;\n if (typeof configHeight === 'number' && configHeight > 0) {\n return configHeight;\n }\n // Default fallback\n return 80;\n }\n\n /**\n * Get the effective group row height for virtualization calculations.\n * Uses DOM-measured height, falling back to original row height.\n */\n #getGroupRowHeight(): number {\n if (this.#measuredGroupRowHeight && this.#measuredGroupRowHeight > 0) {\n return this.#measuredGroupRowHeight;\n }\n // Fall back to original row height (before responsive mode)\n return this.#originalRowHeight ?? 28;\n }\n\n /**\n * Check if there are any group rows in the current dataset.\n * Used to determine if we have mixed row heights.\n */\n #hasGroupRows(): boolean {\n for (const row of this.rows) {\n if ((row as { __isGroupRow?: boolean }).__isGroupRow) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Count group rows and card rows in the current dataset.\n */\n #countRowTypes(): { groupCount: number; cardCount: number } {\n let groupCount = 0;\n let cardCount = 0;\n for (const row of this.rows) {\n if ((row as { __isGroupRow?: boolean }).__isGroupRow) {\n groupCount++;\n } else {\n cardCount++;\n }\n }\n return { groupCount, cardCount };\n }\n\n /**\n * Return total extra height contributed by mixed row heights.\n * This is called by the grid's virtualization system to adjust scrollbar height.\n *\n * The grid calculates: totalRows * baseRowHeight + pluginExtraHeight\n *\n * For mixed layouts (groups + cards), we need to report the difference between\n * actual heights and what the base calculation assumes:\n * - Extra for groups: groupCount * (groupHeight - baseHeight)\n * - Extra for cards: cardCount * (cardHeight - baseHeight)\n *\n * @deprecated Use getRowHeight() instead. This hook will be removed in v3.0.\n */\n override getExtraHeight(): number {\n // Only applies when in responsive mode with cardRenderer\n if (!this.#isResponsive || !this.config.cardRenderer) {\n return 0;\n }\n\n // Only report extra height when there are mixed row types (groups + cards)\n // If all rows are cards, we update the virtualization row height instead\n if (!this.#hasGroupRows()) {\n return 0;\n }\n\n const baseHeight = this.#originalRowHeight ?? 28;\n const groupHeight = this.#getGroupRowHeight();\n const cardHeight = this.#getCardHeight();\n\n const { groupCount, cardCount } = this.#countRowTypes();\n\n // Calculate extra height for both row types\n const groupExtra = groupCount * Math.max(0, groupHeight - baseHeight);\n const cardExtra = cardCount * Math.max(0, cardHeight - baseHeight);\n\n return groupExtra + cardExtra;\n }\n\n /**\n * Return extra height that appears before a given row index.\n * Used by virtualization to correctly calculate scroll positions.\n *\n * Like getExtraHeight, this accounts for both group and card row heights.\n *\n * @deprecated Use getRowHeight() instead. This hook will be removed in v3.0.\n */\n override getExtraHeightBefore(beforeRowIndex: number): number {\n // Only applies when in responsive mode with cardRenderer\n if (!this.#isResponsive || !this.config.cardRenderer) {\n return 0;\n }\n\n // Only report extra height when there are mixed row types\n if (!this.#hasGroupRows()) {\n return 0;\n }\n\n const baseHeight = this.#originalRowHeight ?? 28;\n const groupHeight = this.#getGroupRowHeight();\n const cardHeight = this.#getCardHeight();\n\n const groupHeightDiff = Math.max(0, groupHeight - baseHeight);\n const cardHeightDiff = Math.max(0, cardHeight - baseHeight);\n\n // Count group rows and card rows before the given index\n let groupsBefore = 0;\n let cardsBefore = 0;\n const rows = this.rows;\n const maxIndex = Math.min(beforeRowIndex, rows.length);\n\n for (let i = 0; i < maxIndex; i++) {\n if ((rows[i] as { __isGroupRow?: boolean }).__isGroupRow) {\n groupsBefore++;\n } else {\n cardsBefore++;\n }\n }\n\n return groupsBefore * groupHeightDiff + cardsBefore * cardHeightDiff;\n }\n\n /**\n * Get the height of a specific row based on its type (group row vs card row).\n * Returns undefined if not in responsive mode.\n *\n * @param row - The row data\n * @param _index - The row index (unused, but part of the interface)\n * @returns The row height in pixels, or undefined if not in responsive mode\n */\n override getRowHeight(row: unknown, _index: number): number | undefined {\n // Only applies when in responsive mode with cardRenderer\n if (!this.#isResponsive || !this.config.cardRenderer) {\n return undefined;\n }\n\n // Check if this is a group row\n if ((row as { __isGroupRow?: boolean }).__isGroupRow) {\n return this.#getGroupRowHeight();\n }\n\n // Regular card row\n return this.#getCardHeight();\n }\n\n /**\n * Count the number of card rows (non-group rows) in the current dataset.\n */\n #countCardRows(): number {\n let count = 0;\n for (const row of this.rows) {\n if (!(row as { __isGroupRow?: boolean }).__isGroupRow) {\n count++;\n }\n }\n return count;\n }\n\n /** Pending refresh scheduled via microtask */\n #pendingRefresh = false;\n\n /**\n * Measure card height from DOM after render and detect row count changes.\n * Called in afterRender to ensure scroll calculations are accurate.\n *\n * This handles two scenarios:\n * 1. Card height changes (content overflow, dynamic sizing)\n * 2. Card row count changes (group expand/collapse)\n * 3. Group row height changes\n *\n * For uniform card layouts (no groups), we update the virtualization row height\n * directly to the card height. For mixed layouts (groups + cards), we use the\n * getExtraHeight mechanism to report height differences.\n *\n * The refresh is deferred via microtask to avoid nested render cycles.\n */\n #measureCardHeightFromDOM(): void {\n if (!this.#isResponsive || !this.config.cardRenderer) {\n return;\n }\n\n let needsRefresh = false;\n const internalGrid = this.grid as unknown as InternalGrid;\n const hasGroups = this.#hasGroupRows();\n\n // Check if card row count changed (e.g., group expanded/collapsed)\n const currentCardRowCount = this.#countCardRows();\n if (currentCardRowCount !== this.#lastCardRowCount) {\n this.#lastCardRowCount = currentCardRowCount;\n needsRefresh = true;\n }\n\n // Measure actual group row height from DOM (for mixed layouts)\n if (hasGroups) {\n const groupRow = this.gridElement.querySelector('.data-grid-row.group-row') as HTMLElement | null;\n if (groupRow) {\n const height = groupRow.getBoundingClientRect().height;\n if (height > 0 && height !== this.#measuredGroupRowHeight) {\n this.#measuredGroupRowHeight = height;\n needsRefresh = true;\n }\n }\n }\n\n // Measure actual card height from DOM\n const cardRow = this.gridElement.querySelector('.data-grid-row.responsive-card') as HTMLElement | null;\n if (cardRow) {\n const height = cardRow.getBoundingClientRect().height;\n if (height > 0 && height !== this.#measuredCardHeight) {\n this.#measuredCardHeight = height;\n needsRefresh = true;\n\n // For uniform card layouts (no groups), update virtualization row height directly\n // This ensures proper row recycling and translateY calculations\n if (!hasGroups && internalGrid._virtualization) {\n internalGrid._virtualization.rowHeight = height;\n }\n }\n }\n\n // Defer virtualization refresh to avoid nested render cycles\n // This is called from afterRender, so we can't call refreshVirtualWindow synchronously\n // Use scheduler's VIRTUALIZATION phase to batch properly and avoid duplicate afterRender calls\n if (needsRefresh && !this.#pendingRefresh) {\n this.#pendingRefresh = true;\n queueMicrotask(() => {\n this.#pendingRefresh = false;\n // Only refresh if still attached and in responsive mode\n if (this.grid && this.#isResponsive) {\n // Request virtualization phase through grid's public API\n // This goes through the scheduler which batches and handles afterRender properly\n (this.grid as unknown as InternalGrid).refreshVirtualWindow?.(true, true);\n }\n });\n }\n }\n}\n","/**\n * Row Reordering Plugin\n *\n * Provides keyboard and drag-drop row reordering functionality for tbw-grid.\n * Supports Ctrl+Up/Down keyboard shortcuts and optional drag handle column.\n */\n\nimport { ensureCellVisible } from '../../core/internal/keyboard';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, InternalGrid } from '../../core/types';\nimport styles from './row-reorder.css?inline';\nimport type { PendingMove, RowMoveDetail, RowReorderConfig } from './types';\n\n/** Field name for the drag handle column */\nexport const ROW_DRAG_HANDLE_FIELD = '__tbw_row_drag';\n\n/**\n * Row Reorder Plugin for tbw-grid\n *\n * Enables row reordering via keyboard shortcuts (Ctrl+Up/Down) and drag-drop.\n * Supports validation callbacks and debounced keyboard moves.\n *\n * ## Installation\n *\n * ```ts\n * import { RowReorderPlugin } from '@toolbox-web/grid/plugins/row-reorder';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `enableKeyboard` | `boolean` | `true` | Enable Ctrl+Up/Down shortcuts |\n * | `showDragHandle` | `boolean` | `true` | Show drag handle column |\n * | `dragHandlePosition` | `'left' \\| 'right'` | `'left'` | Drag handle column position |\n * | `dragHandleWidth` | `number` | `40` | Drag handle column width |\n * | `canMove` | `function` | - | Validation callback |\n * | `debounceMs` | `number` | `300` | Debounce time for keyboard moves |\n * | `animation` | `false \\| 'flip'` | `'flip'` | Animation for row moves |\n *\n * ## Keyboard Shortcuts\n *\n * | Key | Action |\n * |-----|--------|\n * | `Ctrl + ↑` | Move focused row up |\n * | `Ctrl + ↓` | Move focused row down |\n *\n * ## Events\n *\n * | Event | Detail | Cancelable | Description |\n * |-------|--------|------------|-------------|\n * | `row-move` | `RowMoveDetail` | Yes | Fired when a row move is attempted |\n *\n * @example Basic Row Reordering\n * ```ts\n * import '@toolbox-web/grid';\n * import { RowReorderPlugin } from '@toolbox-web/grid/plugins/row-reorder';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID' },\n * { field: 'name', header: 'Name' },\n * ],\n * plugins: [new RowReorderPlugin()],\n * };\n *\n * grid.addEventListener('row-move', (e) => {\n * console.log('Row moved from', e.detail.fromIndex, 'to', e.detail.toIndex);\n * });\n * ```\n *\n * @example With Validation\n * ```ts\n * new RowReorderPlugin({\n * canMove: (row, fromIndex, toIndex, direction) => {\n * // Prevent moving locked rows\n * return !row.locked;\n * },\n * })\n * ```\n *\n * @see {@link RowReorderConfig} for all configuration options\n * @see {@link RowMoveDetail} for the event detail structure\n */\nexport class RowReorderPlugin extends BaseGridPlugin<RowReorderConfig> {\n /** @internal */\n readonly name = 'rowReorder';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<RowReorderConfig> {\n return {\n enableKeyboard: true,\n showDragHandle: true,\n dragHandlePosition: 'left',\n dragHandleWidth: 40,\n debounceMs: 150,\n animation: 'flip',\n };\n }\n\n /**\n * Resolve animation type from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationType(): false | 'flip' {\n // Check if animations are globally disabled\n if (!this.isAnimationEnabled) return false;\n\n // Plugin config (with default from defaultConfig)\n if (this.config.animation !== undefined) return this.config.animation;\n\n return 'flip'; // Plugin default\n }\n\n // #region Internal State\n private isDragging = false;\n private draggedRowIndex: number | null = null;\n private dropRowIndex: number | null = null;\n private pendingMove: PendingMove | null = null;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n /** Column index to use when flushing pending move */\n private lastFocusCol = 0;\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.clearDebounceTimer();\n this.isDragging = false;\n this.draggedRowIndex = null;\n this.dropRowIndex = null;\n this.pendingMove = null;\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (!this.config.showDragHandle) {\n return [...columns];\n }\n\n const dragHandleColumn: ColumnConfig = {\n field: ROW_DRAG_HANDLE_FIELD,\n header: '',\n width: this.config.dragHandleWidth ?? 40,\n resizable: false,\n sortable: false,\n filterable: false,\n meta: {\n lockPosition: true,\n suppressMovable: true,\n utility: true,\n },\n viewRenderer: () => {\n const container = document.createElement('div');\n container.className = 'dg-row-drag-handle';\n container.setAttribute('aria-label', 'Drag to reorder');\n container.setAttribute('role', 'button');\n container.setAttribute('tabindex', '-1');\n // Set draggable as property (not just attribute) for proper HTML5 drag-drop\n container.draggable = true;\n\n // Use the grid's configured dragHandle icon\n this.setIcon(container, this.resolveIcon('dragHandle'));\n\n return container;\n },\n };\n\n // Position the drag handle column\n if (this.config.dragHandlePosition === 'right') {\n return [...columns, dragHandleColumn];\n }\n return [dragHandleColumn, ...columns];\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.config.showDragHandle) return;\n\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Set up drag start/end listeners on drag handles\n const handles = gridEl.querySelectorAll('.dg-row-drag-handle');\n handles.forEach((handle) => {\n const handleEl = handle as HTMLElement;\n if (handleEl.getAttribute('data-drag-bound')) return;\n handleEl.setAttribute('data-drag-bound', 'true');\n\n const rowEl = handleEl.closest('.data-grid-row') as HTMLElement;\n if (!rowEl) return;\n\n // Set up dragstart/dragend on the handle\n this.setupHandleDragListeners(handleEl, rowEl);\n });\n\n // Set up drop target listeners on ALL rows (not just the handle's row)\n const rows = gridEl.querySelectorAll('.data-grid-row');\n rows.forEach((row) => {\n const rowEl = row as HTMLElement;\n if (rowEl.getAttribute('data-drop-bound')) return;\n rowEl.setAttribute('data-drop-bound', 'true');\n\n this.setupRowDropListeners(rowEl);\n });\n }\n\n /**\n * Handle Ctrl+Arrow keyboard shortcuts for row reordering.\n * @internal\n */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n if (!this.config.enableKeyboard) return;\n if (!event.ctrlKey || (event.key !== 'ArrowUp' && event.key !== 'ArrowDown')) {\n return;\n }\n\n const grid = this.grid as unknown as InternalGrid;\n const focusRow = grid._focusRow;\n // Use _rows (current visual state) for keyboard moves, not sourceRows\n // This ensures rapid moves work correctly since we update _rows directly\n // Fallback to sourceRows for compatibility with tests\n const rows = grid._rows ?? this.sourceRows;\n\n if (focusRow < 0 || focusRow >= rows.length) return;\n\n const direction = event.key === 'ArrowUp' ? 'up' : 'down';\n const toIndex = direction === 'up' ? focusRow - 1 : focusRow + 1;\n\n // Check bounds\n if (toIndex < 0 || toIndex >= rows.length) return;\n\n const row = rows[focusRow];\n\n // Validate move\n if (this.config.canMove && !this.config.canMove(row, focusRow, toIndex, direction)) {\n return;\n }\n\n // Debounce keyboard moves\n this.handleKeyboardMove(row, focusRow, toIndex, direction, grid._focusCol);\n\n event.preventDefault();\n event.stopPropagation();\n return true;\n }\n\n /**\n * Flush pending keyboard moves when user clicks a cell.\n * This commits the move immediately so focus works correctly.\n * @internal\n */\n override onCellClick(): void {\n // If there's a pending keyboard move, flush it immediately\n // so the user's click focus isn't overridden\n this.flushPendingMove();\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Move a row to a new position programmatically.\n * @param fromIndex - Current index of the row\n * @param toIndex - Target index\n */\n moveRow(fromIndex: number, toIndex: number): void {\n const rows = [...this.sourceRows];\n if (fromIndex < 0 || fromIndex >= rows.length) return;\n if (toIndex < 0 || toIndex >= rows.length) return;\n if (fromIndex === toIndex) return;\n\n const direction = toIndex < fromIndex ? 'up' : 'down';\n const row = rows[fromIndex];\n\n // Validate move\n if (this.config.canMove && !this.config.canMove(row, fromIndex, toIndex, direction)) {\n return;\n }\n\n this.executeMove(row, fromIndex, toIndex, 'keyboard');\n }\n\n /**\n * Check if a row can be moved to a position.\n * @param fromIndex - Current index of the row\n * @param toIndex - Target index\n */\n canMoveRow(fromIndex: number, toIndex: number): boolean {\n const rows = this.sourceRows;\n if (fromIndex < 0 || fromIndex >= rows.length) return false;\n if (toIndex < 0 || toIndex >= rows.length) return false;\n if (fromIndex === toIndex) return false;\n\n if (!this.config.canMove) return true;\n\n const direction = toIndex < fromIndex ? 'up' : 'down';\n return this.config.canMove(rows[fromIndex], fromIndex, toIndex, direction);\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Set up drag start/end listeners on the drag handle element.\n */\n private setupHandleDragListeners(handleEl: HTMLElement, rowEl: HTMLElement): void {\n handleEl.addEventListener('dragstart', (e: DragEvent) => {\n const rowIndex = this.getRowIndex(rowEl);\n if (rowIndex < 0) return;\n\n this.isDragging = true;\n this.draggedRowIndex = rowIndex;\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', String(rowIndex));\n }\n\n rowEl.classList.add('dragging');\n });\n\n handleEl.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedRowIndex = null;\n this.dropRowIndex = null;\n this.clearDragClasses();\n });\n }\n\n /**\n * Set up drop target listeners on a row element.\n * All rows are valid drop targets during drag operations.\n */\n private setupRowDropListeners(rowEl: HTMLElement): void {\n rowEl.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging || this.draggedRowIndex === null) return;\n\n const targetIndex = this.getRowIndex(rowEl);\n if (targetIndex < 0 || targetIndex === this.draggedRowIndex) return;\n\n const rect = rowEl.getBoundingClientRect();\n const midY = rect.top + rect.height / 2;\n const isBefore = e.clientY < midY;\n\n this.dropRowIndex = isBefore ? targetIndex : targetIndex + 1;\n\n rowEl.classList.add('drop-target');\n rowEl.classList.toggle('drop-before', isBefore);\n rowEl.classList.toggle('drop-after', !isBefore);\n });\n\n rowEl.addEventListener('dragleave', () => {\n rowEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n rowEl.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n const fromIndex = this.draggedRowIndex;\n let toIndex = this.dropRowIndex;\n\n if (!this.isDragging || fromIndex === null || toIndex === null) {\n return;\n }\n\n // Adjust toIndex if dropping after the dragged row\n if (toIndex > fromIndex) {\n toIndex--;\n }\n\n if (fromIndex !== toIndex) {\n const rows = this.sourceRows;\n const row = rows[fromIndex];\n const direction = toIndex < fromIndex ? 'up' : 'down';\n\n // Validate move\n if (!this.config.canMove || this.config.canMove(row, fromIndex, toIndex, direction)) {\n this.executeMove(row, fromIndex, toIndex, 'drag');\n }\n }\n });\n }\n\n /**\n * Handle debounced keyboard moves.\n * Rows move immediately for visual feedback, but the event emission is debounced.\n */\n private handleKeyboardMove(\n row: unknown,\n fromIndex: number,\n toIndex: number,\n direction: 'up' | 'down',\n focusCol: number,\n ): void {\n // Track move for debounced event emission\n if (!this.pendingMove) {\n this.pendingMove = {\n originalIndex: fromIndex,\n currentIndex: toIndex,\n row,\n };\n } else {\n // Update the current index for rapid moves\n this.pendingMove.currentIndex = toIndex;\n }\n\n // Store focus column for flush\n this.lastFocusCol = focusCol;\n\n // Move rows immediately for visual feedback\n // Use _rows (current visual state) for rapid moves, not sourceRows\n // Fallback to sourceRows for compatibility with tests\n const grid = this.grid as unknown as InternalGrid;\n const rows = [...(grid._rows ?? this.sourceRows)];\n const [movedRow] = rows.splice(fromIndex, 1);\n rows.splice(toIndex, 0, movedRow);\n\n // Update grid rows immediately (without triggering change events)\n grid._rows = rows;\n\n // Update focus to follow the row\n grid._focusRow = toIndex;\n grid._focusCol = focusCol;\n\n // Refresh virtual window directly - this re-renders from _rows\n // without overwriting _rows from #rows (which requestRender does)\n grid.refreshVirtualWindow(true);\n\n // Ensure focus styling is applied after the row rebuild\n ensureCellVisible(grid);\n\n // Debounce the event emission only\n this.clearDebounceTimer();\n this.debounceTimer = setTimeout(() => {\n this.flushPendingMove();\n }, this.config.debounceMs ?? 300);\n }\n\n /**\n * Flush the pending move by emitting the event.\n * Called when debounce timer fires or user clicks elsewhere.\n */\n private flushPendingMove(): void {\n this.clearDebounceTimer();\n\n if (!this.pendingMove) return;\n\n const { originalIndex, currentIndex, row: movedRow } = this.pendingMove;\n this.pendingMove = null;\n\n if (originalIndex === currentIndex) return;\n\n // Emit cancelable event\n const detail: RowMoveDetail = {\n row: movedRow,\n fromIndex: originalIndex,\n toIndex: currentIndex,\n rows: [...this.sourceRows],\n source: 'keyboard',\n };\n\n const cancelled = this.emitCancelable('row-move', detail);\n if (cancelled) {\n // Revert to original position\n const rows = [...this.sourceRows];\n const [row] = rows.splice(currentIndex, 1);\n rows.splice(originalIndex, 0, row);\n\n const grid = this.grid as unknown as InternalGrid;\n grid._rows = rows;\n grid._focusRow = originalIndex;\n grid._focusCol = this.lastFocusCol;\n grid.refreshVirtualWindow(true);\n ensureCellVisible(grid);\n }\n }\n\n /**\n * Execute a row move and emit the event.\n */\n private executeMove(row: unknown, fromIndex: number, toIndex: number, source: 'keyboard' | 'drag'): void {\n const rows = [...this.sourceRows];\n const [movedRow] = rows.splice(fromIndex, 1);\n rows.splice(toIndex, 0, movedRow);\n\n const detail: RowMoveDetail = {\n row,\n fromIndex,\n toIndex,\n rows,\n source,\n };\n\n // Emit cancelable event\n const cancelled = this.emitCancelable('row-move', detail);\n if (!cancelled) {\n // Apply with animation if enabled\n if (this.animationType === 'flip' && this.gridElement) {\n const oldPositions = this.captureRowPositions();\n this.grid.rows = rows;\n // Wait for the scheduler to process the virtual window update (RAF)\n // before running FLIP animation on the new rows\n requestAnimationFrame(() => {\n void this.gridElement.offsetHeight;\n this.animateFLIP(oldPositions, fromIndex, toIndex);\n });\n } else {\n // No animation, just update rows\n this.grid.rows = rows;\n }\n }\n }\n\n /**\n * Capture row positions before reorder.\n * Maps visual row index to its top position.\n */\n private captureRowPositions(): Map<number, number> {\n const positions = new Map<number, number>();\n this.gridElement?.querySelectorAll('.data-grid-row').forEach((row) => {\n const rowIndex = this.getRowIndex(row as HTMLElement);\n if (rowIndex >= 0) {\n positions.set(rowIndex, row.getBoundingClientRect().top);\n }\n });\n return positions;\n }\n\n /**\n * Apply FLIP animation for row reorder.\n * Uses CSS transitions - JS sets initial transform and toggles class.\n * @param oldPositions - Row positions captured before DOM change\n * @param fromIndex - Original index of moved row\n * @param toIndex - New index of moved row\n */\n private animateFLIP(oldPositions: Map<number, number>, fromIndex: number, toIndex: number): void {\n const gridEl = this.gridElement;\n if (!gridEl || oldPositions.size === 0) return;\n\n // Calculate which row indices were affected and their new positions\n const minIndex = Math.min(fromIndex, toIndex);\n const maxIndex = Math.max(fromIndex, toIndex);\n\n // Build a map of new row index -> delta Y\n const rowsToAnimate: { el: HTMLElement; deltaY: number }[] = [];\n\n gridEl.querySelectorAll('.data-grid-row').forEach((row) => {\n const rowEl = row as HTMLElement;\n const newRowIndex = this.getRowIndex(rowEl);\n if (newRowIndex < 0 || newRowIndex < minIndex || newRowIndex > maxIndex) return;\n\n // Figure out what this row's old index was\n let oldIndex: number;\n if (newRowIndex === toIndex) {\n // This is the moved row\n oldIndex = fromIndex;\n } else if (fromIndex < toIndex) {\n // Row moved down: rows in between shifted up by 1\n oldIndex = newRowIndex + 1;\n } else {\n // Row moved up: rows in between shifted down by 1\n oldIndex = newRowIndex - 1;\n }\n\n const oldTop = oldPositions.get(oldIndex);\n if (oldTop === undefined) return;\n\n const newTop = rowEl.getBoundingClientRect().top;\n const deltaY = oldTop - newTop;\n\n if (Math.abs(deltaY) > 1) {\n rowsToAnimate.push({ el: rowEl, deltaY });\n }\n });\n\n if (rowsToAnimate.length === 0) return;\n\n // Set initial transform (First → Last position offset)\n rowsToAnimate.forEach(({ el, deltaY }) => {\n el.style.transform = `translateY(${deltaY}px)`;\n });\n\n // Force reflow then animate to final position via CSS transition\n void gridEl.offsetHeight;\n\n const duration = this.animationDuration;\n\n requestAnimationFrame(() => {\n rowsToAnimate.forEach(({ el }) => {\n el.classList.add('flip-animating');\n el.style.transform = '';\n });\n\n // Cleanup after animation\n setTimeout(() => {\n rowsToAnimate.forEach(({ el }) => {\n el.style.transform = '';\n el.classList.remove('flip-animating');\n });\n }, duration + 50);\n });\n }\n\n /**\n * Get the row index from a row element by checking data-row attribute on cells.\n * This is consistent with how other plugins retrieve row indices.\n */\n private getRowIndex(rowEl: HTMLElement): number {\n const cell = rowEl.querySelector('.cell[data-row]');\n return cell ? parseInt(cell.getAttribute('data-row') ?? '-1', 10) : -1;\n }\n\n /**\n * Clear all drag-related classes from rows.\n */\n private clearDragClasses(): void {\n this.gridElement?.querySelectorAll('.data-grid-row').forEach((row) => {\n row.classList.remove('dragging', 'drop-target', 'drop-before', 'drop-after');\n });\n }\n\n /**\n * Clear the debounce timer.\n */\n private clearDebounceTimer(): void {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n }\n // #endregion\n}\n","/**\n * Cell Range Selection Core Logic\n *\n * Pure functions for cell range selection operations.\n */\n\nimport type { InternalCellRange, CellRange } from './types';\n\n/**\n * Normalize a range so startRow/startCol are always <= endRow/endCol.\n * This handles cases where user drags from bottom-right to top-left.\n *\n * @param range - The range to normalize\n * @returns Normalized range with start <= end for both dimensions\n */\nexport function normalizeRange(range: InternalCellRange): InternalCellRange {\n return {\n startRow: Math.min(range.startRow, range.endRow),\n startCol: Math.min(range.startCol, range.endCol),\n endRow: Math.max(range.startRow, range.endRow),\n endCol: Math.max(range.startCol, range.endCol),\n };\n}\n\n/**\n * Convert an internal range to the public event format.\n *\n * @param range - The internal range to convert\n * @returns Public CellRange format with from/to coordinates\n */\nexport function toPublicRange(range: InternalCellRange): CellRange {\n const normalized = normalizeRange(range);\n return {\n from: { row: normalized.startRow, col: normalized.startCol },\n to: { row: normalized.endRow, col: normalized.endCol },\n };\n}\n\n/**\n * Convert multiple internal ranges to public format.\n *\n * @param ranges - Array of internal ranges\n * @returns Array of public CellRange format\n */\nexport function toPublicRanges(ranges: InternalCellRange[]): CellRange[] {\n return ranges.map(toPublicRange);\n}\n\n/**\n * Check if a cell is within a specific range.\n *\n * @param row - The row index to check\n * @param col - The column index to check\n * @param range - The range to check against\n * @returns True if the cell is within the range\n */\nexport function isCellInRange(row: number, col: number, range: InternalCellRange): boolean {\n const normalized = normalizeRange(range);\n return (\n row >= normalized.startRow && row <= normalized.endRow && col >= normalized.startCol && col <= normalized.endCol\n );\n}\n\n/**\n * Check if a cell is within any of the provided ranges.\n *\n * @param row - The row index to check\n * @param col - The column index to check\n * @param ranges - Array of ranges to check against\n * @returns True if the cell is within any range\n */\nexport function isCellInAnyRange(row: number, col: number, ranges: InternalCellRange[]): boolean {\n return ranges.some((range) => isCellInRange(row, col, range));\n}\n\n/**\n * Get all cells within a range as an array of {row, col} objects.\n *\n * @param range - The range to enumerate\n * @returns Array of all cell coordinates in the range\n */\nexport function getCellsInRange(range: InternalCellRange): Array<{ row: number; col: number }> {\n const cells: Array<{ row: number; col: number }> = [];\n const normalized = normalizeRange(range);\n\n for (let row = normalized.startRow; row <= normalized.endRow; row++) {\n for (let col = normalized.startCol; col <= normalized.endCol; col++) {\n cells.push({ row, col });\n }\n }\n\n return cells;\n}\n\n/**\n * Get all unique cells across multiple ranges.\n * Deduplicates cells that appear in overlapping ranges.\n *\n * @param ranges - Array of ranges to enumerate\n * @returns Array of unique cell coordinates\n */\nexport function getAllCellsInRanges(ranges: InternalCellRange[]): Array<{ row: number; col: number }> {\n const cellMap = new Map<string, { row: number; col: number }>();\n\n for (const range of ranges) {\n for (const cell of getCellsInRange(range)) {\n cellMap.set(`${cell.row},${cell.col}`, cell);\n }\n }\n\n return [...cellMap.values()];\n}\n\n/**\n * Merge overlapping or adjacent ranges into fewer ranges.\n * Simple implementation - returns ranges as-is for now.\n * More complex merging logic can be added later for optimization.\n *\n * @param ranges - Array of ranges to merge\n * @returns Merged array of ranges\n */\nexport function mergeRanges(ranges: InternalCellRange[]): InternalCellRange[] {\n // Simple implementation - more complex merging can be added later\n return ranges;\n}\n\n/**\n * Create a range from an anchor cell to a current cell position.\n * The range is not normalized - it preserves the direction of selection.\n *\n * @param anchor - The anchor cell (where selection started)\n * @param current - The current cell (where selection ends)\n * @returns An InternalCellRange from anchor to current\n */\nexport function createRangeFromAnchor(\n anchor: { row: number; col: number },\n current: { row: number; col: number }\n): InternalCellRange {\n return {\n startRow: anchor.row,\n startCol: anchor.col,\n endRow: current.row,\n endCol: current.col,\n };\n}\n\n/**\n * Calculate the number of cells in a range.\n *\n * @param range - The range to measure\n * @returns Total number of cells in the range\n */\nexport function getRangeCellCount(range: InternalCellRange): number {\n const normalized = normalizeRange(range);\n const rowCount = normalized.endRow - normalized.startRow + 1;\n const colCount = normalized.endCol - normalized.startCol + 1;\n return rowCount * colCount;\n}\n\n/**\n * Check if two ranges are equal (same boundaries).\n *\n * @param a - First range\n * @param b - Second range\n * @returns True if ranges have same boundaries after normalization\n */\nexport function rangesEqual(a: InternalCellRange, b: InternalCellRange): boolean {\n const normA = normalizeRange(a);\n const normB = normalizeRange(b);\n return (\n normA.startRow === normB.startRow &&\n normA.startCol === normB.startCol &&\n normA.endRow === normB.endRow &&\n normA.endCol === normB.endCol\n );\n}\n\n/**\n * Check if a range is a single cell (1x1).\n *\n * @param range - The range to check\n * @returns True if the range is exactly one cell\n */\nexport function isSingleCell(range: InternalCellRange): boolean {\n const normalized = normalizeRange(range);\n return normalized.startRow === normalized.endRow && normalized.startCol === normalized.endCol;\n}\n","/**\n * Selection Plugin (Class-based)\n *\n * Provides selection functionality for tbw-grid.\n * Supports three modes:\n * - 'cell': Single cell selection (default). No border, just focus highlight.\n * - 'row': Row selection. Clicking a cell selects the entire row.\n * - 'range': Range selection. Shift+click or drag to select rectangular cell ranges.\n */\n\nimport { clearCellFocus, getRowIndexFromCell } from '../../core/internal/utils';\nimport type { GridElement, PluginManifest, PluginQuery } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin, CellClickEvent, CellMouseEvent } from '../../core/plugin/base-plugin';\nimport { isExpanderColumn, isUtilityColumn } from '../../core/plugin/expander-column';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n createRangeFromAnchor,\n getAllCellsInRanges,\n isCellInAnyRange,\n normalizeRange,\n rangesEqual,\n toPublicRanges,\n} from './range-selection';\nimport styles from './selection.css?inline';\nimport type {\n CellRange,\n InternalCellRange,\n SelectionChangeDetail,\n SelectionConfig,\n SelectionMode,\n SelectionResult,\n} from './types';\n\n/** Special field name for the selection checkbox column */\nconst CHECKBOX_COLUMN_FIELD = '__tbw_checkbox';\n\n/**\n * Build the selection change event detail for the current state.\n */\nfunction buildSelectionEvent(\n mode: SelectionMode,\n state: {\n selectedCell: { row: number; col: number } | null;\n selected: Set<number>;\n ranges: InternalCellRange[];\n },\n colCount: number,\n): SelectionChangeDetail {\n if (mode === 'cell' && state.selectedCell) {\n return {\n mode,\n ranges: [\n {\n from: { row: state.selectedCell.row, col: state.selectedCell.col },\n to: { row: state.selectedCell.row, col: state.selectedCell.col },\n },\n ],\n };\n }\n\n if (mode === 'row' && state.selected.size > 0) {\n // Sort rows and merge contiguous indices into minimal ranges\n const sorted = [...state.selected].sort((a, b) => a - b);\n const ranges: CellRange[] = [];\n let start = sorted[0];\n let end = start;\n for (let i = 1; i < sorted.length; i++) {\n if (sorted[i] === end + 1) {\n end = sorted[i];\n } else {\n ranges.push({ from: { row: start, col: 0 }, to: { row: end, col: colCount - 1 } });\n start = sorted[i];\n end = start;\n }\n }\n ranges.push({ from: { row: start, col: 0 }, to: { row: end, col: colCount - 1 } });\n return { mode, ranges };\n }\n\n if (mode === 'range' && state.ranges.length > 0) {\n return { mode, ranges: toPublicRanges(state.ranges) };\n }\n\n return { mode, ranges: [] };\n}\n\n/**\n * Selection Plugin for tbw-grid\n *\n * Adds cell, row, and range selection capabilities to the grid with full keyboard support.\n * Whether you need simple cell highlighting or complex multi-range selections, this plugin has you covered.\n *\n * ## Installation\n *\n * ```ts\n * import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';\n * ```\n *\n * ## Selection Modes\n *\n * Configure the plugin with one of three modes via {@link SelectionConfig}:\n *\n * - **`'cell'`** - Single cell selection (default). Click cells to select individually.\n * - **`'row'`** - Full row selection. Click anywhere in a row to select the entire row.\n * - **`'range'`** - Rectangular selection. Click and drag or Shift+Click to select ranges.\n *\n * ## Keyboard Shortcuts\n *\n * | Shortcut | Action |\n * |----------|--------|\n * | `Arrow Keys` | Move selection |\n * | `Shift + Arrow` | Extend selection (range mode) |\n * | `Ctrl/Cmd + Click` | Toggle selection (multi-select) |\n * | `Shift + Click` | Extend to clicked cell/row |\n * | `Ctrl/Cmd + A` | Select all (range mode) |\n * | `Escape` | Clear selection |\n *\n * ## CSS Custom Properties\n *\n * | Property | Description |\n * |----------|-------------|\n * | `--tbw-focus-background` | Focused row background |\n * | `--tbw-range-selection-bg` | Range selection fill |\n * | `--tbw-range-border-color` | Range selection border |\n *\n * @example Basic row selection\n * ```ts\n * grid.gridConfig = {\n * columns: [...],\n * plugins: [new SelectionPlugin({ mode: 'row' })],\n * };\n * ```\n *\n * @example Range selection with event handling\n * ```ts\n * grid.gridConfig = {\n * plugins: [new SelectionPlugin({ mode: 'range' })],\n * };\n *\n * grid.addEventListener('selection-change', (e) => {\n * const { mode, ranges } = e.detail;\n * console.log(`Selected ${ranges.length} ranges in ${mode} mode`);\n * });\n * ```\n *\n * @example Programmatic selection control\n * ```ts\n * const plugin = grid.getPlugin(SelectionPlugin);\n *\n * // Get current selection\n * const selection = plugin.getSelection();\n * console.log(selection.ranges);\n *\n * // Set selection programmatically\n * plugin.setRanges([{ from: { row: 0, col: 0 }, to: { row: 5, col: 3 } }]);\n *\n * // Clear all selection\n * plugin.clearSelection();\n * ```\n *\n * @see {@link SelectionMode} for detailed mode descriptions\n * @see {@link SelectionConfig} for configuration options\n * @see {@link SelectionResult} for the selection result structure\n * @see [Live Demos](?path=/docs/grid-plugins-selection--docs) for interactive examples\n */\nexport class SelectionPlugin extends BaseGridPlugin<SelectionConfig> {\n /**\n * Plugin manifest - declares queries and configuration validation rules.\n * @internal\n */\n static override readonly manifest: PluginManifest<SelectionConfig> = {\n queries: [\n { type: 'getSelection', description: 'Get the current selection state' },\n { type: 'selectRows', description: 'Select specific rows by index (row mode only)' },\n { type: 'getSelectedRowIndices', description: 'Get sorted array of selected row indices' },\n ],\n configRules: [\n {\n id: 'selection/range-dblclick',\n severity: 'warn',\n message:\n `\"triggerOn: 'dblclick'\" has no effect when mode is \"range\".\\n` +\n ` → Range selection uses drag interaction (mousedown → mousemove), not click events.\\n` +\n ` → The \"triggerOn\" option only affects \"cell\" and \"row\" selection modes.`,\n check: (config) => config.mode === 'range' && config.triggerOn === 'dblclick',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'selection';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<SelectionConfig> {\n return {\n mode: 'cell',\n triggerOn: 'click',\n enabled: true,\n };\n }\n\n // #region Internal State\n /** Row selection state (row mode) */\n private selected = new Set<number>();\n private lastSelected: number | null = null;\n private anchor: number | null = null;\n\n /** Range selection state (range mode) */\n private ranges: InternalCellRange[] = [];\n private activeRange: InternalCellRange | null = null;\n private cellAnchor: { row: number; col: number } | null = null;\n private isDragging = false;\n\n /** Pending keyboard navigation update (processed in afterRender) */\n private pendingKeyboardUpdate: { shiftKey: boolean } | null = null;\n\n /** Cell selection state (cell mode) */\n private selectedCell: { row: number; col: number } | null = null;\n\n /** Last synced focus row — used to detect when grid focus moves so selection follows */\n private lastSyncedFocusRow = -1;\n /** Last synced focus col (cell mode) */\n private lastSyncedFocusCol = -1;\n\n /** True when selection was explicitly set (click/keyboard) — prevents #syncSelectionToFocus from overwriting */\n private explicitSelection = false;\n\n // #endregion\n\n // #region Private Helpers - Selection Enabled Check\n\n /**\n * Check if selection is enabled at the grid level.\n * Grid-wide `selectable: false` or plugin's `enabled: false` disables all selection.\n */\n private isSelectionEnabled(): boolean {\n // Check plugin config first\n if (this.config.enabled === false) return false;\n // Check grid-level config\n return this.grid.effectiveConfig?.selectable !== false;\n }\n\n // #endregion\n\n // #region Private Helpers - Selectability\n\n /**\n * Check if a row/cell is selectable.\n * Returns true if selectable, false if not.\n */\n private checkSelectable(rowIndex: number, colIndex?: number): boolean {\n const { isSelectable } = this.config;\n if (!isSelectable) return true; // No callback = all selectable\n\n const row = this.rows[rowIndex];\n if (!row) return false;\n\n const column = colIndex !== undefined ? this.columns[colIndex] : undefined;\n return isSelectable(row, rowIndex, column, colIndex);\n }\n\n /**\n * Check if an entire row is selectable (for row mode).\n */\n private isRowSelectable(rowIndex: number): boolean {\n return this.checkSelectable(rowIndex);\n }\n\n /**\n * Check if a cell is selectable (for cell/range modes).\n */\n private isCellSelectable(rowIndex: number, colIndex: number): boolean {\n return this.checkSelectable(rowIndex, colIndex);\n }\n\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n\n // Subscribe to events that invalidate selection\n // When rows change due to filtering/grouping/tree operations, selection indices become invalid\n this.on('filter-applied', () => this.clearSelectionSilent());\n this.on('grouping-state-change', () => this.clearSelectionSilent());\n this.on('tree-state-change', () => this.clearSelectionSilent());\n }\n\n /**\n * Handle queries from other plugins.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'getSelection') {\n return this.getSelection();\n }\n if (query.type === 'getSelectedRowIndices') {\n return this.getSelectedRowIndices();\n }\n if (query.type === 'selectRows') {\n this.selectRows(query.context as number[]);\n return true;\n }\n return undefined;\n }\n\n /** @internal */\n override detach(): void {\n this.selected.clear();\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n this.isDragging = false;\n this.selectedCell = null;\n this.pendingKeyboardUpdate = null;\n this.lastSyncedFocusRow = -1;\n this.lastSyncedFocusCol = -1;\n }\n\n /**\n * Clear selection without emitting an event.\n * Used when selection is invalidated by external changes (filtering, grouping, etc.)\n */\n private clearSelectionSilent(): void {\n this.selected.clear();\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n this.selectedCell = null;\n this.lastSelected = null;\n this.anchor = null;\n this.lastSyncedFocusRow = -1;\n this.lastSyncedFocusCol = -1;\n this.requestAfterRender();\n }\n\n // #endregion\n\n // #region Event Handlers\n\n /** @internal */\n override onCellClick(event: CellClickEvent): boolean {\n // Skip all selection if disabled at grid level or plugin level\n if (!this.isSelectionEnabled()) return false;\n\n const { rowIndex, colIndex, originalEvent } = event;\n const { mode, triggerOn = 'click' } = this.config;\n\n // Skip if event type doesn't match configured trigger\n // This allows dblclick mode to only select on double-click\n if (originalEvent.type !== triggerOn) {\n return false;\n }\n\n // Check if this is a utility column (expander columns, etc.)\n const column = this.columns[colIndex];\n const isUtility = column && isUtilityColumn(column);\n\n // CELL MODE: Single cell selection - skip utility columns and non-selectable cells\n if (mode === 'cell') {\n if (isUtility) {\n return false; // Allow event to propagate, but don't select utility cells\n }\n if (!this.isCellSelectable(rowIndex, colIndex)) {\n return false; // Cell is not selectable\n }\n // Only emit if selection actually changed\n const currentCell = this.selectedCell;\n if (currentCell && currentCell.row === rowIndex && currentCell.col === colIndex) {\n return false; // Same cell already selected\n }\n this.selectedCell = { row: rowIndex, col: colIndex };\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return false;\n }\n\n // ROW MODE: Multi-select with Shift/Ctrl, checkbox toggle, or single select\n if (mode === 'row') {\n if (!this.isRowSelectable(rowIndex)) {\n return false; // Row is not selectable\n }\n\n const shiftKey = originalEvent.shiftKey;\n const ctrlKey = originalEvent.ctrlKey || originalEvent.metaKey;\n const isCheckbox = column?.meta?.checkboxColumn === true;\n\n if (shiftKey && this.anchor !== null) {\n // Shift+Click: Range select from anchor to clicked row\n const start = Math.min(this.anchor, rowIndex);\n const end = Math.max(this.anchor, rowIndex);\n if (!ctrlKey) {\n this.selected.clear();\n }\n for (let i = start; i <= end; i++) {\n if (this.isRowSelectable(i)) {\n this.selected.add(i);\n }\n }\n } else if (ctrlKey || isCheckbox) {\n // Ctrl+Click or checkbox click: Toggle individual row\n if (this.selected.has(rowIndex)) {\n this.selected.delete(rowIndex);\n } else {\n this.selected.add(rowIndex);\n }\n this.anchor = rowIndex;\n } else {\n // Plain click: Clear all, select only clicked row\n if (this.selected.size === 1 && this.selected.has(rowIndex)) {\n return false; // Same row already selected\n }\n this.selected.clear();\n this.selected.add(rowIndex);\n this.anchor = rowIndex;\n }\n\n this.lastSelected = rowIndex;\n this.explicitSelection = true;\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return false;\n }\n\n // RANGE MODE: Shift+click extends selection, click starts new\n if (mode === 'range') {\n // Skip utility columns in range mode - don't start selection from them\n if (isUtility) {\n return false;\n }\n\n // Skip non-selectable cells in range mode\n if (!this.isCellSelectable(rowIndex, colIndex)) {\n return false;\n }\n\n const shiftKey = originalEvent.shiftKey;\n const ctrlKey = originalEvent.ctrlKey || originalEvent.metaKey;\n\n if (shiftKey && this.cellAnchor) {\n // Extend selection from anchor\n const newRange = createRangeFromAnchor(this.cellAnchor, { row: rowIndex, col: colIndex });\n\n // Check if range actually changed\n const currentRange = this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] : null;\n if (currentRange && rangesEqual(currentRange, newRange)) {\n return false; // Same range already selected\n }\n\n if (ctrlKey) {\n if (this.ranges.length > 0) {\n this.ranges[this.ranges.length - 1] = newRange;\n } else {\n this.ranges.push(newRange);\n }\n } else {\n this.ranges = [newRange];\n }\n this.activeRange = newRange;\n } else if (ctrlKey) {\n const newRange: InternalCellRange = {\n startRow: rowIndex,\n startCol: colIndex,\n endRow: rowIndex,\n endCol: colIndex,\n };\n this.ranges.push(newRange);\n this.activeRange = newRange;\n this.cellAnchor = { row: rowIndex, col: colIndex };\n } else {\n // Plain click - check if same single-cell range already selected\n const newRange: InternalCellRange = {\n startRow: rowIndex,\n startCol: colIndex,\n endRow: rowIndex,\n endCol: colIndex,\n };\n\n // Only emit if selection actually changed\n if (this.ranges.length === 1 && rangesEqual(this.ranges[0], newRange)) {\n return false; // Same cell already selected\n }\n\n this.ranges = [newRange];\n this.activeRange = newRange;\n this.cellAnchor = { row: rowIndex, col: colIndex };\n }\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n\n this.requestAfterRender();\n return false;\n }\n\n return false;\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean {\n // Skip all selection if disabled at grid level or plugin level\n if (!this.isSelectionEnabled()) return false;\n\n const { mode } = this.config;\n const navKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Tab', 'Home', 'End', 'PageUp', 'PageDown'];\n const isNavKey = navKeys.includes(event.key);\n\n // Escape clears selection in all modes\n // But if editing is active, let the EditingPlugin handle Escape first\n if (event.key === 'Escape') {\n const isEditing = this.grid.query<boolean>('isEditing');\n if (isEditing.some(Boolean)) {\n return false; // Defer to EditingPlugin to cancel the active edit\n }\n\n if (mode === 'cell') {\n this.selectedCell = null;\n } else if (mode === 'row') {\n this.selected.clear();\n this.anchor = null;\n } else if (mode === 'range') {\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n }\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n\n // CELL MODE: Selection follows focus (but respects selectability)\n if (mode === 'cell' && isNavKey) {\n // Use queueMicrotask so grid's handler runs first and updates focusRow/focusCol\n queueMicrotask(() => {\n const focusRow = this.grid._focusRow;\n const focusCol = this.grid._focusCol;\n // Only select if the cell is selectable\n if (this.isCellSelectable(focusRow, focusCol)) {\n this.selectedCell = { row: focusRow, col: focusCol };\n } else {\n // Clear selection when navigating to non-selectable cell\n this.selectedCell = null;\n }\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n });\n return false; // Let grid handle navigation\n }\n\n // ROW MODE: Arrow keys move selection, Shift+Arrow extends, Ctrl+A selects all\n if (mode === 'row') {\n if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {\n const shiftKey = event.shiftKey;\n\n // Set anchor before grid moves focus\n if (shiftKey && this.anchor === null) {\n this.anchor = this.grid._focusRow;\n }\n\n // Let grid move focus first, then sync row selection\n queueMicrotask(() => {\n const focusRow = this.grid._focusRow;\n\n if (shiftKey && this.anchor !== null) {\n // Shift+Arrow: Extend selection from anchor\n this.selected.clear();\n const start = Math.min(this.anchor, focusRow);\n const end = Math.max(this.anchor, focusRow);\n for (let i = start; i <= end; i++) {\n if (this.isRowSelectable(i)) {\n this.selected.add(i);\n }\n }\n } else {\n // Plain arrow: Single select\n if (this.isRowSelectable(focusRow)) {\n this.selected.clear();\n this.selected.add(focusRow);\n this.anchor = focusRow;\n } else {\n this.selected.clear();\n }\n }\n\n this.lastSelected = focusRow;\n this.explicitSelection = true;\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n });\n return false; // Let grid handle navigation\n }\n\n // Ctrl+A: Select all rows (skip when editing)\n if (event.key === 'a' && (event.ctrlKey || event.metaKey)) {\n const isEditing = this.grid.query<boolean>('isEditing');\n if (isEditing.some(Boolean)) return false;\n event.preventDefault();\n event.stopPropagation();\n this.selectAll();\n return true;\n }\n }\n\n // RANGE MODE: Shift+Arrow extends, plain Arrow resets\n // Tab key always navigates without extending (even with Shift)\n if (mode === 'range' && isNavKey) {\n // Tab should not extend selection - it just navigates to the next/previous cell\n const isTabKey = event.key === 'Tab';\n const shouldExtend = event.shiftKey && !isTabKey;\n\n // Capture anchor BEFORE grid moves focus (synchronous)\n // This ensures the anchor is the starting point, not the destination\n if (shouldExtend && !this.cellAnchor) {\n this.cellAnchor = { row: this.grid._focusRow, col: this.grid._focusCol };\n }\n\n // Mark pending update - will be processed in afterRender when grid updates focus\n this.pendingKeyboardUpdate = { shiftKey: shouldExtend };\n\n // Schedule afterRender to run after grid's keyboard handler completes\n // Grid's refreshVirtualWindow(false) skips afterRender for performance,\n // so we explicitly request it to process pendingKeyboardUpdate\n queueMicrotask(() => this.requestAfterRender());\n\n return false; // Let grid handle navigation\n }\n\n // Ctrl+A selects all in range mode (skip when editing)\n if (mode === 'range' && event.key === 'a' && (event.ctrlKey || event.metaKey)) {\n const isEditing = this.grid.query<boolean>('isEditing');\n if (isEditing.some(Boolean)) return false;\n event.preventDefault();\n event.stopPropagation();\n this.selectAll();\n return true;\n }\n\n return false;\n }\n\n /** @internal */\n override onCellMouseDown(event: CellMouseEvent): boolean | void {\n // Skip all selection if disabled at grid level or plugin level\n if (!this.isSelectionEnabled()) return;\n\n if (this.config.mode !== 'range') return;\n if (event.rowIndex === undefined || event.colIndex === undefined) return;\n if (event.rowIndex < 0) return; // Header\n\n // Skip utility columns (expander columns, etc.)\n const column = this.columns[event.colIndex];\n if (column && isUtilityColumn(column)) {\n return; // Don't start selection on utility columns\n }\n\n // Skip non-selectable cells - don't start drag from them\n if (!this.isCellSelectable(event.rowIndex, event.colIndex)) {\n return;\n }\n\n // Let onCellClick handle shift+click for range extension\n if (event.originalEvent.shiftKey && this.cellAnchor) {\n return;\n }\n\n // Start drag selection\n this.isDragging = true;\n const rowIndex = event.rowIndex;\n const colIndex = event.colIndex;\n\n const ctrlKey = event.originalEvent.ctrlKey || event.originalEvent.metaKey;\n\n const newRange: InternalCellRange = {\n startRow: rowIndex,\n startCol: colIndex,\n endRow: rowIndex,\n endCol: colIndex,\n };\n\n // Check if selection is actually changing (for non-Ctrl clicks)\n if (!ctrlKey && this.ranges.length === 1 && rangesEqual(this.ranges[0], newRange)) {\n // Same cell already selected, just update anchor for potential drag\n this.cellAnchor = { row: rowIndex, col: colIndex };\n return true;\n }\n\n this.cellAnchor = { row: rowIndex, col: colIndex };\n\n if (!ctrlKey) {\n this.ranges = [];\n }\n\n this.ranges.push(newRange);\n this.activeRange = newRange;\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n\n /** @internal */\n override onCellMouseMove(event: CellMouseEvent): boolean | void {\n // Skip all selection if disabled at grid level or plugin level\n if (!this.isSelectionEnabled()) return;\n\n if (this.config.mode !== 'range') return;\n if (!this.isDragging || !this.cellAnchor) return;\n if (event.rowIndex === undefined || event.colIndex === undefined) return;\n if (event.rowIndex < 0) return;\n\n // When dragging, clamp to first data column (skip utility columns)\n let targetCol = event.colIndex;\n const column = this.columns[targetCol];\n if (column && isUtilityColumn(column)) {\n // Find the first non-utility column\n const firstDataCol = this.columns.findIndex((col) => !isUtilityColumn(col));\n if (firstDataCol >= 0) {\n targetCol = firstDataCol;\n }\n }\n\n const newRange = createRangeFromAnchor(this.cellAnchor, { row: event.rowIndex, col: targetCol });\n\n // Only update and emit if the range actually changed\n const currentRange = this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] : null;\n if (currentRange && rangesEqual(currentRange, newRange)) {\n return true; // Range unchanged, no need to update\n }\n\n if (this.ranges.length > 0) {\n this.ranges[this.ranges.length - 1] = newRange;\n } else {\n this.ranges.push(newRange);\n }\n this.activeRange = newRange;\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n\n /** @internal */\n override onCellMouseUp(_event: CellMouseEvent): boolean | void {\n // Skip all selection if disabled at grid level or plugin level\n if (!this.isSelectionEnabled()) return;\n\n if (this.config.mode !== 'range') return;\n if (this.isDragging) {\n this.isDragging = false;\n return true;\n }\n }\n\n // #region Checkbox Column\n\n /**\n * Inject checkbox column when `checkbox: true` and mode is `'row'`.\n * @internal\n */\n override processColumns(columns: ColumnConfig[]): ColumnConfig[] {\n if (this.config.checkbox && this.config.mode === 'row') {\n // Check if checkbox column already exists\n if (columns.some((col) => col.field === CHECKBOX_COLUMN_FIELD)) {\n return columns;\n }\n const checkboxCol = this.#createCheckboxColumn();\n // Insert after expander column if present, otherwise first\n const expanderIdx = columns.findIndex(isExpanderColumn);\n const insertAt = expanderIdx >= 0 ? expanderIdx + 1 : 0;\n return [...columns.slice(0, insertAt), checkboxCol, ...columns.slice(insertAt)];\n }\n return columns;\n }\n\n /**\n * Create the checkbox utility column configuration.\n */\n #createCheckboxColumn(): ColumnConfig {\n return {\n field: CHECKBOX_COLUMN_FIELD,\n header: '',\n width: 32,\n resizable: false,\n sortable: false,\n meta: {\n lockPosition: true,\n suppressMovable: true,\n utility: true,\n checkboxColumn: true,\n },\n headerRenderer: () => {\n const container = document.createElement('div');\n container.className = 'tbw-checkbox-header';\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.className = 'tbw-select-all-checkbox';\n checkbox.addEventListener('click', (e) => {\n e.stopPropagation(); // Prevent header sort\n if ((e.target as HTMLInputElement).checked) {\n this.selectAll();\n } else {\n this.clearSelection();\n }\n });\n container.appendChild(checkbox);\n return container;\n },\n renderer: (ctx) => {\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.className = 'tbw-select-row-checkbox';\n // Set initial checked state from current selection\n const cellEl = ctx.cellEl;\n if (cellEl) {\n const rowIndex = parseInt(cellEl.getAttribute('data-row') ?? '-1', 10);\n if (rowIndex >= 0) {\n checkbox.checked = this.selected.has(rowIndex);\n }\n }\n return checkbox;\n },\n };\n }\n\n /**\n * Update checkbox checked states to reflect current selection.\n * Called from #applySelectionClasses.\n */\n #updateCheckboxStates(gridEl: HTMLElement): void {\n // Update row checkboxes\n const rowCheckboxes = gridEl.querySelectorAll('.tbw-select-row-checkbox') as NodeListOf<HTMLInputElement>;\n rowCheckboxes.forEach((checkbox) => {\n const cell = checkbox.closest('.cell');\n const rowIndex = cell ? getRowIndexFromCell(cell) : -1;\n if (rowIndex >= 0) {\n checkbox.checked = this.selected.has(rowIndex);\n }\n });\n\n // Update header select-all checkbox\n const headerCheckbox = gridEl.querySelector('.tbw-select-all-checkbox') as HTMLInputElement | null;\n if (headerCheckbox) {\n const rowCount = this.rows.length;\n let selectableCount = 0;\n if (this.config.isSelectable) {\n for (let i = 0; i < rowCount; i++) {\n if (this.isRowSelectable(i)) selectableCount++;\n }\n } else {\n selectableCount = rowCount;\n }\n const allSelected = selectableCount > 0 && this.selected.size >= selectableCount;\n const someSelected = this.selected.size > 0;\n headerCheckbox.checked = allSelected;\n headerCheckbox.indeterminate = someSelected && !allSelected;\n }\n }\n\n // #endregion\n\n /**\n * Sync selection state to the grid's current focus position.\n * In row mode, keeps `selected` in sync with `_focusRow`.\n * In cell mode, keeps `selectedCell` in sync with `_focusRow`/`_focusCol`.\n * Only updates when the focus has changed since the last sync.\n * Skips when `explicitSelection` is set (click/keyboard set selection directly).\n */\n #syncSelectionToFocus(mode: string): void {\n const focusRow = this.grid._focusRow;\n const focusCol = this.grid._focusCol;\n\n if (mode === 'row') {\n // Skip auto-sync when selection was explicitly set (Shift/Ctrl click, keyboard)\n if (this.explicitSelection) {\n this.explicitSelection = false;\n this.lastSyncedFocusRow = focusRow;\n return;\n }\n\n if (focusRow !== this.lastSyncedFocusRow) {\n this.lastSyncedFocusRow = focusRow;\n if (this.isRowSelectable(focusRow)) {\n if (!this.selected.has(focusRow) || this.selected.size !== 1) {\n this.selected.clear();\n this.selected.add(focusRow);\n this.lastSelected = focusRow;\n this.anchor = focusRow;\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n }\n }\n }\n }\n\n if (mode === 'cell') {\n if (this.explicitSelection) {\n this.explicitSelection = false;\n this.lastSyncedFocusRow = focusRow;\n this.lastSyncedFocusCol = focusCol;\n return;\n }\n\n if (focusRow !== this.lastSyncedFocusRow || focusCol !== this.lastSyncedFocusCol) {\n this.lastSyncedFocusRow = focusRow;\n this.lastSyncedFocusCol = focusCol;\n if (this.isCellSelectable(focusRow, focusCol)) {\n const cur = this.selectedCell;\n if (!cur || cur.row !== focusRow || cur.col !== focusCol) {\n this.selectedCell = { row: focusRow, col: focusCol };\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n }\n }\n }\n }\n }\n\n /**\n * Apply CSS selection classes to row/cell elements.\n * Shared by afterRender and onScrollRender.\n */\n #applySelectionClasses(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n const { mode } = this.config;\n const hasSelectableCallback = !!this.config.isSelectable;\n\n // Clear all selection classes first\n const allCells = gridEl.querySelectorAll('.cell');\n allCells.forEach((cell) => {\n cell.classList.remove('selected', 'top', 'bottom', 'first', 'last');\n // Clear selectable attribute - will be re-applied below\n if (hasSelectableCallback) {\n cell.removeAttribute('data-selectable');\n }\n });\n\n const allRows = gridEl.querySelectorAll('.data-grid-row');\n allRows.forEach((row) => {\n row.classList.remove('selected', 'row-focus');\n // Clear selectable attribute - will be re-applied below\n if (hasSelectableCallback) {\n row.removeAttribute('data-selectable');\n }\n });\n\n // ROW MODE: Add row-focus class to selected rows, disable cell-focus, update checkboxes\n if (mode === 'row') {\n // In row mode, disable ALL cell-focus styling - row selection takes precedence\n clearCellFocus(gridEl);\n\n allRows.forEach((row) => {\n const firstCell = row.querySelector('.cell[data-row]');\n const rowIndex = getRowIndexFromCell(firstCell);\n if (rowIndex >= 0) {\n // Mark non-selectable rows\n if (hasSelectableCallback && !this.isRowSelectable(rowIndex)) {\n row.setAttribute('data-selectable', 'false');\n }\n if (this.selected.has(rowIndex)) {\n row.classList.add('selected', 'row-focus');\n }\n }\n });\n\n // Update checkbox states if checkbox column is enabled\n if (this.config.checkbox) {\n this.#updateCheckboxStates(gridEl);\n }\n }\n\n // CELL/RANGE MODE: Mark non-selectable cells\n if ((mode === 'cell' || mode === 'range') && hasSelectableCallback) {\n const cells = gridEl.querySelectorAll('.cell[data-row][data-col]');\n cells.forEach((cell) => {\n const rowIndex = parseInt(cell.getAttribute('data-row') ?? '-1', 10);\n const colIndex = parseInt(cell.getAttribute('data-col') ?? '-1', 10);\n if (rowIndex >= 0 && colIndex >= 0) {\n if (!this.isCellSelectable(rowIndex, colIndex)) {\n cell.setAttribute('data-selectable', 'false');\n }\n }\n });\n }\n\n // RANGE MODE: Add selected and edge classes to cells\n // Uses neighbor-based edge detection for correct multi-range borders\n if (mode === 'range' && this.ranges.length > 0) {\n // Clear all cell-focus first - selection plugin manages focus styling in range mode\n clearCellFocus(gridEl);\n\n // Pre-normalize ranges for efficient neighbor checks\n const normalizedRanges = this.ranges.map(normalizeRange);\n\n // Fast selection check against pre-normalized ranges\n const isInSelection = (r: number, c: number): boolean => {\n for (const range of normalizedRanges) {\n if (r >= range.startRow && r <= range.endRow && c >= range.startCol && c <= range.endCol) {\n return true;\n }\n }\n return false;\n };\n\n const cells = gridEl.querySelectorAll('.cell[data-row][data-col]');\n cells.forEach((cell) => {\n const rowIndex = parseInt(cell.getAttribute('data-row') ?? '-1', 10);\n const colIndex = parseInt(cell.getAttribute('data-col') ?? '-1', 10);\n if (rowIndex >= 0 && colIndex >= 0) {\n // Skip utility columns entirely - don't add any selection classes\n const column = this.columns[colIndex];\n if (column && isUtilityColumn(column)) {\n return;\n }\n\n if (isInSelection(rowIndex, colIndex)) {\n cell.classList.add('selected');\n\n // Edge detection: add border class where neighbor is not selected\n // This handles single ranges, multi-range, and irregular selections correctly\n if (!isInSelection(rowIndex - 1, colIndex)) cell.classList.add('top');\n if (!isInSelection(rowIndex + 1, colIndex)) cell.classList.add('bottom');\n if (!isInSelection(rowIndex, colIndex - 1)) cell.classList.add('first');\n if (!isInSelection(rowIndex, colIndex + 1)) cell.classList.add('last');\n }\n }\n });\n }\n\n // CELL MODE: Let the grid's native .cell-focus styling handle cell highlighting\n // No additional action needed - the grid already manages focus styling\n }\n\n /** @internal */\n override afterRender(): void {\n // Skip rendering selection if disabled at grid level or plugin level\n if (!this.isSelectionEnabled()) return;\n\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n const container = gridEl.children[0];\n const { mode } = this.config;\n\n // Process pending keyboard navigation update (range mode)\n // This runs AFTER the grid has updated focusRow/focusCol\n if (this.pendingKeyboardUpdate && mode === 'range') {\n const { shiftKey } = this.pendingKeyboardUpdate;\n this.pendingKeyboardUpdate = null;\n\n const currentRow = this.grid._focusRow;\n const currentCol = this.grid._focusCol;\n\n if (shiftKey && this.cellAnchor) {\n // Extend selection from anchor to current focus\n const newRange = createRangeFromAnchor(this.cellAnchor, { row: currentRow, col: currentCol });\n this.ranges = [newRange];\n this.activeRange = newRange;\n } else if (!shiftKey) {\n // Without shift, clear selection (cell-focus will show instead)\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = { row: currentRow, col: currentCol }; // Reset anchor to current position\n }\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n }\n\n // Sync selection to grid's focus position.\n // This ensures selection follows keyboard navigation (Tab, arrows, etc.)\n // regardless of which plugin moved the focus.\n this.#syncSelectionToFocus(mode);\n\n // Set data attribute on host for CSS variable scoping\n (this.grid as unknown as Element).setAttribute('data-selection-mode', mode);\n\n // Toggle .selecting class during drag to prevent text selection\n if (container) {\n container.classList.toggle('selecting', this.isDragging);\n }\n\n this.#applySelectionClasses();\n }\n\n /**\n * Called after scroll-triggered row rendering.\n * Reapplies selection classes to recycled DOM elements.\n * @internal\n */\n override onScrollRender(): void {\n // Skip rendering selection classes if disabled\n if (!this.isSelectionEnabled()) return;\n\n this.#applySelectionClasses();\n }\n\n // #endregion\n\n // #region Public API\n\n /**\n * Get the current selection as a unified result.\n * Works for all selection modes and always returns ranges.\n *\n * @example\n * ```ts\n * const selection = plugin.getSelection();\n * if (selection.ranges.length > 0) {\n * const { from, to } = selection.ranges[0];\n * // For cell mode: from === to (single cell)\n * // For row mode: from.col = 0, to.col = lastCol (full row)\n * // For range mode: rectangular selection\n * }\n * ```\n */\n getSelection(): SelectionResult {\n return {\n mode: this.config.mode,\n ranges: this.#buildEvent().ranges,\n anchor: this.cellAnchor,\n };\n }\n\n /**\n * Get all selected cells across all ranges.\n */\n getSelectedCells(): Array<{ row: number; col: number }> {\n return getAllCellsInRanges(this.ranges);\n }\n\n /**\n * Check if a specific cell is in range selection.\n */\n isCellSelected(row: number, col: number): boolean {\n return isCellInAnyRange(row, col, this.ranges);\n }\n\n /**\n * Select all selectable rows (row mode) or all cells (range mode).\n *\n * In row mode, selects every row where `isSelectable` returns true (or all rows if no callback).\n * In range mode, creates a single range spanning all rows and columns.\n * Has no effect in cell mode.\n *\n * @example\n * ```ts\n * const plugin = grid.getPlugin(SelectionPlugin);\n * plugin.selectAll(); // Selects everything in current mode\n * ```\n */\n selectAll(): void {\n const { mode } = this.config;\n\n if (mode === 'row') {\n this.selected.clear();\n for (let i = 0; i < this.rows.length; i++) {\n if (this.isRowSelectable(i)) {\n this.selected.add(i);\n }\n }\n this.explicitSelection = true;\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n } else if (mode === 'range') {\n const rowCount = this.rows.length;\n const colCount = this.columns.length;\n if (rowCount > 0 && colCount > 0) {\n const allRange: InternalCellRange = {\n startRow: 0,\n startCol: 0,\n endRow: rowCount - 1,\n endCol: colCount - 1,\n };\n this.ranges = [allRange];\n this.activeRange = allRange;\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n }\n }\n }\n\n /**\n * Select specific rows by index (row mode only).\n * Replaces the current selection with the provided row indices.\n * Indices that are out of bounds or fail the `isSelectable` check are ignored.\n *\n * @param indices - Array of row indices to select\n *\n * @example\n * ```ts\n * const plugin = grid.getPlugin(SelectionPlugin);\n * plugin.selectRows([0, 2, 4]); // Select rows 0, 2, and 4\n * ```\n */\n selectRows(indices: number[]): void {\n if (this.config.mode !== 'row') return;\n this.selected.clear();\n for (const idx of indices) {\n if (idx >= 0 && idx < this.rows.length && this.isRowSelectable(idx)) {\n this.selected.add(idx);\n }\n }\n this.anchor = indices.length > 0 ? indices[indices.length - 1] : null;\n this.explicitSelection = true;\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n }\n\n /**\n * Get the indices of all selected rows (convenience for row mode).\n * Returns indices sorted in ascending order.\n *\n * @example\n * ```ts\n * const plugin = grid.getPlugin(SelectionPlugin);\n * const rows = plugin.getSelectedRowIndices(); // [0, 2, 4]\n * ```\n */\n getSelectedRowIndices(): number[] {\n return [...this.selected].sort((a, b) => a - b);\n }\n\n /**\n * Clear all selection.\n */\n clearSelection(): void {\n this.selectedCell = null;\n this.selected.clear();\n this.anchor = null;\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n this.emit<SelectionChangeDetail>('selection-change', { mode: this.config.mode, ranges: [] });\n this.requestAfterRender();\n }\n\n /**\n * Set selected ranges programmatically.\n */\n setRanges(ranges: CellRange[]): void {\n this.ranges = ranges.map((r) => ({\n startRow: r.from.row,\n startCol: r.from.col,\n endRow: r.to.row,\n endCol: r.to.col,\n }));\n this.activeRange = this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] : null;\n this.emit<SelectionChangeDetail>('selection-change', {\n mode: this.config.mode,\n ranges: toPublicRanges(this.ranges),\n });\n this.requestAfterRender();\n }\n\n // #endregion\n\n // #region Private Helpers\n\n #buildEvent(): SelectionChangeDetail {\n return buildSelectionEvent(\n this.config.mode,\n {\n selectedCell: this.selectedCell,\n selected: this.selected,\n ranges: this.ranges,\n },\n this.columns.length,\n );\n }\n\n // #endregion\n}\n","import type { ServerSideDataSource, GetRowsParams, GetRowsResult } from './types';\n\nexport function getBlockNumber(rowIndex: number, blockSize: number): number {\n return Math.floor(rowIndex / blockSize);\n}\n\nexport function getBlockRange(blockNumber: number, blockSize: number): { start: number; end: number } {\n return {\n start: blockNumber * blockSize,\n end: (blockNumber + 1) * blockSize,\n };\n}\n\nexport function getRequiredBlocks(startRow: number, endRow: number, blockSize: number): number[] {\n const startBlock = getBlockNumber(startRow, blockSize);\n const endBlock = getBlockNumber(endRow - 1, blockSize);\n\n const blocks: number[] = [];\n for (let i = startBlock; i <= endBlock; i++) {\n blocks.push(i);\n }\n return blocks;\n}\n\nexport async function loadBlock(\n dataSource: ServerSideDataSource,\n blockNumber: number,\n blockSize: number,\n params: Partial<GetRowsParams>\n): Promise<GetRowsResult> {\n const range = getBlockRange(blockNumber, blockSize);\n\n return dataSource.getRows({\n startRow: range.start,\n endRow: range.end,\n sortModel: params.sortModel,\n filterModel: params.filterModel,\n });\n}\n\nexport function getRowFromCache(\n rowIndex: number,\n blockSize: number,\n loadedBlocks: Map<number, any[]>\n): any | undefined {\n const blockNumber = getBlockNumber(rowIndex, blockSize);\n const block = loadedBlocks.get(blockNumber);\n if (!block) return undefined;\n\n const indexInBlock = rowIndex % blockSize;\n return block[indexInBlock];\n}\n\nexport function isBlockLoaded(blockNumber: number, loadedBlocks: Map<number, any[]>): boolean {\n return loadedBlocks.has(blockNumber);\n}\n\nexport function isBlockLoading(blockNumber: number, loadingBlocks: Set<number>): boolean {\n return loadingBlocks.has(blockNumber);\n}\n","/**\n * Server-Side Data Plugin (Class-based)\n *\n * Provides server-side data loading with caching and lazy loading.\n */\n\nimport { BaseGridPlugin, ScrollEvent } from '../../core/plugin/base-plugin';\nimport { getBlockNumber, getRequiredBlocks, getRowFromCache, loadBlock } from './datasource';\nimport type { ServerSideConfig, ServerSideDataSource } from './types';\n\n/** Scroll debounce delay in ms */\nconst SCROLL_DEBOUNCE_MS = 100;\n\n/**\n * Server-Side Data Plugin for tbw-grid\n *\n * Enables lazy loading of data from a remote server with caching and block-based fetching.\n * Ideal for large datasets where loading all data upfront is impractical.\n *\n * ## Installation\n *\n * ```ts\n * import { ServerSidePlugin } from '@toolbox-web/grid/plugins/server-side';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `pageSize` | `number` | `100` | Rows per block |\n * | `cacheBlockSize` | `number` | `pageSize` | Cache block size |\n * | `maxConcurrentRequests` | `number` | `2` | Max parallel data requests |\n *\n * ## DataSource Interface\n *\n * ```ts\n * interface ServerSideDataSource {\n * getRows(params: GetRowsParams): Promise<GetRowsResult>;\n * }\n * ```\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `setDataSource` | `(ds: ServerSideDataSource) => void` | Set the data source |\n * | `refresh` | `() => void` | Refresh current data |\n * | `clearCache` | `() => void` | Clear all cached blocks |\n *\n * @example Basic Server-Side Loading\n * ```ts\n * import '@toolbox-web/grid';\n * import { ServerSidePlugin } from '@toolbox-web/grid/plugins/server-side';\n *\n * const dataSource = {\n * async getRows(params) {\n * const response = await fetch(\n * `/api/data?start=${params.startRow}&end=${params.endRow}`\n * );\n * const data = await response.json();\n * return { rows: data.rows, totalRowCount: data.total };\n * },\n * };\n *\n * const plugin = new ServerSidePlugin({ pageSize: 50 });\n * grid.gridConfig = {\n * columns: [...],\n * plugins: [plugin],\n * };\n *\n * grid.ready().then(() => plugin.setDataSource(dataSource));\n * ```\n *\n * @see {@link ServerSideConfig} for configuration options\n * @see {@link ServerSideDataSource} for data source interface\n *\n * @internal Extends BaseGridPlugin\n */\nexport class ServerSidePlugin extends BaseGridPlugin<ServerSideConfig> {\n /** @internal */\n readonly name = 'serverSide';\n\n /** @internal */\n protected override get defaultConfig(): Partial<ServerSideConfig> {\n return {\n pageSize: 100,\n cacheBlockSize: 100,\n maxConcurrentRequests: 2,\n };\n }\n\n // #region Internal State\n private dataSource: ServerSideDataSource | null = null;\n private totalRowCount = 0;\n private loadedBlocks = new Map<number, unknown[]>();\n private loadingBlocks = new Set<number>();\n private lastRequestId = 0;\n private scrollDebounceTimer?: ReturnType<typeof setTimeout>;\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.dataSource = null;\n this.totalRowCount = 0;\n this.loadedBlocks.clear();\n this.loadingBlocks.clear();\n this.lastRequestId = 0;\n if (this.scrollDebounceTimer) {\n clearTimeout(this.scrollDebounceTimer);\n this.scrollDebounceTimer = undefined;\n }\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Check current viewport and load any missing blocks.\n */\n private loadRequiredBlocks(): void {\n if (!this.dataSource) return;\n\n // Get fresh viewport from grid's virtualization state\n const gridRef = this.grid as unknown as { _virtualization: { start: number; end: number } };\n const blockSize = this.config.cacheBlockSize ?? 100;\n const viewport = { startRow: gridRef._virtualization.start, endRow: gridRef._virtualization.end };\n\n // Determine which blocks are needed for current viewport\n const requiredBlocks = getRequiredBlocks(viewport.startRow, viewport.endRow, blockSize);\n\n // Load missing blocks\n for (const blockNum of requiredBlocks) {\n if (this.loadedBlocks.has(blockNum) || this.loadingBlocks.has(blockNum)) {\n continue;\n }\n\n // Check concurrent request limit\n if (this.loadingBlocks.size >= (this.config.maxConcurrentRequests ?? 2)) {\n break;\n }\n\n this.loadingBlocks.add(blockNum);\n\n loadBlock(this.dataSource, blockNum, blockSize, {})\n .then((result) => {\n this.loadedBlocks.set(blockNum, result.rows);\n this.totalRowCount = result.totalRowCount;\n this.loadingBlocks.delete(blockNum);\n this.requestRender();\n // Re-check with fresh viewport: user may have scrolled further\n this.loadRequiredBlocks();\n })\n .catch(() => {\n this.loadingBlocks.delete(blockNum);\n });\n }\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processRows(rows: readonly unknown[]): unknown[] {\n if (!this.dataSource) return [...rows];\n\n // Create placeholder rows for total count\n const result: unknown[] = [];\n for (let i = 0; i < this.totalRowCount; i++) {\n const cached = getRowFromCache(i, this.config.cacheBlockSize ?? 100, this.loadedBlocks);\n result.push(cached ?? { __loading: true, __index: i });\n }\n\n return result;\n }\n\n /** @internal */\n override onScroll(event: ScrollEvent): void {\n if (!this.dataSource) return;\n\n // Immediate check for blocks\n this.loadRequiredBlocks();\n\n // Debounce: when scrolling stops, do a final check with fresh viewport\n if (this.scrollDebounceTimer) {\n clearTimeout(this.scrollDebounceTimer);\n }\n this.scrollDebounceTimer = setTimeout(() => {\n this.loadRequiredBlocks();\n }, SCROLL_DEBOUNCE_MS);\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Set the data source for server-side loading.\n * @param dataSource - Data source implementing the getRows method\n */\n setDataSource(dataSource: ServerSideDataSource): void {\n this.dataSource = dataSource;\n this.loadedBlocks.clear();\n this.loadingBlocks.clear();\n\n // Load first block\n const blockSize = this.config.cacheBlockSize ?? 100;\n loadBlock(dataSource, 0, blockSize, {}).then((result) => {\n this.loadedBlocks.set(0, result.rows);\n this.totalRowCount = result.totalRowCount;\n this.requestRender();\n });\n }\n\n /**\n * Refresh all data from the server.\n */\n refresh(): void {\n if (!this.dataSource) return;\n this.loadedBlocks.clear();\n this.loadingBlocks.clear();\n this.requestRender();\n }\n\n /**\n * Clear all cached data without refreshing.\n */\n purgeCache(): void {\n this.loadedBlocks.clear();\n }\n\n /**\n * Get the total row count from the server.\n */\n getTotalRowCount(): number {\n return this.totalRowCount;\n }\n\n /**\n * Check if a specific row is loaded in the cache.\n * @param rowIndex - Row index to check\n */\n isRowLoaded(rowIndex: number): boolean {\n const blockSize = this.config.cacheBlockSize ?? 100;\n const blockNum = getBlockNumber(rowIndex, blockSize);\n return this.loadedBlocks.has(blockNum);\n }\n\n /**\n * Get the number of loaded cache blocks.\n */\n getLoadedBlockCount(): number {\n return this.loadedBlocks.size;\n }\n // #endregion\n}\n","/**\n * Core Tree Data Logic\n *\n * Pure functions for tree flattening, expansion, and traversal.\n */\n\nimport type { FlattenedTreeRow, TreeConfig, TreeRow } from './types';\n\n/**\n * Generates a unique key for a row.\n * Uses row.id if available, otherwise generates from path.\n */\nexport function generateRowKey(row: TreeRow, index: number, parentKey: string | null): string {\n if (row.id !== undefined) return String(row.id);\n return parentKey ? `${parentKey}-${index}` : String(index);\n}\n\n/**\n * Flattens a hierarchical tree into a flat array of rows with metadata.\n * Only includes children of expanded nodes.\n */\nexport function flattenTree(\n rows: readonly TreeRow[],\n config: TreeConfig,\n expandedKeys: Set<string>,\n parentKey: string | null = null,\n depth = 0,\n): FlattenedTreeRow[] {\n const childrenField = config.childrenField ?? 'children';\n const result: FlattenedTreeRow[] = [];\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n const key = generateRowKey(row, i, parentKey);\n const children = row[childrenField];\n const hasChildren = Array.isArray(children) && children.length > 0;\n const isExpanded = expandedKeys.has(key);\n\n result.push({\n key,\n data: row,\n depth,\n hasChildren,\n isExpanded,\n parentKey,\n });\n\n // Recursively add children if expanded\n if (hasChildren && isExpanded) {\n const childRows = flattenTree(children as TreeRow[], config, expandedKeys, key, depth + 1);\n result.push(...childRows);\n }\n }\n\n return result;\n}\n\n/**\n * Toggles the expansion state of a row.\n * Returns a new Set with the toggled state.\n */\nexport function toggleExpand(expandedKeys: Set<string>, key: string): Set<string> {\n const newExpanded = new Set(expandedKeys);\n if (newExpanded.has(key)) {\n newExpanded.delete(key);\n } else {\n newExpanded.add(key);\n }\n return newExpanded;\n}\n\n/**\n * Expands all nodes in the tree.\n * Returns a Set of all parent row keys.\n */\nexport function expandAll(\n rows: readonly TreeRow[],\n config: TreeConfig,\n parentKey: string | null = null,\n depth = 0,\n): Set<string> {\n const childrenField = config.childrenField ?? 'children';\n const keys = new Set<string>();\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n const key = generateRowKey(row, i, parentKey);\n const children = row[childrenField];\n\n if (Array.isArray(children) && children.length > 0) {\n keys.add(key);\n const childKeys = expandAll(children as TreeRow[], config, key, depth + 1);\n for (const k of childKeys) keys.add(k);\n }\n }\n\n return keys;\n}\n\n/**\n * Collapses all nodes.\n * Returns an empty Set.\n */\nexport function collapseAll(): Set<string> {\n return new Set();\n}\n\n/**\n * Gets all descendants of a node from the flattened row list.\n * Useful for operations that need to affect an entire subtree.\n */\nexport function getDescendants(flattenedRows: FlattenedTreeRow[], parentKey: string): FlattenedTreeRow[] {\n const descendants: FlattenedTreeRow[] = [];\n let collecting = false;\n let parentDepth = -1;\n\n for (const row of flattenedRows) {\n if (row.key === parentKey) {\n collecting = true;\n parentDepth = row.depth;\n continue;\n }\n\n if (collecting) {\n if (row.depth > parentDepth) {\n descendants.push(row);\n } else {\n break; // No longer a descendant\n }\n }\n }\n\n return descendants;\n}\n\n/**\n * Finds the path from root to a specific row key.\n * Returns an array of keys from root to the target (inclusive).\n */\nexport function getPathToKey(\n rows: readonly TreeRow[],\n targetKey: string,\n config: TreeConfig,\n parentKey: string | null = null,\n depth = 0,\n): string[] | null {\n const childrenField = config.childrenField ?? 'children';\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n const key = generateRowKey(row, i, parentKey);\n\n if (key === targetKey) {\n return [key];\n }\n\n const children = row[childrenField];\n if (Array.isArray(children) && children.length > 0) {\n const childPath = getPathToKey(children as TreeRow[], targetKey, config, key, depth + 1);\n if (childPath) {\n return [key, ...childPath];\n }\n }\n }\n\n return null;\n}\n\n/**\n * Expands all ancestors of a specific row to make it visible.\n * Returns a new Set with the required keys added.\n */\nexport function expandToKey(\n rows: readonly TreeRow[],\n targetKey: string,\n config: TreeConfig,\n existingExpanded: Set<string>,\n): Set<string> {\n const path = getPathToKey(rows, targetKey, config);\n if (!path) return existingExpanded;\n\n const newExpanded = new Set(existingExpanded);\n // Add all keys except the last one (the target itself)\n for (let i = 0; i < path.length - 1; i++) {\n newExpanded.add(path[i]);\n }\n return newExpanded;\n}\n","/**\n * Tree Structure Auto-Detection\n *\n * Utilities for detecting hierarchical tree data structures.\n */\n\nimport type { TreeRow } from './types';\n\n/**\n * Detects if the data has a tree structure by checking for children arrays.\n */\nexport function detectTreeStructure(rows: readonly TreeRow[], childrenField = 'children'): boolean {\n if (!Array.isArray(rows) || rows.length === 0) return false;\n\n // Check if any row has a non-empty children array\n for (const row of rows) {\n if (!row) continue;\n const children = row[childrenField];\n if (Array.isArray(children) && children.length > 0) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Attempts to infer the children field name from common patterns.\n * Returns the first field that contains an array with items.\n */\nexport function inferChildrenField(rows: readonly TreeRow[]): string | null {\n if (!Array.isArray(rows) || rows.length === 0) return null;\n\n const commonArrayFields = ['children', 'items', 'nodes', 'subRows', 'nested'];\n\n for (const row of rows) {\n if (!row || typeof row !== 'object') continue;\n\n for (const field of commonArrayFields) {\n const value = row[field];\n if (Array.isArray(value) && value.length > 0) {\n return field;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Calculates the maximum depth of the tree.\n * Useful for layout calculations and virtualization.\n */\nexport function getMaxDepth(rows: readonly TreeRow[], childrenField = 'children', currentDepth = 0): number {\n if (!Array.isArray(rows) || rows.length === 0) return currentDepth;\n\n let maxDepth = currentDepth;\n\n for (const row of rows) {\n if (!row) continue;\n const children = row[childrenField];\n if (Array.isArray(children) && children.length > 0) {\n const childDepth = getMaxDepth(children as TreeRow[], childrenField, currentDepth + 1);\n if (childDepth > maxDepth) {\n maxDepth = childDepth;\n }\n }\n }\n\n return maxDepth;\n}\n\n/**\n * Counts total nodes in the tree (including all descendants).\n */\nexport function countNodes(rows: readonly TreeRow[], childrenField = 'children'): number {\n if (!Array.isArray(rows)) return 0;\n\n let count = 0;\n for (const row of rows) {\n if (!row) continue;\n count++;\n const children = row[childrenField];\n if (Array.isArray(children)) {\n count += countNodes(children as TreeRow[], childrenField);\n }\n }\n\n return count;\n}\n","/**\n * Tree Data Plugin\n *\n * Enables hierarchical tree data with expand/collapse, sorting, and auto-detection.\n */\n\nimport {\n BaseGridPlugin,\n CellClickEvent,\n HeaderClickEvent,\n type PluginManifest,\n type PluginQuery,\n} from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, ColumnViewRenderer } from '../../core/types';\nimport { collapseAll, expandAll, expandToKey, toggleExpand } from './tree-data';\nimport { detectTreeStructure, inferChildrenField } from './tree-detect';\nimport styles from './tree.css?inline';\nimport type { ExpandCollapseAnimation, FlattenedTreeRow, TreeConfig, TreeExpandDetail, TreeRow } from './types';\n\ninterface GridWithSortState {\n _sortState?: { field: string; direction: 1 | -1 } | null;\n}\n\n/**\n * Tree Data Plugin for tbw-grid\n *\n * Transforms your flat grid into a hierarchical tree view with expandable parent-child\n * relationships. Ideal for file explorers, organizational charts, nested categories,\n * or any data with a natural hierarchy.\n *\n * ## Installation\n *\n * ```ts\n * import { TreePlugin } from '@toolbox-web/grid/plugins/tree';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `childrenField` | `string` | `'children'` | Field containing child array |\n * | `autoDetect` | `boolean` | `true` | Auto-detect tree structure from data |\n * | `defaultExpanded` | `boolean` | `false` | Expand all nodes initially |\n * | `indentWidth` | `number` | `20` | Indentation per level (pixels) |\n * | `showExpandIcons` | `boolean` | `true` | Show expand/collapse toggle icons |\n * | `animation` | `false \\| 'slide' \\| 'fade'` | `'slide'` | Animation style for expand/collapse |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `expand` | `(nodeId) => void` | Expand a specific node |\n * | `collapse` | `(nodeId) => void` | Collapse a specific node |\n * | `toggle` | `(nodeId) => void` | Toggle a node's expanded state |\n * | `expandAll` | `() => void` | Expand all nodes |\n * | `collapseAll` | `() => void` | Collapse all nodes |\n * | `getExpandedNodes` | `() => Set<string>` | Get currently expanded node keys |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-tree-toggle-size` | `1.25em` | Toggle icon width |\n * | `--tbw-tree-indent-width` | `var(--tbw-tree-toggle-size)` | Indentation per level |\n * | `--tbw-tree-accent` | `var(--tbw-color-accent)` | Toggle icon hover color |\n * | `--tbw-animation-duration` | `200ms` | Expand/collapse animation duration |\n * | `--tbw-animation-easing` | `ease-out` | Animation curve |\n *\n * @example Basic Tree with Nested Children\n * ```ts\n * import '@toolbox-web/grid';\n * import { TreePlugin } from '@toolbox-web/grid/plugins/tree';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name' },\n * { field: 'type', header: 'Type' },\n * { field: 'size', header: 'Size' },\n * ],\n * plugins: [new TreePlugin({ childrenField: 'children', indentWidth: 24 })],\n * };\n * grid.rows = [\n * {\n * id: 1,\n * name: 'Documents',\n * type: 'folder',\n * children: [\n * { id: 2, name: 'Report.docx', type: 'file', size: '24 KB' },\n * ],\n * },\n * ];\n * ```\n *\n * @example Expanded by Default with Custom Animation\n * ```ts\n * new TreePlugin({\n * defaultExpanded: true,\n * animation: 'fade', // 'slide' | 'fade' | false\n * indentWidth: 32,\n * })\n * ```\n *\n * @see {@link TreeConfig} for all configuration options\n * @see {@link FlattenedTreeRow} for the flattened row structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class TreePlugin extends BaseGridPlugin<TreeConfig> {\n static override readonly manifest: PluginManifest = {\n events: [\n {\n type: 'tree-state-change',\n description: 'Emitted when tree expansion state changes (toggle, expand all, collapse all)',\n },\n ],\n queries: [\n {\n type: 'canMoveRow',\n description: 'Returns false for rows with children (parent nodes cannot be reordered)',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'tree';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<TreeConfig> {\n return {\n childrenField: 'children',\n autoDetect: true,\n defaultExpanded: false,\n indentWidth: 20,\n showExpandIcons: true,\n animation: 'slide',\n };\n }\n\n // #region State\n\n private expandedKeys = new Set<string>();\n private initialExpansionDone = false;\n private flattenedRows: FlattenedTreeRow[] = [];\n private rowKeyMap = new Map<string, FlattenedTreeRow>();\n private previousVisibleKeys = new Set<string>();\n private keysToAnimate = new Set<string>();\n private sortState: { field: string; direction: 1 | -1 } | null = null;\n\n /** @internal */\n override detach(): void {\n this.expandedKeys.clear();\n this.initialExpansionDone = false;\n this.flattenedRows = [];\n this.rowKeyMap.clear();\n this.previousVisibleKeys.clear();\n this.keysToAnimate.clear();\n this.sortState = null;\n }\n\n /**\n * Handle plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'canMoveRow') {\n // Tree rows with children cannot be reordered\n const row = query.context as { [key: string]: unknown } | null | undefined;\n const childrenField = this.config.childrenField ?? 'children';\n const children = row?.[childrenField];\n if (Array.isArray(children) && children.length > 0) {\n return false;\n }\n }\n return undefined;\n }\n\n // #endregion\n\n // #region Animation\n\n /**\n * Get expand/collapse animation style from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide';\n }\n\n // #endregion\n\n // #region Auto-Detection\n\n detect(rows: readonly unknown[]): boolean {\n if (!this.config.autoDetect) return false;\n const treeRows = rows as readonly TreeRow[];\n const field = this.config.childrenField ?? inferChildrenField(treeRows) ?? 'children';\n return detectTreeStructure(treeRows, field);\n }\n\n // #endregion\n\n // #region Data Processing\n\n /** @internal */\n override processRows(rows: readonly unknown[]): TreeRow[] {\n const childrenField = this.config.childrenField ?? 'children';\n const treeRows = rows as readonly TreeRow[];\n\n if (!detectTreeStructure(treeRows, childrenField)) {\n this.flattenedRows = [];\n this.rowKeyMap.clear();\n this.previousVisibleKeys.clear();\n return [...rows] as TreeRow[];\n }\n\n // Assign stable keys, then optionally sort\n let data = this.withStableKeys(treeRows);\n if (this.sortState) {\n data = this.sortTree(data, this.sortState.field, this.sortState.direction);\n }\n\n // Initialize expansion if needed\n if (this.config.defaultExpanded && !this.initialExpansionDone) {\n this.expandedKeys = expandAll(data, this.config);\n this.initialExpansionDone = true;\n }\n\n // Flatten and track animations\n this.flattenedRows = this.flattenTree(data, this.expandedKeys);\n this.rowKeyMap.clear();\n this.keysToAnimate.clear();\n const currentKeys = new Set<string>();\n\n for (const row of this.flattenedRows) {\n this.rowKeyMap.set(row.key, row);\n currentKeys.add(row.key);\n if (!this.previousVisibleKeys.has(row.key) && row.depth > 0) {\n this.keysToAnimate.add(row.key);\n }\n }\n this.previousVisibleKeys = currentKeys;\n\n return this.flattenedRows.map((r) => ({\n ...r.data,\n __treeKey: r.key,\n __treeDepth: r.depth,\n __treeHasChildren: r.hasChildren,\n __treeExpanded: r.isExpanded,\n }));\n }\n\n /** Assign stable keys to rows (preserves key across sort operations) */\n private withStableKeys(rows: readonly TreeRow[], parentKey: string | null = null): TreeRow[] {\n const childrenField = this.config.childrenField ?? 'children';\n return rows.map((row, i) => {\n const stableKey = row.__stableKey as string | undefined;\n const key = row.id !== undefined ? String(row.id) : (stableKey ?? (parentKey ? `${parentKey}-${i}` : String(i)));\n const children = row[childrenField];\n const hasChildren = Array.isArray(children) && children.length > 0;\n return {\n ...row,\n __stableKey: key,\n ...(hasChildren ? { [childrenField]: this.withStableKeys(children as TreeRow[], key) } : {}),\n };\n });\n }\n\n /** Flatten tree using stable keys */\n private flattenTree(rows: readonly TreeRow[], expanded: Set<string>, depth = 0): FlattenedTreeRow[] {\n const childrenField = this.config.childrenField ?? 'children';\n const result: FlattenedTreeRow[] = [];\n\n for (const row of rows) {\n const stableKey = row.__stableKey as string | undefined;\n const key = stableKey ?? String(row.id ?? '?');\n const children = row[childrenField];\n const hasChildren = Array.isArray(children) && children.length > 0;\n const isExpanded = expanded.has(key);\n\n result.push({\n key,\n data: row,\n depth,\n hasChildren,\n isExpanded,\n parentKey: depth > 0 ? key.substring(0, key.lastIndexOf('-')) || null : null,\n });\n\n if (hasChildren && isExpanded) {\n result.push(...this.flattenTree(children as TreeRow[], expanded, depth + 1));\n }\n }\n return result;\n }\n\n /** Sort tree recursively, keeping children with parents */\n private sortTree(rows: readonly TreeRow[], field: string, dir: 1 | -1): TreeRow[] {\n const childrenField = this.config.childrenField ?? 'children';\n const sorted = [...rows].sort((a, b) => {\n const aVal = a[field],\n bVal = b[field];\n if (aVal == null && bVal == null) return 0;\n if (aVal == null) return -1;\n if (bVal == null) return 1;\n return aVal > bVal ? dir : aVal < bVal ? -dir : 0;\n });\n return sorted.map((row) => {\n const children = row[childrenField];\n return Array.isArray(children) && children.length > 0\n ? { ...row, [childrenField]: this.sortTree(children as TreeRow[], field, dir) }\n : row;\n });\n }\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (this.flattenedRows.length === 0) return [...columns];\n\n const cols = [...columns] as ColumnConfig[];\n if (cols.length === 0) return cols;\n\n // Wrap the first column's renderer to add tree indentation and expand icons\n // This is the correct approach for trees because:\n // 1. Indentation can grow naturally with depth\n // 2. Expand icons appear inline with content\n // 3. Works with column reordering (icons stay with first visible column)\n const firstCol = cols[0];\n const originalRenderer = firstCol.viewRenderer;\n const getConfig = () => this.config;\n const setIcon = this.setIcon.bind(this);\n const resolveIcon = this.resolveIcon.bind(this);\n\n const wrappedRenderer: ColumnViewRenderer = (ctx) => {\n const { row, value } = ctx;\n const { showExpandIcons = true, indentWidth } = getConfig();\n const treeRow = row as TreeRow;\n const depth = treeRow.__treeDepth ?? 0;\n\n const container = document.createElement('span');\n container.className = 'tree-cell-wrapper';\n container.style.setProperty('--tbw-tree-depth', String(depth));\n // Allow config-based indentWidth to override CSS default\n if (indentWidth !== undefined) {\n container.style.setProperty('--tbw-tree-indent-width', `${indentWidth}px`);\n }\n\n // Add expand/collapse icon or spacer\n if (showExpandIcons) {\n if (treeRow.__treeHasChildren) {\n const icon = document.createElement('span');\n icon.className = `tree-toggle${treeRow.__treeExpanded ? ' expanded' : ''}`;\n setIcon(icon, resolveIcon(treeRow.__treeExpanded ? 'collapse' : 'expand'));\n icon.setAttribute('data-tree-key', String(treeRow.__treeKey ?? ''));\n container.appendChild(icon);\n } else {\n const spacer = document.createElement('span');\n spacer.className = 'tree-spacer';\n container.appendChild(spacer);\n }\n }\n\n // Add the original content\n const content = document.createElement('span');\n content.className = 'tree-content';\n if (originalRenderer) {\n const result = originalRenderer(ctx);\n if (result instanceof Node) {\n content.appendChild(result);\n } else if (typeof result === 'string') {\n content.innerHTML = result;\n }\n } else {\n content.textContent = value != null ? String(value) : '';\n }\n container.appendChild(content);\n\n return container;\n };\n\n cols[0] = { ...firstCol, viewRenderer: wrappedRenderer };\n return cols;\n }\n\n // #endregion\n\n // #region Event Handlers\n\n /** @internal */\n override onCellClick(event: CellClickEvent): boolean {\n const target = event.originalEvent?.target as HTMLElement;\n if (!target?.classList.contains('tree-toggle')) return false;\n\n const key = target.getAttribute('data-tree-key');\n if (!key) return false;\n\n const flatRow = this.rowKeyMap.get(key);\n if (!flatRow) return false;\n\n this.expandedKeys = toggleExpand(this.expandedKeys, key);\n this.emit<TreeExpandDetail>('tree-expand', {\n key,\n row: flatRow.data,\n expanded: this.expandedKeys.has(key),\n depth: flatRow.depth,\n });\n this.requestRender();\n return true;\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion when on a row with children\n if (event.key !== ' ') return;\n\n const focusRow = this.grid._focusRow;\n const flatRow = this.flattenedRows[focusRow];\n if (!flatRow?.hasChildren) return;\n\n event.preventDefault();\n this.expandedKeys = toggleExpand(this.expandedKeys, flatRow.key);\n this.emit<TreeExpandDetail>('tree-expand', {\n key: flatRow.key,\n row: flatRow.data,\n expanded: this.expandedKeys.has(flatRow.key),\n depth: flatRow.depth,\n });\n this.requestRenderWithFocus();\n return true;\n }\n\n /** @internal */\n override onHeaderClick(event: HeaderClickEvent): boolean {\n if (this.flattenedRows.length === 0 || !event.column.sortable) return false;\n\n const { field } = event.column;\n if (!this.sortState || this.sortState.field !== field) {\n this.sortState = { field, direction: 1 };\n } else if (this.sortState.direction === 1) {\n this.sortState = { field, direction: -1 };\n } else {\n this.sortState = null;\n }\n\n // Sync grid sort indicator\n const gridEl = this.grid as unknown as GridWithSortState;\n if (gridEl._sortState !== undefined) {\n gridEl._sortState = this.sortState ? { ...this.sortState } : null;\n }\n\n this.emit('sort-change', { field, direction: this.sortState?.direction ?? 0 });\n this.requestRender();\n return true;\n }\n\n /** @internal */\n override afterRender(): void {\n const style = this.animationStyle;\n if (style === false || this.keysToAnimate.size === 0) return;\n\n const body = this.gridElement?.querySelector('.rows');\n if (!body) return;\n\n const animClass = style === 'fade' ? 'tbw-tree-fade-in' : 'tbw-tree-slide-in';\n for (const rowEl of body.querySelectorAll('.data-grid-row')) {\n const cell = rowEl.querySelector('.cell[data-row]');\n const idx = cell ? parseInt(cell.getAttribute('data-row') ?? '-1', 10) : -1;\n const key = this.flattenedRows[idx]?.key;\n\n if (key && this.keysToAnimate.has(key)) {\n rowEl.classList.add(animClass);\n rowEl.addEventListener('animationend', () => rowEl.classList.remove(animClass), { once: true });\n }\n }\n this.keysToAnimate.clear();\n }\n\n // #endregion\n\n // #region Public API\n\n expand(key: string): void {\n this.expandedKeys.add(key);\n this.requestRender();\n }\n\n collapse(key: string): void {\n this.expandedKeys.delete(key);\n this.requestRender();\n }\n\n toggle(key: string): void {\n this.expandedKeys = toggleExpand(this.expandedKeys, key);\n this.emitPluginEvent('tree-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n expandAll(): void {\n this.expandedKeys = expandAll(this.rows as TreeRow[], this.config);\n this.emitPluginEvent('tree-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n collapseAll(): void {\n this.expandedKeys = collapseAll();\n this.emitPluginEvent('tree-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n isExpanded(key: string): boolean {\n return this.expandedKeys.has(key);\n }\n\n getExpandedKeys(): string[] {\n return [...this.expandedKeys];\n }\n\n getFlattenedRows(): FlattenedTreeRow[] {\n return [...this.flattenedRows];\n }\n\n getRowByKey(key: string): TreeRow | undefined {\n return this.rowKeyMap.get(key)?.data;\n }\n\n expandToKey(key: string): void {\n this.expandedKeys = expandToKey(this.rows as TreeRow[], key, this.config, this.expandedKeys);\n this.requestRender();\n }\n\n // #endregion\n}\n","/**\n * Undo/Redo History Management\n *\n * Pure functions for managing the undo/redo stacks.\n * These functions are stateless and return new state objects.\n */\n\nimport type { EditAction, UndoRedoState } from './types';\n\n/**\n * Push a new action onto the undo stack.\n * Clears the redo stack since new actions invalidate redo history.\n *\n * @param state - Current undo/redo state\n * @param action - The action to add\n * @param maxSize - Maximum history size\n * @returns New state with the action added\n */\nexport function pushAction(state: UndoRedoState, action: EditAction, maxSize: number): UndoRedoState {\n const undoStack = [...state.undoStack, action];\n\n // Trim oldest actions if over max size\n while (undoStack.length > maxSize) {\n undoStack.shift();\n }\n\n return {\n undoStack,\n redoStack: [], // Clear redo on new action\n };\n}\n\n/**\n * Undo the most recent action.\n * Moves the action from undo stack to redo stack.\n *\n * @param state - Current undo/redo state\n * @returns New state and the action that was undone (or null if nothing to undo)\n */\nexport function undo(state: UndoRedoState): {\n newState: UndoRedoState;\n action: EditAction | null;\n} {\n if (state.undoStack.length === 0) {\n return { newState: state, action: null };\n }\n\n const undoStack = [...state.undoStack];\n const action = undoStack.pop();\n\n // This should never happen due to the length check above,\n // but TypeScript needs the explicit check\n if (!action) {\n return { newState: state, action: null };\n }\n\n return {\n newState: {\n undoStack,\n redoStack: [...state.redoStack, action],\n },\n action,\n };\n}\n\n/**\n * Redo the most recently undone action.\n * Moves the action from redo stack back to undo stack.\n *\n * @param state - Current undo/redo state\n * @returns New state and the action that was redone (or null if nothing to redo)\n */\nexport function redo(state: UndoRedoState): {\n newState: UndoRedoState;\n action: EditAction | null;\n} {\n if (state.redoStack.length === 0) {\n return { newState: state, action: null };\n }\n\n const redoStack = [...state.redoStack];\n const action = redoStack.pop();\n\n // This should never happen due to the length check above,\n // but TypeScript needs the explicit check\n if (!action) {\n return { newState: state, action: null };\n }\n\n return {\n newState: {\n undoStack: [...state.undoStack, action],\n redoStack,\n },\n action,\n };\n}\n\n/**\n * Check if there are any actions that can be undone.\n *\n * @param state - Current undo/redo state\n * @returns True if undo is available\n */\nexport function canUndo(state: UndoRedoState): boolean {\n return state.undoStack.length > 0;\n}\n\n/**\n * Check if there are any actions that can be redone.\n *\n * @param state - Current undo/redo state\n * @returns True if redo is available\n */\nexport function canRedo(state: UndoRedoState): boolean {\n return state.redoStack.length > 0;\n}\n\n/**\n * Clear all history, returning an empty state.\n *\n * @returns Fresh empty state\n */\nexport function clearHistory(): UndoRedoState {\n return { undoStack: [], redoStack: [] };\n}\n\n/**\n * Create a new edit action with the current timestamp.\n *\n * @param rowIndex - The row index where the edit occurred\n * @param field - The field (column key) that was edited\n * @param oldValue - The value before the edit\n * @param newValue - The value after the edit\n * @returns A new EditAction object\n */\nexport function createEditAction(rowIndex: number, field: string, oldValue: unknown, newValue: unknown): EditAction {\n return {\n type: 'cell-edit',\n rowIndex,\n field,\n oldValue,\n newValue,\n timestamp: Date.now(),\n };\n}\n","/**\n * Undo/Redo Plugin (Class-based)\n *\n * Provides undo/redo functionality for cell edits in tbw-grid.\n * Supports Ctrl+Z/Cmd+Z for undo and Ctrl+Y/Cmd+Y (or Ctrl+Shift+Z) for redo.\n */\n\nimport { BaseGridPlugin, type GridElement, type PluginDependency } from '../../core/plugin/base-plugin';\nimport { canRedo, canUndo, clearHistory, createEditAction, pushAction, redo, undo } from './history';\nimport type { EditAction, UndoRedoConfig, UndoRedoDetail } from './types';\n\n/**\n * Undo/Redo Plugin for tbw-grid\n *\n * Tracks all cell edits and lets users revert or replay changes with familiar keyboard\n * shortcuts (Ctrl+Z / Ctrl+Y). Maintains an in-memory history stack with configurable\n * depth—perfect for data entry workflows where mistakes happen.\n *\n * > **Required Dependency:** This plugin requires EditingPlugin to be loaded first.\n * > UndoRedo tracks the edit history that EditingPlugin creates.\n *\n * ## Installation\n *\n * ```ts\n * import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';\n * import { UndoRedoPlugin } from '@toolbox-web/grid/plugins/undo-redo';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `maxHistorySize` | `number` | `100` | Maximum actions in history stack |\n *\n * ## Keyboard Shortcuts\n *\n * | Shortcut | Action |\n * |----------|--------|\n * | `Ctrl+Z` / `Cmd+Z` | Undo last edit |\n * | `Ctrl+Y` / `Cmd+Shift+Z` | Redo last undone edit |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `undo` | `() => void` | Undo the last edit |\n * | `redo` | `() => void` | Redo the last undone edit |\n * | `canUndo` | `() => boolean` | Check if undo is available |\n * | `canRedo` | `() => boolean` | Check if redo is available |\n * | `clearHistory` | `() => void` | Clear the entire history stack |\n *\n * @example Basic Usage with EditingPlugin\n * ```ts\n * import '@toolbox-web/grid';\n * import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';\n * import { UndoRedoPlugin } from '@toolbox-web/grid/plugins/undo-redo';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name', editable: true },\n * { field: 'price', header: 'Price', type: 'number', editable: true },\n * ],\n * plugins: [\n * new EditingPlugin({ editOn: 'dblclick' }), // Required - must be first\n * new UndoRedoPlugin({ maxHistorySize: 50 }),\n * ],\n * };\n * ```\n *\n * @see {@link UndoRedoConfig} for configuration options\n * @see {@link EditingPlugin} for the required dependency\n *\n * @internal Extends BaseGridPlugin\n */\nexport class UndoRedoPlugin extends BaseGridPlugin<UndoRedoConfig> {\n /**\n * Plugin dependencies - UndoRedoPlugin requires EditingPlugin to track edits.\n *\n * The EditingPlugin must be loaded BEFORE this plugin in the plugins array.\n * @internal\n */\n static override readonly dependencies: PluginDependency[] = [\n { name: 'editing', required: true, reason: 'UndoRedoPlugin tracks cell edit history' },\n ];\n\n /** @internal */\n readonly name = 'undoRedo';\n\n /** @internal */\n protected override get defaultConfig(): Partial<UndoRedoConfig> {\n return {\n maxHistorySize: 100,\n };\n }\n\n // State as class properties\n private undoStack: EditAction[] = [];\n private redoStack: EditAction[] = [];\n\n /**\n * Subscribe to cell-edit-committed events from EditingPlugin.\n * @internal\n */\n override attach(grid: GridElement): void {\n super.attach(grid);\n // Auto-record edits via Event Bus\n this.on(\n 'cell-edit-committed',\n (detail: { rowIndex: number; field: string; oldValue: unknown; newValue: unknown }) => {\n this.recordEdit(detail.rowIndex, detail.field, detail.oldValue, detail.newValue);\n },\n );\n }\n\n /**\n * Clean up state when plugin is detached.\n * @internal\n */\n override detach(): void {\n this.undoStack = [];\n this.redoStack = [];\n }\n\n /**\n * Handle keyboard shortcuts for undo/redo.\n * - Ctrl+Z / Cmd+Z: Undo\n * - Ctrl+Y / Cmd+Y / Ctrl+Shift+Z / Cmd+Shift+Z: Redo\n * @internal\n */\n override onKeyDown(event: KeyboardEvent): boolean {\n const isUndo = (event.ctrlKey || event.metaKey) && event.key === 'z' && !event.shiftKey;\n const isRedo = (event.ctrlKey || event.metaKey) && (event.key === 'y' || (event.key === 'z' && event.shiftKey));\n\n if (isUndo) {\n const result = undo({ undoStack: this.undoStack, redoStack: this.redoStack });\n if (result.action) {\n // Apply undo - restore old value\n const rows = this.rows as Record<string, unknown>[];\n if (rows[result.action.rowIndex]) {\n rows[result.action.rowIndex][result.action.field] = result.action.oldValue;\n }\n\n // Update state from result\n this.undoStack = result.newState.undoStack;\n this.redoStack = result.newState.redoStack;\n\n this.emit<UndoRedoDetail>('undo', {\n action: result.action,\n type: 'undo',\n });\n\n this.requestRender();\n }\n return true;\n }\n\n if (isRedo) {\n const result = redo({ undoStack: this.undoStack, redoStack: this.redoStack });\n if (result.action) {\n // Apply redo - restore new value\n const rows = this.rows as Record<string, unknown>[];\n if (rows[result.action.rowIndex]) {\n rows[result.action.rowIndex][result.action.field] = result.action.newValue;\n }\n\n // Update state from result\n this.undoStack = result.newState.undoStack;\n this.redoStack = result.newState.redoStack;\n\n this.emit<UndoRedoDetail>('redo', {\n action: result.action,\n type: 'redo',\n });\n\n this.requestRender();\n }\n return true;\n }\n\n return false;\n }\n\n // #region Public API Methods\n\n /**\n * Record a cell edit for undo/redo tracking.\n * Call this when a cell value changes.\n *\n * @param rowIndex - The row index where the edit occurred\n * @param field - The field (column key) that was edited\n * @param oldValue - The value before the edit\n * @param newValue - The value after the edit\n */\n recordEdit(rowIndex: number, field: string, oldValue: unknown, newValue: unknown): void {\n const action = createEditAction(rowIndex, field, oldValue, newValue);\n const newState = pushAction(\n { undoStack: this.undoStack, redoStack: this.redoStack },\n action,\n this.config.maxHistorySize ?? 100,\n );\n this.undoStack = newState.undoStack;\n this.redoStack = newState.redoStack;\n }\n\n /**\n * Programmatically undo the last action.\n *\n * @returns The undone action, or null if nothing to undo\n */\n undo(): EditAction | null {\n const result = undo({ undoStack: this.undoStack, redoStack: this.redoStack });\n if (result.action) {\n const rows = this.rows as Record<string, unknown>[];\n if (rows[result.action.rowIndex]) {\n rows[result.action.rowIndex][result.action.field] = result.action.oldValue;\n }\n this.undoStack = result.newState.undoStack;\n this.redoStack = result.newState.redoStack;\n this.requestRender();\n }\n return result.action;\n }\n\n /**\n * Programmatically redo the last undone action.\n *\n * @returns The redone action, or null if nothing to redo\n */\n redo(): EditAction | null {\n const result = redo({ undoStack: this.undoStack, redoStack: this.redoStack });\n if (result.action) {\n const rows = this.rows as Record<string, unknown>[];\n if (rows[result.action.rowIndex]) {\n rows[result.action.rowIndex][result.action.field] = result.action.newValue;\n }\n this.undoStack = result.newState.undoStack;\n this.redoStack = result.newState.redoStack;\n this.requestRender();\n }\n return result.action;\n }\n\n /**\n * Check if there are any actions that can be undone.\n */\n canUndo(): boolean {\n return canUndo({ undoStack: this.undoStack, redoStack: this.redoStack });\n }\n\n /**\n * Check if there are any actions that can be redone.\n */\n canRedo(): boolean {\n return canRedo({ undoStack: this.undoStack, redoStack: this.redoStack });\n }\n\n /**\n * Clear all undo/redo history.\n */\n clearHistory(): void {\n const newState = clearHistory();\n this.undoStack = newState.undoStack;\n this.redoStack = newState.redoStack;\n }\n\n /**\n * Get a copy of the current undo stack.\n */\n getUndoStack(): EditAction[] {\n return [...this.undoStack];\n }\n\n /**\n * Get a copy of the current redo stack.\n */\n getRedoStack(): EditAction[] {\n return [...this.redoStack];\n }\n // #endregion\n}\n","/**\n * Column Visibility Plugin (Class-based)\n *\n * Provides a UI for column visibility control via the shell's tool panel system.\n * Column visibility is a core grid feature - this plugin provides:\n * - A tool panel for column visibility management (registered with the shell)\n * - Backward-compatible API methods that delegate to grid.setColumnVisible(), etc.\n *\n * The grid emits 'column-visibility' events when columns are shown/hidden,\n * allowing consumers to save user preferences.\n *\n * When a reorder plugin is present, column rows become draggable for reordering.\n * Drag-drop emits 'column-reorder-request' events that the ReorderPlugin can listen for.\n */\n\nimport {\n BaseGridPlugin,\n type PluginDependency,\n type PluginManifest,\n type PluginQuery,\n} from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, ToolPanelDefinition } from '../../core/types';\nimport type { ContextMenuParams, HeaderContextMenuItem } from '../context-menu/types';\nimport type { ColumnGroupInfo, VisibilityConfig } from './types';\nimport styles from './visibility.css?inline';\n\n/**\n * Detail for column-reorder-request events emitted when users drag-drop in the visibility panel.\n */\nexport interface ColumnReorderRequestDetail {\n /** The field name of the column to move */\n field: string;\n /** The source index (before move) */\n fromIndex: number;\n /** The target index (after move) */\n toIndex: number;\n}\n\n/**\n * Check if a column can be moved (respects lockPosition/suppressMovable).\n * Inlined to avoid importing from reorder plugin.\n */\nfunction canMoveColumn(column: ColumnConfig): boolean {\n const meta = column.meta ?? {};\n return meta.lockPosition !== true && meta.suppressMovable !== true;\n}\n\n/**\n * Column Visibility Plugin for tbw-grid\n *\n * Gives users control over which columns are displayed. Hide less important columns\n * by default, let users toggle them via a column chooser UI, or programmatically\n * show/hide columns based on user preferences or screen size.\n *\n * > **Optional Enhancement:** When ReorderPlugin is also loaded, columns in the\n * > visibility panel become draggable for reordering.\n *\n * ## Installation\n *\n * ```ts\n * import { VisibilityPlugin } from '@toolbox-web/grid/plugins/visibility';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `allowHideAll` | `boolean` | `false` | Allow hiding all columns (no minimum) |\n *\n * ## Column Configuration\n *\n * | Property | Type | Default | Description |\n * |----------|------|---------|-------------|\n * | `visible` | `boolean` | `true` | Initial visibility state |\n * | `meta.lockVisibility` | `boolean` | `false` | Prevent user from toggling |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `hideColumn` | `(field: string) => void` | Hide a column |\n * | `showColumn` | `(field: string) => void` | Show a column |\n * | `toggleColumn` | `(field: string) => void` | Toggle visibility |\n * | `showAllColumns` | `() => void` | Show all columns |\n * | `getHiddenColumns` | `() => string[]` | Get list of hidden column fields |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-visibility-hover` | `var(--tbw-color-row-hover)` | Row hover background |\n * | `--tbw-panel-padding` | `0.75em` | Panel content padding |\n * | `--tbw-panel-gap` | `0.5em` | Gap between items |\n *\n * @example Columns Hidden by Default\n * ```ts\n * import '@toolbox-web/grid';\n * import { VisibilityPlugin } from '@toolbox-web/grid/plugins/visibility';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID' },\n * { field: 'name', header: 'Name' },\n * { field: 'phone', header: 'Phone', visible: false }, // Hidden by default\n * { field: 'address', header: 'Address', visible: false },\n * ],\n * plugins: [new VisibilityPlugin()],\n * };\n *\n * // Toggle programmatically\n * const plugin = grid.getPlugin(VisibilityPlugin);\n * plugin.showColumn('phone');\n * ```\n *\n * @example With Drag-to-Reorder\n * ```ts\n * import { VisibilityPlugin } from '@toolbox-web/grid/plugins/visibility';\n * import { ReorderPlugin } from '@toolbox-web/grid/plugins/reorder';\n *\n * grid.gridConfig = {\n * plugins: [\n * new ReorderPlugin(), // Enables drag-drop in visibility panel\n * new VisibilityPlugin(),\n * ],\n * };\n * ```\n *\n * @see {@link VisibilityConfig} for configuration options\n * @see {@link ReorderPlugin} for drag-to-reorder integration\n *\n * @internal Extends BaseGridPlugin\n */\nexport class VisibilityPlugin extends BaseGridPlugin<VisibilityConfig> {\n /**\n * Plugin dependencies - VisibilityPlugin optionally uses ReorderPlugin for drag-drop reordering.\n *\n * When ReorderPlugin is present, columns in the visibility panel become draggable.\n * @internal\n */\n static override readonly dependencies: PluginDependency[] = [\n { name: 'reorder', required: false, reason: 'Enables drag-to-reorder columns in visibility panel' },\n ];\n\n /**\n * Plugin manifest - declares handled queries.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n queries: [\n {\n type: 'getContextMenuItems',\n description: 'Contributes \"Hide column\" item to the header context menu',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'visibility';\n\n /** Tool panel ID for shell integration */\n static readonly PANEL_ID = 'columns';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<VisibilityConfig> {\n return {\n allowHideAll: false,\n };\n }\n\n // #region Internal State\n private columnListElement: HTMLElement | null = null;\n\n // Drag state for reorder integration\n private isDragging = false;\n private draggedField: string | null = null;\n private draggedIndex: number | null = null;\n private dropIndex: number | null = null;\n /** When dragging a group, holds the group ID; null for individual column drags. */\n private draggedGroupId: string | null = null;\n /** Fields belonging to the group currently being dragged. */\n private draggedGroupFields: string[] = [];\n\n /** Clear drag-related classes from all rows and group headers in a list. */\n private clearDragClasses(container: HTMLElement): void {\n container.querySelectorAll('.tbw-visibility-row, .tbw-visibility-group-header').forEach((r) => {\n r.classList.remove('dragging', 'drop-target', 'drop-before', 'drop-after');\n });\n }\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Listen for column-move events (emitted by ReorderPlugin after any reorder,\n // including header drag-drop and visibility panel drag-drop) to keep the\n // panel list in sync with the grid's column order.\n (grid as unknown as HTMLElement).addEventListener(\n 'column-move',\n () => {\n if (this.columnListElement) {\n // column-move fires BEFORE setColumnOrder runs. Defer the rebuild\n // to allow the full reorder cycle (setColumnOrder + renderHeader +\n // refreshVirtualWindow) to complete before reading the new order.\n // Use RAF to run after the current synchronous work and any\n // animation frames queued by the animation system.\n requestAnimationFrame(() => {\n if (this.columnListElement) {\n this.rebuildToggles(this.columnListElement);\n }\n });\n }\n },\n { signal: this.disconnectSignal },\n );\n }\n\n /** @internal */\n override detach(): void {\n this.columnListElement = null;\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n }\n // #endregion\n\n // #region Query Handlers\n\n /**\n * Handle inter-plugin queries.\n * Contributes a \"Hide column\" item to the header context menu.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'getContextMenuItems') {\n const params = query.context as ContextMenuParams;\n if (!params.isHeader) return undefined;\n\n const column = params.column as ColumnConfig;\n if (!column?.field) return undefined;\n\n // Don't offer \"Hide\" for locked-visibility columns\n if (column.meta?.lockVisibility) return undefined;\n\n const items: HeaderContextMenuItem[] = [\n {\n id: 'visibility/hide-column',\n label: 'Hide Column',\n icon: '👁',\n order: 30,\n action: () => this.hideColumn(column.field),\n },\n ];\n\n return items;\n }\n return undefined;\n }\n // #endregion\n\n // #region Shell Integration\n\n /**\n * Register the column visibility tool panel with the shell.\n * @internal\n */\n override getToolPanel(): ToolPanelDefinition | undefined {\n return {\n id: VisibilityPlugin.PANEL_ID,\n title: 'Columns',\n icon: '☰',\n tooltip: 'Column visibility',\n order: 100, // High order so it appears last\n render: (container) => this.renderPanelContent(container),\n };\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Show the visibility sidebar panel.\n * Opens the tool panel and ensures this section is expanded.\n */\n show(): void {\n this.grid.openToolPanel();\n // Ensure our section is expanded\n if (!this.grid.expandedToolPanelSections.includes(VisibilityPlugin.PANEL_ID)) {\n this.grid.toggleToolPanelSection(VisibilityPlugin.PANEL_ID);\n }\n }\n\n /**\n * Hide the visibility sidebar panel.\n */\n hide(): void {\n this.grid.closeToolPanel();\n }\n\n /**\n * Toggle the visibility sidebar panel section.\n */\n toggle(): void {\n // If tool panel is closed, open it first\n if (!this.grid.isToolPanelOpen) {\n this.grid.openToolPanel();\n }\n this.grid.toggleToolPanelSection(VisibilityPlugin.PANEL_ID);\n }\n\n /**\n * Check if a specific column is visible.\n * Delegates to grid.isColumnVisible().\n * @param field - The field name to check\n * @returns True if the column is visible\n */\n isColumnVisible(field: string): boolean {\n return this.grid.isColumnVisible(field);\n }\n\n /**\n * Set visibility for a specific column.\n * Delegates to grid.setColumnVisible().\n * @param field - The field name of the column\n * @param visible - Whether the column should be visible\n */\n setColumnVisible(field: string, visible: boolean): void {\n this.grid.setColumnVisible(field, visible);\n }\n\n /**\n * Get list of all visible column fields.\n * @returns Array of visible field names\n */\n getVisibleColumns(): string[] {\n return this.grid\n .getAllColumns()\n .filter((c) => c.visible)\n .map((c) => c.field);\n }\n\n /**\n * Get list of all hidden column fields.\n * @returns Array of hidden field names\n */\n getHiddenColumns(): string[] {\n return this.grid\n .getAllColumns()\n .filter((c) => !c.visible)\n .map((c) => c.field);\n }\n\n /**\n * Show all columns.\n * Delegates to grid.showAllColumns().\n */\n showAll(): void {\n this.grid.showAllColumns();\n }\n\n /**\n * Toggle visibility for a specific column.\n * Delegates to grid.toggleColumnVisibility().\n * @param field - The field name of the column\n */\n toggleColumn(field: string): void {\n this.grid.toggleColumnVisibility(field);\n }\n\n /**\n * Show a specific column.\n * Delegates to grid.setColumnVisible().\n * @param field - The field name of the column to show\n */\n showColumn(field: string): void {\n this.setColumnVisible(field, true);\n }\n\n /**\n * Hide a specific column.\n * Delegates to grid.setColumnVisible().\n * @param field - The field name of the column to hide\n */\n hideColumn(field: string): void {\n this.setColumnVisible(field, false);\n }\n\n /**\n * Get all columns with their visibility status.\n * Useful for building visibility UI.\n * @returns Array of column info with visibility status\n */\n getAllColumns(): Array<{\n field: string;\n header: string;\n visible: boolean;\n lockVisible?: boolean;\n utility?: boolean;\n }> {\n return this.grid.getAllColumns();\n }\n\n /**\n * Check if the sidebar panel is currently open.\n * @returns True if the panel section is expanded\n */\n isPanelVisible(): boolean {\n return this.grid.isToolPanelOpen && this.grid.expandedToolPanelSections.includes(VisibilityPlugin.PANEL_ID);\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Render the panel content into the shell's tool panel container.\n * Returns a cleanup function.\n */\n private renderPanelContent(container: HTMLElement): (() => void) | void {\n // Create content wrapper\n const wrapper = document.createElement('div');\n wrapper.className = 'tbw-visibility-content';\n\n // Column list container\n const columnList = document.createElement('div');\n columnList.className = 'tbw-visibility-list';\n wrapper.appendChild(columnList);\n\n // Show all button\n const showAllBtn = document.createElement('button');\n showAllBtn.className = 'tbw-visibility-show-all';\n showAllBtn.textContent = 'Show All';\n showAllBtn.addEventListener('click', () => {\n this.grid.showAllColumns();\n this.rebuildToggles(columnList);\n });\n wrapper.appendChild(showAllBtn);\n\n // Store reference\n this.columnListElement = columnList;\n\n // Build initial toggles\n this.rebuildToggles(columnList);\n\n // Append to container\n container.appendChild(wrapper);\n\n // Return cleanup function\n return () => {\n this.columnListElement = null;\n wrapper.remove();\n };\n }\n\n /**\n * Check if a reorder plugin is present (by name to avoid static import).\n */\n private hasReorderPlugin(): boolean {\n const plugin = this.grid?.getPluginByName?.('reorder');\n // Duck-type check - just verify the plugin exists and has a moveColumn method\n return !!(plugin && typeof (plugin as { moveColumn?: unknown }).moveColumn === 'function');\n }\n\n /**\n * Build the column toggle checkboxes.\n * When GroupingColumnsPlugin is present, renders columns under collapsible group headers.\n * When a reorder plugin is present, adds drag handles for reordering.\n */\n private rebuildToggles(columnList: HTMLElement): void {\n const reorderEnabled = this.hasReorderPlugin();\n\n columnList.innerHTML = '';\n\n // getAllColumns() returns columns in their effective display order\n // Filter out utility columns (e.g., expander column) as they're internal\n const allColumns = this.grid.getAllColumns().filter((c) => !c.utility);\n\n // Query for column grouping info from GroupingColumnsPlugin (or any responder)\n const groupResults = this.grid.query<ColumnGroupInfo[]>('getColumnGrouping');\n const groups: ColumnGroupInfo[] = groupResults?.flat().filter((g) => g && g.fields.length > 0) ?? [];\n\n if (groups.length === 0) {\n // No grouping — render flat list (original behavior)\n this.renderFlatColumnList(allColumns, reorderEnabled, columnList);\n return;\n }\n\n // Build field → group lookup\n const fieldToGroup = new Map<string, ColumnGroupInfo>();\n for (const group of groups) {\n for (const field of group.fields) fieldToGroup.set(field, group);\n }\n\n // Walk columns in display order, interleaving groups and ungrouped columns.\n // When we encounter the first column of a group, render the entire group section.\n const renderedGroups = new Set<string>();\n\n for (const col of allColumns) {\n const group = fieldToGroup.get(col.field);\n\n if (group) {\n // Column belongs to a group — render entire group section at first encounter\n if (!renderedGroups.has(group.id)) {\n renderedGroups.add(group.id);\n // Filter allColumns (which is in display order) to group members.\n // This preserves the current column order after reordering,\n // rather than using group.fields which may be in static/original order.\n const groupFieldSet = new Set(group.fields);\n const groupCols = allColumns.filter((c) => groupFieldSet.has(c.field));\n if (groupCols.length > 0) {\n this.renderGroupSection(group, groupCols, reorderEnabled, columnList);\n }\n }\n // Subsequent columns of the same group are already rendered — skip\n } else {\n // Ungrouped column — render as individual row at its natural position\n const fullIndex = allColumns.indexOf(col);\n columnList.appendChild(this.createColumnRow(col, fullIndex, reorderEnabled, columnList));\n }\n }\n }\n\n /**\n * Render a group section with header checkbox and indented column rows.\n */\n private renderGroupSection(\n group: ColumnGroupInfo,\n columns: ReturnType<typeof this.grid.getAllColumns>,\n reorderEnabled: boolean,\n container: HTMLElement,\n ): void {\n // Group header row\n const header = document.createElement('div');\n header.className = 'tbw-visibility-group-header';\n header.setAttribute('data-group-id', group.id);\n\n // Make group header draggable when reorder is enabled\n if (reorderEnabled) {\n header.draggable = true;\n header.classList.add('reorderable');\n this.setupGroupDragListeners(header, group, container);\n }\n\n const headerLabel = document.createElement('label');\n headerLabel.className = 'tbw-visibility-label';\n\n const groupCheckbox = document.createElement('input');\n groupCheckbox.type = 'checkbox';\n\n // Calculate tri-state: all visible, all hidden, or mixed\n const visibleCount = columns.filter((c) => c.visible).length;\n const allLocked = columns.every((c) => c.lockVisible);\n if (visibleCount === columns.length) {\n groupCheckbox.checked = true;\n groupCheckbox.indeterminate = false;\n } else if (visibleCount === 0) {\n groupCheckbox.checked = false;\n groupCheckbox.indeterminate = false;\n } else {\n groupCheckbox.checked = false;\n groupCheckbox.indeterminate = true;\n }\n groupCheckbox.disabled = allLocked;\n\n // Toggle all columns in group\n groupCheckbox.addEventListener('change', () => {\n const newVisible = groupCheckbox.checked;\n for (const col of columns) {\n if (col.lockVisible) continue;\n this.grid.setColumnVisible(col.field, newVisible);\n }\n setTimeout(() => this.rebuildToggles(container), 0);\n });\n\n const headerText = document.createElement('span');\n headerText.textContent = group.label;\n\n headerLabel.appendChild(groupCheckbox);\n headerLabel.appendChild(headerText);\n header.appendChild(headerLabel);\n\n // Add drag handle icon for group if reorderable\n if (reorderEnabled) {\n const handle = document.createElement('span');\n handle.className = 'tbw-visibility-handle';\n this.setIcon(handle, this.resolveIcon('dragHandle'));\n handle.title = 'Drag to reorder group';\n // Insert handle before the label\n header.insertBefore(handle, headerLabel);\n }\n\n container.appendChild(header);\n\n // Render indented column rows\n const allColumnsFullList = this.grid.getAllColumns().filter((c) => !c.utility);\n for (const col of columns) {\n const fullIndex = allColumnsFullList.findIndex((c) => c.field === col.field);\n const row = this.createColumnRow(col, fullIndex, reorderEnabled, container);\n row.classList.add('tbw-visibility-row--grouped');\n container.appendChild(row);\n }\n }\n\n /**\n * Render a flat (ungrouped) list of column rows.\n */\n private renderFlatColumnList(\n columns: ReturnType<typeof this.grid.getAllColumns>,\n reorderEnabled: boolean,\n container: HTMLElement,\n ): void {\n const allColumnsFullList = this.grid.getAllColumns().filter((c) => !c.utility);\n for (const col of columns) {\n const fullIndex = allColumnsFullList.findIndex((c) => c.field === col.field);\n container.appendChild(this.createColumnRow(col, fullIndex, reorderEnabled, container));\n }\n }\n\n /**\n * Create a single column visibility row element.\n */\n private createColumnRow(\n col: ReturnType<typeof this.grid.getAllColumns>[number],\n index: number,\n reorderEnabled: boolean,\n columnList: HTMLElement,\n ): HTMLElement {\n const label = col.header || col.field;\n\n const row = document.createElement('div');\n row.className = col.lockVisible ? 'tbw-visibility-row locked' : 'tbw-visibility-row';\n row.setAttribute('data-field', col.field);\n row.setAttribute('data-index', String(index));\n\n // Add drag handle if reorder is enabled\n if (reorderEnabled && canMoveColumn(col as unknown as ColumnConfig)) {\n row.draggable = true;\n row.classList.add('reorderable');\n this.setupDragListeners(row, col.field, index, columnList);\n }\n\n const labelWrapper = document.createElement('label');\n labelWrapper.className = 'tbw-visibility-label';\n\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.checked = col.visible;\n checkbox.disabled = col.lockVisible ?? false;\n checkbox.addEventListener('change', () => {\n this.grid.toggleColumnVisibility(col.field);\n // Refresh after toggle (grid may re-render)\n setTimeout(() => this.rebuildToggles(columnList), 0);\n });\n\n const text = document.createElement('span');\n text.textContent = label;\n\n labelWrapper.appendChild(checkbox);\n labelWrapper.appendChild(text);\n\n // Add drag handle icon if reorderable\n if (reorderEnabled && canMoveColumn(col as unknown as ColumnConfig)) {\n const handle = document.createElement('span');\n handle.className = 'tbw-visibility-handle';\n this.setIcon(handle, this.resolveIcon('dragHandle'));\n handle.title = 'Drag to reorder';\n row.appendChild(handle);\n }\n\n row.appendChild(labelWrapper);\n return row;\n }\n\n /**\n * Set up drag-and-drop listeners for a group header row.\n * Dragging a group moves all its member columns as a block.\n */\n private setupGroupDragListeners(header: HTMLElement, group: ColumnGroupInfo, columnList: HTMLElement): void {\n header.addEventListener('dragstart', (e: DragEvent) => {\n this.isDragging = true;\n this.draggedGroupId = group.id;\n this.draggedGroupFields = [...group.fields];\n // Use first field as representative for dataTransfer\n this.draggedField = null;\n this.draggedIndex = null;\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', `group:${group.id}`);\n }\n\n // Mark entire group (header + children) as dragging\n header.classList.add('dragging');\n columnList.querySelectorAll(`.tbw-visibility-row--grouped`).forEach((row) => {\n const field = row.getAttribute('data-field');\n if (field && this.draggedGroupFields.includes(field)) {\n row.classList.add('dragging');\n }\n });\n });\n\n header.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedGroupId = null;\n this.draggedGroupFields = [];\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.clearDragClasses(columnList);\n });\n\n // Group headers are also drop targets for other groups\n header.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n // Can't drop onto self\n if (this.draggedGroupId === group.id) return;\n // Can't drop individual columns onto group headers\n if (!this.draggedGroupId) return;\n\n const rect = header.getBoundingClientRect();\n const midY = rect.top + rect.height / 2;\n const before = e.clientY < midY;\n\n this.clearDragClasses(columnList);\n header.classList.add('drop-target');\n header.classList.toggle('drop-before', before);\n header.classList.toggle('drop-after', !before);\n });\n\n header.addEventListener('dragleave', () => {\n header.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n header.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging || !this.draggedGroupId || this.draggedGroupId === group.id) return;\n\n const rect = header.getBoundingClientRect();\n const before = e.clientY < rect.top + rect.height / 2;\n\n this.executeGroupDrop(this.draggedGroupFields, group.fields, before, columnList);\n });\n }\n\n /**\n * Execute a group drop — move the dragged group's fields as a block\n * to the position relative to the target group or column.\n */\n private executeGroupDrop(\n draggedFields: string[],\n targetFields: string[],\n before: boolean,\n columnList: HTMLElement,\n ): void {\n const allColumns = this.grid.getAllColumns();\n const currentOrder = allColumns.map((c) => c.field);\n\n // Remove dragged fields from current order\n const remaining = currentOrder.filter((f) => !draggedFields.includes(f));\n\n // Find insertion point relative to target\n // Use the first field of target group if inserting before, last if after\n const anchorField = before ? targetFields[0] : targetFields[targetFields.length - 1];\n const insertAt = remaining.indexOf(anchorField);\n if (insertAt === -1) return;\n\n // Insert the dragged group block at the correct position\n const insertIndex = before ? insertAt : insertAt + 1;\n\n // Preserve the dragged fields' original relative order\n const draggedInOrder = currentOrder.filter((f) => draggedFields.includes(f));\n remaining.splice(insertIndex, 0, ...draggedInOrder);\n\n this.grid.setColumnOrder(remaining);\n // Panel rebuild handled by column-move listener — but since we're calling\n // setColumnOrder directly (not through ReorderPlugin.moveColumn), we need\n // to manually trigger a rebuild.\n requestAnimationFrame(() => {\n if (this.columnListElement) {\n this.rebuildToggles(this.columnListElement);\n }\n });\n }\n\n /**\n * Set up drag-and-drop event listeners for a row.\n * On drop, emits a 'column-reorder-request' event for other plugins to handle.\n */\n private setupDragListeners(row: HTMLElement, field: string, index: number, columnList: HTMLElement): void {\n row.addEventListener('dragstart', (e: DragEvent) => {\n this.isDragging = true;\n this.draggedField = field;\n this.draggedIndex = index;\n // Clear any stale group drag state\n this.draggedGroupId = null;\n this.draggedGroupFields = [];\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', field);\n }\n\n row.classList.add('dragging');\n });\n\n row.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.clearDragClasses(columnList);\n });\n\n row.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n\n // If dragging a group, only allow drop on ungrouped rows\n if (this.draggedGroupId) {\n if (row.classList.contains('tbw-visibility-row--grouped')) return;\n } else if (this.draggedField === field) {\n return;\n }\n\n const rect = row.getBoundingClientRect();\n const midY = rect.top + rect.height / 2;\n\n this.dropIndex = e.clientY < midY ? index : index + 1;\n\n // Clear other highlights\n this.clearDragClasses(columnList);\n // Re-mark dragged elements\n if (this.draggedGroupId) {\n columnList\n .querySelector(`.tbw-visibility-group-header[data-group-id=\"${this.draggedGroupId}\"]`)\n ?.classList.add('dragging');\n columnList.querySelectorAll('.tbw-visibility-row--grouped').forEach((r) => {\n const f = r.getAttribute('data-field');\n if (f && this.draggedGroupFields.includes(f)) r.classList.add('dragging');\n });\n } else if (this.draggedField) {\n columnList.querySelector(`.tbw-visibility-row[data-field=\"${this.draggedField}\"]`)?.classList.add('dragging');\n }\n\n row.classList.add('drop-target');\n row.classList.toggle('drop-before', e.clientY < midY);\n row.classList.toggle('drop-after', e.clientY >= midY);\n });\n\n row.addEventListener('dragleave', () => {\n row.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n row.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n\n if (!this.isDragging) return;\n\n // Group drop onto an ungrouped row\n if (this.draggedGroupId && this.draggedGroupFields.length > 0) {\n if (row.classList.contains('tbw-visibility-row--grouped')) return;\n const rect = row.getBoundingClientRect();\n const before = e.clientY < rect.top + rect.height / 2;\n this.executeGroupDrop(this.draggedGroupFields, [field], before, columnList);\n return;\n }\n\n // Individual column drop\n const draggedField = this.draggedField;\n const draggedIndex = this.draggedIndex;\n const dropIndex = this.dropIndex;\n\n if (draggedField === null || draggedIndex === null || dropIndex === null) {\n return;\n }\n\n // Calculate the effective target index (in the filtered non-utility list)\n const effectiveToIndex = dropIndex > draggedIndex ? dropIndex - 1 : dropIndex;\n\n if (effectiveToIndex !== draggedIndex) {\n // Convert from non-utility index to full column order index\n // by counting how many utility columns come before the target position\n const allColumns = this.grid.getAllColumns();\n const nonUtilityColumns = allColumns.filter((c) => !c.utility);\n\n // Find the target field at effectiveToIndex in non-utility list\n const targetField = nonUtilityColumns[effectiveToIndex]?.field;\n // Find its actual index in the full column order\n const fullOrderToIndex = targetField ? allColumns.findIndex((c) => c.field === targetField) : allColumns.length;\n\n // Emit a request event - other plugins (like ReorderPlugin) can listen and handle\n const detail: ColumnReorderRequestDetail = {\n field: draggedField,\n fromIndex: draggedIndex, // Not used by ReorderPlugin, just for info\n toIndex: fullOrderToIndex,\n };\n this.emit<ColumnReorderRequestDetail>('column-reorder-request', detail);\n // Panel rebuild is handled by the column-move listener in attach()\n }\n });\n }\n // #endregion\n}\n"],"names":["resolveColumns","columns","fields","onlyVisible","result","c","fieldSet","resolveRows","rows","indices","a","b","i","r","formatValueAsText","value","copyToClipboard","text","textarea","success","parseClipboardText","config","delimiter","newline","normalizedText","currentRow","currentCell","inQuotes","char","readFromClipboard","defaultPasteHandler","detail","grid","pastedRows","target","currentRows","allFields","col","editableMap","newRows","maxPasteRow","rowData","rowOffset","targetRowIndex","emptyRow","field","cellValue","colOffset","ClipboardPlugin","BaseGridPlugin","e","#handleNativePaste","event","#handleCopy","selection","#getSelection","focused","#getFocusedCellFromDOM","parsed","firstRange","targetRow","targetCol","bounds","maxCol","column","pasteWidth","#applyPasteHandler","pasteHandler","#resolveData","options","range","minCol","minRow","maxRow","row","#buildText","includeHeaders","processCell","lines","cells","cell","rowIndexStr","DEFAULT_COLUMN_WIDTH","parseColumnWidth","width","numeric","getColumnWidths","computeColumnOffsets","offsets","offset","computeTotalWidth","sum","getVisibleColumnRange","scrollLeft","viewportWidth","columnOffsets","columnWidths","overscan","columnCount","startCol","binarySearchFirstVisible","rightEdge","endCol","visibleColumns","low","high","mid","shouldVirtualize","threshold","autoEnable","ColumnVirtualizationPlugin","#cleanupStyles","gridEl","headerRow","rowsContainer","rowsBody","fullColumns","isVirtualized","viewport","leftPadding","bodyRows","columnIndex","buildMenuItems","items","params","item","collapseSeparators","isItemDisabled","createMenuElement","onAction","submenuArrow","DEFAULT_GRID_ICONS","menu","hasAnyIcon","separator","menuItem","disabled","icon","label","shortcut","arrow","subMenuItems","subMenu","positionMenu","x","y","menuRect","viewportHeight","left","top","QUERY_GET_CONTEXT_MENU_ITEMS","globalClickHandler","globalKeydownHandler","globalStyleSheet","globalHandlerRefCount","defaultItems","ContextMenuPlugin","rowIndex","currentSelection","menuElement","computed","styles","colorScheme","varName","existing","contextMenuStyles","responses","response","lastGroup","group","container","header","colIndex","selectedRows","pluginItems","converted","fullParams","resolveOptions","raw","createNumberEditor","ctx","input","commit","createBooleanEditor","createDateEditor","createSelectEditor","select","emptyOpt","opt","o","values","createTextEditor","inputVal","defaultEditorFor","resolveEditor","gridTypeDefaults","adapter","appDefault","isSafePropertyKey","key","incrementEditingCount","rowEl","count","clearEditingState","getInputValue","originalValue","noopUpdateRow","_changes","wireEditorInputs","editorHost","EditingPlugin","v","#isGridMode","#activeEditRow","#activeEditCol","#rowEditSnapshots","#changedRowIds","#editingCells","#editorValueCallbacks","#pendingFocusRestore","#pendingRowAnimation","#invalidCells","#gridModeInputFocused","#gridModeEditLocked","#singleCellEdit","signal","internalGrid","silent","#exitRowEdit","cb","FOCUSABLE_EDITOR_SELECTOR","related","activeEl","query","editOn","isDoubleClick","ensureCellVisible","forward","#handleTabNavigation","focusRow","focusCol","newValue","#commitCellValue","#focusCurrentCellEditor","cellEl","activateEvent","legacyEvent","typeDefaults","typeEditorParams","#restoreCellFocus","cellKey","rowStr","colStr","#injectEditor","context","cellElement","id","rowId","message","rowInvalids","#syncInvalidCellAttribute","entries","_","invalid","ids","#syncGridEditState","#beginCellEdit","#startRowEdit","targetCell","editor","editableCols","nextIdx","nextRow","revert","snapshot","current","val","k","changedThisSession","#hasRowChanged","changed","cancelled","callbackKey","oldValue","firstTime","updateRow","changes","invalidWasSet","setInvalid","skipFocus","editFinalized","cancel","colInternal","tplHolder","editorSpec","callbacks","newVal","onValueChange","#renderTemplateEditor","el","produced","placeholder","clone","compiledEditor","node","_m","g","evt","snapshotObj","currentObj","allKeys","rowIdx","colIdx","formatCsvValue","quote","str","buildCsv","bom","processed","downloadBlob","blob","fileName","url","link","downloadCsv","content","escapeXml","buildExcelXml","xml","type","displayValue","downloadExcel","finalName","ExportPlugin","format","selectionState","jsonData","obj","EXPANDER_COLUMN_FIELD","EXPANDER_COLUMN_WIDTH","isExpanderColumn","isUtilityColumn","findExpanderColumn","createExpanderColumnConfig","pluginName","toNumeric","n","matchesFilter","filter","caseSensitive","rawValue","stringValue","compareValue","filterValue","filterRows","filters","f","computeFilterCacheKey","getUniqueValues","FilteringPlugin","cssValue","fieldFiltered","hasAnyFilter","filterList","newCacheKey","inputSpot","inputUnchanged","hasFilter","filterBtn","wasActive","iconName","resizeHandle","fullFilter","panel","className","theme","style","filterPanelStyles","buttonEl","uniqueValues","excludedSet","currentSearchText","excluded","operator","valueTo","usedCustomRenderer","typeDefault","columnType","anchorEl","panelRect","anchorRect","rect","excludedValues","itemHeight","searchContainer","searchInput","actionsRow","selectAllLabel","selectAllCheckbox","selectAllText","updateSelectAllState","checkState","allChecked","noneChecked","newState","renderVisibleItems","valuesContainer","spacer","contentContainer","filteredValues","createItem","index","strValue","checkbox","totalItems","scrollTop","shouldBypassVirtualization","idx","window","computeVirtualWindow","renderValues","filterText","compareFilter","noMatch","debounceTimer","buttonRow","applyBtn","isChecked","original","clearBtn","filterParams","editorParams","toNumber","fallback","num","numericValues","dataMin","dataMax","min","max","step","currentFilter","currentMin","currentMax","rangeContainer","minGroup","minLabel","minInput","maxGroup","maxLabel","maxInput","sliderContainer","sliderTrack","sliderFill","minSlider","maxSlider","updateFill","minVal","maxVal","leftPercent","rightPercent","dateValues","d","formatDateForInput","date","parseFilterParam","minDate","maxDate","currentFrom","currentTo","isBlankFilter","fromGroup","fromLabel","fromInput","toGroup","toLabel","toInput","blankRow","blankCheckbox","blankLabel","toggleDateInputs","from","to","handleResult","filterModel","state","computeColumnGroups","explicitMap","groupsOrdered","pushImplicit","startIdx","cols","prev","run","runStart","applyGroupedHeaderCellClasses","headerRowEl","groups","fieldToGroup","headerCells","gid","last","buildGroupHeaderRow","groupRow","firstGroupCol","startIndex","isImplicit","hasColumnGroups","GroupingColumnsPlugin","#groupEndFields","#onColumnMove","columnOrder","#isGroupContiguous","#flashHeaderCell","#recomputeGroupEndFields","lastGroupEndField","#findLastGroupEndField","groupFields","j","headerCell","#getStableColumnGrouping","columnGroups","allCols","gId","gLabel","groupMap","displayOrder","orderIndex","processedColumns","groupInfo","lastCol","existingGroupRow","finalColumns","gi","groupId","buildGroupedRowModel","expanded","initialExpanded","groupOn","root","path","parent","rawVal","depthIdx","seg","composite","effectiveExpanded","flat","visit","isExpanded","toggleGroupExpansion","expandedKeys","newSet","expandAllGroups","keys","collapseAllGroups","resolveDefaultExpanded","allGroupKeys","getGroupKeys","getGroupRowCount","GroupingRowsPlugin","_index","initialBuild","grouped","currentVisibleKeys","_rowIndex","toggleExpand","handleToggle","body","animClass","btn","depth","aggregators","groupRows","aggregatorEntries","aggregatesContainer","aggRef","runAggregator","aggSpan","colHeader","gridTemplate","toggleRendered","firstColAgg","aggResult","isExpanding","newKeys","existingKey","existingGroup","fn","toggleDetailRow","expandedRows","newExpanded","expandDetailRow","collapseDetailRow","isDetailExpanded","createDetailElement","renderer","detailRow","detailCell","MasterDetailPlugin","detailEl","gridWithAdapter","adapterRenderer","animation","showExpandColumn","expandOnRowClick","collapseOnClickOutside","heightAttr","configUpdates","templateHTML","evaluated","evalTemplateString","sanitizeHTML","measured","measureOnce","#measureAndCacheDetailHeight","onComplete","cleanup","height","previousHeight","cachedHeight","expanderCol","renderCtx","toggle","#syncDetailRows","gridInternal","rowPool","vStart","vEnd","visibleStart","visibleEnd","visibleRowMap","poolLen","dataRows","firstCell","isStillExpanded","isRowVisible","existingDetail","shouldAnimate","totalHeight","beforeRowIndex","baseHeight","detailHeight","start","rowHeight","positionCache","minStart","expandedIndices","cumulativeExtraHeight","actualRowTop","actualDetailBottom","currentRenderer","applySorts","sorts","sort","comparator","defaultComparator","aVal","bVal","toggleSort","shiftKey","maxColumns","s","getSortIndex","sortModel","getSortDirection","MultiSortPlugin","showIndex","sortIndex","sortDir","indicator","insertBefore","badge","model","existingIndex","newEntry","getColumnPinned","resolveStickyPosition","position","direction","resolveInlinePosition","isResolvedLeft","pinned","isResolvedRight","getLeftStickyColumns","getRightStickyColumns","hasStickyColumns","applyStickyOffsets","host","getDirection","right","reorderColumnsForPinning","middle","clearStickyOffsets","QUERY_CAN_MOVE_COLUMN","PinnedColumnsPlugin","#originalColumnOrder","isPinned","currentColumns","currentIndex","updated","copy","remaining","originalIndex","insertIndex","focusedCell","stickyLeftCells","stickyRightCells","skipScroll","isAggregatorConfig","def","createInfoBarElement","pinnedRows","center","rowCount","filteredCount","selectedCount","panelEl","renderCustomPanel","createAggregationContainer","renderAggregationRows","globalFullWidth","rowConfig","renderFullWidthAggregationRow","renderPerColumnAggregationRow","labelValue","labelSpan","renderInlineAggregates","formatter","resolveAggregatedValue","aggDef","aggFn","getAggregator","staticVal","hasAggregators","hasCells","span","buildContext","filterState","PinnedRowsPlugin","aggregationRows","topRows","bottomRows","hasInfoContent","hasBottomInfoBar","needsFooter","newInfoBar","p","getPivotAggregator","getValueAggregator","validatePivotConfig","errors","createValueKey","columnValues","valueField","buildPivot","rowGroupFields","columnGroupFields","valueFields","columnKeys","getUniqueColumnKeys","pivotRows","buildHierarchicalPivotRows","totals","calculateTotals","grandTotal","columnFields","groupByField","parentKey","aggregateValues","total","calculateRowTotal","currentField","remainingFields","hasChildren","groupValue","rowKey","children","colKey","vf","nums","aggregator","aggregatedResult","valueKey","sumRows","flattenPivotRows","defaultExpanded","flatten","child","getAllGroupKeys","collectKeys","AGG_FUNCS","renderPivotPanel","isActive","controller","wrapper","createSection","createOptionsPanel","createFieldZone","createValuesZone","createAvailableFieldsZone","title","contentFactory","section","zoneType","zone","currentFields","createFieldChip","chip","fieldInfo","removeBtn","currentValues","createValueChip","labelWrapper","aggSelect","aggFunc","option","usedFields","availableFields","empty","createCheckbox","checked","onChange","renderPivotGroupRow","indent","renderPivotLeafRow","renderPivotGrandTotalRow","PivotPlugin","indentWidth","flatRows","pr","pivotColumns","rowGroupHeaders","valueHeader","pivotRow","iconKey","grandTotalRow","enabled","targetZone","fieldIndex","ISOLATION_STYLE_ID","createIsolationStylesheet","gridId","orientation","printGridIsolated","gridElement","isolationStyle","resolve","onAfterPrint","DEFAULT_CONFIG","PrintPlugin","#printing","#savedHiddenColumns","#savedVirtualization","#savedRows","#printHeader","#printFooter","#appliedScale","#internalGrid","originalRowCount","limitApplied","limitInfo","startTime","#hidePrintColumns","#addPrintHeader","#disableVirtualization","#printInIsolatedWindow","#triggerPrint","error","#cleanup","titleEl","timestampEl","totalRows","#restorePrintColumns","wasVisible","#toolbarRegistered","#registerToolbarButton","button","canMoveColumn","meta","moveColumn","fromIndex","toIndex","removed","ReorderPlugin","h","headerEl","midX","draggedField","draggedIndex","dropIndex","effectiveToIndex","currentOrder","newOrder","targetColumn","order","originalOrder","positions","oldPositions","deltas","oldLeft","deltaX","duration","applyChange","movedFields","newLeft","ResponsivePlugin","#resizeObserver","#isResponsive","#debounceTimer","#warnedAboutMissingBreakpoint","#currentWidth","#hiddenColumnSet","#valueOnlyColumnSet","#activeBreakpoint","#sortedBreakpoints","#applyResponsiveState","#checkBreakpoint","#parseLightDomCard","#buildHiddenColumnSets","cardEl","breakpointAttr","cardRowHeightAttr","hiddenColumnsAttr","hideHeaderAttr","debounceMsAttr","breakpoint","debounceMs","sanitized","hiddenColumns","#measureCardHeightFromDOM","hasHiddenColumns","hasValueOnlyColumns","#checkMultiBreakpoint","shouldBeResponsive","newActiveBreakpoint","bp","#originalRowHeight","animate","scrollArea","#measuredCardHeight","#measuredGroupRowHeight","#lastCardRowCount","cardContent","cardHeight","#getCardHeight","configHeight","#getGroupRowHeight","#hasGroupRows","#countRowTypes","groupCount","cardCount","groupHeight","groupExtra","cardExtra","groupHeightDiff","cardHeightDiff","groupsBefore","cardsBefore","maxIndex","#countCardRows","#pendingRefresh","needsRefresh","hasGroups","currentCardRowCount","cardRow","ROW_DRAG_HANDLE_FIELD","RowReorderPlugin","dragHandleColumn","handle","handleEl","targetIndex","midY","isBefore","movedRow","source","minIndex","rowsToAnimate","newRowIndex","oldIndex","oldTop","newTop","deltaY","normalizeRange","toPublicRange","normalized","toPublicRanges","ranges","isCellInRange","isCellInAnyRange","getCellsInRange","getAllCellsInRanges","cellMap","createRangeFromAnchor","anchor","rangesEqual","normA","normB","CHECKBOX_COLUMN_FIELD","buildSelectionEvent","mode","colCount","sorted","end","SelectionPlugin","isSelectable","originalEvent","triggerOn","isUtility","#buildEvent","ctrlKey","isCheckbox","newRange","currentRange","isNavKey","isTabKey","shouldExtend","firstDataCol","_event","checkboxCol","#createCheckboxColumn","expanderIdx","insertAt","#updateCheckboxStates","getRowIndexFromCell","headerCheckbox","selectableCount","allSelected","someSelected","#syncSelectionToFocus","cur","#applySelectionClasses","hasSelectableCallback","allRows","clearCellFocus","normalizedRanges","isInSelection","currentCol","allRange","getBlockNumber","blockSize","getBlockRange","blockNumber","getRequiredBlocks","startRow","endRow","startBlock","endBlock","blocks","loadBlock","dataSource","getRowFromCache","loadedBlocks","block","indexInBlock","SCROLL_DEBOUNCE_MS","ServerSidePlugin","gridRef","requiredBlocks","blockNum","cached","generateRowKey","expandAll","childrenField","childKeys","collapseAll","getPathToKey","targetKey","childPath","expandToKey","existingExpanded","detectTreeStructure","inferChildrenField","commonArrayFields","TreePlugin","treeRows","data","currentKeys","stableKey","dir","firstCol","originalRenderer","getConfig","setIcon","resolveIcon","wrappedRenderer","showExpandIcons","treeRow","flatRow","pushAction","action","maxSize","undoStack","undo","redo","redoStack","canUndo","canRedo","clearHistory","createEditAction","UndoRedoPlugin","isUndo","isRedo","VisibilityPlugin","visible","columnList","showAllBtn","plugin","reorderEnabled","allColumns","renderedGroups","groupFieldSet","groupCols","fullIndex","headerLabel","groupCheckbox","visibleCount","allLocked","newVisible","headerText","allColumnsFullList","before","draggedFields","targetFields","anchorField","draggedInOrder","targetField","fullOrderToIndex"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"all.js","sources":["../../../libs/grid/src/lib/plugins/shared/data-collection.ts","../../../libs/grid/src/lib/plugins/clipboard/copy.ts","../../../libs/grid/src/lib/plugins/clipboard/paste.ts","../../../libs/grid/src/lib/plugins/clipboard/types.ts","../../../libs/grid/src/lib/plugins/clipboard/ClipboardPlugin.ts","../../../libs/grid/src/lib/plugins/column-virtualization/column-virtualization.ts","../../../libs/grid/src/lib/plugins/column-virtualization/ColumnVirtualizationPlugin.ts","../../../libs/grid/src/lib/plugins/context-menu/menu.ts","../../../libs/grid/src/lib/plugins/context-menu/ContextMenuPlugin.ts","../../../libs/grid/src/lib/plugins/editing/editors.ts","../../../libs/grid/src/lib/plugins/editing/EditingPlugin.ts","../../../libs/grid/src/lib/plugins/export/csv.ts","../../../libs/grid/src/lib/plugins/export/excel.ts","../../../libs/grid/src/lib/plugins/export/ExportPlugin.ts","../../../libs/grid/src/lib/core/plugin/expander-column.ts","../../../libs/grid/src/lib/plugins/filtering/filter-model.ts","../../../libs/grid/src/lib/plugins/filtering/FilteringPlugin.ts","../../../libs/grid/src/lib/plugins/grouping-columns/grouping-columns.ts","../../../libs/grid/src/lib/plugins/grouping-columns/GroupingColumnsPlugin.ts","../../../libs/grid/src/lib/plugins/grouping-rows/grouping-rows.ts","../../../libs/grid/src/lib/plugins/grouping-rows/GroupingRowsPlugin.ts","../../../libs/grid/src/lib/plugins/master-detail/master-detail.ts","../../../libs/grid/src/lib/plugins/master-detail/MasterDetailPlugin.ts","../../../libs/grid/src/lib/plugins/multi-sort/multi-sort.ts","../../../libs/grid/src/lib/plugins/multi-sort/MultiSortPlugin.ts","../../../libs/grid/src/lib/plugins/pinned-columns/pinned-columns.ts","../../../libs/grid/src/lib/plugins/pinned-columns/PinnedColumnsPlugin.ts","../../../libs/grid/src/lib/plugins/pinned-rows/pinned-rows.ts","../../../libs/grid/src/lib/plugins/pinned-rows/PinnedRowsPlugin.ts","../../../libs/grid/src/lib/plugins/pivot/pivot-model.ts","../../../libs/grid/src/lib/plugins/pivot/pivot-engine.ts","../../../libs/grid/src/lib/plugins/pivot/pivot-panel.ts","../../../libs/grid/src/lib/plugins/pivot/pivot-rows.ts","../../../libs/grid/src/lib/plugins/pivot/PivotPlugin.ts","../../../libs/grid/src/lib/plugins/print/print-isolated.ts","../../../libs/grid/src/lib/plugins/print/PrintPlugin.ts","../../../libs/grid/src/lib/plugins/reorder/column-drag.ts","../../../libs/grid/src/lib/plugins/reorder/ReorderPlugin.ts","../../../libs/grid/src/lib/plugins/responsive/ResponsivePlugin.ts","../../../libs/grid/src/lib/plugins/row-reorder/RowReorderPlugin.ts","../../../libs/grid/src/lib/plugins/selection/range-selection.ts","../../../libs/grid/src/lib/plugins/selection/SelectionPlugin.ts","../../../libs/grid/src/lib/plugins/server-side/datasource.ts","../../../libs/grid/src/lib/plugins/server-side/ServerSidePlugin.ts","../../../libs/grid/src/lib/plugins/tree/tree-data.ts","../../../libs/grid/src/lib/plugins/tree/tree-detect.ts","../../../libs/grid/src/lib/plugins/tree/TreePlugin.ts","../../../libs/grid/src/lib/plugins/undo-redo/history.ts","../../../libs/grid/src/lib/plugins/undo-redo/UndoRedoPlugin.ts","../../../libs/grid/src/lib/plugins/visibility/VisibilityPlugin.ts"],"sourcesContent":["/**\n * Shared Data Collection Utilities\n *\n * Pure functions for resolving columns and formatting values, shared between\n * the Clipboard and Export plugins. Each plugin bundles its own copy of this\n * module (no chunk splitting) since plugin builds inline sibling imports.\n *\n * @internal\n */\n\nimport type { ColumnConfig } from '../../core/types';\n\n/**\n * Resolve which columns to include in a data export or copy operation.\n *\n * Filters out hidden columns, utility columns (`meta.utility`), and\n * internal columns (`__`-prefixed fields). Optionally restricts to an\n * explicit set of field names.\n *\n * @param columns - All column configurations\n * @param fields - If provided, only include columns whose field is in this list\n * @param onlyVisible - When `true` (default), exclude hidden and internal columns\n * @returns Filtered column array preserving original order\n */\nexport function resolveColumns(\n columns: readonly ColumnConfig[],\n fields?: string[],\n onlyVisible = true,\n): ColumnConfig[] {\n let result = columns as ColumnConfig[];\n\n if (onlyVisible) {\n result = result.filter((c) => !c.hidden && !c.field.startsWith('__') && c.meta?.utility !== true);\n }\n\n if (fields?.length) {\n const fieldSet = new Set(fields);\n result = result.filter((c) => fieldSet.has(c.field));\n }\n\n return result;\n}\n\n/**\n * Resolve which rows to include, optionally filtered to specific indices.\n *\n * @param rows - All row data\n * @param indices - If provided, only include rows at these indices (sorted ascending)\n * @returns Filtered row array\n */\nexport function resolveRows<T>(rows: readonly T[], indices?: number[]): T[] {\n if (!indices?.length) return rows as T[];\n\n return [...indices]\n .sort((a, b) => a - b)\n .map((i) => rows[i])\n .filter((r): r is T => r != null);\n}\n\n/**\n * Format a raw cell value as a text string.\n *\n * Provides the common null / Date / object → string conversion shared by\n * both clipboard and export output builders.\n *\n * @param value - The cell value to format\n * @returns A plain-text representation of the value\n */\nexport function formatValueAsText(value: unknown): string {\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'object') return JSON.stringify(value);\n return String(value);\n}\n","/**\n * Clipboard Copy Logic\n *\n * Pure functions for copying grid data to clipboard.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ClipboardConfig } from './types';\n\n/** Parameters for building clipboard text */\nexport interface CopyParams {\n /** All grid rows */\n rows: unknown[];\n /** Column configurations */\n columns: ColumnConfig[];\n /** Selected row indices */\n selectedIndices: Set<number> | number[];\n /** Clipboard configuration */\n config: ClipboardConfig;\n}\n\n/**\n * Format a cell value for clipboard output.\n *\n * Uses custom processCell if provided, otherwise applies default formatting:\n * - null/undefined → empty string\n * - Date → ISO string\n * - Object → JSON string\n * - Other → String conversion with optional quoting\n *\n * @param value - The cell value to format\n * @param field - The field name\n * @param row - The full row object\n * @param config - Clipboard configuration\n * @returns Formatted string value\n */\nexport function formatCellValue(value: unknown, field: string, row: unknown, config: ClipboardConfig): string {\n if (config.processCell) {\n return config.processCell(value, field, row);\n }\n\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'object') return JSON.stringify(value);\n\n const str = String(value);\n const delimiter = config.delimiter ?? '\\t';\n const newline = config.newline ?? '\\n';\n\n // Quote if contains delimiter, newline, or quotes (or if quoteStrings is enabled)\n if (config.quoteStrings || str.includes(delimiter) || str.includes(newline) || str.includes('\"')) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n\n return str;\n}\n\n/**\n * Build clipboard text from selected rows and columns.\n *\n * @param params - Copy parameters including rows, columns, selection, and config\n * @returns Tab-separated (or custom delimiter) text ready for clipboard\n */\nexport function buildClipboardText(params: CopyParams): string {\n const { rows, columns, selectedIndices, config } = params;\n const delimiter = config.delimiter ?? '\\t';\n const newline = config.newline ?? '\\n';\n\n // Filter to visible columns (not hidden, not internal __ prefixed)\n const visibleColumns = columns.filter((c) => !c.hidden && !c.field.startsWith('__'));\n\n const lines: string[] = [];\n\n // Add header row if configured\n if (config.includeHeaders) {\n const headerCells = visibleColumns.map((c) => {\n const header = c.header || c.field;\n // Quote headers if they contain special characters\n if (header.includes(delimiter) || header.includes(newline) || header.includes('\"')) {\n return `\"${header.replace(/\"/g, '\"\"')}\"`;\n }\n return header;\n });\n lines.push(headerCells.join(delimiter));\n }\n\n // Convert indices to sorted array\n const indices = selectedIndices instanceof Set ? [...selectedIndices] : selectedIndices;\n const sortedIndices = [...indices].sort((a, b) => a - b);\n\n // Build data rows\n for (const idx of sortedIndices) {\n const row = rows[idx];\n if (!row) continue;\n\n const cells = visibleColumns.map((col) =>\n formatCellValue((row as Record<string, unknown>)[col.field], col.field, row, config),\n );\n lines.push(cells.join(delimiter));\n }\n\n return lines.join(newline);\n}\n\n/**\n * Copy text to the system clipboard.\n *\n * Uses the modern Clipboard API when available, with fallback\n * to execCommand for older browsers.\n *\n * @param text - The text to copy\n * @returns Promise resolving to true if successful, false otherwise\n */\nexport async function copyToClipboard(text: string): Promise<boolean> {\n try {\n await navigator.clipboard.writeText(text);\n return true;\n } catch (err) {\n console.warn('[copyToClipboard] Clipboard API failed:', err);\n // Fallback for older browsers or when Clipboard API is not available\n const textarea = document.createElement('textarea');\n textarea.value = text;\n textarea.style.position = 'fixed';\n textarea.style.opacity = '0';\n textarea.style.pointerEvents = 'none';\n document.body.appendChild(textarea);\n textarea.select();\n const success = document.execCommand('copy');\n document.body.removeChild(textarea);\n return success;\n }\n}\n","/**\n * Clipboard Paste Logic\n *\n * Pure functions for reading and parsing clipboard data.\n */\n\nimport type { ClipboardConfig } from './types';\n\n/**\n * Parse clipboard text into a 2D array of cell values.\n *\n * Handles:\n * - Quoted values (with escaped double quotes \"\")\n * - Multi-line quoted values (newlines within quotes are preserved)\n * - Custom delimiters and newlines\n * - Empty lines (filtered out only if they contain no data)\n *\n * @param text - Raw clipboard text\n * @param config - Clipboard configuration\n * @returns 2D array where each sub-array is a row of cell values\n */\nexport function parseClipboardText(text: string, config: ClipboardConfig): string[][] {\n const delimiter = config.delimiter ?? '\\t';\n const newline = config.newline ?? '\\n';\n\n // Handle Windows CRLF line endings\n const normalizedText = text.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n\n // Parse the entire text handling quoted fields that may span multiple lines\n const rows: string[][] = [];\n let currentRow: string[] = [];\n let currentCell = '';\n let inQuotes = false;\n\n for (let i = 0; i < normalizedText.length; i++) {\n const char = normalizedText[i];\n\n if (char === '\"' && !inQuotes) {\n // Start of quoted field\n inQuotes = true;\n } else if (char === '\"' && inQuotes) {\n // Check for escaped quote (\"\")\n if (normalizedText[i + 1] === '\"') {\n currentCell += '\"';\n i++; // Skip the second quote\n } else {\n // End of quoted field\n inQuotes = false;\n }\n } else if (char === delimiter && !inQuotes) {\n // Field separator\n currentRow.push(currentCell);\n currentCell = '';\n } else if (char === newline && !inQuotes) {\n // Row separator (only if not inside quotes)\n currentRow.push(currentCell);\n currentCell = '';\n // Only add non-empty rows (at least one non-empty cell or multiple cells)\n if (currentRow.length > 1 || currentRow.some((c) => c.trim() !== '')) {\n rows.push(currentRow);\n }\n currentRow = [];\n } else {\n currentCell += char;\n }\n }\n\n // Handle the last cell and row\n currentRow.push(currentCell);\n if (currentRow.length > 1 || currentRow.some((c) => c.trim() !== '')) {\n rows.push(currentRow);\n }\n\n return rows;\n}\n\n/**\n * Read text from the system clipboard.\n *\n * Uses the modern Clipboard API. Returns empty string if\n * the API is not available or permission is denied.\n *\n * @returns Promise resolving to clipboard text or empty string\n */\nexport async function readFromClipboard(): Promise<string> {\n try {\n return await navigator.clipboard.readText();\n } catch {\n // Permission denied or API not available\n return '';\n }\n}\n","/**\n * Clipboard Plugin Types\n *\n * Type definitions for clipboard copy/paste functionality.\n */\n\nimport type { GridElement } from '../../core/plugin/base-plugin';\n\n/**\n * Custom paste handler function.\n *\n * @param detail - The parsed paste data with target and field info\n * @param grid - The grid element to update\n * @returns `false` to prevent the default paste behavior, or `void`/`true` to allow it\n *\n * @example\n * ```ts\n * // Custom handler that validates before pasting\n * new ClipboardPlugin({\n * pasteHandler: (detail, grid) => {\n * if (!detail.target) return false;\n * // Apply custom validation/transformation...\n * applyPasteData(detail, grid);\n * return false; // We handled it, skip default\n * }\n * })\n * ```\n */\nexport type PasteHandler = (detail: PasteDetail, grid: GridElement) => boolean | void;\n\n/**\n * Options for programmatic copy operations.\n *\n * Allows callers to control exactly which columns and rows are included\n * in a copy, independently of the current selection state.\n *\n * @example Copy specific columns from selected rows\n * ```ts\n * const clipboard = grid.getPlugin(ClipboardPlugin);\n * // User selected rows 0, 2, 4 via a dialog, chose columns to include\n * const text = await clipboard.copy({\n * rowIndices: [0, 2, 4],\n * columns: ['name', 'email'],\n * includeHeaders: true,\n * });\n * ```\n */\nexport interface CopyOptions {\n /** Specific column fields to include. If omitted, uses current selection or all visible columns. */\n columns?: string[];\n /** Specific row indices to copy. If omitted, uses current selection or all rows. */\n rowIndices?: number[];\n /** Include column headers in copied text. Defaults to the plugin config value. */\n includeHeaders?: boolean;\n /** Column delimiter override. Defaults to the plugin config value. */\n delimiter?: string;\n /** Row delimiter override. Defaults to the plugin config value. */\n newline?: string;\n /** Custom cell value processor for this operation. Overrides the plugin config's `processCell`. */\n processCell?: (value: unknown, field: string, row: unknown) => string;\n}\n\n/** Configuration options for the clipboard plugin */\nexport interface ClipboardConfig {\n /** Include column headers in copied text (default: false) */\n includeHeaders?: boolean;\n /** Column delimiter character (default: '\\t' for tab) */\n delimiter?: string;\n /** Row delimiter/newline character (default: '\\n') */\n newline?: string;\n /** Wrap string values with quotes (default: false) */\n quoteStrings?: boolean;\n /** Custom cell value processor for copy operations */\n processCell?: (value: unknown, field: string, row: unknown) => string;\n /**\n * Custom paste handler. By default, the plugin applies pasted data to `grid.rows`\n * starting at the target cell.\n *\n * - Set to a custom function to handle paste yourself\n * - Set to `null` to disable auto-paste (event still fires)\n * - Return `false` from handler to prevent default behavior\n *\n * @default defaultPasteHandler (auto-applies paste data)\n */\n pasteHandler?: PasteHandler | null;\n}\n\n/** Internal state managed by the clipboard plugin */\nexport interface ClipboardState {\n /** The last copied text (for reference/debugging) */\n lastCopied: string | null;\n}\n\n/** Event detail emitted after a successful copy operation */\nexport interface CopyDetail {\n /** The text that was copied to clipboard */\n text: string;\n /** Number of rows copied */\n rowCount: number;\n /** Number of columns copied */\n columnCount: number;\n}\n\n/** Target cell coordinates and bounds for paste operations */\nexport interface PasteTarget {\n /** Target row index (top-left of paste area) */\n row: number;\n /** Target column index (top-left of paste area) */\n col: number;\n /** Target column field name (for easy data mapping) */\n field: string;\n /**\n * Selection bounds that constrain the paste area.\n * If set, paste data will be clipped to fit within these bounds.\n * If null, paste expands freely from the target cell.\n */\n bounds: {\n /** End row index (inclusive) */\n endRow: number;\n /** End column index (inclusive) */\n endCol: number;\n } | null;\n}\n\n/** Event detail emitted after a paste operation */\nexport interface PasteDetail {\n /** Parsed rows from clipboard (2D array of cell values) */\n rows: string[][];\n /** Raw text that was pasted */\n text: string;\n /** The target cell where paste starts (top-left of paste area). Null if no cell is selected. */\n target: PasteTarget | null;\n /**\n * Column fields for each column in the paste range, starting from target.col.\n * Useful for mapping parsed cell values to data fields.\n * Length matches the width of the pasted data (or available columns, whichever is smaller).\n */\n fields: string[];\n}\n\n/**\n * Default paste handler that applies pasted data to grid.rows.\n *\n * This is the built-in handler used when no custom `pasteHandler` is configured.\n * It clones the rows array for immutability and applies values starting at the target cell.\n *\n * Behavior:\n * - Single cell selection: paste expands freely, adds new rows if needed\n * - Range/row selection: paste is clipped to fit within selection bounds\n * - Non-editable columns: values are skipped (column alignment preserved)\n *\n * @param detail - The parsed paste data from clipboard\n * @param grid - The grid element to update\n */\nexport function defaultPasteHandler(detail: PasteDetail, grid: GridElement): void {\n const { rows: pastedRows, target, fields } = detail;\n\n // No target = nothing to do\n if (!target) return;\n\n // Get current rows and columns from grid\n const currentRows = grid.rows as Record<string, unknown>[];\n const columns = grid.effectiveConfig.columns ?? [];\n const allFields = columns.map((col) => col.field);\n\n // Build a map of field -> editable for quick lookup\n const editableMap = new Map<string, boolean>();\n columns.forEach((col) => {\n editableMap.set(col.field, col.editable === true);\n });\n\n // Clone data for immutability\n const newRows = [...currentRows];\n\n // Calculate row bounds\n const maxPasteRow = target.bounds ? target.bounds.endRow : Infinity;\n\n // Apply pasted data starting at target cell\n pastedRows.forEach((rowData, rowOffset) => {\n const targetRowIndex = target.row + rowOffset;\n\n // Stop if we've exceeded the selection bounds\n if (targetRowIndex > maxPasteRow) return;\n\n // Only grow array if no bounds (single cell selection)\n if (!target.bounds) {\n while (targetRowIndex >= newRows.length) {\n const emptyRow: Record<string, unknown> = {};\n allFields.forEach((field) => (emptyRow[field] = ''));\n newRows.push(emptyRow);\n }\n } else if (targetRowIndex >= newRows.length) {\n // With bounds, don't paste beyond existing rows\n return;\n }\n\n // Clone the target row and apply values\n newRows[targetRowIndex] = { ...newRows[targetRowIndex] };\n rowData.forEach((cellValue, colOffset) => {\n // fields array is already constrained by bounds in ClipboardPlugin\n const field = fields[colOffset];\n if (field && editableMap.get(field)) {\n // Only paste into editable columns\n newRows[targetRowIndex][field] = cellValue;\n }\n });\n });\n\n // Update grid with new data\n grid.rows = newRows;\n}\n","/**\n * Clipboard Plugin (Class-based)\n *\n * Provides copy/paste functionality for tbw-grid.\n * Supports Ctrl+C/Cmd+C for copying and Ctrl+V/Cmd+V for pasting.\n *\n * **With Selection plugin:** Copies selected cells/rows/range\n * **Without Selection plugin:** Copies entire grid\n */\n\nimport { BaseGridPlugin, type GridElement, type PluginDependency } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport { formatValueAsText, resolveColumns, resolveRows } from '../shared/data-collection';\nimport { copyToClipboard } from './copy';\nimport { parseClipboardText, readFromClipboard } from './paste';\nimport {\n defaultPasteHandler,\n type ClipboardConfig,\n type CopyDetail,\n type CopyOptions,\n type PasteDetail,\n type PasteTarget,\n} from './types';\n\n/**\n * Clipboard Plugin for tbw-grid\n *\n * Brings familiar copy/cut/paste functionality with full keyboard shortcut support\n * (Ctrl+C, Ctrl+X, Ctrl+V). Handles single cells, multi-cell selections, and integrates\n * seamlessly with Excel and other spreadsheet applications via tab-delimited output.\n *\n * > **Optional Dependency:** Works best with SelectionPlugin for copying/pasting selected\n * > cells. Without SelectionPlugin, copies the entire grid and pastes at row 0, column 0.\n *\n * ## Installation\n *\n * ```ts\n * import { ClipboardPlugin } from '@toolbox-web/grid/plugins/clipboard';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `includeHeaders` | `boolean` | `false` | Include column headers in copied data |\n * | `delimiter` | `string` | `'\\t'` | Column delimiter (tab for Excel compatibility) |\n * | `newline` | `string` | `'\\n'` | Row delimiter |\n * | `quoteStrings` | `boolean` | `false` | Wrap string values in quotes |\n * | `processCell` | `(value, field, row) => string` | - | Custom cell value processor |\n * | `pasteHandler` | `PasteHandler \\| null` | `defaultPasteHandler` | Custom paste handler |\n *\n * ## Keyboard Shortcuts\n *\n * | Shortcut | Action |\n * |----------|--------|\n * | `Ctrl+C` / `Cmd+C` | Copy selected cells |\n * | `Ctrl+V` / `Cmd+V` | Paste into selected cells |\n * | `Ctrl+X` / `Cmd+X` | Cut selected cells |\n *\n * ## Paste Behavior by Selection Type\n *\n * | Selection Type | Paste Behavior |\n * |----------------|----------------|\n * | Single cell | Paste expands freely from that cell |\n * | Range selection | Paste is clipped to fit within the selected range |\n * | Row selection | Paste is clipped to the selected rows |\n * | No selection | Paste starts at row 0, column 0 |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `copy` | `(options?: CopyOptions) => Promise<string>` | Copy to clipboard with optional column/row control |\n * | `copyRows` | `(indices, options?) => Promise<string>` | Copy specific rows to clipboard |\n * | `paste` | `() => Promise<string[][] \\| null>` | Read and parse clipboard content |\n * | `getSelectionAsText` | `(options?: CopyOptions) => string` | Get clipboard text without writing to clipboard |\n * | `getLastCopied` | `() => { text, timestamp } \\| null` | Get info about last copy operation |\n *\n * @example Basic Usage with Excel Compatibility\n * ```ts\n * import '@toolbox-web/grid';\n * import { ClipboardPlugin } from '@toolbox-web/grid/plugins/clipboard';\n * import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';\n *\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * ],\n * plugins: [\n * new SelectionPlugin({ mode: 'range' }),\n * new ClipboardPlugin({\n * includeHeaders: true,\n * delimiter: '\\t', // Tab for Excel\n * }),\n * ],\n * };\n * ```\n *\n * @example Custom Paste Handler\n * ```ts\n * new ClipboardPlugin({\n * pasteHandler: (grid, target, data) => {\n * // Validate or transform data before applying\n * console.log('Pasting', data.length, 'rows');\n * return defaultPasteHandler(grid, target, data);\n * },\n * })\n * ```\n *\n * @see {@link ClipboardConfig} for all configuration options\n * @see {@link SelectionPlugin} for enhanced copy/paste with selection\n *\n * @internal Extends BaseGridPlugin\n */\nexport class ClipboardPlugin extends BaseGridPlugin<ClipboardConfig> {\n /**\n * Plugin dependencies - ClipboardPlugin works best with SelectionPlugin.\n *\n * Without SelectionPlugin: copies entire grid, pastes at row 0 col 0.\n * With SelectionPlugin: copies/pastes based on selection.\n */\n /** @internal */\n static override readonly dependencies: PluginDependency[] = [\n { name: 'selection', required: false, reason: 'Enables copy/paste of selected cells instead of entire grid' },\n ];\n\n /** @internal */\n readonly name = 'clipboard';\n\n /** @internal */\n protected override get defaultConfig(): Partial<ClipboardConfig> {\n return {\n includeHeaders: false,\n delimiter: '\\t',\n newline: '\\n',\n quoteStrings: false,\n };\n }\n\n // #region Internal State\n /** The last copied text (for reference/debugging) */\n private lastCopied: { text: string; timestamp: number } | null = null;\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n\n // Listen for native paste events to get clipboard data synchronously\n // This is more reliable than the async Clipboard API in iframe contexts\n const el = grid as unknown as HTMLElement;\n el.addEventListener('paste', (e: Event) => this.#handleNativePaste(e as ClipboardEvent), {\n signal: this.disconnectSignal,\n });\n }\n\n /** @internal */\n override detach(): void {\n this.lastCopied = null;\n }\n // #endregion\n\n // #region Event Handlers\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean {\n const isCopy = (event.ctrlKey || event.metaKey) && event.key === 'c';\n\n if (isCopy) {\n // Prevent the browser's default copy action so it doesn't overwrite\n // our clipboard write with whatever text is selected in the DOM.\n event.preventDefault();\n this.#handleCopy(event.target as HTMLElement);\n return true;\n }\n\n // For paste, we do NOT return true - let the native paste event fire\n // so we can access clipboardData synchronously in #handleNativePaste\n return false;\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Handle copy operation from keyboard shortcut.\n *\n * For keyboard-triggered copies, respects the current selection or\n * falls back to the focused cell from the DOM.\n */\n #handleCopy(target: HTMLElement): void {\n const selection = this.#getSelection();\n\n // Selection plugin exists but nothing selected → try focused cell from DOM\n if (selection && selection.ranges.length === 0) {\n const focused = this.#getFocusedCellFromDOM(target);\n if (!focused) return;\n const col = this.columns[focused.col];\n if (!col) return;\n this.copy({ rowIndices: [focused.row], columns: [col.field] });\n return;\n }\n\n // Delegate to the public copy() method (selection or full grid)\n this.copy();\n }\n\n /**\n * Handle native paste event (preferred method - works in iframes).\n * Uses synchronous clipboardData from the native paste event.\n *\n * Flow:\n * 1. Parse clipboard text\n * 2. Build target/fields info from selection\n * 3. Emit 'paste' event (for listeners)\n * 4. Call paste handler (if configured) to apply data to grid\n *\n * Selection behavior:\n * - Single cell: paste starts at cell, expands freely\n * - Range/row: paste is clipped to fit within selection bounds\n * - No selection: paste starts at row 0, col 0\n */\n #handleNativePaste(event: ClipboardEvent): void {\n const text = event.clipboardData?.getData('text/plain');\n if (!text) return;\n\n // Prevent default to avoid pasting into contenteditable elements\n event.preventDefault();\n\n const parsed = parseClipboardText(text, this.config);\n\n // Get target cell from selection via query\n const selection = this.#getSelection();\n const firstRange = selection?.ranges?.[0];\n\n // Determine target cell and bounds\n const targetRow = firstRange?.from.row ?? 0;\n const targetCol = firstRange?.from.col ?? 0;\n\n // Check if multi-cell selection (range with different start/end)\n const isMultiCell =\n firstRange &&\n (selection?.mode === 'range' || selection?.mode === 'row') &&\n (firstRange.from.row !== firstRange.to.row || firstRange.from.col !== firstRange.to.col);\n\n const bounds = isMultiCell ? { endRow: firstRange.to.row, endCol: firstRange.to.col } : null;\n const maxCol = bounds?.endCol ?? this.columns.length - 1;\n\n // Build target info\n const column = this.columns[targetCol];\n const target: PasteTarget | null = column ? { row: targetRow, col: targetCol, field: column.field, bounds } : null;\n\n // Build field list for paste width (constrained by bounds if set)\n const fields: string[] = [];\n const pasteWidth = parsed[0]?.length ?? 0;\n for (let i = 0; i < pasteWidth && targetCol + i <= maxCol; i++) {\n const col = this.columns[targetCol + i];\n if (col && !col.hidden) {\n fields.push(col.field);\n }\n }\n\n const detail: PasteDetail = { rows: parsed, text, target, fields };\n\n // Emit the event for any listeners\n this.emit<PasteDetail>('paste', detail);\n\n // Apply paste data using the configured handler (or default)\n this.#applyPasteHandler(detail);\n }\n\n /**\n * Apply the paste handler to update grid data.\n *\n * Uses the configured `pasteHandler`, or the default handler if not specified.\n * Set `pasteHandler: null` in config to disable auto-paste.\n */\n #applyPasteHandler(detail: PasteDetail): void {\n if (!this.grid) return;\n\n const { pasteHandler } = this.config;\n\n // pasteHandler: null means explicitly disabled\n if (pasteHandler === null) return;\n\n // Use custom handler or default\n const handler = pasteHandler ?? defaultPasteHandler;\n handler(detail, this.grid);\n }\n\n /**\n * Get the current selection via Query System.\n * Returns undefined if no selection plugin is loaded or nothing is selected.\n */\n #getSelection(): SelectionQueryResult | undefined {\n const responses = this.grid?.query<SelectionQueryResult>('getSelection');\n return responses?.[0];\n }\n\n /**\n * Resolve columns and rows to include based on options and/or current selection.\n *\n * Priority for columns:\n * 1. `options.columns` (explicit field list)\n * 2. Selection range column bounds (range/cell mode only)\n * 3. All visible non-utility columns\n *\n * Priority for rows:\n * 1. `options.rowIndices` (explicit indices)\n * 2. Selection range row bounds\n * 3. All rows\n */\n #resolveData(options?: CopyOptions): { columns: ColumnConfig[]; rows: Record<string, unknown>[] } {\n const selection = this.#getSelection();\n\n // --- Columns ---\n let columns: ColumnConfig[];\n if (options?.columns) {\n // Caller specified exact fields\n columns = resolveColumns(this.columns, options.columns);\n } else if (selection?.ranges.length && selection.mode !== 'row') {\n // Range/cell selection: restrict to selection column bounds\n const range = selection.ranges[selection.ranges.length - 1];\n const minCol = Math.min(range.from.col, range.to.col);\n const maxCol = Math.max(range.from.col, range.to.col);\n columns = resolveColumns(this.columns.slice(minCol, maxCol + 1));\n } else {\n // Row selection or no selection: all visible columns\n columns = resolveColumns(this.columns);\n }\n\n // --- Rows ---\n let rows: Record<string, unknown>[];\n if (options?.rowIndices) {\n // Caller specified exact row indices\n rows = resolveRows(this.rows as Record<string, unknown>[], options.rowIndices);\n } else if (selection?.ranges.length) {\n // Selection range: extract contiguous row range\n const range = selection.ranges[selection.ranges.length - 1];\n const minRow = Math.min(range.from.row, range.to.row);\n const maxRow = Math.max(range.from.row, range.to.row);\n rows = [];\n for (let r = minRow; r <= maxRow; r++) {\n const row = this.rows[r] as Record<string, unknown> | undefined;\n if (row) rows.push(row);\n }\n } else {\n // No selection: all rows\n rows = this.rows as Record<string, unknown>[];\n }\n\n return { columns, rows };\n }\n\n /**\n * Build delimited text from resolved columns and rows.\n */\n #buildText(columns: ColumnConfig[], rows: Record<string, unknown>[], options?: CopyOptions): string {\n const delimiter = options?.delimiter ?? this.config.delimiter ?? '\\t';\n const newline = options?.newline ?? this.config.newline ?? '\\n';\n const includeHeaders = options?.includeHeaders ?? this.config.includeHeaders ?? false;\n const processCell = options?.processCell ?? this.config.processCell;\n\n const lines: string[] = [];\n\n // Header row\n if (includeHeaders) {\n lines.push(columns.map((c) => c.header || c.field).join(delimiter));\n }\n\n // Data rows\n for (const row of rows) {\n const cells = columns.map((col) => {\n const value = row[col.field];\n if (processCell) return processCell(value, col.field, row);\n return formatValueAsText(value);\n });\n lines.push(cells.join(delimiter));\n }\n\n return lines.join(newline);\n }\n\n /**\n * Get focused cell coordinates from DOM.\n * Used as fallback when SelectionPlugin has no selection.\n */\n #getFocusedCellFromDOM(target: HTMLElement): { row: number; col: number } | null {\n const cell = target.closest('[data-field-cache]') as HTMLElement | null;\n if (!cell) return null;\n\n const field = cell.dataset.fieldCache;\n const rowIndexStr = cell.dataset.row;\n if (!field || !rowIndexStr) return null;\n\n const row = parseInt(rowIndexStr, 10);\n if (isNaN(row)) return null;\n\n const col = this.columns.findIndex((c) => c.field === field);\n if (col === -1) return null;\n\n return { row, col };\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Get the text representation of the current selection (or specified data)\n * without writing to the system clipboard.\n *\n * Useful for previewing what would be copied, or for feeding the text into\n * a custom UI (e.g., a \"copy with column picker\" dialog).\n *\n * @param options - Control which columns/rows to include\n * @returns Delimited text, or empty string if nothing to copy\n *\n * @example Get text for specific columns\n * ```ts\n * const clipboard = grid.getPlugin(ClipboardPlugin);\n * const text = clipboard.getSelectionAsText({\n * columns: ['name', 'email'],\n * includeHeaders: true,\n * });\n * console.log(text);\n * // \"Name\\tEmail\\nAlice\\talice@example.com\\n...\"\n * ```\n */\n getSelectionAsText(options?: CopyOptions): string {\n const { columns, rows } = this.#resolveData(options);\n if (columns.length === 0 || rows.length === 0) return '';\n return this.#buildText(columns, rows, options);\n }\n\n /**\n * Copy data to the system clipboard.\n *\n * Without options, copies the current selection (or entire grid if no selection).\n * With options, copies exactly the specified columns and/or rows — ideal for\n * \"copy with column picker\" workflows where the user selects rows first,\n * then chooses which columns to include via a dialog.\n *\n * @param options - Control which columns/rows to include\n * @returns The copied text\n *\n * @example Copy current selection (default)\n * ```ts\n * const clipboard = grid.getPlugin(ClipboardPlugin);\n * await clipboard.copy();\n * ```\n *\n * @example Copy specific columns from specific rows\n * ```ts\n * // User selected rows in the grid, then picked columns in a dialog\n * const selectedRowIndices = [0, 3, 7];\n * const chosenColumns = ['name', 'department', 'salary'];\n * await clipboard.copy({\n * rowIndices: selectedRowIndices,\n * columns: chosenColumns,\n * includeHeaders: true,\n * });\n * ```\n */\n async copy(options?: CopyOptions): Promise<string> {\n const { columns, rows } = this.#resolveData(options);\n if (columns.length === 0 || rows.length === 0) {\n return '';\n }\n\n const text = this.#buildText(columns, rows, options);\n await copyToClipboard(text);\n this.lastCopied = { text, timestamp: Date.now() };\n this.emit<CopyDetail>('copy', {\n text,\n rowCount: rows.length,\n columnCount: columns.length,\n });\n return text;\n }\n\n /**\n * Copy specific rows by index to clipboard.\n *\n * Convenience wrapper around {@link copy} for row-based workflows.\n * Supports non-contiguous row indices (e.g., `[0, 3, 7]`).\n *\n * @param indices - Array of row indices to copy\n * @param options - Additional copy options (columns, headers, etc.)\n * @returns The copied text\n *\n * @example\n * ```ts\n * const clipboard = grid.getPlugin(ClipboardPlugin);\n * // Copy only rows 0 and 5, including just name and email columns\n * await clipboard.copyRows([0, 5], { columns: ['name', 'email'] });\n * ```\n */\n async copyRows(indices: number[], options?: Omit<CopyOptions, 'rowIndices'>): Promise<string> {\n if (indices.length === 0) return '';\n return this.copy({ ...options, rowIndices: indices });\n }\n\n /**\n * Read and parse clipboard content.\n * @returns Parsed 2D array of cell values, or null if clipboard is empty\n */\n async paste(): Promise<string[][] | null> {\n const text = await readFromClipboard();\n if (!text) return null;\n return parseClipboardText(text, this.config);\n }\n\n /**\n * Get the last copied text and timestamp.\n * @returns The last copied info or null\n */\n getLastCopied(): { text: string; timestamp: number } | null {\n return this.lastCopied;\n }\n // #endregion\n}\n\n// #region Internal Types\n\n/**\n * Range representation for clipboard operations.\n */\ninterface CellRange {\n from: { row: number; col: number };\n to: { row: number; col: number };\n}\n\n/**\n * Selection result returned by the Query System.\n * Matches the SelectionResult type from SelectionPlugin.\n */\ninterface SelectionQueryResult {\n mode: 'cell' | 'row' | 'range';\n ranges: CellRange[];\n anchor: { row: number; col: number } | null;\n}\n// #endregion\n\n// Re-export types\nexport type { ClipboardConfig, CopyDetail, CopyOptions, PasteDetail } from './types';\n","/**\n * Column Virtualization Core Logic\n *\n * Pure functions for horizontal column virtualization operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ColumnVirtualizationViewport } from './types';\n\n/** Default column width when not specified */\nconst DEFAULT_COLUMN_WIDTH = 100;\n\n/**\n * Parse a column width value to pixels.\n * Handles number (px) and string formats.\n *\n * @param width - The width value from column config\n * @returns Width in pixels\n */\nexport function parseColumnWidth(width: string | number | undefined): number {\n if (width === undefined || width === null) {\n return DEFAULT_COLUMN_WIDTH;\n }\n\n if (typeof width === 'number') {\n return width;\n }\n\n // Handle string values - extract numeric part\n const numeric = parseFloat(width);\n if (!isNaN(numeric)) {\n return numeric;\n }\n\n return DEFAULT_COLUMN_WIDTH;\n}\n\n/**\n * Get array of column widths in pixels.\n *\n * @param columns - Column configurations\n * @returns Array of widths in pixels\n */\nexport function getColumnWidths(columns: readonly ColumnConfig[]): number[] {\n return columns.map((col) => parseColumnWidth(col.width));\n}\n\n/**\n * Compute cumulative left offsets for each column.\n *\n * @param columns - Column configurations\n * @returns Array of left offsets in pixels\n */\nexport function computeColumnOffsets(columns: readonly ColumnConfig[]): number[] {\n const offsets: number[] = [];\n let offset = 0;\n\n for (const col of columns) {\n offsets.push(offset);\n offset += parseColumnWidth(col.width);\n }\n\n return offsets;\n}\n\n/**\n * Compute total width of all columns.\n *\n * @param columns - Column configurations\n * @returns Total width in pixels\n */\nexport function computeTotalWidth(columns: readonly ColumnConfig[]): number {\n return columns.reduce((sum, col) => sum + parseColumnWidth(col.width), 0);\n}\n\n/**\n * Find the visible column range based on scroll position.\n * Uses binary search for efficient lookup in large column sets.\n *\n * @param scrollLeft - Current horizontal scroll position\n * @param viewportWidth - Width of the visible viewport\n * @param columnOffsets - Array of column left offsets\n * @param columnWidths - Array of column widths\n * @param overscan - Number of extra columns to render on each side\n * @returns Viewport information with visible column indices\n */\nexport function getVisibleColumnRange(\n scrollLeft: number,\n viewportWidth: number,\n columnOffsets: number[],\n columnWidths: number[],\n overscan: number\n): ColumnVirtualizationViewport {\n const columnCount = columnOffsets.length;\n\n if (columnCount === 0) {\n return { startCol: 0, endCol: 0, visibleColumns: [] };\n }\n\n // Binary search for first visible column\n let startCol = binarySearchFirstVisible(scrollLeft, columnOffsets, columnWidths);\n startCol = Math.max(0, startCol - overscan);\n\n // Find last visible column (without overscan first)\n const rightEdge = scrollLeft + viewportWidth;\n let endCol = startCol;\n\n for (let i = startCol; i < columnCount; i++) {\n if (columnOffsets[i] >= rightEdge) {\n endCol = i - 1;\n break;\n }\n endCol = i;\n }\n\n // Apply overscan to end (only once)\n endCol = Math.min(columnCount - 1, endCol + overscan);\n\n // Build array of visible column indices\n const visibleColumns: number[] = [];\n for (let i = startCol; i <= endCol; i++) {\n visibleColumns.push(i);\n }\n\n return { startCol, endCol, visibleColumns };\n}\n\n/**\n * Binary search to find the first column that is visible.\n *\n * @param scrollLeft - Current scroll position\n * @param columnOffsets - Array of column offsets\n * @param columnWidths - Array of column widths\n * @returns Index of first visible column\n */\nfunction binarySearchFirstVisible(scrollLeft: number, columnOffsets: number[], columnWidths: number[]): number {\n let low = 0;\n let high = columnOffsets.length - 1;\n\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n const colRight = columnOffsets[mid] + columnWidths[mid];\n\n if (colRight <= scrollLeft) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n\n return low;\n}\n\n/**\n * Determine if column virtualization should be active.\n *\n * @param columnCount - Number of columns\n * @param threshold - Column count threshold\n * @param autoEnable - Whether auto-enable is configured\n * @returns True if virtualization should be active\n */\nexport function shouldVirtualize(columnCount: number, threshold: number, autoEnable: boolean): boolean {\n if (!autoEnable) return false;\n return columnCount > threshold;\n}\n","/**\n * Column Virtualization Plugin (Class-based)\n *\n * Provides horizontal column virtualization for grids with many columns.\n * Significantly improves rendering performance when dealing with >30 columns.\n */\n\nimport { BaseGridPlugin, ScrollEvent } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n computeColumnOffsets,\n computeTotalWidth,\n getColumnWidths,\n getVisibleColumnRange,\n shouldVirtualize,\n} from './column-virtualization';\nimport type { ColumnVirtualizationConfig } from './types';\n\n/**\n * Column Virtualization Plugin for tbw-grid\n *\n * Provides horizontal column virtualization for grids with many columns (30+).\n * Only renders visible columns plus overscan, significantly improving rendering\n * performance for wide grids.\n *\n * ## Installation\n *\n * ```ts\n * import { ColumnVirtualizationPlugin } from '@toolbox-web/grid/plugins/column-virtualization';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `autoEnable` | `boolean` | `true` | Auto-enable when column count exceeds threshold |\n * | `threshold` | `number` | `30` | Column count threshold for auto-enable |\n * | `overscan` | `number` | `3` | Extra columns to render beyond visible |\n *\n * ## Requirements\n *\n * - Grid must use `fitMode: 'fixed'`\n * - Columns must have explicit widths\n * - Grid must have fixed height\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `isEnabled` | `() => boolean` | Check if virtualization is active |\n * | `setEnabled` | `(enabled: boolean) => void` | Enable/disable virtualization |\n * | `getVisibleRange` | `() => { start, end }` | Get visible column range |\n *\n * @example Wide Grid with Column Virtualization\n * ```ts\n * import '@toolbox-web/grid';\n * import { ColumnVirtualizationPlugin } from '@toolbox-web/grid/plugins/column-virtualization';\n *\n * grid.gridConfig = {\n * columns: generateManyColumns(100), // 100 columns\n * fitMode: 'fixed', // Required\n * plugins: [\n * new ColumnVirtualizationPlugin({\n * threshold: 30, // Enable when >30 columns\n * overscan: 3, // Render 3 extra columns each side\n * }),\n * ],\n * };\n * ```\n *\n * @see {@link ColumnVirtualizationConfig} for configuration options\n *\n * @internal Extends BaseGridPlugin\n */\nexport class ColumnVirtualizationPlugin extends BaseGridPlugin<ColumnVirtualizationConfig> {\n /** @internal */\n readonly name = 'columnVirtualization';\n\n /** @internal */\n protected override get defaultConfig(): Partial<ColumnVirtualizationConfig> {\n return {\n autoEnable: true,\n threshold: 30,\n overscan: 3,\n };\n }\n\n // #region Internal State\n private isVirtualized = false;\n private startCol = 0;\n private endCol = 0;\n private scrollLeft = 0;\n private totalWidth = 0;\n private columnWidths: number[] = [];\n private columnOffsets: number[] = [];\n /** Store the original full column set for virtualization calculations */\n private originalColumns: readonly ColumnConfig[] = [];\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Initialize state from current columns\n const columns = this.columns;\n this.columnWidths = getColumnWidths(columns);\n this.columnOffsets = computeColumnOffsets(columns);\n this.totalWidth = computeTotalWidth(columns);\n this.endCol = columns.length - 1;\n }\n\n /** @internal */\n override detach(): void {\n // Clean up inline styles set by this plugin\n this.#cleanupStyles();\n\n this.columnWidths = [];\n this.columnOffsets = [];\n this.originalColumns = [];\n this.isVirtualized = false;\n this.startCol = 0;\n this.endCol = 0;\n this.scrollLeft = 0;\n this.totalWidth = 0;\n }\n\n /**\n * Remove inline styles set by this plugin for proper cleanup.\n */\n #cleanupStyles(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n const headerRow = gridEl.querySelector('.header-row') as HTMLElement | null;\n if (headerRow) {\n headerRow.style.paddingLeft = '';\n headerRow.style.minWidth = '';\n }\n\n const bodyRows = gridEl.querySelectorAll('.data-grid-row');\n bodyRows.forEach((row) => {\n (row as HTMLElement).style.paddingLeft = '';\n });\n\n const rowsContainer = gridEl.querySelector('.rows-viewport .rows') as HTMLElement | null;\n if (rowsContainer) {\n rowsContainer.style.width = '';\n }\n\n const rowsBody = gridEl.querySelector('.rows-body') as HTMLElement | null;\n if (rowsBody) {\n rowsBody.style.minWidth = '';\n }\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n // Detect if this is a new column set or just a scroll-triggered re-render.\n // We consider it \"new\" if:\n // 1. We don't have any stored yet (first time)\n // 2. Incoming has AS MANY OR MORE columns than we stored (genuine new config)\n // When virtualization is active, our output is a SUBSET (fewer columns),\n // so a scroll-triggered re-render will have fewer columns than originalColumns.\n // When virtualization is off, our output has the same count as originalColumns,\n // but any config change (e.g., pinning a column) also has the same count.\n // Using >= ensures we pick up property-only changes (like adding `pinned`).\n const isNewColumnSet = this.originalColumns.length === 0 || columns.length >= this.originalColumns.length;\n\n if (isNewColumnSet) {\n // Store the full column set\n this.originalColumns = columns;\n this.columnWidths = getColumnWidths(columns);\n this.columnOffsets = computeColumnOffsets(columns);\n this.totalWidth = computeTotalWidth(columns);\n }\n\n // Use the original (full) column set for virtualization decisions\n const fullColumns = this.originalColumns;\n const isVirtualized = shouldVirtualize(\n fullColumns.length,\n this.config.threshold ?? 30,\n this.config.autoEnable ?? true,\n );\n\n this.isVirtualized = isVirtualized ?? false;\n\n if (!isVirtualized) {\n this.startCol = 0;\n this.endCol = fullColumns.length - 1;\n return [...fullColumns];\n }\n\n // Get viewport width from grid element\n const viewportWidth = (this.grid as unknown as HTMLElement).clientWidth || 800;\n const viewport = getVisibleColumnRange(\n this.scrollLeft,\n viewportWidth,\n this.columnOffsets,\n this.columnWidths,\n this.config.overscan ?? 3,\n );\n\n this.startCol = viewport.startCol;\n this.endCol = viewport.endCol;\n\n // Return only visible columns from the ORIGINAL full set\n return viewport.visibleColumns.map((i) => fullColumns[i]);\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.isVirtualized) return;\n\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Apply left padding to offset scrolled-out columns\n const leftPadding = this.columnOffsets[this.startCol] ?? 0;\n\n const headerRow = gridEl.querySelector('.header-row') as HTMLElement | null;\n const bodyRows = gridEl.querySelectorAll('.data-grid-row');\n\n if (headerRow) {\n headerRow.style.paddingLeft = `${leftPadding}px`;\n // Set min-width on header row to enable horizontal scrolling\n headerRow.style.minWidth = `${this.totalWidth}px`;\n }\n\n bodyRows.forEach((row) => {\n (row as HTMLElement).style.paddingLeft = `${leftPadding}px`;\n });\n\n // Set total width for horizontal scrolling on the rows container\n const rowsContainer = gridEl.querySelector('.rows-viewport .rows') as HTMLElement | null;\n if (rowsContainer) {\n rowsContainer.style.width = `${this.totalWidth}px`;\n }\n\n // Also set min-width on .rows-body to ensure the scroll container knows the total scrollable width\n const rowsBody = gridEl.querySelector('.rows-body') as HTMLElement | null;\n if (rowsBody) {\n rowsBody.style.minWidth = `${this.totalWidth}px`;\n }\n }\n\n /** @internal */\n override onScroll(event: ScrollEvent): void {\n if (!this.isVirtualized) return;\n\n // Check if horizontal scroll position changed significantly\n const scrollDelta = Math.abs(event.scrollLeft - this.scrollLeft);\n if (scrollDelta < 1) return;\n\n // Update scroll position\n this.scrollLeft = event.scrollLeft;\n\n // Recalculate visible columns and request re-render\n // Must use requestColumnsRender() to trigger COLUMNS phase (processColumns hook)\n // requestRender() only requests ROWS phase which doesn't reprocess columns\n this.requestColumnsRender();\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Check if column virtualization is currently active.\n */\n getIsVirtualized(): boolean {\n return this.isVirtualized;\n }\n\n /**\n * Get the current visible column range.\n */\n getVisibleColumnRange(): { start: number; end: number } {\n return { start: this.startCol, end: this.endCol };\n }\n\n /**\n * Scroll the grid to bring a specific column into view.\n * @param columnIndex - Index of the column to scroll to\n */\n scrollToColumn(columnIndex: number): void {\n const offset = this.columnOffsets[columnIndex] ?? 0;\n const gridEl = this.grid as unknown as HTMLElement;\n // Scroll the grid element itself (it's the scroll container)\n gridEl.scrollLeft = offset;\n }\n\n /**\n * Get the left offset for a specific column.\n * @param columnIndex - Index of the column\n */\n getColumnOffset(columnIndex: number): number {\n return this.columnOffsets[columnIndex] ?? 0;\n }\n\n /**\n * Get the total width of all columns.\n */\n getTotalWidth(): number {\n return this.totalWidth;\n }\n // #endregion\n}\n","/**\n * Context Menu Rendering Logic\n *\n * Pure functions for building and positioning context menus.\n */\n\nimport type { IconValue } from '../../core/types';\nimport { DEFAULT_GRID_ICONS } from '../../core/types';\nimport type { ContextMenuItem, ContextMenuParams } from './types';\n\n/**\n * Build the visible menu items by resolving dynamic items and filtering hidden ones.\n *\n * @param items - Menu items configuration (array or factory function)\n * @param params - Context menu parameters for evaluating dynamic properties\n * @returns Filtered array of visible menu items\n */\nexport function buildMenuItems(\n items: ContextMenuItem[] | ((params: ContextMenuParams) => ContextMenuItem[]),\n params: ContextMenuParams,\n): ContextMenuItem[] {\n const menuItems = typeof items === 'function' ? items(params) : items;\n\n return menuItems.filter((item) => {\n if (item.hidden === true) return false;\n if (typeof item.hidden === 'function' && item.hidden(params)) return false;\n return true;\n });\n}\n\n/**\n * Remove consecutive, leading, and trailing separators from a menu item list.\n *\n * @param items - Array of menu items\n * @returns Cleaned array with no redundant separators\n */\nexport function collapseSeparators(items: ContextMenuItem[]): ContextMenuItem[] {\n const result: ContextMenuItem[] = [];\n for (const item of items) {\n if (item.separator) {\n // Skip if the last item is already a separator (consecutive) or list is empty (leading)\n if (result.length === 0 || result[result.length - 1].separator) continue;\n }\n result.push(item);\n }\n // Remove trailing separator\n if (result.length > 0 && result[result.length - 1].separator) {\n result.pop();\n }\n return result;\n}\n\n/**\n * Check if a menu item is disabled.\n *\n * @param item - The menu item to check\n * @param params - Context menu parameters for evaluating dynamic disabled state\n * @returns True if the item is disabled\n */\nexport function isItemDisabled(item: ContextMenuItem, params: ContextMenuParams): boolean {\n if (item.disabled === true) return true;\n if (typeof item.disabled === 'function') return item.disabled(params);\n return false;\n}\n\n/**\n * Create the menu DOM element from a list of menu items.\n *\n * @param items - Array of menu items to render\n * @param params - Context menu parameters for evaluating dynamic properties\n * @param onAction - Callback when a menu item action is triggered\n * @param submenuArrow - Optional custom submenu arrow icon\n * @returns The created menu element\n */\nexport function createMenuElement(\n items: ContextMenuItem[],\n params: ContextMenuParams,\n onAction: (item: ContextMenuItem) => void,\n submenuArrow: IconValue = DEFAULT_GRID_ICONS.submenuArrow,\n): HTMLElement {\n const menu = document.createElement('div');\n menu.className = 'tbw-context-menu';\n menu.setAttribute('role', 'menu');\n\n // Check if any non-separator item has an icon\n const hasAnyIcon = items.some((item) => !item.separator && item.icon);\n\n for (const item of items) {\n if (item.separator) {\n const separator = document.createElement('div');\n separator.className = 'tbw-context-menu-separator';\n separator.setAttribute('role', 'separator');\n menu.appendChild(separator);\n continue;\n }\n\n const menuItem = document.createElement('div');\n menuItem.className = 'tbw-context-menu-item';\n if (item.cssClass) menuItem.classList.add(item.cssClass);\n menuItem.setAttribute('role', 'menuitem');\n menuItem.setAttribute('data-id', item.id);\n\n const disabled = isItemDisabled(item, params);\n if (disabled) {\n menuItem.classList.add('disabled');\n menuItem.setAttribute('aria-disabled', 'true');\n }\n\n if (item.icon) {\n const icon = document.createElement('span');\n icon.className = 'tbw-context-menu-icon';\n icon.innerHTML = item.icon;\n menuItem.appendChild(icon);\n } else if (hasAnyIcon) {\n // Add empty placeholder to align labels when other items have icons\n const icon = document.createElement('span');\n icon.className = 'tbw-context-menu-icon';\n icon.innerHTML = ' ';\n menuItem.appendChild(icon);\n }\n\n const label = document.createElement('span');\n label.className = 'tbw-context-menu-label';\n label.textContent = item.name;\n menuItem.appendChild(label);\n\n if (item.shortcut) {\n const shortcut = document.createElement('span');\n shortcut.className = 'tbw-context-menu-shortcut';\n if (Array.isArray(item.shortcut)) {\n item.shortcut.forEach((key, i) => {\n if (i > 0) shortcut.appendChild(document.createTextNode('+'));\n const kbd = document.createElement('kbd');\n kbd.textContent = key;\n shortcut.appendChild(kbd);\n });\n } else {\n const kbd = document.createElement('kbd');\n kbd.textContent = item.shortcut;\n shortcut.appendChild(kbd);\n }\n menuItem.appendChild(shortcut);\n }\n\n if (item.subMenu?.length) {\n const arrow = document.createElement('span');\n arrow.className = 'tbw-context-menu-arrow';\n // Use provided submenu arrow icon (string or HTMLElement)\n if (typeof submenuArrow === 'string') {\n arrow.innerHTML = submenuArrow;\n } else if (submenuArrow instanceof HTMLElement) {\n arrow.appendChild(submenuArrow.cloneNode(true));\n }\n menuItem.appendChild(arrow);\n\n // Add submenu on hover\n menuItem.addEventListener('mouseenter', () => {\n const existingSubMenu = menuItem.querySelector('.tbw-context-menu');\n if (existingSubMenu) return;\n if (!item.subMenu) return;\n\n const subMenuItems = buildMenuItems(item.subMenu, params);\n const subMenu = createMenuElement(subMenuItems, params, onAction, submenuArrow);\n subMenu.classList.add('tbw-context-submenu');\n subMenu.style.position = 'absolute';\n subMenu.style.left = '100%';\n subMenu.style.top = '0';\n menuItem.style.position = 'relative';\n menuItem.appendChild(subMenu);\n });\n\n menuItem.addEventListener('mouseleave', () => {\n const subMenu = menuItem.querySelector('.tbw-context-menu');\n if (subMenu) subMenu.remove();\n });\n }\n\n if (!disabled && item.action && !item.subMenu) {\n menuItem.addEventListener('click', (e) => {\n e.stopPropagation();\n onAction(item);\n });\n }\n\n menu.appendChild(menuItem);\n }\n\n return menu;\n}\n\n/**\n * Position the menu at the given viewport coordinates.\n * Menu is rendered in document.body with fixed positioning for top-layer behavior.\n *\n * @param menu - The menu element to position\n * @param x - Desired X coordinate (viewport)\n * @param y - Desired Y coordinate (viewport)\n */\nexport function positionMenu(menu: HTMLElement, x: number, y: number): void {\n // Use fixed positioning for top-layer behavior\n menu.style.position = 'fixed';\n menu.style.left = `${x}px`;\n menu.style.top = `${y}px`;\n menu.style.visibility = 'hidden';\n menu.style.zIndex = '10000';\n\n // Force layout to get dimensions\n const menuRect = menu.getBoundingClientRect();\n\n // Calculate visible area within viewport\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n\n let left = x;\n let top = y;\n\n // Check if menu overflows right edge of viewport\n if (x + menuRect.width > viewportWidth) {\n left = x - menuRect.width;\n }\n // Check if menu overflows bottom edge of viewport\n if (y + menuRect.height > viewportHeight) {\n top = y - menuRect.height;\n }\n\n // Ensure we don't go negative\n left = Math.max(0, left);\n top = Math.max(0, top);\n\n menu.style.left = `${left}px`;\n menu.style.top = `${top}px`;\n menu.style.visibility = 'visible';\n}\n","/**\n * Context Menu Plugin (Class-based)\n *\n * Provides right-click context menu functionality for tbw-grid.\n * Supports custom menu items, submenus, icons, shortcuts, and dynamic item generation.\n */\n\nimport type { PluginManifest } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport contextMenuStyles from './context-menu.css?inline';\nimport { buildMenuItems, collapseSeparators, createMenuElement, positionMenu } from './menu';\nimport type { ContextMenuConfig, ContextMenuItem, ContextMenuParams, HeaderContextMenuItem } from './types';\n\n/** Query type for collecting context menu items from plugins */\nconst QUERY_GET_CONTEXT_MENU_ITEMS = 'getContextMenuItems';\n\n/** Global click handler reference for cleanup */\nlet globalClickHandler: ((e: Event) => void) | null = null;\n/** Global keydown handler reference for cleanup */\nlet globalKeydownHandler: ((e: KeyboardEvent) => void) | null = null;\n/** Global stylesheet for context menu (injected once) */\nlet globalStyleSheet: HTMLStyleElement | null = null;\n/** Reference count for instances using global handlers */\nlet globalHandlerRefCount = 0;\n\n/** Default menu items when none are configured */\nconst defaultItems: ContextMenuItem[] = [\n {\n id: 'copy',\n name: 'Copy',\n shortcut: 'Ctrl+C',\n action: (params) => {\n const grid = (params as ContextMenuParams & { grid?: { plugins?: { clipboard?: { copy?: () => void } } } }).grid;\n grid?.plugins?.clipboard?.copy?.();\n },\n },\n { separator: true, id: 'sep1', name: '' },\n {\n id: 'export-csv',\n name: 'Export CSV',\n action: (params) => {\n const grid = (params as ContextMenuParams & { grid?: { plugins?: { export?: { exportCsv?: () => void } } } })\n .grid;\n grid?.plugins?.export?.exportCsv?.();\n },\n },\n];\n\n/**\n * Context Menu Plugin for tbw-grid\n *\n * Adds a customizable right-click menu to grid cells. Build anything from simple\n * copy/paste actions to complex nested menus with conditional visibility, icons,\n * and keyboard shortcuts.\n *\n * ## Installation\n *\n * ```ts\n * import { ContextMenuPlugin } from '@toolbox-web/grid/plugins/context-menu';\n * ```\n *\n * ## Menu Item Structure\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `id` | `string` | Unique item identifier |\n * | `name` | `string` | Display label |\n * | `icon` | `string` | Icon class or HTML |\n * | `shortcut` | `string` | Keyboard shortcut hint |\n * | `action` | `(params) => void` | Click handler |\n * | `disabled` | `boolean \\| (params) => boolean` | Disable condition |\n * | `visible` | `boolean \\| (params) => boolean` | Visibility condition |\n * | `items` | `MenuItem[]` | Submenu items |\n * | `separator` | `boolean` | Create a divider line |\n *\n * ## Menu Context (params)\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `rowIndex` | `number` | Clicked row index |\n * | `colIndex` | `number` | Clicked column index |\n * | `field` | `string` | Column field name |\n * | `value` | `any` | Cell value |\n * | `row` | `any` | Full row data |\n * | `column` | `ColumnConfig` | Column configuration |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-context-menu-bg` | `var(--tbw-color-panel-bg)` | Menu background |\n * | `--tbw-context-menu-fg` | `var(--tbw-color-fg)` | Menu text color |\n * | `--tbw-context-menu-hover` | `var(--tbw-color-row-hover)` | Item hover background |\n *\n * @example Basic Context Menu\n * ```ts\n * import '@toolbox-web/grid';\n * import { ContextMenuPlugin } from '@toolbox-web/grid/plugins/context-menu';\n *\n * grid.gridConfig = {\n * plugins: [\n * new ContextMenuPlugin({\n * items: [\n * { id: 'copy', name: 'Copy', shortcut: 'Ctrl+C', action: (ctx) => navigator.clipboard.writeText(ctx.value) },\n * { separator: true, id: 'sep1', name: '' },\n * { id: 'delete', name: 'Delete', action: (ctx) => removeRow(ctx.rowIndex) },\n * ],\n * }),\n * ],\n * };\n * ```\n *\n * @example Conditional Menu Items\n * ```ts\n * new ContextMenuPlugin({\n * items: [\n * { id: 'edit', name: 'Edit', visible: (ctx) => ctx.column.editable === true },\n * { id: 'delete', name: 'Delete', disabled: (ctx) => ctx.row.locked === true },\n * ],\n * })\n * ```\n *\n * @see {@link ContextMenuConfig} for configuration options\n * @see {@link ContextMenuItem} for menu item structure\n * @see {@link ContextMenuParams} for action callback parameters\n *\n * @internal Extends BaseGridPlugin\n */\nexport class ContextMenuPlugin extends BaseGridPlugin<ContextMenuConfig> {\n /**\n * Plugin manifest - declares queries used by this plugin.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n queries: [\n {\n type: QUERY_GET_CONTEXT_MENU_ITEMS,\n description: 'Collects context menu items from other plugins for header right-click menus',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'contextMenu';\n\n /** @internal */\n protected override get defaultConfig(): Partial<ContextMenuConfig> {\n return {\n items: defaultItems,\n };\n }\n\n // #region Internal State\n private isOpen = false;\n private position = { x: 0, y: 0 };\n private params: ContextMenuParams | null = null;\n private menuElement: HTMLElement | null = null;\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n this.installGlobalHandlers();\n globalHandlerRefCount++;\n }\n\n /** @internal */\n override detach(): void {\n if (this.menuElement) {\n this.menuElement.remove();\n this.menuElement = null;\n }\n this.isOpen = false;\n this.params = null;\n this.uninstallGlobalHandlers();\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Sync selection with the right-clicked row.\n * If the right-clicked row is already selected, keeps the multi-selection.\n * If not, selects only the right-clicked row (standard behavior in file managers / spreadsheets).\n *\n * @returns Sorted array of selected row indices after sync\n */\n private syncSelectionOnContextMenu(rowIndex: number): number[] {\n if (rowIndex < 0) return [];\n\n // Use the query system for loose coupling — no import of SelectionPlugin needed\n const selectionResult = this.grid?.query<number[]>('getSelectedRowIndices');\n const currentSelection = selectionResult?.[0];\n\n // No selection plugin loaded\n if (!currentSelection) return [rowIndex];\n\n if (currentSelection.includes(rowIndex)) {\n // Right-clicked row is already selected — preserve multi-selection\n return currentSelection;\n }\n\n // Right-clicked row is NOT selected — select only this row\n this.grid?.query('selectRows', [rowIndex]);\n return [rowIndex];\n }\n\n /**\n * CSS variables to copy from the grid element to the context menu.\n * Includes both base variables and context-menu specific overrides.\n */\n private static readonly CSS_VARS_TO_COPY = [\n // Base palette (for themes that only set base vars)\n '--tbw-color-panel-bg',\n '--tbw-color-fg',\n '--tbw-color-fg-muted',\n '--tbw-color-border',\n '--tbw-color-row-hover',\n '--tbw-color-shadow',\n '--tbw-color-danger',\n '--tbw-border-radius',\n '--tbw-font-family',\n '--tbw-font-size-sm',\n '--tbw-font-size-xs',\n '--tbw-font-size-2xs',\n '--tbw-spacing-xs',\n '--tbw-icon-size',\n '--tbw-menu-min-width',\n '--tbw-menu-item-padding',\n '--tbw-menu-item-gap',\n // Context menu specific overrides\n '--tbw-context-menu-bg',\n '--tbw-context-menu-fg',\n '--tbw-context-menu-border',\n '--tbw-context-menu-radius',\n '--tbw-context-menu-shadow',\n '--tbw-context-menu-hover',\n '--tbw-context-menu-danger',\n '--tbw-context-menu-muted',\n '--tbw-context-menu-min-width',\n '--tbw-context-menu-font-size',\n '--tbw-context-menu-font-family',\n '--tbw-context-menu-item-padding',\n '--tbw-context-menu-item-gap',\n '--tbw-context-menu-icon-size',\n '--tbw-context-menu-shortcut-size',\n '--tbw-context-menu-arrow-size',\n ];\n\n /**\n * Copy CSS custom properties from the grid element to the menu element.\n * This allows the context menu (appended to document.body) to inherit\n * theme variables set on tbw-grid.\n */\n private copyGridStyles(menuElement: HTMLElement): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n const computed = getComputedStyle(gridEl);\n const styles: string[] = [];\n\n // Copy color-scheme so light-dark() can resolve in the context menu\n const colorScheme = computed.getPropertyValue('color-scheme').trim();\n if (colorScheme) {\n styles.push(`color-scheme: ${colorScheme}`);\n }\n\n for (const varName of ContextMenuPlugin.CSS_VARS_TO_COPY) {\n const value = computed.getPropertyValue(varName).trim();\n if (value) {\n styles.push(`${varName}: ${value}`);\n }\n }\n\n if (styles.length > 0) {\n // Append to existing inline styles (don't overwrite)\n const existing = menuElement.getAttribute('style') || '';\n menuElement.setAttribute('style', existing + styles.join('; ') + ';');\n }\n }\n\n private installGlobalHandlers(): void {\n // Inject global stylesheet for context menu (once)\n // Only inject if we have valid CSS text (Vite's ?inline import)\n // When importing from source without Vite, the import is a module object, not a string\n if (\n !globalStyleSheet &&\n typeof document !== 'undefined' &&\n typeof contextMenuStyles === 'string' &&\n contextMenuStyles\n ) {\n globalStyleSheet = document.createElement('style');\n globalStyleSheet.id = 'tbw-context-menu-styles';\n globalStyleSheet.textContent = contextMenuStyles;\n document.head.appendChild(globalStyleSheet);\n }\n\n // Close menu on click outside\n if (!globalClickHandler) {\n globalClickHandler = () => {\n const menus = document.querySelectorAll('.tbw-context-menu');\n menus.forEach((menu) => menu.remove());\n };\n document.addEventListener('click', globalClickHandler);\n }\n\n // Close on escape\n if (!globalKeydownHandler) {\n globalKeydownHandler = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n const menus = document.querySelectorAll('.tbw-context-menu');\n menus.forEach((menu) => menu.remove());\n }\n };\n document.addEventListener('keydown', globalKeydownHandler);\n }\n }\n\n /**\n * Clean up global handlers when the last instance detaches.\n * Uses reference counting to ensure handlers persist while any grid uses the plugin.\n */\n private uninstallGlobalHandlers(): void {\n globalHandlerRefCount--;\n if (globalHandlerRefCount > 0) return;\n\n // Last instance - clean up all global resources\n if (globalClickHandler) {\n document.removeEventListener('click', globalClickHandler);\n globalClickHandler = null;\n }\n if (globalKeydownHandler) {\n document.removeEventListener('keydown', globalKeydownHandler);\n globalKeydownHandler = null;\n }\n if (globalStyleSheet) {\n globalStyleSheet.remove();\n globalStyleSheet = null;\n }\n }\n\n /**\n * Query all plugins for context menu items via the query system.\n * Each plugin that handles `getContextMenuItems` can return an array of HeaderContextMenuItem.\n */\n private collectPluginItems(params: ContextMenuParams): HeaderContextMenuItem[] {\n if (!this.grid) return [];\n\n const responses = this.grid.query<HeaderContextMenuItem[]>(QUERY_GET_CONTEXT_MENU_ITEMS, params);\n const items: HeaderContextMenuItem[] = [];\n\n for (const response of responses) {\n if (Array.isArray(response)) {\n items.push(...response);\n }\n }\n\n // Sort by order (default 100), then stable by insertion order\n items.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n\n // Insert separators between different order groups\n return this.insertGroupSeparators(items);\n }\n\n /**\n * Insert separators between groups of items with different order ranges.\n * Groups are defined by the tens digit (10-19, 20-29, etc.).\n */\n private insertGroupSeparators(items: HeaderContextMenuItem[]): HeaderContextMenuItem[] {\n if (items.length <= 1) return items;\n\n const result: HeaderContextMenuItem[] = [];\n let lastGroup = -1;\n\n for (const item of items) {\n if (item.separator) {\n result.push(item);\n continue;\n }\n const group = Math.floor((item.order ?? 100) / 10);\n if (lastGroup >= 0 && group !== lastGroup) {\n result.push({\n id: `__sep-${lastGroup}-${group}`,\n label: '',\n separator: true,\n action: () => {\n /* noop */\n },\n });\n }\n lastGroup = group;\n result.push(item);\n }\n\n return result;\n }\n\n /**\n * Convert plugin-contributed HeaderContextMenuItems to the internal ContextMenuItem format.\n */\n private convertPluginItems(items: HeaderContextMenuItem[]): ContextMenuItem[] {\n return items.map((item) => ({\n id: item.id,\n name: item.label,\n icon: item.icon,\n shortcut: item.shortcut,\n disabled: item.disabled ?? false,\n action: () => item.action(),\n separator: item.separator,\n cssClass: item.cssClass,\n }));\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Use querySelector instead of children[0] because light DOM children\n // (e.g. <tbw-grid-column>) are re-appended before .tbw-grid-root, making\n // children[0] point to a declarative element instead of the data container.\n const container = gridEl.querySelector('.tbw-grid-root');\n if (!container) return;\n\n // Check if handler already attached\n if (container.getAttribute('data-context-menu-bound') === 'true') return;\n container.setAttribute('data-context-menu-bound', 'true');\n\n container.addEventListener('contextmenu', (e: Event) => {\n const event = e as MouseEvent;\n event.preventDefault();\n\n const target = event.target as HTMLElement;\n const cell = target.closest('[data-row][data-col]');\n const header = target.closest('[part~=\"header-cell\"]');\n\n let params: ContextMenuParams;\n\n if (cell) {\n const rowIndex = parseInt(cell.getAttribute('data-row') ?? '-1', 10);\n const colIndex = parseInt(cell.getAttribute('data-col') ?? '-1', 10);\n const column = this.visibleColumns[colIndex];\n const row = this.rows[rowIndex];\n\n // Sync selection: if the right-clicked row is not already selected,\n // select it (clearing multi-selection). If it IS selected, keep all.\n const selectedRows = this.syncSelectionOnContextMenu(rowIndex);\n\n params = {\n row,\n rowIndex,\n column,\n columnIndex: colIndex,\n field: column?.field ?? '',\n value: row?.[column?.field as keyof typeof row] ?? null,\n isHeader: false,\n event,\n selectedRows,\n };\n } else if (header) {\n const colIndex = parseInt(header.getAttribute('data-col') ?? '-1', 10);\n const column = this.visibleColumns[colIndex];\n\n params = {\n row: null,\n rowIndex: -1,\n column,\n columnIndex: colIndex,\n field: column?.field ?? '',\n value: null,\n isHeader: true,\n event,\n selectedRows: [],\n };\n } else {\n return;\n }\n\n this.params = params;\n this.position = { x: event.clientX, y: event.clientY };\n\n // Collect plugin-contributed items via the query system\n const pluginItems = this.collectPluginItems(params);\n\n // Build configured items\n let items = buildMenuItems(this.config.items ?? defaultItems, params);\n\n // Merge plugin items with configured items\n if (pluginItems.length > 0) {\n const converted = this.convertPluginItems(pluginItems);\n if (items.length > 0 && converted.length > 0) {\n // Add separator between configured and plugin items\n items = [...items, { id: '__plugin-sep', name: '', separator: true }, ...converted];\n } else {\n items = [...items, ...converted];\n }\n }\n\n // Collapse consecutive/leading/trailing separators\n items = collapseSeparators(items);\n\n if (!items.length) return;\n\n if (this.menuElement) {\n this.menuElement.remove();\n }\n\n this.menuElement = createMenuElement(\n items,\n params,\n (item) => {\n if (item.action) {\n item.action(params);\n }\n this.menuElement?.remove();\n this.menuElement = null;\n this.isOpen = false;\n },\n this.gridIcons.submenuArrow,\n );\n\n document.body.appendChild(this.menuElement);\n this.copyGridStyles(this.menuElement);\n positionMenu(this.menuElement, event.clientX, event.clientY);\n this.isOpen = true;\n\n this.emit('context-menu-open', { params, items });\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Programmatically show the context menu at the specified position.\n * @param x - X coordinate\n * @param y - Y coordinate\n * @param params - Partial context menu parameters\n */\n showMenu(x: number, y: number, params: Partial<ContextMenuParams>): void {\n const fullParams: ContextMenuParams = {\n row: params.row ?? null,\n rowIndex: params.rowIndex ?? -1,\n column: params.column ?? null,\n columnIndex: params.columnIndex ?? -1,\n field: params.field ?? '',\n value: params.value ?? null,\n isHeader: params.isHeader ?? false,\n event: params.event ?? new MouseEvent('contextmenu'),\n selectedRows: params.selectedRows ?? [],\n };\n\n const pluginItems = this.collectPluginItems(fullParams);\n let items = buildMenuItems(this.config.items ?? defaultItems, fullParams);\n\n if (pluginItems.length > 0) {\n const converted = this.convertPluginItems(pluginItems);\n if (items.length > 0 && converted.length > 0) {\n items = [...items, { id: '__plugin-sep', name: '', separator: true }, ...converted];\n } else {\n items = [...items, ...converted];\n }\n }\n\n // Collapse consecutive/leading/trailing separators\n items = collapseSeparators(items);\n\n if (this.menuElement) {\n this.menuElement.remove();\n }\n\n this.menuElement = createMenuElement(\n items,\n fullParams,\n (item) => {\n if (item.action) item.action(fullParams);\n this.menuElement?.remove();\n this.menuElement = null;\n this.isOpen = false;\n },\n this.gridIcons.submenuArrow,\n );\n\n document.body.appendChild(this.menuElement);\n this.copyGridStyles(this.menuElement);\n positionMenu(this.menuElement, x, y);\n this.isOpen = true;\n }\n\n /**\n * Hide the context menu.\n */\n hideMenu(): void {\n if (this.menuElement) {\n this.menuElement.remove();\n this.menuElement = null;\n this.isOpen = false;\n }\n }\n\n /**\n * Check if the context menu is currently open.\n * @returns Whether the menu is open\n */\n isMenuOpen(): boolean {\n return this.isOpen;\n }\n // #endregion\n\n // Styles are injected globally via installGlobalHandlers() since menu renders in document.body\n}\n","/**\n * Default Editors Module\n *\n * Provides built-in editor factories for different column types.\n * Each editor type has its own factory function for consistency and readability.\n *\n * IMPORTANT: Editor factories should NOT call focus() on elements - they are called\n * before the element is appended to the DOM. The calling code (beginBulkEdit,\n * inlineEnterEdit) is responsible for focusing the correct editor after insertion.\n */\n\nimport type { ColumnConfig, ColumnEditorContext } from '../../core/types';\nimport type { DateEditorParams, NumberEditorParams, SelectEditorParams, TextEditorParams } from './types';\n\n// ============================================================================\n// Type Aliases\n// ============================================================================\n\n/** Option shape used by select editor (matches column.options) */\ntype ColumnOption = { label: string; value: unknown };\n\n/** Column with any row type (used for editor factories) */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyColumn = ColumnConfig<any>;\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/** Resolve column.options (handles both array and function forms) */\nfunction resolveOptions(column: AnyColumn): ColumnOption[] {\n const raw = column.options;\n if (!raw) return [];\n return typeof raw === 'function' ? raw() : raw;\n}\n\n// ============================================================================\n// Editor Factories\n// ============================================================================\n\n/** Creates a number input editor */\nfunction createNumberEditor(column: AnyColumn): (ctx: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as NumberEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'number';\n input.value = ctx.value != null ? String(ctx.value) : '';\n\n if (params?.min !== undefined) input.min = String(params.min);\n if (params?.max !== undefined) input.max = String(params.max);\n if (params?.step !== undefined) input.step = String(params.step);\n if (params?.placeholder) input.placeholder = params.placeholder;\n\n const commit = () => 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: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const input = document.createElement('input');\n input.type = 'checkbox';\n input.checked = !!ctx.value;\n input.addEventListener('change', () => ctx.commit(input.checked));\n return input;\n };\n}\n\n/** Creates a date input editor */\nfunction createDateEditor(column: AnyColumn): (ctx: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as DateEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'date';\n\n // Set initial value - handle both Date objects and string dates\n if (ctx.value instanceof Date) {\n input.valueAsDate = ctx.value;\n } else if (typeof ctx.value === 'string' && ctx.value) {\n // String date like \"2019-10-09\" - set directly as value\n input.value = ctx.value.split('T')[0]; // Handle ISO strings too\n }\n if (params?.min) input.min = params.min;\n if (params?.max) input.max = params.max;\n if (params?.placeholder) input.placeholder = params.placeholder;\n\n // Commit function preserves original type (string vs Date)\n const commit = () => {\n if (typeof ctx.value === 'string') {\n // Original was string, return string in YYYY-MM-DD format\n ctx.commit(input.value);\n } else {\n ctx.commit(input.valueAsDate);\n }\n };\n\n input.addEventListener('change', commit);\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return input;\n };\n}\n\n/** Creates a select dropdown editor */\nfunction createSelectEditor(column: AnyColumn): (ctx: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as SelectEditorParams | undefined;\n const select = document.createElement('select');\n if (column.multi) select.multiple = true;\n\n // Add 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: ColumnEditorContext) => HTMLElement {\n return (ctx) => {\n const params = column.editorParams as TextEditorParams | undefined;\n const input = document.createElement('input');\n input.type = 'text';\n input.value = ctx.value != null ? String(ctx.value) : '';\n\n if (params?.maxLength !== undefined) input.maxLength = params.maxLength;\n if (params?.pattern) input.pattern = params.pattern;\n if (params?.placeholder) input.placeholder = params.placeholder;\n\n // Commit function preserves original type when possible\n const commit = () => {\n const inputVal = input.value;\n // Preserve null/undefined: empty input on a null/undefined field means no change\n if ((ctx.value === null || ctx.value === undefined) && inputVal === '') {\n return;\n }\n // Preserve values with characters that <input> can't represent (newlines, etc.).\n // If stripping those characters produces the same string, the user didn't change anything.\n if (typeof ctx.value === 'string' && inputVal === ctx.value.replace(/[\\n\\r]/g, '')) {\n return;\n }\n // Preserve numeric type for custom column types (e.g., currency)\n if (typeof ctx.value === 'number' && inputVal !== '') {\n ctx.commit(Number(inputVal));\n } else {\n ctx.commit(inputVal);\n }\n };\n\n input.addEventListener('blur', commit);\n input.addEventListener('keydown', (e) => {\n if (e.key === 'Enter') commit();\n if (e.key === 'Escape') ctx.cancel();\n });\n\n return input;\n };\n}\n\n// ============================================================================\n// Main Entry Point\n// ============================================================================\n\n/**\n * Returns a default editor factory function for the given column type.\n * Each editor handles commit on blur/Enter, and cancel on Escape.\n *\n * Note: Focus is NOT called here - the calling code handles focusing after DOM insertion.\n */\nexport function defaultEditorFor(column: AnyColumn): (ctx: ColumnEditorContext) => HTMLElement | string {\n switch (column.type) {\n case 'number':\n return createNumberEditor(column);\n case 'boolean':\n return createBooleanEditor();\n case 'date':\n return createDateEditor(column);\n case 'select':\n return createSelectEditor(column);\n default:\n return createTextEditor(column);\n }\n}\n\n// ============================================================================\n// Utility Export (used by EditingPlugin)\n// ============================================================================\n\n/**\n * 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 { ensureCellVisible } from '../../core/internal/keyboard';\nimport { FOCUSABLE_EDITOR_SELECTOR } from '../../core/internal/rows';\nimport type { AfterCellRenderContext, PluginManifest, PluginQuery } 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 {\n CellCommitDetail,\n ChangedRowsResetDetail,\n EditCloseDetail,\n EditingConfig,\n EditOpenDetail,\n EditorContext,\n RowCommitDetail,\n} from './types';\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, column config, and original value.\n * Preserves the type of the original value (e.g., numeric currency values stay as numbers,\n * string dates stay as strings).\n */\nfunction getInputValue(\n input: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n column?: ColumnConfig<any>,\n originalValue?: unknown,\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') {\n // Preserve original type: if original was a string, return string (YYYY-MM-DD format)\n if (typeof originalValue === 'string') {\n return input.value; // input.value is already in YYYY-MM-DD format\n }\n return input.valueAsDate;\n }\n // For text inputs, check if original value was a number to preserve type\n if (typeof originalValue === 'number') {\n return input.value === '' ? null : Number(input.value);\n }\n // Preserve null/undefined: if original was null/undefined and input is empty, return original\n if ((originalValue === null || originalValue === undefined) && input.value === '') {\n return originalValue;\n }\n // Preserve values with characters <input> can't represent (newlines, etc.)\n if (typeof originalValue === 'string' && input.value === originalValue.replace(/[\\n\\r]/g, '')) {\n return originalValue;\n }\n return input.value;\n }\n // For textarea/select, check column type OR original value type\n if (column?.type === 'number' && input.value !== '') {\n return Number(input.value);\n }\n // Preserve numeric type for custom column types (e.g., currency)\n if (typeof originalValue === 'number' && input.value !== '') {\n return Number(input.value);\n }\n // Preserve null/undefined: if original was null/undefined and input is empty, return original\n if ((originalValue === null || originalValue === undefined) && input.value === '') {\n return originalValue;\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 originalValue?: unknown,\n): void {\n const input = editorHost.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (!input) return;\n\n input.addEventListener('blur', () => {\n commit(getInputValue(input, column, originalValue));\n });\n\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.addEventListener('change', () => commit(input.checked));\n } else if (input instanceof HTMLSelectElement) {\n input.addEventListener('change', () => commit(getInputValue(input, column, originalValue)));\n }\n}\n\n// ============================================================================\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 events: [\n {\n type: 'cell-edit-committed',\n description: 'Emitted when a cell edit is committed (for plugin-to-plugin coordination)',\n },\n ],\n queries: [\n {\n type: 'isEditing',\n description: 'Returns whether any cell is currently being edited',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'editing';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<EditingConfig> {\n return {\n mode: 'row',\n editOn: 'click',\n };\n }\n\n /**\n * Whether the grid is in 'grid' mode (all cells always editable).\n */\n get #isGridMode(): boolean {\n return this.config.mode === 'grid';\n }\n\n // #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 /**\n * Value-change callbacks for active editors.\n * Keyed by \"rowIndex:field\" → callback that pushes updated values to the editor.\n * Populated during #injectEditor, cleaned up when editors are removed.\n */\n #editorValueCallbacks = new Map<string, (newValue: unknown) => void>();\n\n /** Flag to restore focus after next render (used when exiting edit mode) */\n #pendingFocusRestore = false;\n\n /** Row index pending animation after render, or -1 if none */\n #pendingRowAnimation = -1;\n\n /**\n * Invalid cell tracking: Map<rowId, Map<field, message>>\n * Used for validation feedback without canceling edits.\n */\n #invalidCells = new Map<string, Map<string, string>>();\n\n /**\n * In grid mode, tracks whether an input field is currently focused.\n * When true: arrow keys work within input (edit mode).\n * When false: arrow keys navigate between cells (navigation mode).\n * Escape switches to navigation mode, Enter switches to edit mode.\n */\n #gridModeInputFocused = false;\n\n /**\n * In grid mode, when true, prevents inputs from auto-focusing.\n * This is set when Escape is pressed (navigation mode) and cleared\n * when Enter is pressed or user explicitly clicks an input.\n */\n #gridModeEditLocked = false;\n\n /**\n * When true, only a single cell is being edited (triggered by F2 or `beginCellEdit`).\n * Tab and Arrow keys commit and close the editor instead of navigating to adjacent cells.\n */\n #singleCellEdit = false;\n\n // #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 (only in 'row' mode)\n document.addEventListener(\n 'keydown',\n (e: KeyboardEvent) => {\n // In grid mode, Escape doesn't exit edit mode\n if (this.#isGridMode) return;\n if (e.key === 'Escape' && this.#activeEditRow !== -1) {\n // Allow users to prevent edit close via callback (e.g., when overlay is open)\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(e);\n if (shouldClose === false) {\n return;\n }\n }\n this.#exitRowEdit(this.#activeEditRow, true);\n }\n },\n { capture: true, signal },\n );\n\n // Click outside to commit editing (only in 'row' mode)\n // Use queueMicrotask to allow pending change events to fire first.\n // This is important for Angular/React editors where the (change) event\n // fires after mousedown but before mouseup/click.\n document.addEventListener(\n 'mousedown',\n (e: MouseEvent) => {\n // In grid mode, clicking outside doesn't exit edit mode\n if (this.#isGridMode) return;\n if (this.#activeEditRow === -1) return;\n const rowEl = internalGrid.findRenderedRowElement?.(this.#activeEditRow);\n if (!rowEl) return;\n const path = (e.composedPath && e.composedPath()) || [];\n if (path.includes(rowEl)) return;\n\n // Allow users to prevent edit close via callback (e.g., when click is inside an overlay)\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(e);\n if (shouldClose === false) {\n return;\n }\n }\n\n // Delay exit to allow pending change/commit events to fire\n queueMicrotask(() => {\n if (this.#activeEditRow !== -1) {\n this.#exitRowEdit(this.#activeEditRow, false);\n }\n });\n },\n { signal },\n );\n\n // Listen for external row mutations to push updated values to active editors.\n // When field A commits and sets field B via updateRow(), field B's editor\n // (if open) must reflect the new value.\n this.gridElement.addEventListener(\n 'cell-change',\n (e: Event) => {\n const detail = (e as CustomEvent).detail as {\n rowIndex: number;\n field: string;\n newValue: unknown;\n source: string;\n };\n // Only push updates from cascade/api sources — not from the editor's own commit\n if (detail.source === 'user') return;\n const key = `${detail.rowIndex}:${detail.field}`;\n const cb = this.#editorValueCallbacks.get(key);\n if (cb) cb(detail.newValue);\n },\n { signal },\n );\n\n // In grid mode, request a full render to trigger afterCellRender hooks\n if (this.#isGridMode) {\n internalGrid._isGridEditMode = true;\n this.gridElement.classList.add('tbw-grid-mode');\n this.requestRender();\n\n // Track focus/blur on inputs to maintain navigation vs edit mode state\n this.gridElement.addEventListener(\n 'focusin',\n (e: FocusEvent) => {\n const target = e.target as HTMLElement;\n if (target.matches(FOCUSABLE_EDITOR_SELECTOR)) {\n // If edit is locked (navigation mode), blur the input immediately\n if (this.#gridModeEditLocked) {\n target.blur();\n this.gridElement.focus();\n return;\n }\n this.#gridModeInputFocused = true;\n }\n },\n { signal },\n );\n\n this.gridElement.addEventListener(\n 'focusout',\n (e: FocusEvent) => {\n const related = e.relatedTarget as HTMLElement | null;\n // Only clear if focus went outside grid or to a non-input element\n if (!related || !this.gridElement.contains(related) || !related.matches(FOCUSABLE_EDITOR_SELECTOR)) {\n this.#gridModeInputFocused = false;\n }\n },\n { signal },\n );\n\n // Handle Escape key directly on the grid element (capture phase)\n // This ensures we intercept Escape even when focus is inside an input\n this.gridElement.addEventListener(\n 'keydown',\n (e: KeyboardEvent) => {\n if (e.key === 'Escape' && this.#gridModeInputFocused) {\n // Allow users to prevent Escape handling via callback (e.g., when overlay is open).\n // In grid mode, Escape transitions from editing to navigation mode, so we check\n // onBeforeEditClose to let overlays (dropdowns, autocompletes) close first.\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(e);\n if (shouldClose === false) {\n return; // Let the overlay handle Escape\n }\n }\n const activeEl = document.activeElement as HTMLElement;\n if (activeEl && this.gridElement.contains(activeEl)) {\n activeEl.blur();\n // Move focus to the grid container so arrow keys work\n this.gridElement.focus();\n }\n this.#gridModeInputFocused = false;\n this.#gridModeEditLocked = true; // Lock edit mode until Enter/click\n e.preventDefault();\n e.stopPropagation();\n }\n },\n { capture: true, signal },\n );\n\n // Handle click on inputs - unlock edit mode when user explicitly clicks\n this.gridElement.addEventListener(\n 'mousedown',\n (e: MouseEvent) => {\n const target = e.target as HTMLElement;\n if (target.matches(FOCUSABLE_EDITOR_SELECTOR)) {\n this.#gridModeEditLocked = false; // User clicked input - allow edit\n }\n },\n { signal },\n );\n }\n }\n\n /** @internal */\n override detach(): void {\n const internalGrid = this.gridElement as unknown as InternalGrid<T>;\n internalGrid._isGridEditMode = false;\n this.gridElement.classList.remove('tbw-grid-mode');\n this.#activeEditRow = -1;\n this.#activeEditCol = -1;\n this.#rowEditSnapshots.clear();\n this.#changedRowIds.clear();\n this.#editingCells.clear();\n this.#editorValueCallbacks.clear();\n this.#gridModeInputFocused = false;\n this.#gridModeEditLocked = false;\n this.#singleCellEdit = false;\n super.detach();\n }\n\n /**\n * Handle plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'isEditing') {\n // In grid mode, we're always editing\n return this.#isGridMode || this.#activeEditRow !== -1;\n }\n return undefined;\n }\n\n // #endregion\n\n // #region Event Handlers (event distribution)\n\n /**\n * Handle cell clicks - start editing if configured for click mode.\n * Both click and dblclick events come through this handler.\n * Starts row-based editing (all editable cells in the row get editors).\n * @internal\n */\n override onCellClick(event: CellClickEvent): boolean | void {\n // In grid mode, all cells are already editable - no need to trigger row edit\n if (this.#isGridMode) return false;\n\n const internalGrid = this.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 (row mode) or exit edit mode (grid mode)\n if (event.key === 'Escape') {\n // In grid mode: blur input to enable arrow key navigation\n if (this.#isGridMode && this.#gridModeInputFocused) {\n // Allow users to prevent Escape handling via callback (e.g., when overlay is open)\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(event);\n if (shouldClose === false) {\n return true; // Handled: block grid navigation, let overlay handle Escape\n }\n }\n const activeEl = document.activeElement as HTMLElement;\n if (activeEl && this.gridElement.contains(activeEl)) {\n activeEl.blur();\n }\n this.#gridModeInputFocused = false;\n // Update focus styling\n this.requestAfterRender();\n return true;\n }\n\n // In row mode: cancel edit\n if (this.#activeEditRow !== -1 && !this.#isGridMode) {\n // Allow users to prevent edit close via callback (e.g., when overlay is open)\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(event);\n if (shouldClose === false) {\n return true; // Handled: block grid navigation, let event reach overlay\n }\n }\n this.#exitRowEdit(this.#activeEditRow, true);\n return true;\n }\n }\n\n // Arrow keys in grid mode when not editing input: navigate cells\n if (\n this.#isGridMode &&\n !this.#gridModeInputFocused &&\n (event.key === 'ArrowUp' || event.key === 'ArrowDown' || event.key === 'ArrowLeft' || event.key === 'ArrowRight')\n ) {\n // Let the grid's default keyboard navigation handle this\n return false;\n }\n\n // Arrow Up/Down in grid mode when input is focused: let the editor handle it\n // (e.g., ArrowDown opens autocomplete/datepicker overlays, ArrowUp/Down navigates options)\n if (this.#isGridMode && this.#gridModeInputFocused && (event.key === 'ArrowUp' || event.key === 'ArrowDown')) {\n return true; // Handled: block grid navigation, let event reach editor\n }\n\n // Arrow Up/Down while editing: commit and exit edit mode, move to adjacent row (only in 'row' mode)\n if ((event.key === 'ArrowUp' || event.key === 'ArrowDown') && this.#activeEditRow !== -1 && !this.#isGridMode) {\n // Allow users to prevent row navigation via callback (e.g., when dropdown is open)\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(event);\n if (shouldClose === false) {\n return true; // Handled: block grid navigation, let event reach dropdown\n }\n }\n\n const maxRow = internalGrid._rows.length - 1;\n const currentRow = this.#activeEditRow;\n\n // Commit the current edit\n this.#exitRowEdit(currentRow, false);\n\n // Move focus to adjacent row (same column)\n if (event.key === 'ArrowDown') {\n internalGrid._focusRow = Math.min(maxRow, internalGrid._focusRow + 1);\n } else {\n internalGrid._focusRow = Math.max(0, internalGrid._focusRow - 1);\n }\n\n event.preventDefault();\n // Ensure the focused cell is scrolled into view\n ensureCellVisible(internalGrid);\n // Request render to update focus styling\n this.requestAfterRender();\n return true;\n }\n\n // Tab/Shift+Tab while editing: move to next/prev editable cell\n if (event.key === 'Tab' && (this.#activeEditRow !== -1 || this.#isGridMode)) {\n event.preventDefault();\n\n // In single-cell edit mode (F2), commit and close instead of navigating\n if (this.#singleCellEdit) {\n this.#exitRowEdit(this.#activeEditRow, false);\n return true;\n }\n\n const forward = !event.shiftKey;\n this.#handleTabNavigation(forward);\n return true;\n }\n\n // Space: toggle boolean cells (only when not in edit mode - let editors handle their own space)\n if (event.key === ' ' || event.key === 'Spacebar') {\n // If we're in row edit mode, let the event pass through to the editor (e.g., checkbox)\n if (this.#activeEditRow !== -1) {\n return false;\n }\n\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n if (focusRow >= 0 && focusCol >= 0) {\n const column = internalGrid._visibleColumns[focusCol];\n const rowData = internalGrid._rows[focusRow];\n if (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 (unmodified): start row edit, commit, or enter edit mode in grid mode\n if (event.key === 'Enter' && !event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey) {\n // In grid mode when not editing: focus the current cell's input\n if (this.#isGridMode && !this.#gridModeInputFocused) {\n this.#focusCurrentCellEditor();\n return true;\n }\n\n if (this.#activeEditRow !== -1) {\n // Allow users to prevent edit close via callback (e.g., when overlay is open)\n // This lets Enter select an item in a dropdown instead of committing the row\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(event);\n if (shouldClose === false) {\n return true; // Handled: block grid navigation, let event reach overlay\n }\n }\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 // F2: begin single-cell edit on the focused cell\n if (event.key === 'F2') {\n if (this.#activeEditRow !== -1 || this.#isGridMode) return false;\n\n const editOn = this.config.editOn ?? internalGrid.effectiveConfig?.editOn;\n if (editOn === false) return false;\n\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n if (focusRow >= 0 && focusCol >= 0) {\n const column = internalGrid._visibleColumns[focusCol];\n if (column?.editable && column.field) {\n event.preventDefault();\n this.beginCellEdit(focusRow, column.field);\n return true;\n }\n }\n return false;\n }\n\n // Don't block other keyboard events\n return false;\n }\n\n // #endregion\n\n // #region Render Hooks\n\n /**\n * Process columns to merge type-level editorParams with column-level.\n * Column-level params take precedence.\n * @internal\n */\n override processColumns(columns: ColumnConfig<T>[]): ColumnConfig<T>[] {\n const internalGrid = this.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 // Animate the row after render completes (so the row element exists)\n if (this.#pendingRowAnimation !== -1) {\n const rowIndex = this.#pendingRowAnimation;\n this.#pendingRowAnimation = -1;\n internalGrid.animateRow?.(rowIndex, 'change');\n }\n\n // In 'grid' mode, editors are injected via afterCellRender hook during render\n if (this.#isGridMode) return;\n\n if (this.#editingCells.size === 0) return;\n\n // Re-inject editors for any editing cells that are visible\n for (const cellKey of this.#editingCells) {\n const [rowStr, colStr] = cellKey.split(':');\n const rowIndex = parseInt(rowStr, 10);\n const colIndex = parseInt(colStr, 10);\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) continue;\n\n const cellEl = rowEl.querySelector(`.cell[data-col=\"${colIndex}\"]`) as HTMLElement | null;\n if (!cellEl || cellEl.classList.contains('editing')) continue;\n\n // Cell is visible but not in editing mode - reinject editor\n const rowData = internalGrid._rows[rowIndex];\n const column = internalGrid._visibleColumns[colIndex];\n if (rowData && column) {\n this.#injectEditor(rowData, rowIndex, column, colIndex, cellEl, true);\n }\n }\n }\n\n /**\n * Hook called after each cell is rendered.\n * In grid mode, injects editors into editable cells during render (no DOM queries needed).\n * @internal\n */\n override afterCellRender(context: AfterCellRenderContext): void {\n // Only inject editors in grid mode\n if (!this.#isGridMode) return;\n\n const { row, rowIndex, column, colIndex, cellElement } = context;\n\n // Skip non-editable columns\n if (!column.editable) return;\n\n // Skip if already has editor\n if (cellElement.classList.contains('editing')) return;\n\n // Inject editor (don't track in editingCells - we're always editing in grid mode)\n this.#injectEditor(row as T, rowIndex, column as ColumnConfig<T>, colIndex, cellElement, true);\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 // #region Cell Validation\n\n /**\n * Mark a cell as invalid with an optional validation message.\n * Invalid cells are marked with a `data-invalid` attribute for styling.\n *\n * @param rowId - The row ID (from getRowId)\n * @param field - The field name\n * @param message - Optional validation message (for tooltips or display)\n *\n * @example\n * ```typescript\n * // In cell-commit handler:\n * grid.addEventListener('cell-commit', (e) => {\n * if (e.detail.field === 'email' && !isValidEmail(e.detail.value)) {\n * e.detail.setInvalid('Invalid email format');\n * }\n * });\n *\n * // Or programmatically:\n * editingPlugin.setInvalid('row-123', 'email', 'Invalid email format');\n * ```\n */\n setInvalid(rowId: string, field: string, message = ''): void {\n let rowInvalids = this.#invalidCells.get(rowId);\n if (!rowInvalids) {\n rowInvalids = new Map();\n this.#invalidCells.set(rowId, rowInvalids);\n }\n rowInvalids.set(field, message);\n this.#syncInvalidCellAttribute(rowId, field, true);\n }\n\n /**\n * Clear the invalid state for a specific cell.\n *\n * @param rowId - The row ID (from getRowId)\n * @param field - The field name\n */\n clearInvalid(rowId: string, field: string): void {\n const rowInvalids = this.#invalidCells.get(rowId);\n if (rowInvalids) {\n rowInvalids.delete(field);\n if (rowInvalids.size === 0) {\n this.#invalidCells.delete(rowId);\n }\n }\n this.#syncInvalidCellAttribute(rowId, field, false);\n }\n\n /**\n * Clear all invalid cells for a specific row.\n *\n * @param rowId - The row ID (from getRowId)\n */\n clearRowInvalid(rowId: string): void {\n const rowInvalids = this.#invalidCells.get(rowId);\n if (rowInvalids) {\n const fields = Array.from(rowInvalids.keys());\n this.#invalidCells.delete(rowId);\n fields.forEach((field) => this.#syncInvalidCellAttribute(rowId, field, false));\n }\n }\n\n /**\n * Clear all invalid cell states across all rows.\n */\n clearAllInvalid(): void {\n const entries = Array.from(this.#invalidCells.entries());\n this.#invalidCells.clear();\n entries.forEach(([rowId, fields]) => {\n fields.forEach((_, field) => this.#syncInvalidCellAttribute(rowId, field, false));\n });\n }\n\n /**\n * Check if a specific cell is marked as invalid.\n *\n * @param rowId - The row ID (from getRowId)\n * @param field - The field name\n * @returns True if the cell is marked as invalid\n */\n isCellInvalid(rowId: string, field: string): boolean {\n return this.#invalidCells.get(rowId)?.has(field) ?? false;\n }\n\n /**\n * Get the validation message for an invalid cell.\n *\n * @param rowId - The row ID (from getRowId)\n * @param field - The field name\n * @returns The validation message, or undefined if cell is valid\n */\n getInvalidMessage(rowId: string, field: string): string | undefined {\n return this.#invalidCells.get(rowId)?.get(field);\n }\n\n /**\n * Check if a row has any invalid cells.\n *\n * @param rowId - The row ID (from getRowId)\n * @returns True if the row has at least one invalid cell\n */\n hasInvalidCells(rowId: string): boolean {\n const rowInvalids = this.#invalidCells.get(rowId);\n return rowInvalids ? rowInvalids.size > 0 : false;\n }\n\n /**\n * Get all invalid fields for a row.\n *\n * @param rowId - The row ID (from getRowId)\n * @returns Map of field names to validation messages\n */\n getInvalidFields(rowId: string): Map<string, string> {\n return new Map(this.#invalidCells.get(rowId) ?? []);\n }\n\n /**\n * Sync the data-invalid attribute on a cell element.\n */\n #syncInvalidCellAttribute(rowId: string, field: string, invalid: boolean): 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 || colIndex === undefined) return;\n\n // Find the row element by rowId\n const rows = internalGrid._rows;\n const rowIndex = rows?.findIndex((r) => {\n try {\n return internalGrid.getRowId?.(r) === rowId;\n } catch {\n return false;\n }\n });\n if (rowIndex === -1 || rowIndex === undefined) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${colIndex}\"]`) as HTMLElement | null;\n if (!cellEl) return;\n\n if (invalid) {\n cellEl.setAttribute('data-invalid', 'true');\n const message = this.#invalidCells.get(rowId)?.get(field);\n if (message) {\n cellEl.setAttribute('title', message);\n }\n } else {\n cellEl.removeAttribute('data-invalid');\n cellEl.removeAttribute('title');\n }\n }\n\n // #endregion\n\n /**\n * Reset all change tracking.\n * @param silent - If true, suppresses the `changed-rows-reset` event\n * @fires changed-rows-reset - Emitted when tracking is reset (unless silent)\n */\n resetChangedRows(silent?: boolean): void {\n const rows = this.changedRows;\n const ids = this.changedRowIds;\n this.#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.#singleCellEdit = true;\n this.#beginCellEdit(rowIndex, colIndex, cellEl);\n }\n\n /**\n * Programmatically begin editing all editable cells in a row.\n * @param rowIndex - Index of the row to edit\n * @fires cell-commit - Emitted for each cell value that is committed\n * @fires row-commit - Emitted when focus leaves the row\n */\n beginBulkEdit(rowIndex: number): void {\n const internalGrid = this.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 // Bulk edit clears single-cell mode\n this.#singleCellEdit = false;\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 * Focus the editor input in the currently focused cell (grid mode only).\n * Used when pressing Enter to enter edit mode from navigation mode.\n */\n #focusCurrentCellEditor(): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const focusRow = internalGrid._focusRow;\n const focusCol = internalGrid._focusCol;\n\n if (focusRow < 0 || focusCol < 0) return;\n\n const rowEl = internalGrid.findRenderedRowElement?.(focusRow);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${focusCol}\"]`) as HTMLElement | null;\n\n if (cellEl?.classList.contains('editing')) {\n const editor = cellEl.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n if (editor) {\n this.#gridModeEditLocked = false; // Unlock edit mode - user pressed Enter\n editor.focus();\n this.#gridModeInputFocused = true;\n // Select all text in text inputs for quick replacement\n if (editor instanceof HTMLInputElement && (editor.type === 'text' || editor.type === 'number')) {\n editor.select();\n }\n }\n }\n }\n\n /**\n * Handle Tab/Shift+Tab navigation while editing.\n * Moves to next/previous editable cell, staying in edit mode.\n * Wraps to next/previous row when reaching row boundaries.\n */\n #handleTabNavigation(forward: boolean): void {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n const rows = internalGrid._rows;\n // In grid mode, use focusRow since there's no active edit row\n const currentRow = this.#isGridMode ? internalGrid._focusRow : this.#activeEditRow;\n\n // Get editable column indices\n const editableCols = internalGrid._visibleColumns.map((c, i) => (c.editable ? i : -1)).filter((i) => i >= 0);\n if (editableCols.length === 0) return;\n\n const currentIdx = editableCols.indexOf(internalGrid._focusCol);\n const nextIdx = currentIdx + (forward ? 1 : -1);\n\n // Can move within same row?\n if (nextIdx >= 0 && nextIdx < editableCols.length) {\n internalGrid._focusCol = editableCols[nextIdx];\n const rowEl = internalGrid.findRenderedRowElement?.(currentRow);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${editableCols[nextIdx]}\"]`) as HTMLElement | null;\n if (cellEl?.classList.contains('editing')) {\n const editor = cellEl.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n editor?.focus({ preventScroll: true });\n }\n ensureCellVisible(internalGrid, { forceHorizontalScroll: true });\n return;\n }\n\n // Can move to adjacent row?\n const nextRow = currentRow + (forward ? 1 : -1);\n if (nextRow >= 0 && nextRow < rows.length) {\n // In grid mode, just move focus (all rows are always editable)\n if (this.#isGridMode) {\n internalGrid._focusRow = nextRow;\n internalGrid._focusCol = forward ? editableCols[0] : editableCols[editableCols.length - 1];\n ensureCellVisible(internalGrid, { forceHorizontalScroll: true });\n // Focus the editor in the new cell after render\n this.requestAfterRender();\n setTimeout(() => {\n const rowEl = internalGrid.findRenderedRowElement?.(nextRow);\n const cellEl = rowEl?.querySelector(`.cell[data-col=\"${internalGrid._focusCol}\"]`) as HTMLElement | null;\n if (cellEl?.classList.contains('editing')) {\n const editor = cellEl.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n editor?.focus({ preventScroll: true });\n }\n }, 0);\n } else {\n // In row mode, commit current row and enter next row\n this.#exitRowEdit(currentRow, false);\n internalGrid._focusRow = nextRow;\n internalGrid._focusCol = forward ? editableCols[0] : editableCols[editableCols.length - 1];\n this.beginBulkEdit(nextRow);\n ensureCellVisible(internalGrid, { forceHorizontalScroll: true });\n }\n }\n // else: at boundary - stay put\n }\n\n /**\n * Sync the internal grid state with the plugin's editing state.\n */\n #syncGridEditState(): void {\n const internalGrid = this.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 // Emit edit-open event (row mode only)\n if (!this.#isGridMode) {\n const internalGrid = this.grid as unknown as InternalGrid<T>;\n let rowId = '';\n try {\n rowId = internalGrid.getRowId?.(rowData) ?? '';\n } catch {\n // Row has no ID\n }\n this.emit<EditOpenDetail<T>>('edit-open', {\n rowIndex,\n rowId,\n row: rowData,\n });\n }\n }\n }\n\n /**\n * Exit editing for a row.\n */\n #exitRowEdit(rowIndex: number, revert: boolean): void {\n if (this.#activeEditRow !== rowIndex) return;\n\n const internalGrid = this.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\n // Skip cells with externally-managed editors (framework adapters like Angular/React/Vue).\n // These editors handle their own commits via the commit() callback - we should NOT\n // try to read values from their DOM inputs (which may contain formatted display values).\n if ((cell as HTMLElement).hasAttribute('data-editor-managed')) {\n return;\n }\n\n const input = cell.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (input) {\n const field = col.field as keyof T;\n const originalValue = current[field];\n const val = getInputValue(input, col, originalValue);\n if (originalValue !== val) {\n this.#commitCellValue(rowIndex, col, val, current);\n }\n }\n });\n }\n\n // 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 this.clearRowInvalid(rowId);\n }\n } else if (!revert && current) {\n // Compare snapshot vs current to detect if changes were made during THIS edit session\n const changedThisSession = this.#hasRowChanged(snapshot, current);\n\n // Check if this row has any cumulative changes (via ID tracking)\n // Fall back to session-based detection when no row ID is available\n const changed = rowId ? this.#changedRowIds.has(rowId) : changedThisSession;\n\n // Emit cancelable row-commit event\n const cancelled = this.emitCancelable<RowCommitDetail<T>>('row-commit', {\n rowIndex,\n rowId: rowId ?? '',\n row: current,\n oldValue: snapshot,\n newValue: current,\n changed,\n changedRows: this.changedRows,\n changedRowIds: this.changedRowIds,\n });\n\n // If consumer called preventDefault(), revert the row\n if (cancelled && snapshot) {\n Object.keys(snapshot as object).forEach((k) => {\n (current as Record<string, unknown>)[k] = (snapshot as Record<string, unknown>)[k];\n });\n if (rowId) {\n this.#changedRowIds.delete(rowId);\n this.clearRowInvalid(rowId);\n }\n } else if (!cancelled && changedThisSession && this.isAnimationEnabled) {\n // Animate the row only if changes were made during this edit session\n // (deferred to afterRender so the row element exists after re-render)\n this.#pendingRowAnimation = rowIndex;\n }\n }\n\n // Clear editing state\n this.#rowEditSnapshots.delete(rowIndex);\n this.#activeEditRow = -1;\n this.#activeEditCol = -1;\n this.#singleCellEdit = false;\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 // Remove value-change callbacks for this row\n for (const callbackKey of this.#editorValueCallbacks.keys()) {\n if (callbackKey.startsWith(`${rowIndex}:`)) {\n this.#editorValueCallbacks.delete(callbackKey);\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 // Emit edit-close event (row mode only, fires for both commit and revert)\n if (!this.#isGridMode && current) {\n this.emit<EditCloseDetail<T>>('edit-close', {\n rowIndex,\n rowId: rowId ?? '',\n row: current,\n reverted: revert,\n });\n }\n }\n\n /**\n * Commit a single cell value change.\n * Uses ID-based change tracking for stability when rows are reordered.\n */\n #commitCellValue(rowIndex: number, column: ColumnConfig<T>, newValue: unknown, rowData: T): void {\n const field = column.field;\n if (!isSafePropertyKey(field)) return;\n const oldValue = (rowData as Record<string, unknown>)[field];\n if (oldValue === newValue) return;\n\n const internalGrid = this.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 // Track whether setInvalid was called during event handling\n let invalidWasSet = false;\n\n // Create setInvalid callback for validation (noop if row has no ID)\n const setInvalid = rowId\n ? (message?: string) => {\n invalidWasSet = true;\n this.setInvalid(rowId!, field, message ?? '');\n }\n : () => {}; // eslint-disable-line @typescript-eslint/no-empty-function\n\n // Emit cancelable event BEFORE applying the value\n const cancelled = this.emitCancelable<CellCommitDetail<T>>('cell-commit', {\n row: rowData,\n rowId: rowId ?? '',\n field,\n oldValue,\n value: newValue,\n rowIndex,\n changedRows: this.changedRows,\n changedRowIds: this.changedRowIds,\n firstTimeForRow: firstTime,\n updateRow,\n setInvalid,\n });\n\n // If consumer called preventDefault(), abort the commit\n if (cancelled) return;\n\n // Clear any previous invalid state for this cell ONLY if setInvalid wasn't called\n // (if setInvalid was called, the handler wants it to remain invalid)\n if (rowId && !invalidWasSet && this.isCellInvalid(rowId, field)) {\n this.clearInvalid(rowId, field);\n }\n\n // Apply the value and mark row as changed\n (rowData as Record<string, unknown>)[field] = newValue;\n if (rowId) {\n this.#changedRowIds.add(rowId);\n }\n this.#syncGridEditState();\n\n // Notify other plugins (e.g., UndoRedoPlugin) about the committed edit\n this.emitPluginEvent('cell-edit-committed', {\n rowIndex,\n field,\n oldValue,\n newValue,\n });\n\n // Mark the row visually as changed (animation happens when row edit closes)\n const rowEl = internalGrid.findRenderedRowElement?.(rowIndex);\n if (rowEl) {\n rowEl.classList.add('changed');\n }\n }\n\n /**\n * Inject an editor into a cell.\n */\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 // In grid mode, always allow commits (we're always editing)\n // In row mode, only allow commits if we're in an active edit session\n if (editFinalized || (!this.#isGridMode && this.#activeEditRow === -1)) return;\n // Resolve rowData fresh from the grid's current rows array.\n // The captured `rowData` may be stale if grid.rows was re-set (e.g. by\n // a FormArray valueChanges subscription pushing getRawValue() which\n // creates new object references). Using the live row ensures the\n // committed value lands on the correct object.\n const currentRowData = ((this.grid as unknown as InternalGrid<T>)._rows[rowIndex] as T) ?? rowData;\n this.#commitCellValue(rowIndex, column, newValue, currentRowData);\n };\n const cancel = () => {\n editFinalized = true;\n if (isSafePropertyKey(column.field)) {\n // Use fresh row data to restore the original value, same rationale as commit\n const currentRowData = ((this.grid as unknown as InternalGrid<T>)._rows[rowIndex] as T) ?? rowData;\n (currentRowData as Record<string, unknown>)[column.field] = originalValue;\n }\n };\n\n const editorHost = document.createElement('div');\n editorHost.className = 'tbw-editor-host';\n cell.innerHTML = '';\n cell.appendChild(editorHost);\n\n // Keydown handler for Enter/Escape\n editorHost.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Enter') {\n // In grid mode, Enter just commits without exiting\n if (this.#isGridMode) {\n e.stopPropagation();\n e.preventDefault();\n // Get current value and commit\n const input = editorHost.querySelector('input,textarea,select') as\n | HTMLInputElement\n | HTMLTextAreaElement\n | HTMLSelectElement\n | null;\n if (input) {\n commit(getInputValue(input, column as ColumnConfig<unknown>, originalValue));\n }\n return;\n }\n // Allow users to prevent edit close via callback (e.g., when overlay is open)\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(e);\n if (shouldClose === false) {\n return; // Let the event propagate to overlay\n }\n }\n e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n this.#exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n // In grid mode, Escape doesn't exit edit mode\n if (this.#isGridMode) {\n e.stopPropagation();\n e.preventDefault();\n return;\n }\n // Allow users to prevent edit close via callback (e.g., when overlay is open)\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(e);\n if (shouldClose === false) {\n return; // Let the event propagate to overlay\n }\n }\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 // Value-change callback registration.\n // Editors call onValueChange(cb) to receive pushes when the underlying row\n // is mutated externally (e.g., via updateRow from another cell's commit).\n // Multiple callbacks can be registered (user + auto-wire).\n const callbackKey = `${rowIndex}:${column.field}`;\n const callbacks: Array<(newValue: unknown) => void> = [];\n this.#editorValueCallbacks.set(callbackKey, (newVal) => {\n for (const cb of callbacks) cb(newVal);\n });\n const onValueChange = (cb: (newValue: unknown) => void) => {\n callbacks.push(cb);\n };\n\n if (editorSpec === 'template' && tplHolder) {\n this.#renderTemplateEditor(editorHost, colInternal, rowData, originalValue, commit, cancel, skipFocus, rowIndex);\n // Auto-update built-in template editors when value changes externally\n onValueChange((newVal) => {\n const input = editorHost.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(\n 'input,textarea,select',\n );\n if (input) {\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.checked = !!newVal;\n } else {\n input.value = String(newVal ?? '');\n }\n }\n });\n } else if (typeof editorSpec === 'string') {\n const el = document.createElement(editorSpec) as HTMLElement & { value?: unknown };\n el.value = value;\n el.addEventListener('change', () => commit(el.value));\n // Auto-update custom element editors when value changes externally\n onValueChange((newVal) => {\n el.value = newVal;\n });\n editorHost.appendChild(el);\n if (!skipFocus) {\n queueMicrotask(() => {\n const focusable = editorHost.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n focusable?.focus({ preventScroll: true });\n });\n }\n } else if (typeof editorSpec === 'function') {\n const ctx: EditorContext<T> = {\n row: rowData,\n rowId: rowId ?? '',\n value,\n field: column.field,\n column,\n commit,\n cancel,\n updateRow,\n onValueChange,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const produced = (editorSpec as any)(ctx);\n if (typeof produced === 'string') {\n editorHost.innerHTML = produced;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n wireEditorInputs(editorHost, column as any, commit, originalValue);\n // Auto-update wired inputs when value changes externally\n onValueChange((newVal) => {\n const input = editorHost.querySelector<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>(\n 'input,textarea,select',\n );\n if (input) {\n if (input instanceof HTMLInputElement && input.type === 'checkbox') {\n input.checked = !!newVal;\n } else {\n input.value = String(newVal ?? '');\n }\n }\n });\n } else if (produced instanceof Node) {\n editorHost.appendChild(produced);\n const isSimpleInput =\n produced instanceof HTMLInputElement ||\n produced instanceof HTMLSelectElement ||\n produced instanceof HTMLTextAreaElement;\n if (!isSimpleInput) {\n cell.setAttribute('data-editor-managed', '');\n } else {\n // Auto-update simple inputs returned by factory functions\n onValueChange((newVal) => {\n if (produced instanceof HTMLInputElement && produced.type === 'checkbox') {\n produced.checked = !!newVal;\n } else {\n (produced as HTMLInputElement).value = String(newVal ?? '');\n }\n });\n }\n }\n if (!skipFocus) {\n queueMicrotask(() => {\n const focusable = editorHost.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n focusable?.focus({ preventScroll: true });\n });\n }\n } else if (editorSpec && typeof editorSpec === 'object') {\n const placeholder = document.createElement('div');\n placeholder.setAttribute('data-external-editor', '');\n placeholder.setAttribute('data-field', column.field);\n editorHost.appendChild(placeholder);\n cell.setAttribute('data-editor-managed', '');\n const context: EditorContext<T> = {\n row: rowData,\n rowId: rowId ?? '',\n value,\n field: column.field,\n column,\n commit,\n cancel,\n updateRow,\n onValueChange,\n };\n if (editorSpec.mount) {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n editorSpec.mount({ placeholder, context: context as any, spec: editorSpec });\n } catch (e) {\n 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, originalValue));\n });\n input.addEventListener('keydown', (evt) => {\n const e = evt as KeyboardEvent;\n if (e.key === 'Enter') {\n // Allow users to prevent edit close via callback (e.g., when overlay is open)\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(e);\n if (shouldClose === false) {\n return; // Let the event propagate to overlay\n }\n }\n e.stopPropagation();\n e.preventDefault();\n editFinalized = true;\n commit(getInputValue(input, column, originalValue));\n this.#exitRowEdit(rowIndex, false);\n }\n if (e.key === 'Escape') {\n // Allow users to prevent edit close via callback (e.g., when overlay is open)\n if (this.config.onBeforeEditClose) {\n const shouldClose = this.config.onBeforeEditClose(e);\n if (shouldClose === false) {\n return; // Let the event propagate to overlay\n }\n }\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 * Compare snapshot vs current row to detect if any values changed during this edit session.\n * Uses shallow comparison of all properties.\n */\n #hasRowChanged(snapshot: T | undefined, current: T): boolean {\n if (!snapshot) return false;\n\n const snapshotObj = snapshot as Record<string, unknown>;\n const currentObj = current as Record<string, unknown>;\n\n // Check all keys in both objects\n const allKeys = new Set([...Object.keys(snapshotObj), ...Object.keys(currentObj)]);\n for (const key of allKeys) {\n if (snapshotObj[key] !== currentObj[key]) {\n return true;\n }\n }\n return false;\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","/**\n * CSV Export Utilities\n *\n * Functions for building and downloading CSV content.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ExportParams } from './types';\n\n/** CSV export options */\nexport interface CsvOptions {\n /** Field delimiter (default: ',') */\n delimiter?: string;\n /** Line separator (default: '\\n') */\n newline?: string;\n /** Whether to quote strings containing special characters (default: true) */\n quoteStrings?: boolean;\n /** Add UTF-8 BOM for Excel compatibility (default: false) */\n bom?: boolean;\n}\n\n/**\n * Format a value for CSV output.\n * Handles null, Date, objects, and strings with special characters.\n */\nexport function formatCsvValue(value: any, quote = true): string {\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'object') return JSON.stringify(value);\n\n const str = String(value);\n\n // Quote if contains special characters (comma, quote, newline)\n if (quote && (str.includes(',') || str.includes('\"') || str.includes('\\n') || str.includes('\\r'))) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n\n return str;\n}\n\n/**\n * Build CSV content from rows and columns.\n */\nexport function buildCsv(rows: any[], columns: ColumnConfig[], params: ExportParams, options: CsvOptions = {}): string {\n const delimiter = options.delimiter ?? ',';\n const newline = options.newline ?? '\\n';\n const lines: string[] = [];\n\n // UTF-8 BOM for Excel compatibility\n const bom = options.bom ? '\\uFEFF' : '';\n\n // Build header row\n if (params.includeHeaders !== false) {\n const headerRow = columns.map((col) => {\n const header = col.header || col.field;\n const processed = params.processHeader ? params.processHeader(header, col.field) : header;\n return formatCsvValue(processed);\n });\n lines.push(headerRow.join(delimiter));\n }\n\n // Build data rows\n for (const row of rows) {\n const cells = columns.map((col) => {\n let value = row[col.field];\n if (params.processCell) {\n value = params.processCell(value, col.field, row);\n }\n return formatCsvValue(value);\n });\n lines.push(cells.join(delimiter));\n }\n\n return bom + lines.join(newline);\n}\n\n/**\n * Download a Blob as a file.\n */\nexport function downloadBlob(blob: Blob, fileName: string): void {\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = fileName;\n link.style.display = 'none';\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n}\n\n/**\n * Download CSV content as a file.\n */\nexport function downloadCsv(content: string, fileName: string): void {\n const blob = new Blob([content], { type: 'text/csv;charset=utf-8;' });\n downloadBlob(blob, fileName);\n}\n","/**\n * Excel Export Utilities\n *\n * Simple Excel XML format export (no external dependencies).\n * Produces XML Spreadsheet 2003 format which opens in Excel.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ExportParams } from './types';\nimport { downloadBlob } from './csv';\n\n/**\n * Escape XML special characters.\n */\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Build Excel XML content from rows and columns.\n * Uses XML Spreadsheet 2003 format for broad compatibility.\n */\nexport function buildExcelXml(rows: any[], columns: ColumnConfig[], params: ExportParams): string {\n let xml = `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<?mso-application progid=\"Excel.Sheet\"?>\n<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"\n xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\">\n<Worksheet ss:Name=\"Sheet1\">\n<Table>`;\n\n // Build header row\n if (params.includeHeaders !== false) {\n xml += '\\n<Row>';\n for (const col of columns) {\n const header = col.header || col.field;\n const processed = params.processHeader ? params.processHeader(header, col.field) : header;\n xml += `<Cell><Data ss:Type=\"String\">${escapeXml(processed)}</Data></Cell>`;\n }\n xml += '</Row>';\n }\n\n // Build data rows\n for (const row of rows) {\n xml += '\\n<Row>';\n for (const col of columns) {\n let value = row[col.field];\n if (params.processCell) {\n value = params.processCell(value, col.field, row);\n }\n\n // Determine cell type based on value\n let type: 'Number' | 'String' | 'DateTime' = 'String';\n let displayValue = '';\n\n if (value == null) {\n displayValue = '';\n } else if (typeof value === 'number' && !isNaN(value)) {\n type = 'Number';\n displayValue = String(value);\n } else if (value instanceof Date) {\n type = 'DateTime';\n displayValue = value.toISOString();\n } else {\n displayValue = escapeXml(String(value));\n }\n\n xml += `<Cell><Data ss:Type=\"${type}\">${displayValue}</Data></Cell>`;\n }\n xml += '</Row>';\n }\n\n xml += '\\n</Table>\\n</Worksheet>\\n</Workbook>';\n return xml;\n}\n\n/**\n * Download Excel XML content as a file.\n */\nexport function downloadExcel(content: string, fileName: string): void {\n const finalName = fileName.endsWith('.xls') ? fileName : `${fileName}.xls`;\n const blob = new Blob([content], {\n type: 'application/vnd.ms-excel;charset=utf-8;',\n });\n downloadBlob(blob, finalName);\n}\n","/**\n * Export Plugin (Class-based)\n *\n * Provides data export functionality for tbw-grid.\n * Supports CSV, Excel (XML), and JSON formats.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport { resolveColumns, resolveRows } from '../shared/data-collection';\nimport { buildCsv, downloadBlob, downloadCsv } from './csv';\nimport { buildExcelXml, downloadExcel } from './excel';\nimport type { ExportCompleteDetail, ExportConfig, ExportFormat, ExportParams } from './types';\n\n/** Selection plugin state interface for type safety */\ninterface SelectionPluginState {\n selected: Set<number>;\n}\n\n/**\n * Export Plugin for tbw-grid\n *\n * Lets users download grid data as CSV, Excel (XML), or JSON with a single click\n * or API call. Great for reporting, data backup, or letting users work with data\n * in Excel. Integrates with SelectionPlugin to export only selected rows.\n *\n * ## Installation\n *\n * ```ts\n * import { ExportPlugin } from '@toolbox-web/grid/plugins/export';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `fileName` | `string` | `'export'` | Base filename (without extension) |\n * | `includeHeaders` | `boolean` | `true` | Include column headers in export |\n * | `onlyVisible` | `boolean` | `true` | Export only visible columns |\n * | `onlySelected` | `boolean` | `false` | Export only selected rows (requires SelectionPlugin) |\n *\n * ## Supported Formats\n *\n * | Format | Method | Description |\n * |--------|--------|-------------|\n * | CSV | `exportToCSV()` | Comma-separated values |\n * | Excel | `exportToExcel()` | Excel XML format (.xlsx) |\n * | JSON | `exportToJSON()` | JSON array of objects |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `exportToCSV` | `(params?) => void` | Export as CSV file |\n * | `exportToExcel` | `(params?) => void` | Export as Excel file |\n * | `exportToJSON` | `(params?) => void` | Export as JSON file |\n * | `isExporting` | `() => boolean` | Check if export is in progress |\n *\n * @example Basic Export with Button\n * ```ts\n * import '@toolbox-web/grid';\n * import { ExportPlugin } from '@toolbox-web/grid/plugins/export';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * ],\n * plugins: [new ExportPlugin({ fileName: 'employees', includeHeaders: true })],\n * };\n *\n * // Trigger export via button\n * document.getElementById('export-btn').addEventListener('click', () => {\n * grid.getPlugin(ExportPlugin).exportToCSV();\n * });\n * ```\n *\n * @example Export Selected Rows Only\n * ```ts\n * import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';\n *\n * grid.gridConfig = {\n * plugins: [\n * new SelectionPlugin({ mode: 'row' }),\n * new ExportPlugin({ onlySelected: true }),\n * ],\n * };\n * ```\n *\n * @see {@link ExportConfig} for all configuration options\n * @see {@link ExportParams} for method parameters\n * @see {@link SelectionPlugin} for exporting selected rows\n *\n * @internal Extends BaseGridPlugin\n */\nexport class ExportPlugin extends BaseGridPlugin<ExportConfig> {\n /** @internal */\n readonly name = 'export';\n\n /** @internal */\n protected override get defaultConfig(): Partial<ExportConfig> {\n return {\n fileName: 'export',\n includeHeaders: true,\n onlyVisible: true,\n onlySelected: false,\n };\n }\n\n // #region Internal State\n private isExportingFlag = false;\n private lastExportInfo: { format: ExportFormat; timestamp: Date } | null = null;\n // #endregion\n\n // #region Private Methods\n\n private performExport(format: ExportFormat, params?: Partial<ExportParams>): void {\n const config = this.config;\n\n // Build full params with defaults\n const fullParams: ExportParams = {\n format,\n fileName: params?.fileName ?? config.fileName ?? 'export',\n includeHeaders: params?.includeHeaders ?? config.includeHeaders,\n processCell: params?.processCell,\n processHeader: params?.processHeader,\n columns: params?.columns,\n rowIndices: params?.rowIndices,\n };\n\n // Get columns to export (shared utility handles hidden/utility filtering)\n const columns = resolveColumns(this.columns, params?.columns, config.onlyVisible) as ColumnConfig[];\n\n // Get rows to export\n let rows: Record<string, unknown>[];\n if (params?.rowIndices) {\n rows = resolveRows(this.rows as Record<string, unknown>[], params.rowIndices);\n } else if (config.onlySelected) {\n const selectionState = this.getSelectionState();\n if (selectionState?.selected?.size) {\n rows = resolveRows(this.rows as Record<string, unknown>[], [...selectionState.selected]);\n } else {\n rows = [...this.rows] as Record<string, unknown>[];\n }\n } else {\n rows = [...this.rows] as Record<string, unknown>[];\n }\n\n this.isExportingFlag = true;\n let fileName = fullParams.fileName!;\n\n try {\n switch (format) {\n case 'csv': {\n const content = buildCsv(rows, columns, fullParams, { bom: true });\n fileName = fileName.endsWith('.csv') ? fileName : `${fileName}.csv`;\n downloadCsv(content, fileName);\n break;\n }\n\n case 'excel': {\n const content = buildExcelXml(rows, columns, fullParams);\n fileName = fileName.endsWith('.xls') ? fileName : `${fileName}.xls`;\n downloadExcel(content, fileName);\n break;\n }\n\n case 'json': {\n const jsonData = rows.map((row) => {\n const obj: Record<string, any> = {};\n for (const col of columns) {\n let value = row[col.field];\n if (fullParams.processCell) {\n value = fullParams.processCell(value, col.field, row);\n }\n obj[col.field] = value;\n }\n return obj;\n });\n const content = JSON.stringify(jsonData, null, 2);\n fileName = fileName.endsWith('.json') ? fileName : `${fileName}.json`;\n const blob = new Blob([content], { type: 'application/json' });\n downloadBlob(blob, fileName);\n break;\n }\n }\n\n this.lastExportInfo = { format, timestamp: new Date() };\n\n this.emit<ExportCompleteDetail>('export-complete', {\n format,\n fileName,\n rowCount: rows.length,\n columnCount: columns.length,\n });\n } finally {\n this.isExportingFlag = false;\n }\n }\n\n private getSelectionState(): SelectionPluginState | null {\n try {\n return (this.grid?.getPluginState?.('selection') as SelectionPluginState | null) ?? null;\n } catch {\n return null;\n }\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Export data to CSV format.\n * @param params - Optional export parameters\n */\n exportCsv(params?: Partial<ExportParams>): void {\n this.performExport('csv', params);\n }\n\n /**\n * Export data to Excel format (XML Spreadsheet).\n * @param params - Optional export parameters\n */\n exportExcel(params?: Partial<ExportParams>): void {\n this.performExport('excel', params);\n }\n\n /**\n * Export data to JSON format.\n * @param params - Optional export parameters\n */\n exportJson(params?: Partial<ExportParams>): void {\n this.performExport('json', params);\n }\n\n /**\n * Check if an export is currently in progress.\n * @returns Whether export is in progress\n */\n isExporting(): boolean {\n return this.isExportingFlag;\n }\n\n /**\n * Get information about the last export.\n * @returns Export info or null if no export has occurred\n */\n getLastExport(): { format: ExportFormat; timestamp: Date } | null {\n return this.lastExportInfo;\n }\n // #endregion\n}\n","/**\n * Shared Expander Column Utilities\n *\n * Provides a fixed expander column for plugins that need expand/collapse icons\n * (MasterDetail, Tree, RowGrouping). The column is:\n * - Always first in the grid\n * - Cannot be reordered (lockPosition: true)\n * - Has no header (empty string)\n * - Has no right border (borderless styling)\n * - Narrow width (just fits the icon)\n */\n\nimport type { ColumnConfig } from '../types';\n\n/** Special field name for the expander column */\nexport const EXPANDER_COLUMN_FIELD = '__tbw_expander';\n\n/** Default width for the expander column (pixels) */\nexport const EXPANDER_COLUMN_WIDTH = 32;\n\n/**\n * Marker interface for expander column renderers.\n * Used to detect if expander column is already present.\n */\nexport interface ExpanderColumnRenderer {\n (ctx: any): HTMLElement;\n __expanderColumn?: true;\n /** Plugin name that created this expander */\n __expanderPlugin?: string;\n}\n\n/**\n * Check if a column is an expander column.\n */\nexport function isExpanderColumn(column: ColumnConfig): boolean {\n return column.field === EXPANDER_COLUMN_FIELD;\n}\n\n/**\n * Check if a column is a utility column (excluded from selection, clipboard, etc.).\n * Utility columns are non-data columns like expander columns.\n */\nexport function isUtilityColumn(column: ColumnConfig): boolean {\n return column.meta?.utility === true;\n}\n\n/**\n * Find an existing expander column in the column array.\n */\nexport function findExpanderColumn(columns: readonly ColumnConfig[]): ColumnConfig | undefined {\n return columns.find(isExpanderColumn);\n}\n\n/**\n * Create the base expander column config.\n * Plugins should add their own renderer to customize the expand icon behavior.\n *\n * @param pluginName - Name of the plugin creating the expander (for debugging)\n * @returns Base column config for the expander column\n */\nexport function createExpanderColumnConfig(pluginName: string): ColumnConfig {\n return {\n field: EXPANDER_COLUMN_FIELD as any,\n header: '', // No header text - visually merges with next column\n width: EXPANDER_COLUMN_WIDTH,\n resizable: false,\n sortable: false,\n filterable: false, // No filter button for expander column\n meta: {\n lockPosition: true,\n suppressMovable: true,\n expanderColumn: true,\n expanderPlugin: pluginName,\n utility: true, // Marks this as a utility column (excluded from selection, clipboard, etc.)\n },\n };\n}\n\n/**\n * Create a container element for expand/collapse toggle icons.\n * Used by plugins to wrap their expand icons with consistent styling.\n *\n * @param expanded - Whether the item is currently expanded\n * @param pluginClass - CSS class prefix for the plugin (e.g., 'master-detail', 'tree')\n * @returns Container span element\n */\nexport function createExpanderContainer(expanded: boolean, pluginClass: string): HTMLSpanElement {\n const container = document.createElement('span');\n container.className = `${pluginClass}-expander expander-cell`;\n if (expanded) {\n container.classList.add('expanded');\n }\n return container;\n}\n\n/**\n * CSS styles for the expander column.\n * Plugins should include this in their styles to ensure consistent appearance.\n */\nexport const EXPANDER_COLUMN_STYLES = `\n/* Expander column data cells - always first, borderless right edge */\n.cell[data-field=\"${EXPANDER_COLUMN_FIELD}\"] {\n border-right: none !important;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n/* Expander column header - completely hidden, no content, no border, no width contribution */\n.header-row .cell[data-field=\"${EXPANDER_COLUMN_FIELD}\"] {\n visibility: hidden;\n border: none !important;\n padding: 0;\n overflow: hidden;\n}\n\n/* The column after the expander should visually extend into the expander header space */\n.header-row .cell[data-field=\"${EXPANDER_COLUMN_FIELD}\"] + .cell {\n /* Pull left to cover the hidden expander header */\n margin-left: -${EXPANDER_COLUMN_WIDTH}px;\n padding-left: calc(var(--tbw-cell-padding, 8px) + ${EXPANDER_COLUMN_WIDTH}px);\n}\n\n/* Expander cell contents */\n.expander-cell {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n cursor: pointer;\n}\n`;\n","/**\n * Filter Model Core Logic\n *\n * Pure functions for filtering operations.\n */\n\nimport type { FilterModel } from './types';\n\n/**\n * Convert a value to a comparable number.\n * Handles Date objects, numeric values, and date/ISO strings.\n */\nfunction toNumeric(value: unknown): number {\n if (value instanceof Date) return value.getTime();\n const n = Number(value);\n if (!isNaN(n)) return n;\n // Try parsing as a date string (ISO 8601, etc.)\n const d = new Date(value as string);\n return d.getTime(); // NaN if unparseable\n}\n\n/**\n * Check if a single row matches a filter condition.\n *\n * @param row - The row data object\n * @param filter - The filter to apply\n * @param caseSensitive - Whether text comparisons are case sensitive\n * @returns True if the row matches the filter\n */\nexport function matchesFilter(row: Record<string, unknown>, filter: FilterModel, caseSensitive = false): boolean {\n const rawValue = row[filter.field];\n\n // Handle blank/notBlank first - these work on null/undefined/empty\n if (filter.operator === 'blank') {\n return rawValue == null || rawValue === '';\n }\n if (filter.operator === 'notBlank') {\n return rawValue != null && rawValue !== '';\n }\n\n // Set operators handle null explicitly: null is never \"in\" a set,\n // and null is never excluded by \"notIn\" (it's not a listed value).\n if (filter.operator === 'notIn') {\n if (rawValue == null) return true;\n return Array.isArray(filter.value) && !filter.value.includes(rawValue);\n }\n if (filter.operator === 'in') {\n return Array.isArray(filter.value) && filter.value.includes(rawValue);\n }\n\n // Null/undefined values don't match other filters\n if (rawValue == null) return false;\n\n // Prepare values for comparison\n const stringValue = String(rawValue);\n const compareValue = caseSensitive ? stringValue : stringValue.toLowerCase();\n const filterValue = caseSensitive ? String(filter.value) : String(filter.value).toLowerCase();\n\n switch (filter.operator) {\n // Text operators\n case 'contains':\n return compareValue.includes(filterValue);\n\n case 'notContains':\n return !compareValue.includes(filterValue);\n\n case 'equals':\n return compareValue === filterValue;\n\n case 'notEquals':\n return compareValue !== filterValue;\n\n case 'startsWith':\n return compareValue.startsWith(filterValue);\n\n case 'endsWith':\n return compareValue.endsWith(filterValue);\n\n // Number/Date operators (use toNumeric for Date objects and date strings)\n case 'lessThan':\n return toNumeric(rawValue) < toNumeric(filter.value);\n\n case 'lessThanOrEqual':\n return toNumeric(rawValue) <= toNumeric(filter.value);\n\n case 'greaterThan':\n return toNumeric(rawValue) > toNumeric(filter.value);\n\n case 'greaterThanOrEqual':\n return toNumeric(rawValue) >= toNumeric(filter.value);\n\n case 'between':\n return toNumeric(rawValue) >= toNumeric(filter.value) && toNumeric(rawValue) <= toNumeric(filter.valueTo);\n\n default:\n return true;\n }\n}\n\n/**\n * Filter rows based on multiple filter conditions (AND logic).\n * All filters must match for a row to be included.\n *\n * @param rows - The rows to filter\n * @param filters - Array of filters to apply\n * @param caseSensitive - Whether text comparisons are case sensitive\n * @returns Filtered rows\n */\nexport function filterRows<T extends Record<string, unknown>>(\n rows: T[],\n filters: FilterModel[],\n caseSensitive = false,\n): T[] {\n if (!filters.length) return rows;\n return rows.filter((row) => filters.every((f) => matchesFilter(row, f, caseSensitive)));\n}\n\n/**\n * Compute a cache key for a set of filters.\n * Used for memoization of filter results.\n *\n * @param filters - Array of filters\n * @returns Stable string key for the filter set\n */\nexport function computeFilterCacheKey(filters: FilterModel[]): string {\n return JSON.stringify(\n filters.map((f) => ({\n field: f.field,\n operator: f.operator,\n value: f.value,\n valueTo: f.valueTo,\n })),\n );\n}\n\n/**\n * Extract unique values from a field across all rows.\n * Useful for populating \"set\" filter dropdowns.\n *\n * @param rows - The rows to extract values from\n * @param field - The field name\n * @returns Sorted array of unique non-null values\n */\nexport function getUniqueValues<T extends Record<string, unknown>>(rows: T[], field: string): unknown[] {\n const values = new Set<unknown>();\n for (const row of rows) {\n const value = row[field];\n if (value != null) {\n values.add(value);\n }\n }\n return [...values].sort((a, b) => {\n // Handle mixed types gracefully\n if (typeof a === 'number' && typeof b === 'number') {\n return a - b;\n }\n return String(a).localeCompare(String(b));\n });\n}\n","/**\n * Filtering Plugin (Class-based)\n *\n * Provides comprehensive filtering functionality for tbw-grid.\n * Supports text, number, date, set, and boolean filters with caching.\n * Includes UI with filter buttons in headers and dropdown filter panels.\n */\n\nimport { computeVirtualWindow, shouldBypassVirtualization } from '../../core/internal/virtualization';\nimport { BaseGridPlugin, type GridElement, type PluginManifest, type PluginQuery } from '../../core/plugin/base-plugin';\nimport { isUtilityColumn } from '../../core/plugin/expander-column';\nimport type { ColumnConfig, ColumnState } from '../../core/types';\nimport type { ContextMenuParams, HeaderContextMenuItem } from '../context-menu/types';\nimport { computeFilterCacheKey, filterRows, getUniqueValues } from './filter-model';\nimport styles from './filtering.css?inline';\nimport filterPanelStyles from './FilteringPlugin.css?inline';\nimport type { FilterChangeDetail, FilterConfig, FilterModel, FilterPanelParams } from './types';\n\n/**\n * Filtering Plugin for tbw-grid\n *\n * Adds column header filters with text search, dropdown options, and custom filter panels.\n * Supports both **local filtering** for small datasets and **async handlers** for server-side\n * filtering on large datasets.\n *\n * ## Installation\n *\n * ```ts\n * import { FilteringPlugin } from '@toolbox-web/grid/plugins/filtering';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `debounceMs` | `number` | `300` | Debounce delay for filter input |\n * | `caseSensitive` | `boolean` | `false` | Case-sensitive string matching |\n * | `trimInput` | `boolean` | `true` | Trim whitespace from filter input |\n * | `useWorker` | `boolean` | `true` | Use Web Worker for datasets >1000 rows |\n * | `filterPanelRenderer` | `FilterPanelRenderer` | - | Custom filter panel renderer |\n * | `valuesHandler` | `FilterValuesHandler` | - | Async handler to fetch unique filter values |\n * | `filterHandler` | `FilterHandler<TRow>` | - | Async handler to apply filters remotely |\n *\n * ## Column Configuration\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `filterable` | `boolean` | Enable filtering for this column |\n * | `filterType` | `'text' \\| 'select' \\| 'number' \\| 'date'` | Filter UI type |\n * | `filterOptions` | `unknown[]` | Predefined options for select filters |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `setFilter` | `(field, value) => void` | Set filter value for a column |\n * | `getFilters` | `() => FilterModel[]` | Get all current filters |\n * | `clearFilters` | `() => void` | Clear all filters |\n * | `clearFilter` | `(field) => void` | Clear filter for a specific column |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-filter-panel-bg` | `var(--tbw-color-panel-bg)` | Panel background |\n * | `--tbw-filter-panel-fg` | `var(--tbw-color-fg)` | Panel text color |\n * | `--tbw-filter-panel-border` | `var(--tbw-color-border)` | Panel border |\n * | `--tbw-filter-active-color` | `var(--tbw-color-accent)` | Active filter indicator |\n * | `--tbw-filter-input-bg` | `var(--tbw-color-bg)` | Input background |\n * | `--tbw-filter-input-focus` | `var(--tbw-color-accent)` | Input focus border |\n *\n * @example Basic Usage with Filterable Columns\n * ```ts\n * import '@toolbox-web/grid';\n * import { FilteringPlugin } from '@toolbox-web/grid/plugins/filtering';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name', filterable: true },\n * { field: 'status', header: 'Status', filterable: true, filterType: 'select' },\n * { field: 'email', header: 'Email', filterable: true },\n * ],\n * plugins: [new FilteringPlugin({ debounceMs: 300 })],\n * };\n * grid.rows = data;\n * ```\n *\n * @example Server-Side Filtering with Async Handlers\n * ```ts\n * new FilteringPlugin({\n * // Fetch unique values from server for filter dropdown\n * valuesHandler: async (field, column) => {\n * const response = await fetch(`/api/distinct-values?field=${field}`);\n * return response.json();\n * },\n * // Apply filters on the server\n * filterHandler: async (filters, currentRows) => {\n * const response = await fetch('/api/data', {\n * method: 'POST',\n * body: JSON.stringify({ filters }),\n * });\n * return response.json();\n * },\n * });\n * ```\n *\n * @see {@link FilterConfig} for all configuration options\n * @see {@link FilterModel} for filter data structure\n * @see {@link FilterPanelParams} for custom panel renderer parameters\n *\n * @internal Extends BaseGridPlugin\n */\nexport class FilteringPlugin extends BaseGridPlugin<FilterConfig> {\n /**\n * Plugin manifest - declares events emitted by this plugin.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n events: [\n {\n type: 'filter-applied',\n description: 'Emitted when filter criteria change. Subscribers can react to row visibility changes.',\n },\n ],\n queries: [\n {\n type: 'getContextMenuItems',\n description: 'Contributes filter-related items to the header context menu',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'filtering';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<FilterConfig> {\n return {\n debounceMs: 300,\n caseSensitive: false,\n trimInput: true,\n useWorker: true,\n };\n }\n\n // #region Helpers\n\n /**\n * Check if filtering is enabled at the grid level.\n * Grid-wide `filterable: false` disables filtering for all columns.\n */\n private isFilteringEnabled(): boolean {\n return this.grid.effectiveConfig?.filterable !== false;\n }\n\n /**\n * Check if a specific column is filterable, respecting both grid-level and column-level settings.\n */\n private isColumnFilterable(col: { filterable?: boolean; field?: string }): boolean {\n if (!this.isFilteringEnabled()) return false;\n return col.filterable !== false;\n }\n\n // #endregion\n\n // #region Internal State\n private filters: Map<string, FilterModel> = new Map();\n private cachedResult: unknown[] | null = null;\n private cacheKey: string | null = null;\n /** Spot-check of input rows for cache invalidation when upstream plugins (e.g. sort) change row order */\n private cachedInputSpot: { len: number; first: unknown; mid: unknown; last: unknown } | null = null;\n private openPanelField: string | null = null;\n private panelElement: HTMLElement | null = null;\n private panelAnchorElement: HTMLElement | null = null; // For CSS anchor positioning cleanup\n private searchText: Map<string, string> = new Map();\n private excludedValues: Map<string, Set<unknown>> = new Map();\n private panelAbortController: AbortController | null = null; // For panel-scoped listeners\n private globalStylesInjected = false;\n\n // Virtualization constants for filter value list\n private static readonly DEFAULT_LIST_ITEM_HEIGHT = 28;\n private static readonly LIST_OVERSCAN = 3;\n private static readonly LIST_BYPASS_THRESHOLD = 50; // Don't virtualize if < 50 items\n\n /**\n * Get the item height from CSS variable or fallback to default.\n * Reads --tbw-filter-item-height from the panel element.\n */\n private getListItemHeight(): number {\n if (this.panelElement) {\n const cssValue = getComputedStyle(this.panelElement).getPropertyValue('--tbw-filter-item-height');\n if (cssValue && cssValue.trim()) {\n const parsed = parseFloat(cssValue);\n if (!isNaN(parsed) && parsed > 0) {\n return parsed;\n }\n }\n }\n return FilteringPlugin.DEFAULT_LIST_ITEM_HEIGHT;\n }\n\n /**\n * Sync excludedValues map from a filter model (for set filters).\n */\n private syncExcludedValues(field: string, filter: FilterModel | null): void {\n if (!filter) {\n this.excludedValues.delete(field);\n } else if (filter.type === 'set' && filter.operator === 'notIn' && Array.isArray(filter.value)) {\n this.excludedValues.set(field, new Set(filter.value));\n } else if (filter.type === 'set') {\n // Other set operators may have different semantics; clear for safety\n this.excludedValues.delete(field);\n }\n }\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n this.injectGlobalStyles();\n }\n\n /** @internal */\n override detach(): void {\n this.filters.clear();\n this.cachedResult = null;\n this.cacheKey = null;\n this.cachedInputSpot = null;\n this.openPanelField = null;\n if (this.panelElement) {\n this.panelElement.remove();\n this.panelElement = null;\n }\n this.searchText.clear();\n this.excludedValues.clear();\n // Abort panel-scoped listeners (document click handler, etc.)\n this.panelAbortController?.abort();\n this.panelAbortController = null;\n }\n // #endregion\n\n // #region Query Handlers\n\n /**\n * Handle inter-plugin queries.\n * Contributes filter-related items to the header context menu.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'getContextMenuItems') {\n const params = query.context as ContextMenuParams;\n if (!params.isHeader) return undefined;\n\n const column = params.column as ColumnConfig;\n if (!column?.field) return undefined;\n\n // Only contribute items if filtering is enabled for this column\n if (!this.isFilteringEnabled()) return undefined;\n if (!this.isColumnFilterable(column)) return undefined;\n\n const items: HeaderContextMenuItem[] = [];\n const fieldFiltered = this.isFieldFiltered(column.field);\n const hasAnyFilter = this.filters.size > 0;\n\n if (fieldFiltered) {\n items.push({\n id: 'filtering/clear-column-filter',\n label: `Clear Filter`,\n icon: '✕',\n order: 20,\n action: () => this.clearFieldFilter(column.field),\n });\n }\n\n if (hasAnyFilter) {\n items.push({\n id: 'filtering/clear-all-filters',\n label: 'Clear All Filters',\n icon: '✕',\n order: 21,\n disabled: !hasAnyFilter,\n action: () => this.clearAllFilters(),\n });\n }\n\n return items.length > 0 ? items : undefined;\n }\n return undefined;\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processRows(rows: readonly unknown[]): unknown[] {\n const filterList = [...this.filters.values()];\n if (!filterList.length) return [...rows];\n\n // If using async filterHandler, processRows becomes a passthrough\n // Actual filtering happens in applyFiltersAsync and rows are set directly on grid\n if (this.config.filterHandler) {\n // Return cached result if available (set by async handler)\n if (this.cachedResult) return this.cachedResult;\n // Otherwise return rows as-is (filtering happens async)\n return [...rows];\n }\n\n // Check cache — also verify input rows haven't changed (e.g. due to sort)\n const newCacheKey = computeFilterCacheKey(filterList);\n const inputSpot = {\n len: rows.length,\n first: rows[0],\n mid: rows[Math.floor(rows.length / 2)],\n last: rows[rows.length - 1],\n };\n const inputUnchanged =\n this.cachedInputSpot != null &&\n inputSpot.len === this.cachedInputSpot.len &&\n inputSpot.first === this.cachedInputSpot.first &&\n inputSpot.mid === this.cachedInputSpot.mid &&\n inputSpot.last === this.cachedInputSpot.last;\n\n if (this.cacheKey === newCacheKey && this.cachedResult && inputUnchanged) {\n return this.cachedResult;\n }\n\n // Filter rows synchronously (worker support can be added later)\n const result = filterRows([...rows] as Record<string, unknown>[], filterList, this.config.caseSensitive);\n\n // Update cache\n this.cachedResult = result;\n this.cacheKey = newCacheKey;\n this.cachedInputSpot = inputSpot;\n\n return result;\n }\n\n /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Find all header cells (using part attribute, not class)\n const headerCells = gridEl.querySelectorAll('[part~=\"header-cell\"]');\n headerCells.forEach((cell) => {\n const colIndex = cell.getAttribute('data-col');\n if (colIndex === null) return;\n\n // Use visibleColumns since data-col is the index within _visibleColumns\n const col = this.visibleColumns[parseInt(colIndex, 10)] as ColumnConfig;\n if (!col || !this.isColumnFilterable(col)) return;\n\n // Skip utility columns (expander, selection checkbox, etc.)\n if (isUtilityColumn(col)) return;\n\n const field = col.field;\n if (!field) return;\n\n const hasFilter = this.filters.has(field);\n\n // Check if button already exists\n let filterBtn = cell.querySelector('.tbw-filter-btn') as HTMLElement | null;\n\n if (filterBtn) {\n // Update active state and icon of existing button\n const wasActive = filterBtn.classList.contains('active');\n filterBtn.classList.toggle('active', hasFilter);\n (cell as HTMLElement).classList.toggle('filtered', hasFilter);\n // Update icon if active state changed\n if (wasActive !== hasFilter) {\n const iconName = hasFilter ? 'filterActive' : 'filter';\n this.setIcon(filterBtn, this.resolveIcon(iconName));\n }\n return;\n }\n\n // Create filter button\n filterBtn = document.createElement('button');\n filterBtn.className = 'tbw-filter-btn';\n filterBtn.setAttribute('aria-label', `Filter ${col.header ?? field}`);\n // Use grid icons configuration\n const iconName = hasFilter ? 'filterActive' : 'filter';\n this.setIcon(filterBtn, this.resolveIcon(iconName));\n\n // Mark button as active if filter exists\n if (hasFilter) {\n filterBtn.classList.add('active');\n (cell as HTMLElement).classList.add('filtered');\n }\n\n filterBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.toggleFilterPanel(field, col, filterBtn!);\n });\n\n // Insert before resize handle to maintain order: [label, sort-indicator, filter-btn, resize-handle]\n const resizeHandle = cell.querySelector('.resize-handle');\n if (resizeHandle) {\n cell.insertBefore(filterBtn, resizeHandle);\n } else {\n cell.appendChild(filterBtn);\n }\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Set a filter on a specific field.\n * Pass null to remove the filter.\n */\n setFilter(field: string, filter: Omit<FilterModel, 'field'> | null): void {\n if (filter === null) {\n this.filters.delete(field);\n this.syncExcludedValues(field, null);\n } else {\n const fullFilter = { ...filter, field };\n this.filters.set(field, fullFilter);\n this.syncExcludedValues(field, fullFilter);\n }\n // Invalidate cache\n this.cachedResult = null;\n this.cacheKey = null;\n this.cachedInputSpot = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0, // Will be accurate after processRows\n });\n // Notify other plugins via Event Bus\n this.emitPluginEvent('filter-applied', { filters: [...this.filters.values()] });\n this.requestRender();\n }\n\n /**\n * Get the current filter for a field.\n */\n getFilter(field: string): FilterModel | undefined {\n return this.filters.get(field);\n }\n\n /**\n * Get all active filters.\n */\n getFilters(): FilterModel[] {\n return [...this.filters.values()];\n }\n\n /**\n * Alias for getFilters() to match functional API naming.\n */\n getFilterModel(): FilterModel[] {\n return this.getFilters();\n }\n\n /**\n * Set filters from an array (replaces all existing filters).\n */\n setFilterModel(filters: FilterModel[]): void {\n this.filters.clear();\n this.excludedValues.clear();\n for (const filter of filters) {\n this.filters.set(filter.field, filter);\n this.syncExcludedValues(filter.field, filter);\n }\n this.cachedResult = null;\n this.cacheKey = null;\n this.cachedInputSpot = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0,\n });\n // Notify other plugins via Event Bus\n this.emitPluginEvent('filter-applied', { filters: [...this.filters.values()] });\n this.requestRender();\n }\n\n /**\n * Clear all filters.\n */\n clearAllFilters(): void {\n this.filters.clear();\n this.excludedValues.clear();\n this.searchText.clear();\n\n this.applyFiltersInternal();\n }\n\n /**\n * Clear filter for a specific field.\n */\n clearFieldFilter(field: string): void {\n this.filters.delete(field);\n this.excludedValues.delete(field);\n this.searchText.delete(field);\n\n this.applyFiltersInternal();\n }\n\n /**\n * Check if a field has an active filter.\n */\n isFieldFiltered(field: string): boolean {\n return this.filters.has(field);\n }\n\n /**\n * Get the count of filtered rows (from cache).\n */\n getFilteredRowCount(): number {\n return this.cachedResult?.length ?? this.rows.length;\n }\n\n /**\n * Get all active filters (alias for getFilters).\n */\n getActiveFilters(): FilterModel[] {\n return this.getFilters();\n }\n\n /**\n * Get unique values for a field (for set filter dropdowns).\n * Uses sourceRows to include all values regardless of current filter.\n */\n getUniqueValues(field: string): unknown[] {\n return getUniqueValues(this.sourceRows as Record<string, unknown>[], field);\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Copy CSS classes and data attributes from grid to filter panel.\n * This ensures theme classes (e.g., .eds-theme) cascade to the panel.\n */\n private copyGridThemeContext(panel: HTMLElement): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Copy all CSS classes from grid to panel (except internal ones)\n for (const className of gridEl.classList) {\n // Skip internal classes that shouldn't be copied\n if (className.startsWith('tbw-') || className === 'selecting') continue;\n panel.classList.add(className);\n }\n\n // Copy data-theme attribute if present\n const theme = gridEl.dataset.theme;\n if (theme) {\n panel.dataset.theme = theme;\n }\n }\n\n /**\n * Inject global styles for filter panel (rendered in document.body)\n */\n private injectGlobalStyles(): void {\n if (this.globalStylesInjected) return;\n if (document.getElementById('tbw-filter-panel-styles')) {\n this.globalStylesInjected = true;\n return;\n }\n // Only inject if we have valid CSS text (Vite's ?inline import)\n // When importing from source without Vite, the import is a module object, not a string\n if (typeof filterPanelStyles !== 'string' || !filterPanelStyles) {\n this.globalStylesInjected = true;\n return;\n }\n const style = document.createElement('style');\n style.id = 'tbw-filter-panel-styles';\n style.textContent = filterPanelStyles;\n document.head.appendChild(style);\n this.globalStylesInjected = true;\n }\n\n /**\n * Toggle the filter panel for a field\n */\n private toggleFilterPanel(field: string, column: ColumnConfig, buttonEl: HTMLElement): void {\n // Close if already open\n if (this.openPanelField === field) {\n this.closeFilterPanel();\n return;\n }\n\n // Close any existing panel\n this.closeFilterPanel();\n\n // Create panel\n const panel = document.createElement('div');\n panel.className = 'tbw-filter-panel';\n // Copy theme classes from grid to panel for proper theming\n this.copyGridThemeContext(panel);\n // Add animation class if animations are enabled\n if (this.isAnimationEnabled) {\n panel.classList.add('tbw-filter-panel-animated');\n }\n this.panelElement = panel;\n this.openPanelField = field;\n\n // If using async valuesHandler, show loading state and fetch values\n if (this.config.valuesHandler) {\n panel.innerHTML = '<div class=\"tbw-filter-loading\">Loading...</div>';\n document.body.appendChild(panel);\n this.positionPanel(panel, buttonEl);\n this.setupPanelCloseHandler(panel, buttonEl);\n\n this.config.valuesHandler(field, column).then((values) => {\n // Check if panel is still open for this field\n if (this.openPanelField !== field || !this.panelElement) return;\n panel.innerHTML = '';\n this.renderPanelContent(field, column, panel, values);\n });\n return;\n }\n\n // Sync path: get unique values from local rows\n const uniqueValues = getUniqueValues(this.sourceRows as Record<string, unknown>[], field);\n\n // Position and append to body BEFORE rendering content\n // so getListItemHeight() can read CSS variables from computed styles\n document.body.appendChild(panel);\n this.positionPanel(panel, buttonEl);\n\n this.renderPanelContent(field, column, panel, uniqueValues);\n this.setupPanelCloseHandler(panel, buttonEl);\n }\n\n /**\n * Render filter panel content with given values\n */\n private renderPanelContent(field: string, column: ColumnConfig, panel: HTMLElement, uniqueValues: unknown[]): void {\n // Get current excluded values or initialize empty\n let excludedSet = this.excludedValues.get(field);\n if (!excludedSet) {\n excludedSet = new Set();\n this.excludedValues.set(field, excludedSet);\n }\n\n // Get current search text\n const currentSearchText = this.searchText.get(field) ?? '';\n\n // Create panel params for custom renderer\n const params: FilterPanelParams = {\n field,\n column,\n uniqueValues,\n excludedValues: excludedSet,\n searchText: currentSearchText,\n applySetFilter: (excluded: unknown[]) => {\n this.applySetFilter(field, excluded);\n this.closeFilterPanel();\n },\n applyTextFilter: (operator, value, valueTo) => {\n this.applyTextFilter(field, operator, value, valueTo);\n this.closeFilterPanel();\n },\n clearFilter: () => {\n this.clearFieldFilter(field);\n this.closeFilterPanel();\n },\n closePanel: () => this.closeFilterPanel(),\n };\n\n // Use custom renderer or default\n // Custom renderer can return undefined to fall back to default panel for specific columns\n // Resolution order: plugin config → typeDefaults → built-in\n let usedCustomRenderer = false;\n\n // 1. Check plugin-level filterPanelRenderer\n if (this.config.filterPanelRenderer) {\n this.config.filterPanelRenderer(panel, params);\n // If renderer added content to panel, it handled rendering\n usedCustomRenderer = panel.children.length > 0;\n }\n\n // 2. Check typeDefaults for this column's type\n if (!usedCustomRenderer && column.type) {\n const typeDefault = this.grid.effectiveConfig.typeDefaults?.[column.type];\n if (typeDefault?.filterPanelRenderer) {\n typeDefault.filterPanelRenderer(panel, params);\n usedCustomRenderer = panel.children.length > 0;\n }\n }\n\n // 3. Fall back to built-in type-specific panel renderers\n if (!usedCustomRenderer) {\n const columnType = column.type;\n if (columnType === 'number') {\n this.renderNumberFilterPanel(panel, params, uniqueValues);\n } else if (columnType === 'date') {\n this.renderDateFilterPanel(panel, params, uniqueValues);\n } else {\n this.renderDefaultFilterPanel(panel, params, uniqueValues, excludedSet);\n }\n }\n }\n\n /**\n * Setup click-outside handler to close the panel\n */\n private setupPanelCloseHandler(panel: HTMLElement, buttonEl: HTMLElement): void {\n // Create abort controller for panel-scoped listeners\n // This allows cleanup when panel closes OR when grid disconnects\n this.panelAbortController = new AbortController();\n\n // Add global click handler to close on outside click\n // Defer to next tick to avoid immediate close from the click that opened the panel\n setTimeout(() => {\n document.addEventListener(\n 'click',\n (e: MouseEvent) => {\n if (!panel.contains(e.target as Node) && e.target !== buttonEl) {\n this.closeFilterPanel();\n }\n },\n { signal: this.panelAbortController?.signal },\n );\n }, 0);\n }\n\n /**\n * Close the filter panel\n */\n private closeFilterPanel(): void {\n const panel = this.panelElement;\n if (panel) {\n panel.remove();\n this.panelElement = null;\n }\n // Clean up anchor name from header cell\n if (this.panelAnchorElement) {\n (this.panelAnchorElement.style as any).anchorName = '';\n this.panelAnchorElement = null;\n }\n this.openPanelField = null;\n // Abort panel-scoped listeners (document click handler)\n this.panelAbortController?.abort();\n this.panelAbortController = null;\n }\n\n /** Cache for CSS anchor positioning support check */\n private static supportsAnchorPositioning: boolean | null = null;\n\n /**\n * Check if browser supports CSS Anchor Positioning\n */\n private static checkAnchorPositioningSupport(): boolean {\n if (FilteringPlugin.supportsAnchorPositioning === null) {\n FilteringPlugin.supportsAnchorPositioning = CSS.supports('anchor-name', '--test');\n }\n return FilteringPlugin.supportsAnchorPositioning;\n }\n\n /**\n * Position the panel below the header cell\n * Uses CSS Anchor Positioning if supported, falls back to JS positioning\n */\n private positionPanel(panel: HTMLElement, buttonEl: HTMLElement): void {\n // Find the parent header cell\n const headerCell = buttonEl.closest('.cell') as HTMLElement | null;\n const anchorEl = headerCell ?? buttonEl;\n\n // Set anchor name on the header cell for CSS anchor positioning\n (anchorEl.style as any).anchorName = '--tbw-filter-anchor';\n this.panelAnchorElement = anchorEl; // Store for cleanup\n\n // If CSS Anchor Positioning is supported, CSS handles positioning\n // but we need to detect if it flipped above to adjust animation\n if (FilteringPlugin.checkAnchorPositioningSupport()) {\n // Check position after CSS anchor positioning takes effect\n requestAnimationFrame(() => {\n const panelRect = panel.getBoundingClientRect();\n const anchorRect = anchorEl.getBoundingClientRect();\n // If panel top is above anchor top, it flipped to above\n if (panelRect.top < anchorRect.top) {\n panel.classList.add('tbw-filter-panel-above');\n }\n });\n return;\n }\n\n // Fallback: JS-based positioning for older browsers\n const rect = anchorEl.getBoundingClientRect();\n\n panel.style.position = 'fixed';\n panel.style.top = `${rect.bottom + 4}px`;\n panel.style.left = `${rect.left}px`;\n\n // Adjust if overflows viewport edges\n requestAnimationFrame(() => {\n const panelRect = panel.getBoundingClientRect();\n\n // Check horizontal overflow - align right edge to header cell right edge\n if (panelRect.right > window.innerWidth - 8) {\n panel.style.left = `${rect.right - panelRect.width}px`;\n }\n\n // Check vertical overflow - flip to above header cell\n if (panelRect.bottom > window.innerHeight - 8) {\n panel.style.top = `${rect.top - panelRect.height - 4}px`;\n panel.classList.add('tbw-filter-panel-above');\n }\n });\n }\n\n /**\n * Render the default filter panel content\n */\n private renderDefaultFilterPanel(\n panel: HTMLElement,\n params: FilterPanelParams,\n uniqueValues: unknown[],\n excludedValues: Set<unknown>,\n ): void {\n const { field } = params;\n // Get item height from CSS variable or use default\n const itemHeight = this.getListItemHeight();\n\n // Search input\n const searchContainer = document.createElement('div');\n searchContainer.className = 'tbw-filter-search';\n\n const searchInput = document.createElement('input');\n searchInput.type = 'text';\n searchInput.placeholder = 'Search...';\n searchInput.className = 'tbw-filter-search-input';\n searchInput.value = this.searchText.get(field) ?? '';\n searchContainer.appendChild(searchInput);\n panel.appendChild(searchContainer);\n\n // Select All tristate checkbox\n const actionsRow = document.createElement('div');\n actionsRow.className = 'tbw-filter-actions';\n\n const selectAllLabel = document.createElement('label');\n selectAllLabel.className = 'tbw-filter-value-item';\n selectAllLabel.style.padding = '0';\n selectAllLabel.style.margin = '0';\n\n const selectAllCheckbox = document.createElement('input');\n selectAllCheckbox.type = 'checkbox';\n selectAllCheckbox.className = 'tbw-filter-checkbox';\n\n const selectAllText = document.createElement('span');\n selectAllText.textContent = 'Select All';\n\n selectAllLabel.appendChild(selectAllCheckbox);\n selectAllLabel.appendChild(selectAllText);\n actionsRow.appendChild(selectAllLabel);\n\n // Update tristate checkbox based on checkState\n const updateSelectAllState = () => {\n const values = [...checkState.values()];\n const allChecked = values.every((v) => v);\n const noneChecked = values.every((v) => !v);\n\n selectAllCheckbox.checked = allChecked;\n selectAllCheckbox.indeterminate = !allChecked && !noneChecked;\n };\n\n // Toggle all on click\n selectAllCheckbox.addEventListener('change', () => {\n const newState = selectAllCheckbox.checked;\n for (const key of checkState.keys()) {\n checkState.set(key, newState);\n }\n updateSelectAllState();\n renderVisibleItems();\n });\n\n panel.appendChild(actionsRow);\n\n // Values container with virtualization support\n const valuesContainer = document.createElement('div');\n valuesContainer.className = 'tbw-filter-values';\n\n // Spacer for virtual height\n const spacer = document.createElement('div');\n spacer.className = 'tbw-filter-values-spacer';\n valuesContainer.appendChild(spacer);\n\n // Content container positioned absolutely\n const contentContainer = document.createElement('div');\n contentContainer.className = 'tbw-filter-values-content';\n valuesContainer.appendChild(contentContainer);\n\n // Track current check state for values (persists across virtualizations)\n const checkState = new Map<string, boolean>();\n uniqueValues.forEach((value) => {\n const key = value == null ? '__null__' : String(value);\n checkState.set(key, !excludedValues.has(value));\n });\n\n // Initialize select all state\n updateSelectAllState();\n\n // Filtered values cache\n let filteredValues: unknown[] = [];\n\n // Create a single checkbox item element\n const createItem = (value: unknown, index: number): HTMLElement => {\n const strValue = value == null ? '(Blank)' : String(value);\n const key = value == null ? '__null__' : String(value);\n\n const item = document.createElement('label');\n item.className = 'tbw-filter-value-item';\n item.style.position = 'absolute';\n item.style.top = `calc(var(--tbw-filter-item-height, 28px) * ${index})`;\n item.style.left = '0';\n item.style.right = '0';\n item.style.boxSizing = 'border-box';\n\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.className = 'tbw-filter-checkbox';\n checkbox.checked = checkState.get(key) ?? true;\n checkbox.dataset.value = key;\n\n // Sync check state on change and update tristate checkbox\n checkbox.addEventListener('change', () => {\n checkState.set(key, checkbox.checked);\n updateSelectAllState();\n });\n\n const label = document.createElement('span');\n label.textContent = strValue;\n\n item.appendChild(checkbox);\n item.appendChild(label);\n return item;\n };\n\n // Render visible items using virtualization\n const renderVisibleItems = () => {\n const totalItems = filteredValues.length;\n const viewportHeight = valuesContainer.clientHeight;\n const scrollTop = valuesContainer.scrollTop;\n\n // Set total height for scrollbar\n spacer.style.height = `${totalItems * itemHeight}px`;\n\n // Bypass virtualization for small lists\n if (shouldBypassVirtualization(totalItems, FilteringPlugin.LIST_BYPASS_THRESHOLD / 3)) {\n contentContainer.innerHTML = '';\n contentContainer.style.transform = 'translateY(0px)';\n filteredValues.forEach((value, idx) => {\n contentContainer.appendChild(createItem(value, idx));\n });\n return;\n }\n\n // Use computeVirtualWindow for real-scroll virtualization\n const window = computeVirtualWindow({\n totalRows: totalItems,\n viewportHeight,\n scrollTop,\n rowHeight: itemHeight,\n overscan: FilteringPlugin.LIST_OVERSCAN,\n });\n\n // Position content container\n contentContainer.style.transform = `translateY(${window.offsetY}px)`;\n\n // Clear and render visible items\n contentContainer.innerHTML = '';\n for (let i = window.start; i < window.end; i++) {\n contentContainer.appendChild(createItem(filteredValues[i], i - window.start));\n }\n };\n\n // Filter and re-render values\n const renderValues = (filterText: string) => {\n const caseSensitive = this.config.caseSensitive ?? false;\n const compareFilter = caseSensitive ? filterText : filterText.toLowerCase();\n\n // Filter the unique values\n filteredValues = uniqueValues.filter((value) => {\n const strValue = value == null ? '(Blank)' : String(value);\n const compareValue = caseSensitive ? strValue : strValue.toLowerCase();\n return !filterText || compareValue.includes(compareFilter);\n });\n\n if (filteredValues.length === 0) {\n spacer.style.height = '0px';\n contentContainer.innerHTML = '';\n const noMatch = document.createElement('div');\n noMatch.className = 'tbw-filter-no-match';\n noMatch.textContent = 'No matching values';\n contentContainer.appendChild(noMatch);\n return;\n }\n\n renderVisibleItems();\n };\n\n // Scroll handler for virtualization\n valuesContainer.addEventListener(\n 'scroll',\n () => {\n if (filteredValues.length > 0) {\n renderVisibleItems();\n }\n },\n { passive: true },\n );\n\n renderValues(searchInput.value);\n panel.appendChild(valuesContainer);\n\n // Debounced search\n let debounceTimer: ReturnType<typeof setTimeout>;\n searchInput.addEventListener('input', () => {\n clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n this.searchText.set(field, searchInput.value);\n renderValues(searchInput.value);\n }, this.config.debounceMs ?? 150);\n });\n\n // Apply/Clear buttons\n const buttonRow = document.createElement('div');\n buttonRow.className = 'tbw-filter-buttons';\n\n const applyBtn = document.createElement('button');\n applyBtn.className = 'tbw-filter-apply-btn';\n applyBtn.textContent = 'Apply';\n applyBtn.addEventListener('click', () => {\n // Read from checkState map (works with virtualization)\n const excluded: unknown[] = [];\n for (const [key, isChecked] of checkState) {\n if (!isChecked) {\n if (key === '__null__') {\n excluded.push(null);\n } else {\n // Try to match original value type\n const original = uniqueValues.find((v) => String(v) === key);\n excluded.push(original !== undefined ? original : key);\n }\n }\n }\n params.applySetFilter(excluded);\n });\n buttonRow.appendChild(applyBtn);\n\n const clearBtn = document.createElement('button');\n clearBtn.className = 'tbw-filter-clear-btn';\n clearBtn.textContent = 'Clear Filter';\n clearBtn.addEventListener('click', () => {\n params.clearFilter();\n });\n buttonRow.appendChild(clearBtn);\n\n panel.appendChild(buttonRow);\n }\n\n /**\n * Render a number range filter panel with min/max inputs and slider\n */\n private renderNumberFilterPanel(panel: HTMLElement, params: FilterPanelParams, uniqueValues: unknown[]): void {\n const { field, column } = params;\n\n // Get range configuration from filterParams, editorParams, or compute from data\n const filterParams = column.filterParams;\n const editorParams = column.editorParams as { min?: number; max?: number; step?: number } | undefined;\n\n // Helper to convert to number\n const toNumber = (val: unknown, fallback: number): number => {\n if (typeof val === 'number') return val;\n if (typeof val === 'string') {\n const num = parseFloat(val);\n return isNaN(num) ? fallback : num;\n }\n return fallback;\n };\n\n // Compute min/max from data if not specified\n const numericValues = uniqueValues.filter((v) => typeof v === 'number' && !isNaN(v)) as number[];\n const dataMin = numericValues.length > 0 ? Math.min(...numericValues) : 0;\n const dataMax = numericValues.length > 0 ? Math.max(...numericValues) : 100;\n\n const min = toNumber(filterParams?.min ?? editorParams?.min, dataMin);\n const max = toNumber(filterParams?.max ?? editorParams?.max, dataMax);\n const step = filterParams?.step ?? editorParams?.step ?? 1;\n\n // Get current filter values if any\n const currentFilter = this.filters.get(field);\n let currentMin = min;\n let currentMax = max;\n if (currentFilter?.operator === 'between') {\n currentMin = toNumber(currentFilter.value, min);\n currentMax = toNumber(currentFilter.valueTo, max);\n } else if (currentFilter?.operator === 'greaterThanOrEqual') {\n currentMin = toNumber(currentFilter.value, min);\n } else if (currentFilter?.operator === 'lessThanOrEqual') {\n currentMax = toNumber(currentFilter.value, max);\n }\n\n // Range inputs container\n const rangeContainer = document.createElement('div');\n rangeContainer.className = 'tbw-filter-range-inputs';\n\n // Min input\n const minGroup = document.createElement('div');\n minGroup.className = 'tbw-filter-range-group';\n\n const minLabel = document.createElement('label');\n minLabel.textContent = 'Min';\n minLabel.className = 'tbw-filter-range-label';\n\n const minInput = document.createElement('input');\n minInput.type = 'number';\n minInput.className = 'tbw-filter-range-input';\n minInput.min = String(min);\n minInput.max = String(max);\n minInput.step = String(step);\n minInput.value = String(currentMin);\n\n minGroup.appendChild(minLabel);\n minGroup.appendChild(minInput);\n rangeContainer.appendChild(minGroup);\n\n // Separator\n const separator = document.createElement('span');\n separator.className = 'tbw-filter-range-separator';\n separator.textContent = '–';\n rangeContainer.appendChild(separator);\n\n // Max input\n const maxGroup = document.createElement('div');\n maxGroup.className = 'tbw-filter-range-group';\n\n const maxLabel = document.createElement('label');\n maxLabel.textContent = 'Max';\n maxLabel.className = 'tbw-filter-range-label';\n\n const maxInput = document.createElement('input');\n maxInput.type = 'number';\n maxInput.className = 'tbw-filter-range-input';\n maxInput.min = String(min);\n maxInput.max = String(max);\n maxInput.step = String(step);\n maxInput.value = String(currentMax);\n\n maxGroup.appendChild(maxLabel);\n maxGroup.appendChild(maxInput);\n rangeContainer.appendChild(maxGroup);\n\n panel.appendChild(rangeContainer);\n\n // Range slider (dual thumb using two range inputs)\n const sliderContainer = document.createElement('div');\n sliderContainer.className = 'tbw-filter-range-slider';\n\n const sliderTrack = document.createElement('div');\n sliderTrack.className = 'tbw-filter-range-track';\n\n const sliderFill = document.createElement('div');\n sliderFill.className = 'tbw-filter-range-fill';\n\n const minSlider = document.createElement('input');\n minSlider.type = 'range';\n minSlider.className = 'tbw-filter-range-thumb tbw-filter-range-thumb-min';\n minSlider.min = String(min);\n minSlider.max = String(max);\n minSlider.step = String(step);\n minSlider.value = String(currentMin);\n\n const maxSlider = document.createElement('input');\n maxSlider.type = 'range';\n maxSlider.className = 'tbw-filter-range-thumb tbw-filter-range-thumb-max';\n maxSlider.min = String(min);\n maxSlider.max = String(max);\n maxSlider.step = String(step);\n maxSlider.value = String(currentMax);\n\n sliderContainer.appendChild(sliderTrack);\n sliderContainer.appendChild(sliderFill);\n sliderContainer.appendChild(minSlider);\n sliderContainer.appendChild(maxSlider);\n panel.appendChild(sliderContainer);\n\n // Update fill position\n const updateFill = () => {\n const minVal = parseFloat(minSlider.value);\n const maxVal = parseFloat(maxSlider.value);\n const range = max - min;\n const leftPercent = ((minVal - min) / range) * 100;\n const rightPercent = ((maxVal - min) / range) * 100;\n sliderFill.style.left = `${leftPercent}%`;\n sliderFill.style.width = `${rightPercent - leftPercent}%`;\n };\n\n // Sync inputs with sliders\n minSlider.addEventListener('input', () => {\n const val = Math.min(parseFloat(minSlider.value), parseFloat(maxSlider.value));\n minSlider.value = String(val);\n minInput.value = String(val);\n updateFill();\n });\n\n maxSlider.addEventListener('input', () => {\n const val = Math.max(parseFloat(maxSlider.value), parseFloat(minSlider.value));\n maxSlider.value = String(val);\n maxInput.value = String(val);\n updateFill();\n });\n\n // Sync sliders with inputs\n minInput.addEventListener('input', () => {\n let val = parseFloat(minInput.value) || min;\n val = Math.max(min, Math.min(val, parseFloat(maxInput.value)));\n minSlider.value = String(val);\n updateFill();\n });\n\n maxInput.addEventListener('input', () => {\n let val = parseFloat(maxInput.value) || max;\n val = Math.min(max, Math.max(val, parseFloat(minInput.value)));\n maxSlider.value = String(val);\n updateFill();\n });\n\n // Initialize fill\n updateFill();\n\n // Apply/Clear buttons\n const buttonRow = document.createElement('div');\n buttonRow.className = 'tbw-filter-buttons';\n\n const applyBtn = document.createElement('button');\n applyBtn.className = 'tbw-filter-apply-btn';\n applyBtn.textContent = 'Apply';\n applyBtn.addEventListener('click', () => {\n const minVal = parseFloat(minInput.value);\n const maxVal = parseFloat(maxInput.value);\n params.applyTextFilter('between', minVal, maxVal);\n });\n buttonRow.appendChild(applyBtn);\n\n const clearBtn = document.createElement('button');\n clearBtn.className = 'tbw-filter-clear-btn';\n clearBtn.textContent = 'Clear Filter';\n clearBtn.addEventListener('click', () => {\n params.clearFilter();\n });\n buttonRow.appendChild(clearBtn);\n\n panel.appendChild(buttonRow);\n }\n\n /**\n * Render a date range filter panel with from/to date inputs\n */\n private renderDateFilterPanel(panel: HTMLElement, params: FilterPanelParams, uniqueValues: unknown[]): void {\n const { field, column } = params;\n\n // Get range configuration from filterParams, editorParams, or compute from data\n const filterParams = column.filterParams;\n const editorParams = column.editorParams as { min?: string; max?: string } | undefined;\n\n // Compute min/max from data if not specified\n const dateValues = uniqueValues\n .filter((v) => v instanceof Date || (typeof v === 'string' && !isNaN(Date.parse(v))))\n .map((v) => (v instanceof Date ? v : new Date(v as string)))\n .filter((d) => !isNaN(d.getTime()));\n\n const dataMin = dateValues.length > 0 ? new Date(Math.min(...dateValues.map((d) => d.getTime()))) : null;\n const dataMax = dateValues.length > 0 ? new Date(Math.max(...dateValues.map((d) => d.getTime()))) : null;\n\n // Format date for input[type=\"date\"] (YYYY-MM-DD)\n const formatDateForInput = (date: Date | null): string => {\n if (!date) return '';\n return date.toISOString().split('T')[0];\n };\n\n const parseFilterParam = (value: unknown): string => {\n if (!value) return '';\n if (typeof value === 'string') return value;\n if (typeof value === 'number') return formatDateForInput(new Date(value));\n return '';\n };\n\n const minDate =\n parseFilterParam(filterParams?.min) || parseFilterParam(editorParams?.min) || formatDateForInput(dataMin);\n const maxDate =\n parseFilterParam(filterParams?.max) || parseFilterParam(editorParams?.max) || formatDateForInput(dataMax);\n\n // Get current filter values if any\n const currentFilter = this.filters.get(field);\n let currentFrom = '';\n let currentTo = '';\n const isBlankFilter = currentFilter?.operator === 'blank';\n if (currentFilter?.operator === 'between') {\n currentFrom = parseFilterParam(currentFilter.value) || '';\n currentTo = parseFilterParam(currentFilter.valueTo) || '';\n } else if (currentFilter?.operator === 'greaterThanOrEqual') {\n currentFrom = parseFilterParam(currentFilter.value) || '';\n } else if (currentFilter?.operator === 'lessThanOrEqual') {\n currentTo = parseFilterParam(currentFilter.value) || '';\n }\n\n // Date range inputs container\n const rangeContainer = document.createElement('div');\n rangeContainer.className = 'tbw-filter-date-range';\n\n // From input\n const fromGroup = document.createElement('div');\n fromGroup.className = 'tbw-filter-date-group';\n\n const fromLabel = document.createElement('label');\n fromLabel.textContent = 'From';\n fromLabel.className = 'tbw-filter-range-label';\n\n const fromInput = document.createElement('input');\n fromInput.type = 'date';\n fromInput.className = 'tbw-filter-date-input';\n if (minDate) fromInput.min = minDate;\n if (maxDate) fromInput.max = maxDate;\n fromInput.value = currentFrom;\n\n fromGroup.appendChild(fromLabel);\n fromGroup.appendChild(fromInput);\n rangeContainer.appendChild(fromGroup);\n\n // Separator\n const separator = document.createElement('span');\n separator.className = 'tbw-filter-range-separator';\n separator.textContent = '–';\n rangeContainer.appendChild(separator);\n\n // To input\n const toGroup = document.createElement('div');\n toGroup.className = 'tbw-filter-date-group';\n\n const toLabel = document.createElement('label');\n toLabel.textContent = 'To';\n toLabel.className = 'tbw-filter-range-label';\n\n const toInput = document.createElement('input');\n toInput.type = 'date';\n toInput.className = 'tbw-filter-date-input';\n if (minDate) toInput.min = minDate;\n if (maxDate) toInput.max = maxDate;\n toInput.value = currentTo;\n\n toGroup.appendChild(toLabel);\n toGroup.appendChild(toInput);\n rangeContainer.appendChild(toGroup);\n\n panel.appendChild(rangeContainer);\n\n // \"Show only blank\" checkbox\n const blankRow = document.createElement('label');\n blankRow.className = 'tbw-filter-blank-option';\n\n const blankCheckbox = document.createElement('input');\n blankCheckbox.type = 'checkbox';\n blankCheckbox.className = 'tbw-filter-blank-checkbox';\n blankCheckbox.checked = isBlankFilter;\n\n const blankLabel = document.createTextNode('Show only blank');\n blankRow.appendChild(blankCheckbox);\n blankRow.appendChild(blankLabel);\n\n // Toggle date inputs disabled state when blank is checked\n const toggleDateInputs = (disabled: boolean): void => {\n fromInput.disabled = disabled;\n toInput.disabled = disabled;\n rangeContainer.classList.toggle('tbw-filter-disabled', disabled);\n };\n toggleDateInputs(isBlankFilter);\n\n blankCheckbox.addEventListener('change', () => {\n toggleDateInputs(blankCheckbox.checked);\n });\n\n panel.appendChild(blankRow);\n\n // Apply/Clear buttons\n const buttonRow = document.createElement('div');\n buttonRow.className = 'tbw-filter-buttons';\n\n const applyBtn = document.createElement('button');\n applyBtn.className = 'tbw-filter-apply-btn';\n applyBtn.textContent = 'Apply';\n applyBtn.addEventListener('click', () => {\n if (blankCheckbox.checked) {\n params.applyTextFilter('blank', '');\n return;\n }\n\n const from = fromInput.value;\n const to = toInput.value;\n\n if (from && to) {\n params.applyTextFilter('between', from, to);\n } else if (from) {\n params.applyTextFilter('greaterThanOrEqual', from);\n } else if (to) {\n params.applyTextFilter('lessThanOrEqual', to);\n } else {\n params.clearFilter();\n }\n });\n buttonRow.appendChild(applyBtn);\n\n const clearBtn = document.createElement('button');\n clearBtn.className = 'tbw-filter-clear-btn';\n clearBtn.textContent = 'Clear Filter';\n clearBtn.addEventListener('click', () => {\n params.clearFilter();\n });\n buttonRow.appendChild(clearBtn);\n\n panel.appendChild(buttonRow);\n }\n\n /**\n * Apply a set filter (exclude values)\n */\n private applySetFilter(field: string, excluded: unknown[]): void {\n // Store excluded values\n this.excludedValues.set(field, new Set(excluded));\n\n if (excluded.length === 0) {\n // No exclusions = no filter\n this.filters.delete(field);\n } else {\n // Create \"notIn\" filter\n this.filters.set(field, {\n field,\n type: 'set',\n operator: 'notIn',\n value: excluded,\n });\n }\n\n this.applyFiltersInternal();\n }\n\n /**\n * Apply a text/number/date filter\n */\n private applyTextFilter(\n field: string,\n operator: FilterModel['operator'],\n value: string | number,\n valueTo?: string | number,\n ): void {\n this.filters.set(field, {\n field,\n type: 'text',\n operator,\n value,\n valueTo,\n });\n\n this.applyFiltersInternal();\n }\n\n /**\n * Internal method to apply filters (sync or async based on config)\n */\n private applyFiltersInternal(): void {\n this.cachedResult = null;\n this.cacheKey = null;\n this.cachedInputSpot = null;\n\n const filterList = [...this.filters.values()];\n\n // If using async filterHandler, delegate to server\n if (this.config.filterHandler) {\n const gridEl = this.grid as unknown as Element;\n gridEl.setAttribute('aria-busy', 'true');\n\n const result = this.config.filterHandler(filterList, this.sourceRows as unknown[]);\n\n // Handle async or sync result\n const handleResult = (rows: unknown[]) => {\n gridEl.removeAttribute('aria-busy');\n this.cachedResult = rows;\n\n // Update grid rows directly for async filtering\n (this.grid as unknown as { rows: unknown[] }).rows = rows;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: filterList,\n filteredRowCount: rows.length,\n });\n // Notify other plugins via Event Bus\n this.emitPluginEvent('filter-applied', { filters: filterList });\n\n // Trigger afterRender to update filter button active state\n this.requestRender();\n };\n\n if (result && typeof (result as Promise<unknown[]>).then === 'function') {\n (result as Promise<unknown[]>).then(handleResult);\n } else {\n handleResult(result as unknown[]);\n }\n return;\n }\n\n // Sync path: emit event and re-render (processRows will handle filtering)\n this.emit<FilterChangeDetail>('filter-change', {\n filters: filterList,\n filteredRowCount: 0,\n });\n // Notify other plugins via Event Bus\n this.emitPluginEvent('filter-applied', { filters: filterList });\n this.requestRender();\n }\n // #endregion\n\n // #region Column State Hooks\n\n /**\n * Return filter state for a column if it has an active filter.\n * @internal\n */\n override getColumnState(field: string): Partial<ColumnState> | undefined {\n const filterModel = this.filters.get(field);\n if (!filterModel) return undefined;\n\n return {\n filter: {\n type: filterModel.type,\n operator: filterModel.operator,\n value: filterModel.value,\n valueTo: filterModel.valueTo,\n },\n };\n }\n\n /**\n * Apply filter state from column state.\n * @internal\n */\n override applyColumnState(field: string, state: ColumnState): void {\n // Only process if the column has filter state\n if (!state.filter) {\n this.filters.delete(field);\n return;\n }\n\n // Reconstruct the FilterModel from the stored state\n const filterModel: FilterModel = {\n field,\n type: state.filter.type,\n operator: state.filter.operator as FilterModel['operator'],\n value: state.filter.value,\n valueTo: state.filter.valueTo,\n };\n\n this.filters.set(field, filterModel);\n // Invalidate cache so filter is reapplied\n this.cachedResult = null;\n this.cacheKey = null;\n this.cachedInputSpot = null;\n }\n // #endregion\n}\n","/**\n * Column Groups Core Logic\n *\n * Pure functions for computing and managing column header groups.\n */\n\n// Import types to enable module augmentation\nimport type { ColumnConfig } from '../../core/types';\nimport './types';\nimport type { ColumnGroup, ColumnGroupInternal } from './types';\n\n/**\n * Compute column groups from column configuration.\n * Handles explicit groups (via column.group) and creates implicit groups for ungrouped columns.\n *\n * @param columns - Array of column configurations\n * @returns Array of column groups, or empty if no meaningful groups\n */\nexport function computeColumnGroups<T>(columns: ColumnConfig<T>[]): ColumnGroup<T>[] {\n if (!columns.length) return [];\n\n const explicitMap = new Map<string, ColumnGroupInternal<T>>();\n const groupsOrdered: ColumnGroupInternal<T>[] = [];\n\n // Helper to push unnamed implicit group for a run of ungrouped columns\n const pushImplicit = (startIdx: number, cols: ColumnConfig<T>[]) => {\n if (!cols.length) return;\n // Merge with previous implicit group if adjacent to reduce noise\n const prev = groupsOrdered[groupsOrdered.length - 1];\n if (prev && prev.implicit && prev.firstIndex + prev.columns.length === startIdx) {\n prev.columns.push(...cols);\n return;\n }\n groupsOrdered.push({\n id: '__implicit__' + startIdx,\n label: undefined,\n columns: cols,\n firstIndex: startIdx,\n implicit: true,\n });\n };\n\n let run: ColumnConfig<T>[] = [];\n let runStart = 0;\n\n columns.forEach((col, idx) => {\n const g = col.group;\n if (!g) {\n if (run.length === 0) runStart = idx;\n run.push(col);\n return;\n }\n // Close any pending implicit run\n if (run.length) {\n pushImplicit(runStart, run.slice());\n run = [];\n }\n const id = typeof g === 'string' ? g : g.id;\n let group = explicitMap.get(id);\n if (!group) {\n group = {\n id,\n label: typeof g === 'string' ? undefined : g.label,\n columns: [],\n firstIndex: idx,\n };\n explicitMap.set(id, group);\n groupsOrdered.push(group);\n }\n group.columns.push(col);\n });\n\n // Trailing implicit run\n if (run.length) pushImplicit(runStart, run);\n\n // If we only have a single implicit group covering all columns, treat as no groups\n if (groupsOrdered.length === 1 && groupsOrdered[0].implicit && groupsOrdered[0].columns.length === columns.length) {\n return [];\n }\n\n return groupsOrdered as ColumnGroup<T>[];\n}\n\n/**\n * Apply CSS classes to header cells based on their group membership.\n *\n * @param headerRowEl - The header row element\n * @param groups - The computed column groups\n * @param columns - The column configurations\n */\nexport function applyGroupedHeaderCellClasses(\n headerRowEl: HTMLElement | null,\n groups: ColumnGroup[],\n columns: ColumnConfig[],\n): void {\n if (!groups.length || !headerRowEl) return;\n\n const fieldToGroup = new Map<string, string>();\n for (const g of groups) {\n for (const c of g.columns) {\n if (c.field) {\n fieldToGroup.set(c.field, g.id);\n }\n }\n }\n\n const headerCells = Array.from(headerRowEl.querySelectorAll('.cell[data-field]')) as HTMLElement[];\n headerCells.forEach((cell) => {\n const f = cell.getAttribute('data-field') || '';\n const gid = fieldToGroup.get(f);\n if (gid) {\n cell.classList.add('grouped');\n if (!cell.getAttribute('data-group')) {\n cell.setAttribute('data-group', gid);\n }\n }\n });\n\n // Mark group end cells for styling\n for (const g of groups) {\n const last = g.columns[g.columns.length - 1];\n const cell = headerCells.find((c) => c.getAttribute('data-field') === last.field);\n if (cell) cell.classList.add('group-end');\n }\n}\n\n/**\n * Build the group header row element.\n *\n * @param groups - The computed column groups\n * @param columns - The column configurations (final array including any plugin-added columns)\n * @returns The group header row element, or null if no groups\n */\nexport function buildGroupHeaderRow(groups: ColumnGroup[], columns: ColumnConfig[]): HTMLElement | null {\n if (groups.length === 0) return null;\n\n const groupRow = document.createElement('div');\n groupRow.className = 'header-group-row';\n groupRow.setAttribute('role', 'row');\n\n for (const g of groups) {\n // Always compute start index from the current columns array, not stored firstIndex.\n // This accounts for plugin-added columns (e.g., expander) that weren't present\n // when the groups were initially computed during processColumns.\n const firstGroupCol = g.columns[0];\n const startIndex = firstGroupCol ? columns.findIndex((c) => c.field === firstGroupCol.field) : -1;\n if (startIndex === -1) continue; // Group columns not in final column list\n\n const isImplicit = String(g.id).startsWith('__implicit__');\n const label = isImplicit ? '' : g.label || g.id;\n\n const cell = document.createElement('div');\n cell.className = 'cell header-group-cell';\n if (isImplicit) cell.classList.add('implicit-group');\n cell.setAttribute('data-group', String(g.id));\n cell.style.gridColumn = `${startIndex + 1} / span ${g.columns.length}`;\n cell.textContent = label;\n groupRow.appendChild(cell);\n }\n\n return groupRow;\n}\n\n/**\n * Check if any columns have group configuration.\n *\n * @param columns - The column configurations\n * @returns True if at least one column has a group\n */\nexport function hasColumnGroups(columns: ColumnConfig[]): boolean {\n return columns.some((col) => col.group != null);\n}\n\n/**\n * Get group ID for a specific column.\n *\n * @param column - The column configuration\n * @returns The group ID, or undefined if not grouped\n */\nexport function getColumnGroupId(column: ColumnConfig): string | undefined {\n const g = column.group;\n if (!g) return undefined;\n return typeof g === 'string' ? g : g.id;\n}\n","/**\n * Column Groups Plugin (Class-based)\n *\n * Enables multi-level column header grouping.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { AfterCellRenderContext, PluginManifest, PluginQuery } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport type { ColumnGroupInfo } from '../visibility/types';\nimport {\n applyGroupedHeaderCellClasses,\n buildGroupHeaderRow,\n computeColumnGroups,\n hasColumnGroups,\n} from './grouping-columns';\nimport styles from './grouping-columns.css?inline';\nimport type { ColumnGroup, GroupingColumnsConfig } from './types';\n\n/**\n * Column Grouping Plugin for tbw-grid\n *\n * Enables visual grouping of columns under shared headers. Supports two approaches:\n * declarative `columnGroups` at the grid level, or inline `group` property on columns.\n *\n * ## Installation\n *\n * ```ts\n * import { GroupingColumnsPlugin } from '@toolbox-web/grid/plugins/grouping-columns';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `showGroupBorders` | `boolean` | `true` | Show borders between groups |\n * | `groupHeaderRenderer` | `function` | - | Custom renderer for group header content |\n *\n * ## Grid Config: `columnGroups`\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `id` | `string` | Unique group identifier |\n * | `header` | `string` | Display label for the group header |\n * | `children` | `string[]` | Array of column field names in this group |\n *\n * ## Column Config: `group`\n *\n * | Type | Description |\n * |------|-------------|\n * | `string` | Simple group ID (used as both id and label) |\n * | `{ id: string; label?: string }` | Group object with explicit id and optional label |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `isGroupingActive` | `() => boolean` | Check if grouping is active |\n * | `getGroups` | `() => ColumnGroup[]` | Get all computed groups |\n * | `getGroupColumns` | `(groupId) => ColumnConfig[]` | Get columns in a specific group |\n * | `refresh` | `() => void` | Force refresh of column groups |\n *\n * @example Declarative columnGroups (Recommended)\n * ```ts\n * import '@toolbox-web/grid';\n * import { GroupingColumnsPlugin } from '@toolbox-web/grid/plugins/grouping-columns';\n *\n * grid.gridConfig = {\n * columnGroups: [\n * { id: 'personal', header: 'Personal Info', children: ['firstName', 'lastName', 'email'] },\n * { id: 'work', header: 'Work Info', children: ['department', 'title', 'salary'] },\n * ],\n * columns: [\n * { field: 'firstName', header: 'First Name' },\n * { field: 'lastName', header: 'Last Name' },\n * // ...\n * ],\n * plugins: [new GroupingColumnsPlugin()],\n * };\n * ```\n *\n * @example Inline group Property\n * ```ts\n * grid.gridConfig = {\n * columns: [\n * { field: 'firstName', header: 'First Name', group: { id: 'personal', label: 'Personal Info' } },\n * { field: 'lastName', header: 'Last Name', group: 'personal' }, // string shorthand\n * ],\n * plugins: [new GroupingColumnsPlugin()],\n * };\n * ```\n *\n * @see {@link GroupingColumnsConfig} for all configuration options\n * @see {@link ColumnGroup} for the group structure\n * @see {@link ReorderPlugin} for drag-to-reorder within groups\n *\n * @internal Extends BaseGridPlugin\n */\nexport class GroupingColumnsPlugin extends BaseGridPlugin<GroupingColumnsConfig> {\n /**\n * Plugin manifest - declares owned properties for configuration validation.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n ownedProperties: [\n {\n property: 'group',\n level: 'column',\n description: 'the \"group\" column property',\n },\n {\n property: 'columnGroups',\n level: 'config',\n description: 'the \"columnGroups\" config property',\n isUsed: (v) => Array.isArray(v) && v.length > 0,\n },\n ],\n queries: [{ type: 'getColumnGrouping', description: 'Returns column group metadata for the visibility panel' }],\n };\n\n /** @internal */\n readonly name = 'groupingColumns';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<GroupingColumnsConfig> {\n return {\n showGroupBorders: true,\n lockGroupOrder: false,\n };\n }\n\n // #region Internal State\n private groups: ColumnGroup[] = [];\n private isActive = false;\n /** Fields that are the last column in a group (for group-end border class). */\n #groupEndFields = new Set<string>();\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Listen for cancelable column-move events to enforce group contiguity\n (grid as unknown as HTMLElement).addEventListener('column-move', this.#onColumnMove, {\n signal: this.disconnectSignal,\n });\n }\n\n /** @internal */\n override detach(): void {\n this.groups = [];\n this.isActive = false;\n this.#groupEndFields.clear();\n }\n\n // #region Column Move Guard\n\n /**\n * Handle the cancelable column-move event.\n * - When lockGroupOrder is enabled, prevents moves that would break group contiguity.\n * - Always refreshes #groupEndFields after a successful move so that afterCellRender\n * applies group-end borders to the correct (reordered) last column.\n */\n #onColumnMove = (e: Event): void => {\n if (!this.isActive) return;\n\n const event = e as CustomEvent<{ field: string; columnOrder: string[] }>;\n const { field, columnOrder } = event.detail;\n\n if (this.config.lockGroupOrder) {\n // Check ALL explicit groups — moving any column (grouped or not) could break contiguity\n for (const group of this.groups) {\n if (group.id.startsWith('__implicit__')) continue;\n if (!this.#isGroupContiguous(group, columnOrder)) {\n event.preventDefault();\n this.#flashHeaderCell(field);\n return;\n }\n }\n }\n\n // Recompute group-end fields based on proposed column order.\n // setColumnOrder runs synchronously after this handler returns,\n // but afterCellRender (which reads #groupEndFields) fires during\n // the subsequent refreshVirtualWindow. Precompute using the\n // proposed columnOrder so the borders are correct immediately.\n this.#recomputeGroupEndFields(columnOrder);\n };\n\n /**\n * Recompute which fields are group-end based on a column order.\n * The last field of each explicit group in the order gets the group-end class.\n */\n #recomputeGroupEndFields(columnOrder: string[]): void {\n this.#groupEndFields.clear();\n // Find the last field of each group (including implicit groups between explicit ones).\n // Skip the very last group overall — no adjacent group follows it, so no separator needed.\n const lastGroupEndField = this.#findLastGroupEndField(columnOrder);\n for (const group of this.groups) {\n const groupFields = new Set(group.columns.map((c) => c.field));\n // Walk the column order in reverse to find the last member of this group\n for (let i = columnOrder.length - 1; i >= 0; i--) {\n if (groupFields.has(columnOrder[i])) {\n const field = columnOrder[i];\n // Don't mark the last group's trailing field — nothing follows it\n if (field !== lastGroupEndField) {\n this.#groupEndFields.add(field);\n }\n break;\n }\n }\n }\n }\n\n /**\n * Find the trailing field of the last group in column order (to exclude from group-end marking).\n */\n #findLastGroupEndField(columnOrder: string[]): string | null {\n if (this.groups.length === 0) return null;\n // Determine which group contains the last field in column order\n for (let i = columnOrder.length - 1; i >= 0; i--) {\n const field = columnOrder[i];\n for (const group of this.groups) {\n if (group.columns.some((c) => c.field === field)) {\n // This group is the last in display order — find its last field\n const groupFields = new Set(group.columns.map((c) => c.field));\n for (let j = columnOrder.length - 1; j >= 0; j--) {\n if (groupFields.has(columnOrder[j])) return columnOrder[j];\n }\n }\n }\n }\n return null;\n }\n\n /**\n * Check if all columns in a group are contiguous in the proposed column order.\n */\n #isGroupContiguous(group: ColumnGroup, columnOrder: string[]): boolean {\n const indices = group.columns\n .map((c) => columnOrder.indexOf(c.field))\n .filter((i) => i !== -1)\n .sort((a, b) => a - b);\n if (indices.length <= 1) return true;\n return indices.length === indices[indices.length - 1] - indices[0] + 1;\n }\n\n /**\n * Flash the header cell with an error color to indicate a blocked move.\n */\n #flashHeaderCell(field: string): void {\n const headerCell = this.gridElement?.querySelector(\n `.header-row [part~=\"header-cell\"][data-field=\"${field}\"]`,\n ) as HTMLElement;\n if (!headerCell) return;\n\n headerCell.style.setProperty('--_flash-color', 'var(--tbw-color-error)');\n headerCell.animate(\n [{ backgroundColor: 'rgba(from var(--_flash-color) r g b / 30%)' }, { backgroundColor: 'transparent' }],\n { duration: 400, easing: 'ease-out' },\n );\n }\n // #endregion\n\n /** @internal */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'getColumnGrouping') {\n return this.#getStableColumnGrouping();\n }\n return undefined;\n }\n\n /**\n * Get stable column grouping info that includes ALL columns (visible and hidden).\n * Used by the visibility panel to maintain group structure regardless of visibility state.\n * Fields within each group are sorted by current display order.\n */\n #getStableColumnGrouping(): ColumnGroupInfo[] {\n let result: ColumnGroupInfo[];\n\n // 1. Prefer declarative columnGroups - always complete, visibility-independent\n const columnGroups = this.grid?.gridConfig?.columnGroups;\n if (columnGroups && Array.isArray(columnGroups) && columnGroups.length > 0) {\n result = columnGroups\n .filter((g) => g.children.length > 0)\n .map((g) => ({\n id: g.id,\n label: g.header,\n fields: [...g.children],\n }));\n } else if (this.isActive && this.groups.length > 0) {\n // 2. If active groups exist from processColumns, use them\n result = this.groups\n .filter((g) => !g.id.startsWith('__implicit__'))\n .map<ColumnGroupInfo>((g) => ({\n id: g.id,\n label: g.label ?? g.id,\n fields: g.columns.map((c) => c.field),\n }));\n\n // Also check hidden columns for inline group properties not in active groups\n const allCols = this.columns as ColumnConfig[];\n for (const col of allCols) {\n if ((col as any).hidden && col.group) {\n const gId = typeof col.group === 'string' ? col.group : col.group.id;\n const gLabel = typeof col.group === 'string' ? col.group : (col.group.label ?? col.group.id);\n const existing = result.find((g) => g.id === gId);\n if (existing) {\n if (!existing.fields.includes(col.field)) existing.fields.push(col.field);\n } else {\n result.push({ id: gId, label: gLabel, fields: [col.field] });\n }\n }\n }\n } else {\n // 3. Fall back: scan ALL columns (including hidden) for inline group properties\n const allCols = this.columns as ColumnConfig[];\n const groupMap = new Map<string, ColumnGroupInfo>();\n for (const col of allCols) {\n if (!col.group) continue;\n const gId = typeof col.group === 'string' ? col.group : col.group.id;\n const gLabel = typeof col.group === 'string' ? col.group : (col.group.label ?? col.group.id);\n const existing = groupMap.get(gId);\n if (existing) {\n if (!existing.fields.includes(col.field)) existing.fields.push(col.field);\n } else {\n groupMap.set(gId, { id: gId, label: gLabel, fields: [col.field] });\n }\n }\n result = Array.from(groupMap.values());\n }\n\n // Sort fields within each group by current display order so consumers\n // (e.g. the visibility panel) render columns in their reordered positions.\n const displayOrder = this.grid?.getColumnOrder();\n if (displayOrder && displayOrder.length > 0) {\n const orderIndex = new Map(displayOrder.map((f, i) => [f, i]));\n for (const group of result) {\n group.fields.sort((a, b) => (orderIndex.get(a) ?? Infinity) - (orderIndex.get(b) ?? Infinity));\n }\n }\n\n return result;\n }\n // #endregion\n\n // #region Static Detection\n\n /**\n * Auto-detect column groups from column configuration.\n * Detects both inline `column.group` properties and declarative `columnGroups` config.\n */\n static detect(rows: readonly any[], config: any): boolean {\n // Check for declarative columnGroups in config\n if (config?.columnGroups && Array.isArray(config.columnGroups) && config.columnGroups.length > 0) {\n return true;\n }\n // Check for inline group properties on columns\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasColumnGroups(columns);\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n // First, check if gridConfig.columnGroups is defined and apply to columns\n const columnGroups = this.grid?.gridConfig?.columnGroups;\n let processedColumns: ColumnConfig[];\n\n if (columnGroups && Array.isArray(columnGroups) && columnGroups.length > 0) {\n // Build a map of field -> group info from the declarative config\n const fieldToGroup = new Map<string, { id: string; label: string }>();\n for (const group of columnGroups) {\n for (const field of group.children) {\n fieldToGroup.set(field, { id: group.id, label: group.header });\n }\n }\n\n // Apply group property to columns that don't already have one\n processedColumns = columns.map((col) => {\n const groupInfo = fieldToGroup.get(col.field);\n if (groupInfo && !col.group) {\n return { ...col, group: groupInfo };\n }\n return col;\n });\n } else {\n processedColumns = [...columns];\n }\n\n // Compute groups from column definitions (now including applied groups)\n const groups = computeColumnGroups(processedColumns);\n\n if (groups.length === 0) {\n this.isActive = false;\n this.groups = [];\n return processedColumns;\n }\n\n this.isActive = true;\n this.groups = groups;\n\n // Pre-compute group-end fields for the afterCellRender hook\n this.#groupEndFields.clear();\n for (const g of groups) {\n const lastCol = g.columns[g.columns.length - 1];\n if (lastCol?.field) {\n this.#groupEndFields.add(lastCol.field);\n }\n }\n\n // Return columns with group info applied\n return processedColumns;\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.isActive) {\n // Remove any existing group header\n const header = this.gridElement?.querySelector('.header');\n const existingGroupRow = header?.querySelector('.header-group-row');\n if (existingGroupRow) existingGroupRow.remove();\n return;\n }\n\n const header = this.gridElement?.querySelector('.header');\n if (!header) return;\n\n // Remove existing group row if present\n const existingGroupRow = header.querySelector('.header-group-row');\n if (existingGroupRow) existingGroupRow.remove();\n\n // Recompute groups from visible columns only (hidden columns have no CSS grid track).\n // This also picks up any plugin-added columns (e.g. expander) that weren't present\n // during processColumns.\n const finalColumns = this.visibleColumns as ColumnConfig[];\n const groups = computeColumnGroups(finalColumns);\n if (groups.length === 0) return;\n\n // Keep #groupEndFields in sync for afterCellRender (covers scheduler-driven renders)\n this.#groupEndFields.clear();\n for (let gi = 0; gi < groups.length; gi++) {\n const g = groups[gi];\n const lastCol = g.columns[g.columns.length - 1];\n // Don't mark the last group — no adjacent group follows it\n if (lastCol?.field && gi < groups.length - 1) {\n this.#groupEndFields.add(lastCol.field);\n }\n }\n\n // Build and insert group header row\n const groupRow = buildGroupHeaderRow(groups, finalColumns);\n if (groupRow) {\n // Toggle border visibility class\n groupRow.classList.toggle('no-borders', !this.config.showGroupBorders);\n\n const headerRow = header.querySelector('.header-row');\n if (headerRow) {\n header.insertBefore(groupRow, headerRow);\n } else {\n header.appendChild(groupRow);\n }\n }\n\n // Apply classes to header cells\n const headerRow = header.querySelector('.header-row') as HTMLElement;\n if (headerRow) {\n // Toggle border visibility on header cells\n headerRow.classList.toggle('no-group-borders', !this.config.showGroupBorders);\n applyGroupedHeaderCellClasses(headerRow, groups, finalColumns);\n }\n }\n\n /**\n * Apply group-end class to individual cells during render and scroll.\n * This is more efficient than querySelectorAll in afterRender and ensures\n * cells recycled during scroll also get the class applied.\n * @internal\n */\n override afterCellRender(context: AfterCellRenderContext): void {\n if (!this.isActive || !this.config.showGroupBorders) return;\n context.cellElement.classList.toggle('group-end', this.#groupEndFields.has(context.column.field));\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Check if column groups are active.\n * @returns Whether grouping is active\n */\n isGroupingActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Get the computed column groups.\n * @returns Array of column groups\n */\n getGroups(): ColumnGroup[] {\n return this.groups;\n }\n\n /**\n * Get columns in a specific group.\n * @param groupId - The group ID to find\n * @returns Array of columns in the group\n */\n getGroupColumns(groupId: string): ColumnConfig[] {\n const group = this.groups.find((g) => g.id === groupId);\n return group ? group.columns : [];\n }\n\n /**\n * Refresh column groups (recompute from current columns).\n */\n refresh(): void {\n this.requestRender();\n }\n // #endregion\n}\n","/**\n * Row Grouping Core Logic\n *\n * Pure functions for building grouped row models and aggregations.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { DefaultExpandedValue, GroupRowModelItem, RenderRow, RowGroupingConfig } from './types';\n\n// Re-export aggregator functions from core for backward compatibility\nexport { getAggregator, listAggregators, registerAggregator, runAggregator } from '../../core/internal/aggregators';\n\ninterface GroupNode {\n key: string; // composite key\n value: any;\n depth: number;\n rows: any[];\n children: Map<string, GroupNode>;\n parent?: GroupNode;\n}\n\ninterface BuildGroupingArgs {\n rows: any[];\n config: RowGroupingConfig;\n expanded: Set<string>;\n /** Initial expanded state to apply (processed by the plugin) */\n initialExpanded?: Set<string>;\n}\n\n/**\n * Build a flattened grouping projection (collapsed by default).\n * Returns empty array when groupOn not configured or all rows ungrouped.\n *\n * @param args - The grouping arguments\n * @returns Flattened array of render rows (groups + data rows)\n */\nexport function buildGroupedRowModel({ rows, config, expanded, initialExpanded }: BuildGroupingArgs): RenderRow[] {\n const groupOn = config.groupOn;\n if (typeof groupOn !== 'function') {\n return [];\n }\n\n const root: GroupNode = { key: '__root__', value: null, depth: -1, rows: [], children: new Map() };\n\n // Build tree structure\n rows.forEach((r) => {\n let path: any = groupOn(r);\n if (path == null || path === false) path = ['__ungrouped__'];\n else if (!Array.isArray(path)) path = [path];\n\n let parent = root;\n path.forEach((rawVal: any, depthIdx: number) => {\n const seg = rawVal == null ? '∅' : String(rawVal);\n const composite = parent.key === '__root__' ? seg : parent.key + '||' + seg;\n let node = parent.children.get(seg);\n if (!node) {\n node = { key: composite, value: rawVal, depth: depthIdx, rows: [], children: new Map(), parent };\n parent.children.set(seg, node);\n }\n parent = node;\n });\n parent.rows.push(r);\n });\n\n // All ungrouped? treat as no grouping\n if (root.children.size === 1 && root.children.has('__ungrouped__')) {\n const only = root.children.get('__ungrouped__')!;\n if (only.rows.length === rows.length) return [];\n }\n\n // Merge expanded sets - use initialExpanded on first render, then expanded takes over\n const effectiveExpanded = new Set([...expanded, ...(initialExpanded ?? [])]);\n\n // Flatten tree to array\n const flat: RenderRow[] = [];\n const visit = (node: GroupNode) => {\n if (node === root) {\n node.children.forEach((c) => visit(c));\n return;\n }\n\n const isExpanded = effectiveExpanded.has(node.key);\n flat.push({\n kind: 'group',\n key: node.key,\n value: node.value,\n depth: node.depth,\n rows: node.rows,\n expanded: isExpanded,\n });\n\n if (isExpanded) {\n if (node.children.size) {\n node.children.forEach((c) => visit(c));\n } else {\n node.rows.forEach((r) => flat.push({ kind: 'data', row: r, rowIndex: rows.indexOf(r) }));\n }\n }\n };\n visit(root);\n\n return flat;\n}\n\n/**\n * Toggle expansion state for a group key.\n *\n * @param expandedKeys - Current set of expanded keys\n * @param key - The group key to toggle\n * @returns New set with toggled state\n */\nexport function toggleGroupExpansion(expandedKeys: Set<string>, key: string): Set<string> {\n const newSet = new Set(expandedKeys);\n if (newSet.has(key)) {\n newSet.delete(key);\n } else {\n newSet.add(key);\n }\n return newSet;\n}\n\n/**\n * Expand all groups.\n *\n * @param rows - The flattened render rows\n * @returns Set of all group keys\n */\nexport function expandAllGroups(rows: RenderRow[]): Set<string> {\n const keys = new Set<string>();\n for (const row of rows) {\n if (row.kind === 'group') {\n keys.add(row.key);\n }\n }\n return keys;\n}\n\n/**\n * Collapse all groups.\n *\n * @returns Empty set\n */\nexport function collapseAllGroups(): Set<string> {\n return new Set();\n}\n\n/**\n * Resolve a defaultExpanded value to a set of keys to expand.\n * This needs to be called AFTER building the group model to get all keys.\n *\n * @param value - The defaultExpanded config value\n * @param allGroupKeys - All group keys from the model\n * @returns Set of keys to expand initially\n */\nexport function resolveDefaultExpanded(value: DefaultExpandedValue, allGroupKeys: string[]): Set<string> {\n if (value === true) {\n // Expand all groups\n return new Set(allGroupKeys);\n }\n if (value === false || value == null) {\n // Collapse all groups\n return new Set();\n }\n if (typeof value === 'number') {\n // Expand group at this index\n const key = allGroupKeys[value];\n return key ? new Set([key]) : new Set();\n }\n if (typeof value === 'string') {\n // Expand group with this key\n return new Set([value]);\n }\n if (Array.isArray(value)) {\n // Expand groups with these keys\n return new Set(value);\n }\n return new Set();\n}\n\n/**\n * Get all group keys from a flattened model.\n *\n * @param rows - The flattened render rows\n * @returns Array of group keys\n */\nexport function getGroupKeys(rows: RenderRow[]): string[] {\n return rows.filter((r): r is GroupRowModelItem => r.kind === 'group').map((r) => r.key);\n}\n\n/**\n * Count total rows in a group (including nested groups).\n *\n * @param groupRow - The group row\n * @returns Total row count\n */\nexport function getGroupRowCount(groupRow: RenderRow): number {\n if (groupRow.kind !== 'group') return 0;\n return groupRow.rows.length;\n}\n","/**\n * Row Grouping Plugin (Class-based)\n *\n * Enables hierarchical row grouping with expand/collapse and aggregations.\n */\n\nimport { BaseGridPlugin, CellClickEvent, type PluginManifest, type PluginQuery } from '../../core/plugin/base-plugin';\nimport { isExpanderColumn } from '../../core/plugin/expander-column';\nimport type { RowElementInternal } from '../../core/types';\nimport {\n buildGroupedRowModel,\n collapseAllGroups,\n expandAllGroups,\n getGroupKeys,\n getGroupRowCount,\n resolveDefaultExpanded,\n runAggregator,\n toggleGroupExpansion,\n} from './grouping-rows';\nimport styles from './grouping-rows.css?inline';\nimport type {\n ExpandCollapseAnimation,\n GroupingRowsConfig,\n GroupRowModelItem,\n GroupToggleDetail,\n RenderRow,\n} from './types';\n\n/**\n * Group state information returned by getGroupState()\n */\nexport interface GroupState {\n /** Whether grouping is currently active */\n isActive: boolean;\n /** Number of expanded groups */\n expandedCount: number;\n /** Total number of groups */\n totalGroups: number;\n /** Array of expanded group keys */\n expandedKeys: string[];\n}\n\n/**\n * Row Grouping Plugin for tbw-grid\n *\n * Organizes rows into collapsible hierarchical groups. Perfect for organizing data\n * by category, department, status, or any other dimension—or even multiple dimensions\n * for nested grouping. Includes aggregation support for summarizing group data.\n *\n * ## Installation\n *\n * ```ts\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `groupOn` | `(row) => string[]` | - | Callback returning group path array |\n * | `defaultExpanded` | `boolean \\\\| number \\\\| string \\\\| string[]` | `false` | Initial expanded state |\n * | `showRowCount` | `boolean` | `true` | Show row count in group header |\n * | `indentWidth` | `number` | `20` | Indentation per level (pixels) |\n * | `fullWidth` | `boolean` | `true` | Group row spans full width |\n * | `animation` | `false \\\\| 'slide' \\\\| 'fade'` | `'slide'` | Expand/collapse animation |\n * | `accordion` | `boolean` | `false` | Only one group open at a time |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `expandGroup` | `(path: string[]) => void` | Expand a specific group |\n * | `collapseGroup` | `(path: string[]) => void` | Collapse a specific group |\n * | `expandAll` | `() => void` | Expand all groups |\n * | `collapseAll` | `() => void` | Collapse all groups |\n * | `isGroupExpanded` | `(path: string[]) => boolean` | Check if group is expanded |\n * | `getGroupState` | `() => GroupState` | Get current grouping state |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-group-indent-width` | `1.25em` | Indentation per group level |\n * | `--tbw-grouping-rows-bg` | `var(--tbw-color-panel-bg)` | Group row background |\n * | `--tbw-grouping-rows-count-color` | `var(--tbw-color-fg-muted)` | Count badge color |\n * | `--tbw-animation-duration` | `200ms` | Expand/collapse animation |\n *\n * @example Single-Level Grouping by Department\n * ```ts\n * import '@toolbox-web/grid';\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/plugins/grouping-rows';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Employee' },\n * { field: 'department', header: 'Department' },\n * { field: 'salary', header: 'Salary', type: 'currency' },\n * ],\n * plugins: [\n * new GroupingRowsPlugin({\n * groupOn: (row) => [row.department],\n * showRowCount: true,\n * defaultExpanded: false,\n * }),\n * ],\n * };\n * ```\n *\n * @example Multi-Level Grouping\n * ```ts\n * new GroupingRowsPlugin({\n * groupOn: (row) => [row.region, row.department, row.team],\n * indentWidth: 24,\n * animation: 'slide',\n * })\n * ```\n *\n * @see {@link GroupingRowsConfig} for all configuration options\n * @see {@link GroupState} for the group state structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class GroupingRowsPlugin extends BaseGridPlugin<GroupingRowsConfig> {\n /**\n * Plugin manifest - declares configuration validation rules and events.\n * @internal\n */\n static override readonly manifest: PluginManifest<GroupingRowsConfig> = {\n events: [\n {\n type: 'grouping-state-change',\n description: 'Emitted when groups are expanded/collapsed. Subscribers can react to row visibility changes.',\n },\n ],\n queries: [\n {\n type: 'canMoveRow',\n description: 'Returns false for group header rows (cannot be reordered)',\n },\n ],\n configRules: [\n {\n id: 'groupingRows/accordion-defaultExpanded',\n severity: 'warn',\n message:\n `\"accordion: true\" and \"defaultExpanded\" (non-false) are used together.\\n` +\n ` → In accordion mode, only one group can be open at a time.\\n` +\n ` → Using defaultExpanded with multiple groups will collapse to one on first toggle.\\n` +\n ` → Consider using \"defaultExpanded: false\" or a single group key/index with accordion mode.`,\n check: (config) =>\n config.accordion === true &&\n config.defaultExpanded !== false &&\n config.defaultExpanded !== undefined &&\n // Allow single group expansion with accordion\n !(typeof config.defaultExpanded === 'number') &&\n !(typeof config.defaultExpanded === 'string') &&\n // Warn if true or array with multiple items\n (config.defaultExpanded === true ||\n (Array.isArray(config.defaultExpanded) && config.defaultExpanded.length > 1)),\n },\n ],\n };\n\n /** @internal */\n readonly name = 'groupingRows';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<GroupingRowsConfig> {\n return {\n defaultExpanded: false,\n showRowCount: true,\n indentWidth: 20,\n aggregators: {},\n animation: 'slide',\n accordion: false,\n };\n }\n\n // #region Internal State\n private expandedKeys: Set<string> = new Set();\n private flattenedRows: RenderRow[] = [];\n private isActive = false;\n private previousVisibleKeys = new Set<string>();\n private keysToAnimate = new Set<string>();\n /** Track if initial defaultExpanded has been applied */\n private hasAppliedDefaultExpanded = false;\n // #endregion\n\n // #region Animation\n\n /**\n * Get expand/collapse animation style from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide';\n }\n\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.expandedKeys.clear();\n this.flattenedRows = [];\n this.isActive = false;\n this.previousVisibleKeys.clear();\n this.keysToAnimate.clear();\n this.hasAppliedDefaultExpanded = false;\n }\n\n /**\n * Provide row height for group header rows.\n *\n * If `groupRowHeight` is configured, returns that value for group rows.\n * This allows the variable row height system to use known heights for\n * group headers without needing to measure them from the DOM.\n *\n * @param row - The row object (may be a group row)\n * @param _index - Index in the processed rows array (unused)\n * @returns Height in pixels for group rows, undefined for data rows\n *\n * @internal Plugin hook for variable row height support\n */\n override getRowHeight(row: unknown, _index: number): number | undefined {\n // Only provide height if groupRowHeight is configured\n if (this.config.groupRowHeight == null) return undefined;\n\n // Check if this is a group row\n if ((row as { __isGroupRow?: boolean }).__isGroupRow === true) {\n return this.config.groupRowHeight;\n }\n\n return undefined;\n }\n\n /**\n * Handle plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'canMoveRow') {\n // Group header rows cannot be reordered\n const row = query.context as { __isGroupRow?: boolean } | null | undefined;\n if (row?.__isGroupRow === true) {\n return false;\n }\n }\n return undefined;\n }\n // #endregion\n\n // #region Hooks\n\n /**\n * Auto-detect grouping configuration from grid config.\n * Called by plugin system to determine if plugin should activate.\n */\n static detect(rows: readonly any[], config: any): boolean {\n return typeof config?.groupOn === 'function' || typeof config?.enableRowGrouping === 'boolean';\n }\n\n /** @internal */\n override processRows(rows: readonly any[]): any[] {\n const config = this.config;\n\n // Check if grouping is configured\n if (typeof config.groupOn !== 'function') {\n this.isActive = false;\n this.flattenedRows = [];\n return [...rows];\n }\n\n // First build: get structure to know all group keys\n // (needed for index-based defaultExpanded)\n const initialBuild = buildGroupedRowModel({\n rows: [...rows],\n config: config,\n expanded: new Set(), // Empty to get all root groups\n });\n\n // If no grouping produced, return original rows\n if (initialBuild.length === 0) {\n this.isActive = false;\n this.flattenedRows = [];\n return [...rows];\n }\n\n // Resolve defaultExpanded on first render only\n let initialExpanded: Set<string> | undefined;\n if (!this.hasAppliedDefaultExpanded && this.expandedKeys.size === 0 && config.defaultExpanded !== false) {\n const allKeys = getGroupKeys(initialBuild);\n initialExpanded = resolveDefaultExpanded(config.defaultExpanded ?? false, allKeys);\n\n // Mark as applied and populate expandedKeys for subsequent toggles\n if (initialExpanded.size > 0) {\n this.expandedKeys = new Set(initialExpanded);\n this.hasAppliedDefaultExpanded = true;\n }\n }\n\n // Build with proper expanded state\n const grouped = buildGroupedRowModel({\n rows: [...rows],\n config: config,\n expanded: this.expandedKeys,\n initialExpanded,\n });\n\n this.isActive = true;\n this.flattenedRows = grouped;\n\n // Track which data rows are newly visible (for animation)\n this.keysToAnimate.clear();\n const currentVisibleKeys = new Set<string>();\n grouped.forEach((item, idx) => {\n if (item.kind === 'data') {\n const key = `data-${idx}`;\n currentVisibleKeys.add(key);\n if (!this.previousVisibleKeys.has(key)) {\n this.keysToAnimate.add(key);\n }\n }\n });\n this.previousVisibleKeys = currentVisibleKeys;\n\n // Return flattened rows for rendering\n // The grid will need to handle group rows specially\n return grouped.map((item) => {\n if (item.kind === 'group') {\n return {\n __isGroupRow: true,\n __groupKey: item.key,\n __groupValue: item.value,\n __groupDepth: item.depth,\n __groupRows: item.rows,\n __groupExpanded: item.expanded,\n __groupRowCount: getGroupRowCount(item),\n // Cache key for variable row height support - survives expand/collapse\n __rowCacheKey: `group:${item.key}`,\n };\n }\n return item.row;\n });\n }\n\n /** @internal */\n override onCellClick(event: CellClickEvent): boolean | void {\n const row = event.row as Record<string, unknown> | undefined;\n\n // Check if this is a group row toggle\n if (row?.__isGroupRow) {\n const target = event.originalEvent.target as HTMLElement;\n if (target?.closest('.group-toggle')) {\n this.toggle(row.__groupKey as string);\n return true; // Prevent default\n }\n }\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion on group rows\n if (event.key !== ' ') return;\n\n const focusRow = this.grid._focusRow;\n const row = this.rows[focusRow] as Record<string, unknown> | undefined;\n\n // Only handle SPACE on group rows\n if (!row?.__isGroupRow) return;\n\n event.preventDefault();\n this.toggle(row.__groupKey as string);\n\n // Restore focus styling after render completes via render pipeline\n this.requestRenderWithFocus();\n return true;\n }\n\n /**\n * Render a row. Returns true if we handled the row (group row), false otherwise.\n * @internal\n */\n override renderRow(row: any, rowEl: HTMLElement, _rowIndex: number): boolean {\n // Only handle group rows\n if (!row?.__isGroupRow) {\n return false;\n }\n\n const config = this.config;\n\n // If a custom renderer is provided, use it\n if (config.groupRowRenderer) {\n const toggleExpand = () => {\n this.toggle(row.__groupKey);\n };\n\n const result = config.groupRowRenderer({\n key: row.__groupKey,\n value: row.__groupValue,\n depth: row.__groupDepth,\n rows: row.__groupRows,\n expanded: row.__groupExpanded,\n toggleExpand,\n });\n\n if (result) {\n rowEl.className = 'data-grid-row group-row';\n (rowEl as RowElementInternal).__isCustomRow = true; // Mark for proper class reset on recycle\n rowEl.setAttribute('data-group-depth', String(row.__groupDepth));\n if (typeof result === 'string') {\n rowEl.innerHTML = result;\n } else {\n rowEl.innerHTML = '';\n rowEl.appendChild(result);\n }\n return true;\n }\n }\n\n // Helper to toggle expansion\n const handleToggle = () => {\n this.toggle(row.__groupKey);\n };\n\n // Default group row rendering - keep data-grid-row class for focus/keyboard navigation\n rowEl.className = 'data-grid-row group-row';\n (rowEl as RowElementInternal).__isCustomRow = true; // Mark for proper class reset on recycle\n rowEl.setAttribute('data-group-depth', String(row.__groupDepth));\n rowEl.setAttribute('role', 'row');\n rowEl.setAttribute('aria-expanded', String(row.__groupExpanded));\n // Use CSS variable for depth-based indentation\n rowEl.style.setProperty('--tbw-group-depth', String(row.__groupDepth || 0));\n if (config.indentWidth !== undefined) {\n rowEl.style.setProperty('--tbw-group-indent-width', `${config.indentWidth}px`);\n }\n // Clear any inline height from previous use (e.g., responsive card mode sets height: auto)\n // This ensures group rows use CSS-defined height, not stale inline styles from recycled elements\n rowEl.style.height = '';\n rowEl.innerHTML = '';\n\n const isFullWidth = config.fullWidth !== false; // default true\n\n if (isFullWidth) {\n this.renderFullWidthGroupRow(row, rowEl, handleToggle);\n } else {\n this.renderPerColumnGroupRow(row, rowEl, handleToggle);\n }\n\n return true;\n }\n\n /** @internal */\n override afterRender(): void {\n const style = this.animationStyle;\n if (style === false || this.keysToAnimate.size === 0) return;\n\n const body = this.gridElement?.querySelector('.rows');\n if (!body) return;\n\n const animClass = style === 'fade' ? 'tbw-group-fade-in' : 'tbw-group-slide-in';\n for (const rowEl of body.querySelectorAll('.data-grid-row:not(.group-row)')) {\n const cell = rowEl.querySelector('.cell[data-row]');\n const idx = cell ? parseInt(cell.getAttribute('data-row') ?? '-1', 10) : -1;\n const item = this.flattenedRows[idx];\n const key = item?.kind === 'data' ? `data-${idx}` : undefined;\n\n if (key && this.keysToAnimate.has(key)) {\n rowEl.classList.add(animClass);\n rowEl.addEventListener('animationend', () => rowEl.classList.remove(animClass), { once: true });\n }\n }\n this.keysToAnimate.clear();\n }\n // #endregion\n\n // #region Private Rendering Helpers\n\n /**\n * Create a toggle button for expanding/collapsing a group.\n */\n private createToggleButton(expanded: boolean, handleToggle: () => void): HTMLButtonElement {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = `group-toggle${expanded ? ' expanded' : ''}`;\n btn.setAttribute('aria-label', expanded ? 'Collapse group' : 'Expand group');\n this.setIcon(btn, this.resolveIcon(expanded ? 'collapse' : 'expand'));\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n handleToggle();\n });\n return btn;\n }\n\n /**\n * Get the formatted label text for a group.\n */\n private getGroupLabelText(value: unknown, depth: number, key: string): string {\n const config = this.config;\n return config.formatLabel ? config.formatLabel(value, depth, key) : String(value);\n }\n\n private renderFullWidthGroupRow(row: any, rowEl: HTMLElement, handleToggle: () => void): void {\n const config = this.config;\n const aggregators = config.aggregators ?? {};\n const groupRows = row.__groupRows ?? [];\n\n // Full-width mode: single spanning cell with toggle + label + count + aggregates\n const cell = document.createElement('div');\n cell.className = 'cell group-full';\n cell.style.gridColumn = '1 / -1';\n cell.setAttribute('role', 'gridcell');\n cell.setAttribute('data-col', '0'); // Required for focus/click delegation\n\n // Toggle button\n cell.appendChild(this.createToggleButton(row.__groupExpanded, handleToggle));\n\n // Group label\n const label = document.createElement('span');\n label.className = 'group-label';\n label.textContent = this.getGroupLabelText(row.__groupValue, row.__groupDepth || 0, row.__groupKey);\n cell.appendChild(label);\n\n // Row count\n if (config.showRowCount !== false) {\n const count = document.createElement('span');\n count.className = 'group-count';\n count.textContent = `(${row.__groupRowCount ?? row.__groupRows?.length ?? 0})`;\n cell.appendChild(count);\n }\n\n // Render aggregates if configured\n const aggregatorEntries = Object.entries(aggregators);\n if (aggregatorEntries.length > 0) {\n const aggregatesContainer = document.createElement('span');\n aggregatesContainer.className = 'group-aggregates';\n\n for (const [field, aggRef] of aggregatorEntries) {\n const col = this.columns.find((c) => c.field === field);\n const result = runAggregator(aggRef, groupRows, field, col);\n if (result != null) {\n const aggSpan = document.createElement('span');\n aggSpan.className = 'group-aggregate';\n aggSpan.setAttribute('data-field', field);\n // Use column header as label if available\n const colHeader = col?.header ?? field;\n aggSpan.textContent = `${colHeader}: ${result}`;\n aggregatesContainer.appendChild(aggSpan);\n }\n }\n\n if (aggregatesContainer.children.length > 0) {\n cell.appendChild(aggregatesContainer);\n }\n }\n\n rowEl.appendChild(cell);\n }\n\n private renderPerColumnGroupRow(row: any, rowEl: HTMLElement, handleToggle: () => void): void {\n const config = this.config;\n const aggregators = config.aggregators ?? {};\n const columns = this.columns;\n const groupRows = row.__groupRows ?? [];\n\n // Get grid template from the grid element\n const bodyEl = this.gridElement?.querySelector('.body') as HTMLElement | null;\n const gridTemplate = bodyEl?.style.gridTemplateColumns || '';\n if (gridTemplate) {\n rowEl.style.display = 'grid';\n rowEl.style.gridTemplateColumns = gridTemplate;\n }\n\n // Track whether we've rendered the toggle button yet (should be in first non-expander column)\n let toggleRendered = false;\n\n columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell group-cell';\n cell.setAttribute('data-col', String(colIdx));\n cell.setAttribute('role', 'gridcell');\n\n // Skip expander columns (they're handled by other plugins like MasterDetail/Tree)\n // but still render an empty cell to maintain grid structure\n if (isExpanderColumn(col)) {\n cell.setAttribute('data-field', col.field);\n rowEl.appendChild(cell);\n return;\n }\n\n // First non-expander column gets the toggle button + label\n if (!toggleRendered) {\n toggleRendered = true;\n cell.appendChild(this.createToggleButton(row.__groupExpanded, handleToggle));\n\n const label = document.createElement('span');\n const firstColAgg = aggregators[col.field];\n if (firstColAgg) {\n const aggResult = runAggregator(firstColAgg, groupRows, col.field, col);\n label.textContent = aggResult != null ? String(aggResult) : String(row.__groupValue);\n } else {\n label.textContent = this.getGroupLabelText(row.__groupValue, row.__groupDepth || 0, row.__groupKey);\n }\n cell.appendChild(label);\n\n if (config.showRowCount !== false) {\n const count = document.createElement('span');\n count.className = 'group-count';\n count.textContent = ` (${groupRows.length})`;\n cell.appendChild(count);\n }\n } else {\n // Other columns: run aggregator if defined\n const aggRef = aggregators[col.field];\n if (aggRef) {\n const result = runAggregator(aggRef, groupRows, col.field, col);\n cell.textContent = result != null ? String(result) : '';\n } else {\n cell.textContent = '';\n }\n }\n\n rowEl.appendChild(cell);\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Expand all groups.\n */\n expandAll(): void {\n this.expandedKeys = expandAllGroups(this.flattenedRows);\n this.emitPluginEvent('grouping-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n /**\n * Collapse all groups.\n */\n collapseAll(): void {\n this.expandedKeys = collapseAllGroups();\n this.emitPluginEvent('grouping-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n /**\n * Toggle expansion of a specific group.\n * In accordion mode, expanding a group will collapse all sibling groups.\n * @param key - The group key to toggle\n */\n toggle(key: string): void {\n const isExpanding = !this.expandedKeys.has(key);\n const config = this.config;\n\n // Find the group to get its depth for accordion mode\n const group = this.flattenedRows.find((r) => r.kind === 'group' && r.key === key) as GroupRowModelItem | undefined;\n\n // In accordion mode, collapse sibling groups when expanding\n if (config.accordion && isExpanding && group) {\n const newKeys = new Set<string>();\n // Keep only ancestors (keys that are prefixes of the current key) and the current key\n for (const existingKey of this.expandedKeys) {\n // Check if existingKey is an ancestor of the toggled key\n // Ancestors have composite keys that are prefixes of child keys (separated by '||')\n if (key.startsWith(existingKey + '||') || existingKey.startsWith(key + '||')) {\n // This is an ancestor or descendant - keep it only if ancestor\n if (key.startsWith(existingKey + '||')) {\n newKeys.add(existingKey);\n }\n } else {\n // Check depth - only keep groups at different depths\n const existingGroup = this.flattenedRows.find((r) => r.kind === 'group' && r.key === existingKey) as\n | GroupRowModelItem\n | undefined;\n if (existingGroup && existingGroup.depth !== group.depth) {\n newKeys.add(existingKey);\n }\n }\n }\n newKeys.add(key);\n this.expandedKeys = newKeys;\n } else {\n this.expandedKeys = toggleGroupExpansion(this.expandedKeys, key);\n }\n\n this.emit<GroupToggleDetail>('group-toggle', {\n key,\n expanded: this.expandedKeys.has(key),\n value: group?.value,\n depth: group?.depth ?? 0,\n });\n\n // Notify other plugins that grouping state changed (row visibility changed)\n this.emitPluginEvent('grouping-state-change', {\n expandedKeys: [...this.expandedKeys],\n });\n\n this.requestRender();\n }\n\n /**\n * Check if a specific group is expanded.\n * @param key - The group key to check\n * @returns Whether the group is expanded\n */\n isExpanded(key: string): boolean {\n return this.expandedKeys.has(key);\n }\n\n /**\n * Expand a specific group.\n * @param key - The group key to expand\n */\n expand(key: string): void {\n if (!this.expandedKeys.has(key)) {\n this.expandedKeys = new Set([...this.expandedKeys, key]);\n this.requestRender();\n }\n }\n\n /**\n * Collapse a specific group.\n * @param key - The group key to collapse\n */\n collapse(key: string): void {\n if (this.expandedKeys.has(key)) {\n const newKeys = new Set(this.expandedKeys);\n newKeys.delete(key);\n this.expandedKeys = newKeys;\n this.requestRender();\n }\n }\n\n /**\n * Get the current group state.\n * @returns Group state information\n */\n getGroupState(): GroupState {\n const groupRows = this.flattenedRows.filter((r) => r.kind === 'group');\n return {\n isActive: this.isActive,\n expandedCount: this.expandedKeys.size,\n totalGroups: groupRows.length,\n expandedKeys: [...this.expandedKeys],\n };\n }\n\n /**\n * Get the total count of visible rows (including group headers).\n * @returns Number of visible rows\n */\n getRowCount(): number {\n return this.flattenedRows.length;\n }\n\n /**\n * Refresh the grouped row model.\n * Call this after modifying groupOn or other config options.\n */\n refreshGroups(): void {\n this.requestRender();\n }\n\n /**\n * Get current expanded group keys.\n * @returns Array of expanded group keys\n */\n getExpandedGroups(): string[] {\n return [...this.expandedKeys];\n }\n\n /**\n * Get the flattened row model.\n * @returns Array of render rows (groups + data rows)\n */\n getFlattenedRows(): RenderRow[] {\n return this.flattenedRows;\n }\n\n /**\n * Check if grouping is currently active.\n * @returns Whether grouping is active\n */\n isGroupingActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Set the groupOn function dynamically.\n * @param fn - The groupOn function or undefined to disable\n */\n setGroupOn(fn: ((row: any) => any[] | any | null | false) | undefined): void {\n (this.config as GroupingRowsConfig).groupOn = fn;\n this.requestRender();\n }\n // #endregion\n}\n","/**\n * Master/Detail Core Logic\n *\n * Pure functions for managing detail row expansion state.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n// Uses `any` for maximum flexibility with user-defined row types.\n\n/**\n * Toggle the expansion state of a detail row.\n * Returns a new Set with the updated state.\n */\nexport function toggleDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n if (newExpanded.has(row)) {\n newExpanded.delete(row);\n } else {\n newExpanded.add(row);\n }\n return newExpanded;\n}\n\n/**\n * Expand a detail row.\n * Returns a new Set with the row added.\n */\nexport function expandDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n newExpanded.add(row);\n return newExpanded;\n}\n\n/**\n * Collapse a detail row.\n * Returns a new Set with the row removed.\n */\nexport function collapseDetailRow(expandedRows: Set<object>, row: object): Set<object> {\n const newExpanded = new Set(expandedRows);\n newExpanded.delete(row);\n return newExpanded;\n}\n\n/**\n * Check if a detail row is expanded.\n */\nexport function isDetailExpanded(expandedRows: Set<object>, row: object): boolean {\n return expandedRows.has(row);\n}\n\n/**\n * Create a detail element for a given row.\n * The element spans all columns and contains the rendered content.\n */\nexport function createDetailElement(\n row: any,\n rowIndex: number,\n renderer: (row: any, rowIndex: number) => HTMLElement | string,\n columnCount: number\n): HTMLElement {\n const detailRow = document.createElement('div');\n detailRow.className = 'master-detail-row';\n detailRow.setAttribute('data-detail-for', String(rowIndex));\n detailRow.setAttribute('role', 'row');\n\n const detailCell = document.createElement('div');\n detailCell.className = 'master-detail-cell';\n detailCell.setAttribute('role', 'cell');\n detailCell.style.gridColumn = `1 / ${columnCount + 1}`;\n\n const content = renderer(row, rowIndex);\n if (typeof content === 'string') {\n detailCell.innerHTML = content;\n } else if (content instanceof HTMLElement) {\n detailCell.appendChild(content);\n }\n\n detailRow.appendChild(detailCell);\n return detailRow;\n}\n","/**\n * Master/Detail Plugin (Class-based)\n *\n * Enables expandable detail rows showing additional content for each row.\n * Animation style is plugin-configured; respects grid-level animation.mode.\n */\n\nimport { evalTemplateString, sanitizeHTML } from '../../core/internal/sanitize';\nimport { BaseGridPlugin, CellClickEvent, GridElement, RowClickEvent } from '../../core/plugin/base-plugin';\nimport { createExpanderColumnConfig, findExpanderColumn, isExpanderColumn } from '../../core/plugin/expander-column';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n collapseDetailRow,\n createDetailElement,\n expandDetailRow,\n isDetailExpanded,\n toggleDetailRow,\n} from './master-detail';\nimport styles from './master-detail.css?inline';\nimport type { DetailExpandDetail, ExpandCollapseAnimation, MasterDetailConfig } from './types';\n\n/**\n * Master-Detail Plugin for tbw-grid\n *\n * Creates expandable detail rows that reveal additional content beneath each master row.\n * Perfect for order/line-item UIs, employee/department views, or any scenario where\n * you need to show related data without navigating away.\n *\n * ## Installation\n *\n * ```ts\n * import { MasterDetailPlugin } from '@toolbox-web/grid/plugins/master-detail';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `detailRenderer` | `(row) => HTMLElement \\| string` | required | Render function for detail content |\n * | `expandOnRowClick` | `boolean` | `false` | Expand when clicking the row |\n * | `detailHeight` | `number \\| 'auto'` | `'auto'` | Fixed height or auto-size |\n * | `collapseOnClickOutside` | `boolean` | `false` | Collapse when clicking outside |\n * | `showExpandColumn` | `boolean` | `true` | Show expand/collapse column |\n * | `animation` | `false \\| 'slide' \\| 'fade'` | `'slide'` | Animation style |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `expandRow` | `(rowIndex) => void` | Expand a specific row |\n * | `collapseRow` | `(rowIndex) => void` | Collapse a specific row |\n * | `toggleRow` | `(rowIndex) => void` | Toggle row expansion |\n * | `expandAll` | `() => void` | Expand all rows |\n * | `collapseAll` | `() => void` | Collapse all rows |\n * | `isRowExpanded` | `(rowIndex) => boolean` | Check if row is expanded |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-master-detail-bg` | `var(--tbw-color-row-alt)` | Detail row background |\n * | `--tbw-master-detail-border` | `var(--tbw-color-border)` | Detail row border |\n * | `--tbw-detail-padding` | `1em` | Detail content padding |\n * | `--tbw-animation-duration` | `200ms` | Expand/collapse animation |\n *\n * @example Basic Master-Detail with HTML Template\n * ```ts\n * import '@toolbox-web/grid';\n * import { MasterDetailPlugin } from '@toolbox-web/grid/plugins/master-detail';\n *\n * grid.gridConfig = {\n * columns: [\n * { field: 'orderId', header: 'Order ID' },\n * { field: 'customer', header: 'Customer' },\n * { field: 'total', header: 'Total', type: 'currency' },\n * ],\n * plugins: [\n * new MasterDetailPlugin({\n * detailRenderer: (row) => `\n * <div class=\"order-details\">\n * <h4>Order Items</h4>\n * <ul>${row.items.map(i => `<li>${i.name} - $${i.price}</li>`).join('')}</ul>\n * </div>\n * `,\n * }),\n * ],\n * };\n * ```\n *\n * @example Nested Grid in Detail\n * ```ts\n * new MasterDetailPlugin({\n * detailRenderer: (row) => {\n * const childGrid = document.createElement('tbw-grid');\n * childGrid.style.height = '200px';\n * childGrid.gridConfig = { columns: [...] };\n * childGrid.rows = row.items || [];\n * return childGrid;\n * },\n * })\n * ```\n *\n * @see {@link MasterDetailConfig} for all configuration options\n * @see {@link DetailExpandDetail} for expand/collapse event details\n *\n * @internal Extends BaseGridPlugin\n */\nexport class MasterDetailPlugin extends BaseGridPlugin<MasterDetailConfig> {\n /** @internal */\n readonly name = 'masterDetail';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<MasterDetailConfig> {\n return {\n detailHeight: 'auto',\n expandOnRowClick: false,\n collapseOnClickOutside: false,\n // Note: showExpandColumn is intentionally NOT defaulted here.\n // If undefined, processColumns() adds expander only when detailRenderer is provided.\n // Set to true for framework adapters that register renderers asynchronously.\n animation: 'slide', // Plugin's own default\n };\n }\n\n // #region Light DOM Parsing\n\n /**\n * Called when plugin is attached to the grid.\n * Parses light DOM for `<tbw-grid-detail>` elements to configure detail templates.\n * @internal\n */\n override attach(grid: GridElement): void {\n super.attach(grid);\n this.parseLightDomDetail();\n }\n\n /**\n * Parse `<tbw-grid-detail>` elements from the grid's light DOM.\n *\n * Allows declarative configuration:\n * ```html\n * <tbw-grid [rows]=\"data\">\n * <tbw-grid-detail>\n * <div class=\"detail-content\">\n * <p>Name: {{ row.name }}</p>\n * <p>Email: {{ row.email }}</p>\n * </div>\n * </tbw-grid-detail>\n * </tbw-grid>\n * ```\n *\n * Attributes:\n * - `animation`: 'slide' | 'fade' | 'false' (default: 'slide')\n * - `show-expand-column`: 'true' | 'false' (default: 'true')\n * - `expand-on-row-click`: 'true' | 'false' (default: 'false')\n * - `collapse-on-click-outside`: 'true' | 'false' (default: 'false')\n * - `height`: number (pixels) or 'auto' (default: 'auto')\n */\n private parseLightDomDetail(): void {\n const gridEl = this.grid as unknown as Element;\n if (!gridEl || typeof gridEl.querySelector !== 'function') return;\n\n const detailEl = gridEl.querySelector('tbw-grid-detail');\n if (!detailEl) return;\n\n // Check if a framework adapter wants to handle this element\n // (e.g., Angular adapter intercepts for ng-template rendering)\n const gridWithAdapter = gridEl as unknown as {\n __frameworkAdapter?: {\n parseDetailElement?: (el: Element) => ((row: any, rowIndex: number) => HTMLElement | string) | undefined;\n };\n };\n if (gridWithAdapter.__frameworkAdapter?.parseDetailElement) {\n const adapterRenderer = gridWithAdapter.__frameworkAdapter.parseDetailElement(detailEl);\n if (adapterRenderer) {\n this.config = { ...this.config, detailRenderer: adapterRenderer };\n return;\n }\n }\n\n // Parse attributes for configuration\n const animation = detailEl.getAttribute('animation');\n const showExpandColumn = detailEl.getAttribute('show-expand-column');\n const expandOnRowClick = detailEl.getAttribute('expand-on-row-click');\n const collapseOnClickOutside = detailEl.getAttribute('collapse-on-click-outside');\n const heightAttr = detailEl.getAttribute('height');\n\n const configUpdates: Partial<MasterDetailConfig> = {};\n\n if (animation !== null) {\n configUpdates.animation = animation === 'false' ? false : (animation as 'slide' | 'fade');\n }\n if (showExpandColumn !== null) {\n configUpdates.showExpandColumn = showExpandColumn !== 'false';\n }\n if (expandOnRowClick !== null) {\n configUpdates.expandOnRowClick = expandOnRowClick === 'true';\n }\n if (collapseOnClickOutside !== null) {\n configUpdates.collapseOnClickOutside = collapseOnClickOutside === 'true';\n }\n if (heightAttr !== null) {\n configUpdates.detailHeight = heightAttr === 'auto' ? 'auto' : parseInt(heightAttr, 10);\n }\n\n // Get template content from innerHTML\n const templateHTML = detailEl.innerHTML.trim();\n if (templateHTML && !this.config.detailRenderer) {\n // Create a template-based renderer using the inner HTML\n configUpdates.detailRenderer = (row: any, _rowIndex: number): string => {\n // Evaluate template expressions like {{ row.field }}\n const evaluated = evalTemplateString(templateHTML, { value: row, row });\n // Sanitize the result to prevent XSS\n return sanitizeHTML(evaluated);\n };\n }\n\n // Merge updates into config\n if (Object.keys(configUpdates).length > 0) {\n this.config = { ...this.config, ...configUpdates };\n }\n }\n\n // #endregion\n\n // #region Animation Helpers\n\n /**\n * Get expand/collapse animation style from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide';\n }\n\n /**\n * Apply expand animation to a detail element.\n * Returns true if animation was applied, false if skipped.\n * When animated, height measurement is deferred to animationend to avoid\n * measuring during the max-height: 0 CSS animation constraint.\n */\n private animateExpand(detailEl: HTMLElement, row?: any, rowIndex?: number): boolean {\n if (!this.isAnimationEnabled || this.animationStyle === false) return false;\n\n detailEl.classList.add('tbw-expanding');\n\n let measured = false;\n const measureOnce = () => {\n if (measured) return;\n measured = true;\n detailEl.classList.remove('tbw-expanding');\n\n // Measure height AFTER animation completes - the element now has its\n // natural height without the max-height constraint from the animation.\n if (row !== undefined && rowIndex !== undefined) {\n this.#measureAndCacheDetailHeight(detailEl, row, rowIndex);\n }\n };\n\n detailEl.addEventListener('animationend', measureOnce, { once: true });\n // Fallback timeout in case animationend doesn't fire (e.g., element detached,\n // animation removed, or framework rendering delays). Matches animateCollapse pattern.\n setTimeout(measureOnce, this.animationDuration + 50);\n return true;\n }\n\n /**\n * Apply collapse animation to a detail element and remove after animation.\n */\n private animateCollapse(detailEl: HTMLElement, onComplete: () => void): void {\n if (!this.isAnimationEnabled || this.animationStyle === false) {\n onComplete();\n return;\n }\n\n detailEl.classList.add('tbw-collapsing');\n const cleanup = () => {\n detailEl.classList.remove('tbw-collapsing');\n onComplete();\n };\n detailEl.addEventListener('animationend', cleanup, { once: true });\n // Fallback timeout in case animation doesn't fire\n setTimeout(cleanup, this.animationDuration + 50);\n }\n\n /**\n * Measure a detail element's height and update the position cache if it changed.\n * Used after layout settles (RAF) or after animation completes (animationend).\n */\n #measureAndCacheDetailHeight(detailEl: HTMLElement, row: any, rowIndex: number): void {\n if (!detailEl.isConnected) return;\n\n const height = detailEl.offsetHeight;\n if (height > 0) {\n const previousHeight = this.measuredDetailHeights.get(row);\n this.measuredDetailHeights.set(row, height);\n\n // Only invalidate if height actually changed\n // This triggers an incremental position cache update, not a full rebuild\n if (previousHeight !== height) {\n this.grid.invalidateRowHeight(rowIndex);\n }\n }\n }\n\n // #endregion\n\n // #region Internal State\n private expandedRows: Set<any> = new Set();\n private detailElements: Map<any, HTMLElement> = new Map();\n /** Cached measured heights - persists even when elements are virtualized out */\n private measuredDetailHeights: Map<any, number> = new Map();\n /** Rows that were just expanded by user action and should animate.\n * Prevents re-animation when rows scroll back into the virtual window. */\n private rowsToAnimate: Set<any> = new Set();\n\n /** Default height for detail rows when not configured */\n private static readonly DEFAULT_DETAIL_HEIGHT = 150;\n\n /**\n * Get the estimated height for a detail row.\n * Uses cached measured height when available (survives virtualization).\n * Avoids reading offsetHeight during CSS animations to prevent poisoning the cache.\n */\n private getDetailHeight(row: any): number {\n // Try DOM element first - works for tests and when element is connected\n const detailEl = this.detailElements.get(row);\n if (detailEl) {\n // Skip DOM measurement if currently animating (max-height constraint gives wrong value)\n const isAnimating = detailEl.classList.contains('tbw-expanding') || detailEl.classList.contains('tbw-collapsing');\n if (!isAnimating) {\n const height = detailEl.offsetHeight;\n if (height > 0) {\n // Cache the measurement for when this row is virtualized out\n this.measuredDetailHeights.set(row, height);\n return height;\n }\n }\n }\n\n // DOM element missing, detached, or animating - check cached measurement\n const cachedHeight = this.measuredDetailHeights.get(row);\n if (cachedHeight && cachedHeight > 0) {\n return cachedHeight;\n }\n\n // Fallback to config or default\n return typeof this.config?.detailHeight === 'number'\n ? this.config.detailHeight\n : MasterDetailPlugin.DEFAULT_DETAIL_HEIGHT;\n }\n\n /**\n * Toggle a row's detail and emit event.\n */\n private toggleAndEmit(row: any, rowIndex: number): void {\n this.expandedRows = toggleDetailRow(this.expandedRows, row as object);\n const expanded = this.expandedRows.has(row as object);\n if (expanded) {\n this.rowsToAnimate.add(row);\n }\n this.emit<DetailExpandDetail>('detail-expand', {\n rowIndex,\n row: row as Record<string, unknown>,\n expanded,\n });\n this.requestRender();\n }\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.expandedRows.clear();\n this.detailElements.clear();\n this.measuredDetailHeights.clear();\n this.rowsToAnimate.clear();\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n // Determine whether to add the expander column:\n // 1. If showExpandColumn === false: never add (explicit opt-out)\n // 2. If showExpandColumn === true: always add (explicit opt-in, for framework adapters)\n // 3. If showExpandColumn is undefined: add only if detailRenderer is provided\n //\n // This supports React/Angular adapters which register renderers asynchronously via light DOM.\n // They must set showExpandColumn: true to get the column immediately, avoiding layout shift.\n const shouldAddExpander =\n this.config.showExpandColumn === true || (this.config.showExpandColumn !== false && !!this.config.detailRenderer);\n\n if (!shouldAddExpander) {\n return [...columns];\n }\n\n const cols = [...columns];\n\n // Check if expander column already exists (from this or another plugin)\n const existingExpander = findExpanderColumn(cols);\n if (existingExpander) {\n // Another plugin already added an expander column - don't add duplicate\n // Our expand logic will be handled via onCellClick on the expander column\n return cols;\n }\n\n // Create dedicated expander column that stays fixed at position 0\n const expanderCol = createExpanderColumnConfig(this.name);\n expanderCol.viewRenderer = (renderCtx) => {\n const { row } = renderCtx;\n const isExpanded = this.expandedRows.has(row as object);\n\n const container = document.createElement('span');\n container.className = 'master-detail-expander expander-cell';\n\n // Expand/collapse toggle icon\n const toggle = document.createElement('span');\n toggle.className = `master-detail-toggle${isExpanded ? ' expanded' : ''}`;\n // Use grid-level icons (fall back to defaults)\n this.setIcon(toggle, this.resolveIcon(isExpanded ? 'collapse' : 'expand'));\n // role=\"button\" is required for aria-expanded to be valid\n toggle.setAttribute('role', 'button');\n toggle.setAttribute('tabindex', '0');\n toggle.setAttribute('aria-expanded', String(isExpanded));\n toggle.setAttribute('aria-label', isExpanded ? 'Collapse details' : 'Expand details');\n container.appendChild(toggle);\n\n return container;\n };\n\n // Prepend expander column to ensure it's always first\n return [expanderCol, ...cols];\n }\n\n /** @internal */\n override onRowClick(event: RowClickEvent): boolean | void {\n if (!this.config.expandOnRowClick || !this.config.detailRenderer) return;\n this.toggleAndEmit(event.row, event.rowIndex);\n return false;\n }\n\n /** @internal */\n override onCellClick(event: CellClickEvent): boolean | void {\n // Handle click on master-detail toggle icon (same pattern as TreePlugin)\n const target = event.originalEvent?.target as HTMLElement;\n if (target?.classList.contains('master-detail-toggle')) {\n this.toggleAndEmit(event.row, event.rowIndex);\n return true; // Prevent default handling\n }\n\n // Sync detail rows after cell click triggers refreshVirtualWindow\n // This runs in microtask to ensure DOM updates are complete\n if (this.expandedRows.size > 0) {\n queueMicrotask(() => this.#syncDetailRows());\n }\n return; // Don't prevent default\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion when focus is on the expander column\n if (event.key !== ' ') return;\n\n const focusCol = this.grid._focusCol;\n const focusRow = this.grid._focusRow;\n const column = this.columns[focusCol];\n\n // Only handle SPACE on expander column\n if (!column || !isExpanderColumn(column)) return;\n\n const row = this.rows[focusRow];\n if (!row) return;\n\n event.preventDefault();\n this.toggleAndEmit(row, focusRow);\n\n // Restore focus styling after render completes via render pipeline\n this.requestRenderWithFocus();\n return true;\n }\n\n /** @internal */\n override afterRender(): void {\n this.#syncDetailRows();\n }\n\n /**\n * Called on scroll to sync detail elements with visible rows.\n * Removes details for rows that scrolled out of view and reattaches for visible rows.\n * @internal\n */\n override onScrollRender(): void {\n if (!this.config.detailRenderer || this.expandedRows.size === 0) return;\n // Full sync needed on scroll to clean up orphaned details\n this.#syncDetailRows();\n }\n\n /**\n * Full sync of detail rows - cleans up stale elements and creates new ones.\n * Detail rows are inserted as siblings AFTER their master row to survive row rebuilds.\n *\n * PERF: Uses the grid's row pool (_rowPool) and virtual window (_virtualization.start/end)\n * to avoid querySelectorAll on every scroll frame. The pool is index-aligned with the\n * virtual window, so pool[i] corresponds to row index (start + i).\n */\n #syncDetailRows(): void {\n if (!this.config.detailRenderer) return;\n\n const body = this.gridElement?.querySelector('.rows');\n if (!body) return;\n\n // Use grid's virtualization state and row pool for O(1) lookups instead of querySelectorAll.\n // The row pool is an array of DOM elements aligned to the virtual window:\n // _rowPool[i] renders row data at index (_virtualization.start + i).\n const gridInternal = this.grid as any;\n const rowPool: HTMLElement[] | undefined = gridInternal._rowPool;\n const vStart: number = gridInternal._virtualization?.start ?? 0;\n const vEnd: number = gridInternal._virtualization?.end ?? 0;\n const columnCount = this.columns.length;\n\n // Build visible row index set from the virtual window range\n const visibleStart = vStart;\n const visibleEnd = vEnd;\n\n // Build a map of row index -> row element using the pool (O(n) where n = visible rows)\n const visibleRowMap = new Map<number, Element>();\n if (rowPool) {\n const poolLen = Math.min(rowPool.length, visibleEnd - visibleStart);\n for (let i = 0; i < poolLen; i++) {\n const rowEl = rowPool[i];\n if (rowEl.parentNode === body) {\n visibleRowMap.set(visibleStart + i, rowEl);\n }\n }\n } else {\n // Fallback: use querySelectorAll if pool is not accessible\n const dataRows = body.querySelectorAll('.data-grid-row');\n for (const rowEl of dataRows) {\n const firstCell = rowEl.querySelector('.cell[data-row]');\n const rowIndex = firstCell ? parseInt(firstCell.getAttribute('data-row') ?? '-1', 10) : -1;\n if (rowIndex >= 0) {\n visibleRowMap.set(rowIndex, rowEl);\n }\n }\n }\n\n // Remove detail rows whose parent row is no longer visible or no longer expanded.\n // Iterate the detailElements map (which we own) instead of querySelectorAll.\n for (const [row, detailEl] of this.detailElements) {\n const rowIndex = this.rows.indexOf(row);\n const isStillExpanded = this.expandedRows.has(row);\n const isRowVisible = rowIndex >= 0 && visibleRowMap.has(rowIndex);\n\n if (!isStillExpanded || !isRowVisible) {\n if (detailEl.parentNode) detailEl.remove();\n this.detailElements.delete(row);\n }\n }\n\n // Insert detail rows for expanded rows that are visible\n for (const [rowIndex, rowEl] of visibleRowMap) {\n const row = this.rows[rowIndex];\n if (!row || !this.expandedRows.has(row)) continue;\n\n // Check if detail already exists for this row\n const existingDetail = this.detailElements.get(row);\n if (existingDetail) {\n // Ensure it's positioned correctly (as next sibling of row element)\n if (existingDetail.previousElementSibling !== rowEl) {\n rowEl.after(existingDetail);\n }\n continue;\n }\n\n // Create new detail element\n const detailEl = createDetailElement(row, rowIndex, this.config.detailRenderer, columnCount);\n\n if (typeof this.config.detailHeight === 'number') {\n detailEl.style.height = `${this.config.detailHeight}px`;\n }\n\n // Insert as sibling after the row element (not as child)\n rowEl.after(detailEl);\n this.detailElements.set(row, detailEl);\n\n // Only animate if this row was just expanded by a user action (click, keyboard, API).\n // Rows re-appearing from scroll (virtualization) should not re-animate.\n const shouldAnimate = this.rowsToAnimate.has(row);\n if (shouldAnimate) {\n this.rowsToAnimate.delete(row);\n }\n\n const willAnimate = shouldAnimate && this.animateExpand(detailEl, row, rowIndex);\n\n if (!willAnimate) {\n // No animation - measure height after layout settles via RAF\n requestAnimationFrame(() => {\n this.#measureAndCacheDetailHeight(detailEl, row, rowIndex);\n });\n }\n // When animating, measurement is deferred to animationend callback\n // (inside animateExpand) to avoid measuring during max-height: 0 constraint\n }\n }\n\n /**\n * Return total extra height from all expanded detail rows.\n * Used by grid virtualization to adjust scrollbar height.\n *\n * @deprecated Use getRowHeight() instead. This hook will be removed in v3.0.\n */\n override getExtraHeight(): number {\n let totalHeight = 0;\n for (const row of this.expandedRows) {\n totalHeight += this.getDetailHeight(row);\n }\n return totalHeight;\n }\n\n /**\n * Return extra height that appears before a given row index.\n * This is the sum of heights of all expanded details whose parent row is before the given index.\n *\n * @deprecated Use getRowHeight() instead. This hook will be removed in v3.0.\n */\n override getExtraHeightBefore(beforeRowIndex: number): number {\n let totalHeight = 0;\n for (const row of this.expandedRows) {\n const rowIndex = this.rows.indexOf(row);\n // Include detail if it's for a row before the given index\n if (rowIndex >= 0 && rowIndex < beforeRowIndex) {\n totalHeight += this.getDetailHeight(row);\n }\n }\n return totalHeight;\n }\n\n /**\n * Get the height of a specific row, including any expanded detail content.\n * Always returns a height to ensure the position cache uses plugin-controlled values\n * rather than stale DOM measurements.\n *\n * @param row - The row data\n * @param _index - The row index (unused, but part of the interface)\n * @returns The row height in pixels (base height for collapsed, base + detail for expanded)\n */\n override getRowHeight(row: unknown, _index: number): number | undefined {\n const isExpanded = this.expandedRows.has(row as object);\n\n if (!isExpanded) {\n // Collapsed row - return undefined to let the grid use its measured/estimated height.\n // This ensures the position cache uses the correct row height from CSS/config.\n return undefined;\n }\n\n // Row is expanded - return base height plus detail height\n // Use grid's defaultRowHeight which reflects the actual measured/configured height\n const baseHeight = this.grid.defaultRowHeight ?? 28;\n const detailHeight = this.getDetailHeight(row);\n\n return baseHeight + detailHeight;\n }\n\n /**\n * Adjust the virtualization start index to keep expanded row visible while its detail is visible.\n * This ensures the detail scrolls smoothly out of view instead of disappearing abruptly.\n */\n override adjustVirtualStart(start: number, scrollTop: number, rowHeight: number): number {\n if (this.expandedRows.size === 0) return start;\n\n // Use position cache for accurate row positions when available (variable heights mode)\n const positionCache = (this.grid as any)?._virtualization?.positionCache as\n | Array<{ offset: number; height: number }>\n | undefined;\n\n let minStart = start;\n\n if (positionCache && positionCache.length > 0) {\n // Variable heights: use position cache for accurate offset\n for (const row of this.expandedRows) {\n const rowIndex = this.rows.indexOf(row);\n if (rowIndex < 0 || rowIndex >= start) continue;\n\n // Position cache already includes cumulative heights from all expanded details\n const detailBottom = positionCache[rowIndex].offset + positionCache[rowIndex].height;\n\n if (detailBottom > scrollTop && rowIndex < minStart) {\n minStart = rowIndex;\n }\n }\n } else {\n // Fixed heights fallback: accumulate detail heights manually\n // Build sorted list of expanded row indices for cumulative height calculation\n const expandedIndices: Array<{ index: number; row: any }> = [];\n for (const row of this.expandedRows) {\n const index = this.rows.indexOf(row);\n if (index >= 0) {\n expandedIndices.push({ index, row });\n }\n }\n expandedIndices.sort((a, b) => a.index - b.index);\n\n let cumulativeExtraHeight = 0;\n\n for (const { index: rowIndex, row } of expandedIndices) {\n const actualRowTop = rowIndex * rowHeight + cumulativeExtraHeight;\n const detailHeight = this.getDetailHeight(row);\n const actualDetailBottom = actualRowTop + rowHeight + detailHeight;\n\n cumulativeExtraHeight += detailHeight;\n\n if (rowIndex >= start) continue;\n\n if (actualDetailBottom > scrollTop && rowIndex < minStart) {\n minStart = rowIndex;\n }\n }\n }\n\n return minStart;\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Expand the detail row at the given index.\n * @param rowIndex - Index of the row to expand\n */\n expand(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.rowsToAnimate.add(row);\n this.expandedRows = expandDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Collapse the detail row at the given index.\n * @param rowIndex - Index of the row to collapse\n */\n collapse(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = collapseDetailRow(this.expandedRows, row);\n this.requestRender();\n }\n }\n\n /**\n * Toggle the detail row at the given index.\n * @param rowIndex - Index of the row to toggle\n */\n toggle(rowIndex: number): void {\n const row = this.rows[rowIndex];\n if (row) {\n this.expandedRows = toggleDetailRow(this.expandedRows, row);\n if (this.expandedRows.has(row)) {\n this.rowsToAnimate.add(row);\n }\n this.requestRender();\n }\n }\n\n /**\n * Check if the detail row at the given index is expanded.\n * @param rowIndex - Index of the row to check\n * @returns Whether the detail row is expanded\n */\n isExpanded(rowIndex: number): boolean {\n const row = this.rows[rowIndex];\n return row ? isDetailExpanded(this.expandedRows, row) : false;\n }\n\n /**\n * Expand all detail rows.\n */\n expandAll(): void {\n for (const row of this.rows) {\n this.rowsToAnimate.add(row);\n this.expandedRows.add(row);\n }\n this.requestRender();\n }\n\n /**\n * Collapse all detail rows.\n */\n collapseAll(): void {\n this.expandedRows.clear();\n this.requestRender();\n }\n\n /**\n * Get the indices of all expanded rows.\n * @returns Array of row indices that are expanded\n */\n getExpandedRows(): number[] {\n const indices: number[] = [];\n for (const row of this.expandedRows) {\n const idx = this.rows.indexOf(row);\n if (idx >= 0) indices.push(idx);\n }\n return indices;\n }\n\n /**\n * Get the detail element for a specific row.\n * @param rowIndex - Index of the row\n * @returns The detail HTMLElement or undefined\n */\n getDetailElement(rowIndex: number): HTMLElement | undefined {\n const row = this.rows[rowIndex];\n return row ? this.detailElements.get(row) : undefined;\n }\n\n /**\n * Re-parse light DOM to refresh the detail renderer.\n * Call this after framework templates are registered (e.g., Angular ngAfterContentInit).\n *\n * This allows frameworks to register templates asynchronously and then\n * update the plugin's detailRenderer.\n */\n refreshDetailRenderer(): void {\n // Force re-parse by temporarily clearing the renderer\n const currentRenderer = this.config.detailRenderer;\n this.config = { ...this.config, detailRenderer: undefined };\n this.parseLightDomDetail();\n\n // If no new renderer was found, restore the original\n if (!this.config.detailRenderer && currentRenderer) {\n this.config = { ...this.config, detailRenderer: currentRenderer };\n }\n\n // Request a COLUMNS phase re-render so processColumns runs again with the new detailRenderer\n // This ensures the expand toggle is added to the first column.\n // Must use refreshColumns() (COLUMNS phase) not requestRender() (ROWS phase)\n // because processColumns only runs at COLUMNS phase or higher.\n if (this.config.detailRenderer) {\n const grid = this.grid as unknown as { refreshColumns?: () => void };\n if (typeof grid.refreshColumns === 'function') {\n grid.refreshColumns();\n } else {\n // Fallback to requestRender if refreshColumns not available\n this.requestRender();\n }\n }\n }\n // #endregion\n}\n","/**\n * Multi-Sort Core Logic\n *\n * Pure functions for multi-column sorting operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { SortModel } from './types';\n\n/**\n * Apply multiple sort columns to a row array.\n * Sorts are applied in order - first sort has highest priority.\n *\n * @param rows - Array of row objects to sort\n * @param sorts - Ordered array of sort configurations\n * @param columns - Column configurations (for custom comparators)\n * @returns New sorted array (does not mutate original)\n */\nexport function applySorts<TRow = unknown>(rows: TRow[], sorts: SortModel[], columns: ColumnConfig<TRow>[]): TRow[] {\n if (!sorts.length) return [...rows];\n\n return [...rows].sort((a, b) => {\n for (const sort of sorts) {\n const col = columns.find((c) => c.field === sort.field);\n const comparator = col?.sortComparator ?? defaultComparator;\n const aVal = (a as Record<string, unknown>)[sort.field];\n const bVal = (b as Record<string, unknown>)[sort.field];\n const result = comparator(aVal, bVal, a, b);\n if (result !== 0) {\n return sort.direction === 'asc' ? result : -result;\n }\n }\n return 0;\n });\n}\n\n/**\n * Default comparator for sorting values.\n * Handles nulls, numbers, dates, and strings.\n *\n * @param a - First value\n * @param b - Second value\n * @returns Comparison result (-1, 0, 1)\n */\nexport function defaultComparator(a: unknown, b: unknown): number {\n // Handle nulls/undefined - push to end\n if (a == null && b == null) return 0;\n if (a == null) return 1;\n if (b == null) return -1;\n\n // Type-aware comparison\n if (typeof a === 'number' && typeof b === 'number') {\n return a - b;\n }\n\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() - b.getTime();\n }\n\n // Boolean comparison\n if (typeof a === 'boolean' && typeof b === 'boolean') {\n return a === b ? 0 : a ? -1 : 1;\n }\n\n // String comparison (fallback)\n return String(a).localeCompare(String(b));\n}\n\n/**\n * Toggle sort state for a field.\n * With shift key: adds/toggles in multi-sort list\n * Without shift key: replaces entire sort with single column\n *\n * @param current - Current sort model\n * @param field - Field to toggle\n * @param shiftKey - Whether shift key is held (multi-sort mode)\n * @param maxColumns - Maximum columns allowed in sort\n * @returns New sort model\n */\nexport function toggleSort(current: SortModel[], field: string, shiftKey: boolean, maxColumns: number): SortModel[] {\n const existing = current.find((s) => s.field === field);\n\n if (shiftKey) {\n // Multi-sort: add/toggle in list\n if (existing) {\n if (existing.direction === 'asc') {\n // Flip to descending\n return current.map((s) => (s.field === field ? { ...s, direction: 'desc' as const } : s));\n } else {\n // Remove from sort\n return current.filter((s) => s.field !== field);\n }\n } else if (current.length < maxColumns) {\n // Add new sort column\n return [...current, { field, direction: 'asc' as const }];\n }\n // Max columns reached, return unchanged\n return current;\n } else {\n // Single sort: replace all\n if (existing?.direction === 'asc') {\n return [{ field, direction: 'desc' }];\n } else if (existing?.direction === 'desc') {\n return [];\n }\n return [{ field, direction: 'asc' }];\n }\n}\n\n/**\n * Get the sort index (1-based) for a field in the sort model.\n * Returns undefined if the field is not in the sort model.\n *\n * @param sortModel - Current sort model\n * @param field - Field to check\n * @returns 1-based index or undefined\n */\nexport function getSortIndex(sortModel: SortModel[], field: string): number | undefined {\n const index = sortModel.findIndex((s) => s.field === field);\n return index >= 0 ? index + 1 : undefined;\n}\n\n/**\n * Get the sort direction for a field in the sort model.\n *\n * @param sortModel - Current sort model\n * @param field - Field to check\n * @returns Sort direction or undefined if not sorted\n */\nexport function getSortDirection(sortModel: SortModel[], field: string): 'asc' | 'desc' | undefined {\n return sortModel.find((s) => s.field === field)?.direction;\n}\n","/**\n * Multi-Sort Plugin (Class-based)\n *\n * Provides multi-column sorting capabilities for tbw-grid.\n * Supports shift+click for adding secondary sort columns.\n */\n\nimport { BaseGridPlugin, HeaderClickEvent } from '../../core/plugin/base-plugin';\nimport type { ColumnState } from '../../core/types';\nimport { applySorts, getSortDirection, getSortIndex, toggleSort } from './multi-sort';\nimport styles from './multi-sort.css?inline';\nimport type { MultiSortConfig, SortModel } from './types';\n\n/**\n * Multi-Sort Plugin for tbw-grid\n *\n * Enables sorting by multiple columns at once—hold Shift and click additional column\n * headers to build up a sort stack. Priority badges show the sort order, so users\n * always know which column takes precedence.\n *\n * ## Installation\n *\n * ```ts\n * import { MultiSortPlugin } from '@toolbox-web/grid/plugins/multi-sort';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `maxSortColumns` | `number` | `3` | Maximum columns to sort by |\n * | `showSortIndex` | `boolean` | `true` | Show sort priority badges |\n * | `initialSort` | `SortModel[]` | - | Pre-configured sort order on load |\n *\n * ## Keyboard Shortcuts\n *\n * | Shortcut | Action |\n * |----------|--------|\n * | `Click header` | Sort by column (clears other sorts) |\n * | `Shift + Click` | Add column to multi-sort stack |\n * | `Ctrl + Click` | Toggle sort direction |\n *\n * ## Events\n *\n * | Event | Detail | Description |\n * |-------|--------|-------------|\n * | `sort-change` | `{ sortModel: SortModel[] }` | Fired when sort changes |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `setSort` | `(sortModel: SortModel[]) => void` | Set sort programmatically |\n * | `getSortModel` | `() => SortModel[]` | Get current sort model |\n * | `clearSort` | `() => void` | Clear all sorting |\n * | `addSort` | `(field, direction) => void` | Add a column to sort |\n * | `removeSort` | `(field) => void` | Remove a column from sort |\n *\n * @example Basic Multi-Column Sorting\n * ```ts\n * import '@toolbox-web/grid';\n * import { MultiSortPlugin } from '@toolbox-web/grid/plugins/multi-sort';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name', sortable: true },\n * { field: 'department', header: 'Department', sortable: true },\n * { field: 'salary', header: 'Salary', type: 'number', sortable: true },\n * ],\n * plugins: [new MultiSortPlugin({ maxSortColumns: 3, showSortIndex: true })],\n * };\n *\n * grid.addEventListener('sort-change', (e) => {\n * console.log('Active sorts:', e.detail.sortModel);\n * });\n * ```\n *\n * @example Initial Sort Configuration\n * ```ts\n * new MultiSortPlugin({\n * initialSort: [\n * { field: 'department', direction: 'asc' },\n * { field: 'salary', direction: 'desc' },\n * ],\n * })\n * ```\n *\n * @see {@link MultiSortConfig} for all configuration options\n * @see {@link SortModel} for the sort model structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class MultiSortPlugin extends BaseGridPlugin<MultiSortConfig> {\n /** @internal */\n readonly name = 'multiSort';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<MultiSortConfig> {\n return {\n maxSortColumns: 3,\n showSortIndex: true,\n };\n }\n\n // #region Internal State\n private sortModel: SortModel[] = [];\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.sortModel = [];\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processRows(rows: readonly unknown[]): unknown[] {\n if (this.sortModel.length === 0) {\n return [...rows];\n }\n return applySorts([...rows], this.sortModel, [...this.columns]);\n }\n\n /** @internal */\n override onHeaderClick(event: HeaderClickEvent): boolean {\n const column = this.columns.find((c) => c.field === event.field);\n if (!column?.sortable) return false;\n\n const shiftKey = event.originalEvent.shiftKey;\n const maxColumns = this.config.maxSortColumns ?? 3;\n\n this.sortModel = toggleSort(this.sortModel, event.field, shiftKey, maxColumns);\n\n this.emit('sort-change', { sortModel: [...this.sortModel] });\n this.requestRender();\n\n return true;\n }\n\n /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n const showIndex = this.config.showSortIndex !== false;\n\n // Update all sortable header cells with sort indicators\n const headerCells = gridEl.querySelectorAll('.header-row .cell[data-field]');\n headerCells.forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (!field) return;\n\n const sortIndex = getSortIndex(this.sortModel, field);\n const sortDir = getSortDirection(this.sortModel, field);\n\n // Remove existing sort index badge (always clean up)\n const existingBadge = cell.querySelector('.sort-index');\n existingBadge?.remove();\n\n if (sortDir) {\n // Column is sorted - remove base indicator and add our own\n const existingIndicator = cell.querySelector('[part~=\"sort-indicator\"], .sort-indicator');\n existingIndicator?.remove();\n\n cell.setAttribute('data-sort', sortDir);\n\n // Add sort arrow indicator - insert BEFORE filter button and resize handle\n // to maintain consistent order: [label, sort-indicator, sort-index, filter-btn, resize-handle]\n const indicator = document.createElement('span');\n indicator.className = 'sort-indicator';\n // Use grid-level icons (fall back to defaults)\n this.setIcon(indicator, this.resolveIcon(sortDir === 'asc' ? 'sortAsc' : 'sortDesc'));\n\n // Find insertion point: before filter button or resize handle\n const filterBtn = cell.querySelector('.tbw-filter-btn');\n const resizeHandle = cell.querySelector('.resize-handle');\n const insertBefore = filterBtn ?? resizeHandle;\n if (insertBefore) {\n cell.insertBefore(indicator, insertBefore);\n } else {\n cell.appendChild(indicator);\n }\n\n // Add sort index badge if multiple columns sorted and showSortIndex is enabled\n if (showIndex && this.sortModel.length > 1 && sortIndex !== undefined) {\n const badge = document.createElement('span');\n badge.className = 'sort-index';\n badge.textContent = String(sortIndex);\n // Insert badge right after the indicator\n if (indicator.nextSibling) {\n cell.insertBefore(badge, indicator.nextSibling);\n } else {\n cell.appendChild(badge);\n }\n }\n } else {\n cell.removeAttribute('data-sort');\n // For unsorted columns, leave the base indicator (⇅) alone\n }\n });\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Get the current sort model.\n * @returns Copy of the current sort model\n */\n getSortModel(): SortModel[] {\n return [...this.sortModel];\n }\n\n /**\n * Set the sort model programmatically.\n * @param model - New sort model to apply\n */\n setSortModel(model: SortModel[]): void {\n this.sortModel = [...model];\n this.emit('sort-change', { sortModel: [...model] });\n this.requestRender();\n }\n\n /**\n * Clear all sorting.\n */\n clearSort(): void {\n this.sortModel = [];\n this.emit('sort-change', { sortModel: [] });\n this.requestRender();\n }\n\n /**\n * Get the sort index (1-based) for a specific field.\n * @param field - Field to check\n * @returns 1-based index or undefined if not sorted\n */\n getSortIndex(field: string): number | undefined {\n return getSortIndex(this.sortModel, field);\n }\n\n /**\n * Get the sort direction for a specific field.\n * @param field - Field to check\n * @returns Sort direction or undefined if not sorted\n */\n getSortDirection(field: string): 'asc' | 'desc' | undefined {\n return getSortDirection(this.sortModel, field);\n }\n // #endregion\n\n // #region Column State Hooks\n\n /**\n * Return sort state for a column if it's in the sort model.\n * @internal\n */\n override getColumnState(field: string): Partial<ColumnState> | undefined {\n const index = this.sortModel.findIndex((s) => s.field === field);\n if (index === -1) return undefined;\n\n const sortEntry = this.sortModel[index];\n return {\n sort: {\n direction: sortEntry.direction,\n priority: index,\n },\n };\n }\n\n /**\n * Apply sort state from column state.\n * Rebuilds the sort model from all column states.\n * @internal\n */\n override applyColumnState(field: string, state: ColumnState): void {\n // Only process if the column has sort state\n if (!state.sort) {\n // Remove this field from sortModel if it exists\n this.sortModel = this.sortModel.filter((s) => s.field !== field);\n return;\n }\n\n // Find existing entry or add new one\n const existingIndex = this.sortModel.findIndex((s) => s.field === field);\n const newEntry: SortModel = {\n field,\n direction: state.sort.direction,\n };\n\n if (existingIndex !== -1) {\n // Update existing entry\n this.sortModel[existingIndex] = newEntry;\n } else {\n // Add at the correct priority position\n this.sortModel.splice(state.sort.priority, 0, newEntry);\n }\n\n // Re-sort the model by priority to ensure correct order\n // This is handled after all columns are processed, but we maintain order here\n }\n // #endregion\n}\n","/**\n * Pinned Columns Core Logic\n *\n * Pure functions for applying pinned (sticky) column positioning.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { getDirection, resolveInlinePosition, type TextDirection } from '../../core/internal/utils';\nimport type { PinnedPosition, ResolvedPinnedPosition } from './types';\n\n// Keep deprecated imports working (StickyPosition = PinnedPosition)\ntype StickyPosition = PinnedPosition;\ntype ResolvedStickyPosition = ResolvedPinnedPosition;\n\n/**\n * Get the effective pinned position from a column, checking `pinned` first then `sticky` (deprecated).\n *\n * @param col - Column configuration object\n * @returns The pinned position, or undefined if not pinned\n */\nexport function getColumnPinned(col: any): PinnedPosition | undefined {\n return col.pinned ?? col.sticky ?? col.meta?.pinned ?? col.meta?.sticky;\n}\n\n/**\n * Resolve a pinned position to a physical position based on text direction.\n *\n * - `'left'` / `'right'` → unchanged (physical values)\n * - `'start'` → `'left'` in LTR, `'right'` in RTL\n * - `'end'` → `'right'` in LTR, `'left'` in RTL\n *\n * @param position - The pinned position (logical or physical)\n * @param direction - Text direction ('ltr' or 'rtl')\n * @returns Physical pinned position ('left' or 'right')\n */\nexport function resolveStickyPosition(position: StickyPosition, direction: TextDirection): ResolvedStickyPosition {\n return resolveInlinePosition(position, direction);\n}\n\n/**\n * Check if a column is pinned on the left (after resolving logical positions).\n */\nfunction isResolvedLeft(col: any, direction: TextDirection): boolean {\n const pinned = getColumnPinned(col);\n if (!pinned) return false;\n return resolveStickyPosition(pinned, direction) === 'left';\n}\n\n/**\n * Check if a column is pinned on the right (after resolving logical positions).\n */\nfunction isResolvedRight(col: any, direction: TextDirection): boolean {\n const pinned = getColumnPinned(col);\n if (!pinned) return false;\n return resolveStickyPosition(pinned, direction) === 'right';\n}\n\n/**\n * Get columns that should be sticky on the left.\n *\n * @param columns - Array of column configurations\n * @param direction - Text direction (default: 'ltr')\n * @returns Array of columns with sticky='left' or sticky='start' (in LTR)\n */\nexport function getLeftStickyColumns(columns: any[], direction: TextDirection = 'ltr'): any[] {\n return columns.filter((col) => isResolvedLeft(col, direction));\n}\n\n/**\n * Get columns that should be sticky on the right.\n *\n * @param columns - Array of column configurations\n * @param direction - Text direction (default: 'ltr')\n * @returns Array of columns with sticky='right' or sticky='end' (in LTR)\n */\nexport function getRightStickyColumns(columns: any[], direction: TextDirection = 'ltr'): any[] {\n return columns.filter((col) => isResolvedRight(col, direction));\n}\n\n/**\n * Check if any columns have sticky positioning.\n *\n * @param columns - Array of column configurations\n * @returns True if any column has sticky position\n */\nexport function hasStickyColumns(columns: any[]): boolean {\n return columns.some((col) => getColumnPinned(col) != null);\n}\n\n/**\n * Get the sticky position of a column.\n *\n * @param column - Column configuration\n * @returns The sticky position or null if not sticky\n */\nexport function getColumnStickyPosition(column: any): StickyPosition | null {\n return getColumnPinned(column) ?? null;\n}\n\n\n/**\n * Calculate left offsets for sticky-left columns.\n * Returns a map of field -> offset in pixels.\n *\n * @param columns - Array of column configurations (in order)\n * @param getColumnWidth - Function to get column width by field\n * @param direction - Text direction (default: 'ltr')\n * @returns Map of field to left offset\n */\nexport function calculateLeftStickyOffsets(\n columns: any[],\n getColumnWidth: (field: string) => number,\n direction: TextDirection = 'ltr',\n): Map<string, number> {\n const offsets = new Map<string, number>();\n let currentOffset = 0;\n\n for (const col of columns) {\n if (isResolvedLeft(col, direction)) {\n offsets.set(col.field, currentOffset);\n currentOffset += getColumnWidth(col.field);\n }\n }\n\n return offsets;\n}\n\n/**\n * Calculate right offsets for sticky-right columns.\n * Processes columns in reverse order.\n *\n * @param columns - Array of column configurations (in order)\n * @param getColumnWidth - Function to get column width by field\n * @param direction - Text direction (default: 'ltr')\n * @returns Map of field to right offset\n */\nexport function calculateRightStickyOffsets(\n columns: any[],\n getColumnWidth: (field: string) => number,\n direction: TextDirection = 'ltr',\n): Map<string, number> {\n const offsets = new Map<string, number>();\n let currentOffset = 0;\n\n // Process in reverse for right-sticky columns\n const reversed = [...columns].reverse();\n for (const col of reversed) {\n if (isResolvedRight(col, direction)) {\n offsets.set(col.field, currentOffset);\n currentOffset += getColumnWidth(col.field);\n }\n }\n\n return offsets;\n}\n\n/**\n * Apply sticky offsets to header and body cells.\n * This modifies the DOM elements in place.\n *\n * @param host - The grid host element (render root for DOM queries)\n * @param columns - Array of column configurations\n */\nexport function applyStickyOffsets(host: HTMLElement, columns: any[]): void {\n // With light DOM, query the host element directly\n const headerCells = Array.from(host.querySelectorAll('.header-row .cell')) as HTMLElement[];\n if (!headerCells.length) return;\n\n // Detect text direction from the host element\n const direction = getDirection(host);\n\n // Apply left sticky (includes 'start' in LTR, 'end' in RTL)\n let left = 0;\n for (const col of columns) {\n if (isResolvedLeft(col, direction)) {\n const cell = headerCells.find((c) => c.getAttribute('data-field') === col.field);\n if (cell) {\n cell.classList.add('sticky-left');\n cell.style.position = 'sticky';\n cell.style.left = left + 'px';\n // Body cells: use data-field for reliable matching (data-col indices may differ\n // between _columns and _visibleColumns due to hidden/utility columns)\n host.querySelectorAll(`.data-grid-row .cell[data-field=\"${col.field}\"]`).forEach((el) => {\n el.classList.add('sticky-left');\n (el as HTMLElement).style.position = 'sticky';\n (el as HTMLElement).style.left = left + 'px';\n });\n left += cell.offsetWidth;\n }\n }\n }\n\n // Apply right sticky (includes 'end' in LTR, 'start' in RTL) - process in reverse\n let right = 0;\n for (const col of [...columns].reverse()) {\n if (isResolvedRight(col, direction)) {\n const cell = headerCells.find((c) => c.getAttribute('data-field') === col.field);\n if (cell) {\n cell.classList.add('sticky-right');\n cell.style.position = 'sticky';\n cell.style.right = right + 'px';\n // Body cells: use data-field for reliable matching\n host.querySelectorAll(`.data-grid-row .cell[data-field=\"${col.field}\"]`).forEach((el) => {\n el.classList.add('sticky-right');\n (el as HTMLElement).style.position = 'sticky';\n (el as HTMLElement).style.right = right + 'px';\n });\n right += cell.offsetWidth;\n }\n }\n }\n}\n\n/**\n * Reorder columns so that pinned-left columns come first and pinned-right columns come last.\n * Maintains the relative order within each group (left-pinned, unpinned, right-pinned).\n *\n * @param columns - Array of column configurations (in their current order)\n * @param direction - Text direction ('ltr' or 'rtl'), used to resolve logical positions\n * @returns New array with pinned columns moved to the edges\n */\nexport function reorderColumnsForPinning(columns: readonly any[], direction: TextDirection = 'ltr'): any[] {\n const left: any[] = [];\n const middle: any[] = [];\n const right: any[] = [];\n\n for (const col of columns) {\n const pinned = getColumnPinned(col);\n if (pinned) {\n const resolved = resolveStickyPosition(pinned, direction);\n if (resolved === 'left') left.push(col);\n else right.push(col);\n } else {\n middle.push(col);\n }\n }\n\n return [...left, ...middle, ...right];\n}\n\n/**\n * Clear sticky positioning from all cells.\n *\n * @param host - The grid host element (render root for DOM queries)\n */\nexport function clearStickyOffsets(host: HTMLElement): void {\n // With light DOM, query the host element directly\n const cells = host.querySelectorAll('.sticky-left, .sticky-right');\n cells.forEach((cell) => {\n cell.classList.remove('sticky-left', 'sticky-right');\n (cell as HTMLElement).style.position = '';\n (cell as HTMLElement).style.left = '';\n (cell as HTMLElement).style.right = '';\n });\n}\n","/**\n * Pinned Columns Plugin (Class-based)\n *\n * Enables column pinning (sticky left/right positioning).\n */\n\nimport { getDirection } from '../../core/internal/utils';\nimport type { PluginManifest, PluginQuery } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport type { ContextMenuParams, HeaderContextMenuItem } from '../context-menu/types';\nimport {\n applyStickyOffsets,\n clearStickyOffsets,\n getColumnPinned,\n getLeftStickyColumns,\n getRightStickyColumns,\n hasStickyColumns,\n reorderColumnsForPinning,\n} from './pinned-columns';\nimport type { PinnedColumnsConfig, PinnedPosition } from './types';\n\n/** Query type constant for checking if a column can be moved */\nconst QUERY_CAN_MOVE_COLUMN = 'canMoveColumn';\n\n/**\n * Pinned Columns Plugin for tbw-grid\n *\n * Freezes columns to the left or right edge of the grid—essential for keeping key\n * identifiers or action buttons visible while scrolling through wide datasets. Just set\n * `pinned: 'left'` or `pinned: 'right'` on your column definitions.\n *\n * ## Installation\n *\n * ```ts\n * import { PinnedColumnsPlugin } from '@toolbox-web/grid/plugins/pinned-columns';\n * ```\n *\n * ## Column Configuration\n *\n * | Property | Type | Description |\n * |----------|------|-------------|\n * | `pinned` | `'left' \\| 'right' \\| 'start' \\| 'end'` | Pin column to edge (logical or physical) |\n * | `meta.lockPinning` | `boolean` | `false` | Prevent user from pin/unpin via context menu |\n *\n * ### RTL Support\n *\n * Use logical values (`start`/`end`) for grids that work in both LTR and RTL layouts:\n * - `'start'` - Pins to left in LTR, right in RTL\n * - `'end'` - Pins to right in LTR, left in RTL\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-pinned-shadow` | `4px 0 8px rgba(0,0,0,0.1)` | Shadow on pinned column edge |\n * | `--tbw-pinned-border` | `var(--tbw-color-border)` | Border between pinned and scrollable |\n *\n * @example Pin ID Left and Actions Right\n * ```ts\n * import '@toolbox-web/grid';\n * import { PinnedColumnsPlugin } from '@toolbox-web/grid/plugins/pinned-columns';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID', pinned: 'left', width: 80 },\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * { field: 'department', header: 'Department' },\n * { field: 'actions', header: 'Actions', pinned: 'right', width: 120 },\n * ],\n * plugins: [new PinnedColumnsPlugin()],\n * };\n * ```\n *\n * @example RTL-Compatible Pinning\n * ```ts\n * // Same config works in LTR and RTL\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID', pinned: 'start' }, // Left in LTR, Right in RTL\n * { field: 'name', header: 'Name' },\n * { field: 'actions', header: 'Actions', pinned: 'end' }, // Right in LTR, Left in RTL\n * ],\n * plugins: [new PinnedColumnsPlugin()],\n * };\n * ```\n *\n * @see {@link PinnedColumnsConfig} for configuration options\n *\n * @internal Extends BaseGridPlugin\n */\nexport class PinnedColumnsPlugin extends BaseGridPlugin<PinnedColumnsConfig> {\n /**\n * Plugin manifest - declares owned properties and handled queries.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n ownedProperties: [\n {\n property: 'pinned',\n level: 'column',\n description: 'the \"pinned\" column property',\n isUsed: (v) => v === 'left' || v === 'right' || v === 'start' || v === 'end',\n },\n {\n property: 'sticky',\n level: 'column',\n description: 'the \"sticky\" column property (deprecated, use \"pinned\")',\n isUsed: (v) => v === 'left' || v === 'right' || v === 'start' || v === 'end',\n },\n ],\n incompatibleWith: [\n {\n name: 'groupingColumns',\n reason:\n 'Pinning reorders columns to the grid edges, but moving a column out of its column group ' +\n 'is not supported. The group header layout cannot accommodate members at different positions.',\n },\n ],\n queries: [\n {\n type: QUERY_CAN_MOVE_COLUMN,\n description: 'Prevents pinned (sticky) columns from being moved/reordered',\n },\n {\n type: 'getStickyOffsets',\n description: 'Returns the sticky offsets for left/right pinned columns',\n },\n {\n type: 'getContextMenuItems',\n description: 'Contributes pin/unpin items to the header context menu',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'pinnedColumns';\n\n /** @internal */\n protected override get defaultConfig(): Partial<PinnedColumnsConfig> {\n return {};\n }\n\n // #region Internal State\n private isApplied = false;\n private leftOffsets = new Map<string, number>();\n private rightOffsets = new Map<string, number>();\n /**\n * Snapshot of the column field order before the first context-menu pin.\n * Used to restore original positions when unpinning.\n */\n #originalColumnOrder: string[] = [];\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.leftOffsets.clear();\n this.rightOffsets.clear();\n this.isApplied = false;\n this.#originalColumnOrder = [];\n }\n // #endregion\n\n // #region Detection\n\n /**\n * Auto-detect sticky columns from column configuration.\n */\n static detect(rows: readonly unknown[], config: { columns?: ColumnConfig[] }): boolean {\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasStickyColumns(columns);\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n const cols = [...columns];\n this.isApplied = hasStickyColumns(cols);\n if (!this.isApplied) return cols;\n\n const host = this.gridElement;\n const direction = host ? getDirection(host) : 'ltr';\n return reorderColumnsForPinning(cols, direction) as ColumnConfig[];\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.isApplied) {\n return;\n }\n\n const host = this.grid as unknown as HTMLElement;\n const columns = [...this.columns];\n\n if (!hasStickyColumns(columns)) {\n clearStickyOffsets(host);\n this.isApplied = false;\n return;\n }\n\n // Apply sticky offsets after a microtask to ensure DOM is ready\n queueMicrotask(() => {\n applyStickyOffsets(host, columns);\n });\n }\n\n /**\n * Handle inter-plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n switch (query.type) {\n case QUERY_CAN_MOVE_COLUMN: {\n // Prevent pinned columns from being moved/reordered.\n // Pinned columns have fixed positions and should not be draggable.\n const column = query.context as ColumnConfig;\n if (getColumnPinned(column) != null) {\n return false;\n }\n return undefined; // Let other plugins or default behavior decide\n }\n case 'getStickyOffsets': {\n // Return the calculated sticky offsets for column virtualization\n return {\n left: Object.fromEntries(this.leftOffsets),\n right: Object.fromEntries(this.rightOffsets),\n };\n }\n case 'getContextMenuItems': {\n const params = query.context as ContextMenuParams;\n if (!params.isHeader) return undefined;\n\n const column = params.column as ColumnConfig;\n if (!column?.field) return undefined;\n\n // Don't offer pin/unpin for locked-pinning columns\n if (column.meta?.lockPinning) return undefined;\n\n // Don't offer pin/unpin when column grouping is active (incompatible)\n const groupingPlugin = this.grid?.getPluginByName('groupingColumns') as\n | { isGroupingActive(): boolean }\n | undefined;\n if (groupingPlugin?.isGroupingActive()) return undefined;\n\n const pinned = getColumnPinned(column);\n const isPinned = pinned != null;\n const items: HeaderContextMenuItem[] = [];\n\n if (isPinned) {\n items.push({\n id: 'pinned/unpin',\n label: 'Unpin Column',\n icon: '📌',\n order: 40,\n action: () => this.setPinPosition(column.field, undefined),\n });\n } else {\n items.push({\n id: 'pinned/pin-left',\n label: 'Pin Left',\n icon: '⬅',\n order: 40,\n action: () => this.setPinPosition(column.field, 'left'),\n });\n items.push({\n id: 'pinned/pin-right',\n label: 'Pin Right',\n icon: '➡',\n order: 41,\n action: () => this.setPinPosition(column.field, 'right'),\n });\n }\n\n return items;\n }\n default:\n return undefined;\n }\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Set the pin position for a column.\n * Updates the column's `pinned` property and triggers a full re-render.\n *\n * @param field - The field name of the column to pin/unpin\n * @param position - The pin position (`'left'`, `'right'`, `'start'`, `'end'`), or `undefined` to unpin\n */\n setPinPosition(field: string, position: PinnedPosition | undefined): void {\n // Read the currently-visible columns from the plugin accessor.\n // These are the post-processColumns result, which is the authoritative column set.\n const currentColumns = this.columns;\n if (!currentColumns?.length) return;\n\n const currentIndex = currentColumns.findIndex((col) => col.field === field);\n if (currentIndex === -1) return;\n\n const gridEl = this.grid as unknown as HTMLElement & { columns?: ColumnConfig[] };\n\n if (position) {\n // PINNING: snapshot original column order if this is the first context-menu pin.\n // The snapshot lets us restore columns to their original positions on unpin.\n if (this.#originalColumnOrder.length === 0) {\n this.#originalColumnOrder = currentColumns.map((c) => c.field);\n }\n\n // Set the pinned property; processColumns will reorder on next render\n const updated = currentColumns.map((col) => {\n if (col.field !== field) return col;\n const copy = { ...col };\n (copy as ColumnConfig & { pinned?: PinnedPosition }).pinned = position;\n delete (copy as ColumnConfig & { sticky?: PinnedPosition }).sticky;\n return copy;\n });\n\n gridEl.columns = updated;\n } else {\n // UNPINNING: restore column to its original position\n const col = currentColumns[currentIndex];\n const copy = { ...col };\n delete (copy as ColumnConfig & { pinned?: PinnedPosition }).pinned;\n delete (copy as ColumnConfig & { sticky?: PinnedPosition }).sticky;\n\n // Remove from current position\n const remaining = [...currentColumns];\n remaining.splice(currentIndex, 1);\n\n // Find the best insertion point using the original order snapshot\n const originalIndex = this.#originalColumnOrder.indexOf(field);\n if (originalIndex >= 0) {\n // Scan remaining non-pinned columns and find the first whose original\n // position is greater than this column's original position.\n let insertIndex = remaining.length;\n for (let i = 0; i < remaining.length; i++) {\n if (getColumnPinned(remaining[i])) continue; // skip pinned columns\n const otherOriginal = this.#originalColumnOrder.indexOf(remaining[i].field);\n if (otherOriginal > originalIndex) {\n insertIndex = i;\n break;\n }\n }\n remaining.splice(insertIndex, 0, copy);\n } else {\n // Original position unknown — keep at current index\n remaining.splice(Math.min(currentIndex, remaining.length), 0, copy);\n }\n\n // If no more pinned columns remain, clear the snapshot\n if (!remaining.some((c) => getColumnPinned(c) != null)) {\n this.#originalColumnOrder = [];\n }\n\n gridEl.columns = remaining;\n }\n }\n\n /**\n * Re-apply sticky offsets (e.g., after column resize).\n */\n refreshStickyOffsets(): void {\n const columns = [...this.columns];\n applyStickyOffsets(this.grid as unknown as HTMLElement, columns);\n }\n\n /**\n * Get columns pinned to the left (after resolving logical positions for current direction).\n */\n getLeftPinnedColumns(): ColumnConfig[] {\n const columns = [...this.columns];\n const direction = getDirection(this.grid as unknown as HTMLElement);\n return getLeftStickyColumns(columns, direction);\n }\n\n /**\n * Get columns pinned to the right (after resolving logical positions for current direction).\n */\n getRightPinnedColumns(): ColumnConfig[] {\n const columns = [...this.columns];\n const direction = getDirection(this.grid as unknown as HTMLElement);\n return getRightStickyColumns(columns, direction);\n }\n\n /**\n * Clear all sticky positioning.\n */\n clearStickyPositions(): void {\n clearStickyOffsets(this.grid as unknown as HTMLElement);\n }\n\n /**\n * Report horizontal scroll boundary offsets for pinned columns.\n * Used by keyboard navigation to ensure focused cells aren't hidden behind sticky columns.\n * @internal\n */\n override getHorizontalScrollOffsets(\n rowEl?: HTMLElement,\n focusedCell?: HTMLElement,\n ): { left: number; right: number; skipScroll?: boolean } | undefined {\n if (!this.isApplied) {\n return undefined;\n }\n\n let left = 0;\n let right = 0;\n\n if (rowEl) {\n // Calculate from rendered cells in the row\n const stickyLeftCells = rowEl.querySelectorAll('.sticky-left');\n const stickyRightCells = rowEl.querySelectorAll('.sticky-right');\n stickyLeftCells.forEach((el) => {\n left += (el as HTMLElement).offsetWidth;\n });\n stickyRightCells.forEach((el) => {\n right += (el as HTMLElement).offsetWidth;\n });\n } else {\n // Fall back to header row if no row element provided\n const host = this.grid as unknown as HTMLElement;\n const headerCells = host.querySelectorAll('.header-row .cell');\n headerCells.forEach((cell) => {\n if (cell.classList.contains('sticky-left')) {\n left += (cell as HTMLElement).offsetWidth;\n } else if (cell.classList.contains('sticky-right')) {\n right += (cell as HTMLElement).offsetWidth;\n }\n });\n }\n\n // Skip horizontal scrolling if focused cell is pinned (it's always visible)\n const skipScroll =\n focusedCell?.classList.contains('sticky-left') || focusedCell?.classList.contains('sticky-right');\n\n return { left, right, skipScroll };\n }\n // #endregion\n}\n","/**\n * Status Bar Rendering Logic\n *\n * Pure functions for creating and updating the status bar UI.\n * Includes both info bar and aggregation row rendering.\n */\n\nimport { getAggregator } from '../../core/internal/aggregators';\nimport type { ColumnConfig } from '../../core/types';\nimport type {\n AggregationRowConfig,\n AggregatorConfig,\n AggregatorDefinition,\n PinnedRowsConfig,\n PinnedRowsContext,\n PinnedRowsPanel,\n} from './types';\n\n/**\n * Check if an aggregator definition is a full config object (with aggFunc and optional formatter).\n */\nfunction isAggregatorConfig(def: AggregatorDefinition): def is AggregatorConfig {\n return typeof def === 'object' && def !== null && 'aggFunc' in def;\n}\n\n/**\n * Creates the info bar DOM element with all configured panels.\n *\n * @param config - The status bar configuration\n * @param context - The current grid context for rendering\n * @returns The complete info bar element\n */\nexport function createInfoBarElement(config: PinnedRowsConfig, context: PinnedRowsContext): HTMLElement {\n const pinnedRows = document.createElement('div');\n pinnedRows.className = 'tbw-pinned-rows';\n pinnedRows.setAttribute('role', 'presentation');\n pinnedRows.setAttribute('aria-live', 'polite');\n\n const left = document.createElement('div');\n left.className = 'tbw-pinned-rows-left';\n\n const center = document.createElement('div');\n center.className = 'tbw-pinned-rows-center';\n\n const right = document.createElement('div');\n right.className = 'tbw-pinned-rows-right';\n\n // Default panels - row count\n if (config.showRowCount !== false) {\n const rowCount = document.createElement('span');\n rowCount.className = 'tbw-status-panel tbw-status-panel-row-count';\n rowCount.textContent = `Total: ${context.totalRows} rows`;\n left.appendChild(rowCount);\n }\n\n // Filtered count panel (only shows when filter is active)\n if (config.showFilteredCount && context.filteredRows !== context.totalRows) {\n const filteredCount = document.createElement('span');\n filteredCount.className = 'tbw-status-panel tbw-status-panel-filtered-count';\n filteredCount.textContent = `Filtered: ${context.filteredRows}`;\n left.appendChild(filteredCount);\n }\n\n // Selected count panel (only shows when rows are selected)\n if (config.showSelectedCount && context.selectedRows > 0) {\n const selectedCount = document.createElement('span');\n selectedCount.className = 'tbw-status-panel tbw-status-panel-selected-count';\n selectedCount.textContent = `Selected: ${context.selectedRows}`;\n right.appendChild(selectedCount);\n }\n\n // Render custom panels\n if (config.customPanels) {\n for (const panel of config.customPanels) {\n const panelEl = renderCustomPanel(panel, context);\n switch (panel.position) {\n case 'left':\n left.appendChild(panelEl);\n break;\n case 'center':\n center.appendChild(panelEl);\n break;\n case 'right':\n right.appendChild(panelEl);\n break;\n }\n }\n }\n\n pinnedRows.appendChild(left);\n pinnedRows.appendChild(center);\n pinnedRows.appendChild(right);\n\n return pinnedRows;\n}\n\n/**\n * Creates a container for aggregation rows at top or bottom.\n *\n * @param position - 'top' or 'bottom'\n * @returns The container element\n */\nexport function createAggregationContainer(position: 'top' | 'bottom'): HTMLElement {\n const container = document.createElement('div');\n container.className = `tbw-aggregation-rows tbw-aggregation-rows-${position}`;\n // Use presentation role since aggregation rows are outside the role=\"grid\" element for layout reasons\n container.setAttribute('role', 'presentation');\n return container;\n}\n\n/**\n * Renders aggregation rows into a container.\n *\n * @param container - The container to render into\n * @param rows - Aggregation row configurations\n * @param columns - Current column configuration\n * @param dataRows - Current row data for aggregation calculations\n * @param globalFullWidth - Global fullWidth default from PinnedRowsConfig (default: false)\n */\nexport function renderAggregationRows(\n container: HTMLElement,\n rows: AggregationRowConfig[],\n columns: ColumnConfig[],\n dataRows: unknown[],\n globalFullWidth = false,\n): void {\n container.innerHTML = '';\n\n for (const rowConfig of rows) {\n const rowEl = document.createElement('div');\n rowEl.className = 'tbw-aggregation-row';\n // Use presentation role since aggregation rows are outside the role=\"grid\" element\n rowEl.setAttribute('role', 'presentation');\n if (rowConfig.id) {\n rowEl.setAttribute('data-aggregation-id', rowConfig.id);\n }\n\n // Per-row fullWidth overrides global default\n const isFullWidth = rowConfig.fullWidth ?? globalFullWidth;\n\n if (isFullWidth) {\n renderFullWidthAggregationRow(rowEl, rowConfig, columns, dataRows);\n } else {\n renderPerColumnAggregationRow(rowEl, rowConfig, columns, dataRows);\n }\n\n container.appendChild(rowEl);\n }\n}\n\n/**\n * Renders a full-width aggregation row: single spanning cell with label and inline aggregated values.\n */\nfunction renderFullWidthAggregationRow(\n rowEl: HTMLElement,\n rowConfig: AggregationRowConfig,\n columns: ColumnConfig[],\n dataRows: unknown[],\n): void {\n const cell = document.createElement('div');\n cell.className = 'tbw-aggregation-cell tbw-aggregation-cell-full';\n cell.style.gridColumn = '1 / -1';\n\n // Label (static string or dynamic function)\n const labelValue = typeof rowConfig.label === 'function' ? rowConfig.label(dataRows, columns) : rowConfig.label;\n if (labelValue) {\n const labelSpan = document.createElement('span');\n labelSpan.className = 'tbw-aggregation-label';\n labelSpan.textContent = labelValue;\n cell.appendChild(labelSpan);\n }\n\n // Inline aggregated values\n const aggregatesContainer = renderInlineAggregates(rowConfig, columns, dataRows);\n if (aggregatesContainer) {\n cell.appendChild(aggregatesContainer);\n }\n\n // If nothing was added (no label, no aggregates), ensure cell is empty but present\n rowEl.appendChild(cell);\n}\n\n/**\n * Renders per-column aggregation cells aligned to the grid template.\n */\nfunction renderPerColumnAggregationRow(\n rowEl: HTMLElement,\n rowConfig: AggregationRowConfig,\n columns: ColumnConfig[],\n dataRows: unknown[],\n): void {\n for (const col of columns) {\n const cell = document.createElement('div');\n cell.className = 'tbw-aggregation-cell';\n cell.setAttribute('data-field', col.field);\n\n const { value, formatter } = resolveAggregatedValue(rowConfig, col, dataRows);\n\n if (value != null) {\n cell.textContent = formatter ? formatter(value, col.field, col) : String(value);\n } else {\n cell.textContent = '';\n }\n rowEl.appendChild(cell);\n }\n}\n\n/**\n * Resolves the aggregated value for a single column in an aggregation row.\n * Returns the computed value and an optional formatter function.\n */\nfunction resolveAggregatedValue(\n rowConfig: AggregationRowConfig,\n col: ColumnConfig,\n dataRows: unknown[],\n): { value: unknown; formatter?: (value: unknown, field: string, column?: ColumnConfig) => string } {\n let value: unknown;\n let formatter: ((value: unknown, field: string, column?: ColumnConfig) => string) | undefined;\n\n // Check for aggregator first\n const aggDef = rowConfig.aggregators?.[col.field];\n if (aggDef) {\n if (isAggregatorConfig(aggDef)) {\n const aggFn = getAggregator(aggDef.aggFunc);\n if (aggFn) {\n value = aggFn(dataRows, col.field, col);\n }\n formatter = aggDef.formatter;\n } else {\n const aggFn = getAggregator(aggDef);\n if (aggFn) {\n value = aggFn(dataRows, col.field, col);\n }\n }\n } else if (rowConfig.cells && Object.prototype.hasOwnProperty.call(rowConfig.cells, col.field)) {\n const staticVal = rowConfig.cells[col.field];\n if (typeof staticVal === 'function') {\n value = staticVal(dataRows, col.field, col);\n } else {\n value = staticVal;\n }\n }\n\n return { value, formatter };\n}\n\n/**\n * Renders inline aggregate values for a full-width aggregation row.\n * Returns a container element with aggregate spans, or null if no aggregates are defined.\n */\nfunction renderInlineAggregates(\n rowConfig: AggregationRowConfig,\n columns: ColumnConfig[],\n dataRows: unknown[],\n): HTMLElement | null {\n // Collect fields that have aggregators or cell values\n const hasAggregators = rowConfig.aggregators && Object.keys(rowConfig.aggregators).length > 0;\n const hasCells = rowConfig.cells && Object.keys(rowConfig.cells).length > 0;\n if (!hasAggregators && !hasCells) return null;\n\n const container = document.createElement('span');\n container.className = 'tbw-aggregation-aggregates';\n\n for (const col of columns) {\n const { value, formatter } = resolveAggregatedValue(rowConfig, col, dataRows);\n if (value != null) {\n const span = document.createElement('span');\n span.className = 'tbw-aggregation-aggregate';\n span.setAttribute('data-field', col.field);\n const header = col.header ?? col.field;\n const displayValue = formatter ? formatter(value, col.field, col) : String(value);\n span.textContent = `${header}: ${displayValue}`;\n container.appendChild(span);\n }\n }\n\n return container.children.length > 0 ? container : null;\n}\n\n/**\n * Renders a custom panel element.\n *\n * @param panel - The panel definition\n * @param context - The current grid context\n * @returns The panel DOM element\n */\nfunction renderCustomPanel(panel: PinnedRowsPanel, context: PinnedRowsContext): HTMLElement {\n const panelEl = document.createElement('div');\n panelEl.className = 'tbw-status-panel tbw-status-panel-custom';\n panelEl.id = `status-panel-${panel.id}`;\n\n const content = panel.render(context);\n\n if (typeof content === 'string') {\n panelEl.innerHTML = content;\n } else {\n panelEl.appendChild(content);\n }\n\n return panelEl;\n}\n\n/**\n * Builds the status bar context from grid state and plugin states.\n *\n * @param rows - Current row data\n * @param columns - Current column configuration\n * @param grid - Grid element reference\n * @param selectionState - Optional selection plugin state\n * @param filterState - Optional filtering plugin state\n * @returns The status bar context\n */\nexport function buildContext(\n rows: unknown[],\n columns: unknown[],\n grid: HTMLElement,\n selectionState?: { selected: Set<number> } | null,\n filterState?: { cachedResult: unknown[] | null } | null,\n): PinnedRowsContext {\n return {\n totalRows: rows.length,\n filteredRows: filterState?.cachedResult?.length ?? rows.length,\n selectedRows: selectionState?.selected?.size ?? 0,\n columns: columns as PinnedRowsContext['columns'],\n rows,\n grid,\n };\n}\n\n// Keep old name as alias for backwards compatibility\nexport const createPinnedRowsElement = createInfoBarElement;\n","/**\n * Pinned Rows Plugin (Class-based)\n *\n * Adds info bars and aggregation rows to the grid.\n * - Info bar: Shows row counts, selection info, and custom panels\n * - Aggregation rows: Footer/header rows with computed values (sum, avg, etc.)\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport { buildContext, createAggregationContainer, createInfoBarElement, renderAggregationRows } from './pinned-rows';\nimport styles from './pinned-rows.css?inline';\nimport type { AggregationRowConfig, PinnedRowsConfig, PinnedRowsContext, PinnedRowsPanel } from './types';\n\n/**\n * Pinned Rows (Status Bar) Plugin for tbw-grid\n *\n * Creates fixed status bars at the top or bottom of the grid for displaying aggregations,\n * row counts, or custom content. Think of it as the \"totals row\" you'd see in a spreadsheet—\n * always visible regardless of scroll position.\n *\n * ## Installation\n *\n * ```ts\n * import { PinnedRowsPlugin } from '@toolbox-web/grid/plugins/pinned-rows';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `position` | `'top' \\| 'bottom'` | `'bottom'` | Status bar position |\n * | `showRowCount` | `boolean` | `true` | Show total row count |\n * | `showSelectedCount` | `boolean` | `true` | Show selected row count |\n * | `showFilteredCount` | `boolean` | `true` | Show filtered row count |\n * | `fullWidth` | `boolean` | `false` | Default fullWidth for aggregation rows |\n * | `aggregationRows` | `AggregationRowConfig[]` | - | Aggregation row configs |\n *\n * ## Built-in Aggregation Functions\n *\n * | Function | Description |\n * |----------|-------------|\n * | `sum` | Sum of values |\n * | `avg` | Average of values |\n * | `count` | Count of rows |\n * | `min` | Minimum value |\n * | `max` | Maximum value |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-pinned-rows-bg` | `var(--tbw-color-panel-bg)` | Status bar background |\n * | `--tbw-pinned-rows-border` | `var(--tbw-color-border)` | Status bar border |\n *\n * @example Status Bar with Aggregation\n * ```ts\n * import '@toolbox-web/grid';\n * import { PinnedRowsPlugin } from '@toolbox-web/grid/plugins/pinned-rows';\n *\n * grid.gridConfig = {\n * columns: [\n * { field: 'product', header: 'Product' },\n * { field: 'quantity', header: 'Qty', type: 'number' },\n * { field: 'price', header: 'Price', type: 'currency' },\n * ],\n * plugins: [\n * new PinnedRowsPlugin({\n * position: 'bottom',\n * showRowCount: true,\n * aggregationRows: [\n * {\n * id: 'totals',\n * aggregators: { quantity: 'sum', price: 'sum' },\n * cells: { product: 'Totals:' },\n * },\n * ],\n * }),\n * ],\n * };\n * ```\n *\n * @see {@link PinnedRowsConfig} for all configuration options\n * @see {@link AggregationRowConfig} for aggregation row structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class PinnedRowsPlugin extends BaseGridPlugin<PinnedRowsConfig> {\n /** @internal */\n readonly name = 'pinnedRows';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<PinnedRowsConfig> {\n return {\n position: 'bottom',\n showRowCount: true,\n showSelectedCount: true,\n showFilteredCount: true,\n };\n }\n\n // #region Internal State\n private infoBarElement: HTMLElement | null = null;\n private topAggregationContainer: HTMLElement | null = null;\n private bottomAggregationContainer: HTMLElement | null = null;\n private footerWrapper: HTMLElement | null = null;\n // #endregion\n\n // #region Lifecycle\n /** @internal */\n override detach(): void {\n if (this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n if (this.topAggregationContainer) {\n this.topAggregationContainer.remove();\n this.topAggregationContainer = null;\n }\n if (this.bottomAggregationContainer) {\n this.bottomAggregationContainer.remove();\n this.bottomAggregationContainer = null;\n }\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n }\n // #endregion\n\n // #region Hooks\n /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Use .tbw-scroll-area so footer is inside the horizontal scroll area,\n // otherwise fall back to .tbw-grid-content or root container\n const container =\n gridEl.querySelector('.tbw-scroll-area') ?? gridEl.querySelector('.tbw-grid-content') ?? gridEl.children[0];\n if (!container) return;\n\n // Clear orphaned element references if they were removed from the DOM\n // (e.g., by buildGridDOMIntoShadow calling replaceChildren())\n // We check if the element is still inside the container rather than isConnected,\n // because in unit tests the mock grid may not be attached to document.body\n if (this.footerWrapper && !container.contains(this.footerWrapper)) {\n this.footerWrapper = null;\n this.bottomAggregationContainer = null;\n this.infoBarElement = null;\n }\n if (this.topAggregationContainer && !container.contains(this.topAggregationContainer)) {\n this.topAggregationContainer = null;\n }\n if (this.infoBarElement && !container.contains(this.infoBarElement)) {\n this.infoBarElement = null;\n }\n\n // Build context with plugin states\n const selectionState = this.getSelectionState();\n const filterState = this.getFilterState();\n\n const context = buildContext(\n this.sourceRows as unknown[],\n this.columns as unknown[],\n this.grid as unknown as HTMLElement,\n selectionState,\n filterState,\n );\n\n // #region Handle Aggregation Rows\n const aggregationRows = this.config.aggregationRows || [];\n const topRows = aggregationRows.filter((r) => r.position === 'top');\n const bottomRows = aggregationRows.filter((r) => r.position !== 'top');\n\n // Top aggregation rows\n if (topRows.length > 0) {\n if (!this.topAggregationContainer) {\n this.topAggregationContainer = createAggregationContainer('top');\n const header = gridEl.querySelector('.header');\n if (header && header.nextSibling) {\n container.insertBefore(this.topAggregationContainer, header.nextSibling);\n } else {\n container.appendChild(this.topAggregationContainer);\n }\n }\n renderAggregationRows(\n this.topAggregationContainer,\n topRows,\n this.visibleColumns as ColumnConfig[],\n this.sourceRows as unknown[],\n this.config.fullWidth,\n );\n } else if (this.topAggregationContainer) {\n this.topAggregationContainer.remove();\n this.topAggregationContainer = null;\n }\n\n // Handle footer\n const hasInfoContent =\n this.config.showRowCount !== false ||\n (this.config.showSelectedCount && context.selectedRows > 0) ||\n (this.config.showFilteredCount && context.filteredRows !== context.totalRows) ||\n (this.config.customPanels && this.config.customPanels.length > 0);\n const hasBottomInfoBar = hasInfoContent && this.config.position !== 'top';\n const needsFooter = bottomRows.length > 0 || hasBottomInfoBar;\n\n // Handle top info bar\n if (hasInfoContent && this.config.position === 'top') {\n if (!this.infoBarElement) {\n this.infoBarElement = createInfoBarElement(this.config, context);\n container.insertBefore(this.infoBarElement, container.firstChild);\n } else {\n const newInfoBar = createInfoBarElement(this.config, context);\n this.infoBarElement.replaceWith(newInfoBar);\n this.infoBarElement = newInfoBar;\n }\n } else if (this.config.position === 'top' && this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n\n // Create/manage footer wrapper\n if (needsFooter) {\n if (!this.footerWrapper) {\n this.footerWrapper = document.createElement('div');\n this.footerWrapper.className = 'tbw-footer';\n container.appendChild(this.footerWrapper);\n }\n\n this.footerWrapper.innerHTML = '';\n\n if (bottomRows.length > 0) {\n if (!this.bottomAggregationContainer) {\n this.bottomAggregationContainer = createAggregationContainer('bottom');\n }\n this.footerWrapper.appendChild(this.bottomAggregationContainer);\n renderAggregationRows(\n this.bottomAggregationContainer,\n bottomRows,\n this.visibleColumns as ColumnConfig[],\n this.sourceRows as unknown[],\n this.config.fullWidth,\n );\n }\n\n if (hasBottomInfoBar) {\n this.infoBarElement = createInfoBarElement(this.config, context);\n this.footerWrapper.appendChild(this.infoBarElement);\n }\n } else {\n this.cleanupFooter();\n }\n // #endregion\n }\n // #endregion\n\n // #region Private Methods\n private cleanup(): void {\n if (this.infoBarElement) {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n if (this.topAggregationContainer) {\n this.topAggregationContainer.remove();\n this.topAggregationContainer = null;\n }\n if (this.bottomAggregationContainer) {\n this.bottomAggregationContainer.remove();\n this.bottomAggregationContainer = null;\n }\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n }\n\n private cleanupFooter(): void {\n if (this.footerWrapper) {\n this.footerWrapper.remove();\n this.footerWrapper = null;\n }\n if (this.bottomAggregationContainer) {\n this.bottomAggregationContainer.remove();\n this.bottomAggregationContainer = null;\n }\n if (this.infoBarElement && this.config.position !== 'top') {\n this.infoBarElement.remove();\n this.infoBarElement = null;\n }\n }\n\n private getSelectionState(): { selected: Set<number> } | null {\n // Try to get selection plugin state\n try {\n return (this.grid?.getPluginState?.('selection') as { selected: Set<number> } | null) ?? null;\n } catch {\n return null;\n }\n }\n\n private getFilterState(): { cachedResult: unknown[] | null } | null {\n try {\n return (this.grid?.getPluginState?.('filtering') as { cachedResult: unknown[] | null } | null) ?? null;\n } catch {\n return null;\n }\n }\n // #endregion\n\n // #region Public API\n /**\n * Refresh the status bar to reflect current grid state.\n */\n refresh(): void {\n this.requestRender();\n }\n\n /**\n * Get the current status bar context.\n * @returns The context with row counts and other info\n */\n getContext(): PinnedRowsContext {\n const selectionState = this.getSelectionState();\n const filterState = this.getFilterState();\n\n return buildContext(\n this.rows as unknown[],\n this.columns as unknown[],\n this.grid as unknown as HTMLElement,\n selectionState,\n filterState,\n );\n }\n\n /**\n * Add a custom panel to the info bar.\n * @param panel - The panel configuration to add\n */\n addPanel(panel: PinnedRowsPanel): void {\n if (!this.config.customPanels) {\n this.config.customPanels = [];\n }\n this.config.customPanels.push(panel);\n this.requestRender();\n }\n\n /**\n * Remove a custom panel by ID.\n * @param id - The panel ID to remove\n */\n removePanel(id: string): void {\n if (this.config.customPanels) {\n this.config.customPanels = this.config.customPanels.filter((p) => p.id !== id);\n this.requestRender();\n }\n }\n\n /**\n * Add an aggregation row.\n * @param row - The aggregation row configuration\n */\n addAggregationRow(row: AggregationRowConfig): void {\n if (!this.config.aggregationRows) {\n this.config.aggregationRows = [];\n }\n this.config.aggregationRows.push(row);\n this.requestRender();\n }\n\n /**\n * Remove an aggregation row by ID.\n * @param id - The aggregation row ID to remove\n */\n removeAggregationRow(id: string): void {\n if (this.config.aggregationRows) {\n this.config.aggregationRows = this.config.aggregationRows.filter((r) => r.id !== id);\n this.requestRender();\n }\n }\n // #endregion\n}\n","import { getValueAggregator } from '../../core/internal/aggregators';\nimport type { PivotConfig } from './types';\n\n// Re-export for backward compatibility within pivot plugin\nexport const getPivotAggregator = getValueAggregator;\n\nexport function validatePivotConfig(config: PivotConfig): string[] {\n const errors: string[] = [];\n\n if (!config.rowGroupFields?.length && !config.columnGroupFields?.length) {\n errors.push('At least one row or column group field is required');\n }\n\n if (!config.valueFields?.length) {\n errors.push('At least one value field is required');\n }\n\n return errors;\n}\n\nexport function createValueKey(columnValues: string[], valueField: string): string {\n return [...columnValues, valueField].join('|');\n}\n","import { createValueKey, getPivotAggregator } from './pivot-model';\nimport type { PivotConfig, PivotResult, PivotRow, PivotValueField } from './types';\n\nexport type PivotDataRow = Record<string, unknown>;\n\n/**\n * Build a hierarchical pivot result from flat data.\n * Supports multiple row group fields for nested hierarchy.\n */\nexport function buildPivot(rows: PivotDataRow[], config: PivotConfig): PivotResult {\n const rowGroupFields = config.rowGroupFields ?? [];\n const columnGroupFields = config.columnGroupFields ?? [];\n const valueFields = config.valueFields ?? [];\n\n // Get unique column combinations\n const columnKeys = getUniqueColumnKeys(rows, columnGroupFields);\n\n // Build hierarchical pivot rows\n const pivotRows = buildHierarchicalPivotRows(\n rows,\n rowGroupFields,\n columnGroupFields,\n columnKeys,\n valueFields,\n 0, // starting depth\n '', // parent key prefix\n );\n\n // Calculate grand totals\n const totals = calculateTotals(pivotRows, columnKeys, valueFields);\n const grandTotal = Object.values(totals).reduce((a, b) => a + b, 0);\n\n return {\n rows: pivotRows,\n columnKeys,\n totals,\n grandTotal,\n };\n}\n\n/**\n * Get unique column key combinations from the data.\n */\nexport function getUniqueColumnKeys(rows: PivotDataRow[], columnFields: string[]): string[] {\n if (columnFields.length === 0) return ['value'];\n\n const keys = new Set<string>();\n for (const row of rows) {\n const key = columnFields.map((f) => String(row[f] ?? '')).join('|');\n keys.add(key);\n }\n return [...keys].sort();\n}\n\n/**\n * Group rows by a single field.\n */\nexport function groupByField(rows: PivotDataRow[], field: string): Map<string, PivotDataRow[]> {\n const groups = new Map<string, PivotDataRow[]>();\n\n for (const row of rows) {\n const key = String(row[field] ?? '');\n const existing = groups.get(key);\n if (existing) {\n existing.push(row);\n } else {\n groups.set(key, [row]);\n }\n }\n\n return groups;\n}\n\n/**\n * Group rows by multiple fields (legacy flat grouping).\n */\nexport function groupByFields(rows: PivotDataRow[], fields: string[]): Map<string, PivotDataRow[]> {\n const groups = new Map<string, PivotDataRow[]>();\n\n for (const row of rows) {\n const key = fields.map((f) => String(row[f] ?? '')).join('|');\n const existing = groups.get(key);\n if (existing) {\n existing.push(row);\n } else {\n groups.set(key, [row]);\n }\n }\n\n return groups;\n}\n\n/**\n * Build hierarchical pivot rows recursively.\n * Each level of rowGroupFields creates a new depth level.\n */\nexport function buildHierarchicalPivotRows(\n rows: PivotDataRow[],\n rowGroupFields: string[],\n columnFields: string[],\n columnKeys: string[],\n valueFields: PivotValueField[],\n depth: number,\n parentKey: string,\n): PivotRow[] {\n const result: PivotRow[] = [];\n\n // If no more row group fields, we're at the leaf level - aggregate the data\n if (rowGroupFields.length === 0) {\n // This shouldn't normally happen as we need at least one grouping field\n // But handle it by creating a single aggregated row\n const values = aggregateValues(rows, columnFields, columnKeys, valueFields);\n const total = calculateRowTotal(values);\n result.push({\n rowKey: parentKey || 'all',\n rowLabel: parentKey || 'All',\n depth,\n values,\n total,\n isGroup: false,\n rowCount: rows.length,\n });\n return result;\n }\n\n // Get the current grouping field\n const currentField = rowGroupFields[0];\n const remainingFields = rowGroupFields.slice(1);\n const hasChildren = remainingFields.length > 0;\n\n // Group rows by current field\n const grouped = groupByField(rows, currentField);\n\n for (const [groupValue, groupRows] of grouped) {\n const rowKey = parentKey ? `${parentKey}|${groupValue}` : groupValue;\n\n // Aggregate values for this group (sum of all child rows)\n const values = aggregateValues(groupRows, columnFields, columnKeys, valueFields);\n const total = calculateRowTotal(values);\n\n // Build children if there are more grouping levels\n let children: PivotRow[] | undefined;\n if (hasChildren) {\n children = buildHierarchicalPivotRows(\n groupRows,\n remainingFields,\n columnFields,\n columnKeys,\n valueFields,\n depth + 1,\n rowKey,\n );\n }\n\n result.push({\n rowKey,\n rowLabel: groupValue || '(blank)',\n depth,\n values,\n total,\n isGroup: hasChildren,\n children,\n rowCount: groupRows.length,\n });\n }\n\n return result;\n}\n\n/**\n * Aggregate values for a set of rows across all column keys.\n */\nexport function aggregateValues(\n rows: PivotDataRow[],\n columnFields: string[],\n columnKeys: string[],\n valueFields: PivotValueField[],\n): Record<string, number | null> {\n const values: Record<string, number | null> = {};\n\n for (const colKey of columnKeys) {\n for (const vf of valueFields) {\n // Filter rows that match this column key\n const matchingRows =\n columnFields.length > 0\n ? rows.filter((r) => columnFields.map((f) => String(r[f] ?? '')).join('|') === colKey)\n : rows;\n\n const nums = matchingRows.map((r) => Number(r[vf.field]) || 0);\n const aggregator = getPivotAggregator(vf.aggFunc);\n const aggregatedResult = nums.length > 0 ? aggregator(nums) : null;\n\n const valueKey = createValueKey([colKey], vf.field);\n values[valueKey] = aggregatedResult;\n }\n }\n\n return values;\n}\n\n/**\n * Calculate the total for a row's values.\n */\nexport function calculateRowTotal(values: Record<string, number | null>): number {\n let sum = 0;\n for (const val of Object.values(values)) {\n sum += val ?? 0;\n }\n return sum;\n}\n\n/**\n * Legacy flat pivot row building (for backwards compatibility).\n */\nexport function buildPivotRows(\n groupedData: Map<string, PivotDataRow[]>,\n columnFields: string[],\n columnKeys: string[],\n valueFields: PivotValueField[],\n depth: number,\n): PivotRow[] {\n const result: PivotRow[] = [];\n\n for (const [rowKey, groupRows] of groupedData) {\n const values = aggregateValues(groupRows, columnFields, columnKeys, valueFields);\n const total = calculateRowTotal(values);\n\n result.push({\n rowKey,\n rowLabel: rowKey || '(blank)',\n depth,\n values,\n total,\n isGroup: false,\n rowCount: groupRows.length,\n });\n }\n\n return result;\n}\n\n/**\n * Calculate grand totals across all pivot rows.\n */\nexport function calculateTotals(\n pivotRows: PivotRow[],\n columnKeys: string[],\n valueFields: PivotValueField[],\n): Record<string, number> {\n const totals: Record<string, number> = {};\n\n // Recursively sum all rows (including nested children)\n function sumRows(rows: PivotRow[]) {\n for (const row of rows) {\n // Only count leaf rows to avoid double-counting\n if (!row.isGroup || !row.children?.length) {\n for (const colKey of columnKeys) {\n for (const vf of valueFields) {\n const valueKey = createValueKey([colKey], vf.field);\n totals[valueKey] = (totals[valueKey] ?? 0) + (row.values[valueKey] ?? 0);\n }\n }\n } else if (row.children) {\n sumRows(row.children);\n }\n }\n }\n\n sumRows(pivotRows);\n return totals;\n}\n\n/**\n * Flatten hierarchical pivot rows for rendering.\n * Respects expanded state - only includes children of expanded groups.\n */\nexport function flattenPivotRows(rows: PivotRow[], expandedKeys?: Set<string>, defaultExpanded = true): PivotRow[] {\n const result: PivotRow[] = [];\n\n function flatten(row: PivotRow) {\n result.push(row);\n\n // Check if this group is expanded\n const isExpanded = expandedKeys ? expandedKeys.has(row.rowKey) : defaultExpanded;\n\n // Only include children if expanded\n if (row.children && isExpanded) {\n for (const child of row.children) {\n flatten(child);\n }\n }\n }\n\n for (const row of rows) {\n flatten(row);\n }\n\n return result;\n}\n\n/**\n * Get all group keys from pivot rows (for expand all / collapse all).\n */\nexport function getAllGroupKeys(rows: PivotRow[]): string[] {\n const keys: string[] = [];\n\n function collectKeys(row: PivotRow) {\n if (row.isGroup) {\n keys.push(row.rowKey);\n }\n if (row.children) {\n for (const child of row.children) {\n collectKeys(child);\n }\n }\n }\n\n for (const row of rows) {\n collectKeys(row);\n }\n\n return keys;\n}\n","/**\n * Pivot Tool Panel Rendering\n *\n * Pure functions for rendering the pivot configuration panel.\n * Separated from PivotPlugin for better code organization.\n */\n\nimport type { AggFunc, PivotConfig, PivotValueField } from './types';\n\n/** All available aggregation functions */\nexport const AGG_FUNCS: AggFunc[] = ['sum', 'avg', 'count', 'min', 'max', 'first', 'last'];\n\n/** Field info for available fields */\nexport interface FieldInfo {\n field: string;\n header: string;\n}\n\n/** Callbacks for panel interactions */\nexport interface PanelCallbacks {\n onTogglePivot: (enabled: boolean) => void;\n onAddFieldToZone: (field: string, zone: 'rowGroups' | 'columnGroups') => void;\n onRemoveFieldFromZone: (field: string, zone: 'rowGroups' | 'columnGroups') => void;\n onAddValueField: (field: string, aggFunc: AggFunc) => void;\n onRemoveValueField: (field: string) => void;\n onUpdateValueAggFunc: (field: string, aggFunc: AggFunc) => void;\n onOptionChange: (option: 'showTotals' | 'showGrandTotal', value: boolean) => void;\n getAvailableFields: () => FieldInfo[];\n}\n\n/** Internal context passed to rendering functions */\ninterface RenderContext {\n config: PivotConfig;\n callbacks: PanelCallbacks;\n signal: AbortSignal;\n}\n\n/**\n * Render the complete pivot panel content.\n * Returns a cleanup function that removes all event listeners and DOM elements.\n */\nexport function renderPivotPanel(\n container: HTMLElement,\n config: PivotConfig,\n isActive: boolean,\n callbacks: PanelCallbacks,\n): () => void {\n // Create AbortController for automatic listener cleanup\n const controller = new AbortController();\n const ctx: RenderContext = { config, callbacks, signal: controller.signal };\n\n const wrapper = document.createElement('div');\n wrapper.className = 'tbw-pivot-panel';\n\n // Options section (at top, includes pivot toggle)\n wrapper.appendChild(createSection('Options', () => createOptionsPanel(isActive, ctx)));\n\n // Row Groups section\n wrapper.appendChild(createSection('Row Groups', () => createFieldZone('rowGroups', ctx)));\n\n // Column Groups section\n wrapper.appendChild(createSection('Column Groups', () => createFieldZone('columnGroups', ctx)));\n\n // Values section\n wrapper.appendChild(createSection('Values', () => createValuesZone(ctx)));\n\n // Available fields section\n wrapper.appendChild(createSection('Available Fields', () => createAvailableFieldsZone(ctx)));\n\n container.appendChild(wrapper);\n\n // Cleanup: abort all listeners, then remove DOM\n return () => {\n controller.abort();\n wrapper.remove();\n };\n}\n\n/**\n * Create a collapsible section wrapper.\n */\nfunction createSection(title: string, contentFactory: () => HTMLElement): HTMLElement {\n const section = document.createElement('div');\n section.className = 'tbw-pivot-section';\n\n const header = document.createElement('div');\n header.className = 'tbw-pivot-section-header';\n header.textContent = title;\n\n const content = document.createElement('div');\n content.className = 'tbw-pivot-section-content';\n content.appendChild(contentFactory());\n\n section.appendChild(header);\n section.appendChild(content);\n\n return section;\n}\n\n/**\n * Create a drop zone for row/column group fields.\n */\nfunction createFieldZone(zoneType: 'rowGroups' | 'columnGroups', ctx: RenderContext): HTMLElement {\n const { config, callbacks, signal } = ctx;\n const zone = document.createElement('div');\n zone.className = 'tbw-pivot-drop-zone';\n zone.setAttribute('data-zone', zoneType);\n\n const currentFields = zoneType === 'rowGroups' ? (config.rowGroupFields ?? []) : (config.columnGroupFields ?? []);\n\n if (currentFields.length === 0) {\n const placeholder = document.createElement('div');\n placeholder.className = 'tbw-pivot-placeholder';\n placeholder.textContent = 'Drag fields here or click to add';\n zone.appendChild(placeholder);\n } else {\n for (const field of currentFields) {\n zone.appendChild(createFieldChip(field, zoneType, ctx));\n }\n }\n\n // Drop handling\n zone.addEventListener(\n 'dragover',\n (e) => {\n e.preventDefault();\n zone.classList.add('drag-over');\n },\n { signal },\n );\n\n zone.addEventListener(\n 'dragleave',\n () => {\n zone.classList.remove('drag-over');\n },\n { signal },\n );\n\n zone.addEventListener(\n 'drop',\n (e) => {\n e.preventDefault();\n zone.classList.remove('drag-over');\n\n const field = e.dataTransfer?.getData('text/plain');\n if (field) {\n callbacks.onAddFieldToZone(field, zoneType);\n }\n },\n { signal },\n );\n\n return zone;\n}\n\n/**\n * Create a field chip for row/column zones.\n */\nfunction createFieldChip(field: string, zoneType: 'rowGroups' | 'columnGroups', ctx: RenderContext): HTMLElement {\n const { callbacks, signal } = ctx;\n const chip = document.createElement('div');\n chip.className = 'tbw-pivot-field-chip';\n chip.draggable = true;\n\n const fieldInfo = callbacks.getAvailableFields().find((f) => f.field === field);\n const label = document.createElement('span');\n label.className = 'tbw-pivot-chip-label';\n label.textContent = fieldInfo?.header ?? field;\n\n const removeBtn = document.createElement('button');\n removeBtn.className = 'tbw-pivot-chip-remove';\n removeBtn.innerHTML = '×';\n removeBtn.title = 'Remove field';\n removeBtn.addEventListener(\n 'click',\n (e) => {\n e.stopPropagation();\n callbacks.onRemoveFieldFromZone(field, zoneType);\n },\n { signal },\n );\n\n chip.appendChild(label);\n chip.appendChild(removeBtn);\n\n // Drag handling for reordering\n chip.addEventListener(\n 'dragstart',\n (e) => {\n e.dataTransfer?.setData('text/plain', field);\n e.dataTransfer?.setData('source-zone', zoneType);\n chip.classList.add('dragging');\n },\n { signal },\n );\n\n chip.addEventListener(\n 'dragend',\n () => {\n chip.classList.remove('dragging');\n },\n { signal },\n );\n\n return chip;\n}\n\n/**\n * Create the values zone with aggregation controls.\n */\nfunction createValuesZone(ctx: RenderContext): HTMLElement {\n const { config, callbacks, signal } = ctx;\n const zone = document.createElement('div');\n zone.className = 'tbw-pivot-drop-zone tbw-pivot-values-zone';\n zone.setAttribute('data-zone', 'values');\n\n const currentValues = config.valueFields ?? [];\n\n if (currentValues.length === 0) {\n const placeholder = document.createElement('div');\n placeholder.className = 'tbw-pivot-placeholder';\n placeholder.textContent = 'Drag numeric fields here for aggregation';\n zone.appendChild(placeholder);\n } else {\n for (const valueField of currentValues) {\n zone.appendChild(createValueChip(valueField, ctx));\n }\n }\n\n // Drop handling with signal for cleanup\n zone.addEventListener(\n 'dragover',\n (e) => {\n e.preventDefault();\n zone.classList.add('drag-over');\n },\n { signal },\n );\n\n zone.addEventListener(\n 'dragleave',\n () => {\n zone.classList.remove('drag-over');\n },\n { signal },\n );\n\n zone.addEventListener(\n 'drop',\n (e) => {\n e.preventDefault();\n zone.classList.remove('drag-over');\n const field = e.dataTransfer?.getData('text/plain');\n if (field) {\n callbacks.onAddValueField(field, 'sum');\n }\n },\n { signal },\n );\n\n return zone;\n}\n\n/**\n * Create a value chip with aggregation selector.\n */\nfunction createValueChip(valueField: PivotValueField, ctx: RenderContext): HTMLElement {\n const { callbacks, signal } = ctx;\n const chip = document.createElement('div');\n chip.className = 'tbw-pivot-field-chip tbw-pivot-value-chip';\n\n const fieldInfo = callbacks.getAvailableFields().find((f) => f.field === valueField.field);\n\n const labelWrapper = document.createElement('div');\n labelWrapper.className = 'tbw-pivot-value-label-wrapper';\n\n const label = document.createElement('span');\n label.className = 'tbw-pivot-chip-label';\n label.textContent = fieldInfo?.header ?? valueField.field;\n\n const aggSelect = document.createElement('select');\n aggSelect.className = 'tbw-pivot-agg-select';\n aggSelect.title = 'Aggregation function';\n\n for (const aggFunc of AGG_FUNCS) {\n const option = document.createElement('option');\n option.value = aggFunc;\n option.textContent = aggFunc.toUpperCase();\n option.selected = aggFunc === valueField.aggFunc;\n aggSelect.appendChild(option);\n }\n\n aggSelect.addEventListener(\n 'change',\n () => {\n callbacks.onUpdateValueAggFunc(valueField.field, aggSelect.value as AggFunc);\n },\n { signal },\n );\n\n const removeBtn = document.createElement('button');\n removeBtn.className = 'tbw-pivot-chip-remove';\n removeBtn.innerHTML = '×';\n removeBtn.title = 'Remove value field';\n removeBtn.addEventListener(\n 'click',\n (e) => {\n e.stopPropagation();\n callbacks.onRemoveValueField(valueField.field);\n },\n { signal },\n );\n\n labelWrapper.appendChild(label);\n labelWrapper.appendChild(aggSelect);\n\n chip.appendChild(labelWrapper);\n chip.appendChild(removeBtn);\n\n return chip;\n}\n\n/**\n * Create the available fields zone.\n */\nfunction createAvailableFieldsZone(ctx: RenderContext): HTMLElement {\n const { config, callbacks, signal } = ctx;\n const zone = document.createElement('div');\n zone.className = 'tbw-pivot-available-fields';\n\n const allFields = callbacks.getAvailableFields();\n const usedFields = new Set([\n ...(config.rowGroupFields ?? []),\n ...(config.columnGroupFields ?? []),\n ...(config.valueFields?.map((v) => v.field) ?? []),\n ]);\n\n // Filter to show only unused fields\n const availableFields = allFields.filter((f) => !usedFields.has(f.field));\n\n if (availableFields.length === 0) {\n const empty = document.createElement('div');\n empty.className = 'tbw-pivot-placeholder';\n empty.textContent = 'All fields are in use';\n zone.appendChild(empty);\n } else {\n for (const field of availableFields) {\n const chip = document.createElement('div');\n chip.className = 'tbw-pivot-field-chip available';\n chip.textContent = field.header;\n chip.draggable = true;\n chip.title = `Drag to add \"${field.field}\" to a zone`;\n\n chip.addEventListener(\n 'dragstart',\n (e) => {\n e.dataTransfer?.setData('text/plain', field.field);\n chip.classList.add('dragging');\n },\n { signal },\n );\n\n chip.addEventListener(\n 'dragend',\n () => {\n chip.classList.remove('dragging');\n },\n { signal },\n );\n\n zone.appendChild(chip);\n }\n }\n\n return zone;\n}\n\n/**\n * Create the options panel with pivot toggle and checkboxes for totals.\n */\nfunction createOptionsPanel(isActive: boolean, ctx: RenderContext): HTMLElement {\n const { config, callbacks, signal } = ctx;\n const panel = document.createElement('div');\n panel.className = 'tbw-pivot-options';\n\n // Pivot Mode toggle\n panel.appendChild(\n createCheckbox(\n 'Enable Pivot View',\n isActive,\n (checked) => {\n callbacks.onTogglePivot(checked);\n },\n signal,\n ),\n );\n\n // Show Totals checkbox\n panel.appendChild(\n createCheckbox(\n 'Show Row Totals',\n config.showTotals ?? true,\n (checked) => {\n callbacks.onOptionChange('showTotals', checked);\n },\n signal,\n ),\n );\n\n // Show Grand Total checkbox\n panel.appendChild(\n createCheckbox(\n 'Show Grand Total',\n config.showGrandTotal ?? true,\n (checked) => {\n callbacks.onOptionChange('showGrandTotal', checked);\n },\n signal,\n ),\n );\n\n return panel;\n}\n\n/**\n * Create a checkbox with label.\n */\nfunction createCheckbox(\n label: string,\n checked: boolean,\n onChange: (checked: boolean) => void,\n signal: AbortSignal,\n): HTMLElement {\n const wrapper = document.createElement('label');\n wrapper.className = 'tbw-pivot-checkbox';\n\n const input = document.createElement('input');\n input.type = 'checkbox';\n input.checked = checked;\n input.addEventListener('change', () => onChange(input.checked), { signal });\n\n const span = document.createElement('span');\n span.textContent = label;\n\n wrapper.appendChild(input);\n wrapper.appendChild(span);\n\n return wrapper;\n}\n","/**\n * Pivot Row Rendering\n *\n * Pure functions for rendering pivot rows (group rows, leaf rows, grand total).\n * Separated from PivotPlugin for better code organization.\n */\n\nimport type { ColumnConfig, IconValue } from '../../core/types';\n\n/** Row data with pivot metadata */\nexport interface PivotRowData {\n __pivotRowKey?: string;\n __pivotLabel?: string;\n __pivotDepth?: number;\n __pivotIndent?: number;\n __pivotExpanded?: boolean;\n __pivotHasChildren?: boolean;\n __pivotRowCount?: number;\n __pivotIsGrandTotal?: boolean;\n [key: string]: unknown;\n}\n\n/** Context for row rendering */\nexport interface RowRenderContext {\n columns: ColumnConfig[];\n rowIndex: number;\n onToggle: (key: string) => void;\n resolveIcon: (iconKey: 'expand' | 'collapse') => IconValue;\n setIcon: (element: HTMLElement, icon: IconValue) => void;\n}\n\n/**\n * Render a pivot group row (has children, can expand/collapse).\n */\nexport function renderPivotGroupRow(row: PivotRowData, rowEl: HTMLElement, ctx: RowRenderContext): boolean {\n rowEl.className = 'data-grid-row pivot-group-row';\n rowEl.setAttribute('data-pivot-depth', String(row.__pivotDepth ?? 0));\n rowEl.setAttribute('data-pivot-key', String(row.__pivotRowKey ?? ''));\n rowEl.setAttribute('role', 'row');\n // Note: aria-expanded is not set here because it's only valid in treegrid, not grid\n // The expand/collapse state is conveyed via the toggle button's aria-label\n rowEl.innerHTML = '';\n\n ctx.columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell';\n cell.setAttribute('data-col', String(colIdx));\n cell.setAttribute('data-row', String(ctx.rowIndex));\n cell.setAttribute('role', 'gridcell');\n\n if (colIdx === 0) {\n // First column: indent + toggle + label + count\n const indent = Number(row.__pivotIndent) || 0;\n cell.style.paddingLeft = `${indent}px`;\n\n // Toggle button\n const rowKey = String(row.__pivotRowKey);\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'pivot-toggle';\n btn.setAttribute('aria-label', row.__pivotExpanded ? 'Collapse group' : 'Expand group');\n ctx.setIcon(btn, ctx.resolveIcon(row.__pivotExpanded ? 'collapse' : 'expand'));\n btn.addEventListener('click', (e) => {\n e.stopPropagation();\n ctx.onToggle(rowKey);\n });\n cell.appendChild(btn);\n\n // Group label\n const label = document.createElement('span');\n label.className = 'pivot-label';\n label.textContent = String(row.__pivotLabel ?? '');\n cell.appendChild(label);\n\n // Row count\n const count = document.createElement('span');\n count.className = 'pivot-count';\n count.textContent = ` (${Number(row.__pivotRowCount) || 0})`;\n cell.appendChild(count);\n } else {\n // Other columns: render value\n const value = row[col.field];\n cell.textContent = value != null ? String(value) : '';\n }\n\n rowEl.appendChild(cell);\n });\n\n return true;\n}\n\n/**\n * Render a pivot leaf row (no children, just indentation).\n */\nexport function renderPivotLeafRow(\n row: PivotRowData,\n rowEl: HTMLElement,\n columns: ColumnConfig[],\n rowIndex: number,\n): boolean {\n rowEl.className = 'data-grid-row pivot-leaf-row';\n rowEl.setAttribute('data-pivot-depth', String(row.__pivotDepth ?? 0));\n rowEl.setAttribute('data-pivot-key', String(row.__pivotRowKey ?? ''));\n rowEl.innerHTML = '';\n\n columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell';\n cell.setAttribute('data-col', String(colIdx));\n cell.setAttribute('data-row', String(rowIndex));\n cell.setAttribute('role', 'gridcell');\n\n if (colIdx === 0) {\n // First column: indent + label (no toggle for leaves)\n const indent = Number(row.__pivotIndent) || 0;\n // Add extra indent for alignment with toggle button\n cell.style.paddingLeft = `${indent + 20}px`;\n\n const label = document.createElement('span');\n label.className = 'pivot-label';\n label.textContent = String(row.__pivotLabel ?? '');\n cell.appendChild(label);\n } else {\n // Other columns: render value\n const value = row[col.field];\n cell.textContent = value != null ? String(value) : '';\n }\n\n rowEl.appendChild(cell);\n });\n\n return true;\n}\n\n/**\n * Render the grand total row.\n */\nexport function renderPivotGrandTotalRow(row: PivotRowData, rowEl: HTMLElement, columns: ColumnConfig[]): boolean {\n rowEl.className = 'pivot-grand-total-row';\n // Use role=presentation since grand total is rendered outside the role=grid element\n rowEl.setAttribute('role', 'presentation');\n rowEl.innerHTML = '';\n\n columns.forEach((col, colIdx) => {\n const cell = document.createElement('div');\n cell.className = 'cell';\n cell.setAttribute('data-col', String(colIdx));\n // No role attribute - parent row has role=presentation so children don't need grid semantics\n\n if (colIdx === 0) {\n // First column: Grand Total label\n const label = document.createElement('span');\n label.className = 'pivot-label';\n label.textContent = 'Grand Total';\n cell.appendChild(label);\n } else {\n // Other columns: render totals\n const value = row[col.field];\n cell.textContent = value != null ? String(value) : '';\n }\n\n rowEl.appendChild(cell);\n });\n\n return true;\n}\n","/**\n * Pivot Plugin (Class-based)\n *\n * Provides pivot table functionality for tbw-grid.\n * Transforms flat data into grouped, aggregated pivot views.\n * Includes a tool panel for interactive pivot configuration.\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, ToolPanelDefinition } from '../../core/types';\nimport { buildPivot, flattenPivotRows, getAllGroupKeys, type PivotDataRow } from './pivot-engine';\nimport { createValueKey, validatePivotConfig } from './pivot-model';\nimport { renderPivotPanel, type FieldInfo, type PanelCallbacks } from './pivot-panel';\nimport { renderPivotGrandTotalRow, renderPivotGroupRow, renderPivotLeafRow, type PivotRowData } from './pivot-rows';\nimport type { AggFunc, ExpandCollapseAnimation, PivotConfig, PivotResult, PivotValueField } from './types';\n\n// Import CSS as inline string (Vite handles this)\nimport styles from './pivot.css?inline';\n\n/**\n * Pivot Table Plugin for tbw-grid\n *\n * Transforms flat data into a pivot table view with grouped rows, grouped columns,\n * and aggregated values. Includes an interactive tool panel for configuring\n * row groups, column groups, and value aggregations at runtime.\n *\n * ## Installation\n *\n * ```ts\n * import { PivotPlugin } from '@toolbox-web/grid/plugins/pivot';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `active` | `boolean` | `true` | Whether pivot is active on load |\n * | `rowGroupFields` | `string[]` | `[]` | Fields for row grouping |\n * | `columnGroupFields` | `string[]` | `[]` | Fields for column grouping |\n * | `valueFields` | `ValueField[]` | `[]` | Aggregation value fields |\n * | `showTotals` | `boolean` | `true` | Show row subtotals |\n * | `showGrandTotal` | `boolean` | `true` | Show grand total row |\n * | `showToolPanel` | `boolean` | `true` | Show interactive pivot panel |\n * | `defaultExpanded` | `boolean` | `true` | Groups expanded by default |\n * | `indentWidth` | `number` | `20` | Indent per depth level (px) |\n * | `animation` | `false \\| 'slide' \\| 'fade'` | `'slide'` | Expand/collapse animation |\n *\n * ## Aggregation Functions\n *\n * `sum`, `avg`, `count`, `min`, `max`, `first`, `last`\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `expandGroup` | `(path: string[]) => void` | Expand a specific group |\n * | `collapseGroup` | `(path: string[]) => void` | Collapse a specific group |\n * | `expandAll` | `() => void` | Expand all groups |\n * | `collapseAll` | `() => void` | Collapse all groups |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-pivot-group-bg` | `var(--tbw-color-row-alt)` | Group row background |\n * | `--tbw-pivot-grand-total-bg` | `var(--tbw-color-header-bg)` | Grand total row |\n *\n * @example Basic Pivot Table\n * ```ts\n * import '@toolbox-web/grid';\n * import { PivotPlugin } from '@toolbox-web/grid/plugins/pivot';\n *\n * grid.gridConfig = {\n * columns: [...],\n * plugins: [\n * new PivotPlugin({\n * rowGroupFields: ['region', 'product'],\n * columnGroupFields: ['quarter'],\n * valueFields: [{ field: 'sales', aggFunc: 'sum', header: 'Total' }],\n * }),\n * ],\n * };\n * ```\n *\n * @example Programmatic-Only (No Tool Panel)\n * ```ts\n * new PivotPlugin({\n * showToolPanel: false,\n * rowGroupFields: ['category'],\n * valueFields: [{ field: 'amount', aggFunc: 'sum' }],\n * })\n * ```\n *\n * @see {@link PivotConfig} for all configuration options\n * @see {@link PivotValueField} for value field structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class PivotPlugin extends BaseGridPlugin<PivotConfig> {\n /** @internal */\n readonly name = 'pivot';\n /** @internal */\n override readonly styles = styles;\n\n /** Tool panel ID for shell integration */\n static readonly PANEL_ID = 'pivot';\n\n /** @internal */\n protected override get defaultConfig(): Partial<PivotConfig> {\n return {\n active: true,\n showTotals: true,\n showGrandTotal: true,\n showToolPanel: true,\n animation: 'slide',\n };\n }\n\n // #region Internal State\n private isActive = false;\n private hasInitialized = false;\n private pivotResult: PivotResult | null = null;\n private fieldHeaderMap: Map<string, string> = new Map();\n private expandedKeys: Set<string> = new Set();\n private defaultExpanded = true;\n /** Tracks whether user has manually interacted with expand/collapse */\n private userHasToggledExpand = false;\n private originalColumns: Array<{ field: string; header: string }> = [];\n private panelContainer: HTMLElement | null = null;\n private grandTotalFooter: HTMLElement | null = null;\n private previousVisibleKeys = new Set<string>();\n private keysToAnimate = new Set<string>();\n\n /**\n * Check if the plugin has valid pivot configuration (at least value fields).\n */\n private hasValidPivotConfig(): boolean {\n return (this.config.valueFields?.length ?? 0) > 0;\n }\n\n /**\n * Get expand/collapse animation style from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide';\n }\n\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.isActive = false;\n this.hasInitialized = false;\n this.pivotResult = null;\n this.fieldHeaderMap.clear();\n this.originalColumns = [];\n this.panelContainer = null;\n this.cleanupGrandTotalFooter();\n this.previousVisibleKeys.clear();\n this.keysToAnimate.clear();\n this.userHasToggledExpand = false;\n }\n\n // #endregion\n\n // #region Shell Integration\n\n /** @internal */\n override getToolPanel(): ToolPanelDefinition | undefined {\n // Allow users to disable the tool panel for programmatic-only pivot\n // Check userConfig first (works before attach), then merged config\n const showToolPanel = this.config?.showToolPanel ?? this.userConfig?.showToolPanel ?? true;\n if (showToolPanel === false) {\n return undefined;\n }\n\n return {\n id: PivotPlugin.PANEL_ID,\n title: 'Pivot',\n icon: '⊞',\n tooltip: 'Configure pivot table',\n order: 90,\n render: (container) => this.renderPanel(container),\n };\n }\n\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processRows(rows: readonly unknown[]): PivotDataRow[] {\n // Auto-enable pivot if config.active is true and we have valid pivot fields\n if (!this.hasInitialized && this.config.active !== false && this.hasValidPivotConfig()) {\n this.hasInitialized = true;\n this.isActive = true;\n }\n\n if (!this.isActive) {\n return [...rows] as PivotDataRow[];\n }\n\n const errors = validatePivotConfig(this.config);\n if (errors.length > 0) {\n this.warn(`Config errors: ${errors.join(', ')}`);\n return [...rows] as PivotDataRow[];\n }\n\n this.buildFieldHeaderMap();\n this.defaultExpanded = this.config.defaultExpanded ?? true;\n\n // Build pivot first so we have the rows structure\n this.pivotResult = buildPivot(rows as PivotDataRow[], this.config);\n\n // Initialize expanded state with defaults if first build AND user hasn't manually toggled\n // This prevents re-expanding when user collapses all groups\n if (this.expandedKeys.size === 0 && this.defaultExpanded && !this.userHasToggledExpand) {\n this.expandAllKeys();\n }\n\n // Return flattened pivot rows respecting expanded state\n const indentWidth = this.config.indentWidth ?? 20;\n const flatRows: PivotDataRow[] = flattenPivotRows(\n this.pivotResult.rows,\n this.expandedKeys,\n this.defaultExpanded,\n ).map((pr) => ({\n __pivotRowKey: pr.rowKey,\n __pivotLabel: pr.rowLabel,\n __pivotDepth: pr.depth,\n __pivotIsGroup: pr.isGroup,\n __pivotHasChildren: Boolean(pr.children?.length),\n __pivotExpanded: this.expandedKeys.has(pr.rowKey),\n __pivotRowCount: pr.rowCount ?? 0,\n __pivotIndent: pr.depth * indentWidth,\n __pivotTotal: pr.total,\n ...pr.values,\n }));\n\n // Track which rows are newly visible (for animation)\n this.keysToAnimate.clear();\n const currentVisibleKeys = new Set<string>();\n for (const row of flatRows) {\n const key = row.__pivotRowKey as string;\n currentVisibleKeys.add(key);\n // Animate non-root rows that weren't previously visible\n if (!this.previousVisibleKeys.has(key) && (row.__pivotDepth as number) > 0) {\n this.keysToAnimate.add(key);\n }\n }\n this.previousVisibleKeys = currentVisibleKeys;\n\n // Grand total is rendered as a pinned footer row in afterRender,\n // not as part of the scrolling row data\n\n return flatRows;\n }\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (!this.isActive || !this.pivotResult) {\n return [...columns];\n }\n\n const pivotColumns: ColumnConfig[] = [];\n\n // Row label column\n const rowGroupHeaders = (this.config.rowGroupFields ?? []).map((f) => this.fieldHeaderMap.get(f) ?? f).join(' / ');\n pivotColumns.push({\n field: '__pivotLabel',\n header: rowGroupHeaders || 'Group',\n width: 200,\n });\n\n // Value columns for each column key\n for (const colKey of this.pivotResult.columnKeys) {\n for (const vf of this.config.valueFields ?? []) {\n const valueKey = createValueKey([colKey], vf.field);\n const valueHeader = vf.header || this.fieldHeaderMap.get(vf.field) || vf.field;\n pivotColumns.push({\n field: valueKey,\n header: `${colKey} - ${valueHeader} (${vf.aggFunc})`,\n width: 120,\n type: 'number',\n });\n }\n }\n\n // Totals column\n if (this.config.showTotals) {\n pivotColumns.push({\n field: '__pivotTotal',\n header: 'Total',\n width: 100,\n type: 'number',\n });\n }\n\n return pivotColumns;\n }\n\n /** @internal */\n override renderRow(row: Record<string, unknown>, rowEl: HTMLElement, rowIndex: number): boolean {\n const pivotRow = row as PivotRowData;\n\n // Handle pivot group row (has children)\n if (pivotRow.__pivotRowKey && pivotRow.__pivotHasChildren) {\n return renderPivotGroupRow(pivotRow, rowEl, {\n columns: this.gridColumns,\n rowIndex,\n onToggle: (key) => this.toggle(key),\n resolveIcon: (iconKey) => this.resolveIcon(iconKey),\n setIcon: (el, icon) => this.setIcon(el, icon),\n });\n }\n\n // Handle pivot leaf row (no children but in pivot mode)\n if (pivotRow.__pivotRowKey !== undefined && this.isActive) {\n return renderPivotLeafRow(pivotRow, rowEl, this.gridColumns, rowIndex);\n }\n\n // Clean up any leftover pivot styling from pooled row elements\n this.cleanupPivotStyling(rowEl);\n\n return false;\n }\n\n /**\n * Remove pivot-specific classes, attributes, and inline styles from a row element.\n * Called when pivot mode is disabled to clean up reused DOM elements.\n * Clears innerHTML so the grid's default renderer can rebuild the row.\n */\n private cleanupPivotStyling(rowEl: HTMLElement): void {\n // Check if this row was previously rendered by pivot (has pivot classes)\n const wasPivotRow =\n rowEl.classList.contains('pivot-group-row') ||\n rowEl.classList.contains('pivot-leaf-row') ||\n rowEl.classList.contains('pivot-grand-total-row');\n\n if (wasPivotRow) {\n // Remove pivot row classes and restore the default grid row class\n rowEl.classList.remove('pivot-group-row', 'pivot-leaf-row', 'pivot-grand-total-row');\n rowEl.classList.add('data-grid-row');\n\n // Remove pivot-specific attributes\n rowEl.removeAttribute('data-pivot-depth');\n\n // Clear the row content so the default renderer can rebuild it\n rowEl.innerHTML = '';\n }\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion on pivot group rows\n if (event.key !== ' ') return;\n if (!this.isActive) return;\n\n const focusRow = this.grid._focusRow;\n const row = this.rows[focusRow] as Record<string, unknown> | undefined;\n\n // Only handle SPACE on pivot group rows with children\n if (!row?.__pivotIsGroup || !row.__pivotHasChildren) return;\n\n event.preventDefault();\n this.toggle(row.__pivotRowKey as string);\n\n // Restore focus styling after render completes via render pipeline\n this.requestRenderWithFocus();\n return true;\n }\n\n /** @internal */\n override afterRender(): void {\n // Render grand total as a sticky pinned footer when pivot is active\n if (this.isActive && this.config.showGrandTotal && this.pivotResult) {\n this.renderGrandTotalFooter();\n } else {\n this.cleanupGrandTotalFooter();\n }\n\n // Apply animations to newly visible rows\n const style = this.animationStyle;\n if (style === false || this.keysToAnimate.size === 0) return;\n\n const body = this.gridElement?.querySelector('.rows');\n if (!body) return;\n\n const animClass = style === 'fade' ? 'tbw-pivot-fade-in' : 'tbw-pivot-slide-in';\n for (const rowEl of body.querySelectorAll('.pivot-group-row, .pivot-leaf-row')) {\n const key = (rowEl as HTMLElement).dataset.pivotKey;\n if (key && this.keysToAnimate.has(key)) {\n rowEl.classList.add(animClass);\n rowEl.addEventListener('animationend', () => rowEl.classList.remove(animClass), { once: true });\n }\n }\n this.keysToAnimate.clear();\n }\n\n /**\n * Render the grand total row as a sticky footer pinned to the bottom.\n */\n private renderGrandTotalFooter(): void {\n if (!this.pivotResult) return;\n\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Find the scroll container to append the footer\n const container =\n gridEl.querySelector('.tbw-scroll-area') ?? gridEl.querySelector('.tbw-grid-content') ?? gridEl.children[0];\n if (!container) return;\n\n // Create footer if it doesn't exist\n if (!this.grandTotalFooter) {\n this.grandTotalFooter = document.createElement('div');\n this.grandTotalFooter.className = 'pivot-grand-total-footer';\n container.appendChild(this.grandTotalFooter);\n }\n\n // Build the row data for grand total\n const grandTotalRow: PivotRowData = {\n __pivotRowKey: '__grandTotal',\n __pivotLabel: 'Grand Total',\n __pivotIsGrandTotal: true,\n __pivotTotal: this.pivotResult.grandTotal,\n ...this.pivotResult.totals,\n };\n\n // Render the grand total row into the footer\n renderPivotGrandTotalRow(grandTotalRow, this.grandTotalFooter, this.gridColumns);\n }\n\n /**\n * Remove the grand total footer element.\n */\n private cleanupGrandTotalFooter(): void {\n if (this.grandTotalFooter) {\n this.grandTotalFooter.remove();\n this.grandTotalFooter = null;\n }\n }\n\n // #endregion\n\n // #region Expand/Collapse API\n\n toggle(key: string): void {\n this.userHasToggledExpand = true;\n if (this.expandedKeys.has(key)) {\n this.expandedKeys.delete(key);\n } else {\n this.expandedKeys.add(key);\n }\n this.requestRender();\n }\n\n expand(key: string): void {\n this.userHasToggledExpand = true;\n this.expandedKeys.add(key);\n this.requestRender();\n }\n\n collapse(key: string): void {\n this.userHasToggledExpand = true;\n this.expandedKeys.delete(key);\n this.requestRender();\n }\n\n expandAll(): void {\n this.userHasToggledExpand = true;\n this.expandAllKeys();\n this.requestRender();\n }\n\n collapseAll(): void {\n this.userHasToggledExpand = true;\n this.expandedKeys.clear();\n this.requestRender();\n }\n\n /**\n * Add all group keys from the current pivot result to expandedKeys.\n */\n private expandAllKeys(): void {\n if (!this.pivotResult) return;\n const allKeys = getAllGroupKeys(this.pivotResult.rows);\n for (const key of allKeys) {\n this.expandedKeys.add(key);\n }\n }\n\n isExpanded(key: string): boolean {\n return this.expandedKeys.has(key);\n }\n\n // #endregion\n\n // #region Public API\n\n enablePivot(): void {\n if (this.originalColumns.length === 0) {\n this.captureOriginalColumns();\n }\n this.isActive = true;\n this.requestRender();\n }\n\n disablePivot(): void {\n this.isActive = false;\n this.pivotResult = null;\n this.requestRender();\n }\n\n isPivotActive(): boolean {\n return this.isActive;\n }\n\n getPivotResult(): PivotResult | null {\n return this.pivotResult;\n }\n\n setRowGroupFields(fields: string[]): void {\n this.config.rowGroupFields = fields;\n this.requestRender();\n }\n\n setColumnGroupFields(fields: string[]): void {\n this.config.columnGroupFields = fields;\n this.requestRender();\n }\n\n setValueFields(fields: PivotValueField[]): void {\n this.config.valueFields = fields;\n this.requestRender();\n }\n\n refresh(): void {\n this.pivotResult = null;\n this.requestRender();\n }\n\n // #endregion\n\n // #region Tool Panel API\n\n /**\n * Show the pivot tool panel.\n * Opens the tool panel and ensures this section is expanded.\n */\n showPanel(): void {\n this.grid.openToolPanel();\n // Ensure our section is expanded\n if (!this.grid.expandedToolPanelSections.includes(PivotPlugin.PANEL_ID)) {\n this.grid.toggleToolPanelSection(PivotPlugin.PANEL_ID);\n }\n }\n\n /**\n * Hide the tool panel.\n */\n hidePanel(): void {\n this.grid.closeToolPanel();\n }\n\n /**\n * Toggle the pivot tool panel section.\n */\n togglePanel(): void {\n // If tool panel is closed, open it first\n if (!this.grid.isToolPanelOpen) {\n this.grid.openToolPanel();\n }\n this.grid.toggleToolPanelSection(PivotPlugin.PANEL_ID);\n }\n\n /**\n * Check if the pivot panel section is currently expanded.\n */\n isPanelVisible(): boolean {\n return this.grid.isToolPanelOpen && this.grid.expandedToolPanelSections.includes(PivotPlugin.PANEL_ID);\n }\n\n // #endregion\n\n // #region Private Helpers\n\n private get gridColumns(): ColumnConfig[] {\n return (this.grid.columns ?? []) as ColumnConfig[];\n }\n\n /**\n * Refresh pivot and update tool panel if active.\n */\n private refreshIfActive(): void {\n if (this.isActive) this.refresh();\n this.refreshPanel();\n }\n\n private buildFieldHeaderMap(): void {\n const availableFields = this.getAvailableFields();\n this.fieldHeaderMap.clear();\n for (const field of availableFields) {\n this.fieldHeaderMap.set(field.field, field.header);\n }\n }\n\n private getAvailableFields(): FieldInfo[] {\n if (this.originalColumns.length > 0) {\n return this.originalColumns;\n }\n return this.captureOriginalColumns();\n }\n\n private captureOriginalColumns(): FieldInfo[] {\n try {\n const columns = this.grid.getAllColumns?.() ?? this.grid.columns ?? [];\n this.originalColumns = columns\n .filter((col: { field: string }) => !col.field.startsWith('__pivot'))\n .map((col: { field: string; header?: string }) => ({\n field: col.field,\n header: col.header ?? col.field,\n }));\n return this.originalColumns;\n } catch {\n return [];\n }\n }\n\n private renderPanel(container: HTMLElement): (() => void) | void {\n this.panelContainer = container;\n\n if (this.originalColumns.length === 0) {\n this.captureOriginalColumns();\n }\n\n const callbacks: PanelCallbacks = {\n onTogglePivot: (enabled) => {\n if (enabled) {\n this.enablePivot();\n } else {\n this.disablePivot();\n }\n this.refreshPanel();\n },\n onAddFieldToZone: (field, zone) => this.addFieldToZone(field, zone),\n onRemoveFieldFromZone: (field, zone) => this.removeFieldFromZone(field, zone),\n onAddValueField: (field, aggFunc) => this.addValueField(field, aggFunc),\n onRemoveValueField: (field) => this.removeValueField(field),\n onUpdateValueAggFunc: (field, aggFunc) => this.updateValueAggFunc(field, aggFunc),\n onOptionChange: (option, value) => {\n this.config[option] = value;\n if (this.isActive) this.refresh();\n },\n getAvailableFields: () => this.getAvailableFields(),\n };\n\n return renderPivotPanel(container, this.config, this.isActive, callbacks);\n }\n\n private refreshPanel(): void {\n if (!this.panelContainer) return;\n this.panelContainer.innerHTML = '';\n this.renderPanel(this.panelContainer);\n }\n\n private addFieldToZone(field: string, zoneType: 'rowGroups' | 'columnGroups'): void {\n if (zoneType === 'rowGroups') {\n const current = this.config.rowGroupFields ?? [];\n if (!current.includes(field)) {\n this.config.rowGroupFields = [...current, field];\n }\n } else {\n const current = this.config.columnGroupFields ?? [];\n if (!current.includes(field)) {\n this.config.columnGroupFields = [...current, field];\n }\n }\n\n this.removeFromOtherZones(field, zoneType);\n this.refreshIfActive();\n }\n\n private removeFieldFromZone(field: string, zoneType: 'rowGroups' | 'columnGroups'): void {\n if (zoneType === 'rowGroups') {\n this.config.rowGroupFields = (this.config.rowGroupFields ?? []).filter((f) => f !== field);\n } else {\n this.config.columnGroupFields = (this.config.columnGroupFields ?? []).filter((f) => f !== field);\n }\n\n this.refreshIfActive();\n }\n\n private removeFromOtherZones(field: string, targetZone: 'rowGroups' | 'columnGroups' | 'values'): void {\n if (targetZone !== 'rowGroups') {\n this.config.rowGroupFields = (this.config.rowGroupFields ?? []).filter((f) => f !== field);\n }\n if (targetZone !== 'columnGroups') {\n this.config.columnGroupFields = (this.config.columnGroupFields ?? []).filter((f) => f !== field);\n }\n if (targetZone !== 'values') {\n this.config.valueFields = (this.config.valueFields ?? []).filter((v) => v.field !== field);\n }\n }\n\n private addValueField(field: string, aggFunc: AggFunc): void {\n const current = this.config.valueFields ?? [];\n if (!current.some((v) => v.field === field)) {\n this.config.valueFields = [...current, { field, aggFunc }];\n }\n\n this.removeFromOtherZones(field, 'values');\n this.refreshIfActive();\n }\n\n private removeValueField(field: string): void {\n this.config.valueFields = (this.config.valueFields ?? []).filter((v) => v.field !== field);\n this.refreshIfActive();\n }\n\n private updateValueAggFunc(field: string, aggFunc: AggFunc): void {\n const valueFields = this.config.valueFields ?? [];\n const fieldIndex = valueFields.findIndex((v) => v.field === field);\n if (fieldIndex >= 0) {\n valueFields[fieldIndex] = { ...valueFields[fieldIndex], aggFunc };\n this.config.valueFields = [...valueFields];\n }\n if (this.isActive) this.refresh();\n }\n\n // #endregion\n}\n","/**\n * Utility for printing a grid in isolation by hiding all other page content.\n *\n * This approach keeps the grid in place (with virtualization disabled by PrintPlugin)\n * and uses CSS to hide everything else on the page during printing.\n */\n\nimport type { PrintOrientation } from './types';\n\nexport interface PrintIsolatedOptions {\n /** Page orientation hint */\n orientation?: PrintOrientation;\n}\n\n/** ID for the isolation stylesheet */\nconst ISOLATION_STYLE_ID = 'tbw-print-isolation-style';\n\n/**\n * Create a stylesheet that hides everything except the target grid.\n * Uses the grid's ID to target it specifically.\n */\nfunction createIsolationStylesheet(gridId: string, orientation: PrintOrientation): HTMLStyleElement {\n const style = document.createElement('style');\n style.id = ISOLATION_STYLE_ID;\n style.textContent = `\n /* Print isolation: hide everything except the target grid */\n @media print {\n /* Hide all body children by default */\n body > *:not(#${gridId}) {\n display: none !important;\n }\n\n /* But show the grid and ensure it's not hidden by ancestor rules */\n #${gridId} {\n display: block !important;\n position: static !important;\n visibility: visible !important;\n opacity: 1 !important;\n overflow: visible !important;\n height: auto !important;\n width: 100% !important;\n max-height: none !important;\n margin: 0 !important;\n padding: 0 !important;\n transform: none !important;\n }\n\n /* If grid is nested, we need to show its ancestors too */\n #${gridId},\n #${gridId} * {\n visibility: visible !important;\n }\n\n /* Walk up the DOM and show all ancestors of the grid */\n body *:has(> #${gridId}),\n body *:has(#${gridId}) {\n display: block !important;\n visibility: visible !important;\n opacity: 1 !important;\n overflow: visible !important;\n height: auto !important;\n position: static !important;\n transform: none !important;\n background: transparent !important;\n border: none !important;\n padding: 0 !important;\n margin: 0 !important;\n }\n\n /* Hide siblings of ancestors (everything that's not in the path to the grid) */\n body *:has(#${gridId}) > *:not(:has(#${gridId})):not(#${gridId}) {\n display: none !important;\n }\n\n /* Page settings */\n @page {\n size: ${orientation};\n margin: 1cm;\n }\n\n /* Ensure proper print styling */\n body {\n margin: 0 !important;\n padding: 0 !important;\n background: white !important;\n color-scheme: light !important;\n }\n }\n\n /* Screen: also apply isolation for print preview */\n @media screen {\n /* When this stylesheet is active, we're about to print */\n /* No screen-specific rules needed - isolation only applies to print */\n }\n `;\n return style;\n}\n\n/**\n * Print a grid in isolation by hiding all other page content.\n *\n * This function adds a temporary stylesheet that uses CSS to hide everything\n * on the page except the target grid during printing. The grid stays in place\n * with all its data (virtualization should be disabled separately).\n *\n * @param gridElement - The tbw-grid element to print (must have an ID)\n * @param options - Optional configuration\n * @returns Promise that resolves when the print dialog closes\n *\n * @example\n * ```typescript\n * import { printGridIsolated } from '@toolbox-web/grid/plugins/print';\n *\n * const grid = document.querySelector('tbw-grid');\n * await printGridIsolated(grid, { orientation: 'landscape' });\n * ```\n */\nexport async function printGridIsolated(gridElement: HTMLElement, options: PrintIsolatedOptions = {}): Promise<void> {\n const { orientation = 'landscape' } = options;\n\n const gridId = gridElement.id;\n\n // Warn if multiple elements share this ID (user-set IDs could collide)\n const elementsWithId = document.querySelectorAll(`#${CSS.escape(gridId)}`);\n if (elementsWithId.length > 1) {\n console.warn(\n `[tbw-grid:print] Multiple elements found with id=\"${gridId}\". ` +\n `Print isolation may not work correctly. Ensure each grid has a unique ID.`,\n );\n }\n\n // Remove any existing isolation stylesheet\n document.getElementById(ISOLATION_STYLE_ID)?.remove();\n\n // Add the isolation stylesheet\n const isolationStyle = createIsolationStylesheet(gridId, orientation);\n document.head.appendChild(isolationStyle);\n\n return new Promise((resolve) => {\n // Listen for afterprint event to cleanup\n const onAfterPrint = () => {\n window.removeEventListener('afterprint', onAfterPrint);\n // Remove isolation stylesheet\n document.getElementById(ISOLATION_STYLE_ID)?.remove();\n resolve();\n };\n window.addEventListener('afterprint', onAfterPrint);\n\n // Trigger print\n window.print();\n\n // Fallback timeout in case afterprint doesn't fire (some browsers)\n setTimeout(() => {\n window.removeEventListener('afterprint', onAfterPrint);\n document.getElementById(ISOLATION_STYLE_ID)?.remove();\n resolve();\n }, 5000);\n });\n}\n","/**\n * Print Plugin (Class-based)\n *\n * Provides print layout functionality for tbw-grid.\n * Temporarily disables virtualization to render all rows and uses\n * @media print CSS for print-optimized styling.\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { InternalGrid, ToolbarContentDefinition } from '../../core/types';\nimport { printGridIsolated } from './print-isolated';\nimport styles from './print.css?inline';\nimport type { PrintCompleteDetail, PrintConfig, PrintParams, PrintStartDetail } from './types';\n\n/**\n * Extended grid interface for PrintPlugin internal access.\n * Includes registerToolbarContent which is available on the grid class\n * but not exposed in the standard plugin API.\n */\ninterface PrintGridRef extends InternalGrid {\n registerToolbarContent?(content: ToolbarContentDefinition): void;\n unregisterToolbarContent?(contentId: string): void;\n}\n\n/** Default configuration */\nconst DEFAULT_CONFIG: Required<PrintConfig> = {\n button: false,\n orientation: 'landscape',\n warnThreshold: 500,\n maxRows: 0,\n includeTitle: true,\n includeTimestamp: true,\n title: '',\n isolate: false,\n};\n\n/**\n * Print Plugin for tbw-grid\n *\n * Enables printing the full grid content by temporarily disabling virtualization\n * and applying print-optimized styles. Handles large datasets gracefully with\n * configurable row limits.\n *\n * ## Installation\n *\n * ```ts\n * import { PrintPlugin } from '@toolbox-web/grid/plugins/print';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `button` | `boolean` | `false` | Show print button in toolbar |\n * | `orientation` | `'portrait' \\| 'landscape'` | `'landscape'` | Page orientation |\n * | `warnThreshold` | `number` | `500` | Show confirmation dialog when rows exceed this (0 = no warning) |\n * | `maxRows` | `number` | `0` | Hard limit on printed rows (0 = unlimited) |\n * | `includeTitle` | `boolean` | `true` | Include grid title in print |\n * | `includeTimestamp` | `boolean` | `true` | Include timestamp in footer |\n * | `title` | `string` | `''` | Custom print title |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `print` | `(params?) => Promise<void>` | Trigger print dialog |\n * | `isPrinting` | `() => boolean` | Check if print is in progress |\n *\n * ## Events\n *\n * | Event | Detail | Description |\n * |-------|--------|-------------|\n * | `print-start` | `PrintStartDetail` | Fired when print begins |\n * | `print-complete` | `PrintCompleteDetail` | Fired when print completes |\n *\n * @example Basic Print\n * ```ts\n * import { PrintPlugin } from '@toolbox-web/grid/plugins/print';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * plugins: [new PrintPlugin()],\n * };\n *\n * // Trigger print\n * const printPlugin = grid.getPlugin(PrintPlugin);\n * await printPlugin.print();\n * ```\n *\n * @example With Toolbar Button\n * ```ts\n * grid.gridConfig = {\n * plugins: [new PrintPlugin({ button: true, orientation: 'landscape' })],\n * };\n * ```\n *\n * @see {@link PrintConfig} for all configuration options\n */\nexport class PrintPlugin extends BaseGridPlugin<PrintConfig> {\n /** @internal */\n readonly name = 'print';\n\n /** @internal */\n override readonly version = '1.0.0';\n\n /** CSS styles for print mode */\n override readonly styles = styles;\n\n /** Current print state */\n #printing = false;\n\n /** Saved column visibility state */\n #savedHiddenColumns: Map<string, boolean> | null = null;\n\n /** Saved virtualization state */\n #savedVirtualization: { bypassThreshold: number } | null = null;\n\n /** Saved rows when maxRows limit is applied */\n #savedRows: unknown[] | null = null;\n\n /** Print header element */\n #printHeader: HTMLElement | null = null;\n\n /** Print footer element */\n #printFooter: HTMLElement | null = null;\n\n /** Applied scale factor (legacy, used for cleanup) */\n #appliedScale: number | null = null;\n\n /**\n * Get the grid typed as PrintGridRef for internal access.\n */\n get #internalGrid(): PrintGridRef {\n return this.grid as unknown as PrintGridRef;\n }\n\n /**\n * Check if print is currently in progress\n */\n isPrinting(): boolean {\n return this.#printing;\n }\n\n /**\n * Trigger the browser print dialog\n *\n * This method:\n * 1. Validates row count against maxRows limit\n * 2. Disables virtualization to render all rows\n * 3. Applies print-specific CSS classes\n * 4. Opens the browser print dialog (or isolated window if `isolate: true`)\n * 5. Restores normal state after printing\n *\n * @param params - Optional parameters to override config for this print\n * @param params.isolate - If true, prints in an isolated window containing only the grid\n * @returns Promise that resolves when print dialog closes\n */\n async print(params?: PrintParams): Promise<void> {\n if (this.#printing) {\n console.warn('[PrintPlugin] Print already in progress');\n return;\n }\n\n const grid = this.gridElement;\n if (!grid) {\n console.warn('[PrintPlugin] Grid not available');\n return;\n }\n\n const config = { ...DEFAULT_CONFIG, ...this.config, ...params };\n const rows = this.rows;\n const originalRowCount = rows.length;\n let rowCount = originalRowCount;\n let limitApplied = false;\n\n // Check if we should warn about large datasets\n if (config.warnThreshold > 0 && originalRowCount > config.warnThreshold) {\n const limitInfo =\n config.maxRows > 0 ? `\\n\\nNote: Output will be limited to ${config.maxRows.toLocaleString()} rows.` : '';\n const proceed = confirm(\n `This grid has ${originalRowCount.toLocaleString()} rows. ` +\n `Printing large datasets may cause performance issues or browser slowdowns.${limitInfo}\\n\\n` +\n `Click OK to continue, or Cancel to abort.`,\n );\n if (!proceed) {\n return;\n }\n }\n\n // Apply hard row limit if configured\n if (config.maxRows > 0 && originalRowCount > config.maxRows) {\n rowCount = config.maxRows;\n limitApplied = true;\n }\n\n this.#printing = true;\n\n // Track timing for duration reporting\n const startTime = performance.now();\n\n // Emit print-start event\n this.emit<PrintStartDetail>('print-start', {\n rowCount,\n limitApplied,\n originalRowCount,\n });\n\n try {\n // Save current virtualization state\n const internalGrid = this.#internalGrid;\n this.#savedVirtualization = {\n bypassThreshold: internalGrid._virtualization?.bypassThreshold ?? 24,\n };\n\n // Hide columns marked with printHidden\n this.#hidePrintColumns();\n\n // Apply row limit if configured\n if (limitApplied) {\n this.#savedRows = this.sourceRows;\n // Set limited rows on the grid\n (this.grid as unknown as { rows: unknown[] }).rows = this.sourceRows.slice(0, rowCount);\n // Wait for grid to process new rows\n await new Promise((resolve) => setTimeout(resolve, 50));\n }\n\n // Add print header if configured\n if (config.includeTitle || config.includeTimestamp) {\n this.#addPrintHeader(config);\n }\n\n // Disable virtualization to render all rows\n // This forces the grid to render all rows in the DOM\n await this.#disableVirtualization();\n\n // Wait for next frame to ensure DOM is updated\n await new Promise((resolve) => requestAnimationFrame(resolve));\n await new Promise((resolve) => requestAnimationFrame(resolve));\n\n // Add orientation class for @page rules\n grid.classList.add(`print-${config.orientation}`);\n\n // Wait for next frame to ensure DOM is updated\n await new Promise((resolve) => requestAnimationFrame(resolve));\n await new Promise((resolve) => requestAnimationFrame(resolve));\n\n // Trigger browser print dialog (isolated or inline)\n if (config.isolate) {\n await this.#printInIsolatedWindow(config);\n } else {\n await this.#triggerPrint();\n }\n\n // Emit print-complete event\n this.emit<PrintCompleteDetail>('print-complete', {\n success: true,\n rowCount,\n duration: Math.round(performance.now() - startTime),\n });\n } catch (error) {\n console.error('[PrintPlugin] Print failed:', error);\n this.emit<PrintCompleteDetail>('print-complete', {\n success: false,\n rowCount: 0,\n duration: Math.round(performance.now() - startTime),\n });\n } finally {\n // Restore normal state\n this.#cleanup();\n this.#printing = false;\n }\n }\n\n /**\n * Add print header with title and timestamp\n */\n #addPrintHeader(config: Required<PrintConfig>): void {\n const grid = this.gridElement;\n if (!grid) return;\n\n // Create print header\n this.#printHeader = document.createElement('div');\n this.#printHeader.className = 'tbw-print-header';\n\n // Title\n if (config.includeTitle) {\n const title = config.title || this.grid.effectiveConfig?.shell?.header?.title || 'Grid Data';\n const titleEl = document.createElement('div');\n titleEl.className = 'tbw-print-header-title';\n titleEl.textContent = title;\n this.#printHeader.appendChild(titleEl);\n }\n\n // Timestamp\n if (config.includeTimestamp) {\n const timestampEl = document.createElement('div');\n timestampEl.className = 'tbw-print-header-timestamp';\n timestampEl.textContent = `Printed: ${new Date().toLocaleString()}`;\n this.#printHeader.appendChild(timestampEl);\n }\n\n // Insert at the beginning of the grid\n grid.insertBefore(this.#printHeader, grid.firstChild);\n\n // Create print footer\n this.#printFooter = document.createElement('div');\n this.#printFooter.className = 'tbw-print-footer';\n this.#printFooter.textContent = `Page generated from ${window.location.hostname}`;\n grid.appendChild(this.#printFooter);\n }\n\n /**\n * Disable virtualization to render all rows\n */\n async #disableVirtualization(): Promise<void> {\n const internalGrid = this.#internalGrid;\n if (!internalGrid._virtualization) return;\n\n // Set bypass threshold higher than total row count to disable virtualization\n // This makes the grid render all rows (up to maxRows) instead of just visible ones\n const totalRows = this.rows.length;\n internalGrid._virtualization.bypassThreshold = totalRows + 100;\n\n // Force a full refresh to re-render with virtualization disabled\n internalGrid.refreshVirtualWindow(true);\n\n // Wait for render to complete\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n\n /**\n * Trigger the browser print dialog\n */\n async #triggerPrint(): Promise<void> {\n return new Promise((resolve) => {\n // Listen for afterprint event\n const onAfterPrint = () => {\n window.removeEventListener('afterprint', onAfterPrint);\n resolve();\n };\n window.addEventListener('afterprint', onAfterPrint);\n\n // Trigger print\n window.print();\n\n // Fallback timeout in case afterprint doesn't fire (some browsers)\n setTimeout(() => {\n // Guard against test environment teardown where window may be undefined\n if (typeof window !== 'undefined') {\n window.removeEventListener('afterprint', onAfterPrint);\n }\n resolve();\n }, 1000);\n });\n }\n\n /**\n * Print in isolation by hiding all other page content.\n * This excludes navigation, sidebars, etc. while keeping the grid in place.\n */\n async #printInIsolatedWindow(config: Required<PrintConfig>): Promise<void> {\n const grid = this.gridElement;\n if (!grid) return;\n\n await printGridIsolated(grid, {\n orientation: config.orientation,\n });\n }\n\n /**\n * Hide columns marked with printHidden: true\n */\n #hidePrintColumns(): void {\n const columns = this.columns;\n if (!columns) return;\n\n // Save current hidden state and hide print columns\n this.#savedHiddenColumns = new Map();\n\n for (const col of columns) {\n if (col.printHidden && col.field) {\n // Save current visibility state (true = visible, false = hidden)\n this.#savedHiddenColumns.set(col.field, !col.hidden);\n // Hide the column for printing\n this.grid.setColumnVisible(col.field, false);\n }\n }\n }\n\n /**\n * Restore columns that were hidden for printing\n */\n #restorePrintColumns(): void {\n if (!this.#savedHiddenColumns) return;\n\n for (const [field, wasVisible] of this.#savedHiddenColumns) {\n // Restore original visibility\n this.grid.setColumnVisible(field, wasVisible);\n }\n\n this.#savedHiddenColumns = null;\n }\n\n /**\n * Cleanup after printing\n */\n #cleanup(): void {\n const grid = this.gridElement;\n if (!grid) return;\n\n // Restore columns that were hidden for printing\n this.#restorePrintColumns();\n\n // Remove orientation classes (both original and possibly switched)\n grid.classList.remove('print-portrait', 'print-landscape');\n\n // Remove scaling transform if applied (legacy)\n if (this.#appliedScale !== null) {\n grid.style.transform = '';\n grid.style.transformOrigin = '';\n grid.style.width = '';\n this.#appliedScale = null;\n }\n\n // Remove print header/footer\n if (this.#printHeader) {\n this.#printHeader.remove();\n this.#printHeader = null;\n }\n if (this.#printFooter) {\n this.#printFooter.remove();\n this.#printFooter = null;\n }\n\n // Restore virtualization\n const internalGrid = this.#internalGrid;\n if (this.#savedVirtualization && internalGrid._virtualization) {\n internalGrid._virtualization.bypassThreshold = this.#savedVirtualization.bypassThreshold;\n internalGrid.refreshVirtualWindow(true);\n this.#savedVirtualization = null;\n }\n\n // Restore original rows if they were limited\n if (this.#savedRows !== null) {\n (this.grid as unknown as { rows: unknown[] }).rows = this.#savedRows;\n this.#savedRows = null;\n }\n }\n\n /**\n * Register toolbar button if configured\n * @internal\n */\n override afterRender(): void {\n // Register toolbar on first render when button is enabled\n if (this.config?.button && !this.#toolbarRegistered) {\n this.#registerToolbarButton();\n this.#toolbarRegistered = true;\n }\n }\n\n /** Track if toolbar button is registered */\n #toolbarRegistered = false;\n\n /**\n * Register print button in toolbar\n */\n #registerToolbarButton(): void {\n const grid = this.#internalGrid;\n\n // Register toolbar content\n grid.registerToolbarContent?.({\n id: 'print-button',\n order: 900, // High order to appear at the end\n render: (container: HTMLElement) => {\n const button = document.createElement('button');\n button.className = 'tbw-toolbar-btn tbw-print-btn';\n button.title = 'Print grid';\n button.type = 'button';\n\n // Use print icon\n const icon = this.resolveIcon('print') || '🖨️';\n this.setIcon(button, icon);\n\n button.addEventListener(\n 'click',\n () => {\n this.print();\n },\n { signal: this.disconnectSignal },\n );\n\n container.appendChild(button);\n },\n });\n }\n}\n","/**\n * Column Reordering Core Logic\n *\n * Pure functions for column drag and reordering operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\n\n/**\n * Check if a column can be moved based on its own metadata.\n * This checks column-level properties like lockPosition and suppressMovable.\n *\n * Note: For full movability checks including plugin constraints (e.g., pinned columns),\n * use `grid.query<boolean>('canMoveColumn', column)` which queries all plugins that\n * declare the 'canMoveColumn' query in their manifest.\n *\n * @param column - The column configuration to check\n * @returns True if the column can be moved based on its metadata\n */\nexport function canMoveColumn<TRow = unknown>(column: ColumnConfig<TRow>): boolean {\n // Check for lockPosition or suppressMovable properties in the column config\n const meta = column.meta ?? {};\n return meta.lockPosition !== true && meta.suppressMovable !== true;\n}\n\n/**\n * Move a column from one position to another in the order array.\n *\n * @param columns - Array of field names in current order\n * @param fromIndex - The current index of the column to move\n * @param toIndex - The target index to move the column to\n * @returns New array with updated order\n */\nexport function moveColumn(columns: string[], fromIndex: number, toIndex: number): string[] {\n if (fromIndex === toIndex) return columns;\n if (fromIndex < 0 || fromIndex >= columns.length) return columns;\n if (toIndex < 0 || toIndex > columns.length) return columns;\n\n const result = [...columns];\n const [removed] = result.splice(fromIndex, 1);\n result.splice(toIndex, 0, removed);\n return result;\n}\n\n/**\n * Calculate the drop index based on the current drag position.\n *\n * @param dragX - The current X position of the drag\n * @param headerRect - The bounding rect of the header container\n * @param columnWidths - Array of column widths in order\n * @returns The index where the column should be dropped\n */\nexport function getDropIndex(dragX: number, headerRect: DOMRect, columnWidths: number[]): number {\n let x = headerRect.left;\n\n for (let i = 0; i < columnWidths.length; i++) {\n const mid = x + columnWidths[i] / 2;\n if (dragX < mid) return i;\n x += columnWidths[i];\n }\n\n return columnWidths.length;\n}\n\n/**\n * Reorder columns according to a specified order.\n * Columns not in the order array are appended at the end.\n *\n * @param columns - Array of column configurations\n * @param order - Array of field names specifying the desired order\n * @returns New array of columns in the specified order\n */\nexport function reorderColumns<TRow = unknown>(columns: ColumnConfig<TRow>[], order: string[]): ColumnConfig<TRow>[] {\n const columnMap = new Map<string, ColumnConfig<TRow>>(columns.map((c) => [c.field as string, c]));\n const reordered: ColumnConfig<TRow>[] = [];\n\n // Add columns in specified order\n for (const field of order) {\n const col = columnMap.get(field);\n if (col) {\n reordered.push(col);\n columnMap.delete(field);\n }\n }\n\n // Add any remaining columns not in order\n for (const col of columnMap.values()) {\n reordered.push(col);\n }\n\n return reordered;\n}\n","/**\n * Column Reordering Plugin (Class-based)\n *\n * Provides drag-and-drop column reordering functionality for tbw-grid.\n * Supports keyboard and mouse interactions with visual feedback.\n * Uses FLIP animation technique for smooth column transitions.\n *\n * Animation respects grid-level animation.mode setting but style is plugin-configured.\n */\n\nimport { ensureCellVisible } from '../../core/internal/keyboard';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, InternalGrid } from '../../core/types';\nimport { canMoveColumn, moveColumn } from './column-drag';\nimport styles from './reorder.css?inline';\nimport type { ColumnMoveDetail, ReorderConfig } from './types';\n\n/**\n * Column Reorder Plugin for tbw-grid\n *\n * Lets users rearrange columns by dragging and dropping column headers. Supports smooth\n * FLIP animations, fade transitions, or instant reordering. Animation respects the\n * grid-level `animation.mode` setting.\n *\n * ## Installation\n *\n * ```ts\n * import { ReorderPlugin } from '@toolbox-web/grid/plugins/reorder';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `animation` | `false \\| 'flip' \\| 'fade'` | `'flip'` | Animation type for column moves |\n * | `animationDuration` | `number` | `200` | Animation duration in ms |\n *\n * ## Keyboard Shortcuts\n *\n * | Key | Action |\n * |-----|--------|\n * | `Alt + ←` | Move focused column left |\n * | `Alt + →` | Move focused column right |\n *\n * ## Events\n *\n * | Event | Detail | Cancelable | Description |\n * |-------|--------|------------|-------------|\n * | `column-move` | `{ field, fromIndex, toIndex, columnOrder }` | Yes | Fired when a column move is attempted |\n *\n * @example Basic Drag-and-Drop Reordering\n * ```ts\n * import '@toolbox-web/grid';\n * import { ReorderPlugin } from '@toolbox-web/grid/plugins/reorder';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID' },\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' },\n * ],\n * plugins: [new ReorderPlugin({ animation: 'flip', animationDuration: 200 })],\n * };\n *\n * // Persist column order\n * grid.addEventListener('column-move', (e) => {\n * localStorage.setItem('columnOrder', JSON.stringify(e.detail.columnOrder));\n * });\n * ```\n *\n * @example Prevent Moves That Break Group Boundaries\n * ```ts\n * grid.addEventListener('column-move', (e) => {\n * if (!isValidMoveWithinGroup(e.detail.field, e.detail.fromIndex, e.detail.toIndex)) {\n * e.preventDefault(); // Column snaps back to original position\n * }\n * });\n * ```\n *\n * @see {@link ReorderConfig} for all configuration options\n * @see {@link ColumnMoveDetail} for the event detail structure\n * @see {@link GroupingColumnsPlugin} for column group integration\n *\n * @internal Extends BaseGridPlugin\n */\nexport class ReorderPlugin extends BaseGridPlugin<ReorderConfig> {\n /** @internal */\n readonly name = 'reorder';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<ReorderConfig> {\n return {\n animation: 'flip', // Plugin's own default\n };\n }\n\n /**\n * Resolve animation type from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationType(): false | 'flip' | 'fade' {\n // Check if animations are globally disabled\n if (!this.isAnimationEnabled) return false;\n\n // Plugin config (with default from defaultConfig)\n if (this.config.animation !== undefined) return this.config.animation;\n\n return 'flip'; // Plugin default\n }\n\n /**\n * Get animation duration, allowing plugin config override.\n * Uses base class animationDuration for default.\n */\n protected override get animationDuration(): number {\n // Plugin config override\n if (this.config.animationDuration !== undefined) {\n return this.config.animationDuration;\n }\n return super.animationDuration;\n }\n\n // #region Internal State\n private isDragging = false;\n private draggedField: string | null = null;\n private draggedIndex: number | null = null;\n private dropIndex: number | null = null;\n\n /**\n * Check if a column can be moved, considering both column config and plugin queries.\n */\n private canMoveColumnWithPlugins(column: ColumnConfig | undefined): boolean {\n if (!column || !canMoveColumn(column)) return false;\n // Query plugins that respond to 'canMoveColumn' (e.g., PinnedColumnsPlugin)\n const responses = this.grid.query<boolean>('canMoveColumn', column);\n return !responses.includes(false);\n }\n\n /**\n * Clear all drag-related classes from header cells.\n */\n private clearDragClasses(): void {\n this.gridElement?.querySelectorAll('.header-row > .cell').forEach((h) => {\n h.classList.remove('dragging', 'drop-target', 'drop-before', 'drop-after');\n });\n }\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Listen for reorder requests from other plugins (e.g., VisibilityPlugin)\n // Uses disconnectSignal for automatic cleanup - no need for manual removeEventListener\n (grid as unknown as HTMLElement).addEventListener(\n 'column-reorder-request',\n (e: Event) => {\n const detail = (e as CustomEvent).detail;\n if (detail?.field && typeof detail.toIndex === 'number') {\n this.moveColumn(detail.field, detail.toIndex);\n }\n },\n { signal: this.disconnectSignal },\n );\n }\n\n /** @internal */\n override detach(): void {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override afterRender(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n const headers = gridEl.querySelectorAll('.header-row > .cell');\n\n headers.forEach((header) => {\n const headerEl = header as HTMLElement;\n const field = headerEl.getAttribute('data-field');\n if (!field) return;\n\n const column = this.columns.find((c) => c.field === field);\n if (!this.canMoveColumnWithPlugins(column)) {\n headerEl.draggable = false;\n return;\n }\n\n headerEl.draggable = true;\n\n // Remove existing listeners to prevent duplicates\n if (headerEl.getAttribute('data-dragstart-bound')) return;\n headerEl.setAttribute('data-dragstart-bound', 'true');\n\n headerEl.addEventListener('dragstart', (e: DragEvent) => {\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.isDragging = true;\n this.draggedField = field;\n this.draggedIndex = orderIndex;\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', field);\n }\n\n headerEl.classList.add('dragging');\n });\n\n headerEl.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.clearDragClasses();\n });\n\n headerEl.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging || this.draggedField === field) return;\n\n const rect = headerEl.getBoundingClientRect();\n const midX = rect.left + rect.width / 2;\n\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.dropIndex = e.clientX < midX ? orderIndex : orderIndex + 1;\n\n headerEl.classList.add('drop-target');\n headerEl.classList.toggle('drop-before', e.clientX < midX);\n headerEl.classList.toggle('drop-after', e.clientX >= midX);\n });\n\n headerEl.addEventListener('dragleave', () => {\n headerEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n headerEl.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n const draggedField = this.draggedField;\n const draggedIndex = this.draggedIndex;\n const dropIndex = this.dropIndex;\n\n if (!this.isDragging || draggedField === null || draggedIndex === null || dropIndex === null) {\n return;\n }\n\n const effectiveToIndex = dropIndex > draggedIndex ? dropIndex - 1 : dropIndex;\n const currentOrder = this.getColumnOrder();\n const newOrder = moveColumn(currentOrder, draggedIndex, effectiveToIndex);\n\n const detail: ColumnMoveDetail = {\n field: draggedField,\n fromIndex: draggedIndex,\n toIndex: effectiveToIndex,\n columnOrder: newOrder,\n };\n\n // Emit cancelable event first - only update if not cancelled\n const cancelled = this.emitCancelable('column-move', detail);\n if (!cancelled) {\n // Update the grid's column order (with optional view transition)\n this.updateColumnOrder(newOrder);\n }\n });\n });\n }\n\n /**\n * Handle Alt+Arrow keyboard shortcuts for column reordering.\n * @internal\n */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n if (!event.altKey || (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight')) {\n return;\n }\n\n const grid = this.grid as unknown as { _focusCol: number; _visibleColumns: ColumnConfig[] };\n const focusCol = grid._focusCol;\n const columns = grid._visibleColumns;\n\n if (focusCol < 0 || focusCol >= columns.length) return;\n\n const column = columns[focusCol];\n if (!this.canMoveColumnWithPlugins(column)) return;\n\n const currentOrder = this.getColumnOrder();\n const fromIndex = currentOrder.indexOf(column.field);\n if (fromIndex === -1) return;\n\n const toIndex = event.key === 'ArrowLeft' ? fromIndex - 1 : fromIndex + 1;\n\n // Check bounds\n if (toIndex < 0 || toIndex >= currentOrder.length) return;\n\n // Check if target position is allowed (e.g., not into pinned area)\n const targetColumn = columns.find((c) => c.field === currentOrder[toIndex]);\n if (!this.canMoveColumnWithPlugins(targetColumn)) return;\n\n this.moveColumn(column.field, toIndex);\n\n // Update focus to follow the moved column and refresh visual focus state\n grid._focusCol = toIndex;\n ensureCellVisible(this.grid as unknown as InternalGrid);\n\n event.preventDefault();\n event.stopPropagation();\n return true;\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Get the current column order from the grid.\n * @returns Array of field names in display order\n */\n getColumnOrder(): string[] {\n return this.grid.getColumnOrder();\n }\n\n /**\n * Move a column to a new position.\n * @param field - The field name of the column to move\n * @param toIndex - The target index\n */\n moveColumn(field: string, toIndex: number): void {\n const currentOrder = this.getColumnOrder();\n const fromIndex = currentOrder.indexOf(field);\n if (fromIndex === -1) return;\n\n const newOrder = moveColumn(currentOrder, fromIndex, toIndex);\n\n // Emit cancelable event first - only update if not cancelled\n const cancelled = this.emitCancelable<ColumnMoveDetail>('column-move', {\n field,\n fromIndex,\n toIndex,\n columnOrder: newOrder,\n });\n if (!cancelled) {\n // Update with view transition\n this.updateColumnOrder(newOrder);\n }\n }\n\n /**\n * Set a specific column order.\n * @param order - Array of field names in desired order\n */\n setColumnOrder(order: string[]): void {\n this.updateColumnOrder(order);\n }\n\n /**\n * Reset column order to the original configuration order.\n */\n resetColumnOrder(): void {\n const originalOrder = this.columns.map((c) => c.field);\n this.updateColumnOrder(originalOrder);\n }\n // #endregion\n\n // #region View Transition\n\n /**\n * Capture header cell positions before reorder.\n */\n private captureHeaderPositions(): Map<string, number> {\n const positions = new Map<string, number>();\n this.gridElement?.querySelectorAll('.header-row > .cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (field) positions.set(field, cell.getBoundingClientRect().left);\n });\n return positions;\n }\n\n /**\n * Apply FLIP animation for column reorder.\n * Uses CSS transitions - JS sets initial transform and toggles class.\n * @param oldPositions - Header positions captured before DOM change\n */\n private animateFLIP(oldPositions: Map<string, number>): void {\n const gridEl = this.gridElement;\n if (!gridEl || oldPositions.size === 0) return;\n\n // Compute deltas from header cells (stable reference points)\n const deltas = new Map<string, number>();\n gridEl.querySelectorAll('.header-row > .cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (!field) return;\n const oldLeft = oldPositions.get(field);\n if (oldLeft === undefined) return;\n const deltaX = oldLeft - cell.getBoundingClientRect().left;\n if (Math.abs(deltaX) > 1) deltas.set(field, deltaX);\n });\n\n if (deltas.size === 0) return;\n\n // Set initial transform (First → Last position offset)\n const cells: HTMLElement[] = [];\n gridEl.querySelectorAll('.cell[data-field]').forEach((cell) => {\n const deltaX = deltas.get(cell.getAttribute('data-field') ?? '');\n if (deltaX !== undefined) {\n const el = cell as HTMLElement;\n el.style.transform = `translateX(${deltaX}px)`;\n cells.push(el);\n }\n });\n\n if (cells.length === 0) return;\n\n // Force reflow then animate to final position via CSS transition\n void this.gridElement.offsetHeight;\n\n const duration = this.animationDuration;\n\n requestAnimationFrame(() => {\n cells.forEach((el) => {\n el.classList.add('flip-animating');\n el.style.transform = '';\n });\n\n // Cleanup after animation\n setTimeout(() => {\n cells.forEach((el) => {\n el.style.transform = '';\n el.classList.remove('flip-animating');\n });\n }, duration + 50);\n });\n }\n\n /**\n * Apply crossfade animation for moved columns.\n * Uses CSS keyframes - JS just toggles classes.\n */\n private animateFade(applyChange: () => void): void {\n const gridEl = this.gridElement;\n if (!gridEl) {\n applyChange();\n return;\n }\n\n // Capture old positions to detect which columns moved\n const oldPositions = this.captureHeaderPositions();\n\n // Apply the change first\n applyChange();\n\n // Find which columns changed position\n const movedFields = new Set<string>();\n gridEl.querySelectorAll('.header-row > .cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (!field) return;\n const oldLeft = oldPositions.get(field);\n if (oldLeft === undefined) return;\n const newLeft = cell.getBoundingClientRect().left;\n if (Math.abs(oldLeft - newLeft) > 1) {\n movedFields.add(field);\n }\n });\n\n if (movedFields.size === 0) return;\n\n // Add animation class to moved columns (headers + body cells)\n const cells: HTMLElement[] = [];\n gridEl.querySelectorAll('.cell[data-field]').forEach((cell) => {\n const field = cell.getAttribute('data-field');\n if (field && movedFields.has(field)) {\n const el = cell as HTMLElement;\n el.classList.add('fade-animating');\n cells.push(el);\n }\n });\n\n if (cells.length === 0) return;\n\n // Remove class after animation completes\n const duration = this.animationDuration;\n setTimeout(() => {\n cells.forEach((el) => el.classList.remove('fade-animating'));\n }, duration + 50);\n }\n\n /**\n * Update column order with configured animation.\n */\n private updateColumnOrder(newOrder: string[]): void {\n const animation = this.animationType;\n\n if (animation === 'flip' && this.gridElement) {\n const oldPositions = this.captureHeaderPositions();\n this.grid.setColumnOrder(newOrder);\n // Wait for the scheduler to process the virtual window update (RAF)\n // before running FLIP animation on the new cells\n requestAnimationFrame(() => {\n void this.gridElement.offsetHeight;\n this.animateFLIP(oldPositions);\n });\n } else if (animation === 'fade') {\n this.animateFade(() => this.grid.setColumnOrder(newOrder));\n } else {\n this.grid.setColumnOrder(newOrder);\n }\n\n this.grid.requestStateChange?.();\n }\n // #endregion\n}\n","/**\n * Responsive Plugin\n *\n * Transforms the grid from tabular layout to a card/list layout when the grid\n * width falls below a configurable breakpoint. This enables grids to work in\n * narrow containers (split-pane UIs, mobile viewports, dashboard widgets).\n *\n * ## Installation\n *\n * ```ts\n * import { ResponsivePlugin } from '@toolbox-web/grid/plugins/responsive';\n *\n * const config: GridConfig = {\n * plugins: [new ResponsivePlugin({ breakpoint: 500 })],\n * };\n * ```\n *\n * ## How It Works\n *\n * 1. ResizeObserver monitors the grid element's width\n * 2. When `width < breakpoint`, adds `data-responsive` attribute to grid\n * 3. CSS transforms cells from horizontal to vertical layout\n * 4. Each cell displays \"Header: Value\" using CSS `::before` pseudo-element\n *\n * @see [Responsive Demo](?path=/story/grid-plugins-responsive--default)\n */\n\nimport { ensureCellVisible } from '../../core/internal/keyboard';\nimport { evalTemplateString, sanitizeHTML } from '../../core/internal/sanitize';\nimport { BaseGridPlugin, type GridElement, type PluginManifest, type PluginQuery } from '../../core/plugin/base-plugin';\nimport type { InternalGrid } from '../../core/types';\nimport styles from './responsive.css?inline';\nimport type { BreakpointConfig, HiddenColumnConfig, ResponsiveChangeDetail, ResponsivePluginConfig } from './types';\n\n/**\n * Responsive Plugin for tbw-grid\n *\n * Adds automatic card layout mode when the grid width falls below a configurable\n * breakpoint. Perfect for responsive designs, split-pane UIs, and mobile viewports.\n *\n * @template T The row data type\n *\n * @example\n * ```ts\n * // Basic usage - switch to card layout below 500px\n * const config: GridConfig = {\n * plugins: [new ResponsivePlugin({ breakpoint: 500 })],\n * };\n * ```\n *\n * @example\n * ```ts\n * // Hide less important columns in card mode\n * const config: GridConfig = {\n * plugins: [\n * new ResponsivePlugin({\n * breakpoint: 600,\n * hiddenColumns: ['createdAt', 'updatedAt'],\n * }),\n * ],\n * };\n * ```\n *\n * @example\n * ```ts\n * // Custom card renderer for advanced layouts\n * const config: GridConfig = {\n * plugins: [\n * new ResponsivePlugin({\n * breakpoint: 400,\n * cardRenderer: (row) => {\n * const card = document.createElement('div');\n * card.className = 'custom-card';\n * card.innerHTML = `<strong>${row.name}</strong><br>${row.email}`;\n * return card;\n * },\n * }),\n * ],\n * };\n * ```\n */\nexport class ResponsivePlugin<T = unknown> extends BaseGridPlugin<ResponsivePluginConfig<T>> {\n readonly name = 'responsive';\n override readonly version = '1.0.0';\n override readonly styles = styles;\n\n /**\n * Plugin manifest declaring incompatibilities with other plugins.\n */\n static override readonly manifest: PluginManifest = {\n incompatibleWith: [\n {\n name: 'groupingRows',\n reason:\n 'Responsive card layout does not yet support row grouping. ' +\n 'The variable row heights (cards vs group headers) cause scroll calculation issues.',\n },\n ],\n queries: [\n {\n type: 'isCardMode',\n description: 'Returns whether the grid is currently in responsive card mode',\n },\n ],\n };\n\n #resizeObserver?: ResizeObserver;\n #isResponsive = false;\n #debounceTimer?: ReturnType<typeof setTimeout>;\n #warnedAboutMissingBreakpoint = false;\n #currentWidth = 0;\n /** Set of column fields to completely hide */\n #hiddenColumnSet: Set<string> = new Set();\n /** Set of column fields to show value only (no header label) */\n #valueOnlyColumnSet: Set<string> = new Set();\n /** Currently active breakpoint, or null if none */\n #activeBreakpoint: BreakpointConfig | null = null;\n /** Sorted breakpoints from largest to smallest */\n #sortedBreakpoints: BreakpointConfig[] = [];\n\n /**\n * Check if currently in responsive mode.\n * @returns `true` if the grid is in card layout mode\n */\n isResponsive(): boolean {\n return this.#isResponsive;\n }\n\n /**\n * Force responsive mode regardless of width.\n * Useful for testing or manual control.\n * @param enabled - Whether to enable responsive mode\n */\n setResponsive(enabled: boolean): void {\n if (enabled !== this.#isResponsive) {\n this.#isResponsive = enabled;\n this.#applyResponsiveState();\n this.emit('responsive-change', {\n isResponsive: enabled,\n width: this.#currentWidth,\n breakpoint: this.config.breakpoint ?? 0,\n } satisfies ResponsiveChangeDetail);\n }\n }\n\n /**\n * Update breakpoint dynamically.\n * @param width - New breakpoint width in pixels\n */\n setBreakpoint(width: number): void {\n this.config.breakpoint = width;\n this.#checkBreakpoint(this.#currentWidth);\n }\n\n /**\n * Set a custom card renderer.\n * This allows framework adapters to provide template-based renderers at runtime.\n * @param renderer - The card renderer function, or undefined to use default\n */\n setCardRenderer(renderer: ResponsivePluginConfig<T>['cardRenderer']): void {\n this.config.cardRenderer = renderer;\n // If already in responsive mode, trigger a re-render to apply the new renderer\n if (this.#isResponsive) {\n this.requestRender();\n }\n }\n\n /**\n * Get current grid width.\n * @returns Width of the grid element in pixels\n */\n getWidth(): number {\n return this.#currentWidth;\n }\n\n /**\n * Get the currently active breakpoint config (multi-breakpoint mode only).\n * @returns The active BreakpointConfig, or null if no breakpoint is active\n */\n getActiveBreakpoint(): BreakpointConfig | null {\n return this.#activeBreakpoint;\n }\n\n override attach(grid: GridElement): void {\n super.attach(grid);\n\n // Parse light DOM configuration first (may update this.config)\n this.#parseLightDomCard();\n\n // Build hidden column sets from config\n this.#buildHiddenColumnSets(this.config.hiddenColumns);\n\n // Sort breakpoints from largest to smallest for evaluation\n if (this.config.breakpoints?.length) {\n this.#sortedBreakpoints = [...this.config.breakpoints].sort((a, b) => b.maxWidth - a.maxWidth);\n }\n\n // Observe the grid element itself (not internal viewport)\n // This captures the container width including when shell panels open/close\n this.#resizeObserver = new ResizeObserver((entries) => {\n const width = entries[0]?.contentRect.width ?? 0;\n this.#currentWidth = width;\n\n // Debounce to avoid thrashing during resize drag\n clearTimeout(this.#debounceTimer);\n this.#debounceTimer = setTimeout(() => {\n this.#checkBreakpoint(width);\n }, this.config.debounceMs ?? 100);\n });\n\n this.#resizeObserver.observe(this.gridElement);\n }\n\n // #region Light DOM Parsing\n\n /**\n * Parse `<tbw-grid-responsive-card>` elements from the grid's light DOM.\n *\n * Allows declarative configuration:\n * ```html\n * <tbw-grid [rows]=\"data\">\n * <tbw-grid-responsive-card breakpoint=\"500\" card-row-height=\"80\">\n * <div class=\"custom-card\">\n * <strong>{{ row.name }}</strong>\n * <span>{{ row.email }}</span>\n * </div>\n * </tbw-grid-responsive-card>\n * </tbw-grid>\n * ```\n *\n * Attributes:\n * - `breakpoint`: number - Width threshold for responsive mode\n * - `card-row-height`: number | 'auto' - Card height (default: 'auto')\n * - `hidden-columns`: string - Comma-separated fields to hide\n * - `hide-header`: 'true' | 'false' - Hide header row (default: 'true')\n * - `debounce-ms`: number - Resize debounce delay (default: 100)\n */\n #parseLightDomCard(): void {\n const gridEl = this.grid as unknown as Element;\n if (!gridEl || typeof gridEl.querySelector !== 'function') return;\n\n const cardEl = gridEl.querySelector('tbw-grid-responsive-card');\n if (!cardEl) return;\n\n // Check if a framework adapter wants to handle this element\n // (e.g., React adapter intercepts for JSX rendering)\n const gridWithAdapter = gridEl as unknown as {\n __frameworkAdapter?: {\n parseResponsiveCardElement?: (el: Element) => ((row: T, rowIndex: number) => HTMLElement) | undefined;\n };\n };\n if (gridWithAdapter.__frameworkAdapter?.parseResponsiveCardElement) {\n const adapterRenderer = gridWithAdapter.__frameworkAdapter.parseResponsiveCardElement(cardEl);\n if (adapterRenderer) {\n this.config = { ...this.config, cardRenderer: adapterRenderer };\n // Continue to parse attributes even if adapter provides renderer\n }\n }\n\n // Parse attributes for configuration\n const breakpointAttr = cardEl.getAttribute('breakpoint');\n const cardRowHeightAttr = cardEl.getAttribute('card-row-height');\n const hiddenColumnsAttr = cardEl.getAttribute('hidden-columns');\n const hideHeaderAttr = cardEl.getAttribute('hide-header');\n const debounceMsAttr = cardEl.getAttribute('debounce-ms');\n\n const configUpdates: Partial<ResponsivePluginConfig<T>> = {};\n\n if (breakpointAttr !== null) {\n const breakpoint = parseInt(breakpointAttr, 10);\n if (!isNaN(breakpoint)) {\n configUpdates.breakpoint = breakpoint;\n }\n }\n\n if (cardRowHeightAttr !== null) {\n configUpdates.cardRowHeight = cardRowHeightAttr === 'auto' ? 'auto' : parseInt(cardRowHeightAttr, 10);\n }\n\n if (hiddenColumnsAttr !== null) {\n // Parse comma-separated field names\n configUpdates.hiddenColumns = hiddenColumnsAttr\n .split(',')\n .map((s) => s.trim())\n .filter((s) => s.length > 0);\n }\n\n if (hideHeaderAttr !== null) {\n configUpdates.hideHeader = hideHeaderAttr !== 'false';\n }\n\n if (debounceMsAttr !== null) {\n const debounceMs = parseInt(debounceMsAttr, 10);\n if (!isNaN(debounceMs)) {\n configUpdates.debounceMs = debounceMs;\n }\n }\n\n // Get template content from innerHTML (only if no renderer already set)\n const templateHTML = cardEl.innerHTML.trim();\n if (templateHTML && !this.config.cardRenderer && !gridWithAdapter.__frameworkAdapter?.parseResponsiveCardElement) {\n // Create a template-based renderer using the inner HTML\n configUpdates.cardRenderer = (row: T): HTMLElement => {\n // Evaluate template expressions like {{ row.field }}\n const evaluated = evalTemplateString(templateHTML, { value: row, row: row as Record<string, unknown> });\n // Sanitize the result to prevent XSS\n const sanitized = sanitizeHTML(evaluated);\n const container = document.createElement('div');\n container.className = 'tbw-responsive-card-content';\n container.innerHTML = sanitized;\n return container;\n };\n }\n\n // Merge updates into config (light DOM values override constructor config)\n if (Object.keys(configUpdates).length > 0) {\n this.config = { ...this.config, ...configUpdates };\n }\n }\n\n // #endregion\n\n /**\n * Build the hidden and value-only column sets from config.\n */\n #buildHiddenColumnSets(hiddenColumns?: HiddenColumnConfig[]): void {\n this.#hiddenColumnSet.clear();\n this.#valueOnlyColumnSet.clear();\n\n if (!hiddenColumns) return;\n\n for (const col of hiddenColumns) {\n if (typeof col === 'string') {\n this.#hiddenColumnSet.add(col);\n } else if (col.showValue) {\n this.#valueOnlyColumnSet.add(col.field);\n } else {\n this.#hiddenColumnSet.add(col.field);\n }\n }\n }\n\n override detach(): void {\n this.#resizeObserver?.disconnect();\n this.#resizeObserver = undefined;\n clearTimeout(this.#debounceTimer);\n this.#debounceTimer = undefined;\n\n // Clean up attribute\n if (this.gridElement) {\n this.gridElement.removeAttribute('data-responsive');\n }\n\n super.detach();\n }\n\n /**\n * Handle plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'isCardMode') {\n return this.#isResponsive;\n }\n return undefined;\n }\n\n /**\n * Apply hidden and value-only columns.\n * In legacy mode (single breakpoint), only applies when in responsive mode.\n * In multi-breakpoint mode, applies whenever there's an active breakpoint.\n */\n override afterRender(): void {\n // Measure card height for virtualization calculations\n this.#measureCardHeightFromDOM();\n\n // In single breakpoint mode, only apply when responsive\n // In multi-breakpoint mode, apply when there's an active breakpoint\n const shouldApply = this.#sortedBreakpoints.length > 0 ? this.#activeBreakpoint !== null : this.#isResponsive;\n\n if (!shouldApply) {\n return;\n }\n\n const hasHiddenColumns = this.#hiddenColumnSet.size > 0;\n const hasValueOnlyColumns = this.#valueOnlyColumnSet.size > 0;\n\n if (!hasHiddenColumns && !hasValueOnlyColumns) {\n return;\n }\n\n // Mark cells for hidden columns and value-only columns\n const cells = this.gridElement.querySelectorAll('.cell[data-field]');\n for (const cell of cells) {\n const field = cell.getAttribute('data-field');\n if (!field) continue;\n\n // Apply hidden attribute\n if (this.#hiddenColumnSet.has(field)) {\n cell.setAttribute('data-responsive-hidden', '');\n cell.removeAttribute('data-responsive-value-only');\n }\n // Apply value-only attribute (shows value without header label)\n else if (this.#valueOnlyColumnSet.has(field)) {\n cell.setAttribute('data-responsive-value-only', '');\n cell.removeAttribute('data-responsive-hidden');\n }\n // Clear any previous responsive attributes\n else {\n cell.removeAttribute('data-responsive-hidden');\n cell.removeAttribute('data-responsive-value-only');\n }\n }\n }\n\n /**\n * Check if width has crossed any breakpoint threshold.\n * Handles both single breakpoint (legacy) and multi-breakpoint modes.\n */\n #checkBreakpoint(width: number): void {\n // Multi-breakpoint mode\n if (this.#sortedBreakpoints.length > 0) {\n this.#checkMultiBreakpoint(width);\n return;\n }\n\n // Legacy single breakpoint mode\n const breakpoint = this.config.breakpoint ?? 0;\n\n // Warn once if breakpoint not configured (0 means never responsive)\n if (breakpoint === 0 && !this.#warnedAboutMissingBreakpoint) {\n this.#warnedAboutMissingBreakpoint = true;\n console.warn(\n \"[tbw-grid:ResponsivePlugin] No breakpoint configured. Responsive mode is disabled. Set a breakpoint based on your grid's column count.\",\n );\n }\n\n const shouldBeResponsive = breakpoint > 0 && width < breakpoint;\n\n if (shouldBeResponsive !== this.#isResponsive) {\n this.#isResponsive = shouldBeResponsive;\n this.#applyResponsiveState();\n this.emit('responsive-change', {\n isResponsive: shouldBeResponsive,\n width,\n breakpoint,\n } satisfies ResponsiveChangeDetail);\n this.requestRender();\n }\n }\n\n /**\n * Check breakpoints in multi-breakpoint mode.\n * Evaluates breakpoints from largest to smallest, applying the first match.\n */\n #checkMultiBreakpoint(width: number): void {\n // Find the active breakpoint (first one where width <= maxWidth)\n // Since sorted largest to smallest, we find the largest matching breakpoint\n let newActiveBreakpoint: BreakpointConfig | null = null;\n\n for (const bp of this.#sortedBreakpoints) {\n if (width <= bp.maxWidth) {\n newActiveBreakpoint = bp;\n // Continue to find the most specific (smallest) matching breakpoint\n }\n }\n\n // Check if breakpoint changed\n const breakpointChanged = newActiveBreakpoint !== this.#activeBreakpoint;\n\n if (breakpointChanged) {\n this.#activeBreakpoint = newActiveBreakpoint;\n\n // Update hidden column sets from active breakpoint\n if (newActiveBreakpoint?.hiddenColumns) {\n this.#buildHiddenColumnSets(newActiveBreakpoint.hiddenColumns);\n } else {\n // Fall back to top-level hiddenColumns config\n this.#buildHiddenColumnSets(this.config.hiddenColumns);\n }\n\n // Determine if we should be in card layout\n const shouldBeResponsive = newActiveBreakpoint?.cardLayout === true;\n\n if (shouldBeResponsive !== this.#isResponsive) {\n this.#isResponsive = shouldBeResponsive;\n this.#applyResponsiveState();\n }\n\n // Emit event for any breakpoint change\n this.emit('responsive-change', {\n isResponsive: this.#isResponsive,\n width,\n breakpoint: newActiveBreakpoint?.maxWidth ?? 0,\n } satisfies ResponsiveChangeDetail);\n\n this.requestRender();\n }\n }\n\n /** Original row height before entering responsive mode, for restoration on exit */\n #originalRowHeight?: number;\n\n /**\n * Apply the responsive state to the grid element.\n * Handles scroll reset when entering responsive mode and row height restoration on exit.\n */\n #applyResponsiveState(): void {\n this.gridElement.toggleAttribute('data-responsive', this.#isResponsive);\n\n // Apply animation attribute if enabled (default: true)\n const animate = this.config.animate !== false;\n this.gridElement.toggleAttribute('data-responsive-animate', animate);\n\n // Set custom animation duration if provided\n if (this.config.animationDuration) {\n this.gridElement.style.setProperty('--tbw-responsive-duration', `${this.config.animationDuration}ms`);\n }\n\n // Cast to internal type for virtualization access\n const internalGrid = this.grid as unknown as InternalGrid;\n\n if (this.#isResponsive) {\n // Store original row height before responsive mode changes it\n if (internalGrid._virtualization) {\n this.#originalRowHeight = internalGrid._virtualization.rowHeight;\n }\n\n // Reset horizontal scroll position when entering responsive mode\n // The CSS hides overflow but doesn't reset the scroll position\n const scrollArea = this.gridElement.querySelector('.tbw-scroll-area') as HTMLElement | null;\n if (scrollArea) {\n scrollArea.scrollLeft = 0;\n }\n } else {\n // Exiting responsive mode - clean up inline styles set by renderRow\n // The rows are reused from the pool, so we need to remove the card-specific styles\n const rows = this.gridElement.querySelectorAll('.data-grid-row');\n for (const row of rows) {\n (row as HTMLElement).style.height = '';\n row.classList.remove('responsive-card');\n }\n\n // Restore original row height\n if (this.#originalRowHeight && this.#originalRowHeight > 0 && internalGrid._virtualization) {\n internalGrid._virtualization.rowHeight = this.#originalRowHeight;\n this.#originalRowHeight = undefined;\n }\n\n // Clear cached measurements so they're remeasured fresh when re-entering responsive mode\n // Without this, stale measurements cause incorrect height calculations after scrolling\n this.#measuredCardHeight = undefined;\n this.#measuredGroupRowHeight = undefined;\n this.#lastCardRowCount = undefined;\n }\n }\n\n /**\n * Custom row rendering when cardRenderer is provided and in responsive mode.\n *\n * When a cardRenderer is configured, this hook takes over row rendering to display\n * the custom card layout instead of the default cell structure.\n *\n * @param row - The row data object\n * @param rowEl - The row DOM element to render into\n * @param rowIndex - The index of the row in the data array\n * @returns `true` if rendered (prevents default), `void` for default rendering\n */\n override renderRow(row: unknown, rowEl: HTMLElement, rowIndex: number): boolean | void {\n // Only override when in responsive mode AND cardRenderer is provided\n if (!this.#isResponsive || !this.config.cardRenderer) {\n return; // Let default rendering proceed\n }\n\n // Skip group rows from GroupingRowsPlugin - they have special structure\n // and should use their own renderer\n if ((row as { __isGroupRow?: boolean }).__isGroupRow) {\n return; // Let GroupingRowsPlugin handle group row rendering\n }\n\n // Clear existing content\n rowEl.replaceChildren();\n\n // Call user's cardRenderer to get custom content\n const cardContent = this.config.cardRenderer(row as T, rowIndex);\n\n // Reset className - clears any stale classes from previous use (e.g., 'group-row' from recycled element)\n // This follows the same pattern as GroupingRowsPlugin which sets className explicitly\n rowEl.className = 'data-grid-row responsive-card';\n\n // Handle cardRowHeight\n const cardHeight = this.config.cardRowHeight ?? 'auto';\n if (cardHeight !== 'auto') {\n rowEl.style.height = `${cardHeight}px`;\n } else {\n // Remove any virtualization-set height for auto mode\n rowEl.style.height = 'auto';\n }\n\n // Append the custom card content\n rowEl.appendChild(cardContent);\n\n return true; // We handled rendering\n }\n\n /**\n * Handle keyboard navigation in responsive mode.\n *\n * In responsive mode, the visual layout is inverted:\n * - Cells are stacked vertically within each \"card\" (row)\n * - DOWN/UP visually moves within the card (between fields)\n * - Page Down/Page Up or Ctrl+Down/Up moves between cards\n *\n * For custom cardRenderers, keyboard navigation is disabled entirely\n * since the implementor controls the card content and should handle\n * navigation via their own event handlers.\n *\n * @returns `true` if the event was handled and default behavior should be prevented\n */\n override onKeyDown(e: KeyboardEvent): boolean {\n if (!this.#isResponsive) {\n return false;\n }\n\n // If custom cardRenderer is provided, disable grid's keyboard navigation\n // The implementor is responsible for their own navigation\n if (this.config.cardRenderer) {\n const navKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];\n if (navKeys.includes(e.key)) {\n // Let the event bubble - implementor can handle it\n return false;\n }\n }\n\n // Swap arrow key behavior for CSS-only responsive mode\n // In card layout, cells are stacked vertically:\n // Card 1: Card 2:\n // ID: 1 ID: 2\n // Name: Alice Name: Bob <- ArrowRight goes here\n // Dept: Eng Dept: Mkt\n // ↓ ArrowDown goes here\n //\n // ArrowDown/Up = move within card (change column/field)\n // ArrowRight/Left = move between cards (change row)\n const maxRow = this.rows.length - 1;\n const maxCol = this.visibleColumns.length - 1;\n\n switch (e.key) {\n case 'ArrowDown':\n // Move down WITHIN card (to next field/column)\n if (this.grid._focusCol < maxCol) {\n this.grid._focusCol += 1;\n e.preventDefault();\n ensureCellVisible(this.grid as unknown as InternalGrid);\n return true;\n }\n // At bottom of card - optionally move to next card's first field\n if (this.grid._focusRow < maxRow) {\n this.grid._focusRow += 1;\n this.grid._focusCol = 0;\n e.preventDefault();\n ensureCellVisible(this.grid as unknown as InternalGrid);\n return true;\n }\n break;\n\n case 'ArrowUp':\n // Move up WITHIN card (to previous field/column)\n if (this.grid._focusCol > 0) {\n this.grid._focusCol -= 1;\n e.preventDefault();\n ensureCellVisible(this.grid as unknown as InternalGrid);\n return true;\n }\n // At top of card - optionally move to previous card's last field\n if (this.grid._focusRow > 0) {\n this.grid._focusRow -= 1;\n this.grid._focusCol = maxCol;\n e.preventDefault();\n ensureCellVisible(this.grid as unknown as InternalGrid);\n return true;\n }\n break;\n\n case 'ArrowRight':\n // Move to NEXT card (same field)\n if (this.grid._focusRow < maxRow) {\n this.grid._focusRow += 1;\n e.preventDefault();\n ensureCellVisible(this.grid as unknown as InternalGrid);\n return true;\n }\n break;\n\n case 'ArrowLeft':\n // Move to PREVIOUS card (same field)\n if (this.grid._focusRow > 0) {\n this.grid._focusRow -= 1;\n e.preventDefault();\n ensureCellVisible(this.grid as unknown as InternalGrid);\n return true;\n }\n break;\n }\n\n return false;\n }\n\n // ============================================\n // Variable Height Support for Mixed Row Types\n // ============================================\n\n /** Measured card height from DOM for virtualization calculations */\n #measuredCardHeight?: number;\n\n /** Measured group row height from DOM for virtualization calculations */\n #measuredGroupRowHeight?: number;\n\n /** Last known card row count for detecting changes (e.g., group expand/collapse) */\n #lastCardRowCount?: number;\n\n /**\n * Get the effective card height for virtualization calculations.\n * Prioritizes DOM-measured height (actual rendered size) over config,\n * since content can overflow the configured height.\n */\n #getCardHeight(): number {\n // Prefer measured height - it reflects actual rendered size including overflow\n if (this.#measuredCardHeight && this.#measuredCardHeight > 0) {\n return this.#measuredCardHeight;\n }\n // Fall back to explicit config\n const configHeight = this.config.cardRowHeight;\n if (typeof configHeight === 'number' && configHeight > 0) {\n return configHeight;\n }\n // Default fallback\n return 80;\n }\n\n /**\n * Get the effective group row height for virtualization calculations.\n * Uses DOM-measured height, falling back to original row height.\n */\n #getGroupRowHeight(): number {\n if (this.#measuredGroupRowHeight && this.#measuredGroupRowHeight > 0) {\n return this.#measuredGroupRowHeight;\n }\n // Fall back to original row height (before responsive mode)\n return this.#originalRowHeight ?? 28;\n }\n\n /**\n * Check if there are any group rows in the current dataset.\n * Used to determine if we have mixed row heights.\n */\n #hasGroupRows(): boolean {\n for (const row of this.rows) {\n if ((row as { __isGroupRow?: boolean }).__isGroupRow) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Count group rows and card rows in the current dataset.\n */\n #countRowTypes(): { groupCount: number; cardCount: number } {\n let groupCount = 0;\n let cardCount = 0;\n for (const row of this.rows) {\n if ((row as { __isGroupRow?: boolean }).__isGroupRow) {\n groupCount++;\n } else {\n cardCount++;\n }\n }\n return { groupCount, cardCount };\n }\n\n /**\n * Return total extra height contributed by mixed row heights.\n * This is called by the grid's virtualization system to adjust scrollbar height.\n *\n * The grid calculates: totalRows * baseRowHeight + pluginExtraHeight\n *\n * For mixed layouts (groups + cards), we need to report the difference between\n * actual heights and what the base calculation assumes:\n * - Extra for groups: groupCount * (groupHeight - baseHeight)\n * - Extra for cards: cardCount * (cardHeight - baseHeight)\n *\n * @deprecated Use getRowHeight() instead. This hook will be removed in v3.0.\n */\n override getExtraHeight(): number {\n // Only applies when in responsive mode with cardRenderer\n if (!this.#isResponsive || !this.config.cardRenderer) {\n return 0;\n }\n\n // Only report extra height when there are mixed row types (groups + cards)\n // If all rows are cards, we update the virtualization row height instead\n if (!this.#hasGroupRows()) {\n return 0;\n }\n\n const baseHeight = this.#originalRowHeight ?? 28;\n const groupHeight = this.#getGroupRowHeight();\n const cardHeight = this.#getCardHeight();\n\n const { groupCount, cardCount } = this.#countRowTypes();\n\n // Calculate extra height for both row types\n const groupExtra = groupCount * Math.max(0, groupHeight - baseHeight);\n const cardExtra = cardCount * Math.max(0, cardHeight - baseHeight);\n\n return groupExtra + cardExtra;\n }\n\n /**\n * Return extra height that appears before a given row index.\n * Used by virtualization to correctly calculate scroll positions.\n *\n * Like getExtraHeight, this accounts for both group and card row heights.\n *\n * @deprecated Use getRowHeight() instead. This hook will be removed in v3.0.\n */\n override getExtraHeightBefore(beforeRowIndex: number): number {\n // Only applies when in responsive mode with cardRenderer\n if (!this.#isResponsive || !this.config.cardRenderer) {\n return 0;\n }\n\n // Only report extra height when there are mixed row types\n if (!this.#hasGroupRows()) {\n return 0;\n }\n\n const baseHeight = this.#originalRowHeight ?? 28;\n const groupHeight = this.#getGroupRowHeight();\n const cardHeight = this.#getCardHeight();\n\n const groupHeightDiff = Math.max(0, groupHeight - baseHeight);\n const cardHeightDiff = Math.max(0, cardHeight - baseHeight);\n\n // Count group rows and card rows before the given index\n let groupsBefore = 0;\n let cardsBefore = 0;\n const rows = this.rows;\n const maxIndex = Math.min(beforeRowIndex, rows.length);\n\n for (let i = 0; i < maxIndex; i++) {\n if ((rows[i] as { __isGroupRow?: boolean }).__isGroupRow) {\n groupsBefore++;\n } else {\n cardsBefore++;\n }\n }\n\n return groupsBefore * groupHeightDiff + cardsBefore * cardHeightDiff;\n }\n\n /**\n * Get the height of a specific row based on its type (group row vs card row).\n * Returns undefined if not in responsive mode.\n *\n * @param row - The row data\n * @param _index - The row index (unused, but part of the interface)\n * @returns The row height in pixels, or undefined if not in responsive mode\n */\n override getRowHeight(row: unknown, _index: number): number | undefined {\n // Only applies when in responsive mode with cardRenderer\n if (!this.#isResponsive || !this.config.cardRenderer) {\n return undefined;\n }\n\n // Check if this is a group row\n if ((row as { __isGroupRow?: boolean }).__isGroupRow) {\n return this.#getGroupRowHeight();\n }\n\n // Regular card row\n return this.#getCardHeight();\n }\n\n /**\n * Count the number of card rows (non-group rows) in the current dataset.\n */\n #countCardRows(): number {\n let count = 0;\n for (const row of this.rows) {\n if (!(row as { __isGroupRow?: boolean }).__isGroupRow) {\n count++;\n }\n }\n return count;\n }\n\n /** Pending refresh scheduled via microtask */\n #pendingRefresh = false;\n\n /**\n * Measure card height from DOM after render and detect row count changes.\n * Called in afterRender to ensure scroll calculations are accurate.\n *\n * This handles two scenarios:\n * 1. Card height changes (content overflow, dynamic sizing)\n * 2. Card row count changes (group expand/collapse)\n * 3. Group row height changes\n *\n * For uniform card layouts (no groups), we update the virtualization row height\n * directly to the card height. For mixed layouts (groups + cards), we use the\n * getExtraHeight mechanism to report height differences.\n *\n * The refresh is deferred via microtask to avoid nested render cycles.\n */\n #measureCardHeightFromDOM(): void {\n if (!this.#isResponsive || !this.config.cardRenderer) {\n return;\n }\n\n let needsRefresh = false;\n const internalGrid = this.grid as unknown as InternalGrid;\n const hasGroups = this.#hasGroupRows();\n\n // Check if card row count changed (e.g., group expanded/collapsed)\n const currentCardRowCount = this.#countCardRows();\n if (currentCardRowCount !== this.#lastCardRowCount) {\n this.#lastCardRowCount = currentCardRowCount;\n needsRefresh = true;\n }\n\n // Measure actual group row height from DOM (for mixed layouts)\n if (hasGroups) {\n const groupRow = this.gridElement.querySelector('.data-grid-row.group-row') as HTMLElement | null;\n if (groupRow) {\n const height = groupRow.getBoundingClientRect().height;\n if (height > 0 && height !== this.#measuredGroupRowHeight) {\n this.#measuredGroupRowHeight = height;\n needsRefresh = true;\n }\n }\n }\n\n // Measure actual card height from DOM\n const cardRow = this.gridElement.querySelector('.data-grid-row.responsive-card') as HTMLElement | null;\n if (cardRow) {\n const height = cardRow.getBoundingClientRect().height;\n if (height > 0 && height !== this.#measuredCardHeight) {\n this.#measuredCardHeight = height;\n needsRefresh = true;\n\n // For uniform card layouts (no groups), update virtualization row height directly\n // This ensures proper row recycling and translateY calculations\n if (!hasGroups && internalGrid._virtualization) {\n internalGrid._virtualization.rowHeight = height;\n }\n }\n }\n\n // Defer virtualization refresh to avoid nested render cycles\n // This is called from afterRender, so we can't call refreshVirtualWindow synchronously\n // Use scheduler's VIRTUALIZATION phase to batch properly and avoid duplicate afterRender calls\n if (needsRefresh && !this.#pendingRefresh) {\n this.#pendingRefresh = true;\n queueMicrotask(() => {\n this.#pendingRefresh = false;\n // Only refresh if still attached and in responsive mode\n if (this.grid && this.#isResponsive) {\n // Request virtualization phase through grid's public API\n // This goes through the scheduler which batches and handles afterRender properly\n (this.grid as unknown as InternalGrid).refreshVirtualWindow?.(true, true);\n }\n });\n }\n }\n}\n","/**\n * Row Reordering Plugin\n *\n * Provides keyboard and drag-drop row reordering functionality for tbw-grid.\n * Supports Ctrl+Up/Down keyboard shortcuts and optional drag handle column.\n */\n\nimport { ensureCellVisible } from '../../core/internal/keyboard';\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, InternalGrid } from '../../core/types';\nimport styles from './row-reorder.css?inline';\nimport type { PendingMove, RowMoveDetail, RowReorderConfig } from './types';\n\n/** Field name for the drag handle column */\nexport const ROW_DRAG_HANDLE_FIELD = '__tbw_row_drag';\n\n/**\n * Row Reorder Plugin for tbw-grid\n *\n * Enables row reordering via keyboard shortcuts (Ctrl+Up/Down) and drag-drop.\n * Supports validation callbacks and debounced keyboard moves.\n *\n * ## Installation\n *\n * ```ts\n * import { RowReorderPlugin } from '@toolbox-web/grid/plugins/row-reorder';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `enableKeyboard` | `boolean` | `true` | Enable Ctrl+Up/Down shortcuts |\n * | `showDragHandle` | `boolean` | `true` | Show drag handle column |\n * | `dragHandlePosition` | `'left' \\| 'right'` | `'left'` | Drag handle column position |\n * | `dragHandleWidth` | `number` | `40` | Drag handle column width |\n * | `canMove` | `function` | - | Validation callback |\n * | `debounceMs` | `number` | `300` | Debounce time for keyboard moves |\n * | `animation` | `false \\| 'flip'` | `'flip'` | Animation for row moves |\n *\n * ## Keyboard Shortcuts\n *\n * | Key | Action |\n * |-----|--------|\n * | `Ctrl + ↑` | Move focused row up |\n * | `Ctrl + ↓` | Move focused row down |\n *\n * ## Events\n *\n * | Event | Detail | Cancelable | Description |\n * |-------|--------|------------|-------------|\n * | `row-move` | `RowMoveDetail` | Yes | Fired when a row move is attempted |\n *\n * @example Basic Row Reordering\n * ```ts\n * import '@toolbox-web/grid';\n * import { RowReorderPlugin } from '@toolbox-web/grid/plugins/row-reorder';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID' },\n * { field: 'name', header: 'Name' },\n * ],\n * plugins: [new RowReorderPlugin()],\n * };\n *\n * grid.addEventListener('row-move', (e) => {\n * console.log('Row moved from', e.detail.fromIndex, 'to', e.detail.toIndex);\n * });\n * ```\n *\n * @example With Validation\n * ```ts\n * new RowReorderPlugin({\n * canMove: (row, fromIndex, toIndex, direction) => {\n * // Prevent moving locked rows\n * return !row.locked;\n * },\n * })\n * ```\n *\n * @see {@link RowReorderConfig} for all configuration options\n * @see {@link RowMoveDetail} for the event detail structure\n */\nexport class RowReorderPlugin extends BaseGridPlugin<RowReorderConfig> {\n /** @internal */\n readonly name = 'rowReorder';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<RowReorderConfig> {\n return {\n enableKeyboard: true,\n showDragHandle: true,\n dragHandlePosition: 'left',\n dragHandleWidth: 40,\n debounceMs: 150,\n animation: 'flip',\n };\n }\n\n /**\n * Resolve animation type from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationType(): false | 'flip' {\n // Check if animations are globally disabled\n if (!this.isAnimationEnabled) return false;\n\n // Plugin config (with default from defaultConfig)\n if (this.config.animation !== undefined) return this.config.animation;\n\n return 'flip'; // Plugin default\n }\n\n // #region Internal State\n private isDragging = false;\n private draggedRowIndex: number | null = null;\n private dropRowIndex: number | null = null;\n private pendingMove: PendingMove | null = null;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n /** Column index to use when flushing pending move */\n private lastFocusCol = 0;\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.clearDebounceTimer();\n this.isDragging = false;\n this.draggedRowIndex = null;\n this.dropRowIndex = null;\n this.pendingMove = null;\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (!this.config.showDragHandle) {\n return [...columns];\n }\n\n const dragHandleColumn: ColumnConfig = {\n field: ROW_DRAG_HANDLE_FIELD,\n header: '',\n width: this.config.dragHandleWidth ?? 40,\n resizable: false,\n sortable: false,\n filterable: false,\n meta: {\n lockPosition: true,\n suppressMovable: true,\n utility: true,\n },\n viewRenderer: () => {\n const container = document.createElement('div');\n container.className = 'dg-row-drag-handle';\n container.setAttribute('aria-label', 'Drag to reorder');\n container.setAttribute('role', 'button');\n container.setAttribute('tabindex', '-1');\n // Set draggable as property (not just attribute) for proper HTML5 drag-drop\n container.draggable = true;\n\n // Use the grid's configured dragHandle icon\n this.setIcon(container, this.resolveIcon('dragHandle'));\n\n return container;\n },\n };\n\n // Position the drag handle column\n if (this.config.dragHandlePosition === 'right') {\n return [...columns, dragHandleColumn];\n }\n return [dragHandleColumn, ...columns];\n }\n\n /** @internal */\n override afterRender(): void {\n if (!this.config.showDragHandle) return;\n\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n // Set up drag start/end listeners on drag handles\n const handles = gridEl.querySelectorAll('.dg-row-drag-handle');\n handles.forEach((handle) => {\n const handleEl = handle as HTMLElement;\n if (handleEl.getAttribute('data-drag-bound')) return;\n handleEl.setAttribute('data-drag-bound', 'true');\n\n const rowEl = handleEl.closest('.data-grid-row') as HTMLElement;\n if (!rowEl) return;\n\n // Set up dragstart/dragend on the handle\n this.setupHandleDragListeners(handleEl, rowEl);\n });\n\n // Set up drop target listeners on ALL rows (not just the handle's row)\n const rows = gridEl.querySelectorAll('.data-grid-row');\n rows.forEach((row) => {\n const rowEl = row as HTMLElement;\n if (rowEl.getAttribute('data-drop-bound')) return;\n rowEl.setAttribute('data-drop-bound', 'true');\n\n this.setupRowDropListeners(rowEl);\n });\n }\n\n /**\n * Handle Ctrl+Arrow keyboard shortcuts for row reordering.\n * @internal\n */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n if (!this.config.enableKeyboard) return;\n if (!event.ctrlKey || (event.key !== 'ArrowUp' && event.key !== 'ArrowDown')) {\n return;\n }\n\n const grid = this.grid as unknown as InternalGrid;\n const focusRow = grid._focusRow;\n // Use _rows (current visual state) for keyboard moves, not sourceRows\n // This ensures rapid moves work correctly since we update _rows directly\n // Fallback to sourceRows for compatibility with tests\n const rows = grid._rows ?? this.sourceRows;\n\n if (focusRow < 0 || focusRow >= rows.length) return;\n\n const direction = event.key === 'ArrowUp' ? 'up' : 'down';\n const toIndex = direction === 'up' ? focusRow - 1 : focusRow + 1;\n\n // Check bounds\n if (toIndex < 0 || toIndex >= rows.length) return;\n\n const row = rows[focusRow];\n\n // Validate move\n if (this.config.canMove && !this.config.canMove(row, focusRow, toIndex, direction)) {\n return;\n }\n\n // Debounce keyboard moves\n this.handleKeyboardMove(row, focusRow, toIndex, direction, grid._focusCol);\n\n event.preventDefault();\n event.stopPropagation();\n return true;\n }\n\n /**\n * Flush pending keyboard moves when user clicks a cell.\n * This commits the move immediately so focus works correctly.\n * @internal\n */\n override onCellClick(): void {\n // If there's a pending keyboard move, flush it immediately\n // so the user's click focus isn't overridden\n this.flushPendingMove();\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Move a row to a new position programmatically.\n * @param fromIndex - Current index of the row\n * @param toIndex - Target index\n */\n moveRow(fromIndex: number, toIndex: number): void {\n const rows = [...this.sourceRows];\n if (fromIndex < 0 || fromIndex >= rows.length) return;\n if (toIndex < 0 || toIndex >= rows.length) return;\n if (fromIndex === toIndex) return;\n\n const direction = toIndex < fromIndex ? 'up' : 'down';\n const row = rows[fromIndex];\n\n // Validate move\n if (this.config.canMove && !this.config.canMove(row, fromIndex, toIndex, direction)) {\n return;\n }\n\n this.executeMove(row, fromIndex, toIndex, 'keyboard');\n }\n\n /**\n * Check if a row can be moved to a position.\n * @param fromIndex - Current index of the row\n * @param toIndex - Target index\n */\n canMoveRow(fromIndex: number, toIndex: number): boolean {\n const rows = this.sourceRows;\n if (fromIndex < 0 || fromIndex >= rows.length) return false;\n if (toIndex < 0 || toIndex >= rows.length) return false;\n if (fromIndex === toIndex) return false;\n\n if (!this.config.canMove) return true;\n\n const direction = toIndex < fromIndex ? 'up' : 'down';\n return this.config.canMove(rows[fromIndex], fromIndex, toIndex, direction);\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Set up drag start/end listeners on the drag handle element.\n */\n private setupHandleDragListeners(handleEl: HTMLElement, rowEl: HTMLElement): void {\n handleEl.addEventListener('dragstart', (e: DragEvent) => {\n const rowIndex = this.getRowIndex(rowEl);\n if (rowIndex < 0) return;\n\n this.isDragging = true;\n this.draggedRowIndex = rowIndex;\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', String(rowIndex));\n }\n\n rowEl.classList.add('dragging');\n });\n\n handleEl.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedRowIndex = null;\n this.dropRowIndex = null;\n this.clearDragClasses();\n });\n }\n\n /**\n * Set up drop target listeners on a row element.\n * All rows are valid drop targets during drag operations.\n */\n private setupRowDropListeners(rowEl: HTMLElement): void {\n rowEl.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging || this.draggedRowIndex === null) return;\n\n const targetIndex = this.getRowIndex(rowEl);\n if (targetIndex < 0 || targetIndex === this.draggedRowIndex) return;\n\n const rect = rowEl.getBoundingClientRect();\n const midY = rect.top + rect.height / 2;\n const isBefore = e.clientY < midY;\n\n this.dropRowIndex = isBefore ? targetIndex : targetIndex + 1;\n\n rowEl.classList.add('drop-target');\n rowEl.classList.toggle('drop-before', isBefore);\n rowEl.classList.toggle('drop-after', !isBefore);\n });\n\n rowEl.addEventListener('dragleave', () => {\n rowEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n rowEl.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n const fromIndex = this.draggedRowIndex;\n let toIndex = this.dropRowIndex;\n\n if (!this.isDragging || fromIndex === null || toIndex === null) {\n return;\n }\n\n // Adjust toIndex if dropping after the dragged row\n if (toIndex > fromIndex) {\n toIndex--;\n }\n\n if (fromIndex !== toIndex) {\n const rows = this.sourceRows;\n const row = rows[fromIndex];\n const direction = toIndex < fromIndex ? 'up' : 'down';\n\n // Validate move\n if (!this.config.canMove || this.config.canMove(row, fromIndex, toIndex, direction)) {\n this.executeMove(row, fromIndex, toIndex, 'drag');\n }\n }\n });\n }\n\n /**\n * Handle debounced keyboard moves.\n * Rows move immediately for visual feedback, but the event emission is debounced.\n */\n private handleKeyboardMove(\n row: unknown,\n fromIndex: number,\n toIndex: number,\n direction: 'up' | 'down',\n focusCol: number,\n ): void {\n // Track move for debounced event emission\n if (!this.pendingMove) {\n this.pendingMove = {\n originalIndex: fromIndex,\n currentIndex: toIndex,\n row,\n };\n } else {\n // Update the current index for rapid moves\n this.pendingMove.currentIndex = toIndex;\n }\n\n // Store focus column for flush\n this.lastFocusCol = focusCol;\n\n // Move rows immediately for visual feedback\n // Use _rows (current visual state) for rapid moves, not sourceRows\n // Fallback to sourceRows for compatibility with tests\n const grid = this.grid as unknown as InternalGrid;\n const rows = [...(grid._rows ?? this.sourceRows)];\n const [movedRow] = rows.splice(fromIndex, 1);\n rows.splice(toIndex, 0, movedRow);\n\n // Update grid rows immediately (without triggering change events)\n grid._rows = rows;\n\n // Update focus to follow the row\n grid._focusRow = toIndex;\n grid._focusCol = focusCol;\n\n // Refresh virtual window directly - this re-renders from _rows\n // without overwriting _rows from #rows (which requestRender does)\n grid.refreshVirtualWindow(true);\n\n // Ensure focus styling is applied after the row rebuild\n ensureCellVisible(grid);\n\n // Debounce the event emission only\n this.clearDebounceTimer();\n this.debounceTimer = setTimeout(() => {\n this.flushPendingMove();\n }, this.config.debounceMs ?? 300);\n }\n\n /**\n * Flush the pending move by emitting the event.\n * Called when debounce timer fires or user clicks elsewhere.\n */\n private flushPendingMove(): void {\n this.clearDebounceTimer();\n\n if (!this.pendingMove) return;\n\n const { originalIndex, currentIndex, row: movedRow } = this.pendingMove;\n this.pendingMove = null;\n\n if (originalIndex === currentIndex) return;\n\n // Emit cancelable event\n const detail: RowMoveDetail = {\n row: movedRow,\n fromIndex: originalIndex,\n toIndex: currentIndex,\n rows: [...this.sourceRows],\n source: 'keyboard',\n };\n\n const cancelled = this.emitCancelable('row-move', detail);\n if (cancelled) {\n // Revert to original position\n const rows = [...this.sourceRows];\n const [row] = rows.splice(currentIndex, 1);\n rows.splice(originalIndex, 0, row);\n\n const grid = this.grid as unknown as InternalGrid;\n grid._rows = rows;\n grid._focusRow = originalIndex;\n grid._focusCol = this.lastFocusCol;\n grid.refreshVirtualWindow(true);\n ensureCellVisible(grid);\n }\n }\n\n /**\n * Execute a row move and emit the event.\n */\n private executeMove(row: unknown, fromIndex: number, toIndex: number, source: 'keyboard' | 'drag'): void {\n const rows = [...this.sourceRows];\n const [movedRow] = rows.splice(fromIndex, 1);\n rows.splice(toIndex, 0, movedRow);\n\n const detail: RowMoveDetail = {\n row,\n fromIndex,\n toIndex,\n rows,\n source,\n };\n\n // Emit cancelable event\n const cancelled = this.emitCancelable('row-move', detail);\n if (!cancelled) {\n // Apply with animation if enabled\n if (this.animationType === 'flip' && this.gridElement) {\n const oldPositions = this.captureRowPositions();\n this.grid.rows = rows;\n // Wait for the scheduler to process the virtual window update (RAF)\n // before running FLIP animation on the new rows\n requestAnimationFrame(() => {\n void this.gridElement.offsetHeight;\n this.animateFLIP(oldPositions, fromIndex, toIndex);\n });\n } else {\n // No animation, just update rows\n this.grid.rows = rows;\n }\n }\n }\n\n /**\n * Capture row positions before reorder.\n * Maps visual row index to its top position.\n */\n private captureRowPositions(): Map<number, number> {\n const positions = new Map<number, number>();\n this.gridElement?.querySelectorAll('.data-grid-row').forEach((row) => {\n const rowIndex = this.getRowIndex(row as HTMLElement);\n if (rowIndex >= 0) {\n positions.set(rowIndex, row.getBoundingClientRect().top);\n }\n });\n return positions;\n }\n\n /**\n * Apply FLIP animation for row reorder.\n * Uses CSS transitions - JS sets initial transform and toggles class.\n * @param oldPositions - Row positions captured before DOM change\n * @param fromIndex - Original index of moved row\n * @param toIndex - New index of moved row\n */\n private animateFLIP(oldPositions: Map<number, number>, fromIndex: number, toIndex: number): void {\n const gridEl = this.gridElement;\n if (!gridEl || oldPositions.size === 0) return;\n\n // Calculate which row indices were affected and their new positions\n const minIndex = Math.min(fromIndex, toIndex);\n const maxIndex = Math.max(fromIndex, toIndex);\n\n // Build a map of new row index -> delta Y\n const rowsToAnimate: { el: HTMLElement; deltaY: number }[] = [];\n\n gridEl.querySelectorAll('.data-grid-row').forEach((row) => {\n const rowEl = row as HTMLElement;\n const newRowIndex = this.getRowIndex(rowEl);\n if (newRowIndex < 0 || newRowIndex < minIndex || newRowIndex > maxIndex) return;\n\n // Figure out what this row's old index was\n let oldIndex: number;\n if (newRowIndex === toIndex) {\n // This is the moved row\n oldIndex = fromIndex;\n } else if (fromIndex < toIndex) {\n // Row moved down: rows in between shifted up by 1\n oldIndex = newRowIndex + 1;\n } else {\n // Row moved up: rows in between shifted down by 1\n oldIndex = newRowIndex - 1;\n }\n\n const oldTop = oldPositions.get(oldIndex);\n if (oldTop === undefined) return;\n\n const newTop = rowEl.getBoundingClientRect().top;\n const deltaY = oldTop - newTop;\n\n if (Math.abs(deltaY) > 1) {\n rowsToAnimate.push({ el: rowEl, deltaY });\n }\n });\n\n if (rowsToAnimate.length === 0) return;\n\n // Set initial transform (First → Last position offset)\n rowsToAnimate.forEach(({ el, deltaY }) => {\n el.style.transform = `translateY(${deltaY}px)`;\n });\n\n // Force reflow then animate to final position via CSS transition\n void gridEl.offsetHeight;\n\n const duration = this.animationDuration;\n\n requestAnimationFrame(() => {\n rowsToAnimate.forEach(({ el }) => {\n el.classList.add('flip-animating');\n el.style.transform = '';\n });\n\n // Cleanup after animation\n setTimeout(() => {\n rowsToAnimate.forEach(({ el }) => {\n el.style.transform = '';\n el.classList.remove('flip-animating');\n });\n }, duration + 50);\n });\n }\n\n /**\n * Get the row index from a row element by checking data-row attribute on cells.\n * This is consistent with how other plugins retrieve row indices.\n */\n private getRowIndex(rowEl: HTMLElement): number {\n const cell = rowEl.querySelector('.cell[data-row]');\n return cell ? parseInt(cell.getAttribute('data-row') ?? '-1', 10) : -1;\n }\n\n /**\n * Clear all drag-related classes from rows.\n */\n private clearDragClasses(): void {\n this.gridElement?.querySelectorAll('.data-grid-row').forEach((row) => {\n row.classList.remove('dragging', 'drop-target', 'drop-before', 'drop-after');\n });\n }\n\n /**\n * Clear the debounce timer.\n */\n private clearDebounceTimer(): void {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n }\n // #endregion\n}\n","/**\n * Cell Range Selection Core Logic\n *\n * Pure functions for cell range selection operations.\n */\n\nimport type { InternalCellRange, CellRange } from './types';\n\n/**\n * Normalize a range so startRow/startCol are always <= endRow/endCol.\n * This handles cases where user drags from bottom-right to top-left.\n *\n * @param range - The range to normalize\n * @returns Normalized range with start <= end for both dimensions\n */\nexport function normalizeRange(range: InternalCellRange): InternalCellRange {\n return {\n startRow: Math.min(range.startRow, range.endRow),\n startCol: Math.min(range.startCol, range.endCol),\n endRow: Math.max(range.startRow, range.endRow),\n endCol: Math.max(range.startCol, range.endCol),\n };\n}\n\n/**\n * Convert an internal range to the public event format.\n *\n * @param range - The internal range to convert\n * @returns Public CellRange format with from/to coordinates\n */\nexport function toPublicRange(range: InternalCellRange): CellRange {\n const normalized = normalizeRange(range);\n return {\n from: { row: normalized.startRow, col: normalized.startCol },\n to: { row: normalized.endRow, col: normalized.endCol },\n };\n}\n\n/**\n * Convert multiple internal ranges to public format.\n *\n * @param ranges - Array of internal ranges\n * @returns Array of public CellRange format\n */\nexport function toPublicRanges(ranges: InternalCellRange[]): CellRange[] {\n return ranges.map(toPublicRange);\n}\n\n/**\n * Check if a cell is within a specific range.\n *\n * @param row - The row index to check\n * @param col - The column index to check\n * @param range - The range to check against\n * @returns True if the cell is within the range\n */\nexport function isCellInRange(row: number, col: number, range: InternalCellRange): boolean {\n const normalized = normalizeRange(range);\n return (\n row >= normalized.startRow && row <= normalized.endRow && col >= normalized.startCol && col <= normalized.endCol\n );\n}\n\n/**\n * Check if a cell is within any of the provided ranges.\n *\n * @param row - The row index to check\n * @param col - The column index to check\n * @param ranges - Array of ranges to check against\n * @returns True if the cell is within any range\n */\nexport function isCellInAnyRange(row: number, col: number, ranges: InternalCellRange[]): boolean {\n return ranges.some((range) => isCellInRange(row, col, range));\n}\n\n/**\n * Get all cells within a range as an array of {row, col} objects.\n *\n * @param range - The range to enumerate\n * @returns Array of all cell coordinates in the range\n */\nexport function getCellsInRange(range: InternalCellRange): Array<{ row: number; col: number }> {\n const cells: Array<{ row: number; col: number }> = [];\n const normalized = normalizeRange(range);\n\n for (let row = normalized.startRow; row <= normalized.endRow; row++) {\n for (let col = normalized.startCol; col <= normalized.endCol; col++) {\n cells.push({ row, col });\n }\n }\n\n return cells;\n}\n\n/**\n * Get all unique cells across multiple ranges.\n * Deduplicates cells that appear in overlapping ranges.\n *\n * @param ranges - Array of ranges to enumerate\n * @returns Array of unique cell coordinates\n */\nexport function getAllCellsInRanges(ranges: InternalCellRange[]): Array<{ row: number; col: number }> {\n const cellMap = new Map<string, { row: number; col: number }>();\n\n for (const range of ranges) {\n for (const cell of getCellsInRange(range)) {\n cellMap.set(`${cell.row},${cell.col}`, cell);\n }\n }\n\n return [...cellMap.values()];\n}\n\n/**\n * Merge overlapping or adjacent ranges into fewer ranges.\n * Simple implementation - returns ranges as-is for now.\n * More complex merging logic can be added later for optimization.\n *\n * @param ranges - Array of ranges to merge\n * @returns Merged array of ranges\n */\nexport function mergeRanges(ranges: InternalCellRange[]): InternalCellRange[] {\n // Simple implementation - more complex merging can be added later\n return ranges;\n}\n\n/**\n * Create a range from an anchor cell to a current cell position.\n * The range is not normalized - it preserves the direction of selection.\n *\n * @param anchor - The anchor cell (where selection started)\n * @param current - The current cell (where selection ends)\n * @returns An InternalCellRange from anchor to current\n */\nexport function createRangeFromAnchor(\n anchor: { row: number; col: number },\n current: { row: number; col: number }\n): InternalCellRange {\n return {\n startRow: anchor.row,\n startCol: anchor.col,\n endRow: current.row,\n endCol: current.col,\n };\n}\n\n/**\n * Calculate the number of cells in a range.\n *\n * @param range - The range to measure\n * @returns Total number of cells in the range\n */\nexport function getRangeCellCount(range: InternalCellRange): number {\n const normalized = normalizeRange(range);\n const rowCount = normalized.endRow - normalized.startRow + 1;\n const colCount = normalized.endCol - normalized.startCol + 1;\n return rowCount * colCount;\n}\n\n/**\n * Check if two ranges are equal (same boundaries).\n *\n * @param a - First range\n * @param b - Second range\n * @returns True if ranges have same boundaries after normalization\n */\nexport function rangesEqual(a: InternalCellRange, b: InternalCellRange): boolean {\n const normA = normalizeRange(a);\n const normB = normalizeRange(b);\n return (\n normA.startRow === normB.startRow &&\n normA.startCol === normB.startCol &&\n normA.endRow === normB.endRow &&\n normA.endCol === normB.endCol\n );\n}\n\n/**\n * Check if a range is a single cell (1x1).\n *\n * @param range - The range to check\n * @returns True if the range is exactly one cell\n */\nexport function isSingleCell(range: InternalCellRange): boolean {\n const normalized = normalizeRange(range);\n return normalized.startRow === normalized.endRow && normalized.startCol === normalized.endCol;\n}\n","/**\n * Selection Plugin (Class-based)\n *\n * Provides selection functionality for tbw-grid.\n * Supports three modes:\n * - 'cell': Single cell selection (default). No border, just focus highlight.\n * - 'row': Row selection. Clicking a cell selects the entire row.\n * - 'range': Range selection. Shift+click or drag to select rectangular cell ranges.\n */\n\nimport { clearCellFocus, getRowIndexFromCell } from '../../core/internal/utils';\nimport type { GridElement, PluginManifest, PluginQuery } from '../../core/plugin/base-plugin';\nimport { BaseGridPlugin, CellClickEvent, CellMouseEvent } from '../../core/plugin/base-plugin';\nimport { isExpanderColumn, isUtilityColumn } from '../../core/plugin/expander-column';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n createRangeFromAnchor,\n getAllCellsInRanges,\n isCellInAnyRange,\n normalizeRange,\n rangesEqual,\n toPublicRanges,\n} from './range-selection';\nimport styles from './selection.css?inline';\nimport type {\n CellRange,\n InternalCellRange,\n SelectionChangeDetail,\n SelectionConfig,\n SelectionMode,\n SelectionResult,\n} from './types';\n\n/** Special field name for the selection checkbox column */\nconst CHECKBOX_COLUMN_FIELD = '__tbw_checkbox';\n\n/**\n * Build the selection change event detail for the current state.\n */\nfunction buildSelectionEvent(\n mode: SelectionMode,\n state: {\n selectedCell: { row: number; col: number } | null;\n selected: Set<number>;\n ranges: InternalCellRange[];\n },\n colCount: number,\n): SelectionChangeDetail {\n if (mode === 'cell' && state.selectedCell) {\n return {\n mode,\n ranges: [\n {\n from: { row: state.selectedCell.row, col: state.selectedCell.col },\n to: { row: state.selectedCell.row, col: state.selectedCell.col },\n },\n ],\n };\n }\n\n if (mode === 'row' && state.selected.size > 0) {\n // Sort rows and merge contiguous indices into minimal ranges\n const sorted = [...state.selected].sort((a, b) => a - b);\n const ranges: CellRange[] = [];\n let start = sorted[0];\n let end = start;\n for (let i = 1; i < sorted.length; i++) {\n if (sorted[i] === end + 1) {\n end = sorted[i];\n } else {\n ranges.push({ from: { row: start, col: 0 }, to: { row: end, col: colCount - 1 } });\n start = sorted[i];\n end = start;\n }\n }\n ranges.push({ from: { row: start, col: 0 }, to: { row: end, col: colCount - 1 } });\n return { mode, ranges };\n }\n\n if (mode === 'range' && state.ranges.length > 0) {\n return { mode, ranges: toPublicRanges(state.ranges) };\n }\n\n return { mode, ranges: [] };\n}\n\n/**\n * Selection Plugin for tbw-grid\n *\n * Adds cell, row, and range selection capabilities to the grid with full keyboard support.\n * Whether you need simple cell highlighting or complex multi-range selections, this plugin has you covered.\n *\n * ## Installation\n *\n * ```ts\n * import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';\n * ```\n *\n * ## Selection Modes\n *\n * Configure the plugin with one of three modes via {@link SelectionConfig}:\n *\n * - **`'cell'`** - Single cell selection (default). Click cells to select individually.\n * - **`'row'`** - Full row selection. Click anywhere in a row to select the entire row.\n * - **`'range'`** - Rectangular selection. Click and drag or Shift+Click to select ranges.\n *\n * ## Keyboard Shortcuts\n *\n * | Shortcut | Action |\n * |----------|--------|\n * | `Arrow Keys` | Move selection |\n * | `Shift + Arrow` | Extend selection (range mode) |\n * | `Ctrl/Cmd + Click` | Toggle selection (multi-select) |\n * | `Shift + Click` | Extend to clicked cell/row |\n * | `Ctrl/Cmd + A` | Select all (range mode) |\n * | `Escape` | Clear selection |\n *\n * ## CSS Custom Properties\n *\n * | Property | Description |\n * |----------|-------------|\n * | `--tbw-focus-background` | Focused row background |\n * | `--tbw-range-selection-bg` | Range selection fill |\n * | `--tbw-range-border-color` | Range selection border |\n *\n * @example Basic row selection\n * ```ts\n * grid.gridConfig = {\n * columns: [...],\n * plugins: [new SelectionPlugin({ mode: 'row' })],\n * };\n * ```\n *\n * @example Range selection with event handling\n * ```ts\n * grid.gridConfig = {\n * plugins: [new SelectionPlugin({ mode: 'range' })],\n * };\n *\n * grid.addEventListener('selection-change', (e) => {\n * const { mode, ranges } = e.detail;\n * console.log(`Selected ${ranges.length} ranges in ${mode} mode`);\n * });\n * ```\n *\n * @example Programmatic selection control\n * ```ts\n * const plugin = grid.getPlugin(SelectionPlugin);\n *\n * // Get current selection\n * const selection = plugin.getSelection();\n * console.log(selection.ranges);\n *\n * // Set selection programmatically\n * plugin.setRanges([{ from: { row: 0, col: 0 }, to: { row: 5, col: 3 } }]);\n *\n * // Clear all selection\n * plugin.clearSelection();\n * ```\n *\n * @see {@link SelectionMode} for detailed mode descriptions\n * @see {@link SelectionConfig} for configuration options\n * @see {@link SelectionResult} for the selection result structure\n * @see [Live Demos](?path=/docs/grid-plugins-selection--docs) for interactive examples\n */\nexport class SelectionPlugin extends BaseGridPlugin<SelectionConfig> {\n /**\n * Plugin manifest - declares queries and configuration validation rules.\n * @internal\n */\n static override readonly manifest: PluginManifest<SelectionConfig> = {\n queries: [\n { type: 'getSelection', description: 'Get the current selection state' },\n { type: 'selectRows', description: 'Select specific rows by index (row mode only)' },\n { type: 'getSelectedRowIndices', description: 'Get sorted array of selected row indices' },\n ],\n configRules: [\n {\n id: 'selection/range-dblclick',\n severity: 'warn',\n message:\n `\"triggerOn: 'dblclick'\" has no effect when mode is \"range\".\\n` +\n ` → Range selection uses drag interaction (mousedown → mousemove), not click events.\\n` +\n ` → The \"triggerOn\" option only affects \"cell\" and \"row\" selection modes.`,\n check: (config) => config.mode === 'range' && config.triggerOn === 'dblclick',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'selection';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<SelectionConfig> {\n return {\n mode: 'cell',\n triggerOn: 'click',\n enabled: true,\n };\n }\n\n // #region Internal State\n /** Row selection state (row mode) */\n private selected = new Set<number>();\n private lastSelected: number | null = null;\n private anchor: number | null = null;\n\n /** Range selection state (range mode) */\n private ranges: InternalCellRange[] = [];\n private activeRange: InternalCellRange | null = null;\n private cellAnchor: { row: number; col: number } | null = null;\n private isDragging = false;\n\n /** Pending keyboard navigation update (processed in afterRender) */\n private pendingKeyboardUpdate: { shiftKey: boolean } | null = null;\n\n /** Cell selection state (cell mode) */\n private selectedCell: { row: number; col: number } | null = null;\n\n /** Last synced focus row — used to detect when grid focus moves so selection follows */\n private lastSyncedFocusRow = -1;\n /** Last synced focus col (cell mode) */\n private lastSyncedFocusCol = -1;\n\n /** True when selection was explicitly set (click/keyboard) — prevents #syncSelectionToFocus from overwriting */\n private explicitSelection = false;\n\n // #endregion\n\n // #region Private Helpers - Selection Enabled Check\n\n /**\n * Check if selection is enabled at the grid level.\n * Grid-wide `selectable: false` or plugin's `enabled: false` disables all selection.\n */\n private isSelectionEnabled(): boolean {\n // Check plugin config first\n if (this.config.enabled === false) return false;\n // Check grid-level config\n return this.grid.effectiveConfig?.selectable !== false;\n }\n\n // #endregion\n\n // #region Private Helpers - Selectability\n\n /**\n * Check if a row/cell is selectable.\n * Returns true if selectable, false if not.\n */\n private checkSelectable(rowIndex: number, colIndex?: number): boolean {\n const { isSelectable } = this.config;\n if (!isSelectable) return true; // No callback = all selectable\n\n const row = this.rows[rowIndex];\n if (!row) return false;\n\n const column = colIndex !== undefined ? this.columns[colIndex] : undefined;\n return isSelectable(row, rowIndex, column, colIndex);\n }\n\n /**\n * Check if an entire row is selectable (for row mode).\n */\n private isRowSelectable(rowIndex: number): boolean {\n return this.checkSelectable(rowIndex);\n }\n\n /**\n * Check if a cell is selectable (for cell/range modes).\n */\n private isCellSelectable(rowIndex: number, colIndex: number): boolean {\n return this.checkSelectable(rowIndex, colIndex);\n }\n\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: GridElement): void {\n super.attach(grid);\n\n // Subscribe to events that invalidate selection\n // When rows change due to filtering/grouping/tree operations, selection indices become invalid\n this.on('filter-applied', () => this.clearSelectionSilent());\n this.on('grouping-state-change', () => this.clearSelectionSilent());\n this.on('tree-state-change', () => this.clearSelectionSilent());\n }\n\n /**\n * Handle queries from other plugins.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'getSelection') {\n return this.getSelection();\n }\n if (query.type === 'getSelectedRowIndices') {\n return this.getSelectedRowIndices();\n }\n if (query.type === 'selectRows') {\n this.selectRows(query.context as number[]);\n return true;\n }\n return undefined;\n }\n\n /** @internal */\n override detach(): void {\n this.selected.clear();\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n this.isDragging = false;\n this.selectedCell = null;\n this.pendingKeyboardUpdate = null;\n this.lastSyncedFocusRow = -1;\n this.lastSyncedFocusCol = -1;\n }\n\n /**\n * Clear selection without emitting an event.\n * Used when selection is invalidated by external changes (filtering, grouping, etc.)\n */\n private clearSelectionSilent(): void {\n this.selected.clear();\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n this.selectedCell = null;\n this.lastSelected = null;\n this.anchor = null;\n this.lastSyncedFocusRow = -1;\n this.lastSyncedFocusCol = -1;\n this.requestAfterRender();\n }\n\n // #endregion\n\n // #region Event Handlers\n\n /** @internal */\n override onCellClick(event: CellClickEvent): boolean {\n // Skip all selection if disabled at grid level or plugin level\n if (!this.isSelectionEnabled()) return false;\n\n const { rowIndex, colIndex, originalEvent } = event;\n const { mode, triggerOn = 'click' } = this.config;\n\n // Skip if event type doesn't match configured trigger\n // This allows dblclick mode to only select on double-click\n if (originalEvent.type !== triggerOn) {\n return false;\n }\n\n // Check if this is a utility column (expander columns, etc.)\n const column = this.columns[colIndex];\n const isUtility = column && isUtilityColumn(column);\n\n // CELL MODE: Single cell selection - skip utility columns and non-selectable cells\n if (mode === 'cell') {\n if (isUtility) {\n return false; // Allow event to propagate, but don't select utility cells\n }\n if (!this.isCellSelectable(rowIndex, colIndex)) {\n return false; // Cell is not selectable\n }\n // Only emit if selection actually changed\n const currentCell = this.selectedCell;\n if (currentCell && currentCell.row === rowIndex && currentCell.col === colIndex) {\n return false; // Same cell already selected\n }\n this.selectedCell = { row: rowIndex, col: colIndex };\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return false;\n }\n\n // ROW MODE: Multi-select with Shift/Ctrl, checkbox toggle, or single select\n if (mode === 'row') {\n if (!this.isRowSelectable(rowIndex)) {\n return false; // Row is not selectable\n }\n\n const shiftKey = originalEvent.shiftKey;\n const ctrlKey = originalEvent.ctrlKey || originalEvent.metaKey;\n const isCheckbox = column?.meta?.checkboxColumn === true;\n\n if (shiftKey && this.anchor !== null) {\n // Shift+Click: Range select from anchor to clicked row\n const start = Math.min(this.anchor, rowIndex);\n const end = Math.max(this.anchor, rowIndex);\n if (!ctrlKey) {\n this.selected.clear();\n }\n for (let i = start; i <= end; i++) {\n if (this.isRowSelectable(i)) {\n this.selected.add(i);\n }\n }\n } else if (ctrlKey || isCheckbox) {\n // Ctrl+Click or checkbox click: Toggle individual row\n if (this.selected.has(rowIndex)) {\n this.selected.delete(rowIndex);\n } else {\n this.selected.add(rowIndex);\n }\n this.anchor = rowIndex;\n } else {\n // Plain click: Clear all, select only clicked row\n if (this.selected.size === 1 && this.selected.has(rowIndex)) {\n return false; // Same row already selected\n }\n this.selected.clear();\n this.selected.add(rowIndex);\n this.anchor = rowIndex;\n }\n\n this.lastSelected = rowIndex;\n this.explicitSelection = true;\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return false;\n }\n\n // RANGE MODE: Shift+click extends selection, click starts new\n if (mode === 'range') {\n // Skip utility columns in range mode - don't start selection from them\n if (isUtility) {\n return false;\n }\n\n // Skip non-selectable cells in range mode\n if (!this.isCellSelectable(rowIndex, colIndex)) {\n return false;\n }\n\n const shiftKey = originalEvent.shiftKey;\n const ctrlKey = originalEvent.ctrlKey || originalEvent.metaKey;\n\n if (shiftKey && this.cellAnchor) {\n // Extend selection from anchor\n const newRange = createRangeFromAnchor(this.cellAnchor, { row: rowIndex, col: colIndex });\n\n // Check if range actually changed\n const currentRange = this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] : null;\n if (currentRange && rangesEqual(currentRange, newRange)) {\n return false; // Same range already selected\n }\n\n if (ctrlKey) {\n if (this.ranges.length > 0) {\n this.ranges[this.ranges.length - 1] = newRange;\n } else {\n this.ranges.push(newRange);\n }\n } else {\n this.ranges = [newRange];\n }\n this.activeRange = newRange;\n } else if (ctrlKey) {\n const newRange: InternalCellRange = {\n startRow: rowIndex,\n startCol: colIndex,\n endRow: rowIndex,\n endCol: colIndex,\n };\n this.ranges.push(newRange);\n this.activeRange = newRange;\n this.cellAnchor = { row: rowIndex, col: colIndex };\n } else {\n // Plain click - check if same single-cell range already selected\n const newRange: InternalCellRange = {\n startRow: rowIndex,\n startCol: colIndex,\n endRow: rowIndex,\n endCol: colIndex,\n };\n\n // Only emit if selection actually changed\n if (this.ranges.length === 1 && rangesEqual(this.ranges[0], newRange)) {\n return false; // Same cell already selected\n }\n\n this.ranges = [newRange];\n this.activeRange = newRange;\n this.cellAnchor = { row: rowIndex, col: colIndex };\n }\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n\n this.requestAfterRender();\n return false;\n }\n\n return false;\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean {\n // Skip all selection if disabled at grid level or plugin level\n if (!this.isSelectionEnabled()) return false;\n\n const { mode } = this.config;\n const navKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Tab', 'Home', 'End', 'PageUp', 'PageDown'];\n const isNavKey = navKeys.includes(event.key);\n\n // Escape clears selection in all modes\n // But if editing is active, let the EditingPlugin handle Escape first\n if (event.key === 'Escape') {\n const isEditing = this.grid.query<boolean>('isEditing');\n if (isEditing.some(Boolean)) {\n return false; // Defer to EditingPlugin to cancel the active edit\n }\n\n if (mode === 'cell') {\n this.selectedCell = null;\n } else if (mode === 'row') {\n this.selected.clear();\n this.anchor = null;\n } else if (mode === 'range') {\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n }\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n\n // CELL MODE: Selection follows focus (but respects selectability)\n if (mode === 'cell' && isNavKey) {\n // Use queueMicrotask so grid's handler runs first and updates focusRow/focusCol\n queueMicrotask(() => {\n const focusRow = this.grid._focusRow;\n const focusCol = this.grid._focusCol;\n // Only select if the cell is selectable\n if (this.isCellSelectable(focusRow, focusCol)) {\n this.selectedCell = { row: focusRow, col: focusCol };\n } else {\n // Clear selection when navigating to non-selectable cell\n this.selectedCell = null;\n }\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n });\n return false; // Let grid handle navigation\n }\n\n // ROW MODE: Arrow keys move selection, Shift+Arrow extends, Ctrl+A selects all\n if (mode === 'row') {\n if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {\n const shiftKey = event.shiftKey;\n\n // Set anchor before grid moves focus\n if (shiftKey && this.anchor === null) {\n this.anchor = this.grid._focusRow;\n }\n\n // Let grid move focus first, then sync row selection\n queueMicrotask(() => {\n const focusRow = this.grid._focusRow;\n\n if (shiftKey && this.anchor !== null) {\n // Shift+Arrow: Extend selection from anchor\n this.selected.clear();\n const start = Math.min(this.anchor, focusRow);\n const end = Math.max(this.anchor, focusRow);\n for (let i = start; i <= end; i++) {\n if (this.isRowSelectable(i)) {\n this.selected.add(i);\n }\n }\n } else {\n // Plain arrow: Single select\n if (this.isRowSelectable(focusRow)) {\n this.selected.clear();\n this.selected.add(focusRow);\n this.anchor = focusRow;\n } else {\n this.selected.clear();\n }\n }\n\n this.lastSelected = focusRow;\n this.explicitSelection = true;\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n });\n return false; // Let grid handle navigation\n }\n\n // Ctrl+A: Select all rows (skip when editing)\n if (event.key === 'a' && (event.ctrlKey || event.metaKey)) {\n const isEditing = this.grid.query<boolean>('isEditing');\n if (isEditing.some(Boolean)) return false;\n event.preventDefault();\n event.stopPropagation();\n this.selectAll();\n return true;\n }\n }\n\n // RANGE MODE: Shift+Arrow extends, plain Arrow resets\n // Tab key always navigates without extending (even with Shift)\n if (mode === 'range' && isNavKey) {\n // Tab should not extend selection - it just navigates to the next/previous cell\n const isTabKey = event.key === 'Tab';\n const shouldExtend = event.shiftKey && !isTabKey;\n\n // Capture anchor BEFORE grid moves focus (synchronous)\n // This ensures the anchor is the starting point, not the destination\n if (shouldExtend && !this.cellAnchor) {\n this.cellAnchor = { row: this.grid._focusRow, col: this.grid._focusCol };\n }\n\n // Mark pending update - will be processed in afterRender when grid updates focus\n this.pendingKeyboardUpdate = { shiftKey: shouldExtend };\n\n // Schedule afterRender to run after grid's keyboard handler completes\n // Grid's refreshVirtualWindow(false) skips afterRender for performance,\n // so we explicitly request it to process pendingKeyboardUpdate\n queueMicrotask(() => this.requestAfterRender());\n\n return false; // Let grid handle navigation\n }\n\n // Ctrl+A selects all in range mode (skip when editing)\n if (mode === 'range' && event.key === 'a' && (event.ctrlKey || event.metaKey)) {\n const isEditing = this.grid.query<boolean>('isEditing');\n if (isEditing.some(Boolean)) return false;\n event.preventDefault();\n event.stopPropagation();\n this.selectAll();\n return true;\n }\n\n return false;\n }\n\n /** @internal */\n override onCellMouseDown(event: CellMouseEvent): boolean | void {\n // Skip all selection if disabled at grid level or plugin level\n if (!this.isSelectionEnabled()) return;\n\n if (this.config.mode !== 'range') return;\n if (event.rowIndex === undefined || event.colIndex === undefined) return;\n if (event.rowIndex < 0) return; // Header\n\n // Skip utility columns (expander columns, etc.)\n const column = this.columns[event.colIndex];\n if (column && isUtilityColumn(column)) {\n return; // Don't start selection on utility columns\n }\n\n // Skip non-selectable cells - don't start drag from them\n if (!this.isCellSelectable(event.rowIndex, event.colIndex)) {\n return;\n }\n\n // Let onCellClick handle shift+click for range extension\n if (event.originalEvent.shiftKey && this.cellAnchor) {\n return;\n }\n\n // Start drag selection\n this.isDragging = true;\n const rowIndex = event.rowIndex;\n const colIndex = event.colIndex;\n\n const ctrlKey = event.originalEvent.ctrlKey || event.originalEvent.metaKey;\n\n const newRange: InternalCellRange = {\n startRow: rowIndex,\n startCol: colIndex,\n endRow: rowIndex,\n endCol: colIndex,\n };\n\n // Check if selection is actually changing (for non-Ctrl clicks)\n if (!ctrlKey && this.ranges.length === 1 && rangesEqual(this.ranges[0], newRange)) {\n // Same cell already selected, just update anchor for potential drag\n this.cellAnchor = { row: rowIndex, col: colIndex };\n return true;\n }\n\n this.cellAnchor = { row: rowIndex, col: colIndex };\n\n if (!ctrlKey) {\n this.ranges = [];\n }\n\n this.ranges.push(newRange);\n this.activeRange = newRange;\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n\n /** @internal */\n override onCellMouseMove(event: CellMouseEvent): boolean | void {\n // Skip all selection if disabled at grid level or plugin level\n if (!this.isSelectionEnabled()) return;\n\n if (this.config.mode !== 'range') return;\n if (!this.isDragging || !this.cellAnchor) return;\n if (event.rowIndex === undefined || event.colIndex === undefined) return;\n if (event.rowIndex < 0) return;\n\n // When dragging, clamp to first data column (skip utility columns)\n let targetCol = event.colIndex;\n const column = this.columns[targetCol];\n if (column && isUtilityColumn(column)) {\n // Find the first non-utility column\n const firstDataCol = this.columns.findIndex((col) => !isUtilityColumn(col));\n if (firstDataCol >= 0) {\n targetCol = firstDataCol;\n }\n }\n\n const newRange = createRangeFromAnchor(this.cellAnchor, { row: event.rowIndex, col: targetCol });\n\n // Only update and emit if the range actually changed\n const currentRange = this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] : null;\n if (currentRange && rangesEqual(currentRange, newRange)) {\n return true; // Range unchanged, no need to update\n }\n\n if (this.ranges.length > 0) {\n this.ranges[this.ranges.length - 1] = newRange;\n } else {\n this.ranges.push(newRange);\n }\n this.activeRange = newRange;\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n\n /** @internal */\n override onCellMouseUp(_event: CellMouseEvent): boolean | void {\n // Skip all selection if disabled at grid level or plugin level\n if (!this.isSelectionEnabled()) return;\n\n if (this.config.mode !== 'range') return;\n if (this.isDragging) {\n this.isDragging = false;\n return true;\n }\n }\n\n // #region Checkbox Column\n\n /**\n * Inject checkbox column when `checkbox: true` and mode is `'row'`.\n * @internal\n */\n override processColumns(columns: ColumnConfig[]): ColumnConfig[] {\n if (this.config.checkbox && this.config.mode === 'row') {\n // Check if checkbox column already exists\n if (columns.some((col) => col.field === CHECKBOX_COLUMN_FIELD)) {\n return columns;\n }\n const checkboxCol = this.#createCheckboxColumn();\n // Insert after expander column if present, otherwise first\n const expanderIdx = columns.findIndex(isExpanderColumn);\n const insertAt = expanderIdx >= 0 ? expanderIdx + 1 : 0;\n return [...columns.slice(0, insertAt), checkboxCol, ...columns.slice(insertAt)];\n }\n return columns;\n }\n\n /**\n * Create the checkbox utility column configuration.\n */\n #createCheckboxColumn(): ColumnConfig {\n return {\n field: CHECKBOX_COLUMN_FIELD,\n header: '',\n width: 32,\n resizable: false,\n sortable: false,\n meta: {\n lockPosition: true,\n suppressMovable: true,\n utility: true,\n checkboxColumn: true,\n },\n headerRenderer: () => {\n const container = document.createElement('div');\n container.className = 'tbw-checkbox-header';\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.className = 'tbw-select-all-checkbox';\n checkbox.addEventListener('click', (e) => {\n e.stopPropagation(); // Prevent header sort\n if ((e.target as HTMLInputElement).checked) {\n this.selectAll();\n } else {\n this.clearSelection();\n }\n });\n container.appendChild(checkbox);\n return container;\n },\n renderer: (ctx) => {\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.className = 'tbw-select-row-checkbox';\n // Set initial checked state from current selection\n const cellEl = ctx.cellEl;\n if (cellEl) {\n const rowIndex = parseInt(cellEl.getAttribute('data-row') ?? '-1', 10);\n if (rowIndex >= 0) {\n checkbox.checked = this.selected.has(rowIndex);\n }\n }\n return checkbox;\n },\n };\n }\n\n /**\n * Update checkbox checked states to reflect current selection.\n * Called from #applySelectionClasses.\n */\n #updateCheckboxStates(gridEl: HTMLElement): void {\n // Update row checkboxes\n const rowCheckboxes = gridEl.querySelectorAll('.tbw-select-row-checkbox') as NodeListOf<HTMLInputElement>;\n rowCheckboxes.forEach((checkbox) => {\n const cell = checkbox.closest('.cell');\n const rowIndex = cell ? getRowIndexFromCell(cell) : -1;\n if (rowIndex >= 0) {\n checkbox.checked = this.selected.has(rowIndex);\n }\n });\n\n // Update header select-all checkbox\n const headerCheckbox = gridEl.querySelector('.tbw-select-all-checkbox') as HTMLInputElement | null;\n if (headerCheckbox) {\n const rowCount = this.rows.length;\n let selectableCount = 0;\n if (this.config.isSelectable) {\n for (let i = 0; i < rowCount; i++) {\n if (this.isRowSelectable(i)) selectableCount++;\n }\n } else {\n selectableCount = rowCount;\n }\n const allSelected = selectableCount > 0 && this.selected.size >= selectableCount;\n const someSelected = this.selected.size > 0;\n headerCheckbox.checked = allSelected;\n headerCheckbox.indeterminate = someSelected && !allSelected;\n }\n }\n\n // #endregion\n\n /**\n * Sync selection state to the grid's current focus position.\n * In row mode, keeps `selected` in sync with `_focusRow`.\n * In cell mode, keeps `selectedCell` in sync with `_focusRow`/`_focusCol`.\n * Only updates when the focus has changed since the last sync.\n * Skips when `explicitSelection` is set (click/keyboard set selection directly).\n */\n #syncSelectionToFocus(mode: string): void {\n const focusRow = this.grid._focusRow;\n const focusCol = this.grid._focusCol;\n\n if (mode === 'row') {\n // Skip auto-sync when selection was explicitly set (Shift/Ctrl click, keyboard)\n if (this.explicitSelection) {\n this.explicitSelection = false;\n this.lastSyncedFocusRow = focusRow;\n return;\n }\n\n if (focusRow !== this.lastSyncedFocusRow) {\n this.lastSyncedFocusRow = focusRow;\n if (this.isRowSelectable(focusRow)) {\n if (!this.selected.has(focusRow) || this.selected.size !== 1) {\n this.selected.clear();\n this.selected.add(focusRow);\n this.lastSelected = focusRow;\n this.anchor = focusRow;\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n }\n }\n }\n }\n\n if (mode === 'cell') {\n if (this.explicitSelection) {\n this.explicitSelection = false;\n this.lastSyncedFocusRow = focusRow;\n this.lastSyncedFocusCol = focusCol;\n return;\n }\n\n if (focusRow !== this.lastSyncedFocusRow || focusCol !== this.lastSyncedFocusCol) {\n this.lastSyncedFocusRow = focusRow;\n this.lastSyncedFocusCol = focusCol;\n if (this.isCellSelectable(focusRow, focusCol)) {\n const cur = this.selectedCell;\n if (!cur || cur.row !== focusRow || cur.col !== focusCol) {\n this.selectedCell = { row: focusRow, col: focusCol };\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n }\n }\n }\n }\n }\n\n /**\n * Apply CSS selection classes to row/cell elements.\n * Shared by afterRender and onScrollRender.\n */\n #applySelectionClasses(): void {\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n const { mode } = this.config;\n const hasSelectableCallback = !!this.config.isSelectable;\n\n // Clear all selection classes first\n const allCells = gridEl.querySelectorAll('.cell');\n allCells.forEach((cell) => {\n cell.classList.remove('selected', 'top', 'bottom', 'first', 'last');\n // Clear selectable attribute - will be re-applied below\n if (hasSelectableCallback) {\n cell.removeAttribute('data-selectable');\n }\n });\n\n const allRows = gridEl.querySelectorAll('.data-grid-row');\n allRows.forEach((row) => {\n row.classList.remove('selected', 'row-focus');\n // Clear selectable attribute - will be re-applied below\n if (hasSelectableCallback) {\n row.removeAttribute('data-selectable');\n }\n });\n\n // ROW MODE: Add row-focus class to selected rows, disable cell-focus, update checkboxes\n if (mode === 'row') {\n // In row mode, disable ALL cell-focus styling - row selection takes precedence\n clearCellFocus(gridEl);\n\n allRows.forEach((row) => {\n const firstCell = row.querySelector('.cell[data-row]');\n const rowIndex = getRowIndexFromCell(firstCell);\n if (rowIndex >= 0) {\n // Mark non-selectable rows\n if (hasSelectableCallback && !this.isRowSelectable(rowIndex)) {\n row.setAttribute('data-selectable', 'false');\n }\n if (this.selected.has(rowIndex)) {\n row.classList.add('selected', 'row-focus');\n }\n }\n });\n\n // Update checkbox states if checkbox column is enabled\n if (this.config.checkbox) {\n this.#updateCheckboxStates(gridEl);\n }\n }\n\n // CELL/RANGE MODE: Mark non-selectable cells\n if ((mode === 'cell' || mode === 'range') && hasSelectableCallback) {\n const cells = gridEl.querySelectorAll('.cell[data-row][data-col]');\n cells.forEach((cell) => {\n const rowIndex = parseInt(cell.getAttribute('data-row') ?? '-1', 10);\n const colIndex = parseInt(cell.getAttribute('data-col') ?? '-1', 10);\n if (rowIndex >= 0 && colIndex >= 0) {\n if (!this.isCellSelectable(rowIndex, colIndex)) {\n cell.setAttribute('data-selectable', 'false');\n }\n }\n });\n }\n\n // RANGE MODE: Add selected and edge classes to cells\n // Uses neighbor-based edge detection for correct multi-range borders\n if (mode === 'range' && this.ranges.length > 0) {\n // Clear all cell-focus first - selection plugin manages focus styling in range mode\n clearCellFocus(gridEl);\n\n // Pre-normalize ranges for efficient neighbor checks\n const normalizedRanges = this.ranges.map(normalizeRange);\n\n // Fast selection check against pre-normalized ranges\n const isInSelection = (r: number, c: number): boolean => {\n for (const range of normalizedRanges) {\n if (r >= range.startRow && r <= range.endRow && c >= range.startCol && c <= range.endCol) {\n return true;\n }\n }\n return false;\n };\n\n const cells = gridEl.querySelectorAll('.cell[data-row][data-col]');\n cells.forEach((cell) => {\n const rowIndex = parseInt(cell.getAttribute('data-row') ?? '-1', 10);\n const colIndex = parseInt(cell.getAttribute('data-col') ?? '-1', 10);\n if (rowIndex >= 0 && colIndex >= 0) {\n // Skip utility columns entirely - don't add any selection classes\n const column = this.columns[colIndex];\n if (column && isUtilityColumn(column)) {\n return;\n }\n\n if (isInSelection(rowIndex, colIndex)) {\n cell.classList.add('selected');\n\n // Edge detection: add border class where neighbor is not selected\n // This handles single ranges, multi-range, and irregular selections correctly\n if (!isInSelection(rowIndex - 1, colIndex)) cell.classList.add('top');\n if (!isInSelection(rowIndex + 1, colIndex)) cell.classList.add('bottom');\n if (!isInSelection(rowIndex, colIndex - 1)) cell.classList.add('first');\n if (!isInSelection(rowIndex, colIndex + 1)) cell.classList.add('last');\n }\n }\n });\n }\n\n // CELL MODE: Let the grid's native .cell-focus styling handle cell highlighting\n // No additional action needed - the grid already manages focus styling\n }\n\n /** @internal */\n override afterRender(): void {\n // Skip rendering selection if disabled at grid level or plugin level\n if (!this.isSelectionEnabled()) return;\n\n const gridEl = this.gridElement;\n if (!gridEl) return;\n\n const container = gridEl.children[0];\n const { mode } = this.config;\n\n // Process pending keyboard navigation update (range mode)\n // This runs AFTER the grid has updated focusRow/focusCol\n if (this.pendingKeyboardUpdate && mode === 'range') {\n const { shiftKey } = this.pendingKeyboardUpdate;\n this.pendingKeyboardUpdate = null;\n\n const currentRow = this.grid._focusRow;\n const currentCol = this.grid._focusCol;\n\n if (shiftKey && this.cellAnchor) {\n // Extend selection from anchor to current focus\n const newRange = createRangeFromAnchor(this.cellAnchor, { row: currentRow, col: currentCol });\n this.ranges = [newRange];\n this.activeRange = newRange;\n } else if (!shiftKey) {\n // Without shift, clear selection (cell-focus will show instead)\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = { row: currentRow, col: currentCol }; // Reset anchor to current position\n }\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n }\n\n // Sync selection to grid's focus position.\n // This ensures selection follows keyboard navigation (Tab, arrows, etc.)\n // regardless of which plugin moved the focus.\n this.#syncSelectionToFocus(mode);\n\n // Set data attribute on host for CSS variable scoping\n (this.grid as unknown as Element).setAttribute('data-selection-mode', mode);\n\n // Toggle .selecting class during drag to prevent text selection\n if (container) {\n container.classList.toggle('selecting', this.isDragging);\n }\n\n this.#applySelectionClasses();\n }\n\n /**\n * Called after scroll-triggered row rendering.\n * Reapplies selection classes to recycled DOM elements.\n * @internal\n */\n override onScrollRender(): void {\n // Skip rendering selection classes if disabled\n if (!this.isSelectionEnabled()) return;\n\n this.#applySelectionClasses();\n }\n\n // #endregion\n\n // #region Public API\n\n /**\n * Get the current selection as a unified result.\n * Works for all selection modes and always returns ranges.\n *\n * @example\n * ```ts\n * const selection = plugin.getSelection();\n * if (selection.ranges.length > 0) {\n * const { from, to } = selection.ranges[0];\n * // For cell mode: from === to (single cell)\n * // For row mode: from.col = 0, to.col = lastCol (full row)\n * // For range mode: rectangular selection\n * }\n * ```\n */\n getSelection(): SelectionResult {\n return {\n mode: this.config.mode,\n ranges: this.#buildEvent().ranges,\n anchor: this.cellAnchor,\n };\n }\n\n /**\n * Get all selected cells across all ranges.\n */\n getSelectedCells(): Array<{ row: number; col: number }> {\n return getAllCellsInRanges(this.ranges);\n }\n\n /**\n * Check if a specific cell is in range selection.\n */\n isCellSelected(row: number, col: number): boolean {\n return isCellInAnyRange(row, col, this.ranges);\n }\n\n /**\n * Select all selectable rows (row mode) or all cells (range mode).\n *\n * In row mode, selects every row where `isSelectable` returns true (or all rows if no callback).\n * In range mode, creates a single range spanning all rows and columns.\n * Has no effect in cell mode.\n *\n * @example\n * ```ts\n * const plugin = grid.getPlugin(SelectionPlugin);\n * plugin.selectAll(); // Selects everything in current mode\n * ```\n */\n selectAll(): void {\n const { mode } = this.config;\n\n if (mode === 'row') {\n this.selected.clear();\n for (let i = 0; i < this.rows.length; i++) {\n if (this.isRowSelectable(i)) {\n this.selected.add(i);\n }\n }\n this.explicitSelection = true;\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n } else if (mode === 'range') {\n const rowCount = this.rows.length;\n const colCount = this.columns.length;\n if (rowCount > 0 && colCount > 0) {\n const allRange: InternalCellRange = {\n startRow: 0,\n startCol: 0,\n endRow: rowCount - 1,\n endCol: colCount - 1,\n };\n this.ranges = [allRange];\n this.activeRange = allRange;\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n }\n }\n }\n\n /**\n * Select specific rows by index (row mode only).\n * Replaces the current selection with the provided row indices.\n * Indices that are out of bounds or fail the `isSelectable` check are ignored.\n *\n * @param indices - Array of row indices to select\n *\n * @example\n * ```ts\n * const plugin = grid.getPlugin(SelectionPlugin);\n * plugin.selectRows([0, 2, 4]); // Select rows 0, 2, and 4\n * ```\n */\n selectRows(indices: number[]): void {\n if (this.config.mode !== 'row') return;\n this.selected.clear();\n for (const idx of indices) {\n if (idx >= 0 && idx < this.rows.length && this.isRowSelectable(idx)) {\n this.selected.add(idx);\n }\n }\n this.anchor = indices.length > 0 ? indices[indices.length - 1] : null;\n this.explicitSelection = true;\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n }\n\n /**\n * Get the indices of all selected rows (convenience for row mode).\n * Returns indices sorted in ascending order.\n *\n * @example\n * ```ts\n * const plugin = grid.getPlugin(SelectionPlugin);\n * const rows = plugin.getSelectedRowIndices(); // [0, 2, 4]\n * ```\n */\n getSelectedRowIndices(): number[] {\n return [...this.selected].sort((a, b) => a - b);\n }\n\n /**\n * Clear all selection.\n */\n clearSelection(): void {\n this.selectedCell = null;\n this.selected.clear();\n this.anchor = null;\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n this.emit<SelectionChangeDetail>('selection-change', { mode: this.config.mode, ranges: [] });\n this.requestAfterRender();\n }\n\n /**\n * Set selected ranges programmatically.\n */\n setRanges(ranges: CellRange[]): void {\n this.ranges = ranges.map((r) => ({\n startRow: r.from.row,\n startCol: r.from.col,\n endRow: r.to.row,\n endCol: r.to.col,\n }));\n this.activeRange = this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] : null;\n this.emit<SelectionChangeDetail>('selection-change', {\n mode: this.config.mode,\n ranges: toPublicRanges(this.ranges),\n });\n this.requestAfterRender();\n }\n\n // #endregion\n\n // #region Private Helpers\n\n #buildEvent(): SelectionChangeDetail {\n return buildSelectionEvent(\n this.config.mode,\n {\n selectedCell: this.selectedCell,\n selected: this.selected,\n ranges: this.ranges,\n },\n this.columns.length,\n );\n }\n\n // #endregion\n}\n","import type { ServerSideDataSource, GetRowsParams, GetRowsResult } from './types';\n\nexport function getBlockNumber(rowIndex: number, blockSize: number): number {\n return Math.floor(rowIndex / blockSize);\n}\n\nexport function getBlockRange(blockNumber: number, blockSize: number): { start: number; end: number } {\n return {\n start: blockNumber * blockSize,\n end: (blockNumber + 1) * blockSize,\n };\n}\n\nexport function getRequiredBlocks(startRow: number, endRow: number, blockSize: number): number[] {\n const startBlock = getBlockNumber(startRow, blockSize);\n const endBlock = getBlockNumber(endRow - 1, blockSize);\n\n const blocks: number[] = [];\n for (let i = startBlock; i <= endBlock; i++) {\n blocks.push(i);\n }\n return blocks;\n}\n\nexport async function loadBlock(\n dataSource: ServerSideDataSource,\n blockNumber: number,\n blockSize: number,\n params: Partial<GetRowsParams>\n): Promise<GetRowsResult> {\n const range = getBlockRange(blockNumber, blockSize);\n\n return dataSource.getRows({\n startRow: range.start,\n endRow: range.end,\n sortModel: params.sortModel,\n filterModel: params.filterModel,\n });\n}\n\nexport function getRowFromCache(\n rowIndex: number,\n blockSize: number,\n loadedBlocks: Map<number, any[]>\n): any | undefined {\n const blockNumber = getBlockNumber(rowIndex, blockSize);\n const block = loadedBlocks.get(blockNumber);\n if (!block) return undefined;\n\n const indexInBlock = rowIndex % blockSize;\n return block[indexInBlock];\n}\n\nexport function isBlockLoaded(blockNumber: number, loadedBlocks: Map<number, any[]>): boolean {\n return loadedBlocks.has(blockNumber);\n}\n\nexport function isBlockLoading(blockNumber: number, loadingBlocks: Set<number>): boolean {\n return loadingBlocks.has(blockNumber);\n}\n","/**\n * Server-Side Data Plugin (Class-based)\n *\n * Provides server-side data loading with caching and lazy loading.\n */\n\nimport { BaseGridPlugin, ScrollEvent } from '../../core/plugin/base-plugin';\nimport { getBlockNumber, getRequiredBlocks, getRowFromCache, loadBlock } from './datasource';\nimport type { ServerSideConfig, ServerSideDataSource } from './types';\n\n/** Scroll debounce delay in ms */\nconst SCROLL_DEBOUNCE_MS = 100;\n\n/**\n * Server-Side Data Plugin for tbw-grid\n *\n * Enables lazy loading of data from a remote server with caching and block-based fetching.\n * Ideal for large datasets where loading all data upfront is impractical.\n *\n * ## Installation\n *\n * ```ts\n * import { ServerSidePlugin } from '@toolbox-web/grid/plugins/server-side';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `pageSize` | `number` | `100` | Rows per block |\n * | `cacheBlockSize` | `number` | `pageSize` | Cache block size |\n * | `maxConcurrentRequests` | `number` | `2` | Max parallel data requests |\n *\n * ## DataSource Interface\n *\n * ```ts\n * interface ServerSideDataSource {\n * getRows(params: GetRowsParams): Promise<GetRowsResult>;\n * }\n * ```\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `setDataSource` | `(ds: ServerSideDataSource) => void` | Set the data source |\n * | `refresh` | `() => void` | Refresh current data |\n * | `clearCache` | `() => void` | Clear all cached blocks |\n *\n * @example Basic Server-Side Loading\n * ```ts\n * import '@toolbox-web/grid';\n * import { ServerSidePlugin } from '@toolbox-web/grid/plugins/server-side';\n *\n * const dataSource = {\n * async getRows(params) {\n * const response = await fetch(\n * `/api/data?start=${params.startRow}&end=${params.endRow}`\n * );\n * const data = await response.json();\n * return { rows: data.rows, totalRowCount: data.total };\n * },\n * };\n *\n * const plugin = new ServerSidePlugin({ pageSize: 50 });\n * grid.gridConfig = {\n * columns: [...],\n * plugins: [plugin],\n * };\n *\n * grid.ready().then(() => plugin.setDataSource(dataSource));\n * ```\n *\n * @see {@link ServerSideConfig} for configuration options\n * @see {@link ServerSideDataSource} for data source interface\n *\n * @internal Extends BaseGridPlugin\n */\nexport class ServerSidePlugin extends BaseGridPlugin<ServerSideConfig> {\n /** @internal */\n readonly name = 'serverSide';\n\n /** @internal */\n protected override get defaultConfig(): Partial<ServerSideConfig> {\n return {\n pageSize: 100,\n cacheBlockSize: 100,\n maxConcurrentRequests: 2,\n };\n }\n\n // #region Internal State\n private dataSource: ServerSideDataSource | null = null;\n private totalRowCount = 0;\n private loadedBlocks = new Map<number, unknown[]>();\n private loadingBlocks = new Set<number>();\n private lastRequestId = 0;\n private scrollDebounceTimer?: ReturnType<typeof setTimeout>;\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override detach(): void {\n this.dataSource = null;\n this.totalRowCount = 0;\n this.loadedBlocks.clear();\n this.loadingBlocks.clear();\n this.lastRequestId = 0;\n if (this.scrollDebounceTimer) {\n clearTimeout(this.scrollDebounceTimer);\n this.scrollDebounceTimer = undefined;\n }\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Check current viewport and load any missing blocks.\n */\n private loadRequiredBlocks(): void {\n if (!this.dataSource) return;\n\n // Get fresh viewport from grid's virtualization state\n const gridRef = this.grid as unknown as { _virtualization: { start: number; end: number } };\n const blockSize = this.config.cacheBlockSize ?? 100;\n const viewport = { startRow: gridRef._virtualization.start, endRow: gridRef._virtualization.end };\n\n // Determine which blocks are needed for current viewport\n const requiredBlocks = getRequiredBlocks(viewport.startRow, viewport.endRow, blockSize);\n\n // Load missing blocks\n for (const blockNum of requiredBlocks) {\n if (this.loadedBlocks.has(blockNum) || this.loadingBlocks.has(blockNum)) {\n continue;\n }\n\n // Check concurrent request limit\n if (this.loadingBlocks.size >= (this.config.maxConcurrentRequests ?? 2)) {\n break;\n }\n\n this.loadingBlocks.add(blockNum);\n\n loadBlock(this.dataSource, blockNum, blockSize, {})\n .then((result) => {\n this.loadedBlocks.set(blockNum, result.rows);\n this.totalRowCount = result.totalRowCount;\n this.loadingBlocks.delete(blockNum);\n this.requestRender();\n // Re-check with fresh viewport: user may have scrolled further\n this.loadRequiredBlocks();\n })\n .catch(() => {\n this.loadingBlocks.delete(blockNum);\n });\n }\n }\n // #endregion\n\n // #region Hooks\n\n /** @internal */\n override processRows(rows: readonly unknown[]): unknown[] {\n if (!this.dataSource) return [...rows];\n\n // Create placeholder rows for total count\n const result: unknown[] = [];\n for (let i = 0; i < this.totalRowCount; i++) {\n const cached = getRowFromCache(i, this.config.cacheBlockSize ?? 100, this.loadedBlocks);\n result.push(cached ?? { __loading: true, __index: i });\n }\n\n return result;\n }\n\n /** @internal */\n override onScroll(event: ScrollEvent): void {\n if (!this.dataSource) return;\n\n // Immediate check for blocks\n this.loadRequiredBlocks();\n\n // Debounce: when scrolling stops, do a final check with fresh viewport\n if (this.scrollDebounceTimer) {\n clearTimeout(this.scrollDebounceTimer);\n }\n this.scrollDebounceTimer = setTimeout(() => {\n this.loadRequiredBlocks();\n }, SCROLL_DEBOUNCE_MS);\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Set the data source for server-side loading.\n * @param dataSource - Data source implementing the getRows method\n */\n setDataSource(dataSource: ServerSideDataSource): void {\n this.dataSource = dataSource;\n this.loadedBlocks.clear();\n this.loadingBlocks.clear();\n\n // Load first block\n const blockSize = this.config.cacheBlockSize ?? 100;\n loadBlock(dataSource, 0, blockSize, {}).then((result) => {\n this.loadedBlocks.set(0, result.rows);\n this.totalRowCount = result.totalRowCount;\n this.requestRender();\n });\n }\n\n /**\n * Refresh all data from the server.\n */\n refresh(): void {\n if (!this.dataSource) return;\n this.loadedBlocks.clear();\n this.loadingBlocks.clear();\n this.requestRender();\n }\n\n /**\n * Clear all cached data without refreshing.\n */\n purgeCache(): void {\n this.loadedBlocks.clear();\n }\n\n /**\n * Get the total row count from the server.\n */\n getTotalRowCount(): number {\n return this.totalRowCount;\n }\n\n /**\n * Check if a specific row is loaded in the cache.\n * @param rowIndex - Row index to check\n */\n isRowLoaded(rowIndex: number): boolean {\n const blockSize = this.config.cacheBlockSize ?? 100;\n const blockNum = getBlockNumber(rowIndex, blockSize);\n return this.loadedBlocks.has(blockNum);\n }\n\n /**\n * Get the number of loaded cache blocks.\n */\n getLoadedBlockCount(): number {\n return this.loadedBlocks.size;\n }\n // #endregion\n}\n","/**\n * Core Tree Data Logic\n *\n * Pure functions for tree flattening, expansion, and traversal.\n */\n\nimport type { FlattenedTreeRow, TreeConfig, TreeRow } from './types';\n\n/**\n * Generates a unique key for a row.\n * Uses row.id if available, otherwise generates from path.\n */\nexport function generateRowKey(row: TreeRow, index: number, parentKey: string | null): string {\n if (row.id !== undefined) return String(row.id);\n return parentKey ? `${parentKey}-${index}` : String(index);\n}\n\n/**\n * Flattens a hierarchical tree into a flat array of rows with metadata.\n * Only includes children of expanded nodes.\n */\nexport function flattenTree(\n rows: readonly TreeRow[],\n config: TreeConfig,\n expandedKeys: Set<string>,\n parentKey: string | null = null,\n depth = 0,\n): FlattenedTreeRow[] {\n const childrenField = config.childrenField ?? 'children';\n const result: FlattenedTreeRow[] = [];\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n const key = generateRowKey(row, i, parentKey);\n const children = row[childrenField];\n const hasChildren = Array.isArray(children) && children.length > 0;\n const isExpanded = expandedKeys.has(key);\n\n result.push({\n key,\n data: row,\n depth,\n hasChildren,\n isExpanded,\n parentKey,\n });\n\n // Recursively add children if expanded\n if (hasChildren && isExpanded) {\n const childRows = flattenTree(children as TreeRow[], config, expandedKeys, key, depth + 1);\n result.push(...childRows);\n }\n }\n\n return result;\n}\n\n/**\n * Toggles the expansion state of a row.\n * Returns a new Set with the toggled state.\n */\nexport function toggleExpand(expandedKeys: Set<string>, key: string): Set<string> {\n const newExpanded = new Set(expandedKeys);\n if (newExpanded.has(key)) {\n newExpanded.delete(key);\n } else {\n newExpanded.add(key);\n }\n return newExpanded;\n}\n\n/**\n * Expands all nodes in the tree.\n * Returns a Set of all parent row keys.\n */\nexport function expandAll(\n rows: readonly TreeRow[],\n config: TreeConfig,\n parentKey: string | null = null,\n depth = 0,\n): Set<string> {\n const childrenField = config.childrenField ?? 'children';\n const keys = new Set<string>();\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n const key = generateRowKey(row, i, parentKey);\n const children = row[childrenField];\n\n if (Array.isArray(children) && children.length > 0) {\n keys.add(key);\n const childKeys = expandAll(children as TreeRow[], config, key, depth + 1);\n for (const k of childKeys) keys.add(k);\n }\n }\n\n return keys;\n}\n\n/**\n * Collapses all nodes.\n * Returns an empty Set.\n */\nexport function collapseAll(): Set<string> {\n return new Set();\n}\n\n/**\n * Gets all descendants of a node from the flattened row list.\n * Useful for operations that need to affect an entire subtree.\n */\nexport function getDescendants(flattenedRows: FlattenedTreeRow[], parentKey: string): FlattenedTreeRow[] {\n const descendants: FlattenedTreeRow[] = [];\n let collecting = false;\n let parentDepth = -1;\n\n for (const row of flattenedRows) {\n if (row.key === parentKey) {\n collecting = true;\n parentDepth = row.depth;\n continue;\n }\n\n if (collecting) {\n if (row.depth > parentDepth) {\n descendants.push(row);\n } else {\n break; // No longer a descendant\n }\n }\n }\n\n return descendants;\n}\n\n/**\n * Finds the path from root to a specific row key.\n * Returns an array of keys from root to the target (inclusive).\n */\nexport function getPathToKey(\n rows: readonly TreeRow[],\n targetKey: string,\n config: TreeConfig,\n parentKey: string | null = null,\n depth = 0,\n): string[] | null {\n const childrenField = config.childrenField ?? 'children';\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n const key = generateRowKey(row, i, parentKey);\n\n if (key === targetKey) {\n return [key];\n }\n\n const children = row[childrenField];\n if (Array.isArray(children) && children.length > 0) {\n const childPath = getPathToKey(children as TreeRow[], targetKey, config, key, depth + 1);\n if (childPath) {\n return [key, ...childPath];\n }\n }\n }\n\n return null;\n}\n\n/**\n * Expands all ancestors of a specific row to make it visible.\n * Returns a new Set with the required keys added.\n */\nexport function expandToKey(\n rows: readonly TreeRow[],\n targetKey: string,\n config: TreeConfig,\n existingExpanded: Set<string>,\n): Set<string> {\n const path = getPathToKey(rows, targetKey, config);\n if (!path) return existingExpanded;\n\n const newExpanded = new Set(existingExpanded);\n // Add all keys except the last one (the target itself)\n for (let i = 0; i < path.length - 1; i++) {\n newExpanded.add(path[i]);\n }\n return newExpanded;\n}\n","/**\n * Tree Structure Auto-Detection\n *\n * Utilities for detecting hierarchical tree data structures.\n */\n\nimport type { TreeRow } from './types';\n\n/**\n * Detects if the data has a tree structure by checking for children arrays.\n */\nexport function detectTreeStructure(rows: readonly TreeRow[], childrenField = 'children'): boolean {\n if (!Array.isArray(rows) || rows.length === 0) return false;\n\n // Check if any row has a non-empty children array\n for (const row of rows) {\n if (!row) continue;\n const children = row[childrenField];\n if (Array.isArray(children) && children.length > 0) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Attempts to infer the children field name from common patterns.\n * Returns the first field that contains an array with items.\n */\nexport function inferChildrenField(rows: readonly TreeRow[]): string | null {\n if (!Array.isArray(rows) || rows.length === 0) return null;\n\n const commonArrayFields = ['children', 'items', 'nodes', 'subRows', 'nested'];\n\n for (const row of rows) {\n if (!row || typeof row !== 'object') continue;\n\n for (const field of commonArrayFields) {\n const value = row[field];\n if (Array.isArray(value) && value.length > 0) {\n return field;\n }\n }\n }\n\n return null;\n}\n\n/**\n * Calculates the maximum depth of the tree.\n * Useful for layout calculations and virtualization.\n */\nexport function getMaxDepth(rows: readonly TreeRow[], childrenField = 'children', currentDepth = 0): number {\n if (!Array.isArray(rows) || rows.length === 0) return currentDepth;\n\n let maxDepth = currentDepth;\n\n for (const row of rows) {\n if (!row) continue;\n const children = row[childrenField];\n if (Array.isArray(children) && children.length > 0) {\n const childDepth = getMaxDepth(children as TreeRow[], childrenField, currentDepth + 1);\n if (childDepth > maxDepth) {\n maxDepth = childDepth;\n }\n }\n }\n\n return maxDepth;\n}\n\n/**\n * Counts total nodes in the tree (including all descendants).\n */\nexport function countNodes(rows: readonly TreeRow[], childrenField = 'children'): number {\n if (!Array.isArray(rows)) return 0;\n\n let count = 0;\n for (const row of rows) {\n if (!row) continue;\n count++;\n const children = row[childrenField];\n if (Array.isArray(children)) {\n count += countNodes(children as TreeRow[], childrenField);\n }\n }\n\n return count;\n}\n","/**\n * Tree Data Plugin\n *\n * Enables hierarchical tree data with expand/collapse, sorting, and auto-detection.\n */\n\nimport {\n BaseGridPlugin,\n CellClickEvent,\n HeaderClickEvent,\n type PluginManifest,\n type PluginQuery,\n} from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, ColumnViewRenderer } from '../../core/types';\nimport { collapseAll, expandAll, expandToKey, toggleExpand } from './tree-data';\nimport { detectTreeStructure, inferChildrenField } from './tree-detect';\nimport styles from './tree.css?inline';\nimport type { ExpandCollapseAnimation, FlattenedTreeRow, TreeConfig, TreeExpandDetail, TreeRow } from './types';\n\ninterface GridWithSortState {\n _sortState?: { field: string; direction: 1 | -1 } | null;\n}\n\n/**\n * Tree Data Plugin for tbw-grid\n *\n * Transforms your flat grid into a hierarchical tree view with expandable parent-child\n * relationships. Ideal for file explorers, organizational charts, nested categories,\n * or any data with a natural hierarchy.\n *\n * ## Installation\n *\n * ```ts\n * import { TreePlugin } from '@toolbox-web/grid/plugins/tree';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `childrenField` | `string` | `'children'` | Field containing child array |\n * | `autoDetect` | `boolean` | `true` | Auto-detect tree structure from data |\n * | `defaultExpanded` | `boolean` | `false` | Expand all nodes initially |\n * | `indentWidth` | `number` | `20` | Indentation per level (pixels) |\n * | `showExpandIcons` | `boolean` | `true` | Show expand/collapse toggle icons |\n * | `animation` | `false \\| 'slide' \\| 'fade'` | `'slide'` | Animation style for expand/collapse |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `expand` | `(nodeId) => void` | Expand a specific node |\n * | `collapse` | `(nodeId) => void` | Collapse a specific node |\n * | `toggle` | `(nodeId) => void` | Toggle a node's expanded state |\n * | `expandAll` | `() => void` | Expand all nodes |\n * | `collapseAll` | `() => void` | Collapse all nodes |\n * | `getExpandedNodes` | `() => Set<string>` | Get currently expanded node keys |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-tree-toggle-size` | `1.25em` | Toggle icon width |\n * | `--tbw-tree-indent-width` | `var(--tbw-tree-toggle-size)` | Indentation per level |\n * | `--tbw-tree-accent` | `var(--tbw-color-accent)` | Toggle icon hover color |\n * | `--tbw-animation-duration` | `200ms` | Expand/collapse animation duration |\n * | `--tbw-animation-easing` | `ease-out` | Animation curve |\n *\n * @example Basic Tree with Nested Children\n * ```ts\n * import '@toolbox-web/grid';\n * import { TreePlugin } from '@toolbox-web/grid/plugins/tree';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name' },\n * { field: 'type', header: 'Type' },\n * { field: 'size', header: 'Size' },\n * ],\n * plugins: [new TreePlugin({ childrenField: 'children', indentWidth: 24 })],\n * };\n * grid.rows = [\n * {\n * id: 1,\n * name: 'Documents',\n * type: 'folder',\n * children: [\n * { id: 2, name: 'Report.docx', type: 'file', size: '24 KB' },\n * ],\n * },\n * ];\n * ```\n *\n * @example Expanded by Default with Custom Animation\n * ```ts\n * new TreePlugin({\n * defaultExpanded: true,\n * animation: 'fade', // 'slide' | 'fade' | false\n * indentWidth: 32,\n * })\n * ```\n *\n * @see {@link TreeConfig} for all configuration options\n * @see {@link FlattenedTreeRow} for the flattened row structure\n *\n * @internal Extends BaseGridPlugin\n */\nexport class TreePlugin extends BaseGridPlugin<TreeConfig> {\n static override readonly manifest: PluginManifest = {\n events: [\n {\n type: 'tree-state-change',\n description: 'Emitted when tree expansion state changes (toggle, expand all, collapse all)',\n },\n ],\n queries: [\n {\n type: 'canMoveRow',\n description: 'Returns false for rows with children (parent nodes cannot be reordered)',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'tree';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<TreeConfig> {\n return {\n childrenField: 'children',\n autoDetect: true,\n defaultExpanded: false,\n indentWidth: 20,\n showExpandIcons: true,\n animation: 'slide',\n };\n }\n\n // #region State\n\n private expandedKeys = new Set<string>();\n private initialExpansionDone = false;\n private flattenedRows: FlattenedTreeRow[] = [];\n private rowKeyMap = new Map<string, FlattenedTreeRow>();\n private previousVisibleKeys = new Set<string>();\n private keysToAnimate = new Set<string>();\n private sortState: { field: string; direction: 1 | -1 } | null = null;\n\n /** @internal */\n override detach(): void {\n this.expandedKeys.clear();\n this.initialExpansionDone = false;\n this.flattenedRows = [];\n this.rowKeyMap.clear();\n this.previousVisibleKeys.clear();\n this.keysToAnimate.clear();\n this.sortState = null;\n }\n\n /**\n * Handle plugin queries.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'canMoveRow') {\n // Tree rows with children cannot be reordered\n const row = query.context as { [key: string]: unknown } | null | undefined;\n const childrenField = this.config.childrenField ?? 'children';\n const children = row?.[childrenField];\n if (Array.isArray(children) && children.length > 0) {\n return false;\n }\n }\n return undefined;\n }\n\n // #endregion\n\n // #region Animation\n\n /**\n * Get expand/collapse animation style from plugin config.\n * Uses base class isAnimationEnabled to respect grid-level settings.\n */\n private get animationStyle(): ExpandCollapseAnimation {\n if (!this.isAnimationEnabled) return false;\n return this.config.animation ?? 'slide';\n }\n\n // #endregion\n\n // #region Auto-Detection\n\n detect(rows: readonly unknown[]): boolean {\n if (!this.config.autoDetect) return false;\n const treeRows = rows as readonly TreeRow[];\n const field = this.config.childrenField ?? inferChildrenField(treeRows) ?? 'children';\n return detectTreeStructure(treeRows, field);\n }\n\n // #endregion\n\n // #region Data Processing\n\n /** @internal */\n override processRows(rows: readonly unknown[]): TreeRow[] {\n const childrenField = this.config.childrenField ?? 'children';\n const treeRows = rows as readonly TreeRow[];\n\n if (!detectTreeStructure(treeRows, childrenField)) {\n this.flattenedRows = [];\n this.rowKeyMap.clear();\n this.previousVisibleKeys.clear();\n return [...rows] as TreeRow[];\n }\n\n // Assign stable keys, then optionally sort\n let data = this.withStableKeys(treeRows);\n if (this.sortState) {\n data = this.sortTree(data, this.sortState.field, this.sortState.direction);\n }\n\n // Initialize expansion if needed\n if (this.config.defaultExpanded && !this.initialExpansionDone) {\n this.expandedKeys = expandAll(data, this.config);\n this.initialExpansionDone = true;\n }\n\n // Flatten and track animations\n this.flattenedRows = this.flattenTree(data, this.expandedKeys);\n this.rowKeyMap.clear();\n this.keysToAnimate.clear();\n const currentKeys = new Set<string>();\n\n for (const row of this.flattenedRows) {\n this.rowKeyMap.set(row.key, row);\n currentKeys.add(row.key);\n if (!this.previousVisibleKeys.has(row.key) && row.depth > 0) {\n this.keysToAnimate.add(row.key);\n }\n }\n this.previousVisibleKeys = currentKeys;\n\n return this.flattenedRows.map((r) => ({\n ...r.data,\n __treeKey: r.key,\n __treeDepth: r.depth,\n __treeHasChildren: r.hasChildren,\n __treeExpanded: r.isExpanded,\n }));\n }\n\n /** Assign stable keys to rows (preserves key across sort operations) */\n private withStableKeys(rows: readonly TreeRow[], parentKey: string | null = null): TreeRow[] {\n const childrenField = this.config.childrenField ?? 'children';\n return rows.map((row, i) => {\n const stableKey = row.__stableKey as string | undefined;\n const key = row.id !== undefined ? String(row.id) : (stableKey ?? (parentKey ? `${parentKey}-${i}` : String(i)));\n const children = row[childrenField];\n const hasChildren = Array.isArray(children) && children.length > 0;\n return {\n ...row,\n __stableKey: key,\n ...(hasChildren ? { [childrenField]: this.withStableKeys(children as TreeRow[], key) } : {}),\n };\n });\n }\n\n /** Flatten tree using stable keys */\n private flattenTree(rows: readonly TreeRow[], expanded: Set<string>, depth = 0): FlattenedTreeRow[] {\n const childrenField = this.config.childrenField ?? 'children';\n const result: FlattenedTreeRow[] = [];\n\n for (const row of rows) {\n const stableKey = row.__stableKey as string | undefined;\n const key = stableKey ?? String(row.id ?? '?');\n const children = row[childrenField];\n const hasChildren = Array.isArray(children) && children.length > 0;\n const isExpanded = expanded.has(key);\n\n result.push({\n key,\n data: row,\n depth,\n hasChildren,\n isExpanded,\n parentKey: depth > 0 ? key.substring(0, key.lastIndexOf('-')) || null : null,\n });\n\n if (hasChildren && isExpanded) {\n result.push(...this.flattenTree(children as TreeRow[], expanded, depth + 1));\n }\n }\n return result;\n }\n\n /** Sort tree recursively, keeping children with parents */\n private sortTree(rows: readonly TreeRow[], field: string, dir: 1 | -1): TreeRow[] {\n const childrenField = this.config.childrenField ?? 'children';\n const sorted = [...rows].sort((a, b) => {\n const aVal = a[field],\n bVal = b[field];\n if (aVal == null && bVal == null) return 0;\n if (aVal == null) return -1;\n if (bVal == null) return 1;\n return aVal > bVal ? dir : aVal < bVal ? -dir : 0;\n });\n return sorted.map((row) => {\n const children = row[childrenField];\n return Array.isArray(children) && children.length > 0\n ? { ...row, [childrenField]: this.sortTree(children as TreeRow[], field, dir) }\n : row;\n });\n }\n\n /** @internal */\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (this.flattenedRows.length === 0) return [...columns];\n\n const cols = [...columns] as ColumnConfig[];\n if (cols.length === 0) return cols;\n\n // Wrap the first column's renderer to add tree indentation and expand icons\n // This is the correct approach for trees because:\n // 1. Indentation can grow naturally with depth\n // 2. Expand icons appear inline with content\n // 3. Works with column reordering (icons stay with first visible column)\n const firstCol = cols[0];\n const originalRenderer = firstCol.viewRenderer;\n const getConfig = () => this.config;\n const setIcon = this.setIcon.bind(this);\n const resolveIcon = this.resolveIcon.bind(this);\n\n const wrappedRenderer: ColumnViewRenderer = (ctx) => {\n const { row, value } = ctx;\n const { showExpandIcons = true, indentWidth } = getConfig();\n const treeRow = row as TreeRow;\n const depth = treeRow.__treeDepth ?? 0;\n\n const container = document.createElement('span');\n container.className = 'tree-cell-wrapper';\n container.style.setProperty('--tbw-tree-depth', String(depth));\n // Allow config-based indentWidth to override CSS default\n if (indentWidth !== undefined) {\n container.style.setProperty('--tbw-tree-indent-width', `${indentWidth}px`);\n }\n\n // Add expand/collapse icon or spacer\n if (showExpandIcons) {\n if (treeRow.__treeHasChildren) {\n const icon = document.createElement('span');\n icon.className = `tree-toggle${treeRow.__treeExpanded ? ' expanded' : ''}`;\n setIcon(icon, resolveIcon(treeRow.__treeExpanded ? 'collapse' : 'expand'));\n icon.setAttribute('data-tree-key', String(treeRow.__treeKey ?? ''));\n container.appendChild(icon);\n } else {\n const spacer = document.createElement('span');\n spacer.className = 'tree-spacer';\n container.appendChild(spacer);\n }\n }\n\n // Add the original content\n const content = document.createElement('span');\n content.className = 'tree-content';\n if (originalRenderer) {\n const result = originalRenderer(ctx);\n if (result instanceof Node) {\n content.appendChild(result);\n } else if (typeof result === 'string') {\n content.innerHTML = result;\n }\n } else {\n content.textContent = value != null ? String(value) : '';\n }\n container.appendChild(content);\n\n return container;\n };\n\n cols[0] = { ...firstCol, viewRenderer: wrappedRenderer };\n return cols;\n }\n\n // #endregion\n\n // #region Event Handlers\n\n /** @internal */\n override onCellClick(event: CellClickEvent): boolean {\n const target = event.originalEvent?.target as HTMLElement;\n if (!target?.classList.contains('tree-toggle')) return false;\n\n const key = target.getAttribute('data-tree-key');\n if (!key) return false;\n\n const flatRow = this.rowKeyMap.get(key);\n if (!flatRow) return false;\n\n this.expandedKeys = toggleExpand(this.expandedKeys, key);\n this.emit<TreeExpandDetail>('tree-expand', {\n key,\n row: flatRow.data,\n expanded: this.expandedKeys.has(key),\n depth: flatRow.depth,\n });\n this.requestRender();\n return true;\n }\n\n /** @internal */\n override onKeyDown(event: KeyboardEvent): boolean | void {\n // SPACE toggles expansion when on a row with children\n if (event.key !== ' ') return;\n\n const focusRow = this.grid._focusRow;\n const flatRow = this.flattenedRows[focusRow];\n if (!flatRow?.hasChildren) return;\n\n event.preventDefault();\n this.expandedKeys = toggleExpand(this.expandedKeys, flatRow.key);\n this.emit<TreeExpandDetail>('tree-expand', {\n key: flatRow.key,\n row: flatRow.data,\n expanded: this.expandedKeys.has(flatRow.key),\n depth: flatRow.depth,\n });\n this.requestRenderWithFocus();\n return true;\n }\n\n /** @internal */\n override onHeaderClick(event: HeaderClickEvent): boolean {\n if (this.flattenedRows.length === 0 || !event.column.sortable) return false;\n\n const { field } = event.column;\n if (!this.sortState || this.sortState.field !== field) {\n this.sortState = { field, direction: 1 };\n } else if (this.sortState.direction === 1) {\n this.sortState = { field, direction: -1 };\n } else {\n this.sortState = null;\n }\n\n // Sync grid sort indicator\n const gridEl = this.grid as unknown as GridWithSortState;\n if (gridEl._sortState !== undefined) {\n gridEl._sortState = this.sortState ? { ...this.sortState } : null;\n }\n\n this.emit('sort-change', { field, direction: this.sortState?.direction ?? 0 });\n this.requestRender();\n return true;\n }\n\n /** @internal */\n override afterRender(): void {\n const style = this.animationStyle;\n if (style === false || this.keysToAnimate.size === 0) return;\n\n const body = this.gridElement?.querySelector('.rows');\n if (!body) return;\n\n const animClass = style === 'fade' ? 'tbw-tree-fade-in' : 'tbw-tree-slide-in';\n for (const rowEl of body.querySelectorAll('.data-grid-row')) {\n const cell = rowEl.querySelector('.cell[data-row]');\n const idx = cell ? parseInt(cell.getAttribute('data-row') ?? '-1', 10) : -1;\n const key = this.flattenedRows[idx]?.key;\n\n if (key && this.keysToAnimate.has(key)) {\n rowEl.classList.add(animClass);\n rowEl.addEventListener('animationend', () => rowEl.classList.remove(animClass), { once: true });\n }\n }\n this.keysToAnimate.clear();\n }\n\n // #endregion\n\n // #region Public API\n\n expand(key: string): void {\n this.expandedKeys.add(key);\n this.requestRender();\n }\n\n collapse(key: string): void {\n this.expandedKeys.delete(key);\n this.requestRender();\n }\n\n toggle(key: string): void {\n this.expandedKeys = toggleExpand(this.expandedKeys, key);\n this.emitPluginEvent('tree-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n expandAll(): void {\n this.expandedKeys = expandAll(this.rows as TreeRow[], this.config);\n this.emitPluginEvent('tree-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n collapseAll(): void {\n this.expandedKeys = collapseAll();\n this.emitPluginEvent('tree-state-change', { expandedKeys: [...this.expandedKeys] });\n this.requestRender();\n }\n\n isExpanded(key: string): boolean {\n return this.expandedKeys.has(key);\n }\n\n getExpandedKeys(): string[] {\n return [...this.expandedKeys];\n }\n\n getFlattenedRows(): FlattenedTreeRow[] {\n return [...this.flattenedRows];\n }\n\n getRowByKey(key: string): TreeRow | undefined {\n return this.rowKeyMap.get(key)?.data;\n }\n\n expandToKey(key: string): void {\n this.expandedKeys = expandToKey(this.rows as TreeRow[], key, this.config, this.expandedKeys);\n this.requestRender();\n }\n\n // #endregion\n}\n","/**\n * Undo/Redo History Management\n *\n * Pure functions for managing the undo/redo stacks.\n * These functions are stateless and return new state objects.\n */\n\nimport type { EditAction, UndoRedoState } from './types';\n\n/**\n * Push a new action onto the undo stack.\n * Clears the redo stack since new actions invalidate redo history.\n *\n * @param state - Current undo/redo state\n * @param action - The action to add\n * @param maxSize - Maximum history size\n * @returns New state with the action added\n */\nexport function pushAction(state: UndoRedoState, action: EditAction, maxSize: number): UndoRedoState {\n const undoStack = [...state.undoStack, action];\n\n // Trim oldest actions if over max size\n while (undoStack.length > maxSize) {\n undoStack.shift();\n }\n\n return {\n undoStack,\n redoStack: [], // Clear redo on new action\n };\n}\n\n/**\n * Undo the most recent action.\n * Moves the action from undo stack to redo stack.\n *\n * @param state - Current undo/redo state\n * @returns New state and the action that was undone (or null if nothing to undo)\n */\nexport function undo(state: UndoRedoState): {\n newState: UndoRedoState;\n action: EditAction | null;\n} {\n if (state.undoStack.length === 0) {\n return { newState: state, action: null };\n }\n\n const undoStack = [...state.undoStack];\n const action = undoStack.pop();\n\n // This should never happen due to the length check above,\n // but TypeScript needs the explicit check\n if (!action) {\n return { newState: state, action: null };\n }\n\n return {\n newState: {\n undoStack,\n redoStack: [...state.redoStack, action],\n },\n action,\n };\n}\n\n/**\n * Redo the most recently undone action.\n * Moves the action from redo stack back to undo stack.\n *\n * @param state - Current undo/redo state\n * @returns New state and the action that was redone (or null if nothing to redo)\n */\nexport function redo(state: UndoRedoState): {\n newState: UndoRedoState;\n action: EditAction | null;\n} {\n if (state.redoStack.length === 0) {\n return { newState: state, action: null };\n }\n\n const redoStack = [...state.redoStack];\n const action = redoStack.pop();\n\n // This should never happen due to the length check above,\n // but TypeScript needs the explicit check\n if (!action) {\n return { newState: state, action: null };\n }\n\n return {\n newState: {\n undoStack: [...state.undoStack, action],\n redoStack,\n },\n action,\n };\n}\n\n/**\n * Check if there are any actions that can be undone.\n *\n * @param state - Current undo/redo state\n * @returns True if undo is available\n */\nexport function canUndo(state: UndoRedoState): boolean {\n return state.undoStack.length > 0;\n}\n\n/**\n * Check if there are any actions that can be redone.\n *\n * @param state - Current undo/redo state\n * @returns True if redo is available\n */\nexport function canRedo(state: UndoRedoState): boolean {\n return state.redoStack.length > 0;\n}\n\n/**\n * Clear all history, returning an empty state.\n *\n * @returns Fresh empty state\n */\nexport function clearHistory(): UndoRedoState {\n return { undoStack: [], redoStack: [] };\n}\n\n/**\n * Create a new edit action with the current timestamp.\n *\n * @param rowIndex - The row index where the edit occurred\n * @param field - The field (column key) that was edited\n * @param oldValue - The value before the edit\n * @param newValue - The value after the edit\n * @returns A new EditAction object\n */\nexport function createEditAction(rowIndex: number, field: string, oldValue: unknown, newValue: unknown): EditAction {\n return {\n type: 'cell-edit',\n rowIndex,\n field,\n oldValue,\n newValue,\n timestamp: Date.now(),\n };\n}\n","/**\n * Undo/Redo Plugin (Class-based)\n *\n * Provides undo/redo functionality for cell edits in tbw-grid.\n * Supports Ctrl+Z/Cmd+Z for undo and Ctrl+Y/Cmd+Y (or Ctrl+Shift+Z) for redo.\n */\n\nimport { BaseGridPlugin, type GridElement, type PluginDependency } from '../../core/plugin/base-plugin';\nimport { canRedo, canUndo, clearHistory, createEditAction, pushAction, redo, undo } from './history';\nimport type { EditAction, UndoRedoConfig, UndoRedoDetail } from './types';\n\n/**\n * Undo/Redo Plugin for tbw-grid\n *\n * Tracks all cell edits and lets users revert or replay changes with familiar keyboard\n * shortcuts (Ctrl+Z / Ctrl+Y). Maintains an in-memory history stack with configurable\n * depth—perfect for data entry workflows where mistakes happen.\n *\n * > **Required Dependency:** This plugin requires EditingPlugin to be loaded first.\n * > UndoRedo tracks the edit history that EditingPlugin creates.\n *\n * ## Installation\n *\n * ```ts\n * import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';\n * import { UndoRedoPlugin } from '@toolbox-web/grid/plugins/undo-redo';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `maxHistorySize` | `number` | `100` | Maximum actions in history stack |\n *\n * ## Keyboard Shortcuts\n *\n * | Shortcut | Action |\n * |----------|--------|\n * | `Ctrl+Z` / `Cmd+Z` | Undo last edit |\n * | `Ctrl+Y` / `Cmd+Shift+Z` | Redo last undone edit |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `undo` | `() => void` | Undo the last edit |\n * | `redo` | `() => void` | Redo the last undone edit |\n * | `canUndo` | `() => boolean` | Check if undo is available |\n * | `canRedo` | `() => boolean` | Check if redo is available |\n * | `clearHistory` | `() => void` | Clear the entire history stack |\n *\n * @example Basic Usage with EditingPlugin\n * ```ts\n * import '@toolbox-web/grid';\n * import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';\n * import { UndoRedoPlugin } from '@toolbox-web/grid/plugins/undo-redo';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name', editable: true },\n * { field: 'price', header: 'Price', type: 'number', editable: true },\n * ],\n * plugins: [\n * new EditingPlugin({ editOn: 'dblclick' }), // Required - must be first\n * new UndoRedoPlugin({ maxHistorySize: 50 }),\n * ],\n * };\n * ```\n *\n * @see {@link UndoRedoConfig} for configuration options\n * @see {@link EditingPlugin} for the required dependency\n *\n * @internal Extends BaseGridPlugin\n */\nexport class UndoRedoPlugin extends BaseGridPlugin<UndoRedoConfig> {\n /**\n * Plugin dependencies - UndoRedoPlugin requires EditingPlugin to track edits.\n *\n * The EditingPlugin must be loaded BEFORE this plugin in the plugins array.\n * @internal\n */\n static override readonly dependencies: PluginDependency[] = [\n { name: 'editing', required: true, reason: 'UndoRedoPlugin tracks cell edit history' },\n ];\n\n /** @internal */\n readonly name = 'undoRedo';\n\n /** @internal */\n protected override get defaultConfig(): Partial<UndoRedoConfig> {\n return {\n maxHistorySize: 100,\n };\n }\n\n // State as class properties\n private undoStack: EditAction[] = [];\n private redoStack: EditAction[] = [];\n\n /**\n * Subscribe to cell-edit-committed events from EditingPlugin.\n * @internal\n */\n override attach(grid: GridElement): void {\n super.attach(grid);\n // Auto-record edits via Event Bus\n this.on(\n 'cell-edit-committed',\n (detail: { rowIndex: number; field: string; oldValue: unknown; newValue: unknown }) => {\n this.recordEdit(detail.rowIndex, detail.field, detail.oldValue, detail.newValue);\n },\n );\n }\n\n /**\n * Clean up state when plugin is detached.\n * @internal\n */\n override detach(): void {\n this.undoStack = [];\n this.redoStack = [];\n }\n\n /**\n * Handle keyboard shortcuts for undo/redo.\n * - Ctrl+Z / Cmd+Z: Undo\n * - Ctrl+Y / Cmd+Y / Ctrl+Shift+Z / Cmd+Shift+Z: Redo\n * @internal\n */\n override onKeyDown(event: KeyboardEvent): boolean {\n const isUndo = (event.ctrlKey || event.metaKey) && event.key === 'z' && !event.shiftKey;\n const isRedo = (event.ctrlKey || event.metaKey) && (event.key === 'y' || (event.key === 'z' && event.shiftKey));\n\n if (isUndo) {\n const result = undo({ undoStack: this.undoStack, redoStack: this.redoStack });\n if (result.action) {\n // Apply undo - restore old value\n const rows = this.rows as Record<string, unknown>[];\n if (rows[result.action.rowIndex]) {\n rows[result.action.rowIndex][result.action.field] = result.action.oldValue;\n }\n\n // Update state from result\n this.undoStack = result.newState.undoStack;\n this.redoStack = result.newState.redoStack;\n\n this.emit<UndoRedoDetail>('undo', {\n action: result.action,\n type: 'undo',\n });\n\n this.requestRender();\n }\n return true;\n }\n\n if (isRedo) {\n const result = redo({ undoStack: this.undoStack, redoStack: this.redoStack });\n if (result.action) {\n // Apply redo - restore new value\n const rows = this.rows as Record<string, unknown>[];\n if (rows[result.action.rowIndex]) {\n rows[result.action.rowIndex][result.action.field] = result.action.newValue;\n }\n\n // Update state from result\n this.undoStack = result.newState.undoStack;\n this.redoStack = result.newState.redoStack;\n\n this.emit<UndoRedoDetail>('redo', {\n action: result.action,\n type: 'redo',\n });\n\n this.requestRender();\n }\n return true;\n }\n\n return false;\n }\n\n // #region Public API Methods\n\n /**\n * Record a cell edit for undo/redo tracking.\n * Call this when a cell value changes.\n *\n * @param rowIndex - The row index where the edit occurred\n * @param field - The field (column key) that was edited\n * @param oldValue - The value before the edit\n * @param newValue - The value after the edit\n */\n recordEdit(rowIndex: number, field: string, oldValue: unknown, newValue: unknown): void {\n const action = createEditAction(rowIndex, field, oldValue, newValue);\n const newState = pushAction(\n { undoStack: this.undoStack, redoStack: this.redoStack },\n action,\n this.config.maxHistorySize ?? 100,\n );\n this.undoStack = newState.undoStack;\n this.redoStack = newState.redoStack;\n }\n\n /**\n * Programmatically undo the last action.\n *\n * @returns The undone action, or null if nothing to undo\n */\n undo(): EditAction | null {\n const result = undo({ undoStack: this.undoStack, redoStack: this.redoStack });\n if (result.action) {\n const rows = this.rows as Record<string, unknown>[];\n if (rows[result.action.rowIndex]) {\n rows[result.action.rowIndex][result.action.field] = result.action.oldValue;\n }\n this.undoStack = result.newState.undoStack;\n this.redoStack = result.newState.redoStack;\n this.requestRender();\n }\n return result.action;\n }\n\n /**\n * Programmatically redo the last undone action.\n *\n * @returns The redone action, or null if nothing to redo\n */\n redo(): EditAction | null {\n const result = redo({ undoStack: this.undoStack, redoStack: this.redoStack });\n if (result.action) {\n const rows = this.rows as Record<string, unknown>[];\n if (rows[result.action.rowIndex]) {\n rows[result.action.rowIndex][result.action.field] = result.action.newValue;\n }\n this.undoStack = result.newState.undoStack;\n this.redoStack = result.newState.redoStack;\n this.requestRender();\n }\n return result.action;\n }\n\n /**\n * Check if there are any actions that can be undone.\n */\n canUndo(): boolean {\n return canUndo({ undoStack: this.undoStack, redoStack: this.redoStack });\n }\n\n /**\n * Check if there are any actions that can be redone.\n */\n canRedo(): boolean {\n return canRedo({ undoStack: this.undoStack, redoStack: this.redoStack });\n }\n\n /**\n * Clear all undo/redo history.\n */\n clearHistory(): void {\n const newState = clearHistory();\n this.undoStack = newState.undoStack;\n this.redoStack = newState.redoStack;\n }\n\n /**\n * Get a copy of the current undo stack.\n */\n getUndoStack(): EditAction[] {\n return [...this.undoStack];\n }\n\n /**\n * Get a copy of the current redo stack.\n */\n getRedoStack(): EditAction[] {\n return [...this.redoStack];\n }\n // #endregion\n}\n","/**\n * Column Visibility Plugin (Class-based)\n *\n * Provides a UI for column visibility control via the shell's tool panel system.\n * Column visibility is a core grid feature - this plugin provides:\n * - A tool panel for column visibility management (registered with the shell)\n * - Backward-compatible API methods that delegate to grid.setColumnVisible(), etc.\n *\n * The grid emits 'column-visibility' events when columns are shown/hidden,\n * allowing consumers to save user preferences.\n *\n * When a reorder plugin is present, column rows become draggable for reordering.\n * Drag-drop emits 'column-reorder-request' events that the ReorderPlugin can listen for.\n */\n\nimport {\n BaseGridPlugin,\n type PluginDependency,\n type PluginManifest,\n type PluginQuery,\n} from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, ToolPanelDefinition } from '../../core/types';\nimport type { ContextMenuParams, HeaderContextMenuItem } from '../context-menu/types';\nimport type { ColumnGroupInfo, VisibilityConfig } from './types';\nimport styles from './visibility.css?inline';\n\n/**\n * Detail for column-reorder-request events emitted when users drag-drop in the visibility panel.\n */\nexport interface ColumnReorderRequestDetail {\n /** The field name of the column to move */\n field: string;\n /** The source index (before move) */\n fromIndex: number;\n /** The target index (after move) */\n toIndex: number;\n}\n\n/**\n * Check if a column can be moved (respects lockPosition/suppressMovable).\n * Inlined to avoid importing from reorder plugin.\n */\nfunction canMoveColumn(column: ColumnConfig): boolean {\n const meta = column.meta ?? {};\n return meta.lockPosition !== true && meta.suppressMovable !== true;\n}\n\n/**\n * Column Visibility Plugin for tbw-grid\n *\n * Gives users control over which columns are displayed. Hide less important columns\n * by default, let users toggle them via a column chooser UI, or programmatically\n * show/hide columns based on user preferences or screen size.\n *\n * > **Optional Enhancement:** When ReorderPlugin is also loaded, columns in the\n * > visibility panel become draggable for reordering.\n *\n * ## Installation\n *\n * ```ts\n * import { VisibilityPlugin } from '@toolbox-web/grid/plugins/visibility';\n * ```\n *\n * ## Configuration Options\n *\n * | Option | Type | Default | Description |\n * |--------|------|---------|-------------|\n * | `allowHideAll` | `boolean` | `false` | Allow hiding all columns (no minimum) |\n *\n * ## Column Configuration\n *\n * | Property | Type | Default | Description |\n * |----------|------|---------|-------------|\n * | `visible` | `boolean` | `true` | Initial visibility state |\n * | `meta.lockVisibility` | `boolean` | `false` | Prevent user from toggling |\n *\n * ## Programmatic API\n *\n * | Method | Signature | Description |\n * |--------|-----------|-------------|\n * | `hideColumn` | `(field: string) => void` | Hide a column |\n * | `showColumn` | `(field: string) => void` | Show a column |\n * | `toggleColumn` | `(field: string) => void` | Toggle visibility |\n * | `showAllColumns` | `() => void` | Show all columns |\n * | `getHiddenColumns` | `() => string[]` | Get list of hidden column fields |\n *\n * ## CSS Custom Properties\n *\n * | Property | Default | Description |\n * |----------|---------|-------------|\n * | `--tbw-visibility-hover` | `var(--tbw-color-row-hover)` | Row hover background |\n * | `--tbw-panel-padding` | `0.75em` | Panel content padding |\n * | `--tbw-panel-gap` | `0.5em` | Gap between items |\n *\n * @example Columns Hidden by Default\n * ```ts\n * import '@toolbox-web/grid';\n * import { VisibilityPlugin } from '@toolbox-web/grid/plugins/visibility';\n *\n * const grid = document.querySelector('tbw-grid');\n * grid.gridConfig = {\n * columns: [\n * { field: 'id', header: 'ID' },\n * { field: 'name', header: 'Name' },\n * { field: 'phone', header: 'Phone', visible: false }, // Hidden by default\n * { field: 'address', header: 'Address', visible: false },\n * ],\n * plugins: [new VisibilityPlugin()],\n * };\n *\n * // Toggle programmatically\n * const plugin = grid.getPlugin(VisibilityPlugin);\n * plugin.showColumn('phone');\n * ```\n *\n * @example With Drag-to-Reorder\n * ```ts\n * import { VisibilityPlugin } from '@toolbox-web/grid/plugins/visibility';\n * import { ReorderPlugin } from '@toolbox-web/grid/plugins/reorder';\n *\n * grid.gridConfig = {\n * plugins: [\n * new ReorderPlugin(), // Enables drag-drop in visibility panel\n * new VisibilityPlugin(),\n * ],\n * };\n * ```\n *\n * @see {@link VisibilityConfig} for configuration options\n * @see {@link ReorderPlugin} for drag-to-reorder integration\n *\n * @internal Extends BaseGridPlugin\n */\nexport class VisibilityPlugin extends BaseGridPlugin<VisibilityConfig> {\n /**\n * Plugin dependencies - VisibilityPlugin optionally uses ReorderPlugin for drag-drop reordering.\n *\n * When ReorderPlugin is present, columns in the visibility panel become draggable.\n * @internal\n */\n static override readonly dependencies: PluginDependency[] = [\n { name: 'reorder', required: false, reason: 'Enables drag-to-reorder columns in visibility panel' },\n ];\n\n /**\n * Plugin manifest - declares handled queries.\n * @internal\n */\n static override readonly manifest: PluginManifest = {\n queries: [\n {\n type: 'getContextMenuItems',\n description: 'Contributes \"Hide column\" item to the header context menu',\n },\n ],\n };\n\n /** @internal */\n readonly name = 'visibility';\n\n /** Tool panel ID for shell integration */\n static readonly PANEL_ID = 'columns';\n /** @internal */\n override readonly styles = styles;\n\n /** @internal */\n protected override get defaultConfig(): Partial<VisibilityConfig> {\n return {\n allowHideAll: false,\n };\n }\n\n // #region Internal State\n private columnListElement: HTMLElement | null = null;\n\n // Drag state for reorder integration\n private isDragging = false;\n private draggedField: string | null = null;\n private draggedIndex: number | null = null;\n private dropIndex: number | null = null;\n /** When dragging a group, holds the group ID; null for individual column drags. */\n private draggedGroupId: string | null = null;\n /** Fields belonging to the group currently being dragged. */\n private draggedGroupFields: string[] = [];\n\n /** Clear drag-related classes from all rows and group headers in a list. */\n private clearDragClasses(container: HTMLElement): void {\n container.querySelectorAll('.tbw-visibility-row, .tbw-visibility-group-header').forEach((r) => {\n r.classList.remove('dragging', 'drop-target', 'drop-before', 'drop-after');\n });\n }\n // #endregion\n\n // #region Lifecycle\n\n /** @internal */\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Listen for column-move events (emitted by ReorderPlugin after any reorder,\n // including header drag-drop and visibility panel drag-drop) to keep the\n // panel list in sync with the grid's column order.\n (grid as unknown as HTMLElement).addEventListener(\n 'column-move',\n () => {\n if (this.columnListElement) {\n // column-move fires BEFORE setColumnOrder runs. Defer the rebuild\n // to allow the full reorder cycle (setColumnOrder + renderHeader +\n // refreshVirtualWindow) to complete before reading the new order.\n // Use RAF to run after the current synchronous work and any\n // animation frames queued by the animation system.\n requestAnimationFrame(() => {\n if (this.columnListElement) {\n this.rebuildToggles(this.columnListElement);\n }\n });\n }\n },\n { signal: this.disconnectSignal },\n );\n }\n\n /** @internal */\n override detach(): void {\n this.columnListElement = null;\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n }\n // #endregion\n\n // #region Query Handlers\n\n /**\n * Handle inter-plugin queries.\n * Contributes a \"Hide column\" item to the header context menu.\n * @internal\n */\n override handleQuery(query: PluginQuery): unknown {\n if (query.type === 'getContextMenuItems') {\n const params = query.context as ContextMenuParams;\n if (!params.isHeader) return undefined;\n\n const column = params.column as ColumnConfig;\n if (!column?.field) return undefined;\n\n // Don't offer \"Hide\" for locked-visibility columns\n if (column.meta?.lockVisibility) return undefined;\n\n const items: HeaderContextMenuItem[] = [\n {\n id: 'visibility/hide-column',\n label: 'Hide Column',\n icon: '👁',\n order: 30,\n action: () => this.hideColumn(column.field),\n },\n ];\n\n return items;\n }\n return undefined;\n }\n // #endregion\n\n // #region Shell Integration\n\n /**\n * Register the column visibility tool panel with the shell.\n * @internal\n */\n override getToolPanel(): ToolPanelDefinition | undefined {\n return {\n id: VisibilityPlugin.PANEL_ID,\n title: 'Columns',\n icon: '☰',\n tooltip: 'Column visibility',\n order: 100, // High order so it appears last\n render: (container) => this.renderPanelContent(container),\n };\n }\n // #endregion\n\n // #region Public API\n\n /**\n * Show the visibility sidebar panel.\n * Opens the tool panel and ensures this section is expanded.\n */\n show(): void {\n this.grid.openToolPanel();\n // Ensure our section is expanded\n if (!this.grid.expandedToolPanelSections.includes(VisibilityPlugin.PANEL_ID)) {\n this.grid.toggleToolPanelSection(VisibilityPlugin.PANEL_ID);\n }\n }\n\n /**\n * Hide the visibility sidebar panel.\n */\n hide(): void {\n this.grid.closeToolPanel();\n }\n\n /**\n * Toggle the visibility sidebar panel section.\n */\n toggle(): void {\n // If tool panel is closed, open it first\n if (!this.grid.isToolPanelOpen) {\n this.grid.openToolPanel();\n }\n this.grid.toggleToolPanelSection(VisibilityPlugin.PANEL_ID);\n }\n\n /**\n * Check if a specific column is visible.\n * Delegates to grid.isColumnVisible().\n * @param field - The field name to check\n * @returns True if the column is visible\n */\n isColumnVisible(field: string): boolean {\n return this.grid.isColumnVisible(field);\n }\n\n /**\n * Set visibility for a specific column.\n * Delegates to grid.setColumnVisible().\n * @param field - The field name of the column\n * @param visible - Whether the column should be visible\n */\n setColumnVisible(field: string, visible: boolean): void {\n this.grid.setColumnVisible(field, visible);\n }\n\n /**\n * Get list of all visible column fields.\n * @returns Array of visible field names\n */\n getVisibleColumns(): string[] {\n return this.grid\n .getAllColumns()\n .filter((c) => c.visible)\n .map((c) => c.field);\n }\n\n /**\n * Get list of all hidden column fields.\n * @returns Array of hidden field names\n */\n getHiddenColumns(): string[] {\n return this.grid\n .getAllColumns()\n .filter((c) => !c.visible)\n .map((c) => c.field);\n }\n\n /**\n * Show all columns.\n * Delegates to grid.showAllColumns().\n */\n showAll(): void {\n this.grid.showAllColumns();\n }\n\n /**\n * Toggle visibility for a specific column.\n * Delegates to grid.toggleColumnVisibility().\n * @param field - The field name of the column\n */\n toggleColumn(field: string): void {\n this.grid.toggleColumnVisibility(field);\n }\n\n /**\n * Show a specific column.\n * Delegates to grid.setColumnVisible().\n * @param field - The field name of the column to show\n */\n showColumn(field: string): void {\n this.setColumnVisible(field, true);\n }\n\n /**\n * Hide a specific column.\n * Delegates to grid.setColumnVisible().\n * @param field - The field name of the column to hide\n */\n hideColumn(field: string): void {\n this.setColumnVisible(field, false);\n }\n\n /**\n * Get all columns with their visibility status.\n * Useful for building visibility UI.\n * @returns Array of column info with visibility status\n */\n getAllColumns(): Array<{\n field: string;\n header: string;\n visible: boolean;\n lockVisible?: boolean;\n utility?: boolean;\n }> {\n return this.grid.getAllColumns();\n }\n\n /**\n * Check if the sidebar panel is currently open.\n * @returns True if the panel section is expanded\n */\n isPanelVisible(): boolean {\n return this.grid.isToolPanelOpen && this.grid.expandedToolPanelSections.includes(VisibilityPlugin.PANEL_ID);\n }\n // #endregion\n\n // #region Private Methods\n\n /**\n * Render the panel content into the shell's tool panel container.\n * Returns a cleanup function.\n */\n private renderPanelContent(container: HTMLElement): (() => void) | void {\n // Create content wrapper\n const wrapper = document.createElement('div');\n wrapper.className = 'tbw-visibility-content';\n\n // Column list container\n const columnList = document.createElement('div');\n columnList.className = 'tbw-visibility-list';\n wrapper.appendChild(columnList);\n\n // Show all button\n const showAllBtn = document.createElement('button');\n showAllBtn.className = 'tbw-visibility-show-all';\n showAllBtn.textContent = 'Show All';\n showAllBtn.addEventListener('click', () => {\n this.grid.showAllColumns();\n this.rebuildToggles(columnList);\n });\n wrapper.appendChild(showAllBtn);\n\n // Store reference\n this.columnListElement = columnList;\n\n // Build initial toggles\n this.rebuildToggles(columnList);\n\n // Append to container\n container.appendChild(wrapper);\n\n // Return cleanup function\n return () => {\n this.columnListElement = null;\n wrapper.remove();\n };\n }\n\n /**\n * Check if a reorder plugin is present (by name to avoid static import).\n */\n private hasReorderPlugin(): boolean {\n const plugin = this.grid?.getPluginByName?.('reorder');\n // Duck-type check - just verify the plugin exists and has a moveColumn method\n return !!(plugin && typeof (plugin as { moveColumn?: unknown }).moveColumn === 'function');\n }\n\n /**\n * Build the column toggle checkboxes.\n * When GroupingColumnsPlugin is present, renders columns under collapsible group headers.\n * When a reorder plugin is present, adds drag handles for reordering.\n */\n private rebuildToggles(columnList: HTMLElement): void {\n const reorderEnabled = this.hasReorderPlugin();\n\n columnList.innerHTML = '';\n\n // getAllColumns() returns columns in their effective display order\n // Filter out utility columns (e.g., expander column) as they're internal\n const allColumns = this.grid.getAllColumns().filter((c) => !c.utility);\n\n // Query for column grouping info from GroupingColumnsPlugin (or any responder)\n const groupResults = this.grid.query<ColumnGroupInfo[]>('getColumnGrouping');\n const groups: ColumnGroupInfo[] = groupResults?.flat().filter((g) => g && g.fields.length > 0) ?? [];\n\n if (groups.length === 0) {\n // No grouping — render flat list (original behavior)\n this.renderFlatColumnList(allColumns, reorderEnabled, columnList);\n return;\n }\n\n // Build field → group lookup\n const fieldToGroup = new Map<string, ColumnGroupInfo>();\n for (const group of groups) {\n for (const field of group.fields) fieldToGroup.set(field, group);\n }\n\n // Walk columns in display order, interleaving groups and ungrouped columns.\n // When we encounter the first column of a group, render the entire group section.\n const renderedGroups = new Set<string>();\n\n for (const col of allColumns) {\n const group = fieldToGroup.get(col.field);\n\n if (group) {\n // Column belongs to a group — render entire group section at first encounter\n if (!renderedGroups.has(group.id)) {\n renderedGroups.add(group.id);\n // Filter allColumns (which is in display order) to group members.\n // This preserves the current column order after reordering,\n // rather than using group.fields which may be in static/original order.\n const groupFieldSet = new Set(group.fields);\n const groupCols = allColumns.filter((c) => groupFieldSet.has(c.field));\n if (groupCols.length > 0) {\n this.renderGroupSection(group, groupCols, reorderEnabled, columnList);\n }\n }\n // Subsequent columns of the same group are already rendered — skip\n } else {\n // Ungrouped column — render as individual row at its natural position\n const fullIndex = allColumns.indexOf(col);\n columnList.appendChild(this.createColumnRow(col, fullIndex, reorderEnabled, columnList));\n }\n }\n }\n\n /**\n * Render a group section with header checkbox and indented column rows.\n */\n private renderGroupSection(\n group: ColumnGroupInfo,\n columns: ReturnType<typeof this.grid.getAllColumns>,\n reorderEnabled: boolean,\n container: HTMLElement,\n ): void {\n // Group header row\n const header = document.createElement('div');\n header.className = 'tbw-visibility-group-header';\n header.setAttribute('data-group-id', group.id);\n\n // Make group header draggable when reorder is enabled\n if (reorderEnabled) {\n header.draggable = true;\n header.classList.add('reorderable');\n this.setupGroupDragListeners(header, group, container);\n }\n\n const headerLabel = document.createElement('label');\n headerLabel.className = 'tbw-visibility-label';\n\n const groupCheckbox = document.createElement('input');\n groupCheckbox.type = 'checkbox';\n\n // Calculate tri-state: all visible, all hidden, or mixed\n const visibleCount = columns.filter((c) => c.visible).length;\n const allLocked = columns.every((c) => c.lockVisible);\n if (visibleCount === columns.length) {\n groupCheckbox.checked = true;\n groupCheckbox.indeterminate = false;\n } else if (visibleCount === 0) {\n groupCheckbox.checked = false;\n groupCheckbox.indeterminate = false;\n } else {\n groupCheckbox.checked = false;\n groupCheckbox.indeterminate = true;\n }\n groupCheckbox.disabled = allLocked;\n\n // Toggle all columns in group\n groupCheckbox.addEventListener('change', () => {\n const newVisible = groupCheckbox.checked;\n for (const col of columns) {\n if (col.lockVisible) continue;\n this.grid.setColumnVisible(col.field, newVisible);\n }\n setTimeout(() => this.rebuildToggles(container), 0);\n });\n\n const headerText = document.createElement('span');\n headerText.textContent = group.label;\n\n headerLabel.appendChild(groupCheckbox);\n headerLabel.appendChild(headerText);\n header.appendChild(headerLabel);\n\n // Add drag handle icon for group if reorderable\n if (reorderEnabled) {\n const handle = document.createElement('span');\n handle.className = 'tbw-visibility-handle';\n this.setIcon(handle, this.resolveIcon('dragHandle'));\n handle.title = 'Drag to reorder group';\n // Insert handle before the label\n header.insertBefore(handle, headerLabel);\n }\n\n container.appendChild(header);\n\n // Render indented column rows\n const allColumnsFullList = this.grid.getAllColumns().filter((c) => !c.utility);\n for (const col of columns) {\n const fullIndex = allColumnsFullList.findIndex((c) => c.field === col.field);\n const row = this.createColumnRow(col, fullIndex, reorderEnabled, container);\n row.classList.add('tbw-visibility-row--grouped');\n container.appendChild(row);\n }\n }\n\n /**\n * Render a flat (ungrouped) list of column rows.\n */\n private renderFlatColumnList(\n columns: ReturnType<typeof this.grid.getAllColumns>,\n reorderEnabled: boolean,\n container: HTMLElement,\n ): void {\n const allColumnsFullList = this.grid.getAllColumns().filter((c) => !c.utility);\n for (const col of columns) {\n const fullIndex = allColumnsFullList.findIndex((c) => c.field === col.field);\n container.appendChild(this.createColumnRow(col, fullIndex, reorderEnabled, container));\n }\n }\n\n /**\n * Create a single column visibility row element.\n */\n private createColumnRow(\n col: ReturnType<typeof this.grid.getAllColumns>[number],\n index: number,\n reorderEnabled: boolean,\n columnList: HTMLElement,\n ): HTMLElement {\n const label = col.header || col.field;\n\n const row = document.createElement('div');\n row.className = col.lockVisible ? 'tbw-visibility-row locked' : 'tbw-visibility-row';\n row.setAttribute('data-field', col.field);\n row.setAttribute('data-index', String(index));\n\n // Add drag handle if reorder is enabled\n if (reorderEnabled && canMoveColumn(col as unknown as ColumnConfig)) {\n row.draggable = true;\n row.classList.add('reorderable');\n this.setupDragListeners(row, col.field, index, columnList);\n }\n\n const labelWrapper = document.createElement('label');\n labelWrapper.className = 'tbw-visibility-label';\n\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.checked = col.visible;\n checkbox.disabled = col.lockVisible ?? false;\n checkbox.addEventListener('change', () => {\n this.grid.toggleColumnVisibility(col.field);\n // Refresh after toggle (grid may re-render)\n setTimeout(() => this.rebuildToggles(columnList), 0);\n });\n\n const text = document.createElement('span');\n text.textContent = label;\n\n labelWrapper.appendChild(checkbox);\n labelWrapper.appendChild(text);\n\n // Add drag handle icon if reorderable\n if (reorderEnabled && canMoveColumn(col as unknown as ColumnConfig)) {\n const handle = document.createElement('span');\n handle.className = 'tbw-visibility-handle';\n this.setIcon(handle, this.resolveIcon('dragHandle'));\n handle.title = 'Drag to reorder';\n row.appendChild(handle);\n }\n\n row.appendChild(labelWrapper);\n return row;\n }\n\n /**\n * Set up drag-and-drop listeners for a group header row.\n * Dragging a group moves all its member columns as a block.\n */\n private setupGroupDragListeners(header: HTMLElement, group: ColumnGroupInfo, columnList: HTMLElement): void {\n header.addEventListener('dragstart', (e: DragEvent) => {\n this.isDragging = true;\n this.draggedGroupId = group.id;\n this.draggedGroupFields = [...group.fields];\n // Use first field as representative for dataTransfer\n this.draggedField = null;\n this.draggedIndex = null;\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', `group:${group.id}`);\n }\n\n // Mark entire group (header + children) as dragging\n header.classList.add('dragging');\n columnList.querySelectorAll(`.tbw-visibility-row--grouped`).forEach((row) => {\n const field = row.getAttribute('data-field');\n if (field && this.draggedGroupFields.includes(field)) {\n row.classList.add('dragging');\n }\n });\n });\n\n header.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedGroupId = null;\n this.draggedGroupFields = [];\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.clearDragClasses(columnList);\n });\n\n // Group headers are also drop targets for other groups\n header.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n // Can't drop onto self\n if (this.draggedGroupId === group.id) return;\n // Can't drop individual columns onto group headers\n if (!this.draggedGroupId) return;\n\n const rect = header.getBoundingClientRect();\n const midY = rect.top + rect.height / 2;\n const before = e.clientY < midY;\n\n this.clearDragClasses(columnList);\n header.classList.add('drop-target');\n header.classList.toggle('drop-before', before);\n header.classList.toggle('drop-after', !before);\n });\n\n header.addEventListener('dragleave', () => {\n header.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n header.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging || !this.draggedGroupId || this.draggedGroupId === group.id) return;\n\n const rect = header.getBoundingClientRect();\n const before = e.clientY < rect.top + rect.height / 2;\n\n this.executeGroupDrop(this.draggedGroupFields, group.fields, before, columnList);\n });\n }\n\n /**\n * Execute a group drop — move the dragged group's fields as a block\n * to the position relative to the target group or column.\n */\n private executeGroupDrop(\n draggedFields: string[],\n targetFields: string[],\n before: boolean,\n columnList: HTMLElement,\n ): void {\n const allColumns = this.grid.getAllColumns();\n const currentOrder = allColumns.map((c) => c.field);\n\n // Remove dragged fields from current order\n const remaining = currentOrder.filter((f) => !draggedFields.includes(f));\n\n // Find insertion point relative to target\n // Use the first field of target group if inserting before, last if after\n const anchorField = before ? targetFields[0] : targetFields[targetFields.length - 1];\n const insertAt = remaining.indexOf(anchorField);\n if (insertAt === -1) return;\n\n // Insert the dragged group block at the correct position\n const insertIndex = before ? insertAt : insertAt + 1;\n\n // Preserve the dragged fields' original relative order\n const draggedInOrder = currentOrder.filter((f) => draggedFields.includes(f));\n remaining.splice(insertIndex, 0, ...draggedInOrder);\n\n this.grid.setColumnOrder(remaining);\n // Panel rebuild handled by column-move listener — but since we're calling\n // setColumnOrder directly (not through ReorderPlugin.moveColumn), we need\n // to manually trigger a rebuild.\n requestAnimationFrame(() => {\n if (this.columnListElement) {\n this.rebuildToggles(this.columnListElement);\n }\n });\n }\n\n /**\n * Set up drag-and-drop event listeners for a row.\n * On drop, emits a 'column-reorder-request' event for other plugins to handle.\n */\n private setupDragListeners(row: HTMLElement, field: string, index: number, columnList: HTMLElement): void {\n row.addEventListener('dragstart', (e: DragEvent) => {\n this.isDragging = true;\n this.draggedField = field;\n this.draggedIndex = index;\n // Clear any stale group drag state\n this.draggedGroupId = null;\n this.draggedGroupFields = [];\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', field);\n }\n\n row.classList.add('dragging');\n });\n\n row.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n this.clearDragClasses(columnList);\n });\n\n row.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging) return;\n\n // If dragging a group, only allow drop on ungrouped rows\n if (this.draggedGroupId) {\n if (row.classList.contains('tbw-visibility-row--grouped')) return;\n } else if (this.draggedField === field) {\n return;\n }\n\n const rect = row.getBoundingClientRect();\n const midY = rect.top + rect.height / 2;\n\n this.dropIndex = e.clientY < midY ? index : index + 1;\n\n // Clear other highlights\n this.clearDragClasses(columnList);\n // Re-mark dragged elements\n if (this.draggedGroupId) {\n columnList\n .querySelector(`.tbw-visibility-group-header[data-group-id=\"${this.draggedGroupId}\"]`)\n ?.classList.add('dragging');\n columnList.querySelectorAll('.tbw-visibility-row--grouped').forEach((r) => {\n const f = r.getAttribute('data-field');\n if (f && this.draggedGroupFields.includes(f)) r.classList.add('dragging');\n });\n } else if (this.draggedField) {\n columnList.querySelector(`.tbw-visibility-row[data-field=\"${this.draggedField}\"]`)?.classList.add('dragging');\n }\n\n row.classList.add('drop-target');\n row.classList.toggle('drop-before', e.clientY < midY);\n row.classList.toggle('drop-after', e.clientY >= midY);\n });\n\n row.addEventListener('dragleave', () => {\n row.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n row.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n\n if (!this.isDragging) return;\n\n // Group drop onto an ungrouped row\n if (this.draggedGroupId && this.draggedGroupFields.length > 0) {\n if (row.classList.contains('tbw-visibility-row--grouped')) return;\n const rect = row.getBoundingClientRect();\n const before = e.clientY < rect.top + rect.height / 2;\n this.executeGroupDrop(this.draggedGroupFields, [field], before, columnList);\n return;\n }\n\n // Individual column drop\n const draggedField = this.draggedField;\n const draggedIndex = this.draggedIndex;\n const dropIndex = this.dropIndex;\n\n if (draggedField === null || draggedIndex === null || dropIndex === null) {\n return;\n }\n\n // Calculate the effective target index (in the filtered non-utility list)\n const effectiveToIndex = dropIndex > draggedIndex ? dropIndex - 1 : dropIndex;\n\n if (effectiveToIndex !== draggedIndex) {\n // Convert from non-utility index to full column order index\n // by counting how many utility columns come before the target position\n const allColumns = this.grid.getAllColumns();\n const nonUtilityColumns = allColumns.filter((c) => !c.utility);\n\n // Find the target field at effectiveToIndex in non-utility list\n const targetField = nonUtilityColumns[effectiveToIndex]?.field;\n // Find its actual index in the full column order\n const fullOrderToIndex = targetField ? allColumns.findIndex((c) => c.field === targetField) : allColumns.length;\n\n // Emit a request event - other plugins (like ReorderPlugin) can listen and handle\n const detail: ColumnReorderRequestDetail = {\n field: draggedField,\n fromIndex: draggedIndex, // Not used by ReorderPlugin, just for info\n toIndex: fullOrderToIndex,\n };\n this.emit<ColumnReorderRequestDetail>('column-reorder-request', detail);\n // Panel rebuild is handled by the column-move listener in attach()\n }\n });\n }\n // #endregion\n}\n"],"names":["resolveColumns","columns","fields","onlyVisible","result","c","fieldSet","resolveRows","rows","indices","a","b","i","r","formatValueAsText","value","copyToClipboard","text","err","textarea","success","parseClipboardText","config","delimiter","newline","normalizedText","currentRow","currentCell","inQuotes","char","readFromClipboard","defaultPasteHandler","detail","grid","pastedRows","target","currentRows","allFields","col","editableMap","newRows","maxPasteRow","rowData","rowOffset","targetRowIndex","emptyRow","field","cellValue","colOffset","ClipboardPlugin","BaseGridPlugin","e","#handleNativePaste","event","#handleCopy","selection","#getSelection","focused","#getFocusedCellFromDOM","parsed","firstRange","targetRow","targetCol","bounds","maxCol","column","pasteWidth","#applyPasteHandler","pasteHandler","#resolveData","options","range","minCol","minRow","maxRow","row","#buildText","includeHeaders","processCell","lines","cells","cell","rowIndexStr","DEFAULT_COLUMN_WIDTH","parseColumnWidth","width","numeric","getColumnWidths","computeColumnOffsets","offsets","offset","computeTotalWidth","sum","getVisibleColumnRange","scrollLeft","viewportWidth","columnOffsets","columnWidths","overscan","columnCount","startCol","binarySearchFirstVisible","rightEdge","endCol","visibleColumns","low","high","mid","shouldVirtualize","threshold","autoEnable","ColumnVirtualizationPlugin","#cleanupStyles","gridEl","headerRow","rowsContainer","rowsBody","fullColumns","isVirtualized","viewport","leftPadding","bodyRows","columnIndex","buildMenuItems","items","params","item","collapseSeparators","isItemDisabled","createMenuElement","onAction","submenuArrow","DEFAULT_GRID_ICONS","menu","hasAnyIcon","separator","menuItem","disabled","icon","label","shortcut","key","kbd","arrow","subMenuItems","subMenu","positionMenu","x","y","menuRect","viewportHeight","left","top","QUERY_GET_CONTEXT_MENU_ITEMS","globalClickHandler","globalKeydownHandler","globalStyleSheet","globalHandlerRefCount","defaultItems","ContextMenuPlugin","rowIndex","currentSelection","menuElement","computed","styles","colorScheme","varName","existing","contextMenuStyles","responses","response","lastGroup","group","container","header","colIndex","selectedRows","pluginItems","converted","fullParams","resolveOptions","raw","createNumberEditor","ctx","input","commit","createBooleanEditor","createDateEditor","createSelectEditor","select","emptyOpt","opt","o","values","createTextEditor","inputVal","defaultEditorFor","resolveEditor","gridTypeDefaults","adapter","appDefault","isSafePropertyKey","incrementEditingCount","rowEl","count","clearEditingState","getInputValue","originalValue","noopUpdateRow","_changes","wireEditorInputs","editorHost","EditingPlugin","v","#isGridMode","#activeEditRow","#activeEditCol","#rowEditSnapshots","#changedRowIds","#editingCells","#editorValueCallbacks","#pendingFocusRestore","#pendingRowAnimation","#invalidCells","#gridModeInputFocused","#gridModeEditLocked","#singleCellEdit","signal","internalGrid","silent","#exitRowEdit","cb","FOCUSABLE_EDITOR_SELECTOR","related","activeEl","query","editOn","isDoubleClick","ensureCellVisible","forward","#handleTabNavigation","focusRow","focusCol","newValue","#commitCellValue","#focusCurrentCellEditor","cellEl","activateEvent","legacyEvent","typeDefaults","typeEditorParams","#restoreCellFocus","cellKey","rowStr","colStr","#injectEditor","context","cellElement","id","rowId","message","rowInvalids","#syncInvalidCellAttribute","entries","_","invalid","ids","#syncGridEditState","#beginCellEdit","#startRowEdit","targetCell","editor","editableCols","nextIdx","nextRow","revert","snapshot","current","val","k","changedThisSession","#hasRowChanged","changed","cancelled","callbackKey","oldValue","firstTime","updateRow","changes","invalidWasSet","setInvalid","skipFocus","editFinalized","currentRowData","cancel","colInternal","tplHolder","editorSpec","callbacks","newVal","onValueChange","#renderTemplateEditor","el","produced","placeholder","clone","compiledEditor","node","_m","g","evt","snapshotObj","currentObj","allKeys","rowIdx","colIdx","formatCsvValue","quote","str","buildCsv","bom","processed","downloadBlob","blob","fileName","url","link","downloadCsv","content","escapeXml","buildExcelXml","xml","type","displayValue","downloadExcel","finalName","ExportPlugin","format","selectionState","jsonData","obj","EXPANDER_COLUMN_FIELD","EXPANDER_COLUMN_WIDTH","isExpanderColumn","isUtilityColumn","findExpanderColumn","createExpanderColumnConfig","pluginName","toNumeric","n","matchesFilter","filter","caseSensitive","rawValue","stringValue","compareValue","filterValue","filterRows","filters","f","computeFilterCacheKey","getUniqueValues","FilteringPlugin","cssValue","fieldFiltered","hasAnyFilter","filterList","newCacheKey","inputSpot","inputUnchanged","hasFilter","filterBtn","wasActive","iconName","resizeHandle","fullFilter","panel","className","theme","style","filterPanelStyles","buttonEl","uniqueValues","excludedSet","currentSearchText","excluded","operator","valueTo","usedCustomRenderer","typeDefault","columnType","anchorEl","panelRect","anchorRect","rect","excludedValues","itemHeight","searchContainer","searchInput","actionsRow","selectAllLabel","selectAllCheckbox","selectAllText","updateSelectAllState","checkState","allChecked","noneChecked","newState","renderVisibleItems","valuesContainer","spacer","contentContainer","filteredValues","createItem","index","strValue","checkbox","totalItems","scrollTop","shouldBypassVirtualization","idx","window","computeVirtualWindow","renderValues","filterText","compareFilter","noMatch","debounceTimer","buttonRow","applyBtn","isChecked","original","clearBtn","filterParams","editorParams","toNumber","fallback","num","numericValues","dataMin","dataMax","min","max","step","currentFilter","currentMin","currentMax","rangeContainer","minGroup","minLabel","minInput","maxGroup","maxLabel","maxInput","sliderContainer","sliderTrack","sliderFill","minSlider","maxSlider","updateFill","minVal","maxVal","leftPercent","rightPercent","dateValues","d","formatDateForInput","date","parseFilterParam","minDate","maxDate","currentFrom","currentTo","isBlankFilter","fromGroup","fromLabel","fromInput","toGroup","toLabel","toInput","blankRow","blankCheckbox","blankLabel","toggleDateInputs","from","to","handleResult","filterModel","state","computeColumnGroups","explicitMap","groupsOrdered","pushImplicit","startIdx","cols","prev","run","runStart","applyGroupedHeaderCellClasses","headerRowEl","groups","fieldToGroup","headerCells","gid","last","buildGroupHeaderRow","groupRow","firstGroupCol","startIndex","isImplicit","hasColumnGroups","GroupingColumnsPlugin","#groupEndFields","#onColumnMove","columnOrder","#isGroupContiguous","#flashHeaderCell","#recomputeGroupEndFields","lastGroupEndField","#findLastGroupEndField","groupFields","j","headerCell","#getStableColumnGrouping","columnGroups","allCols","gId","gLabel","groupMap","displayOrder","orderIndex","processedColumns","groupInfo","lastCol","existingGroupRow","finalColumns","gi","groupId","buildGroupedRowModel","expanded","initialExpanded","groupOn","root","path","parent","rawVal","depthIdx","seg","composite","effectiveExpanded","flat","visit","isExpanded","toggleGroupExpansion","expandedKeys","newSet","expandAllGroups","keys","collapseAllGroups","resolveDefaultExpanded","allGroupKeys","getGroupKeys","getGroupRowCount","GroupingRowsPlugin","_index","initialBuild","grouped","currentVisibleKeys","_rowIndex","toggleExpand","handleToggle","body","animClass","btn","depth","aggregators","groupRows","aggregatorEntries","aggregatesContainer","aggRef","runAggregator","aggSpan","colHeader","gridTemplate","toggleRendered","firstColAgg","aggResult","isExpanding","newKeys","existingKey","existingGroup","fn","toggleDetailRow","expandedRows","newExpanded","expandDetailRow","collapseDetailRow","isDetailExpanded","createDetailElement","renderer","detailRow","detailCell","MasterDetailPlugin","detailEl","gridWithAdapter","adapterRenderer","animation","showExpandColumn","expandOnRowClick","collapseOnClickOutside","heightAttr","configUpdates","templateHTML","evaluated","evalTemplateString","sanitizeHTML","measured","measureOnce","#measureAndCacheDetailHeight","onComplete","cleanup","height","previousHeight","cachedHeight","expanderCol","renderCtx","toggle","#syncDetailRows","gridInternal","rowPool","vStart","vEnd","visibleStart","visibleEnd","visibleRowMap","poolLen","dataRows","firstCell","isStillExpanded","isRowVisible","existingDetail","shouldAnimate","totalHeight","beforeRowIndex","baseHeight","detailHeight","start","rowHeight","positionCache","minStart","expandedIndices","cumulativeExtraHeight","actualRowTop","actualDetailBottom","currentRenderer","applySorts","sorts","sort","comparator","defaultComparator","aVal","bVal","toggleSort","shiftKey","maxColumns","s","getSortIndex","sortModel","getSortDirection","MultiSortPlugin","showIndex","sortIndex","sortDir","indicator","insertBefore","badge","model","existingIndex","newEntry","getColumnPinned","resolveStickyPosition","position","direction","resolveInlinePosition","isResolvedLeft","pinned","isResolvedRight","getLeftStickyColumns","getRightStickyColumns","hasStickyColumns","applyStickyOffsets","host","getDirection","right","reorderColumnsForPinning","middle","clearStickyOffsets","QUERY_CAN_MOVE_COLUMN","PinnedColumnsPlugin","#originalColumnOrder","isPinned","currentColumns","currentIndex","updated","copy","remaining","originalIndex","insertIndex","focusedCell","stickyLeftCells","stickyRightCells","skipScroll","isAggregatorConfig","def","createInfoBarElement","pinnedRows","center","rowCount","filteredCount","selectedCount","panelEl","renderCustomPanel","createAggregationContainer","renderAggregationRows","globalFullWidth","rowConfig","renderFullWidthAggregationRow","renderPerColumnAggregationRow","labelValue","labelSpan","renderInlineAggregates","formatter","resolveAggregatedValue","aggDef","aggFn","getAggregator","staticVal","hasAggregators","hasCells","span","buildContext","filterState","PinnedRowsPlugin","aggregationRows","topRows","bottomRows","hasInfoContent","hasBottomInfoBar","needsFooter","newInfoBar","p","getPivotAggregator","getValueAggregator","validatePivotConfig","errors","createValueKey","columnValues","valueField","buildPivot","rowGroupFields","columnGroupFields","valueFields","columnKeys","getUniqueColumnKeys","pivotRows","buildHierarchicalPivotRows","totals","calculateTotals","grandTotal","columnFields","groupByField","parentKey","aggregateValues","total","calculateRowTotal","currentField","remainingFields","hasChildren","groupValue","rowKey","children","colKey","vf","nums","aggregator","aggregatedResult","valueKey","sumRows","flattenPivotRows","defaultExpanded","flatten","child","getAllGroupKeys","collectKeys","AGG_FUNCS","renderPivotPanel","isActive","controller","wrapper","createSection","createOptionsPanel","createFieldZone","createValuesZone","createAvailableFieldsZone","title","contentFactory","section","zoneType","zone","currentFields","createFieldChip","chip","fieldInfo","removeBtn","currentValues","createValueChip","labelWrapper","aggSelect","aggFunc","option","usedFields","availableFields","empty","createCheckbox","checked","onChange","renderPivotGroupRow","indent","renderPivotLeafRow","renderPivotGrandTotalRow","PivotPlugin","indentWidth","flatRows","pr","pivotColumns","rowGroupHeaders","valueHeader","pivotRow","iconKey","grandTotalRow","enabled","targetZone","fieldIndex","ISOLATION_STYLE_ID","createIsolationStylesheet","gridId","orientation","printGridIsolated","gridElement","isolationStyle","resolve","onAfterPrint","DEFAULT_CONFIG","PrintPlugin","#printing","#savedHiddenColumns","#savedVirtualization","#savedRows","#printHeader","#printFooter","#appliedScale","#internalGrid","originalRowCount","limitApplied","limitInfo","startTime","#hidePrintColumns","#addPrintHeader","#disableVirtualization","#printInIsolatedWindow","#triggerPrint","error","#cleanup","titleEl","timestampEl","totalRows","#restorePrintColumns","wasVisible","#toolbarRegistered","#registerToolbarButton","button","canMoveColumn","meta","moveColumn","fromIndex","toIndex","removed","ReorderPlugin","h","headerEl","midX","draggedField","draggedIndex","dropIndex","effectiveToIndex","currentOrder","newOrder","targetColumn","order","originalOrder","positions","oldPositions","deltas","oldLeft","deltaX","duration","applyChange","movedFields","newLeft","ResponsivePlugin","#resizeObserver","#isResponsive","#debounceTimer","#warnedAboutMissingBreakpoint","#currentWidth","#hiddenColumnSet","#valueOnlyColumnSet","#activeBreakpoint","#sortedBreakpoints","#applyResponsiveState","#checkBreakpoint","#parseLightDomCard","#buildHiddenColumnSets","cardEl","breakpointAttr","cardRowHeightAttr","hiddenColumnsAttr","hideHeaderAttr","debounceMsAttr","breakpoint","debounceMs","sanitized","hiddenColumns","#measureCardHeightFromDOM","hasHiddenColumns","hasValueOnlyColumns","#checkMultiBreakpoint","shouldBeResponsive","newActiveBreakpoint","bp","#originalRowHeight","animate","scrollArea","#measuredCardHeight","#measuredGroupRowHeight","#lastCardRowCount","cardContent","cardHeight","#getCardHeight","configHeight","#getGroupRowHeight","#hasGroupRows","#countRowTypes","groupCount","cardCount","groupHeight","groupExtra","cardExtra","groupHeightDiff","cardHeightDiff","groupsBefore","cardsBefore","maxIndex","#countCardRows","#pendingRefresh","needsRefresh","hasGroups","currentCardRowCount","cardRow","ROW_DRAG_HANDLE_FIELD","RowReorderPlugin","dragHandleColumn","handle","handleEl","targetIndex","midY","isBefore","movedRow","source","minIndex","rowsToAnimate","newRowIndex","oldIndex","oldTop","newTop","deltaY","normalizeRange","toPublicRange","normalized","toPublicRanges","ranges","isCellInRange","isCellInAnyRange","getCellsInRange","getAllCellsInRanges","cellMap","createRangeFromAnchor","anchor","rangesEqual","normA","normB","CHECKBOX_COLUMN_FIELD","buildSelectionEvent","mode","colCount","sorted","end","SelectionPlugin","isSelectable","originalEvent","triggerOn","isUtility","#buildEvent","ctrlKey","isCheckbox","newRange","currentRange","isNavKey","isTabKey","shouldExtend","firstDataCol","_event","checkboxCol","#createCheckboxColumn","expanderIdx","insertAt","#updateCheckboxStates","getRowIndexFromCell","headerCheckbox","selectableCount","allSelected","someSelected","#syncSelectionToFocus","cur","#applySelectionClasses","hasSelectableCallback","allRows","clearCellFocus","normalizedRanges","isInSelection","currentCol","allRange","getBlockNumber","blockSize","getBlockRange","blockNumber","getRequiredBlocks","startRow","endRow","startBlock","endBlock","blocks","loadBlock","dataSource","getRowFromCache","loadedBlocks","block","indexInBlock","SCROLL_DEBOUNCE_MS","ServerSidePlugin","gridRef","requiredBlocks","blockNum","cached","generateRowKey","expandAll","childrenField","childKeys","collapseAll","getPathToKey","targetKey","childPath","expandToKey","existingExpanded","detectTreeStructure","inferChildrenField","commonArrayFields","TreePlugin","treeRows","data","currentKeys","stableKey","dir","firstCol","originalRenderer","getConfig","setIcon","resolveIcon","wrappedRenderer","showExpandIcons","treeRow","flatRow","pushAction","action","maxSize","undoStack","undo","redo","redoStack","canUndo","canRedo","clearHistory","createEditAction","UndoRedoPlugin","isUndo","isRedo","VisibilityPlugin","visible","columnList","showAllBtn","plugin","reorderEnabled","allColumns","renderedGroups","groupFieldSet","groupCols","fullIndex","headerLabel","groupCheckbox","visibleCount","allLocked","newVisible","headerText","allColumnsFullList","before","draggedFields","targetFields","anchorField","draggedInOrder","targetField","fullOrderToIndex"],"mappings":"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"}
|