@toolbox-web/grid 1.26.1 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all.js +2 -2
- package/all.js.map +1 -1
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/lib/core/grid.d.ts +64 -1
- package/lib/core/internal/diagnostics.d.ts +4 -3
- package/lib/core/internal/row-manager.d.ts +3 -1
- package/lib/core/plugin/base-plugin.d.ts +2 -2
- package/lib/core/types.d.ts +59 -3
- package/lib/features/registry.js.map +1 -1
- package/lib/plugins/clipboard/ClipboardPlugin.d.ts +1 -1
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/ContextMenuPlugin.d.ts +24 -5
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/editing/EditingPlugin.d.ts +1 -1
- package/lib/plugins/editing/index.js.map +1 -1
- package/lib/plugins/export/ExportPlugin.d.ts +1 -1
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/index.d.ts +2 -2
- package/lib/plugins/filtering/index.js +1 -1
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/GroupingColumnsPlugin.d.ts +2 -2
- package/lib/plugins/grouping-columns/grouping-columns.d.ts +18 -3
- package/lib/plugins/grouping-columns/index.d.ts +0 -1
- package/lib/plugins/grouping-columns/index.js +1 -1
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-columns/types.d.ts +1 -7
- package/lib/plugins/grouping-rows/index.d.ts +2 -1
- package/lib/plugins/grouping-rows/index.js +1 -1
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/MasterDetailPlugin.d.ts +2 -0
- package/lib/plugins/master-detail/index.js +1 -1
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/master-detail/types.d.ts +20 -1
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/PinnedColumnsPlugin.d.ts +8 -1
- package/lib/plugins/pinned-columns/index.js +1 -1
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-columns/pinned-columns.d.ts +11 -1
- package/lib/plugins/pinned-rows/index.d.ts +1 -1
- package/lib/plugins/pinned-rows/index.js +1 -1
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pinned-rows/types.d.ts +10 -3
- package/lib/plugins/pivot/PivotPlugin.d.ts +107 -1
- package/lib/plugins/pivot/index.d.ts +2 -1
- package/lib/plugins/pivot/index.js +1 -1
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/print/index.js.map +1 -1
- package/lib/plugins/print/types.d.ts +0 -3
- package/lib/plugins/reorder-columns/ReorderPlugin.d.ts +19 -2
- package/lib/plugins/reorder-columns/index.js +1 -1
- package/lib/plugins/reorder-columns/index.js.map +1 -1
- package/lib/plugins/reorder-rows/index.d.ts +1 -1
- package/lib/plugins/reorder-rows/index.js.map +1 -1
- package/lib/plugins/responsive/ResponsivePlugin.d.ts +1 -1
- package/lib/plugins/responsive/index.js +1 -1
- package/lib/plugins/responsive/index.js.map +1 -1
- package/lib/plugins/selection/SelectionPlugin.d.ts +1 -1
- package/lib/plugins/selection/index.js +1 -1
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/selection/types.d.ts +3 -3
- package/lib/plugins/server-side/ServerSidePlugin.d.ts +6 -1
- package/lib/plugins/server-side/index.js +1 -1
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/tree/TreePlugin.d.ts +116 -0
- package/lib/plugins/tree/index.d.ts +1 -1
- package/lib/plugins/tree/index.js +1 -1
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/tree/types.d.ts +16 -1
- package/lib/plugins/undo-redo/UndoRedoPlugin.d.ts +1 -1
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/undo-redo/types.d.ts +15 -3
- package/lib/plugins/visibility/VisibilityPlugin.d.ts +18 -5
- package/lib/plugins/visibility/index.js +1 -1
- package/lib/plugins/visibility/index.js.map +1 -1
- package/package.json +1 -1
- package/public.d.ts +2 -4
- package/themes/dg-theme-material.css +16 -4
- package/umd/grid.all.umd.js +1 -1
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +1 -1
- package/umd/grid.umd.js.map +1 -1
- package/umd/plugins/clipboard.umd.js.map +1 -1
- package/umd/plugins/context-menu.umd.js.map +1 -1
- package/umd/plugins/editing.umd.js.map +1 -1
- package/umd/plugins/export.umd.js.map +1 -1
- package/umd/plugins/filtering.umd.js +1 -1
- package/umd/plugins/filtering.umd.js.map +1 -1
- package/umd/plugins/grouping-columns.umd.js +1 -1
- package/umd/plugins/grouping-columns.umd.js.map +1 -1
- package/umd/plugins/grouping-rows.umd.js +1 -1
- package/umd/plugins/grouping-rows.umd.js.map +1 -1
- package/umd/plugins/master-detail.umd.js +1 -1
- package/umd/plugins/master-detail.umd.js.map +1 -1
- package/umd/plugins/pinned-columns.umd.js +1 -1
- package/umd/plugins/pinned-columns.umd.js.map +1 -1
- package/umd/plugins/pinned-rows.umd.js +1 -1
- package/umd/plugins/pinned-rows.umd.js.map +1 -1
- package/umd/plugins/pivot.umd.js +1 -1
- package/umd/plugins/pivot.umd.js.map +1 -1
- package/umd/plugins/reorder-columns.umd.js +1 -1
- package/umd/plugins/reorder-columns.umd.js.map +1 -1
- package/umd/plugins/responsive.umd.js +1 -1
- package/umd/plugins/responsive.umd.js.map +1 -1
- package/umd/plugins/selection.umd.js +1 -1
- package/umd/plugins/selection.umd.js.map +1 -1
- package/umd/plugins/server-side.umd.js +1 -1
- package/umd/plugins/server-side.umd.js.map +1 -1
- package/umd/plugins/tree.umd.js +1 -1
- package/umd/plugins/tree.umd.js.map +1 -1
- package/umd/plugins/undo-redo.umd.js.map +1 -1
- package/umd/plugins/visibility.umd.js +1 -1
- package/umd/plugins/visibility.umd.js.map +1 -1
package/umd/grid.umd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grid.umd.js","sources":["../../../../libs/grid/src/lib/core/internal/aria.ts","../../../../libs/grid/src/lib/core/types.ts","../../../../libs/grid/src/lib/core/internal/diagnostics.ts","../../../../libs/grid/src/lib/core/internal/columns.ts","../../../../libs/grid/src/lib/core/internal/sanitize.ts","../../../../libs/grid/src/lib/core/internal/sorting.ts","../../../../libs/grid/src/lib/core/internal/header.ts","../../../../libs/grid/src/lib/core/internal/inference.ts","../../../../libs/grid/src/lib/core/internal/render-scheduler.ts","../../../../libs/grid/src/lib/core/internal/config-manager.ts","../../../../libs/grid/src/lib/core/internal/utils.ts","../../../../libs/grid/src/lib/core/internal/rows.ts","../../../../libs/grid/src/lib/core/internal/keyboard.ts","../../../../libs/grid/src/lib/core/internal/event-delegation.ts","../../../../libs/grid/src/lib/core/internal/feature-hook.ts","../../../../libs/grid/src/lib/core/internal/focus-manager.ts","../../../../libs/grid/src/lib/core/internal/idle-scheduler.ts","../../../../libs/grid/src/lib/core/internal/loading.ts","../../../../libs/grid/src/lib/core/internal/resize.ts","../../../../libs/grid/src/lib/core/internal/row-animation.ts","../../../../libs/grid/src/lib/core/internal/row-manager.ts","../../../../libs/grid/src/lib/core/internal/dom-builder.ts","../../../../libs/grid/src/lib/core/internal/shell.ts","../../../../libs/grid/src/lib/core/internal/style-injector.ts","../../../../libs/grid/src/lib/core/internal/touch-scroll.ts","../../../../libs/grid/src/lib/core/internal/validate-config.ts","../../../../libs/grid/src/lib/core/internal/virtualization.ts","../../../../libs/grid/src/lib/core/internal/virtualization-manager.ts","../../../../libs/grid/src/lib/core/plugin/plugin-manager.ts","../../../../libs/grid/src/lib/core/grid.ts","../../../../libs/grid/src/lib/core/styles/index.ts","../../../../libs/grid/src/lib/core/constants.ts","../../../../libs/grid/src/public.ts","../../../../libs/grid/src/lib/core/internal/aggregators.ts","../../../../libs/grid/src/lib/core/plugin/base-plugin.ts","../../../../libs/grid/src/lib/core/plugin/types.ts"],"sourcesContent":["/**\n * ARIA Accessibility Helpers\n *\n * Pure functions for managing ARIA attributes on grid elements.\n * Implements caching to avoid redundant DOM writes during scroll.\n *\n * @module internal/aria\n */\n\nimport type { GridConfig } from '../types';\nimport type { ShellState } from './shell';\n\n// #region Types\n\n/**\n * State for caching ARIA attributes to avoid redundant DOM writes.\n */\nexport interface AriaState {\n /** Last set row count */\n rowCount: number;\n /** Last set column count */\n colCount: number;\n /** Last set aria-label */\n ariaLabel: string | undefined;\n /** Last set aria-describedby */\n ariaDescribedBy: string | undefined;\n}\n\n/**\n * Create initial ARIA state.\n */\nexport function createAriaState(): AriaState {\n return {\n rowCount: -1,\n colCount: -1,\n ariaLabel: undefined,\n ariaDescribedBy: undefined,\n };\n}\n\n// #endregion\n\n// #region Count Updates\n\n/**\n * Update ARIA row and column counts on grid elements.\n * Uses caching to avoid redundant DOM writes on every scroll frame.\n *\n * @param state - ARIA state for caching\n * @param rowsBodyEl - Element to set aria-rowcount/aria-colcount on\n * @param bodyEl - Element to set role=\"rowgroup\" on\n * @param rowCount - Current row count\n * @param colCount - Current column count\n * @returns true if anything was updated\n */\nexport function updateAriaCounts(\n state: AriaState,\n rowsBodyEl: HTMLElement | null,\n bodyEl: HTMLElement | null,\n rowCount: number,\n colCount: number,\n): boolean {\n // Skip if nothing changed (hot path optimization for scroll)\n if (rowCount === state.rowCount && colCount === state.colCount) {\n return false;\n }\n\n const prevRowCount = state.rowCount;\n state.rowCount = rowCount;\n state.colCount = colCount;\n\n // Update ARIA counts on inner grid element\n if (rowsBodyEl) {\n rowsBodyEl.setAttribute('aria-rowcount', String(rowCount));\n rowsBodyEl.setAttribute('aria-colcount', String(colCount));\n }\n\n // Set role=\"rowgroup\" on .rows only when there are rows (ARIA compliance)\n if (rowCount !== prevRowCount && bodyEl) {\n if (rowCount > 0) {\n bodyEl.setAttribute('role', 'rowgroup');\n } else {\n bodyEl.removeAttribute('role');\n }\n }\n\n return true;\n}\n\n// #endregion\n\n// #region Label Updates\n\n/**\n * Determine the effective aria-label for the grid.\n * Priority: explicit config > shell title > nothing\n *\n * @param config - Grid configuration\n * @param shellState - Shell state (for light DOM title)\n * @returns The aria-label to use, or undefined\n */\nexport function getEffectiveAriaLabel<T>(\n config: GridConfig<T> | undefined,\n shellState: ShellState | undefined,\n): string | undefined {\n const explicitLabel = config?.gridAriaLabel;\n if (explicitLabel) return explicitLabel;\n\n const shellTitle = config?.shell?.header?.title ?? shellState?.lightDomTitle;\n return shellTitle ?? undefined;\n}\n\n/**\n * Update ARIA label and describedby attributes on the grid container.\n * Uses caching to avoid redundant DOM writes.\n *\n * @param state - ARIA state for caching\n * @param rowsBodyEl - Element to set aria-label/aria-describedby on\n * @param config - Grid configuration\n * @param shellState - Shell state (for light DOM title)\n * @returns true if anything was updated\n */\nexport function updateAriaLabels<T>(\n state: AriaState,\n rowsBodyEl: HTMLElement | null,\n config: GridConfig<T> | undefined,\n shellState: ShellState | undefined,\n): boolean {\n if (!rowsBodyEl) return false;\n\n let updated = false;\n\n // Determine aria-label: explicit config > shell title > nothing\n const ariaLabel = getEffectiveAriaLabel(config, shellState);\n\n // Update aria-label only if changed\n if (ariaLabel !== state.ariaLabel) {\n state.ariaLabel = ariaLabel;\n if (ariaLabel) {\n rowsBodyEl.setAttribute('aria-label', ariaLabel);\n } else {\n rowsBodyEl.removeAttribute('aria-label');\n }\n updated = true;\n }\n\n // Update aria-describedby only if changed\n const ariaDescribedBy = config?.gridAriaDescribedBy;\n if (ariaDescribedBy !== state.ariaDescribedBy) {\n state.ariaDescribedBy = ariaDescribedBy;\n if (ariaDescribedBy) {\n rowsBodyEl.setAttribute('aria-describedby', ariaDescribedBy);\n } else {\n rowsBodyEl.removeAttribute('aria-describedby');\n }\n updated = true;\n }\n\n return updated;\n}\n\n// #endregion\n\n// #region Live Announcements\n\n/**\n * Announce a message to screen readers via the grid's aria-live region.\n * Clears then sets text to ensure repeated messages are re-announced.\n *\n * @param gridEl - The grid host element (or any ancestor containing `.tbw-sr-only`)\n * @param message - The message to announce\n */\nexport function announce(gridEl: HTMLElement, message: string): void {\n const el = gridEl.querySelector?.('.tbw-sr-only');\n if (!el) return;\n // Clear first so identical consecutive messages are still announced\n el.textContent = '';\n requestAnimationFrame(() => {\n el.textContent = message;\n });\n}\n\n// #endregion\n","import type { ShellState } from './internal/shell';\nimport type { RowPosition } from './internal/virtualization';\nimport type { PluginQuery } from './plugin/base-plugin';\nimport type { AfterCellRenderContext, AfterRowRenderContext, CellMouseEvent } from './plugin/types';\n\n/**\n * Position entry for a single row in the position cache.\n * Part of variable row height virtualization.\n *\n * Re-exported from position-cache.ts for public API consistency.\n *\n * @see VirtualState.positionCache\n * @category Plugin Development\n */\nexport type RowPositionEntry = RowPosition;\n\n// #region DataGridElement Interface\n/**\n * The compiled web component interface for DataGrid.\n *\n * This interface represents the `<tbw-grid>` custom element, combining\n * the public grid API with standard HTMLElement functionality.\n *\n * @example\n * ```typescript\n * // Query existing grid with type safety\n * import { queryGrid } from '@toolbox-web/grid';\n * const grid = queryGrid<Employee>('tbw-grid');\n * grid.rows = employees;\n * grid.on('cell-click', (detail) => console.log(detail));\n *\n * // Create grid programmatically\n * import { createGrid } from '@toolbox-web/grid';\n * const grid = createGrid<Employee>({\n * columns: [{ field: 'name' }, { field: 'email' }],\n * });\n * document.body.appendChild(grid);\n * ```\n *\n * @see {@link PublicGrid} for the public API methods and properties\n * @see {@link createGrid} for typed grid creation\n * @see {@link queryGrid} for typed grid querying\n */\nexport interface DataGridElement extends PublicGrid, HTMLElement {}\n// #endregion\n\n// #region ScrollToRowOptions\n/**\n * Options for the {@link PublicGrid.scrollToRow} method.\n *\n * @group Focus & Navigation\n *\n * @example\n * ```typescript\n * grid.scrollToRow(42, { align: 'center', behavior: 'smooth' });\n * ```\n */\nexport interface ScrollToRowOptions {\n /**\n * Where to position the row in the viewport.\n *\n * - `'start'` — top of the viewport\n * - `'center'` — vertically centered\n * - `'end'` — bottom of the viewport\n * - `'nearest'` — scroll only if the row is outside the viewport (default)\n *\n * @defaultValue `'nearest'`\n */\n align?: 'start' | 'center' | 'end' | 'nearest';\n /**\n * Scroll behavior.\n *\n * - `'instant'` — jump immediately (default)\n * - `'smooth'` — animate the scroll\n *\n * @defaultValue `'instant'`\n */\n behavior?: 'smooth' | 'instant';\n}\n// #endregion\n\n// #region PublicGrid Interface\n/**\n * Public API interface for DataGrid component.\n *\n * **Property Getters vs Setters:**\n *\n * Property getters return the EFFECTIVE (resolved) value after merging all config sources.\n * This is the \"current situation\" - what consumers and plugins need to know.\n *\n * Property setters accept input values which are merged into the effective config.\n * Multiple sources can contribute (gridConfig, columns prop, light DOM, individual props).\n *\n * For example:\n * - `grid.fitMode` returns the resolved fitMode (e.g., 'stretch' even if you set undefined)\n * - `grid.columns` returns the effective columns after merging\n * - `grid.gridConfig` returns the full effective config\n */\nexport interface PublicGrid<T = any> {\n /**\n * Full config object. Setter merges with other inputs per precedence rules.\n * Getter returns the effective (resolved) config.\n */\n gridConfig?: GridConfig<T>;\n /**\n * Column definitions.\n * Getter returns effective columns (after merging config, light DOM, inference).\n */\n columns?: ColumnConfig<T>[];\n /** Current row data (after plugin processing like grouping, filtering). */\n rows?: T[];\n /** Insert a row at a visible index, bypassing the sort/filter pipeline. Auto-animates by default. */\n insertRow?(index: number, row: T, animate?: boolean): Promise<void>;\n /** Remove a row at a visible index, bypassing the sort/filter pipeline. Auto-animates by default. */\n removeRow?(index: number, animate?: boolean): Promise<T | undefined>;\n /** Resolves once the component has finished initial work (layout, inference). */\n ready?: () => Promise<void>;\n /** Force a layout / measurement pass (e.g. after container resize). */\n forceLayout?: () => Promise<void>;\n /** Return effective resolved config (after inference & precedence). */\n getConfig?: () => Promise<Readonly<GridConfig<T>>>;\n /** Toggle expansion state of a group row by its generated key. */\n toggleGroup?: (key: string) => Promise<void>;\n\n // Custom Styles API\n /**\n * Register custom CSS styles to be injected into the grid.\n * Use this to style custom cell renderers, editors, or detail panels.\n * @param id - Unique identifier for the style block (for removal/updates)\n * @param css - CSS string to inject\n */\n registerStyles?: (id: string, css: string) => void;\n /**\n * Remove previously registered custom styles.\n * @param id - The ID used when registering the styles\n */\n unregisterStyles?: (id: string) => void;\n /**\n * Get list of registered custom style IDs.\n */\n getRegisteredStyles?: () => string[];\n\n // Plugin API\n /**\n * Get a plugin instance by its class.\n *\n * **Prefer {@link getPluginByName}** — it avoids importing the plugin class\n * and returns the actual registered instance with full type narrowing.\n *\n * @example\n * ```typescript\n * // Preferred: by name\n * const selection = grid.getPluginByName('selection');\n *\n * // Alternative: by class\n * const selection = grid.getPlugin(SelectionPlugin);\n * if (selection) {\n * selection.selectAll();\n * }\n * ```\n */\n getPlugin?<P>(PluginClass: new (...args: any[]) => P): P | undefined;\n /**\n * Get a plugin instance by its name.\n *\n * When a plugin augments the {@link PluginNameMap} interface, the return\n * type is narrowed automatically:\n *\n * ```typescript\n * const editing = grid.getPluginByName('editing');\n * editing?.beginBulkEdit(0); // ✅ typed as EditingPlugin\n * ```\n *\n * For unknown names the return type falls back to `GridPlugin | undefined`.\n */\n getPluginByName?<K extends string>(\n name: K,\n ): (K extends keyof PluginNameMap ? PluginNameMap[K] : GridPlugin) | undefined;\n\n // Shell API\n /**\n * Re-render the shell header (title, column groups, toolbar).\n * Call this after dynamically adding/removing tool panels or toolbar buttons.\n */\n refreshShellHeader?(): void;\n /**\n * Register a custom tool panel in the sidebar.\n *\n * @example\n * ```typescript\n * grid.registerToolPanel({\n * id: 'analytics',\n * title: 'Analytics',\n * icon: '📊',\n * render: (container) => {\n * container.innerHTML = '<div>Charts here...</div>';\n * }\n * });\n * ```\n */\n registerToolPanel?(panel: ToolPanelDefinition): void;\n /**\n * Unregister a previously registered tool panel.\n */\n unregisterToolPanel?(panelId: string): void;\n /**\n * Open the tool panel sidebar.\n */\n openToolPanel?(): void;\n /**\n * Close the tool panel sidebar.\n */\n closeToolPanel?(): void;\n /**\n * Toggle the tool panel sidebar open or closed.\n */\n toggleToolPanel?(): void;\n /**\n * Toggle an accordion section expanded or collapsed within the tool panel.\n * @param sectionId - The ID of the section to toggle\n */\n toggleToolPanelSection?(sectionId: string): void;\n\n // State Persistence API\n /**\n * Get the current column state including order, width, visibility, and sort.\n * Use for persisting user preferences to localStorage or a backend.\n *\n * @example\n * ```typescript\n * const state = grid.getColumnState();\n * localStorage.setItem('gridState', JSON.stringify(state));\n * ```\n */\n getColumnState?(): GridColumnState;\n /**\n * Set/restore the column state.\n * Can be set before or after grid initialization.\n *\n * @example\n * ```typescript\n * const saved = localStorage.getItem('gridState');\n * if (saved) {\n * grid.columnState = JSON.parse(saved);\n * }\n * ```\n */\n columnState?: GridColumnState;\n\n // Loading API\n /**\n * Whether the grid is currently in a loading state.\n * When true, displays a loading overlay with spinner.\n *\n * Can also be set via the `loading` HTML attribute.\n *\n * @example\n * ```typescript\n * // Show loading overlay\n * grid.loading = true;\n * const data = await fetchData();\n * grid.rows = data;\n * grid.loading = false;\n * ```\n */\n loading?: boolean;\n\n /**\n * Set loading state for a specific row.\n * Displays a small spinner indicator on the row.\n *\n * Use when persisting row data or performing row-level async operations.\n *\n * @param rowId - The row's unique identifier (from getRowId)\n * @param loading - Whether the row is loading\n *\n * @example\n * ```typescript\n * // Show loading while saving row\n * grid.setRowLoading('emp-123', true);\n * await saveRow(row);\n * grid.setRowLoading('emp-123', false);\n * ```\n */\n setRowLoading?(rowId: string, loading: boolean): void;\n\n /**\n * Set loading state for a specific cell.\n * Displays a small spinner indicator on the cell.\n *\n * Use when performing cell-level async operations (e.g., validation, lookup).\n *\n * @param rowId - The row's unique identifier (from getRowId)\n * @param field - The column field\n * @param loading - Whether the cell is loading\n *\n * @example\n * ```typescript\n * // Show loading while validating cell\n * grid.setCellLoading('emp-123', 'email', true);\n * const isValid = await validateEmail(email);\n * grid.setCellLoading('emp-123', 'email', false);\n * ```\n */\n setCellLoading?(rowId: string, field: string, loading: boolean): void;\n\n /**\n * Check if a row is currently in loading state.\n * @param rowId - The row's unique identifier\n */\n isRowLoading?(rowId: string): boolean;\n\n /**\n * Check if a cell is currently in loading state.\n * @param rowId - The row's unique identifier\n * @param field - The column field\n */\n isCellLoading?(rowId: string, field: string): boolean;\n\n /**\n * Clear all row and cell loading states.\n */\n clearAllLoading?(): void;\n\n // Focus Management API\n /**\n * Register an external DOM element as a logical focus container of this grid.\n *\n * Focus moving into a registered container is treated as if it stayed inside\n * the grid: `data-has-focus` is preserved, click-outside commit is suppressed,\n * and the editing focus trap (when enabled) won't reclaim focus.\n *\n * Typical use case: overlay panels (datepickers, dropdowns, autocompletes)\n * that render at `<body>` level to escape grid overflow clipping.\n *\n * @param el - The external element to register\n *\n * @example\n * ```typescript\n * const overlay = document.createElement('div');\n * document.body.appendChild(overlay);\n *\n * // Tell the grid this overlay is \"part of\" the grid\n * grid.registerExternalFocusContainer(overlay);\n *\n * // Later, when overlay is removed\n * grid.unregisterExternalFocusContainer(overlay);\n * ```\n */\n registerExternalFocusContainer?(el: Element): void;\n\n /**\n * Unregister a previously registered external focus container.\n *\n * @param el - The element to unregister\n */\n unregisterExternalFocusContainer?(el: Element): void;\n\n /**\n * Check whether focus is logically inside this grid.\n *\n * Returns `true` when `document.activeElement` (or the given node) is\n * inside the grid's own DOM **or** inside any element registered via\n * {@link registerExternalFocusContainer}.\n *\n * @param node - Optional node to test. Defaults to `document.activeElement`.\n *\n * @example\n * ```typescript\n * if (grid.containsFocus()) {\n * console.log('Grid or one of its overlays has focus');\n * }\n * ```\n */\n containsFocus?(node?: Node | null): boolean;\n\n // Focus & Navigation API\n /**\n * Move focus to a specific cell.\n *\n * @param rowIndex - Row index (0-based, in the current processed row array)\n * @param column - Column index (0-based into visible columns) or field name\n */\n focusCell?(rowIndex: number, column: number | string): void;\n\n /**\n * The currently focused cell position, or `null` if no rows are loaded.\n */\n readonly focusedCell?: { rowIndex: number; colIndex: number; field: string } | null;\n\n /**\n * Scroll to make a row visible by its index.\n *\n * @param rowIndex - Row index (0-based, in the current processed row array)\n * @param options - Scroll alignment and behavior\n */\n scrollToRow?(rowIndex: number, options?: ScrollToRowOptions): void;\n\n /**\n * Scroll to make a row visible by its unique ID.\n *\n * @param rowId - The row's unique identifier (from {@link GridConfig.getRowId | getRowId})\n * @param options - Scroll alignment and behavior\n */\n scrollToRowById?(rowId: string, options?: ScrollToRowOptions): void;\n}\n// #endregion\n\n// #region InternalGrid Interface\n/**\n * Internal-only augmented interface for DataGrid component.\n *\n * Member prefixes indicate accessibility:\n * - `_underscore` = protected members - private outside core, accessible to plugins. Marked with @internal.\n * - `__doubleUnderscore` = deeply internal members - private outside core, only for internal functions.\n *\n * @category Plugin Development\n */\nexport interface InternalGrid<T = any> extends PublicGrid<T>, GridConfig<T> {\n // Element methods available because DataGridElement extends HTMLElement\n /** The element's `id` attribute. Available because DataGridElement extends HTMLElement. */\n id: string;\n /**\n * The grid's host HTMLElement (`this`). Use instead of casting `grid as unknown as HTMLElement`.\n * @internal\n */\n readonly _hostElement: HTMLElement;\n querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;\n querySelector<E extends Element = Element>(selectors: string): E | null;\n querySelectorAll<K extends keyof HTMLElementTagNameMap>(selectors: K): NodeListOf<HTMLElementTagNameMap[K]>;\n querySelectorAll<E extends Element = Element>(selectors: string): NodeListOf<E>;\n _rows: T[];\n _columns: ColumnInternal<T>[];\n /** Visible columns only (excludes hidden). Use for rendering. */\n _visibleColumns: ColumnInternal<T>[];\n _headerRowEl: HTMLElement;\n _bodyEl: HTMLElement;\n _rowPool: RowElementInternal[];\n _resizeController: ResizeController;\n _sortState: { field: string; direction: 1 | -1 } | null;\n /** Original unfiltered/unprocessed rows. @internal */\n sourceRows: T[];\n /** Framework adapter instance (set by Grid directives). @internal */\n __frameworkAdapter?: FrameworkAdapter;\n __originalOrder: T[];\n __rowRenderEpoch: number;\n __didInitialAutoSize?: boolean;\n __lightDomColumnsCache?: ColumnInternal[];\n __originalColumnNodes?: HTMLElement[];\n /** Cell display value cache. @internal */\n __cellDisplayCache?: Map<number, string[]>;\n /** Cache epoch for cell display values. @internal */\n __cellCacheEpoch?: number;\n /** Cached header row count for virtualization. @internal */\n __cachedHeaderRowCount?: number;\n /** Cached flag for whether grid has special columns (custom renderers, etc.). @internal */\n __hasSpecialColumns?: boolean;\n /** Cached flag for whether any plugin has renderRow hooks. @internal */\n __hasRenderRowPlugins?: boolean;\n _gridTemplate: string;\n _virtualization: VirtualState;\n _focusRow: number;\n _focusCol: number;\n /** Currently active edit row index. Injected by EditingPlugin. @internal */\n _activeEditRows?: number;\n /** Whether the grid is in 'grid' editing mode (all rows editable). Injected by EditingPlugin. @internal */\n _isGridEditMode?: boolean;\n /** Snapshots of row data before editing. Injected by EditingPlugin. @internal */\n _rowEditSnapshots?: Map<number, T>;\n /** Get all changed rows. Injected by EditingPlugin. */\n changedRows?: T[];\n /** Get IDs of all changed rows. Injected by EditingPlugin. */\n changedRowIds?: string[];\n /** Internal Set for O(1) lookup in the render hot path. Injected by EditingPlugin. @internal */\n _changedRowIdSet?: ReadonlySet<string>;\n effectiveConfig?: GridConfig<T>;\n findHeaderRow?: () => HTMLElement;\n refreshVirtualWindow: (full: boolean, skipAfterRender?: boolean) => boolean;\n /** @internal Trigger a COLUMNS-phase re-render. */\n refreshColumns?: () => void;\n updateTemplate?: () => void;\n findRenderedRowElement?: (rowIndex: number) => HTMLElement | null;\n /** Get a row by its ID. Implemented in grid.ts */\n getRow?: (id: string) => T | undefined;\n /** Get a row and its current index by ID. Returns undefined if not found. @internal */\n _getRowEntry: (id: string) => { row: T; index: number } | undefined;\n /** Get the unique ID for a row. Implemented in grid.ts */\n getRowId?: (row: T) => string;\n /** Update a row by ID. Implemented in grid.ts */\n updateRow?: (id: string, changes: Partial<T>, source?: UpdateSource) => void;\n /** Animate a single row. Returns Promise that resolves when animation completes. Implemented in grid.ts */\n animateRow?: (rowIndex: number, type: RowAnimationType) => Promise<boolean>;\n /** Animate multiple rows. Returns Promise that resolves when all animations complete. Implemented in grid.ts */\n animateRows?: (rowIndices: number[], type: RowAnimationType) => Promise<number>;\n /** Animate a row by its ID. Returns Promise that resolves when animation completes. Implemented in grid.ts */\n animateRowById?: (rowId: string, type: RowAnimationType) => Promise<boolean>;\n /** Begin bulk edit on a row. Injected by EditingPlugin. */\n beginBulkEdit?: (rowIndex: number) => void;\n /** Commit active row edit. Injected by EditingPlugin. */\n commitActiveRowEdit?: () => void;\n /** Dispatch cell click to plugin system, returns true if handled */\n _dispatchCellClick?: (event: MouseEvent, rowIndex: number, colIndex: number, cellEl: HTMLElement) => boolean;\n /** Dispatch row click to plugin system, returns true if handled */\n _dispatchRowClick?: (event: MouseEvent, rowIndex: number, row: any, rowEl: HTMLElement) => boolean;\n /** Dispatch header click to plugin system, returns true if handled */\n _dispatchHeaderClick?: (event: MouseEvent | KeyboardEvent, col: ColumnConfig, headerEl: HTMLElement) => boolean;\n /** Dispatch keydown to plugin system, returns true if handled */\n _dispatchKeyDown?: (event: KeyboardEvent) => boolean;\n /** Dispatch cell mouse events for drag operations. Returns true if any plugin started a drag. */\n _dispatchCellMouseDown?: (event: CellMouseEvent) => boolean;\n /** Dispatch cell mouse move during drag. */\n _dispatchCellMouseMove?: (event: CellMouseEvent) => void;\n /** Dispatch cell mouse up to end drag. */\n _dispatchCellMouseUp?: (event: CellMouseEvent) => void;\n /** Call afterCellRender hook on all plugins. Called from rows.ts after each cell is rendered. @internal */\n _afterCellRender?: (context: AfterCellRenderContext<T>) => void;\n /** Check if any plugin has registered an afterCellRender hook. Used to skip hook call for performance. @internal */\n _hasAfterCellRenderHook?: () => boolean;\n /** Call afterRowRender hook on all plugins. Called from rows.ts after each row is rendered. @internal */\n _afterRowRender?: (context: AfterRowRenderContext<T>) => void;\n /** Check if any plugin has registered an afterRowRender hook. Used to skip hook call for performance. @internal */\n _hasAfterRowRenderHook?: () => boolean;\n /** Get horizontal scroll boundary offsets from plugins */\n _getHorizontalScrollOffsets?: (\n rowEl?: HTMLElement,\n focusedCell?: HTMLElement,\n ) => { left: number; right: number; skipScroll?: boolean };\n /** Query all plugins with a generic query and collect responses */\n queryPlugins?: <T>(query: PluginQuery) => T[];\n /** Request emission of column-state-change event (debounced) */\n requestStateChange?: () => void;\n\n // Methods exposed for extracted managers (VirtualizationManager, FocusManager, RowManager, RenderScheduler)\n /** @internal */ _renderVisibleRows(start: number, end: number, epoch?: number): void;\n /** @internal */ _updateAriaCounts(totalRows: number, totalCols: number): void;\n /** @internal */ _requestSchedulerPhase(phase: number, source: string): void;\n /** @internal */ _rebuildRowIdMap(): void;\n /** @internal */ _emitDataChange(): void;\n /** @internal */ _getPluginExtraHeight(): number;\n /** @internal */ _getPluginRowHeight(row: T, index: number): number | undefined;\n /** @internal */ _getPluginExtraHeightBefore(start: number): number;\n /** @internal */ _adjustPluginVirtualStart(start: number, scrollTop: number, rowHeight: number): number | undefined;\n /** @internal */ _afterPluginRender(): void;\n /** @internal */ _emitPluginEvent(event: string, detail: unknown): void;\n\n // Scheduler pipeline callbacks\n /** @internal */ _schedulerMergeConfig(): void;\n /** @internal */ _schedulerProcessColumns(): void;\n /** @internal */ _schedulerProcessRows(): void;\n /** @internal */ _schedulerRenderHeader(): void;\n /** @internal */ _schedulerUpdateTemplate(): void;\n /** @internal */ _schedulerAfterRender(): void;\n /** @internal */ readonly _schedulerIsConnected: boolean;\n\n // Shell controller & config manager support\n /** @internal The render root element for DOM queries. */\n readonly _renderRoot: Element;\n /** @internal Emit a custom event from the grid. */\n _emit(eventName: string, detail: unknown): void;\n /** @internal Get accordion expand/collapse icons from effective config. */\n readonly _accordionIcons: { expand: IconValue; collapse: IconValue };\n /** @internal Shell state for config manager shell merging. */\n readonly _shellState: ShellState;\n /** @internal Clear the row pool and body element. */\n _clearRowPool(): void;\n /** @internal Run grid setup (DOM rebuild). */\n _setup(): void;\n /** @internal Apply animation configuration to host element. */\n _applyAnimationConfig(config: GridConfig): void;\n}\n\n/**\n * Grid reference type combining InternalGrid with HTMLElement.\n * Used by extracted managers that need both internal grid state and DOM APIs.\n * @internal\n */\nexport type GridHost<T = any> = InternalGrid<T> & HTMLElement;\n// #endregion\n\n// #region Column Types\n/**\n * Built-in primitive column types with automatic formatting and editing support.\n *\n * - `'string'` - Text content, default text input editor\n * - `'number'` - Numeric content, right-aligned, number input editor\n * - `'date'` - Date content, formatted display, date picker editor\n * - `'boolean'` - True/false, rendered as checkbox\n * - `'select'` - Dropdown selection from `options` array\n *\n * @example\n * ```typescript\n * columns: [\n * { field: 'name', type: 'string' },\n * { field: 'age', type: 'number' },\n * { field: 'hireDate', type: 'date' },\n * { field: 'active', type: 'boolean' },\n * { field: 'department', type: 'select', options: [\n * { label: 'Engineering', value: 'eng' },\n * { label: 'Sales', value: 'sales' },\n * ]},\n * ]\n * ```\n *\n * @see {@link ColumnType} for custom type support\n * @see {@link TypeDefault} for type-level defaults\n */\nexport type PrimitiveColumnType = 'number' | 'string' | 'date' | 'boolean' | 'select';\n\n/**\n * Column type - built-in primitives or custom type strings.\n *\n * Use built-in types for automatic formatting, or define custom types\n * (e.g., 'currency', 'country') with type-level defaults via `typeDefaults`.\n *\n * @example\n * ```typescript\n * // Built-in types\n * { field: 'name', type: 'string' }\n * { field: 'salary', type: 'number' }\n *\n * // Custom types with defaults\n * grid.gridConfig = {\n * columns: [\n * { field: 'salary', type: 'currency' },\n * { field: 'birthCountry', type: 'country' },\n * ],\n * typeDefaults: {\n * currency: {\n * format: (v) => `$${Number(v).toFixed(2)}`,\n * },\n * country: {\n * renderer: (ctx) => `🌍 ${ctx.value}`,\n * },\n * },\n * };\n * ```\n *\n * @see {@link PrimitiveColumnType} for built-in types\n * @see {@link TypeDefault} for defining custom type defaults\n */\nexport type ColumnType = PrimitiveColumnType | (string & {});\n// #endregion\n\n// #region TypeDefault Interface\n/**\n * Type-level defaults for formatters and renderers.\n * Applied to all columns of a given type unless overridden at column level.\n *\n * Note: `editor` and `editorParams` are added via module augmentation when\n * EditingPlugin is imported. `filterPanelRenderer` is added by FilteringPlugin.\n *\n * @example\n * ```typescript\n * typeDefaults: {\n * currency: {\n * format: (value) => new Intl.NumberFormat('en-US', {\n * style: 'currency',\n * currency: 'USD',\n * }).format(value as number),\n * },\n * country: {\n * renderer: (ctx) => {\n * const span = document.createElement('span');\n * span.innerHTML = `<img src=\"/flags/${ctx.value}.svg\" /> ${ctx.value}`;\n * return span;\n * }\n * }\n * }\n * ```\n *\n * @see {@link ColumnViewRenderer} for custom renderer function signature\n * @see {@link ColumnType} for type strings that can have defaults\n * @see {@link GridConfig.typeDefaults} for registering type defaults\n */\nexport interface TypeDefault<TRow = unknown> {\n /**\n * Default formatter for all columns of this type.\n *\n * Transforms the raw cell value into a display string. Use when you need\n * consistent formatting across columns without custom DOM (e.g., currency,\n * percentages, dates with specific locale).\n *\n * **Resolution Priority**: Column `format` → Type `format` → Built-in\n *\n * @example\n * ```typescript\n * typeDefaults: {\n * currency: {\n * format: (value) => new Intl.NumberFormat('en-US', {\n * style: 'currency',\n * currency: 'USD',\n * }).format(value as number),\n * },\n * percentage: {\n * format: (value) => `${(value as number * 100).toFixed(1)}%`,\n * }\n * }\n * ```\n */\n format?: (value: unknown, row: TRow) => string;\n\n /**\n * Default renderer for all columns of this type.\n *\n * Creates custom DOM for the cell content. Use when you need more than\n * text formatting (e.g., icons, badges, interactive elements).\n *\n * **Resolution Priority**: Column `renderer` → Type `renderer` → App-level (adapter) → Built-in\n *\n * @example\n * ```typescript\n * typeDefaults: {\n * status: {\n * renderer: (ctx) => {\n * const badge = document.createElement('span');\n * badge.className = `badge badge-${ctx.value}`;\n * badge.textContent = ctx.value as string;\n * return badge;\n * }\n * },\n * country: {\n * renderer: (ctx) => {\n * const span = document.createElement('span');\n * span.innerHTML = `<img src=\"/flags/${ctx.value}.svg\" /> ${ctx.value}`;\n * return span;\n * }\n * }\n * }\n * ```\n */\n renderer?: ColumnViewRenderer<TRow, unknown>;\n}\n// #endregion\n\n// #region BaseColumnConfig Interface\n/**\n * Base contract for a column configuration.\n *\n * Defines the fundamental properties all columns share. Extended by {@link ColumnConfig}\n * with additional features like custom renderers and grouping.\n *\n * @example\n * ```typescript\n * // Basic column with common properties\n * const columns: BaseColumnConfig<Employee>[] = [\n * {\n * field: 'name',\n * header: 'Full Name',\n * sortable: true,\n * resizable: true,\n * },\n * {\n * field: 'salary',\n * type: 'number',\n * width: 120,\n * format: (value) => `$${value.toLocaleString()}`,\n * sortComparator: (a, b) => a - b,\n * },\n * {\n * field: 'department',\n * type: 'select',\n * options: [\n * { label: 'Engineering', value: 'eng' },\n * { label: 'Sales', value: 'sales' },\n * ],\n * },\n * ];\n * ```\n *\n * @see {@link ColumnConfig} for full column configuration with renderers\n * @see {@link ColumnType} for type options\n */\nexport interface BaseColumnConfig<TRow = any, TValue = any> {\n /** Unique field key referencing property in row objects */\n field: keyof TRow & string;\n /** Visible header label; defaults to capitalized field */\n header?: string;\n /**\n * Column data type.\n *\n * Built-in types: `'string'`, `'number'`, `'date'`, `'boolean'`, `'select'`\n *\n * Custom types (e.g., `'currency'`, `'country'`) can have type-level defaults\n * via `gridConfig.typeDefaults` or framework adapter registries.\n *\n * @default Inferred from first row data\n */\n type?: ColumnType;\n /** Column width in pixels; fixed size (no flexibility) */\n width?: string | number;\n /** Minimum column width in pixels (stretch mode only); when set, column uses minmax(minWidth, 1fr) */\n minWidth?: number;\n /** Whether column can be sorted */\n sortable?: boolean;\n /** Whether column can be resized by user */\n resizable?: boolean;\n /** Optional custom comparator for sorting (a,b) -> number */\n sortComparator?: (a: TValue, b: TValue, rowA: TRow, rowB: TRow) => number;\n /** For select type - available options */\n options?: Array<{ label: string; value: unknown }> | (() => Array<{ label: string; value: unknown }>);\n /** For select - allow multi select */\n multi?: boolean;\n /**\n * Formats the raw cell value into a display string.\n *\n * Used both for **cell rendering** and the **built-in filter panel**:\n * - In cells, the formatted value replaces the raw value as text content.\n * - In the filter panel (set filter), checkbox labels show the formatted value\n * instead of the raw value, and search matches against the formatted text.\n *\n * The `row` parameter is available during cell rendering but is `undefined`\n * when called from the filter panel (standalone value formatting). Avoid\n * accessing `row` properties in format functions intended for filter display.\n *\n * @example\n * ```typescript\n * // Currency formatter — works in both cells and filter panel\n * {\n * field: 'price',\n * format: (value) => `$${Number(value).toFixed(2)}`,\n * }\n *\n * // ID-to-name lookup — filter panel shows names, not IDs\n * {\n * field: 'departmentId',\n * format: (value) => departmentMap.get(value as string) ?? String(value),\n * }\n * ```\n */\n format?: (value: TValue, row: TRow) => string;\n /** Arbitrary extra metadata */\n meta?: Record<string, unknown>;\n}\n// #endregion\n\n// #region ColumnConfig Interface\n/**\n * Full column configuration including custom renderers, editors, and grouping metadata.\n *\n * Extends {@link BaseColumnConfig} with additional features for customizing\n * how cells are displayed and edited.\n *\n * @example\n * ```typescript\n * const columns: ColumnConfig<Employee>[] = [\n * // Basic sortable column\n * { field: 'id', header: 'ID', width: 60, sortable: true },\n *\n * // Column with custom renderer\n * {\n * field: 'name',\n * header: 'Employee',\n * renderer: (ctx) => {\n * const div = document.createElement('div');\n * div.innerHTML = `<img src=\"${ctx.row.avatar}\" /><span>${ctx.value}</span>`;\n * return div;\n * },\n * },\n *\n * // Column with custom header\n * {\n * field: 'email',\n * headerLabelRenderer: (ctx) => `${ctx.value} 📧`,\n * },\n *\n * // Editable column (requires EditingPlugin)\n * {\n * field: 'status',\n * editable: true,\n * editor: (ctx) => {\n * const select = document.createElement('select');\n * // ... editor implementation\n * return select;\n * },\n * },\n *\n * // Hidden column (can be shown via VisibilityPlugin)\n * { field: 'internalNotes', hidden: true },\n * ];\n * ```\n *\n * @see {@link BaseColumnConfig} for basic column properties\n * @see {@link ColumnViewRenderer} for custom cell renderers\n * @see {@link ColumnEditorSpec} for custom cell editors\n * @see {@link HeaderRenderer} for custom header renderers\n */\nexport interface ColumnConfig<TRow = any> extends BaseColumnConfig<TRow, any> {\n /**\n * Optional custom cell renderer function. Alias for `viewRenderer`.\n * Can return an HTMLElement, a Node, or an HTML string (which will be sanitized).\n *\n * @example\n * ```typescript\n * // Simple string template\n * renderer: (ctx) => `<span class=\"badge\">${ctx.value}</span>`\n *\n * // DOM element\n * renderer: (ctx) => {\n * const el = document.createElement('span');\n * el.textContent = ctx.value;\n * return el;\n * }\n * ```\n */\n renderer?: ColumnViewRenderer<TRow, any>;\n /** Optional custom view renderer used instead of default text rendering */\n viewRenderer?: ColumnViewRenderer<TRow, any>;\n /** External view spec (lets host app mount any framework component) */\n externalView?: {\n component: unknown;\n props?: Record<string, unknown>;\n mount?: (options: {\n placeholder: HTMLElement;\n context: CellRenderContext<TRow, unknown>;\n spec: unknown;\n }) => void | { dispose?: () => void };\n };\n /** Whether the column is initially hidden */\n hidden?: boolean;\n /** Prevent this column from being hidden programmatically */\n lockVisible?: boolean;\n /**\n * Dynamic CSS class(es) for cells in this column.\n * Called for each cell during rendering. Return class names to add to the cell element.\n *\n * @example\n * ```typescript\n * // Highlight negative values\n * cellClass: (value, row, column) => value < 0 ? ['negative', 'text-red'] : []\n *\n * // Status-based styling\n * cellClass: (value) => [`status-${value}`]\n *\n * // Single class as string\n * cellClass: (value) => value < 0 ? 'negative' : ''\n * ```\n */\n cellClass?: (value: unknown, row: TRow, column: ColumnConfig<TRow>) => string | string[];\n\n /**\n * Custom header label renderer. Customize the label content while the grid\n * handles sort icons, filter buttons, resize handles, and click interactions.\n *\n * Use this for simple customizations like adding icons, badges, or units.\n *\n * @example\n * ```typescript\n * // Add required field indicator\n * headerLabelRenderer: (ctx) => `${ctx.value} <span class=\"required\">*</span>`\n *\n * // Add unit to header\n * headerLabelRenderer: (ctx) => {\n * const span = document.createElement('span');\n * span.innerHTML = `${ctx.value}<br/><small>(kg)</small>`;\n * return span;\n * }\n * ```\n */\n headerLabelRenderer?: HeaderLabelRenderer<TRow>;\n\n /**\n * Custom header cell renderer. Complete control over the entire header cell.\n * Resize handles are added automatically for resizable columns.\n *\n * The context provides helper functions to include standard elements:\n * - `renderSortIcon()` - Returns sort indicator element (null if not sortable)\n * - `renderFilterButton()` - Returns filter button (null if not filterable)\n *\n * **Precedence**: `headerRenderer` > `headerLabelRenderer` > `header` > `field`\n *\n * @example\n * ```typescript\n * headerRenderer: (ctx) => {\n * const div = document.createElement('div');\n * div.className = 'custom-header';\n * div.innerHTML = `<span>${ctx.value}</span>`;\n * const sortIcon = ctx.renderSortIcon();\n * if (sortIcon) div.appendChild(sortIcon);\n * return div;\n * }\n * ```\n */\n headerRenderer?: HeaderRenderer<TRow>;\n}\n// #endregion\n\n// #region ColumnConfigMap Type\n/**\n * Array of column configurations.\n * Convenience type alias for `ColumnConfig<TRow>[]`.\n *\n * @example\n * ```typescript\n * const columns: ColumnConfigMap<Employee> = [\n * { field: 'name', header: 'Full Name', sortable: true },\n * { field: 'email', header: 'Email Address' },\n * { field: 'department', type: 'select', options: deptOptions },\n * ];\n *\n * grid.columns = columns;\n * ```\n *\n * @see {@link ColumnConfig} for individual column options\n * @see {@link GridConfig.columns} for setting columns on the grid\n */\nexport type ColumnConfigMap<TRow = any> = ColumnConfig<TRow>[];\n// #endregion\n\n// #region Editor Types\n/**\n * Editor specification for inline cell editing.\n * Supports multiple formats for maximum flexibility.\n *\n * **Format Options:**\n * - `string` - Custom element tag name (e.g., 'my-date-picker')\n * - `function` - Factory function returning an editor element\n * - `object` - External component spec for framework integration\n *\n * @example\n * ```typescript\n * // 1. Custom element tag name\n * columns: [\n * { field: 'date', editor: 'my-date-picker' }\n * ]\n *\n * // 2. Factory function (full control)\n * columns: [\n * {\n * field: 'status',\n * editor: (ctx) => {\n * const select = document.createElement('select');\n * select.innerHTML = `\n * <option value=\"active\">Active</option>\n * <option value=\"inactive\">Inactive</option>\n * `;\n * select.value = ctx.value;\n * select.onchange = () => ctx.commit(select.value);\n * select.onkeydown = (e) => {\n * if (e.key === 'Escape') ctx.cancel();\n * };\n * return select;\n * }\n * }\n * ]\n *\n * // 3. External component (React, Angular, Vue)\n * columns: [\n * {\n * field: 'country',\n * editor: {\n * component: CountrySelect,\n * props: { showFlags: true }\n * }\n * }\n * ]\n * ```\n *\n * @see {@link ColumnEditorContext} for the context passed to factory functions\n */\nexport type ColumnEditorSpec<TRow = unknown, TValue = unknown> =\n | string // custom element tag name\n | ((context: ColumnEditorContext<TRow, TValue>) => HTMLElement | string)\n | {\n /** Arbitrary component reference (class, function, token) */\n component: unknown;\n /** Optional static props passed to mount */\n props?: Record<string, unknown>;\n /** Optional custom mount function; if provided we call it directly instead of emitting an event */\n mount?: (options: {\n placeholder: HTMLElement;\n context: ColumnEditorContext<TRow, TValue>;\n spec: unknown;\n }) => void | { dispose?: () => void };\n };\n\n/**\n * Context object provided to editor factories allowing mutation (commit/cancel) of a cell value.\n *\n * The `commit` and `cancel` functions control the editing lifecycle:\n * - Call `commit(newValue)` to save changes and exit edit mode\n * - Call `cancel()` to discard changes and exit edit mode\n *\n * @example\n * ```typescript\n * const myEditor: ColumnEditorSpec = (ctx: ColumnEditorContext) => {\n * const input = document.createElement('input');\n * input.value = ctx.value;\n * input.className = 'my-editor';\n *\n * // Save on Enter, cancel on Escape\n * input.onkeydown = (e) => {\n * if (e.key === 'Enter') {\n * ctx.commit(input.value);\n * } else if (e.key === 'Escape') {\n * ctx.cancel();\n * }\n * };\n *\n * // Access row data for validation\n * if (ctx.row.locked) {\n * input.disabled = true;\n * }\n *\n * return input;\n * };\n * ```\n *\n * @see {@link ColumnEditorSpec} for editor specification options\n */\nexport interface ColumnEditorContext<TRow = any, TValue = any> {\n /** Underlying full row object for the active edit. */\n row: TRow;\n /** Current cell value (mutable only via commit). */\n value: TValue;\n /** Field name being edited. */\n field: keyof TRow & string;\n /** Column configuration reference. */\n column: ColumnConfig<TRow>;\n /**\n * Stable row identifier (from `getRowId`).\n * Empty string if no `getRowId` is configured.\n */\n rowId: string;\n /** Accept the edit; triggers change tracking + rerender. */\n commit: (newValue: TValue) => void;\n /** Abort edit without persisting changes. */\n cancel: () => void;\n /**\n * Update other fields in this row while the editor is open.\n * Changes are committed with source `'cascade'`, triggering\n * `cell-change` events and `onValueChange` pushes to sibling editors.\n *\n * Useful for editors that affect multiple fields (e.g., an address\n * lookup that sets city + zip + state).\n *\n * @example\n * ```typescript\n * // In a cell-commit listener:\n * grid.on('cell-commit', (detail) => {\n * if (detail.field === 'quantity') {\n * detail.updateRow({ total: detail.row.price * detail.value });\n * }\n * });\n * ```\n */\n updateRow: (changes: Partial<TRow>) => void;\n /**\n * Register a callback invoked when the cell's underlying value changes\n * while the editor is open (e.g., via `updateRow()` from another cell's commit).\n *\n * Built-in editors auto-update their input values. Custom/framework editors\n * should use this to stay in sync with external mutations.\n *\n * @example\n * ```typescript\n * const editor = (ctx: ColumnEditorContext) => {\n * const input = document.createElement('input');\n * input.value = String(ctx.value);\n * ctx.onValueChange?.((newValue) => {\n * input.value = String(newValue);\n * });\n * return input;\n * };\n * ```\n */\n onValueChange?: (callback: (newValue: TValue) => void) => void;\n}\n// #endregion\n\n// #region Renderer Types\n/**\n * Context passed to custom view renderers (pure display – no commit helpers).\n *\n * Used by `viewRenderer` and `renderer` column properties to create\n * custom cell content. Return a DOM node or HTML string.\n *\n * @example\n * ```typescript\n * // Status badge renderer\n * const statusRenderer: ColumnViewRenderer = (ctx: CellRenderContext) => {\n * const badge = document.createElement('span');\n * badge.className = `badge badge-${ctx.value}`;\n * badge.textContent = ctx.value;\n * return badge;\n * };\n *\n * // Progress bar using row data\n * const progressRenderer: ColumnViewRenderer = (ctx) => {\n * const bar = document.createElement('div');\n * bar.className = 'progress-bar';\n * bar.style.width = `${ctx.value}%`;\n * bar.title = `${ctx.row.taskName}: ${ctx.value}%`;\n * return bar;\n * };\n *\n * // Return HTML string (simpler, less performant)\n * const htmlRenderer: ColumnViewRenderer = (ctx) => {\n * return `<strong>${ctx.value}</strong>`;\n * };\n * ```\n *\n * @see {@link ColumnViewRenderer} for the renderer function signature\n */\nexport interface CellRenderContext<TRow = any, TValue = any> {\n /** Row object for the cell being rendered. */\n row: TRow;\n /** Value at field. */\n value: TValue;\n /** Field key. */\n field: keyof TRow & string;\n /** Column configuration reference. */\n column: ColumnConfig<TRow>;\n /**\n * The cell DOM element being rendered into.\n * Framework adapters can use this to cache per-cell state (e.g., React roots).\n * @internal\n */\n cellEl?: HTMLElement;\n}\n\n/**\n * Custom view renderer function for cell content.\n *\n * Returns one of:\n * - `Node` - DOM element to display in the cell\n * - `string` - HTML string (parsed and inserted)\n * - `void | null` - Use default text rendering\n *\n * @example\n * ```typescript\n * // DOM element (recommended for interactivity)\n * const avatarRenderer: ColumnViewRenderer<Employee> = (ctx) => {\n * const img = document.createElement('img');\n * img.src = ctx.row.avatarUrl;\n * img.alt = ctx.row.name;\n * img.className = 'avatar';\n * return img;\n * };\n *\n * // HTML string (simpler, good for static content)\n * const emailRenderer: ColumnViewRenderer = (ctx) => {\n * return `<a href=\"mailto:${ctx.value}\">${ctx.value}</a>`;\n * };\n *\n * // Conditional rendering\n * const conditionalRenderer: ColumnViewRenderer = (ctx) => {\n * if (!ctx.value) return null; // Use default\n * return `<em>${ctx.value}</em>`;\n * };\n * ```\n *\n * @see {@link CellRenderContext} for available context properties\n */\nexport type ColumnViewRenderer<TRow = unknown, TValue = unknown> = (\n ctx: CellRenderContext<TRow, TValue>,\n) => Node | string | void | null;\n// #endregion\n\n// #region Header Renderer Types\n/**\n * Context passed to `headerLabelRenderer` for customizing header label content.\n * The framework handles sort icons, filter buttons, resize handles, and click interactions.\n *\n * @example\n * ```typescript\n * headerLabelRenderer: (ctx) => {\n * const span = document.createElement('span');\n * span.innerHTML = `${ctx.value} <span class=\"required\">*</span>`;\n * return span;\n * }\n * ```\n */\nexport interface HeaderLabelContext<TRow = unknown> {\n /** Column configuration reference. */\n column: ColumnConfig<TRow>;\n /** The header text (from column.header or column.field). */\n value: string;\n}\n\n/**\n * Context passed to `headerRenderer` for complete control over header cell content.\n * When using this, you control the header content. Resize handles are added automatically\n * for resizable columns.\n *\n * @example\n * ```typescript\n * headerRenderer: (ctx) => {\n * const div = document.createElement('div');\n * div.className = 'custom-header';\n * div.innerHTML = `<span>${ctx.value}</span>`;\n * // Optionally include sort icon\n * const sortIcon = ctx.renderSortIcon();\n * if (sortIcon) div.appendChild(sortIcon);\n * return div;\n * }\n * ```\n */\nexport interface HeaderCellContext<TRow = unknown> {\n /** Column configuration reference. */\n column: ColumnConfig<TRow>;\n /** The header text (from column.header or column.field). */\n value: string;\n /** Current sort state for this column. */\n sortState: 'asc' | 'desc' | null;\n /** Whether the column has an active filter. */\n filterActive: boolean;\n /** The header cell DOM element being rendered into. */\n cellEl: HTMLElement;\n /**\n * Render the standard sort indicator icon.\n * Returns null if column is not sortable.\n */\n renderSortIcon: () => HTMLElement | null;\n /**\n * Render the standard filter button.\n * Returns null if FilteringPlugin is not active or column is not filterable.\n * Note: The actual button is added by FilteringPlugin's afterRender hook.\n */\n renderFilterButton: () => HTMLElement | null;\n}\n\n/**\n * Header label renderer function type.\n * Customize the label while framework handles sort icons, filter buttons, resize handles.\n *\n * Use this for simple label customizations without taking over the entire header.\n * The grid automatically appends sort icons, filter buttons, and resize handles.\n *\n * @example\n * ```typescript\n * // Add required indicator\n * const requiredHeader: HeaderLabelRenderer = (ctx) => {\n * return `${ctx.value} <span style=\"color: red;\">*</span>`;\n * };\n *\n * // Add unit suffix\n * const priceHeader: HeaderLabelRenderer = (ctx) => {\n * const span = document.createElement('span');\n * span.innerHTML = `${ctx.value} <small>(USD)</small>`;\n * return span;\n * };\n *\n * // Column config usage\n * columns: [\n * { field: 'name', headerLabelRenderer: requiredHeader },\n * { field: 'price', headerLabelRenderer: priceHeader },\n * ]\n * ```\n *\n * @see {@link HeaderLabelContext} for context properties\n * @see {@link HeaderRenderer} for full header control\n */\nexport type HeaderLabelRenderer<TRow = unknown> = (ctx: HeaderLabelContext<TRow>) => Node | string | void | null;\n\n/**\n * Header cell renderer function type.\n * Full control over header cell content. User is responsible for all content and interactions.\n *\n * When using this, you have complete control but must manually include\n * sort icons, filter buttons, and resize handles using the helper functions.\n *\n * @example\n * ```typescript\n * // Custom header with all standard elements\n * const customHeader: HeaderRenderer = (ctx) => {\n * const div = document.createElement('div');\n * div.className = 'custom-header';\n * div.innerHTML = `<span class=\"label\">${ctx.value}</span>`;\n *\n * // Add sort icon (returns null if not sortable)\n * const sortIcon = ctx.renderSortIcon();\n * if (sortIcon) div.appendChild(sortIcon);\n *\n * // Add filter button (returns null if not filterable)\n * const filterBtn = ctx.renderFilterButton();\n * if (filterBtn) div.appendChild(filterBtn);\n *\n * // Resize handles are added automatically for resizable columns\n * return div;\n * };\n *\n * // Minimal header (no sort/resize)\n * const minimalHeader: HeaderRenderer = (ctx) => {\n * return `<div class=\"minimal\">${ctx.value}</div>`;\n * };\n *\n * // Column config usage\n * columns: [\n * { field: 'name', headerRenderer: customHeader },\n * ]\n * ```\n *\n * @see {@link HeaderCellContext} for context properties and helper functions\n * @see {@link HeaderLabelRenderer} for simpler label-only customization\n */\nexport type HeaderRenderer<TRow = unknown> = (ctx: HeaderCellContext<TRow>) => Node | string | void | null;\n// #endregion\n\n// #region Framework Adapter Interface\n/**\n * Framework adapter interface for handling framework-specific component instantiation.\n * Allows framework libraries (Angular, React, Vue) to register handlers that convert\n * declarative light DOM elements into functional renderers/editors.\n *\n * @example\n * ```typescript\n * // In @toolbox-web/grid-angular\n * class AngularGridAdapter implements FrameworkAdapter {\n * canHandle(element: HTMLElement): boolean {\n * return element.tagName.startsWith('APP-');\n * }\n * createRenderer(element: HTMLElement): ColumnViewRenderer {\n * return (ctx) => {\n * // Angular-specific instantiation logic\n * const componentRef = createComponent(...);\n * componentRef.setInput('value', ctx.value);\n * return componentRef.location.nativeElement;\n * };\n * }\n * createEditor(element: HTMLElement): ColumnEditorSpec {\n * return (ctx) => {\n * // Angular-specific editor with commit/cancel\n * const componentRef = createComponent(...);\n * componentRef.setInput('value', ctx.value);\n * // Subscribe to commit/cancel outputs\n * return componentRef.location.nativeElement;\n * };\n * }\n * }\n *\n * // User registers adapter once in their app\n * GridElement.registerAdapter(new AngularGridAdapter(injector, appRef));\n * ```\n * @category Framework Adapters\n */\nexport interface FrameworkAdapter {\n /**\n * Determines if this adapter can handle the given element.\n * Typically checks tag name, attributes, or other conventions.\n */\n canHandle(element: HTMLElement): boolean;\n\n /**\n * Creates a view renderer function from a light DOM element.\n * The renderer receives cell context and returns DOM or string.\n * Returns undefined if no renderer template is registered, allowing the grid\n * to use its default rendering.\n */\n createRenderer<TRow = unknown, TValue = unknown>(element: HTMLElement): ColumnViewRenderer<TRow, TValue> | undefined;\n\n /**\n * Creates an editor spec from a light DOM element.\n * The editor receives context with commit/cancel and returns DOM.\n * Returns undefined if no editor template is registered, allowing the grid\n * to use its default built-in editors.\n */\n createEditor<TRow = unknown, TValue = unknown>(element: HTMLElement): ColumnEditorSpec<TRow, TValue> | undefined;\n\n /**\n * Creates a tool panel renderer from a light DOM element.\n * The renderer receives a container element and optionally returns a cleanup function.\n */\n createToolPanelRenderer?(element: HTMLElement): ((container: HTMLElement) => void | (() => void)) | undefined;\n\n /**\n * Gets type-level defaults from an application-level registry.\n * Used by Angular's `GridTypeRegistry` and React's `GridTypeProvider`.\n *\n * @param type - The column type (e.g., 'date', 'currency', 'country')\n * @returns Type defaults for renderer/editor, or undefined if not registered\n */\n getTypeDefault?<TRow = unknown>(type: string): TypeDefault<TRow> | undefined;\n\n /**\n * Pre-process a grid config before the grid core applies it.\n * Framework adapters use this to convert framework-specific component references\n * (Angular classes, Vue components, React elements) to DOM-returning functions.\n *\n * Called automatically by the grid's `set gridConfig` setter when a\n * `__frameworkAdapter` is present on the grid instance.\n *\n * Must be **idempotent** — already-processed configs must pass through safely.\n *\n * @param config - The raw grid config (may contain framework-specific values)\n * @returns Processed config with DOM-returning functions\n */\n processConfig?<TRow = unknown>(config: GridConfig<TRow>): GridConfig<TRow>;\n\n /**\n * Called when a cell's content is about to be wiped (e.g., when exiting edit mode,\n * scroll-recycling a row, or rebuilding a row).\n *\n * Framework adapters should use this to properly destroy cached views/components\n * associated with the cell to prevent memory leaks.\n *\n * @param cellEl - The cell element whose content is being released\n */\n releaseCell?(cellEl: HTMLElement): void;\n\n /**\n * Unmount a specific framework container and free its resources.\n *\n * Called by the grid core (e.g., MasterDetailPlugin) when a container\n * created by the adapter is about to be removed from the DOM.\n * The adapter should destroy the associated framework instance\n * (React root, Vue app, Angular view) and remove it from tracking arrays.\n *\n * @param container - The container element returned by a create* method\n */\n unmount?(container: HTMLElement): void;\n\n /**\n * Parse a `<tbw-grid-detail>` element and return a detail renderer function.\n * Used by MasterDetailPlugin to support framework-specific detail templates.\n */\n parseDetailElement?<TRow = unknown>(\n element: Element,\n ): ((row: TRow, rowIndex: number) => HTMLElement | string) | undefined;\n\n /**\n * Parse a `<tbw-grid-responsive-card>` element and return a card renderer function.\n * Used by ResponsivePlugin to support framework-specific card templates.\n */\n parseResponsiveCardElement?<TRow = unknown>(\n element: Element,\n ): ((row: TRow, rowIndex: number) => HTMLElement) | undefined;\n}\n// #endregion\n\n// #region Internal Types\n\n/**\n * Column internal properties used during light DOM parsing.\n * Stores attribute-based names before they're resolved to actual functions.\n * @internal\n */\nexport interface ColumnParsedAttributes {\n /** Editor name from `editor` attribute (resolved later) */\n __editorName?: string;\n /** Renderer name from `renderer` attribute (resolved later) */\n __rendererName?: string;\n}\n\n/**\n * Extended column config used internally.\n * Includes all internal properties needed during grid lifecycle.\n *\n * Plugin developers may need to access these when working with\n * column caching and compiled templates.\n *\n * @example\n * ```typescript\n * import type { ColumnInternal } from '@toolbox-web/grid';\n *\n * class MyPlugin extends BaseGridPlugin {\n * afterRender(): void {\n * // Access internal column properties\n * const columns = this.columns as ColumnInternal[];\n * for (const col of columns) {\n * // Check if column was auto-sized\n * if (col.__autoSized) {\n * console.log(`${col.field} was auto-sized`);\n * }\n * }\n * }\n * }\n * ```\n *\n * @see {@link ColumnConfig} for public column properties\n * @category Plugin Development\n * @internal\n */\nexport interface ColumnInternal<T = any> extends ColumnConfig<T>, ColumnParsedAttributes {\n __autoSized?: boolean;\n __userResized?: boolean;\n __renderedWidth?: number;\n /** Original configured width (for reset on double-click) */\n __originalWidth?: number;\n __viewTemplate?: HTMLElement;\n __editorTemplate?: HTMLElement;\n __headerTemplate?: HTMLElement;\n __compiledView?: CompiledViewFunction<T>;\n __compiledEditor?: (ctx: EditorExecContext<T>) => string;\n}\n\n/**\n * Row element with internal tracking properties.\n * Used during virtualization and row pooling.\n *\n * @category Plugin Development\n * @internal\n */\nexport interface RowElementInternal extends HTMLElement {\n /** Epoch marker for row render invalidation */\n __epoch?: number;\n /** Reference to the row data object for change detection */\n __rowDataRef?: unknown;\n /** Count of cells currently in edit mode */\n __editingCellCount?: number;\n /** Flag indicating this is a custom-rendered row (group row, etc.) */\n __isCustomRow?: boolean;\n}\n\n/**\n * Type-safe access to element.part API (DOMTokenList-like).\n * Used for CSS ::part styling support.\n * @internal\n */\nexport interface ElementWithPart {\n part?: DOMTokenList;\n}\n\n/**\n * Compiled view function type with optional blocked flag.\n * The __blocked flag is set when a template contains unsafe expressions.\n *\n * @category Plugin Development\n * @internal\n */\nexport interface CompiledViewFunction<T = any> {\n (ctx: CellContext<T>): string;\n /** Set to true when template was blocked due to unsafe expressions */\n __blocked?: boolean;\n}\n\n/**\n * Runtime cell context used internally for compiled template execution.\n *\n * Contains the minimal context needed to render a cell: the row data,\n * cell value, field name, and column configuration.\n *\n * @example\n * ```typescript\n * import type { CellContext, ColumnInternal } from '@toolbox-web/grid';\n *\n * // Used internally by compiled templates\n * const renderCell = (ctx: CellContext) => {\n * return `<span title=\"${ctx.field}\">${ctx.value}</span>`;\n * };\n * ```\n *\n * @see {@link CellRenderContext} for public cell render context\n * @see {@link EditorExecContext} for editor context with commit/cancel\n * @category Plugin Development\n */\nexport interface CellContext<T = any> {\n row: T;\n value: unknown;\n field: string;\n column: ColumnInternal<T>;\n}\n\n/**\n * Internal editor execution context extending the generic cell context with commit helpers.\n *\n * Used internally by the editing system. For public editor APIs,\n * prefer using {@link ColumnEditorContext}.\n *\n * @example\n * ```typescript\n * import type { EditorExecContext } from '@toolbox-web/grid';\n *\n * // Internal editor template execution\n * const execEditor = (ctx: EditorExecContext) => {\n * const input = document.createElement('input');\n * input.value = String(ctx.value);\n * input.onkeydown = (e) => {\n * if (e.key === 'Enter') ctx.commit(input.value);\n * if (e.key === 'Escape') ctx.cancel();\n * };\n * return input;\n * };\n * ```\n *\n * @see {@link ColumnEditorContext} for public editor context\n * @see {@link CellContext} for base cell context\n * @category Plugin Development\n */\nexport interface EditorExecContext<T = any> extends CellContext<T> {\n commit: (newValue: unknown) => void;\n cancel: () => void;\n}\n\n/**\n * Controller managing drag-based column resize lifecycle.\n *\n * Exposed internally for plugins that need to interact with resize behavior.\n *\n * @example\n * ```typescript\n * import type { ResizeController, InternalGrid } from '@toolbox-web/grid';\n *\n * class MyPlugin extends BaseGridPlugin {\n * handleColumnAction(colIndex: number): void {\n * const grid = this.grid as InternalGrid;\n * const resizeCtrl = grid._resizeController;\n *\n * // Check if resize is in progress\n * if (resizeCtrl?.isResizing) {\n * return; // Don't interfere\n * }\n *\n * // Reset column to configured width\n * resizeCtrl?.resetColumn(colIndex);\n * }\n * }\n * ```\n *\n * @see {@link ColumnResizeDetail} for resize event details\n * @category Plugin Development\n */\nexport interface ResizeController {\n start: (e: MouseEvent, colIndex: number, cell: HTMLElement) => void;\n /** Reset a column to its configured width (or auto-size if none configured). */\n resetColumn: (colIndex: number) => void;\n dispose: () => void;\n /** True while a resize drag is in progress (used to suppress header click/sort). */\n isResizing: boolean;\n}\n\n/**\n * Virtual window bookkeeping; modified in-place as scroll position changes.\n *\n * Tracks virtualization state for row rendering. The grid only renders\n * rows within the visible viewport window (start to end) plus overscan.\n *\n * @example\n * ```typescript\n * import type { VirtualState, InternalGrid } from '@toolbox-web/grid';\n *\n * class MyPlugin extends BaseGridPlugin {\n * logVirtualWindow(): void {\n * const grid = this.grid as InternalGrid;\n * const vs = grid.virtualization;\n *\n * console.log(`Row height: ${vs.rowHeight}px`);\n * console.log(`Visible rows: ${vs.start} to ${vs.end}`);\n * console.log(`Virtualization: ${vs.enabled ? 'on' : 'off'}`);\n * }\n * }\n * ```\n *\n * @see {@link GridConfig.rowHeight} for configuring row height\n * @category Plugin Development\n */\nexport interface VirtualState {\n enabled: boolean;\n rowHeight: number;\n /** Threshold for bypassing virtualization (renders all rows if totalRows <= bypassThreshold) */\n bypassThreshold: number;\n start: number;\n end: number;\n /** Faux scrollbar element that provides scroll events (AG Grid pattern) */\n container: HTMLElement | null;\n /** Rows viewport element for measuring visible area height */\n viewportEl: HTMLElement | null;\n /** Spacer element inside faux scrollbar for setting virtual height */\n totalHeightEl: HTMLElement | null;\n\n // --- Variable Row Height Support (Phase 1) ---\n\n /**\n * Position cache for variable row heights.\n * Index-based array mapping row index → {offset, height, measured}.\n * Rebuilt when row count changes (expand/collapse, filter).\n * `null` when using uniform row heights (default).\n */\n positionCache: RowPositionEntry[] | null;\n\n /**\n * Height cache by row identity.\n * Persists row heights across position cache rebuilds.\n * Uses dual storage: Map for string keys (rowId, __rowCacheKey) and WeakMap for object refs.\n */\n heightCache: {\n /** Heights keyed by string (synthetic rows with __rowCacheKey, or rowId-keyed rows) */\n byKey: Map<string, number>;\n /** Heights keyed by object reference (data rows without rowId) */\n byRef: WeakMap<object, number>;\n };\n\n /** Running average of measured row heights. Used for estimating unmeasured rows. */\n averageHeight: number;\n\n /** Number of rows that have been measured. */\n measuredCount: number;\n\n /** Whether variable row height mode is active (rowHeight is a function). */\n variableHeights: boolean;\n\n // --- Cached Geometry (avoid forced layout reads on scroll hot path) ---\n\n /** Cached viewport element height. Updated by ResizeObserver and force-refresh only. */\n cachedViewportHeight: number;\n\n /** Cached faux scrollbar element height. Updated alongside viewport height. */\n cachedFauxHeight: number;\n\n /** Cached scroll-area element height. Updated alongside viewport/faux heights. */\n cachedScrollAreaHeight: number;\n\n /** Cached reference to .tbw-scroll-area element. Set during scroll listener setup. */\n scrollAreaEl: HTMLElement | null;\n}\n\n// RowElementInternal is now defined earlier in the file with all internal properties\n\n/**\n * Union type for input-like elements that have a `value` property.\n * Covers standard form elements and custom elements with value semantics.\n *\n * @category Plugin Development\n * @internal\n */\nexport type InputLikeElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | { value: unknown };\n// #endregion\n\n// #region Grouping & Footer Public Types\n/**\n * Group row rendering customization options.\n * Controls how group header rows are displayed in the GroupingRowsPlugin.\n *\n * @example\n * ```typescript\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/all';\n *\n * new GroupingRowsPlugin({\n * groupOn: (row) => [row.department, row.team],\n * render: {\n * // Group row spans all columns\n * fullWidth: true,\n *\n * // Custom label format\n * formatLabel: (value, depth, key) => {\n * if (depth === 0) return `Department: ${value}`;\n * return `Team: ${value}`;\n * },\n *\n * // Show aggregates in group rows (when not fullWidth)\n * aggregators: {\n * salary: 'sum',\n * age: 'avg',\n * },\n *\n * // Custom CSS class\n * class: 'my-group-row',\n * },\n * });\n * ```\n *\n * @see {@link AggregatorRef} for aggregation options\n */\nexport interface RowGroupRenderConfig {\n /** If true, group rows span all columns (single full-width cell). Default false. */\n fullWidth?: boolean;\n /** Optional label formatter override. Receives raw group value + depth. */\n formatLabel?: (value: unknown, depth: number, key: string) => string;\n /** Optional aggregate overrides per field for group summary cells (only when not fullWidth). */\n aggregators?: Record<string, AggregatorRef>;\n /** Additional CSS class applied to each group row root element. */\n class?: string;\n}\n\n/**\n * Reference to an aggregation function for footer/group summaries.\n *\n * Can be either:\n * - A built-in aggregator name: `'sum'`, `'avg'`, `'min'`, `'max'`, `'count'`\n * - A custom function that calculates the aggregate value\n *\n * @example\n * ```typescript\n * // Built-in aggregator\n * { field: 'amount', aggregator: 'sum' }\n *\n * // Custom aggregator function\n * { field: 'price', aggregator: (rows, field) => {\n * const values = rows.map(r => r[field]).filter(v => v != null);\n * return values.length ? Math.max(...values) : null;\n * }}\n * ```\n *\n * @see {@link RowGroupRenderConfig} for using aggregators in group rows\n */\nexport type AggregatorRef = string | ((rows: unknown[], field: string, column?: unknown) => unknown);\n\n/**\n * Result of automatic column inference from sample rows.\n *\n * When no columns are configured, the grid analyzes the first row of data\n * to automatically generate column definitions with inferred types.\n *\n * @example\n * ```typescript\n * // Automatic inference (no columns configured)\n * grid.rows = [\n * { name: 'Alice', age: 30, active: true, hireDate: new Date() },\n * ];\n * // Grid infers:\n * // - name: type 'string'\n * // - age: type 'number'\n * // - active: type 'boolean'\n * // - hireDate: type 'date'\n *\n * // Access inferred result programmatically\n * const config = await grid.getConfig();\n * console.log(config.columns); // Inferred columns\n * ```\n *\n * @see {@link ColumnConfig} for column configuration options\n * @see {@link ColumnType} for type inference rules\n */\nexport interface InferredColumnResult<TRow = unknown> {\n /** Generated column configurations based on data analysis */\n columns: ColumnConfigMap<TRow>;\n /** Map of field names to their inferred types */\n typeMap: Record<string, ColumnType>;\n}\n\n/**\n * Column sizing mode.\n *\n * - `'fixed'` - Columns use their configured widths. Horizontal scrolling if content overflows.\n * - `'stretch'` - Columns stretch proportionally to fill available width. No horizontal scrolling.\n *\n * @example\n * ```typescript\n * // Fixed widths - good for many columns\n * grid.fitMode = 'fixed';\n *\n * // Stretch to fill - good for few columns\n * grid.fitMode = 'stretch';\n *\n * // Via gridConfig\n * grid.gridConfig = { fitMode: 'stretch' };\n * ```\n */\nexport const FitModeEnum = {\n STRETCH: 'stretch',\n FIXED: 'fixed',\n} as const;\nexport type FitMode = (typeof FitModeEnum)[keyof typeof FitModeEnum]; // evaluates to 'stretch' | 'fixed'\n// #endregion\n\n// #region Plugin Interface\n/**\n * Minimal plugin interface for type-checking.\n * This interface is defined here to avoid circular imports with BaseGridPlugin.\n * All plugins must satisfy this shape (BaseGridPlugin implements it).\n *\n * @example\n * ```typescript\n * // Using plugins in grid config\n * import { SelectionPlugin, FilteringPlugin } from '@toolbox-web/grid/all';\n *\n * grid.gridConfig = {\n * plugins: [\n * new SelectionPlugin({ mode: 'row' }),\n * new FilteringPlugin({ debounceMs: 200 }),\n * ],\n * };\n *\n * // Accessing plugin instance at runtime (preferred)\n * const selection = grid.getPluginByName('selection');\n * if (selection) {\n * selection.selectAll();\n * }\n * ```\n *\n * @category Plugin Development\n */\nexport interface GridPlugin {\n /** Unique plugin identifier */\n readonly name: string;\n /** Plugin version */\n readonly version: string;\n /** CSS styles to inject into the grid */\n readonly styles?: string;\n}\n\n/**\n * Plugin name-to-type registry for type-safe `getPluginByName()`.\n *\n * Plugins augment this interface via `declare module` so that\n * `grid.getPluginByName('editing')` returns `EditingPlugin | undefined`\n * instead of `GridPlugin | undefined`.\n *\n * @example\n * ```typescript\n * // Plugin augmentation (done automatically when you import a plugin):\n * declare module '../../core/types' {\n * interface PluginNameMap {\n * editing: EditingPlugin;\n * }\n * }\n *\n * // Consumer usage — fully typed:\n * const editing = grid.getPluginByName('editing');\n * editing?.beginBulkEdit(0); // ✅ No cast needed\n * ```\n *\n * @category Plugin Development\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface\nexport interface PluginNameMap {}\n// #endregion\n\n// #region Feature Config\n/**\n * Declarative feature configuration interface.\n *\n * This interface is intentionally empty in core — it is populated via **module augmentation**\n * by feature side-effect imports (`@toolbox-web/grid/features/selection`, etc.).\n * Third-party plugins can also augment this interface to add their own features.\n *\n * @example\n * ```ts\n * // Each feature import augments this interface:\n * import '@toolbox-web/grid/features/selection';\n * import '@toolbox-web/grid/features/filtering';\n *\n * grid.gridConfig = {\n * features: {\n * selection: 'range', // ← typed by selection feature module\n * filtering: { debounceMs: 200 }, // ← typed by filtering feature module\n * },\n * };\n * ```\n *\n * @example Third-party augmentation\n * ```ts\n * declare module '@toolbox-web/grid' {\n * interface FeatureConfig {\n * sparkline?: boolean | SparklineConfig;\n * }\n * }\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface\nexport interface FeatureConfig<TRow = unknown> {}\n// #endregion\n\n// #region Grid Config\n/**\n * Grid configuration object - the **single source of truth** for grid behavior.\n *\n * Users can configure the grid via multiple input methods, all of which converge\n * into an effective `GridConfig` internally:\n *\n * **Configuration Input Methods:**\n * - `gridConfig` property - direct assignment of this object\n * - `columns` property - shorthand for `gridConfig.columns`\n * - `fitMode` property - shorthand for `gridConfig.fitMode`\n * - Light DOM `<tbw-grid-column>` - declarative columns (merged into `columns`)\n * - Light DOM `<tbw-grid-header>` - declarative shell header (merged into `shell.header`)\n *\n * **Precedence (when same property set multiple ways):**\n * Individual props (`fitMode`) > `columns` prop > Light DOM > `gridConfig`\n *\n * @example\n * ```ts\n * // Via gridConfig (recommended for complex setups)\n * grid.gridConfig = {\n * columns: [{ field: 'name' }, { field: 'age' }],\n * fitMode: 'stretch',\n * plugins: [new SelectionPlugin()],\n * shell: { header: { title: 'My Grid' } }\n * };\n *\n * // Via individual props (convenience for simple cases)\n * grid.columns = [{ field: 'name' }, { field: 'age' }];\n * grid.fitMode = 'stretch';\n * ```\n */\nexport interface GridConfig<TRow = any> {\n /**\n * Column definitions. Can also be set via `columns` prop or `<tbw-grid-column>` light DOM.\n * @see {@link ColumnConfig} for column options\n * @see {@link ColumnConfigMap}\n */\n columns?: ColumnConfigMap<TRow>;\n /**\n * Dynamic CSS class(es) for data rows.\n * Called for each row during rendering. Return class names to add to the row element.\n *\n * @example\n * ```typescript\n * // Highlight inactive rows\n * rowClass: (row) => row.active ? [] : ['inactive', 'dimmed']\n *\n * // Status-based row styling\n * rowClass: (row) => [`priority-${row.priority}`]\n *\n * // Single class as string\n * rowClass: (row) => row.isNew ? 'new-row' : ''\n * ```\n */\n rowClass?: (row: TRow) => string | string[];\n /** Sizing mode for columns. Can also be set via `fitMode` prop. */\n fitMode?: FitMode;\n\n /**\n * Grid-wide sorting toggle.\n * When false, disables sorting for all columns regardless of their individual `sortable` setting.\n * When true (default), columns with `sortable: true` can be sorted.\n *\n * This affects:\n * - Header click handlers for sorting\n * - Sort indicator visibility\n * - Multi-sort plugin behavior (if loaded)\n *\n * @default true\n *\n * @example\n * ```typescript\n * // Disable all sorting\n * gridConfig = { sortable: false };\n *\n * // Enable sorting (default) - individual columns still need sortable: true\n * gridConfig = { sortable: true };\n * ```\n */\n sortable?: boolean;\n\n /**\n * Grid-wide resizing toggle.\n * When false, disables column resizing for all columns regardless of their individual `resizable` setting.\n * When true (default), columns with `resizable: true` (or resizable not set, since it defaults to true) can be resized.\n *\n * This affects:\n * - Resize handle visibility in header cells\n * - Double-click to auto-size behavior\n *\n * @default true\n *\n * @example\n * ```typescript\n * // Disable all column resizing\n * gridConfig = { resizable: false };\n *\n * // Enable resizing (default) - individual columns can opt out with resizable: false\n * gridConfig = { resizable: true };\n * ```\n */\n resizable?: boolean;\n\n /**\n * Row height in pixels for virtualization calculations.\n * The virtualization system assumes uniform row heights for performance.\n *\n * If not specified, the grid measures the first rendered row's height,\n * which respects the CSS variable `--tbw-row-height` set by themes.\n *\n * Set this explicitly when:\n * - Row content may wrap to multiple lines (also set `--tbw-cell-white-space: normal`)\n * - Using custom row templates with variable content\n * - You want to override theme-defined row height\n * - Rows have different heights based on content (use function form)\n *\n * **Variable Row Heights**: When a function is provided, the grid enables variable height\n * virtualization. Heights are measured on first render and cached by row identity.\n *\n * @default Auto-measured from first row (respects --tbw-row-height CSS variable)\n *\n * @example\n * ```ts\n * // Fixed height for all rows\n * gridConfig = { rowHeight: 56 };\n *\n * // Variable height based on content\n * gridConfig = {\n * rowHeight: (row, index) => row.hasDetails ? 80 : 40,\n * };\n *\n * // Return undefined to trigger DOM auto-measurement\n * gridConfig = {\n * rowHeight: (row) => row.isExpanded ? undefined : 40,\n * };\n * ```\n */\n rowHeight?: number | ((row: TRow, index: number) => number | undefined);\n /**\n * Array of plugin instances.\n * Each plugin is instantiated with its configuration and attached to this grid.\n *\n * @example\n * ```ts\n * plugins: [\n * new SelectionPlugin({ mode: 'range' }),\n * new MultiSortPlugin(),\n * new FilteringPlugin({ debounceMs: 150 }),\n * ]\n * ```\n */\n plugins?: GridPlugin[];\n\n /**\n * Declarative feature configuration.\n * Alternative to manually creating plugin instances in `plugins`.\n * Features are resolved using the core feature registry.\n *\n * Import feature modules as side effects to register them:\n * ```ts\n * import '@toolbox-web/grid/features/selection';\n * import '@toolbox-web/grid/features/filtering';\n * ```\n *\n * Then configure declaratively:\n * ```ts\n * gridConfig = {\n * features: {\n * selection: 'range',\n * filtering: { debounceMs: 200 },\n * editing: 'dblclick',\n * },\n * };\n * ```\n *\n * Both `features` and `plugins` can be used together — features-generated plugins\n * are created first, then manual `plugins` are appended. Duplicates are skipped\n * (manual `plugins` take precedence).\n */\n features?: Partial<FeatureConfig<TRow>>;\n\n /**\n * Saved column state to restore on initialization.\n * Includes order, width, visibility, sort, and plugin-contributed state.\n */\n columnState?: GridColumnState;\n\n /**\n * Shell configuration for header bar and tool panels.\n * When configured, adds an optional wrapper with title, toolbar, and collapsible side panels.\n */\n shell?: ShellConfig;\n\n /**\n * Grid-wide icon configuration.\n * Provides consistent icons across all plugins (tree, grouping, sorting, etc.).\n * Plugins will use these by default but can override with their own config.\n */\n icons?: GridIcons;\n\n /**\n * Grid-wide animation configuration.\n * Controls animations for expand/collapse, reordering, and other visual transitions.\n * Individual plugins can override these defaults in their own config.\n */\n animation?: AnimationConfig;\n\n /**\n * Custom sort handler for full control over sorting behavior.\n *\n * When provided, this handler is called instead of the built-in sorting logic.\n * Enables custom sorting algorithms, server-side sorting, or plugin-specific sorting.\n *\n * The handler receives:\n * - `rows`: Current row array to sort\n * - `sortState`: Sort field and direction (1 = asc, -1 = desc)\n * - `columns`: Column configurations (for accessing sortComparator)\n *\n * Return the sorted array (sync) or a Promise that resolves to the sorted array (async).\n * For server-side sorting, return a Promise that resolves when data is fetched.\n *\n * @example\n * ```ts\n * // Custom stable sort\n * sortHandler: (rows, state, cols) => {\n * return stableSort(rows, (a, b) => compare(a[state.field], b[state.field]) * state.direction);\n * }\n *\n * // Server-side sorting\n * sortHandler: async (rows, state) => {\n * const response = await fetch(`/api/data?sort=${state.field}&dir=${state.direction}`);\n * return response.json();\n * }\n * ```\n */\n sortHandler?: SortHandler<TRow>;\n\n /**\n * Function to extract a unique identifier from a row.\n * Used by `updateRow()`, `getRow()`, and ID-based tracking.\n *\n * If not provided, falls back to `row.id` or `row._id` if present.\n * Rows without IDs are silently skipped during map building.\n * Only throws when explicitly calling `getRowId()` or `updateRow()` on a row without an ID.\n *\n * @example\n * ```ts\n * // Simple field\n * getRowId: (row) => row.id\n *\n * // Composite key\n * getRowId: (row) => `${row.voyageId}-${row.legNumber}`\n *\n * // UUID field\n * getRowId: (row) => row.uuid\n * ```\n */\n getRowId?: (row: TRow) => string;\n\n /**\n * Type-level renderer and editor defaults.\n *\n * Keys can be:\n * - Built-in types: `'string'`, `'number'`, `'date'`, `'boolean'`, `'select'`\n * - Custom types: `'currency'`, `'country'`, `'status'`, etc.\n *\n * Resolution order (highest priority first):\n * 1. Column-level (`column.renderer` / `column.editor`)\n * 2. Grid-level (`gridConfig.typeDefaults[column.type]`)\n * 3. App-level (Angular `GridTypeRegistry`, React `GridTypeProvider`)\n * 4. Built-in (checkbox for boolean, select for select, etc.)\n * 5. Fallback (plain text / text input)\n *\n * @example\n * ```typescript\n * typeDefaults: {\n * date: { editor: myDatePickerEditor },\n * country: {\n * renderer: (ctx) => {\n * const span = document.createElement('span');\n * span.innerHTML = `<img src=\"/flags/${ctx.value}.svg\" /> ${ctx.value}`;\n * return span;\n * },\n * editor: (ctx) => createCountrySelect(ctx)\n * }\n * }\n * ```\n */\n typeDefaults?: Record<string, TypeDefault<TRow>>;\n\n // #region Accessibility\n\n /**\n * Accessible label for the grid.\n * Sets `aria-label` on the grid's internal table element for screen readers.\n *\n * If not provided and `shell.header.title` is set, the title is used automatically.\n *\n * @example\n * ```ts\n * gridConfig = { gridAriaLabel: 'Employee data' };\n * ```\n */\n gridAriaLabel?: string;\n\n /**\n * ID of an element that describes the grid.\n * Sets `aria-describedby` on the grid's internal table element.\n *\n * @example\n * ```html\n * <p id=\"grid-desc\">This table shows all active employees.</p>\n * <tbw-grid></tbw-grid>\n * ```\n * ```ts\n * gridConfig = { gridAriaDescribedBy: 'grid-desc' };\n * ```\n */\n gridAriaDescribedBy?: string;\n\n // #endregion\n\n // #region Loading\n\n /**\n * Custom renderer for the loading overlay.\n *\n * When provided, replaces the default spinner with custom content.\n * Receives a context object with the current loading size.\n *\n * @example\n * ```typescript\n * // Simple text loading indicator\n * loadingRenderer: () => {\n * const el = document.createElement('div');\n * el.textContent = 'Loading...';\n * return el;\n * }\n *\n * // Custom spinner component\n * loadingRenderer: (ctx) => {\n * const spinner = document.createElement('my-spinner');\n * spinner.size = ctx.size === 'large' ? 48 : 24;\n * return spinner;\n * }\n * ```\n */\n loadingRenderer?: LoadingRenderer;\n\n // #endregion\n}\n// #endregion\n\n// #region Animation\n\n/**\n * Sort state passed to custom sort handlers.\n * Represents the current sorting configuration for a column.\n *\n * @example\n * ```typescript\n * // In a custom sort handler\n * const sortHandler: SortHandler = (rows, sortState, columns) => {\n * const { field, direction } = sortState;\n * console.log(`Sorting by ${field} ${direction === 1 ? 'ASC' : 'DESC'}`);\n *\n * return [...rows].sort((a, b) => {\n * const aVal = a[field];\n * const bVal = b[field];\n * return (aVal < bVal ? -1 : aVal > bVal ? 1 : 0) * direction;\n * });\n * };\n * ```\n *\n * @see {@link SortHandler} for custom sort handler signature\n * @see {@link SortChangeDetail} for sort change events\n */\nexport interface SortState {\n /** Field to sort by */\n field: string;\n /** Sort direction: 1 = ascending, -1 = descending */\n direction: 1 | -1;\n}\n\n/**\n * Custom sort handler function signature.\n *\n * Enables full control over sorting behavior including server-side sorting,\n * custom algorithms, or multi-column sorting.\n *\n * @param rows - Current row array to sort\n * @param sortState - Sort field and direction\n * @param columns - Column configurations (for accessing sortComparator)\n * @returns Sorted array (sync) or Promise resolving to sorted array (async)\n *\n * @example\n * ```typescript\n * // Custom client-side sort with locale awareness\n * const localeSortHandler: SortHandler<Employee> = (rows, state, cols) => {\n * const col = cols.find(c => c.field === state.field);\n * return [...rows].sort((a, b) => {\n * const aVal = String(a[state.field] ?? '');\n * const bVal = String(b[state.field] ?? '');\n * return aVal.localeCompare(bVal) * state.direction;\n * });\n * };\n *\n * // Server-side sorting\n * const serverSortHandler: SortHandler<Employee> = async (rows, state) => {\n * const response = await fetch(\n * `/api/employees?sortBy=${state.field}&dir=${state.direction}`\n * );\n * return response.json();\n * };\n *\n * grid.gridConfig = {\n * sortHandler: localeSortHandler,\n * };\n * ```\n *\n * @see {@link SortState} for the sort state object\n * @see {@link GridConfig.sortHandler} for configuring the handler\n * @see {@link BaseColumnConfig.sortComparator} for column-level comparators\n */\nexport type SortHandler<TRow = any> = (\n rows: TRow[],\n sortState: SortState,\n columns: ColumnConfig<TRow>[],\n) => TRow[] | Promise<TRow[]>;\n\n// #region Loading\n\n/**\n * Loading indicator size variant.\n *\n * - `'large'`: 48x48px max - used for grid-level loading overlay (`grid.loading = true`)\n * - `'small'`: Follows row height - used for row/cell loading states\n *\n * @example\n * ```typescript\n * // Custom loading renderer adapting to size\n * const myLoader: LoadingRenderer = (ctx) => {\n * if (ctx.size === 'large') {\n * // Full overlay spinner\n * return '<div class=\"spinner-lg\"></div>';\n * }\n * // Inline row/cell spinner\n * return '<span class=\"spinner-sm\"></span>';\n * };\n * ```\n *\n * @see {@link LoadingRenderer} for custom loading renderer\n * @see {@link LoadingContext} for context passed to renderers\n */\nexport type LoadingSize = 'large' | 'small';\n\n/**\n * Context passed to custom loading renderers.\n *\n * Provides information about the loading indicator being rendered,\n * allowing the renderer to adapt its appearance based on the size variant.\n *\n * @example\n * ```typescript\n * const myLoadingRenderer: LoadingRenderer = (ctx: LoadingContext) => {\n * if (ctx.size === 'large') {\n * // Full-size spinner for grid-level loading\n * return '<div class=\"large-spinner\"></div>';\n * } else {\n * // Compact spinner for row/cell loading\n * return '<div class=\"small-spinner\"></div>';\n * }\n * };\n * ```\n *\n * @see {@link LoadingRenderer} for the renderer function signature\n * @see {@link LoadingSize} for available size variants\n */\nexport interface LoadingContext {\n /** The size variant being rendered: 'large' for grid-level, 'small' for row/cell */\n size: LoadingSize;\n}\n\n/**\n * Custom loading renderer function.\n * Returns an element or HTML string to display as the loading indicator.\n *\n * Used with the `loadingRenderer` property in {@link GridConfig} to replace\n * the default spinner with custom content.\n *\n * @param context - Context containing size information\n * @returns HTMLElement or HTML string\n *\n * @example\n * ```typescript\n * // Simple text loading indicator\n * const textLoader: LoadingRenderer = () => {\n * const el = document.createElement('div');\n * el.textContent = 'Loading...';\n * return el;\n * };\n *\n * // Custom spinner with size awareness\n * const customSpinner: LoadingRenderer = (ctx) => {\n * const spinner = document.createElement('my-spinner');\n * spinner.size = ctx.size === 'large' ? 48 : 24;\n * return spinner;\n * };\n *\n * // Material Design-style progress bar\n * const progressBar: LoadingRenderer = () => {\n * const container = document.createElement('div');\n * container.className = 'progress-bar-container';\n * container.innerHTML = '<div class=\"progress-bar\"></div>';\n * return container;\n * };\n *\n * grid.gridConfig = {\n * loadingRenderer: customSpinner,\n * };\n * ```\n *\n * @see {@link LoadingContext} for the context object passed to the renderer\n * @see {@link LoadingSize} for size variants ('large' | 'small')\n */\nexport type LoadingRenderer = (context: LoadingContext) => HTMLElement | string;\n\n// #endregion\n\n// #region Data Change Event\n\n/**\n * Detail for the `data-change` event.\n *\n * Fired whenever the grid's row data changes — including new data assignment,\n * row insertion/removal, and in-place mutations via `updateRow()`.\n *\n * Use this to keep external UI in sync with the grid's current data state\n * (row counts, summaries, charts, etc.).\n *\n * @example\n * ```typescript\n * grid.on('data-change', ({ rowCount, sourceRowCount }) => {\n * console.log(`${rowCount} rows visible of ${sourceRowCount} total`);\n * });\n * ```\n *\n * @see {@link DataGridEventMap} for all event types\n * @category Events\n */\nexport interface DataChangeDetail {\n /** Number of visible (processed) rows */\n rowCount: number;\n /** Total number of source rows (before filtering/grouping) */\n sourceRowCount: number;\n}\n\n// #endregion\n\n// #region Data Update Management\n\n/**\n * Indicates the origin of a data change.\n * Used to prevent infinite loops in cascade update handlers.\n *\n * - `'user'`: Direct user interaction via EditingPlugin (typing, selecting)\n * - `'cascade'`: Triggered by `updateRow()` in an event handler\n * - `'api'`: External programmatic update via `grid.updateRow()`\n *\n * @example\n * ```typescript\n * grid.on('cell-change', (detail) => {\n * const { source, field, newValue } = detail;\n *\n * // Only cascade updates for user edits\n * if (source === 'user' && field === 'price') {\n * // Update calculated field (marked as 'cascade')\n * grid.updateRow(detail.rowId, {\n * total: newValue * detail.row.quantity,\n * });\n * }\n *\n * // Ignore cascade updates to prevent infinite loops\n * if (source === 'cascade') return;\n * });\n * ```\n *\n * @see {@link CellChangeDetail} for the event detail containing source\n * @category Data Management\n */\nexport type UpdateSource = 'user' | 'cascade' | 'api';\n\n/**\n * Detail for cell-change event (emitted by core after mutation).\n * This is an informational event that fires for ALL data mutations.\n *\n * Use this event for:\n * - Logging/auditing changes\n * - Cascading updates (updating other fields based on a change)\n * - Syncing changes to external state\n *\n * @example\n * ```typescript\n * grid.on('cell-change', ({ row, rowId, field, oldValue, newValue, source }) => {\n * console.log(`${field} changed from ${oldValue} to ${newValue}`);\n * console.log(`Change source: ${source}`);\n *\n * // Cascade: update total when price changes\n * if (source === 'user' && field === 'price') {\n * grid.updateRow(rowId, { total: newValue * row.quantity });\n * }\n * });\n * ```\n *\n * @see {@link UpdateSource} for understanding change origins\n * @see {@link CellCommitDetail} for the commit event (editing lifecycle)\n * @category Events\n */\nexport interface CellChangeDetail<TRow = unknown> {\n /** The row object (after mutation) */\n row: TRow;\n /** Stable row identifier */\n rowId: string;\n /** Current index in rows array */\n rowIndex: number;\n /** Field that changed */\n field: string;\n /** Value before change */\n oldValue: unknown;\n /** Value after change */\n newValue: unknown;\n /** All changes passed to updateRow/updateRows (for context) */\n changes: Partial<TRow>;\n /** Origin of this change */\n source: UpdateSource;\n}\n\n/**\n * Batch update specification for updateRows().\n *\n * Used when you need to update multiple rows at once efficiently.\n * The grid will batch all updates and trigger a single re-render.\n *\n * @example\n * ```typescript\n * // Update multiple rows in a single batch\n * const updates: RowUpdate<Employee>[] = [\n * { id: 'emp-1', changes: { status: 'active', updatedAt: new Date() } },\n * { id: 'emp-2', changes: { status: 'inactive' } },\n * { id: 'emp-3', changes: { salary: 75000 } },\n * ];\n *\n * grid.updateRows(updates);\n * ```\n *\n * @see {@link CellChangeDetail} for individual change events\n * @see {@link GridConfig.getRowId} for row identification\n * @category Data Management\n */\nexport interface RowUpdate<TRow = unknown> {\n /** Row identifier (from getRowId) */\n id: string;\n /** Fields to update */\n changes: Partial<TRow>;\n}\n\n// #endregion\n\n/**\n * Animation behavior mode.\n * - `true` or `'on'`: Animations always enabled\n * - `false` or `'off'`: Animations always disabled\n * - `'reduced-motion'`: Respects `prefers-reduced-motion` media query (default)\n *\n * @example\n * ```typescript\n * // Force animations on (ignore system preference)\n * grid.gridConfig = { animation: { mode: 'on' } };\n *\n * // Disable all animations\n * grid.gridConfig = { animation: { mode: false } };\n *\n * // Respect user's accessibility settings (default)\n * grid.gridConfig = { animation: { mode: 'reduced-motion' } };\n * ```\n *\n * @see {@link AnimationConfig} for full animation configuration\n */\nexport type AnimationMode = boolean | 'on' | 'off' | 'reduced-motion';\n\n/**\n * Animation style for visual transitions.\n * - `'slide'`: Slide/transform animation (e.g., expand down, slide left/right)\n * - `'fade'`: Opacity fade animation\n * - `'flip'`: FLIP technique for position changes (First, Last, Invert, Play)\n * - `false`: No animation for this specific feature\n *\n * @example\n * ```typescript\n * // Plugin-specific animation styles\n * new TreePlugin({\n * expandAnimation: 'slide', // Slide children down when expanding\n * });\n *\n * new ReorderPlugin({\n * animation: 'flip', // FLIP animation for column reordering\n * });\n * ```\n *\n * @see {@link AnimationConfig} for grid-wide animation settings\n * @see {@link ExpandCollapseAnimation} for expand/collapse-specific styles\n */\nexport type AnimationStyle = 'slide' | 'fade' | 'flip' | false;\n\n/**\n * Animation style for expand/collapse operations.\n * Subset of AnimationStyle - excludes 'flip' which is for position changes.\n * - `'slide'`: Slide down/up animation for expanding/collapsing content\n * - `'fade'`: Fade in/out animation\n * - `false`: No animation\n *\n * @example\n * ```typescript\n * // Tree rows slide down when expanding\n * new TreePlugin({ expandAnimation: 'slide' });\n *\n * // Row groups fade in/out\n * new GroupingRowsPlugin({ expandAnimation: 'fade' });\n *\n * // Master-detail panels with no animation\n * new MasterDetailPlugin({ expandAnimation: false });\n * ```\n *\n * @see {@link AnimationStyle} for all animation styles\n * @see {@link AnimationConfig} for grid-wide settings\n */\nexport type ExpandCollapseAnimation = 'slide' | 'fade' | false;\n\n/**\n * Type of row animation.\n * - `'change'`: Flash highlight when row data changes (e.g., after cell edit)\n * - `'insert'`: Slide-in animation for newly added rows\n * - `'remove'`: Fade-out animation for rows being removed\n *\n * @example\n * ```typescript\n * // Internal usage - row animation is triggered automatically:\n * // - 'change' after cell-commit event\n * // - 'insert' when rows are added to the grid\n * // - 'remove' when rows are deleted\n *\n * // The animation respects AnimationConfig.mode\n * grid.gridConfig = {\n * animation: { mode: 'on', duration: 300 },\n * };\n * ```\n *\n * @see {@link AnimationConfig} for animation configuration\n */\nexport type RowAnimationType = 'change' | 'insert' | 'remove';\n\n/**\n * Grid-wide animation configuration.\n * Controls global animation behavior - individual plugins define their own animation styles.\n * Duration and easing values set corresponding CSS variables on the grid element.\n *\n * @example\n * ```typescript\n * // Enable animations regardless of system preferences\n * grid.gridConfig = {\n * animation: {\n * mode: 'on',\n * duration: 300,\n * easing: 'cubic-bezier(0.4, 0, 0.2, 1)',\n * },\n * };\n *\n * // Disable all animations\n * grid.gridConfig = {\n * animation: { mode: 'off' },\n * };\n *\n * // Respect user's reduced-motion preference (default)\n * grid.gridConfig = {\n * animation: { mode: 'reduced-motion' },\n * };\n * ```\n *\n * @see {@link AnimationMode} for mode options\n */\nexport interface AnimationConfig {\n /**\n * Global animation mode.\n * @default 'reduced-motion'\n */\n mode?: AnimationMode;\n\n /**\n * Default animation duration in milliseconds.\n * Sets `--tbw-animation-duration` CSS variable.\n * @default 200\n */\n duration?: number;\n\n /**\n * Default easing function.\n * Sets `--tbw-animation-easing` CSS variable.\n * @default 'ease-out'\n */\n easing?: string;\n}\n\n/** Default animation configuration */\nexport const DEFAULT_ANIMATION_CONFIG: Required<Omit<AnimationConfig, 'sort'>> = {\n mode: 'reduced-motion',\n duration: 200,\n easing: 'ease-out',\n};\n\n// #endregion\n\n// #region Grid Icons\n\n/** Icon value - can be a string (text/HTML) or HTMLElement */\nexport type IconValue = string | HTMLElement;\n\n/**\n * Grid-wide icon configuration.\n * All icons are optional - sensible defaults are used when not specified.\n *\n * Icons can be text (including emoji), HTML strings (for SVG), or HTMLElement instances.\n *\n * @example\n * ```typescript\n * grid.gridConfig = {\n * icons: {\n * // Emoji icons\n * expand: '➕',\n * collapse: '➖',\n *\n * // Custom SVG icon\n * sortAsc: '<svg viewBox=\"0 0 16 16\"><path d=\"M8 4l4 8H4z\"/></svg>',\n *\n * // Font icon class (wrap in span)\n * filter: '<span class=\"icon icon-filter\"></span>',\n * },\n * };\n * ```\n *\n * @see {@link IconValue} for allowed icon formats\n */\nexport interface GridIcons {\n /** Expand icon for collapsed items (trees, groups, details). Default: '▶' */\n expand?: IconValue;\n /** Collapse icon for expanded items (trees, groups, details). Default: '▼' */\n collapse?: IconValue;\n /** Sort ascending indicator. Default: '▲' */\n sortAsc?: IconValue;\n /** Sort descending indicator. Default: '▼' */\n sortDesc?: IconValue;\n /** Sort neutral/unsorted indicator. Default: '⇅' */\n sortNone?: IconValue;\n /** Submenu arrow for context menus. Default: '▶' */\n submenuArrow?: IconValue;\n /** Drag handle icon for reordering. Default: '⋮⋮' */\n dragHandle?: IconValue;\n /** Tool panel toggle icon in toolbar. Default: '☰' */\n toolPanel?: IconValue;\n /** Filter icon in column headers. Default: SVG funnel icon */\n filter?: IconValue;\n /** Filter icon when filter is active. Default: same as filter with accent color */\n filterActive?: IconValue;\n /** Print icon for print button. Default: '🖨️' */\n print?: IconValue;\n}\n\n/** Default filter icon SVG */\nconst DEFAULT_FILTER_ICON =\n '<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\"><path fill=\"currentColor\" d=\"M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z\"/></svg>';\n\n/** Default icons used when not overridden */\nexport const DEFAULT_GRID_ICONS: Required<GridIcons> = {\n expand: '▶',\n collapse: '▼',\n sortAsc: '▲',\n sortDesc: '▼',\n sortNone: '⇅',\n submenuArrow: '▶',\n dragHandle: '⋮⋮',\n toolPanel: '☰',\n filter: DEFAULT_FILTER_ICON,\n filterActive: DEFAULT_FILTER_ICON,\n print: '🖨️',\n};\n// #endregion\n\n// #region Shell Configuration\n\n/**\n * Shell configuration for the grid's optional header bar and tool panels.\n *\n * The shell provides a wrapper around the grid with:\n * - Header bar with title, toolbar buttons, and custom content\n * - Collapsible side panel for filters, column visibility, settings, etc.\n *\n * @example\n * ```typescript\n * grid.gridConfig = {\n * shell: {\n * header: {\n * title: 'Employee Directory',\n * },\n * toolPanel: {\n * position: 'right',\n * defaultOpen: 'columns', // Open by default\n * },\n * },\n * plugins: [new VisibilityPlugin()], // Adds \"Columns\" panel\n * };\n *\n * // Register custom tool panels\n * grid.registerToolPanel({\n * id: 'filters',\n * title: 'Filters',\n * icon: '🔍',\n * render: (container) => {\n * container.innerHTML = '<div>Filter controls...</div>';\n * },\n * });\n * ```\n *\n * @see {@link ShellHeaderConfig} for header options\n * @see {@link ToolPanelConfig} for tool panel options\n */\nexport interface ShellConfig {\n /** Shell header bar configuration */\n header?: ShellHeaderConfig;\n /** Tool panel configuration */\n toolPanel?: ToolPanelConfig;\n /**\n * Registered tool panels (from plugins, API, or Light DOM).\n * These are the actual panel definitions that can be opened.\n * @internal Set by ConfigManager during merge\n */\n toolPanels?: ToolPanelDefinition[];\n /**\n * Registered header content sections (from plugins or API).\n * Content rendered in the center of the shell header.\n * @internal Set by ConfigManager during merge\n */\n headerContents?: HeaderContentDefinition[];\n}\n\n/**\n * Shell header bar configuration\n */\nexport interface ShellHeaderConfig {\n /** Grid title displayed on the left (optional) */\n title?: string;\n /** Custom toolbar content (rendered before tool panel toggle) */\n toolbarContents?: ToolbarContentDefinition[];\n /**\n * Light DOM header content elements (parsed from <tbw-grid-header> children).\n * @internal Set by ConfigManager during merge\n */\n lightDomContent?: HTMLElement[];\n /**\n * Whether a tool buttons container was found in light DOM.\n * @internal Set by ConfigManager during merge\n */\n hasToolButtonsContainer?: boolean;\n}\n\n/**\n * Tool panel configuration\n */\nexport interface ToolPanelConfig {\n /** Panel position: 'left' | 'right' (default: 'right') */\n position?: 'left' | 'right';\n /** Default panel width in pixels (default: 280) */\n width?: number;\n /** Panel ID to open by default on load */\n defaultOpen?: string;\n /** Whether to persist open/closed state (requires Column State Events) */\n persistState?: boolean;\n /**\n * Close the tool panel when clicking outside of it.\n * When `true`, clicking anywhere outside the tool panel (but inside the grid)\n * will close the panel automatically.\n * @default false\n */\n closeOnClickOutside?: boolean;\n}\n\n/**\n * Toolbar content definition for the shell header toolbar area.\n * Register via `registerToolbarContent()` or use light DOM `<tbw-grid-tool-buttons>`.\n *\n * @example\n * ```typescript\n * grid.registerToolbarContent({\n * id: 'my-toolbar',\n * order: 10,\n * render: (container) => {\n * const btn = document.createElement('button');\n * btn.textContent = 'Refresh';\n * btn.onclick = () => console.log('clicked');\n * container.appendChild(btn);\n * return () => btn.remove();\n * },\n * });\n * ```\n */\nexport interface ToolbarContentDefinition {\n /** Unique content ID */\n id: string;\n /** Content factory - called once when shell header renders */\n render: (container: HTMLElement) => void | (() => void);\n /** Called when content is removed (for cleanup) */\n onDestroy?: () => void;\n /** Order priority (lower = first, default: 100) */\n order?: number;\n}\n\n/**\n * Tool panel definition registered by plugins or consumers.\n *\n * Register via `grid.registerToolPanel()` to add panels to the sidebar.\n * Panels appear as collapsible sections with icons and titles.\n *\n * @example\n * ```typescript\n * grid.registerToolPanel({\n * id: 'filters',\n * title: 'Filters',\n * icon: '🔍',\n * tooltip: 'Filter grid data',\n * order: 10, // Lower = appears first\n * render: (container) => {\n * container.innerHTML = `\n * <div class=\"filter-panel\">\n * <input type=\"text\" placeholder=\"Search...\" />\n * </div>\n * `;\n * // Return cleanup function\n * return () => container.innerHTML = '';\n * },\n * onClose: () => {\n * console.log('Filter panel closed');\n * },\n * });\n * ```\n *\n * @see {@link ShellConfig} for shell configuration\n */\nexport interface ToolPanelDefinition {\n /** Unique panel ID */\n id: string;\n /** Panel title shown in accordion header */\n title: string;\n /** Icon for accordion section header (optional, emoji or SVG) */\n icon?: string;\n /** Tooltip for accordion section header */\n tooltip?: string;\n /** Panel content factory - called when panel section opens */\n render: (container: HTMLElement) => void | (() => void);\n /** Called when panel closes (for cleanup) */\n onClose?: () => void;\n /** Panel order priority (lower = first, default: 100) */\n order?: number;\n}\n\n/**\n * Header content definition for plugins contributing to shell header center section.\n *\n * Register via `grid.registerHeaderContent()` to add content between\n * the title and toolbar buttons.\n *\n * @example\n * ```typescript\n * grid.registerHeaderContent({\n * id: 'row-count',\n * order: 10,\n * render: (container) => {\n * const span = document.createElement('span');\n * span.className = 'row-count';\n * span.textContent = `${grid.rows.length} rows`;\n * container.appendChild(span);\n *\n * // Update on data changes\n * const unsub = grid.on('data-change', () => {\n * span.textContent = `${grid.rows.length} rows`;\n * });\n *\n * return () => {\n * unsub();\n * };\n * },\n * });\n * ```\n *\n * @see {@link ShellConfig} for shell configuration\n */\nexport interface HeaderContentDefinition {\n /** Unique content ID */\n id: string;\n /** Content factory - called once when shell header renders */\n render: (container: HTMLElement) => void | (() => void);\n /** Called when content is removed (for cleanup) */\n onDestroy?: () => void;\n /** Order priority (lower = first, default: 100) */\n order?: number;\n}\n// #endregion\n\n// #region Column State (Persistence)\n\n/**\n * State for a single column. Captures user-driven changes at runtime.\n * Plugins can extend this interface via module augmentation to add their own state.\n *\n * Used with `grid.getColumnState()` and `grid.columnState` for persisting\n * user customizations (column widths, order, visibility, sort).\n *\n * @example\n * ```typescript\n * // Save column state to localStorage\n * const state = grid.getColumnState();\n * localStorage.setItem('gridState', JSON.stringify(state));\n *\n * // Restore on page load\n * const saved = localStorage.getItem('gridState');\n * if (saved) {\n * grid.columnState = JSON.parse(saved);\n * }\n *\n * // Example column state structure\n * const state: GridColumnState = {\n * columns: [\n * { field: 'name', order: 0, width: 200, hidden: false },\n * { field: 'email', order: 1, width: 300, hidden: false },\n * { field: 'phone', order: 2, hidden: true }, // Hidden column\n * ],\n * sort: { field: 'name', direction: 1 },\n * };\n * ```\n *\n * @example\n * ```typescript\n * // Plugin augmentation example (in filtering plugin)\n * declare module '@toolbox-web/grid' {\n * interface ColumnState {\n * filter?: FilterValue;\n * }\n * }\n * ```\n *\n * @see {@link GridColumnState} for the full state object\n */\nexport interface ColumnState {\n /** Column field identifier */\n field: string;\n /** Position index after reordering (0-based) */\n order: number;\n /** Width in pixels (undefined = use default) */\n width?: number;\n /** Visibility state */\n visible: boolean;\n /** Sort state (undefined = not sorted). */\n sort?: ColumnSortState;\n}\n\n/**\n * Sort state for a column.\n * Used within {@link ColumnState} to track sort direction and priority.\n *\n * @see {@link ColumnState} for column state persistence\n * @see {@link SortChangeDetail} for sort change events\n */\nexport interface ColumnSortState {\n /** Sort direction */\n direction: 'asc' | 'desc';\n /** Priority for multi-sort (0 = primary, 1 = secondary, etc.) */\n priority: number;\n}\n\n/**\n * Complete grid column state for persistence.\n * Contains state for all columns, including plugin-contributed properties.\n *\n * @example\n * ```typescript\n * // Save state\n * const state = grid.getColumnState();\n * localStorage.setItem('grid-state', JSON.stringify(state));\n *\n * // Restore state\n * grid.columnState = JSON.parse(localStorage.getItem('grid-state'));\n * ```\n *\n * @see {@link ColumnState} for individual column state\n * @see {@link PublicGrid.getColumnState} for retrieving state\n */\nexport interface GridColumnState {\n /** Array of column states. */\n columns: ColumnState[];\n}\n// #endregion\n\n// #region Public Event Detail Interfaces\n/**\n * Detail for a cell click event.\n * Provides full context about the clicked cell including row data.\n *\n * @example\n * ```typescript\n * grid.on('cell-click', ({ row, field, value, rowIndex, colIndex }) => {\n * console.log(`Clicked ${field} = ${value} in row ${rowIndex}`);\n *\n * // Access the full row data\n * if (row.status === 'pending') {\n * showApprovalDialog(row);\n * }\n * });\n * ```\n *\n * @category Events\n */\nexport interface CellClickDetail<TRow = unknown> {\n /** Zero-based row index of the clicked cell. */\n rowIndex: number;\n /** Zero-based column index of the clicked cell. */\n colIndex: number;\n /** Column configuration object for the clicked cell. */\n column: ColumnConfig<TRow>;\n /** Field name of the clicked column. */\n field: string;\n /** Cell value at the clicked position. */\n value: unknown;\n /** Full row data object. */\n row: TRow;\n /** The clicked cell element. */\n cellEl: HTMLElement;\n /** The original mouse event. */\n originalEvent: MouseEvent;\n}\n\n/**\n * Detail for a row click event.\n * Provides context about the clicked row.\n *\n * @example\n * ```typescript\n * grid.on('row-click', ({ row, rowIndex, rowEl }) => {\n * console.log(`Clicked row ${rowIndex}: ${row.name}`);\n *\n * // Highlight the row\n * rowEl.classList.add('selected');\n *\n * // Open detail panel\n * showDetailPanel(row);\n * });\n * ```\n *\n * @category Events\n */\nexport interface RowClickDetail<TRow = unknown> {\n /** Zero-based row index of the clicked row. */\n rowIndex: number;\n /** Full row data object. */\n row: TRow;\n /** The clicked row element. */\n rowEl: HTMLElement;\n /** The original mouse event. */\n originalEvent: MouseEvent;\n}\n\n/**\n * Detail for a sort change (direction 0 indicates cleared sort).\n *\n * @example\n * ```typescript\n * grid.on('sort-change', ({ field, direction }) => {\n * if (direction === 0) {\n * console.log(`Sort cleared on ${field}`);\n * } else {\n * const dir = direction === 1 ? 'ascending' : 'descending';\n * console.log(`Sorted by ${field} ${dir}`);\n * }\n *\n * // Fetch sorted data from server\n * fetchData({ sortBy: field, sortDir: direction });\n * });\n * ```\n *\n * @see {@link SortState} for the sort state object\n * @see {@link SortHandler} for custom sort handlers\n * @category Events\n */\nexport interface SortChangeDetail {\n /** Sorted field key. */\n field: string;\n /** Direction: 1 ascending, -1 descending, 0 cleared. */\n direction: 1 | -1 | 0;\n}\n\n/**\n * Column resize event detail containing final pixel width.\n *\n * @example\n * ```typescript\n * grid.on('column-resize', ({ field, width }) => {\n * console.log(`Column ${field} resized to ${width}px`);\n *\n * // Persist to user preferences\n * saveColumnWidth(field, width);\n * });\n * ```\n *\n * @see {@link ColumnState} for persisting column state\n * @see {@link ResizeController} for resize implementation\n * @category Events\n */\nexport interface ColumnResizeDetail {\n /** Resized column field key. */\n field: string;\n /** New width in pixels. */\n width: number;\n}\n\n/**\n * Trigger type for cell activation.\n * - `'keyboard'`: Enter key pressed on focused cell\n * - `'pointer'`: Mouse/touch/pen click on cell\n *\n * @see {@link CellActivateDetail} for the activation event detail\n * @category Events\n */\nexport type CellActivateTrigger = 'keyboard' | 'pointer';\n\n/**\n * Fired when a cell is activated by user interaction (Enter key or click).\n * Unified event for both keyboard and pointer activation.\n *\n * @example\n * ```typescript\n * grid.on('cell-activate', ({ row, field, value, trigger, cellEl }, event) => {\n * if (trigger === 'keyboard') {\n * console.log('Activated via Enter key');\n * } else {\n * console.log('Activated via click/tap');\n * }\n *\n * // Start custom editing for specific columns\n * if (field === 'notes') {\n * event.preventDefault(); // Prevent default editing\n * openNotesEditor(row, cellEl);\n * }\n * });\n * ```\n *\n * @see {@link CellClickDetail} for click-only events\n * @see {@link CellActivateTrigger} for trigger types\n * @category Events\n */\nexport interface CellActivateDetail<TRow = unknown> {\n /** Zero-based row index of the activated cell. */\n rowIndex: number;\n /** Zero-based column index of the activated cell. */\n colIndex: number;\n /** Field name of the activated column. */\n field: string;\n /** Cell value at the activated position. */\n value: unknown;\n /** Full row data object. */\n row: TRow;\n /** The activated cell element. */\n cellEl: HTMLElement;\n /** What triggered the activation. */\n trigger: CellActivateTrigger;\n /** The original event (KeyboardEvent for keyboard, MouseEvent/PointerEvent for pointer). */\n originalEvent: KeyboardEvent | MouseEvent | PointerEvent;\n}\n\n/**\n * @deprecated Use `CellActivateDetail` instead. Will be removed in next major version.\n * Kept for backwards compatibility. Will be removed in v2.\n *\n * @category Events\n */\nexport interface ActivateCellDetail {\n /** Zero-based row index now focused. */\n row: number;\n /** Zero-based column index now focused. */\n col: number;\n}\n\n/**\n * Event detail for mounting external view renderers.\n *\n * Emitted when a cell uses an external component spec (React, Angular, Vue)\n * and needs the framework adapter to mount the component.\n *\n * @example\n * ```typescript\n * // Framework adapter listens for this event\n * grid.on('mount-external-view', ({ placeholder, spec, context }) => {\n * // Mount framework component into placeholder\n * mountComponent(spec.component, placeholder, context);\n * });\n * ```\n *\n * @see {@link ColumnConfig.externalView} for external view spec\n * @see {@link FrameworkAdapter} for adapter interface\n * @category Framework Adapters\n */\nexport interface ExternalMountViewDetail<TRow = unknown> {\n placeholder: HTMLElement;\n spec: unknown;\n context: { row: TRow; value: unknown; field: string; column: unknown };\n}\n\n/**\n * Event detail for mounting external editor renderers.\n *\n * Emitted when a cell uses an external editor component spec and needs\n * the framework adapter to mount the editor with commit/cancel bindings.\n *\n * @example\n * ```typescript\n * // Framework adapter listens for this event\n * grid.on('mount-external-editor', ({ placeholder, spec, context }) => {\n * // Mount framework editor with commit/cancel wired\n * mountEditor(spec.component, placeholder, {\n * value: context.value,\n * onCommit: context.commit,\n * onCancel: context.cancel,\n * });\n * });\n * ```\n *\n * @see {@link ColumnEditorSpec} for external editor spec\n * @see {@link FrameworkAdapter} for adapter interface\n * @category Framework Adapters\n */\nexport interface ExternalMountEditorDetail<TRow = unknown> {\n placeholder: HTMLElement;\n spec: unknown;\n context: {\n row: TRow;\n value: unknown;\n field: string;\n column: unknown;\n commit: (v: unknown) => void;\n cancel: () => void;\n };\n}\n\n/**\n * Maps event names to their detail payload types.\n *\n * Used by {@link DataGridElement.on | grid.on()} and `addEventListener()` for\n * fully typed event handling. Plugins extend this map via module augmentation.\n *\n * @example\n * ```typescript\n * // Recommended: grid.on() auto-unwraps the detail\n * const off = grid.on('cell-click', ({ field, value, row }) => {\n * console.log(`Clicked ${field} = ${value}`);\n * });\n * off(); // unsubscribe\n *\n * // addEventListener works too (useful for { once, signal, capture })\n * grid.addEventListener('cell-click', (e) => {\n * console.log(e.detail.field);\n * }, { once: true });\n * ```\n *\n * @see {@link DataGridElement.on} for the recommended subscription API\n * @see {@link DataGridCustomEvent} for typed CustomEvent wrapper\n * @see {@link DGEvents} for event name constants\n * @category Events\n */\nexport interface DataGridEventMap<TRow = unknown> {\n /**\n * Fired when a cell is clicked.\n * Provides full context: row data, column config, cell element, and the original mouse event.\n *\n * @example\n * ```typescript\n * grid.on('cell-click', ({ row, field, value, cellEl }) => {\n * console.log(`Clicked ${field} = ${value}`);\n *\n * // Open a detail dialog for a specific column\n * if (field === 'avatar') {\n * showImagePreview(row.avatarUrl, cellEl);\n * }\n * });\n * ```\n *\n * @see {@link CellClickDetail}\n * @group Core Events\n */\n 'cell-click': CellClickDetail<TRow>;\n\n /**\n * Fired when a row is clicked (anywhere on the row).\n * Use for row-level actions like opening a detail panel or navigating.\n *\n * @example\n * ```typescript\n * grid.on('row-click', ({ row, rowIndex }) => {\n * console.log(`Row ${rowIndex}: ${row.name}`);\n *\n * // Navigate to detail page\n * router.navigate(`/employees/${row.id}`);\n * });\n * ```\n *\n * @see {@link RowClickDetail}\n * @group Core Events\n */\n 'row-click': RowClickDetail<TRow>;\n\n /**\n * Fired when a cell is activated by Enter key or pointer click.\n * Unified event for both keyboard and pointer activation — use this\n * instead of the deprecated `activate-cell`.\n *\n * Call `event.preventDefault()` to suppress default behavior (e.g., inline editing).\n *\n * @example\n * ```typescript\n * grid.on('cell-activate', ({ row, field, trigger, cellEl }, event) => {\n * // Custom editing for a specific column\n * if (field === 'notes') {\n * event.preventDefault();\n * openRichTextEditor(row, cellEl);\n * }\n *\n * console.log(`Activated via ${trigger}`); // 'keyboard' | 'pointer'\n * });\n * ```\n *\n * @see {@link CellActivateDetail}\n * @see {@link CellActivateTrigger}\n * @group Core Events\n */\n 'cell-activate': CellActivateDetail<TRow>;\n\n /**\n * Fired after any data mutation — user edits, cascade updates, or API calls.\n * This is an informational event for logging, auditing, or cascading updates\n * to related fields. Check `source` to distinguish edit origins.\n *\n * @example\n * ```typescript\n * grid.on('cell-change', ({ row, rowId, field, oldValue, newValue, source }) => {\n * console.log(`${field}: ${oldValue} → ${newValue} (${source})`);\n *\n * // Cascade: recalculate total when quantity changes\n * if (source === 'user' && field === 'quantity') {\n * grid.updateRow(rowId, { total: newValue * row.price });\n * }\n * });\n * ```\n *\n * @see {@link CellChangeDetail}\n * @see {@link UpdateSource}\n * @group Core Events\n */\n 'cell-change': CellChangeDetail<TRow>;\n\n /**\n * Fired whenever the grid's row data changes — new data assignment,\n * row insertion/removal, or in-place mutations via `updateRow()`.\n * Use to keep external UI (row counts, summaries, charts) in sync.\n *\n * @example\n * ```typescript\n * grid.on('data-change', ({ rowCount, sourceRowCount }) => {\n * statusBar.textContent = `${rowCount} of ${sourceRowCount} rows`;\n * });\n * ```\n *\n * @see {@link DataChangeDetail}\n * @group Core Events\n */\n 'data-change': DataChangeDetail;\n\n /**\n * Emitted when a cell with an external view renderer (React, Angular, Vue component)\n * needs to be mounted. Framework adapters listen for this event internally.\n *\n * @example\n * ```typescript\n * // Custom framework adapter\n * grid.on('mount-external-view', ({ placeholder, spec, context }) => {\n * myFramework.render(spec.component, placeholder, {\n * row: context.row,\n * value: context.value,\n * });\n * });\n * ```\n *\n * @see {@link ExternalMountViewDetail}\n * @see {@link FrameworkAdapter}\n * @group Framework Adapter Events\n */\n 'mount-external-view': ExternalMountViewDetail<TRow>;\n\n /**\n * Emitted when a cell with an external editor component (React, Angular, Vue)\n * needs to be mounted with commit/cancel bindings. Framework adapters listen\n * for this event internally.\n *\n * @example\n * ```typescript\n * // Custom framework adapter\n * grid.on('mount-external-editor', ({ placeholder, spec, context }) => {\n * myFramework.render(spec.component, placeholder, {\n * value: context.value,\n * onSave: context.commit,\n * onCancel: context.cancel,\n * });\n * });\n * ```\n *\n * @see {@link ExternalMountEditorDetail}\n * @see {@link FrameworkAdapter}\n * @group Framework Adapter Events\n */\n 'mount-external-editor': ExternalMountEditorDetail<TRow>;\n\n /**\n * Fired when the sort state changes — column header click, programmatic sort,\n * or sort cleared. `direction: 0` indicates the sort was removed.\n *\n * @example\n * ```typescript\n * grid.on('sort-change', ({ field, direction }) => {\n * if (direction === 0) {\n * console.log('Sort cleared');\n * } else {\n * console.log(`Sorted by ${field} ${direction === 1 ? 'ASC' : 'DESC'}`);\n * }\n *\n * // Server-side sorting\n * fetchData({ sortBy: field, sortDir: direction });\n * });\n * ```\n *\n * @see {@link SortChangeDetail}\n * @see {@link SortHandler}\n * @group Core Events\n */\n 'sort-change': SortChangeDetail;\n\n /**\n * Fired when a column is resized by the user dragging the resize handle.\n * Use to persist column widths to user preferences or localStorage.\n *\n * @example\n * ```typescript\n * grid.on('column-resize', ({ field, width }) => {\n * console.log(`Column \"${field}\" resized to ${width}px`);\n *\n * // Persist to localStorage\n * const widths = JSON.parse(localStorage.getItem('col-widths') ?? '{}');\n * widths[field] = width;\n * localStorage.setItem('col-widths', JSON.stringify(widths));\n * });\n * ```\n *\n * @see {@link ColumnResizeDetail}\n * @group Core Events\n */\n 'column-resize': ColumnResizeDetail;\n\n /**\n * @deprecated Use `cell-activate` instead. Will be removed in v2.\n * @see {@link ActivateCellDetail}\n * @group Core Events\n */\n 'activate-cell': ActivateCellDetail;\n\n /**\n * Fired when column state changes — reordering, resizing, visibility toggle,\n * or sort changes. Use with `getColumnState()` / `columnState` setter for\n * full state persistence.\n *\n * @example\n * ```typescript\n * grid.on('column-state-change', (state) => {\n * localStorage.setItem('grid-state', JSON.stringify(state));\n * console.log(`${state.columns.length} columns in state`);\n * });\n *\n * // Restore on load\n * const saved = localStorage.getItem('grid-state');\n * if (saved) grid.columnState = JSON.parse(saved);\n * ```\n *\n * @see {@link GridColumnState}\n * @see {@link ColumnState}\n * @group Core Events\n */\n 'column-state-change': GridColumnState;\n\n // Note: 'cell-commit', 'row-commit', 'changed-rows-reset' are added via\n // module augmentation by EditingPlugin when imported\n}\n\n/**\n * Extracts the event detail type for a given event name.\n *\n * Utility type for getting the detail payload type of a specific event.\n *\n * @example\n * ```typescript\n * // Extract detail type for specific event\n * type ClickDetail = DataGridEventDetail<'cell-click', Employee>;\n * // Equivalent to: CellClickDetail<Employee>\n *\n * // Use in generic handler\n * function logDetail<K extends keyof DataGridEventMap>(\n * eventName: K,\n * detail: DataGridEventDetail<K>,\n * ): void {\n * console.log(`${eventName}:`, detail);\n * }\n * ```\n *\n * @see {@link DataGridEventMap} for all event types\n * @category Events\n */\nexport type DataGridEventDetail<K extends keyof DataGridEventMap<unknown>, TRow = unknown> = DataGridEventMap<TRow>[K];\n\n/**\n * Custom event type for DataGrid events with typed detail payload.\n *\n * Primarily useful when you need to declare handler parameters with\n * `addEventListener`. For most use cases, prefer {@link DataGridElement.on | grid.on()}\n * which handles typing automatically.\n *\n * @example\n * ```typescript\n * // Typed handler for addEventListener\n * function onCellClick(e: DataGridCustomEvent<'cell-click', Employee>): void {\n * const { row, field, value } = e.detail;\n * console.log(`Clicked ${field} = ${value} on ${row.name}`);\n * }\n * grid.addEventListener('cell-click', onCellClick);\n *\n * // With grid.on() you don't need this type — it's inferred:\n * grid.on('cell-click', ({ row, field, value }) => {\n * console.log(`Clicked ${field} = ${value} on ${row.name}`);\n * });\n * ```\n *\n * @see {@link DataGridElement.on} for the recommended subscription API\n * @see {@link DataGridEventMap} for all event types\n * @see {@link DataGridEventDetail} for extracting detail type only\n * @category Events\n */\nexport type DataGridCustomEvent<K extends keyof DataGridEventMap<unknown>, TRow = unknown> = CustomEvent<\n DataGridEventMap<TRow>[K]\n>;\n\n/**\n * Template evaluation context for dynamic templates.\n *\n * @category Plugin Development\n */\nexport interface EvalContext {\n value: unknown;\n row: Record<string, unknown> | null;\n}\n// #endregion\n","/**\n * Centralized diagnostic messages for @toolbox-web/grid.\n *\n * Every user-facing warning, error, or info message in the grid has a unique\n * diagnostic code (e.g. `TBW001`). Each code maps to a section on the online\n * troubleshooting page, giving developers a direct link to resolution steps.\n *\n * ## Usage\n *\n * ```ts\n * import { MISSING_BREAKPOINT, warnDiagnostic, throwDiagnostic } from './diagnostics';\n *\n * // Warn with a code\n * warnDiagnostic(MISSING_BREAKPOINT, 'Set a breakpoint...', gridId);\n *\n * // Throw with a code\n * throwDiagnostic(MISSING_ROW_ID, 'Configure getRowId...', gridId);\n * ```\n *\n * Plugins should prefer `this.warn(MISSING_BREAKPOINT, message)` via BaseGridPlugin\n * instead of importing this module directly.\n *\n * @internal\n */\n\n// #region Grid Prefix\n\n/**\n * Build the `[tbw-grid]` or `[tbw-grid#my-id]` log prefix.\n * Pass `pluginName` for a scoped prefix like `[tbw-grid:SelectionPlugin]`.\n */\nexport function gridPrefix(gridId?: string, pluginName?: string): string {\n const id = gridId ? `#${gridId}` : '';\n const plugin = pluginName ? `:${pluginName}` : '';\n return `[tbw-grid${id}${plugin}]`;\n}\n\n// #endregion\n\n// #region Diagnostic Codes\n\n/**\n * Diagnostic codes used across the grid library.\n *\n * Each code is an individual export so that unused codes are tree-shaken\n * from bundles that don't reference them (esbuild can't tree-shake\n * properties from a single object).\n *\n * Naming: TBW + 3-digit number.\n * Ranges:\n * 001–019 Configuration validation (missing plugins, bad config)\n * 020–029 Plugin lifecycle (dependencies, incompatibilities, deprecation)\n * 030–039 Feature registry\n * 040–049 Row operations (row ID, row mutations)\n * 050–059 Column operations (width, template)\n * 060–069 Rendering (callbacks, formatters, external views)\n * 070–079 Shell (tool panels, header/toolbar content)\n * 080–089 Editing & editors\n * 090–099 Print\n * 100–109 Clipboard\n * 110–119 Plugin-specific (responsive, undo-redo, grouping-columns)\n * 120–129 Style injection\n * 130–139 Attribute parsing\n */\n\n// --- Config validation (001–019) ---\n/** Column uses a plugin-owned property but the plugin is not loaded. */\nexport const MISSING_PLUGIN = 'TBW001' as const;\n/** Grid config uses a plugin-owned property but the plugin is not loaded. */\nexport const MISSING_PLUGIN_CONFIG = 'TBW002' as const;\n/** Plugin config rule violation (error severity). */\nexport const CONFIG_RULE_ERROR = 'TBW003' as const;\n/** Plugin config rule violation (warning severity). */\nexport const CONFIG_RULE_WARN = 'TBW004' as const;\n\n// --- Plugin lifecycle (020–029) ---\n/** Required plugin dependency is missing. */\nexport const MISSING_DEPENDENCY = 'TBW020' as const;\n/** Optional plugin dependency is missing. */\nexport const OPTIONAL_DEPENDENCY = 'TBW021' as const;\n/** Two loaded plugins are incompatible. */\nexport const INCOMPATIBLE_PLUGINS = 'TBW022' as const;\n/** Plugin uses deprecated hooks. */\nexport const DEPRECATED_HOOK = 'TBW023' as const;\n/** Error thrown inside a plugin event handler. */\nexport const PLUGIN_EVENT_ERROR = 'TBW024' as const;\n\n// --- Feature registry (030–039) ---\n/** Feature was re-registered (overwritten). */\nexport const FEATURE_REREGISTERED = 'TBW030' as const;\n/** Feature configured but not imported. */\nexport const FEATURE_NOT_IMPORTED = 'TBW031' as const;\n/** Feature depends on another feature that is not enabled. */\nexport const FEATURE_MISSING_DEP = 'TBW032' as const;\n\n// --- Row operations (040–049) ---\n/** Cannot determine row ID (no getRowId and no id property). */\nexport const MISSING_ROW_ID = 'TBW040' as const;\n/** Row with given ID not found. */\nexport const ROW_NOT_FOUND = 'TBW041' as const;\n\n// --- Column operations (050–059) ---\n/** Column has an invalid CSS width value. */\nexport const INVALID_COLUMN_WIDTH = 'TBW050' as const;\n\n// --- Rendering callbacks (060–069) ---\n/** rowClass callback threw an error. */\nexport const ROW_CLASS_ERROR = 'TBW060' as const;\n/** cellClass callback threw an error. */\nexport const CELL_CLASS_ERROR = 'TBW061' as const;\n/** Column format function threw an error. */\nexport const FORMAT_ERROR = 'TBW062' as const;\n/** External view mount() threw an error. */\nexport const VIEW_MOUNT_ERROR = 'TBW063' as const;\n/** External view event dispatch error. */\nexport const VIEW_DISPATCH_ERROR = 'TBW064' as const;\n\n// --- Shell (070–079) ---\n/** Tool panel missing required id or title. */\nexport const TOOL_PANEL_MISSING_ATTR = 'TBW070' as const;\n/** No tool panels registered. */\nexport const NO_TOOL_PANELS = 'TBW071' as const;\n/** Tool panel section not found. */\nexport const TOOL_PANEL_NOT_FOUND = 'TBW072' as const;\n/** Tool panel already registered. */\nexport const TOOL_PANEL_DUPLICATE = 'TBW073' as const;\n/** Header content already registered. */\nexport const HEADER_CONTENT_DUPLICATE = 'TBW074' as const;\n/** Toolbar content already registered. */\nexport const TOOLBAR_CONTENT_DUPLICATE = 'TBW075' as const;\n\n// --- Editing & editors (080–089) ---\n/** External editor mount() threw an error. */\nexport const EDITOR_MOUNT_ERROR = 'TBW080' as const;\n\n// --- Print (090–099) ---\n/** Print already in progress. */\nexport const PRINT_IN_PROGRESS = 'TBW090' as const;\n/** Grid not available for printing. */\nexport const PRINT_NO_GRID = 'TBW091' as const;\n/** Print operation failed. */\nexport const PRINT_FAILED = 'TBW092' as const;\n/** Multiple elements share the same grid ID (print isolation issue). */\nexport const PRINT_DUPLICATE_ID = 'TBW093' as const;\n\n// --- Clipboard (100–109) ---\n/** Clipboard API write failed. */\nexport const CLIPBOARD_FAILED = 'TBW100' as const;\n\n// --- Plugin-specific (110–119) ---\n/** ResponsivePlugin: no breakpoint configured. */\nexport const MISSING_BREAKPOINT = 'TBW110' as const;\n/** UndoRedoPlugin: transaction already in progress. */\nexport const TRANSACTION_IN_PROGRESS = 'TBW111' as const;\n/** UndoRedoPlugin: no transaction in progress. */\nexport const NO_TRANSACTION = 'TBW112' as const;\n/** GroupingColumnsPlugin: missing id or header on column group definition. */\nexport const COLUMN_GROUP_NO_ID = 'TBW113' as const;\n/** GroupingColumnsPlugin: conflicting columnGroups sources. */\nexport const COLUMN_GROUPS_CONFLICT = 'TBW114' as const;\n\n// --- Style injection (120–129) ---\n/** Failed to extract grid.css from document stylesheets. */\nexport const STYLE_EXTRACT_FAILED = 'TBW120' as const;\n/** Could not find grid.css in document.styleSheets. */\nexport const STYLE_NOT_FOUND = 'TBW121' as const;\n\n// --- Attribute parsing (130–139) ---\n/** Invalid JSON in an HTML attribute. */\nexport const INVALID_ATTRIBUTE_JSON = 'TBW130' as const;\n\nexport type DiagnosticCode =\n | typeof MISSING_PLUGIN\n | typeof MISSING_PLUGIN_CONFIG\n | typeof CONFIG_RULE_ERROR\n | typeof CONFIG_RULE_WARN\n | typeof MISSING_DEPENDENCY\n | typeof OPTIONAL_DEPENDENCY\n | typeof INCOMPATIBLE_PLUGINS\n | typeof DEPRECATED_HOOK\n | typeof PLUGIN_EVENT_ERROR\n | typeof FEATURE_REREGISTERED\n | typeof FEATURE_NOT_IMPORTED\n | typeof FEATURE_MISSING_DEP\n | typeof MISSING_ROW_ID\n | typeof ROW_NOT_FOUND\n | typeof INVALID_COLUMN_WIDTH\n | typeof ROW_CLASS_ERROR\n | typeof CELL_CLASS_ERROR\n | typeof FORMAT_ERROR\n | typeof VIEW_MOUNT_ERROR\n | typeof VIEW_DISPATCH_ERROR\n | typeof TOOL_PANEL_MISSING_ATTR\n | typeof NO_TOOL_PANELS\n | typeof TOOL_PANEL_NOT_FOUND\n | typeof TOOL_PANEL_DUPLICATE\n | typeof HEADER_CONTENT_DUPLICATE\n | typeof TOOLBAR_CONTENT_DUPLICATE\n | typeof EDITOR_MOUNT_ERROR\n | typeof PRINT_IN_PROGRESS\n | typeof PRINT_NO_GRID\n | typeof PRINT_FAILED\n | typeof PRINT_DUPLICATE_ID\n | typeof CLIPBOARD_FAILED\n | typeof MISSING_BREAKPOINT\n | typeof TRANSACTION_IN_PROGRESS\n | typeof NO_TRANSACTION\n | typeof COLUMN_GROUP_NO_ID\n | typeof COLUMN_GROUPS_CONFLICT\n | typeof STYLE_EXTRACT_FAILED\n | typeof STYLE_NOT_FOUND\n | typeof INVALID_ATTRIBUTE_JSON;\n\n// #endregion\n\n// #region Docs URL\n\nconst DOCS_BASE = 'https://toolboxjs.com/grid/errors';\n\n/** Build a direct link to the troubleshooting section for a code. */\nfunction docsUrl(code: DiagnosticCode): string {\n return `${DOCS_BASE}#${code.toLowerCase()}`;\n}\n\n// #endregion\n\n// #region Formatting\n\n/**\n * Format a diagnostic message with prefix, code, and docs link.\n *\n * Output format:\n * ```\n * [tbw-grid#my-id] TBW001: Your message here.\n *\n * → More info: https://toolboxjs.com/grid/errors#tbw001\n * ```\n */\nexport function formatDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): string {\n const prefix = gridPrefix(gridId, pluginName);\n return `${prefix} ${code}: ${message}\\n\\n → More info: ${docsUrl(code)}`;\n}\n\n// #endregion\n\n// #region Public API\n\n/**\n * Throw an error with a diagnostic code and docs link.\n * Use for configuration errors and API misuse that should halt execution.\n */\nexport function throwDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): never {\n throw new Error(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log a warning with a diagnostic code and docs link.\n * Use for recoverable issues the developer should fix.\n */\nexport function warnDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.warn(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log an info message with a diagnostic code and docs link.\n * Use for optional/soft dependency notifications.\n */\nexport function infoDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.info(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log an error with a diagnostic code and docs link.\n * Use for non-throwing errors (e.g., failed async operations).\n */\nexport function errorDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.error(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n// #endregion\n","import type { ColumnConfig, ColumnInternal, ElementWithPart, GridHost, PrimitiveColumnType } from '../types';\nimport { FitModeEnum } from '../types';\nimport { INVALID_COLUMN_WIDTH, warnDiagnostic } from './diagnostics';\n\n// #region Light DOM Parsing\n/** Global DataGridElement class (may or may not be registered) */\ninterface DataGridElementClass {\n getAdapters?: () => readonly {\n canHandle: (el: HTMLElement) => boolean;\n createRenderer: (el: HTMLElement) => ((ctx: unknown) => Node | string | void) | undefined;\n createEditor: (el: HTMLElement) => ((ctx: unknown) => HTMLElement | string) | undefined;\n }[];\n}\n\n/**\n * Parse `<tbw-grid-column>` elements from the host light DOM into column config objects,\n * capturing template elements for later cloning / compilation.\n */\nexport function parseLightDomColumns(host: HTMLElement): ColumnInternal[] {\n const domColumns = Array.from(host.querySelectorAll('tbw-grid-column')) as HTMLElement[];\n return domColumns\n .map((el) => {\n const field = el.getAttribute('field') || '';\n if (!field) return null;\n const rawType = el.getAttribute('type') || undefined;\n const allowedTypes = new Set<PrimitiveColumnType>(['number', 'string', 'date', 'boolean', 'select']);\n const type =\n rawType && allowedTypes.has(rawType as PrimitiveColumnType) ? (rawType as PrimitiveColumnType) : undefined;\n const header = el.getAttribute('header') || undefined;\n const sortable = el.hasAttribute('sortable');\n const editable = el.hasAttribute('editable');\n const config: ColumnInternal = { field, type, header, sortable, editable };\n\n // Parse width attribute (supports px values, percentages, or plain numbers)\n const widthAttr = el.getAttribute('width');\n if (widthAttr) {\n const numericWidth = parseFloat(widthAttr);\n if (!isNaN(numericWidth) && /^\\d+(\\.\\d+)?$/.test(widthAttr.trim())) {\n config.width = numericWidth;\n } else {\n config.width = widthAttr; // e.g. \"100px\", \"20%\", \"1fr\"\n }\n }\n\n // Parse minWidth attribute (numeric only)\n const minWidthAttr = el.getAttribute('minWidth') || el.getAttribute('min-width');\n if (minWidthAttr) {\n const numericMinWidth = parseFloat(minWidthAttr);\n if (!isNaN(numericMinWidth)) {\n config.minWidth = numericMinWidth;\n }\n }\n\n if (el.hasAttribute('resizable')) config.resizable = true;\n if (el.hasAttribute('sizable')) config.resizable = true; // legacy attribute support\n\n // Parse editor and renderer attribute names for programmatic lookup\n const editorName = el.getAttribute('editor');\n const rendererName = el.getAttribute('renderer');\n if (editorName) config.__editorName = editorName;\n if (rendererName) config.__rendererName = rendererName;\n\n // Parse options attribute for select/typeahead: \"value1:Label1,value2:Label2\" or \"value1,value2\"\n const optionsAttr = el.getAttribute('options');\n if (optionsAttr) {\n config.options = optionsAttr.split(',').map((item) => {\n const [value, label] = item.includes(':') ? item.split(':') : [item.trim(), item.trim()];\n return { value: value.trim(), label: label?.trim() || value.trim() };\n });\n }\n const viewTpl = el.querySelector('tbw-grid-column-view');\n const editorTpl = el.querySelector('tbw-grid-column-editor');\n const headerTpl = el.querySelector('tbw-grid-column-header');\n if (viewTpl) config.__viewTemplate = viewTpl as HTMLElement;\n if (editorTpl) config.__editorTemplate = editorTpl as HTMLElement;\n if (headerTpl) config.__headerTemplate = headerTpl as HTMLElement;\n\n // Check if framework adapters can handle template wrapper elements or the column element itself\n // React adapter registers on the column element, Angular uses inner template wrappers\n const DataGridElementClassRef = (globalThis as { DataGridElement?: DataGridElementClass }).DataGridElement;\n const adapters = DataGridElementClassRef?.getAdapters?.() ?? [];\n\n // First check inner view template, then column element itself\n const viewTarget = (viewTpl ?? el) as HTMLElement;\n const viewAdapter = adapters.find((a) => a.canHandle(viewTarget));\n if (viewAdapter) {\n // Only assign if adapter returns a truthy renderer\n // Adapters return undefined when only an editor is registered (no view template)\n const renderer = viewAdapter.createRenderer(viewTarget);\n if (renderer) {\n config.viewRenderer = renderer;\n }\n }\n\n // First check inner editor template, then column element itself\n const editorTarget = (editorTpl ?? el) as HTMLElement;\n const editorAdapter = adapters.find((a) => a.canHandle(editorTarget));\n if (editorAdapter) {\n // Only assign if adapter returns a truthy editor\n const editor = editorAdapter.createEditor(editorTarget);\n if (editor) {\n config.editor = editor;\n }\n }\n\n return config;\n })\n .filter((c): c is ColumnInternal => !!c);\n}\n// #endregion\n\n// #region Column Merging\n/**\n * Merge programmatic columns with light DOM columns by field name, allowing DOM-provided\n * attributes / templates to supplement (not overwrite) programmatic definitions.\n * Any DOM columns without a programmatic counterpart are appended.\n * When multiple DOM columns exist for the same field (e.g., separate renderer and editor),\n * their properties are merged together.\n */\nexport function mergeColumns(\n programmatic: ColumnConfig[] | undefined,\n dom: ColumnConfig[] | undefined,\n): ColumnInternal[] {\n if ((!programmatic || !programmatic.length) && (!dom || !dom.length)) return [];\n if (!programmatic || !programmatic.length) return (dom || []) as ColumnInternal[];\n if (!dom || !dom.length) return programmatic as ColumnInternal[];\n\n // Build domMap by merging multiple DOM columns with the same field\n // This supports React pattern where renderer and editor are in separate GridColumn elements\n const domMap: Record<string, ColumnInternal> = {};\n (dom as ColumnInternal[]).forEach((c) => {\n const existing = domMap[c.field];\n if (existing) {\n // Merge this column's properties into the existing one\n if (c.header && !existing.header) existing.header = c.header;\n if (c.type && !existing.type) existing.type = c.type;\n if (c.sortable) existing.sortable = true;\n if (c.editable) existing.editable = true;\n if (c.resizable) existing.resizable = true;\n if (c.width != null && existing.width == null) existing.width = c.width;\n if (c.minWidth != null && existing.minWidth == null) existing.minWidth = c.minWidth;\n if (c.__viewTemplate) existing.__viewTemplate = c.__viewTemplate;\n if (c.__editorTemplate) existing.__editorTemplate = c.__editorTemplate;\n if (c.__headerTemplate) existing.__headerTemplate = c.__headerTemplate;\n // Support both 'renderer' alias and 'viewRenderer'\n const cRenderer = c.renderer || c.viewRenderer;\n const existingRenderer = existing.renderer || existing.viewRenderer;\n if (cRenderer && !existingRenderer) {\n existing.viewRenderer = cRenderer;\n if (c.renderer) existing.renderer = cRenderer;\n }\n if (c.editor && !existing.editor) existing.editor = c.editor;\n } else {\n domMap[c.field] = { ...c };\n }\n });\n\n const merged: ColumnInternal[] = (programmatic as ColumnInternal[]).map((c) => {\n const d = domMap[c.field];\n if (!d) return c;\n const m: ColumnInternal = { ...c };\n if (d.header && !m.header) m.header = d.header;\n if (d.type && !m.type) m.type = d.type;\n m.sortable = c.sortable || d.sortable;\n if (c.resizable === true || d.resizable === true) m.resizable = true;\n m.editable = c.editable || d.editable;\n // Merge width/minWidth from DOM if not set programmatically\n if (d.width != null && m.width == null) m.width = d.width;\n if (d.minWidth != null && m.minWidth == null) m.minWidth = d.minWidth;\n if (d.__viewTemplate) m.__viewTemplate = d.__viewTemplate;\n if (d.__editorTemplate) m.__editorTemplate = d.__editorTemplate;\n if (d.__headerTemplate) m.__headerTemplate = d.__headerTemplate;\n // Merge framework adapter renderers/editors from DOM (support both 'renderer' alias and 'viewRenderer')\n const dRenderer = d.renderer || d.viewRenderer;\n const mRenderer = m.renderer || m.viewRenderer;\n if (dRenderer && !mRenderer) {\n m.viewRenderer = dRenderer;\n if (d.renderer) m.renderer = dRenderer;\n }\n if (d.editor && !m.editor) m.editor = d.editor;\n delete domMap[c.field];\n return m;\n });\n Object.keys(domMap).forEach((field) => merged.push(domMap[field]));\n return merged;\n}\n// #endregion\n\n// #region Part Helpers\n/**\n * Safely add a token to an element's `part` attribute (supporting the CSS ::part API)\n * without duplicating values. Falls back to string manipulation if `el.part` API isn't present.\n */\nexport function addPart(el: HTMLElement, token: string): void {\n try {\n (el as ElementWithPart).part?.add?.(token);\n } catch {\n /* empty */\n }\n const existing = el.getAttribute('part');\n if (!existing) el.setAttribute('part', token);\n else if (!existing.split(/\\s+/).includes(token)) el.setAttribute('part', existing + ' ' + token);\n}\n// #endregion\n\n// #region Auto-Sizing\n/**\n * Measure rendered header + visible cell content to assign initial pixel widths\n * to columns when in `content` fit mode. Runs only once unless fit mode changes.\n */\nexport function autoSizeColumns(grid: GridHost): void {\n const mode = grid.effectiveConfig?.fitMode || grid.fitMode || FitModeEnum.STRETCH;\n // Run for both stretch (to derive baseline pixel widths before fr distribution) and fixed.\n if (mode !== FitModeEnum.STRETCH && mode !== FitModeEnum.FIXED) return;\n if (grid.__didInitialAutoSize) return;\n if (!grid.isConnected) return;\n const headerCells = Array.from(grid._headerRowEl?.children || []) as HTMLElement[];\n if (!headerCells.length) return;\n let changed = false;\n grid._visibleColumns.forEach((col: ColumnInternal, i: number) => {\n if (col.width) return;\n const headerCell = headerCells[i];\n let max = headerCell ? headerCell.scrollWidth : 0;\n for (const rowEl of grid._rowPool) {\n const cell = rowEl.children[i] as HTMLElement | undefined;\n if (cell) {\n const w = cell.scrollWidth;\n if (w > max) max = w;\n }\n }\n if (max > 0) {\n col.width = max + 2;\n col.__autoSized = true;\n changed = true;\n }\n });\n if (changed) updateTemplate(grid);\n grid.__didInitialAutoSize = true;\n}\n// #endregion\n\n// #region Template Generation\n/**\n * Compute and apply the CSS grid template string that drives column layout.\n * Uses `fr` units for flexible (non user-resized) columns in stretch mode, otherwise\n * explicit pixel widths or auto sizing.\n */\n// Valid CSS grid track size patterns: numbers with units (px, %, fr, em, rem, etc.),\n// calc(), min-content, max-content, minmax(), fit-content(), auto\nconst VALID_CSS_WIDTH =\n /^(?:\\d+(?:\\.\\d+)?(?:px|%|fr|em|rem|ch|vw|vh|vmin|vmax)|calc\\(.+\\)|min-content|max-content|minmax\\(.+\\)|fit-content\\(.+\\)|auto)$/i;\n\n/** Resolve a column width to a CSS grid track value. Numbers get `px` appended; strings pass through with a dev-mode validity check. */\nfunction resolveWidth(width: string | number, field?: string): string {\n if (typeof width === 'number') return `${width}px`;\n if (!VALID_CSS_WIDTH.test(width)) {\n warnDiagnostic(\n INVALID_COLUMN_WIDTH,\n `Column '${field ?? '?'}' has an invalid CSS width value: '${width}'. Expected a number (px) or a valid CSS unit string (e.g. '30%', '2fr', 'calc(...)').`,\n );\n }\n return width;\n}\n\nexport function updateTemplate(grid: GridHost): void {\n // Modes:\n // - 'stretch': columns with explicit width use that width; columns without width are flexible\n // Uses minmax(minWidth, maxWidth) when both min/max specified (bounded flex)\n // Uses minmax(minWidth, 1fr) when only min specified (grows unbounded)\n // Uses minmax(defaultMin, maxWidth) when only max specified (capped growth)\n // - 'fixed': columns with explicit width use that width; columns without width use max-content\n const mode = grid.effectiveConfig?.fitMode || grid.fitMode || FitModeEnum.STRETCH;\n\n if (mode === FitModeEnum.STRETCH) {\n grid._gridTemplate = grid._visibleColumns\n .map((c: ColumnInternal) => {\n if (c.width != null) return resolveWidth(c.width, c.field);\n // Flexible column: pure 1fr unless minWidth specified\n const min = c.minWidth;\n return min != null ? `minmax(${min}px, 1fr)` : '1fr';\n })\n .join(' ')\n .trim();\n } else {\n // fixed mode: explicit pixel widths or max-content for content-based sizing\n grid._gridTemplate = grid._visibleColumns\n .map((c: ColumnInternal) => {\n if (c.width != null) return resolveWidth(c.width, c.field);\n return 'max-content';\n })\n .join(' ');\n }\n grid.style.setProperty('--tbw-column-template', grid._gridTemplate);\n}\n// #endregion\n","// Centralized template expression evaluation & sanitization utilities.\n// Responsible for safely interpolating {{ }} expressions while blocking\n// access to dangerous globals / reflective capabilities.\nimport type { CompiledViewFunction, EvalContext } from '../types';\n\n// #region Constants\nconst EXPR_RE = /{{\\s*([^}]+)\\s*}}/g;\nconst EMPTY_SENTINEL = '__DG_EMPTY__';\nconst SAFE_EXPR = /^[\\w$. '?+\\-*/%:()!<>=,&|]+$/;\nconst FORBIDDEN =\n /__(proto|defineGetter|defineSetter)|constructor|window|globalThis|global|process|Function|import|eval|Reflect|Proxy|Error|arguments|document|location|cookie|localStorage|sessionStorage|indexedDB|fetch|XMLHttpRequest|WebSocket|Worker|SharedWorker|ServiceWorker|opener|parent|top|frames|self|this\\b/;\n// #endregion\n\n// #region HTML Sanitization\n\n/**\n * Escape a plain text string for safe insertion into HTML.\n * Converts special HTML characters to their entity equivalents.\n *\n * @param text - Plain text string to escape\n * @returns HTML-safe string\n */\nexport function escapeHtml(text: string): string {\n if (!text || typeof text !== 'string') return '';\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Tags that are considered dangerous and will be completely removed.\n * These can execute scripts, load external resources, or manipulate the page.\n */\nconst DANGEROUS_TAGS = new Set([\n 'script',\n 'iframe',\n 'object',\n 'embed',\n 'form',\n 'input',\n 'button',\n 'textarea',\n 'select',\n 'link',\n 'meta',\n 'base',\n 'style',\n 'template',\n 'slot',\n 'portal',\n 'frame',\n 'frameset',\n 'applet',\n 'noscript',\n 'noembed',\n 'plaintext',\n 'xmp',\n 'listing',\n]);\n\n/**\n * Attributes that are considered dangerous - event handlers and data loading.\n */\nconst DANGEROUS_ATTR_PATTERN = /^on\\w+$/i;\n\n/**\n * Attributes that can contain URLs which might be javascript: or data: URIs.\n */\nconst URL_ATTRS = new Set(['href', 'src', 'action', 'formaction', 'data', 'srcdoc', 'xlink:href', 'poster', 'srcset']);\n\n/**\n * Protocol patterns that are dangerous in URLs.\n */\nconst DANGEROUS_URL_PROTOCOL = /^\\s*(javascript|vbscript|data|blob):/i;\n\n/**\n * Sanitize an HTML string by removing dangerous tags and attributes.\n * This is a defense-in-depth measure for content rendered via innerHTML.\n *\n * @param html - Raw HTML string to sanitize\n * @returns Sanitized HTML string safe for innerHTML\n */\nexport function sanitizeHTML(html: string): string {\n if (!html || typeof html !== 'string') return '';\n\n // Fast path: if no HTML tags at all, return as-is (already safe)\n if (html.indexOf('<') === -1) return html;\n\n const template = document.createElement('template');\n template.innerHTML = html;\n\n sanitizeNode(template.content);\n\n return template.innerHTML;\n}\n\n/**\n * Recursively sanitize a DOM node tree.\n */\nfunction sanitizeNode(root: DocumentFragment | Element): void {\n const toRemove: Element[] = [];\n\n // Use querySelectorAll to find all elements, then filter\n const elements = root.querySelectorAll('*');\n\n for (const el of elements) {\n const tagName = el.tagName.toLowerCase();\n\n // Check if tag is dangerous\n if (DANGEROUS_TAGS.has(tagName)) {\n toRemove.push(el);\n continue;\n }\n\n // SVG elements need special handling - they can contain script-like behavior\n if (tagName === 'svg' || el.namespaceURI === 'http://www.w3.org/2000/svg') {\n // Remove entire SVG if it has any suspicious attributes\n const hasDangerousContent = Array.from(el.attributes).some(\n (attr) => DANGEROUS_ATTR_PATTERN.test(attr.name) || attr.name === 'href' || attr.name === 'xlink:href',\n );\n if (hasDangerousContent) {\n toRemove.push(el);\n continue;\n }\n }\n\n // Check and remove dangerous attributes\n const attrsToRemove: string[] = [];\n for (const attr of el.attributes) {\n const attrName = attr.name.toLowerCase();\n\n // Event handlers (onclick, onerror, onload, etc.)\n if (DANGEROUS_ATTR_PATTERN.test(attrName)) {\n attrsToRemove.push(attr.name);\n continue;\n }\n\n // URL attributes with dangerous protocols\n if (URL_ATTRS.has(attrName) && DANGEROUS_URL_PROTOCOL.test(attr.value)) {\n attrsToRemove.push(attr.name);\n continue;\n }\n\n // style attribute can contain expressions (IE) or url() with javascript:\n if (attrName === 'style' && /expression\\s*\\(|javascript:|behavior\\s*:/i.test(attr.value)) {\n attrsToRemove.push(attr.name);\n continue;\n }\n }\n\n attrsToRemove.forEach((name) => el.removeAttribute(name));\n }\n\n // Remove dangerous elements (do this after iteration to avoid modifying during traversal)\n toRemove.forEach((el) => el.remove());\n}\n\n// #endregion\n\n// #region Template Evaluation\nexport function evalTemplateString(raw: string, ctx: EvalContext): string {\n if (!raw || raw.indexOf('{{') === -1) return raw; // fast path (no expressions)\n const parts: { expr: string; result: string }[] = [];\n const evaluated = raw.replace(EXPR_RE, (_m, expr) => {\n const res = evalSingle(expr, ctx);\n parts.push({ expr: expr.trim(), result: res });\n return res;\n });\n const finalStr = postProcess(evaluated);\n // If every part evaluated to EMPTY_SENTINEL we treat this as intentionally blank.\n // If any expression was blocked due to forbidden token (EMPTY_SENTINEL) we *still* only output ''\n // but do not escalate to BLOCKED_SENTINEL unless the original contained explicit forbidden tokens.\n const allEmpty = parts.length && parts.every((p) => p.result === '' || p.result === EMPTY_SENTINEL);\n const hadForbidden = REFLECTIVE_RE.test(raw);\n if (hadForbidden || allEmpty) return '';\n return finalStr;\n}\n\nfunction evalSingle(expr: string, ctx: EvalContext): string {\n expr = (expr || '').trim();\n if (!expr) return EMPTY_SENTINEL;\n if (REFLECTIVE_RE.test(expr)) return EMPTY_SENTINEL;\n if (expr === 'value') return ctx.value == null ? EMPTY_SENTINEL : String(ctx.value);\n if (expr.startsWith('row.') && !/[()?]/.test(expr) && !expr.includes(':')) {\n const key = expr.slice(4);\n const v = ctx.row ? ctx.row[key] : undefined;\n return v == null ? EMPTY_SENTINEL : String(v);\n }\n if (expr.length > 80) return EMPTY_SENTINEL;\n if (!SAFE_EXPR.test(expr) || FORBIDDEN.test(expr)) return EMPTY_SENTINEL;\n const dotChain = expr.match(/\\./g);\n if (dotChain && dotChain.length > 1) return EMPTY_SENTINEL;\n try {\n const fn = new Function('value', 'row', `return (${expr});`);\n const out = fn(ctx.value, ctx.row);\n const str = out == null ? '' : String(out);\n if (REFLECTIVE_RE.test(str)) return EMPTY_SENTINEL;\n return str || EMPTY_SENTINEL;\n } catch {\n return EMPTY_SENTINEL;\n }\n}\n// #endregion\n\n// #region Cell Scrubbing\n/** Pattern matching reflective/introspective APIs that must be stripped from output. */\nconst REFLECTIVE_RE = /Reflect|Proxy|ownKeys/;\n\nfunction postProcess(s: string): string {\n if (!s) return s;\n return s.replace(new RegExp(EMPTY_SENTINEL, 'g'), '').replace(/Reflect\\.[^<>{}\\s]+|\\bProxy\\b|ownKeys\\([^)]*\\)/g, '');\n}\n\nexport function finalCellScrub(cell: HTMLElement): void {\n if (!REFLECTIVE_RE.test(cell.textContent || '')) return;\n // First pass: clear only text nodes containing forbidden tokens\n for (const n of cell.childNodes) {\n if (n.nodeType === Node.TEXT_NODE && REFLECTIVE_RE.test(n.textContent || '')) n.textContent = '';\n }\n // If forbidden tokens persist in element nodes, nuke everything\n if (REFLECTIVE_RE.test(cell.textContent || '')) {\n cell.textContent = '';\n }\n}\n// #endregion\n\n// #region Template Compilation\nexport function compileTemplate(raw: string) {\n const forceBlank = REFLECTIVE_RE.test(raw);\n const fn = ((ctx: EvalContext) => {\n if (forceBlank) return '';\n const out = evalTemplateString(raw, ctx);\n return out;\n }) as CompiledViewFunction;\n fn.__blocked = forceBlank;\n return fn;\n}\n// #endregion\n","/**\n * Sorting Module\n *\n * Handles column sorting state transitions and row ordering.\n */\n\nimport type { ColumnConfig, GridHost, InternalGrid, SortHandler, SortState } from '../types';\nimport { announce } from './aria';\nimport { renderHeader } from './header';\n\n/**\n * Default comparator used when no column-level `sortComparator` is configured.\n * Pushes `null`/`undefined` to the end and compares remaining values via `>` / `<`\n * operators, which works correctly for numbers and falls back to lexicographic\n * comparison for strings.\n *\n * Use this as a fallback inside a custom `sortComparator` when you only need\n * special handling for certain values:\n *\n * @example\n * ```typescript\n * import { defaultComparator } from '@toolbox-web/grid';\n *\n * const column = {\n * field: 'priority',\n * sortComparator: (a, b, rowA, rowB) => {\n * // Pin \"urgent\" to the top, then fall back to default ordering\n * if (a === 'urgent') return -1;\n * if (b === 'urgent') return 1;\n * return defaultComparator(a, b);\n * },\n * };\n * ```\n *\n * @see {@link BaseColumnConfig.sortComparator} for column-level comparators\n * @see {@link builtInSort} for the full sort handler that uses this comparator\n * @category Factory Functions\n */\nexport function defaultComparator(a: unknown, b: unknown): number {\n if (a == null && b == null) return 0;\n if (a == null) return -1;\n if (b == null) return 1;\n return a > b ? 1 : a < b ? -1 : 0;\n}\n\n/**\n * The default `sortHandler` used when none is provided in {@link GridConfig.sortHandler}.\n * Reads each column's `sortComparator` (falling back to {@link defaultComparator})\n * and returns a sorted copy of the rows array.\n *\n * Use this as a fallback inside a custom `sortHandler` when you only need to\n * intercept sorting for specific columns or add pre/post-processing:\n *\n * @example\n * ```typescript\n * import { builtInSort } from '@toolbox-web/grid';\n * import type { SortHandler } from '@toolbox-web/grid';\n *\n * const customSort: SortHandler<Employee> = (rows, state, columns) => {\n * // Server-side sort for the \"salary\" column, client-side for everything else\n * if (state.field === 'salary') {\n * return fetch(`/api/employees?sort=${state.field}&dir=${state.direction}`)\n * .then(res => res.json());\n * }\n * return builtInSort(rows, state, columns);\n * };\n *\n * grid.gridConfig = { sortHandler: customSort };\n * ```\n *\n * @see {@link GridConfig.sortHandler} for configuring the handler\n * @see {@link defaultComparator} for the comparator used per column\n * @category Factory Functions\n */\nexport function builtInSort<T>(rows: T[], sortState: SortState, columns: ColumnConfig<T>[]): T[] {\n const col = columns.find((c) => c.field === sortState.field);\n const comparator = col?.sortComparator ?? defaultComparator;\n const { field, direction } = sortState;\n\n return [...rows].sort((rA: any, rB: any) => {\n return comparator(rA[field], rB[field], rA, rB) * direction;\n });\n}\n\n/**\n * Apply sort result to grid and update UI.\n * Called after sync or async sort completes.\n */\nfunction finalizeSortResult<T>(grid: GridHost<T>, sortedRows: T[], col: ColumnConfig<T>, dir: 1 | -1): void {\n grid._rows = sortedRows;\n // Bump epoch so renderVisibleRows triggers full inline rebuild\n grid.__rowRenderEpoch++;\n // Invalidate pooled rows to guarantee rebuild\n grid._rowPool.forEach((r) => (r.__epoch = -1));\n renderHeader(grid);\n grid.refreshVirtualWindow(true);\n grid.dispatchEvent(new CustomEvent('sort-change', { detail: { field: col.field, direction: dir } }));\n announce(grid, `Sorted by ${col.header ?? col.field}, ${dir === 1 ? 'ascending' : 'descending'}`);\n // Trigger state change after sort applied\n grid.requestStateChange?.();\n}\n\n/**\n * Cycle sort state for a column: none -> ascending -> descending -> none.\n * Restores original row order when clearing sort.\n */\nexport function toggleSort(grid: GridHost, col: ColumnConfig<any>): void {\n if (!grid._sortState || grid._sortState.field !== col.field) {\n if (!grid._sortState) grid.__originalOrder = grid._rows.slice();\n applySort(grid, col, 1);\n } else if (grid._sortState.direction === 1) {\n applySort(grid, col, -1);\n } else {\n grid._sortState = null;\n // Force full row rebuild after clearing sort so templated cells reflect original order\n grid.__rowRenderEpoch++;\n // Invalidate existing pooled row epochs so virtualization triggers a full inline rebuild\n grid._rowPool.forEach((r) => (r.__epoch = -1));\n grid._rows = grid.__originalOrder.slice();\n renderHeader(grid);\n // After re-render ensure cleared column shows aria-sort=\"none\" baseline.\n const headers = grid._headerRowEl?.querySelectorAll('[role=\"columnheader\"].sortable');\n headers?.forEach((h) => {\n if (!h.getAttribute('aria-sort')) h.setAttribute('aria-sort', 'none');\n else if (h.getAttribute('aria-sort') === 'ascending' || h.getAttribute('aria-sort') === 'descending') {\n // The active column was re-rendered already, but normalize any missing ones.\n if (!grid._sortState) h.setAttribute('aria-sort', 'none');\n }\n });\n grid.refreshVirtualWindow(true);\n grid.dispatchEvent(new CustomEvent('sort-change', { detail: { field: col.field, direction: 0 } }));\n announce(grid, 'Sort cleared');\n // Trigger state change after sort is cleared\n grid.requestStateChange?.();\n }\n}\n\n/**\n * Re-apply the current core sort to rows during #rebuildRowModel.\n * Updates __originalOrder so \"clear sort\" restores the current dataset.\n * Returns rows unchanged if no core sort is active or handler is async.\n */\nexport function reapplyCoreSort<T>(grid: InternalGrid<T>, rows: T[]): T[] {\n if (!grid._sortState) return rows;\n grid.__originalOrder = [...rows];\n const handler: SortHandler<any> = grid.effectiveConfig?.sortHandler ?? builtInSort;\n const result = handler(rows, grid._sortState, grid._columns as ColumnConfig<any>[]);\n if (result && typeof (result as Promise<unknown[]>).then === 'function') return rows;\n return result as T[];\n}\n\n/**\n * Apply a concrete sort direction to rows.\n *\n * Uses custom sortHandler from gridConfig if provided, otherwise uses built-in sorting.\n * Supports both sync and async handlers (for server-side sorting).\n */\nexport function applySort(grid: GridHost, col: ColumnConfig<any>, dir: 1 | -1): void {\n grid._sortState = { field: col.field, direction: dir };\n\n const sortState: SortState = { field: col.field, direction: dir };\n const columns = grid._columns as ColumnConfig<any>[];\n\n // Get custom handler from effectiveConfig, or use built-in\n const handler: SortHandler<any> = grid.effectiveConfig?.sortHandler ?? builtInSort;\n\n const result = handler(grid._rows, sortState, columns);\n\n // Handle async (Promise) or sync result\n if (result && typeof (result as Promise<unknown[]>).then === 'function') {\n // Async handler - wait for result\n (result as Promise<unknown[]>).then((sortedRows) => {\n finalizeSortResult(grid, sortedRows, col, dir);\n });\n } else {\n // Sync handler - apply immediately\n finalizeSortResult(grid, result as unknown[], col, dir);\n }\n}\n","/**\n * Header Rendering Module\n *\n * Handles rendering of the grid header row with sorting and resize affordances.\n * Supports custom header renderers via `headerRenderer` (full control) and\n * `headerLabelRenderer` (label only) column properties.\n */\n\nimport type { ColumnInternal, GridHost, HeaderCellContext, IconValue, InternalGrid } from '../types';\nimport { DEFAULT_GRID_ICONS } from '../types';\nimport { addPart } from './columns';\nimport { sanitizeHTML } from './sanitize';\nimport { toggleSort } from './sorting';\n\n// #region Helper Functions\n/**\n * Check if a column is sortable, respecting both column-level and grid-level settings.\n * Grid-wide `sortable: false` disables sorting for all columns.\n * Grid-wide `sortable: true` (or undefined) allows column-level `sortable` to control behavior.\n */\nfunction isColumnSortable(grid: InternalGrid, col: ColumnInternal): boolean {\n // Grid-wide sortable defaults to true if not specified\n const gridSortable = grid.effectiveConfig?.sortable !== false;\n return gridSortable && col.sortable === true;\n}\n\n/**\n * Check if a column is resizable, respecting both column-level and grid-level settings.\n * Grid-wide `resizable: false` disables resizing for all columns.\n * Grid-wide `resizable: true` (or undefined) allows column-level `resizable` to control behavior.\n */\nfunction isColumnResizable(grid: InternalGrid, col: ColumnInternal): boolean {\n // Grid-wide resizable defaults to true if not specified\n const gridResizable = grid.effectiveConfig?.resizable !== false;\n // Column-level resizable defaults to true if not specified\n return gridResizable && col.resizable !== false;\n}\n\n/**\n * Set an icon value on an element. Handles both string and HTMLElement icons.\n */\nfunction setIcon(element: HTMLElement, icon: IconValue): void {\n if (typeof icon === 'string') {\n element.textContent = icon;\n } else if (icon instanceof HTMLElement) {\n element.innerHTML = '';\n element.appendChild(icon.cloneNode(true));\n }\n}\n\n/**\n * Create a sort indicator element for a column.\n */\nfunction createSortIndicator(grid: InternalGrid, col: ColumnInternal): HTMLElement {\n const icon = document.createElement('span');\n addPart(icon, 'sort-indicator');\n const active = grid._sortState?.field === col.field ? grid._sortState.direction : 0;\n const icons = { ...DEFAULT_GRID_ICONS, ...grid.icons };\n const iconValue = active === 1 ? icons.sortAsc : active === -1 ? icons.sortDesc : icons.sortNone;\n setIcon(icon, iconValue);\n return icon;\n}\n\n/**\n * Create a resize handle element.\n */\nfunction createResizeHandle(grid: InternalGrid, colIndex: number, cell: HTMLElement): HTMLElement {\n const handle = document.createElement('div');\n handle.className = 'resize-handle';\n handle.setAttribute('aria-hidden', 'true');\n handle.addEventListener('mousedown', (e: MouseEvent) => {\n e.stopPropagation();\n e.preventDefault();\n grid._resizeController.start(e, colIndex, cell);\n });\n handle.addEventListener('dblclick', (e: MouseEvent) => {\n e.stopPropagation();\n e.preventDefault();\n grid._resizeController.resetColumn(colIndex);\n });\n return handle;\n}\n\n/**\n * Setup sorting click/keyboard handlers for a header cell.\n */\nfunction setupSortHandlers(grid: GridHost, col: ColumnInternal, colIndex: number, cell: HTMLElement): void {\n cell.classList.add('sortable');\n cell.tabIndex = 0;\n const active = grid._sortState?.field === col.field ? grid._sortState.direction : 0;\n cell.setAttribute('aria-sort', active === 0 ? 'none' : active === 1 ? 'ascending' : 'descending');\n\n cell.addEventListener('click', (e) => {\n if (grid._resizeController?.isResizing) return;\n if (grid._dispatchHeaderClick?.(e, col, cell)) return;\n toggleSort(grid, col);\n });\n cell.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n if (grid._dispatchHeaderClick?.(e, col, cell)) return;\n toggleSort(grid, col);\n }\n });\n}\n\n/**\n * Append renderer output to cell element.\n * Handles Node, string (with sanitization), or null/void (no-op).\n */\nfunction appendRendererOutput(cell: HTMLElement, output: Node | string | void | null): void {\n if (output == null) return;\n if (typeof output === 'string') {\n // sanitizeHTML returns a sanitized HTML string, use a container to convert to DOM\n const container = document.createElement('span');\n container.innerHTML = sanitizeHTML(output);\n // Move all child nodes to the cell\n while (container.firstChild) {\n cell.appendChild(container.firstChild);\n }\n } else if (output instanceof Node) {\n cell.appendChild(output);\n }\n}\n// #endregion\n\n// #region Render Header\n/**\n * Rebuild the header row DOM based on current column configuration, attaching\n * sorting and resize affordances where enabled.\n *\n * Rendering precedence:\n * 1. `headerRenderer` - Full control over header cell content\n * 2. `headerLabelRenderer` - Custom label, framework handles icons/interactions\n * 3. `__headerTemplate` - Light DOM template (framework adapter)\n * 4. `header` property - Plain text header\n * 5. `field` - Fallback to field name\n */\nexport function renderHeader(grid: GridHost): void {\n grid._headerRowEl = grid.findHeaderRow!();\n const headerRow = grid._headerRowEl as HTMLElement;\n\n // Guard: DOM may not be built yet\n if (!headerRow) {\n return;\n }\n\n headerRow.innerHTML = '';\n\n grid._visibleColumns.forEach((col: ColumnInternal, i: number) => {\n const cell = document.createElement('div');\n cell.className = 'cell';\n addPart(cell, 'header-cell');\n cell.setAttribute('role', 'columnheader');\n\n // aria-colindex is 1-based\n cell.setAttribute('aria-colindex', String(i + 1));\n cell.setAttribute('data-field', col.field);\n cell.setAttribute('data-col', String(i)); // Add data-col for consistency with body cells\n if (col.type) cell.setAttribute('data-type', col.type);\n\n // Compute header value and sort state for context\n const headerValue = col.header ?? col.field;\n const sortDirection = grid._sortState?.field === col.field ? grid._sortState.direction : 0;\n const sortState: 'asc' | 'desc' | null = sortDirection === 1 ? 'asc' : sortDirection === -1 ? 'desc' : null;\n\n // Check for headerRenderer (full control mode)\n if (col.headerRenderer) {\n // Create context with helper functions\n const ctx: HeaderCellContext<any> = {\n column: col,\n value: headerValue,\n sortState,\n filterActive: false, // Will be set by FilteringPlugin if active\n cellEl: cell,\n renderSortIcon: () => (isColumnSortable(grid, col) ? createSortIndicator(grid, col) : null),\n renderFilterButton: () => null, // FilteringPlugin adds filter button via afterRender\n };\n\n const output = col.headerRenderer(ctx);\n appendRendererOutput(cell, output);\n\n // Setup sort handlers if sortable (user may not have included sort icon but still want click-to-sort)\n if (isColumnSortable(grid, col)) {\n setupSortHandlers(grid, col, i, cell);\n }\n\n // Always add resize handle if resizable (not user's responsibility)\n if (isColumnResizable(grid, col)) {\n cell.classList.add('resizable');\n cell.appendChild(createResizeHandle(grid, i, cell));\n }\n }\n // Check for headerLabelRenderer (label-only mode)\n else if (col.headerLabelRenderer) {\n const ctx = {\n column: col,\n value: headerValue,\n };\n\n const output = col.headerLabelRenderer(ctx);\n // Wrap output in a span for consistency with default rendering\n const span = document.createElement('span');\n if (output == null) {\n span.textContent = headerValue;\n } else if (typeof output === 'string') {\n span.innerHTML = sanitizeHTML(output);\n } else if (output instanceof Node) {\n span.appendChild(output);\n }\n cell.appendChild(span);\n\n // Framework handles the rest: sort icon, resize handle\n if (isColumnSortable(grid, col)) {\n setupSortHandlers(grid, col, i, cell);\n cell.appendChild(createSortIndicator(grid, col));\n }\n if (isColumnResizable(grid, col)) {\n cell.classList.add('resizable');\n cell.appendChild(createResizeHandle(grid, i, cell));\n }\n }\n // Light DOM template (framework adapter)\n else if (col.__headerTemplate) {\n Array.from(col.__headerTemplate.childNodes).forEach((n) => cell.appendChild(n.cloneNode(true)));\n\n // Standard affordances\n if (isColumnSortable(grid, col)) {\n setupSortHandlers(grid, col, i, cell);\n cell.appendChild(createSortIndicator(grid, col));\n }\n if (isColumnResizable(grid, col)) {\n cell.classList.add('resizable');\n cell.appendChild(createResizeHandle(grid, i, cell));\n }\n }\n // Default: plain text header\n else {\n const span = document.createElement('span');\n span.textContent = headerValue;\n cell.appendChild(span);\n\n // Standard affordances\n if (isColumnSortable(grid, col)) {\n setupSortHandlers(grid, col, i, cell);\n cell.appendChild(createSortIndicator(grid, col));\n }\n if (isColumnResizable(grid, col)) {\n cell.classList.add('resizable');\n cell.appendChild(createResizeHandle(grid, i, cell));\n }\n }\n\n headerRow.appendChild(cell);\n });\n\n // Ensure every sortable header has a baseline aria-sort if not already set during construction.\n headerRow.querySelectorAll('.cell.sortable').forEach((el) => {\n if (!el.getAttribute('aria-sort')) el.setAttribute('aria-sort', 'none');\n });\n\n // Set ARIA role only if header has children (role=\"row\" requires columnheader children)\n // When grid is cleared with 0 columns, the header row should not have role=\"row\"\n if (headerRow.children.length > 0) {\n headerRow.setAttribute('role', 'row');\n headerRow.setAttribute('aria-rowindex', '1');\n } else {\n headerRow.removeAttribute('role');\n headerRow.removeAttribute('aria-rowindex');\n }\n}\n// #endregion\n","import type { ColumnConfigMap, ColumnType, InferredColumnResult, PrimitiveColumnType } from '../types';\n/**\n * Best-effort primitive type inference for a cell value used during automatic column generation.\n */\nfunction inferType(value: any): PrimitiveColumnType {\n if (value == null) return 'string';\n if (typeof value === 'number') return 'number';\n if (typeof value === 'boolean') return 'boolean';\n if (value instanceof Date) return 'date';\n if (typeof value === 'string' && /\\d{4}-\\d{2}-\\d{2}/.test(value) && !isNaN(Date.parse(value))) return 'date';\n return 'string';\n}\n/**\n * Derive column definitions from provided configuration or by inspecting the first row of data.\n * Returns both the resolved column array and a field->type map.\n */\nexport function inferColumns<TRow extends Record<string, unknown>>(\n rows: TRow[],\n provided?: ColumnConfigMap<TRow>,\n): InferredColumnResult<TRow> {\n if (provided && provided.length) {\n const typeMap: Record<string, ColumnType> = {};\n provided.forEach((col) => {\n if (col.type) typeMap[col.field] = col.type;\n });\n return { columns: provided, typeMap };\n }\n const sample = rows[0] || ({} as TRow);\n const columns: ColumnConfigMap<TRow> = Object.keys(sample).map((k) => {\n const v = (sample as Record<string, unknown>)[k];\n const type = inferType(v);\n return { field: k as keyof TRow & string, header: k.charAt(0).toUpperCase() + k.slice(1), type };\n });\n const typeMap: Record<string, ColumnType> = {};\n columns.forEach((c) => {\n typeMap[c.field] = c.type || 'string';\n });\n return { columns, typeMap };\n}\nexport { inferType };\n","/**\n * Centralized Render Scheduler for the Grid component.\n *\n * This scheduler batches all rendering work into a single requestAnimationFrame,\n * eliminating race conditions between different parts of the grid (ResizeObserver,\n * framework adapters, virtualization, etc.) that previously scheduled independent RAFs.\n *\n * ## Design Principles\n *\n * 1. **Single RAF per frame**: All render requests are batched into one RAF callback\n * 2. **Phase-based execution**: Work is organized into ordered phases that run sequentially\n * 3. **Highest-phase wins**: Multiple requests merge to the highest requested phase\n * 4. **Framework-agnostic timing**: Eliminates need for \"double RAF\" hacks\n *\n * ## Render Phases (execute in order)\n *\n * - STYLE (1): Lightweight style/class updates, plugin afterRender hooks\n * - VIRTUALIZATION (2): Virtual window recalculation (scroll, resize)\n * - HEADER (3): Header re-render only\n * - ROWS (4): Row model rebuild + header + template + virtual window\n * - COLUMNS (5): Column processing + rows phase work\n * - FULL (6): Complete render including config merge\n *\n * @example\n * ```typescript\n * // The scheduler takes the grid (InternalGrid) directly:\n * const scheduler = new RenderScheduler(this as unknown as InternalGrid);\n *\n * // Request a full render\n * scheduler.requestPhase(RenderPhase.FULL, 'initial-setup');\n *\n * // Wait for render to complete\n * await scheduler.whenReady();\n * ```\n */\n\nimport type { InternalGrid } from '../types';\n\n// #region Types & Enums\n/**\n * Render phases in order of execution.\n * Higher phases include all lower phase work.\n *\n * @category Plugin Development\n */\nexport enum RenderPhase {\n /** Lightweight style updates only (plugin afterRender hooks) */\n STYLE = 1,\n /** Virtual window recalculation (includes STYLE) */\n VIRTUALIZATION = 2,\n /** Header re-render (includes VIRTUALIZATION) */\n HEADER = 3,\n /** Row model rebuild (includes HEADER) */\n ROWS = 4,\n /** Column processing (includes ROWS) */\n COLUMNS = 5,\n /** Full render including config merge (includes COLUMNS) */\n FULL = 6,\n}\n\n/**\n * @internal Scheduler now takes InternalGrid directly — no callback interface needed.\n */\n// #endregion\n\n// #region RenderScheduler\n/**\n * Centralized render scheduler that batches all grid rendering into single RAF.\n */\nexport class RenderScheduler {\n readonly #grid: InternalGrid;\n\n /** Current pending phase (0 = none pending) */\n #pendingPhase: RenderPhase | 0 = 0;\n\n /** RAF handle for cancellation */\n #rafHandle = 0;\n\n /** Promise that resolves when current render completes */\n #readyPromise: Promise<void> | null = null;\n #readyResolve: (() => void) | null = null;\n\n /** Initial ready resolver (for component's initial ready() promise) */\n #initialReadyResolver: (() => void) | null = null;\n #initialReadyFired = false;\n\n constructor(grid: InternalGrid) {\n this.#grid = grid;\n }\n\n /**\n * Request a render at the specified phase.\n * Multiple requests are batched - the highest phase wins.\n *\n * @param phase - The render phase to execute\n * @param _source - Debug identifier for what triggered this request (unused, kept for API compatibility)\n */\n requestPhase(phase: RenderPhase, _source: string): void {\n // Merge to highest phase\n if (phase > this.#pendingPhase) {\n this.#pendingPhase = phase;\n }\n\n // Schedule RAF if not already scheduled\n if (this.#rafHandle === 0) {\n this.#ensureReadyPromise();\n this.#rafHandle = requestAnimationFrame(() => this.#flush());\n }\n }\n\n /**\n * Returns a promise that resolves when the current render cycle completes.\n * If no render is pending, returns an already-resolved promise.\n */\n whenReady(): Promise<void> {\n if (this.#readyPromise) {\n return this.#readyPromise;\n }\n return Promise.resolve();\n }\n\n /**\n * Set the initial ready resolver (called once on first render).\n * This connects to the grid's `ready()` promise.\n */\n setInitialReadyResolver(resolver: () => void): void {\n this.#initialReadyResolver = resolver;\n }\n\n /**\n * Cancel any pending render.\n * Useful for cleanup when component disconnects.\n */\n cancel(): void {\n if (this.#rafHandle !== 0) {\n cancelAnimationFrame(this.#rafHandle);\n this.#rafHandle = 0;\n }\n this.#pendingPhase = 0;\n\n // Resolve any pending ready promise (don't leave it hanging)\n if (this.#readyResolve) {\n this.#readyResolve();\n this.#readyResolve = null;\n this.#readyPromise = null;\n }\n }\n\n /**\n * Check if a render is currently pending.\n */\n get isPending(): boolean {\n return this.#pendingPhase !== 0;\n }\n\n /**\n * Get the current pending phase (0 if none).\n */\n get pendingPhase(): RenderPhase | 0 {\n return this.#pendingPhase;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private Methods\n // ─────────────────────────────────────────────────────────────────────────\n\n #ensureReadyPromise(): void {\n if (!this.#readyPromise) {\n this.#readyPromise = new Promise<void>((resolve) => {\n this.#readyResolve = resolve;\n });\n }\n }\n\n /**\n * Execute all pending render work in phase order.\n * This is the single RAF callback that does all rendering.\n */\n #flush(): void {\n this.#rafHandle = 0;\n\n // Bail if component disconnected\n if (!this.#grid._schedulerIsConnected) {\n this.#pendingPhase = 0;\n if (this.#readyResolve) {\n this.#readyResolve();\n this.#readyResolve = null;\n this.#readyPromise = null;\n }\n return;\n }\n\n const phase = this.#pendingPhase;\n this.#pendingPhase = 0;\n\n // Execute phases in order (higher phases include lower phase work)\n // The execution order respects data dependencies:\n // mergeConfig → processRows → processColumns → renderHeader → virtualWindow → afterRender\n\n // mergeConfig runs for FULL phase OR COLUMNS phase (to pick up framework adapter renderers)\n // IMPORTANT: mergeConfig must run BEFORE processRows because the row model depends on\n // column configuration, and framework adapters (React/Angular) may register renderers\n // asynchronously after the initial gridConfig is set.\n if (phase >= RenderPhase.COLUMNS) {\n this.#grid._schedulerMergeConfig();\n }\n\n // Phase 4 (ROWS): Rebuild row model\n // NOTE: processRows MUST run before processColumns because tree plugin's\n // processColumns depends on flattenedRows populated by processRows\n if (phase >= RenderPhase.ROWS) {\n this.#grid._schedulerProcessRows();\n }\n\n // Phase 5 (COLUMNS): Process columns + update template\n if (phase >= RenderPhase.COLUMNS) {\n this.#grid._schedulerProcessColumns();\n this.#grid._schedulerUpdateTemplate();\n }\n\n // Phase 3 (HEADER): Render header\n if (phase >= RenderPhase.HEADER) {\n this.#grid._schedulerRenderHeader();\n }\n\n // Phase 2 (VIRTUALIZATION): Recalculate virtual window\n if (phase >= RenderPhase.VIRTUALIZATION) {\n this.#grid.refreshVirtualWindow(true, true);\n }\n\n // Phase 1 (STYLE): Run afterRender hooks (always runs)\n if (phase >= RenderPhase.STYLE) {\n this.#grid._schedulerAfterRender();\n }\n\n // Fire initial ready resolver once\n if (!this.#initialReadyFired && this.#initialReadyResolver) {\n this.#initialReadyFired = true;\n this.#initialReadyResolver();\n }\n\n // Resolve the ready promise\n if (this.#readyResolve) {\n this.#readyResolve();\n this.#readyResolve = null;\n this.#readyPromise = null;\n }\n }\n}\n// #endregion\n","/**\n * ConfigManager - Unified Configuration Lifecycle Management\n *\n * Manages all configuration concerns for the grid:\n * - Source collection (gridConfig, columns, attributes, Light DOM)\n * - Two-layer config (frozen original + mutable effective)\n * - State persistence (collect/apply/reset via diff)\n * - Change notification for re-rendering\n *\n * This is an internal module - grid.ts delegates to ConfigManager\n * but the public API remains unchanged.\n */\n\nimport type { BaseGridPlugin } from '../plugin';\nimport type {\n ColumnConfig,\n ColumnConfigMap,\n ColumnInternal,\n ColumnSortState,\n ColumnState,\n FitMode,\n GridColumnState,\n GridConfig,\n GridHost,\n} from '../types';\nimport { mergeColumns, parseLightDomColumns, updateTemplate } from './columns';\nimport { renderHeader } from './header';\nimport { inferColumns } from './inference';\nimport { RenderPhase } from './render-scheduler';\nimport { compileTemplate } from './sanitize';\n\n/** Debounce timeout for state change events */\nconst STATE_CHANGE_DEBOUNCE_MS = 100;\n\n/**\n * ConfigManager handles all configuration lifecycle for the grid.\n *\n * Manages:\n * - Source collection (gridConfig, columns, attributes, Light DOM)\n * - Effective config (merged from all sources)\n * - State persistence (collectState, applyState, resetState)\n * - Column visibility and ordering\n */\nexport class ConfigManager<T = unknown> {\n // #region Sources (raw input from user)\n #gridConfig?: GridConfig<T>;\n #columns?: ColumnConfig<T>[] | ColumnConfigMap<T>;\n #fitMode?: FitMode;\n\n // Light DOM cache\n #lightDomColumnsCache?: ColumnInternal<T>[];\n #originalColumnNodes?: HTMLElement[];\n // #endregion\n\n // #region Two-Layer Config Architecture\n /**\n * Original config (frozen) - Built from sources, never mutated.\n * This is the \"canonical\" config that sources compile into.\n * Used as the reset point for effectiveConfig.\n */\n #originalConfig: GridConfig<T> = {};\n\n /**\n * Effective config (mutable) - Cloned from original, runtime mutations here.\n * This is what rendering reads from.\n * Runtime changes: hidden, width, sort order, column order.\n */\n #effectiveConfig: GridConfig<T> = {};\n // #endregion\n\n // #region State Tracking\n #sourcesChanged = true;\n #changeListeners: Array<() => void> = [];\n #lightDomObserver?: MutationObserver;\n #stateChangeTimeoutId?: ReturnType<typeof setTimeout>;\n #lightDomDebounceTimer?: ReturnType<typeof setTimeout>;\n #initialColumnState?: GridColumnState;\n #grid: GridHost<T>;\n\n // Shell state (Light DOM title)\n #lightDomTitle?: string;\n\n constructor(grid: GridHost<T>) {\n this.#grid = grid;\n }\n // #endregion\n\n // #region Getters\n /** Get the frozen original config (compiled from sources, immutable) */\n get original(): GridConfig<T> {\n return this.#originalConfig;\n }\n\n /** Get the mutable effective config (current runtime state) */\n get effective(): GridConfig<T> {\n return this.#effectiveConfig;\n }\n\n /** Get columns from effective config */\n get columns(): ColumnInternal<T>[] {\n return (this.#effectiveConfig.columns ?? []) as ColumnInternal<T>[];\n }\n\n /** Set columns on effective config */\n set columns(value: ColumnInternal<T>[]) {\n this.#effectiveConfig.columns = value as ColumnConfig<T>[];\n }\n\n /** Get light DOM columns cache */\n get lightDomColumnsCache(): ColumnInternal<T>[] | undefined {\n return this.#lightDomColumnsCache;\n }\n\n /** Set light DOM columns cache */\n set lightDomColumnsCache(value: ColumnInternal<T>[] | undefined) {\n this.#lightDomColumnsCache = value;\n }\n\n /** Get original column nodes */\n get originalColumnNodes(): HTMLElement[] | undefined {\n return this.#originalColumnNodes;\n }\n\n /** Set original column nodes */\n set originalColumnNodes(value: HTMLElement[] | undefined) {\n this.#originalColumnNodes = value;\n }\n\n /** Get light DOM title */\n get lightDomTitle(): string | undefined {\n return this.#lightDomTitle;\n }\n\n /** Set light DOM title */\n set lightDomTitle(value: string | undefined) {\n this.#lightDomTitle = value;\n }\n\n /** Get initial column state */\n get initialColumnState(): GridColumnState | undefined {\n return this.#initialColumnState;\n }\n\n /** Set initial column state */\n set initialColumnState(value: GridColumnState | undefined) {\n this.#initialColumnState = value;\n }\n // #endregion\n\n // #region Source Management\n /**\n * Check if sources have changed since last merge.\n */\n get sourcesChanged(): boolean {\n return this.#sourcesChanged;\n }\n\n /**\n * Mark that sources have changed and need re-merging.\n * Call this when external state (shell maps, etc.) that feeds into\n * collectAllSources() has been updated.\n */\n markSourcesChanged(): void {\n this.#sourcesChanged = true;\n }\n // #endregion\n\n // #region Source Setters\n /** Set gridConfig source */\n setGridConfig(config: GridConfig<T> | undefined): void {\n this.#gridConfig = config;\n this.#sourcesChanged = true;\n // Clear light DOM cache for framework async content\n this.#lightDomColumnsCache = undefined;\n }\n\n /** Get the raw gridConfig source */\n getGridConfig(): GridConfig<T> | undefined {\n return this.#gridConfig;\n }\n\n /** Set columns source */\n setColumns(columns: ColumnConfig<T>[] | ColumnConfigMap<T> | undefined): void {\n this.#columns = columns;\n this.#sourcesChanged = true;\n }\n\n /** Get the raw columns source */\n getColumns(): ColumnConfig<T>[] | ColumnConfigMap<T> | undefined {\n return this.#columns;\n }\n\n /** Set fitMode source */\n setFitMode(mode: FitMode | undefined): void {\n this.#fitMode = mode;\n this.#sourcesChanged = true;\n }\n\n /** Get the raw fitMode source */\n getFitMode(): FitMode | undefined {\n return this.#fitMode;\n }\n // #endregion\n\n // #region Config Lifecycle\n /**\n * Merge all sources into effective config.\n * Also applies post-merge operations (rowHeight, fixed mode widths, animation).\n *\n * Called by RenderScheduler's mergeConfig phase.\n *\n * Two-layer architecture:\n * 1. Sources → #originalConfig (frozen, immutable)\n * 2. Clone → #effectiveConfig (mutable, runtime changes)\n *\n * When sources change, both layers are rebuilt.\n * When sources haven't changed AND columns exist, this is a no-op.\n * Runtime mutations only affect effectiveConfig.\n * resetState() clones originalConfig back to effectiveConfig.\n */\n merge(): void {\n // Only rebuild when sources have actually changed.\n // Exception: always rebuild if we don't have columns yet (inference may be needed)\n const hasColumns = (this.#effectiveConfig.columns?.length ?? 0) > 0;\n if (!this.#sourcesChanged && hasColumns) {\n return; // effectiveConfig is already valid\n }\n\n // Build config from all sources\n const base = this.#collectAllSources();\n\n // Mark sources as processed\n this.#sourcesChanged = false;\n\n // Freeze as the new original config (immutable reference point)\n this.#originalConfig = base;\n Object.freeze(this.#originalConfig);\n if (this.#originalConfig.columns) {\n // Deep freeze columns array (but not the column objects themselves,\n // as we need effectiveConfig columns to be mutable)\n Object.freeze(this.#originalConfig.columns);\n }\n\n // Clone to effective config (mutable copy for runtime changes)\n this.#effectiveConfig = this.#cloneConfig(this.#originalConfig);\n\n // Apply post-merge operations to effectiveConfig\n this.#applyPostMergeOperations();\n }\n\n /**\n * Deep clone a config object, handling functions (renderers, editors).\n * Uses structuredClone where possible, with fallback for function properties.\n */\n #cloneConfig(config: GridConfig<T>): GridConfig<T> {\n // Can't use structuredClone because config may contain functions\n const clone: GridConfig<T> = { ...config };\n\n // Deep clone columns (they may have runtime-mutable state)\n if (config.columns) {\n clone.columns = config.columns.map((col) => ({ ...col }));\n }\n\n // Deep clone shell if present\n if (config.shell) {\n clone.shell = {\n ...config.shell,\n header: config.shell.header ? { ...config.shell.header } : undefined,\n toolPanel: config.shell.toolPanel ? { ...config.shell.toolPanel } : undefined,\n toolPanels: config.shell.toolPanels?.map((p) => ({ ...p })),\n headerContents: config.shell.headerContents?.map((h) => ({ ...h })),\n };\n }\n\n return clone;\n }\n\n /**\n * Apply operations that depend on the merged effective config.\n * These were previously in grid.ts #mergeEffectiveConfig().\n */\n #applyPostMergeOperations(): void {\n const config = this.#effectiveConfig;\n\n // Apply typeDefaults to columns that have a type but no explicit renderer/format\n // This is done at config time for performance - no runtime lookup needed\n this.#applyTypeDefaultsToColumns();\n\n // Apply rowHeight from config if specified (only for numeric values)\n // Function-based rowHeight is handled by variable height virtualization\n if (typeof config.rowHeight === 'number' && config.rowHeight > 0) {\n this.#grid._virtualization.rowHeight = config.rowHeight;\n }\n\n // If fixed mode and width not specified: assign default 80px\n if (config.fitMode === 'fixed') {\n const columns = this.columns;\n columns.forEach((c) => {\n if (c.width == null) c.width = 80;\n });\n }\n\n // Apply animation configuration to host element\n this.#grid._applyAnimationConfig(config);\n }\n\n /**\n * Apply typeDefaults from gridConfig to columns.\n * For each column with a `type` property that matches a key in `typeDefaults`,\n * copy the renderer/format to the column if not already set.\n *\n * This is done at config merge time for performance - avoids runtime lookups.\n */\n #applyTypeDefaultsToColumns(): void {\n const typeDefaults = this.#effectiveConfig.typeDefaults;\n if (!typeDefaults) return;\n\n const columns = this.columns;\n for (const col of columns) {\n if (!col.type) continue;\n\n const typeDefault = typeDefaults[col.type];\n if (!typeDefault) continue;\n\n // Apply renderer if column doesn't have one\n // Priority: column.renderer > column.viewRenderer > typeDefault.renderer\n if (!col.renderer && !col.viewRenderer && typeDefault.renderer) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n col.renderer = typeDefault.renderer as any;\n }\n\n // Apply format if column doesn't have one\n if (!col.format && typeDefault.format) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n col.format = typeDefault.format as any;\n }\n\n // Apply editor if column doesn't have one\n if (!col.editor && typeDefault.editor) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n col.editor = typeDefault.editor as any;\n }\n\n // Apply editorParams if column doesn't have them\n if (!col.editorParams && typeDefault.editorParams) {\n col.editorParams = typeDefault.editorParams;\n }\n }\n }\n\n /**\n * Collect all sources into a single config object.\n * This is the core merge logic extracted from grid.ts #mergeEffectiveConfig.\n *\n * Collects all sources into a canonical config object.\n * This becomes the frozen #originalConfig.\n *\n * Sources (in order of precedence, low to high):\n * 1. gridConfig.columns\n * 2. Light DOM columns (merged with config columns)\n * 3. columns prop (overrides if set)\n * 4. Inferred columns (if still empty)\n *\n * Runtime state (hidden, width) is NOT preserved here - that's in effectiveConfig.\n */\n #collectAllSources(): GridConfig<T> {\n const base: GridConfig<T> = this.#gridConfig ? { ...this.#gridConfig } : {};\n const configColumns: ColumnConfig<T>[] = Array.isArray(base.columns) ? [...base.columns] : [];\n\n // Light DOM cached parse - clone to avoid mutation\n const domCols: ColumnConfig<T>[] = (this.#lightDomColumnsCache ?? []).map((c) => ({\n ...c,\n })) as ColumnConfig<T>[];\n\n // Use mergeColumns to combine config columns with light DOM columns\n // This handles all the complex merge logic including templates and renderers\n let columns: ColumnInternal<T>[] = mergeColumns(\n configColumns as ColumnInternal<T>[],\n domCols as ColumnInternal<T>[],\n ) as ColumnInternal<T>[];\n\n // Columns prop highest structural precedence (overrides merged result)\n if (this.#columns && (this.#columns as ColumnConfig<T>[]).length) {\n // When columns prop is set, merge with light DOM columns for renderers/templates\n columns = mergeColumns(\n this.#columns as ColumnInternal<T>[],\n domCols as ColumnInternal<T>[],\n ) as ColumnInternal<T>[];\n }\n\n // Inference if still empty\n const rows = this.#grid.sourceRows;\n if (columns.length === 0 && rows.length) {\n const result = inferColumns(rows as Record<string, unknown>[]);\n columns = result.columns as ColumnInternal<T>[];\n }\n\n if (columns.length) {\n // Apply per-column defaults\n columns.forEach((c) => {\n if (c.sortable === undefined) c.sortable = true;\n if (c.resizable === undefined) c.resizable = true;\n if (c.__originalWidth === undefined && typeof c.width === 'number') {\n c.__originalWidth = c.width;\n }\n });\n\n // Compile inline templates (from light DOM <template> elements)\n columns.forEach((c) => {\n if (c.__viewTemplate && !c.__compiledView) {\n c.__compiledView = compileTemplate((c.__viewTemplate as HTMLElement).innerHTML);\n }\n if (c.__editorTemplate && !c.__compiledEditor) {\n c.__compiledEditor = compileTemplate(c.__editorTemplate.innerHTML);\n }\n });\n\n base.columns = columns as ColumnConfig<T>[];\n }\n\n // Individual prop overrides\n if (this.#fitMode) base.fitMode = this.#fitMode;\n if (!base.fitMode) base.fitMode = 'stretch';\n\n // ========================================================================\n // Merge shell configuration from ShellState into effectiveConfig.shell\n // This ensures a single source of truth for all shell config\n // ========================================================================\n this.#mergeShellConfig(base);\n\n // Store columnState from gridConfig if not already set\n if (base.columnState && !this.#initialColumnState) {\n this.#initialColumnState = base.columnState;\n }\n\n return base;\n }\n\n /**\n * Merge shell state into base config's shell property.\n * Ensures effectiveConfig.shell is the single source of truth.\n *\n * IMPORTANT: This method must NOT mutate the original gridConfig.\n * We shallow-clone the shell hierarchy to avoid side effects.\n */\n #mergeShellConfig(base: GridConfig<T>): void {\n // Clone shell hierarchy to avoid mutating original gridConfig\n // base.shell may still reference this.#gridConfig.shell, so we need fresh objects\n base.shell = base.shell ? { ...base.shell } : {};\n base.shell.header = base.shell.header ? { ...base.shell.header } : {};\n\n // Sync light DOM title\n const shellLightDomTitle = this.#grid._shellState.lightDomTitle;\n if (shellLightDomTitle) {\n this.#lightDomTitle = shellLightDomTitle;\n }\n if (this.#lightDomTitle && !base.shell.header.title) {\n base.shell.header.title = this.#lightDomTitle;\n }\n\n // Sync light DOM header content elements\n const lightDomHeaderContent = this.#grid._shellState.lightDomHeaderContent;\n if (lightDomHeaderContent?.length > 0) {\n base.shell.header.lightDomContent = lightDomHeaderContent;\n }\n\n // Sync hasToolButtonsContainer from shell state\n if (this.#grid._shellState.hasToolButtonsContainer) {\n base.shell.header.hasToolButtonsContainer = true;\n }\n\n // Sync tool panels (from plugins + API + Light DOM)\n const toolPanelsMap = this.#grid._shellState.toolPanels;\n if (toolPanelsMap.size > 0) {\n const panels = Array.from(toolPanelsMap.values());\n // Sort by order (lower = first, default 100)\n panels.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n base.shell.toolPanels = panels;\n }\n\n // Sync header contents (from plugins + API)\n const headerContentsMap = this.#grid._shellState.headerContents;\n if (headerContentsMap.size > 0) {\n const contents = Array.from(headerContentsMap.values());\n // Sort by order\n contents.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n base.shell.headerContents = contents;\n }\n\n // Sync toolbar contents (from API - config contents are already in base.shell.header.toolbarContents)\n // We need to merge config contents (from gridConfig) with API contents (from registerToolbarContent)\n // API contents can be added/removed dynamically, so we need to rebuild from current state each time\n const toolbarContentsMap = this.#grid._shellState.toolbarContents;\n const apiContents = Array.from(toolbarContentsMap.values());\n\n // Get ORIGINAL config contents (from gridConfig, not from previous merges)\n // We use a fresh read from gridConfig to avoid accumulating stale API contents\n const originalConfigContents = this.#gridConfig?.shell?.header?.toolbarContents ?? [];\n\n // Merge: config contents + API contents (config takes precedence by id)\n const configIds = new Set(originalConfigContents.map((c) => c.id));\n const mergedContents = [...originalConfigContents];\n for (const content of apiContents) {\n if (!configIds.has(content.id)) {\n mergedContents.push(content);\n }\n }\n\n // Sort by order\n mergedContents.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n base.shell.header.toolbarContents = mergedContents;\n }\n // #endregion\n\n // #region State Persistence\n /**\n * Collect current column state by diffing original vs effective.\n * Returns only the changes from the original configuration.\n */\n collectState(plugins: BaseGridPlugin[]): GridColumnState {\n const columns = this.columns;\n const sortStates = this.#getSortState();\n\n return {\n columns: columns.map((col, index) => {\n const state: ColumnState = {\n field: col.field,\n order: index,\n visible: !col.hidden,\n };\n\n // Include width if set\n const internalCol = col as ColumnInternal<T>;\n if (internalCol.__renderedWidth !== undefined) {\n state.width = internalCol.__renderedWidth;\n } else if (col.width !== undefined) {\n state.width = typeof col.width === 'string' ? parseFloat(col.width) : col.width;\n }\n\n // Include sort state\n const sortState = sortStates.get(col.field);\n if (sortState) {\n state.sort = sortState;\n }\n\n // Collect from plugins\n for (const plugin of plugins) {\n if (plugin.getColumnState) {\n const pluginState = plugin.getColumnState(col.field);\n if (pluginState) {\n Object.assign(state, pluginState);\n }\n }\n }\n\n return state;\n }),\n };\n }\n\n /**\n * Apply column state to the grid.\n */\n applyState(state: GridColumnState, plugins: BaseGridPlugin[]): void {\n if (!state.columns || state.columns.length === 0) return;\n\n const allColumns = this.columns;\n const stateMap = new Map(state.columns.map((s) => [s.field, s]));\n\n // Apply width and visibility\n const updatedColumns = allColumns.map((col) => {\n const s = stateMap.get(col.field);\n if (!s) return col;\n\n const updated: ColumnInternal<T> = { ...col };\n\n if (s.width !== undefined) {\n updated.width = s.width;\n updated.__renderedWidth = s.width;\n }\n\n if (s.visible !== undefined) {\n updated.hidden = !s.visible;\n }\n\n return updated;\n });\n\n // Reorder columns\n updatedColumns.sort((a, b) => {\n const orderA = stateMap.get(a.field)?.order ?? Infinity;\n const orderB = stateMap.get(b.field)?.order ?? Infinity;\n return orderA - orderB;\n });\n\n this.columns = updatedColumns;\n\n // Apply sort state\n const sortedByPriority = state.columns\n .filter((s) => s.sort !== undefined)\n .sort((a, b) => (a.sort?.priority ?? 0) - (b.sort?.priority ?? 0));\n\n if (sortedByPriority.length > 0) {\n const primarySort = sortedByPriority[0];\n if (primarySort.sort) {\n this.#grid._sortState = {\n field: primarySort.field,\n direction: primarySort.sort.direction === 'asc' ? 1 : -1,\n };\n }\n } else {\n this.#grid._sortState = null;\n }\n\n // Let plugins apply their state\n for (const plugin of plugins) {\n if (plugin.applyColumnState) {\n for (const colState of state.columns) {\n plugin.applyColumnState(colState.field, colState);\n }\n }\n }\n }\n\n /**\n * Reset state to original configuration.\n *\n * Two-layer architecture: Clones #originalConfig back to #effectiveConfig.\n * This discards all runtime changes (hidden, width, order) and restores\n * the state to what was compiled from sources.\n */\n resetState(plugins: BaseGridPlugin[]): void {\n // Clear initial state\n this.#initialColumnState = undefined;\n\n // Reset sort state\n this.#grid._sortState = null;\n\n // Clone original config back to effective (discards all runtime changes)\n this.#effectiveConfig = this.#cloneConfig(this.#originalConfig);\n\n // Apply post-merge operations (rowHeight, fixed mode widths, animation)\n this.#applyPostMergeOperations();\n\n // Notify plugins to reset\n for (const plugin of plugins) {\n if (plugin.applyColumnState) {\n for (const col of this.columns) {\n plugin.applyColumnState(col.field, {\n field: col.field,\n order: 0,\n visible: true,\n });\n }\n }\n }\n\n // Request state change notification\n this.requestStateChange(plugins);\n }\n\n /**\n * Get sort state as a map.\n */\n #getSortState(): Map<string, ColumnSortState> {\n const sortMap = new Map<string, ColumnSortState>();\n const sortState = this.#grid._sortState;\n\n if (sortState) {\n sortMap.set(sortState.field, {\n direction: sortState.direction === 1 ? 'asc' : 'desc',\n priority: 0,\n });\n }\n\n return sortMap;\n }\n\n /**\n * Request a debounced state change event.\n */\n requestStateChange(plugins: BaseGridPlugin[]): void {\n if (this.#stateChangeTimeoutId) {\n clearTimeout(this.#stateChangeTimeoutId);\n }\n\n this.#stateChangeTimeoutId = setTimeout(() => {\n this.#stateChangeTimeoutId = undefined;\n const state = this.collectState(plugins);\n this.#grid._emit('column-state-change', state);\n }, STATE_CHANGE_DEBOUNCE_MS);\n }\n // #endregion\n\n // #region Column Visibility API\n /**\n * Set the visibility of a column.\n * @returns true if visibility changed, false otherwise\n */\n setColumnVisible(field: string, visible: boolean): boolean {\n const allCols = this.columns;\n const col = allCols.find((c) => c.field === field);\n\n if (!col) return false;\n if (!visible && col.lockVisible) return false;\n\n // Ensure at least one column remains visible\n if (!visible) {\n const remainingVisible = allCols.filter((c) => !c.hidden && c.field !== field).length;\n if (remainingVisible === 0) return false;\n }\n\n const wasHidden = !!col.hidden;\n if (wasHidden === !visible) return false; // No change\n\n col.hidden = !visible;\n\n this.#grid._emit('column-visibility', {\n field,\n visible,\n visibleColumns: allCols.filter((c) => !c.hidden).map((c) => c.field),\n });\n\n this.#grid._clearRowPool();\n this.#grid._setup();\n\n return true;\n }\n\n /**\n * Toggle column visibility.\n */\n toggleColumnVisibility(field: string): boolean {\n const col = this.columns.find((c) => c.field === field);\n return col ? this.setColumnVisible(field, !!col.hidden) : false;\n }\n\n /**\n * Check if a column is visible.\n */\n isColumnVisible(field: string): boolean {\n const col = this.columns.find((c) => c.field === field);\n return col ? !col.hidden : false;\n }\n\n /**\n * Show all columns.\n */\n showAllColumns(): void {\n const allCols = this.columns;\n if (!allCols.some((c) => c.hidden)) return;\n\n allCols.forEach((c) => (c.hidden = false));\n\n this.#grid._emit('column-visibility', {\n visibleColumns: allCols.map((c) => c.field),\n });\n\n this.#grid._clearRowPool();\n this.#grid._setup();\n }\n\n /**\n * Get all columns with visibility info.\n */\n getAllColumns(): Array<{\n field: string;\n header: string;\n visible: boolean;\n lockVisible?: boolean;\n utility?: boolean;\n }> {\n return this.columns.map((c) => ({\n field: c.field,\n header: c.header || c.field,\n visible: !c.hidden,\n lockVisible: c.lockVisible,\n utility: c.meta?.utility === true,\n }));\n }\n\n /**\n * Get current column order.\n */\n getColumnOrder(): string[] {\n return this.columns.map((c) => c.field);\n }\n\n /**\n * Set column order.\n */\n setColumnOrder(order: string[]): void {\n if (!order.length) return;\n\n const columnMap = new Map(this.columns.map((c) => [c.field as string, c]));\n const reordered: ColumnInternal<T>[] = [];\n\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 remaining columns not in order\n for (const col of columnMap.values()) {\n reordered.push(col);\n }\n\n this.columns = reordered;\n\n renderHeader(this.#grid);\n updateTemplate(this.#grid);\n this.#grid._requestSchedulerPhase(RenderPhase.VIRTUALIZATION, 'configManager');\n }\n // #endregion\n\n // #region Light DOM Observer\n /**\n * Parse light DOM columns from host element.\n */\n parseLightDomColumns(host: HTMLElement): void {\n if (!this.#lightDomColumnsCache) {\n this.#originalColumnNodes = Array.from(host.querySelectorAll('tbw-grid-column')) as HTMLElement[];\n this.#lightDomColumnsCache = this.#originalColumnNodes.length ? parseLightDomColumns(host) : [];\n }\n }\n\n /**\n * Clear the light DOM columns cache.\n */\n clearLightDomCache(): void {\n this.#lightDomColumnsCache = undefined;\n }\n\n /**\n * Registered Light DOM element handlers.\n * Maps element tag names to callbacks that are invoked when those elements change.\n *\n * This is a generic mechanism - plugins (or future ShellPlugin) register\n * what elements they care about and handle parsing themselves.\n */\n #lightDomHandlers: Map<string, () => void> = new Map();\n\n /**\n * Register a handler for Light DOM element changes.\n * When elements matching the tag name are added/removed/changed,\n * the callback will be invoked (debounced).\n *\n * @param tagName - The lowercase tag name to watch (e.g., 'tbw-grid-header')\n * @param callback - Called when matching elements change\n */\n registerLightDomHandler(tagName: string, callback: () => void): void {\n this.#lightDomHandlers.set(tagName.toLowerCase(), callback);\n }\n\n /**\n * Unregister a Light DOM element handler.\n */\n unregisterLightDomHandler(tagName: string): void {\n this.#lightDomHandlers.delete(tagName.toLowerCase());\n }\n\n /**\n * Set up MutationObserver to watch for Light DOM changes.\n * This is generic infrastructure - specific handling is done via registered handlers.\n *\n * When Light DOM elements are added/removed/changed, the observer:\n * 1. Identifies which registered tag names were affected\n * 2. Debounces multiple mutations into a single callback per handler\n * 3. Invokes the registered callbacks\n *\n * This mechanism allows plugins to register their own Light DOM elements\n * and handle parsing themselves, then hand config to ConfigManager.\n *\n * @param host - The host element to observe (the grid element)\n */\n observeLightDOM(host: HTMLElement): void {\n // Clean up any existing observer\n if (this.#lightDomObserver) {\n this.#lightDomObserver.disconnect();\n }\n\n // Track which handlers need to be called (debounced)\n const pendingCallbacks = new Set<string>();\n\n const processPendingCallbacks = () => {\n this.#lightDomDebounceTimer = undefined;\n for (const tagName of pendingCallbacks) {\n const handler = this.#lightDomHandlers.get(tagName);\n handler?.();\n }\n pendingCallbacks.clear();\n };\n\n this.#lightDomObserver = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n // Check added nodes\n for (const node of mutation.addedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n const el = node as Element;\n const tagName = el.tagName.toLowerCase();\n\n // Check if any handler is interested in this element\n if (this.#lightDomHandlers.has(tagName)) {\n pendingCallbacks.add(tagName);\n }\n }\n\n // Check for attribute changes\n if (mutation.type === 'attributes' && mutation.target.nodeType === Node.ELEMENT_NODE) {\n const el = mutation.target as Element;\n const tagName = el.tagName.toLowerCase();\n if (this.#lightDomHandlers.has(tagName)) {\n pendingCallbacks.add(tagName);\n }\n }\n }\n\n // Debounce - batch all mutations into single callbacks\n if (pendingCallbacks.size > 0 && !this.#lightDomDebounceTimer) {\n this.#lightDomDebounceTimer = setTimeout(processPendingCallbacks, 0);\n }\n });\n\n // Observe children and their attributes\n this.#lightDomObserver.observe(host, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['title', 'field', 'header', 'width', 'hidden', 'id', 'icon', 'tooltip', 'order'],\n });\n }\n // #endregion\n\n // #region Change Notification\n /**\n * Register a change listener.\n */\n onChange(callback: () => void): void {\n this.#changeListeners.push(callback);\n }\n\n /**\n * Notify all change listeners.\n */\n notifyChange(): void {\n for (const cb of this.#changeListeners) {\n cb();\n }\n }\n // #endregion\n\n // #region Cleanup\n /**\n * Dispose of the ConfigManager and clean up resources.\n */\n dispose(): void {\n this.#lightDomObserver?.disconnect();\n this.#changeListeners = [];\n if (this.#stateChangeTimeoutId) {\n clearTimeout(this.#stateChangeTimeoutId);\n }\n if (this.#lightDomDebounceTimer) {\n clearTimeout(this.#lightDomDebounceTimer);\n this.#lightDomDebounceTimer = undefined;\n }\n }\n // #endregion\n}\n","// #region Environment Helpers\n\n/**\n * Check if we're running in a development environment.\n * Returns true for localhost or when NODE_ENV !== 'production'.\n * Used to show warnings only in development.\n */\nexport function isDevelopment(): boolean {\n // Check for localhost (browser environment)\n if (typeof window !== 'undefined' && window.location) {\n const hostname = window.location.hostname;\n if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') {\n return true;\n }\n }\n // Check for NODE_ENV (build-time or SSR)\n if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {\n return true;\n }\n return false;\n}\n\n// #endregion\n\n// #region Cell Rendering Helpers\n\n/**\n * Generate accessible HTML for a boolean cell.\n * Uses role=\"checkbox\" with proper aria attributes.\n */\nexport function booleanCellHTML(value: boolean): string {\n return `<span role=\"checkbox\" aria-checked=\"${value}\" aria-label=\"${value}\">${value ? '🗹' : '☐'}</span>`;\n}\n\n/**\n * Format a date value for display.\n * Handles Date objects, timestamps, and date strings.\n * Returns empty string for invalid dates.\n */\nexport function formatDateValue(value: unknown): string {\n if (value == null || value === '') return '';\n if (value instanceof Date) {\n return isNaN(value.getTime()) ? '' : value.toLocaleDateString();\n }\n if (typeof value === 'number' || typeof value === 'string') {\n const d = new Date(value);\n return isNaN(d.getTime()) ? '' : d.toLocaleDateString();\n }\n return '';\n}\n\n/**\n * Get the row index from a cell element's data-row attribute.\n * Falls back to calculating from parent row's DOM position if data-row is missing.\n * Returns -1 if no valid row index is found.\n */\nexport function getRowIndexFromCell(cell: Element | null): number {\n if (!cell) return -1;\n const attr = cell.getAttribute('data-row');\n if (attr) return parseInt(attr, 10);\n\n // Fallback: find the parent .data-grid-row and calculate index from siblings\n const rowEl = cell.closest('.data-grid-row');\n if (!rowEl) return -1;\n\n const parent = rowEl.parentElement;\n if (!parent) return -1;\n\n // Get all data-grid-row siblings and find this row's index\n const rows = parent.querySelectorAll(':scope > .data-grid-row');\n for (let i = 0; i < rows.length; i++) {\n if (rows[i] === rowEl) return i;\n }\n return -1;\n}\n\n/**\n * Get the column index from a cell element's data-col attribute.\n * Returns -1 if no valid column index is found.\n */\nexport function getColIndexFromCell(cell: Element | null): number {\n if (!cell) return -1;\n const attr = cell.getAttribute('data-col');\n return attr ? parseInt(attr, 10) : -1;\n}\n\n/**\n * Clear all cell-focus styling from a root element.\n * Used when changing focus or when selection plugin takes over focus management.\n */\nexport function clearCellFocus(root: Element | null): void {\n if (!root) return;\n root.querySelectorAll('.cell-focus').forEach((el) => el.classList.remove('cell-focus'));\n}\n// #endregion\n\n// #region RTL Helpers\n\n/** Text direction */\nexport type TextDirection = 'ltr' | 'rtl';\n\n/**\n * Get the text direction for an element.\n * Reads from the computed style, which respects the `dir` attribute on the element\n * or any ancestor, as well as CSS `direction` property.\n *\n * @param element - The element to check direction for\n * @returns 'ltr' or 'rtl'\n *\n * @example\n * ```typescript\n * // Detect grid's direction\n * const dir = getDirection(gridElement);\n * if (dir === 'rtl') {\n * // Handle RTL layout\n * }\n * ```\n */\nexport function getDirection(element: Element): TextDirection {\n // Try computed style first (works in real browsers)\n try {\n const computedDir = getComputedStyle(element).direction;\n if (computedDir === 'rtl') return 'rtl';\n } catch {\n // getComputedStyle may fail in some test environments\n }\n\n // Fallback: check dir attribute on element or ancestors\n // This handles test environments where getComputedStyle may not reflect dir attribute\n try {\n const dirAttr = element.closest?.('[dir]')?.getAttribute('dir');\n if (dirAttr === 'rtl') return 'rtl';\n } catch {\n // closest may not be available on mock elements\n }\n\n return 'ltr';\n}\n\n/**\n * Check if an element is in RTL mode.\n *\n * @param element - The element to check\n * @returns true if the element's text direction is right-to-left\n */\nexport function isRTL(element: Element): boolean {\n return getDirection(element) === 'rtl';\n}\n\n/**\n * Resolve a logical inline position to a physical position based on text direction.\n *\n * - `'start'` → `'left'` in LTR, `'right'` in RTL\n * - `'end'` → `'right'` in LTR, `'left'` in RTL\n * - `'left'` / `'right'` → unchanged (physical values)\n *\n * @param position - Logical or physical position\n * @param direction - Text direction ('ltr' or 'rtl')\n * @returns Physical position ('left' or 'right')\n *\n * @example\n * ```typescript\n * resolveInlinePosition('start', 'ltr'); // 'left'\n * resolveInlinePosition('start', 'rtl'); // 'right'\n * resolveInlinePosition('left', 'rtl'); // 'left' (unchanged)\n * ```\n */\nexport function resolveInlinePosition(\n position: 'left' | 'right' | 'start' | 'end',\n direction: TextDirection,\n): 'left' | 'right' {\n if (position === 'left' || position === 'right') {\n return position;\n }\n if (direction === 'rtl') {\n return position === 'start' ? 'right' : 'left';\n }\n return position === 'start' ? 'left' : 'right';\n}\n// #endregion\n","import type { ColumnInternal, ColumnViewRenderer, GridHost, InternalGrid, RowElementInternal } from '../types';\nimport {\n CELL_CLASS_ERROR,\n FORMAT_ERROR,\n ROW_CLASS_ERROR,\n VIEW_DISPATCH_ERROR,\n VIEW_MOUNT_ERROR,\n warnDiagnostic,\n} from './diagnostics';\nimport { ensureCellVisible } from './keyboard';\nimport { evalTemplateString, finalCellScrub, sanitizeHTML } from './sanitize';\nimport { booleanCellHTML, clearCellFocus, formatDateValue, getRowIndexFromCell } from './utils';\n\n/** Callback type for plugin row rendering hook */\nexport type RenderRowHook = (row: any, rowEl: HTMLElement, rowIndex: number) => boolean;\n\n// #region Type Defaults Resolution\n/**\n * Resolves the renderer for a column using the priority chain:\n * 1. Column-level (`column.renderer` / `column.viewRenderer`)\n * NOTE: typeDefaults are applied to columns at config merge time,\n * so columns with matching types already have their renderer set.\n * 2. App-level (framework adapter's `getTypeDefault`)\n * 3. Returns undefined (caller uses built-in or fallback)\n */\nexport function resolveRenderer<TRow>(\n grid: InternalGrid<TRow>,\n col: ColumnInternal<TRow>,\n): ColumnViewRenderer<TRow, unknown> | undefined {\n // 1. Column-level renderer (highest priority)\n // NOTE: typeDefaults from gridConfig are applied to columns at config merge time\n // by ConfigManager.#applyTypeDefaultsToColumns(), so they appear here as col.renderer\n const columnRenderer = col.renderer || col.viewRenderer;\n if (columnRenderer) return columnRenderer;\n\n // No type specified - no type defaults to check\n if (!col.type) return undefined;\n\n // 2. App-level registry (via framework adapter)\n // This is for framework adapters that register type defaults dynamically\n const adapter = grid.__frameworkAdapter;\n if (adapter?.getTypeDefault) {\n const appDefault = adapter.getTypeDefault<TRow>(col.type);\n if (appDefault?.renderer) {\n return appDefault.renderer;\n }\n }\n\n // 3. No custom renderer - caller uses built-in/fallback\n return undefined;\n}\n\n/**\n * Resolves the format function for a column using the priority chain:\n * 1. Column-level (`column.format`)\n * NOTE: typeDefaults are applied to columns at config merge time,\n * so columns with matching types already have their format set.\n * 2. App-level (framework adapter's `getTypeDefault`)\n * 3. Returns undefined (caller uses built-in or fallback)\n */\nexport function resolveFormat<TRow>(\n grid: InternalGrid<TRow>,\n col: ColumnInternal<TRow>,\n): ((value: unknown, row: TRow) => string) | undefined {\n // 1. Column-level format (highest priority)\n // NOTE: typeDefaults from gridConfig are applied to columns at config merge time\n // by ConfigManager.#applyTypeDefaultsToColumns(), so they appear here as col.format\n if (col.format) return col.format;\n\n // No type specified - no type defaults to check\n if (!col.type) return undefined;\n\n // 2. App-level registry (via framework adapter)\n // This is for framework adapters that register type defaults dynamically\n const adapter = grid.__frameworkAdapter;\n if (adapter?.getTypeDefault) {\n const appDefault = adapter.getTypeDefault<TRow>(col.type);\n if (appDefault?.format) {\n return appDefault.format as (value: unknown, row: TRow) => string;\n }\n }\n\n // 3. No custom format - caller uses built-in/fallback\n return undefined;\n}\n// #endregion\n\n// #region DOM State Helpers\n/**\n * CSS selector for focusable editor elements within a cell.\n * Used by EditingPlugin and keyboard navigation.\n */\nexport const FOCUSABLE_EDITOR_SELECTOR =\n 'input,select,textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Check if a row element has any cells in editing mode.\n * This is a DOM-level check used for virtualization recycling.\n */\nfunction hasEditingCells(rowEl: RowElementInternal): boolean {\n return (rowEl.__editingCellCount ?? 0) > 0;\n}\n\n/**\n * Clear all editing state from a row element.\n * Called when a row element is recycled for a different data row.\n */\nfunction clearEditingState(rowEl: RowElementInternal): void {\n rowEl.__editingCellCount = 0;\n rowEl.removeAttribute('data-has-editing');\n // Clear editing class from all cells\n const cells = rowEl.querySelectorAll('.cell.editing');\n cells.forEach((cell) => cell.classList.remove('editing'));\n}\n// #endregion\n\n// #region Template Cloning System\n// Using template cloning is 3-4x faster than document.createElement + setAttribute\n// for repetitive element creation because the browser can skip parsing.\n\n/**\n * Cell template for cloning. Pre-configured with static attributes.\n * Dynamic attributes (data-col, data-row, etc.) are set after cloning.\n */\nconst cellTemplate = document.createElement('template');\ncellTemplate.innerHTML = '<div class=\"cell\" role=\"gridcell\" part=\"cell\"></div>';\n\n/**\n * Row template for cloning. Pre-configured with static attributes.\n * Dynamic attributes (data-row) and children (cells) are set after cloning.\n */\nconst rowTemplate = document.createElement('template');\nrowTemplate.innerHTML = '<div class=\"data-grid-row\" role=\"row\" part=\"row\"></div>';\n\n/**\n * Create a cell element from template. Significantly faster than createElement + setAttribute.\n */\nfunction createCellFromTemplate(): HTMLDivElement {\n return cellTemplate.content.firstElementChild!.cloneNode(true) as HTMLDivElement;\n}\n\n/**\n * Create a row element from template. Significantly faster than createElement + setAttribute.\n */\nexport function createRowFromTemplate(): HTMLDivElement {\n return rowTemplate.content.firstElementChild!.cloneNode(true) as HTMLDivElement;\n}\n// #endregion\n\n// #region Row Rendering\n/**\n * Invalidate the cell cache (call when rows or columns change).\n */\nexport function invalidateCellCache(grid: InternalGrid): void {\n grid.__cellDisplayCache = undefined;\n grid.__cellCacheEpoch = undefined;\n grid.__hasSpecialColumns = undefined; // Reset fast-path check\n}\n\n/**\n * Render / patch the visible window of rows [start, end) using a recyclable DOM pool.\n * Newly required row elements are created and appended; excess are detached.\n * Uses an epoch counter to force full row rebuilds when structural changes (like columns) occur.\n * @param renderRowHook - Optional callback that plugins can use to render custom rows (e.g., group rows).\n * If it returns true, default rendering is skipped for that row.\n */\nexport function renderVisibleRows(\n grid: GridHost,\n start: number,\n end: number,\n epoch?: number,\n renderRowHook?: RenderRowHook,\n): void {\n const needed = Math.max(0, end - start);\n const bodyEl = grid._bodyEl;\n const columns = grid._visibleColumns;\n const colLen = columns.length;\n\n // Cache header row count once (check for group header row existence)\n let headerRowCount = grid.__cachedHeaderRowCount;\n if (headerRowCount === undefined) {\n headerRowCount = grid.querySelector('.header-group-row') ? 2 : 1;\n grid.__cachedHeaderRowCount = headerRowCount;\n }\n\n // Pool management: grow pool if needed\n // Note: click/dblclick handlers are delegated at grid level for efficiency\n while (grid._rowPool.length < needed) {\n // Use template cloning - 3-4x faster than createElement + setAttribute\n const rowEl = createRowFromTemplate();\n grid._rowPool.push(rowEl);\n }\n\n // Remove excess pool elements from DOM and shrink pool\n if (grid._rowPool.length > needed) {\n for (let i = needed; i < grid._rowPool.length; i++) {\n const el = grid._rowPool[i];\n if (el.parentNode === bodyEl) el.remove();\n }\n grid._rowPool.length = needed;\n }\n\n // Check if any plugin has a renderRow hook (cache this)\n const hasRenderRowPlugins = renderRowHook && grid.__hasRenderRowPlugins !== false;\n\n // Check if any plugin wants row-level hooks (avoid overhead when not needed)\n const hasRowHook = grid._hasAfterRowRenderHook?.() ?? false;\n\n // Cache variable-height function for per-row CSS variable override\n const varHeightFn =\n grid._virtualization?.variableHeights && typeof grid.effectiveConfig?.rowHeight === 'function'\n ? (grid.effectiveConfig.rowHeight as (row: unknown, index: number) => number | undefined)\n : null;\n\n for (let i = 0; i < needed; i++) {\n const rowIndex = start + i;\n const rowData = grid._rows[rowIndex];\n const rowEl = grid._rowPool[i] as RowElementInternal;\n\n // Always set aria-rowindex (1-based, accounting for header rows)\n rowEl.setAttribute('aria-rowindex', String(rowIndex + headerRowCount + 1));\n\n // Let plugins handle custom row rendering (e.g., group rows)\n if (hasRenderRowPlugins && renderRowHook!(rowData, rowEl, rowIndex)) {\n rowEl.__epoch = epoch;\n rowEl.__rowDataRef = rowData;\n if (rowEl.parentNode !== bodyEl) bodyEl.appendChild(rowEl);\n continue;\n }\n\n const rowEpoch = rowEl.__epoch;\n const prevRef = rowEl.__rowDataRef;\n let cellCount = rowEl.children.length;\n\n // Loading overlay is a non-cell child appended at the end — exclude from cell count\n // to avoid false structure-invalid detection that causes unnecessary full rebuilds.\n if (cellCount > colLen && rowEl.lastElementChild?.classList.contains('tbw-row-loading-overlay')) {\n cellCount--;\n }\n\n // Check if we need a full rebuild vs fast update\n const epochMatch = rowEpoch === epoch;\n const structureValid = epochMatch && cellCount === colLen;\n const dataRefChanged = prevRef !== rowData;\n // In grid edit mode, all rows have editing cells that must be preserved\n const isGridEditMode = !!grid._isGridEditMode;\n\n // Need external view rebuild check when structure is valid but data changed\n let needsExternalRebuild = false;\n if (structureValid && dataRefChanged) {\n for (let c = 0; c < colLen; c++) {\n const col = columns[c];\n if (col.externalView) {\n const cellCheck = rowEl.querySelector(`.cell[data-col=\"${c}\"] [data-external-view]`);\n if (!cellCheck) {\n needsExternalRebuild = true;\n break;\n }\n }\n }\n }\n\n if (!structureValid || needsExternalRebuild) {\n // Full rebuild needed - epoch changed, cell count mismatch, or external view missing\n // Use cached editing state for O(1) check instead of querySelector\n const hasEditing = hasEditingCells(rowEl);\n // In grid edit mode, treat recycled rows (different data ref) as needing a rebuild\n // so afterCellRender can re-evaluate per-cell editability for the new row data.\n const isActivelyEditedRow = (isGridEditMode && !dataRefChanged) || grid._activeEditRows === rowIndex;\n\n // If DOM element has editors but this is NOT the actively edited row, clear them\n // (This happens when virtualization recycles the DOM element for a different row)\n if (hasEditing && !isActivelyEditedRow) {\n // Force full rebuild to clear stale editors\n if (rowEl.__isCustomRow) {\n rowEl.className = 'data-grid-row';\n rowEl.setAttribute('role', 'row');\n rowEl.__isCustomRow = false;\n }\n clearEditingState(rowEl); // Clear editing state before rebuild\n renderInlineRow(grid, rowEl, rowData, rowIndex);\n rowEl.__epoch = epoch;\n rowEl.__rowDataRef = rowData;\n } else if (hasEditing && isActivelyEditedRow) {\n // Row is in editing mode AND this is the correct row - preserve editors\n fastPatchRow(grid, rowEl, rowData, rowIndex);\n rowEl.__rowDataRef = rowData;\n } else {\n if (rowEl.__isCustomRow) {\n rowEl.className = 'data-grid-row';\n rowEl.setAttribute('role', 'row');\n rowEl.__isCustomRow = false;\n }\n renderInlineRow(grid, rowEl, rowData, rowIndex);\n rowEl.__epoch = epoch;\n rowEl.__rowDataRef = rowData;\n // NOTE: If this is the actively edited row, EditingPlugin's onScrollRender() will inject editors\n }\n } else if (dataRefChanged) {\n // Same structure, different row data - fast update\n // Use cached editing state for O(1) check instead of querySelector\n const hasEditing = hasEditingCells(rowEl);\n // In grid edit mode with changed data ref, clear editors and rebuild\n // so afterCellRender can re-evaluate per-cell editability for the new row.\n const isActivelyEditedRow = grid._activeEditRows === rowIndex;\n\n // If DOM element has editors but this is NOT the actively edited row, clear them\n if (hasEditing && !isActivelyEditedRow) {\n clearEditingState(rowEl); // Clear editing state before rebuild\n renderInlineRow(grid, rowEl, rowData, rowIndex);\n rowEl.__epoch = epoch;\n rowEl.__rowDataRef = rowData;\n } else {\n fastPatchRow(grid, rowEl, rowData, rowIndex);\n rowEl.__rowDataRef = rowData;\n // NOTE: If this is the actively edited row, EditingPlugin's onScrollRender() will inject editors\n }\n } else {\n // Same row data reference - just patch if any values changed\n // Use cached editing state for O(1) check instead of querySelector\n const hasEditing = hasEditingCells(rowEl);\n // Same data ref means no recycling — safe to preserve editors in grid mode.\n const isActivelyEditedRow = isGridEditMode || grid._activeEditRows === rowIndex;\n\n // If DOM element has editors but this is NOT the actively edited row, clear them\n if (hasEditing && !isActivelyEditedRow) {\n clearEditingState(rowEl); // Clear editing state before rebuild\n renderInlineRow(grid, rowEl, rowData, rowIndex);\n rowEl.__epoch = epoch;\n rowEl.__rowDataRef = rowData;\n } else {\n fastPatchRow(grid, rowEl, rowData, rowIndex);\n // NOTE: If this is the actively edited row, EditingPlugin's onScrollRender() will inject editors\n }\n }\n\n // Changed class toggle - check if row ID is in changedRowIds Set (EditingPlugin)\n let isChanged = false;\n const changedRowIdSet = grid._changedRowIdSet;\n if (changedRowIdSet && changedRowIdSet.size > 0) {\n try {\n const rowId = grid.getRowId?.(rowData);\n if (rowId) {\n isChanged = changedRowIdSet.has(rowId);\n }\n } catch {\n // Row has no ID - not tracked as changed\n }\n }\n const hasChangedClass = rowEl.classList.contains('changed');\n if (isChanged !== hasChangedClass) {\n rowEl.classList.toggle('changed', isChanged);\n }\n\n // Apply rowClass callback if configured\n const rowClassFn = grid.effectiveConfig?.rowClass;\n if (rowClassFn) {\n // Remove previous dynamic classes (stored in data attribute)\n const prevClasses = rowEl.getAttribute('data-dynamic-classes');\n if (prevClasses) {\n prevClasses.split(' ').forEach((cls) => cls && rowEl.classList.remove(cls));\n }\n try {\n const result = rowClassFn(rowData);\n const newClasses = typeof result === 'string' ? result.split(/\\s+/) : result;\n if (newClasses && newClasses.length > 0) {\n let dynamicClassStr = '';\n for (const c of newClasses) {\n if (c && typeof c === 'string') {\n rowEl.classList.add(c);\n dynamicClassStr += (dynamicClassStr ? ' ' : '') + c;\n }\n }\n rowEl.setAttribute('data-dynamic-classes', dynamicClassStr);\n } else {\n rowEl.removeAttribute('data-dynamic-classes');\n }\n } catch (e) {\n warnDiagnostic(ROW_CLASS_ERROR, `rowClass callback error: ${e}`, grid.id);\n rowEl.removeAttribute('data-dynamic-classes');\n }\n }\n\n // Apply per-row variable height via --tbw-row-height CSS custom property.\n // Cells bind to this variable (min-height: var(--tbw-row-height)), so setting\n // it on the row element makes both the row and its cells respect the override.\n // The #measureRowHeight guard in grid.ts prevents this from corrupting s.rowHeight.\n if (varHeightFn) {\n const h = varHeightFn(rowData, rowIndex);\n if (h !== undefined && h > 0) {\n rowEl.style.setProperty('--tbw-row-height', `${h}px`);\n } else {\n rowEl.style.removeProperty('--tbw-row-height');\n }\n }\n\n // Call row-level plugin hook if any plugin registered it\n if (hasRowHook) {\n grid._afterRowRender?.({\n row: rowData,\n rowIndex,\n rowElement: rowEl,\n });\n }\n\n if (rowEl.parentNode !== bodyEl) bodyEl.appendChild(rowEl);\n }\n}\n// #endregion\n\n// #region Row Patching\n/**\n * Fast patch path for an already-rendered row: updates plain text cells whose data changed\n * while skipping cells with external views, templates, or active editors.\n *\n * Optimized for scroll performance - avoids querySelectorAll in favor of children access.\n */\nfunction fastPatchRow(grid: GridHost, rowEl: HTMLElement, rowData: any, rowIndex: number): void {\n const children = rowEl.children;\n const columns = grid._visibleColumns;\n const colsLen = columns.length;\n const childLen = children.length;\n const minLen = colsLen < childLen ? colsLen : childLen;\n const focusRow = grid._focusRow;\n const focusCol = grid._focusCol;\n\n // Check if any plugin wants cell-level hooks (avoid overhead when not needed)\n const hasCellHook = grid._hasAfterCellRenderHook?.() ?? false;\n\n // Ultra-fast path: if no special columns (templates, formatters, etc.), use direct assignment\n // Check is cached on grid to avoid repeated iteration\n let hasSpecialCols = grid.__hasSpecialColumns;\n if (hasSpecialCols === undefined) {\n hasSpecialCols = false;\n // NOTE: typeDefaults are now applied to columns at config merge time\n // by ConfigManager.#applyTypeDefaultsToColumns(), so columns already have\n // their renderer/format set if a typeDefault matched. No runtime lookup needed.\n const adapter = grid.__frameworkAdapter;\n for (let i = 0; i < colsLen; i++) {\n const col = columns[i];\n if (\n col.__viewTemplate ||\n col.__compiledView ||\n col.renderer ||\n col.viewRenderer ||\n col.externalView ||\n col.format ||\n col.cellClass ||\n col.type === 'date' ||\n col.type === 'boolean' ||\n // Check for adapter-level type defaults (framework adapters)\n (col.type && adapter?.getTypeDefault?.(col.type)?.renderer) ||\n (col.type && adapter?.getTypeDefault?.(col.type)?.format)\n ) {\n hasSpecialCols = true;\n break;\n }\n }\n grid.__hasSpecialColumns = hasSpecialCols;\n }\n\n const rowIndexStr = String(rowIndex);\n\n // Ultra-fast path for plain text grids - just set textContent directly\n if (!hasSpecialCols) {\n for (let i = 0; i < minLen; i++) {\n const cell = children[i] as HTMLElement;\n\n // Skip cells in edit mode - they have editors that must be preserved\n if (cell.classList.contains('editing')) continue;\n\n // Release editor views if cell has element children (indicating prior editor/renderer DOM).\n // Plain text cells (textContent-only) have no element children, so this is a fast O(1) skip.\n if (cell.firstElementChild) grid.__frameworkAdapter?.releaseCell?.(cell);\n\n const col = columns[i];\n const value = rowData[col.field];\n cell.textContent = value == null ? '' : String(value);\n // Update data-row for click handling\n if (cell.getAttribute('data-row') !== rowIndexStr) {\n cell.setAttribute('data-row', rowIndexStr);\n }\n // Update focus state - must be data-driven, not DOM-element-driven\n const shouldHaveFocus = focusRow === rowIndex && focusCol === i;\n const hasFocus = cell.classList.contains('cell-focus');\n if (shouldHaveFocus !== hasFocus) {\n cell.classList.toggle('cell-focus', shouldHaveFocus);\n // aria-selected only valid for gridcell, not checkbox (but ultra-fast path has no special cols)\n cell.setAttribute('aria-selected', String(shouldHaveFocus));\n }\n\n // Call cell-level plugin hook if any plugin registered it\n if (hasCellHook) {\n grid._afterCellRender?.({\n row: rowData,\n rowIndex,\n column: col,\n colIndex: i,\n value,\n cellElement: cell,\n rowElement: rowEl,\n });\n }\n }\n return;\n }\n\n // Check if any external view placeholder is missing - if so, do full rebuild\n for (let i = 0; i < minLen; i++) {\n const col = columns[i];\n if (col.externalView) {\n const cell = children[i] as HTMLElement;\n if (!cell.querySelector('[data-external-view]')) {\n renderInlineRow(grid, rowEl, rowData, rowIndex);\n return;\n }\n }\n }\n\n // Standard path for grids with special columns\n for (let i = 0; i < minLen; i++) {\n const col = columns[i];\n const cell = children[i] as HTMLElement;\n\n // Update data-row for click handling\n if (cell.getAttribute('data-row') !== rowIndexStr) {\n cell.setAttribute('data-row', rowIndexStr);\n }\n\n // Check editing state once — reused for focus guard and content skip below.\n const isEditing = cell.classList.contains('editing');\n\n // Update focus state - must be data-driven, not DOM-element-driven.\n // Skip editing cells — their focus state is managed by the navigation\n // system (ensureCellVisible), not the render pipeline. Toggling here\n // would fire MutationObservers (e.g., overlay editors) causing\n // premature overlay teardown during re-renders triggered by resize.\n if (!isEditing) {\n const shouldHaveFocus = focusRow === rowIndex && focusCol === i;\n const hasFocus = cell.classList.contains('cell-focus');\n if (shouldHaveFocus !== hasFocus) {\n cell.classList.toggle('cell-focus', shouldHaveFocus);\n cell.setAttribute('aria-selected', String(shouldHaveFocus));\n }\n }\n\n // Apply cellClass callback if configured\n const cellClassFn = col.cellClass;\n if (cellClassFn) {\n // Remove previous dynamic classes\n const prevClasses = cell.getAttribute('data-dynamic-classes');\n if (prevClasses) {\n prevClasses.split(' ').forEach((cls) => cls && cell.classList.remove(cls));\n }\n try {\n const value = rowData[col.field];\n const result = cellClassFn(value, rowData, col);\n const cellClasses = typeof result === 'string' ? result.split(/\\s+/) : result;\n if (cellClasses && cellClasses.length > 0) {\n const validClasses = cellClasses.filter((c: string) => c && typeof c === 'string');\n validClasses.forEach((cls: string) => cell.classList.add(cls));\n cell.setAttribute('data-dynamic-classes', validClasses.join(' '));\n } else {\n cell.removeAttribute('data-dynamic-classes');\n }\n } catch (e) {\n warnDiagnostic(CELL_CLASS_ERROR, `cellClass callback error for column '${col.field}': ${e}`, grid.id);\n cell.removeAttribute('data-dynamic-classes');\n }\n }\n\n // Skip cells in edit mode\n if (isEditing) continue;\n\n // Handle viewRenderer/renderer - must re-invoke to get updated content\n // Uses priority chain: column → typeDefaults → adapter → built-in\n const cellRenderer = resolveRenderer(grid, col);\n if (cellRenderer) {\n const renderedValue = rowData[col.field];\n // Pass cellEl for framework adapters that want to cache per-cell\n const produced = cellRenderer({\n row: rowData,\n value: renderedValue,\n field: col.field,\n column: col,\n cellEl: cell,\n });\n if (typeof produced === 'string') {\n // Release editor views before wiping cell content\n grid.__frameworkAdapter?.releaseCell?.(cell);\n cell.innerHTML = sanitizeHTML(produced);\n } else if (produced instanceof Node) {\n // Check if this container is already a child of the cell (reused by framework adapter)\n if (produced.parentElement !== cell) {\n // Release editor views before wiping cell content\n grid.__frameworkAdapter?.releaseCell?.(cell);\n cell.innerHTML = '';\n cell.appendChild(produced);\n }\n // If already a child, the framework adapter has re-rendered in place\n } else if (produced == null) {\n // Renderer returned null/undefined - show raw value\n grid.__frameworkAdapter?.releaseCell?.(cell);\n cell.textContent = renderedValue == null ? '' : String(renderedValue);\n }\n // If produced is truthy but not a string or Node, the framework handles it\n // Call cell-level plugin hook - cell was rendered\n if (hasCellHook) {\n grid._afterCellRender?.({\n row: rowData,\n rowIndex,\n column: col,\n colIndex: i,\n value: renderedValue,\n cellElement: cell,\n rowElement: rowEl,\n });\n }\n continue;\n }\n\n // Handle compiled view templates — re-evaluate with current row data\n if (col.__compiledView) {\n const value = rowData[col.field];\n const output = col.__compiledView({ row: rowData, value, field: col.field, column: col });\n const blocked = col.__compiledView.__blocked;\n if (blocked) {\n cell.textContent = '';\n } else {\n // Release any framework views before replacing innerHTML\n if (cell.firstElementChild) grid.__frameworkAdapter?.releaseCell?.(cell);\n cell.innerHTML = sanitizeHTML(output);\n finalCellScrub(cell);\n }\n if (hasCellHook) {\n grid._afterCellRender?.({\n row: rowData,\n rowIndex,\n column: col,\n colIndex: i,\n value,\n cellElement: cell,\n rowElement: rowEl,\n });\n }\n continue;\n }\n\n // Handle inline view templates — re-evaluate with current row data\n if (col.__viewTemplate) {\n const value = rowData[col.field];\n const rawTpl = col.__viewTemplate.innerHTML;\n if (/Reflect\\.|\\bProxy\\b|ownKeys\\(/.test(rawTpl)) {\n cell.textContent = '';\n } else {\n if (cell.firstElementChild) grid.__frameworkAdapter?.releaseCell?.(cell);\n cell.innerHTML = sanitizeHTML(evalTemplateString(rawTpl, { row: rowData, value }));\n finalCellScrub(cell);\n }\n if (hasCellHook) {\n grid._afterCellRender?.({\n row: rowData,\n rowIndex,\n column: col,\n colIndex: i,\n value,\n cellElement: cell,\n rowElement: rowEl,\n });\n }\n continue;\n }\n\n // Skip external view cells (mounted once, manages own state)\n if (col.externalView) {\n continue;\n }\n\n // Compute and set display value\n const value = rowData[col.field];\n let displayStr: string;\n\n // Resolve format using priority chain: column → typeDefaults → adapter\n const formatFn = resolveFormat(grid, col);\n if (formatFn) {\n try {\n const formatted = formatFn(value, rowData);\n displayStr = formatted == null ? '' : String(formatted);\n } catch (e) {\n // Log format errors as warnings (user configuration issue)\n warnDiagnostic(FORMAT_ERROR, `Format error in column '${col.field}': ${e}`, grid.id);\n displayStr = value == null ? '' : String(value);\n }\n cell.textContent = displayStr;\n } else if (col.type === 'date') {\n displayStr = formatDateValue(value);\n cell.textContent = displayStr;\n } else if (col.type === 'boolean') {\n // Boolean cells have inner span with checkbox role for ARIA compliance\n cell.innerHTML = booleanCellHTML(!!value);\n } else {\n displayStr = value == null ? '' : String(value);\n cell.textContent = displayStr;\n }\n\n // Call cell-level plugin hook - cell was rendered\n if (hasCellHook) {\n grid._afterCellRender?.({\n row: rowData,\n rowIndex,\n column: col,\n colIndex: i,\n value,\n cellElement: cell,\n rowElement: rowEl,\n });\n }\n }\n}\n// #endregion\n\n// #region Cell Rendering\n/**\n * Full reconstruction of a row's set of cells including templated, external view, and formatted content.\n * Attaches event handlers for editing and accessibility per cell.\n */\nexport function renderInlineRow(grid: GridHost, rowEl: HTMLElement, rowData: any, rowIndex: number): void {\n // Clear loading state before rebuild — grid will re-apply after render for actually-loading rows.\n // This prevents stale tbw-row-loading class from persisting when pool elements are recycled.\n rowEl.classList.remove('tbw-row-loading');\n rowEl.removeAttribute('aria-busy');\n\n // Release framework editor views before wiping DOM to prevent memory leaks.\n // Without this, Angular EmbeddedViewRefs / React roots / Vue apps created by\n // editor factories would remain alive in the adapter's tracking arrays even\n // after their DOM is destroyed, leaking memory on every edit cycle.\n const adapter = grid.__frameworkAdapter;\n if (adapter?.releaseCell) {\n const children = rowEl.children;\n for (let i = children.length - 1; i >= 0; i--) {\n adapter.releaseCell(children[i] as HTMLElement);\n }\n }\n\n rowEl.innerHTML = '';\n\n // Pre-cache values used in the loop\n const columns = grid._visibleColumns;\n const colsLen = columns.length;\n const focusRow = grid._focusRow;\n const focusCol = grid._focusCol;\n\n // Check if any plugin wants cell-level hooks (avoid overhead when not needed)\n const hasCellHook = grid._hasAfterCellRenderHook?.() ?? false;\n\n // Use DocumentFragment for batch DOM insertion\n const fragment = document.createDocumentFragment();\n\n for (let colIndex = 0; colIndex < colsLen; colIndex++) {\n const col = columns[colIndex];\n // Use template cloning - 3-4x faster than createElement + setAttribute\n const cell = createCellFromTemplate();\n\n // Only set dynamic attributes (role, class, part are already set in template)\n // aria-colindex is 1-based\n cell.setAttribute('aria-colindex', String(colIndex + 1));\n cell.setAttribute('data-col', String(colIndex));\n cell.setAttribute('data-row', String(rowIndex));\n cell.setAttribute('data-field', col.field); // Field name for column identification\n cell.setAttribute('data-header', col.header ?? col.field); // Header text for responsive CSS\n if (col.type) cell.setAttribute('data-type', col.type);\n\n let value = (rowData as Record<string, unknown>)[col.field];\n // Resolve format using priority chain: column → typeDefaults → adapter\n const formatFn = resolveFormat(grid, col);\n if (formatFn) {\n try {\n value = formatFn(value, rowData);\n } catch (e) {\n // Log format errors as warnings (user configuration issue)\n warnDiagnostic(FORMAT_ERROR, `Format error in column '${col.field}': ${e}`, grid.id);\n }\n }\n\n const compiled = col.__compiledView;\n const tplHolder = col.__viewTemplate;\n // Resolve renderer using priority chain: column → typeDefaults → adapter → built-in\n const viewRenderer = resolveRenderer(grid, col);\n const externalView = col.externalView;\n\n // Track if we used a template that needs sanitization\n let needsSanitization = false;\n\n if (viewRenderer) {\n // Pass cellEl for framework adapters that want to cache per-cell\n const produced = viewRenderer({ row: rowData, value, field: col.field, column: col, cellEl: cell });\n if (typeof produced === 'string') {\n // Sanitize HTML from viewRenderer to prevent XSS from user-controlled data\n cell.innerHTML = sanitizeHTML(produced);\n needsSanitization = true;\n } else if (produced instanceof Node) {\n // Check if this container is already a child of the cell (reused by framework adapter)\n if (produced.parentElement !== cell) {\n // Clear any existing content before appending new container\n cell.textContent = '';\n cell.appendChild(produced);\n }\n // If already a child, the framework adapter has re-rendered in place\n } else if (produced == null) {\n // Renderer returned null/undefined - show raw value\n cell.textContent = value == null ? '' : String(value);\n }\n // If produced is truthy but not a string or Node (e.g., framework placeholder),\n // don't modify the cell - the framework adapter handles rendering\n } else if (externalView) {\n const spec = externalView;\n const placeholder = document.createElement('div');\n placeholder.setAttribute('data-external-view', '');\n placeholder.setAttribute('data-field', col.field);\n cell.appendChild(placeholder);\n const context = { row: rowData, value, field: col.field, column: col };\n if (spec.mount) {\n try {\n spec.mount({ placeholder, context, spec });\n } catch (e) {\n // Log mount errors as warnings (user configuration issue)\n warnDiagnostic(VIEW_MOUNT_ERROR, `External view mount error for column '${col.field}': ${e}`, grid.id);\n }\n } else {\n queueMicrotask(() => {\n try {\n grid.dispatchEvent(\n new CustomEvent('mount-external-view', {\n bubbles: true,\n composed: true,\n detail: { placeholder, spec, context },\n }),\n );\n } catch (e) {\n // Log dispatch errors as warnings\n warnDiagnostic(\n VIEW_DISPATCH_ERROR,\n `External view event dispatch error for column '${col.field}': ${e}`,\n grid.id,\n );\n }\n });\n }\n placeholder.setAttribute('data-mounted', '');\n } else if (compiled) {\n const output = compiled({ row: rowData, value, field: col.field, column: col });\n const blocked = compiled.__blocked;\n // Sanitize compiled template output to prevent XSS\n cell.innerHTML = blocked ? '' : sanitizeHTML(output);\n needsSanitization = true;\n if (blocked) {\n // Forcefully clear any residual whitespace text nodes for deterministic emptiness\n cell.textContent = '';\n cell.setAttribute('data-blocked-template', '');\n }\n } else if (tplHolder) {\n const rawTpl = tplHolder.innerHTML;\n if (/Reflect\\.|\\bProxy\\b|ownKeys\\(/.test(rawTpl)) {\n cell.textContent = '';\n cell.setAttribute('data-blocked-template', '');\n } else {\n // Sanitize inline template output to prevent XSS\n cell.innerHTML = sanitizeHTML(evalTemplateString(rawTpl, { row: rowData, value }));\n needsSanitization = true;\n }\n } else {\n // Plain value rendering - compute display directly (matches Stencil performance)\n // If formatFn was applied, value is already formatted - just use it\n if (formatFn) {\n cell.textContent = value == null ? '' : String(value);\n } else if (col.type === 'date') {\n cell.textContent = formatDateValue(value);\n } else if (col.type === 'boolean') {\n // Wrap checkbox in span to satisfy ARIA: gridcell can contain checkbox\n cell.innerHTML = booleanCellHTML(!!value);\n } else {\n cell.textContent = value == null ? '' : String(value);\n }\n }\n\n // Only run expensive sanitization when we used innerHTML with user content\n if (needsSanitization) {\n finalCellScrub(cell);\n // Defensive: if forbidden tokens leaked via async or framework hydration, scrub again.\n const textContent = cell.textContent || '';\n if (/Proxy|Reflect\\.ownKeys/.test(textContent)) {\n cell.textContent = textContent.replace(/Proxy|Reflect\\.ownKeys/g, '').trim();\n if (/Proxy|Reflect\\.ownKeys/.test(cell.textContent || '')) cell.textContent = '';\n }\n }\n\n if (cell.hasAttribute('data-blocked-template')) {\n // If anything at all remains (e.g., 'function () { [native code] }'), blank it completely.\n if ((cell.textContent || '').trim().length) cell.textContent = '';\n }\n // Mark editable cells with tabindex for keyboard navigation\n // Event handlers are set up via delegation in setupCellEventDelegation()\n const isEditable = typeof col.editable === 'function' ? col.editable(rowData) : col.editable;\n if (isEditable) {\n cell.tabIndex = 0;\n } else if (col.type === 'boolean') {\n // Non-editable boolean cells should NOT toggle on space key\n // They are read-only, only set tabindex for focus navigation\n if (!cell.hasAttribute('tabindex')) cell.tabIndex = 0;\n }\n\n // Initialize focus state (must match fastPatchRow for consistent behavior)\n if (focusRow === rowIndex && focusCol === colIndex) {\n cell.classList.add('cell-focus');\n cell.setAttribute('aria-selected', 'true');\n } else {\n cell.setAttribute('aria-selected', 'false');\n }\n\n // Apply cellClass callback if configured\n const cellClassFn = col.cellClass;\n if (cellClassFn) {\n try {\n const cellValue = (rowData as Record<string, unknown>)[col.field];\n const result = cellClassFn(cellValue, rowData, col);\n const cellClasses = typeof result === 'string' ? result.split(/\\s+/) : result;\n if (cellClasses && cellClasses.length > 0) {\n let dynamicClassStr = '';\n for (const c of cellClasses) {\n if (c && typeof c === 'string') {\n cell.classList.add(c);\n dynamicClassStr += (dynamicClassStr ? ' ' : '') + c;\n }\n }\n cell.setAttribute('data-dynamic-classes', dynamicClassStr);\n }\n } catch (e) {\n warnDiagnostic(CELL_CLASS_ERROR, `cellClass callback error for column '${col.field}': ${e}`, grid.id);\n }\n }\n\n // Call cell-level plugin hook if any plugin registered it\n if (hasCellHook) {\n grid._afterCellRender?.({\n row: rowData,\n rowIndex,\n column: col,\n colIndex,\n value,\n cellElement: cell,\n rowElement: rowEl,\n });\n }\n\n fragment.appendChild(cell);\n }\n\n // Single DOM operation to append all cells\n rowEl.appendChild(fragment);\n}\n// #endregion\n\n// #region Interaction\n/**\n * Handle click / double click interaction to focus cells.\n * Edit triggering is handled by EditingPlugin via onCellClick hook.\n */\nexport function handleRowClick(grid: GridHost, e: MouseEvent, rowEl: HTMLElement): void {\n if ((e.target as HTMLElement)?.closest('.resize-handle')) return;\n const firstCell = rowEl.querySelector('.cell[data-row]') as HTMLElement | null;\n const rowIndex = getRowIndexFromCell(firstCell);\n if (rowIndex < 0) return;\n const rowData = grid._rows[rowIndex];\n if (!rowData) return;\n\n // Dispatch row click to plugin system first (e.g., for master-detail expansion)\n if (grid._dispatchRowClick?.(e, rowIndex, rowData, rowEl)) {\n return;\n }\n\n const cellEl = (e.target as HTMLElement)?.closest('.cell[data-col]') as HTMLElement | null;\n if (cellEl) {\n const colIndex = Number(cellEl.getAttribute('data-col'));\n if (!isNaN(colIndex)) {\n // Dispatch to plugin system first - if handled (e.g., edit triggered), stop propagation\n if (grid._dispatchCellClick?.(e, rowIndex, colIndex, cellEl)) {\n return;\n }\n\n // Always update focus to the clicked cell\n const focusChanged = grid._focusRow !== rowIndex || grid._focusCol !== colIndex;\n grid._focusRow = rowIndex;\n grid._focusCol = colIndex;\n\n // If clicking an already-editing cell, just update focus styling and return\n if (cellEl.classList.contains('editing')) {\n if (focusChanged) {\n // Update .cell-focus class to reflect new focus (clear from grid element)\n clearCellFocus(grid._bodyEl ?? grid);\n cellEl.classList.add('cell-focus');\n }\n // Prefer the actual click target when it's a focusable element inside the\n // cell. This preserves user intent — e.g., clicking an <input> inside a\n // mat-chip-grid should focus that input, not the first chip row (which\n // also matches FOCUSABLE_EDITOR_SELECTOR via [tabindex]).\n const target = e.target as HTMLElement;\n const editor =\n cellEl.contains(target) && target.matches(FOCUSABLE_EDITOR_SELECTOR)\n ? target\n : (cellEl.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null);\n try {\n editor?.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n return;\n }\n\n ensureCellVisible(grid);\n }\n }\n}\n// #endregion\n","/**\n * Central keyboard handler attached to the host element. Manages navigation, paging,\n * and edit lifecycle triggers while respecting active form field interactions.\n */\nimport type { GridHost } from '../types';\nimport { FOCUSABLE_EDITOR_SELECTOR } from './rows';\nimport { clearCellFocus, isRTL } from './utils';\n\n// #region Keyboard Handler\nexport function handleGridKeyDown(grid: GridHost, e: KeyboardEvent): void {\n // Dispatch to plugin system first - if any plugin handles it, stop here\n if (grid._dispatchKeyDown?.(e)) {\n return;\n }\n\n const maxRow = grid._rows.length - 1;\n const maxCol = grid._visibleColumns.length - 1;\n const editing = grid._activeEditRows !== undefined && grid._activeEditRows !== -1;\n const col = grid._visibleColumns[grid._focusCol];\n const colType = col?.type;\n const path = e.composedPath?.() ?? [];\n const target = (path.length ? path[0] : e.target) as HTMLElement | null;\n const isFormField = (el: HTMLElement | null) => {\n if (!el) return false;\n const tag = el.tagName;\n if (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') return true;\n if (el.isContentEditable) return true;\n return false;\n };\n if (isFormField(target) && (e.key === 'Home' || e.key === 'End')) return;\n if (isFormField(target) && (e.key === 'ArrowUp' || e.key === 'ArrowDown')) {\n if ((target as HTMLInputElement).tagName === 'INPUT' && (target as HTMLInputElement).type === 'number') return;\n }\n // Let arrow left/right navigate within text inputs instead of moving cells\n if (isFormField(target) && (e.key === 'ArrowLeft' || e.key === 'ArrowRight')) return;\n // Let Enter/Escape be handled by the input's own handlers first\n if (isFormField(target) && (e.key === 'Enter' || e.key === 'Escape')) return;\n if (editing && colType === 'select' && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) return;\n switch (e.key) {\n case 'Tab': {\n e.preventDefault();\n const forward = !e.shiftKey;\n if (forward) {\n if (grid._focusCol < maxCol) grid._focusCol += 1;\n else {\n if (typeof grid.commitActiveRowEdit === 'function') grid.commitActiveRowEdit();\n if (grid._focusRow < maxRow) {\n grid._focusRow += 1;\n grid._focusCol = 0;\n }\n }\n } else {\n if (grid._focusCol > 0) grid._focusCol -= 1;\n else if (grid._focusRow > 0) {\n if (typeof grid.commitActiveRowEdit === 'function' && grid._activeEditRows === grid._focusRow)\n grid.commitActiveRowEdit();\n grid._focusRow -= 1;\n grid._focusCol = maxCol;\n }\n }\n ensureCellVisible(grid);\n return;\n }\n case 'ArrowDown':\n if (editing && typeof grid.commitActiveRowEdit === 'function') grid.commitActiveRowEdit();\n grid._focusRow = Math.min(maxRow, grid._focusRow + 1);\n e.preventDefault();\n break;\n case 'ArrowUp':\n if (editing && typeof grid.commitActiveRowEdit === 'function') grid.commitActiveRowEdit();\n grid._focusRow = Math.max(0, grid._focusRow - 1);\n e.preventDefault();\n break;\n case 'ArrowRight': {\n // In RTL mode, ArrowRight moves toward the start (lower column index)\n const rtl = isRTL(grid);\n if (rtl) {\n grid._focusCol = Math.max(0, grid._focusCol - 1);\n } else {\n grid._focusCol = Math.min(maxCol, grid._focusCol + 1);\n }\n e.preventDefault();\n break;\n }\n case 'ArrowLeft': {\n // In RTL mode, ArrowLeft moves toward the end (higher column index)\n const rtl = isRTL(grid);\n if (rtl) {\n grid._focusCol = Math.min(maxCol, grid._focusCol + 1);\n } else {\n grid._focusCol = Math.max(0, grid._focusCol - 1);\n }\n e.preventDefault();\n break;\n }\n case 'Home':\n if (e.ctrlKey || e.metaKey) {\n // CTRL+Home: navigate to first row, first cell\n if (editing && typeof grid.commitActiveRowEdit === 'function') grid.commitActiveRowEdit();\n grid._focusRow = 0;\n grid._focusCol = 0;\n } else {\n // Home: navigate to first cell in current row\n grid._focusCol = 0;\n }\n e.preventDefault();\n ensureCellVisible(grid, { forceScrollLeft: true });\n return;\n case 'End':\n if (e.ctrlKey || e.metaKey) {\n // CTRL+End: navigate to last row, last cell\n if (editing && typeof grid.commitActiveRowEdit === 'function') grid.commitActiveRowEdit();\n grid._focusRow = maxRow;\n grid._focusCol = maxCol;\n } else {\n // End: navigate to last cell in current row\n grid._focusCol = maxCol;\n }\n e.preventDefault();\n ensureCellVisible(grid, { forceScrollRight: true });\n return;\n case 'PageDown':\n grid._focusRow = Math.min(maxRow, grid._focusRow + 20);\n e.preventDefault();\n break;\n case 'PageUp':\n grid._focusRow = Math.max(0, grid._focusRow - 20);\n e.preventDefault();\n break;\n // NOTE: Enter key is handled by EditingPlugin. If no plugin handles it,\n // we dispatch the unified cell-activate event for custom handling.\n case 'Enter': {\n const rowIndex = grid._focusRow;\n const colIndex = grid._focusCol;\n const column = grid._visibleColumns[colIndex];\n const row = grid._rows[rowIndex];\n const field = column?.field ?? '';\n const value = field && row ? (row as Record<string, unknown>)[field] : undefined;\n const cellEl = grid.querySelector(`[data-row=\"${rowIndex}\"][data-col=\"${colIndex}\"]`) as HTMLElement | undefined;\n\n const detail = {\n rowIndex,\n colIndex,\n column,\n field,\n value,\n row,\n cellEl,\n trigger: 'keyboard' as const,\n originalEvent: e,\n };\n\n // Emit unified cell-activate event\n const activateEvent = new CustomEvent('cell-activate', {\n cancelable: true,\n detail,\n });\n grid.dispatchEvent(activateEvent);\n\n // Also emit deprecated activate-cell for backwards compatibility\n const legacyEvent = new CustomEvent('activate-cell', {\n cancelable: true,\n detail: { row: rowIndex, col: colIndex },\n });\n grid.dispatchEvent(legacyEvent);\n\n // If either event was prevented, block further keyboard processing\n if (activateEvent.defaultPrevented || legacyEvent.defaultPrevented) {\n e.preventDefault();\n return;\n }\n // Otherwise allow normal keyboard processing\n break;\n }\n default:\n return;\n }\n ensureCellVisible(grid);\n}\n// #endregion\n\n// #region Cell Visibility\n/**\n * Options for ensureCellVisible to control scroll behavior.\n */\ninterface EnsureCellVisibleOptions {\n /** Force scroll to the leftmost position (for Home key) */\n forceScrollLeft?: boolean;\n /** Force scroll to the rightmost position (for End key) */\n forceScrollRight?: boolean;\n /** Force horizontal scroll even in edit mode (for Tab navigation) */\n forceHorizontalScroll?: boolean;\n}\n\n/**\n * Scroll the viewport (virtualized or static) so the focused cell's row is visible\n * and apply visual focus styling / tabindex management.\n */\nexport function ensureCellVisible(grid: GridHost, options?: EnsureCellVisibleOptions): void {\n if (grid._virtualization?.enabled) {\n const { rowHeight, container, viewportEl } = grid._virtualization;\n // container is the faux scrollbar element that handles actual scrolling\n // viewportEl is the visible area element that has the correct height\n const scrollEl = container as HTMLElement | undefined;\n const visibleHeight = viewportEl?.clientHeight ?? scrollEl?.clientHeight ?? 0;\n if (scrollEl && visibleHeight > 0) {\n const y = grid._focusRow * rowHeight;\n if (y < scrollEl.scrollTop) {\n scrollEl.scrollTop = y;\n } else if (y + rowHeight > scrollEl.scrollTop + visibleHeight) {\n scrollEl.scrollTop = y - visibleHeight + rowHeight;\n }\n }\n }\n // Skip refreshVirtualWindow when in edit mode to avoid wiping editors\n const isEditing = (grid._activeEditRows !== undefined && grid._activeEditRows !== -1) || !!grid._isGridEditMode;\n if (!isEditing) {\n grid.refreshVirtualWindow(false);\n }\n clearCellFocus(grid._bodyEl);\n // Clear previous aria-selected markers\n Array.from(grid._bodyEl.querySelectorAll('[aria-selected=\"true\"]')).forEach((el) => {\n el.setAttribute('aria-selected', 'false');\n });\n const rowIndex = grid._focusRow;\n const vStart = grid._virtualization.start ?? 0;\n const vEnd = grid._virtualization.end ?? grid._rows.length;\n if (rowIndex >= vStart && rowIndex < vEnd) {\n const rowEl = grid._bodyEl.querySelectorAll('.data-grid-row')[rowIndex - vStart] as HTMLElement | null;\n // Try exact column match first, then query by data-col, then fallback to first cell (for full-width group rows)\n let cell = rowEl?.children[grid._focusCol] as HTMLElement | undefined;\n if (!cell || !cell.classList?.contains('cell')) {\n cell = (rowEl?.querySelector(`.cell[data-col=\"${grid._focusCol}\"]`) ??\n rowEl?.querySelector('.cell[data-col]')) as HTMLElement | undefined;\n }\n if (cell) {\n cell.classList.add('cell-focus');\n cell.setAttribute('aria-selected', 'true');\n\n // Horizontal scroll: ensure focused cell is visible in the horizontal scroll area\n // The .tbw-scroll-area element handles horizontal scrolling\n // Skip horizontal scrolling when in edit mode to prevent scroll jumps when editors are created\n // Unless forceHorizontalScroll is set (e.g., for Tab navigation while editing)\n const scrollArea = grid.querySelector('.tbw-scroll-area') as HTMLElement | null;\n if (scrollArea && cell && (!isEditing || options?.forceHorizontalScroll)) {\n // Handle forced scroll for Home/End keys - always scroll to edge\n if (options?.forceScrollLeft) {\n scrollArea.scrollLeft = 0;\n } else if (options?.forceScrollRight) {\n scrollArea.scrollLeft = scrollArea.scrollWidth - scrollArea.clientWidth;\n } else {\n // Get scroll boundary offsets from plugins (e.g., pinned columns)\n // This allows plugins to report how much of the scroll area they obscure\n // and whether the focused cell should skip scrolling (e.g., pinned cells are always visible)\n const offsets = grid._getHorizontalScrollOffsets?.(rowEl ?? undefined, cell) ?? { left: 0, right: 0 };\n\n if (!offsets.skipScroll) {\n // Get cell position relative to the scroll area\n const cellRect = cell.getBoundingClientRect();\n const scrollAreaRect = scrollArea.getBoundingClientRect();\n // Calculate the cell's position relative to scroll area's visible region\n const cellLeft = cellRect.left - scrollAreaRect.left + scrollArea.scrollLeft;\n const cellRight = cellLeft + cellRect.width;\n // Adjust visible boundaries to account for plugin-reported offsets\n const visibleLeft = scrollArea.scrollLeft + offsets.left;\n const visibleRight = scrollArea.scrollLeft + scrollArea.clientWidth - offsets.right;\n // Scroll horizontally if needed\n if (cellLeft < visibleLeft) {\n scrollArea.scrollLeft = cellLeft - offsets.left;\n } else if (cellRight > visibleRight) {\n scrollArea.scrollLeft = cellRight - scrollArea.clientWidth + offsets.right;\n }\n }\n }\n }\n\n if (isEditing && cell.classList.contains('editing')) {\n // Editing cell: focus the editor input inside it\n const focusTarget = cell.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n if (focusTarget && document.activeElement !== focusTarget) {\n try {\n focusTarget.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n }\n } else if (isEditing && !cell.contains(document.activeElement)) {\n // Active edit row but this cell isn't the editing cell — focus it\n // so Tab navigation within the row can attach editors\n if (!cell.hasAttribute('tabindex')) cell.setAttribute('tabindex', '-1');\n try {\n cell.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n } else if (!isEditing) {\n // NOT editing: keep focus on the grid element (tabindex=0) rather than\n // individual cells. In a virtualized grid, cells can be detached by\n // subsequent render cycles (e.g., SelectionPlugin's requestAfterRender\n // → RAF → row recycling). A detached focused cell causes activeElement\n // to revert to <body>, breaking keyboard navigation.\n // Visual focus is managed by the .cell-focus CSS class + data-has-focus.\n if (document.activeElement !== grid) {\n grid.focus({ preventScroll: true });\n }\n }\n }\n }\n}\n// #endregion\n","/**\n * Event Delegation Module\n *\n * Consolidates all delegated event handling for the grid.\n * Uses event delegation (single listener on container) rather than per-cell/per-row\n * listeners to minimize memory usage.\n *\n * This module provides:\n * - setupCellEventDelegation: Body-level handlers (mousedown, click, dblclick on cells/rows)\n * - setupRootEventDelegation: Root-level handlers (keydown, mousedown for plugins, drag tracking)\n *\n * Edit triggering is handled separately by the EditingPlugin via\n * onCellClick and onKeyDown hooks.\n */\n\nimport type { CellMouseEvent } from '../plugin/types';\nimport type { GridHost, InternalGrid } from '../types';\nimport { handleGridKeyDown } from './keyboard';\nimport { handleRowClick } from './rows';\nimport { clearCellFocus, getColIndexFromCell, getRowIndexFromCell } from './utils';\n\n// #region Utilities\n// Track drag state per grid instance (avoids polluting InternalGrid interface)\nconst dragState = new WeakMap<InternalGrid, boolean>();\n// #endregion\n\n// #region Cell Mouse Handlers\n/**\n * Handle delegated mousedown on cells.\n * Updates focus position for navigation.\n *\n * IMPORTANT: This must NOT call refreshVirtualWindow or any function that\n * re-renders DOM elements. Doing so would replace the element the user clicked on,\n * causing the subsequent click event to fire on a detached element and not bubble\n * to parent handlers (like handleRowClick).\n *\n * For mouse interactions, the cell is already visible (user clicked on it),\n * so we only need to update focus state without scrolling or re-rendering.\n */\nfunction handleCellMousedown(grid: InternalGrid, cell: HTMLElement): void {\n const rowIndex = getRowIndexFromCell(cell);\n const colIndex = getColIndexFromCell(cell);\n if (rowIndex < 0 || colIndex < 0) return;\n\n grid._focusRow = rowIndex;\n grid._focusCol = colIndex;\n\n // Update focus styling directly without triggering re-render.\n // ensureCellVisible() would call refreshVirtualWindow() which replaces DOM elements,\n // breaking the click event that follows this mousedown.\n clearCellFocus(grid._bodyEl);\n cell.classList.add('cell-focus');\n cell.setAttribute('aria-selected', 'true');\n\n // Ensure the grid element has DOM focus so keyboard events (like Ctrl+C for clipboard)\n // bubble through the grid's keydown listener. Without this, clicking on non-focusable\n // cells may leave focus elsewhere, making keyboard shortcuts unreachable.\n // Always call focus() — even when activeElement is inside the grid — because\n // browser default behaviors (e.g., shift+click text selection, cell re-pooling)\n // can move focus to document.body between mousedown and the next keydown.\n const gridEl = cell.closest('tbw-grid') as HTMLElement | null;\n if (gridEl && document.activeElement !== gridEl) {\n gridEl.focus({ preventScroll: true });\n }\n}\n// #endregion\n\n// #region Mouse Event Building\n/**\n * Build a CellMouseEvent from a native MouseEvent.\n * Extracts cell/row information from the event target.\n */\nfunction buildCellMouseEvent(\n grid: InternalGrid,\n renderRoot: HTMLElement,\n e: MouseEvent,\n type: 'mousedown' | 'mousemove' | 'mouseup',\n): CellMouseEvent {\n // For document-level events (mousemove/mouseup during drag), e.target won't be inside shadow DOM.\n // Use composedPath to find elements inside shadow roots, or fall back to elementFromPoint.\n let target: Element | null = null;\n\n // composedPath gives us the full path including shadow DOM elements\n const path = e.composedPath?.() as Element[] | undefined;\n if (path && path.length > 0) {\n target = path[0];\n } else {\n target = e.target as Element;\n }\n\n // If target is not inside our element (e.g., for document-level events),\n // use elementFromPoint to find the actual element under the mouse\n if (target && !renderRoot.contains(target)) {\n const elAtPoint = document.elementFromPoint(e.clientX, e.clientY);\n if (elAtPoint) {\n target = elAtPoint;\n }\n }\n\n // Cells have data-col and data-row attributes\n const cellEl = target?.closest?.('[data-col]') as HTMLElement | null;\n const rowEl = target?.closest?.('.data-grid-row') as HTMLElement | null;\n const headerEl = target?.closest?.('.header-row') as HTMLElement | null;\n\n let rowIndex: number | undefined;\n let colIndex: number | undefined;\n let row: unknown;\n let field: string | undefined;\n let value: unknown;\n let column: unknown;\n\n if (cellEl) {\n // Get indices from cell attributes\n rowIndex = parseInt(cellEl.getAttribute('data-row') ?? '-1', 10);\n colIndex = parseInt(cellEl.getAttribute('data-col') ?? '-1', 10);\n if (rowIndex >= 0 && colIndex >= 0) {\n row = grid._rows[rowIndex];\n // colIndex from data-col is a visible-column index (rendering uses _visibleColumns)\n column = grid._visibleColumns[colIndex];\n field = (column as { field?: string })?.field;\n value = row && field ? (row as Record<string, unknown>)[field] : undefined;\n }\n }\n\n return {\n type,\n row,\n rowIndex: rowIndex !== undefined && rowIndex >= 0 ? rowIndex : undefined,\n colIndex: colIndex !== undefined && colIndex >= 0 ? colIndex : undefined,\n field,\n value,\n column: column as CellMouseEvent['column'],\n originalEvent: e,\n cellElement: cellEl ?? undefined,\n rowElement: rowEl ?? undefined,\n isHeader: !!headerEl,\n cell:\n rowIndex !== undefined && colIndex !== undefined && rowIndex >= 0 && colIndex >= 0\n ? { row: rowIndex, col: colIndex }\n : undefined,\n };\n}\n// #endregion\n\n// #region Drag Tracking\n/**\n * Handle mousedown events and dispatch to plugin system.\n */\nfunction handleMouseDown(grid: InternalGrid, renderRoot: HTMLElement, e: MouseEvent): void {\n const event = buildCellMouseEvent(grid, renderRoot, e, 'mousedown');\n const handled = grid._dispatchCellMouseDown?.(event) ?? false;\n\n // If any plugin handled mousedown, start tracking for drag\n if (handled) {\n dragState.set(grid, true);\n }\n}\n\n/**\n * Handle mousemove events (only when dragging).\n */\nfunction handleMouseMove(grid: InternalGrid, renderRoot: HTMLElement, e: MouseEvent): void {\n if (!dragState.get(grid)) return;\n\n const event = buildCellMouseEvent(grid, renderRoot, e, 'mousemove');\n grid._dispatchCellMouseMove?.(event);\n}\n\n/**\n * Handle mouseup events.\n */\nfunction handleMouseUp(grid: InternalGrid, renderRoot: HTMLElement, e: MouseEvent): void {\n if (!dragState.get(grid)) return;\n\n const event = buildCellMouseEvent(grid, renderRoot, e, 'mouseup');\n grid._dispatchCellMouseUp?.(event);\n dragState.set(grid, false);\n}\n// #endregion\n\n// #region Setup Functions\n/**\n * Set up delegated event listeners on the grid body.\n * Consolidates all row/cell mouse event handling into a single set of listeners.\n * Call once during grid initialization.\n *\n * Benefits:\n * - 3 listeners total vs N*2 listeners (where N = pool size)\n * - Consistent event handling across all rows\n * - Automatic cleanup via AbortController signal\n *\n * @param grid - The grid instance\n * @param bodyEl - The .rows element containing all data rows\n * @param signal - AbortSignal for cleanup\n */\nexport function setupCellEventDelegation(grid: GridHost, bodyEl: HTMLElement, signal: AbortSignal): void {\n // Mousedown - update focus on any cell (not just editable)\n bodyEl.addEventListener(\n 'mousedown',\n (e) => {\n const cell = (e.target as HTMLElement).closest('.cell[data-col]') as HTMLElement | null;\n if (!cell) return;\n\n // Skip if clicking inside an editing cell (let the editor handle it)\n if (cell.classList.contains('editing')) return;\n\n // Skip preventDefault when clicking a draggable element (or its children).\n // Native HTML5 drag-and-drop requires mousedown to NOT be prevented;\n // otherwise the browser never fires dragstart.\n const target = e.target as HTMLElement;\n const isDraggable = target.draggable || target.closest('[draggable=\"true\"]');\n\n // Prevent the browser from managing focus for grid cells.\n // Without this, the browser can steal focus (e.g., shift+click extends\n // a native text selection, moving activeElement to document.body).\n // We manage focus explicitly via handleCellMousedown → gridEl.focus().\n if (!isDraggable) {\n e.preventDefault();\n }\n\n handleCellMousedown(grid, cell);\n },\n { signal },\n );\n\n // Click - handle row/cell click interactions\n bodyEl.addEventListener(\n 'click',\n (e) => {\n const rowEl = (e.target as HTMLElement).closest('.data-grid-row') as HTMLElement | null;\n if (rowEl) handleRowClick(grid, e as MouseEvent, rowEl);\n\n // After click handling: keep focus on the grid element (not individual cells).\n // In a virtualized grid, cells can be detached by subsequent render cycles\n // (e.g., SelectionPlugin's requestAfterRender → RAF render → row recycling).\n // A detached focused cell causes activeElement to revert to <body>, breaking\n // keyboard shortcuts like Ctrl+C. The grid element (tabindex=0) is stable\n // and receives all keyboard events via bubble phase.\n // Skip if an editor is active — editors manage their own focus.\n if (!document.activeElement?.closest('.cell.editing')) {\n const gridEl = (e.target as HTMLElement).closest('tbw-grid') as HTMLElement | null;\n if (gridEl) gridEl.focus({ preventScroll: true });\n }\n },\n { signal },\n );\n\n // Dblclick - same handler as click (edit triggering handled by EditingPlugin)\n bodyEl.addEventListener(\n 'dblclick',\n (e) => {\n const rowEl = (e.target as HTMLElement).closest('.data-grid-row') as HTMLElement | null;\n if (rowEl) handleRowClick(grid, e as MouseEvent, rowEl);\n },\n { signal },\n );\n}\n\n/**\n * Set up root-level and document-level event listeners.\n * These are added once per grid lifetime (not re-attached on DOM recreation).\n *\n * Includes:\n * - keydown: Keyboard navigation (arrows, Enter, Escape)\n * - mousedown: Plugin dispatch for cell interactions\n * - mousemove/mouseup: Global drag tracking\n *\n * @param grid - The grid instance\n * @param gridElement - The grid element (for keydown)\n * @param renderRoot - The render root element (for mousedown)\n * @param signal - AbortSignal for cleanup\n */\nexport function setupRootEventDelegation(\n grid: GridHost,\n gridElement: HTMLElement,\n renderRoot: HTMLElement,\n signal: AbortSignal,\n): void {\n // Element-level keydown handler for keyboard navigation\n gridElement.addEventListener('keydown', (e) => handleGridKeyDown(grid, e), { signal });\n\n // Central mouse event handling for plugins\n renderRoot.addEventListener('mousedown', (e) => handleMouseDown(grid, renderRoot, e as MouseEvent), { signal });\n\n // Track global mousemove/mouseup for drag operations (column resize, selection, etc.)\n document.addEventListener('mousemove', (e: MouseEvent) => handleMouseMove(grid, renderRoot, e), { signal });\n document.addEventListener('mouseup', (e: MouseEvent) => handleMouseUp(grid, renderRoot, e), { signal });\n}\n// #endregion\n","/**\n * Hook point for the feature registry to connect to the grid core\n * without introducing circular imports or adding registry code to the core bundle.\n *\n * - grid.ts reads `resolveFeatures` to convert `gridConfig.features` into plugins.\n * - registry.ts calls `setFeatureResolver()` at load time to provide the implementation.\n *\n * If no feature modules are imported, `resolveFeatures` stays undefined,\n * and the grid ignores `gridConfig.features` (zero cost).\n *\n * @internal\n */\n\nimport type { GridPlugin } from '../types';\n\n/** Feature-to-plugin resolver function type. */\nexport type FeatureResolverFn = (features: Record<string, unknown>) => GridPlugin[];\n\n/** Resolver set by the feature registry when loaded. undefined until first feature import. */\nexport let resolveFeatures: FeatureResolverFn | undefined;\n\n/** Called by `features/registry.ts` at module evaluation time. @internal */\nexport function setFeatureResolver(fn: FeatureResolverFn): void {\n resolveFeatures = fn;\n}\n","/**\n * FocusManager — encapsulates focus/navigation state and external focus\n * container tracking that were previously inline in the DataGridElement class.\n *\n * Owns:\n * - External focus container registry (register/unregister/containsFocus)\n * - Focus cell navigation (focusCell, focusedCell getter)\n * - Scroll-to-row logic (scrollToRow, scrollToRowById)\n *\n * Takes the grid reference directly (tightly coupled — this manager\n * can never live outside the grid).\n */\nimport type { GridHost, ScrollToRowOptions } from '../types';\nimport { ensureCellVisible } from './keyboard';\n\n// #region FocusManager\n\nexport class FocusManager<T = any> {\n readonly #grid: GridHost<T>;\n\n // External focus containers — overlay panels (datepickers, dropdowns)\n // that render at <body> level but should be treated as \"inside\" the grid.\n #externalFocusContainers = new Set<Element>();\n #externalFocusCleanups = new Map<Element, () => void>();\n\n constructor(grid: GridHost<T>) {\n this.#grid = grid;\n }\n\n // #region Focus & Navigation\n\n /**\n * Move focus to a specific cell.\n * Accepts a column index or field name.\n */\n focusCell(rowIndex: number, column: number | string): void {\n const grid = this.#grid;\n const maxRow = grid._rows.length - 1;\n if (maxRow < 0) return;\n\n let colIdx: number;\n if (typeof column === 'string') {\n colIdx = grid._visibleColumns.findIndex((c) => c.field === column);\n if (colIdx < 0) return;\n } else {\n colIdx = column;\n }\n\n const maxCol = grid._visibleColumns.length - 1;\n if (maxCol < 0) return;\n\n grid._focusRow = Math.max(0, Math.min(rowIndex, maxRow));\n grid._focusCol = Math.max(0, Math.min(colIdx, maxCol));\n ensureCellVisible(grid);\n }\n\n /**\n * The currently focused cell position, or `null` if no rows are loaded.\n */\n get focusedCell(): { rowIndex: number; colIndex: number; field: string } | null {\n const grid = this.#grid;\n if (grid._rows.length === 0 || grid._visibleColumns.length === 0) return null;\n const col = grid._visibleColumns[grid._focusCol];\n return {\n rowIndex: grid._focusRow,\n colIndex: grid._focusCol,\n field: col?.field ?? '',\n };\n }\n\n /**\n * Scroll the viewport so a row is visible.\n */\n scrollToRow(rowIndex: number, options?: ScrollToRowOptions): void {\n const virt = this.#grid._virtualization;\n if (!virt.enabled) return;\n\n const scrollEl = virt.container as HTMLElement | undefined;\n if (!scrollEl) return;\n\n const totalRows = this.#grid._rows.length;\n if (totalRows === 0) return;\n\n const idx = Math.max(0, Math.min(rowIndex, totalRows - 1));\n const align = options?.align ?? 'nearest';\n const behavior = options?.behavior ?? 'instant';\n\n // Calculate row offset and height, accounting for variable row heights\n let rowTop: number;\n let rowH: number;\n const pc = virt.positionCache;\n if (virt.variableHeights && pc && pc.length > idx) {\n rowTop = pc[idx].offset;\n rowH = pc[idx].height;\n } else {\n rowTop = idx * virt.rowHeight;\n rowH = virt.rowHeight;\n }\n\n const viewportH = virt.viewportEl?.clientHeight ?? scrollEl.clientHeight ?? 0;\n if (viewportH <= 0) return;\n\n const currentTop = scrollEl.scrollTop;\n const rowBottom = rowTop + rowH;\n const viewBottom = currentTop + viewportH;\n\n let target: number;\n switch (align) {\n case 'start':\n target = rowTop;\n break;\n case 'center':\n target = rowTop - viewportH / 2 + rowH / 2;\n break;\n case 'end':\n target = rowBottom - viewportH;\n break;\n case 'nearest':\n default:\n // Already fully visible — no scroll needed\n if (rowTop >= currentTop && rowBottom <= viewBottom) return;\n // Scroll up or down to bring row into view (minimum movement)\n target = rowTop < currentTop ? rowTop : rowBottom - viewportH;\n break;\n }\n\n target = Math.max(0, target);\n\n if (behavior === 'smooth') {\n scrollEl.scrollTo({ top: target, behavior: 'smooth' });\n } else {\n scrollEl.scrollTop = target;\n }\n }\n\n /**\n * Scroll the viewport so a row is visible, identified by its unique ID.\n */\n scrollToRowById(rowId: string, options?: ScrollToRowOptions): void {\n const entry = this.#grid._getRowEntry(rowId);\n if (!entry) return;\n this.scrollToRow(entry.index, options);\n }\n\n // #endregion\n\n // #region External Focus Containers\n\n /**\n * Register an external DOM element as a logical focus container of this grid.\n * Focus moving into a registered container is treated as if it stayed inside the grid.\n */\n registerExternalFocusContainer(el: Element): void {\n if (this.#externalFocusContainers.has(el)) return;\n this.#externalFocusContainers.add(el);\n\n const ac = new AbortController();\n const signal = ac.signal;\n const gridEl = this.#grid;\n\n el.addEventListener(\n 'focusin',\n () => {\n gridEl.dataset.hasFocus = '';\n },\n { signal },\n );\n\n el.addEventListener(\n 'focusout',\n (e) => {\n const newFocus = (e as FocusEvent).relatedTarget as Node | null;\n if (!newFocus || !this.containsFocus(newFocus)) {\n delete gridEl.dataset.hasFocus;\n }\n },\n { signal },\n );\n\n this.#externalFocusCleanups.set(el, () => ac.abort());\n }\n\n /**\n * Unregister a previously registered external focus container.\n */\n unregisterExternalFocusContainer(el: Element): void {\n this.#externalFocusContainers.delete(el);\n const cleanup = this.#externalFocusCleanups.get(el);\n if (cleanup) {\n cleanup();\n this.#externalFocusCleanups.delete(el);\n }\n }\n\n /**\n * Check whether focus is logically inside this grid.\n * Returns `true` when the node is inside the grid's DOM or any external container.\n */\n containsFocus(node?: Node | null): boolean {\n const target = node ?? document.activeElement;\n if (!target) return false;\n if (this.#grid.contains(target)) return true;\n return this.isInExternalFocusContainer(target);\n }\n\n /**\n * Check whether a node is inside any registered external focus container.\n */\n isInExternalFocusContainer(node: Node): boolean {\n for (const container of this.#externalFocusContainers) {\n if (container.contains(node)) return true;\n }\n return false;\n }\n\n // #endregion\n\n // #region Cleanup\n\n /**\n * Clean up all external focus container listeners.\n * Called when the grid disconnects.\n */\n destroy(): void {\n for (const cleanup of this.#externalFocusCleanups.values()) {\n cleanup();\n }\n this.#externalFocusCleanups.clear();\n this.#externalFocusContainers.clear();\n }\n\n // #endregion\n}\n\n// #endregion\n","/**\n * Idle Scheduler - Defer non-critical work to browser idle periods.\n *\n * Uses requestIdleCallback where available, with fallback to setTimeout.\n * This allows the main thread to remain responsive during startup.\n */\n\n/**\n * Check if requestIdleCallback is available (not in Safari < 17.4).\n */\nconst hasIdleCallback = typeof requestIdleCallback === 'function';\n\n/**\n * IdleDeadline-compatible interface for fallback.\n */\ninterface IdleDeadlineLike {\n didTimeout: boolean;\n timeRemaining(): number;\n}\n\n/**\n * Schedule work to run during browser idle time.\n * Falls back to setTimeout(0) if requestIdleCallback is not available.\n *\n * @param callback - Work to run when idle\n * @param options - Optional timeout configuration\n * @returns Handle for cancellation\n */\nexport function scheduleIdle(callback: (deadline: IdleDeadlineLike) => void, options?: { timeout?: number }): number {\n if (hasIdleCallback) {\n return requestIdleCallback(callback, options);\n }\n\n // Fallback for Safari (before 17.4) and older browsers\n return window.setTimeout(() => {\n const start = Date.now();\n callback({\n didTimeout: false,\n timeRemaining: () => Math.max(0, 50 - (Date.now() - start)),\n });\n }, 1);\n}\n\n/**\n * Cancel a scheduled idle callback.\n */\nexport function cancelIdle(handle: number): void {\n if (hasIdleCallback) {\n cancelIdleCallback(handle);\n } else {\n clearTimeout(handle);\n }\n}\n","/**\n * Loading State Module\n *\n * Handles loading overlays, row loading, and cell loading states.\n * Provides DOM manipulation helpers for loading indicators.\n *\n * @module internal/loading\n */\n\nimport type { GridConfig, LoadingContext } from '../types';\n\n/**\n * Create the default spinner element.\n * @param size - 'large' for grid overlay, 'small' for row/cell\n */\nexport function createDefaultSpinner(size: 'large' | 'small'): HTMLElement {\n const spinner = document.createElement('div');\n spinner.className = `tbw-spinner tbw-spinner--${size}`;\n spinner.setAttribute('role', 'progressbar');\n spinner.setAttribute('aria-label', 'Loading');\n return spinner;\n}\n\n/**\n * Create loading content using custom renderer or default spinner.\n * @param size - 'large' for grid overlay, 'small' for row/cell\n * @param renderer - Optional custom loading renderer from config\n */\nexport function createLoadingContent(size: 'large' | 'small', renderer?: GridConfig['loadingRenderer']): HTMLElement {\n if (renderer) {\n const context: LoadingContext = { size };\n const result = renderer(context);\n if (typeof result === 'string') {\n const wrapper = document.createElement('div');\n wrapper.innerHTML = result;\n return wrapper;\n }\n return result;\n }\n\n return createDefaultSpinner(size);\n}\n\n/**\n * Create or update the loading overlay element.\n * @param renderer - Optional custom loading renderer from config\n */\nexport function createLoadingOverlay(renderer?: GridConfig['loadingRenderer']): HTMLElement {\n const overlay = document.createElement('div');\n overlay.className = 'tbw-loading-overlay';\n overlay.setAttribute('role', 'status');\n overlay.setAttribute('aria-live', 'polite');\n overlay.appendChild(createLoadingContent('large', renderer));\n return overlay;\n}\n\n/**\n * Show the loading overlay on the grid root element.\n * @param gridRoot - The .tbw-grid-root element\n * @param overlayEl - The overlay element (will be cached by caller)\n */\nexport function showLoadingOverlay(gridRoot: Element, overlayEl: HTMLElement): void {\n gridRoot.appendChild(overlayEl);\n}\n\n/**\n * Hide the loading overlay.\n * @param overlayEl - The overlay element to remove\n */\nexport function hideLoadingOverlay(overlayEl: HTMLElement | undefined): void {\n overlayEl?.remove();\n}\n\n/**\n * Update a row element's loading state.\n * Uses real DOM elements instead of ::before/::after pseudo-elements\n * because the selection plugin already uses ::after on rows for border styling.\n * @param rowEl - The row element\n * @param loading - Whether the row is loading\n */\nexport function setRowLoadingState(rowEl: HTMLElement, loading: boolean): void {\n if (loading) {\n rowEl.classList.add('tbw-row-loading');\n rowEl.setAttribute('aria-busy', 'true');\n\n // Create overlay + spinner DOM elements if not already present\n if (!rowEl.querySelector('.tbw-row-loading-overlay')) {\n const overlay = document.createElement('div');\n overlay.className = 'tbw-row-loading-overlay';\n overlay.setAttribute('aria-hidden', 'true');\n\n const spinner = document.createElement('div');\n spinner.className = 'tbw-row-loading-spinner';\n overlay.appendChild(spinner);\n\n rowEl.appendChild(overlay);\n }\n } else {\n rowEl.classList.remove('tbw-row-loading');\n rowEl.removeAttribute('aria-busy');\n\n // Remove overlay + spinner DOM elements\n rowEl.querySelector('.tbw-row-loading-overlay')?.remove();\n }\n}\n\n/**\n * Update a cell element's loading state.\n * @param cellEl - The cell element\n * @param loading - Whether the cell is loading\n */\nexport function setCellLoadingState(cellEl: HTMLElement, loading: boolean): void {\n if (loading) {\n cellEl.classList.add('tbw-cell-loading');\n cellEl.setAttribute('aria-busy', 'true');\n } else {\n cellEl.classList.remove('tbw-cell-loading');\n cellEl.removeAttribute('aria-busy');\n }\n}\n","import type { GridHost, ResizeController } from '../types';\n\nexport function createResizeController(grid: GridHost): ResizeController {\n let resizeState: { startX: number; colIndex: number; startWidth: number } | null = null;\n let pendingRaf: number | null = null;\n let prevCursor: string | null = null;\n let prevUserSelect: string | null = null;\n const onMove = (e: MouseEvent) => {\n if (!resizeState) return;\n const delta = e.clientX - resizeState.startX;\n const width = Math.max(40, resizeState.startWidth + delta);\n const col = grid._visibleColumns[resizeState.colIndex];\n col.width = width;\n col.__userResized = true;\n col.__renderedWidth = width;\n if (pendingRaf == null) {\n pendingRaf = requestAnimationFrame(() => {\n pendingRaf = null;\n grid.updateTemplate?.();\n });\n }\n grid.dispatchEvent(new CustomEvent('column-resize', { detail: { field: col.field, width } }));\n };\n let justFinishedResize = false;\n const onUp = () => {\n const hadResize = resizeState !== null;\n // Set flag to suppress click events that fire immediately after mouseup\n if (hadResize) {\n justFinishedResize = true;\n requestAnimationFrame(() => {\n justFinishedResize = false;\n });\n }\n window.removeEventListener('mousemove', onMove);\n window.removeEventListener('mouseup', onUp);\n if (prevCursor !== null) {\n document.documentElement.style.cursor = prevCursor;\n prevCursor = null;\n }\n if (prevUserSelect !== null) {\n document.body.style.userSelect = prevUserSelect;\n prevUserSelect = null;\n }\n resizeState = null;\n // Trigger state change after resize completes\n if (hadResize && grid.requestStateChange) {\n grid.requestStateChange();\n }\n };\n return {\n get isResizing() {\n return resizeState !== null || justFinishedResize;\n },\n start(e, colIndex, cell) {\n e.preventDefault();\n // Use the column's configured/rendered width, not the cell's bounding rect.\n // The bounding rect can be incorrect if CSS grid-column spanning is in effect\n // (e.g., when previous columns are display:none and this cell spans multiple tracks).\n const col = grid._visibleColumns[colIndex];\n // Only use numeric widths; string widths (e.g., \"100px\", \"20%\") fall back to bounding rect\n const colWidth = typeof col?.width === 'number' ? col.width : undefined;\n const startWidth = col?.__renderedWidth ?? colWidth ?? cell.getBoundingClientRect().width;\n resizeState = { startX: e.clientX, colIndex, startWidth };\n window.addEventListener('mousemove', onMove);\n window.addEventListener('mouseup', onUp);\n if (prevCursor === null) prevCursor = document.documentElement.style.cursor;\n document.documentElement.style.cursor = 'e-resize';\n if (prevUserSelect === null) prevUserSelect = document.body.style.userSelect;\n document.body.style.userSelect = 'none';\n },\n resetColumn(colIndex) {\n const col = grid._visibleColumns[colIndex];\n if (!col) return;\n\n // Reset to original configured width (or undefined for auto-sizing)\n col.__userResized = false;\n col.__renderedWidth = undefined;\n col.width = col.__originalWidth;\n\n grid.updateTemplate?.();\n grid.requestStateChange?.();\n grid.dispatchEvent(new CustomEvent('column-resize-reset', { detail: { field: col.field, width: col.width } }));\n },\n dispose() {\n onUp();\n },\n };\n}\n","/**\n * Row Animation Module\n *\n * Provides row-level animation utilities for the grid.\n * Animations are CSS-based and triggered via data attributes.\n *\n * Supported animations:\n * - `change`: Flash highlight for modified rows (e.g., after editing)\n * - `insert`: Slide-in animation for newly added rows\n * - `remove`: Fade-out animation for rows being removed\n *\n * @module internal/row-animation\n */\n\nimport type { InternalGrid, RowAnimationType } from '../types';\n\n// #region Constants\n\n/**\n * Data attribute used to trigger row animations via CSS.\n */\nconst ANIMATION_ATTR = 'data-animating';\n\n/**\n * Map of animation types to their CSS custom property duration names.\n */\nconst DURATION_PROPS: Record<RowAnimationType, string> = {\n change: '--tbw-row-change-duration',\n insert: '--tbw-row-insert-duration',\n remove: '--tbw-row-remove-duration',\n};\n\n/**\n * Default animation durations in milliseconds.\n */\nconst DEFAULT_DURATIONS: Record<RowAnimationType, number> = {\n change: 500,\n insert: 300,\n remove: 200,\n};\n// #endregion\n\n// #region Internal Helpers\n/**\n * Parse a CSS duration string (e.g., \"500ms\", \"0.5s\") to milliseconds.\n */\nfunction parseDuration(value: string): number {\n const trimmed = value.trim().toLowerCase();\n if (trimmed.endsWith('ms')) {\n return parseFloat(trimmed);\n }\n if (trimmed.endsWith('s')) {\n return parseFloat(trimmed) * 1000;\n }\n return parseFloat(trimmed);\n}\n\n/**\n * Get the animation duration for a row element.\n * Reads from CSS custom property or falls back to default.\n */\nfunction getAnimationDuration(rowEl: HTMLElement, animationType: RowAnimationType): number {\n const prop = DURATION_PROPS[animationType];\n const computed = getComputedStyle(rowEl).getPropertyValue(prop);\n if (computed) {\n const parsed = parseDuration(computed);\n if (!isNaN(parsed) && parsed > 0) {\n return parsed;\n }\n }\n return DEFAULT_DURATIONS[animationType];\n}\n// #endregion\n\n// #region Public API\n/**\n * Animate a single row element.\n *\n * @param rowEl - The row DOM element to animate\n * @param animationType - The type of animation to apply\n * @param onComplete - Optional callback when animation completes\n */\nexport function animateRowElement(rowEl: HTMLElement, animationType: RowAnimationType, onComplete?: () => void): void {\n // Remove any existing animation first (allows re-triggering)\n rowEl.removeAttribute(ANIMATION_ATTR);\n\n // Force a reflow to restart the animation\n void rowEl.offsetWidth;\n\n // Apply the animation\n rowEl.setAttribute(ANIMATION_ATTR, animationType);\n\n // Get duration and schedule cleanup\n const duration = getAnimationDuration(rowEl, animationType);\n\n setTimeout(() => {\n // For 'remove' animations, skip removing the attribute since the element\n // will be destroyed by the onComplete callback. This prevents a visual\n // flash where the element snaps back to its original state.\n if (animationType !== 'remove') {\n rowEl.removeAttribute(ANIMATION_ATTR);\n }\n onComplete?.();\n }, duration);\n}\n\n/**\n * Animate a row by its data index.\n *\n * @param grid - The grid instance\n * @param rowIndex - The data row index (not DOM position)\n * @param animationType - The type of animation to apply\n * @returns Promise resolving to `true` if the row was found and animated, `false` otherwise\n */\nexport function animateRow<T>(\n grid: InternalGrid<T>,\n rowIndex: number,\n animationType: RowAnimationType,\n): Promise<boolean> {\n // Guard against invalid indices\n if (rowIndex < 0) {\n return Promise.resolve(false);\n }\n\n const rowEl = grid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) {\n // Row is virtualized out of view - nothing to animate\n return Promise.resolve(false);\n }\n\n return new Promise((resolve) => {\n animateRowElement(rowEl, animationType, () => resolve(true));\n });\n}\n\n/**\n * Animate multiple rows by their data indices.\n *\n * @param grid - The grid instance\n * @param rowIndices - Array of data row indices to animate\n * @param animationType - The type of animation to apply\n * @returns Promise resolving to the number of rows actually animated (visible in viewport)\n */\nexport function animateRows<T>(\n grid: InternalGrid<T>,\n rowIndices: number[],\n animationType: RowAnimationType,\n): Promise<number> {\n return Promise.all(rowIndices.map((idx) => animateRow(grid, idx, animationType))).then(\n (results) => results.filter(Boolean).length,\n );\n}\n\n/**\n * Animate a row by its ID.\n *\n * @param grid - The grid instance\n * @param rowId - The row ID (requires getRowId to be configured)\n * @param animationType - The type of animation to apply\n * @returns Promise resolving to `true` if the row was found and animated, `false` otherwise\n */\nexport function animateRowById<T>(\n grid: InternalGrid<T>,\n rowId: string,\n animationType: RowAnimationType,\n): Promise<boolean> {\n // Find row index by searching _rows\n const rows = grid._rows ?? [];\n const getRowId = grid.getRowId;\n if (!getRowId) {\n return Promise.resolve(false);\n }\n\n const rowIndex = rows.findIndex((row) => {\n if (row == null) return false;\n try {\n return getRowId(row) === rowId;\n } catch {\n return false;\n }\n });\n if (rowIndex < 0) {\n return Promise.resolve(false);\n }\n return animateRow(grid, rowIndex, animationType);\n}\n// #endregion\n","/**\n * RowManager — encapsulates row CRUD operations that were previously\n * inline in the DataGridElement class.\n *\n * Owns:\n * - Row ID resolution (tryResolveRowId, resolveRowIdOrThrow)\n * - Row lookup (getRow, getRowEntry)\n * - Row mutation (updateRow, updateRows, insertRow, removeRow)\n *\n * Takes the grid reference directly (tightly coupled — this manager\n * can never live outside the grid).\n */\nimport type { CellChangeDetail, GridHost, UpdateSource } from '../types';\nimport { MISSING_ROW_ID, ROW_NOT_FOUND, throwDiagnostic } from './diagnostics';\nimport { RenderPhase } from './render-scheduler';\nimport { animateRow } from './row-animation';\nimport { invalidateCellCache } from './rows';\n\n// #region Standalone Row ID Helpers\n\n/**\n * Try to resolve the ID for a row using a configured getRowId or fallback.\n * Returns undefined if no ID can be determined (non-throwing).\n *\n * Exported so grid.ts can use it in `#rebuildRowIdMap` without going\n * through the RowManager instance.\n */\nexport function tryResolveRowId<T>(row: T, getRowId?: (row: T) => string): string | undefined {\n if (getRowId) {\n return getRowId(row);\n }\n\n // Fallback: common ID fields\n const r = row as Record<string, unknown>;\n if ('id' in r && r.id != null) return String(r.id);\n if ('_id' in r && r._id != null) return String(r._id);\n\n return undefined;\n}\n\n/**\n * Resolve the ID for a row, throwing if not found.\n * Exported so grid.ts `getRowId()` can call it without the RowManager.\n */\nexport function resolveRowIdOrThrow<T>(row: T, gridId: string, getRowId?: (row: T) => string): string {\n const id = tryResolveRowId(row, getRowId);\n if (id === undefined) {\n throwDiagnostic(\n MISSING_ROW_ID,\n 'Cannot determine row ID. ' + 'Configure getRowId in gridConfig or ensure rows have an \"id\" property.',\n gridId,\n );\n }\n return id;\n}\n\n// #endregion\n\n// #region RowManager\n\nexport class RowManager<T = any> {\n readonly #grid: GridHost<T>;\n\n constructor(grid: GridHost<T>) {\n this.#grid = grid;\n }\n\n // --- Row ID resolution ---\n\n resolveRowId(row: T): string {\n return resolveRowIdOrThrow(row, this.#grid.id, this.#grid.effectiveConfig?.getRowId);\n }\n\n // --- Row lookup ---\n\n getRow(id: string): T | undefined {\n return this.#grid._getRowEntry(id)?.row;\n }\n\n getRowEntry(id: string): { row: T; index: number } | undefined {\n return this.#grid._getRowEntry(id);\n }\n\n // --- Row updates ---\n\n updateRow(id: string, changes: Partial<T>, source: UpdateSource = 'api'): void {\n const grid = this.#grid;\n const entry = grid._getRowEntry(id);\n if (!entry) {\n throwDiagnostic(\n ROW_NOT_FOUND,\n `Row with ID \"${id}\" not found. ` + `Ensure the row exists and getRowId is correctly configured.`,\n grid.id,\n );\n }\n\n const { row, index } = entry;\n const changedFields: Array<{ field: string; oldValue: unknown; newValue: unknown }> = [];\n\n // Compute changes and apply in-place\n for (const [field, newValue] of Object.entries(changes)) {\n const oldValue = (row as Record<string, unknown>)[field];\n if (oldValue !== newValue) {\n changedFields.push({ field, oldValue, newValue });\n (row as Record<string, unknown>)[field] = newValue;\n }\n }\n\n // Emit cell-change for each changed field\n for (const { field, oldValue, newValue } of changedFields) {\n grid.dispatchEvent(\n new CustomEvent('cell-change', {\n detail: {\n row,\n rowId: id,\n rowIndex: index,\n field,\n oldValue,\n newValue,\n changes,\n source,\n } as CellChangeDetail<T>,\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n // Schedule re-render if anything changed.\n // Use VIRTUALIZATION (not ROWS) so the visible cells are re-rendered\n // without rebuilding the row model. A ROWS-phase rebuild re-applies\n // sort/filter from #rows, which moves rows inserted via insertRow()\n // to their sorted position — appearing as \"ghost\" duplicates.\n // Since data was already mutated in-place, fastPatchRow will pick up\n // the new values from the row object directly.\n if (changedFields.length > 0) {\n invalidateCellCache(grid);\n grid._requestSchedulerPhase(RenderPhase.VIRTUALIZATION, 'updateRow');\n grid._emitDataChange();\n }\n }\n\n updateRows(updates: Array<{ id: string; changes: Partial<T> }>, source: UpdateSource = 'api'): void {\n const grid = this.#grid;\n let anyChanged = false;\n\n for (const { id, changes } of updates) {\n const entry = grid._getRowEntry(id);\n if (!entry) {\n throwDiagnostic(\n ROW_NOT_FOUND,\n `Row with ID \"${id}\" not found. ` + `Ensure the row exists and getRowId is correctly configured.`,\n grid.id,\n );\n }\n\n const { row, index } = entry;\n\n // Compute changes and apply in-place\n for (const [field, newValue] of Object.entries(changes)) {\n const oldValue = (row as Record<string, unknown>)[field];\n if (oldValue !== newValue) {\n anyChanged = true;\n (row as Record<string, unknown>)[field] = newValue;\n\n // Emit cell-change for each changed field\n grid.dispatchEvent(\n new CustomEvent('cell-change', {\n detail: {\n row,\n rowId: id,\n rowIndex: index,\n field,\n oldValue,\n newValue,\n changes,\n source,\n } as CellChangeDetail<T>,\n bubbles: true,\n composed: true,\n }),\n );\n }\n }\n }\n\n // Schedule single re-render for all changes.\n // Use VIRTUALIZATION (not ROWS) — see updateRow for rationale.\n if (anyChanged) {\n invalidateCellCache(grid);\n grid._requestSchedulerPhase(RenderPhase.VIRTUALIZATION, 'updateRows');\n grid._emitDataChange();\n }\n }\n\n // --- Row mutation ---\n\n async insertRow(index: number, row: T, animate = true): Promise<void> {\n const grid = this.#grid;\n\n // Clamp index to valid range\n const idx = Math.max(0, Math.min(index, grid._rows.length));\n\n // Add to source data (position irrelevant — pipeline will re-sort later)\n grid.sourceRows = [...grid.sourceRows, row];\n\n // Insert into processed view at the exact visible position\n const newRows = [...grid._rows];\n newRows.splice(idx, 0, row);\n grid._rows = newRows;\n\n // Keep __originalOrder in sync so \"clear sort\" includes the new row\n if (grid._sortState) {\n grid.__originalOrder = [...grid.__originalOrder, row];\n }\n\n // Refresh caches and trigger immediate re-render\n invalidateCellCache(grid);\n grid._rebuildRowIdMap();\n grid.__rowRenderEpoch++;\n for (const r of grid._rowPool) r.__epoch = -1;\n grid.refreshVirtualWindow(true);\n\n // Notify plugins about the inserted row (e.g., editing dirty tracking)\n grid._emitPluginEvent('row-inserted', { row, index: idx });\n\n grid._emitDataChange();\n\n if (animate) {\n await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()));\n await animateRow(grid, idx, 'insert');\n }\n }\n\n async removeRow(index: number, animate = true): Promise<T | undefined> {\n const grid = this.#grid;\n const row = grid._rows[index];\n if (!row) return undefined;\n\n if (animate) {\n await animateRow(grid, index, 'remove');\n }\n\n // Find current position by reference (may have shifted during animation)\n const currentIdx = grid._rows.indexOf(row);\n if (currentIdx < 0) return row; // Already removed by something else\n\n // Remove from processed view\n const newRows = [...grid._rows];\n newRows.splice(currentIdx, 1);\n grid._rows = newRows;\n\n // Remove from source data\n const srcIdx = grid.sourceRows.indexOf(row);\n if (srcIdx >= 0) {\n const newSource = [...grid.sourceRows];\n newSource.splice(srcIdx, 1);\n grid.sourceRows = newSource;\n }\n\n // Keep __originalOrder in sync\n if (grid._sortState) {\n const origIdx = grid.__originalOrder.indexOf(row);\n if (origIdx >= 0) {\n const newOrig = [...grid.__originalOrder];\n newOrig.splice(origIdx, 1);\n grid.__originalOrder = newOrig;\n }\n }\n\n // Refresh caches and trigger immediate re-render\n invalidateCellCache(grid);\n grid._rebuildRowIdMap();\n grid.__rowRenderEpoch++;\n for (const r of grid._rowPool) r.__epoch = -1;\n grid.refreshVirtualWindow(true);\n\n grid._emitDataChange();\n\n // Clean up stale remove animation attributes after re-render\n if (animate) {\n requestAnimationFrame(() => {\n grid.querySelectorAll('[data-animating=\"remove\"]').forEach((el) => {\n el.removeAttribute('data-animating');\n });\n });\n }\n\n return row;\n }\n}\n\n// #endregion\n","/**\n * DOM Builder - Direct DOM construction for performance.\n *\n * Using direct DOM APIs instead of innerHTML is significantly faster:\n * - No HTML parsing by the browser\n * - No template string concatenation\n * - Immediate element creation without serialization/deserialization\n *\n * Benchmark: DOM construction is ~2-3x faster than innerHTML for complex structures.\n */\n\n// #region Element Factories\n/**\n * Create an element with attributes and optional children.\n * Optimized helper that avoids repeated function calls.\n */\nexport function createElement<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n attrs?: Record<string, string>,\n children?: (Node | string | null | undefined)[],\n): HTMLElementTagNameMap[K] {\n const el = document.createElement(tag);\n\n if (attrs) {\n for (const key in attrs) {\n const value = attrs[key];\n if (value !== undefined && value !== null) {\n el.setAttribute(key, value);\n }\n }\n }\n\n if (children) {\n for (const child of children) {\n if (child == null) continue;\n if (typeof child === 'string') {\n el.appendChild(document.createTextNode(child));\n } else {\n el.appendChild(child);\n }\n }\n }\n\n return el;\n}\n\n/**\n * Create a text node (shorthand).\n */\nexport function text(content: string): Text {\n return document.createTextNode(content);\n}\n\n/**\n * Create an element with class (common pattern).\n */\nexport function div(className?: string, attrs?: Record<string, string>): HTMLDivElement {\n const el = document.createElement('div');\n if (className) el.className = className;\n if (attrs) {\n for (const key in attrs) {\n const value = attrs[key];\n if (value !== undefined && value !== null) {\n el.setAttribute(key, value);\n }\n }\n }\n return el;\n}\n\n/**\n * Create a button element.\n */\nexport function button(className?: string, attrs?: Record<string, string>, content?: string | Node): HTMLButtonElement {\n const el = document.createElement('button');\n if (className) el.className = className;\n if (attrs) {\n for (const key in attrs) {\n const value = attrs[key];\n if (value !== undefined && value !== null) {\n el.setAttribute(key, value);\n }\n }\n }\n if (content) {\n if (typeof content === 'string') {\n el.textContent = content;\n } else {\n el.appendChild(content);\n }\n }\n return el;\n}\n\n/**\n * Append multiple children to a parent element.\n */\nexport function appendChildren(parent: Element, children: (Node | null | undefined)[]): void {\n for (const child of children) {\n if (child) parent.appendChild(child);\n }\n}\n\n/**\n * Set multiple attributes on an element.\n */\nexport function setAttrs(el: Element, attrs: Record<string, string | undefined>): void {\n for (const key in attrs) {\n const value = attrs[key];\n if (value !== undefined && value !== null) {\n el.setAttribute(key, value);\n }\n }\n}\n// #endregion\n\n// #region Grid Templates\n/**\n * Template for grid content (the core scrollable grid area).\n * Pre-built once, then cloned for each grid instance.\n */\nconst gridContentTemplate = document.createElement('template');\ngridContentTemplate.innerHTML = `\n <div class=\"tbw-scroll-area\">\n <div class=\"rows-body-wrapper\">\n <div class=\"rows-body\" role=\"grid\">\n <div class=\"header\" role=\"rowgroup\">\n <div class=\"header-row\" role=\"row\" part=\"header-row\"></div>\n </div>\n <div class=\"rows-container\" role=\"presentation\">\n <div class=\"rows-viewport\" role=\"presentation\">\n <div class=\"rows\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"faux-vscroll\">\n <div class=\"faux-vscroll-spacer\"></div>\n </div>\n <div class=\"tbw-sr-only\" aria-live=\"polite\" aria-atomic=\"true\"></div>\n`;\n\n/**\n * Clone the grid content structure.\n * Using template cloning is faster than createElement for nested structures.\n */\nexport function cloneGridContent(): DocumentFragment {\n return gridContentTemplate.content.cloneNode(true) as DocumentFragment;\n}\n// #endregion\n\n// #region Grid DOM Building\n/**\n * Build the grid root structure using direct DOM construction.\n * This is called once per grid instance during initial render.\n */\nexport interface GridDOMOptions {\n hasShell: boolean;\n /** Shell header element (pre-built) */\n shellHeader?: Element;\n /** Shell body element with tool panel (pre-built) */\n shellBody?: Element;\n}\n\n/**\n * Build the complete grid DOM structure.\n * Returns a DocumentFragment ready to be appended to shadow root.\n */\nexport function buildGridDOM(options: GridDOMOptions): DocumentFragment {\n const fragment = document.createDocumentFragment();\n\n const root = div(options.hasShell ? 'tbw-grid-root has-shell' : 'tbw-grid-root');\n\n if (options.hasShell && options.shellHeader && options.shellBody) {\n // Shell mode: header + body (with grid content inside)\n root.appendChild(options.shellHeader);\n root.appendChild(options.shellBody);\n } else {\n // No shell: just grid content in wrapper\n const contentWrapper = div('tbw-grid-content');\n contentWrapper.appendChild(cloneGridContent());\n root.appendChild(contentWrapper);\n }\n\n fragment.appendChild(root);\n return fragment;\n}\n// #endregion\n\n// #region Shell Header\n/**\n * Build shell header using direct DOM construction.\n *\n * Note: The grid no longer creates buttons from config (icon/action).\n * Config/API buttons only use element or render function.\n * Light DOM buttons are slotted directly.\n * The ONLY button the grid creates is the panel toggle.\n */\nexport interface ShellHeaderOptions {\n title?: string;\n hasPanels: boolean;\n isPanelOpen: boolean;\n toolPanelIcon: string;\n /** Config toolbar contents with render function (pre-sorted by order) */\n configButtons: Array<{\n id: string;\n hasRender?: boolean;\n }>;\n /** API toolbar contents with render function (pre-sorted by order) */\n apiButtons: Array<{\n id: string;\n hasRender?: boolean;\n }>;\n}\n\n/**\n * Build shell header element directly without innerHTML.\n */\nexport function buildShellHeader(options: ShellHeaderOptions): HTMLDivElement {\n const header = div('tbw-shell-header', { part: 'shell-header', role: 'presentation' });\n\n // Title\n if (options.title) {\n const titleEl = div('tbw-shell-title');\n titleEl.textContent = options.title;\n header.appendChild(titleEl);\n }\n\n // Shell content with placeholder for light DOM header content\n const content = div('tbw-shell-content', {\n part: 'shell-content',\n role: 'presentation',\n 'data-light-dom-header-content': '',\n });\n header.appendChild(content);\n\n // Toolbar\n const toolbar = div('tbw-shell-toolbar', { part: 'shell-toolbar', role: 'presentation' });\n\n // Placeholders for config toolbar contents with render function\n for (const btn of options.configButtons) {\n if (btn.hasRender) {\n toolbar.appendChild(div('tbw-toolbar-content-slot', { 'data-toolbar-content': btn.id }));\n }\n }\n // Placeholders for API toolbar contents with render function\n for (const btn of options.apiButtons) {\n if (btn.hasRender) {\n toolbar.appendChild(div('tbw-toolbar-content-slot', { 'data-toolbar-content': btn.id }));\n }\n }\n\n // Separator between custom content and panel toggle\n const hasCustomContent =\n options.configButtons.some((b) => b.hasRender) || options.apiButtons.some((b) => b.hasRender);\n if (hasCustomContent && options.hasPanels) {\n toolbar.appendChild(div('tbw-toolbar-separator'));\n }\n\n // Panel toggle button\n if (options.hasPanels) {\n const toggleBtn = button(options.isPanelOpen ? 'tbw-toolbar-btn active' : 'tbw-toolbar-btn', {\n 'data-panel-toggle': '',\n title: 'Settings',\n 'aria-label': 'Toggle settings panel',\n 'aria-pressed': String(options.isPanelOpen),\n 'aria-controls': 'tbw-tool-panel',\n });\n toggleBtn.innerHTML = options.toolPanelIcon;\n toolbar.appendChild(toggleBtn);\n }\n\n header.appendChild(toolbar);\n return header;\n}\n// #endregion\n\n// #region Shell Body\n/**\n * Build shell body (grid content + optional tool panel).\n */\nexport interface ShellBodyOptions {\n position: 'left' | 'right';\n isPanelOpen: boolean;\n expandIcon: string;\n collapseIcon: string;\n /** Sorted panels for accordion */\n panels: Array<{\n id: string;\n title: string;\n icon?: string;\n isExpanded: boolean;\n }>;\n}\n\n/**\n * Build shell body element directly without innerHTML.\n */\nexport function buildShellBody(options: ShellBodyOptions): HTMLDivElement {\n const body = div('tbw-shell-body');\n const hasPanel = options.panels.length > 0;\n const isSinglePanel = options.panels.length === 1;\n\n // Grid content wrapper with cloned grid structure\n const gridContent = div('tbw-grid-content');\n gridContent.appendChild(cloneGridContent());\n\n // Tool panel\n let panelEl: HTMLElement | null = null;\n if (hasPanel) {\n panelEl = createElement('aside', {\n class: options.isPanelOpen ? 'tbw-tool-panel open' : 'tbw-tool-panel',\n part: 'tool-panel',\n 'data-position': options.position,\n role: 'presentation',\n id: 'tbw-tool-panel',\n });\n\n // Resize handle\n const resizeHandlePosition = options.position === 'left' ? 'right' : 'left';\n panelEl.appendChild(\n div('tbw-tool-panel-resize', {\n 'data-resize-handle': '',\n 'data-handle-position': resizeHandlePosition,\n 'aria-hidden': 'true',\n }),\n );\n\n // Panel content with accordion\n const panelContent = div('tbw-tool-panel-content', { role: 'presentation' });\n const accordion = div('tbw-accordion');\n\n for (const panel of options.panels) {\n const sectionClasses = `tbw-accordion-section${panel.isExpanded ? ' expanded' : ''}${isSinglePanel ? ' single' : ''}`;\n const section = div(sectionClasses, { 'data-section': panel.id });\n\n // Accordion header button\n const headerBtn = button('tbw-accordion-header', {\n 'aria-expanded': String(panel.isExpanded),\n 'aria-controls': `tbw-section-${panel.id}`,\n });\n if (isSinglePanel) headerBtn.setAttribute('aria-disabled', 'true');\n\n // Icon\n if (panel.icon) {\n const iconSpan = createElement('span', { class: 'tbw-accordion-icon' });\n iconSpan.innerHTML = panel.icon;\n headerBtn.appendChild(iconSpan);\n }\n\n // Title\n const titleSpan = createElement('span', { class: 'tbw-accordion-title' });\n titleSpan.textContent = panel.title;\n headerBtn.appendChild(titleSpan);\n\n // Chevron (hidden for single panel) — always use expandIcon, CSS rotation handles state\n if (!isSinglePanel) {\n const chevronSpan = createElement('span', { class: 'tbw-accordion-chevron' });\n chevronSpan.innerHTML = options.expandIcon;\n headerBtn.appendChild(chevronSpan);\n }\n\n section.appendChild(headerBtn);\n\n // Accordion content (empty, will be filled by panel render functions)\n section.appendChild(\n div('tbw-accordion-content', {\n id: `tbw-section-${panel.id}`,\n role: 'presentation',\n }),\n );\n\n accordion.appendChild(section);\n }\n\n panelContent.appendChild(accordion);\n panelEl.appendChild(panelContent);\n }\n\n // Append in correct order based on position\n if (options.position === 'left' && panelEl) {\n body.appendChild(panelEl);\n body.appendChild(gridContent);\n } else {\n body.appendChild(gridContent);\n if (panelEl) body.appendChild(panelEl);\n }\n\n return body;\n}\n// #endregion\n","/**\n * Shell infrastructure for grid header bar and tool panels.\n *\n * The shell is an optional wrapper layer that provides:\n * - Header bar with title, plugin content, and toolbar buttons\n * - Tool panels that plugins can register content into\n * - Light DOM parsing for framework-friendly configuration\n */\n\nimport type {\n HeaderContentDefinition,\n IconValue,\n InternalGrid,\n ShellConfig,\n ToolbarContentDefinition,\n ToolPanelDefinition,\n} from '../types';\nimport { DEFAULT_GRID_ICONS } from '../types';\nimport {\n HEADER_CONTENT_DUPLICATE,\n NO_TOOL_PANELS,\n TOOL_PANEL_DUPLICATE,\n TOOL_PANEL_MISSING_ATTR,\n TOOL_PANEL_NOT_FOUND,\n TOOLBAR_CONTENT_DUPLICATE,\n warnDiagnostic,\n} from './diagnostics';\nimport { escapeHtml } from './sanitize';\n\n// #region Types & State\n/**\n * Convert an IconValue to a string for rendering in HTML.\n */\nfunction iconToString(icon: IconValue | undefined): string {\n if (!icon) return '';\n if (typeof icon === 'string') return icon;\n // For HTMLElement, get the outerHTML\n return icon.outerHTML;\n}\n\n/**\n * State for managing shell UI.\n *\n * This interface holds both configuration-like properties (toolPanels, headerContents)\n * and runtime state (isPanelOpen, expandedSections). The Maps allow for efficient\n * registration/unregistration of panels and content.\n */\nexport interface ShellState {\n /** Registered tool panels (from plugins + consumer API) */\n toolPanels: Map<string, ToolPanelDefinition>;\n /** Registered header content (from plugins + consumer API) */\n headerContents: Map<string, HeaderContentDefinition>;\n /** Toolbar content registered via API or light DOM */\n toolbarContents: Map<string, ToolbarContentDefinition>;\n /** Whether a <tbw-grid-tool-buttons> container was found in light DOM */\n hasToolButtonsContainer: boolean;\n /** Light DOM header content elements */\n lightDomHeaderContent: HTMLElement[];\n /** Light DOM header title from <tbw-grid-header title=\"...\"> */\n lightDomTitle: string | null;\n /** IDs of tool panels registered from light DOM (to avoid re-parsing) */\n lightDomToolPanelIds: Set<string>;\n /** IDs of toolbar content registered from light DOM (to avoid re-parsing) */\n lightDomToolbarContentIds: Set<string>;\n /** IDs of tool panels registered via registerToolPanel API */\n apiToolPanelIds: Set<string>;\n /** IDs of header content registered via registerHeaderContent API */\n apiHeaderContentIds: Set<string>;\n /** Whether the tool panel sidebar is open */\n isPanelOpen: boolean;\n /** Which accordion sections are expanded (by panel ID) */\n expandedSections: Set<string>;\n /** Whether light DOM header content has been moved to placeholder (perf optimization) */\n lightDomContentMoved: boolean;\n /** Cleanup functions for header content render returns */\n headerContentCleanups: Map<string, () => void>;\n /** Cleanup functions for each panel section's render return */\n panelCleanups: Map<string, () => void>;\n /** Cleanup functions for toolbar content render returns */\n toolbarContentCleanups: Map<string, () => void>;\n}\n\n/**\n * Runtime-only shell state (not configuration).\n *\n * Configuration (toolPanels, headerContents, toolbarContents, title) lives in\n * effectiveConfig.shell. This state holds runtime UI state and cleanup functions.\n */\nexport interface ShellRuntimeState {\n /** Whether the tool panel sidebar is currently open */\n isPanelOpen: boolean;\n /** Which accordion sections are currently expanded (by panel ID) */\n expandedSections: Set<string>;\n /** Cleanup functions for header content render returns */\n headerContentCleanups: Map<string, () => void>;\n /** Cleanup functions for each panel section's render return */\n panelCleanups: Map<string, () => void>;\n /** Cleanup functions for toolbar content render returns */\n toolbarContentCleanups: Map<string, () => void>;\n /** IDs of tool panels registered from light DOM (to avoid re-parsing) */\n lightDomToolPanelIds: Set<string>;\n /** IDs of tool panels registered via registerToolPanel API */\n apiToolPanelIds: Set<string>;\n /** Whether a <tbw-grid-tool-buttons> container was found in light DOM */\n hasToolButtonsContainer: boolean;\n}\n\n/**\n * Create initial shell runtime state.\n */\nexport function createShellRuntimeState(): ShellRuntimeState {\n return {\n isPanelOpen: false,\n expandedSections: new Set(),\n headerContentCleanups: new Map(),\n panelCleanups: new Map(),\n toolbarContentCleanups: new Map(),\n lightDomToolPanelIds: new Set(),\n apiToolPanelIds: new Set(),\n hasToolButtonsContainer: false,\n };\n}\n\n/**\n * Create initial shell state.\n */\nexport function createShellState(): ShellState {\n return {\n toolPanels: new Map(),\n headerContents: new Map(),\n toolbarContents: new Map(),\n hasToolButtonsContainer: false,\n lightDomHeaderContent: [],\n lightDomTitle: null,\n lightDomToolPanelIds: new Set(),\n lightDomToolbarContentIds: new Set(),\n apiToolPanelIds: new Set(),\n apiHeaderContentIds: new Set(),\n isPanelOpen: false,\n expandedSections: new Set(),\n headerContentCleanups: new Map(),\n panelCleanups: new Map(),\n toolbarContentCleanups: new Map(),\n lightDomContentMoved: false,\n };\n}\n// #endregion\n\n// #region Render Functions\n/**\n * Determine if shell header should be rendered.\n * Reads only from effectiveConfig.shell (single source of truth).\n */\nexport function shouldRenderShellHeader(config: ShellConfig | undefined): boolean {\n // Check if title is configured\n if (config?.header?.title) return true;\n\n // Check if config has toolbar contents\n if (config?.header?.toolbarContents?.length) return true;\n\n // Check if any tool panels are registered\n if (config?.toolPanels?.length) return true;\n\n // Check if any header content is registered\n if (config?.headerContents?.length) return true;\n\n // Check if light DOM has header elements\n if (config?.header?.lightDomContent?.length) return true;\n\n // Check if a toolbar buttons container was found\n if (config?.header?.hasToolButtonsContainer) return true;\n\n return false;\n}\n\n/**\n * Render the shell header HTML.\n *\n * Toolbar contents come from two sources:\n * 1. Light DOM slot (users provide their own HTML in <tbw-grid-tool-buttons>)\n * 2. Config/API with render function (programmatic insertion)\n *\n * Users have full control over toolbar HTML, styling, and behavior.\n * The only button the grid creates is the tool panel toggle.\n *\n * @param toolPanelIcon - Icon for the tool panel toggle (from grid icon config)\n */\nexport function renderShellHeader(\n config: ShellConfig | undefined,\n state: ShellState,\n toolPanelIcon: IconValue = '☰',\n): string {\n const title = config?.header?.title ?? state.lightDomTitle ?? '';\n const hasTitle = !!title;\n const iconStr = iconToString(toolPanelIcon);\n\n // Get all toolbar contents from effectiveConfig (already merged: config + API + light DOM)\n // The config-manager merges state.toolbarContents into effectiveConfig.shell.header.toolbarContents\n // Also include state.toolbarContents directly for cases where renderShellHeader is called\n // before config-manager has merged (e.g., unit tests, initial render)\n const configContents = config?.header?.toolbarContents ?? [];\n const stateContents = [...state.toolbarContents.values()];\n\n // Merge: use config contents, add state contents that aren't in config\n const configIds = new Set(configContents.map((c) => c.id));\n const allContents = [...configContents];\n for (const content of stateContents) {\n if (!configIds.has(content.id)) {\n allContents.push(content);\n }\n }\n\n const hasCustomContent = allContents.length > 0;\n const hasPanels = state.toolPanels.size > 0;\n const showSeparator = hasCustomContent && hasPanels;\n\n // Sort contents by order for slot placement\n const sortedContents = [...allContents].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n\n // Build toolbar HTML\n let toolbarHtml = '';\n\n // Create slots for all contents (unified: config + API + light DOM)\n for (const content of sortedContents) {\n toolbarHtml += `<div class=\"tbw-toolbar-content-slot\" data-toolbar-content=\"${content.id}\"></div>`;\n }\n\n // Separator between custom content and panel toggle\n if (showSeparator) {\n toolbarHtml += '<div class=\"tbw-toolbar-separator\"></div>';\n }\n\n // Single panel toggle button (the ONLY button the grid creates)\n if (hasPanels) {\n const isOpen = state.isPanelOpen;\n const toggleClass = isOpen ? 'tbw-toolbar-btn active' : 'tbw-toolbar-btn';\n toolbarHtml += `<button class=\"${toggleClass}\" data-panel-toggle title=\"Settings\" aria-label=\"Toggle settings panel\" aria-pressed=\"${isOpen}\" aria-controls=\"tbw-tool-panel\">${iconStr}</button>`;\n }\n\n return `\n <div class=\"tbw-shell-header\" part=\"shell-header\" role=\"presentation\">\n ${hasTitle ? `<div class=\"tbw-shell-title\">${escapeHtml(title)}</div>` : ''}\n <div class=\"tbw-shell-content\" part=\"shell-content\" role=\"presentation\" data-light-dom-header-content></div>\n <div class=\"tbw-shell-toolbar\" part=\"shell-toolbar\" role=\"presentation\">\n ${toolbarHtml}\n </div>\n </div>\n `;\n}\n\n/**\n * Render the shell body wrapper HTML (contains grid content + accordion-style tool panel).\n * @param icons - Optional icons for expand/collapse chevrons (from grid config)\n */\nexport function renderShellBody(\n config: ShellConfig | undefined,\n state: ShellState,\n gridContentHtml: string,\n icons?: { expand?: IconValue; collapse?: IconValue },\n): string {\n const position = config?.toolPanel?.position ?? 'right';\n const hasPanel = state.toolPanels.size > 0;\n const isOpen = state.isPanelOpen;\n const expandIcon = iconToString(icons?.expand ?? DEFAULT_GRID_ICONS.expand);\n\n // Sort panels by order for accordion sections\n const sortedPanels = [...state.toolPanels.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n const isSinglePanel = sortedPanels.length === 1;\n\n // Build accordion sections HTML\n let accordionHtml = '';\n for (const panel of sortedPanels) {\n const isExpanded = state.expandedSections.has(panel.id);\n const iconHtml = panel.icon ? `<span class=\"tbw-accordion-icon\">${panel.icon}</span>` : '';\n // Hide chevron for single panel (no toggling needed)\n // Always use expandIcon (▶) — CSS rotation handles the expanded state\n const chevronHtml = isSinglePanel ? '' : `<span class=\"tbw-accordion-chevron\">${expandIcon}</span>`;\n // Disable accordion toggle for single panel\n const sectionClasses = `tbw-accordion-section${isExpanded ? ' expanded' : ''}${isSinglePanel ? ' single' : ''}`;\n accordionHtml += `\n <div class=\"${sectionClasses}\" data-section=\"${panel.id}\">\n <button class=\"tbw-accordion-header\" aria-expanded=\"${isExpanded}\" aria-controls=\"tbw-section-${panel.id}\"${isSinglePanel ? ' aria-disabled=\"true\"' : ''}>\n ${iconHtml}\n <span class=\"tbw-accordion-title\">${panel.title}</span>\n ${chevronHtml}\n </button>\n <div class=\"tbw-accordion-content\" id=\"tbw-section-${panel.id}\" role=\"presentation\"></div>\n </div>\n `;\n }\n\n // Resize handle position depends on panel position\n const resizeHandlePosition = position === 'left' ? 'right' : 'left';\n\n const panelHtml = hasPanel\n ? `\n <aside class=\"tbw-tool-panel${isOpen ? ' open' : ''}\" part=\"tool-panel\" data-position=\"${position}\" role=\"presentation\" id=\"tbw-tool-panel\">\n <div class=\"tbw-tool-panel-resize\" data-resize-handle data-handle-position=\"${resizeHandlePosition}\" aria-hidden=\"true\"></div>\n <div class=\"tbw-tool-panel-content\" role=\"presentation\">\n <div class=\"tbw-accordion\">\n ${accordionHtml}\n </div>\n </div>\n </aside>\n `\n : '';\n\n // For left position, panel comes before content in DOM order\n if (position === 'left') {\n return `\n <div class=\"tbw-shell-body\">\n ${panelHtml}\n <div class=\"tbw-grid-content\">\n ${gridContentHtml}\n </div>\n </div>\n `;\n }\n\n return `\n <div class=\"tbw-shell-body\">\n <div class=\"tbw-grid-content\">\n ${gridContentHtml}\n </div>\n ${panelHtml}\n </div>\n `;\n}\n// #endregion\n\n// #region Light DOM Parsing\n/**\n * Parse light DOM shell elements (tbw-grid-header, etc.).\n * Safe to call multiple times - will only parse once when elements are available.\n */\nexport function parseLightDomShell(host: HTMLElement, state: ShellState): void {\n const headerEl = host.querySelector('tbw-grid-header');\n if (!headerEl) return;\n\n // Parse title attribute (only if not already parsed)\n if (!state.lightDomTitle) {\n const title = headerEl.getAttribute('title');\n if (title) {\n state.lightDomTitle = title;\n }\n }\n\n // Parse header content elements - store references but don't set slot (light DOM doesn't use slots)\n const headerContents = headerEl.querySelectorAll('tbw-grid-header-content');\n if (headerContents.length > 0 && state.lightDomHeaderContent.length === 0) {\n state.lightDomHeaderContent = Array.from(headerContents) as HTMLElement[];\n }\n\n // Hide the light DOM header container (it was just for declarative config)\n (headerEl as HTMLElement).style.display = 'none';\n}\n\n/**\n * Callback type for creating a toolbar content renderer from a light DOM container.\n * This is used by framework adapters (Angular, React, etc.) to create renderers\n * from their template syntax.\n */\nexport type ToolbarContentRendererFactory = (\n container: HTMLElement,\n) => ((target: HTMLElement) => void | (() => void)) | undefined;\n\n/**\n * Parse toolbar buttons container element (<tbw-grid-tool-buttons>).\n * This is a content container - we don't parse individual children.\n * The entire container content is registered as a single toolbar content entry.\n *\n * Example:\n * ```html\n * <tbw-grid>\n * <tbw-grid-tool-buttons>\n * <button>My button</button>\n * <button>My other button</button>\n * </tbw-grid-tool-buttons>\n * </tbw-grid>\n * ```\n *\n * The container's children are moved to the toolbar area during render.\n * We treat this as opaque content - users control what goes inside.\n *\n * @param host - The grid host element\n * @param state - Shell state to update\n * @param rendererFactory - Optional factory for creating renderers (used by framework adapters)\n */\nexport function parseLightDomToolButtons(\n host: HTMLElement,\n state: ShellState,\n rendererFactory?: ToolbarContentRendererFactory,\n): void {\n // Look for the toolbar buttons container element\n const toolButtonsContainer = host.querySelector(':scope > tbw-grid-tool-buttons') as HTMLElement | null;\n if (!toolButtonsContainer) return;\n\n // Mark that we found the container (for shouldRenderShellHeader)\n state.hasToolButtonsContainer = true;\n\n // Skip if already registered\n const id = 'light-dom-toolbar-content';\n if (state.lightDomToolbarContentIds.has(id)) return;\n\n // Register as a single content entry with a render function\n const adapterRenderer = rendererFactory?.(toolButtonsContainer);\n\n const contentDef: ToolbarContentDefinition = {\n id,\n order: 0, // Light DOM content comes first\n render:\n adapterRenderer ??\n ((target: HTMLElement) => {\n // Move all children from the light DOM container to the target\n while (toolButtonsContainer.firstChild) {\n target.appendChild(toolButtonsContainer.firstChild);\n }\n // Return cleanup that moves children back to original container\n // This preserves them across full re-renders that destroy the slot\n return () => {\n while (target.firstChild) {\n toolButtonsContainer.appendChild(target.firstChild);\n }\n };\n }),\n };\n\n state.toolbarContents.set(id, contentDef);\n state.lightDomToolbarContentIds.add(id);\n\n // Hide the original container\n toolButtonsContainer.style.display = 'none';\n}\n\n/**\n * Callback type for creating a tool panel renderer from a light DOM element.\n * This is used by framework adapters (Angular, React, etc.) to create renderers\n * from their template syntax.\n */\nexport type ToolPanelRendererFactory = (\n element: HTMLElement,\n) => ((container: HTMLElement) => void | (() => void)) | undefined;\n\n/**\n * Parse light DOM tool panel elements (<tbw-grid-tool-panel>).\n * These can appear as direct children of <tbw-grid> for declarative tool panel configuration.\n *\n * Attributes:\n * - `id` (required): Unique panel identifier\n * - `title` (required): Panel title shown in accordion header\n * - `icon`: Icon for accordion section header (emoji or text)\n * - `tooltip`: Tooltip for accordion section header\n * - `order`: Panel order priority (lower = first, default: 100)\n *\n * For vanilla JS, the element's innerHTML is used as the panel content.\n * For framework adapters, the adapter can provide a custom renderer factory.\n *\n * @param host - The grid host element\n * @param state - Shell state to update\n * @param rendererFactory - Optional factory for creating renderers (used by framework adapters)\n */\nexport function parseLightDomToolPanels(\n host: HTMLElement,\n state: ShellState,\n rendererFactory?: ToolPanelRendererFactory,\n): void {\n const toolPanelElements = host.querySelectorAll(':scope > tbw-grid-tool-panel');\n\n toolPanelElements.forEach((element) => {\n const panelEl = element as HTMLElement;\n const id = panelEl.getAttribute('id');\n const title = panelEl.getAttribute('title');\n\n // Skip if required attributes are missing\n if (!id || !title) {\n warnDiagnostic(\n TOOL_PANEL_MISSING_ATTR,\n `Tool panel missing required id or title attribute: id=\"${id ?? ''}\", title=\"${title ?? ''}\"`,\n );\n return;\n }\n\n const icon = panelEl.getAttribute('icon') ?? undefined;\n const tooltip = panelEl.getAttribute('tooltip') ?? undefined;\n const order = parseInt(panelEl.getAttribute('order') ?? '100', 10);\n\n // Try framework adapter first, then fall back to innerHTML\n let render: (container: HTMLElement) => void | (() => void);\n\n const adapterRenderer = rendererFactory?.(panelEl);\n if (adapterRenderer) {\n render = adapterRenderer;\n } else {\n // Vanilla fallback: use innerHTML as static content\n const content = panelEl.innerHTML.trim();\n render = (container: HTMLElement) => {\n const wrapper = document.createElement('div');\n wrapper.innerHTML = content;\n container.appendChild(wrapper);\n return () => wrapper.remove();\n };\n }\n\n // Check if panel was already parsed\n const existingPanel = state.toolPanels.get(id);\n\n // If already parsed and we have an adapter renderer, update the render function\n // and re-read attributes from DOM (Angular may have updated them after initial parse)\n // This handles the case where Angular templates register after initial parsing\n if (existingPanel) {\n if (adapterRenderer) {\n // Update render function with framework adapter renderer\n existingPanel.render = render;\n\n // Re-read attributes from DOM - framework may have set them after initial parse\n // (e.g., Angular directive sets attributes in an effect after template is available)\n existingPanel.order = order;\n existingPanel.icon = icon;\n existingPanel.tooltip = tooltip;\n // Note: title and id are required and shouldn't change\n\n // Clear existing cleanup to force re-render with new renderer\n const cleanup = state.panelCleanups.get(id);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(id);\n }\n }\n return;\n }\n\n // Register the tool panel\n const panel: ToolPanelDefinition = {\n id,\n title,\n icon,\n tooltip,\n order,\n render,\n };\n\n state.toolPanels.set(id, panel);\n state.lightDomToolPanelIds.add(id);\n\n // Hide the light DOM element\n panelEl.style.display = 'none';\n });\n}\n// #endregion\n\n// #region Event Handlers\n/**\n * Set up event listeners for shell toolbar buttons and accordion.\n */\nexport function setupShellEventListeners(\n renderRoot: Element,\n config: ShellConfig | undefined,\n state: ShellState,\n callbacks: {\n onPanelToggle: () => void;\n onSectionToggle: (sectionId: string) => void;\n },\n): void {\n const toolbar = renderRoot.querySelector('.tbw-shell-toolbar');\n if (toolbar) {\n toolbar.addEventListener('click', (e) => {\n const target = e.target as HTMLElement;\n\n // Handle single panel toggle button\n const panelToggle = target.closest('[data-panel-toggle]') as HTMLElement | null;\n if (panelToggle) {\n callbacks.onPanelToggle();\n return;\n }\n });\n }\n\n // Accordion header clicks\n const accordion = renderRoot.querySelector('.tbw-accordion');\n if (accordion) {\n accordion.addEventListener('click', (e) => {\n const target = e.target as HTMLElement;\n const header = target.closest('.tbw-accordion-header') as HTMLElement | null;\n if (header) {\n const section = header.closest('[data-section]') as HTMLElement | null;\n const sectionId = section?.getAttribute('data-section');\n if (sectionId) {\n callbacks.onSectionToggle(sectionId);\n }\n }\n });\n }\n}\n\n/**\n * Set up a click-outside listener that closes the tool panel when the user\n * clicks anywhere inside the grid but outside the tool panel itself.\n *\n * Only active when `config.toolPanel.closeOnClickOutside` is `true` AND the\n * panel is open. The listener is added on `mousedown` (not `click`) so it\n * fires before focus changes.\n *\n * @returns A cleanup function that removes the listener.\n */\nexport function setupClickOutsideDismiss(\n gridElement: Element,\n config: ShellConfig | undefined,\n state: ShellState,\n onClose: () => void,\n): () => void {\n if (!config?.toolPanel?.closeOnClickOutside) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n const handler = (e: Event) => {\n if (!state.isPanelOpen) return;\n\n const target = e.target as Element | null;\n if (!target) return;\n\n // Ignore clicks inside the tool panel itself or its toggle button\n if (target.closest('.tbw-tool-panel') || target.closest('[data-panel-toggle]')) {\n return;\n }\n\n onClose();\n };\n\n gridElement.addEventListener('mousedown', handler);\n return () => gridElement.removeEventListener('mousedown', handler);\n}\n\n/**\n * Set up resize handle for tool panel.\n * Returns a cleanup function to remove event listeners.\n */\nexport function setupToolPanelResize(\n renderRoot: Element,\n config: ShellConfig | undefined,\n onResize: (width: number) => void,\n): () => void {\n const panel = renderRoot.querySelector('.tbw-tool-panel') as HTMLElement | null;\n const handle = renderRoot.querySelector('[data-resize-handle]') as HTMLElement | null;\n const shellBody = renderRoot.querySelector('.tbw-shell-body') as HTMLElement | null;\n if (!panel || !handle || !shellBody) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n const position = config?.toolPanel?.position ?? 'right';\n const minWidth = 200;\n\n let startX = 0;\n let startWidth = 0;\n let maxWidth = 0;\n let isResizing = false;\n\n const onMouseMove = (e: MouseEvent) => {\n if (!isResizing) return;\n e.preventDefault();\n\n // For right-positioned panel: dragging left (negative clientX change) should expand\n // For left-positioned panel: dragging right (positive clientX change) should expand\n const delta = position === 'left' ? e.clientX - startX : startX - e.clientX;\n const newWidth = Math.min(maxWidth, Math.max(minWidth, startWidth + delta));\n\n panel.style.width = `${newWidth}px`;\n };\n\n const onMouseUp = () => {\n if (!isResizing) return;\n isResizing = false;\n handle.classList.remove('resizing');\n panel.style.transition = ''; // Re-enable transition\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n\n // Get final width and notify\n const finalWidth = panel.getBoundingClientRect().width;\n onResize(finalWidth);\n\n document.removeEventListener('mousemove', onMouseMove);\n document.removeEventListener('mouseup', onMouseUp);\n };\n\n const onMouseDown = (e: MouseEvent) => {\n e.preventDefault();\n isResizing = true;\n startX = e.clientX;\n startWidth = panel.getBoundingClientRect().width;\n // Calculate max width dynamically based on grid container width\n maxWidth = shellBody.getBoundingClientRect().width - 20; // Leave 20px margin\n handle.classList.add('resizing');\n panel.style.transition = 'none'; // Disable transition for smooth resize\n document.body.style.cursor = 'col-resize';\n document.body.style.userSelect = 'none';\n\n document.addEventListener('mousemove', onMouseMove);\n document.addEventListener('mouseup', onMouseUp);\n };\n\n handle.addEventListener('mousedown', onMouseDown);\n\n // Return cleanup function\n return () => {\n handle.removeEventListener('mousedown', onMouseDown);\n document.removeEventListener('mousemove', onMouseMove);\n document.removeEventListener('mouseup', onMouseUp);\n };\n}\n// #endregion\n\n// #region Content Rendering\n/**\n * Render toolbar content (render functions) into toolbar slots.\n * All contents (config + API + light DOM) are now unified.\n */\nexport function renderCustomToolbarContents(\n renderRoot: Element,\n config: ShellConfig | undefined,\n state: ShellState,\n): void {\n // Merge config contents with state contents (same logic as renderShellHeader)\n const configContents = config?.header?.toolbarContents ?? [];\n const stateContents = [...state.toolbarContents.values()];\n const configIds = new Set(configContents.map((c) => c.id));\n const allContents = [...configContents];\n for (const content of stateContents) {\n if (!configIds.has(content.id)) {\n allContents.push(content);\n }\n }\n\n // Only process contents that need rendering (have render and cleanup not already set)\n for (const content of allContents) {\n // Skip if already rendered (cleanup exists)\n if (state.toolbarContentCleanups.has(content.id)) continue;\n if (!content.render) continue;\n\n const slot = renderRoot.querySelector(`[data-toolbar-content=\"${content.id}\"]`);\n if (!slot) continue;\n\n const cleanup = content.render(slot as HTMLElement);\n if (cleanup) {\n state.toolbarContentCleanups.set(content.id, cleanup);\n }\n }\n}\n\n/**\n * Render header content from plugins into the shell content area.\n * Also moves light DOM header content to the placeholder (once).\n */\nexport function renderHeaderContent(renderRoot: Element, state: ShellState): void {\n // Early exit if nothing to do (most common path after initial render)\n const hasLightDomContent = state.lightDomHeaderContent.length > 0 && !state.lightDomContentMoved;\n const hasPluginContent = state.headerContents.size > 0;\n if (!hasLightDomContent && !hasPluginContent) return;\n\n const contentArea = renderRoot.querySelector('.tbw-shell-content');\n if (!contentArea) return;\n\n // Move light DOM header content to placeholder - only once (perf optimization)\n if (hasLightDomContent) {\n for (const el of state.lightDomHeaderContent) {\n el.style.display = ''; // Show it (was hidden in the original container)\n contentArea.appendChild(el);\n }\n state.lightDomContentMoved = true;\n }\n\n // Sort by order\n const sortedContents = [...state.headerContents.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n\n for (const content of sortedContents) {\n // Clean up previous render if any\n const existingCleanup = state.headerContentCleanups.get(content.id);\n if (existingCleanup) {\n existingCleanup();\n state.headerContentCleanups.delete(content.id);\n }\n\n // Check if container already exists\n let container = contentArea.querySelector(`[data-header-content=\"${content.id}\"]`) as HTMLElement | null;\n if (!container) {\n container = document.createElement('div');\n container.setAttribute('data-header-content', content.id);\n contentArea.appendChild(container);\n }\n\n const cleanup = content.render(container);\n if (cleanup) {\n state.headerContentCleanups.set(content.id, cleanup);\n }\n }\n}\n\n/**\n * Render content for expanded accordion sections.\n * @param icons - Optional icons for expand/collapse chevrons (from grid config)\n */\nexport function renderPanelContent(\n renderRoot: Element,\n state: ShellState,\n icons?: { expand?: IconValue; collapse?: IconValue },\n): void {\n if (!state.isPanelOpen) return;\n\n for (const [panelId, panel] of state.toolPanels) {\n const isExpanded = state.expandedSections.has(panelId);\n const section = renderRoot.querySelector(`[data-section=\"${panelId}\"]`);\n const contentArea = section?.querySelector('.tbw-accordion-content') as HTMLElement | null;\n\n if (!section || !contentArea) continue;\n\n // Update expanded state\n section.classList.toggle('expanded', isExpanded);\n const header = section.querySelector('.tbw-accordion-header');\n if (header) {\n header.setAttribute('aria-expanded', String(isExpanded));\n }\n // Don't swap chevron icon — CSS rotation handles expanded/collapsed state\n\n if (isExpanded) {\n // Check if content is already rendered\n if (contentArea.children.length === 0) {\n // Render panel content\n const cleanup = panel.render(contentArea);\n if (cleanup) {\n state.panelCleanups.set(panelId, cleanup);\n }\n }\n } else {\n // Clean up and clear content when collapsed\n const cleanup = state.panelCleanups.get(panelId);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(panelId);\n }\n contentArea.innerHTML = '';\n }\n }\n}\n\n/**\n * Update toolbar button active states.\n */\nexport function updateToolbarActiveStates(renderRoot: Element, state: ShellState): void {\n // Update single panel toggle button\n const panelToggle = renderRoot.querySelector('[data-panel-toggle]');\n if (panelToggle) {\n panelToggle.classList.toggle('active', state.isPanelOpen);\n panelToggle.setAttribute('aria-pressed', String(state.isPanelOpen));\n }\n}\n\n/**\n * Update tool panel open/close state.\n */\nexport function updatePanelState(renderRoot: Element, state: ShellState): void {\n const panel = renderRoot.querySelector('.tbw-tool-panel') as HTMLElement | null;\n if (!panel) return;\n\n panel.classList.toggle('open', state.isPanelOpen);\n\n // Clear inline width when closing (resize sets inline style that overrides CSS)\n if (!state.isPanelOpen) {\n panel.style.width = '';\n }\n}\n\n/**\n * Prepare shell state for a full re-render.\n * Runs cleanup functions so content (toolbar buttons, panels, headers)\n * can be restored to their original containers and re-rendered into new DOM.\n */\nexport function prepareForRerender(state: ShellState): void {\n // Run cleanups for toolbar contents (moves elements back to original containers)\n for (const cleanup of state.toolbarContentCleanups.values()) {\n cleanup();\n }\n state.toolbarContentCleanups.clear();\n\n // Run cleanups for panel contents (old DOM will be destroyed)\n for (const cleanup of state.panelCleanups.values()) {\n cleanup();\n }\n state.panelCleanups.clear();\n\n // Run cleanups for header contents (old DOM will be destroyed)\n for (const cleanup of state.headerContentCleanups.values()) {\n cleanup();\n }\n state.headerContentCleanups.clear();\n\n // Allow light DOM content to be re-moved into new DOM\n state.lightDomContentMoved = false;\n}\n\n/**\n * Cleanup all shell state when grid disconnects.\n */\nexport function cleanupShellState(state: ShellState): void {\n // Clean up header content\n for (const cleanup of state.headerContentCleanups.values()) {\n cleanup();\n }\n state.headerContentCleanups.clear();\n\n // Clean up panel content\n for (const cleanup of state.panelCleanups.values()) {\n cleanup();\n }\n state.panelCleanups.clear();\n\n // Clean up toolbar contents\n for (const cleanup of state.toolbarContentCleanups.values()) {\n cleanup();\n }\n state.toolbarContentCleanups.clear();\n\n // Call onDestroy for all toolbar contents\n for (const content of state.toolbarContents.values()) {\n content.onDestroy?.();\n }\n\n // Invoke onClose for all open panels\n if (state.isPanelOpen) {\n for (const sectionId of state.expandedSections) {\n const panel = state.toolPanels.get(sectionId);\n panel?.onClose?.();\n }\n }\n\n // Reset panel state\n state.isPanelOpen = false;\n state.expandedSections.clear();\n\n // Clear registrations\n state.toolPanels.clear();\n state.headerContents.clear();\n state.toolbarContents.clear();\n state.lightDomHeaderContent = [];\n\n // Clear light DOM tracking sets (allow re-parsing)\n state.lightDomToolPanelIds.clear();\n state.lightDomToolbarContentIds.clear();\n\n // Reset move tracking flag (allow re-initialization)\n state.lightDomContentMoved = false;\n}\n// #endregion\n\n// #region ShellController\n/**\n * Controller interface for managing shell/tool panel behavior.\n */\nexport interface ShellController {\n /** Whether the shell has been initialized */\n readonly isInitialized: boolean;\n /** Set the initialized state */\n setInitialized(value: boolean): void;\n /** Whether the tool panel is currently open */\n readonly isPanelOpen: boolean;\n /** Get the currently active panel ID (deprecated) */\n readonly activePanel: string | null;\n /** Get IDs of expanded accordion sections */\n readonly expandedSections: string[];\n /** Open the tool panel */\n openToolPanel(): void;\n /** Close the tool panel */\n closeToolPanel(): void;\n /** Toggle the tool panel */\n toggleToolPanel(): void;\n /** Toggle an accordion section */\n toggleToolPanelSection(sectionId: string): void;\n /** Get registered tool panels */\n getToolPanels(): ToolPanelDefinition[];\n /** Register a tool panel */\n registerToolPanel(panel: ToolPanelDefinition): void;\n /** Unregister a tool panel */\n unregisterToolPanel(panelId: string): void;\n /** Get registered header contents */\n getHeaderContents(): HeaderContentDefinition[];\n /** Register header content */\n registerHeaderContent(content: HeaderContentDefinition): void;\n /** Unregister header content */\n unregisterHeaderContent(contentId: string): void;\n /** Get all registered toolbar contents */\n getToolbarContents(): ToolbarContentDefinition[];\n /** Register toolbar content */\n registerToolbarContent(content: ToolbarContentDefinition): void;\n /** Unregister toolbar content */\n unregisterToolbarContent(contentId: string): void;\n}\n\n/**\n * Create a ShellController instance.\n * The controller encapsulates all tool panel orchestration logic.\n */\nexport function createShellController(state: ShellState, grid: InternalGrid): ShellController {\n let initialized = false;\n\n const controller: ShellController = {\n get isInitialized() {\n return initialized;\n },\n setInitialized(value: boolean) {\n initialized = value;\n },\n\n get isPanelOpen() {\n return state.isPanelOpen;\n },\n\n get activePanel() {\n // For backward compatibility, return first expanded section if panel is open\n if (state.isPanelOpen && state.expandedSections.size > 0) {\n return [...state.expandedSections][0];\n }\n return null;\n },\n\n get expandedSections() {\n return [...state.expandedSections];\n },\n\n openToolPanel() {\n if (state.isPanelOpen) return;\n if (state.toolPanels.size === 0) {\n warnDiagnostic(NO_TOOL_PANELS, 'No tool panels registered', grid.id);\n return;\n }\n\n state.isPanelOpen = true;\n\n // Auto-expand first section if none expanded\n if (state.expandedSections.size === 0 && state.toolPanels.size > 0) {\n const sortedPanels = [...state.toolPanels.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n const firstPanel = sortedPanels[0];\n if (firstPanel) {\n state.expandedSections.add(firstPanel.id);\n }\n }\n\n // Update UI\n const shadow = grid._renderRoot;\n updateToolbarActiveStates(shadow, state);\n updatePanelState(shadow, state);\n\n // Render accordion sections\n renderPanelContent(shadow, state, grid._accordionIcons);\n\n // Emit event\n grid._emit('tool-panel-open', { sections: controller.expandedSections });\n },\n\n closeToolPanel() {\n if (!state.isPanelOpen) return;\n\n // Clean up all panel content\n for (const cleanup of state.panelCleanups.values()) {\n cleanup();\n }\n state.panelCleanups.clear();\n\n // Call onClose for all panels\n for (const panel of state.toolPanels.values()) {\n panel.onClose?.();\n }\n\n state.isPanelOpen = false;\n\n // Update UI\n const shadow = grid._renderRoot;\n updateToolbarActiveStates(shadow, state);\n updatePanelState(shadow, state);\n\n // Emit event\n grid._emit('tool-panel-close', {});\n },\n\n toggleToolPanel() {\n if (state.isPanelOpen) {\n controller.closeToolPanel();\n } else {\n controller.openToolPanel();\n }\n },\n\n toggleToolPanelSection(sectionId: string) {\n const panel = state.toolPanels.get(sectionId);\n if (!panel) {\n warnDiagnostic(TOOL_PANEL_NOT_FOUND, `Tool panel section \"${sectionId}\" not found`, grid.id);\n return;\n }\n\n // Don't allow toggling when there's only one panel (it should stay expanded)\n if (state.toolPanels.size === 1) {\n return;\n }\n\n const shadow = grid._renderRoot;\n const isExpanded = state.expandedSections.has(sectionId);\n\n if (isExpanded) {\n // Collapsing current section\n const cleanup = state.panelCleanups.get(sectionId);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(sectionId);\n }\n panel.onClose?.();\n state.expandedSections.delete(sectionId);\n updateAccordionSectionState(shadow, sectionId, false);\n } else {\n // Expanding - first collapse all others (exclusive accordion)\n for (const [otherId, otherPanel] of state.toolPanels) {\n if (otherId !== sectionId && state.expandedSections.has(otherId)) {\n const cleanup = state.panelCleanups.get(otherId);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(otherId);\n }\n otherPanel.onClose?.();\n state.expandedSections.delete(otherId);\n updateAccordionSectionState(shadow, otherId, false);\n // Clear content of collapsed section\n const contentEl = shadow.querySelector(`[data-section=\"${otherId}\"] .tbw-accordion-content`);\n if (contentEl) contentEl.innerHTML = '';\n }\n }\n // Now expand the target section\n state.expandedSections.add(sectionId);\n updateAccordionSectionState(shadow, sectionId, true);\n renderAccordionSectionContent(shadow, state, sectionId);\n }\n\n // Emit event\n grid._emit('tool-panel-section-toggle', { id: sectionId, expanded: !isExpanded });\n },\n\n getToolPanels() {\n return [...state.toolPanels.values()];\n },\n\n registerToolPanel(panel: ToolPanelDefinition) {\n if (state.toolPanels.has(panel.id)) {\n warnDiagnostic(TOOL_PANEL_DUPLICATE, `Tool panel \"${panel.id}\" already registered`, grid.id);\n return;\n }\n state.toolPanels.set(panel.id, panel);\n\n if (initialized) {\n grid.refreshShellHeader?.();\n }\n },\n\n unregisterToolPanel(panelId: string) {\n // Close panel if open and this section is expanded\n if (state.expandedSections.has(panelId)) {\n const cleanup = state.panelCleanups.get(panelId);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(panelId);\n }\n state.expandedSections.delete(panelId);\n }\n\n state.toolPanels.delete(panelId);\n\n if (initialized) {\n grid.refreshShellHeader?.();\n }\n },\n\n getHeaderContents() {\n return [...state.headerContents.values()];\n },\n\n registerHeaderContent(content: HeaderContentDefinition) {\n if (state.headerContents.has(content.id)) {\n warnDiagnostic(HEADER_CONTENT_DUPLICATE, `Header content \"${content.id}\" already registered`, grid.id);\n return;\n }\n state.headerContents.set(content.id, content);\n\n if (initialized) {\n renderHeaderContent(grid._renderRoot, state);\n }\n },\n\n unregisterHeaderContent(contentId: string) {\n // Clean up\n const cleanup = state.headerContentCleanups.get(contentId);\n if (cleanup) {\n cleanup();\n state.headerContentCleanups.delete(contentId);\n }\n\n // Call onDestroy\n const content = state.headerContents.get(contentId);\n content?.onDestroy?.();\n\n state.headerContents.delete(contentId);\n\n // Remove DOM element\n const el = grid._renderRoot.querySelector(`[data-header-content=\"${contentId}\"]`);\n el?.remove();\n },\n\n getToolbarContents() {\n return [...state.toolbarContents.values()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n },\n\n registerToolbarContent(content: ToolbarContentDefinition) {\n if (state.toolbarContents.has(content.id)) {\n warnDiagnostic(TOOLBAR_CONTENT_DUPLICATE, `Toolbar content \"${content.id}\" already registered`, grid.id);\n return;\n }\n state.toolbarContents.set(content.id, content);\n\n if (initialized) {\n grid.refreshShellHeader?.();\n }\n },\n\n unregisterToolbarContent(contentId: string) {\n // Clean up\n const cleanup = state.toolbarContentCleanups.get(contentId);\n if (cleanup) {\n cleanup();\n state.toolbarContentCleanups.delete(contentId);\n }\n\n // Call onDestroy if defined\n const content = state.toolbarContents.get(contentId);\n if (content?.onDestroy) {\n content.onDestroy();\n }\n\n state.toolbarContents.delete(contentId);\n\n if (initialized) {\n grid.refreshShellHeader?.();\n }\n },\n };\n\n return controller;\n}\n\n/**\n * Update accordion section visual state.\n */\nfunction updateAccordionSectionState(renderRoot: Element, sectionId: string, expanded: boolean): void {\n const section = renderRoot.querySelector(`[data-section=\"${sectionId}\"]`);\n if (section) {\n section.classList.toggle('expanded', expanded);\n }\n}\n\n/**\n * Render content for a single accordion section.\n */\nfunction renderAccordionSectionContent(renderRoot: Element, state: ShellState, sectionId: string): void {\n const panel = state.toolPanels.get(sectionId);\n if (!panel?.render) return;\n\n const contentEl = renderRoot.querySelector(`[data-section=\"${sectionId}\"] .tbw-accordion-content`);\n if (!contentEl) return;\n\n const cleanup = panel.render(contentEl as HTMLElement);\n if (cleanup) {\n state.panelCleanups.set(sectionId, cleanup);\n }\n}\n// #endregion\n\n// #region Grid HTML Templates\n/**\n * Core grid content HTML template.\n * Uses faux scrollbar pattern for smooth virtualized scrolling.\n */\nexport const GRID_CONTENT_HTML = `\n <div class=\"tbw-scroll-area\">\n <div class=\"rows-body-wrapper\">\n <div class=\"rows-body\" role=\"grid\">\n <div class=\"header\" role=\"rowgroup\">\n <div class=\"header-row\" role=\"row\" part=\"header-row\"></div>\n </div>\n <div class=\"rows-container\" role=\"presentation\">\n <div class=\"rows-viewport\" role=\"presentation\">\n <div class=\"rows\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"faux-vscroll\">\n <div class=\"faux-vscroll-spacer\"></div>\n </div>\n <div class=\"tbw-sr-only\" aria-live=\"polite\" aria-atomic=\"true\"></div>\n`;\n// #endregion\n\n// #region DOM Construction\nimport {\n buildGridDOM,\n buildShellBody,\n buildShellHeader,\n type ShellBodyOptions,\n type ShellHeaderOptions,\n} from './dom-builder';\n\n/**\n * Build the complete grid DOM structure using direct DOM construction.\n * This is 2-3x faster than innerHTML for initial render.\n *\n * @param renderRoot - The element to render into (will be cleared)\n * @param shellConfig - Shell configuration\n * @param state - Shell state\n * @param icons - Optional icons\n * @returns Whether shell is active (for post-render setup)\n */\nexport function buildGridDOMIntoElement(\n renderRoot: Element,\n shellConfig: ShellConfig | undefined,\n runtimeState: { isPanelOpen: boolean; expandedSections: Set<string> },\n icons?: { toolPanel?: IconValue; expand?: IconValue; collapse?: IconValue },\n): boolean {\n const hasShell = shouldRenderShellHeader(shellConfig);\n\n // Preserve light DOM elements before clearing (they contain user content)\n // These are custom elements used for declarative configuration\n const lightDomElements: Element[] = [];\n const lightDomSelectors = [\n 'tbw-grid-header',\n 'tbw-grid-tool-buttons',\n 'tbw-grid-tool-panel',\n 'tbw-grid-column',\n 'tbw-grid-detail',\n 'tbw-grid-responsive-card',\n ];\n for (const selector of lightDomSelectors) {\n const elements = renderRoot.querySelectorAll(`:scope > ${selector}`);\n elements.forEach((el) => lightDomElements.push(el));\n }\n\n // Clear existing content (this would delete light DOM elements, so we preserved them first)\n renderRoot.replaceChildren();\n\n // Re-append preserved light DOM elements (hidden, they're used for config)\n for (const el of lightDomElements) {\n renderRoot.appendChild(el);\n }\n\n if (hasShell) {\n const toolPanelIcon = iconToString(icons?.toolPanel ?? DEFAULT_GRID_ICONS.toolPanel);\n const expandIcon = iconToString(icons?.expand ?? DEFAULT_GRID_ICONS.expand);\n const collapseIcon = iconToString(icons?.collapse ?? DEFAULT_GRID_ICONS.collapse);\n\n // All toolbar contents now come from shellConfig (merged by ConfigManager)\n const allContents = shellConfig?.header?.toolbarContents ?? [];\n const sortedContents = [...allContents].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n\n // All panels now come from shellConfig (merged by ConfigManager)\n const allPanels = shellConfig?.toolPanels ?? [];\n const sortedPanels = [...allPanels].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n\n // Build header options\n const headerOptions: ShellHeaderOptions = {\n title: shellConfig?.header?.title ?? undefined,\n hasPanels: sortedPanels.length > 0,\n isPanelOpen: runtimeState.isPanelOpen,\n toolPanelIcon,\n // All contents are now in config (no more separate config vs API distinction for rendering)\n configButtons: sortedContents.map((c) => ({\n id: c.id,\n hasElement: false,\n hasRender: !!c.render,\n })),\n apiButtons: [], // No longer needed - all contents merged into configButtons\n };\n\n // Build body options\n const bodyOptions: ShellBodyOptions = {\n position: shellConfig?.toolPanel?.position ?? 'right',\n isPanelOpen: runtimeState.isPanelOpen,\n expandIcon,\n collapseIcon,\n panels: sortedPanels.map((p) => ({\n id: p.id,\n title: p.title,\n icon: iconToString(p.icon),\n isExpanded: runtimeState.expandedSections.has(p.id),\n })),\n };\n\n // Build shell elements\n const shellHeader = buildShellHeader(headerOptions);\n const shellBody = buildShellBody(bodyOptions);\n\n // Build and append complete DOM\n const fragment = buildGridDOM({\n hasShell: true,\n shellHeader,\n shellBody,\n });\n renderRoot.appendChild(fragment);\n } else {\n // No shell - just grid content\n const fragment = buildGridDOM({ hasShell: false });\n renderRoot.appendChild(fragment);\n }\n\n return hasShell;\n}\n// #endregion\n","/**\n * Style Injector Module\n *\n * Handles injection of grid and plugin styles into the document.\n * Uses a singleton pattern to avoid duplicate injection across multiple grid instances.\n *\n * @module internal/style-injector\n */\n\nimport { STYLE_EXTRACT_FAILED, STYLE_NOT_FOUND, warnDiagnostic } from './diagnostics';\n\n// #region State\n/** ID for the consolidated grid stylesheet in document.head */\nconst STYLE_ELEMENT_ID = 'tbw-grid-styles';\n\n/** Track injected base styles CSS text */\nlet baseStyles = '';\n\n/** Track injected plugin styles by plugin name (accumulates across all grid instances) */\nconst pluginStylesMap = new Map<string, string>();\n// #endregion\n\n// #region Internal Helpers\n/**\n * Get or create the consolidated style element in document.head.\n * All grid and plugin styles are combined into this single element.\n */\nfunction getStyleElement(): HTMLStyleElement {\n let styleEl = document.getElementById(STYLE_ELEMENT_ID) as HTMLStyleElement | null;\n if (!styleEl) {\n styleEl = document.createElement('style');\n styleEl.id = STYLE_ELEMENT_ID;\n styleEl.setAttribute('data-tbw-grid', 'true');\n document.head.appendChild(styleEl);\n }\n return styleEl;\n}\n\n/**\n * Update the consolidated stylesheet with current base + plugin styles.\n */\nfunction updateStyleElement(): void {\n const styleEl = getStyleElement();\n // Combine base styles and all accumulated plugin styles\n const pluginStyles = Array.from(pluginStylesMap.values()).join('\\n');\n styleEl.textContent = `${baseStyles}\\n\\n/* Plugin Styles */\\n${pluginStyles}`;\n}\n// #endregion\n\n// #region Public API\n/**\n * Add plugin styles to the accumulated plugin styles map.\n * Returns true if any new styles were added.\n */\nexport function addPluginStyles(pluginStyles: Array<{ name: string; styles: string }>): boolean {\n let hasNewStyles = false;\n\n for (const { name, styles } of pluginStyles) {\n if (!pluginStylesMap.has(name)) {\n pluginStylesMap.set(name, styles);\n hasNewStyles = true;\n }\n }\n\n if (hasNewStyles) {\n updateStyleElement();\n }\n\n return hasNewStyles;\n}\n\n/**\n * Extract grid CSS from document.styleSheets (Angular fallback).\n * Angular bundles CSS files into one stylesheet, so we search for it.\n */\nexport function extractGridCssFromDocument(): string | null {\n try {\n // Try to find the stylesheet containing grid CSS\n // Angular bundles all CSS files from angular.json styles array into one stylesheet\n for (const stylesheet of Array.from(document.styleSheets)) {\n try {\n // For inline/bundled stylesheets, check if it contains grid CSS\n const rules = Array.from(stylesheet.cssRules || []);\n const cssText = rules.map((rule) => rule.cssText).join('\\n');\n\n // Check if this stylesheet contains grid CSS by looking for distinctive selectors\n // Without Shadow DOM, we look for tbw-grid nesting selectors\n if (cssText.includes('.tbw-grid-root') && cssText.includes('tbw-grid')) {\n // Found the bundled stylesheet with grid CSS - use ALL of it\n // This includes core grid.css + all plugin CSS files\n return cssText;\n }\n } catch {\n // CORS or access restriction - skip\n continue;\n }\n }\n } catch (err) {\n warnDiagnostic(STYLE_EXTRACT_FAILED, `Failed to extract grid.css from document stylesheets: ${err}`);\n }\n\n return null;\n}\n\n/**\n * Inject grid styles into the document.\n * All styles go into a single <style id=\"tbw-grid-styles\"> element in document.head.\n * Uses a singleton pattern to avoid duplicate injection across multiple grid instances.\n *\n * @param inlineStyles - CSS string from Vite ?inline import (may be empty in Angular)\n */\nexport async function injectStyles(inlineStyles: string): Promise<void> {\n // If base styles already injected, nothing to do\n if (baseStyles) {\n return;\n }\n\n // If styles is a string (from ?inline import in Vite builds), use it directly\n if (typeof inlineStyles === 'string' && inlineStyles.length > 0) {\n baseStyles = inlineStyles;\n updateStyleElement();\n return;\n }\n\n // Fallback: styles is undefined (e.g., when imported in Angular from source without Vite processing)\n // Angular includes grid.css in global styles - extract it from document.styleSheets\n // Wait a bit for Angular to finish loading styles\n await new Promise((resolve) => setTimeout(resolve, 50));\n\n const gridCssText = extractGridCssFromDocument();\n\n if (gridCssText) {\n baseStyles = gridCssText;\n updateStyleElement();\n } else if (typeof process === 'undefined' || process.env?.['NODE_ENV'] !== 'test') {\n // Only warn in non-test environments - test environments (happy-dom, jsdom) don't load stylesheets\n warnDiagnostic(\n STYLE_NOT_FOUND,\n 'Could not find grid.css in document.styleSheets. Grid styling will not work. ' +\n `Available stylesheets: ${Array.from(document.styleSheets)\n .map((s) => s.href || '(inline)')\n .join(', ')}`,\n );\n }\n}\n// #endregion\n\n// #region Testing\n/**\n * Reset style injector state (for testing purposes only).\n * @internal\n */\nexport function _resetForTesting(): void {\n baseStyles = '';\n pluginStylesMap.clear();\n const styleEl = document.getElementById(STYLE_ELEMENT_ID);\n styleEl?.remove();\n}\n// #endregion\n","/**\n * Touch scrolling controller for mobile devices.\n *\n * Uses pointer events with setPointerCapture to ensure events are delivered\n * directly to the grid element, even when DOM virtualization replaces the\n * original touch target element mid-gesture.\n *\n * Without pointer capture, touch events are dispatched to the original target\n * element (e.g., a cell or card). When virtualization recycles that element's\n * content, the target is removed from the DOM and subsequent touchmove/touchend\n * events are lost — causing scrolling to stop after ~2 rows.\n *\n * setPointerCapture routes all events for a pointer ID directly to the capture\n * element (.tbw-grid-content), bypassing target resolution entirely.\n *\n * Uses incremental deltas (frame-to-frame) rather than absolute offsets from\n * the gesture start, which is more robust when virtualized content height\n * changes during scrolling (e.g., responsive card mode with variable-height rows).\n */\n\n// #region Types\nexport interface TouchScrollState {\n startY: number | null;\n startX: number | null;\n lastY: number | null;\n lastX: number | null;\n lastTime: number | null;\n velocityY: number;\n velocityX: number;\n momentumRaf: number;\n /** Once we start scrolling the grid, lock until gesture ends to prevent browser takeover */\n locked: boolean;\n /** Active pointer ID for setPointerCapture tracking (null = no active gesture) */\n activePointerId: number | null;\n}\n\nexport interface TouchScrollElements {\n fauxScrollbar: HTMLElement;\n scrollArea: HTMLElement | null;\n}\n// #endregion\n\n// #region State Management\n/**\n * Create initial touch scroll state.\n */\nexport function createTouchScrollState(): TouchScrollState {\n return {\n startY: null,\n startX: null,\n lastY: null,\n lastX: null,\n lastTime: null,\n velocityY: 0,\n velocityX: 0,\n momentumRaf: 0,\n locked: false,\n activePointerId: null,\n };\n}\n\n/**\n * Reset touch scroll state (called on touchend or cleanup).\n */\nexport function resetTouchState(state: TouchScrollState): void {\n state.startY = null;\n state.startX = null;\n state.lastY = null;\n state.lastX = null;\n state.lastTime = null;\n state.locked = false;\n}\n\n/**\n * Cancel any ongoing momentum animation.\n */\nexport function cancelMomentum(state: TouchScrollState): void {\n if (state.momentumRaf) {\n cancelAnimationFrame(state.momentumRaf);\n state.momentumRaf = 0;\n }\n}\n// #endregion\n\n// #region Touch Handlers\n/**\n * Handle gesture start (from pointerdown).\n */\nexport function handleTouchStart(clientX: number, clientY: number, state: TouchScrollState): void {\n // Cancel any ongoing momentum animation\n cancelMomentum(state);\n\n state.startY = clientY;\n state.startX = clientX;\n state.lastY = clientY;\n state.lastX = clientX;\n state.lastTime = performance.now();\n state.velocityY = 0;\n state.velocityX = 0;\n state.locked = false;\n}\n\n/**\n * Handle gesture move (from pointermove).\n * Uses incremental deltas (from previous position) for robustness.\n * Returns true if the event should be prevented (grid is handling scroll).\n */\nexport function handleTouchMove(clientX: number, clientY: number, state: TouchScrollState, elements: TouchScrollElements): boolean {\n if (state.lastY === null || state.lastX === null) {\n return false;\n }\n\n const now = performance.now();\n\n // Incremental delta since last move (not from gesture start)\n const incrY = state.lastY - clientY;\n const incrX = state.lastX - clientX;\n\n // Calculate velocity for momentum scrolling\n if (state.lastTime !== null) {\n const dt = now - state.lastTime;\n if (dt > 0) {\n state.velocityY = incrY / dt;\n state.velocityX = incrX / dt;\n }\n }\n state.lastY = clientY;\n state.lastX = clientX;\n state.lastTime = now;\n\n // If already locked to grid scrolling, keep preventing default\n if (state.locked) {\n elements.fauxScrollbar.scrollTop += incrY;\n if (elements.scrollArea) {\n elements.scrollArea.scrollLeft += incrX;\n }\n return true;\n }\n\n // Determine scroll direction on first significant move (> 3px from start)\n const totalDeltaY = state.startY !== null ? Math.abs(state.startY - clientY) : 0;\n const totalDeltaX = state.startX !== null ? Math.abs(state.startX - clientX) : 0;\n if (totalDeltaY < 3 && totalDeltaX < 3) return false;\n\n // Determine primary gesture direction from total delta, not micro-frame delta.\n // Using totalDelta avoids mis-detection from finger jitter between frames.\n const isVerticalGesture = totalDeltaY >= totalDeltaX;\n\n // Check if grid has scrollable content (don't check boundary position —\n // the browser clamps scrollTop automatically, and checking boundaries here\n // causes the lock to fail when momentum brought us to an edge).\n const { scrollHeight, clientHeight } = elements.fauxScrollbar;\n const hasVerticalScroll = scrollHeight - clientHeight > 0;\n\n let hasHorizontalScroll = false;\n if (elements.scrollArea) {\n const { scrollWidth, clientWidth } = elements.scrollArea;\n hasHorizontalScroll = scrollWidth - clientWidth > 0;\n }\n\n if ((isVerticalGesture && hasVerticalScroll) || (!isVerticalGesture && hasHorizontalScroll)) {\n // Lock to grid scrolling for the rest of this gesture\n state.locked = true;\n elements.fauxScrollbar.scrollTop += incrY;\n if (elements.scrollArea) {\n elements.scrollArea.scrollLeft += incrX;\n }\n return true;\n }\n\n // Grid can't scroll in this direction — let the page scroll\n return false;\n}\n\n/**\n * Handle touchend event.\n * Starts momentum scrolling if velocity is significant.\n */\nexport function handleTouchEnd(state: TouchScrollState, elements: TouchScrollElements): void {\n const minVelocity = 0.1; // pixels per ms threshold\n\n // Start momentum scrolling if there's significant velocity\n if (Math.abs(state.velocityY) > minVelocity || Math.abs(state.velocityX) > minVelocity) {\n startMomentumScroll(state, elements);\n }\n\n resetTouchState(state);\n}\n// #endregion\n\n// #region Momentum Scrolling\n/**\n * Start momentum scrolling animation.\n */\nfunction startMomentumScroll(state: TouchScrollState, elements: TouchScrollElements): void {\n const friction = 0.95; // Deceleration factor per frame\n const minVelocity = 0.01; // Stop threshold in px/ms\n\n const animate = () => {\n // Apply friction\n state.velocityY *= friction;\n state.velocityX *= friction;\n\n // Convert velocity (px/ms) to per-frame scroll amount (~16ms per frame)\n const scrollY = state.velocityY * 16;\n const scrollX = state.velocityX * 16;\n\n // Apply scroll if above threshold\n if (Math.abs(state.velocityY) > minVelocity) {\n elements.fauxScrollbar.scrollTop += scrollY;\n }\n if (Math.abs(state.velocityX) > minVelocity && elements.scrollArea) {\n elements.scrollArea.scrollLeft += scrollX;\n }\n\n // Continue animation if still moving\n if (Math.abs(state.velocityY) > minVelocity || Math.abs(state.velocityX) > minVelocity) {\n state.momentumRaf = requestAnimationFrame(animate);\n } else {\n state.momentumRaf = 0;\n }\n };\n\n state.momentumRaf = requestAnimationFrame(animate);\n}\n// #endregion\n\n// #region Setup\n/**\n * Set up pointer event listeners on the grid content element for touch scrolling.\n *\n * Uses pointer events + setPointerCapture instead of touch events because\n * DOM virtualization can destroy the original touch target mid-gesture,\n * causing touch events to stop being delivered. Pointer capture routes\n * all events directly to the grid element regardless of DOM changes.\n */\nexport function setupTouchScrollListeners(\n gridContentEl: HTMLElement,\n state: TouchScrollState,\n elements: TouchScrollElements,\n signal: AbortSignal,\n): void {\n gridContentEl.addEventListener(\n 'pointerdown',\n (e: PointerEvent) => {\n // Only handle touch (not mouse/pen), and only one finger at a time\n if (e.pointerType !== 'touch' || state.activePointerId !== null) return;\n state.activePointerId = e.pointerId;\n gridContentEl.setPointerCapture(e.pointerId);\n handleTouchStart(e.clientX, e.clientY, state);\n },\n { passive: true, signal },\n );\n\n gridContentEl.addEventListener(\n 'pointermove',\n (e: PointerEvent) => {\n if (e.pointerId !== state.activePointerId) return;\n const shouldPrevent = handleTouchMove(e.clientX, e.clientY, state, elements);\n if (shouldPrevent) {\n e.preventDefault();\n }\n },\n { passive: false, signal },\n );\n\n gridContentEl.addEventListener(\n 'pointerup',\n (e: PointerEvent) => {\n if (e.pointerId !== state.activePointerId) return;\n state.activePointerId = null;\n handleTouchEnd(state, elements);\n },\n { passive: true, signal },\n );\n\n gridContentEl.addEventListener(\n 'pointercancel',\n (e: PointerEvent) => {\n if (e.pointerId !== state.activePointerId) return;\n state.activePointerId = null;\n resetTouchState(state);\n },\n { passive: true, signal },\n );\n\n // Safety net: if pointer capture is lost unexpectedly, clean up\n gridContentEl.addEventListener(\n 'lostpointercapture',\n (e: PointerEvent) => {\n if (e.pointerId !== state.activePointerId) return;\n state.activePointerId = null;\n resetTouchState(state);\n },\n { passive: true, signal },\n );\n}\n// #endregion\n","/**\n * Configuration Validation\n *\n * Runtime validators that check for plugin-specific properties in config\n * and throw helpful errors if the required plugin is not loaded.\n *\n * This catches common mistakes like using `editable: true` without EditingPlugin.\n *\n * Uses a static registry of known plugin-owned properties to detect when users\n * configure features that require plugins they haven't loaded.\n */\n\nimport type { BaseGridPlugin, PluginManifest, PluginPropertyDefinition } from '../plugin';\nimport type { ColumnConfig, GridConfig } from '../types';\nimport {\n CONFIG_RULE_ERROR,\n CONFIG_RULE_WARN,\n INCOMPATIBLE_PLUGINS,\n MISSING_DEPENDENCY,\n MISSING_PLUGIN,\n MISSING_PLUGIN_CONFIG,\n OPTIONAL_DEPENDENCY,\n infoDiagnostic,\n throwDiagnostic,\n warnDiagnostic,\n} from './diagnostics';\nimport { isDevelopment } from './utils';\n\n/**\n * Internal property definition with plugin name attached.\n * Extends PluginPropertyDefinition with required pluginName for validation.\n */\ninterface InternalPropertyDefinition extends PluginPropertyDefinition {\n pluginName: string;\n}\n\n// #region Known Properties Registry\n/**\n * Static registry of known plugin-owned column properties.\n * This enables detection of plugin properties even when the plugin isn't loaded.\n * Properties defined here allow helpful error messages when plugins are missing.\n *\n * ## Why This Exists (The Validation Paradox)\n *\n * We need to detect when a developer uses a plugin-owned property (like `editable`)\n * but forgets to add the plugin. However, if the plugin isn't loaded, we can't\n * read its manifest! The manifest only exists when the plugin class is imported.\n *\n * This static registry solves that: it's a \"well-known properties\" list that exists\n * independently of whether plugins are loaded.\n *\n * ## When Adding New Plugin-Owned Properties\n *\n * 1. **Always**: Add to the plugin's manifest `ownedProperties` (documentation, lives with plugin)\n * 2. **Optionally**: Add here if you want \"forgot to add plugin\" detection for that property\n *\n * Not every property needs to be here - only high-value ones where developers commonly\n * forget to add the plugin. Third-party plugins can't be listed here anyway.\n *\n * ## Future Improvement\n *\n * A build-time script could generate these arrays from plugin manifests,\n * creating a single source of truth. For now, they're maintained manually.\n */\nconst KNOWN_COLUMN_PROPERTIES: InternalPropertyDefinition[] = [\n // EditingPlugin\n {\n property: 'editable',\n pluginName: 'editing',\n level: 'column',\n description: 'the \"editable\" column property',\n isUsed: (v) => v === true || typeof v === 'function',\n },\n {\n property: 'editor',\n pluginName: 'editing',\n level: 'column',\n description: 'the \"editor\" column property',\n },\n {\n property: 'editorParams',\n pluginName: 'editing',\n level: 'column',\n description: 'the \"editorParams\" column property',\n },\n // GroupingColumnsPlugin\n {\n property: 'group',\n pluginName: 'groupingColumns',\n level: 'column',\n description: 'the \"group\" column property',\n },\n // PinnedColumnsPlugin\n {\n property: 'pinned',\n pluginName: 'pinnedColumns',\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 pluginName: 'pinnedColumns',\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\n/**\n * Static registry of known plugin-owned grid config properties.\n */\nconst KNOWN_CONFIG_PROPERTIES: InternalPropertyDefinition[] = [\n // EditingPlugin\n {\n property: 'rowEditable',\n pluginName: 'editing',\n level: 'config',\n description: 'the \"rowEditable\" config property',\n isUsed: (v) => typeof v === 'function',\n },\n // GroupingColumnsPlugin\n {\n property: 'columnGroups',\n pluginName: 'groupingColumns',\n level: 'config',\n description: 'the \"columnGroups\" config property',\n isUsed: (v) => Array.isArray(v) && v.length > 0,\n },\n];\n// #endregion\n\n// #region Import Hints\n/**\n * Convert a camelCase plugin name to kebab-case for import paths.\n * e.g. 'groupingRows' → 'grouping-rows', 'editing' → 'editing'\n */\nfunction toKebabCase(s: string): string {\n return s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);\n}\n\n/**\n * Generate the import hint for a plugin from its name.\n * e.g. 'editing' → \"import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';\"\n */\nfunction getImportHint(pluginName: string): string {\n return `import { ${capitalize(pluginName)}Plugin } from '@toolbox-web/grid/plugins/${toKebabCase(pluginName)}';`;\n}\n// #endregion\n\n// #region Development Mode\n// #endregion\n\n// #region Helper Functions\n/**\n * Helper to capitalize a plugin name for display.\n */\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n\n/**\n * Check if a plugin with the given name is present in the plugins array.\n */\nfunction hasPlugin(plugins: readonly BaseGridPlugin[], pluginName: string): boolean {\n return plugins.some((p) => p.name === pluginName);\n}\n// #endregion\n\n// #region Property Validation\n/**\n * Validate that column properties requiring plugins have those plugins loaded.\n *\n * @param config - The merged grid configuration\n * @param plugins - The array of loaded plugins\n * @throws Error if a plugin-owned property is used without the plugin\n */\nexport function validatePluginProperties<T>(\n config: GridConfig<T>,\n plugins: readonly BaseGridPlugin[],\n gridId?: string,\n): void {\n // Use static registries of known plugin-owned properties\n const columnProps = KNOWN_COLUMN_PROPERTIES;\n const configProps = KNOWN_CONFIG_PROPERTIES;\n\n // Group errors by plugin to avoid spamming multiple errors\n const missingPlugins = new Map<\n string,\n { description: string; importHint: string; fields: string[]; isConfigProperty?: boolean }\n >();\n\n // Helper to add an error for a missing plugin\n function addError(\n pluginName: string,\n description: string,\n importHint: string,\n field: string,\n isConfigProperty = false,\n ) {\n if (!missingPlugins.has(pluginName)) {\n missingPlugins.set(pluginName, { description, importHint, fields: [], isConfigProperty });\n }\n // Entry is guaranteed to exist after the set above\n const entry = missingPlugins.get(pluginName)!;\n if (!entry.fields.includes(field)) {\n entry.fields.push(field);\n }\n }\n\n // Validate grid config properties\n for (const def of configProps) {\n const value = (config as Record<string, unknown>)[def.property];\n const isUsed = def.isUsed ? def.isUsed(value) : value !== undefined;\n\n if (isUsed && !hasPlugin(plugins, def.pluginName)) {\n addError(def.pluginName, def.description, getImportHint(def.pluginName), def.property, true);\n }\n }\n\n // Validate column properties\n const columns = config.columns;\n if (columns && columns.length > 0) {\n for (const column of columns) {\n for (const def of columnProps) {\n const value = (column as unknown as Record<string, unknown>)[def.property];\n // Use custom isUsed check if provided, otherwise check for defined value\n const isUsed = def.isUsed ? def.isUsed(value) : value !== undefined;\n\n if (isUsed && !hasPlugin(plugins, def.pluginName)) {\n const field = (column as ColumnConfig).field || '<unknown>';\n addError(def.pluginName, def.description, getImportHint(def.pluginName), field);\n }\n }\n }\n }\n\n // Throw a single consolidated error if any missing plugins\n if (missingPlugins.size > 0) {\n const errors: string[] = [];\n for (const [pluginName, { description, importHint, fields, isConfigProperty }] of missingPlugins) {\n if (isConfigProperty) {\n // Config-level property error\n errors.push(\n `Config uses ${description}, but the required plugin is not loaded.\\n` +\n ` → Add the plugin to your gridConfig.plugins array:\\n` +\n ` ${importHint}\\n` +\n ` plugins: [new ${capitalize(pluginName)}Plugin(), ...]`,\n );\n } else {\n // Column-level property error\n const fieldList = fields.slice(0, 3).join(', ') + (fields.length > 3 ? `, ... (${fields.length} total)` : '');\n errors.push(\n `Column(s) [${fieldList}] use ${description}, but the required plugin is not loaded.\\n` +\n ` → Add the plugin to your gridConfig.plugins array:\\n` +\n ` ${importHint}\\n` +\n ` plugins: [new ${capitalize(pluginName)}Plugin(), ...]`,\n );\n }\n }\n\n // Use MISSING_PLUGIN for column-level errors, MISSING_PLUGIN_CONFIG for config-level\n const code = [...missingPlugins.values()].some((e) => e.isConfigProperty) ? MISSING_PLUGIN_CONFIG : MISSING_PLUGIN;\n throwDiagnostic(\n code,\n `Configuration error:\\n\\n${errors.join('\\n\\n')}\\n\\n` +\n `This validation helps catch misconfigurations early. ` +\n `The properties listed above require their respective plugins to function.`,\n gridId,\n );\n }\n}\n// #endregion\n\n// #region Config Rules Validation\n/**\n * Validate plugin configuration rules declared in manifests.\n * Called after plugins are attached to check for invalid/conflicting configurations.\n *\n * Rules with severity 'error' throw an error.\n * Rules with severity 'warn' log a warning to console.\n *\n * @param plugins - The array of attached plugins (with config already merged)\n */\nexport function validatePluginConfigRules(plugins: readonly BaseGridPlugin[], gridId?: string): void {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n for (const plugin of plugins) {\n const PluginClass = plugin.constructor as typeof BaseGridPlugin;\n const manifest = PluginClass.manifest as PluginManifest | undefined;\n if (!manifest?.configRules) continue;\n\n for (const rule of manifest.configRules) {\n // Access plugin's merged config via protected property\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const pluginConfig = (plugin as any).config;\n if (rule.check(pluginConfig)) {\n const formatted = `[${capitalize(plugin.name)}Plugin] Configuration warning: ${rule.message}`;\n if (rule.severity === 'error') {\n errors.push(formatted);\n } else {\n warnings.push(formatted);\n }\n }\n }\n }\n\n // Log warnings only in development (don't pollute production logs)\n if (warnings.length > 0 && isDevelopment()) {\n for (const warning of warnings) {\n warnDiagnostic(CONFIG_RULE_WARN, warning, gridId);\n }\n }\n\n // Throw consolidated error if any (always, regardless of environment)\n if (errors.length > 0) {\n throwDiagnostic(CONFIG_RULE_ERROR, `Configuration error:\\n\\n${errors.join('\\n\\n')}`, gridId);\n }\n}\n// #endregion\n\n// #region Dependency Validation\n/**\n * Validate plugin-to-plugin dependencies.\n * Called by PluginManager when attaching a new plugin.\n *\n * Dependencies are read from the plugin's static `dependencies` property.\n *\n * For hard dependencies (required: true), throws an error if the dependency is not loaded.\n * For soft dependencies (required: false), logs an info message but continues.\n *\n * @param plugin - The plugin instance being attached\n * @param loadedPlugins - The array of already-loaded plugins\n * @throws Error if a required dependency is missing\n */\nexport function validatePluginDependencies(\n plugin: BaseGridPlugin,\n loadedPlugins: readonly BaseGridPlugin[],\n gridId?: string,\n): void {\n const pluginName = plugin.name;\n const PluginClass = plugin.constructor as typeof BaseGridPlugin;\n\n // Get dependencies from plugin's static property\n const dependencies = PluginClass.dependencies ?? [];\n\n // Validate each dependency\n for (const dep of dependencies) {\n const requiredPlugin = dep.name;\n const required = dep.required ?? true; // Default to required\n const reason = dep.reason;\n const hasRequired = loadedPlugins.some((p) => p.name === requiredPlugin);\n\n if (!hasRequired) {\n const reasonText = reason ?? `${capitalize(pluginName)}Plugin requires ${capitalize(requiredPlugin)}Plugin`;\n const importHint = getImportHint(requiredPlugin);\n\n if (required) {\n throwDiagnostic(\n MISSING_DEPENDENCY,\n `Plugin dependency error:\\n\\n` +\n `${reasonText}.\\n\\n` +\n ` → Add the plugin to your gridConfig.plugins array BEFORE ${capitalize(pluginName)}Plugin:\\n` +\n ` ${importHint}\\n` +\n ` plugins: [new ${capitalize(requiredPlugin)}Plugin(), new ${capitalize(pluginName)}Plugin()]`,\n gridId,\n );\n } else {\n // Soft dependency - log info message but continue\n infoDiagnostic(\n OPTIONAL_DEPENDENCY,\n `${capitalize(pluginName)}Plugin: Optional \"${requiredPlugin}\" plugin not found. ` +\n `Some features may be unavailable.`,\n gridId,\n );\n }\n }\n }\n}\n// #endregion\n\n// #region Incompatibility Validation\n/**\n * Validate that no incompatible plugins are loaded together.\n * Called after all plugins are attached to the grid.\n *\n * Incompatibilities are read from each plugin's manifest `incompatibleWith` property.\n * When a conflict is detected, a warning is logged (in development mode).\n *\n * @param plugins - All attached plugins\n */\nexport function validatePluginIncompatibilities(plugins: readonly BaseGridPlugin[], gridId?: string): void {\n // Only warn in development mode to avoid polluting production logs\n if (!isDevelopment()) return;\n\n const pluginNames = new Set(plugins.map((p) => p.name));\n const warned = new Set<string>(); // Avoid duplicate warnings for symmetric conflicts\n\n for (const plugin of plugins) {\n const PluginClass = plugin.constructor as typeof BaseGridPlugin;\n const manifest = PluginClass.manifest as PluginManifest | undefined;\n if (!manifest?.incompatibleWith) continue;\n\n for (const incompatibility of manifest.incompatibleWith) {\n if (pluginNames.has(incompatibility.name)) {\n // Create a symmetric key to avoid warning twice (A→B and B→A)\n const key = [plugin.name, incompatibility.name].sort().join('↔');\n if (warned.has(key)) continue;\n warned.add(key);\n\n warnDiagnostic(\n INCOMPATIBLE_PLUGINS,\n `${capitalize(plugin.name)}Plugin and ${capitalize(incompatibility.name)}Plugin are both loaded, ` +\n `but they are currently incompatible.\\n\\n` +\n ` → ${incompatibility.reason}\\n\\n` +\n ` Consider removing one of these plugins to avoid unexpected behavior.`,\n gridId,\n );\n }\n }\n }\n}\n// #endregion\n","/**\n * Row Virtualization Module\n *\n * Provides all virtualization-related functionality for the grid:\n *\n * 1. **Position Cache** (index-based): Maps row index → {offset, height, measured}\n * - Rebuilt when row count changes (expand/collapse, filter)\n * - Used for scroll position → row index lookups (binary search)\n *\n * 2. **Height Cache** (identity-based): Maps row identity → height\n * - Persists across expand/collapse\n * - Uses rowId string keys when available, WeakMap otherwise\n * - Synthetic rows use __rowCacheKey for stable identity\n *\n * 3. **Virtual Window**: Computes which rows to render based on scroll position\n *\n * 4. **Row Measurement**: Measures rendered row heights from DOM\n */\n\n// #region Types\n\n/**\n * Position entry for a single row in the position cache.\n * @category Plugin Development\n */\nexport interface RowPosition {\n /** Cumulative offset from top in pixels */\n offset: number;\n /** Row height in pixels */\n height: number;\n /** Whether this is a measured value (true) or estimate (false) */\n measured: boolean;\n}\n\n/**\n * Height cache that persists row heights across position cache rebuilds.\n * Uses dual storage: Map for string keys (rowId, __rowCacheKey) and WeakMap for object refs.\n * @category Plugin Development\n */\nexport interface HeightCache {\n /** Heights keyed by string (for synthetic rows with __rowCacheKey or rowId-keyed rows) */\n byKey: Map<string, number>;\n /** Heights keyed by object reference (for data rows without rowId) */\n byRef: WeakMap<object, number>;\n}\n\n/**\n * Configuration for the position cache.\n */\nexport interface PositionCacheConfig<T = unknown> {\n /** Function to get row ID (if configured) */\n rowId?: (row: T) => string | number;\n}\n\n// #endregion\n\n// #region Height Cache Operations\n\n/**\n * Create a new empty height cache.\n */\nexport function createHeightCache(): HeightCache {\n return {\n byKey: new Map(),\n byRef: new WeakMap(),\n };\n}\n\n/**\n * Get the cache key for a row.\n * Returns string for synthetic rows (__rowCacheKey) or rowId-keyed rows,\n * or the row object itself for WeakMap lookup.\n */\nexport function getRowCacheKey<T>(row: T, rowId?: (row: T) => string | number): string | T {\n if (!row || typeof row !== 'object') return row;\n\n // 1. Synthetic rows: plugins MUST add __rowCacheKey\n if ('__rowCacheKey' in row) {\n return (row as { __rowCacheKey: string }).__rowCacheKey;\n }\n\n // 2. rowId property directly on the row (common pattern)\n if ('rowId' in row && (row as { rowId: string | number }).rowId != null) {\n return `id:${(row as { rowId: string | number }).rowId}`;\n }\n\n // 3. User-provided rowId function (if configured)\n if (rowId) {\n return `id:${rowId(row)}`;\n }\n\n // 4. Object identity (for WeakMap)\n return row;\n}\n\n/**\n * Get cached height for a row.\n * @returns Cached height or undefined if not cached\n */\nexport function getCachedHeight<T>(\n cache: HeightCache,\n row: T,\n rowId?: (row: T) => string | number,\n): number | undefined {\n const key = getRowCacheKey(row, rowId);\n\n if (typeof key === 'string') {\n return cache.byKey.get(key);\n }\n\n // Object key - use WeakMap\n if (key && typeof key === 'object') {\n return cache.byRef.get(key);\n }\n\n return undefined;\n}\n\n/**\n * Set cached height for a row.\n */\nexport function setCachedHeight<T>(\n cache: HeightCache,\n row: T,\n height: number,\n rowId?: (row: T) => string | number,\n): void {\n const key = getRowCacheKey(row, rowId);\n\n if (typeof key === 'string') {\n cache.byKey.set(key, height);\n } else if (key && typeof key === 'object') {\n cache.byRef.set(key, height);\n }\n}\n\n// #endregion\n\n// #region Position Cache Operations\n\n/**\n * Rebuild position cache preserving known heights from height cache.\n * Called when row count changes (expand/collapse, filter, data change).\n *\n * @param rows - Array of row data\n * @param heightCache - Height cache with persisted measurements\n * @param estimatedHeight - Estimated height for unmeasured rows\n * @param config - Position cache configuration\n * @param getPluginHeight - Optional function to get height from plugins\n * @returns New position cache\n */\nexport function rebuildPositionCache<T>(\n rows: T[],\n heightCache: HeightCache,\n estimatedHeight: number,\n config: PositionCacheConfig<T>,\n getPluginHeight?: (row: T, index: number) => number | undefined,\n): RowPosition[] {\n const cache: RowPosition[] = new Array(rows.length);\n let offset = 0;\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n\n // Height resolution order:\n // 1. Plugin's getRowHeight() (for synthetic rows with known heights)\n let height = getPluginHeight?.(row, i);\n let measured = height !== undefined;\n\n // 2. Cached height from previous measurements\n if (height === undefined) {\n height = getCachedHeight(heightCache, row, config.rowId);\n measured = height !== undefined;\n }\n\n // 3. Fall back to estimate\n if (height === undefined) {\n height = estimatedHeight;\n measured = false;\n }\n\n cache[i] = { offset, height, measured };\n offset += height;\n }\n\n return cache;\n}\n\n/**\n * Update a single row's height in the position cache.\n * Recalculates offsets for all subsequent rows.\n *\n * @param cache - Position cache to update\n * @param index - Row index to update\n * @param newHeight - New measured height\n */\nexport function updateRowHeight(cache: RowPosition[], index: number, newHeight: number): void {\n if (index < 0 || index >= cache.length) return;\n\n const entry = cache[index];\n const heightDiff = newHeight - entry.height;\n\n if (heightDiff === 0) return;\n\n // Update this row\n entry.height = newHeight;\n entry.measured = true;\n\n // Recalculate offsets for all subsequent rows\n for (let i = index + 1; i < cache.length; i++) {\n cache[i].offset += heightDiff;\n }\n}\n\n/**\n * Get total content height from position cache.\n *\n * @param cache - Position cache\n * @returns Total height in pixels\n */\nexport function getTotalHeight(cache: RowPosition[]): number {\n if (cache.length === 0) return 0;\n const last = cache[cache.length - 1];\n return last.offset + last.height;\n}\n\n// #endregion\n\n// #region Binary Search\n\n/**\n * Find the row index at a given scroll offset using binary search.\n * Returns the index of the row that contains the given pixel offset.\n *\n * @param cache - Position cache\n * @param targetOffset - Scroll offset in pixels\n * @returns Row index at that offset, or -1 if cache is empty\n */\nexport function getRowIndexAtOffset(cache: RowPosition[], targetOffset: number): number {\n if (cache.length === 0) return -1;\n if (targetOffset <= 0) return 0;\n\n let low = 0;\n let high = cache.length - 1;\n\n while (low <= high) {\n const mid = Math.floor((low + high) / 2);\n const entry = cache[mid];\n const entryEnd = entry.offset + entry.height;\n\n if (targetOffset < entry.offset) {\n high = mid - 1;\n } else if (targetOffset >= entryEnd) {\n low = mid + 1;\n } else {\n // targetOffset is within this row\n return mid;\n }\n }\n\n // Return the closest row (low will be just past the target)\n return Math.max(0, Math.min(low, cache.length - 1));\n}\n\n// #endregion\n\n// #region Statistics\n\n/**\n * Calculate the average measured height.\n * Used for estimating unmeasured rows.\n *\n * @param cache - Position cache\n * @param defaultHeight - Default height to use if no measurements\n * @returns Average measured height\n */\nexport function calculateAverageHeight(cache: RowPosition[], defaultHeight: number): number {\n let totalHeight = 0;\n let measuredCount = 0;\n\n for (const entry of cache) {\n if (entry.measured) {\n totalHeight += entry.height;\n measuredCount++;\n }\n }\n\n return measuredCount > 0 ? totalHeight / measuredCount : defaultHeight;\n}\n\n/**\n * Count how many rows have been measured.\n *\n * @param cache - Position cache\n * @returns Number of measured rows\n */\nexport function countMeasuredRows(cache: RowPosition[]): number {\n let count = 0;\n for (const entry of cache) {\n if (entry.measured) count++;\n }\n return count;\n}\n\n// #endregion\n// #region Row Measurement\n\n/**\n * Result of measuring rendered row heights.\n */\nexport interface RowMeasurementResult {\n /** Whether any heights changed */\n hasChanges: boolean;\n /** Updated measured row count */\n measuredCount: number;\n /** Updated average height */\n averageHeight: number;\n}\n\n/**\n * Context for measuring rendered rows.\n */\nexport interface RowMeasurementContext<T> {\n /** Position cache to update */\n positionCache: RowPosition[];\n /** Height cache for persistence */\n heightCache: HeightCache;\n /** Row data array */\n rows: T[];\n /** Default row height */\n defaultHeight: number;\n /** Start index of rendered window */\n start: number;\n /** End index of rendered window (exclusive) */\n end: number;\n /** Function to get plugin height for a row */\n getPluginHeight?: (row: T, index: number) => number | undefined;\n /** Function to get row ID for height cache keying */\n getRowId?: (row: T) => string | number;\n}\n\n/**\n * Measure rendered row heights from DOM elements and update caches.\n * Returns measurement statistics for updating virtualization state.\n *\n * @param context - Measurement context with all dependencies\n * @param rowElements - NodeList of rendered row elements\n * @returns Measurement result with flags and statistics\n */\nexport function measureRenderedRowHeights<T>(\n context: RowMeasurementContext<T>,\n rowElements: NodeListOf<Element>,\n): RowMeasurementResult {\n const { positionCache, heightCache, rows, start, end, getPluginHeight, getRowId } = context;\n\n let hasChanges = false;\n\n rowElements.forEach((rowEl) => {\n const rowIndexStr = (rowEl as HTMLElement).dataset.rowIndex;\n if (!rowIndexStr) return;\n\n const rowIndex = parseInt(rowIndexStr, 10);\n if (rowIndex < start || rowIndex >= end || rowIndex >= rows.length) return;\n\n const row = rows[rowIndex];\n\n // Check if a plugin provides the height for this row\n const pluginHeight = getPluginHeight?.(row, rowIndex);\n\n if (pluginHeight !== undefined) {\n // Plugin provides height - use it for position cache\n const currentEntry = positionCache[rowIndex];\n if (!currentEntry.measured || Math.abs(currentEntry.height - pluginHeight) > 1) {\n updateRowHeight(positionCache, rowIndex, pluginHeight);\n hasChanges = true;\n }\n return; // Don't measure DOM for plugin-managed rows\n }\n\n // No plugin height - use DOM measurement\n const measuredHeight = (rowEl as HTMLElement).offsetHeight;\n\n if (measuredHeight > 0) {\n const currentEntry = positionCache[rowIndex];\n\n // Only update if height differs significantly (> 1px to avoid oscillation)\n if (!currentEntry.measured || Math.abs(currentEntry.height - measuredHeight) > 1) {\n updateRowHeight(positionCache, rowIndex, measuredHeight);\n setCachedHeight(heightCache, row, measuredHeight, getRowId);\n hasChanges = true;\n }\n }\n });\n\n // Recompute stats\n const measuredCount = hasChanges ? countMeasuredRows(positionCache) : 0;\n const averageHeight = hasChanges ? calculateAverageHeight(positionCache, context.defaultHeight) : 0;\n\n return { hasChanges, measuredCount, averageHeight };\n}\n\n/**\n * Compute measurement statistics for a position cache, excluding plugin-managed rows.\n * Used after rebuilding position cache to get accurate averages for non-plugin rows.\n *\n * @param cache - Position cache\n * @param rows - Row data array\n * @param defaultHeight - Default height if no measurements\n * @param getPluginHeight - Function to check if plugin manages row height\n * @returns Object with measured count and average height\n */\nexport function computeAverageExcludingPluginRows<T>(\n cache: RowPosition[],\n rows: T[],\n defaultHeight: number,\n getPluginHeight?: (row: T, index: number) => number | undefined,\n): { measuredCount: number; averageHeight: number } {\n let measuredCount = 0;\n let totalMeasured = 0;\n\n for (let i = 0; i < cache.length; i++) {\n const entry = cache[i];\n if (entry.measured) {\n // Only include in average if plugin doesn't provide height for this row\n const pluginHeight = getPluginHeight?.(rows[i], i);\n if (pluginHeight === undefined) {\n totalMeasured += entry.height;\n measuredCount++;\n }\n }\n }\n\n return {\n measuredCount,\n averageHeight: measuredCount > 0 ? totalMeasured / measuredCount : defaultHeight,\n };\n}\n\n// #endregion\n\n// #region Fixed-Height Virtual Window\n\n/**\n * Result of computing a virtual window for fixed-height rows.\n * Used by plugins like FilteringPlugin for virtualized dropdowns.\n */\nexport interface VirtualWindow {\n /** First row index to render (inclusive) */\n start: number;\n /** Last row index to render (exclusive) */\n end: number;\n /** Pixel offset to apply to the rows container (translateY) */\n offsetY: number;\n /** Total height of the scrollable content */\n totalHeight: number;\n}\n\n/** Parameters for computing the virtual window */\nexport interface VirtualWindowParams {\n /** Total number of rows */\n totalRows: number;\n /** Height of the viewport in pixels */\n viewportHeight: number;\n /** Current scroll top position */\n scrollTop: number;\n /** Height of each row in pixels */\n rowHeight: number;\n /** Number of extra rows to render above/below viewport */\n overscan: number;\n}\n\n/**\n * Compute the virtual row window based on scroll position and viewport.\n * Simple fixed-height implementation for plugins needing basic virtualization.\n *\n * @param params - Parameters for computing the window\n * @returns VirtualWindow with start/end indices and transforms\n */\nexport function computeVirtualWindow(params: VirtualWindowParams): VirtualWindow {\n const { totalRows, viewportHeight, scrollTop, rowHeight, overscan } = params;\n\n const visibleRows = Math.ceil(viewportHeight / rowHeight);\n\n // Render overscan rows in each direction (total window = visible + 2*overscan)\n let start = Math.floor(scrollTop / rowHeight) - overscan;\n if (start < 0) start = 0;\n\n let end = start + visibleRows + overscan * 2;\n if (end > totalRows) end = totalRows;\n\n // Ensure start is adjusted if we hit the end\n if (end === totalRows && start > 0) {\n start = Math.max(0, end - visibleRows - overscan * 2);\n }\n\n return {\n start,\n end,\n offsetY: start * rowHeight,\n totalHeight: totalRows * rowHeight,\n };\n}\n\n/**\n * Determine if virtualization should be bypassed for small datasets.\n * When there are very few items, the overhead of virtualization isn't worth it.\n *\n * @param totalRows - Total number of items\n * @param threshold - Bypass threshold (skip virtualization if totalRows <= threshold)\n * @returns True if virtualization should be bypassed\n */\nexport function shouldBypassVirtualization(totalRows: number, threshold: number): boolean {\n return totalRows <= threshold;\n}\n\n// #endregion\n","/**\n * VirtualizationManager — encapsulates all virtualization state and logic\n * that was previously inline in the DataGridElement class.\n *\n * Owns the VirtualState, position/height caches, and the core\n * refreshVirtualWindow algorithm. Takes the grid reference directly\n * (tightly coupled — this manager can never live outside the grid).\n */\nimport type { InternalGrid, VirtualState } from '../types';\nimport { RenderPhase } from './render-scheduler';\nimport {\n computeAverageExcludingPluginRows,\n getRowIndexAtOffset,\n getTotalHeight,\n measureRenderedRowHeights,\n rebuildPositionCache,\n updateRowHeight,\n} from './virtualization';\n\n// #region VirtualizationManager\n\nexport class VirtualizationManager<T = any> {\n readonly #grid: InternalGrid<T>;\n\n // The full virtualization state — still a plain object so plugins can read\n // fields directly via `grid._virtualization` (they access the same reference).\n readonly state: VirtualState;\n\n constructor(grid: InternalGrid<T>, initialState?: Partial<VirtualState>) {\n this.#grid = grid;\n this.state = {\n enabled: true,\n rowHeight: 28,\n bypassThreshold: 24,\n start: 0,\n end: 0,\n container: null,\n viewportEl: null,\n totalHeightEl: null,\n positionCache: null,\n heightCache: {\n byKey: new Map<string, number>(),\n byRef: new WeakMap<object, number>(),\n },\n averageHeight: 28,\n measuredCount: 0,\n variableHeights: false,\n cachedViewportHeight: 0,\n cachedFauxHeight: 0,\n cachedScrollAreaHeight: 0,\n scrollAreaEl: null,\n ...initialState,\n };\n }\n\n // #region Cached Geometry\n\n /**\n * Update cached viewport and faux scrollbar geometry.\n * Called by ResizeObserver and on force-refresh to avoid forced layout reads during scroll.\n */\n updateCachedGeometry(): void {\n const s = this.state;\n const fauxScrollbar = s.container;\n const viewportEl = s.viewportEl ?? fauxScrollbar;\n if (viewportEl) {\n s.cachedViewportHeight = viewportEl.clientHeight;\n }\n if (fauxScrollbar) {\n s.cachedFauxHeight = fauxScrollbar.clientHeight;\n }\n const scrollAreaEl = s.scrollAreaEl;\n if (scrollAreaEl) {\n s.cachedScrollAreaHeight = scrollAreaEl.clientHeight;\n }\n }\n\n // #endregion\n\n // #region Spacer Height\n\n /**\n * Calculate total height for the faux scrollbar spacer element.\n * Used by both bypass and virtualized rendering paths to ensure consistent scroll behavior.\n *\n * @param totalRows - Total number of rows to calculate height for\n * @param forceRead - When true, reads fresh geometry from DOM (used after structural changes).\n * When false, uses cached values from ResizeObserver to avoid forced synchronous layout.\n */\n calculateTotalSpacerHeight(totalRows: number, forceRead = false): number {\n const s = this.state;\n\n let fauxScrollHeight: number;\n let viewportHeight: number;\n let scrollAreaHeight: number;\n\n if (forceRead) {\n const fauxScrollbar = s.container ?? this.#grid._hostElement;\n const viewportEl = s.viewportEl ?? fauxScrollbar;\n const scrollAreaEl = s.scrollAreaEl;\n\n fauxScrollHeight = fauxScrollbar?.clientHeight ?? 0;\n viewportHeight = viewportEl?.clientHeight ?? 0;\n scrollAreaHeight = scrollAreaEl ? scrollAreaEl.clientHeight : fauxScrollHeight;\n\n s.cachedFauxHeight = fauxScrollHeight;\n s.cachedViewportHeight = viewportHeight;\n s.cachedScrollAreaHeight = scrollAreaHeight;\n } else {\n fauxScrollHeight = s.cachedFauxHeight;\n viewportHeight = s.cachedViewportHeight;\n scrollAreaHeight = s.cachedScrollAreaHeight || fauxScrollHeight;\n }\n\n const viewportHeightDiff = scrollAreaHeight - viewportHeight;\n const hScrollbarPadding = Math.max(0, fauxScrollHeight - scrollAreaHeight);\n\n let rowContentHeight: number;\n let pluginExtraHeight = 0;\n\n if (s.variableHeights && s.positionCache) {\n rowContentHeight = getTotalHeight(s.positionCache);\n } else {\n rowContentHeight = totalRows * s.rowHeight;\n pluginExtraHeight = this.#grid._getPluginExtraHeight();\n }\n\n return rowContentHeight + viewportHeightDiff + pluginExtraHeight + hScrollbarPadding;\n }\n\n // #endregion\n\n // #region Position Cache\n\n /**\n * Initialize or rebuild the position cache for variable row heights.\n * Called when rows change or variable heights mode is enabled.\n */\n initializePositionCache(): void {\n const s = this.state;\n if (!s.variableHeights) return;\n\n const grid = this.#grid;\n const rows = grid._rows;\n const estimatedHeight = s.rowHeight || 28;\n const rowHeightFn = grid.effectiveConfig?.rowHeight as\n | ((row: T, index: number) => number | undefined)\n | undefined;\n const getRowId = grid.effectiveConfig?.getRowId;\n const rowIdFn = getRowId ? (row: T) => getRowId(row) : undefined;\n\n s.positionCache = rebuildPositionCache(rows, s.heightCache, estimatedHeight, { rowId: rowIdFn }, (row, index) => {\n const pluginHeight = grid._getPluginRowHeight(row, index);\n if (pluginHeight !== undefined) return pluginHeight;\n if (rowHeightFn) {\n const height = rowHeightFn(row, index);\n if (height !== undefined && height > 0) return height;\n }\n return undefined;\n });\n\n const stats = computeAverageExcludingPluginRows(s.positionCache, rows, estimatedHeight, (row, index) =>\n grid._getPluginRowHeight(row, index),\n );\n s.measuredCount = stats.measuredCount;\n if (stats.measuredCount > 0) {\n s.averageHeight = stats.averageHeight;\n }\n }\n\n /**\n * Invalidate a row's height in the position cache.\n * Call this when a plugin changes a row's height (e.g., expanding/collapsing a detail panel).\n * Updates the position cache incrementally O(1) + offset recalc O(k) without a full rebuild.\n *\n * @param rowIndex - Index of the row whose height changed\n * @param newHeight - Optional new height. If not provided, queries plugins for height.\n */\n invalidateRowHeight(rowIndex: number, newHeight?: number): void {\n const s = this.state;\n if (!s.variableHeights) return;\n if (!s.positionCache) return;\n\n const rows = this.#grid._rows;\n if (rowIndex < 0 || rowIndex >= rows.length) return;\n\n const row = rows[rowIndex];\n\n let height = newHeight;\n if (height === undefined) {\n height = this.#grid._getPluginRowHeight(row, rowIndex);\n }\n if (height === undefined) {\n height = s.rowHeight;\n }\n\n const currentEntry = s.positionCache[rowIndex];\n if (!currentEntry || Math.abs(currentEntry.height - height) < 1) {\n return;\n }\n\n updateRowHeight(s.positionCache, rowIndex, height);\n\n if (s.totalHeightEl) {\n const newTotalHeight = this.calculateTotalSpacerHeight(rows.length);\n s.totalHeightEl.style.height = `${newTotalHeight}px`;\n }\n }\n\n // #endregion\n\n // #region Row Measurement\n\n /**\n * Measure rendered row heights and update position cache.\n * Called after rows are rendered to capture actual DOM heights.\n * Only runs when variable heights mode is enabled.\n */\n measureRenderedRowHeights(start: number, end: number): void {\n const s = this.state;\n if (!s.variableHeights) return;\n if (!s.positionCache) return;\n\n const grid = this.#grid;\n const bodyEl = grid._bodyEl;\n if (!bodyEl) return;\n\n const rowElements = bodyEl.querySelectorAll('.data-grid-row');\n const getRowId = grid.effectiveConfig?.getRowId;\n const rows = grid._rows;\n\n const result = measureRenderedRowHeights(\n {\n positionCache: s.positionCache,\n heightCache: s.heightCache,\n rows,\n defaultHeight: s.rowHeight,\n start,\n end,\n getPluginHeight: (row, index) => grid._getPluginRowHeight(row, index),\n getRowId: getRowId ? (row: T) => getRowId(row) : undefined,\n },\n rowElements,\n );\n\n if (result.hasChanges) {\n s.measuredCount = result.measuredCount;\n s.averageHeight = result.averageHeight;\n\n if (s.totalHeightEl) {\n const newTotalHeight = this.calculateTotalSpacerHeight(rows.length);\n s.totalHeightEl.style.height = `${newTotalHeight}px`;\n }\n }\n }\n\n // #endregion\n\n // #region Core Virtual Window\n\n /**\n * Core virtualization routine. Chooses between bypass (small datasets), grouped window rendering,\n * or standard row window rendering.\n * @param force - Whether to force a full refresh (not just scroll update)\n * @param skipAfterRender - When true, skip calling afterRender (used by scheduler which calls it separately)\n * @returns Whether the visible row window changed (start/end differ from previous)\n */\n refreshVirtualWindow(force = false, skipAfterRender = false): boolean {\n const s = this.state;\n const grid = this.#grid;\n const bodyEl = grid._bodyEl;\n if (!bodyEl) return false;\n\n const totalRows = grid._rows.length;\n\n if (!s.enabled) {\n grid._renderVisibleRows(0, totalRows);\n if (!skipAfterRender) {\n grid._afterPluginRender();\n }\n return true;\n }\n\n if (totalRows <= s.bypassThreshold) {\n s.start = 0;\n s.end = totalRows;\n if (force) {\n bodyEl.style.transform = 'translateY(0px)';\n }\n grid._renderVisibleRows(0, totalRows, grid.__rowRenderEpoch);\n if (force && s.totalHeightEl) {\n s.totalHeightEl.style.height = `${this.calculateTotalSpacerHeight(totalRows, true)}px`;\n }\n grid._updateAriaCounts(totalRows, grid._visibleColumns.length);\n if (!skipAfterRender) {\n grid._afterPluginRender();\n }\n return true;\n }\n\n // --- Normal virtualization path with faux scrollbar pattern ---\n const fauxScrollbar = s.container!;\n const viewportEl = s.viewportEl ?? fauxScrollbar;\n\n const viewportHeight = force\n ? (s.cachedViewportHeight = viewportEl.clientHeight)\n : s.cachedViewportHeight || (s.cachedViewportHeight = viewportEl.clientHeight);\n const rowHeight = s.rowHeight;\n const scrollTop = fauxScrollbar.scrollTop;\n\n // On force refresh with variable heights, rebuild the position cache\n // to pick up any height changes from plugins (e.g., ResponsivePlugin\n // measuring actual card heights from DOM after first render).\n if (force && s.variableHeights) {\n this.initializePositionCache();\n }\n\n let start: number;\n const positionCache = s.positionCache;\n\n // Variable row heights: use binary search on position cache\n if (s.variableHeights && positionCache && positionCache.length > 0) {\n start = getRowIndexAtOffset(positionCache, scrollTop);\n if (start === -1) start = 0;\n } else {\n start = Math.floor(scrollTop / rowHeight);\n\n let iterations = 0;\n const maxIterations = 10;\n while (iterations < maxIterations) {\n const extraHeightBefore = grid._getPluginExtraHeightBefore(start);\n const adjustedStart = Math.floor((scrollTop - extraHeightBefore) / rowHeight);\n if (adjustedStart >= start || adjustedStart < 0) break;\n start = adjustedStart;\n iterations++;\n }\n }\n\n // Round down to even number for zebra stripe parity\n start = start - (start % 2);\n if (start < 0) start = 0;\n\n // Allow plugins to extend the start index backwards\n const pluginAdjustedStart = grid._adjustPluginVirtualStart(start, scrollTop, rowHeight);\n if (pluginAdjustedStart !== undefined && pluginAdjustedStart < start) {\n start = pluginAdjustedStart;\n start = start - (start % 2);\n if (start < 0) start = 0;\n }\n\n // Calculate end of visible window\n let end: number;\n\n if (s.variableHeights && positionCache && positionCache.length > 0) {\n const targetHeight = viewportHeight + rowHeight * 3; // 3 rows overscan\n let accumulatedHeight = 0;\n end = start;\n\n while (end < totalRows && accumulatedHeight < targetHeight) {\n accumulatedHeight += positionCache[end].height;\n end++;\n }\n\n const minRows = Math.ceil(viewportHeight / rowHeight) + 3;\n if (end - start < minRows) {\n end = Math.min(start + minRows, totalRows);\n }\n } else {\n const visibleCount = Math.ceil(viewportHeight / rowHeight) + 3;\n end = start + visibleCount;\n }\n\n if (end > totalRows) end = totalRows;\n\n // Early-exit: visible window unchanged and not force\n const prevStart = s.start;\n const prevEnd = s.end;\n if (!force && start === prevStart && end === prevEnd) {\n return false;\n }\n\n s.start = start;\n s.end = end;\n\n // Read faux scrollbar height (cached on scroll path, fresh on force)\n const fauxScrollHeight = force\n ? (s.cachedFauxHeight = fauxScrollbar.clientHeight)\n : s.cachedFauxHeight || (s.cachedFauxHeight = fauxScrollbar.clientHeight);\n\n if (force) {\n const scrollAreaEl = s.scrollAreaEl;\n if (scrollAreaEl) {\n s.cachedScrollAreaHeight = scrollAreaEl.clientHeight;\n }\n }\n\n // Guard: stale DOM references\n if (fauxScrollHeight === 0 && viewportHeight > 0) {\n grid._requestSchedulerPhase(RenderPhase.VIRTUALIZATION, 'stale-refs-retry');\n return false;\n }\n\n // Recalculate spacer height on force refresh\n if (force && s.totalHeightEl) {\n const totalHeight = this.calculateTotalSpacerHeight(totalRows);\n s.totalHeightEl.style.height = `${totalHeight}px`;\n }\n\n // Calculate sub-pixel transform offset\n let startRowOffset: number;\n if (s.variableHeights && positionCache && positionCache[start]) {\n startRowOffset = positionCache[start].offset;\n } else {\n const extraHeightBeforeStart = grid._getPluginExtraHeightBefore(start);\n startRowOffset = start * rowHeight + extraHeightBeforeStart;\n }\n\n const subPixelOffset = -(scrollTop - startRowOffset);\n bodyEl.style.transform = `translateY(${subPixelOffset}px)`;\n\n grid._renderVisibleRows(start, end, grid.__rowRenderEpoch);\n\n // Measure rendered row heights on force refresh\n if (force && s.variableHeights) {\n this.measureRenderedRowHeights(start, end);\n }\n\n grid._updateAriaCounts(totalRows, grid._visibleColumns.length);\n\n // Run plugin afterRender hooks on force refresh\n if (force && !skipAfterRender) {\n grid._afterPluginRender();\n\n // Recalculate spacer height in microtask to catch plugin DOM changes\n queueMicrotask(() => {\n if (!s.totalHeightEl) return;\n const newTotalHeight = this.calculateTotalSpacerHeight(totalRows);\n if (s.cachedFauxHeight === 0 && s.cachedViewportHeight > 0) return;\n s.totalHeightEl.style.height = `${newTotalHeight}px`;\n });\n }\n\n return true;\n }\n\n // #endregion\n}\n\n// #endregion\n","/**\n * Plugin Manager\n *\n * Manages plugin instances for a single grid.\n * Each grid has its own PluginManager with its own set of plugin instances.\n *\n * **Plugin Order Matters**: Plugins are executed in the order they appear in the\n * `plugins` array. This affects the order of hook execution (processRows, processColumns,\n * afterRender, etc.). For example, if you want filtering to run before grouping,\n * add FilteringPlugin before GroupingRowsPlugin in the array.\n */\n\nimport { DEPRECATED_HOOK, PLUGIN_EVENT_ERROR, errorDiagnostic, warnDiagnostic } from '../internal/diagnostics';\nimport { isDevelopment } from '../internal/utils';\nimport { validatePluginDependencies } from '../internal/validate-config';\nimport type { ColumnConfig } from '../types';\nimport type {\n AfterCellRenderContext,\n AfterRowRenderContext,\n BaseGridPlugin,\n CellClickEvent,\n CellEditor,\n CellMouseEvent,\n CellRenderer,\n GridElement,\n HeaderClickEvent,\n HeaderRenderer,\n PluginQuery,\n RowClickEvent,\n ScrollEvent,\n} from './base-plugin';\n\n/**\n * Manages plugins for a single grid instance.\n *\n * Plugins are executed in array order. This is intentional and documented behavior.\n * Place plugins in the order you want their hooks to execute.\n */\nexport class PluginManager {\n // #region Properties\n /** Plugin instances in order of attachment */\n private plugins: BaseGridPlugin[] = [];\n\n /** Get all registered plugins (read-only) */\n getPlugins(): readonly BaseGridPlugin[] {\n return this.plugins;\n }\n\n /** Map from plugin class to instance for fast lookup */\n private pluginMap: Map<new (...args: unknown[]) => BaseGridPlugin, BaseGridPlugin> = new Map();\n\n /** Cell renderers registered by plugins */\n private cellRenderers: Map<string, CellRenderer> = new Map();\n\n /** Header renderers registered by plugins */\n private headerRenderers: Map<string, HeaderRenderer> = new Map();\n\n /** Cell editors registered by plugins */\n private cellEditors: Map<string, CellEditor> = new Map();\n\n /** Cached hook presence flags — invalidated on plugin attach/detach */\n private _hasAfterCellRender = false;\n private _hasAfterRowRender = false;\n // #endregion\n\n // #region Event Bus State\n /**\n * Event listeners indexed by event type.\n * Maps event type → Map<plugin instance → callback>.\n * Using plugin instance as key enables auto-cleanup on detach.\n */\n private eventListeners: Map<string, Map<BaseGridPlugin, (detail: unknown) => void>> = new Map();\n // #endregion\n\n // #region Query System State\n /**\n * Query handlers indexed by query type.\n * Maps query type → Set of plugin instances that declare handling it.\n * Built from manifest.queries during plugin attach.\n */\n private queryHandlers: Map<string, Set<BaseGridPlugin>> = new Map();\n // #endregion\n\n // #region Deprecation Warnings\n /** Set of plugin constructors that have been warned about deprecated hooks */\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type -- WeakSet key is plugin.constructor identity\n private static deprecationWarned = new WeakSet<Function>();\n // #endregion\n\n // #region Lifecycle\n constructor(private grid: GridElement) {}\n\n /**\n * Attach all plugins from the config.\n */\n attachAll(plugins: BaseGridPlugin[]): void {\n for (const plugin of plugins) {\n this.attach(plugin);\n }\n }\n\n /**\n * Attach a plugin to this grid.\n * Validates dependencies and notifies other plugins of the new attachment.\n */\n attach(plugin: BaseGridPlugin): void {\n // Validate plugin dependencies BEFORE attaching\n // This throws if a required dependency is missing\n validatePluginDependencies(plugin, this.plugins, this.grid.getAttribute('id') ?? undefined);\n\n // Store by constructor for type-safe lookup\n this.pluginMap.set(plugin.constructor as new (...args: unknown[]) => BaseGridPlugin, plugin);\n this.plugins.push(plugin);\n\n // Register renderers/editors\n if (plugin.cellRenderers) {\n for (const [type, renderer] of Object.entries(plugin.cellRenderers)) {\n this.cellRenderers.set(type, renderer);\n }\n }\n if (plugin.headerRenderers) {\n for (const [type, renderer] of Object.entries(plugin.headerRenderers)) {\n this.headerRenderers.set(type, renderer);\n }\n }\n if (plugin.cellEditors) {\n for (const [type, editor] of Object.entries(plugin.cellEditors)) {\n this.cellEditors.set(type, editor);\n }\n }\n\n // Register query handlers from manifest\n this.registerQueryHandlers(plugin);\n\n // Warn about deprecated hooks (once per plugin class)\n this.warnDeprecatedHooks(plugin);\n\n // Call attach lifecycle method\n plugin.attach(this.grid);\n\n // Invalidate hook caches\n this.#invalidateHookCaches();\n\n // Notify existing plugins of the new attachment\n for (const existingPlugin of this.plugins) {\n if (existingPlugin !== plugin && existingPlugin.onPluginAttached) {\n existingPlugin.onPluginAttached(plugin.name, plugin);\n }\n }\n }\n\n /**\n * Register query handlers from a plugin's manifest.\n */\n private registerQueryHandlers(plugin: BaseGridPlugin): void {\n const PluginClass = plugin.constructor as typeof BaseGridPlugin;\n const manifest = PluginClass.manifest;\n if (!manifest?.queries) return;\n\n for (const queryDef of manifest.queries) {\n let handlers = this.queryHandlers.get(queryDef.type);\n if (!handlers) {\n handlers = new Set();\n this.queryHandlers.set(queryDef.type, handlers);\n }\n handlers.add(plugin);\n }\n }\n\n /**\n * Warn about deprecated plugin hooks.\n * Only warns once per plugin class, only in development environments.\n */\n private warnDeprecatedHooks(plugin: BaseGridPlugin): void {\n const PluginClass = plugin.constructor;\n\n // Skip if already warned for this plugin class\n if (PluginManager.deprecationWarned.has(PluginClass)) return;\n\n // Only warn in development\n if (!isDevelopment()) return;\n\n const hasOldHooks =\n typeof plugin.getExtraHeight === 'function' || typeof plugin.getExtraHeightBefore === 'function';\n\n const hasNewHook = typeof plugin.getRowHeight === 'function';\n\n // Warn if using old hooks without new hook\n if (hasOldHooks && !hasNewHook) {\n PluginManager.deprecationWarned.add(PluginClass);\n warnDiagnostic(\n DEPRECATED_HOOK,\n `\"${plugin.name}\" uses getExtraHeight() / getExtraHeightBefore() ` +\n `which are deprecated and will be removed in v2.0.\\n` +\n ` → Migrate to getRowHeight(row, index) for better variable row height support.`,\n this.grid.getAttribute('id') ?? undefined,\n );\n }\n }\n\n /**\n * Unregister query handlers for a plugin.\n */\n private unregisterQueryHandlers(plugin: BaseGridPlugin): void {\n for (const [queryType, handlers] of this.queryHandlers) {\n handlers.delete(plugin);\n if (handlers.size === 0) {\n this.queryHandlers.delete(queryType);\n }\n }\n }\n\n /**\n * Detach all plugins and clean up.\n * Notifies plugins of detachment via onPluginDetached hook.\n */\n detachAll(): void {\n // Notify all plugins before detaching (in forward order)\n for (const plugin of this.plugins) {\n for (const otherPlugin of this.plugins) {\n if (otherPlugin !== plugin && otherPlugin.onPluginDetached) {\n otherPlugin.onPluginDetached(plugin.name);\n }\n }\n }\n\n // Detach in reverse order\n for (let i = this.plugins.length - 1; i >= 0; i--) {\n const plugin = this.plugins[i];\n this.unsubscribeAll(plugin); // Clean up event subscriptions\n this.unregisterQueryHandlers(plugin); // Clean up query handlers\n plugin.detach();\n }\n this.plugins = [];\n this.pluginMap.clear();\n this.cellRenderers.clear();\n this.headerRenderers.clear();\n this.cellEditors.clear();\n this.eventListeners.clear();\n this.queryHandlers.clear();\n this._hasAfterCellRender = false;\n this._hasAfterRowRender = false;\n }\n // #endregion\n\n // #region Plugin Lookup\n /**\n * Get a plugin instance by its class.\n */\n getPlugin<T extends BaseGridPlugin>(PluginClass: new (...args: any[]) => T): T | undefined {\n return this.pluginMap.get(PluginClass) as T | undefined;\n }\n\n /**\n * Get a plugin instance by its name.\n */\n getPluginByName(name: string): BaseGridPlugin | undefined {\n return this.plugins.find((p) => p.name === name || p.aliases?.includes(name));\n }\n\n /**\n * Check if a plugin is attached.\n */\n hasPlugin<T extends BaseGridPlugin>(PluginClass: new (...args: any[]) => T): boolean {\n return this.pluginMap.has(PluginClass);\n }\n\n /**\n * Get all attached plugins.\n */\n getAll(): readonly BaseGridPlugin[] {\n return this.plugins;\n }\n\n /**\n * Get names of all registered plugins (for debugging).\n */\n getRegisteredPluginNames(): string[] {\n return this.plugins.map((p) => p.name);\n }\n // #endregion\n\n // #region Renderers & Styles\n /**\n * Get a cell renderer by type name.\n */\n getCellRenderer(type: string): CellRenderer | undefined {\n return this.cellRenderers.get(type);\n }\n\n /**\n * Get a header renderer by type name.\n */\n getHeaderRenderer(type: string): HeaderRenderer | undefined {\n return this.headerRenderers.get(type);\n }\n\n /**\n * Get a cell editor by type name.\n */\n getCellEditor(type: string): CellEditor | undefined {\n return this.cellEditors.get(type);\n }\n\n /**\n * Get all CSS styles from all plugins as structured data.\n * Returns an array of { name, styles } for each plugin with styles.\n */\n getPluginStyles(): Array<{ name: string; styles: string }> {\n return this.plugins.filter((p) => p.styles).map((p) => ({ name: p.name, styles: p.styles! }));\n }\n // #endregion\n\n // #region Hook Execution\n\n /**\n * Execute processRows hook on all plugins.\n */\n processRows(rows: readonly any[]): any[] {\n let result = [...rows];\n for (const plugin of this.plugins) {\n if (plugin.processRows) {\n result = plugin.processRows(result);\n }\n }\n return result;\n }\n\n /**\n * Execute processColumns hook on all plugins.\n */\n processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n let result = [...columns];\n for (const plugin of this.plugins) {\n if (plugin.processColumns) {\n result = plugin.processColumns(result);\n }\n }\n return result;\n }\n\n /**\n * Execute beforeRender hook on all plugins.\n */\n beforeRender(): void {\n for (const plugin of this.plugins) {\n plugin.beforeRender?.();\n }\n }\n\n /**\n * Execute afterRender hook on all plugins.\n */\n afterRender(): void {\n for (const plugin of this.plugins) {\n plugin.afterRender?.();\n }\n }\n\n /**\n * Execute afterCellRender hook on all plugins for a single cell.\n * Called during cell rendering for efficient cell-level modifications.\n *\n * @param context - The cell render context\n */\n afterCellRender(context: AfterCellRenderContext): void {\n for (const plugin of this.plugins) {\n plugin.afterCellRender?.(context);\n }\n }\n\n /**\n * Check if any plugin has the afterCellRender hook implemented.\n * Cached — invalidated on plugin attach/detach.\n */\n hasAfterCellRenderHook(): boolean {\n return this._hasAfterCellRender;\n }\n\n /**\n * Execute afterRowRender hook on all plugins for a single row.\n * Called after all cells in a row are rendered for efficient row-level modifications.\n *\n * @param context - The row render context\n */\n afterRowRender(context: AfterRowRenderContext): void {\n for (const plugin of this.plugins) {\n plugin.afterRowRender?.(context);\n }\n }\n\n /**\n * Check if any plugin has the afterRowRender hook implemented.\n * Cached — invalidated on plugin attach/detach.\n */\n hasAfterRowRenderHook(): boolean {\n return this._hasAfterRowRender;\n }\n\n /** Recompute cached hook presence flags. */\n #invalidateHookCaches(): void {\n this._hasAfterCellRender = this.plugins.some((p) => typeof p.afterCellRender === 'function');\n this._hasAfterRowRender = this.plugins.some((p) => typeof p.afterRowRender === 'function');\n }\n\n /**\n * Execute onScrollRender hook on all plugins.\n * Called after scroll-triggered row rendering for lightweight visual state updates.\n */\n onScrollRender(): void {\n for (const plugin of this.plugins) {\n plugin.onScrollRender?.();\n }\n }\n\n /**\n * Get total extra height contributed by plugins (e.g., expanded detail rows).\n * Used to adjust scrollbar height calculations.\n */\n getExtraHeight(): number {\n let total = 0;\n for (const plugin of this.plugins) {\n if (typeof plugin.getExtraHeight === 'function') {\n total += plugin.getExtraHeight();\n }\n }\n return total;\n }\n\n /**\n * Check if any plugin is contributing extra height.\n * When true, plugins are managing variable row heights and the grid should\n * not override the base row height via #measureRowHeight().\n */\n hasExtraHeight(): boolean {\n for (const plugin of this.plugins) {\n if (typeof plugin.getExtraHeight === 'function' && plugin.getExtraHeight() > 0) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Get extra height from plugins that appears before a given row index.\n * Used by virtualization to correctly position the scroll window.\n */\n getExtraHeightBefore(beforeRowIndex: number): number {\n let total = 0;\n for (const plugin of this.plugins) {\n if (typeof plugin.getExtraHeightBefore === 'function') {\n total += plugin.getExtraHeightBefore(beforeRowIndex);\n }\n }\n return total;\n }\n\n /**\n * Get the height of a specific row from plugins.\n * Used for synthetic rows (group headers, etc.) that have fixed heights.\n * Returns undefined if no plugin provides a height for this row.\n */\n getRowHeight(row: unknown, index: number): number | undefined {\n for (const plugin of this.plugins) {\n if (typeof plugin.getRowHeight === 'function') {\n const height = plugin.getRowHeight(row, index);\n if (height !== undefined) {\n return height;\n }\n }\n }\n return undefined;\n }\n\n /**\n * Check if any plugin implements the getRowHeight() hook.\n * When true, the grid should use variable heights mode with position caching.\n */\n hasRowHeightPlugin(): boolean {\n for (const plugin of this.plugins) {\n if (typeof plugin.getRowHeight === 'function') {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Adjust the virtualization start index based on plugin needs.\n * Returns the minimum start index from all plugins.\n */\n adjustVirtualStart(start: number, scrollTop: number, rowHeight: number): number {\n let adjustedStart = start;\n for (const plugin of this.plugins) {\n if (typeof plugin.adjustVirtualStart === 'function') {\n const pluginStart = plugin.adjustVirtualStart(start, scrollTop, rowHeight);\n if (pluginStart < adjustedStart) {\n adjustedStart = pluginStart;\n }\n }\n }\n return adjustedStart;\n }\n\n /**\n * Execute renderRow hook on all plugins.\n * Returns true if any plugin handled the row.\n */\n renderRow(row: any, rowEl: HTMLElement, rowIndex: number): boolean {\n for (const plugin of this.plugins) {\n if (plugin.renderRow?.(row, rowEl, rowIndex)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Query all plugins with a generic query and collect responses.\n * This enables inter-plugin communication without the core knowing plugin-specific concepts.\n *\n * Uses manifest-based routing when available: only plugins that declare handling\n * the query type in their `manifest.queries` are invoked. Falls back to querying\n * all plugins for backwards compatibility with legacy plugins.\n *\n * Checks both `handleQuery` (preferred) and `onPluginQuery` (legacy) hooks.\n *\n * @param query - The query object containing type and context\n * @returns Array of non-undefined responses from plugins\n */\n queryPlugins<T>(query: PluginQuery): T[] {\n const responses: T[] = [];\n\n // Try manifest-based routing first\n const handlers = this.queryHandlers.get(query.type);\n if (handlers && handlers.size > 0) {\n // Route only to plugins that declared this query type\n for (const plugin of handlers) {\n const response = plugin.handleQuery?.(query) ?? plugin.onPluginQuery?.(query);\n if (response !== undefined) {\n responses.push(response as T);\n }\n }\n return responses;\n }\n\n // Fallback: query all plugins (legacy behavior for plugins without manifest)\n for (const plugin of this.plugins) {\n // Try handleQuery first (new API), fall back to onPluginQuery (legacy)\n const response = plugin.handleQuery?.(query) ?? plugin.onPluginQuery?.(query);\n if (response !== undefined) {\n responses.push(response as T);\n }\n }\n return responses;\n }\n // #endregion\n\n // #region Event Bus\n /**\n * Subscribe a plugin to an event type.\n * The subscription is automatically cleaned up when the plugin is detached.\n *\n * @param plugin - The subscribing plugin instance\n * @param eventType - The event type to listen for\n * @param callback - The callback to invoke when the event is emitted\n */\n subscribe(plugin: BaseGridPlugin, eventType: string, callback: (detail: unknown) => void): void {\n let listeners = this.eventListeners.get(eventType);\n if (!listeners) {\n listeners = new Map();\n this.eventListeners.set(eventType, listeners);\n }\n listeners.set(plugin, callback);\n }\n\n /**\n * Unsubscribe a plugin from an event type.\n *\n * @param plugin - The subscribing plugin instance\n * @param eventType - The event type to stop listening for\n */\n unsubscribe(plugin: BaseGridPlugin, eventType: string): void {\n const listeners = this.eventListeners.get(eventType);\n if (listeners) {\n listeners.delete(plugin);\n if (listeners.size === 0) {\n this.eventListeners.delete(eventType);\n }\n }\n }\n\n /**\n * Unsubscribe a plugin from all events.\n * Called automatically when a plugin is detached.\n *\n * @param plugin - The plugin to unsubscribe\n */\n unsubscribeAll(plugin: BaseGridPlugin): void {\n for (const [eventType, listeners] of this.eventListeners) {\n listeners.delete(plugin);\n if (listeners.size === 0) {\n this.eventListeners.delete(eventType);\n }\n }\n }\n\n /**\n * Emit an event to all subscribed plugins.\n * This is for inter-plugin communication only; it does not dispatch DOM events.\n *\n * @param eventType - The event type to emit\n * @param detail - The event payload\n */\n emitPluginEvent<T>(eventType: string, detail: T): void {\n const listeners = this.eventListeners.get(eventType);\n if (listeners) {\n for (const callback of listeners.values()) {\n try {\n callback(detail);\n } catch (error) {\n errorDiagnostic(\n PLUGIN_EVENT_ERROR,\n `Error in plugin event handler for \"${eventType}\": ${error}`,\n this.grid.getAttribute('id') ?? undefined,\n );\n }\n }\n }\n }\n // #endregion\n\n // #region Event Hooks\n /**\n * Execute onKeyDown hook on all plugins.\n * Returns true if any plugin handled the event.\n */\n onKeyDown(event: KeyboardEvent): boolean {\n for (const plugin of this.plugins) {\n if (plugin.onKeyDown?.(event)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Execute onCellClick hook on all plugins.\n * Returns true if any plugin handled the event.\n */\n onCellClick(event: CellClickEvent): boolean {\n for (const plugin of this.plugins) {\n if (plugin.onCellClick?.(event)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Execute onRowClick hook on all plugins.\n * Returns true if any plugin handled the event.\n */\n onRowClick(event: RowClickEvent): boolean {\n for (const plugin of this.plugins) {\n if (plugin.onRowClick?.(event)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Execute onHeaderClick hook on all plugins.\n * Returns true if any plugin handled the event.\n */\n onHeaderClick(event: HeaderClickEvent): boolean {\n for (const plugin of this.plugins) {\n if (plugin.onHeaderClick?.(event)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Execute onScroll hook on all plugins.\n */\n onScroll(event: ScrollEvent): void {\n for (const plugin of this.plugins) {\n plugin.onScroll?.(event);\n }\n }\n\n /**\n * Execute onCellMouseDown hook on all plugins.\n * Returns true if any plugin handled the event.\n */\n onCellMouseDown(event: CellMouseEvent): boolean {\n for (const plugin of this.plugins) {\n if (plugin.onCellMouseDown?.(event)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Execute onCellMouseMove hook on all plugins.\n * Returns true if any plugin handled the event.\n */\n onCellMouseMove(event: CellMouseEvent): boolean {\n for (const plugin of this.plugins) {\n if (plugin.onCellMouseMove?.(event)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Execute onCellMouseUp hook on all plugins.\n * Returns true if any plugin handled the event.\n */\n onCellMouseUp(event: CellMouseEvent): boolean {\n for (const plugin of this.plugins) {\n if (plugin.onCellMouseUp?.(event)) {\n return true;\n }\n }\n return false;\n }\n\n // #endregion\n\n // #region Scroll Boundary Hooks\n\n /**\n * Collect horizontal scroll boundary offsets from all plugins.\n * Combines offsets from all plugins that report them.\n *\n * @param rowEl - The row element (optional, for calculating widths from rendered cells)\n * @param focusedCell - The currently focused cell element (optional, to determine if scrolling should be skipped)\n * @returns Combined left and right pixel offsets, plus skipScroll if any plugin requests it\n */\n getHorizontalScrollOffsets(\n rowEl?: HTMLElement,\n focusedCell?: HTMLElement,\n ): { left: number; right: number; skipScroll?: boolean } {\n let left = 0;\n let right = 0;\n let skipScroll = false;\n for (const plugin of this.plugins) {\n const offsets = plugin.getHorizontalScrollOffsets?.(rowEl, focusedCell);\n if (offsets) {\n left += offsets.left;\n right += offsets.right;\n if (offsets.skipScroll) {\n skipScroll = true;\n }\n }\n }\n return { left, right, skipScroll };\n }\n // #endregion\n\n // #region Shell Integration Hooks\n\n /**\n * Collect tool panels from all plugins.\n * Returns panels sorted by order (ascending).\n */\n getToolPanels(): {\n plugin: BaseGridPlugin;\n panel: NonNullable<ReturnType<NonNullable<BaseGridPlugin['getToolPanel']>>>;\n }[] {\n const panels: {\n plugin: BaseGridPlugin;\n panel: NonNullable<ReturnType<NonNullable<BaseGridPlugin['getToolPanel']>>>;\n }[] = [];\n for (const plugin of this.plugins) {\n const panel = plugin.getToolPanel?.();\n if (panel) {\n panels.push({ plugin, panel });\n }\n }\n // Sort by order (ascending), default to 0\n return panels.sort((a, b) => (a.panel.order ?? 0) - (b.panel.order ?? 0));\n }\n\n /**\n * Collect header contents from all plugins.\n * Returns contents sorted by order (ascending).\n */\n getHeaderContents(): {\n plugin: BaseGridPlugin;\n content: NonNullable<ReturnType<NonNullable<BaseGridPlugin['getHeaderContent']>>>;\n }[] {\n const contents: {\n plugin: BaseGridPlugin;\n content: NonNullable<ReturnType<NonNullable<BaseGridPlugin['getHeaderContent']>>>;\n }[] = [];\n for (const plugin of this.plugins) {\n const content = plugin.getHeaderContent?.();\n if (content) {\n contents.push({ plugin, content });\n }\n }\n // Sort by order (ascending), default to 0\n return contents.sort((a, b) => (a.content.order ?? 0) - (b.content.order ?? 0));\n }\n // #endregion\n}\n","import { createAriaState, updateAriaCounts, updateAriaLabels, type AriaState } from './internal/aria';\nimport { autoSizeColumns, updateTemplate } from './internal/columns';\nimport { ConfigManager } from './internal/config-manager';\nimport { INVALID_ATTRIBUTE_JSON, warnDiagnostic } from './internal/diagnostics';\nimport { setupCellEventDelegation, setupRootEventDelegation } from './internal/event-delegation';\nimport { resolveFeatures } from './internal/feature-hook';\nimport { FocusManager } from './internal/focus-manager';\nimport { renderHeader } from './internal/header';\nimport { cancelIdle, scheduleIdle } from './internal/idle-scheduler';\nimport { ensureCellVisible } from './internal/keyboard';\nimport {\n createLoadingOverlay,\n hideLoadingOverlay,\n setCellLoadingState,\n setRowLoadingState,\n showLoadingOverlay,\n} from './internal/loading';\nimport { RenderPhase, RenderScheduler } from './internal/render-scheduler';\nimport { createResizeController } from './internal/resize';\nimport { animateRow, animateRowById, animateRows } from './internal/row-animation';\nimport { resolveRowIdOrThrow, RowManager, tryResolveRowId } from './internal/row-manager';\nimport { invalidateCellCache, renderVisibleRows } from './internal/rows';\nimport {\n buildGridDOMIntoElement,\n cleanupShellState,\n createShellController,\n createShellState,\n parseLightDomShell,\n parseLightDomToolButtons,\n parseLightDomToolPanels,\n prepareForRerender,\n renderCustomToolbarContents,\n renderHeaderContent,\n renderPanelContent,\n renderShellHeader,\n setupClickOutsideDismiss,\n setupShellEventListeners,\n setupToolPanelResize,\n shouldRenderShellHeader,\n updatePanelState,\n updateToolbarActiveStates,\n type ShellController,\n type ShellState,\n type ToolPanelRendererFactory,\n} from './internal/shell';\nimport { reapplyCoreSort } from './internal/sorting';\nimport { addPluginStyles, injectStyles } from './internal/style-injector';\nimport {\n cancelMomentum,\n createTouchScrollState,\n setupTouchScrollListeners,\n type TouchScrollState,\n} from './internal/touch-scroll';\nimport {\n validatePluginConfigRules,\n validatePluginIncompatibilities,\n validatePluginProperties,\n} from './internal/validate-config';\nimport { getRowIndexAtOffset } from './internal/virtualization';\nimport { VirtualizationManager } from './internal/virtualization-manager';\nimport type { AfterCellRenderContext, AfterRowRenderContext, CellMouseEvent, ScrollEvent } from './plugin';\nimport type {\n BaseGridPlugin,\n CellClickEvent,\n HeaderClickEvent,\n PluginQuery,\n RowClickEvent,\n} from './plugin/base-plugin';\nimport { PluginManager } from './plugin/plugin-manager';\nimport styles from './styles';\nimport type {\n AnimationConfig,\n ColumnConfig,\n ColumnConfigMap,\n ColumnInternal,\n DataChangeDetail,\n DataGridEventMap,\n FitMode,\n FrameworkAdapter,\n GridColumnState,\n GridConfig,\n HeaderContentDefinition,\n IconValue,\n InternalGrid,\n PluginNameMap,\n ResizeController,\n RowAnimationType,\n RowElementInternal,\n ScrollToRowOptions,\n ToolbarContentDefinition,\n ToolPanelDefinition,\n UpdateSource,\n VirtualState,\n} from './types';\nimport { DEFAULT_ANIMATION_CONFIG, DEFAULT_GRID_ICONS } from './types';\n\n/**\n * High-performance data grid web component.\n *\n * ## Instantiation\n *\n * **Do not call the constructor directly.** Web components must be created via\n * the DOM API. Use one of these approaches:\n *\n * ```typescript\n * // Recommended: Use the createGrid() factory for TypeScript type safety\n * import { createGrid, SelectionPlugin } from '@toolbox-web/grid/all';\n *\n * const grid = createGrid<Employee>({\n * columns: [\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' }\n * ],\n * plugins: [new SelectionPlugin()]\n * });\n * grid.rows = employees;\n * document.body.appendChild(grid);\n *\n * // Alternative: Query existing element from DOM\n * import { queryGrid } from '@toolbox-web/grid';\n * const grid = queryGrid<Employee>('#my-grid');\n *\n * // Alternative: Use document.createElement (loses type inference)\n * const grid = document.createElement('tbw-grid');\n * ```\n *\n * ## Configuration Architecture\n *\n * The grid follows a **single source of truth** pattern where all configuration\n * is managed by ConfigManager. Users can set configuration via multiple inputs:\n *\n * **Input Sources (precedence low → high):**\n * 1. `gridConfig` property - base configuration object\n * 2. Light DOM elements:\n * - `<tbw-grid-column>` → `effectiveConfig.columns`\n * - `<tbw-grid-header title=\"...\">` → `effectiveConfig.shell.header.title`\n * - `<tbw-grid-header-content>` → `effectiveConfig.shell.header.content`\n * 3. `columns` property → merged into `effectiveConfig.columns`\n * 4. `fitMode` property → merged into `effectiveConfig.fitMode`\n * 5. Column inference from first row (if no columns defined)\n *\n * **Derived State:**\n * - `_columns` - processed columns from `effectiveConfig.columns` after plugin hooks\n * - `_rows` - processed rows after plugin hooks (grouping, filtering, etc.)\n *\n * ConfigManager.merge() is the single place where all inputs converge.\n * All rendering and logic should read from `effectiveConfig` or derived state.\n *\n * @element tbw-grid\n *\n * @csspart container - The main grid container\n * @csspart header - The header row container\n * @csspart body - The body/rows container\n *\n * @cssprop --tbw-color-bg - Background color\n * @cssprop --tbw-color-fg - Foreground/text color\n */\n// Injected by Vite at build time from package.json\ndeclare const __GRID_VERSION__: string;\n\nexport class DataGridElement<T = any> extends HTMLElement implements InternalGrid<T> {\n // TODO: Rename to 'data-grid' when migration is complete\n static readonly tagName = 'tbw-grid';\n /** Version of the grid component, injected at build time from package.json */\n static readonly version = typeof __GRID_VERSION__ !== 'undefined' ? __GRID_VERSION__ : 'dev';\n\n /** Static counter for generating unique grid IDs */\n static #instanceCounter = 0;\n\n // #region Static Methods - Framework Adapters\n /**\n * Registry of framework adapters that handle converting light DOM elements\n * to functional renderers/editors. Framework libraries (Angular, React, Vue)\n * register adapters to enable zero-boilerplate component integration.\n */\n private static adapters: FrameworkAdapter[] = [];\n\n /**\n * Register a framework adapter for handling framework-specific components.\n * Adapters are checked in registration order when processing light DOM templates.\n *\n * @example\n * ```typescript\n * // In @toolbox-web/grid-angular\n * import { AngularGridAdapter } from '@toolbox-web/grid-angular';\n *\n * // One-time setup in app\n * GridElement.registerAdapter(new AngularGridAdapter(injector, appRef));\n * ```\n * @category Framework Adapters\n */\n static registerAdapter(adapter: FrameworkAdapter): void {\n this.adapters.push(adapter);\n }\n\n /**\n * Get all registered framework adapters.\n * Used internally by light DOM parsing to find adapters that can handle templates.\n * @category Framework Adapters\n */\n static getAdapters(): readonly FrameworkAdapter[] {\n return this.adapters;\n }\n\n /**\n * Clear all registered adapters (primarily for testing).\n * @category Framework Adapters\n */\n static clearAdapters(): void {\n this.adapters = [];\n }\n // #endregion\n\n // #region Static Methods - Observed Attributes\n /** @internal Web component lifecycle - not part of public API */\n static get observedAttributes(): string[] {\n return ['rows', 'columns', 'grid-config', 'fit-mode', 'loading'];\n }\n // #endregion\n\n /**\n * The render root for the grid. Without Shadow DOM, this is the element itself.\n * This abstraction allows internal code to work the same way regardless of DOM mode.\n */\n get #renderRoot(): HTMLElement {\n return this;\n }\n\n #initialized = false;\n\n // Ready Promise\n #readyPromise: Promise<void>;\n #readyResolve?: () => void;\n\n // #region Input Properties\n // Raw rows are stored here. Config sources (gridConfig, columns, fitMode)\n // are owned by ConfigManager. Grid.ts property setters delegate to ConfigManager.\n #rows: T[] = [];\n // #endregion\n\n // #region Private properties\n // effectiveConfig is owned by ConfigManager - access via getter\n get #effectiveConfig(): GridConfig<T> {\n return this.#configManager?.effective ?? {};\n }\n\n #connected = false;\n\n // Batched Updates - coalesces rapid property changes into single update\n #pendingUpdate = false;\n #pendingUpdateFlags = {\n rows: false,\n columns: false,\n gridConfig: false,\n fitMode: false,\n };\n\n // Render Scheduler - centralizes all rendering through RAF\n #scheduler!: RenderScheduler;\n\n #scrollRaf = 0;\n #pendingScrollTop: number | null = null;\n #hasScrollPlugins = false; // Cached flag for plugin scroll handlers\n #needsRowHeightMeasurement = false; // Flag to measure row height after render (for plugin-based variable heights)\n #scrollMeasureTimeout = 0; // Debounce timer for measuring rows after scroll settles\n #renderRowHook?: (row: any, rowEl: HTMLElement, rowIndex: number) => boolean; // Cached hook to avoid closures\n #touchState: TouchScrollState = createTouchScrollState();\n #eventAbortController?: AbortController;\n #resizeObserver?: ResizeObserver;\n #rowHeightObserver?: ResizeObserver; // Watches first row for size changes (CSS loading, custom renderers)\n #idleCallbackHandle?: number; // Handle for cancelling deferred idle work\n\n // Pooled scroll event object (reused to avoid GC pressure during scroll)\n #pooledScrollEvent: ScrollEvent = {\n scrollTop: 0,\n scrollLeft: 0,\n scrollHeight: 0,\n scrollWidth: 0,\n clientHeight: 0,\n clientWidth: 0,\n };\n\n // Plugin System\n #pluginManager!: PluginManager;\n #lastPluginsArray?: BaseGridPlugin[]; // Track last attached plugins to avoid unnecessary re-initialization\n #lastFeaturesConfig?: Record<string, unknown>; // Track last features config for change detection\n\n // Virtualization manager — owns VirtualState + all virtualization methods\n #virtManager!: VirtualizationManager<T>;\n\n // Focus manager — owns focus/navigation state and external focus containers\n #focusManager!: FocusManager<T>;\n\n // Row manager — owns row CRUD operations (updateRow, insertRow, removeRow, etc.)\n #rowManager!: RowManager<T>;\n\n /**\n * Exposes plugin manager for event bus operations (subscribe/unsubscribe/emit).\n * Plugins access this via `this.grid._pluginManager` in `BaseGridPlugin.on/off/emitPluginEvent`.\n * @internal\n */\n get _pluginManager(): PluginManager | undefined {\n return this.#pluginManager;\n }\n\n // Event Listeners\n #eventListenersAdded = false; // Guard against adding duplicate component-level listeners\n #scrollAbortController?: AbortController; // Separate controller for DOM scroll listeners (recreated on DOM changes)\n #scrollAreaEl?: HTMLElement; // Reference to horizontal scroll container (.tbw-scroll-area)\n\n // Column State\n #initialColumnState?: GridColumnState;\n\n // Config Manager\n #configManager!: ConfigManager<T>;\n\n // Shell State\n #shellState: ShellState = createShellState();\n #shellController!: ShellController;\n #resizeCleanup?: () => void;\n #clickOutsideCleanup?: () => void;\n\n // Loading State\n #loading = false;\n #loadingRows = new Set<string>(); // Row IDs currently loading\n #loadingCells = new Map<string, Set<string>>(); // Map<rowId, Set<field>> for cells loading\n #loadingOverlayEl?: HTMLElement; // Cached loading overlay element\n\n // Row ID Map - O(1) lookup for rows by ID\n #rowIdMap = new Map<string, { row: T; index: number }>();\n // #endregion\n\n // #region Derived State\n // _rows: result of applying plugin processRows hooks\n _rows: T[] = [];\n\n // _baseColumns: columns before plugin transformation (analogous to #rows for row processing)\n // This is the source of truth for processColumns - plugins transform these\n #baseColumns: ColumnInternal<T>[] = [];\n\n // _columns is a getter/setter that operates on effectiveConfig.columns\n // This ensures effectiveConfig.columns is the single source of truth for columns\n // _columns always contains ALL columns (including hidden)\n get _columns(): ColumnInternal<T>[] {\n return (this.#effectiveConfig.columns ?? []) as ColumnInternal<T>[];\n }\n set _columns(value: ColumnInternal<T>[]) {\n this.#effectiveConfig.columns = value as ColumnConfig<T>[];\n this.#visibleColumnsCache = undefined;\n }\n\n // visibleColumns returns only visible columns for rendering\n // This is what header/row rendering should use\n // Cached — invalidated when _columns is set\n #visibleColumnsCache?: ColumnInternal<T>[];\n get _visibleColumns(): ColumnInternal<T>[] {\n return (this.#visibleColumnsCache ??= this._columns.filter((c) => !c.hidden));\n }\n // #endregion\n\n // #region Runtime State (Plugin-accessible)\n // DOM references\n _headerRowEl!: HTMLElement;\n _bodyEl!: HTMLElement;\n _rowPool: RowElementInternal[] = [];\n _resizeController!: ResizeController;\n\n // Virtualization — delegated to VirtualizationManager, exposed via getter for plugin access\n get _virtualization(): VirtualState {\n return this.#virtManager.state;\n }\n set _virtualization(value: VirtualState) {\n // Support test mocking — merge incoming partial state into the live state object\n Object.assign(this.#virtManager.state, value);\n }\n\n // Focus & navigation\n _focusRow = 0;\n _focusCol = 0;\n /** Flag to restore focus styling after next render. @internal */\n _restoreFocusAfterRender = false;\n\n // Sort state\n _sortState: { field: string; direction: 1 | -1 } | null = null;\n\n // Layout\n _gridTemplate = '';\n // #endregion\n\n // #region Implementation Details (Internal only)\n __rowRenderEpoch = 0;\n __didInitialAutoSize = false;\n\n /** Light DOM columns cache - delegates to ConfigManager */\n get __lightDomColumnsCache(): ColumnInternal[] | undefined {\n return this.#configManager?.lightDomColumnsCache as ColumnInternal[] | undefined;\n }\n set __lightDomColumnsCache(value: ColumnInternal[] | undefined) {\n if (this.#configManager) {\n this.#configManager.lightDomColumnsCache = value as ColumnInternal<T>[] | undefined;\n }\n }\n\n /** Original column nodes - delegates to ConfigManager */\n get __originalColumnNodes(): HTMLElement[] | undefined {\n return this.#configManager?.originalColumnNodes;\n }\n set __originalColumnNodes(value: HTMLElement[] | undefined) {\n if (this.#configManager) {\n this.#configManager.originalColumnNodes = value;\n }\n }\n\n __originalOrder: T[] = [];\n\n /**\n * Framework adapter instance set by framework directives (Angular Grid, React DataGrid).\n * Used to handle framework-specific component rendering.\n * @internal\n */\n __frameworkAdapter?: FrameworkAdapter;\n\n // Cached DOM refs for hot path (refreshVirtualWindow) - avoid querySelector per scroll\n __rowsBodyEl: HTMLElement | null = null;\n // #endregion\n\n // #region Public API Props (getters/setters)\n // Getters return the EFFECTIVE value (after merging), not the raw input.\n // This is what consumers and plugins need - the current resolved state.\n // Setters update input properties which trigger re-merge into effectiveConfig.\n\n /**\n * Get or set the row data displayed in the grid.\n *\n * The getter returns processed rows (after filtering, sorting, grouping by plugins).\n * The setter accepts new source data and triggers a re-render.\n *\n * @group Configuration\n * @example\n * ```typescript\n * // Set initial data\n * grid.rows = employees;\n *\n * // Update with new data (triggers re-render)\n * grid.rows = [...employees, newEmployee];\n *\n * // Read current (processed) rows\n * console.log(`Displaying ${grid.rows.length} rows`);\n * ```\n */\n get rows(): T[] {\n return this._rows;\n }\n set rows(value: T[]) {\n const oldValue = this.#rows;\n this.#rows = value;\n if (oldValue !== value) {\n this.#queueUpdate('rows');\n }\n }\n\n /**\n * Get the original unfiltered/unprocessed source rows.\n *\n * Use this when you need access to all source data regardless of active\n * filters, sorting, or grouping applied by plugins. The `rows` property\n * returns processed data, while `sourceRows` returns the original input.\n *\n * @group Configuration\n * @example\n * ```typescript\n * // Get total count including filtered-out rows\n * console.log(`${grid.rows.length} of ${grid.sourceRows.length} rows visible`);\n *\n * // Export all data, not just visible\n * exportToCSV(grid.sourceRows);\n * ```\n */\n get sourceRows(): T[] {\n return this.#rows;\n }\n\n /** @internal Used by RowManager for insertRow/removeRow mutations. */\n set sourceRows(rows: T[]) {\n this.#rows = rows;\n }\n\n /**\n * Get or set the column configurations.\n *\n * The getter returns processed columns (after plugin transformations).\n * The setter accepts an array of column configs or a column config map.\n *\n * @group Configuration\n * @example\n * ```typescript\n * // Set columns as array\n * grid.columns = [\n * { field: 'name', header: 'Name', width: 200 },\n * { field: 'email', header: 'Email' },\n * { field: 'role', header: 'Role', width: 120 }\n * ];\n *\n * // Set columns as map (keyed by field)\n * grid.columns = {\n * name: { header: 'Name', width: 200 },\n * email: { header: 'Email' },\n * role: { header: 'Role', width: 120 }\n * };\n *\n * // Read current columns\n * grid.columns.forEach(col => {\n * console.log(`${col.field}: ${col.width ?? 'auto'}`);\n * });\n * ```\n */\n get columns(): ColumnConfig<T>[] {\n return [...this._columns] as ColumnConfig<T>[];\n }\n set columns(value: ColumnConfig<T>[] | ColumnConfigMap<T> | undefined) {\n const oldValue = this.#configManager?.getColumns();\n this.#configManager?.setColumns(value);\n if (oldValue !== value) {\n this.#queueUpdate('columns');\n }\n }\n\n /**\n * Get or set the full grid configuration object.\n *\n * The getter returns the effective (merged) configuration.\n * The setter accepts a new configuration and triggers a full re-render.\n *\n * @group Configuration\n * @example\n * ```typescript\n * import { SelectionPlugin, SortingPlugin } from '@toolbox-web/grid/all';\n *\n * // Set full configuration\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name' },\n * { field: 'status', header: 'Status' }\n * ],\n * fitMode: 'stretch',\n * plugins: [\n * new SelectionPlugin({ mode: 'row' }),\n * new SortingPlugin()\n * ]\n * };\n *\n * // Read current configuration\n * console.log('Fit mode:', grid.gridConfig.fitMode);\n * console.log('Columns:', grid.gridConfig.columns?.length);\n * ```\n */\n get gridConfig(): GridConfig<T> {\n return this.#effectiveConfig;\n }\n set gridConfig(value: GridConfig<T> | undefined) {\n // Let the framework adapter pre-process the config (convert component\n // classes / VNodes / JSX to DOM-returning functions) before the grid sees it.\n if (value && this.__frameworkAdapter?.processConfig) {\n value = this.__frameworkAdapter.processConfig(value);\n }\n const oldValue = this.#configManager?.getGridConfig();\n this.#configManager?.setGridConfig(value);\n if (oldValue !== value) {\n // Clear light DOM column cache so columns are re-parsed from light DOM\n // This is needed for frameworks like Angular that project content asynchronously\n this.#configManager.clearLightDomCache();\n this.#queueUpdate('gridConfig');\n }\n }\n\n /**\n * Get or set the column sizing mode.\n *\n * - `'stretch'` (default): Columns stretch to fill available width\n * - `'fixed'`: Columns use explicit widths; horizontal scroll if needed\n * - `'auto'`: Columns auto-size to content on initial render\n *\n * @group Configuration\n * @example\n * ```typescript\n * // Use fixed widths with horizontal scroll\n * grid.fitMode = 'fixed';\n *\n * // Stretch columns to fill container\n * grid.fitMode = 'stretch';\n *\n * // Auto-size columns based on content\n * grid.fitMode = 'auto';\n * ```\n */\n get fitMode(): FitMode {\n return this.#effectiveConfig.fitMode ?? 'stretch';\n }\n set fitMode(value: FitMode | undefined) {\n const oldValue = this.#configManager?.getFitMode();\n this.#configManager?.setFitMode(value);\n if (oldValue !== value) {\n this.#queueUpdate('fitMode');\n }\n }\n\n // #region Loading API\n\n /**\n * Whether the grid is currently in a loading state.\n * When true, displays a loading overlay with spinner (or custom loadingRenderer).\n *\n * @example\n * ```typescript\n * grid.loading = true;\n * const data = await fetchData();\n * grid.rows = data;\n * grid.loading = false;\n * ```\n */\n get loading(): boolean {\n return this.#loading;\n }\n\n set loading(value: boolean) {\n const wasLoading = this.#loading;\n this.#loading = value;\n\n // Toggle attribute for CSS styling and external queries\n if (value) {\n this.setAttribute('loading', '');\n } else {\n this.removeAttribute('loading');\n }\n\n // Only update overlay if state actually changed\n if (wasLoading !== value) {\n this.#updateLoadingOverlay();\n }\n }\n\n /**\n * Set loading state for a specific row.\n * Shows a small spinner indicator on the row.\n *\n * @param rowId - The row's unique identifier (from getRowId)\n * @param loading - Whether the row is loading\n *\n * @example\n * ```typescript\n * // Show loading while saving row data\n * grid.setRowLoading('row-123', true);\n * await saveRow(rowData);\n * grid.setRowLoading('row-123', false);\n * ```\n */\n setRowLoading(rowId: string, loading: boolean): void {\n const wasLoading = this.#loadingRows.has(rowId);\n if (loading) {\n this.#loadingRows.add(rowId);\n } else {\n this.#loadingRows.delete(rowId);\n }\n\n // Update row element if state changed\n if (wasLoading !== loading) {\n this.#updateRowLoadingState(rowId, loading);\n }\n }\n\n /**\n * Set loading state for a specific cell.\n * Shows a small spinner indicator on the cell.\n *\n * @param rowId - The row's unique identifier\n * @param field - The column field\n * @param loading - Whether the cell is loading\n *\n * @example\n * ```typescript\n * // Show loading while validating a single field\n * grid.setCellLoading('row-123', 'email', true);\n * const valid = await validateEmail(newValue);\n * grid.setCellLoading('row-123', 'email', false);\n * ```\n */\n setCellLoading(rowId: string, field: string, loading: boolean): void {\n let cellFields = this.#loadingCells.get(rowId);\n const wasLoading = cellFields?.has(field) ?? false;\n\n if (loading) {\n if (!cellFields) {\n cellFields = new Set();\n this.#loadingCells.set(rowId, cellFields);\n }\n cellFields.add(field);\n } else {\n cellFields?.delete(field);\n // Clean up empty sets\n if (cellFields?.size === 0) {\n this.#loadingCells.delete(rowId);\n }\n }\n\n // Update cell element if state changed\n if (wasLoading !== loading) {\n this.#updateCellLoadingState(rowId, field, loading);\n }\n }\n\n /**\n * Check if a row is currently in loading state.\n * @param rowId - The row's unique identifier\n */\n isRowLoading(rowId: string): boolean {\n return this.#loadingRows.has(rowId);\n }\n\n /**\n * Check if a cell is currently in loading state.\n * @param rowId - The row's unique identifier\n * @param field - The column field\n */\n isCellLoading(rowId: string, field: string): boolean {\n return this.#loadingCells.get(rowId)?.has(field) ?? false;\n }\n\n /**\n * Clear all row and cell loading states.\n */\n clearAllLoading(): void {\n this.loading = false;\n\n // Clear all row loading states\n for (const rowId of this.#loadingRows) {\n this.#updateRowLoadingState(rowId, false);\n }\n this.#loadingRows.clear();\n\n // Clear all cell loading states\n for (const [rowId, fields] of this.#loadingCells) {\n for (const field of fields) {\n this.#updateCellLoadingState(rowId, field, false);\n }\n }\n this.#loadingCells.clear();\n }\n\n // #endregion\n\n /**\n * Effective config accessor for internal modules and plugins.\n * Returns the merged config (single source of truth) before plugin processing.\n * Use this when you need the raw merged config (e.g., for column definitions including hidden).\n * @group State Access\n * @internal Plugin API\n */\n get effectiveConfig(): GridConfig<T> {\n return this.#effectiveConfig;\n }\n\n /**\n * Get the disconnect signal for event listener cleanup.\n * This signal is aborted when the grid disconnects from the DOM.\n * Plugins and internal code can use this for automatic listener cleanup.\n * @group State Access\n * @internal Plugin API\n * @example\n * element.addEventListener('click', handler, { signal: this.grid.disconnectSignal });\n */\n get disconnectSignal(): AbortSignal {\n // Ensure AbortController exists (created in connectedCallback before plugins attach)\n if (!this.#eventAbortController) {\n this.#eventAbortController = new AbortController();\n }\n return this.#eventAbortController.signal;\n }\n // #endregion\n\n /**\n * @internal Do not call directly. Use `createGrid()` or `document.createElement('tbw-grid')`.\n */\n constructor() {\n super();\n // No Shadow DOM - render directly into the element\n void this.#injectStyles(); // Fire and forget - styles load asynchronously\n this.#readyPromise = new Promise((res) => (this.#readyResolve = res));\n\n // Initialize virtualization manager (tightly coupled — reads grid state directly)\n this.#virtManager = new VirtualizationManager<T>(this);\n\n // Initialize focus manager (tightly coupled — reads grid state directly)\n this.#focusManager = new FocusManager<T>(this);\n\n // Initialize row manager (tightly coupled — reads grid state directly)\n this.#rowManager = new RowManager<T>(this);\n\n // Initialize render scheduler (tightly coupled — calls grid pipeline methods directly)\n this.#scheduler = new RenderScheduler(this);\n // Connect ready promise to scheduler\n this.#scheduler.setInitialReadyResolver(() => this.#readyResolve?.());\n\n // Initialize shell controller (reads directly from grid internals)\n this.#shellController = createShellController(this.#shellState, this);\n\n // Initialize config manager (reads directly from grid internals)\n this.#configManager = new ConfigManager<T>(this);\n }\n\n /**\n * Inject grid styles into the document.\n * Delegates to the style-injector module (singleton pattern).\n */\n async #injectStyles(): Promise<void> {\n await injectStyles(styles);\n }\n\n // #region Plugin System\n /**\n * Get a plugin instance by its class constructor.\n *\n * **Prefer {@link getPluginByName}** for most use cases — it avoids importing the plugin class\n * and returns the actual instance registered in the grid.\n *\n * @example\n * ```ts\n * // Preferred: by name (no import needed)\n * const selection = grid.getPluginByName('selection');\n *\n * // Alternative: by class (requires import)\n * import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';\n * const selection = grid.getPlugin(SelectionPlugin);\n * selection?.selectAll();\n * ```\n *\n * @param PluginClass - The plugin class (constructor) to look up.\n * @returns The plugin instance, or `undefined` if not registered.\n * @group Plugin Communication\n */\n getPlugin<P>(PluginClass: new (...args: any[]) => P): P | undefined {\n return this.#pluginManager?.getPlugin(PluginClass as new (...args: any[]) => BaseGridPlugin) as P | undefined;\n }\n\n /**\n * Get a plugin instance by its string name.\n * Useful for loose coupling when you don't want to import the plugin class\n * (e.g., in framework adapters or dynamic scenarios).\n *\n * @example\n * ```ts\n * const editing = grid.getPluginByName('editing');\n * ```\n *\n * @param name - The plugin name (matches {@link BaseGridPlugin.name}).\n * @returns The plugin instance, or `undefined` if not registered.\n * @group Plugin Communication\n */\n getPluginByName<K extends string>(\n name: K,\n ): (K extends keyof PluginNameMap ? PluginNameMap[K] : BaseGridPlugin) | undefined {\n return this.#pluginManager?.getPluginByName(name) as any;\n }\n\n /**\n * Request a full re-render of the grid.\n * Called by plugins when they need the grid to update.\n * Note: This does NOT reset plugin state - just re-processes rows/columns and renders.\n * @group Rendering\n * @internal Plugin API\n */\n requestRender(): void {\n this.#scheduler.requestPhase(RenderPhase.ROWS, 'plugin:requestRender');\n }\n\n /**\n * Request a columns re-render of the grid.\n * Called by plugins when they need to trigger processColumns hooks.\n * This uses a higher render phase than requestRender() to ensure\n * column processing occurs.\n * @group Rendering\n * @internal Plugin API\n */\n requestColumnsRender(): void {\n this.#scheduler.requestPhase(RenderPhase.COLUMNS, 'plugin:requestColumnsRender');\n }\n\n /**\n * Request a full re-render and restore focus styling afterward.\n * Use this when a plugin action (like expand/collapse) triggers a render\n * but needs to maintain keyboard navigation focus.\n * @group Rendering\n * @internal Plugin API\n */\n requestRenderWithFocus(): void {\n this._restoreFocusAfterRender = true;\n this.#scheduler.requestPhase(RenderPhase.ROWS, 'plugin:requestRenderWithFocus');\n }\n\n /**\n * Update the grid's column template CSS.\n * Called by resize controller during column resize operations.\n * @group Rendering\n * @internal Plugin API\n */\n updateTemplate(): void {\n updateTemplate(this);\n }\n\n /**\n * Request a lightweight style update without rebuilding DOM.\n * Called by plugins when they only need to update CSS classes/styles.\n * This runs all plugin afterRender hooks without rebuilding row/column DOM.\n * @group Rendering\n * @internal Plugin API\n */\n requestAfterRender(): void {\n this.#scheduler.requestPhase(RenderPhase.STYLE, 'plugin:requestAfterRender');\n }\n\n /**\n * Re-render visible rows without rebuilding the row model or recalculating geometry.\n * Uses non-force refreshVirtualWindow to avoid spacer height recalculations that\n * can cause scroll position oscillation. Useful when row data has been updated in-place\n * (e.g., server-side block loads replacing placeholders with real data).\n * @group Rendering\n * @internal Plugin API\n */\n requestVirtualRefresh(): void {\n // Invalidate start so refreshVirtualWindow doesn't early-exit\n this._virtualization.start = -1;\n this.refreshVirtualWindow(false);\n }\n\n /**\n * Initialize plugin system with instances from config.\n * Plugins are class instances passed in gridConfig.plugins[].\n * If gridConfig.features is set and the feature registry is loaded,\n * feature-derived plugins are prepended before explicit plugins.\n */\n #initializePlugins(): void {\n // Create plugin manager for this grid\n this.#pluginManager = new PluginManager(this);\n\n // Get plugin instances from config - ensure it's an array\n const pluginsConfig = this.#effectiveConfig?.plugins;\n const explicitPlugins = Array.isArray(pluginsConfig) ? (pluginsConfig as BaseGridPlugin[]) : [];\n\n // Resolve feature-derived plugins (if feature registry is loaded)\n const features = this.#effectiveConfig?.features;\n let featurePlugins: BaseGridPlugin[] = [];\n if (features && resolveFeatures) {\n featurePlugins = resolveFeatures(features as Record<string, unknown>) as BaseGridPlugin[];\n }\n\n // Merge: feature-derived first (for dependency ordering), then explicit plugins\n const allPlugins = featurePlugins.length > 0 ? [...featurePlugins, ...explicitPlugins] : explicitPlugins;\n\n // Attach all plugins\n this.#pluginManager.attachAll(allPlugins);\n }\n\n /**\n * Inject all plugin styles into the consolidated style element.\n * Plugin styles are appended after base grid styles in the same <style> element.\n * Uses a Map to accumulate styles from all grid instances on the page.\n */\n #injectAllPluginStyles(): void {\n const pluginStyles = this.#pluginManager?.getPluginStyles() ?? [];\n addPluginStyles(pluginStyles);\n }\n\n /**\n * Update plugins when grid config changes.\n * With class-based plugins, we need to detach old and attach new.\n * Skips re-initialization if the plugins array and features config haven't changed.\n */\n #updatePluginConfigs(): void {\n // Get the new plugins array from config\n const pluginsConfig = this.#effectiveConfig?.plugins;\n const newPlugins = Array.isArray(pluginsConfig) ? (pluginsConfig as BaseGridPlugin[]) : [];\n const newFeatures = (this.#effectiveConfig?.features as Record<string, unknown>) ?? undefined;\n\n // Check if features config changed (by reference)\n const featuresChanged = newFeatures !== this.#lastFeaturesConfig;\n\n // Check if plugins have actually changed (same array reference or same contents)\n // This avoids unnecessary detach/attach cycles on every render\n const pluginsUnchanged =\n this.#lastPluginsArray === newPlugins ||\n (this.#lastPluginsArray !== undefined &&\n this.#lastPluginsArray.length === newPlugins.length &&\n this.#lastPluginsArray.every((p, i) => p === newPlugins[i]));\n\n if (pluginsUnchanged && !featuresChanged) {\n // Nothing changed - just update reference tracking\n this.#lastPluginsArray = newPlugins;\n return;\n }\n\n // Plugins have changed - detach old and attach new\n if (this.#pluginManager) {\n this.#pluginManager.detachAll();\n }\n\n // Clear plugin-contributed panels BEFORE re-initializing plugins\n // This is critical: when plugins are re-initialized, they create NEW instances\n // with NEW render functions. The old panel definitions have stale closures.\n // We preserve light DOM panels (tracked in lightDomToolPanelIds) and\n // API-registered panels (tracked in apiToolPanelIds).\n for (const panelId of this.#shellState.toolPanels.keys()) {\n const isLightDom = this.#shellState.lightDomToolPanelIds.has(panelId);\n const isApiRegistered = this.#shellState.apiToolPanelIds.has(panelId);\n if (!isLightDom && !isApiRegistered) {\n // Clean up any active panel cleanup function\n const cleanup = this.#shellState.panelCleanups.get(panelId);\n if (cleanup) {\n cleanup();\n this.#shellState.panelCleanups.delete(panelId);\n }\n this.#shellState.toolPanels.delete(panelId);\n }\n }\n\n // Similarly clear plugin-contributed header contents\n // Preserve API-registered header contents (tracked in apiHeaderContentIds).\n for (const contentId of this.#shellState.headerContents.keys()) {\n if (this.#shellState.apiHeaderContentIds.has(contentId)) continue;\n const cleanup = this.#shellState.headerContentCleanups.get(contentId);\n if (cleanup) {\n cleanup();\n this.#shellState.headerContentCleanups.delete(contentId);\n }\n this.#shellState.headerContents.delete(contentId);\n }\n\n this.#initializePlugins();\n this.#injectAllPluginStyles();\n\n // Track the new plugins array and features config\n this.#lastPluginsArray = newPlugins;\n this.#lastFeaturesConfig = newFeatures;\n\n // Re-check variable heights mode: a plugin with getRowHeight() may have been added\n // after initial setup (e.g., Angular/React set gridConfig asynchronously via effects).\n // Without this, variableHeights stays false and the scroll handler uses fixed-height math,\n // producing incorrect translateY when detail rows are expanded.\n this.#configureVariableHeights();\n\n // Re-collect plugin shell contributions (tool panels, header content)\n // Now the new plugin instances will add their fresh panel definitions\n this.#collectPluginShellContributions();\n\n // Update cached scroll plugin flag and re-setup scroll listeners if needed\n // This ensures horizontal scroll listener is added when plugins with onScroll handlers are added\n const hadScrollPlugins = this.#hasScrollPlugins;\n this.#hasScrollPlugins = this.#pluginManager?.getAll().some((p) => p.onScroll) ?? false;\n\n // Re-setup scroll listeners if scroll plugins were added (flag changed from false to true)\n if (!hadScrollPlugins && this.#hasScrollPlugins) {\n const gridContent = this.#renderRoot.querySelector('.tbw-grid-content');\n const gridRoot = gridContent ?? this.#renderRoot.querySelector('.tbw-grid-root');\n this.#setupScrollListeners(gridRoot);\n }\n }\n\n /**\n * Clean up plugin states when grid disconnects.\n */\n #destroyPlugins(): void {\n this.#pluginManager?.detachAll();\n }\n\n /**\n * Collect tool panels and header content from all plugins.\n * Called after plugins are attached but before render.\n */\n #collectPluginShellContributions(): void {\n if (!this.#pluginManager) return;\n\n // Collect tool panels from plugins\n const pluginPanels = this.#pluginManager.getToolPanels();\n for (const { panel } of pluginPanels) {\n // Skip if already registered (light DOM or API takes precedence)\n if (!this.#shellState.toolPanels.has(panel.id)) {\n this.#shellState.toolPanels.set(panel.id, panel);\n }\n }\n\n // Collect header contents from plugins\n const pluginContents = this.#pluginManager.getHeaderContents();\n for (const { content } of pluginContents) {\n // Skip if already registered (light DOM or API takes precedence)\n if (!this.#shellState.headerContents.has(content.id)) {\n this.#shellState.headerContents.set(content.id, content);\n }\n }\n }\n\n /**\n * Gets a renderer factory for tool panels from registered framework adapters.\n * Returns a factory function that tries each adapter in order until one handles the element.\n */\n #getToolPanelRendererFactory(): ToolPanelRendererFactory | undefined {\n const adapters = DataGridElement.getAdapters();\n if (adapters.length === 0 && !this.__frameworkAdapter) return undefined;\n\n // Also check for instance-level adapter (e.g., __frameworkAdapter from Angular Grid directive)\n const instanceAdapter = this.__frameworkAdapter;\n\n return (element: HTMLElement) => {\n // Try instance adapter first (from Grid directive)\n if (instanceAdapter?.createToolPanelRenderer) {\n const renderer = instanceAdapter.createToolPanelRenderer(element);\n if (renderer) return renderer;\n }\n\n // Try global adapters\n for (const adapter of adapters) {\n if (adapter.createToolPanelRenderer) {\n const renderer = adapter.createToolPanelRenderer(element);\n if (renderer) return renderer;\n }\n }\n\n return undefined;\n };\n }\n // #endregion\n\n // #region Lifecycle\n /** @internal Web component lifecycle - not part of public API */\n connectedCallback(): void {\n if (!this.hasAttribute('tabindex')) this.tabIndex = 0;\n if (!this.hasAttribute('version')) this.setAttribute('version', DataGridElement.version);\n // Ensure grid has a unique ID for print isolation and other use cases\n if (!this.id) {\n this.id = `tbw-grid-${++DataGridElement.#instanceCounter}`;\n }\n this._rows = Array.isArray(this.#rows) ? [...this.#rows] : [];\n\n // Create AbortController for all event listeners (grid internal + plugins)\n // This must happen BEFORE plugins attach so they can use disconnectSignal\n // Abort any previous controller first (in case of re-connect)\n if (this.#eventAbortController) {\n this.#eventAbortController.abort();\n this.#eventListenersAdded = false; // Reset so listeners can be re-added\n }\n this.#eventAbortController = new AbortController();\n\n // Cancel any pending idle work from previous connection\n if (this.#idleCallbackHandle) {\n cancelIdle(this.#idleCallbackHandle);\n this.#idleCallbackHandle = undefined;\n }\n\n // === CRITICAL PATH (synchronous) - needed for first paint ===\n\n // Parse light DOM shell elements BEFORE merging config\n this.#parseLightDom();\n // Parse light DOM columns (must be before merge to pick up templates)\n this.#configManager.parseLightDomColumns(this);\n\n // Merge all config sources into effectiveConfig (including columns and shell)\n this.#configManager.merge();\n\n // Initialize plugin system (now plugins can access disconnectSignal)\n this.#initializePlugins();\n\n // Track the initial plugins array and features to avoid unnecessary re-initialization\n const pluginsConfig = this.#effectiveConfig?.plugins;\n this.#lastPluginsArray = Array.isArray(pluginsConfig) ? (pluginsConfig as BaseGridPlugin[]) : [];\n this.#lastFeaturesConfig = (this.#effectiveConfig?.features as Record<string, unknown>) ?? undefined;\n\n // Collect tool panels and header content from plugins (must be before render)\n this.#collectPluginShellContributions();\n\n if (!this.#initialized) {\n this.#render();\n this.#injectAllPluginStyles(); // Inject plugin styles after render\n this.#initialized = true;\n }\n this.#afterConnect();\n\n // === DEFERRED WORK (idle) - not needed for first paint ===\n this.#idleCallbackHandle = scheduleIdle(\n () => {\n // Set up Light DOM observation via ConfigManager\n // This handles frameworks like Angular that project content asynchronously\n this.#setupLightDomHandlers();\n },\n { timeout: 100 },\n );\n }\n\n /** @internal Web component lifecycle - not part of public API */\n disconnectedCallback(): void {\n // Cancel any pending idle work\n if (this.#idleCallbackHandle) {\n cancelIdle(this.#idleCallbackHandle);\n this.#idleCallbackHandle = undefined;\n }\n\n // Cancel any pending scroll measurement\n if (this.#scrollMeasureTimeout) {\n clearTimeout(this.#scrollMeasureTimeout);\n this.#scrollMeasureTimeout = 0;\n }\n\n // Clean up plugin states\n this.#destroyPlugins();\n\n // Clean up shell state\n cleanupShellState(this.#shellState);\n this.#shellController.setInitialized(false);\n\n // Clean up tool panel resize handler\n this.#resizeCleanup?.();\n this.#resizeCleanup = undefined;\n\n // Clean up click-outside dismiss handler\n this.#clickOutsideCleanup?.();\n this.#clickOutsideCleanup = undefined;\n\n // Cancel any ongoing touch momentum animation\n cancelMomentum(this.#touchState);\n\n // Abort all event listeners (internal + document-level)\n // This cleans up all listeners added with { signal } option\n if (this.#eventAbortController) {\n this.#eventAbortController.abort();\n this.#eventAbortController = undefined;\n }\n // Also abort scroll-specific listeners (separate controller)\n this.#scrollAbortController?.abort();\n this.#scrollAbortController = undefined;\n this.#eventListenersAdded = false; // Reset so listeners can be re-added on reconnect\n\n if (this._resizeController) {\n this._resizeController.dispose();\n }\n if (this.#resizeObserver) {\n this.#resizeObserver.disconnect();\n this.#resizeObserver = undefined;\n }\n if (this.#rowHeightObserver) {\n this.#rowHeightObserver.disconnect();\n this.#rowHeightObserver = undefined;\n this.#rowHeightObserverSetup = false;\n }\n\n // Clear caches to prevent memory leaks\n invalidateCellCache(this);\n this.#customStyleSheets.clear();\n this._virtualization.heightCache?.byKey.clear();\n\n // Clear plugin tracking to allow fresh initialization on reconnect\n this.#lastPluginsArray = undefined;\n this.#lastFeaturesConfig = undefined;\n\n // Clear row pool - detach from DOM and release references\n for (const rowEl of this._rowPool) {\n rowEl.remove();\n }\n this._rowPool.length = 0;\n\n // Clear cached DOM refs to prevent stale references\n this.__rowsBodyEl = null;\n\n this.#connected = false;\n }\n\n /**\n * Handle HTML attribute changes.\n * Only processes attribute values when SET (non-null).\n * Removing an attribute does NOT clear JS-set properties.\n * @internal Web component lifecycle - not part of public API\n */\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n // Handle boolean attributes first (presence = true, absence/null = false, \"false\" = false)\n if (name === 'loading') {\n const isLoading = newValue !== null && newValue !== 'false';\n if (this.loading !== isLoading) {\n this.loading = isLoading;\n }\n return;\n }\n\n if (oldValue === newValue || !newValue || newValue === 'null' || newValue === 'undefined') return;\n\n // JSON attributes need parsing\n if (name === 'rows' || name === 'columns' || name === 'grid-config') {\n try {\n const parsed = JSON.parse(newValue);\n if (name === 'rows') this.rows = parsed;\n else if (name === 'columns') this.columns = parsed;\n else if (name === 'grid-config') this.gridConfig = parsed;\n } catch {\n warnDiagnostic(INVALID_ATTRIBUTE_JSON, `Invalid JSON for '${name}' attribute: ${newValue}`, this.id);\n }\n } else if (name === 'fit-mode') {\n this.fitMode = newValue as FitMode;\n }\n }\n\n #afterConnect(): void {\n // Shell changes the DOM structure - need to find elements appropriately\n const gridContent = this.#renderRoot.querySelector('.tbw-grid-content');\n const gridRoot = gridContent ?? this.#renderRoot.querySelector('.tbw-grid-root');\n\n this._headerRowEl = gridRoot?.querySelector('.header-row') as HTMLElement;\n // Faux scrollbar pattern:\n // - .faux-vscroll-spacer sets virtual height\n // - .rows-viewport provides visible height for virtualization calculations\n this._virtualization.totalHeightEl = gridRoot?.querySelector('.faux-vscroll-spacer') as HTMLElement;\n this._virtualization.viewportEl = gridRoot?.querySelector('.rows-viewport') as HTMLElement;\n this._bodyEl = gridRoot?.querySelector('.rows') as HTMLElement;\n\n // Cache DOM refs for hot path (refreshVirtualWindow) - avoid querySelector per scroll\n this.__rowsBodyEl = gridRoot?.querySelector('.rows-body') as HTMLElement;\n\n // Initialize shell header content and custom buttons if shell is active\n if (this.#shellController.isInitialized) {\n // Render plugin header content\n renderHeaderContent(this.#renderRoot, this.#shellState);\n // Render custom toolbar contents (render modes) - all contents unified in effectiveConfig\n renderCustomToolbarContents(this.#renderRoot, this.#effectiveConfig?.shell, this.#shellState);\n // Open default section if configured\n const defaultOpen = this.#effectiveConfig?.shell?.toolPanel?.defaultOpen;\n if (defaultOpen && this.#shellState.toolPanels.has(defaultOpen)) {\n this.openToolPanel();\n this.#shellState.expandedSections.add(defaultOpen);\n }\n // Restore panel content if panel was already open (e.g., after position change re-render)\n if (this.#shellState.isPanelOpen) {\n updatePanelState(this.#renderRoot, this.#shellState);\n renderPanelContent(this.#renderRoot, this.#shellState, {\n expand: this.#effectiveConfig?.icons?.expand,\n collapse: this.#effectiveConfig?.icons?.collapse,\n });\n updateToolbarActiveStates(this.#renderRoot, this.#shellState);\n }\n }\n\n // Mark for tests that afterConnect ran\n this.setAttribute('data-upgraded', '');\n this.#connected = true;\n\n // Create resize controller BEFORE setup - renderHeader() needs it for resize handle mousedown events\n this._resizeController = createResizeController(this);\n\n // Run setup\n this.#setup();\n\n // Set up DOM-element scroll listeners (these need to be re-attached when DOM is recreated)\n this.#setupScrollListeners(gridRoot);\n\n // Only add component-level event listeners once (afterConnect can be called multiple times)\n // When shell state changes or refreshShellHeader is called, we re-run afterConnect\n // but component-level listeners should not be duplicated (they don't depend on DOM elements)\n // Scroll listeners are already set up above and handle DOM recreation via their own AbortController\n if (this.#eventListenersAdded) {\n return;\n }\n this.#eventListenersAdded = true;\n\n // Get the signal for event listener cleanup (AbortController created in connectedCallback)\n const signal = this.disconnectSignal;\n\n // Set up all root-level and document-level event listeners\n // Consolidates keydown, mousedown, mousemove, mouseup in one place (event-delegation.ts)\n setupRootEventDelegation(this, this, this.#renderRoot, signal);\n\n // Note: click/dblclick handlers are set up via setupCellEventDelegation in #setupScrollListeners\n // This consolidates all body-level delegated event handlers in one place (event-delegation.ts)\n\n // Configure variable row heights based on plugins and user config\n this.#configureVariableHeights();\n\n // Initialize ARIA selection state\n queueMicrotask(() => this.#updateAriaSelection());\n\n // Request initial render through the scheduler.\n // The scheduler resolves ready() after the render cycle completes.\n // Framework adapters (React/Angular) will request COLUMNS phase via refreshColumns(),\n // which will be batched with this request - highest phase wins.\n this.#scheduler.requestPhase(RenderPhase.FULL, 'afterConnect');\n }\n\n /**\n * Configure variable row heights based on plugins and user config.\n * Called from both #afterConnect (initial setup) and #updatePluginConfigs (plugin changes).\n *\n * Handles three scenarios:\n * 1. Variable heights needed (rowHeight function or plugin with getRowHeight) → enable + init cache\n * 2. Fixed numeric rowHeight → set directly\n * 3. No config → measure from DOM after first paint\n */\n #configureVariableHeights(): void {\n const userRowHeight = this.#effectiveConfig.rowHeight;\n const hasRowHeightPlugin = this.#pluginManager.hasRowHeightPlugin();\n\n if (typeof userRowHeight === 'function' || hasRowHeightPlugin) {\n if (!this._virtualization.variableHeights) {\n this._virtualization.variableHeights = true;\n this._virtualization.rowHeight =\n typeof userRowHeight === 'number' && userRowHeight > 0 ? userRowHeight : this._virtualization.rowHeight || 28;\n this.#virtManager.initializePositionCache();\n if (typeof userRowHeight !== 'function') {\n this.#needsRowHeightMeasurement = true;\n }\n }\n } else if (!hasRowHeightPlugin && typeof userRowHeight !== 'function' && this._virtualization.variableHeights) {\n // Plugin was removed — revert to fixed heights\n this._virtualization.variableHeights = false;\n this._virtualization.positionCache = null;\n } else if (typeof userRowHeight === 'number' && userRowHeight > 0) {\n this._virtualization.rowHeight = userRowHeight;\n this._virtualization.variableHeights = false;\n } else {\n // No config — measure from DOM after first paint\n // ResizeObserver in #setupScrollListeners handles subsequent dynamic changes\n requestAnimationFrame(() => this.#measureRowHeight());\n }\n }\n\n /**\n * Measure actual row height from DOM.\n * Finds the tallest cell to account for custom renderers that may push height.\n */\n #measureRowHeight(): void {\n // Skip if a plugin is managing variable row heights (e.g., ResponsivePlugin with groups)\n // In that case, the plugin handles height via getExtraHeight() and we shouldn't\n // override the base row height, which would cause oscillation loops.\n if (this.#pluginManager.hasExtraHeight()) {\n return;\n }\n\n const firstRow = this._bodyEl?.querySelector('.data-grid-row') as HTMLElement | null;\n if (!firstRow) return;\n\n // Skip if the observed row has a per-row --tbw-row-height override.\n // Variable-height rows set this inline, and measuring them would ratchet\n // the global s.rowHeight up, corrupting the position cache for ALL rows.\n // Normal rows (no override) are still measured so s.rowHeight reflects the\n // true default height from CSS/themes.\n if (firstRow.style.getPropertyValue('--tbw-row-height')) {\n return;\n }\n\n // Find the tallest cell in the row (custom renderers may push some cells taller)\n const cells = firstRow.querySelectorAll('.cell');\n let maxCellHeight = 0;\n cells.forEach((cell) => {\n const h = (cell as HTMLElement).offsetHeight;\n if (h > maxCellHeight) maxCellHeight = h;\n });\n\n const rowRect = (firstRow as HTMLElement).getBoundingClientRect();\n\n // Use the larger of row height or max cell height\n const measuredHeight = Math.max(rowRect.height, maxCellHeight);\n // Only accept height INCREASES (with 1px threshold for sub-pixel rounding).\n // Decreases are ignored because mixed-height content (e.g., server-side plugin\n // placeholder rows vs real data rows) can cause oscillation: the ResizeObserver\n // measures a short placeholder → rowHeight decreases → spacer shrinks → scroll\n // maps to different rows (tall real data) → rowHeight increases → spacer grows\n // → scroll maps back to placeholders → repeat forever.\n if (measuredHeight > 0 && measuredHeight - this._virtualization.rowHeight > 1) {\n this._virtualization.rowHeight = measuredHeight;\n // Use scheduler to batch with other pending work\n this.#scheduler.requestPhase(RenderPhase.VIRTUALIZATION, 'measureRowHeight');\n }\n }\n\n /**\n * Measure actual row height from DOM for plugin-based variable heights.\n * Similar to #measureRowHeight but rebuilds the position cache after measurement.\n * Called after first render when a plugin implements getRowHeight() but user didn't provide a rowHeight function.\n */\n #measureRowHeightForPlugins(): void {\n const firstRow = this._bodyEl?.querySelector('.data-grid-row');\n if (!firstRow) return;\n\n // Find the tallest cell in the row (custom renderers may push some cells taller)\n const cells = firstRow.querySelectorAll('.cell');\n let maxCellHeight = 0;\n cells.forEach((cell) => {\n const h = (cell as HTMLElement).offsetHeight;\n if (h > maxCellHeight) maxCellHeight = h;\n });\n\n const rowRect = (firstRow as HTMLElement).getBoundingClientRect();\n\n // Use the larger of row height or max cell height\n const measuredHeight = Math.max(rowRect.height, maxCellHeight);\n\n // Update rowHeight if measurement is valid and different\n if (measuredHeight > 0) {\n const heightChanged = Math.abs(measuredHeight - this._virtualization.rowHeight) > 1;\n if (heightChanged) {\n this._virtualization.rowHeight = measuredHeight;\n }\n\n // ALWAYS rebuild position cache when this method is called (first render with plugins)\n // The position cache may have been built with the wrong estimated height (e.g., 28px)\n // even if rowHeight was later updated by ResizeObserver (e.g., to 33px)\n this.#virtManager.initializePositionCache();\n\n // Update spacer height with the correct total\n if (this._virtualization.totalHeightEl) {\n const newHeight = this.#virtManager.calculateTotalSpacerHeight(this._rows.length);\n this._virtualization.totalHeightEl.style.height = `${newHeight}px`;\n }\n }\n }\n\n /**\n * Set up scroll-related event listeners on DOM elements.\n * These need to be re-attached when the DOM is recreated (e.g., shell toggle).\n * Uses a separate AbortController that is recreated each time.\n */\n #setupScrollListeners(gridRoot: Element | null): void {\n // Abort any existing scroll listeners before adding new ones\n this.#scrollAbortController?.abort();\n this.#scrollAbortController = new AbortController();\n const scrollSignal = this.#scrollAbortController.signal;\n\n // Faux scrollbar pattern: scroll events come from the fake scrollbar element\n // Content area doesn't scroll - rows are positioned via transforms\n const fauxScrollbar = gridRoot?.querySelector('.faux-vscroll') as HTMLElement;\n const rowsEl = gridRoot?.querySelector('.rows') as HTMLElement;\n\n // Store reference for scroll position reading in refreshVirtualWindow\n this._virtualization.container = fauxScrollbar ?? this;\n\n // Cache whether any plugin has scroll handlers (checked once during setup)\n this.#hasScrollPlugins = this.#pluginManager?.getAll().some((p) => p.onScroll) ?? false;\n\n if (fauxScrollbar && rowsEl) {\n fauxScrollbar.addEventListener(\n 'scroll',\n () => {\n // Fast exit if no scroll processing needed\n if (!this._virtualization.enabled && !this.#hasScrollPlugins) return;\n\n const currentScrollTop = fauxScrollbar.scrollTop;\n const rowHeight = this._virtualization.rowHeight;\n\n // Bypass mode: all rows are rendered, just translate by scroll position\n // No need for virtual window calculations\n if (this._rows.length <= this._virtualization.bypassThreshold) {\n rowsEl.style.transform = `translateY(${-currentScrollTop}px)`;\n } else {\n // Virtualized mode: calculate sub-pixel offset for smooth scrolling\n // Even-aligned start preserves zebra stripe parity\n // DOM nth-child(even) will always match data row parity\n const positionCache = this._virtualization.positionCache;\n let rawStart: number;\n let startRowOffset: number;\n\n if (this._virtualization.variableHeights && positionCache && positionCache.length > 0) {\n // Variable heights: use binary search on position cache\n rawStart = getRowIndexAtOffset(positionCache, currentScrollTop);\n if (rawStart === -1) rawStart = 0;\n const evenAlignedStart = rawStart - (rawStart % 2);\n // Use actual offset from position cache for accurate transform\n startRowOffset = positionCache[evenAlignedStart]?.offset ?? evenAlignedStart * rowHeight;\n } else {\n // Fixed heights: simple division\n rawStart = Math.floor(currentScrollTop / rowHeight);\n const evenAlignedStart = rawStart - (rawStart % 2);\n startRowOffset = evenAlignedStart * rowHeight;\n }\n\n const subPixelOffset = -(currentScrollTop - startRowOffset);\n rowsEl.style.transform = `translateY(${subPixelOffset}px)`;\n }\n\n // Batch content update with requestAnimationFrame\n // Old content stays visible with smooth offset until new content renders\n this.#pendingScrollTop = currentScrollTop;\n if (!this.#scrollRaf) {\n this.#scrollRaf = requestAnimationFrame(() => {\n this.#scrollRaf = 0;\n if (this.#pendingScrollTop !== null) {\n this.#onScrollBatched(this.#pendingScrollTop);\n this.#pendingScrollTop = null;\n }\n });\n }\n },\n { passive: true, signal: scrollSignal },\n );\n\n // Horizontal scroll listener on scroll area\n // This is needed for plugins like column virtualization that need to respond to horizontal scroll\n const scrollArea = this.#renderRoot.querySelector('.tbw-scroll-area') as HTMLElement;\n this.#scrollAreaEl = scrollArea; // Store reference for use in #onScrollBatched\n this._virtualization.scrollAreaEl = scrollArea; // Cache for #calculateTotalSpacerHeight\n if (scrollArea && this.#hasScrollPlugins) {\n scrollArea.addEventListener(\n 'scroll',\n () => {\n // Dispatch horizontal scroll to plugins\n const scrollEvent = this.#pooledScrollEvent;\n scrollEvent.scrollTop = fauxScrollbar.scrollTop;\n scrollEvent.scrollLeft = scrollArea.scrollLeft;\n scrollEvent.scrollHeight = fauxScrollbar.scrollHeight;\n scrollEvent.scrollWidth = scrollArea.scrollWidth;\n scrollEvent.clientHeight = fauxScrollbar.clientHeight;\n scrollEvent.clientWidth = scrollArea.clientWidth;\n this.#pluginManager?.onScroll(scrollEvent);\n },\n { passive: true, signal: scrollSignal },\n );\n }\n\n // Forward wheel events from content area to faux scrollbar\n // Without this, mouse wheel over content wouldn't scroll\n // Listen on .tbw-grid-content to capture wheel events from entire grid area\n // Note: gridRoot may already BE .tbw-grid-content when shell is active, so search from shadow root\n const gridContentEl = this.#renderRoot.querySelector('.tbw-grid-content') as HTMLElement;\n // Use the already-stored scrollArea reference\n const scrollAreaForWheel = this.#scrollAreaEl;\n if (gridContentEl) {\n gridContentEl.addEventListener(\n 'wheel',\n (e: WheelEvent) => {\n // Don't intercept wheel events when a native <select> picker is open.\n // The picker (base-select) renders in the top layer but wheel events\n // still target the grid content — intercepting them would scroll the\n // grid and cause the browser to close the picker popup.\n try {\n if (gridContentEl.querySelector('select:open')) return;\n } catch {\n /* :open pseudo-class not supported — ignore */\n }\n\n // SHIFT+wheel or trackpad deltaX = horizontal scroll\n const isHorizontal = e.shiftKey || Math.abs(e.deltaX) > Math.abs(e.deltaY);\n\n if (isHorizontal && scrollAreaForWheel) {\n const delta = e.shiftKey ? e.deltaY : e.deltaX;\n const { scrollLeft, scrollWidth, clientWidth } = scrollAreaForWheel;\n const canScroll = (delta > 0 && scrollLeft < scrollWidth - clientWidth) || (delta < 0 && scrollLeft > 0);\n if (canScroll) {\n e.preventDefault();\n scrollAreaForWheel.scrollLeft += delta;\n }\n } else if (!isHorizontal) {\n const { scrollTop, scrollHeight, clientHeight } = fauxScrollbar;\n const canScroll =\n (e.deltaY > 0 && scrollTop < scrollHeight - clientHeight) || (e.deltaY < 0 && scrollTop > 0);\n if (canScroll) {\n e.preventDefault();\n fauxScrollbar.scrollTop += e.deltaY;\n }\n }\n // If can't scroll, event bubbles to scroll the page\n },\n { passive: false, signal: scrollSignal },\n );\n\n // Touch scrolling support for mobile devices\n // Supports both vertical (via faux scrollbar) and horizontal (via scroll area) scrolling\n // Includes momentum scrolling for natural \"flick\" behavior\n setupTouchScrollListeners(\n gridContentEl,\n this.#touchState,\n { fauxScrollbar, scrollArea: scrollAreaForWheel },\n scrollSignal,\n );\n }\n }\n\n // Set up delegated event handlers for cell interactions (click, dblclick, keydown)\n // This replaces per-cell event listeners with a single set of delegated handlers\n // Dramatically reduces memory usage: 4 listeners total vs. 30,000+ for large grids\n if (this._bodyEl) {\n setupCellEventDelegation(this, this._bodyEl, scrollSignal);\n }\n\n // Disconnect existing resize observer before creating new one\n // This ensures we observe the NEW viewport element after DOM recreation\n this.#resizeObserver?.disconnect();\n\n // Resize observer to refresh virtualization when viewport size changes\n // (e.g., when footer is added, window resizes, or shell panel toggles)\n if (this._virtualization.viewportEl) {\n this.#resizeObserver = new ResizeObserver(() => {\n // Update cached geometry so refreshVirtualWindow can skip forced layout reads\n this.#updateCachedGeometry();\n // Use scheduler for viewport resize - batches with other pending work\n // The scheduler already batches multiple requests per RAF\n this.#scheduler.requestPhase(RenderPhase.VIRTUALIZATION, 'resize-observer');\n });\n this.#resizeObserver.observe(this._virtualization.viewportEl);\n }\n\n // Note: We no longer need to schedule init-virtualization here since\n // the initial FULL render from #setup already includes virtualization.\n // Requesting it again here caused duplicate renders on initialization.\n\n // Track focus state via data attribute\n // Listen on render root to catch focus events from child elements\n (this.#renderRoot as EventTarget).addEventListener(\n 'focusin',\n () => {\n this.dataset.hasFocus = '';\n },\n { signal: scrollSignal },\n );\n (this.#renderRoot as EventTarget).addEventListener(\n 'focusout',\n (e) => {\n // Only remove if focus is leaving the grid entirely\n // relatedTarget is null when focus leaves the document, or the new focus target\n const newFocus = (e as FocusEvent).relatedTarget as Node | null;\n if (\n !newFocus ||\n (!this.#renderRoot.contains(newFocus) && !this.#focusManager.isInExternalFocusContainer(newFocus))\n ) {\n delete this.dataset.hasFocus;\n }\n },\n { signal: scrollSignal },\n );\n }\n\n /**\n * Set up ResizeObserver on first row to detect height changes.\n * Called after rows are rendered to observe the actual content.\n * Handles dynamic CSS loading, lazy images, font loading, column virtualization, etc.\n */\n #rowHeightObserverSetup = false; // Only set up once per lifecycle\n #setupRowHeightObserver(): void {\n // Only set up once - row height measurement is one-time during initialization\n if (this.#rowHeightObserverSetup) return;\n\n const firstRow = this._bodyEl?.querySelector('.data-grid-row') as HTMLElement | null;\n if (!firstRow) return;\n\n this.#rowHeightObserverSetup = true;\n this.#rowHeightObserver?.disconnect();\n\n // Observe the row element itself, not individual cells.\n // This catches all height changes including:\n // - Custom renderers that push cell height\n // - Column virtualization adding/removing columns\n // - Dynamic content loading (images, fonts)\n // Note: ResizeObserver fires on initial observation in modern browsers,\n // so no separate measurement call is needed.\n this.#rowHeightObserver = new ResizeObserver(() => {\n this.#measureRowHeight();\n });\n this.#rowHeightObserver.observe(firstRow);\n }\n // #endregion\n\n // #region Event System\n /**\n * Add a typed event listener for grid events.\n *\n * This override provides type-safe event handling for DataGrid-specific events.\n * The event detail is automatically typed based on the event name.\n *\n * **Prefer {@link on | grid.on()}** for most use cases — it auto-unwraps the\n * detail payload and returns an unsubscribe function for easy cleanup.\n *\n * Use `addEventListener` when you need standard DOM options like `once`,\n * `capture`, `passive`, or `signal` (AbortController).\n *\n * @example\n * ```typescript\n * // Recommended: use grid.on() instead\n * const off = grid.on('cell-click', (detail) => {\n * console.log(detail.field, detail.value);\n * });\n *\n * // addEventListener is useful when you need DOM listener options\n * grid.addEventListener('cell-click', (e) => {\n * console.log(e.detail.field, e.detail.value);\n * }, { once: true });\n *\n * // Or with AbortController for batch cleanup\n * const controller = new AbortController();\n * grid.addEventListener('sort-change', (e) => {\n * fetchData({ sortBy: e.detail.field });\n * }, { signal: controller.signal });\n * controller.abort(); // removes all listeners tied to this signal\n * ```\n *\n * @category Events\n */\n override addEventListener<K extends keyof DataGridEventMap<T>>(\n type: K,\n listener: (this: DataGridElement<T>, ev: CustomEvent<DataGridEventMap<T>[K]>) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n override addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n override addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject | ((ev: CustomEvent) => void),\n options?: boolean | AddEventListenerOptions,\n ): void {\n super.addEventListener(type, listener as EventListener, options);\n }\n\n /**\n * Remove a typed event listener for grid events.\n *\n * @category Events\n */\n override removeEventListener<K extends keyof DataGridEventMap<T>>(\n type: K,\n listener: (this: DataGridElement<T>, ev: CustomEvent<DataGridEventMap<T>[K]>) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n override removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n override removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject | ((ev: CustomEvent) => void),\n options?: boolean | EventListenerOptions,\n ): void {\n super.removeEventListener(type, listener as EventListener, options);\n }\n\n /**\n * Subscribe to a typed grid event. **Recommended** over `addEventListener`.\n *\n * Returns an unsubscribe function — call it to remove the listener.\n * The listener receives the event **detail** as its first argument\n * (no need to dig into `e.detail`), and the raw `CustomEvent` as\n * the second argument when you need `preventDefault()` or `stopPropagation()`.\n *\n * @remarks\n * Advantages over `addEventListener`:\n * - Auto-unwraps `event.detail` — your callback receives the payload directly\n * - Returns an unsubscribe function — no need to keep a reference to the handler\n * - Fully typed — event name → detail type is inferred automatically\n *\n * Use `addEventListener` instead when you need DOM listener options\n * like `once`, `capture`, `passive`, or `signal` (AbortController).\n *\n * @example\n * ```typescript\n * // Basic usage — detail is auto-unwrapped\n * const off = grid.on('cell-click', ({ row, field, value }) => {\n * console.log(`Clicked ${field} = ${value}`);\n * });\n *\n * // Clean up when done\n * off();\n * ```\n *\n * @example\n * ```typescript\n * // Access the raw event for preventDefault/stopPropagation\n * grid.on('cell-activate', (detail, event) => {\n * if (detail.field === 'notes') {\n * event.preventDefault(); // suppress default editing\n * openRichTextEditor(detail.row, detail.cellEl);\n * }\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Multiple subscriptions with easy teardown\n * const unsubs = [\n * grid.on('sort-change', ({ field, direction }) => {\n * console.log(`Sorted by ${field} ${direction === 1 ? 'ASC' : 'DESC'}`);\n * }),\n * grid.on('column-resize', ({ field, width }) => {\n * saveColumnWidth(field, width);\n * }),\n * grid.on('data-change', ({ rowCount }) => {\n * statusBar.textContent = `${rowCount} rows`;\n * }),\n * ];\n *\n * // Teardown all at once\n * unsubs.forEach((off) => off());\n * ```\n *\n * @category Events\n */\n on<K extends keyof DataGridEventMap<T>>(\n type: K,\n listener: (detail: DataGridEventMap<T>[K], event: CustomEvent<DataGridEventMap<T>[K]>) => void,\n ): () => void;\n on(type: string, listener: (detail: unknown, event: CustomEvent) => void): () => void;\n on(type: string, listener: (detail: unknown, event: CustomEvent) => void): () => void {\n const handler = ((e: CustomEvent) => {\n listener(e.detail, e);\n }) as EventListener;\n this.addEventListener(type, handler);\n return () => this.removeEventListener(type, handler);\n }\n\n #emit<D>(eventName: string, detail: D): void {\n this.dispatchEvent(new CustomEvent(eventName, { detail, bubbles: true, composed: true }));\n }\n\n _emitDataChange(): void {\n this.#emit<DataChangeDetail>('data-change', {\n rowCount: this._rows.length,\n sourceRowCount: this.#rows.length,\n });\n }\n\n /** Update ARIA selection attributes on rendered rows/cells */\n #updateAriaSelection(): void {\n // Mark active row and cell with aria-selected\n const rows = this._bodyEl?.querySelectorAll('.data-grid-row');\n rows?.forEach((row, rowIdx) => {\n const isActiveRow = rowIdx === this._focusRow;\n row.setAttribute('aria-selected', String(isActiveRow));\n row.querySelectorAll('.cell').forEach((cell, colIdx) => {\n (cell as HTMLElement).setAttribute('aria-selected', String(isActiveRow && colIdx === this._focusCol));\n });\n });\n }\n // #endregion\n\n // #region Batched Update System\n // Allows multiple property changes within the same microtask to be coalesced\n // into a single update cycle, dramatically reducing redundant renders.\n\n /**\n * Queue an update for a specific property type.\n * All updates queued within the same microtask are batched together.\n */\n #queueUpdate(type: 'rows' | 'columns' | 'gridConfig' | 'fitMode'): void {\n this.#pendingUpdateFlags[type] = true;\n\n // If already queued, skip scheduling\n if (this.#pendingUpdate) return;\n\n this.#pendingUpdate = true;\n // Use queueMicrotask to batch synchronous property sets\n queueMicrotask(() => this.#flushPendingUpdates());\n }\n\n /**\n * Process all pending updates in optimal order.\n * Priority: gridConfig first (may affect all), then columns, rows, fitMode, editMode\n */\n #flushPendingUpdates(): void {\n if (!this.#pendingUpdate || !this.#connected) {\n this.#pendingUpdate = false;\n return;\n }\n\n const flags = this.#pendingUpdateFlags;\n\n // Reset flags before processing to allow new updates during processing\n this.#pendingUpdate = false;\n this.#pendingUpdateFlags = {\n rows: false,\n columns: false,\n gridConfig: false,\n fitMode: false,\n };\n\n // If gridConfig changed, it supersedes columns/fit changes\n // but we still need to handle rows if set separately\n if (flags.gridConfig) {\n this.#applyGridConfigUpdate();\n // Still process rows if set separately (e.g., grid.gridConfig = ...; grid.rows = ...;)\n if (flags.rows) {\n this.#applyRowsUpdate();\n }\n return;\n }\n\n // Process remaining changes in dependency order\n if (flags.columns) {\n this.#applyColumnsUpdate();\n }\n if (flags.rows) {\n this.#applyRowsUpdate();\n }\n if (flags.fitMode) {\n this.#applyFitModeUpdate();\n }\n }\n\n // Individual update applicators - these do the actual work\n #applyRowsUpdate(): void {\n this._rows = Array.isArray(this.#rows) ? [...this.#rows] : [];\n // Rebuild row ID map for O(1) lookups\n this._rebuildRowIdMap();\n // Request a ROWS phase render through the scheduler.\n // This batches with any other pending work (e.g., React adapter's refreshColumns).\n this.#scheduler.requestPhase(RenderPhase.ROWS, 'applyRowsUpdate');\n }\n\n /**\n * Rebuild the row ID map for O(1) lookups.\n * Called when rows array changes.\n */\n _rebuildRowIdMap(): void {\n this.#rowIdMap.clear();\n const getRowId = this.#effectiveConfig.getRowId;\n\n this._rows.forEach((row, index) => {\n const id = tryResolveRowId(row, getRowId);\n if (id !== undefined) {\n this.#rowIdMap.set(id, { row, index });\n }\n // Rows without IDs are skipped - they won't be accessible via getRow/updateRow\n });\n }\n\n #applyColumnsUpdate(): void {\n invalidateCellCache(this);\n this.#configManager.merge();\n this.#setup();\n }\n\n #applyFitModeUpdate(): void {\n this.#configManager.merge();\n const mode = this.#effectiveConfig.fitMode;\n if (mode === 'fixed') {\n this.__didInitialAutoSize = false;\n autoSizeColumns(this);\n } else {\n this._columns.forEach((c: any) => {\n if (!c.__userResized && c.__autoSized) delete c.width;\n });\n updateTemplate(this);\n }\n }\n\n #applyGridConfigUpdate(): void {\n // Parse shell config (title, etc.) - needed for React where gridConfig is set after initial render\n // Note: We call individual parsers here instead of #parseLightDom() because we need to\n // parse tool panels AFTER plugins are initialized (see below)\n parseLightDomShell(this, this.#shellState);\n parseLightDomToolButtons(this, this.#shellState);\n\n const hadShell = !!this.#renderRoot.querySelector('.has-shell');\n const hadToolPanel = !!this.#renderRoot.querySelector('.tbw-tool-panel');\n const accordionSectionsBefore = this.#renderRoot.querySelectorAll('.tbw-accordion-section').length;\n const prevPosition =\n (this.#renderRoot.querySelector('.tbw-tool-panel') as HTMLElement)?.dataset.position ?? 'right';\n\n this.#configManager.parseLightDomColumns(this);\n this.#configManager.merge();\n this.#updatePluginConfigs();\n\n // Re-check variable heights after config merge: rowHeight may have changed\n // from a number to a function (or vice versa) without any plugin changes.\n this.#configureVariableHeights();\n\n // Parse light DOM tool panels AFTER plugins are initialized\n parseLightDomToolPanels(this, this.#shellState, this.#getToolPanelRendererFactory());\n this.#configManager.markSourcesChanged();\n this.#configManager.merge();\n\n const nowNeedsShell = shouldRenderShellHeader(this.#effectiveConfig?.shell);\n const nowHasToolPanels = (this.#effectiveConfig?.shell?.toolPanels?.length ?? 0) > 0;\n const toolPanelCount = this.#effectiveConfig?.shell?.toolPanels?.length ?? 0;\n const newPosition = this.#effectiveConfig?.shell?.toolPanel?.position ?? 'right';\n\n // Full re-render needed if shell state, panel count, or panel position changed\n const needsFullRerender =\n hadShell !== nowNeedsShell ||\n (!hadToolPanel && nowHasToolPanels) ||\n (hadToolPanel && toolPanelCount !== accordionSectionsBefore) ||\n (hadToolPanel && prevPosition !== newPosition);\n\n if (needsFullRerender) {\n // Prepare shell state for re-render (moves toolbar buttons back to original container)\n prepareForRerender(this.#shellState);\n this.#render();\n this.#injectAllPluginStyles();\n this.#afterConnect();\n this._rebuildRowIdMap();\n return;\n }\n\n if (hadShell) {\n this.#updateShellHeaderInPlace();\n }\n\n this._rebuildRowIdMap();\n this.#scheduler.requestPhase(RenderPhase.COLUMNS, 'applyGridConfigUpdate');\n }\n\n /**\n * Update the shell header DOM in place without a full re-render.\n * Handles title, toolbar buttons, and other shell header changes.\n */\n #updateShellHeaderInPlace(): void {\n const shellHeader = this.#renderRoot.querySelector('.tbw-shell-header');\n if (!shellHeader) return;\n\n const title = this.#effectiveConfig.shell?.header?.title ?? this.#shellState.lightDomTitle;\n\n // Update or create title element\n let titleEl = shellHeader.querySelector('.tbw-shell-title') as HTMLElement | null;\n if (title) {\n if (!titleEl) {\n // Create title element if it doesn't exist\n titleEl = document.createElement('h2');\n titleEl.className = 'tbw-shell-title';\n titleEl.setAttribute('part', 'shell-title');\n // Insert at the beginning of the shell header\n shellHeader.insertBefore(titleEl, shellHeader.firstChild);\n }\n titleEl.textContent = title;\n } else if (titleEl) {\n // Remove title element if no title\n titleEl.remove();\n }\n }\n\n // NOTE: Legacy watch handlers have been replaced by the batched update system.\n // The #queueUpdate() method schedules updates which are processed by #flushPendingUpdates()\n // and individual #apply*Update() methods. This coalesces rapid property changes\n // (e.g., setting rows, columns, gridConfig in quick succession) into a single update cycle.\n\n #processColumns(): void {\n // Bump the row-render epoch so that renderVisibleRows knows the column\n // structure may have changed and triggers a full inline rebuild for each\n // pooled row element. This is intentionally done here — NOT inside\n // refreshVirtualWindow — so that a pure ROWS-phase refresh (data change,\n // same columns) can skip the expensive destroy-and-recreate cycle and\n // use fastPatchRow instead.\n this.__rowRenderEpoch++;\n\n // Let plugins process visible columns (column grouping, etc.)\n // Start from base columns (before any plugin transformation) - like #rebuildRowModel uses #rows\n if (this.#pluginManager) {\n // Use base columns as source of truth, falling back to current _columns if not set\n const sourceColumns = this.#baseColumns.length > 0 ? this.#baseColumns : this._columns;\n const visibleCols = sourceColumns.filter((c) => !c.hidden);\n const hiddenCols = sourceColumns.filter((c) => c.hidden);\n const processedColumns = this.#pluginManager.processColumns([...visibleCols]);\n\n // If plugins modified visible columns, update them\n if (processedColumns !== visibleCols) {\n // Build set for quick lookup\n const processedFields = new Set(processedColumns.map((c: ColumnInternal) => c.field));\n\n // Check if this is a complete column replacement (e.g., pivot mode)\n // If no processed columns match original columns, use processed columns directly\n const hasMatchingFields = visibleCols.some((c) => processedFields.has(c.field));\n\n if (!hasMatchingFields && processedColumns.length > 0) {\n // Complete replacement: use processed columns directly (pivot mode)\n // Preserve hidden columns at the end\n this._columns = [...processedColumns, ...hiddenCols] as ColumnInternal<T>[];\n } else {\n // Plugins may have modified, added, or reordered visible columns.\n // Re-interleave hidden columns at their original positions (from sourceColumns)\n // so that showing a column later restores it to its correct position.\n this._columns = this.#mergeColumnsPreservingOrder(\n sourceColumns,\n processedColumns as ColumnInternal<T>[],\n hiddenCols,\n );\n }\n } else {\n // Plugins returned columns unchanged, but we may need to restore from base\n this._columns = [...sourceColumns] as ColumnInternal<T>[];\n }\n }\n }\n\n /**\n * Merge plugin-processed visible columns with hidden columns, preserving original order.\n * Hidden columns are re-inserted at their original positions (from sourceColumns) so that\n * showing a column later restores it to the correct position instead of appending at end.\n *\n * @param sourceColumns - The original columns in correct order (from #baseColumns)\n * @param processedVisible - Plugin-processed visible columns\n * @param hiddenCols - Hidden columns to re-interleave\n * @returns Merged columns array preserving original ordering for hidden columns\n */\n #mergeColumnsPreservingOrder(\n sourceColumns: ColumnInternal<T>[],\n processedVisible: ColumnInternal<T>[],\n hiddenCols: ColumnInternal<T>[],\n ): ColumnInternal<T>[] {\n if (hiddenCols.length === 0) return processedVisible;\n\n // Build a map of processed visible columns by field for O(1) lookup\n const processedMap = new Map<string, ColumnInternal<T>>();\n for (const col of processedVisible) {\n processedMap.set(col.field, col);\n }\n\n // Collect plugin-added columns (not in source) — these go at the end\n const sourceFields = new Set(sourceColumns.map((c) => c.field));\n const pluginAdded: ColumnInternal<T>[] = [];\n for (const col of processedVisible) {\n if (!sourceFields.has(col.field)) {\n pluginAdded.push(col);\n }\n }\n\n // Walk sourceColumns in original order, picking from processed (visible) or hidden\n const result: ColumnInternal<T>[] = [];\n for (const srcCol of sourceColumns) {\n const processed = processedMap.get(srcCol.field);\n if (processed) {\n // Visible column — use the plugin-processed version\n result.push(processed);\n } else if (srcCol.hidden) {\n // Hidden column — keep at original position\n result.push(srcCol);\n }\n // else: column was removed by plugins — skip\n }\n\n // Append any plugin-added columns (e.g., expander) at the end\n result.push(...pluginAdded);\n\n return result;\n }\n\n /** Recompute row model via plugin hooks. */\n #rebuildRowModel(): void {\n // Invalidate cell display value cache - rows are changing\n invalidateCellCache(this);\n\n // Start fresh from original rows (plugins will transform them)\n const originalRows = Array.isArray(this.#rows) ? [...this.#rows] : [];\n\n // Re-apply core sort before plugins so sorted order is maintained on data refresh.\n // This runs BEFORE processRows so grouping/filtering work on sorted data.\n const sortedRows = reapplyCoreSort(this, originalRows);\n\n // Let plugins process rows (they may add, remove, transform rows)\n // Plugins can add markers for specialized rendering via the renderRow hook\n const processedRows = this.#pluginManager?.processRows(sortedRows) ?? sortedRows;\n\n // Store processed rows for rendering\n // Note: processedRows may contain group markers that plugins handle via renderRow hook\n this._rows = processedRows as T[];\n\n // Rebuild row ID map to keep indices in sync with the (possibly sorted) _rows.\n // Without this, #rowIdMap has stale indices from the unsorted copy set in #applyRowsUpdate.\n this._rebuildRowIdMap();\n\n // Rebuild position cache for variable heights (rows may have changed)\n if (this._virtualization.variableHeights) {\n this.#virtManager.initializePositionCache();\n }\n\n this._emitDataChange();\n }\n\n /**\n * Apply animation configuration to CSS custom properties on the host element.\n * This makes the grid's animation settings available to plugins via CSS variables.\n * Called by ConfigManager after merge.\n */\n #applyAnimationConfig(gridConfig: GridConfig<T>): void {\n const config: AnimationConfig = {\n ...DEFAULT_ANIMATION_CONFIG,\n ...gridConfig.animation,\n };\n\n // Resolve animation mode\n const mode = config.mode ?? 'reduced-motion';\n let enabled: 0 | 1 = 1;\n\n if (mode === false || mode === 'off') {\n enabled = 0;\n } else if (mode === true || mode === 'on') {\n enabled = 1;\n }\n // For 'reduced-motion', we leave enabled=1 and let CSS @media query handle it\n\n // Set CSS custom properties\n this.style.setProperty('--tbw-animation-duration', `${config.duration}ms`);\n this.style.setProperty('--tbw-animation-easing', config.easing ?? 'ease-out');\n this.style.setProperty('--tbw-animation-enabled', String(enabled));\n\n // Set data attribute for mode-based CSS selectors\n this.dataset.animationMode = typeof mode === 'boolean' ? (mode ? 'on' : 'off') : mode;\n }\n // #endregion\n\n // #region Internal Helpers\n _renderVisibleRows(start: number, end: number, epoch = this.__rowRenderEpoch): void {\n // Use cached hook to avoid creating closures on every render (hot path optimization)\n if (!this.#renderRowHook) {\n this.#renderRowHook = (row: any, rowEl: HTMLElement, rowIndex: number): boolean => {\n return this.#pluginManager?.renderRow(row, rowEl, rowIndex) ?? false;\n };\n }\n renderVisibleRows(this, start, end, epoch, this.#renderRowHook);\n\n // Re-apply loading state for rows that are currently loading.\n // renderInlineRow clears innerHTML (destroying overlay DOM) and removes the loading class,\n // so we must re-inject the overlay after row rendering completes.\n if (this.#loadingRows.size > 0) {\n for (const rowId of this.#loadingRows) {\n this.#updateRowLoadingState(rowId, true);\n }\n }\n }\n\n // ARIA state - uses external module for pure functions\n #ariaState: AriaState = createAriaState();\n\n /** Updates ARIA row/col counts. Delegates to aria.ts module. */\n _updateAriaCounts(rowCount: number, colCount: number): void {\n updateAriaCounts(this.#ariaState, this.__rowsBodyEl, this._bodyEl, rowCount, colCount);\n }\n\n // --- Methods exposed for extracted managers ---\n\n /** @internal Request a render at the given phase through the scheduler. */\n _requestSchedulerPhase(phase: number, source: string): void {\n this.#scheduler.requestPhase(phase, source);\n }\n\n /** @internal Plugin extra height for spacer calculation. */\n _getPluginExtraHeight(): number {\n return this.#pluginManager?.getExtraHeight() ?? 0;\n }\n\n /** @internal Plugin-specific row height override. */\n _getPluginRowHeight(row: T, index: number): number | undefined {\n return this.#pluginManager?.getRowHeight?.(row, index);\n }\n\n /** @internal Plugin extra height before a given row index. */\n _getPluginExtraHeightBefore(start: number): number {\n return this.#pluginManager?.getExtraHeightBefore?.(start) ?? 0;\n }\n\n /** @internal Let plugins adjust the virtual start index backwards. */\n _adjustPluginVirtualStart(start: number, scrollTop: number, rowHeight: number): number | undefined {\n return this.#pluginManager?.adjustVirtualStart(start, scrollTop, rowHeight);\n }\n\n /** @internal Run plugin afterRender hooks. */\n _afterPluginRender(): void {\n this.#pluginManager?.afterRender();\n }\n\n /** @internal Emit a plugin event through the plugin manager. */\n _emitPluginEvent(event: string, detail: unknown): void {\n this.#pluginManager?.emitPluginEvent(event, detail);\n }\n\n // --- Scheduler pipeline methods ---\n\n /** @internal Merge effective config (FULL/COLUMNS phase). */\n _schedulerMergeConfig(): void {\n this.#configManager.parseLightDomColumns(this);\n this.#configManager.merge();\n this.#updatePluginConfigs();\n validatePluginProperties(this.#effectiveConfig, this.#pluginManager?.getPlugins() ?? [], this.id);\n validatePluginConfigRules(this.#pluginManager?.getPlugins() ?? [], this.id);\n validatePluginIncompatibilities(this.#pluginManager?.getPlugins() ?? [], this.id);\n this.#updateAriaLabels();\n this.#baseColumns = [...this._columns];\n }\n\n /** @internal Process columns through plugins (COLUMNS phase). */\n _schedulerProcessColumns(): void {\n this.#processColumns();\n }\n\n /** @internal Rebuild row model through plugins (ROWS phase). */\n _schedulerProcessRows(): void {\n this.#rebuildRowModel();\n }\n\n /** @internal Render header DOM (HEADER phase). */\n _schedulerRenderHeader(): void {\n renderHeader(this);\n }\n\n /** @internal Update CSS grid template (COLUMNS phase). */\n _schedulerUpdateTemplate(): void {\n updateTemplate(this);\n }\n\n /** @internal Run afterRender hooks and post-render bookkeeping (STYLE phase). */\n _schedulerAfterRender(): void {\n this.#pluginManager?.afterRender();\n\n // Recalculate spacer height after plugins modify the DOM in afterRender.\n if (this._virtualization.enabled && this._virtualization.totalHeightEl) {\n queueMicrotask(() => {\n if (!this._virtualization.totalHeightEl) return;\n const newTotalHeight = this.#virtManager.calculateTotalSpacerHeight(this._rows.length);\n this._virtualization.totalHeightEl.style.height = `${newTotalHeight}px`;\n });\n }\n\n // Auto-size columns on first render if fitMode is 'fixed'\n const mode = this.#effectiveConfig.fitMode;\n if (mode === 'fixed' && !this.__didInitialAutoSize) {\n this.__didInitialAutoSize = true;\n autoSizeColumns(this);\n }\n // Restore focus styling if requested by a plugin\n if (this._restoreFocusAfterRender) {\n this._restoreFocusAfterRender = false;\n ensureCellVisible(this);\n }\n // Set up row height observer after first render (rows are now in DOM)\n if (this._virtualization.enabled && !this.#rowHeightObserverSetup) {\n this.#setupRowHeightObserver();\n }\n // Measure base row height for plugin-based variable heights on first render\n if (this.#needsRowHeightMeasurement) {\n this.#needsRowHeightMeasurement = false;\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n this.#measureRowHeightForPlugins();\n });\n });\n }\n\n // Show loading overlay if loading was set before the grid root was created.\n if (this.#loading) {\n this.#updateLoadingOverlay();\n }\n }\n\n /** @internal Whether the grid is fully connected (scheduler guard). */\n get _schedulerIsConnected(): boolean {\n return this.isConnected && this.#connected;\n }\n\n // Shell controller & config manager support (replaces callback closures)\n /** @internal The grid's host HTMLElement. Use instead of casting `grid as unknown as HTMLElement`. */\n get _hostElement(): HTMLElement {\n return this;\n }\n\n /** @internal The grid's render root element for DOM queries. */\n get _renderRoot(): HTMLElement {\n return this.#renderRoot;\n }\n\n /** @internal Emit a custom event from the grid. */\n _emit(eventName: string, detail: unknown): void {\n this.#emit(eventName, detail);\n }\n\n /** @internal Get accordion expand/collapse icons from effective config. */\n get _accordionIcons(): { expand: IconValue; collapse: IconValue } {\n return {\n expand: this.#effectiveConfig?.icons?.expand ?? DEFAULT_GRID_ICONS.expand,\n collapse: this.#effectiveConfig?.icons?.collapse ?? DEFAULT_GRID_ICONS.collapse,\n };\n }\n\n /** @internal Shell state for config manager shell merging. */\n get _shellState(): ShellState {\n return this.#shellState;\n }\n\n /** @internal Clear the row pool and body element. */\n _clearRowPool(): void {\n this._rowPool.length = 0;\n if (this._bodyEl) this._bodyEl.innerHTML = '';\n this.__rowRenderEpoch++;\n }\n\n /** @internal Run grid setup (DOM rebuild). */\n _setup(): void {\n this.#setup();\n }\n\n /** @internal Apply animation configuration to host element. */\n _applyAnimationConfig(config: GridConfig<T>): void {\n this.#applyAnimationConfig(config);\n }\n\n /** Updates ARIA label and describedby attributes. Delegates to aria.ts module. */\n #updateAriaLabels(): void {\n updateAriaLabels(this.#ariaState, this.__rowsBodyEl, this.#effectiveConfig, this.#shellState);\n }\n\n /**\n * Update the loading overlay visibility.\n * Called when `loading` property changes.\n */\n #updateLoadingOverlay(): void {\n const gridRoot = this.querySelector('.tbw-grid-root');\n if (!gridRoot) return;\n\n if (this.#loading) {\n // Create overlay if it doesn't exist\n if (!this.#loadingOverlayEl) {\n this.#loadingOverlayEl = createLoadingOverlay(this.#effectiveConfig?.loadingRenderer);\n }\n showLoadingOverlay(gridRoot, this.#loadingOverlayEl);\n } else {\n hideLoadingOverlay(this.#loadingOverlayEl);\n }\n }\n\n /**\n * Update a row's loading state in the DOM.\n * @param rowId - The row's unique identifier\n * @param loading - Whether the row is loading\n */\n #updateRowLoadingState(rowId: string, loading: boolean): void {\n // Find the row element by row ID\n const rowData = this.#rowIdMap.get(rowId);\n if (!rowData) return;\n\n const rowEl = this.findRenderedRowElement?.(rowData.index);\n if (!rowEl) return;\n\n setRowLoadingState(rowEl, loading);\n }\n\n /**\n * Update a cell's loading state in the DOM.\n * @param rowId - The row's unique identifier\n * @param field - The column field\n * @param loading - Whether the cell is loading\n */\n #updateCellLoadingState(rowId: string, field: string, loading: boolean): void {\n // Find the row element by row ID\n const rowData = this.#rowIdMap.get(rowId);\n if (!rowData) return;\n\n const rowEl = this.findRenderedRowElement?.(rowData.index);\n if (!rowEl) return;\n\n // Find the cell by field\n const colIndex = this._visibleColumns.findIndex((c) => c.field === field);\n if (colIndex < 0) return;\n\n const cellEl = rowEl.children[colIndex] as HTMLElement | undefined;\n if (!cellEl) return;\n\n setCellLoadingState(cellEl, loading);\n }\n\n /**\n * Request a full grid re-setup through the render scheduler.\n * This method queues all the config merging, column/row processing, and rendering\n * to happen in the next animation frame via the scheduler.\n *\n * Previously this method executed rendering synchronously, but that caused race\n * conditions with framework adapters that also schedule their own render work.\n */\n #setup(): void {\n if (!this.isConnected) return;\n if (!this._headerRowEl || !this._bodyEl) {\n return;\n }\n\n // Read light DOM column configuration (synchronous DOM read)\n this.#configManager.parseLightDomColumns(this);\n\n // Apply initial column state synchronously if present\n // (needs to happen before scheduler to avoid flash of unstyled content)\n if (this.#initialColumnState) {\n const state = this.#initialColumnState;\n this.#initialColumnState = undefined; // Clear to avoid re-applying\n // Temporarily merge config so applyState has columns to work with\n this.#configManager.merge();\n const plugins = (this.#pluginManager?.getAll() ?? []) as BaseGridPlugin[];\n this.#configManager.applyState(state, plugins);\n }\n\n // Ensure legacy inline grid styles are cleared from container\n if (this._bodyEl) {\n this._bodyEl.style.display = '';\n this._bodyEl.style.gridTemplateColumns = '';\n }\n\n // Request full render through scheduler - batches with framework adapter work\n this.#scheduler.requestPhase(RenderPhase.FULL, 'setup');\n }\n\n #onScrollBatched(scrollTop: number): void {\n // PERF: Read all geometry values BEFORE DOM writes to avoid forced synchronous layout.\n // refreshVirtualWindow and onScrollRender write to the DOM (transforms, innerHTML, attributes).\n // Reading geometry after those writes forces the browser to synchronously compute layout.\n // By reading first, we batch reads together, then do all writes.\n let scrollLeft = 0;\n let scrollHeight = 0;\n let scrollWidth = 0;\n let clientHeight = 0;\n let clientWidth = 0;\n if (this.#hasScrollPlugins) {\n const fauxScrollbar = this._virtualization.container;\n const scrollArea = this.#scrollAreaEl;\n scrollLeft = scrollArea?.scrollLeft ?? 0;\n scrollHeight = fauxScrollbar?.scrollHeight ?? 0;\n scrollWidth = scrollArea?.scrollWidth ?? 0;\n clientHeight = fauxScrollbar?.clientHeight ?? 0;\n clientWidth = scrollArea?.clientWidth ?? 0;\n }\n\n // Faux scrollbar pattern: content never scrolls, just update transforms\n // Old content stays visible until new transforms are applied\n const windowChanged = this.refreshVirtualWindow(false);\n\n // Let plugins reapply visual state to recycled DOM elements\n // Only run when the visible window actually changed to avoid expensive DOM queries\n if (windowChanged) {\n this.#pluginManager?.onScrollRender();\n }\n\n // Schedule debounced measurement for variable heights mode\n // This progressively builds up the height cache as user scrolls\n if (this._virtualization.variableHeights) {\n if (this.#scrollMeasureTimeout) {\n clearTimeout(this.#scrollMeasureTimeout);\n }\n // Measure after scroll settles (100ms debounce)\n this.#scrollMeasureTimeout = window.setTimeout(() => {\n this.#scrollMeasureTimeout = 0;\n this.#virtManager.measureRenderedRowHeights(this._virtualization.start, this._virtualization.end);\n }, 100);\n }\n\n // Dispatch to plugins (using cached flag and pooled event object to avoid GC)\n // Geometry values were read before DOM writes above - use pre-read values.\n if (this.#hasScrollPlugins) {\n const scrollEvent = this.#pooledScrollEvent;\n scrollEvent.scrollTop = scrollTop;\n scrollEvent.scrollLeft = scrollLeft;\n scrollEvent.scrollHeight = scrollHeight;\n scrollEvent.scrollWidth = scrollWidth;\n scrollEvent.clientHeight = clientHeight;\n scrollEvent.clientWidth = clientWidth;\n this.#pluginManager?.onScroll(scrollEvent);\n }\n }\n\n /**\n * Find the header row element in the shadow DOM.\n * Used by plugins that need to access header cells for styling or measurement.\n * @group DOM Access\n * @internal Plugin API\n */\n findHeaderRow(): HTMLElement {\n return this.#renderRoot.querySelector('.header-row') as HTMLElement;\n }\n\n /**\n * Find a rendered row element by its data row index.\n * Returns null if the row is not currently rendered (virtualized out of view).\n * Used by plugins that need to access specific row elements for styling or measurement.\n * @group DOM Access\n * @internal Plugin API\n * @param rowIndex - The data row index (not the DOM position)\n */\n findRenderedRowElement(rowIndex: number): HTMLElement | null {\n const s = this._virtualization;\n const poolIndex = rowIndex - s.start;\n if (poolIndex >= 0 && poolIndex < this._rowPool.length && poolIndex < s.end - s.start) {\n return this._rowPool[poolIndex];\n }\n return null;\n }\n\n /**\n * Dispatch a cell click event to the plugin system, then emit a public event.\n * Plugins get first chance to handle the event. After plugins process it,\n * a `cell-click` CustomEvent is dispatched for external listeners.\n *\n * @returns `true` if any plugin handled (consumed) the event, or if consumer canceled\n * @fires cell-activate - Unified activation event (cancelable) - fires FIRST\n * @fires cell-click - Emitted after plugins process the click, with full cell context\n */\n _dispatchCellClick(event: MouseEvent, rowIndex: number, colIndex: number, cellEl: HTMLElement): boolean {\n const row = this._rows[rowIndex];\n // colIndex from data-col is a visible-column index (rendering uses _visibleColumns).\n // Use _visibleColumns so the resolved column matches the clicked cell.\n const col = this._visibleColumns[colIndex];\n if (!row || !col) return false;\n\n const field = col.field;\n const value = (row as Record<string, unknown>)[field];\n\n // Emit cell-activate FIRST (cancelable) - consumers can prevent plugin behavior\n const activateEvent = new CustomEvent('cell-activate', {\n cancelable: true,\n bubbles: true,\n composed: true,\n detail: {\n rowIndex,\n colIndex,\n column: col,\n field,\n value,\n row,\n cellEl,\n trigger: 'pointer' as const,\n originalEvent: event,\n },\n });\n this.dispatchEvent(activateEvent);\n\n // If consumer canceled, don't let plugins handle it\n if (activateEvent.defaultPrevented) {\n return true; // Treated as \"handled\"\n }\n\n const cellClickEvent: CellClickEvent = {\n row,\n rowIndex,\n colIndex,\n column: col,\n field,\n value,\n cellEl,\n originalEvent: event,\n };\n\n // Let plugins handle (editing, selection, etc.)\n const handled = this.#pluginManager?.onCellClick(cellClickEvent) ?? false;\n\n // Emit informational cell-click event for external listeners\n this.#emit('cell-click', cellClickEvent);\n\n return handled;\n }\n\n /**\n * Dispatch a row click event to the plugin system, then emit a public event.\n * Plugins get first chance to handle the event. After plugins process it,\n * a `row-click` CustomEvent is dispatched for external listeners.\n *\n * @returns `true` if any plugin handled (consumed) the event\n * @fires row-click - Emitted after plugins process the click, with full row context\n */\n _dispatchRowClick(event: MouseEvent, rowIndex: number, row: any, rowEl: HTMLElement): boolean {\n if (!row) return false;\n\n const rowClickEvent: RowClickEvent = {\n rowIndex,\n row,\n rowEl,\n originalEvent: event,\n };\n\n // Let plugins handle first\n const handled = this.#pluginManager?.onRowClick(rowClickEvent) ?? false;\n\n // Emit public event for external listeners (reuse same event object)\n this.#emit('row-click', rowClickEvent);\n\n return handled;\n }\n\n /**\n * Dispatch a header click event to the plugin system.\n * Returns true if any plugin handled the event.\n */\n _dispatchHeaderClick(event: MouseEvent | KeyboardEvent, col: ColumnConfig, headerEl: HTMLElement): boolean {\n if (!col) return false;\n\n const headerClickEvent: HeaderClickEvent = {\n colIndex: this._columns.indexOf(col as ColumnInternal<T>),\n field: col.field,\n column: col,\n headerEl,\n originalEvent: event,\n };\n\n return this.#pluginManager?.onHeaderClick(headerClickEvent) ?? false;\n }\n\n /**\n * Dispatch a keyboard event to the plugin system.\n * Returns true if any plugin handled the event.\n */\n _dispatchKeyDown(event: KeyboardEvent): boolean {\n return this.#pluginManager?.onKeyDown(event) ?? false;\n }\n\n /**\n * Get horizontal scroll boundary offsets from plugins.\n * Used by keyboard navigation to ensure focused cells are fully visible\n * when plugins like pinned columns obscure part of the scroll area.\n */\n _getHorizontalScrollOffsets(\n rowEl?: HTMLElement,\n focusedCell?: HTMLElement,\n ): { left: number; right: number; skipScroll?: boolean } {\n return this.#pluginManager?.getHorizontalScrollOffsets(rowEl, focusedCell) ?? { left: 0, right: 0 };\n }\n\n /**\n * Query all plugins with a generic query and collect responses.\n * This enables inter-plugin communication without the core knowing plugin-specific concepts.\n * @group Plugin Communication\n * @internal Plugin API\n *\n * @example\n * // Check if any plugin vetoes moving a column\n * const responses = grid.queryPlugins<boolean>({ type: 'canMoveColumn', context: column });\n * const canMove = !responses.includes(false);\n *\n * @deprecated Use the simplified `query<T>(type, context)` method instead. Will be removed in v2.\n */\n queryPlugins<T>(query: PluginQuery): T[] {\n return this.#pluginManager?.queryPlugins<T>(query) ?? [];\n }\n\n /**\n * Query plugins with a simplified API.\n * This is a convenience wrapper around `queryPlugins` that uses a flat signature.\n *\n * @param type - The query type (e.g., 'canMoveColumn')\n * @param context - The query context/parameters\n * @returns Array of non-undefined responses from plugins\n * @group Plugin Communication\n * @internal Plugin API\n *\n * @example\n * // Check if any plugin vetoes moving a column\n * const responses = grid.query<boolean>('canMoveColumn', column);\n * const canMove = !responses.includes(false);\n *\n * // Get context menu items from all plugins\n * const items = grid.query<ContextMenuItem[]>('getContextMenuItems', params).flat();\n */\n query<T>(type: string, context: unknown): T[] {\n return this.#pluginManager?.queryPlugins<T>({ type, context }) ?? [];\n }\n\n /**\n * Dispatch cell mouse events for drag operations.\n * Returns true if any plugin started a drag.\n * @group Event Dispatching\n * @internal Plugin API - called by event-delegation.ts\n */\n _dispatchCellMouseDown(event: CellMouseEvent): boolean {\n return this.#pluginManager?.onCellMouseDown(event) ?? false;\n }\n\n /**\n * Dispatch cell mouse move during drag.\n * @group Event Dispatching\n * @internal Plugin API - called by event-delegation.ts\n */\n _dispatchCellMouseMove(event: CellMouseEvent): void {\n this.#pluginManager?.onCellMouseMove(event);\n }\n\n /**\n * Dispatch cell mouse up to end drag.\n * @group Event Dispatching\n * @internal Plugin API - called by event-delegation.ts\n */\n _dispatchCellMouseUp(event: CellMouseEvent): void {\n this.#pluginManager?.onCellMouseUp(event);\n }\n\n /**\n * Call afterCellRender hook on all plugins.\n * This is called by rows.ts for each cell after it's rendered,\n * allowing plugins to modify cells during render rather than\n * requiring expensive post-render DOM queries.\n *\n * @group Plugin Hooks\n * @internal Plugin API - called by rows.ts\n */\n _afterCellRender(context: AfterCellRenderContext<T>): void {\n // Cast needed because PluginManager uses unknown for row type\n this.#pluginManager?.afterCellRender(context as AfterCellRenderContext);\n }\n\n /**\n * Check if any plugin has registered an afterCellRender hook.\n * Used to skip the hook call entirely for performance when no plugins need it.\n *\n * @group Plugin Hooks\n * @internal Plugin API - called by rows.ts\n */\n _hasAfterCellRenderHook(): boolean {\n return this.#pluginManager?.hasAfterCellRenderHook() ?? false;\n }\n\n /**\n * Call afterRowRender hook on all plugins that have registered for it.\n * Called by rows.ts after each row is completely rendered.\n *\n * @param context - Context containing row data, index, and DOM element\n *\n * @group Plugin Hooks\n * @internal Plugin API - called by rows.ts\n */\n _afterRowRender(context: AfterRowRenderContext<T>): void {\n // Cast needed because PluginManager uses unknown for row type\n this.#pluginManager?.afterRowRender(context as AfterRowRenderContext);\n }\n\n /**\n * Check if any plugin has registered an afterRowRender hook.\n * Used to skip the hook call entirely for performance when no plugins need it.\n *\n * @group Plugin Hooks\n * @internal Plugin API - called by rows.ts\n */\n _hasAfterRowRenderHook(): boolean {\n return this.#pluginManager?.hasAfterRowRenderHook() ?? false;\n }\n\n /**\n * Wait for the grid to be ready.\n * Resolves once the component has finished initial setup, including\n * column inference, plugin initialization, and first render.\n *\n * @group Lifecycle\n * @returns Promise that resolves when the grid is ready\n *\n * @example\n * ```typescript\n * const grid = queryGrid('tbw-grid');\n * await grid.ready();\n * console.log('Grid is ready, rows:', grid.rows.length);\n * ```\n */\n async ready(): Promise<void> {\n return this.#readyPromise;\n }\n\n /**\n * Force a full layout/render cycle.\n * Use this after programmatic changes that require re-measurement,\n * such as container resize or dynamic style changes.\n *\n * @group Lifecycle\n * @returns Promise that resolves when the render cycle completes\n *\n * @example\n * ```typescript\n * // After resizing the container\n * container.style.width = '800px';\n * await grid.forceLayout();\n * console.log('Grid re-rendered');\n * ```\n */\n async forceLayout(): Promise<void> {\n // Request a full render cycle through the scheduler\n this.#scheduler.requestPhase(RenderPhase.FULL, 'forceLayout');\n // Wait for the render cycle to complete\n return this.#scheduler.whenReady();\n }\n\n /**\n * Get a frozen snapshot of the current effective configuration.\n * The returned object is read-only and reflects all merged config sources.\n *\n * @group Configuration\n * @returns Promise resolving to frozen configuration object\n *\n * @example\n * ```typescript\n * const config = await grid.getConfig();\n * console.log('Columns:', config.columns?.length);\n * console.log('Fit mode:', config.fitMode);\n * ```\n */\n async getConfig(): Promise<Readonly<GridConfig<T>>> {\n return Object.freeze({ ...(this.#effectiveConfig || {}) });\n }\n // #endregion\n\n // #region Row API (delegated to RowManager)\n /**\n * Get the unique ID for a row.\n * Uses the configured `getRowId` function or falls back to `row.id` / `row._id`.\n *\n * @group Data Management\n * @param row - The row object\n * @returns The row's unique identifier\n * @throws Error if no ID can be determined\n *\n * @example\n * ```typescript\n * const id = grid.getRowId(row);\n * console.log('Row ID:', id);\n * ```\n */\n getRowId(row: T): string {\n return resolveRowIdOrThrow(row, this.id, this.#effectiveConfig.getRowId);\n }\n\n /**\n * Get a row by its ID.\n * O(1) lookup via internal Map.\n *\n * @group Data Management\n * @param id - Row identifier (from getRowId)\n * @returns The row object, or undefined if not found\n *\n * @example\n * ```typescript\n * const row = grid.getRow('cargo-123');\n * if (row) {\n * console.log('Found:', row.name);\n * }\n * ```\n */\n getRow(id: string): T | undefined {\n return this.#rowManager.getRow(id);\n }\n\n /**\n * Get a row and its current index by ID.\n * Used internally by plugins to resolve a row's current position in `_rows`\n * after sorting, filtering, or rows replacement.\n * @internal\n */\n _getRowEntry(id: string): { row: T; index: number } | undefined {\n return this.#rowIdMap.get(id);\n }\n\n /**\n * Update a row by ID.\n * Mutates the row in-place and emits `cell-change` for each changed field.\n *\n * @group Data Management\n * @param id - Row identifier (from getRowId)\n * @param changes - Partial row data to merge\n * @param source - Origin of update (default: 'api')\n * @throws Error if row is not found\n * @fires cell-change - For each field that changed\n *\n * @example\n * ```typescript\n * // WebSocket update handler\n * socket.on('cargo-update', (data) => {\n * grid.updateRow(data.id, { status: data.status, eta: data.eta });\n * });\n * ```\n */\n updateRow(id: string, changes: Partial<T>, source: UpdateSource = 'api'): void {\n this.#rowManager.updateRow(id, changes, source);\n }\n\n /**\n * Batch update multiple rows.\n * More efficient than multiple `updateRow()` calls - single render cycle.\n *\n * @group Data Management\n * @param updates - Array of { id, changes } objects\n * @param source - Origin of updates (default: 'api')\n * @throws Error if any row is not found\n * @fires cell-change - For each field that changed on each row\n *\n * @example\n * ```typescript\n * // Bulk status update\n * grid.updateRows([\n * { id: 'cargo-1', changes: { status: 'shipped' } },\n * { id: 'cargo-2', changes: { status: 'shipped' } },\n * ]);\n * ```\n */\n updateRows(updates: Array<{ id: string; changes: Partial<T> }>, source: UpdateSource = 'api'): void {\n this.#rowManager.updateRows(updates, source);\n }\n\n /**\n * Animate a row at the specified index.\n * Applies a visual animation to highlight changes, insertions, or removals.\n * Returns a `Promise` that resolves when the animation completes.\n *\n * **Animation types:**\n * - `'change'`: Flash highlight (for data changes, e.g., after cell edit)\n * - `'insert'`: Slide-in animation (for newly added rows)\n * - `'remove'`: Fade-out animation (for rows being removed)\n *\n * The animation is purely visual - it does not affect the data.\n * For remove animations, the row remains in the DOM until animation completes.\n *\n * @group Row Animation\n * @param rowIndex - Index of the row in the current row set\n * @param type - Type of animation to apply\n * @returns `true` if the row was found and animated, `false` otherwise\n *\n * @example\n * ```typescript\n * // Highlight a row and wait for the animation to finish\n * await grid.animateRow(rowIndex, 'change');\n *\n * // Fire-and-forget (animation runs in background)\n * grid.animateRow(rowIndex, 'insert');\n * ```\n */\n animateRow(rowIndex: number, type: RowAnimationType): Promise<boolean> {\n return animateRow(this, rowIndex, type);\n }\n\n /**\n * Animate multiple rows at once.\n * More efficient than calling `animateRow()` multiple times.\n * Returns a `Promise` that resolves when all animations complete.\n *\n * @group Row Animation\n * @param rowIndices - Indices of the rows to animate\n * @param type - Type of animation to apply to all rows\n * @returns Number of rows that were actually animated (visible in viewport)\n *\n * @example\n * ```typescript\n * // Highlight all changed rows after bulk update\n * const changedIndices = [0, 2, 5];\n * await grid.animateRows(changedIndices, 'change');\n * ```\n */\n animateRows(rowIndices: number[], type: RowAnimationType): Promise<number> {\n return animateRows(this, rowIndices, type);\n }\n\n /**\n * Animate a row by its ID.\n * Uses the configured `getRowId` function to find the row.\n * Returns a `Promise` that resolves when the animation completes.\n *\n * @group Row Animation\n * @param rowId - The row's unique identifier (from getRowId)\n * @param type - Type of animation to apply\n * @returns `true` if the row was found and animated, `false` otherwise\n *\n * @example\n * ```typescript\n * // Highlight a row after real-time update\n * socket.on('row-updated', async (data) => {\n * grid.updateRow(data.id, data.changes);\n * await grid.animateRowById(data.id, 'change');\n * });\n * ```\n */\n animateRowById(rowId: string, type: RowAnimationType): Promise<boolean> {\n return animateRowById(this, rowId, type);\n }\n\n /**\n * Insert a row at a specific position in the current view.\n *\n * The row is spliced into the visible (processed) row array at `index` and\n * appended to the source data so that future pipeline runs (sort, filter,\n * group) include it. No plugin processing is triggered — the row stays\n * exactly where you place it until the next full pipeline run.\n *\n * By default, an `'insert'` animation is applied. Pass `animate: false` to\n * skip the animation. The returned `Promise` resolves when the animation\n * completes (or immediately when `animate` is `false`).\n *\n * @group Data Management\n * @param index - Visible row index at which to insert (0-based)\n * @param row - The row data object to insert\n * @param animate - Whether to apply an 'insert' animation (default: `true`)\n *\n * @example\n * ```typescript\n * // Insert with animation (default)\n * grid.insertRow(3, { id: nextId(), name: '', status: 'Draft' });\n *\n * // Insert without animation\n * grid.insertRow(3, newRow, false);\n *\n * // Await animation completion\n * await grid.insertRow(3, newRow);\n * ```\n */\n async insertRow(index: number, row: T, animate = true): Promise<void> {\n return this.#rowManager.insertRow(index, row, animate);\n }\n\n /**\n * Remove a row at a specific position in the current view.\n *\n * The row is removed from both the visible (processed) row array and the\n * source data. No plugin processing is triggered — remaining rows keep their\n * current positions until the next full pipeline run.\n *\n * By default, a `'remove'` animation plays before the row is removed.\n * Pass `animate: false` to remove immediately. When animated, `await` the\n * result to ensure the row has been fully removed from data.\n *\n * @group Data Management\n * @param index - Visible row index to remove (0-based)\n * @param animate - Whether to apply a 'remove' animation first (default: `true`)\n * @returns The removed row object, or `undefined` if index was out of range\n *\n * @example\n * ```typescript\n * // Remove with fade-out animation (default)\n * await grid.removeRow(5);\n *\n * // Remove immediately, no animation\n * const removed = await grid.removeRow(5, false);\n * ```\n */\n async removeRow(index: number, animate = true): Promise<T | undefined> {\n return this.#rowManager.removeRow(index, animate);\n }\n\n /**\n * Suspend row processing for the next rows update.\n *\n * @deprecated This method is a no-op. Use {@link insertRow} or {@link removeRow}\n * instead, which correctly preserve the current sort/filter view while adding\n * or removing individual rows. Will be removed in v2.\n *\n * @group Lifecycle\n */\n suspendProcessing(): void {\n // No-op — kept for backwards compatibility.\n }\n // #endregion\n\n // #region Focus & Navigation API (delegated to FocusManager)\n /**\n * Move focus to a specific cell.\n *\n * Accepts a column index (into the visible columns array) or a field name.\n * The grid scrolls the cell into view and applies focus styling.\n *\n * @group Focus & Navigation\n * @param rowIndex - The row index to focus (0-based, in the current processed row array)\n * @param column - Column index (0-based into visible columns) or field name string\n *\n * @example\n * ```typescript\n * // Focus by column index\n * grid.focusCell(0, 2);\n *\n * // Focus by field name\n * grid.focusCell(5, 'email');\n * ```\n */\n focusCell(rowIndex: number, column: number | string): void {\n this.#focusManager.focusCell(rowIndex, column);\n }\n\n /**\n * The currently focused cell position, or `null` if no rows are loaded.\n *\n * Returns a snapshot object with the row index, visible column index, and\n * the field name of the focused column.\n *\n * @group Focus & Navigation\n *\n * @example\n * ```typescript\n * const cell = grid.focusedCell;\n * if (cell) {\n * console.log(`Focused on row ${cell.rowIndex}, column \"${cell.field}\"`);\n * }\n * ```\n */\n get focusedCell(): { rowIndex: number; colIndex: number; field: string } | null {\n return this.#focusManager.focusedCell;\n }\n\n /**\n * Scroll the viewport so a row is visible.\n *\n * Uses the grid's internal virtualization state (row height, position cache)\n * to calculate the correct scroll offset, including support for variable\n * row heights and grouped rows.\n *\n * @group Focus & Navigation\n * @param rowIndex - Row index (0-based, in the current processed row array)\n * @param options - Alignment and scroll behavior\n *\n * @example\n * ```typescript\n * // Scroll to row, only if not already visible\n * grid.scrollToRow(42);\n *\n * // Center the row in the viewport with smooth scrolling\n * grid.scrollToRow(42, { align: 'center', behavior: 'smooth' });\n * ```\n */\n scrollToRow(rowIndex: number, options?: ScrollToRowOptions): void {\n this.#focusManager.scrollToRow(rowIndex, options);\n }\n\n /**\n * Scroll the viewport so a row is visible, identified by its unique ID.\n *\n * Uses {@link GridConfig.getRowId | getRowId} (or the fallback `row.id` / `row._id`)\n * to find the row in the current (possibly sorted/filtered) data, then delegates\n * to {@link scrollToRow}.\n *\n * @group Focus & Navigation\n * @param rowId - The row's unique identifier\n * @param options - Alignment and scroll behavior\n *\n * @example\n * ```typescript\n * // After inserting a row, scroll to it by ID\n * grid.scrollToRowById('emp-42', { align: 'center' });\n * ```\n */\n scrollToRowById(rowId: string, options?: ScrollToRowOptions): void {\n this.#focusManager.scrollToRowById(rowId, options);\n }\n // #endregion\n\n // #region Column API\n /**\n * Show or hide a column by field name.\n *\n * @group Column Visibility\n * @param field - The field name of the column to modify\n * @param visible - Whether the column should be visible\n * @returns `true` if the visibility changed, `false` if unchanged\n * @fires column-state-change - Emitted when the visibility changes\n *\n * @example\n * ```typescript\n * // Hide the email column\n * grid.setColumnVisible('email', false);\n *\n * // Show it again\n * grid.setColumnVisible('email', true);\n * ```\n */\n setColumnVisible(field: string, visible: boolean): boolean {\n const result = this.#configManager.setColumnVisible(field, visible);\n if (result) {\n this.requestStateChange();\n }\n return result;\n }\n\n /**\n * Toggle a column's visibility.\n *\n * @group Column Visibility\n * @param field - The field name of the column to toggle\n * @returns The new visibility state (`true` = visible, `false` = hidden)\n * @fires column-state-change - Emitted when the visibility changes\n *\n * @example\n * ```typescript\n * // Toggle the notes column visibility\n * const isNowVisible = grid.toggleColumnVisibility('notes');\n * console.log(`Notes column is now ${isNowVisible ? 'visible' : 'hidden'}`);\n * ```\n */\n toggleColumnVisibility(field: string): boolean {\n const result = this.#configManager.toggleColumnVisibility(field);\n if (result) {\n this.requestStateChange();\n }\n return result;\n }\n\n /**\n * Check if a column is currently visible.\n *\n * @group Column Visibility\n * @param field - The field name to check\n * @returns `true` if the column is visible, `false` if hidden\n *\n * @example\n * ```typescript\n * if (grid.isColumnVisible('email')) {\n * console.log('Email column is showing');\n * }\n * ```\n */\n isColumnVisible(field: string): boolean {\n return this.#configManager.isColumnVisible(field);\n }\n\n /**\n * Show all columns, resetting any hidden columns to visible.\n *\n * @group Column Visibility\n * @fires column-state-change - Emitted when column visibility changes\n * @example\n * ```typescript\n * // Reset button handler\n * resetButton.onclick = () => grid.showAllColumns();\n * ```\n */\n showAllColumns(): void {\n this.#configManager.showAllColumns();\n this.requestStateChange();\n }\n\n /**\n * Get metadata for all columns including visibility state.\n * Useful for building a column picker UI.\n *\n * @group Column Visibility\n * @returns Array of column info objects\n *\n * @example\n * ```typescript\n * // Build a column visibility menu\n * const columns = grid.getAllColumns();\n * columns.forEach(col => {\n * if (!col.utility) { // Skip utility columns like selection checkbox\n * const menuItem = document.createElement('label');\n * menuItem.innerHTML = `\n * <input type=\"checkbox\" ${col.visible ? 'checked' : ''}>\n * ${col.header}\n * `;\n * menuItem.querySelector('input').onchange = () =>\n * grid.toggleColumnVisibility(col.field);\n * menu.appendChild(menuItem);\n * }\n * });\n * ```\n */\n getAllColumns(): Array<{\n field: string;\n header: string;\n visible: boolean;\n lockVisible?: boolean;\n utility?: boolean;\n }> {\n return this.#configManager.getAllColumns();\n }\n\n /**\n * Set the display order of columns.\n *\n * @group Column Order\n * @param order - Array of field names in desired order\n * @fires column-state-change - Emitted when column order changes\n *\n * @example\n * ```typescript\n * // Move 'status' column to first position\n * const currentOrder = grid.getColumnOrder();\n * const newOrder = ['status', ...currentOrder.filter(f => f !== 'status')];\n * grid.setColumnOrder(newOrder);\n * ```\n */\n setColumnOrder(order: string[]): void {\n this.#configManager.setColumnOrder(order);\n this.requestStateChange();\n }\n\n /**\n * Get the current column display order.\n *\n * @group Column Order\n * @returns Array of field names in display order\n *\n * @example\n * ```typescript\n * const order = grid.getColumnOrder();\n * console.log('Columns:', order.join(', '));\n * ```\n */\n getColumnOrder(): string[] {\n return this.#configManager.getColumnOrder();\n }\n\n /**\n * Get the current column state for persistence.\n * Returns a serializable object including column order, widths, visibility,\n * sort state, and any plugin-specific state.\n *\n * Use this to save user preferences to localStorage or a database.\n *\n * @group State Persistence\n * @returns Serializable column state object\n *\n * @example\n * ```typescript\n * // Save state to localStorage\n * const state = grid.getColumnState();\n * localStorage.setItem('gridState', JSON.stringify(state));\n *\n * // Later, restore the state\n * const saved = localStorage.getItem('gridState');\n * if (saved) {\n * grid.columnState = JSON.parse(saved);\n * }\n * ```\n */\n getColumnState(): GridColumnState {\n const plugins = this.#pluginManager?.getAll() ?? [];\n return this.#configManager.collectState(plugins as BaseGridPlugin[]);\n }\n\n /**\n * Set the column state, restoring all saved preferences.\n * Can be set before or after grid initialization.\n *\n * @group State Persistence\n * @fires column-state-change - Emitted after state is applied (if grid is initialized)\n * @example\n * ```typescript\n * // Restore saved state on page load\n * const grid = queryGrid('tbw-grid');\n * const saved = localStorage.getItem('myGridState');\n * if (saved) {\n * grid.columnState = JSON.parse(saved);\n * }\n * ```\n */\n set columnState(state: GridColumnState | undefined) {\n if (!state) return;\n\n // Store for use after initialization if called before ready\n this.#initialColumnState = state;\n this.#configManager.initialColumnState = state;\n\n // If already initialized, apply immediately\n if (this.#initialized) {\n this.#applyColumnState(state);\n }\n }\n\n /**\n * Get the current column state.\n * Alias for `getColumnState()` for property-style access.\n * @group State Persistence\n */\n get columnState(): GridColumnState | undefined {\n return this.getColumnState();\n }\n\n /**\n * Apply column state internally.\n */\n #applyColumnState(state: GridColumnState): void {\n const plugins = (this.#pluginManager?.getAll() ?? []) as BaseGridPlugin[];\n this.#configManager.applyState(state, plugins);\n\n // Re-setup to apply changes\n this.#setup();\n }\n\n /**\n * Request a state change event to be emitted.\n * Called internally after resize, reorder, visibility, or sort changes.\n * Plugins should call this after changing their state.\n * The event is debounced to avoid excessive events during drag operations.\n * @group State Persistence\n * @internal Plugin API\n */\n requestStateChange(): void {\n const plugins = (this.#pluginManager?.getAll() ?? []) as BaseGridPlugin[];\n this.#configManager.requestStateChange(plugins);\n }\n\n /**\n * Reset column state to initial configuration.\n * Clears all user modifications including order, widths, visibility, and sort.\n *\n * @group State Persistence\n * @fires column-state-change - Emitted after state is reset\n * @example\n * ```typescript\n * // Reset button handler\n * resetBtn.onclick = () => {\n * grid.resetColumnState();\n * localStorage.removeItem('gridState');\n * };\n * ```\n */\n resetColumnState(): void {\n // Clear initial state\n this.#initialColumnState = undefined;\n this.__originalOrder = [];\n\n // Use ConfigManager to reset state\n const plugins = (this.#pluginManager?.getAll() ?? []) as BaseGridPlugin[];\n this.#configManager.resetState(plugins);\n\n // Re-initialize columns from config\n this.#configManager.merge();\n this.#setup();\n }\n // #endregion\n\n // #region Shell & Tool Panel API\n /**\n * Check if the tool panel sidebar is currently open.\n *\n * The tool panel is an accordion-based sidebar that contains sections\n * registered by plugins or via `registerToolPanel()`.\n *\n * @group Tool Panel\n * @example\n * ```typescript\n * // Conditionally show/hide a \"toggle panel\" button\n * const isPanelOpen = grid.isToolPanelOpen;\n * toggleButton.textContent = isPanelOpen ? 'Close Panel' : 'Open Panel';\n * ```\n */\n get isToolPanelOpen(): boolean {\n return this.#shellController.isPanelOpen;\n }\n\n /**\n * The default row height in pixels.\n *\n * For fixed heights, this is the configured or CSS-measured row height.\n * For variable heights, this is the average/estimated row height.\n * Plugins should use this instead of hardcoding row heights.\n *\n * @group Virtualization\n */\n get defaultRowHeight(): number {\n return this._virtualization.rowHeight;\n }\n\n /**\n * Get the IDs of currently expanded accordion sections in the tool panel.\n *\n * Multiple sections can be expanded simultaneously in the accordion view.\n *\n * @group Tool Panel\n * @example\n * ```typescript\n * // Check which sections are expanded\n * const expanded = grid.expandedToolPanelSections;\n * console.log('Expanded sections:', expanded);\n * // e.g., ['columnVisibility', 'filtering']\n * ```\n */\n get expandedToolPanelSections(): string[] {\n return this.#shellController.expandedSections;\n }\n\n /**\n * Open the tool panel sidebar.\n *\n * The tool panel displays an accordion view with all registered panel sections.\n * Each section can be expanded/collapsed independently.\n *\n * @group Tool Panel\n * @example\n * ```typescript\n * // Open the tool panel when a toolbar button is clicked\n * settingsButton.addEventListener('click', () => {\n * grid.openToolPanel();\n * });\n * ```\n */\n openToolPanel(): void {\n this.#shellController.openToolPanel();\n }\n\n /**\n * Close the tool panel sidebar.\n *\n * @group Tool Panel\n * @example\n * ```typescript\n * // Close the panel after user makes a selection\n * grid.closeToolPanel();\n * ```\n */\n closeToolPanel(): void {\n this.#shellController.closeToolPanel();\n }\n\n /**\n * Toggle the tool panel sidebar open or closed.\n *\n * @group Tool Panel\n * @example\n * ```typescript\n * // Wire up a toggle button\n * toggleButton.addEventListener('click', () => {\n * grid.toggleToolPanel();\n * });\n * ```\n */\n toggleToolPanel(): void {\n this.#shellController.toggleToolPanel();\n }\n\n /**\n * Toggle an accordion section expanded or collapsed within the tool panel.\n *\n * @group Tool Panel\n * @param sectionId - The ID of the section to toggle (matches `ToolPanelDefinition.id`)\n *\n * @example\n * ```typescript\n * // Expand the column visibility section programmatically\n * grid.openToolPanel();\n * grid.toggleToolPanelSection('columnVisibility');\n * ```\n */\n toggleToolPanelSection(sectionId: string): void {\n this.#shellController.toggleToolPanelSection(sectionId);\n }\n\n /**\n * Get all registered tool panel definitions.\n *\n * Returns both plugin-registered panels and panels registered via `registerToolPanel()`.\n *\n * @group Tool Panel\n * @returns Array of tool panel definitions\n *\n * @example\n * ```typescript\n * // List all available panels\n * const panels = grid.getToolPanels();\n * panels.forEach(panel => {\n * console.log(`Panel: ${panel.title} (${panel.id})`);\n * });\n * ```\n */\n getToolPanels(): ToolPanelDefinition[] {\n return this.#shellController.getToolPanels();\n }\n\n /**\n * Register a custom tool panel section.\n *\n * Use this API to add custom UI sections to the tool panel sidebar\n * without creating a full plugin. The panel will appear as an accordion\n * section in the tool panel.\n *\n * @group Tool Panel\n * @param panel - The tool panel definition\n *\n * @example\n * ```typescript\n * // Register a custom \"Export\" panel\n * grid.registerToolPanel({\n * id: 'export',\n * title: 'Export Options',\n * icon: '📥',\n * order: 50, // Lower order = higher in list\n * render: (container) => {\n * container.innerHTML = `\n * <button id=\"export-csv\">Export CSV</button>\n * <button id=\"export-json\">Export JSON</button>\n * `;\n * container.querySelector('#export-csv')?.addEventListener('click', () => {\n * exportToCSV(grid.rows);\n * });\n * }\n * });\n * ```\n */\n registerToolPanel(panel: ToolPanelDefinition): void {\n this.#shellState.apiToolPanelIds.add(panel.id);\n this.#shellController.registerToolPanel(panel);\n }\n\n /**\n * Unregister a custom tool panel section.\n *\n * @group Tool Panel\n * @param panelId - The ID of the panel to remove\n *\n * @example\n * ```typescript\n * // Remove the export panel when no longer needed\n * grid.unregisterToolPanel('export');\n * ```\n */\n unregisterToolPanel(panelId: string): void {\n this.#shellState.apiToolPanelIds.delete(panelId);\n this.#shellController.unregisterToolPanel(panelId);\n }\n\n // ════════════════════════════════════════════════════════════════════════════\n // Header Content API\n // ════════════════════════════════════════════════════════════════════════════\n // Header content appears in the grid's header bar area (above the column headers).\n\n /**\n * Get all registered header content definitions.\n *\n * @group Header Content\n * @returns Array of header content definitions\n *\n * @example\n * ```typescript\n * const contents = grid.getHeaderContents();\n * console.log('Header sections:', contents.map(c => c.id));\n * ```\n */\n getHeaderContents(): HeaderContentDefinition[] {\n return this.#shellController.getHeaderContents();\n }\n\n /**\n * Register custom header content.\n *\n * Header content appears in the grid's header bar area, which is displayed\n * above the column headers. Use this for search boxes, filters, or other\n * controls that should be prominently visible.\n *\n * @group Header Content\n * @param content - The header content definition\n *\n * @example\n * ```typescript\n * // Add a global search box to the header\n * grid.registerHeaderContent({\n * id: 'global-search',\n * order: 10,\n * render: (container) => {\n * const input = document.createElement('input');\n * input.type = 'search';\n * input.placeholder = 'Search all columns...';\n * input.addEventListener('input', (e) => {\n * const term = (e.target as HTMLInputElement).value;\n * filterGrid(term);\n * });\n * container.appendChild(input);\n * }\n * });\n * ```\n */\n registerHeaderContent(content: HeaderContentDefinition): void {\n this.#shellState.apiHeaderContentIds.add(content.id);\n this.#shellController.registerHeaderContent(content);\n }\n\n /**\n * Unregister custom header content.\n *\n * @group Header Content\n * @param contentId - The ID of the content to remove\n *\n * @example\n * ```typescript\n * grid.unregisterHeaderContent('global-search');\n * ```\n */\n unregisterHeaderContent(contentId: string): void {\n this.#shellState.apiHeaderContentIds.delete(contentId);\n this.#shellController.unregisterHeaderContent(contentId);\n }\n\n // ════════════════════════════════════════════════════════════════════════════\n // Toolbar Content API\n // ════════════════════════════════════════════════════════════════════════════\n // Toolbar content appears in the grid's toolbar area (typically below header,\n // above column headers). Use for action buttons, dropdowns, etc.\n\n /**\n * Get all registered toolbar content definitions.\n *\n * @group Toolbar\n * @returns Array of toolbar content definitions\n *\n * @example\n * ```typescript\n * const contents = grid.getToolbarContents();\n * console.log('Toolbar items:', contents.map(c => c.id));\n * ```\n */\n getToolbarContents(): ToolbarContentDefinition[] {\n return this.#shellController.getToolbarContents();\n }\n\n /**\n * Register custom toolbar content.\n *\n * Toolbar content appears in the grid's toolbar area. Use this for action\n * buttons, dropdowns, or other controls that should be easily accessible.\n * Content is rendered in order of the `order` property (lower = first).\n *\n * @group Toolbar\n * @param content - The toolbar content definition\n *\n * @example\n * ```typescript\n * // Add export buttons to the toolbar\n * grid.registerToolbarContent({\n * id: 'export-buttons',\n * order: 100, // Position in toolbar (lower = first)\n * render: (container) => {\n * const csvBtn = document.createElement('button');\n * csvBtn.textContent = 'Export CSV';\n * csvBtn.className = 'tbw-toolbar-btn';\n * csvBtn.addEventListener('click', () => exportToCSV(grid.rows));\n *\n * const jsonBtn = document.createElement('button');\n * jsonBtn.textContent = 'Export JSON';\n * jsonBtn.className = 'tbw-toolbar-btn';\n * jsonBtn.addEventListener('click', () => exportToJSON(grid.rows));\n *\n * container.append(csvBtn, jsonBtn);\n * }\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Add a dropdown filter to the toolbar\n * grid.registerToolbarContent({\n * id: 'status-filter',\n * order: 50,\n * render: (container) => {\n * const select = document.createElement('select');\n * select.innerHTML = `\n * <option value=\"\">All Statuses</option>\n * <option value=\"active\">Active</option>\n * <option value=\"inactive\">Inactive</option>\n * `;\n * select.addEventListener('change', (e) => {\n * const status = (e.target as HTMLSelectElement).value;\n * applyStatusFilter(status);\n * });\n * container.appendChild(select);\n * }\n * });\n * ```\n */\n registerToolbarContent(content: ToolbarContentDefinition): void {\n this.#shellController.registerToolbarContent(content);\n }\n\n /**\n * Unregister custom toolbar content.\n *\n * @group Toolbar\n * @param contentId - The ID of the content to remove\n *\n * @example\n * ```typescript\n * // Remove export buttons when switching to read-only mode\n * grid.unregisterToolbarContent('export-buttons');\n * ```\n */\n unregisterToolbarContent(contentId: string): void {\n this.#shellController.unregisterToolbarContent(contentId);\n }\n\n /** Pending shell refresh - used to batch multiple rapid calls */\n #pendingShellRefresh = false;\n\n /**\n * Re-parse light DOM shell elements and refresh shell header.\n * Call this after dynamically modifying <tbw-grid-header> children.\n *\n * Multiple calls are batched via microtask to avoid redundant DOM rebuilds\n * when registering multiple tool panels in sequence.\n *\n * @internal Plugin API\n */\n refreshShellHeader(): void {\n // Batch multiple rapid calls into a single microtask\n if (this.#pendingShellRefresh) return;\n this.#pendingShellRefresh = true;\n\n queueMicrotask(() => {\n this.#pendingShellRefresh = false;\n if (!this.isConnected) return;\n\n // Re-parse light DOM (header, tool buttons, and tool panels)\n this.#parseLightDom();\n\n // Mark sources as changed since shell parsing may have updated state maps\n this.#configManager.markSourcesChanged();\n\n // Re-merge config to sync shell state changes into effectiveConfig.shell\n this.#configManager.merge();\n\n // Prepare shell state for re-render (moves toolbar buttons back to original container)\n prepareForRerender(this.#shellState);\n\n // Re-render the entire grid (shell structure may change)\n this.#render();\n this.#injectAllPluginStyles(); // Re-inject after render clears shadow DOM\n\n // Use lighter-weight post-render setup instead of full #afterConnect()\n // This avoids requesting another FULL render since #render() already rebuilt the DOM\n this.#afterShellRefresh();\n });\n }\n\n /**\n * Lighter-weight post-render setup after shell refresh.\n * Unlike #afterConnect(), this skips scheduler FULL request since DOM is already built.\n */\n #afterShellRefresh(): void {\n // Shell changes the DOM structure - need to re-cache element references\n const gridContent = this.#renderRoot.querySelector('.tbw-grid-content');\n const gridRoot = gridContent ?? this.#renderRoot.querySelector('.tbw-grid-root');\n\n this._headerRowEl = gridRoot?.querySelector('.header-row') as HTMLElement;\n this._virtualization.totalHeightEl = gridRoot?.querySelector('.faux-vscroll-spacer') as HTMLElement;\n this._virtualization.viewportEl = gridRoot?.querySelector('.rows-viewport') as HTMLElement;\n this._bodyEl = gridRoot?.querySelector('.rows') as HTMLElement;\n this.__rowsBodyEl = gridRoot?.querySelector('.rows-body') as HTMLElement;\n\n // Render shell header content and custom toolbar contents\n if (this.#shellController.isInitialized) {\n renderHeaderContent(this.#renderRoot, this.#shellState);\n renderCustomToolbarContents(this.#renderRoot, this.#effectiveConfig?.shell, this.#shellState);\n\n const defaultOpen = this.#effectiveConfig?.shell?.toolPanel?.defaultOpen;\n if (defaultOpen && this.#shellState.toolPanels.has(defaultOpen)) {\n this.openToolPanel();\n this.#shellState.expandedSections.add(defaultOpen);\n }\n\n // Restore panel content if panel was already open (e.g., after plugin re-init triggers refreshShellHeader)\n if (this.#shellState.isPanelOpen) {\n updatePanelState(this.#renderRoot, this.#shellState);\n renderPanelContent(this.#renderRoot, this.#shellState, {\n expand: this.#effectiveConfig?.icons?.expand,\n collapse: this.#effectiveConfig?.icons?.collapse,\n });\n updateToolbarActiveStates(this.#renderRoot, this.#shellState);\n }\n }\n\n // Re-create resize controller (DOM elements changed)\n this._resizeController = createResizeController(this);\n\n // Re-setup scroll listeners (DOM elements changed)\n this.#setupScrollListeners(gridRoot);\n\n // Request COLUMNS phase to reprocess columns (including column groups) and render header\n // #render() rebuilds the DOM with an empty header-row, so we need COLUMNS phase (5)\n // which includes processColumns (for column groups), processRows, and renderHeader\n this.#scheduler.requestPhase(RenderPhase.COLUMNS, 'shellRefresh');\n }\n // #endregion\n\n // #region Custom Styles API\n /** Map of registered custom stylesheets by ID - uses adoptedStyleSheets which survive DOM rebuilds */\n #customStyleSheets = new Map<string, CSSStyleSheet>();\n\n /**\n * Register custom CSS styles to be injected into the grid's shadow DOM.\n * Use this to style custom cell renderers, editors, or detail panels.\n *\n * Uses adoptedStyleSheets for efficiency - styles survive shadow DOM rebuilds.\n *\n * @group Custom Styles\n * @param id - Unique identifier for the style block (for removal/updates)\n * @param css - CSS string to inject\n *\n * @example\n * ```typescript\n * // Register custom styles for a detail panel\n * grid.registerStyles('my-detail-styles', `\n * .my-detail-panel { padding: 16px; }\n * .my-detail-table { width: 100%; }\n * `);\n *\n * // Update styles later\n * grid.registerStyles('my-detail-styles', updatedCss);\n *\n * // Remove styles\n * grid.unregisterStyles('my-detail-styles');\n * ```\n */\n registerStyles(id: string, css: string): void {\n // Create or update the stylesheet\n let sheet = this.#customStyleSheets.get(id);\n if (!sheet) {\n sheet = new CSSStyleSheet();\n this.#customStyleSheets.set(id, sheet);\n }\n sheet.replaceSync(css);\n\n // Update adoptedStyleSheets to include all custom sheets\n this.#updateAdoptedStyleSheets();\n }\n\n /**\n * Remove previously registered custom styles.\n *\n * @group Custom Styles\n * @param id - The ID used when registering the styles\n */\n unregisterStyles(id: string): void {\n if (this.#customStyleSheets.delete(id)) {\n this.#updateAdoptedStyleSheets();\n }\n }\n\n /**\n * Get list of registered custom style IDs.\n *\n * @group Custom Styles\n */\n getRegisteredStyles(): string[] {\n return Array.from(this.#customStyleSheets.keys());\n }\n\n /**\n * Update document.adoptedStyleSheets to include custom sheets.\n * Without Shadow DOM, all custom styles go into the document.\n */\n #updateAdoptedStyleSheets(): void {\n const customSheets = Array.from(this.#customStyleSheets.values());\n\n // Start with document's existing sheets (excluding any we've added before)\n // We track custom sheets by their presence in our map\n const existingSheets = document.adoptedStyleSheets.filter(\n (sheet) => !Array.from(this.#customStyleSheets.values()).includes(sheet),\n );\n\n document.adoptedStyleSheets = [...existingSheets, ...customSheets];\n }\n // #endregion\n\n // #region External Focus Containers (delegated to FocusManager)\n /**\n * Register an external DOM element as a logical focus container of this grid.\n *\n * Focus moving into a registered container is treated as if it stayed inside\n * the grid: `data-has-focus` is preserved, click-outside commit is suppressed,\n * and the editing focus trap (when enabled) won't reclaim focus.\n *\n * Typical use case: overlay panels (datepickers, dropdowns, autocompletes)\n * that render at `<body>` level to escape grid overflow clipping.\n *\n * @group Focus Management\n * @param el - The external element to register\n *\n * @example\n * ```typescript\n * const overlay = document.createElement('div');\n * document.body.appendChild(overlay);\n *\n * // Tell the grid this overlay is \"part of\" the grid\n * grid.registerExternalFocusContainer(overlay);\n *\n * // Later, when overlay is removed\n * grid.unregisterExternalFocusContainer(overlay);\n * ```\n */\n registerExternalFocusContainer(el: Element): void {\n this.#focusManager.registerExternalFocusContainer(el);\n }\n\n /**\n * Unregister a previously registered external focus container.\n *\n * @group Focus Management\n * @param el - The element to unregister\n */\n unregisterExternalFocusContainer(el: Element): void {\n this.#focusManager.unregisterExternalFocusContainer(el);\n }\n\n /**\n * Check whether focus is logically inside this grid.\n *\n * Returns `true` when `document.activeElement` (or the given node) is\n * inside the grid's own DOM **or** inside any element registered via\n * {@link registerExternalFocusContainer}.\n *\n * @group Focus Management\n * @param node - Optional node to test. Defaults to `document.activeElement`.\n *\n * @example\n * ```typescript\n * if (grid.containsFocus()) {\n * console.log('Grid or one of its overlays has focus');\n * }\n * ```\n */\n containsFocus(node?: Node | null): boolean {\n return this.#focusManager.containsFocus(node);\n }\n // #endregion\n\n // #region Light DOM Helpers\n /**\n * Parse all light DOM shell elements in one call.\n * Consolidates parsing of header, tool buttons, and tool panels.\n */\n #parseLightDom(): void {\n parseLightDomShell(this, this.#shellState);\n parseLightDomToolButtons(this, this.#shellState);\n parseLightDomToolPanels(this, this.#shellState, this.#getToolPanelRendererFactory());\n }\n\n /**\n * Replace the shell header element in the DOM with freshly rendered HTML.\n * Used when title or tool buttons are added dynamically via light DOM.\n */\n #replaceShellHeaderElement(): void {\n const shellHeader = this.#renderRoot.querySelector('.tbw-shell-header');\n if (!shellHeader) return;\n\n // Prepare for re-render (moves toolbar buttons back to original container)\n prepareForRerender(this.#shellState);\n\n const newHeaderHtml = renderShellHeader(\n this.#effectiveConfig.shell,\n this.#shellState,\n this.#effectiveConfig.icons?.toolPanel,\n );\n const temp = document.createElement('div');\n temp.innerHTML = newHeaderHtml;\n const newHeader = temp.firstElementChild;\n if (newHeader) {\n shellHeader.replaceWith(newHeader);\n this.#setupShellListeners();\n // Render custom toolbar contents into the newly created slots\n renderCustomToolbarContents(this.#renderRoot, this.#effectiveConfig?.shell, this.#shellState);\n }\n }\n\n /**\n * Set up Light DOM handlers via ConfigManager's observer infrastructure.\n * This handles frameworks like Angular that project content asynchronously.\n *\n * The observer is owned by ConfigManager (generic infrastructure).\n * The handlers (parsing logic) are owned here (eventually ShellPlugin).\n *\n * This separation allows plugins to register their own Light DOM elements\n * and handle parsing themselves.\n */\n #setupLightDomHandlers(): void {\n // Handler for shell header element changes\n const handleShellChange = () => {\n const hadTitle = this.#shellState.lightDomTitle;\n const hadToolButtons = this.#shellState.hasToolButtonsContainer;\n this.#parseLightDom();\n const hasTitle = this.#shellState.lightDomTitle;\n const hasToolButtons = this.#shellState.hasToolButtonsContainer;\n\n if ((hasTitle && !hadTitle) || (hasToolButtons && !hadToolButtons)) {\n this.#configManager.markSourcesChanged();\n this.#configManager.merge();\n this.#replaceShellHeaderElement();\n }\n };\n\n // Handler for column element changes\n const handleColumnChange = () => {\n this.__lightDomColumnsCache = undefined;\n this.#setup();\n };\n\n // Register handlers with ConfigManager\n // Shell-related elements (eventually these move to ShellPlugin)\n this.#configManager.registerLightDomHandler('tbw-grid-header', handleShellChange);\n this.#configManager.registerLightDomHandler('tbw-grid-tool-buttons', handleShellChange);\n this.#configManager.registerLightDomHandler('tbw-grid-tool-panel', handleShellChange);\n\n // Column elements (core grid functionality)\n this.#configManager.registerLightDomHandler('tbw-grid-column', handleColumnChange);\n this.#configManager.registerLightDomHandler('tbw-grid-detail', handleColumnChange);\n\n // Start observing\n this.#configManager.observeLightDOM(this);\n }\n\n /**\n * Re-parse light DOM column elements and refresh the grid.\n * Call this after framework adapters have registered their templates.\n * Uses the render scheduler to batch with other pending updates.\n * @category Framework Adapters\n */\n refreshColumns(): void {\n // Clear the column cache to force re-parsing\n this.__lightDomColumnsCache = undefined;\n\n // Invalidate cell cache to reset __hasSpecialColumns flag\n // This is critical for frameworks like React where renderers are registered asynchronously\n // after the initial render (which may have cached __hasSpecialColumns = false)\n invalidateCellCache(this);\n\n // Re-parse light DOM columns SYNCHRONOUSLY to pick up newly registered framework renderers\n // This must happen before the scheduler runs processColumns\n this.#configManager.parseLightDomColumns(this);\n\n // Re-parse light DOM shell elements (may have been rendered asynchronously by frameworks)\n const hadTitle = this.#shellState.lightDomTitle;\n const hadToolButtons = this.#shellState.hasToolButtonsContainer;\n this.#parseLightDom();\n const hasTitle = this.#shellState.lightDomTitle;\n const hasToolButtons = this.#shellState.hasToolButtonsContainer;\n\n // If title or tool buttons were added via light DOM, update the shell header in place\n // The shell may already be rendered (due to plugins/panels), but without the title\n const needsShellRefresh = (hasTitle && !hadTitle) || (hasToolButtons && !hadToolButtons);\n if (needsShellRefresh) {\n // Mark sources as changed since shell parsing may have updated state maps\n this.#configManager.markSourcesChanged();\n // Merge the new title into effectiveConfig\n this.#configManager.merge();\n this.#replaceShellHeaderElement();\n }\n\n // Request a COLUMNS phase render through the scheduler\n // This batches with any other pending work (e.g., afterConnect)\n this.#scheduler.requestPhase(RenderPhase.COLUMNS, 'refreshColumns');\n }\n // #endregion\n\n // #region Virtualization (delegated to VirtualizationManager)\n\n /**\n * Update cached viewport and faux scrollbar geometry.\n * Called by ResizeObserver and on force-refresh to avoid forced layout reads during scroll.\n * @internal\n */\n #updateCachedGeometry(): void {\n this.#virtManager.updateCachedGeometry();\n }\n\n /**\n * Core virtualization routine. Delegates to VirtualizationManager.\n * @param force - Whether to force a full refresh (not just scroll update)\n * @param skipAfterRender - When true, skip calling afterRender (used by scheduler which calls it separately)\n * @returns Whether the visible row window changed (start/end differ from previous)\n * @internal Plugin API\n */\n refreshVirtualWindow(force = false, skipAfterRender = false): boolean {\n return this.#virtManager.refreshVirtualWindow(force, skipAfterRender);\n }\n\n /**\n * Invalidate a row's height in the position cache.\n * Call this when a plugin changes a row's height (e.g., expanding/collapsing a detail panel).\n * @param rowIndex - Index of the row whose height changed\n * @param newHeight - Optional new height. If not provided, queries plugins for height.\n */\n invalidateRowHeight(rowIndex: number, newHeight?: number): void {\n this.#virtManager.invalidateRowHeight(rowIndex, newHeight);\n }\n\n // #endregion\n\n // #region Render\n #render(): void {\n // Parse light DOM shell elements before rendering\n this.#parseLightDom();\n\n // Mark sources as changed since shell parsing may have updated state maps\n this.#configManager.markSourcesChanged();\n\n // Re-merge config to pick up any newly parsed light DOM shell settings\n this.#configManager.merge();\n\n const shellConfig = this.#effectiveConfig?.shell;\n\n // Render using direct DOM construction (2-3x faster than innerHTML)\n // Pass only minimal runtime state (isPanelOpen, expandedSections) - config comes from effectiveConfig.shell\n const hasShell = buildGridDOMIntoElement(\n this.#renderRoot,\n shellConfig,\n { isPanelOpen: this.#shellState.isPanelOpen, expandedSections: this.#shellState.expandedSections },\n this.#effectiveConfig?.icons,\n );\n\n if (hasShell) {\n this.#setupShellListeners();\n this.#shellController.setInitialized(true);\n }\n }\n\n /**\n * Set up shell event listeners after render.\n */\n #setupShellListeners(): void {\n setupShellEventListeners(this.#renderRoot, this.#effectiveConfig?.shell, this.#shellState, {\n onPanelToggle: () => this.toggleToolPanel(),\n onSectionToggle: (sectionId: string) => this.toggleToolPanelSection(sectionId),\n });\n\n // Set up tool panel resize\n this.#resizeCleanup?.();\n this.#resizeCleanup = setupToolPanelResize(this.#renderRoot, this.#effectiveConfig?.shell, (width: number) => {\n // Update the CSS variable to persist the new width\n this.style.setProperty('--tbw-tool-panel-width', `${width}px`);\n });\n\n // Set up click-outside dismiss for tool panel\n this.#clickOutsideCleanup?.();\n this.#clickOutsideCleanup = setupClickOutsideDismiss(this, this.#effectiveConfig?.shell, this.#shellState, () =>\n this.closeToolPanel(),\n );\n }\n // #endregion\n}\n\n// Self-registering custom element\nif (!customElements.get(DataGridElement.tagName)) {\n customElements.define(DataGridElement.tagName, DataGridElement);\n}\n\n// Make DataGridElement accessible globally for framework adapters\nglobalThis.DataGridElement = DataGridElement;\n\n// Type augmentation for querySelector/createElement and globalThis\ndeclare global {\n var DataGridElement: typeof import('./grid').DataGridElement;\n interface HTMLElementTagNameMap {\n 'tbw-grid': DataGridElement;\n }\n}\n","/**\n * Grid Base Styles - Concatenated from partials\n *\n * This module imports all CSS partials and exports them as a single string.\n * Each partial is wrapped in @layer tbw-base for proper cascade ordering.\n *\n * CSS Cascade Layers priority (lowest to highest):\n * - tbw-base: Core grid styles (this file)\n * - tbw-plugins: Plugin styles (override base)\n * - tbw-theme: Theme overrides (override plugins)\n * - Unlayered CSS: User customizations (highest priority - always wins)\n *\n * @module styles\n */\n\n// Import all CSS partials as inline strings (Vite handles ?inline)\nimport animations from './animations.css?inline';\nimport base from './base.css?inline';\nimport header from './header.css?inline';\nimport loading from './loading.css?inline';\nimport mediaQueries from './media-queries.css?inline';\nimport rows from './rows.css?inline';\nimport shell from './shell.css?inline';\nimport toolPanel from './tool-panel.css?inline';\nimport variables from './variables.css?inline';\n\n/**\n * Complete grid base styles.\n *\n * Concatenates all CSS partials in the correct order:\n * 1. Layer declaration (defines cascade order)\n * 2. Variables (CSS custom properties)\n * 3. Base (root element styles)\n * 4. Header (column headers, sort, resize)\n * 5. Rows (data rows and cells)\n * 6. Shell (toolbar, layout)\n * 7. Tool Panel (side panels, accordion)\n * 8. Loading (spinners, overlays)\n * 9. Animations (keyframes, transitions)\n * 10. Media Queries (accessibility, responsive)\n */\nexport const gridStyles = `@layer tbw-base, tbw-plugins, tbw-theme;\n\n${variables}\n${base}\n${header}\n${rows}\n${shell}\n${toolPanel}\n${loading}\n${animations}\n${mediaQueries}\n`;\n\n// Default export for backwards compatibility with `import styles from './grid.css?inline'`\nexport default gridStyles;\n","/**\n * Grid DOM Constants\n *\n * Centralized constants for CSS classes, data attributes, and selectors\n * used throughout the grid. Use these instead of magic strings.\n *\n * Note: Some constants here are used by plugins but defined in core because:\n * 1. The core CSS needs to style these elements (e.g., sticky positioning)\n * 2. Multiple plugins may share the same class names\n *\n * Plugins can define their own additional constants and export them.\n * See each plugin's index.ts for plugin-specific exports.\n */\n\n// #region CSS Classes\n\n/**\n * CSS class names used in the grid's shadow DOM.\n * Use these when adding/removing classes or querying elements.\n *\n * Classes are organized by:\n * - Core: Used by the grid core for structure and basic functionality\n * - Shared: Used by multiple features/plugins, styled by core CSS\n *\n * @category Plugin Development\n */\nexport const GridClasses = {\n // ─── Core Structure ───────────────────────────────────────────────\n ROOT: 'tbw-grid-root',\n HEADER: 'header',\n HEADER_ROW: 'header-row',\n HEADER_CELL: 'header-cell',\n\n // Body structure\n ROWS_VIEWPORT: 'rows-viewport',\n ROWS_SPACER: 'rows-spacer',\n ROWS_CONTAINER: 'rows',\n\n // Row elements\n DATA_ROW: 'data-row',\n GROUP_ROW: 'group-row',\n\n // Cell elements\n DATA_CELL: 'data-cell',\n\n // ─── Core States ──────────────────────────────────────────────────\n SELECTED: 'selected',\n FOCUSED: 'focused',\n EDITING: 'editing',\n EXPANDED: 'expanded',\n COLLAPSED: 'collapsed',\n DRAGGING: 'dragging',\n RESIZING: 'resizing',\n\n // Sorting (core feature)\n SORTABLE: 'sortable',\n SORTED_ASC: 'sorted-asc',\n SORTED_DESC: 'sorted-desc',\n\n // Visibility\n HIDDEN: 'hidden',\n\n // ─── Shared Classes (used by plugins, styled by core CSS) ────────\n // These are defined here because core CSS provides the base styling.\n // Plugins apply these classes; core CSS defines how they look.\n\n // Sticky positioning (PinnedColumnsPlugin applies, core CSS styles)\n STICKY_LEFT: 'sticky-left',\n STICKY_RIGHT: 'sticky-right',\n\n // Pinned rows (PinnedRowsPlugin applies, core CSS styles)\n PINNED_TOP: 'pinned-top',\n PINNED_BOTTOM: 'pinned-bottom',\n\n // Tree structure (TreePlugin applies, core CSS styles)\n TREE_TOGGLE: 'tree-toggle',\n TREE_INDENT: 'tree-indent',\n\n // Grouping (GroupingRowsPlugin applies, core CSS styles)\n GROUP_TOGGLE: 'group-toggle',\n GROUP_LABEL: 'group-label',\n GROUP_COUNT: 'group-count',\n\n // Selection (SelectionPlugin applies, core CSS styles)\n RANGE_SELECTION: 'range-selection',\n SELECTION_OVERLAY: 'selection-overlay',\n} as const;\n\n// #endregion\n\n// #region Data Attributes\n\n/**\n * Data attribute names used on grid elements.\n * Use these when getting/setting data attributes.\n *\n * @category Plugin Development\n */\nexport const GridDataAttrs = {\n // ─── Core Attributes ──────────────────────────────────────────────\n ROW_INDEX: 'data-row-index',\n COL_INDEX: 'data-col-index',\n FIELD: 'data-field',\n\n // ─── Shared Attributes (used by plugins) ──────────────────────────\n GROUP_KEY: 'data-group-key', // GroupingRowsPlugin\n TREE_LEVEL: 'data-tree-level', // TreePlugin\n STICKY: 'data-sticky', // PinnedColumnsPlugin\n} as const;\n\n// #endregion\n\n// #region Selectors\n\n/**\n * Common CSS selectors for querying grid elements.\n * Built from the class constants for consistency.\n *\n * @category Plugin Development\n */\nexport const GridSelectors = {\n ROOT: `.${GridClasses.ROOT}`,\n HEADER: `.${GridClasses.HEADER}`,\n HEADER_ROW: `.${GridClasses.HEADER_ROW}`,\n HEADER_CELL: `.${GridClasses.HEADER_CELL}`,\n ROWS_VIEWPORT: `.${GridClasses.ROWS_VIEWPORT}`,\n ROWS_CONTAINER: `.${GridClasses.ROWS_CONTAINER}`,\n DATA_ROW: `.${GridClasses.DATA_ROW}`,\n DATA_CELL: `.${GridClasses.DATA_CELL}`,\n GROUP_ROW: `.${GridClasses.GROUP_ROW}`,\n\n // By data attribute\n ROW_BY_INDEX: (index: number) => `.${GridClasses.DATA_ROW}[${GridDataAttrs.ROW_INDEX}=\"${index}\"]`,\n CELL_BY_FIELD: (field: string) => `.${GridClasses.DATA_CELL}[${GridDataAttrs.FIELD}=\"${field}\"]`,\n CELL_AT: (row: number, col: number) =>\n `.${GridClasses.DATA_ROW}[${GridDataAttrs.ROW_INDEX}=\"${row}\"] .${GridClasses.DATA_CELL}[${GridDataAttrs.COL_INDEX}=\"${col}\"]`,\n\n // State selectors\n SELECTED_ROWS: `.${GridClasses.DATA_ROW}.${GridClasses.SELECTED}`,\n EDITING_CELL: `.${GridClasses.DATA_CELL}.${GridClasses.EDITING}`,\n} as const;\n\n// #endregion\n\n// #region CSS Custom Properties\n\n/**\n * CSS custom property names for theming.\n * Use these when programmatically setting styles.\n *\n * @category Plugin Development\n */\nexport const GridCSSVars = {\n // Colors\n COLOR_BG: '--tbw-color-bg',\n COLOR_FG: '--tbw-color-fg',\n COLOR_FG_MUTED: '--tbw-color-fg-muted',\n COLOR_BORDER: '--tbw-color-border',\n COLOR_ACCENT: '--tbw-color-accent',\n COLOR_HEADER_BG: '--tbw-color-header-bg',\n COLOR_HEADER_FG: '--tbw-color-header-fg',\n COLOR_SELECTION: '--tbw-color-selection',\n COLOR_ROW_HOVER: '--tbw-color-row-hover',\n COLOR_ROW_ALT: '--tbw-color-row-alt',\n\n // Sizing\n ROW_HEIGHT: '--tbw-row-height',\n HEADER_HEIGHT: '--tbw-header-height',\n CELL_PADDING: '--tbw-cell-padding',\n\n // Typography\n FONT_FAMILY: '--tbw-font-family',\n FONT_SIZE: '--tbw-font-size',\n\n // Borders\n BORDER_RADIUS: '--tbw-border-radius',\n FOCUS_OUTLINE: '--tbw-focus-outline',\n} as const;\n\n// #endregion\n\n// Type helpers\nexport type GridClassName = (typeof GridClasses)[keyof typeof GridClasses];\nexport type GridDataAttr = (typeof GridDataAttrs)[keyof typeof GridDataAttrs];\nexport type GridCSSVar = (typeof GridCSSVars)[keyof typeof GridCSSVars];\n","/**\n * @toolbox-web/grid - A high-performance, framework-agnostic data grid web component.\n *\n * This is the public API surface. Only symbols exported here are considered stable.\n *\n * @packageDocumentation\n * @module Core\n */\n\n// #region Public API surface - only export what consumers need\nexport { DataGridElement, DataGridElement as GridElement } from './lib/core/grid';\n\n/**\n * Clean type alias for the grid element.\n * Use this in place of `DataGridElement<T>` for more concise code.\n *\n * @example\n * ```typescript\n * import { TbwGrid, createGrid } from '@toolbox-web/grid';\n *\n * const grid: TbwGrid<Employee> = createGrid();\n * grid.rows = employees;\n * ```\n */\nexport type { DataGridElement as TbwGrid } from './lib/core/grid';\n\n// Import needed for factory functions (value import: tagName is accessed at runtime)\nimport { DataGridElement } from './lib/core/grid';\nimport type { GridConfig } from './lib/core/types';\n\n// #region Factory Functions\n/**\n * Create a new typed grid element programmatically.\n *\n * This avoids the need to cast when creating grids in TypeScript:\n * ```typescript\n * // Before: manual cast required\n * const grid = document.createElement('tbw-grid') as DataGridElement<Employee>;\n *\n * // After: fully typed\n * const grid = createGrid<Employee>({\n * columns: [{ field: 'name' }],\n * plugins: [new SelectionPlugin()],\n * });\n * grid.rows = employees; // ✓ Typed!\n * ```\n *\n * @param config - Optional initial grid configuration\n * @returns A typed DataGridElement instance\n */\nexport function createGrid<TRow = unknown>(config?: Partial<GridConfig<TRow>>): DataGridElement<TRow> {\n const grid = document.createElement('tbw-grid') as DataGridElement<TRow>;\n if (config) {\n grid.gridConfig = config as GridConfig<TRow>;\n }\n return grid;\n}\n\n/**\n * Query an existing grid element from the DOM with proper typing.\n *\n * **Sync mode** (default) — returns the element immediately. The element may not\n * be upgraded yet if the grid module hasn't loaded.\n *\n * **Async mode** — pass `true` as the second or third argument to wait for the\n * custom element to be defined and upgraded before resolving. This guarantees\n * all `DataGridElement` methods (e.g. `registerStyles`, `ready`, `on`) are\n * available on the returned instance.\n *\n * @example\n * ```typescript\n * // Sync — unchanged from before\n * const grid = queryGrid<Employee>('#my-grid');\n * if (grid) {\n * grid.rows = employees; // ✓ Typed (assumes element is upgraded)\n * }\n *\n * // Async — waits for custom-element upgrade\n * const grid = await queryGrid<Employee>('#my-grid', true);\n * if (grid) {\n * grid.registerStyles('my-id', '.cell { color: red; }'); // ✓ Safe\n * }\n *\n * // Async with parent scope\n * const grid = await queryGrid<Employee>('tbw-grid', container, true);\n * ```\n *\n * @param selector - CSS selector to find the grid element\n * @param parentOrAwait - Parent node to search within, or `true` to wait for upgrade\n * @param awaitUpgrade - When `true`, waits for the custom element to be defined before resolving\n * @returns The typed grid element (or null), either synchronously or as a Promise\n */\nexport function queryGrid<TRow = unknown>(selector: string): DataGridElement<TRow> | null;\nexport function queryGrid<TRow = unknown>(selector: string, parent: ParentNode): DataGridElement<TRow> | null;\nexport function queryGrid<TRow = unknown>(selector: string, awaitUpgrade: true): Promise<DataGridElement<TRow> | null>;\nexport function queryGrid<TRow = unknown>(\n selector: string,\n parent: ParentNode,\n awaitUpgrade: true,\n): Promise<DataGridElement<TRow> | null>;\nexport function queryGrid<TRow = unknown>(\n selector: string,\n parentOrAwait?: ParentNode | boolean,\n awaitUpgrade?: boolean,\n): DataGridElement<TRow> | null | Promise<DataGridElement<TRow> | null> {\n let parent: ParentNode = document;\n let shouldAwait = false;\n\n if (typeof parentOrAwait === 'boolean') {\n shouldAwait = parentOrAwait;\n } else if (parentOrAwait) {\n parent = parentOrAwait;\n shouldAwait = !!awaitUpgrade;\n }\n\n if (shouldAwait) {\n return customElements.whenDefined(DataGridElement.tagName).then(() => {\n return parent.querySelector(selector) as DataGridElement<TRow> | null;\n });\n }\n\n return parent.querySelector(selector) as DataGridElement<TRow> | null;\n}\n// #endregion\n\n/**\n * Event name constants for DataGrid (public API).\n *\n * Use these constants instead of string literals for type-safe event handling.\n *\n * @example\n * ```typescript\n * import { DGEvents } from '@toolbox-web/grid';\n *\n * // Type-safe event listening\n * grid.addEventListener(DGEvents.CELL_CLICK, (e) => {\n * console.log('Cell clicked:', e.detail);\n * });\n *\n * grid.addEventListener(DGEvents.SORT_CHANGE, (e) => {\n * const { field, direction } = e.detail;\n * console.log(`Sorted by ${field}`);\n * });\n *\n * grid.addEventListener(DGEvents.CELL_COMMIT, (e) => {\n * // Save edited value\n * saveToServer(e.detail.row);\n * });\n * ```\n *\n * @see {@link PluginEvents} for plugin-specific events\n * @see {@link DataGridEventMap} for event detail types\n * @category Events\n */\nexport const DGEvents = {\n /** Emitted by core after any data mutation */\n CELL_CHANGE: 'cell-change',\n CELL_COMMIT: 'cell-commit',\n ROW_COMMIT: 'row-commit',\n EDIT_OPEN: 'edit-open',\n EDIT_CLOSE: 'edit-close',\n CHANGED_ROWS_RESET: 'changed-rows-reset',\n MOUNT_EXTERNAL_VIEW: 'mount-external-view',\n MOUNT_EXTERNAL_EDITOR: 'mount-external-editor',\n SORT_CHANGE: 'sort-change',\n COLUMN_RESIZE: 'column-resize',\n ACTIVATE_CELL: 'activate-cell',\n /** Unified cell activation event (keyboard or pointer) */\n CELL_ACTIVATE: 'cell-activate',\n GROUP_TOGGLE: 'group-toggle',\n COLUMN_STATE_CHANGE: 'column-state-change',\n /** Emitted when grid row data changes (set, insert, remove, update) */\n DATA_CHANGE: 'data-change',\n} as const;\n\n/**\n * Union type of all DataGrid event names.\n *\n * @example\n * ```typescript\n * function addListener(grid: DataGridElement, event: DGEventName): void {\n * grid.addEventListener(event, (e) => console.log(e));\n * }\n * ```\n *\n * @see {@link DGEvents} for event constants\n * @category Events\n */\nexport type DGEventName = (typeof DGEvents)[keyof typeof DGEvents];\n\n/**\n * Plugin event constants (mirrors DGEvents pattern).\n *\n * Events emitted by built-in plugins. Import the relevant plugin\n * to access these events.\n *\n * @example\n * ```typescript\n * import { PluginEvents } from '@toolbox-web/grid';\n * import { SelectionPlugin } from '@toolbox-web/grid/all';\n *\n * // Listen for selection changes\n * grid.addEventListener(PluginEvents.SELECTION_CHANGE, (e) => {\n * console.log('Selected rows:', e.detail.selectedRows);\n * });\n *\n * // Listen for filter changes\n * grid.addEventListener(PluginEvents.FILTER_CHANGE, (e) => {\n * console.log('Active filters:', e.detail);\n * });\n *\n * // Listen for tree expand/collapse\n * grid.addEventListener(PluginEvents.TREE_EXPAND, (e) => {\n * const { row, expanded } = e.detail;\n * console.log(`Row ${expanded ? 'expanded' : 'collapsed'}`);\n * });\n * ```\n *\n * @see {@link DGEvents} for core grid events\n * @category Events\n */\nexport const PluginEvents = {\n // Selection plugin\n SELECTION_CHANGE: 'selection-change',\n // Tree plugin\n TREE_EXPAND: 'tree-expand',\n // Filtering plugin\n FILTER_CHANGE: 'filter-change',\n // Sorting plugin\n SORT_MODEL_CHANGE: 'sort-model-change',\n // Export plugin\n EXPORT_START: 'export-start',\n EXPORT_COMPLETE: 'export-complete',\n // Clipboard plugin\n CLIPBOARD_COPY: 'clipboard-copy',\n CLIPBOARD_PASTE: 'clipboard-paste',\n // Context menu plugin\n CONTEXT_MENU_OPEN: 'context-menu-open',\n CONTEXT_MENU_CLOSE: 'context-menu-close',\n // Undo/Redo plugin\n HISTORY_CHANGE: 'history-change',\n // Server-side plugin\n SERVER_LOADING: 'server-loading',\n SERVER_ERROR: 'server-error',\n // Visibility plugin\n COLUMN_VISIBILITY_CHANGE: 'column-visibility-change',\n // Reorder plugin\n COLUMN_REORDER: 'column-reorder',\n // Master-detail plugin\n DETAIL_EXPAND: 'detail-expand',\n // Grouping rows plugin\n GROUP_EXPAND: 'group-expand',\n} as const;\n\n/**\n * Union type of all plugin event names.\n *\n * @example\n * ```typescript\n * function addPluginListener(grid: DataGridElement, event: PluginEventName): void {\n * grid.addEventListener(event, (e) => console.log(e));\n * }\n * ```\n *\n * @see {@link PluginEvents} for event constants\n * @category Events\n */\nexport type PluginEventName = (typeof PluginEvents)[keyof typeof PluginEvents];\n\n// Public type exports\nexport type {\n /** @deprecated Use CellActivateDetail instead */\n ActivateCellDetail,\n AggregatorRef,\n // Animation types\n AnimationConfig,\n AnimationMode,\n AnimationStyle,\n BaseColumnConfig,\n // Event detail types\n CellActivateDetail,\n CellActivateTrigger,\n CellChangeDetail,\n CellClickDetail,\n CellRenderContext,\n ColumnConfig,\n ColumnConfigMap,\n ColumnEditorContext,\n // Column features\n ColumnEditorSpec,\n ColumnResizeDetail,\n // Column state types\n ColumnSortState,\n ColumnState,\n // Type-level defaults\n ColumnType,\n ColumnViewRenderer,\n DataChangeDetail,\n DataGridCustomEvent,\n DataGridElement as DataGridElementInterface,\n DataGridEventMap,\n ExpandCollapseAnimation,\n ExternalMountEditorDetail,\n ExternalMountViewDetail,\n // Feature configuration (augmentable by feature modules)\n FeatureConfig,\n FitMode,\n // Framework adapter interface\n FrameworkAdapter,\n GridColumnState,\n // Core configuration types\n GridConfig,\n // Icons\n GridIcons,\n // Plugin interface (minimal shape for type-checking)\n GridPlugin,\n // Header renderer types\n HeaderCellContext,\n // Shell types\n HeaderContentDefinition,\n HeaderLabelContext,\n HeaderLabelRenderer,\n HeaderRenderer,\n IconValue,\n // Inference types\n InferredColumnResult,\n // Loading types\n LoadingContext,\n LoadingRenderer,\n LoadingSize,\n PrimitiveColumnType,\n // Public interface\n PublicGrid,\n // Row animation type\n RowAnimationType,\n RowClickDetail,\n // Grouping & Footer types\n RowGroupRenderConfig,\n // Data update management\n RowUpdate,\n // Focus & Navigation\n ScrollToRowOptions,\n ShellConfig,\n ShellHeaderConfig,\n SortChangeDetail,\n // Sorting types\n SortHandler,\n SortState,\n ToolbarContentDefinition,\n ToolPanelConfig,\n ToolPanelDefinition,\n TypeDefault,\n UpdateSource\n} from './lib/core/types';\n\n// Re-export FitModeEnum for runtime usage\nexport { DEFAULT_ANIMATION_CONFIG, DEFAULT_GRID_ICONS, FitModeEnum } from './lib/core/types';\n\n// Re-export sorting utilities for custom sort handlers\nexport { builtInSort, defaultComparator } from './lib/core/internal/sorting';\n// #endregion\n\n// #region Plugin Development\n// Plugin base class - for creating custom plugins\nexport { BaseGridPlugin, PLUGIN_QUERIES } from './lib/core/plugin';\nexport type {\n AfterCellRenderContext,\n AfterRowRenderContext,\n CellMouseEvent,\n EventDefinition,\n PluginDependency,\n PluginManifest,\n PluginQuery,\n QueryDefinition\n} from './lib/core/plugin';\n\n// DOM constants - for querying grid elements and styling\nexport { GridClasses, GridCSSVars, GridDataAttrs, GridSelectors } from './lib/core/constants';\nexport type { GridClassName, GridCSSVar, GridDataAttr } from './lib/core/constants';\n\n// Note: Plugin-specific types (SelectionConfig, FilterConfig, etc.) are exported\n// from their respective plugin entry points:\n// import { SelectionPlugin, type SelectionConfig } from '@toolbox-web/grid/plugins/selection';\n// import { FilteringPlugin, type FilterConfig } from '@toolbox-web/grid/plugins/filtering';\n// Or import all plugins + types from: '@toolbox-web/grid/all'\n// #endregion\n\n// #region Advanced Types for Custom Plugins & Enterprise Extensions\n/**\n * Internal types for advanced users building custom plugins or enterprise extensions.\n *\n * These types provide access to grid internals that may be needed for deep customization.\n * While not part of the \"stable\" API, they are exported for power users who need them.\n *\n * @remarks\n * Use with caution - these types expose internal implementation details.\n * The underscore-prefixed members they reference are considered less stable\n * than the public API surface.\n *\n * @example\n * ```typescript\n * import { BaseGridPlugin } from '@toolbox-web/grid';\n * import type { InternalGrid, ColumnInternal } from '@toolbox-web/grid';\n *\n * export class MyPlugin extends BaseGridPlugin<MyConfig> {\n * afterRender(): void {\n * // Access grid internals with proper typing\n * const grid = this.grid as InternalGrid;\n * const columns = grid._columns as ColumnInternal[];\n * // ...\n * }\n * }\n * ```\n */\n\n/**\n * Column configuration with internal cache properties.\n * Extends the public ColumnConfig with compiled template caches (__compiledView, __viewTemplate, etc.)\n * @category Plugin Development\n * @internal\n */\nexport type { ColumnInternal } from './lib/core/types';\n\n/**\n * Compiled template function with __blocked property for error handling.\n * @category Plugin Development\n * @internal\n */\nexport type { CompiledViewFunction } from './lib/core/types';\n\n/**\n * Full internal grid interface extending PublicGrid with internal state.\n * Provides typed access to _columns, _rows, virtualization state, etc.\n * @category Plugin Development\n * @internal\n */\nexport type { InternalGrid } from './lib/core/types';\n\n/**\n * Cell context for renderer/editor operations.\n * @category Plugin Development\n * @internal\n */\nexport type { CellContext } from './lib/core/types';\n\n/**\n * Editor execution context extending CellContext with commit/cancel functions.\n * @category Plugin Development\n * @internal\n */\nexport type { EditorExecContext } from './lib/core/types';\n\n/**\n * Template evaluation context for dynamic templates.\n * @category Plugin Development\n * @internal\n */\nexport type { EvalContext } from './lib/core/types';\n\n/**\n * Column resize controller interface.\n * @category Plugin Development\n * @internal\n */\nexport type { ResizeController } from './lib/core/types';\n\n/**\n * Row virtualization state interface.\n * @category Plugin Development\n * @internal\n */\nexport type { VirtualState } from './lib/core/types';\n\n/**\n * Row element with internal editing state cache.\n * Used for tracking editing cell count without querySelector.\n * @category Plugin Development\n * @internal\n */\nexport type { RowElementInternal } from './lib/core/types';\n\n/**\n * Union type for input-like elements that have a `value` property.\n * Covers standard form elements and custom elements with value semantics.\n * @category Plugin Development\n * @internal\n */\nexport type { InputLikeElement } from './lib/core/types';\n\n/**\n * Utility type to safely cast a grid element to InternalGrid for plugin use.\n *\n * @example\n * ```typescript\n * import type { AsInternalGrid, InternalGrid } from '@toolbox-web/grid';\n *\n * class MyPlugin extends BaseGridPlugin {\n * get internalGrid(): InternalGrid {\n * return this.grid as AsInternalGrid;\n * }\n * }\n * ```\n * @category Plugin Development\n * @internal\n */\nexport type AsInternalGrid<T = unknown> = import('./lib/core/types').InternalGrid<T>;\n\n/**\n * Render phase enum for debugging and understanding the render pipeline.\n * Higher phases include all lower phase work.\n * @category Plugin Development\n */\nexport { RenderPhase } from './lib/core/internal/render-scheduler';\n\n/**\n * Hook used by `@toolbox-web/grid/features/registry` to wire the feature resolver\n * into the grid core without adding registry code to the main bundle.\n * Not for external use — call only from built feature-registry entry point.\n * @internal\n */\nexport { setFeatureResolver } from './lib/core/internal/feature-hook';\n// #endregion\n","/**\n * Aggregators Core Registry\n *\n * Provides a central registry for aggregator functions.\n * Built-in aggregators are provided by default.\n * Plugins can register additional aggregators.\n *\n * The registry is exposed as a singleton object that can be accessed:\n * - By ES module imports: import { aggregatorRegistry } from '@toolbox-web/grid'\n * - By UMD/CDN: TbwGrid.aggregatorRegistry\n * - By plugins via context: ctx.aggregatorRegistry\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport type AggregatorFn = (rows: any[], field: string, column?: any) => any;\nexport type AggregatorRef = string | AggregatorFn;\n\n/** Built-in aggregator functions */\nconst builtInAggregators: Record<string, AggregatorFn> = {\n sum: (rows, field) => rows.reduce((acc, row) => acc + (Number(row[field]) || 0), 0),\n avg: (rows, field) => {\n const sum = rows.reduce((acc, row) => acc + (Number(row[field]) || 0), 0);\n return rows.length ? sum / rows.length : 0;\n },\n count: (rows) => rows.length,\n min: (rows, field) => (rows.length ? Math.min(...rows.map((r) => Number(r[field]) || Infinity)) : 0),\n max: (rows, field) => (rows.length ? Math.max(...rows.map((r) => Number(r[field]) || -Infinity)) : 0),\n first: (rows, field) => rows[0]?.[field],\n last: (rows, field) => rows[rows.length - 1]?.[field],\n};\n\n/** Custom aggregator registry (for plugins to add to) */\nconst customAggregators: Map<string, AggregatorFn> = new Map();\n\n/**\n * The aggregator registry singleton.\n * Plugins should access this through context or the global namespace.\n */\nexport const aggregatorRegistry = {\n /**\n * Register a custom aggregator function.\n */\n register(name: string, fn: AggregatorFn): void {\n customAggregators.set(name, fn);\n },\n\n /**\n * Unregister a custom aggregator function.\n */\n unregister(name: string): void {\n customAggregators.delete(name);\n },\n\n /**\n * Get an aggregator function by reference.\n */\n get(ref: AggregatorRef | undefined): AggregatorFn | undefined {\n if (ref === undefined) return undefined;\n if (typeof ref === 'function') return ref;\n // Check custom first, then built-in\n return customAggregators.get(ref) ?? builtInAggregators[ref];\n },\n\n /**\n * Run an aggregator on a set of rows.\n */\n run(ref: AggregatorRef | undefined, rows: any[], field: string, column?: any): any {\n const fn = this.get(ref);\n return fn ? fn(rows, field, column) : undefined;\n },\n\n /**\n * Check if an aggregator exists.\n */\n has(name: string): boolean {\n return customAggregators.has(name) || name in builtInAggregators;\n },\n\n /**\n * List all available aggregator names.\n */\n list(): string[] {\n return [...Object.keys(builtInAggregators), ...customAggregators.keys()];\n },\n};\n\n// #region Value-based Aggregators\n// Used by plugins like Pivot that work with pre-extracted numeric values\n\nexport type ValueAggregatorFn = (values: number[]) => number;\n\n/**\n * Built-in value-based aggregators.\n * These operate on arrays of numbers (unlike row-based aggregators).\n */\nconst builtInValueAggregators: Record<string, ValueAggregatorFn> = {\n sum: (vals) => vals.reduce((a, b) => a + b, 0),\n avg: (vals) => (vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : 0),\n count: (vals) => vals.length,\n min: (vals) => (vals.length ? Math.min(...vals) : 0),\n max: (vals) => (vals.length ? Math.max(...vals) : 0),\n first: (vals) => vals[0] ?? 0,\n last: (vals) => vals[vals.length - 1] ?? 0,\n};\n\n/**\n * Get a value-based aggregator function.\n * Used by Pivot plugin and other features that aggregate pre-extracted values.\n *\n * @param aggFunc - Aggregation function name ('sum', 'avg', 'count', 'min', 'max', 'first', 'last')\n * @returns Aggregator function that takes number[] and returns number\n */\nexport function getValueAggregator(aggFunc: string): ValueAggregatorFn {\n return builtInValueAggregators[aggFunc] ?? builtInValueAggregators.sum;\n}\n\n/**\n * Run a value-based aggregator on a set of values.\n *\n * @param aggFunc - Aggregation function name\n * @param values - Array of numbers to aggregate\n * @returns Aggregated result\n */\nexport function runValueAggregator(aggFunc: string, values: number[]): number {\n return getValueAggregator(aggFunc)(values);\n}\n// #endregion\n\n// Legacy function exports for backward compatibility\nexport const registerAggregator = aggregatorRegistry.register.bind(aggregatorRegistry);\nexport const unregisterAggregator = aggregatorRegistry.unregister.bind(aggregatorRegistry);\nexport const getAggregator = aggregatorRegistry.get.bind(aggregatorRegistry);\nexport const runAggregator = aggregatorRegistry.run.bind(aggregatorRegistry);\nexport const listAggregators = aggregatorRegistry.list.bind(aggregatorRegistry);\n","/**\n * Base Grid Plugin Class\n *\n * All plugins extend this abstract class.\n * Plugins are instantiated per-grid and manage their own state.\n */\n\n// Injected by Vite at build time from package.json (same as grid.ts)\ndeclare const __GRID_VERSION__: string;\n\nimport { type DiagnosticCode, formatDiagnostic, gridPrefix } from '../internal/diagnostics';\nimport type {\n ColumnConfig,\n ColumnState,\n GridPlugin,\n HeaderContentDefinition,\n IconValue,\n PluginNameMap,\n ToolPanelDefinition,\n} from '../types';\nimport { DEFAULT_GRID_ICONS } from '../types';\n\n// Re-export shared plugin types for convenience\nexport { PLUGIN_QUERIES } from './types';\nexport type {\n AfterCellRenderContext,\n AfterRowRenderContext,\n CellClickEvent,\n CellCoords,\n CellEditor,\n CellMouseEvent,\n CellRenderer,\n ContextMenuItem,\n ContextMenuParams,\n GridElementRef,\n HeaderClickEvent,\n HeaderRenderer,\n KeyboardModifiers,\n PluginCellRenderContext,\n PluginQuery,\n RowClickEvent,\n ScrollEvent,\n} from './types';\n\nimport type {\n AfterCellRenderContext,\n AfterRowRenderContext,\n CellClickEvent,\n CellEditor,\n CellMouseEvent,\n CellRenderer,\n GridElementRef,\n HeaderClickEvent,\n HeaderRenderer,\n PluginQuery,\n RowClickEvent,\n ScrollEvent,\n} from './types';\n\n/**\n * Grid element interface for plugins.\n * Extends GridElementRef with plugin-specific methods.\n * Note: effectiveConfig is already available from GridElementRef.\n */\nexport interface GridElement extends GridElementRef {\n getPlugin<T extends BaseGridPlugin>(PluginClass: new (...args: any[]) => T): T | undefined;\n getPluginByName<K extends string>(\n name: K,\n ): (K extends keyof PluginNameMap ? PluginNameMap[K] : BaseGridPlugin) | undefined;\n /**\n * Get a plugin's state by plugin name.\n * This is a loose-coupling method for plugins to access other plugins' state\n * without importing the plugin class directly.\n * @internal Plugin API\n */\n getPluginState?(name: string): unknown;\n\n // Focus Management\n /** Register an external DOM element as a logical focus container. @internal Plugin API */\n registerExternalFocusContainer?(el: Element): void;\n /** Unregister a previously registered external focus container. @internal Plugin API */\n unregisterExternalFocusContainer?(el: Element): void;\n /** Check whether focus is logically inside this grid (own DOM + external containers). @internal Plugin API */\n containsFocus?(node?: Node | null): boolean;\n}\n\n/**\n * Header render context for plugin header renderers.\n */\nexport interface PluginHeaderRenderContext {\n /** Column configuration */\n column: ColumnConfig;\n /** Column index */\n colIndex: number;\n}\n\n// ============================================================================\n// Plugin Dependency Types\n// ============================================================================\n\n/**\n * Declares a dependency on another plugin.\n *\n * @category Plugin Development\n * @example\n * ```typescript\n * export class UndoRedoPlugin extends BaseGridPlugin {\n * static readonly dependencies: PluginDependency[] = [\n * { name: 'editing', required: true },\n * ];\n * }\n * ```\n */\nexport interface PluginDependency {\n /**\n * The name of the required plugin (matches the plugin's `name` property).\n * Use string names for loose coupling - avoids static imports.\n */\n name: string;\n\n /**\n * Whether this dependency is required (hard) or optional (soft).\n * - `true`: Plugin cannot function without it. Throws error if missing.\n * - `false`: Plugin works with reduced functionality. Logs info if missing.\n * @default true\n */\n required?: boolean;\n\n /**\n * Human-readable reason for this dependency.\n * Shown in error/info messages to help users understand why.\n * @example \"UndoRedoPlugin needs EditingPlugin to track cell edits\"\n */\n reason?: string;\n}\n\n/**\n * Declares an incompatibility between plugins.\n * When both plugins are loaded, a warning is logged to help users understand the conflict.\n *\n * @category Plugin Development\n */\nexport interface PluginIncompatibility {\n /**\n * The name of the incompatible plugin (matches the plugin's `name` property).\n */\n name: string;\n\n /**\n * Human-readable reason for the incompatibility.\n * Should explain why the plugins conflict and any workarounds.\n * @example \"Responsive card layout does not support row grouping yet\"\n */\n reason: string;\n}\n\n// ============================================================================\n// Plugin Query & Event Definitions\n// ============================================================================\n\n/**\n * Defines a query that a plugin can handle.\n * Other plugins or the grid can send this query type to retrieve data.\n *\n * @category Plugin Development\n *\n * @example\n * ```typescript\n * // In manifest\n * queries: [\n * {\n * type: 'canMoveColumn',\n * description: 'Check if a column can be moved/reordered',\n * },\n * ]\n *\n * // In plugin class\n * handleQuery(query: PluginQuery): unknown {\n * if (query.type === 'canMoveColumn') {\n * return this.canMoveColumn(query.context);\n * }\n * }\n * ```\n */\nexport interface QueryDefinition {\n /**\n * The query type identifier (e.g., 'canMoveColumn', 'getContextMenuItems').\n * Should be unique across all plugins.\n */\n type: string;\n\n /**\n * Human-readable description of what the query does.\n */\n description?: string;\n}\n\n/**\n * Defines an event that a plugin can emit.\n * Other plugins can subscribe to these events via the Event Bus.\n *\n * @category Plugin Development\n *\n * @example\n * ```typescript\n * // In manifest\n * events: [\n * {\n * type: 'filter-change',\n * description: 'Emitted when filter criteria change',\n * },\n * ]\n *\n * // In plugin class - emit\n * this.emitPluginEvent('filter-change', { field: 'name', value: 'Alice' });\n *\n * // In another plugin - subscribe\n * this.on('filter-change', (detail) => console.log('Filter changed:', detail));\n * ```\n */\nexport interface EventDefinition {\n /**\n * The event type identifier (e.g., 'filter-change', 'selection-change').\n * Used when emitting and subscribing to events.\n */\n type: string;\n\n /**\n * Human-readable description of what the event represents.\n */\n description?: string;\n\n /**\n * Whether this event is cancelable via `event.preventDefault()`.\n * @default false\n */\n cancelable?: boolean;\n}\n\n// ============================================================================\n// Plugin Manifest Types\n// ============================================================================\n\n/**\n * Defines a property that a plugin \"owns\" - used for configuration validation.\n * When this property is used without the owning plugin loaded, an error is thrown.\n *\n * @category Plugin Development\n */\nexport interface PluginPropertyDefinition {\n /** The property name on column or grid config */\n property: string;\n /** Whether this is a column-level or config-level property */\n level: 'column' | 'config';\n /** Human-readable description for error messages (e.g., 'the \"editable\" column property') */\n description: string;\n /** Import path hint for error messages (e.g., \"import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';\") */\n importHint?: string;\n /** Custom check for whether property is considered \"used\" (default: truthy value check) */\n isUsed?: (value: unknown) => boolean;\n}\n\n/**\n * A configuration validation rule for detecting invalid/conflicting settings.\n * Plugins declare rules in their manifest; the validator executes them during grid initialization.\n *\n * @category Plugin Development\n * @template TConfig - The plugin's configuration type\n */\nexport interface PluginConfigRule<TConfig = unknown> {\n /** Rule identifier for debugging (e.g., 'selection/range-dblclick') */\n id: string;\n /** Severity: 'error' throws, 'warn' logs warning */\n severity: 'error' | 'warn';\n /** Human-readable message shown when rule is violated */\n message: string;\n /** Predicate returning true if the rule is VIOLATED (i.e., config is invalid) */\n check: (pluginConfig: TConfig) => boolean;\n}\n\n/**\n * Hook names that can have execution priority configured.\n *\n * @category Plugin Development\n */\nexport type HookName =\n | 'processColumns'\n | 'processRows'\n | 'afterRender'\n | 'afterCellRender'\n | 'afterRowRender'\n | 'onCellClick'\n | 'onCellMouseDown'\n | 'onCellMouseMove'\n | 'onCellMouseUp'\n | 'onKeyDown'\n | 'onScroll'\n | 'onScrollRender';\n\n/**\n * Static metadata about a plugin's capabilities and requirements.\n * Declared as a static property on plugin classes.\n *\n * @category Plugin Development\n * @template TConfig - The plugin's configuration type\n *\n * @example\n * ```typescript\n * export class MyPlugin extends BaseGridPlugin<MyConfig> {\n * static override readonly manifest: PluginManifest<MyConfig> = {\n * ownedProperties: [\n * { property: 'myProp', level: 'column', description: 'the \"myProp\" column property' },\n * ],\n * configRules: [\n * { id: 'my-plugin/invalid-combo', severity: 'warn', message: '...', check: (c) => c.a && c.b },\n * ],\n * };\n * readonly name = 'myPlugin';\n * }\n * ```\n */\nexport interface PluginManifest<TConfig = unknown> {\n /**\n * Properties this plugin owns - validated by validate-config.ts.\n * If a user uses one of these properties without loading the plugin, an error is thrown.\n */\n ownedProperties?: PluginPropertyDefinition[];\n\n /**\n * Hook execution priority (higher = later, default 0).\n * Use negative values to run earlier, positive to run later.\n */\n hookPriority?: Partial<Record<HookName, number>>;\n\n /**\n * Configuration validation rules - checked during grid initialization.\n * Rules with severity 'error' throw, 'warn' logs to console.\n */\n configRules?: PluginConfigRule<TConfig>[];\n\n /**\n * Plugins that are incompatible with this plugin.\n * When both plugins are loaded together, a warning is shown.\n *\n * @example\n * ```typescript\n * incompatibleWith: [\n * { name: 'groupingRows', reason: 'Responsive card layout does not support row grouping yet' },\n * ],\n * ```\n */\n incompatibleWith?: PluginIncompatibility[];\n\n /**\n * Queries this plugin can handle.\n * Declares what query types this plugin responds to via `handleQuery()`.\n * This replaces the centralized PLUGIN_QUERIES approach with manifest-declared queries.\n *\n * @example\n * ```typescript\n * queries: [\n * { type: 'canMoveColumn', description: 'Check if a column can be moved' },\n * ],\n * ```\n */\n queries?: QueryDefinition[];\n\n /**\n * Events this plugin can emit.\n * Declares what event types other plugins can subscribe to via `on()`.\n *\n * @example\n * ```typescript\n * events: [\n * { type: 'filter-change', description: 'Emitted when filter criteria change' },\n * ],\n * ```\n */\n events?: EventDefinition[];\n}\n\n/**\n * Abstract base class for all grid plugins.\n *\n * @category Plugin Development\n * @template TConfig - Configuration type for the plugin\n */\nexport abstract class BaseGridPlugin<TConfig = unknown> implements GridPlugin {\n /**\n * Plugin dependencies - declare other plugins this one requires.\n *\n * Dependencies are validated when the plugin is attached.\n * Required dependencies throw an error if missing.\n * Optional dependencies log an info message if missing.\n *\n * @example\n * ```typescript\n * static readonly dependencies: PluginDependency[] = [\n * { name: 'editing', required: true, reason: 'Tracks cell edits for undo/redo' },\n * { name: 'selection', required: false, reason: 'Enables selection-based undo' },\n * ];\n * ```\n */\n static readonly dependencies?: PluginDependency[];\n\n /**\n * Plugin manifest - declares owned properties, config rules, and hook priorities.\n *\n * This is read by the configuration validator to:\n * - Validate that required plugins are loaded when their properties are used\n * - Execute configRules to detect invalid/conflicting settings\n * - Order hook execution based on priority\n *\n * @example\n * ```typescript\n * static override readonly manifest: PluginManifest<MyConfig> = {\n * ownedProperties: [\n * { property: 'myProp', level: 'column', description: 'the \"myProp\" column property' },\n * ],\n * configRules: [\n * { id: 'myPlugin/conflict', severity: 'warn', message: '...', check: (c) => c.a && c.b },\n * ],\n * };\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n static readonly manifest?: PluginManifest<any>;\n\n /** Unique plugin identifier (derived from class name by default) */\n abstract readonly name: string;\n\n /**\n * Alternative names for backward compatibility.\n * `getPluginByName()` matches against both `name` and `aliases`.\n * @internal\n */\n readonly aliases?: readonly string[];\n\n /**\n * Plugin version - defaults to grid version for built-in plugins.\n * Third-party plugins can override with their own semver.\n */\n readonly version: string = typeof __GRID_VERSION__ !== 'undefined' ? __GRID_VERSION__ : 'dev';\n\n /** CSS styles to inject into the grid's shadow DOM */\n readonly styles?: string;\n\n /** Custom cell renderers keyed by type name */\n readonly cellRenderers?: Record<string, CellRenderer>;\n\n /** Custom header renderers keyed by type name */\n readonly headerRenderers?: Record<string, HeaderRenderer>;\n\n /** Custom cell editors keyed by type name */\n readonly cellEditors?: Record<string, CellEditor>;\n\n /** The grid instance this plugin is attached to */\n protected grid!: GridElement;\n\n /** Plugin configuration - merged with defaults in attach() */\n protected config!: TConfig;\n\n /** User-provided configuration from constructor */\n protected readonly userConfig: Partial<TConfig>;\n\n /**\n * Plugin-level AbortController for event listener cleanup.\n * Created fresh in attach(), aborted in detach().\n * This ensures event listeners are properly cleaned up when plugins are re-attached.\n */\n #abortController?: AbortController;\n\n /**\n * Default configuration - subclasses should override this getter.\n * Note: This must be a getter (not property initializer) for proper inheritance\n * since property initializers run after parent constructor.\n */\n protected get defaultConfig(): Partial<TConfig> {\n return {};\n }\n\n constructor(config: Partial<TConfig> = {}) {\n this.userConfig = config;\n }\n\n /**\n * Called when the plugin is attached to a grid.\n * Override to set up event listeners, initialize state, etc.\n *\n * @example\n * ```ts\n * attach(grid: GridElement): void {\n * super.attach(grid);\n * // Set up document-level listeners with auto-cleanup\n * document.addEventListener('keydown', this.handleEscape, {\n * signal: this.disconnectSignal\n * });\n * }\n * ```\n */\n attach(grid: GridElement): void {\n // Abort any previous abort controller (in case of re-attach without detach)\n this.#abortController?.abort();\n // Create fresh abort controller for this attachment\n this.#abortController = new AbortController();\n\n this.grid = grid;\n // Merge config here (after subclass construction is complete)\n this.config = { ...this.defaultConfig, ...this.userConfig } as TConfig;\n }\n\n /**\n * Called when the plugin is detached from a grid.\n * Override to clean up event listeners, timers, etc.\n *\n * @example\n * ```ts\n * detach(): void {\n * // Clean up any state not handled by disconnectSignal\n * this.selectedRows.clear();\n * this.cache = null;\n * }\n * ```\n */\n detach(): void {\n // Abort the plugin's abort controller to clean up all event listeners\n // registered with { signal: this.disconnectSignal }\n this.#abortController?.abort();\n this.#abortController = undefined;\n // Override in subclass for additional cleanup\n }\n\n /**\n * Called when another plugin is attached to the same grid.\n * Use for inter-plugin coordination, e.g., to integrate with new plugins.\n *\n * @param name - The name of the plugin that was attached\n * @param plugin - The plugin instance (for direct access if needed)\n *\n * @example\n * ```ts\n * onPluginAttached(name: string, plugin: BaseGridPlugin): void {\n * if (name === 'selection') {\n * // Integrate with selection plugin\n * this.selectionPlugin = plugin as SelectionPlugin;\n * }\n * }\n * ```\n */\n onPluginAttached?(name: string, plugin: BaseGridPlugin): void;\n\n /**\n * Called when another plugin is detached from the same grid.\n * Use to clean up inter-plugin references.\n *\n * @param name - The name of the plugin that was detached\n *\n * @example\n * ```ts\n * onPluginDetached(name: string): void {\n * if (name === 'selection') {\n * this.selectionPlugin = undefined;\n * }\n * }\n * ```\n */\n onPluginDetached?(name: string): void;\n\n /**\n * Get another plugin instance from the same grid.\n * Use for inter-plugin communication.\n *\n * **Prefer {@link BaseGridPlugin.grid grid.getPluginByName()}** when you don't need the class import.\n *\n * @example\n * ```ts\n * // Preferred: by name\n * const selection = this.grid?.getPluginByName('selection');\n *\n * // Alternative: by class\n * const selection = this.getPlugin(SelectionPlugin);\n * if (selection) {\n * const selectedRows = selection.getSelectedRows();\n * }\n * ```\n */\n protected getPlugin<T extends BaseGridPlugin>(PluginClass: new (...args: any[]) => T): T | undefined {\n return this.grid?.getPlugin(PluginClass);\n }\n\n /**\n * Emit a custom event from the grid.\n */\n protected emit<T>(eventName: string, detail: T): void {\n this.grid?.dispatchEvent?.(new CustomEvent(eventName, { detail, bubbles: true }));\n }\n\n /**\n * Emit a cancelable custom event from the grid.\n * @returns `true` if the event was cancelled (preventDefault called), `false` otherwise\n */\n protected emitCancelable<T>(eventName: string, detail: T): boolean {\n const event = new CustomEvent(eventName, { detail, bubbles: true, cancelable: true });\n this.grid?.dispatchEvent?.(event);\n return event.defaultPrevented;\n }\n\n // =========================================================================\n // Event Bus - Plugin-to-Plugin Communication\n // =========================================================================\n\n /**\n * Subscribe to an event from another plugin.\n * The subscription is automatically cleaned up when this plugin is detached.\n *\n * @category Plugin Development\n * @param eventType - The event type to listen for (e.g., 'filter-change')\n * @param callback - The callback to invoke when the event is emitted\n *\n * @example\n * ```typescript\n * // In attach() or other initialization\n * this.on('filter-change', (detail) => {\n * console.log('Filter changed:', detail);\n * });\n * ```\n */\n protected on<T = unknown>(eventType: string, callback: (detail: T) => void): void {\n this.grid?._pluginManager?.subscribe(this, eventType, callback as (detail: unknown) => void);\n }\n\n /**\n * Unsubscribe from a plugin event.\n *\n * @category Plugin Development\n * @param eventType - The event type to stop listening for\n *\n * @example\n * ```typescript\n * this.off('filter-change');\n * ```\n */\n protected off(eventType: string): void {\n this.grid?._pluginManager?.unsubscribe(this, eventType);\n }\n\n /**\n * Emit an event to other plugins via the Event Bus.\n * This is for inter-plugin communication only; it does NOT dispatch DOM events.\n * Use `emit()` to dispatch DOM events that external consumers can listen to.\n *\n * @category Plugin Development\n * @param eventType - The event type to emit (should be declared in manifest.events)\n * @param detail - The event payload\n *\n * @example\n * ```typescript\n * // Emit to other plugins (not DOM)\n * this.emitPluginEvent('filter-change', { field: 'name', value: 'Alice' });\n *\n * // For DOM events that consumers can addEventListener to:\n * this.emit('filter-change', { field: 'name', value: 'Alice' });\n * ```\n */\n protected emitPluginEvent<T>(eventType: string, detail: T): void {\n this.grid?._pluginManager?.emitPluginEvent(eventType, detail);\n }\n\n /**\n * Request a re-render of the grid.\n * Uses ROWS phase - does NOT trigger processColumns hooks.\n */\n protected requestRender(): void {\n this.grid?.requestRender?.();\n }\n\n /**\n * Request a columns re-render of the grid.\n * Uses COLUMNS phase - triggers processColumns hooks.\n * Use this when your plugin needs to reprocess column configuration.\n */\n protected requestColumnsRender(): void {\n (this.grid as { requestColumnsRender?: () => void })?.requestColumnsRender?.();\n }\n\n /**\n * Request a re-render and restore focus styling afterward.\n * Use this when a plugin action (like expand/collapse) triggers a render\n * but needs to maintain keyboard navigation focus.\n */\n protected requestRenderWithFocus(): void {\n this.grid?.requestRenderWithFocus?.();\n }\n\n /**\n * Request a lightweight style update without rebuilding DOM.\n * Use this instead of requestRender() when only CSS classes need updating.\n */\n protected requestAfterRender(): void {\n this.grid?.requestAfterRender?.();\n }\n\n /**\n * Re-render visible rows without rebuilding the row model or recalculating geometry.\n * Use this when row data has been updated in-place (e.g., server-side block loads)\n * and only the visible viewport needs to re-render.\n */\n protected requestVirtualRefresh(): void {\n this.grid?.requestVirtualRefresh?.();\n }\n\n /**\n * Get the current rows from the grid.\n */\n protected get rows(): any[] {\n return this.grid?.rows ?? [];\n }\n\n /**\n * Get the original unfiltered/unprocessed rows from the grid.\n * Use this when you need all source data regardless of active filters.\n */\n protected get sourceRows(): any[] {\n return this.grid?.sourceRows ?? [];\n }\n\n /**\n * Get the current columns from the grid.\n */\n protected get columns(): ColumnConfig[] {\n return this.grid?.columns ?? [];\n }\n\n /**\n * Get only visible columns from the grid (excludes hidden).\n * Use this for rendering that needs to match the grid template.\n */\n protected get visibleColumns(): ColumnConfig[] {\n return this.grid?._visibleColumns ?? [];\n }\n\n /**\n * Get the grid as an HTMLElement for direct DOM operations.\n * Use sparingly - prefer the typed GridElementRef API when possible.\n *\n * @example\n * ```ts\n * const width = this.gridElement.clientWidth;\n * this.gridElement.classList.add('my-plugin-active');\n * ```\n */\n protected get gridElement(): HTMLElement {\n return this.grid?._hostElement;\n }\n\n /**\n * Get the disconnect signal for event listener cleanup.\n * This signal is aborted when the grid disconnects from the DOM.\n * Use this when adding event listeners that should be cleaned up automatically.\n *\n * Best for:\n * - Document/window-level listeners added in attach()\n * - Listeners on the grid element itself\n * - Any listener that should persist across renders\n *\n * Not needed for:\n * - Listeners on elements created in afterRender() (removed with element)\n *\n * @example\n * element.addEventListener('click', handler, { signal: this.disconnectSignal });\n * document.addEventListener('keydown', handler, { signal: this.disconnectSignal });\n */\n protected get disconnectSignal(): AbortSignal {\n // Return the plugin's own abort signal for proper cleanup on detach/re-attach\n // Falls back to grid's signal if plugin's controller not yet created\n return this.#abortController?.signal ?? this.grid?.disconnectSignal;\n }\n\n /**\n * Get the grid-level icons configuration.\n * Returns merged icons (user config + defaults).\n */\n protected get gridIcons(): typeof DEFAULT_GRID_ICONS {\n const userIcons = this.grid?.gridConfig?.icons ?? {};\n return { ...DEFAULT_GRID_ICONS, ...userIcons };\n }\n\n // #region Animation Helpers\n\n /**\n * Check if animations are enabled at the grid level.\n * Respects gridConfig.animation.mode and the CSS variable set by the grid.\n *\n * Plugins should use this to skip animations when:\n * - Animation mode is 'off' or `false`\n * - User prefers reduced motion and mode is 'reduced-motion' (default)\n *\n * @example\n * ```ts\n * private get animationStyle(): 'slide' | 'fade' | false {\n * if (!this.isAnimationEnabled) return false;\n * return this.config.animation ?? 'slide';\n * }\n * ```\n */\n protected get isAnimationEnabled(): boolean {\n const mode = this.grid?.effectiveConfig?.animation?.mode ?? 'reduced-motion';\n\n // Explicit off = disabled\n if (mode === false || mode === 'off') return false;\n\n // Explicit on = always enabled\n if (mode === true || mode === 'on') return true;\n\n // reduced-motion: check CSS variable (set by grid based on media query)\n const host = this.gridElement;\n if (host) {\n const enabled = getComputedStyle(host).getPropertyValue('--tbw-animation-enabled').trim();\n return enabled !== '0';\n }\n\n return true; // Default to enabled\n }\n\n /**\n * Get the animation duration in milliseconds from CSS variable.\n * Falls back to 200ms if not set.\n *\n * Plugins can use this for their animation timing to stay consistent\n * with the grid-level animation.duration setting.\n *\n * @example\n * ```ts\n * element.animate(keyframes, { duration: this.animationDuration });\n * ```\n */\n protected get animationDuration(): number {\n const host = this.gridElement;\n if (host) {\n const durationStr = getComputedStyle(host).getPropertyValue('--tbw-animation-duration').trim();\n const parsed = parseInt(durationStr, 10);\n if (!isNaN(parsed)) return parsed;\n }\n return 200; // Default\n }\n\n // #endregion\n\n /**\n * Resolve an icon value to string or HTMLElement.\n * Checks plugin config first, then grid-level icons, then defaults.\n *\n * @param iconKey - The icon key in GridIcons (e.g., 'expand', 'collapse')\n * @param pluginOverride - Optional plugin-level override\n * @returns The resolved icon value\n */\n protected resolveIcon(iconKey: keyof typeof DEFAULT_GRID_ICONS, pluginOverride?: IconValue): IconValue {\n // Plugin override takes precedence\n if (pluginOverride !== undefined) {\n return pluginOverride;\n }\n // Then grid-level config\n return this.gridIcons[iconKey];\n }\n\n /**\n * Set an icon value on an element.\n * Handles both string (text/HTML) and HTMLElement values.\n *\n * @param element - The element to set the icon on\n * @param icon - The icon value (string or HTMLElement)\n */\n protected setIcon(element: HTMLElement, icon: IconValue): void {\n if (typeof icon === 'string') {\n element.innerHTML = icon;\n } else if (icon instanceof HTMLElement) {\n element.innerHTML = '';\n element.appendChild(icon.cloneNode(true));\n }\n }\n\n /**\n * Log a warning with an optional diagnostic code.\n *\n * When a diagnostic code is provided, the message is formatted with the code\n * and a link to the troubleshooting docs.\n *\n * @example\n * ```ts\n * this.warn('Something went wrong'); // plain\n * this.warn(MISSING_BREAKPOINT, 'Set a breakpoint'); // with code + docs link\n * ```\n */\n protected warn(message: string): void;\n protected warn(code: DiagnosticCode, message: string): void;\n protected warn(codeOrMessage: DiagnosticCode | string, message?: string): void {\n if (message !== undefined) {\n // Called with (code, message)\n console.warn(formatDiagnostic(codeOrMessage as DiagnosticCode, message, this.gridElement.id, this.name));\n } else {\n // Called with (message) — plain warning, no diagnostic code\n console.warn(`${gridPrefix(this.gridElement.id, this.name)} ${codeOrMessage}`);\n }\n }\n\n /**\n * Throw an error with a diagnostic code and docs link.\n * Use for configuration errors and API misuse that should halt execution.\n */\n protected throwDiagnostic(code: DiagnosticCode, message: string): never {\n throw new Error(formatDiagnostic(code, message, this.gridElement.id, this.name));\n }\n\n // #region Lifecycle Hooks\n\n /**\n * Transform rows before rendering.\n * Called during each render cycle before rows are rendered to the DOM.\n * Use this to filter, sort, or add computed properties to rows.\n *\n * @param rows - The current rows array (readonly to encourage returning a new array)\n * @returns The modified rows array to render\n *\n * @example\n * ```ts\n * processRows(rows: readonly any[]): any[] {\n * // Filter out hidden rows\n * return rows.filter(row => !row._hidden);\n * }\n * ```\n *\n * @example\n * ```ts\n * processRows(rows: readonly any[]): any[] {\n * // Add computed properties\n * return rows.map(row => ({\n * ...row,\n * _fullName: `${row.firstName} ${row.lastName}`\n * }));\n * }\n * ```\n */\n processRows?(rows: readonly any[]): any[];\n\n /**\n * Transform columns before rendering.\n * Called during each render cycle before column headers and cells are rendered.\n * Use this to add, remove, or modify column definitions.\n *\n * @param columns - The current columns array (readonly to encourage returning a new array)\n * @returns The modified columns array to render\n *\n * @example\n * ```ts\n * processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n * // Add a selection checkbox column\n * return [\n * { field: '_select', header: '', width: 40 },\n * ...columns\n * ];\n * }\n * ```\n */\n processColumns?(columns: readonly ColumnConfig[]): ColumnConfig[];\n\n /**\n * Called before each render cycle begins.\n * Use this to prepare state or cache values needed during rendering.\n *\n * **Note:** This hook is currently a placeholder for future implementation.\n * It is defined in the interface but not yet invoked by the grid's render pipeline.\n * If you need this functionality, please open an issue or contribute an implementation.\n *\n * @example\n * ```ts\n * beforeRender(): void {\n * this.visibleRowCount = this.calculateVisibleRows();\n * }\n * ```\n */\n beforeRender?(): void;\n\n /**\n * Called after each render cycle completes.\n * Use this for DOM manipulation, adding event listeners to rendered elements,\n * or applying visual effects like selection highlights.\n *\n * @example\n * ```ts\n * afterRender(): void {\n * // Apply selection styling to rendered rows\n * const rows = this.gridElement?.querySelectorAll('.data-row');\n * rows?.forEach((row, i) => {\n * row.classList.toggle('selected', this.selectedRows.has(i));\n * });\n * }\n * ```\n */\n afterRender?(): void;\n\n /**\n * Called after each cell is rendered.\n * This hook is more efficient than `afterRender()` for cell-level modifications\n * because you receive the cell context directly - no DOM queries needed.\n *\n * Use cases:\n * - Adding selection/highlight classes to specific cells\n * - Injecting badges or decorations\n * - Applying conditional styling based on cell value\n *\n * Performance note: Called for every visible cell during render. Keep implementation fast.\n * This hook is also called during scroll when cells are recycled.\n *\n * @param context - The cell render context with row, column, value, and elements\n *\n * @example\n * ```ts\n * afterCellRender(context: AfterCellRenderContext): void {\n * // Add selection class without DOM queries\n * if (this.isSelected(context.rowIndex, context.colIndex)) {\n * context.cellElement.classList.add('selected');\n * }\n *\n * // Add validation error styling\n * if (this.hasError(context.row, context.column.field)) {\n * context.cellElement.classList.add('has-error');\n * }\n * }\n * ```\n */\n afterCellRender?(context: AfterCellRenderContext): void;\n\n /**\n * Called after a row is fully rendered (all cells complete).\n * Use this for row-level decorations, styling, or ARIA attributes.\n *\n * Common use cases:\n * - Adding selection classes to entire rows (row-focus, selected)\n * - Setting row-level ARIA attributes\n * - Applying row validation highlighting\n * - Tree indentation styling\n *\n * Performance note: Called for every visible row during render. Keep implementation fast.\n * This hook is also called during scroll when rows are recycled.\n *\n * @param context - The row render context with row data and element\n *\n * @example\n * ```ts\n * afterRowRender(context: AfterRowRenderContext): void {\n * // Add row selection class without DOM queries\n * if (this.isRowSelected(context.rowIndex)) {\n * context.rowElement.classList.add('selected', 'row-focus');\n * }\n *\n * // Add validation error styling\n * if (this.rowHasErrors(context.row)) {\n * context.rowElement.classList.add('has-errors');\n * }\n * }\n * ```\n */\n afterRowRender?(context: AfterRowRenderContext): void;\n\n /**\n * Called after scroll-triggered row rendering completes.\n * This is a lightweight hook for applying visual state to recycled DOM elements.\n * Use this instead of afterRender when you need to reapply styling during scroll.\n *\n * Performance note: This is called frequently during scroll. Keep implementation fast.\n *\n * @example\n * ```ts\n * onScrollRender(): void {\n * // Reapply selection state to visible cells\n * this.applySelectionToVisibleCells();\n * }\n * ```\n */\n onScrollRender?(): void;\n\n /**\n * Return extra height contributed by this plugin (e.g., expanded detail rows).\n * Used to adjust scrollbar height calculations for virtualization.\n *\n * @returns Total extra height in pixels\n *\n * @deprecated Use {@link getRowHeight} instead. This hook will be removed in v2.0.\n * The new `getRowHeight(row, index)` hook provides per-row height information which\n * enables better position caching and variable row height support.\n *\n * @example\n * ```ts\n * // OLD (deprecated):\n * getExtraHeight(): number {\n * return this.expandedRows.size * this.detailHeight;\n * }\n *\n * // NEW (preferred):\n * getRowHeight(row: unknown, index: number): number | undefined {\n * if (this.isExpanded(row)) {\n * return this.baseRowHeight + this.getDetailHeight(row);\n * }\n * return undefined;\n * }\n * ```\n */\n getExtraHeight?(): number;\n\n /**\n * Return extra height that appears before a given row index.\n * Used by virtualization to correctly calculate scroll positions when\n * there's variable height content (like expanded detail rows) above the viewport.\n *\n * @param beforeRowIndex - The row index to calculate extra height before\n * @returns Extra height in pixels that appears before this row\n *\n * @deprecated Use {@link getRowHeight} instead. This hook will be removed in v2.0.\n * The new `getRowHeight(row, index)` hook provides per-row height information which\n * enables better position caching and variable row height support.\n *\n * @example\n * ```ts\n * // OLD (deprecated):\n * getExtraHeightBefore(beforeRowIndex: number): number {\n * let height = 0;\n * for (const expandedRowIndex of this.expandedRowIndices) {\n * if (expandedRowIndex < beforeRowIndex) {\n * height += this.getDetailHeight(expandedRowIndex);\n * }\n * }\n * return height;\n * }\n *\n * // NEW (preferred):\n * getRowHeight(row: unknown, index: number): number | undefined {\n * if (this.isExpanded(row)) {\n * return this.baseRowHeight + this.getDetailHeight(row);\n * }\n * return undefined;\n * }\n * ```\n */\n getExtraHeightBefore?(beforeRowIndex: number): number;\n\n /**\n * Get the height of a specific row.\n * Used for synthetic rows (group headers, detail panels, etc.) that have fixed heights.\n * Return undefined if this plugin does not manage the height for this row.\n *\n * This hook is called during position cache rebuilds for variable row height virtualization.\n * Plugins that create synthetic rows should implement this to provide accurate heights.\n *\n * @param row - The row data\n * @param index - The row index in the processed rows array\n * @returns The row height in pixels, or undefined if not managed by this plugin\n *\n * @example\n * ```ts\n * getRowHeight(row: unknown, index: number): number | undefined {\n * // Group headers have a fixed height\n * if (this.isGroupHeader(row)) {\n * return 32;\n * }\n * return undefined; // Let grid use default/measured height\n * }\n * ```\n */\n getRowHeight?(row: unknown, index: number): number | undefined;\n\n /**\n * Adjust the virtualization start index to render additional rows before the visible range.\n * Use this when expanded content (like detail rows) needs its parent row to remain rendered\n * even when the parent row itself has scrolled above the viewport.\n *\n * @param start - The calculated start row index\n * @param scrollTop - The current scroll position\n * @param rowHeight - The height of a single row\n * @returns The adjusted start index (lower than or equal to original start)\n *\n * @example\n * ```ts\n * adjustVirtualStart(start: number, scrollTop: number, rowHeight: number): number {\n * // If row 5 is expanded and scrolled partially, keep it rendered\n * for (const expandedRowIndex of this.expandedRowIndices) {\n * const expandedRowTop = expandedRowIndex * rowHeight;\n * const expandedRowBottom = expandedRowTop + rowHeight + this.detailHeight;\n * if (expandedRowBottom > scrollTop && expandedRowIndex < start) {\n * return expandedRowIndex;\n * }\n * }\n * return start;\n * }\n * ```\n */\n adjustVirtualStart?(start: number, scrollTop: number, rowHeight: number): number;\n\n /**\n * Render a custom row, bypassing the default row rendering.\n * Use this for special row types like group headers, detail rows, or footers.\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 the plugin handled rendering (prevents default), `false`/`void` for default rendering\n *\n * @example\n * ```ts\n * renderRow(row: any, rowEl: HTMLElement, rowIndex: number): boolean | void {\n * if (row._isGroupHeader) {\n * rowEl.innerHTML = `<div class=\"group-header\">${row._groupLabel}</div>`;\n * return true; // Handled - skip default rendering\n * }\n * // Return void to let default rendering proceed\n * }\n * ```\n */\n renderRow?(row: any, rowEl: HTMLElement, rowIndex: number): boolean | void;\n\n // #endregion\n\n // #region Inter-Plugin Communication\n\n /**\n * Handle queries from other plugins.\n * This is the generic mechanism for inter-plugin communication.\n * Plugins can respond to well-known query types or define their own.\n *\n * **Prefer `handleQuery` for new plugins** - it has the same signature but\n * a clearer name. `onPluginQuery` is kept for backwards compatibility.\n *\n * @category Plugin Development\n * @param query - The query object with type and context\n * @returns Query-specific response, or undefined if not handling this query\n *\n * @example\n * ```ts\n * onPluginQuery(query: PluginQuery): unknown {\n * switch (query.type) {\n * case PLUGIN_QUERIES.CAN_MOVE_COLUMN:\n * // Prevent moving pinned columns\n * const column = query.context as ColumnConfig;\n * if (column.sticky === 'left' || column.sticky === 'right') {\n * return false;\n * }\n * break;\n * case PLUGIN_QUERIES.GET_CONTEXT_MENU_ITEMS:\n * const params = query.context as ContextMenuParams;\n * return [{ id: 'my-action', label: 'My Action', action: () => {} }];\n * }\n * }\n * ```\n * @deprecated Use `handleQuery` instead for new plugins. Will be removed in v2.\n */\n onPluginQuery?(query: PluginQuery): unknown;\n\n /**\n * Handle queries from other plugins or the grid.\n *\n * Queries are declared in `manifest.queries` and dispatched via `grid.query()`.\n * This enables type-safe, discoverable inter-plugin communication.\n *\n * @category Plugin Development\n * @param query - The query object with type and context\n * @returns Query-specific response, or undefined if not handling this query\n *\n * @example\n * ```ts\n * // In manifest\n * static override readonly manifest: PluginManifest = {\n * queries: [\n * { type: 'canMoveColumn', description: 'Check if a column can be moved' },\n * ],\n * };\n *\n * // In plugin class\n * handleQuery(query: PluginQuery): unknown {\n * if (query.type === 'canMoveColumn') {\n * const column = query.context as ColumnConfig;\n * return !column.sticky; // Can't move sticky columns\n * }\n * }\n * ```\n */\n handleQuery?(query: PluginQuery): unknown;\n\n // #endregion\n\n // #region Interaction Hooks\n\n /**\n * Handle keyboard events on the grid.\n * Called when a key is pressed while the grid or a cell has focus.\n *\n * @param event - The native KeyboardEvent\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\n *\n * @example\n * ```ts\n * onKeyDown(event: KeyboardEvent): boolean | void {\n * // Handle Ctrl+A for select all\n * if (event.ctrlKey && event.key === 'a') {\n * this.selectAllRows();\n * return true; // Prevent default browser select-all\n * }\n * }\n * ```\n */\n onKeyDown?(event: KeyboardEvent): boolean | void;\n\n /**\n * Handle cell click events.\n * Called when a data cell is clicked (not headers).\n *\n * @param event - Cell click event with row/column context\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\n *\n * @example\n * ```ts\n * onCellClick(event: CellClickEvent): boolean | void {\n * if (event.field === '_select') {\n * this.toggleRowSelection(event.rowIndex);\n * return true; // Handled\n * }\n * }\n * ```\n */\n onCellClick?(event: CellClickEvent): boolean | void;\n\n /**\n * Handle row click events.\n * Called when any part of a data row is clicked.\n * Note: This is called in addition to onCellClick, not instead of.\n *\n * @param event - Row click event with row context\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\n *\n * @example\n * ```ts\n * onRowClick(event: RowClickEvent): boolean | void {\n * if (this.config.mode === 'row') {\n * this.selectRow(event.rowIndex, event.originalEvent);\n * return true;\n * }\n * }\n * ```\n */\n onRowClick?(event: RowClickEvent): boolean | void;\n\n /**\n * Handle header click events.\n * Called when a column header is clicked. Commonly used for sorting.\n *\n * @param event - Header click event with column context\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\n *\n * @example\n * ```ts\n * onHeaderClick(event: HeaderClickEvent): boolean | void {\n * if (event.column.sortable !== false) {\n * this.toggleSort(event.field);\n * return true;\n * }\n * }\n * ```\n */\n onHeaderClick?(event: HeaderClickEvent): boolean | void;\n\n /**\n * Handle scroll events on the grid viewport.\n * Called during scrolling. Note: This may be called frequently; debounce if needed.\n *\n * @param event - Scroll event with scroll position and viewport dimensions\n *\n * @example\n * ```ts\n * onScroll(event: ScrollEvent): void {\n * // Update sticky column positions\n * this.updateStickyPositions(event.scrollLeft);\n * }\n * ```\n */\n onScroll?(event: ScrollEvent): void;\n\n /**\n * Handle cell mousedown events.\n * Used for initiating drag operations like range selection or column resize.\n *\n * @param event - Mouse event with cell context\n * @returns `true` to indicate drag started (prevents text selection), `false`/`void` otherwise\n *\n * @example\n * ```ts\n * onCellMouseDown(event: CellMouseEvent): boolean | void {\n * if (event.rowIndex !== undefined && this.config.mode === 'range') {\n * this.startDragSelection(event.rowIndex, event.colIndex);\n * return true; // Prevent text selection\n * }\n * }\n * ```\n */\n onCellMouseDown?(event: CellMouseEvent): boolean | void;\n\n /**\n * Handle cell mousemove events during drag operations.\n * Only called when a drag is in progress (after mousedown returned true).\n *\n * @param event - Mouse event with current cell context\n * @returns `true` to continue handling the drag, `false`/`void` otherwise\n *\n * @example\n * ```ts\n * onCellMouseMove(event: CellMouseEvent): boolean | void {\n * if (this.isDragging && event.rowIndex !== undefined) {\n * this.extendSelection(event.rowIndex, event.colIndex);\n * return true;\n * }\n * }\n * ```\n */\n onCellMouseMove?(event: CellMouseEvent): boolean | void;\n\n /**\n * Handle cell mouseup events to end drag operations.\n *\n * @param event - Mouse event with final cell context\n * @returns `true` if drag was finalized, `false`/`void` otherwise\n *\n * @example\n * ```ts\n * onCellMouseUp(event: CellMouseEvent): boolean | void {\n * if (this.isDragging) {\n * this.finalizeDragSelection();\n * this.isDragging = false;\n * return true;\n * }\n * }\n * ```\n */\n onCellMouseUp?(event: CellMouseEvent): boolean | void;\n\n // Note: Context menu items are provided via handleQuery('getContextMenuItems').\n // This keeps the core decoupled from the context-menu plugin specifics.\n\n // #endregion\n\n // #region Column State Hooks\n\n /**\n * Contribute plugin-specific state for a column.\n * Called by the grid when collecting column state for serialization.\n * Plugins can add their own properties to the column state.\n *\n * @param field - The field name of the column\n * @returns Partial column state with plugin-specific properties, or undefined if no state to contribute\n *\n * @example\n * ```ts\n * getColumnState(field: string): Partial<ColumnState> | undefined {\n * const filterModel = this.filterModels.get(field);\n * if (filterModel) {\n * // Uses module augmentation to add filter property to ColumnState\n * return { filter: filterModel } as Partial<ColumnState>;\n * }\n * return undefined;\n * }\n * ```\n */\n getColumnState?(field: string): Partial<ColumnState> | undefined;\n\n /**\n * Apply plugin-specific state to a column.\n * Called by the grid when restoring column state from serialized data.\n * Plugins should restore their internal state based on the provided state.\n *\n * @param field - The field name of the column\n * @param state - The column state to apply (may contain plugin-specific properties)\n *\n * @example\n * ```ts\n * applyColumnState(field: string, state: ColumnState): void {\n * // Check for filter property added via module augmentation\n * const filter = (state as any).filter;\n * if (filter) {\n * this.filterModels.set(field, filter);\n * this.applyFilter();\n * }\n * }\n * ```\n */\n applyColumnState?(field: string, state: ColumnState): void;\n\n // #endregion\n\n // #region Scroll Boundary Hooks\n\n /**\n * Report horizontal scroll boundary offsets for this plugin.\n * Plugins that obscure part of the scroll area (e.g., pinned/sticky columns)\n * should return how much space they occupy on each side.\n * The keyboard navigation uses this to ensure focused cells are fully visible.\n *\n * @param rowEl - The row element (optional, for calculating widths from rendered cells)\n * @param focusedCell - The currently focused cell element (optional, to determine if scrolling should be skipped)\n * @returns Object with left/right pixel offsets and optional skipScroll flag, or undefined if plugin has no offsets\n *\n * @example\n * ```ts\n * getHorizontalScrollOffsets(rowEl?: HTMLElement, focusedCell?: HTMLElement): { left: number; right: number; skipScroll?: boolean } | undefined {\n * // Calculate total width of left-pinned columns\n * const leftCells = rowEl?.querySelectorAll('.sticky-left') ?? [];\n * let left = 0;\n * leftCells.forEach(el => { left += (el as HTMLElement).offsetWidth; });\n * // Skip scroll if focused cell is pinned (always visible)\n * const skipScroll = focusedCell?.classList.contains('sticky-left');\n * return { left, right: 0, skipScroll };\n * }\n * ```\n */\n getHorizontalScrollOffsets?(\n rowEl?: HTMLElement,\n focusedCell?: HTMLElement,\n ): { left: number; right: number; skipScroll?: boolean } | undefined;\n\n // #endregion\n\n // #region Shell Integration Hooks\n\n /**\n * Register a tool panel for this plugin.\n * Return undefined if plugin has no tool panel.\n * The shell will create a toolbar toggle button and render the panel content\n * when the user opens the panel.\n *\n * @returns Tool panel definition, or undefined if plugin has no panel\n *\n * @example\n * ```ts\n * getToolPanel(): ToolPanelDefinition | undefined {\n * return {\n * id: 'columns',\n * title: 'Columns',\n * icon: '☰',\n * tooltip: 'Show/hide columns',\n * order: 10,\n * render: (container) => {\n * this.renderColumnList(container);\n * return () => this.cleanup();\n * },\n * };\n * }\n * ```\n */\n getToolPanel?(): ToolPanelDefinition | undefined;\n\n /**\n * Register content for the shell header center section.\n * Return undefined if plugin has no header content.\n * Examples: search input, selection summary, status indicators.\n *\n * @returns Header content definition, or undefined if plugin has no header content\n *\n * @example\n * ```ts\n * getHeaderContent(): HeaderContentDefinition | undefined {\n * return {\n * id: 'quick-filter',\n * order: 10,\n * render: (container) => {\n * const input = document.createElement('input');\n * input.type = 'text';\n * input.placeholder = 'Search...';\n * input.addEventListener('input', this.handleInput);\n * container.appendChild(input);\n * return () => input.removeEventListener('input', this.handleInput);\n * },\n * };\n * }\n * ```\n */\n getHeaderContent?(): HeaderContentDefinition | undefined;\n\n // #endregion\n}\n","/**\n * Shared types for the plugin system.\n *\n * These types are used by both the base plugin class and the grid core.\n * Centralizing them here avoids circular imports and reduces duplication.\n */\n\nimport type { ColumnConfig, GridConfig, ToolPanelDefinition, UpdateSource } from '../types';\n\n// #region Event Types\n/**\n * Keyboard modifier flags\n */\nexport interface KeyboardModifiers {\n ctrl?: boolean;\n shift?: boolean;\n alt?: boolean;\n meta?: boolean;\n}\n\n/**\n * Cell coordinates\n */\nexport interface CellCoords {\n row: number;\n col: number;\n}\n\n/**\n * Cell click event\n */\nexport interface CellClickEvent {\n rowIndex: number;\n colIndex: number;\n column: ColumnConfig;\n field: string;\n value: unknown;\n row: unknown;\n cellEl: HTMLElement;\n originalEvent: MouseEvent;\n}\n\n/**\n * Row click event\n */\nexport interface RowClickEvent {\n rowIndex: number;\n row: unknown;\n rowEl: HTMLElement;\n originalEvent: MouseEvent;\n}\n\n/**\n * Header click event\n */\nexport interface HeaderClickEvent {\n colIndex: number;\n field: string;\n column: ColumnConfig;\n headerEl: HTMLElement;\n originalEvent: MouseEvent | KeyboardEvent;\n}\n\n/**\n * Scroll event\n */\nexport interface ScrollEvent {\n scrollTop: number;\n scrollLeft: number;\n scrollHeight: number;\n scrollWidth: number;\n clientHeight: number;\n clientWidth: number;\n originalEvent?: Event;\n}\n\n/**\n * Cell mouse event (for drag operations, selection, etc.)\n * @category Plugin Development\n */\nexport interface CellMouseEvent {\n /** Event type: mousedown, mousemove, or mouseup */\n type: 'mousedown' | 'mousemove' | 'mouseup';\n /** Row index, undefined if not over a data cell */\n rowIndex?: number;\n /** Column index, undefined if not over a cell */\n colIndex?: number;\n /** Field name, undefined if not over a cell */\n field?: string;\n /** Cell value, undefined if not over a data cell */\n value?: unknown;\n /** Row data object, undefined if not over a data row */\n row?: unknown;\n /** Column configuration, undefined if not over a column */\n column?: ColumnConfig;\n /** The cell element, undefined if not over a cell */\n cellElement?: HTMLElement;\n /** The row element, undefined if not over a row */\n rowElement?: HTMLElement;\n /** Whether the event is over a header cell */\n isHeader: boolean;\n /** Cell coordinates if over a valid data cell */\n cell?: CellCoords;\n /** The original mouse event */\n originalEvent: MouseEvent;\n}\n// #endregion\n\n// #region Render Context Types\n/**\n * Context passed to the `afterCellRender` plugin hook.\n *\n * This provides efficient cell-level access without requiring DOM queries.\n * Plugins receive this context for each cell as it's rendered, enabling\n * targeted modifications instead of post-render DOM traversal.\n *\n * @category Plugin Development\n * @template TRow - The row data type\n *\n * @example\n * ```typescript\n * afterCellRender(context: AfterCellRenderContext): void {\n * if (this.isSelected(context.rowIndex, context.colIndex)) {\n * context.cellElement.classList.add('selected');\n * }\n * }\n * ```\n */\nexport interface AfterCellRenderContext<TRow = unknown> {\n /** The row data object */\n row: TRow;\n /** Zero-based row index in the visible rows array */\n rowIndex: number;\n /** The column configuration */\n column: ColumnConfig<TRow>;\n /** Zero-based column index in the visible columns array */\n colIndex: number;\n /** The cell value (row[column.field]) */\n value: unknown;\n /** The cell DOM element - can be modified by the plugin */\n cellElement: HTMLElement;\n /** The row DOM element - for context, prefer using cellElement */\n rowElement: HTMLElement;\n}\n\n/**\n * Context passed to the `afterRowRender` plugin hook.\n *\n * This provides efficient row-level access after all cells are rendered.\n * Plugins receive this context for each row, enabling row-level modifications\n * without requiring DOM queries in afterRender.\n *\n * @category Plugin Development\n * @template TRow - The row data type\n *\n * @example\n * ```typescript\n * afterRowRender(context: AfterRowRenderContext): void {\n * if (this.isRowSelected(context.rowIndex)) {\n * context.rowElement.classList.add('selected', 'row-focus');\n * }\n * }\n * ```\n */\nexport interface AfterRowRenderContext<TRow = unknown> {\n /** The row data object */\n row: TRow;\n /** Zero-based row index in the visible rows array */\n rowIndex: number;\n /** The row DOM element - can be modified by the plugin */\n rowElement: HTMLElement;\n}\n// #endregion\n\n// #region Context Menu Types\n/**\n * Context menu parameters\n */\nexport interface ContextMenuParams {\n x: number;\n y: number;\n rowIndex?: number;\n colIndex?: number;\n field?: string;\n value?: unknown;\n row?: unknown;\n column?: ColumnConfig;\n isHeader?: boolean;\n}\n\n/**\n * Context menu item (used by context-menu plugin query)\n */\nexport interface ContextMenuItem {\n id: string;\n label: string;\n icon?: string;\n disabled?: boolean;\n separator?: boolean;\n children?: ContextMenuItem[];\n action?: (params: ContextMenuParams) => void;\n}\n// #endregion\n\n// #region Plugin Query Types\n/**\n * Generic plugin query for inter-plugin communication.\n * Plugins can define their own query types as string constants\n * and respond to queries from other plugins.\n *\n * @category Plugin Development\n */\nexport interface PluginQuery<T = unknown> {\n /** Query type identifier (e.g., 'canMoveColumn', 'getContextMenuItems') */\n type: string;\n /** Query-specific context/parameters */\n context: T;\n}\n\n/**\n * Well-known plugin query types.\n * Plugins can define additional query types beyond these.\n *\n * @deprecated Use string literals with `grid.query()` instead. Query types should\n * be declared in the responding plugin's `manifest.queries` for automatic routing.\n * This constant will be removed in v2.0.\n *\n * @example\n * // Before (deprecated):\n * import { PLUGIN_QUERIES } from '@toolbox-web/grid';\n * const responses = grid.queryPlugins({ type: PLUGIN_QUERIES.CAN_MOVE_COLUMN, context: column });\n *\n * // After (recommended):\n * const responses = grid.query<boolean>('canMoveColumn', column);\n */\nexport const PLUGIN_QUERIES = {\n /** Ask if a column can be moved. Context: ColumnConfig. Response: boolean | undefined */\n CAN_MOVE_COLUMN: 'canMoveColumn',\n /** Get context menu items. Context: ContextMenuParams. Response: ContextMenuItem[] */\n GET_CONTEXT_MENU_ITEMS: 'getContextMenuItems',\n} as const;\n// #endregion\n\n// #region Cell Renderer Types\n/**\n * Cell render context for plugin cell renderers.\n * Provides full context including position and editing state.\n */\nexport interface PluginCellRenderContext {\n /** The cell value */\n value: unknown;\n /** The row data object */\n row: unknown;\n /** The row index in the data array */\n rowIndex: number;\n /** The column index */\n colIndex: number;\n /** The field name */\n field: string;\n /** The column configuration */\n column: ColumnConfig;\n /** Whether the cell is being edited */\n isEditing: boolean;\n}\n\n/**\n * Cell renderer function type for plugins.\n */\nexport type CellRenderer = (ctx: PluginCellRenderContext) => string | HTMLElement;\n\n/**\n * Header renderer function type for plugins.\n */\nexport type HeaderRenderer = (ctx: { column: ColumnConfig; colIndex: number }) => string | HTMLElement;\n\n/**\n * Cell editor interface for plugins.\n */\nexport interface CellEditor {\n create(ctx: PluginCellRenderContext, commitFn: (value: unknown) => void, cancelFn: () => void): HTMLElement;\n getValue?(element: HTMLElement): unknown;\n focus?(element: HTMLElement): void;\n}\n// #endregion\n\n// #region GridElementRef Interface\n/**\n * Minimal grid interface for plugins.\n * This avoids circular imports with the full DataGridElement.\n *\n * Member prefixes indicate accessibility:\n * - `_underscore` = protected members accessible to plugins (marked @internal in full interface)\n */\nexport interface GridElementRef {\n // =========================================================================\n // HTMLElement-like Properties (avoid casting to HTMLElement)\n // =========================================================================\n\n /** Grid element width in pixels. */\n readonly clientWidth: number;\n /** Grid element height in pixels. */\n readonly clientHeight: number;\n /** Add an event listener to the grid element. */\n addEventListener<K extends keyof HTMLElementEventMap>(\n type: K,\n listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => unknown,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n /** Remove an event listener from the grid element. */\n removeEventListener<K extends keyof HTMLElementEventMap>(\n type: K,\n listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => unknown,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n /** Set an attribute on the grid element. */\n setAttribute(name: string, value: string): void;\n /** Get an attribute from the grid element. */\n getAttribute(name: string): string | null;\n /** Remove an attribute from the grid element. */\n removeAttribute(name: string): void;\n\n /**\n * The grid's host HTMLElement.\n * Use this instead of casting the grid to HTMLElement.\n * @internal Plugin API\n */\n readonly _hostElement: HTMLElement;\n\n // =========================================================================\n // Grid Data & Configuration\n // =========================================================================\n\n /** Current rows (after plugin processing like grouping, filtering). */\n rows: unknown[];\n /** Original unfiltered/unprocessed rows. */\n sourceRows: unknown[];\n /** Column configurations. */\n columns: ColumnConfig[];\n /** Visible columns only (excludes hidden). Use for rendering. @internal */\n _visibleColumns: ColumnConfig[];\n /** Full grid configuration. */\n gridConfig: GridConfig;\n /** Effective (merged) configuration - the single source of truth. */\n effectiveConfig: GridConfig;\n\n // =========================================================================\n // Row Update API\n // =========================================================================\n\n /**\n * Get the unique ID for a row.\n * Uses configured `getRowId` function or falls back to `row.id` / `row._id`.\n * @throws Error if no ID can be determined\n */\n getRowId(row: unknown): string;\n\n /**\n * Get a row by its ID.\n * O(1) lookup via internal Map.\n * @returns The row object, or undefined if not found\n */\n getRow(id: string): unknown | undefined;\n\n /**\n * Update a row by ID.\n * Mutates the row in-place and emits `cell-change` for each changed field.\n * @param id - Row identifier (from getRowId)\n * @param changes - Partial row data to merge\n * @param source - Origin of update (default: 'api')\n * @throws Error if row is not found\n */\n updateRow(id: string, changes: Record<string, unknown>, source?: UpdateSource): void;\n\n /**\n * Batch update multiple rows.\n * More efficient than multiple `updateRow()` calls - single render cycle.\n * @param updates - Array of { id, changes } objects\n * @param source - Origin of updates (default: 'api')\n * @throws Error if any row is not found\n */\n updateRows(updates: Array<{ id: string; changes: Record<string, unknown> }>, source?: UpdateSource): void;\n\n // =========================================================================\n // Focus & Lifecycle\n // =========================================================================\n\n /** Current focused row index. @internal */\n _focusRow: number;\n /** Current focused column index. @internal */\n _focusCol: number;\n /** AbortSignal that is aborted when the grid disconnects from the DOM. */\n disconnectSignal: AbortSignal;\n\n // =========================================================================\n // Rendering\n // =========================================================================\n\n /** Request a full re-render of the grid. */\n requestRender(): void;\n /** Request a full re-render and restore focus styling afterward. */\n requestRenderWithFocus(): void;\n /** Request a lightweight style update without rebuilding DOM. */\n requestAfterRender(): void;\n /** Re-render visible rows without rebuilding the row model or recalculating geometry. */\n requestVirtualRefresh(): void;\n /** Force a layout pass. */\n forceLayout(): Promise<void>;\n /** Dispatch an event from the grid element. */\n dispatchEvent(event: Event): boolean;\n\n // =========================================================================\n // Inter-plugin Communication\n // =========================================================================\n\n /**\n * Access to the plugin manager for event bus operations.\n * @internal - Use BaseGridPlugin's on/off/emitPluginEvent helpers instead.\n */\n _pluginManager?: {\n subscribe(plugin: unknown, eventType: string, callback: (detail: unknown) => void): void;\n unsubscribe(plugin: unknown, eventType: string): void;\n emitPluginEvent<T>(eventType: string, detail: T): void;\n };\n\n /**\n * Query all plugins with a generic query and collect responses.\n * Used for inter-plugin communication (e.g., asking PinnedColumnsPlugin\n * if a column can be moved).\n *\n * @example\n * const responses = grid.queryPlugins<boolean>({\n * type: PLUGIN_QUERIES.CAN_MOVE_COLUMN,\n * context: column\n * });\n * const canMove = !responses.includes(false);\n */\n queryPlugins<T>(query: PluginQuery): T[];\n\n /**\n * Query plugins with a simplified API.\n * Convenience wrapper that uses a flat signature.\n *\n * @param type - The query type (e.g., 'canMoveColumn')\n * @param context - The query context/parameters\n * @returns Array of non-undefined responses from plugins\n *\n * @example\n * const responses = grid.query<boolean>('canMoveColumn', column);\n * const canMove = !responses.includes(false);\n */\n query<T>(type: string, context?: unknown): T[];\n\n // =========================================================================\n // DOM Access\n // =========================================================================\n\n /**\n * Find the rendered DOM element for a row by its data index.\n * Returns null if the row is not currently rendered (virtualized out).\n */\n findRenderedRowElement(rowIndex: number): HTMLElement | null;\n\n // =========================================================================\n // Column Visibility API\n // =========================================================================\n\n /**\n * Get all columns including hidden ones.\n * Returns field, header, visibility status, lock state, and utility flag.\n */\n getAllColumns(): Array<{\n field: string;\n header: string;\n visible: boolean;\n lockVisible?: boolean;\n utility?: boolean;\n }>;\n\n /**\n * Set visibility for a specific column.\n * @returns true if state changed, false if column not found or already in state\n */\n setColumnVisible(field: string, visible: boolean): boolean;\n\n /**\n * Toggle visibility for a specific column.\n * @returns true if state changed, false if column not found\n */\n toggleColumnVisibility(field: string): boolean;\n\n /**\n * Check if a column is currently visible.\n */\n isColumnVisible(field: string): boolean;\n\n /**\n * Show all hidden columns.\n */\n showAllColumns(): void;\n\n // =========================================================================\n // Column Order API\n // =========================================================================\n\n /**\n * Get the current column display order as array of field names.\n */\n getColumnOrder(): string[];\n\n /**\n * Set the column display order.\n * @param order Array of field names in desired order\n */\n setColumnOrder(order: string[]): void;\n\n /**\n * Request emission of column-state-change event (debounced).\n * Call after programmatic column changes that should notify consumers.\n */\n requestStateChange?(): void;\n\n // =========================================================================\n // Tool Panel API (Shell Integration)\n // =========================================================================\n\n /**\n * Whether the tool panel sidebar is currently open.\n */\n readonly isToolPanelOpen: boolean;\n\n /**\n * The default row height in pixels.\n * For fixed heights, this is the configured or CSS-measured row height.\n * For variable heights, this is the average/estimated row height.\n * Plugins should use this instead of hardcoding row heights.\n */\n readonly defaultRowHeight: number;\n\n /**\n * Get the IDs of expanded accordion sections.\n */\n readonly expandedToolPanelSections: string[];\n\n /**\n * Open the tool panel sidebar (accordion view with all registered panels).\n */\n openToolPanel(): void;\n\n /**\n * Close the tool panel sidebar.\n */\n closeToolPanel(): void;\n\n /**\n * Toggle the tool panel sidebar open/closed.\n */\n toggleToolPanel(): void;\n\n /**\n * Toggle a specific accordion section expanded/collapsed.\n * @param sectionId - The panel ID to toggle (matches ToolPanelDefinition.id)\n */\n toggleToolPanelSection(sectionId: string): void;\n\n /**\n * Get registered tool panel definitions.\n */\n getToolPanels(): ToolPanelDefinition[];\n\n // =========================================================================\n // Variable Row Height API\n // =========================================================================\n\n /**\n * Invalidate a row's height in the position cache.\n * Call this when a plugin changes a row's height (e.g., expanding a detail panel).\n * The position cache will be updated incrementally without a full rebuild.\n *\n * @param rowIndex - Index of the row whose height changed\n * @param newHeight - Optional new height. If not provided, queries plugins for height.\n */\n invalidateRowHeight(rowIndex: number, newHeight?: number): void;\n}\n// #endregion\n"],"names":["announce","gridEl","message","el","querySelector","textContent","requestAnimationFrame","FitModeEnum","STRETCH","FIXED","DEFAULT_ANIMATION_CONFIG","mode","duration","easing","DEFAULT_FILTER_ICON","DEFAULT_GRID_ICONS","expand","collapse","sortAsc","sortDesc","sortNone","submenuArrow","dragHandle","toolPanel","filter","filterActive","print","gridPrefix","gridId","pluginName","ROW_NOT_FOUND","CELL_CLASS_ERROR","FORMAT_ERROR","formatDiagnostic","code","toLowerCase","docsUrl","throwDiagnostic","Error","warnDiagnostic","console","warn","infoDiagnostic","info","errorDiagnostic","error","mergeColumns","programmatic","dom","length","domMap","forEach","c","existing","field","header","type","sortable","editable","resizable","width","minWidth","__viewTemplate","__editorTemplate","__headerTemplate","cRenderer","renderer","viewRenderer","existingRenderer","editor","merged","map","d","m","dRenderer","mRenderer","Object","keys","push","addPart","token","part","add","getAttribute","split","includes","setAttribute","autoSizeColumns","grid","effectiveConfig","fitMode","__didInitialAutoSize","isConnected","headerCells","Array","from","_headerRowEl","children","changed","_visibleColumns","col","i","headerCell","max","scrollWidth","rowEl","_rowPool","cell","w","__autoSized","VALID_CSS_WIDTH","resolveWidth","test","updateTemplate","_gridTemplate","min","join","trim","style","setProperty","EXPR_RE","EMPTY_SENTINEL","SAFE_EXPR","FORBIDDEN","DANGEROUS_TAGS","Set","DANGEROUS_ATTR_PATTERN","URL_ATTRS","DANGEROUS_URL_PROTOCOL","sanitizeHTML","html","indexOf","template","document","createElement","innerHTML","root","toRemove","elements","querySelectorAll","tagName","has","namespaceURI","attributes","some","attr","name","attrsToRemove","attrName","value","removeAttribute","remove","sanitizeNode","content","evalTemplateString","raw","ctx","parts","evaluated","replace","_m","expr","res","REFLECTIVE_RE","String","startsWith","key","slice","v","row","dotChain","match","out","Function","fn","str","evalSingle","result","finalStr","s","RegExp","allEmpty","every","p","finalCellScrub","n","childNodes","nodeType","Node","TEXT_NODE","compileTemplate","forceBlank","__blocked","defaultComparator","a","b","builtInSort","rows","sortState","columns","find","comparator","sortComparator","direction","sort","rA","rB","finalizeSortResult","sortedRows","dir","_rows","__rowRenderEpoch","r","__epoch","renderHeader","refreshVirtualWindow","dispatchEvent","CustomEvent","detail","requestStateChange","toggleSort","_sortState","applySort","__originalOrder","headers","h","_columns","sortHandler","then","isColumnSortable","isColumnResizable","createSortIndicator","icon","active","icons","element","HTMLElement","appendChild","cloneNode","setIcon","createResizeHandle","colIndex","handle","className","addEventListener","e","stopPropagation","preventDefault","_resizeController","start","resetColumn","setupSortHandlers","classList","tabIndex","isResizing","_dispatchHeaderClick","findHeaderRow","headerRow","headerValue","sortDirection","headerRenderer","column","cellEl","renderSortIcon","renderFilterButton","output","container","firstChild","appendRendererOutput","headerLabelRenderer","span","inferColumns","provided","sample","k","Date","isNaN","parse","charAt","toUpperCase","typeMap","RenderPhase","RenderScheduler","pendingPhase","rafHandle","readyPromise","readyResolve","initialReadyResolver","initialReadyFired","constructor","this","requestPhase","phase","_source","ensureReadyPromise","flush","whenReady","Promise","resolve","setInitialReadyResolver","resolver","cancel","cancelAnimationFrame","isPending","_schedulerIsConnected","_schedulerMergeConfig","_schedulerProcessRows","_schedulerProcessColumns","_schedulerUpdateTemplate","_schedulerRenderHeader","_schedulerAfterRender","ConfigManager","gridConfig","lightDomColumnsCache","originalColumnNodes","originalConfig","sourcesChanged","changeListeners","lightDomObserver","stateChangeTimeoutId","lightDomDebounceTimer","initialColumnState","lightDomTitle","original","effective","markSourcesChanged","setGridConfig","config","getGridConfig","setColumns","getColumns","setFitMode","getFitMode","merge","hasColumns","base","collectAllSources","freeze","cloneConfig","applyPostMergeOperations","clone","shell","toolPanels","headerContents","applyTypeDefaultsToColumns","rowHeight","_virtualization","_applyAnimationConfig","typeDefaults","typeDefault","format","editorParams","configColumns","isArray","domCols","sourceRows","__originalWidth","__compiledView","__compiledEditor","mergeShellConfig","columnState","shellLightDomTitle","_shellState","title","lightDomHeaderContent","lightDomContent","hasToolButtonsContainer","toolPanelsMap","size","panels","values","order","headerContentsMap","contents","toolbarContentsMap","toolbarContents","apiContents","originalConfigContents","configIds","id","mergedContents","collectState","plugins","sortStates","getSortState","index","state","visible","hidden","internalCol","__renderedWidth","parseFloat","get","plugin","getColumnState","pluginState","assign","applyState","allColumns","stateMap","Map","updatedColumns","updated","Infinity","sortedByPriority","priority","primarySort","applyColumnState","colState","resetState","sortMap","set","clearTimeout","setTimeout","_emit","setColumnVisible","allCols","lockVisible","visibleColumns","_clearRowPool","_setup","toggleColumnVisibility","isColumnVisible","showAllColumns","getAllColumns","utility","meta","getColumnOrder","setColumnOrder","columnMap","reordered","delete","_requestSchedulerPhase","VIRTUALIZATION","parseLightDomColumns","host","rawType","hasAttribute","widthAttr","numericWidth","minWidthAttr","numericMinWidth","editorName","rendererName","__editorName","__rendererName","optionsAttr","options","item","label","viewTpl","editorTpl","headerTpl","DataGridElementClassRef","globalThis","DataGridElement","adapters","getAdapters","viewTarget","viewAdapter","canHandle","createRenderer","editorTarget","editorAdapter","createEditor","clearLightDomCache","lightDomHandlers","registerLightDomHandler","callback","unregisterLightDomHandler","observeLightDOM","disconnect","pendingCallbacks","processPendingCallbacks","handler","clear","MutationObserver","mutations","mutation","node","addedNodes","ELEMENT_NODE","target","observe","childList","subtree","attributeFilter","onChange","notifyChange","cb","dispose","isDevelopment","window","location","hostname","process","env","NODE_ENV","booleanCellHTML","formatDateValue","getTime","toLocaleDateString","getRowIndexFromCell","parseInt","closest","parent","parentElement","clearCellFocus","isRTL","getComputedStyle","dirAttr","getDirection","resolveRenderer","columnRenderer","adapter","__frameworkAdapter","getTypeDefault","appDefault","resolveFormat","FOCUSABLE_EDITOR_SELECTOR","hasEditingCells","__editingCellCount","clearEditingState","cellTemplate","rowTemplate","createCellFromTemplate","firstElementChild","createRowFromTemplate","invalidateCellCache","__cellDisplayCache","__cellCacheEpoch","__hasSpecialColumns","fastPatchRow","rowData","rowIndex","colsLen","childLen","minLen","focusRow","_focusRow","focusCol","_focusCol","hasCellHook","_hasAfterCellRenderHook","hasSpecialCols","externalView","cellClass","rowIndexStr","renderInlineRow","isEditing","contains","shouldHaveFocus","toggle","cellClassFn","prevClasses","cls","cellClasses","validClasses","cellRenderer","renderedValue","produced","releaseCell","_afterCellRender","cellElement","rowElement","rawTpl","displayStr","formatFn","formatted","fragment","createDocumentFragment","compiled","tplHolder","needsSanitization","spec","placeholder","context","mount","queueMicrotask","bubbles","composed","blocked","dynamicClassStr","handleRowClick","_dispatchRowClick","Number","_dispatchCellClick","focusChanged","_bodyEl","matches","focus","preventScroll","ensureCellVisible","enabled","viewportEl","scrollEl","visibleHeight","clientHeight","y","scrollTop","_activeEditRows","_isGridEditMode","vStart","vEnd","end","scrollArea","forceHorizontalScroll","forceScrollLeft","scrollLeft","forceScrollRight","clientWidth","offsets","_getHorizontalScrollOffsets","left","right","skipScroll","cellRect","getBoundingClientRect","scrollAreaRect","cellLeft","cellRight","visibleLeft","visibleRight","focusTarget","activeElement","dragState","WeakMap","handleCellMousedown","getColIndexFromCell","buildCellMouseEvent","renderRoot","path","composedPath","elAtPoint","elementFromPoint","clientX","clientY","headerEl","originalEvent","isHeader","setupRootEventDelegation","gridElement","signal","_dispatchKeyDown","maxRow","maxCol","editing","colType","isFormField","tag","isContentEditable","shiftKey","commitActiveRowEdit","Math","rtl","ctrlKey","metaKey","activateEvent","cancelable","trigger","legacyEvent","defaultPrevented","handleGridKeyDown","event","_dispatchCellMouseDown","handleMouseDown","_dispatchCellMouseMove","handleMouseMove","_dispatchCellMouseUp","handleMouseUp","resolveFeatures","FocusManager","externalFocusContainers","externalFocusCleanups","focusCell","colIdx","findIndex","focusedCell","scrollToRow","virt","totalRows","idx","align","behavior","rowTop","rowH","pc","positionCache","variableHeights","offset","height","viewportH","currentTop","rowBottom","viewBottom","scrollTo","top","scrollToRowById","rowId","entry","_getRowEntry","registerExternalFocusContainer","ac","AbortController","dataset","hasFocus","newFocus","relatedTarget","containsFocus","abort","unregisterExternalFocusContainer","cleanup","isInExternalFocusContainer","destroy","hasIdleCallback","requestIdleCallback","cancelIdle","cancelIdleCallback","createLoadingContent","wrapper","spinner","createDefaultSpinner","createResizeController","resizeState","pendingRaf","prevCursor","prevUserSelect","onMove","delta","startX","startWidth","__userResized","justFinishedResize","onUp","hadResize","removeEventListener","documentElement","cursor","body","userSelect","colWidth","ANIMATION_ATTR","DURATION_PROPS","change","insert","DEFAULT_DURATIONS","getAnimationDuration","animationType","prop","computed","getPropertyValue","parsed","trimmed","endsWith","parseDuration","animateRow","findRenderedRowElement","onComplete","offsetWidth","animateRowElement","tryResolveRowId","getRowId","_id","resolveRowIdOrThrow","RowManager","resolveRowId","getRow","getRowEntry","updateRow","changes","source","changedFields","newValue","entries","oldValue","_emitDataChange","updateRows","updates","anyChanged","insertRow","animate","newRows","splice","_rebuildRowIdMap","_emitPluginEvent","removeRow","currentIdx","srcIdx","newSource","origIdx","newOrig","attrs","div","button","gridContentTemplate","cloneGridContent","buildGridDOM","hasShell","shellHeader","shellBody","contentWrapper","iconToString","outerHTML","shouldRenderShellHeader","parseLightDomShell","display","parseLightDomToolButtons","rendererFactory","toolButtonsContainer","lightDomToolbarContentIds","contentDef","render","parseLightDomToolPanels","panelEl","tooltip","adapterRenderer","existingPanel","panelCleanups","panel","lightDomToolPanelIds","renderCustomToolbarContents","configContents","stateContents","allContents","toolbarContentCleanups","slot","renderHeaderContent","hasLightDomContent","lightDomContentMoved","hasPluginContent","contentArea","sortedContents","existingCleanup","headerContentCleanups","renderPanelContent","isPanelOpen","panelId","isExpanded","expandedSections","section","updateToolbarActiveStates","panelToggle","updatePanelState","prepareForRerender","updateAccordionSectionState","sectionId","expanded","buildGridDOMIntoElement","shellConfig","runtimeState","lightDomElements","lightDomSelectors","selector","replaceChildren","toolPanelIcon","expandIcon","sortedPanels","headerOptions","hasPanels","configButtons","hasElement","hasRender","apiButtons","bodyOptions","position","role","titleEl","toolbar","btn","toggleBtn","buildShellHeader","hasPanel","isSinglePanel","gridContent","class","resizeHandlePosition","panelContent","accordion","headerBtn","iconSpan","titleSpan","chevronSpan","buildShellBody","STYLE_ELEMENT_ID","baseStyles","pluginStylesMap","updateStyleElement","styleEl","getElementById","head","getStyleElement","pluginStyles","async","injectStyles","inlineStyles","gridCssText","stylesheet","styleSheets","cssText","cssRules","rule","err","extractGridCssFromDocument","href","resetTouchState","startY","lastY","lastX","lastTime","locked","cancelMomentum","momentumRaf","handleTouchEnd","abs","velocityY","velocityX","friction","minVelocity","scrollY","scrollX","fauxScrollbar","startMomentumScroll","setupTouchScrollListeners","gridContentEl","pointerType","activePointerId","pointerId","setPointerCapture","performance","now","handleTouchStart","passive","shouldPrevent","incrY","incrX","dt","totalDeltaY","totalDeltaX","isVerticalGesture","scrollHeight","hasVerticalScroll","hasHorizontalScroll","handleTouchMove","KNOWN_COLUMN_PROPERTIES","property","level","description","isUsed","KNOWN_CONFIG_PROPERTIES","getImportHint","capitalize","hasPlugin","getRowCacheKey","__rowCacheKey","getCachedHeight","cache","byKey","byRef","updateRowHeight","newHeight","heightDiff","measured","getRowIndexAtOffset","targetOffset","low","high","mid","floor","entryEnd","measureRenderedRowHeights","rowElements","heightCache","getPluginHeight","hasChanges","pluginHeight","currentEntry","measuredHeight","offsetHeight","setCachedHeight","measuredCount","count","countMeasuredRows","averageHeight","defaultHeight","totalHeight","calculateAverageHeight","VirtualizationManager","initialState","bypassThreshold","totalHeightEl","cachedViewportHeight","cachedFauxHeight","cachedScrollAreaHeight","scrollAreaEl","updateCachedGeometry","calculateTotalSpacerHeight","forceRead","fauxScrollHeight","viewportHeight","scrollAreaHeight","_hostElement","viewportHeightDiff","hScrollbarPadding","rowContentHeight","pluginExtraHeight","last","getTotalHeight","_getPluginExtraHeight","initializePositionCache","estimatedHeight","rowHeightFn","rowIdFn","rebuildPositionCache","_getPluginRowHeight","stats","totalMeasured","computeAverageExcludingPluginRows","invalidateRowHeight","newTotalHeight","bodyEl","force","skipAfterRender","_renderVisibleRows","_afterPluginRender","transform","_updateAriaCounts","iterations","maxIterations","extraHeightBefore","_getPluginExtraHeightBefore","adjustedStart","pluginAdjustedStart","_adjustPluginVirtualStart","targetHeight","accumulatedHeight","minRows","ceil","prevStart","prevEnd","startRowOffset","subPixelOffset","PluginManager","getPlugins","pluginMap","cellRenderers","headerRenderers","cellEditors","_hasAfterCellRender","_hasAfterRowRender","eventListeners","queryHandlers","static","WeakSet","attachAll","attach","loadedPlugins","dependencies","dep","requiredPlugin","required","reason","reasonText","importHint","validatePluginDependencies","registerQueryHandlers","warnDeprecatedHooks","invalidateHookCaches","existingPlugin","onPluginAttached","manifest","queries","queryDef","handlers","PluginClass","deprecationWarned","hasOldHooks","getExtraHeight","getExtraHeightBefore","hasNewHook","getRowHeight","unregisterQueryHandlers","queryType","detachAll","otherPlugin","onPluginDetached","unsubscribeAll","detach","getPlugin","getPluginByName","aliases","getAll","getRegisteredPluginNames","getCellRenderer","getHeaderRenderer","getCellEditor","getPluginStyles","styles","processRows","processColumns","beforeRender","afterRender","afterCellRender","hasAfterCellRenderHook","afterRowRender","hasAfterRowRenderHook","onScrollRender","total","hasExtraHeight","beforeRowIndex","hasRowHeightPlugin","adjustVirtualStart","pluginStart","renderRow","queryPlugins","query","responses","response","handleQuery","onPluginQuery","subscribe","eventType","listeners","unsubscribe","emitPluginEvent","onKeyDown","onCellClick","onRowClick","onHeaderClick","onScroll","onCellMouseDown","onCellMouseMove","onCellMouseUp","getHorizontalScrollOffsets","getToolPanels","getToolPanel","getHeaderContents","getHeaderContent","__GRID_VERSION__","registerAdapter","clearAdapters","observedAttributes","initialized","configManager","connected","pendingUpdate","pendingUpdateFlags","scheduler","scrollRaf","pendingScrollTop","hasScrollPlugins","needsRowHeightMeasurement","scrollMeasureTimeout","renderRowHook","touchState","eventAbortController","resizeObserver","rowHeightObserver","idleCallbackHandle","pooledScrollEvent","pluginManager","lastPluginsArray","lastFeaturesConfig","virtManager","focusManager","rowManager","_pluginManager","eventListenersAdded","scrollAbortController","shellState","apiToolPanelIds","apiHeaderContentIds","createShellState","shellController","resizeCleanup","clickOutsideCleanup","loading","loadingRows","loadingCells","loadingOverlayEl","rowIdMap","baseColumns","visibleColumnsCache","_restoreFocusAfterRender","__lightDomColumnsCache","__originalColumnNodes","__rowsBodyEl","queueUpdate","processConfig","wasLoading","updateLoadingOverlay","setRowLoading","updateRowLoadingState","setCellLoading","cellFields","updateCellLoadingState","isRowLoading","isCellLoading","clearAllLoading","fields","disconnectSignal","super","controller","isInitialized","setInitialized","activePanel","openToolPanel","firstPanel","shadow","_renderRoot","_accordionIcons","sections","closeToolPanel","onClose","toggleToolPanel","toggleToolPanelSection","otherId","otherPanel","contentEl","renderAccordionSectionContent","registerToolPanel","refreshShellHeader","unregisterToolPanel","registerHeaderContent","unregisterHeaderContent","contentId","onDestroy","getToolbarContents","registerToolbarContent","unregisterToolbarContent","createShellController","requestRender","ROWS","requestColumnsRender","COLUMNS","requestRenderWithFocus","requestAfterRender","STYLE","requestVirtualRefresh","initializePlugins","pluginsConfig","explicitPlugins","features","featurePlugins","allPlugins","injectAllPluginStyles","hasNewStyles","addPluginStyles","updatePluginConfigs","newPlugins","newFeatures","featuresChanged","isLightDom","isApiRegistered","configureVariableHeights","collectPluginShellContributions","hadScrollPlugins","gridRoot","setupScrollListeners","destroyPlugins","pluginPanels","pluginContents","getToolPanelRendererFactory","instanceAdapter","createToolPanelRenderer","connectedCallback","version","instanceCounter","parseLightDom","afterConnect","setupLightDomHandlers","timeout","didTimeout","timeRemaining","disconnectedCallback","cleanupShellState","rowHeightObserverSetup","customStyleSheets","attributeChangedCallback","isLoading","JSON","defaultOpen","setup","updateAriaSelection","FULL","userRowHeight","measureRowHeight","firstRow","cells","maxCellHeight","rowRect","measureRowHeightForPlugins","scrollSignal","rowsEl","currentScrollTop","rawStart","evenAlignedStart","onScrollBatched","scrollEvent","scrollAreaForWheel","isHorizontal","deltaX","deltaY","draggable","ResizeObserver","setupRowHeightObserver","listener","on","emit","eventName","rowCount","sourceRowCount","rowIdx","isActiveRow","flushPendingUpdates","flags","applyGridConfigUpdate","applyRowsUpdate","applyColumnsUpdate","applyFitModeUpdate","hadShell","hadToolPanel","accordionSectionsBefore","prevPosition","nowNeedsShell","nowHasToolPanels","toolPanelCount","newPosition","updateShellHeaderInPlace","insertBefore","sourceColumns","visibleCols","hiddenCols","processedColumns","processedFields","mergeColumnsPreservingOrder","processedVisible","processedMap","sourceFields","pluginAdded","srcCol","processed","rebuildRowModel","reapplyCoreSort","processedRows","applyAnimationConfig","animation","animationMode","epoch","needed","colLen","headerRowCount","__cachedHeaderRowCount","parentNode","hasRenderRowPlugins","__hasRenderRowPlugins","hasRowHook","_hasAfterRowRenderHook","varHeightFn","__rowDataRef","rowEpoch","prevRef","cellCount","lastElementChild","structureValid","dataRefChanged","isGridEditMode","needsExternalRebuild","hasEditing","isActivelyEditedRow","__isCustomRow","isChanged","changedRowIdSet","_changedRowIdSet","rowClassFn","rowClass","newClasses","removeProperty","_afterRowRender","renderVisibleRows","ariaState","colCount","ariaLabel","ariaDescribedBy","rowsBodyEl","prevRowCount","updateAriaCounts","columnProps","configProps","missingPlugins","addError","isConfigProperty","def","errors","fieldList","validatePluginProperties","warnings","configRules","pluginConfig","check","severity","warning","validatePluginConfigRules","pluginNames","warned","incompatibleWith","incompatibility","validatePluginIncompatibilities","updateAriaLabels","explicitLabel","gridAriaLabel","getEffectiveAriaLabel","gridAriaDescribedBy","overlayEl","overlay","createLoadingOverlay","loadingRenderer","showLoadingOverlay","setRowLoadingState","setCellLoadingState","gridTemplateColumns","poolIndex","cellClickEvent","handled","rowClickEvent","headerClickEvent","ready","forceLayout","getConfig","animateRows","rowIndices","all","results","Boolean","animateRowById","suspendProcessing","resetColumnState","isToolPanelOpen","defaultRowHeight","expandedToolPanelSections","pendingShellRefresh","afterShellRefresh","registerStyles","css","sheet","CSSStyleSheet","replaceSync","updateAdoptedStyleSheets","unregisterStyles","getRegisteredStyles","customSheets","existingSheets","adoptedStyleSheets","replaceShellHeaderElement","newHeaderHtml","hasTitle","iconStr","hasCustomContent","showSeparator","toolbarHtml","isOpen","text","renderShellHeader","temp","newHeader","replaceWith","setupShellListeners","handleShellChange","hadTitle","hadToolButtons","hasToolButtons","handleColumnChange","refreshColumns","callbacks","onPanelToggle","onSectionToggle","setupShellEventListeners","onResize","maxWidth","onMouseMove","newWidth","onMouseUp","transition","finalWidth","onMouseDown","setupToolPanelResize","closeOnClickOutside","setupClickOutsideDismiss","customElements","define","GridClasses","ROOT","HEADER","HEADER_ROW","HEADER_CELL","ROWS_VIEWPORT","ROWS_SPACER","ROWS_CONTAINER","DATA_ROW","GROUP_ROW","DATA_CELL","SELECTED","FOCUSED","EDITING","EXPANDED","COLLAPSED","DRAGGING","RESIZING","SORTABLE","SORTED_ASC","SORTED_DESC","HIDDEN","STICKY_LEFT","STICKY_RIGHT","PINNED_TOP","PINNED_BOTTOM","TREE_TOGGLE","TREE_INDENT","GROUP_TOGGLE","GROUP_LABEL","GROUP_COUNT","RANGE_SELECTION","SELECTION_OVERLAY","GridDataAttrs","ROW_INDEX","COL_INDEX","FIELD","GROUP_KEY","TREE_LEVEL","STICKY","GridSelectors","ROW_BY_INDEX","CELL_BY_FIELD","CELL_AT","SELECTED_ROWS","EDITING_CELL","builtInAggregators","sum","reduce","acc","avg","first","customAggregators","aggregatorRegistry","register","unregister","ref","run","list","builtInValueAggregators","vals","getValueAggregator","aggFunc","registerAggregator","bind","unregisterAggregator","getAggregator","runAggregator","listAggregators","userConfig","abortController","defaultConfig","emitCancelable","off","gridIcons","userIcons","isAnimationEnabled","animationDuration","durationStr","resolveIcon","iconKey","pluginOverride","codeOrMessage","CELL_CHANGE","CELL_COMMIT","ROW_COMMIT","EDIT_OPEN","EDIT_CLOSE","CHANGED_ROWS_RESET","MOUNT_EXTERNAL_VIEW","MOUNT_EXTERNAL_EDITOR","SORT_CHANGE","COLUMN_RESIZE","ACTIVATE_CELL","CELL_ACTIVATE","COLUMN_STATE_CHANGE","DATA_CHANGE","COLOR_BG","COLOR_FG","COLOR_FG_MUTED","COLOR_BORDER","COLOR_ACCENT","COLOR_HEADER_BG","COLOR_HEADER_FG","COLOR_SELECTION","COLOR_ROW_HOVER","COLOR_ROW_ALT","ROW_HEIGHT","HEADER_HEIGHT","CELL_PADDING","FONT_FAMILY","FONT_SIZE","BORDER_RADIUS","FOCUS_OUTLINE","CAN_MOVE_COLUMN","GET_CONTEXT_MENU_ITEMS","SELECTION_CHANGE","TREE_EXPAND","FILTER_CHANGE","SORT_MODEL_CHANGE","EXPORT_START","EXPORT_COMPLETE","CLIPBOARD_COPY","CLIPBOARD_PASTE","CONTEXT_MENU_OPEN","CONTEXT_MENU_CLOSE","HISTORY_CHANGE","SERVER_LOADING","SERVER_ERROR","COLUMN_VISIBILITY_CHANGE","COLUMN_REORDER","DETAIL_EXPAND","GROUP_EXPAND","parentOrAwait","awaitUpgrade","shouldAwait","whenDefined"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"grid.umd.js","sources":["../../../../libs/grid/src/lib/core/internal/aria.ts","../../../../libs/grid/src/lib/core/types.ts","../../../../libs/grid/src/lib/core/internal/diagnostics.ts","../../../../libs/grid/src/lib/core/internal/columns.ts","../../../../libs/grid/src/lib/core/internal/sanitize.ts","../../../../libs/grid/src/lib/core/internal/sorting.ts","../../../../libs/grid/src/lib/core/internal/header.ts","../../../../libs/grid/src/lib/core/internal/inference.ts","../../../../libs/grid/src/lib/core/internal/render-scheduler.ts","../../../../libs/grid/src/lib/core/internal/config-manager.ts","../../../../libs/grid/src/lib/core/internal/utils.ts","../../../../libs/grid/src/lib/core/internal/rows.ts","../../../../libs/grid/src/lib/core/internal/keyboard.ts","../../../../libs/grid/src/lib/core/internal/event-delegation.ts","../../../../libs/grid/src/lib/core/internal/feature-hook.ts","../../../../libs/grid/src/lib/core/internal/focus-manager.ts","../../../../libs/grid/src/lib/core/internal/idle-scheduler.ts","../../../../libs/grid/src/lib/core/internal/loading.ts","../../../../libs/grid/src/lib/core/internal/resize.ts","../../../../libs/grid/src/lib/core/internal/row-animation.ts","../../../../libs/grid/src/lib/core/internal/row-manager.ts","../../../../libs/grid/src/lib/core/internal/dom-builder.ts","../../../../libs/grid/src/lib/core/internal/shell.ts","../../../../libs/grid/src/lib/core/internal/style-injector.ts","../../../../libs/grid/src/lib/core/internal/touch-scroll.ts","../../../../libs/grid/src/lib/core/internal/validate-config.ts","../../../../libs/grid/src/lib/core/internal/virtualization.ts","../../../../libs/grid/src/lib/core/internal/virtualization-manager.ts","../../../../libs/grid/src/lib/core/plugin/plugin-manager.ts","../../../../libs/grid/src/lib/core/grid.ts","../../../../libs/grid/src/lib/core/styles/index.ts","../../../../libs/grid/src/lib/core/constants.ts","../../../../libs/grid/src/public.ts","../../../../libs/grid/src/lib/core/internal/aggregators.ts","../../../../libs/grid/src/lib/core/plugin/base-plugin.ts","../../../../libs/grid/src/lib/core/plugin/types.ts"],"sourcesContent":["/**\n * ARIA Accessibility Helpers\n *\n * Pure functions for managing ARIA attributes on grid elements.\n * Implements caching to avoid redundant DOM writes during scroll.\n *\n * @module internal/aria\n */\n\nimport type { GridConfig } from '../types';\nimport type { ShellState } from './shell';\n\n// #region Types\n\n/**\n * State for caching ARIA attributes to avoid redundant DOM writes.\n */\nexport interface AriaState {\n /** Last set row count */\n rowCount: number;\n /** Last set column count */\n colCount: number;\n /** Last set aria-label */\n ariaLabel: string | undefined;\n /** Last set aria-describedby */\n ariaDescribedBy: string | undefined;\n}\n\n/**\n * Create initial ARIA state.\n */\nexport function createAriaState(): AriaState {\n return {\n rowCount: -1,\n colCount: -1,\n ariaLabel: undefined,\n ariaDescribedBy: undefined,\n };\n}\n\n// #endregion\n\n// #region Count Updates\n\n/**\n * Update ARIA row and column counts on grid elements.\n * Uses caching to avoid redundant DOM writes on every scroll frame.\n *\n * @param state - ARIA state for caching\n * @param rowsBodyEl - Element to set aria-rowcount/aria-colcount on\n * @param bodyEl - Element to set role=\"rowgroup\" on\n * @param rowCount - Current row count\n * @param colCount - Current column count\n * @returns true if anything was updated\n */\nexport function updateAriaCounts(\n state: AriaState,\n rowsBodyEl: HTMLElement | null,\n bodyEl: HTMLElement | null,\n rowCount: number,\n colCount: number,\n): boolean {\n // Skip if nothing changed (hot path optimization for scroll)\n if (rowCount === state.rowCount && colCount === state.colCount) {\n return false;\n }\n\n const prevRowCount = state.rowCount;\n state.rowCount = rowCount;\n state.colCount = colCount;\n\n // Update ARIA counts on inner grid element\n if (rowsBodyEl) {\n rowsBodyEl.setAttribute('aria-rowcount', String(rowCount));\n rowsBodyEl.setAttribute('aria-colcount', String(colCount));\n }\n\n // Set role=\"rowgroup\" on .rows only when there are rows (ARIA compliance)\n if (rowCount !== prevRowCount && bodyEl) {\n if (rowCount > 0) {\n bodyEl.setAttribute('role', 'rowgroup');\n } else {\n bodyEl.removeAttribute('role');\n }\n }\n\n return true;\n}\n\n// #endregion\n\n// #region Label Updates\n\n/**\n * Determine the effective aria-label for the grid.\n * Priority: explicit config > shell title > nothing\n *\n * @param config - Grid configuration\n * @param shellState - Shell state (for light DOM title)\n * @returns The aria-label to use, or undefined\n */\nexport function getEffectiveAriaLabel<T>(\n config: GridConfig<T> | undefined,\n shellState: ShellState | undefined,\n): string | undefined {\n const explicitLabel = config?.gridAriaLabel;\n if (explicitLabel) return explicitLabel;\n\n const shellTitle = config?.shell?.header?.title ?? shellState?.lightDomTitle;\n return shellTitle ?? undefined;\n}\n\n/**\n * Update ARIA label and describedby attributes on the grid container.\n * Uses caching to avoid redundant DOM writes.\n *\n * @param state - ARIA state for caching\n * @param rowsBodyEl - Element to set aria-label/aria-describedby on\n * @param config - Grid configuration\n * @param shellState - Shell state (for light DOM title)\n * @returns true if anything was updated\n */\nexport function updateAriaLabels<T>(\n state: AriaState,\n rowsBodyEl: HTMLElement | null,\n config: GridConfig<T> | undefined,\n shellState: ShellState | undefined,\n): boolean {\n if (!rowsBodyEl) return false;\n\n let updated = false;\n\n // Determine aria-label: explicit config > shell title > nothing\n const ariaLabel = getEffectiveAriaLabel(config, shellState);\n\n // Update aria-label only if changed\n if (ariaLabel !== state.ariaLabel) {\n state.ariaLabel = ariaLabel;\n if (ariaLabel) {\n rowsBodyEl.setAttribute('aria-label', ariaLabel);\n } else {\n rowsBodyEl.removeAttribute('aria-label');\n }\n updated = true;\n }\n\n // Update aria-describedby only if changed\n const ariaDescribedBy = config?.gridAriaDescribedBy;\n if (ariaDescribedBy !== state.ariaDescribedBy) {\n state.ariaDescribedBy = ariaDescribedBy;\n if (ariaDescribedBy) {\n rowsBodyEl.setAttribute('aria-describedby', ariaDescribedBy);\n } else {\n rowsBodyEl.removeAttribute('aria-describedby');\n }\n updated = true;\n }\n\n return updated;\n}\n\n// #endregion\n\n// #region Live Announcements\n\n/**\n * Announce a message to screen readers via the grid's aria-live region.\n * Clears then sets text to ensure repeated messages are re-announced.\n *\n * @param gridEl - The grid host element (or any ancestor containing `.tbw-sr-only`)\n * @param message - The message to announce\n */\nexport function announce(gridEl: HTMLElement, message: string): void {\n const el = gridEl.querySelector?.('.tbw-sr-only');\n if (!el) return;\n // Clear first so identical consecutive messages are still announced\n el.textContent = '';\n requestAnimationFrame(() => {\n el.textContent = message;\n });\n}\n\n// #endregion\n","import type { ShellState } from './internal/shell';\nimport type { RowPosition } from './internal/virtualization';\nimport type { PluginQuery } from './plugin/base-plugin';\nimport type { AfterCellRenderContext, AfterRowRenderContext, CellMouseEvent } from './plugin/types';\n\n/**\n * Position entry for a single row in the position cache.\n * Part of variable row height virtualization.\n *\n * Re-exported from position-cache.ts for public API consistency.\n *\n * @see VirtualState.positionCache\n * @category Plugin Development\n */\nexport type RowPositionEntry = RowPosition;\n\n// #region DataGridElement Interface\n/**\n * The compiled web component interface for DataGrid.\n *\n * This interface represents the `<tbw-grid>` custom element, combining\n * the public grid API with standard HTMLElement functionality.\n *\n * @example\n * ```typescript\n * // Query existing grid with type safety\n * import { queryGrid } from '@toolbox-web/grid';\n * const grid = queryGrid<Employee>('tbw-grid');\n * grid.rows = employees;\n * grid.on('cell-click', (detail) => console.log(detail));\n *\n * // Create grid programmatically\n * import { createGrid } from '@toolbox-web/grid';\n * const grid = createGrid<Employee>({\n * columns: [{ field: 'name' }, { field: 'email' }],\n * });\n * document.body.appendChild(grid);\n * ```\n *\n * @see {@link PublicGrid} for the public API methods and properties\n * @see {@link createGrid} for typed grid creation\n * @see {@link queryGrid} for typed grid querying\n */\nexport interface DataGridElement extends PublicGrid, HTMLElement {}\n// #endregion\n\n// #region ScrollToRowOptions\n/**\n * Options for the {@link PublicGrid.scrollToRow} method.\n *\n * @group Focus & Navigation\n *\n * @example\n * ```typescript\n * grid.scrollToRow(42, { align: 'center', behavior: 'smooth' });\n * ```\n */\nexport interface ScrollToRowOptions {\n /**\n * Where to position the row in the viewport.\n *\n * - `'start'` — top of the viewport\n * - `'center'` — vertically centered\n * - `'end'` — bottom of the viewport\n * - `'nearest'` — scroll only if the row is outside the viewport (default)\n *\n * @defaultValue `'nearest'`\n */\n align?: 'start' | 'center' | 'end' | 'nearest';\n /**\n * Scroll behavior.\n *\n * - `'instant'` — jump immediately (default)\n * - `'smooth'` — animate the scroll\n *\n * @defaultValue `'instant'`\n */\n behavior?: 'smooth' | 'instant';\n}\n// #endregion\n\n// #region PublicGrid Interface\n/**\n * Public API interface for DataGrid component.\n *\n * **Property Getters vs Setters:**\n *\n * Property getters return the EFFECTIVE (resolved) value after merging all config sources.\n * This is the \"current situation\" - what consumers and plugins need to know.\n *\n * Property setters accept input values which are merged into the effective config.\n * Multiple sources can contribute (gridConfig, columns prop, light DOM, individual props).\n *\n * For example:\n * - `grid.fitMode` returns the resolved fitMode (e.g., 'stretch' even if you set undefined)\n * - `grid.columns` returns the effective columns after merging\n * - `grid.gridConfig` returns the full effective config\n */\nexport interface PublicGrid<T = any> {\n /**\n * Full config object. Setter merges with other inputs per precedence rules.\n * Getter returns the effective (resolved) config.\n */\n gridConfig?: GridConfig<T>;\n /**\n * Column definitions.\n * Getter returns effective columns (after merging config, light DOM, inference).\n */\n columns?: ColumnConfig<T>[];\n /** Current row data (after plugin processing like grouping, filtering). */\n rows?: T[];\n /** Insert a row at a visible index, bypassing the sort/filter pipeline. Auto-animates by default. */\n insertRow?(index: number, row: T, animate?: boolean): Promise<void>;\n /** Remove a row at a visible index, bypassing the sort/filter pipeline. Auto-animates by default. */\n removeRow?(index: number, animate?: boolean): Promise<T | undefined>;\n /** Apply a batch of add/update/remove mutations in a single render cycle. */\n applyTransaction?(transaction: RowTransaction<T>, animate?: boolean): Promise<TransactionResult<T>>;\n /** Batch-friendly version — merges rapid calls within a single animation frame. */\n applyTransactionAsync?(transaction: RowTransaction<T>): Promise<TransactionResult<T>>;\n /** Resolves once the component has finished initial work (layout, inference). */\n ready?: () => Promise<void>;\n /** Force a layout / measurement pass (e.g. after container resize). */\n forceLayout?: () => Promise<void>;\n /** Return effective resolved config (after inference & precedence). */\n getConfig?: () => Promise<Readonly<GridConfig<T>>>;\n /** Toggle expansion state of a group row by its generated key. */\n toggleGroup?: (key: string) => Promise<void>;\n\n // Custom Styles API\n /**\n * Register custom CSS styles to be injected into the grid.\n * Use this to style custom cell renderers, editors, or detail panels.\n * @param id - Unique identifier for the style block (for removal/updates)\n * @param css - CSS string to inject\n */\n registerStyles?: (id: string, css: string) => void;\n /**\n * Remove previously registered custom styles.\n * @param id - The ID used when registering the styles\n */\n unregisterStyles?: (id: string) => void;\n /**\n * Get list of registered custom style IDs.\n */\n getRegisteredStyles?: () => string[];\n\n // Plugin API\n /**\n * Get a plugin instance by its class.\n *\n * **Prefer {@link getPluginByName}** — it avoids importing the plugin class\n * and returns the actual registered instance with full type narrowing.\n *\n * @example\n * ```typescript\n * // Preferred: by name\n * const selection = grid.getPluginByName('selection');\n *\n * // Alternative: by class\n * const selection = grid.getPlugin(SelectionPlugin);\n * if (selection) {\n * selection.selectAll();\n * }\n * ```\n */\n getPlugin?<P>(PluginClass: new (...args: any[]) => P): P | undefined;\n /**\n * Get a plugin instance by its name.\n *\n * When a plugin augments the `PluginNameMap` interface, the return\n * type is narrowed automatically:\n *\n * ```typescript\n * const editing = grid.getPluginByName('editing');\n * editing?.beginBulkEdit(0); // ✅ typed as EditingPlugin\n * ```\n *\n * For unknown names the return type falls back to `GridPlugin | undefined`.\n */\n getPluginByName?<K extends string>(\n name: K,\n ): (K extends keyof PluginNameMap ? PluginNameMap[K] : GridPlugin) | undefined;\n\n // Shell API\n /**\n * Re-render the shell header (title, column groups, toolbar).\n * Call this after dynamically adding/removing tool panels or toolbar buttons.\n */\n refreshShellHeader?(): void;\n /**\n * Register a custom tool panel in the sidebar.\n *\n * @example\n * ```typescript\n * grid.registerToolPanel({\n * id: 'analytics',\n * title: 'Analytics',\n * icon: '📊',\n * render: (container) => {\n * container.innerHTML = '<div>Charts here...</div>';\n * }\n * });\n * ```\n */\n registerToolPanel?(panel: ToolPanelDefinition): void;\n /**\n * Unregister a previously registered tool panel.\n */\n unregisterToolPanel?(panelId: string): void;\n /**\n * Open the tool panel sidebar.\n */\n openToolPanel?(): void;\n /**\n * Close the tool panel sidebar.\n */\n closeToolPanel?(): void;\n /**\n * Toggle the tool panel sidebar open or closed.\n */\n toggleToolPanel?(): void;\n /**\n * Toggle an accordion section expanded or collapsed within the tool panel.\n * @param sectionId - The ID of the section to toggle\n */\n toggleToolPanelSection?(sectionId: string): void;\n\n // State Persistence API\n /**\n * Get the current column state including order, width, visibility, and sort.\n * Use for persisting user preferences to localStorage or a backend.\n *\n * @example\n * ```typescript\n * const state = grid.getColumnState();\n * localStorage.setItem('gridState', JSON.stringify(state));\n * ```\n */\n getColumnState?(): GridColumnState;\n /**\n * Set/restore the column state.\n * Can be set before or after grid initialization.\n *\n * @example\n * ```typescript\n * const saved = localStorage.getItem('gridState');\n * if (saved) {\n * grid.columnState = JSON.parse(saved);\n * }\n * ```\n */\n columnState?: GridColumnState;\n\n // Loading API\n /**\n * Whether the grid is currently in a loading state.\n * When true, displays a loading overlay with spinner.\n *\n * Can also be set via the `loading` HTML attribute.\n *\n * @example\n * ```typescript\n * // Show loading overlay\n * grid.loading = true;\n * const data = await fetchData();\n * grid.rows = data;\n * grid.loading = false;\n * ```\n */\n loading?: boolean;\n\n /**\n * Set loading state for a specific row.\n * Displays a small spinner indicator on the row.\n *\n * Use when persisting row data or performing row-level async operations.\n *\n * @param rowId - The row's unique identifier (from getRowId)\n * @param loading - Whether the row is loading\n *\n * @example\n * ```typescript\n * // Show loading while saving row\n * grid.setRowLoading('emp-123', true);\n * await saveRow(row);\n * grid.setRowLoading('emp-123', false);\n * ```\n */\n setRowLoading?(rowId: string, loading: boolean): void;\n\n /**\n * Set loading state for a specific cell.\n * Displays a small spinner indicator on the cell.\n *\n * Use when performing cell-level async operations (e.g., validation, lookup).\n *\n * @param rowId - The row's unique identifier (from getRowId)\n * @param field - The column field\n * @param loading - Whether the cell is loading\n *\n * @example\n * ```typescript\n * // Show loading while validating cell\n * grid.setCellLoading('emp-123', 'email', true);\n * const isValid = await validateEmail(email);\n * grid.setCellLoading('emp-123', 'email', false);\n * ```\n */\n setCellLoading?(rowId: string, field: string, loading: boolean): void;\n\n /**\n * Check if a row is currently in loading state.\n * @param rowId - The row's unique identifier\n */\n isRowLoading?(rowId: string): boolean;\n\n /**\n * Check if a cell is currently in loading state.\n * @param rowId - The row's unique identifier\n * @param field - The column field\n */\n isCellLoading?(rowId: string, field: string): boolean;\n\n /**\n * Clear all row and cell loading states.\n */\n clearAllLoading?(): void;\n\n // Focus Management API\n /**\n * Register an external DOM element as a logical focus container of this grid.\n *\n * Focus moving into a registered container is treated as if it stayed inside\n * the grid: `data-has-focus` is preserved, click-outside commit is suppressed,\n * and the editing focus trap (when enabled) won't reclaim focus.\n *\n * Typical use case: overlay panels (datepickers, dropdowns, autocompletes)\n * that render at `<body>` level to escape grid overflow clipping.\n *\n * @param el - The external element to register\n *\n * @example\n * ```typescript\n * const overlay = document.createElement('div');\n * document.body.appendChild(overlay);\n *\n * // Tell the grid this overlay is \"part of\" the grid\n * grid.registerExternalFocusContainer(overlay);\n *\n * // Later, when overlay is removed\n * grid.unregisterExternalFocusContainer(overlay);\n * ```\n */\n registerExternalFocusContainer?(el: Element): void;\n\n /**\n * Unregister a previously registered external focus container.\n *\n * @param el - The element to unregister\n */\n unregisterExternalFocusContainer?(el: Element): void;\n\n /**\n * Check whether focus is logically inside this grid.\n *\n * Returns `true` when `document.activeElement` (or the given node) is\n * inside the grid's own DOM **or** inside any element registered via\n * {@link registerExternalFocusContainer}.\n *\n * @param node - Optional node to test. Defaults to `document.activeElement`.\n *\n * @example\n * ```typescript\n * if (grid.containsFocus()) {\n * console.log('Grid or one of its overlays has focus');\n * }\n * ```\n */\n containsFocus?(node?: Node | null): boolean;\n\n // Focus & Navigation API\n /**\n * Move focus to a specific cell.\n *\n * @param rowIndex - Row index (0-based, in the current processed row array)\n * @param column - Column index (0-based into visible columns) or field name\n */\n focusCell?(rowIndex: number, column: number | string): void;\n\n /**\n * The currently focused cell position, or `null` if no rows are loaded.\n */\n readonly focusedCell?: { rowIndex: number; colIndex: number; field: string } | null;\n\n /**\n * Scroll to make a row visible by its index.\n *\n * @param rowIndex - Row index (0-based, in the current processed row array)\n * @param options - Scroll alignment and behavior\n */\n scrollToRow?(rowIndex: number, options?: ScrollToRowOptions): void;\n\n /**\n * Scroll to make a row visible by its unique ID.\n *\n * @param rowId - The row's unique identifier (from {@link GridConfig.getRowId | getRowId})\n * @param options - Scroll alignment and behavior\n */\n scrollToRowById?(rowId: string, options?: ScrollToRowOptions): void;\n}\n// #endregion\n\n// #region InternalGrid Interface\n/**\n * Internal-only augmented interface for DataGrid component.\n *\n * Member prefixes indicate accessibility:\n * - `_underscore` = protected members - private outside core, accessible to plugins. Marked with @internal.\n * - `__doubleUnderscore` = deeply internal members - private outside core, only for internal functions.\n *\n * @category Plugin Development\n */\nexport interface InternalGrid<T = any> extends PublicGrid<T>, GridConfig<T> {\n // Element methods available because DataGridElement extends HTMLElement\n /** The element's `id` attribute. Available because DataGridElement extends HTMLElement. */\n id: string;\n /**\n * The grid's host HTMLElement (`this`). Use instead of casting `grid as unknown as HTMLElement`.\n * @internal\n */\n readonly _hostElement: HTMLElement;\n querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;\n querySelector<E extends Element = Element>(selectors: string): E | null;\n querySelectorAll<K extends keyof HTMLElementTagNameMap>(selectors: K): NodeListOf<HTMLElementTagNameMap[K]>;\n querySelectorAll<E extends Element = Element>(selectors: string): NodeListOf<E>;\n _rows: T[];\n _columns: ColumnInternal<T>[];\n /** Visible columns only (excludes hidden). Use for rendering. */\n _visibleColumns: ColumnInternal<T>[];\n _headerRowEl: HTMLElement;\n _bodyEl: HTMLElement;\n _rowPool: RowElementInternal[];\n _resizeController: ResizeController;\n _sortState: { field: string; direction: 1 | -1 } | null;\n /** Original unfiltered/unprocessed rows. @internal */\n sourceRows: T[];\n /** Framework adapter instance (set by Grid directives). @internal */\n __frameworkAdapter?: FrameworkAdapter;\n __originalOrder: T[];\n __rowRenderEpoch: number;\n __didInitialAutoSize?: boolean;\n __lightDomColumnsCache?: ColumnInternal[];\n __originalColumnNodes?: HTMLElement[];\n /** Cell display value cache. @internal */\n __cellDisplayCache?: Map<number, string[]>;\n /** Cache epoch for cell display values. @internal */\n __cellCacheEpoch?: number;\n /** Cached header row count for virtualization. @internal */\n __cachedHeaderRowCount?: number;\n /** Cached flag for whether grid has special columns (custom renderers, etc.). @internal */\n __hasSpecialColumns?: boolean;\n /** Cached flag for whether any plugin has renderRow hooks. @internal */\n __hasRenderRowPlugins?: boolean;\n _gridTemplate: string;\n _virtualization: VirtualState;\n _focusRow: number;\n _focusCol: number;\n /** Currently active edit row index. Injected by EditingPlugin. @internal */\n _activeEditRows?: number;\n /** Whether the grid is in 'grid' editing mode (all rows editable). Injected by EditingPlugin. @internal */\n _isGridEditMode?: boolean;\n /** Snapshots of row data before editing. Injected by EditingPlugin. @internal */\n _rowEditSnapshots?: Map<number, T>;\n /** Get all changed rows. Injected by EditingPlugin. */\n changedRows?: T[];\n /** Get IDs of all changed rows. Injected by EditingPlugin. */\n changedRowIds?: string[];\n /** Internal Set for O(1) lookup in the render hot path. Injected by EditingPlugin. @internal */\n _changedRowIdSet?: ReadonlySet<string>;\n effectiveConfig?: GridConfig<T>;\n findHeaderRow?: () => HTMLElement;\n refreshVirtualWindow: (full: boolean, skipAfterRender?: boolean) => boolean;\n /** @internal Trigger a COLUMNS-phase re-render. */\n refreshColumns?: () => void;\n updateTemplate?: () => void;\n findRenderedRowElement?: (rowIndex: number) => HTMLElement | null;\n /** Get a row by its ID. Implemented in grid.ts */\n getRow?: (id: string) => T | undefined;\n /** Get a row and its current index by ID. Returns undefined if not found. @internal */\n _getRowEntry: (id: string) => { row: T; index: number } | undefined;\n /** Get the unique ID for a row. Implemented in grid.ts */\n getRowId?: (row: T) => string;\n /** Update a row by ID. Implemented in grid.ts */\n updateRow?: (id: string, changes: Partial<T>, source?: UpdateSource) => void;\n /** Animate a single row. Returns Promise that resolves when animation completes. Implemented in grid.ts */\n animateRow?: (rowIndex: number, type: RowAnimationType) => Promise<boolean>;\n /** Animate multiple rows. Returns Promise that resolves when all animations complete. Implemented in grid.ts */\n animateRows?: (rowIndices: number[], type: RowAnimationType) => Promise<number>;\n /** Animate a row by its ID. Returns Promise that resolves when animation completes. Implemented in grid.ts */\n animateRowById?: (rowId: string, type: RowAnimationType) => Promise<boolean>;\n /** Begin bulk edit on a row. Injected by EditingPlugin. */\n beginBulkEdit?: (rowIndex: number) => void;\n /** Commit active row edit. Injected by EditingPlugin. */\n commitActiveRowEdit?: () => void;\n /** Dispatch cell click to plugin system, returns true if handled */\n _dispatchCellClick?: (event: MouseEvent, rowIndex: number, colIndex: number, cellEl: HTMLElement) => boolean;\n /** Dispatch row click to plugin system, returns true if handled */\n _dispatchRowClick?: (event: MouseEvent, rowIndex: number, row: any, rowEl: HTMLElement) => boolean;\n /** Dispatch header click to plugin system, returns true if handled */\n _dispatchHeaderClick?: (event: MouseEvent | KeyboardEvent, col: ColumnConfig, headerEl: HTMLElement) => boolean;\n /** Dispatch keydown to plugin system, returns true if handled */\n _dispatchKeyDown?: (event: KeyboardEvent) => boolean;\n /** Dispatch cell mouse events for drag operations. Returns true if any plugin started a drag. */\n _dispatchCellMouseDown?: (event: CellMouseEvent) => boolean;\n /** Dispatch cell mouse move during drag. */\n _dispatchCellMouseMove?: (event: CellMouseEvent) => void;\n /** Dispatch cell mouse up to end drag. */\n _dispatchCellMouseUp?: (event: CellMouseEvent) => void;\n /** Call afterCellRender hook on all plugins. Called from rows.ts after each cell is rendered. @internal */\n _afterCellRender?: (context: AfterCellRenderContext<T>) => void;\n /** Check if any plugin has registered an afterCellRender hook. Used to skip hook call for performance. @internal */\n _hasAfterCellRenderHook?: () => boolean;\n /** Call afterRowRender hook on all plugins. Called from rows.ts after each row is rendered. @internal */\n _afterRowRender?: (context: AfterRowRenderContext<T>) => void;\n /** Check if any plugin has registered an afterRowRender hook. Used to skip hook call for performance. @internal */\n _hasAfterRowRenderHook?: () => boolean;\n /** Get horizontal scroll boundary offsets from plugins */\n _getHorizontalScrollOffsets?: (\n rowEl?: HTMLElement,\n focusedCell?: HTMLElement,\n ) => { left: number; right: number; skipScroll?: boolean };\n /** Query all plugins with a generic query and collect responses */\n queryPlugins?: <T>(query: PluginQuery) => T[];\n /** Request emission of column-state-change event (debounced) */\n requestStateChange?: () => void;\n\n // Methods exposed for extracted managers (VirtualizationManager, FocusManager, RowManager, RenderScheduler)\n /** @internal */ _renderVisibleRows(start: number, end: number, epoch?: number): void;\n /** @internal */ _updateAriaCounts(totalRows: number, totalCols: number): void;\n /** @internal */ _requestSchedulerPhase(phase: number, source: string): void;\n /** @internal */ _rebuildRowIdMap(): void;\n /** @internal */ _emitDataChange(): void;\n /** @internal */ _getPluginExtraHeight(): number;\n /** @internal */ _getPluginRowHeight(row: T, index: number): number | undefined;\n /** @internal */ _getPluginExtraHeightBefore(start: number): number;\n /** @internal */ _adjustPluginVirtualStart(start: number, scrollTop: number, rowHeight: number): number | undefined;\n /** @internal */ _afterPluginRender(): void;\n /** @internal */ _emitPluginEvent(event: string, detail: unknown): void;\n\n // Scheduler pipeline callbacks\n /** @internal */ _schedulerMergeConfig(): void;\n /** @internal */ _schedulerProcessColumns(): void;\n /** @internal */ _schedulerProcessRows(): void;\n /** @internal */ _schedulerRenderHeader(): void;\n /** @internal */ _schedulerUpdateTemplate(): void;\n /** @internal */ _schedulerAfterRender(): void;\n /** @internal */ readonly _schedulerIsConnected: boolean;\n\n // Shell controller & config manager support\n /** @internal The render root element for DOM queries. */\n readonly _renderRoot: Element;\n /** @internal Emit a custom event from the grid. */\n _emit(eventName: string, detail: unknown): void;\n /** @internal Get accordion expand/collapse icons from effective config. */\n readonly _accordionIcons: { expand: IconValue; collapse: IconValue };\n /** @internal Shell state for config manager shell merging. */\n readonly _shellState: ShellState;\n /** @internal Clear the row pool and body element. */\n _clearRowPool(): void;\n /** @internal Run grid setup (DOM rebuild). */\n _setup(): void;\n /** @internal Apply animation configuration to host element. */\n _applyAnimationConfig(config: GridConfig): void;\n}\n\n/**\n * Grid reference type combining InternalGrid with HTMLElement.\n * Used by extracted managers that need both internal grid state and DOM APIs.\n * @internal\n */\nexport type GridHost<T = any> = InternalGrid<T> & HTMLElement;\n// #endregion\n\n// #region Column Types\n/**\n * Built-in primitive column types with automatic formatting and editing support.\n *\n * - `'string'` - Text content, default text input editor\n * - `'number'` - Numeric content, right-aligned, number input editor\n * - `'date'` - Date content, formatted display, date picker editor\n * - `'boolean'` - True/false, rendered as checkbox\n * - `'select'` - Dropdown selection from `options` array\n *\n * @example\n * ```typescript\n * columns: [\n * { field: 'name', type: 'string' },\n * { field: 'age', type: 'number' },\n * { field: 'hireDate', type: 'date' },\n * { field: 'active', type: 'boolean' },\n * { field: 'department', type: 'select', options: [\n * { label: 'Engineering', value: 'eng' },\n * { label: 'Sales', value: 'sales' },\n * ]},\n * ]\n * ```\n *\n * @see {@link ColumnType} for custom type support\n * @see {@link TypeDefault} for type-level defaults\n */\nexport type PrimitiveColumnType = 'number' | 'string' | 'date' | 'boolean' | 'select';\n\n/**\n * Column type - built-in primitives or custom type strings.\n *\n * Use built-in types for automatic formatting, or define custom types\n * (e.g., 'currency', 'country') with type-level defaults via `typeDefaults`.\n *\n * @example\n * ```typescript\n * // Built-in types\n * { field: 'name', type: 'string' }\n * { field: 'salary', type: 'number' }\n *\n * // Custom types with defaults\n * grid.gridConfig = {\n * columns: [\n * { field: 'salary', type: 'currency' },\n * { field: 'birthCountry', type: 'country' },\n * ],\n * typeDefaults: {\n * currency: {\n * format: (v) => `$${Number(v).toFixed(2)}`,\n * },\n * country: {\n * renderer: (ctx) => `🌍 ${ctx.value}`,\n * },\n * },\n * };\n * ```\n *\n * @see {@link PrimitiveColumnType} for built-in types\n * @see {@link TypeDefault} for defining custom type defaults\n */\nexport type ColumnType = PrimitiveColumnType | (string & {});\n// #endregion\n\n// #region TypeDefault Interface\n/**\n * Type-level defaults for formatters and renderers.\n * Applied to all columns of a given type unless overridden at column level.\n *\n * Note: `editor` and `editorParams` are added via module augmentation when\n * EditingPlugin is imported. `filterPanelRenderer` is added by FilteringPlugin.\n *\n * @example\n * ```typescript\n * typeDefaults: {\n * currency: {\n * format: (value) => new Intl.NumberFormat('en-US', {\n * style: 'currency',\n * currency: 'USD',\n * }).format(value as number),\n * },\n * country: {\n * renderer: (ctx) => {\n * const span = document.createElement('span');\n * span.innerHTML = `<img src=\"/flags/${ctx.value}.svg\" /> ${ctx.value}`;\n * return span;\n * }\n * }\n * }\n * ```\n *\n * @see {@link ColumnViewRenderer} for custom renderer function signature\n * @see {@link ColumnType} for type strings that can have defaults\n * @see {@link GridConfig.typeDefaults} for registering type defaults\n */\nexport interface TypeDefault<TRow = unknown> {\n /**\n * Default formatter for all columns of this type.\n *\n * Transforms the raw cell value into a display string. Use when you need\n * consistent formatting across columns without custom DOM (e.g., currency,\n * percentages, dates with specific locale).\n *\n * **Resolution Priority**: Column `format` → Type `format` → Built-in\n *\n * @example\n * ```typescript\n * typeDefaults: {\n * currency: {\n * format: (value) => new Intl.NumberFormat('en-US', {\n * style: 'currency',\n * currency: 'USD',\n * }).format(value as number),\n * },\n * percentage: {\n * format: (value) => `${(value as number * 100).toFixed(1)}%`,\n * }\n * }\n * ```\n */\n format?: (value: unknown, row: TRow) => string;\n\n /**\n * Default renderer for all columns of this type.\n *\n * Creates custom DOM for the cell content. Use when you need more than\n * text formatting (e.g., icons, badges, interactive elements).\n *\n * **Resolution Priority**: Column `renderer` → Type `renderer` → App-level (adapter) → Built-in\n *\n * @example\n * ```typescript\n * typeDefaults: {\n * status: {\n * renderer: (ctx) => {\n * const badge = document.createElement('span');\n * badge.className = `badge badge-${ctx.value}`;\n * badge.textContent = ctx.value as string;\n * return badge;\n * }\n * },\n * country: {\n * renderer: (ctx) => {\n * const span = document.createElement('span');\n * span.innerHTML = `<img src=\"/flags/${ctx.value}.svg\" /> ${ctx.value}`;\n * return span;\n * }\n * }\n * }\n * ```\n */\n renderer?: ColumnViewRenderer<TRow, unknown>;\n}\n// #endregion\n\n// #region BaseColumnConfig Interface\n/**\n * Base contract for a column configuration.\n *\n * Defines the fundamental properties all columns share. Extended by {@link ColumnConfig}\n * with additional features like custom renderers and grouping.\n *\n * @example\n * ```typescript\n * // Basic column with common properties\n * const columns: BaseColumnConfig<Employee>[] = [\n * {\n * field: 'name',\n * header: 'Full Name',\n * sortable: true,\n * resizable: true,\n * },\n * {\n * field: 'salary',\n * type: 'number',\n * width: 120,\n * format: (value) => `$${value.toLocaleString()}`,\n * sortComparator: (a, b) => a - b,\n * },\n * {\n * field: 'department',\n * type: 'select',\n * options: [\n * { label: 'Engineering', value: 'eng' },\n * { label: 'Sales', value: 'sales' },\n * ],\n * },\n * ];\n * ```\n *\n * @see {@link ColumnConfig} for full column configuration with renderers\n * @see {@link ColumnType} for type options\n */\nexport interface BaseColumnConfig<TRow = any, TValue = any> {\n /** Unique field key referencing property in row objects */\n field: keyof TRow & string;\n /** Visible header label; defaults to capitalized field */\n header?: string;\n /**\n * Column data type.\n *\n * Built-in types: `'string'`, `'number'`, `'date'`, `'boolean'`, `'select'`\n *\n * Custom types (e.g., `'currency'`, `'country'`) can have type-level defaults\n * via `gridConfig.typeDefaults` or framework adapter registries.\n *\n * @default Inferred from first row data\n */\n type?: ColumnType;\n /** Column width in pixels; fixed size (no flexibility) */\n width?: string | number;\n /** Minimum column width in pixels (stretch mode only); when set, column uses minmax(minWidth, 1fr) */\n minWidth?: number;\n /** Whether column can be sorted */\n sortable?: boolean;\n /** Whether column can be resized by user */\n resizable?: boolean;\n /** Optional custom comparator for sorting (a,b) -> number */\n sortComparator?: (a: TValue, b: TValue, rowA: TRow, rowB: TRow) => number;\n /** For select type - available options */\n options?: Array<{ label: string; value: unknown }> | (() => Array<{ label: string; value: unknown }>);\n /** For select - allow multi select */\n multi?: boolean;\n /**\n * Formats the raw cell value into a display string.\n *\n * Used both for **cell rendering** and the **built-in filter panel**:\n * - In cells, the formatted value replaces the raw value as text content.\n * - In the filter panel (set filter), checkbox labels show the formatted value\n * instead of the raw value, and search matches against the formatted text.\n *\n * The `row` parameter is available during cell rendering but is `undefined`\n * when called from the filter panel (standalone value formatting). Avoid\n * accessing `row` properties in format functions intended for filter display.\n *\n * @example\n * ```typescript\n * // Currency formatter — works in both cells and filter panel\n * {\n * field: 'price',\n * format: (value) => `$${Number(value).toFixed(2)}`,\n * }\n *\n * // ID-to-name lookup — filter panel shows names, not IDs\n * {\n * field: 'departmentId',\n * format: (value) => departmentMap.get(value as string) ?? String(value),\n * }\n * ```\n */\n format?: (value: TValue, row: TRow) => string;\n /** Arbitrary extra metadata */\n meta?: Record<string, unknown>;\n}\n// #endregion\n\n// #region ColumnConfig Interface\n/**\n * Full column configuration including custom renderers, editors, and grouping metadata.\n *\n * Extends {@link BaseColumnConfig} with additional features for customizing\n * how cells are displayed and edited.\n *\n * @example\n * ```typescript\n * const columns: ColumnConfig<Employee>[] = [\n * // Basic sortable column\n * { field: 'id', header: 'ID', width: 60, sortable: true },\n *\n * // Column with custom renderer\n * {\n * field: 'name',\n * header: 'Employee',\n * renderer: (ctx) => {\n * const div = document.createElement('div');\n * div.innerHTML = `<img src=\"${ctx.row.avatar}\" /><span>${ctx.value}</span>`;\n * return div;\n * },\n * },\n *\n * // Column with custom header\n * {\n * field: 'email',\n * headerLabelRenderer: (ctx) => `${ctx.value} 📧`,\n * },\n *\n * // Editable column (requires EditingPlugin)\n * {\n * field: 'status',\n * editable: true,\n * editor: (ctx) => {\n * const select = document.createElement('select');\n * // ... editor implementation\n * return select;\n * },\n * },\n *\n * // Hidden column (can be shown via VisibilityPlugin)\n * { field: 'internalNotes', hidden: true },\n * ];\n * ```\n *\n * @see {@link BaseColumnConfig} for basic column properties\n * @see {@link ColumnViewRenderer} for custom cell renderers\n * @see {@link ColumnEditorSpec} for custom cell editors\n * @see {@link HeaderRenderer} for custom header renderers\n */\nexport interface ColumnConfig<TRow = any> extends BaseColumnConfig<TRow, any> {\n /**\n * Optional custom cell renderer function. Alias for `viewRenderer`.\n * Can return an HTMLElement, a Node, or an HTML string (which will be sanitized).\n *\n * @example\n * ```typescript\n * // Simple string template\n * renderer: (ctx) => `<span class=\"badge\">${ctx.value}</span>`\n *\n * // DOM element\n * renderer: (ctx) => {\n * const el = document.createElement('span');\n * el.textContent = ctx.value;\n * return el;\n * }\n * ```\n */\n renderer?: ColumnViewRenderer<TRow, any>;\n /** Optional custom view renderer used instead of default text rendering */\n viewRenderer?: ColumnViewRenderer<TRow, any>;\n /** External view spec (lets host app mount any framework component) */\n externalView?: {\n component: unknown;\n props?: Record<string, unknown>;\n mount?: (options: {\n placeholder: HTMLElement;\n context: CellRenderContext<TRow, unknown>;\n spec: unknown;\n }) => void | { dispose?: () => void };\n };\n /** Whether the column is initially hidden */\n hidden?: boolean;\n /** Prevent this column from being hidden programmatically */\n lockVisible?: boolean;\n /**\n * Dynamic CSS class(es) for cells in this column.\n * Called for each cell during rendering. Return class names to add to the cell element.\n *\n * @example\n * ```typescript\n * // Highlight negative values\n * cellClass: (value, row, column) => value < 0 ? ['negative', 'text-red'] : []\n *\n * // Status-based styling\n * cellClass: (value) => [`status-${value}`]\n *\n * // Single class as string\n * cellClass: (value) => value < 0 ? 'negative' : ''\n * ```\n */\n cellClass?: (value: unknown, row: TRow, column: ColumnConfig<TRow>) => string | string[];\n\n /**\n * Custom header label renderer. Customize the label content while the grid\n * handles sort icons, filter buttons, resize handles, and click interactions.\n *\n * Use this for simple customizations like adding icons, badges, or units.\n *\n * @example\n * ```typescript\n * // Add required field indicator\n * headerLabelRenderer: (ctx) => `${ctx.value} <span class=\"required\">*</span>`\n *\n * // Add unit to header\n * headerLabelRenderer: (ctx) => {\n * const span = document.createElement('span');\n * span.innerHTML = `${ctx.value}<br/><small>(kg)</small>`;\n * return span;\n * }\n * ```\n */\n headerLabelRenderer?: HeaderLabelRenderer<TRow>;\n\n /**\n * Custom header cell renderer. Complete control over the entire header cell.\n * Resize handles are added automatically for resizable columns.\n *\n * The context provides helper functions to include standard elements:\n * - `renderSortIcon()` - Returns sort indicator element (null if not sortable)\n * - `renderFilterButton()` - Returns filter button (null if not filterable)\n *\n * **Precedence**: `headerRenderer` > `headerLabelRenderer` > `header` > `field`\n *\n * @example\n * ```typescript\n * headerRenderer: (ctx) => {\n * const div = document.createElement('div');\n * div.className = 'custom-header';\n * div.innerHTML = `<span>${ctx.value}</span>`;\n * const sortIcon = ctx.renderSortIcon();\n * if (sortIcon) div.appendChild(sortIcon);\n * return div;\n * }\n * ```\n */\n headerRenderer?: HeaderRenderer<TRow>;\n}\n// #endregion\n\n// #region ColumnConfigMap Type\n/**\n * Array of column configurations.\n * Convenience type alias for `ColumnConfig<TRow>[]`.\n *\n * @example\n * ```typescript\n * const columns: ColumnConfigMap<Employee> = [\n * { field: 'name', header: 'Full Name', sortable: true },\n * { field: 'email', header: 'Email Address' },\n * { field: 'department', type: 'select', options: deptOptions },\n * ];\n *\n * grid.columns = columns;\n * ```\n *\n * @see {@link ColumnConfig} for individual column options\n * @see {@link GridConfig.columns} for setting columns on the grid\n */\nexport type ColumnConfigMap<TRow = any> = ColumnConfig<TRow>[];\n// #endregion\n\n// #region Editor Types\n/**\n * Editor specification for inline cell editing.\n * Supports multiple formats for maximum flexibility.\n *\n * **Format Options:**\n * - `string` - Custom element tag name (e.g., 'my-date-picker')\n * - `function` - Factory function returning an editor element\n * - `object` - External component spec for framework integration\n *\n * @example\n * ```typescript\n * // 1. Custom element tag name\n * columns: [\n * { field: 'date', editor: 'my-date-picker' }\n * ]\n *\n * // 2. Factory function (full control)\n * columns: [\n * {\n * field: 'status',\n * editor: (ctx) => {\n * const select = document.createElement('select');\n * select.innerHTML = `\n * <option value=\"active\">Active</option>\n * <option value=\"inactive\">Inactive</option>\n * `;\n * select.value = ctx.value;\n * select.onchange = () => ctx.commit(select.value);\n * select.onkeydown = (e) => {\n * if (e.key === 'Escape') ctx.cancel();\n * };\n * return select;\n * }\n * }\n * ]\n *\n * // 3. External component (React, Angular, Vue)\n * columns: [\n * {\n * field: 'country',\n * editor: {\n * component: CountrySelect,\n * props: { showFlags: true }\n * }\n * }\n * ]\n * ```\n *\n * @see {@link ColumnEditorContext} for the context passed to factory functions\n */\nexport type ColumnEditorSpec<TRow = unknown, TValue = unknown> =\n | string // custom element tag name\n | ((context: ColumnEditorContext<TRow, TValue>) => HTMLElement | string)\n | {\n /** Arbitrary component reference (class, function, token) */\n component: unknown;\n /** Optional static props passed to mount */\n props?: Record<string, unknown>;\n /** Optional custom mount function; if provided we call it directly instead of emitting an event */\n mount?: (options: {\n placeholder: HTMLElement;\n context: ColumnEditorContext<TRow, TValue>;\n spec: unknown;\n }) => void | { dispose?: () => void };\n };\n\n/**\n * Context object provided to editor factories allowing mutation (commit/cancel) of a cell value.\n *\n * The `commit` and `cancel` functions control the editing lifecycle:\n * - Call `commit(newValue)` to save changes and exit edit mode\n * - Call `cancel()` to discard changes and exit edit mode\n *\n * @example\n * ```typescript\n * const myEditor: ColumnEditorSpec = (ctx: ColumnEditorContext) => {\n * const input = document.createElement('input');\n * input.value = ctx.value;\n * input.className = 'my-editor';\n *\n * // Save on Enter, cancel on Escape\n * input.onkeydown = (e) => {\n * if (e.key === 'Enter') {\n * ctx.commit(input.value);\n * } else if (e.key === 'Escape') {\n * ctx.cancel();\n * }\n * };\n *\n * // Access row data for validation\n * if (ctx.row.locked) {\n * input.disabled = true;\n * }\n *\n * return input;\n * };\n * ```\n *\n * @see {@link ColumnEditorSpec} for editor specification options\n */\nexport interface ColumnEditorContext<TRow = any, TValue = any> {\n /** Underlying full row object for the active edit. */\n row: TRow;\n /** Current cell value (mutable only via commit). */\n value: TValue;\n /** Field name being edited. */\n field: keyof TRow & string;\n /** Column configuration reference. */\n column: ColumnConfig<TRow>;\n /**\n * Stable row identifier (from `getRowId`).\n * Empty string if no `getRowId` is configured.\n */\n rowId: string;\n /** Accept the edit; triggers change tracking + rerender. */\n commit: (newValue: TValue) => void;\n /** Abort edit without persisting changes. */\n cancel: () => void;\n /**\n * Update other fields in this row while the editor is open.\n * Changes are committed with source `'cascade'`, triggering\n * `cell-change` events and `onValueChange` pushes to sibling editors.\n *\n * Useful for editors that affect multiple fields (e.g., an address\n * lookup that sets city + zip + state).\n *\n * @example\n * ```typescript\n * // In a cell-commit listener:\n * grid.on('cell-commit', (detail) => {\n * if (detail.field === 'quantity') {\n * detail.updateRow({ total: detail.row.price * detail.value });\n * }\n * });\n * ```\n */\n updateRow: (changes: Partial<TRow>) => void;\n /**\n * Register a callback invoked when the cell's underlying value changes\n * while the editor is open (e.g., via `updateRow()` from another cell's commit).\n *\n * Built-in editors auto-update their input values. Custom/framework editors\n * should use this to stay in sync with external mutations.\n *\n * @example\n * ```typescript\n * const editor = (ctx: ColumnEditorContext) => {\n * const input = document.createElement('input');\n * input.value = String(ctx.value);\n * ctx.onValueChange?.((newValue) => {\n * input.value = String(newValue);\n * });\n * return input;\n * };\n * ```\n */\n onValueChange?: (callback: (newValue: TValue) => void) => void;\n}\n// #endregion\n\n// #region Renderer Types\n/**\n * Context passed to custom view renderers (pure display – no commit helpers).\n *\n * Used by `viewRenderer` and `renderer` column properties to create\n * custom cell content. Return a DOM node or HTML string.\n *\n * @example\n * ```typescript\n * // Status badge renderer\n * const statusRenderer: ColumnViewRenderer = (ctx: CellRenderContext) => {\n * const badge = document.createElement('span');\n * badge.className = `badge badge-${ctx.value}`;\n * badge.textContent = ctx.value;\n * return badge;\n * };\n *\n * // Progress bar using row data\n * const progressRenderer: ColumnViewRenderer = (ctx) => {\n * const bar = document.createElement('div');\n * bar.className = 'progress-bar';\n * bar.style.width = `${ctx.value}%`;\n * bar.title = `${ctx.row.taskName}: ${ctx.value}%`;\n * return bar;\n * };\n *\n * // Return HTML string (simpler, less performant)\n * const htmlRenderer: ColumnViewRenderer = (ctx) => {\n * return `<strong>${ctx.value}</strong>`;\n * };\n * ```\n *\n * @see {@link ColumnViewRenderer} for the renderer function signature\n */\nexport interface CellRenderContext<TRow = any, TValue = any> {\n /** Row object for the cell being rendered. */\n row: TRow;\n /** Value at field. */\n value: TValue;\n /** Field key. */\n field: keyof TRow & string;\n /** Column configuration reference. */\n column: ColumnConfig<TRow>;\n /**\n * The cell DOM element being rendered into.\n * Framework adapters can use this to cache per-cell state (e.g., React roots).\n * @internal\n */\n cellEl?: HTMLElement;\n}\n\n/**\n * Custom view renderer function for cell content.\n *\n * Returns one of:\n * - `Node` - DOM element to display in the cell\n * - `string` - HTML string (parsed and inserted)\n * - `void | null` - Use default text rendering\n *\n * @example\n * ```typescript\n * // DOM element (recommended for interactivity)\n * const avatarRenderer: ColumnViewRenderer<Employee> = (ctx) => {\n * const img = document.createElement('img');\n * img.src = ctx.row.avatarUrl;\n * img.alt = ctx.row.name;\n * img.className = 'avatar';\n * return img;\n * };\n *\n * // HTML string (simpler, good for static content)\n * const emailRenderer: ColumnViewRenderer = (ctx) => {\n * return `<a href=\"mailto:${ctx.value}\">${ctx.value}</a>`;\n * };\n *\n * // Conditional rendering\n * const conditionalRenderer: ColumnViewRenderer = (ctx) => {\n * if (!ctx.value) return null; // Use default\n * return `<em>${ctx.value}</em>`;\n * };\n * ```\n *\n * @see {@link CellRenderContext} for available context properties\n */\nexport type ColumnViewRenderer<TRow = unknown, TValue = unknown> = (\n ctx: CellRenderContext<TRow, TValue>,\n) => Node | string | void | null;\n// #endregion\n\n// #region Header Renderer Types\n/**\n * Context passed to `headerLabelRenderer` for customizing header label content.\n * The framework handles sort icons, filter buttons, resize handles, and click interactions.\n *\n * @example\n * ```typescript\n * headerLabelRenderer: (ctx) => {\n * const span = document.createElement('span');\n * span.innerHTML = `${ctx.value} <span class=\"required\">*</span>`;\n * return span;\n * }\n * ```\n */\nexport interface HeaderLabelContext<TRow = unknown> {\n /** Column configuration reference. */\n column: ColumnConfig<TRow>;\n /** The header text (from column.header or column.field). */\n value: string;\n}\n\n/**\n * Context passed to `headerRenderer` for complete control over header cell content.\n * When using this, you control the header content. Resize handles are added automatically\n * for resizable columns.\n *\n * @example\n * ```typescript\n * headerRenderer: (ctx) => {\n * const div = document.createElement('div');\n * div.className = 'custom-header';\n * div.innerHTML = `<span>${ctx.value}</span>`;\n * // Optionally include sort icon\n * const sortIcon = ctx.renderSortIcon();\n * if (sortIcon) div.appendChild(sortIcon);\n * return div;\n * }\n * ```\n */\nexport interface HeaderCellContext<TRow = unknown> {\n /** Column configuration reference. */\n column: ColumnConfig<TRow>;\n /** The header text (from column.header or column.field). */\n value: string;\n /** Current sort state for this column. */\n sortState: 'asc' | 'desc' | null;\n /** Whether the column has an active filter. */\n filterActive: boolean;\n /** The header cell DOM element being rendered into. */\n cellEl: HTMLElement;\n /**\n * Render the standard sort indicator icon.\n * Returns null if column is not sortable.\n */\n renderSortIcon: () => HTMLElement | null;\n /**\n * Render the standard filter button.\n * Returns null if FilteringPlugin is not active or column is not filterable.\n * Note: The actual button is added by FilteringPlugin's afterRender hook.\n */\n renderFilterButton: () => HTMLElement | null;\n}\n\n/**\n * Header label renderer function type.\n * Customize the label while framework handles sort icons, filter buttons, resize handles.\n *\n * Use this for simple label customizations without taking over the entire header.\n * The grid automatically appends sort icons, filter buttons, and resize handles.\n *\n * @example\n * ```typescript\n * // Add required indicator\n * const requiredHeader: HeaderLabelRenderer = (ctx) => {\n * return `${ctx.value} <span style=\"color: red;\">*</span>`;\n * };\n *\n * // Add unit suffix\n * const priceHeader: HeaderLabelRenderer = (ctx) => {\n * const span = document.createElement('span');\n * span.innerHTML = `${ctx.value} <small>(USD)</small>`;\n * return span;\n * };\n *\n * // Column config usage\n * columns: [\n * { field: 'name', headerLabelRenderer: requiredHeader },\n * { field: 'price', headerLabelRenderer: priceHeader },\n * ]\n * ```\n *\n * @see {@link HeaderLabelContext} for context properties\n * @see {@link HeaderRenderer} for full header control\n */\nexport type HeaderLabelRenderer<TRow = unknown> = (ctx: HeaderLabelContext<TRow>) => Node | string | void | null;\n\n/**\n * Header cell renderer function type.\n * Full control over header cell content. User is responsible for all content and interactions.\n *\n * When using this, you have complete control but must manually include\n * sort icons, filter buttons, and resize handles using the helper functions.\n *\n * @example\n * ```typescript\n * // Custom header with all standard elements\n * const customHeader: HeaderRenderer = (ctx) => {\n * const div = document.createElement('div');\n * div.className = 'custom-header';\n * div.innerHTML = `<span class=\"label\">${ctx.value}</span>`;\n *\n * // Add sort icon (returns null if not sortable)\n * const sortIcon = ctx.renderSortIcon();\n * if (sortIcon) div.appendChild(sortIcon);\n *\n * // Add filter button (returns null if not filterable)\n * const filterBtn = ctx.renderFilterButton();\n * if (filterBtn) div.appendChild(filterBtn);\n *\n * // Resize handles are added automatically for resizable columns\n * return div;\n * };\n *\n * // Minimal header (no sort/resize)\n * const minimalHeader: HeaderRenderer = (ctx) => {\n * return `<div class=\"minimal\">${ctx.value}</div>`;\n * };\n *\n * // Column config usage\n * columns: [\n * { field: 'name', headerRenderer: customHeader },\n * ]\n * ```\n *\n * @see {@link HeaderCellContext} for context properties and helper functions\n * @see {@link HeaderLabelRenderer} for simpler label-only customization\n */\nexport type HeaderRenderer<TRow = unknown> = (ctx: HeaderCellContext<TRow>) => Node | string | void | null;\n// #endregion\n\n// #region Framework Adapter Interface\n/**\n * Framework adapter interface for handling framework-specific component instantiation.\n * Allows framework libraries (Angular, React, Vue) to register handlers that convert\n * declarative light DOM elements into functional renderers/editors.\n *\n * @example\n * ```typescript\n * // In @toolbox-web/grid-angular\n * class AngularGridAdapter implements FrameworkAdapter {\n * canHandle(element: HTMLElement): boolean {\n * return element.tagName.startsWith('APP-');\n * }\n * createRenderer(element: HTMLElement): ColumnViewRenderer {\n * return (ctx) => {\n * // Angular-specific instantiation logic\n * const componentRef = createComponent(...);\n * componentRef.setInput('value', ctx.value);\n * return componentRef.location.nativeElement;\n * };\n * }\n * createEditor(element: HTMLElement): ColumnEditorSpec {\n * return (ctx) => {\n * // Angular-specific editor with commit/cancel\n * const componentRef = createComponent(...);\n * componentRef.setInput('value', ctx.value);\n * // Subscribe to commit/cancel outputs\n * return componentRef.location.nativeElement;\n * };\n * }\n * }\n *\n * // User registers adapter once in their app\n * GridElement.registerAdapter(new AngularGridAdapter(injector, appRef));\n * ```\n * @category Framework Adapters\n */\nexport interface FrameworkAdapter {\n /**\n * Determines if this adapter can handle the given element.\n * Typically checks tag name, attributes, or other conventions.\n */\n canHandle(element: HTMLElement): boolean;\n\n /**\n * Creates a view renderer function from a light DOM element.\n * The renderer receives cell context and returns DOM or string.\n * Returns undefined if no renderer template is registered, allowing the grid\n * to use its default rendering.\n */\n createRenderer<TRow = unknown, TValue = unknown>(element: HTMLElement): ColumnViewRenderer<TRow, TValue> | undefined;\n\n /**\n * Creates an editor spec from a light DOM element.\n * The editor receives context with commit/cancel and returns DOM.\n * Returns undefined if no editor template is registered, allowing the grid\n * to use its default built-in editors.\n */\n createEditor<TRow = unknown, TValue = unknown>(element: HTMLElement): ColumnEditorSpec<TRow, TValue> | undefined;\n\n /**\n * Creates a tool panel renderer from a light DOM element.\n * The renderer receives a container element and optionally returns a cleanup function.\n */\n createToolPanelRenderer?(element: HTMLElement): ((container: HTMLElement) => void | (() => void)) | undefined;\n\n /**\n * Gets type-level defaults from an application-level registry.\n * Used by Angular's `GridTypeRegistry` and React's `GridTypeProvider`.\n *\n * @param type - The column type (e.g., 'date', 'currency', 'country')\n * @returns Type defaults for renderer/editor, or undefined if not registered\n */\n getTypeDefault?<TRow = unknown>(type: string): TypeDefault<TRow> | undefined;\n\n /**\n * Pre-process a grid config before the grid core applies it.\n * Framework adapters use this to convert framework-specific component references\n * (Angular classes, Vue components, React elements) to DOM-returning functions.\n *\n * Called automatically by the grid's `set gridConfig` setter when a\n * `__frameworkAdapter` is present on the grid instance.\n *\n * Must be **idempotent** — already-processed configs must pass through safely.\n *\n * @param config - The raw grid config (may contain framework-specific values)\n * @returns Processed config with DOM-returning functions\n */\n processConfig?<TRow = unknown>(config: GridConfig<TRow>): GridConfig<TRow>;\n\n /**\n * Called when a cell's content is about to be wiped (e.g., when exiting edit mode,\n * scroll-recycling a row, or rebuilding a row).\n *\n * Framework adapters should use this to properly destroy cached views/components\n * associated with the cell to prevent memory leaks.\n *\n * @param cellEl - The cell element whose content is being released\n */\n releaseCell?(cellEl: HTMLElement): void;\n\n /**\n * Unmount a specific framework container and free its resources.\n *\n * Called by the grid core (e.g., MasterDetailPlugin) when a container\n * created by the adapter is about to be removed from the DOM.\n * The adapter should destroy the associated framework instance\n * (React root, Vue app, Angular view) and remove it from tracking arrays.\n *\n * @param container - The container element returned by a create* method\n */\n unmount?(container: HTMLElement): void;\n\n /**\n * Parse a `<tbw-grid-detail>` element and return a detail renderer function.\n * Used by MasterDetailPlugin to support framework-specific detail templates.\n */\n parseDetailElement?<TRow = unknown>(\n element: Element,\n ): ((row: TRow, rowIndex: number) => HTMLElement | string) | undefined;\n\n /**\n * Parse a `<tbw-grid-responsive-card>` element and return a card renderer function.\n * Used by ResponsivePlugin to support framework-specific card templates.\n */\n parseResponsiveCardElement?<TRow = unknown>(\n element: Element,\n ): ((row: TRow, rowIndex: number) => HTMLElement) | undefined;\n}\n// #endregion\n\n// #region Internal Types\n\n/**\n * Column internal properties used during light DOM parsing.\n * Stores attribute-based names before they're resolved to actual functions.\n * @internal\n */\nexport interface ColumnParsedAttributes {\n /** Editor name from `editor` attribute (resolved later) */\n __editorName?: string;\n /** Renderer name from `renderer` attribute (resolved later) */\n __rendererName?: string;\n}\n\n/**\n * Extended column config used internally.\n * Includes all internal properties needed during grid lifecycle.\n *\n * Plugin developers may need to access these when working with\n * column caching and compiled templates.\n *\n * @example\n * ```typescript\n * import type { ColumnInternal } from '@toolbox-web/grid';\n *\n * class MyPlugin extends BaseGridPlugin {\n * afterRender(): void {\n * // Access internal column properties\n * const columns = this.columns as ColumnInternal[];\n * for (const col of columns) {\n * // Check if column was auto-sized\n * if (col.__autoSized) {\n * console.log(`${col.field} was auto-sized`);\n * }\n * }\n * }\n * }\n * ```\n *\n * @see {@link ColumnConfig} for public column properties\n * @category Plugin Development\n * @internal\n */\nexport interface ColumnInternal<T = any> extends ColumnConfig<T>, ColumnParsedAttributes {\n __autoSized?: boolean;\n __userResized?: boolean;\n __renderedWidth?: number;\n /** Original configured width (for reset on double-click) */\n __originalWidth?: number;\n __viewTemplate?: HTMLElement;\n __editorTemplate?: HTMLElement;\n __headerTemplate?: HTMLElement;\n __compiledView?: CompiledViewFunction<T>;\n __compiledEditor?: (ctx: EditorExecContext<T>) => string;\n}\n\n/**\n * Row element with internal tracking properties.\n * Used during virtualization and row pooling.\n *\n * @category Plugin Development\n * @internal\n */\nexport interface RowElementInternal extends HTMLElement {\n /** Epoch marker for row render invalidation */\n __epoch?: number;\n /** Reference to the row data object for change detection */\n __rowDataRef?: unknown;\n /** Count of cells currently in edit mode */\n __editingCellCount?: number;\n /** Flag indicating this is a custom-rendered row (group row, etc.) */\n __isCustomRow?: boolean;\n}\n\n/**\n * Type-safe access to element.part API (DOMTokenList-like).\n * Used for CSS ::part styling support.\n * @internal\n */\nexport interface ElementWithPart {\n part?: DOMTokenList;\n}\n\n/**\n * Compiled view function type with optional blocked flag.\n * The __blocked flag is set when a template contains unsafe expressions.\n *\n * @category Plugin Development\n * @internal\n */\nexport interface CompiledViewFunction<T = any> {\n (ctx: CellContext<T>): string;\n /** Set to true when template was blocked due to unsafe expressions */\n __blocked?: boolean;\n}\n\n/**\n * Runtime cell context used internally for compiled template execution.\n *\n * Contains the minimal context needed to render a cell: the row data,\n * cell value, field name, and column configuration.\n *\n * @example\n * ```typescript\n * import type { CellContext, ColumnInternal } from '@toolbox-web/grid';\n *\n * // Used internally by compiled templates\n * const renderCell = (ctx: CellContext) => {\n * return `<span title=\"${ctx.field}\">${ctx.value}</span>`;\n * };\n * ```\n *\n * @see {@link CellRenderContext} for public cell render context\n * @see {@link EditorExecContext} for editor context with commit/cancel\n * @category Plugin Development\n */\nexport interface CellContext<T = any> {\n row: T;\n value: unknown;\n field: string;\n column: ColumnInternal<T>;\n}\n\n/**\n * Internal editor execution context extending the generic cell context with commit helpers.\n *\n * Used internally by the editing system. For public editor APIs,\n * prefer using {@link ColumnEditorContext}.\n *\n * @example\n * ```typescript\n * import type { EditorExecContext } from '@toolbox-web/grid';\n *\n * // Internal editor template execution\n * const execEditor = (ctx: EditorExecContext) => {\n * const input = document.createElement('input');\n * input.value = String(ctx.value);\n * input.onkeydown = (e) => {\n * if (e.key === 'Enter') ctx.commit(input.value);\n * if (e.key === 'Escape') ctx.cancel();\n * };\n * return input;\n * };\n * ```\n *\n * @see {@link ColumnEditorContext} for public editor context\n * @see {@link CellContext} for base cell context\n * @category Plugin Development\n */\nexport interface EditorExecContext<T = any> extends CellContext<T> {\n commit: (newValue: unknown) => void;\n cancel: () => void;\n}\n\n/**\n * Controller managing drag-based column resize lifecycle.\n *\n * Exposed internally for plugins that need to interact with resize behavior.\n *\n * @example\n * ```typescript\n * import type { ResizeController, InternalGrid } from '@toolbox-web/grid';\n *\n * class MyPlugin extends BaseGridPlugin {\n * handleColumnAction(colIndex: number): void {\n * const grid = this.grid as InternalGrid;\n * const resizeCtrl = grid._resizeController;\n *\n * // Check if resize is in progress\n * if (resizeCtrl?.isResizing) {\n * return; // Don't interfere\n * }\n *\n * // Reset column to configured width\n * resizeCtrl?.resetColumn(colIndex);\n * }\n * }\n * ```\n *\n * @see {@link ColumnResizeDetail} for resize event details\n * @category Plugin Development\n */\nexport interface ResizeController {\n start: (e: MouseEvent, colIndex: number, cell: HTMLElement) => void;\n /** Reset a column to its configured width (or auto-size if none configured). */\n resetColumn: (colIndex: number) => void;\n dispose: () => void;\n /** True while a resize drag is in progress (used to suppress header click/sort). */\n isResizing: boolean;\n}\n\n/**\n * Virtual window bookkeeping; modified in-place as scroll position changes.\n *\n * Tracks virtualization state for row rendering. The grid only renders\n * rows within the visible viewport window (start to end) plus overscan.\n *\n * @example\n * ```typescript\n * import type { VirtualState, InternalGrid } from '@toolbox-web/grid';\n *\n * class MyPlugin extends BaseGridPlugin {\n * logVirtualWindow(): void {\n * const grid = this.grid as InternalGrid;\n * const vs = grid.virtualization;\n *\n * console.log(`Row height: ${vs.rowHeight}px`);\n * console.log(`Visible rows: ${vs.start} to ${vs.end}`);\n * console.log(`Virtualization: ${vs.enabled ? 'on' : 'off'}`);\n * }\n * }\n * ```\n *\n * @see {@link GridConfig.rowHeight} for configuring row height\n * @category Plugin Development\n */\nexport interface VirtualState {\n enabled: boolean;\n rowHeight: number;\n /** Threshold for bypassing virtualization (renders all rows if totalRows <= bypassThreshold) */\n bypassThreshold: number;\n start: number;\n end: number;\n /** Faux scrollbar element that provides scroll events (AG Grid pattern) */\n container: HTMLElement | null;\n /** Rows viewport element for measuring visible area height */\n viewportEl: HTMLElement | null;\n /** Spacer element inside faux scrollbar for setting virtual height */\n totalHeightEl: HTMLElement | null;\n\n // --- Variable Row Height Support (Phase 1) ---\n\n /**\n * Position cache for variable row heights.\n * Index-based array mapping row index → {offset, height, measured}.\n * Rebuilt when row count changes (expand/collapse, filter).\n * `null` when using uniform row heights (default).\n */\n positionCache: RowPositionEntry[] | null;\n\n /**\n * Height cache by row identity.\n * Persists row heights across position cache rebuilds.\n * Uses dual storage: Map for string keys (rowId, __rowCacheKey) and WeakMap for object refs.\n */\n heightCache: {\n /** Heights keyed by string (synthetic rows with __rowCacheKey, or rowId-keyed rows) */\n byKey: Map<string, number>;\n /** Heights keyed by object reference (data rows without rowId) */\n byRef: WeakMap<object, number>;\n };\n\n /** Running average of measured row heights. Used for estimating unmeasured rows. */\n averageHeight: number;\n\n /** Number of rows that have been measured. */\n measuredCount: number;\n\n /** Whether variable row height mode is active (rowHeight is a function). */\n variableHeights: boolean;\n\n // --- Cached Geometry (avoid forced layout reads on scroll hot path) ---\n\n /** Cached viewport element height. Updated by ResizeObserver and force-refresh only. */\n cachedViewportHeight: number;\n\n /** Cached faux scrollbar element height. Updated alongside viewport height. */\n cachedFauxHeight: number;\n\n /** Cached scroll-area element height. Updated alongside viewport/faux heights. */\n cachedScrollAreaHeight: number;\n\n /** Cached reference to .tbw-scroll-area element. Set during scroll listener setup. */\n scrollAreaEl: HTMLElement | null;\n}\n\n// RowElementInternal is now defined earlier in the file with all internal properties\n\n/**\n * Union type for input-like elements that have a `value` property.\n * Covers standard form elements and custom elements with value semantics.\n *\n * @category Plugin Development\n * @internal\n */\nexport type InputLikeElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | { value: unknown };\n// #endregion\n\n// #region Grouping & Footer Public Types\n/**\n * Group row rendering customization options.\n * Controls how group header rows are displayed in the GroupingRowsPlugin.\n *\n * @example\n * ```typescript\n * import { GroupingRowsPlugin } from '@toolbox-web/grid/all';\n *\n * new GroupingRowsPlugin({\n * groupOn: (row) => [row.department, row.team],\n * render: {\n * // Group row spans all columns\n * fullWidth: true,\n *\n * // Custom label format\n * formatLabel: (value, depth, key) => {\n * if (depth === 0) return `Department: ${value}`;\n * return `Team: ${value}`;\n * },\n *\n * // Show aggregates in group rows (when not fullWidth)\n * aggregators: {\n * salary: 'sum',\n * age: 'avg',\n * },\n *\n * // Custom CSS class\n * class: 'my-group-row',\n * },\n * });\n * ```\n *\n * @see {@link AggregatorRef} for aggregation options\n */\nexport interface RowGroupRenderConfig {\n /** If true, group rows span all columns (single full-width cell). Default false. */\n fullWidth?: boolean;\n /** Optional label formatter override. Receives raw group value + depth. */\n formatLabel?: (value: unknown, depth: number, key: string) => string;\n /** Optional aggregate overrides per field for group summary cells (only when not fullWidth). */\n aggregators?: Record<string, AggregatorRef>;\n /** Additional CSS class applied to each group row root element. */\n class?: string;\n}\n\n/**\n * Reference to an aggregation function for footer/group summaries.\n *\n * Can be either:\n * - A built-in aggregator name: `'sum'`, `'avg'`, `'min'`, `'max'`, `'count'`\n * - A custom function that calculates the aggregate value\n *\n * @example\n * ```typescript\n * // Built-in aggregator\n * { field: 'amount', aggregator: 'sum' }\n *\n * // Custom aggregator function\n * { field: 'price', aggregator: (rows, field) => {\n * const values = rows.map(r => r[field]).filter(v => v != null);\n * return values.length ? Math.max(...values) : null;\n * }}\n * ```\n *\n * @see {@link RowGroupRenderConfig} for using aggregators in group rows\n */\nexport type AggregatorRef = string | ((rows: unknown[], field: string, column?: unknown) => unknown);\n\n/**\n * Result of automatic column inference from sample rows.\n *\n * When no columns are configured, the grid analyzes the first row of data\n * to automatically generate column definitions with inferred types.\n *\n * @example\n * ```typescript\n * // Automatic inference (no columns configured)\n * grid.rows = [\n * { name: 'Alice', age: 30, active: true, hireDate: new Date() },\n * ];\n * // Grid infers:\n * // - name: type 'string'\n * // - age: type 'number'\n * // - active: type 'boolean'\n * // - hireDate: type 'date'\n *\n * // Access inferred result programmatically\n * const config = await grid.getConfig();\n * console.log(config.columns); // Inferred columns\n * ```\n *\n * @see {@link ColumnConfig} for column configuration options\n * @see {@link ColumnType} for type inference rules\n */\nexport interface InferredColumnResult<TRow = unknown> {\n /** Generated column configurations based on data analysis */\n columns: ColumnConfigMap<TRow>;\n /** Map of field names to their inferred types */\n typeMap: Record<string, ColumnType>;\n}\n\n/**\n * Column sizing mode.\n *\n * - `'fixed'` - Columns use their configured widths. Horizontal scrolling if content overflows.\n * - `'stretch'` - Columns stretch proportionally to fill available width. No horizontal scrolling.\n *\n * @example\n * ```typescript\n * // Fixed widths - good for many columns\n * grid.fitMode = 'fixed';\n *\n * // Stretch to fill - good for few columns\n * grid.fitMode = 'stretch';\n *\n * // Via gridConfig\n * grid.gridConfig = { fitMode: 'stretch' };\n * ```\n */\nexport const FitModeEnum = {\n STRETCH: 'stretch',\n FIXED: 'fixed',\n} as const;\nexport type FitMode = (typeof FitModeEnum)[keyof typeof FitModeEnum]; // evaluates to 'stretch' | 'fixed'\n// #endregion\n\n// #region Plugin Interface\n/**\n * Minimal plugin interface for type-checking.\n * This interface is defined here to avoid circular imports with BaseGridPlugin.\n * All plugins must satisfy this shape (BaseGridPlugin implements it).\n *\n * @example\n * ```typescript\n * // Using plugins in grid config\n * import { SelectionPlugin, FilteringPlugin } from '@toolbox-web/grid/all';\n *\n * grid.gridConfig = {\n * plugins: [\n * new SelectionPlugin({ mode: 'row' }),\n * new FilteringPlugin({ debounceMs: 200 }),\n * ],\n * };\n *\n * // Accessing plugin instance at runtime (preferred)\n * const selection = grid.getPluginByName('selection');\n * if (selection) {\n * selection.selectAll();\n * }\n * ```\n *\n * @category Plugin Development\n */\nexport interface GridPlugin {\n /** Unique plugin identifier */\n readonly name: string;\n /** Plugin version */\n readonly version: string;\n /** CSS styles to inject into the grid */\n readonly styles?: string;\n}\n\n/**\n * Plugin name-to-type registry for type-safe `getPluginByName()`.\n *\n * Plugins augment this interface via `declare module` so that\n * `grid.getPluginByName('editing')` returns `EditingPlugin | undefined`\n * instead of `GridPlugin | undefined`.\n *\n * @example\n * ```typescript\n * // Plugin augmentation (done automatically when you import a plugin):\n * declare module '../../core/types' {\n * interface PluginNameMap {\n * editing: EditingPlugin;\n * }\n * }\n *\n * // Consumer usage — fully typed:\n * const editing = grid.getPluginByName('editing');\n * editing?.beginBulkEdit(0); // ✅ No cast needed\n * ```\n *\n * @category Plugin Development\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface\nexport interface PluginNameMap {}\n// #endregion\n\n// #region Feature Config\n/**\n * Declarative feature configuration interface.\n *\n * This interface is intentionally empty in core — it is populated via **module augmentation**\n * by feature side-effect imports (`@toolbox-web/grid/features/selection`, etc.).\n * Third-party plugins can also augment this interface to add their own features.\n *\n * @example\n * ```ts\n * // Each feature import augments this interface:\n * import '@toolbox-web/grid/features/selection';\n * import '@toolbox-web/grid/features/filtering';\n *\n * grid.gridConfig = {\n * features: {\n * selection: 'range', // ← typed by selection feature module\n * filtering: { debounceMs: 200 }, // ← typed by filtering feature module\n * },\n * };\n * ```\n *\n * @example Third-party augmentation\n * ```ts\n * declare module '@toolbox-web/grid' {\n * interface FeatureConfig {\n * sparkline?: boolean | SparklineConfig;\n * }\n * }\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type, @typescript-eslint/no-empty-interface\nexport interface FeatureConfig<TRow = unknown> {}\n// #endregion\n\n// #region Grid Config\n/**\n * Grid configuration object - the **single source of truth** for grid behavior.\n *\n * Users can configure the grid via multiple input methods, all of which converge\n * into an effective `GridConfig` internally:\n *\n * **Configuration Input Methods:**\n * - `gridConfig` property - direct assignment of this object\n * - `columns` property - shorthand for `gridConfig.columns`\n * - `fitMode` property - shorthand for `gridConfig.fitMode`\n * - Light DOM `<tbw-grid-column>` - declarative columns (merged into `columns`)\n * - Light DOM `<tbw-grid-header>` - declarative shell header (merged into `shell.header`)\n *\n * **Precedence (when same property set multiple ways):**\n * Individual props (`fitMode`) > `columns` prop > Light DOM > `gridConfig`\n *\n * @example\n * ```ts\n * // Via gridConfig (recommended for complex setups)\n * grid.gridConfig = {\n * columns: [{ field: 'name' }, { field: 'age' }],\n * fitMode: 'stretch',\n * plugins: [new SelectionPlugin()],\n * shell: { header: { title: 'My Grid' } }\n * };\n *\n * // Via individual props (convenience for simple cases)\n * grid.columns = [{ field: 'name' }, { field: 'age' }];\n * grid.fitMode = 'stretch';\n * ```\n */\nexport interface GridConfig<TRow = any> {\n /**\n * Column definitions. Can also be set via `columns` prop or `<tbw-grid-column>` light DOM.\n * @see {@link ColumnConfig} for column options\n * @see {@link ColumnConfigMap}\n */\n columns?: ColumnConfigMap<TRow>;\n /**\n * Dynamic CSS class(es) for data rows.\n * Called for each row during rendering. Return class names to add to the row element.\n *\n * @example\n * ```typescript\n * // Highlight inactive rows\n * rowClass: (row) => row.active ? [] : ['inactive', 'dimmed']\n *\n * // Status-based row styling\n * rowClass: (row) => [`priority-${row.priority}`]\n *\n * // Single class as string\n * rowClass: (row) => row.isNew ? 'new-row' : ''\n * ```\n */\n rowClass?: (row: TRow) => string | string[];\n /** Sizing mode for columns. Can also be set via `fitMode` prop. */\n fitMode?: FitMode;\n\n /**\n * Grid-wide sorting toggle.\n * When false, disables sorting for all columns regardless of their individual `sortable` setting.\n * When true (default), columns with `sortable: true` can be sorted.\n *\n * This affects:\n * - Header click handlers for sorting\n * - Sort indicator visibility\n * - Multi-sort plugin behavior (if loaded)\n *\n * @default true\n *\n * @example\n * ```typescript\n * // Disable all sorting\n * gridConfig = { sortable: false };\n *\n * // Enable sorting (default) - individual columns still need sortable: true\n * gridConfig = { sortable: true };\n * ```\n */\n sortable?: boolean;\n\n /**\n * Grid-wide resizing toggle.\n * When false, disables column resizing for all columns regardless of their individual `resizable` setting.\n * When true (default), columns with `resizable: true` (or resizable not set, since it defaults to true) can be resized.\n *\n * This affects:\n * - Resize handle visibility in header cells\n * - Double-click to auto-size behavior\n *\n * @default true\n *\n * @example\n * ```typescript\n * // Disable all column resizing\n * gridConfig = { resizable: false };\n *\n * // Enable resizing (default) - individual columns can opt out with resizable: false\n * gridConfig = { resizable: true };\n * ```\n */\n resizable?: boolean;\n\n /**\n * Row height in pixels for virtualization calculations.\n * The virtualization system assumes uniform row heights for performance.\n *\n * If not specified, the grid measures the first rendered row's height,\n * which respects the CSS variable `--tbw-row-height` set by themes.\n *\n * Set this explicitly when:\n * - Row content may wrap to multiple lines (also set `--tbw-cell-white-space: normal`)\n * - Using custom row templates with variable content\n * - You want to override theme-defined row height\n * - Rows have different heights based on content (use function form)\n *\n * **Variable Row Heights**: When a function is provided, the grid enables variable height\n * virtualization. Heights are measured on first render and cached by row identity.\n *\n * @default Auto-measured from first row (respects --tbw-row-height CSS variable)\n *\n * @example\n * ```ts\n * // Fixed height for all rows\n * gridConfig = { rowHeight: 56 };\n *\n * // Variable height based on content\n * gridConfig = {\n * rowHeight: (row, index) => row.hasDetails ? 80 : 40,\n * };\n *\n * // Return undefined to trigger DOM auto-measurement\n * gridConfig = {\n * rowHeight: (row) => row.isExpanded ? undefined : 40,\n * };\n * ```\n */\n rowHeight?: number | ((row: TRow, index: number) => number | undefined);\n /**\n * Array of plugin instances.\n * Each plugin is instantiated with its configuration and attached to this grid.\n *\n * @example\n * ```ts\n * plugins: [\n * new SelectionPlugin({ mode: 'range' }),\n * new MultiSortPlugin(),\n * new FilteringPlugin({ debounceMs: 150 }),\n * ]\n * ```\n */\n plugins?: GridPlugin[];\n\n /**\n * Declarative feature configuration.\n * Alternative to manually creating plugin instances in `plugins`.\n * Features are resolved using the core feature registry.\n *\n * Import feature modules as side effects to register them:\n * ```ts\n * import '@toolbox-web/grid/features/selection';\n * import '@toolbox-web/grid/features/filtering';\n * ```\n *\n * Then configure declaratively:\n * ```ts\n * gridConfig = {\n * features: {\n * selection: 'range',\n * filtering: { debounceMs: 200 },\n * editing: 'dblclick',\n * },\n * };\n * ```\n *\n * Both `features` and `plugins` can be used together — features-generated plugins\n * are created first, then manual `plugins` are appended. Duplicates are skipped\n * (manual `plugins` take precedence).\n */\n features?: Partial<FeatureConfig<TRow>>;\n\n /**\n * Saved column state to restore on initialization.\n * Includes order, width, visibility, sort, and plugin-contributed state.\n */\n columnState?: GridColumnState;\n\n /**\n * Shell configuration for header bar and tool panels.\n * When configured, adds an optional wrapper with title, toolbar, and collapsible side panels.\n */\n shell?: ShellConfig;\n\n /**\n * Grid-wide icon configuration.\n * Provides consistent icons across all plugins (tree, grouping, sorting, etc.).\n * Plugins will use these by default but can override with their own config.\n */\n icons?: GridIcons;\n\n /**\n * Grid-wide animation configuration.\n * Controls animations for expand/collapse, reordering, and other visual transitions.\n * Individual plugins can override these defaults in their own config.\n */\n animation?: AnimationConfig;\n\n /**\n * Custom sort handler for full control over sorting behavior.\n *\n * When provided, this handler is called instead of the built-in sorting logic.\n * Enables custom sorting algorithms, server-side sorting, or plugin-specific sorting.\n *\n * The handler receives:\n * - `rows`: Current row array to sort\n * - `sortState`: Sort field and direction (1 = asc, -1 = desc)\n * - `columns`: Column configurations (for accessing sortComparator)\n *\n * Return the sorted array (sync) or a Promise that resolves to the sorted array (async).\n * For server-side sorting, return a Promise that resolves when data is fetched.\n *\n * @example\n * ```ts\n * // Custom stable sort\n * sortHandler: (rows, state, cols) => {\n * return stableSort(rows, (a, b) => compare(a[state.field], b[state.field]) * state.direction);\n * }\n *\n * // Server-side sorting\n * sortHandler: async (rows, state) => {\n * const response = await fetch(`/api/data?sort=${state.field}&dir=${state.direction}`);\n * return response.json();\n * }\n * ```\n */\n sortHandler?: SortHandler<TRow>;\n\n /**\n * Function to extract a unique identifier from a row.\n * Used by `updateRow()`, `getRow()`, and ID-based tracking.\n *\n * If not provided, falls back to `row.id` or `row._id` if present.\n * Rows without IDs are silently skipped during map building.\n * Only throws when explicitly calling `getRowId()` or `updateRow()` on a row without an ID.\n *\n * @example\n * ```ts\n * // Simple field\n * getRowId: (row) => row.id\n *\n * // Composite key\n * getRowId: (row) => `${row.voyageId}-${row.legNumber}`\n *\n * // UUID field\n * getRowId: (row) => row.uuid\n * ```\n */\n getRowId?: (row: TRow) => string;\n\n /**\n * Type-level renderer and editor defaults.\n *\n * Keys can be:\n * - Built-in types: `'string'`, `'number'`, `'date'`, `'boolean'`, `'select'`\n * - Custom types: `'currency'`, `'country'`, `'status'`, etc.\n *\n * Resolution order (highest priority first):\n * 1. Column-level (`column.renderer` / `column.editor`)\n * 2. Grid-level (`gridConfig.typeDefaults[column.type]`)\n * 3. App-level (Angular `GridTypeRegistry`, React `GridTypeProvider`)\n * 4. Built-in (checkbox for boolean, select for select, etc.)\n * 5. Fallback (plain text / text input)\n *\n * @example\n * ```typescript\n * typeDefaults: {\n * date: { editor: myDatePickerEditor },\n * country: {\n * renderer: (ctx) => {\n * const span = document.createElement('span');\n * span.innerHTML = `<img src=\"/flags/${ctx.value}.svg\" /> ${ctx.value}`;\n * return span;\n * },\n * editor: (ctx) => createCountrySelect(ctx)\n * }\n * }\n * ```\n */\n typeDefaults?: Record<string, TypeDefault<TRow>>;\n\n // #region Accessibility\n\n /**\n * Accessible label for the grid.\n * Sets `aria-label` on the grid's internal table element for screen readers.\n *\n * If not provided and `shell.header.title` is set, the title is used automatically.\n *\n * @example\n * ```ts\n * gridConfig = { gridAriaLabel: 'Employee data' };\n * ```\n */\n gridAriaLabel?: string;\n\n /**\n * ID of an element that describes the grid.\n * Sets `aria-describedby` on the grid's internal table element.\n *\n * @example\n * ```html\n * <p id=\"grid-desc\">This table shows all active employees.</p>\n * <tbw-grid></tbw-grid>\n * ```\n * ```ts\n * gridConfig = { gridAriaDescribedBy: 'grid-desc' };\n * ```\n */\n gridAriaDescribedBy?: string;\n\n // #endregion\n\n // #region Loading\n\n /**\n * Custom renderer for the loading overlay.\n *\n * When provided, replaces the default spinner with custom content.\n * Receives a context object with the current loading size.\n *\n * @example\n * ```typescript\n * // Simple text loading indicator\n * loadingRenderer: () => {\n * const el = document.createElement('div');\n * el.textContent = 'Loading...';\n * return el;\n * }\n *\n * // Custom spinner component\n * loadingRenderer: (ctx) => {\n * const spinner = document.createElement('my-spinner');\n * spinner.size = ctx.size === 'large' ? 48 : 24;\n * return spinner;\n * }\n * ```\n */\n loadingRenderer?: LoadingRenderer;\n\n // #endregion\n}\n// #endregion\n\n// #region Animation\n\n/**\n * Sort state passed to custom sort handlers.\n * Represents the current sorting configuration for a column.\n *\n * @example\n * ```typescript\n * // In a custom sort handler\n * const sortHandler: SortHandler = (rows, sortState, columns) => {\n * const { field, direction } = sortState;\n * console.log(`Sorting by ${field} ${direction === 1 ? 'ASC' : 'DESC'}`);\n *\n * return [...rows].sort((a, b) => {\n * const aVal = a[field];\n * const bVal = b[field];\n * return (aVal < bVal ? -1 : aVal > bVal ? 1 : 0) * direction;\n * });\n * };\n * ```\n *\n * @see {@link SortHandler} for custom sort handler signature\n * @see {@link SortChangeDetail} for sort change events\n */\nexport interface SortState {\n /** Field to sort by */\n field: string;\n /** Sort direction: 1 = ascending, -1 = descending */\n direction: 1 | -1;\n}\n\n/**\n * Custom sort handler function signature.\n *\n * Enables full control over sorting behavior including server-side sorting,\n * custom algorithms, or multi-column sorting.\n *\n * @param rows - Current row array to sort\n * @param sortState - Sort field and direction\n * @param columns - Column configurations (for accessing sortComparator)\n * @returns Sorted array (sync) or Promise resolving to sorted array (async)\n *\n * @example\n * ```typescript\n * // Custom client-side sort with locale awareness\n * const localeSortHandler: SortHandler<Employee> = (rows, state, cols) => {\n * const col = cols.find(c => c.field === state.field);\n * return [...rows].sort((a, b) => {\n * const aVal = String(a[state.field] ?? '');\n * const bVal = String(b[state.field] ?? '');\n * return aVal.localeCompare(bVal) * state.direction;\n * });\n * };\n *\n * // Server-side sorting\n * const serverSortHandler: SortHandler<Employee> = async (rows, state) => {\n * const response = await fetch(\n * `/api/employees?sortBy=${state.field}&dir=${state.direction}`\n * );\n * return response.json();\n * };\n *\n * grid.gridConfig = {\n * sortHandler: localeSortHandler,\n * };\n * ```\n *\n * @see {@link SortState} for the sort state object\n * @see {@link GridConfig.sortHandler} for configuring the handler\n * @see {@link BaseColumnConfig.sortComparator} for column-level comparators\n */\nexport type SortHandler<TRow = any> = (\n rows: TRow[],\n sortState: SortState,\n columns: ColumnConfig<TRow>[],\n) => TRow[] | Promise<TRow[]>;\n\n// #region Loading\n\n/**\n * Loading indicator size variant.\n *\n * - `'large'`: 48x48px max - used for grid-level loading overlay (`grid.loading = true`)\n * - `'small'`: Follows row height - used for row/cell loading states\n *\n * @example\n * ```typescript\n * // Custom loading renderer adapting to size\n * const myLoader: LoadingRenderer = (ctx) => {\n * if (ctx.size === 'large') {\n * // Full overlay spinner\n * return '<div class=\"spinner-lg\"></div>';\n * }\n * // Inline row/cell spinner\n * return '<span class=\"spinner-sm\"></span>';\n * };\n * ```\n *\n * @see {@link LoadingRenderer} for custom loading renderer\n * @see {@link LoadingContext} for context passed to renderers\n */\nexport type LoadingSize = 'large' | 'small';\n\n/**\n * Context passed to custom loading renderers.\n *\n * Provides information about the loading indicator being rendered,\n * allowing the renderer to adapt its appearance based on the size variant.\n *\n * @example\n * ```typescript\n * const myLoadingRenderer: LoadingRenderer = (ctx: LoadingContext) => {\n * if (ctx.size === 'large') {\n * // Full-size spinner for grid-level loading\n * return '<div class=\"large-spinner\"></div>';\n * } else {\n * // Compact spinner for row/cell loading\n * return '<div class=\"small-spinner\"></div>';\n * }\n * };\n * ```\n *\n * @see {@link LoadingRenderer} for the renderer function signature\n * @see {@link LoadingSize} for available size variants\n */\nexport interface LoadingContext {\n /** The size variant being rendered: 'large' for grid-level, 'small' for row/cell */\n size: LoadingSize;\n}\n\n/**\n * Custom loading renderer function.\n * Returns an element or HTML string to display as the loading indicator.\n *\n * Used with the `loadingRenderer` property in {@link GridConfig} to replace\n * the default spinner with custom content.\n *\n * @param context - Context containing size information\n * @returns HTMLElement or HTML string\n *\n * @example\n * ```typescript\n * // Simple text loading indicator\n * const textLoader: LoadingRenderer = () => {\n * const el = document.createElement('div');\n * el.textContent = 'Loading...';\n * return el;\n * };\n *\n * // Custom spinner with size awareness\n * const customSpinner: LoadingRenderer = (ctx) => {\n * const spinner = document.createElement('my-spinner');\n * spinner.size = ctx.size === 'large' ? 48 : 24;\n * return spinner;\n * };\n *\n * // Material Design-style progress bar\n * const progressBar: LoadingRenderer = () => {\n * const container = document.createElement('div');\n * container.className = 'progress-bar-container';\n * container.innerHTML = '<div class=\"progress-bar\"></div>';\n * return container;\n * };\n *\n * grid.gridConfig = {\n * loadingRenderer: customSpinner,\n * };\n * ```\n *\n * @see {@link LoadingContext} for the context object passed to the renderer\n * @see {@link LoadingSize} for size variants ('large' | 'small')\n */\nexport type LoadingRenderer = (context: LoadingContext) => HTMLElement | string;\n\n// #endregion\n\n// #region Data Change Event\n\n/**\n * Detail for the `data-change` event.\n *\n * Fired whenever the grid's row data changes — including new data assignment,\n * row insertion/removal, and in-place mutations via `updateRow()`.\n *\n * Use this to keep external UI in sync with the grid's current data state\n * (row counts, summaries, charts, etc.).\n *\n * @example\n * ```typescript\n * grid.on('data-change', ({ rowCount, sourceRowCount }) => {\n * console.log(`${rowCount} rows visible of ${sourceRowCount} total`);\n * });\n * ```\n *\n * @see {@link DataGridEventMap} for all event types\n * @category Events\n */\nexport interface DataChangeDetail {\n /** Number of visible (processed) rows */\n rowCount: number;\n /** Total number of source rows (before filtering/grouping) */\n sourceRowCount: number;\n}\n\n// #endregion\n\n// #region Data Update Management\n\n/**\n * Indicates the origin of a data change.\n * Used to prevent infinite loops in cascade update handlers.\n *\n * - `'user'`: Direct user interaction via EditingPlugin (typing, selecting)\n * - `'cascade'`: Triggered by `updateRow()` in an event handler\n * - `'api'`: External programmatic update via `grid.updateRow()`\n *\n * @example\n * ```typescript\n * grid.on('cell-change', (detail) => {\n * const { source, field, newValue } = detail;\n *\n * // Only cascade updates for user edits\n * if (source === 'user' && field === 'price') {\n * // Update calculated field (marked as 'cascade')\n * grid.updateRow(detail.rowId, {\n * total: newValue * detail.row.quantity,\n * });\n * }\n *\n * // Ignore cascade updates to prevent infinite loops\n * if (source === 'cascade') return;\n * });\n * ```\n *\n * @see {@link CellChangeDetail} for the event detail containing source\n * @category Data Management\n */\nexport type UpdateSource = 'user' | 'cascade' | 'api';\n\n/**\n * Detail for cell-change event (emitted by core after mutation).\n * This is an informational event that fires for ALL data mutations.\n *\n * Use this event for:\n * - Logging/auditing changes\n * - Cascading updates (updating other fields based on a change)\n * - Syncing changes to external state\n *\n * @example\n * ```typescript\n * grid.on('cell-change', ({ row, rowId, field, oldValue, newValue, source }) => {\n * console.log(`${field} changed from ${oldValue} to ${newValue}`);\n * console.log(`Change source: ${source}`);\n *\n * // Cascade: update total when price changes\n * if (source === 'user' && field === 'price') {\n * grid.updateRow(rowId, { total: newValue * row.quantity });\n * }\n * });\n * ```\n *\n * @see {@link UpdateSource} for understanding change origins\n * @see CellCommitDetail for the commit event (editing lifecycle)\n * @category Events\n */\nexport interface CellChangeDetail<TRow = unknown> {\n /** The row object (after mutation) */\n row: TRow;\n /** Stable row identifier */\n rowId: string;\n /** Current index in rows array */\n rowIndex: number;\n /** Field that changed */\n field: string;\n /** Value before change */\n oldValue: unknown;\n /** Value after change */\n newValue: unknown;\n /** All changes passed to updateRow/updateRows (for context) */\n changes: Partial<TRow>;\n /** Origin of this change */\n source: UpdateSource;\n}\n\n/**\n * Batch update specification for updateRows().\n *\n * Used when you need to update multiple rows at once efficiently.\n * The grid will batch all updates and trigger a single re-render.\n *\n * @example\n * ```typescript\n * // Update multiple rows in a single batch\n * const updates: RowUpdate<Employee>[] = [\n * { id: 'emp-1', changes: { status: 'active', updatedAt: new Date() } },\n * { id: 'emp-2', changes: { status: 'inactive' } },\n * { id: 'emp-3', changes: { salary: 75000 } },\n * ];\n *\n * grid.updateRows(updates);\n * ```\n *\n * @see {@link CellChangeDetail} for individual change events\n * @see {@link GridConfig.getRowId} for row identification\n * @category Data Management\n */\nexport interface RowUpdate<TRow = unknown> {\n /** Row identifier (from getRowId) */\n id: string;\n /** Fields to update */\n changes: Partial<TRow>;\n}\n\n/**\n * A batch of row mutations to apply atomically in a single render cycle.\n *\n * All adds, updates, and removes are processed together with one re-render,\n * making this far more efficient than calling `insertRow`, `updateRow`, and\n * `removeRow` individually — especially for high-frequency streaming data.\n *\n * Row identification for `update` and `remove` uses the grid's configured\n * {@link GridConfig.getRowId | getRowId} function.\n *\n * @example\n * ```typescript\n * // Apply a mixed transaction from a WebSocket message\n * const result = await grid.applyTransaction({\n * add: [{ id: 'new-1', name: 'Alice', status: 'Active' }],\n * update: [{ id: 'emp-5', changes: { status: 'Inactive' } }],\n * remove: [{ id: 'emp-3' }],\n * });\n *\n * console.log(`Added: ${result.added.length}, Updated: ${result.updated.length}, Removed: ${result.removed.length}`);\n * ```\n *\n * @see {@link TransactionResult} for the result structure\n * @category Data Management\n */\nexport interface RowTransaction<TRow = unknown> {\n /** Rows to insert. Appended at the end of the current view. */\n add?: TRow[];\n /** Rows to update in-place by ID. */\n update?: RowUpdate<TRow>[];\n /** Rows to remove by ID. */\n remove?: Array<{ id: string }>;\n}\n\n/**\n * Result of a {@link RowTransaction} applied via `applyTransaction`.\n *\n * Contains the actual row objects that were affected, useful for\n * post-processing or logging.\n *\n * @see {@link RowTransaction} for the input structure\n * @category Data Management\n */\nexport interface TransactionResult<TRow = unknown> {\n /** Rows that were successfully added. */\n added: TRow[];\n /** Rows that were successfully updated (references to the mutated row objects). */\n updated: TRow[];\n /** Rows that were successfully removed. */\n removed: TRow[];\n}\n\n// #endregion\n\n/**\n * Animation behavior mode.\n * - `true` or `'on'`: Animations always enabled\n * - `false` or `'off'`: Animations always disabled\n * - `'reduced-motion'`: Respects `prefers-reduced-motion` media query (default)\n *\n * @example\n * ```typescript\n * // Force animations on (ignore system preference)\n * grid.gridConfig = { animation: { mode: 'on' } };\n *\n * // Disable all animations\n * grid.gridConfig = { animation: { mode: false } };\n *\n * // Respect user's accessibility settings (default)\n * grid.gridConfig = { animation: { mode: 'reduced-motion' } };\n * ```\n *\n * @see {@link AnimationConfig} for full animation configuration\n */\nexport type AnimationMode = boolean | 'on' | 'off' | 'reduced-motion';\n\n/**\n * Animation style for visual transitions.\n * - `'slide'`: Slide/transform animation (e.g., expand down, slide left/right)\n * - `'fade'`: Opacity fade animation\n * - `'flip'`: FLIP technique for position changes (First, Last, Invert, Play)\n * - `false`: No animation for this specific feature\n *\n * @example\n * ```typescript\n * // Plugin-specific animation styles\n * new TreePlugin({\n * expandAnimation: 'slide', // Slide children down when expanding\n * });\n *\n * new ReorderPlugin({\n * animation: 'flip', // FLIP animation for column reordering\n * });\n * ```\n *\n * @see {@link AnimationConfig} for grid-wide animation settings\n * @see {@link ExpandCollapseAnimation} for expand/collapse-specific styles\n */\nexport type AnimationStyle = 'slide' | 'fade' | 'flip' | false;\n\n/**\n * Animation style for expand/collapse operations.\n * Subset of AnimationStyle - excludes 'flip' which is for position changes.\n * - `'slide'`: Slide down/up animation for expanding/collapsing content\n * - `'fade'`: Fade in/out animation\n * - `false`: No animation\n *\n * @example\n * ```typescript\n * // Tree rows slide down when expanding\n * new TreePlugin({ expandAnimation: 'slide' });\n *\n * // Row groups fade in/out\n * new GroupingRowsPlugin({ expandAnimation: 'fade' });\n *\n * // Master-detail panels with no animation\n * new MasterDetailPlugin({ expandAnimation: false });\n * ```\n *\n * @see {@link AnimationStyle} for all animation styles\n * @see {@link AnimationConfig} for grid-wide settings\n */\nexport type ExpandCollapseAnimation = 'slide' | 'fade' | false;\n\n/**\n * Type of row animation.\n * - `'change'`: Flash highlight when row data changes (e.g., after cell edit)\n * - `'insert'`: Slide-in animation for newly added rows\n * - `'remove'`: Fade-out animation for rows being removed\n *\n * @example\n * ```typescript\n * // Internal usage - row animation is triggered automatically:\n * // - 'change' after cell-commit event\n * // - 'insert' when rows are added to the grid\n * // - 'remove' when rows are deleted\n *\n * // The animation respects AnimationConfig.mode\n * grid.gridConfig = {\n * animation: { mode: 'on', duration: 300 },\n * };\n * ```\n *\n * @see {@link AnimationConfig} for animation configuration\n */\nexport type RowAnimationType = 'change' | 'insert' | 'remove';\n\n/**\n * Grid-wide animation configuration.\n * Controls global animation behavior - individual plugins define their own animation styles.\n * Duration and easing values set corresponding CSS variables on the grid element.\n *\n * @example\n * ```typescript\n * // Enable animations regardless of system preferences\n * grid.gridConfig = {\n * animation: {\n * mode: 'on',\n * duration: 300,\n * easing: 'cubic-bezier(0.4, 0, 0.2, 1)',\n * },\n * };\n *\n * // Disable all animations\n * grid.gridConfig = {\n * animation: { mode: 'off' },\n * };\n *\n * // Respect user's reduced-motion preference (default)\n * grid.gridConfig = {\n * animation: { mode: 'reduced-motion' },\n * };\n * ```\n *\n * @see {@link AnimationMode} for mode options\n */\nexport interface AnimationConfig {\n /**\n * Global animation mode.\n * @default 'reduced-motion'\n */\n mode?: AnimationMode;\n\n /**\n * Default animation duration in milliseconds.\n * Sets `--tbw-animation-duration` CSS variable.\n * @default 200\n */\n duration?: number;\n\n /**\n * Default easing function.\n * Sets `--tbw-animation-easing` CSS variable.\n * @default 'ease-out'\n */\n easing?: string;\n}\n\n/** Default animation configuration */\nexport const DEFAULT_ANIMATION_CONFIG: Required<Omit<AnimationConfig, 'sort'>> = {\n mode: 'reduced-motion',\n duration: 200,\n easing: 'ease-out',\n};\n\n// #endregion\n\n// #region Grid Icons\n\n/** Icon value - can be a string (text/HTML) or HTMLElement */\nexport type IconValue = string | HTMLElement;\n\n/**\n * Grid-wide icon configuration.\n * All icons are optional - sensible defaults are used when not specified.\n *\n * Icons can be text (including emoji), HTML strings (for SVG), or HTMLElement instances.\n *\n * @example\n * ```typescript\n * grid.gridConfig = {\n * icons: {\n * // Emoji icons\n * expand: '➕',\n * collapse: '➖',\n *\n * // Custom SVG icon\n * sortAsc: '<svg viewBox=\"0 0 16 16\"><path d=\"M8 4l4 8H4z\"/></svg>',\n *\n * // Font icon class (wrap in span)\n * filter: '<span class=\"icon icon-filter\"></span>',\n * },\n * };\n * ```\n *\n * @see {@link IconValue} for allowed icon formats\n */\nexport interface GridIcons {\n /** Expand icon for collapsed items (trees, groups, details). Default: '▶' */\n expand?: IconValue;\n /** Collapse icon for expanded items (trees, groups, details). Default: '▼' */\n collapse?: IconValue;\n /** Sort ascending indicator. Default: '▲' */\n sortAsc?: IconValue;\n /** Sort descending indicator. Default: '▼' */\n sortDesc?: IconValue;\n /** Sort neutral/unsorted indicator. Default: '⇅' */\n sortNone?: IconValue;\n /** Submenu arrow for context menus. Default: '▶' */\n submenuArrow?: IconValue;\n /** Drag handle icon for reordering. Default: '⋮⋮' */\n dragHandle?: IconValue;\n /** Tool panel toggle icon in toolbar. Default: '☰' */\n toolPanel?: IconValue;\n /** Filter icon in column headers. Default: SVG funnel icon */\n filter?: IconValue;\n /** Filter icon when filter is active. Default: same as filter with accent color */\n filterActive?: IconValue;\n /** Print icon for print button. Default: '🖨️' */\n print?: IconValue;\n}\n\n/** Default filter icon SVG */\nconst DEFAULT_FILTER_ICON =\n '<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\"><path fill=\"currentColor\" d=\"M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z\"/></svg>';\n\n/** Default icons used when not overridden */\nexport const DEFAULT_GRID_ICONS: Required<GridIcons> = {\n expand: '▶',\n collapse: '▼',\n sortAsc: '▲',\n sortDesc: '▼',\n sortNone: '⇅',\n submenuArrow: '▶',\n dragHandle: '⋮⋮',\n toolPanel: '☰',\n filter: DEFAULT_FILTER_ICON,\n filterActive: DEFAULT_FILTER_ICON,\n print: '🖨️',\n};\n// #endregion\n\n// #region Shell Configuration\n\n/**\n * Shell configuration for the grid's optional header bar and tool panels.\n *\n * The shell provides a wrapper around the grid with:\n * - Header bar with title, toolbar buttons, and custom content\n * - Collapsible side panel for filters, column visibility, settings, etc.\n *\n * @example\n * ```typescript\n * grid.gridConfig = {\n * shell: {\n * header: {\n * title: 'Employee Directory',\n * },\n * toolPanel: {\n * position: 'right',\n * defaultOpen: 'columns', // Open by default\n * },\n * },\n * plugins: [new VisibilityPlugin()], // Adds \"Columns\" panel\n * };\n *\n * // Register custom tool panels\n * grid.registerToolPanel({\n * id: 'filters',\n * title: 'Filters',\n * icon: '🔍',\n * render: (container) => {\n * container.innerHTML = '<div>Filter controls...</div>';\n * },\n * });\n * ```\n *\n * @see {@link ShellHeaderConfig} for header options\n * @see {@link ToolPanelConfig} for tool panel options\n */\nexport interface ShellConfig {\n /** Shell header bar configuration */\n header?: ShellHeaderConfig;\n /** Tool panel configuration */\n toolPanel?: ToolPanelConfig;\n /**\n * Registered tool panels (from plugins, API, or Light DOM).\n * These are the actual panel definitions that can be opened.\n * @internal Set by ConfigManager during merge\n */\n toolPanels?: ToolPanelDefinition[];\n /**\n * Registered header content sections (from plugins or API).\n * Content rendered in the center of the shell header.\n * @internal Set by ConfigManager during merge\n */\n headerContents?: HeaderContentDefinition[];\n}\n\n/**\n * Shell header bar configuration\n */\nexport interface ShellHeaderConfig {\n /** Grid title displayed on the left (optional) */\n title?: string;\n /** Custom toolbar content (rendered before tool panel toggle) */\n toolbarContents?: ToolbarContentDefinition[];\n /**\n * Light DOM header content elements (parsed from <tbw-grid-header> children).\n * @internal Set by ConfigManager during merge\n */\n lightDomContent?: HTMLElement[];\n /**\n * Whether a tool buttons container was found in light DOM.\n * @internal Set by ConfigManager during merge\n */\n hasToolButtonsContainer?: boolean;\n}\n\n/**\n * Tool panel configuration\n */\nexport interface ToolPanelConfig {\n /** Panel position: 'left' | 'right' (default: 'right') */\n position?: 'left' | 'right';\n /** Default panel width in pixels (default: 280) */\n width?: number;\n /** Panel ID to open by default on load */\n defaultOpen?: string;\n /** Whether to persist open/closed state (requires Column State Events) */\n persistState?: boolean;\n /**\n * Close the tool panel when clicking outside of it.\n * When `true`, clicking anywhere outside the tool panel (but inside the grid)\n * will close the panel automatically.\n * @default false\n */\n closeOnClickOutside?: boolean;\n}\n\n/**\n * Toolbar content definition for the shell header toolbar area.\n * Register via `registerToolbarContent()` or use light DOM `<tbw-grid-tool-buttons>`.\n *\n * @example\n * ```typescript\n * grid.registerToolbarContent({\n * id: 'my-toolbar',\n * order: 10,\n * render: (container) => {\n * const btn = document.createElement('button');\n * btn.textContent = 'Refresh';\n * btn.onclick = () => console.log('clicked');\n * container.appendChild(btn);\n * return () => btn.remove();\n * },\n * });\n * ```\n */\nexport interface ToolbarContentDefinition {\n /** Unique content ID */\n id: string;\n /** Content factory - called once when shell header renders */\n render: (container: HTMLElement) => void | (() => void);\n /** Called when content is removed (for cleanup) */\n onDestroy?: () => void;\n /** Order priority (lower = first, default: 100) */\n order?: number;\n}\n\n/**\n * Tool panel definition registered by plugins or consumers.\n *\n * Register via `grid.registerToolPanel()` to add panels to the sidebar.\n * Panels appear as collapsible sections with icons and titles.\n *\n * @example\n * ```typescript\n * grid.registerToolPanel({\n * id: 'filters',\n * title: 'Filters',\n * icon: '🔍',\n * tooltip: 'Filter grid data',\n * order: 10, // Lower = appears first\n * render: (container) => {\n * container.innerHTML = `\n * <div class=\"filter-panel\">\n * <input type=\"text\" placeholder=\"Search...\" />\n * </div>\n * `;\n * // Return cleanup function\n * return () => container.innerHTML = '';\n * },\n * onClose: () => {\n * console.log('Filter panel closed');\n * },\n * });\n * ```\n *\n * @see {@link ShellConfig} for shell configuration\n */\nexport interface ToolPanelDefinition {\n /** Unique panel ID */\n id: string;\n /** Panel title shown in accordion header */\n title: string;\n /** Icon for accordion section header (optional, emoji or SVG) */\n icon?: string;\n /** Tooltip for accordion section header */\n tooltip?: string;\n /** Panel content factory - called when panel section opens */\n render: (container: HTMLElement) => void | (() => void);\n /** Called when panel closes (for cleanup) */\n onClose?: () => void;\n /** Panel order priority (lower = first, default: 100) */\n order?: number;\n}\n\n/**\n * Header content definition for plugins contributing to shell header center section.\n *\n * Register via `grid.registerHeaderContent()` to add content between\n * the title and toolbar buttons.\n *\n * @example\n * ```typescript\n * grid.registerHeaderContent({\n * id: 'row-count',\n * order: 10,\n * render: (container) => {\n * const span = document.createElement('span');\n * span.className = 'row-count';\n * span.textContent = `${grid.rows.length} rows`;\n * container.appendChild(span);\n *\n * // Update on data changes\n * const unsub = grid.on('data-change', () => {\n * span.textContent = `${grid.rows.length} rows`;\n * });\n *\n * return () => {\n * unsub();\n * };\n * },\n * });\n * ```\n *\n * @see {@link ShellConfig} for shell configuration\n */\nexport interface HeaderContentDefinition {\n /** Unique content ID */\n id: string;\n /** Content factory - called once when shell header renders */\n render: (container: HTMLElement) => void | (() => void);\n /** Called when content is removed (for cleanup) */\n onDestroy?: () => void;\n /** Order priority (lower = first, default: 100) */\n order?: number;\n}\n// #endregion\n\n// #region Column State (Persistence)\n\n/**\n * State for a single column. Captures user-driven changes at runtime.\n * Plugins can extend this interface via module augmentation to add their own state.\n *\n * Used with `grid.getColumnState()` and `grid.columnState` for persisting\n * user customizations (column widths, order, visibility, sort).\n *\n * @example\n * ```typescript\n * // Save column state to localStorage\n * const state = grid.getColumnState();\n * localStorage.setItem('gridState', JSON.stringify(state));\n *\n * // Restore on page load\n * const saved = localStorage.getItem('gridState');\n * if (saved) {\n * grid.columnState = JSON.parse(saved);\n * }\n *\n * // Example column state structure\n * const state: GridColumnState = {\n * columns: [\n * { field: 'name', order: 0, width: 200, hidden: false },\n * { field: 'email', order: 1, width: 300, hidden: false },\n * { field: 'phone', order: 2, hidden: true }, // Hidden column\n * ],\n * sort: { field: 'name', direction: 1 },\n * };\n * ```\n *\n * @example\n * ```typescript\n * // Plugin augmentation example (in filtering plugin)\n * declare module '@toolbox-web/grid' {\n * interface ColumnState {\n * filter?: FilterValue;\n * }\n * }\n * ```\n *\n * @see {@link GridColumnState} for the full state object\n */\nexport interface ColumnState {\n /** Column field identifier */\n field: string;\n /** Position index after reordering (0-based) */\n order: number;\n /** Width in pixels (undefined = use default) */\n width?: number;\n /** Visibility state */\n visible: boolean;\n /** Sort state (undefined = not sorted). */\n sort?: ColumnSortState;\n}\n\n/**\n * Sort state for a column.\n * Used within {@link ColumnState} to track sort direction and priority.\n *\n * @see {@link ColumnState} for column state persistence\n * @see {@link SortChangeDetail} for sort change events\n */\nexport interface ColumnSortState {\n /** Sort direction */\n direction: 'asc' | 'desc';\n /** Priority for multi-sort (0 = primary, 1 = secondary, etc.) */\n priority: number;\n}\n\n/**\n * Complete grid column state for persistence.\n * Contains state for all columns, including plugin-contributed properties.\n *\n * @example\n * ```typescript\n * // Save state\n * const state = grid.getColumnState();\n * localStorage.setItem('grid-state', JSON.stringify(state));\n *\n * // Restore state\n * grid.columnState = JSON.parse(localStorage.getItem('grid-state'));\n * ```\n *\n * @see {@link ColumnState} for individual column state\n * @see {@link PublicGrid.getColumnState} for retrieving state\n */\nexport interface GridColumnState {\n /** Array of column states. */\n columns: ColumnState[];\n}\n// #endregion\n\n// #region Public Event Detail Interfaces\n/**\n * Detail for a cell click event.\n * Provides full context about the clicked cell including row data.\n *\n * @example\n * ```typescript\n * grid.on('cell-click', ({ row, field, value, rowIndex, colIndex }) => {\n * console.log(`Clicked ${field} = ${value} in row ${rowIndex}`);\n *\n * // Access the full row data\n * if (row.status === 'pending') {\n * showApprovalDialog(row);\n * }\n * });\n * ```\n *\n * @category Events\n */\nexport interface CellClickDetail<TRow = unknown> {\n /** Zero-based row index of the clicked cell. */\n rowIndex: number;\n /** Zero-based column index of the clicked cell. */\n colIndex: number;\n /** Column configuration object for the clicked cell. */\n column: ColumnConfig<TRow>;\n /** Field name of the clicked column. */\n field: string;\n /** Cell value at the clicked position. */\n value: unknown;\n /** Full row data object. */\n row: TRow;\n /** The clicked cell element. */\n cellEl: HTMLElement;\n /** The original mouse event. */\n originalEvent: MouseEvent;\n}\n\n/**\n * Detail for a row click event.\n * Provides context about the clicked row.\n *\n * @example\n * ```typescript\n * grid.on('row-click', ({ row, rowIndex, rowEl }) => {\n * console.log(`Clicked row ${rowIndex}: ${row.name}`);\n *\n * // Highlight the row\n * rowEl.classList.add('selected');\n *\n * // Open detail panel\n * showDetailPanel(row);\n * });\n * ```\n *\n * @category Events\n */\nexport interface RowClickDetail<TRow = unknown> {\n /** Zero-based row index of the clicked row. */\n rowIndex: number;\n /** Full row data object. */\n row: TRow;\n /** The clicked row element. */\n rowEl: HTMLElement;\n /** The original mouse event. */\n originalEvent: MouseEvent;\n}\n\n/**\n * Detail for a sort change (direction 0 indicates cleared sort).\n *\n * @example\n * ```typescript\n * grid.on('sort-change', ({ field, direction }) => {\n * if (direction === 0) {\n * console.log(`Sort cleared on ${field}`);\n * } else {\n * const dir = direction === 1 ? 'ascending' : 'descending';\n * console.log(`Sorted by ${field} ${dir}`);\n * }\n *\n * // Fetch sorted data from server\n * fetchData({ sortBy: field, sortDir: direction });\n * });\n * ```\n *\n * @see {@link SortState} for the sort state object\n * @see {@link SortHandler} for custom sort handlers\n * @category Events\n */\nexport interface SortChangeDetail {\n /** Sorted field key. */\n field: string;\n /** Direction: 1 ascending, -1 descending, 0 cleared. */\n direction: 1 | -1 | 0;\n}\n\n/**\n * Column resize event detail containing final pixel width.\n *\n * @example\n * ```typescript\n * grid.on('column-resize', ({ field, width }) => {\n * console.log(`Column ${field} resized to ${width}px`);\n *\n * // Persist to user preferences\n * saveColumnWidth(field, width);\n * });\n * ```\n *\n * @see {@link ColumnState} for persisting column state\n * @see {@link ResizeController} for resize implementation\n * @category Events\n */\nexport interface ColumnResizeDetail {\n /** Resized column field key. */\n field: string;\n /** New width in pixels. */\n width: number;\n}\n\n/**\n * Trigger type for cell activation.\n * - `'keyboard'`: Enter key pressed on focused cell\n * - `'pointer'`: Mouse/touch/pen click on cell\n *\n * @see {@link CellActivateDetail} for the activation event detail\n * @category Events\n */\nexport type CellActivateTrigger = 'keyboard' | 'pointer';\n\n/**\n * Fired when a cell is activated by user interaction (Enter key or click).\n * Unified event for both keyboard and pointer activation.\n *\n * @example\n * ```typescript\n * grid.on('cell-activate', ({ row, field, value, trigger, cellEl }, event) => {\n * if (trigger === 'keyboard') {\n * console.log('Activated via Enter key');\n * } else {\n * console.log('Activated via click/tap');\n * }\n *\n * // Start custom editing for specific columns\n * if (field === 'notes') {\n * event.preventDefault(); // Prevent default editing\n * openNotesEditor(row, cellEl);\n * }\n * });\n * ```\n *\n * @see {@link CellClickDetail} for click-only events\n * @see {@link CellActivateTrigger} for trigger types\n * @category Events\n */\nexport interface CellActivateDetail<TRow = unknown> {\n /** Zero-based row index of the activated cell. */\n rowIndex: number;\n /** Zero-based column index of the activated cell. */\n colIndex: number;\n /** Field name of the activated column. */\n field: string;\n /** Cell value at the activated position. */\n value: unknown;\n /** Full row data object. */\n row: TRow;\n /** The activated cell element. */\n cellEl: HTMLElement;\n /** What triggered the activation. */\n trigger: CellActivateTrigger;\n /** The original event (KeyboardEvent for keyboard, MouseEvent/PointerEvent for pointer). */\n originalEvent: KeyboardEvent | MouseEvent | PointerEvent;\n}\n\n/**\n * @deprecated Use `CellActivateDetail` instead. Will be removed in next major version.\n * Kept for backwards compatibility. Will be removed in v2.\n *\n * @category Events\n */\nexport interface ActivateCellDetail {\n /** Zero-based row index now focused. */\n row: number;\n /** Zero-based column index now focused. */\n col: number;\n}\n\n/**\n * Event detail for mounting external view renderers.\n *\n * Emitted when a cell uses an external component spec (React, Angular, Vue)\n * and needs the framework adapter to mount the component.\n *\n * @example\n * ```typescript\n * // Framework adapter listens for this event\n * grid.on('mount-external-view', ({ placeholder, spec, context }) => {\n * // Mount framework component into placeholder\n * mountComponent(spec.component, placeholder, context);\n * });\n * ```\n *\n * @see {@link ColumnConfig.externalView} for external view spec\n * @see {@link FrameworkAdapter} for adapter interface\n * @category Framework Adapters\n */\nexport interface ExternalMountViewDetail<TRow = unknown> {\n placeholder: HTMLElement;\n spec: unknown;\n context: { row: TRow; value: unknown; field: string; column: unknown };\n}\n\n/**\n * Event detail for mounting external editor renderers.\n *\n * Emitted when a cell uses an external editor component spec and needs\n * the framework adapter to mount the editor with commit/cancel bindings.\n *\n * @example\n * ```typescript\n * // Framework adapter listens for this event\n * grid.on('mount-external-editor', ({ placeholder, spec, context }) => {\n * // Mount framework editor with commit/cancel wired\n * mountEditor(spec.component, placeholder, {\n * value: context.value,\n * onCommit: context.commit,\n * onCancel: context.cancel,\n * });\n * });\n * ```\n *\n * @see {@link ColumnEditorSpec} for external editor spec\n * @see {@link FrameworkAdapter} for adapter interface\n * @category Framework Adapters\n */\nexport interface ExternalMountEditorDetail<TRow = unknown> {\n placeholder: HTMLElement;\n spec: unknown;\n context: {\n row: TRow;\n value: unknown;\n field: string;\n column: unknown;\n commit: (v: unknown) => void;\n cancel: () => void;\n };\n}\n\n/**\n * Maps event names to their detail payload types.\n *\n * Used by {@link DataGridElement.on | grid.on()} and `addEventListener()` for\n * fully typed event handling. Plugins extend this map via module augmentation.\n *\n * @example\n * ```typescript\n * // Recommended: grid.on() auto-unwraps the detail\n * const off = grid.on('cell-click', ({ field, value, row }) => {\n * console.log(`Clicked ${field} = ${value}`);\n * });\n * off(); // unsubscribe\n *\n * // addEventListener works too (useful for { once, signal, capture })\n * grid.addEventListener('cell-click', (e) => {\n * console.log(e.detail.field);\n * }, { once: true });\n * ```\n *\n * @see {@link DataGridElement.on} for the recommended subscription API\n * @see {@link DataGridCustomEvent} for typed CustomEvent wrapper\n * @see {@link DGEvents} for event name constants\n * @category Events\n */\nexport interface DataGridEventMap<TRow = unknown> {\n /**\n * Fired when a cell is clicked.\n * Provides full context: row data, column config, cell element, and the original mouse event.\n *\n * @example\n * ```typescript\n * grid.on('cell-click', ({ row, field, value, cellEl }) => {\n * console.log(`Clicked ${field} = ${value}`);\n *\n * // Open a detail dialog for a specific column\n * if (field === 'avatar') {\n * showImagePreview(row.avatarUrl, cellEl);\n * }\n * });\n * ```\n *\n * @see {@link CellClickDetail}\n * @group Core Events\n */\n 'cell-click': CellClickDetail<TRow>;\n\n /**\n * Fired when a row is clicked (anywhere on the row).\n * Use for row-level actions like opening a detail panel or navigating.\n *\n * @example\n * ```typescript\n * grid.on('row-click', ({ row, rowIndex }) => {\n * console.log(`Row ${rowIndex}: ${row.name}`);\n *\n * // Navigate to detail page\n * router.navigate(`/employees/${row.id}`);\n * });\n * ```\n *\n * @see {@link RowClickDetail}\n * @group Core Events\n */\n 'row-click': RowClickDetail<TRow>;\n\n /**\n * Fired when a cell is activated by Enter key or pointer click.\n * Unified event for both keyboard and pointer activation — use this\n * instead of the deprecated `activate-cell`.\n *\n * Call `event.preventDefault()` to suppress default behavior (e.g., inline editing).\n *\n * @example\n * ```typescript\n * grid.on('cell-activate', ({ row, field, trigger, cellEl }, event) => {\n * // Custom editing for a specific column\n * if (field === 'notes') {\n * event.preventDefault();\n * openRichTextEditor(row, cellEl);\n * }\n *\n * console.log(`Activated via ${trigger}`); // 'keyboard' | 'pointer'\n * });\n * ```\n *\n * @see {@link CellActivateDetail}\n * @see {@link CellActivateTrigger}\n * @group Core Events\n */\n 'cell-activate': CellActivateDetail<TRow>;\n\n /**\n * Fired after any data mutation — user edits, cascade updates, or API calls.\n * This is an informational event for logging, auditing, or cascading updates\n * to related fields. Check `source` to distinguish edit origins.\n *\n * @example\n * ```typescript\n * grid.on('cell-change', ({ row, rowId, field, oldValue, newValue, source }) => {\n * console.log(`${field}: ${oldValue} → ${newValue} (${source})`);\n *\n * // Cascade: recalculate total when quantity changes\n * if (source === 'user' && field === 'quantity') {\n * grid.updateRow(rowId, { total: newValue * row.price });\n * }\n * });\n * ```\n *\n * @see {@link CellChangeDetail}\n * @see {@link UpdateSource}\n * @group Core Events\n */\n 'cell-change': CellChangeDetail<TRow>;\n\n /**\n * Fired whenever the grid's row data changes — new data assignment,\n * row insertion/removal, or in-place mutations via `updateRow()`.\n * Use to keep external UI (row counts, summaries, charts) in sync.\n *\n * @example\n * ```typescript\n * grid.on('data-change', ({ rowCount, sourceRowCount }) => {\n * statusBar.textContent = `${rowCount} of ${sourceRowCount} rows`;\n * });\n * ```\n *\n * @see {@link DataChangeDetail}\n * @group Core Events\n */\n 'data-change': DataChangeDetail;\n\n /**\n * Emitted when a cell with an external view renderer (React, Angular, Vue component)\n * needs to be mounted. Framework adapters listen for this event internally.\n *\n * @example\n * ```typescript\n * // Custom framework adapter\n * grid.on('mount-external-view', ({ placeholder, spec, context }) => {\n * myFramework.render(spec.component, placeholder, {\n * row: context.row,\n * value: context.value,\n * });\n * });\n * ```\n *\n * @see {@link ExternalMountViewDetail}\n * @see {@link FrameworkAdapter}\n * @group Framework Adapter Events\n */\n 'mount-external-view': ExternalMountViewDetail<TRow>;\n\n /**\n * Emitted when a cell with an external editor component (React, Angular, Vue)\n * needs to be mounted with commit/cancel bindings. Framework adapters listen\n * for this event internally.\n *\n * @example\n * ```typescript\n * // Custom framework adapter\n * grid.on('mount-external-editor', ({ placeholder, spec, context }) => {\n * myFramework.render(spec.component, placeholder, {\n * value: context.value,\n * onSave: context.commit,\n * onCancel: context.cancel,\n * });\n * });\n * ```\n *\n * @see {@link ExternalMountEditorDetail}\n * @see {@link FrameworkAdapter}\n * @group Framework Adapter Events\n */\n 'mount-external-editor': ExternalMountEditorDetail<TRow>;\n\n /**\n * Fired when the sort state changes — column header click, programmatic sort,\n * or sort cleared. `direction: 0` indicates the sort was removed.\n *\n * @example\n * ```typescript\n * grid.on('sort-change', ({ field, direction }) => {\n * if (direction === 0) {\n * console.log('Sort cleared');\n * } else {\n * console.log(`Sorted by ${field} ${direction === 1 ? 'ASC' : 'DESC'}`);\n * }\n *\n * // Server-side sorting\n * fetchData({ sortBy: field, sortDir: direction });\n * });\n * ```\n *\n * @see {@link SortChangeDetail}\n * @see {@link SortHandler}\n * @group Core Events\n */\n 'sort-change': SortChangeDetail;\n\n /**\n * Fired when a column is resized by the user dragging the resize handle.\n * Use to persist column widths to user preferences or localStorage.\n *\n * @example\n * ```typescript\n * grid.on('column-resize', ({ field, width }) => {\n * console.log(`Column \"${field}\" resized to ${width}px`);\n *\n * // Persist to localStorage\n * const widths = JSON.parse(localStorage.getItem('col-widths') ?? '{}');\n * widths[field] = width;\n * localStorage.setItem('col-widths', JSON.stringify(widths));\n * });\n * ```\n *\n * @see {@link ColumnResizeDetail}\n * @group Core Events\n */\n 'column-resize': ColumnResizeDetail;\n\n /**\n * @deprecated Use `cell-activate` instead. Will be removed in v2.\n * @see {@link ActivateCellDetail}\n * @group Core Events\n */\n 'activate-cell': ActivateCellDetail;\n\n /**\n * Fired when column state changes — reordering, resizing, visibility toggle,\n * or sort changes. Use with `getColumnState()` / `columnState` setter for\n * full state persistence.\n *\n * @example\n * ```typescript\n * grid.on('column-state-change', (state) => {\n * localStorage.setItem('grid-state', JSON.stringify(state));\n * console.log(`${state.columns.length} columns in state`);\n * });\n *\n * // Restore on load\n * const saved = localStorage.getItem('grid-state');\n * if (saved) grid.columnState = JSON.parse(saved);\n * ```\n *\n * @see {@link GridColumnState}\n * @see {@link ColumnState}\n * @group Core Events\n */\n 'column-state-change': GridColumnState;\n\n // Note: 'cell-commit', 'row-commit', 'changed-rows-reset' are added via\n // module augmentation by EditingPlugin when imported\n}\n\n/**\n * Extracts the event detail type for a given event name.\n *\n * Utility type for getting the detail payload type of a specific event.\n *\n * @example\n * ```typescript\n * // Extract detail type for specific event\n * type ClickDetail = DataGridEventDetail<'cell-click', Employee>;\n * // Equivalent to: CellClickDetail<Employee>\n *\n * // Use in generic handler\n * function logDetail<K extends keyof DataGridEventMap>(\n * eventName: K,\n * detail: DataGridEventDetail<K>,\n * ): void {\n * console.log(`${eventName}:`, detail);\n * }\n * ```\n *\n * @see {@link DataGridEventMap} for all event types\n * @category Events\n */\nexport type DataGridEventDetail<K extends keyof DataGridEventMap<unknown>, TRow = unknown> = DataGridEventMap<TRow>[K];\n\n/**\n * Custom event type for DataGrid events with typed detail payload.\n *\n * Primarily useful when you need to declare handler parameters with\n * `addEventListener`. For most use cases, prefer {@link DataGridElement.on | grid.on()}\n * which handles typing automatically.\n *\n * @example\n * ```typescript\n * // Typed handler for addEventListener\n * function onCellClick(e: DataGridCustomEvent<'cell-click', Employee>): void {\n * const { row, field, value } = e.detail;\n * console.log(`Clicked ${field} = ${value} on ${row.name}`);\n * }\n * grid.addEventListener('cell-click', onCellClick);\n *\n * // With grid.on() you don't need this type — it's inferred:\n * grid.on('cell-click', ({ row, field, value }) => {\n * console.log(`Clicked ${field} = ${value} on ${row.name}`);\n * });\n * ```\n *\n * @see {@link DataGridElement.on} for the recommended subscription API\n * @see {@link DataGridEventMap} for all event types\n * @see `DataGridEventDetail` for extracting detail type only\n * @category Events\n */\nexport type DataGridCustomEvent<K extends keyof DataGridEventMap<unknown>, TRow = unknown> = CustomEvent<\n DataGridEventMap<TRow>[K]\n>;\n\n/**\n * Template evaluation context for dynamic templates.\n *\n * @category Plugin Development\n */\nexport interface EvalContext {\n value: unknown;\n row: Record<string, unknown> | null;\n}\n// #endregion\n","/**\n * Centralized diagnostic messages for @toolbox-web/grid.\n *\n * Every user-facing warning, error, or info message in the grid has a unique\n * diagnostic code (e.g. `TBW001`). Each code maps to a section on the online\n * troubleshooting page, giving developers a direct link to resolution steps.\n *\n * ## Usage\n *\n * ```ts\n * import { MISSING_BREAKPOINT, warnDiagnostic, throwDiagnostic } from './diagnostics';\n *\n * // Warn with a code\n * warnDiagnostic(MISSING_BREAKPOINT, 'Set a breakpoint...', gridId);\n *\n * // Throw with a code\n * throwDiagnostic(MISSING_ROW_ID, 'Configure getRowId...', gridId);\n * ```\n *\n * Plugins should prefer `this.warn(MISSING_BREAKPOINT, message)` via BaseGridPlugin\n * instead of importing this module directly.\n *\n * @internal\n */\n\n// #region Grid Prefix\n\n/**\n * Build the `[tbw-grid]` or `[tbw-grid#my-id]` log prefix.\n * Pass `pluginName` for a scoped prefix like `[tbw-grid:SelectionPlugin]`.\n */\nexport function gridPrefix(gridId?: string, pluginName?: string): string {\n const id = gridId ? `#${gridId}` : '';\n const plugin = pluginName ? `:${pluginName}` : '';\n return `[tbw-grid${id}${plugin}]`;\n}\n\n// #endregion\n\n// #region Diagnostic Codes\n\n/**\n * Diagnostic codes used across the grid library.\n *\n * Each code is an individual export so that unused codes are tree-shaken\n * from bundles that don't reference them (esbuild can't tree-shake\n * properties from a single object).\n *\n * Naming: TBW + 3-digit number.\n * Ranges:\n * 001–019 Configuration validation (missing plugins, bad config)\n * 020–029 Plugin lifecycle (dependencies, incompatibilities, deprecation)\n * 030–039 Feature registry\n * 040–049 Row operations (row ID, row mutations)\n * 050–059 Column operations (width, template)\n * 060–069 Rendering (callbacks, formatters, external views)\n * 070–079 Shell (tool panels, header/toolbar content)\n * 080–089 Editing & editors\n * 090–099 Print\n * 100–109 Clipboard\n * 110–119 Plugin-specific (responsive, undo-redo, grouping-columns)\n * 120–129 Style injection\n * 130–139 Attribute parsing\n */\n\n// --- Config validation (001–019) ---\n/** Column uses a plugin-owned property but the plugin is not loaded. */\nexport const MISSING_PLUGIN = 'TBW001' as const;\n/** Grid config uses a plugin-owned property but the plugin is not loaded. */\nexport const MISSING_PLUGIN_CONFIG = 'TBW002' as const;\n/** Plugin config rule violation (error severity). */\nexport const CONFIG_RULE_ERROR = 'TBW003' as const;\n/** Plugin config rule violation (warning severity). */\nexport const CONFIG_RULE_WARN = 'TBW004' as const;\n\n// --- Plugin lifecycle (020–029) ---\n/** Required plugin dependency is missing. */\nexport const MISSING_DEPENDENCY = 'TBW020' as const;\n/** Optional plugin dependency is missing. */\nexport const OPTIONAL_DEPENDENCY = 'TBW021' as const;\n/** Two loaded plugins are incompatible. */\nexport const INCOMPATIBLE_PLUGINS = 'TBW022' as const;\n/** Plugin uses deprecated hooks. */\nexport const DEPRECATED_HOOK = 'TBW023' as const;\n/** Error thrown inside a plugin event handler. */\nexport const PLUGIN_EVENT_ERROR = 'TBW024' as const;\n\n// --- Feature registry (030–039) ---\n/** Feature was re-registered (overwritten). */\nexport const FEATURE_REREGISTERED = 'TBW030' as const;\n/** Feature configured but not imported. */\nexport const FEATURE_NOT_IMPORTED = 'TBW031' as const;\n/** Feature depends on another feature that is not enabled. */\nexport const FEATURE_MISSING_DEP = 'TBW032' as const;\n\n// --- Row operations (040–049) ---\n/** Cannot determine row ID (no getRowId and no id property). */\nexport const MISSING_ROW_ID = 'TBW040' as const;\n/** Row with given ID not found. */\nexport const ROW_NOT_FOUND = 'TBW041' as const;\n\n// --- Column operations (050–059) ---\n/** Column has an invalid CSS width value. */\nexport const INVALID_COLUMN_WIDTH = 'TBW050' as const;\n\n// --- Rendering callbacks (060–069) ---\n/** rowClass callback threw an error. */\nexport const ROW_CLASS_ERROR = 'TBW060' as const;\n/** cellClass callback threw an error. */\nexport const CELL_CLASS_ERROR = 'TBW061' as const;\n/** Column format function threw an error. */\nexport const FORMAT_ERROR = 'TBW062' as const;\n/** External view mount() threw an error. */\nexport const VIEW_MOUNT_ERROR = 'TBW063' as const;\n/** External view event dispatch error. */\nexport const VIEW_DISPATCH_ERROR = 'TBW064' as const;\n\n// --- Shell (070–079) ---\n/** Tool panel missing required id or title. */\nexport const TOOL_PANEL_MISSING_ATTR = 'TBW070' as const;\n/** No tool panels registered. */\nexport const NO_TOOL_PANELS = 'TBW071' as const;\n/** Tool panel section not found. */\nexport const TOOL_PANEL_NOT_FOUND = 'TBW072' as const;\n/** Tool panel already registered. */\nexport const TOOL_PANEL_DUPLICATE = 'TBW073' as const;\n/** Header content already registered. */\nexport const HEADER_CONTENT_DUPLICATE = 'TBW074' as const;\n/** Toolbar content already registered. */\nexport const TOOLBAR_CONTENT_DUPLICATE = 'TBW075' as const;\n\n// --- Editing & editors (080–089) ---\n/** External editor mount() threw an error. */\nexport const EDITOR_MOUNT_ERROR = 'TBW080' as const;\n\n// --- Print (090–099) ---\n/** Print already in progress. */\nexport const PRINT_IN_PROGRESS = 'TBW090' as const;\n/** Grid not available for printing. */\nexport const PRINT_NO_GRID = 'TBW091' as const;\n/** Print operation failed. */\nexport const PRINT_FAILED = 'TBW092' as const;\n/** Multiple elements share the same grid ID (print isolation issue). */\nexport const PRINT_DUPLICATE_ID = 'TBW093' as const;\n\n// --- Clipboard (100–109) ---\n/** Clipboard API write failed. */\nexport const CLIPBOARD_FAILED = 'TBW100' as const;\n\n// --- Plugin-specific (110–119) ---\n/** ResponsivePlugin: no breakpoint configured. */\nexport const MISSING_BREAKPOINT = 'TBW110' as const;\n/** UndoRedoPlugin: transaction already in progress. */\nexport const TRANSACTION_IN_PROGRESS = 'TBW111' as const;\n/** UndoRedoPlugin: no transaction in progress. */\nexport const NO_TRANSACTION = 'TBW112' as const;\n/** GroupingColumnsPlugin: missing id or header on column group definition. */\nexport const COLUMN_GROUP_NO_ID = 'TBW113' as const;\n/** GroupingColumnsPlugin: conflicting columnGroups sources. */\nexport const COLUMN_GROUPS_CONFLICT = 'TBW114' as const;\n\n// --- Style injection (120–129) ---\n/** Failed to extract grid.css from document stylesheets. */\nexport const STYLE_EXTRACT_FAILED = 'TBW120' as const;\n/** Could not find grid.css in document.styleSheets. */\nexport const STYLE_NOT_FOUND = 'TBW121' as const;\n\n// --- Attribute parsing (130–139) ---\n/** Invalid JSON in an HTML attribute. */\nexport const INVALID_ATTRIBUTE_JSON = 'TBW130' as const;\n\nexport type DiagnosticCode =\n | typeof MISSING_PLUGIN\n | typeof MISSING_PLUGIN_CONFIG\n | typeof CONFIG_RULE_ERROR\n | typeof CONFIG_RULE_WARN\n | typeof MISSING_DEPENDENCY\n | typeof OPTIONAL_DEPENDENCY\n | typeof INCOMPATIBLE_PLUGINS\n | typeof DEPRECATED_HOOK\n | typeof PLUGIN_EVENT_ERROR\n | typeof FEATURE_REREGISTERED\n | typeof FEATURE_NOT_IMPORTED\n | typeof FEATURE_MISSING_DEP\n | typeof MISSING_ROW_ID\n | typeof ROW_NOT_FOUND\n | typeof INVALID_COLUMN_WIDTH\n | typeof ROW_CLASS_ERROR\n | typeof CELL_CLASS_ERROR\n | typeof FORMAT_ERROR\n | typeof VIEW_MOUNT_ERROR\n | typeof VIEW_DISPATCH_ERROR\n | typeof TOOL_PANEL_MISSING_ATTR\n | typeof NO_TOOL_PANELS\n | typeof TOOL_PANEL_NOT_FOUND\n | typeof TOOL_PANEL_DUPLICATE\n | typeof HEADER_CONTENT_DUPLICATE\n | typeof TOOLBAR_CONTENT_DUPLICATE\n | typeof EDITOR_MOUNT_ERROR\n | typeof PRINT_IN_PROGRESS\n | typeof PRINT_NO_GRID\n | typeof PRINT_FAILED\n | typeof PRINT_DUPLICATE_ID\n | typeof CLIPBOARD_FAILED\n | typeof MISSING_BREAKPOINT\n | typeof TRANSACTION_IN_PROGRESS\n | typeof NO_TRANSACTION\n | typeof COLUMN_GROUP_NO_ID\n | typeof COLUMN_GROUPS_CONFLICT\n | typeof STYLE_EXTRACT_FAILED\n | typeof STYLE_NOT_FOUND\n | typeof INVALID_ATTRIBUTE_JSON;\n\n// #endregion\n\n// #region Docs URL\n\nconst DOCS_BASE = 'https://toolboxjs.com/grid/errors';\n\n/** Build a direct link to the troubleshooting section for a code. */\nfunction docsUrl(code: DiagnosticCode): string {\n return `${DOCS_BASE}#${code.toLowerCase()}`;\n}\n\n// #endregion\n\n// #region Formatting\n\n/**\n * Format a diagnostic message with prefix, code, and docs link.\n *\n * Output format:\n * ```\n * [tbw-grid#my-id] TBW001: Your message here.\n *\n * → More info: https://toolboxjs.com/grid/errors#tbw001\n * ```\n */\nexport function formatDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): string {\n const prefix = gridPrefix(gridId, pluginName);\n return `${prefix} ${code}: ${message}\\n\\n → More info: ${docsUrl(code)}`;\n}\n\n// #endregion\n\n// #region Public API\n\n/**\n * Throw an error with a diagnostic code and docs link.\n * Use for configuration errors and API misuse that should halt execution.\n */\nexport function throwDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): never {\n throw new Error(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log a warning with a diagnostic code and docs link.\n * Use for recoverable issues the developer should fix.\n */\nexport function warnDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.warn(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log a debug message with a diagnostic code and docs link.\n * Use for optional/soft dependency notifications — visible only when\n * the browser DevTools \"Verbose\" log level is enabled.\n */\nexport function debugDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.debug(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n/**\n * Log an error with a diagnostic code and docs link.\n * Use for non-throwing errors (e.g., failed async operations).\n */\nexport function errorDiagnostic(code: DiagnosticCode, message: string, gridId?: string, pluginName?: string): void {\n console.error(formatDiagnostic(code, message, gridId, pluginName));\n}\n\n// #endregion\n","import type { ColumnConfig, ColumnInternal, ElementWithPart, GridHost, PrimitiveColumnType } from '../types';\nimport { FitModeEnum } from '../types';\nimport { INVALID_COLUMN_WIDTH, warnDiagnostic } from './diagnostics';\n\n// #region Light DOM Parsing\n/** Global DataGridElement class (may or may not be registered) */\ninterface DataGridElementClass {\n getAdapters?: () => readonly {\n canHandle: (el: HTMLElement) => boolean;\n createRenderer: (el: HTMLElement) => ((ctx: unknown) => Node | string | void) | undefined;\n createEditor: (el: HTMLElement) => ((ctx: unknown) => HTMLElement | string) | undefined;\n }[];\n}\n\n/**\n * Parse `<tbw-grid-column>` elements from the host light DOM into column config objects,\n * capturing template elements for later cloning / compilation.\n */\nexport function parseLightDomColumns(host: HTMLElement): ColumnInternal[] {\n const domColumns = Array.from(host.querySelectorAll('tbw-grid-column')) as HTMLElement[];\n return domColumns\n .map((el) => {\n const field = el.getAttribute('field') || '';\n if (!field) return null;\n const rawType = el.getAttribute('type') || undefined;\n const allowedTypes = new Set<PrimitiveColumnType>(['number', 'string', 'date', 'boolean', 'select']);\n const type =\n rawType && allowedTypes.has(rawType as PrimitiveColumnType) ? (rawType as PrimitiveColumnType) : undefined;\n const header = el.getAttribute('header') || undefined;\n const sortable = el.hasAttribute('sortable');\n const editable = el.hasAttribute('editable');\n const config: ColumnInternal = { field, type, header, sortable, editable };\n\n // Parse width attribute (supports px values, percentages, or plain numbers)\n const widthAttr = el.getAttribute('width');\n if (widthAttr) {\n const numericWidth = parseFloat(widthAttr);\n if (!isNaN(numericWidth) && /^\\d+(\\.\\d+)?$/.test(widthAttr.trim())) {\n config.width = numericWidth;\n } else {\n config.width = widthAttr; // e.g. \"100px\", \"20%\", \"1fr\"\n }\n }\n\n // Parse minWidth attribute (numeric only)\n const minWidthAttr = el.getAttribute('minWidth') || el.getAttribute('min-width');\n if (minWidthAttr) {\n const numericMinWidth = parseFloat(minWidthAttr);\n if (!isNaN(numericMinWidth)) {\n config.minWidth = numericMinWidth;\n }\n }\n\n if (el.hasAttribute('resizable')) config.resizable = true;\n if (el.hasAttribute('sizable')) config.resizable = true; // legacy attribute support\n\n // Parse editor and renderer attribute names for programmatic lookup\n const editorName = el.getAttribute('editor');\n const rendererName = el.getAttribute('renderer');\n if (editorName) config.__editorName = editorName;\n if (rendererName) config.__rendererName = rendererName;\n\n // Parse options attribute for select/typeahead: \"value1:Label1,value2:Label2\" or \"value1,value2\"\n const optionsAttr = el.getAttribute('options');\n if (optionsAttr) {\n config.options = optionsAttr.split(',').map((item) => {\n const [value, label] = item.includes(':') ? item.split(':') : [item.trim(), item.trim()];\n return { value: value.trim(), label: label?.trim() || value.trim() };\n });\n }\n const viewTpl = el.querySelector('tbw-grid-column-view');\n const editorTpl = el.querySelector('tbw-grid-column-editor');\n const headerTpl = el.querySelector('tbw-grid-column-header');\n if (viewTpl) config.__viewTemplate = viewTpl as HTMLElement;\n if (editorTpl) config.__editorTemplate = editorTpl as HTMLElement;\n if (headerTpl) config.__headerTemplate = headerTpl as HTMLElement;\n\n // Check if framework adapters can handle template wrapper elements or the column element itself\n // React adapter registers on the column element, Angular uses inner template wrappers\n const DataGridElementClassRef = (globalThis as { DataGridElement?: DataGridElementClass }).DataGridElement;\n const adapters = DataGridElementClassRef?.getAdapters?.() ?? [];\n\n // First check inner view template, then column element itself\n const viewTarget = (viewTpl ?? el) as HTMLElement;\n const viewAdapter = adapters.find((a) => a.canHandle(viewTarget));\n if (viewAdapter) {\n // Only assign if adapter returns a truthy renderer\n // Adapters return undefined when only an editor is registered (no view template)\n const renderer = viewAdapter.createRenderer(viewTarget);\n if (renderer) {\n config.viewRenderer = renderer;\n }\n }\n\n // First check inner editor template, then column element itself\n const editorTarget = (editorTpl ?? el) as HTMLElement;\n const editorAdapter = adapters.find((a) => a.canHandle(editorTarget));\n if (editorAdapter) {\n // Only assign if adapter returns a truthy editor\n const editor = editorAdapter.createEditor(editorTarget);\n if (editor) {\n config.editor = editor;\n }\n }\n\n return config;\n })\n .filter((c): c is ColumnInternal => !!c);\n}\n// #endregion\n\n// #region Column Merging\n/**\n * Merge programmatic columns with light DOM columns by field name, allowing DOM-provided\n * attributes / templates to supplement (not overwrite) programmatic definitions.\n * Any DOM columns without a programmatic counterpart are appended.\n * When multiple DOM columns exist for the same field (e.g., separate renderer and editor),\n * their properties are merged together.\n */\nexport function mergeColumns(\n programmatic: ColumnConfig[] | undefined,\n dom: ColumnConfig[] | undefined,\n): ColumnInternal[] {\n if ((!programmatic || !programmatic.length) && (!dom || !dom.length)) return [];\n if (!programmatic || !programmatic.length) return (dom || []) as ColumnInternal[];\n if (!dom || !dom.length) return programmatic as ColumnInternal[];\n\n // Build domMap by merging multiple DOM columns with the same field\n // This supports React pattern where renderer and editor are in separate GridColumn elements\n const domMap: Record<string, ColumnInternal> = {};\n (dom as ColumnInternal[]).forEach((c) => {\n const existing = domMap[c.field];\n if (existing) {\n // Merge this column's properties into the existing one\n if (c.header && !existing.header) existing.header = c.header;\n if (c.type && !existing.type) existing.type = c.type;\n if (c.sortable) existing.sortable = true;\n if (c.editable) existing.editable = true;\n if (c.resizable) existing.resizable = true;\n if (c.width != null && existing.width == null) existing.width = c.width;\n if (c.minWidth != null && existing.minWidth == null) existing.minWidth = c.minWidth;\n if (c.__viewTemplate) existing.__viewTemplate = c.__viewTemplate;\n if (c.__editorTemplate) existing.__editorTemplate = c.__editorTemplate;\n if (c.__headerTemplate) existing.__headerTemplate = c.__headerTemplate;\n // Support both 'renderer' alias and 'viewRenderer'\n const cRenderer = c.renderer || c.viewRenderer;\n const existingRenderer = existing.renderer || existing.viewRenderer;\n if (cRenderer && !existingRenderer) {\n existing.viewRenderer = cRenderer;\n if (c.renderer) existing.renderer = cRenderer;\n }\n if (c.editor && !existing.editor) existing.editor = c.editor;\n } else {\n domMap[c.field] = { ...c };\n }\n });\n\n const merged: ColumnInternal[] = (programmatic as ColumnInternal[]).map((c) => {\n const d = domMap[c.field];\n if (!d) return c;\n const m: ColumnInternal = { ...c };\n if (d.header && !m.header) m.header = d.header;\n if (d.type && !m.type) m.type = d.type;\n m.sortable = c.sortable || d.sortable;\n if (c.resizable === true || d.resizable === true) m.resizable = true;\n m.editable = c.editable || d.editable;\n // Merge width/minWidth from DOM if not set programmatically\n if (d.width != null && m.width == null) m.width = d.width;\n if (d.minWidth != null && m.minWidth == null) m.minWidth = d.minWidth;\n if (d.__viewTemplate) m.__viewTemplate = d.__viewTemplate;\n if (d.__editorTemplate) m.__editorTemplate = d.__editorTemplate;\n if (d.__headerTemplate) m.__headerTemplate = d.__headerTemplate;\n // Merge framework adapter renderers/editors from DOM (support both 'renderer' alias and 'viewRenderer')\n const dRenderer = d.renderer || d.viewRenderer;\n const mRenderer = m.renderer || m.viewRenderer;\n if (dRenderer && !mRenderer) {\n m.viewRenderer = dRenderer;\n if (d.renderer) m.renderer = dRenderer;\n }\n if (d.editor && !m.editor) m.editor = d.editor;\n delete domMap[c.field];\n return m;\n });\n Object.keys(domMap).forEach((field) => merged.push(domMap[field]));\n return merged;\n}\n// #endregion\n\n// #region Part Helpers\n/**\n * Safely add a token to an element's `part` attribute (supporting the CSS ::part API)\n * without duplicating values. Falls back to string manipulation if `el.part` API isn't present.\n */\nexport function addPart(el: HTMLElement, token: string): void {\n try {\n (el as ElementWithPart).part?.add?.(token);\n } catch {\n /* empty */\n }\n const existing = el.getAttribute('part');\n if (!existing) el.setAttribute('part', token);\n else if (!existing.split(/\\s+/).includes(token)) el.setAttribute('part', existing + ' ' + token);\n}\n// #endregion\n\n// #region Auto-Sizing\n/**\n * Measure rendered header + visible cell content to assign initial pixel widths\n * to columns when in `content` fit mode. Runs only once unless fit mode changes.\n */\nexport function autoSizeColumns(grid: GridHost): void {\n const mode = grid.effectiveConfig?.fitMode || grid.fitMode || FitModeEnum.STRETCH;\n // Run for both stretch (to derive baseline pixel widths before fr distribution) and fixed.\n if (mode !== FitModeEnum.STRETCH && mode !== FitModeEnum.FIXED) return;\n if (grid.__didInitialAutoSize) return;\n if (!grid.isConnected) return;\n const headerCells = Array.from(grid._headerRowEl?.children || []) as HTMLElement[];\n if (!headerCells.length) return;\n let changed = false;\n grid._visibleColumns.forEach((col: ColumnInternal, i: number) => {\n if (col.width) return;\n const headerCell = headerCells[i];\n let max = headerCell ? headerCell.scrollWidth : 0;\n for (const rowEl of grid._rowPool) {\n const cell = rowEl.children[i] as HTMLElement | undefined;\n if (cell) {\n const w = cell.scrollWidth;\n if (w > max) max = w;\n }\n }\n if (max > 0) {\n col.width = max + 2;\n col.__autoSized = true;\n changed = true;\n }\n });\n if (changed) updateTemplate(grid);\n grid.__didInitialAutoSize = true;\n}\n// #endregion\n\n// #region Template Generation\n/**\n * Compute and apply the CSS grid template string that drives column layout.\n * Uses `fr` units for flexible (non user-resized) columns in stretch mode, otherwise\n * explicit pixel widths or auto sizing.\n */\n// Valid CSS grid track size patterns: numbers with units (px, %, fr, em, rem, etc.),\n// calc(), min-content, max-content, minmax(), fit-content(), auto\nconst VALID_CSS_WIDTH =\n /^(?:\\d+(?:\\.\\d+)?(?:px|%|fr|em|rem|ch|vw|vh|vmin|vmax)|calc\\(.+\\)|min-content|max-content|minmax\\(.+\\)|fit-content\\(.+\\)|auto)$/i;\n\n/** Resolve a column width to a CSS grid track value. Numbers get `px` appended; strings pass through with a dev-mode validity check. */\nfunction resolveWidth(width: string | number, field?: string): string {\n if (typeof width === 'number') return `${width}px`;\n if (!VALID_CSS_WIDTH.test(width)) {\n warnDiagnostic(\n INVALID_COLUMN_WIDTH,\n `Column '${field ?? '?'}' has an invalid CSS width value: '${width}'. Expected a number (px) or a valid CSS unit string (e.g. '30%', '2fr', 'calc(...)').`,\n );\n }\n return width;\n}\n\nexport function updateTemplate(grid: GridHost): void {\n // Modes:\n // - 'stretch': columns with explicit width use that width; columns without width are flexible\n // Uses minmax(minWidth, maxWidth) when both min/max specified (bounded flex)\n // Uses minmax(minWidth, 1fr) when only min specified (grows unbounded)\n // Uses minmax(defaultMin, maxWidth) when only max specified (capped growth)\n // - 'fixed': columns with explicit width use that width; columns without width use max-content\n const mode = grid.effectiveConfig?.fitMode || grid.fitMode || FitModeEnum.STRETCH;\n\n if (mode === FitModeEnum.STRETCH) {\n grid._gridTemplate = grid._visibleColumns\n .map((c: ColumnInternal) => {\n if (c.width != null) return resolveWidth(c.width, c.field);\n // Flexible column: pure 1fr unless minWidth specified\n const min = c.minWidth;\n return min != null ? `minmax(${min}px, 1fr)` : '1fr';\n })\n .join(' ')\n .trim();\n } else {\n // fixed mode: explicit pixel widths or max-content for content-based sizing\n grid._gridTemplate = grid._visibleColumns\n .map((c: ColumnInternal) => {\n if (c.width != null) return resolveWidth(c.width, c.field);\n return 'max-content';\n })\n .join(' ');\n }\n grid.style.setProperty('--tbw-column-template', grid._gridTemplate);\n}\n// #endregion\n","// Centralized template expression evaluation & sanitization utilities.\n// Responsible for safely interpolating {{ }} expressions while blocking\n// access to dangerous globals / reflective capabilities.\nimport type { CompiledViewFunction, EvalContext } from '../types';\n\n// #region Constants\nconst EXPR_RE = /{{\\s*([^}]+)\\s*}}/g;\nconst EMPTY_SENTINEL = '__DG_EMPTY__';\nconst SAFE_EXPR = /^[\\w$. '?+\\-*/%:()!<>=,&|]+$/;\nconst FORBIDDEN =\n /__(proto|defineGetter|defineSetter)|constructor|window|globalThis|global|process|Function|import|eval|Reflect|Proxy|Error|arguments|document|location|cookie|localStorage|sessionStorage|indexedDB|fetch|XMLHttpRequest|WebSocket|Worker|SharedWorker|ServiceWorker|opener|parent|top|frames|self|this\\b/;\n// #endregion\n\n// #region HTML Sanitization\n\n/**\n * Escape a plain text string for safe insertion into HTML.\n * Converts special HTML characters to their entity equivalents.\n *\n * @param text - Plain text string to escape\n * @returns HTML-safe string\n */\nexport function escapeHtml(text: string): string {\n if (!text || typeof text !== 'string') return '';\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Tags that are considered dangerous and will be completely removed.\n * These can execute scripts, load external resources, or manipulate the page.\n */\nconst DANGEROUS_TAGS = new Set([\n 'script',\n 'iframe',\n 'object',\n 'embed',\n 'form',\n 'input',\n 'button',\n 'textarea',\n 'select',\n 'link',\n 'meta',\n 'base',\n 'style',\n 'template',\n 'slot',\n 'portal',\n 'frame',\n 'frameset',\n 'applet',\n 'noscript',\n 'noembed',\n 'plaintext',\n 'xmp',\n 'listing',\n]);\n\n/**\n * Attributes that are considered dangerous - event handlers and data loading.\n */\nconst DANGEROUS_ATTR_PATTERN = /^on\\w+$/i;\n\n/**\n * Attributes that can contain URLs which might be javascript: or data: URIs.\n */\nconst URL_ATTRS = new Set(['href', 'src', 'action', 'formaction', 'data', 'srcdoc', 'xlink:href', 'poster', 'srcset']);\n\n/**\n * Protocol patterns that are dangerous in URLs.\n */\nconst DANGEROUS_URL_PROTOCOL = /^\\s*(javascript|vbscript|data|blob):/i;\n\n/**\n * Sanitize an HTML string by removing dangerous tags and attributes.\n * This is a defense-in-depth measure for content rendered via innerHTML.\n *\n * @param html - Raw HTML string to sanitize\n * @returns Sanitized HTML string safe for innerHTML\n */\nexport function sanitizeHTML(html: string): string {\n if (!html || typeof html !== 'string') return '';\n\n // Fast path: if no HTML tags at all, return as-is (already safe)\n if (html.indexOf('<') === -1) return html;\n\n const template = document.createElement('template');\n template.innerHTML = html;\n\n sanitizeNode(template.content);\n\n return template.innerHTML;\n}\n\n/**\n * Recursively sanitize a DOM node tree.\n */\nfunction sanitizeNode(root: DocumentFragment | Element): void {\n const toRemove: Element[] = [];\n\n // Use querySelectorAll to find all elements, then filter\n const elements = root.querySelectorAll('*');\n\n for (const el of elements) {\n const tagName = el.tagName.toLowerCase();\n\n // Check if tag is dangerous\n if (DANGEROUS_TAGS.has(tagName)) {\n toRemove.push(el);\n continue;\n }\n\n // SVG elements need special handling - they can contain script-like behavior\n if (tagName === 'svg' || el.namespaceURI === 'http://www.w3.org/2000/svg') {\n // Remove entire SVG if it has any suspicious attributes\n const hasDangerousContent = Array.from(el.attributes).some(\n (attr) => DANGEROUS_ATTR_PATTERN.test(attr.name) || attr.name === 'href' || attr.name === 'xlink:href',\n );\n if (hasDangerousContent) {\n toRemove.push(el);\n continue;\n }\n }\n\n // Check and remove dangerous attributes\n const attrsToRemove: string[] = [];\n for (const attr of el.attributes) {\n const attrName = attr.name.toLowerCase();\n\n // Event handlers (onclick, onerror, onload, etc.)\n if (DANGEROUS_ATTR_PATTERN.test(attrName)) {\n attrsToRemove.push(attr.name);\n continue;\n }\n\n // URL attributes with dangerous protocols\n if (URL_ATTRS.has(attrName) && DANGEROUS_URL_PROTOCOL.test(attr.value)) {\n attrsToRemove.push(attr.name);\n continue;\n }\n\n // style attribute can contain expressions (IE) or url() with javascript:\n if (attrName === 'style' && /expression\\s*\\(|javascript:|behavior\\s*:/i.test(attr.value)) {\n attrsToRemove.push(attr.name);\n continue;\n }\n }\n\n attrsToRemove.forEach((name) => el.removeAttribute(name));\n }\n\n // Remove dangerous elements (do this after iteration to avoid modifying during traversal)\n toRemove.forEach((el) => el.remove());\n}\n\n// #endregion\n\n// #region Template Evaluation\nexport function evalTemplateString(raw: string, ctx: EvalContext): string {\n if (!raw || raw.indexOf('{{') === -1) return raw; // fast path (no expressions)\n const parts: { expr: string; result: string }[] = [];\n const evaluated = raw.replace(EXPR_RE, (_m, expr) => {\n const res = evalSingle(expr, ctx);\n parts.push({ expr: expr.trim(), result: res });\n return res;\n });\n const finalStr = postProcess(evaluated);\n // If every part evaluated to EMPTY_SENTINEL we treat this as intentionally blank.\n // If any expression was blocked due to forbidden token (EMPTY_SENTINEL) we *still* only output ''\n // but do not escalate to BLOCKED_SENTINEL unless the original contained explicit forbidden tokens.\n const allEmpty = parts.length && parts.every((p) => p.result === '' || p.result === EMPTY_SENTINEL);\n const hadForbidden = REFLECTIVE_RE.test(raw);\n if (hadForbidden || allEmpty) return '';\n return finalStr;\n}\n\nfunction evalSingle(expr: string, ctx: EvalContext): string {\n expr = (expr || '').trim();\n if (!expr) return EMPTY_SENTINEL;\n if (REFLECTIVE_RE.test(expr)) return EMPTY_SENTINEL;\n if (expr === 'value') return ctx.value == null ? EMPTY_SENTINEL : String(ctx.value);\n if (expr.startsWith('row.') && !/[()?]/.test(expr) && !expr.includes(':')) {\n const key = expr.slice(4);\n const v = ctx.row ? ctx.row[key] : undefined;\n return v == null ? EMPTY_SENTINEL : String(v);\n }\n if (expr.length > 80) return EMPTY_SENTINEL;\n if (!SAFE_EXPR.test(expr) || FORBIDDEN.test(expr)) return EMPTY_SENTINEL;\n const dotChain = expr.match(/\\./g);\n if (dotChain && dotChain.length > 1) return EMPTY_SENTINEL;\n try {\n const fn = new Function('value', 'row', `return (${expr});`);\n const out = fn(ctx.value, ctx.row);\n const str = out == null ? '' : String(out);\n if (REFLECTIVE_RE.test(str)) return EMPTY_SENTINEL;\n return str || EMPTY_SENTINEL;\n } catch {\n return EMPTY_SENTINEL;\n }\n}\n// #endregion\n\n// #region Cell Scrubbing\n/** Pattern matching reflective/introspective APIs that must be stripped from output. */\nconst REFLECTIVE_RE = /Reflect|Proxy|ownKeys/;\n\nfunction postProcess(s: string): string {\n if (!s) return s;\n return s.replace(new RegExp(EMPTY_SENTINEL, 'g'), '').replace(/Reflect\\.[^<>{}\\s]+|\\bProxy\\b|ownKeys\\([^)]*\\)/g, '');\n}\n\nexport function finalCellScrub(cell: HTMLElement): void {\n if (!REFLECTIVE_RE.test(cell.textContent || '')) return;\n // First pass: clear only text nodes containing forbidden tokens\n for (const n of cell.childNodes) {\n if (n.nodeType === Node.TEXT_NODE && REFLECTIVE_RE.test(n.textContent || '')) n.textContent = '';\n }\n // If forbidden tokens persist in element nodes, nuke everything\n if (REFLECTIVE_RE.test(cell.textContent || '')) {\n cell.textContent = '';\n }\n}\n// #endregion\n\n// #region Template Compilation\nexport function compileTemplate(raw: string) {\n const forceBlank = REFLECTIVE_RE.test(raw);\n const fn = ((ctx: EvalContext) => {\n if (forceBlank) return '';\n const out = evalTemplateString(raw, ctx);\n return out;\n }) as CompiledViewFunction;\n fn.__blocked = forceBlank;\n return fn;\n}\n// #endregion\n","/**\n * Sorting Module\n *\n * Handles column sorting state transitions and row ordering.\n */\n\nimport type { ColumnConfig, GridHost, InternalGrid, SortHandler, SortState } from '../types';\nimport { announce } from './aria';\nimport { renderHeader } from './header';\n\n/**\n * Default comparator used when no column-level `sortComparator` is configured.\n * Pushes `null`/`undefined` to the end and compares remaining values via `>` / `<`\n * operators, which works correctly for numbers and falls back to lexicographic\n * comparison for strings.\n *\n * Use this as a fallback inside a custom `sortComparator` when you only need\n * special handling for certain values:\n *\n * @example\n * ```typescript\n * import { defaultComparator } from '@toolbox-web/grid';\n *\n * const column = {\n * field: 'priority',\n * sortComparator: (a, b, rowA, rowB) => {\n * // Pin \"urgent\" to the top, then fall back to default ordering\n * if (a === 'urgent') return -1;\n * if (b === 'urgent') return 1;\n * return defaultComparator(a, b);\n * },\n * };\n * ```\n *\n * @see {@link BaseColumnConfig.sortComparator} for column-level comparators\n * @see {@link builtInSort} for the full sort handler that uses this comparator\n * @category Factory Functions\n */\nexport function defaultComparator(a: unknown, b: unknown): number {\n if (a == null && b == null) return 0;\n if (a == null) return -1;\n if (b == null) return 1;\n return a > b ? 1 : a < b ? -1 : 0;\n}\n\n/**\n * The default `sortHandler` used when none is provided in {@link GridConfig.sortHandler}.\n * Reads each column's `sortComparator` (falling back to {@link defaultComparator})\n * and returns a sorted copy of the rows array.\n *\n * Use this as a fallback inside a custom `sortHandler` when you only need to\n * intercept sorting for specific columns or add pre/post-processing:\n *\n * @example\n * ```typescript\n * import { builtInSort } from '@toolbox-web/grid';\n * import type { SortHandler } from '@toolbox-web/grid';\n *\n * const customSort: SortHandler<Employee> = (rows, state, columns) => {\n * // Server-side sort for the \"salary\" column, client-side for everything else\n * if (state.field === 'salary') {\n * return fetch(`/api/employees?sort=${state.field}&dir=${state.direction}`)\n * .then(res => res.json());\n * }\n * return builtInSort(rows, state, columns);\n * };\n *\n * grid.gridConfig = { sortHandler: customSort };\n * ```\n *\n * @see {@link GridConfig.sortHandler} for configuring the handler\n * @see {@link defaultComparator} for the comparator used per column\n * @category Factory Functions\n */\nexport function builtInSort<T>(rows: T[], sortState: SortState, columns: ColumnConfig<T>[]): T[] {\n const col = columns.find((c) => c.field === sortState.field);\n const comparator = col?.sortComparator ?? defaultComparator;\n const { field, direction } = sortState;\n\n return [...rows].sort((rA: any, rB: any) => {\n return comparator(rA[field], rB[field], rA, rB) * direction;\n });\n}\n\n/**\n * Apply sort result to grid and update UI.\n * Called after sync or async sort completes.\n */\nfunction finalizeSortResult<T>(grid: GridHost<T>, sortedRows: T[], col: ColumnConfig<T>, dir: 1 | -1): void {\n grid._rows = sortedRows;\n // Bump epoch so renderVisibleRows triggers full inline rebuild\n grid.__rowRenderEpoch++;\n // Invalidate pooled rows to guarantee rebuild\n grid._rowPool.forEach((r) => (r.__epoch = -1));\n renderHeader(grid);\n grid.refreshVirtualWindow(true);\n grid.dispatchEvent(new CustomEvent('sort-change', { detail: { field: col.field, direction: dir } }));\n announce(grid, `Sorted by ${col.header ?? col.field}, ${dir === 1 ? 'ascending' : 'descending'}`);\n // Trigger state change after sort applied\n grid.requestStateChange?.();\n}\n\n/**\n * Cycle sort state for a column: none -> ascending -> descending -> none.\n * Restores original row order when clearing sort.\n */\nexport function toggleSort(grid: GridHost, col: ColumnConfig<any>): void {\n if (!grid._sortState || grid._sortState.field !== col.field) {\n if (!grid._sortState) grid.__originalOrder = grid._rows.slice();\n applySort(grid, col, 1);\n } else if (grid._sortState.direction === 1) {\n applySort(grid, col, -1);\n } else {\n grid._sortState = null;\n // Force full row rebuild after clearing sort so templated cells reflect original order\n grid.__rowRenderEpoch++;\n // Invalidate existing pooled row epochs so virtualization triggers a full inline rebuild\n grid._rowPool.forEach((r) => (r.__epoch = -1));\n grid._rows = grid.__originalOrder.slice();\n renderHeader(grid);\n // After re-render ensure cleared column shows aria-sort=\"none\" baseline.\n const headers = grid._headerRowEl?.querySelectorAll('[role=\"columnheader\"].sortable');\n headers?.forEach((h) => {\n if (!h.getAttribute('aria-sort')) h.setAttribute('aria-sort', 'none');\n else if (h.getAttribute('aria-sort') === 'ascending' || h.getAttribute('aria-sort') === 'descending') {\n // The active column was re-rendered already, but normalize any missing ones.\n if (!grid._sortState) h.setAttribute('aria-sort', 'none');\n }\n });\n grid.refreshVirtualWindow(true);\n grid.dispatchEvent(new CustomEvent('sort-change', { detail: { field: col.field, direction: 0 } }));\n announce(grid, 'Sort cleared');\n // Trigger state change after sort is cleared\n grid.requestStateChange?.();\n }\n}\n\n/**\n * Re-apply the current core sort to rows during #rebuildRowModel.\n * Updates __originalOrder so \"clear sort\" restores the current dataset.\n * Returns rows unchanged if no core sort is active or handler is async.\n */\nexport function reapplyCoreSort<T>(grid: InternalGrid<T>, rows: T[]): T[] {\n if (!grid._sortState) return rows;\n grid.__originalOrder = [...rows];\n const handler: SortHandler<any> = grid.effectiveConfig?.sortHandler ?? builtInSort;\n const result = handler(rows, grid._sortState, grid._columns as ColumnConfig<any>[]);\n if (result && typeof (result as Promise<unknown[]>).then === 'function') return rows;\n return result as T[];\n}\n\n/**\n * Apply a concrete sort direction to rows.\n *\n * Uses custom sortHandler from gridConfig if provided, otherwise uses built-in sorting.\n * Supports both sync and async handlers (for server-side sorting).\n */\nexport function applySort(grid: GridHost, col: ColumnConfig<any>, dir: 1 | -1): void {\n grid._sortState = { field: col.field, direction: dir };\n\n const sortState: SortState = { field: col.field, direction: dir };\n const columns = grid._columns as ColumnConfig<any>[];\n\n // Get custom handler from effectiveConfig, or use built-in\n const handler: SortHandler<any> = grid.effectiveConfig?.sortHandler ?? builtInSort;\n\n const result = handler(grid._rows, sortState, columns);\n\n // Handle async (Promise) or sync result\n if (result && typeof (result as Promise<unknown[]>).then === 'function') {\n // Async handler - wait for result\n (result as Promise<unknown[]>).then((sortedRows) => {\n finalizeSortResult(grid, sortedRows, col, dir);\n });\n } else {\n // Sync handler - apply immediately\n finalizeSortResult(grid, result as unknown[], col, dir);\n }\n}\n","/**\n * Header Rendering Module\n *\n * Handles rendering of the grid header row with sorting and resize affordances.\n * Supports custom header renderers via `headerRenderer` (full control) and\n * `headerLabelRenderer` (label only) column properties.\n */\n\nimport type { ColumnInternal, GridHost, HeaderCellContext, IconValue, InternalGrid } from '../types';\nimport { DEFAULT_GRID_ICONS } from '../types';\nimport { addPart } from './columns';\nimport { sanitizeHTML } from './sanitize';\nimport { toggleSort } from './sorting';\n\n// #region Helper Functions\n/**\n * Check if a column is sortable, respecting both column-level and grid-level settings.\n * Grid-wide `sortable: false` disables sorting for all columns.\n * Grid-wide `sortable: true` (or undefined) allows column-level `sortable` to control behavior.\n */\nfunction isColumnSortable(grid: InternalGrid, col: ColumnInternal): boolean {\n // Grid-wide sortable defaults to true if not specified\n const gridSortable = grid.effectiveConfig?.sortable !== false;\n return gridSortable && col.sortable === true;\n}\n\n/**\n * Check if a column is resizable, respecting both column-level and grid-level settings.\n * Grid-wide `resizable: false` disables resizing for all columns.\n * Grid-wide `resizable: true` (or undefined) allows column-level `resizable` to control behavior.\n */\nfunction isColumnResizable(grid: InternalGrid, col: ColumnInternal): boolean {\n // Grid-wide resizable defaults to true if not specified\n const gridResizable = grid.effectiveConfig?.resizable !== false;\n // Column-level resizable defaults to true if not specified\n return gridResizable && col.resizable !== false;\n}\n\n/**\n * Set an icon value on an element. Handles both string and HTMLElement icons.\n */\nfunction setIcon(element: HTMLElement, icon: IconValue): void {\n if (typeof icon === 'string') {\n element.textContent = icon;\n } else if (icon instanceof HTMLElement) {\n element.innerHTML = '';\n element.appendChild(icon.cloneNode(true));\n }\n}\n\n/**\n * Create a sort indicator element for a column.\n */\nfunction createSortIndicator(grid: InternalGrid, col: ColumnInternal): HTMLElement {\n const icon = document.createElement('span');\n addPart(icon, 'sort-indicator');\n const active = grid._sortState?.field === col.field ? grid._sortState.direction : 0;\n const icons = { ...DEFAULT_GRID_ICONS, ...grid.icons };\n const iconValue = active === 1 ? icons.sortAsc : active === -1 ? icons.sortDesc : icons.sortNone;\n setIcon(icon, iconValue);\n return icon;\n}\n\n/**\n * Create a resize handle element.\n */\nfunction createResizeHandle(grid: InternalGrid, colIndex: number, cell: HTMLElement): HTMLElement {\n const handle = document.createElement('div');\n handle.className = 'resize-handle';\n handle.setAttribute('aria-hidden', 'true');\n handle.addEventListener('mousedown', (e: MouseEvent) => {\n e.stopPropagation();\n e.preventDefault();\n grid._resizeController.start(e, colIndex, cell);\n });\n handle.addEventListener('dblclick', (e: MouseEvent) => {\n e.stopPropagation();\n e.preventDefault();\n grid._resizeController.resetColumn(colIndex);\n });\n return handle;\n}\n\n/**\n * Setup sorting click/keyboard handlers for a header cell.\n */\nfunction setupSortHandlers(grid: GridHost, col: ColumnInternal, colIndex: number, cell: HTMLElement): void {\n cell.classList.add('sortable');\n cell.tabIndex = 0;\n const active = grid._sortState?.field === col.field ? grid._sortState.direction : 0;\n cell.setAttribute('aria-sort', active === 0 ? 'none' : active === 1 ? 'ascending' : 'descending');\n\n cell.addEventListener('click', (e) => {\n if (grid._resizeController?.isResizing) return;\n if (grid._dispatchHeaderClick?.(e, col, cell)) return;\n toggleSort(grid, col);\n });\n cell.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n if (grid._dispatchHeaderClick?.(e, col, cell)) return;\n toggleSort(grid, col);\n }\n });\n}\n\n/**\n * Append renderer output to cell element.\n * Handles Node, string (with sanitization), or null/void (no-op).\n */\nfunction appendRendererOutput(cell: HTMLElement, output: Node | string | void | null): void {\n if (output == null) return;\n if (typeof output === 'string') {\n // sanitizeHTML returns a sanitized HTML string, use a container to convert to DOM\n const container = document.createElement('span');\n container.innerHTML = sanitizeHTML(output);\n // Move all child nodes to the cell\n while (container.firstChild) {\n cell.appendChild(container.firstChild);\n }\n } else if (output instanceof Node) {\n cell.appendChild(output);\n }\n}\n// #endregion\n\n// #region Render Header\n/**\n * Rebuild the header row DOM based on current column configuration, attaching\n * sorting and resize affordances where enabled.\n *\n * Rendering precedence:\n * 1. `headerRenderer` - Full control over header cell content\n * 2. `headerLabelRenderer` - Custom label, framework handles icons/interactions\n * 3. `__headerTemplate` - Light DOM template (framework adapter)\n * 4. `header` property - Plain text header\n * 5. `field` - Fallback to field name\n */\nexport function renderHeader(grid: GridHost): void {\n grid._headerRowEl = grid.findHeaderRow!();\n const headerRow = grid._headerRowEl as HTMLElement;\n\n // Guard: DOM may not be built yet\n if (!headerRow) {\n return;\n }\n\n headerRow.innerHTML = '';\n\n grid._visibleColumns.forEach((col: ColumnInternal, i: number) => {\n const cell = document.createElement('div');\n cell.className = 'cell';\n addPart(cell, 'header-cell');\n cell.setAttribute('role', 'columnheader');\n\n // aria-colindex is 1-based\n cell.setAttribute('aria-colindex', String(i + 1));\n cell.setAttribute('data-field', col.field);\n cell.setAttribute('data-col', String(i)); // Add data-col for consistency with body cells\n if (col.type) cell.setAttribute('data-type', col.type);\n\n // Compute header value and sort state for context\n const headerValue = col.header ?? col.field;\n const sortDirection = grid._sortState?.field === col.field ? grid._sortState.direction : 0;\n const sortState: 'asc' | 'desc' | null = sortDirection === 1 ? 'asc' : sortDirection === -1 ? 'desc' : null;\n\n // Check for headerRenderer (full control mode)\n if (col.headerRenderer) {\n // Create context with helper functions\n const ctx: HeaderCellContext<any> = {\n column: col,\n value: headerValue,\n sortState,\n filterActive: false, // Will be set by FilteringPlugin if active\n cellEl: cell,\n renderSortIcon: () => (isColumnSortable(grid, col) ? createSortIndicator(grid, col) : null),\n renderFilterButton: () => null, // FilteringPlugin adds filter button via afterRender\n };\n\n const output = col.headerRenderer(ctx);\n appendRendererOutput(cell, output);\n\n // Setup sort handlers if sortable (user may not have included sort icon but still want click-to-sort)\n if (isColumnSortable(grid, col)) {\n setupSortHandlers(grid, col, i, cell);\n }\n\n // Always add resize handle if resizable (not user's responsibility)\n if (isColumnResizable(grid, col)) {\n cell.classList.add('resizable');\n cell.appendChild(createResizeHandle(grid, i, cell));\n }\n }\n // Check for headerLabelRenderer (label-only mode)\n else if (col.headerLabelRenderer) {\n const ctx = {\n column: col,\n value: headerValue,\n };\n\n const output = col.headerLabelRenderer(ctx);\n // Wrap output in a span for consistency with default rendering\n const span = document.createElement('span');\n if (output == null) {\n span.textContent = headerValue;\n } else if (typeof output === 'string') {\n span.innerHTML = sanitizeHTML(output);\n } else if (output instanceof Node) {\n span.appendChild(output);\n }\n cell.appendChild(span);\n\n // Framework handles the rest: sort icon, resize handle\n if (isColumnSortable(grid, col)) {\n setupSortHandlers(grid, col, i, cell);\n cell.appendChild(createSortIndicator(grid, col));\n }\n if (isColumnResizable(grid, col)) {\n cell.classList.add('resizable');\n cell.appendChild(createResizeHandle(grid, i, cell));\n }\n }\n // Light DOM template (framework adapter)\n else if (col.__headerTemplate) {\n Array.from(col.__headerTemplate.childNodes).forEach((n) => cell.appendChild(n.cloneNode(true)));\n\n // Standard affordances\n if (isColumnSortable(grid, col)) {\n setupSortHandlers(grid, col, i, cell);\n cell.appendChild(createSortIndicator(grid, col));\n }\n if (isColumnResizable(grid, col)) {\n cell.classList.add('resizable');\n cell.appendChild(createResizeHandle(grid, i, cell));\n }\n }\n // Default: plain text header\n else {\n const span = document.createElement('span');\n span.textContent = headerValue;\n cell.appendChild(span);\n\n // Standard affordances\n if (isColumnSortable(grid, col)) {\n setupSortHandlers(grid, col, i, cell);\n cell.appendChild(createSortIndicator(grid, col));\n }\n if (isColumnResizable(grid, col)) {\n cell.classList.add('resizable');\n cell.appendChild(createResizeHandle(grid, i, cell));\n }\n }\n\n headerRow.appendChild(cell);\n });\n\n // Ensure every sortable header has a baseline aria-sort if not already set during construction.\n headerRow.querySelectorAll('.cell.sortable').forEach((el) => {\n if (!el.getAttribute('aria-sort')) el.setAttribute('aria-sort', 'none');\n });\n\n // Set ARIA role only if header has children (role=\"row\" requires columnheader children)\n // When grid is cleared with 0 columns, the header row should not have role=\"row\"\n if (headerRow.children.length > 0) {\n headerRow.setAttribute('role', 'row');\n headerRow.setAttribute('aria-rowindex', '1');\n } else {\n headerRow.removeAttribute('role');\n headerRow.removeAttribute('aria-rowindex');\n }\n}\n// #endregion\n","import type { ColumnConfigMap, ColumnType, InferredColumnResult, PrimitiveColumnType } from '../types';\n/**\n * Best-effort primitive type inference for a cell value used during automatic column generation.\n */\nfunction inferType(value: any): PrimitiveColumnType {\n if (value == null) return 'string';\n if (typeof value === 'number') return 'number';\n if (typeof value === 'boolean') return 'boolean';\n if (value instanceof Date) return 'date';\n if (typeof value === 'string' && /\\d{4}-\\d{2}-\\d{2}/.test(value) && !isNaN(Date.parse(value))) return 'date';\n return 'string';\n}\n/**\n * Derive column definitions from provided configuration or by inspecting the first row of data.\n * Returns both the resolved column array and a field->type map.\n */\nexport function inferColumns<TRow extends Record<string, unknown>>(\n rows: TRow[],\n provided?: ColumnConfigMap<TRow>,\n): InferredColumnResult<TRow> {\n if (provided && provided.length) {\n const typeMap: Record<string, ColumnType> = {};\n provided.forEach((col) => {\n if (col.type) typeMap[col.field] = col.type;\n });\n return { columns: provided, typeMap };\n }\n const sample = rows[0] || ({} as TRow);\n const columns: ColumnConfigMap<TRow> = Object.keys(sample).map((k) => {\n const v = (sample as Record<string, unknown>)[k];\n const type = inferType(v);\n return { field: k as keyof TRow & string, header: k.charAt(0).toUpperCase() + k.slice(1), type };\n });\n const typeMap: Record<string, ColumnType> = {};\n columns.forEach((c) => {\n typeMap[c.field] = c.type || 'string';\n });\n return { columns, typeMap };\n}\nexport { inferType };\n","/**\n * Centralized Render Scheduler for the Grid component.\n *\n * This scheduler batches all rendering work into a single requestAnimationFrame,\n * eliminating race conditions between different parts of the grid (ResizeObserver,\n * framework adapters, virtualization, etc.) that previously scheduled independent RAFs.\n *\n * ## Design Principles\n *\n * 1. **Single RAF per frame**: All render requests are batched into one RAF callback\n * 2. **Phase-based execution**: Work is organized into ordered phases that run sequentially\n * 3. **Highest-phase wins**: Multiple requests merge to the highest requested phase\n * 4. **Framework-agnostic timing**: Eliminates need for \"double RAF\" hacks\n *\n * ## Render Phases (execute in order)\n *\n * - STYLE (1): Lightweight style/class updates, plugin afterRender hooks\n * - VIRTUALIZATION (2): Virtual window recalculation (scroll, resize)\n * - HEADER (3): Header re-render only\n * - ROWS (4): Row model rebuild + header + template + virtual window\n * - COLUMNS (5): Column processing + rows phase work\n * - FULL (6): Complete render including config merge\n *\n * @example\n * ```typescript\n * // The scheduler takes the grid (InternalGrid) directly:\n * const scheduler = new RenderScheduler(this as unknown as InternalGrid);\n *\n * // Request a full render\n * scheduler.requestPhase(RenderPhase.FULL, 'initial-setup');\n *\n * // Wait for render to complete\n * await scheduler.whenReady();\n * ```\n */\n\nimport type { InternalGrid } from '../types';\n\n// #region Types & Enums\n/**\n * Render phases in order of execution.\n * Higher phases include all lower phase work.\n *\n * @category Plugin Development\n */\nexport enum RenderPhase {\n /** Lightweight style updates only (plugin afterRender hooks) */\n STYLE = 1,\n /** Virtual window recalculation (includes STYLE) */\n VIRTUALIZATION = 2,\n /** Header re-render (includes VIRTUALIZATION) */\n HEADER = 3,\n /** Row model rebuild (includes HEADER) */\n ROWS = 4,\n /** Column processing (includes ROWS) */\n COLUMNS = 5,\n /** Full render including config merge (includes COLUMNS) */\n FULL = 6,\n}\n\n/**\n * @internal Scheduler now takes InternalGrid directly — no callback interface needed.\n */\n// #endregion\n\n// #region RenderScheduler\n/**\n * Centralized render scheduler that batches all grid rendering into single RAF.\n */\nexport class RenderScheduler {\n readonly #grid: InternalGrid;\n\n /** Current pending phase (0 = none pending) */\n #pendingPhase: RenderPhase | 0 = 0;\n\n /** RAF handle for cancellation */\n #rafHandle = 0;\n\n /** Promise that resolves when current render completes */\n #readyPromise: Promise<void> | null = null;\n #readyResolve: (() => void) | null = null;\n\n /** Initial ready resolver (for component's initial ready() promise) */\n #initialReadyResolver: (() => void) | null = null;\n #initialReadyFired = false;\n\n constructor(grid: InternalGrid) {\n this.#grid = grid;\n }\n\n /**\n * Request a render at the specified phase.\n * Multiple requests are batched - the highest phase wins.\n *\n * @param phase - The render phase to execute\n * @param _source - Debug identifier for what triggered this request (unused, kept for API compatibility)\n */\n requestPhase(phase: RenderPhase, _source: string): void {\n // Merge to highest phase\n if (phase > this.#pendingPhase) {\n this.#pendingPhase = phase;\n }\n\n // Schedule RAF if not already scheduled\n if (this.#rafHandle === 0) {\n this.#ensureReadyPromise();\n this.#rafHandle = requestAnimationFrame(() => this.#flush());\n }\n }\n\n /**\n * Returns a promise that resolves when the current render cycle completes.\n * If no render is pending, returns an already-resolved promise.\n */\n whenReady(): Promise<void> {\n if (this.#readyPromise) {\n return this.#readyPromise;\n }\n return Promise.resolve();\n }\n\n /**\n * Set the initial ready resolver (called once on first render).\n * This connects to the grid's `ready()` promise.\n */\n setInitialReadyResolver(resolver: () => void): void {\n this.#initialReadyResolver = resolver;\n }\n\n /**\n * Cancel any pending render.\n * Useful for cleanup when component disconnects.\n */\n cancel(): void {\n if (this.#rafHandle !== 0) {\n cancelAnimationFrame(this.#rafHandle);\n this.#rafHandle = 0;\n }\n this.#pendingPhase = 0;\n\n // Resolve any pending ready promise (don't leave it hanging)\n if (this.#readyResolve) {\n this.#readyResolve();\n this.#readyResolve = null;\n this.#readyPromise = null;\n }\n }\n\n /**\n * Check if a render is currently pending.\n */\n get isPending(): boolean {\n return this.#pendingPhase !== 0;\n }\n\n /**\n * Get the current pending phase (0 if none).\n */\n get pendingPhase(): RenderPhase | 0 {\n return this.#pendingPhase;\n }\n\n // ─────────────────────────────────────────────────────────────────────────\n // Private Methods\n // ─────────────────────────────────────────────────────────────────────────\n\n #ensureReadyPromise(): void {\n if (!this.#readyPromise) {\n this.#readyPromise = new Promise<void>((resolve) => {\n this.#readyResolve = resolve;\n });\n }\n }\n\n /**\n * Execute all pending render work in phase order.\n * This is the single RAF callback that does all rendering.\n */\n #flush(): void {\n this.#rafHandle = 0;\n\n // Bail if component disconnected\n if (!this.#grid._schedulerIsConnected) {\n this.#pendingPhase = 0;\n if (this.#readyResolve) {\n this.#readyResolve();\n this.#readyResolve = null;\n this.#readyPromise = null;\n }\n return;\n }\n\n const phase = this.#pendingPhase;\n this.#pendingPhase = 0;\n\n // Execute phases in order (higher phases include lower phase work)\n // The execution order respects data dependencies:\n // mergeConfig → processRows → processColumns → renderHeader → virtualWindow → afterRender\n\n // mergeConfig runs for FULL phase OR COLUMNS phase (to pick up framework adapter renderers)\n // IMPORTANT: mergeConfig must run BEFORE processRows because the row model depends on\n // column configuration, and framework adapters (React/Angular) may register renderers\n // asynchronously after the initial gridConfig is set.\n if (phase >= RenderPhase.COLUMNS) {\n this.#grid._schedulerMergeConfig();\n }\n\n // Phase 4 (ROWS): Rebuild row model\n // NOTE: processRows MUST run before processColumns because tree plugin's\n // processColumns depends on flattenedRows populated by processRows\n if (phase >= RenderPhase.ROWS) {\n this.#grid._schedulerProcessRows();\n }\n\n // Phase 5 (COLUMNS): Process columns + update template\n if (phase >= RenderPhase.COLUMNS) {\n this.#grid._schedulerProcessColumns();\n this.#grid._schedulerUpdateTemplate();\n }\n\n // Phase 3 (HEADER): Render header\n if (phase >= RenderPhase.HEADER) {\n this.#grid._schedulerRenderHeader();\n }\n\n // Phase 2 (VIRTUALIZATION): Recalculate virtual window\n if (phase >= RenderPhase.VIRTUALIZATION) {\n this.#grid.refreshVirtualWindow(true, true);\n }\n\n // Phase 1 (STYLE): Run afterRender hooks (always runs)\n if (phase >= RenderPhase.STYLE) {\n this.#grid._schedulerAfterRender();\n }\n\n // Fire initial ready resolver once\n if (!this.#initialReadyFired && this.#initialReadyResolver) {\n this.#initialReadyFired = true;\n this.#initialReadyResolver();\n }\n\n // Resolve the ready promise\n if (this.#readyResolve) {\n this.#readyResolve();\n this.#readyResolve = null;\n this.#readyPromise = null;\n }\n }\n}\n// #endregion\n","/**\n * ConfigManager - Unified Configuration Lifecycle Management\n *\n * Manages all configuration concerns for the grid:\n * - Source collection (gridConfig, columns, attributes, Light DOM)\n * - Two-layer config (frozen original + mutable effective)\n * - State persistence (collect/apply/reset via diff)\n * - Change notification for re-rendering\n *\n * This is an internal module - grid.ts delegates to ConfigManager\n * but the public API remains unchanged.\n */\n\nimport type { BaseGridPlugin } from '../plugin';\nimport type {\n ColumnConfig,\n ColumnConfigMap,\n ColumnInternal,\n ColumnSortState,\n ColumnState,\n FitMode,\n GridColumnState,\n GridConfig,\n GridHost,\n} from '../types';\nimport { mergeColumns, parseLightDomColumns, updateTemplate } from './columns';\nimport { renderHeader } from './header';\nimport { inferColumns } from './inference';\nimport { RenderPhase } from './render-scheduler';\nimport { compileTemplate } from './sanitize';\n\n/** Debounce timeout for state change events */\nconst STATE_CHANGE_DEBOUNCE_MS = 100;\n\n/**\n * ConfigManager handles all configuration lifecycle for the grid.\n *\n * Manages:\n * - Source collection (gridConfig, columns, attributes, Light DOM)\n * - Effective config (merged from all sources)\n * - State persistence (collectState, applyState, resetState)\n * - Column visibility and ordering\n */\nexport class ConfigManager<T = unknown> {\n // #region Sources (raw input from user)\n #gridConfig?: GridConfig<T>;\n #columns?: ColumnConfig<T>[] | ColumnConfigMap<T>;\n #fitMode?: FitMode;\n\n // Light DOM cache\n #lightDomColumnsCache?: ColumnInternal<T>[];\n #originalColumnNodes?: HTMLElement[];\n // #endregion\n\n // #region Two-Layer Config Architecture\n /**\n * Original config (frozen) - Built from sources, never mutated.\n * This is the \"canonical\" config that sources compile into.\n * Used as the reset point for effectiveConfig.\n */\n #originalConfig: GridConfig<T> = {};\n\n /**\n * Effective config (mutable) - Cloned from original, runtime mutations here.\n * This is what rendering reads from.\n * Runtime changes: hidden, width, sort order, column order.\n */\n #effectiveConfig: GridConfig<T> = {};\n // #endregion\n\n // #region State Tracking\n #sourcesChanged = true;\n #changeListeners: Array<() => void> = [];\n #lightDomObserver?: MutationObserver;\n #stateChangeTimeoutId?: ReturnType<typeof setTimeout>;\n #lightDomDebounceTimer?: ReturnType<typeof setTimeout>;\n #initialColumnState?: GridColumnState;\n #grid: GridHost<T>;\n\n // Shell state (Light DOM title)\n #lightDomTitle?: string;\n\n constructor(grid: GridHost<T>) {\n this.#grid = grid;\n }\n // #endregion\n\n // #region Getters\n /** Get the frozen original config (compiled from sources, immutable) */\n get original(): GridConfig<T> {\n return this.#originalConfig;\n }\n\n /** Get the mutable effective config (current runtime state) */\n get effective(): GridConfig<T> {\n return this.#effectiveConfig;\n }\n\n /** Get columns from effective config */\n get columns(): ColumnInternal<T>[] {\n return (this.#effectiveConfig.columns ?? []) as ColumnInternal<T>[];\n }\n\n /** Set columns on effective config */\n set columns(value: ColumnInternal<T>[]) {\n this.#effectiveConfig.columns = value as ColumnConfig<T>[];\n }\n\n /** Get light DOM columns cache */\n get lightDomColumnsCache(): ColumnInternal<T>[] | undefined {\n return this.#lightDomColumnsCache;\n }\n\n /** Set light DOM columns cache */\n set lightDomColumnsCache(value: ColumnInternal<T>[] | undefined) {\n this.#lightDomColumnsCache = value;\n }\n\n /** Get original column nodes */\n get originalColumnNodes(): HTMLElement[] | undefined {\n return this.#originalColumnNodes;\n }\n\n /** Set original column nodes */\n set originalColumnNodes(value: HTMLElement[] | undefined) {\n this.#originalColumnNodes = value;\n }\n\n /** Get light DOM title */\n get lightDomTitle(): string | undefined {\n return this.#lightDomTitle;\n }\n\n /** Set light DOM title */\n set lightDomTitle(value: string | undefined) {\n this.#lightDomTitle = value;\n }\n\n /** Get initial column state */\n get initialColumnState(): GridColumnState | undefined {\n return this.#initialColumnState;\n }\n\n /** Set initial column state */\n set initialColumnState(value: GridColumnState | undefined) {\n this.#initialColumnState = value;\n }\n // #endregion\n\n // #region Source Management\n /**\n * Check if sources have changed since last merge.\n */\n get sourcesChanged(): boolean {\n return this.#sourcesChanged;\n }\n\n /**\n * Mark that sources have changed and need re-merging.\n * Call this when external state (shell maps, etc.) that feeds into\n * collectAllSources() has been updated.\n */\n markSourcesChanged(): void {\n this.#sourcesChanged = true;\n }\n // #endregion\n\n // #region Source Setters\n /** Set gridConfig source */\n setGridConfig(config: GridConfig<T> | undefined): void {\n this.#gridConfig = config;\n this.#sourcesChanged = true;\n // Clear light DOM cache for framework async content\n this.#lightDomColumnsCache = undefined;\n }\n\n /** Get the raw gridConfig source */\n getGridConfig(): GridConfig<T> | undefined {\n return this.#gridConfig;\n }\n\n /** Set columns source */\n setColumns(columns: ColumnConfig<T>[] | ColumnConfigMap<T> | undefined): void {\n this.#columns = columns;\n this.#sourcesChanged = true;\n }\n\n /** Get the raw columns source */\n getColumns(): ColumnConfig<T>[] | ColumnConfigMap<T> | undefined {\n return this.#columns;\n }\n\n /** Set fitMode source */\n setFitMode(mode: FitMode | undefined): void {\n this.#fitMode = mode;\n this.#sourcesChanged = true;\n }\n\n /** Get the raw fitMode source */\n getFitMode(): FitMode | undefined {\n return this.#fitMode;\n }\n // #endregion\n\n // #region Config Lifecycle\n /**\n * Merge all sources into effective config.\n * Also applies post-merge operations (rowHeight, fixed mode widths, animation).\n *\n * Called by RenderScheduler's mergeConfig phase.\n *\n * Two-layer architecture:\n * 1. Sources → #originalConfig (frozen, immutable)\n * 2. Clone → #effectiveConfig (mutable, runtime changes)\n *\n * When sources change, both layers are rebuilt.\n * When sources haven't changed AND columns exist, this is a no-op.\n * Runtime mutations only affect effectiveConfig.\n * resetState() clones originalConfig back to effectiveConfig.\n */\n merge(): void {\n // Only rebuild when sources have actually changed.\n // Exception: always rebuild if we don't have columns yet (inference may be needed)\n const hasColumns = (this.#effectiveConfig.columns?.length ?? 0) > 0;\n if (!this.#sourcesChanged && hasColumns) {\n return; // effectiveConfig is already valid\n }\n\n // Build config from all sources\n const base = this.#collectAllSources();\n\n // Mark sources as processed\n this.#sourcesChanged = false;\n\n // Freeze as the new original config (immutable reference point)\n this.#originalConfig = base;\n Object.freeze(this.#originalConfig);\n if (this.#originalConfig.columns) {\n // Deep freeze columns array (but not the column objects themselves,\n // as we need effectiveConfig columns to be mutable)\n Object.freeze(this.#originalConfig.columns);\n }\n\n // Clone to effective config (mutable copy for runtime changes)\n this.#effectiveConfig = this.#cloneConfig(this.#originalConfig);\n\n // Apply post-merge operations to effectiveConfig\n this.#applyPostMergeOperations();\n }\n\n /**\n * Deep clone a config object, handling functions (renderers, editors).\n * Uses structuredClone where possible, with fallback for function properties.\n */\n #cloneConfig(config: GridConfig<T>): GridConfig<T> {\n // Can't use structuredClone because config may contain functions\n const clone: GridConfig<T> = { ...config };\n\n // Deep clone columns (they may have runtime-mutable state)\n if (config.columns) {\n clone.columns = config.columns.map((col) => ({ ...col }));\n }\n\n // Deep clone shell if present\n if (config.shell) {\n clone.shell = {\n ...config.shell,\n header: config.shell.header ? { ...config.shell.header } : undefined,\n toolPanel: config.shell.toolPanel ? { ...config.shell.toolPanel } : undefined,\n toolPanels: config.shell.toolPanels?.map((p) => ({ ...p })),\n headerContents: config.shell.headerContents?.map((h) => ({ ...h })),\n };\n }\n\n return clone;\n }\n\n /**\n * Apply operations that depend on the merged effective config.\n * These were previously in grid.ts #mergeEffectiveConfig().\n */\n #applyPostMergeOperations(): void {\n const config = this.#effectiveConfig;\n\n // Apply typeDefaults to columns that have a type but no explicit renderer/format\n // This is done at config time for performance - no runtime lookup needed\n this.#applyTypeDefaultsToColumns();\n\n // Apply rowHeight from config if specified (only for numeric values)\n // Function-based rowHeight is handled by variable height virtualization\n if (typeof config.rowHeight === 'number' && config.rowHeight > 0) {\n this.#grid._virtualization.rowHeight = config.rowHeight;\n }\n\n // If fixed mode and width not specified: assign default 80px\n if (config.fitMode === 'fixed') {\n const columns = this.columns;\n columns.forEach((c) => {\n if (c.width == null) c.width = 80;\n });\n }\n\n // Apply animation configuration to host element\n this.#grid._applyAnimationConfig(config);\n }\n\n /**\n * Apply typeDefaults from gridConfig to columns.\n * For each column with a `type` property that matches a key in `typeDefaults`,\n * copy the renderer/format to the column if not already set.\n *\n * This is done at config merge time for performance - avoids runtime lookups.\n */\n #applyTypeDefaultsToColumns(): void {\n const typeDefaults = this.#effectiveConfig.typeDefaults;\n if (!typeDefaults) return;\n\n const columns = this.columns;\n for (const col of columns) {\n if (!col.type) continue;\n\n const typeDefault = typeDefaults[col.type];\n if (!typeDefault) continue;\n\n // Apply renderer if column doesn't have one\n // Priority: column.renderer > column.viewRenderer > typeDefault.renderer\n if (!col.renderer && !col.viewRenderer && typeDefault.renderer) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n col.renderer = typeDefault.renderer as any;\n }\n\n // Apply format if column doesn't have one\n if (!col.format && typeDefault.format) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n col.format = typeDefault.format as any;\n }\n\n // Apply editor if column doesn't have one\n if (!col.editor && typeDefault.editor) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n col.editor = typeDefault.editor as any;\n }\n\n // Apply editorParams if column doesn't have them\n if (!col.editorParams && typeDefault.editorParams) {\n col.editorParams = typeDefault.editorParams;\n }\n }\n }\n\n /**\n * Collect all sources into a single config object.\n * This is the core merge logic extracted from grid.ts #mergeEffectiveConfig.\n *\n * Collects all sources into a canonical config object.\n * This becomes the frozen #originalConfig.\n *\n * Sources (in order of precedence, low to high):\n * 1. gridConfig.columns\n * 2. Light DOM columns (merged with config columns)\n * 3. columns prop (overrides if set)\n * 4. Inferred columns (if still empty)\n *\n * Runtime state (hidden, width) is NOT preserved here - that's in effectiveConfig.\n */\n #collectAllSources(): GridConfig<T> {\n const base: GridConfig<T> = this.#gridConfig ? { ...this.#gridConfig } : {};\n const configColumns: ColumnConfig<T>[] = Array.isArray(base.columns) ? [...base.columns] : [];\n\n // Light DOM cached parse - clone to avoid mutation\n const domCols: ColumnConfig<T>[] = (this.#lightDomColumnsCache ?? []).map((c) => ({\n ...c,\n })) as ColumnConfig<T>[];\n\n // Use mergeColumns to combine config columns with light DOM columns\n // This handles all the complex merge logic including templates and renderers\n let columns: ColumnInternal<T>[] = mergeColumns(\n configColumns as ColumnInternal<T>[],\n domCols as ColumnInternal<T>[],\n ) as ColumnInternal<T>[];\n\n // Columns prop highest structural precedence (overrides merged result)\n if (this.#columns && (this.#columns as ColumnConfig<T>[]).length) {\n // When columns prop is set, merge with light DOM columns for renderers/templates\n columns = mergeColumns(\n this.#columns as ColumnInternal<T>[],\n domCols as ColumnInternal<T>[],\n ) as ColumnInternal<T>[];\n }\n\n // Inference if still empty\n const rows = this.#grid.sourceRows;\n if (columns.length === 0 && rows.length) {\n const result = inferColumns(rows as Record<string, unknown>[]);\n columns = result.columns as ColumnInternal<T>[];\n }\n\n if (columns.length) {\n // Apply per-column defaults\n columns.forEach((c) => {\n if (c.sortable === undefined) c.sortable = true;\n if (c.resizable === undefined) c.resizable = true;\n if (c.__originalWidth === undefined && typeof c.width === 'number') {\n c.__originalWidth = c.width;\n }\n });\n\n // Compile inline templates (from light DOM <template> elements)\n columns.forEach((c) => {\n if (c.__viewTemplate && !c.__compiledView) {\n c.__compiledView = compileTemplate((c.__viewTemplate as HTMLElement).innerHTML);\n }\n if (c.__editorTemplate && !c.__compiledEditor) {\n c.__compiledEditor = compileTemplate(c.__editorTemplate.innerHTML);\n }\n });\n\n base.columns = columns as ColumnConfig<T>[];\n }\n\n // Individual prop overrides\n if (this.#fitMode) base.fitMode = this.#fitMode;\n if (!base.fitMode) base.fitMode = 'stretch';\n\n // ========================================================================\n // Merge shell configuration from ShellState into effectiveConfig.shell\n // This ensures a single source of truth for all shell config\n // ========================================================================\n this.#mergeShellConfig(base);\n\n // Store columnState from gridConfig if not already set\n if (base.columnState && !this.#initialColumnState) {\n this.#initialColumnState = base.columnState;\n }\n\n return base;\n }\n\n /**\n * Merge shell state into base config's shell property.\n * Ensures effectiveConfig.shell is the single source of truth.\n *\n * IMPORTANT: This method must NOT mutate the original gridConfig.\n * We shallow-clone the shell hierarchy to avoid side effects.\n */\n #mergeShellConfig(base: GridConfig<T>): void {\n // Clone shell hierarchy to avoid mutating original gridConfig\n // base.shell may still reference this.#gridConfig.shell, so we need fresh objects\n base.shell = base.shell ? { ...base.shell } : {};\n base.shell.header = base.shell.header ? { ...base.shell.header } : {};\n\n // Sync light DOM title\n const shellLightDomTitle = this.#grid._shellState.lightDomTitle;\n if (shellLightDomTitle) {\n this.#lightDomTitle = shellLightDomTitle;\n }\n if (this.#lightDomTitle && !base.shell.header.title) {\n base.shell.header.title = this.#lightDomTitle;\n }\n\n // Sync light DOM header content elements\n const lightDomHeaderContent = this.#grid._shellState.lightDomHeaderContent;\n if (lightDomHeaderContent?.length > 0) {\n base.shell.header.lightDomContent = lightDomHeaderContent;\n }\n\n // Sync hasToolButtonsContainer from shell state\n if (this.#grid._shellState.hasToolButtonsContainer) {\n base.shell.header.hasToolButtonsContainer = true;\n }\n\n // Sync tool panels (from plugins + API + Light DOM)\n const toolPanelsMap = this.#grid._shellState.toolPanels;\n if (toolPanelsMap.size > 0) {\n const panels = Array.from(toolPanelsMap.values());\n // Sort by order (lower = first, default 100)\n panels.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n base.shell.toolPanels = panels;\n }\n\n // Sync header contents (from plugins + API)\n const headerContentsMap = this.#grid._shellState.headerContents;\n if (headerContentsMap.size > 0) {\n const contents = Array.from(headerContentsMap.values());\n // Sort by order\n contents.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n base.shell.headerContents = contents;\n }\n\n // Sync toolbar contents (from API - config contents are already in base.shell.header.toolbarContents)\n // We need to merge config contents (from gridConfig) with API contents (from registerToolbarContent)\n // API contents can be added/removed dynamically, so we need to rebuild from current state each time\n const toolbarContentsMap = this.#grid._shellState.toolbarContents;\n const apiContents = Array.from(toolbarContentsMap.values());\n\n // Get ORIGINAL config contents (from gridConfig, not from previous merges)\n // We use a fresh read from gridConfig to avoid accumulating stale API contents\n const originalConfigContents = this.#gridConfig?.shell?.header?.toolbarContents ?? [];\n\n // Merge: config contents + API contents (config takes precedence by id)\n const configIds = new Set(originalConfigContents.map((c) => c.id));\n const mergedContents = [...originalConfigContents];\n for (const content of apiContents) {\n if (!configIds.has(content.id)) {\n mergedContents.push(content);\n }\n }\n\n // Sort by order\n mergedContents.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n base.shell.header.toolbarContents = mergedContents;\n }\n // #endregion\n\n // #region State Persistence\n /**\n * Collect current column state by diffing original vs effective.\n * Returns only the changes from the original configuration.\n */\n collectState(plugins: BaseGridPlugin[]): GridColumnState {\n const columns = this.columns;\n const sortStates = this.#getSortState();\n\n return {\n columns: columns.map((col, index) => {\n const state: ColumnState = {\n field: col.field,\n order: index,\n visible: !col.hidden,\n };\n\n // Include width if set\n const internalCol = col as ColumnInternal<T>;\n if (internalCol.__renderedWidth !== undefined) {\n state.width = internalCol.__renderedWidth;\n } else if (col.width !== undefined) {\n state.width = typeof col.width === 'string' ? parseFloat(col.width) : col.width;\n }\n\n // Include sort state\n const sortState = sortStates.get(col.field);\n if (sortState) {\n state.sort = sortState;\n }\n\n // Collect from plugins\n for (const plugin of plugins) {\n if (plugin.getColumnState) {\n const pluginState = plugin.getColumnState(col.field);\n if (pluginState) {\n Object.assign(state, pluginState);\n }\n }\n }\n\n return state;\n }),\n };\n }\n\n /**\n * Apply column state to the grid.\n */\n applyState(state: GridColumnState, plugins: BaseGridPlugin[]): void {\n if (!state.columns || state.columns.length === 0) return;\n\n const allColumns = this.columns;\n const stateMap = new Map(state.columns.map((s) => [s.field, s]));\n\n // Apply width and visibility\n const updatedColumns = allColumns.map((col) => {\n const s = stateMap.get(col.field);\n if (!s) return col;\n\n const updated: ColumnInternal<T> = { ...col };\n\n if (s.width !== undefined) {\n updated.width = s.width;\n updated.__renderedWidth = s.width;\n }\n\n if (s.visible !== undefined) {\n updated.hidden = !s.visible;\n }\n\n return updated;\n });\n\n // Reorder columns\n updatedColumns.sort((a, b) => {\n const orderA = stateMap.get(a.field)?.order ?? Infinity;\n const orderB = stateMap.get(b.field)?.order ?? Infinity;\n return orderA - orderB;\n });\n\n this.columns = updatedColumns;\n\n // Apply sort state\n const sortedByPriority = state.columns\n .filter((s) => s.sort !== undefined)\n .sort((a, b) => (a.sort?.priority ?? 0) - (b.sort?.priority ?? 0));\n\n if (sortedByPriority.length > 0) {\n const primarySort = sortedByPriority[0];\n if (primarySort.sort) {\n this.#grid._sortState = {\n field: primarySort.field,\n direction: primarySort.sort.direction === 'asc' ? 1 : -1,\n };\n }\n } else {\n this.#grid._sortState = null;\n }\n\n // Let plugins apply their state\n for (const plugin of plugins) {\n if (plugin.applyColumnState) {\n for (const colState of state.columns) {\n plugin.applyColumnState(colState.field, colState);\n }\n }\n }\n }\n\n /**\n * Reset state to original configuration.\n *\n * Two-layer architecture: Clones #originalConfig back to #effectiveConfig.\n * This discards all runtime changes (hidden, width, order) and restores\n * the state to what was compiled from sources.\n */\n resetState(plugins: BaseGridPlugin[]): void {\n // Clear initial state\n this.#initialColumnState = undefined;\n\n // Reset sort state\n this.#grid._sortState = null;\n\n // Clone original config back to effective (discards all runtime changes)\n this.#effectiveConfig = this.#cloneConfig(this.#originalConfig);\n\n // Apply post-merge operations (rowHeight, fixed mode widths, animation)\n this.#applyPostMergeOperations();\n\n // Notify plugins to reset\n for (const plugin of plugins) {\n if (plugin.applyColumnState) {\n for (const col of this.columns) {\n plugin.applyColumnState(col.field, {\n field: col.field,\n order: 0,\n visible: true,\n });\n }\n }\n }\n\n // Request state change notification\n this.requestStateChange(plugins);\n }\n\n /**\n * Get sort state as a map.\n */\n #getSortState(): Map<string, ColumnSortState> {\n const sortMap = new Map<string, ColumnSortState>();\n const sortState = this.#grid._sortState;\n\n if (sortState) {\n sortMap.set(sortState.field, {\n direction: sortState.direction === 1 ? 'asc' : 'desc',\n priority: 0,\n });\n }\n\n return sortMap;\n }\n\n /**\n * Request a debounced state change event.\n */\n requestStateChange(plugins: BaseGridPlugin[]): void {\n if (this.#stateChangeTimeoutId) {\n clearTimeout(this.#stateChangeTimeoutId);\n }\n\n this.#stateChangeTimeoutId = setTimeout(() => {\n this.#stateChangeTimeoutId = undefined;\n const state = this.collectState(plugins);\n this.#grid._emit('column-state-change', state);\n }, STATE_CHANGE_DEBOUNCE_MS);\n }\n // #endregion\n\n // #region Column Visibility API\n /**\n * Set the visibility of a column.\n * @returns true if visibility changed, false otherwise\n */\n setColumnVisible(field: string, visible: boolean): boolean {\n const allCols = this.columns;\n const col = allCols.find((c) => c.field === field);\n\n if (!col) return false;\n if (!visible && col.lockVisible) return false;\n\n // Ensure at least one column remains visible\n if (!visible) {\n const remainingVisible = allCols.filter((c) => !c.hidden && c.field !== field).length;\n if (remainingVisible === 0) return false;\n }\n\n const wasHidden = !!col.hidden;\n if (wasHidden === !visible) return false; // No change\n\n col.hidden = !visible;\n\n this.#grid._emit('column-visibility', {\n field,\n visible,\n visibleColumns: allCols.filter((c) => !c.hidden).map((c) => c.field),\n });\n\n this.#grid._clearRowPool();\n this.#grid._setup();\n\n return true;\n }\n\n /**\n * Toggle column visibility.\n */\n toggleColumnVisibility(field: string): boolean {\n const col = this.columns.find((c) => c.field === field);\n return col ? this.setColumnVisible(field, !!col.hidden) : false;\n }\n\n /**\n * Check if a column is visible.\n */\n isColumnVisible(field: string): boolean {\n const col = this.columns.find((c) => c.field === field);\n return col ? !col.hidden : false;\n }\n\n /**\n * Show all columns.\n */\n showAllColumns(): void {\n const allCols = this.columns;\n if (!allCols.some((c) => c.hidden)) return;\n\n allCols.forEach((c) => (c.hidden = false));\n\n this.#grid._emit('column-visibility', {\n visibleColumns: allCols.map((c) => c.field),\n });\n\n this.#grid._clearRowPool();\n this.#grid._setup();\n }\n\n /**\n * Get all columns with visibility info.\n */\n getAllColumns(): Array<{\n field: string;\n header: string;\n visible: boolean;\n lockVisible?: boolean;\n utility?: boolean;\n }> {\n return this.columns.map((c) => ({\n field: c.field,\n header: c.header || c.field,\n visible: !c.hidden,\n lockVisible: c.lockVisible,\n utility: c.meta?.utility === true,\n }));\n }\n\n /**\n * Get current column order.\n */\n getColumnOrder(): string[] {\n return this.columns.map((c) => c.field);\n }\n\n /**\n * Set column order.\n */\n setColumnOrder(order: string[]): void {\n if (!order.length) return;\n\n const columnMap = new Map(this.columns.map((c) => [c.field as string, c]));\n const reordered: ColumnInternal<T>[] = [];\n\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 remaining columns not in order\n for (const col of columnMap.values()) {\n reordered.push(col);\n }\n\n this.columns = reordered;\n\n renderHeader(this.#grid);\n updateTemplate(this.#grid);\n this.#grid._requestSchedulerPhase(RenderPhase.VIRTUALIZATION, 'configManager');\n }\n // #endregion\n\n // #region Light DOM Observer\n /**\n * Parse light DOM columns from host element.\n */\n parseLightDomColumns(host: HTMLElement): void {\n if (!this.#lightDomColumnsCache) {\n this.#originalColumnNodes = Array.from(host.querySelectorAll('tbw-grid-column')) as HTMLElement[];\n this.#lightDomColumnsCache = this.#originalColumnNodes.length ? parseLightDomColumns(host) : [];\n }\n }\n\n /**\n * Clear the light DOM columns cache.\n */\n clearLightDomCache(): void {\n this.#lightDomColumnsCache = undefined;\n }\n\n /**\n * Registered Light DOM element handlers.\n * Maps element tag names to callbacks that are invoked when those elements change.\n *\n * This is a generic mechanism - plugins (or future ShellPlugin) register\n * what elements they care about and handle parsing themselves.\n */\n #lightDomHandlers: Map<string, () => void> = new Map();\n\n /**\n * Register a handler for Light DOM element changes.\n * When elements matching the tag name are added/removed/changed,\n * the callback will be invoked (debounced).\n *\n * @param tagName - The lowercase tag name to watch (e.g., 'tbw-grid-header')\n * @param callback - Called when matching elements change\n */\n registerLightDomHandler(tagName: string, callback: () => void): void {\n this.#lightDomHandlers.set(tagName.toLowerCase(), callback);\n }\n\n /**\n * Unregister a Light DOM element handler.\n */\n unregisterLightDomHandler(tagName: string): void {\n this.#lightDomHandlers.delete(tagName.toLowerCase());\n }\n\n /**\n * Set up MutationObserver to watch for Light DOM changes.\n * This is generic infrastructure - specific handling is done via registered handlers.\n *\n * When Light DOM elements are added/removed/changed, the observer:\n * 1. Identifies which registered tag names were affected\n * 2. Debounces multiple mutations into a single callback per handler\n * 3. Invokes the registered callbacks\n *\n * This mechanism allows plugins to register their own Light DOM elements\n * and handle parsing themselves, then hand config to ConfigManager.\n *\n * @param host - The host element to observe (the grid element)\n */\n observeLightDOM(host: HTMLElement): void {\n // Clean up any existing observer\n if (this.#lightDomObserver) {\n this.#lightDomObserver.disconnect();\n }\n\n // Track which handlers need to be called (debounced)\n const pendingCallbacks = new Set<string>();\n\n const processPendingCallbacks = () => {\n this.#lightDomDebounceTimer = undefined;\n for (const tagName of pendingCallbacks) {\n const handler = this.#lightDomHandlers.get(tagName);\n handler?.();\n }\n pendingCallbacks.clear();\n };\n\n this.#lightDomObserver = new MutationObserver((mutations) => {\n for (const mutation of mutations) {\n // Check added nodes\n for (const node of mutation.addedNodes) {\n if (node.nodeType !== Node.ELEMENT_NODE) continue;\n const el = node as Element;\n const tagName = el.tagName.toLowerCase();\n\n // Check if any handler is interested in this element\n if (this.#lightDomHandlers.has(tagName)) {\n pendingCallbacks.add(tagName);\n }\n }\n\n // Check for attribute changes\n if (mutation.type === 'attributes' && mutation.target.nodeType === Node.ELEMENT_NODE) {\n const el = mutation.target as Element;\n const tagName = el.tagName.toLowerCase();\n if (this.#lightDomHandlers.has(tagName)) {\n pendingCallbacks.add(tagName);\n }\n }\n }\n\n // Debounce - batch all mutations into single callbacks\n if (pendingCallbacks.size > 0 && !this.#lightDomDebounceTimer) {\n this.#lightDomDebounceTimer = setTimeout(processPendingCallbacks, 0);\n }\n });\n\n // Observe children and their attributes\n this.#lightDomObserver.observe(host, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['title', 'field', 'header', 'width', 'hidden', 'id', 'icon', 'tooltip', 'order'],\n });\n }\n // #endregion\n\n // #region Change Notification\n /**\n * Register a change listener.\n */\n onChange(callback: () => void): void {\n this.#changeListeners.push(callback);\n }\n\n /**\n * Notify all change listeners.\n */\n notifyChange(): void {\n for (const cb of this.#changeListeners) {\n cb();\n }\n }\n // #endregion\n\n // #region Cleanup\n /**\n * Dispose of the ConfigManager and clean up resources.\n */\n dispose(): void {\n this.#lightDomObserver?.disconnect();\n this.#changeListeners = [];\n if (this.#stateChangeTimeoutId) {\n clearTimeout(this.#stateChangeTimeoutId);\n }\n if (this.#lightDomDebounceTimer) {\n clearTimeout(this.#lightDomDebounceTimer);\n this.#lightDomDebounceTimer = undefined;\n }\n }\n // #endregion\n}\n","// #region Environment Helpers\n\n/**\n * Check if we're running in a development environment.\n * Returns true for localhost or when NODE_ENV !== 'production'.\n * Used to show warnings only in development.\n */\nexport function isDevelopment(): boolean {\n // Check for localhost (browser environment)\n if (typeof window !== 'undefined' && window.location) {\n const hostname = window.location.hostname;\n if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') {\n return true;\n }\n }\n // Check for NODE_ENV (build-time or SSR)\n if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {\n return true;\n }\n return false;\n}\n\n// #endregion\n\n// #region Cell Rendering Helpers\n\n/**\n * Generate accessible HTML for a boolean cell.\n * Uses role=\"checkbox\" with proper aria attributes.\n */\nexport function booleanCellHTML(value: boolean): string {\n return `<span role=\"checkbox\" aria-checked=\"${value}\" aria-label=\"${value}\">${value ? '🗹' : '☐'}</span>`;\n}\n\n/**\n * Format a date value for display.\n * Handles Date objects, timestamps, and date strings.\n * Returns empty string for invalid dates.\n */\nexport function formatDateValue(value: unknown): string {\n if (value == null || value === '') return '';\n if (value instanceof Date) {\n return isNaN(value.getTime()) ? '' : value.toLocaleDateString();\n }\n if (typeof value === 'number' || typeof value === 'string') {\n const d = new Date(value);\n return isNaN(d.getTime()) ? '' : d.toLocaleDateString();\n }\n return '';\n}\n\n/**\n * Get the row index from a cell element's data-row attribute.\n * Falls back to calculating from parent row's DOM position if data-row is missing.\n * Returns -1 if no valid row index is found.\n */\nexport function getRowIndexFromCell(cell: Element | null): number {\n if (!cell) return -1;\n const attr = cell.getAttribute('data-row');\n if (attr) return parseInt(attr, 10);\n\n // Fallback: find the parent .data-grid-row and calculate index from siblings\n const rowEl = cell.closest('.data-grid-row');\n if (!rowEl) return -1;\n\n const parent = rowEl.parentElement;\n if (!parent) return -1;\n\n // Get all data-grid-row siblings and find this row's index\n const rows = parent.querySelectorAll(':scope > .data-grid-row');\n for (let i = 0; i < rows.length; i++) {\n if (rows[i] === rowEl) return i;\n }\n return -1;\n}\n\n/**\n * Get the column index from a cell element's data-col attribute.\n * Returns -1 if no valid column index is found.\n */\nexport function getColIndexFromCell(cell: Element | null): number {\n if (!cell) return -1;\n const attr = cell.getAttribute('data-col');\n return attr ? parseInt(attr, 10) : -1;\n}\n\n/**\n * Clear all cell-focus styling from a root element.\n * Used when changing focus or when selection plugin takes over focus management.\n */\nexport function clearCellFocus(root: Element | null): void {\n if (!root) return;\n root.querySelectorAll('.cell-focus').forEach((el) => el.classList.remove('cell-focus'));\n}\n// #endregion\n\n// #region RTL Helpers\n\n/** Text direction */\nexport type TextDirection = 'ltr' | 'rtl';\n\n/**\n * Get the text direction for an element.\n * Reads from the computed style, which respects the `dir` attribute on the element\n * or any ancestor, as well as CSS `direction` property.\n *\n * @param element - The element to check direction for\n * @returns 'ltr' or 'rtl'\n *\n * @example\n * ```typescript\n * // Detect grid's direction\n * const dir = getDirection(gridElement);\n * if (dir === 'rtl') {\n * // Handle RTL layout\n * }\n * ```\n */\nexport function getDirection(element: Element): TextDirection {\n // Try computed style first (works in real browsers)\n try {\n const computedDir = getComputedStyle(element).direction;\n if (computedDir === 'rtl') return 'rtl';\n } catch {\n // getComputedStyle may fail in some test environments\n }\n\n // Fallback: check dir attribute on element or ancestors\n // This handles test environments where getComputedStyle may not reflect dir attribute\n try {\n const dirAttr = element.closest?.('[dir]')?.getAttribute('dir');\n if (dirAttr === 'rtl') return 'rtl';\n } catch {\n // closest may not be available on mock elements\n }\n\n return 'ltr';\n}\n\n/**\n * Check if an element is in RTL mode.\n *\n * @param element - The element to check\n * @returns true if the element's text direction is right-to-left\n */\nexport function isRTL(element: Element): boolean {\n return getDirection(element) === 'rtl';\n}\n\n/**\n * Resolve a logical inline position to a physical position based on text direction.\n *\n * - `'start'` → `'left'` in LTR, `'right'` in RTL\n * - `'end'` → `'right'` in LTR, `'left'` in RTL\n * - `'left'` / `'right'` → unchanged (physical values)\n *\n * @param position - Logical or physical position\n * @param direction - Text direction ('ltr' or 'rtl')\n * @returns Physical position ('left' or 'right')\n *\n * @example\n * ```typescript\n * resolveInlinePosition('start', 'ltr'); // 'left'\n * resolveInlinePosition('start', 'rtl'); // 'right'\n * resolveInlinePosition('left', 'rtl'); // 'left' (unchanged)\n * ```\n */\nexport function resolveInlinePosition(\n position: 'left' | 'right' | 'start' | 'end',\n direction: TextDirection,\n): 'left' | 'right' {\n if (position === 'left' || position === 'right') {\n return position;\n }\n if (direction === 'rtl') {\n return position === 'start' ? 'right' : 'left';\n }\n return position === 'start' ? 'left' : 'right';\n}\n// #endregion\n","import type { ColumnInternal, ColumnViewRenderer, GridHost, InternalGrid, RowElementInternal } from '../types';\nimport {\n CELL_CLASS_ERROR,\n FORMAT_ERROR,\n ROW_CLASS_ERROR,\n VIEW_DISPATCH_ERROR,\n VIEW_MOUNT_ERROR,\n warnDiagnostic,\n} from './diagnostics';\nimport { ensureCellVisible } from './keyboard';\nimport { evalTemplateString, finalCellScrub, sanitizeHTML } from './sanitize';\nimport { booleanCellHTML, clearCellFocus, formatDateValue, getRowIndexFromCell } from './utils';\n\n/** Callback type for plugin row rendering hook */\nexport type RenderRowHook = (row: any, rowEl: HTMLElement, rowIndex: number) => boolean;\n\n// #region Type Defaults Resolution\n/**\n * Resolves the renderer for a column using the priority chain:\n * 1. Column-level (`column.renderer` / `column.viewRenderer`)\n * NOTE: typeDefaults are applied to columns at config merge time,\n * so columns with matching types already have their renderer set.\n * 2. App-level (framework adapter's `getTypeDefault`)\n * 3. Returns undefined (caller uses built-in or fallback)\n */\nexport function resolveRenderer<TRow>(\n grid: InternalGrid<TRow>,\n col: ColumnInternal<TRow>,\n): ColumnViewRenderer<TRow, unknown> | undefined {\n // 1. Column-level renderer (highest priority)\n // NOTE: typeDefaults from gridConfig are applied to columns at config merge time\n // by ConfigManager.#applyTypeDefaultsToColumns(), so they appear here as col.renderer\n const columnRenderer = col.renderer || col.viewRenderer;\n if (columnRenderer) return columnRenderer;\n\n // No type specified - no type defaults to check\n if (!col.type) return undefined;\n\n // 2. App-level registry (via framework adapter)\n // This is for framework adapters that register type defaults dynamically\n const adapter = grid.__frameworkAdapter;\n if (adapter?.getTypeDefault) {\n const appDefault = adapter.getTypeDefault<TRow>(col.type);\n if (appDefault?.renderer) {\n return appDefault.renderer;\n }\n }\n\n // 3. No custom renderer - caller uses built-in/fallback\n return undefined;\n}\n\n/**\n * Resolves the format function for a column using the priority chain:\n * 1. Column-level (`column.format`)\n * NOTE: typeDefaults are applied to columns at config merge time,\n * so columns with matching types already have their format set.\n * 2. App-level (framework adapter's `getTypeDefault`)\n * 3. Returns undefined (caller uses built-in or fallback)\n */\nexport function resolveFormat<TRow>(\n grid: InternalGrid<TRow>,\n col: ColumnInternal<TRow>,\n): ((value: unknown, row: TRow) => string) | undefined {\n // 1. Column-level format (highest priority)\n // NOTE: typeDefaults from gridConfig are applied to columns at config merge time\n // by ConfigManager.#applyTypeDefaultsToColumns(), so they appear here as col.format\n if (col.format) return col.format;\n\n // No type specified - no type defaults to check\n if (!col.type) return undefined;\n\n // 2. App-level registry (via framework adapter)\n // This is for framework adapters that register type defaults dynamically\n const adapter = grid.__frameworkAdapter;\n if (adapter?.getTypeDefault) {\n const appDefault = adapter.getTypeDefault<TRow>(col.type);\n if (appDefault?.format) {\n return appDefault.format as (value: unknown, row: TRow) => string;\n }\n }\n\n // 3. No custom format - caller uses built-in/fallback\n return undefined;\n}\n// #endregion\n\n// #region DOM State Helpers\n/**\n * CSS selector for focusable editor elements within a cell.\n * Used by EditingPlugin and keyboard navigation.\n */\nexport const FOCUSABLE_EDITOR_SELECTOR =\n 'input,select,textarea,[contenteditable=\"true\"],[contenteditable=\"\"],[tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Check if a row element has any cells in editing mode.\n * This is a DOM-level check used for virtualization recycling.\n */\nfunction hasEditingCells(rowEl: RowElementInternal): boolean {\n return (rowEl.__editingCellCount ?? 0) > 0;\n}\n\n/**\n * Clear all editing state from a row element.\n * Called when a row element is recycled for a different data row.\n */\nfunction clearEditingState(rowEl: RowElementInternal): void {\n rowEl.__editingCellCount = 0;\n rowEl.removeAttribute('data-has-editing');\n // Clear editing class from all cells\n const cells = rowEl.querySelectorAll('.cell.editing');\n cells.forEach((cell) => cell.classList.remove('editing'));\n}\n// #endregion\n\n// #region Template Cloning System\n// Using template cloning is 3-4x faster than document.createElement + setAttribute\n// for repetitive element creation because the browser can skip parsing.\n\n/**\n * Cell template for cloning. Pre-configured with static attributes.\n * Dynamic attributes (data-col, data-row, etc.) are set after cloning.\n */\nconst cellTemplate = document.createElement('template');\ncellTemplate.innerHTML = '<div class=\"cell\" role=\"gridcell\" part=\"cell\"></div>';\n\n/**\n * Row template for cloning. Pre-configured with static attributes.\n * Dynamic attributes (data-row) and children (cells) are set after cloning.\n */\nconst rowTemplate = document.createElement('template');\nrowTemplate.innerHTML = '<div class=\"data-grid-row\" role=\"row\" part=\"row\"></div>';\n\n/**\n * Create a cell element from template. Significantly faster than createElement + setAttribute.\n */\nfunction createCellFromTemplate(): HTMLDivElement {\n return cellTemplate.content.firstElementChild!.cloneNode(true) as HTMLDivElement;\n}\n\n/**\n * Create a row element from template. Significantly faster than createElement + setAttribute.\n */\nexport function createRowFromTemplate(): HTMLDivElement {\n return rowTemplate.content.firstElementChild!.cloneNode(true) as HTMLDivElement;\n}\n// #endregion\n\n// #region Row Rendering\n/**\n * Invalidate the cell cache (call when rows or columns change).\n */\nexport function invalidateCellCache(grid: InternalGrid): void {\n grid.__cellDisplayCache = undefined;\n grid.__cellCacheEpoch = undefined;\n grid.__hasSpecialColumns = undefined; // Reset fast-path check\n}\n\n/**\n * Render / patch the visible window of rows [start, end) using a recyclable DOM pool.\n * Newly required row elements are created and appended; excess are detached.\n * Uses an epoch counter to force full row rebuilds when structural changes (like columns) occur.\n * @param renderRowHook - Optional callback that plugins can use to render custom rows (e.g., group rows).\n * If it returns true, default rendering is skipped for that row.\n */\nexport function renderVisibleRows(\n grid: GridHost,\n start: number,\n end: number,\n epoch?: number,\n renderRowHook?: RenderRowHook,\n): void {\n const needed = Math.max(0, end - start);\n const bodyEl = grid._bodyEl;\n const columns = grid._visibleColumns;\n const colLen = columns.length;\n\n // Cache header row count once (check for group header row existence)\n let headerRowCount = grid.__cachedHeaderRowCount;\n if (headerRowCount === undefined) {\n headerRowCount = grid.querySelector('.header-group-row') ? 2 : 1;\n grid.__cachedHeaderRowCount = headerRowCount;\n }\n\n // Pool management: grow pool if needed\n // Note: click/dblclick handlers are delegated at grid level for efficiency\n while (grid._rowPool.length < needed) {\n // Use template cloning - 3-4x faster than createElement + setAttribute\n const rowEl = createRowFromTemplate();\n grid._rowPool.push(rowEl);\n }\n\n // Remove excess pool elements from DOM and shrink pool\n if (grid._rowPool.length > needed) {\n for (let i = needed; i < grid._rowPool.length; i++) {\n const el = grid._rowPool[i];\n if (el.parentNode === bodyEl) el.remove();\n }\n grid._rowPool.length = needed;\n }\n\n // Check if any plugin has a renderRow hook (cache this)\n const hasRenderRowPlugins = renderRowHook && grid.__hasRenderRowPlugins !== false;\n\n // Check if any plugin wants row-level hooks (avoid overhead when not needed)\n const hasRowHook = grid._hasAfterRowRenderHook?.() ?? false;\n\n // Cache variable-height function for per-row CSS variable override\n const varHeightFn =\n grid._virtualization?.variableHeights && typeof grid.effectiveConfig?.rowHeight === 'function'\n ? (grid.effectiveConfig.rowHeight as (row: unknown, index: number) => number | undefined)\n : null;\n\n for (let i = 0; i < needed; i++) {\n const rowIndex = start + i;\n const rowData = grid._rows[rowIndex];\n const rowEl = grid._rowPool[i] as RowElementInternal;\n\n // Always set aria-rowindex (1-based, accounting for header rows)\n rowEl.setAttribute('aria-rowindex', String(rowIndex + headerRowCount + 1));\n\n // Let plugins handle custom row rendering (e.g., group rows)\n if (hasRenderRowPlugins && renderRowHook!(rowData, rowEl, rowIndex)) {\n rowEl.__epoch = epoch;\n rowEl.__rowDataRef = rowData;\n if (rowEl.parentNode !== bodyEl) bodyEl.appendChild(rowEl);\n continue;\n }\n\n const rowEpoch = rowEl.__epoch;\n const prevRef = rowEl.__rowDataRef;\n let cellCount = rowEl.children.length;\n\n // Loading overlay is a non-cell child appended at the end — exclude from cell count\n // to avoid false structure-invalid detection that causes unnecessary full rebuilds.\n if (cellCount > colLen && rowEl.lastElementChild?.classList.contains('tbw-row-loading-overlay')) {\n cellCount--;\n }\n\n // Check if we need a full rebuild vs fast update\n const epochMatch = rowEpoch === epoch;\n const structureValid = epochMatch && cellCount === colLen;\n const dataRefChanged = prevRef !== rowData;\n // In grid edit mode, all rows have editing cells that must be preserved\n const isGridEditMode = !!grid._isGridEditMode;\n\n // Need external view rebuild check when structure is valid but data changed\n let needsExternalRebuild = false;\n if (structureValid && dataRefChanged) {\n for (let c = 0; c < colLen; c++) {\n const col = columns[c];\n if (col.externalView) {\n const cellCheck = rowEl.querySelector(`.cell[data-col=\"${c}\"] [data-external-view]`);\n if (!cellCheck) {\n needsExternalRebuild = true;\n break;\n }\n }\n }\n }\n\n if (!structureValid || needsExternalRebuild) {\n // Full rebuild needed - epoch changed, cell count mismatch, or external view missing\n // Use cached editing state for O(1) check instead of querySelector\n const hasEditing = hasEditingCells(rowEl);\n // In grid edit mode, treat recycled rows (different data ref) as needing a rebuild\n // so afterCellRender can re-evaluate per-cell editability for the new row data.\n const isActivelyEditedRow = (isGridEditMode && !dataRefChanged) || grid._activeEditRows === rowIndex;\n\n // If DOM element has editors but this is NOT the actively edited row, clear them\n // (This happens when virtualization recycles the DOM element for a different row)\n if (hasEditing && !isActivelyEditedRow) {\n // Force full rebuild to clear stale editors\n if (rowEl.__isCustomRow) {\n rowEl.className = 'data-grid-row';\n rowEl.setAttribute('role', 'row');\n rowEl.__isCustomRow = false;\n }\n clearEditingState(rowEl); // Clear editing state before rebuild\n renderInlineRow(grid, rowEl, rowData, rowIndex);\n rowEl.__epoch = epoch;\n rowEl.__rowDataRef = rowData;\n } else if (hasEditing && isActivelyEditedRow) {\n // Row is in editing mode AND this is the correct row - preserve editors\n fastPatchRow(grid, rowEl, rowData, rowIndex);\n rowEl.__rowDataRef = rowData;\n } else {\n if (rowEl.__isCustomRow) {\n rowEl.className = 'data-grid-row';\n rowEl.setAttribute('role', 'row');\n rowEl.__isCustomRow = false;\n }\n renderInlineRow(grid, rowEl, rowData, rowIndex);\n rowEl.__epoch = epoch;\n rowEl.__rowDataRef = rowData;\n // NOTE: If this is the actively edited row, EditingPlugin's onScrollRender() will inject editors\n }\n } else if (dataRefChanged) {\n // Same structure, different row data - fast update\n // Use cached editing state for O(1) check instead of querySelector\n const hasEditing = hasEditingCells(rowEl);\n // In grid edit mode with changed data ref, clear editors and rebuild\n // so afterCellRender can re-evaluate per-cell editability for the new row.\n const isActivelyEditedRow = grid._activeEditRows === rowIndex;\n\n // If DOM element has editors but this is NOT the actively edited row, clear them\n if (hasEditing && !isActivelyEditedRow) {\n clearEditingState(rowEl); // Clear editing state before rebuild\n renderInlineRow(grid, rowEl, rowData, rowIndex);\n rowEl.__epoch = epoch;\n rowEl.__rowDataRef = rowData;\n } else {\n fastPatchRow(grid, rowEl, rowData, rowIndex);\n rowEl.__rowDataRef = rowData;\n // NOTE: If this is the actively edited row, EditingPlugin's onScrollRender() will inject editors\n }\n } else {\n // Same row data reference - just patch if any values changed\n // Use cached editing state for O(1) check instead of querySelector\n const hasEditing = hasEditingCells(rowEl);\n // Same data ref means no recycling — safe to preserve editors in grid mode.\n const isActivelyEditedRow = isGridEditMode || grid._activeEditRows === rowIndex;\n\n // If DOM element has editors but this is NOT the actively edited row, clear them\n if (hasEditing && !isActivelyEditedRow) {\n clearEditingState(rowEl); // Clear editing state before rebuild\n renderInlineRow(grid, rowEl, rowData, rowIndex);\n rowEl.__epoch = epoch;\n rowEl.__rowDataRef = rowData;\n } else {\n fastPatchRow(grid, rowEl, rowData, rowIndex);\n // NOTE: If this is the actively edited row, EditingPlugin's onScrollRender() will inject editors\n }\n }\n\n // Changed class toggle - check if row ID is in changedRowIds Set (EditingPlugin)\n let isChanged = false;\n const changedRowIdSet = grid._changedRowIdSet;\n if (changedRowIdSet && changedRowIdSet.size > 0) {\n try {\n const rowId = grid.getRowId?.(rowData);\n if (rowId) {\n isChanged = changedRowIdSet.has(rowId);\n }\n } catch {\n // Row has no ID - not tracked as changed\n }\n }\n const hasChangedClass = rowEl.classList.contains('changed');\n if (isChanged !== hasChangedClass) {\n rowEl.classList.toggle('changed', isChanged);\n }\n\n // Apply rowClass callback if configured\n const rowClassFn = grid.effectiveConfig?.rowClass;\n if (rowClassFn) {\n // Remove previous dynamic classes (stored in data attribute)\n const prevClasses = rowEl.getAttribute('data-dynamic-classes');\n if (prevClasses) {\n prevClasses.split(' ').forEach((cls) => cls && rowEl.classList.remove(cls));\n }\n try {\n const result = rowClassFn(rowData);\n const newClasses = typeof result === 'string' ? result.split(/\\s+/) : result;\n if (newClasses && newClasses.length > 0) {\n let dynamicClassStr = '';\n for (const c of newClasses) {\n if (c && typeof c === 'string') {\n rowEl.classList.add(c);\n dynamicClassStr += (dynamicClassStr ? ' ' : '') + c;\n }\n }\n rowEl.setAttribute('data-dynamic-classes', dynamicClassStr);\n } else {\n rowEl.removeAttribute('data-dynamic-classes');\n }\n } catch (e) {\n warnDiagnostic(ROW_CLASS_ERROR, `rowClass callback error: ${e}`, grid.id);\n rowEl.removeAttribute('data-dynamic-classes');\n }\n }\n\n // Apply per-row variable height via --tbw-row-height CSS custom property.\n // Cells bind to this variable (min-height: var(--tbw-row-height)), so setting\n // it on the row element makes both the row and its cells respect the override.\n // The #measureRowHeight guard in grid.ts prevents this from corrupting s.rowHeight.\n if (varHeightFn) {\n const h = varHeightFn(rowData, rowIndex);\n if (h !== undefined && h > 0) {\n rowEl.style.setProperty('--tbw-row-height', `${h}px`);\n } else {\n rowEl.style.removeProperty('--tbw-row-height');\n }\n }\n\n // Call row-level plugin hook if any plugin registered it\n if (hasRowHook) {\n grid._afterRowRender?.({\n row: rowData,\n rowIndex,\n rowElement: rowEl,\n });\n }\n\n if (rowEl.parentNode !== bodyEl) bodyEl.appendChild(rowEl);\n }\n}\n// #endregion\n\n// #region Row Patching\n/**\n * Fast patch path for an already-rendered row: updates plain text cells whose data changed\n * while skipping cells with external views, templates, or active editors.\n *\n * Optimized for scroll performance - avoids querySelectorAll in favor of children access.\n */\nfunction fastPatchRow(grid: GridHost, rowEl: HTMLElement, rowData: any, rowIndex: number): void {\n const children = rowEl.children;\n const columns = grid._visibleColumns;\n const colsLen = columns.length;\n const childLen = children.length;\n const minLen = colsLen < childLen ? colsLen : childLen;\n const focusRow = grid._focusRow;\n const focusCol = grid._focusCol;\n\n // Check if any plugin wants cell-level hooks (avoid overhead when not needed)\n const hasCellHook = grid._hasAfterCellRenderHook?.() ?? false;\n\n // Ultra-fast path: if no special columns (templates, formatters, etc.), use direct assignment\n // Check is cached on grid to avoid repeated iteration\n let hasSpecialCols = grid.__hasSpecialColumns;\n if (hasSpecialCols === undefined) {\n hasSpecialCols = false;\n // NOTE: typeDefaults are now applied to columns at config merge time\n // by ConfigManager.#applyTypeDefaultsToColumns(), so columns already have\n // their renderer/format set if a typeDefault matched. No runtime lookup needed.\n const adapter = grid.__frameworkAdapter;\n for (let i = 0; i < colsLen; i++) {\n const col = columns[i];\n if (\n col.__viewTemplate ||\n col.__compiledView ||\n col.renderer ||\n col.viewRenderer ||\n col.externalView ||\n col.format ||\n col.cellClass ||\n col.type === 'date' ||\n col.type === 'boolean' ||\n // Check for adapter-level type defaults (framework adapters)\n (col.type && adapter?.getTypeDefault?.(col.type)?.renderer) ||\n (col.type && adapter?.getTypeDefault?.(col.type)?.format)\n ) {\n hasSpecialCols = true;\n break;\n }\n }\n grid.__hasSpecialColumns = hasSpecialCols;\n }\n\n const rowIndexStr = String(rowIndex);\n\n // Ultra-fast path for plain text grids - just set textContent directly\n if (!hasSpecialCols) {\n for (let i = 0; i < minLen; i++) {\n const cell = children[i] as HTMLElement;\n\n // Skip cells in edit mode - they have editors that must be preserved\n if (cell.classList.contains('editing')) continue;\n\n // Release editor views if cell has element children (indicating prior editor/renderer DOM).\n // Plain text cells (textContent-only) have no element children, so this is a fast O(1) skip.\n if (cell.firstElementChild) grid.__frameworkAdapter?.releaseCell?.(cell);\n\n const col = columns[i];\n const value = rowData[col.field];\n cell.textContent = value == null ? '' : String(value);\n // Update data-row for click handling\n if (cell.getAttribute('data-row') !== rowIndexStr) {\n cell.setAttribute('data-row', rowIndexStr);\n }\n // Update focus state - must be data-driven, not DOM-element-driven\n const shouldHaveFocus = focusRow === rowIndex && focusCol === i;\n const hasFocus = cell.classList.contains('cell-focus');\n if (shouldHaveFocus !== hasFocus) {\n cell.classList.toggle('cell-focus', shouldHaveFocus);\n // aria-selected only valid for gridcell, not checkbox (but ultra-fast path has no special cols)\n cell.setAttribute('aria-selected', String(shouldHaveFocus));\n }\n\n // Call cell-level plugin hook if any plugin registered it\n if (hasCellHook) {\n grid._afterCellRender?.({\n row: rowData,\n rowIndex,\n column: col,\n colIndex: i,\n value,\n cellElement: cell,\n rowElement: rowEl,\n });\n }\n }\n return;\n }\n\n // Check if any external view placeholder is missing - if so, do full rebuild\n for (let i = 0; i < minLen; i++) {\n const col = columns[i];\n if (col.externalView) {\n const cell = children[i] as HTMLElement;\n if (!cell.querySelector('[data-external-view]')) {\n renderInlineRow(grid, rowEl, rowData, rowIndex);\n return;\n }\n }\n }\n\n // Standard path for grids with special columns\n for (let i = 0; i < minLen; i++) {\n const col = columns[i];\n const cell = children[i] as HTMLElement;\n\n // Update data-row for click handling\n if (cell.getAttribute('data-row') !== rowIndexStr) {\n cell.setAttribute('data-row', rowIndexStr);\n }\n\n // Check editing state once — reused for focus guard and content skip below.\n const isEditing = cell.classList.contains('editing');\n\n // Update focus state - must be data-driven, not DOM-element-driven.\n // Skip editing cells — their focus state is managed by the navigation\n // system (ensureCellVisible), not the render pipeline. Toggling here\n // would fire MutationObservers (e.g., overlay editors) causing\n // premature overlay teardown during re-renders triggered by resize.\n if (!isEditing) {\n const shouldHaveFocus = focusRow === rowIndex && focusCol === i;\n const hasFocus = cell.classList.contains('cell-focus');\n if (shouldHaveFocus !== hasFocus) {\n cell.classList.toggle('cell-focus', shouldHaveFocus);\n cell.setAttribute('aria-selected', String(shouldHaveFocus));\n }\n }\n\n // Apply cellClass callback if configured\n const cellClassFn = col.cellClass;\n if (cellClassFn) {\n // Remove previous dynamic classes\n const prevClasses = cell.getAttribute('data-dynamic-classes');\n if (prevClasses) {\n prevClasses.split(' ').forEach((cls) => cls && cell.classList.remove(cls));\n }\n try {\n const value = rowData[col.field];\n const result = cellClassFn(value, rowData, col);\n const cellClasses = typeof result === 'string' ? result.split(/\\s+/) : result;\n if (cellClasses && cellClasses.length > 0) {\n const validClasses = cellClasses.filter((c: string) => c && typeof c === 'string');\n validClasses.forEach((cls: string) => cell.classList.add(cls));\n cell.setAttribute('data-dynamic-classes', validClasses.join(' '));\n } else {\n cell.removeAttribute('data-dynamic-classes');\n }\n } catch (e) {\n warnDiagnostic(CELL_CLASS_ERROR, `cellClass callback error for column '${col.field}': ${e}`, grid.id);\n cell.removeAttribute('data-dynamic-classes');\n }\n }\n\n // Skip cells in edit mode\n if (isEditing) continue;\n\n // Handle viewRenderer/renderer - must re-invoke to get updated content\n // Uses priority chain: column → typeDefaults → adapter → built-in\n const cellRenderer = resolveRenderer(grid, col);\n if (cellRenderer) {\n const renderedValue = rowData[col.field];\n // Pass cellEl for framework adapters that want to cache per-cell\n const produced = cellRenderer({\n row: rowData,\n value: renderedValue,\n field: col.field,\n column: col,\n cellEl: cell,\n });\n if (typeof produced === 'string') {\n // Release editor views before wiping cell content\n grid.__frameworkAdapter?.releaseCell?.(cell);\n cell.innerHTML = sanitizeHTML(produced);\n } else if (produced instanceof Node) {\n // Check if this container is already a child of the cell (reused by framework adapter)\n if (produced.parentElement !== cell) {\n // Release editor views before wiping cell content\n grid.__frameworkAdapter?.releaseCell?.(cell);\n cell.innerHTML = '';\n cell.appendChild(produced);\n }\n // If already a child, the framework adapter has re-rendered in place\n } else if (produced == null) {\n // Renderer returned null/undefined - show raw value\n grid.__frameworkAdapter?.releaseCell?.(cell);\n cell.textContent = renderedValue == null ? '' : String(renderedValue);\n }\n // If produced is truthy but not a string or Node, the framework handles it\n // Call cell-level plugin hook - cell was rendered\n if (hasCellHook) {\n grid._afterCellRender?.({\n row: rowData,\n rowIndex,\n column: col,\n colIndex: i,\n value: renderedValue,\n cellElement: cell,\n rowElement: rowEl,\n });\n }\n continue;\n }\n\n // Handle compiled view templates — re-evaluate with current row data\n if (col.__compiledView) {\n const value = rowData[col.field];\n const output = col.__compiledView({ row: rowData, value, field: col.field, column: col });\n const blocked = col.__compiledView.__blocked;\n if (blocked) {\n cell.textContent = '';\n } else {\n // Release any framework views before replacing innerHTML\n if (cell.firstElementChild) grid.__frameworkAdapter?.releaseCell?.(cell);\n cell.innerHTML = sanitizeHTML(output);\n finalCellScrub(cell);\n }\n if (hasCellHook) {\n grid._afterCellRender?.({\n row: rowData,\n rowIndex,\n column: col,\n colIndex: i,\n value,\n cellElement: cell,\n rowElement: rowEl,\n });\n }\n continue;\n }\n\n // Handle inline view templates — re-evaluate with current row data\n if (col.__viewTemplate) {\n const value = rowData[col.field];\n const rawTpl = col.__viewTemplate.innerHTML;\n if (/Reflect\\.|\\bProxy\\b|ownKeys\\(/.test(rawTpl)) {\n cell.textContent = '';\n } else {\n if (cell.firstElementChild) grid.__frameworkAdapter?.releaseCell?.(cell);\n cell.innerHTML = sanitizeHTML(evalTemplateString(rawTpl, { row: rowData, value }));\n finalCellScrub(cell);\n }\n if (hasCellHook) {\n grid._afterCellRender?.({\n row: rowData,\n rowIndex,\n column: col,\n colIndex: i,\n value,\n cellElement: cell,\n rowElement: rowEl,\n });\n }\n continue;\n }\n\n // Skip external view cells (mounted once, manages own state)\n if (col.externalView) {\n continue;\n }\n\n // Compute and set display value\n const value = rowData[col.field];\n let displayStr: string;\n\n // Resolve format using priority chain: column → typeDefaults → adapter\n const formatFn = resolveFormat(grid, col);\n if (formatFn) {\n try {\n const formatted = formatFn(value, rowData);\n displayStr = formatted == null ? '' : String(formatted);\n } catch (e) {\n // Log format errors as warnings (user configuration issue)\n warnDiagnostic(FORMAT_ERROR, `Format error in column '${col.field}': ${e}`, grid.id);\n displayStr = value == null ? '' : String(value);\n }\n cell.textContent = displayStr;\n } else if (col.type === 'date') {\n displayStr = formatDateValue(value);\n cell.textContent = displayStr;\n } else if (col.type === 'boolean') {\n // Boolean cells have inner span with checkbox role for ARIA compliance\n cell.innerHTML = booleanCellHTML(!!value);\n } else {\n displayStr = value == null ? '' : String(value);\n cell.textContent = displayStr;\n }\n\n // Call cell-level plugin hook - cell was rendered\n if (hasCellHook) {\n grid._afterCellRender?.({\n row: rowData,\n rowIndex,\n column: col,\n colIndex: i,\n value,\n cellElement: cell,\n rowElement: rowEl,\n });\n }\n }\n}\n// #endregion\n\n// #region Cell Rendering\n/**\n * Full reconstruction of a row's set of cells including templated, external view, and formatted content.\n * Attaches event handlers for editing and accessibility per cell.\n */\nexport function renderInlineRow(grid: GridHost, rowEl: HTMLElement, rowData: any, rowIndex: number): void {\n // Clear loading state before rebuild — grid will re-apply after render for actually-loading rows.\n // This prevents stale tbw-row-loading class from persisting when pool elements are recycled.\n rowEl.classList.remove('tbw-row-loading');\n rowEl.removeAttribute('aria-busy');\n\n // Release framework editor views before wiping DOM to prevent memory leaks.\n // Without this, Angular EmbeddedViewRefs / React roots / Vue apps created by\n // editor factories would remain alive in the adapter's tracking arrays even\n // after their DOM is destroyed, leaking memory on every edit cycle.\n const adapter = grid.__frameworkAdapter;\n if (adapter?.releaseCell) {\n const children = rowEl.children;\n for (let i = children.length - 1; i >= 0; i--) {\n adapter.releaseCell(children[i] as HTMLElement);\n }\n }\n\n rowEl.innerHTML = '';\n\n // Pre-cache values used in the loop\n const columns = grid._visibleColumns;\n const colsLen = columns.length;\n const focusRow = grid._focusRow;\n const focusCol = grid._focusCol;\n\n // Check if any plugin wants cell-level hooks (avoid overhead when not needed)\n const hasCellHook = grid._hasAfterCellRenderHook?.() ?? false;\n\n // Use DocumentFragment for batch DOM insertion\n const fragment = document.createDocumentFragment();\n\n for (let colIndex = 0; colIndex < colsLen; colIndex++) {\n const col = columns[colIndex];\n // Use template cloning - 3-4x faster than createElement + setAttribute\n const cell = createCellFromTemplate();\n\n // Only set dynamic attributes (role, class, part are already set in template)\n // aria-colindex is 1-based\n cell.setAttribute('aria-colindex', String(colIndex + 1));\n cell.setAttribute('data-col', String(colIndex));\n cell.setAttribute('data-row', String(rowIndex));\n cell.setAttribute('data-field', col.field); // Field name for column identification\n cell.setAttribute('data-header', col.header ?? col.field); // Header text for responsive CSS\n if (col.type) cell.setAttribute('data-type', col.type);\n\n let value = (rowData as Record<string, unknown>)[col.field];\n // Resolve format using priority chain: column → typeDefaults → adapter\n const formatFn = resolveFormat(grid, col);\n if (formatFn) {\n try {\n value = formatFn(value, rowData);\n } catch (e) {\n // Log format errors as warnings (user configuration issue)\n warnDiagnostic(FORMAT_ERROR, `Format error in column '${col.field}': ${e}`, grid.id);\n }\n }\n\n const compiled = col.__compiledView;\n const tplHolder = col.__viewTemplate;\n // Resolve renderer using priority chain: column → typeDefaults → adapter → built-in\n const viewRenderer = resolveRenderer(grid, col);\n const externalView = col.externalView;\n\n // Track if we used a template that needs sanitization\n let needsSanitization = false;\n\n if (viewRenderer) {\n // Pass cellEl for framework adapters that want to cache per-cell\n const produced = viewRenderer({ row: rowData, value, field: col.field, column: col, cellEl: cell });\n if (typeof produced === 'string') {\n // Sanitize HTML from viewRenderer to prevent XSS from user-controlled data\n cell.innerHTML = sanitizeHTML(produced);\n needsSanitization = true;\n } else if (produced instanceof Node) {\n // Check if this container is already a child of the cell (reused by framework adapter)\n if (produced.parentElement !== cell) {\n // Clear any existing content before appending new container\n cell.textContent = '';\n cell.appendChild(produced);\n }\n // If already a child, the framework adapter has re-rendered in place\n } else if (produced == null) {\n // Renderer returned null/undefined - show raw value\n cell.textContent = value == null ? '' : String(value);\n }\n // If produced is truthy but not a string or Node (e.g., framework placeholder),\n // don't modify the cell - the framework adapter handles rendering\n } else if (externalView) {\n const spec = externalView;\n const placeholder = document.createElement('div');\n placeholder.setAttribute('data-external-view', '');\n placeholder.setAttribute('data-field', col.field);\n cell.appendChild(placeholder);\n const context = { row: rowData, value, field: col.field, column: col };\n if (spec.mount) {\n try {\n spec.mount({ placeholder, context, spec });\n } catch (e) {\n // Log mount errors as warnings (user configuration issue)\n warnDiagnostic(VIEW_MOUNT_ERROR, `External view mount error for column '${col.field}': ${e}`, grid.id);\n }\n } else {\n queueMicrotask(() => {\n try {\n grid.dispatchEvent(\n new CustomEvent('mount-external-view', {\n bubbles: true,\n composed: true,\n detail: { placeholder, spec, context },\n }),\n );\n } catch (e) {\n // Log dispatch errors as warnings\n warnDiagnostic(\n VIEW_DISPATCH_ERROR,\n `External view event dispatch error for column '${col.field}': ${e}`,\n grid.id,\n );\n }\n });\n }\n placeholder.setAttribute('data-mounted', '');\n } else if (compiled) {\n const output = compiled({ row: rowData, value, field: col.field, column: col });\n const blocked = compiled.__blocked;\n // Sanitize compiled template output to prevent XSS\n cell.innerHTML = blocked ? '' : sanitizeHTML(output);\n needsSanitization = true;\n if (blocked) {\n // Forcefully clear any residual whitespace text nodes for deterministic emptiness\n cell.textContent = '';\n cell.setAttribute('data-blocked-template', '');\n }\n } else if (tplHolder) {\n const rawTpl = tplHolder.innerHTML;\n if (/Reflect\\.|\\bProxy\\b|ownKeys\\(/.test(rawTpl)) {\n cell.textContent = '';\n cell.setAttribute('data-blocked-template', '');\n } else {\n // Sanitize inline template output to prevent XSS\n cell.innerHTML = sanitizeHTML(evalTemplateString(rawTpl, { row: rowData, value }));\n needsSanitization = true;\n }\n } else {\n // Plain value rendering - compute display directly (matches Stencil performance)\n // If formatFn was applied, value is already formatted - just use it\n if (formatFn) {\n cell.textContent = value == null ? '' : String(value);\n } else if (col.type === 'date') {\n cell.textContent = formatDateValue(value);\n } else if (col.type === 'boolean') {\n // Wrap checkbox in span to satisfy ARIA: gridcell can contain checkbox\n cell.innerHTML = booleanCellHTML(!!value);\n } else {\n cell.textContent = value == null ? '' : String(value);\n }\n }\n\n // Only run expensive sanitization when we used innerHTML with user content\n if (needsSanitization) {\n finalCellScrub(cell);\n // Defensive: if forbidden tokens leaked via async or framework hydration, scrub again.\n const textContent = cell.textContent || '';\n if (/Proxy|Reflect\\.ownKeys/.test(textContent)) {\n cell.textContent = textContent.replace(/Proxy|Reflect\\.ownKeys/g, '').trim();\n if (/Proxy|Reflect\\.ownKeys/.test(cell.textContent || '')) cell.textContent = '';\n }\n }\n\n if (cell.hasAttribute('data-blocked-template')) {\n // If anything at all remains (e.g., 'function () { [native code] }'), blank it completely.\n if ((cell.textContent || '').trim().length) cell.textContent = '';\n }\n // Mark editable cells with tabindex for keyboard navigation\n // Event handlers are set up via delegation in setupCellEventDelegation()\n const isEditable = typeof col.editable === 'function' ? col.editable(rowData) : col.editable;\n if (isEditable) {\n cell.tabIndex = 0;\n } else if (col.type === 'boolean') {\n // Non-editable boolean cells should NOT toggle on space key\n // They are read-only, only set tabindex for focus navigation\n if (!cell.hasAttribute('tabindex')) cell.tabIndex = 0;\n }\n\n // Initialize focus state (must match fastPatchRow for consistent behavior)\n if (focusRow === rowIndex && focusCol === colIndex) {\n cell.classList.add('cell-focus');\n cell.setAttribute('aria-selected', 'true');\n } else {\n cell.setAttribute('aria-selected', 'false');\n }\n\n // Apply cellClass callback if configured\n const cellClassFn = col.cellClass;\n if (cellClassFn) {\n try {\n const cellValue = (rowData as Record<string, unknown>)[col.field];\n const result = cellClassFn(cellValue, rowData, col);\n const cellClasses = typeof result === 'string' ? result.split(/\\s+/) : result;\n if (cellClasses && cellClasses.length > 0) {\n let dynamicClassStr = '';\n for (const c of cellClasses) {\n if (c && typeof c === 'string') {\n cell.classList.add(c);\n dynamicClassStr += (dynamicClassStr ? ' ' : '') + c;\n }\n }\n cell.setAttribute('data-dynamic-classes', dynamicClassStr);\n }\n } catch (e) {\n warnDiagnostic(CELL_CLASS_ERROR, `cellClass callback error for column '${col.field}': ${e}`, grid.id);\n }\n }\n\n // Call cell-level plugin hook if any plugin registered it\n if (hasCellHook) {\n grid._afterCellRender?.({\n row: rowData,\n rowIndex,\n column: col,\n colIndex,\n value,\n cellElement: cell,\n rowElement: rowEl,\n });\n }\n\n fragment.appendChild(cell);\n }\n\n // Single DOM operation to append all cells\n rowEl.appendChild(fragment);\n}\n// #endregion\n\n// #region Interaction\n/**\n * Handle click / double click interaction to focus cells.\n * Edit triggering is handled by EditingPlugin via onCellClick hook.\n */\nexport function handleRowClick(grid: GridHost, e: MouseEvent, rowEl: HTMLElement): void {\n if ((e.target as HTMLElement)?.closest('.resize-handle')) return;\n const firstCell = rowEl.querySelector('.cell[data-row]') as HTMLElement | null;\n const rowIndex = getRowIndexFromCell(firstCell);\n if (rowIndex < 0) return;\n const rowData = grid._rows[rowIndex];\n if (!rowData) return;\n\n // Dispatch row click to plugin system first (e.g., for master-detail expansion)\n if (grid._dispatchRowClick?.(e, rowIndex, rowData, rowEl)) {\n return;\n }\n\n const cellEl = (e.target as HTMLElement)?.closest('.cell[data-col]') as HTMLElement | null;\n if (cellEl) {\n const colIndex = Number(cellEl.getAttribute('data-col'));\n if (!isNaN(colIndex)) {\n // Dispatch to plugin system first - if handled (e.g., edit triggered), stop propagation\n if (grid._dispatchCellClick?.(e, rowIndex, colIndex, cellEl)) {\n return;\n }\n\n // Always update focus to the clicked cell\n const focusChanged = grid._focusRow !== rowIndex || grid._focusCol !== colIndex;\n grid._focusRow = rowIndex;\n grid._focusCol = colIndex;\n\n // If clicking an already-editing cell, just update focus styling and return\n if (cellEl.classList.contains('editing')) {\n if (focusChanged) {\n // Update .cell-focus class to reflect new focus (clear from grid element)\n clearCellFocus(grid._bodyEl ?? grid);\n cellEl.classList.add('cell-focus');\n }\n // Prefer the actual click target when it's a focusable element inside the\n // cell. This preserves user intent — e.g., clicking an <input> inside a\n // mat-chip-grid should focus that input, not the first chip row (which\n // also matches FOCUSABLE_EDITOR_SELECTOR via [tabindex]).\n const target = e.target as HTMLElement;\n const editor =\n cellEl.contains(target) && target.matches(FOCUSABLE_EDITOR_SELECTOR)\n ? target\n : (cellEl.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null);\n try {\n editor?.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n return;\n }\n\n ensureCellVisible(grid);\n }\n }\n}\n// #endregion\n","/**\n * Central keyboard handler attached to the host element. Manages navigation, paging,\n * and edit lifecycle triggers while respecting active form field interactions.\n */\nimport type { GridHost } from '../types';\nimport { FOCUSABLE_EDITOR_SELECTOR } from './rows';\nimport { clearCellFocus, isRTL } from './utils';\n\n// #region Keyboard Handler\nexport function handleGridKeyDown(grid: GridHost, e: KeyboardEvent): void {\n // Dispatch to plugin system first - if any plugin handles it, stop here\n if (grid._dispatchKeyDown?.(e)) {\n return;\n }\n\n const maxRow = grid._rows.length - 1;\n const maxCol = grid._visibleColumns.length - 1;\n const editing = grid._activeEditRows !== undefined && grid._activeEditRows !== -1;\n const col = grid._visibleColumns[grid._focusCol];\n const colType = col?.type;\n const path = e.composedPath?.() ?? [];\n const target = (path.length ? path[0] : e.target) as HTMLElement | null;\n const isFormField = (el: HTMLElement | null) => {\n if (!el) return false;\n const tag = el.tagName;\n if (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') return true;\n if (el.isContentEditable) return true;\n return false;\n };\n if (isFormField(target) && (e.key === 'Home' || e.key === 'End')) return;\n if (isFormField(target) && (e.key === 'ArrowUp' || e.key === 'ArrowDown')) {\n if ((target as HTMLInputElement).tagName === 'INPUT' && (target as HTMLInputElement).type === 'number') return;\n }\n // Let arrow left/right navigate within text inputs instead of moving cells\n if (isFormField(target) && (e.key === 'ArrowLeft' || e.key === 'ArrowRight')) return;\n // Let Enter/Escape be handled by the input's own handlers first\n if (isFormField(target) && (e.key === 'Enter' || e.key === 'Escape')) return;\n if (editing && colType === 'select' && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) return;\n switch (e.key) {\n case 'Tab': {\n e.preventDefault();\n const forward = !e.shiftKey;\n if (forward) {\n if (grid._focusCol < maxCol) grid._focusCol += 1;\n else {\n if (typeof grid.commitActiveRowEdit === 'function') grid.commitActiveRowEdit();\n if (grid._focusRow < maxRow) {\n grid._focusRow += 1;\n grid._focusCol = 0;\n }\n }\n } else {\n if (grid._focusCol > 0) grid._focusCol -= 1;\n else if (grid._focusRow > 0) {\n if (typeof grid.commitActiveRowEdit === 'function' && grid._activeEditRows === grid._focusRow)\n grid.commitActiveRowEdit();\n grid._focusRow -= 1;\n grid._focusCol = maxCol;\n }\n }\n ensureCellVisible(grid);\n return;\n }\n case 'ArrowDown':\n if (editing && typeof grid.commitActiveRowEdit === 'function') grid.commitActiveRowEdit();\n grid._focusRow = Math.min(maxRow, grid._focusRow + 1);\n e.preventDefault();\n break;\n case 'ArrowUp':\n if (editing && typeof grid.commitActiveRowEdit === 'function') grid.commitActiveRowEdit();\n grid._focusRow = Math.max(0, grid._focusRow - 1);\n e.preventDefault();\n break;\n case 'ArrowRight': {\n // In RTL mode, ArrowRight moves toward the start (lower column index)\n const rtl = isRTL(grid);\n if (rtl) {\n grid._focusCol = Math.max(0, grid._focusCol - 1);\n } else {\n grid._focusCol = Math.min(maxCol, grid._focusCol + 1);\n }\n e.preventDefault();\n break;\n }\n case 'ArrowLeft': {\n // In RTL mode, ArrowLeft moves toward the end (higher column index)\n const rtl = isRTL(grid);\n if (rtl) {\n grid._focusCol = Math.min(maxCol, grid._focusCol + 1);\n } else {\n grid._focusCol = Math.max(0, grid._focusCol - 1);\n }\n e.preventDefault();\n break;\n }\n case 'Home':\n if (e.ctrlKey || e.metaKey) {\n // CTRL+Home: navigate to first row, first cell\n if (editing && typeof grid.commitActiveRowEdit === 'function') grid.commitActiveRowEdit();\n grid._focusRow = 0;\n grid._focusCol = 0;\n } else {\n // Home: navigate to first cell in current row\n grid._focusCol = 0;\n }\n e.preventDefault();\n ensureCellVisible(grid, { forceScrollLeft: true });\n return;\n case 'End':\n if (e.ctrlKey || e.metaKey) {\n // CTRL+End: navigate to last row, last cell\n if (editing && typeof grid.commitActiveRowEdit === 'function') grid.commitActiveRowEdit();\n grid._focusRow = maxRow;\n grid._focusCol = maxCol;\n } else {\n // End: navigate to last cell in current row\n grid._focusCol = maxCol;\n }\n e.preventDefault();\n ensureCellVisible(grid, { forceScrollRight: true });\n return;\n case 'PageDown':\n grid._focusRow = Math.min(maxRow, grid._focusRow + 20);\n e.preventDefault();\n break;\n case 'PageUp':\n grid._focusRow = Math.max(0, grid._focusRow - 20);\n e.preventDefault();\n break;\n // NOTE: Enter key is handled by EditingPlugin. If no plugin handles it,\n // we dispatch the unified cell-activate event for custom handling.\n case 'Enter': {\n const rowIndex = grid._focusRow;\n const colIndex = grid._focusCol;\n const column = grid._visibleColumns[colIndex];\n const row = grid._rows[rowIndex];\n const field = column?.field ?? '';\n const value = field && row ? (row as Record<string, unknown>)[field] : undefined;\n const cellEl = grid.querySelector(`[data-row=\"${rowIndex}\"][data-col=\"${colIndex}\"]`) as HTMLElement | undefined;\n\n const detail = {\n rowIndex,\n colIndex,\n column,\n field,\n value,\n row,\n cellEl,\n trigger: 'keyboard' as const,\n originalEvent: e,\n };\n\n // Emit unified cell-activate event\n const activateEvent = new CustomEvent('cell-activate', {\n cancelable: true,\n detail,\n });\n grid.dispatchEvent(activateEvent);\n\n // Also emit deprecated activate-cell for backwards compatibility\n const legacyEvent = new CustomEvent('activate-cell', {\n cancelable: true,\n detail: { row: rowIndex, col: colIndex },\n });\n grid.dispatchEvent(legacyEvent);\n\n // If either event was prevented, block further keyboard processing\n if (activateEvent.defaultPrevented || legacyEvent.defaultPrevented) {\n e.preventDefault();\n return;\n }\n // Otherwise allow normal keyboard processing\n break;\n }\n default:\n return;\n }\n ensureCellVisible(grid);\n}\n// #endregion\n\n// #region Cell Visibility\n/**\n * Options for ensureCellVisible to control scroll behavior.\n */\ninterface EnsureCellVisibleOptions {\n /** Force scroll to the leftmost position (for Home key) */\n forceScrollLeft?: boolean;\n /** Force scroll to the rightmost position (for End key) */\n forceScrollRight?: boolean;\n /** Force horizontal scroll even in edit mode (for Tab navigation) */\n forceHorizontalScroll?: boolean;\n}\n\n/**\n * Scroll the viewport (virtualized or static) so the focused cell's row is visible\n * and apply visual focus styling / tabindex management.\n */\nexport function ensureCellVisible(grid: GridHost, options?: EnsureCellVisibleOptions): void {\n if (grid._virtualization?.enabled) {\n const { rowHeight, container, viewportEl } = grid._virtualization;\n // container is the faux scrollbar element that handles actual scrolling\n // viewportEl is the visible area element that has the correct height\n const scrollEl = container as HTMLElement | undefined;\n const visibleHeight = viewportEl?.clientHeight ?? scrollEl?.clientHeight ?? 0;\n if (scrollEl && visibleHeight > 0) {\n const y = grid._focusRow * rowHeight;\n if (y < scrollEl.scrollTop) {\n scrollEl.scrollTop = y;\n } else if (y + rowHeight > scrollEl.scrollTop + visibleHeight) {\n scrollEl.scrollTop = y - visibleHeight + rowHeight;\n }\n }\n }\n // Skip refreshVirtualWindow when in edit mode to avoid wiping editors\n const isEditing = (grid._activeEditRows !== undefined && grid._activeEditRows !== -1) || !!grid._isGridEditMode;\n if (!isEditing) {\n grid.refreshVirtualWindow(false);\n }\n clearCellFocus(grid._bodyEl);\n // Clear previous aria-selected markers\n Array.from(grid._bodyEl.querySelectorAll('[aria-selected=\"true\"]')).forEach((el) => {\n el.setAttribute('aria-selected', 'false');\n });\n const rowIndex = grid._focusRow;\n const vStart = grid._virtualization.start ?? 0;\n const vEnd = grid._virtualization.end ?? grid._rows.length;\n if (rowIndex >= vStart && rowIndex < vEnd) {\n const rowEl = grid._bodyEl.querySelectorAll('.data-grid-row')[rowIndex - vStart] as HTMLElement | null;\n // Try exact column match first, then query by data-col, then fallback to first cell (for full-width group rows)\n let cell = rowEl?.children[grid._focusCol] as HTMLElement | undefined;\n if (!cell || !cell.classList?.contains('cell')) {\n cell = (rowEl?.querySelector(`.cell[data-col=\"${grid._focusCol}\"]`) ??\n rowEl?.querySelector('.cell[data-col]')) as HTMLElement | undefined;\n }\n if (cell) {\n cell.classList.add('cell-focus');\n cell.setAttribute('aria-selected', 'true');\n\n // Horizontal scroll: ensure focused cell is visible in the horizontal scroll area\n // The .tbw-scroll-area element handles horizontal scrolling\n // Skip horizontal scrolling when in edit mode to prevent scroll jumps when editors are created\n // Unless forceHorizontalScroll is set (e.g., for Tab navigation while editing)\n const scrollArea = grid.querySelector('.tbw-scroll-area') as HTMLElement | null;\n if (scrollArea && cell && (!isEditing || options?.forceHorizontalScroll)) {\n // Handle forced scroll for Home/End keys - always scroll to edge\n if (options?.forceScrollLeft) {\n scrollArea.scrollLeft = 0;\n } else if (options?.forceScrollRight) {\n scrollArea.scrollLeft = scrollArea.scrollWidth - scrollArea.clientWidth;\n } else {\n // Get scroll boundary offsets from plugins (e.g., pinned columns)\n // This allows plugins to report how much of the scroll area they obscure\n // and whether the focused cell should skip scrolling (e.g., pinned cells are always visible)\n const offsets = grid._getHorizontalScrollOffsets?.(rowEl ?? undefined, cell) ?? { left: 0, right: 0 };\n\n if (!offsets.skipScroll) {\n // Get cell position relative to the scroll area\n const cellRect = cell.getBoundingClientRect();\n const scrollAreaRect = scrollArea.getBoundingClientRect();\n // Calculate the cell's position relative to scroll area's visible region\n const cellLeft = cellRect.left - scrollAreaRect.left + scrollArea.scrollLeft;\n const cellRight = cellLeft + cellRect.width;\n // Adjust visible boundaries to account for plugin-reported offsets\n const visibleLeft = scrollArea.scrollLeft + offsets.left;\n const visibleRight = scrollArea.scrollLeft + scrollArea.clientWidth - offsets.right;\n // Scroll horizontally if needed\n if (cellLeft < visibleLeft) {\n scrollArea.scrollLeft = cellLeft - offsets.left;\n } else if (cellRight > visibleRight) {\n scrollArea.scrollLeft = cellRight - scrollArea.clientWidth + offsets.right;\n }\n }\n }\n }\n\n if (isEditing && cell.classList.contains('editing')) {\n // Editing cell: focus the editor input inside it\n const focusTarget = cell.querySelector(FOCUSABLE_EDITOR_SELECTOR) as HTMLElement | null;\n if (focusTarget && document.activeElement !== focusTarget) {\n try {\n focusTarget.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n }\n } else if (isEditing && !cell.contains(document.activeElement)) {\n // Active edit row but this cell isn't the editing cell — focus it\n // so Tab navigation within the row can attach editors\n if (!cell.hasAttribute('tabindex')) cell.setAttribute('tabindex', '-1');\n try {\n cell.focus({ preventScroll: true });\n } catch {\n /* empty */\n }\n } else if (!isEditing) {\n // NOT editing: keep focus on the grid element (tabindex=0) rather than\n // individual cells. In a virtualized grid, cells can be detached by\n // subsequent render cycles (e.g., SelectionPlugin's requestAfterRender\n // → RAF → row recycling). A detached focused cell causes activeElement\n // to revert to <body>, breaking keyboard navigation.\n // Visual focus is managed by the .cell-focus CSS class + data-has-focus.\n if (document.activeElement !== grid) {\n grid.focus({ preventScroll: true });\n }\n }\n }\n }\n}\n// #endregion\n","/**\n * Event Delegation Module\n *\n * Consolidates all delegated event handling for the grid.\n * Uses event delegation (single listener on container) rather than per-cell/per-row\n * listeners to minimize memory usage.\n *\n * This module provides:\n * - setupCellEventDelegation: Body-level handlers (mousedown, click, dblclick on cells/rows)\n * - setupRootEventDelegation: Root-level handlers (keydown, mousedown for plugins, drag tracking)\n *\n * Edit triggering is handled separately by the EditingPlugin via\n * onCellClick and onKeyDown hooks.\n */\n\nimport type { CellMouseEvent } from '../plugin/types';\nimport type { GridHost, InternalGrid } from '../types';\nimport { handleGridKeyDown } from './keyboard';\nimport { handleRowClick } from './rows';\nimport { clearCellFocus, getColIndexFromCell, getRowIndexFromCell } from './utils';\n\n// #region Utilities\n// Track drag state per grid instance (avoids polluting InternalGrid interface)\nconst dragState = new WeakMap<InternalGrid, boolean>();\n// #endregion\n\n// #region Cell Mouse Handlers\n/**\n * Handle delegated mousedown on cells.\n * Updates focus position for navigation.\n *\n * IMPORTANT: This must NOT call refreshVirtualWindow or any function that\n * re-renders DOM elements. Doing so would replace the element the user clicked on,\n * causing the subsequent click event to fire on a detached element and not bubble\n * to parent handlers (like handleRowClick).\n *\n * For mouse interactions, the cell is already visible (user clicked on it),\n * so we only need to update focus state without scrolling or re-rendering.\n */\nfunction handleCellMousedown(grid: InternalGrid, cell: HTMLElement): void {\n const rowIndex = getRowIndexFromCell(cell);\n const colIndex = getColIndexFromCell(cell);\n if (rowIndex < 0 || colIndex < 0) return;\n\n grid._focusRow = rowIndex;\n grid._focusCol = colIndex;\n\n // Update focus styling directly without triggering re-render.\n // ensureCellVisible() would call refreshVirtualWindow() which replaces DOM elements,\n // breaking the click event that follows this mousedown.\n clearCellFocus(grid._bodyEl);\n cell.classList.add('cell-focus');\n cell.setAttribute('aria-selected', 'true');\n\n // Ensure the grid element has DOM focus so keyboard events (like Ctrl+C for clipboard)\n // bubble through the grid's keydown listener. Without this, clicking on non-focusable\n // cells may leave focus elsewhere, making keyboard shortcuts unreachable.\n // Always call focus() — even when activeElement is inside the grid — because\n // browser default behaviors (e.g., shift+click text selection, cell re-pooling)\n // can move focus to document.body between mousedown and the next keydown.\n const gridEl = cell.closest('tbw-grid') as HTMLElement | null;\n if (gridEl && document.activeElement !== gridEl) {\n gridEl.focus({ preventScroll: true });\n }\n}\n// #endregion\n\n// #region Mouse Event Building\n/**\n * Build a CellMouseEvent from a native MouseEvent.\n * Extracts cell/row information from the event target.\n */\nfunction buildCellMouseEvent(\n grid: InternalGrid,\n renderRoot: HTMLElement,\n e: MouseEvent,\n type: 'mousedown' | 'mousemove' | 'mouseup',\n): CellMouseEvent {\n // For document-level events (mousemove/mouseup during drag), e.target won't be inside shadow DOM.\n // Use composedPath to find elements inside shadow roots, or fall back to elementFromPoint.\n let target: Element | null = null;\n\n // composedPath gives us the full path including shadow DOM elements\n const path = e.composedPath?.() as Element[] | undefined;\n if (path && path.length > 0) {\n target = path[0];\n } else {\n target = e.target as Element;\n }\n\n // If target is not inside our element (e.g., for document-level events),\n // use elementFromPoint to find the actual element under the mouse\n if (target && !renderRoot.contains(target)) {\n const elAtPoint = document.elementFromPoint(e.clientX, e.clientY);\n if (elAtPoint) {\n target = elAtPoint;\n }\n }\n\n // Cells have data-col and data-row attributes\n const cellEl = target?.closest?.('[data-col]') as HTMLElement | null;\n const rowEl = target?.closest?.('.data-grid-row') as HTMLElement | null;\n const headerEl = target?.closest?.('.header-row') as HTMLElement | null;\n\n let rowIndex: number | undefined;\n let colIndex: number | undefined;\n let row: unknown;\n let field: string | undefined;\n let value: unknown;\n let column: unknown;\n\n if (cellEl) {\n // Get indices from cell attributes\n rowIndex = parseInt(cellEl.getAttribute('data-row') ?? '-1', 10);\n colIndex = parseInt(cellEl.getAttribute('data-col') ?? '-1', 10);\n if (rowIndex >= 0 && colIndex >= 0) {\n row = grid._rows[rowIndex];\n // colIndex from data-col is a visible-column index (rendering uses _visibleColumns)\n column = grid._visibleColumns[colIndex];\n field = (column as { field?: string })?.field;\n value = row && field ? (row as Record<string, unknown>)[field] : undefined;\n }\n }\n\n return {\n type,\n row,\n rowIndex: rowIndex !== undefined && rowIndex >= 0 ? rowIndex : undefined,\n colIndex: colIndex !== undefined && colIndex >= 0 ? colIndex : undefined,\n field,\n value,\n column: column as CellMouseEvent['column'],\n originalEvent: e,\n cellElement: cellEl ?? undefined,\n rowElement: rowEl ?? undefined,\n isHeader: !!headerEl,\n cell:\n rowIndex !== undefined && colIndex !== undefined && rowIndex >= 0 && colIndex >= 0\n ? { row: rowIndex, col: colIndex }\n : undefined,\n };\n}\n// #endregion\n\n// #region Drag Tracking\n/**\n * Handle mousedown events and dispatch to plugin system.\n */\nfunction handleMouseDown(grid: InternalGrid, renderRoot: HTMLElement, e: MouseEvent): void {\n const event = buildCellMouseEvent(grid, renderRoot, e, 'mousedown');\n const handled = grid._dispatchCellMouseDown?.(event) ?? false;\n\n // If any plugin handled mousedown, start tracking for drag\n if (handled) {\n dragState.set(grid, true);\n }\n}\n\n/**\n * Handle mousemove events (only when dragging).\n */\nfunction handleMouseMove(grid: InternalGrid, renderRoot: HTMLElement, e: MouseEvent): void {\n if (!dragState.get(grid)) return;\n\n const event = buildCellMouseEvent(grid, renderRoot, e, 'mousemove');\n grid._dispatchCellMouseMove?.(event);\n}\n\n/**\n * Handle mouseup events.\n */\nfunction handleMouseUp(grid: InternalGrid, renderRoot: HTMLElement, e: MouseEvent): void {\n if (!dragState.get(grid)) return;\n\n const event = buildCellMouseEvent(grid, renderRoot, e, 'mouseup');\n grid._dispatchCellMouseUp?.(event);\n dragState.set(grid, false);\n}\n// #endregion\n\n// #region Setup Functions\n/**\n * Set up delegated event listeners on the grid body.\n * Consolidates all row/cell mouse event handling into a single set of listeners.\n * Call once during grid initialization.\n *\n * Benefits:\n * - 3 listeners total vs N*2 listeners (where N = pool size)\n * - Consistent event handling across all rows\n * - Automatic cleanup via AbortController signal\n *\n * @param grid - The grid instance\n * @param bodyEl - The .rows element containing all data rows\n * @param signal - AbortSignal for cleanup\n */\nexport function setupCellEventDelegation(grid: GridHost, bodyEl: HTMLElement, signal: AbortSignal): void {\n // Mousedown - update focus on any cell (not just editable)\n bodyEl.addEventListener(\n 'mousedown',\n (e) => {\n const cell = (e.target as HTMLElement).closest('.cell[data-col]') as HTMLElement | null;\n if (!cell) return;\n\n // Skip if clicking inside an editing cell (let the editor handle it)\n if (cell.classList.contains('editing')) return;\n\n // Skip preventDefault when clicking a draggable element (or its children).\n // Native HTML5 drag-and-drop requires mousedown to NOT be prevented;\n // otherwise the browser never fires dragstart.\n const target = e.target as HTMLElement;\n const isDraggable = target.draggable || target.closest('[draggable=\"true\"]');\n\n // Prevent the browser from managing focus for grid cells.\n // Without this, the browser can steal focus (e.g., shift+click extends\n // a native text selection, moving activeElement to document.body).\n // We manage focus explicitly via handleCellMousedown → gridEl.focus().\n if (!isDraggable) {\n e.preventDefault();\n }\n\n handleCellMousedown(grid, cell);\n },\n { signal },\n );\n\n // Click - handle row/cell click interactions\n bodyEl.addEventListener(\n 'click',\n (e) => {\n const rowEl = (e.target as HTMLElement).closest('.data-grid-row') as HTMLElement | null;\n if (rowEl) handleRowClick(grid, e as MouseEvent, rowEl);\n\n // After click handling: keep focus on the grid element (not individual cells).\n // In a virtualized grid, cells can be detached by subsequent render cycles\n // (e.g., SelectionPlugin's requestAfterRender → RAF render → row recycling).\n // A detached focused cell causes activeElement to revert to <body>, breaking\n // keyboard shortcuts like Ctrl+C. The grid element (tabindex=0) is stable\n // and receives all keyboard events via bubble phase.\n // Skip if an editor is active — editors manage their own focus.\n if (!document.activeElement?.closest('.cell.editing')) {\n const gridEl = (e.target as HTMLElement).closest('tbw-grid') as HTMLElement | null;\n if (gridEl) gridEl.focus({ preventScroll: true });\n }\n },\n { signal },\n );\n\n // Dblclick - same handler as click (edit triggering handled by EditingPlugin)\n bodyEl.addEventListener(\n 'dblclick',\n (e) => {\n const rowEl = (e.target as HTMLElement).closest('.data-grid-row') as HTMLElement | null;\n if (rowEl) handleRowClick(grid, e as MouseEvent, rowEl);\n },\n { signal },\n );\n}\n\n/**\n * Set up root-level and document-level event listeners.\n * These are added once per grid lifetime (not re-attached on DOM recreation).\n *\n * Includes:\n * - keydown: Keyboard navigation (arrows, Enter, Escape)\n * - mousedown: Plugin dispatch for cell interactions\n * - mousemove/mouseup: Global drag tracking\n *\n * @param grid - The grid instance\n * @param gridElement - The grid element (for keydown)\n * @param renderRoot - The render root element (for mousedown)\n * @param signal - AbortSignal for cleanup\n */\nexport function setupRootEventDelegation(\n grid: GridHost,\n gridElement: HTMLElement,\n renderRoot: HTMLElement,\n signal: AbortSignal,\n): void {\n // Element-level keydown handler for keyboard navigation\n gridElement.addEventListener('keydown', (e) => handleGridKeyDown(grid, e), { signal });\n\n // Central mouse event handling for plugins\n renderRoot.addEventListener('mousedown', (e) => handleMouseDown(grid, renderRoot, e as MouseEvent), { signal });\n\n // Track global mousemove/mouseup for drag operations (column resize, selection, etc.)\n document.addEventListener('mousemove', (e: MouseEvent) => handleMouseMove(grid, renderRoot, e), { signal });\n document.addEventListener('mouseup', (e: MouseEvent) => handleMouseUp(grid, renderRoot, e), { signal });\n}\n// #endregion\n","/**\n * Hook point for the feature registry to connect to the grid core\n * without introducing circular imports or adding registry code to the core bundle.\n *\n * - grid.ts reads `resolveFeatures` to convert `gridConfig.features` into plugins.\n * - registry.ts calls `setFeatureResolver()` at load time to provide the implementation.\n *\n * If no feature modules are imported, `resolveFeatures` stays undefined,\n * and the grid ignores `gridConfig.features` (zero cost).\n *\n * @internal\n */\n\nimport type { GridPlugin } from '../types';\n\n/** Feature-to-plugin resolver function type. */\nexport type FeatureResolverFn = (features: Record<string, unknown>) => GridPlugin[];\n\n/** Resolver set by the feature registry when loaded. undefined until first feature import. */\nexport let resolveFeatures: FeatureResolverFn | undefined;\n\n/** Called by `features/registry.ts` at module evaluation time. @internal */\nexport function setFeatureResolver(fn: FeatureResolverFn): void {\n resolveFeatures = fn;\n}\n","/**\n * FocusManager — encapsulates focus/navigation state and external focus\n * container tracking that were previously inline in the DataGridElement class.\n *\n * Owns:\n * - External focus container registry (register/unregister/containsFocus)\n * - Focus cell navigation (focusCell, focusedCell getter)\n * - Scroll-to-row logic (scrollToRow, scrollToRowById)\n *\n * Takes the grid reference directly (tightly coupled — this manager\n * can never live outside the grid).\n */\nimport type { GridHost, ScrollToRowOptions } from '../types';\nimport { ensureCellVisible } from './keyboard';\n\n// #region FocusManager\n\nexport class FocusManager<T = any> {\n readonly #grid: GridHost<T>;\n\n // External focus containers — overlay panels (datepickers, dropdowns)\n // that render at <body> level but should be treated as \"inside\" the grid.\n #externalFocusContainers = new Set<Element>();\n #externalFocusCleanups = new Map<Element, () => void>();\n\n constructor(grid: GridHost<T>) {\n this.#grid = grid;\n }\n\n // #region Focus & Navigation\n\n /**\n * Move focus to a specific cell.\n * Accepts a column index or field name.\n */\n focusCell(rowIndex: number, column: number | string): void {\n const grid = this.#grid;\n const maxRow = grid._rows.length - 1;\n if (maxRow < 0) return;\n\n let colIdx: number;\n if (typeof column === 'string') {\n colIdx = grid._visibleColumns.findIndex((c) => c.field === column);\n if (colIdx < 0) return;\n } else {\n colIdx = column;\n }\n\n const maxCol = grid._visibleColumns.length - 1;\n if (maxCol < 0) return;\n\n grid._focusRow = Math.max(0, Math.min(rowIndex, maxRow));\n grid._focusCol = Math.max(0, Math.min(colIdx, maxCol));\n ensureCellVisible(grid);\n }\n\n /**\n * The currently focused cell position, or `null` if no rows are loaded.\n */\n get focusedCell(): { rowIndex: number; colIndex: number; field: string } | null {\n const grid = this.#grid;\n if (grid._rows.length === 0 || grid._visibleColumns.length === 0) return null;\n const col = grid._visibleColumns[grid._focusCol];\n return {\n rowIndex: grid._focusRow,\n colIndex: grid._focusCol,\n field: col?.field ?? '',\n };\n }\n\n /**\n * Scroll the viewport so a row is visible.\n */\n scrollToRow(rowIndex: number, options?: ScrollToRowOptions): void {\n const virt = this.#grid._virtualization;\n if (!virt.enabled) return;\n\n const scrollEl = virt.container as HTMLElement | undefined;\n if (!scrollEl) return;\n\n const totalRows = this.#grid._rows.length;\n if (totalRows === 0) return;\n\n const idx = Math.max(0, Math.min(rowIndex, totalRows - 1));\n const align = options?.align ?? 'nearest';\n const behavior = options?.behavior ?? 'instant';\n\n // Calculate row offset and height, accounting for variable row heights\n let rowTop: number;\n let rowH: number;\n const pc = virt.positionCache;\n if (virt.variableHeights && pc && pc.length > idx) {\n rowTop = pc[idx].offset;\n rowH = pc[idx].height;\n } else {\n rowTop = idx * virt.rowHeight;\n rowH = virt.rowHeight;\n }\n\n const viewportH = virt.viewportEl?.clientHeight ?? scrollEl.clientHeight ?? 0;\n if (viewportH <= 0) return;\n\n const currentTop = scrollEl.scrollTop;\n const rowBottom = rowTop + rowH;\n const viewBottom = currentTop + viewportH;\n\n let target: number;\n switch (align) {\n case 'start':\n target = rowTop;\n break;\n case 'center':\n target = rowTop - viewportH / 2 + rowH / 2;\n break;\n case 'end':\n target = rowBottom - viewportH;\n break;\n case 'nearest':\n default:\n // Already fully visible — no scroll needed\n if (rowTop >= currentTop && rowBottom <= viewBottom) return;\n // Scroll up or down to bring row into view (minimum movement)\n target = rowTop < currentTop ? rowTop : rowBottom - viewportH;\n break;\n }\n\n target = Math.max(0, target);\n\n if (behavior === 'smooth') {\n scrollEl.scrollTo({ top: target, behavior: 'smooth' });\n } else {\n scrollEl.scrollTop = target;\n }\n }\n\n /**\n * Scroll the viewport so a row is visible, identified by its unique ID.\n */\n scrollToRowById(rowId: string, options?: ScrollToRowOptions): void {\n const entry = this.#grid._getRowEntry(rowId);\n if (!entry) return;\n this.scrollToRow(entry.index, options);\n }\n\n // #endregion\n\n // #region External Focus Containers\n\n /**\n * Register an external DOM element as a logical focus container of this grid.\n * Focus moving into a registered container is treated as if it stayed inside the grid.\n */\n registerExternalFocusContainer(el: Element): void {\n if (this.#externalFocusContainers.has(el)) return;\n this.#externalFocusContainers.add(el);\n\n const ac = new AbortController();\n const signal = ac.signal;\n const gridEl = this.#grid;\n\n el.addEventListener(\n 'focusin',\n () => {\n gridEl.dataset.hasFocus = '';\n },\n { signal },\n );\n\n el.addEventListener(\n 'focusout',\n (e) => {\n const newFocus = (e as FocusEvent).relatedTarget as Node | null;\n if (!newFocus || !this.containsFocus(newFocus)) {\n delete gridEl.dataset.hasFocus;\n }\n },\n { signal },\n );\n\n this.#externalFocusCleanups.set(el, () => ac.abort());\n }\n\n /**\n * Unregister a previously registered external focus container.\n */\n unregisterExternalFocusContainer(el: Element): void {\n this.#externalFocusContainers.delete(el);\n const cleanup = this.#externalFocusCleanups.get(el);\n if (cleanup) {\n cleanup();\n this.#externalFocusCleanups.delete(el);\n }\n }\n\n /**\n * Check whether focus is logically inside this grid.\n * Returns `true` when the node is inside the grid's DOM or any external container.\n */\n containsFocus(node?: Node | null): boolean {\n const target = node ?? document.activeElement;\n if (!target) return false;\n if (this.#grid.contains(target)) return true;\n return this.isInExternalFocusContainer(target);\n }\n\n /**\n * Check whether a node is inside any registered external focus container.\n */\n isInExternalFocusContainer(node: Node): boolean {\n for (const container of this.#externalFocusContainers) {\n if (container.contains(node)) return true;\n }\n return false;\n }\n\n // #endregion\n\n // #region Cleanup\n\n /**\n * Clean up all external focus container listeners.\n * Called when the grid disconnects.\n */\n destroy(): void {\n for (const cleanup of this.#externalFocusCleanups.values()) {\n cleanup();\n }\n this.#externalFocusCleanups.clear();\n this.#externalFocusContainers.clear();\n }\n\n // #endregion\n}\n\n// #endregion\n","/**\n * Idle Scheduler - Defer non-critical work to browser idle periods.\n *\n * Uses requestIdleCallback where available, with fallback to setTimeout.\n * This allows the main thread to remain responsive during startup.\n */\n\n/**\n * Check if requestIdleCallback is available (not in Safari < 17.4).\n */\nconst hasIdleCallback = typeof requestIdleCallback === 'function';\n\n/**\n * IdleDeadline-compatible interface for fallback.\n */\ninterface IdleDeadlineLike {\n didTimeout: boolean;\n timeRemaining(): number;\n}\n\n/**\n * Schedule work to run during browser idle time.\n * Falls back to setTimeout(0) if requestIdleCallback is not available.\n *\n * @param callback - Work to run when idle\n * @param options - Optional timeout configuration\n * @returns Handle for cancellation\n */\nexport function scheduleIdle(callback: (deadline: IdleDeadlineLike) => void, options?: { timeout?: number }): number {\n if (hasIdleCallback) {\n return requestIdleCallback(callback, options);\n }\n\n // Fallback for Safari (before 17.4) and older browsers\n return window.setTimeout(() => {\n const start = Date.now();\n callback({\n didTimeout: false,\n timeRemaining: () => Math.max(0, 50 - (Date.now() - start)),\n });\n }, 1);\n}\n\n/**\n * Cancel a scheduled idle callback.\n */\nexport function cancelIdle(handle: number): void {\n if (hasIdleCallback) {\n cancelIdleCallback(handle);\n } else {\n clearTimeout(handle);\n }\n}\n","/**\n * Loading State Module\n *\n * Handles loading overlays, row loading, and cell loading states.\n * Provides DOM manipulation helpers for loading indicators.\n *\n * @module internal/loading\n */\n\nimport type { GridConfig, LoadingContext } from '../types';\n\n/**\n * Create the default spinner element.\n * @param size - 'large' for grid overlay, 'small' for row/cell\n */\nexport function createDefaultSpinner(size: 'large' | 'small'): HTMLElement {\n const spinner = document.createElement('div');\n spinner.className = `tbw-spinner tbw-spinner--${size}`;\n spinner.setAttribute('role', 'progressbar');\n spinner.setAttribute('aria-label', 'Loading');\n return spinner;\n}\n\n/**\n * Create loading content using custom renderer or default spinner.\n * @param size - 'large' for grid overlay, 'small' for row/cell\n * @param renderer - Optional custom loading renderer from config\n */\nexport function createLoadingContent(size: 'large' | 'small', renderer?: GridConfig['loadingRenderer']): HTMLElement {\n if (renderer) {\n const context: LoadingContext = { size };\n const result = renderer(context);\n if (typeof result === 'string') {\n const wrapper = document.createElement('div');\n wrapper.innerHTML = result;\n return wrapper;\n }\n return result;\n }\n\n return createDefaultSpinner(size);\n}\n\n/**\n * Create or update the loading overlay element.\n * @param renderer - Optional custom loading renderer from config\n */\nexport function createLoadingOverlay(renderer?: GridConfig['loadingRenderer']): HTMLElement {\n const overlay = document.createElement('div');\n overlay.className = 'tbw-loading-overlay';\n overlay.setAttribute('role', 'status');\n overlay.setAttribute('aria-live', 'polite');\n overlay.appendChild(createLoadingContent('large', renderer));\n return overlay;\n}\n\n/**\n * Show the loading overlay on the grid root element.\n * @param gridRoot - The .tbw-grid-root element\n * @param overlayEl - The overlay element (will be cached by caller)\n */\nexport function showLoadingOverlay(gridRoot: Element, overlayEl: HTMLElement): void {\n gridRoot.appendChild(overlayEl);\n}\n\n/**\n * Hide the loading overlay.\n * @param overlayEl - The overlay element to remove\n */\nexport function hideLoadingOverlay(overlayEl: HTMLElement | undefined): void {\n overlayEl?.remove();\n}\n\n/**\n * Update a row element's loading state.\n * Uses real DOM elements instead of ::before/::after pseudo-elements\n * because the selection plugin already uses ::after on rows for border styling.\n * @param rowEl - The row element\n * @param loading - Whether the row is loading\n */\nexport function setRowLoadingState(rowEl: HTMLElement, loading: boolean): void {\n if (loading) {\n rowEl.classList.add('tbw-row-loading');\n rowEl.setAttribute('aria-busy', 'true');\n\n // Create overlay + spinner DOM elements if not already present\n if (!rowEl.querySelector('.tbw-row-loading-overlay')) {\n const overlay = document.createElement('div');\n overlay.className = 'tbw-row-loading-overlay';\n overlay.setAttribute('aria-hidden', 'true');\n\n const spinner = document.createElement('div');\n spinner.className = 'tbw-row-loading-spinner';\n overlay.appendChild(spinner);\n\n rowEl.appendChild(overlay);\n }\n } else {\n rowEl.classList.remove('tbw-row-loading');\n rowEl.removeAttribute('aria-busy');\n\n // Remove overlay + spinner DOM elements\n rowEl.querySelector('.tbw-row-loading-overlay')?.remove();\n }\n}\n\n/**\n * Update a cell element's loading state.\n * @param cellEl - The cell element\n * @param loading - Whether the cell is loading\n */\nexport function setCellLoadingState(cellEl: HTMLElement, loading: boolean): void {\n if (loading) {\n cellEl.classList.add('tbw-cell-loading');\n cellEl.setAttribute('aria-busy', 'true');\n } else {\n cellEl.classList.remove('tbw-cell-loading');\n cellEl.removeAttribute('aria-busy');\n }\n}\n","import type { GridHost, ResizeController } from '../types';\n\nexport function createResizeController(grid: GridHost): ResizeController {\n let resizeState: { startX: number; colIndex: number; startWidth: number } | null = null;\n let pendingRaf: number | null = null;\n let prevCursor: string | null = null;\n let prevUserSelect: string | null = null;\n const onMove = (e: MouseEvent) => {\n if (!resizeState) return;\n const delta = e.clientX - resizeState.startX;\n const width = Math.max(40, resizeState.startWidth + delta);\n const col = grid._visibleColumns[resizeState.colIndex];\n col.width = width;\n col.__userResized = true;\n col.__renderedWidth = width;\n if (pendingRaf == null) {\n pendingRaf = requestAnimationFrame(() => {\n pendingRaf = null;\n grid.updateTemplate?.();\n });\n }\n grid.dispatchEvent(new CustomEvent('column-resize', { detail: { field: col.field, width } }));\n };\n let justFinishedResize = false;\n const onUp = () => {\n const hadResize = resizeState !== null;\n // Set flag to suppress click events that fire immediately after mouseup\n if (hadResize) {\n justFinishedResize = true;\n requestAnimationFrame(() => {\n justFinishedResize = false;\n });\n }\n window.removeEventListener('mousemove', onMove);\n window.removeEventListener('mouseup', onUp);\n if (prevCursor !== null) {\n document.documentElement.style.cursor = prevCursor;\n prevCursor = null;\n }\n if (prevUserSelect !== null) {\n document.body.style.userSelect = prevUserSelect;\n prevUserSelect = null;\n }\n resizeState = null;\n // Trigger state change after resize completes\n if (hadResize && grid.requestStateChange) {\n grid.requestStateChange();\n }\n };\n\n /**\n * Freeze all flexible (non-explicitly-sized) columns to their current rendered\n * pixel widths. This prevents CSS Grid `fr` redistribution from shifting\n * neighboring columns while the user drags a resize handle.\n */\n function freezeFlexibleColumns(colIndex: number, headerRow: HTMLElement): void {\n const cells = headerRow.querySelectorAll<HTMLElement>('.cell');\n for (let i = 0; i < grid._visibleColumns.length; i++) {\n if (i === colIndex) continue;\n const col = grid._visibleColumns[i];\n // Only freeze columns that are currently flexible (no explicit width)\n if (col.width == null && !col.__userResized) {\n const cellEl = cells[i];\n const rendered = cellEl?.getBoundingClientRect().width;\n if (rendered) {\n col.width = Math.round(rendered);\n col.__userResized = true;\n col.__renderedWidth = col.width;\n }\n }\n }\n }\n\n return {\n get isResizing() {\n return resizeState !== null || justFinishedResize;\n },\n start(e, colIndex, cell) {\n e.preventDefault();\n\n // Freeze flexible columns before resizing so they hold their current width\n const headerRow = grid._headerRowEl ?? grid.findHeaderRow?.();\n if (headerRow) freezeFlexibleColumns(colIndex, headerRow);\n\n // Use the column's configured/rendered width, not the cell's bounding rect.\n // The bounding rect can be incorrect if CSS grid-column spanning is in effect\n // (e.g., when previous columns are display:none and this cell spans multiple tracks).\n const col = grid._visibleColumns[colIndex];\n // Only use numeric widths; string widths (e.g., \"100px\", \"20%\") fall back to bounding rect\n const colWidth = typeof col?.width === 'number' ? col.width : undefined;\n const startWidth = col?.__renderedWidth ?? colWidth ?? cell.getBoundingClientRect().width;\n resizeState = { startX: e.clientX, colIndex, startWidth };\n window.addEventListener('mousemove', onMove);\n window.addEventListener('mouseup', onUp);\n if (prevCursor === null) prevCursor = document.documentElement.style.cursor;\n document.documentElement.style.cursor = 'e-resize';\n if (prevUserSelect === null) prevUserSelect = document.body.style.userSelect;\n document.body.style.userSelect = 'none';\n },\n resetColumn(colIndex) {\n const col = grid._visibleColumns[colIndex];\n if (!col) return;\n\n // Reset to original configured width (or undefined for auto-sizing)\n col.__userResized = false;\n col.__renderedWidth = undefined;\n col.width = col.__originalWidth;\n\n grid.updateTemplate?.();\n grid.requestStateChange?.();\n grid.dispatchEvent(new CustomEvent('column-resize-reset', { detail: { field: col.field, width: col.width } }));\n },\n dispose() {\n onUp();\n },\n };\n}\n","/**\n * Row Animation Module\n *\n * Provides row-level animation utilities for the grid.\n * Animations are CSS-based and triggered via data attributes.\n *\n * Supported animations:\n * - `change`: Flash highlight for modified rows (e.g., after editing)\n * - `insert`: Slide-in animation for newly added rows\n * - `remove`: Fade-out animation for rows being removed\n *\n * @module internal/row-animation\n */\n\nimport type { InternalGrid, RowAnimationType } from '../types';\n\n// #region Constants\n\n/**\n * Data attribute used to trigger row animations via CSS.\n */\nconst ANIMATION_ATTR = 'data-animating';\n\n/**\n * Map of animation types to their CSS custom property duration names.\n */\nconst DURATION_PROPS: Record<RowAnimationType, string> = {\n change: '--tbw-row-change-duration',\n insert: '--tbw-row-insert-duration',\n remove: '--tbw-row-remove-duration',\n};\n\n/**\n * Default animation durations in milliseconds.\n */\nconst DEFAULT_DURATIONS: Record<RowAnimationType, number> = {\n change: 500,\n insert: 300,\n remove: 200,\n};\n// #endregion\n\n// #region Internal Helpers\n/**\n * Parse a CSS duration string (e.g., \"500ms\", \"0.5s\") to milliseconds.\n */\nfunction parseDuration(value: string): number {\n const trimmed = value.trim().toLowerCase();\n if (trimmed.endsWith('ms')) {\n return parseFloat(trimmed);\n }\n if (trimmed.endsWith('s')) {\n return parseFloat(trimmed) * 1000;\n }\n return parseFloat(trimmed);\n}\n\n/**\n * Get the animation duration for a row element.\n * Reads from CSS custom property or falls back to default.\n */\nfunction getAnimationDuration(rowEl: HTMLElement, animationType: RowAnimationType): number {\n const prop = DURATION_PROPS[animationType];\n const computed = getComputedStyle(rowEl).getPropertyValue(prop);\n if (computed) {\n const parsed = parseDuration(computed);\n if (!isNaN(parsed) && parsed > 0) {\n return parsed;\n }\n }\n return DEFAULT_DURATIONS[animationType];\n}\n// #endregion\n\n// #region Public API\n/**\n * Animate a single row element.\n *\n * @param rowEl - The row DOM element to animate\n * @param animationType - The type of animation to apply\n * @param onComplete - Optional callback when animation completes\n */\nexport function animateRowElement(rowEl: HTMLElement, animationType: RowAnimationType, onComplete?: () => void): void {\n // Remove any existing animation first (allows re-triggering)\n rowEl.removeAttribute(ANIMATION_ATTR);\n\n // Force a reflow to restart the animation\n void rowEl.offsetWidth;\n\n // Apply the animation\n rowEl.setAttribute(ANIMATION_ATTR, animationType);\n\n // Get duration and schedule cleanup\n const duration = getAnimationDuration(rowEl, animationType);\n\n setTimeout(() => {\n // For 'remove' animations, skip removing the attribute since the element\n // will be destroyed by the onComplete callback. This prevents a visual\n // flash where the element snaps back to its original state.\n if (animationType !== 'remove') {\n rowEl.removeAttribute(ANIMATION_ATTR);\n }\n onComplete?.();\n }, duration);\n}\n\n/**\n * Animate a row by its data index.\n *\n * @param grid - The grid instance\n * @param rowIndex - The data row index (not DOM position)\n * @param animationType - The type of animation to apply\n * @returns Promise resolving to `true` if the row was found and animated, `false` otherwise\n */\nexport function animateRow<T>(\n grid: InternalGrid<T>,\n rowIndex: number,\n animationType: RowAnimationType,\n): Promise<boolean> {\n // Guard against invalid indices\n if (rowIndex < 0) {\n return Promise.resolve(false);\n }\n\n const rowEl = grid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) {\n // Row is virtualized out of view - nothing to animate\n return Promise.resolve(false);\n }\n\n return new Promise((resolve) => {\n animateRowElement(rowEl, animationType, () => resolve(true));\n });\n}\n\n/**\n * Animate multiple rows by their data indices.\n *\n * @param grid - The grid instance\n * @param rowIndices - Array of data row indices to animate\n * @param animationType - The type of animation to apply\n * @returns Promise resolving to the number of rows actually animated (visible in viewport)\n */\nexport function animateRows<T>(\n grid: InternalGrid<T>,\n rowIndices: number[],\n animationType: RowAnimationType,\n): Promise<number> {\n return Promise.all(rowIndices.map((idx) => animateRow(grid, idx, animationType))).then(\n (results) => results.filter(Boolean).length,\n );\n}\n\n/**\n * Animate a row by its ID.\n *\n * @param grid - The grid instance\n * @param rowId - The row ID (requires getRowId to be configured)\n * @param animationType - The type of animation to apply\n * @returns Promise resolving to `true` if the row was found and animated, `false` otherwise\n */\nexport function animateRowById<T>(\n grid: InternalGrid<T>,\n rowId: string,\n animationType: RowAnimationType,\n): Promise<boolean> {\n // Find row index by searching _rows\n const rows = grid._rows ?? [];\n const getRowId = grid.getRowId;\n if (!getRowId) {\n return Promise.resolve(false);\n }\n\n const rowIndex = rows.findIndex((row) => {\n if (row == null) return false;\n try {\n return getRowId(row) === rowId;\n } catch {\n return false;\n }\n });\n if (rowIndex < 0) {\n return Promise.resolve(false);\n }\n return animateRow(grid, rowIndex, animationType);\n}\n// #endregion\n","/**\n * RowManager — encapsulates row CRUD operations that were previously\n * inline in the DataGridElement class.\n *\n * Owns:\n * - Row ID resolution (tryResolveRowId, resolveRowIdOrThrow)\n * - Row lookup (getRow, getRowEntry)\n * - Row mutation (updateRow, updateRows, insertRow, removeRow)\n *\n * Takes the grid reference directly (tightly coupled — this manager\n * can never live outside the grid).\n */\nimport type { CellChangeDetail, GridHost, RowTransaction, TransactionResult, UpdateSource } from '../types';\nimport { MISSING_ROW_ID, ROW_NOT_FOUND, throwDiagnostic } from './diagnostics';\nimport { RenderPhase } from './render-scheduler';\nimport { animateRow } from './row-animation';\nimport { invalidateCellCache } from './rows';\n\n// #region Standalone Row ID Helpers\n\n/**\n * Try to resolve the ID for a row using a configured getRowId or fallback.\n * Returns undefined if no ID can be determined (non-throwing).\n *\n * Exported so grid.ts can use it in `#rebuildRowIdMap` without going\n * through the RowManager instance.\n */\nexport function tryResolveRowId<T>(row: T, getRowId?: (row: T) => string): string | undefined {\n if (getRowId) {\n return getRowId(row);\n }\n\n // Fallback: common ID fields\n const r = row as Record<string, unknown>;\n if ('id' in r && r.id != null) return String(r.id);\n if ('_id' in r && r._id != null) return String(r._id);\n\n return undefined;\n}\n\n/**\n * Resolve the ID for a row, throwing if not found.\n * Exported so grid.ts `getRowId()` can call it without the RowManager.\n */\nexport function resolveRowIdOrThrow<T>(row: T, gridId: string, getRowId?: (row: T) => string): string {\n const id = tryResolveRowId(row, getRowId);\n if (id === undefined) {\n throwDiagnostic(\n MISSING_ROW_ID,\n 'Cannot determine row ID. ' + 'Configure getRowId in gridConfig or ensure rows have an \"id\" property.',\n gridId,\n );\n }\n return id;\n}\n\n// #endregion\n\n// #region RowManager\n\nexport class RowManager<T = any> {\n readonly #grid: GridHost<T>;\n\n constructor(grid: GridHost<T>) {\n this.#grid = grid;\n }\n\n // --- Row ID resolution ---\n\n resolveRowId(row: T): string {\n return resolveRowIdOrThrow(row, this.#grid.id, this.#grid.effectiveConfig?.getRowId);\n }\n\n // --- Row lookup ---\n\n getRow(id: string): T | undefined {\n return this.#grid._getRowEntry(id)?.row;\n }\n\n getRowEntry(id: string): { row: T; index: number } | undefined {\n return this.#grid._getRowEntry(id);\n }\n\n // --- Row updates ---\n\n updateRow(id: string, changes: Partial<T>, source: UpdateSource = 'api'): void {\n const grid = this.#grid;\n const entry = grid._getRowEntry(id);\n if (!entry) {\n throwDiagnostic(\n ROW_NOT_FOUND,\n `Row with ID \"${id}\" not found. ` + `Ensure the row exists and getRowId is correctly configured.`,\n grid.id,\n );\n }\n\n const { row, index } = entry;\n const changedFields: Array<{ field: string; oldValue: unknown; newValue: unknown }> = [];\n\n // Compute changes and apply in-place\n for (const [field, newValue] of Object.entries(changes)) {\n const oldValue = (row as Record<string, unknown>)[field];\n if (oldValue !== newValue) {\n changedFields.push({ field, oldValue, newValue });\n (row as Record<string, unknown>)[field] = newValue;\n }\n }\n\n // Emit cell-change for each changed field\n for (const { field, oldValue, newValue } of changedFields) {\n grid.dispatchEvent(\n new CustomEvent('cell-change', {\n detail: {\n row,\n rowId: id,\n rowIndex: index,\n field,\n oldValue,\n newValue,\n changes,\n source,\n } as CellChangeDetail<T>,\n bubbles: true,\n composed: true,\n }),\n );\n }\n\n // Schedule re-render if anything changed.\n // Use VIRTUALIZATION (not ROWS) so the visible cells are re-rendered\n // without rebuilding the row model. A ROWS-phase rebuild re-applies\n // sort/filter from #rows, which moves rows inserted via insertRow()\n // to their sorted position — appearing as \"ghost\" duplicates.\n // Since data was already mutated in-place, fastPatchRow will pick up\n // the new values from the row object directly.\n if (changedFields.length > 0) {\n invalidateCellCache(grid);\n grid._requestSchedulerPhase(RenderPhase.VIRTUALIZATION, 'updateRow');\n grid._emitDataChange();\n }\n }\n\n updateRows(updates: Array<{ id: string; changes: Partial<T> }>, source: UpdateSource = 'api'): void {\n const grid = this.#grid;\n let anyChanged = false;\n\n for (const { id, changes } of updates) {\n const entry = grid._getRowEntry(id);\n if (!entry) {\n throwDiagnostic(\n ROW_NOT_FOUND,\n `Row with ID \"${id}\" not found. ` + `Ensure the row exists and getRowId is correctly configured.`,\n grid.id,\n );\n }\n\n const { row, index } = entry;\n\n // Compute changes and apply in-place\n for (const [field, newValue] of Object.entries(changes)) {\n const oldValue = (row as Record<string, unknown>)[field];\n if (oldValue !== newValue) {\n anyChanged = true;\n (row as Record<string, unknown>)[field] = newValue;\n\n // Emit cell-change for each changed field\n grid.dispatchEvent(\n new CustomEvent('cell-change', {\n detail: {\n row,\n rowId: id,\n rowIndex: index,\n field,\n oldValue,\n newValue,\n changes,\n source,\n } as CellChangeDetail<T>,\n bubbles: true,\n composed: true,\n }),\n );\n }\n }\n }\n\n // Schedule single re-render for all changes.\n // Use VIRTUALIZATION (not ROWS) — see updateRow for rationale.\n if (anyChanged) {\n invalidateCellCache(grid);\n grid._requestSchedulerPhase(RenderPhase.VIRTUALIZATION, 'updateRows');\n grid._emitDataChange();\n }\n }\n\n // --- Row mutation ---\n\n async insertRow(index: number, row: T, animate = true): Promise<void> {\n const grid = this.#grid;\n\n // Clamp index to valid range\n const idx = Math.max(0, Math.min(index, grid._rows.length));\n\n // Add to source data (position irrelevant — pipeline will re-sort later)\n grid.sourceRows = [...grid.sourceRows, row];\n\n // Insert into processed view at the exact visible position\n const newRows = [...grid._rows];\n newRows.splice(idx, 0, row);\n grid._rows = newRows;\n\n // Keep __originalOrder in sync so \"clear sort\" includes the new row\n if (grid._sortState) {\n grid.__originalOrder = [...grid.__originalOrder, row];\n }\n\n // Refresh caches and trigger immediate re-render\n invalidateCellCache(grid);\n grid._rebuildRowIdMap();\n grid.__rowRenderEpoch++;\n for (const r of grid._rowPool) r.__epoch = -1;\n grid.refreshVirtualWindow(true);\n\n // Notify plugins about the inserted row (e.g., editing dirty tracking)\n grid._emitPluginEvent('row-inserted', { row, index: idx });\n\n grid._emitDataChange();\n\n if (animate) {\n await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()));\n await animateRow(grid, idx, 'insert');\n }\n }\n\n async removeRow(index: number, animate = true): Promise<T | undefined> {\n const grid = this.#grid;\n const row = grid._rows[index];\n if (!row) return undefined;\n\n if (animate) {\n await animateRow(grid, index, 'remove');\n }\n\n // Find current position by reference (may have shifted during animation)\n const currentIdx = grid._rows.indexOf(row);\n if (currentIdx < 0) return row; // Already removed by something else\n\n // Remove from processed view\n const newRows = [...grid._rows];\n newRows.splice(currentIdx, 1);\n grid._rows = newRows;\n\n // Remove from source data\n const srcIdx = grid.sourceRows.indexOf(row);\n if (srcIdx >= 0) {\n const newSource = [...grid.sourceRows];\n newSource.splice(srcIdx, 1);\n grid.sourceRows = newSource;\n }\n\n // Keep __originalOrder in sync\n if (grid._sortState) {\n const origIdx = grid.__originalOrder.indexOf(row);\n if (origIdx >= 0) {\n const newOrig = [...grid.__originalOrder];\n newOrig.splice(origIdx, 1);\n grid.__originalOrder = newOrig;\n }\n }\n\n // Refresh caches and trigger immediate re-render\n invalidateCellCache(grid);\n grid._rebuildRowIdMap();\n grid.__rowRenderEpoch++;\n for (const r of grid._rowPool) r.__epoch = -1;\n grid.refreshVirtualWindow(true);\n\n grid._emitDataChange();\n\n // Clean up stale remove animation attributes after re-render\n if (animate) {\n requestAnimationFrame(() => {\n grid.querySelectorAll('[data-animating=\"remove\"]').forEach((el) => {\n el.removeAttribute('data-animating');\n });\n });\n }\n\n return row;\n }\n\n // --- Transaction API ---\n\n async applyTransaction(transaction: RowTransaction<T>, animate = true): Promise<TransactionResult<T>> {\n const grid = this.#grid;\n const result: TransactionResult<T> = { added: [], updated: [], removed: [] };\n\n // 1. Process removes first (before indices shift from inserts)\n if (transaction.remove?.length) {\n for (const { id } of transaction.remove) {\n const entry = grid._getRowEntry(id);\n if (!entry) continue;\n\n const { row } = entry;\n\n if (animate) {\n const idx = grid._rows.indexOf(row);\n if (idx >= 0) await animateRow(grid, idx, 'remove');\n }\n\n // Find current position (may have shifted during animation)\n const currentIdx = grid._rows.indexOf(row);\n if (currentIdx < 0) {\n result.removed.push(row);\n continue;\n }\n\n const newRows = [...grid._rows];\n newRows.splice(currentIdx, 1);\n grid._rows = newRows;\n\n const srcIdx = grid.sourceRows.indexOf(row);\n if (srcIdx >= 0) {\n const newSource = [...grid.sourceRows];\n newSource.splice(srcIdx, 1);\n grid.sourceRows = newSource;\n }\n\n if (grid._sortState) {\n const origIdx = grid.__originalOrder.indexOf(row);\n if (origIdx >= 0) {\n const newOrig = [...grid.__originalOrder];\n newOrig.splice(origIdx, 1);\n grid.__originalOrder = newOrig;\n }\n }\n\n result.removed.push(row);\n }\n }\n\n // Collect removed IDs so updates don't target removed rows\n const removedIds = new Set(transaction.remove?.map((r) => r.id));\n\n // 2. Process updates (in-place mutation, no structural change)\n if (transaction.update?.length) {\n for (const { id, changes } of transaction.update) {\n if (removedIds.has(id)) continue;\n const entry = grid._getRowEntry(id);\n if (!entry) continue;\n\n const { row, index } = entry;\n let changed = false;\n\n for (const [field, newValue] of Object.entries(changes)) {\n const oldValue = (row as Record<string, unknown>)[field];\n if (oldValue !== newValue) {\n changed = true;\n (row as Record<string, unknown>)[field] = newValue;\n\n grid.dispatchEvent(\n new CustomEvent('cell-change', {\n detail: {\n row,\n rowId: id,\n rowIndex: index,\n field,\n oldValue,\n newValue,\n changes,\n source: 'api',\n } as CellChangeDetail<T>,\n bubbles: true,\n composed: true,\n }),\n );\n }\n }\n\n if (changed) result.updated.push(row);\n }\n }\n\n // 3. Process adds (append to end)\n if (transaction.add?.length) {\n for (const row of transaction.add) {\n grid.sourceRows = [...grid.sourceRows, row];\n\n const newRows = [...grid._rows];\n newRows.push(row);\n grid._rows = newRows;\n\n if (grid._sortState) {\n grid.__originalOrder = [...grid.__originalOrder, row];\n }\n\n result.added.push(row);\n }\n }\n\n // 4. Single render pass for all mutations\n const hasStructuralChange = result.added.length > 0 || result.removed.length > 0;\n const hasUpdates = result.updated.length > 0;\n\n if (hasStructuralChange) {\n invalidateCellCache(grid);\n grid._rebuildRowIdMap();\n grid.__rowRenderEpoch++;\n for (const r of grid._rowPool) r.__epoch = -1;\n grid.refreshVirtualWindow(true);\n } else if (hasUpdates) {\n invalidateCellCache(grid);\n grid._requestSchedulerPhase(RenderPhase.VIRTUALIZATION, 'applyTransaction');\n }\n\n if (hasStructuralChange || hasUpdates) {\n grid._emitDataChange();\n }\n\n // 5. Animate added rows\n if (animate && result.added.length > 0) {\n await new Promise<void>((resolve) => requestAnimationFrame(() => resolve()));\n for (const row of result.added) {\n const idx = grid._rows.indexOf(row);\n if (idx >= 0) await animateRow(grid, idx, 'insert');\n }\n }\n\n // 6. Animate updated rows\n if (animate && result.updated.length > 0) {\n for (const row of result.updated) {\n const idx = grid._rows.indexOf(row);\n if (idx >= 0) await animateRow(grid, idx, 'change');\n }\n }\n\n // 7. Clean up stale remove animations\n if (animate && result.removed.length > 0) {\n requestAnimationFrame(() => {\n grid.querySelectorAll('[data-animating=\"remove\"]').forEach((el) => {\n el.removeAttribute('data-animating');\n });\n });\n }\n\n return result;\n }\n\n #pendingTransaction: RowTransaction<T> | null = null;\n #pendingTransactionResolvers: Array<(result: TransactionResult<T>) => void> = [];\n #transactionRafId: number | null = null;\n\n applyTransactionAsync(transaction: RowTransaction<T>): Promise<TransactionResult<T>> {\n // Merge into pending batch\n if (!this.#pendingTransaction) {\n this.#pendingTransaction = { add: [], update: [], remove: [] };\n }\n if (transaction.add) this.#pendingTransaction.add!.push(...transaction.add);\n if (transaction.update) this.#pendingTransaction.update!.push(...transaction.update);\n if (transaction.remove) this.#pendingTransaction.remove!.push(...transaction.remove);\n\n return new Promise<TransactionResult<T>>((resolve) => {\n this.#pendingTransactionResolvers.push(resolve);\n\n if (this.#transactionRafId === null) {\n this.#transactionRafId = requestAnimationFrame(() => {\n this.#transactionRafId = null;\n const batch = this.#pendingTransaction!;\n const resolvers = this.#pendingTransactionResolvers;\n this.#pendingTransaction = null;\n this.#pendingTransactionResolvers = [];\n\n // Apply batched transaction without per-row animation (too many)\n this.applyTransaction(batch, false).then((result) => {\n for (const resolver of resolvers) resolver(result);\n });\n });\n }\n });\n }\n}\n\n// #endregion\n","/**\n * DOM Builder - Direct DOM construction for performance.\n *\n * Using direct DOM APIs instead of innerHTML is significantly faster:\n * - No HTML parsing by the browser\n * - No template string concatenation\n * - Immediate element creation without serialization/deserialization\n *\n * Benchmark: DOM construction is ~2-3x faster than innerHTML for complex structures.\n */\n\n// #region Element Factories\n/**\n * Create an element with attributes and optional children.\n * Optimized helper that avoids repeated function calls.\n */\nexport function createElement<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n attrs?: Record<string, string>,\n children?: (Node | string | null | undefined)[],\n): HTMLElementTagNameMap[K] {\n const el = document.createElement(tag);\n\n if (attrs) {\n for (const key in attrs) {\n const value = attrs[key];\n if (value !== undefined && value !== null) {\n el.setAttribute(key, value);\n }\n }\n }\n\n if (children) {\n for (const child of children) {\n if (child == null) continue;\n if (typeof child === 'string') {\n el.appendChild(document.createTextNode(child));\n } else {\n el.appendChild(child);\n }\n }\n }\n\n return el;\n}\n\n/**\n * Create a text node (shorthand).\n */\nexport function text(content: string): Text {\n return document.createTextNode(content);\n}\n\n/**\n * Create an element with class (common pattern).\n */\nexport function div(className?: string, attrs?: Record<string, string>): HTMLDivElement {\n const el = document.createElement('div');\n if (className) el.className = className;\n if (attrs) {\n for (const key in attrs) {\n const value = attrs[key];\n if (value !== undefined && value !== null) {\n el.setAttribute(key, value);\n }\n }\n }\n return el;\n}\n\n/**\n * Create a button element.\n */\nexport function button(className?: string, attrs?: Record<string, string>, content?: string | Node): HTMLButtonElement {\n const el = document.createElement('button');\n if (className) el.className = className;\n if (attrs) {\n for (const key in attrs) {\n const value = attrs[key];\n if (value !== undefined && value !== null) {\n el.setAttribute(key, value);\n }\n }\n }\n if (content) {\n if (typeof content === 'string') {\n el.textContent = content;\n } else {\n el.appendChild(content);\n }\n }\n return el;\n}\n\n/**\n * Append multiple children to a parent element.\n */\nexport function appendChildren(parent: Element, children: (Node | null | undefined)[]): void {\n for (const child of children) {\n if (child) parent.appendChild(child);\n }\n}\n\n/**\n * Set multiple attributes on an element.\n */\nexport function setAttrs(el: Element, attrs: Record<string, string | undefined>): void {\n for (const key in attrs) {\n const value = attrs[key];\n if (value !== undefined && value !== null) {\n el.setAttribute(key, value);\n }\n }\n}\n// #endregion\n\n// #region Grid Templates\n/**\n * Template for grid content (the core scrollable grid area).\n * Pre-built once, then cloned for each grid instance.\n */\nconst gridContentTemplate = document.createElement('template');\ngridContentTemplate.innerHTML = `\n <div class=\"tbw-scroll-area\">\n <div class=\"rows-body-wrapper\">\n <div class=\"rows-body\" role=\"grid\">\n <div class=\"header\" role=\"rowgroup\">\n <div class=\"header-row\" role=\"row\" part=\"header-row\"></div>\n </div>\n <div class=\"rows-container\" role=\"presentation\">\n <div class=\"rows-viewport\" role=\"presentation\">\n <div class=\"rows\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"faux-vscroll\">\n <div class=\"faux-vscroll-spacer\"></div>\n </div>\n <div class=\"tbw-sr-only\" aria-live=\"polite\" aria-atomic=\"true\"></div>\n`;\n\n/**\n * Clone the grid content structure.\n * Using template cloning is faster than createElement for nested structures.\n */\nexport function cloneGridContent(): DocumentFragment {\n return gridContentTemplate.content.cloneNode(true) as DocumentFragment;\n}\n// #endregion\n\n// #region Grid DOM Building\n/**\n * Build the grid root structure using direct DOM construction.\n * This is called once per grid instance during initial render.\n */\nexport interface GridDOMOptions {\n hasShell: boolean;\n /** Shell header element (pre-built) */\n shellHeader?: Element;\n /** Shell body element with tool panel (pre-built) */\n shellBody?: Element;\n}\n\n/**\n * Build the complete grid DOM structure.\n * Returns a DocumentFragment ready to be appended to shadow root.\n */\nexport function buildGridDOM(options: GridDOMOptions): DocumentFragment {\n const fragment = document.createDocumentFragment();\n\n const root = div(options.hasShell ? 'tbw-grid-root has-shell' : 'tbw-grid-root');\n\n if (options.hasShell && options.shellHeader && options.shellBody) {\n // Shell mode: header + body (with grid content inside)\n root.appendChild(options.shellHeader);\n root.appendChild(options.shellBody);\n } else {\n // No shell: just grid content in wrapper\n const contentWrapper = div('tbw-grid-content');\n contentWrapper.appendChild(cloneGridContent());\n root.appendChild(contentWrapper);\n }\n\n fragment.appendChild(root);\n return fragment;\n}\n// #endregion\n\n// #region Shell Header\n/**\n * Build shell header using direct DOM construction.\n *\n * Note: The grid no longer creates buttons from config (icon/action).\n * Config/API buttons only use element or render function.\n * Light DOM buttons are slotted directly.\n * The ONLY button the grid creates is the panel toggle.\n */\nexport interface ShellHeaderOptions {\n title?: string;\n hasPanels: boolean;\n isPanelOpen: boolean;\n toolPanelIcon: string;\n /** Config toolbar contents with render function (pre-sorted by order) */\n configButtons: Array<{\n id: string;\n hasRender?: boolean;\n }>;\n /** API toolbar contents with render function (pre-sorted by order) */\n apiButtons: Array<{\n id: string;\n hasRender?: boolean;\n }>;\n}\n\n/**\n * Build shell header element directly without innerHTML.\n */\nexport function buildShellHeader(options: ShellHeaderOptions): HTMLDivElement {\n const header = div('tbw-shell-header', { part: 'shell-header', role: 'presentation' });\n\n // Title\n if (options.title) {\n const titleEl = div('tbw-shell-title');\n titleEl.textContent = options.title;\n header.appendChild(titleEl);\n }\n\n // Shell content with placeholder for light DOM header content\n const content = div('tbw-shell-content', {\n part: 'shell-content',\n role: 'presentation',\n 'data-light-dom-header-content': '',\n });\n header.appendChild(content);\n\n // Toolbar\n const toolbar = div('tbw-shell-toolbar', { part: 'shell-toolbar', role: 'presentation' });\n\n // Placeholders for config toolbar contents with render function\n for (const btn of options.configButtons) {\n if (btn.hasRender) {\n toolbar.appendChild(div('tbw-toolbar-content-slot', { 'data-toolbar-content': btn.id }));\n }\n }\n // Placeholders for API toolbar contents with render function\n for (const btn of options.apiButtons) {\n if (btn.hasRender) {\n toolbar.appendChild(div('tbw-toolbar-content-slot', { 'data-toolbar-content': btn.id }));\n }\n }\n\n // Separator between custom content and panel toggle\n const hasCustomContent =\n options.configButtons.some((b) => b.hasRender) || options.apiButtons.some((b) => b.hasRender);\n if (hasCustomContent && options.hasPanels) {\n toolbar.appendChild(div('tbw-toolbar-separator'));\n }\n\n // Panel toggle button\n if (options.hasPanels) {\n const toggleBtn = button(options.isPanelOpen ? 'tbw-toolbar-btn active' : 'tbw-toolbar-btn', {\n 'data-panel-toggle': '',\n title: 'Settings',\n 'aria-label': 'Toggle settings panel',\n 'aria-pressed': String(options.isPanelOpen),\n 'aria-controls': 'tbw-tool-panel',\n });\n toggleBtn.innerHTML = options.toolPanelIcon;\n toolbar.appendChild(toggleBtn);\n }\n\n header.appendChild(toolbar);\n return header;\n}\n// #endregion\n\n// #region Shell Body\n/**\n * Build shell body (grid content + optional tool panel).\n */\nexport interface ShellBodyOptions {\n position: 'left' | 'right';\n isPanelOpen: boolean;\n expandIcon: string;\n collapseIcon: string;\n /** Sorted panels for accordion */\n panels: Array<{\n id: string;\n title: string;\n icon?: string;\n isExpanded: boolean;\n }>;\n}\n\n/**\n * Build shell body element directly without innerHTML.\n */\nexport function buildShellBody(options: ShellBodyOptions): HTMLDivElement {\n const body = div('tbw-shell-body');\n const hasPanel = options.panels.length > 0;\n const isSinglePanel = options.panels.length === 1;\n\n // Grid content wrapper with cloned grid structure\n const gridContent = div('tbw-grid-content');\n gridContent.appendChild(cloneGridContent());\n\n // Tool panel\n let panelEl: HTMLElement | null = null;\n if (hasPanel) {\n panelEl = createElement('aside', {\n class: options.isPanelOpen ? 'tbw-tool-panel open' : 'tbw-tool-panel',\n part: 'tool-panel',\n 'data-position': options.position,\n role: 'presentation',\n id: 'tbw-tool-panel',\n });\n\n // Resize handle\n const resizeHandlePosition = options.position === 'left' ? 'right' : 'left';\n panelEl.appendChild(\n div('tbw-tool-panel-resize', {\n 'data-resize-handle': '',\n 'data-handle-position': resizeHandlePosition,\n 'aria-hidden': 'true',\n }),\n );\n\n // Panel content with accordion\n const panelContent = div('tbw-tool-panel-content', { role: 'presentation' });\n const accordion = div('tbw-accordion');\n\n for (const panel of options.panels) {\n const sectionClasses = `tbw-accordion-section${panel.isExpanded ? ' expanded' : ''}${isSinglePanel ? ' single' : ''}`;\n const section = div(sectionClasses, { 'data-section': panel.id });\n\n // Accordion header button\n const headerBtn = button('tbw-accordion-header', {\n 'aria-expanded': String(panel.isExpanded),\n 'aria-controls': `tbw-section-${panel.id}`,\n });\n if (isSinglePanel) headerBtn.setAttribute('aria-disabled', 'true');\n\n // Icon\n if (panel.icon) {\n const iconSpan = createElement('span', { class: 'tbw-accordion-icon' });\n iconSpan.innerHTML = panel.icon;\n headerBtn.appendChild(iconSpan);\n }\n\n // Title\n const titleSpan = createElement('span', { class: 'tbw-accordion-title' });\n titleSpan.textContent = panel.title;\n headerBtn.appendChild(titleSpan);\n\n // Chevron (hidden for single panel) — always use expandIcon, CSS rotation handles state\n if (!isSinglePanel) {\n const chevronSpan = createElement('span', { class: 'tbw-accordion-chevron' });\n chevronSpan.innerHTML = options.expandIcon;\n headerBtn.appendChild(chevronSpan);\n }\n\n section.appendChild(headerBtn);\n\n // Accordion content (empty, will be filled by panel render functions)\n section.appendChild(\n div('tbw-accordion-content', {\n id: `tbw-section-${panel.id}`,\n role: 'presentation',\n }),\n );\n\n accordion.appendChild(section);\n }\n\n panelContent.appendChild(accordion);\n panelEl.appendChild(panelContent);\n }\n\n // Append in correct order based on position\n if (options.position === 'left' && panelEl) {\n body.appendChild(panelEl);\n body.appendChild(gridContent);\n } else {\n body.appendChild(gridContent);\n if (panelEl) body.appendChild(panelEl);\n }\n\n return body;\n}\n// #endregion\n","/**\n * Shell infrastructure for grid header bar and tool panels.\n *\n * The shell is an optional wrapper layer that provides:\n * - Header bar with title, plugin content, and toolbar buttons\n * - Tool panels that plugins can register content into\n * - Light DOM parsing for framework-friendly configuration\n */\n\nimport type {\n HeaderContentDefinition,\n IconValue,\n InternalGrid,\n ShellConfig,\n ToolbarContentDefinition,\n ToolPanelDefinition,\n} from '../types';\nimport { DEFAULT_GRID_ICONS } from '../types';\nimport {\n HEADER_CONTENT_DUPLICATE,\n NO_TOOL_PANELS,\n TOOL_PANEL_DUPLICATE,\n TOOL_PANEL_MISSING_ATTR,\n TOOL_PANEL_NOT_FOUND,\n TOOLBAR_CONTENT_DUPLICATE,\n warnDiagnostic,\n} from './diagnostics';\nimport { escapeHtml } from './sanitize';\n\n// #region Types & State\n/**\n * Convert an IconValue to a string for rendering in HTML.\n */\nfunction iconToString(icon: IconValue | undefined): string {\n if (!icon) return '';\n if (typeof icon === 'string') return icon;\n // For HTMLElement, get the outerHTML\n return icon.outerHTML;\n}\n\n/**\n * State for managing shell UI.\n *\n * This interface holds both configuration-like properties (toolPanels, headerContents)\n * and runtime state (isPanelOpen, expandedSections). The Maps allow for efficient\n * registration/unregistration of panels and content.\n */\nexport interface ShellState {\n /** Registered tool panels (from plugins + consumer API) */\n toolPanels: Map<string, ToolPanelDefinition>;\n /** Registered header content (from plugins + consumer API) */\n headerContents: Map<string, HeaderContentDefinition>;\n /** Toolbar content registered via API or light DOM */\n toolbarContents: Map<string, ToolbarContentDefinition>;\n /** Whether a <tbw-grid-tool-buttons> container was found in light DOM */\n hasToolButtonsContainer: boolean;\n /** Light DOM header content elements */\n lightDomHeaderContent: HTMLElement[];\n /** Light DOM header title from <tbw-grid-header title=\"...\"> */\n lightDomTitle: string | null;\n /** IDs of tool panels registered from light DOM (to avoid re-parsing) */\n lightDomToolPanelIds: Set<string>;\n /** IDs of toolbar content registered from light DOM (to avoid re-parsing) */\n lightDomToolbarContentIds: Set<string>;\n /** IDs of tool panels registered via registerToolPanel API */\n apiToolPanelIds: Set<string>;\n /** IDs of header content registered via registerHeaderContent API */\n apiHeaderContentIds: Set<string>;\n /** Whether the tool panel sidebar is open */\n isPanelOpen: boolean;\n /** Which accordion sections are expanded (by panel ID) */\n expandedSections: Set<string>;\n /** Whether light DOM header content has been moved to placeholder (perf optimization) */\n lightDomContentMoved: boolean;\n /** Cleanup functions for header content render returns */\n headerContentCleanups: Map<string, () => void>;\n /** Cleanup functions for each panel section's render return */\n panelCleanups: Map<string, () => void>;\n /** Cleanup functions for toolbar content render returns */\n toolbarContentCleanups: Map<string, () => void>;\n}\n\n/**\n * Runtime-only shell state (not configuration).\n *\n * Configuration (toolPanels, headerContents, toolbarContents, title) lives in\n * effectiveConfig.shell. This state holds runtime UI state and cleanup functions.\n */\nexport interface ShellRuntimeState {\n /** Whether the tool panel sidebar is currently open */\n isPanelOpen: boolean;\n /** Which accordion sections are currently expanded (by panel ID) */\n expandedSections: Set<string>;\n /** Cleanup functions for header content render returns */\n headerContentCleanups: Map<string, () => void>;\n /** Cleanup functions for each panel section's render return */\n panelCleanups: Map<string, () => void>;\n /** Cleanup functions for toolbar content render returns */\n toolbarContentCleanups: Map<string, () => void>;\n /** IDs of tool panels registered from light DOM (to avoid re-parsing) */\n lightDomToolPanelIds: Set<string>;\n /** IDs of tool panels registered via registerToolPanel API */\n apiToolPanelIds: Set<string>;\n /** Whether a <tbw-grid-tool-buttons> container was found in light DOM */\n hasToolButtonsContainer: boolean;\n}\n\n/**\n * Create initial shell runtime state.\n */\nexport function createShellRuntimeState(): ShellRuntimeState {\n return {\n isPanelOpen: false,\n expandedSections: new Set(),\n headerContentCleanups: new Map(),\n panelCleanups: new Map(),\n toolbarContentCleanups: new Map(),\n lightDomToolPanelIds: new Set(),\n apiToolPanelIds: new Set(),\n hasToolButtonsContainer: false,\n };\n}\n\n/**\n * Create initial shell state.\n */\nexport function createShellState(): ShellState {\n return {\n toolPanels: new Map(),\n headerContents: new Map(),\n toolbarContents: new Map(),\n hasToolButtonsContainer: false,\n lightDomHeaderContent: [],\n lightDomTitle: null,\n lightDomToolPanelIds: new Set(),\n lightDomToolbarContentIds: new Set(),\n apiToolPanelIds: new Set(),\n apiHeaderContentIds: new Set(),\n isPanelOpen: false,\n expandedSections: new Set(),\n headerContentCleanups: new Map(),\n panelCleanups: new Map(),\n toolbarContentCleanups: new Map(),\n lightDomContentMoved: false,\n };\n}\n// #endregion\n\n// #region Render Functions\n/**\n * Determine if shell header should be rendered.\n * Reads only from effectiveConfig.shell (single source of truth).\n */\nexport function shouldRenderShellHeader(config: ShellConfig | undefined): boolean {\n // Check if title is configured\n if (config?.header?.title) return true;\n\n // Check if config has toolbar contents\n if (config?.header?.toolbarContents?.length) return true;\n\n // Check if any tool panels are registered\n if (config?.toolPanels?.length) return true;\n\n // Check if any header content is registered\n if (config?.headerContents?.length) return true;\n\n // Check if light DOM has header elements\n if (config?.header?.lightDomContent?.length) return true;\n\n // Check if a toolbar buttons container was found\n if (config?.header?.hasToolButtonsContainer) return true;\n\n return false;\n}\n\n/**\n * Render the shell header HTML.\n *\n * Toolbar contents come from two sources:\n * 1. Light DOM slot (users provide their own HTML in <tbw-grid-tool-buttons>)\n * 2. Config/API with render function (programmatic insertion)\n *\n * Users have full control over toolbar HTML, styling, and behavior.\n * The only button the grid creates is the tool panel toggle.\n *\n * @param toolPanelIcon - Icon for the tool panel toggle (from grid icon config)\n */\nexport function renderShellHeader(\n config: ShellConfig | undefined,\n state: ShellState,\n toolPanelIcon: IconValue = '☰',\n): string {\n const title = config?.header?.title ?? state.lightDomTitle ?? '';\n const hasTitle = !!title;\n const iconStr = iconToString(toolPanelIcon);\n\n // Get all toolbar contents from effectiveConfig (already merged: config + API + light DOM)\n // The config-manager merges state.toolbarContents into effectiveConfig.shell.header.toolbarContents\n // Also include state.toolbarContents directly for cases where renderShellHeader is called\n // before config-manager has merged (e.g., unit tests, initial render)\n const configContents = config?.header?.toolbarContents ?? [];\n const stateContents = [...state.toolbarContents.values()];\n\n // Merge: use config contents, add state contents that aren't in config\n const configIds = new Set(configContents.map((c) => c.id));\n const allContents = [...configContents];\n for (const content of stateContents) {\n if (!configIds.has(content.id)) {\n allContents.push(content);\n }\n }\n\n const hasCustomContent = allContents.length > 0;\n const hasPanels = state.toolPanels.size > 0;\n const showSeparator = hasCustomContent && hasPanels;\n\n // Sort contents by order for slot placement\n const sortedContents = [...allContents].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n\n // Build toolbar HTML\n let toolbarHtml = '';\n\n // Create slots for all contents (unified: config + API + light DOM)\n for (const content of sortedContents) {\n toolbarHtml += `<div class=\"tbw-toolbar-content-slot\" data-toolbar-content=\"${content.id}\"></div>`;\n }\n\n // Separator between custom content and panel toggle\n if (showSeparator) {\n toolbarHtml += '<div class=\"tbw-toolbar-separator\"></div>';\n }\n\n // Single panel toggle button (the ONLY button the grid creates)\n if (hasPanels) {\n const isOpen = state.isPanelOpen;\n const toggleClass = isOpen ? 'tbw-toolbar-btn active' : 'tbw-toolbar-btn';\n toolbarHtml += `<button class=\"${toggleClass}\" data-panel-toggle title=\"Settings\" aria-label=\"Toggle settings panel\" aria-pressed=\"${isOpen}\" aria-controls=\"tbw-tool-panel\">${iconStr}</button>`;\n }\n\n return `\n <div class=\"tbw-shell-header\" part=\"shell-header\" role=\"presentation\">\n ${hasTitle ? `<div class=\"tbw-shell-title\">${escapeHtml(title)}</div>` : ''}\n <div class=\"tbw-shell-content\" part=\"shell-content\" role=\"presentation\" data-light-dom-header-content></div>\n <div class=\"tbw-shell-toolbar\" part=\"shell-toolbar\" role=\"presentation\">\n ${toolbarHtml}\n </div>\n </div>\n `;\n}\n\n/**\n * Render the shell body wrapper HTML (contains grid content + accordion-style tool panel).\n * @param icons - Optional icons for expand/collapse chevrons (from grid config)\n */\nexport function renderShellBody(\n config: ShellConfig | undefined,\n state: ShellState,\n gridContentHtml: string,\n icons?: { expand?: IconValue; collapse?: IconValue },\n): string {\n const position = config?.toolPanel?.position ?? 'right';\n const hasPanel = state.toolPanels.size > 0;\n const isOpen = state.isPanelOpen;\n const expandIcon = iconToString(icons?.expand ?? DEFAULT_GRID_ICONS.expand);\n\n // Sort panels by order for accordion sections\n const sortedPanels = [...state.toolPanels.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n const isSinglePanel = sortedPanels.length === 1;\n\n // Build accordion sections HTML\n let accordionHtml = '';\n for (const panel of sortedPanels) {\n const isExpanded = state.expandedSections.has(panel.id);\n const iconHtml = panel.icon ? `<span class=\"tbw-accordion-icon\">${panel.icon}</span>` : '';\n // Hide chevron for single panel (no toggling needed)\n // Always use expandIcon (▶) — CSS rotation handles the expanded state\n const chevronHtml = isSinglePanel ? '' : `<span class=\"tbw-accordion-chevron\">${expandIcon}</span>`;\n // Disable accordion toggle for single panel\n const sectionClasses = `tbw-accordion-section${isExpanded ? ' expanded' : ''}${isSinglePanel ? ' single' : ''}`;\n accordionHtml += `\n <div class=\"${sectionClasses}\" data-section=\"${panel.id}\">\n <button class=\"tbw-accordion-header\" aria-expanded=\"${isExpanded}\" aria-controls=\"tbw-section-${panel.id}\"${isSinglePanel ? ' aria-disabled=\"true\"' : ''}>\n ${iconHtml}\n <span class=\"tbw-accordion-title\">${panel.title}</span>\n ${chevronHtml}\n </button>\n <div class=\"tbw-accordion-content\" id=\"tbw-section-${panel.id}\" role=\"presentation\"></div>\n </div>\n `;\n }\n\n // Resize handle position depends on panel position\n const resizeHandlePosition = position === 'left' ? 'right' : 'left';\n\n const panelHtml = hasPanel\n ? `\n <aside class=\"tbw-tool-panel${isOpen ? ' open' : ''}\" part=\"tool-panel\" data-position=\"${position}\" role=\"presentation\" id=\"tbw-tool-panel\">\n <div class=\"tbw-tool-panel-resize\" data-resize-handle data-handle-position=\"${resizeHandlePosition}\" aria-hidden=\"true\"></div>\n <div class=\"tbw-tool-panel-content\" role=\"presentation\">\n <div class=\"tbw-accordion\">\n ${accordionHtml}\n </div>\n </div>\n </aside>\n `\n : '';\n\n // For left position, panel comes before content in DOM order\n if (position === 'left') {\n return `\n <div class=\"tbw-shell-body\">\n ${panelHtml}\n <div class=\"tbw-grid-content\">\n ${gridContentHtml}\n </div>\n </div>\n `;\n }\n\n return `\n <div class=\"tbw-shell-body\">\n <div class=\"tbw-grid-content\">\n ${gridContentHtml}\n </div>\n ${panelHtml}\n </div>\n `;\n}\n// #endregion\n\n// #region Light DOM Parsing\n/**\n * Parse light DOM shell elements (tbw-grid-header, etc.).\n * Safe to call multiple times - will only parse once when elements are available.\n */\nexport function parseLightDomShell(host: HTMLElement, state: ShellState): void {\n const headerEl = host.querySelector('tbw-grid-header');\n if (!headerEl) return;\n\n // Parse title attribute (only if not already parsed)\n if (!state.lightDomTitle) {\n const title = headerEl.getAttribute('title');\n if (title) {\n state.lightDomTitle = title;\n }\n }\n\n // Parse header content elements - store references but don't set slot (light DOM doesn't use slots)\n const headerContents = headerEl.querySelectorAll('tbw-grid-header-content');\n if (headerContents.length > 0 && state.lightDomHeaderContent.length === 0) {\n state.lightDomHeaderContent = Array.from(headerContents) as HTMLElement[];\n }\n\n // Hide the light DOM header container (it was just for declarative config)\n (headerEl as HTMLElement).style.display = 'none';\n}\n\n/**\n * Callback type for creating a toolbar content renderer from a light DOM container.\n * This is used by framework adapters (Angular, React, etc.) to create renderers\n * from their template syntax.\n */\nexport type ToolbarContentRendererFactory = (\n container: HTMLElement,\n) => ((target: HTMLElement) => void | (() => void)) | undefined;\n\n/**\n * Parse toolbar buttons container element (<tbw-grid-tool-buttons>).\n * This is a content container - we don't parse individual children.\n * The entire container content is registered as a single toolbar content entry.\n *\n * Example:\n * ```html\n * <tbw-grid>\n * <tbw-grid-tool-buttons>\n * <button>My button</button>\n * <button>My other button</button>\n * </tbw-grid-tool-buttons>\n * </tbw-grid>\n * ```\n *\n * The container's children are moved to the toolbar area during render.\n * We treat this as opaque content - users control what goes inside.\n *\n * @param host - The grid host element\n * @param state - Shell state to update\n * @param rendererFactory - Optional factory for creating renderers (used by framework adapters)\n */\nexport function parseLightDomToolButtons(\n host: HTMLElement,\n state: ShellState,\n rendererFactory?: ToolbarContentRendererFactory,\n): void {\n // Look for the toolbar buttons container element\n const toolButtonsContainer = host.querySelector(':scope > tbw-grid-tool-buttons') as HTMLElement | null;\n if (!toolButtonsContainer) return;\n\n // Mark that we found the container (for shouldRenderShellHeader)\n state.hasToolButtonsContainer = true;\n\n // Skip if already registered\n const id = 'light-dom-toolbar-content';\n if (state.lightDomToolbarContentIds.has(id)) return;\n\n // Register as a single content entry with a render function\n const adapterRenderer = rendererFactory?.(toolButtonsContainer);\n\n const contentDef: ToolbarContentDefinition = {\n id,\n order: 0, // Light DOM content comes first\n render:\n adapterRenderer ??\n ((target: HTMLElement) => {\n // Move all children from the light DOM container to the target\n while (toolButtonsContainer.firstChild) {\n target.appendChild(toolButtonsContainer.firstChild);\n }\n // Return cleanup that moves children back to original container\n // This preserves them across full re-renders that destroy the slot\n return () => {\n while (target.firstChild) {\n toolButtonsContainer.appendChild(target.firstChild);\n }\n };\n }),\n };\n\n state.toolbarContents.set(id, contentDef);\n state.lightDomToolbarContentIds.add(id);\n\n // Hide the original container\n toolButtonsContainer.style.display = 'none';\n}\n\n/**\n * Callback type for creating a tool panel renderer from a light DOM element.\n * This is used by framework adapters (Angular, React, etc.) to create renderers\n * from their template syntax.\n */\nexport type ToolPanelRendererFactory = (\n element: HTMLElement,\n) => ((container: HTMLElement) => void | (() => void)) | undefined;\n\n/**\n * Parse light DOM tool panel elements (<tbw-grid-tool-panel>).\n * These can appear as direct children of <tbw-grid> for declarative tool panel configuration.\n *\n * Attributes:\n * - `id` (required): Unique panel identifier\n * - `title` (required): Panel title shown in accordion header\n * - `icon`: Icon for accordion section header (emoji or text)\n * - `tooltip`: Tooltip for accordion section header\n * - `order`: Panel order priority (lower = first, default: 100)\n *\n * For vanilla JS, the element's innerHTML is used as the panel content.\n * For framework adapters, the adapter can provide a custom renderer factory.\n *\n * @param host - The grid host element\n * @param state - Shell state to update\n * @param rendererFactory - Optional factory for creating renderers (used by framework adapters)\n */\nexport function parseLightDomToolPanels(\n host: HTMLElement,\n state: ShellState,\n rendererFactory?: ToolPanelRendererFactory,\n): void {\n const toolPanelElements = host.querySelectorAll(':scope > tbw-grid-tool-panel');\n\n toolPanelElements.forEach((element) => {\n const panelEl = element as HTMLElement;\n const id = panelEl.getAttribute('id');\n const title = panelEl.getAttribute('title');\n\n // Skip if required attributes are missing\n if (!id || !title) {\n warnDiagnostic(\n TOOL_PANEL_MISSING_ATTR,\n `Tool panel missing required id or title attribute: id=\"${id ?? ''}\", title=\"${title ?? ''}\"`,\n );\n return;\n }\n\n const icon = panelEl.getAttribute('icon') ?? undefined;\n const tooltip = panelEl.getAttribute('tooltip') ?? undefined;\n const order = parseInt(panelEl.getAttribute('order') ?? '100', 10);\n\n // Try framework adapter first, then fall back to innerHTML\n let render: (container: HTMLElement) => void | (() => void);\n\n const adapterRenderer = rendererFactory?.(panelEl);\n if (adapterRenderer) {\n render = adapterRenderer;\n } else {\n // Vanilla fallback: use innerHTML as static content\n const content = panelEl.innerHTML.trim();\n render = (container: HTMLElement) => {\n const wrapper = document.createElement('div');\n wrapper.innerHTML = content;\n container.appendChild(wrapper);\n return () => wrapper.remove();\n };\n }\n\n // Check if panel was already parsed\n const existingPanel = state.toolPanels.get(id);\n\n // If already parsed and we have an adapter renderer, update the render function\n // and re-read attributes from DOM (Angular may have updated them after initial parse)\n // This handles the case where Angular templates register after initial parsing\n if (existingPanel) {\n if (adapterRenderer) {\n // Update render function with framework adapter renderer\n existingPanel.render = render;\n\n // Re-read attributes from DOM - framework may have set them after initial parse\n // (e.g., Angular directive sets attributes in an effect after template is available)\n existingPanel.order = order;\n existingPanel.icon = icon;\n existingPanel.tooltip = tooltip;\n // Note: title and id are required and shouldn't change\n\n // Clear existing cleanup to force re-render with new renderer\n const cleanup = state.panelCleanups.get(id);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(id);\n }\n }\n return;\n }\n\n // Register the tool panel\n const panel: ToolPanelDefinition = {\n id,\n title,\n icon,\n tooltip,\n order,\n render,\n };\n\n state.toolPanels.set(id, panel);\n state.lightDomToolPanelIds.add(id);\n\n // Hide the light DOM element\n panelEl.style.display = 'none';\n });\n}\n// #endregion\n\n// #region Event Handlers\n/**\n * Set up event listeners for shell toolbar buttons and accordion.\n */\nexport function setupShellEventListeners(\n renderRoot: Element,\n config: ShellConfig | undefined,\n state: ShellState,\n callbacks: {\n onPanelToggle: () => void;\n onSectionToggle: (sectionId: string) => void;\n },\n): void {\n const toolbar = renderRoot.querySelector('.tbw-shell-toolbar');\n if (toolbar) {\n toolbar.addEventListener('click', (e) => {\n const target = e.target as HTMLElement;\n\n // Handle single panel toggle button\n const panelToggle = target.closest('[data-panel-toggle]') as HTMLElement | null;\n if (panelToggle) {\n callbacks.onPanelToggle();\n return;\n }\n });\n }\n\n // Accordion header clicks\n const accordion = renderRoot.querySelector('.tbw-accordion');\n if (accordion) {\n accordion.addEventListener('click', (e) => {\n const target = e.target as HTMLElement;\n const header = target.closest('.tbw-accordion-header') as HTMLElement | null;\n if (header) {\n const section = header.closest('[data-section]') as HTMLElement | null;\n const sectionId = section?.getAttribute('data-section');\n if (sectionId) {\n callbacks.onSectionToggle(sectionId);\n }\n }\n });\n }\n}\n\n/**\n * Set up a click-outside listener that closes the tool panel when the user\n * clicks anywhere inside the grid but outside the tool panel itself.\n *\n * Only active when `config.toolPanel.closeOnClickOutside` is `true` AND the\n * panel is open. The listener is added on `mousedown` (not `click`) so it\n * fires before focus changes.\n *\n * @returns A cleanup function that removes the listener.\n */\nexport function setupClickOutsideDismiss(\n gridElement: Element,\n config: ShellConfig | undefined,\n state: ShellState,\n onClose: () => void,\n): () => void {\n if (!config?.toolPanel?.closeOnClickOutside) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n const handler = (e: Event) => {\n if (!state.isPanelOpen) return;\n\n const target = e.target as Element | null;\n if (!target) return;\n\n // Ignore clicks inside the tool panel itself or its toggle button\n if (target.closest('.tbw-tool-panel') || target.closest('[data-panel-toggle]')) {\n return;\n }\n\n onClose();\n };\n\n gridElement.addEventListener('mousedown', handler);\n return () => gridElement.removeEventListener('mousedown', handler);\n}\n\n/**\n * Set up resize handle for tool panel.\n * Returns a cleanup function to remove event listeners.\n */\nexport function setupToolPanelResize(\n renderRoot: Element,\n config: ShellConfig | undefined,\n onResize: (width: number) => void,\n): () => void {\n const panel = renderRoot.querySelector('.tbw-tool-panel') as HTMLElement | null;\n const handle = renderRoot.querySelector('[data-resize-handle]') as HTMLElement | null;\n const shellBody = renderRoot.querySelector('.tbw-shell-body') as HTMLElement | null;\n if (!panel || !handle || !shellBody) {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n return () => {};\n }\n\n const position = config?.toolPanel?.position ?? 'right';\n const minWidth = 200;\n\n let startX = 0;\n let startWidth = 0;\n let maxWidth = 0;\n let isResizing = false;\n\n const onMouseMove = (e: MouseEvent) => {\n if (!isResizing) return;\n e.preventDefault();\n\n // For right-positioned panel: dragging left (negative clientX change) should expand\n // For left-positioned panel: dragging right (positive clientX change) should expand\n const delta = position === 'left' ? e.clientX - startX : startX - e.clientX;\n const newWidth = Math.min(maxWidth, Math.max(minWidth, startWidth + delta));\n\n panel.style.width = `${newWidth}px`;\n };\n\n const onMouseUp = () => {\n if (!isResizing) return;\n isResizing = false;\n handle.classList.remove('resizing');\n panel.style.transition = ''; // Re-enable transition\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n\n // Get final width and notify\n const finalWidth = panel.getBoundingClientRect().width;\n onResize(finalWidth);\n\n document.removeEventListener('mousemove', onMouseMove);\n document.removeEventListener('mouseup', onMouseUp);\n };\n\n const onMouseDown = (e: MouseEvent) => {\n e.preventDefault();\n isResizing = true;\n startX = e.clientX;\n startWidth = panel.getBoundingClientRect().width;\n // Calculate max width dynamically based on grid container width\n maxWidth = shellBody.getBoundingClientRect().width - 20; // Leave 20px margin\n handle.classList.add('resizing');\n panel.style.transition = 'none'; // Disable transition for smooth resize\n document.body.style.cursor = 'col-resize';\n document.body.style.userSelect = 'none';\n\n document.addEventListener('mousemove', onMouseMove);\n document.addEventListener('mouseup', onMouseUp);\n };\n\n handle.addEventListener('mousedown', onMouseDown);\n\n // Return cleanup function\n return () => {\n handle.removeEventListener('mousedown', onMouseDown);\n document.removeEventListener('mousemove', onMouseMove);\n document.removeEventListener('mouseup', onMouseUp);\n };\n}\n// #endregion\n\n// #region Content Rendering\n/**\n * Render toolbar content (render functions) into toolbar slots.\n * All contents (config + API + light DOM) are now unified.\n */\nexport function renderCustomToolbarContents(\n renderRoot: Element,\n config: ShellConfig | undefined,\n state: ShellState,\n): void {\n // Merge config contents with state contents (same logic as renderShellHeader)\n const configContents = config?.header?.toolbarContents ?? [];\n const stateContents = [...state.toolbarContents.values()];\n const configIds = new Set(configContents.map((c) => c.id));\n const allContents = [...configContents];\n for (const content of stateContents) {\n if (!configIds.has(content.id)) {\n allContents.push(content);\n }\n }\n\n // Only process contents that need rendering (have render and cleanup not already set)\n for (const content of allContents) {\n // Skip if already rendered (cleanup exists)\n if (state.toolbarContentCleanups.has(content.id)) continue;\n if (!content.render) continue;\n\n const slot = renderRoot.querySelector(`[data-toolbar-content=\"${content.id}\"]`);\n if (!slot) continue;\n\n const cleanup = content.render(slot as HTMLElement);\n if (cleanup) {\n state.toolbarContentCleanups.set(content.id, cleanup);\n }\n }\n}\n\n/**\n * Render header content from plugins into the shell content area.\n * Also moves light DOM header content to the placeholder (once).\n */\nexport function renderHeaderContent(renderRoot: Element, state: ShellState): void {\n // Early exit if nothing to do (most common path after initial render)\n const hasLightDomContent = state.lightDomHeaderContent.length > 0 && !state.lightDomContentMoved;\n const hasPluginContent = state.headerContents.size > 0;\n if (!hasLightDomContent && !hasPluginContent) return;\n\n const contentArea = renderRoot.querySelector('.tbw-shell-content');\n if (!contentArea) return;\n\n // Move light DOM header content to placeholder - only once (perf optimization)\n if (hasLightDomContent) {\n for (const el of state.lightDomHeaderContent) {\n el.style.display = ''; // Show it (was hidden in the original container)\n contentArea.appendChild(el);\n }\n state.lightDomContentMoved = true;\n }\n\n // Sort by order\n const sortedContents = [...state.headerContents.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n\n for (const content of sortedContents) {\n // Clean up previous render if any\n const existingCleanup = state.headerContentCleanups.get(content.id);\n if (existingCleanup) {\n existingCleanup();\n state.headerContentCleanups.delete(content.id);\n }\n\n // Check if container already exists\n let container = contentArea.querySelector(`[data-header-content=\"${content.id}\"]`) as HTMLElement | null;\n if (!container) {\n container = document.createElement('div');\n container.setAttribute('data-header-content', content.id);\n contentArea.appendChild(container);\n }\n\n const cleanup = content.render(container);\n if (cleanup) {\n state.headerContentCleanups.set(content.id, cleanup);\n }\n }\n}\n\n/**\n * Render content for expanded accordion sections.\n * @param icons - Optional icons for expand/collapse chevrons (from grid config)\n */\nexport function renderPanelContent(\n renderRoot: Element,\n state: ShellState,\n icons?: { expand?: IconValue; collapse?: IconValue },\n): void {\n if (!state.isPanelOpen) return;\n\n for (const [panelId, panel] of state.toolPanels) {\n const isExpanded = state.expandedSections.has(panelId);\n const section = renderRoot.querySelector(`[data-section=\"${panelId}\"]`);\n const contentArea = section?.querySelector('.tbw-accordion-content') as HTMLElement | null;\n\n if (!section || !contentArea) continue;\n\n // Update expanded state\n section.classList.toggle('expanded', isExpanded);\n const header = section.querySelector('.tbw-accordion-header');\n if (header) {\n header.setAttribute('aria-expanded', String(isExpanded));\n }\n // Don't swap chevron icon — CSS rotation handles expanded/collapsed state\n\n if (isExpanded) {\n // Check if content is already rendered\n if (contentArea.children.length === 0) {\n // Render panel content\n const cleanup = panel.render(contentArea);\n if (cleanup) {\n state.panelCleanups.set(panelId, cleanup);\n }\n }\n } else {\n // Clean up and clear content when collapsed\n const cleanup = state.panelCleanups.get(panelId);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(panelId);\n }\n contentArea.innerHTML = '';\n }\n }\n}\n\n/**\n * Update toolbar button active states.\n */\nexport function updateToolbarActiveStates(renderRoot: Element, state: ShellState): void {\n // Update single panel toggle button\n const panelToggle = renderRoot.querySelector('[data-panel-toggle]');\n if (panelToggle) {\n panelToggle.classList.toggle('active', state.isPanelOpen);\n panelToggle.setAttribute('aria-pressed', String(state.isPanelOpen));\n }\n}\n\n/**\n * Update tool panel open/close state.\n */\nexport function updatePanelState(renderRoot: Element, state: ShellState): void {\n const panel = renderRoot.querySelector('.tbw-tool-panel') as HTMLElement | null;\n if (!panel) return;\n\n panel.classList.toggle('open', state.isPanelOpen);\n\n // Clear inline width when closing (resize sets inline style that overrides CSS)\n if (!state.isPanelOpen) {\n panel.style.width = '';\n }\n}\n\n/**\n * Prepare shell state for a full re-render.\n * Runs cleanup functions so content (toolbar buttons, panels, headers)\n * can be restored to their original containers and re-rendered into new DOM.\n */\nexport function prepareForRerender(state: ShellState): void {\n // Run cleanups for toolbar contents (moves elements back to original containers)\n for (const cleanup of state.toolbarContentCleanups.values()) {\n cleanup();\n }\n state.toolbarContentCleanups.clear();\n\n // Run cleanups for panel contents (old DOM will be destroyed)\n for (const cleanup of state.panelCleanups.values()) {\n cleanup();\n }\n state.panelCleanups.clear();\n\n // Run cleanups for header contents (old DOM will be destroyed)\n for (const cleanup of state.headerContentCleanups.values()) {\n cleanup();\n }\n state.headerContentCleanups.clear();\n\n // Allow light DOM content to be re-moved into new DOM\n state.lightDomContentMoved = false;\n}\n\n/**\n * Cleanup all shell state when grid disconnects.\n */\nexport function cleanupShellState(state: ShellState): void {\n // Clean up header content\n for (const cleanup of state.headerContentCleanups.values()) {\n cleanup();\n }\n state.headerContentCleanups.clear();\n\n // Clean up panel content\n for (const cleanup of state.panelCleanups.values()) {\n cleanup();\n }\n state.panelCleanups.clear();\n\n // Clean up toolbar contents\n for (const cleanup of state.toolbarContentCleanups.values()) {\n cleanup();\n }\n state.toolbarContentCleanups.clear();\n\n // Call onDestroy for all toolbar contents\n for (const content of state.toolbarContents.values()) {\n content.onDestroy?.();\n }\n\n // Invoke onClose for all open panels\n if (state.isPanelOpen) {\n for (const sectionId of state.expandedSections) {\n const panel = state.toolPanels.get(sectionId);\n panel?.onClose?.();\n }\n }\n\n // Reset panel state\n state.isPanelOpen = false;\n state.expandedSections.clear();\n\n // Clear registrations\n state.toolPanels.clear();\n state.headerContents.clear();\n state.toolbarContents.clear();\n state.lightDomHeaderContent = [];\n\n // Clear light DOM tracking sets (allow re-parsing)\n state.lightDomToolPanelIds.clear();\n state.lightDomToolbarContentIds.clear();\n\n // Reset move tracking flag (allow re-initialization)\n state.lightDomContentMoved = false;\n}\n// #endregion\n\n// #region ShellController\n/**\n * Controller interface for managing shell/tool panel behavior.\n */\nexport interface ShellController {\n /** Whether the shell has been initialized */\n readonly isInitialized: boolean;\n /** Set the initialized state */\n setInitialized(value: boolean): void;\n /** Whether the tool panel is currently open */\n readonly isPanelOpen: boolean;\n /** Get the currently active panel ID (deprecated) */\n readonly activePanel: string | null;\n /** Get IDs of expanded accordion sections */\n readonly expandedSections: string[];\n /** Open the tool panel */\n openToolPanel(): void;\n /** Close the tool panel */\n closeToolPanel(): void;\n /** Toggle the tool panel */\n toggleToolPanel(): void;\n /** Toggle an accordion section */\n toggleToolPanelSection(sectionId: string): void;\n /** Get registered tool panels */\n getToolPanels(): ToolPanelDefinition[];\n /** Register a tool panel */\n registerToolPanel(panel: ToolPanelDefinition): void;\n /** Unregister a tool panel */\n unregisterToolPanel(panelId: string): void;\n /** Get registered header contents */\n getHeaderContents(): HeaderContentDefinition[];\n /** Register header content */\n registerHeaderContent(content: HeaderContentDefinition): void;\n /** Unregister header content */\n unregisterHeaderContent(contentId: string): void;\n /** Get all registered toolbar contents */\n getToolbarContents(): ToolbarContentDefinition[];\n /** Register toolbar content */\n registerToolbarContent(content: ToolbarContentDefinition): void;\n /** Unregister toolbar content */\n unregisterToolbarContent(contentId: string): void;\n}\n\n/**\n * Create a ShellController instance.\n * The controller encapsulates all tool panel orchestration logic.\n */\nexport function createShellController(state: ShellState, grid: InternalGrid): ShellController {\n let initialized = false;\n\n const controller: ShellController = {\n get isInitialized() {\n return initialized;\n },\n setInitialized(value: boolean) {\n initialized = value;\n },\n\n get isPanelOpen() {\n return state.isPanelOpen;\n },\n\n get activePanel() {\n // For backward compatibility, return first expanded section if panel is open\n if (state.isPanelOpen && state.expandedSections.size > 0) {\n return [...state.expandedSections][0];\n }\n return null;\n },\n\n get expandedSections() {\n return [...state.expandedSections];\n },\n\n openToolPanel() {\n if (state.isPanelOpen) return;\n if (state.toolPanels.size === 0) {\n warnDiagnostic(NO_TOOL_PANELS, 'No tool panels registered', grid.id);\n return;\n }\n\n state.isPanelOpen = true;\n\n // Auto-expand first section if none expanded\n if (state.expandedSections.size === 0 && state.toolPanels.size > 0) {\n const sortedPanels = [...state.toolPanels.values()].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n const firstPanel = sortedPanels[0];\n if (firstPanel) {\n state.expandedSections.add(firstPanel.id);\n }\n }\n\n // Update UI\n const shadow = grid._renderRoot;\n updateToolbarActiveStates(shadow, state);\n updatePanelState(shadow, state);\n\n // Render accordion sections\n renderPanelContent(shadow, state, grid._accordionIcons);\n\n // Emit event\n grid._emit('tool-panel-open', { sections: controller.expandedSections });\n },\n\n closeToolPanel() {\n if (!state.isPanelOpen) return;\n\n // Clean up all panel content\n for (const cleanup of state.panelCleanups.values()) {\n cleanup();\n }\n state.panelCleanups.clear();\n\n // Call onClose for all panels\n for (const panel of state.toolPanels.values()) {\n panel.onClose?.();\n }\n\n state.isPanelOpen = false;\n\n // Update UI\n const shadow = grid._renderRoot;\n updateToolbarActiveStates(shadow, state);\n updatePanelState(shadow, state);\n\n // Emit event\n grid._emit('tool-panel-close', {});\n },\n\n toggleToolPanel() {\n if (state.isPanelOpen) {\n controller.closeToolPanel();\n } else {\n controller.openToolPanel();\n }\n },\n\n toggleToolPanelSection(sectionId: string) {\n const panel = state.toolPanels.get(sectionId);\n if (!panel) {\n warnDiagnostic(TOOL_PANEL_NOT_FOUND, `Tool panel section \"${sectionId}\" not found`, grid.id);\n return;\n }\n\n // Don't allow toggling when there's only one panel (it should stay expanded)\n if (state.toolPanels.size === 1) {\n return;\n }\n\n const shadow = grid._renderRoot;\n const isExpanded = state.expandedSections.has(sectionId);\n\n if (isExpanded) {\n // Collapsing current section\n const cleanup = state.panelCleanups.get(sectionId);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(sectionId);\n }\n panel.onClose?.();\n state.expandedSections.delete(sectionId);\n updateAccordionSectionState(shadow, sectionId, false);\n } else {\n // Expanding - first collapse all others (exclusive accordion)\n for (const [otherId, otherPanel] of state.toolPanels) {\n if (otherId !== sectionId && state.expandedSections.has(otherId)) {\n const cleanup = state.panelCleanups.get(otherId);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(otherId);\n }\n otherPanel.onClose?.();\n state.expandedSections.delete(otherId);\n updateAccordionSectionState(shadow, otherId, false);\n // Clear content of collapsed section\n const contentEl = shadow.querySelector(`[data-section=\"${otherId}\"] .tbw-accordion-content`);\n if (contentEl) contentEl.innerHTML = '';\n }\n }\n // Now expand the target section\n state.expandedSections.add(sectionId);\n updateAccordionSectionState(shadow, sectionId, true);\n renderAccordionSectionContent(shadow, state, sectionId);\n }\n\n // Emit event\n grid._emit('tool-panel-section-toggle', { id: sectionId, expanded: !isExpanded });\n },\n\n getToolPanels() {\n return [...state.toolPanels.values()];\n },\n\n registerToolPanel(panel: ToolPanelDefinition) {\n if (state.toolPanels.has(panel.id)) {\n warnDiagnostic(TOOL_PANEL_DUPLICATE, `Tool panel \"${panel.id}\" already registered`, grid.id);\n return;\n }\n state.toolPanels.set(panel.id, panel);\n\n if (initialized) {\n grid.refreshShellHeader?.();\n }\n },\n\n unregisterToolPanel(panelId: string) {\n // Close panel if open and this section is expanded\n if (state.expandedSections.has(panelId)) {\n const cleanup = state.panelCleanups.get(panelId);\n if (cleanup) {\n cleanup();\n state.panelCleanups.delete(panelId);\n }\n state.expandedSections.delete(panelId);\n }\n\n state.toolPanels.delete(panelId);\n\n if (initialized) {\n grid.refreshShellHeader?.();\n }\n },\n\n getHeaderContents() {\n return [...state.headerContents.values()];\n },\n\n registerHeaderContent(content: HeaderContentDefinition) {\n if (state.headerContents.has(content.id)) {\n warnDiagnostic(HEADER_CONTENT_DUPLICATE, `Header content \"${content.id}\" already registered`, grid.id);\n return;\n }\n state.headerContents.set(content.id, content);\n\n if (initialized) {\n renderHeaderContent(grid._renderRoot, state);\n }\n },\n\n unregisterHeaderContent(contentId: string) {\n // Clean up\n const cleanup = state.headerContentCleanups.get(contentId);\n if (cleanup) {\n cleanup();\n state.headerContentCleanups.delete(contentId);\n }\n\n // Call onDestroy\n const content = state.headerContents.get(contentId);\n content?.onDestroy?.();\n\n state.headerContents.delete(contentId);\n\n // Remove DOM element\n const el = grid._renderRoot.querySelector(`[data-header-content=\"${contentId}\"]`);\n el?.remove();\n },\n\n getToolbarContents() {\n return [...state.toolbarContents.values()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n },\n\n registerToolbarContent(content: ToolbarContentDefinition) {\n if (state.toolbarContents.has(content.id)) {\n warnDiagnostic(TOOLBAR_CONTENT_DUPLICATE, `Toolbar content \"${content.id}\" already registered`, grid.id);\n return;\n }\n state.toolbarContents.set(content.id, content);\n\n if (initialized) {\n grid.refreshShellHeader?.();\n }\n },\n\n unregisterToolbarContent(contentId: string) {\n // Clean up\n const cleanup = state.toolbarContentCleanups.get(contentId);\n if (cleanup) {\n cleanup();\n state.toolbarContentCleanups.delete(contentId);\n }\n\n // Call onDestroy if defined\n const content = state.toolbarContents.get(contentId);\n if (content?.onDestroy) {\n content.onDestroy();\n }\n\n state.toolbarContents.delete(contentId);\n\n if (initialized) {\n grid.refreshShellHeader?.();\n }\n },\n };\n\n return controller;\n}\n\n/**\n * Update accordion section visual state.\n */\nfunction updateAccordionSectionState(renderRoot: Element, sectionId: string, expanded: boolean): void {\n const section = renderRoot.querySelector(`[data-section=\"${sectionId}\"]`);\n if (section) {\n section.classList.toggle('expanded', expanded);\n }\n}\n\n/**\n * Render content for a single accordion section.\n */\nfunction renderAccordionSectionContent(renderRoot: Element, state: ShellState, sectionId: string): void {\n const panel = state.toolPanels.get(sectionId);\n if (!panel?.render) return;\n\n const contentEl = renderRoot.querySelector(`[data-section=\"${sectionId}\"] .tbw-accordion-content`);\n if (!contentEl) return;\n\n const cleanup = panel.render(contentEl as HTMLElement);\n if (cleanup) {\n state.panelCleanups.set(sectionId, cleanup);\n }\n}\n// #endregion\n\n// #region Grid HTML Templates\n/**\n * Core grid content HTML template.\n * Uses faux scrollbar pattern for smooth virtualized scrolling.\n */\nexport const GRID_CONTENT_HTML = `\n <div class=\"tbw-scroll-area\">\n <div class=\"rows-body-wrapper\">\n <div class=\"rows-body\" role=\"grid\">\n <div class=\"header\" role=\"rowgroup\">\n <div class=\"header-row\" role=\"row\" part=\"header-row\"></div>\n </div>\n <div class=\"rows-container\" role=\"presentation\">\n <div class=\"rows-viewport\" role=\"presentation\">\n <div class=\"rows\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"faux-vscroll\">\n <div class=\"faux-vscroll-spacer\"></div>\n </div>\n <div class=\"tbw-sr-only\" aria-live=\"polite\" aria-atomic=\"true\"></div>\n`;\n// #endregion\n\n// #region DOM Construction\nimport {\n buildGridDOM,\n buildShellBody,\n buildShellHeader,\n type ShellBodyOptions,\n type ShellHeaderOptions,\n} from './dom-builder';\n\n/**\n * Build the complete grid DOM structure using direct DOM construction.\n * This is 2-3x faster than innerHTML for initial render.\n *\n * @param renderRoot - The element to render into (will be cleared)\n * @param shellConfig - Shell configuration\n * @param state - Shell state\n * @param icons - Optional icons\n * @returns Whether shell is active (for post-render setup)\n */\nexport function buildGridDOMIntoElement(\n renderRoot: Element,\n shellConfig: ShellConfig | undefined,\n runtimeState: { isPanelOpen: boolean; expandedSections: Set<string> },\n icons?: { toolPanel?: IconValue; expand?: IconValue; collapse?: IconValue },\n): boolean {\n const hasShell = shouldRenderShellHeader(shellConfig);\n\n // Preserve light DOM elements before clearing (they contain user content)\n // These are custom elements used for declarative configuration\n const lightDomElements: Element[] = [];\n const lightDomSelectors = [\n 'tbw-grid-header',\n 'tbw-grid-tool-buttons',\n 'tbw-grid-tool-panel',\n 'tbw-grid-column',\n 'tbw-grid-detail',\n 'tbw-grid-responsive-card',\n ];\n for (const selector of lightDomSelectors) {\n const elements = renderRoot.querySelectorAll(`:scope > ${selector}`);\n elements.forEach((el) => lightDomElements.push(el));\n }\n\n // Clear existing content (this would delete light DOM elements, so we preserved them first)\n renderRoot.replaceChildren();\n\n // Re-append preserved light DOM elements (hidden, they're used for config)\n for (const el of lightDomElements) {\n renderRoot.appendChild(el);\n }\n\n if (hasShell) {\n const toolPanelIcon = iconToString(icons?.toolPanel ?? DEFAULT_GRID_ICONS.toolPanel);\n const expandIcon = iconToString(icons?.expand ?? DEFAULT_GRID_ICONS.expand);\n const collapseIcon = iconToString(icons?.collapse ?? DEFAULT_GRID_ICONS.collapse);\n\n // All toolbar contents now come from shellConfig (merged by ConfigManager)\n const allContents = shellConfig?.header?.toolbarContents ?? [];\n const sortedContents = [...allContents].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n\n // All panels now come from shellConfig (merged by ConfigManager)\n const allPanels = shellConfig?.toolPanels ?? [];\n const sortedPanels = [...allPanels].sort((a, b) => (a.order ?? 100) - (b.order ?? 100));\n\n // Build header options\n const headerOptions: ShellHeaderOptions = {\n title: shellConfig?.header?.title ?? undefined,\n hasPanels: sortedPanels.length > 0,\n isPanelOpen: runtimeState.isPanelOpen,\n toolPanelIcon,\n // All contents are now in config (no more separate config vs API distinction for rendering)\n configButtons: sortedContents.map((c) => ({\n id: c.id,\n hasElement: false,\n hasRender: !!c.render,\n })),\n apiButtons: [], // No longer needed - all contents merged into configButtons\n };\n\n // Build body options\n const bodyOptions: ShellBodyOptions = {\n position: shellConfig?.toolPanel?.position ?? 'right',\n isPanelOpen: runtimeState.isPanelOpen,\n expandIcon,\n collapseIcon,\n panels: sortedPanels.map((p) => ({\n id: p.id,\n title: p.title,\n icon: iconToString(p.icon),\n isExpanded: runtimeState.expandedSections.has(p.id),\n })),\n };\n\n // Build shell elements\n const shellHeader = buildShellHeader(headerOptions);\n const shellBody = buildShellBody(bodyOptions);\n\n // Build and append complete DOM\n const fragment = buildGridDOM({\n hasShell: true,\n shellHeader,\n shellBody,\n });\n renderRoot.appendChild(fragment);\n } else {\n // No shell - just grid content\n const fragment = buildGridDOM({ hasShell: false });\n renderRoot.appendChild(fragment);\n }\n\n return hasShell;\n}\n// #endregion\n","/**\n * Style Injector Module\n *\n * Handles injection of grid and plugin styles into the document.\n * Uses a singleton pattern to avoid duplicate injection across multiple grid instances.\n *\n * @module internal/style-injector\n */\n\nimport { STYLE_EXTRACT_FAILED, STYLE_NOT_FOUND, warnDiagnostic } from './diagnostics';\n\n// #region State\n/** ID for the consolidated grid stylesheet in document.head */\nconst STYLE_ELEMENT_ID = 'tbw-grid-styles';\n\n/** Track injected base styles CSS text */\nlet baseStyles = '';\n\n/** Track injected plugin styles by plugin name (accumulates across all grid instances) */\nconst pluginStylesMap = new Map<string, string>();\n// #endregion\n\n// #region Internal Helpers\n/**\n * Get or create the consolidated style element in document.head.\n * All grid and plugin styles are combined into this single element.\n */\nfunction getStyleElement(): HTMLStyleElement {\n let styleEl = document.getElementById(STYLE_ELEMENT_ID) as HTMLStyleElement | null;\n if (!styleEl) {\n styleEl = document.createElement('style');\n styleEl.id = STYLE_ELEMENT_ID;\n styleEl.setAttribute('data-tbw-grid', 'true');\n document.head.appendChild(styleEl);\n }\n return styleEl;\n}\n\n/**\n * Update the consolidated stylesheet with current base + plugin styles.\n */\nfunction updateStyleElement(): void {\n const styleEl = getStyleElement();\n // Combine base styles and all accumulated plugin styles\n const pluginStyles = Array.from(pluginStylesMap.values()).join('\\n');\n styleEl.textContent = `${baseStyles}\\n\\n/* Plugin Styles */\\n${pluginStyles}`;\n}\n// #endregion\n\n// #region Public API\n/**\n * Add plugin styles to the accumulated plugin styles map.\n * Returns true if any new styles were added.\n */\nexport function addPluginStyles(pluginStyles: Array<{ name: string; styles: string }>): boolean {\n let hasNewStyles = false;\n\n for (const { name, styles } of pluginStyles) {\n if (!pluginStylesMap.has(name)) {\n pluginStylesMap.set(name, styles);\n hasNewStyles = true;\n }\n }\n\n if (hasNewStyles) {\n updateStyleElement();\n }\n\n return hasNewStyles;\n}\n\n/**\n * Extract grid CSS from document.styleSheets (Angular fallback).\n * Angular bundles CSS files into one stylesheet, so we search for it.\n */\nexport function extractGridCssFromDocument(): string | null {\n try {\n // Try to find the stylesheet containing grid CSS\n // Angular bundles all CSS files from angular.json styles array into one stylesheet\n for (const stylesheet of Array.from(document.styleSheets)) {\n try {\n // For inline/bundled stylesheets, check if it contains grid CSS\n const rules = Array.from(stylesheet.cssRules || []);\n const cssText = rules.map((rule) => rule.cssText).join('\\n');\n\n // Check if this stylesheet contains grid CSS by looking for distinctive selectors\n // Without Shadow DOM, we look for tbw-grid nesting selectors\n if (cssText.includes('.tbw-grid-root') && cssText.includes('tbw-grid')) {\n // Found the bundled stylesheet with grid CSS - use ALL of it\n // This includes core grid.css + all plugin CSS files\n return cssText;\n }\n } catch {\n // CORS or access restriction - skip\n continue;\n }\n }\n } catch (err) {\n warnDiagnostic(STYLE_EXTRACT_FAILED, `Failed to extract grid.css from document stylesheets: ${err}`);\n }\n\n return null;\n}\n\n/**\n * Inject grid styles into the document.\n * All styles go into a single <style id=\"tbw-grid-styles\"> element in document.head.\n * Uses a singleton pattern to avoid duplicate injection across multiple grid instances.\n *\n * @param inlineStyles - CSS string from Vite ?inline import (may be empty in Angular)\n */\nexport async function injectStyles(inlineStyles: string): Promise<void> {\n // If base styles already injected, nothing to do\n if (baseStyles) {\n return;\n }\n\n // If styles is a string (from ?inline import in Vite builds), use it directly\n if (typeof inlineStyles === 'string' && inlineStyles.length > 0) {\n baseStyles = inlineStyles;\n updateStyleElement();\n return;\n }\n\n // Fallback: styles is undefined (e.g., when imported in Angular from source without Vite processing)\n // Angular includes grid.css in global styles - extract it from document.styleSheets\n // Wait a bit for Angular to finish loading styles\n await new Promise((resolve) => setTimeout(resolve, 50));\n\n const gridCssText = extractGridCssFromDocument();\n\n if (gridCssText) {\n baseStyles = gridCssText;\n updateStyleElement();\n } else if (typeof process === 'undefined' || process.env?.['NODE_ENV'] !== 'test') {\n // Only warn in non-test environments - test environments (happy-dom, jsdom) don't load stylesheets\n warnDiagnostic(\n STYLE_NOT_FOUND,\n 'Could not find grid.css in document.styleSheets. Grid styling will not work. ' +\n `Available stylesheets: ${Array.from(document.styleSheets)\n .map((s) => s.href || '(inline)')\n .join(', ')}`,\n );\n }\n}\n// #endregion\n\n// #region Testing\n/**\n * Reset style injector state (for testing purposes only).\n * @internal\n */\nexport function _resetForTesting(): void {\n baseStyles = '';\n pluginStylesMap.clear();\n const styleEl = document.getElementById(STYLE_ELEMENT_ID);\n styleEl?.remove();\n}\n// #endregion\n","/**\n * Touch scrolling controller for mobile devices.\n *\n * Uses pointer events with setPointerCapture to ensure events are delivered\n * directly to the grid element, even when DOM virtualization replaces the\n * original touch target element mid-gesture.\n *\n * Without pointer capture, touch events are dispatched to the original target\n * element (e.g., a cell or card). When virtualization recycles that element's\n * content, the target is removed from the DOM and subsequent touchmove/touchend\n * events are lost — causing scrolling to stop after ~2 rows.\n *\n * setPointerCapture routes all events for a pointer ID directly to the capture\n * element (.tbw-grid-content), bypassing target resolution entirely.\n *\n * Uses incremental deltas (frame-to-frame) rather than absolute offsets from\n * the gesture start, which is more robust when virtualized content height\n * changes during scrolling (e.g., responsive card mode with variable-height rows).\n */\n\n// #region Types\nexport interface TouchScrollState {\n startY: number | null;\n startX: number | null;\n lastY: number | null;\n lastX: number | null;\n lastTime: number | null;\n velocityY: number;\n velocityX: number;\n momentumRaf: number;\n /** Once we start scrolling the grid, lock until gesture ends to prevent browser takeover */\n locked: boolean;\n /** Active pointer ID for setPointerCapture tracking (null = no active gesture) */\n activePointerId: number | null;\n}\n\nexport interface TouchScrollElements {\n fauxScrollbar: HTMLElement;\n scrollArea: HTMLElement | null;\n}\n// #endregion\n\n// #region State Management\n/**\n * Create initial touch scroll state.\n */\nexport function createTouchScrollState(): TouchScrollState {\n return {\n startY: null,\n startX: null,\n lastY: null,\n lastX: null,\n lastTime: null,\n velocityY: 0,\n velocityX: 0,\n momentumRaf: 0,\n locked: false,\n activePointerId: null,\n };\n}\n\n/**\n * Reset touch scroll state (called on touchend or cleanup).\n */\nexport function resetTouchState(state: TouchScrollState): void {\n state.startY = null;\n state.startX = null;\n state.lastY = null;\n state.lastX = null;\n state.lastTime = null;\n state.locked = false;\n}\n\n/**\n * Cancel any ongoing momentum animation.\n */\nexport function cancelMomentum(state: TouchScrollState): void {\n if (state.momentumRaf) {\n cancelAnimationFrame(state.momentumRaf);\n state.momentumRaf = 0;\n }\n}\n// #endregion\n\n// #region Touch Handlers\n/**\n * Handle gesture start (from pointerdown).\n */\nexport function handleTouchStart(clientX: number, clientY: number, state: TouchScrollState): void {\n // Cancel any ongoing momentum animation\n cancelMomentum(state);\n\n state.startY = clientY;\n state.startX = clientX;\n state.lastY = clientY;\n state.lastX = clientX;\n state.lastTime = performance.now();\n state.velocityY = 0;\n state.velocityX = 0;\n state.locked = false;\n}\n\n/**\n * Handle gesture move (from pointermove).\n * Uses incremental deltas (from previous position) for robustness.\n * Returns true if the event should be prevented (grid is handling scroll).\n */\nexport function handleTouchMove(clientX: number, clientY: number, state: TouchScrollState, elements: TouchScrollElements): boolean {\n if (state.lastY === null || state.lastX === null) {\n return false;\n }\n\n const now = performance.now();\n\n // Incremental delta since last move (not from gesture start)\n const incrY = state.lastY - clientY;\n const incrX = state.lastX - clientX;\n\n // Calculate velocity for momentum scrolling\n if (state.lastTime !== null) {\n const dt = now - state.lastTime;\n if (dt > 0) {\n state.velocityY = incrY / dt;\n state.velocityX = incrX / dt;\n }\n }\n state.lastY = clientY;\n state.lastX = clientX;\n state.lastTime = now;\n\n // If already locked to grid scrolling, keep preventing default\n if (state.locked) {\n elements.fauxScrollbar.scrollTop += incrY;\n if (elements.scrollArea) {\n elements.scrollArea.scrollLeft += incrX;\n }\n return true;\n }\n\n // Determine scroll direction on first significant move (> 3px from start)\n const totalDeltaY = state.startY !== null ? Math.abs(state.startY - clientY) : 0;\n const totalDeltaX = state.startX !== null ? Math.abs(state.startX - clientX) : 0;\n if (totalDeltaY < 3 && totalDeltaX < 3) return false;\n\n // Determine primary gesture direction from total delta, not micro-frame delta.\n // Using totalDelta avoids mis-detection from finger jitter between frames.\n const isVerticalGesture = totalDeltaY >= totalDeltaX;\n\n // Check if grid has scrollable content (don't check boundary position —\n // the browser clamps scrollTop automatically, and checking boundaries here\n // causes the lock to fail when momentum brought us to an edge).\n const { scrollHeight, clientHeight } = elements.fauxScrollbar;\n const hasVerticalScroll = scrollHeight - clientHeight > 0;\n\n let hasHorizontalScroll = false;\n if (elements.scrollArea) {\n const { scrollWidth, clientWidth } = elements.scrollArea;\n hasHorizontalScroll = scrollWidth - clientWidth > 0;\n }\n\n if ((isVerticalGesture && hasVerticalScroll) || (!isVerticalGesture && hasHorizontalScroll)) {\n // Lock to grid scrolling for the rest of this gesture\n state.locked = true;\n elements.fauxScrollbar.scrollTop += incrY;\n if (elements.scrollArea) {\n elements.scrollArea.scrollLeft += incrX;\n }\n return true;\n }\n\n // Grid can't scroll in this direction — let the page scroll\n return false;\n}\n\n/**\n * Handle touchend event.\n * Starts momentum scrolling if velocity is significant.\n */\nexport function handleTouchEnd(state: TouchScrollState, elements: TouchScrollElements): void {\n const minVelocity = 0.1; // pixels per ms threshold\n\n // Start momentum scrolling if there's significant velocity\n if (Math.abs(state.velocityY) > minVelocity || Math.abs(state.velocityX) > minVelocity) {\n startMomentumScroll(state, elements);\n }\n\n resetTouchState(state);\n}\n// #endregion\n\n// #region Momentum Scrolling\n/**\n * Start momentum scrolling animation.\n */\nfunction startMomentumScroll(state: TouchScrollState, elements: TouchScrollElements): void {\n const friction = 0.95; // Deceleration factor per frame\n const minVelocity = 0.01; // Stop threshold in px/ms\n\n const animate = () => {\n // Apply friction\n state.velocityY *= friction;\n state.velocityX *= friction;\n\n // Convert velocity (px/ms) to per-frame scroll amount (~16ms per frame)\n const scrollY = state.velocityY * 16;\n const scrollX = state.velocityX * 16;\n\n // Apply scroll if above threshold\n if (Math.abs(state.velocityY) > minVelocity) {\n elements.fauxScrollbar.scrollTop += scrollY;\n }\n if (Math.abs(state.velocityX) > minVelocity && elements.scrollArea) {\n elements.scrollArea.scrollLeft += scrollX;\n }\n\n // Continue animation if still moving\n if (Math.abs(state.velocityY) > minVelocity || Math.abs(state.velocityX) > minVelocity) {\n state.momentumRaf = requestAnimationFrame(animate);\n } else {\n state.momentumRaf = 0;\n }\n };\n\n state.momentumRaf = requestAnimationFrame(animate);\n}\n// #endregion\n\n// #region Setup\n/**\n * Set up pointer event listeners on the grid content element for touch scrolling.\n *\n * Uses pointer events + setPointerCapture instead of touch events because\n * DOM virtualization can destroy the original touch target mid-gesture,\n * causing touch events to stop being delivered. Pointer capture routes\n * all events directly to the grid element regardless of DOM changes.\n */\nexport function setupTouchScrollListeners(\n gridContentEl: HTMLElement,\n state: TouchScrollState,\n elements: TouchScrollElements,\n signal: AbortSignal,\n): void {\n gridContentEl.addEventListener(\n 'pointerdown',\n (e: PointerEvent) => {\n // Only handle touch (not mouse/pen), and only one finger at a time\n if (e.pointerType !== 'touch' || state.activePointerId !== null) return;\n state.activePointerId = e.pointerId;\n gridContentEl.setPointerCapture(e.pointerId);\n handleTouchStart(e.clientX, e.clientY, state);\n },\n { passive: true, signal },\n );\n\n gridContentEl.addEventListener(\n 'pointermove',\n (e: PointerEvent) => {\n if (e.pointerId !== state.activePointerId) return;\n const shouldPrevent = handleTouchMove(e.clientX, e.clientY, state, elements);\n if (shouldPrevent) {\n e.preventDefault();\n }\n },\n { passive: false, signal },\n );\n\n gridContentEl.addEventListener(\n 'pointerup',\n (e: PointerEvent) => {\n if (e.pointerId !== state.activePointerId) return;\n state.activePointerId = null;\n handleTouchEnd(state, elements);\n },\n { passive: true, signal },\n );\n\n gridContentEl.addEventListener(\n 'pointercancel',\n (e: PointerEvent) => {\n if (e.pointerId !== state.activePointerId) return;\n state.activePointerId = null;\n resetTouchState(state);\n },\n { passive: true, signal },\n );\n\n // Safety net: if pointer capture is lost unexpectedly, clean up\n gridContentEl.addEventListener(\n 'lostpointercapture',\n (e: PointerEvent) => {\n if (e.pointerId !== state.activePointerId) return;\n state.activePointerId = null;\n resetTouchState(state);\n },\n { passive: true, signal },\n );\n}\n// #endregion\n","/**\n * Configuration Validation\n *\n * Runtime validators that check for plugin-specific properties in config\n * and throw helpful errors if the required plugin is not loaded.\n *\n * This catches common mistakes like using `editable: true` without EditingPlugin.\n *\n * Uses a static registry of known plugin-owned properties to detect when users\n * configure features that require plugins they haven't loaded.\n */\n\nimport type { BaseGridPlugin, PluginManifest, PluginPropertyDefinition } from '../plugin';\nimport type { ColumnConfig, GridConfig } from '../types';\nimport {\n CONFIG_RULE_ERROR,\n CONFIG_RULE_WARN,\n INCOMPATIBLE_PLUGINS,\n MISSING_DEPENDENCY,\n MISSING_PLUGIN,\n MISSING_PLUGIN_CONFIG,\n OPTIONAL_DEPENDENCY,\n debugDiagnostic,\n throwDiagnostic,\n warnDiagnostic,\n} from './diagnostics';\nimport { isDevelopment } from './utils';\n\n/**\n * Internal property definition with plugin name attached.\n * Extends PluginPropertyDefinition with required pluginName for validation.\n */\ninterface InternalPropertyDefinition extends PluginPropertyDefinition {\n pluginName: string;\n}\n\n// #region Known Properties Registry\n/**\n * Static registry of known plugin-owned column properties.\n * This enables detection of plugin properties even when the plugin isn't loaded.\n * Properties defined here allow helpful error messages when plugins are missing.\n *\n * ## Why This Exists (The Validation Paradox)\n *\n * We need to detect when a developer uses a plugin-owned property (like `editable`)\n * but forgets to add the plugin. However, if the plugin isn't loaded, we can't\n * read its manifest! The manifest only exists when the plugin class is imported.\n *\n * This static registry solves that: it's a \"well-known properties\" list that exists\n * independently of whether plugins are loaded.\n *\n * ## When Adding New Plugin-Owned Properties\n *\n * 1. **Always**: Add to the plugin's manifest `ownedProperties` (documentation, lives with plugin)\n * 2. **Optionally**: Add here if you want \"forgot to add plugin\" detection for that property\n *\n * Not every property needs to be here - only high-value ones where developers commonly\n * forget to add the plugin. Third-party plugins can't be listed here anyway.\n *\n * ## Future Improvement\n *\n * A build-time script could generate these arrays from plugin manifests,\n * creating a single source of truth. For now, they're maintained manually.\n */\nconst KNOWN_COLUMN_PROPERTIES: InternalPropertyDefinition[] = [\n // EditingPlugin\n {\n property: 'editable',\n pluginName: 'editing',\n level: 'column',\n description: 'the \"editable\" column property',\n isUsed: (v) => v === true || typeof v === 'function',\n },\n {\n property: 'editor',\n pluginName: 'editing',\n level: 'column',\n description: 'the \"editor\" column property',\n },\n {\n property: 'editorParams',\n pluginName: 'editing',\n level: 'column',\n description: 'the \"editorParams\" column property',\n },\n // GroupingColumnsPlugin\n {\n property: 'group',\n pluginName: 'groupingColumns',\n level: 'column',\n description: 'the \"group\" column property',\n },\n // PinnedColumnsPlugin\n {\n property: 'pinned',\n pluginName: 'pinnedColumns',\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 pluginName: 'pinnedColumns',\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\n/**\n * Static registry of known plugin-owned grid config properties.\n */\nconst KNOWN_CONFIG_PROPERTIES: InternalPropertyDefinition[] = [\n // EditingPlugin\n {\n property: 'rowEditable',\n pluginName: 'editing',\n level: 'config',\n description: 'the \"rowEditable\" config property',\n isUsed: (v) => typeof v === 'function',\n },\n // GroupingColumnsPlugin\n {\n property: 'columnGroups',\n pluginName: 'groupingColumns',\n level: 'config',\n description: 'the \"columnGroups\" config property',\n isUsed: (v) => Array.isArray(v) && v.length > 0,\n },\n];\n// #endregion\n\n// #region Import Hints\n/**\n * Convert a camelCase plugin name to kebab-case for import paths.\n * e.g. 'groupingRows' → 'grouping-rows', 'editing' → 'editing'\n */\nfunction toKebabCase(s: string): string {\n return s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);\n}\n\n/**\n * Generate the import hint for a plugin from its name.\n * e.g. 'editing' → \"import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';\"\n */\nfunction getImportHint(pluginName: string): string {\n return `import { ${capitalize(pluginName)}Plugin } from '@toolbox-web/grid/plugins/${toKebabCase(pluginName)}';`;\n}\n// #endregion\n\n// #region Development Mode\n// #endregion\n\n// #region Helper Functions\n/**\n * Helper to capitalize a plugin name for display.\n */\nfunction capitalize(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n\n/**\n * Check if a plugin with the given name is present in the plugins array.\n */\nfunction hasPlugin(plugins: readonly BaseGridPlugin[], pluginName: string): boolean {\n return plugins.some((p) => p.name === pluginName);\n}\n// #endregion\n\n// #region Property Validation\n/**\n * Validate that column properties requiring plugins have those plugins loaded.\n *\n * @param config - The merged grid configuration\n * @param plugins - The array of loaded plugins\n * @throws Error if a plugin-owned property is used without the plugin\n */\nexport function validatePluginProperties<T>(\n config: GridConfig<T>,\n plugins: readonly BaseGridPlugin[],\n gridId?: string,\n): void {\n // Use static registries of known plugin-owned properties\n const columnProps = KNOWN_COLUMN_PROPERTIES;\n const configProps = KNOWN_CONFIG_PROPERTIES;\n\n // Group errors by plugin to avoid spamming multiple errors\n const missingPlugins = new Map<\n string,\n { description: string; importHint: string; fields: string[]; isConfigProperty?: boolean }\n >();\n\n // Helper to add an error for a missing plugin\n function addError(\n pluginName: string,\n description: string,\n importHint: string,\n field: string,\n isConfigProperty = false,\n ) {\n if (!missingPlugins.has(pluginName)) {\n missingPlugins.set(pluginName, { description, importHint, fields: [], isConfigProperty });\n }\n // Entry is guaranteed to exist after the set above\n const entry = missingPlugins.get(pluginName)!;\n if (!entry.fields.includes(field)) {\n entry.fields.push(field);\n }\n }\n\n // Validate grid config properties\n for (const def of configProps) {\n const value = (config as Record<string, unknown>)[def.property];\n const isUsed = def.isUsed ? def.isUsed(value) : value !== undefined;\n\n if (isUsed && !hasPlugin(plugins, def.pluginName)) {\n addError(def.pluginName, def.description, getImportHint(def.pluginName), def.property, true);\n }\n }\n\n // Validate column properties\n const columns = config.columns;\n if (columns && columns.length > 0) {\n for (const column of columns) {\n for (const def of columnProps) {\n const value = (column as unknown as Record<string, unknown>)[def.property];\n // Use custom isUsed check if provided, otherwise check for defined value\n const isUsed = def.isUsed ? def.isUsed(value) : value !== undefined;\n\n if (isUsed && !hasPlugin(plugins, def.pluginName)) {\n const field = (column as ColumnConfig).field || '<unknown>';\n addError(def.pluginName, def.description, getImportHint(def.pluginName), field);\n }\n }\n }\n }\n\n // Throw a single consolidated error if any missing plugins\n if (missingPlugins.size > 0) {\n const errors: string[] = [];\n for (const [pluginName, { description, importHint, fields, isConfigProperty }] of missingPlugins) {\n if (isConfigProperty) {\n // Config-level property error\n errors.push(\n `Config uses ${description}, but the required plugin is not loaded.\\n` +\n ` → Add the plugin to your gridConfig.plugins array:\\n` +\n ` ${importHint}\\n` +\n ` plugins: [new ${capitalize(pluginName)}Plugin(), ...]`,\n );\n } else {\n // Column-level property error\n const fieldList = fields.slice(0, 3).join(', ') + (fields.length > 3 ? `, ... (${fields.length} total)` : '');\n errors.push(\n `Column(s) [${fieldList}] use ${description}, but the required plugin is not loaded.\\n` +\n ` → Add the plugin to your gridConfig.plugins array:\\n` +\n ` ${importHint}\\n` +\n ` plugins: [new ${capitalize(pluginName)}Plugin(), ...]`,\n );\n }\n }\n\n // Use MISSING_PLUGIN for column-level errors, MISSING_PLUGIN_CONFIG for config-level\n const code = [...missingPlugins.values()].some((e) => e.isConfigProperty) ? MISSING_PLUGIN_CONFIG : MISSING_PLUGIN;\n throwDiagnostic(\n code,\n `Configuration error:\\n\\n${errors.join('\\n\\n')}\\n\\n` +\n `This validation helps catch misconfigurations early. ` +\n `The properties listed above require their respective plugins to function.`,\n gridId,\n );\n }\n}\n// #endregion\n\n// #region Config Rules Validation\n/**\n * Validate plugin configuration rules declared in manifests.\n * Called after plugins are attached to check for invalid/conflicting configurations.\n *\n * Rules with severity 'error' throw an error.\n * Rules with severity 'warn' log a warning to console.\n *\n * @param plugins - The array of attached plugins (with config already merged)\n */\nexport function validatePluginConfigRules(plugins: readonly BaseGridPlugin[], gridId?: string): void {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n for (const plugin of plugins) {\n const PluginClass = plugin.constructor as typeof BaseGridPlugin;\n const manifest = PluginClass.manifest as PluginManifest | undefined;\n if (!manifest?.configRules) continue;\n\n for (const rule of manifest.configRules) {\n // Access plugin's merged config via protected property\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const pluginConfig = (plugin as any).config;\n if (rule.check(pluginConfig)) {\n const formatted = `[${capitalize(plugin.name)}Plugin] Configuration warning: ${rule.message}`;\n if (rule.severity === 'error') {\n errors.push(formatted);\n } else {\n warnings.push(formatted);\n }\n }\n }\n }\n\n // Log warnings only in development (don't pollute production logs)\n if (warnings.length > 0 && isDevelopment()) {\n for (const warning of warnings) {\n warnDiagnostic(CONFIG_RULE_WARN, warning, gridId);\n }\n }\n\n // Throw consolidated error if any (always, regardless of environment)\n if (errors.length > 0) {\n throwDiagnostic(CONFIG_RULE_ERROR, `Configuration error:\\n\\n${errors.join('\\n\\n')}`, gridId);\n }\n}\n// #endregion\n\n// #region Dependency Validation\n/**\n * Validate plugin-to-plugin dependencies.\n * Called by PluginManager when attaching a new plugin.\n *\n * Dependencies are read from the plugin's static `dependencies` property.\n *\n * For hard dependencies (required: true), throws an error if the dependency is not loaded.\n * For soft dependencies (required: false), logs an info message but continues.\n *\n * @param plugin - The plugin instance being attached\n * @param loadedPlugins - The array of already-loaded plugins\n * @throws Error if a required dependency is missing\n */\nexport function validatePluginDependencies(\n plugin: BaseGridPlugin,\n loadedPlugins: readonly BaseGridPlugin[],\n gridId?: string,\n): void {\n const pluginName = plugin.name;\n const PluginClass = plugin.constructor as typeof BaseGridPlugin;\n\n // Get dependencies from plugin's static property\n const dependencies = PluginClass.dependencies ?? [];\n\n // Validate each dependency\n for (const dep of dependencies) {\n const requiredPlugin = dep.name;\n const required = dep.required ?? true; // Default to required\n const reason = dep.reason;\n const hasRequired = loadedPlugins.some((p) => p.name === requiredPlugin);\n\n if (!hasRequired) {\n const reasonText = reason ?? `${capitalize(pluginName)}Plugin requires ${capitalize(requiredPlugin)}Plugin`;\n const importHint = getImportHint(requiredPlugin);\n\n if (required) {\n throwDiagnostic(\n MISSING_DEPENDENCY,\n `Plugin dependency error:\\n\\n` +\n `${reasonText}.\\n\\n` +\n ` → Add the plugin to your gridConfig.plugins array BEFORE ${capitalize(pluginName)}Plugin:\\n` +\n ` ${importHint}\\n` +\n ` plugins: [new ${capitalize(requiredPlugin)}Plugin(), new ${capitalize(pluginName)}Plugin()]`,\n gridId,\n );\n } else {\n // Soft dependency - log info message but continue\n debugDiagnostic(\n OPTIONAL_DEPENDENCY,\n `${capitalize(pluginName)}Plugin: Optional \"${requiredPlugin}\" plugin not found. ` +\n `Some features may be unavailable.`,\n gridId,\n );\n }\n }\n }\n}\n// #endregion\n\n// #region Incompatibility Validation\n/**\n * Validate that no incompatible plugins are loaded together.\n * Called after all plugins are attached to the grid.\n *\n * Incompatibilities are read from each plugin's manifest `incompatibleWith` property.\n * When a conflict is detected, a warning is logged (in development mode).\n *\n * @param plugins - All attached plugins\n */\nexport function validatePluginIncompatibilities(plugins: readonly BaseGridPlugin[], gridId?: string): void {\n // Only warn in development mode to avoid polluting production logs\n if (!isDevelopment()) return;\n\n const pluginNames = new Set(plugins.map((p) => p.name));\n const warned = new Set<string>(); // Avoid duplicate warnings for symmetric conflicts\n\n for (const plugin of plugins) {\n const PluginClass = plugin.constructor as typeof BaseGridPlugin;\n const manifest = PluginClass.manifest as PluginManifest | undefined;\n if (!manifest?.incompatibleWith) continue;\n\n for (const incompatibility of manifest.incompatibleWith) {\n if (pluginNames.has(incompatibility.name)) {\n // Create a symmetric key to avoid warning twice (A→B and B→A)\n const key = [plugin.name, incompatibility.name].sort().join('↔');\n if (warned.has(key)) continue;\n warned.add(key);\n\n warnDiagnostic(\n INCOMPATIBLE_PLUGINS,\n `${capitalize(plugin.name)}Plugin and ${capitalize(incompatibility.name)}Plugin are both loaded, ` +\n `but they are currently incompatible.\\n\\n` +\n ` → ${incompatibility.reason}\\n\\n` +\n ` Consider removing one of these plugins to avoid unexpected behavior.`,\n gridId,\n );\n }\n }\n }\n}\n// #endregion\n","/**\n * Row Virtualization Module\n *\n * Provides all virtualization-related functionality for the grid:\n *\n * 1. **Position Cache** (index-based): Maps row index → {offset, height, measured}\n * - Rebuilt when row count changes (expand/collapse, filter)\n * - Used for scroll position → row index lookups (binary search)\n *\n * 2. **Height Cache** (identity-based): Maps row identity → height\n * - Persists across expand/collapse\n * - Uses rowId string keys when available, WeakMap otherwise\n * - Synthetic rows use __rowCacheKey for stable identity\n *\n * 3. **Virtual Window**: Computes which rows to render based on scroll position\n *\n * 4. **Row Measurement**: Measures rendered row heights from DOM\n */\n\n// #region Types\n\n/**\n * Position entry for a single row in the position cache.\n * @category Plugin Development\n */\nexport interface RowPosition {\n /** Cumulative offset from top in pixels */\n offset: number;\n /** Row height in pixels */\n height: number;\n /** Whether this is a measured value (true) or estimate (false) */\n measured: boolean;\n}\n\n/**\n * Height cache that persists row heights across position cache rebuilds.\n * Uses dual storage: Map for string keys (rowId, __rowCacheKey) and WeakMap for object refs.\n * @category Plugin Development\n */\nexport interface HeightCache {\n /** Heights keyed by string (for synthetic rows with __rowCacheKey or rowId-keyed rows) */\n byKey: Map<string, number>;\n /** Heights keyed by object reference (for data rows without rowId) */\n byRef: WeakMap<object, number>;\n}\n\n/**\n * Configuration for the position cache.\n */\nexport interface PositionCacheConfig<T = unknown> {\n /** Function to get row ID (if configured) */\n rowId?: (row: T) => string | number;\n}\n\n// #endregion\n\n// #region Height Cache Operations\n\n/**\n * Create a new empty height cache.\n */\nexport function createHeightCache(): HeightCache {\n return {\n byKey: new Map(),\n byRef: new WeakMap(),\n };\n}\n\n/**\n * Get the cache key for a row.\n * Returns string for synthetic rows (__rowCacheKey) or rowId-keyed rows,\n * or the row object itself for WeakMap lookup.\n */\nexport function getRowCacheKey<T>(row: T, rowId?: (row: T) => string | number): string | T {\n if (!row || typeof row !== 'object') return row;\n\n // 1. Synthetic rows: plugins MUST add __rowCacheKey\n if ('__rowCacheKey' in row) {\n return (row as { __rowCacheKey: string }).__rowCacheKey;\n }\n\n // 2. rowId property directly on the row (common pattern)\n if ('rowId' in row && (row as { rowId: string | number }).rowId != null) {\n return `id:${(row as { rowId: string | number }).rowId}`;\n }\n\n // 3. User-provided rowId function (if configured)\n if (rowId) {\n return `id:${rowId(row)}`;\n }\n\n // 4. Object identity (for WeakMap)\n return row;\n}\n\n/**\n * Get cached height for a row.\n * @returns Cached height or undefined if not cached\n */\nexport function getCachedHeight<T>(\n cache: HeightCache,\n row: T,\n rowId?: (row: T) => string | number,\n): number | undefined {\n const key = getRowCacheKey(row, rowId);\n\n if (typeof key === 'string') {\n return cache.byKey.get(key);\n }\n\n // Object key - use WeakMap\n if (key && typeof key === 'object') {\n return cache.byRef.get(key);\n }\n\n return undefined;\n}\n\n/**\n * Set cached height for a row.\n */\nexport function setCachedHeight<T>(\n cache: HeightCache,\n row: T,\n height: number,\n rowId?: (row: T) => string | number,\n): void {\n const key = getRowCacheKey(row, rowId);\n\n if (typeof key === 'string') {\n cache.byKey.set(key, height);\n } else if (key && typeof key === 'object') {\n cache.byRef.set(key, height);\n }\n}\n\n// #endregion\n\n// #region Position Cache Operations\n\n/**\n * Rebuild position cache preserving known heights from height cache.\n * Called when row count changes (expand/collapse, filter, data change).\n *\n * @param rows - Array of row data\n * @param heightCache - Height cache with persisted measurements\n * @param estimatedHeight - Estimated height for unmeasured rows\n * @param config - Position cache configuration\n * @param getPluginHeight - Optional function to get height from plugins\n * @returns New position cache\n */\nexport function rebuildPositionCache<T>(\n rows: T[],\n heightCache: HeightCache,\n estimatedHeight: number,\n config: PositionCacheConfig<T>,\n getPluginHeight?: (row: T, index: number) => number | undefined,\n): RowPosition[] {\n const cache: RowPosition[] = new Array(rows.length);\n let offset = 0;\n\n for (let i = 0; i < rows.length; i++) {\n const row = rows[i];\n\n // Height resolution order:\n // 1. Plugin's getRowHeight() (for synthetic rows with known heights)\n let height = getPluginHeight?.(row, i);\n let measured = height !== undefined;\n\n // 2. Cached height from previous measurements\n if (height === undefined) {\n height = getCachedHeight(heightCache, row, config.rowId);\n measured = height !== undefined;\n }\n\n // 3. Fall back to estimate\n if (height === undefined) {\n height = estimatedHeight;\n measured = false;\n }\n\n cache[i] = { offset, height, measured };\n offset += height;\n }\n\n return cache;\n}\n\n/**\n * Update a single row's height in the position cache.\n * Recalculates offsets for all subsequent rows.\n *\n * @param cache - Position cache to update\n * @param index - Row index to update\n * @param newHeight - New measured height\n */\nexport function updateRowHeight(cache: RowPosition[], index: number, newHeight: number): void {\n if (index < 0 || index >= cache.length) return;\n\n const entry = cache[index];\n const heightDiff = newHeight - entry.height;\n\n if (heightDiff === 0) return;\n\n // Update this row\n entry.height = newHeight;\n entry.measured = true;\n\n // Recalculate offsets for all subsequent rows\n for (let i = index + 1; i < cache.length; i++) {\n cache[i].offset += heightDiff;\n }\n}\n\n/**\n * Get total content height from position cache.\n *\n * @param cache - Position cache\n * @returns Total height in pixels\n */\nexport function getTotalHeight(cache: RowPosition[]): number {\n if (cache.length === 0) return 0;\n const last = cache[cache.length - 1];\n return last.offset + last.height;\n}\n\n// #endregion\n\n// #region Binary Search\n\n/**\n * Find the row index at a given scroll offset using binary search.\n * Returns the index of the row that contains the given pixel offset.\n *\n * @param cache - Position cache\n * @param targetOffset - Scroll offset in pixels\n * @returns Row index at that offset, or -1 if cache is empty\n */\nexport function getRowIndexAtOffset(cache: RowPosition[], targetOffset: number): number {\n if (cache.length === 0) return -1;\n if (targetOffset <= 0) return 0;\n\n let low = 0;\n let high = cache.length - 1;\n\n while (low <= high) {\n const mid = Math.floor((low + high) / 2);\n const entry = cache[mid];\n const entryEnd = entry.offset + entry.height;\n\n if (targetOffset < entry.offset) {\n high = mid - 1;\n } else if (targetOffset >= entryEnd) {\n low = mid + 1;\n } else {\n // targetOffset is within this row\n return mid;\n }\n }\n\n // Return the closest row (low will be just past the target)\n return Math.max(0, Math.min(low, cache.length - 1));\n}\n\n// #endregion\n\n// #region Statistics\n\n/**\n * Calculate the average measured height.\n * Used for estimating unmeasured rows.\n *\n * @param cache - Position cache\n * @param defaultHeight - Default height to use if no measurements\n * @returns Average measured height\n */\nexport function calculateAverageHeight(cache: RowPosition[], defaultHeight: number): number {\n let totalHeight = 0;\n let measuredCount = 0;\n\n for (const entry of cache) {\n if (entry.measured) {\n totalHeight += entry.height;\n measuredCount++;\n }\n }\n\n return measuredCount > 0 ? totalHeight / measuredCount : defaultHeight;\n}\n\n/**\n * Count how many rows have been measured.\n *\n * @param cache - Position cache\n * @returns Number of measured rows\n */\nexport function countMeasuredRows(cache: RowPosition[]): number {\n let count = 0;\n for (const entry of cache) {\n if (entry.measured) count++;\n }\n return count;\n}\n\n// #endregion\n// #region Row Measurement\n\n/**\n * Result of measuring rendered row heights.\n */\nexport interface RowMeasurementResult {\n /** Whether any heights changed */\n hasChanges: boolean;\n /** Updated measured row count */\n measuredCount: number;\n /** Updated average height */\n averageHeight: number;\n}\n\n/**\n * Context for measuring rendered rows.\n */\nexport interface RowMeasurementContext<T> {\n /** Position cache to update */\n positionCache: RowPosition[];\n /** Height cache for persistence */\n heightCache: HeightCache;\n /** Row data array */\n rows: T[];\n /** Default row height */\n defaultHeight: number;\n /** Start index of rendered window */\n start: number;\n /** End index of rendered window (exclusive) */\n end: number;\n /** Function to get plugin height for a row */\n getPluginHeight?: (row: T, index: number) => number | undefined;\n /** Function to get row ID for height cache keying */\n getRowId?: (row: T) => string | number;\n}\n\n/**\n * Measure rendered row heights from DOM elements and update caches.\n * Returns measurement statistics for updating virtualization state.\n *\n * @param context - Measurement context with all dependencies\n * @param rowElements - NodeList of rendered row elements\n * @returns Measurement result with flags and statistics\n */\nexport function measureRenderedRowHeights<T>(\n context: RowMeasurementContext<T>,\n rowElements: NodeListOf<Element>,\n): RowMeasurementResult {\n const { positionCache, heightCache, rows, start, end, getPluginHeight, getRowId } = context;\n\n let hasChanges = false;\n\n rowElements.forEach((rowEl) => {\n const rowIndexStr = (rowEl as HTMLElement).dataset.rowIndex;\n if (!rowIndexStr) return;\n\n const rowIndex = parseInt(rowIndexStr, 10);\n if (rowIndex < start || rowIndex >= end || rowIndex >= rows.length) return;\n\n const row = rows[rowIndex];\n\n // Check if a plugin provides the height for this row\n const pluginHeight = getPluginHeight?.(row, rowIndex);\n\n if (pluginHeight !== undefined) {\n // Plugin provides height - use it for position cache\n const currentEntry = positionCache[rowIndex];\n if (!currentEntry.measured || Math.abs(currentEntry.height - pluginHeight) > 1) {\n updateRowHeight(positionCache, rowIndex, pluginHeight);\n hasChanges = true;\n }\n return; // Don't measure DOM for plugin-managed rows\n }\n\n // No plugin height - use DOM measurement\n const measuredHeight = (rowEl as HTMLElement).offsetHeight;\n\n if (measuredHeight > 0) {\n const currentEntry = positionCache[rowIndex];\n\n // Only update if height differs significantly (> 1px to avoid oscillation)\n if (!currentEntry.measured || Math.abs(currentEntry.height - measuredHeight) > 1) {\n updateRowHeight(positionCache, rowIndex, measuredHeight);\n setCachedHeight(heightCache, row, measuredHeight, getRowId);\n hasChanges = true;\n }\n }\n });\n\n // Recompute stats\n const measuredCount = hasChanges ? countMeasuredRows(positionCache) : 0;\n const averageHeight = hasChanges ? calculateAverageHeight(positionCache, context.defaultHeight) : 0;\n\n return { hasChanges, measuredCount, averageHeight };\n}\n\n/**\n * Compute measurement statistics for a position cache, excluding plugin-managed rows.\n * Used after rebuilding position cache to get accurate averages for non-plugin rows.\n *\n * @param cache - Position cache\n * @param rows - Row data array\n * @param defaultHeight - Default height if no measurements\n * @param getPluginHeight - Function to check if plugin manages row height\n * @returns Object with measured count and average height\n */\nexport function computeAverageExcludingPluginRows<T>(\n cache: RowPosition[],\n rows: T[],\n defaultHeight: number,\n getPluginHeight?: (row: T, index: number) => number | undefined,\n): { measuredCount: number; averageHeight: number } {\n let measuredCount = 0;\n let totalMeasured = 0;\n\n for (let i = 0; i < cache.length; i++) {\n const entry = cache[i];\n if (entry.measured) {\n // Only include in average if plugin doesn't provide height for this row\n const pluginHeight = getPluginHeight?.(rows[i], i);\n if (pluginHeight === undefined) {\n totalMeasured += entry.height;\n measuredCount++;\n }\n }\n }\n\n return {\n measuredCount,\n averageHeight: measuredCount > 0 ? totalMeasured / measuredCount : defaultHeight,\n };\n}\n\n// #endregion\n\n// #region Fixed-Height Virtual Window\n\n/**\n * Result of computing a virtual window for fixed-height rows.\n * Used by plugins like FilteringPlugin for virtualized dropdowns.\n */\nexport interface VirtualWindow {\n /** First row index to render (inclusive) */\n start: number;\n /** Last row index to render (exclusive) */\n end: number;\n /** Pixel offset to apply to the rows container (translateY) */\n offsetY: number;\n /** Total height of the scrollable content */\n totalHeight: number;\n}\n\n/** Parameters for computing the virtual window */\nexport interface VirtualWindowParams {\n /** Total number of rows */\n totalRows: number;\n /** Height of the viewport in pixels */\n viewportHeight: number;\n /** Current scroll top position */\n scrollTop: number;\n /** Height of each row in pixels */\n rowHeight: number;\n /** Number of extra rows to render above/below viewport */\n overscan: number;\n}\n\n/**\n * Compute the virtual row window based on scroll position and viewport.\n * Simple fixed-height implementation for plugins needing basic virtualization.\n *\n * @param params - Parameters for computing the window\n * @returns VirtualWindow with start/end indices and transforms\n */\nexport function computeVirtualWindow(params: VirtualWindowParams): VirtualWindow {\n const { totalRows, viewportHeight, scrollTop, rowHeight, overscan } = params;\n\n const visibleRows = Math.ceil(viewportHeight / rowHeight);\n\n // Render overscan rows in each direction (total window = visible + 2*overscan)\n let start = Math.floor(scrollTop / rowHeight) - overscan;\n if (start < 0) start = 0;\n\n let end = start + visibleRows + overscan * 2;\n if (end > totalRows) end = totalRows;\n\n // Ensure start is adjusted if we hit the end\n if (end === totalRows && start > 0) {\n start = Math.max(0, end - visibleRows - overscan * 2);\n }\n\n return {\n start,\n end,\n offsetY: start * rowHeight,\n totalHeight: totalRows * rowHeight,\n };\n}\n\n/**\n * Determine if virtualization should be bypassed for small datasets.\n * When there are very few items, the overhead of virtualization isn't worth it.\n *\n * @param totalRows - Total number of items\n * @param threshold - Bypass threshold (skip virtualization if totalRows <= threshold)\n * @returns True if virtualization should be bypassed\n */\nexport function shouldBypassVirtualization(totalRows: number, threshold: number): boolean {\n return totalRows <= threshold;\n}\n\n// #endregion\n","/**\n * VirtualizationManager — encapsulates all virtualization state and logic\n * that was previously inline in the DataGridElement class.\n *\n * Owns the VirtualState, position/height caches, and the core\n * refreshVirtualWindow algorithm. Takes the grid reference directly\n * (tightly coupled — this manager can never live outside the grid).\n */\nimport type { InternalGrid, VirtualState } from '../types';\nimport { RenderPhase } from './render-scheduler';\nimport {\n computeAverageExcludingPluginRows,\n getRowIndexAtOffset,\n getTotalHeight,\n measureRenderedRowHeights,\n rebuildPositionCache,\n updateRowHeight,\n} from './virtualization';\n\n// #region VirtualizationManager\n\nexport class VirtualizationManager<T = any> {\n readonly #grid: InternalGrid<T>;\n\n // The full virtualization state — still a plain object so plugins can read\n // fields directly via `grid._virtualization` (they access the same reference).\n readonly state: VirtualState;\n\n constructor(grid: InternalGrid<T>, initialState?: Partial<VirtualState>) {\n this.#grid = grid;\n this.state = {\n enabled: true,\n rowHeight: 28,\n bypassThreshold: 24,\n start: 0,\n end: 0,\n container: null,\n viewportEl: null,\n totalHeightEl: null,\n positionCache: null,\n heightCache: {\n byKey: new Map<string, number>(),\n byRef: new WeakMap<object, number>(),\n },\n averageHeight: 28,\n measuredCount: 0,\n variableHeights: false,\n cachedViewportHeight: 0,\n cachedFauxHeight: 0,\n cachedScrollAreaHeight: 0,\n scrollAreaEl: null,\n ...initialState,\n };\n }\n\n // #region Cached Geometry\n\n /**\n * Update cached viewport and faux scrollbar geometry.\n * Called by ResizeObserver and on force-refresh to avoid forced layout reads during scroll.\n */\n updateCachedGeometry(): void {\n const s = this.state;\n const fauxScrollbar = s.container;\n const viewportEl = s.viewportEl ?? fauxScrollbar;\n if (viewportEl) {\n s.cachedViewportHeight = viewportEl.clientHeight;\n }\n if (fauxScrollbar) {\n s.cachedFauxHeight = fauxScrollbar.clientHeight;\n }\n const scrollAreaEl = s.scrollAreaEl;\n if (scrollAreaEl) {\n s.cachedScrollAreaHeight = scrollAreaEl.clientHeight;\n }\n }\n\n // #endregion\n\n // #region Spacer Height\n\n /**\n * Calculate total height for the faux scrollbar spacer element.\n * Used by both bypass and virtualized rendering paths to ensure consistent scroll behavior.\n *\n * @param totalRows - Total number of rows to calculate height for\n * @param forceRead - When true, reads fresh geometry from DOM (used after structural changes).\n * When false, uses cached values from ResizeObserver to avoid forced synchronous layout.\n */\n calculateTotalSpacerHeight(totalRows: number, forceRead = false): number {\n const s = this.state;\n\n let fauxScrollHeight: number;\n let viewportHeight: number;\n let scrollAreaHeight: number;\n\n if (forceRead) {\n const fauxScrollbar = s.container ?? this.#grid._hostElement;\n const viewportEl = s.viewportEl ?? fauxScrollbar;\n const scrollAreaEl = s.scrollAreaEl;\n\n fauxScrollHeight = fauxScrollbar?.clientHeight ?? 0;\n viewportHeight = viewportEl?.clientHeight ?? 0;\n scrollAreaHeight = scrollAreaEl ? scrollAreaEl.clientHeight : fauxScrollHeight;\n\n s.cachedFauxHeight = fauxScrollHeight;\n s.cachedViewportHeight = viewportHeight;\n s.cachedScrollAreaHeight = scrollAreaHeight;\n } else {\n fauxScrollHeight = s.cachedFauxHeight;\n viewportHeight = s.cachedViewportHeight;\n scrollAreaHeight = s.cachedScrollAreaHeight || fauxScrollHeight;\n }\n\n const viewportHeightDiff = scrollAreaHeight - viewportHeight;\n const hScrollbarPadding = Math.max(0, fauxScrollHeight - scrollAreaHeight);\n\n let rowContentHeight: number;\n let pluginExtraHeight = 0;\n\n if (s.variableHeights && s.positionCache) {\n rowContentHeight = getTotalHeight(s.positionCache);\n } else {\n rowContentHeight = totalRows * s.rowHeight;\n pluginExtraHeight = this.#grid._getPluginExtraHeight();\n }\n\n return rowContentHeight + viewportHeightDiff + pluginExtraHeight + hScrollbarPadding;\n }\n\n // #endregion\n\n // #region Position Cache\n\n /**\n * Initialize or rebuild the position cache for variable row heights.\n * Called when rows change or variable heights mode is enabled.\n */\n initializePositionCache(): void {\n const s = this.state;\n if (!s.variableHeights) return;\n\n const grid = this.#grid;\n const rows = grid._rows;\n const estimatedHeight = s.rowHeight || 28;\n const rowHeightFn = grid.effectiveConfig?.rowHeight as\n | ((row: T, index: number) => number | undefined)\n | undefined;\n const getRowId = grid.effectiveConfig?.getRowId;\n const rowIdFn = getRowId ? (row: T) => getRowId(row) : undefined;\n\n s.positionCache = rebuildPositionCache(rows, s.heightCache, estimatedHeight, { rowId: rowIdFn }, (row, index) => {\n const pluginHeight = grid._getPluginRowHeight(row, index);\n if (pluginHeight !== undefined) return pluginHeight;\n if (rowHeightFn) {\n const height = rowHeightFn(row, index);\n if (height !== undefined && height > 0) return height;\n }\n return undefined;\n });\n\n const stats = computeAverageExcludingPluginRows(s.positionCache, rows, estimatedHeight, (row, index) =>\n grid._getPluginRowHeight(row, index),\n );\n s.measuredCount = stats.measuredCount;\n if (stats.measuredCount > 0) {\n s.averageHeight = stats.averageHeight;\n }\n }\n\n /**\n * Invalidate a row's height in the position cache.\n * Call this when a plugin changes a row's height (e.g., expanding/collapsing a detail panel).\n * Updates the position cache incrementally O(1) + offset recalc O(k) without a full rebuild.\n *\n * @param rowIndex - Index of the row whose height changed\n * @param newHeight - Optional new height. If not provided, queries plugins for height.\n */\n invalidateRowHeight(rowIndex: number, newHeight?: number): void {\n const s = this.state;\n if (!s.variableHeights) return;\n if (!s.positionCache) return;\n\n const rows = this.#grid._rows;\n if (rowIndex < 0 || rowIndex >= rows.length) return;\n\n const row = rows[rowIndex];\n\n let height = newHeight;\n if (height === undefined) {\n height = this.#grid._getPluginRowHeight(row, rowIndex);\n }\n if (height === undefined) {\n height = s.rowHeight;\n }\n\n const currentEntry = s.positionCache[rowIndex];\n if (!currentEntry || Math.abs(currentEntry.height - height) < 1) {\n return;\n }\n\n updateRowHeight(s.positionCache, rowIndex, height);\n\n if (s.totalHeightEl) {\n const newTotalHeight = this.calculateTotalSpacerHeight(rows.length);\n s.totalHeightEl.style.height = `${newTotalHeight}px`;\n }\n }\n\n // #endregion\n\n // #region Row Measurement\n\n /**\n * Measure rendered row heights and update position cache.\n * Called after rows are rendered to capture actual DOM heights.\n * Only runs when variable heights mode is enabled.\n */\n measureRenderedRowHeights(start: number, end: number): void {\n const s = this.state;\n if (!s.variableHeights) return;\n if (!s.positionCache) return;\n\n const grid = this.#grid;\n const bodyEl = grid._bodyEl;\n if (!bodyEl) return;\n\n const rowElements = bodyEl.querySelectorAll('.data-grid-row');\n const getRowId = grid.effectiveConfig?.getRowId;\n const rows = grid._rows;\n\n const result = measureRenderedRowHeights(\n {\n positionCache: s.positionCache,\n heightCache: s.heightCache,\n rows,\n defaultHeight: s.rowHeight,\n start,\n end,\n getPluginHeight: (row, index) => grid._getPluginRowHeight(row, index),\n getRowId: getRowId ? (row: T) => getRowId(row) : undefined,\n },\n rowElements,\n );\n\n if (result.hasChanges) {\n s.measuredCount = result.measuredCount;\n s.averageHeight = result.averageHeight;\n\n if (s.totalHeightEl) {\n const newTotalHeight = this.calculateTotalSpacerHeight(rows.length);\n s.totalHeightEl.style.height = `${newTotalHeight}px`;\n }\n }\n }\n\n // #endregion\n\n // #region Core Virtual Window\n\n /**\n * Core virtualization routine. Chooses between bypass (small datasets), grouped window rendering,\n * or standard row window rendering.\n * @param force - Whether to force a full refresh (not just scroll update)\n * @param skipAfterRender - When true, skip calling afterRender (used by scheduler which calls it separately)\n * @returns Whether the visible row window changed (start/end differ from previous)\n */\n refreshVirtualWindow(force = false, skipAfterRender = false): boolean {\n const s = this.state;\n const grid = this.#grid;\n const bodyEl = grid._bodyEl;\n if (!bodyEl) return false;\n\n const totalRows = grid._rows.length;\n\n if (!s.enabled) {\n grid._renderVisibleRows(0, totalRows);\n if (!skipAfterRender) {\n grid._afterPluginRender();\n }\n return true;\n }\n\n if (totalRows <= s.bypassThreshold) {\n s.start = 0;\n s.end = totalRows;\n if (force) {\n bodyEl.style.transform = 'translateY(0px)';\n }\n grid._renderVisibleRows(0, totalRows, grid.__rowRenderEpoch);\n if (force && s.variableHeights) {\n this.initializePositionCache();\n }\n if (force && s.totalHeightEl) {\n s.totalHeightEl.style.height = `${this.calculateTotalSpacerHeight(totalRows, true)}px`;\n }\n grid._updateAriaCounts(totalRows, grid._visibleColumns.length);\n if (!skipAfterRender) {\n grid._afterPluginRender();\n }\n return true;\n }\n\n // --- Normal virtualization path with faux scrollbar pattern ---\n const fauxScrollbar = s.container!;\n const viewportEl = s.viewportEl ?? fauxScrollbar;\n\n const viewportHeight = force\n ? (s.cachedViewportHeight = viewportEl.clientHeight)\n : s.cachedViewportHeight || (s.cachedViewportHeight = viewportEl.clientHeight);\n const rowHeight = s.rowHeight;\n const scrollTop = fauxScrollbar.scrollTop;\n\n // On force refresh with variable heights, rebuild the position cache\n // to pick up any height changes from plugins (e.g., ResponsivePlugin\n // measuring actual card heights from DOM after first render).\n if (force && s.variableHeights) {\n this.initializePositionCache();\n }\n\n let start: number;\n const positionCache = s.positionCache;\n\n // Variable row heights: use binary search on position cache\n if (s.variableHeights && positionCache && positionCache.length > 0) {\n start = getRowIndexAtOffset(positionCache, scrollTop);\n if (start === -1) start = 0;\n } else {\n start = Math.floor(scrollTop / rowHeight);\n\n let iterations = 0;\n const maxIterations = 10;\n while (iterations < maxIterations) {\n const extraHeightBefore = grid._getPluginExtraHeightBefore(start);\n const adjustedStart = Math.floor((scrollTop - extraHeightBefore) / rowHeight);\n if (adjustedStart >= start || adjustedStart < 0) break;\n start = adjustedStart;\n iterations++;\n }\n }\n\n // Round down to even number for zebra stripe parity\n start = start - (start % 2);\n if (start < 0) start = 0;\n\n // Allow plugins to extend the start index backwards\n const pluginAdjustedStart = grid._adjustPluginVirtualStart(start, scrollTop, rowHeight);\n if (pluginAdjustedStart !== undefined && pluginAdjustedStart < start) {\n start = pluginAdjustedStart;\n start = start - (start % 2);\n if (start < 0) start = 0;\n }\n\n // Calculate end of visible window\n let end: number;\n\n if (s.variableHeights && positionCache && positionCache.length > 0) {\n const targetHeight = viewportHeight + rowHeight * 3; // 3 rows overscan\n let accumulatedHeight = 0;\n end = start;\n\n while (end < totalRows && accumulatedHeight < targetHeight) {\n accumulatedHeight += positionCache[end].height;\n end++;\n }\n\n const minRows = Math.ceil(viewportHeight / rowHeight) + 3;\n if (end - start < minRows) {\n end = Math.min(start + minRows, totalRows);\n }\n } else {\n const visibleCount = Math.ceil(viewportHeight / rowHeight) + 3;\n end = start + visibleCount;\n }\n\n if (end > totalRows) end = totalRows;\n\n // Early-exit: visible window unchanged and not force\n const prevStart = s.start;\n const prevEnd = s.end;\n if (!force && start === prevStart && end === prevEnd) {\n return false;\n }\n\n s.start = start;\n s.end = end;\n\n // Read faux scrollbar height (cached on scroll path, fresh on force)\n const fauxScrollHeight = force\n ? (s.cachedFauxHeight = fauxScrollbar.clientHeight)\n : s.cachedFauxHeight || (s.cachedFauxHeight = fauxScrollbar.clientHeight);\n\n if (force) {\n const scrollAreaEl = s.scrollAreaEl;\n if (scrollAreaEl) {\n s.cachedScrollAreaHeight = scrollAreaEl.clientHeight;\n }\n }\n\n // Guard: stale DOM references\n if (fauxScrollHeight === 0 && viewportHeight > 0) {\n grid._requestSchedulerPhase(RenderPhase.VIRTUALIZATION, 'stale-refs-retry');\n return false;\n }\n\n // Recalculate spacer height on force refresh\n if (force && s.totalHeightEl) {\n const totalHeight = this.calculateTotalSpacerHeight(totalRows);\n s.totalHeightEl.style.height = `${totalHeight}px`;\n }\n\n // Calculate sub-pixel transform offset\n let startRowOffset: number;\n if (s.variableHeights && positionCache && positionCache[start]) {\n startRowOffset = positionCache[start].offset;\n } else {\n const extraHeightBeforeStart = grid._getPluginExtraHeightBefore(start);\n startRowOffset = start * rowHeight + extraHeightBeforeStart;\n }\n\n const subPixelOffset = -(scrollTop - startRowOffset);\n bodyEl.style.transform = `translateY(${subPixelOffset}px)`;\n\n grid._renderVisibleRows(start, end, grid.__rowRenderEpoch);\n\n // Measure rendered row heights on force refresh\n if (force && s.variableHeights) {\n this.measureRenderedRowHeights(start, end);\n }\n\n grid._updateAriaCounts(totalRows, grid._visibleColumns.length);\n\n // Run plugin afterRender hooks on force refresh\n if (force && !skipAfterRender) {\n grid._afterPluginRender();\n\n // Recalculate spacer height in microtask to catch plugin DOM changes\n queueMicrotask(() => {\n if (!s.totalHeightEl) return;\n const newTotalHeight = this.calculateTotalSpacerHeight(totalRows);\n if (s.cachedFauxHeight === 0 && s.cachedViewportHeight > 0) return;\n s.totalHeightEl.style.height = `${newTotalHeight}px`;\n });\n }\n\n return true;\n }\n\n // #endregion\n}\n\n// #endregion\n","/**\n * Plugin Manager\n *\n * Manages plugin instances for a single grid.\n * Each grid has its own PluginManager with its own set of plugin instances.\n *\n * **Plugin Order Matters**: Plugins are executed in the order they appear in the\n * `plugins` array. This affects the order of hook execution (processRows, processColumns,\n * afterRender, etc.). For example, if you want filtering to run before grouping,\n * add FilteringPlugin before GroupingRowsPlugin in the array.\n */\n\nimport { DEPRECATED_HOOK, PLUGIN_EVENT_ERROR, errorDiagnostic, warnDiagnostic } from '../internal/diagnostics';\nimport { isDevelopment } from '../internal/utils';\nimport { validatePluginDependencies } from '../internal/validate-config';\nimport type { ColumnConfig } from '../types';\nimport type {\n AfterCellRenderContext,\n AfterRowRenderContext,\n BaseGridPlugin,\n CellClickEvent,\n CellEditor,\n CellMouseEvent,\n CellRenderer,\n GridElement,\n HeaderClickEvent,\n HeaderRenderer,\n PluginQuery,\n RowClickEvent,\n ScrollEvent,\n} from './base-plugin';\n\n/**\n * Manages plugins for a single grid instance.\n *\n * Plugins are executed in array order. This is intentional and documented behavior.\n * Place plugins in the order you want their hooks to execute.\n */\nexport class PluginManager {\n // #region Properties\n /** Plugin instances in order of attachment */\n private plugins: BaseGridPlugin[] = [];\n\n /** Get all registered plugins (read-only) */\n getPlugins(): readonly BaseGridPlugin[] {\n return this.plugins;\n }\n\n /** Map from plugin class to instance for fast lookup */\n private pluginMap: Map<new (...args: unknown[]) => BaseGridPlugin, BaseGridPlugin> = new Map();\n\n /** Cell renderers registered by plugins */\n private cellRenderers: Map<string, CellRenderer> = new Map();\n\n /** Header renderers registered by plugins */\n private headerRenderers: Map<string, HeaderRenderer> = new Map();\n\n /** Cell editors registered by plugins */\n private cellEditors: Map<string, CellEditor> = new Map();\n\n /** Cached hook presence flags — invalidated on plugin attach/detach */\n private _hasAfterCellRender = false;\n private _hasAfterRowRender = false;\n // #endregion\n\n // #region Event Bus State\n /**\n * Event listeners indexed by event type.\n * Maps event type → Map<plugin instance → callback>.\n * Using plugin instance as key enables auto-cleanup on detach.\n */\n private eventListeners: Map<string, Map<BaseGridPlugin, (detail: unknown) => void>> = new Map();\n // #endregion\n\n // #region Query System State\n /**\n * Query handlers indexed by query type.\n * Maps query type → Set of plugin instances that declare handling it.\n * Built from manifest.queries during plugin attach.\n */\n private queryHandlers: Map<string, Set<BaseGridPlugin>> = new Map();\n // #endregion\n\n // #region Deprecation Warnings\n /** Set of plugin constructors that have been warned about deprecated hooks */\n // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type -- WeakSet key is plugin.constructor identity\n private static deprecationWarned = new WeakSet<Function>();\n // #endregion\n\n // #region Lifecycle\n constructor(private grid: GridElement) {}\n\n /**\n * Attach all plugins from the config.\n */\n attachAll(plugins: BaseGridPlugin[]): void {\n for (const plugin of plugins) {\n this.attach(plugin);\n }\n }\n\n /**\n * Attach a plugin to this grid.\n * Validates dependencies and notifies other plugins of the new attachment.\n */\n attach(plugin: BaseGridPlugin): void {\n // Validate plugin dependencies BEFORE attaching\n // This throws if a required dependency is missing\n validatePluginDependencies(plugin, this.plugins, this.grid.getAttribute('id') ?? undefined);\n\n // Store by constructor for type-safe lookup\n this.pluginMap.set(plugin.constructor as new (...args: unknown[]) => BaseGridPlugin, plugin);\n this.plugins.push(plugin);\n\n // Register renderers/editors\n if (plugin.cellRenderers) {\n for (const [type, renderer] of Object.entries(plugin.cellRenderers)) {\n this.cellRenderers.set(type, renderer);\n }\n }\n if (plugin.headerRenderers) {\n for (const [type, renderer] of Object.entries(plugin.headerRenderers)) {\n this.headerRenderers.set(type, renderer);\n }\n }\n if (plugin.cellEditors) {\n for (const [type, editor] of Object.entries(plugin.cellEditors)) {\n this.cellEditors.set(type, editor);\n }\n }\n\n // Register query handlers from manifest\n this.registerQueryHandlers(plugin);\n\n // Warn about deprecated hooks (once per plugin class)\n this.warnDeprecatedHooks(plugin);\n\n // Call attach lifecycle method\n plugin.attach(this.grid);\n\n // Invalidate hook caches\n this.#invalidateHookCaches();\n\n // Notify existing plugins of the new attachment\n for (const existingPlugin of this.plugins) {\n if (existingPlugin !== plugin && existingPlugin.onPluginAttached) {\n existingPlugin.onPluginAttached(plugin.name, plugin);\n }\n }\n }\n\n /**\n * Register query handlers from a plugin's manifest.\n */\n private registerQueryHandlers(plugin: BaseGridPlugin): void {\n const PluginClass = plugin.constructor as typeof BaseGridPlugin;\n const manifest = PluginClass.manifest;\n if (!manifest?.queries) return;\n\n for (const queryDef of manifest.queries) {\n let handlers = this.queryHandlers.get(queryDef.type);\n if (!handlers) {\n handlers = new Set();\n this.queryHandlers.set(queryDef.type, handlers);\n }\n handlers.add(plugin);\n }\n }\n\n /**\n * Warn about deprecated plugin hooks.\n * Only warns once per plugin class, only in development environments.\n */\n private warnDeprecatedHooks(plugin: BaseGridPlugin): void {\n const PluginClass = plugin.constructor;\n\n // Skip if already warned for this plugin class\n if (PluginManager.deprecationWarned.has(PluginClass)) return;\n\n // Only warn in development\n if (!isDevelopment()) return;\n\n const hasOldHooks =\n typeof plugin.getExtraHeight === 'function' || typeof plugin.getExtraHeightBefore === 'function';\n\n const hasNewHook = typeof plugin.getRowHeight === 'function';\n\n // Warn if using old hooks without new hook\n if (hasOldHooks && !hasNewHook) {\n PluginManager.deprecationWarned.add(PluginClass);\n warnDiagnostic(\n DEPRECATED_HOOK,\n `\"${plugin.name}\" uses getExtraHeight() / getExtraHeightBefore() ` +\n `which are deprecated and will be removed in v2.0.\\n` +\n ` → Migrate to getRowHeight(row, index) for better variable row height support.`,\n this.grid.getAttribute('id') ?? undefined,\n );\n }\n }\n\n /**\n * Unregister query handlers for a plugin.\n */\n private unregisterQueryHandlers(plugin: BaseGridPlugin): void {\n for (const [queryType, handlers] of this.queryHandlers) {\n handlers.delete(plugin);\n if (handlers.size === 0) {\n this.queryHandlers.delete(queryType);\n }\n }\n }\n\n /**\n * Detach all plugins and clean up.\n * Notifies plugins of detachment via onPluginDetached hook.\n */\n detachAll(): void {\n // Notify all plugins before detaching (in forward order)\n for (const plugin of this.plugins) {\n for (const otherPlugin of this.plugins) {\n if (otherPlugin !== plugin && otherPlugin.onPluginDetached) {\n otherPlugin.onPluginDetached(plugin.name);\n }\n }\n }\n\n // Detach in reverse order\n for (let i = this.plugins.length - 1; i >= 0; i--) {\n const plugin = this.plugins[i];\n this.unsubscribeAll(plugin); // Clean up event subscriptions\n this.unregisterQueryHandlers(plugin); // Clean up query handlers\n plugin.detach();\n }\n this.plugins = [];\n this.pluginMap.clear();\n this.cellRenderers.clear();\n this.headerRenderers.clear();\n this.cellEditors.clear();\n this.eventListeners.clear();\n this.queryHandlers.clear();\n this._hasAfterCellRender = false;\n this._hasAfterRowRender = false;\n }\n // #endregion\n\n // #region Plugin Lookup\n /**\n * Get a plugin instance by its class.\n */\n getPlugin<T extends BaseGridPlugin>(PluginClass: new (...args: any[]) => T): T | undefined {\n return this.pluginMap.get(PluginClass) as T | undefined;\n }\n\n /**\n * Get a plugin instance by its name.\n */\n getPluginByName(name: string): BaseGridPlugin | undefined {\n return this.plugins.find((p) => p.name === name || p.aliases?.includes(name));\n }\n\n /**\n * Check if a plugin is attached.\n */\n hasPlugin<T extends BaseGridPlugin>(PluginClass: new (...args: any[]) => T): boolean {\n return this.pluginMap.has(PluginClass);\n }\n\n /**\n * Get all attached plugins.\n */\n getAll(): readonly BaseGridPlugin[] {\n return this.plugins;\n }\n\n /**\n * Get names of all registered plugins (for debugging).\n */\n getRegisteredPluginNames(): string[] {\n return this.plugins.map((p) => p.name);\n }\n // #endregion\n\n // #region Renderers & Styles\n /**\n * Get a cell renderer by type name.\n */\n getCellRenderer(type: string): CellRenderer | undefined {\n return this.cellRenderers.get(type);\n }\n\n /**\n * Get a header renderer by type name.\n */\n getHeaderRenderer(type: string): HeaderRenderer | undefined {\n return this.headerRenderers.get(type);\n }\n\n /**\n * Get a cell editor by type name.\n */\n getCellEditor(type: string): CellEditor | undefined {\n return this.cellEditors.get(type);\n }\n\n /**\n * Get all CSS styles from all plugins as structured data.\n * Returns an array of { name, styles } for each plugin with styles.\n */\n getPluginStyles(): Array<{ name: string; styles: string }> {\n return this.plugins.filter((p) => p.styles).map((p) => ({ name: p.name, styles: p.styles! }));\n }\n // #endregion\n\n // #region Hook Execution\n\n /**\n * Execute processRows hook on all plugins.\n */\n processRows(rows: readonly any[]): any[] {\n let result = [...rows];\n for (const plugin of this.plugins) {\n if (plugin.processRows) {\n result = plugin.processRows(result);\n }\n }\n return result;\n }\n\n /**\n * Execute processColumns hook on all plugins.\n */\n processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n let result = [...columns];\n for (const plugin of this.plugins) {\n if (plugin.processColumns) {\n result = plugin.processColumns(result);\n }\n }\n return result;\n }\n\n /**\n * Execute beforeRender hook on all plugins.\n */\n beforeRender(): void {\n for (const plugin of this.plugins) {\n plugin.beforeRender?.();\n }\n }\n\n /**\n * Execute afterRender hook on all plugins.\n */\n afterRender(): void {\n for (const plugin of this.plugins) {\n plugin.afterRender?.();\n }\n }\n\n /**\n * Execute afterCellRender hook on all plugins for a single cell.\n * Called during cell rendering for efficient cell-level modifications.\n *\n * @param context - The cell render context\n */\n afterCellRender(context: AfterCellRenderContext): void {\n for (const plugin of this.plugins) {\n plugin.afterCellRender?.(context);\n }\n }\n\n /**\n * Check if any plugin has the afterCellRender hook implemented.\n * Cached — invalidated on plugin attach/detach.\n */\n hasAfterCellRenderHook(): boolean {\n return this._hasAfterCellRender;\n }\n\n /**\n * Execute afterRowRender hook on all plugins for a single row.\n * Called after all cells in a row are rendered for efficient row-level modifications.\n *\n * @param context - The row render context\n */\n afterRowRender(context: AfterRowRenderContext): void {\n for (const plugin of this.plugins) {\n plugin.afterRowRender?.(context);\n }\n }\n\n /**\n * Check if any plugin has the afterRowRender hook implemented.\n * Cached — invalidated on plugin attach/detach.\n */\n hasAfterRowRenderHook(): boolean {\n return this._hasAfterRowRender;\n }\n\n /** Recompute cached hook presence flags. */\n #invalidateHookCaches(): void {\n this._hasAfterCellRender = this.plugins.some((p) => typeof p.afterCellRender === 'function');\n this._hasAfterRowRender = this.plugins.some((p) => typeof p.afterRowRender === 'function');\n }\n\n /**\n * Execute onScrollRender hook on all plugins.\n * Called after scroll-triggered row rendering for lightweight visual state updates.\n */\n onScrollRender(): void {\n for (const plugin of this.plugins) {\n plugin.onScrollRender?.();\n }\n }\n\n /**\n * Get total extra height contributed by plugins (e.g., expanded detail rows).\n * Used to adjust scrollbar height calculations.\n */\n getExtraHeight(): number {\n let total = 0;\n for (const plugin of this.plugins) {\n if (typeof plugin.getExtraHeight === 'function') {\n total += plugin.getExtraHeight();\n }\n }\n return total;\n }\n\n /**\n * Check if any plugin is contributing extra height.\n * When true, plugins are managing variable row heights and the grid should\n * not override the base row height via #measureRowHeight().\n */\n hasExtraHeight(): boolean {\n for (const plugin of this.plugins) {\n if (typeof plugin.getExtraHeight === 'function' && plugin.getExtraHeight() > 0) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Get extra height from plugins that appears before a given row index.\n * Used by virtualization to correctly position the scroll window.\n */\n getExtraHeightBefore(beforeRowIndex: number): number {\n let total = 0;\n for (const plugin of this.plugins) {\n if (typeof plugin.getExtraHeightBefore === 'function') {\n total += plugin.getExtraHeightBefore(beforeRowIndex);\n }\n }\n return total;\n }\n\n /**\n * Get the height of a specific row from plugins.\n * Used for synthetic rows (group headers, etc.) that have fixed heights.\n * Returns undefined if no plugin provides a height for this row.\n */\n getRowHeight(row: unknown, index: number): number | undefined {\n for (const plugin of this.plugins) {\n if (typeof plugin.getRowHeight === 'function') {\n const height = plugin.getRowHeight(row, index);\n if (height !== undefined) {\n return height;\n }\n }\n }\n return undefined;\n }\n\n /**\n * Check if any plugin implements the getRowHeight() hook.\n * When true, the grid should use variable heights mode with position caching.\n */\n hasRowHeightPlugin(): boolean {\n for (const plugin of this.plugins) {\n if (typeof plugin.getRowHeight === 'function') {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Adjust the virtualization start index based on plugin needs.\n * Returns the minimum start index from all plugins.\n */\n adjustVirtualStart(start: number, scrollTop: number, rowHeight: number): number {\n let adjustedStart = start;\n for (const plugin of this.plugins) {\n if (typeof plugin.adjustVirtualStart === 'function') {\n const pluginStart = plugin.adjustVirtualStart(start, scrollTop, rowHeight);\n if (pluginStart < adjustedStart) {\n adjustedStart = pluginStart;\n }\n }\n }\n return adjustedStart;\n }\n\n /**\n * Execute renderRow hook on all plugins.\n * Returns true if any plugin handled the row.\n */\n renderRow(row: any, rowEl: HTMLElement, rowIndex: number): boolean {\n for (const plugin of this.plugins) {\n if (plugin.renderRow?.(row, rowEl, rowIndex)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Query all plugins with a generic query and collect responses.\n * This enables inter-plugin communication without the core knowing plugin-specific concepts.\n *\n * Uses manifest-based routing when available: only plugins that declare handling\n * the query type in their `manifest.queries` are invoked. Falls back to querying\n * all plugins for backwards compatibility with legacy plugins.\n *\n * Checks both `handleQuery` (preferred) and `onPluginQuery` (legacy) hooks.\n *\n * @param query - The query object containing type and context\n * @returns Array of non-undefined responses from plugins\n */\n queryPlugins<T>(query: PluginQuery): T[] {\n const responses: T[] = [];\n\n // Try manifest-based routing first\n const handlers = this.queryHandlers.get(query.type);\n if (handlers && handlers.size > 0) {\n // Route only to plugins that declared this query type\n for (const plugin of handlers) {\n const response = plugin.handleQuery?.(query) ?? plugin.onPluginQuery?.(query);\n if (response !== undefined) {\n responses.push(response as T);\n }\n }\n return responses;\n }\n\n // Fallback: query all plugins (legacy behavior for plugins without manifest)\n for (const plugin of this.plugins) {\n // Try handleQuery first (new API), fall back to onPluginQuery (legacy)\n const response = plugin.handleQuery?.(query) ?? plugin.onPluginQuery?.(query);\n if (response !== undefined) {\n responses.push(response as T);\n }\n }\n return responses;\n }\n // #endregion\n\n // #region Event Bus\n /**\n * Subscribe a plugin to an event type.\n * The subscription is automatically cleaned up when the plugin is detached.\n *\n * @param plugin - The subscribing plugin instance\n * @param eventType - The event type to listen for\n * @param callback - The callback to invoke when the event is emitted\n */\n subscribe(plugin: BaseGridPlugin, eventType: string, callback: (detail: unknown) => void): void {\n let listeners = this.eventListeners.get(eventType);\n if (!listeners) {\n listeners = new Map();\n this.eventListeners.set(eventType, listeners);\n }\n listeners.set(plugin, callback);\n }\n\n /**\n * Unsubscribe a plugin from an event type.\n *\n * @param plugin - The subscribing plugin instance\n * @param eventType - The event type to stop listening for\n */\n unsubscribe(plugin: BaseGridPlugin, eventType: string): void {\n const listeners = this.eventListeners.get(eventType);\n if (listeners) {\n listeners.delete(plugin);\n if (listeners.size === 0) {\n this.eventListeners.delete(eventType);\n }\n }\n }\n\n /**\n * Unsubscribe a plugin from all events.\n * Called automatically when a plugin is detached.\n *\n * @param plugin - The plugin to unsubscribe\n */\n unsubscribeAll(plugin: BaseGridPlugin): void {\n for (const [eventType, listeners] of this.eventListeners) {\n listeners.delete(plugin);\n if (listeners.size === 0) {\n this.eventListeners.delete(eventType);\n }\n }\n }\n\n /**\n * Emit an event to all subscribed plugins.\n * This is for inter-plugin communication only; it does not dispatch DOM events.\n *\n * @param eventType - The event type to emit\n * @param detail - The event payload\n */\n emitPluginEvent<T>(eventType: string, detail: T): void {\n const listeners = this.eventListeners.get(eventType);\n if (listeners) {\n for (const callback of listeners.values()) {\n try {\n callback(detail);\n } catch (error) {\n errorDiagnostic(\n PLUGIN_EVENT_ERROR,\n `Error in plugin event handler for \"${eventType}\": ${error}`,\n this.grid.getAttribute('id') ?? undefined,\n );\n }\n }\n }\n }\n // #endregion\n\n // #region Event Hooks\n /**\n * Execute onKeyDown hook on all plugins.\n * Returns true if any plugin handled the event.\n */\n onKeyDown(event: KeyboardEvent): boolean {\n for (const plugin of this.plugins) {\n if (plugin.onKeyDown?.(event)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Execute onCellClick hook on all plugins.\n * Returns true if any plugin handled the event.\n */\n onCellClick(event: CellClickEvent): boolean {\n for (const plugin of this.plugins) {\n if (plugin.onCellClick?.(event)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Execute onRowClick hook on all plugins.\n * Returns true if any plugin handled the event.\n */\n onRowClick(event: RowClickEvent): boolean {\n for (const plugin of this.plugins) {\n if (plugin.onRowClick?.(event)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Execute onHeaderClick hook on all plugins.\n * Returns true if any plugin handled the event.\n */\n onHeaderClick(event: HeaderClickEvent): boolean {\n for (const plugin of this.plugins) {\n if (plugin.onHeaderClick?.(event)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Execute onScroll hook on all plugins.\n */\n onScroll(event: ScrollEvent): void {\n for (const plugin of this.plugins) {\n plugin.onScroll?.(event);\n }\n }\n\n /**\n * Execute onCellMouseDown hook on all plugins.\n * Returns true if any plugin handled the event.\n */\n onCellMouseDown(event: CellMouseEvent): boolean {\n for (const plugin of this.plugins) {\n if (plugin.onCellMouseDown?.(event)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Execute onCellMouseMove hook on all plugins.\n * Returns true if any plugin handled the event.\n */\n onCellMouseMove(event: CellMouseEvent): boolean {\n for (const plugin of this.plugins) {\n if (plugin.onCellMouseMove?.(event)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Execute onCellMouseUp hook on all plugins.\n * Returns true if any plugin handled the event.\n */\n onCellMouseUp(event: CellMouseEvent): boolean {\n for (const plugin of this.plugins) {\n if (plugin.onCellMouseUp?.(event)) {\n return true;\n }\n }\n return false;\n }\n\n // #endregion\n\n // #region Scroll Boundary Hooks\n\n /**\n * Collect horizontal scroll boundary offsets from all plugins.\n * Combines offsets from all plugins that report them.\n *\n * @param rowEl - The row element (optional, for calculating widths from rendered cells)\n * @param focusedCell - The currently focused cell element (optional, to determine if scrolling should be skipped)\n * @returns Combined left and right pixel offsets, plus skipScroll if any plugin requests it\n */\n getHorizontalScrollOffsets(\n rowEl?: HTMLElement,\n focusedCell?: HTMLElement,\n ): { left: number; right: number; skipScroll?: boolean } {\n let left = 0;\n let right = 0;\n let skipScroll = false;\n for (const plugin of this.plugins) {\n const offsets = plugin.getHorizontalScrollOffsets?.(rowEl, focusedCell);\n if (offsets) {\n left += offsets.left;\n right += offsets.right;\n if (offsets.skipScroll) {\n skipScroll = true;\n }\n }\n }\n return { left, right, skipScroll };\n }\n // #endregion\n\n // #region Shell Integration Hooks\n\n /**\n * Collect tool panels from all plugins.\n * Returns panels sorted by order (ascending).\n */\n getToolPanels(): {\n plugin: BaseGridPlugin;\n panel: NonNullable<ReturnType<NonNullable<BaseGridPlugin['getToolPanel']>>>;\n }[] {\n const panels: {\n plugin: BaseGridPlugin;\n panel: NonNullable<ReturnType<NonNullable<BaseGridPlugin['getToolPanel']>>>;\n }[] = [];\n for (const plugin of this.plugins) {\n const panel = plugin.getToolPanel?.();\n if (panel) {\n panels.push({ plugin, panel });\n }\n }\n // Sort by order (ascending), default to 0\n return panels.sort((a, b) => (a.panel.order ?? 0) - (b.panel.order ?? 0));\n }\n\n /**\n * Collect header contents from all plugins.\n * Returns contents sorted by order (ascending).\n */\n getHeaderContents(): {\n plugin: BaseGridPlugin;\n content: NonNullable<ReturnType<NonNullable<BaseGridPlugin['getHeaderContent']>>>;\n }[] {\n const contents: {\n plugin: BaseGridPlugin;\n content: NonNullable<ReturnType<NonNullable<BaseGridPlugin['getHeaderContent']>>>;\n }[] = [];\n for (const plugin of this.plugins) {\n const content = plugin.getHeaderContent?.();\n if (content) {\n contents.push({ plugin, content });\n }\n }\n // Sort by order (ascending), default to 0\n return contents.sort((a, b) => (a.content.order ?? 0) - (b.content.order ?? 0));\n }\n // #endregion\n}\n","import { createAriaState, updateAriaCounts, updateAriaLabels, type AriaState } from './internal/aria';\nimport { autoSizeColumns, updateTemplate } from './internal/columns';\nimport { ConfigManager } from './internal/config-manager';\nimport { INVALID_ATTRIBUTE_JSON, warnDiagnostic } from './internal/diagnostics';\nimport { setupCellEventDelegation, setupRootEventDelegation } from './internal/event-delegation';\nimport { resolveFeatures } from './internal/feature-hook';\nimport { FocusManager } from './internal/focus-manager';\nimport { renderHeader } from './internal/header';\nimport { cancelIdle, scheduleIdle } from './internal/idle-scheduler';\nimport { ensureCellVisible } from './internal/keyboard';\nimport {\n createLoadingOverlay,\n hideLoadingOverlay,\n setCellLoadingState,\n setRowLoadingState,\n showLoadingOverlay,\n} from './internal/loading';\nimport { RenderPhase, RenderScheduler } from './internal/render-scheduler';\nimport { createResizeController } from './internal/resize';\nimport { animateRow, animateRowById, animateRows } from './internal/row-animation';\nimport { resolveRowIdOrThrow, RowManager, tryResolveRowId } from './internal/row-manager';\nimport { invalidateCellCache, renderVisibleRows } from './internal/rows';\nimport {\n buildGridDOMIntoElement,\n cleanupShellState,\n createShellController,\n createShellState,\n parseLightDomShell,\n parseLightDomToolButtons,\n parseLightDomToolPanels,\n prepareForRerender,\n renderCustomToolbarContents,\n renderHeaderContent,\n renderPanelContent,\n renderShellHeader,\n setupClickOutsideDismiss,\n setupShellEventListeners,\n setupToolPanelResize,\n shouldRenderShellHeader,\n updatePanelState,\n updateToolbarActiveStates,\n type ShellController,\n type ShellState,\n type ToolPanelRendererFactory,\n} from './internal/shell';\nimport { reapplyCoreSort } from './internal/sorting';\nimport { addPluginStyles, injectStyles } from './internal/style-injector';\nimport {\n cancelMomentum,\n createTouchScrollState,\n setupTouchScrollListeners,\n type TouchScrollState,\n} from './internal/touch-scroll';\nimport {\n validatePluginConfigRules,\n validatePluginIncompatibilities,\n validatePluginProperties,\n} from './internal/validate-config';\nimport { getRowIndexAtOffset } from './internal/virtualization';\nimport { VirtualizationManager } from './internal/virtualization-manager';\nimport type { AfterCellRenderContext, AfterRowRenderContext, CellMouseEvent, ScrollEvent } from './plugin';\nimport type {\n BaseGridPlugin,\n CellClickEvent,\n HeaderClickEvent,\n PluginQuery,\n RowClickEvent,\n} from './plugin/base-plugin';\nimport { PluginManager } from './plugin/plugin-manager';\nimport styles from './styles';\nimport type {\n AnimationConfig,\n ColumnConfig,\n ColumnConfigMap,\n ColumnInternal,\n DataChangeDetail,\n DataGridEventMap,\n FitMode,\n FrameworkAdapter,\n GridColumnState,\n GridConfig,\n HeaderContentDefinition,\n IconValue,\n InternalGrid,\n PluginNameMap,\n ResizeController,\n RowAnimationType,\n RowElementInternal,\n RowTransaction,\n ScrollToRowOptions,\n ToolbarContentDefinition,\n ToolPanelDefinition,\n TransactionResult,\n UpdateSource,\n VirtualState,\n} from './types';\nimport { DEFAULT_ANIMATION_CONFIG, DEFAULT_GRID_ICONS } from './types';\n\n/**\n * High-performance data grid web component.\n *\n * ## Instantiation\n *\n * **Do not call the constructor directly.** Web components must be created via\n * the DOM API. Use one of these approaches:\n *\n * ```typescript\n * // Recommended: Use the createGrid() factory for TypeScript type safety\n * import { createGrid, SelectionPlugin } from '@toolbox-web/grid/all';\n *\n * const grid = createGrid<Employee>({\n * columns: [\n * { field: 'name', header: 'Name' },\n * { field: 'email', header: 'Email' }\n * ],\n * plugins: [new SelectionPlugin()]\n * });\n * grid.rows = employees;\n * document.body.appendChild(grid);\n *\n * // Alternative: Query existing element from DOM\n * import { queryGrid } from '@toolbox-web/grid';\n * const grid = queryGrid<Employee>('#my-grid');\n *\n * // Alternative: Use document.createElement (loses type inference)\n * const grid = document.createElement('tbw-grid');\n * ```\n *\n * ## Configuration Architecture\n *\n * The grid follows a **single source of truth** pattern where all configuration\n * is managed by ConfigManager. Users can set configuration via multiple inputs:\n *\n * **Input Sources (precedence low → high):**\n * 1. `gridConfig` property - base configuration object\n * 2. Light DOM elements:\n * - `<tbw-grid-column>` → `effectiveConfig.columns`\n * - `<tbw-grid-header title=\"...\">` → `effectiveConfig.shell.header.title`\n * - `<tbw-grid-header-content>` → `effectiveConfig.shell.header.content`\n * 3. `columns` property → merged into `effectiveConfig.columns`\n * 4. `fitMode` property → merged into `effectiveConfig.fitMode`\n * 5. Column inference from first row (if no columns defined)\n *\n * **Derived State:**\n * - `_columns` - processed columns from `effectiveConfig.columns` after plugin hooks\n * - `_rows` - processed rows after plugin hooks (grouping, filtering, etc.)\n *\n * ConfigManager.merge() is the single place where all inputs converge.\n * All rendering and logic should read from `effectiveConfig` or derived state.\n *\n * @element tbw-grid\n *\n * @csspart container - The main grid container\n * @csspart header - The header row container\n * @csspart body - The body/rows container\n *\n * @cssprop --tbw-color-bg - Background color\n * @cssprop --tbw-color-fg - Foreground/text color\n */\n// Injected by Vite at build time from package.json\ndeclare const __GRID_VERSION__: string;\n\nexport class DataGridElement<T = any> extends HTMLElement implements InternalGrid<T> {\n // TODO: Rename to 'data-grid' when migration is complete\n static readonly tagName = 'tbw-grid';\n /** Version of the grid component, injected at build time from package.json */\n static readonly version = typeof __GRID_VERSION__ !== 'undefined' ? __GRID_VERSION__ : 'dev';\n\n /** Static counter for generating unique grid IDs */\n static #instanceCounter = 0;\n\n // #region Static Methods - Framework Adapters\n /**\n * Registry of framework adapters that handle converting light DOM elements\n * to functional renderers/editors. Framework libraries (Angular, React, Vue)\n * register adapters to enable zero-boilerplate component integration.\n */\n private static adapters: FrameworkAdapter[] = [];\n\n /**\n * Register a framework adapter for handling framework-specific components.\n * Adapters are checked in registration order when processing light DOM templates.\n *\n * @example\n * ```typescript\n * // In @toolbox-web/grid-angular\n * import { AngularGridAdapter } from '@toolbox-web/grid-angular';\n *\n * // One-time setup in app\n * GridElement.registerAdapter(new AngularGridAdapter(injector, appRef));\n * ```\n * @category Framework Adapters\n */\n static registerAdapter(adapter: FrameworkAdapter): void {\n this.adapters.push(adapter);\n }\n\n /**\n * Get all registered framework adapters.\n * Used internally by light DOM parsing to find adapters that can handle templates.\n * @category Framework Adapters\n */\n static getAdapters(): readonly FrameworkAdapter[] {\n return this.adapters;\n }\n\n /**\n * Clear all registered adapters (primarily for testing).\n * @category Framework Adapters\n */\n static clearAdapters(): void {\n this.adapters = [];\n }\n // #endregion\n\n // #region Static Methods - Observed Attributes\n /** @internal Web component lifecycle - not part of public API */\n static get observedAttributes(): string[] {\n return ['rows', 'columns', 'grid-config', 'fit-mode', 'loading'];\n }\n // #endregion\n\n /**\n * The render root for the grid. Without Shadow DOM, this is the element itself.\n * This abstraction allows internal code to work the same way regardless of DOM mode.\n */\n get #renderRoot(): HTMLElement {\n return this;\n }\n\n #initialized = false;\n\n // Ready Promise\n #readyPromise: Promise<void>;\n #readyResolve?: () => void;\n\n // #region Input Properties\n // Raw rows are stored here. Config sources (gridConfig, columns, fitMode)\n // are owned by ConfigManager. Grid.ts property setters delegate to ConfigManager.\n #rows: T[] = [];\n // #endregion\n\n // #region Private properties\n // effectiveConfig is owned by ConfigManager - access via getter\n get #effectiveConfig(): GridConfig<T> {\n return this.#configManager?.effective ?? {};\n }\n\n #connected = false;\n\n // Batched Updates - coalesces rapid property changes into single update\n #pendingUpdate = false;\n #pendingUpdateFlags = {\n rows: false,\n columns: false,\n gridConfig: false,\n fitMode: false,\n };\n\n // Render Scheduler - centralizes all rendering through RAF\n #scheduler!: RenderScheduler;\n\n #scrollRaf = 0;\n #pendingScrollTop: number | null = null;\n #hasScrollPlugins = false; // Cached flag for plugin scroll handlers\n #needsRowHeightMeasurement = false; // Flag to measure row height after render (for plugin-based variable heights)\n #scrollMeasureTimeout = 0; // Debounce timer for measuring rows after scroll settles\n #renderRowHook?: (row: any, rowEl: HTMLElement, rowIndex: number) => boolean; // Cached hook to avoid closures\n #touchState: TouchScrollState = createTouchScrollState();\n #eventAbortController?: AbortController;\n #resizeObserver?: ResizeObserver;\n #rowHeightObserver?: ResizeObserver; // Watches first row for size changes (CSS loading, custom renderers)\n #idleCallbackHandle?: number; // Handle for cancelling deferred idle work\n\n // Pooled scroll event object (reused to avoid GC pressure during scroll)\n #pooledScrollEvent: ScrollEvent = {\n scrollTop: 0,\n scrollLeft: 0,\n scrollHeight: 0,\n scrollWidth: 0,\n clientHeight: 0,\n clientWidth: 0,\n };\n\n // Plugin System\n #pluginManager!: PluginManager;\n #lastPluginsArray?: BaseGridPlugin[]; // Track last attached plugins to avoid unnecessary re-initialization\n #lastFeaturesConfig?: Record<string, unknown>; // Track last features config for change detection\n\n // Virtualization manager — owns VirtualState + all virtualization methods\n #virtManager!: VirtualizationManager<T>;\n\n // Focus manager — owns focus/navigation state and external focus containers\n #focusManager!: FocusManager<T>;\n\n // Row manager — owns row CRUD operations (updateRow, insertRow, removeRow, etc.)\n #rowManager!: RowManager<T>;\n\n /**\n * Exposes plugin manager for event bus operations (subscribe/unsubscribe/emit).\n * Plugins access this via `this.grid._pluginManager` in `BaseGridPlugin.on/off/emitPluginEvent`.\n * @internal\n */\n get _pluginManager(): PluginManager | undefined {\n return this.#pluginManager;\n }\n\n // Event Listeners\n #eventListenersAdded = false; // Guard against adding duplicate component-level listeners\n #scrollAbortController?: AbortController; // Separate controller for DOM scroll listeners (recreated on DOM changes)\n #scrollAreaEl?: HTMLElement; // Reference to horizontal scroll container (.tbw-scroll-area)\n\n // Column State\n #initialColumnState?: GridColumnState;\n\n // Config Manager\n #configManager!: ConfigManager<T>;\n\n // Shell State\n #shellState: ShellState = createShellState();\n #shellController!: ShellController;\n #resizeCleanup?: () => void;\n #clickOutsideCleanup?: () => void;\n\n // Loading State\n #loading = false;\n #loadingRows = new Set<string>(); // Row IDs currently loading\n #loadingCells = new Map<string, Set<string>>(); // Map<rowId, Set<field>> for cells loading\n #loadingOverlayEl?: HTMLElement; // Cached loading overlay element\n\n // Row ID Map - O(1) lookup for rows by ID\n #rowIdMap = new Map<string, { row: T; index: number }>();\n // #endregion\n\n // #region Derived State\n // _rows: result of applying plugin processRows hooks\n _rows: T[] = [];\n\n // _baseColumns: columns before plugin transformation (analogous to #rows for row processing)\n // This is the source of truth for processColumns - plugins transform these\n #baseColumns: ColumnInternal<T>[] = [];\n\n // _columns is a getter/setter that operates on effectiveConfig.columns\n // This ensures effectiveConfig.columns is the single source of truth for columns\n // _columns always contains ALL columns (including hidden)\n get _columns(): ColumnInternal<T>[] {\n return (this.#effectiveConfig.columns ?? []) as ColumnInternal<T>[];\n }\n set _columns(value: ColumnInternal<T>[]) {\n this.#effectiveConfig.columns = value as ColumnConfig<T>[];\n this.#visibleColumnsCache = undefined;\n }\n\n // visibleColumns returns only visible columns for rendering\n // This is what header/row rendering should use\n // Cached — invalidated when _columns is set\n #visibleColumnsCache?: ColumnInternal<T>[];\n get _visibleColumns(): ColumnInternal<T>[] {\n return (this.#visibleColumnsCache ??= this._columns.filter((c) => !c.hidden));\n }\n // #endregion\n\n // #region Runtime State (Plugin-accessible)\n // DOM references\n _headerRowEl!: HTMLElement;\n _bodyEl!: HTMLElement;\n _rowPool: RowElementInternal[] = [];\n _resizeController!: ResizeController;\n\n // Virtualization — delegated to VirtualizationManager, exposed via getter for plugin access\n get _virtualization(): VirtualState {\n return this.#virtManager.state;\n }\n set _virtualization(value: VirtualState) {\n // Support test mocking — merge incoming partial state into the live state object\n Object.assign(this.#virtManager.state, value);\n }\n\n // Focus & navigation\n _focusRow = 0;\n _focusCol = 0;\n /** Flag to restore focus styling after next render. @internal */\n _restoreFocusAfterRender = false;\n\n // Sort state\n _sortState: { field: string; direction: 1 | -1 } | null = null;\n\n // Layout\n _gridTemplate = '';\n // #endregion\n\n // #region Implementation Details (Internal only)\n __rowRenderEpoch = 0;\n __didInitialAutoSize = false;\n\n /** Light DOM columns cache - delegates to ConfigManager */\n get __lightDomColumnsCache(): ColumnInternal[] | undefined {\n return this.#configManager?.lightDomColumnsCache as ColumnInternal[] | undefined;\n }\n set __lightDomColumnsCache(value: ColumnInternal[] | undefined) {\n if (this.#configManager) {\n this.#configManager.lightDomColumnsCache = value as ColumnInternal<T>[] | undefined;\n }\n }\n\n /** Original column nodes - delegates to ConfigManager */\n get __originalColumnNodes(): HTMLElement[] | undefined {\n return this.#configManager?.originalColumnNodes;\n }\n set __originalColumnNodes(value: HTMLElement[] | undefined) {\n if (this.#configManager) {\n this.#configManager.originalColumnNodes = value;\n }\n }\n\n __originalOrder: T[] = [];\n\n /**\n * Framework adapter instance set by framework directives (Angular Grid, React DataGrid).\n * Used to handle framework-specific component rendering.\n * @internal\n */\n __frameworkAdapter?: FrameworkAdapter;\n\n // Cached DOM refs for hot path (refreshVirtualWindow) - avoid querySelector per scroll\n __rowsBodyEl: HTMLElement | null = null;\n // #endregion\n\n // #region Public API Props (getters/setters)\n // Getters return the EFFECTIVE value (after merging), not the raw input.\n // This is what consumers and plugins need - the current resolved state.\n // Setters update input properties which trigger re-merge into effectiveConfig.\n\n /**\n * Get or set the row data displayed in the grid.\n *\n * The getter returns processed rows (after filtering, sorting, grouping by plugins).\n * The setter accepts new source data and triggers a re-render.\n *\n * @group Configuration\n * @example\n * ```typescript\n * // Set initial data\n * grid.rows = employees;\n *\n * // Update with new data (triggers re-render)\n * grid.rows = [...employees, newEmployee];\n *\n * // Read current (processed) rows\n * console.log(`Displaying ${grid.rows.length} rows`);\n * ```\n */\n get rows(): T[] {\n return this._rows;\n }\n set rows(value: T[]) {\n const oldValue = this.#rows;\n this.#rows = value;\n if (oldValue !== value) {\n this.#queueUpdate('rows');\n }\n }\n\n /**\n * Get the original unfiltered/unprocessed source rows.\n *\n * Use this when you need access to all source data regardless of active\n * filters, sorting, or grouping applied by plugins. The `rows` property\n * returns processed data, while `sourceRows` returns the original input.\n *\n * @group Configuration\n * @example\n * ```typescript\n * // Get total count including filtered-out rows\n * console.log(`${grid.rows.length} of ${grid.sourceRows.length} rows visible`);\n *\n * // Export all data, not just visible\n * exportToCSV(grid.sourceRows);\n * ```\n */\n get sourceRows(): T[] {\n return this.#rows;\n }\n\n /** @internal Used by RowManager for insertRow/removeRow mutations. */\n set sourceRows(rows: T[]) {\n this.#rows = rows;\n }\n\n /**\n * Get or set the column configurations.\n *\n * The getter returns processed columns (after plugin transformations).\n * The setter accepts an array of column configs or a column config map.\n *\n * @group Configuration\n * @example\n * ```typescript\n * // Set columns as array\n * grid.columns = [\n * { field: 'name', header: 'Name', width: 200 },\n * { field: 'email', header: 'Email' },\n * { field: 'role', header: 'Role', width: 120 }\n * ];\n *\n * // Set columns as map (keyed by field)\n * grid.columns = {\n * name: { header: 'Name', width: 200 },\n * email: { header: 'Email' },\n * role: { header: 'Role', width: 120 }\n * };\n *\n * // Read current columns\n * grid.columns.forEach(col => {\n * console.log(`${col.field}: ${col.width ?? 'auto'}`);\n * });\n * ```\n */\n get columns(): ColumnConfig<T>[] {\n return [...this._columns] as ColumnConfig<T>[];\n }\n set columns(value: ColumnConfig<T>[] | ColumnConfigMap<T> | undefined) {\n const oldValue = this.#configManager?.getColumns();\n this.#configManager?.setColumns(value);\n if (oldValue !== value) {\n this.#queueUpdate('columns');\n }\n }\n\n /**\n * Get or set the full grid configuration object.\n *\n * The getter returns the effective (merged) configuration.\n * The setter accepts a new configuration and triggers a full re-render.\n *\n * @group Configuration\n * @example\n * ```typescript\n * import { SelectionPlugin, SortingPlugin } from '@toolbox-web/grid/all';\n *\n * // Set full configuration\n * grid.gridConfig = {\n * columns: [\n * { field: 'name', header: 'Name' },\n * { field: 'status', header: 'Status' }\n * ],\n * fitMode: 'stretch',\n * plugins: [\n * new SelectionPlugin({ mode: 'row' }),\n * new SortingPlugin()\n * ]\n * };\n *\n * // Read current configuration\n * console.log('Fit mode:', grid.gridConfig.fitMode);\n * console.log('Columns:', grid.gridConfig.columns?.length);\n * ```\n */\n get gridConfig(): GridConfig<T> {\n return this.#effectiveConfig;\n }\n set gridConfig(value: GridConfig<T> | undefined) {\n // Let the framework adapter pre-process the config (convert component\n // classes / VNodes / JSX to DOM-returning functions) before the grid sees it.\n if (value && this.__frameworkAdapter?.processConfig) {\n value = this.__frameworkAdapter.processConfig(value);\n }\n const oldValue = this.#configManager?.getGridConfig();\n this.#configManager?.setGridConfig(value);\n if (oldValue !== value) {\n // Clear light DOM column cache so columns are re-parsed from light DOM\n // This is needed for frameworks like Angular that project content asynchronously\n this.#configManager.clearLightDomCache();\n this.#queueUpdate('gridConfig');\n }\n }\n\n /**\n * Get or set the column sizing mode.\n *\n * - `'stretch'` (default): Columns stretch to fill available width\n * - `'fixed'`: Columns use explicit widths; horizontal scroll if needed\n * - `'auto'`: Columns auto-size to content on initial render\n *\n * @group Configuration\n * @example\n * ```typescript\n * // Use fixed widths with horizontal scroll\n * grid.fitMode = 'fixed';\n *\n * // Stretch columns to fill container\n * grid.fitMode = 'stretch';\n *\n * // Auto-size columns based on content\n * grid.fitMode = 'auto';\n * ```\n */\n get fitMode(): FitMode {\n return this.#effectiveConfig.fitMode ?? 'stretch';\n }\n set fitMode(value: FitMode | undefined) {\n const oldValue = this.#configManager?.getFitMode();\n this.#configManager?.setFitMode(value);\n if (oldValue !== value) {\n this.#queueUpdate('fitMode');\n }\n }\n\n // #region Loading API\n\n /**\n * Whether the grid is currently in a loading state.\n * When true, displays a loading overlay with spinner (or custom loadingRenderer).\n *\n * @example\n * ```typescript\n * grid.loading = true;\n * const data = await fetchData();\n * grid.rows = data;\n * grid.loading = false;\n * ```\n */\n get loading(): boolean {\n return this.#loading;\n }\n\n set loading(value: boolean) {\n const wasLoading = this.#loading;\n this.#loading = value;\n\n // Toggle attribute for CSS styling and external queries\n if (value) {\n this.setAttribute('loading', '');\n } else {\n this.removeAttribute('loading');\n }\n\n // Only update overlay if state actually changed\n if (wasLoading !== value) {\n this.#updateLoadingOverlay();\n }\n }\n\n /**\n * Set loading state for a specific row.\n * Shows a small spinner indicator on the row.\n *\n * @param rowId - The row's unique identifier (from getRowId)\n * @param loading - Whether the row is loading\n *\n * @example\n * ```typescript\n * // Show loading while saving row data\n * grid.setRowLoading('row-123', true);\n * await saveRow(rowData);\n * grid.setRowLoading('row-123', false);\n * ```\n */\n setRowLoading(rowId: string, loading: boolean): void {\n const wasLoading = this.#loadingRows.has(rowId);\n if (loading) {\n this.#loadingRows.add(rowId);\n } else {\n this.#loadingRows.delete(rowId);\n }\n\n // Update row element if state changed\n if (wasLoading !== loading) {\n this.#updateRowLoadingState(rowId, loading);\n }\n }\n\n /**\n * Set loading state for a specific cell.\n * Shows a small spinner indicator on the cell.\n *\n * @param rowId - The row's unique identifier\n * @param field - The column field\n * @param loading - Whether the cell is loading\n *\n * @example\n * ```typescript\n * // Show loading while validating a single field\n * grid.setCellLoading('row-123', 'email', true);\n * const valid = await validateEmail(newValue);\n * grid.setCellLoading('row-123', 'email', false);\n * ```\n */\n setCellLoading(rowId: string, field: string, loading: boolean): void {\n let cellFields = this.#loadingCells.get(rowId);\n const wasLoading = cellFields?.has(field) ?? false;\n\n if (loading) {\n if (!cellFields) {\n cellFields = new Set();\n this.#loadingCells.set(rowId, cellFields);\n }\n cellFields.add(field);\n } else {\n cellFields?.delete(field);\n // Clean up empty sets\n if (cellFields?.size === 0) {\n this.#loadingCells.delete(rowId);\n }\n }\n\n // Update cell element if state changed\n if (wasLoading !== loading) {\n this.#updateCellLoadingState(rowId, field, loading);\n }\n }\n\n /**\n * Check if a row is currently in loading state.\n * @param rowId - The row's unique identifier\n */\n isRowLoading(rowId: string): boolean {\n return this.#loadingRows.has(rowId);\n }\n\n /**\n * Check if a cell is currently in loading state.\n * @param rowId - The row's unique identifier\n * @param field - The column field\n */\n isCellLoading(rowId: string, field: string): boolean {\n return this.#loadingCells.get(rowId)?.has(field) ?? false;\n }\n\n /**\n * Clear all row and cell loading states.\n */\n clearAllLoading(): void {\n this.loading = false;\n\n // Clear all row loading states\n for (const rowId of this.#loadingRows) {\n this.#updateRowLoadingState(rowId, false);\n }\n this.#loadingRows.clear();\n\n // Clear all cell loading states\n for (const [rowId, fields] of this.#loadingCells) {\n for (const field of fields) {\n this.#updateCellLoadingState(rowId, field, false);\n }\n }\n this.#loadingCells.clear();\n }\n\n // #endregion\n\n /**\n * Effective config accessor for internal modules and plugins.\n * Returns the merged config (single source of truth) before plugin processing.\n * Use this when you need the raw merged config (e.g., for column definitions including hidden).\n * @group State Access\n * @internal Plugin API\n */\n get effectiveConfig(): GridConfig<T> {\n return this.#effectiveConfig;\n }\n\n /**\n * Get the disconnect signal for event listener cleanup.\n * This signal is aborted when the grid disconnects from the DOM.\n * Plugins and internal code can use this for automatic listener cleanup.\n * @group State Access\n * @internal Plugin API\n * @example\n * element.addEventListener('click', handler, { signal: this.grid.disconnectSignal });\n */\n get disconnectSignal(): AbortSignal {\n // Ensure AbortController exists (created in connectedCallback before plugins attach)\n if (!this.#eventAbortController) {\n this.#eventAbortController = new AbortController();\n }\n return this.#eventAbortController.signal;\n }\n // #endregion\n\n /**\n * @internal Do not call directly. Use `createGrid()` or `document.createElement('tbw-grid')`.\n */\n constructor() {\n super();\n // No Shadow DOM - render directly into the element\n void this.#injectStyles(); // Fire and forget - styles load asynchronously\n this.#readyPromise = new Promise((res) => (this.#readyResolve = res));\n\n // Initialize virtualization manager (tightly coupled — reads grid state directly)\n this.#virtManager = new VirtualizationManager<T>(this);\n\n // Initialize focus manager (tightly coupled — reads grid state directly)\n this.#focusManager = new FocusManager<T>(this);\n\n // Initialize row manager (tightly coupled — reads grid state directly)\n this.#rowManager = new RowManager<T>(this);\n\n // Initialize render scheduler (tightly coupled — calls grid pipeline methods directly)\n this.#scheduler = new RenderScheduler(this);\n // Connect ready promise to scheduler\n this.#scheduler.setInitialReadyResolver(() => this.#readyResolve?.());\n\n // Initialize shell controller (reads directly from grid internals)\n this.#shellController = createShellController(this.#shellState, this);\n\n // Initialize config manager (reads directly from grid internals)\n this.#configManager = new ConfigManager<T>(this);\n }\n\n /**\n * Inject grid styles into the document.\n * Delegates to the style-injector module (singleton pattern).\n */\n async #injectStyles(): Promise<void> {\n await injectStyles(styles);\n }\n\n // #region Plugin System\n /**\n * Get a plugin instance by its class constructor.\n *\n * **Prefer {@link getPluginByName}** for most use cases — it avoids importing the plugin class\n * and returns the actual instance registered in the grid.\n *\n * @example\n * ```ts\n * // Preferred: by name (no import needed)\n * const selection = grid.getPluginByName('selection');\n *\n * // Alternative: by class (requires import)\n * import { SelectionPlugin } from '@toolbox-web/grid/plugins/selection';\n * const selection = grid.getPlugin(SelectionPlugin);\n * selection?.selectAll();\n * ```\n *\n * @param PluginClass - The plugin class (constructor) to look up.\n * @returns The plugin instance, or `undefined` if not registered.\n * @group Plugin Communication\n */\n getPlugin<P>(PluginClass: new (...args: any[]) => P): P | undefined {\n return this.#pluginManager?.getPlugin(PluginClass as new (...args: any[]) => BaseGridPlugin) as P | undefined;\n }\n\n /**\n * Get a plugin instance by its string name.\n * Useful for loose coupling when you don't want to import the plugin class\n * (e.g., in framework adapters or dynamic scenarios).\n *\n * @example\n * ```ts\n * const editing = grid.getPluginByName('editing');\n * ```\n *\n * @param name - The plugin name (matches {@link BaseGridPlugin.name}).\n * @returns The plugin instance, or `undefined` if not registered.\n * @group Plugin Communication\n */\n getPluginByName<K extends string>(\n name: K,\n ): (K extends keyof PluginNameMap ? PluginNameMap[K] : BaseGridPlugin) | undefined {\n return this.#pluginManager?.getPluginByName(name) as any;\n }\n\n /**\n * Request a full re-render of the grid.\n * Called by plugins when they need the grid to update.\n * Note: This does NOT reset plugin state - just re-processes rows/columns and renders.\n * @group Rendering\n * @internal Plugin API\n */\n requestRender(): void {\n this.#scheduler.requestPhase(RenderPhase.ROWS, 'plugin:requestRender');\n }\n\n /**\n * Request a columns re-render of the grid.\n * Called by plugins when they need to trigger processColumns hooks.\n * This uses a higher render phase than requestRender() to ensure\n * column processing occurs.\n * @group Rendering\n * @internal Plugin API\n */\n requestColumnsRender(): void {\n this.#scheduler.requestPhase(RenderPhase.COLUMNS, 'plugin:requestColumnsRender');\n }\n\n /**\n * Request a full re-render and restore focus styling afterward.\n * Use this when a plugin action (like expand/collapse) triggers a render\n * but needs to maintain keyboard navigation focus.\n * @group Rendering\n * @internal Plugin API\n */\n requestRenderWithFocus(): void {\n this._restoreFocusAfterRender = true;\n this.#scheduler.requestPhase(RenderPhase.ROWS, 'plugin:requestRenderWithFocus');\n }\n\n /**\n * Update the grid's column template CSS.\n * Called by resize controller during column resize operations.\n * @group Rendering\n * @internal Plugin API\n */\n updateTemplate(): void {\n updateTemplate(this);\n }\n\n /**\n * Request a lightweight style update without rebuilding DOM.\n * Called by plugins when they only need to update CSS classes/styles.\n * This runs all plugin afterRender hooks without rebuilding row/column DOM.\n * @group Rendering\n * @internal Plugin API\n */\n requestAfterRender(): void {\n this.#scheduler.requestPhase(RenderPhase.STYLE, 'plugin:requestAfterRender');\n }\n\n /**\n * Re-render visible rows without rebuilding the row model or recalculating geometry.\n * Uses non-force refreshVirtualWindow to avoid spacer height recalculations that\n * can cause scroll position oscillation. Useful when row data has been updated in-place\n * (e.g., server-side block loads replacing placeholders with real data).\n * @group Rendering\n * @internal Plugin API\n */\n requestVirtualRefresh(): void {\n // Invalidate start so refreshVirtualWindow doesn't early-exit\n this._virtualization.start = -1;\n this.refreshVirtualWindow(false);\n }\n\n /**\n * Initialize plugin system with instances from config.\n * Plugins are class instances passed in gridConfig.plugins[].\n * If gridConfig.features is set and the feature registry is loaded,\n * feature-derived plugins are prepended before explicit plugins.\n */\n #initializePlugins(): void {\n // Create plugin manager for this grid\n this.#pluginManager = new PluginManager(this);\n\n // Get plugin instances from config - ensure it's an array\n const pluginsConfig = this.#effectiveConfig?.plugins;\n const explicitPlugins = Array.isArray(pluginsConfig) ? (pluginsConfig as BaseGridPlugin[]) : [];\n\n // Resolve feature-derived plugins (if feature registry is loaded)\n const features = this.#effectiveConfig?.features;\n let featurePlugins: BaseGridPlugin[] = [];\n if (features && resolveFeatures) {\n featurePlugins = resolveFeatures(features as Record<string, unknown>) as BaseGridPlugin[];\n }\n\n // Merge: feature-derived first (for dependency ordering), then explicit plugins\n const allPlugins = featurePlugins.length > 0 ? [...featurePlugins, ...explicitPlugins] : explicitPlugins;\n\n // Attach all plugins\n this.#pluginManager.attachAll(allPlugins);\n }\n\n /**\n * Inject all plugin styles into the consolidated style element.\n * Plugin styles are appended after base grid styles in the same <style> element.\n * Uses a Map to accumulate styles from all grid instances on the page.\n */\n #injectAllPluginStyles(): void {\n const pluginStyles = this.#pluginManager?.getPluginStyles() ?? [];\n addPluginStyles(pluginStyles);\n }\n\n /**\n * Update plugins when grid config changes.\n * With class-based plugins, we need to detach old and attach new.\n * Skips re-initialization if the plugins array and features config haven't changed.\n */\n #updatePluginConfigs(): void {\n // Get the new plugins array from config\n const pluginsConfig = this.#effectiveConfig?.plugins;\n const newPlugins = Array.isArray(pluginsConfig) ? (pluginsConfig as BaseGridPlugin[]) : [];\n const newFeatures = (this.#effectiveConfig?.features as Record<string, unknown>) ?? undefined;\n\n // Check if features config changed (by reference)\n const featuresChanged = newFeatures !== this.#lastFeaturesConfig;\n\n // Check if plugins have actually changed (same array reference or same contents)\n // This avoids unnecessary detach/attach cycles on every render\n const pluginsUnchanged =\n this.#lastPluginsArray === newPlugins ||\n (this.#lastPluginsArray !== undefined &&\n this.#lastPluginsArray.length === newPlugins.length &&\n this.#lastPluginsArray.every((p, i) => p === newPlugins[i]));\n\n if (pluginsUnchanged && !featuresChanged) {\n // Nothing changed - just update reference tracking\n this.#lastPluginsArray = newPlugins;\n return;\n }\n\n // Plugins have changed - detach old and attach new\n if (this.#pluginManager) {\n this.#pluginManager.detachAll();\n }\n\n // Clear plugin-contributed panels BEFORE re-initializing plugins\n // This is critical: when plugins are re-initialized, they create NEW instances\n // with NEW render functions. The old panel definitions have stale closures.\n // We preserve light DOM panels (tracked in lightDomToolPanelIds) and\n // API-registered panels (tracked in apiToolPanelIds).\n for (const panelId of this.#shellState.toolPanels.keys()) {\n const isLightDom = this.#shellState.lightDomToolPanelIds.has(panelId);\n const isApiRegistered = this.#shellState.apiToolPanelIds.has(panelId);\n if (!isLightDom && !isApiRegistered) {\n // Clean up any active panel cleanup function\n const cleanup = this.#shellState.panelCleanups.get(panelId);\n if (cleanup) {\n cleanup();\n this.#shellState.panelCleanups.delete(panelId);\n }\n this.#shellState.toolPanels.delete(panelId);\n }\n }\n\n // Similarly clear plugin-contributed header contents\n // Preserve API-registered header contents (tracked in apiHeaderContentIds).\n for (const contentId of this.#shellState.headerContents.keys()) {\n if (this.#shellState.apiHeaderContentIds.has(contentId)) continue;\n const cleanup = this.#shellState.headerContentCleanups.get(contentId);\n if (cleanup) {\n cleanup();\n this.#shellState.headerContentCleanups.delete(contentId);\n }\n this.#shellState.headerContents.delete(contentId);\n }\n\n this.#initializePlugins();\n this.#injectAllPluginStyles();\n\n // Track the new plugins array and features config\n this.#lastPluginsArray = newPlugins;\n this.#lastFeaturesConfig = newFeatures;\n\n // Re-check variable heights mode: a plugin with getRowHeight() may have been added\n // after initial setup (e.g., Angular/React set gridConfig asynchronously via effects).\n // Without this, variableHeights stays false and the scroll handler uses fixed-height math,\n // producing incorrect translateY when detail rows are expanded.\n this.#configureVariableHeights();\n\n // Re-collect plugin shell contributions (tool panels, header content)\n // Now the new plugin instances will add their fresh panel definitions\n this.#collectPluginShellContributions();\n\n // Update cached scroll plugin flag and re-setup scroll listeners if needed\n // This ensures horizontal scroll listener is added when plugins with onScroll handlers are added\n const hadScrollPlugins = this.#hasScrollPlugins;\n this.#hasScrollPlugins = this.#pluginManager?.getAll().some((p) => p.onScroll) ?? false;\n\n // Re-setup scroll listeners if scroll plugins were added (flag changed from false to true)\n if (!hadScrollPlugins && this.#hasScrollPlugins) {\n const gridContent = this.#renderRoot.querySelector('.tbw-grid-content');\n const gridRoot = gridContent ?? this.#renderRoot.querySelector('.tbw-grid-root');\n this.#setupScrollListeners(gridRoot);\n }\n }\n\n /**\n * Clean up plugin states when grid disconnects.\n */\n #destroyPlugins(): void {\n this.#pluginManager?.detachAll();\n }\n\n /**\n * Collect tool panels and header content from all plugins.\n * Called after plugins are attached but before render.\n */\n #collectPluginShellContributions(): void {\n if (!this.#pluginManager) return;\n\n // Collect tool panels from plugins\n const pluginPanels = this.#pluginManager.getToolPanels();\n for (const { panel } of pluginPanels) {\n // Skip if already registered (light DOM or API takes precedence)\n if (!this.#shellState.toolPanels.has(panel.id)) {\n this.#shellState.toolPanels.set(panel.id, panel);\n }\n }\n\n // Collect header contents from plugins\n const pluginContents = this.#pluginManager.getHeaderContents();\n for (const { content } of pluginContents) {\n // Skip if already registered (light DOM or API takes precedence)\n if (!this.#shellState.headerContents.has(content.id)) {\n this.#shellState.headerContents.set(content.id, content);\n }\n }\n }\n\n /**\n * Gets a renderer factory for tool panels from registered framework adapters.\n * Returns a factory function that tries each adapter in order until one handles the element.\n */\n #getToolPanelRendererFactory(): ToolPanelRendererFactory | undefined {\n const adapters = DataGridElement.getAdapters();\n if (adapters.length === 0 && !this.__frameworkAdapter) return undefined;\n\n // Also check for instance-level adapter (e.g., __frameworkAdapter from Angular Grid directive)\n const instanceAdapter = this.__frameworkAdapter;\n\n return (element: HTMLElement) => {\n // Try instance adapter first (from Grid directive)\n if (instanceAdapter?.createToolPanelRenderer) {\n const renderer = instanceAdapter.createToolPanelRenderer(element);\n if (renderer) return renderer;\n }\n\n // Try global adapters\n for (const adapter of adapters) {\n if (adapter.createToolPanelRenderer) {\n const renderer = adapter.createToolPanelRenderer(element);\n if (renderer) return renderer;\n }\n }\n\n return undefined;\n };\n }\n // #endregion\n\n // #region Lifecycle\n /** @internal Web component lifecycle - not part of public API */\n connectedCallback(): void {\n if (!this.hasAttribute('tabindex')) this.tabIndex = 0;\n if (!this.hasAttribute('version')) this.setAttribute('version', DataGridElement.version);\n // Ensure grid has a unique ID for print isolation and other use cases\n if (!this.id) {\n this.id = `tbw-grid-${++DataGridElement.#instanceCounter}`;\n }\n this._rows = Array.isArray(this.#rows) ? [...this.#rows] : [];\n\n // Create AbortController for all event listeners (grid internal + plugins)\n // This must happen BEFORE plugins attach so they can use disconnectSignal\n // Abort any previous controller first (in case of re-connect)\n if (this.#eventAbortController) {\n this.#eventAbortController.abort();\n this.#eventListenersAdded = false; // Reset so listeners can be re-added\n }\n this.#eventAbortController = new AbortController();\n\n // Cancel any pending idle work from previous connection\n if (this.#idleCallbackHandle) {\n cancelIdle(this.#idleCallbackHandle);\n this.#idleCallbackHandle = undefined;\n }\n\n // === CRITICAL PATH (synchronous) - needed for first paint ===\n\n // Parse light DOM shell elements BEFORE merging config\n this.#parseLightDom();\n // Parse light DOM columns (must be before merge to pick up templates)\n this.#configManager.parseLightDomColumns(this);\n\n // Merge all config sources into effectiveConfig (including columns and shell)\n this.#configManager.merge();\n\n // Initialize plugin system (now plugins can access disconnectSignal)\n this.#initializePlugins();\n\n // Track the initial plugins array and features to avoid unnecessary re-initialization\n const pluginsConfig = this.#effectiveConfig?.plugins;\n this.#lastPluginsArray = Array.isArray(pluginsConfig) ? (pluginsConfig as BaseGridPlugin[]) : [];\n this.#lastFeaturesConfig = (this.#effectiveConfig?.features as Record<string, unknown>) ?? undefined;\n\n // Collect tool panels and header content from plugins (must be before render)\n this.#collectPluginShellContributions();\n\n if (!this.#initialized) {\n this.#render();\n this.#injectAllPluginStyles(); // Inject plugin styles after render\n this.#initialized = true;\n }\n this.#afterConnect();\n\n // === DEFERRED WORK (idle) - not needed for first paint ===\n this.#idleCallbackHandle = scheduleIdle(\n () => {\n // Set up Light DOM observation via ConfigManager\n // This handles frameworks like Angular that project content asynchronously\n this.#setupLightDomHandlers();\n },\n { timeout: 100 },\n );\n }\n\n /** @internal Web component lifecycle - not part of public API */\n disconnectedCallback(): void {\n // Cancel any pending idle work\n if (this.#idleCallbackHandle) {\n cancelIdle(this.#idleCallbackHandle);\n this.#idleCallbackHandle = undefined;\n }\n\n // Cancel any pending scroll measurement\n if (this.#scrollMeasureTimeout) {\n clearTimeout(this.#scrollMeasureTimeout);\n this.#scrollMeasureTimeout = 0;\n }\n\n // Clean up plugin states\n this.#destroyPlugins();\n\n // Clean up shell state\n cleanupShellState(this.#shellState);\n this.#shellController.setInitialized(false);\n\n // Clean up tool panel resize handler\n this.#resizeCleanup?.();\n this.#resizeCleanup = undefined;\n\n // Clean up click-outside dismiss handler\n this.#clickOutsideCleanup?.();\n this.#clickOutsideCleanup = undefined;\n\n // Cancel any ongoing touch momentum animation\n cancelMomentum(this.#touchState);\n\n // Abort all event listeners (internal + document-level)\n // This cleans up all listeners added with { signal } option\n if (this.#eventAbortController) {\n this.#eventAbortController.abort();\n this.#eventAbortController = undefined;\n }\n // Also abort scroll-specific listeners (separate controller)\n this.#scrollAbortController?.abort();\n this.#scrollAbortController = undefined;\n this.#eventListenersAdded = false; // Reset so listeners can be re-added on reconnect\n\n if (this._resizeController) {\n this._resizeController.dispose();\n }\n if (this.#resizeObserver) {\n this.#resizeObserver.disconnect();\n this.#resizeObserver = undefined;\n }\n if (this.#rowHeightObserver) {\n this.#rowHeightObserver.disconnect();\n this.#rowHeightObserver = undefined;\n this.#rowHeightObserverSetup = false;\n }\n\n // Clear caches to prevent memory leaks\n invalidateCellCache(this);\n this.#customStyleSheets.clear();\n this._virtualization.heightCache?.byKey.clear();\n\n // Clear plugin tracking to allow fresh initialization on reconnect\n this.#lastPluginsArray = undefined;\n this.#lastFeaturesConfig = undefined;\n\n // Clear row pool - detach from DOM and release references\n for (const rowEl of this._rowPool) {\n rowEl.remove();\n }\n this._rowPool.length = 0;\n\n // Clear cached DOM refs to prevent stale references\n this.__rowsBodyEl = null;\n\n this.#connected = false;\n }\n\n /**\n * Handle HTML attribute changes.\n * Only processes attribute values when SET (non-null).\n * Removing an attribute does NOT clear JS-set properties.\n * @internal Web component lifecycle - not part of public API\n */\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n // Handle boolean attributes first (presence = true, absence/null = false, \"false\" = false)\n if (name === 'loading') {\n const isLoading = newValue !== null && newValue !== 'false';\n if (this.loading !== isLoading) {\n this.loading = isLoading;\n }\n return;\n }\n\n if (oldValue === newValue || !newValue || newValue === 'null' || newValue === 'undefined') return;\n\n // JSON attributes need parsing\n if (name === 'rows' || name === 'columns' || name === 'grid-config') {\n try {\n const parsed = JSON.parse(newValue);\n if (name === 'rows') this.rows = parsed;\n else if (name === 'columns') this.columns = parsed;\n else if (name === 'grid-config') this.gridConfig = parsed;\n } catch {\n warnDiagnostic(INVALID_ATTRIBUTE_JSON, `Invalid JSON for '${name}' attribute: ${newValue}`, this.id);\n }\n } else if (name === 'fit-mode') {\n this.fitMode = newValue as FitMode;\n }\n }\n\n #afterConnect(): void {\n // Shell changes the DOM structure - need to find elements appropriately\n const gridContent = this.#renderRoot.querySelector('.tbw-grid-content');\n const gridRoot = gridContent ?? this.#renderRoot.querySelector('.tbw-grid-root');\n\n this._headerRowEl = gridRoot?.querySelector('.header-row') as HTMLElement;\n // Faux scrollbar pattern:\n // - .faux-vscroll-spacer sets virtual height\n // - .rows-viewport provides visible height for virtualization calculations\n this._virtualization.totalHeightEl = gridRoot?.querySelector('.faux-vscroll-spacer') as HTMLElement;\n this._virtualization.viewportEl = gridRoot?.querySelector('.rows-viewport') as HTMLElement;\n this._bodyEl = gridRoot?.querySelector('.rows') as HTMLElement;\n\n // Cache DOM refs for hot path (refreshVirtualWindow) - avoid querySelector per scroll\n this.__rowsBodyEl = gridRoot?.querySelector('.rows-body') as HTMLElement;\n\n // Initialize shell header content and custom buttons if shell is active\n if (this.#shellController.isInitialized) {\n // Render plugin header content\n renderHeaderContent(this.#renderRoot, this.#shellState);\n // Render custom toolbar contents (render modes) - all contents unified in effectiveConfig\n renderCustomToolbarContents(this.#renderRoot, this.#effectiveConfig?.shell, this.#shellState);\n // Open default section if configured\n const defaultOpen = this.#effectiveConfig?.shell?.toolPanel?.defaultOpen;\n if (defaultOpen && this.#shellState.toolPanels.has(defaultOpen)) {\n this.openToolPanel();\n this.#shellState.expandedSections.add(defaultOpen);\n }\n // Restore panel content if panel was already open (e.g., after position change re-render)\n if (this.#shellState.isPanelOpen) {\n updatePanelState(this.#renderRoot, this.#shellState);\n renderPanelContent(this.#renderRoot, this.#shellState, {\n expand: this.#effectiveConfig?.icons?.expand,\n collapse: this.#effectiveConfig?.icons?.collapse,\n });\n updateToolbarActiveStates(this.#renderRoot, this.#shellState);\n }\n }\n\n // Mark for tests that afterConnect ran\n this.setAttribute('data-upgraded', '');\n this.#connected = true;\n\n // Create resize controller BEFORE setup - renderHeader() needs it for resize handle mousedown events\n this._resizeController = createResizeController(this);\n\n // Run setup\n this.#setup();\n\n // Set up DOM-element scroll listeners (these need to be re-attached when DOM is recreated)\n this.#setupScrollListeners(gridRoot);\n\n // Only add component-level event listeners once (afterConnect can be called multiple times)\n // When shell state changes or refreshShellHeader is called, we re-run afterConnect\n // but component-level listeners should not be duplicated (they don't depend on DOM elements)\n // Scroll listeners are already set up above and handle DOM recreation via their own AbortController\n if (this.#eventListenersAdded) {\n return;\n }\n this.#eventListenersAdded = true;\n\n // Get the signal for event listener cleanup (AbortController created in connectedCallback)\n const signal = this.disconnectSignal;\n\n // Set up all root-level and document-level event listeners\n // Consolidates keydown, mousedown, mousemove, mouseup in one place (event-delegation.ts)\n setupRootEventDelegation(this, this, this.#renderRoot, signal);\n\n // Note: click/dblclick handlers are set up via setupCellEventDelegation in #setupScrollListeners\n // This consolidates all body-level delegated event handlers in one place (event-delegation.ts)\n\n // Configure variable row heights based on plugins and user config\n this.#configureVariableHeights();\n\n // Initialize ARIA selection state\n queueMicrotask(() => this.#updateAriaSelection());\n\n // Request initial render through the scheduler.\n // The scheduler resolves ready() after the render cycle completes.\n // Framework adapters (React/Angular) will request COLUMNS phase via refreshColumns(),\n // which will be batched with this request - highest phase wins.\n this.#scheduler.requestPhase(RenderPhase.FULL, 'afterConnect');\n }\n\n /**\n * Configure variable row heights based on plugins and user config.\n * Called from both #afterConnect (initial setup) and #updatePluginConfigs (plugin changes).\n *\n * Handles three scenarios:\n * 1. Variable heights needed (rowHeight function or plugin with getRowHeight) → enable + init cache\n * 2. Fixed numeric rowHeight → set directly\n * 3. No config → measure from DOM after first paint\n */\n #configureVariableHeights(): void {\n const userRowHeight = this.#effectiveConfig.rowHeight;\n const hasRowHeightPlugin = this.#pluginManager.hasRowHeightPlugin();\n\n if (typeof userRowHeight === 'function' || hasRowHeightPlugin) {\n if (!this._virtualization.variableHeights) {\n this._virtualization.variableHeights = true;\n this._virtualization.rowHeight =\n typeof userRowHeight === 'number' && userRowHeight > 0 ? userRowHeight : this._virtualization.rowHeight || 28;\n this.#virtManager.initializePositionCache();\n if (typeof userRowHeight !== 'function') {\n this.#needsRowHeightMeasurement = true;\n }\n }\n } else if (!hasRowHeightPlugin && typeof userRowHeight !== 'function' && this._virtualization.variableHeights) {\n // Plugin was removed — revert to fixed heights\n this._virtualization.variableHeights = false;\n this._virtualization.positionCache = null;\n } else if (typeof userRowHeight === 'number' && userRowHeight > 0) {\n this._virtualization.rowHeight = userRowHeight;\n this._virtualization.variableHeights = false;\n } else {\n // No config — measure from DOM after first paint\n // ResizeObserver in #setupScrollListeners handles subsequent dynamic changes\n requestAnimationFrame(() => this.#measureRowHeight());\n }\n }\n\n /**\n * Measure actual row height from DOM.\n * Finds the tallest cell to account for custom renderers that may push height.\n */\n #measureRowHeight(): void {\n // Skip if a plugin is managing variable row heights (e.g., ResponsivePlugin with groups)\n // In that case, the plugin handles height via getExtraHeight() and we shouldn't\n // override the base row height, which would cause oscillation loops.\n if (this.#pluginManager.hasExtraHeight()) {\n return;\n }\n\n const firstRow = this._bodyEl?.querySelector('.data-grid-row') as HTMLElement | null;\n if (!firstRow) return;\n\n // Skip if the observed row has a per-row --tbw-row-height override.\n // Variable-height rows set this inline, and measuring them would ratchet\n // the global s.rowHeight up, corrupting the position cache for ALL rows.\n // Normal rows (no override) are still measured so s.rowHeight reflects the\n // true default height from CSS/themes.\n if (firstRow.style.getPropertyValue('--tbw-row-height')) {\n return;\n }\n\n // Resolve the --tbw-row-height CSS variable to detect theme changes.\n // When a theme is swapped, the computed value changes (e.g., 52px → 28px).\n // We accept both increases AND decreases from the CSS variable because\n // this reflects an intentional style change, not content oscillation.\n const cssRowHeight = this.#resolveCssRowHeight();\n\n // Find the tallest cell in the row (custom renderers may push some cells taller)\n const cells = firstRow.querySelectorAll('.cell');\n let maxCellHeight = 0;\n cells.forEach((cell) => {\n const h = (cell as HTMLElement).offsetHeight;\n if (h > maxCellHeight) maxCellHeight = h;\n });\n\n const rowRect = (firstRow as HTMLElement).getBoundingClientRect();\n\n // Use the larger of row height or max cell height\n const measuredHeight = Math.max(rowRect.height, maxCellHeight);\n\n // Determine if the row height changed.\n // - CSS variable changes (theme switch): accept both increases AND decreases\n // - Content-based changes: only accept increases to avoid oscillation from\n // mixed-height content (e.g., server-side plugin placeholder vs real rows)\n const currentHeight = this._virtualization.rowHeight;\n const cssChanged = cssRowHeight > 0 && Math.abs(cssRowHeight - currentHeight) > 1;\n const contentGrew = measuredHeight > 0 && measuredHeight - currentHeight > 1;\n\n if (cssChanged || contentGrew) {\n // Prefer CSS-resolved height for theme changes; use measured height for content growth\n this._virtualization.rowHeight = cssChanged ? Math.max(cssRowHeight, measuredHeight) : measuredHeight;\n // Use scheduler to batch with other pending work\n this.#scheduler.requestPhase(RenderPhase.VIRTUALIZATION, 'measureRowHeight');\n }\n }\n\n /**\n * Resolve the --tbw-row-height CSS variable to a pixel value.\n * Reads from an existing row cell (which uses min-height: var(--tbw-row-height))\n * to avoid creating/removing temporary DOM elements.\n * Returns 0 if no row is available or the variable is unset.\n */\n #resolveCssRowHeight(): number {\n const raw = getComputedStyle(this).getPropertyValue('--tbw-row-height').trim();\n if (!raw) return 0;\n // Pure pixel value — fast path, no DOM measurement needed\n if (raw.endsWith('px')) return parseFloat(raw) || 0;\n // Relative units (em, rem, etc.) — resolve from an existing cell's computed min-height.\n // Cells bind to --tbw-row-height via CSS, so their resolved min-height gives the pixel value.\n const cell = this._bodyEl?.querySelector(\n '.data-grid-row:not([style*=\"--tbw-row-height\"]) > .cell',\n ) as HTMLElement | null;\n if (!cell) return 0;\n const minHeight = parseFloat(getComputedStyle(cell).minHeight);\n return minHeight > 0 ? minHeight : 0;\n }\n\n /**\n * Measure actual row height from DOM for plugin-based variable heights.\n * Similar to #measureRowHeight but rebuilds the position cache after measurement.\n * Called after first render when a plugin implements getRowHeight() but user didn't provide a rowHeight function.\n */\n #measureRowHeightForPlugins(): void {\n const firstRow = this._bodyEl?.querySelector('.data-grid-row');\n if (!firstRow) return;\n\n // Find the tallest cell in the row (custom renderers may push some cells taller)\n const cells = firstRow.querySelectorAll('.cell');\n let maxCellHeight = 0;\n cells.forEach((cell) => {\n const h = (cell as HTMLElement).offsetHeight;\n if (h > maxCellHeight) maxCellHeight = h;\n });\n\n const rowRect = (firstRow as HTMLElement).getBoundingClientRect();\n\n // Use the larger of row height or max cell height\n const measuredHeight = Math.max(rowRect.height, maxCellHeight);\n\n // Update rowHeight if measurement is valid and different\n if (measuredHeight > 0) {\n const heightChanged = Math.abs(measuredHeight - this._virtualization.rowHeight) > 1;\n if (heightChanged) {\n this._virtualization.rowHeight = measuredHeight;\n }\n\n // ALWAYS rebuild position cache when this method is called (first render with plugins)\n // The position cache may have been built with the wrong estimated height (e.g., 28px)\n // even if rowHeight was later updated by ResizeObserver (e.g., to 33px)\n this.#virtManager.initializePositionCache();\n\n // Update spacer height with the correct total\n if (this._virtualization.totalHeightEl) {\n const newHeight = this.#virtManager.calculateTotalSpacerHeight(this._rows.length);\n this._virtualization.totalHeightEl.style.height = `${newHeight}px`;\n }\n }\n }\n\n /**\n * Set up scroll-related event listeners on DOM elements.\n * These need to be re-attached when the DOM is recreated (e.g., shell toggle).\n * Uses a separate AbortController that is recreated each time.\n */\n #setupScrollListeners(gridRoot: Element | null): void {\n // Abort any existing scroll listeners before adding new ones\n this.#scrollAbortController?.abort();\n this.#scrollAbortController = new AbortController();\n const scrollSignal = this.#scrollAbortController.signal;\n\n // Faux scrollbar pattern: scroll events come from the fake scrollbar element\n // Content area doesn't scroll - rows are positioned via transforms\n const fauxScrollbar = gridRoot?.querySelector('.faux-vscroll') as HTMLElement;\n const rowsEl = gridRoot?.querySelector('.rows') as HTMLElement;\n\n // Store reference for scroll position reading in refreshVirtualWindow\n this._virtualization.container = fauxScrollbar ?? this;\n\n // Cache whether any plugin has scroll handlers (checked once during setup)\n this.#hasScrollPlugins = this.#pluginManager?.getAll().some((p) => p.onScroll) ?? false;\n\n if (fauxScrollbar && rowsEl) {\n fauxScrollbar.addEventListener(\n 'scroll',\n () => {\n // Fast exit if no scroll processing needed\n if (!this._virtualization.enabled && !this.#hasScrollPlugins) return;\n\n const currentScrollTop = fauxScrollbar.scrollTop;\n const rowHeight = this._virtualization.rowHeight;\n\n // Bypass mode: all rows are rendered, just translate by scroll position\n // No need for virtual window calculations\n if (this._rows.length <= this._virtualization.bypassThreshold) {\n rowsEl.style.transform = `translateY(${-currentScrollTop}px)`;\n } else {\n // Virtualized mode: calculate sub-pixel offset for smooth scrolling\n // Even-aligned start preserves zebra stripe parity\n // DOM nth-child(even) will always match data row parity\n const positionCache = this._virtualization.positionCache;\n let rawStart: number;\n let startRowOffset: number;\n\n if (this._virtualization.variableHeights && positionCache && positionCache.length > 0) {\n // Variable heights: use binary search on position cache\n rawStart = getRowIndexAtOffset(positionCache, currentScrollTop);\n if (rawStart === -1) rawStart = 0;\n const evenAlignedStart = rawStart - (rawStart % 2);\n // Use actual offset from position cache for accurate transform\n startRowOffset = positionCache[evenAlignedStart]?.offset ?? evenAlignedStart * rowHeight;\n } else {\n // Fixed heights: simple division\n rawStart = Math.floor(currentScrollTop / rowHeight);\n const evenAlignedStart = rawStart - (rawStart % 2);\n startRowOffset = evenAlignedStart * rowHeight;\n }\n\n const subPixelOffset = -(currentScrollTop - startRowOffset);\n rowsEl.style.transform = `translateY(${subPixelOffset}px)`;\n }\n\n // Batch content update with requestAnimationFrame\n // Old content stays visible with smooth offset until new content renders\n this.#pendingScrollTop = currentScrollTop;\n if (!this.#scrollRaf) {\n this.#scrollRaf = requestAnimationFrame(() => {\n this.#scrollRaf = 0;\n if (this.#pendingScrollTop !== null) {\n this.#onScrollBatched(this.#pendingScrollTop);\n this.#pendingScrollTop = null;\n }\n });\n }\n },\n { passive: true, signal: scrollSignal },\n );\n\n // Horizontal scroll listener on scroll area\n // This is needed for plugins like column virtualization that need to respond to horizontal scroll\n const scrollArea = this.#renderRoot.querySelector('.tbw-scroll-area') as HTMLElement;\n this.#scrollAreaEl = scrollArea; // Store reference for use in #onScrollBatched\n this._virtualization.scrollAreaEl = scrollArea; // Cache for #calculateTotalSpacerHeight\n if (scrollArea && this.#hasScrollPlugins) {\n scrollArea.addEventListener(\n 'scroll',\n () => {\n // Dispatch horizontal scroll to plugins\n const scrollEvent = this.#pooledScrollEvent;\n scrollEvent.scrollTop = fauxScrollbar.scrollTop;\n scrollEvent.scrollLeft = scrollArea.scrollLeft;\n scrollEvent.scrollHeight = fauxScrollbar.scrollHeight;\n scrollEvent.scrollWidth = scrollArea.scrollWidth;\n scrollEvent.clientHeight = fauxScrollbar.clientHeight;\n scrollEvent.clientWidth = scrollArea.clientWidth;\n this.#pluginManager?.onScroll(scrollEvent);\n },\n { passive: true, signal: scrollSignal },\n );\n }\n\n // Forward wheel events from content area to faux scrollbar\n // Without this, mouse wheel over content wouldn't scroll\n // Listen on .tbw-grid-content to capture wheel events from entire grid area\n // Note: gridRoot may already BE .tbw-grid-content when shell is active, so search from shadow root\n const gridContentEl = this.#renderRoot.querySelector('.tbw-grid-content') as HTMLElement;\n // Use the already-stored scrollArea reference\n const scrollAreaForWheel = this.#scrollAreaEl;\n if (gridContentEl) {\n gridContentEl.addEventListener(\n 'wheel',\n (e: WheelEvent) => {\n // Don't intercept wheel events when a native <select> picker is open.\n // The picker (base-select) renders in the top layer but wheel events\n // still target the grid content — intercepting them would scroll the\n // grid and cause the browser to close the picker popup.\n try {\n if (gridContentEl.querySelector('select:open')) return;\n } catch {\n /* :open pseudo-class not supported — ignore */\n }\n\n // SHIFT+wheel or trackpad deltaX = horizontal scroll\n const isHorizontal = e.shiftKey || Math.abs(e.deltaX) > Math.abs(e.deltaY);\n\n if (isHorizontal && scrollAreaForWheel) {\n const delta = e.shiftKey ? e.deltaY : e.deltaX;\n const { scrollLeft, scrollWidth, clientWidth } = scrollAreaForWheel;\n const canScroll = (delta > 0 && scrollLeft < scrollWidth - clientWidth) || (delta < 0 && scrollLeft > 0);\n if (canScroll) {\n e.preventDefault();\n scrollAreaForWheel.scrollLeft += delta;\n }\n } else if (!isHorizontal) {\n const { scrollTop, scrollHeight, clientHeight } = fauxScrollbar;\n const canScroll =\n (e.deltaY > 0 && scrollTop < scrollHeight - clientHeight) || (e.deltaY < 0 && scrollTop > 0);\n if (canScroll) {\n e.preventDefault();\n fauxScrollbar.scrollTop += e.deltaY;\n }\n }\n // If can't scroll, event bubbles to scroll the page\n },\n { passive: false, signal: scrollSignal },\n );\n\n // Touch scrolling support for mobile devices\n // Supports both vertical (via faux scrollbar) and horizontal (via scroll area) scrolling\n // Includes momentum scrolling for natural \"flick\" behavior\n setupTouchScrollListeners(\n gridContentEl,\n this.#touchState,\n { fauxScrollbar, scrollArea: scrollAreaForWheel },\n scrollSignal,\n );\n }\n }\n\n // Set up delegated event handlers for cell interactions (click, dblclick, keydown)\n // This replaces per-cell event listeners with a single set of delegated handlers\n // Dramatically reduces memory usage: 4 listeners total vs. 30,000+ for large grids\n if (this._bodyEl) {\n setupCellEventDelegation(this, this._bodyEl, scrollSignal);\n }\n\n // Disconnect existing resize observer before creating new one\n // This ensures we observe the NEW viewport element after DOM recreation\n this.#resizeObserver?.disconnect();\n\n // Resize observer to refresh virtualization when viewport size changes\n // (e.g., when footer is added, window resizes, or shell panel toggles)\n if (this._virtualization.viewportEl) {\n this.#resizeObserver = new ResizeObserver(() => {\n // Update cached geometry so refreshVirtualWindow can skip forced layout reads\n this.#updateCachedGeometry();\n // Use scheduler for viewport resize - batches with other pending work\n // The scheduler already batches multiple requests per RAF\n this.#scheduler.requestPhase(RenderPhase.VIRTUALIZATION, 'resize-observer');\n });\n this.#resizeObserver.observe(this._virtualization.viewportEl);\n }\n\n // Note: We no longer need to schedule init-virtualization here since\n // the initial FULL render from #setup already includes virtualization.\n // Requesting it again here caused duplicate renders on initialization.\n\n // Track focus state via data attribute\n // Listen on render root to catch focus events from child elements\n (this.#renderRoot as EventTarget).addEventListener(\n 'focusin',\n () => {\n this.dataset.hasFocus = '';\n },\n { signal: scrollSignal },\n );\n (this.#renderRoot as EventTarget).addEventListener(\n 'focusout',\n (e) => {\n // Only remove if focus is leaving the grid entirely\n // relatedTarget is null when focus leaves the document, or the new focus target\n const newFocus = (e as FocusEvent).relatedTarget as Node | null;\n if (\n !newFocus ||\n (!this.#renderRoot.contains(newFocus) && !this.#focusManager.isInExternalFocusContainer(newFocus))\n ) {\n delete this.dataset.hasFocus;\n }\n },\n { signal: scrollSignal },\n );\n }\n\n /**\n * Set up ResizeObserver on first row to detect height changes.\n * Called after rows are rendered to observe the actual content.\n * Handles dynamic CSS loading, lazy images, font loading, column virtualization, etc.\n */\n #rowHeightObserverSetup = false; // Only set up once per lifecycle\n #setupRowHeightObserver(): void {\n // Only set up once - row height measurement is one-time during initialization\n if (this.#rowHeightObserverSetup) return;\n\n const firstRow = this._bodyEl?.querySelector('.data-grid-row') as HTMLElement | null;\n if (!firstRow) return;\n\n this.#rowHeightObserverSetup = true;\n this.#rowHeightObserver?.disconnect();\n\n // Observe the row element itself, not individual cells.\n // This catches all height changes including:\n // - Custom renderers that push cell height\n // - Column virtualization adding/removing columns\n // - Dynamic content loading (images, fonts)\n // Note: ResizeObserver fires on initial observation in modern browsers,\n // so no separate measurement call is needed.\n this.#rowHeightObserver = new ResizeObserver(() => {\n this.#measureRowHeight();\n });\n this.#rowHeightObserver.observe(firstRow);\n }\n // #endregion\n\n // #region Event System\n /**\n * Add a typed event listener for grid events.\n *\n * This override provides type-safe event handling for DataGrid-specific events.\n * The event detail is automatically typed based on the event name.\n *\n * **Prefer {@link on | grid.on()}** for most use cases — it auto-unwraps the\n * detail payload and returns an unsubscribe function for easy cleanup.\n *\n * Use `addEventListener` when you need standard DOM options like `once`,\n * `capture`, `passive`, or `signal` (AbortController).\n *\n * @example\n * ```typescript\n * // Recommended: use grid.on() instead\n * const off = grid.on('cell-click', (detail) => {\n * console.log(detail.field, detail.value);\n * });\n *\n * // addEventListener is useful when you need DOM listener options\n * grid.addEventListener('cell-click', (e) => {\n * console.log(e.detail.field, e.detail.value);\n * }, { once: true });\n *\n * // Or with AbortController for batch cleanup\n * const controller = new AbortController();\n * grid.addEventListener('sort-change', (e) => {\n * fetchData({ sortBy: e.detail.field });\n * }, { signal: controller.signal });\n * controller.abort(); // removes all listeners tied to this signal\n * ```\n *\n * @category Events\n */\n override addEventListener<K extends keyof DataGridEventMap<T>>(\n type: K,\n listener: (this: DataGridElement<T>, ev: CustomEvent<DataGridEventMap<T>[K]>) => void,\n options?: boolean | AddEventListenerOptions,\n ): void;\n override addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n override addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject | ((ev: CustomEvent) => void),\n options?: boolean | AddEventListenerOptions,\n ): void {\n super.addEventListener(type, listener as EventListener, options);\n }\n\n /**\n * Remove a typed event listener for grid events.\n *\n * @category Events\n */\n override removeEventListener<K extends keyof DataGridEventMap<T>>(\n type: K,\n listener: (this: DataGridElement<T>, ev: CustomEvent<DataGridEventMap<T>[K]>) => void,\n options?: boolean | EventListenerOptions,\n ): void;\n override removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n override removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject | ((ev: CustomEvent) => void),\n options?: boolean | EventListenerOptions,\n ): void {\n super.removeEventListener(type, listener as EventListener, options);\n }\n\n /**\n * Subscribe to a typed grid event. **Recommended** over `addEventListener`.\n *\n * Returns an unsubscribe function — call it to remove the listener.\n * The listener receives the event **detail** as its first argument\n * (no need to dig into `e.detail`), and the raw `CustomEvent` as\n * the second argument when you need `preventDefault()` or `stopPropagation()`.\n *\n * @remarks\n * Advantages over `addEventListener`:\n * - Auto-unwraps `event.detail` — your callback receives the payload directly\n * - Returns an unsubscribe function — no need to keep a reference to the handler\n * - Fully typed — event name → detail type is inferred automatically\n *\n * Use `addEventListener` instead when you need DOM listener options\n * like `once`, `capture`, `passive`, or `signal` (AbortController).\n *\n * @example\n * ```typescript\n * // Basic usage — detail is auto-unwrapped\n * const off = grid.on('cell-click', ({ row, field, value }) => {\n * console.log(`Clicked ${field} = ${value}`);\n * });\n *\n * // Clean up when done\n * off();\n * ```\n *\n * @example\n * ```typescript\n * // Access the raw event for preventDefault/stopPropagation\n * grid.on('cell-activate', (detail, event) => {\n * if (detail.field === 'notes') {\n * event.preventDefault(); // suppress default editing\n * openRichTextEditor(detail.row, detail.cellEl);\n * }\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Multiple subscriptions with easy teardown\n * const unsubs = [\n * grid.on('sort-change', ({ field, direction }) => {\n * console.log(`Sorted by ${field} ${direction === 1 ? 'ASC' : 'DESC'}`);\n * }),\n * grid.on('column-resize', ({ field, width }) => {\n * saveColumnWidth(field, width);\n * }),\n * grid.on('data-change', ({ rowCount }) => {\n * statusBar.textContent = `${rowCount} rows`;\n * }),\n * ];\n *\n * // Teardown all at once\n * unsubs.forEach((off) => off());\n * ```\n *\n * @category Events\n */\n on<K extends keyof DataGridEventMap<T>>(\n type: K,\n listener: (detail: DataGridEventMap<T>[K], event: CustomEvent<DataGridEventMap<T>[K]>) => void,\n ): () => void;\n on(type: string, listener: (detail: unknown, event: CustomEvent) => void): () => void;\n on(type: string, listener: (detail: unknown, event: CustomEvent) => void): () => void {\n const handler = ((e: CustomEvent) => {\n listener(e.detail, e);\n }) as EventListener;\n this.addEventListener(type, handler);\n return () => this.removeEventListener(type, handler);\n }\n\n #emit<D>(eventName: string, detail: D): void {\n this.dispatchEvent(new CustomEvent(eventName, { detail, bubbles: true, composed: true }));\n }\n\n _emitDataChange(): void {\n this.#emit<DataChangeDetail>('data-change', {\n rowCount: this._rows.length,\n sourceRowCount: this.#rows.length,\n });\n }\n\n /** Update ARIA selection attributes on rendered rows/cells */\n #updateAriaSelection(): void {\n // Mark active row and cell with aria-selected\n const rows = this._bodyEl?.querySelectorAll('.data-grid-row');\n rows?.forEach((row, rowIdx) => {\n const isActiveRow = rowIdx === this._focusRow;\n row.setAttribute('aria-selected', String(isActiveRow));\n row.querySelectorAll('.cell').forEach((cell, colIdx) => {\n (cell as HTMLElement).setAttribute('aria-selected', String(isActiveRow && colIdx === this._focusCol));\n });\n });\n }\n // #endregion\n\n // #region Batched Update System\n // Allows multiple property changes within the same microtask to be coalesced\n // into a single update cycle, dramatically reducing redundant renders.\n\n /**\n * Queue an update for a specific property type.\n * All updates queued within the same microtask are batched together.\n */\n #queueUpdate(type: 'rows' | 'columns' | 'gridConfig' | 'fitMode'): void {\n this.#pendingUpdateFlags[type] = true;\n\n // If already queued, skip scheduling\n if (this.#pendingUpdate) return;\n\n this.#pendingUpdate = true;\n // Use queueMicrotask to batch synchronous property sets\n queueMicrotask(() => this.#flushPendingUpdates());\n }\n\n /**\n * Process all pending updates in optimal order.\n * Priority: gridConfig first (may affect all), then columns, rows, fitMode, editMode\n */\n #flushPendingUpdates(): void {\n if (!this.#pendingUpdate || !this.#connected) {\n this.#pendingUpdate = false;\n return;\n }\n\n const flags = this.#pendingUpdateFlags;\n\n // Reset flags before processing to allow new updates during processing\n this.#pendingUpdate = false;\n this.#pendingUpdateFlags = {\n rows: false,\n columns: false,\n gridConfig: false,\n fitMode: false,\n };\n\n // If gridConfig changed, it supersedes columns/fit changes\n // but we still need to handle rows if set separately\n if (flags.gridConfig) {\n this.#applyGridConfigUpdate();\n // Still process rows if set separately (e.g., grid.gridConfig = ...; grid.rows = ...;)\n if (flags.rows) {\n this.#applyRowsUpdate();\n }\n return;\n }\n\n // Process remaining changes in dependency order\n if (flags.columns) {\n this.#applyColumnsUpdate();\n }\n if (flags.rows) {\n this.#applyRowsUpdate();\n }\n if (flags.fitMode) {\n this.#applyFitModeUpdate();\n }\n }\n\n // Individual update applicators - these do the actual work\n #applyRowsUpdate(): void {\n this._rows = Array.isArray(this.#rows) ? [...this.#rows] : [];\n // Rebuild row ID map for O(1) lookups\n this._rebuildRowIdMap();\n // Request a ROWS phase render through the scheduler.\n // This batches with any other pending work (e.g., React adapter's refreshColumns).\n this.#scheduler.requestPhase(RenderPhase.ROWS, 'applyRowsUpdate');\n }\n\n /**\n * Rebuild the row ID map for O(1) lookups.\n * Called when rows array changes.\n */\n _rebuildRowIdMap(): void {\n this.#rowIdMap.clear();\n const getRowId = this.#effectiveConfig.getRowId;\n\n this._rows.forEach((row, index) => {\n const id = tryResolveRowId(row, getRowId);\n if (id !== undefined) {\n this.#rowIdMap.set(id, { row, index });\n }\n // Rows without IDs are skipped - they won't be accessible via getRow/updateRow\n });\n }\n\n #applyColumnsUpdate(): void {\n invalidateCellCache(this);\n this.#configManager.merge();\n this.#setup();\n }\n\n #applyFitModeUpdate(): void {\n this.#configManager.merge();\n const mode = this.#effectiveConfig.fitMode;\n if (mode === 'fixed') {\n this.__didInitialAutoSize = false;\n autoSizeColumns(this);\n } else {\n this._columns.forEach((c: any) => {\n if (!c.__userResized && c.__autoSized) delete c.width;\n });\n updateTemplate(this);\n }\n }\n\n #applyGridConfigUpdate(): void {\n // Parse shell config (title, etc.) - needed for React where gridConfig is set after initial render\n // Note: We call individual parsers here instead of #parseLightDom() because we need to\n // parse tool panels AFTER plugins are initialized (see below)\n parseLightDomShell(this, this.#shellState);\n parseLightDomToolButtons(this, this.#shellState);\n\n const hadShell = !!this.#renderRoot.querySelector('.has-shell');\n const hadToolPanel = !!this.#renderRoot.querySelector('.tbw-tool-panel');\n const accordionSectionsBefore = this.#renderRoot.querySelectorAll('.tbw-accordion-section').length;\n const prevPosition =\n (this.#renderRoot.querySelector('.tbw-tool-panel') as HTMLElement)?.dataset.position ?? 'right';\n\n this.#configManager.parseLightDomColumns(this);\n this.#configManager.merge();\n this.#updatePluginConfigs();\n\n // Re-check variable heights after config merge: rowHeight may have changed\n // from a number to a function (or vice versa) without any plugin changes.\n this.#configureVariableHeights();\n\n // Parse light DOM tool panels AFTER plugins are initialized\n parseLightDomToolPanels(this, this.#shellState, this.#getToolPanelRendererFactory());\n this.#configManager.markSourcesChanged();\n this.#configManager.merge();\n\n const nowNeedsShell = shouldRenderShellHeader(this.#effectiveConfig?.shell);\n const nowHasToolPanels = (this.#effectiveConfig?.shell?.toolPanels?.length ?? 0) > 0;\n const toolPanelCount = this.#effectiveConfig?.shell?.toolPanels?.length ?? 0;\n const newPosition = this.#effectiveConfig?.shell?.toolPanel?.position ?? 'right';\n\n // Full re-render needed if shell state, panel count, or panel position changed\n const needsFullRerender =\n hadShell !== nowNeedsShell ||\n (!hadToolPanel && nowHasToolPanels) ||\n (hadToolPanel && toolPanelCount !== accordionSectionsBefore) ||\n (hadToolPanel && prevPosition !== newPosition);\n\n if (needsFullRerender) {\n // Prepare shell state for re-render (moves toolbar buttons back to original container)\n prepareForRerender(this.#shellState);\n this.#render();\n this.#injectAllPluginStyles();\n this.#afterConnect();\n this._rebuildRowIdMap();\n return;\n }\n\n if (hadShell) {\n this.#updateShellHeaderInPlace();\n }\n\n this._rebuildRowIdMap();\n this.#scheduler.requestPhase(RenderPhase.COLUMNS, 'applyGridConfigUpdate');\n }\n\n /**\n * Update the shell header DOM in place without a full re-render.\n * Handles title, toolbar buttons, and other shell header changes.\n */\n #updateShellHeaderInPlace(): void {\n const shellHeader = this.#renderRoot.querySelector('.tbw-shell-header');\n if (!shellHeader) return;\n\n const title = this.#effectiveConfig.shell?.header?.title ?? this.#shellState.lightDomTitle;\n\n // Update or create title element\n let titleEl = shellHeader.querySelector('.tbw-shell-title') as HTMLElement | null;\n if (title) {\n if (!titleEl) {\n // Create title element if it doesn't exist\n titleEl = document.createElement('h2');\n titleEl.className = 'tbw-shell-title';\n titleEl.setAttribute('part', 'shell-title');\n // Insert at the beginning of the shell header\n shellHeader.insertBefore(titleEl, shellHeader.firstChild);\n }\n titleEl.textContent = title;\n } else if (titleEl) {\n // Remove title element if no title\n titleEl.remove();\n }\n }\n\n // NOTE: Legacy watch handlers have been replaced by the batched update system.\n // The #queueUpdate() method schedules updates which are processed by #flushPendingUpdates()\n // and individual #apply*Update() methods. This coalesces rapid property changes\n // (e.g., setting rows, columns, gridConfig in quick succession) into a single update cycle.\n\n #processColumns(): void {\n // Bump the row-render epoch so that renderVisibleRows knows the column\n // structure may have changed and triggers a full inline rebuild for each\n // pooled row element. This is intentionally done here — NOT inside\n // refreshVirtualWindow — so that a pure ROWS-phase refresh (data change,\n // same columns) can skip the expensive destroy-and-recreate cycle and\n // use fastPatchRow instead.\n this.__rowRenderEpoch++;\n\n // Let plugins process visible columns (column grouping, etc.)\n // Start from base columns (before any plugin transformation) - like #rebuildRowModel uses #rows\n if (this.#pluginManager) {\n // Use base columns as source of truth, falling back to current _columns if not set\n const sourceColumns = this.#baseColumns.length > 0 ? this.#baseColumns : this._columns;\n const visibleCols = sourceColumns.filter((c) => !c.hidden);\n const hiddenCols = sourceColumns.filter((c) => c.hidden);\n const processedColumns = this.#pluginManager.processColumns([...visibleCols]);\n\n // If plugins modified visible columns, update them\n if (processedColumns !== visibleCols) {\n // Build set for quick lookup\n const processedFields = new Set(processedColumns.map((c: ColumnInternal) => c.field));\n\n // Check if this is a complete column replacement (e.g., pivot mode)\n // If no processed columns match original columns, use processed columns directly\n const hasMatchingFields = visibleCols.some((c) => processedFields.has(c.field));\n\n if (!hasMatchingFields && processedColumns.length > 0) {\n // Complete replacement: use processed columns directly (pivot mode)\n // Preserve hidden columns at the end\n this._columns = [...processedColumns, ...hiddenCols] as ColumnInternal<T>[];\n } else {\n // Plugins may have modified, added, or reordered visible columns.\n // Re-interleave hidden columns at their original positions (from sourceColumns)\n // so that showing a column later restores it to its correct position.\n this._columns = this.#mergeColumnsPreservingOrder(\n sourceColumns,\n processedColumns as ColumnInternal<T>[],\n hiddenCols,\n );\n }\n } else {\n // Plugins returned columns unchanged, but we may need to restore from base\n this._columns = [...sourceColumns] as ColumnInternal<T>[];\n }\n }\n }\n\n /**\n * Merge plugin-processed visible columns with hidden columns, preserving original order.\n * Hidden columns are re-inserted at their original positions (from sourceColumns) so that\n * showing a column later restores it to the correct position instead of appending at end.\n *\n * @param sourceColumns - The original columns in correct order (from #baseColumns)\n * @param processedVisible - Plugin-processed visible columns\n * @param hiddenCols - Hidden columns to re-interleave\n * @returns Merged columns array preserving original ordering for hidden columns\n */\n #mergeColumnsPreservingOrder(\n sourceColumns: ColumnInternal<T>[],\n processedVisible: ColumnInternal<T>[],\n hiddenCols: ColumnInternal<T>[],\n ): ColumnInternal<T>[] {\n if (hiddenCols.length === 0) return processedVisible;\n\n // Build a map of processed visible columns by field for O(1) lookup\n const processedMap = new Map<string, ColumnInternal<T>>();\n for (const col of processedVisible) {\n processedMap.set(col.field, col);\n }\n\n // Collect plugin-added columns (not in source) — these go at the end\n const sourceFields = new Set(sourceColumns.map((c) => c.field));\n const pluginAdded: ColumnInternal<T>[] = [];\n for (const col of processedVisible) {\n if (!sourceFields.has(col.field)) {\n pluginAdded.push(col);\n }\n }\n\n // Walk sourceColumns in original order, picking from processed (visible) or hidden\n const result: ColumnInternal<T>[] = [];\n for (const srcCol of sourceColumns) {\n const processed = processedMap.get(srcCol.field);\n if (processed) {\n // Visible column — use the plugin-processed version\n result.push(processed);\n } else if (srcCol.hidden) {\n // Hidden column — keep at original position\n result.push(srcCol);\n }\n // else: column was removed by plugins — skip\n }\n\n // Append any plugin-added columns (e.g., expander) at the end\n result.push(...pluginAdded);\n\n return result;\n }\n\n /** Recompute row model via plugin hooks. */\n #rebuildRowModel(): void {\n // Invalidate cell display value cache - rows are changing\n invalidateCellCache(this);\n\n // Start fresh from original rows (plugins will transform them)\n const originalRows = Array.isArray(this.#rows) ? [...this.#rows] : [];\n\n // Re-apply core sort before plugins so sorted order is maintained on data refresh.\n // This runs BEFORE processRows so grouping/filtering work on sorted data.\n const sortedRows = reapplyCoreSort(this, originalRows);\n\n // Let plugins process rows (they may add, remove, transform rows)\n // Plugins can add markers for specialized rendering via the renderRow hook\n const processedRows = this.#pluginManager?.processRows(sortedRows) ?? sortedRows;\n\n // Store processed rows for rendering\n // Note: processedRows may contain group markers that plugins handle via renderRow hook\n this._rows = processedRows as T[];\n\n // Rebuild row ID map to keep indices in sync with the (possibly sorted) _rows.\n // Without this, #rowIdMap has stale indices from the unsorted copy set in #applyRowsUpdate.\n this._rebuildRowIdMap();\n\n // Rebuild position cache for variable heights (rows may have changed)\n if (this._virtualization.variableHeights) {\n this.#virtManager.initializePositionCache();\n }\n\n this._emitDataChange();\n }\n\n /**\n * Apply animation configuration to CSS custom properties on the host element.\n * This makes the grid's animation settings available to plugins via CSS variables.\n * Called by ConfigManager after merge.\n */\n #applyAnimationConfig(gridConfig: GridConfig<T>): void {\n const config: AnimationConfig = {\n ...DEFAULT_ANIMATION_CONFIG,\n ...gridConfig.animation,\n };\n\n // Resolve animation mode\n const mode = config.mode ?? 'reduced-motion';\n let enabled: 0 | 1 = 1;\n\n if (mode === false || mode === 'off') {\n enabled = 0;\n } else if (mode === true || mode === 'on') {\n enabled = 1;\n }\n // For 'reduced-motion', we leave enabled=1 and let CSS @media query handle it\n\n // Set CSS custom properties\n this.style.setProperty('--tbw-animation-duration', `${config.duration}ms`);\n this.style.setProperty('--tbw-animation-easing', config.easing ?? 'ease-out');\n this.style.setProperty('--tbw-animation-enabled', String(enabled));\n\n // Set data attribute for mode-based CSS selectors\n this.dataset.animationMode = typeof mode === 'boolean' ? (mode ? 'on' : 'off') : mode;\n }\n // #endregion\n\n // #region Internal Helpers\n _renderVisibleRows(start: number, end: number, epoch = this.__rowRenderEpoch): void {\n // Use cached hook to avoid creating closures on every render (hot path optimization)\n if (!this.#renderRowHook) {\n this.#renderRowHook = (row: any, rowEl: HTMLElement, rowIndex: number): boolean => {\n return this.#pluginManager?.renderRow(row, rowEl, rowIndex) ?? false;\n };\n }\n renderVisibleRows(this, start, end, epoch, this.#renderRowHook);\n\n // Re-apply loading state for rows that are currently loading.\n // renderInlineRow clears innerHTML (destroying overlay DOM) and removes the loading class,\n // so we must re-inject the overlay after row rendering completes.\n if (this.#loadingRows.size > 0) {\n for (const rowId of this.#loadingRows) {\n this.#updateRowLoadingState(rowId, true);\n }\n }\n }\n\n // ARIA state - uses external module for pure functions\n #ariaState: AriaState = createAriaState();\n\n /** Updates ARIA row/col counts. Delegates to aria.ts module. */\n _updateAriaCounts(rowCount: number, colCount: number): void {\n updateAriaCounts(this.#ariaState, this.__rowsBodyEl, this._bodyEl, rowCount, colCount);\n }\n\n // --- Methods exposed for extracted managers ---\n\n /** @internal Request a render at the given phase through the scheduler. */\n _requestSchedulerPhase(phase: number, source: string): void {\n this.#scheduler.requestPhase(phase, source);\n }\n\n /** @internal Plugin extra height for spacer calculation. */\n _getPluginExtraHeight(): number {\n return this.#pluginManager?.getExtraHeight() ?? 0;\n }\n\n /** @internal Plugin-specific row height override. */\n _getPluginRowHeight(row: T, index: number): number | undefined {\n return this.#pluginManager?.getRowHeight?.(row, index);\n }\n\n /** @internal Plugin extra height before a given row index. */\n _getPluginExtraHeightBefore(start: number): number {\n return this.#pluginManager?.getExtraHeightBefore?.(start) ?? 0;\n }\n\n /** @internal Let plugins adjust the virtual start index backwards. */\n _adjustPluginVirtualStart(start: number, scrollTop: number, rowHeight: number): number | undefined {\n return this.#pluginManager?.adjustVirtualStart(start, scrollTop, rowHeight);\n }\n\n /** @internal Run plugin afterRender hooks. */\n _afterPluginRender(): void {\n this.#pluginManager?.afterRender();\n }\n\n /** @internal Emit a plugin event through the plugin manager. */\n _emitPluginEvent(event: string, detail: unknown): void {\n this.#pluginManager?.emitPluginEvent(event, detail);\n }\n\n // --- Scheduler pipeline methods ---\n\n /** @internal Merge effective config (FULL/COLUMNS phase). */\n _schedulerMergeConfig(): void {\n this.#configManager.parseLightDomColumns(this);\n this.#configManager.merge();\n this.#updatePluginConfigs();\n validatePluginProperties(this.#effectiveConfig, this.#pluginManager?.getPlugins() ?? [], this.id);\n validatePluginConfigRules(this.#pluginManager?.getPlugins() ?? [], this.id);\n validatePluginIncompatibilities(this.#pluginManager?.getPlugins() ?? [], this.id);\n this.#updateAriaLabels();\n this.#baseColumns = [...this._columns];\n }\n\n /** @internal Process columns through plugins (COLUMNS phase). */\n _schedulerProcessColumns(): void {\n this.#processColumns();\n }\n\n /** @internal Rebuild row model through plugins (ROWS phase). */\n _schedulerProcessRows(): void {\n this.#rebuildRowModel();\n }\n\n /** @internal Render header DOM (HEADER phase). */\n _schedulerRenderHeader(): void {\n renderHeader(this);\n }\n\n /** @internal Update CSS grid template (COLUMNS phase). */\n _schedulerUpdateTemplate(): void {\n updateTemplate(this);\n }\n\n /** @internal Run afterRender hooks and post-render bookkeeping (STYLE phase). */\n _schedulerAfterRender(): void {\n this.#pluginManager?.afterRender();\n\n // Recalculate spacer height after plugins modify the DOM in afterRender.\n if (this._virtualization.enabled && this._virtualization.totalHeightEl) {\n queueMicrotask(() => {\n if (!this._virtualization.totalHeightEl) return;\n const newTotalHeight = this.#virtManager.calculateTotalSpacerHeight(this._rows.length);\n this._virtualization.totalHeightEl.style.height = `${newTotalHeight}px`;\n });\n }\n\n // Auto-size columns on first render if fitMode is 'fixed'\n const mode = this.#effectiveConfig.fitMode;\n if (mode === 'fixed' && !this.__didInitialAutoSize) {\n this.__didInitialAutoSize = true;\n autoSizeColumns(this);\n }\n // Restore focus styling if requested by a plugin\n if (this._restoreFocusAfterRender) {\n this._restoreFocusAfterRender = false;\n ensureCellVisible(this);\n }\n // Set up row height observer after first render (rows are now in DOM)\n if (this._virtualization.enabled && !this.#rowHeightObserverSetup) {\n this.#setupRowHeightObserver();\n }\n // Measure base row height for plugin-based variable heights on first render\n if (this.#needsRowHeightMeasurement) {\n this.#needsRowHeightMeasurement = false;\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n this.#measureRowHeightForPlugins();\n });\n });\n }\n\n // Show loading overlay if loading was set before the grid root was created.\n if (this.#loading) {\n this.#updateLoadingOverlay();\n }\n }\n\n /** @internal Whether the grid is fully connected (scheduler guard). */\n get _schedulerIsConnected(): boolean {\n return this.isConnected && this.#connected;\n }\n\n // Shell controller & config manager support (replaces callback closures)\n /** @internal The grid's host HTMLElement. Use instead of casting `grid as unknown as HTMLElement`. */\n get _hostElement(): HTMLElement {\n return this;\n }\n\n /** @internal The grid's render root element for DOM queries. */\n get _renderRoot(): HTMLElement {\n return this.#renderRoot;\n }\n\n /** @internal Emit a custom event from the grid. */\n _emit(eventName: string, detail: unknown): void {\n this.#emit(eventName, detail);\n }\n\n /** @internal Get accordion expand/collapse icons from effective config. */\n get _accordionIcons(): { expand: IconValue; collapse: IconValue } {\n return {\n expand: this.#effectiveConfig?.icons?.expand ?? DEFAULT_GRID_ICONS.expand,\n collapse: this.#effectiveConfig?.icons?.collapse ?? DEFAULT_GRID_ICONS.collapse,\n };\n }\n\n /** @internal Shell state for config manager shell merging. */\n get _shellState(): ShellState {\n return this.#shellState;\n }\n\n /** @internal Clear the row pool and body element. */\n _clearRowPool(): void {\n this._rowPool.length = 0;\n if (this._bodyEl) this._bodyEl.innerHTML = '';\n this.__rowRenderEpoch++;\n }\n\n /** @internal Run grid setup (DOM rebuild). */\n _setup(): void {\n this.#setup();\n }\n\n /** @internal Apply animation configuration to host element. */\n _applyAnimationConfig(config: GridConfig<T>): void {\n this.#applyAnimationConfig(config);\n }\n\n /** Updates ARIA label and describedby attributes. Delegates to aria.ts module. */\n #updateAriaLabels(): void {\n updateAriaLabels(this.#ariaState, this.__rowsBodyEl, this.#effectiveConfig, this.#shellState);\n }\n\n /**\n * Update the loading overlay visibility.\n * Called when `loading` property changes.\n */\n #updateLoadingOverlay(): void {\n const gridRoot = this.querySelector('.tbw-grid-root');\n if (!gridRoot) return;\n\n if (this.#loading) {\n // Create overlay if it doesn't exist\n if (!this.#loadingOverlayEl) {\n this.#loadingOverlayEl = createLoadingOverlay(this.#effectiveConfig?.loadingRenderer);\n }\n showLoadingOverlay(gridRoot, this.#loadingOverlayEl);\n } else {\n hideLoadingOverlay(this.#loadingOverlayEl);\n }\n }\n\n /**\n * Update a row's loading state in the DOM.\n * @param rowId - The row's unique identifier\n * @param loading - Whether the row is loading\n */\n #updateRowLoadingState(rowId: string, loading: boolean): void {\n // Find the row element by row ID\n const rowData = this.#rowIdMap.get(rowId);\n if (!rowData) return;\n\n const rowEl = this.findRenderedRowElement?.(rowData.index);\n if (!rowEl) return;\n\n setRowLoadingState(rowEl, loading);\n }\n\n /**\n * Update a cell's loading state in the DOM.\n * @param rowId - The row's unique identifier\n * @param field - The column field\n * @param loading - Whether the cell is loading\n */\n #updateCellLoadingState(rowId: string, field: string, loading: boolean): void {\n // Find the row element by row ID\n const rowData = this.#rowIdMap.get(rowId);\n if (!rowData) return;\n\n const rowEl = this.findRenderedRowElement?.(rowData.index);\n if (!rowEl) return;\n\n // Find the cell by field\n const colIndex = this._visibleColumns.findIndex((c) => c.field === field);\n if (colIndex < 0) return;\n\n const cellEl = rowEl.children[colIndex] as HTMLElement | undefined;\n if (!cellEl) return;\n\n setCellLoadingState(cellEl, loading);\n }\n\n /**\n * Request a full grid re-setup through the render scheduler.\n * This method queues all the config merging, column/row processing, and rendering\n * to happen in the next animation frame via the scheduler.\n *\n * Previously this method executed rendering synchronously, but that caused race\n * conditions with framework adapters that also schedule their own render work.\n */\n #setup(): void {\n if (!this.isConnected) return;\n if (!this._headerRowEl || !this._bodyEl) {\n return;\n }\n\n // Read light DOM column configuration (synchronous DOM read)\n this.#configManager.parseLightDomColumns(this);\n\n // Apply initial column state synchronously if present\n // (needs to happen before scheduler to avoid flash of unstyled content)\n if (this.#initialColumnState) {\n const state = this.#initialColumnState;\n this.#initialColumnState = undefined; // Clear to avoid re-applying\n // Temporarily merge config so applyState has columns to work with\n this.#configManager.merge();\n const plugins = (this.#pluginManager?.getAll() ?? []) as BaseGridPlugin[];\n this.#configManager.applyState(state, plugins);\n }\n\n // Ensure legacy inline grid styles are cleared from container\n if (this._bodyEl) {\n this._bodyEl.style.display = '';\n this._bodyEl.style.gridTemplateColumns = '';\n }\n\n // Request full render through scheduler - batches with framework adapter work\n this.#scheduler.requestPhase(RenderPhase.FULL, 'setup');\n }\n\n #onScrollBatched(scrollTop: number): void {\n // PERF: Read all geometry values BEFORE DOM writes to avoid forced synchronous layout.\n // refreshVirtualWindow and onScrollRender write to the DOM (transforms, innerHTML, attributes).\n // Reading geometry after those writes forces the browser to synchronously compute layout.\n // By reading first, we batch reads together, then do all writes.\n let scrollLeft = 0;\n let scrollHeight = 0;\n let scrollWidth = 0;\n let clientHeight = 0;\n let clientWidth = 0;\n if (this.#hasScrollPlugins) {\n const fauxScrollbar = this._virtualization.container;\n const scrollArea = this.#scrollAreaEl;\n scrollLeft = scrollArea?.scrollLeft ?? 0;\n scrollHeight = fauxScrollbar?.scrollHeight ?? 0;\n scrollWidth = scrollArea?.scrollWidth ?? 0;\n clientHeight = fauxScrollbar?.clientHeight ?? 0;\n clientWidth = scrollArea?.clientWidth ?? 0;\n }\n\n // Faux scrollbar pattern: content never scrolls, just update transforms\n // Old content stays visible until new transforms are applied\n const windowChanged = this.refreshVirtualWindow(false);\n\n // Let plugins reapply visual state to recycled DOM elements\n // Only run when the visible window actually changed to avoid expensive DOM queries\n if (windowChanged) {\n this.#pluginManager?.onScrollRender();\n }\n\n // Schedule debounced measurement for variable heights mode\n // This progressively builds up the height cache as user scrolls\n if (this._virtualization.variableHeights) {\n if (this.#scrollMeasureTimeout) {\n clearTimeout(this.#scrollMeasureTimeout);\n }\n // Measure after scroll settles (100ms debounce)\n this.#scrollMeasureTimeout = window.setTimeout(() => {\n this.#scrollMeasureTimeout = 0;\n this.#virtManager.measureRenderedRowHeights(this._virtualization.start, this._virtualization.end);\n }, 100);\n }\n\n // Dispatch to plugins (using cached flag and pooled event object to avoid GC)\n // Geometry values were read before DOM writes above - use pre-read values.\n if (this.#hasScrollPlugins) {\n const scrollEvent = this.#pooledScrollEvent;\n scrollEvent.scrollTop = scrollTop;\n scrollEvent.scrollLeft = scrollLeft;\n scrollEvent.scrollHeight = scrollHeight;\n scrollEvent.scrollWidth = scrollWidth;\n scrollEvent.clientHeight = clientHeight;\n scrollEvent.clientWidth = clientWidth;\n this.#pluginManager?.onScroll(scrollEvent);\n }\n }\n\n /**\n * Find the header row element in the shadow DOM.\n * Used by plugins that need to access header cells for styling or measurement.\n * @group DOM Access\n * @internal Plugin API\n */\n findHeaderRow(): HTMLElement {\n return this.#renderRoot.querySelector('.header-row') as HTMLElement;\n }\n\n /**\n * Find a rendered row element by its data row index.\n * Returns null if the row is not currently rendered (virtualized out of view).\n * Used by plugins that need to access specific row elements for styling or measurement.\n * @group DOM Access\n * @internal Plugin API\n * @param rowIndex - The data row index (not the DOM position)\n */\n findRenderedRowElement(rowIndex: number): HTMLElement | null {\n const s = this._virtualization;\n const poolIndex = rowIndex - s.start;\n if (poolIndex >= 0 && poolIndex < this._rowPool.length && poolIndex < s.end - s.start) {\n return this._rowPool[poolIndex];\n }\n return null;\n }\n\n /**\n * Dispatch a cell click event to the plugin system, then emit a public event.\n * Plugins get first chance to handle the event. After plugins process it,\n * a `cell-click` CustomEvent is dispatched for external listeners.\n *\n * @returns `true` if any plugin handled (consumed) the event, or if consumer canceled\n * @fires cell-activate - Unified activation event (cancelable) - fires FIRST\n * @fires cell-click - Emitted after plugins process the click, with full cell context\n */\n _dispatchCellClick(event: MouseEvent, rowIndex: number, colIndex: number, cellEl: HTMLElement): boolean {\n const row = this._rows[rowIndex];\n // colIndex from data-col is a visible-column index (rendering uses _visibleColumns).\n // Use _visibleColumns so the resolved column matches the clicked cell.\n const col = this._visibleColumns[colIndex];\n if (!row || !col) return false;\n\n const field = col.field;\n const value = (row as Record<string, unknown>)[field];\n\n // Emit cell-activate FIRST (cancelable) - consumers can prevent plugin behavior\n const activateEvent = new CustomEvent('cell-activate', {\n cancelable: true,\n bubbles: true,\n composed: true,\n detail: {\n rowIndex,\n colIndex,\n column: col,\n field,\n value,\n row,\n cellEl,\n trigger: 'pointer' as const,\n originalEvent: event,\n },\n });\n this.dispatchEvent(activateEvent);\n\n // If consumer canceled, don't let plugins handle it\n if (activateEvent.defaultPrevented) {\n return true; // Treated as \"handled\"\n }\n\n const cellClickEvent: CellClickEvent = {\n row,\n rowIndex,\n colIndex,\n column: col,\n field,\n value,\n cellEl,\n originalEvent: event,\n };\n\n // Let plugins handle (editing, selection, etc.)\n const handled = this.#pluginManager?.onCellClick(cellClickEvent) ?? false;\n\n // Emit informational cell-click event for external listeners\n this.#emit('cell-click', cellClickEvent);\n\n return handled;\n }\n\n /**\n * Dispatch a row click event to the plugin system, then emit a public event.\n * Plugins get first chance to handle the event. After plugins process it,\n * a `row-click` CustomEvent is dispatched for external listeners.\n *\n * @returns `true` if any plugin handled (consumed) the event\n * @fires row-click - Emitted after plugins process the click, with full row context\n */\n _dispatchRowClick(event: MouseEvent, rowIndex: number, row: any, rowEl: HTMLElement): boolean {\n if (!row) return false;\n\n const rowClickEvent: RowClickEvent = {\n rowIndex,\n row,\n rowEl,\n originalEvent: event,\n };\n\n // Let plugins handle first\n const handled = this.#pluginManager?.onRowClick(rowClickEvent) ?? false;\n\n // Emit public event for external listeners (reuse same event object)\n this.#emit('row-click', rowClickEvent);\n\n return handled;\n }\n\n /**\n * Dispatch a header click event to the plugin system.\n * Returns true if any plugin handled the event.\n */\n _dispatchHeaderClick(event: MouseEvent | KeyboardEvent, col: ColumnConfig, headerEl: HTMLElement): boolean {\n if (!col) return false;\n\n const headerClickEvent: HeaderClickEvent = {\n colIndex: this._columns.indexOf(col as ColumnInternal<T>),\n field: col.field,\n column: col,\n headerEl,\n originalEvent: event,\n };\n\n return this.#pluginManager?.onHeaderClick(headerClickEvent) ?? false;\n }\n\n /**\n * Dispatch a keyboard event to the plugin system.\n * Returns true if any plugin handled the event.\n */\n _dispatchKeyDown(event: KeyboardEvent): boolean {\n return this.#pluginManager?.onKeyDown(event) ?? false;\n }\n\n /**\n * Get horizontal scroll boundary offsets from plugins.\n * Used by keyboard navigation to ensure focused cells are fully visible\n * when plugins like pinned columns obscure part of the scroll area.\n */\n _getHorizontalScrollOffsets(\n rowEl?: HTMLElement,\n focusedCell?: HTMLElement,\n ): { left: number; right: number; skipScroll?: boolean } {\n return this.#pluginManager?.getHorizontalScrollOffsets(rowEl, focusedCell) ?? { left: 0, right: 0 };\n }\n\n /**\n * Query all plugins with a generic query and collect responses.\n * This enables inter-plugin communication without the core knowing plugin-specific concepts.\n * @group Plugin Communication\n * @internal Plugin API\n *\n * @example\n * // Check if any plugin vetoes moving a column\n * const responses = grid.queryPlugins<boolean>({ type: 'canMoveColumn', context: column });\n * const canMove = !responses.includes(false);\n *\n * @deprecated Use the simplified `query<T>(type, context)` method instead. Will be removed in v2.\n */\n queryPlugins<T>(query: PluginQuery): T[] {\n return this.#pluginManager?.queryPlugins<T>(query) ?? [];\n }\n\n /**\n * Query plugins with a simplified API.\n * This is a convenience wrapper around `queryPlugins` that uses a flat signature.\n *\n * @param type - The query type (e.g., 'canMoveColumn')\n * @param context - The query context/parameters\n * @returns Array of non-undefined responses from plugins\n * @group Plugin Communication\n * @internal Plugin API\n *\n * @example\n * // Check if any plugin vetoes moving a column\n * const responses = grid.query<boolean>('canMoveColumn', column);\n * const canMove = !responses.includes(false);\n *\n * // Get context menu items from all plugins\n * const items = grid.query<ContextMenuItem[]>('getContextMenuItems', params).flat();\n */\n query<T>(type: string, context: unknown): T[] {\n return this.#pluginManager?.queryPlugins<T>({ type, context }) ?? [];\n }\n\n /**\n * Dispatch cell mouse events for drag operations.\n * Returns true if any plugin started a drag.\n * @group Event Dispatching\n * @internal Plugin API - called by event-delegation.ts\n */\n _dispatchCellMouseDown(event: CellMouseEvent): boolean {\n return this.#pluginManager?.onCellMouseDown(event) ?? false;\n }\n\n /**\n * Dispatch cell mouse move during drag.\n * @group Event Dispatching\n * @internal Plugin API - called by event-delegation.ts\n */\n _dispatchCellMouseMove(event: CellMouseEvent): void {\n this.#pluginManager?.onCellMouseMove(event);\n }\n\n /**\n * Dispatch cell mouse up to end drag.\n * @group Event Dispatching\n * @internal Plugin API - called by event-delegation.ts\n */\n _dispatchCellMouseUp(event: CellMouseEvent): void {\n this.#pluginManager?.onCellMouseUp(event);\n }\n\n /**\n * Call afterCellRender hook on all plugins.\n * This is called by rows.ts for each cell after it's rendered,\n * allowing plugins to modify cells during render rather than\n * requiring expensive post-render DOM queries.\n *\n * @group Plugin Hooks\n * @internal Plugin API - called by rows.ts\n */\n _afterCellRender(context: AfterCellRenderContext<T>): void {\n // Cast needed because PluginManager uses unknown for row type\n this.#pluginManager?.afterCellRender(context as AfterCellRenderContext);\n }\n\n /**\n * Check if any plugin has registered an afterCellRender hook.\n * Used to skip the hook call entirely for performance when no plugins need it.\n *\n * @group Plugin Hooks\n * @internal Plugin API - called by rows.ts\n */\n _hasAfterCellRenderHook(): boolean {\n return this.#pluginManager?.hasAfterCellRenderHook() ?? false;\n }\n\n /**\n * Call afterRowRender hook on all plugins that have registered for it.\n * Called by rows.ts after each row is completely rendered.\n *\n * @param context - Context containing row data, index, and DOM element\n *\n * @group Plugin Hooks\n * @internal Plugin API - called by rows.ts\n */\n _afterRowRender(context: AfterRowRenderContext<T>): void {\n // Cast needed because PluginManager uses unknown for row type\n this.#pluginManager?.afterRowRender(context as AfterRowRenderContext);\n }\n\n /**\n * Check if any plugin has registered an afterRowRender hook.\n * Used to skip the hook call entirely for performance when no plugins need it.\n *\n * @group Plugin Hooks\n * @internal Plugin API - called by rows.ts\n */\n _hasAfterRowRenderHook(): boolean {\n return this.#pluginManager?.hasAfterRowRenderHook() ?? false;\n }\n\n /**\n * Wait for the grid to be ready.\n * Resolves once the component has finished initial setup, including\n * column inference, plugin initialization, and first render.\n *\n * @group Lifecycle\n * @returns Promise that resolves when the grid is ready\n *\n * @example\n * ```typescript\n * const grid = queryGrid('tbw-grid');\n * await grid.ready();\n * console.log('Grid is ready, rows:', grid.rows.length);\n * ```\n */\n async ready(): Promise<void> {\n return this.#readyPromise;\n }\n\n /**\n * Force a full layout/render cycle.\n * Use this after programmatic changes that require re-measurement,\n * such as container resize or dynamic style changes.\n *\n * @group Lifecycle\n * @returns Promise that resolves when the render cycle completes\n *\n * @example\n * ```typescript\n * // After resizing the container\n * container.style.width = '800px';\n * await grid.forceLayout();\n * console.log('Grid re-rendered');\n * ```\n */\n async forceLayout(): Promise<void> {\n // Request a full render cycle through the scheduler\n this.#scheduler.requestPhase(RenderPhase.FULL, 'forceLayout');\n // Wait for the render cycle to complete\n return this.#scheduler.whenReady();\n }\n\n /**\n * Get a frozen snapshot of the current effective configuration.\n * The returned object is read-only and reflects all merged config sources.\n *\n * @group Configuration\n * @returns Promise resolving to frozen configuration object\n *\n * @example\n * ```typescript\n * const config = await grid.getConfig();\n * console.log('Columns:', config.columns?.length);\n * console.log('Fit mode:', config.fitMode);\n * ```\n */\n async getConfig(): Promise<Readonly<GridConfig<T>>> {\n return Object.freeze({ ...(this.#effectiveConfig || {}) });\n }\n // #endregion\n\n // #region Row API (delegated to RowManager)\n /**\n * Get the unique ID for a row.\n * Uses the configured `getRowId` function or falls back to `row.id` / `row._id`.\n *\n * @group Data Management\n * @param row - The row object\n * @returns The row's unique identifier\n * @throws Error if no ID can be determined\n *\n * @example\n * ```typescript\n * const id = grid.getRowId(row);\n * console.log('Row ID:', id);\n * ```\n */\n getRowId(row: T): string {\n return resolveRowIdOrThrow(row, this.id, this.#effectiveConfig.getRowId);\n }\n\n /**\n * Get a row by its ID.\n * O(1) lookup via internal Map.\n *\n * @group Data Management\n * @param id - Row identifier (from getRowId)\n * @returns The row object, or undefined if not found\n *\n * @example\n * ```typescript\n * const row = grid.getRow('cargo-123');\n * if (row) {\n * console.log('Found:', row.name);\n * }\n * ```\n */\n getRow(id: string): T | undefined {\n return this.#rowManager.getRow(id);\n }\n\n /**\n * Get a row and its current index by ID.\n * Used internally by plugins to resolve a row's current position in `_rows`\n * after sorting, filtering, or rows replacement.\n * @internal\n */\n _getRowEntry(id: string): { row: T; index: number } | undefined {\n return this.#rowIdMap.get(id);\n }\n\n /**\n * Update a row by ID.\n * Mutates the row in-place and emits `cell-change` for each changed field.\n *\n * @group Data Management\n * @param id - Row identifier (from getRowId)\n * @param changes - Partial row data to merge\n * @param source - Origin of update (default: 'api')\n * @throws Error if row is not found\n * @fires cell-change - For each field that changed\n *\n * @example\n * ```typescript\n * // WebSocket update handler\n * socket.on('cargo-update', (data) => {\n * grid.updateRow(data.id, { status: data.status, eta: data.eta });\n * });\n * ```\n */\n updateRow(id: string, changes: Partial<T>, source: UpdateSource = 'api'): void {\n this.#rowManager.updateRow(id, changes, source);\n }\n\n /**\n * Batch update multiple rows.\n * More efficient than multiple `updateRow()` calls - single render cycle.\n *\n * @group Data Management\n * @param updates - Array of { id, changes } objects\n * @param source - Origin of updates (default: 'api')\n * @throws Error if any row is not found\n * @fires cell-change - For each field that changed on each row\n *\n * @example\n * ```typescript\n * // Bulk status update\n * grid.updateRows([\n * { id: 'cargo-1', changes: { status: 'shipped' } },\n * { id: 'cargo-2', changes: { status: 'shipped' } },\n * ]);\n * ```\n */\n updateRows(updates: Array<{ id: string; changes: Partial<T> }>, source: UpdateSource = 'api'): void {\n this.#rowManager.updateRows(updates, source);\n }\n\n /**\n * Animate a row at the specified index.\n * Applies a visual animation to highlight changes, insertions, or removals.\n * Returns a `Promise` that resolves when the animation completes.\n *\n * **Animation types:**\n * - `'change'`: Flash highlight (for data changes, e.g., after cell edit)\n * - `'insert'`: Slide-in animation (for newly added rows)\n * - `'remove'`: Fade-out animation (for rows being removed)\n *\n * The animation is purely visual - it does not affect the data.\n * For remove animations, the row remains in the DOM until animation completes.\n *\n * @group Row Animation\n * @param rowIndex - Index of the row in the current row set\n * @param type - Type of animation to apply\n * @returns `true` if the row was found and animated, `false` otherwise\n *\n * @example\n * ```typescript\n * // Highlight a row and wait for the animation to finish\n * await grid.animateRow(rowIndex, 'change');\n *\n * // Fire-and-forget (animation runs in background)\n * grid.animateRow(rowIndex, 'insert');\n * ```\n */\n animateRow(rowIndex: number, type: RowAnimationType): Promise<boolean> {\n return animateRow(this, rowIndex, type);\n }\n\n /**\n * Animate multiple rows at once.\n * More efficient than calling `animateRow()` multiple times.\n * Returns a `Promise` that resolves when all animations complete.\n *\n * @group Row Animation\n * @param rowIndices - Indices of the rows to animate\n * @param type - Type of animation to apply to all rows\n * @returns Number of rows that were actually animated (visible in viewport)\n *\n * @example\n * ```typescript\n * // Highlight all changed rows after bulk update\n * const changedIndices = [0, 2, 5];\n * await grid.animateRows(changedIndices, 'change');\n * ```\n */\n animateRows(rowIndices: number[], type: RowAnimationType): Promise<number> {\n return animateRows(this, rowIndices, type);\n }\n\n /**\n * Animate a row by its ID.\n * Uses the configured `getRowId` function to find the row.\n * Returns a `Promise` that resolves when the animation completes.\n *\n * @group Row Animation\n * @param rowId - The row's unique identifier (from getRowId)\n * @param type - Type of animation to apply\n * @returns `true` if the row was found and animated, `false` otherwise\n *\n * @example\n * ```typescript\n * // Highlight a row after real-time update\n * socket.on('row-updated', async (data) => {\n * grid.updateRow(data.id, data.changes);\n * await grid.animateRowById(data.id, 'change');\n * });\n * ```\n */\n animateRowById(rowId: string, type: RowAnimationType): Promise<boolean> {\n return animateRowById(this, rowId, type);\n }\n\n /**\n * Insert a row at a specific position in the current view.\n *\n * The row is spliced into the visible (processed) row array at `index` and\n * appended to the source data so that future pipeline runs (sort, filter,\n * group) include it. No plugin processing is triggered — the row stays\n * exactly where you place it until the next full pipeline run.\n *\n * By default, an `'insert'` animation is applied. Pass `animate: false` to\n * skip the animation. The returned `Promise` resolves when the animation\n * completes (or immediately when `animate` is `false`).\n *\n * @group Data Management\n * @param index - Visible row index at which to insert (0-based)\n * @param row - The row data object to insert\n * @param animate - Whether to apply an 'insert' animation (default: `true`)\n *\n * @example\n * ```typescript\n * // Insert with animation (default)\n * grid.insertRow(3, { id: nextId(), name: '', status: 'Draft' });\n *\n * // Insert without animation\n * grid.insertRow(3, newRow, false);\n *\n * // Await animation completion\n * await grid.insertRow(3, newRow);\n * ```\n */\n async insertRow(index: number, row: T, animate = true): Promise<void> {\n return this.#rowManager.insertRow(index, row, animate);\n }\n\n /**\n * Remove a row at a specific position in the current view.\n *\n * The row is removed from both the visible (processed) row array and the\n * source data. No plugin processing is triggered — remaining rows keep their\n * current positions until the next full pipeline run.\n *\n * By default, a `'remove'` animation plays before the row is removed.\n * Pass `animate: false` to remove immediately. When animated, `await` the\n * result to ensure the row has been fully removed from data.\n *\n * @group Data Management\n * @param index - Visible row index to remove (0-based)\n * @param animate - Whether to apply a 'remove' animation first (default: `true`)\n * @returns The removed row object, or `undefined` if index was out of range\n *\n * @example\n * ```typescript\n * // Remove with fade-out animation (default)\n * await grid.removeRow(5);\n *\n * // Remove immediately, no animation\n * const removed = await grid.removeRow(5, false);\n * ```\n */\n async removeRow(index: number, animate = true): Promise<T | undefined> {\n return this.#rowManager.removeRow(index, animate);\n }\n\n /**\n * Apply a batch of row mutations (add, update, remove) in a single render cycle.\n *\n * This is the most efficient way to apply multiple row changes at once — ideal\n * for streaming data from WebSocket, SSE, or any real-time source.\n *\n * Operations are applied in order: removes → updates → adds. This ensures\n * updates don't target rows about to be removed, and adds don't collide\n * with existing rows.\n *\n * @group Data Management\n * @param transaction - The mutations to apply\n * @param animate - Whether to animate the changes (default: `true`)\n * @returns Result with the actual row objects affected\n *\n * @example\n * ```typescript\n * // Apply a mixed transaction\n * const result = await grid.applyTransaction({\n * add: [{ id: 'new-1', name: 'Alice' }],\n * update: [{ id: 'emp-5', changes: { status: 'Inactive' } }],\n * remove: [{ id: 'emp-3' }],\n * });\n *\n * // Wire up a WebSocket stream\n * ws.onmessage = (e) => {\n * const event = JSON.parse(e.data);\n * grid.applyTransaction({\n * [event.type]: event.type === 'update'\n * ? [{ id: event.rowId, changes: event.changes }]\n * : event.type === 'add'\n * ? [event.row]\n * : [{ id: event.rowId }],\n * });\n * };\n * ```\n */\n async applyTransaction(transaction: RowTransaction<T>, animate = true): Promise<TransactionResult<T>> {\n return this.#rowManager.applyTransaction(transaction, animate);\n }\n\n /**\n * Apply a transaction asynchronously, batching rapid calls into a single render.\n *\n * Ideal for high-frequency streaming where many small updates arrive faster\n * than the browser can render. All transactions queued within the same\n * animation frame are merged and applied together.\n *\n * Animations are disabled for batched transactions to avoid visual overload.\n *\n * @group Data Management\n * @param transaction - The mutations to apply\n * @returns Result with the actual row objects affected (after batching)\n *\n * @example\n * ```typescript\n * // High-frequency WebSocket — updates batched into single renders\n * ws.onmessage = (e) => {\n * const event = JSON.parse(e.data);\n * grid.applyTransactionAsync({\n * update: [{ id: event.id, changes: event.changes }],\n * });\n * };\n * ```\n */\n applyTransactionAsync(transaction: RowTransaction<T>): Promise<TransactionResult<T>> {\n return this.#rowManager.applyTransactionAsync(transaction);\n }\n\n /**\n * Suspend row processing for the next rows update.\n *\n * @deprecated This method is a no-op. Use {@link insertRow} or {@link removeRow}\n * instead, which correctly preserve the current sort/filter view while adding\n * or removing individual rows. Will be removed in v2.\n *\n * @group Lifecycle\n */\n suspendProcessing(): void {\n // No-op — kept for backwards compatibility.\n }\n // #endregion\n\n // #region Focus & Navigation API (delegated to FocusManager)\n /**\n * Move focus to a specific cell.\n *\n * Accepts a column index (into the visible columns array) or a field name.\n * The grid scrolls the cell into view and applies focus styling.\n *\n * @group Focus & Navigation\n * @param rowIndex - The row index to focus (0-based, in the current processed row array)\n * @param column - Column index (0-based into visible columns) or field name string\n *\n * @example\n * ```typescript\n * // Focus by column index\n * grid.focusCell(0, 2);\n *\n * // Focus by field name\n * grid.focusCell(5, 'email');\n * ```\n */\n focusCell(rowIndex: number, column: number | string): void {\n this.#focusManager.focusCell(rowIndex, column);\n }\n\n /**\n * The currently focused cell position, or `null` if no rows are loaded.\n *\n * Returns a snapshot object with the row index, visible column index, and\n * the field name of the focused column.\n *\n * @group Focus & Navigation\n *\n * @example\n * ```typescript\n * const cell = grid.focusedCell;\n * if (cell) {\n * console.log(`Focused on row ${cell.rowIndex}, column \"${cell.field}\"`);\n * }\n * ```\n */\n get focusedCell(): { rowIndex: number; colIndex: number; field: string } | null {\n return this.#focusManager.focusedCell;\n }\n\n /**\n * Scroll the viewport so a row is visible.\n *\n * Uses the grid's internal virtualization state (row height, position cache)\n * to calculate the correct scroll offset, including support for variable\n * row heights and grouped rows.\n *\n * @group Focus & Navigation\n * @param rowIndex - Row index (0-based, in the current processed row array)\n * @param options - Alignment and scroll behavior\n *\n * @example\n * ```typescript\n * // Scroll to row, only if not already visible\n * grid.scrollToRow(42);\n *\n * // Center the row in the viewport with smooth scrolling\n * grid.scrollToRow(42, { align: 'center', behavior: 'smooth' });\n * ```\n */\n scrollToRow(rowIndex: number, options?: ScrollToRowOptions): void {\n this.#focusManager.scrollToRow(rowIndex, options);\n }\n\n /**\n * Scroll the viewport so a row is visible, identified by its unique ID.\n *\n * Uses {@link GridConfig.getRowId | getRowId} (or the fallback `row.id` / `row._id`)\n * to find the row in the current (possibly sorted/filtered) data, then delegates\n * to {@link scrollToRow}.\n *\n * @group Focus & Navigation\n * @param rowId - The row's unique identifier\n * @param options - Alignment and scroll behavior\n *\n * @example\n * ```typescript\n * // After inserting a row, scroll to it by ID\n * grid.scrollToRowById('emp-42', { align: 'center' });\n * ```\n */\n scrollToRowById(rowId: string, options?: ScrollToRowOptions): void {\n this.#focusManager.scrollToRowById(rowId, options);\n }\n // #endregion\n\n // #region Column API\n /**\n * Show or hide a column by field name.\n *\n * @group Column Visibility\n * @param field - The field name of the column to modify\n * @param visible - Whether the column should be visible\n * @returns `true` if the visibility changed, `false` if unchanged\n * @fires column-state-change - Emitted when the visibility changes\n *\n * @example\n * ```typescript\n * // Hide the email column\n * grid.setColumnVisible('email', false);\n *\n * // Show it again\n * grid.setColumnVisible('email', true);\n * ```\n */\n setColumnVisible(field: string, visible: boolean): boolean {\n const result = this.#configManager.setColumnVisible(field, visible);\n if (result) {\n this.requestStateChange();\n }\n return result;\n }\n\n /**\n * Toggle a column's visibility.\n *\n * @group Column Visibility\n * @param field - The field name of the column to toggle\n * @returns The new visibility state (`true` = visible, `false` = hidden)\n * @fires column-state-change - Emitted when the visibility changes\n *\n * @example\n * ```typescript\n * // Toggle the notes column visibility\n * const isNowVisible = grid.toggleColumnVisibility('notes');\n * console.log(`Notes column is now ${isNowVisible ? 'visible' : 'hidden'}`);\n * ```\n */\n toggleColumnVisibility(field: string): boolean {\n const result = this.#configManager.toggleColumnVisibility(field);\n if (result) {\n this.requestStateChange();\n }\n return result;\n }\n\n /**\n * Check if a column is currently visible.\n *\n * @group Column Visibility\n * @param field - The field name to check\n * @returns `true` if the column is visible, `false` if hidden\n *\n * @example\n * ```typescript\n * if (grid.isColumnVisible('email')) {\n * console.log('Email column is showing');\n * }\n * ```\n */\n isColumnVisible(field: string): boolean {\n return this.#configManager.isColumnVisible(field);\n }\n\n /**\n * Show all columns, resetting any hidden columns to visible.\n *\n * @group Column Visibility\n * @fires column-state-change - Emitted when column visibility changes\n * @example\n * ```typescript\n * // Reset button handler\n * resetButton.onclick = () => grid.showAllColumns();\n * ```\n */\n showAllColumns(): void {\n this.#configManager.showAllColumns();\n this.requestStateChange();\n }\n\n /**\n * Get metadata for all columns including visibility state.\n * Useful for building a column picker UI.\n *\n * @group Column Visibility\n * @returns Array of column info objects\n *\n * @example\n * ```typescript\n * // Build a column visibility menu\n * const columns = grid.getAllColumns();\n * columns.forEach(col => {\n * if (!col.utility) { // Skip utility columns like selection checkbox\n * const menuItem = document.createElement('label');\n * menuItem.innerHTML = `\n * <input type=\"checkbox\" ${col.visible ? 'checked' : ''}>\n * ${col.header}\n * `;\n * menuItem.querySelector('input').onchange = () =>\n * grid.toggleColumnVisibility(col.field);\n * menu.appendChild(menuItem);\n * }\n * });\n * ```\n */\n getAllColumns(): Array<{\n field: string;\n header: string;\n visible: boolean;\n lockVisible?: boolean;\n utility?: boolean;\n }> {\n return this.#configManager.getAllColumns();\n }\n\n /**\n * Set the display order of columns.\n *\n * @group Column Order\n * @param order - Array of field names in desired order\n * @fires column-state-change - Emitted when column order changes\n *\n * @example\n * ```typescript\n * // Move 'status' column to first position\n * const currentOrder = grid.getColumnOrder();\n * const newOrder = ['status', ...currentOrder.filter(f => f !== 'status')];\n * grid.setColumnOrder(newOrder);\n * ```\n */\n setColumnOrder(order: string[]): void {\n this.#configManager.setColumnOrder(order);\n this.requestStateChange();\n }\n\n /**\n * Get the current column display order.\n *\n * @group Column Order\n * @returns Array of field names in display order\n *\n * @example\n * ```typescript\n * const order = grid.getColumnOrder();\n * console.log('Columns:', order.join(', '));\n * ```\n */\n getColumnOrder(): string[] {\n return this.#configManager.getColumnOrder();\n }\n\n /**\n * Get the current column state for persistence.\n * Returns a serializable object including column order, widths, visibility,\n * sort state, and any plugin-specific state.\n *\n * Use this to save user preferences to localStorage or a database.\n *\n * @group State Persistence\n * @returns Serializable column state object\n *\n * @example\n * ```typescript\n * // Save state to localStorage\n * const state = grid.getColumnState();\n * localStorage.setItem('gridState', JSON.stringify(state));\n *\n * // Later, restore the state\n * const saved = localStorage.getItem('gridState');\n * if (saved) {\n * grid.columnState = JSON.parse(saved);\n * }\n * ```\n */\n getColumnState(): GridColumnState {\n const plugins = this.#pluginManager?.getAll() ?? [];\n return this.#configManager.collectState(plugins as BaseGridPlugin[]);\n }\n\n /**\n * Set the column state, restoring all saved preferences.\n * Can be set before or after grid initialization.\n *\n * @group State Persistence\n * @fires column-state-change - Emitted after state is applied (if grid is initialized)\n * @example\n * ```typescript\n * // Restore saved state on page load\n * const grid = queryGrid('tbw-grid');\n * const saved = localStorage.getItem('myGridState');\n * if (saved) {\n * grid.columnState = JSON.parse(saved);\n * }\n * ```\n */\n set columnState(state: GridColumnState | undefined) {\n if (!state) return;\n\n // Store for use after initialization if called before ready\n this.#initialColumnState = state;\n this.#configManager.initialColumnState = state;\n\n // If already initialized, apply immediately\n if (this.#initialized) {\n this.#applyColumnState(state);\n }\n }\n\n /**\n * Get the current column state.\n * Alias for `getColumnState()` for property-style access.\n * @group State Persistence\n */\n get columnState(): GridColumnState | undefined {\n return this.getColumnState();\n }\n\n /**\n * Apply column state internally.\n */\n #applyColumnState(state: GridColumnState): void {\n const plugins = (this.#pluginManager?.getAll() ?? []) as BaseGridPlugin[];\n this.#configManager.applyState(state, plugins);\n\n // Re-setup to apply changes\n this.#setup();\n }\n\n /**\n * Request a state change event to be emitted.\n * Called internally after resize, reorder, visibility, or sort changes.\n * Plugins should call this after changing their state.\n * The event is debounced to avoid excessive events during drag operations.\n * @group State Persistence\n * @internal Plugin API\n */\n requestStateChange(): void {\n const plugins = (this.#pluginManager?.getAll() ?? []) as BaseGridPlugin[];\n this.#configManager.requestStateChange(plugins);\n }\n\n /**\n * Reset column state to initial configuration.\n * Clears all user modifications including order, widths, visibility, and sort.\n *\n * @group State Persistence\n * @fires column-state-change - Emitted after state is reset\n * @example\n * ```typescript\n * // Reset button handler\n * resetBtn.onclick = () => {\n * grid.resetColumnState();\n * localStorage.removeItem('gridState');\n * };\n * ```\n */\n resetColumnState(): void {\n // Clear initial state\n this.#initialColumnState = undefined;\n this.__originalOrder = [];\n\n // Use ConfigManager to reset state\n const plugins = (this.#pluginManager?.getAll() ?? []) as BaseGridPlugin[];\n this.#configManager.resetState(plugins);\n\n // Re-initialize columns from config\n this.#configManager.merge();\n this.#setup();\n }\n // #endregion\n\n // #region Shell & Tool Panel API\n /**\n * Check if the tool panel sidebar is currently open.\n *\n * The tool panel is an accordion-based sidebar that contains sections\n * registered by plugins or via `registerToolPanel()`.\n *\n * @group Tool Panel\n * @example\n * ```typescript\n * // Conditionally show/hide a \"toggle panel\" button\n * const isPanelOpen = grid.isToolPanelOpen;\n * toggleButton.textContent = isPanelOpen ? 'Close Panel' : 'Open Panel';\n * ```\n */\n get isToolPanelOpen(): boolean {\n return this.#shellController.isPanelOpen;\n }\n\n /**\n * The default row height in pixels.\n *\n * For fixed heights, this is the configured or CSS-measured row height.\n * For variable heights, this is the average/estimated row height.\n * Plugins should use this instead of hardcoding row heights.\n *\n * @group Virtualization\n */\n get defaultRowHeight(): number {\n return this._virtualization.rowHeight;\n }\n\n /**\n * Get the IDs of currently expanded accordion sections in the tool panel.\n *\n * Multiple sections can be expanded simultaneously in the accordion view.\n *\n * @group Tool Panel\n * @example\n * ```typescript\n * // Check which sections are expanded\n * const expanded = grid.expandedToolPanelSections;\n * console.log('Expanded sections:', expanded);\n * // e.g., ['columnVisibility', 'filtering']\n * ```\n */\n get expandedToolPanelSections(): string[] {\n return this.#shellController.expandedSections;\n }\n\n /**\n * Open the tool panel sidebar.\n *\n * The tool panel displays an accordion view with all registered panel sections.\n * Each section can be expanded/collapsed independently.\n *\n * @group Tool Panel\n * @example\n * ```typescript\n * // Open the tool panel when a toolbar button is clicked\n * settingsButton.addEventListener('click', () => {\n * grid.openToolPanel();\n * });\n * ```\n */\n openToolPanel(): void {\n this.#shellController.openToolPanel();\n }\n\n /**\n * Close the tool panel sidebar.\n *\n * @group Tool Panel\n * @example\n * ```typescript\n * // Close the panel after user makes a selection\n * grid.closeToolPanel();\n * ```\n */\n closeToolPanel(): void {\n this.#shellController.closeToolPanel();\n }\n\n /**\n * Toggle the tool panel sidebar open or closed.\n *\n * @group Tool Panel\n * @example\n * ```typescript\n * // Wire up a toggle button\n * toggleButton.addEventListener('click', () => {\n * grid.toggleToolPanel();\n * });\n * ```\n */\n toggleToolPanel(): void {\n this.#shellController.toggleToolPanel();\n }\n\n /**\n * Toggle an accordion section expanded or collapsed within the tool panel.\n *\n * @group Tool Panel\n * @param sectionId - The ID of the section to toggle (matches `ToolPanelDefinition.id`)\n *\n * @example\n * ```typescript\n * // Expand the column visibility section programmatically\n * grid.openToolPanel();\n * grid.toggleToolPanelSection('columnVisibility');\n * ```\n */\n toggleToolPanelSection(sectionId: string): void {\n this.#shellController.toggleToolPanelSection(sectionId);\n }\n\n /**\n * Get all registered tool panel definitions.\n *\n * Returns both plugin-registered panels and panels registered via `registerToolPanel()`.\n *\n * @group Tool Panel\n * @returns Array of tool panel definitions\n *\n * @example\n * ```typescript\n * // List all available panels\n * const panels = grid.getToolPanels();\n * panels.forEach(panel => {\n * console.log(`Panel: ${panel.title} (${panel.id})`);\n * });\n * ```\n */\n getToolPanels(): ToolPanelDefinition[] {\n return this.#shellController.getToolPanels();\n }\n\n /**\n * Register a custom tool panel section.\n *\n * Use this API to add custom UI sections to the tool panel sidebar\n * without creating a full plugin. The panel will appear as an accordion\n * section in the tool panel.\n *\n * @group Tool Panel\n * @param panel - The tool panel definition\n *\n * @example\n * ```typescript\n * // Register a custom \"Export\" panel\n * grid.registerToolPanel({\n * id: 'export',\n * title: 'Export Options',\n * icon: '📥',\n * order: 50, // Lower order = higher in list\n * render: (container) => {\n * container.innerHTML = `\n * <button id=\"export-csv\">Export CSV</button>\n * <button id=\"export-json\">Export JSON</button>\n * `;\n * container.querySelector('#export-csv')?.addEventListener('click', () => {\n * exportToCSV(grid.rows);\n * });\n * }\n * });\n * ```\n */\n registerToolPanel(panel: ToolPanelDefinition): void {\n this.#shellState.apiToolPanelIds.add(panel.id);\n this.#shellController.registerToolPanel(panel);\n }\n\n /**\n * Unregister a custom tool panel section.\n *\n * @group Tool Panel\n * @param panelId - The ID of the panel to remove\n *\n * @example\n * ```typescript\n * // Remove the export panel when no longer needed\n * grid.unregisterToolPanel('export');\n * ```\n */\n unregisterToolPanel(panelId: string): void {\n this.#shellState.apiToolPanelIds.delete(panelId);\n this.#shellController.unregisterToolPanel(panelId);\n }\n\n // ════════════════════════════════════════════════════════════════════════════\n // Header Content API\n // ════════════════════════════════════════════════════════════════════════════\n // Header content appears in the grid's header bar area (above the column headers).\n\n /**\n * Get all registered header content definitions.\n *\n * @group Header Content\n * @returns Array of header content definitions\n *\n * @example\n * ```typescript\n * const contents = grid.getHeaderContents();\n * console.log('Header sections:', contents.map(c => c.id));\n * ```\n */\n getHeaderContents(): HeaderContentDefinition[] {\n return this.#shellController.getHeaderContents();\n }\n\n /**\n * Register custom header content.\n *\n * Header content appears in the grid's header bar area, which is displayed\n * above the column headers. Use this for search boxes, filters, or other\n * controls that should be prominently visible.\n *\n * @group Header Content\n * @param content - The header content definition\n *\n * @example\n * ```typescript\n * // Add a global search box to the header\n * grid.registerHeaderContent({\n * id: 'global-search',\n * order: 10,\n * render: (container) => {\n * const input = document.createElement('input');\n * input.type = 'search';\n * input.placeholder = 'Search all columns...';\n * input.addEventListener('input', (e) => {\n * const term = (e.target as HTMLInputElement).value;\n * filterGrid(term);\n * });\n * container.appendChild(input);\n * }\n * });\n * ```\n */\n registerHeaderContent(content: HeaderContentDefinition): void {\n this.#shellState.apiHeaderContentIds.add(content.id);\n this.#shellController.registerHeaderContent(content);\n }\n\n /**\n * Unregister custom header content.\n *\n * @group Header Content\n * @param contentId - The ID of the content to remove\n *\n * @example\n * ```typescript\n * grid.unregisterHeaderContent('global-search');\n * ```\n */\n unregisterHeaderContent(contentId: string): void {\n this.#shellState.apiHeaderContentIds.delete(contentId);\n this.#shellController.unregisterHeaderContent(contentId);\n }\n\n // ════════════════════════════════════════════════════════════════════════════\n // Toolbar Content API\n // ════════════════════════════════════════════════════════════════════════════\n // Toolbar content appears in the grid's toolbar area (typically below header,\n // above column headers). Use for action buttons, dropdowns, etc.\n\n /**\n * Get all registered toolbar content definitions.\n *\n * @group Toolbar\n * @returns Array of toolbar content definitions\n *\n * @example\n * ```typescript\n * const contents = grid.getToolbarContents();\n * console.log('Toolbar items:', contents.map(c => c.id));\n * ```\n */\n getToolbarContents(): ToolbarContentDefinition[] {\n return this.#shellController.getToolbarContents();\n }\n\n /**\n * Register custom toolbar content.\n *\n * Toolbar content appears in the grid's toolbar area. Use this for action\n * buttons, dropdowns, or other controls that should be easily accessible.\n * Content is rendered in order of the `order` property (lower = first).\n *\n * @group Toolbar\n * @param content - The toolbar content definition\n *\n * @example\n * ```typescript\n * // Add export buttons to the toolbar\n * grid.registerToolbarContent({\n * id: 'export-buttons',\n * order: 100, // Position in toolbar (lower = first)\n * render: (container) => {\n * const csvBtn = document.createElement('button');\n * csvBtn.textContent = 'Export CSV';\n * csvBtn.className = 'tbw-toolbar-btn';\n * csvBtn.addEventListener('click', () => exportToCSV(grid.rows));\n *\n * const jsonBtn = document.createElement('button');\n * jsonBtn.textContent = 'Export JSON';\n * jsonBtn.className = 'tbw-toolbar-btn';\n * jsonBtn.addEventListener('click', () => exportToJSON(grid.rows));\n *\n * container.append(csvBtn, jsonBtn);\n * }\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Add a dropdown filter to the toolbar\n * grid.registerToolbarContent({\n * id: 'status-filter',\n * order: 50,\n * render: (container) => {\n * const select = document.createElement('select');\n * select.innerHTML = `\n * <option value=\"\">All Statuses</option>\n * <option value=\"active\">Active</option>\n * <option value=\"inactive\">Inactive</option>\n * `;\n * select.addEventListener('change', (e) => {\n * const status = (e.target as HTMLSelectElement).value;\n * applyStatusFilter(status);\n * });\n * container.appendChild(select);\n * }\n * });\n * ```\n */\n registerToolbarContent(content: ToolbarContentDefinition): void {\n this.#shellController.registerToolbarContent(content);\n }\n\n /**\n * Unregister custom toolbar content.\n *\n * @group Toolbar\n * @param contentId - The ID of the content to remove\n *\n * @example\n * ```typescript\n * // Remove export buttons when switching to read-only mode\n * grid.unregisterToolbarContent('export-buttons');\n * ```\n */\n unregisterToolbarContent(contentId: string): void {\n this.#shellController.unregisterToolbarContent(contentId);\n }\n\n /** Pending shell refresh - used to batch multiple rapid calls */\n #pendingShellRefresh = false;\n\n /**\n * Re-parse light DOM shell elements and refresh shell header.\n * Call this after dynamically modifying <tbw-grid-header> children.\n *\n * Multiple calls are batched via microtask to avoid redundant DOM rebuilds\n * when registering multiple tool panels in sequence.\n *\n * @internal Plugin API\n */\n refreshShellHeader(): void {\n // Batch multiple rapid calls into a single microtask\n if (this.#pendingShellRefresh) return;\n this.#pendingShellRefresh = true;\n\n queueMicrotask(() => {\n this.#pendingShellRefresh = false;\n if (!this.isConnected) return;\n\n // Re-parse light DOM (header, tool buttons, and tool panels)\n this.#parseLightDom();\n\n // Mark sources as changed since shell parsing may have updated state maps\n this.#configManager.markSourcesChanged();\n\n // Re-merge config to sync shell state changes into effectiveConfig.shell\n this.#configManager.merge();\n\n // Prepare shell state for re-render (moves toolbar buttons back to original container)\n prepareForRerender(this.#shellState);\n\n // Re-render the entire grid (shell structure may change)\n this.#render();\n this.#injectAllPluginStyles(); // Re-inject after render clears shadow DOM\n\n // Use lighter-weight post-render setup instead of full #afterConnect()\n // This avoids requesting another FULL render since #render() already rebuilt the DOM\n this.#afterShellRefresh();\n });\n }\n\n /**\n * Lighter-weight post-render setup after shell refresh.\n * Unlike #afterConnect(), this skips scheduler FULL request since DOM is already built.\n */\n #afterShellRefresh(): void {\n // Shell changes the DOM structure - need to re-cache element references\n const gridContent = this.#renderRoot.querySelector('.tbw-grid-content');\n const gridRoot = gridContent ?? this.#renderRoot.querySelector('.tbw-grid-root');\n\n this._headerRowEl = gridRoot?.querySelector('.header-row') as HTMLElement;\n this._virtualization.totalHeightEl = gridRoot?.querySelector('.faux-vscroll-spacer') as HTMLElement;\n this._virtualization.viewportEl = gridRoot?.querySelector('.rows-viewport') as HTMLElement;\n this._bodyEl = gridRoot?.querySelector('.rows') as HTMLElement;\n this.__rowsBodyEl = gridRoot?.querySelector('.rows-body') as HTMLElement;\n\n // Render shell header content and custom toolbar contents\n if (this.#shellController.isInitialized) {\n renderHeaderContent(this.#renderRoot, this.#shellState);\n renderCustomToolbarContents(this.#renderRoot, this.#effectiveConfig?.shell, this.#shellState);\n\n const defaultOpen = this.#effectiveConfig?.shell?.toolPanel?.defaultOpen;\n if (defaultOpen && this.#shellState.toolPanels.has(defaultOpen)) {\n this.openToolPanel();\n this.#shellState.expandedSections.add(defaultOpen);\n }\n\n // Restore panel content if panel was already open (e.g., after plugin re-init triggers refreshShellHeader)\n if (this.#shellState.isPanelOpen) {\n updatePanelState(this.#renderRoot, this.#shellState);\n renderPanelContent(this.#renderRoot, this.#shellState, {\n expand: this.#effectiveConfig?.icons?.expand,\n collapse: this.#effectiveConfig?.icons?.collapse,\n });\n updateToolbarActiveStates(this.#renderRoot, this.#shellState);\n }\n }\n\n // Re-create resize controller (DOM elements changed)\n this._resizeController = createResizeController(this);\n\n // Re-setup scroll listeners (DOM elements changed)\n this.#setupScrollListeners(gridRoot);\n\n // Request COLUMNS phase to reprocess columns (including column groups) and render header\n // #render() rebuilds the DOM with an empty header-row, so we need COLUMNS phase (5)\n // which includes processColumns (for column groups), processRows, and renderHeader\n this.#scheduler.requestPhase(RenderPhase.COLUMNS, 'shellRefresh');\n }\n // #endregion\n\n // #region Custom Styles API\n /** Map of registered custom stylesheets by ID - uses adoptedStyleSheets which survive DOM rebuilds */\n #customStyleSheets = new Map<string, CSSStyleSheet>();\n\n /**\n * Register custom CSS styles to be injected into the grid's shadow DOM.\n * Use this to style custom cell renderers, editors, or detail panels.\n *\n * Uses adoptedStyleSheets for efficiency - styles survive shadow DOM rebuilds.\n *\n * @group Custom Styles\n * @param id - Unique identifier for the style block (for removal/updates)\n * @param css - CSS string to inject\n *\n * @example\n * ```typescript\n * // Register custom styles for a detail panel\n * grid.registerStyles('my-detail-styles', `\n * .my-detail-panel { padding: 16px; }\n * .my-detail-table { width: 100%; }\n * `);\n *\n * // Update styles later\n * grid.registerStyles('my-detail-styles', updatedCss);\n *\n * // Remove styles\n * grid.unregisterStyles('my-detail-styles');\n * ```\n */\n registerStyles(id: string, css: string): void {\n // Create or update the stylesheet\n let sheet = this.#customStyleSheets.get(id);\n if (!sheet) {\n sheet = new CSSStyleSheet();\n this.#customStyleSheets.set(id, sheet);\n }\n sheet.replaceSync(css);\n\n // Update adoptedStyleSheets to include all custom sheets\n this.#updateAdoptedStyleSheets();\n }\n\n /**\n * Remove previously registered custom styles.\n *\n * @group Custom Styles\n * @param id - The ID used when registering the styles\n */\n unregisterStyles(id: string): void {\n if (this.#customStyleSheets.delete(id)) {\n this.#updateAdoptedStyleSheets();\n }\n }\n\n /**\n * Get list of registered custom style IDs.\n *\n * @group Custom Styles\n */\n getRegisteredStyles(): string[] {\n return Array.from(this.#customStyleSheets.keys());\n }\n\n /**\n * Update document.adoptedStyleSheets to include custom sheets.\n * Without Shadow DOM, all custom styles go into the document.\n */\n #updateAdoptedStyleSheets(): void {\n const customSheets = Array.from(this.#customStyleSheets.values());\n\n // Start with document's existing sheets (excluding any we've added before)\n // We track custom sheets by their presence in our map\n const existingSheets = document.adoptedStyleSheets.filter(\n (sheet) => !Array.from(this.#customStyleSheets.values()).includes(sheet),\n );\n\n document.adoptedStyleSheets = [...existingSheets, ...customSheets];\n }\n // #endregion\n\n // #region External Focus Containers (delegated to FocusManager)\n /**\n * Register an external DOM element as a logical focus container of this grid.\n *\n * Focus moving into a registered container is treated as if it stayed inside\n * the grid: `data-has-focus` is preserved, click-outside commit is suppressed,\n * and the editing focus trap (when enabled) won't reclaim focus.\n *\n * Typical use case: overlay panels (datepickers, dropdowns, autocompletes)\n * that render at `<body>` level to escape grid overflow clipping.\n *\n * @group Focus Management\n * @param el - The external element to register\n *\n * @example\n * ```typescript\n * const overlay = document.createElement('div');\n * document.body.appendChild(overlay);\n *\n * // Tell the grid this overlay is \"part of\" the grid\n * grid.registerExternalFocusContainer(overlay);\n *\n * // Later, when overlay is removed\n * grid.unregisterExternalFocusContainer(overlay);\n * ```\n */\n registerExternalFocusContainer(el: Element): void {\n this.#focusManager.registerExternalFocusContainer(el);\n }\n\n /**\n * Unregister a previously registered external focus container.\n *\n * @group Focus Management\n * @param el - The element to unregister\n */\n unregisterExternalFocusContainer(el: Element): void {\n this.#focusManager.unregisterExternalFocusContainer(el);\n }\n\n /**\n * Check whether focus is logically inside this grid.\n *\n * Returns `true` when `document.activeElement` (or the given node) is\n * inside the grid's own DOM **or** inside any element registered via\n * {@link registerExternalFocusContainer}.\n *\n * @group Focus Management\n * @param node - Optional node to test. Defaults to `document.activeElement`.\n *\n * @example\n * ```typescript\n * if (grid.containsFocus()) {\n * console.log('Grid or one of its overlays has focus');\n * }\n * ```\n */\n containsFocus(node?: Node | null): boolean {\n return this.#focusManager.containsFocus(node);\n }\n // #endregion\n\n // #region Light DOM Helpers\n /**\n * Parse all light DOM shell elements in one call.\n * Consolidates parsing of header, tool buttons, and tool panels.\n */\n #parseLightDom(): void {\n parseLightDomShell(this, this.#shellState);\n parseLightDomToolButtons(this, this.#shellState);\n parseLightDomToolPanels(this, this.#shellState, this.#getToolPanelRendererFactory());\n }\n\n /**\n * Replace the shell header element in the DOM with freshly rendered HTML.\n * Used when title or tool buttons are added dynamically via light DOM.\n */\n #replaceShellHeaderElement(): void {\n const shellHeader = this.#renderRoot.querySelector('.tbw-shell-header');\n if (!shellHeader) return;\n\n // Prepare for re-render (moves toolbar buttons back to original container)\n prepareForRerender(this.#shellState);\n\n const newHeaderHtml = renderShellHeader(\n this.#effectiveConfig.shell,\n this.#shellState,\n this.#effectiveConfig.icons?.toolPanel,\n );\n const temp = document.createElement('div');\n temp.innerHTML = newHeaderHtml;\n const newHeader = temp.firstElementChild;\n if (newHeader) {\n shellHeader.replaceWith(newHeader);\n this.#setupShellListeners();\n // Render custom toolbar contents into the newly created slots\n renderCustomToolbarContents(this.#renderRoot, this.#effectiveConfig?.shell, this.#shellState);\n }\n }\n\n /**\n * Set up Light DOM handlers via ConfigManager's observer infrastructure.\n * This handles frameworks like Angular that project content asynchronously.\n *\n * The observer is owned by ConfigManager (generic infrastructure).\n * The handlers (parsing logic) are owned here (eventually ShellPlugin).\n *\n * This separation allows plugins to register their own Light DOM elements\n * and handle parsing themselves.\n */\n #setupLightDomHandlers(): void {\n // Handler for shell header element changes\n const handleShellChange = () => {\n const hadTitle = this.#shellState.lightDomTitle;\n const hadToolButtons = this.#shellState.hasToolButtonsContainer;\n this.#parseLightDom();\n const hasTitle = this.#shellState.lightDomTitle;\n const hasToolButtons = this.#shellState.hasToolButtonsContainer;\n\n if ((hasTitle && !hadTitle) || (hasToolButtons && !hadToolButtons)) {\n this.#configManager.markSourcesChanged();\n this.#configManager.merge();\n this.#replaceShellHeaderElement();\n }\n };\n\n // Handler for column element changes\n const handleColumnChange = () => {\n this.__lightDomColumnsCache = undefined;\n this.#setup();\n };\n\n // Register handlers with ConfigManager\n // Shell-related elements (eventually these move to ShellPlugin)\n this.#configManager.registerLightDomHandler('tbw-grid-header', handleShellChange);\n this.#configManager.registerLightDomHandler('tbw-grid-tool-buttons', handleShellChange);\n this.#configManager.registerLightDomHandler('tbw-grid-tool-panel', handleShellChange);\n\n // Column elements (core grid functionality)\n this.#configManager.registerLightDomHandler('tbw-grid-column', handleColumnChange);\n this.#configManager.registerLightDomHandler('tbw-grid-detail', handleColumnChange);\n\n // Start observing\n this.#configManager.observeLightDOM(this);\n }\n\n /**\n * Re-parse light DOM column elements and refresh the grid.\n * Call this after framework adapters have registered their templates.\n * Uses the render scheduler to batch with other pending updates.\n * @category Framework Adapters\n */\n refreshColumns(): void {\n // Clear the column cache to force re-parsing\n this.__lightDomColumnsCache = undefined;\n\n // Invalidate cell cache to reset __hasSpecialColumns flag\n // This is critical for frameworks like React where renderers are registered asynchronously\n // after the initial render (which may have cached __hasSpecialColumns = false)\n invalidateCellCache(this);\n\n // Re-parse light DOM columns SYNCHRONOUSLY to pick up newly registered framework renderers\n // This must happen before the scheduler runs processColumns\n this.#configManager.parseLightDomColumns(this);\n\n // Re-parse light DOM shell elements (may have been rendered asynchronously by frameworks)\n const hadTitle = this.#shellState.lightDomTitle;\n const hadToolButtons = this.#shellState.hasToolButtonsContainer;\n this.#parseLightDom();\n const hasTitle = this.#shellState.lightDomTitle;\n const hasToolButtons = this.#shellState.hasToolButtonsContainer;\n\n // If title or tool buttons were added via light DOM, update the shell header in place\n // The shell may already be rendered (due to plugins/panels), but without the title\n const needsShellRefresh = (hasTitle && !hadTitle) || (hasToolButtons && !hadToolButtons);\n if (needsShellRefresh) {\n // Mark sources as changed since shell parsing may have updated state maps\n this.#configManager.markSourcesChanged();\n // Merge the new title into effectiveConfig\n this.#configManager.merge();\n this.#replaceShellHeaderElement();\n }\n\n // Request a COLUMNS phase render through the scheduler\n // This batches with any other pending work (e.g., afterConnect)\n this.#scheduler.requestPhase(RenderPhase.COLUMNS, 'refreshColumns');\n }\n // #endregion\n\n // #region Virtualization (delegated to VirtualizationManager)\n\n /**\n * Update cached viewport and faux scrollbar geometry.\n * Called by ResizeObserver and on force-refresh to avoid forced layout reads during scroll.\n * @internal\n */\n #updateCachedGeometry(): void {\n this.#virtManager.updateCachedGeometry();\n }\n\n /**\n * Core virtualization routine. Delegates to VirtualizationManager.\n * @param force - Whether to force a full refresh (not just scroll update)\n * @param skipAfterRender - When true, skip calling afterRender (used by scheduler which calls it separately)\n * @returns Whether the visible row window changed (start/end differ from previous)\n * @internal Plugin API\n */\n refreshVirtualWindow(force = false, skipAfterRender = false): boolean {\n return this.#virtManager.refreshVirtualWindow(force, skipAfterRender);\n }\n\n /**\n * Invalidate a row's height in the position cache.\n * Call this when a plugin changes a row's height (e.g., expanding/collapsing a detail panel).\n * @param rowIndex - Index of the row whose height changed\n * @param newHeight - Optional new height. If not provided, queries plugins for height.\n */\n invalidateRowHeight(rowIndex: number, newHeight?: number): void {\n this.#virtManager.invalidateRowHeight(rowIndex, newHeight);\n }\n\n // #endregion\n\n // #region Render\n #render(): void {\n // Parse light DOM shell elements before rendering\n this.#parseLightDom();\n\n // Mark sources as changed since shell parsing may have updated state maps\n this.#configManager.markSourcesChanged();\n\n // Re-merge config to pick up any newly parsed light DOM shell settings\n this.#configManager.merge();\n\n const shellConfig = this.#effectiveConfig?.shell;\n\n // Render using direct DOM construction (2-3x faster than innerHTML)\n // Pass only minimal runtime state (isPanelOpen, expandedSections) - config comes from effectiveConfig.shell\n const hasShell = buildGridDOMIntoElement(\n this.#renderRoot,\n shellConfig,\n { isPanelOpen: this.#shellState.isPanelOpen, expandedSections: this.#shellState.expandedSections },\n this.#effectiveConfig?.icons,\n );\n\n if (hasShell) {\n this.#setupShellListeners();\n this.#shellController.setInitialized(true);\n }\n }\n\n /**\n * Set up shell event listeners after render.\n */\n #setupShellListeners(): void {\n setupShellEventListeners(this.#renderRoot, this.#effectiveConfig?.shell, this.#shellState, {\n onPanelToggle: () => this.toggleToolPanel(),\n onSectionToggle: (sectionId: string) => this.toggleToolPanelSection(sectionId),\n });\n\n // Set up tool panel resize\n this.#resizeCleanup?.();\n this.#resizeCleanup = setupToolPanelResize(this.#renderRoot, this.#effectiveConfig?.shell, (width: number) => {\n // Update the CSS variable to persist the new width\n this.style.setProperty('--tbw-tool-panel-width', `${width}px`);\n });\n\n // Set up click-outside dismiss for tool panel\n this.#clickOutsideCleanup?.();\n this.#clickOutsideCleanup = setupClickOutsideDismiss(this, this.#effectiveConfig?.shell, this.#shellState, () =>\n this.closeToolPanel(),\n );\n }\n // #endregion\n}\n\n// Self-registering custom element\nif (!customElements.get(DataGridElement.tagName)) {\n customElements.define(DataGridElement.tagName, DataGridElement);\n}\n\n// Make DataGridElement accessible globally for framework adapters\nglobalThis.DataGridElement = DataGridElement;\n\n// Type augmentation for querySelector/createElement and globalThis\ndeclare global {\n var DataGridElement: typeof import('./grid').DataGridElement;\n interface HTMLElementTagNameMap {\n 'tbw-grid': DataGridElement;\n }\n}\n","/**\n * Grid Base Styles - Concatenated from partials\n *\n * This module imports all CSS partials and exports them as a single string.\n * Each partial is wrapped in @layer tbw-base for proper cascade ordering.\n *\n * CSS Cascade Layers priority (lowest to highest):\n * - tbw-base: Core grid styles (this file)\n * - tbw-plugins: Plugin styles (override base)\n * - tbw-theme: Theme overrides (override plugins)\n * - Unlayered CSS: User customizations (highest priority - always wins)\n *\n * @module styles\n */\n\n// Import all CSS partials as inline strings (Vite handles ?inline)\nimport animations from './animations.css?inline';\nimport base from './base.css?inline';\nimport header from './header.css?inline';\nimport loading from './loading.css?inline';\nimport mediaQueries from './media-queries.css?inline';\nimport rows from './rows.css?inline';\nimport shell from './shell.css?inline';\nimport toolPanel from './tool-panel.css?inline';\nimport variables from './variables.css?inline';\n\n/**\n * Complete grid base styles.\n *\n * Concatenates all CSS partials in the correct order:\n * 1. Layer declaration (defines cascade order)\n * 2. Variables (CSS custom properties)\n * 3. Base (root element styles)\n * 4. Header (column headers, sort, resize)\n * 5. Rows (data rows and cells)\n * 6. Shell (toolbar, layout)\n * 7. Tool Panel (side panels, accordion)\n * 8. Loading (spinners, overlays)\n * 9. Animations (keyframes, transitions)\n * 10. Media Queries (accessibility, responsive)\n */\nexport const gridStyles = `@layer tbw-base, tbw-plugins, tbw-theme;\n\n${variables}\n${base}\n${header}\n${rows}\n${shell}\n${toolPanel}\n${loading}\n${animations}\n${mediaQueries}\n`;\n\n// Default export for backwards compatibility with `import styles from './grid.css?inline'`\nexport default gridStyles;\n","/**\n * Grid DOM Constants\n *\n * Centralized constants for CSS classes, data attributes, and selectors\n * used throughout the grid. Use these instead of magic strings.\n *\n * Note: Some constants here are used by plugins but defined in core because:\n * 1. The core CSS needs to style these elements (e.g., sticky positioning)\n * 2. Multiple plugins may share the same class names\n *\n * Plugins can define their own additional constants and export them.\n * See each plugin's index.ts for plugin-specific exports.\n */\n\n// #region CSS Classes\n\n/**\n * CSS class names used in the grid's shadow DOM.\n * Use these when adding/removing classes or querying elements.\n *\n * Classes are organized by:\n * - Core: Used by the grid core for structure and basic functionality\n * - Shared: Used by multiple features/plugins, styled by core CSS\n *\n * @category Plugin Development\n */\nexport const GridClasses = {\n // ─── Core Structure ───────────────────────────────────────────────\n ROOT: 'tbw-grid-root',\n HEADER: 'header',\n HEADER_ROW: 'header-row',\n HEADER_CELL: 'header-cell',\n\n // Body structure\n ROWS_VIEWPORT: 'rows-viewport',\n ROWS_SPACER: 'rows-spacer',\n ROWS_CONTAINER: 'rows',\n\n // Row elements\n DATA_ROW: 'data-row',\n GROUP_ROW: 'group-row',\n\n // Cell elements\n DATA_CELL: 'data-cell',\n\n // ─── Core States ──────────────────────────────────────────────────\n SELECTED: 'selected',\n FOCUSED: 'focused',\n EDITING: 'editing',\n EXPANDED: 'expanded',\n COLLAPSED: 'collapsed',\n DRAGGING: 'dragging',\n RESIZING: 'resizing',\n\n // Sorting (core feature)\n SORTABLE: 'sortable',\n SORTED_ASC: 'sorted-asc',\n SORTED_DESC: 'sorted-desc',\n\n // Visibility\n HIDDEN: 'hidden',\n\n // ─── Shared Classes (used by plugins, styled by core CSS) ────────\n // These are defined here because core CSS provides the base styling.\n // Plugins apply these classes; core CSS defines how they look.\n\n // Sticky positioning (PinnedColumnsPlugin applies, core CSS styles)\n STICKY_LEFT: 'sticky-left',\n STICKY_RIGHT: 'sticky-right',\n\n // Pinned rows (PinnedRowsPlugin applies, core CSS styles)\n PINNED_TOP: 'pinned-top',\n PINNED_BOTTOM: 'pinned-bottom',\n\n // Tree structure (TreePlugin applies, core CSS styles)\n TREE_TOGGLE: 'tree-toggle',\n TREE_INDENT: 'tree-indent',\n\n // Grouping (GroupingRowsPlugin applies, core CSS styles)\n GROUP_TOGGLE: 'group-toggle',\n GROUP_LABEL: 'group-label',\n GROUP_COUNT: 'group-count',\n\n // Selection (SelectionPlugin applies, core CSS styles)\n RANGE_SELECTION: 'range-selection',\n SELECTION_OVERLAY: 'selection-overlay',\n} as const;\n\n// #endregion\n\n// #region Data Attributes\n\n/**\n * Data attribute names used on grid elements.\n * Use these when getting/setting data attributes.\n *\n * @category Plugin Development\n */\nexport const GridDataAttrs = {\n // ─── Core Attributes ──────────────────────────────────────────────\n ROW_INDEX: 'data-row-index',\n COL_INDEX: 'data-col-index',\n FIELD: 'data-field',\n\n // ─── Shared Attributes (used by plugins) ──────────────────────────\n GROUP_KEY: 'data-group-key', // GroupingRowsPlugin\n TREE_LEVEL: 'data-tree-level', // TreePlugin\n STICKY: 'data-sticky', // PinnedColumnsPlugin\n} as const;\n\n// #endregion\n\n// #region Selectors\n\n/**\n * Common CSS selectors for querying grid elements.\n * Built from the class constants for consistency.\n *\n * @category Plugin Development\n */\nexport const GridSelectors = {\n ROOT: `.${GridClasses.ROOT}`,\n HEADER: `.${GridClasses.HEADER}`,\n HEADER_ROW: `.${GridClasses.HEADER_ROW}`,\n HEADER_CELL: `.${GridClasses.HEADER_CELL}`,\n ROWS_VIEWPORT: `.${GridClasses.ROWS_VIEWPORT}`,\n ROWS_CONTAINER: `.${GridClasses.ROWS_CONTAINER}`,\n DATA_ROW: `.${GridClasses.DATA_ROW}`,\n DATA_CELL: `.${GridClasses.DATA_CELL}`,\n GROUP_ROW: `.${GridClasses.GROUP_ROW}`,\n\n // By data attribute\n ROW_BY_INDEX: (index: number) => `.${GridClasses.DATA_ROW}[${GridDataAttrs.ROW_INDEX}=\"${index}\"]`,\n CELL_BY_FIELD: (field: string) => `.${GridClasses.DATA_CELL}[${GridDataAttrs.FIELD}=\"${field}\"]`,\n CELL_AT: (row: number, col: number) =>\n `.${GridClasses.DATA_ROW}[${GridDataAttrs.ROW_INDEX}=\"${row}\"] .${GridClasses.DATA_CELL}[${GridDataAttrs.COL_INDEX}=\"${col}\"]`,\n\n // State selectors\n SELECTED_ROWS: `.${GridClasses.DATA_ROW}.${GridClasses.SELECTED}`,\n EDITING_CELL: `.${GridClasses.DATA_CELL}.${GridClasses.EDITING}`,\n} as const;\n\n// #endregion\n\n// #region CSS Custom Properties\n\n/**\n * CSS custom property names for theming.\n * Use these when programmatically setting styles.\n *\n * @category Plugin Development\n */\nexport const GridCSSVars = {\n // Colors\n COLOR_BG: '--tbw-color-bg',\n COLOR_FG: '--tbw-color-fg',\n COLOR_FG_MUTED: '--tbw-color-fg-muted',\n COLOR_BORDER: '--tbw-color-border',\n COLOR_ACCENT: '--tbw-color-accent',\n COLOR_HEADER_BG: '--tbw-color-header-bg',\n COLOR_HEADER_FG: '--tbw-color-header-fg',\n COLOR_SELECTION: '--tbw-color-selection',\n COLOR_ROW_HOVER: '--tbw-color-row-hover',\n COLOR_ROW_ALT: '--tbw-color-row-alt',\n\n // Sizing\n ROW_HEIGHT: '--tbw-row-height',\n HEADER_HEIGHT: '--tbw-header-height',\n CELL_PADDING: '--tbw-cell-padding',\n\n // Typography\n FONT_FAMILY: '--tbw-font-family',\n FONT_SIZE: '--tbw-font-size',\n\n // Borders\n BORDER_RADIUS: '--tbw-border-radius',\n FOCUS_OUTLINE: '--tbw-focus-outline',\n} as const;\n\n// #endregion\n\n// Type helpers\nexport type GridClassName = (typeof GridClasses)[keyof typeof GridClasses];\nexport type GridDataAttr = (typeof GridDataAttrs)[keyof typeof GridDataAttrs];\nexport type GridCSSVar = (typeof GridCSSVars)[keyof typeof GridCSSVars];\n","/**\n * @toolbox-web/grid - A high-performance, framework-agnostic data grid web component.\n *\n * This is the public API surface. Only symbols exported here are considered stable.\n *\n * @packageDocumentation\n * @module Core\n */\n\n// #region Public API surface - only export what consumers need\nexport { DataGridElement, DataGridElement as GridElement } from './lib/core/grid';\n\n/**\n * Clean type alias for the grid element.\n * Use this in place of `DataGridElement<T>` for more concise code.\n *\n * @example\n * ```typescript\n * import { TbwGrid, createGrid } from '@toolbox-web/grid';\n *\n * const grid: TbwGrid<Employee> = createGrid();\n * grid.rows = employees;\n * ```\n */\nexport type { DataGridElement as TbwGrid } from './lib/core/grid';\n\n// Import needed for factory functions (value import: tagName is accessed at runtime)\nimport { DataGridElement } from './lib/core/grid';\nimport type { GridConfig } from './lib/core/types';\n\n// #region Factory Functions\n/**\n * Create a new typed grid element programmatically.\n *\n * This avoids the need to cast when creating grids in TypeScript:\n * ```typescript\n * // Before: manual cast required\n * const grid = document.createElement('tbw-grid') as DataGridElement<Employee>;\n *\n * // After: fully typed\n * const grid = createGrid<Employee>({\n * columns: [{ field: 'name' }],\n * plugins: [new SelectionPlugin()],\n * });\n * grid.rows = employees; // ✓ Typed!\n * ```\n *\n * @param config - Optional initial grid configuration\n * @returns A typed DataGridElement instance\n */\nexport function createGrid<TRow = unknown>(config?: Partial<GridConfig<TRow>>): DataGridElement<TRow> {\n const grid = document.createElement('tbw-grid') as DataGridElement<TRow>;\n if (config) {\n grid.gridConfig = config as GridConfig<TRow>;\n }\n return grid;\n}\n\n/**\n * Query an existing grid element from the DOM with proper typing.\n *\n * **Sync mode** (default) — returns the element immediately. The element may not\n * be upgraded yet if the grid module hasn't loaded.\n *\n * **Async mode** — pass `true` as the second or third argument to wait for the\n * custom element to be defined and upgraded before resolving. This guarantees\n * all `DataGridElement` methods (e.g. `registerStyles`, `ready`, `on`) are\n * available on the returned instance.\n *\n * @example\n * ```typescript\n * // Sync — unchanged from before\n * const grid = queryGrid<Employee>('#my-grid');\n * if (grid) {\n * grid.rows = employees; // ✓ Typed (assumes element is upgraded)\n * }\n *\n * // Async — waits for custom-element upgrade\n * const grid = await queryGrid<Employee>('#my-grid', true);\n * if (grid) {\n * grid.registerStyles('my-id', '.cell { color: red; }'); // ✓ Safe\n * }\n *\n * // Async with parent scope\n * const grid = await queryGrid<Employee>('tbw-grid', container, true);\n * ```\n *\n * @param selector - CSS selector to find the grid element\n * @returns The typed grid element (or null), either synchronously or as a Promise\n */\nexport function queryGrid<TRow = unknown>(selector: string): DataGridElement<TRow> | null;\nexport function queryGrid<TRow = unknown>(selector: string, parent: ParentNode): DataGridElement<TRow> | null;\nexport function queryGrid<TRow = unknown>(selector: string, awaitUpgrade: true): Promise<DataGridElement<TRow> | null>;\nexport function queryGrid<TRow = unknown>(\n selector: string,\n parent: ParentNode,\n awaitUpgrade: true,\n): Promise<DataGridElement<TRow> | null>;\nexport function queryGrid<TRow = unknown>(\n selector: string,\n parentOrAwait?: ParentNode | boolean,\n awaitUpgrade?: boolean,\n): DataGridElement<TRow> | null | Promise<DataGridElement<TRow> | null> {\n let parent: ParentNode = document;\n let shouldAwait = false;\n\n if (typeof parentOrAwait === 'boolean') {\n shouldAwait = parentOrAwait;\n } else if (parentOrAwait) {\n parent = parentOrAwait;\n shouldAwait = !!awaitUpgrade;\n }\n\n if (shouldAwait) {\n return customElements.whenDefined(DataGridElement.tagName).then(() => {\n return parent.querySelector(selector) as DataGridElement<TRow> | null;\n });\n }\n\n return parent.querySelector(selector) as DataGridElement<TRow> | null;\n}\n// #endregion\n\n/**\n * Event name constants for DataGrid (public API).\n *\n * Use these constants instead of string literals for type-safe event handling.\n *\n * @example\n * ```typescript\n * import { DGEvents } from '@toolbox-web/grid';\n *\n * // Type-safe event listening\n * grid.addEventListener(DGEvents.CELL_CLICK, (e) => {\n * console.log('Cell clicked:', e.detail);\n * });\n *\n * grid.addEventListener(DGEvents.SORT_CHANGE, (e) => {\n * const { field, direction } = e.detail;\n * console.log(`Sorted by ${field}`);\n * });\n *\n * grid.addEventListener(DGEvents.CELL_COMMIT, (e) => {\n * // Save edited value\n * saveToServer(e.detail.row);\n * });\n * ```\n *\n * @see {@link PluginEvents} for plugin-specific events\n * @see {@link DataGridEventMap} for event detail types\n * @category Events\n */\nexport const DGEvents = {\n /** Emitted by core after any data mutation */\n CELL_CHANGE: 'cell-change',\n CELL_COMMIT: 'cell-commit',\n ROW_COMMIT: 'row-commit',\n EDIT_OPEN: 'edit-open',\n EDIT_CLOSE: 'edit-close',\n CHANGED_ROWS_RESET: 'changed-rows-reset',\n MOUNT_EXTERNAL_VIEW: 'mount-external-view',\n MOUNT_EXTERNAL_EDITOR: 'mount-external-editor',\n SORT_CHANGE: 'sort-change',\n COLUMN_RESIZE: 'column-resize',\n ACTIVATE_CELL: 'activate-cell',\n /** Unified cell activation event (keyboard or pointer) */\n CELL_ACTIVATE: 'cell-activate',\n GROUP_TOGGLE: 'group-toggle',\n COLUMN_STATE_CHANGE: 'column-state-change',\n /** Emitted when grid row data changes (set, insert, remove, update) */\n DATA_CHANGE: 'data-change',\n} as const;\n\n/**\n * Union type of all DataGrid event names.\n *\n * @example\n * ```typescript\n * function addListener(grid: DataGridElement, event: DGEventName): void {\n * grid.addEventListener(event, (e) => console.log(e));\n * }\n * ```\n *\n * @see {@link DGEvents} for event constants\n * @category Events\n */\nexport type DGEventName = (typeof DGEvents)[keyof typeof DGEvents];\n\n/**\n * Plugin event constants (mirrors DGEvents pattern).\n *\n * Events emitted by built-in plugins. Import the relevant plugin\n * to access these events.\n *\n * @example\n * ```typescript\n * import { PluginEvents } from '@toolbox-web/grid';\n * import { SelectionPlugin } from '@toolbox-web/grid/all';\n *\n * // Listen for selection changes\n * grid.addEventListener(PluginEvents.SELECTION_CHANGE, (e) => {\n * console.log('Selected rows:', e.detail.selectedRows);\n * });\n *\n * // Listen for filter changes\n * grid.addEventListener(PluginEvents.FILTER_CHANGE, (e) => {\n * console.log('Active filters:', e.detail);\n * });\n *\n * // Listen for tree expand/collapse\n * grid.addEventListener(PluginEvents.TREE_EXPAND, (e) => {\n * const { row, expanded } = e.detail;\n * console.log(`Row ${expanded ? 'expanded' : 'collapsed'}`);\n * });\n * ```\n *\n * @see {@link DGEvents} for core grid events\n * @category Events\n */\nexport const PluginEvents = {\n // Selection plugin\n SELECTION_CHANGE: 'selection-change',\n // Tree plugin\n TREE_EXPAND: 'tree-expand',\n // Filtering plugin\n FILTER_CHANGE: 'filter-change',\n // Sorting plugin\n SORT_MODEL_CHANGE: 'sort-model-change',\n // Export plugin\n EXPORT_START: 'export-start',\n EXPORT_COMPLETE: 'export-complete',\n // Clipboard plugin\n CLIPBOARD_COPY: 'clipboard-copy',\n CLIPBOARD_PASTE: 'clipboard-paste',\n // Context menu plugin\n CONTEXT_MENU_OPEN: 'context-menu-open',\n CONTEXT_MENU_CLOSE: 'context-menu-close',\n // Undo/Redo plugin\n HISTORY_CHANGE: 'history-change',\n // Server-side plugin\n SERVER_LOADING: 'server-loading',\n SERVER_ERROR: 'server-error',\n // Visibility plugin\n COLUMN_VISIBILITY_CHANGE: 'column-visibility-change',\n // Reorder plugin\n COLUMN_REORDER: 'column-reorder',\n // Master-detail plugin\n DETAIL_EXPAND: 'detail-expand',\n // Grouping rows plugin\n GROUP_EXPAND: 'group-expand',\n} as const;\n\n/**\n * Union type of all plugin event names.\n *\n * @example\n * ```typescript\n * function addPluginListener(grid: DataGridElement, event: PluginEventName): void {\n * grid.addEventListener(event, (e) => console.log(e));\n * }\n * ```\n *\n * @see {@link PluginEvents} for event constants\n * @category Events\n */\nexport type PluginEventName = (typeof PluginEvents)[keyof typeof PluginEvents];\n\n// Public type exports\nexport type {\n /** @deprecated Use CellActivateDetail instead */\n ActivateCellDetail,\n AggregatorRef,\n // Animation types\n AnimationConfig,\n AnimationMode,\n AnimationStyle,\n BaseColumnConfig,\n // Event detail types\n CellActivateDetail,\n CellActivateTrigger,\n CellChangeDetail,\n CellClickDetail,\n CellRenderContext,\n ColumnConfig,\n ColumnConfigMap,\n ColumnEditorContext,\n // Column features\n ColumnEditorSpec,\n ColumnResizeDetail,\n // Column state types\n ColumnSortState,\n ColumnState,\n // Type-level defaults\n ColumnType,\n ColumnViewRenderer,\n DataChangeDetail,\n DataGridCustomEvent,\n DataGridElement as DataGridElementInterface,\n DataGridEventMap,\n ExpandCollapseAnimation,\n ExternalMountEditorDetail,\n ExternalMountViewDetail,\n // Feature configuration (augmentable by feature modules)\n FeatureConfig,\n FitMode,\n // Framework adapter interface\n FrameworkAdapter,\n GridColumnState,\n // Core configuration types\n GridConfig,\n // Icons\n GridIcons,\n // Plugin interface (minimal shape for type-checking)\n GridPlugin,\n // Header renderer types\n HeaderCellContext,\n // Shell types\n HeaderContentDefinition,\n HeaderLabelContext,\n HeaderLabelRenderer,\n HeaderRenderer,\n IconValue,\n // Inference types\n InferredColumnResult,\n // Loading types\n LoadingContext,\n LoadingRenderer,\n LoadingSize,\n PrimitiveColumnType,\n // Public interface\n PublicGrid,\n // Row animation type\n RowAnimationType,\n RowClickDetail,\n // Grouping & Footer types\n RowGroupRenderConfig,\n // Data update management\n RowTransaction,\n RowUpdate,\n // Focus & Navigation\n ScrollToRowOptions,\n ShellConfig,\n ShellHeaderConfig,\n SortChangeDetail,\n // Sorting types\n SortHandler,\n SortState,\n ToolbarContentDefinition,\n ToolPanelConfig,\n ToolPanelDefinition,\n TransactionResult,\n TypeDefault,\n UpdateSource,\n} from './lib/core/types';\n\n// Re-export FitModeEnum for runtime usage\nexport { DEFAULT_ANIMATION_CONFIG, DEFAULT_GRID_ICONS, FitModeEnum } from './lib/core/types';\n\n// Re-export sorting utilities for custom sort handlers\nexport { builtInSort, defaultComparator } from './lib/core/internal/sorting';\n// #endregion\n\n// #region Plugin Development\n// Plugin base class - for creating custom plugins\nexport { BaseGridPlugin, PLUGIN_QUERIES } from './lib/core/plugin';\nexport type {\n AfterCellRenderContext,\n AfterRowRenderContext,\n CellMouseEvent,\n EventDefinition,\n PluginDependency,\n PluginManifest,\n PluginQuery,\n QueryDefinition,\n} from './lib/core/plugin';\n\n// DOM constants - for querying grid elements and styling\nexport { GridClasses, GridCSSVars, GridDataAttrs, GridSelectors } from './lib/core/constants';\nexport type { GridClassName, GridCSSVar, GridDataAttr } from './lib/core/constants';\n\n// Note: Plugin-specific types (SelectionConfig, FilterConfig, etc.) are exported\n// from their respective plugin entry points:\n// import { SelectionPlugin, type SelectionConfig } from '@toolbox-web/grid/plugins/selection';\n// import { FilteringPlugin, type FilterConfig } from '@toolbox-web/grid/plugins/filtering';\n// Or import all plugins + types from: '@toolbox-web/grid/all'\n// #endregion\n\n// #region Advanced Types for Custom Plugins & Enterprise Extensions\n/**\n * Internal types for advanced users building custom plugins or enterprise extensions.\n *\n * These types provide access to grid internals that may be needed for deep customization.\n * While not part of the \"stable\" API, they are exported for power users who need them.\n *\n * @remarks\n * Use with caution - these types expose internal implementation details.\n * The underscore-prefixed members they reference are considered less stable\n * than the public API surface.\n *\n * @example\n * ```typescript\n * import { BaseGridPlugin } from '@toolbox-web/grid';\n * import type { InternalGrid, ColumnInternal } from '@toolbox-web/grid';\n *\n * export class MyPlugin extends BaseGridPlugin<MyConfig> {\n * afterRender(): void {\n * // Access grid internals with proper typing\n * const grid = this.grid as InternalGrid;\n * const columns = grid._columns as ColumnInternal[];\n * // ...\n * }\n * }\n * ```\n */\n\n/**\n * Column configuration with internal cache properties.\n * Extends the public ColumnConfig with compiled template caches (__compiledView, __viewTemplate, etc.)\n * @category Plugin Development\n * @internal\n */\nexport type { ColumnInternal } from './lib/core/types';\n\n/**\n * Compiled template function with __blocked property for error handling.\n * @category Plugin Development\n * @internal\n */\nexport type { CompiledViewFunction } from './lib/core/types';\n\n/**\n * Full internal grid interface extending PublicGrid with internal state.\n * Provides typed access to _columns, _rows, virtualization state, etc.\n * @category Plugin Development\n * @internal\n */\nexport type { InternalGrid } from './lib/core/types';\n\n/**\n * Cell context for renderer/editor operations.\n * @category Plugin Development\n * @internal\n */\nexport type { CellContext } from './lib/core/types';\n\n/**\n * Editor execution context extending CellContext with commit/cancel functions.\n * @category Plugin Development\n * @internal\n */\nexport type { EditorExecContext } from './lib/core/types';\n\n/**\n * Template evaluation context for dynamic templates.\n * @category Plugin Development\n * @internal\n */\nexport type { EvalContext } from './lib/core/types';\n\n/**\n * Column resize controller interface.\n * @category Plugin Development\n * @internal\n */\nexport type { ResizeController } from './lib/core/types';\n\n/**\n * Row virtualization state interface.\n * @category Plugin Development\n * @internal\n */\nexport type { VirtualState } from './lib/core/types';\n\n/**\n * Row element with internal editing state cache.\n * Used for tracking editing cell count without querySelector.\n * @category Plugin Development\n * @internal\n */\nexport type { RowElementInternal } from './lib/core/types';\n\n/**\n * Union type for input-like elements that have a `value` property.\n * Covers standard form elements and custom elements with value semantics.\n * @category Plugin Development\n * @internal\n */\nexport type { InputLikeElement } from './lib/core/types';\n\n/**\n * Utility type to safely cast a grid element to InternalGrid for plugin use.\n *\n * @example\n * ```typescript\n * import type { AsInternalGrid, InternalGrid } from '@toolbox-web/grid';\n *\n * class MyPlugin extends BaseGridPlugin {\n * get internalGrid(): InternalGrid {\n * return this.grid as AsInternalGrid;\n * }\n * }\n * ```\n * @category Plugin Development\n * @internal\n */\nexport type AsInternalGrid<T = unknown> = import('./lib/core/types').InternalGrid<T>;\n\n/**\n * Render phase enum for debugging and understanding the render pipeline.\n * Higher phases include all lower phase work.\n * @category Plugin Development\n */\nexport { RenderPhase } from './lib/core/internal/render-scheduler';\n\n/**\n * Hook used by `@toolbox-web/grid/features/registry` to wire the feature resolver\n * into the grid core without adding registry code to the main bundle.\n * Not for external use — call only from built feature-registry entry point.\n * @internal\n */\nexport { setFeatureResolver } from './lib/core/internal/feature-hook';\n// #endregion\n","/**\n * Aggregators Core Registry\n *\n * Provides a central registry for aggregator functions.\n * Built-in aggregators are provided by default.\n * Plugins can register additional aggregators.\n *\n * The registry is exposed as a singleton object that can be accessed:\n * - By ES module imports: import { aggregatorRegistry } from '@toolbox-web/grid'\n * - By UMD/CDN: TbwGrid.aggregatorRegistry\n * - By plugins via context: ctx.aggregatorRegistry\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport type AggregatorFn = (rows: any[], field: string, column?: any) => any;\nexport type AggregatorRef = string | AggregatorFn;\n\n/** Built-in aggregator functions */\nconst builtInAggregators: Record<string, AggregatorFn> = {\n sum: (rows, field) => rows.reduce((acc, row) => acc + (Number(row[field]) || 0), 0),\n avg: (rows, field) => {\n const sum = rows.reduce((acc, row) => acc + (Number(row[field]) || 0), 0);\n return rows.length ? sum / rows.length : 0;\n },\n count: (rows) => rows.length,\n min: (rows, field) => (rows.length ? Math.min(...rows.map((r) => Number(r[field]) || Infinity)) : 0),\n max: (rows, field) => (rows.length ? Math.max(...rows.map((r) => Number(r[field]) || -Infinity)) : 0),\n first: (rows, field) => rows[0]?.[field],\n last: (rows, field) => rows[rows.length - 1]?.[field],\n};\n\n/** Custom aggregator registry (for plugins to add to) */\nconst customAggregators: Map<string, AggregatorFn> = new Map();\n\n/**\n * The aggregator registry singleton.\n * Plugins should access this through context or the global namespace.\n */\nexport const aggregatorRegistry = {\n /**\n * Register a custom aggregator function.\n */\n register(name: string, fn: AggregatorFn): void {\n customAggregators.set(name, fn);\n },\n\n /**\n * Unregister a custom aggregator function.\n */\n unregister(name: string): void {\n customAggregators.delete(name);\n },\n\n /**\n * Get an aggregator function by reference.\n */\n get(ref: AggregatorRef | undefined): AggregatorFn | undefined {\n if (ref === undefined) return undefined;\n if (typeof ref === 'function') return ref;\n // Check custom first, then built-in\n return customAggregators.get(ref) ?? builtInAggregators[ref];\n },\n\n /**\n * Run an aggregator on a set of rows.\n */\n run(ref: AggregatorRef | undefined, rows: any[], field: string, column?: any): any {\n const fn = this.get(ref);\n return fn ? fn(rows, field, column) : undefined;\n },\n\n /**\n * Check if an aggregator exists.\n */\n has(name: string): boolean {\n return customAggregators.has(name) || name in builtInAggregators;\n },\n\n /**\n * List all available aggregator names.\n */\n list(): string[] {\n return [...Object.keys(builtInAggregators), ...customAggregators.keys()];\n },\n};\n\n// #region Value-based Aggregators\n// Used by plugins like Pivot that work with pre-extracted numeric values\n\nexport type ValueAggregatorFn = (values: number[]) => number;\n\n/**\n * Built-in value-based aggregators.\n * These operate on arrays of numbers (unlike row-based aggregators).\n */\nconst builtInValueAggregators: Record<string, ValueAggregatorFn> = {\n sum: (vals) => vals.reduce((a, b) => a + b, 0),\n avg: (vals) => (vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : 0),\n count: (vals) => vals.length,\n min: (vals) => (vals.length ? Math.min(...vals) : 0),\n max: (vals) => (vals.length ? Math.max(...vals) : 0),\n first: (vals) => vals[0] ?? 0,\n last: (vals) => vals[vals.length - 1] ?? 0,\n};\n\n/**\n * Get a value-based aggregator function.\n * Used by Pivot plugin and other features that aggregate pre-extracted values.\n *\n * @param aggFunc - Aggregation function name ('sum', 'avg', 'count', 'min', 'max', 'first', 'last')\n * @returns Aggregator function that takes number[] and returns number\n */\nexport function getValueAggregator(aggFunc: string): ValueAggregatorFn {\n return builtInValueAggregators[aggFunc] ?? builtInValueAggregators.sum;\n}\n\n/**\n * Run a value-based aggregator on a set of values.\n *\n * @param aggFunc - Aggregation function name\n * @param values - Array of numbers to aggregate\n * @returns Aggregated result\n */\nexport function runValueAggregator(aggFunc: string, values: number[]): number {\n return getValueAggregator(aggFunc)(values);\n}\n// #endregion\n\n// Legacy function exports for backward compatibility\nexport const registerAggregator = aggregatorRegistry.register.bind(aggregatorRegistry);\nexport const unregisterAggregator = aggregatorRegistry.unregister.bind(aggregatorRegistry);\nexport const getAggregator = aggregatorRegistry.get.bind(aggregatorRegistry);\nexport const runAggregator = aggregatorRegistry.run.bind(aggregatorRegistry);\nexport const listAggregators = aggregatorRegistry.list.bind(aggregatorRegistry);\n","/**\n * Base Grid Plugin Class\n *\n * All plugins extend this abstract class.\n * Plugins are instantiated per-grid and manage their own state.\n */\n\n// Injected by Vite at build time from package.json (same as grid.ts)\ndeclare const __GRID_VERSION__: string;\n\nimport { type DiagnosticCode, formatDiagnostic, gridPrefix } from '../internal/diagnostics';\nimport type {\n ColumnConfig,\n ColumnState,\n GridPlugin,\n HeaderContentDefinition,\n IconValue,\n PluginNameMap,\n ToolPanelDefinition,\n} from '../types';\nimport { DEFAULT_GRID_ICONS } from '../types';\n\n// Re-export shared plugin types for convenience\nexport { PLUGIN_QUERIES } from './types';\nexport type {\n AfterCellRenderContext,\n AfterRowRenderContext,\n CellClickEvent,\n CellCoords,\n CellEditor,\n CellMouseEvent,\n CellRenderer,\n ContextMenuItem,\n ContextMenuParams,\n GridElementRef,\n HeaderClickEvent,\n HeaderRenderer,\n KeyboardModifiers,\n PluginCellRenderContext,\n PluginQuery,\n RowClickEvent,\n ScrollEvent,\n} from './types';\n\nimport type {\n AfterCellRenderContext,\n AfterRowRenderContext,\n CellClickEvent,\n CellEditor,\n CellMouseEvent,\n CellRenderer,\n GridElementRef,\n HeaderClickEvent,\n HeaderRenderer,\n PluginQuery,\n RowClickEvent,\n ScrollEvent,\n} from './types';\n\n/**\n * Grid element interface for plugins.\n * Extends GridElementRef with plugin-specific methods.\n * Note: effectiveConfig is already available from GridElementRef.\n */\nexport interface GridElement extends GridElementRef {\n getPlugin<T extends BaseGridPlugin>(PluginClass: new (...args: any[]) => T): T | undefined;\n getPluginByName<K extends string>(\n name: K,\n ): (K extends keyof PluginNameMap ? PluginNameMap[K] : BaseGridPlugin) | undefined;\n /**\n * Get a plugin's state by plugin name.\n * This is a loose-coupling method for plugins to access other plugins' state\n * without importing the plugin class directly.\n * @internal Plugin API\n */\n getPluginState?(name: string): unknown;\n\n // Focus Management\n /** Register an external DOM element as a logical focus container. @internal Plugin API */\n registerExternalFocusContainer?(el: Element): void;\n /** Unregister a previously registered external focus container. @internal Plugin API */\n unregisterExternalFocusContainer?(el: Element): void;\n /** Check whether focus is logically inside this grid (own DOM + external containers). @internal Plugin API */\n containsFocus?(node?: Node | null): boolean;\n}\n\n/**\n * Header render context for plugin header renderers.\n */\nexport interface PluginHeaderRenderContext {\n /** Column configuration */\n column: ColumnConfig;\n /** Column index */\n colIndex: number;\n}\n\n// ============================================================================\n// Plugin Dependency Types\n// ============================================================================\n\n/**\n * Declares a dependency on another plugin.\n *\n * @category Plugin Development\n * @example\n * ```typescript\n * export class UndoRedoPlugin extends BaseGridPlugin {\n * static readonly dependencies: PluginDependency[] = [\n * { name: 'editing', required: true },\n * ];\n * }\n * ```\n */\nexport interface PluginDependency {\n /**\n * The name of the required plugin (matches the plugin's `name` property).\n * Use string names for loose coupling - avoids static imports.\n */\n name: string;\n\n /**\n * Whether this dependency is required (hard) or optional (soft).\n * - `true`: Plugin cannot function without it. Throws error if missing.\n * - `false`: Plugin works with reduced functionality. Logs info if missing.\n * @default true\n */\n required?: boolean;\n\n /**\n * Human-readable reason for this dependency.\n * Shown in error/info messages to help users understand why.\n * @example \"UndoRedoPlugin needs EditingPlugin to track cell edits\"\n */\n reason?: string;\n}\n\n/**\n * Declares an incompatibility between plugins.\n * When both plugins are loaded, a warning is logged to help users understand the conflict.\n *\n * @category Plugin Development\n */\nexport interface PluginIncompatibility {\n /**\n * The name of the incompatible plugin (matches the plugin's `name` property).\n */\n name: string;\n\n /**\n * Human-readable reason for the incompatibility.\n * Should explain why the plugins conflict and any workarounds.\n * @example \"Both transform the entire row model in different ways\"\n */\n reason: string;\n}\n\n// ============================================================================\n// Plugin Query & Event Definitions\n// ============================================================================\n\n/**\n * Defines a query that a plugin can handle.\n * Other plugins or the grid can send this query type to retrieve data.\n *\n * @category Plugin Development\n *\n * @example\n * ```typescript\n * // In manifest\n * queries: [\n * {\n * type: 'canMoveColumn',\n * description: 'Check if a column can be moved/reordered',\n * },\n * ]\n *\n * // In plugin class\n * handleQuery(query: PluginQuery): unknown {\n * if (query.type === 'canMoveColumn') {\n * return this.canMoveColumn(query.context);\n * }\n * }\n * ```\n */\nexport interface QueryDefinition {\n /**\n * The query type identifier (e.g., 'canMoveColumn', 'getContextMenuItems').\n * Should be unique across all plugins.\n */\n type: string;\n\n /**\n * Human-readable description of what the query does.\n */\n description?: string;\n}\n\n/**\n * Defines an event that a plugin can emit.\n * Other plugins can subscribe to these events via the Event Bus.\n *\n * @category Plugin Development\n *\n * @example\n * ```typescript\n * // In manifest\n * events: [\n * {\n * type: 'filter-change',\n * description: 'Emitted when filter criteria change',\n * },\n * ]\n *\n * // In plugin class - emit\n * this.emitPluginEvent('filter-change', { field: 'name', value: 'Alice' });\n *\n * // In another plugin - subscribe\n * this.on('filter-change', (detail) => console.log('Filter changed:', detail));\n * ```\n */\nexport interface EventDefinition {\n /**\n * The event type identifier (e.g., 'filter-change', 'selection-change').\n * Used when emitting and subscribing to events.\n */\n type: string;\n\n /**\n * Human-readable description of what the event represents.\n */\n description?: string;\n\n /**\n * Whether this event is cancelable via `event.preventDefault()`.\n * @default false\n */\n cancelable?: boolean;\n}\n\n// ============================================================================\n// Plugin Manifest Types\n// ============================================================================\n\n/**\n * Defines a property that a plugin \"owns\" - used for configuration validation.\n * When this property is used without the owning plugin loaded, an error is thrown.\n *\n * @category Plugin Development\n */\nexport interface PluginPropertyDefinition {\n /** The property name on column or grid config */\n property: string;\n /** Whether this is a column-level or config-level property */\n level: 'column' | 'config';\n /** Human-readable description for error messages (e.g., 'the \"editable\" column property') */\n description: string;\n /** Import path hint for error messages (e.g., \"import { EditingPlugin } from '@toolbox-web/grid/plugins/editing';\") */\n importHint?: string;\n /** Custom check for whether property is considered \"used\" (default: truthy value check) */\n isUsed?: (value: unknown) => boolean;\n}\n\n/**\n * A configuration validation rule for detecting invalid/conflicting settings.\n * Plugins declare rules in their manifest; the validator executes them during grid initialization.\n *\n * @category Plugin Development\n * @template TConfig - The plugin's configuration type\n */\nexport interface PluginConfigRule<TConfig = unknown> {\n /** Rule identifier for debugging (e.g., 'selection/range-dblclick') */\n id: string;\n /** Severity: 'error' throws, 'warn' logs warning */\n severity: 'error' | 'warn';\n /** Human-readable message shown when rule is violated */\n message: string;\n /** Predicate returning true if the rule is VIOLATED (i.e., config is invalid) */\n check: (pluginConfig: TConfig) => boolean;\n}\n\n/**\n * Hook names that can have execution priority configured.\n *\n * @category Plugin Development\n */\nexport type HookName =\n | 'processColumns'\n | 'processRows'\n | 'afterRender'\n | 'afterCellRender'\n | 'afterRowRender'\n | 'onCellClick'\n | 'onCellMouseDown'\n | 'onCellMouseMove'\n | 'onCellMouseUp'\n | 'onKeyDown'\n | 'onScroll'\n | 'onScrollRender';\n\n/**\n * Static metadata about a plugin's capabilities and requirements.\n * Declared as a static property on plugin classes.\n *\n * @category Plugin Development\n * @template TConfig - The plugin's configuration type\n *\n * @example\n * ```typescript\n * export class MyPlugin extends BaseGridPlugin<MyConfig> {\n * static override readonly manifest: PluginManifest<MyConfig> = {\n * ownedProperties: [\n * { property: 'myProp', level: 'column', description: 'the \"myProp\" column property' },\n * ],\n * configRules: [\n * { id: 'my-plugin/invalid-combo', severity: 'warn', message: '...', check: (c) => c.a && c.b },\n * ],\n * };\n * readonly name = 'myPlugin';\n * }\n * ```\n */\nexport interface PluginManifest<TConfig = unknown> {\n /**\n * Properties this plugin owns - validated by validate-config.ts.\n * If a user uses one of these properties without loading the plugin, an error is thrown.\n */\n ownedProperties?: PluginPropertyDefinition[];\n\n /**\n * Hook execution priority (higher = later, default 0).\n * Use negative values to run earlier, positive to run later.\n */\n hookPriority?: Partial<Record<HookName, number>>;\n\n /**\n * Configuration validation rules - checked during grid initialization.\n * Rules with severity 'error' throw, 'warn' logs to console.\n */\n configRules?: PluginConfigRule<TConfig>[];\n\n /**\n * Plugins that are incompatible with this plugin.\n * When both plugins are loaded together, a warning is shown.\n *\n * @example\n * ```typescript\n * incompatibleWith: [\n * { name: 'tree', reason: 'Both transform the entire row model in different ways' },\n * ],\n * ```\n */\n incompatibleWith?: PluginIncompatibility[];\n\n /**\n * Queries this plugin can handle.\n * Declares what query types this plugin responds to via `handleQuery()`.\n * This replaces the centralized PLUGIN_QUERIES approach with manifest-declared queries.\n *\n * @example\n * ```typescript\n * queries: [\n * { type: 'canMoveColumn', description: 'Check if a column can be moved' },\n * ],\n * ```\n */\n queries?: QueryDefinition[];\n\n /**\n * Events this plugin can emit.\n * Declares what event types other plugins can subscribe to via `on()`.\n *\n * @example\n * ```typescript\n * events: [\n * { type: 'filter-change', description: 'Emitted when filter criteria change' },\n * ],\n * ```\n */\n events?: EventDefinition[];\n}\n\n/**\n * Abstract base class for all grid plugins.\n *\n * @category Plugin Development\n * @template TConfig - Configuration type for the plugin\n */\nexport abstract class BaseGridPlugin<TConfig = unknown> implements GridPlugin {\n /**\n * Plugin dependencies - declare other plugins this one requires.\n *\n * Dependencies are validated when the plugin is attached.\n * Required dependencies throw an error if missing.\n * Optional dependencies log an info message if missing.\n *\n * @example\n * ```typescript\n * static readonly dependencies: PluginDependency[] = [\n * { name: 'editing', required: true, reason: 'Tracks cell edits for undo/redo' },\n * { name: 'selection', required: false, reason: 'Enables selection-based undo' },\n * ];\n * ```\n */\n static readonly dependencies?: PluginDependency[];\n\n /**\n * Plugin manifest - declares owned properties, config rules, and hook priorities.\n *\n * This is read by the configuration validator to:\n * - Validate that required plugins are loaded when their properties are used\n * - Execute configRules to detect invalid/conflicting settings\n * - Order hook execution based on priority\n *\n * @example\n * ```typescript\n * static override readonly manifest: PluginManifest<MyConfig> = {\n * ownedProperties: [\n * { property: 'myProp', level: 'column', description: 'the \"myProp\" column property' },\n * ],\n * configRules: [\n * { id: 'myPlugin/conflict', severity: 'warn', message: '...', check: (c) => c.a && c.b },\n * ],\n * };\n * ```\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n static readonly manifest?: PluginManifest<any>;\n\n /** Unique plugin identifier (derived from class name by default) */\n abstract readonly name: string;\n\n /**\n * Alternative names for backward compatibility.\n * `getPluginByName()` matches against both `name` and `aliases`.\n * @internal\n */\n readonly aliases?: readonly string[];\n\n /**\n * Plugin version - defaults to grid version for built-in plugins.\n * Third-party plugins can override with their own semver.\n */\n readonly version: string = typeof __GRID_VERSION__ !== 'undefined' ? __GRID_VERSION__ : 'dev';\n\n /** CSS styles to inject into the grid's shadow DOM */\n readonly styles?: string;\n\n /** Custom cell renderers keyed by type name */\n readonly cellRenderers?: Record<string, CellRenderer>;\n\n /** Custom header renderers keyed by type name */\n readonly headerRenderers?: Record<string, HeaderRenderer>;\n\n /** Custom cell editors keyed by type name */\n readonly cellEditors?: Record<string, CellEditor>;\n\n /** The grid instance this plugin is attached to */\n protected grid!: GridElement;\n\n /** Plugin configuration - merged with defaults in attach() */\n protected config!: TConfig;\n\n /** User-provided configuration from constructor */\n protected readonly userConfig: Partial<TConfig>;\n\n /**\n * Plugin-level AbortController for event listener cleanup.\n * Created fresh in attach(), aborted in detach().\n * This ensures event listeners are properly cleaned up when plugins are re-attached.\n */\n #abortController?: AbortController;\n\n /**\n * Default configuration - subclasses should override this getter.\n * Note: This must be a getter (not property initializer) for proper inheritance\n * since property initializers run after parent constructor.\n */\n protected get defaultConfig(): Partial<TConfig> {\n return {};\n }\n\n constructor(config: Partial<TConfig> = {}) {\n this.userConfig = config;\n }\n\n /**\n * Called when the plugin is attached to a grid.\n * Override to set up event listeners, initialize state, etc.\n *\n * @example\n * ```ts\n * attach(grid: GridElement): void {\n * super.attach(grid);\n * // Set up document-level listeners with auto-cleanup\n * document.addEventListener('keydown', this.handleEscape, {\n * signal: this.disconnectSignal\n * });\n * }\n * ```\n */\n attach(grid: GridElement): void {\n // Abort any previous abort controller (in case of re-attach without detach)\n this.#abortController?.abort();\n // Create fresh abort controller for this attachment\n this.#abortController = new AbortController();\n\n this.grid = grid;\n // Merge config here (after subclass construction is complete)\n this.config = { ...this.defaultConfig, ...this.userConfig } as TConfig;\n }\n\n /**\n * Called when the plugin is detached from a grid.\n * Override to clean up event listeners, timers, etc.\n *\n * @example\n * ```ts\n * detach(): void {\n * // Clean up any state not handled by disconnectSignal\n * this.selectedRows.clear();\n * this.cache = null;\n * }\n * ```\n */\n detach(): void {\n // Abort the plugin's abort controller to clean up all event listeners\n // registered with { signal: this.disconnectSignal }\n this.#abortController?.abort();\n this.#abortController = undefined;\n // Override in subclass for additional cleanup\n }\n\n /**\n * Called when another plugin is attached to the same grid.\n * Use for inter-plugin coordination, e.g., to integrate with new plugins.\n *\n * @param name - The name of the plugin that was attached\n * @param plugin - The plugin instance (for direct access if needed)\n *\n * @example\n * ```ts\n * onPluginAttached(name: string, plugin: BaseGridPlugin): void {\n * if (name === 'selection') {\n * // Integrate with selection plugin\n * this.selectionPlugin = plugin as SelectionPlugin;\n * }\n * }\n * ```\n */\n onPluginAttached?(name: string, plugin: BaseGridPlugin): void;\n\n /**\n * Called when another plugin is detached from the same grid.\n * Use to clean up inter-plugin references.\n *\n * @param name - The name of the plugin that was detached\n *\n * @example\n * ```ts\n * onPluginDetached(name: string): void {\n * if (name === 'selection') {\n * this.selectionPlugin = undefined;\n * }\n * }\n * ```\n */\n onPluginDetached?(name: string): void;\n\n /**\n * Get another plugin instance from the same grid.\n * Use for inter-plugin communication.\n *\n * **Prefer {@link BaseGridPlugin.grid grid.getPluginByName()}** when you don't need the class import.\n *\n * @example\n * ```ts\n * // Preferred: by name\n * const selection = this.grid?.getPluginByName('selection');\n *\n * // Alternative: by class\n * const selection = this.getPlugin(SelectionPlugin);\n * if (selection) {\n * const selectedRows = selection.getSelectedRows();\n * }\n * ```\n */\n protected getPlugin<T extends BaseGridPlugin>(PluginClass: new (...args: any[]) => T): T | undefined {\n return this.grid?.getPlugin(PluginClass);\n }\n\n /**\n * Emit a custom event from the grid.\n */\n protected emit<T>(eventName: string, detail: T): void {\n this.grid?.dispatchEvent?.(new CustomEvent(eventName, { detail, bubbles: true }));\n }\n\n /**\n * Emit a cancelable custom event from the grid.\n * @returns `true` if the event was cancelled (preventDefault called), `false` otherwise\n */\n protected emitCancelable<T>(eventName: string, detail: T): boolean {\n const event = new CustomEvent(eventName, { detail, bubbles: true, cancelable: true });\n this.grid?.dispatchEvent?.(event);\n return event.defaultPrevented;\n }\n\n // =========================================================================\n // Event Bus - Plugin-to-Plugin Communication\n // =========================================================================\n\n /**\n * Subscribe to an event from another plugin.\n * The subscription is automatically cleaned up when this plugin is detached.\n *\n * @category Plugin Development\n * @param eventType - The event type to listen for (e.g., 'filter-change')\n * @param callback - The callback to invoke when the event is emitted\n *\n * @example\n * ```typescript\n * // In attach() or other initialization\n * this.on('filter-change', (detail) => {\n * console.log('Filter changed:', detail);\n * });\n * ```\n */\n protected on<T = unknown>(eventType: string, callback: (detail: T) => void): void {\n this.grid?._pluginManager?.subscribe(this, eventType, callback as (detail: unknown) => void);\n }\n\n /**\n * Unsubscribe from a plugin event.\n *\n * @category Plugin Development\n * @param eventType - The event type to stop listening for\n *\n * @example\n * ```typescript\n * this.off('filter-change');\n * ```\n */\n protected off(eventType: string): void {\n this.grid?._pluginManager?.unsubscribe(this, eventType);\n }\n\n /**\n * Emit an event to other plugins via the Event Bus.\n * This is for inter-plugin communication only; it does NOT dispatch DOM events.\n * Use `emit()` to dispatch DOM events that external consumers can listen to.\n *\n * @category Plugin Development\n * @param eventType - The event type to emit (should be declared in manifest.events)\n * @param detail - The event payload\n *\n * @example\n * ```typescript\n * // Emit to other plugins (not DOM)\n * this.emitPluginEvent('filter-change', { field: 'name', value: 'Alice' });\n *\n * // For DOM events that consumers can addEventListener to:\n * this.emit('filter-change', { field: 'name', value: 'Alice' });\n * ```\n */\n protected emitPluginEvent<T>(eventType: string, detail: T): void {\n this.grid?._pluginManager?.emitPluginEvent(eventType, detail);\n }\n\n /**\n * Request a re-render of the grid.\n * Uses ROWS phase - does NOT trigger processColumns hooks.\n */\n protected requestRender(): void {\n this.grid?.requestRender?.();\n }\n\n /**\n * Request a columns re-render of the grid.\n * Uses COLUMNS phase - triggers processColumns hooks.\n * Use this when your plugin needs to reprocess column configuration.\n */\n protected requestColumnsRender(): void {\n (this.grid as { requestColumnsRender?: () => void })?.requestColumnsRender?.();\n }\n\n /**\n * Request a re-render and restore focus styling afterward.\n * Use this when a plugin action (like expand/collapse) triggers a render\n * but needs to maintain keyboard navigation focus.\n */\n protected requestRenderWithFocus(): void {\n this.grid?.requestRenderWithFocus?.();\n }\n\n /**\n * Request a lightweight style update without rebuilding DOM.\n * Use this instead of requestRender() when only CSS classes need updating.\n */\n protected requestAfterRender(): void {\n this.grid?.requestAfterRender?.();\n }\n\n /**\n * Re-render visible rows without rebuilding the row model or recalculating geometry.\n * Use this when row data has been updated in-place (e.g., server-side block loads)\n * and only the visible viewport needs to re-render.\n */\n protected requestVirtualRefresh(): void {\n this.grid?.requestVirtualRefresh?.();\n }\n\n /**\n * Get the current rows from the grid.\n */\n protected get rows(): any[] {\n return this.grid?.rows ?? [];\n }\n\n /**\n * Get the original unfiltered/unprocessed rows from the grid.\n * Use this when you need all source data regardless of active filters.\n */\n protected get sourceRows(): any[] {\n return this.grid?.sourceRows ?? [];\n }\n\n /**\n * Get the current columns from the grid.\n */\n protected get columns(): ColumnConfig[] {\n return this.grid?.columns ?? [];\n }\n\n /**\n * Get only visible columns from the grid (excludes hidden).\n * Use this for rendering that needs to match the grid template.\n */\n protected get visibleColumns(): ColumnConfig[] {\n return this.grid?._visibleColumns ?? [];\n }\n\n /**\n * Get the grid as an HTMLElement for direct DOM operations.\n * Use sparingly - prefer the typed GridElementRef API when possible.\n *\n * @example\n * ```ts\n * const width = this.gridElement.clientWidth;\n * this.gridElement.classList.add('my-plugin-active');\n * ```\n */\n protected get gridElement(): HTMLElement {\n return this.grid?._hostElement;\n }\n\n /**\n * Get the disconnect signal for event listener cleanup.\n * This signal is aborted when the grid disconnects from the DOM.\n * Use this when adding event listeners that should be cleaned up automatically.\n *\n * Best for:\n * - Document/window-level listeners added in attach()\n * - Listeners on the grid element itself\n * - Any listener that should persist across renders\n *\n * Not needed for:\n * - Listeners on elements created in afterRender() (removed with element)\n *\n * @example\n * element.addEventListener('click', handler, { signal: this.disconnectSignal });\n * document.addEventListener('keydown', handler, { signal: this.disconnectSignal });\n */\n protected get disconnectSignal(): AbortSignal {\n // Return the plugin's own abort signal for proper cleanup on detach/re-attach\n // Falls back to grid's signal if plugin's controller not yet created\n return this.#abortController?.signal ?? this.grid?.disconnectSignal;\n }\n\n /**\n * Get the grid-level icons configuration.\n * Returns merged icons (user config + defaults).\n */\n protected get gridIcons(): typeof DEFAULT_GRID_ICONS {\n const userIcons = this.grid?.gridConfig?.icons ?? {};\n return { ...DEFAULT_GRID_ICONS, ...userIcons };\n }\n\n // #region Animation Helpers\n\n /**\n * Check if animations are enabled at the grid level.\n * Respects gridConfig.animation.mode and the CSS variable set by the grid.\n *\n * Plugins should use this to skip animations when:\n * - Animation mode is 'off' or `false`\n * - User prefers reduced motion and mode is 'reduced-motion' (default)\n *\n * @example\n * ```ts\n * private get animationStyle(): 'slide' | 'fade' | false {\n * if (!this.isAnimationEnabled) return false;\n * return this.config.animation ?? 'slide';\n * }\n * ```\n */\n protected get isAnimationEnabled(): boolean {\n const mode = this.grid?.effectiveConfig?.animation?.mode ?? 'reduced-motion';\n\n // Explicit off = disabled\n if (mode === false || mode === 'off') return false;\n\n // Explicit on = always enabled\n if (mode === true || mode === 'on') return true;\n\n // reduced-motion: check CSS variable (set by grid based on media query)\n const host = this.gridElement;\n if (host) {\n const enabled = getComputedStyle(host).getPropertyValue('--tbw-animation-enabled').trim();\n return enabled !== '0';\n }\n\n return true; // Default to enabled\n }\n\n /**\n * Get the animation duration in milliseconds from CSS variable.\n * Falls back to 200ms if not set.\n *\n * Plugins can use this for their animation timing to stay consistent\n * with the grid-level animation.duration setting.\n *\n * @example\n * ```ts\n * element.animate(keyframes, { duration: this.animationDuration });\n * ```\n */\n protected get animationDuration(): number {\n const host = this.gridElement;\n if (host) {\n const durationStr = getComputedStyle(host).getPropertyValue('--tbw-animation-duration').trim();\n const parsed = parseInt(durationStr, 10);\n if (!isNaN(parsed)) return parsed;\n }\n return 200; // Default\n }\n\n // #endregion\n\n /**\n * Resolve an icon value to string or HTMLElement.\n * Checks plugin config first, then grid-level icons, then defaults.\n *\n * @param iconKey - The icon key in GridIcons (e.g., 'expand', 'collapse')\n * @param pluginOverride - Optional plugin-level override\n * @returns The resolved icon value\n */\n protected resolveIcon(iconKey: keyof typeof DEFAULT_GRID_ICONS, pluginOverride?: IconValue): IconValue {\n // Plugin override takes precedence\n if (pluginOverride !== undefined) {\n return pluginOverride;\n }\n // Then grid-level config\n return this.gridIcons[iconKey];\n }\n\n /**\n * Set an icon value on an element.\n * Handles both string (text/HTML) and HTMLElement values.\n *\n * @param element - The element to set the icon on\n * @param icon - The icon value (string or HTMLElement)\n */\n protected setIcon(element: HTMLElement, icon: IconValue): void {\n if (typeof icon === 'string') {\n element.innerHTML = icon;\n } else if (icon instanceof HTMLElement) {\n element.innerHTML = '';\n element.appendChild(icon.cloneNode(true));\n }\n }\n\n /**\n * Log a warning with an optional diagnostic code.\n *\n * When a diagnostic code is provided, the message is formatted with the code\n * and a link to the troubleshooting docs.\n *\n * @example\n * ```ts\n * this.warn('Something went wrong'); // plain\n * this.warn(MISSING_BREAKPOINT, 'Set a breakpoint'); // with code + docs link\n * ```\n */\n protected warn(message: string): void;\n protected warn(code: DiagnosticCode, message: string): void;\n protected warn(codeOrMessage: DiagnosticCode | string, message?: string): void {\n if (message !== undefined) {\n // Called with (code, message)\n console.warn(formatDiagnostic(codeOrMessage as DiagnosticCode, message, this.gridElement.id, this.name));\n } else {\n // Called with (message) — plain warning, no diagnostic code\n console.warn(`${gridPrefix(this.gridElement.id, this.name)} ${codeOrMessage}`);\n }\n }\n\n /**\n * Throw an error with a diagnostic code and docs link.\n * Use for configuration errors and API misuse that should halt execution.\n */\n protected throwDiagnostic(code: DiagnosticCode, message: string): never {\n throw new Error(formatDiagnostic(code, message, this.gridElement.id, this.name));\n }\n\n // #region Lifecycle Hooks\n\n /**\n * Transform rows before rendering.\n * Called during each render cycle before rows are rendered to the DOM.\n * Use this to filter, sort, or add computed properties to rows.\n *\n * @param rows - The current rows array (readonly to encourage returning a new array)\n * @returns The modified rows array to render\n *\n * @example\n * ```ts\n * processRows(rows: readonly any[]): any[] {\n * // Filter out hidden rows\n * return rows.filter(row => !row._hidden);\n * }\n * ```\n *\n * @example\n * ```ts\n * processRows(rows: readonly any[]): any[] {\n * // Add computed properties\n * return rows.map(row => ({\n * ...row,\n * _fullName: `${row.firstName} ${row.lastName}`\n * }));\n * }\n * ```\n */\n processRows?(rows: readonly any[]): any[];\n\n /**\n * Transform columns before rendering.\n * Called during each render cycle before column headers and cells are rendered.\n * Use this to add, remove, or modify column definitions.\n *\n * @param columns - The current columns array (readonly to encourage returning a new array)\n * @returns The modified columns array to render\n *\n * @example\n * ```ts\n * processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n * // Add a selection checkbox column\n * return [\n * { field: '_select', header: '', width: 40 },\n * ...columns\n * ];\n * }\n * ```\n */\n processColumns?(columns: readonly ColumnConfig[]): ColumnConfig[];\n\n /**\n * Called before each render cycle begins.\n * Use this to prepare state or cache values needed during rendering.\n *\n * **Note:** This hook is currently a placeholder for future implementation.\n * It is defined in the interface but not yet invoked by the grid's render pipeline.\n * If you need this functionality, please open an issue or contribute an implementation.\n *\n * @example\n * ```ts\n * beforeRender(): void {\n * this.visibleRowCount = this.calculateVisibleRows();\n * }\n * ```\n */\n beforeRender?(): void;\n\n /**\n * Called after each render cycle completes.\n * Use this for DOM manipulation, adding event listeners to rendered elements,\n * or applying visual effects like selection highlights.\n *\n * @example\n * ```ts\n * afterRender(): void {\n * // Apply selection styling to rendered rows\n * const rows = this.gridElement?.querySelectorAll('.data-row');\n * rows?.forEach((row, i) => {\n * row.classList.toggle('selected', this.selectedRows.has(i));\n * });\n * }\n * ```\n */\n afterRender?(): void;\n\n /**\n * Called after each cell is rendered.\n * This hook is more efficient than `afterRender()` for cell-level modifications\n * because you receive the cell context directly - no DOM queries needed.\n *\n * Use cases:\n * - Adding selection/highlight classes to specific cells\n * - Injecting badges or decorations\n * - Applying conditional styling based on cell value\n *\n * Performance note: Called for every visible cell during render. Keep implementation fast.\n * This hook is also called during scroll when cells are recycled.\n *\n * @param context - The cell render context with row, column, value, and elements\n *\n * @example\n * ```ts\n * afterCellRender(context: AfterCellRenderContext): void {\n * // Add selection class without DOM queries\n * if (this.isSelected(context.rowIndex, context.colIndex)) {\n * context.cellElement.classList.add('selected');\n * }\n *\n * // Add validation error styling\n * if (this.hasError(context.row, context.column.field)) {\n * context.cellElement.classList.add('has-error');\n * }\n * }\n * ```\n */\n afterCellRender?(context: AfterCellRenderContext): void;\n\n /**\n * Called after a row is fully rendered (all cells complete).\n * Use this for row-level decorations, styling, or ARIA attributes.\n *\n * Common use cases:\n * - Adding selection classes to entire rows (row-focus, selected)\n * - Setting row-level ARIA attributes\n * - Applying row validation highlighting\n * - Tree indentation styling\n *\n * Performance note: Called for every visible row during render. Keep implementation fast.\n * This hook is also called during scroll when rows are recycled.\n *\n * @param context - The row render context with row data and element\n *\n * @example\n * ```ts\n * afterRowRender(context: AfterRowRenderContext): void {\n * // Add row selection class without DOM queries\n * if (this.isRowSelected(context.rowIndex)) {\n * context.rowElement.classList.add('selected', 'row-focus');\n * }\n *\n * // Add validation error styling\n * if (this.rowHasErrors(context.row)) {\n * context.rowElement.classList.add('has-errors');\n * }\n * }\n * ```\n */\n afterRowRender?(context: AfterRowRenderContext): void;\n\n /**\n * Called after scroll-triggered row rendering completes.\n * This is a lightweight hook for applying visual state to recycled DOM elements.\n * Use this instead of afterRender when you need to reapply styling during scroll.\n *\n * Performance note: This is called frequently during scroll. Keep implementation fast.\n *\n * @example\n * ```ts\n * onScrollRender(): void {\n * // Reapply selection state to visible cells\n * this.applySelectionToVisibleCells();\n * }\n * ```\n */\n onScrollRender?(): void;\n\n /**\n * Return extra height contributed by this plugin (e.g., expanded detail rows).\n * Used to adjust scrollbar height calculations for virtualization.\n *\n * @returns Total extra height in pixels\n *\n * @deprecated Use {@link getRowHeight} instead. This hook will be removed in v2.0.\n * The new `getRowHeight(row, index)` hook provides per-row height information which\n * enables better position caching and variable row height support.\n *\n * @example\n * ```ts\n * // OLD (deprecated):\n * getExtraHeight(): number {\n * return this.expandedRows.size * this.detailHeight;\n * }\n *\n * // NEW (preferred):\n * getRowHeight(row: unknown, index: number): number | undefined {\n * if (this.isExpanded(row)) {\n * return this.baseRowHeight + this.getDetailHeight(row);\n * }\n * return undefined;\n * }\n * ```\n */\n getExtraHeight?(): number;\n\n /**\n * Return extra height that appears before a given row index.\n * Used by virtualization to correctly calculate scroll positions when\n * there's variable height content (like expanded detail rows) above the viewport.\n *\n * @param beforeRowIndex - The row index to calculate extra height before\n * @returns Extra height in pixels that appears before this row\n *\n * @deprecated Use {@link getRowHeight} instead. This hook will be removed in v2.0.\n * The new `getRowHeight(row, index)` hook provides per-row height information which\n * enables better position caching and variable row height support.\n *\n * @example\n * ```ts\n * // OLD (deprecated):\n * getExtraHeightBefore(beforeRowIndex: number): number {\n * let height = 0;\n * for (const expandedRowIndex of this.expandedRowIndices) {\n * if (expandedRowIndex < beforeRowIndex) {\n * height += this.getDetailHeight(expandedRowIndex);\n * }\n * }\n * return height;\n * }\n *\n * // NEW (preferred):\n * getRowHeight(row: unknown, index: number): number | undefined {\n * if (this.isExpanded(row)) {\n * return this.baseRowHeight + this.getDetailHeight(row);\n * }\n * return undefined;\n * }\n * ```\n */\n getExtraHeightBefore?(beforeRowIndex: number): number;\n\n /**\n * Get the height of a specific row.\n * Used for synthetic rows (group headers, detail panels, etc.) that have fixed heights.\n * Return undefined if this plugin does not manage the height for this row.\n *\n * This hook is called during position cache rebuilds for variable row height virtualization.\n * Plugins that create synthetic rows should implement this to provide accurate heights.\n *\n * @param row - The row data\n * @param index - The row index in the processed rows array\n * @returns The row height in pixels, or undefined if not managed by this plugin\n *\n * @example\n * ```ts\n * getRowHeight(row: unknown, index: number): number | undefined {\n * // Group headers have a fixed height\n * if (this.isGroupHeader(row)) {\n * return 32;\n * }\n * return undefined; // Let grid use default/measured height\n * }\n * ```\n */\n getRowHeight?(row: unknown, index: number): number | undefined;\n\n /**\n * Adjust the virtualization start index to render additional rows before the visible range.\n * Use this when expanded content (like detail rows) needs its parent row to remain rendered\n * even when the parent row itself has scrolled above the viewport.\n *\n * @param start - The calculated start row index\n * @param scrollTop - The current scroll position\n * @param rowHeight - The height of a single row\n * @returns The adjusted start index (lower than or equal to original start)\n *\n * @example\n * ```ts\n * adjustVirtualStart(start: number, scrollTop: number, rowHeight: number): number {\n * // If row 5 is expanded and scrolled partially, keep it rendered\n * for (const expandedRowIndex of this.expandedRowIndices) {\n * const expandedRowTop = expandedRowIndex * rowHeight;\n * const expandedRowBottom = expandedRowTop + rowHeight + this.detailHeight;\n * if (expandedRowBottom > scrollTop && expandedRowIndex < start) {\n * return expandedRowIndex;\n * }\n * }\n * return start;\n * }\n * ```\n */\n adjustVirtualStart?(start: number, scrollTop: number, rowHeight: number): number;\n\n /**\n * Render a custom row, bypassing the default row rendering.\n * Use this for special row types like group headers, detail rows, or footers.\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 the plugin handled rendering (prevents default), `false`/`void` for default rendering\n *\n * @example\n * ```ts\n * renderRow(row: any, rowEl: HTMLElement, rowIndex: number): boolean | void {\n * if (row._isGroupHeader) {\n * rowEl.innerHTML = `<div class=\"group-header\">${row._groupLabel}</div>`;\n * return true; // Handled - skip default rendering\n * }\n * // Return void to let default rendering proceed\n * }\n * ```\n */\n renderRow?(row: any, rowEl: HTMLElement, rowIndex: number): boolean | void;\n\n // #endregion\n\n // #region Inter-Plugin Communication\n\n /**\n * Handle queries from other plugins.\n * This is the generic mechanism for inter-plugin communication.\n * Plugins can respond to well-known query types or define their own.\n *\n * **Prefer `handleQuery` for new plugins** - it has the same signature but\n * a clearer name. `onPluginQuery` is kept for backwards compatibility.\n *\n * @category Plugin Development\n * @param query - The query object with type and context\n * @returns Query-specific response, or undefined if not handling this query\n *\n * @example\n * ```ts\n * onPluginQuery(query: PluginQuery): unknown {\n * switch (query.type) {\n * case PLUGIN_QUERIES.CAN_MOVE_COLUMN:\n * // Prevent moving pinned columns\n * const column = query.context as ColumnConfig;\n * if (column.sticky === 'left' || column.sticky === 'right') {\n * return false;\n * }\n * break;\n * case PLUGIN_QUERIES.GET_CONTEXT_MENU_ITEMS:\n * const params = query.context as ContextMenuParams;\n * return [{ id: 'my-action', label: 'My Action', action: () => {} }];\n * }\n * }\n * ```\n * @deprecated Use `handleQuery` instead for new plugins. Will be removed in v2.\n */\n onPluginQuery?(query: PluginQuery): unknown;\n\n /**\n * Handle queries from other plugins or the grid.\n *\n * Queries are declared in `manifest.queries` and dispatched via `grid.query()`.\n * This enables type-safe, discoverable inter-plugin communication.\n *\n * @category Plugin Development\n * @param query - The query object with type and context\n * @returns Query-specific response, or undefined if not handling this query\n *\n * @example\n * ```ts\n * // In manifest\n * static override readonly manifest: PluginManifest = {\n * queries: [\n * { type: 'canMoveColumn', description: 'Check if a column can be moved' },\n * ],\n * };\n *\n * // In plugin class\n * handleQuery(query: PluginQuery): unknown {\n * if (query.type === 'canMoveColumn') {\n * const column = query.context as ColumnConfig;\n * return !column.sticky; // Can't move sticky columns\n * }\n * }\n * ```\n */\n handleQuery?(query: PluginQuery): unknown;\n\n // #endregion\n\n // #region Interaction Hooks\n\n /**\n * Handle keyboard events on the grid.\n * Called when a key is pressed while the grid or a cell has focus.\n *\n * @param event - The native KeyboardEvent\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\n *\n * @example\n * ```ts\n * onKeyDown(event: KeyboardEvent): boolean | void {\n * // Handle Ctrl+A for select all\n * if (event.ctrlKey && event.key === 'a') {\n * this.selectAllRows();\n * return true; // Prevent default browser select-all\n * }\n * }\n * ```\n */\n onKeyDown?(event: KeyboardEvent): boolean | void;\n\n /**\n * Handle cell click events.\n * Called when a data cell is clicked (not headers).\n *\n * @param event - Cell click event with row/column context\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\n *\n * @example\n * ```ts\n * onCellClick(event: CellClickEvent): boolean | void {\n * if (event.field === '_select') {\n * this.toggleRowSelection(event.rowIndex);\n * return true; // Handled\n * }\n * }\n * ```\n */\n onCellClick?(event: CellClickEvent): boolean | void;\n\n /**\n * Handle row click events.\n * Called when any part of a data row is clicked.\n * Note: This is called in addition to onCellClick, not instead of.\n *\n * @param event - Row click event with row context\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\n *\n * @example\n * ```ts\n * onRowClick(event: RowClickEvent): boolean | void {\n * if (this.config.mode === 'row') {\n * this.selectRow(event.rowIndex, event.originalEvent);\n * return true;\n * }\n * }\n * ```\n */\n onRowClick?(event: RowClickEvent): boolean | void;\n\n /**\n * Handle header click events.\n * Called when a column header is clicked. Commonly used for sorting.\n *\n * @param event - Header click event with column context\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\n *\n * @example\n * ```ts\n * onHeaderClick(event: HeaderClickEvent): boolean | void {\n * if (event.column.sortable !== false) {\n * this.toggleSort(event.field);\n * return true;\n * }\n * }\n * ```\n */\n onHeaderClick?(event: HeaderClickEvent): boolean | void;\n\n /**\n * Handle scroll events on the grid viewport.\n * Called during scrolling. Note: This may be called frequently; debounce if needed.\n *\n * @param event - Scroll event with scroll position and viewport dimensions\n *\n * @example\n * ```ts\n * onScroll(event: ScrollEvent): void {\n * // Update sticky column positions\n * this.updateStickyPositions(event.scrollLeft);\n * }\n * ```\n */\n onScroll?(event: ScrollEvent): void;\n\n /**\n * Handle cell mousedown events.\n * Used for initiating drag operations like range selection or column resize.\n *\n * @param event - Mouse event with cell context\n * @returns `true` to indicate drag started (prevents text selection), `false`/`void` otherwise\n *\n * @example\n * ```ts\n * onCellMouseDown(event: CellMouseEvent): boolean | void {\n * if (event.rowIndex !== undefined && this.config.mode === 'range') {\n * this.startDragSelection(event.rowIndex, event.colIndex);\n * return true; // Prevent text selection\n * }\n * }\n * ```\n */\n onCellMouseDown?(event: CellMouseEvent): boolean | void;\n\n /**\n * Handle cell mousemove events during drag operations.\n * Only called when a drag is in progress (after mousedown returned true).\n *\n * @param event - Mouse event with current cell context\n * @returns `true` to continue handling the drag, `false`/`void` otherwise\n *\n * @example\n * ```ts\n * onCellMouseMove(event: CellMouseEvent): boolean | void {\n * if (this.isDragging && event.rowIndex !== undefined) {\n * this.extendSelection(event.rowIndex, event.colIndex);\n * return true;\n * }\n * }\n * ```\n */\n onCellMouseMove?(event: CellMouseEvent): boolean | void;\n\n /**\n * Handle cell mouseup events to end drag operations.\n *\n * @param event - Mouse event with final cell context\n * @returns `true` if drag was finalized, `false`/`void` otherwise\n *\n * @example\n * ```ts\n * onCellMouseUp(event: CellMouseEvent): boolean | void {\n * if (this.isDragging) {\n * this.finalizeDragSelection();\n * this.isDragging = false;\n * return true;\n * }\n * }\n * ```\n */\n onCellMouseUp?(event: CellMouseEvent): boolean | void;\n\n // Note: Context menu items are provided via handleQuery('getContextMenuItems').\n // This keeps the core decoupled from the context-menu plugin specifics.\n\n // #endregion\n\n // #region Column State Hooks\n\n /**\n * Contribute plugin-specific state for a column.\n * Called by the grid when collecting column state for serialization.\n * Plugins can add their own properties to the column state.\n *\n * @param field - The field name of the column\n * @returns Partial column state with plugin-specific properties, or undefined if no state to contribute\n *\n * @example\n * ```ts\n * getColumnState(field: string): Partial<ColumnState> | undefined {\n * const filterModel = this.filterModels.get(field);\n * if (filterModel) {\n * // Uses module augmentation to add filter property to ColumnState\n * return { filter: filterModel } as Partial<ColumnState>;\n * }\n * return undefined;\n * }\n * ```\n */\n getColumnState?(field: string): Partial<ColumnState> | undefined;\n\n /**\n * Apply plugin-specific state to a column.\n * Called by the grid when restoring column state from serialized data.\n * Plugins should restore their internal state based on the provided state.\n *\n * @param field - The field name of the column\n * @param state - The column state to apply (may contain plugin-specific properties)\n *\n * @example\n * ```ts\n * applyColumnState(field: string, state: ColumnState): void {\n * // Check for filter property added via module augmentation\n * const filter = (state as any).filter;\n * if (filter) {\n * this.filterModels.set(field, filter);\n * this.applyFilter();\n * }\n * }\n * ```\n */\n applyColumnState?(field: string, state: ColumnState): void;\n\n // #endregion\n\n // #region Scroll Boundary Hooks\n\n /**\n * Report horizontal scroll boundary offsets for this plugin.\n * Plugins that obscure part of the scroll area (e.g., pinned/sticky columns)\n * should return how much space they occupy on each side.\n * The keyboard navigation uses this to ensure focused cells are fully visible.\n *\n * @param rowEl - The row element (optional, for calculating widths from rendered cells)\n * @param focusedCell - The currently focused cell element (optional, to determine if scrolling should be skipped)\n * @returns Object with left/right pixel offsets and optional skipScroll flag, or undefined if plugin has no offsets\n *\n * @example\n * ```ts\n * getHorizontalScrollOffsets(rowEl?: HTMLElement, focusedCell?: HTMLElement): { left: number; right: number; skipScroll?: boolean } | undefined {\n * // Calculate total width of left-pinned columns\n * const leftCells = rowEl?.querySelectorAll('.sticky-left') ?? [];\n * let left = 0;\n * leftCells.forEach(el => { left += (el as HTMLElement).offsetWidth; });\n * // Skip scroll if focused cell is pinned (always visible)\n * const skipScroll = focusedCell?.classList.contains('sticky-left');\n * return { left, right: 0, skipScroll };\n * }\n * ```\n */\n getHorizontalScrollOffsets?(\n rowEl?: HTMLElement,\n focusedCell?: HTMLElement,\n ): { left: number; right: number; skipScroll?: boolean } | undefined;\n\n // #endregion\n\n // #region Shell Integration Hooks\n\n /**\n * Register a tool panel for this plugin.\n * Return undefined if plugin has no tool panel.\n * The shell will create a toolbar toggle button and render the panel content\n * when the user opens the panel.\n *\n * @returns Tool panel definition, or undefined if plugin has no panel\n *\n * @example\n * ```ts\n * getToolPanel(): ToolPanelDefinition | undefined {\n * return {\n * id: 'columns',\n * title: 'Columns',\n * icon: '☰',\n * tooltip: 'Show/hide columns',\n * order: 10,\n * render: (container) => {\n * this.renderColumnList(container);\n * return () => this.cleanup();\n * },\n * };\n * }\n * ```\n */\n getToolPanel?(): ToolPanelDefinition | undefined;\n\n /**\n * Register content for the shell header center section.\n * Return undefined if plugin has no header content.\n * Examples: search input, selection summary, status indicators.\n *\n * @returns Header content definition, or undefined if plugin has no header content\n *\n * @example\n * ```ts\n * getHeaderContent(): HeaderContentDefinition | undefined {\n * return {\n * id: 'quick-filter',\n * order: 10,\n * render: (container) => {\n * const input = document.createElement('input');\n * input.type = 'text';\n * input.placeholder = 'Search...';\n * input.addEventListener('input', this.handleInput);\n * container.appendChild(input);\n * return () => input.removeEventListener('input', this.handleInput);\n * },\n * };\n * }\n * ```\n */\n getHeaderContent?(): HeaderContentDefinition | undefined;\n\n // #endregion\n}\n","/**\n * Shared types for the plugin system.\n *\n * These types are used by both the base plugin class and the grid core.\n * Centralizing them here avoids circular imports and reduces duplication.\n */\n\nimport type { ColumnConfig, GridConfig, ToolPanelDefinition, UpdateSource } from '../types';\n\n// #region Event Types\n/**\n * Keyboard modifier flags\n */\nexport interface KeyboardModifiers {\n ctrl?: boolean;\n shift?: boolean;\n alt?: boolean;\n meta?: boolean;\n}\n\n/**\n * Cell coordinates\n */\nexport interface CellCoords {\n row: number;\n col: number;\n}\n\n/**\n * Cell click event\n */\nexport interface CellClickEvent {\n rowIndex: number;\n colIndex: number;\n column: ColumnConfig;\n field: string;\n value: unknown;\n row: unknown;\n cellEl: HTMLElement;\n originalEvent: MouseEvent;\n}\n\n/**\n * Row click event\n */\nexport interface RowClickEvent {\n rowIndex: number;\n row: unknown;\n rowEl: HTMLElement;\n originalEvent: MouseEvent;\n}\n\n/**\n * Header click event\n */\nexport interface HeaderClickEvent {\n colIndex: number;\n field: string;\n column: ColumnConfig;\n headerEl: HTMLElement;\n originalEvent: MouseEvent | KeyboardEvent;\n}\n\n/**\n * Scroll event\n */\nexport interface ScrollEvent {\n scrollTop: number;\n scrollLeft: number;\n scrollHeight: number;\n scrollWidth: number;\n clientHeight: number;\n clientWidth: number;\n originalEvent?: Event;\n}\n\n/**\n * Cell mouse event (for drag operations, selection, etc.)\n * @category Plugin Development\n */\nexport interface CellMouseEvent {\n /** Event type: mousedown, mousemove, or mouseup */\n type: 'mousedown' | 'mousemove' | 'mouseup';\n /** Row index, undefined if not over a data cell */\n rowIndex?: number;\n /** Column index, undefined if not over a cell */\n colIndex?: number;\n /** Field name, undefined if not over a cell */\n field?: string;\n /** Cell value, undefined if not over a data cell */\n value?: unknown;\n /** Row data object, undefined if not over a data row */\n row?: unknown;\n /** Column configuration, undefined if not over a column */\n column?: ColumnConfig;\n /** The cell element, undefined if not over a cell */\n cellElement?: HTMLElement;\n /** The row element, undefined if not over a row */\n rowElement?: HTMLElement;\n /** Whether the event is over a header cell */\n isHeader: boolean;\n /** Cell coordinates if over a valid data cell */\n cell?: CellCoords;\n /** The original mouse event */\n originalEvent: MouseEvent;\n}\n// #endregion\n\n// #region Render Context Types\n/**\n * Context passed to the `afterCellRender` plugin hook.\n *\n * This provides efficient cell-level access without requiring DOM queries.\n * Plugins receive this context for each cell as it's rendered, enabling\n * targeted modifications instead of post-render DOM traversal.\n *\n * @category Plugin Development\n * @template TRow - The row data type\n *\n * @example\n * ```typescript\n * afterCellRender(context: AfterCellRenderContext): void {\n * if (this.isSelected(context.rowIndex, context.colIndex)) {\n * context.cellElement.classList.add('selected');\n * }\n * }\n * ```\n */\nexport interface AfterCellRenderContext<TRow = unknown> {\n /** The row data object */\n row: TRow;\n /** Zero-based row index in the visible rows array */\n rowIndex: number;\n /** The column configuration */\n column: ColumnConfig<TRow>;\n /** Zero-based column index in the visible columns array */\n colIndex: number;\n /** The cell value (row[column.field]) */\n value: unknown;\n /** The cell DOM element - can be modified by the plugin */\n cellElement: HTMLElement;\n /** The row DOM element - for context, prefer using cellElement */\n rowElement: HTMLElement;\n}\n\n/**\n * Context passed to the `afterRowRender` plugin hook.\n *\n * This provides efficient row-level access after all cells are rendered.\n * Plugins receive this context for each row, enabling row-level modifications\n * without requiring DOM queries in afterRender.\n *\n * @category Plugin Development\n * @template TRow - The row data type\n *\n * @example\n * ```typescript\n * afterRowRender(context: AfterRowRenderContext): void {\n * if (this.isRowSelected(context.rowIndex)) {\n * context.rowElement.classList.add('selected', 'row-focus');\n * }\n * }\n * ```\n */\nexport interface AfterRowRenderContext<TRow = unknown> {\n /** The row data object */\n row: TRow;\n /** Zero-based row index in the visible rows array */\n rowIndex: number;\n /** The row DOM element - can be modified by the plugin */\n rowElement: HTMLElement;\n}\n// #endregion\n\n// #region Context Menu Types\n/**\n * Context menu parameters\n */\nexport interface ContextMenuParams {\n x: number;\n y: number;\n rowIndex?: number;\n colIndex?: number;\n field?: string;\n value?: unknown;\n row?: unknown;\n column?: ColumnConfig;\n isHeader?: boolean;\n}\n\n/**\n * Context menu item (used by context-menu plugin query)\n */\nexport interface ContextMenuItem {\n id: string;\n label: string;\n icon?: string;\n disabled?: boolean;\n separator?: boolean;\n children?: ContextMenuItem[];\n action?: (params: ContextMenuParams) => void;\n}\n// #endregion\n\n// #region Plugin Query Types\n/**\n * Generic plugin query for inter-plugin communication.\n * Plugins can define their own query types as string constants\n * and respond to queries from other plugins.\n *\n * @category Plugin Development\n */\nexport interface PluginQuery<T = unknown> {\n /** Query type identifier (e.g., 'canMoveColumn', 'getContextMenuItems') */\n type: string;\n /** Query-specific context/parameters */\n context: T;\n}\n\n/**\n * Well-known plugin query types.\n * Plugins can define additional query types beyond these.\n *\n * @deprecated Use string literals with `grid.query()` instead. Query types should\n * be declared in the responding plugin's `manifest.queries` for automatic routing.\n * This constant will be removed in v2.0.\n *\n * @example\n * // Before (deprecated):\n * import { PLUGIN_QUERIES } from '@toolbox-web/grid';\n * const responses = grid.queryPlugins({ type: PLUGIN_QUERIES.CAN_MOVE_COLUMN, context: column });\n *\n * // After (recommended):\n * const responses = grid.query<boolean>('canMoveColumn', column);\n */\nexport const PLUGIN_QUERIES = {\n /** Ask if a column can be moved. Context: ColumnConfig. Response: boolean | undefined */\n CAN_MOVE_COLUMN: 'canMoveColumn',\n /** Get context menu items. Context: ContextMenuParams. Response: ContextMenuItem[] */\n GET_CONTEXT_MENU_ITEMS: 'getContextMenuItems',\n} as const;\n// #endregion\n\n// #region Cell Renderer Types\n/**\n * Cell render context for plugin cell renderers.\n * Provides full context including position and editing state.\n */\nexport interface PluginCellRenderContext {\n /** The cell value */\n value: unknown;\n /** The row data object */\n row: unknown;\n /** The row index in the data array */\n rowIndex: number;\n /** The column index */\n colIndex: number;\n /** The field name */\n field: string;\n /** The column configuration */\n column: ColumnConfig;\n /** Whether the cell is being edited */\n isEditing: boolean;\n}\n\n/**\n * Cell renderer function type for plugins.\n */\nexport type CellRenderer = (ctx: PluginCellRenderContext) => string | HTMLElement;\n\n/**\n * Header renderer function type for plugins.\n */\nexport type HeaderRenderer = (ctx: { column: ColumnConfig; colIndex: number }) => string | HTMLElement;\n\n/**\n * Cell editor interface for plugins.\n */\nexport interface CellEditor {\n create(ctx: PluginCellRenderContext, commitFn: (value: unknown) => void, cancelFn: () => void): HTMLElement;\n getValue?(element: HTMLElement): unknown;\n focus?(element: HTMLElement): void;\n}\n// #endregion\n\n// #region GridElementRef Interface\n/**\n * Minimal grid interface for plugins.\n * This avoids circular imports with the full DataGridElement.\n *\n * Member prefixes indicate accessibility:\n * - `_underscore` = protected members accessible to plugins (marked @internal in full interface)\n */\nexport interface GridElementRef {\n // =========================================================================\n // HTMLElement-like Properties (avoid casting to HTMLElement)\n // =========================================================================\n\n /** Grid element width in pixels. */\n readonly clientWidth: number;\n /** Grid element height in pixels. */\n readonly clientHeight: number;\n /** Add an event listener to the grid element. */\n addEventListener<K extends keyof HTMLElementEventMap>(\n type: K,\n listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => unknown,\n options?: boolean | AddEventListenerOptions,\n ): void;\n addEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | AddEventListenerOptions,\n ): void;\n /** Remove an event listener from the grid element. */\n removeEventListener<K extends keyof HTMLElementEventMap>(\n type: K,\n listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => unknown,\n options?: boolean | EventListenerOptions,\n ): void;\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: boolean | EventListenerOptions,\n ): void;\n /** Set an attribute on the grid element. */\n setAttribute(name: string, value: string): void;\n /** Get an attribute from the grid element. */\n getAttribute(name: string): string | null;\n /** Remove an attribute from the grid element. */\n removeAttribute(name: string): void;\n\n /**\n * The grid's host HTMLElement.\n * Use this instead of casting the grid to HTMLElement.\n * @internal Plugin API\n */\n readonly _hostElement: HTMLElement;\n\n // =========================================================================\n // Grid Data & Configuration\n // =========================================================================\n\n /** Current rows (after plugin processing like grouping, filtering). */\n rows: unknown[];\n /** Original unfiltered/unprocessed rows. */\n sourceRows: unknown[];\n /** Column configurations. */\n columns: ColumnConfig[];\n /** Visible columns only (excludes hidden). Use for rendering. @internal */\n _visibleColumns: ColumnConfig[];\n /** Full grid configuration. */\n gridConfig: GridConfig;\n /** Effective (merged) configuration - the single source of truth. */\n effectiveConfig: GridConfig;\n\n // =========================================================================\n // Row Update API\n // =========================================================================\n\n /**\n * Get the unique ID for a row.\n * Uses configured `getRowId` function or falls back to `row.id` / `row._id`.\n * @throws Error if no ID can be determined\n */\n getRowId(row: unknown): string;\n\n /**\n * Get a row by its ID.\n * O(1) lookup via internal Map.\n * @returns The row object, or undefined if not found\n */\n getRow(id: string): unknown | undefined;\n\n /**\n * Update a row by ID.\n * Mutates the row in-place and emits `cell-change` for each changed field.\n * @param id - Row identifier (from getRowId)\n * @param changes - Partial row data to merge\n * @param source - Origin of update (default: 'api')\n * @throws Error if row is not found\n */\n updateRow(id: string, changes: Record<string, unknown>, source?: UpdateSource): void;\n\n /**\n * Batch update multiple rows.\n * More efficient than multiple `updateRow()` calls - single render cycle.\n * @param updates - Array of { id, changes } objects\n * @param source - Origin of updates (default: 'api')\n * @throws Error if any row is not found\n */\n updateRows(updates: Array<{ id: string; changes: Record<string, unknown> }>, source?: UpdateSource): void;\n\n // =========================================================================\n // Focus & Lifecycle\n // =========================================================================\n\n /** Current focused row index. @internal */\n _focusRow: number;\n /** Current focused column index. @internal */\n _focusCol: number;\n /** AbortSignal that is aborted when the grid disconnects from the DOM. */\n disconnectSignal: AbortSignal;\n\n // =========================================================================\n // Rendering\n // =========================================================================\n\n /** Request a full re-render of the grid. */\n requestRender(): void;\n /** Request a full re-render and restore focus styling afterward. */\n requestRenderWithFocus(): void;\n /** Request a lightweight style update without rebuilding DOM. */\n requestAfterRender(): void;\n /** Re-render visible rows without rebuilding the row model or recalculating geometry. */\n requestVirtualRefresh(): void;\n /** Force a layout pass. */\n forceLayout(): Promise<void>;\n /** Dispatch an event from the grid element. */\n dispatchEvent(event: Event): boolean;\n\n // =========================================================================\n // Inter-plugin Communication\n // =========================================================================\n\n /**\n * Access to the plugin manager for event bus operations.\n * @internal - Use BaseGridPlugin's on/off/emitPluginEvent helpers instead.\n */\n _pluginManager?: {\n subscribe(plugin: unknown, eventType: string, callback: (detail: unknown) => void): void;\n unsubscribe(plugin: unknown, eventType: string): void;\n emitPluginEvent<T>(eventType: string, detail: T): void;\n };\n\n /**\n * Query all plugins with a generic query and collect responses.\n * Used for inter-plugin communication (e.g., asking PinnedColumnsPlugin\n * if a column can be moved).\n *\n * @example\n * const responses = grid.queryPlugins<boolean>({\n * type: PLUGIN_QUERIES.CAN_MOVE_COLUMN,\n * context: column\n * });\n * const canMove = !responses.includes(false);\n */\n queryPlugins<T>(query: PluginQuery): T[];\n\n /**\n * Query plugins with a simplified API.\n * Convenience wrapper that uses a flat signature.\n *\n * @param type - The query type (e.g., 'canMoveColumn')\n * @param context - The query context/parameters\n * @returns Array of non-undefined responses from plugins\n *\n * @example\n * const responses = grid.query<boolean>('canMoveColumn', column);\n * const canMove = !responses.includes(false);\n */\n query<T>(type: string, context?: unknown): T[];\n\n // =========================================================================\n // DOM Access\n // =========================================================================\n\n /**\n * Find the rendered DOM element for a row by its data index.\n * Returns null if the row is not currently rendered (virtualized out).\n */\n findRenderedRowElement(rowIndex: number): HTMLElement | null;\n\n // =========================================================================\n // Column Visibility API\n // =========================================================================\n\n /**\n * Get all columns including hidden ones.\n * Returns field, header, visibility status, lock state, and utility flag.\n */\n getAllColumns(): Array<{\n field: string;\n header: string;\n visible: boolean;\n lockVisible?: boolean;\n utility?: boolean;\n }>;\n\n /**\n * Set visibility for a specific column.\n * @returns true if state changed, false if column not found or already in state\n */\n setColumnVisible(field: string, visible: boolean): boolean;\n\n /**\n * Toggle visibility for a specific column.\n * @returns true if state changed, false if column not found\n */\n toggleColumnVisibility(field: string): boolean;\n\n /**\n * Check if a column is currently visible.\n */\n isColumnVisible(field: string): boolean;\n\n /**\n * Show all hidden columns.\n */\n showAllColumns(): void;\n\n // =========================================================================\n // Column Order API\n // =========================================================================\n\n /**\n * Get the current column display order as array of field names.\n */\n getColumnOrder(): string[];\n\n /**\n * Set the column display order.\n * @param order Array of field names in desired order\n */\n setColumnOrder(order: string[]): void;\n\n /**\n * Request emission of column-state-change event (debounced).\n * Call after programmatic column changes that should notify consumers.\n */\n requestStateChange?(): void;\n\n // =========================================================================\n // Tool Panel API (Shell Integration)\n // =========================================================================\n\n /**\n * Whether the tool panel sidebar is currently open.\n */\n readonly isToolPanelOpen: boolean;\n\n /**\n * The default row height in pixels.\n * For fixed heights, this is the configured or CSS-measured row height.\n * For variable heights, this is the average/estimated row height.\n * Plugins should use this instead of hardcoding row heights.\n */\n readonly defaultRowHeight: number;\n\n /**\n * Get the IDs of expanded accordion sections.\n */\n readonly expandedToolPanelSections: string[];\n\n /**\n * Open the tool panel sidebar (accordion view with all registered panels).\n */\n openToolPanel(): void;\n\n /**\n * Close the tool panel sidebar.\n */\n closeToolPanel(): void;\n\n /**\n * Toggle the tool panel sidebar open/closed.\n */\n toggleToolPanel(): void;\n\n /**\n * Toggle a specific accordion section expanded/collapsed.\n * @param sectionId - The panel ID to toggle (matches ToolPanelDefinition.id)\n */\n toggleToolPanelSection(sectionId: string): void;\n\n /**\n * Get registered tool panel definitions.\n */\n getToolPanels(): ToolPanelDefinition[];\n\n // =========================================================================\n // Variable Row Height API\n // =========================================================================\n\n /**\n * Invalidate a row's height in the position cache.\n * Call this when a plugin changes a row's height (e.g., expanding a detail panel).\n * The position cache will be updated incrementally without a full rebuild.\n *\n * @param rowIndex - Index of the row whose height changed\n * @param newHeight - Optional new height. If not provided, queries plugins for height.\n */\n invalidateRowHeight(rowIndex: number, newHeight?: number): void;\n}\n// #endregion\n"],"names":["announce","gridEl","message","el","querySelector","textContent","requestAnimationFrame","FitModeEnum","STRETCH","FIXED","DEFAULT_ANIMATION_CONFIG","mode","duration","easing","DEFAULT_FILTER_ICON","DEFAULT_GRID_ICONS","expand","collapse","sortAsc","sortDesc","sortNone","submenuArrow","dragHandle","toolPanel","filter","filterActive","print","gridPrefix","gridId","pluginName","ROW_NOT_FOUND","CELL_CLASS_ERROR","FORMAT_ERROR","formatDiagnostic","code","toLowerCase","docsUrl","throwDiagnostic","Error","warnDiagnostic","console","warn","debugDiagnostic","debug","errorDiagnostic","error","mergeColumns","programmatic","dom","length","domMap","forEach","c","existing","field","header","type","sortable","editable","resizable","width","minWidth","__viewTemplate","__editorTemplate","__headerTemplate","cRenderer","renderer","viewRenderer","existingRenderer","editor","merged","map","d","m","dRenderer","mRenderer","Object","keys","push","addPart","token","part","add","getAttribute","split","includes","setAttribute","autoSizeColumns","grid","effectiveConfig","fitMode","__didInitialAutoSize","isConnected","headerCells","Array","from","_headerRowEl","children","changed","_visibleColumns","col","i","headerCell","max","scrollWidth","rowEl","_rowPool","cell","w","__autoSized","VALID_CSS_WIDTH","resolveWidth","test","updateTemplate","_gridTemplate","min","join","trim","style","setProperty","EXPR_RE","EMPTY_SENTINEL","SAFE_EXPR","FORBIDDEN","DANGEROUS_TAGS","Set","DANGEROUS_ATTR_PATTERN","URL_ATTRS","DANGEROUS_URL_PROTOCOL","sanitizeHTML","html","indexOf","template","document","createElement","innerHTML","root","toRemove","elements","querySelectorAll","tagName","has","namespaceURI","attributes","some","attr","name","attrsToRemove","attrName","value","removeAttribute","remove","sanitizeNode","content","evalTemplateString","raw","ctx","parts","evaluated","replace","_m","expr","res","REFLECTIVE_RE","String","startsWith","key","slice","v","row","dotChain","match","out","Function","fn","str","evalSingle","result","finalStr","s","RegExp","allEmpty","every","p","finalCellScrub","n","childNodes","nodeType","Node","TEXT_NODE","compileTemplate","forceBlank","__blocked","defaultComparator","a","b","builtInSort","rows","sortState","columns","find","comparator","sortComparator","direction","sort","rA","rB","finalizeSortResult","sortedRows","dir","_rows","__rowRenderEpoch","r","__epoch","renderHeader","refreshVirtualWindow","dispatchEvent","CustomEvent","detail","requestStateChange","toggleSort","_sortState","applySort","__originalOrder","headers","h","_columns","sortHandler","then","isColumnSortable","isColumnResizable","createSortIndicator","icon","active","icons","element","HTMLElement","appendChild","cloneNode","setIcon","createResizeHandle","colIndex","handle","className","addEventListener","e","stopPropagation","preventDefault","_resizeController","start","resetColumn","setupSortHandlers","classList","tabIndex","isResizing","_dispatchHeaderClick","findHeaderRow","headerRow","headerValue","sortDirection","headerRenderer","column","cellEl","renderSortIcon","renderFilterButton","output","container","firstChild","appendRendererOutput","headerLabelRenderer","span","inferColumns","provided","sample","k","Date","isNaN","parse","charAt","toUpperCase","typeMap","RenderPhase","RenderScheduler","pendingPhase","rafHandle","readyPromise","readyResolve","initialReadyResolver","initialReadyFired","constructor","this","requestPhase","phase","_source","ensureReadyPromise","flush","whenReady","Promise","resolve","setInitialReadyResolver","resolver","cancel","cancelAnimationFrame","isPending","_schedulerIsConnected","_schedulerMergeConfig","_schedulerProcessRows","_schedulerProcessColumns","_schedulerUpdateTemplate","_schedulerRenderHeader","_schedulerAfterRender","ConfigManager","gridConfig","lightDomColumnsCache","originalColumnNodes","originalConfig","sourcesChanged","changeListeners","lightDomObserver","stateChangeTimeoutId","lightDomDebounceTimer","initialColumnState","lightDomTitle","original","effective","markSourcesChanged","setGridConfig","config","getGridConfig","setColumns","getColumns","setFitMode","getFitMode","merge","hasColumns","base","collectAllSources","freeze","cloneConfig","applyPostMergeOperations","clone","shell","toolPanels","headerContents","applyTypeDefaultsToColumns","rowHeight","_virtualization","_applyAnimationConfig","typeDefaults","typeDefault","format","editorParams","configColumns","isArray","domCols","sourceRows","__originalWidth","__compiledView","__compiledEditor","mergeShellConfig","columnState","shellLightDomTitle","_shellState","title","lightDomHeaderContent","lightDomContent","hasToolButtonsContainer","toolPanelsMap","size","panels","values","order","headerContentsMap","contents","toolbarContentsMap","toolbarContents","apiContents","originalConfigContents","configIds","id","mergedContents","collectState","plugins","sortStates","getSortState","index","state","visible","hidden","internalCol","__renderedWidth","parseFloat","get","plugin","getColumnState","pluginState","assign","applyState","allColumns","stateMap","Map","updatedColumns","updated","Infinity","sortedByPriority","priority","primarySort","applyColumnState","colState","resetState","sortMap","set","clearTimeout","setTimeout","_emit","setColumnVisible","allCols","lockVisible","visibleColumns","_clearRowPool","_setup","toggleColumnVisibility","isColumnVisible","showAllColumns","getAllColumns","utility","meta","getColumnOrder","setColumnOrder","columnMap","reordered","delete","_requestSchedulerPhase","VIRTUALIZATION","parseLightDomColumns","host","rawType","hasAttribute","widthAttr","numericWidth","minWidthAttr","numericMinWidth","editorName","rendererName","__editorName","__rendererName","optionsAttr","options","item","label","viewTpl","editorTpl","headerTpl","DataGridElementClassRef","globalThis","DataGridElement","adapters","getAdapters","viewTarget","viewAdapter","canHandle","createRenderer","editorTarget","editorAdapter","createEditor","clearLightDomCache","lightDomHandlers","registerLightDomHandler","callback","unregisterLightDomHandler","observeLightDOM","disconnect","pendingCallbacks","processPendingCallbacks","handler","clear","MutationObserver","mutations","mutation","node","addedNodes","ELEMENT_NODE","target","observe","childList","subtree","attributeFilter","onChange","notifyChange","cb","dispose","isDevelopment","window","location","hostname","process","env","NODE_ENV","booleanCellHTML","formatDateValue","getTime","toLocaleDateString","getRowIndexFromCell","parseInt","closest","parent","parentElement","clearCellFocus","isRTL","getComputedStyle","dirAttr","getDirection","resolveRenderer","columnRenderer","adapter","__frameworkAdapter","getTypeDefault","appDefault","resolveFormat","FOCUSABLE_EDITOR_SELECTOR","hasEditingCells","__editingCellCount","clearEditingState","cellTemplate","rowTemplate","createCellFromTemplate","firstElementChild","createRowFromTemplate","invalidateCellCache","__cellDisplayCache","__cellCacheEpoch","__hasSpecialColumns","fastPatchRow","rowData","rowIndex","colsLen","childLen","minLen","focusRow","_focusRow","focusCol","_focusCol","hasCellHook","_hasAfterCellRenderHook","hasSpecialCols","externalView","cellClass","rowIndexStr","renderInlineRow","isEditing","contains","shouldHaveFocus","toggle","cellClassFn","prevClasses","cls","cellClasses","validClasses","cellRenderer","renderedValue","produced","releaseCell","_afterCellRender","cellElement","rowElement","rawTpl","displayStr","formatFn","formatted","fragment","createDocumentFragment","compiled","tplHolder","needsSanitization","spec","placeholder","context","mount","queueMicrotask","bubbles","composed","blocked","dynamicClassStr","handleRowClick","_dispatchRowClick","Number","_dispatchCellClick","focusChanged","_bodyEl","matches","focus","preventScroll","ensureCellVisible","enabled","viewportEl","scrollEl","visibleHeight","clientHeight","y","scrollTop","_activeEditRows","_isGridEditMode","vStart","vEnd","end","scrollArea","forceHorizontalScroll","forceScrollLeft","scrollLeft","forceScrollRight","clientWidth","offsets","_getHorizontalScrollOffsets","left","right","skipScroll","cellRect","getBoundingClientRect","scrollAreaRect","cellLeft","cellRight","visibleLeft","visibleRight","focusTarget","activeElement","dragState","WeakMap","handleCellMousedown","getColIndexFromCell","buildCellMouseEvent","renderRoot","path","composedPath","elAtPoint","elementFromPoint","clientX","clientY","headerEl","originalEvent","isHeader","setupRootEventDelegation","gridElement","signal","_dispatchKeyDown","maxRow","maxCol","editing","colType","isFormField","tag","isContentEditable","shiftKey","commitActiveRowEdit","Math","rtl","ctrlKey","metaKey","activateEvent","cancelable","trigger","legacyEvent","defaultPrevented","handleGridKeyDown","event","_dispatchCellMouseDown","handleMouseDown","_dispatchCellMouseMove","handleMouseMove","_dispatchCellMouseUp","handleMouseUp","resolveFeatures","FocusManager","externalFocusContainers","externalFocusCleanups","focusCell","colIdx","findIndex","focusedCell","scrollToRow","virt","totalRows","idx","align","behavior","rowTop","rowH","pc","positionCache","variableHeights","offset","height","viewportH","currentTop","rowBottom","viewBottom","scrollTo","top","scrollToRowById","rowId","entry","_getRowEntry","registerExternalFocusContainer","ac","AbortController","dataset","hasFocus","newFocus","relatedTarget","containsFocus","abort","unregisterExternalFocusContainer","cleanup","isInExternalFocusContainer","destroy","hasIdleCallback","requestIdleCallback","cancelIdle","cancelIdleCallback","createLoadingContent","wrapper","spinner","createDefaultSpinner","createResizeController","resizeState","pendingRaf","prevCursor","prevUserSelect","onMove","delta","startX","startWidth","__userResized","justFinishedResize","onUp","hadResize","removeEventListener","documentElement","cursor","body","userSelect","cells","rendered","round","freezeFlexibleColumns","colWidth","ANIMATION_ATTR","DURATION_PROPS","change","insert","DEFAULT_DURATIONS","getAnimationDuration","animationType","prop","computed","getPropertyValue","parsed","trimmed","endsWith","parseDuration","animateRow","findRenderedRowElement","onComplete","offsetWidth","animateRowElement","tryResolveRowId","getRowId","_id","resolveRowIdOrThrow","RowManager","resolveRowId","getRow","getRowEntry","updateRow","changes","source","changedFields","newValue","entries","oldValue","_emitDataChange","updateRows","updates","anyChanged","insertRow","animate","newRows","splice","_rebuildRowIdMap","_emitPluginEvent","removeRow","currentIdx","srcIdx","newSource","origIdx","newOrig","applyTransaction","transaction","added","removed","removedIds","update","hasStructuralChange","hasUpdates","pendingTransaction","pendingTransactionResolvers","transactionRafId","applyTransactionAsync","batch","resolvers","attrs","div","button","gridContentTemplate","cloneGridContent","buildGridDOM","hasShell","shellHeader","shellBody","contentWrapper","iconToString","outerHTML","shouldRenderShellHeader","parseLightDomShell","display","parseLightDomToolButtons","rendererFactory","toolButtonsContainer","lightDomToolbarContentIds","contentDef","render","parseLightDomToolPanels","panelEl","tooltip","adapterRenderer","existingPanel","panelCleanups","panel","lightDomToolPanelIds","renderCustomToolbarContents","configContents","stateContents","allContents","toolbarContentCleanups","slot","renderHeaderContent","hasLightDomContent","lightDomContentMoved","hasPluginContent","contentArea","sortedContents","existingCleanup","headerContentCleanups","renderPanelContent","isPanelOpen","panelId","isExpanded","expandedSections","section","updateToolbarActiveStates","panelToggle","updatePanelState","prepareForRerender","updateAccordionSectionState","sectionId","expanded","buildGridDOMIntoElement","shellConfig","runtimeState","lightDomElements","lightDomSelectors","selector","replaceChildren","toolPanelIcon","expandIcon","sortedPanels","headerOptions","hasPanels","configButtons","hasElement","hasRender","apiButtons","bodyOptions","position","role","titleEl","toolbar","btn","toggleBtn","buildShellHeader","hasPanel","isSinglePanel","gridContent","class","resizeHandlePosition","panelContent","accordion","headerBtn","iconSpan","titleSpan","chevronSpan","buildShellBody","STYLE_ELEMENT_ID","baseStyles","pluginStylesMap","updateStyleElement","styleEl","getElementById","head","getStyleElement","pluginStyles","async","injectStyles","inlineStyles","gridCssText","stylesheet","styleSheets","cssText","cssRules","rule","err","extractGridCssFromDocument","href","resetTouchState","startY","lastY","lastX","lastTime","locked","cancelMomentum","momentumRaf","handleTouchEnd","abs","velocityY","velocityX","friction","minVelocity","scrollY","scrollX","fauxScrollbar","startMomentumScroll","setupTouchScrollListeners","gridContentEl","pointerType","activePointerId","pointerId","setPointerCapture","performance","now","handleTouchStart","passive","shouldPrevent","incrY","incrX","dt","totalDeltaY","totalDeltaX","isVerticalGesture","scrollHeight","hasVerticalScroll","hasHorizontalScroll","handleTouchMove","KNOWN_COLUMN_PROPERTIES","property","level","description","isUsed","KNOWN_CONFIG_PROPERTIES","getImportHint","capitalize","hasPlugin","getRowCacheKey","__rowCacheKey","getCachedHeight","cache","byKey","byRef","updateRowHeight","newHeight","heightDiff","measured","getRowIndexAtOffset","targetOffset","low","high","mid","floor","entryEnd","measureRenderedRowHeights","rowElements","heightCache","getPluginHeight","hasChanges","pluginHeight","currentEntry","measuredHeight","offsetHeight","setCachedHeight","measuredCount","count","countMeasuredRows","averageHeight","defaultHeight","totalHeight","calculateAverageHeight","VirtualizationManager","initialState","bypassThreshold","totalHeightEl","cachedViewportHeight","cachedFauxHeight","cachedScrollAreaHeight","scrollAreaEl","updateCachedGeometry","calculateTotalSpacerHeight","forceRead","fauxScrollHeight","viewportHeight","scrollAreaHeight","_hostElement","viewportHeightDiff","hScrollbarPadding","rowContentHeight","pluginExtraHeight","last","getTotalHeight","_getPluginExtraHeight","initializePositionCache","estimatedHeight","rowHeightFn","rowIdFn","rebuildPositionCache","_getPluginRowHeight","stats","totalMeasured","computeAverageExcludingPluginRows","invalidateRowHeight","newTotalHeight","bodyEl","force","skipAfterRender","_renderVisibleRows","_afterPluginRender","transform","_updateAriaCounts","iterations","maxIterations","extraHeightBefore","_getPluginExtraHeightBefore","adjustedStart","pluginAdjustedStart","_adjustPluginVirtualStart","targetHeight","accumulatedHeight","minRows","ceil","prevStart","prevEnd","startRowOffset","subPixelOffset","PluginManager","getPlugins","pluginMap","cellRenderers","headerRenderers","cellEditors","_hasAfterCellRender","_hasAfterRowRender","eventListeners","queryHandlers","static","WeakSet","attachAll","attach","loadedPlugins","dependencies","dep","requiredPlugin","required","reason","reasonText","importHint","validatePluginDependencies","registerQueryHandlers","warnDeprecatedHooks","invalidateHookCaches","existingPlugin","onPluginAttached","manifest","queries","queryDef","handlers","PluginClass","deprecationWarned","hasOldHooks","getExtraHeight","getExtraHeightBefore","hasNewHook","getRowHeight","unregisterQueryHandlers","queryType","detachAll","otherPlugin","onPluginDetached","unsubscribeAll","detach","getPlugin","getPluginByName","aliases","getAll","getRegisteredPluginNames","getCellRenderer","getHeaderRenderer","getCellEditor","getPluginStyles","styles","processRows","processColumns","beforeRender","afterRender","afterCellRender","hasAfterCellRenderHook","afterRowRender","hasAfterRowRenderHook","onScrollRender","total","hasExtraHeight","beforeRowIndex","hasRowHeightPlugin","adjustVirtualStart","pluginStart","renderRow","queryPlugins","query","responses","response","handleQuery","onPluginQuery","subscribe","eventType","listeners","unsubscribe","emitPluginEvent","onKeyDown","onCellClick","onRowClick","onHeaderClick","onScroll","onCellMouseDown","onCellMouseMove","onCellMouseUp","getHorizontalScrollOffsets","getToolPanels","getToolPanel","getHeaderContents","getHeaderContent","__GRID_VERSION__","registerAdapter","clearAdapters","observedAttributes","initialized","configManager","connected","pendingUpdate","pendingUpdateFlags","scheduler","scrollRaf","pendingScrollTop","hasScrollPlugins","needsRowHeightMeasurement","scrollMeasureTimeout","renderRowHook","touchState","eventAbortController","resizeObserver","rowHeightObserver","idleCallbackHandle","pooledScrollEvent","pluginManager","lastPluginsArray","lastFeaturesConfig","virtManager","focusManager","rowManager","_pluginManager","eventListenersAdded","scrollAbortController","shellState","apiToolPanelIds","apiHeaderContentIds","createShellState","shellController","resizeCleanup","clickOutsideCleanup","loading","loadingRows","loadingCells","loadingOverlayEl","rowIdMap","baseColumns","visibleColumnsCache","_restoreFocusAfterRender","__lightDomColumnsCache","__originalColumnNodes","__rowsBodyEl","queueUpdate","processConfig","wasLoading","updateLoadingOverlay","setRowLoading","updateRowLoadingState","setCellLoading","cellFields","updateCellLoadingState","isRowLoading","isCellLoading","clearAllLoading","fields","disconnectSignal","super","controller","isInitialized","setInitialized","activePanel","openToolPanel","firstPanel","shadow","_renderRoot","_accordionIcons","sections","closeToolPanel","onClose","toggleToolPanel","toggleToolPanelSection","otherId","otherPanel","contentEl","renderAccordionSectionContent","registerToolPanel","refreshShellHeader","unregisterToolPanel","registerHeaderContent","unregisterHeaderContent","contentId","onDestroy","getToolbarContents","registerToolbarContent","unregisterToolbarContent","createShellController","requestRender","ROWS","requestColumnsRender","COLUMNS","requestRenderWithFocus","requestAfterRender","STYLE","requestVirtualRefresh","initializePlugins","pluginsConfig","explicitPlugins","features","featurePlugins","allPlugins","injectAllPluginStyles","hasNewStyles","addPluginStyles","updatePluginConfigs","newPlugins","newFeatures","featuresChanged","isLightDom","isApiRegistered","configureVariableHeights","collectPluginShellContributions","hadScrollPlugins","gridRoot","setupScrollListeners","destroyPlugins","pluginPanels","pluginContents","getToolPanelRendererFactory","instanceAdapter","createToolPanelRenderer","connectedCallback","version","instanceCounter","parseLightDom","afterConnect","setupLightDomHandlers","timeout","didTimeout","timeRemaining","disconnectedCallback","cleanupShellState","rowHeightObserverSetup","customStyleSheets","attributeChangedCallback","isLoading","JSON","defaultOpen","setup","updateAriaSelection","FULL","userRowHeight","measureRowHeight","firstRow","cssRowHeight","resolveCssRowHeight","maxCellHeight","rowRect","currentHeight","cssChanged","minHeight","measureRowHeightForPlugins","scrollSignal","rowsEl","currentScrollTop","rawStart","evenAlignedStart","onScrollBatched","scrollEvent","scrollAreaForWheel","isHorizontal","deltaX","deltaY","draggable","ResizeObserver","setupRowHeightObserver","listener","on","emit","eventName","rowCount","sourceRowCount","rowIdx","isActiveRow","flushPendingUpdates","flags","applyGridConfigUpdate","applyRowsUpdate","applyColumnsUpdate","applyFitModeUpdate","hadShell","hadToolPanel","accordionSectionsBefore","prevPosition","nowNeedsShell","nowHasToolPanels","toolPanelCount","newPosition","updateShellHeaderInPlace","insertBefore","sourceColumns","visibleCols","hiddenCols","processedColumns","processedFields","mergeColumnsPreservingOrder","processedVisible","processedMap","sourceFields","pluginAdded","srcCol","processed","rebuildRowModel","reapplyCoreSort","processedRows","applyAnimationConfig","animation","animationMode","epoch","needed","colLen","headerRowCount","__cachedHeaderRowCount","parentNode","hasRenderRowPlugins","__hasRenderRowPlugins","hasRowHook","_hasAfterRowRenderHook","varHeightFn","__rowDataRef","rowEpoch","prevRef","cellCount","lastElementChild","structureValid","dataRefChanged","isGridEditMode","needsExternalRebuild","hasEditing","isActivelyEditedRow","__isCustomRow","isChanged","changedRowIdSet","_changedRowIdSet","rowClassFn","rowClass","newClasses","removeProperty","_afterRowRender","renderVisibleRows","ariaState","colCount","ariaLabel","ariaDescribedBy","rowsBodyEl","prevRowCount","updateAriaCounts","columnProps","configProps","missingPlugins","addError","isConfigProperty","def","errors","fieldList","validatePluginProperties","warnings","configRules","pluginConfig","check","severity","warning","validatePluginConfigRules","pluginNames","warned","incompatibleWith","incompatibility","validatePluginIncompatibilities","updateAriaLabels","explicitLabel","gridAriaLabel","getEffectiveAriaLabel","gridAriaDescribedBy","overlayEl","overlay","createLoadingOverlay","loadingRenderer","showLoadingOverlay","setRowLoadingState","setCellLoadingState","gridTemplateColumns","poolIndex","cellClickEvent","handled","rowClickEvent","headerClickEvent","ready","forceLayout","getConfig","animateRows","rowIndices","all","results","Boolean","animateRowById","suspendProcessing","resetColumnState","isToolPanelOpen","defaultRowHeight","expandedToolPanelSections","pendingShellRefresh","afterShellRefresh","registerStyles","css","sheet","CSSStyleSheet","replaceSync","updateAdoptedStyleSheets","unregisterStyles","getRegisteredStyles","customSheets","existingSheets","adoptedStyleSheets","replaceShellHeaderElement","newHeaderHtml","hasTitle","iconStr","hasCustomContent","showSeparator","toolbarHtml","isOpen","text","renderShellHeader","temp","newHeader","replaceWith","setupShellListeners","handleShellChange","hadTitle","hadToolButtons","hasToolButtons","handleColumnChange","refreshColumns","callbacks","onPanelToggle","onSectionToggle","setupShellEventListeners","onResize","maxWidth","onMouseMove","newWidth","onMouseUp","transition","finalWidth","onMouseDown","setupToolPanelResize","closeOnClickOutside","setupClickOutsideDismiss","customElements","define","GridClasses","ROOT","HEADER","HEADER_ROW","HEADER_CELL","ROWS_VIEWPORT","ROWS_SPACER","ROWS_CONTAINER","DATA_ROW","GROUP_ROW","DATA_CELL","SELECTED","FOCUSED","EDITING","EXPANDED","COLLAPSED","DRAGGING","RESIZING","SORTABLE","SORTED_ASC","SORTED_DESC","HIDDEN","STICKY_LEFT","STICKY_RIGHT","PINNED_TOP","PINNED_BOTTOM","TREE_TOGGLE","TREE_INDENT","GROUP_TOGGLE","GROUP_LABEL","GROUP_COUNT","RANGE_SELECTION","SELECTION_OVERLAY","GridDataAttrs","ROW_INDEX","COL_INDEX","FIELD","GROUP_KEY","TREE_LEVEL","STICKY","GridSelectors","ROW_BY_INDEX","CELL_BY_FIELD","CELL_AT","SELECTED_ROWS","EDITING_CELL","builtInAggregators","sum","reduce","acc","avg","first","customAggregators","aggregatorRegistry","register","unregister","ref","run","list","builtInValueAggregators","vals","getValueAggregator","aggFunc","registerAggregator","bind","unregisterAggregator","getAggregator","runAggregator","listAggregators","userConfig","abortController","defaultConfig","emitCancelable","off","gridIcons","userIcons","isAnimationEnabled","animationDuration","durationStr","resolveIcon","iconKey","pluginOverride","codeOrMessage","CELL_CHANGE","CELL_COMMIT","ROW_COMMIT","EDIT_OPEN","EDIT_CLOSE","CHANGED_ROWS_RESET","MOUNT_EXTERNAL_VIEW","MOUNT_EXTERNAL_EDITOR","SORT_CHANGE","COLUMN_RESIZE","ACTIVATE_CELL","CELL_ACTIVATE","COLUMN_STATE_CHANGE","DATA_CHANGE","COLOR_BG","COLOR_FG","COLOR_FG_MUTED","COLOR_BORDER","COLOR_ACCENT","COLOR_HEADER_BG","COLOR_HEADER_FG","COLOR_SELECTION","COLOR_ROW_HOVER","COLOR_ROW_ALT","ROW_HEIGHT","HEADER_HEIGHT","CELL_PADDING","FONT_FAMILY","FONT_SIZE","BORDER_RADIUS","FOCUS_OUTLINE","CAN_MOVE_COLUMN","GET_CONTEXT_MENU_ITEMS","SELECTION_CHANGE","TREE_EXPAND","FILTER_CHANGE","SORT_MODEL_CHANGE","EXPORT_START","EXPORT_COMPLETE","CLIPBOARD_COPY","CLIPBOARD_PASTE","CONTEXT_MENU_OPEN","CONTEXT_MENU_CLOSE","HISTORY_CHANGE","SERVER_LOADING","SERVER_ERROR","COLUMN_VISIBILITY_CHANGE","COLUMN_REORDER","DETAIL_EXPAND","GROUP_EXPAND","parentOrAwait","awaitUpgrade","shouldAwait","whenDefined"],"mappings":"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"}
|