@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/plugins/selection/range-selection.ts","../../../../../../libs/grid/src/lib/plugins/selection/SelectionPlugin.ts","../../../../../../libs/grid/src/lib/plugins/selection/row-selection.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 * Cell Range Selection Core Logic\n *\n * Pure functions for cell range selection operations.\n */\n\nimport type { InternalCellRange, CellRange } from './types';\n\n/**\n * Normalize a range so startRow/startCol are always <= endRow/endCol.\n * This handles cases where user drags from bottom-right to top-left.\n *\n * @param range - The range to normalize\n * @returns Normalized range with start <= end for both dimensions\n */\nexport function normalizeRange(range: InternalCellRange): InternalCellRange {\n return {\n startRow: Math.min(range.startRow, range.endRow),\n startCol: Math.min(range.startCol, range.endCol),\n endRow: Math.max(range.startRow, range.endRow),\n endCol: Math.max(range.startCol, range.endCol),\n };\n}\n\n/**\n * Convert an internal range to the public event format.\n *\n * @param range - The internal range to convert\n * @returns Public CellRange format with from/to coordinates\n */\nexport function toPublicRange(range: InternalCellRange): CellRange {\n const normalized = normalizeRange(range);\n return {\n from: { row: normalized.startRow, col: normalized.startCol },\n to: { row: normalized.endRow, col: normalized.endCol },\n };\n}\n\n/**\n * Convert multiple internal ranges to public format.\n *\n * @param ranges - Array of internal ranges\n * @returns Array of public CellRange format\n */\nexport function toPublicRanges(ranges: InternalCellRange[]): CellRange[] {\n return ranges.map(toPublicRange);\n}\n\n/**\n * Check if a cell is within a specific range.\n *\n * @param row - The row index to check\n * @param col - The column index to check\n * @param range - The range to check against\n * @returns True if the cell is within the range\n */\nexport function isCellInRange(row: number, col: number, range: InternalCellRange): boolean {\n const normalized = normalizeRange(range);\n return (\n row >= normalized.startRow && row <= normalized.endRow && col >= normalized.startCol && col <= normalized.endCol\n );\n}\n\n/**\n * Check if a cell is within any of the provided ranges.\n *\n * @param row - The row index to check\n * @param col - The column index to check\n * @param ranges - Array of ranges to check against\n * @returns True if the cell is within any range\n */\nexport function isCellInAnyRange(row: number, col: number, ranges: InternalCellRange[]): boolean {\n return ranges.some((range) => isCellInRange(row, col, range));\n}\n\n/**\n * Get all cells within a range as an array of {row, col} objects.\n *\n * @param range - The range to enumerate\n * @returns Array of all cell coordinates in the range\n */\nexport function getCellsInRange(range: InternalCellRange): Array<{ row: number; col: number }> {\n const cells: Array<{ row: number; col: number }> = [];\n const normalized = normalizeRange(range);\n\n for (let row = normalized.startRow; row <= normalized.endRow; row++) {\n for (let col = normalized.startCol; col <= normalized.endCol; col++) {\n cells.push({ row, col });\n }\n }\n\n return cells;\n}\n\n/**\n * Get all unique cells across multiple ranges.\n * Deduplicates cells that appear in overlapping ranges.\n *\n * @param ranges - Array of ranges to enumerate\n * @returns Array of unique cell coordinates\n */\nexport function getAllCellsInRanges(ranges: InternalCellRange[]): Array<{ row: number; col: number }> {\n const cellMap = new Map<string, { row: number; col: number }>();\n\n for (const range of ranges) {\n for (const cell of getCellsInRange(range)) {\n cellMap.set(`${cell.row},${cell.col}`, cell);\n }\n }\n\n return [...cellMap.values()];\n}\n\n/**\n * Merge overlapping or adjacent ranges into fewer ranges.\n * Simple implementation - returns ranges as-is for now.\n * More complex merging logic can be added later for optimization.\n *\n * @param ranges - Array of ranges to merge\n * @returns Merged array of ranges\n */\nexport function mergeRanges(ranges: InternalCellRange[]): InternalCellRange[] {\n // Simple implementation - more complex merging can be added later\n return ranges;\n}\n\n/**\n * Create a range from an anchor cell to a current cell position.\n * The range is not normalized - it preserves the direction of selection.\n *\n * @param anchor - The anchor cell (where selection started)\n * @param current - The current cell (where selection ends)\n * @returns An InternalCellRange from anchor to current\n */\nexport function createRangeFromAnchor(\n anchor: { row: number; col: number },\n current: { row: number; col: number }\n): InternalCellRange {\n return {\n startRow: anchor.row,\n startCol: anchor.col,\n endRow: current.row,\n endCol: current.col,\n };\n}\n\n/**\n * Calculate the number of cells in a range.\n *\n * @param range - The range to measure\n * @returns Total number of cells in the range\n */\nexport function getRangeCellCount(range: InternalCellRange): number {\n const normalized = normalizeRange(range);\n const rowCount = normalized.endRow - normalized.startRow + 1;\n const colCount = normalized.endCol - normalized.startCol + 1;\n return rowCount * colCount;\n}\n\n/**\n * Check if two ranges are equal (same boundaries).\n *\n * @param a - First range\n * @param b - Second range\n * @returns True if ranges have same boundaries after normalization\n */\nexport function rangesEqual(a: InternalCellRange, b: InternalCellRange): boolean {\n const normA = normalizeRange(a);\n const normB = normalizeRange(b);\n return (\n normA.startRow === normB.startRow &&\n normA.startCol === normB.startCol &&\n normA.endRow === normB.endRow &&\n normA.endCol === normB.endCol\n );\n}\n\n/**\n * Check if a range is a single cell (1x1).\n *\n * @param range - The range to check\n * @returns True if the range is exactly one cell\n */\nexport function isSingleCell(range: InternalCellRange): boolean {\n const normalized = normalizeRange(range);\n return normalized.startRow === normalized.endRow && normalized.startCol === normalized.endCol;\n}\n","/**\n * Selection Plugin (Class-based)\n *\n * Provides selection functionality for tbw-grid.\n * Supports three modes:\n * - 'cell': Single cell selection (default). No border, just focus highlight.\n * - 'row': Row selection. Clicking a cell selects the entire row.\n * - 'range': Range selection. Shift+click or drag to select rectangular cell ranges.\n */\n\nimport { BaseGridPlugin, CellClickEvent, CellMouseEvent } from '../../core/plugin/base-plugin';\nimport {\n createRangeFromAnchor,\n getAllCellsInRanges,\n isCellInAnyRange,\n normalizeRange,\n toPublicRanges,\n} from './range-selection';\nimport type { CellRange, InternalCellRange, SelectionChangeDetail, SelectionConfig, SelectionMode } from './types';\n\n/**\n * Build the selection change event detail for the current state.\n */\nfunction buildSelectionEvent(\n mode: SelectionMode,\n state: {\n selectedCell: { row: number; col: number } | null;\n selected: Set<number>;\n ranges: InternalCellRange[];\n },\n colCount: number\n): SelectionChangeDetail {\n if (mode === 'cell' && state.selectedCell) {\n return {\n mode,\n ranges: [\n {\n from: { row: state.selectedCell.row, col: state.selectedCell.col },\n to: { row: state.selectedCell.row, col: state.selectedCell.col },\n },\n ],\n };\n }\n\n if (mode === 'row' && state.selected.size > 0) {\n const ranges = [...state.selected].map((rowIndex) => ({\n from: { row: rowIndex, col: 0 },\n to: { row: rowIndex, col: colCount - 1 },\n }));\n return { mode, ranges };\n }\n\n if (mode === 'range' && state.ranges.length > 0) {\n return { mode, ranges: toPublicRanges(state.ranges) };\n }\n\n return { mode, ranges: [] };\n}\n\n/**\n * Selection Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new SelectionPlugin({ mode: 'range' })\n * ```\n */\nexport class SelectionPlugin extends BaseGridPlugin<SelectionConfig> {\n readonly name = 'selection';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<SelectionConfig> {\n return {\n mode: 'cell',\n };\n }\n\n // ===== Internal State =====\n /** Row selection state (row mode) */\n private selected = new Set<number>();\n private lastSelected: number | null = null;\n private anchor: number | null = null;\n\n /** Range selection state (range mode) */\n private ranges: InternalCellRange[] = [];\n private activeRange: InternalCellRange | null = null;\n private cellAnchor: { row: number; col: number } | null = null;\n private isDragging = false;\n\n /** Cell selection state (cell mode) */\n private selectedCell: { row: number; col: number } | null = null;\n\n // ===== Lifecycle =====\n\n override detach(): void {\n this.selected.clear();\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n this.isDragging = false;\n this.selectedCell = null;\n }\n\n // ===== Event Handlers =====\n\n override onCellClick(event: CellClickEvent): boolean {\n const { rowIndex, colIndex, originalEvent } = event;\n const { mode } = this.config;\n\n // ===== CELL MODE: Single cell selection =====\n if (mode === 'cell') {\n this.selectedCell = { row: rowIndex, col: colIndex };\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return false;\n }\n\n // ===== ROW MODE: Select entire row =====\n if (mode === 'row') {\n this.selected.clear();\n this.selected.add(rowIndex);\n this.lastSelected = rowIndex;\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return false;\n }\n\n // ===== RANGE MODE: Shift+click extends selection, click starts new =====\n if (mode === 'range') {\n const shiftKey = originalEvent.shiftKey;\n const ctrlKey = originalEvent.ctrlKey || originalEvent.metaKey;\n\n if (shiftKey && this.cellAnchor) {\n // Extend selection from anchor\n const newRange = createRangeFromAnchor(this.cellAnchor, { row: rowIndex, col: colIndex });\n\n if (ctrlKey) {\n if (this.ranges.length > 0) {\n this.ranges[this.ranges.length - 1] = newRange;\n } else {\n this.ranges.push(newRange);\n }\n } else {\n this.ranges = [newRange];\n }\n this.activeRange = newRange;\n } else if (ctrlKey) {\n const newRange: InternalCellRange = {\n startRow: rowIndex,\n startCol: colIndex,\n endRow: rowIndex,\n endCol: colIndex,\n };\n this.ranges.push(newRange);\n this.activeRange = newRange;\n this.cellAnchor = { row: rowIndex, col: colIndex };\n } else {\n const newRange: InternalCellRange = {\n startRow: rowIndex,\n startCol: colIndex,\n endRow: rowIndex,\n endCol: colIndex,\n };\n this.ranges = [newRange];\n this.activeRange = newRange;\n this.cellAnchor = { row: rowIndex, col: colIndex };\n }\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n\n this.requestAfterRender();\n return false;\n }\n\n return false;\n }\n\n override onKeyDown(event: KeyboardEvent): boolean {\n const { mode } = this.config;\n\n // Escape clears selection in all modes\n if (event.key === 'Escape') {\n if (mode === 'cell') {\n this.selectedCell = null;\n } else if (mode === 'row') {\n this.selected.clear();\n this.anchor = null;\n } else if (mode === 'range') {\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n }\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n\n // Ctrl+A selects all in range mode\n if (mode === 'range' && event.key === 'a' && (event.ctrlKey || event.metaKey)) {\n const rowCount = this.rows.length;\n const colCount = this.columns.length;\n if (rowCount > 0 && colCount > 0) {\n const allRange: InternalCellRange = {\n startRow: 0,\n startCol: 0,\n endRow: rowCount - 1,\n endCol: colCount - 1,\n };\n this.ranges = [allRange];\n this.activeRange = allRange;\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n }\n\n return false;\n }\n\n override onCellMouseDown(event: CellMouseEvent): boolean | void {\n if (this.config.mode !== 'range') return;\n if (event.rowIndex === undefined || event.colIndex === undefined) return;\n if (event.rowIndex < 0) return; // Header\n\n // Let onCellClick handle shift+click for range extension\n if (event.originalEvent.shiftKey && this.cellAnchor) {\n return;\n }\n\n // Start drag selection\n this.isDragging = true;\n const rowIndex = event.rowIndex;\n const colIndex = event.colIndex;\n this.cellAnchor = { row: rowIndex, col: colIndex };\n\n const ctrlKey = event.originalEvent.ctrlKey || event.originalEvent.metaKey;\n if (!ctrlKey) {\n this.ranges = [];\n }\n\n const newRange: InternalCellRange = {\n startRow: rowIndex,\n startCol: colIndex,\n endRow: rowIndex,\n endCol: colIndex,\n };\n this.ranges.push(newRange);\n this.activeRange = newRange;\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n\n override onCellMouseMove(event: CellMouseEvent): boolean | void {\n if (this.config.mode !== 'range') return;\n if (!this.isDragging || !this.cellAnchor) return;\n if (event.rowIndex === undefined || event.colIndex === undefined) return;\n if (event.rowIndex < 0) return;\n\n const newRange = createRangeFromAnchor(this.cellAnchor, { row: event.rowIndex, col: event.colIndex });\n\n if (this.ranges.length > 0) {\n this.ranges[this.ranges.length - 1] = newRange;\n } else {\n this.ranges.push(newRange);\n }\n this.activeRange = newRange;\n\n this.emit<SelectionChangeDetail>('selection-change', this.#buildEvent());\n this.requestAfterRender();\n return true;\n }\n\n override onCellMouseUp(_event: CellMouseEvent): boolean | void {\n if (this.config.mode !== 'range') return;\n if (this.isDragging) {\n this.isDragging = false;\n return true;\n }\n }\n\n override afterRender(): void {\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n const container = shadowRoot.children[0];\n const { mode } = this.config;\n\n // Set data attribute on host for CSS variable scoping\n (this.grid as unknown as Element).setAttribute('data-selection-mode', mode);\n\n // Toggle .selecting class during drag to prevent text selection\n if (container) {\n container.classList.toggle('selecting', this.isDragging);\n }\n\n // Clear all selection classes first\n const allCells = shadowRoot.querySelectorAll('.cell');\n allCells.forEach((cell) => {\n cell.classList.remove('selected', 'top', 'bottom', 'first', 'last');\n });\n\n const allRows = shadowRoot.querySelectorAll('.data-grid-row');\n allRows.forEach((row) => {\n row.classList.remove('selected');\n });\n\n // ===== ROW MODE: Add row-focus class to selected rows =====\n if (mode === 'row') {\n allRows.forEach((row) => row.classList.remove('row-focus'));\n\n allRows.forEach((row) => {\n const firstCell = row.querySelector('.cell[data-row]');\n const rowIndex = parseInt(firstCell?.getAttribute('data-row') ?? '-1', 10);\n if (rowIndex >= 0 && this.selected.has(rowIndex)) {\n row.classList.add('selected', 'row-focus');\n }\n });\n }\n\n // ===== RANGE MODE: Add selected and edge classes to cells =====\n if (mode === 'range' && this.ranges.length > 0) {\n const normalized = this.activeRange ? normalizeRange(this.activeRange) : null;\n\n const cells = shadowRoot.querySelectorAll('.cell[data-row][data-col]');\n cells.forEach((cell) => {\n const rowIndex = parseInt(cell.getAttribute('data-row') ?? '-1', 10);\n const colIndex = parseInt(cell.getAttribute('data-col') ?? '-1', 10);\n if (rowIndex >= 0 && colIndex >= 0) {\n const inRange = isCellInAnyRange(rowIndex, colIndex, this.ranges);\n\n if (inRange) {\n cell.classList.add('selected');\n\n if (normalized) {\n if (rowIndex === normalized.startRow) cell.classList.add('top');\n if (rowIndex === normalized.endRow) cell.classList.add('bottom');\n if (colIndex === normalized.startCol) cell.classList.add('first');\n if (colIndex === normalized.endCol) cell.classList.add('last');\n }\n }\n }\n });\n }\n }\n\n // ===== Public API =====\n\n /**\n * Get the selected cell (cell mode only).\n */\n getSelectedCell(): { row: number; col: number } | null {\n return this.selectedCell;\n }\n\n /**\n * Get all selected row indices (row mode).\n */\n getSelectedRows(): number[] {\n return [...this.selected];\n }\n\n /**\n * Get all selected cell ranges in public format.\n */\n getRanges(): CellRange[] {\n return toPublicRanges(this.ranges);\n }\n\n /**\n * Get all selected cells across all ranges.\n */\n getSelectedCells(): Array<{ row: number; col: number }> {\n return getAllCellsInRanges(this.ranges);\n }\n\n /**\n * Check if a specific cell is in range selection.\n */\n isCellSelected(row: number, col: number): boolean {\n return isCellInAnyRange(row, col, this.ranges);\n }\n\n /**\n * Clear all selection.\n */\n clearSelection(): void {\n this.selectedCell = null;\n this.selected.clear();\n this.anchor = null;\n this.ranges = [];\n this.activeRange = null;\n this.cellAnchor = null;\n this.emit<SelectionChangeDetail>('selection-change', { mode: this.config.mode, ranges: [] });\n this.requestAfterRender();\n }\n\n /**\n * Set selected ranges programmatically.\n */\n setRanges(ranges: CellRange[]): void {\n this.ranges = ranges.map((r) => ({\n startRow: r.from.row,\n startCol: r.from.col,\n endRow: r.to.row,\n endCol: r.to.col,\n }));\n this.activeRange = this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] : null;\n this.emit<SelectionChangeDetail>('selection-change', {\n mode: this.config.mode,\n ranges: toPublicRanges(this.ranges),\n });\n this.requestAfterRender();\n }\n\n // ===== Private Helpers =====\n\n #buildEvent(): SelectionChangeDetail {\n return buildSelectionEvent(\n this.config.mode,\n {\n selectedCell: this.selectedCell,\n selected: this.selected,\n ranges: this.ranges,\n },\n this.columns.length\n );\n }\n\n // ===== Styles =====\n\n override readonly styles = `\n /* Prevent text selection during range drag */\n :host .selecting .data-grid-row > .cell {\n user-select: none;\n }\n\n /* Row selection - use accent color for row focus */\n :host .data-grid-row.row-focus {\n background-color: var(--tbw-focus-background, rgba(from var(--tbw-color-accent) r g b / 12%));\n }\n\n /* Disable cell-focus outline in row mode - row is the focus unit */\n :host([data-selection-mode=\"row\"]) .cell-focus {\n outline: none;\n }\n\n /* Selection cell styles - for range mode */\n :host .data-grid-row > .cell.selected {\n background-color: var(--tbw-range-selection-bg);\n }\n :host .data-grid-row > .cell.selected.top {\n border-top: 2px solid var(--tbw-range-border-color);\n }\n :host .data-grid-row > .cell.selected.bottom {\n border-bottom: 2px solid var(--tbw-range-border-color);\n }\n :host .data-grid-row > .cell.selected.first {\n border-left: 2px solid var(--tbw-range-border-color);\n }\n :host .data-grid-row > .cell.selected.last {\n border-right: 2px solid var(--tbw-range-border-color);\n }\n `;\n}\n","/**\n * Row Selection Core Logic\n *\n * Pure functions for row selection operations.\n */\n\nimport type { SelectionMode, SelectionState } from './types';\n\n/** Click modifier keys state */\nexport interface ClickModifiers {\n shiftKey: boolean;\n ctrlKey: boolean;\n metaKey: boolean;\n}\n\n/** Result from handling a row click for selection */\nexport interface RowClickResult {\n selected: Set<number>;\n lastSelected: number;\n anchor: number | null;\n}\n\n/**\n * Handle a row click event for selection purposes.\n *\n * In single mode: always selects the clicked row, clearing others.\n * In multiple mode:\n * - Plain click: clears selection and selects clicked row\n * - Ctrl/Cmd+click: toggles the clicked row\n * - Shift+click: range select from anchor to clicked row\n *\n * @param state - Current selection state\n * @param rowIndex - The clicked row index\n * @param mode - Selection mode ('single' or 'multiple')\n * @param modifiers - Keyboard modifiers held during click\n * @returns Updated selection state values\n */\nexport function handleRowClick(\n state: SelectionState,\n rowIndex: number,\n mode: SelectionMode,\n modifiers: ClickModifiers\n): RowClickResult {\n const newSelected = new Set(state.selected);\n let anchor = state.anchor;\n\n if ((mode as string) === 'single') {\n newSelected.clear();\n newSelected.add(rowIndex);\n anchor = rowIndex;\n } else if ((mode as string) === 'multiple') {\n const ctrlOrMeta = modifiers.ctrlKey || modifiers.metaKey;\n\n if (modifiers.shiftKey && state.anchor !== null) {\n // Range selection from anchor\n const start = Math.min(state.anchor, rowIndex);\n const end = Math.max(state.anchor, rowIndex);\n for (let i = start; i <= end; i++) {\n newSelected.add(i);\n }\n } else if (ctrlOrMeta) {\n // Toggle selection\n if (newSelected.has(rowIndex)) {\n newSelected.delete(rowIndex);\n } else {\n newSelected.add(rowIndex);\n }\n anchor = rowIndex;\n } else {\n // Clear and select single\n newSelected.clear();\n newSelected.add(rowIndex);\n anchor = rowIndex;\n }\n }\n\n return { selected: newSelected, lastSelected: rowIndex, anchor };\n}\n\n/**\n * Create a set containing all row indices (for select all).\n *\n * @param rowCount - Total number of rows\n * @returns Set containing indices 0 to rowCount-1\n */\nexport function selectAll(rowCount: number): Set<number> {\n const selected = new Set<number>();\n for (let i = 0; i < rowCount; i++) {\n selected.add(i);\n }\n return selected;\n}\n\n/**\n * Compute the difference between two selection states.\n *\n * @param oldSelected - Previous selection set\n * @param newSelected - New selection set\n * @returns Object with added and removed row indices\n */\nexport function computeSelectionDiff(\n oldSelected: Set<number>,\n newSelected: Set<number>\n): { added: number[]; removed: number[] } {\n const added: number[] = [];\n const removed: number[] = [];\n\n for (const idx of newSelected) {\n if (!oldSelected.has(idx)) added.push(idx);\n }\n for (const idx of oldSelected) {\n if (!newSelected.has(idx)) removed.push(idx);\n }\n\n return { added, removed };\n}\n"],"names":["BaseGridPlugin","config","grid","PluginClass","eventName","detail","message","normalizeRange","range","toPublicRange","normalized","toPublicRanges","ranges","isCellInRange","row","col","isCellInAnyRange","getCellsInRange","cells","getAllCellsInRanges","cellMap","cell","createRangeFromAnchor","anchor","current","buildSelectionEvent","mode","state","colCount","rowIndex","SelectionPlugin","event","colIndex","originalEvent","#buildEvent","shiftKey","ctrlKey","newRange","rowCount","allRange","_event","shadowRoot","container","allRows","firstCell","r","handleRowClick","modifiers","newSelected","ctrlOrMeta","start","end","i","selectAll","selected","computeSelectionDiff","oldSelected","added","removed","idx"],"mappings":"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"}
@@ -0,0 +1,241 @@
1
+ class a {
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, o) {
53
+ this.grid?.dispatchEvent?.(new CustomEvent(e, { detail: o, 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 l(s, e) {
108
+ return Math.floor(s / e);
109
+ }
110
+ function d(s, e) {
111
+ return {
112
+ start: s * e,
113
+ end: (s + 1) * e
114
+ };
115
+ }
116
+ function u(s, e, o) {
117
+ const t = l(s, o), r = l(e - 1, o), i = [];
118
+ for (let n = t; n <= r; n++)
119
+ i.push(n);
120
+ return i;
121
+ }
122
+ async function c(s, e, o, t) {
123
+ const r = d(e, o);
124
+ return s.getRows({
125
+ startRow: r.start,
126
+ endRow: r.end,
127
+ sortModel: t.sortModel,
128
+ filterModel: t.filterModel
129
+ });
130
+ }
131
+ function h(s, e, o) {
132
+ const t = l(s, e), r = o.get(t);
133
+ if (!r) return;
134
+ const i = s % e;
135
+ return r[i];
136
+ }
137
+ const g = 100;
138
+ class R extends a {
139
+ name = "serverSide";
140
+ version = "1.0.0";
141
+ get defaultConfig() {
142
+ return {
143
+ enabled: !0,
144
+ pageSize: 100,
145
+ cacheBlockSize: 100,
146
+ maxConcurrentRequests: 2
147
+ };
148
+ }
149
+ // ===== Internal State =====
150
+ dataSource = null;
151
+ totalRowCount = 0;
152
+ loadedBlocks = /* @__PURE__ */ new Map();
153
+ loadingBlocks = /* @__PURE__ */ new Set();
154
+ lastRequestId = 0;
155
+ scrollDebounceTimer;
156
+ // ===== Lifecycle =====
157
+ detach() {
158
+ this.dataSource = null, this.totalRowCount = 0, this.loadedBlocks.clear(), this.loadingBlocks.clear(), this.lastRequestId = 0, this.scrollDebounceTimer && (clearTimeout(this.scrollDebounceTimer), this.scrollDebounceTimer = void 0);
159
+ }
160
+ // ===== Private Methods =====
161
+ /**
162
+ * Check current viewport and load any missing blocks.
163
+ */
164
+ loadRequiredBlocks() {
165
+ if (!this.dataSource) return;
166
+ const e = this.grid, o = this.config.cacheBlockSize ?? 100, t = { startRow: e.virtualization.start, endRow: e.virtualization.end }, r = u(t.startRow, t.endRow, o);
167
+ for (const i of r)
168
+ if (!(this.loadedBlocks.has(i) || this.loadingBlocks.has(i))) {
169
+ if (this.loadingBlocks.size >= (this.config.maxConcurrentRequests ?? 2))
170
+ break;
171
+ this.loadingBlocks.add(i), c(this.dataSource, i, o, {}).then((n) => {
172
+ this.loadedBlocks.set(i, n.rows), this.totalRowCount = n.totalRowCount, this.loadingBlocks.delete(i), this.requestRender(), this.loadRequiredBlocks();
173
+ }).catch(() => {
174
+ this.loadingBlocks.delete(i);
175
+ });
176
+ }
177
+ }
178
+ // ===== Hooks =====
179
+ processRows(e) {
180
+ if (!this.dataSource) return [...e];
181
+ const o = [];
182
+ for (let t = 0; t < this.totalRowCount; t++) {
183
+ const r = h(t, this.config.cacheBlockSize ?? 100, this.loadedBlocks);
184
+ o.push(r ?? { __loading: !0, __index: t });
185
+ }
186
+ return o;
187
+ }
188
+ onScroll(e) {
189
+ this.dataSource && (this.loadRequiredBlocks(), this.scrollDebounceTimer && clearTimeout(this.scrollDebounceTimer), this.scrollDebounceTimer = setTimeout(() => {
190
+ this.loadRequiredBlocks();
191
+ }, g));
192
+ }
193
+ // ===== Public API =====
194
+ /**
195
+ * Set the data source for server-side loading.
196
+ * @param dataSource - Data source implementing the getRows method
197
+ */
198
+ setDataSource(e) {
199
+ this.dataSource = e, this.loadedBlocks.clear(), this.loadingBlocks.clear();
200
+ const o = this.config.cacheBlockSize ?? 100;
201
+ c(e, 0, o, {}).then((t) => {
202
+ this.loadedBlocks.set(0, t.rows), this.totalRowCount = t.totalRowCount, this.requestRender();
203
+ });
204
+ }
205
+ /**
206
+ * Refresh all data from the server.
207
+ */
208
+ refresh() {
209
+ this.dataSource && (this.loadedBlocks.clear(), this.loadingBlocks.clear(), this.requestRender());
210
+ }
211
+ /**
212
+ * Clear all cached data without refreshing.
213
+ */
214
+ purgeCache() {
215
+ this.loadedBlocks.clear();
216
+ }
217
+ /**
218
+ * Get the total row count from the server.
219
+ */
220
+ getTotalRowCount() {
221
+ return this.totalRowCount;
222
+ }
223
+ /**
224
+ * Check if a specific row is loaded in the cache.
225
+ * @param rowIndex - Row index to check
226
+ */
227
+ isRowLoaded(e) {
228
+ const o = this.config.cacheBlockSize ?? 100, t = l(e, o);
229
+ return this.loadedBlocks.has(t);
230
+ }
231
+ /**
232
+ * Get the number of loaded cache blocks.
233
+ */
234
+ getLoadedBlockCount() {
235
+ return this.loadedBlocks.size;
236
+ }
237
+ }
238
+ export {
239
+ R as ServerSidePlugin
240
+ };
241
+ //# 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/server-side/datasource.ts","../../../../../../libs/grid/src/lib/plugins/server-side/ServerSidePlugin.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","import type { ServerSideDataSource, GetRowsParams, GetRowsResult } from './types';\n\nexport function getBlockNumber(rowIndex: number, blockSize: number): number {\n return Math.floor(rowIndex / blockSize);\n}\n\nexport function getBlockRange(blockNumber: number, blockSize: number): { start: number; end: number } {\n return {\n start: blockNumber * blockSize,\n end: (blockNumber + 1) * blockSize,\n };\n}\n\nexport function getRequiredBlocks(startRow: number, endRow: number, blockSize: number): number[] {\n const startBlock = getBlockNumber(startRow, blockSize);\n const endBlock = getBlockNumber(endRow - 1, blockSize);\n\n const blocks: number[] = [];\n for (let i = startBlock; i <= endBlock; i++) {\n blocks.push(i);\n }\n return blocks;\n}\n\nexport async function loadBlock(\n dataSource: ServerSideDataSource,\n blockNumber: number,\n blockSize: number,\n params: Partial<GetRowsParams>\n): Promise<GetRowsResult> {\n const range = getBlockRange(blockNumber, blockSize);\n\n return dataSource.getRows({\n startRow: range.start,\n endRow: range.end,\n sortModel: params.sortModel,\n filterModel: params.filterModel,\n });\n}\n\nexport function getRowFromCache(\n rowIndex: number,\n blockSize: number,\n loadedBlocks: Map<number, any[]>\n): any | undefined {\n const blockNumber = getBlockNumber(rowIndex, blockSize);\n const block = loadedBlocks.get(blockNumber);\n if (!block) return undefined;\n\n const indexInBlock = rowIndex % blockSize;\n return block[indexInBlock];\n}\n\nexport function isBlockLoaded(blockNumber: number, loadedBlocks: Map<number, any[]>): boolean {\n return loadedBlocks.has(blockNumber);\n}\n\nexport function isBlockLoading(blockNumber: number, loadingBlocks: Set<number>): boolean {\n return loadingBlocks.has(blockNumber);\n}\n","/**\n * Server-Side Data Plugin (Class-based)\n *\n * Provides server-side data loading with caching and lazy loading.\n */\n\nimport { BaseGridPlugin, ScrollEvent } from '../../core/plugin/base-plugin';\nimport { getBlockNumber, getRequiredBlocks, getRowFromCache, loadBlock } from './datasource';\nimport type { ServerSideConfig, ServerSideDataSource } from './types';\n\n/** Scroll debounce delay in ms */\nconst SCROLL_DEBOUNCE_MS = 100;\n\n/**\n * Server-Side Plugin for tbw-grid\n *\n * @example\n * ```ts\n * const plugin = new ServerSidePlugin({ cacheBlockSize: 100 });\n * plugin.setDataSource(myDataSource);\n * ```\n */\nexport class ServerSidePlugin extends BaseGridPlugin<ServerSideConfig> {\n readonly name = 'serverSide';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<ServerSideConfig> {\n return {\n enabled: true,\n pageSize: 100,\n cacheBlockSize: 100,\n maxConcurrentRequests: 2,\n };\n }\n\n // ===== Internal State =====\n private dataSource: ServerSideDataSource | null = null;\n private totalRowCount = 0;\n private loadedBlocks = new Map<number, unknown[]>();\n private loadingBlocks = new Set<number>();\n private lastRequestId = 0;\n private scrollDebounceTimer?: ReturnType<typeof setTimeout>;\n\n // ===== Lifecycle =====\n\n override detach(): void {\n this.dataSource = null;\n this.totalRowCount = 0;\n this.loadedBlocks.clear();\n this.loadingBlocks.clear();\n this.lastRequestId = 0;\n if (this.scrollDebounceTimer) {\n clearTimeout(this.scrollDebounceTimer);\n this.scrollDebounceTimer = undefined;\n }\n }\n\n // ===== Private Methods =====\n\n /**\n * Check current viewport and load any missing blocks.\n */\n private loadRequiredBlocks(): void {\n if (!this.dataSource) return;\n\n // Get fresh viewport from grid's virtualization state\n const gridRef = this.grid as unknown as { virtualization: { start: number; end: number } };\n const blockSize = this.config.cacheBlockSize ?? 100;\n const viewport = { startRow: gridRef.virtualization.start, endRow: gridRef.virtualization.end };\n\n // Determine which blocks are needed for current viewport\n const requiredBlocks = getRequiredBlocks(viewport.startRow, viewport.endRow, blockSize);\n\n // Load missing blocks\n for (const blockNum of requiredBlocks) {\n if (this.loadedBlocks.has(blockNum) || this.loadingBlocks.has(blockNum)) {\n continue;\n }\n\n // Check concurrent request limit\n if (this.loadingBlocks.size >= (this.config.maxConcurrentRequests ?? 2)) {\n break;\n }\n\n this.loadingBlocks.add(blockNum);\n\n loadBlock(this.dataSource, blockNum, blockSize, {})\n .then((result) => {\n this.loadedBlocks.set(blockNum, result.rows);\n this.totalRowCount = result.totalRowCount;\n this.loadingBlocks.delete(blockNum);\n this.requestRender();\n // Re-check with fresh viewport: user may have scrolled further\n this.loadRequiredBlocks();\n })\n .catch(() => {\n this.loadingBlocks.delete(blockNum);\n });\n }\n }\n\n // ===== Hooks =====\n\n override processRows(rows: readonly unknown[]): unknown[] {\n if (!this.dataSource) return [...rows];\n\n // Create placeholder rows for total count\n const result: unknown[] = [];\n for (let i = 0; i < this.totalRowCount; i++) {\n const cached = getRowFromCache(i, this.config.cacheBlockSize ?? 100, this.loadedBlocks);\n result.push(cached ?? { __loading: true, __index: i });\n }\n\n return result;\n }\n\n override onScroll(event: ScrollEvent): void {\n if (!this.dataSource) return;\n\n // Immediate check for blocks\n this.loadRequiredBlocks();\n\n // Debounce: when scrolling stops, do a final check with fresh viewport\n if (this.scrollDebounceTimer) {\n clearTimeout(this.scrollDebounceTimer);\n }\n this.scrollDebounceTimer = setTimeout(() => {\n this.loadRequiredBlocks();\n }, SCROLL_DEBOUNCE_MS);\n }\n\n // ===== Public API =====\n\n /**\n * Set the data source for server-side loading.\n * @param dataSource - Data source implementing the getRows method\n */\n setDataSource(dataSource: ServerSideDataSource): void {\n this.dataSource = dataSource;\n this.loadedBlocks.clear();\n this.loadingBlocks.clear();\n\n // Load first block\n const blockSize = this.config.cacheBlockSize ?? 100;\n loadBlock(dataSource, 0, blockSize, {}).then((result) => {\n this.loadedBlocks.set(0, result.rows);\n this.totalRowCount = result.totalRowCount;\n this.requestRender();\n });\n }\n\n /**\n * Refresh all data from the server.\n */\n refresh(): void {\n if (!this.dataSource) return;\n this.loadedBlocks.clear();\n this.loadingBlocks.clear();\n this.requestRender();\n }\n\n /**\n * Clear all cached data without refreshing.\n */\n purgeCache(): void {\n this.loadedBlocks.clear();\n }\n\n /**\n * Get the total row count from the server.\n */\n getTotalRowCount(): number {\n return this.totalRowCount;\n }\n\n /**\n * Check if a specific row is loaded in the cache.\n * @param rowIndex - Row index to check\n */\n isRowLoaded(rowIndex: number): boolean {\n const blockSize = this.config.cacheBlockSize ?? 100;\n const blockNum = getBlockNumber(rowIndex, blockSize);\n return this.loadedBlocks.has(blockNum);\n }\n\n /**\n * Get the number of loaded cache blocks.\n */\n getLoadedBlockCount(): number {\n return this.loadedBlocks.size;\n }\n}\n"],"names":["BaseGridPlugin","config","grid","PluginClass","eventName","detail","message","getBlockNumber","rowIndex","blockSize","getBlockRange","blockNumber","getRequiredBlocks","startRow","endRow","startBlock","endBlock","blocks","i","loadBlock","dataSource","params","range","getRowFromCache","loadedBlocks","block","indexInBlock","SCROLL_DEBOUNCE_MS","ServerSidePlugin","gridRef","viewport","requiredBlocks","blockNum","result","rows","cached","event"],"mappings":"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"}