@toolbox-web/grid 0.0.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.
Files changed (87) hide show
  1. package/all.d.ts +3518 -0
  2. package/all.js +3762 -0
  3. package/all.js.map +1 -0
  4. package/index.d.ts +2367 -0
  5. package/index.js +3105 -0
  6. package/index.js.map +1 -0
  7. package/lib/plugins/clipboard/index.js +365 -0
  8. package/lib/plugins/clipboard/index.js.map +1 -0
  9. package/lib/plugins/column-virtualization/index.js +255 -0
  10. package/lib/plugins/column-virtualization/index.js.map +1 -0
  11. package/lib/plugins/context-menu/index.js +341 -0
  12. package/lib/plugins/context-menu/index.js.map +1 -0
  13. package/lib/plugins/export/index.js +305 -0
  14. package/lib/plugins/export/index.js.map +1 -0
  15. package/lib/plugins/filtering/index.js +759 -0
  16. package/lib/plugins/filtering/index.js.map +1 -0
  17. package/lib/plugins/grouping-columns/index.js +283 -0
  18. package/lib/plugins/grouping-columns/index.js.map +1 -0
  19. package/lib/plugins/grouping-rows/index.js +494 -0
  20. package/lib/plugins/grouping-rows/index.js.map +1 -0
  21. package/lib/plugins/master-detail/index.js +303 -0
  22. package/lib/plugins/master-detail/index.js.map +1 -0
  23. package/lib/plugins/multi-sort/index.js +270 -0
  24. package/lib/plugins/multi-sort/index.js.map +1 -0
  25. package/lib/plugins/pinned-columns/index.js +221 -0
  26. package/lib/plugins/pinned-columns/index.js.map +1 -0
  27. package/lib/plugins/pinned-rows/index.js +459 -0
  28. package/lib/plugins/pinned-rows/index.js.map +1 -0
  29. package/lib/plugins/pivot/index.js +326 -0
  30. package/lib/plugins/pivot/index.js.map +1 -0
  31. package/lib/plugins/reorder/index.js +260 -0
  32. package/lib/plugins/reorder/index.js.map +1 -0
  33. package/lib/plugins/selection/index.js +426 -0
  34. package/lib/plugins/selection/index.js.map +1 -0
  35. package/lib/plugins/server-side/index.js +241 -0
  36. package/lib/plugins/server-side/index.js.map +1 -0
  37. package/lib/plugins/tree/index.js +383 -0
  38. package/lib/plugins/tree/index.js.map +1 -0
  39. package/lib/plugins/undo-redo/index.js +289 -0
  40. package/lib/plugins/undo-redo/index.js.map +1 -0
  41. package/lib/plugins/visibility/index.js +430 -0
  42. package/lib/plugins/visibility/index.js.map +1 -0
  43. package/package.json +53 -0
  44. package/themes/dg-theme-contrast.css +43 -0
  45. package/themes/dg-theme-large.css +54 -0
  46. package/themes/dg-theme-standard.css +19 -0
  47. package/themes/dg-theme-vibrant.css +16 -0
  48. package/umd/grid.all.umd.js +660 -0
  49. package/umd/grid.all.umd.js.map +1 -0
  50. package/umd/grid.umd.js +105 -0
  51. package/umd/grid.umd.js.map +1 -0
  52. package/umd/plugins/clipboard.umd.js +9 -0
  53. package/umd/plugins/clipboard.umd.js.map +1 -0
  54. package/umd/plugins/column-virtualization.umd.js +2 -0
  55. package/umd/plugins/column-virtualization.umd.js.map +1 -0
  56. package/umd/plugins/context-menu.umd.js +53 -0
  57. package/umd/plugins/context-menu.umd.js.map +1 -0
  58. package/umd/plugins/export.umd.js +14 -0
  59. package/umd/plugins/export.umd.js.map +1 -0
  60. package/umd/plugins/filtering.umd.js +175 -0
  61. package/umd/plugins/filtering.umd.js.map +1 -0
  62. package/umd/plugins/grouping-columns.umd.js +29 -0
  63. package/umd/plugins/grouping-columns.umd.js.map +1 -0
  64. package/umd/plugins/grouping-rows.umd.js +40 -0
  65. package/umd/plugins/grouping-rows.umd.js.map +1 -0
  66. package/umd/plugins/master-detail.umd.js +27 -0
  67. package/umd/plugins/master-detail.umd.js.map +1 -0
  68. package/umd/plugins/multi-sort.umd.js +26 -0
  69. package/umd/plugins/multi-sort.umd.js.map +1 -0
  70. package/umd/plugins/pinned-columns.umd.js +2 -0
  71. package/umd/plugins/pinned-columns.umd.js.map +1 -0
  72. package/umd/plugins/pinned-rows.umd.js +73 -0
  73. package/umd/plugins/pinned-rows.umd.js.map +1 -0
  74. package/umd/plugins/pivot.umd.js +8 -0
  75. package/umd/plugins/pivot.umd.js.map +1 -0
  76. package/umd/plugins/reorder.umd.js +31 -0
  77. package/umd/plugins/reorder.umd.js.map +1 -0
  78. package/umd/plugins/selection.umd.js +34 -0
  79. package/umd/plugins/selection.umd.js.map +1 -0
  80. package/umd/plugins/server-side.umd.js +2 -0
  81. package/umd/plugins/server-side.umd.js.map +1 -0
  82. package/umd/plugins/tree.umd.js +11 -0
  83. package/umd/plugins/tree.umd.js.map +1 -0
  84. package/umd/plugins/undo-redo.umd.js +2 -0
  85. package/umd/plugins/undo-redo.umd.js.map +1 -0
  86. package/umd/plugins/visibility.umd.js +94 -0
  87. package/umd/plugins/visibility.umd.js.map +1 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../../../../../libs/grid/src/lib/core/plugin/base-plugin.ts","../../../../../../libs/grid/src/lib/core/internal/virtualization.ts","../../../../../../libs/grid/src/lib/plugins/filtering/filter-model.ts","../../../../../../libs/grid/src/lib/plugins/filtering/FilteringPlugin.ts"],"sourcesContent":["/**\r\n * Base Grid Plugin Class\r\n *\r\n * All plugins extend this abstract class.\r\n * Plugins are instantiated per-grid and manage their own state.\r\n */\r\n\r\nimport type { ColumnConfig, ColumnState, HeaderContentDefinition, ToolPanelDefinition } from '../types';\r\n\r\n// Forward declare to avoid circular imports\r\nexport interface GridElement {\r\n shadowRoot: ShadowRoot | null;\r\n rows: any[];\r\n columns: ColumnConfig[];\r\n gridConfig: any;\r\n requestRender(): void;\r\n requestAfterRender(): void;\r\n forceLayout(): Promise<void>;\r\n getPlugin<T extends BaseGridPlugin>(PluginClass: new (...args: any[]) => T): T | undefined;\r\n getPluginByName(name: string): BaseGridPlugin | undefined;\r\n dispatchEvent(event: Event): boolean;\r\n}\r\n\r\n/**\r\n * Keyboard modifier flags\r\n */\r\nexport interface KeyboardModifiers {\r\n ctrl?: boolean;\r\n shift?: boolean;\r\n alt?: boolean;\r\n meta?: boolean;\r\n}\r\n\r\n/**\r\n * Cell coordinates\r\n */\r\nexport interface CellCoords {\r\n row: number;\r\n col: number;\r\n}\r\n\r\n/**\r\n * Cell click event\r\n */\r\nexport interface CellClickEvent {\r\n rowIndex: number;\r\n colIndex: number;\r\n field: string;\r\n value: any;\r\n row: any;\r\n cellEl: HTMLElement;\r\n originalEvent: MouseEvent;\r\n}\r\n\r\n/**\r\n * Row click event\r\n */\r\nexport interface RowClickEvent {\r\n rowIndex: number;\r\n row: any;\r\n rowEl: HTMLElement;\r\n originalEvent: MouseEvent;\r\n}\r\n\r\n/**\r\n * Header click event\r\n */\r\nexport interface HeaderClickEvent {\r\n colIndex: number;\r\n field: string;\r\n column: ColumnConfig;\r\n headerEl: HTMLElement;\r\n originalEvent: MouseEvent;\r\n}\r\n\r\n/**\r\n * Scroll event\r\n */\r\nexport interface ScrollEvent {\r\n scrollTop: number;\r\n scrollLeft: number;\r\n scrollHeight: number;\r\n scrollWidth: number;\r\n clientHeight: number;\r\n clientWidth: number;\r\n originalEvent?: Event;\r\n}\r\n\r\n/**\r\n * Cell mouse event (for drag operations, selection, etc.)\r\n */\r\nexport interface CellMouseEvent {\r\n /** Event type: mousedown, mousemove, or mouseup */\r\n type: 'mousedown' | 'mousemove' | 'mouseup';\r\n /** Row index, undefined if not over a data cell */\r\n rowIndex?: number;\r\n /** Column index, undefined if not over a cell */\r\n colIndex?: number;\r\n /** Field name, undefined if not over a cell */\r\n field?: string;\r\n /** Cell value, undefined if not over a data cell */\r\n value?: unknown;\r\n /** Row data object, undefined if not over a data row */\r\n row?: unknown;\r\n /** Column configuration, undefined if not over a column */\r\n column?: ColumnConfig;\r\n /** The cell element, undefined if not over a cell */\r\n cellElement?: HTMLElement;\r\n /** The row element, undefined if not over a row */\r\n rowElement?: HTMLElement;\r\n /** Whether the event is over a header cell */\r\n isHeader: boolean;\r\n /** Cell coordinates if over a valid data cell */\r\n cell?: CellCoords;\r\n /** The original mouse event */\r\n originalEvent: MouseEvent;\r\n}\r\n\r\n/**\r\n * Context menu parameters\r\n */\r\nexport interface ContextMenuParams {\r\n x: number;\r\n y: number;\r\n rowIndex?: number;\r\n colIndex?: number;\r\n field?: string;\r\n value?: any;\r\n row?: any;\r\n column?: ColumnConfig;\r\n isHeader?: boolean;\r\n}\r\n\r\n/**\r\n * Context menu item\r\n */\r\nexport interface ContextMenuItem {\r\n id: string;\r\n label: string;\r\n icon?: string;\r\n disabled?: boolean;\r\n separator?: boolean;\r\n children?: ContextMenuItem[];\r\n action?: (params: ContextMenuParams) => void;\r\n}\r\n\r\n/**\r\n * Cell render context for plugin cell renderers.\r\n * Provides full context including position and editing state.\r\n *\r\n * Note: This differs from the core `CellRenderContext` in types.ts which is\r\n * simpler and used for column view renderers. This version provides additional\r\n * context needed by plugins that register custom cell renderers.\r\n */\r\nexport interface PluginCellRenderContext {\r\n /** The cell value */\r\n value: any;\r\n /** The field/column key */\r\n field: string;\r\n /** The row data object */\r\n row: any;\r\n /** Row index in the data array */\r\n rowIndex: number;\r\n /** Column index */\r\n colIndex: number;\r\n /** Column configuration */\r\n column: ColumnConfig;\r\n /** Whether the cell is currently in edit mode */\r\n isEditing: boolean;\r\n}\r\n\r\n/**\r\n * Header render context for plugin header renderers.\r\n */\r\nexport interface PluginHeaderRenderContext {\r\n /** Column configuration */\r\n column: ColumnConfig;\r\n /** Column index */\r\n colIndex: number;\r\n}\r\n\r\n/**\r\n * Cell renderer function type for plugins.\r\n */\r\nexport type CellRenderer = (ctx: PluginCellRenderContext) => string | HTMLElement;\r\n\r\n/**\r\n * Header renderer function type for plugins.\r\n */\r\nexport type HeaderRenderer = (ctx: PluginHeaderRenderContext) => string | HTMLElement;\r\n\r\n/**\r\n * Cell editor interface for plugins.\r\n */\r\nexport interface CellEditor {\r\n create(ctx: PluginCellRenderContext, commitFn: (value: any) => void, cancelFn: () => void): HTMLElement;\r\n getValue?(element: HTMLElement): any;\r\n focus?(element: HTMLElement): void;\r\n}\r\n\r\n/**\r\n * Abstract base class for all grid plugins.\r\n *\r\n * @template TConfig - Configuration type for the plugin\r\n */\r\nexport abstract class BaseGridPlugin<TConfig = unknown> {\r\n /** Unique plugin identifier (derived from class name by default) */\r\n abstract readonly name: string;\r\n\r\n /** Plugin version - override in subclass if needed */\r\n readonly version: string = '1.0.0';\r\n\r\n /** CSS styles to inject into the grid's shadow DOM */\r\n readonly styles?: string;\r\n\r\n /** Custom cell renderers keyed by type name */\r\n readonly cellRenderers?: Record<string, CellRenderer>;\r\n\r\n /** Custom header renderers keyed by type name */\r\n readonly headerRenderers?: Record<string, HeaderRenderer>;\r\n\r\n /** Custom cell editors keyed by type name */\r\n readonly cellEditors?: Record<string, CellEditor>;\r\n\r\n /** The grid instance this plugin is attached to */\r\n protected grid!: GridElement;\r\n\r\n /** Plugin configuration - merged with defaults in attach() */\r\n protected config!: TConfig;\r\n\r\n /** User-provided configuration from constructor */\r\n private readonly userConfig: Partial<TConfig>;\r\n\r\n /**\r\n * Default configuration - subclasses should override this getter.\r\n * Note: This must be a getter (not property initializer) for proper inheritance\r\n * since property initializers run after parent constructor.\r\n */\r\n protected get defaultConfig(): Partial<TConfig> {\r\n return {};\r\n }\r\n\r\n constructor(config: Partial<TConfig> = {}) {\r\n this.userConfig = config;\r\n }\r\n\r\n /**\r\n * Called when the plugin is attached to a grid.\r\n * Override to set up event listeners, initialize state, etc.\r\n */\r\n attach(grid: GridElement): void {\r\n this.grid = grid;\r\n // Merge config here (after subclass construction is complete)\r\n this.config = { ...this.defaultConfig, ...this.userConfig } as TConfig;\r\n }\r\n\r\n /**\r\n * Called when the plugin is detached from a grid.\r\n * Override to clean up event listeners, timers, etc.\r\n */\r\n detach(): void {\r\n // Override in subclass\r\n }\r\n\r\n /**\r\n * Get another plugin instance from the same grid.\r\n * Use for inter-plugin communication.\r\n */\r\n protected getPlugin<T extends BaseGridPlugin>(PluginClass: new (...args: any[]) => T): T | undefined {\r\n return this.grid?.getPlugin(PluginClass);\r\n }\r\n\r\n /**\r\n * Emit a custom event from the grid.\r\n */\r\n protected emit<T>(eventName: string, detail: T): void {\r\n this.grid?.dispatchEvent?.(new CustomEvent(eventName, { detail, bubbles: true }));\r\n }\r\n\r\n /**\r\n * Request a re-render of the grid.\r\n */\r\n protected requestRender(): void {\r\n this.grid?.requestRender?.();\r\n }\r\n\r\n /**\r\n * Request a lightweight style update without rebuilding DOM.\r\n * Use this instead of requestRender() when only CSS classes need updating.\r\n */\r\n protected requestAfterRender(): void {\r\n this.grid?.requestAfterRender?.();\r\n }\r\n\r\n /**\r\n * Get the current rows from the grid.\r\n */\r\n protected get rows(): any[] {\r\n return this.grid?.rows ?? [];\r\n }\r\n\r\n /**\r\n * Get the original unfiltered/unprocessed rows from the grid.\r\n * Use this when you need all source data regardless of active filters.\r\n */\r\n protected get sourceRows(): any[] {\r\n return (this.grid as any)?.sourceRows ?? [];\r\n }\r\n\r\n /**\r\n * Get the current columns from the grid.\r\n */\r\n protected get columns(): ColumnConfig[] {\r\n return this.grid?.columns ?? [];\r\n }\r\n\r\n /**\r\n * Get only visible columns from the grid (excludes hidden).\r\n * Use this for rendering that needs to match the grid template.\r\n */\r\n protected get visibleColumns(): ColumnConfig[] {\r\n return (this.grid as any)?.visibleColumns ?? [];\r\n }\r\n\r\n /**\r\n * Get the shadow root of the grid.\r\n */\r\n protected get shadowRoot(): ShadowRoot | null {\r\n return this.grid?.shadowRoot ?? null;\r\n }\r\n\r\n /**\r\n * Log a warning message.\r\n */\r\n protected warn(message: string): void {\r\n console.warn(`[tbw-grid:${this.name}] ${message}`);\r\n }\r\n\r\n // ===== Lifecycle Hooks (override as needed) =====\r\n\r\n /**\r\n * Transform rows before rendering.\r\n * Called during each render cycle before rows are rendered to the DOM.\r\n * Use this to filter, sort, or add computed properties to rows.\r\n *\r\n * @param rows - The current rows array (readonly to encourage returning a new array)\r\n * @returns The modified rows array to render\r\n *\r\n * @example\r\n * ```ts\r\n * processRows(rows: readonly any[]): any[] {\r\n * // Filter out hidden rows\r\n * return rows.filter(row => !row._hidden);\r\n * }\r\n * ```\r\n *\r\n * @example\r\n * ```ts\r\n * processRows(rows: readonly any[]): any[] {\r\n * // Add computed properties\r\n * return rows.map(row => ({\r\n * ...row,\r\n * _fullName: `${row.firstName} ${row.lastName}`\r\n * }));\r\n * }\r\n * ```\r\n */\r\n processRows?(rows: readonly any[]): any[];\r\n\r\n /**\r\n * Transform columns before rendering.\r\n * Called during each render cycle before column headers and cells are rendered.\r\n * Use this to add, remove, or modify column definitions.\r\n *\r\n * @param columns - The current columns array (readonly to encourage returning a new array)\r\n * @returns The modified columns array to render\r\n *\r\n * @example\r\n * ```ts\r\n * processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\r\n * // Add a selection checkbox column\r\n * return [\r\n * { field: '_select', header: '', width: 40 },\r\n * ...columns\r\n * ];\r\n * }\r\n * ```\r\n */\r\n processColumns?(columns: readonly ColumnConfig[]): ColumnConfig[];\r\n\r\n /**\r\n * Called before each render cycle begins.\r\n * Use this to prepare state or cache values needed during rendering.\r\n *\r\n * @example\r\n * ```ts\r\n * beforeRender(): void {\r\n * this.visibleRowCount = this.calculateVisibleRows();\r\n * }\r\n * ```\r\n */\r\n beforeRender?(): void;\r\n\r\n /**\r\n * Called after each render cycle completes.\r\n * Use this for DOM manipulation, adding event listeners to rendered elements,\r\n * or applying visual effects like selection highlights.\r\n *\r\n * @example\r\n * ```ts\r\n * afterRender(): void {\r\n * // Apply selection styling to rendered rows\r\n * const rows = this.shadowRoot?.querySelectorAll('.data-row');\r\n * rows?.forEach((row, i) => {\r\n * row.classList.toggle('selected', this.selectedRows.has(i));\r\n * });\r\n * }\r\n * ```\r\n */\r\n afterRender?(): void;\r\n\r\n /**\r\n * Render a custom row, bypassing the default row rendering.\r\n * Use this for special row types like group headers, detail rows, or footers.\r\n *\r\n * @param row - The row data object\r\n * @param rowEl - The row DOM element to render into\r\n * @param rowIndex - The index of the row in the data array\r\n * @returns `true` if the plugin handled rendering (prevents default), `false`/`void` for default rendering\r\n *\r\n * @example\r\n * ```ts\r\n * renderRow(row: any, rowEl: HTMLElement, rowIndex: number): boolean | void {\r\n * if (row._isGroupHeader) {\r\n * rowEl.innerHTML = `<div class=\"group-header\">${row._groupLabel}</div>`;\r\n * return true; // Handled - skip default rendering\r\n * }\r\n * // Return void to let default rendering proceed\r\n * }\r\n * ```\r\n */\r\n renderRow?(row: any, rowEl: HTMLElement, rowIndex: number): boolean | void;\r\n\r\n // ===== Interaction Hooks (override as needed) =====\r\n\r\n /**\r\n * Handle keyboard events on the grid.\r\n * Called when a key is pressed while the grid or a cell has focus.\r\n *\r\n * @param event - The native KeyboardEvent\r\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\r\n *\r\n * @example\r\n * ```ts\r\n * onKeyDown(event: KeyboardEvent): boolean | void {\r\n * // Handle Ctrl+A for select all\r\n * if (event.ctrlKey && event.key === 'a') {\r\n * this.selectAllRows();\r\n * return true; // Prevent default browser select-all\r\n * }\r\n * }\r\n * ```\r\n */\r\n onKeyDown?(event: KeyboardEvent): boolean | void;\r\n\r\n /**\r\n * Handle cell click events.\r\n * Called when a data cell is clicked (not headers).\r\n *\r\n * @param event - Cell click event with row/column context\r\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\r\n *\r\n * @example\r\n * ```ts\r\n * onCellClick(event: CellClickEvent): boolean | void {\r\n * if (event.field === '_select') {\r\n * this.toggleRowSelection(event.rowIndex);\r\n * return true; // Handled\r\n * }\r\n * }\r\n * ```\r\n */\r\n onCellClick?(event: CellClickEvent): boolean | void;\r\n\r\n /**\r\n * Handle row click events.\r\n * Called when any part of a data row is clicked.\r\n * Note: This is called in addition to onCellClick, not instead of.\r\n *\r\n * @param event - Row click event with row context\r\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\r\n *\r\n * @example\r\n * ```ts\r\n * onRowClick(event: RowClickEvent): boolean | void {\r\n * if (this.config.mode === 'row') {\r\n * this.selectRow(event.rowIndex, event.originalEvent);\r\n * return true;\r\n * }\r\n * }\r\n * ```\r\n */\r\n onRowClick?(event: RowClickEvent): boolean | void;\r\n\r\n /**\r\n * Handle header click events.\r\n * Called when a column header is clicked. Commonly used for sorting.\r\n *\r\n * @param event - Header click event with column context\r\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\r\n *\r\n * @example\r\n * ```ts\r\n * onHeaderClick(event: HeaderClickEvent): boolean | void {\r\n * if (event.column.sortable !== false) {\r\n * this.toggleSort(event.field);\r\n * return true;\r\n * }\r\n * }\r\n * ```\r\n */\r\n onHeaderClick?(event: HeaderClickEvent): boolean | void;\r\n\r\n /**\r\n * Handle scroll events on the grid viewport.\r\n * Called during scrolling. Note: This may be called frequently; debounce if needed.\r\n *\r\n * @param event - Scroll event with scroll position and viewport dimensions\r\n *\r\n * @example\r\n * ```ts\r\n * onScroll(event: ScrollEvent): void {\r\n * // Update sticky column positions\r\n * this.updateStickyPositions(event.scrollLeft);\r\n * }\r\n * ```\r\n */\r\n onScroll?(event: ScrollEvent): void;\r\n\r\n /**\r\n * Handle cell mousedown events.\r\n * Used for initiating drag operations like range selection or column resize.\r\n *\r\n * @param event - Mouse event with cell context\r\n * @returns `true` to indicate drag started (prevents text selection), `false`/`void` otherwise\r\n *\r\n * @example\r\n * ```ts\r\n * onCellMouseDown(event: CellMouseEvent): boolean | void {\r\n * if (event.rowIndex !== undefined && this.config.mode === 'range') {\r\n * this.startDragSelection(event.rowIndex, event.colIndex);\r\n * return true; // Prevent text selection\r\n * }\r\n * }\r\n * ```\r\n */\r\n onCellMouseDown?(event: CellMouseEvent): boolean | void;\r\n\r\n /**\r\n * Handle cell mousemove events during drag operations.\r\n * Only called when a drag is in progress (after mousedown returned true).\r\n *\r\n * @param event - Mouse event with current cell context\r\n * @returns `true` to continue handling the drag, `false`/`void` otherwise\r\n *\r\n * @example\r\n * ```ts\r\n * onCellMouseMove(event: CellMouseEvent): boolean | void {\r\n * if (this.isDragging && event.rowIndex !== undefined) {\r\n * this.extendSelection(event.rowIndex, event.colIndex);\r\n * return true;\r\n * }\r\n * }\r\n * ```\r\n */\r\n onCellMouseMove?(event: CellMouseEvent): boolean | void;\r\n\r\n /**\r\n * Handle cell mouseup events to end drag operations.\r\n *\r\n * @param event - Mouse event with final cell context\r\n * @returns `true` if drag was finalized, `false`/`void` otherwise\r\n *\r\n * @example\r\n * ```ts\r\n * onCellMouseUp(event: CellMouseEvent): boolean | void {\r\n * if (this.isDragging) {\r\n * this.finalizeDragSelection();\r\n * this.isDragging = false;\r\n * return true;\r\n * }\r\n * }\r\n * ```\r\n */\r\n onCellMouseUp?(event: CellMouseEvent): boolean | void;\r\n\r\n /**\r\n * Provide context menu items when right-clicking on the grid.\r\n * Multiple plugins can contribute items; they are merged into a single menu.\r\n *\r\n * @param params - Context about where the menu was triggered (row, column, etc.)\r\n * @returns Array of menu items to display\r\n *\r\n * @example\r\n * ```ts\r\n * getContextMenuItems(params: ContextMenuParams): ContextMenuItem[] {\r\n * if (params.isHeader) {\r\n * return [\r\n * { id: 'sort-asc', label: 'Sort Ascending', action: () => this.sortAsc(params.field) },\r\n * { id: 'sort-desc', label: 'Sort Descending', action: () => this.sortDesc(params.field) },\r\n * ];\r\n * }\r\n * return [\r\n * { id: 'copy', label: 'Copy Cell', action: () => this.copyCell(params) },\r\n * ];\r\n * }\r\n * ```\r\n */\r\n getContextMenuItems?(params: ContextMenuParams): ContextMenuItem[];\r\n\r\n // ===== Column State Hooks (override as needed) =====\r\n\r\n /**\r\n * Contribute plugin-specific state for a column.\r\n * Called by the grid when collecting column state for serialization.\r\n * Plugins can add their own properties to the column state.\r\n *\r\n * @param field - The field name of the column\r\n * @returns Partial column state with plugin-specific properties, or undefined if no state to contribute\r\n *\r\n * @example\r\n * ```ts\r\n * getColumnState(field: string): Partial<ColumnState> | undefined {\r\n * const filterModel = this.filterModels.get(field);\r\n * if (filterModel) {\r\n * // Uses module augmentation to add filter property to ColumnState\r\n * return { filter: filterModel } as Partial<ColumnState>;\r\n * }\r\n * return undefined;\r\n * }\r\n * ```\r\n */\r\n getColumnState?(field: string): Partial<ColumnState> | undefined;\r\n\r\n /**\r\n * Apply plugin-specific state to a column.\r\n * Called by the grid when restoring column state from serialized data.\r\n * Plugins should restore their internal state based on the provided state.\r\n *\r\n * @param field - The field name of the column\r\n * @param state - The column state to apply (may contain plugin-specific properties)\r\n *\r\n * @example\r\n * ```ts\r\n * applyColumnState(field: string, state: ColumnState): void {\r\n * // Check for filter property added via module augmentation\r\n * const filter = (state as any).filter;\r\n * if (filter) {\r\n * this.filterModels.set(field, filter);\r\n * this.applyFilter();\r\n * }\r\n * }\r\n * ```\r\n */\r\n applyColumnState?(field: string, state: ColumnState): void;\r\n\r\n // ===== Shell Integration Hooks (override as needed) =====\r\n\r\n /**\r\n * Register a tool panel for this plugin.\r\n * Return undefined if plugin has no tool panel.\r\n * The shell will create a toolbar toggle button and render the panel content\r\n * when the user opens the panel.\r\n *\r\n * @returns Tool panel definition, or undefined if plugin has no panel\r\n *\r\n * @example\r\n * ```ts\r\n * getToolPanel(): ToolPanelDefinition | undefined {\r\n * return {\r\n * id: 'columns',\r\n * title: 'Columns',\r\n * icon: '☰',\r\n * tooltip: 'Show/hide columns',\r\n * order: 10,\r\n * render: (container) => {\r\n * this.renderColumnList(container);\r\n * return () => this.cleanup();\r\n * },\r\n * };\r\n * }\r\n * ```\r\n */\r\n getToolPanel?(): ToolPanelDefinition | undefined;\r\n\r\n /**\r\n * Register content for the shell header center section.\r\n * Return undefined if plugin has no header content.\r\n * Examples: search input, selection summary, status indicators.\r\n *\r\n * @returns Header content definition, or undefined if plugin has no header content\r\n *\r\n * @example\r\n * ```ts\r\n * getHeaderContent(): HeaderContentDefinition | undefined {\r\n * return {\r\n * id: 'quick-filter',\r\n * order: 10,\r\n * render: (container) => {\r\n * const input = document.createElement('input');\r\n * input.type = 'text';\r\n * input.placeholder = 'Search...';\r\n * input.addEventListener('input', this.handleInput);\r\n * container.appendChild(input);\r\n * return () => input.removeEventListener('input', this.handleInput);\r\n * },\r\n * };\r\n * }\r\n * ```\r\n */\r\n getHeaderContent?(): HeaderContentDefinition | undefined;\r\n}\r\n","/**\n * Row Virtualization Core Logic\n *\n * Pure functions for vertical row virtualization operations.\n * Manages which rows are rendered based on scroll position and viewport size.\n */\n\n/** Result of computing a virtual window */\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 /** Previous scroll position for velocity calculation */\n prevScrollTop?: number;\n}\n\n/**\n * Compute the virtual row window based on scroll position and viewport.\n * Uses directional overscan - renders more rows in the scroll direction.\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 // Simple approach: render a large fixed window centered on current position\n // This is more predictable than velocity-based sizing\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/**\n * Compute the row index from a Y pixel position.\n *\n * @param y - Y position in pixels (relative to content top)\n * @param rowHeight - Height of each row\n * @returns Row index at that position\n */\nexport function getRowIndexFromY(y: number, rowHeight: number): number {\n return Math.floor(y / rowHeight);\n}\n\n/**\n * Compute the Y offset for a given row index.\n *\n * @param rowIndex - Row index\n * @param rowHeight - Height of each row\n * @returns Y position in pixels\n */\nexport function getRowOffsetY(rowIndex: number, rowHeight: number): number {\n return rowIndex * rowHeight;\n}\n\n/**\n * Compute the range of rows that are fully or partially visible in the viewport.\n * This is the range without overscan - just what the user can actually see.\n *\n * @param scrollTop - Current scroll position\n * @param viewportHeight - Height of the viewport\n * @param rowHeight - Height of each row\n * @param totalRows - Total number of rows\n * @returns Object with first and last visible row indices\n */\nexport function getVisibleRowRange(\n scrollTop: number,\n viewportHeight: number,\n rowHeight: number,\n totalRows: number\n): { first: number; last: number } {\n const first = Math.floor(scrollTop / rowHeight);\n let last = Math.ceil((scrollTop + viewportHeight) / rowHeight) - 1;\n if (last >= totalRows) last = totalRows - 1;\n if (first > last) return { first: 0, last: 0 };\n return { first, last };\n}\n\n/**\n * Check if a row is currently rendered in the virtual window.\n *\n * @param rowIndex - Row index to check\n * @param windowStart - Start of the render window\n * @param windowEnd - End of the render window\n * @returns True if the row is rendered\n */\nexport function isRowRendered(rowIndex: number, windowStart: number, windowEnd: number): boolean {\n return rowIndex >= windowStart && rowIndex < windowEnd;\n}\n\n/**\n * Clamp a row index to valid bounds.\n *\n * @param rowIndex - Row index to clamp\n * @param totalRows - Total number of rows\n * @returns Clamped row index (0 to totalRows - 1)\n */\nexport function clampRowIndex(rowIndex: number, totalRows: number): number {\n if (totalRows === 0) return 0;\n if (rowIndex < 0) return 0;\n if (rowIndex >= totalRows) return totalRows - 1;\n return rowIndex;\n}\n\n/**\n * Compute the scroll position needed to bring a row into view.\n *\n * @param rowIndex - Row index to scroll to\n * @param rowHeight - Height of each row\n * @param viewportHeight - Height of the viewport\n * @param currentScrollTop - Current scroll position\n * @returns New scroll position, or null if row is already visible\n */\nexport function computeScrollToRow(\n rowIndex: number,\n rowHeight: number,\n viewportHeight: number,\n currentScrollTop: number\n): number | null {\n const rowTop = rowIndex * rowHeight;\n const rowBottom = rowTop + rowHeight;\n\n // Row is above viewport\n if (rowTop < currentScrollTop) {\n return rowTop;\n }\n\n // Row is below viewport\n if (rowBottom > currentScrollTop + viewportHeight) {\n return rowBottom - viewportHeight;\n }\n\n // Row is already visible\n return null;\n}\n","/**\n * Filter Model Core Logic\n *\n * Pure functions for filtering operations.\n */\n\nimport type { FilterModel } from './types';\n\n/**\n * Check if a single row matches a filter condition.\n *\n * @param row - The row data object\n * @param filter - The filter to apply\n * @param caseSensitive - Whether text comparisons are case sensitive\n * @returns True if the row matches the filter\n */\nexport function matchesFilter(row: Record<string, unknown>, filter: FilterModel, caseSensitive = false): boolean {\n const rawValue = row[filter.field];\n\n // Handle blank/notBlank first - these work on null/undefined/empty\n if (filter.operator === 'blank') {\n return rawValue == null || rawValue === '';\n }\n if (filter.operator === 'notBlank') {\n return rawValue != null && rawValue !== '';\n }\n\n // Null/undefined values don't match other filters\n if (rawValue == null) return false;\n\n // Prepare values for comparison\n const stringValue = String(rawValue);\n const compareValue = caseSensitive ? stringValue : stringValue.toLowerCase();\n const filterValue = caseSensitive ? String(filter.value) : String(filter.value).toLowerCase();\n\n switch (filter.operator) {\n // Text operators\n case 'contains':\n return compareValue.includes(filterValue);\n\n case 'notContains':\n return !compareValue.includes(filterValue);\n\n case 'equals':\n return compareValue === filterValue;\n\n case 'notEquals':\n return compareValue !== filterValue;\n\n case 'startsWith':\n return compareValue.startsWith(filterValue);\n\n case 'endsWith':\n return compareValue.endsWith(filterValue);\n\n // Number/Date operators (use raw numeric values)\n case 'lessThan':\n return Number(rawValue) < Number(filter.value);\n\n case 'lessThanOrEqual':\n return Number(rawValue) <= Number(filter.value);\n\n case 'greaterThan':\n return Number(rawValue) > Number(filter.value);\n\n case 'greaterThanOrEqual':\n return Number(rawValue) >= Number(filter.value);\n\n case 'between':\n return Number(rawValue) >= Number(filter.value) && Number(rawValue) <= Number(filter.valueTo);\n\n // Set operators\n case 'in':\n return Array.isArray(filter.value) && filter.value.includes(rawValue);\n\n case 'notIn':\n return Array.isArray(filter.value) && !filter.value.includes(rawValue);\n\n default:\n return true;\n }\n}\n\n/**\n * Filter rows based on multiple filter conditions (AND logic).\n * All filters must match for a row to be included.\n *\n * @param rows - The rows to filter\n * @param filters - Array of filters to apply\n * @param caseSensitive - Whether text comparisons are case sensitive\n * @returns Filtered rows\n */\nexport function filterRows<T extends Record<string, unknown>>(\n rows: T[],\n filters: FilterModel[],\n caseSensitive = false\n): T[] {\n if (!filters.length) return rows;\n return rows.filter((row) => filters.every((f) => matchesFilter(row, f, caseSensitive)));\n}\n\n/**\n * Compute a cache key for a set of filters.\n * Used for memoization of filter results.\n *\n * @param filters - Array of filters\n * @returns Stable string key for the filter set\n */\nexport function computeFilterCacheKey(filters: FilterModel[]): string {\n return JSON.stringify(\n filters.map((f) => ({\n field: f.field,\n operator: f.operator,\n value: f.value,\n valueTo: f.valueTo,\n }))\n );\n}\n\n/**\n * Extract unique values from a field across all rows.\n * Useful for populating \"set\" filter dropdowns.\n *\n * @param rows - The rows to extract values from\n * @param field - The field name\n * @returns Sorted array of unique non-null values\n */\nexport function getUniqueValues<T extends Record<string, unknown>>(rows: T[], field: string): unknown[] {\n const values = new Set<unknown>();\n for (const row of rows) {\n const value = row[field];\n if (value != null) {\n values.add(value);\n }\n }\n return [...values].sort((a, b) => {\n // Handle mixed types gracefully\n if (typeof a === 'number' && typeof b === 'number') {\n return a - b;\n }\n return String(a).localeCompare(String(b));\n });\n}\n","/**\n * Filtering Plugin (Class-based)\n *\n * Provides comprehensive filtering functionality for tbw-grid.\n * Supports text, number, date, set, and boolean filters with caching.\n * Includes UI with filter buttons in headers and dropdown filter panels.\n */\n\nimport { BaseGridPlugin, type GridElement } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig, ColumnState } from '../../core/types';\nimport { computeVirtualWindow, shouldBypassVirtualization } from '../../core/internal/virtualization';\nimport { computeFilterCacheKey, filterRows, getUniqueValues } from './filter-model';\nimport type { FilterChangeDetail, FilterConfig, FilterModel, FilterPanelParams } from './types';\n\n/** Global styles for filter panel (rendered in document.body) */\nconst filterPanelStyles = `\n.tbw-filter-panel {\n position: fixed;\n background: var(--tbw-filter-panel-bg, var(--tbw-color-panel-bg, light-dark(#eeeeee, #222222)));\n color: var(--tbw-filter-panel-fg, var(--tbw-color-fg, light-dark(#222222, #eeeeee)));\n border: 1px solid var(--tbw-filter-panel-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n border-radius: var(--tbw-filter-panel-radius, var(--tbw-border-radius, 4px));\n box-shadow: 0 4px 16px var(--tbw-filter-panel-shadow, var(--tbw-color-shadow, light-dark(rgba(0,0,0,0.1), rgba(0,0,0,0.3))));\n padding: 12px;\n z-index: 10000;\n min-width: 200px;\n max-width: 280px;\n max-height: 350px;\n display: flex;\n flex-direction: column;\n font-family: var(--tbw-font-family, system-ui, sans-serif);\n font-size: var(--tbw-font-size, 13px);\n}\n\n.tbw-filter-search {\n margin-bottom: 8px;\n}\n\n.tbw-filter-search-input {\n width: 100%;\n padding: 6px 10px;\n background: var(--tbw-filter-input-bg, var(--tbw-color-bg, transparent));\n color: inherit;\n border: 1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n border-radius: var(--tbw-filter-input-radius, 4px);\n font-size: inherit;\n box-sizing: border-box;\n}\n\n.tbw-filter-search-input:focus {\n outline: none;\n border-color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n box-shadow: 0 0 0 2px rgba(from var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6)) r g b / 15%);\n}\n\n.tbw-filter-actions {\n display: flex;\n padding: 4px 2px;\n margin-bottom: 8px;\n border-bottom: 1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n}\n\n.tbw-filter-action-btn {\n background: transparent;\n border: none;\n color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n cursor: pointer;\n font-size: 12px;\n padding: 2px 0;\n}\n\n.tbw-filter-action-btn:hover {\n text-decoration: underline;\n}\n\n.tbw-filter-values {\n flex: 1;\n overflow-y: auto;\n margin-bottom: 8px;\n max-height: 180px;\n position: relative;\n}\n\n.tbw-filter-values-spacer {\n width: 1px;\n}\n\n.tbw-filter-values-content {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n}\n\n.tbw-filter-value-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 4px 2px;\n cursor: pointer;\n border-radius: 3px;\n}\n\n.tbw-filter-value-item:hover {\n background: var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)));\n}\n\n.tbw-filter-checkbox {\n margin: 0;\n cursor: pointer;\n accent-color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n}\n\n.tbw-filter-no-match {\n color: var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));\n padding: 8px 0;\n text-align: center;\n font-style: italic;\n}\n\n.tbw-filter-buttons {\n display: flex;\n gap: 8px;\n padding-top: 8px;\n border-top: 1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n}\n\n.tbw-filter-apply-btn {\n flex: 1;\n padding: 6px 12px;\n background: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n color: var(--tbw-filter-accent-fg, var(--tbw-color-accent-fg, light-dark(#ffffff, #000000)));\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n}\n\n.tbw-filter-apply-btn:hover {\n filter: brightness(0.9);\n}\n\n.tbw-filter-clear-btn {\n flex: 1;\n padding: 6px 12px;\n background: transparent;\n color: var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));\n border: 1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));\n border-radius: 4px;\n cursor: pointer;\n font-size: 13px;\n}\n\n.tbw-filter-clear-btn:hover {\n background: var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)));\n}\n`;\n\n/**\n * Filtering Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new FilteringPlugin({ enabled: true, debounceMs: 300 })\n * ```\n */\nexport class FilteringPlugin extends BaseGridPlugin<FilterConfig> {\n readonly name = 'filtering';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<FilterConfig> {\n return {\n enabled: true,\n debounceMs: 300,\n caseSensitive: false,\n trimInput: true,\n useWorker: true,\n };\n }\n\n // ===== Internal State =====\n private filters: Map<string, FilterModel> = new Map();\n private cachedResult: unknown[] | null = null;\n private cacheKey: string | null = null;\n private openPanelField: string | null = null;\n private panelElement: HTMLElement | null = null;\n private searchText: Map<string, string> = new Map();\n private excludedValues: Map<string, Set<unknown>> = new Map();\n private documentClickHandler: ((e: MouseEvent) => void) | null = null;\n private globalStylesInjected = false;\n\n // Virtualization constants for filter value list\n private static readonly LIST_ITEM_HEIGHT = 28;\n private static readonly LIST_OVERSCAN = 3;\n private static readonly LIST_BYPASS_THRESHOLD = 50; // Don't virtualize if < 50 items\n\n // ===== Lifecycle =====\n\n override attach(grid: GridElement): void {\n super.attach(grid);\n this.injectGlobalStyles();\n }\n\n override detach(): void {\n this.filters.clear();\n this.cachedResult = null;\n this.cacheKey = null;\n this.openPanelField = null;\n if (this.panelElement) {\n this.panelElement.remove();\n this.panelElement = null;\n }\n this.searchText.clear();\n this.excludedValues.clear();\n this.removeDocumentClickHandler();\n }\n\n // ===== Hooks =====\n\n override processRows(rows: readonly unknown[]): unknown[] {\n const filterList = [...this.filters.values()];\n if (!filterList.length) return [...rows];\n\n // Check cache\n const newCacheKey = computeFilterCacheKey(filterList);\n if (this.cacheKey === newCacheKey && this.cachedResult) {\n return this.cachedResult;\n }\n\n // Filter rows synchronously (worker support can be added later)\n const result = filterRows([...rows] as Record<string, unknown>[], filterList, this.config.caseSensitive);\n\n // Update cache\n this.cachedResult = result;\n this.cacheKey = newCacheKey;\n\n return result;\n }\n\n override afterRender(): void {\n if (!this.config.enabled) return;\n\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n // Find all header cells (using part attribute, not class)\n const headerCells = shadowRoot.querySelectorAll('[part~=\"header-cell\"]');\n headerCells.forEach((cell) => {\n const colIndex = cell.getAttribute('data-col');\n if (colIndex === null) return;\n\n const col = this.columns[parseInt(colIndex, 10)] as ColumnConfig;\n if (!col || col.filterable === false) return;\n\n // Skip if button already exists\n if (cell.querySelector('.tbw-filter-btn')) return;\n\n const field = col.field;\n if (!field) return;\n\n // Create filter button\n const filterBtn = document.createElement('button');\n filterBtn.className = 'tbw-filter-btn';\n filterBtn.setAttribute('aria-label', `Filter ${col.header ?? field}`);\n filterBtn.innerHTML = `<svg viewBox=\"0 0 16 16\" width=\"12\" height=\"12\"><path fill=\"currentColor\" d=\"M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z\"/></svg>`;\n\n // Mark button as active if filter exists\n if (this.filters.has(field)) {\n filterBtn.classList.add('active');\n cell.classList.add('filtered');\n }\n\n filterBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n this.toggleFilterPanel(field, col, filterBtn);\n });\n\n // Append to header cell\n cell.appendChild(filterBtn);\n });\n }\n\n // ===== Public API =====\n\n /**\n * Set a filter on a specific field.\n * Pass null to remove the filter.\n */\n setFilter(field: string, filter: Omit<FilterModel, 'field'> | null): void {\n if (filter === null) {\n this.filters.delete(field);\n } else {\n this.filters.set(field, { ...filter, field });\n }\n // Invalidate cache\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0, // Will be accurate after processRows\n });\n this.requestRender();\n }\n\n /**\n * Get the current filter for a field.\n */\n getFilter(field: string): FilterModel | undefined {\n return this.filters.get(field);\n }\n\n /**\n * Get all active filters.\n */\n getFilters(): FilterModel[] {\n return [...this.filters.values()];\n }\n\n /**\n * Alias for getFilters() to match functional API naming.\n */\n getFilterModel(): FilterModel[] {\n return this.getFilters();\n }\n\n /**\n * Set filters from an array (replaces all existing filters).\n */\n setFilterModel(filters: FilterModel[]): void {\n this.filters.clear();\n for (const filter of filters) {\n this.filters.set(filter.field, filter);\n }\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0,\n });\n this.requestRender();\n }\n\n /**\n * Clear all filters.\n */\n clearAllFilters(): void {\n this.filters.clear();\n this.excludedValues.clear();\n this.searchText.clear();\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [],\n filteredRowCount: this.rows.length,\n });\n this.requestRender();\n }\n\n /**\n * Clear filter for a specific field.\n */\n clearFieldFilter(field: string): void {\n this.filters.delete(field);\n this.excludedValues.delete(field);\n this.searchText.delete(field);\n\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0,\n });\n this.requestRender();\n }\n\n /**\n * Check if a field has an active filter.\n */\n isFieldFiltered(field: string): boolean {\n return this.filters.has(field);\n }\n\n /**\n * Get the count of filtered rows (from cache).\n */\n getFilteredRowCount(): number {\n return this.cachedResult?.length ?? this.rows.length;\n }\n\n /**\n * Get all active filters (alias for getFilters).\n */\n getActiveFilters(): FilterModel[] {\n return this.getFilters();\n }\n\n /**\n * Get unique values for a field (for set filter dropdowns).\n * Uses sourceRows to include all values regardless of current filter.\n */\n getUniqueValues(field: string): unknown[] {\n return getUniqueValues(this.sourceRows as Record<string, unknown>[], field);\n }\n\n // ===== Private Methods =====\n\n /**\n * Inject global styles for filter panel (rendered in document.body)\n */\n private injectGlobalStyles(): void {\n if (this.globalStylesInjected) return;\n if (document.getElementById('tbw-filter-panel-styles')) {\n this.globalStylesInjected = true;\n return;\n }\n const style = document.createElement('style');\n style.id = 'tbw-filter-panel-styles';\n style.textContent = filterPanelStyles;\n document.head.appendChild(style);\n this.globalStylesInjected = true;\n }\n\n /**\n * Toggle the filter panel for a field\n */\n private toggleFilterPanel(field: string, column: ColumnConfig, buttonEl: HTMLElement): void {\n // Close if already open\n if (this.openPanelField === field) {\n this.closeFilterPanel();\n return;\n }\n\n // Close any existing panel\n this.closeFilterPanel();\n\n // Create panel\n const panel = document.createElement('div');\n panel.className = 'tbw-filter-panel';\n this.panelElement = panel;\n this.openPanelField = field;\n\n // Get unique values for this field (from source rows, not filtered)\n const uniqueValues = getUniqueValues(this.sourceRows as Record<string, unknown>[], field);\n\n // Get current excluded values or initialize empty\n let excludedSet = this.excludedValues.get(field);\n if (!excludedSet) {\n excludedSet = new Set();\n this.excludedValues.set(field, excludedSet);\n }\n\n // Get current search text\n const currentSearchText = this.searchText.get(field) ?? '';\n\n // Create panel params for custom renderer\n const params: FilterPanelParams = {\n field,\n column,\n uniqueValues,\n excludedValues: excludedSet,\n searchText: currentSearchText,\n applySetFilter: (excluded: unknown[]) => {\n this.applySetFilter(field, excluded);\n this.closeFilterPanel();\n },\n applyTextFilter: (operator, value, valueTo) => {\n this.applyTextFilter(field, operator, value, valueTo);\n this.closeFilterPanel();\n },\n clearFilter: () => {\n this.clearFieldFilter(field);\n this.closeFilterPanel();\n },\n closePanel: () => this.closeFilterPanel(),\n };\n\n // Use custom renderer or default\n // Custom renderer can return undefined to fall back to default panel for specific columns\n let usedCustomRenderer = false;\n if (this.config.filterPanelRenderer) {\n const result = this.config.filterPanelRenderer(panel, params);\n // If renderer added content to panel, it handled rendering\n usedCustomRenderer = panel.children.length > 0;\n }\n if (!usedCustomRenderer) {\n this.renderDefaultFilterPanel(panel, params, uniqueValues, excludedSet);\n }\n\n // Position and append to body\n document.body.appendChild(panel);\n this.positionPanel(panel, buttonEl);\n\n // Add global click handler to close on outside click\n const handler = (e: MouseEvent) => {\n if (!panel.contains(e.target as Node) && e.target !== buttonEl) {\n this.closeFilterPanel();\n }\n };\n this.documentClickHandler = handler;\n // Defer to next tick to avoid immediate close\n setTimeout(() => {\n document.addEventListener('click', handler);\n }, 0);\n }\n\n /**\n * Close the filter panel\n */\n private closeFilterPanel(): void {\n if (this.panelElement) {\n this.panelElement.remove();\n this.panelElement = null;\n }\n this.openPanelField = null;\n this.removeDocumentClickHandler();\n }\n\n /**\n * Remove the document click handler\n */\n private removeDocumentClickHandler(): void {\n if (this.documentClickHandler) {\n document.removeEventListener('click', this.documentClickHandler);\n this.documentClickHandler = null;\n }\n }\n\n /**\n * Position the panel below the button\n */\n private positionPanel(panel: HTMLElement, buttonEl: HTMLElement): void {\n const rect = buttonEl.getBoundingClientRect();\n panel.style.position = 'fixed';\n panel.style.top = `${rect.bottom + 4}px`;\n panel.style.left = `${rect.left}px`;\n\n // Adjust if overflows right edge\n requestAnimationFrame(() => {\n const panelRect = panel.getBoundingClientRect();\n if (panelRect.right > window.innerWidth - 8) {\n panel.style.left = `${window.innerWidth - panelRect.width - 8}px`;\n }\n // Adjust if overflows bottom\n if (panelRect.bottom > window.innerHeight - 8) {\n panel.style.top = `${rect.top - panelRect.height - 4}px`;\n }\n });\n }\n\n /**\n * Render the default filter panel content\n */\n private renderDefaultFilterPanel(\n panel: HTMLElement,\n params: FilterPanelParams,\n uniqueValues: unknown[],\n excludedValues: Set<unknown>\n ): void {\n const { field } = params;\n\n // Search input\n const searchContainer = document.createElement('div');\n searchContainer.className = 'tbw-filter-search';\n\n const searchInput = document.createElement('input');\n searchInput.type = 'text';\n searchInput.placeholder = 'Search...';\n searchInput.className = 'tbw-filter-search-input';\n searchInput.value = this.searchText.get(field) ?? '';\n searchContainer.appendChild(searchInput);\n panel.appendChild(searchContainer);\n\n // Select All tristate checkbox\n const actionsRow = document.createElement('div');\n actionsRow.className = 'tbw-filter-actions';\n\n const selectAllLabel = document.createElement('label');\n selectAllLabel.className = 'tbw-filter-value-item';\n selectAllLabel.style.padding = '0';\n selectAllLabel.style.margin = '0';\n\n const selectAllCheckbox = document.createElement('input');\n selectAllCheckbox.type = 'checkbox';\n selectAllCheckbox.className = 'tbw-filter-checkbox';\n\n const selectAllText = document.createElement('span');\n selectAllText.textContent = 'Select All';\n\n selectAllLabel.appendChild(selectAllCheckbox);\n selectAllLabel.appendChild(selectAllText);\n actionsRow.appendChild(selectAllLabel);\n\n // Update tristate checkbox based on checkState\n const updateSelectAllState = () => {\n const values = [...checkState.values()];\n const allChecked = values.every((v) => v);\n const noneChecked = values.every((v) => !v);\n\n selectAllCheckbox.checked = allChecked;\n selectAllCheckbox.indeterminate = !allChecked && !noneChecked;\n };\n\n // Toggle all on click\n selectAllCheckbox.addEventListener('change', () => {\n const newState = selectAllCheckbox.checked;\n for (const key of checkState.keys()) {\n checkState.set(key, newState);\n }\n updateSelectAllState();\n renderVisibleItems();\n });\n\n panel.appendChild(actionsRow);\n\n // Values container with virtualization support\n const valuesContainer = document.createElement('div');\n valuesContainer.className = 'tbw-filter-values';\n\n // Spacer for virtual height\n const spacer = document.createElement('div');\n spacer.className = 'tbw-filter-values-spacer';\n valuesContainer.appendChild(spacer);\n\n // Content container positioned absolutely\n const contentContainer = document.createElement('div');\n contentContainer.className = 'tbw-filter-values-content';\n valuesContainer.appendChild(contentContainer);\n\n // Track current check state for values (persists across virtualizations)\n const checkState = new Map<string, boolean>();\n uniqueValues.forEach((value) => {\n const key = value == null ? '__null__' : String(value);\n checkState.set(key, !excludedValues.has(value));\n });\n\n // Initialize select all state\n updateSelectAllState();\n\n // Filtered values cache\n let filteredValues: unknown[] = [];\n\n // Create a single checkbox item element\n const createItem = (value: unknown, index: number): HTMLElement => {\n const strValue = value == null ? '(Blank)' : String(value);\n const key = value == null ? '__null__' : String(value);\n\n const item = document.createElement('label');\n item.className = 'tbw-filter-value-item';\n item.style.position = 'absolute';\n item.style.top = `${index * FilteringPlugin.LIST_ITEM_HEIGHT}px`;\n item.style.left = '0';\n item.style.right = '0';\n item.style.height = `${FilteringPlugin.LIST_ITEM_HEIGHT}px`;\n item.style.boxSizing = 'border-box';\n\n const checkbox = document.createElement('input');\n checkbox.type = 'checkbox';\n checkbox.className = 'tbw-filter-checkbox';\n checkbox.checked = checkState.get(key) ?? true;\n checkbox.dataset.value = key;\n\n // Sync check state on change and update tristate checkbox\n checkbox.addEventListener('change', () => {\n checkState.set(key, checkbox.checked);\n updateSelectAllState();\n });\n\n const label = document.createElement('span');\n label.textContent = strValue;\n\n item.appendChild(checkbox);\n item.appendChild(label);\n return item;\n };\n\n // Render visible items using virtualization\n const renderVisibleItems = () => {\n const totalItems = filteredValues.length;\n const viewportHeight = valuesContainer.clientHeight;\n const scrollTop = valuesContainer.scrollTop;\n\n // Set total height for scrollbar\n spacer.style.height = `${totalItems * FilteringPlugin.LIST_ITEM_HEIGHT}px`;\n\n // Bypass virtualization for small lists\n if (shouldBypassVirtualization(totalItems, FilteringPlugin.LIST_BYPASS_THRESHOLD / 3)) {\n contentContainer.innerHTML = '';\n contentContainer.style.transform = 'translateY(0px)';\n filteredValues.forEach((value, idx) => {\n contentContainer.appendChild(createItem(value, idx));\n });\n return;\n }\n\n // Use computeVirtualWindow for real-scroll virtualization\n const window = computeVirtualWindow({\n totalRows: totalItems,\n viewportHeight,\n scrollTop,\n rowHeight: FilteringPlugin.LIST_ITEM_HEIGHT,\n overscan: FilteringPlugin.LIST_OVERSCAN,\n });\n\n // Position content container\n contentContainer.style.transform = `translateY(${window.offsetY}px)`;\n\n // Clear and render visible items\n contentContainer.innerHTML = '';\n for (let i = window.start; i < window.end; i++) {\n contentContainer.appendChild(createItem(filteredValues[i], i - window.start));\n }\n };\n\n // Filter and re-render values\n const renderValues = (filterText: string) => {\n const lowerFilter = filterText.toLowerCase();\n\n // Filter the unique values\n filteredValues = uniqueValues.filter((value) => {\n const strValue = value == null ? '(Blank)' : String(value);\n return !filterText || strValue.toLowerCase().includes(lowerFilter);\n });\n\n if (filteredValues.length === 0) {\n spacer.style.height = '0px';\n contentContainer.innerHTML = '';\n const noMatch = document.createElement('div');\n noMatch.className = 'tbw-filter-no-match';\n noMatch.textContent = 'No matching values';\n contentContainer.appendChild(noMatch);\n return;\n }\n\n renderVisibleItems();\n };\n\n // Scroll handler for virtualization\n valuesContainer.addEventListener(\n 'scroll',\n () => {\n if (filteredValues.length > 0) {\n renderVisibleItems();\n }\n },\n { passive: true }\n );\n\n renderValues(searchInput.value);\n panel.appendChild(valuesContainer);\n\n // Debounced search\n let debounceTimer: ReturnType<typeof setTimeout>;\n searchInput.addEventListener('input', () => {\n clearTimeout(debounceTimer);\n debounceTimer = setTimeout(() => {\n this.searchText.set(field, searchInput.value);\n renderValues(searchInput.value);\n }, this.config.debounceMs ?? 150);\n });\n\n // Apply/Clear buttons\n const buttonRow = document.createElement('div');\n buttonRow.className = 'tbw-filter-buttons';\n\n const applyBtn = document.createElement('button');\n applyBtn.className = 'tbw-filter-apply-btn';\n applyBtn.textContent = 'Apply';\n applyBtn.addEventListener('click', () => {\n // Read from checkState map (works with virtualization)\n const excluded: unknown[] = [];\n for (const [key, isChecked] of checkState) {\n if (!isChecked) {\n if (key === '__null__') {\n excluded.push(null);\n } else {\n // Try to match original value type\n const original = uniqueValues.find((v) => String(v) === key);\n excluded.push(original !== undefined ? original : key);\n }\n }\n }\n params.applySetFilter(excluded);\n });\n buttonRow.appendChild(applyBtn);\n\n const clearBtn = document.createElement('button');\n clearBtn.className = 'tbw-filter-clear-btn';\n clearBtn.textContent = 'Clear Filter';\n clearBtn.addEventListener('click', () => {\n params.clearFilter();\n });\n buttonRow.appendChild(clearBtn);\n\n panel.appendChild(buttonRow);\n }\n\n /**\n * Apply a set filter (exclude values)\n */\n private applySetFilter(field: string, excluded: unknown[]): void {\n // Store excluded values\n this.excludedValues.set(field, new Set(excluded));\n\n if (excluded.length === 0) {\n // No exclusions = no filter\n this.filters.delete(field);\n } else {\n // Create \"notIn\" filter\n this.filters.set(field, {\n field,\n type: 'set',\n operator: 'notIn',\n value: excluded,\n });\n }\n\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0,\n });\n this.requestRender();\n }\n\n /**\n * Apply a text filter\n */\n private applyTextFilter(field: string, operator: FilterModel['operator'], value: string, valueTo?: string): void {\n this.filters.set(field, {\n field,\n type: 'text',\n operator,\n value,\n valueTo,\n });\n\n this.cachedResult = null;\n this.cacheKey = null;\n\n this.emit<FilterChangeDetail>('filter-change', {\n filters: [...this.filters.values()],\n filteredRowCount: 0,\n });\n this.requestRender();\n }\n\n // ===== Column State Hooks =====\n\n /**\n * Return filter state for a column if it has an active filter.\n */\n override getColumnState(field: string): Partial<ColumnState> | undefined {\n const filterModel = this.filters.get(field);\n if (!filterModel) return undefined;\n\n return {\n filter: {\n type: filterModel.type,\n operator: filterModel.operator,\n value: filterModel.value,\n valueTo: filterModel.valueTo,\n },\n };\n }\n\n /**\n * Apply filter state from column state.\n */\n override applyColumnState(field: string, state: ColumnState): void {\n // Only process if the column has filter state\n if (!state.filter) {\n this.filters.delete(field);\n return;\n }\n\n // Reconstruct the FilterModel from the stored state\n const filterModel: FilterModel = {\n field,\n type: state.filter.type,\n operator: state.filter.operator as FilterModel['operator'],\n value: state.filter.value,\n valueTo: state.filter.valueTo,\n };\n\n this.filters.set(field, filterModel);\n // Invalidate cache so filter is reapplied\n this.cachedResult = null;\n this.cacheKey = null;\n }\n\n // ===== Styles =====\n\n override readonly styles = `\n .header-cell.filtered::before {\n content: '';\n position: absolute;\n top: 4px;\n right: 4px;\n width: 6px;\n height: 6px;\n background: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n border-radius: 50%;\n }\n .tbw-filter-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n background: transparent;\n border: none;\n cursor: pointer;\n padding: 2px;\n margin-left: 4px;\n opacity: 0.4;\n transition: opacity 0.15s;\n color: inherit;\n vertical-align: middle;\n }\n .tbw-filter-btn:hover,\n .tbw-filter-btn.active {\n opacity: 1;\n }\n .tbw-filter-btn.active {\n color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));\n }\n `;\n}\n"],"names":["BaseGridPlugin","config","grid","PluginClass","eventName","detail","message","computeVirtualWindow","params","totalRows","viewportHeight","scrollTop","rowHeight","overscan","visibleRows","start","end","shouldBypassVirtualization","threshold","matchesFilter","row","filter","caseSensitive","rawValue","stringValue","compareValue","filterValue","filterRows","rows","filters","f","computeFilterCacheKey","getUniqueValues","field","values","value","a","b","filterPanelStyles","FilteringPlugin","filterList","newCacheKey","result","shadowRoot","cell","colIndex","col","filterBtn","e","style","column","buttonEl","panel","uniqueValues","excludedSet","currentSearchText","excluded","operator","valueTo","usedCustomRenderer","handler","rect","panelRect","excludedValues","searchContainer","searchInput","actionsRow","selectAllLabel","selectAllCheckbox","selectAllText","updateSelectAllState","checkState","allChecked","v","noneChecked","newState","key","renderVisibleItems","valuesContainer","spacer","contentContainer","filteredValues","createItem","index","strValue","item","checkbox","label","totalItems","idx","window","i","renderValues","filterText","lowerFilter","noMatch","debounceTimer","buttonRow","applyBtn","isChecked","original","clearBtn","filterModel","state"],"mappings":"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"}
@@ -0,0 +1,283 @@
1
+ class g {
2
+ /** Plugin version - override in subclass if needed */
3
+ version = "1.0.0";
4
+ /** CSS styles to inject into the grid's shadow DOM */
5
+ styles;
6
+ /** Custom cell renderers keyed by type name */
7
+ cellRenderers;
8
+ /** Custom header renderers keyed by type name */
9
+ headerRenderers;
10
+ /** Custom cell editors keyed by type name */
11
+ cellEditors;
12
+ /** The grid instance this plugin is attached to */
13
+ grid;
14
+ /** Plugin configuration - merged with defaults in attach() */
15
+ config;
16
+ /** User-provided configuration from constructor */
17
+ userConfig;
18
+ /**
19
+ * Default configuration - subclasses should override this getter.
20
+ * Note: This must be a getter (not property initializer) for proper inheritance
21
+ * since property initializers run after parent constructor.
22
+ */
23
+ get defaultConfig() {
24
+ return {};
25
+ }
26
+ constructor(e = {}) {
27
+ this.userConfig = e;
28
+ }
29
+ /**
30
+ * Called when the plugin is attached to a grid.
31
+ * Override to set up event listeners, initialize state, etc.
32
+ */
33
+ attach(e) {
34
+ this.grid = e, this.config = { ...this.defaultConfig, ...this.userConfig };
35
+ }
36
+ /**
37
+ * Called when the plugin is detached from a grid.
38
+ * Override to clean up event listeners, timers, etc.
39
+ */
40
+ detach() {
41
+ }
42
+ /**
43
+ * Get another plugin instance from the same grid.
44
+ * Use for inter-plugin communication.
45
+ */
46
+ getPlugin(e) {
47
+ return this.grid?.getPlugin(e);
48
+ }
49
+ /**
50
+ * Emit a custom event from the grid.
51
+ */
52
+ emit(e, r) {
53
+ this.grid?.dispatchEvent?.(new CustomEvent(e, { detail: r, bubbles: !0 }));
54
+ }
55
+ /**
56
+ * Request a re-render of the grid.
57
+ */
58
+ requestRender() {
59
+ this.grid?.requestRender?.();
60
+ }
61
+ /**
62
+ * Request a lightweight style update without rebuilding DOM.
63
+ * Use this instead of requestRender() when only CSS classes need updating.
64
+ */
65
+ requestAfterRender() {
66
+ this.grid?.requestAfterRender?.();
67
+ }
68
+ /**
69
+ * Get the current rows from the grid.
70
+ */
71
+ get rows() {
72
+ return this.grid?.rows ?? [];
73
+ }
74
+ /**
75
+ * Get the original unfiltered/unprocessed rows from the grid.
76
+ * Use this when you need all source data regardless of active filters.
77
+ */
78
+ get sourceRows() {
79
+ return this.grid?.sourceRows ?? [];
80
+ }
81
+ /**
82
+ * Get the current columns from the grid.
83
+ */
84
+ get columns() {
85
+ return this.grid?.columns ?? [];
86
+ }
87
+ /**
88
+ * Get only visible columns from the grid (excludes hidden).
89
+ * Use this for rendering that needs to match the grid template.
90
+ */
91
+ get visibleColumns() {
92
+ return this.grid?.visibleColumns ?? [];
93
+ }
94
+ /**
95
+ * Get the shadow root of the grid.
96
+ */
97
+ get shadowRoot() {
98
+ return this.grid?.shadowRoot ?? null;
99
+ }
100
+ /**
101
+ * Log a warning message.
102
+ */
103
+ warn(e) {
104
+ console.warn(`[tbw-grid:${this.name}] ${e}`);
105
+ }
106
+ }
107
+ function a(l) {
108
+ if (!l.length) return [];
109
+ const e = /* @__PURE__ */ new Map(), r = [], t = (s, i) => {
110
+ if (!i.length) return;
111
+ const n = r[r.length - 1];
112
+ if (n && n.implicit && n.firstIndex + n.columns.length === s) {
113
+ n.columns.push(...i);
114
+ return;
115
+ }
116
+ r.push({
117
+ id: "__implicit__" + s,
118
+ label: void 0,
119
+ columns: i,
120
+ firstIndex: s,
121
+ implicit: !0
122
+ });
123
+ };
124
+ let u = [], o = 0;
125
+ return l.forEach((s, i) => {
126
+ const n = s.group;
127
+ if (!n) {
128
+ u.length === 0 && (o = i), u.push(s);
129
+ return;
130
+ }
131
+ u.length && (t(o, u.slice()), u = []);
132
+ const c = typeof n == "string" ? n : n.id;
133
+ let d = e.get(c);
134
+ d || (d = {
135
+ id: c,
136
+ label: typeof n == "string" ? void 0 : n.label,
137
+ columns: [],
138
+ firstIndex: i
139
+ }, e.set(c, d), r.push(d)), d.columns.push(s);
140
+ }), u.length && t(o, u), r.length === 1 && r[0].implicit && r[0].columns.length === l.length ? [] : r;
141
+ }
142
+ function h(l, e, r) {
143
+ if (!e.length || !l) return;
144
+ const t = /* @__PURE__ */ new Map();
145
+ for (const o of e)
146
+ for (const s of o.columns)
147
+ s?.field && t.set(s.field, o.id);
148
+ const u = Array.from(l.querySelectorAll(".cell[data-field]"));
149
+ u.forEach((o) => {
150
+ const s = o.getAttribute("data-field") || "", i = t.get(s);
151
+ i && (o.classList.add("grouped"), o.getAttribute("data-group") || o.setAttribute("data-group", i));
152
+ });
153
+ for (const o of e) {
154
+ const s = o.columns[o.columns.length - 1], i = u.find((n) => n.getAttribute("data-field") === s.field);
155
+ i && i.classList.add("group-end");
156
+ }
157
+ }
158
+ function p(l, e) {
159
+ if (l.length === 0) return null;
160
+ const r = document.createElement("div");
161
+ r.className = "header-group-row", r.setAttribute("role", "row");
162
+ for (const t of l) {
163
+ const u = t.firstIndex != null ? t.firstIndex : e.findIndex((n) => t.columns.includes(n)), o = String(t.id).startsWith("__implicit__"), s = o ? "" : t.label || t.id, i = document.createElement("div");
164
+ i.className = "cell header-group-cell", o && i.classList.add("implicit-group"), i.setAttribute("data-group", String(t.id)), i.style.gridColumn = `${u + 1} / span ${t.columns.length}`, i.textContent = s, r.appendChild(i);
165
+ }
166
+ return r;
167
+ }
168
+ function f(l) {
169
+ return l.some((e) => e.group != null);
170
+ }
171
+ class m extends g {
172
+ name = "groupingColumns";
173
+ version = "1.0.0";
174
+ get defaultConfig() {
175
+ return {
176
+ enabled: !0,
177
+ showGroupBorders: !0
178
+ };
179
+ }
180
+ // ===== Internal State =====
181
+ groups = [];
182
+ isActive = !1;
183
+ // ===== Lifecycle =====
184
+ detach() {
185
+ this.groups = [], this.isActive = !1;
186
+ }
187
+ // ===== Static Detection =====
188
+ /**
189
+ * Auto-detect column groups from column configuration.
190
+ */
191
+ static detect(e, r) {
192
+ const t = r?.columns;
193
+ return Array.isArray(t) ? f(t) : !1;
194
+ }
195
+ // ===== Hooks =====
196
+ processColumns(e) {
197
+ if (!this.config.enabled)
198
+ return this.isActive = !1, this.groups = [], [...e];
199
+ const r = a(e);
200
+ return r.length === 0 ? (this.isActive = !1, this.groups = [], [...e]) : (this.isActive = !0, this.groups = r, [...e]);
201
+ }
202
+ afterRender() {
203
+ if (!this.isActive || this.groups.length === 0) {
204
+ const s = this.shadowRoot?.querySelector(".header")?.querySelector(".header-group-row");
205
+ s && s.remove();
206
+ return;
207
+ }
208
+ const e = this.shadowRoot?.querySelector(".header");
209
+ if (!e) return;
210
+ const r = e.querySelector(".header-group-row");
211
+ r && r.remove();
212
+ const t = p(this.groups, this.columns);
213
+ if (t) {
214
+ const o = e.querySelector(".header-row");
215
+ o ? e.insertBefore(t, o) : e.appendChild(t);
216
+ }
217
+ const u = e.querySelector(".header-row");
218
+ u && h(u, this.groups, this.columns);
219
+ }
220
+ // ===== Public API =====
221
+ /**
222
+ * Check if column groups are active.
223
+ * @returns Whether grouping is active
224
+ */
225
+ isGroupingActive() {
226
+ return this.isActive;
227
+ }
228
+ /**
229
+ * Get the computed column groups.
230
+ * @returns Array of column groups
231
+ */
232
+ getGroups() {
233
+ return this.groups;
234
+ }
235
+ /**
236
+ * Get columns in a specific group.
237
+ * @param groupId - The group ID to find
238
+ * @returns Array of columns in the group
239
+ */
240
+ getGroupColumns(e) {
241
+ const r = this.groups.find((t) => t.id === e);
242
+ return r ? r.columns : [];
243
+ }
244
+ /**
245
+ * Refresh column groups (recompute from current columns).
246
+ */
247
+ refresh() {
248
+ this.requestRender();
249
+ }
250
+ // ===== Styles =====
251
+ styles = `
252
+ .header-group-row {
253
+ display: grid;
254
+ grid-auto-flow: column;
255
+ background: var(--tbw-grouping-columns-header-bg, var(--tbw-color-header-bg));
256
+ border-bottom: 1px solid var(--tbw-grouping-columns-border, var(--tbw-color-border));
257
+ }
258
+ .header-group-cell {
259
+ display: flex;
260
+ align-items: center;
261
+ justify-content: center;
262
+ padding: 4px 8px;
263
+ font-weight: 600;
264
+ font-size: 0.9em;
265
+ text-transform: uppercase;
266
+ letter-spacing: 0.5px;
267
+ border-right: 1px solid var(--tbw-grouping-columns-border, var(--tbw-color-border));
268
+ }
269
+ .header-group-cell:last-child {
270
+ border-right: none;
271
+ }
272
+ .header-row .cell.grouped {
273
+ border-top: none;
274
+ }
275
+ .header-row .cell.group-end {
276
+ border-right: 2px solid var(--tbw-grouping-columns-separator, var(--tbw-color-border-strong));
277
+ }
278
+ `;
279
+ }
280
+ export {
281
+ m as GroupingColumnsPlugin
282
+ };
283
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../../../../../libs/grid/src/lib/core/plugin/base-plugin.ts","../../../../../../libs/grid/src/lib/plugins/grouping-columns/grouping-columns.ts","../../../../../../libs/grid/src/lib/plugins/grouping-columns/GroupingColumnsPlugin.ts"],"sourcesContent":["/**\r\n * Base Grid Plugin Class\r\n *\r\n * All plugins extend this abstract class.\r\n * Plugins are instantiated per-grid and manage their own state.\r\n */\r\n\r\nimport type { ColumnConfig, ColumnState, HeaderContentDefinition, ToolPanelDefinition } from '../types';\r\n\r\n// Forward declare to avoid circular imports\r\nexport interface GridElement {\r\n shadowRoot: ShadowRoot | null;\r\n rows: any[];\r\n columns: ColumnConfig[];\r\n gridConfig: any;\r\n requestRender(): void;\r\n requestAfterRender(): void;\r\n forceLayout(): Promise<void>;\r\n getPlugin<T extends BaseGridPlugin>(PluginClass: new (...args: any[]) => T): T | undefined;\r\n getPluginByName(name: string): BaseGridPlugin | undefined;\r\n dispatchEvent(event: Event): boolean;\r\n}\r\n\r\n/**\r\n * Keyboard modifier flags\r\n */\r\nexport interface KeyboardModifiers {\r\n ctrl?: boolean;\r\n shift?: boolean;\r\n alt?: boolean;\r\n meta?: boolean;\r\n}\r\n\r\n/**\r\n * Cell coordinates\r\n */\r\nexport interface CellCoords {\r\n row: number;\r\n col: number;\r\n}\r\n\r\n/**\r\n * Cell click event\r\n */\r\nexport interface CellClickEvent {\r\n rowIndex: number;\r\n colIndex: number;\r\n field: string;\r\n value: any;\r\n row: any;\r\n cellEl: HTMLElement;\r\n originalEvent: MouseEvent;\r\n}\r\n\r\n/**\r\n * Row click event\r\n */\r\nexport interface RowClickEvent {\r\n rowIndex: number;\r\n row: any;\r\n rowEl: HTMLElement;\r\n originalEvent: MouseEvent;\r\n}\r\n\r\n/**\r\n * Header click event\r\n */\r\nexport interface HeaderClickEvent {\r\n colIndex: number;\r\n field: string;\r\n column: ColumnConfig;\r\n headerEl: HTMLElement;\r\n originalEvent: MouseEvent;\r\n}\r\n\r\n/**\r\n * Scroll event\r\n */\r\nexport interface ScrollEvent {\r\n scrollTop: number;\r\n scrollLeft: number;\r\n scrollHeight: number;\r\n scrollWidth: number;\r\n clientHeight: number;\r\n clientWidth: number;\r\n originalEvent?: Event;\r\n}\r\n\r\n/**\r\n * Cell mouse event (for drag operations, selection, etc.)\r\n */\r\nexport interface CellMouseEvent {\r\n /** Event type: mousedown, mousemove, or mouseup */\r\n type: 'mousedown' | 'mousemove' | 'mouseup';\r\n /** Row index, undefined if not over a data cell */\r\n rowIndex?: number;\r\n /** Column index, undefined if not over a cell */\r\n colIndex?: number;\r\n /** Field name, undefined if not over a cell */\r\n field?: string;\r\n /** Cell value, undefined if not over a data cell */\r\n value?: unknown;\r\n /** Row data object, undefined if not over a data row */\r\n row?: unknown;\r\n /** Column configuration, undefined if not over a column */\r\n column?: ColumnConfig;\r\n /** The cell element, undefined if not over a cell */\r\n cellElement?: HTMLElement;\r\n /** The row element, undefined if not over a row */\r\n rowElement?: HTMLElement;\r\n /** Whether the event is over a header cell */\r\n isHeader: boolean;\r\n /** Cell coordinates if over a valid data cell */\r\n cell?: CellCoords;\r\n /** The original mouse event */\r\n originalEvent: MouseEvent;\r\n}\r\n\r\n/**\r\n * Context menu parameters\r\n */\r\nexport interface ContextMenuParams {\r\n x: number;\r\n y: number;\r\n rowIndex?: number;\r\n colIndex?: number;\r\n field?: string;\r\n value?: any;\r\n row?: any;\r\n column?: ColumnConfig;\r\n isHeader?: boolean;\r\n}\r\n\r\n/**\r\n * Context menu item\r\n */\r\nexport interface ContextMenuItem {\r\n id: string;\r\n label: string;\r\n icon?: string;\r\n disabled?: boolean;\r\n separator?: boolean;\r\n children?: ContextMenuItem[];\r\n action?: (params: ContextMenuParams) => void;\r\n}\r\n\r\n/**\r\n * Cell render context for plugin cell renderers.\r\n * Provides full context including position and editing state.\r\n *\r\n * Note: This differs from the core `CellRenderContext` in types.ts which is\r\n * simpler and used for column view renderers. This version provides additional\r\n * context needed by plugins that register custom cell renderers.\r\n */\r\nexport interface PluginCellRenderContext {\r\n /** The cell value */\r\n value: any;\r\n /** The field/column key */\r\n field: string;\r\n /** The row data object */\r\n row: any;\r\n /** Row index in the data array */\r\n rowIndex: number;\r\n /** Column index */\r\n colIndex: number;\r\n /** Column configuration */\r\n column: ColumnConfig;\r\n /** Whether the cell is currently in edit mode */\r\n isEditing: boolean;\r\n}\r\n\r\n/**\r\n * Header render context for plugin header renderers.\r\n */\r\nexport interface PluginHeaderRenderContext {\r\n /** Column configuration */\r\n column: ColumnConfig;\r\n /** Column index */\r\n colIndex: number;\r\n}\r\n\r\n/**\r\n * Cell renderer function type for plugins.\r\n */\r\nexport type CellRenderer = (ctx: PluginCellRenderContext) => string | HTMLElement;\r\n\r\n/**\r\n * Header renderer function type for plugins.\r\n */\r\nexport type HeaderRenderer = (ctx: PluginHeaderRenderContext) => string | HTMLElement;\r\n\r\n/**\r\n * Cell editor interface for plugins.\r\n */\r\nexport interface CellEditor {\r\n create(ctx: PluginCellRenderContext, commitFn: (value: any) => void, cancelFn: () => void): HTMLElement;\r\n getValue?(element: HTMLElement): any;\r\n focus?(element: HTMLElement): void;\r\n}\r\n\r\n/**\r\n * Abstract base class for all grid plugins.\r\n *\r\n * @template TConfig - Configuration type for the plugin\r\n */\r\nexport abstract class BaseGridPlugin<TConfig = unknown> {\r\n /** Unique plugin identifier (derived from class name by default) */\r\n abstract readonly name: string;\r\n\r\n /** Plugin version - override in subclass if needed */\r\n readonly version: string = '1.0.0';\r\n\r\n /** CSS styles to inject into the grid's shadow DOM */\r\n readonly styles?: string;\r\n\r\n /** Custom cell renderers keyed by type name */\r\n readonly cellRenderers?: Record<string, CellRenderer>;\r\n\r\n /** Custom header renderers keyed by type name */\r\n readonly headerRenderers?: Record<string, HeaderRenderer>;\r\n\r\n /** Custom cell editors keyed by type name */\r\n readonly cellEditors?: Record<string, CellEditor>;\r\n\r\n /** The grid instance this plugin is attached to */\r\n protected grid!: GridElement;\r\n\r\n /** Plugin configuration - merged with defaults in attach() */\r\n protected config!: TConfig;\r\n\r\n /** User-provided configuration from constructor */\r\n private readonly userConfig: Partial<TConfig>;\r\n\r\n /**\r\n * Default configuration - subclasses should override this getter.\r\n * Note: This must be a getter (not property initializer) for proper inheritance\r\n * since property initializers run after parent constructor.\r\n */\r\n protected get defaultConfig(): Partial<TConfig> {\r\n return {};\r\n }\r\n\r\n constructor(config: Partial<TConfig> = {}) {\r\n this.userConfig = config;\r\n }\r\n\r\n /**\r\n * Called when the plugin is attached to a grid.\r\n * Override to set up event listeners, initialize state, etc.\r\n */\r\n attach(grid: GridElement): void {\r\n this.grid = grid;\r\n // Merge config here (after subclass construction is complete)\r\n this.config = { ...this.defaultConfig, ...this.userConfig } as TConfig;\r\n }\r\n\r\n /**\r\n * Called when the plugin is detached from a grid.\r\n * Override to clean up event listeners, timers, etc.\r\n */\r\n detach(): void {\r\n // Override in subclass\r\n }\r\n\r\n /**\r\n * Get another plugin instance from the same grid.\r\n * Use for inter-plugin communication.\r\n */\r\n protected getPlugin<T extends BaseGridPlugin>(PluginClass: new (...args: any[]) => T): T | undefined {\r\n return this.grid?.getPlugin(PluginClass);\r\n }\r\n\r\n /**\r\n * Emit a custom event from the grid.\r\n */\r\n protected emit<T>(eventName: string, detail: T): void {\r\n this.grid?.dispatchEvent?.(new CustomEvent(eventName, { detail, bubbles: true }));\r\n }\r\n\r\n /**\r\n * Request a re-render of the grid.\r\n */\r\n protected requestRender(): void {\r\n this.grid?.requestRender?.();\r\n }\r\n\r\n /**\r\n * Request a lightweight style update without rebuilding DOM.\r\n * Use this instead of requestRender() when only CSS classes need updating.\r\n */\r\n protected requestAfterRender(): void {\r\n this.grid?.requestAfterRender?.();\r\n }\r\n\r\n /**\r\n * Get the current rows from the grid.\r\n */\r\n protected get rows(): any[] {\r\n return this.grid?.rows ?? [];\r\n }\r\n\r\n /**\r\n * Get the original unfiltered/unprocessed rows from the grid.\r\n * Use this when you need all source data regardless of active filters.\r\n */\r\n protected get sourceRows(): any[] {\r\n return (this.grid as any)?.sourceRows ?? [];\r\n }\r\n\r\n /**\r\n * Get the current columns from the grid.\r\n */\r\n protected get columns(): ColumnConfig[] {\r\n return this.grid?.columns ?? [];\r\n }\r\n\r\n /**\r\n * Get only visible columns from the grid (excludes hidden).\r\n * Use this for rendering that needs to match the grid template.\r\n */\r\n protected get visibleColumns(): ColumnConfig[] {\r\n return (this.grid as any)?.visibleColumns ?? [];\r\n }\r\n\r\n /**\r\n * Get the shadow root of the grid.\r\n */\r\n protected get shadowRoot(): ShadowRoot | null {\r\n return this.grid?.shadowRoot ?? null;\r\n }\r\n\r\n /**\r\n * Log a warning message.\r\n */\r\n protected warn(message: string): void {\r\n console.warn(`[tbw-grid:${this.name}] ${message}`);\r\n }\r\n\r\n // ===== Lifecycle Hooks (override as needed) =====\r\n\r\n /**\r\n * Transform rows before rendering.\r\n * Called during each render cycle before rows are rendered to the DOM.\r\n * Use this to filter, sort, or add computed properties to rows.\r\n *\r\n * @param rows - The current rows array (readonly to encourage returning a new array)\r\n * @returns The modified rows array to render\r\n *\r\n * @example\r\n * ```ts\r\n * processRows(rows: readonly any[]): any[] {\r\n * // Filter out hidden rows\r\n * return rows.filter(row => !row._hidden);\r\n * }\r\n * ```\r\n *\r\n * @example\r\n * ```ts\r\n * processRows(rows: readonly any[]): any[] {\r\n * // Add computed properties\r\n * return rows.map(row => ({\r\n * ...row,\r\n * _fullName: `${row.firstName} ${row.lastName}`\r\n * }));\r\n * }\r\n * ```\r\n */\r\n processRows?(rows: readonly any[]): any[];\r\n\r\n /**\r\n * Transform columns before rendering.\r\n * Called during each render cycle before column headers and cells are rendered.\r\n * Use this to add, remove, or modify column definitions.\r\n *\r\n * @param columns - The current columns array (readonly to encourage returning a new array)\r\n * @returns The modified columns array to render\r\n *\r\n * @example\r\n * ```ts\r\n * processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\r\n * // Add a selection checkbox column\r\n * return [\r\n * { field: '_select', header: '', width: 40 },\r\n * ...columns\r\n * ];\r\n * }\r\n * ```\r\n */\r\n processColumns?(columns: readonly ColumnConfig[]): ColumnConfig[];\r\n\r\n /**\r\n * Called before each render cycle begins.\r\n * Use this to prepare state or cache values needed during rendering.\r\n *\r\n * @example\r\n * ```ts\r\n * beforeRender(): void {\r\n * this.visibleRowCount = this.calculateVisibleRows();\r\n * }\r\n * ```\r\n */\r\n beforeRender?(): void;\r\n\r\n /**\r\n * Called after each render cycle completes.\r\n * Use this for DOM manipulation, adding event listeners to rendered elements,\r\n * or applying visual effects like selection highlights.\r\n *\r\n * @example\r\n * ```ts\r\n * afterRender(): void {\r\n * // Apply selection styling to rendered rows\r\n * const rows = this.shadowRoot?.querySelectorAll('.data-row');\r\n * rows?.forEach((row, i) => {\r\n * row.classList.toggle('selected', this.selectedRows.has(i));\r\n * });\r\n * }\r\n * ```\r\n */\r\n afterRender?(): void;\r\n\r\n /**\r\n * Render a custom row, bypassing the default row rendering.\r\n * Use this for special row types like group headers, detail rows, or footers.\r\n *\r\n * @param row - The row data object\r\n * @param rowEl - The row DOM element to render into\r\n * @param rowIndex - The index of the row in the data array\r\n * @returns `true` if the plugin handled rendering (prevents default), `false`/`void` for default rendering\r\n *\r\n * @example\r\n * ```ts\r\n * renderRow(row: any, rowEl: HTMLElement, rowIndex: number): boolean | void {\r\n * if (row._isGroupHeader) {\r\n * rowEl.innerHTML = `<div class=\"group-header\">${row._groupLabel}</div>`;\r\n * return true; // Handled - skip default rendering\r\n * }\r\n * // Return void to let default rendering proceed\r\n * }\r\n * ```\r\n */\r\n renderRow?(row: any, rowEl: HTMLElement, rowIndex: number): boolean | void;\r\n\r\n // ===== Interaction Hooks (override as needed) =====\r\n\r\n /**\r\n * Handle keyboard events on the grid.\r\n * Called when a key is pressed while the grid or a cell has focus.\r\n *\r\n * @param event - The native KeyboardEvent\r\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\r\n *\r\n * @example\r\n * ```ts\r\n * onKeyDown(event: KeyboardEvent): boolean | void {\r\n * // Handle Ctrl+A for select all\r\n * if (event.ctrlKey && event.key === 'a') {\r\n * this.selectAllRows();\r\n * return true; // Prevent default browser select-all\r\n * }\r\n * }\r\n * ```\r\n */\r\n onKeyDown?(event: KeyboardEvent): boolean | void;\r\n\r\n /**\r\n * Handle cell click events.\r\n * Called when a data cell is clicked (not headers).\r\n *\r\n * @param event - Cell click event with row/column context\r\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\r\n *\r\n * @example\r\n * ```ts\r\n * onCellClick(event: CellClickEvent): boolean | void {\r\n * if (event.field === '_select') {\r\n * this.toggleRowSelection(event.rowIndex);\r\n * return true; // Handled\r\n * }\r\n * }\r\n * ```\r\n */\r\n onCellClick?(event: CellClickEvent): boolean | void;\r\n\r\n /**\r\n * Handle row click events.\r\n * Called when any part of a data row is clicked.\r\n * Note: This is called in addition to onCellClick, not instead of.\r\n *\r\n * @param event - Row click event with row context\r\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\r\n *\r\n * @example\r\n * ```ts\r\n * onRowClick(event: RowClickEvent): boolean | void {\r\n * if (this.config.mode === 'row') {\r\n * this.selectRow(event.rowIndex, event.originalEvent);\r\n * return true;\r\n * }\r\n * }\r\n * ```\r\n */\r\n onRowClick?(event: RowClickEvent): boolean | void;\r\n\r\n /**\r\n * Handle header click events.\r\n * Called when a column header is clicked. Commonly used for sorting.\r\n *\r\n * @param event - Header click event with column context\r\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\r\n *\r\n * @example\r\n * ```ts\r\n * onHeaderClick(event: HeaderClickEvent): boolean | void {\r\n * if (event.column.sortable !== false) {\r\n * this.toggleSort(event.field);\r\n * return true;\r\n * }\r\n * }\r\n * ```\r\n */\r\n onHeaderClick?(event: HeaderClickEvent): boolean | void;\r\n\r\n /**\r\n * Handle scroll events on the grid viewport.\r\n * Called during scrolling. Note: This may be called frequently; debounce if needed.\r\n *\r\n * @param event - Scroll event with scroll position and viewport dimensions\r\n *\r\n * @example\r\n * ```ts\r\n * onScroll(event: ScrollEvent): void {\r\n * // Update sticky column positions\r\n * this.updateStickyPositions(event.scrollLeft);\r\n * }\r\n * ```\r\n */\r\n onScroll?(event: ScrollEvent): void;\r\n\r\n /**\r\n * Handle cell mousedown events.\r\n * Used for initiating drag operations like range selection or column resize.\r\n *\r\n * @param event - Mouse event with cell context\r\n * @returns `true` to indicate drag started (prevents text selection), `false`/`void` otherwise\r\n *\r\n * @example\r\n * ```ts\r\n * onCellMouseDown(event: CellMouseEvent): boolean | void {\r\n * if (event.rowIndex !== undefined && this.config.mode === 'range') {\r\n * this.startDragSelection(event.rowIndex, event.colIndex);\r\n * return true; // Prevent text selection\r\n * }\r\n * }\r\n * ```\r\n */\r\n onCellMouseDown?(event: CellMouseEvent): boolean | void;\r\n\r\n /**\r\n * Handle cell mousemove events during drag operations.\r\n * Only called when a drag is in progress (after mousedown returned true).\r\n *\r\n * @param event - Mouse event with current cell context\r\n * @returns `true` to continue handling the drag, `false`/`void` otherwise\r\n *\r\n * @example\r\n * ```ts\r\n * onCellMouseMove(event: CellMouseEvent): boolean | void {\r\n * if (this.isDragging && event.rowIndex !== undefined) {\r\n * this.extendSelection(event.rowIndex, event.colIndex);\r\n * return true;\r\n * }\r\n * }\r\n * ```\r\n */\r\n onCellMouseMove?(event: CellMouseEvent): boolean | void;\r\n\r\n /**\r\n * Handle cell mouseup events to end drag operations.\r\n *\r\n * @param event - Mouse event with final cell context\r\n * @returns `true` if drag was finalized, `false`/`void` otherwise\r\n *\r\n * @example\r\n * ```ts\r\n * onCellMouseUp(event: CellMouseEvent): boolean | void {\r\n * if (this.isDragging) {\r\n * this.finalizeDragSelection();\r\n * this.isDragging = false;\r\n * return true;\r\n * }\r\n * }\r\n * ```\r\n */\r\n onCellMouseUp?(event: CellMouseEvent): boolean | void;\r\n\r\n /**\r\n * Provide context menu items when right-clicking on the grid.\r\n * Multiple plugins can contribute items; they are merged into a single menu.\r\n *\r\n * @param params - Context about where the menu was triggered (row, column, etc.)\r\n * @returns Array of menu items to display\r\n *\r\n * @example\r\n * ```ts\r\n * getContextMenuItems(params: ContextMenuParams): ContextMenuItem[] {\r\n * if (params.isHeader) {\r\n * return [\r\n * { id: 'sort-asc', label: 'Sort Ascending', action: () => this.sortAsc(params.field) },\r\n * { id: 'sort-desc', label: 'Sort Descending', action: () => this.sortDesc(params.field) },\r\n * ];\r\n * }\r\n * return [\r\n * { id: 'copy', label: 'Copy Cell', action: () => this.copyCell(params) },\r\n * ];\r\n * }\r\n * ```\r\n */\r\n getContextMenuItems?(params: ContextMenuParams): ContextMenuItem[];\r\n\r\n // ===== Column State Hooks (override as needed) =====\r\n\r\n /**\r\n * Contribute plugin-specific state for a column.\r\n * Called by the grid when collecting column state for serialization.\r\n * Plugins can add their own properties to the column state.\r\n *\r\n * @param field - The field name of the column\r\n * @returns Partial column state with plugin-specific properties, or undefined if no state to contribute\r\n *\r\n * @example\r\n * ```ts\r\n * getColumnState(field: string): Partial<ColumnState> | undefined {\r\n * const filterModel = this.filterModels.get(field);\r\n * if (filterModel) {\r\n * // Uses module augmentation to add filter property to ColumnState\r\n * return { filter: filterModel } as Partial<ColumnState>;\r\n * }\r\n * return undefined;\r\n * }\r\n * ```\r\n */\r\n getColumnState?(field: string): Partial<ColumnState> | undefined;\r\n\r\n /**\r\n * Apply plugin-specific state to a column.\r\n * Called by the grid when restoring column state from serialized data.\r\n * Plugins should restore their internal state based on the provided state.\r\n *\r\n * @param field - The field name of the column\r\n * @param state - The column state to apply (may contain plugin-specific properties)\r\n *\r\n * @example\r\n * ```ts\r\n * applyColumnState(field: string, state: ColumnState): void {\r\n * // Check for filter property added via module augmentation\r\n * const filter = (state as any).filter;\r\n * if (filter) {\r\n * this.filterModels.set(field, filter);\r\n * this.applyFilter();\r\n * }\r\n * }\r\n * ```\r\n */\r\n applyColumnState?(field: string, state: ColumnState): void;\r\n\r\n // ===== Shell Integration Hooks (override as needed) =====\r\n\r\n /**\r\n * Register a tool panel for this plugin.\r\n * Return undefined if plugin has no tool panel.\r\n * The shell will create a toolbar toggle button and render the panel content\r\n * when the user opens the panel.\r\n *\r\n * @returns Tool panel definition, or undefined if plugin has no panel\r\n *\r\n * @example\r\n * ```ts\r\n * getToolPanel(): ToolPanelDefinition | undefined {\r\n * return {\r\n * id: 'columns',\r\n * title: 'Columns',\r\n * icon: '☰',\r\n * tooltip: 'Show/hide columns',\r\n * order: 10,\r\n * render: (container) => {\r\n * this.renderColumnList(container);\r\n * return () => this.cleanup();\r\n * },\r\n * };\r\n * }\r\n * ```\r\n */\r\n getToolPanel?(): ToolPanelDefinition | undefined;\r\n\r\n /**\r\n * Register content for the shell header center section.\r\n * Return undefined if plugin has no header content.\r\n * Examples: search input, selection summary, status indicators.\r\n *\r\n * @returns Header content definition, or undefined if plugin has no header content\r\n *\r\n * @example\r\n * ```ts\r\n * getHeaderContent(): HeaderContentDefinition | undefined {\r\n * return {\r\n * id: 'quick-filter',\r\n * order: 10,\r\n * render: (container) => {\r\n * const input = document.createElement('input');\r\n * input.type = 'text';\r\n * input.placeholder = 'Search...';\r\n * input.addEventListener('input', this.handleInput);\r\n * container.appendChild(input);\r\n * return () => input.removeEventListener('input', this.handleInput);\r\n * },\r\n * };\r\n * }\r\n * ```\r\n */\r\n getHeaderContent?(): HeaderContentDefinition | undefined;\r\n}\r\n","/**\n * Column Groups Core Logic\n *\n * Pure functions for computing and managing column header groups.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ColumnGroup, ColumnGroupInternal } from './types';\n\n/**\n * Compute column groups from column configuration.\n * Handles explicit groups (via column.group) and creates implicit groups for ungrouped columns.\n *\n * @param columns - Array of column configurations\n * @returns Array of column groups, or empty if no meaningful groups\n */\nexport function computeColumnGroups<T>(columns: ColumnConfig<T>[]): ColumnGroup<T>[] {\n if (!columns.length) return [];\n\n const explicitMap = new Map<string, ColumnGroupInternal<T>>();\n const groupsOrdered: ColumnGroupInternal<T>[] = [];\n\n // Helper to push unnamed implicit group for a run of ungrouped columns\n const pushImplicit = (startIdx: number, cols: ColumnConfig<T>[]) => {\n if (!cols.length) return;\n // Merge with previous implicit group if adjacent to reduce noise\n const prev = groupsOrdered[groupsOrdered.length - 1];\n if (prev && prev.implicit && prev.firstIndex + prev.columns.length === startIdx) {\n prev.columns.push(...cols);\n return;\n }\n groupsOrdered.push({\n id: '__implicit__' + startIdx,\n label: undefined,\n columns: cols,\n firstIndex: startIdx,\n implicit: true,\n });\n };\n\n let run: ColumnConfig<T>[] = [];\n let runStart = 0;\n\n columns.forEach((col, idx) => {\n const g: any = (col as any).group;\n if (!g) {\n if (run.length === 0) runStart = idx;\n run.push(col);\n return;\n }\n // Close any pending implicit run\n if (run.length) {\n pushImplicit(runStart, run.slice());\n run = [];\n }\n const id = typeof g === 'string' ? g : g.id;\n let group = explicitMap.get(id);\n if (!group) {\n group = {\n id,\n label: typeof g === 'string' ? undefined : g.label,\n columns: [],\n firstIndex: idx,\n };\n explicitMap.set(id, group);\n groupsOrdered.push(group);\n }\n group.columns.push(col);\n });\n\n // Trailing implicit run\n if (run.length) pushImplicit(runStart, run);\n\n // If we only have a single implicit group covering all columns, treat as no groups\n if (\n groupsOrdered.length === 1 &&\n groupsOrdered[0].implicit &&\n groupsOrdered[0].columns.length === columns.length\n ) {\n return [];\n }\n\n return groupsOrdered as ColumnGroup<T>[];\n}\n\n/**\n * Apply CSS classes to header cells based on their group membership.\n *\n * @param headerRowEl - The header row element\n * @param groups - The computed column groups\n * @param columns - The column configurations\n */\nexport function applyGroupedHeaderCellClasses(\n headerRowEl: HTMLElement | null,\n groups: ColumnGroup[],\n columns: ColumnConfig<any>[]\n): void {\n if (!groups.length || !headerRowEl) return;\n\n const fieldToGroup = new Map<string, string>();\n for (const g of groups) {\n for (const c of g.columns) {\n if ((c as any)?.field) {\n fieldToGroup.set((c as any).field, g.id);\n }\n }\n }\n\n const headerCells = Array.from(headerRowEl.querySelectorAll('.cell[data-field]')) as HTMLElement[];\n headerCells.forEach((cell) => {\n const f = cell.getAttribute('data-field') || '';\n const gid = fieldToGroup.get(f);\n if (gid) {\n cell.classList.add('grouped');\n if (!cell.getAttribute('data-group')) {\n cell.setAttribute('data-group', gid);\n }\n }\n });\n\n // Mark group end cells for styling\n for (const g of groups) {\n const last = g.columns[g.columns.length - 1];\n const cell = headerCells.find((c) => c.getAttribute('data-field') === (last as any).field);\n if (cell) cell.classList.add('group-end');\n }\n}\n\n/**\n * Build the group header row element.\n *\n * @param groups - The computed column groups\n * @param columns - The column configurations\n * @returns The group header row element, or null if no groups\n */\nexport function buildGroupHeaderRow(\n groups: ColumnGroup[],\n columns: ColumnConfig<any>[]\n): HTMLElement | null {\n if (groups.length === 0) return null;\n\n const groupRow = document.createElement('div');\n groupRow.className = 'header-group-row';\n groupRow.setAttribute('role', 'row');\n\n for (const g of groups) {\n const startIndex =\n g.firstIndex != null\n ? g.firstIndex\n : columns.findIndex((c) => (g.columns as any[]).includes(c));\n\n const isImplicit = String(g.id).startsWith('__implicit__');\n const label = isImplicit ? '' : g.label || g.id;\n\n const cell = document.createElement('div');\n cell.className = 'cell header-group-cell';\n if (isImplicit) cell.classList.add('implicit-group');\n cell.setAttribute('data-group', String(g.id));\n cell.style.gridColumn = `${startIndex + 1} / span ${g.columns.length}`;\n cell.textContent = label;\n groupRow.appendChild(cell);\n }\n\n return groupRow;\n}\n\n/**\n * Check if any columns have group configuration.\n *\n * @param columns - The column configurations\n * @returns True if at least one column has a group\n */\nexport function hasColumnGroups(columns: ColumnConfig<any>[]): boolean {\n return columns.some((col) => (col as any).group != null);\n}\n\n/**\n * Get group ID for a specific column.\n *\n * @param column - The column configuration\n * @returns The group ID, or undefined if not grouped\n */\nexport function getColumnGroupId(column: ColumnConfig<any>): string | undefined {\n const g = (column as any).group;\n if (!g) return undefined;\n return typeof g === 'string' ? g : g.id;\n}\n","/**\n * Column Groups Plugin (Class-based)\n *\n * Enables multi-level column header grouping.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n applyGroupedHeaderCellClasses,\n buildGroupHeaderRow,\n computeColumnGroups,\n hasColumnGroups,\n} from './grouping-columns';\nimport type { ColumnGroup, GroupingColumnsConfig } from './types';\n\n/**\n * Column Groups Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new GroupingColumnsPlugin({\n * enabled: true,\n * showGroupBorders: true,\n * })\n * ```\n */\nexport class GroupingColumnsPlugin extends BaseGridPlugin<GroupingColumnsConfig> {\n readonly name = 'groupingColumns';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<GroupingColumnsConfig> {\n return {\n enabled: true,\n showGroupBorders: true,\n };\n }\n\n // ===== Internal State =====\n private groups: ColumnGroup[] = [];\n private isActive = false;\n\n // ===== Lifecycle =====\n\n override detach(): void {\n this.groups = [];\n this.isActive = false;\n }\n\n // ===== Static Detection =====\n\n /**\n * Auto-detect column groups from column configuration.\n */\n static detect(rows: readonly any[], config: any): boolean {\n const columns = config?.columns;\n if (!Array.isArray(columns)) return false;\n return hasColumnGroups(columns);\n }\n\n // ===== Hooks =====\n\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n if (!this.config.enabled) {\n this.isActive = false;\n this.groups = [];\n return [...columns];\n }\n\n // Compute groups from column definitions\n const groups = computeColumnGroups(columns as ColumnConfig[]);\n\n if (groups.length === 0) {\n this.isActive = false;\n this.groups = [];\n return [...columns];\n }\n\n this.isActive = true;\n this.groups = groups;\n\n // Return columns unchanged - the afterRender hook will add the group header\n return [...columns];\n }\n\n override afterRender(): void {\n if (!this.isActive || this.groups.length === 0) {\n // Remove any existing group header\n const header = this.shadowRoot?.querySelector('.header');\n const existingGroupRow = header?.querySelector('.header-group-row');\n if (existingGroupRow) existingGroupRow.remove();\n return;\n }\n\n const header = this.shadowRoot?.querySelector('.header');\n if (!header) return;\n\n // Remove existing group row if present\n const existingGroupRow = header.querySelector('.header-group-row');\n if (existingGroupRow) existingGroupRow.remove();\n\n // Build and insert group header row\n const groupRow = buildGroupHeaderRow(this.groups, this.columns as ColumnConfig[]);\n if (groupRow) {\n const headerRow = header.querySelector('.header-row');\n if (headerRow) {\n header.insertBefore(groupRow, headerRow);\n } else {\n header.appendChild(groupRow);\n }\n }\n\n // Apply classes to header cells\n const headerRow = header.querySelector('.header-row') as HTMLElement;\n if (headerRow) {\n applyGroupedHeaderCellClasses(headerRow, this.groups, this.columns as ColumnConfig[]);\n }\n }\n\n // ===== Public API =====\n\n /**\n * Check if column groups are active.\n * @returns Whether grouping is active\n */\n isGroupingActive(): boolean {\n return this.isActive;\n }\n\n /**\n * Get the computed column groups.\n * @returns Array of column groups\n */\n getGroups(): ColumnGroup[] {\n return this.groups;\n }\n\n /**\n * Get columns in a specific group.\n * @param groupId - The group ID to find\n * @returns Array of columns in the group\n */\n getGroupColumns(groupId: string): ColumnConfig[] {\n const group = this.groups.find((g) => g.id === groupId);\n return group ? group.columns : [];\n }\n\n /**\n * Refresh column groups (recompute from current columns).\n */\n refresh(): void {\n this.requestRender();\n }\n\n // ===== Styles =====\n\n override readonly styles = `\n .header-group-row {\n display: grid;\n grid-auto-flow: column;\n background: var(--tbw-grouping-columns-header-bg, var(--tbw-color-header-bg));\n border-bottom: 1px solid var(--tbw-grouping-columns-border, var(--tbw-color-border));\n }\n .header-group-cell {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 4px 8px;\n font-weight: 600;\n font-size: 0.9em;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n border-right: 1px solid var(--tbw-grouping-columns-border, var(--tbw-color-border));\n }\n .header-group-cell:last-child {\n border-right: none;\n }\n .header-row .cell.grouped {\n border-top: none;\n }\n .header-row .cell.group-end {\n border-right: 2px solid var(--tbw-grouping-columns-separator, var(--tbw-color-border-strong));\n }\n `;\n}\n"],"names":["BaseGridPlugin","config","grid","PluginClass","eventName","detail","message","computeColumnGroups","columns","explicitMap","groupsOrdered","pushImplicit","startIdx","cols","prev","run","runStart","col","idx","g","id","group","applyGroupedHeaderCellClasses","headerRowEl","groups","fieldToGroup","c","headerCells","cell","f","gid","last","buildGroupHeaderRow","groupRow","startIndex","isImplicit","label","hasColumnGroups","GroupingColumnsPlugin","rows","existingGroupRow","header","headerRow","groupId"],"mappings":"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"}