@toolbox-web/grid 1.13.0 → 1.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all.js +1850 -1742
- package/all.js.map +1 -1
- package/index.js +159 -139
- package/index.js.map +1 -1
- package/lib/core/grid.d.ts.map +1 -1
- package/lib/core/internal/loading.d.ts +2 -0
- package/lib/core/internal/loading.d.ts.map +1 -1
- package/lib/core/internal/row-animation.d.ts.map +1 -1
- package/lib/core/internal/rows.d.ts.map +1 -1
- package/lib/core/plugin/types.d.ts +1 -1
- package/lib/core/plugin/types.d.ts.map +1 -1
- package/lib/core/types.d.ts +44 -1
- package/lib/core/types.d.ts.map +1 -1
- package/lib/plugins/clipboard/ClipboardPlugin.d.ts +69 -8
- package/lib/plugins/clipboard/ClipboardPlugin.d.ts.map +1 -1
- package/lib/plugins/clipboard/index.d.ts +1 -1
- package/lib/plugins/clipboard/index.d.ts.map +1 -1
- package/lib/plugins/clipboard/index.js +257 -192
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/clipboard/types.d.ts +31 -0
- package/lib/plugins/clipboard/types.d.ts.map +1 -1
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/ContextMenuPlugin.d.ts +8 -0
- package/lib/plugins/context-menu/ContextMenuPlugin.d.ts.map +1 -1
- package/lib/plugins/context-menu/index.js +75 -60
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/context-menu/types.d.ts +7 -0
- package/lib/plugins/context-menu/types.d.ts.map +1 -1
- package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
- package/lib/plugins/editing/editors.d.ts +2 -2
- package/lib/plugins/editing/editors.d.ts.map +1 -1
- package/lib/plugins/editing/index.js +420 -381
- package/lib/plugins/editing/index.js.map +1 -1
- package/lib/plugins/editing/types.d.ts +6 -23
- package/lib/plugins/editing/types.d.ts.map +1 -1
- package/lib/plugins/export/ExportPlugin.d.ts.map +1 -1
- package/lib/plugins/export/index.js +75 -66
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/FilteringPlugin.d.ts +2 -0
- package/lib/plugins/filtering/FilteringPlugin.d.ts.map +1 -1
- package/lib/plugins/filtering/filter-model.d.ts.map +1 -1
- package/lib/plugins/filtering/index.d.ts +1 -1
- package/lib/plugins/filtering/index.d.ts.map +1 -1
- package/lib/plugins/filtering/index.js +319 -290
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-rows/PinnedRowsPlugin.d.ts +1 -0
- package/lib/plugins/pinned-rows/PinnedRowsPlugin.d.ts.map +1 -1
- package/lib/plugins/pinned-rows/index.js +118 -87
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pinned-rows/pinned-rows.d.ts +2 -1
- package/lib/plugins/pinned-rows/pinned-rows.d.ts.map +1 -1
- package/lib/plugins/pinned-rows/types.d.ts +23 -2
- package/lib/plugins/pinned-rows/types.d.ts.map +1 -1
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/print/index.js.map +1 -1
- package/lib/plugins/reorder/index.js.map +1 -1
- package/lib/plugins/responsive/index.js.map +1 -1
- package/lib/plugins/row-reorder/index.js.map +1 -1
- package/lib/plugins/selection/SelectionPlugin.d.ts.map +1 -1
- package/lib/plugins/selection/index.js +94 -86
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/shared/data-collection.d.ts +33 -0
- package/lib/plugins/shared/data-collection.d.ts.map +1 -0
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/index.js.map +1 -1
- package/package.json +1 -1
- package/umd/grid.all.umd.js +31 -31
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +4 -4
- package/umd/grid.umd.js.map +1 -1
- package/umd/plugins/clipboard.umd.js +5 -5
- package/umd/plugins/clipboard.umd.js.map +1 -1
- package/umd/plugins/context-menu.umd.js +1 -1
- package/umd/plugins/context-menu.umd.js.map +1 -1
- package/umd/plugins/editing.umd.js +1 -1
- package/umd/plugins/editing.umd.js.map +1 -1
- package/umd/plugins/export.umd.js +7 -7
- 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/pinned-rows.umd.js +1 -1
- package/umd/plugins/pinned-rows.umd.js.map +1 -1
- package/umd/plugins/selection.umd.js +2 -2
- package/umd/plugins/selection.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/columns.ts","../../../../libs/grid/src/lib/core/internal/inference.ts","../../../../libs/grid/src/lib/core/internal/sanitize.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/sorting.ts","../../../../libs/grid/src/lib/core/internal/header.ts","../../../../libs/grid/src/lib/core/internal/idle-scheduler.ts","../../../../libs/grid/src/lib/core/internal/loading.ts","../../../../libs/grid/src/lib/core/internal/render-scheduler.ts","../../../../libs/grid/src/lib/core/internal/resize.ts","../../../../libs/grid/src/lib/core/internal/row-animation.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/plugin/plugin-manager.ts","../../../../libs/grid/src/lib/core/styles/index.ts","../../../../libs/grid/src/lib/core/grid.ts","../../../../libs/grid/src/lib/core/plugin/types.ts","../../../../libs/grid/src/lib/core/plugin/base-plugin.ts","../../../../libs/grid/src/lib/core/constants.ts","../../../../libs/grid/src/public.ts","../../../../libs/grid/src/lib/core/internal/aggregators.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","import 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\n * const grid = document.querySelector('tbw-grid') as DataGridElement<Employee>;\n * grid.rows = employees;\n * grid.addEventListener('cell-click', (e) => console.log(e.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 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 /** 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 * @example\n * ```typescript\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 * Prefer `getPlugin(PluginClass)` for type safety.\n */\n getPluginByName?(name: string): 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// #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 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 /** 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 effectiveConfig?: GridConfig<T>;\n findHeaderRow?: () => HTMLElement;\n refreshVirtualWindow: (full: boolean, skipAfterRender?: boolean) => boolean;\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 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. Implemented in grid.ts */\n animateRow?: (rowIndex: number, type: RowAnimationType) => void;\n /** Animate multiple rows. Implemented in grid.ts */\n animateRows?: (rowIndices: number[], type: RowAnimationType) => void;\n /** Animate a row by its ID. Implemented in grid.ts */\n animateRowById?: (rowId: string, type: RowAnimationType) => 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, colIndex: number, 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// #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 /** Optional formatter */\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 */\n cellClass?: (value: unknown, row: TRow, column: ColumnConfig<TRow>) => 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 /** Accept the edit; triggers change tracking + rerender. */\n commit: (newValue: TValue) => void;\n /** Abort edit without persisting changes. */\n cancel: () => 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 */\n createRenderer<TRow = unknown, TValue = unknown>(element: HTMLElement): ColumnViewRenderer<TRow, TValue>;\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// #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 * groupBy: ['department', '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\n * const selection = grid.getPlugin(SelectionPlugin);\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// #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 */\n rowClass?: (row: TRow) => 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 * 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 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.addEventListener('cell-change', (e) => {\n * const { source, field, newValue } = e.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(e.detail.rowId, {\n * total: newValue * e.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.addEventListener('cell-change', (e: CustomEvent<CellChangeDetail>) => {\n * const { row, rowId, field, oldValue, newValue, source } = e.detail;\n *\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\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 update = () => span.textContent = `${grid.rows.length} rows`;\n * grid.addEventListener('data-change', update);\n *\n * return () => {\n * grid.removeEventListener('data-change', update);\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.addEventListener('cell-click', (e: CustomEvent<CellClickDetail>) => {\n * const { row, field, value, rowIndex, colIndex } = e.detail;\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 /** 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.addEventListener('row-click', (e: CustomEvent<RowClickDetail>) => {\n * const { row, rowIndex, rowEl } = e.detail;\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.addEventListener('sort-change', (e: CustomEvent<SortChangeDetail>) => {\n * const { field, direction } = e.detail;\n *\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.addEventListener('column-resize', (e: CustomEvent<ColumnResizeDetail>) => {\n * const { field, width } = e.detail;\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.addEventListener('cell-activate', (e: CustomEvent<CellActivateDetail>) => {\n * const { row, field, value, trigger, cellEl } = e.detail;\n *\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 * openNotesEditor(row, cellEl);\n * e.preventDefault(); // Prevent default editing\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.\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.addEventListener('mount-external-view', (e: CustomEvent<ExternalMountViewDetail>) => {\n * const { placeholder, spec, context } = e.detail;\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.addEventListener('mount-external-editor', (e: CustomEvent<ExternalMountEditorDetail>) => {\n * const { placeholder, spec, context } = e.detail;\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 * Use this interface for strongly typed event handling.\n *\n * @example\n * ```typescript\n * // Type-safe event listener\n * function handleEvent<K extends keyof DataGridEventMap>(\n * grid: DataGridElement,\n * event: K,\n * handler: (detail: DataGridEventMap[K]) => void,\n * ): void {\n * grid.addEventListener(event, (e: CustomEvent) => handler(e.detail));\n * }\n *\n * handleEvent(grid, 'cell-click', (detail) => {\n * console.log(detail.field); // Type-safe access\n * });\n * ```\n *\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 'cell-click': CellClickDetail<TRow>;\n 'row-click': RowClickDetail<TRow>;\n 'cell-activate': CellActivateDetail<TRow>;\n 'cell-change': CellChangeDetail<TRow>;\n 'mount-external-view': ExternalMountViewDetail<TRow>;\n 'mount-external-editor': ExternalMountEditorDetail<TRow>;\n 'sort-change': SortChangeDetail;\n 'column-resize': ColumnResizeDetail;\n /** @deprecated Use 'cell-activate' instead */\n 'activate-cell': ActivateCellDetail;\n 'column-state-change': GridColumnState;\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 * Use this type when you need to cast or declare event handler parameters.\n *\n * @example\n * ```typescript\n * // Strongly typed event handler\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 *\n * grid.addEventListener('cell-click', onCellClick);\n * ```\n *\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// Internal code now reuses the public ColumnEditorContext; provide alias for backward compatibility\nexport type EditorContext<T = unknown> = ColumnEditorContext<T, unknown>;\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","import type { ColumnConfig, ColumnInternal, ElementWithPart, InternalGrid, PrimitiveColumnType } from '../types';\nimport { FitModeEnum } from '../types';\n\n// #region Light DOM Parsing\n/** Global DataGridElement class (may or may not be registered) */\ninterface DataGridElementClass {\n getAdapters?: () => Array<{\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: InternalGrid): 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 as unknown as HTMLElement).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 */\nexport function updateTemplate(grid: InternalGrid): 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) return `${c.width}px`;\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) => (c.width ? `${c.width}px` : 'max-content'))\n .join(' ');\n }\n (grid as unknown as HTMLElement).style.setProperty('--tbw-column-template', grid._gridTemplate);\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","// 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 * 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 HeaderContentDefinition,\n ToolbarContentDefinition,\n ToolPanelDefinition,\n} from '../types';\nimport { mergeColumns, parseLightDomColumns } from './columns';\nimport { inferColumns } from './inference';\nimport { compileTemplate } from './sanitize';\n\n/** Debounce timeout for state change events */\nconst STATE_CHANGE_DEBOUNCE_MS = 100;\n\n/**\n * Callbacks for ConfigManager to notify the grid of changes.\n */\nexport interface ConfigManagerCallbacks<T> {\n /** Get current rows for column inference */\n getRows: () => T[];\n /** Get current sort state */\n getSortState: () => { field: string; direction: 1 | -1 } | null;\n /** Set sort state */\n setSortState: (state: { field: string; direction: 1 | -1 } | null) => void;\n /** Notify that config changed and re-render is needed */\n onConfigChange: () => void;\n /** Emit a custom event */\n emit: (eventName: string, detail: unknown) => void;\n /** Clear row pool for visibility changes */\n clearRowPool: () => void;\n /** Run setup after state changes */\n setup: () => void;\n /** Render header */\n renderHeader: () => void;\n /** Update column template */\n updateTemplate: () => void;\n /** Refresh virtual window */\n refreshVirtualWindow: () => void;\n /** Get virtualization state for row height application */\n getVirtualization: () => { rowHeight: number };\n /** Set row height on virtualization state */\n setRowHeight: (height: number) => void;\n /** Apply animation config to host element */\n applyAnimationConfig: (config: GridConfig<T>) => void;\n /** Get light DOM title from shell state (synced from parseLightDomShell) */\n getShellLightDomTitle: () => string | null;\n /** Get registered tool panels from shell state */\n getShellToolPanels: () => Map<string, ToolPanelDefinition>;\n /** Get registered header contents from shell state */\n getShellHeaderContents: () => Map<string, HeaderContentDefinition>;\n /** Get registered toolbar contents from shell state */\n getShellToolbarContents: () => Map<string, ToolbarContentDefinition>;\n /** Get light DOM header content elements */\n getShellLightDomHeaderContent: () => HTMLElement[];\n /** Get whether a tool buttons container was found in light DOM */\n getShellHasToolButtonsContainer: () => boolean;\n}\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 #initialColumnState?: GridColumnState;\n #callbacks: ConfigManagerCallbacks<T>;\n\n // Shell state (Light DOM title)\n #lightDomTitle?: string;\n\n constructor(callbacks: ConfigManagerCallbacks<T>) {\n this.#callbacks = callbacks;\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.#callbacks.setRowHeight(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.#callbacks.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.#callbacks.getRows();\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.#callbacks.getShellLightDomTitle();\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.#callbacks.getShellLightDomHeaderContent();\n if (lightDomHeaderContent?.length > 0) {\n base.shell.header.lightDomContent = lightDomHeaderContent;\n }\n\n // Sync hasToolButtonsContainer from shell state\n if (this.#callbacks.getShellHasToolButtonsContainer()) {\n base.shell.header.hasToolButtonsContainer = true;\n }\n\n // Sync tool panels (from plugins + API + Light DOM)\n const toolPanelsMap = this.#callbacks.getShellToolPanels();\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.#callbacks.getShellHeaderContents();\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.#callbacks.getShellToolbarContents();\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.#callbacks.setSortState({\n field: primarySort.field,\n direction: primarySort.sort.direction === 'asc' ? 1 : -1,\n });\n }\n } else {\n this.#callbacks.setSortState(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.#callbacks.setSortState(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.#callbacks.getSortState();\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.#callbacks.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.#callbacks.emit('column-visibility', {\n field,\n visible,\n visibleColumns: allCols.filter((c) => !c.hidden).map((c) => c.field),\n });\n\n this.#callbacks.clearRowPool();\n this.#callbacks.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.#callbacks.emit('column-visibility', {\n visibleColumns: allCols.map((c) => c.field),\n });\n\n this.#callbacks.clearRowPool();\n this.#callbacks.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 this.#callbacks.renderHeader();\n this.#callbacks.updateTemplate();\n this.#callbacks.refreshVirtualWindow();\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 let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n const processPendingCallbacks = () => {\n debounceTimer = null;\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 && !debounceTimer) {\n debounceTimer = 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 }\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 (shadowRoot or bodyEl).\n * Used when changing focus or when selection plugin takes over focus management.\n */\nexport function clearCellFocus(root: Element | ShadowRoot | 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, InternalGrid, RowElementInternal } from '../types';\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: InternalGrid,\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 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 const cellCount = rowEl.children.length;\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\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 const isActivelyEditedRow = 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 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 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 // 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 changedRowIds = grid.changedRowIds;\n if (changedRowIds && changedRowIds.length > 0) {\n try {\n const rowId = grid.getRowId?.(rowData);\n if (rowId) {\n isChanged = changedRowIds.includes(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 newClasses = rowClassFn(rowData);\n if (newClasses && newClasses.length > 0) {\n const validClasses = newClasses.filter((c) => c && typeof c === 'string');\n validClasses.forEach((cls) => rowEl.classList.add(cls));\n rowEl.setAttribute('data-dynamic-classes', validClasses.join(' '));\n } else {\n rowEl.removeAttribute('data-dynamic-classes');\n }\n } catch (e) {\n console.warn(`[tbw-grid] rowClass callback error:`, e);\n rowEl.removeAttribute('data-dynamic-classes');\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: InternalGrid, 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.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 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 // 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 cell.setAttribute('aria-selected', String(shouldHaveFocus));\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 cellClasses = cellClassFn(value, rowData, col);\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 console.warn(`[tbw-grid] cellClass callback error for column '${col.field}':`, e);\n cell.removeAttribute('data-dynamic-classes');\n }\n }\n\n // Skip cells in edit mode\n if (cell.classList.contains('editing')) 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 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 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 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 // Skip templated / external cells (these need full rebuild to remount)\n if (col.__viewTemplate || col.__compiledView || 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 console.warn(`[tbw-grid] Format error in column '${col.field}':`, e);\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: InternalGrid, rowEl: HTMLElement, rowData: any, rowIndex: number): void {\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 const gridEl = grid as unknown as HTMLElement;\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 console.warn(`[tbw-grid] Format error in column '${col.field}':`, e);\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 console.warn(`[tbw-grid] External view mount error for column '${col.field}':`, e);\n }\n } else {\n queueMicrotask(() => {\n try {\n gridEl.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 console.warn(`[tbw-grid] External view event dispatch error for column '${col.field}':`, e);\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 if (col.editable) {\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 cellClasses = cellClassFn(cellValue, rowData, col);\n if (cellClasses && cellClasses.length > 0) {\n const validClasses = cellClasses.filter((c) => c && typeof c === 'string');\n validClasses.forEach((cls) => cell.classList.add(cls));\n cell.setAttribute('data-dynamic-classes', validClasses.join(' '));\n }\n } catch (e) {\n console.warn(`[tbw-grid] cellClass callback error for column '${col.field}':`, e);\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: InternalGrid, 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 // Focus the editor in the cell\n const editor = 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 { InternalGrid } from '../types';\nimport { FOCUSABLE_EDITOR_SELECTOR } from './rows';\nimport { clearCellFocus, isRTL } from './utils';\n\n// #region Keyboard Handler\nexport function handleGridKeyDown(grid: InternalGrid, 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 as unknown as HTMLElement);\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 as unknown as HTMLElement);\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 as unknown as HTMLElement).querySelector(\n `[data-row=\"${rowIndex}\"][data-col=\"${colIndex}\"]`,\n ) as HTMLElement | undefined;\n\n const detail = {\n rowIndex,\n colIndex,\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 as unknown as HTMLElement).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 as unknown as HTMLElement).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: InternalGrid, 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;\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 (grid._activeEditRows !== undefined && grid._activeEditRows !== -1 && cell.classList.contains('editing')) {\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 (!cell.contains(document.activeElement)) {\n if (!cell.hasAttribute('tabindex')) cell.setAttribute('tabindex', '-1');\n try {\n cell.focus({ preventScroll: true });\n } catch {\n /* empty */\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 { 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// #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 column = grid._columns[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: InternalGrid, 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 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 { 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: InternalGrid,\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 * Sorting Module\n *\n * Handles column sorting state transitions and row ordering.\n */\n\nimport type { ColumnConfig, InternalGrid, SortHandler, SortState } from '../types';\nimport { renderHeader } from './header';\n\n/**\n * Default comparator for sorting values.\n * Handles nulls (pushed to end), numbers, and string fallback.\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 * Built-in sort implementation using column comparator or default.\n * This is the default sortHandler when none is configured.\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: InternalGrid<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 as unknown as HTMLElement).dispatchEvent(\n new CustomEvent('sort-change', { detail: { field: col.field, direction: dir } }),\n );\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: InternalGrid, 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 as unknown as HTMLElement).dispatchEvent(\n new CustomEvent('sort-change', { detail: { field: col.field, direction: 0 } }),\n );\n // Trigger state change after sort is cleared\n grid.requestStateChange?.();\n }\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: InternalGrid, 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, 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: InternalGrid, 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, colIndex, 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 as unknown as MouseEvent, colIndex, 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: InternalGrid): 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\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","/**\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) as unknown as number;\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 * @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 } else {\n rowEl.classList.remove('tbw-row-loading');\n rowEl.removeAttribute('aria-busy');\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","/**\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 * const scheduler = new RenderScheduler({\n * mergeConfig: () => this.#mergeEffectiveConfig(),\n * processColumns: () => this.#processColumns(),\n * processRows: () => this.#rebuildRowModel(),\n * renderHeader: () => renderHeader(this),\n * updateTemplate: () => updateTemplate(this),\n * renderVirtualWindow: () => this.refreshVirtualWindow(true),\n * afterRender: () => this.#pluginManager?.afterRender(),\n * isConnected: () => this.isConnected && this.#connected,\n * });\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\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/**\n * Callbacks that the scheduler invokes during flush.\n * Each callback corresponds to work done in a specific phase.\n */\nexport interface RenderCallbacks {\n /** Merge effective config (FULL phase) */\n mergeConfig: () => void;\n /** Process columns through plugins (COLUMNS phase) */\n processColumns: () => void;\n /** Rebuild row model through plugins (ROWS phase) */\n processRows: () => void;\n /** Render header DOM (HEADER phase) */\n renderHeader: () => void;\n /** Update CSS grid template (ROWS phase) */\n updateTemplate: () => void;\n /** Recalculate virtual window (VIRTUALIZATION phase) */\n renderVirtualWindow: () => void;\n /** Run plugin afterRender hooks (STYLE phase) */\n afterRender: () => void;\n /** Check if grid is still connected to DOM */\n isConnected: () => boolean;\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 #callbacks: RenderCallbacks;\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(callbacks: RenderCallbacks) {\n this.#callbacks = callbacks;\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.#callbacks.isConnected()) {\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.#callbacks.mergeConfig();\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.#callbacks.processRows();\n }\n\n // Phase 5 (COLUMNS): Process columns + update template\n if (phase >= RenderPhase.COLUMNS) {\n this.#callbacks.processColumns();\n this.#callbacks.updateTemplate();\n }\n\n // Phase 3 (HEADER): Render header\n if (phase >= RenderPhase.HEADER) {\n this.#callbacks.renderHeader();\n }\n\n // Phase 2 (VIRTUALIZATION): Recalculate virtual window\n if (phase >= RenderPhase.VIRTUALIZATION) {\n this.#callbacks.renderVirtualWindow();\n }\n\n // Phase 1 (STYLE): Run afterRender hooks (always runs)\n if (phase >= RenderPhase.STYLE) {\n this.#callbacks.afterRender();\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","import type { InternalGrid, ResizeController } from '../types';\n\nexport function createResizeController(grid: InternalGrid): 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 as unknown as HTMLElement).dispatchEvent(\n new CustomEvent('column-resize', { detail: { field: col.field, width } }),\n );\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 as unknown as HTMLElement).dispatchEvent(\n new CustomEvent('column-resize-reset', { detail: { field: col.field, width: col.width } }),\n );\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 true if the row was found and animated, false otherwise\n */\nexport function animateRow<T>(grid: InternalGrid<T>, rowIndex: number, animationType: RowAnimationType): boolean {\n // Guard against invalid indices\n if (rowIndex < 0) {\n return false;\n }\n\n const rowEl = grid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) {\n // Row is virtualized out of view - nothing to animate\n return false;\n }\n\n animateRowElement(rowEl, animationType);\n return true;\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 Number of rows that were actually animated (visible in viewport)\n */\nexport function animateRows<T>(grid: InternalGrid<T>, rowIndices: number[], animationType: RowAnimationType): number {\n let animatedCount = 0;\n for (const rowIndex of rowIndices) {\n if (animateRow(grid, rowIndex, animationType)) {\n animatedCount++;\n }\n }\n return animatedCount;\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 true if the row was found and animated, false otherwise\n */\nexport function animateRowById<T>(grid: InternalGrid<T>, rowId: string, animationType: RowAnimationType): boolean {\n // Find row index by searching _rows\n const rows = grid._rows ?? [];\n const getRowId = grid.getRowId;\n if (!getRowId) {\n return false;\n }\n\n const rowIndex = rows.findIndex((row) => getRowId(row) === rowId);\n if (rowIndex < 0) {\n return false;\n }\n return animateRow(grid, rowIndex, animationType);\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`;\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)\n if (!isSinglePanel) {\n const chevronSpan = createElement('span', { class: 'tbw-accordion-chevron' });\n chevronSpan.innerHTML = panel.isExpanded ? options.collapseIcon : 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 ShellConfig,\n ToolbarContentDefinition,\n ToolPanelDefinition,\n} from '../types';\nimport { DEFAULT_GRID_ICONS } from '../types';\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 /** 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 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 const collapseIcon = iconToString(icons?.collapse ?? DEFAULT_GRID_ICONS.collapse);\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 const chevronHtml = isSinglePanel\n ? ''\n : `<span class=\"tbw-accordion-chevron\">${isExpanded ? collapseIcon : 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 console.warn(\n `[parseLightDomToolPanels] 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 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 const expandIcon = iconToString(icons?.expand ?? DEFAULT_GRID_ICONS.expand);\n const collapseIcon = iconToString(icons?.collapse ?? DEFAULT_GRID_ICONS.collapse);\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 const chevron = section.querySelector('.tbw-accordion-chevron');\n if (chevron) {\n chevron.innerHTML = isExpanded ? collapseIcon : expandIcon;\n }\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 (like toolbar buttons) can be restored\n * to their original containers and moved to new slots after re-render.\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\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 * Callbacks for ShellController to communicate with the grid.\n * This interface decouples the controller from grid internals.\n */\nexport interface ShellControllerCallbacks {\n /** Get the render root for DOM queries (the grid element) */\n getShadow: () => Element;\n /** Get the current shell config */\n getShellConfig: () => ShellConfig | undefined;\n /** Get accordion expand/collapse icons */\n getAccordionIcons: () => { expand: IconValue; collapse: IconValue };\n /** Emit an event from the grid */\n emit: (eventName: string, detail: unknown) => void;\n /** Refresh the shell header (re-parse light DOM and re-render) */\n refreshShellHeader: () => void;\n}\n\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, callbacks: ShellControllerCallbacks): 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 console.warn('[tbw-grid] No tool panels registered');\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 = callbacks.getShadow();\n updateToolbarActiveStates(shadow, state);\n updatePanelState(shadow, state);\n\n // Render accordion sections\n renderPanelContent(shadow, state, callbacks.getAccordionIcons());\n\n // Emit event\n callbacks.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 = callbacks.getShadow();\n updateToolbarActiveStates(shadow, state);\n updatePanelState(shadow, state);\n\n // Emit event\n callbacks.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 console.warn(`[tbw-grid] Tool panel section \"${sectionId}\" not found`);\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 = callbacks.getShadow();\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 callbacks.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 console.warn(`[tbw-grid] Tool panel \"${panel.id}\" already registered`);\n return;\n }\n state.toolPanels.set(panel.id, panel);\n\n if (initialized) {\n callbacks.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 callbacks.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 console.warn(`[tbw-grid] Header content \"${content.id}\" already registered`);\n return;\n }\n state.headerContents.set(content.id, content);\n\n if (initialized) {\n renderHeaderContent(callbacks.getShadow(), 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 = callbacks.getShadow().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 console.warn(`[tbw-grid] Toolbar content \"${content.id}\" already registered`);\n return;\n }\n state.toolbarContents.set(content.id, content);\n\n if (initialized) {\n callbacks.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 callbacks.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`;\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\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 console.warn('[tbw-grid] 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 console.warn(\n '[tbw-grid] Could not find grid.css in document.styleSheets. Grid styling will not work.',\n 'Available stylesheets:',\n Array.from(document.styleSheets).map((s) => s.href || '(inline)'),\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 * Handles touch events for scrolling with momentum physics.\n * Supports both vertical (faux scrollbar) and horizontal (scroll area) scrolling.\n */\n\n// #region Types\nexport interface TouchScrollState {\n startY: number | null;\n startX: number | null;\n scrollTop: number | null;\n scrollLeft: number | null;\n lastY: number | null;\n lastX: number | null;\n lastTime: number | null;\n velocityY: number;\n velocityX: number;\n momentumRaf: number;\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 scrollTop: null,\n scrollLeft: null,\n lastY: null,\n lastX: null,\n lastTime: null,\n velocityY: 0,\n velocityX: 0,\n momentumRaf: 0,\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.scrollTop = null;\n state.scrollLeft = null;\n state.lastY = null;\n state.lastX = null;\n state.lastTime = null;\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 touchstart event.\n */\nexport function handleTouchStart(e: TouchEvent, state: TouchScrollState, elements: TouchScrollElements): void {\n if (e.touches.length !== 1) return;\n\n // Cancel any ongoing momentum animation\n cancelMomentum(state);\n\n const touch = e.touches[0];\n state.startY = touch.clientY;\n state.startX = touch.clientX;\n state.lastY = touch.clientY;\n state.lastX = touch.clientX;\n state.lastTime = performance.now();\n state.scrollTop = elements.fauxScrollbar.scrollTop;\n state.scrollLeft = elements.scrollArea?.scrollLeft ?? 0;\n state.velocityY = 0;\n state.velocityX = 0;\n}\n\n/**\n * Handle touchmove event.\n * Returns true if the event should be prevented (grid scrolled).\n */\nexport function handleTouchMove(e: TouchEvent, state: TouchScrollState, elements: TouchScrollElements): boolean {\n if (\n e.touches.length !== 1 ||\n state.startY === null ||\n state.startX === null ||\n state.scrollTop === null ||\n state.scrollLeft === null\n ) {\n return false;\n }\n\n const touch = e.touches[0];\n const currentY = touch.clientY;\n const currentX = touch.clientX;\n const now = performance.now();\n\n const deltaY = state.startY - currentY;\n const deltaX = state.startX - currentX;\n\n // Calculate velocity for momentum scrolling\n if (state.lastTime !== null && state.lastY !== null && state.lastX !== null) {\n const dt = now - state.lastTime;\n if (dt > 0) {\n // Velocity in pixels per millisecond\n state.velocityY = (state.lastY - currentY) / dt;\n state.velocityX = (state.lastX - currentX) / dt;\n }\n }\n state.lastY = currentY;\n state.lastX = currentX;\n state.lastTime = now;\n\n // Check if grid can scroll in the requested directions\n const { scrollTop, scrollHeight, clientHeight } = elements.fauxScrollbar;\n const maxScrollY = scrollHeight - clientHeight;\n const canScrollVertically = (deltaY > 0 && scrollTop < maxScrollY) || (deltaY < 0 && scrollTop > 0);\n\n let canScrollHorizontally = false;\n if (elements.scrollArea) {\n const { scrollLeft, scrollWidth, clientWidth } = elements.scrollArea;\n const maxScrollX = scrollWidth - clientWidth;\n canScrollHorizontally = (deltaX > 0 && scrollLeft < maxScrollX) || (deltaX < 0 && scrollLeft > 0);\n }\n\n // Apply scroll if grid can scroll in that direction\n if (canScrollVertically) {\n elements.fauxScrollbar.scrollTop = state.scrollTop + deltaY;\n }\n if (canScrollHorizontally && elements.scrollArea) {\n elements.scrollArea.scrollLeft = state.scrollLeft + deltaX;\n }\n\n // Return true to prevent page scroll when we scrolled the grid\n return canScrollVertically || canScrollHorizontally;\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 touch scroll event listeners on the grid content element.\n * Returns a cleanup function that removes all listeners.\n */\nexport function setupTouchScrollListeners(\n gridContentEl: HTMLElement,\n state: TouchScrollState,\n elements: TouchScrollElements,\n signal: AbortSignal,\n): void {\n gridContentEl.addEventListener('touchstart', (e: TouchEvent) => handleTouchStart(e, state, elements), {\n passive: true,\n signal,\n });\n\n gridContentEl.addEventListener(\n 'touchmove',\n (e: TouchEvent) => {\n const shouldPrevent = handleTouchMove(e, state, elements);\n if (shouldPrevent) {\n e.preventDefault();\n }\n },\n { passive: false, signal },\n );\n\n gridContentEl.addEventListener('touchend', () => handleTouchEnd(state, elements), { passive: true, signal });\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 { 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,\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: 'sticky',\n pluginName: 'pinnedColumns',\n level: 'column',\n description: 'the \"sticky\" column property',\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 // 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>(config: GridConfig<T>, plugins: readonly BaseGridPlugin[]): 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 throw new Error(\n `[tbw-grid] 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 );\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[]): 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 prefix = `[tbw-grid:${capitalize(plugin.name)}Plugin]`;\n const formatted = `${prefix} 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 console.warn(warning);\n }\n }\n\n // Throw consolidated error if any (always, regardless of environment)\n if (errors.length > 0) {\n throw new Error(`[tbw-grid] Configuration error:\\n\\n${errors.join('\\n\\n')}`);\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(plugin: BaseGridPlugin, loadedPlugins: readonly BaseGridPlugin[]): 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 throw new Error(\n `[tbw-grid] 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 );\n } else {\n // Soft dependency - log info message but continue\n console.info(\n `[tbw-grid] ${capitalize(pluginName)}Plugin: Optional \"${requiredPlugin}\" plugin not found. ` +\n `Some features may be unavailable.`,\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[]): 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 console.warn(\n `[tbw-grid] Plugin incompatibility warning:\\n\\n` +\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 );\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 * 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 { 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 // #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);\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 // 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 console.warn(\n `[tbw-grid] Deprecation warning: \"${plugin.name}\" uses getExtraHeight() / getExtraHeightBefore() ` +\n `which are deprecated and will be removed in v3.0.\\n` +\n ` → Migrate to getRowHeight(row, index) for better variable row height support.\\n` +\n ` → See: https://toolbox-web.dev/docs/grid/plugins/migration#row-height-hooks`,\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 }\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);\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 * Used to skip the hook call overhead when no plugins need it.\n */\n hasAfterCellRenderHook(): boolean {\n return this.plugins.some((p) => typeof p.afterCellRender === 'function');\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 * Used to skip the hook call overhead when no plugins need it.\n */\n hasAfterRowRenderHook(): boolean {\n return 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 console.error(`[tbw-grid] Error in plugin event handler for \"${eventType}\":`, error);\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","/**\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 = `/**\n * tbw-grid Light DOM Styles\n *\n * This stylesheet uses CSS nesting to scope all styles to the tbw-grid element.\n * All selectors are automatically prefixed with \\`tbw-grid\\` for encapsulation.\n *\n * CSS Cascade Layers are used to control style priority:\n * - tbw-base: Core grid styles (lowest priority)\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 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting\n * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@layer\n */\n\n/* Declare layer order - earlier layers have lower priority */\n@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","import { createAriaState, updateAriaCounts, updateAriaLabels, type AriaState } from './internal/aria';\nimport { autoSizeColumns, updateTemplate } from './internal/columns';\nimport { ConfigManager } from './internal/config-manager';\nimport { setupCellEventDelegation, setupRootEventDelegation } from './internal/event-delegation';\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 { 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 renderShellHeader,\n setupShellEventListeners,\n setupToolPanelResize,\n shouldRenderShellHeader,\n type ShellController,\n type ShellState,\n type ToolPanelRendererFactory,\n} from './internal/shell';\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 {\n computeAverageExcludingPluginRows,\n getRowIndexAtOffset,\n getTotalHeight,\n measureRenderedRowHeights,\n rebuildPositionCache,\n updateRowHeight,\n} from './internal/virtualization';\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 CellChangeDetail,\n ColumnConfig,\n ColumnConfigMap,\n ColumnInternal,\n DataGridEventMap,\n FitMode,\n FrameworkAdapter,\n GridColumnState,\n GridConfig,\n HeaderContentDefinition,\n InternalGrid,\n ResizeController,\n RowAnimationType,\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 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\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\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 }\n\n // visibleColumns returns only visible columns for rendering\n // This is what header/row rendering should use\n get _visibleColumns(): ColumnInternal<T>[] {\n return 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: HTMLElement[] = [];\n _resizeController!: ResizeController;\n\n // Virtualization & scroll state\n _virtualization: VirtualState = {\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 // Variable row height support\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 };\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 /**\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 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 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 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 render scheduler with callbacks\n this.#scheduler = new RenderScheduler({\n mergeConfig: () => {\n // Re-parse light DOM columns to pick up framework adapter renderers\n // This is essential for React/Angular where renderers register asynchronously\n this.#configManager.parseLightDomColumns(this as unknown as HTMLElement);\n this.#configManager.merge();\n this.#updatePluginConfigs(); // Sync plugin configs (including auto-detection) before processing\n // Validate that plugin-specific column properties have their required plugins loaded\n // This runs after plugins are loaded and config is merged\n validatePluginProperties(this.#effectiveConfig, this.#pluginManager?.getPlugins() ?? []);\n // Validate plugin configRules (errors/warnings for invalid config combinations)\n validatePluginConfigRules(this.#pluginManager?.getPlugins() ?? []);\n // Validate plugin incompatibilities (warnings for conflicting plugin combinations)\n validatePluginIncompatibilities(this.#pluginManager?.getPlugins() ?? []);\n // Update ARIA labels (explicit config or derived from shell title)\n this.#updateAriaLabels();\n // Store base columns before plugin transformation\n this.#baseColumns = [...this._columns];\n },\n processColumns: () => this.#processColumns(),\n processRows: () => this.#rebuildRowModel(),\n renderHeader: () => renderHeader(this),\n updateTemplate: () => updateTemplate(this),\n renderVirtualWindow: () => this.refreshVirtualWindow(true, true),\n afterRender: () => {\n this.#pluginManager?.afterRender();\n\n // Recalculate spacer height after plugins modify the DOM in afterRender.\n // Plugins like MasterDetailPlugin create/remove detail elements in afterRender,\n // which changes the total content height. Since refreshVirtualWindow was called\n // with skipAfterRender=true (scheduler calls afterRender separately), the\n // microtask that normally handles this recalculation was skipped.\n // Use forceRead=false (cached geometry) since the scheduler's renderVirtualWindow\n // already read fresh geometry and updated caches before calling afterRender.\n // Plugins modify content inside containers, not container geometry itself.\n // Any container geometry changes are caught asynchronously by ResizeObserver.\n if (this._virtualization.enabled && this._virtualization.totalHeightEl) {\n queueMicrotask(() => {\n if (!this._virtualization.totalHeightEl) return;\n const newTotalHeight = this.#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 // Uses RAF to ensure browser has computed layout before measuring\n if (this.#needsRowHeightMeasurement) {\n this.#needsRowHeightMeasurement = false;\n // Double RAF ensures layout is fully computed (first RAF schedules after paint,\n // second RAF runs after that frame's layout)\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n this.#measureRowHeightForPlugins();\n });\n });\n }\n },\n isConnected: () => this.isConnected && this.#connected,\n });\n // Connect ready promise to scheduler\n this.#scheduler.setInitialReadyResolver(() => this.#readyResolve?.());\n\n // Initialize shell controller with callbacks\n this.#shellController = createShellController(this.#shellState, {\n getShadow: () => this.#renderRoot,\n getShellConfig: () => this.#effectiveConfig?.shell,\n getAccordionIcons: () => ({\n expand: this.#effectiveConfig?.icons?.expand ?? DEFAULT_GRID_ICONS.expand,\n collapse: this.#effectiveConfig?.icons?.collapse ?? DEFAULT_GRID_ICONS.collapse,\n }),\n emit: (eventName, detail) => this.#emit(eventName, detail),\n refreshShellHeader: () => this.refreshShellHeader(),\n });\n\n // Initialize config manager with callbacks\n this.#configManager = new ConfigManager<T>({\n getRows: () => this.#rows,\n getSortState: () => this._sortState,\n setSortState: (state) => {\n this._sortState = state;\n },\n onConfigChange: () => {\n this.#scheduler.requestPhase(RenderPhase.FULL, 'configChange');\n },\n emit: (eventName, detail) => this.#emit(eventName, detail),\n clearRowPool: () => {\n this._rowPool.length = 0;\n if (this._bodyEl) this._bodyEl.innerHTML = '';\n this.__rowRenderEpoch++;\n },\n setup: () => this.#setup(),\n renderHeader: () => renderHeader(this),\n updateTemplate: () => updateTemplate(this),\n refreshVirtualWindow: () => this.#scheduler.requestPhase(RenderPhase.VIRTUALIZATION, 'configManager'),\n getVirtualization: () => this._virtualization,\n setRowHeight: (height) => {\n this._virtualization.rowHeight = height;\n },\n applyAnimationConfig: (config) => this.#applyAnimationConfig(config),\n getShellLightDomTitle: () => this.#shellState.lightDomTitle,\n getShellToolPanels: () => this.#shellState.toolPanels,\n getShellHeaderContents: () => this.#shellState.headerContents,\n getShellToolbarContents: () => this.#shellState.toolbarContents,\n getShellLightDomHeaderContent: () => this.#shellState.lightDomHeaderContent,\n getShellHasToolButtonsContainer: () => this.#shellState.hasToolButtonsContainer,\n });\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.\n * Used by plugins for inter-plugin communication.\n * @group Plugin Communication\n * @internal Plugin API\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 name.\n * Used for loose coupling between plugins (avoids static imports).\n * @group Plugin Communication\n * @internal Plugin API\n */\n getPluginByName(name: string): BaseGridPlugin | undefined {\n return this.#pluginManager?.getPluginByName(name);\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 * Initialize plugin system with instances from config.\n * Plugins are class instances passed in gridConfig.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 plugins = Array.isArray(pluginsConfig) ? (pluginsConfig as BaseGridPlugin[]) : [];\n\n // Attach all plugins\n this.#pluginManager.attachAll(plugins);\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 hasn'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\n // Check if plugins have actually changed (same array reference or same contents)\n // This avoids unnecessary detach/attach cycles on every render\n if (this.#lastPluginsArray === newPlugins) {\n return; // Same array reference - no change\n }\n\n // Check if the arrays have the same plugin instances\n if (\n this.#lastPluginsArray &&\n this.#lastPluginsArray.length === newPlugins.length &&\n this.#lastPluginsArray.every((p, i) => p === newPlugins[i])\n ) {\n // Same plugins in same order - just update the 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 // Header contents don't have a light DOM tracking set, so clear all and re-collect\n for (const contentId of this.#shellState.headerContents.keys()) {\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\n this.#lastPluginsArray = newPlugins;\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 as unknown as HTMLElement);\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 to avoid unnecessary re-initialization\n const pluginsConfig = this.#effectiveConfig?.plugins;\n this.#lastPluginsArray = Array.isArray(pluginsConfig) ? (pluginsConfig as BaseGridPlugin[]) : [];\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 // 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\n // Clear plugin tracking to allow fresh initialization on reconnect\n this.#lastPluginsArray = 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 console.warn(`[tbw-grid] Invalid JSON for '${name}' attribute:`, newValue);\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 }\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 as unknown as InternalGrid<T>);\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 as unknown as InternalGrid<T>, 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.#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');\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 // Use a 1px threshold to avoid oscillation from sub-pixel rounding\n if (measuredHeight > 0 && Math.abs(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.#initializePositionCache();\n\n // Update spacer height with the correct total\n if (this._virtualization.totalHeightEl) {\n const newHeight = this.#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 // 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 as unknown as InternalGrid<T>, 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 (shadow DOM doesn't reliably support :focus-within)\n // Listen on shadow root to catch focus events from shadow DOM elements\n // Cast to EventTarget since TypeScript's lib.dom doesn't include focus events on ShadowRoot\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 (!newFocus || !this.#renderRoot.contains(newFocus)) {\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 * @example\n * ```typescript\n * // Type-safe: detail is CellClickDetail<Employee>\n * grid.addEventListener('cell-click', (e) => {\n * console.log(e.detail.field, e.detail.value);\n * });\n *\n * // Works with editing events when EditingPlugin is imported\n * grid.addEventListener('cell-commit', (e) => {\n * console.log(e.detail.oldValue, '→', e.detail.value);\n * });\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 #emit<D>(eventName: string, detail: D): void {\n this.dispatchEvent(new CustomEvent(eventName, { detail, bubbles: true, composed: true }));\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 = this.#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 /**\n * Try to resolve the ID for a row using configured getRowId or fallback.\n * Returns undefined if no ID can be determined (non-throwing).\n * Used internally by #rebuildRowIdMap.\n */\n #tryResolveRowId(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 * Used by public getRowId() method.\n */\n #resolveRowIdOrThrow(row: T, getRowId?: (row: T) => string): string {\n const id = this.#tryResolveRowId(row, getRowId);\n if (id === undefined) {\n throw new Error(\n '[tbw-grid] Cannot determine row ID. ' +\n 'Configure getRowId in gridConfig or ensure rows have an \"id\" property.',\n );\n }\n return id;\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\n this.#configManager.parseLightDomColumns(this as unknown as HTMLElement);\n this.#configManager.merge();\n this.#updatePluginConfigs();\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\n // Full re-render needed if shell state or panel count changed\n const needsFullRerender =\n hadShell !== nowNeedsShell ||\n (!hadToolPanel && nowHasToolPanels) ||\n (hadToolPanel && toolPanelCount !== accordionSectionsBefore);\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 // 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:\n // 1. Modified existing columns\n // 2. Added new columns (e.g., expander column)\n // 3. Reordered columns\n // We trust the plugin's output order and include all columns they returned\n // plus any hidden columns at the end\n this._columns = [...processedColumns, ...hiddenCols] as ColumnInternal<T>[];\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 /** 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 // 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(originalRows) ?? originalRows;\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 position cache for variable heights (rows may have changed)\n if (this._virtualization.variableHeights) {\n this.#initializePositionCache();\n }\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 as unknown as InternalGrid<T>, start, end, epoch, this.#renderRowHook);\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 /** 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 as unknown as HTMLElement);\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.#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 return (\n (Array.from(this._bodyEl.querySelectorAll('.data-grid-row')) as HTMLElement[]).find((r) => {\n const cell = r.querySelector('.cell[data-row]');\n return cell && Number(cell.getAttribute('data-row')) === rowIndex;\n }) || null\n );\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 const col = this._columns[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 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 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, colIndex: number, headerEl: HTMLElement): boolean {\n const col = this._columns[colIndex];\n if (!col) return false;\n\n const headerClickEvent: HeaderClickEvent = {\n colIndex,\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.\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 = document.querySelector('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\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 this.#resolveRowIdOrThrow(row, 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.#rowIdMap.get(id)?.row;\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 const entry = this.#rowIdMap.get(id);\n if (!entry) {\n throw new Error(\n `[tbw-grid] Row with ID \"${id}\" not found. ` + `Ensure the row exists and getRowId is correctly configured.`,\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 this.#emit('cell-change', {\n row,\n rowId: id,\n rowIndex: index,\n field,\n oldValue,\n newValue,\n changes,\n source,\n } as CellChangeDetail<T>);\n }\n\n // Schedule re-render if anything changed\n if (changedFields.length > 0) {\n this.#scheduler.requestPhase(RenderPhase.ROWS, 'updateRow');\n }\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 let anyChanged = false;\n\n for (const { id, changes } of updates) {\n const entry = this.#rowIdMap.get(id);\n if (!entry) {\n throw new Error(\n `[tbw-grid] Row with ID \"${id}\" not found. ` + `Ensure the row exists and getRowId is correctly configured.`,\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 this.#emit('cell-change', {\n row,\n rowId: id,\n rowIndex: index,\n field,\n oldValue,\n newValue,\n changes,\n source,\n } as CellChangeDetail<T>);\n }\n }\n }\n\n // Schedule single re-render for all changes\n if (anyChanged) {\n this.#scheduler.requestPhase(RenderPhase.ROWS, 'updateRows');\n }\n }\n\n /**\n * Animate a row at the specified index.\n * Applies a visual animation to highlight changes, insertions, or removals.\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 *\n * @example\n * ```typescript\n * // Highlight a row after external data update\n * grid.updateRow('row-123', { status: 'shipped' });\n * grid.animateRow(rowIndex, 'change');\n *\n * // Animate new row insertion\n * grid.rows = [...grid.rows, newRow];\n * grid.animateRow(grid.rows.length - 1, 'insert');\n * ```\n */\n animateRow(rowIndex: number, type: RowAnimationType): void {\n animateRow(this as unknown as InternalGrid, rowIndex, type);\n }\n\n /**\n * Animate multiple rows at once.\n * More efficient than calling `animateRow()` multiple times.\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 *\n * @example\n * ```typescript\n * // Highlight all changed rows after bulk update\n * const changedIndices = [0, 2, 5];\n * grid.animateRows(changedIndices, 'change');\n * ```\n */\n animateRows(rowIndices: number[], type: RowAnimationType): void {\n animateRows(this as unknown as InternalGrid, rowIndices, type);\n }\n\n /**\n * Animate a row by its ID.\n * Uses the configured `getRowId` function to find the row.\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', (data) => {\n * grid.updateRow(data.id, data.changes);\n * grid.animateRowById(data.id, 'change');\n * });\n * ```\n */\n animateRowById(rowId: string, type: RowAnimationType): boolean {\n return animateRowById(this as unknown as InternalGrid, rowId, type);\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 = document.querySelector('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.#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.#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 = 'dg-toolbar-btn';\n * csvBtn.addEventListener('click', () => exportToCSV(grid.rows));\n *\n * const jsonBtn = document.createElement('button');\n * jsonBtn.textContent = 'Export JSON';\n * jsonBtn.className = 'dg-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\n // Re-create resize controller (DOM elements changed)\n this._resizeController = createResizeController(this as unknown as InternalGrid<T>);\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\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 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 as unknown as HTMLElement);\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 as unknown as HTMLElement);\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\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 const fauxScrollbar = this._virtualization.container;\n const viewportEl = this._virtualization.viewportEl ?? fauxScrollbar;\n if (viewportEl) {\n this._virtualization.cachedViewportHeight = viewportEl.clientHeight;\n }\n if (fauxScrollbar) {\n this._virtualization.cachedFauxHeight = fauxScrollbar.clientHeight;\n }\n const scrollAreaEl = this._virtualization.scrollAreaEl;\n if (scrollAreaEl) {\n this._virtualization.cachedScrollAreaHeight = scrollAreaEl.clientHeight;\n }\n }\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 virt = this._virtualization;\n\n // Use cached geometry to avoid forced synchronous layout reads.\n // Caches are kept current by ResizeObserver (#updateCachedGeometry) and force-refresh paths.\n // Only read fresh values when forceRead is explicitly requested (structural DOM changes).\n let fauxScrollHeight: number;\n let viewportHeight: number;\n let scrollAreaHeight: number;\n\n if (forceRead) {\n const fauxScrollbar = virt.container ?? this;\n const viewportEl = virt.viewportEl ?? fauxScrollbar;\n const scrollAreaEl = virt.scrollAreaEl;\n\n fauxScrollHeight = fauxScrollbar.clientHeight;\n viewportHeight = viewportEl.clientHeight;\n scrollAreaHeight = scrollAreaEl ? scrollAreaEl.clientHeight : fauxScrollHeight;\n\n // Update caches while we have fresh values\n virt.cachedFauxHeight = fauxScrollHeight;\n virt.cachedViewportHeight = viewportHeight;\n virt.cachedScrollAreaHeight = scrollAreaHeight;\n } else {\n fauxScrollHeight = virt.cachedFauxHeight;\n viewportHeight = virt.cachedViewportHeight;\n scrollAreaHeight = virt.cachedScrollAreaHeight || fauxScrollHeight;\n }\n\n // Use scroll-area height as reference since it contains the actual content\n const viewportHeightDiff = scrollAreaHeight - viewportHeight;\n\n // Horizontal scrollbar compensation: When a horizontal scrollbar appears inside scroll-area,\n // the faux scrollbar (sibling) is taller than scroll-area. Add the difference as padding.\n const hScrollbarPadding = Math.max(0, fauxScrollHeight - scrollAreaHeight);\n\n // Calculate row content height - use position cache for variable heights, simple multiplication otherwise\n let rowContentHeight: number;\n let pluginExtraHeight = 0;\n\n if (virt.variableHeights && virt.positionCache) {\n // Variable heights mode: position cache includes heights from getRowHeight() plugin hooks\n // Do NOT add pluginExtraHeight here - it would double-count since getRowHeight() already\n // reports expanded detail heights via the position cache\n rowContentHeight = getTotalHeight(virt.positionCache);\n } else {\n // Fixed heights mode: use simple multiplication and add plugin extra heights\n // (for plugins using deprecated getExtraHeight() that don't implement getRowHeight())\n rowContentHeight = totalRows * virt.rowHeight;\n pluginExtraHeight = this.#pluginManager?.getExtraHeight() ?? 0;\n }\n\n const total = rowContentHeight + viewportHeightDiff + pluginExtraHeight + hScrollbarPadding;\n return total;\n }\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 * @internal\n */\n #initializePositionCache(): void {\n if (!this._virtualization.variableHeights) return;\n\n const rows = this._rows;\n const estimatedHeight = this._virtualization.rowHeight || 28;\n const rowHeightFn = this.#effectiveConfig.rowHeight as ((row: T, index: number) => number | undefined) | undefined;\n const getRowId = this.#effectiveConfig.getRowId;\n const rowIdFn = getRowId ? (row: T) => getRowId(row) : undefined;\n\n // Build position cache with priority: plugin height > rowHeight function > cached > estimated\n this._virtualization.positionCache = rebuildPositionCache(\n rows,\n this._virtualization.heightCache,\n estimatedHeight,\n { rowId: rowIdFn },\n (row, index) => {\n const pluginHeight = this.#pluginManager?.getRowHeight?.(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\n // Compute stats excluding plugin-managed rows\n const stats = computeAverageExcludingPluginRows(\n this._virtualization.positionCache,\n rows,\n estimatedHeight,\n (row, index) => this.#pluginManager?.getRowHeight?.(row, index),\n );\n this._virtualization.measuredCount = stats.measuredCount;\n if (stats.measuredCount > 0) {\n this._virtualization.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 if (!this._virtualization.variableHeights) return;\n if (!this._virtualization.positionCache) return;\n if (rowIndex < 0 || rowIndex >= this._rows.length) return;\n\n const positionCache = this._virtualization.positionCache;\n const row = this._rows[rowIndex];\n\n // Determine the new height\n let height = newHeight;\n if (height === undefined) {\n // Query plugin for height\n height = this.#pluginManager?.getRowHeight?.(row, rowIndex);\n }\n if (height === undefined) {\n // Fall back to base row height\n height = this._virtualization.rowHeight;\n }\n\n const currentEntry = positionCache[rowIndex];\n if (!currentEntry || Math.abs(currentEntry.height - height) < 1) {\n // No significant change\n return;\n }\n\n // Update position cache incrementally (updates this row + all subsequent offsets)\n updateRowHeight(positionCache, rowIndex, height);\n\n // Update spacer height\n if (this._virtualization.totalHeightEl) {\n const newTotalHeight = this.#calculateTotalSpacerHeight(this._rows.length);\n this._virtualization.totalHeightEl.style.height = `${newTotalHeight}px`;\n }\n }\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 * @internal\n */\n #measureRenderedRowHeights(start: number, end: number): void {\n if (!this._virtualization.variableHeights) return;\n if (!this._virtualization.positionCache) return;\n if (!this._bodyEl) return;\n\n const rowElements = this._bodyEl.querySelectorAll('.data-grid-row');\n const getRowId = this.#effectiveConfig.getRowId;\n\n const result = measureRenderedRowHeights(\n {\n positionCache: this._virtualization.positionCache,\n heightCache: this._virtualization.heightCache,\n rows: this._rows,\n defaultHeight: this._virtualization.rowHeight,\n start,\n end,\n getPluginHeight: (row, index) => this.#pluginManager?.getRowHeight?.(row, index),\n getRowId: getRowId ? (row: T) => getRowId(row) : undefined,\n },\n rowElements,\n );\n\n if (result.hasChanges) {\n this._virtualization.measuredCount = result.measuredCount;\n this._virtualization.averageHeight = result.averageHeight;\n\n // Update total height spacer\n if (this._virtualization.totalHeightEl) {\n const newTotalHeight = this.#calculateTotalSpacerHeight(this._rows.length);\n this._virtualization.totalHeightEl.style.height = `${newTotalHeight}px`;\n }\n }\n }\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 * @internal Plugin API\n */\n refreshVirtualWindow(force = false, skipAfterRender = false): boolean {\n if (!this._bodyEl) return false;\n\n const totalRows = this._rows.length;\n\n if (!this._virtualization.enabled) {\n this.#renderVisibleRows(0, totalRows);\n if (!skipAfterRender) {\n this.#pluginManager?.afterRender();\n }\n return true;\n }\n\n if (this._rows.length <= this._virtualization.bypassThreshold) {\n this._virtualization.start = 0;\n this._virtualization.end = totalRows;\n // Only reset transform on force refresh (initial render, data change)\n // Don't reset on scroll-triggered updates - the scroll handler manages transforms\n if (force) {\n this._bodyEl.style.transform = 'translateY(0px)';\n }\n this.#renderVisibleRows(0, totalRows, force ? ++this.__rowRenderEpoch : this.__rowRenderEpoch);\n // Only recalculate height on force refresh (structural changes)\n // Skip on scroll-only updates to prevent scrollbar jumpiness\n if (force && this._virtualization.totalHeightEl) {\n this._virtualization.totalHeightEl.style.height = `${this.#calculateTotalSpacerHeight(totalRows, true)}px`;\n }\n // Update ARIA counts on the grid container\n this.#updateAriaCounts(totalRows, this._visibleColumns.length);\n if (!skipAfterRender) {\n this.#pluginManager?.afterRender();\n }\n return true;\n }\n\n // --- Normal virtualization path with faux scrollbar pattern ---\n // Faux scrollbar provides scrollTop, viewport provides visible height\n const fauxScrollbar = this._virtualization.container ?? this;\n const viewportEl = this._virtualization.viewportEl ?? fauxScrollbar;\n // On force-refresh (structural changes), read fresh geometry and update cache.\n // On scroll-triggered updates, use cached height to avoid forced synchronous layout.\n // The cache is kept current by ResizeObserver + force-refresh calls.\n const viewportHeight = force\n ? (this._virtualization.cachedViewportHeight = viewportEl.clientHeight)\n : this._virtualization.cachedViewportHeight ||\n (this._virtualization.cachedViewportHeight = viewportEl.clientHeight);\n const rowHeight = this._virtualization.rowHeight;\n const scrollTop = fauxScrollbar.scrollTop;\n\n let start: number;\n const positionCache = this._virtualization.positionCache;\n\n // Variable row heights: use binary search on position cache\n if (this._virtualization.variableHeights && positionCache && positionCache.length > 0) {\n start = getRowIndexAtOffset(positionCache, scrollTop);\n if (start === -1) start = 0;\n } else {\n // Uniform row heights: simple division\n // When plugins add extra height (e.g., expanded details), the scroll position\n // includes that extra height. We need to find the actual row at scrollTop\n // by iteratively accounting for cumulative extra heights.\n // This prevents jumping when scrolling past expanded content.\n start = Math.floor(scrollTop / rowHeight);\n\n // Iteratively refine: the initial guess may be too high because scrollTop\n // includes extra heights from expanded rows before it. Adjust downward.\n let iterations = 0;\n const maxIterations = 10; // Prevent infinite loop\n while (iterations < maxIterations) {\n const extraHeightBefore = this.#pluginManager?.getExtraHeightBefore?.(start) ?? 0;\n const adjustedStart = Math.floor((scrollTop - extraHeightBefore) / rowHeight);\n if (adjustedStart >= start || adjustedStart < 0) break;\n start = adjustedStart;\n iterations++;\n }\n }\n\n // Faux scrollbar pattern: calculate effective position for this start\n // With translateY(0), the first rendered row appears at viewport top\n // Round down to even number so DOM nth-child(even) always matches data row parity\n // This prevents zebra stripe flickering during scroll since rows shift in pairs\n start = start - (start % 2);\n if (start < 0) start = 0;\n\n // Allow plugins to extend the start index backwards\n // (e.g., to keep expanded detail rows visible as they scroll out)\n const pluginAdjustedStart = this.#pluginManager?.adjustVirtualStart(start, scrollTop, rowHeight);\n if (pluginAdjustedStart !== undefined && pluginAdjustedStart < start) {\n start = pluginAdjustedStart;\n // Re-apply even alignment after plugin adjustment\n start = start - (start % 2);\n if (start < 0) start = 0;\n }\n\n // Faux pattern buffer: render enough rows to fill viewport + overscan\n // For variable heights, calculate based on actual heights from position cache\n // This ensures expanded detail rows don't cause blank space at bottom\n let end: number;\n\n if (this._virtualization.variableHeights && positionCache && positionCache.length > 0) {\n // Variable heights: walk forward from start, summing actual heights\n // until we've covered the viewport height plus some overscan\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 // Ensure we always have at least a few rows for edge cases\n const minRows = Math.ceil(viewportHeight / rowHeight) + 3;\n if (end - start < minRows) {\n end = Math.min(start + minRows, totalRows);\n }\n } else {\n // Fixed heights: simple calculation\n const visibleCount = Math.ceil(viewportHeight / rowHeight) + 3;\n end = start + visibleCount;\n }\n\n if (end > totalRows) end = totalRows;\n\n // Early-exit: if the visible window hasn't changed and this isn't a force refresh,\n // skip re-rendering rows entirely. The sync scroll handler already updated the transform.\n // This is the key perf optimization - row rendering is the most expensive part.\n const prevStart = this._virtualization.start;\n const prevEnd = this._virtualization.end;\n if (!force && start === prevStart && end === prevEnd) {\n // Transform was already applied by the sync scroll handler.\n // Just update it here for consistency (the sync handler may have used a slightly\n // different sub-pixel offset due to timing, but it's close enough to skip re-render).\n return false;\n }\n\n this._virtualization.start = start;\n this._virtualization.end = end;\n\n // Height spacer for scrollbar\n // The faux-vscroll is a sibling of .tbw-scroll-area, so it doesn't shrink when\n // elements inside scroll-area (header, column groups, footer, hScrollbar) take vertical space.\n // viewportHeightDiff captures ALL these differences - no extra buffer needed.\n // Use cached geometry on scroll path; read fresh on force-refresh.\n const fauxScrollHeight = force\n ? (this._virtualization.cachedFauxHeight = fauxScrollbar.clientHeight)\n : this._virtualization.cachedFauxHeight || (this._virtualization.cachedFauxHeight = fauxScrollbar.clientHeight);\n\n // Also update scroll-area height cache on force-refresh\n if (force) {\n const scrollAreaEl = this._virtualization.scrollAreaEl;\n if (scrollAreaEl) {\n this._virtualization.cachedScrollAreaHeight = scrollAreaEl.clientHeight;\n }\n }\n\n // Guard: Skip height calculation if faux scrollbar has no height but viewport does\n // This indicates stale DOM references during recreation (e.g., shell toggle)\n // When both are 0 (test environment or not in DOM), proceed normally\n if (fauxScrollHeight === 0 && viewportHeight > 0) {\n // Stale refs detected, schedule retry through the scheduler\n // Using scheduler ensures this batches with other pending work\n this.#scheduler.requestPhase(RenderPhase.VIRTUALIZATION, 'stale-refs-retry');\n return false;\n }\n\n // Only recalculate height on force refresh (structural changes like data/plugin changes)\n // Skip on scroll-only updates to prevent scrollbar jumpiness from repeated DOM reads\n if (force && this._virtualization.totalHeightEl) {\n const totalHeight = this.#calculateTotalSpacerHeight(totalRows);\n this._virtualization.totalHeightEl.style.height = `${totalHeight}px`;\n }\n\n // Smooth scroll: apply offset for fluid motion\n // Since start is even-aligned, offset is distance from that aligned position\n // This creates smooth sliding while preserving zebra stripe parity\n //\n // Get actual offset for the start row:\n // - Variable heights mode: use position cache offset (already includes heights from getRowHeight())\n // - Fixed heights mode: use simple multiplication + extraHeightBefore for deprecated hooks\n let startRowOffset: number;\n if (this._virtualization.variableHeights && positionCache && positionCache[start]) {\n // Position cache offset already accounts for variable heights via getRowHeight()\n // Do NOT add extraHeightBefore - it would double-count\n startRowOffset = positionCache[start].offset;\n } else {\n // Fixed heights mode: add extra heights from deprecated hooks\n const extraHeightBeforeStart = this.#pluginManager?.getExtraHeightBefore?.(start) ?? 0;\n startRowOffset = start * rowHeight + extraHeightBeforeStart;\n }\n\n const subPixelOffset = -(scrollTop - startRowOffset);\n this._bodyEl.style.transform = `translateY(${subPixelOffset}px)`;\n\n this.#renderVisibleRows(start, end, force ? ++this.__rowRenderEpoch : this.__rowRenderEpoch);\n\n // Measure rendered row heights for variable height mode\n // Only on force refresh to avoid hot path overhead during scroll\n if (force && this._virtualization.variableHeights) {\n this.#measureRenderedRowHeights(start, end);\n }\n\n // Update ARIA counts on the grid container\n this.#updateAriaCounts(totalRows, this._visibleColumns.length);\n\n // Only run plugin afterRender hooks on force refresh (structural changes)\n // Skip on scroll-triggered renders for maximum performance\n if (force && !skipAfterRender) {\n this.#pluginManager?.afterRender();\n\n // After plugins modify the DOM (e.g., add footer, column groups),\n // heights may have changed. Recalculate spacer height in a microtask\n // to catch these changes before the next paint.\n // Use forceRead=false (cached geometry) since the force path above\n // already read fresh geometry and updated all caches. Plugins modify\n // content inside containers, not container geometry itself.\n queueMicrotask(() => {\n if (!this._virtualization.totalHeightEl) return;\n\n // Use cached geometry - avoids forced synchronous layout\n const newTotalHeight = this.#calculateTotalSpacerHeight(totalRows);\n\n // Guard: skip if stale refs detected (0 faux height with positive viewport)\n if (this._virtualization.cachedFauxHeight === 0 && this._virtualization.cachedViewportHeight > 0) return;\n\n this._virtualization.totalHeightEl.style.height = `${newTotalHeight}px`;\n });\n }\n\n return true;\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 // #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\n(globalThis as unknown as { DataGridElement: typeof DataGridElement }).DataGridElement = DataGridElement;\n\n// Type augmentation for querySelector/createElement\ndeclare global {\n interface HTMLElementTagNameMap {\n 'tbw-grid': DataGridElement;\n }\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 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;\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 */\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 a future major version.\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 /** The grid's shadow root. */\n shadowRoot: ShadowRoot | null;\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 // 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 /** 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","/**\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 {\n ColumnConfig,\n ColumnState,\n GridPlugin,\n HeaderContentDefinition,\n IconValue,\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(name: string): 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\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 * 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 * @example\n * ```ts\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 * 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 as unknown as HTMLElement;\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 message.\n */\n protected warn(message: string): void {\n console.warn(`[tbw-grid:${this.name}] ${message}`);\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 v3.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 v3.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.\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 * 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 types needed for factory functions\nimport type { 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 * This avoids the need to cast when querying grids:\n * ```typescript\n * // Before: manual cast required\n * const grid = document.querySelector('tbw-grid') as DataGridElement<Employee>;\n *\n * // After: fully typed\n * const grid = queryGrid<Employee>('#my-grid');\n * if (grid) {\n * grid.rows = employees; // ✓ Typed!\n * }\n * ```\n *\n * @param selector - CSS selector to find the grid element\n * @param parent - Parent node to search within (defaults to document)\n * @returns The typed grid element or null if not found\n */\nexport function queryGrid<TRow = unknown>(\n selector: string,\n parent: ParentNode = document,\n): DataGridElement<TRow> | null {\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} 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 DataGridCustomEvent,\n DataGridElement as DataGridElementInterface,\n DataGridEventMap,\n ExpandCollapseAnimation,\n ExternalMountEditorDetail,\n ExternalMountViewDetail,\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 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 EventDefinition,\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// #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"],"names":["createAriaState","updateAriaCounts","state","rowsBodyEl","bodyEl","rowCount","colCount","prevRowCount","getEffectiveAriaLabel","config","shellState","explicitLabel","updateAriaLabels","updated","ariaLabel","ariaDescribedBy","FitModeEnum","DEFAULT_ANIMATION_CONFIG","DEFAULT_FILTER_ICON","DEFAULT_GRID_ICONS","parseLightDomColumns","host","el","field","rawType","type","header","sortable","editable","widthAttr","numericWidth","minWidthAttr","numericMinWidth","editorName","rendererName","optionsAttr","item","value","label","viewTpl","editorTpl","headerTpl","adapters","viewTarget","viewAdapter","a","renderer","editorTarget","editorAdapter","editor","c","mergeColumns","programmatic","dom","domMap","existing","cRenderer","existingRenderer","merged","d","m","dRenderer","mRenderer","addPart","token","autoSizeColumns","grid","mode","headerCells","changed","col","i","headerCell","max","rowEl","cell","w","updateTemplate","min","inferType","inferColumns","rows","provided","sample","columns","k","v","typeMap","EXPR_RE","EMPTY_SENTINEL","SAFE_EXPR","FORBIDDEN","escapeHtml","text","DANGEROUS_TAGS","DANGEROUS_ATTR_PATTERN","URL_ATTRS","DANGEROUS_URL_PROTOCOL","sanitizeHTML","html","template","sanitizeNode","root","toRemove","elements","tagName","attr","attrsToRemove","attrName","name","evalTemplateString","raw","ctx","parts","evaluated","_m","expr","res","evalSingle","finalStr","postProcess","allEmpty","p","REFLECTIVE_RE","key","dotChain","out","str","s","finalCellScrub","n","compileTemplate","forceBlank","fn","STATE_CHANGE_DEBOUNCE_MS","ConfigManager","#gridConfig","#columns","#fitMode","#lightDomColumnsCache","#originalColumnNodes","#originalConfig","#effectiveConfig","#sourcesChanged","#changeListeners","#lightDomObserver","#stateChangeTimeoutId","#initialColumnState","#callbacks","#lightDomTitle","callbacks","hasColumns","base","#collectAllSources","#cloneConfig","#applyPostMergeOperations","clone","h","#applyTypeDefaultsToColumns","typeDefaults","typeDefault","configColumns","domCols","#mergeShellConfig","shellLightDomTitle","lightDomHeaderContent","toolPanelsMap","panels","b","headerContentsMap","contents","toolbarContentsMap","apiContents","originalConfigContents","configIds","mergedContents","content","plugins","sortStates","#getSortState","index","internalCol","sortState","plugin","pluginState","allColumns","stateMap","updatedColumns","orderA","orderB","sortedByPriority","primarySort","colState","sortMap","visible","allCols","order","columnMap","reordered","#lightDomHandlers","callback","pendingCallbacks","debounceTimer","processPendingCallbacks","mutations","mutation","node","cb","isDevelopment","hostname","booleanCellHTML","formatDateValue","getRowIndexFromCell","parent","getColIndexFromCell","clearCellFocus","getDirection","element","isRTL","resolveRenderer","columnRenderer","adapter","appDefault","resolveFormat","FOCUSABLE_EDITOR_SELECTOR","hasEditingCells","clearEditingState","cellTemplate","rowTemplate","createCellFromTemplate","createRowFromTemplate","invalidateCellCache","renderVisibleRows","start","end","epoch","renderRowHook","needed","colLen","headerRowCount","hasRenderRowPlugins","hasRowHook","rowIndex","rowData","rowEpoch","prevRef","cellCount","structureValid","dataRefChanged","needsExternalRebuild","hasEditing","isActivelyEditedRow","renderInlineRow","fastPatchRow","isChanged","changedRowIds","rowId","hasChangedClass","rowClassFn","prevClasses","cls","newClasses","validClasses","e","children","colsLen","childLen","minLen","focusRow","focusCol","hasCellHook","hasSpecialCols","rowIndexStr","shouldHaveFocus","hasFocus","cellClassFn","cellClasses","cellRenderer","renderedValue","produced","displayStr","formatFn","formatted","gridEl","fragment","colIndex","compiled","tplHolder","viewRenderer","externalView","needsSanitization","spec","placeholder","context","output","blocked","rawTpl","textContent","cellValue","handleRowClick","firstCell","cellEl","focusChanged","ensureCellVisible","handleGridKeyDown","maxRow","maxCol","editing","colType","path","target","isFormField","tag","column","row","detail","activateEvent","legacyEvent","options","rowHeight","container","viewportEl","scrollEl","visibleHeight","y","isEditing","vStart","vEnd","scrollArea","offsets","cellRect","scrollAreaRect","cellLeft","cellRight","visibleLeft","visibleRight","focusTarget","dragState","handleCellMousedown","buildCellMouseEvent","renderRoot","elAtPoint","headerEl","handleMouseDown","event","handleMouseMove","handleMouseUp","setupCellEventDelegation","signal","setupRootEventDelegation","gridElement","defaultComparator","builtInSort","comparator","direction","rA","rB","finalizeSortResult","sortedRows","dir","r","renderHeader","toggleSort","applySort","result","isColumnSortable","isColumnResizable","setIcon","icon","createSortIndicator","active","icons","iconValue","createResizeHandle","handle","setupSortHandlers","appendRendererOutput","headerRow","headerValue","sortDirection","span","hasIdleCallback","scheduleIdle","cancelIdle","createDefaultSpinner","size","spinner","createLoadingContent","wrapper","createLoadingOverlay","overlay","showLoadingOverlay","gridRoot","overlayEl","hideLoadingOverlay","setRowLoadingState","loading","setCellLoadingState","RenderPhase","RenderScheduler","#pendingPhase","#rafHandle","#readyPromise","#readyResolve","#initialReadyResolver","#initialReadyFired","phase","_source","#ensureReadyPromise","#flush","resolver","resolve","createResizeController","resizeState","pendingRaf","prevCursor","prevUserSelect","onMove","delta","width","justFinishedResize","onUp","hadResize","colWidth","startWidth","ANIMATION_ATTR","DURATION_PROPS","DEFAULT_DURATIONS","parseDuration","trimmed","getAnimationDuration","animationType","prop","computed","parsed","animateRowElement","onComplete","duration","animateRow","animateRows","rowIndices","animatedCount","animateRowById","getRowId","createElement","attrs","div","className","button","gridContentTemplate","cloneGridContent","buildGridDOM","contentWrapper","buildShellHeader","titleEl","toolbar","btn","toggleBtn","buildShellBody","body","hasPanel","isSinglePanel","gridContent","panelEl","resizeHandlePosition","panelContent","accordion","panel","sectionClasses","section","headerBtn","iconSpan","titleSpan","chevronSpan","iconToString","createShellState","shouldRenderShellHeader","renderShellHeader","toolPanelIcon","title","hasTitle","iconStr","configContents","stateContents","allContents","hasCustomContent","hasPanels","showSeparator","sortedContents","toolbarHtml","isOpen","parseLightDomShell","headerContents","parseLightDomToolButtons","rendererFactory","toolButtonsContainer","id","contentDef","parseLightDomToolPanels","tooltip","render","adapterRenderer","existingPanel","cleanup","setupShellEventListeners","sectionId","setupToolPanelResize","onResize","shellBody","position","minWidth","startX","maxWidth","isResizing","onMouseMove","newWidth","onMouseUp","finalWidth","onMouseDown","renderCustomToolbarContents","slot","renderHeaderContent","hasLightDomContent","hasPluginContent","contentArea","existingCleanup","renderPanelContent","expandIcon","collapseIcon","panelId","isExpanded","chevron","updateToolbarActiveStates","panelToggle","updatePanelState","prepareForRerender","cleanupShellState","createShellController","initialized","controller","firstPanel","shadow","updateAccordionSectionState","otherId","otherPanel","contentEl","renderAccordionSectionContent","contentId","expanded","buildGridDOMIntoElement","shellConfig","runtimeState","hasShell","lightDomElements","lightDomSelectors","selector","sortedPanels","headerOptions","bodyOptions","shellHeader","STYLE_ELEMENT_ID","baseStyles","pluginStylesMap","getStyleElement","styleEl","updateStyleElement","pluginStyles","addPluginStyles","hasNewStyles","styles","extractGridCssFromDocument","stylesheet","cssText","rule","err","injectStyles","inlineStyles","gridCssText","createTouchScrollState","resetTouchState","cancelMomentum","handleTouchStart","touch","handleTouchMove","currentY","currentX","now","deltaY","deltaX","dt","scrollTop","scrollHeight","clientHeight","maxScrollY","canScrollVertically","canScrollHorizontally","scrollLeft","scrollWidth","clientWidth","maxScrollX","handleTouchEnd","startMomentumScroll","animate","scrollY","scrollX","setupTouchScrollListeners","gridContentEl","KNOWN_COLUMN_PROPERTIES","KNOWN_CONFIG_PROPERTIES","toKebabCase","getImportHint","pluginName","capitalize","hasPlugin","validatePluginProperties","columnProps","configProps","missingPlugins","addError","description","importHint","isConfigProperty","entry","def","errors","fields","fieldList","validatePluginConfigRules","warnings","manifest","pluginConfig","warning","validatePluginDependencies","loadedPlugins","dependencies","dep","requiredPlugin","required","reason","reasonText","validatePluginIncompatibilities","pluginNames","warned","incompatibility","getRowCacheKey","getCachedHeight","cache","setCachedHeight","height","rebuildPositionCache","heightCache","estimatedHeight","getPluginHeight","offset","measured","updateRowHeight","newHeight","heightDiff","getTotalHeight","last","getRowIndexAtOffset","targetOffset","low","high","mid","entryEnd","calculateAverageHeight","defaultHeight","totalHeight","measuredCount","countMeasuredRows","count","measureRenderedRowHeights","rowElements","positionCache","hasChanges","pluginHeight","currentEntry","measuredHeight","averageHeight","computeAverageExcludingPluginRows","totalMeasured","PluginManager","existingPlugin","queryDef","handlers","PluginClass","hasOldHooks","hasNewHook","queryType","otherPlugin","total","beforeRowIndex","adjustedStart","pluginStart","query","responses","response","eventType","listeners","error","focusedCell","left","right","skipScroll","gridStyles","DataGridElement","#instanceCounter","#renderRoot","#initialized","#rows","#configManager","#connected","#pendingUpdate","#pendingUpdateFlags","#scheduler","#scrollRaf","#pendingScrollTop","#hasScrollPlugins","#needsRowHeightMeasurement","#scrollMeasureTimeout","#renderRowHook","#touchState","#eventAbortController","#resizeObserver","#rowHeightObserver","#idleCallbackHandle","#pooledScrollEvent","#pluginManager","#lastPluginsArray","#eventListenersAdded","#scrollAbortController","#scrollAreaEl","#shellState","#shellController","#resizeCleanup","#loading","#loadingRows","#loadingCells","#loadingOverlayEl","#rowIdMap","#baseColumns","oldValue","#queueUpdate","wasLoading","#updateLoadingOverlay","#updateRowLoadingState","cellFields","#updateCellLoadingState","#injectStyles","#updatePluginConfigs","#updateAriaLabels","#processColumns","#rebuildRowModel","newTotalHeight","#calculateTotalSpacerHeight","#rowHeightObserverSetup","#setupRowHeightObserver","#measureRowHeightForPlugins","eventName","#emit","#setup","#applyAnimationConfig","#initializePlugins","pluginsConfig","#injectAllPluginStyles","newPlugins","isLightDom","isApiRegistered","#configureVariableHeights","#collectPluginShellContributions","hadScrollPlugins","#setupScrollListeners","#destroyPlugins","pluginPanels","pluginContents","#getToolPanelRendererFactory","instanceAdapter","#parseLightDom","#render","#afterConnect","#setupLightDomHandlers","#customStyleSheets","newValue","isLoading","defaultOpen","#updateAriaSelection","userRowHeight","hasRowHeightPlugin","#initializePositionCache","#measureRowHeight","firstRow","cells","maxCellHeight","rowRect","scrollSignal","fauxScrollbar","rowsEl","currentScrollTop","rawStart","startRowOffset","evenAlignedStart","subPixelOffset","#onScrollBatched","scrollEvent","scrollAreaForWheel","isHorizontal","#updateCachedGeometry","newFocus","listener","rowIdx","isActiveRow","colIdx","#flushPendingUpdates","flags","#applyGridConfigUpdate","#applyRowsUpdate","#applyColumnsUpdate","#applyFitModeUpdate","#rebuildRowIdMap","#tryResolveRowId","#resolveRowIdOrThrow","hadShell","hadToolPanel","accordionSectionsBefore","nowNeedsShell","nowHasToolPanels","toolPanelCount","#updateShellHeaderInPlace","sourceColumns","visibleCols","hiddenCols","processedColumns","processedFields","originalRows","processedRows","gridConfig","enabled","#renderVisibleRows","#ariaState","#updateAriaCounts","#measureRenderedRowHeights","cellClickEvent","handled","rowClickEvent","headerClickEvent","changes","source","changedFields","updates","anyChanged","#applyColumnState","#pendingShellRefresh","#afterShellRefresh","css","sheet","#updateAdoptedStyleSheets","customSheets","existingSheets","#replaceShellHeaderElement","newHeaderHtml","temp","newHeader","#setupShellListeners","handleShellChange","hadTitle","hadToolButtons","hasToolButtons","handleColumnChange","scrollAreaEl","totalRows","forceRead","virt","fauxScrollHeight","viewportHeight","scrollAreaHeight","viewportHeightDiff","hScrollbarPadding","rowContentHeight","pluginExtraHeight","rowHeightFn","rowIdFn","stats","force","skipAfterRender","iterations","maxIterations","extraHeightBefore","pluginAdjustedStart","targetHeight","accumulatedHeight","minRows","visibleCount","prevStart","prevEnd","extraHeightBeforeStart","PLUGIN_QUERIES","BaseGridPlugin","#abortController","userIcons","durationStr","iconKey","pluginOverride","message","GridClasses","GridDataAttrs","GridSelectors","GridCSSVars","createGrid","queryGrid","DGEvents","PluginEvents","builtInAggregators","acc","sum","customAggregators","aggregatorRegistry","ref","builtInValueAggregators","vals","getValueAggregator","aggFunc","runValueAggregator","values","registerAggregator","unregisterAggregator","getAggregator","runAggregator","listAggregators"],"mappings":"gOA+BO,SAASA,GAA6B,CAC3C,MAAO,CACL,SAAU,GACV,SAAU,GACV,UAAW,OACX,gBAAiB,MAAA,CAErB,CAiBO,SAASC,GACdC,EACAC,EACAC,EACAC,EACAC,EACS,CAET,GAAID,IAAaH,EAAM,UAAYI,IAAaJ,EAAM,SACpD,MAAO,GAGT,MAAMK,EAAeL,EAAM,SAC3B,OAAAA,EAAM,SAAWG,EACjBH,EAAM,SAAWI,EAGbH,IACFA,EAAW,aAAa,gBAAiB,OAAOE,CAAQ,CAAC,EACzDF,EAAW,aAAa,gBAAiB,OAAOG,CAAQ,CAAC,GAIvDD,IAAaE,GAAgBH,IAC3BC,EAAW,EACbD,EAAO,aAAa,OAAQ,UAAU,EAEtCA,EAAO,gBAAgB,MAAM,GAI1B,EACT,CAcO,SAASI,GACdC,EACAC,EACoB,CACpB,MAAMC,EAAgBF,GAAQ,cAC9B,OAAIE,IAEeF,GAAQ,OAAO,QAAQ,OAASC,GAAY,eAC1C,OACvB,CAYO,SAASE,GACdV,EACAC,EACAM,EACAC,EACS,CACT,GAAI,CAACP,EAAY,MAAO,GAExB,IAAIU,EAAU,GAGd,MAAMC,EAAYN,GAAsBC,EAAQC,CAAU,EAGtDI,IAAcZ,EAAM,YACtBA,EAAM,UAAYY,EACdA,EACFX,EAAW,aAAa,aAAcW,CAAS,EAE/CX,EAAW,gBAAgB,YAAY,EAEzCU,EAAU,IAIZ,MAAME,EAAkBN,GAAQ,oBAChC,OAAIM,IAAoBb,EAAM,kBAC5BA,EAAM,gBAAkBa,EACpBA,EACFZ,EAAW,aAAa,mBAAoBY,CAAe,EAE3DZ,EAAW,gBAAgB,kBAAkB,EAE/CU,EAAU,IAGLA,CACT,CCw6CO,MAAMG,EAAc,CACzB,QAAS,UACT,MAAO,OACT,EA4xBaC,GAAoE,CAC/E,KAAM,iBACN,SAAU,IACV,OAAQ,UACV,EA4DMC,GACJ,iRAGWC,EAA0C,CACrD,OAAQ,IACR,SAAU,IACV,QAAS,IACT,SAAU,IACV,SAAU,IACV,aAAc,IACd,WAAY,KACZ,UAAW,IACX,OAAQD,GACR,aAAcA,GACd,MAAO,KACT,ECr6EO,SAASE,GAAqBC,EAAqC,CAExE,OADmB,MAAM,KAAKA,EAAK,iBAAiB,iBAAiB,CAAC,EAEnE,IAAKC,GAAO,CACX,MAAMC,EAAQD,EAAG,aAAa,OAAO,GAAK,GAC1C,GAAI,CAACC,EAAO,OAAO,KACnB,MAAMC,EAAUF,EAAG,aAAa,MAAM,GAAK,OAErCG,EACJD,OAFuB,IAAyB,CAAC,SAAU,SAAU,OAAQ,UAAW,QAAQ,CAAC,EAEzE,IAAIA,CAA8B,EAAKA,EAAkC,OAC7FE,EAASJ,EAAG,aAAa,QAAQ,GAAK,OACtCK,EAAWL,EAAG,aAAa,UAAU,EACrCM,EAAWN,EAAG,aAAa,UAAU,EACrCb,EAAyB,CAAE,MAAAc,EAAO,KAAAE,EAAM,OAAAC,EAAQ,SAAAC,EAAU,SAAAC,CAAA,EAG1DC,EAAYP,EAAG,aAAa,OAAO,EACzC,GAAIO,EAAW,CACb,MAAMC,EAAe,WAAWD,CAAS,EACrC,CAAC,MAAMC,CAAY,GAAK,gBAAgB,KAAKD,EAAU,KAAA,CAAM,EAC/DpB,EAAO,MAAQqB,EAEfrB,EAAO,MAAQoB,CAEnB,CAGA,MAAME,EAAeT,EAAG,aAAa,UAAU,GAAKA,EAAG,aAAa,WAAW,EAC/E,GAAIS,EAAc,CAChB,MAAMC,EAAkB,WAAWD,CAAY,EAC1C,MAAMC,CAAe,IACxBvB,EAAO,SAAWuB,EAEtB,CAEIV,EAAG,aAAa,WAAW,MAAU,UAAY,IACjDA,EAAG,aAAa,SAAS,MAAU,UAAY,IAGnD,MAAMW,EAAaX,EAAG,aAAa,QAAQ,EACrCY,EAAeZ,EAAG,aAAa,UAAU,EAC3CW,MAAmB,aAAeA,GAClCC,MAAqB,eAAiBA,GAG1C,MAAMC,EAAcb,EAAG,aAAa,SAAS,EACzCa,IACF1B,EAAO,QAAU0B,EAAY,MAAM,GAAG,EAAE,IAAKC,GAAS,CACpD,KAAM,CAACC,EAAOC,CAAK,EAAIF,EAAK,SAAS,GAAG,EAAIA,EAAK,MAAM,GAAG,EAAI,CAACA,EAAK,OAAQA,EAAK,MAAM,EACvF,MAAO,CAAE,MAAOC,EAAM,OAAQ,MAAOC,GAAO,KAAA,GAAUD,EAAM,MAAK,CACnE,CAAC,GAEH,MAAME,EAAUjB,EAAG,cAAc,sBAAsB,EACjDkB,EAAYlB,EAAG,cAAc,wBAAwB,EACrDmB,EAAYnB,EAAG,cAAc,wBAAwB,EACvDiB,MAAgB,eAAiBA,GACjCC,MAAkB,iBAAmBA,GACrCC,MAAkB,iBAAmBA,GAKzC,MAAMC,EAD2B,WAA0D,iBACjD,cAAA,GAAmB,CAAA,EAGvDC,EAAcJ,GAAWjB,EACzBsB,EAAcF,EAAS,KAAMG,GAAMA,EAAE,UAAUF,CAAU,CAAC,EAChE,GAAIC,EAAa,CAGf,MAAME,EAAWF,EAAY,eAAeD,CAAU,EAClDG,IACFrC,EAAO,aAAeqC,EAE1B,CAGA,MAAMC,EAAgBP,GAAalB,EAC7B0B,EAAgBN,EAAS,KAAMG,GAAMA,EAAE,UAAUE,CAAY,CAAC,EACpE,GAAIC,EAAe,CAEjB,MAAMC,EAASD,EAAc,aAAaD,CAAY,EAClDE,IACFxC,EAAO,OAASwC,EAEpB,CAEA,OAAOxC,CACT,CAAC,EACA,OAAQyC,GAA2B,CAAC,CAACA,CAAC,CAC3C,CAWO,SAASC,GACdC,EACAC,EACkB,CAClB,IAAK,CAACD,GAAgB,CAACA,EAAa,UAAY,CAACC,GAAO,CAACA,EAAI,QAAS,MAAO,CAAA,EAC7E,GAAI,CAACD,GAAgB,CAACA,EAAa,OAAQ,OAAQC,GAAO,CAAA,EAC1D,GAAI,CAACA,GAAO,CAACA,EAAI,OAAQ,OAAOD,EAIhC,MAAME,EAAyC,CAAA,EAC9CD,EAAyB,QAASH,GAAM,CACvC,MAAMK,EAAWD,EAAOJ,EAAE,KAAK,EAC/B,GAAIK,EAAU,CAERL,EAAE,QAAU,CAACK,EAAS,SAAQA,EAAS,OAASL,EAAE,QAClDA,EAAE,MAAQ,CAACK,EAAS,OAAMA,EAAS,KAAOL,EAAE,MAC5CA,EAAE,WAAUK,EAAS,SAAW,IAChCL,EAAE,WAAUK,EAAS,SAAW,IAChCL,EAAE,YAAWK,EAAS,UAAY,IAClCL,EAAE,OAAS,MAAQK,EAAS,OAAS,OAAMA,EAAS,MAAQL,EAAE,OAC9DA,EAAE,UAAY,MAAQK,EAAS,UAAY,OAAMA,EAAS,SAAWL,EAAE,UACvEA,EAAE,iBAAgBK,EAAS,eAAiBL,EAAE,gBAC9CA,EAAE,mBAAkBK,EAAS,iBAAmBL,EAAE,kBAClDA,EAAE,mBAAkBK,EAAS,iBAAmBL,EAAE,kBAEtD,MAAMM,EAAYN,EAAE,UAAYA,EAAE,aAC5BO,EAAmBF,EAAS,UAAYA,EAAS,aACnDC,GAAa,CAACC,IAChBF,EAAS,aAAeC,EACpBN,EAAE,WAAUK,EAAS,SAAWC,IAElCN,EAAE,QAAU,CAACK,EAAS,SAAQA,EAAS,OAASL,EAAE,OACxD,MACEI,EAAOJ,EAAE,KAAK,EAAI,CAAE,GAAGA,CAAA,CAE3B,CAAC,EAED,MAAMQ,EAA4BN,EAAkC,IAAKF,GAAM,CAC7E,MAAMS,EAAIL,EAAOJ,EAAE,KAAK,EACxB,GAAI,CAACS,EAAG,OAAOT,EACf,MAAMU,EAAoB,CAAE,GAAGV,CAAA,EAC3BS,EAAE,QAAU,CAACC,EAAE,SAAQA,EAAE,OAASD,EAAE,QACpCA,EAAE,MAAQ,CAACC,EAAE,OAAMA,EAAE,KAAOD,EAAE,MAClCC,EAAE,SAAWV,EAAE,UAAYS,EAAE,UACzBT,EAAE,YAAc,IAAQS,EAAE,YAAc,QAAQ,UAAY,IAChEC,EAAE,SAAWV,EAAE,UAAYS,EAAE,SAEzBA,EAAE,OAAS,MAAQC,EAAE,OAAS,OAAMA,EAAE,MAAQD,EAAE,OAChDA,EAAE,UAAY,MAAQC,EAAE,UAAY,OAAMA,EAAE,SAAWD,EAAE,UACzDA,EAAE,iBAAgBC,EAAE,eAAiBD,EAAE,gBACvCA,EAAE,mBAAkBC,EAAE,iBAAmBD,EAAE,kBAC3CA,EAAE,mBAAkBC,EAAE,iBAAmBD,EAAE,kBAE/C,MAAME,EAAYF,EAAE,UAAYA,EAAE,aAC5BG,EAAYF,EAAE,UAAYA,EAAE,aAClC,OAAIC,GAAa,CAACC,IAChBF,EAAE,aAAeC,EACbF,EAAE,WAAUC,EAAE,SAAWC,IAE3BF,EAAE,QAAU,CAACC,EAAE,SAAQA,EAAE,OAASD,EAAE,QACxC,OAAOL,EAAOJ,EAAE,KAAK,EACdU,CACT,CAAC,EACD,cAAO,KAAKN,CAAM,EAAE,QAAS/B,GAAUmC,EAAO,KAAKJ,EAAO/B,CAAK,CAAC,CAAC,EAC1DmC,CACT,CAQO,SAASK,GAAQzC,EAAiB0C,EAAqB,CAC5D,GAAI,CACD1C,EAAuB,MAAM,MAAM0C,CAAK,CAC3C,MAAQ,CAER,CACA,MAAMT,EAAWjC,EAAG,aAAa,MAAM,EAClCiC,EACKA,EAAS,MAAM,KAAK,EAAE,SAASS,CAAK,GAAG1C,EAAG,aAAa,OAAQiC,EAAW,IAAMS,CAAK,EADhF1C,EAAG,aAAa,OAAQ0C,CAAK,CAE9C,CAQO,SAASC,GAAgBC,EAA0B,CACxD,MAAMC,EAAOD,EAAK,iBAAiB,SAAWA,EAAK,SAAWlD,EAAY,QAI1E,GAFImD,IAASnD,EAAY,SAAWmD,IAASnD,EAAY,OACrDkD,EAAK,sBACL,CAAEA,EAAgC,YAAa,OACnD,MAAME,EAAc,MAAM,KAAKF,EAAK,cAAc,UAAY,EAAE,EAChE,GAAI,CAACE,EAAY,OAAQ,OACzB,IAAIC,EAAU,GACdH,EAAK,gBAAgB,QAAQ,CAACI,EAAqBC,IAAc,CAC/D,GAAID,EAAI,MAAO,OACf,MAAME,EAAaJ,EAAYG,CAAC,EAChC,IAAIE,EAAMD,EAAaA,EAAW,YAAc,EAChD,UAAWE,KAASR,EAAK,SAAU,CACjC,MAAMS,EAAOD,EAAM,SAASH,CAAC,EAC7B,GAAII,EAAM,CACR,MAAMC,EAAID,EAAK,YACXC,EAAIH,IAAKA,EAAMG,EACrB,CACF,CACIH,EAAM,IACRH,EAAI,MAAQG,EAAM,EAClBH,EAAI,YAAc,GAClBD,EAAU,GAEd,CAAC,EACGA,KAAwBH,CAAI,EAChCA,EAAK,qBAAuB,EAC9B,CASO,SAASW,EAAeX,EAA0B,EAO1CA,EAAK,iBAAiB,SAAWA,EAAK,SAAWlD,EAAY,WAE7DA,EAAY,QACvBkD,EAAK,cAAgBA,EAAK,gBACvB,IAAKhB,GAAsB,CAC1B,GAAIA,EAAE,MAAO,MAAO,GAAGA,EAAE,KAAK,KAE9B,MAAM4B,EAAM5B,EAAE,SACd,OAAO4B,GAAO,KAAO,UAAUA,CAAG,WAAa,KACjD,CAAC,EACA,KAAK,GAAG,EACR,KAAA,EAGHZ,EAAK,cAAgBA,EAAK,gBACvB,IAAKhB,GAAuBA,EAAE,MAAQ,GAAGA,EAAE,KAAK,KAAO,aAAc,EACrE,KAAK,GAAG,EAEZgB,EAAgC,MAAM,YAAY,wBAAyBA,EAAK,aAAa,CAChG,CC5QA,SAASa,GAAU1C,EAAiC,CAClD,OAAIA,GAAS,KAAa,SACtB,OAAOA,GAAU,SAAiB,SAClC,OAAOA,GAAU,UAAkB,UACnCA,aAAiB,MACjB,OAAOA,GAAU,UAAY,oBAAoB,KAAKA,CAAK,GAAK,CAAC,MAAM,KAAK,MAAMA,CAAK,CAAC,EAAU,OAC/F,QACT,CAKO,SAAS2C,GACdC,EACAC,EAC4B,CAQ5B,MAAMC,EAASF,EAAK,CAAC,GAAM,CAAA,EACrBG,EAAiC,OAAO,KAAKD,CAAM,EAAE,IAAKE,GAAM,CACpE,MAAMC,EAAKH,EAAmCE,CAAC,EACzC5D,EAAOsD,GAAUO,CAAC,EACxB,MAAO,CAAE,MAAOD,EAA0B,OAAQA,EAAE,OAAO,CAAC,EAAE,YAAA,EAAgBA,EAAE,MAAM,CAAC,EAAG,KAAA5D,CAAA,CAC5F,CAAC,EACK8D,EAAsC,CAAA,EAC5C,OAAAH,EAAQ,QAASlC,GAAM,CACrBqC,EAAQrC,EAAE,KAAK,EAAIA,EAAE,MAAQ,QAC/B,CAAC,EACM,CAAE,QAAAkC,EAAS,QAAAG,CAAA,CACpB,CChCA,MAAMC,GAAU,qBACVC,EAAiB,eACjBC,GAAY,+BACZC,GACJ,2SAYK,SAASC,GAAWC,EAAsB,CAC/C,MAAI,CAACA,GAAQ,OAAOA,GAAS,SAAiB,GACvCA,EACJ,QAAQ,KAAM,OAAO,EACrB,QAAQ,KAAM,MAAM,EACpB,QAAQ,KAAM,MAAM,EACpB,QAAQ,KAAM,QAAQ,EACtB,QAAQ,KAAM,OAAO,CAC1B,CAMA,MAAMC,OAAqB,IAAI,CAC7B,SACA,SACA,SACA,QACA,OACA,QACA,SACA,WACA,SACA,OACA,OACA,OACA,QACA,WACA,OACA,SACA,QACA,WACA,SACA,WACA,UACA,YACA,MACA,SACF,CAAC,EAKKC,GAAyB,WAKzBC,GAAY,IAAI,IAAI,CAAC,OAAQ,MAAO,SAAU,aAAc,OAAQ,SAAU,aAAc,SAAU,QAAQ,CAAC,EAK/GC,GAAyB,wCASxB,SAASC,EAAaC,EAAsB,CACjD,GAAI,CAACA,GAAQ,OAAOA,GAAS,SAAU,MAAO,GAG9C,GAAIA,EAAK,QAAQ,GAAG,IAAM,GAAI,OAAOA,EAErC,MAAMC,EAAW,SAAS,cAAc,UAAU,EAClD,OAAAA,EAAS,UAAYD,EAErBE,GAAaD,EAAS,OAAO,EAEtBA,EAAS,SAClB,CAKA,SAASC,GAAaC,EAAwC,CAC5D,MAAMC,EAAsB,CAAA,EAGtBC,EAAWF,EAAK,iBAAiB,GAAG,EAE1C,UAAWhF,KAAMkF,EAAU,CACzB,MAAMC,EAAUnF,EAAG,QAAQ,YAAA,EAG3B,GAAIwE,GAAe,IAAIW,CAAO,EAAG,CAC/BF,EAAS,KAAKjF,CAAE,EAChB,QACF,CAGA,IAAImF,IAAY,OAASnF,EAAG,eAAiB,+BAEf,MAAM,KAAKA,EAAG,UAAU,EAAE,KACnDoF,GAASX,GAAuB,KAAKW,EAAK,IAAI,GAAKA,EAAK,OAAS,QAAUA,EAAK,OAAS,YAAA,EAEnE,CACvBH,EAAS,KAAKjF,CAAE,EAChB,QACF,CAIF,MAAMqF,EAA0B,CAAA,EAChC,UAAWD,KAAQpF,EAAG,WAAY,CAChC,MAAMsF,EAAWF,EAAK,KAAK,YAAA,EAG3B,GAAIX,GAAuB,KAAKa,CAAQ,EAAG,CACzCD,EAAc,KAAKD,EAAK,IAAI,EAC5B,QACF,CAGA,GAAIV,GAAU,IAAIY,CAAQ,GAAKX,GAAuB,KAAKS,EAAK,KAAK,EAAG,CACtEC,EAAc,KAAKD,EAAK,IAAI,EAC5B,QACF,CAGA,GAAIE,IAAa,SAAW,4CAA4C,KAAKF,EAAK,KAAK,EAAG,CACxFC,EAAc,KAAKD,EAAK,IAAI,EAC5B,QACF,CACF,CAEAC,EAAc,QAASE,GAASvF,EAAG,gBAAgBuF,CAAI,CAAC,CAC1D,CAGAN,EAAS,QAASjF,GAAOA,EAAG,QAAQ,CACtC,CAKO,SAASwF,GAAmBC,EAAaC,EAA0B,CACxE,GAAI,CAACD,GAAOA,EAAI,QAAQ,IAAI,IAAM,GAAI,OAAOA,EAC7C,MAAME,EAA4C,CAAA,EAC5CC,EAAYH,EAAI,QAAQvB,GAAS,CAAC2B,EAAIC,IAAS,CACnD,MAAMC,EAAMC,GAAWF,EAAMJ,CAAG,EAChC,OAAAC,EAAM,KAAK,CAAE,KAAMG,EAAK,OAAQ,OAAQC,EAAK,EACtCA,CACT,CAAC,EACKE,EAAWC,GAAYN,CAAS,EAIhCO,EAAWR,EAAM,QAAUA,EAAM,MAAOS,GAAMA,EAAE,SAAW,IAAMA,EAAE,SAAWjC,CAAc,EAElG,OADqBkC,EAAc,KAAKZ,CAAG,GACvBU,EAAiB,GAC9BF,CACT,CAEA,SAASD,GAAWF,EAAcJ,EAA0B,CAG1D,GAFAI,GAAQA,GAAQ,IAAI,KAAA,EAChB,CAACA,GACDO,EAAc,KAAKP,CAAI,EAAG,OAAO3B,EACrC,GAAI2B,IAAS,QAAS,OAAOJ,EAAI,OAAS,KAAOvB,EAAiB,OAAOuB,EAAI,KAAK,EAClF,GAAII,EAAK,WAAW,MAAM,GAAK,CAAC,QAAQ,KAAKA,CAAI,GAAK,CAACA,EAAK,SAAS,GAAG,EAAG,CACzE,MAAMQ,EAAMR,EAAK,MAAM,CAAC,EAClB9B,EAAI0B,EAAI,IAAMA,EAAI,IAAIY,CAAG,EAAI,OACnC,OAAOtC,GAAK,KAAOG,EAAiB,OAAOH,CAAC,CAC9C,CAEA,GADI8B,EAAK,OAAS,IACd,CAAC1B,GAAU,KAAK0B,CAAI,GAAKzB,GAAU,KAAKyB,CAAI,EAAG,OAAO3B,EAC1D,MAAMoC,EAAWT,EAAK,MAAM,KAAK,EACjC,GAAIS,GAAYA,EAAS,OAAS,EAAG,OAAOpC,EAC5C,GAAI,CAEF,MAAMqC,EADK,IAAI,SAAS,QAAS,MAAO,WAAWV,CAAI,IAAI,EAC5CJ,EAAI,MAAOA,EAAI,GAAG,EAC3Be,EAAMD,GAAO,KAAO,GAAK,OAAOA,CAAG,EACzC,OAAIH,EAAc,KAAKI,CAAG,EAAUtC,EAC7BsC,GAAOtC,CAChB,MAAQ,CACN,OAAOA,CACT,CACF,CAKA,MAAMkC,EAAgB,wBAEtB,SAASH,GAAYQ,EAAmB,CACtC,OAAKA,GACEA,EAAE,QAAQ,IAAI,OAAOvC,EAAgB,GAAG,EAAG,EAAE,EAAE,QAAQ,kDAAmD,EAAE,CACrH,CAEO,SAASwC,GAAetD,EAAyB,CACtD,GAAKgD,EAAc,KAAKhD,EAAK,aAAe,EAAE,EAE9C,WAAWuD,KAAKvD,EAAK,WACfuD,EAAE,WAAa,KAAK,WAAaP,EAAc,KAAKO,EAAE,aAAe,EAAE,IAAGA,EAAE,YAAc,IAG5FP,EAAc,KAAKhD,EAAK,aAAe,EAAE,IAC3CA,EAAK,YAAc,IAEvB,CAIO,SAASwD,GAAgBpB,EAAa,CAC3C,MAAMqB,EAAaT,EAAc,KAAKZ,CAAG,EACnCsB,GAAOrB,GACPoB,EAAmB,GACXtB,GAAmBC,EAAKC,CAAG,GAGzC,OAAAqB,EAAG,UAAYD,EACRC,CACT,CC/MA,MAAMC,GAA2B,IAuD1B,MAAMC,EAA2B,CAEtCC,GACAC,GACAC,GAGAC,GACAC,GASAC,GAAiC,CAAA,EAOjCC,GAAkC,CAAA,EAIlCC,GAAkB,GAClBC,GAAsC,CAAA,EACtCC,GACAC,GACAC,GACAC,GAGAC,GAEA,YAAYC,EAAsC,CAChD,KAAKF,GAAaE,CACpB,CAKA,IAAI,UAA0B,CAC5B,OAAO,KAAKT,EACd,CAGA,IAAI,WAA2B,CAC7B,OAAO,KAAKC,EACd,CAGA,IAAI,SAA+B,CACjC,OAAQ,KAAKA,GAAiB,SAAW,CAAA,CAC3C,CAGA,IAAI,QAAQzG,EAA4B,CACtC,KAAKyG,GAAiB,QAAUzG,CAClC,CAGA,IAAI,sBAAwD,CAC1D,OAAO,KAAKsG,EACd,CAGA,IAAI,qBAAqBtG,EAAwC,CAC/D,KAAKsG,GAAwBtG,CAC/B,CAGA,IAAI,qBAAiD,CACnD,OAAO,KAAKuG,EACd,CAGA,IAAI,oBAAoBvG,EAAkC,CACxD,KAAKuG,GAAuBvG,CAC9B,CAGA,IAAI,eAAoC,CACtC,OAAO,KAAKgH,EACd,CAGA,IAAI,cAAchH,EAA2B,CAC3C,KAAKgH,GAAiBhH,CACxB,CAGA,IAAI,oBAAkD,CACpD,OAAO,KAAK8G,EACd,CAGA,IAAI,mBAAmB9G,EAAoC,CACzD,KAAK8G,GAAsB9G,CAC7B,CAOA,IAAI,gBAA0B,CAC5B,OAAO,KAAK0G,EACd,CAOA,oBAA2B,CACzB,KAAKA,GAAkB,EACzB,CAKA,cAActI,EAAyC,CACrD,KAAK+H,GAAc/H,EACnB,KAAKsI,GAAkB,GAEvB,KAAKJ,GAAwB,MAC/B,CAGA,eAA2C,CACzC,OAAO,KAAKH,EACd,CAGA,WAAWpD,EAAmE,CAC5E,KAAKqD,GAAWrD,EAChB,KAAK2D,GAAkB,EACzB,CAGA,YAAiE,CAC/D,OAAO,KAAKN,EACd,CAGA,WAAWtE,EAAiC,CAC1C,KAAKuE,GAAWvE,EAChB,KAAK4E,GAAkB,EACzB,CAGA,YAAkC,CAChC,OAAO,KAAKL,EACd,CAmBA,OAAc,CAGZ,MAAMa,GAAc,KAAKT,GAAiB,SAAS,QAAU,GAAK,EAClE,GAAI,CAAC,KAAKC,IAAmBQ,EAC3B,OAIF,MAAMC,EAAO,KAAKC,GAAA,EAGlB,KAAKV,GAAkB,GAGvB,KAAKF,GAAkBW,EACvB,OAAO,OAAO,KAAKX,EAAe,EAC9B,KAAKA,GAAgB,SAGvB,OAAO,OAAO,KAAKA,GAAgB,OAAO,EAI5C,KAAKC,GAAmB,KAAKY,GAAa,KAAKb,EAAe,EAG9D,KAAKc,GAAA,CACP,CAMAD,GAAajJ,EAAsC,CAEjD,MAAMmJ,EAAuB,CAAE,GAAGnJ,CAAA,EAGlC,OAAIA,EAAO,UACTmJ,EAAM,QAAUnJ,EAAO,QAAQ,IAAK6D,IAAS,CAAE,GAAGA,CAAA,EAAM,GAItD7D,EAAO,QACTmJ,EAAM,MAAQ,CACZ,GAAGnJ,EAAO,MACV,OAAQA,EAAO,MAAM,OAAS,CAAE,GAAGA,EAAO,MAAM,MAAA,EAAW,OAC3D,UAAWA,EAAO,MAAM,UAAY,CAAE,GAAGA,EAAO,MAAM,SAAA,EAAc,OACpE,WAAYA,EAAO,MAAM,YAAY,IAAKiH,IAAO,CAAE,GAAGA,CAAA,EAAI,EAC1D,eAAgBjH,EAAO,MAAM,gBAAgB,IAAKoJ,IAAO,CAAE,GAAGA,GAAI,CAAA,GAI/DD,CACT,CAMAD,IAAkC,CAChC,MAAMlJ,EAAS,KAAKqI,GAIpB,KAAKgB,GAAA,EAID,OAAOrJ,EAAO,WAAc,UAAYA,EAAO,UAAY,GAC7D,KAAK2I,GAAW,aAAa3I,EAAO,SAAS,EAI3CA,EAAO,UAAY,SACL,KAAK,QACb,QAASyC,GAAM,CACjBA,EAAE,OAAS,OAAMA,EAAE,MAAQ,GACjC,CAAC,EAIH,KAAKkG,GAAW,qBAAqB3I,CAAM,CAC7C,CASAqJ,IAAoC,CAClC,MAAMC,EAAe,KAAKjB,GAAiB,aAC3C,GAAI,CAACiB,EAAc,OAEnB,MAAM3E,EAAU,KAAK,QACrB,UAAWd,KAAOc,EAAS,CACzB,GAAI,CAACd,EAAI,KAAM,SAEf,MAAM0F,EAAcD,EAAazF,EAAI,IAAI,EACpC0F,IAID,CAAC1F,EAAI,UAAY,CAACA,EAAI,cAAgB0F,EAAY,WAEpD1F,EAAI,SAAW0F,EAAY,UAIzB,CAAC1F,EAAI,QAAU0F,EAAY,SAE7B1F,EAAI,OAAS0F,EAAY,QAIvB,CAAC1F,EAAI,QAAU0F,EAAY,SAE7B1F,EAAI,OAAS0F,EAAY,QAIvB,CAAC1F,EAAI,cAAgB0F,EAAY,eACnC1F,EAAI,aAAe0F,EAAY,cAEnC,CACF,CAiBAP,IAAoC,CAClC,MAAMD,EAAsB,KAAKhB,GAAc,CAAE,GAAG,KAAKA,EAAA,EAAgB,CAAA,EACnEyB,EAAmC,MAAM,QAAQT,EAAK,OAAO,EAAI,CAAC,GAAGA,EAAK,OAAO,EAAI,CAAA,EAGrFU,GAA8B,KAAKvB,IAAyB,CAAA,GAAI,IAAKzF,IAAO,CAChF,GAAGA,CAAA,EACH,EAIF,IAAIkC,EAA+BjC,GACjC8G,EACAC,CAAA,EAIE,KAAKzB,IAAa,KAAKA,GAA+B,SAExDrD,EAAUjC,GACR,KAAKsF,GACLyB,CAAA,GAKJ,MAAMjF,EAAO,KAAKmE,GAAW,QAAA,EAC7B,OAAIhE,EAAQ,SAAW,GAAKH,EAAK,SAE/BG,EADeJ,GAAaC,CAAiC,EAC5C,SAGfG,EAAQ,SAEVA,EAAQ,QAASlC,GAAM,CACjBA,EAAE,WAAa,SAAWA,EAAE,SAAW,IACvCA,EAAE,YAAc,SAAWA,EAAE,UAAY,IACzCA,EAAE,kBAAoB,QAAa,OAAOA,EAAE,OAAU,WACxDA,EAAE,gBAAkBA,EAAE,MAE1B,CAAC,EAGDkC,EAAQ,QAASlC,GAAM,CACjBA,EAAE,gBAAkB,CAACA,EAAE,iBACzBA,EAAE,eAAiBiF,GAAiBjF,EAAE,eAA+B,SAAS,GAE5EA,EAAE,kBAAoB,CAACA,EAAE,mBAC3BA,EAAE,iBAAmBiF,GAAgBjF,EAAE,iBAAiB,SAAS,EAErE,CAAC,EAEDsG,EAAK,QAAUpE,GAIb,KAAKsD,KAAUc,EAAK,QAAU,KAAKd,IAClCc,EAAK,UAASA,EAAK,QAAU,WAMlC,KAAKW,GAAkBX,CAAI,EAGvBA,EAAK,aAAe,CAAC,KAAKL,KAC5B,KAAKA,GAAsBK,EAAK,aAG3BA,CACT,CASAW,GAAkBX,EAA2B,CAG3CA,EAAK,MAAQA,EAAK,MAAQ,CAAE,GAAGA,EAAK,KAAA,EAAU,CAAA,EAC9CA,EAAK,MAAM,OAASA,EAAK,MAAM,OAAS,CAAE,GAAGA,EAAK,MAAM,MAAA,EAAW,CAAA,EAGnE,MAAMY,EAAqB,KAAKhB,GAAW,sBAAA,EACvCgB,IACF,KAAKf,GAAiBe,GAEpB,KAAKf,IAAkB,CAACG,EAAK,MAAM,OAAO,QAC5CA,EAAK,MAAM,OAAO,MAAQ,KAAKH,IAIjC,MAAMgB,EAAwB,KAAKjB,GAAW,8BAAA,EAC1CiB,GAAuB,OAAS,IAClCb,EAAK,MAAM,OAAO,gBAAkBa,GAIlC,KAAKjB,GAAW,oCAClBI,EAAK,MAAM,OAAO,wBAA0B,IAI9C,MAAMc,EAAgB,KAAKlB,GAAW,mBAAA,EACtC,GAAIkB,EAAc,KAAO,EAAG,CAC1B,MAAMC,EAAS,MAAM,KAAKD,EAAc,QAAQ,EAEhDC,EAAO,KAAK,CAAC1H,EAAG2H,KAAO3H,EAAE,OAAS,MAAQ2H,EAAE,OAAS,IAAI,EACzDhB,EAAK,MAAM,WAAae,CAC1B,CAGA,MAAME,EAAoB,KAAKrB,GAAW,uBAAA,EAC1C,GAAIqB,EAAkB,KAAO,EAAG,CAC9B,MAAMC,EAAW,MAAM,KAAKD,EAAkB,QAAQ,EAEtDC,EAAS,KAAK,CAAC7H,EAAG2H,KAAO3H,EAAE,OAAS,MAAQ2H,EAAE,OAAS,IAAI,EAC3DhB,EAAK,MAAM,eAAiBkB,CAC9B,CAKA,MAAMC,EAAqB,KAAKvB,GAAW,wBAAA,EACrCwB,EAAc,MAAM,KAAKD,EAAmB,QAAQ,EAIpDE,EAAyB,KAAKrC,IAAa,OAAO,QAAQ,iBAAmB,CAAA,EAG7EsC,EAAY,IAAI,IAAID,EAAuB,IAAK3H,GAAMA,EAAE,EAAE,CAAC,EAC3D6H,EAAiB,CAAC,GAAGF,CAAsB,EACjD,UAAWG,KAAWJ,EACfE,EAAU,IAAIE,EAAQ,EAAE,GAC3BD,EAAe,KAAKC,CAAO,EAK/BD,EAAe,KAAK,CAAClI,EAAG2H,KAAO3H,EAAE,OAAS,IAAM2H,EAAE,OAAS,EAAE,EAC7DhB,EAAK,MAAM,OAAO,gBAAkBuB,CACtC,CAQA,aAAaE,EAA4C,CACvD,MAAM7F,EAAU,KAAK,QACf8F,EAAa,KAAKC,GAAA,EAExB,MAAO,CACL,QAAS/F,EAAQ,IAAI,CAACd,EAAK8G,IAAU,CACnC,MAAMlL,EAAqB,CACzB,MAAOoE,EAAI,MACX,MAAO8G,EACP,QAAS,CAAC9G,EAAI,MAAA,EAIV+G,EAAc/G,EAChB+G,EAAY,kBAAoB,OAClCnL,EAAM,MAAQmL,EAAY,gBACjB/G,EAAI,QAAU,SACvBpE,EAAM,MAAQ,OAAOoE,EAAI,OAAU,SAAW,WAAWA,EAAI,KAAK,EAAIA,EAAI,OAI5E,MAAMgH,EAAYJ,EAAW,IAAI5G,EAAI,KAAK,EACtCgH,IACFpL,EAAM,KAAOoL,GAIf,UAAWC,KAAUN,EACnB,GAAIM,EAAO,eAAgB,CACzB,MAAMC,EAAcD,EAAO,eAAejH,EAAI,KAAK,EAC/CkH,GACF,OAAO,OAAOtL,EAAOsL,CAAW,CAEpC,CAGF,OAAOtL,CACT,CAAC,CAAA,CAEL,CAKA,WAAWA,EAAwB+K,EAAiC,CAClE,GAAI,CAAC/K,EAAM,SAAWA,EAAM,QAAQ,SAAW,EAAG,OAElD,MAAMuL,EAAa,KAAK,QAClBC,EAAW,IAAI,IAAIxL,EAAM,QAAQ,IAAK8H,GAAM,CAACA,EAAE,MAAOA,CAAC,CAAC,CAAC,EAGzD2D,EAAiBF,EAAW,IAAKnH,GAAQ,CAC7C,MAAM0D,EAAI0D,EAAS,IAAIpH,EAAI,KAAK,EAChC,GAAI,CAAC0D,EAAG,OAAO1D,EAEf,MAAMzD,EAA6B,CAAE,GAAGyD,CAAA,EAExC,OAAI0D,EAAE,QAAU,SACdnH,EAAQ,MAAQmH,EAAE,MAClBnH,EAAQ,gBAAkBmH,EAAE,OAG1BA,EAAE,UAAY,SAChBnH,EAAQ,OAAS,CAACmH,EAAE,SAGfnH,CACT,CAAC,EAGD8K,EAAe,KAAK,CAAC9I,EAAG2H,IAAM,CAC5B,MAAMoB,EAASF,EAAS,IAAI7I,EAAE,KAAK,GAAG,OAAS,IACzCgJ,EAASH,EAAS,IAAIlB,EAAE,KAAK,GAAG,OAAS,IAC/C,OAAOoB,EAASC,CAClB,CAAC,EAED,KAAK,QAAUF,EAGf,MAAMG,EAAmB5L,EAAM,QAC5B,OAAQ8H,GAAMA,EAAE,OAAS,MAAS,EAClC,KAAK,CAACnF,EAAG2H,KAAO3H,EAAE,MAAM,UAAY,IAAM2H,EAAE,MAAM,UAAY,EAAE,EAEnE,GAAIsB,EAAiB,OAAS,EAAG,CAC/B,MAAMC,EAAcD,EAAiB,CAAC,EAClCC,EAAY,MACd,KAAK3C,GAAW,aAAa,CAC3B,MAAO2C,EAAY,MACnB,UAAWA,EAAY,KAAK,YAAc,MAAQ,EAAI,EAAA,CACvD,CAEL,MACE,KAAK3C,GAAW,aAAa,IAAI,EAInC,UAAWmC,KAAUN,EACnB,GAAIM,EAAO,iBACT,UAAWS,KAAY9L,EAAM,QAC3BqL,EAAO,iBAAiBS,EAAS,MAAOA,CAAQ,CAIxD,CASA,WAAWf,EAAiC,CAE1C,KAAK9B,GAAsB,OAG3B,KAAKC,GAAW,aAAa,IAAI,EAGjC,KAAKN,GAAmB,KAAKY,GAAa,KAAKb,EAAe,EAG9D,KAAKc,GAAA,EAGL,UAAW4B,KAAUN,EACnB,GAAIM,EAAO,iBACT,UAAWjH,KAAO,KAAK,QACrBiH,EAAO,iBAAiBjH,EAAI,MAAO,CACjC,MAAOA,EAAI,MACX,MAAO,EACP,QAAS,EAAA,CACV,EAMP,KAAK,mBAAmB2G,CAAO,CACjC,CAKAE,IAA8C,CAC5C,MAAMc,MAAc,IACdX,EAAY,KAAKlC,GAAW,aAAA,EAElC,OAAIkC,GACFW,EAAQ,IAAIX,EAAU,MAAO,CAC3B,UAAWA,EAAU,YAAc,EAAI,MAAQ,OAC/C,SAAU,CAAA,CACX,EAGIW,CACT,CAKA,mBAAmBhB,EAAiC,CAC9C,KAAK/B,IACP,aAAa,KAAKA,EAAqB,EAGzC,KAAKA,GAAwB,WAAW,IAAM,CAC5C,KAAKA,GAAwB,OAC7B,MAAMhJ,EAAQ,KAAK,aAAa+K,CAAO,EACvC,KAAK7B,GAAW,KAAK,sBAAuBlJ,CAAK,CACnD,EAAGoI,EAAwB,CAC7B,CAQA,iBAAiB/G,EAAe2K,EAA2B,CACzD,MAAMC,EAAU,KAAK,QACf7H,EAAM6H,EAAQ,KAAMjJ,GAAMA,EAAE,QAAU3B,CAAK,EAYjD,MAVI,CAAC+C,GACD,CAAC4H,GAAW5H,EAAI,aAGhB,CAAC4H,GACsBC,EAAQ,OAAQjJ,GAAM,CAACA,EAAE,QAAUA,EAAE,QAAU3B,CAAK,EAAE,SACtD,GAGT,CAAC,CAAC+C,EAAI,SACN,CAAC4H,EAAgB,IAEnC5H,EAAI,OAAS,CAAC4H,EAEd,KAAK9C,GAAW,KAAK,oBAAqB,CACxC,MAAA7H,EACA,QAAA2K,EACA,eAAgBC,EAAQ,OAAQjJ,GAAM,CAACA,EAAE,MAAM,EAAE,IAAKA,GAAMA,EAAE,KAAK,CAAA,CACpE,EAED,KAAKkG,GAAW,aAAA,EAChB,KAAKA,GAAW,MAAA,EAET,GACT,CAKA,uBAAuB7H,EAAwB,CAC7C,MAAM+C,EAAM,KAAK,QAAQ,KAAMpB,GAAMA,EAAE,QAAU3B,CAAK,EACtD,OAAO+C,EAAM,KAAK,iBAAiB/C,EAAO,CAAC,CAAC+C,EAAI,MAAM,EAAI,EAC5D,CAKA,gBAAgB/C,EAAwB,CACtC,MAAM+C,EAAM,KAAK,QAAQ,KAAMpB,GAAMA,EAAE,QAAU3B,CAAK,EACtD,OAAO+C,EAAM,CAACA,EAAI,OAAS,EAC7B,CAKA,gBAAuB,CACrB,MAAM6H,EAAU,KAAK,QAChBA,EAAQ,KAAMjJ,GAAMA,EAAE,MAAM,IAEjCiJ,EAAQ,QAASjJ,GAAOA,EAAE,OAAS,EAAM,EAEzC,KAAKkG,GAAW,KAAK,oBAAqB,CACxC,eAAgB+C,EAAQ,IAAKjJ,GAAMA,EAAE,KAAK,CAAA,CAC3C,EAED,KAAKkG,GAAW,aAAA,EAChB,KAAKA,GAAW,MAAA,EAClB,CAKA,eAMG,CACD,OAAO,KAAK,QAAQ,IAAKlG,IAAO,CAC9B,MAAOA,EAAE,MACT,OAAQA,EAAE,QAAUA,EAAE,MACtB,QAAS,CAACA,EAAE,OACZ,YAAaA,EAAE,YACf,QAASA,EAAE,MAAM,UAAY,EAAA,EAC7B,CACJ,CAKA,gBAA2B,CACzB,OAAO,KAAK,QAAQ,IAAKA,GAAMA,EAAE,KAAK,CACxC,CAKA,eAAekJ,EAAuB,CACpC,GAAI,CAACA,EAAM,OAAQ,OAEnB,MAAMC,EAAY,IAAI,IAAI,KAAK,QAAQ,IAAKnJ,GAAM,CAACA,EAAE,MAAiBA,CAAC,CAAC,CAAC,EACnEoJ,EAAiC,CAAA,EAEvC,UAAW/K,KAAS6K,EAAO,CACzB,MAAM9H,EAAM+H,EAAU,IAAI9K,CAAK,EAC3B+C,IACFgI,EAAU,KAAKhI,CAAG,EAClB+H,EAAU,OAAO9K,CAAK,EAE1B,CAGA,UAAW+C,KAAO+H,EAAU,SAC1BC,EAAU,KAAKhI,CAAG,EAGpB,KAAK,QAAUgI,EAEf,KAAKlD,GAAW,aAAA,EAChB,KAAKA,GAAW,eAAA,EAChB,KAAKA,GAAW,qBAAA,CAClB,CAOA,qBAAqB/H,EAAyB,CACvC,KAAKsH,KACR,KAAKC,GAAuB,MAAM,KAAKvH,EAAK,iBAAiB,iBAAiB,CAAC,EAC/E,KAAKsH,GAAwB,KAAKC,GAAqB,OAASxH,GAAqBC,CAAI,EAAI,CAAA,EAEjG,CAKA,oBAA2B,CACzB,KAAKsH,GAAwB,MAC/B,CASA4D,OAAiD,IAUjD,wBAAwB9F,EAAiB+F,EAA4B,CACnE,KAAKD,GAAkB,IAAI9F,EAAQ,YAAA,EAAe+F,CAAQ,CAC5D,CAKA,0BAA0B/F,EAAuB,CAC/C,KAAK8F,GAAkB,OAAO9F,EAAQ,YAAA,CAAa,CACrD,CAgBA,gBAAgBpF,EAAyB,CAEnC,KAAK4H,IACP,KAAKA,GAAkB,WAAA,EAIzB,MAAMwD,MAAuB,IAC7B,IAAIC,EAAsD,KAE1D,MAAMC,EAA0B,IAAM,CACpCD,EAAgB,KAChB,UAAWjG,KAAWgG,EACJ,KAAKF,GAAkB,IAAI9F,CAAO,IAClD,EAEFgG,EAAiB,MAAA,CACnB,EAEA,KAAKxD,GAAoB,IAAI,iBAAkB2D,GAAc,CAC3D,UAAWC,KAAYD,EAAW,CAEhC,UAAWE,KAAQD,EAAS,WAAY,CACtC,GAAIC,EAAK,WAAa,KAAK,aAAc,SAEzC,MAAMrG,EADKqG,EACQ,QAAQ,YAAA,EAGvB,KAAKP,GAAkB,IAAI9F,CAAO,GACpCgG,EAAiB,IAAIhG,CAAO,CAEhC,CAGA,GAAIoG,EAAS,OAAS,cAAgBA,EAAS,OAAO,WAAa,KAAK,aAAc,CAEpF,MAAMpG,EADKoG,EAAS,OACD,QAAQ,YAAA,EACvB,KAAKN,GAAkB,IAAI9F,CAAO,GACpCgG,EAAiB,IAAIhG,CAAO,CAEhC,CACF,CAGIgG,EAAiB,KAAO,GAAK,CAACC,IAChCA,EAAgB,WAAWC,EAAyB,CAAC,EAEzD,CAAC,EAGD,KAAK1D,GAAkB,QAAQ5H,EAAM,CACnC,UAAW,GACX,QAAS,GACT,WAAY,GACZ,gBAAiB,CAAC,QAAS,QAAS,SAAU,QAAS,SAAU,KAAM,OAAQ,UAAW,OAAO,CAAA,CAClG,CACH,CAOA,SAASmL,EAA4B,CACnC,KAAKxD,GAAiB,KAAKwD,CAAQ,CACrC,CAKA,cAAqB,CACnB,UAAWO,KAAM,KAAK/D,GACpB+D,EAAA,CAEJ,CAOA,SAAgB,CACd,KAAK9D,IAAmB,WAAA,EACxB,KAAKD,GAAmB,CAAA,EACpB,KAAKE,IACP,aAAa,KAAKA,EAAqB,CAE3C,CAEF,CC3+BO,SAAS8D,IAAyB,CAEvC,GAAI,OAAO,OAAW,KAAe,OAAO,SAAU,CACpD,MAAMC,EAAW,OAAO,SAAS,SACjC,GAAIA,IAAa,aAAeA,IAAa,aAAeA,IAAa,MACvE,MAAO,EAEX,CAEA,OAAI,OAAO,QAAY,KAAe,QAAQ,KAAK,WAAa,YAIlE,CAUO,SAASC,GAAgB7K,EAAwB,CACtD,MAAO,uCAAuCA,CAAK,iBAAiBA,CAAK,KAAKA,EAAQ,YAAc,SAAS,SAC/G,CAOO,SAAS8K,GAAgB9K,EAAwB,CACtD,GAAIA,GAAS,MAAQA,IAAU,GAAI,MAAO,GAC1C,GAAIA,aAAiB,KACnB,OAAO,MAAMA,EAAM,QAAA,CAAS,EAAI,GAAKA,EAAM,mBAAA,EAE7C,GAAI,OAAOA,GAAU,UAAY,OAAOA,GAAU,SAAU,CAC1D,MAAMsB,EAAI,IAAI,KAAKtB,CAAK,EACxB,OAAO,MAAMsB,EAAE,QAAA,CAAS,EAAI,GAAKA,EAAE,mBAAA,CACrC,CACA,MAAO,EACT,CAOO,SAASyJ,GAAoBzI,EAA8B,CAChE,GAAI,CAACA,EAAM,MAAO,GAClB,MAAM+B,EAAO/B,EAAK,aAAa,UAAU,EACzC,GAAI+B,EAAM,OAAO,SAASA,EAAM,EAAE,EAGlC,MAAMhC,EAAQC,EAAK,QAAQ,gBAAgB,EAC3C,GAAI,CAACD,EAAO,MAAO,GAEnB,MAAM2I,EAAS3I,EAAM,cACrB,GAAI,CAAC2I,EAAQ,MAAO,GAGpB,MAAMpI,EAAOoI,EAAO,iBAAiB,yBAAyB,EAC9D,QAAS9I,EAAI,EAAGA,EAAIU,EAAK,OAAQV,IAC/B,GAAIU,EAAKV,CAAC,IAAMG,EAAO,OAAOH,EAEhC,MAAO,EACT,CAMO,SAAS+I,GAAoB3I,EAA8B,CAChE,GAAI,CAACA,EAAM,MAAO,GAClB,MAAM+B,EAAO/B,EAAK,aAAa,UAAU,EACzC,OAAO+B,EAAO,SAASA,EAAM,EAAE,EAAI,EACrC,CAMO,SAAS6G,GAAejH,EAAyC,CACjEA,GACLA,EAAK,iBAAiB,aAAa,EAAE,QAAShF,GAAOA,EAAG,UAAU,OAAO,YAAY,CAAC,CACxF,CAyBO,SAASkM,GAAaC,EAAiC,CAE5D,GAAI,CAEF,GADoB,iBAAiBA,CAAO,EAAE,YAC1B,MAAO,MAAO,KACpC,MAAQ,CAER,CAIA,GAAI,CAEF,GADgBA,EAAQ,UAAU,OAAO,GAAG,aAAa,KAAK,IAC9C,MAAO,MAAO,KAChC,MAAQ,CAER,CAEA,MAAO,KACT,CAQO,SAASC,GAAMD,EAA2B,CAC/C,OAAOD,GAAaC,CAAO,IAAM,KACnC,CClIO,SAASE,GACdzJ,EACAI,EAC+C,CAI/C,MAAMsJ,EAAiBtJ,EAAI,UAAYA,EAAI,aAC3C,GAAIsJ,EAAgB,OAAOA,EAG3B,GAAI,CAACtJ,EAAI,KAAM,OAIf,MAAMuJ,EAAU3J,EAAK,mBACrB,GAAI2J,GAAS,eAAgB,CAC3B,MAAMC,EAAaD,EAAQ,eAAqBvJ,EAAI,IAAI,EACxD,GAAIwJ,GAAY,SACd,OAAOA,EAAW,QAEtB,CAIF,CAUO,SAASC,GACd7J,EACAI,EACqD,CAIrD,GAAIA,EAAI,OAAQ,OAAOA,EAAI,OAG3B,GAAI,CAACA,EAAI,KAAM,OAIf,MAAMuJ,EAAU3J,EAAK,mBACrB,GAAI2J,GAAS,eAAgB,CAC3B,MAAMC,EAAaD,EAAQ,eAAqBvJ,EAAI,IAAI,EACxD,GAAIwJ,GAAY,OACd,OAAOA,EAAW,MAEtB,CAIF,CAQO,MAAME,GACX,sGAMF,SAASC,GAAgBvJ,EAAoC,CAC3D,OAAQA,EAAM,oBAAsB,GAAK,CAC3C,CAMA,SAASwJ,GAAkBxJ,EAAiC,CAC1DA,EAAM,mBAAqB,EAC3BA,EAAM,gBAAgB,kBAAkB,EAE1BA,EAAM,iBAAiB,eAAe,EAC9C,QAASC,GAASA,EAAK,UAAU,OAAO,SAAS,CAAC,CAC1D,CAWA,MAAMwJ,GAAe,SAAS,cAAc,UAAU,EACtDA,GAAa,UAAY,uDAMzB,MAAMC,GAAc,SAAS,cAAc,UAAU,EACrDA,GAAY,UAAY,0DAKxB,SAASC,IAAyC,CAChD,OAAOF,GAAa,QAAQ,kBAAmB,UAAU,EAAI,CAC/D,CAKO,SAASG,IAAwC,CACtD,OAAOF,GAAY,QAAQ,kBAAmB,UAAU,EAAI,CAC9D,CAOO,SAASG,EAAoBrK,EAA0B,CAC5DA,EAAK,mBAAqB,OAC1BA,EAAK,iBAAmB,OACxBA,EAAK,oBAAsB,MAC7B,CASO,SAASsK,GACdtK,EACAuK,EACAC,EACAC,EACAC,EACM,CACN,MAAMC,EAAS,KAAK,IAAI,EAAGH,EAAMD,CAAK,EAChCrO,EAAS8D,EAAK,QACdkB,EAAUlB,EAAK,gBACf4K,EAAS1J,EAAQ,OAGvB,IAAI2J,EAAiB7K,EAAK,uBAQ1B,IAPI6K,IAAmB,SACrBA,EAAiB7K,EAAK,cAAc,mBAAmB,EAAI,EAAI,EAC/DA,EAAK,uBAAyB6K,GAKzB7K,EAAK,SAAS,OAAS2K,GAAQ,CAEpC,MAAMnK,EAAQ4J,GAAA,EACdpK,EAAK,SAAS,KAAKQ,CAAK,CAC1B,CAGA,GAAIR,EAAK,SAAS,OAAS2K,EAAQ,CACjC,QAAStK,EAAIsK,EAAQtK,EAAIL,EAAK,SAAS,OAAQK,IAAK,CAClD,MAAMjD,EAAK4C,EAAK,SAASK,CAAC,EACtBjD,EAAG,aAAelB,GAAQkB,EAAG,OAAA,CACnC,CACA4C,EAAK,SAAS,OAAS2K,CACzB,CAGA,MAAMG,EAAsBJ,GAAiB1K,EAAK,wBAA0B,GAGtE+K,EAAa/K,EAAK,yBAAA,GAA8B,GAEtD,QAASK,EAAI,EAAGA,EAAIsK,EAAQtK,IAAK,CAC/B,MAAM2K,EAAWT,EAAQlK,EACnB4K,EAAUjL,EAAK,MAAMgL,CAAQ,EAC7BxK,EAAQR,EAAK,SAASK,CAAC,EAM7B,GAHAG,EAAM,aAAa,gBAAiB,OAAOwK,EAAWH,EAAiB,CAAC,CAAC,EAGrEC,GAAuBJ,EAAeO,EAASzK,EAAOwK,CAAQ,EAAG,CACnExK,EAAM,QAAUiK,EAChBjK,EAAM,aAAeyK,EACjBzK,EAAM,aAAetE,GAAQA,EAAO,YAAYsE,CAAK,EACzD,QACF,CAEA,MAAM0K,EAAW1K,EAAM,QACjB2K,EAAU3K,EAAM,aAChB4K,EAAY5K,EAAM,SAAS,OAI3B6K,EADaH,IAAaT,GACKW,IAAcR,EAC7CU,EAAiBH,IAAYF,EAGnC,IAAIM,EAAuB,GAC3B,GAAIF,GAAkBC,GACpB,QAAStM,EAAI,EAAGA,EAAI4L,EAAQ5L,IAE1B,GADYkC,EAAQlC,CAAC,EACb,cAEF,CADcwB,EAAM,cAAc,mBAAmBxB,CAAC,yBAAyB,EACnE,CACduM,EAAuB,GACvB,KACF,EAKN,GAAI,CAACF,GAAkBE,EAAsB,CAG3C,MAAMC,EAAazB,GAAgBvJ,CAAK,EAClCiL,EAAsBzL,EAAK,kBAAoBgL,EAIjDQ,GAAc,CAACC,GAEbjL,EAAM,gBACRA,EAAM,UAAY,gBAClBA,EAAM,aAAa,OAAQ,KAAK,EAChCA,EAAM,cAAgB,IAExBwJ,GAAkBxJ,CAAK,EACvBkL,EAAgB1L,EAAMQ,EAAOyK,EAASD,CAAQ,EAC9CxK,EAAM,QAAUiK,EAChBjK,EAAM,aAAeyK,GACZO,GAAcC,GAEvBE,GAAa3L,EAAMQ,EAAOyK,EAASD,CAAQ,EAC3CxK,EAAM,aAAeyK,IAEjBzK,EAAM,gBACRA,EAAM,UAAY,gBAClBA,EAAM,aAAa,OAAQ,KAAK,EAChCA,EAAM,cAAgB,IAExBkL,EAAgB1L,EAAMQ,EAAOyK,EAASD,CAAQ,EAC9CxK,EAAM,QAAUiK,EAChBjK,EAAM,aAAeyK,EAGzB,SAAWK,EAAgB,CAGzB,MAAME,EAAazB,GAAgBvJ,CAAK,EAClCiL,EAAsBzL,EAAK,kBAAoBgL,EAGjDQ,GAAc,CAACC,GACjBzB,GAAkBxJ,CAAK,EACvBkL,EAAgB1L,EAAMQ,EAAOyK,EAASD,CAAQ,EAC9CxK,EAAM,QAAUiK,EAChBjK,EAAM,aAAeyK,IAErBU,GAAa3L,EAAMQ,EAAOyK,EAASD,CAAQ,EAC3CxK,EAAM,aAAeyK,EAGzB,KAAO,CAGL,MAAMO,EAAazB,GAAgBvJ,CAAK,EAClCiL,EAAsBzL,EAAK,kBAAoBgL,EAGjDQ,GAAc,CAACC,GACjBzB,GAAkBxJ,CAAK,EACvBkL,EAAgB1L,EAAMQ,EAAOyK,EAASD,CAAQ,EAC9CxK,EAAM,QAAUiK,EAChBjK,EAAM,aAAeyK,GAErBU,GAAa3L,EAAMQ,EAAOyK,EAASD,CAAQ,CAG/C,CAGA,IAAIY,EAAY,GAChB,MAAMC,EAAgB7L,EAAK,cAC3B,GAAI6L,GAAiBA,EAAc,OAAS,EAC1C,GAAI,CACF,MAAMC,EAAQ9L,EAAK,WAAWiL,CAAO,EACjCa,IACFF,EAAYC,EAAc,SAASC,CAAK,EAE5C,MAAQ,CAER,CAEF,MAAMC,EAAkBvL,EAAM,UAAU,SAAS,SAAS,EACtDoL,IAAcG,GAChBvL,EAAM,UAAU,OAAO,UAAWoL,CAAS,EAI7C,MAAMI,EAAahM,EAAK,iBAAiB,SACzC,GAAIgM,EAAY,CAEd,MAAMC,EAAczL,EAAM,aAAa,sBAAsB,EACzDyL,GACFA,EAAY,MAAM,GAAG,EAAE,QAASC,GAAQA,GAAO1L,EAAM,UAAU,OAAO0L,CAAG,CAAC,EAE5E,GAAI,CACF,MAAMC,EAAaH,EAAWf,CAAO,EACrC,GAAIkB,GAAcA,EAAW,OAAS,EAAG,CACvC,MAAMC,GAAeD,EAAW,OAAQnN,IAAMA,IAAK,OAAOA,IAAM,QAAQ,EACxEoN,GAAa,QAASF,IAAQ1L,EAAM,UAAU,IAAI0L,EAAG,CAAC,EACtD1L,EAAM,aAAa,uBAAwB4L,GAAa,KAAK,GAAG,CAAC,CACnE,MACE5L,EAAM,gBAAgB,sBAAsB,CAEhD,OAAS6L,EAAG,CACV,QAAQ,KAAK,sCAAuCA,CAAC,EACrD7L,EAAM,gBAAgB,sBAAsB,CAC9C,CACF,CAGIuK,GACF/K,EAAK,kBAAkB,CACrB,IAAKiL,EACL,SAAAD,EACA,WAAYxK,CAAA,CACb,EAGCA,EAAM,aAAetE,GAAQA,EAAO,YAAYsE,CAAK,CAC3D,CACF,CAUA,SAASmL,GAAa3L,EAAoBQ,EAAoByK,EAAcD,EAAwB,CAClG,MAAMsB,EAAW9L,EAAM,SACjBU,EAAUlB,EAAK,gBACfuM,EAAUrL,EAAQ,OAClBsL,EAAWF,EAAS,OACpBG,EAASF,EAAUC,EAAWD,EAAUC,EACxCE,EAAW1M,EAAK,UAChB2M,EAAW3M,EAAK,UAGhB4M,EAAc5M,EAAK,0BAAA,GAA+B,GAIxD,IAAI6M,EAAiB7M,EAAK,oBAC1B,GAAI6M,IAAmB,OAAW,CAChCA,EAAiB,GAIjB,MAAMlD,EAAU3J,EAAK,mBACrB,QAASK,EAAI,EAAGA,EAAIkM,EAASlM,IAAK,CAChC,MAAMD,EAAMc,EAAQb,CAAC,EACrB,GACED,EAAI,gBACJA,EAAI,gBACJA,EAAI,UACJA,EAAI,cACJA,EAAI,cACJA,EAAI,QACJA,EAAI,OAAS,QACbA,EAAI,OAAS,WAEZA,EAAI,MAAQuJ,GAAS,iBAAiBvJ,EAAI,IAAI,GAAG,UACjDA,EAAI,MAAQuJ,GAAS,iBAAiBvJ,EAAI,IAAI,GAAG,OAClD,CACAyM,EAAiB,GACjB,KACF,CACF,CACA7M,EAAK,oBAAsB6M,CAC7B,CAEA,MAAMC,EAAc,OAAO9B,CAAQ,EAGnC,GAAI,CAAC6B,EAAgB,CACnB,QAASxM,EAAI,EAAGA,EAAIoM,EAAQpM,IAAK,CAC/B,MAAMI,EAAO6L,EAASjM,CAAC,EAGvB,GAAII,EAAK,UAAU,SAAS,SAAS,EAAG,SAExC,MAAML,EAAMc,EAAQb,CAAC,EACflC,EAAQ8M,EAAQ7K,EAAI,KAAK,EAC/BK,EAAK,YAActC,GAAS,KAAO,GAAK,OAAOA,CAAK,EAEhDsC,EAAK,aAAa,UAAU,IAAMqM,GACpCrM,EAAK,aAAa,WAAYqM,CAAW,EAG3C,MAAMC,EAAkBL,IAAa1B,GAAY2B,IAAatM,EACxD2M,EAAWvM,EAAK,UAAU,SAAS,YAAY,EACjDsM,IAAoBC,IACtBvM,EAAK,UAAU,OAAO,aAAcsM,CAAe,EAEnDtM,EAAK,aAAa,gBAAiB,OAAOsM,CAAe,CAAC,GAIxDH,GACF5M,EAAK,mBAAmB,CACtB,IAAKiL,EACL,SAAAD,EACA,OAAQ5K,EACR,SAAUC,EACV,MAAAlC,EACA,YAAasC,EACb,WAAYD,CAAA,CACb,CAEL,CACA,MACF,CAGA,QAASH,EAAI,EAAGA,EAAIoM,EAAQpM,IAE1B,GADYa,EAAQb,CAAC,EACb,cAEF,CADSiM,EAASjM,CAAC,EACb,cAAc,sBAAsB,EAAG,CAC/CqL,EAAgB1L,EAAMQ,EAAOyK,EAASD,CAAQ,EAC9C,MACF,CAKJ,QAAS3K,EAAI,EAAGA,EAAIoM,EAAQpM,IAAK,CAC/B,MAAMD,EAAMc,EAAQb,CAAC,EACfI,EAAO6L,EAASjM,CAAC,EAGnBI,EAAK,aAAa,UAAU,IAAMqM,GACpCrM,EAAK,aAAa,WAAYqM,CAAW,EAI3C,MAAMC,EAAkBL,IAAa1B,GAAY2B,IAAatM,EACxD2M,EAAWvM,EAAK,UAAU,SAAS,YAAY,EACjDsM,IAAoBC,IACtBvM,EAAK,UAAU,OAAO,aAAcsM,CAAe,EACnDtM,EAAK,aAAa,gBAAiB,OAAOsM,CAAe,CAAC,GAI5D,MAAME,EAAc7M,EAAI,UACxB,GAAI6M,EAAa,CAEf,MAAMhB,EAAcxL,EAAK,aAAa,sBAAsB,EACxDwL,GACFA,EAAY,MAAM,GAAG,EAAE,QAASC,GAAQA,GAAOzL,EAAK,UAAU,OAAOyL,CAAG,CAAC,EAE3E,GAAI,CACF,MAAM/N,EAAQ8M,EAAQ7K,EAAI,KAAK,EACzB8M,EAAcD,EAAY9O,EAAO8M,EAAS7K,CAAG,EACnD,GAAI8M,GAAeA,EAAY,OAAS,EAAG,CACzC,MAAMd,EAAec,EAAY,OAAQlO,GAAcA,GAAK,OAAOA,GAAM,QAAQ,EACjFoN,EAAa,QAASF,GAAgBzL,EAAK,UAAU,IAAIyL,CAAG,CAAC,EAC7DzL,EAAK,aAAa,uBAAwB2L,EAAa,KAAK,GAAG,CAAC,CAClE,MACE3L,EAAK,gBAAgB,sBAAsB,CAE/C,OAAS4L,EAAG,CACV,QAAQ,KAAK,mDAAmDjM,EAAI,KAAK,KAAMiM,CAAC,EAChF5L,EAAK,gBAAgB,sBAAsB,CAC7C,CACF,CAGA,GAAIA,EAAK,UAAU,SAAS,SAAS,EAAG,SAIxC,MAAM0M,EAAe1D,GAAgBzJ,EAAMI,CAAG,EAC9C,GAAI+M,EAAc,CAChB,MAAMC,EAAgBnC,EAAQ7K,EAAI,KAAK,EAEjCiN,EAAWF,EAAa,CAC5B,IAAKlC,EACL,MAAOmC,EACP,MAAOhN,EAAI,MACX,OAAQA,EACR,OAAQK,CAAA,CACT,EACG,OAAO4M,GAAa,SACtB5M,EAAK,UAAYuB,EAAaqL,CAAQ,EAC7BA,aAAoB,KAEzBA,EAAS,gBAAkB5M,IAC7BA,EAAK,UAAY,GACjBA,EAAK,YAAY4M,CAAQ,GAGlBA,GAAY,OAErB5M,EAAK,YAAc2M,GAAiB,KAAO,GAAK,OAAOA,CAAa,GAIlER,GACF5M,EAAK,mBAAmB,CACtB,IAAKiL,EACL,SAAAD,EACA,OAAQ5K,EACR,SAAUC,EACV,MAAO+M,EACP,YAAa3M,EACb,WAAYD,CAAA,CACb,EAEH,QACF,CAGA,GAAIJ,EAAI,gBAAkBA,EAAI,gBAAkBA,EAAI,aAClD,SAIF,MAAMjC,EAAQ8M,EAAQ7K,EAAI,KAAK,EAC/B,IAAIkN,EAGJ,MAAMC,EAAW1D,GAAc7J,EAAMI,CAAG,EACxC,GAAImN,EAAU,CACZ,GAAI,CACF,MAAMC,EAAYD,EAASpP,EAAO8M,CAAO,EACzCqC,EAAaE,GAAa,KAAO,GAAK,OAAOA,CAAS,CACxD,OAASnB,EAAG,CAEV,QAAQ,KAAK,sCAAsCjM,EAAI,KAAK,KAAMiM,CAAC,EACnEiB,EAAanP,GAAS,KAAO,GAAK,OAAOA,CAAK,CAChD,CACAsC,EAAK,YAAc6M,CACrB,MAAWlN,EAAI,OAAS,QACtBkN,EAAarE,GAAgB9K,CAAK,EAClCsC,EAAK,YAAc6M,GACVlN,EAAI,OAAS,UAEtBK,EAAK,UAAYuI,GAAgB,CAAC,CAAC7K,CAAK,GAExCmP,EAAanP,GAAS,KAAO,GAAK,OAAOA,CAAK,EAC9CsC,EAAK,YAAc6M,GAIjBV,GACF5M,EAAK,mBAAmB,CACtB,IAAKiL,EACL,SAAAD,EACA,OAAQ5K,EACR,SAAUC,EACV,MAAAlC,EACA,YAAasC,EACb,WAAYD,CAAA,CACb,CAEL,CACF,CAQO,SAASkL,EAAgB1L,EAAoBQ,EAAoByK,EAAcD,EAAwB,CAC5GxK,EAAM,UAAY,GAGlB,MAAMU,EAAUlB,EAAK,gBACfuM,EAAUrL,EAAQ,OAClBwL,EAAW1M,EAAK,UAChB2M,EAAW3M,EAAK,UAChByN,EAASzN,EAGT4M,EAAc5M,EAAK,0BAAA,GAA+B,GAGlD0N,EAAW,SAAS,uBAAA,EAE1B,QAASC,EAAW,EAAGA,EAAWpB,EAASoB,IAAY,CACrD,MAAMvN,EAAMc,EAAQyM,CAAQ,EAEtBlN,EAAO0J,GAAA,EAIb1J,EAAK,aAAa,gBAAiB,OAAOkN,EAAW,CAAC,CAAC,EACvDlN,EAAK,aAAa,WAAY,OAAOkN,CAAQ,CAAC,EAC9ClN,EAAK,aAAa,WAAY,OAAOuK,CAAQ,CAAC,EAC9CvK,EAAK,aAAa,aAAcL,EAAI,KAAK,EACzCK,EAAK,aAAa,cAAeL,EAAI,QAAUA,EAAI,KAAK,EACpDA,EAAI,MAAMK,EAAK,aAAa,YAAaL,EAAI,IAAI,EAErD,IAAIjC,EAAS8M,EAAoC7K,EAAI,KAAK,EAE1D,MAAMmN,EAAW1D,GAAc7J,EAAMI,CAAG,EACxC,GAAImN,EACF,GAAI,CACFpP,EAAQoP,EAASpP,EAAO8M,CAAO,CACjC,OAASoB,EAAG,CAEV,QAAQ,KAAK,sCAAsCjM,EAAI,KAAK,KAAMiM,CAAC,CACrE,CAGF,MAAMuB,EAAWxN,EAAI,eACfyN,EAAYzN,EAAI,eAEhB0N,EAAerE,GAAgBzJ,EAAMI,CAAG,EACxC2N,EAAe3N,EAAI,aAGzB,IAAI4N,EAAoB,GAExB,GAAIF,EAAc,CAEhB,MAAMT,EAAWS,EAAa,CAAE,IAAK7C,EAAS,MAAA9M,EAAO,MAAOiC,EAAI,MAAO,OAAQA,EAAK,OAAQK,EAAM,EAC9F,OAAO4M,GAAa,UAEtB5M,EAAK,UAAYuB,EAAaqL,CAAQ,EACtCW,EAAoB,IACXX,aAAoB,KAEzBA,EAAS,gBAAkB5M,IAE7BA,EAAK,YAAc,GACnBA,EAAK,YAAY4M,CAAQ,GAGlBA,GAAY,OAErB5M,EAAK,YAActC,GAAS,KAAO,GAAK,OAAOA,CAAK,EAIxD,SAAW4P,EAAc,CACvB,MAAME,EAAOF,EACPG,EAAc,SAAS,cAAc,KAAK,EAChDA,EAAY,aAAa,qBAAsB,EAAE,EACjDA,EAAY,aAAa,aAAc9N,EAAI,KAAK,EAChDK,EAAK,YAAYyN,CAAW,EAC5B,MAAMC,EAAU,CAAE,IAAKlD,EAAS,MAAA9M,EAAO,MAAOiC,EAAI,MAAO,OAAQA,CAAA,EACjE,GAAI6N,EAAK,MACP,GAAI,CACFA,EAAK,MAAM,CAAE,YAAAC,EAAa,QAAAC,EAAS,KAAAF,EAAM,CAC3C,OAAS5B,EAAG,CAEV,QAAQ,KAAK,oDAAoDjM,EAAI,KAAK,KAAMiM,CAAC,CACnF,MAEA,eAAe,IAAM,CACnB,GAAI,CACFoB,EAAO,cACL,IAAI,YAAY,sBAAuB,CACrC,QAAS,GACT,SAAU,GACV,OAAQ,CAAE,YAAAS,EAAa,KAAAD,EAAM,QAAAE,CAAA,CAAQ,CACtC,CAAA,CAEL,OAAS9B,EAAG,CAEV,QAAQ,KAAK,6DAA6DjM,EAAI,KAAK,KAAMiM,CAAC,CAC5F,CACF,CAAC,EAEH6B,EAAY,aAAa,eAAgB,EAAE,CAC7C,SAAWN,EAAU,CACnB,MAAMQ,EAASR,EAAS,CAAE,IAAK3C,EAAS,MAAA9M,EAAO,MAAOiC,EAAI,MAAO,OAAQA,CAAA,CAAK,EACxEiO,EAAUT,EAAS,UAEzBnN,EAAK,UAAY4N,EAAU,GAAKrM,EAAaoM,CAAM,EACnDJ,EAAoB,GAChBK,IAEF5N,EAAK,YAAc,GACnBA,EAAK,aAAa,wBAAyB,EAAE,EAEjD,SAAWoN,EAAW,CACpB,MAAMS,EAAST,EAAU,UACrB,gCAAgC,KAAKS,CAAM,GAC7C7N,EAAK,YAAc,GACnBA,EAAK,aAAa,wBAAyB,EAAE,IAG7CA,EAAK,UAAYuB,EAAaY,GAAmB0L,EAAQ,CAAE,IAAKrD,EAAS,MAAA9M,CAAA,CAAO,CAAC,EACjF6P,EAAoB,GAExB,MAGMT,EACF9M,EAAK,YAActC,GAAS,KAAO,GAAK,OAAOA,CAAK,EAC3CiC,EAAI,OAAS,OACtBK,EAAK,YAAcwI,GAAgB9K,CAAK,EAC/BiC,EAAI,OAAS,UAEtBK,EAAK,UAAYuI,GAAgB,CAAC,CAAC7K,CAAK,EAExCsC,EAAK,YAActC,GAAS,KAAO,GAAK,OAAOA,CAAK,EAKxD,GAAI6P,EAAmB,CACrBjK,GAAetD,CAAI,EAEnB,MAAM8N,EAAc9N,EAAK,aAAe,GACpC,yBAAyB,KAAK8N,CAAW,IAC3C9N,EAAK,YAAc8N,EAAY,QAAQ,0BAA2B,EAAE,EAAE,KAAA,EAClE,yBAAyB,KAAK9N,EAAK,aAAe,EAAE,MAAQ,YAAc,IAElF,CAEIA,EAAK,aAAa,uBAAuB,IAEtCA,EAAK,aAAe,IAAI,OAAO,WAAa,YAAc,IAI7DL,EAAI,SACNK,EAAK,SAAW,EACPL,EAAI,OAAS,YAGjBK,EAAK,aAAa,UAAU,MAAQ,SAAW,IAIlDiM,IAAa1B,GAAY2B,IAAagB,GACxClN,EAAK,UAAU,IAAI,YAAY,EAC/BA,EAAK,aAAa,gBAAiB,MAAM,GAEzCA,EAAK,aAAa,gBAAiB,OAAO,EAI5C,MAAMwM,EAAc7M,EAAI,UACxB,GAAI6M,EACF,GAAI,CACF,MAAMuB,EAAavD,EAAoC7K,EAAI,KAAK,EAC1D8M,EAAcD,EAAYuB,EAAWvD,EAAS7K,CAAG,EACvD,GAAI8M,GAAeA,EAAY,OAAS,EAAG,CACzC,MAAMd,EAAec,EAAY,OAAQlO,GAAMA,GAAK,OAAOA,GAAM,QAAQ,EACzEoN,EAAa,QAASF,GAAQzL,EAAK,UAAU,IAAIyL,CAAG,CAAC,EACrDzL,EAAK,aAAa,uBAAwB2L,EAAa,KAAK,GAAG,CAAC,CAClE,CACF,OAASC,EAAG,CACV,QAAQ,KAAK,mDAAmDjM,EAAI,KAAK,KAAMiM,CAAC,CAClF,CAIEO,GACF5M,EAAK,mBAAmB,CACtB,IAAKiL,EACL,SAAAD,EACA,OAAQ5K,EACR,SAAAuN,EACA,MAAAxP,EACA,YAAasC,EACb,WAAYD,CAAA,CACb,EAGHkN,EAAS,YAAYjN,CAAI,CAC3B,CAGAD,EAAM,YAAYkN,CAAQ,CAC5B,CAQO,SAASe,GAAezO,EAAoB,EAAeQ,EAA0B,CAC1F,GAAK,EAAE,QAAwB,QAAQ,gBAAgB,EAAG,OAC1D,MAAMkO,EAAYlO,EAAM,cAAc,iBAAiB,EACjDwK,EAAW9B,GAAoBwF,CAAS,EAC9C,GAAI1D,EAAW,EAAG,OAClB,MAAMC,EAAUjL,EAAK,MAAMgL,CAAQ,EAInC,GAHI,CAACC,GAGDjL,EAAK,oBAAoB,EAAGgL,EAAUC,EAASzK,CAAK,EACtD,OAGF,MAAMmO,EAAU,EAAE,QAAwB,QAAQ,iBAAiB,EACnE,GAAIA,EAAQ,CACV,MAAMhB,EAAW,OAAOgB,EAAO,aAAa,UAAU,CAAC,EACvD,GAAI,CAAC,MAAMhB,CAAQ,EAAG,CAEpB,GAAI3N,EAAK,qBAAqB,EAAGgL,EAAU2C,EAAUgB,CAAM,EACzD,OAIF,MAAMC,EAAe5O,EAAK,YAAcgL,GAAYhL,EAAK,YAAc2N,EAKvE,GAJA3N,EAAK,UAAYgL,EACjBhL,EAAK,UAAY2N,EAGbgB,EAAO,UAAU,SAAS,SAAS,EAAG,CACpCC,IAEFvF,GAAerJ,EAAK,SAAWA,CAAI,EACnC2O,EAAO,UAAU,IAAI,YAAY,GAGnC,MAAM5P,EAAS4P,EAAO,cAAc7E,EAAyB,EAC7D,GAAI,CACF/K,GAAQ,MAAM,CAAE,cAAe,EAAA,CAAM,CACvC,MAAQ,CAER,CACA,MACF,CAEA8P,EAAkB7O,CAAI,CACxB,CACF,CACF,CC51BO,SAAS8O,GAAkB9O,EAAoB,EAAwB,CAE5E,GAAIA,EAAK,mBAAmB,CAAC,EAC3B,OAGF,MAAM+O,EAAS/O,EAAK,MAAM,OAAS,EAC7BgP,EAAShP,EAAK,gBAAgB,OAAS,EACvCiP,EAAUjP,EAAK,kBAAoB,QAAaA,EAAK,kBAAoB,GAEzEkP,EADMlP,EAAK,gBAAgBA,EAAK,SAAS,GAC1B,KACfmP,EAAO,EAAE,eAAA,GAAoB,CAAA,EAC7BC,EAAUD,EAAK,OAASA,EAAK,CAAC,EAAI,EAAE,OACpCE,EAAejS,GAA2B,CAC9C,GAAI,CAACA,EAAI,MAAO,GAChB,MAAMkS,EAAMlS,EAAG,QAEf,MADI,GAAAkS,IAAQ,SAAWA,IAAQ,UAAYA,IAAQ,YAC/ClS,EAAG,kBAET,EACA,GAAI,EAAAiS,EAAYD,CAAM,IAAM,EAAE,MAAQ,QAAU,EAAE,MAAQ,SACtD,EAAAC,EAAYD,CAAM,IAAM,EAAE,MAAQ,WAAa,EAAE,MAAQ,cACtDA,EAA4B,UAAY,SAAYA,EAA4B,OAAS,WAG5F,EAAAC,EAAYD,CAAM,IAAM,EAAE,MAAQ,aAAe,EAAE,MAAQ,gBAE3D,EAAAC,EAAYD,CAAM,IAAM,EAAE,MAAQ,SAAW,EAAE,MAAQ,YACvD,EAAAH,GAAWC,IAAY,WAAa,EAAE,MAAQ,aAAe,EAAE,MAAQ,YAC3E,QAAQ,EAAE,IAAA,CACR,IAAK,MAAO,CACV,EAAE,eAAA,EACc,CAAC,EAAE,SAEblP,EAAK,UAAYgP,EAAQhP,EAAK,WAAa,GAEzC,OAAOA,EAAK,qBAAwB,cAAiB,oBAAA,EACrDA,EAAK,UAAY+O,IACnB/O,EAAK,WAAa,EAClBA,EAAK,UAAY,IAIjBA,EAAK,UAAY,EAAGA,EAAK,WAAa,EACjCA,EAAK,UAAY,IACpB,OAAOA,EAAK,qBAAwB,YAAcA,EAAK,kBAAoBA,EAAK,WAClFA,EAAK,oBAAA,EACPA,EAAK,WAAa,EAClBA,EAAK,UAAYgP,GAGrBH,EAAkB7O,CAAI,EACtB,MACF,CACA,IAAK,YACCiP,GAAW,OAAOjP,EAAK,qBAAwB,cAAiB,oBAAA,EACpEA,EAAK,UAAY,KAAK,IAAI+O,EAAQ/O,EAAK,UAAY,CAAC,EACpD,EAAE,eAAA,EACF,MACF,IAAK,UACCiP,GAAW,OAAOjP,EAAK,qBAAwB,cAAiB,oBAAA,EACpEA,EAAK,UAAY,KAAK,IAAI,EAAGA,EAAK,UAAY,CAAC,EAC/C,EAAE,eAAA,EACF,MACF,IAAK,aAAc,CAELwJ,GAAMxJ,CAA8B,EAE9CA,EAAK,UAAY,KAAK,IAAI,EAAGA,EAAK,UAAY,CAAC,EAE/CA,EAAK,UAAY,KAAK,IAAIgP,EAAQhP,EAAK,UAAY,CAAC,EAEtD,EAAE,eAAA,EACF,KACF,CACA,IAAK,YAAa,CAEJwJ,GAAMxJ,CAA8B,EAE9CA,EAAK,UAAY,KAAK,IAAIgP,EAAQhP,EAAK,UAAY,CAAC,EAEpDA,EAAK,UAAY,KAAK,IAAI,EAAGA,EAAK,UAAY,CAAC,EAEjD,EAAE,eAAA,EACF,KACF,CACA,IAAK,QACC,EAAE,SAAW,EAAE,WAEbiP,GAAW,OAAOjP,EAAK,qBAAwB,cAAiB,oBAAA,EACpEA,EAAK,UAAY,GACjBA,EAAK,UAAY,EAKnB,EAAE,eAAA,EACF6O,EAAkB7O,EAAM,CAAE,gBAAiB,EAAA,CAAM,EACjD,OACF,IAAK,OACC,EAAE,SAAW,EAAE,WAEbiP,GAAW,OAAOjP,EAAK,qBAAwB,cAAiB,oBAAA,EACpEA,EAAK,UAAY+O,GACjB/O,EAAK,UAAYgP,EAKnB,EAAE,eAAA,EACFH,EAAkB7O,EAAM,CAAE,iBAAkB,EAAA,CAAM,EAClD,OACF,IAAK,WACHA,EAAK,UAAY,KAAK,IAAI+O,EAAQ/O,EAAK,UAAY,EAAE,EACrD,EAAE,eAAA,EACF,MACF,IAAK,SACHA,EAAK,UAAY,KAAK,IAAI,EAAGA,EAAK,UAAY,EAAE,EAChD,EAAE,eAAA,EACF,MAGF,IAAK,QAAS,CACZ,MAAMgL,EAAWhL,EAAK,UAChB2N,EAAW3N,EAAK,UAChBuP,EAASvP,EAAK,gBAAgB2N,CAAQ,EACtC6B,EAAMxP,EAAK,MAAMgL,CAAQ,EACzB3N,EAAQkS,GAAQ,OAAS,GACzBpR,EAAQd,GAASmS,EAAOA,EAAgCnS,CAAK,EAAI,OACjEsR,EAAU3O,EAAgC,cAC9C,cAAcgL,CAAQ,gBAAgB2C,CAAQ,IAAA,EAG1C8B,EAAS,CACb,SAAAzE,EACA,SAAA2C,EACA,MAAAtQ,EACA,MAAAc,EACA,IAAAqR,EACA,OAAAb,EACA,QAAS,WACT,cAAe,CAAA,EAIXe,EAAgB,IAAI,YAAY,gBAAiB,CACrD,WAAY,GACZ,OAAAD,CAAA,CACD,EACAzP,EAAgC,cAAc0P,CAAa,EAG5D,MAAMC,EAAc,IAAI,YAAY,gBAAiB,CACnD,WAAY,GACZ,OAAQ,CAAE,IAAK3E,EAAU,IAAK2C,CAAA,CAAS,CACxC,EAID,GAHC3N,EAAgC,cAAc2P,CAAW,EAGtDD,EAAc,kBAAoBC,EAAY,iBAAkB,CAClE,EAAE,eAAA,EACF,MACF,CAEA,KACF,CACA,QACE,MAAA,CAEJd,EAAkB7O,CAAI,EACxB,CAoBO,SAAS6O,EAAkB7O,EAAoB4P,EAA0C,CAC9F,GAAI5P,EAAK,iBAAiB,QAAS,CACjC,KAAM,CAAE,UAAA6P,EAAW,UAAAC,EAAW,WAAAC,CAAA,EAAe/P,EAAK,gBAG5CgQ,EAAWF,EACXG,EAAgBF,GAAY,cAAgBC,GAAU,cAAgB,EAC5E,GAAIA,GAAYC,EAAgB,EAAG,CACjC,MAAMC,EAAIlQ,EAAK,UAAY6P,EACvBK,EAAIF,EAAS,UACfA,EAAS,UAAYE,EACZA,EAAIL,EAAYG,EAAS,UAAYC,IAC9CD,EAAS,UAAYE,EAAID,EAAgBJ,EAE7C,CACF,CAEA,MAAMM,EAAYnQ,EAAK,kBAAoB,QAAaA,EAAK,kBAAoB,GAC5EmQ,GACHnQ,EAAK,qBAAqB,EAAK,EAEjCqJ,GAAerJ,EAAK,OAAO,EAE3B,MAAM,KAAKA,EAAK,QAAQ,iBAAiB,wBAAwB,CAAC,EAAE,QAAS5C,GAAO,CAClFA,EAAG,aAAa,gBAAiB,OAAO,CAC1C,CAAC,EACD,MAAM4N,EAAWhL,EAAK,UAChBoQ,EAASpQ,EAAK,gBAAgB,OAAS,EACvCqQ,EAAOrQ,EAAK,gBAAgB,KAAOA,EAAK,MAAM,OACpD,GAAIgL,GAAYoF,GAAUpF,EAAWqF,EAAM,CACzC,MAAM7P,EAAQR,EAAK,QAAQ,iBAAiB,gBAAgB,EAAEgL,EAAWoF,CAAM,EAE/E,IAAI3P,EAAOD,GAAO,SAASR,EAAK,SAAS,EAKzC,IAJI,CAACS,GAAQ,CAACA,EAAK,WAAW,SAAS,MAAM,KAC3CA,EAAQD,GAAO,cAAc,mBAAmBR,EAAK,SAAS,IAAI,GAChEQ,GAAO,cAAc,iBAAiB,GAEtCC,EAAM,CACRA,EAAK,UAAU,IAAI,YAAY,EAC/BA,EAAK,aAAa,gBAAiB,MAAM,EAMzC,MAAM6P,EAAatQ,EAAK,cAAc,kBAAkB,EACxD,GAAIsQ,GAAc7P,IAAS,CAAC0P,GAAaP,GAAS,uBAEhD,GAAIA,GAAS,gBACXU,EAAW,WAAa,UACfV,GAAS,iBAClBU,EAAW,WAAaA,EAAW,YAAcA,EAAW,gBACvD,CAIL,MAAMC,EAAUvQ,EAAK,8BAA8BQ,GAAS,OAAWC,CAAI,GAAK,CAAE,KAAM,EAAG,MAAO,CAAA,EAElG,GAAI,CAAC8P,EAAQ,WAAY,CAEvB,MAAMC,EAAW/P,EAAK,sBAAA,EAChBgQ,EAAiBH,EAAW,sBAAA,EAE5BI,EAAWF,EAAS,KAAOC,EAAe,KAAOH,EAAW,WAC5DK,EAAYD,EAAWF,EAAS,MAEhCI,EAAcN,EAAW,WAAaC,EAAQ,KAC9CM,EAAeP,EAAW,WAAaA,EAAW,YAAcC,EAAQ,MAE1EG,EAAWE,EACbN,EAAW,WAAaI,EAAWH,EAAQ,KAClCI,EAAYE,IACrBP,EAAW,WAAaK,EAAYL,EAAW,YAAcC,EAAQ,MAEzE,CACF,CAGF,GAAIvQ,EAAK,kBAAoB,QAAaA,EAAK,kBAAoB,IAAMS,EAAK,UAAU,SAAS,SAAS,EAAG,CAC3G,MAAMqQ,EAAcrQ,EAAK,cAAcqJ,EAAyB,EAChE,GAAIgH,GAAe,SAAS,gBAAkBA,EAC5C,GAAI,CACFA,EAAY,MAAM,CAAE,cAAe,EAAA,CAAM,CAC3C,MAAQ,CAER,CAEJ,SAAW,CAACrQ,EAAK,SAAS,SAAS,aAAa,EAAG,CAC5CA,EAAK,aAAa,UAAU,GAAGA,EAAK,aAAa,WAAY,IAAI,EACtE,GAAI,CACFA,EAAK,MAAM,CAAE,cAAe,EAAA,CAAM,CACpC,MAAQ,CAER,CACF,CACF,CACF,CACF,CCjRA,MAAMsQ,MAAgB,QAgBtB,SAASC,GAAoBhR,EAAoBS,EAAyB,CACxE,MAAMuK,EAAW9B,GAAoBzI,CAAI,EACnCkN,EAAWvE,GAAoB3I,CAAI,EACrCuK,EAAW,GAAK2C,EAAW,IAE/B3N,EAAK,UAAYgL,EACjBhL,EAAK,UAAY2N,EAKjBtE,GAAerJ,EAAK,OAAO,EAC3BS,EAAK,UAAU,IAAI,YAAY,EAC/BA,EAAK,aAAa,gBAAiB,MAAM,EAC3C,CAQA,SAASwQ,GACPjR,EACAkR,EACA7E,EACA9O,EACgB,CAGhB,IAAI6R,EAAyB,KAG7B,MAAMD,EAAO9C,EAAE,eAAA,EASf,GARI8C,GAAQA,EAAK,OAAS,EACxBC,EAASD,EAAK,CAAC,EAEfC,EAAS/C,EAAE,OAKT+C,GAAU,CAAC8B,EAAW,SAAS9B,CAAM,EAAG,CAC1C,MAAM+B,EAAY,SAAS,iBAAiB9E,EAAE,QAASA,EAAE,OAAO,EAC5D8E,IACF/B,EAAS+B,EAEb,CAGA,MAAMxC,EAASS,GAAQ,UAAU,YAAY,EACvC5O,EAAQ4O,GAAQ,UAAU,gBAAgB,EAC1CgC,EAAWhC,GAAQ,UAAU,aAAa,EAEhD,IAAIpE,EACA2C,EACA6B,EACAnS,EACAc,EACAoR,EAEJ,OAAIZ,IAEF3D,EAAW,SAAS2D,EAAO,aAAa,UAAU,GAAK,KAAM,EAAE,EAC/DhB,EAAW,SAASgB,EAAO,aAAa,UAAU,GAAK,KAAM,EAAE,EAC3D3D,GAAY,GAAK2C,GAAY,IAC/B6B,EAAMxP,EAAK,MAAMgL,CAAQ,EACzBuE,EAASvP,EAAK,SAAS2N,CAAQ,EAC/BtQ,EAASkS,GAA+B,MACxCpR,EAAQqR,GAAOnS,EAASmS,EAAgCnS,CAAK,EAAI,SAI9D,CACL,KAAAE,EACA,IAAAiS,EACA,SAAUxE,IAAa,QAAaA,GAAY,EAAIA,EAAW,OAC/D,SAAU2C,IAAa,QAAaA,GAAY,EAAIA,EAAW,OAC/D,MAAAtQ,EACA,MAAAc,EACA,OAAAoR,EACA,cAAelD,EACf,YAAasC,GAAU,OACvB,WAAYnO,GAAS,OACrB,SAAU,CAAC,CAAC4Q,EACZ,KACEpG,IAAa,QAAa2C,IAAa,QAAa3C,GAAY,GAAK2C,GAAY,EAC7E,CAAE,IAAK3C,EAAU,IAAK2C,GACtB,MAAA,CAEV,CAOA,SAAS0D,GAAgBrR,EAAoBkR,EAAyB7E,EAAqB,CACzF,MAAMiF,EAAQL,GAAoBjR,EAAMkR,EAAY7E,EAAG,WAAW,GAClDrM,EAAK,yBAAyBsR,CAAK,GAAK,KAItDP,EAAU,IAAI/Q,EAAM,EAAI,CAE5B,CAKA,SAASuR,GAAgBvR,EAAoBkR,EAAyB7E,EAAqB,CACzF,GAAI,CAAC0E,EAAU,IAAI/Q,CAAI,EAAG,OAE1B,MAAMsR,EAAQL,GAAoBjR,EAAMkR,EAAY7E,EAAG,WAAW,EAClErM,EAAK,yBAAyBsR,CAAK,CACrC,CAKA,SAASE,GAAcxR,EAAoBkR,EAAyB7E,EAAqB,CACvF,GAAI,CAAC0E,EAAU,IAAI/Q,CAAI,EAAG,OAE1B,MAAMsR,EAAQL,GAAoBjR,EAAMkR,EAAY7E,EAAG,SAAS,EAChErM,EAAK,uBAAuBsR,CAAK,EACjCP,EAAU,IAAI/Q,EAAM,EAAK,CAC3B,CAkBO,SAASyR,GAAyBzR,EAAoB9D,EAAqBwV,EAA2B,CAE3GxV,EAAO,iBACL,YACCmQ,GAAM,CACL,MAAM5L,EAAQ4L,EAAE,OAAuB,QAAQ,iBAAiB,EAC3D5L,IAGDA,EAAK,UAAU,SAAS,SAAS,GAErCuQ,GAAoBhR,EAAMS,CAAI,EAChC,EACA,CAAE,OAAAiR,CAAA,CAAO,EAIXxV,EAAO,iBACL,QACCmQ,GAAM,CACL,MAAM7L,EAAS6L,EAAE,OAAuB,QAAQ,gBAAgB,EAC5D7L,GAAOiO,GAAezO,EAAMqM,EAAiB7L,CAAK,CACxD,EACA,CAAE,OAAAkR,CAAA,CAAO,EAIXxV,EAAO,iBACL,WACCmQ,GAAM,CACL,MAAM7L,EAAS6L,EAAE,OAAuB,QAAQ,gBAAgB,EAC5D7L,GAAOiO,GAAezO,EAAMqM,EAAiB7L,CAAK,CACxD,EACA,CAAE,OAAAkR,CAAA,CAAO,CAEb,CAgBO,SAASC,GACd3R,EACA4R,EACAV,EACAQ,EACM,CAENE,EAAY,iBAAiB,UAAYvF,GAAMyC,GAAkB9O,EAAMqM,CAAC,EAAG,CAAE,OAAAqF,EAAQ,EAGrFR,EAAW,iBAAiB,YAAc7E,GAAMgF,GAAgBrR,EAAMkR,EAAY7E,CAAe,EAAG,CAAE,OAAAqF,CAAA,CAAQ,EAG9G,SAAS,iBAAiB,YAAcrF,GAAkBkF,GAAgBvR,EAAMkR,EAAY7E,CAAC,EAAG,CAAE,OAAAqF,CAAA,CAAQ,EAC1G,SAAS,iBAAiB,UAAYrF,GAAkBmF,GAAcxR,EAAMkR,EAAY7E,CAAC,EAAG,CAAE,OAAAqF,CAAA,CAAQ,CACxG,CC5OO,SAASG,GAAkBlT,EAAY2H,EAAoB,CAChE,OAAI3H,GAAK,MAAQ2H,GAAK,KAAa,EAC/B3H,GAAK,KAAa,GAClB2H,GAAK,MACF3H,EAAI2H,EADW,EACH3H,EAAI2H,EAAI,GAAK,CAClC,CAMO,SAASwL,GAAe/Q,EAAWqG,EAAsBlG,EAAiC,CAE/F,MAAM6Q,EADM7Q,EAAQ,KAAMlC,GAAMA,EAAE,QAAUoI,EAAU,KAAK,GACnC,gBAAkByK,GACpC,CAAE,MAAAxU,EAAO,UAAA2U,CAAA,EAAc5K,EAE7B,MAAO,CAAC,GAAGrG,CAAI,EAAE,KAAK,CAACkR,EAASC,IACvBH,EAAWE,EAAG5U,CAAK,EAAG6U,EAAG7U,CAAK,EAAG4U,EAAIC,CAAE,EAAIF,CACnD,CACH,CAMA,SAASG,GAAsBnS,EAAuBoS,EAAiBhS,EAAsBiS,EAAmB,CAC9GrS,EAAK,MAAQoS,EAEbpS,EAAK,mBAELA,EAAK,SAAS,QAASsS,GAAOA,EAAE,QAAU,EAAG,EAC7CC,GAAavS,CAAI,EACjBA,EAAK,qBAAqB,EAAI,EAC7BA,EAAgC,cAC/B,IAAI,YAAY,cAAe,CAAE,OAAQ,CAAE,MAAOI,EAAI,MAAO,UAAWiS,EAAI,CAAG,CAAA,EAGjFrS,EAAK,qBAAA,CACP,CAMO,SAASwS,GAAWxS,EAAoBI,EAA8B,CACvE,CAACJ,EAAK,YAAcA,EAAK,WAAW,QAAUI,EAAI,OAC/CJ,EAAK,eAAiB,gBAAkBA,EAAK,MAAM,MAAA,GACxDyS,GAAUzS,EAAMI,EAAK,CAAC,GACbJ,EAAK,WAAW,YAAc,EACvCyS,GAAUzS,EAAMI,EAAK,EAAE,GAEvBJ,EAAK,WAAa,KAElBA,EAAK,mBAELA,EAAK,SAAS,QAASsS,GAAOA,EAAE,QAAU,EAAG,EAC7CtS,EAAK,MAAQA,EAAK,gBAAgB,MAAA,EAClCuS,GAAavS,CAAI,EAEDA,EAAK,cAAc,iBAAiB,gCAAgC,GAC3E,QAAS2F,GAAM,CACjBA,EAAE,aAAa,WAAW,GACtBA,EAAE,aAAa,WAAW,IAAM,aAAeA,EAAE,aAAa,WAAW,IAAM,gBAEjF3F,EAAK,YAAY2F,EAAE,aAAa,YAAa,MAAM,GAHxBA,EAAE,aAAa,YAAa,MAAM,CAKtE,CAAC,EACD3F,EAAK,qBAAqB,EAAI,EAC7BA,EAAgC,cAC/B,IAAI,YAAY,cAAe,CAAE,OAAQ,CAAE,MAAOI,EAAI,MAAO,UAAW,EAAE,CAAG,CAAA,EAG/EJ,EAAK,qBAAA,EAET,CAQO,SAASyS,GAAUzS,EAAoBI,EAAwBiS,EAAmB,CACvFrS,EAAK,WAAa,CAAE,MAAOI,EAAI,MAAO,UAAWiS,CAAA,EAEjD,MAAMjL,EAAuB,CAAE,MAAOhH,EAAI,MAAO,UAAWiS,CAAA,EACtDnR,EAAUlB,EAAK,SAKf0S,GAF4B1S,EAAK,iBAAiB,aAAe8R,IAEhD9R,EAAK,MAAOoH,EAAWlG,CAAO,EAGjDwR,GAAU,OAAQA,EAA8B,MAAS,WAE1DA,EAA8B,KAAMN,GAAe,CAClDD,GAAmBnS,EAAMoS,EAAYhS,EAAKiS,CAAG,CAC/C,CAAC,EAGDF,GAAmBnS,EAAM0S,EAAqBtS,EAAKiS,CAAG,CAE1D,CChGA,SAASM,EAAiB3S,EAAoBI,EAA8B,CAG1E,OADqBJ,EAAK,iBAAiB,WAAa,IACjCI,EAAI,WAAa,EAC1C,CAOA,SAASwS,EAAkB5S,EAAoBI,EAA8B,CAI3E,OAFsBJ,EAAK,iBAAiB,YAAc,IAElCI,EAAI,YAAc,EAC5C,CAKA,SAASyS,GAAQtJ,EAAsBuJ,EAAuB,CACxD,OAAOA,GAAS,SAClBvJ,EAAQ,YAAcuJ,EACbA,aAAgB,cACzBvJ,EAAQ,UAAY,GACpBA,EAAQ,YAAYuJ,EAAK,UAAU,EAAI,CAAC,EAE5C,CAKA,SAASC,EAAoB/S,EAAoBI,EAAkC,CACjF,MAAM0S,EAAO,SAAS,cAAc,MAAM,EAC1CjT,GAAQiT,EAAM,gBAAgB,EAC9B,MAAME,EAAShT,EAAK,YAAY,QAAUI,EAAI,MAAQJ,EAAK,WAAW,UAAY,EAC5EiT,EAAQ,CAAE,GAAGhW,EAAoB,GAAG+C,EAAK,KAAA,EACzCkT,EAAYF,IAAW,EAAIC,EAAM,QAAUD,IAAW,GAAKC,EAAM,SAAWA,EAAM,SACxF,OAAAJ,GAAQC,EAAMI,CAAS,EAChBJ,CACT,CAKA,SAASK,GAAmBnT,EAAoB2N,EAAkBlN,EAAgC,CAChG,MAAM2S,EAAS,SAAS,cAAc,KAAK,EAC3C,OAAAA,EAAO,UAAY,gBACnBA,EAAO,aAAa,cAAe,MAAM,EACzCA,EAAO,iBAAiB,YAAc/G,GAAkB,CACtDA,EAAE,gBAAA,EACFA,EAAE,eAAA,EACFrM,EAAK,kBAAkB,MAAMqM,EAAGsB,EAAUlN,CAAI,CAChD,CAAC,EACD2S,EAAO,iBAAiB,WAAa/G,GAAkB,CACrDA,EAAE,gBAAA,EACFA,EAAE,eAAA,EACFrM,EAAK,kBAAkB,YAAY2N,CAAQ,CAC7C,CAAC,EACMyF,CACT,CAKA,SAASC,GAAkBrT,EAAoBI,EAAqBuN,EAAkBlN,EAAyB,CAC7GA,EAAK,UAAU,IAAI,UAAU,EAC7BA,EAAK,SAAW,EAChB,MAAMuS,EAAShT,EAAK,YAAY,QAAUI,EAAI,MAAQJ,EAAK,WAAW,UAAY,EAClFS,EAAK,aAAa,YAAauS,IAAW,EAAI,OAASA,IAAW,EAAI,YAAc,YAAY,EAEhGvS,EAAK,iBAAiB,QAAU4L,GAAM,CAChCrM,EAAK,mBAAmB,YACxBA,EAAK,uBAAuBqM,EAAGsB,EAAUlN,CAAI,GACjD+R,GAAWxS,EAAMI,CAAG,CACtB,CAAC,EACDK,EAAK,iBAAiB,UAAY4L,GAAM,CACtC,GAAIA,EAAE,MAAQ,SAAWA,EAAE,MAAQ,IAAK,CAEtC,GADAA,EAAE,eAAA,EACErM,EAAK,uBAAuBqM,EAA4BsB,EAAUlN,CAAI,EAAG,OAC7E+R,GAAWxS,EAAMI,CAAG,CACtB,CACF,CAAC,CACH,CAMA,SAASkT,GAAqB7S,EAAmB2N,EAA2C,CAC1F,GAAIA,GAAU,KACd,GAAI,OAAOA,GAAW,SAAU,CAE9B,MAAM0B,EAAY,SAAS,cAAc,MAAM,EAG/C,IAFAA,EAAU,UAAY9N,EAAaoM,CAAM,EAElC0B,EAAU,YACfrP,EAAK,YAAYqP,EAAU,UAAU,CAEzC,MAAW1B,aAAkB,MAC3B3N,EAAK,YAAY2N,CAAM,CAE3B,CAeO,SAASmE,GAAavS,EAA0B,CACrDA,EAAK,aAAeA,EAAK,cAAA,EACzB,MAAMuT,EAAYvT,EAAK,aAGlBuT,IAILA,EAAU,UAAY,GAEtBvT,EAAK,gBAAgB,QAAQ,CAACI,EAAqBC,IAAc,CAC/D,MAAMI,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAY,OACjBZ,GAAQY,EAAM,aAAa,EAC3BA,EAAK,aAAa,OAAQ,cAAc,EAGxCA,EAAK,aAAa,gBAAiB,OAAOJ,EAAI,CAAC,CAAC,EAChDI,EAAK,aAAa,aAAcL,EAAI,KAAK,EACzCK,EAAK,aAAa,WAAY,OAAOJ,CAAC,CAAC,EAGvC,MAAMmT,EAAcpT,EAAI,QAAUA,EAAI,MAChCqT,EAAgBzT,EAAK,YAAY,QAAUI,EAAI,MAAQJ,EAAK,WAAW,UAAY,EACnFoH,EAAmCqM,IAAkB,EAAI,MAAQA,IAAkB,GAAK,OAAS,KAGvG,GAAIrT,EAAI,eAAgB,CAEtB,MAAM0C,EAA8B,CAClC,OAAQ1C,EACR,MAAOoT,EACP,UAAApM,EACA,aAAc,GACd,OAAQ3G,EACR,eAAgB,IAAOkS,EAAiB3S,EAAMI,CAAG,EAAI2S,EAAoB/S,EAAMI,CAAG,EAAI,KACtF,mBAAoB,IAAM,IAAA,EAGtBgO,EAAShO,EAAI,eAAe0C,CAAG,EACrCwQ,GAAqB7S,EAAM2N,CAAM,EAG7BuE,EAAiB3S,EAAMI,CAAG,GAC5BiT,GAAkBrT,EAAMI,EAAKC,EAAGI,CAAI,EAIlCmS,EAAkB5S,EAAMI,CAAG,IAC7BK,EAAK,UAAU,IAAI,WAAW,EAC9BA,EAAK,YAAY0S,GAAmBnT,EAAMK,EAAGI,CAAI,CAAC,EAEtD,SAESL,EAAI,oBAAqB,CAChC,MAAM0C,EAAM,CACV,OAAQ1C,EACR,MAAOoT,CAAA,EAGHpF,EAAShO,EAAI,oBAAoB0C,CAAG,EAEpC4Q,EAAO,SAAS,cAAc,MAAM,EACtCtF,GAAU,KACZsF,EAAK,YAAcF,EACV,OAAOpF,GAAW,SAC3BsF,EAAK,UAAY1R,EAAaoM,CAAM,EAC3BA,aAAkB,MAC3BsF,EAAK,YAAYtF,CAAM,EAEzB3N,EAAK,YAAYiT,CAAI,EAGjBf,EAAiB3S,EAAMI,CAAG,IAC5BiT,GAAkBrT,EAAMI,EAAKC,EAAGI,CAAI,EACpCA,EAAK,YAAYsS,EAAoB/S,EAAMI,CAAG,CAAC,GAE7CwS,EAAkB5S,EAAMI,CAAG,IAC7BK,EAAK,UAAU,IAAI,WAAW,EAC9BA,EAAK,YAAY0S,GAAmBnT,EAAMK,EAAGI,CAAI,CAAC,EAEtD,SAESL,EAAI,iBACX,MAAM,KAAKA,EAAI,iBAAiB,UAAU,EAAE,QAAS4D,GAAMvD,EAAK,YAAYuD,EAAE,UAAU,EAAI,CAAC,CAAC,EAG1F2O,EAAiB3S,EAAMI,CAAG,IAC5BiT,GAAkBrT,EAAMI,EAAKC,EAAGI,CAAI,EACpCA,EAAK,YAAYsS,EAAoB/S,EAAMI,CAAG,CAAC,GAE7CwS,EAAkB5S,EAAMI,CAAG,IAC7BK,EAAK,UAAU,IAAI,WAAW,EAC9BA,EAAK,YAAY0S,GAAmBnT,EAAMK,EAAGI,CAAI,CAAC,OAIjD,CACH,MAAMiT,EAAO,SAAS,cAAc,MAAM,EAC1CA,EAAK,YAAcF,EACnB/S,EAAK,YAAYiT,CAAI,EAGjBf,EAAiB3S,EAAMI,CAAG,IAC5BiT,GAAkBrT,EAAMI,EAAKC,EAAGI,CAAI,EACpCA,EAAK,YAAYsS,EAAoB/S,EAAMI,CAAG,CAAC,GAE7CwS,EAAkB5S,EAAMI,CAAG,IAC7BK,EAAK,UAAU,IAAI,WAAW,EAC9BA,EAAK,YAAY0S,GAAmBnT,EAAMK,EAAGI,CAAI,CAAC,EAEtD,CAEA8S,EAAU,YAAY9S,CAAI,CAC5B,CAAC,EAGD8S,EAAU,iBAAiB,gBAAgB,EAAE,QAASnW,GAAO,CACtDA,EAAG,aAAa,WAAW,GAAGA,EAAG,aAAa,YAAa,MAAM,CACxE,CAAC,EAIGmW,EAAU,SAAS,OAAS,GAC9BA,EAAU,aAAa,OAAQ,KAAK,EACpCA,EAAU,aAAa,gBAAiB,GAAG,IAE3CA,EAAU,gBAAgB,MAAM,EAChCA,EAAU,gBAAgB,eAAe,GAE7C,CCnQA,MAAMI,GAAkB,OAAO,qBAAwB,WAkBhD,SAASC,GAAatL,EAAgDsH,EAAwC,CACnH,OAAI+D,GACK,oBAAoBrL,EAAUsH,CAAO,EAIvC,OAAO,WAAW,IAAM,CAC7B,MAAMrF,EAAQ,KAAK,IAAA,EACnBjC,EAAS,CACP,WAAY,GACZ,cAAe,IAAM,KAAK,IAAI,EAAG,IAAM,KAAK,IAAA,EAAQiC,EAAM,CAAA,CAC3D,CACH,EAAG,CAAC,CACN,CAKO,SAASsJ,GAAWT,EAAsB,CAC3CO,GACF,mBAAmBP,CAAM,EAEzB,aAAaA,CAAM,CAEvB,CCrCO,SAASU,GAAqBC,EAAsC,CACzE,MAAMC,EAAU,SAAS,cAAc,KAAK,EAC5C,OAAAA,EAAQ,UAAY,4BAA4BD,CAAI,GACpDC,EAAQ,aAAa,OAAQ,aAAa,EAC1CA,EAAQ,aAAa,aAAc,SAAS,EACrCA,CACT,CAOO,SAASC,GAAqBF,EAAyBnV,EAAuD,CACnH,GAAIA,EAAU,CAEZ,MAAM8T,EAAS9T,EADiB,CAAE,KAAAmV,CAAA,CACH,EAC/B,GAAI,OAAOrB,GAAW,SAAU,CAC9B,MAAMwB,EAAU,SAAS,cAAc,KAAK,EAC5C,OAAAA,EAAQ,UAAYxB,EACbwB,CACT,CACA,OAAOxB,CACT,CAEA,OAAOoB,GAAqBC,CAAI,CAClC,CAMO,SAASI,GAAqBvV,EAAuD,CAC1F,MAAMwV,EAAU,SAAS,cAAc,KAAK,EAC5C,OAAAA,EAAQ,UAAY,sBACpBA,EAAQ,aAAa,OAAQ,QAAQ,EACrCA,EAAQ,aAAa,YAAa,QAAQ,EAC1CA,EAAQ,YAAYH,GAAqB,QAASrV,CAAQ,CAAC,EACpDwV,CACT,CAOO,SAASC,GAAmBC,EAAmBC,EAA8B,CAClFD,EAAS,YAAYC,CAAS,CAChC,CAMO,SAASC,GAAmBD,EAA0C,CAC3EA,GAAW,OAAA,CACb,CAOO,SAASE,GAAmBjU,EAAoBkU,EAAwB,CACzEA,GACFlU,EAAM,UAAU,IAAI,iBAAiB,EACrCA,EAAM,aAAa,YAAa,MAAM,IAEtCA,EAAM,UAAU,OAAO,iBAAiB,EACxCA,EAAM,gBAAgB,WAAW,EAErC,CAOO,SAASmU,GAAoBhG,EAAqB+F,EAAwB,CAC3EA,GACF/F,EAAO,UAAU,IAAI,kBAAkB,EACvCA,EAAO,aAAa,YAAa,MAAM,IAEvCA,EAAO,UAAU,OAAO,kBAAkB,EAC1CA,EAAO,gBAAgB,WAAW,EAEtC,CClDO,IAAKiG,GAAAA,IAEVA,EAAAA,EAAA,MAAQ,CAAA,EAAR,QAEAA,EAAAA,EAAA,eAAiB,CAAA,EAAjB,iBAEAA,EAAAA,EAAA,OAAS,CAAA,EAAT,SAEAA,EAAAA,EAAA,KAAO,CAAA,EAAP,OAEAA,EAAAA,EAAA,QAAU,CAAA,EAAV,UAEAA,EAAAA,EAAA,KAAO,CAAA,EAAP,OAZUA,IAAAA,GAAA,CAAA,CAAA,EA4CL,MAAMC,EAAgB,CAClB3P,GAGT4P,GAAiC,EAGjCC,GAAa,EAGbC,GAAsC,KACtCC,GAAqC,KAGrCC,GAA6C,KAC7CC,GAAqB,GAErB,YAAY/P,EAA4B,CACtC,KAAKF,GAAaE,CACpB,CASA,aAAagQ,EAAoBC,EAAuB,CAElDD,EAAQ,KAAKN,KACf,KAAKA,GAAgBM,GAInB,KAAKL,KAAe,IACtB,KAAKO,GAAA,EACL,KAAKP,GAAa,sBAAsB,IAAM,KAAKQ,IAAQ,EAE/D,CAMA,WAA2B,CACzB,OAAI,KAAKP,GACA,KAAKA,GAEP,QAAQ,QAAA,CACjB,CAMA,wBAAwBQ,EAA4B,CAClD,KAAKN,GAAwBM,CAC/B,CAMA,QAAe,CACT,KAAKT,KAAe,IACtB,qBAAqB,KAAKA,EAAU,EACpC,KAAKA,GAAa,GAEpB,KAAKD,GAAgB,EAGjB,KAAKG,KACP,KAAKA,GAAA,EACL,KAAKA,GAAgB,KACrB,KAAKD,GAAgB,KAEzB,CAKA,IAAI,WAAqB,CACvB,OAAO,KAAKF,KAAkB,CAChC,CAKA,IAAI,cAAgC,CAClC,OAAO,KAAKA,EACd,CAMAQ,IAA4B,CACrB,KAAKN,KACR,KAAKA,GAAgB,IAAI,QAAeS,GAAY,CAClD,KAAKR,GAAgBQ,CACvB,CAAC,EAEL,CAMAF,IAAe,CAIb,GAHA,KAAKR,GAAa,EAGd,CAAC,KAAK7P,GAAW,cAAe,CAClC,KAAK4P,GAAgB,EACjB,KAAKG,KACP,KAAKA,GAAA,EACL,KAAKA,GAAgB,KACrB,KAAKD,GAAgB,MAEvB,MACF,CAEA,MAAMI,EAAQ,KAAKN,GACnB,KAAKA,GAAgB,EAUjBM,GAAS,GACX,KAAKlQ,GAAW,YAAA,EAMdkQ,GAAS,GACX,KAAKlQ,GAAW,YAAA,EAIdkQ,GAAS,IACX,KAAKlQ,GAAW,eAAA,EAChB,KAAKA,GAAW,eAAA,GAIdkQ,GAAS,GACX,KAAKlQ,GAAW,aAAA,EAIdkQ,GAAS,GACX,KAAKlQ,GAAW,oBAAA,EAIdkQ,GAAS,GACX,KAAKlQ,GAAW,YAAA,EAId,CAAC,KAAKiQ,IAAsB,KAAKD,KACnC,KAAKC,GAAqB,GAC1B,KAAKD,GAAA,GAIH,KAAKD,KACP,KAAKA,GAAA,EACL,KAAKA,GAAgB,KACrB,KAAKD,GAAgB,KAEzB,CACF,CChRO,SAASU,GAAuB1V,EAAsC,CAC3E,IAAI2V,EAA+E,KAC/EC,EAA4B,KAC5BC,EAA4B,KAC5BC,EAAgC,KACpC,MAAMC,EAAU1J,GAAkB,CAChC,GAAI,CAACsJ,EAAa,OAClB,MAAMK,EAAQ3J,EAAE,QAAUsJ,EAAY,OAChCM,EAAQ,KAAK,IAAI,GAAIN,EAAY,WAAaK,CAAK,EACnD5V,EAAMJ,EAAK,gBAAgB2V,EAAY,QAAQ,EACrDvV,EAAI,MAAQ6V,EACZ7V,EAAI,cAAgB,GACpBA,EAAI,gBAAkB6V,EAClBL,GAAc,OAChBA,EAAa,sBAAsB,IAAM,CACvCA,EAAa,KACb5V,EAAK,iBAAA,CACP,CAAC,GAEFA,EAAgC,cAC/B,IAAI,YAAY,gBAAiB,CAAE,OAAQ,CAAE,MAAOI,EAAI,MAAO,MAAA6V,EAAM,CAAG,CAAA,CAE5E,EACA,IAAIC,EAAqB,GACzB,MAAMC,EAAO,IAAM,CACjB,MAAMC,EAAYT,IAAgB,KAE9BS,IACFF,EAAqB,GACrB,sBAAsB,IAAM,CAC1BA,EAAqB,EACvB,CAAC,GAEH,OAAO,oBAAoB,YAAaH,CAAM,EAC9C,OAAO,oBAAoB,UAAWI,CAAI,EACtCN,IAAe,OACjB,SAAS,gBAAgB,MAAM,OAASA,EACxCA,EAAa,MAEXC,IAAmB,OACrB,SAAS,KAAK,MAAM,WAAaA,EACjCA,EAAiB,MAEnBH,EAAc,KAEVS,GAAapW,EAAK,oBACpBA,EAAK,mBAAA,CAET,EACA,MAAO,CACL,IAAI,YAAa,CACf,OAAO2V,IAAgB,MAAQO,CACjC,EACA,MAAM7J,EAAGsB,EAAUlN,EAAM,CACvB4L,EAAE,eAAA,EAIF,MAAMjM,EAAMJ,EAAK,gBAAgB2N,CAAQ,EAEnC0I,EAAW,OAAOjW,GAAK,OAAU,SAAWA,EAAI,MAAQ,OACxDkW,EAAalW,GAAK,iBAAmBiW,GAAY5V,EAAK,wBAAwB,MACpFkV,EAAc,CAAE,OAAQtJ,EAAE,QAAS,SAAAsB,EAAU,WAAA2I,CAAA,EAC7C,OAAO,iBAAiB,YAAaP,CAAM,EAC3C,OAAO,iBAAiB,UAAWI,CAAI,EACnCN,IAAe,OAAMA,EAAa,SAAS,gBAAgB,MAAM,QACrE,SAAS,gBAAgB,MAAM,OAAS,WACpCC,IAAmB,OAAMA,EAAiB,SAAS,KAAK,MAAM,YAClE,SAAS,KAAK,MAAM,WAAa,MACnC,EACA,YAAYnI,EAAU,CACpB,MAAMvN,EAAMJ,EAAK,gBAAgB2N,CAAQ,EACpCvN,IAGLA,EAAI,cAAgB,GACpBA,EAAI,gBAAkB,OACtBA,EAAI,MAAQA,EAAI,gBAEhBJ,EAAK,iBAAA,EACLA,EAAK,qBAAA,EACJA,EAAgC,cAC/B,IAAI,YAAY,sBAAuB,CAAE,OAAQ,CAAE,MAAOI,EAAI,MAAO,MAAOA,EAAI,KAAA,EAAS,CAAA,EAE7F,EACA,SAAU,CACR+V,EAAA,CACF,CAAA,CAEJ,CCtEA,MAAMI,GAAiB,iBAKjBC,GAAmD,CACvD,OAAQ,4BACR,OAAQ,4BACR,OAAQ,2BACV,EAKMC,GAAsD,CAC1D,OAAQ,IACR,OAAQ,IACR,OAAQ,GACV,EAOA,SAASC,GAAcvY,EAAuB,CAC5C,MAAMwY,EAAUxY,EAAM,KAAA,EAAO,YAAA,EAC7B,OAAIwY,EAAQ,SAAS,IAAI,EAChB,WAAWA,CAAO,EAEvBA,EAAQ,SAAS,GAAG,EACf,WAAWA,CAAO,EAAI,IAExB,WAAWA,CAAO,CAC3B,CAMA,SAASC,GAAqBpW,EAAoBqW,EAAyC,CACzF,MAAMC,EAAON,GAAeK,CAAa,EACnCE,EAAW,iBAAiBvW,CAAK,EAAE,iBAAiBsW,CAAI,EAC9D,GAAIC,EAAU,CACZ,MAAMC,EAASN,GAAcK,CAAQ,EACrC,GAAI,CAAC,MAAMC,CAAM,GAAKA,EAAS,EAC7B,OAAOA,CAEX,CACA,OAAOP,GAAkBI,CAAa,CACxC,CAWO,SAASI,GAAkBzW,EAAoBqW,EAAiCK,EAA+B,CAEpH1W,EAAM,gBAAgB+V,EAAc,EAG/B/V,EAAM,YAGXA,EAAM,aAAa+V,GAAgBM,CAAa,EAGhD,MAAMM,EAAWP,GAAqBpW,EAAOqW,CAAa,EAE1D,WAAW,IAAM,CAIXA,IAAkB,UACpBrW,EAAM,gBAAgB+V,EAAc,CAGxC,EAAGY,CAAQ,CACb,CAUO,SAASC,GAAcpX,EAAuBgL,EAAkB6L,EAA0C,CAE/G,GAAI7L,EAAW,EACb,MAAO,GAGT,MAAMxK,EAAQR,EAAK,yBAAyBgL,CAAQ,EACpD,OAAKxK,GAKLyW,GAAkBzW,EAAOqW,CAAa,EAC/B,IAJE,EAKX,CAUO,SAASQ,GAAerX,EAAuBsX,EAAsBT,EAAyC,CACnH,IAAIU,EAAgB,EACpB,UAAWvM,KAAYsM,EACjBF,GAAWpX,EAAMgL,EAAU6L,CAAa,GAC1CU,IAGJ,OAAOA,CACT,CAUO,SAASC,GAAkBxX,EAAuB8L,EAAe+K,EAA0C,CAEhH,MAAM9V,EAAOf,EAAK,OAAS,CAAA,EACrByX,EAAWzX,EAAK,SACtB,GAAI,CAACyX,EACH,MAAO,GAGT,MAAMzM,EAAWjK,EAAK,UAAWyO,GAAQiI,EAASjI,CAAG,IAAM1D,CAAK,EAChE,OAAId,EAAW,EACN,GAEFoM,GAAWpX,EAAMgL,EAAU6L,CAAa,CACjD,CCzJO,SAASa,GACdpI,EACAqI,EACArL,EAC0B,CAC1B,MAAMlP,EAAK,SAAS,cAAckS,CAAG,EAErC,GAAIqI,EACF,UAAWjU,KAAOiU,EAAO,CACvB,MAAMxZ,EAAQwZ,EAAMjU,CAAG,EACIvF,GAAU,MACnCf,EAAG,aAAasG,EAAKvF,CAAK,CAE9B,CAcF,OAAOf,CACT,CAYO,SAASwa,EAAIC,EAAoBF,EAAgD,CACtF,MAAMva,EAAK,SAAS,cAAc,KAAK,EAEvC,GADIya,MAAc,UAAYA,GAC1BF,EACF,UAAWjU,KAAOiU,EAAO,CACvB,MAAMxZ,EAAQwZ,EAAMjU,CAAG,EACIvF,GAAU,MACnCf,EAAG,aAAasG,EAAKvF,CAAK,CAE9B,CAEF,OAAOf,CACT,CAKO,SAAS0a,GAAOD,EAAoBF,EAAgC7Q,EAA4C,CACrH,MAAM1J,EAAK,SAAS,cAAc,QAAQ,EAE1C,GADIya,MAAc,UAAYA,GAC1BF,EACF,UAAWjU,KAAOiU,EAAO,CACvB,MAAMxZ,EAAQwZ,EAAMjU,CAAG,EACIvF,GAAU,MACnCf,EAAG,aAAasG,EAAKvF,CAAK,CAE9B,CASF,OAAOf,CACT,CA6BA,MAAM2a,GAAsB,SAAS,cAAc,UAAU,EAC7DA,GAAoB,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBzB,SAASC,IAAqC,CACnD,OAAOD,GAAoB,QAAQ,UAAU,EAAI,CACnD,CAoBO,SAASE,GAAarI,EAA2C,CACtE,MAAMlC,EAAW,SAAS,uBAAA,EAEpBtL,EAAOwV,EAAIhI,EAAQ,SAAW,0BAA4B,eAAe,EAE/E,GAAIA,EAAQ,UAAYA,EAAQ,aAAeA,EAAQ,UAErDxN,EAAK,YAAYwN,EAAQ,WAAW,EACpCxN,EAAK,YAAYwN,EAAQ,SAAS,MAC7B,CAEL,MAAMsI,EAAiBN,EAAI,kBAAkB,EAC7CM,EAAe,YAAYF,IAAkB,EAC7C5V,EAAK,YAAY8V,CAAc,CACjC,CAEA,OAAAxK,EAAS,YAAYtL,CAAI,EAClBsL,CACT,CAgCO,SAASyK,GAAiBvI,EAA6C,CAC5E,MAAMpS,EAASoa,EAAI,mBAAoB,CAAE,KAAM,eAAgB,KAAM,eAAgB,EAGrF,GAAIhI,EAAQ,MAAO,CACjB,MAAMwI,EAAUR,EAAI,iBAAiB,EACrCQ,EAAQ,YAAcxI,EAAQ,MAC9BpS,EAAO,YAAY4a,CAAO,CAC5B,CAGA,MAAMtR,EAAU8Q,EAAI,oBAAqB,CACvC,KAAM,gBACN,KAAM,eACN,gCAAiC,EAAA,CAClC,EACDpa,EAAO,YAAYsJ,CAAO,EAG1B,MAAMuR,EAAUT,EAAI,oBAAqB,CAAE,KAAM,gBAAiB,KAAM,eAAgB,EAGxF,UAAWU,KAAO1I,EAAQ,cACpB0I,EAAI,WACND,EAAQ,YAAYT,EAAI,2BAA4B,CAAE,uBAAwBU,EAAI,EAAA,CAAI,CAAC,EAI3F,UAAWA,KAAO1I,EAAQ,WACpB0I,EAAI,WACND,EAAQ,YAAYT,EAAI,2BAA4B,CAAE,uBAAwBU,EAAI,EAAA,CAAI,CAAC,EAY3F,IANE1I,EAAQ,cAAc,KAAMtJ,GAAMA,EAAE,SAAS,GAAKsJ,EAAQ,WAAW,KAAMtJ,GAAMA,EAAE,SAAS,IACtEsJ,EAAQ,WAC9ByI,EAAQ,YAAYT,EAAI,uBAAuB,CAAC,EAI9ChI,EAAQ,UAAW,CACrB,MAAM2I,EAAYT,GAAOlI,EAAQ,YAAc,yBAA2B,kBAAmB,CAC3F,oBAAqB,GACrB,MAAO,WACP,aAAc,wBACd,eAAgB,OAAOA,EAAQ,WAAW,EAC1C,gBAAiB,gBAAA,CAClB,EACD2I,EAAU,UAAY3I,EAAQ,cAC9ByI,EAAQ,YAAYE,CAAS,CAC/B,CAEA,OAAA/a,EAAO,YAAY6a,CAAO,EACnB7a,CACT,CAwBO,SAASgb,GAAe5I,EAA2C,CACxE,MAAM6I,EAAOb,EAAI,gBAAgB,EAC3Bc,EAAW9I,EAAQ,OAAO,OAAS,EACnC+I,EAAgB/I,EAAQ,OAAO,SAAW,EAG1CgJ,EAAchB,EAAI,kBAAkB,EAC1CgB,EAAY,YAAYZ,IAAkB,EAG1C,IAAIa,EAA8B,KAClC,GAAIH,EAAU,CACZG,EAAUnB,GAAc,QAAS,CAC/B,MAAO9H,EAAQ,YAAc,sBAAwB,iBACrD,KAAM,aACN,gBAAiBA,EAAQ,SACzB,KAAM,eACN,GAAI,gBAAA,CACL,EAGD,MAAMkJ,EAAuBlJ,EAAQ,WAAa,OAAS,QAAU,OACrEiJ,EAAQ,YACNjB,EAAI,wBAAyB,CAC3B,qBAAsB,GACtB,uBAAwBkB,EACxB,cAAe,MAAA,CAChB,CAAA,EAIH,MAAMC,EAAenB,EAAI,yBAA0B,CAAE,KAAM,eAAgB,EACrEoB,EAAYpB,EAAI,eAAe,EAErC,UAAWqB,KAASrJ,EAAQ,OAAQ,CAClC,MAAMsJ,EAAiB,wBAAwBD,EAAM,WAAa,YAAc,EAAE,GAAGN,EAAgB,UAAY,EAAE,GAC7GQ,EAAUvB,EAAIsB,EAAgB,CAAE,eAAgBD,EAAM,GAAI,EAG1DG,EAAYtB,GAAO,uBAAwB,CAC/C,gBAAiB,OAAOmB,EAAM,UAAU,EACxC,gBAAiB,eAAeA,EAAM,EAAE,EAAA,CACzC,EAID,GAHIN,GAAeS,EAAU,aAAa,gBAAiB,MAAM,EAG7DH,EAAM,KAAM,CACd,MAAMI,EAAW3B,GAAc,OAAQ,CAAE,MAAO,qBAAsB,EACtE2B,EAAS,UAAYJ,EAAM,KAC3BG,EAAU,YAAYC,CAAQ,CAChC,CAGA,MAAMC,EAAY5B,GAAc,OAAQ,CAAE,MAAO,sBAAuB,EAKxE,GAJA4B,EAAU,YAAcL,EAAM,MAC9BG,EAAU,YAAYE,CAAS,EAG3B,CAACX,EAAe,CAClB,MAAMY,EAAc7B,GAAc,OAAQ,CAAE,MAAO,wBAAyB,EAC5E6B,EAAY,UAAYN,EAAM,WAAarJ,EAAQ,aAAeA,EAAQ,WAC1EwJ,EAAU,YAAYG,CAAW,CACnC,CAEAJ,EAAQ,YAAYC,CAAS,EAG7BD,EAAQ,YACNvB,EAAI,wBAAyB,CAC3B,GAAI,eAAeqB,EAAM,EAAE,GAC3B,KAAM,cAAA,CACP,CAAA,EAGHD,EAAU,YAAYG,CAAO,CAC/B,CAEAJ,EAAa,YAAYC,CAAS,EAClCH,EAAQ,YAAYE,CAAY,CAClC,CAGA,OAAInJ,EAAQ,WAAa,QAAUiJ,GACjCJ,EAAK,YAAYI,CAAO,EACxBJ,EAAK,YAAYG,CAAW,IAE5BH,EAAK,YAAYG,CAAW,EACxBC,GAASJ,EAAK,YAAYI,CAAO,GAGhCJ,CACT,CC9WA,SAASe,EAAa1G,EAAqC,CACzD,OAAKA,EACD,OAAOA,GAAS,SAAiBA,EAE9BA,EAAK,UAHM,EAIpB,CAsFO,SAAS2G,IAA+B,CAC7C,MAAO,CACL,eAAgB,IAChB,mBAAoB,IACpB,oBAAqB,IACrB,wBAAyB,GACzB,sBAAuB,CAAA,EACvB,cAAe,KACf,yBAA0B,IAC1B,8BAA+B,IAC/B,oBAAqB,IACrB,YAAa,GACb,qBAAsB,IACtB,0BAA2B,IAC3B,kBAAmB,IACnB,2BAA4B,IAC5B,qBAAsB,EAAA,CAE1B,CAQO,SAASC,GAAwBnd,EAA0C,CAiBhF,MAfI,GAAAA,GAAQ,QAAQ,OAGhBA,GAAQ,QAAQ,iBAAiB,QAGjCA,GAAQ,YAAY,QAGpBA,GAAQ,gBAAgB,QAGxBA,GAAQ,QAAQ,iBAAiB,QAGjCA,GAAQ,QAAQ,wBAGtB,CAcO,SAASod,GACdpd,EACAP,EACA4d,EAA2B,IACnB,CACR,MAAMC,EAAQtd,GAAQ,QAAQ,OAASP,EAAM,eAAiB,GACxD8d,EAAW,CAAC,CAACD,EACbE,EAAUP,EAAaI,CAAa,EAMpCI,EAAiBzd,GAAQ,QAAQ,iBAAmB,CAAA,EACpD0d,EAAgB,CAAC,GAAGje,EAAM,gBAAgB,QAAQ,EAGlD4K,EAAY,IAAI,IAAIoT,EAAe,IAAKhb,GAAMA,EAAE,EAAE,CAAC,EACnDkb,EAAc,CAAC,GAAGF,CAAc,EACtC,UAAWlT,KAAWmT,EACfrT,EAAU,IAAIE,EAAQ,EAAE,GAC3BoT,EAAY,KAAKpT,CAAO,EAI5B,MAAMqT,EAAmBD,EAAY,OAAS,EACxCE,EAAYpe,EAAM,WAAW,KAAO,EACpCqe,EAAgBF,GAAoBC,EAGpCE,EAAiB,CAAC,GAAGJ,CAAW,EAAE,KAAK,CAACvb,EAAG2H,KAAO3H,EAAE,OAAS,IAAM2H,EAAE,OAAS,EAAE,EAGtF,IAAIiU,EAAc,GAGlB,UAAWzT,KAAWwT,EACpBC,GAAe,+DAA+DzT,EAAQ,EAAE,WAS1F,GALIuT,IACFE,GAAe,6CAIbH,EAAW,CACb,MAAMI,EAASxe,EAAM,YAErBue,GAAe,kBADKC,EAAS,yBAA2B,iBACZ,yFAAyFA,CAAM,oCAAoCT,CAAO,WACxL,CAEA,MAAO;AAAA;AAAA,QAEDD,EAAW,gCAAgCpY,GAAWmY,CAAK,CAAC,SAAW,EAAE;AAAA;AAAA;AAAA,UAGvEU,CAAW;AAAA;AAAA;AAAA,GAIrB,CAyFO,SAASE,GAAmBtd,EAAmBnB,EAAyB,CAC7E,MAAMoV,EAAWjU,EAAK,cAAc,iBAAiB,EACrD,GAAI,CAACiU,EAAU,OAGf,GAAI,CAACpV,EAAM,cAAe,CACxB,MAAM6d,EAAQzI,EAAS,aAAa,OAAO,EACvCyI,IACF7d,EAAM,cAAgB6d,EAE1B,CAGA,MAAMa,EAAiBtJ,EAAS,iBAAiB,yBAAyB,EACtEsJ,EAAe,OAAS,GAAK1e,EAAM,sBAAsB,SAAW,IACtEA,EAAM,sBAAwB,MAAM,KAAK0e,CAAc,GAIxDtJ,EAAyB,MAAM,QAAU,MAC5C,CAiCO,SAASuJ,GACdxd,EACAnB,EACA4e,EACM,CAEN,MAAMC,EAAuB1d,EAAK,cAAc,gCAAgC,EAChF,GAAI,CAAC0d,EAAsB,OAG3B7e,EAAM,wBAA0B,GAGhC,MAAM8e,EAAK,4BACX,GAAI9e,EAAM,0BAA0B,IAAI8e,CAAE,EAAG,OAK7C,MAAMC,EAAuC,CAC3C,GAAAD,EACA,MAAO,EACP,QAEI1L,GAAwB,CAExB,KAAOyL,EAAqB,YAC1BzL,EAAO,YAAYyL,EAAqB,UAAU,EAIpD,MAAO,IAAM,CACX,KAAOzL,EAAO,YACZyL,EAAqB,YAAYzL,EAAO,UAAU,CAEtD,CACF,EAAA,EAGJpT,EAAM,gBAAgB,IAAI8e,EAAIC,CAAU,EACxC/e,EAAM,0BAA0B,IAAI8e,CAAE,EAGtCD,EAAqB,MAAM,QAAU,MACvC,CA6BO,SAASG,GACd7d,EACAnB,EACA4e,EACM,CACoBzd,EAAK,iBAAiB,8BAA8B,EAE5D,QAASoM,GAAY,CACrC,MAAMsP,EAAUtP,EACVuR,EAAKjC,EAAQ,aAAa,IAAI,EAC9BgB,EAAQhB,EAAQ,aAAa,OAAO,EAG1C,GAAI,CAACiC,GAAM,CAACjB,EAAO,CACjB,QAAQ,KACN,oFAAoFiB,GAAM,EAAE,aAAajB,GAAS,EAAE,GAAA,EAEtH,MACF,CAEA,MAAM/G,EAAO+F,EAAQ,aAAa,MAAM,GAAK,OACvCoC,EAAUpC,EAAQ,aAAa,SAAS,GAAK,OAC7C3Q,EAAQ,SAAS2Q,EAAQ,aAAa,OAAO,GAAK,MAAO,EAAE,EAGjE,IAAIqC,EAEJ,MAAMC,EAAkBP,IAAkB/B,CAAO,EACjD,GAAIsC,EACFD,EAASC,MACJ,CAEL,MAAMrU,EAAU+R,EAAQ,UAAU,KAAA,EAClCqC,EAAUpL,GAA2B,CACnC,MAAMoE,EAAU,SAAS,cAAc,KAAK,EAC5C,OAAAA,EAAQ,UAAYpN,EACpBgJ,EAAU,YAAYoE,CAAO,EACtB,IAAMA,EAAQ,OAAA,CACvB,CACF,CAGA,MAAMkH,EAAgBpf,EAAM,WAAW,IAAI8e,CAAE,EAK7C,GAAIM,EAAe,CACjB,GAAID,EAAiB,CAEnBC,EAAc,OAASF,EAIvBE,EAAc,MAAQlT,EACtBkT,EAAc,KAAOtI,EACrBsI,EAAc,QAAUH,EAIxB,MAAMI,EAAUrf,EAAM,cAAc,IAAI8e,CAAE,EACtCO,IACFA,EAAA,EACArf,EAAM,cAAc,OAAO8e,CAAE,EAEjC,CACA,MACF,CAGA,MAAM7B,EAA6B,CACjC,GAAA6B,EACA,MAAAjB,EACA,KAAA/G,EACA,QAAAmI,EACA,MAAA/S,EACA,OAAAgT,CAAA,EAGFlf,EAAM,WAAW,IAAI8e,EAAI7B,CAAK,EAC9Bjd,EAAM,qBAAqB,IAAI8e,CAAE,EAGjCjC,EAAQ,MAAM,QAAU,MAC1B,CAAC,CACH,CAOO,SAASyC,GACdpK,EACA3U,EACAP,EACAoJ,EAIM,CACN,MAAMiT,EAAUnH,EAAW,cAAc,oBAAoB,EACzDmH,GACFA,EAAQ,iBAAiB,QAAUhM,GAAM,CAKvC,GAJeA,EAAE,OAGU,QAAQ,qBAAqB,EACvC,CACfjH,EAAU,cAAA,EACV,MACF,CACF,CAAC,EAIH,MAAM4T,EAAY9H,EAAW,cAAc,gBAAgB,EACvD8H,GACFA,EAAU,iBAAiB,QAAU3M,GAAM,CAEzC,MAAM7O,EADS6O,EAAE,OACK,QAAQ,uBAAuB,EACrD,GAAI7O,EAAQ,CAEV,MAAM+d,EADU/d,EAAO,QAAQ,gBAAgB,GACpB,aAAa,cAAc,EAClD+d,GACFnW,EAAU,gBAAgBmW,CAAS,CAEvC,CACF,CAAC,CAEL,CAMO,SAASC,GACdtK,EACA3U,EACAkf,EACY,CACZ,MAAMxC,EAAQ/H,EAAW,cAAc,iBAAiB,EAClDkC,EAASlC,EAAW,cAAc,sBAAsB,EACxDwK,EAAYxK,EAAW,cAAc,iBAAiB,EAC5D,GAAI,CAAC+H,GAAS,CAAC7F,GAAU,CAACsI,EAExB,MAAO,IAAM,CAAC,EAGhB,MAAMC,EAAWpf,GAAQ,WAAW,UAAY,QAC1Cqf,EAAW,IAEjB,IAAIC,EAAS,EACTvF,EAAa,EACbwF,EAAW,EACXC,EAAa,GAEjB,MAAMC,EAAe3P,GAAkB,CACrC,GAAI,CAAC0P,EAAY,OACjB1P,EAAE,eAAA,EAIF,MAAM2J,EAAQ2F,IAAa,OAAStP,EAAE,QAAUwP,EAASA,EAASxP,EAAE,QAC9D4P,EAAW,KAAK,IAAIH,EAAU,KAAK,IAAIF,EAAUtF,EAAaN,CAAK,CAAC,EAE1EiD,EAAM,MAAM,MAAQ,GAAGgD,CAAQ,IACjC,EAEMC,EAAY,IAAM,CACtB,GAAI,CAACH,EAAY,OACjBA,EAAa,GACb3I,EAAO,UAAU,OAAO,UAAU,EAClC6F,EAAM,MAAM,WAAa,GACzB,SAAS,KAAK,MAAM,OAAS,GAC7B,SAAS,KAAK,MAAM,WAAa,GAGjC,MAAMkD,EAAalD,EAAM,sBAAA,EAAwB,MACjDwC,EAASU,CAAU,EAEnB,SAAS,oBAAoB,YAAaH,CAAW,EACrD,SAAS,oBAAoB,UAAWE,CAAS,CACnD,EAEME,EAAe/P,GAAkB,CACrCA,EAAE,eAAA,EACF0P,EAAa,GACbF,EAASxP,EAAE,QACXiK,EAAa2C,EAAM,wBAAwB,MAE3C6C,EAAWJ,EAAU,sBAAA,EAAwB,MAAQ,GACrDtI,EAAO,UAAU,IAAI,UAAU,EAC/B6F,EAAM,MAAM,WAAa,OACzB,SAAS,KAAK,MAAM,OAAS,aAC7B,SAAS,KAAK,MAAM,WAAa,OAEjC,SAAS,iBAAiB,YAAa+C,CAAW,EAClD,SAAS,iBAAiB,UAAWE,CAAS,CAChD,EAEA,OAAA9I,EAAO,iBAAiB,YAAagJ,CAAW,EAGzC,IAAM,CACXhJ,EAAO,oBAAoB,YAAagJ,CAAW,EACnD,SAAS,oBAAoB,YAAaJ,CAAW,EACrD,SAAS,oBAAoB,UAAWE,CAAS,CACnD,CACF,CAQO,SAASG,GACdnL,EACA3U,EACAP,EACM,CAEN,MAAMge,EAAiBzd,GAAQ,QAAQ,iBAAmB,CAAA,EACpD0d,EAAgB,CAAC,GAAGje,EAAM,gBAAgB,QAAQ,EAClD4K,EAAY,IAAI,IAAIoT,EAAe,IAAKhb,GAAMA,EAAE,EAAE,CAAC,EACnDkb,EAAc,CAAC,GAAGF,CAAc,EACtC,UAAWlT,KAAWmT,EACfrT,EAAU,IAAIE,EAAQ,EAAE,GAC3BoT,EAAY,KAAKpT,CAAO,EAK5B,UAAWA,KAAWoT,EAAa,CAGjC,GADIle,EAAM,uBAAuB,IAAI8K,EAAQ,EAAE,GAC3C,CAACA,EAAQ,OAAQ,SAErB,MAAMwV,EAAOpL,EAAW,cAAc,0BAA0BpK,EAAQ,EAAE,IAAI,EAC9E,GAAI,CAACwV,EAAM,SAEX,MAAMjB,EAAUvU,EAAQ,OAAOwV,CAAmB,EAC9CjB,GACFrf,EAAM,uBAAuB,IAAI8K,EAAQ,GAAIuU,CAAO,CAExD,CACF,CAMO,SAASkB,GAAoBrL,EAAqBlV,EAAyB,CAEhF,MAAMwgB,EAAqBxgB,EAAM,sBAAsB,OAAS,GAAK,CAACA,EAAM,qBACtEygB,EAAmBzgB,EAAM,eAAe,KAAO,EACrD,GAAI,CAACwgB,GAAsB,CAACC,EAAkB,OAE9C,MAAMC,EAAcxL,EAAW,cAAc,oBAAoB,EACjE,GAAI,CAACwL,EAAa,OAGlB,GAAIF,EAAoB,CACtB,UAAWpf,KAAMpB,EAAM,sBACrBoB,EAAG,MAAM,QAAU,GACnBsf,EAAY,YAAYtf,CAAE,EAE5BpB,EAAM,qBAAuB,EAC/B,CAGA,MAAMse,EAAiB,CAAC,GAAGte,EAAM,eAAe,QAAQ,EAAE,KAAK,CAAC2C,EAAG2H,KAAO3H,EAAE,OAAS,MAAQ2H,EAAE,OAAS,IAAI,EAE5G,UAAWQ,KAAWwT,EAAgB,CAEpC,MAAMqC,EAAkB3gB,EAAM,sBAAsB,IAAI8K,EAAQ,EAAE,EAC9D6V,IACFA,EAAA,EACA3gB,EAAM,sBAAsB,OAAO8K,EAAQ,EAAE,GAI/C,IAAIgJ,EAAY4M,EAAY,cAAc,yBAAyB5V,EAAQ,EAAE,IAAI,EAC5EgJ,IACHA,EAAY,SAAS,cAAc,KAAK,EACxCA,EAAU,aAAa,sBAAuBhJ,EAAQ,EAAE,EACxD4V,EAAY,YAAY5M,CAAS,GAGnC,MAAMuL,EAAUvU,EAAQ,OAAOgJ,CAAS,EACpCuL,GACFrf,EAAM,sBAAsB,IAAI8K,EAAQ,GAAIuU,CAAO,CAEvD,CACF,CAMO,SAASuB,GACd1L,EACAlV,EACAiX,EACM,CACN,GAAI,CAACjX,EAAM,YAAa,OAExB,MAAM6gB,EAAarD,EAAavG,GAAO,QAAUhW,EAAmB,MAAM,EACpE6f,EAAetD,EAAavG,GAAO,UAAYhW,EAAmB,QAAQ,EAEhF,SAAW,CAAC8f,EAAS9D,CAAK,IAAKjd,EAAM,WAAY,CAC/C,MAAMghB,EAAahhB,EAAM,iBAAiB,IAAI+gB,CAAO,EAC/C5D,EAAUjI,EAAW,cAAc,kBAAkB6L,CAAO,IAAI,EAChEL,EAAcvD,GAAS,cAAc,wBAAwB,EAEnE,GAAI,CAACA,GAAW,CAACuD,EAAa,SAG9BvD,EAAQ,UAAU,OAAO,WAAY6D,CAAU,EAC/C,MAAMxf,EAAS2b,EAAQ,cAAc,uBAAuB,EACxD3b,GACFA,EAAO,aAAa,gBAAiB,OAAOwf,CAAU,CAAC,EAEzD,MAAMC,EAAU9D,EAAQ,cAAc,wBAAwB,EAK9D,GAJI8D,IACFA,EAAQ,UAAYD,EAAaF,EAAeD,GAG9CG,GAEF,GAAIN,EAAY,SAAS,SAAW,EAAG,CAErC,MAAMrB,EAAUpC,EAAM,OAAOyD,CAAW,EACpCrB,GACFrf,EAAM,cAAc,IAAI+gB,EAAS1B,CAAO,CAE5C,MACK,CAEL,MAAMA,EAAUrf,EAAM,cAAc,IAAI+gB,CAAO,EAC3C1B,IACFA,EAAA,EACArf,EAAM,cAAc,OAAO+gB,CAAO,GAEpCL,EAAY,UAAY,EAC1B,CACF,CACF,CAKO,SAASQ,GAA0BhM,EAAqBlV,EAAyB,CAEtF,MAAMmhB,EAAcjM,EAAW,cAAc,qBAAqB,EAC9DiM,IACFA,EAAY,UAAU,OAAO,SAAUnhB,EAAM,WAAW,EACxDmhB,EAAY,aAAa,eAAgB,OAAOnhB,EAAM,WAAW,CAAC,EAEtE,CAKO,SAASohB,GAAiBlM,EAAqBlV,EAAyB,CAC7E,MAAMid,EAAQ/H,EAAW,cAAc,iBAAiB,EACnD+H,IAELA,EAAM,UAAU,OAAO,OAAQjd,EAAM,WAAW,EAG3CA,EAAM,cACTid,EAAM,MAAM,MAAQ,IAExB,CAOO,SAASoE,GAAmBrhB,EAAyB,CAE1D,UAAWqf,KAAWrf,EAAM,uBAAuB,OAAA,EACjDqf,EAAA,EAEFrf,EAAM,uBAAuB,MAAA,CAC/B,CAKO,SAASshB,GAAkBthB,EAAyB,CAEzD,UAAWqf,KAAWrf,EAAM,sBAAsB,OAAA,EAChDqf,EAAA,EAEFrf,EAAM,sBAAsB,MAAA,EAG5B,UAAWqf,KAAWrf,EAAM,cAAc,OAAA,EACxCqf,EAAA,EAEFrf,EAAM,cAAc,MAAA,EAGpB,UAAWqf,KAAWrf,EAAM,uBAAuB,OAAA,EACjDqf,EAAA,EAEFrf,EAAM,uBAAuB,MAAA,EAG7B,UAAW8K,KAAW9K,EAAM,gBAAgB,OAAA,EAC1C8K,EAAQ,YAAA,EAIV,GAAI9K,EAAM,YACR,UAAWuf,KAAavf,EAAM,iBACdA,EAAM,WAAW,IAAIuf,CAAS,GACrC,UAAA,EAKXvf,EAAM,YAAc,GACpBA,EAAM,iBAAiB,MAAA,EAGvBA,EAAM,WAAW,MAAA,EACjBA,EAAM,eAAe,MAAA,EACrBA,EAAM,gBAAgB,MAAA,EACtBA,EAAM,sBAAwB,CAAA,EAG9BA,EAAM,qBAAqB,MAAA,EAC3BA,EAAM,0BAA0B,MAAA,EAGhCA,EAAM,qBAAuB,EAC/B,CAmEO,SAASuhB,GAAsBvhB,EAAmBoJ,EAAsD,CAC7G,IAAIoY,EAAc,GAElB,MAAMC,EAA8B,CAClC,IAAI,eAAgB,CAClB,OAAOD,CACT,EACA,eAAerf,EAAgB,CAC7Bqf,EAAcrf,CAChB,EAEA,IAAI,aAAc,CAChB,OAAOnC,EAAM,WACf,EAEA,IAAI,aAAc,CAEhB,OAAIA,EAAM,aAAeA,EAAM,iBAAiB,KAAO,EAC9C,CAAC,GAAGA,EAAM,gBAAgB,EAAE,CAAC,EAE/B,IACT,EAEA,IAAI,kBAAmB,CACrB,MAAO,CAAC,GAAGA,EAAM,gBAAgB,CACnC,EAEA,eAAgB,CACd,GAAIA,EAAM,YAAa,OACvB,GAAIA,EAAM,WAAW,OAAS,EAAG,CAC/B,QAAQ,KAAK,sCAAsC,EACnD,MACF,CAKA,GAHAA,EAAM,YAAc,GAGhBA,EAAM,iBAAiB,OAAS,GAAKA,EAAM,WAAW,KAAO,EAAG,CAElE,MAAM0hB,EADe,CAAC,GAAG1hB,EAAM,WAAW,QAAQ,EAAE,KAAK,CAAC2C,EAAG2H,KAAO3H,EAAE,OAAS,MAAQ2H,EAAE,OAAS,IAAI,EACtE,CAAC,EAC7BoX,GACF1hB,EAAM,iBAAiB,IAAI0hB,EAAW,EAAE,CAE5C,CAGA,MAAMC,EAASvY,EAAU,UAAA,EACzB8X,GAA0BS,EAAQ3hB,CAAK,EACvCohB,GAAiBO,EAAQ3hB,CAAK,EAG9B4gB,GAAmBe,EAAQ3hB,EAAOoJ,EAAU,kBAAA,CAAmB,EAG/DA,EAAU,KAAK,kBAAmB,CAAE,SAAUqY,EAAW,iBAAkB,CAC7E,EAEA,gBAAiB,CACf,GAAI,CAACzhB,EAAM,YAAa,OAGxB,UAAWqf,KAAWrf,EAAM,cAAc,OAAA,EACxCqf,EAAA,EAEFrf,EAAM,cAAc,MAAA,EAGpB,UAAWid,KAASjd,EAAM,WAAW,OAAA,EACnCid,EAAM,UAAA,EAGRjd,EAAM,YAAc,GAGpB,MAAM2hB,EAASvY,EAAU,UAAA,EACzB8X,GAA0BS,EAAQ3hB,CAAK,EACvCohB,GAAiBO,EAAQ3hB,CAAK,EAG9BoJ,EAAU,KAAK,mBAAoB,EAAE,CACvC,EAEA,iBAAkB,CACZpJ,EAAM,YACRyhB,EAAW,eAAA,EAEXA,EAAW,cAAA,CAEf,EAEA,uBAAuBlC,EAAmB,CACxC,MAAMtC,EAAQjd,EAAM,WAAW,IAAIuf,CAAS,EAC5C,GAAI,CAACtC,EAAO,CACV,QAAQ,KAAK,kCAAkCsC,CAAS,aAAa,EACrE,MACF,CAGA,GAAIvf,EAAM,WAAW,OAAS,EAC5B,OAGF,MAAM2hB,EAASvY,EAAU,UAAA,EACnB4X,EAAahhB,EAAM,iBAAiB,IAAIuf,CAAS,EAEvD,GAAIyB,EAAY,CAEd,MAAM3B,EAAUrf,EAAM,cAAc,IAAIuf,CAAS,EAC7CF,IACFA,EAAA,EACArf,EAAM,cAAc,OAAOuf,CAAS,GAEtCtC,EAAM,UAAA,EACNjd,EAAM,iBAAiB,OAAOuf,CAAS,EACvCqC,GAA4BD,EAAQpC,EAAW,EAAK,CACtD,KAAO,CAEL,SAAW,CAACsC,EAASC,CAAU,IAAK9hB,EAAM,WACxC,GAAI6hB,IAAYtC,GAAavf,EAAM,iBAAiB,IAAI6hB,CAAO,EAAG,CAChE,MAAMxC,EAAUrf,EAAM,cAAc,IAAI6hB,CAAO,EAC3CxC,IACFA,EAAA,EACArf,EAAM,cAAc,OAAO6hB,CAAO,GAEpCC,EAAW,UAAA,EACX9hB,EAAM,iBAAiB,OAAO6hB,CAAO,EACrCD,GAA4BD,EAAQE,EAAS,EAAK,EAElD,MAAME,EAAYJ,EAAO,cAAc,kBAAkBE,CAAO,2BAA2B,EACvFE,MAAqB,UAAY,GACvC,CAGF/hB,EAAM,iBAAiB,IAAIuf,CAAS,EACpCqC,GAA4BD,EAAQpC,EAAW,EAAI,EACnDyC,GAA8BL,EAAQ3hB,EAAOuf,CAAS,CACxD,CAGAnW,EAAU,KAAK,4BAA6B,CAAE,GAAImW,EAAW,SAAU,CAACyB,EAAY,CACtF,EAEA,eAAgB,CACd,MAAO,CAAC,GAAGhhB,EAAM,WAAW,QAAQ,CACtC,EAEA,kBAAkBid,EAA4B,CAC5C,GAAIjd,EAAM,WAAW,IAAIid,EAAM,EAAE,EAAG,CAClC,QAAQ,KAAK,0BAA0BA,EAAM,EAAE,sBAAsB,EACrE,MACF,CACAjd,EAAM,WAAW,IAAIid,EAAM,GAAIA,CAAK,EAEhCuE,GACFpY,EAAU,mBAAA,CAEd,EAEA,oBAAoB2X,EAAiB,CAEnC,GAAI/gB,EAAM,iBAAiB,IAAI+gB,CAAO,EAAG,CACvC,MAAM1B,EAAUrf,EAAM,cAAc,IAAI+gB,CAAO,EAC3C1B,IACFA,EAAA,EACArf,EAAM,cAAc,OAAO+gB,CAAO,GAEpC/gB,EAAM,iBAAiB,OAAO+gB,CAAO,CACvC,CAEA/gB,EAAM,WAAW,OAAO+gB,CAAO,EAE3BS,GACFpY,EAAU,mBAAA,CAEd,EAEA,mBAAoB,CAClB,MAAO,CAAC,GAAGpJ,EAAM,eAAe,QAAQ,CAC1C,EAEA,sBAAsB8K,EAAkC,CACtD,GAAI9K,EAAM,eAAe,IAAI8K,EAAQ,EAAE,EAAG,CACxC,QAAQ,KAAK,8BAA8BA,EAAQ,EAAE,sBAAsB,EAC3E,MACF,CACA9K,EAAM,eAAe,IAAI8K,EAAQ,GAAIA,CAAO,EAExC0W,GACFjB,GAAoBnX,EAAU,UAAA,EAAapJ,CAAK,CAEpD,EAEA,wBAAwBiiB,EAAmB,CAEzC,MAAM5C,EAAUrf,EAAM,sBAAsB,IAAIiiB,CAAS,EACrD5C,IACFA,EAAA,EACArf,EAAM,sBAAsB,OAAOiiB,CAAS,GAI9BjiB,EAAM,eAAe,IAAIiiB,CAAS,GACzC,YAAA,EAETjiB,EAAM,eAAe,OAAOiiB,CAAS,EAG1B7Y,EAAU,UAAA,EAAY,cAAc,yBAAyB6Y,CAAS,IAAI,GACjF,OAAA,CACN,EAEA,oBAAqB,CACnB,MAAO,CAAC,GAAGjiB,EAAM,gBAAgB,OAAA,CAAQ,EAAE,KAAK,CAAC2C,EAAG2H,KAAO3H,EAAE,OAAS,IAAM2H,EAAE,OAAS,EAAE,CAC3F,EAEA,uBAAuBQ,EAAmC,CACxD,GAAI9K,EAAM,gBAAgB,IAAI8K,EAAQ,EAAE,EAAG,CACzC,QAAQ,KAAK,+BAA+BA,EAAQ,EAAE,sBAAsB,EAC5E,MACF,CACA9K,EAAM,gBAAgB,IAAI8K,EAAQ,GAAIA,CAAO,EAEzC0W,GACFpY,EAAU,mBAAA,CAEd,EAEA,yBAAyB6Y,EAAmB,CAE1C,MAAM5C,EAAUrf,EAAM,uBAAuB,IAAIiiB,CAAS,EACtD5C,IACFA,EAAA,EACArf,EAAM,uBAAuB,OAAOiiB,CAAS,GAI/C,MAAMnX,EAAU9K,EAAM,gBAAgB,IAAIiiB,CAAS,EAC/CnX,GAAS,WACXA,EAAQ,UAAA,EAGV9K,EAAM,gBAAgB,OAAOiiB,CAAS,EAElCT,GACFpY,EAAU,mBAAA,CAEd,CAAA,EAGF,OAAOqY,CACT,CAKA,SAASG,GAA4B1M,EAAqBqK,EAAmB2C,EAAyB,CACpG,MAAM/E,EAAUjI,EAAW,cAAc,kBAAkBqK,CAAS,IAAI,EACpEpC,GACFA,EAAQ,UAAU,OAAO,WAAY+E,CAAQ,CAEjD,CAKA,SAASF,GAA8B9M,EAAqBlV,EAAmBuf,EAAyB,CACtG,MAAMtC,EAAQjd,EAAM,WAAW,IAAIuf,CAAS,EAC5C,GAAI,CAACtC,GAAO,OAAQ,OAEpB,MAAM8E,EAAY7M,EAAW,cAAc,kBAAkBqK,CAAS,2BAA2B,EACjG,GAAI,CAACwC,EAAW,OAEhB,MAAM1C,EAAUpC,EAAM,OAAO8E,CAAwB,EACjD1C,GACFrf,EAAM,cAAc,IAAIuf,EAAWF,CAAO,CAE9C,CAgDO,SAAS8C,GACdjN,EACAkN,EACAC,EACApL,EACS,CACT,MAAMqL,EAAW5E,GAAwB0E,CAAW,EAI9CG,EAA8B,CAAA,EAC9BC,EAAoB,CACxB,kBACA,wBACA,sBACA,kBACA,kBACA,0BAAA,EAEF,UAAWC,KAAYD,EACJtN,EAAW,iBAAiB,YAAYuN,CAAQ,EAAE,EAC1D,QAASrhB,GAAOmhB,EAAiB,KAAKnhB,CAAE,CAAC,EAIpD8T,EAAW,gBAAA,EAGX,UAAW9T,KAAMmhB,EACfrN,EAAW,YAAY9T,CAAE,EAG3B,GAAIkhB,EAAU,CACZ,MAAM1E,EAAgBJ,EAAavG,GAAO,WAAahW,EAAmB,SAAS,EAC7E4f,EAAarD,EAAavG,GAAO,QAAUhW,EAAmB,MAAM,EACpE6f,EAAetD,EAAavG,GAAO,UAAYhW,EAAmB,QAAQ,EAI1Eqd,EAAiB,CAAC,GADJ8D,GAAa,QAAQ,iBAAmB,CAAA,CACtB,EAAE,KAAK,CAACzf,EAAG2H,KAAO3H,EAAE,OAAS,IAAM2H,EAAE,OAAS,EAAE,EAIhFoY,EAAe,CAAC,GADJN,GAAa,YAAc,CAAA,CACX,EAAE,KAAK,CAACzf,EAAG2H,KAAO3H,EAAE,OAAS,MAAQ2H,EAAE,OAAS,IAAI,EAGhFqY,EAAoC,CACxC,MAAOP,GAAa,QAAQ,OAAS,OACrC,UAAWM,EAAa,OAAS,EACjC,YAAaL,EAAa,YAC1B,cAAAzE,EAEA,cAAeU,EAAe,IAAKtb,IAAO,CACxC,GAAIA,EAAE,GACN,WAAY,GACZ,UAAW,CAAC,CAACA,EAAE,MAAA,EACf,EACF,WAAY,CAAA,CAAC,EAIT4f,EAAgC,CACpC,SAAUR,GAAa,WAAW,UAAY,QAC9C,YAAaC,EAAa,YAC1B,WAAAxB,EACA,aAAAC,EACA,OAAQ4B,EAAa,IAAKlb,IAAO,CAC/B,GAAIA,EAAE,GACN,MAAOA,EAAE,MACT,KAAMgW,EAAahW,EAAE,IAAI,EACzB,WAAY6a,EAAa,iBAAiB,IAAI7a,EAAE,EAAE,CAAA,EAClD,CAAA,EAIEqb,EAAc1G,GAAiBwG,CAAa,EAC5CjD,EAAYlD,GAAeoG,CAAW,EAGtClR,EAAWuK,GAAa,CAC5B,SAAU,GACV,YAAA4G,EACA,UAAAnD,CAAA,CACD,EACDxK,EAAW,YAAYxD,CAAQ,CACjC,KAAO,CAEL,MAAMA,EAAWuK,GAAa,CAAE,SAAU,GAAO,EACjD/G,EAAW,YAAYxD,CAAQ,CACjC,CAEA,OAAO4Q,CACT,CCn1CA,MAAMQ,GAAmB,kBAGzB,IAAIC,GAAa,GAGjB,MAAMC,OAAsB,IAQ5B,SAASC,IAAoC,CAC3C,IAAIC,EAAU,SAAS,eAAeJ,EAAgB,EACtD,OAAKI,IACHA,EAAU,SAAS,cAAc,OAAO,EACxCA,EAAQ,GAAKJ,GACbI,EAAQ,aAAa,gBAAiB,MAAM,EAC5C,SAAS,KAAK,YAAYA,CAAO,GAE5BA,CACT,CAKA,SAASC,IAA2B,CAClC,MAAMD,EAAUD,GAAA,EAEVG,EAAe,MAAM,KAAKJ,GAAgB,QAAQ,EAAE,KAAK;AAAA,CAAI,EACnEE,EAAQ,YAAc,GAAGH,EAAU;AAAA;AAAA;AAAA,EAA4BK,CAAY,EAC7E,CAQO,SAASC,GAAgBD,EAAgE,CAC9F,IAAIE,EAAe,GAEnB,SAAW,CAAE,KAAA3c,EAAM,OAAA4c,CAAA,IAAYH,EACxBJ,GAAgB,IAAIrc,CAAI,IAC3Bqc,GAAgB,IAAIrc,EAAM4c,CAAM,EAChCD,EAAe,IAInB,OAAIA,GACFH,GAAA,EAGKG,CACT,CAMO,SAASE,IAA4C,CAC1D,GAAI,CAGF,UAAWC,KAAc,MAAM,KAAK,SAAS,WAAW,EACtD,GAAI,CAGF,MAAMC,EADQ,MAAM,KAAKD,EAAW,UAAY,CAAA,CAAE,EAC5B,IAAKE,GAASA,EAAK,OAAO,EAAE,KAAK;AAAA,CAAI,EAI3D,GAAID,EAAQ,SAAS,gBAAgB,GAAKA,EAAQ,SAAS,UAAU,EAGnE,OAAOA,CAEX,MAAQ,CAEN,QACF,CAEJ,OAASE,EAAK,CACZ,QAAQ,KAAK,mEAAoEA,CAAG,CACtF,CAEA,OAAO,IACT,CASA,eAAsBC,GAAaC,EAAqC,CAEtE,GAAIf,GACF,OAIF,GAAI,OAAOe,GAAiB,UAAYA,EAAa,OAAS,EAAG,CAC/Df,GAAae,EACbX,GAAA,EACA,MACF,CAKA,MAAM,IAAI,QAAS1J,GAAY,WAAWA,EAAS,EAAE,CAAC,EAEtD,MAAMsK,EAAcP,GAAA,EAEhBO,GACFhB,GAAagB,EACbZ,GAAA,IACS,OAAO,QAAY,KAAe,QAAQ,KAAM,WAAgB,SAEzE,QAAQ,KACN,0FACA,yBACA,MAAM,KAAK,SAAS,WAAW,EAAE,IAAKrb,GAAMA,EAAE,MAAQ,UAAU,CAAA,CAGtE,CC7GO,SAASkc,IAA2C,CACzD,MAAO,CACL,OAAQ,KACR,OAAQ,KACR,UAAW,KACX,WAAY,KACZ,MAAO,KACP,MAAO,KACP,SAAU,KACV,UAAW,EACX,UAAW,EACX,YAAa,CAAA,CAEjB,CAKO,SAASC,GAAgBjkB,EAA+B,CAC7DA,EAAM,OAAS,KACfA,EAAM,OAAS,KACfA,EAAM,UAAY,KAClBA,EAAM,WAAa,KACnBA,EAAM,MAAQ,KACdA,EAAM,MAAQ,KACdA,EAAM,SAAW,IACnB,CAKO,SAASkkB,GAAelkB,EAA+B,CACxDA,EAAM,cACR,qBAAqBA,EAAM,WAAW,EACtCA,EAAM,YAAc,EAExB,CAOO,SAASmkB,GAAiB9T,EAAerQ,EAAyBsG,EAAqC,CAC5G,GAAI+J,EAAE,QAAQ,SAAW,EAAG,OAG5B6T,GAAelkB,CAAK,EAEpB,MAAMokB,EAAQ/T,EAAE,QAAQ,CAAC,EACzBrQ,EAAM,OAASokB,EAAM,QACrBpkB,EAAM,OAASokB,EAAM,QACrBpkB,EAAM,MAAQokB,EAAM,QACpBpkB,EAAM,MAAQokB,EAAM,QACpBpkB,EAAM,SAAW,YAAY,IAAA,EAC7BA,EAAM,UAAYsG,EAAS,cAAc,UACzCtG,EAAM,WAAasG,EAAS,YAAY,YAAc,EACtDtG,EAAM,UAAY,EAClBA,EAAM,UAAY,CACpB,CAMO,SAASqkB,GAAgBhU,EAAerQ,EAAyBsG,EAAwC,CAC9G,GACE+J,EAAE,QAAQ,SAAW,GACrBrQ,EAAM,SAAW,MACjBA,EAAM,SAAW,MACjBA,EAAM,YAAc,MACpBA,EAAM,aAAe,KAErB,MAAO,GAGT,MAAMokB,EAAQ/T,EAAE,QAAQ,CAAC,EACnBiU,EAAWF,EAAM,QACjBG,EAAWH,EAAM,QACjBI,EAAM,YAAY,IAAA,EAElBC,EAASzkB,EAAM,OAASskB,EACxBI,EAAS1kB,EAAM,OAASukB,EAG9B,GAAIvkB,EAAM,WAAa,MAAQA,EAAM,QAAU,MAAQA,EAAM,QAAU,KAAM,CAC3E,MAAM2kB,EAAKH,EAAMxkB,EAAM,SACnB2kB,EAAK,IAEP3kB,EAAM,WAAaA,EAAM,MAAQskB,GAAYK,EAC7C3kB,EAAM,WAAaA,EAAM,MAAQukB,GAAYI,EAEjD,CACA3kB,EAAM,MAAQskB,EACdtkB,EAAM,MAAQukB,EACdvkB,EAAM,SAAWwkB,EAGjB,KAAM,CAAE,UAAAI,EAAW,aAAAC,EAAc,aAAAC,CAAA,EAAiBxe,EAAS,cACrDye,EAAaF,EAAeC,EAC5BE,EAAuBP,EAAS,GAAKG,EAAYG,GAAgBN,EAAS,GAAKG,EAAY,EAEjG,IAAIK,EAAwB,GAC5B,GAAI3e,EAAS,WAAY,CACvB,KAAM,CAAE,WAAA4e,EAAY,YAAAC,EAAa,YAAAC,CAAA,EAAgB9e,EAAS,WACpD+e,EAAaF,EAAcC,EACjCH,EAAyBP,EAAS,GAAKQ,EAAaG,GAAgBX,EAAS,GAAKQ,EAAa,CACjG,CAGA,OAAIF,IACF1e,EAAS,cAAc,UAAYtG,EAAM,UAAYykB,GAEnDQ,GAAyB3e,EAAS,aACpCA,EAAS,WAAW,WAAatG,EAAM,WAAa0kB,GAI/CM,GAAuBC,CAChC,CAMO,SAASK,GAAetlB,EAAyBsG,EAAqC,EAIvF,KAAK,IAAItG,EAAM,SAAS,EAAI,IAAe,KAAK,IAAIA,EAAM,SAAS,EAAI,KACzEulB,GAAoBvlB,EAAOsG,CAAQ,EAGrC2d,GAAgBjkB,CAAK,CACvB,CAOA,SAASulB,GAAoBvlB,EAAyBsG,EAAqC,CAIzF,MAAMkf,EAAU,IAAM,CAEpBxlB,EAAM,WAAa,IACnBA,EAAM,WAAa,IAGnB,MAAMylB,EAAUzlB,EAAM,UAAY,GAC5B0lB,EAAU1lB,EAAM,UAAY,GAG9B,KAAK,IAAIA,EAAM,SAAS,EAAI,MAC9BsG,EAAS,cAAc,WAAamf,GAElC,KAAK,IAAIzlB,EAAM,SAAS,EAAI,KAAesG,EAAS,aACtDA,EAAS,WAAW,YAAcof,GAIhC,KAAK,IAAI1lB,EAAM,SAAS,EAAI,KAAe,KAAK,IAAIA,EAAM,SAAS,EAAI,IACzEA,EAAM,YAAc,sBAAsBwlB,CAAO,EAEjDxlB,EAAM,YAAc,CAExB,EAEAA,EAAM,YAAc,sBAAsBwlB,CAAO,CACnD,CAQO,SAASG,GACdC,EACA5lB,EACAsG,EACAoP,EACM,CACNkQ,EAAc,iBAAiB,aAAevV,GAAkB8T,GAAiB9T,EAAGrQ,EAAOsG,CAAQ,EAAG,CACpG,QAAS,GACT,OAAAoP,CAAA,CACD,EAEDkQ,EAAc,iBACZ,YACCvV,GAAkB,CACKgU,GAAgBhU,EAAGrQ,EAAOsG,CAAQ,GAEtD+J,EAAE,eAAA,CAEN,EACA,CAAE,QAAS,GAAO,OAAAqF,CAAA,CAAO,EAG3BkQ,EAAc,iBAAiB,WAAY,IAAMN,GAAetlB,EAAOsG,CAAQ,EAAG,CAAE,QAAS,GAAM,OAAAoP,CAAA,CAAQ,CAC7G,CCrLA,MAAMmQ,GAAwD,CAE5D,CACE,SAAU,WACV,WAAY,UACZ,MAAO,SACP,YAAa,iCACb,OAASzgB,GAAMA,IAAM,EAAA,EAEvB,CACE,SAAU,SACV,WAAY,UACZ,MAAO,SACP,YAAa,8BAAA,EAEf,CACE,SAAU,eACV,WAAY,UACZ,MAAO,SACP,YAAa,oCAAA,EAGf,CACE,SAAU,QACV,WAAY,kBACZ,MAAO,SACP,YAAa,6BAAA,EAGf,CACE,SAAU,SACV,WAAY,gBACZ,MAAO,SACP,YAAa,+BACb,OAASA,GAAMA,IAAM,QAAUA,IAAM,SAAWA,IAAM,SAAWA,IAAM,KAAA,CAE3E,EAKM0gB,GAAwD,CAE5D,CACE,SAAU,eACV,WAAY,kBACZ,MAAO,SACP,YAAa,qCACb,OAAS1gB,GAAM,MAAM,QAAQA,CAAC,GAAKA,EAAE,OAAS,CAAA,CAElD,EAQA,SAAS2gB,GAAYje,EAAmB,CACtC,OAAOA,EAAE,QAAQ,SAAWpE,GAAM,IAAIA,EAAE,YAAA,CAAa,EAAE,CACzD,CAMA,SAASsiB,GAAcC,EAA4B,CACjD,MAAO,YAAYC,EAAWD,CAAU,CAAC,4CAA4CF,GAAYE,CAAU,CAAC,IAC9G,CAUA,SAASC,EAAWpe,EAAmB,CACrC,OAAOA,EAAE,OAAO,CAAC,EAAE,cAAgBA,EAAE,MAAM,CAAC,CAC9C,CAKA,SAASqe,GAAUpb,EAAoCkb,EAA6B,CAClF,OAAOlb,EAAQ,KAAMvD,GAAMA,EAAE,OAASye,CAAU,CAClD,CAWO,SAASG,GAA4B7lB,EAAuBwK,EAA0C,CAE3G,MAAMsb,EAAcR,GACdS,EAAcR,GAGdS,MAAqB,IAM3B,SAASC,EACPP,EACAQ,EACAC,EACArlB,EACAslB,EAAmB,GACnB,CACKJ,EAAe,IAAIN,CAAU,GAChCM,EAAe,IAAIN,EAAY,CAAE,YAAAQ,EAAa,WAAAC,EAAY,OAAQ,GAAI,iBAAAC,EAAkB,EAG1F,MAAMC,EAAQL,EAAe,IAAIN,CAAU,EACtCW,EAAM,OAAO,SAASvlB,CAAK,GAC9BulB,EAAM,OAAO,KAAKvlB,CAAK,CAE3B,CAGA,UAAWwlB,KAAOP,EAAa,CAC7B,MAAMnkB,EAAS5B,EAAmCsmB,EAAI,QAAQ,GAC/CA,EAAI,OAASA,EAAI,OAAO1kB,CAAK,EAAIA,IAAU,SAE5C,CAACgkB,GAAUpb,EAAS8b,EAAI,UAAU,GAC9CL,EAASK,EAAI,WAAYA,EAAI,YAAab,GAAca,EAAI,UAAU,EAAGA,EAAI,SAAU,EAAI,CAE/F,CAGA,MAAM3hB,EAAU3E,EAAO,QACvB,GAAI2E,GAAWA,EAAQ,OAAS,EAC9B,UAAWqO,KAAUrO,EACnB,UAAW2hB,KAAOR,EAAa,CAC7B,MAAMlkB,EAASoR,EAA8CsT,EAAI,QAAQ,EAIzE,IAFeA,EAAI,OAASA,EAAI,OAAO1kB,CAAK,EAAIA,IAAU,SAE5C,CAACgkB,GAAUpb,EAAS8b,EAAI,UAAU,EAAG,CACjD,MAAMxlB,EAASkS,EAAwB,OAAS,YAChDiT,EAASK,EAAI,WAAYA,EAAI,YAAab,GAAca,EAAI,UAAU,EAAGxlB,CAAK,CAChF,CACF,CAKJ,GAAIklB,EAAe,KAAO,EAAG,CAC3B,MAAMO,EAAmB,CAAA,EACzB,SAAW,CAACb,EAAY,CAAE,YAAAQ,EAAa,WAAAC,EAAY,OAAAK,EAAQ,iBAAAJ,EAAkB,IAAKJ,EAChF,GAAII,EAEFG,EAAO,KACL,eAAeL,CAAW;AAAA;AAAA,MAEjBC,CAAU;AAAA,oBACIR,EAAWD,CAAU,CAAC,gBAAA,MAE1C,CAEL,MAAMe,EAAYD,EAAO,MAAM,EAAG,CAAC,EAAE,KAAK,IAAI,GAAKA,EAAO,OAAS,EAAI,UAAUA,EAAO,MAAM,UAAY,IAC1GD,EAAO,KACL,cAAcE,CAAS,SAASP,CAAW;AAAA;AAAA,MAElCC,CAAU;AAAA,oBACIR,EAAWD,CAAU,CAAC,gBAAA,CAEjD,CAGF,MAAM,IAAI,MACR;AAAA;AAAA,EAAsCa,EAAO,KAAK;AAAA;AAAA,CAAM,CAAC;AAAA;AAAA,+HAAA,CAI7D,CACF,CAaO,SAASG,GAA0Blc,EAA0C,CAClF,MAAM+b,EAAmB,CAAA,EACnBI,EAAqB,CAAA,EAE3B,UAAW7b,KAAUN,EAAS,CAE5B,MAAMoc,EADc9b,EAAO,YACE,SAC7B,GAAK8b,GAAU,YAEf,UAAWxD,KAAQwD,EAAS,YAAa,CAGvC,MAAMC,EAAgB/b,EAAe,OACrC,GAAIsY,EAAK,MAAMyD,CAAY,EAAG,CAE5B,MAAM5V,EAAY,GADH,aAAa0U,EAAW7a,EAAO,IAAI,CAAC,SACxB,2BAA2BsY,EAAK,OAAO,GAC9DA,EAAK,WAAa,QACpBmD,EAAO,KAAKtV,CAAS,EAErB0V,EAAS,KAAK1V,CAAS,CAE3B,CACF,CACF,CAGA,GAAI0V,EAAS,OAAS,GAAKpa,GAAA,EACzB,UAAWua,KAAWH,EACpB,QAAQ,KAAKG,CAAO,EAKxB,GAAIP,EAAO,OAAS,EAClB,MAAM,IAAI,MAAM;AAAA;AAAA,EAAsCA,EAAO,KAAK;AAAA;AAAA,CAAM,CAAC,EAAE,CAE/E,CAiBO,SAASQ,GAA2Bjc,EAAwBkc,EAAgD,CACjH,MAAMtB,EAAa5a,EAAO,KAIpBmc,EAHcnc,EAAO,YAGM,cAAgB,CAAA,EAGjD,UAAWoc,KAAOD,EAAc,CAC9B,MAAME,EAAiBD,EAAI,KACrBE,EAAWF,EAAI,UAAY,GAC3BG,EAASH,EAAI,OAGnB,GAAI,CAFgBF,EAAc,KAAM/f,GAAMA,EAAE,OAASkgB,CAAc,EAErD,CAChB,MAAMG,EAAaD,GAAU,GAAG1B,EAAWD,CAAU,CAAC,mBAAmBC,EAAWwB,CAAc,CAAC,SAC7FhB,EAAaV,GAAc0B,CAAc,EAE/C,GAAIC,EACF,MAAM,IAAI,MACR;AAAA;AAAA,EACKE,CAAU;AAAA;AAAA,6DACiD3B,EAAWD,CAAU,CAAC;AAAA,MAC7ES,CAAU;AAAA,oBACIR,EAAWwB,CAAc,CAAC,iBAAiBxB,EAAWD,CAAU,CAAC,WAAA,EAI1F,QAAQ,KACN,cAAcC,EAAWD,CAAU,CAAC,qBAAqByB,CAAc,uDAAA,CAI7E,CACF,CACF,CAaO,SAASI,GAAgC/c,EAA0C,CAExF,GAAI,CAAC+B,KAAiB,OAEtB,MAAMib,EAAc,IAAI,IAAIhd,EAAQ,IAAKvD,GAAMA,EAAE,IAAI,CAAC,EAChDwgB,MAAa,IAEnB,UAAW3c,KAAUN,EAAS,CAE5B,MAAMoc,EADc9b,EAAO,YACE,SAC7B,GAAK8b,GAAU,kBAEf,UAAWc,KAAmBd,EAAS,iBACrC,GAAIY,EAAY,IAAIE,EAAgB,IAAI,EAAG,CAEzC,MAAMvgB,EAAM,CAAC2D,EAAO,KAAM4c,EAAgB,IAAI,EAAE,KAAA,EAAO,KAAK,GAAG,EAC/D,GAAID,EAAO,IAAItgB,CAAG,EAAG,SACrBsgB,EAAO,IAAItgB,CAAG,EAEd,QAAQ,KACN;AAAA;AAAA,EACKwe,EAAW7a,EAAO,IAAI,CAAC,cAAc6a,EAAW+B,EAAgB,IAAI,CAAC;AAAA;AAAA,MAEjEA,EAAgB,MAAM;AAAA;AAAA,uEAAA,CAGnC,EAEJ,CACF,CClTO,SAASC,GAAkB1U,EAAQ1D,EAAiD,CACzF,MAAI,CAAC0D,GAAO,OAAOA,GAAQ,SAAiBA,EAGxC,kBAAmBA,EACbA,EAAkC,cAIxC,UAAWA,GAAQA,EAAmC,OAAS,KAC1D,MAAOA,EAAmC,KAAK,GAIpD1D,EACK,MAAMA,EAAM0D,CAAG,CAAC,GAIlBA,CACT,CAMO,SAAS2U,GACdC,EACA5U,EACA1D,EACoB,CACpB,MAAMpI,EAAMwgB,GAAe1U,EAAK1D,CAAK,EAErC,GAAI,OAAOpI,GAAQ,SACjB,OAAO0gB,EAAM,MAAM,IAAI1gB,CAAG,EAI5B,GAAIA,GAAO,OAAOA,GAAQ,SACxB,OAAO0gB,EAAM,MAAM,IAAI1gB,CAAG,CAI9B,CAKO,SAAS2gB,GACdD,EACA5U,EACA8U,EACAxY,EACM,CACN,MAAMpI,EAAMwgB,GAAe1U,EAAK1D,CAAK,EAEjC,OAAOpI,GAAQ,SACjB0gB,EAAM,MAAM,IAAI1gB,EAAK4gB,CAAM,EAClB5gB,GAAO,OAAOA,GAAQ,UAC/B0gB,EAAM,MAAM,IAAI1gB,EAAK4gB,CAAM,CAE/B,CAiBO,SAASC,GACdxjB,EACAyjB,EACAC,EACAloB,EACAmoB,EACe,CACf,MAAMN,EAAuB,IAAI,MAAMrjB,EAAK,MAAM,EAClD,IAAI4jB,EAAS,EAEb,QAAStkB,EAAI,EAAGA,EAAIU,EAAK,OAAQV,IAAK,CACpC,MAAMmP,EAAMzO,EAAKV,CAAC,EAIlB,IAAIikB,EAASI,IAAkBlV,EAAKnP,CAAC,EACjCukB,EAAWN,IAAW,OAGtBA,IAAW,SACbA,EAASH,GAAgBK,EAAahV,EAAKjT,EAAO,KAAK,EACvDqoB,EAAWN,IAAW,QAIpBA,IAAW,SACbA,EAASG,EACTG,EAAW,IAGbR,EAAM/jB,CAAC,EAAI,CAAE,OAAAskB,EAAQ,OAAAL,EAAQ,SAAAM,CAAA,EAC7BD,GAAUL,CACZ,CAEA,OAAOF,CACT,CAUO,SAASS,GAAgBT,EAAsBld,EAAe4d,EAAyB,CAC5F,GAAI5d,EAAQ,GAAKA,GAASkd,EAAM,OAAQ,OAExC,MAAMxB,EAAQwB,EAAMld,CAAK,EACnB6d,EAAaD,EAAYlC,EAAM,OAErC,GAAImC,IAAe,EAGnB,CAAAnC,EAAM,OAASkC,EACflC,EAAM,SAAW,GAGjB,QAASviB,EAAI6G,EAAQ,EAAG7G,EAAI+jB,EAAM,OAAQ/jB,IACxC+jB,EAAM/jB,CAAC,EAAE,QAAU0kB,EAEvB,CAQO,SAASC,GAAeZ,EAA8B,CAC3D,GAAIA,EAAM,SAAW,EAAG,MAAO,GAC/B,MAAMa,EAAOb,EAAMA,EAAM,OAAS,CAAC,EACnC,OAAOa,EAAK,OAASA,EAAK,MAC5B,CAcO,SAASC,GAAoBd,EAAsBe,EAA8B,CACtF,GAAIf,EAAM,SAAW,EAAG,MAAO,GAC/B,GAAIe,GAAgB,EAAG,MAAO,GAE9B,IAAIC,EAAM,EACNC,EAAOjB,EAAM,OAAS,EAE1B,KAAOgB,GAAOC,GAAM,CAClB,MAAMC,EAAM,KAAK,OAAOF,EAAMC,GAAQ,CAAC,EACjCzC,EAAQwB,EAAMkB,CAAG,EACjBC,EAAW3C,EAAM,OAASA,EAAM,OAEtC,GAAIuC,EAAevC,EAAM,OACvByC,EAAOC,EAAM,UACJH,GAAgBI,EACzBH,EAAME,EAAM,MAGZ,QAAOA,CAEX,CAGA,OAAO,KAAK,IAAI,EAAG,KAAK,IAAIF,EAAKhB,EAAM,OAAS,CAAC,CAAC,CACpD,CAcO,SAASoB,GAAuBpB,EAAsBqB,EAA+B,CAC1F,IAAIC,EAAc,EACdC,EAAgB,EAEpB,UAAW/C,KAASwB,EACdxB,EAAM,WACR8C,GAAe9C,EAAM,OACrB+C,KAIJ,OAAOA,EAAgB,EAAID,EAAcC,EAAgBF,CAC3D,CAQO,SAASG,GAAkBxB,EAA8B,CAC9D,IAAIyB,EAAQ,EACZ,UAAWjD,KAASwB,EACdxB,EAAM,UAAUiD,IAEtB,OAAOA,CACT,CA+CO,SAASC,GACd3X,EACA4X,EACsB,CACtB,KAAM,CAAE,cAAAC,EAAe,YAAAxB,EAAa,KAAAzjB,EAAM,MAAAwJ,EAAO,IAAAC,EAAK,gBAAAka,EAAiB,SAAAjN,GAAatJ,EAEpF,IAAI8X,EAAa,GAEjBF,EAAY,QAASvlB,GAAU,CAC7B,MAAMsM,EAAetM,EAAsB,QAAQ,SACnD,GAAI,CAACsM,EAAa,OAElB,MAAM9B,EAAW,SAAS8B,EAAa,EAAE,EACzC,GAAI9B,EAAWT,GAASS,GAAYR,GAAOQ,GAAYjK,EAAK,OAAQ,OAEpE,MAAMyO,EAAMzO,EAAKiK,CAAQ,EAGnBkb,EAAexB,IAAkBlV,EAAKxE,CAAQ,EAEpD,GAAIkb,IAAiB,OAAW,CAE9B,MAAMC,EAAeH,EAAchb,CAAQ,GACvC,CAACmb,EAAa,UAAY,KAAK,IAAIA,EAAa,OAASD,CAAY,EAAI,KAC3ErB,GAAgBmB,EAAehb,EAAUkb,CAAY,EACrDD,EAAa,IAEf,MACF,CAGA,MAAMG,EAAkB5lB,EAAsB,aAE9C,GAAI4lB,EAAiB,EAAG,CACtB,MAAMD,EAAeH,EAAchb,CAAQ,GAGvC,CAACmb,EAAa,UAAY,KAAK,IAAIA,EAAa,OAASC,CAAc,EAAI,KAC7EvB,GAAgBmB,EAAehb,EAAUob,CAAc,EACvD/B,GAAgBG,EAAahV,EAAK4W,EAAgB3O,CAAQ,EAC1DwO,EAAa,GAEjB,CACF,CAAC,EAGD,MAAMN,EAAgBM,EAAaL,GAAkBI,CAAa,EAAI,EAChEK,EAAgBJ,EAAaT,GAAuBQ,EAAe7X,EAAQ,aAAa,EAAI,EAElG,MAAO,CAAE,WAAA8X,EAAY,cAAAN,EAAe,cAAAU,CAAA,CACtC,CAYO,SAASC,GACdlC,EACArjB,EACA0kB,EACAf,EACkD,CAClD,IAAIiB,EAAgB,EAChBY,EAAgB,EAEpB,QAASlmB,EAAI,EAAGA,EAAI+jB,EAAM,OAAQ/jB,IAAK,CACrC,MAAMuiB,EAAQwB,EAAM/jB,CAAC,EACjBuiB,EAAM,UAEa8B,IAAkB3jB,EAAKV,CAAC,EAAGA,CAAC,IAC5B,SACnBkmB,GAAiB3D,EAAM,OACvB+C,IAGN,CAEA,MAAO,CACL,cAAAA,EACA,cAAeA,EAAgB,EAAIY,EAAgBZ,EAAgBF,CAAA,CAEvE,CC/YO,MAAMe,CAAc,CAgDzB,YAAoBxmB,EAAmB,CAAnB,KAAA,KAAAA,CAAoB,CA7ChC,QAA4B,CAAA,EAGpC,YAAwC,CACtC,OAAO,KAAK,OACd,CAGQ,cAAiF,IAGjF,kBAA+C,IAG/C,oBAAmD,IAGnD,gBAA2C,IAS3C,mBAAkF,IASlF,kBAAsD,IAM9D,OAAe,kBAAoB,IAAI,QASvC,UAAU+G,EAAiC,CACzC,UAAWM,KAAUN,EACnB,KAAK,OAAOM,CAAM,CAEtB,CAMA,OAAOA,EAA8B,CAUnC,GAPAic,GAA2Bjc,EAAQ,KAAK,OAAO,EAG/C,KAAK,UAAU,IAAIA,EAAO,YAA2DA,CAAM,EAC3F,KAAK,QAAQ,KAAKA,CAAM,EAGpBA,EAAO,cACT,SAAW,CAAC9J,EAAMqB,CAAQ,IAAK,OAAO,QAAQyI,EAAO,aAAa,EAChE,KAAK,cAAc,IAAI9J,EAAMqB,CAAQ,EAGzC,GAAIyI,EAAO,gBACT,SAAW,CAAC9J,EAAMqB,CAAQ,IAAK,OAAO,QAAQyI,EAAO,eAAe,EAClE,KAAK,gBAAgB,IAAI9J,EAAMqB,CAAQ,EAG3C,GAAIyI,EAAO,YACT,SAAW,CAAC9J,EAAMwB,CAAM,IAAK,OAAO,QAAQsI,EAAO,WAAW,EAC5D,KAAK,YAAY,IAAI9J,EAAMwB,CAAM,EAKrC,KAAK,sBAAsBsI,CAAM,EAGjC,KAAK,oBAAoBA,CAAM,EAG/BA,EAAO,OAAO,KAAK,IAAI,EAGvB,UAAWof,KAAkB,KAAK,QAC5BA,IAAmBpf,GAAUof,EAAe,kBAC9CA,EAAe,iBAAiBpf,EAAO,KAAMA,CAAM,CAGzD,CAKQ,sBAAsBA,EAA8B,CAE1D,MAAM8b,EADc9b,EAAO,YACE,SAC7B,GAAK8b,GAAU,QAEf,UAAWuD,KAAYvD,EAAS,QAAS,CACvC,IAAIwD,EAAW,KAAK,cAAc,IAAID,EAAS,IAAI,EAC9CC,IACHA,MAAe,IACf,KAAK,cAAc,IAAID,EAAS,KAAMC,CAAQ,GAEhDA,EAAS,IAAItf,CAAM,CACrB,CACF,CAMQ,oBAAoBA,EAA8B,CACxD,MAAMuf,EAAcvf,EAAO,YAM3B,GAHImf,EAAc,kBAAkB,IAAII,CAAW,GAG/C,CAAC9d,KAAiB,OAEtB,MAAM+d,EACJ,OAAOxf,EAAO,gBAAmB,YAAc,OAAOA,EAAO,sBAAyB,WAElFyf,EAAa,OAAOzf,EAAO,cAAiB,WAG9Cwf,GAAe,CAACC,IAClBN,EAAc,kBAAkB,IAAII,CAAW,EAC/C,QAAQ,KACN,oCAAoCvf,EAAO,IAAI;AAAA;AAAA,8EAAA,EAMrD,CAKQ,wBAAwBA,EAA8B,CAC5D,SAAW,CAAC0f,EAAWJ,CAAQ,IAAK,KAAK,cACvCA,EAAS,OAAOtf,CAAM,EAClBsf,EAAS,OAAS,GACpB,KAAK,cAAc,OAAOI,CAAS,CAGzC,CAMA,WAAkB,CAEhB,UAAW1f,KAAU,KAAK,QACxB,UAAW2f,KAAe,KAAK,QACzBA,IAAgB3f,GAAU2f,EAAY,kBACxCA,EAAY,iBAAiB3f,EAAO,IAAI,EAM9C,QAAShH,EAAI,KAAK,QAAQ,OAAS,EAAGA,GAAK,EAAGA,IAAK,CACjD,MAAMgH,EAAS,KAAK,QAAQhH,CAAC,EAC7B,KAAK,eAAegH,CAAM,EAC1B,KAAK,wBAAwBA,CAAM,EACnCA,EAAO,OAAA,CACT,CACA,KAAK,QAAU,CAAA,EACf,KAAK,UAAU,MAAA,EACf,KAAK,cAAc,MAAA,EACnB,KAAK,gBAAgB,MAAA,EACrB,KAAK,YAAY,MAAA,EACjB,KAAK,eAAe,MAAA,EACpB,KAAK,cAAc,MAAA,CACrB,CAOA,UAAoCuf,EAAuD,CACzF,OAAO,KAAK,UAAU,IAAIA,CAAW,CACvC,CAKA,gBAAgBjkB,EAA0C,CACxD,OAAO,KAAK,QAAQ,KAAMa,GAAMA,EAAE,OAASb,CAAI,CACjD,CAKA,UAAoCikB,EAAiD,CACnF,OAAO,KAAK,UAAU,IAAIA,CAAW,CACvC,CAKA,QAAoC,CAClC,OAAO,KAAK,OACd,CAKA,0BAAqC,CACnC,OAAO,KAAK,QAAQ,IAAKpjB,GAAMA,EAAE,IAAI,CACvC,CAOA,gBAAgBjG,EAAwC,CACtD,OAAO,KAAK,cAAc,IAAIA,CAAI,CACpC,CAKA,kBAAkBA,EAA0C,CAC1D,OAAO,KAAK,gBAAgB,IAAIA,CAAI,CACtC,CAKA,cAAcA,EAAsC,CAClD,OAAO,KAAK,YAAY,IAAIA,CAAI,CAClC,CAMA,iBAA2D,CACzD,OAAO,KAAK,QAAQ,OAAQiG,GAAMA,EAAE,MAAM,EAAE,IAAKA,IAAO,CAAE,KAAMA,EAAE,KAAM,OAAQA,EAAE,QAAU,CAC9F,CAQA,YAAYzC,EAA6B,CACvC,IAAI2R,EAAS,CAAC,GAAG3R,CAAI,EACrB,UAAWsG,KAAU,KAAK,QACpBA,EAAO,cACTqL,EAASrL,EAAO,YAAYqL,CAAM,GAGtC,OAAOA,CACT,CAKA,eAAexR,EAAkD,CAC/D,IAAIwR,EAAS,CAAC,GAAGxR,CAAO,EACxB,UAAWmG,KAAU,KAAK,QACpBA,EAAO,iBACTqL,EAASrL,EAAO,eAAeqL,CAAM,GAGzC,OAAOA,CACT,CAKA,cAAqB,CACnB,UAAWrL,KAAU,KAAK,QACxBA,EAAO,eAAA,CAEX,CAKA,aAAoB,CAClB,UAAWA,KAAU,KAAK,QACxBA,EAAO,cAAA,CAEX,CAQA,gBAAgB8G,EAAuC,CACrD,UAAW9G,KAAU,KAAK,QACxBA,EAAO,kBAAkB8G,CAAO,CAEpC,CAMA,wBAAkC,CAChC,OAAO,KAAK,QAAQ,KAAM3K,GAAM,OAAOA,EAAE,iBAAoB,UAAU,CACzE,CAQA,eAAe2K,EAAsC,CACnD,UAAW9G,KAAU,KAAK,QACxBA,EAAO,iBAAiB8G,CAAO,CAEnC,CAMA,uBAAiC,CAC/B,OAAO,KAAK,QAAQ,KAAM3K,GAAM,OAAOA,EAAE,gBAAmB,UAAU,CACxE,CAMA,gBAAuB,CACrB,UAAW6D,KAAU,KAAK,QACxBA,EAAO,iBAAA,CAEX,CAMA,gBAAyB,CACvB,IAAI4f,EAAQ,EACZ,UAAW5f,KAAU,KAAK,QACpB,OAAOA,EAAO,gBAAmB,aACnC4f,GAAS5f,EAAO,eAAA,GAGpB,OAAO4f,CACT,CAOA,gBAA0B,CACxB,UAAW5f,KAAU,KAAK,QACxB,GAAI,OAAOA,EAAO,gBAAmB,YAAcA,EAAO,eAAA,EAAmB,EAC3E,MAAO,GAGX,MAAO,EACT,CAMA,qBAAqB6f,EAAgC,CACnD,IAAID,EAAQ,EACZ,UAAW5f,KAAU,KAAK,QACpB,OAAOA,EAAO,sBAAyB,aACzC4f,GAAS5f,EAAO,qBAAqB6f,CAAc,GAGvD,OAAOD,CACT,CAOA,aAAazX,EAActI,EAAmC,CAC5D,UAAWG,KAAU,KAAK,QACxB,GAAI,OAAOA,EAAO,cAAiB,WAAY,CAC7C,MAAMid,EAASjd,EAAO,aAAamI,EAAKtI,CAAK,EAC7C,GAAIod,IAAW,OACb,OAAOA,CAEX,CAGJ,CAMA,oBAA8B,CAC5B,UAAWjd,KAAU,KAAK,QACxB,GAAI,OAAOA,EAAO,cAAiB,WACjC,MAAO,GAGX,MAAO,EACT,CAMA,mBAAmBkD,EAAeqW,EAAmB/Q,EAA2B,CAC9E,IAAIsX,EAAgB5c,EACpB,UAAWlD,KAAU,KAAK,QACxB,GAAI,OAAOA,EAAO,oBAAuB,WAAY,CACnD,MAAM+f,EAAc/f,EAAO,mBAAmBkD,EAAOqW,EAAW/Q,CAAS,EACrEuX,EAAcD,IAChBA,EAAgBC,EAEpB,CAEF,OAAOD,CACT,CAMA,UAAU3X,EAAUhP,EAAoBwK,EAA2B,CACjE,UAAW3D,KAAU,KAAK,QACxB,GAAIA,EAAO,YAAYmI,EAAKhP,EAAOwK,CAAQ,EACzC,MAAO,GAGX,MAAO,EACT,CAeA,aAAgBqc,EAAyB,CACvC,MAAMC,EAAiB,CAAA,EAGjBX,EAAW,KAAK,cAAc,IAAIU,EAAM,IAAI,EAClD,GAAIV,GAAYA,EAAS,KAAO,EAAG,CAEjC,UAAWtf,KAAUsf,EAAU,CAC7B,MAAMY,EAAWlgB,EAAO,cAAcggB,CAAK,GAAKhgB,EAAO,gBAAgBggB,CAAK,EACxEE,IAAa,QACfD,EAAU,KAAKC,CAAa,CAEhC,CACA,OAAOD,CACT,CAGA,UAAWjgB,KAAU,KAAK,QAAS,CAEjC,MAAMkgB,EAAWlgB,EAAO,cAAcggB,CAAK,GAAKhgB,EAAO,gBAAgBggB,CAAK,EACxEE,IAAa,QACfD,EAAU,KAAKC,CAAa,CAEhC,CACA,OAAOD,CACT,CAYA,UAAUjgB,EAAwBmgB,EAAmBlf,EAA2C,CAC9F,IAAImf,EAAY,KAAK,eAAe,IAAID,CAAS,EAC5CC,IACHA,MAAgB,IAChB,KAAK,eAAe,IAAID,EAAWC,CAAS,GAE9CA,EAAU,IAAIpgB,EAAQiB,CAAQ,CAChC,CAQA,YAAYjB,EAAwBmgB,EAAyB,CAC3D,MAAMC,EAAY,KAAK,eAAe,IAAID,CAAS,EAC/CC,IACFA,EAAU,OAAOpgB,CAAM,EACnBogB,EAAU,OAAS,GACrB,KAAK,eAAe,OAAOD,CAAS,EAG1C,CAQA,eAAengB,EAA8B,CAC3C,SAAW,CAACmgB,EAAWC,CAAS,IAAK,KAAK,eACxCA,EAAU,OAAOpgB,CAAM,EACnBogB,EAAU,OAAS,GACrB,KAAK,eAAe,OAAOD,CAAS,CAG1C,CASA,gBAAmBA,EAAmB/X,EAAiB,CACrD,MAAMgY,EAAY,KAAK,eAAe,IAAID,CAAS,EACnD,GAAIC,EACF,UAAWnf,KAAYmf,EAAU,SAC/B,GAAI,CACFnf,EAASmH,CAAM,CACjB,OAASiY,EAAO,CACd,QAAQ,MAAM,iDAAiDF,CAAS,KAAME,CAAK,CACrF,CAGN,CAQA,UAAUpW,EAA+B,CACvC,UAAWjK,KAAU,KAAK,QACxB,GAAIA,EAAO,YAAYiK,CAAK,EAC1B,MAAO,GAGX,MAAO,EACT,CAMA,YAAYA,EAAgC,CAC1C,UAAWjK,KAAU,KAAK,QACxB,GAAIA,EAAO,cAAciK,CAAK,EAC5B,MAAO,GAGX,MAAO,EACT,CAMA,WAAWA,EAA+B,CACxC,UAAWjK,KAAU,KAAK,QACxB,GAAIA,EAAO,aAAaiK,CAAK,EAC3B,MAAO,GAGX,MAAO,EACT,CAMA,cAAcA,EAAkC,CAC9C,UAAWjK,KAAU,KAAK,QACxB,GAAIA,EAAO,gBAAgBiK,CAAK,EAC9B,MAAO,GAGX,MAAO,EACT,CAKA,SAASA,EAA0B,CACjC,UAAWjK,KAAU,KAAK,QACxBA,EAAO,WAAWiK,CAAK,CAE3B,CAMA,gBAAgBA,EAAgC,CAC9C,UAAWjK,KAAU,KAAK,QACxB,GAAIA,EAAO,kBAAkBiK,CAAK,EAChC,MAAO,GAGX,MAAO,EACT,CAMA,gBAAgBA,EAAgC,CAC9C,UAAWjK,KAAU,KAAK,QACxB,GAAIA,EAAO,kBAAkBiK,CAAK,EAChC,MAAO,GAGX,MAAO,EACT,CAMA,cAAcA,EAAgC,CAC5C,UAAWjK,KAAU,KAAK,QACxB,GAAIA,EAAO,gBAAgBiK,CAAK,EAC9B,MAAO,GAGX,MAAO,EACT,CAcA,2BACE9Q,EACAmnB,EACuD,CACvD,IAAIC,EAAO,EACPC,EAAQ,EACRC,EAAa,GACjB,UAAWzgB,KAAU,KAAK,QAAS,CACjC,MAAMkJ,EAAUlJ,EAAO,6BAA6B7G,EAAOmnB,CAAW,EAClEpX,IACFqX,GAAQrX,EAAQ,KAChBsX,GAAStX,EAAQ,MACbA,EAAQ,aACVuX,EAAa,IAGnB,CACA,MAAO,CAAE,KAAAF,EAAM,MAAAC,EAAO,WAAAC,CAAA,CACxB,CASA,eAGI,CACF,MAAMzhB,EAGA,CAAA,EACN,UAAWgB,KAAU,KAAK,QAAS,CACjC,MAAM4R,EAAQ5R,EAAO,eAAA,EACjB4R,GACF5S,EAAO,KAAK,CAAE,OAAAgB,EAAQ,MAAA4R,CAAA,CAAO,CAEjC,CAEA,OAAO5S,EAAO,KAAK,CAAC1H,EAAG2H,KAAO3H,EAAE,MAAM,OAAS,IAAM2H,EAAE,MAAM,OAAS,EAAE,CAC1E,CAMA,mBAGI,CACF,MAAME,EAGA,CAAA,EACN,UAAWa,KAAU,KAAK,QAAS,CACjC,MAAMP,EAAUO,EAAO,mBAAA,EACnBP,GACFN,EAAS,KAAK,CAAE,OAAAa,EAAQ,QAAAP,CAAA,CAAS,CAErC,CAEA,OAAON,EAAS,KAAK,CAAC7H,EAAG2H,KAAO3H,EAAE,QAAQ,OAAS,IAAM2H,EAAE,QAAQ,OAAS,EAAE,CAChF,CAEF,CC9uBO,MAAMyhB,GAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,ECgHnB,MAAMC,UAAiC,WAAuC,CAEnF,OAAgB,QAAU,WAE1B,OAAgB,QAAU,OAAO,iBAAqB,IAAc,iBAAmB,MAGvF,MAAOC,GAAmB,EAQ1B,OAAe,SAA+B,CAAA,EAgB9C,OAAO,gBAAgBte,EAAiC,CACtD,KAAK,SAAS,KAAKA,CAAO,CAC5B,CAOA,OAAO,aAA2C,CAChD,OAAO,KAAK,QACd,CAMA,OAAO,eAAsB,CAC3B,KAAK,SAAW,CAAA,CAClB,CAIA,WAAW,oBAA+B,CACxC,MAAO,CAAC,OAAQ,UAAW,cAAe,WAAY,SAAS,CACjE,CAOA,GAAIue,IAA2B,CAC7B,OAAO,IACT,CAEAC,GAAe,GAGfnT,GACAC,GAKAmT,GAAa,CAAA,EAKb,GAAIxjB,IAAkC,CACpC,OAAO,KAAKyjB,IAAgB,WAAa,CAAA,CAC3C,CAEAC,GAAa,GAGbC,GAAiB,GACjBC,GAAsB,CACpB,KAAM,GACN,QAAS,GACT,WAAY,GACZ,QAAS,EAAA,EAIXC,GAEAC,GAAa,EACbC,GAAmC,KACnCC,GAAoB,GACpBC,GAA6B,GAC7BC,GAAwB,EACxBC,GACAC,GAAgChJ,GAAA,EAChCiJ,GACAC,GACAC,GACAC,GAGAC,GAAkC,CAChC,UAAW,EACX,WAAY,EACZ,aAAc,EACd,YAAa,EACb,aAAc,EACd,YAAa,CAAA,EAIfC,GACAC,GAGAC,GAAuB,GACvBC,GACAC,GAGAzkB,GAGAojB,GAGAsB,GAA0BlQ,GAAA,EAC1BmQ,GACAC,GAGAC,GAAW,GACXC,OAAmB,IACnBC,OAAoB,IACpBC,GAGAC,OAAgB,IAKhB,MAAa,CAAA,EAIbC,GAAoC,CAAA,EAKpC,IAAI,UAAgC,CAClC,OAAQ,KAAKvlB,GAAiB,SAAW,CAAA,CAC3C,CACA,IAAI,SAASzG,EAA4B,CACvC,KAAKyG,GAAiB,QAAUzG,CAClC,CAIA,IAAI,iBAAuC,CACzC,OAAO,KAAK,SAAS,OAAQa,GAAM,CAACA,EAAE,MAAM,CAC9C,CAKA,aACA,QACA,SAA0B,CAAA,EAC1B,kBAGA,gBAAgC,CAC9B,QAAS,GACT,UAAW,GACX,gBAAiB,GACjB,MAAO,EACP,IAAK,EACL,UAAW,KACX,WAAY,KACZ,cAAe,KAEf,cAAe,KACf,YAAa,CACX,UAAW,IACX,UAAW,OAAwB,EAErC,cAAe,GACf,cAAe,EACf,gBAAiB,GACjB,qBAAsB,EACtB,iBAAkB,EAClB,uBAAwB,EACxB,aAAc,IAAA,EAIhB,UAAY,EACZ,UAAY,EAEZ,yBAA2B,GAG3B,WAA0D,KAG1D,cAAgB,GAIhB,iBAAmB,EACnB,qBAAuB,GAGvB,IAAI,wBAAuD,CACzD,OAAO,KAAKqpB,IAAgB,oBAC9B,CACA,IAAI,uBAAuBlqB,EAAqC,CAC1D,KAAKkqB,KACP,KAAKA,GAAe,qBAAuBlqB,EAE/C,CAGA,IAAI,uBAAmD,CACrD,OAAO,KAAKkqB,IAAgB,mBAC9B,CACA,IAAI,sBAAsBlqB,EAAkC,CACtD,KAAKkqB,KACP,KAAKA,GAAe,oBAAsBlqB,EAE9C,CAEA,gBAAuB,CAAA,EAOvB,mBAGA,aAAmC,KA2BnC,IAAI,MAAY,CACd,OAAO,KAAK,KACd,CACA,IAAI,KAAKA,EAAY,CACnB,MAAMisB,EAAW,KAAKhC,GACtB,KAAKA,GAAQjqB,EACTisB,IAAajsB,GACf,KAAKksB,GAAa,MAAM,CAE5B,CAmBA,IAAI,YAAkB,CACpB,OAAO,KAAKjC,EACd,CA+BA,IAAI,SAA6B,CAC/B,MAAO,CAAC,GAAG,KAAK,QAAQ,CAC1B,CACA,IAAI,QAAQjqB,EAA2D,CACrE,MAAMisB,EAAW,KAAK/B,IAAgB,WAAA,EACtC,KAAKA,IAAgB,WAAWlqB,CAAK,EACjCisB,IAAajsB,GACf,KAAKksB,GAAa,SAAS,CAE/B,CA+BA,IAAI,YAA4B,CAC9B,OAAO,KAAKzlB,EACd,CACA,IAAI,WAAWzG,EAAkC,CAC/C,MAAMisB,EAAW,KAAK/B,IAAgB,cAAA,EACtC,KAAKA,IAAgB,cAAclqB,CAAK,EACpCisB,IAAajsB,IAGf,KAAKkqB,GAAe,mBAAA,EACpB,KAAKgC,GAAa,YAAY,EAElC,CAsBA,IAAI,SAAmB,CACrB,OAAO,KAAKzlB,GAAiB,SAAW,SAC1C,CACA,IAAI,QAAQzG,EAA4B,CACtC,MAAMisB,EAAW,KAAK/B,IAAgB,WAAA,EACtC,KAAKA,IAAgB,WAAWlqB,CAAK,EACjCisB,IAAajsB,GACf,KAAKksB,GAAa,SAAS,CAE/B,CAgBA,IAAI,SAAmB,CACrB,OAAO,KAAKP,EACd,CAEA,IAAI,QAAQ3rB,EAAgB,CAC1B,MAAMmsB,EAAa,KAAKR,GACxB,KAAKA,GAAW3rB,EAGZA,EACF,KAAK,aAAa,UAAW,EAAE,EAE/B,KAAK,gBAAgB,SAAS,EAI5BmsB,IAAensB,GACjB,KAAKosB,IAAA,CAET,CASA,cAAcze,EAAe4I,EAAwB,CACnD,MAAM4V,EAAa,KAAKP,GAAa,IAAIje,CAAK,EAC1C4I,EACF,KAAKqV,GAAa,IAAIje,CAAK,EAE3B,KAAKie,GAAa,OAAOje,CAAK,EAI5Bwe,IAAe5V,GACjB,KAAK8V,IAAuB1e,EAAO4I,CAAO,CAE9C,CAUA,eAAe5I,EAAezO,EAAeqX,EAAwB,CACnE,IAAI+V,EAAa,KAAKT,GAAc,IAAIle,CAAK,EAC7C,MAAMwe,EAAaG,GAAY,IAAIptB,CAAK,GAAK,GAEzCqX,GACG+V,IACHA,MAAiB,IACjB,KAAKT,GAAc,IAAIle,EAAO2e,CAAU,GAE1CA,EAAW,IAAIptB,CAAK,IAEpBotB,GAAY,OAAOptB,CAAK,EAEpBotB,GAAY,OAAS,GACvB,KAAKT,GAAc,OAAOle,CAAK,GAK/Bwe,IAAe5V,GACjB,KAAKgW,IAAwB5e,EAAOzO,EAAOqX,CAAO,CAEtD,CAMA,aAAa5I,EAAwB,CACnC,OAAO,KAAKie,GAAa,IAAIje,CAAK,CACpC,CAOA,cAAcA,EAAezO,EAAwB,CACnD,OAAO,KAAK2sB,GAAc,IAAIle,CAAK,GAAG,IAAIzO,CAAK,GAAK,EACtD,CAKA,iBAAwB,CACtB,KAAK,QAAU,GAGf,UAAWyO,KAAS,KAAKie,GACvB,KAAKS,IAAuB1e,EAAO,EAAK,EAE1C,KAAKie,GAAa,MAAA,EAGlB,SAAW,CAACje,EAAOiX,CAAM,IAAK,KAAKiH,GACjC,UAAW3sB,KAAS0lB,EAClB,KAAK2H,IAAwB5e,EAAOzO,EAAO,EAAK,EAGpD,KAAK2sB,GAAc,MAAA,CACrB,CAWA,IAAI,iBAAiC,CACnC,OAAO,KAAKplB,EACd,CAWA,IAAI,kBAAgC,CAElC,OAAK,KAAKqkB,KACR,KAAKA,GAAwB,IAAI,iBAE5B,KAAKA,GAAsB,MACpC,CAMA,aAAc,CACZ,MAAA,EAEK,KAAK0B,IAAA,EACV,KAAK3V,GAAgB,IAAI,QAAS7R,GAAS,KAAK8R,GAAgB9R,CAAI,EAGpE,KAAKslB,GAAa,IAAI5T,GAAgB,CACpC,YAAa,IAAM,CAGjB,KAAKwT,GAAe,qBAAqB,IAA8B,EACvE,KAAKA,GAAe,MAAA,EACpB,KAAKuC,IAAA,EAGLxI,GAAyB,KAAKxd,GAAkB,KAAK0kB,IAAgB,WAAA,GAAgB,EAAE,EAEvFrG,GAA0B,KAAKqG,IAAgB,WAAA,GAAgB,CAAA,CAAE,EAEjExF,GAAgC,KAAKwF,IAAgB,WAAA,GAAgB,CAAA,CAAE,EAEvE,KAAKuB,IAAA,EAEL,KAAKV,GAAe,CAAC,GAAG,KAAK,QAAQ,CACvC,EACA,eAAgB,IAAM,KAAKW,IAAA,EAC3B,YAAa,IAAM,KAAKC,IAAA,EACxB,aAAc,IAAMxY,GAAa,IAAI,EACrC,eAAgB,IAAM5R,EAAe,IAAI,EACzC,oBAAqB,IAAM,KAAK,qBAAqB,GAAM,EAAI,EAC/D,YAAa,IAAM,CACjB,KAAK2oB,IAAgB,YAAA,EAWjB,KAAK,gBAAgB,SAAW,KAAK,gBAAgB,eACvD,eAAe,IAAM,CACnB,GAAI,CAAC,KAAK,gBAAgB,cAAe,OACzC,MAAM0B,EAAiB,KAAKC,GAA4B,KAAK,MAAM,MAAM,EACzE,KAAK,gBAAgB,cAAc,MAAM,OAAS,GAAGD,CAAc,IACrE,CAAC,EAIU,KAAKpmB,GAAiB,UACtB,SAAW,CAAC,KAAK,uBAC5B,KAAK,qBAAuB,GAC5B7E,GAAgB,IAAI,GAGlB,KAAK,2BACP,KAAK,yBAA2B,GAChC8O,EAAkB,IAAI,GAGpB,KAAK,gBAAgB,SAAW,CAAC,KAAKqc,IACxC,KAAKC,IAAA,EAIH,KAAKtC,KACP,KAAKA,GAA6B,GAGlC,sBAAsB,IAAM,CAC1B,sBAAsB,IAAM,CAC1B,KAAKuC,IAAA,CACP,CAAC,CACH,CAAC,EAEL,EACA,YAAa,IAAM,KAAK,aAAe,KAAK9C,EAAA,CAC7C,EAED,KAAKG,GAAW,wBAAwB,IAAM,KAAKxT,MAAiB,EAGpE,KAAK2U,GAAmBrM,GAAsB,KAAKoM,GAAa,CAC9D,UAAW,IAAM,KAAKzB,GACtB,eAAgB,IAAM,KAAKtjB,IAAkB,MAC7C,kBAAmB,KAAO,CACxB,OAAQ,KAAKA,IAAkB,OAAO,QAAU3H,EAAmB,OACnE,SAAU,KAAK2H,IAAkB,OAAO,UAAY3H,EAAmB,QAAA,GAEzE,KAAM,CAACouB,EAAW5b,IAAW,KAAK6b,GAAMD,EAAW5b,CAAM,EACzD,mBAAoB,IAAM,KAAK,mBAAA,CAAmB,CACnD,EAGD,KAAK4Y,GAAiB,IAAIhkB,GAAiB,CACzC,QAAS,IAAM,KAAK+jB,GACpB,aAAc,IAAM,KAAK,WACzB,aAAepsB,GAAU,CACvB,KAAK,WAAaA,CACpB,EACA,eAAgB,IAAM,CACpB,KAAKysB,GAAW,aAAa7T,EAAY,KAAM,cAAc,CAC/D,EACA,KAAM,CAACyW,EAAW5b,IAAW,KAAK6b,GAAMD,EAAW5b,CAAM,EACzD,aAAc,IAAM,CAClB,KAAK,SAAS,OAAS,EACnB,KAAK,UAAS,KAAK,QAAQ,UAAY,IAC3C,KAAK,kBACP,EACA,MAAO,IAAM,KAAK8b,GAAA,EAClB,aAAc,IAAMhZ,GAAa,IAAI,EACrC,eAAgB,IAAM5R,EAAe,IAAI,EACzC,qBAAsB,IAAM,KAAK8nB,GAAW,aAAa7T,EAAY,eAAgB,eAAe,EACpG,kBAAmB,IAAM,KAAK,gBAC9B,aAAe0P,GAAW,CACxB,KAAK,gBAAgB,UAAYA,CACnC,EACA,qBAAuB/nB,GAAW,KAAKivB,IAAsBjvB,CAAM,EACnE,sBAAuB,IAAM,KAAKotB,GAAY,cAC9C,mBAAoB,IAAM,KAAKA,GAAY,WAC3C,uBAAwB,IAAM,KAAKA,GAAY,eAC/C,wBAAyB,IAAM,KAAKA,GAAY,gBAChD,8BAA+B,IAAM,KAAKA,GAAY,sBACtD,gCAAiC,IAAM,KAAKA,GAAY,uBAAA,CACzD,CACH,CAMA,KAAMgB,KAA+B,CACnC,MAAM9K,GAAaN,EAAM,CAC3B,CASA,UAAaqH,EAAuD,CAClE,OAAO,KAAK0C,IAAgB,UAAU1C,CAAqD,CAC7F,CAQA,gBAAgBjkB,EAA0C,CACxD,OAAO,KAAK2mB,IAAgB,gBAAgB3mB,CAAI,CAClD,CASA,eAAsB,CACpB,KAAK8lB,GAAW,aAAa7T,EAAY,KAAM,sBAAsB,CACvE,CAUA,sBAA6B,CAC3B,KAAK6T,GAAW,aAAa7T,EAAY,QAAS,6BAA6B,CACjF,CASA,wBAA+B,CAC7B,KAAK,yBAA2B,GAChC,KAAK6T,GAAW,aAAa7T,EAAY,KAAM,+BAA+B,CAChF,CAQA,gBAAuB,CACrBjU,EAAe,IAAI,CACrB,CASA,oBAA2B,CACzB,KAAK8nB,GAAW,aAAa7T,EAAY,MAAO,2BAA2B,CAC7E,CAMA6W,IAA2B,CAEzB,KAAKnC,GAAiB,IAAI9C,EAAc,IAAI,EAG5C,MAAMkF,EAAgB,KAAK9mB,IAAkB,QACvCmC,EAAU,MAAM,QAAQ2kB,CAAa,EAAKA,EAAqC,CAAA,EAGrF,KAAKpC,GAAe,UAAUviB,CAAO,CACvC,CAOA4kB,IAA+B,CAC7B,MAAMvM,EAAe,KAAKkK,IAAgB,gBAAA,GAAqB,CAAA,EAC/DjK,GAAgBD,CAAY,CAC9B,CAOAwL,KAA6B,CAE3B,MAAMc,EAAgB,KAAK9mB,IAAkB,QACvCgnB,EAAa,MAAM,QAAQF,CAAa,EAAKA,EAAqC,CAAA,EAIxF,GAAI,KAAKnC,KAAsBqC,EAC7B,OAIF,GACE,KAAKrC,IACL,KAAKA,GAAkB,SAAWqC,EAAW,QAC7C,KAAKrC,GAAkB,MAAM,CAAC/lB,EAAGnD,IAAMmD,IAAMooB,EAAWvrB,CAAC,CAAC,EAC1D,CAEA,KAAKkpB,GAAoBqC,EACzB,MACF,CAGI,KAAKtC,IACP,KAAKA,GAAe,UAAA,EAQtB,UAAWvM,KAAW,KAAK4M,GAAY,WAAW,OAAQ,CACxD,MAAMkC,EAAa,KAAKlC,GAAY,qBAAqB,IAAI5M,CAAO,EAC9D+O,EAAkB,KAAKnC,GAAY,gBAAgB,IAAI5M,CAAO,EACpE,GAAI,CAAC8O,GAAc,CAACC,EAAiB,CAEnC,MAAMzQ,EAAU,KAAKsO,GAAY,cAAc,IAAI5M,CAAO,EACtD1B,IACFA,EAAA,EACA,KAAKsO,GAAY,cAAc,OAAO5M,CAAO,GAE/C,KAAK4M,GAAY,WAAW,OAAO5M,CAAO,CAC5C,CACF,CAIA,UAAWkB,KAAa,KAAK0L,GAAY,eAAe,OAAQ,CAC9D,MAAMtO,EAAU,KAAKsO,GAAY,sBAAsB,IAAI1L,CAAS,EAChE5C,IACFA,EAAA,EACA,KAAKsO,GAAY,sBAAsB,OAAO1L,CAAS,GAEzD,KAAK0L,GAAY,eAAe,OAAO1L,CAAS,CAClD,CAEA,KAAKwN,GAAA,EACL,KAAKE,GAAA,EAGL,KAAKpC,GAAoBqC,EAMzB,KAAKG,IAAA,EAIL,KAAKC,IAAA,EAIL,MAAMC,EAAmB,KAAKrD,GAI9B,GAHA,KAAKA,GAAoB,KAAKU,IAAgB,OAAA,EAAS,KAAM9lB,GAAMA,EAAE,QAAQ,GAAK,GAG9E,CAACyoB,GAAoB,KAAKrD,GAAmB,CAE/C,MAAMtU,EADc,KAAK4T,GAAY,cAAc,mBAAmB,GACtC,KAAKA,GAAY,cAAc,gBAAgB,EAC/E,KAAKgE,GAAsB5X,CAAQ,CACrC,CACF,CAKA6X,KAAwB,CACtB,KAAK7C,IAAgB,UAAA,CACvB,CAMA0C,KAAyC,CACvC,GAAI,CAAC,KAAK1C,GAAgB,OAG1B,MAAM8C,EAAe,KAAK9C,GAAe,cAAA,EACzC,SAAW,CAAE,MAAArQ,CAAA,IAAWmT,EAEjB,KAAKzC,GAAY,WAAW,IAAI1Q,EAAM,EAAE,GAC3C,KAAK0Q,GAAY,WAAW,IAAI1Q,EAAM,GAAIA,CAAK,EAKnD,MAAMoT,EAAiB,KAAK/C,GAAe,kBAAA,EAC3C,SAAW,CAAE,QAAAxiB,CAAA,IAAaulB,EAEnB,KAAK1C,GAAY,eAAe,IAAI7iB,EAAQ,EAAE,GACjD,KAAK6iB,GAAY,eAAe,IAAI7iB,EAAQ,GAAIA,CAAO,CAG7D,CAMAwlB,KAAqE,CACnE,MAAM9tB,EAAWwpB,EAAgB,YAAA,EACjC,GAAIxpB,EAAS,SAAW,GAAK,CAAC,KAAK,mBAAoB,OAGvD,MAAM+tB,EAAkB,KAAK,mBAE7B,OAAQhjB,GAAyB,CAE/B,GAAIgjB,GAAiB,wBAAyB,CAC5C,MAAM3tB,EAAW2tB,EAAgB,wBAAwBhjB,CAAO,EAChE,GAAI3K,EAAU,OAAOA,CACvB,CAGA,UAAW+K,KAAWnL,EACpB,GAAImL,EAAQ,wBAAyB,CACnC,MAAM/K,EAAW+K,EAAQ,wBAAwBJ,CAAO,EACxD,GAAI3K,EAAU,OAAOA,CACvB,CAIJ,CACF,CAKA,mBAA0B,CACnB,KAAK,aAAa,UAAU,SAAQ,SAAW,GAC/C,KAAK,aAAa,SAAS,GAAG,KAAK,aAAa,UAAWopB,EAAgB,OAAO,EAElF,KAAK,KACR,KAAK,GAAK,YAAY,EAAEA,EAAgBC,EAAgB,IAE1D,KAAK,MAAQ,MAAM,QAAQ,KAAKG,EAAK,EAAI,CAAC,GAAG,KAAKA,EAAK,EAAI,CAAA,EAKvD,KAAKa,KACP,KAAKA,GAAsB,MAAA,EAC3B,KAAKO,GAAuB,IAE9B,KAAKP,GAAwB,IAAI,gBAG7B,KAAKG,KACPvV,GAAW,KAAKuV,EAAmB,EACnC,KAAKA,GAAsB,QAM7B,KAAKoD,GAAA,EAEL,KAAKnE,GAAe,qBAAqB,IAA8B,EAGvE,KAAKA,GAAe,MAAA,EAGpB,KAAKoD,GAAA,EAGL,MAAMC,EAAgB,KAAK9mB,IAAkB,QAC7C,KAAK2kB,GAAoB,MAAM,QAAQmC,CAAa,EAAKA,EAAqC,CAAA,EAG9F,KAAKM,IAAA,EAEA,KAAK7D,KACR,KAAKsE,GAAA,EACL,KAAKd,GAAA,EACL,KAAKxD,GAAe,IAEtB,KAAKuE,IAAA,EAGL,KAAKtD,GAAsBxV,GACzB,IAAM,CAGJ,KAAK+Y,IAAA,CACP,EACA,CAAE,QAAS,GAAA,CAAI,CAEnB,CAGA,sBAA6B,CAEvB,KAAKvD,KACPvV,GAAW,KAAKuV,EAAmB,EACnC,KAAKA,GAAsB,QAIzB,KAAKN,KACP,aAAa,KAAKA,EAAqB,EACvC,KAAKA,GAAwB,GAI/B,KAAKqD,IAAA,EAGL7O,GAAkB,KAAKqM,EAAW,EAClC,KAAKC,GAAiB,eAAe,EAAK,EAG1C,KAAKC,KAAA,EACL,KAAKA,GAAiB,OAGtB3J,GAAe,KAAK8I,EAAW,EAI3B,KAAKC,KACP,KAAKA,GAAsB,MAAA,EAC3B,KAAKA,GAAwB,QAG/B,KAAKQ,IAAwB,MAAA,EAC7B,KAAKA,GAAyB,OAC9B,KAAKD,GAAuB,GAExB,KAAK,mBACP,KAAK,kBAAkB,QAAA,EAErB,KAAKN,KACP,KAAKA,GAAgB,WAAA,EACrB,KAAKA,GAAkB,QAErB,KAAKC,KACP,KAAKA,GAAmB,WAAA,EACxB,KAAKA,GAAqB,OAC1B,KAAK+B,GAA0B,IAIjC7gB,EAAoB,IAAI,EACxB,KAAKuiB,GAAmB,MAAA,EAGxB,KAAKrD,GAAoB,OAGzB,UAAW/oB,KAAS,KAAK,SACvBA,EAAM,OAAA,EAER,KAAK,SAAS,OAAS,EAGvB,KAAK,aAAe,KAEpB,KAAK8nB,GAAa,EACpB,CAQA,yBAAyB3lB,EAAcynB,EAAyByC,EAA+B,CAE7F,GAAIlqB,IAAS,UAAW,CACtB,MAAMmqB,EAAYD,IAAa,MAAQA,IAAa,QAChD,KAAK,UAAYC,IACnB,KAAK,QAAUA,GAEjB,MACF,CAEA,GAAI,EAAA1C,IAAayC,GAAY,CAACA,GAAYA,IAAa,QAAUA,IAAa,aAG9E,GAAIlqB,IAAS,QAAUA,IAAS,WAAaA,IAAS,cACpD,GAAI,CACF,MAAMqU,EAAS,KAAK,MAAM6V,CAAQ,EAC9BlqB,IAAS,OAAQ,KAAK,KAAOqU,EACxBrU,IAAS,UAAW,KAAK,QAAUqU,EACnCrU,IAAS,gBAAe,KAAK,WAAaqU,EACrD,MAAQ,CACN,QAAQ,KAAK,gCAAgCrU,CAAI,eAAgBkqB,CAAQ,CAC3E,MACSlqB,IAAS,aAClB,KAAK,QAAUkqB,EAEnB,CAEAH,KAAsB,CAGpB,MAAMpY,EADc,KAAK4T,GAAY,cAAc,mBAAmB,GACtC,KAAKA,GAAY,cAAc,gBAAgB,EAc/E,GAZA,KAAK,aAAe5T,GAAU,cAAc,aAAa,EAIzD,KAAK,gBAAgB,cAAgBA,GAAU,cAAc,sBAAsB,EACnF,KAAK,gBAAgB,WAAaA,GAAU,cAAc,gBAAgB,EAC1E,KAAK,QAAUA,GAAU,cAAc,OAAO,EAG9C,KAAK,aAAeA,GAAU,cAAc,YAAY,EAGpD,KAAKsV,GAAiB,cAAe,CAEvCrN,GAAoB,KAAK2L,GAAa,KAAKyB,EAAW,EAEtDtN,GAA4B,KAAK6L,GAAa,KAAKtjB,IAAkB,MAAO,KAAK+kB,EAAW,EAE5F,MAAMoD,EAAc,KAAKnoB,IAAkB,OAAO,WAAW,YACzDmoB,GAAe,KAAKpD,GAAY,WAAW,IAAIoD,CAAW,IAC5D,KAAK,cAAA,EACL,KAAKpD,GAAY,iBAAiB,IAAIoD,CAAW,EAErD,CAmBA,GAhBA,KAAK,aAAa,gBAAiB,EAAE,EACrC,KAAKzE,GAAa,GAGlB,KAAK,kBAAoB5S,GAAuB,IAAkC,EAGlF,KAAK6V,GAAA,EAGL,KAAKW,GAAsB5X,CAAQ,EAM/B,KAAKkV,GACP,OAEF,KAAKA,GAAuB,GAG5B,MAAM9X,EAAS,KAAK,iBAIpBC,GAAyB,KAAoC,KAAM,KAAKuW,GAAaxW,CAAM,EAM3F,KAAKqa,IAAA,EAGL,eAAe,IAAM,KAAKiB,KAAsB,EAMhD,KAAKvE,GAAW,aAAa7T,EAAY,KAAM,cAAc,CAC/D,CAWAmX,KAAkC,CAChC,MAAMkB,EAAgB,KAAKroB,GAAiB,UACtCsoB,EAAqB,KAAK5D,GAAe,mBAAA,EAE3C,OAAO2D,GAAkB,YAAcC,EACpC,KAAK,gBAAgB,kBACxB,KAAK,gBAAgB,gBAAkB,GACvC,KAAK,gBAAgB,UACnB,OAAOD,GAAkB,UAAYA,EAAgB,EAAIA,EAAgB,KAAK,gBAAgB,WAAa,GAC7G,KAAKE,GAAA,EACD,OAAOF,GAAkB,aAC3B,KAAKpE,GAA6B,KAG7B,CAACqE,GAAsB,OAAOD,GAAkB,YAAc,KAAK,gBAAgB,iBAE5F,KAAK,gBAAgB,gBAAkB,GACvC,KAAK,gBAAgB,cAAgB,MAC5B,OAAOA,GAAkB,UAAYA,EAAgB,GAC9D,KAAK,gBAAgB,UAAYA,EACjC,KAAK,gBAAgB,gBAAkB,IAIvC,sBAAsB,IAAM,KAAKG,KAAmB,CAExD,CAMAA,KAA0B,CAIxB,GAAI,KAAK9D,GAAe,iBACtB,OAGF,MAAM+D,EAAW,KAAK,SAAS,cAAc,gBAAgB,EAC7D,GAAI,CAACA,EAAU,OAGf,MAAMC,EAAQD,EAAS,iBAAiB,OAAO,EAC/C,IAAIE,EAAgB,EACpBD,EAAM,QAAS7sB,GAAS,CACtB,MAAMkF,EAAKlF,EAAqB,aAC5BkF,EAAI4nB,IAAeA,EAAgB5nB,EACzC,CAAC,EAED,MAAM6nB,EAAWH,EAAyB,sBAAA,EAGpCjH,EAAiB,KAAK,IAAIoH,EAAQ,OAAQD,CAAa,EAEzDnH,EAAiB,GAAK,KAAK,IAAIA,EAAiB,KAAK,gBAAgB,SAAS,EAAI,IACpF,KAAK,gBAAgB,UAAYA,EAEjC,KAAKqC,GAAW,aAAa7T,EAAY,eAAgB,kBAAkB,EAE/E,CAOAwW,KAAoC,CAClC,MAAMiC,EAAW,KAAK,SAAS,cAAc,gBAAgB,EAC7D,GAAI,CAACA,EAAU,OAGf,MAAMC,EAAQD,EAAS,iBAAiB,OAAO,EAC/C,IAAIE,EAAgB,EACpBD,EAAM,QAAS7sB,GAAS,CACtB,MAAMkF,EAAKlF,EAAqB,aAC5BkF,EAAI4nB,IAAeA,EAAgB5nB,EACzC,CAAC,EAED,MAAM6nB,EAAWH,EAAyB,sBAAA,EAGpCjH,EAAiB,KAAK,IAAIoH,EAAQ,OAAQD,CAAa,EAG7D,GAAInH,EAAiB,IACG,KAAK,IAAIA,EAAiB,KAAK,gBAAgB,SAAS,EAAI,IAEhF,KAAK,gBAAgB,UAAYA,GAMnC,KAAK+G,GAAA,EAGD,KAAK,gBAAgB,eAAe,CACtC,MAAMrI,EAAY,KAAKmG,GAA4B,KAAK,MAAM,MAAM,EACpE,KAAK,gBAAgB,cAAc,MAAM,OAAS,GAAGnG,CAAS,IAChE,CAEJ,CAOAoH,GAAsB5X,EAAgC,CAEpD,KAAKmV,IAAwB,MAAA,EAC7B,KAAKA,GAAyB,IAAI,gBAClC,MAAMgE,EAAe,KAAKhE,GAAuB,OAI3CiE,EAAgBpZ,GAAU,cAAc,eAAe,EACvDqZ,EAASrZ,GAAU,cAAc,OAAO,EAQ9C,GALA,KAAK,gBAAgB,UAAYoZ,GAAiB,KAGlD,KAAK9E,GAAoB,KAAKU,IAAgB,OAAA,EAAS,KAAM9lB,GAAMA,EAAE,QAAQ,GAAK,GAE9EkqB,GAAiBC,EAAQ,CAC3BD,EAAc,iBACZ,SACA,IAAM,CAEJ,GAAI,CAAC,KAAK,gBAAgB,SAAW,CAAC,KAAK9E,GAAmB,OAE9D,MAAMgF,EAAmBF,EAAc,UACjC7d,EAAY,KAAK,gBAAgB,UAIvC,GAAI,KAAK,MAAM,QAAU,KAAK,gBAAgB,gBAC5C8d,EAAO,MAAM,UAAY,cAAc,CAACC,CAAgB,UACnD,CAIL,MAAM5H,EAAgB,KAAK,gBAAgB,cAC3C,IAAI6H,EACAC,EAEJ,GAAI,KAAK,gBAAgB,iBAAmB9H,GAAiBA,EAAc,OAAS,EAAG,CAErF6H,EAAW3I,GAAoBc,EAAe4H,CAAgB,EAC1DC,IAAa,KAAIA,EAAW,GAChC,MAAME,EAAmBF,EAAYA,EAAW,EAEhDC,EAAiB9H,EAAc+H,CAAgB,GAAG,QAAUA,EAAmBle,CACjF,MAEEge,EAAW,KAAK,MAAMD,EAAmB/d,CAAS,EAElDie,GADyBD,EAAYA,EAAW,GACZhe,EAGtC,MAAMme,EAAiB,EAAEJ,EAAmBE,GAC5CH,EAAO,MAAM,UAAY,cAAcK,CAAc,KACvD,CAIA,KAAKrF,GAAoBiF,EACpB,KAAKlF,KACR,KAAKA,GAAa,sBAAsB,IAAM,CAC5C,KAAKA,GAAa,EACd,KAAKC,KAAsB,OAC7B,KAAKsF,IAAiB,KAAKtF,EAAiB,EAC5C,KAAKA,GAAoB,KAE7B,CAAC,EAEL,EACA,CAAE,QAAS,GAAM,OAAQ8E,CAAA,CAAa,EAKxC,MAAMnd,EAAa,KAAK4X,GAAY,cAAc,kBAAkB,EACpE,KAAKwB,GAAgBpZ,EACrB,KAAK,gBAAgB,aAAeA,EAChCA,GAAc,KAAKsY,IACrBtY,EAAW,iBACT,SACA,IAAM,CAEJ,MAAM4d,EAAc,KAAK7E,GACzB6E,EAAY,UAAYR,EAAc,UACtCQ,EAAY,WAAa5d,EAAW,WACpC4d,EAAY,aAAeR,EAAc,aACzCQ,EAAY,YAAc5d,EAAW,YACrC4d,EAAY,aAAeR,EAAc,aACzCQ,EAAY,YAAc5d,EAAW,YACrC,KAAKgZ,IAAgB,SAAS4E,CAAW,CAC3C,EACA,CAAE,QAAS,GAAM,OAAQT,CAAA,CAAa,EAQ1C,MAAM7L,EAAgB,KAAKsG,GAAY,cAAc,mBAAmB,EAElEiG,EAAqB,KAAKzE,GAC5B9H,IACFA,EAAc,iBACZ,QACCvV,GAAkB,CAEjB,MAAM+hB,EAAe/hB,EAAE,UAAY,KAAK,IAAIA,EAAE,MAAM,EAAI,KAAK,IAAIA,EAAE,MAAM,EAEzE,GAAI+hB,GAAgBD,EAAoB,CACtC,MAAMnY,EAAQ3J,EAAE,SAAWA,EAAE,OAASA,EAAE,OAClC,CAAE,WAAA6U,EAAY,YAAAC,EAAa,YAAAC,CAAA,EAAgB+M,GAC9BnY,EAAQ,GAAKkL,EAAaC,EAAcC,GAAiBpL,EAAQ,GAAKkL,EAAa,KAEpG7U,EAAE,eAAA,EACF8hB,EAAmB,YAAcnY,EAErC,SAAW,CAACoY,EAAc,CACxB,KAAM,CAAE,UAAAxN,EAAW,aAAAC,EAAc,aAAAC,CAAA,EAAiB4M,GAE/CrhB,EAAE,OAAS,GAAKuU,EAAYC,EAAeC,GAAkBzU,EAAE,OAAS,GAAKuU,EAAY,KAE1FvU,EAAE,eAAA,EACFqhB,EAAc,WAAarhB,EAAE,OAEjC,CAEF,EACA,CAAE,QAAS,GAAO,OAAQohB,CAAA,CAAa,EAMzC9L,GACEC,EACA,KAAKoH,GACL,CAAE,cAAA0E,EAAe,WAAYS,CAAA,EAC7BV,CAAA,EAGN,CAKI,KAAK,SACPhc,GAAyB,KAAoC,KAAK,QAASgc,CAAY,EAKzF,KAAKvE,IAAiB,WAAA,EAIlB,KAAK,gBAAgB,aACvB,KAAKA,GAAkB,IAAI,eAAe,IAAM,CAE9C,KAAKmF,IAAA,EAGL,KAAK5F,GAAW,aAAa7T,EAAY,eAAgB,iBAAiB,CAC5E,CAAC,EACD,KAAKsU,GAAgB,QAAQ,KAAK,gBAAgB,UAAU,GAU7D,KAAKhB,GAA4B,iBAChC,UACA,IAAM,CACJ,KAAK,QAAQ,SAAW,EAC1B,EACA,CAAE,OAAQuF,CAAA,CAAa,EAExB,KAAKvF,GAA4B,iBAChC,WACC7b,GAAM,CAGL,MAAMiiB,EAAYjiB,EAAiB,eAC/B,CAACiiB,GAAY,CAAC,KAAKpG,GAAY,SAASoG,CAAQ,IAClD,OAAO,KAAK,QAAQ,QAExB,EACA,CAAE,OAAQb,CAAA,CAAa,CAE3B,CAOAvC,GAA0B,GAC1BC,KAAgC,CAE9B,GAAI,KAAKD,GAAyB,OAElC,MAAMmC,EAAW,KAAK,SAAS,cAAc,gBAAgB,EACxDA,IAEL,KAAKnC,GAA0B,GAC/B,KAAK/B,IAAoB,WAAA,EASzB,KAAKA,GAAqB,IAAI,eAAe,IAAM,CACjD,KAAKiE,IAAA,CACP,CAAC,EACD,KAAKjE,GAAmB,QAAQkE,CAAQ,EAC1C,CAmCS,iBACP9vB,EACAgxB,EACA3e,EACM,CACN,MAAM,iBAAiBrS,EAAMgxB,EAA2B3e,CAAO,CACjE,CAiBS,oBACPrS,EACAgxB,EACA3e,EACM,CACN,MAAM,oBAAoBrS,EAAMgxB,EAA2B3e,CAAO,CACpE,CAEA0b,GAASD,EAAmB5b,EAAiB,CAC3C,KAAK,cAAc,IAAI,YAAY4b,EAAW,CAAE,OAAA5b,EAAQ,QAAS,GAAM,SAAU,EAAA,CAAM,CAAC,CAC1F,CAGAud,KAA6B,CAEd,KAAK,SAAS,iBAAiB,gBAAgB,GACtD,QAAQ,CAACxd,EAAKgf,IAAW,CAC7B,MAAMC,EAAcD,IAAW,KAAK,UACpChf,EAAI,aAAa,gBAAiB,OAAOif,CAAW,CAAC,EACrDjf,EAAI,iBAAiB,OAAO,EAAE,QAAQ,CAAC/O,EAAMiuB,IAAW,CACrDjuB,EAAqB,aAAa,gBAAiB,OAAOguB,GAAeC,IAAW,KAAK,SAAS,CAAC,CACtG,CAAC,CACH,CAAC,CACH,CAWArE,GAAa9sB,EAA2D,CACtE,KAAKirB,GAAoBjrB,CAAI,EAAI,GAG7B,MAAKgrB,KAET,KAAKA,GAAiB,GAEtB,eAAe,IAAM,KAAKoG,KAAsB,EAClD,CAMAA,KAA6B,CAC3B,GAAI,CAAC,KAAKpG,IAAkB,CAAC,KAAKD,GAAY,CAC5C,KAAKC,GAAiB,GACtB,MACF,CAEA,MAAMqG,EAAQ,KAAKpG,GAanB,GAVA,KAAKD,GAAiB,GACtB,KAAKC,GAAsB,CACzB,KAAM,GACN,QAAS,GACT,WAAY,GACZ,QAAS,EAAA,EAKPoG,EAAM,WAAY,CACpB,KAAKC,IAAA,EAEDD,EAAM,MACR,KAAKE,IAAA,EAEP,MACF,CAGIF,EAAM,SACR,KAAKG,IAAA,EAEHH,EAAM,MACR,KAAKE,IAAA,EAEHF,EAAM,SACR,KAAKI,IAAA,CAET,CAGAF,KAAyB,CACvB,KAAK,MAAQ,MAAM,QAAQ,KAAK1G,EAAK,EAAI,CAAC,GAAG,KAAKA,EAAK,EAAI,CAAA,EAE3D,KAAK6G,GAAA,EAGL,KAAKxG,GAAW,aAAa7T,EAAY,KAAM,iBAAiB,CAClE,CAMAqa,IAAyB,CACvB,KAAK/E,GAAU,MAAA,EACf,MAAMzS,EAAW,KAAK7S,GAAiB,SAEvC,KAAK,MAAM,QAAQ,CAAC4K,EAAKtI,IAAU,CACjC,MAAM4T,EAAK,KAAKoU,IAAiB1f,EAAKiI,CAAQ,EAC1CqD,IAAO,QACT,KAAKoP,GAAU,IAAIpP,EAAI,CAAE,IAAAtL,EAAK,MAAAtI,EAAO,CAGzC,CAAC,CACH,CAOAgoB,IAAiB1f,EAAQiI,EAAmD,CAC1E,GAAIA,EACF,OAAOA,EAASjI,CAAG,EAIrB,MAAM8C,EAAI9C,EACV,GAAI,OAAQ8C,GAAKA,EAAE,IAAM,KAAM,OAAO,OAAOA,EAAE,EAAE,EACjD,GAAI,QAASA,GAAKA,EAAE,KAAO,KAAM,OAAO,OAAOA,EAAE,GAAG,CAGtD,CAMA6c,IAAqB3f,EAAQiI,EAAuC,CAClE,MAAMqD,EAAK,KAAKoU,IAAiB1f,EAAKiI,CAAQ,EAC9C,GAAIqD,IAAO,OACT,MAAM,IAAI,MACR,4GAAA,EAIJ,OAAOA,CACT,CAEAiU,KAA4B,CAC1B1kB,EAAoB,IAAI,EACxB,KAAKge,GAAe,MAAA,EACpB,KAAKkD,GAAA,CACP,CAEAyD,KAA4B,CAC1B,KAAK3G,GAAe,MAAA,EACP,KAAKzjB,GAAiB,UACtB,SACX,KAAK,qBAAuB,GAC5B7E,GAAgB,IAAI,IAEpB,KAAK,SAAS,QAASf,GAAW,CAC5B,CAACA,EAAE,eAAiBA,EAAE,oBAAoBA,EAAE,KAClD,CAAC,EACD2B,EAAe,IAAI,EAEvB,CAEAkuB,KAA+B,CAI7BpU,GAAmB,KAAM,KAAKkP,EAAW,EACzChP,GAAyB,KAAM,KAAKgP,EAAW,EAE/C,MAAMyF,EAAW,CAAC,CAAC,KAAKlH,GAAY,cAAc,YAAY,EACxDmH,EAAe,CAAC,CAAC,KAAKnH,GAAY,cAAc,iBAAiB,EACjEoH,EAA0B,KAAKpH,GAAY,iBAAiB,wBAAwB,EAAE,OAE5F,KAAKG,GAAe,qBAAqB,IAA8B,EACvE,KAAKA,GAAe,MAAA,EACpB,KAAKuC,IAAA,EAGL5P,GAAwB,KAAM,KAAK2O,GAAa,KAAK2C,KAA8B,EACnF,KAAKjE,GAAe,mBAAA,EACpB,KAAKA,GAAe,MAAA,EAEpB,MAAMkH,EAAgB7V,GAAwB,KAAK9U,IAAkB,KAAK,EACpE4qB,GAAoB,KAAK5qB,IAAkB,OAAO,YAAY,QAAU,GAAK,EAC7E6qB,EAAiB,KAAK7qB,IAAkB,OAAO,YAAY,QAAU,EAQ3E,GAJEwqB,IAAaG,GACZ,CAACF,GAAgBG,GACjBH,GAAgBI,IAAmBH,EAEf,CAErBjS,GAAmB,KAAKsM,EAAW,EACnC,KAAK8C,GAAA,EACL,KAAKd,GAAA,EACL,KAAKe,IAAA,EACL,KAAKuC,GAAA,EACL,MACF,CAEIG,GACF,KAAKM,IAAA,EAGP,KAAKT,GAAA,EACL,KAAKxG,GAAW,aAAa7T,EAAY,QAAS,uBAAuB,CAC3E,CAMA8a,KAAkC,CAChC,MAAM7Q,EAAc,KAAKqJ,GAAY,cAAc,mBAAmB,EACtE,GAAI,CAACrJ,EAAa,OAElB,MAAMhF,EAAQ,KAAKjV,GAAiB,OAAO,QAAQ,OAAS,KAAK+kB,GAAY,cAG7E,IAAIvR,EAAUyG,EAAY,cAAc,kBAAkB,EACtDhF,GACGzB,IAEHA,EAAU,SAAS,cAAc,IAAI,EACrCA,EAAQ,UAAY,kBACpBA,EAAQ,aAAa,OAAQ,aAAa,EAE1CyG,EAAY,aAAazG,EAASyG,EAAY,UAAU,GAE1DzG,EAAQ,YAAcyB,GACbzB,GAETA,EAAQ,OAAA,CAEZ,CAOA0S,KAAwB,CAGtB,GAAI,KAAKxB,GAAgB,CAEvB,MAAMqG,EAAgB,KAAKxF,GAAa,OAAS,EAAI,KAAKA,GAAe,KAAK,SACxEyF,EAAcD,EAAc,OAAQ3wB,GAAM,CAACA,EAAE,MAAM,EACnD6wB,EAAaF,EAAc,OAAQ3wB,GAAMA,EAAE,MAAM,EACjD8wB,EAAmB,KAAKxG,GAAe,eAAe,CAAC,GAAGsG,CAAW,CAAC,EAG5E,GAAIE,IAAqBF,EAAa,CAEpC,MAAMG,EAAkB,IAAI,IAAID,EAAiB,IAAK9wB,GAAsBA,EAAE,KAAK,CAAC,EAMhF,CAFsB4wB,EAAY,KAAM5wB,GAAM+wB,EAAgB,IAAI/wB,EAAE,KAAK,CAAC,GAEpD8wB,EAAiB,OAAS,EAGlD,KAAK,SAAW,CAAC,GAAGA,EAAkB,GAAGD,CAAU,EAQnD,KAAK,SAAW,CAAC,GAAGC,EAAkB,GAAGD,CAAU,CAEvD,MAEE,KAAK,SAAW,CAAC,GAAGF,CAAa,CAErC,CACF,CAGA5E,KAAyB,CAEvB1gB,EAAoB,IAAI,EAGxB,MAAM2lB,EAAe,MAAM,QAAQ,KAAK5H,EAAK,EAAI,CAAC,GAAG,KAAKA,EAAK,EAAI,CAAA,EAI7D6H,EAAgB,KAAK3G,IAAgB,YAAY0G,CAAY,GAAKA,EAIxE,KAAK,MAAQC,EAGT,KAAK,gBAAgB,iBACvB,KAAK9C,GAAA,CAET,CAOA3B,IAAsB0E,EAAiC,CACrD,MAAM3zB,EAA0B,CAC9B,GAAGQ,GACH,GAAGmzB,EAAW,SAAA,EAIVjwB,EAAO1D,EAAO,MAAQ,iBAC5B,IAAI4zB,EAAiB,EAEjBlwB,IAAS,IAASA,IAAS,MAC7BkwB,EAAU,GACDlwB,IAAS,IAAQA,IAAS,QACnCkwB,EAAU,GAKZ,KAAK,MAAM,YAAY,2BAA4B,GAAG5zB,EAAO,QAAQ,IAAI,EACzE,KAAK,MAAM,YAAY,yBAA0BA,EAAO,QAAU,UAAU,EAC5E,KAAK,MAAM,YAAY,0BAA2B,OAAO4zB,CAAO,CAAC,EAGjE,KAAK,QAAQ,cAAgB,OAAOlwB,GAAS,UAAaA,EAAO,KAAO,MAASA,CACnF,CAIAmwB,GAAmB7lB,EAAeC,EAAaC,EAAQ,KAAK,iBAAwB,CAE7E,KAAKse,KACR,KAAKA,GAAiB,CAACvZ,EAAUhP,EAAoBwK,IAC5C,KAAKse,IAAgB,UAAU9Z,EAAKhP,EAAOwK,CAAQ,GAAK,IAGnEV,GAAkB,KAAoCC,EAAOC,EAAKC,EAAO,KAAKse,EAAc,CAC9F,CAGAsH,IAAwBv0B,EAAA,EAGxBw0B,IAAkBn0B,EAAkBC,EAAwB,CAC1DL,GAAiB,KAAKs0B,IAAY,KAAK,aAAc,KAAK,QAASl0B,EAAUC,CAAQ,CACvF,CAGAyuB,KAA0B,CACxBnuB,GAAiB,KAAK2zB,IAAY,KAAK,aAAc,KAAKzrB,GAAkB,KAAK+kB,EAAW,CAC9F,CAMAY,KAA8B,CAC5B,MAAMjW,EAAW,KAAK,cAAc,gBAAgB,EAC/CA,IAED,KAAKwV,IAEF,KAAKG,KACR,KAAKA,GAAoB9V,GAAqB,KAAKvP,IAAkB,eAAe,GAEtFyP,GAAmBC,EAAU,KAAK2V,EAAiB,GAEnDzV,GAAmB,KAAKyV,EAAiB,EAE7C,CAOAO,IAAuB1e,EAAe4I,EAAwB,CAE5D,MAAMzJ,EAAU,KAAKif,GAAU,IAAIpe,CAAK,EACxC,GAAI,CAACb,EAAS,OAEd,MAAMzK,EAAQ,KAAK,yBAAyByK,EAAQ,KAAK,EACpDzK,GAELiU,GAAmBjU,EAAOkU,CAAO,CACnC,CAQAgW,IAAwB5e,EAAezO,EAAeqX,EAAwB,CAE5E,MAAMzJ,EAAU,KAAKif,GAAU,IAAIpe,CAAK,EACxC,GAAI,CAACb,EAAS,OAEd,MAAMzK,EAAQ,KAAK,yBAAyByK,EAAQ,KAAK,EACzD,GAAI,CAACzK,EAAO,OAGZ,MAAMmN,EAAW,KAAK,gBAAgB,UAAW3O,GAAMA,EAAE,QAAU3B,CAAK,EACxE,GAAIsQ,EAAW,EAAG,OAElB,MAAMgB,EAASnO,EAAM,SAASmN,CAAQ,EACjCgB,GAELgG,GAAoBhG,EAAQ+F,CAAO,CACrC,CAUA6W,IAAe,CACb,GAAK,KAAK,aACN,GAAC,KAAK,cAAgB,CAAC,KAAK,SAShC,IAJA,KAAKlD,GAAe,qBAAqB,IAA8B,EAInE,KAAKpjB,GAAqB,CAC5B,MAAMjJ,EAAQ,KAAKiJ,GACnB,KAAKA,GAAsB,OAE3B,KAAKojB,GAAe,MAAA,EACpB,MAAMthB,EAAW,KAAKuiB,IAAgB,OAAA,GAAY,CAAA,EAClD,KAAKjB,GAAe,WAAWrsB,EAAO+K,CAAO,CAC/C,CAGI,KAAK,UACP,KAAK,QAAQ,MAAM,QAAU,GAC7B,KAAK,QAAQ,MAAM,oBAAsB,IAI3C,KAAK0hB,GAAW,aAAa7T,EAAY,KAAM,OAAO,EACxD,CAEAqZ,IAAiBrN,EAAyB,CAKxC,IAAIM,EAAa,EACbL,EAAe,EACfM,EAAc,EACdL,EAAe,EACfM,EAAc,EAClB,GAAI,KAAKwH,GAAmB,CAC1B,MAAM8E,EAAgB,KAAK,gBAAgB,UACrCpd,EAAa,KAAKoZ,GACxBxI,EAAa5Q,GAAY,YAAc,EACvCuQ,EAAe6M,GAAe,cAAgB,EAC9CvM,EAAc7Q,GAAY,aAAe,EACzCwQ,EAAe4M,GAAe,cAAgB,EAC9CtM,EAAc9Q,GAAY,aAAe,CAC3C,CA2BA,GAvBsB,KAAK,qBAAqB,EAAK,GAKnD,KAAKgZ,IAAgB,eAAA,EAKnB,KAAK,gBAAgB,kBACnB,KAAKR,IACP,aAAa,KAAKA,EAAqB,EAGzC,KAAKA,GAAwB,OAAO,WAAW,IAAM,CACnD,KAAKA,GAAwB,EAC7B,KAAKyH,IAA2B,KAAK,gBAAgB,MAAO,KAAK,gBAAgB,GAAG,CACtF,EAAG,GAAG,GAKJ,KAAK3H,GAAmB,CAC1B,MAAMsF,EAAc,KAAK7E,GACzB6E,EAAY,UAAYtN,EACxBsN,EAAY,WAAahN,EACzBgN,EAAY,aAAerN,EAC3BqN,EAAY,YAAc/M,EAC1B+M,EAAY,aAAepN,EAC3BoN,EAAY,YAAc9M,EAC1B,KAAKkI,IAAgB,SAAS4E,CAAW,CAC3C,CACF,CAQA,eAA6B,CAC3B,OAAO,KAAKhG,GAAY,cAAc,aAAa,CACrD,CAUA,uBAAuBld,EAAsC,CAC3D,OACG,MAAM,KAAK,KAAK,QAAQ,iBAAiB,gBAAgB,CAAC,EAAoB,KAAMsH,GAAM,CACzF,MAAM7R,EAAO6R,EAAE,cAAc,iBAAiB,EAC9C,OAAO7R,GAAQ,OAAOA,EAAK,aAAa,UAAU,CAAC,IAAMuK,CAC3D,CAAC,GAAK,IAEV,CAWA,mBAAmBsG,EAAmBtG,EAAkB2C,EAAkBgB,EAA8B,CACtG,MAAMa,EAAM,KAAK,MAAMxE,CAAQ,EACzB5K,EAAM,KAAK,SAASuN,CAAQ,EAClC,GAAI,CAAC6B,GAAO,CAACpP,EAAK,MAAO,GAEzB,MAAM/C,EAAQ+C,EAAI,MACZjC,EAASqR,EAAgCnS,CAAK,EAG9CqS,EAAgB,IAAI,YAAY,gBAAiB,CACrD,WAAY,GACZ,QAAS,GACT,SAAU,GACV,OAAQ,CACN,SAAA1E,EACA,SAAA2C,EACA,MAAAtQ,EACA,MAAAc,EACA,IAAAqR,EACA,OAAAb,EACA,QAAS,UACT,cAAe2C,CAAA,CACjB,CACD,EAID,GAHA,KAAK,cAAc5B,CAAa,EAG5BA,EAAc,iBAChB,MAAO,GAGT,MAAM8gB,EAAiC,CACrC,IAAAhhB,EACA,SAAAxE,EACA,SAAA2C,EACA,MAAAtQ,EACA,MAAAc,EACA,OAAAwQ,EACA,cAAe2C,CAAA,EAIXmf,EAAU,KAAKnH,IAAgB,YAAYkH,CAAc,GAAK,GAGpE,YAAKlF,GAAM,aAAckF,CAAc,EAEhCC,CACT,CAUA,kBAAkBnf,EAAmBtG,EAAkBwE,EAAUhP,EAA6B,CAC5F,GAAI,CAACgP,EAAK,MAAO,GAEjB,MAAMkhB,EAA+B,CACnC,SAAA1lB,EACA,IAAAwE,EACA,MAAAhP,EACA,cAAe8Q,CAAA,EAIXmf,EAAU,KAAKnH,IAAgB,WAAWoH,CAAa,GAAK,GAGlE,YAAKpF,GAAM,YAAaoF,CAAa,EAE9BD,CACT,CAMA,qBAAqBnf,EAAmB3D,EAAkByD,EAAgC,CACxF,MAAMhR,EAAM,KAAK,SAASuN,CAAQ,EAClC,GAAI,CAACvN,EAAK,MAAO,GAEjB,MAAMuwB,EAAqC,CACzC,SAAAhjB,EACA,MAAOvN,EAAI,MACX,OAAQA,EACR,SAAAgR,EACA,cAAeE,CAAA,EAGjB,OAAO,KAAKgY,IAAgB,cAAcqH,CAAgB,GAAK,EACjE,CAMA,iBAAiBrf,EAA+B,CAC9C,OAAO,KAAKgY,IAAgB,UAAUhY,CAAK,GAAK,EAClD,CAOA,4BACE9Q,EACAmnB,EACuD,CACvD,OAAO,KAAK2B,IAAgB,2BAA2B9oB,EAAOmnB,CAAW,GAAK,CAAE,KAAM,EAAG,MAAO,CAAA,CAClG,CAeA,aAAgBN,EAAyB,CACvC,OAAO,KAAKiC,IAAgB,aAAgBjC,CAAK,GAAK,CAAA,CACxD,CAoBA,MAAS9pB,EAAc4Q,EAAuB,CAC5C,OAAO,KAAKmb,IAAgB,aAAgB,CAAE,KAAA/rB,EAAM,QAAA4Q,CAAA,CAAS,GAAK,CAAA,CACpE,CAQA,uBAAuBmD,EAAgC,CACrD,OAAO,KAAKgY,IAAgB,gBAAgBhY,CAAK,GAAK,EACxD,CAOA,uBAAuBA,EAA6B,CAClD,KAAKgY,IAAgB,gBAAgBhY,CAAK,CAC5C,CAOA,qBAAqBA,EAA6B,CAChD,KAAKgY,IAAgB,cAAchY,CAAK,CAC1C,CAWA,iBAAiBnD,EAA0C,CAEzD,KAAKmb,IAAgB,gBAAgBnb,CAAiC,CACxE,CASA,yBAAmC,CACjC,OAAO,KAAKmb,IAAgB,uBAAA,GAA4B,EAC1D,CAWA,gBAAgBnb,EAAyC,CAEvD,KAAKmb,IAAgB,eAAenb,CAAgC,CACtE,CASA,wBAAkC,CAChC,OAAO,KAAKmb,IAAgB,sBAAA,GAA2B,EACzD,CAiBA,MAAM,OAAuB,CAC3B,OAAO,KAAKtU,EACd,CAkBA,MAAM,aAA6B,CAEjC,YAAKyT,GAAW,aAAa7T,EAAY,KAAM,aAAa,EAErD,KAAK6T,GAAW,UAAA,CACzB,CAgBA,MAAM,WAA8C,CAClD,OAAO,OAAO,OAAO,CAAE,GAAI,KAAK7jB,IAAoB,CAAA,EAAK,CAC3D,CAmBA,SAAS4K,EAAgB,CACvB,OAAO,KAAK2f,IAAqB3f,EAAK,KAAK5K,GAAiB,QAAQ,CACtE,CAkBA,OAAOkW,EAA2B,CAChC,OAAO,KAAKoP,GAAU,IAAIpP,CAAE,GAAG,GACjC,CAqBA,UAAUA,EAAY8V,EAAqBC,EAAuB,MAAa,CAC7E,MAAMjO,EAAQ,KAAKsH,GAAU,IAAIpP,CAAE,EACnC,GAAI,CAAC8H,EACH,MAAM,IAAI,MACR,2BAA2B9H,CAAE,0EAAA,EAIjC,KAAM,CAAE,IAAAtL,EAAK,MAAAtI,CAAA,EAAU0b,EACjBkO,EAAgF,CAAA,EAGtF,SAAW,CAACzzB,EAAOwvB,CAAQ,IAAK,OAAO,QAAQ+D,CAAO,EAAG,CACvD,MAAMxG,EAAY5a,EAAgCnS,CAAK,EACnD+sB,IAAayC,IACfiE,EAAc,KAAK,CAAE,MAAAzzB,EAAO,SAAA+sB,EAAU,SAAAyC,EAAU,EAC/Crd,EAAgCnS,CAAK,EAAIwvB,EAE9C,CAGA,SAAW,CAAE,MAAAxvB,EAAO,SAAA+sB,EAAU,SAAAyC,CAAA,IAAciE,EAC1C,KAAKxF,GAAM,cAAe,CACxB,IAAA9b,EACA,MAAOsL,EACP,SAAU5T,EACV,MAAA7J,EACA,SAAA+sB,EACA,SAAAyC,EACA,QAAA+D,EACA,OAAAC,CAAA,CACsB,EAItBC,EAAc,OAAS,GACzB,KAAKrI,GAAW,aAAa7T,EAAY,KAAM,WAAW,CAE9D,CAqBA,WAAWmc,EAAqDF,EAAuB,MAAa,CAClG,IAAIG,EAAa,GAEjB,SAAW,CAAE,GAAAlW,EAAI,QAAA8V,CAAA,IAAaG,EAAS,CACrC,MAAMnO,EAAQ,KAAKsH,GAAU,IAAIpP,CAAE,EACnC,GAAI,CAAC8H,EACH,MAAM,IAAI,MACR,2BAA2B9H,CAAE,0EAAA,EAIjC,KAAM,CAAE,IAAAtL,EAAK,MAAAtI,CAAA,EAAU0b,EAGvB,SAAW,CAACvlB,EAAOwvB,CAAQ,IAAK,OAAO,QAAQ+D,CAAO,EAAG,CACvD,MAAMxG,EAAY5a,EAAgCnS,CAAK,EACnD+sB,IAAayC,IACfmE,EAAa,GACZxhB,EAAgCnS,CAAK,EAAIwvB,EAG1C,KAAKvB,GAAM,cAAe,CACxB,IAAA9b,EACA,MAAOsL,EACP,SAAU5T,EACV,MAAA7J,EACA,SAAA+sB,EACA,SAAAyC,EACA,QAAA+D,EACA,OAAAC,CAAA,CACsB,EAE5B,CACF,CAGIG,GACF,KAAKvI,GAAW,aAAa7T,EAAY,KAAM,YAAY,CAE/D,CA6BA,WAAW5J,EAAkBzN,EAA8B,CACzD6Z,GAAW,KAAiCpM,EAAUzN,CAAI,CAC5D,CAiBA,YAAY+Z,EAAsB/Z,EAA8B,CAC9D8Z,GAAY,KAAiCC,EAAY/Z,CAAI,CAC/D,CAoBA,eAAeuO,EAAevO,EAAiC,CAC7D,OAAOia,GAAe,KAAiC1L,EAAOvO,CAAI,CACpE,CAsBA,iBAAiBF,EAAe2K,EAA2B,CACzD,MAAM0K,EAAS,KAAK2V,GAAe,iBAAiBhrB,EAAO2K,CAAO,EAClE,OAAI0K,GACF,KAAK,mBAAA,EAEAA,CACT,CAiBA,uBAAuBrV,EAAwB,CAC7C,MAAMqV,EAAS,KAAK2V,GAAe,uBAAuBhrB,CAAK,EAC/D,OAAIqV,GACF,KAAK,mBAAA,EAEAA,CACT,CAgBA,gBAAgBrV,EAAwB,CACtC,OAAO,KAAKgrB,GAAe,gBAAgBhrB,CAAK,CAClD,CAaA,gBAAuB,CACrB,KAAKgrB,GAAe,eAAA,EACpB,KAAK,mBAAA,CACP,CA2BA,eAMG,CACD,OAAO,KAAKA,GAAe,cAAA,CAC7B,CAiBA,eAAengB,EAAuB,CACpC,KAAKmgB,GAAe,eAAengB,CAAK,EACxC,KAAK,mBAAA,CACP,CAcA,gBAA2B,CACzB,OAAO,KAAKmgB,GAAe,eAAA,CAC7B,CAyBA,gBAAkC,CAChC,MAAMthB,EAAU,KAAKuiB,IAAgB,OAAA,GAAY,CAAA,EACjD,OAAO,KAAKjB,GAAe,aAAathB,CAA2B,CACrE,CAkBA,IAAI,YAAY/K,EAAoC,CAC7CA,IAGL,KAAKiJ,GAAsBjJ,EAC3B,KAAKqsB,GAAe,mBAAqBrsB,EAGrC,KAAKmsB,IACP,KAAK8I,IAAkBj1B,CAAK,EAEhC,CAOA,IAAI,aAA2C,CAC7C,OAAO,KAAK,eAAA,CACd,CAKAi1B,IAAkBj1B,EAA8B,CAC9C,MAAM+K,EAAW,KAAKuiB,IAAgB,OAAA,GAAY,CAAA,EAClD,KAAKjB,GAAe,WAAWrsB,EAAO+K,CAAO,EAG7C,KAAKwkB,GAAA,CACP,CAUA,oBAA2B,CACzB,MAAMxkB,EAAW,KAAKuiB,IAAgB,OAAA,GAAY,CAAA,EAClD,KAAKjB,GAAe,mBAAmBthB,CAAO,CAChD,CAiBA,kBAAyB,CAEvB,KAAK9B,GAAsB,OAC3B,KAAK,gBAAkB,CAAA,EAGvB,MAAM8B,EAAW,KAAKuiB,IAAgB,OAAA,GAAY,CAAA,EAClD,KAAKjB,GAAe,WAAWthB,CAAO,EAGtC,KAAKshB,GAAe,MAAA,EACpB,KAAKkD,GAAA,CACP,CAkBA,IAAI,iBAA2B,CAC7B,OAAO,KAAK3B,GAAiB,WAC/B,CAWA,IAAI,kBAA2B,CAC7B,OAAO,KAAK,gBAAgB,SAC9B,CAgBA,IAAI,2BAAsC,CACxC,OAAO,KAAKA,GAAiB,gBAC/B,CAiBA,eAAsB,CACpB,KAAKA,GAAiB,cAAA,CACxB,CAYA,gBAAuB,CACrB,KAAKA,GAAiB,eAAA,CACxB,CAcA,iBAAwB,CACtB,KAAKA,GAAiB,gBAAA,CACxB,CAeA,uBAAuBrO,EAAyB,CAC9C,KAAKqO,GAAiB,uBAAuBrO,CAAS,CACxD,CAmBA,eAAuC,CACrC,OAAO,KAAKqO,GAAiB,cAAA,CAC/B,CAgCA,kBAAkB3Q,EAAkC,CAClD,KAAK0Q,GAAY,gBAAgB,IAAI1Q,EAAM,EAAE,EAC7C,KAAK2Q,GAAiB,kBAAkB3Q,CAAK,CAC/C,CAcA,oBAAoB8D,EAAuB,CACzC,KAAK4M,GAAY,gBAAgB,OAAO5M,CAAO,EAC/C,KAAK6M,GAAiB,oBAAoB7M,CAAO,CACnD,CAmBA,mBAA+C,CAC7C,OAAO,KAAK6M,GAAiB,kBAAA,CAC/B,CA+BA,sBAAsB9iB,EAAwC,CAC5D,KAAK8iB,GAAiB,sBAAsB9iB,CAAO,CACrD,CAaA,wBAAwBmX,EAAyB,CAC/C,KAAK2L,GAAiB,wBAAwB3L,CAAS,CACzD,CAoBA,oBAAiD,CAC/C,OAAO,KAAK2L,GAAiB,mBAAA,CAC/B,CAwDA,uBAAuB9iB,EAAyC,CAC9D,KAAK8iB,GAAiB,uBAAuB9iB,CAAO,CACtD,CAcA,yBAAyBmX,EAAyB,CAChD,KAAK2L,GAAiB,yBAAyB3L,CAAS,CAC1D,CAGAiT,GAAuB,GAWvB,oBAA2B,CAErB,KAAKA,KACT,KAAKA,GAAuB,GAE5B,eAAe,IAAM,CACnB,KAAKA,GAAuB,GACvB,KAAK,cAGV,KAAK1E,GAAA,EAGL,KAAKnE,GAAe,mBAAA,EAGpB,KAAKA,GAAe,MAAA,EAGpBhL,GAAmB,KAAKsM,EAAW,EAGnC,KAAK8C,GAAA,EACL,KAAKd,GAAA,EAIL,KAAKwF,IAAA,EACP,CAAC,EACH,CAMAA,KAA2B,CAGzB,MAAM7c,EADc,KAAK4T,GAAY,cAAc,mBAAmB,GACtC,KAAKA,GAAY,cAAc,gBAAgB,EAS/E,GAPA,KAAK,aAAe5T,GAAU,cAAc,aAAa,EACzD,KAAK,gBAAgB,cAAgBA,GAAU,cAAc,sBAAsB,EACnF,KAAK,gBAAgB,WAAaA,GAAU,cAAc,gBAAgB,EAC1E,KAAK,QAAUA,GAAU,cAAc,OAAO,EAC9C,KAAK,aAAeA,GAAU,cAAc,YAAY,EAGpD,KAAKsV,GAAiB,cAAe,CACvCrN,GAAoB,KAAK2L,GAAa,KAAKyB,EAAW,EACtDtN,GAA4B,KAAK6L,GAAa,KAAKtjB,IAAkB,MAAO,KAAK+kB,EAAW,EAE5F,MAAMoD,EAAc,KAAKnoB,IAAkB,OAAO,WAAW,YACzDmoB,GAAe,KAAKpD,GAAY,WAAW,IAAIoD,CAAW,IAC5D,KAAK,cAAA,EACL,KAAKpD,GAAY,iBAAiB,IAAIoD,CAAW,EAErD,CAGA,KAAK,kBAAoBrX,GAAuB,IAAkC,EAGlF,KAAKwW,GAAsB5X,CAAQ,EAKnC,KAAKmU,GAAW,aAAa7T,EAAY,QAAS,cAAc,CAClE,CAIAgY,OAAyB,IA2BzB,eAAe9R,EAAYsW,EAAmB,CAE5C,IAAIC,EAAQ,KAAKzE,GAAmB,IAAI9R,CAAE,EACrCuW,IACHA,EAAQ,IAAI,cACZ,KAAKzE,GAAmB,IAAI9R,EAAIuW,CAAK,GAEvCA,EAAM,YAAYD,CAAG,EAGrB,KAAKE,IAAA,CACP,CAQA,iBAAiBxW,EAAkB,CAC7B,KAAK8R,GAAmB,OAAO9R,CAAE,GACnC,KAAKwW,IAAA,CAET,CAOA,qBAAgC,CAC9B,OAAO,MAAM,KAAK,KAAK1E,GAAmB,MAAM,CAClD,CAMA0E,KAAkC,CAChC,MAAMC,EAAe,MAAM,KAAK,KAAK3E,GAAmB,QAAQ,EAI1D4E,EAAiB,SAAS,mBAAmB,OAChDH,GAAU,CAAC,MAAM,KAAK,KAAKzE,GAAmB,OAAA,CAAQ,EAAE,SAASyE,CAAK,CAAA,EAGzE,SAAS,mBAAqB,CAAC,GAAGG,EAAgB,GAAGD,CAAY,CACnE,CAQA/E,IAAuB,CACrB/R,GAAmB,KAAM,KAAKkP,EAAW,EACzChP,GAAyB,KAAM,KAAKgP,EAAW,EAC/C3O,GAAwB,KAAM,KAAK2O,GAAa,KAAK2C,KAA8B,CACrF,CAMAmF,KAAmC,CACjC,MAAM5S,EAAc,KAAKqJ,GAAY,cAAc,mBAAmB,EACtE,GAAI,CAACrJ,EAAa,OAGlBxB,GAAmB,KAAKsM,EAAW,EAEnC,MAAM+H,EAAgB/X,GACpB,KAAK/U,GAAiB,MACtB,KAAK+kB,GACL,KAAK/kB,GAAiB,OAAO,SAAA,EAEzB+sB,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,UAAYD,EACjB,MAAME,EAAYD,EAAK,kBACnBC,IACF/S,EAAY,YAAY+S,CAAS,EACjC,KAAKC,IAAA,EAELxV,GAA4B,KAAK6L,GAAa,KAAKtjB,IAAkB,MAAO,KAAK+kB,EAAW,EAEhG,CAYAgD,KAA+B,CAE7B,MAAMmF,EAAoB,IAAM,CAC9B,MAAMC,EAAW,KAAKpI,GAAY,cAC5BqI,EAAiB,KAAKrI,GAAY,wBACxC,KAAK6C,GAAA,EACL,MAAM1S,EAAW,KAAK6P,GAAY,cAC5BsI,EAAiB,KAAKtI,GAAY,yBAEnC7P,GAAY,CAACiY,GAAcE,GAAkB,CAACD,KACjD,KAAK3J,GAAe,mBAAA,EACpB,KAAKA,GAAe,MAAA,EACpB,KAAKoJ,IAAA,EAET,EAGMS,EAAqB,IAAM,CAC/B,KAAK,uBAAyB,OAC9B,KAAK3G,GAAA,CACP,EAIA,KAAKlD,GAAe,wBAAwB,kBAAmByJ,CAAiB,EAChF,KAAKzJ,GAAe,wBAAwB,wBAAyByJ,CAAiB,EACtF,KAAKzJ,GAAe,wBAAwB,sBAAuByJ,CAAiB,EAGpF,KAAKzJ,GAAe,wBAAwB,kBAAmB6J,CAAkB,EACjF,KAAK7J,GAAe,wBAAwB,kBAAmB6J,CAAkB,EAGjF,KAAK7J,GAAe,gBAAgB,IAA8B,CACpE,CAQA,gBAAuB,CAErB,KAAK,uBAAyB,OAK9Bhe,EAAoB,IAAI,EAIxB,KAAKge,GAAe,qBAAqB,IAA8B,EAGvE,MAAM0J,EAAW,KAAKpI,GAAY,cAC5BqI,EAAiB,KAAKrI,GAAY,wBACxC,KAAK6C,GAAA,EACL,MAAM1S,EAAW,KAAK6P,GAAY,cAC5BsI,EAAiB,KAAKtI,GAAY,yBAIb7P,GAAY,CAACiY,GAAcE,GAAkB,CAACD,KAGvE,KAAK3J,GAAe,mBAAA,EAEpB,KAAKA,GAAe,MAAA,EACpB,KAAKoJ,IAAA,GAKP,KAAKhJ,GAAW,aAAa7T,EAAY,QAAS,gBAAgB,CACpE,CAUAyZ,KAA8B,CAC5B,MAAMX,EAAgB,KAAK,gBAAgB,UACrC3d,EAAa,KAAK,gBAAgB,YAAc2d,EAClD3d,IACF,KAAK,gBAAgB,qBAAuBA,EAAW,cAErD2d,IACF,KAAK,gBAAgB,iBAAmBA,EAAc,cAExD,MAAMyE,EAAe,KAAK,gBAAgB,aACtCA,IACF,KAAK,gBAAgB,uBAAyBA,EAAa,aAE/D,CAUAlH,GAA4BmH,EAAmBC,EAAY,GAAe,CACxE,MAAMC,EAAO,KAAK,gBAKlB,IAAIC,EACAC,EACAC,EAEJ,GAAIJ,EAAW,CACb,MAAM3E,EAAgB4E,EAAK,WAAa,KAClCviB,EAAauiB,EAAK,YAAc5E,EAChCyE,EAAeG,EAAK,aAE1BC,EAAmB7E,EAAc,aACjC8E,EAAiBziB,EAAW,aAC5B0iB,EAAmBN,EAAeA,EAAa,aAAeI,EAG9DD,EAAK,iBAAmBC,EACxBD,EAAK,qBAAuBE,EAC5BF,EAAK,uBAAyBG,CAChC,MACEF,EAAmBD,EAAK,iBACxBE,EAAiBF,EAAK,qBACtBG,EAAmBH,EAAK,wBAA0BC,EAIpD,MAAMG,EAAqBD,EAAmBD,EAIxCG,EAAoB,KAAK,IAAI,EAAGJ,EAAmBE,CAAgB,EAGzE,IAAIG,EACAC,EAAoB,EAExB,OAAIP,EAAK,iBAAmBA,EAAK,cAI/BM,EAAmB5N,GAAesN,EAAK,aAAa,GAIpDM,EAAmBR,EAAYE,EAAK,UACpCO,EAAoB,KAAKvJ,IAAgB,eAAA,GAAoB,GAGjDsJ,EAAmBF,EAAqBG,EAAoBF,CAE5E,CAOAxF,IAAiC,CAC/B,GAAI,CAAC,KAAK,gBAAgB,gBAAiB,OAE3C,MAAMpsB,EAAO,KAAK,MACZ0jB,EAAkB,KAAK,gBAAgB,WAAa,GACpDqO,EAAc,KAAKluB,GAAiB,UACpC6S,EAAW,KAAK7S,GAAiB,SACjCmuB,EAAUtb,EAAYjI,GAAWiI,EAASjI,CAAG,EAAI,OAGvD,KAAK,gBAAgB,cAAgB+U,GACnCxjB,EACA,KAAK,gBAAgB,YACrB0jB,EACA,CAAE,MAAOsO,CAAA,EACT,CAACvjB,EAAKtI,IAAU,CACd,MAAMgf,EAAe,KAAKoD,IAAgB,eAAe9Z,EAAKtI,CAAK,EACnE,GAAIgf,IAAiB,OAAW,OAAOA,EACvC,GAAI4M,EAAa,CACf,MAAMxO,EAASwO,EAAYtjB,EAAKtI,CAAK,EACrC,GAAIod,IAAW,QAAaA,EAAS,EAAG,OAAOA,CACjD,CAEF,CAAA,EAIF,MAAM0O,EAAQ1M,GACZ,KAAK,gBAAgB,cACrBvlB,EACA0jB,EACA,CAACjV,EAAKtI,IAAU,KAAKoiB,IAAgB,eAAe9Z,EAAKtI,CAAK,CAAA,EAEhE,KAAK,gBAAgB,cAAgB8rB,EAAM,cACvCA,EAAM,cAAgB,IACxB,KAAK,gBAAgB,cAAgBA,EAAM,cAE/C,CAUA,oBAAoBhoB,EAAkB8Z,EAA0B,CAG9D,GAFI,CAAC,KAAK,gBAAgB,iBACtB,CAAC,KAAK,gBAAgB,eACtB9Z,EAAW,GAAKA,GAAY,KAAK,MAAM,OAAQ,OAEnD,MAAMgb,EAAgB,KAAK,gBAAgB,cACrCxW,EAAM,KAAK,MAAMxE,CAAQ,EAG/B,IAAIsZ,EAASQ,EACTR,IAAW,SAEbA,EAAS,KAAKgF,IAAgB,eAAe9Z,EAAKxE,CAAQ,GAExDsZ,IAAW,SAEbA,EAAS,KAAK,gBAAgB,WAGhC,MAAM6B,EAAeH,EAAchb,CAAQ,EAC3C,GAAI,GAACmb,GAAgB,KAAK,IAAIA,EAAa,OAAS7B,CAAM,EAAI,KAM9DO,GAAgBmB,EAAehb,EAAUsZ,CAAM,EAG3C,KAAK,gBAAgB,eAAe,CACtC,MAAM0G,EAAiB,KAAKC,GAA4B,KAAK,MAAM,MAAM,EACzE,KAAK,gBAAgB,cAAc,MAAM,OAAS,GAAGD,CAAc,IACrE,CACF,CAQAuF,IAA2BhmB,EAAeC,EAAmB,CAG3D,GAFI,CAAC,KAAK,gBAAgB,iBACtB,CAAC,KAAK,gBAAgB,eACtB,CAAC,KAAK,QAAS,OAEnB,MAAMub,EAAc,KAAK,QAAQ,iBAAiB,gBAAgB,EAC5DtO,EAAW,KAAK7S,GAAiB,SAEjC8N,EAASoT,GACb,CACE,cAAe,KAAK,gBAAgB,cACpC,YAAa,KAAK,gBAAgB,YAClC,KAAM,KAAK,MACX,cAAe,KAAK,gBAAgB,UACpC,MAAAvb,EACA,IAAAC,EACA,gBAAiB,CAACgF,EAAKtI,IAAU,KAAKoiB,IAAgB,eAAe9Z,EAAKtI,CAAK,EAC/E,SAAUuQ,EAAYjI,GAAWiI,EAASjI,CAAG,EAAI,MAAA,EAEnDuW,CAAA,EAGF,GAAIrT,EAAO,aACT,KAAK,gBAAgB,cAAgBA,EAAO,cAC5C,KAAK,gBAAgB,cAAgBA,EAAO,cAGxC,KAAK,gBAAgB,eAAe,CACtC,MAAMsY,EAAiB,KAAKC,GAA4B,KAAK,MAAM,MAAM,EACzE,KAAK,gBAAgB,cAAc,MAAM,OAAS,GAAGD,CAAc,IACrE,CAEJ,CAUA,qBAAqBiI,EAAQ,GAAOC,EAAkB,GAAgB,CACpE,GAAI,CAAC,KAAK,QAAS,MAAO,GAE1B,MAAMd,EAAY,KAAK,MAAM,OAE7B,GAAI,CAAC,KAAK,gBAAgB,QACxB,YAAKhC,GAAmB,EAAGgC,CAAS,EAC/Bc,GACH,KAAK5J,IAAgB,YAAA,EAEhB,GAGT,GAAI,KAAK,MAAM,QAAU,KAAK,gBAAgB,gBAC5C,YAAK,gBAAgB,MAAQ,EAC7B,KAAK,gBAAgB,IAAM8I,EAGvBa,IACF,KAAK,QAAQ,MAAM,UAAY,mBAEjC,KAAK7C,GAAmB,EAAGgC,EAAWa,EAAQ,EAAE,KAAK,iBAAmB,KAAK,gBAAgB,EAGzFA,GAAS,KAAK,gBAAgB,gBAChC,KAAK,gBAAgB,cAAc,MAAM,OAAS,GAAG,KAAKhI,GAA4BmH,EAAW,EAAI,CAAC,MAGxG,KAAK9B,IAAkB8B,EAAW,KAAK,gBAAgB,MAAM,EACxDc,GACH,KAAK5J,IAAgB,YAAA,EAEhB,GAKT,MAAMoE,EAAgB,KAAK,gBAAgB,WAAa,KAClD3d,EAAa,KAAK,gBAAgB,YAAc2d,EAIhD8E,EAAiBS,EAClB,KAAK,gBAAgB,qBAAuBljB,EAAW,aACxD,KAAK,gBAAgB,uBACpB,KAAK,gBAAgB,qBAAuBA,EAAW,cACtDF,EAAY,KAAK,gBAAgB,UACjC+Q,EAAY8M,EAAc,UAEhC,IAAInjB,EACJ,MAAMyb,EAAgB,KAAK,gBAAgB,cAG3C,GAAI,KAAK,gBAAgB,iBAAmBA,GAAiBA,EAAc,OAAS,EAClFzb,EAAQ2a,GAAoBc,EAAepF,CAAS,EAChDrW,IAAU,KAAIA,EAAQ,OACrB,CAMLA,EAAQ,KAAK,MAAMqW,EAAY/Q,CAAS,EAIxC,IAAIsjB,EAAa,EACjB,MAAMC,EAAgB,GACtB,KAAOD,EAAaC,GAAe,CACjC,MAAMC,EAAoB,KAAK/J,IAAgB,uBAAuB/e,CAAK,GAAK,EAC1E4c,EAAgB,KAAK,OAAOvG,EAAYyS,GAAqBxjB,CAAS,EAC5E,GAAIsX,GAAiB5c,GAAS4c,EAAgB,EAAG,MACjD5c,EAAQ4c,EACRgM,GACF,CACF,CAMA5oB,EAAQA,EAASA,EAAQ,EACrBA,EAAQ,IAAGA,EAAQ,GAIvB,MAAM+oB,EAAsB,KAAKhK,IAAgB,mBAAmB/e,EAAOqW,EAAW/Q,CAAS,EAC3FyjB,IAAwB,QAAaA,EAAsB/oB,IAC7DA,EAAQ+oB,EAER/oB,EAAQA,EAASA,EAAQ,EACrBA,EAAQ,IAAGA,EAAQ,IAMzB,IAAIC,EAEJ,GAAI,KAAK,gBAAgB,iBAAmBwb,GAAiBA,EAAc,OAAS,EAAG,CAGrF,MAAMuN,EAAef,EAAiB3iB,EAAY,EAClD,IAAI2jB,EAAoB,EAGxB,IAFAhpB,EAAMD,EAECC,EAAM4nB,GAAaoB,EAAoBD,GAC5CC,GAAqBxN,EAAcxb,CAAG,EAAE,OACxCA,IAIF,MAAMipB,EAAU,KAAK,KAAKjB,EAAiB3iB,CAAS,EAAI,EACpDrF,EAAMD,EAAQkpB,IAChBjpB,EAAM,KAAK,IAAID,EAAQkpB,EAASrB,CAAS,EAE7C,KAAO,CAEL,MAAMsB,EAAe,KAAK,KAAKlB,EAAiB3iB,CAAS,EAAI,EAC7DrF,EAAMD,EAAQmpB,CAChB,CAEIlpB,EAAM4nB,IAAW5nB,EAAM4nB,GAK3B,MAAMuB,EAAY,KAAK,gBAAgB,MACjCC,EAAU,KAAK,gBAAgB,IACrC,GAAI,CAACX,GAAS1oB,IAAUopB,GAAanpB,IAAQopB,EAI3C,MAAO,GAGT,KAAK,gBAAgB,MAAQrpB,EAC7B,KAAK,gBAAgB,IAAMC,EAO3B,MAAM+nB,EAAmBU,EACpB,KAAK,gBAAgB,iBAAmBvF,EAAc,aACvD,KAAK,gBAAgB,mBAAqB,KAAK,gBAAgB,iBAAmBA,EAAc,cAGpG,GAAIuF,EAAO,CACT,MAAMd,EAAe,KAAK,gBAAgB,aACtCA,IACF,KAAK,gBAAgB,uBAAyBA,EAAa,aAE/D,CAKA,GAAII,IAAqB,GAAKC,EAAiB,EAG7C,YAAK/J,GAAW,aAAa7T,EAAY,eAAgB,kBAAkB,EACpE,GAKT,GAAIqe,GAAS,KAAK,gBAAgB,cAAe,CAC/C,MAAMvN,EAAc,KAAKuF,GAA4BmH,CAAS,EAC9D,KAAK,gBAAgB,cAAc,MAAM,OAAS,GAAG1M,CAAW,IAClE,CASA,IAAIoI,EACJ,GAAI,KAAK,gBAAgB,iBAAmB9H,GAAiBA,EAAczb,CAAK,EAG9EujB,EAAiB9H,EAAczb,CAAK,EAAE,WACjC,CAEL,MAAMspB,EAAyB,KAAKvK,IAAgB,uBAAuB/e,CAAK,GAAK,EACrFujB,EAAiBvjB,EAAQsF,EAAYgkB,CACvC,CAEA,MAAM7F,EAAiB,EAAEpN,EAAYkN,GACrC,YAAK,QAAQ,MAAM,UAAY,cAAcE,CAAc,MAE3D,KAAKoC,GAAmB7lB,EAAOC,EAAKyoB,EAAQ,EAAE,KAAK,iBAAmB,KAAK,gBAAgB,EAIvFA,GAAS,KAAK,gBAAgB,iBAChC,KAAK1C,IAA2BhmB,EAAOC,CAAG,EAI5C,KAAK8lB,IAAkB8B,EAAW,KAAK,gBAAgB,MAAM,EAIzDa,GAAS,CAACC,IACZ,KAAK5J,IAAgB,YAAA,EAQrB,eAAe,IAAM,CACnB,GAAI,CAAC,KAAK,gBAAgB,cAAe,OAGzC,MAAM0B,EAAiB,KAAKC,GAA4BmH,CAAS,EAG7D,KAAK,gBAAgB,mBAAqB,GAAK,KAAK,gBAAgB,qBAAuB,IAE/F,KAAK,gBAAgB,cAAc,MAAM,OAAS,GAAGpH,CAAc,KACrE,CAAC,GAGI,EACT,CAIAyB,IAAgB,CAEd,KAAKD,GAAA,EAGL,KAAKnE,GAAe,mBAAA,EAGpB,KAAKA,GAAe,MAAA,EAEpB,MAAMjK,EAAc,KAAKxZ,IAAkB,MAI1BuZ,GACf,KAAK+J,GACL9J,EACA,CAAE,YAAa,KAAKuL,GAAY,YAAa,iBAAkB,KAAKA,GAAY,gBAAA,EAChF,KAAK/kB,IAAkB,KAAA,IAIvB,KAAKitB,IAAA,EACL,KAAKjI,GAAiB,eAAe,EAAI,EAE7C,CAKAiI,KAA6B,CAC3BvW,GAAyB,KAAK4M,GAAa,KAAKtjB,IAAkB,MAAO,KAAK+kB,GAAa,CACzF,cAAe,IAAM,KAAK,gBAAA,EAC1B,gBAAkBpO,GAAsB,KAAK,uBAAuBA,CAAS,CAAA,CAC9E,EAGD,KAAKsO,KAAA,EACL,KAAKA,GAAiBrO,GAAqB,KAAK0M,GAAa,KAAKtjB,IAAkB,MAAQqR,GAAkB,CAE5G,KAAK,MAAM,YAAY,yBAA0B,GAAGA,CAAK,IAAI,CAC/D,CAAC,CACH,CAEF,CAGK,eAAe,IAAI+R,EAAgB,OAAO,GAC7C,eAAe,OAAOA,EAAgB,QAASA,CAAe,EAI/D,WAAsE,gBAAkBA,ECv9HlF,MAAM8L,GAAiB,CAE5B,gBAAiB,gBAEjB,uBAAwB,qBAC1B,ECyIO,MAAeC,EAAwD,CAgB5E,OAAgB,aAuBhB,OAAgB,SASP,QAAkB,OAAO,iBAAqB,IAAc,iBAAmB,MAG/E,OAGA,cAGA,gBAGA,YAGC,KAGA,OAGS,WAOnBC,GAOA,IAAc,eAAkC,CAC9C,MAAO,CAAA,CACT,CAEA,YAAYz3B,EAA2B,GAAI,CACzC,KAAK,WAAaA,CACpB,CAiBA,OAAOyD,EAAyB,CAE9B,KAAKg0B,IAAkB,MAAA,EAEvB,KAAKA,GAAmB,IAAI,gBAE5B,KAAK,KAAOh0B,EAEZ,KAAK,OAAS,CAAE,GAAG,KAAK,cAAe,GAAG,KAAK,UAAA,CACjD,CAeA,QAAe,CAGb,KAAKg0B,IAAkB,MAAA,EACvB,KAAKA,GAAmB,MAE1B,CAkDU,UAAoCpN,EAAuD,CACnG,OAAO,KAAK,MAAM,UAAUA,CAAW,CACzC,CAKU,KAAQyE,EAAmB5b,EAAiB,CACpD,KAAK,MAAM,gBAAgB,IAAI,YAAY4b,EAAW,CAAE,OAAA5b,EAAQ,QAAS,EAAA,CAAM,CAAC,CAClF,CAMU,eAAkB4b,EAAmB5b,EAAoB,CACjE,MAAM6B,EAAQ,IAAI,YAAY+Z,EAAW,CAAE,OAAA5b,EAAQ,QAAS,GAAM,WAAY,GAAM,EACpF,YAAK,MAAM,gBAAgB6B,CAAK,EACzBA,EAAM,gBACf,CAsBU,GAAgBkW,EAAmBlf,EAAqC,CAChF,KAAK,MAAM,gBAAgB,UAAU,KAAMkf,EAAWlf,CAAqC,CAC7F,CAaU,IAAIkf,EAAyB,CACrC,KAAK,MAAM,gBAAgB,YAAY,KAAMA,CAAS,CACxD,CAoBU,gBAAmBA,EAAmB/X,EAAiB,CAC/D,KAAK,MAAM,gBAAgB,gBAAgB+X,EAAW/X,CAAM,CAC9D,CAMU,eAAsB,CAC9B,KAAK,MAAM,gBAAA,CACb,CAOU,sBAA6B,CACpC,KAAK,MAAgD,uBAAA,CACxD,CAOU,wBAA+B,CACvC,KAAK,MAAM,yBAAA,CACb,CAMU,oBAA2B,CACnC,KAAK,MAAM,qBAAA,CACb,CAKA,IAAc,MAAc,CAC1B,OAAO,KAAK,MAAM,MAAQ,CAAA,CAC5B,CAMA,IAAc,YAAoB,CAChC,OAAO,KAAK,MAAM,YAAc,CAAA,CAClC,CAKA,IAAc,SAA0B,CACtC,OAAO,KAAK,MAAM,SAAW,CAAA,CAC/B,CAMA,IAAc,gBAAiC,CAC7C,OAAO,KAAK,MAAM,iBAAmB,CAAA,CACvC,CAYA,IAAc,aAA2B,CACvC,OAAO,KAAK,IACd,CAmBA,IAAc,kBAAgC,CAG5C,OAAO,KAAKukB,IAAkB,QAAU,KAAK,MAAM,gBACrD,CAMA,IAAc,WAAuC,CACnD,MAAMC,EAAY,KAAK,MAAM,YAAY,OAAS,CAAA,EAClD,MAAO,CAAE,GAAGh3B,EAAoB,GAAGg3B,CAAA,CACrC,CAoBA,IAAc,oBAA8B,CAC1C,MAAMh0B,EAAO,KAAK,MAAM,iBAAiB,WAAW,MAAQ,iBAG5D,GAAIA,IAAS,IAASA,IAAS,MAAO,MAAO,GAG7C,GAAIA,IAAS,IAAQA,IAAS,KAAM,MAAO,GAG3C,MAAM9C,EAAO,KAAK,YAClB,OAAIA,EACc,iBAAiBA,CAAI,EAAE,iBAAiB,yBAAyB,EAAE,KAAA,IAChE,IAGd,EACT,CAcA,IAAc,mBAA4B,CACxC,MAAMA,EAAO,KAAK,YAClB,GAAIA,EAAM,CACR,MAAM+2B,EAAc,iBAAiB/2B,CAAI,EAAE,iBAAiB,0BAA0B,EAAE,KAAA,EAClF6Z,EAAS,SAASkd,EAAa,EAAE,EACvC,GAAI,CAAC,MAAMld,CAAM,EAAG,OAAOA,CAC7B,CACA,MAAO,IACT,CAYU,YAAYmd,EAA0CC,EAAuC,CAErG,OAAIA,IAAmB,OACdA,EAGF,KAAK,UAAUD,CAAO,CAC/B,CASU,QAAQ5qB,EAAsBuJ,EAAuB,CACzD,OAAOA,GAAS,SAClBvJ,EAAQ,UAAYuJ,EACXA,aAAgB,cACzBvJ,EAAQ,UAAY,GACpBA,EAAQ,YAAYuJ,EAAK,UAAU,EAAI,CAAC,EAE5C,CAKU,KAAKuhB,EAAuB,CACpC,QAAQ,KAAK,aAAa,KAAK,IAAI,KAAKA,CAAO,EAAE,CACnD,CAgqBF,CC19CO,MAAMC,EAAc,CAEzB,KAAM,gBACN,OAAQ,SACR,WAAY,aACZ,YAAa,cAGb,cAAe,gBACf,YAAa,cACb,eAAgB,OAGhB,SAAU,WACV,UAAW,YAGX,UAAW,YAGX,SAAU,WACV,QAAS,UACT,QAAS,UACT,SAAU,WACV,UAAW,YACX,SAAU,WACV,SAAU,WAGV,SAAU,WACV,WAAY,aACZ,YAAa,cAGb,OAAQ,SAOR,YAAa,cACb,aAAc,eAGd,WAAY,aACZ,cAAe,gBAGf,YAAa,cACb,YAAa,cAGb,aAAc,eACd,YAAa,cACb,YAAa,cAGb,gBAAiB,kBACjB,kBAAmB,mBACrB,EAYaC,EAAgB,CAE3B,UAAW,iBACX,UAAW,iBACX,MAAO,aAGP,UAAW,iBACX,WAAY,kBACZ,OAAQ,aACV,EAYaC,GAAgB,CAC3B,KAAM,IAAIF,EAAY,IAAI,GAC1B,OAAQ,IAAIA,EAAY,MAAM,GAC9B,WAAY,IAAIA,EAAY,UAAU,GACtC,YAAa,IAAIA,EAAY,WAAW,GACxC,cAAe,IAAIA,EAAY,aAAa,GAC5C,eAAgB,IAAIA,EAAY,cAAc,GAC9C,SAAU,IAAIA,EAAY,QAAQ,GAClC,UAAW,IAAIA,EAAY,SAAS,GACpC,UAAW,IAAIA,EAAY,SAAS,GAGpC,aAAeptB,GAAkB,IAAIotB,EAAY,QAAQ,IAAIC,EAAc,SAAS,KAAKrtB,CAAK,KAC9F,cAAgB7J,GAAkB,IAAIi3B,EAAY,SAAS,IAAIC,EAAc,KAAK,KAAKl3B,CAAK,KAC5F,QAAS,CAACmS,EAAapP,IACrB,IAAIk0B,EAAY,QAAQ,IAAIC,EAAc,SAAS,KAAK/kB,CAAG,OAAO8kB,EAAY,SAAS,IAAIC,EAAc,SAAS,KAAKn0B,CAAG,KAG5H,cAAe,IAAIk0B,EAAY,QAAQ,IAAIA,EAAY,QAAQ,GAC/D,aAAc,IAAIA,EAAY,SAAS,IAAIA,EAAY,OAAO,EAChE,EAYaG,GAAc,CAEzB,SAAU,iBACV,SAAU,iBACV,eAAgB,uBAChB,aAAc,qBACd,aAAc,qBACd,gBAAiB,wBACjB,gBAAiB,wBACjB,gBAAiB,wBACjB,gBAAiB,wBACjB,cAAe,sBAGf,WAAY,mBACZ,cAAe,sBACf,aAAc,qBAGd,YAAa,oBACb,UAAW,kBAGX,cAAe,sBACf,cAAe,qBACjB,EC/HO,SAASC,GAA2Bn4B,EAA2D,CACpG,MAAMyD,EAAO,SAAS,cAAc,UAAU,EAC9C,OAAIzD,IACFyD,EAAK,WAAazD,GAEbyD,CACT,CAqBO,SAAS20B,GACdlW,EACAtV,EAAqB,SACS,CAC9B,OAAOA,EAAO,cAAcsV,CAAQ,CACtC,CAgCO,MAAMmW,GAAW,CAEtB,YAAa,cACb,YAAa,cACb,WAAY,aACZ,UAAW,YACX,WAAY,aACZ,mBAAoB,qBACpB,oBAAqB,sBACrB,sBAAuB,wBACvB,YAAa,cACb,cAAe,gBACf,cAAe,gBAEf,cAAe,gBACf,aAAc,eACd,oBAAqB,qBACvB,EAgDaC,GAAe,CAE1B,iBAAkB,mBAElB,YAAa,cAEb,cAAe,gBAEf,kBAAmB,oBAEnB,aAAc,eACd,gBAAiB,kBAEjB,eAAgB,iBAChB,gBAAiB,kBAEjB,kBAAmB,oBACnB,mBAAoB,qBAEpB,eAAgB,iBAEhB,eAAgB,iBAChB,aAAc,eAEd,yBAA0B,2BAE1B,eAAgB,iBAEhB,cAAe,gBAEf,aAAc,cAChB,EC/LMC,GAAmD,CACvD,IAAK,CAAC/zB,EAAM1D,IAAU0D,EAAK,OAAO,CAACg0B,EAAKvlB,IAAQulB,GAAO,OAAOvlB,EAAInS,CAAK,CAAC,GAAK,GAAI,CAAC,EAClF,IAAK,CAAC0D,EAAM1D,IAAU,CACpB,MAAM23B,EAAMj0B,EAAK,OAAO,CAACg0B,EAAKvlB,IAAQulB,GAAO,OAAOvlB,EAAInS,CAAK,CAAC,GAAK,GAAI,CAAC,EACxE,OAAO0D,EAAK,OAASi0B,EAAMj0B,EAAK,OAAS,CAC3C,EACA,MAAQA,GAASA,EAAK,OACtB,IAAK,CAACA,EAAM1D,IAAW0D,EAAK,OAAS,KAAK,IAAI,GAAGA,EAAK,IAAKuR,GAAM,OAAOA,EAAEjV,CAAK,CAAC,GAAK,GAAQ,CAAC,EAAI,EAClG,IAAK,CAAC0D,EAAM1D,IAAW0D,EAAK,OAAS,KAAK,IAAI,GAAGA,EAAK,IAAKuR,GAAM,OAAOA,EAAEjV,CAAK,CAAC,GAAK,IAAS,CAAC,EAAI,EACnG,MAAO,CAAC0D,EAAM1D,IAAU0D,EAAK,CAAC,IAAI1D,CAAK,EACvC,KAAM,CAAC0D,EAAM1D,IAAU0D,EAAKA,EAAK,OAAS,CAAC,IAAI1D,CAAK,CACtD,EAGM43B,MAAmD,IAM5CC,EAAqB,CAIhC,SAASvyB,EAAcwB,EAAwB,CAC7C8wB,EAAkB,IAAItyB,EAAMwB,CAAE,CAChC,EAKA,WAAWxB,EAAoB,CAC7BsyB,EAAkB,OAAOtyB,CAAI,CAC/B,EAKA,IAAIwyB,EAA0D,CAC5D,GAAIA,IAAQ,OACZ,OAAI,OAAOA,GAAQ,WAAmBA,EAE/BF,EAAkB,IAAIE,CAAG,GAAKL,GAAmBK,CAAG,CAC7D,EAKA,IAAIA,EAAgCp0B,EAAa1D,EAAekS,EAAmB,CACjF,MAAMpL,EAAK,KAAK,IAAIgxB,CAAG,EACvB,OAAOhxB,EAAKA,EAAGpD,EAAM1D,EAAOkS,CAAM,EAAI,MACxC,EAKA,IAAI5M,EAAuB,CACzB,OAAOsyB,EAAkB,IAAItyB,CAAI,GAAKA,KAAQmyB,EAChD,EAKA,MAAiB,CACf,MAAO,CAAC,GAAG,OAAO,KAAKA,EAAkB,EAAG,GAAGG,EAAkB,MAAM,CACzE,CACF,EAWMG,GAA6D,CACjE,IAAMC,GAASA,EAAK,OAAO,CAAC12B,EAAG2H,IAAM3H,EAAI2H,EAAG,CAAC,EAC7C,IAAM+uB,GAAUA,EAAK,OAASA,EAAK,OAAO,CAAC12B,EAAG2H,IAAM3H,EAAI2H,EAAG,CAAC,EAAI+uB,EAAK,OAAS,EAC9E,MAAQA,GAASA,EAAK,OACtB,IAAMA,GAAUA,EAAK,OAAS,KAAK,IAAI,GAAGA,CAAI,EAAI,EAClD,IAAMA,GAAUA,EAAK,OAAS,KAAK,IAAI,GAAGA,CAAI,EAAI,EAClD,MAAQA,GAASA,EAAK,CAAC,GAAK,EAC5B,KAAOA,GAASA,EAAKA,EAAK,OAAS,CAAC,GAAK,CAC3C,EASO,SAASC,GAAmBC,EAAoC,CACrE,OAAOH,GAAwBG,CAAO,GAAKH,GAAwB,GACrE,CASO,SAASI,GAAmBD,EAAiBE,EAA0B,CAC5E,OAAOH,GAAmBC,CAAO,EAAEE,CAAM,CAC3C,CAIO,MAAMC,GAAqBR,EAAmB,SAAS,KAAKA,CAAkB,EACxES,GAAuBT,EAAmB,WAAW,KAAKA,CAAkB,EAC5EU,GAAgBV,EAAmB,IAAI,KAAKA,CAAkB,EAC9DW,GAAgBX,EAAmB,IAAI,KAAKA,CAAkB,EAC9DY,GAAkBZ,EAAmB,KAAK,KAAKA,CAAkB"}
|
|
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/columns.ts","../../../../libs/grid/src/lib/core/internal/inference.ts","../../../../libs/grid/src/lib/core/internal/sanitize.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/sorting.ts","../../../../libs/grid/src/lib/core/internal/header.ts","../../../../libs/grid/src/lib/core/internal/idle-scheduler.ts","../../../../libs/grid/src/lib/core/internal/loading.ts","../../../../libs/grid/src/lib/core/internal/render-scheduler.ts","../../../../libs/grid/src/lib/core/internal/resize.ts","../../../../libs/grid/src/lib/core/internal/row-animation.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/plugin/plugin-manager.ts","../../../../libs/grid/src/lib/core/styles/index.ts","../../../../libs/grid/src/lib/core/grid.ts","../../../../libs/grid/src/lib/core/plugin/types.ts","../../../../libs/grid/src/lib/core/plugin/base-plugin.ts","../../../../libs/grid/src/lib/core/constants.ts","../../../../libs/grid/src/public.ts","../../../../libs/grid/src/lib/core/internal/aggregators.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","import 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\n * const grid = document.querySelector('tbw-grid') as DataGridElement<Employee>;\n * grid.rows = employees;\n * grid.addEventListener('cell-click', (e) => console.log(e.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 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 /** 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 * @example\n * ```typescript\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 * Prefer `getPlugin(PluginClass)` for type safety.\n */\n getPluginByName?(name: string): 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// #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 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 /** 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 effectiveConfig?: GridConfig<T>;\n findHeaderRow?: () => HTMLElement;\n refreshVirtualWindow: (full: boolean, skipAfterRender?: boolean) => boolean;\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 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. Implemented in grid.ts */\n animateRow?: (rowIndex: number, type: RowAnimationType) => void;\n /** Animate multiple rows. Implemented in grid.ts */\n animateRows?: (rowIndices: number[], type: RowAnimationType) => void;\n /** Animate a row by its ID. Implemented in grid.ts */\n animateRowById?: (rowId: string, type: RowAnimationType) => 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, colIndex: number, 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// #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 /** Optional formatter */\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 */\n cellClass?: (value: unknown, row: TRow, column: ColumnConfig<TRow>) => 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.addEventListener('cell-commit', (e) => {\n * if (e.detail.field === 'quantity') {\n * e.detail.updateRow({ total: e.detail.row.price * e.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 */\n createRenderer<TRow = unknown, TValue = unknown>(element: HTMLElement): ColumnViewRenderer<TRow, TValue>;\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// #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 * groupBy: ['department', '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\n * const selection = grid.getPlugin(SelectionPlugin);\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// #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 */\n rowClass?: (row: TRow) => 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 * 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 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.addEventListener('cell-change', (e) => {\n * const { source, field, newValue } = e.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(e.detail.rowId, {\n * total: newValue * e.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.addEventListener('cell-change', (e: CustomEvent<CellChangeDetail>) => {\n * const { row, rowId, field, oldValue, newValue, source } = e.detail;\n *\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\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 update = () => span.textContent = `${grid.rows.length} rows`;\n * grid.addEventListener('data-change', update);\n *\n * return () => {\n * grid.removeEventListener('data-change', update);\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.addEventListener('cell-click', (e: CustomEvent<CellClickDetail>) => {\n * const { row, field, value, rowIndex, colIndex } = e.detail;\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 /** 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.addEventListener('row-click', (e: CustomEvent<RowClickDetail>) => {\n * const { row, rowIndex, rowEl } = e.detail;\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.addEventListener('sort-change', (e: CustomEvent<SortChangeDetail>) => {\n * const { field, direction } = e.detail;\n *\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.addEventListener('column-resize', (e: CustomEvent<ColumnResizeDetail>) => {\n * const { field, width } = e.detail;\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.addEventListener('cell-activate', (e: CustomEvent<CellActivateDetail>) => {\n * const { row, field, value, trigger, cellEl } = e.detail;\n *\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 * openNotesEditor(row, cellEl);\n * e.preventDefault(); // Prevent default editing\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.\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.addEventListener('mount-external-view', (e: CustomEvent<ExternalMountViewDetail>) => {\n * const { placeholder, spec, context } = e.detail;\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.addEventListener('mount-external-editor', (e: CustomEvent<ExternalMountEditorDetail>) => {\n * const { placeholder, spec, context } = e.detail;\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 * Use this interface for strongly typed event handling.\n *\n * @example\n * ```typescript\n * // Type-safe event listener\n * function handleEvent<K extends keyof DataGridEventMap>(\n * grid: DataGridElement,\n * event: K,\n * handler: (detail: DataGridEventMap[K]) => void,\n * ): void {\n * grid.addEventListener(event, (e: CustomEvent) => handler(e.detail));\n * }\n *\n * handleEvent(grid, 'cell-click', (detail) => {\n * console.log(detail.field); // Type-safe access\n * });\n * ```\n *\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 'cell-click': CellClickDetail<TRow>;\n 'row-click': RowClickDetail<TRow>;\n 'cell-activate': CellActivateDetail<TRow>;\n 'cell-change': CellChangeDetail<TRow>;\n 'mount-external-view': ExternalMountViewDetail<TRow>;\n 'mount-external-editor': ExternalMountEditorDetail<TRow>;\n 'sort-change': SortChangeDetail;\n 'column-resize': ColumnResizeDetail;\n /** @deprecated Use 'cell-activate' instead */\n 'activate-cell': ActivateCellDetail;\n 'column-state-change': GridColumnState;\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 * Use this type when you need to cast or declare event handler parameters.\n *\n * @example\n * ```typescript\n * // Strongly typed event handler\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 *\n * grid.addEventListener('cell-click', onCellClick);\n * ```\n *\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","import type { ColumnConfig, ColumnInternal, ElementWithPart, InternalGrid, PrimitiveColumnType } from '../types';\nimport { FitModeEnum } from '../types';\n\n// #region Light DOM Parsing\n/** Global DataGridElement class (may or may not be registered) */\ninterface DataGridElementClass {\n getAdapters?: () => Array<{\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: InternalGrid): 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 as unknown as HTMLElement).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 */\nexport function updateTemplate(grid: InternalGrid): 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) return `${c.width}px`;\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) => (c.width ? `${c.width}px` : 'max-content'))\n .join(' ');\n }\n (grid as unknown as HTMLElement).style.setProperty('--tbw-column-template', grid._gridTemplate);\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","// 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 * 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 HeaderContentDefinition,\n ToolbarContentDefinition,\n ToolPanelDefinition,\n} from '../types';\nimport { mergeColumns, parseLightDomColumns } from './columns';\nimport { inferColumns } from './inference';\nimport { compileTemplate } from './sanitize';\n\n/** Debounce timeout for state change events */\nconst STATE_CHANGE_DEBOUNCE_MS = 100;\n\n/**\n * Callbacks for ConfigManager to notify the grid of changes.\n */\nexport interface ConfigManagerCallbacks<T> {\n /** Get current rows for column inference */\n getRows: () => T[];\n /** Get current sort state */\n getSortState: () => { field: string; direction: 1 | -1 } | null;\n /** Set sort state */\n setSortState: (state: { field: string; direction: 1 | -1 } | null) => void;\n /** Notify that config changed and re-render is needed */\n onConfigChange: () => void;\n /** Emit a custom event */\n emit: (eventName: string, detail: unknown) => void;\n /** Clear row pool for visibility changes */\n clearRowPool: () => void;\n /** Run setup after state changes */\n setup: () => void;\n /** Render header */\n renderHeader: () => void;\n /** Update column template */\n updateTemplate: () => void;\n /** Refresh virtual window */\n refreshVirtualWindow: () => void;\n /** Get virtualization state for row height application */\n getVirtualization: () => { rowHeight: number };\n /** Set row height on virtualization state */\n setRowHeight: (height: number) => void;\n /** Apply animation config to host element */\n applyAnimationConfig: (config: GridConfig<T>) => void;\n /** Get light DOM title from shell state (synced from parseLightDomShell) */\n getShellLightDomTitle: () => string | null;\n /** Get registered tool panels from shell state */\n getShellToolPanels: () => Map<string, ToolPanelDefinition>;\n /** Get registered header contents from shell state */\n getShellHeaderContents: () => Map<string, HeaderContentDefinition>;\n /** Get registered toolbar contents from shell state */\n getShellToolbarContents: () => Map<string, ToolbarContentDefinition>;\n /** Get light DOM header content elements */\n getShellLightDomHeaderContent: () => HTMLElement[];\n /** Get whether a tool buttons container was found in light DOM */\n getShellHasToolButtonsContainer: () => boolean;\n}\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 #initialColumnState?: GridColumnState;\n #callbacks: ConfigManagerCallbacks<T>;\n\n // Shell state (Light DOM title)\n #lightDomTitle?: string;\n\n constructor(callbacks: ConfigManagerCallbacks<T>) {\n this.#callbacks = callbacks;\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.#callbacks.setRowHeight(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.#callbacks.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.#callbacks.getRows();\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.#callbacks.getShellLightDomTitle();\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.#callbacks.getShellLightDomHeaderContent();\n if (lightDomHeaderContent?.length > 0) {\n base.shell.header.lightDomContent = lightDomHeaderContent;\n }\n\n // Sync hasToolButtonsContainer from shell state\n if (this.#callbacks.getShellHasToolButtonsContainer()) {\n base.shell.header.hasToolButtonsContainer = true;\n }\n\n // Sync tool panels (from plugins + API + Light DOM)\n const toolPanelsMap = this.#callbacks.getShellToolPanels();\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.#callbacks.getShellHeaderContents();\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.#callbacks.getShellToolbarContents();\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.#callbacks.setSortState({\n field: primarySort.field,\n direction: primarySort.sort.direction === 'asc' ? 1 : -1,\n });\n }\n } else {\n this.#callbacks.setSortState(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.#callbacks.setSortState(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.#callbacks.getSortState();\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.#callbacks.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.#callbacks.emit('column-visibility', {\n field,\n visible,\n visibleColumns: allCols.filter((c) => !c.hidden).map((c) => c.field),\n });\n\n this.#callbacks.clearRowPool();\n this.#callbacks.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.#callbacks.emit('column-visibility', {\n visibleColumns: allCols.map((c) => c.field),\n });\n\n this.#callbacks.clearRowPool();\n this.#callbacks.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 this.#callbacks.renderHeader();\n this.#callbacks.updateTemplate();\n this.#callbacks.refreshVirtualWindow();\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 let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n const processPendingCallbacks = () => {\n debounceTimer = null;\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 && !debounceTimer) {\n debounceTimer = 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 }\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 (shadowRoot or bodyEl).\n * Used when changing focus or when selection plugin takes over focus management.\n */\nexport function clearCellFocus(root: Element | ShadowRoot | 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, InternalGrid, RowElementInternal } from '../types';\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: InternalGrid,\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 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\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 const isActivelyEditedRow = 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 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 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 // 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 changedRowIds = grid.changedRowIds;\n if (changedRowIds && changedRowIds.length > 0) {\n try {\n const rowId = grid.getRowId?.(rowData);\n if (rowId) {\n isChanged = changedRowIds.includes(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 newClasses = rowClassFn(rowData);\n if (newClasses && newClasses.length > 0) {\n const validClasses = newClasses.filter((c) => c && typeof c === 'string');\n validClasses.forEach((cls) => rowEl.classList.add(cls));\n rowEl.setAttribute('data-dynamic-classes', validClasses.join(' '));\n } else {\n rowEl.removeAttribute('data-dynamic-classes');\n }\n } catch (e) {\n console.warn(`[tbw-grid] rowClass callback error:`, e);\n rowEl.removeAttribute('data-dynamic-classes');\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: InternalGrid, 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.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 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 // 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 cell.setAttribute('aria-selected', String(shouldHaveFocus));\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 cellClasses = cellClassFn(value, rowData, col);\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 console.warn(`[tbw-grid] cellClass callback error for column '${col.field}':`, e);\n cell.removeAttribute('data-dynamic-classes');\n }\n }\n\n // Skip cells in edit mode\n if (cell.classList.contains('editing')) 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 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 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 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 // Skip templated / external cells (these need full rebuild to remount)\n if (col.__viewTemplate || col.__compiledView || 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 console.warn(`[tbw-grid] Format error in column '${col.field}':`, e);\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: InternalGrid, 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 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 const gridEl = grid as unknown as HTMLElement;\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 console.warn(`[tbw-grid] Format error in column '${col.field}':`, e);\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 console.warn(`[tbw-grid] External view mount error for column '${col.field}':`, e);\n }\n } else {\n queueMicrotask(() => {\n try {\n gridEl.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 console.warn(`[tbw-grid] External view event dispatch error for column '${col.field}':`, e);\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 if (col.editable) {\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 cellClasses = cellClassFn(cellValue, rowData, col);\n if (cellClasses && cellClasses.length > 0) {\n const validClasses = cellClasses.filter((c) => c && typeof c === 'string');\n validClasses.forEach((cls) => cell.classList.add(cls));\n cell.setAttribute('data-dynamic-classes', validClasses.join(' '));\n }\n } catch (e) {\n console.warn(`[tbw-grid] cellClass callback error for column '${col.field}':`, e);\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: InternalGrid, 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 // Focus the editor in the cell\n const editor = 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 { InternalGrid } from '../types';\nimport { FOCUSABLE_EDITOR_SELECTOR } from './rows';\nimport { clearCellFocus, isRTL } from './utils';\n\n// #region Keyboard Handler\nexport function handleGridKeyDown(grid: InternalGrid, 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 as unknown as HTMLElement);\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 as unknown as HTMLElement);\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 as unknown as HTMLElement).querySelector(\n `[data-row=\"${rowIndex}\"][data-col=\"${colIndex}\"]`,\n ) as HTMLElement | undefined;\n\n const detail = {\n rowIndex,\n colIndex,\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 as unknown as HTMLElement).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 as unknown as HTMLElement).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: InternalGrid, 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;\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 (grid._activeEditRows !== undefined && grid._activeEditRows !== -1 && cell.classList.contains('editing')) {\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 (!cell.contains(document.activeElement)) {\n if (!cell.hasAttribute('tabindex')) cell.setAttribute('tabindex', '-1');\n try {\n cell.focus({ preventScroll: true });\n } catch {\n /* empty */\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 { 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// #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 column = grid._columns[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: InternalGrid, 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 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 { 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: InternalGrid,\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 * Sorting Module\n *\n * Handles column sorting state transitions and row ordering.\n */\n\nimport type { ColumnConfig, InternalGrid, SortHandler, SortState } from '../types';\nimport { renderHeader } from './header';\n\n/**\n * Default comparator for sorting values.\n * Handles nulls (pushed to end), numbers, and string fallback.\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 * Built-in sort implementation using column comparator or default.\n * This is the default sortHandler when none is configured.\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: InternalGrid<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 as unknown as HTMLElement).dispatchEvent(\n new CustomEvent('sort-change', { detail: { field: col.field, direction: dir } }),\n );\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: InternalGrid, 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 as unknown as HTMLElement).dispatchEvent(\n new CustomEvent('sort-change', { detail: { field: col.field, direction: 0 } }),\n );\n // Trigger state change after sort is cleared\n grid.requestStateChange?.();\n }\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: InternalGrid, 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, 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: InternalGrid, 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, colIndex, 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 as unknown as MouseEvent, colIndex, 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: InternalGrid): 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\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","/**\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) as unknown as number;\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","/**\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 * const scheduler = new RenderScheduler({\n * mergeConfig: () => this.#mergeEffectiveConfig(),\n * processColumns: () => this.#processColumns(),\n * processRows: () => this.#rebuildRowModel(),\n * renderHeader: () => renderHeader(this),\n * updateTemplate: () => updateTemplate(this),\n * renderVirtualWindow: () => this.refreshVirtualWindow(true),\n * afterRender: () => this.#pluginManager?.afterRender(),\n * isConnected: () => this.isConnected && this.#connected,\n * });\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\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/**\n * Callbacks that the scheduler invokes during flush.\n * Each callback corresponds to work done in a specific phase.\n */\nexport interface RenderCallbacks {\n /** Merge effective config (FULL phase) */\n mergeConfig: () => void;\n /** Process columns through plugins (COLUMNS phase) */\n processColumns: () => void;\n /** Rebuild row model through plugins (ROWS phase) */\n processRows: () => void;\n /** Render header DOM (HEADER phase) */\n renderHeader: () => void;\n /** Update CSS grid template (ROWS phase) */\n updateTemplate: () => void;\n /** Recalculate virtual window (VIRTUALIZATION phase) */\n renderVirtualWindow: () => void;\n /** Run plugin afterRender hooks (STYLE phase) */\n afterRender: () => void;\n /** Check if grid is still connected to DOM */\n isConnected: () => boolean;\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 #callbacks: RenderCallbacks;\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(callbacks: RenderCallbacks) {\n this.#callbacks = callbacks;\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.#callbacks.isConnected()) {\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.#callbacks.mergeConfig();\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.#callbacks.processRows();\n }\n\n // Phase 5 (COLUMNS): Process columns + update template\n if (phase >= RenderPhase.COLUMNS) {\n this.#callbacks.processColumns();\n this.#callbacks.updateTemplate();\n }\n\n // Phase 3 (HEADER): Render header\n if (phase >= RenderPhase.HEADER) {\n this.#callbacks.renderHeader();\n }\n\n // Phase 2 (VIRTUALIZATION): Recalculate virtual window\n if (phase >= RenderPhase.VIRTUALIZATION) {\n this.#callbacks.renderVirtualWindow();\n }\n\n // Phase 1 (STYLE): Run afterRender hooks (always runs)\n if (phase >= RenderPhase.STYLE) {\n this.#callbacks.afterRender();\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","import type { InternalGrid, ResizeController } from '../types';\n\nexport function createResizeController(grid: InternalGrid): 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 as unknown as HTMLElement).dispatchEvent(\n new CustomEvent('column-resize', { detail: { field: col.field, width } }),\n );\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 as unknown as HTMLElement).dispatchEvent(\n new CustomEvent('column-resize-reset', { detail: { field: col.field, width: col.width } }),\n );\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 true if the row was found and animated, false otherwise\n */\nexport function animateRow<T>(grid: InternalGrid<T>, rowIndex: number, animationType: RowAnimationType): boolean {\n // Guard against invalid indices\n if (rowIndex < 0) {\n return false;\n }\n\n const rowEl = grid.findRenderedRowElement?.(rowIndex);\n if (!rowEl) {\n // Row is virtualized out of view - nothing to animate\n return false;\n }\n\n animateRowElement(rowEl, animationType);\n return true;\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 Number of rows that were actually animated (visible in viewport)\n */\nexport function animateRows<T>(grid: InternalGrid<T>, rowIndices: number[], animationType: RowAnimationType): number {\n let animatedCount = 0;\n for (const rowIndex of rowIndices) {\n if (animateRow(grid, rowIndex, animationType)) {\n animatedCount++;\n }\n }\n return animatedCount;\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 true if the row was found and animated, false otherwise\n */\nexport function animateRowById<T>(grid: InternalGrid<T>, rowId: string, animationType: RowAnimationType): boolean {\n // Find row index by searching _rows\n const rows = grid._rows ?? [];\n const getRowId = grid.getRowId;\n if (!getRowId) {\n return 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 false;\n }\n return animateRow(grid, rowIndex, animationType);\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`;\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)\n if (!isSinglePanel) {\n const chevronSpan = createElement('span', { class: 'tbw-accordion-chevron' });\n chevronSpan.innerHTML = panel.isExpanded ? options.collapseIcon : 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 ShellConfig,\n ToolbarContentDefinition,\n ToolPanelDefinition,\n} from '../types';\nimport { DEFAULT_GRID_ICONS } from '../types';\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 /** 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 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 const collapseIcon = iconToString(icons?.collapse ?? DEFAULT_GRID_ICONS.collapse);\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 const chevronHtml = isSinglePanel\n ? ''\n : `<span class=\"tbw-accordion-chevron\">${isExpanded ? collapseIcon : 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 console.warn(\n `[parseLightDomToolPanels] 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 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 const expandIcon = iconToString(icons?.expand ?? DEFAULT_GRID_ICONS.expand);\n const collapseIcon = iconToString(icons?.collapse ?? DEFAULT_GRID_ICONS.collapse);\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 const chevron = section.querySelector('.tbw-accordion-chevron');\n if (chevron) {\n chevron.innerHTML = isExpanded ? collapseIcon : expandIcon;\n }\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 (like toolbar buttons) can be restored\n * to their original containers and moved to new slots after re-render.\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\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 * Callbacks for ShellController to communicate with the grid.\n * This interface decouples the controller from grid internals.\n */\nexport interface ShellControllerCallbacks {\n /** Get the render root for DOM queries (the grid element) */\n getShadow: () => Element;\n /** Get the current shell config */\n getShellConfig: () => ShellConfig | undefined;\n /** Get accordion expand/collapse icons */\n getAccordionIcons: () => { expand: IconValue; collapse: IconValue };\n /** Emit an event from the grid */\n emit: (eventName: string, detail: unknown) => void;\n /** Refresh the shell header (re-parse light DOM and re-render) */\n refreshShellHeader: () => void;\n}\n\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, callbacks: ShellControllerCallbacks): 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 console.warn('[tbw-grid] No tool panels registered');\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 = callbacks.getShadow();\n updateToolbarActiveStates(shadow, state);\n updatePanelState(shadow, state);\n\n // Render accordion sections\n renderPanelContent(shadow, state, callbacks.getAccordionIcons());\n\n // Emit event\n callbacks.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 = callbacks.getShadow();\n updateToolbarActiveStates(shadow, state);\n updatePanelState(shadow, state);\n\n // Emit event\n callbacks.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 console.warn(`[tbw-grid] Tool panel section \"${sectionId}\" not found`);\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 = callbacks.getShadow();\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 callbacks.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 console.warn(`[tbw-grid] Tool panel \"${panel.id}\" already registered`);\n return;\n }\n state.toolPanels.set(panel.id, panel);\n\n if (initialized) {\n callbacks.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 callbacks.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 console.warn(`[tbw-grid] Header content \"${content.id}\" already registered`);\n return;\n }\n state.headerContents.set(content.id, content);\n\n if (initialized) {\n renderHeaderContent(callbacks.getShadow(), 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 = callbacks.getShadow().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 console.warn(`[tbw-grid] Toolbar content \"${content.id}\" already registered`);\n return;\n }\n state.toolbarContents.set(content.id, content);\n\n if (initialized) {\n callbacks.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 callbacks.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`;\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\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 console.warn('[tbw-grid] 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 console.warn(\n '[tbw-grid] Could not find grid.css in document.styleSheets. Grid styling will not work.',\n 'Available stylesheets:',\n Array.from(document.styleSheets).map((s) => s.href || '(inline)'),\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 * Handles touch events for scrolling with momentum physics.\n * Supports both vertical (faux scrollbar) and horizontal (scroll area) scrolling.\n */\n\n// #region Types\nexport interface TouchScrollState {\n startY: number | null;\n startX: number | null;\n scrollTop: number | null;\n scrollLeft: number | null;\n lastY: number | null;\n lastX: number | null;\n lastTime: number | null;\n velocityY: number;\n velocityX: number;\n momentumRaf: number;\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 scrollTop: null,\n scrollLeft: null,\n lastY: null,\n lastX: null,\n lastTime: null,\n velocityY: 0,\n velocityX: 0,\n momentumRaf: 0,\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.scrollTop = null;\n state.scrollLeft = null;\n state.lastY = null;\n state.lastX = null;\n state.lastTime = null;\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 touchstart event.\n */\nexport function handleTouchStart(e: TouchEvent, state: TouchScrollState, elements: TouchScrollElements): void {\n if (e.touches.length !== 1) return;\n\n // Cancel any ongoing momentum animation\n cancelMomentum(state);\n\n const touch = e.touches[0];\n state.startY = touch.clientY;\n state.startX = touch.clientX;\n state.lastY = touch.clientY;\n state.lastX = touch.clientX;\n state.lastTime = performance.now();\n state.scrollTop = elements.fauxScrollbar.scrollTop;\n state.scrollLeft = elements.scrollArea?.scrollLeft ?? 0;\n state.velocityY = 0;\n state.velocityX = 0;\n}\n\n/**\n * Handle touchmove event.\n * Returns true if the event should be prevented (grid scrolled).\n */\nexport function handleTouchMove(e: TouchEvent, state: TouchScrollState, elements: TouchScrollElements): boolean {\n if (\n e.touches.length !== 1 ||\n state.startY === null ||\n state.startX === null ||\n state.scrollTop === null ||\n state.scrollLeft === null\n ) {\n return false;\n }\n\n const touch = e.touches[0];\n const currentY = touch.clientY;\n const currentX = touch.clientX;\n const now = performance.now();\n\n const deltaY = state.startY - currentY;\n const deltaX = state.startX - currentX;\n\n // Calculate velocity for momentum scrolling\n if (state.lastTime !== null && state.lastY !== null && state.lastX !== null) {\n const dt = now - state.lastTime;\n if (dt > 0) {\n // Velocity in pixels per millisecond\n state.velocityY = (state.lastY - currentY) / dt;\n state.velocityX = (state.lastX - currentX) / dt;\n }\n }\n state.lastY = currentY;\n state.lastX = currentX;\n state.lastTime = now;\n\n // Check if grid can scroll in the requested directions\n const { scrollTop, scrollHeight, clientHeight } = elements.fauxScrollbar;\n const maxScrollY = scrollHeight - clientHeight;\n const canScrollVertically = (deltaY > 0 && scrollTop < maxScrollY) || (deltaY < 0 && scrollTop > 0);\n\n let canScrollHorizontally = false;\n if (elements.scrollArea) {\n const { scrollLeft, scrollWidth, clientWidth } = elements.scrollArea;\n const maxScrollX = scrollWidth - clientWidth;\n canScrollHorizontally = (deltaX > 0 && scrollLeft < maxScrollX) || (deltaX < 0 && scrollLeft > 0);\n }\n\n // Apply scroll if grid can scroll in that direction\n if (canScrollVertically) {\n elements.fauxScrollbar.scrollTop = state.scrollTop + deltaY;\n }\n if (canScrollHorizontally && elements.scrollArea) {\n elements.scrollArea.scrollLeft = state.scrollLeft + deltaX;\n }\n\n // Return true to prevent page scroll when we scrolled the grid\n return canScrollVertically || canScrollHorizontally;\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 touch scroll event listeners on the grid content element.\n * Returns a cleanup function that removes all listeners.\n */\nexport function setupTouchScrollListeners(\n gridContentEl: HTMLElement,\n state: TouchScrollState,\n elements: TouchScrollElements,\n signal: AbortSignal,\n): void {\n gridContentEl.addEventListener('touchstart', (e: TouchEvent) => handleTouchStart(e, state, elements), {\n passive: true,\n signal,\n });\n\n gridContentEl.addEventListener(\n 'touchmove',\n (e: TouchEvent) => {\n const shouldPrevent = handleTouchMove(e, state, elements);\n if (shouldPrevent) {\n e.preventDefault();\n }\n },\n { passive: false, signal },\n );\n\n gridContentEl.addEventListener('touchend', () => handleTouchEnd(state, elements), { passive: true, signal });\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 { 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,\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: 'sticky',\n pluginName: 'pinnedColumns',\n level: 'column',\n description: 'the \"sticky\" column property',\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 // 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>(config: GridConfig<T>, plugins: readonly BaseGridPlugin[]): 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 throw new Error(\n `[tbw-grid] 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 );\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[]): 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 prefix = `[tbw-grid:${capitalize(plugin.name)}Plugin]`;\n const formatted = `${prefix} 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 console.warn(warning);\n }\n }\n\n // Throw consolidated error if any (always, regardless of environment)\n if (errors.length > 0) {\n throw new Error(`[tbw-grid] Configuration error:\\n\\n${errors.join('\\n\\n')}`);\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(plugin: BaseGridPlugin, loadedPlugins: readonly BaseGridPlugin[]): 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 throw new Error(\n `[tbw-grid] 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 );\n } else {\n // Soft dependency - log info message but continue\n console.info(\n `[tbw-grid] ${capitalize(pluginName)}Plugin: Optional \"${requiredPlugin}\" plugin not found. ` +\n `Some features may be unavailable.`,\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[]): 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 console.warn(\n `[tbw-grid] Plugin incompatibility warning:\\n\\n` +\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 );\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 * 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 { 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 // #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);\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 // 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 console.warn(\n `[tbw-grid] Deprecation warning: \"${plugin.name}\" uses getExtraHeight() / getExtraHeightBefore() ` +\n `which are deprecated and will be removed in v3.0.\\n` +\n ` → Migrate to getRowHeight(row, index) for better variable row height support.\\n` +\n ` → See: https://toolbox-web.dev/docs/grid/plugins/migration#row-height-hooks`,\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 }\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);\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 * Used to skip the hook call overhead when no plugins need it.\n */\n hasAfterCellRenderHook(): boolean {\n return this.plugins.some((p) => typeof p.afterCellRender === 'function');\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 * Used to skip the hook call overhead when no plugins need it.\n */\n hasAfterRowRenderHook(): boolean {\n return 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 console.error(`[tbw-grid] Error in plugin event handler for \"${eventType}\":`, error);\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","/**\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 = `/**\n * tbw-grid Light DOM Styles\n *\n * This stylesheet uses CSS nesting to scope all styles to the tbw-grid element.\n * All selectors are automatically prefixed with \\`tbw-grid\\` for encapsulation.\n *\n * CSS Cascade Layers are used to control style priority:\n * - tbw-base: Core grid styles (lowest priority)\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 * @see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting\n * @see https://developer.mozilla.org/en-US/docs/Web/CSS/@layer\n */\n\n/* Declare layer order - earlier layers have lower priority */\n@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","import { createAriaState, updateAriaCounts, updateAriaLabels, type AriaState } from './internal/aria';\nimport { autoSizeColumns, updateTemplate } from './internal/columns';\nimport { ConfigManager } from './internal/config-manager';\nimport { setupCellEventDelegation, setupRootEventDelegation } from './internal/event-delegation';\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 { 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 renderShellHeader,\n setupShellEventListeners,\n setupToolPanelResize,\n shouldRenderShellHeader,\n type ShellController,\n type ShellState,\n type ToolPanelRendererFactory,\n} from './internal/shell';\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 {\n computeAverageExcludingPluginRows,\n getRowIndexAtOffset,\n getTotalHeight,\n measureRenderedRowHeights,\n rebuildPositionCache,\n updateRowHeight,\n} from './internal/virtualization';\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 CellChangeDetail,\n ColumnConfig,\n ColumnConfigMap,\n ColumnInternal,\n DataGridEventMap,\n FitMode,\n FrameworkAdapter,\n GridColumnState,\n GridConfig,\n HeaderContentDefinition,\n InternalGrid,\n ResizeController,\n RowAnimationType,\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 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\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\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 }\n\n // visibleColumns returns only visible columns for rendering\n // This is what header/row rendering should use\n get _visibleColumns(): ColumnInternal<T>[] {\n return 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: HTMLElement[] = [];\n _resizeController!: ResizeController;\n\n // Virtualization & scroll state\n _virtualization: VirtualState = {\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 // Variable row height support\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 };\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 /**\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 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 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 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 render scheduler with callbacks\n this.#scheduler = new RenderScheduler({\n mergeConfig: () => {\n // Re-parse light DOM columns to pick up framework adapter renderers\n // This is essential for React/Angular where renderers register asynchronously\n this.#configManager.parseLightDomColumns(this as unknown as HTMLElement);\n this.#configManager.merge();\n this.#updatePluginConfigs(); // Sync plugin configs (including auto-detection) before processing\n // Validate that plugin-specific column properties have their required plugins loaded\n // This runs after plugins are loaded and config is merged\n validatePluginProperties(this.#effectiveConfig, this.#pluginManager?.getPlugins() ?? []);\n // Validate plugin configRules (errors/warnings for invalid config combinations)\n validatePluginConfigRules(this.#pluginManager?.getPlugins() ?? []);\n // Validate plugin incompatibilities (warnings for conflicting plugin combinations)\n validatePluginIncompatibilities(this.#pluginManager?.getPlugins() ?? []);\n // Update ARIA labels (explicit config or derived from shell title)\n this.#updateAriaLabels();\n // Store base columns before plugin transformation\n this.#baseColumns = [...this._columns];\n },\n processColumns: () => this.#processColumns(),\n processRows: () => this.#rebuildRowModel(),\n renderHeader: () => renderHeader(this),\n updateTemplate: () => updateTemplate(this),\n renderVirtualWindow: () => this.refreshVirtualWindow(true, true),\n afterRender: () => {\n this.#pluginManager?.afterRender();\n\n // Recalculate spacer height after plugins modify the DOM in afterRender.\n // Plugins like MasterDetailPlugin create/remove detail elements in afterRender,\n // which changes the total content height. Since refreshVirtualWindow was called\n // with skipAfterRender=true (scheduler calls afterRender separately), the\n // microtask that normally handles this recalculation was skipped.\n // Use forceRead=false (cached geometry) since the scheduler's renderVirtualWindow\n // already read fresh geometry and updated caches before calling afterRender.\n // Plugins modify content inside containers, not container geometry itself.\n // Any container geometry changes are caught asynchronously by ResizeObserver.\n if (this._virtualization.enabled && this._virtualization.totalHeightEl) {\n queueMicrotask(() => {\n if (!this._virtualization.totalHeightEl) return;\n const newTotalHeight = this.#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 // Uses RAF to ensure browser has computed layout before measuring\n if (this.#needsRowHeightMeasurement) {\n this.#needsRowHeightMeasurement = false;\n // Double RAF ensures layout is fully computed (first RAF schedules after paint,\n // second RAF runs after that frame's layout)\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 // The setter calls #updateLoadingOverlay which requires .tbw-grid-root,\n // but that element doesn't exist until this first render completes.\n if (this.#loading) {\n this.#updateLoadingOverlay();\n }\n },\n isConnected: () => this.isConnected && this.#connected,\n });\n // Connect ready promise to scheduler\n this.#scheduler.setInitialReadyResolver(() => this.#readyResolve?.());\n\n // Initialize shell controller with callbacks\n this.#shellController = createShellController(this.#shellState, {\n getShadow: () => this.#renderRoot,\n getShellConfig: () => this.#effectiveConfig?.shell,\n getAccordionIcons: () => ({\n expand: this.#effectiveConfig?.icons?.expand ?? DEFAULT_GRID_ICONS.expand,\n collapse: this.#effectiveConfig?.icons?.collapse ?? DEFAULT_GRID_ICONS.collapse,\n }),\n emit: (eventName, detail) => this.#emit(eventName, detail),\n refreshShellHeader: () => this.refreshShellHeader(),\n });\n\n // Initialize config manager with callbacks\n this.#configManager = new ConfigManager<T>({\n getRows: () => this.#rows,\n getSortState: () => this._sortState,\n setSortState: (state) => {\n this._sortState = state;\n },\n onConfigChange: () => {\n this.#scheduler.requestPhase(RenderPhase.FULL, 'configChange');\n },\n emit: (eventName, detail) => this.#emit(eventName, detail),\n clearRowPool: () => {\n this._rowPool.length = 0;\n if (this._bodyEl) this._bodyEl.innerHTML = '';\n this.__rowRenderEpoch++;\n },\n setup: () => this.#setup(),\n renderHeader: () => renderHeader(this),\n updateTemplate: () => updateTemplate(this),\n refreshVirtualWindow: () => this.#scheduler.requestPhase(RenderPhase.VIRTUALIZATION, 'configManager'),\n getVirtualization: () => this._virtualization,\n setRowHeight: (height) => {\n this._virtualization.rowHeight = height;\n },\n applyAnimationConfig: (config) => this.#applyAnimationConfig(config),\n getShellLightDomTitle: () => this.#shellState.lightDomTitle,\n getShellToolPanels: () => this.#shellState.toolPanels,\n getShellHeaderContents: () => this.#shellState.headerContents,\n getShellToolbarContents: () => this.#shellState.toolbarContents,\n getShellLightDomHeaderContent: () => this.#shellState.lightDomHeaderContent,\n getShellHasToolButtonsContainer: () => this.#shellState.hasToolButtonsContainer,\n });\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.\n * Used by plugins for inter-plugin communication.\n * @group Plugin Communication\n * @internal Plugin API\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 name.\n * Used for loose coupling between plugins (avoids static imports).\n * @group Plugin Communication\n * @internal Plugin API\n */\n getPluginByName(name: string): BaseGridPlugin | undefined {\n return this.#pluginManager?.getPluginByName(name);\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 * Initialize plugin system with instances from config.\n * Plugins are class instances passed in gridConfig.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 plugins = Array.isArray(pluginsConfig) ? (pluginsConfig as BaseGridPlugin[]) : [];\n\n // Attach all plugins\n this.#pluginManager.attachAll(plugins);\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 hasn'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\n // Check if plugins have actually changed (same array reference or same contents)\n // This avoids unnecessary detach/attach cycles on every render\n if (this.#lastPluginsArray === newPlugins) {\n return; // Same array reference - no change\n }\n\n // Check if the arrays have the same plugin instances\n if (\n this.#lastPluginsArray &&\n this.#lastPluginsArray.length === newPlugins.length &&\n this.#lastPluginsArray.every((p, i) => p === newPlugins[i])\n ) {\n // Same plugins in same order - just update the 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 // Header contents don't have a light DOM tracking set, so clear all and re-collect\n for (const contentId of this.#shellState.headerContents.keys()) {\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\n this.#lastPluginsArray = newPlugins;\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 as unknown as HTMLElement);\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 to avoid unnecessary re-initialization\n const pluginsConfig = this.#effectiveConfig?.plugins;\n this.#lastPluginsArray = Array.isArray(pluginsConfig) ? (pluginsConfig as BaseGridPlugin[]) : [];\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 // 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\n // Clear plugin tracking to allow fresh initialization on reconnect\n this.#lastPluginsArray = 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 console.warn(`[tbw-grid] Invalid JSON for '${name}' attribute:`, newValue);\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 }\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 as unknown as InternalGrid<T>);\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 as unknown as InternalGrid<T>, 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.#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');\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 // Use a 1px threshold to avoid oscillation from sub-pixel rounding\n if (measuredHeight > 0 && Math.abs(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.#initializePositionCache();\n\n // Update spacer height with the correct total\n if (this._virtualization.totalHeightEl) {\n const newHeight = this.#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 // 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 as unknown as InternalGrid<T>, 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 (shadow DOM doesn't reliably support :focus-within)\n // Listen on shadow root to catch focus events from shadow DOM elements\n // Cast to EventTarget since TypeScript's lib.dom doesn't include focus events on ShadowRoot\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 (!newFocus || !this.#renderRoot.contains(newFocus)) {\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 * @example\n * ```typescript\n * // Type-safe: detail is CellClickDetail<Employee>\n * grid.addEventListener('cell-click', (e) => {\n * console.log(e.detail.field, e.detail.value);\n * });\n *\n * // Works with editing events when EditingPlugin is imported\n * grid.addEventListener('cell-commit', (e) => {\n * console.log(e.detail.oldValue, '→', e.detail.value);\n * });\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 #emit<D>(eventName: string, detail: D): void {\n this.dispatchEvent(new CustomEvent(eventName, { detail, bubbles: true, composed: true }));\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 = this.#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 /**\n * Try to resolve the ID for a row using configured getRowId or fallback.\n * Returns undefined if no ID can be determined (non-throwing).\n * Used internally by #rebuildRowIdMap.\n */\n #tryResolveRowId(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 * Used by public getRowId() method.\n */\n #resolveRowIdOrThrow(row: T, getRowId?: (row: T) => string): string {\n const id = this.#tryResolveRowId(row, getRowId);\n if (id === undefined) {\n throw new Error(\n '[tbw-grid] Cannot determine row ID. ' +\n 'Configure getRowId in gridConfig or ensure rows have an \"id\" property.',\n );\n }\n return id;\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\n this.#configManager.parseLightDomColumns(this as unknown as HTMLElement);\n this.#configManager.merge();\n this.#updatePluginConfigs();\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\n // Full re-render needed if shell state or panel count changed\n const needsFullRerender =\n hadShell !== nowNeedsShell ||\n (!hadToolPanel && nowHasToolPanels) ||\n (hadToolPanel && toolPanelCount !== accordionSectionsBefore);\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 // 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:\n // 1. Modified existing columns\n // 2. Added new columns (e.g., expander column)\n // 3. Reordered columns\n // We trust the plugin's output order and include all columns they returned\n // plus any hidden columns at the end\n this._columns = [...processedColumns, ...hiddenCols] as ColumnInternal<T>[];\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 /** 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 // 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(originalRows) ?? originalRows;\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 position cache for variable heights (rows may have changed)\n if (this._virtualization.variableHeights) {\n this.#initializePositionCache();\n }\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 as unknown as InternalGrid<T>, 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 /** 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 as unknown as HTMLElement);\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.#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 return (\n (Array.from(this._bodyEl.querySelectorAll('.data-grid-row')) as HTMLElement[]).find((r) => {\n const cell = r.querySelector('.cell[data-row]');\n return cell && Number(cell.getAttribute('data-row')) === rowIndex;\n }) || null\n );\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 const col = this._columns[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 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 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, colIndex: number, headerEl: HTMLElement): boolean {\n const col = this._columns[colIndex];\n if (!col) return false;\n\n const headerClickEvent: HeaderClickEvent = {\n colIndex,\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.\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 = document.querySelector('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\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 this.#resolveRowIdOrThrow(row, 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.#rowIdMap.get(id)?.row;\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 const entry = this.#rowIdMap.get(id);\n if (!entry) {\n throw new Error(\n `[tbw-grid] Row with ID \"${id}\" not found. ` + `Ensure the row exists and getRowId is correctly configured.`,\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 this.#emit('cell-change', {\n row,\n rowId: id,\n rowIndex: index,\n field,\n oldValue,\n newValue,\n changes,\n source,\n } as CellChangeDetail<T>);\n }\n\n // Schedule re-render if anything changed\n if (changedFields.length > 0) {\n this.#scheduler.requestPhase(RenderPhase.ROWS, 'updateRow');\n }\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 let anyChanged = false;\n\n for (const { id, changes } of updates) {\n const entry = this.#rowIdMap.get(id);\n if (!entry) {\n throw new Error(\n `[tbw-grid] Row with ID \"${id}\" not found. ` + `Ensure the row exists and getRowId is correctly configured.`,\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 this.#emit('cell-change', {\n row,\n rowId: id,\n rowIndex: index,\n field,\n oldValue,\n newValue,\n changes,\n source,\n } as CellChangeDetail<T>);\n }\n }\n }\n\n // Schedule single re-render for all changes\n if (anyChanged) {\n this.#scheduler.requestPhase(RenderPhase.ROWS, 'updateRows');\n }\n }\n\n /**\n * Animate a row at the specified index.\n * Applies a visual animation to highlight changes, insertions, or removals.\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 *\n * @example\n * ```typescript\n * // Highlight a row after external data update\n * grid.updateRow('row-123', { status: 'shipped' });\n * grid.animateRow(rowIndex, 'change');\n *\n * // Animate new row insertion\n * grid.rows = [...grid.rows, newRow];\n * grid.animateRow(grid.rows.length - 1, 'insert');\n * ```\n */\n animateRow(rowIndex: number, type: RowAnimationType): void {\n animateRow(this as unknown as InternalGrid, rowIndex, type);\n }\n\n /**\n * Animate multiple rows at once.\n * More efficient than calling `animateRow()` multiple times.\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 *\n * @example\n * ```typescript\n * // Highlight all changed rows after bulk update\n * const changedIndices = [0, 2, 5];\n * grid.animateRows(changedIndices, 'change');\n * ```\n */\n animateRows(rowIndices: number[], type: RowAnimationType): void {\n animateRows(this as unknown as InternalGrid, rowIndices, type);\n }\n\n /**\n * Animate a row by its ID.\n * Uses the configured `getRowId` function to find the row.\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', (data) => {\n * grid.updateRow(data.id, data.changes);\n * grid.animateRowById(data.id, 'change');\n * });\n * ```\n */\n animateRowById(rowId: string, type: RowAnimationType): boolean {\n return animateRowById(this as unknown as InternalGrid, rowId, type);\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 = document.querySelector('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.#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.#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 = 'dg-toolbar-btn';\n * csvBtn.addEventListener('click', () => exportToCSV(grid.rows));\n *\n * const jsonBtn = document.createElement('button');\n * jsonBtn.textContent = 'Export JSON';\n * jsonBtn.className = 'dg-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\n // Re-create resize controller (DOM elements changed)\n this._resizeController = createResizeController(this as unknown as InternalGrid<T>);\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\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 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 as unknown as HTMLElement);\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 as unknown as HTMLElement);\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\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 const fauxScrollbar = this._virtualization.container;\n const viewportEl = this._virtualization.viewportEl ?? fauxScrollbar;\n if (viewportEl) {\n this._virtualization.cachedViewportHeight = viewportEl.clientHeight;\n }\n if (fauxScrollbar) {\n this._virtualization.cachedFauxHeight = fauxScrollbar.clientHeight;\n }\n const scrollAreaEl = this._virtualization.scrollAreaEl;\n if (scrollAreaEl) {\n this._virtualization.cachedScrollAreaHeight = scrollAreaEl.clientHeight;\n }\n }\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 virt = this._virtualization;\n\n // Use cached geometry to avoid forced synchronous layout reads.\n // Caches are kept current by ResizeObserver (#updateCachedGeometry) and force-refresh paths.\n // Only read fresh values when forceRead is explicitly requested (structural DOM changes).\n let fauxScrollHeight: number;\n let viewportHeight: number;\n let scrollAreaHeight: number;\n\n if (forceRead) {\n const fauxScrollbar = virt.container ?? this;\n const viewportEl = virt.viewportEl ?? fauxScrollbar;\n const scrollAreaEl = virt.scrollAreaEl;\n\n fauxScrollHeight = fauxScrollbar.clientHeight;\n viewportHeight = viewportEl.clientHeight;\n scrollAreaHeight = scrollAreaEl ? scrollAreaEl.clientHeight : fauxScrollHeight;\n\n // Update caches while we have fresh values\n virt.cachedFauxHeight = fauxScrollHeight;\n virt.cachedViewportHeight = viewportHeight;\n virt.cachedScrollAreaHeight = scrollAreaHeight;\n } else {\n fauxScrollHeight = virt.cachedFauxHeight;\n viewportHeight = virt.cachedViewportHeight;\n scrollAreaHeight = virt.cachedScrollAreaHeight || fauxScrollHeight;\n }\n\n // Use scroll-area height as reference since it contains the actual content\n const viewportHeightDiff = scrollAreaHeight - viewportHeight;\n\n // Horizontal scrollbar compensation: When a horizontal scrollbar appears inside scroll-area,\n // the faux scrollbar (sibling) is taller than scroll-area. Add the difference as padding.\n const hScrollbarPadding = Math.max(0, fauxScrollHeight - scrollAreaHeight);\n\n // Calculate row content height - use position cache for variable heights, simple multiplication otherwise\n let rowContentHeight: number;\n let pluginExtraHeight = 0;\n\n if (virt.variableHeights && virt.positionCache) {\n // Variable heights mode: position cache includes heights from getRowHeight() plugin hooks\n // Do NOT add pluginExtraHeight here - it would double-count since getRowHeight() already\n // reports expanded detail heights via the position cache\n rowContentHeight = getTotalHeight(virt.positionCache);\n } else {\n // Fixed heights mode: use simple multiplication and add plugin extra heights\n // (for plugins using deprecated getExtraHeight() that don't implement getRowHeight())\n rowContentHeight = totalRows * virt.rowHeight;\n pluginExtraHeight = this.#pluginManager?.getExtraHeight() ?? 0;\n }\n\n const total = rowContentHeight + viewportHeightDiff + pluginExtraHeight + hScrollbarPadding;\n return total;\n }\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 * @internal\n */\n #initializePositionCache(): void {\n if (!this._virtualization.variableHeights) return;\n\n const rows = this._rows;\n const estimatedHeight = this._virtualization.rowHeight || 28;\n const rowHeightFn = this.#effectiveConfig.rowHeight as ((row: T, index: number) => number | undefined) | undefined;\n const getRowId = this.#effectiveConfig.getRowId;\n const rowIdFn = getRowId ? (row: T) => getRowId(row) : undefined;\n\n // Build position cache with priority: plugin height > rowHeight function > cached > estimated\n this._virtualization.positionCache = rebuildPositionCache(\n rows,\n this._virtualization.heightCache,\n estimatedHeight,\n { rowId: rowIdFn },\n (row, index) => {\n const pluginHeight = this.#pluginManager?.getRowHeight?.(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\n // Compute stats excluding plugin-managed rows\n const stats = computeAverageExcludingPluginRows(\n this._virtualization.positionCache,\n rows,\n estimatedHeight,\n (row, index) => this.#pluginManager?.getRowHeight?.(row, index),\n );\n this._virtualization.measuredCount = stats.measuredCount;\n if (stats.measuredCount > 0) {\n this._virtualization.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 if (!this._virtualization.variableHeights) return;\n if (!this._virtualization.positionCache) return;\n if (rowIndex < 0 || rowIndex >= this._rows.length) return;\n\n const positionCache = this._virtualization.positionCache;\n const row = this._rows[rowIndex];\n\n // Determine the new height\n let height = newHeight;\n if (height === undefined) {\n // Query plugin for height\n height = this.#pluginManager?.getRowHeight?.(row, rowIndex);\n }\n if (height === undefined) {\n // Fall back to base row height\n height = this._virtualization.rowHeight;\n }\n\n const currentEntry = positionCache[rowIndex];\n if (!currentEntry || Math.abs(currentEntry.height - height) < 1) {\n // No significant change\n return;\n }\n\n // Update position cache incrementally (updates this row + all subsequent offsets)\n updateRowHeight(positionCache, rowIndex, height);\n\n // Update spacer height\n if (this._virtualization.totalHeightEl) {\n const newTotalHeight = this.#calculateTotalSpacerHeight(this._rows.length);\n this._virtualization.totalHeightEl.style.height = `${newTotalHeight}px`;\n }\n }\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 * @internal\n */\n #measureRenderedRowHeights(start: number, end: number): void {\n if (!this._virtualization.variableHeights) return;\n if (!this._virtualization.positionCache) return;\n if (!this._bodyEl) return;\n\n const rowElements = this._bodyEl.querySelectorAll('.data-grid-row');\n const getRowId = this.#effectiveConfig.getRowId;\n\n const result = measureRenderedRowHeights(\n {\n positionCache: this._virtualization.positionCache,\n heightCache: this._virtualization.heightCache,\n rows: this._rows,\n defaultHeight: this._virtualization.rowHeight,\n start,\n end,\n getPluginHeight: (row, index) => this.#pluginManager?.getRowHeight?.(row, index),\n getRowId: getRowId ? (row: T) => getRowId(row) : undefined,\n },\n rowElements,\n );\n\n if (result.hasChanges) {\n this._virtualization.measuredCount = result.measuredCount;\n this._virtualization.averageHeight = result.averageHeight;\n\n // Update total height spacer\n if (this._virtualization.totalHeightEl) {\n const newTotalHeight = this.#calculateTotalSpacerHeight(this._rows.length);\n this._virtualization.totalHeightEl.style.height = `${newTotalHeight}px`;\n }\n }\n }\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 * @internal Plugin API\n */\n refreshVirtualWindow(force = false, skipAfterRender = false): boolean {\n if (!this._bodyEl) return false;\n\n const totalRows = this._rows.length;\n\n if (!this._virtualization.enabled) {\n this.#renderVisibleRows(0, totalRows);\n if (!skipAfterRender) {\n this.#pluginManager?.afterRender();\n }\n return true;\n }\n\n if (this._rows.length <= this._virtualization.bypassThreshold) {\n this._virtualization.start = 0;\n this._virtualization.end = totalRows;\n // Only reset transform on force refresh (initial render, data change)\n // Don't reset on scroll-triggered updates - the scroll handler manages transforms\n if (force) {\n this._bodyEl.style.transform = 'translateY(0px)';\n }\n this.#renderVisibleRows(0, totalRows, force ? ++this.__rowRenderEpoch : this.__rowRenderEpoch);\n // Only recalculate height on force refresh (structural changes)\n // Skip on scroll-only updates to prevent scrollbar jumpiness\n if (force && this._virtualization.totalHeightEl) {\n this._virtualization.totalHeightEl.style.height = `${this.#calculateTotalSpacerHeight(totalRows, true)}px`;\n }\n // Update ARIA counts on the grid container\n this.#updateAriaCounts(totalRows, this._visibleColumns.length);\n if (!skipAfterRender) {\n this.#pluginManager?.afterRender();\n }\n return true;\n }\n\n // --- Normal virtualization path with faux scrollbar pattern ---\n // Faux scrollbar provides scrollTop, viewport provides visible height\n const fauxScrollbar = this._virtualization.container ?? this;\n const viewportEl = this._virtualization.viewportEl ?? fauxScrollbar;\n // On force-refresh (structural changes), read fresh geometry and update cache.\n // On scroll-triggered updates, use cached height to avoid forced synchronous layout.\n // The cache is kept current by ResizeObserver + force-refresh calls.\n const viewportHeight = force\n ? (this._virtualization.cachedViewportHeight = viewportEl.clientHeight)\n : this._virtualization.cachedViewportHeight ||\n (this._virtualization.cachedViewportHeight = viewportEl.clientHeight);\n const rowHeight = this._virtualization.rowHeight;\n const scrollTop = fauxScrollbar.scrollTop;\n\n let start: number;\n const positionCache = this._virtualization.positionCache;\n\n // Variable row heights: use binary search on position cache\n if (this._virtualization.variableHeights && positionCache && positionCache.length > 0) {\n start = getRowIndexAtOffset(positionCache, scrollTop);\n if (start === -1) start = 0;\n } else {\n // Uniform row heights: simple division\n // When plugins add extra height (e.g., expanded details), the scroll position\n // includes that extra height. We need to find the actual row at scrollTop\n // by iteratively accounting for cumulative extra heights.\n // This prevents jumping when scrolling past expanded content.\n start = Math.floor(scrollTop / rowHeight);\n\n // Iteratively refine: the initial guess may be too high because scrollTop\n // includes extra heights from expanded rows before it. Adjust downward.\n let iterations = 0;\n const maxIterations = 10; // Prevent infinite loop\n while (iterations < maxIterations) {\n const extraHeightBefore = this.#pluginManager?.getExtraHeightBefore?.(start) ?? 0;\n const adjustedStart = Math.floor((scrollTop - extraHeightBefore) / rowHeight);\n if (adjustedStart >= start || adjustedStart < 0) break;\n start = adjustedStart;\n iterations++;\n }\n }\n\n // Faux scrollbar pattern: calculate effective position for this start\n // With translateY(0), the first rendered row appears at viewport top\n // Round down to even number so DOM nth-child(even) always matches data row parity\n // This prevents zebra stripe flickering during scroll since rows shift in pairs\n start = start - (start % 2);\n if (start < 0) start = 0;\n\n // Allow plugins to extend the start index backwards\n // (e.g., to keep expanded detail rows visible as they scroll out)\n const pluginAdjustedStart = this.#pluginManager?.adjustVirtualStart(start, scrollTop, rowHeight);\n if (pluginAdjustedStart !== undefined && pluginAdjustedStart < start) {\n start = pluginAdjustedStart;\n // Re-apply even alignment after plugin adjustment\n start = start - (start % 2);\n if (start < 0) start = 0;\n }\n\n // Faux pattern buffer: render enough rows to fill viewport + overscan\n // For variable heights, calculate based on actual heights from position cache\n // This ensures expanded detail rows don't cause blank space at bottom\n let end: number;\n\n if (this._virtualization.variableHeights && positionCache && positionCache.length > 0) {\n // Variable heights: walk forward from start, summing actual heights\n // until we've covered the viewport height plus some overscan\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 // Ensure we always have at least a few rows for edge cases\n const minRows = Math.ceil(viewportHeight / rowHeight) + 3;\n if (end - start < minRows) {\n end = Math.min(start + minRows, totalRows);\n }\n } else {\n // Fixed heights: simple calculation\n const visibleCount = Math.ceil(viewportHeight / rowHeight) + 3;\n end = start + visibleCount;\n }\n\n if (end > totalRows) end = totalRows;\n\n // Early-exit: if the visible window hasn't changed and this isn't a force refresh,\n // skip re-rendering rows entirely. The sync scroll handler already updated the transform.\n // This is the key perf optimization - row rendering is the most expensive part.\n const prevStart = this._virtualization.start;\n const prevEnd = this._virtualization.end;\n if (!force && start === prevStart && end === prevEnd) {\n // Transform was already applied by the sync scroll handler.\n // Just update it here for consistency (the sync handler may have used a slightly\n // different sub-pixel offset due to timing, but it's close enough to skip re-render).\n return false;\n }\n\n this._virtualization.start = start;\n this._virtualization.end = end;\n\n // Height spacer for scrollbar\n // The faux-vscroll is a sibling of .tbw-scroll-area, so it doesn't shrink when\n // elements inside scroll-area (header, column groups, footer, hScrollbar) take vertical space.\n // viewportHeightDiff captures ALL these differences - no extra buffer needed.\n // Use cached geometry on scroll path; read fresh on force-refresh.\n const fauxScrollHeight = force\n ? (this._virtualization.cachedFauxHeight = fauxScrollbar.clientHeight)\n : this._virtualization.cachedFauxHeight || (this._virtualization.cachedFauxHeight = fauxScrollbar.clientHeight);\n\n // Also update scroll-area height cache on force-refresh\n if (force) {\n const scrollAreaEl = this._virtualization.scrollAreaEl;\n if (scrollAreaEl) {\n this._virtualization.cachedScrollAreaHeight = scrollAreaEl.clientHeight;\n }\n }\n\n // Guard: Skip height calculation if faux scrollbar has no height but viewport does\n // This indicates stale DOM references during recreation (e.g., shell toggle)\n // When both are 0 (test environment or not in DOM), proceed normally\n if (fauxScrollHeight === 0 && viewportHeight > 0) {\n // Stale refs detected, schedule retry through the scheduler\n // Using scheduler ensures this batches with other pending work\n this.#scheduler.requestPhase(RenderPhase.VIRTUALIZATION, 'stale-refs-retry');\n return false;\n }\n\n // Only recalculate height on force refresh (structural changes like data/plugin changes)\n // Skip on scroll-only updates to prevent scrollbar jumpiness from repeated DOM reads\n if (force && this._virtualization.totalHeightEl) {\n const totalHeight = this.#calculateTotalSpacerHeight(totalRows);\n this._virtualization.totalHeightEl.style.height = `${totalHeight}px`;\n }\n\n // Smooth scroll: apply offset for fluid motion\n // Since start is even-aligned, offset is distance from that aligned position\n // This creates smooth sliding while preserving zebra stripe parity\n //\n // Get actual offset for the start row:\n // - Variable heights mode: use position cache offset (already includes heights from getRowHeight())\n // - Fixed heights mode: use simple multiplication + extraHeightBefore for deprecated hooks\n let startRowOffset: number;\n if (this._virtualization.variableHeights && positionCache && positionCache[start]) {\n // Position cache offset already accounts for variable heights via getRowHeight()\n // Do NOT add extraHeightBefore - it would double-count\n startRowOffset = positionCache[start].offset;\n } else {\n // Fixed heights mode: add extra heights from deprecated hooks\n const extraHeightBeforeStart = this.#pluginManager?.getExtraHeightBefore?.(start) ?? 0;\n startRowOffset = start * rowHeight + extraHeightBeforeStart;\n }\n\n const subPixelOffset = -(scrollTop - startRowOffset);\n this._bodyEl.style.transform = `translateY(${subPixelOffset}px)`;\n\n this.#renderVisibleRows(start, end, force ? ++this.__rowRenderEpoch : this.__rowRenderEpoch);\n\n // Measure rendered row heights for variable height mode\n // Only on force refresh to avoid hot path overhead during scroll\n if (force && this._virtualization.variableHeights) {\n this.#measureRenderedRowHeights(start, end);\n }\n\n // Update ARIA counts on the grid container\n this.#updateAriaCounts(totalRows, this._visibleColumns.length);\n\n // Only run plugin afterRender hooks on force refresh (structural changes)\n // Skip on scroll-triggered renders for maximum performance\n if (force && !skipAfterRender) {\n this.#pluginManager?.afterRender();\n\n // After plugins modify the DOM (e.g., add footer, column groups),\n // heights may have changed. Recalculate spacer height in a microtask\n // to catch these changes before the next paint.\n // Use forceRead=false (cached geometry) since the force path above\n // already read fresh geometry and updated all caches. Plugins modify\n // content inside containers, not container geometry itself.\n queueMicrotask(() => {\n if (!this._virtualization.totalHeightEl) return;\n\n // Use cached geometry - avoids forced synchronous layout\n const newTotalHeight = this.#calculateTotalSpacerHeight(totalRows);\n\n // Guard: skip if stale refs detected (0 faux height with positive viewport)\n if (this._virtualization.cachedFauxHeight === 0 && this._virtualization.cachedViewportHeight > 0) return;\n\n this._virtualization.totalHeightEl.style.height = `${newTotalHeight}px`;\n });\n }\n\n return true;\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 // #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\n(globalThis as unknown as { DataGridElement: typeof DataGridElement }).DataGridElement = DataGridElement;\n\n// Type augmentation for querySelector/createElement\ndeclare global {\n interface HTMLElementTagNameMap {\n 'tbw-grid': DataGridElement;\n }\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 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;\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 */\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 a future major version.\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 /** The grid's shadow root. */\n shadowRoot: ShadowRoot | null;\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 // 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 /** 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","/**\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 {\n ColumnConfig,\n ColumnState,\n GridPlugin,\n HeaderContentDefinition,\n IconValue,\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(name: string): 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\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 * 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 * @example\n * ```ts\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 * 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 as unknown as HTMLElement;\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 message.\n */\n protected warn(message: string): void {\n console.warn(`[tbw-grid:${this.name}] ${message}`);\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 v3.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 v3.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.\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 * 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 types needed for factory functions\nimport type { 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 * This avoids the need to cast when querying grids:\n * ```typescript\n * // Before: manual cast required\n * const grid = document.querySelector('tbw-grid') as DataGridElement<Employee>;\n *\n * // After: fully typed\n * const grid = queryGrid<Employee>('#my-grid');\n * if (grid) {\n * grid.rows = employees; // ✓ Typed!\n * }\n * ```\n *\n * @param selector - CSS selector to find the grid element\n * @param parent - Parent node to search within (defaults to document)\n * @returns The typed grid element or null if not found\n */\nexport function queryGrid<TRow = unknown>(\n selector: string,\n parent: ParentNode = document,\n): DataGridElement<TRow> | null {\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} 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 DataGridCustomEvent,\n DataGridElement as DataGridElementInterface,\n DataGridEventMap,\n ExpandCollapseAnimation,\n ExternalMountEditorDetail,\n ExternalMountViewDetail,\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 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 EventDefinition,\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// #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"],"names":["createAriaState","updateAriaCounts","state","rowsBodyEl","bodyEl","rowCount","colCount","prevRowCount","getEffectiveAriaLabel","config","shellState","explicitLabel","updateAriaLabels","updated","ariaLabel","ariaDescribedBy","FitModeEnum","DEFAULT_ANIMATION_CONFIG","DEFAULT_FILTER_ICON","DEFAULT_GRID_ICONS","parseLightDomColumns","host","el","field","rawType","type","header","sortable","editable","widthAttr","numericWidth","minWidthAttr","numericMinWidth","editorName","rendererName","optionsAttr","item","value","label","viewTpl","editorTpl","headerTpl","adapters","viewTarget","viewAdapter","a","renderer","editorTarget","editorAdapter","editor","c","mergeColumns","programmatic","dom","domMap","existing","cRenderer","existingRenderer","merged","d","m","dRenderer","mRenderer","addPart","token","autoSizeColumns","grid","mode","headerCells","changed","col","i","headerCell","max","rowEl","cell","w","updateTemplate","min","inferType","inferColumns","rows","provided","sample","columns","k","v","typeMap","EXPR_RE","EMPTY_SENTINEL","SAFE_EXPR","FORBIDDEN","escapeHtml","text","DANGEROUS_TAGS","DANGEROUS_ATTR_PATTERN","URL_ATTRS","DANGEROUS_URL_PROTOCOL","sanitizeHTML","html","template","sanitizeNode","root","toRemove","elements","tagName","attr","attrsToRemove","attrName","name","evalTemplateString","raw","ctx","parts","evaluated","_m","expr","res","evalSingle","finalStr","postProcess","allEmpty","p","REFLECTIVE_RE","key","dotChain","out","str","s","finalCellScrub","n","compileTemplate","forceBlank","fn","STATE_CHANGE_DEBOUNCE_MS","ConfigManager","#gridConfig","#columns","#fitMode","#lightDomColumnsCache","#originalColumnNodes","#originalConfig","#effectiveConfig","#sourcesChanged","#changeListeners","#lightDomObserver","#stateChangeTimeoutId","#initialColumnState","#callbacks","#lightDomTitle","callbacks","hasColumns","base","#collectAllSources","#cloneConfig","#applyPostMergeOperations","clone","h","#applyTypeDefaultsToColumns","typeDefaults","typeDefault","configColumns","domCols","#mergeShellConfig","shellLightDomTitle","lightDomHeaderContent","toolPanelsMap","panels","b","headerContentsMap","contents","toolbarContentsMap","apiContents","originalConfigContents","configIds","mergedContents","content","plugins","sortStates","#getSortState","index","internalCol","sortState","plugin","pluginState","allColumns","stateMap","updatedColumns","orderA","orderB","sortedByPriority","primarySort","colState","sortMap","visible","allCols","order","columnMap","reordered","#lightDomHandlers","callback","pendingCallbacks","debounceTimer","processPendingCallbacks","mutations","mutation","node","cb","isDevelopment","hostname","booleanCellHTML","formatDateValue","getRowIndexFromCell","parent","getColIndexFromCell","clearCellFocus","getDirection","element","isRTL","resolveRenderer","columnRenderer","adapter","appDefault","resolveFormat","FOCUSABLE_EDITOR_SELECTOR","hasEditingCells","clearEditingState","cellTemplate","rowTemplate","createCellFromTemplate","createRowFromTemplate","invalidateCellCache","renderVisibleRows","start","end","epoch","renderRowHook","needed","colLen","headerRowCount","hasRenderRowPlugins","hasRowHook","rowIndex","rowData","rowEpoch","prevRef","cellCount","structureValid","dataRefChanged","needsExternalRebuild","hasEditing","isActivelyEditedRow","renderInlineRow","fastPatchRow","isChanged","changedRowIds","rowId","hasChangedClass","rowClassFn","prevClasses","cls","newClasses","validClasses","e","children","colsLen","childLen","minLen","focusRow","focusCol","hasCellHook","hasSpecialCols","rowIndexStr","shouldHaveFocus","hasFocus","cellClassFn","cellClasses","cellRenderer","renderedValue","produced","displayStr","formatFn","formatted","gridEl","fragment","colIndex","compiled","tplHolder","viewRenderer","externalView","needsSanitization","spec","placeholder","context","output","blocked","rawTpl","textContent","cellValue","handleRowClick","firstCell","cellEl","focusChanged","ensureCellVisible","handleGridKeyDown","maxRow","maxCol","editing","colType","path","target","isFormField","tag","column","row","detail","activateEvent","legacyEvent","options","rowHeight","container","viewportEl","scrollEl","visibleHeight","y","isEditing","vStart","vEnd","scrollArea","offsets","cellRect","scrollAreaRect","cellLeft","cellRight","visibleLeft","visibleRight","focusTarget","dragState","handleCellMousedown","buildCellMouseEvent","renderRoot","elAtPoint","headerEl","handleMouseDown","event","handleMouseMove","handleMouseUp","setupCellEventDelegation","signal","setupRootEventDelegation","gridElement","defaultComparator","builtInSort","comparator","direction","rA","rB","finalizeSortResult","sortedRows","dir","r","renderHeader","toggleSort","applySort","result","isColumnSortable","isColumnResizable","setIcon","icon","createSortIndicator","active","icons","iconValue","createResizeHandle","handle","setupSortHandlers","appendRendererOutput","headerRow","headerValue","sortDirection","span","hasIdleCallback","scheduleIdle","cancelIdle","createDefaultSpinner","size","spinner","createLoadingContent","wrapper","createLoadingOverlay","overlay","showLoadingOverlay","gridRoot","overlayEl","hideLoadingOverlay","setRowLoadingState","loading","setCellLoadingState","RenderPhase","RenderScheduler","#pendingPhase","#rafHandle","#readyPromise","#readyResolve","#initialReadyResolver","#initialReadyFired","phase","_source","#ensureReadyPromise","#flush","resolver","resolve","createResizeController","resizeState","pendingRaf","prevCursor","prevUserSelect","onMove","delta","width","justFinishedResize","onUp","hadResize","colWidth","startWidth","ANIMATION_ATTR","DURATION_PROPS","DEFAULT_DURATIONS","parseDuration","trimmed","getAnimationDuration","animationType","prop","computed","parsed","animateRowElement","onComplete","duration","animateRow","animateRows","rowIndices","animatedCount","animateRowById","getRowId","createElement","attrs","div","className","button","gridContentTemplate","cloneGridContent","buildGridDOM","contentWrapper","buildShellHeader","titleEl","toolbar","btn","toggleBtn","buildShellBody","body","hasPanel","isSinglePanel","gridContent","panelEl","resizeHandlePosition","panelContent","accordion","panel","sectionClasses","section","headerBtn","iconSpan","titleSpan","chevronSpan","iconToString","createShellState","shouldRenderShellHeader","renderShellHeader","toolPanelIcon","title","hasTitle","iconStr","configContents","stateContents","allContents","hasCustomContent","hasPanels","showSeparator","sortedContents","toolbarHtml","isOpen","parseLightDomShell","headerContents","parseLightDomToolButtons","rendererFactory","toolButtonsContainer","id","contentDef","parseLightDomToolPanels","tooltip","render","adapterRenderer","existingPanel","cleanup","setupShellEventListeners","sectionId","setupToolPanelResize","onResize","shellBody","position","minWidth","startX","maxWidth","isResizing","onMouseMove","newWidth","onMouseUp","finalWidth","onMouseDown","renderCustomToolbarContents","slot","renderHeaderContent","hasLightDomContent","hasPluginContent","contentArea","existingCleanup","renderPanelContent","expandIcon","collapseIcon","panelId","isExpanded","chevron","updateToolbarActiveStates","panelToggle","updatePanelState","prepareForRerender","cleanupShellState","createShellController","initialized","controller","firstPanel","shadow","updateAccordionSectionState","otherId","otherPanel","contentEl","renderAccordionSectionContent","contentId","expanded","buildGridDOMIntoElement","shellConfig","runtimeState","hasShell","lightDomElements","lightDomSelectors","selector","sortedPanels","headerOptions","bodyOptions","shellHeader","STYLE_ELEMENT_ID","baseStyles","pluginStylesMap","getStyleElement","styleEl","updateStyleElement","pluginStyles","addPluginStyles","hasNewStyles","styles","extractGridCssFromDocument","stylesheet","cssText","rule","err","injectStyles","inlineStyles","gridCssText","createTouchScrollState","resetTouchState","cancelMomentum","handleTouchStart","touch","handleTouchMove","currentY","currentX","now","deltaY","deltaX","dt","scrollTop","scrollHeight","clientHeight","maxScrollY","canScrollVertically","canScrollHorizontally","scrollLeft","scrollWidth","clientWidth","maxScrollX","handleTouchEnd","startMomentumScroll","animate","scrollY","scrollX","setupTouchScrollListeners","gridContentEl","KNOWN_COLUMN_PROPERTIES","KNOWN_CONFIG_PROPERTIES","toKebabCase","getImportHint","pluginName","capitalize","hasPlugin","validatePluginProperties","columnProps","configProps","missingPlugins","addError","description","importHint","isConfigProperty","entry","def","errors","fields","fieldList","validatePluginConfigRules","warnings","manifest","pluginConfig","warning","validatePluginDependencies","loadedPlugins","dependencies","dep","requiredPlugin","required","reason","reasonText","validatePluginIncompatibilities","pluginNames","warned","incompatibility","getRowCacheKey","getCachedHeight","cache","setCachedHeight","height","rebuildPositionCache","heightCache","estimatedHeight","getPluginHeight","offset","measured","updateRowHeight","newHeight","heightDiff","getTotalHeight","last","getRowIndexAtOffset","targetOffset","low","high","mid","entryEnd","calculateAverageHeight","defaultHeight","totalHeight","measuredCount","countMeasuredRows","count","measureRenderedRowHeights","rowElements","positionCache","hasChanges","pluginHeight","currentEntry","measuredHeight","averageHeight","computeAverageExcludingPluginRows","totalMeasured","PluginManager","existingPlugin","queryDef","handlers","PluginClass","hasOldHooks","hasNewHook","queryType","otherPlugin","total","beforeRowIndex","adjustedStart","pluginStart","query","responses","response","eventType","listeners","error","focusedCell","left","right","skipScroll","gridStyles","DataGridElement","#instanceCounter","#renderRoot","#initialized","#rows","#configManager","#connected","#pendingUpdate","#pendingUpdateFlags","#scheduler","#scrollRaf","#pendingScrollTop","#hasScrollPlugins","#needsRowHeightMeasurement","#scrollMeasureTimeout","#renderRowHook","#touchState","#eventAbortController","#resizeObserver","#rowHeightObserver","#idleCallbackHandle","#pooledScrollEvent","#pluginManager","#lastPluginsArray","#eventListenersAdded","#scrollAbortController","#scrollAreaEl","#shellState","#shellController","#resizeCleanup","#loading","#loadingRows","#loadingCells","#loadingOverlayEl","#rowIdMap","#baseColumns","oldValue","#queueUpdate","wasLoading","#updateLoadingOverlay","#updateRowLoadingState","cellFields","#updateCellLoadingState","#injectStyles","#updatePluginConfigs","#updateAriaLabels","#processColumns","#rebuildRowModel","newTotalHeight","#calculateTotalSpacerHeight","#rowHeightObserverSetup","#setupRowHeightObserver","#measureRowHeightForPlugins","eventName","#emit","#setup","#applyAnimationConfig","#initializePlugins","pluginsConfig","#injectAllPluginStyles","newPlugins","isLightDom","isApiRegistered","#configureVariableHeights","#collectPluginShellContributions","hadScrollPlugins","#setupScrollListeners","#destroyPlugins","pluginPanels","pluginContents","#getToolPanelRendererFactory","instanceAdapter","#parseLightDom","#render","#afterConnect","#setupLightDomHandlers","#customStyleSheets","newValue","isLoading","defaultOpen","#updateAriaSelection","userRowHeight","hasRowHeightPlugin","#initializePositionCache","#measureRowHeight","firstRow","cells","maxCellHeight","rowRect","scrollSignal","fauxScrollbar","rowsEl","currentScrollTop","rawStart","startRowOffset","evenAlignedStart","subPixelOffset","#onScrollBatched","scrollEvent","scrollAreaForWheel","isHorizontal","#updateCachedGeometry","newFocus","listener","rowIdx","isActiveRow","colIdx","#flushPendingUpdates","flags","#applyGridConfigUpdate","#applyRowsUpdate","#applyColumnsUpdate","#applyFitModeUpdate","#rebuildRowIdMap","#tryResolveRowId","#resolveRowIdOrThrow","hadShell","hadToolPanel","accordionSectionsBefore","nowNeedsShell","nowHasToolPanels","toolPanelCount","#updateShellHeaderInPlace","sourceColumns","visibleCols","hiddenCols","processedColumns","processedFields","originalRows","processedRows","gridConfig","enabled","#renderVisibleRows","#ariaState","#updateAriaCounts","#measureRenderedRowHeights","cellClickEvent","handled","rowClickEvent","headerClickEvent","changes","source","changedFields","updates","anyChanged","#applyColumnState","#pendingShellRefresh","#afterShellRefresh","css","sheet","#updateAdoptedStyleSheets","customSheets","existingSheets","#replaceShellHeaderElement","newHeaderHtml","temp","newHeader","#setupShellListeners","handleShellChange","hadTitle","hadToolButtons","hasToolButtons","handleColumnChange","scrollAreaEl","totalRows","forceRead","virt","fauxScrollHeight","viewportHeight","scrollAreaHeight","viewportHeightDiff","hScrollbarPadding","rowContentHeight","pluginExtraHeight","rowHeightFn","rowIdFn","stats","force","skipAfterRender","iterations","maxIterations","extraHeightBefore","pluginAdjustedStart","targetHeight","accumulatedHeight","minRows","visibleCount","prevStart","prevEnd","extraHeightBeforeStart","PLUGIN_QUERIES","BaseGridPlugin","#abortController","userIcons","durationStr","iconKey","pluginOverride","message","GridClasses","GridDataAttrs","GridSelectors","GridCSSVars","createGrid","queryGrid","DGEvents","PluginEvents","builtInAggregators","acc","sum","customAggregators","aggregatorRegistry","ref","builtInValueAggregators","vals","getValueAggregator","aggFunc","runValueAggregator","values","registerAggregator","unregisterAggregator","getAggregator","runAggregator","listAggregators"],"mappings":"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"}
|