@toolbox-web/grid 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all.d.ts +50 -6
- package/all.js +101 -98
- package/all.js.map +1 -1
- package/index.d.ts +54 -0
- package/index.js +793 -692
- package/index.js.map +1 -1
- package/lib/plugins/clipboard/index.js +55 -35
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/column-virtualization/index.js +49 -29
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/index.js +35 -15
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/export/index.js +52 -32
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/index.js +116 -99
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/index.js +42 -22
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-rows/index.js +20 -0
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/index.js +50 -27
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/multi-sort/index.js +25 -5
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/index.js +20 -0
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-rows/index.js +20 -0
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pivot/index.js +20 -0
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/reorder/index.js +56 -33
- package/lib/plugins/reorder/index.js.map +1 -1
- package/lib/plugins/selection/index.js +138 -100
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/server-side/index.js +20 -0
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/tree/index.js +76 -53
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/undo-redo/index.js +20 -0
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/index.js +20 -0
- package/lib/plugins/visibility/index.js.map +1 -1
- package/package.json +4 -1
- package/themes/dg-theme-contrast.css +43 -43
- package/themes/dg-theme-large.css +54 -54
- package/themes/dg-theme-standard.css +19 -19
- package/themes/dg-theme-vibrant.css +16 -16
- package/umd/grid.all.umd.js +24 -24
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +14 -14
- package/umd/grid.umd.js.map +1 -1
- package/umd/plugins/filtering.umd.js +3 -3
- package/umd/plugins/filtering.umd.js.map +1 -1
- package/umd/plugins/master-detail.umd.js +2 -2
- package/umd/plugins/master-detail.umd.js.map +1 -1
- package/umd/plugins/multi-sort.umd.js.map +1 -1
- package/umd/plugins/reorder.umd.js +1 -1
- package/umd/plugins/reorder.umd.js.map +1 -1
- package/umd/plugins/selection.umd.js +2 -2
- package/umd/plugins/selection.umd.js.map +1 -1
- package/umd/plugins/tree.umd.js +2 -2
- package/umd/plugins/tree.umd.js.map +1 -1
- package/umd/plugins/visibility.umd.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../../../libs/grid/src/lib/core/plugin/base-plugin.ts","../../../../../../libs/grid/src/lib/plugins/column-virtualization/column-virtualization.ts","../../../../../../libs/grid/src/lib/plugins/column-virtualization/ColumnVirtualizationPlugin.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 Virtualization Core Logic\n *\n * Pure functions for horizontal column virtualization operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ColumnVirtualizationViewport } from './types';\n\n/** Default column width when not specified */\nconst DEFAULT_COLUMN_WIDTH = 100;\n\n/**\n * Parse a column width value to pixels.\n * Handles number (px) and string formats.\n *\n * @param width - The width value from column config\n * @returns Width in pixels\n */\nexport function parseColumnWidth(width: string | number | undefined): number {\n if (width === undefined || width === null) {\n return DEFAULT_COLUMN_WIDTH;\n }\n\n if (typeof width === 'number') {\n return width;\n }\n\n // Handle string values - extract numeric part\n const numeric = parseFloat(width);\n if (!isNaN(numeric)) {\n return numeric;\n }\n\n return DEFAULT_COLUMN_WIDTH;\n}\n\n/**\n * Get array of column widths in pixels.\n *\n * @param columns - Column configurations\n * @returns Array of widths in pixels\n */\nexport function getColumnWidths(columns: readonly ColumnConfig[]): number[] {\n return columns.map((col) => parseColumnWidth(col.width));\n}\n\n/**\n * Compute cumulative left offsets for each column.\n *\n * @param columns - Column configurations\n * @returns Array of left offsets in pixels\n */\nexport function computeColumnOffsets(columns: readonly ColumnConfig[]): number[] {\n const offsets: number[] = [];\n let offset = 0;\n\n for (const col of columns) {\n offsets.push(offset);\n offset += parseColumnWidth(col.width);\n }\n\n return offsets;\n}\n\n/**\n * Compute total width of all columns.\n *\n * @param columns - Column configurations\n * @returns Total width in pixels\n */\nexport function computeTotalWidth(columns: readonly ColumnConfig[]): number {\n return columns.reduce((sum, col) => sum + parseColumnWidth(col.width), 0);\n}\n\n/**\n * Find the visible column range based on scroll position.\n * Uses binary search for efficient lookup in large column sets.\n *\n * @param scrollLeft - Current horizontal scroll position\n * @param viewportWidth - Width of the visible viewport\n * @param columnOffsets - Array of column left offsets\n * @param columnWidths - Array of column widths\n * @param overscan - Number of extra columns to render on each side\n * @returns Viewport information with visible column indices\n */\nexport function getVisibleColumnRange(\n scrollLeft: number,\n viewportWidth: number,\n columnOffsets: number[],\n columnWidths: number[],\n overscan: number\n): ColumnVirtualizationViewport {\n const columnCount = columnOffsets.length;\n\n if (columnCount === 0) {\n return { startCol: 0, endCol: 0, visibleColumns: [] };\n }\n\n // Binary search for first visible column\n let startCol = binarySearchFirstVisible(scrollLeft, columnOffsets, columnWidths);\n startCol = Math.max(0, startCol - overscan);\n\n // Find last visible column (without overscan first)\n const rightEdge = scrollLeft + viewportWidth;\n let endCol = startCol;\n\n for (let i = startCol; i < columnCount; i++) {\n if (columnOffsets[i] >= rightEdge) {\n endCol = i - 1;\n break;\n }\n endCol = i;\n }\n\n // Apply overscan to end (only once)\n endCol = Math.min(columnCount - 1, endCol + overscan);\n\n // Build array of visible column indices\n const visibleColumns: number[] = [];\n for (let i = startCol; i <= endCol; i++) {\n visibleColumns.push(i);\n }\n\n return { startCol, endCol, visibleColumns };\n}\n\n/**\n * Binary search to find the first column that is visible.\n *\n * @param scrollLeft - Current scroll position\n * @param columnOffsets - Array of column offsets\n * @param columnWidths - Array of column widths\n * @returns Index of first visible column\n */\nfunction binarySearchFirstVisible(scrollLeft: number, columnOffsets: number[], columnWidths: number[]): number {\n let low = 0;\n let high = columnOffsets.length - 1;\n\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n const colRight = columnOffsets[mid] + columnWidths[mid];\n\n if (colRight <= scrollLeft) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n\n return low;\n}\n\n/**\n * Determine if column virtualization should be active.\n *\n * @param columnCount - Number of columns\n * @param threshold - Column count threshold\n * @param autoEnable - Whether auto-enable is configured\n * @returns True if virtualization should be active\n */\nexport function shouldVirtualize(columnCount: number, threshold: number, autoEnable: boolean): boolean {\n if (!autoEnable) return false;\n return columnCount > threshold;\n}\n","/**\n * Column Virtualization Plugin (Class-based)\n *\n * Provides horizontal column virtualization for grids with many columns.\n * Significantly improves rendering performance when dealing with >30 columns.\n */\n\nimport { BaseGridPlugin, ScrollEvent } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n computeColumnOffsets,\n computeTotalWidth,\n getColumnWidths,\n getVisibleColumnRange,\n shouldVirtualize,\n} from './column-virtualization';\nimport type { ColumnVirtualizationConfig } from './types';\n\n/**\n * Column Virtualization Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new ColumnVirtualizationPlugin({ threshold: 30, overscan: 3 })\n * ```\n */\nexport class ColumnVirtualizationPlugin extends BaseGridPlugin<ColumnVirtualizationConfig> {\n readonly name = 'columnVirtualization';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<ColumnVirtualizationConfig> {\n return {\n enabled: true,\n autoEnable: true,\n threshold: 30,\n overscan: 3,\n };\n }\n\n // ===== Internal State =====\n private isVirtualized = false;\n private startCol = 0;\n private endCol = 0;\n private scrollLeft = 0;\n private totalWidth = 0;\n private columnWidths: number[] = [];\n private columnOffsets: number[] = [];\n\n // ===== Lifecycle =====\n\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Initialize state from current columns\n const columns = this.columns;\n this.columnWidths = getColumnWidths(columns);\n this.columnOffsets = computeColumnOffsets(columns);\n this.totalWidth = computeTotalWidth(columns);\n this.endCol = columns.length - 1;\n }\n\n override detach(): void {\n this.columnWidths = [];\n this.columnOffsets = [];\n this.isVirtualized = false;\n this.startCol = 0;\n this.endCol = 0;\n this.scrollLeft = 0;\n this.totalWidth = 0;\n }\n\n // ===== Hooks =====\n\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n const isVirtualized =\n this.config.enabled &&\n shouldVirtualize(columns.length, this.config.threshold ?? 30, this.config.autoEnable ?? true);\n\n // Update state with current column metrics\n this.isVirtualized = isVirtualized ?? false;\n this.columnWidths = getColumnWidths(columns);\n this.columnOffsets = computeColumnOffsets(columns);\n this.totalWidth = computeTotalWidth(columns);\n\n if (!isVirtualized) {\n this.startCol = 0;\n this.endCol = columns.length - 1;\n return [...columns];\n }\n\n // Get viewport width from grid element\n const viewportWidth = (this.grid as unknown as HTMLElement).clientWidth || 800;\n const viewport = getVisibleColumnRange(\n this.scrollLeft,\n viewportWidth,\n this.columnOffsets,\n this.columnWidths,\n this.config.overscan ?? 3\n );\n\n this.startCol = viewport.startCol;\n this.endCol = viewport.endCol;\n\n // Return only visible columns\n return viewport.visibleColumns.map((i) => columns[i]);\n }\n\n override afterRender(): void {\n if (!this.isVirtualized) return;\n\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n // Apply left padding to offset scrolled-out columns\n const leftPadding = this.columnOffsets[this.startCol] ?? 0;\n\n const headerRow = shadowRoot.querySelector('.header-row');\n const bodyRows = shadowRoot.querySelectorAll('.data-grid-row');\n\n if (headerRow) {\n (headerRow as HTMLElement).style.paddingLeft = `${leftPadding}px`;\n }\n\n bodyRows.forEach((row) => {\n (row as HTMLElement).style.paddingLeft = `${leftPadding}px`;\n });\n\n // Set total width for horizontal scrolling on the rows container\n const rowsContainer = shadowRoot.querySelector('.rows-viewport .rows');\n if (rowsContainer) {\n (rowsContainer as HTMLElement).style.width = `${this.totalWidth}px`;\n }\n }\n\n override onScroll(event: ScrollEvent): void {\n if (!this.isVirtualized) return;\n\n // Check if horizontal scroll position changed significantly\n const scrollDelta = Math.abs(event.scrollLeft - this.scrollLeft);\n if (scrollDelta < 1) return;\n\n // Update scroll position\n this.scrollLeft = event.scrollLeft;\n\n // Recalculate visible columns and request re-render\n this.requestRender();\n }\n\n // ===== Public API =====\n\n /**\n * Check if column virtualization is currently active.\n */\n getIsVirtualized(): boolean {\n return this.isVirtualized;\n }\n\n /**\n * Get the current visible column range.\n */\n getVisibleColumnRange(): { start: number; end: number } {\n return { start: this.startCol, end: this.endCol };\n }\n\n /**\n * Scroll the grid to bring a specific column into view.\n * @param columnIndex - Index of the column to scroll to\n */\n scrollToColumn(columnIndex: number): void {\n const offset = this.columnOffsets[columnIndex] ?? 0;\n const gridEl = this.grid as unknown as HTMLElement;\n // Scroll the grid element itself (it's the scroll container)\n gridEl.scrollLeft = offset;\n }\n\n /**\n * Get the left offset for a specific column.\n * @param columnIndex - Index of the column\n */\n getColumnOffset(columnIndex: number): number {\n return this.columnOffsets[columnIndex] ?? 0;\n }\n\n /**\n * Get the total width of all columns.\n */\n getTotalWidth(): number {\n return this.totalWidth;\n }\n}\n"],"names":["BaseGridPlugin","config","grid","PluginClass","eventName","detail","message","DEFAULT_COLUMN_WIDTH","parseColumnWidth","width","numeric","getColumnWidths","columns","col","computeColumnOffsets","offsets","offset","computeTotalWidth","sum","getVisibleColumnRange","scrollLeft","viewportWidth","columnOffsets","columnWidths","overscan","columnCount","startCol","binarySearchFirstVisible","rightEdge","endCol","i","visibleColumns","low","high","mid","shouldVirtualize","threshold","autoEnable","ColumnVirtualizationPlugin","isVirtualized","viewport","shadowRoot","leftPadding","headerRow","bodyRows","row","rowsContainer","event","columnIndex","gridEl"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../../../libs/grid/src/lib/core/plugin/base-plugin.ts","../../../../../../libs/grid/src/lib/plugins/column-virtualization/column-virtualization.ts","../../../../../../libs/grid/src/lib/plugins/column-virtualization/ColumnVirtualizationPlugin.ts"],"sourcesContent":["/**\n * Base Grid Plugin Class\n *\n * All plugins extend this abstract class.\n * Plugins are instantiated per-grid and manage their own state.\n */\n\nimport type { ColumnConfig, ColumnState, HeaderContentDefinition, ToolPanelDefinition } from '../types';\n\n// Forward declare to avoid circular imports\nexport interface GridElement {\n shadowRoot: ShadowRoot | null;\n rows: any[];\n columns: ColumnConfig[];\n gridConfig: any;\n /** AbortSignal that is aborted when the grid disconnects from the DOM */\n disconnectSignal: AbortSignal;\n requestRender(): void;\n requestAfterRender(): void;\n forceLayout(): Promise<void>;\n getPlugin<T extends BaseGridPlugin>(PluginClass: new (...args: any[]) => T): T | undefined;\n getPluginByName(name: string): BaseGridPlugin | undefined;\n dispatchEvent(event: Event): boolean;\n}\n\n/**\n * Keyboard modifier flags\n */\nexport interface KeyboardModifiers {\n ctrl?: boolean;\n shift?: boolean;\n alt?: boolean;\n meta?: boolean;\n}\n\n/**\n * Cell coordinates\n */\nexport interface CellCoords {\n row: number;\n col: number;\n}\n\n/**\n * Cell click event\n */\nexport interface CellClickEvent {\n rowIndex: number;\n colIndex: number;\n field: string;\n value: any;\n row: any;\n cellEl: HTMLElement;\n originalEvent: MouseEvent;\n}\n\n/**\n * Row click event\n */\nexport interface RowClickEvent {\n rowIndex: number;\n row: any;\n rowEl: HTMLElement;\n originalEvent: MouseEvent;\n}\n\n/**\n * Header click event\n */\nexport interface HeaderClickEvent {\n colIndex: number;\n field: string;\n column: ColumnConfig;\n headerEl: HTMLElement;\n originalEvent: MouseEvent;\n}\n\n/**\n * Scroll event\n */\nexport interface ScrollEvent {\n scrollTop: number;\n scrollLeft: number;\n scrollHeight: number;\n scrollWidth: number;\n clientHeight: number;\n clientWidth: number;\n originalEvent?: Event;\n}\n\n/**\n * Cell mouse event (for drag operations, selection, etc.)\n */\nexport interface CellMouseEvent {\n /** Event type: mousedown, mousemove, or mouseup */\n type: 'mousedown' | 'mousemove' | 'mouseup';\n /** Row index, undefined if not over a data cell */\n rowIndex?: number;\n /** Column index, undefined if not over a cell */\n colIndex?: number;\n /** Field name, undefined if not over a cell */\n field?: string;\n /** Cell value, undefined if not over a data cell */\n value?: unknown;\n /** Row data object, undefined if not over a data row */\n row?: unknown;\n /** Column configuration, undefined if not over a column */\n column?: ColumnConfig;\n /** The cell element, undefined if not over a cell */\n cellElement?: HTMLElement;\n /** The row element, undefined if not over a row */\n rowElement?: HTMLElement;\n /** Whether the event is over a header cell */\n isHeader: boolean;\n /** Cell coordinates if over a valid data cell */\n cell?: CellCoords;\n /** The original mouse event */\n originalEvent: MouseEvent;\n}\n\n/**\n * Context menu parameters\n */\nexport interface ContextMenuParams {\n x: number;\n y: number;\n rowIndex?: number;\n colIndex?: number;\n field?: string;\n value?: any;\n row?: any;\n column?: ColumnConfig;\n isHeader?: boolean;\n}\n\n/**\n * Context menu item\n */\nexport interface ContextMenuItem {\n id: string;\n label: string;\n icon?: string;\n disabled?: boolean;\n separator?: boolean;\n children?: ContextMenuItem[];\n action?: (params: ContextMenuParams) => void;\n}\n\n/**\n * Cell render context for plugin cell renderers.\n * Provides full context including position and editing state.\n *\n * Note: This differs from the core `CellRenderContext` in types.ts which is\n * simpler and used for column view renderers. This version provides additional\n * context needed by plugins that register custom cell renderers.\n */\nexport interface PluginCellRenderContext {\n /** The cell value */\n value: any;\n /** The field/column key */\n field: string;\n /** The row data object */\n row: any;\n /** Row index in the data array */\n rowIndex: number;\n /** Column index */\n colIndex: number;\n /** Column configuration */\n column: ColumnConfig;\n /** Whether the cell is currently in edit mode */\n isEditing: boolean;\n}\n\n/**\n * Header render context for plugin header renderers.\n */\nexport interface PluginHeaderRenderContext {\n /** Column configuration */\n column: ColumnConfig;\n /** Column index */\n colIndex: number;\n}\n\n/**\n * Cell renderer function type for plugins.\n */\nexport type CellRenderer = (ctx: PluginCellRenderContext) => string | HTMLElement;\n\n/**\n * Header renderer function type for plugins.\n */\nexport type HeaderRenderer = (ctx: PluginHeaderRenderContext) => string | HTMLElement;\n\n/**\n * Cell editor interface for plugins.\n */\nexport interface CellEditor {\n create(ctx: PluginCellRenderContext, commitFn: (value: any) => void, cancelFn: () => void): HTMLElement;\n getValue?(element: HTMLElement): any;\n focus?(element: HTMLElement): void;\n}\n\n/**\n * Abstract base class for all grid plugins.\n *\n * @template TConfig - Configuration type for the plugin\n */\nexport abstract class BaseGridPlugin<TConfig = unknown> {\n /** Unique plugin identifier (derived from class name by default) */\n abstract readonly name: string;\n\n /** Plugin version - override in subclass if needed */\n readonly version: string = '1.0.0';\n\n /** CSS styles to inject into the grid's shadow DOM */\n readonly styles?: string;\n\n /** Custom cell renderers keyed by type name */\n readonly cellRenderers?: Record<string, CellRenderer>;\n\n /** Custom header renderers keyed by type name */\n readonly headerRenderers?: Record<string, HeaderRenderer>;\n\n /** Custom cell editors keyed by type name */\n readonly cellEditors?: Record<string, CellEditor>;\n\n /** The grid instance this plugin is attached to */\n protected grid!: GridElement;\n\n /** Plugin configuration - merged with defaults in attach() */\n protected config!: TConfig;\n\n /** User-provided configuration from constructor */\n private readonly userConfig: Partial<TConfig>;\n\n /**\n * Default configuration - subclasses should override this getter.\n * Note: This must be a getter (not property initializer) for proper inheritance\n * since property initializers run after parent constructor.\n */\n protected get defaultConfig(): Partial<TConfig> {\n return {};\n }\n\n constructor(config: Partial<TConfig> = {}) {\n this.userConfig = config;\n }\n\n /**\n * Called when the plugin is attached to a grid.\n * Override to set up event listeners, initialize state, etc.\n */\n attach(grid: GridElement): void {\n this.grid = grid;\n // Merge config here (after subclass construction is complete)\n this.config = { ...this.defaultConfig, ...this.userConfig } as TConfig;\n }\n\n /**\n * Called when the plugin is detached from a grid.\n * Override to clean up event listeners, timers, etc.\n */\n detach(): void {\n // Override in subclass\n }\n\n /**\n * Get another plugin instance from the same grid.\n * Use for inter-plugin communication.\n */\n protected getPlugin<T extends BaseGridPlugin>(PluginClass: new (...args: any[]) => T): T | undefined {\n return this.grid?.getPlugin(PluginClass);\n }\n\n /**\n * Emit a custom event from the grid.\n */\n protected emit<T>(eventName: string, detail: T): void {\n this.grid?.dispatchEvent?.(new CustomEvent(eventName, { detail, bubbles: true }));\n }\n\n /**\n * Request a re-render of the grid.\n */\n protected requestRender(): void {\n this.grid?.requestRender?.();\n }\n\n /**\n * Request a lightweight style update without rebuilding DOM.\n * Use this instead of requestRender() when only CSS classes need updating.\n */\n protected requestAfterRender(): void {\n this.grid?.requestAfterRender?.();\n }\n\n /**\n * Get the current rows from the grid.\n */\n protected get rows(): any[] {\n return this.grid?.rows ?? [];\n }\n\n /**\n * Get the original unfiltered/unprocessed rows from the grid.\n * Use this when you need all source data regardless of active filters.\n */\n protected get sourceRows(): any[] {\n return (this.grid as any)?.sourceRows ?? [];\n }\n\n /**\n * Get the current columns from the grid.\n */\n protected get columns(): ColumnConfig[] {\n return this.grid?.columns ?? [];\n }\n\n /**\n * Get only visible columns from the grid (excludes hidden).\n * Use this for rendering that needs to match the grid template.\n */\n protected get visibleColumns(): ColumnConfig[] {\n return (this.grid as any)?.visibleColumns ?? [];\n }\n\n /**\n * Get the shadow root of the grid.\n */\n protected get shadowRoot(): ShadowRoot | null {\n return this.grid?.shadowRoot ?? null;\n }\n\n /**\n * Get the disconnect signal for event listener cleanup.\n * This signal is aborted when the grid disconnects from the DOM.\n * Use this when adding event listeners that should be cleaned up automatically.\n *\n * Best for:\n * - Document/window-level listeners added in attach()\n * - Listeners on the grid element itself\n * - Any listener that should persist across renders\n *\n * Not needed for:\n * - Listeners on elements created in afterRender() (removed with element)\n *\n * @example\n * element.addEventListener('click', handler, { signal: this.disconnectSignal });\n * document.addEventListener('keydown', handler, { signal: this.disconnectSignal });\n */\n protected get disconnectSignal(): AbortSignal {\n return this.grid?.disconnectSignal;\n }\n\n /**\n * Log a warning message.\n */\n protected warn(message: string): void {\n console.warn(`[tbw-grid:${this.name}] ${message}`);\n }\n\n // ===== Lifecycle Hooks (override as needed) =====\n\n /**\n * Transform rows before rendering.\n * Called during each render cycle before rows are rendered to the DOM.\n * Use this to filter, sort, or add computed properties to rows.\n *\n * @param rows - The current rows array (readonly to encourage returning a new array)\n * @returns The modified rows array to render\n *\n * @example\n * ```ts\n * processRows(rows: readonly any[]): any[] {\n * // Filter out hidden rows\n * return rows.filter(row => !row._hidden);\n * }\n * ```\n *\n * @example\n * ```ts\n * processRows(rows: readonly any[]): any[] {\n * // Add computed properties\n * return rows.map(row => ({\n * ...row,\n * _fullName: `${row.firstName} ${row.lastName}`\n * }));\n * }\n * ```\n */\n processRows?(rows: readonly any[]): any[];\n\n /**\n * Transform columns before rendering.\n * Called during each render cycle before column headers and cells are rendered.\n * Use this to add, remove, or modify column definitions.\n *\n * @param columns - The current columns array (readonly to encourage returning a new array)\n * @returns The modified columns array to render\n *\n * @example\n * ```ts\n * processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n * // Add a selection checkbox column\n * return [\n * { field: '_select', header: '', width: 40 },\n * ...columns\n * ];\n * }\n * ```\n */\n processColumns?(columns: readonly ColumnConfig[]): ColumnConfig[];\n\n /**\n * Called before each render cycle begins.\n * Use this to prepare state or cache values needed during rendering.\n *\n * @example\n * ```ts\n * beforeRender(): void {\n * this.visibleRowCount = this.calculateVisibleRows();\n * }\n * ```\n */\n beforeRender?(): void;\n\n /**\n * Called after each render cycle completes.\n * Use this for DOM manipulation, adding event listeners to rendered elements,\n * or applying visual effects like selection highlights.\n *\n * @example\n * ```ts\n * afterRender(): void {\n * // Apply selection styling to rendered rows\n * const rows = this.shadowRoot?.querySelectorAll('.data-row');\n * rows?.forEach((row, i) => {\n * row.classList.toggle('selected', this.selectedRows.has(i));\n * });\n * }\n * ```\n */\n afterRender?(): void;\n\n /**\n * Called after scroll-triggered row rendering completes.\n * This is a lightweight hook for applying visual state to recycled DOM elements.\n * Use this instead of afterRender when you need to reapply styling during scroll.\n *\n * Performance note: This is called frequently during scroll. Keep implementation fast.\n *\n * @example\n * ```ts\n * onScrollRender(): void {\n * // Reapply selection state to visible cells\n * this.applySelectionToVisibleCells();\n * }\n * ```\n */\n onScrollRender?(): void;\n\n /**\n * Render a custom row, bypassing the default row rendering.\n * Use this for special row types like group headers, detail rows, or footers.\n *\n * @param row - The row data object\n * @param rowEl - The row DOM element to render into\n * @param rowIndex - The index of the row in the data array\n * @returns `true` if the plugin handled rendering (prevents default), `false`/`void` for default rendering\n *\n * @example\n * ```ts\n * renderRow(row: any, rowEl: HTMLElement, rowIndex: number): boolean | void {\n * if (row._isGroupHeader) {\n * rowEl.innerHTML = `<div class=\"group-header\">${row._groupLabel}</div>`;\n * return true; // Handled - skip default rendering\n * }\n * // Return void to let default rendering proceed\n * }\n * ```\n */\n renderRow?(row: any, rowEl: HTMLElement, rowIndex: number): boolean | void;\n\n // ===== Interaction Hooks (override as needed) =====\n\n /**\n * Handle keyboard events on the grid.\n * Called when a key is pressed while the grid or a cell has focus.\n *\n * @param event - The native KeyboardEvent\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\n *\n * @example\n * ```ts\n * onKeyDown(event: KeyboardEvent): boolean | void {\n * // Handle Ctrl+A for select all\n * if (event.ctrlKey && event.key === 'a') {\n * this.selectAllRows();\n * return true; // Prevent default browser select-all\n * }\n * }\n * ```\n */\n onKeyDown?(event: KeyboardEvent): boolean | void;\n\n /**\n * Handle cell click events.\n * Called when a data cell is clicked (not headers).\n *\n * @param event - Cell click event with row/column context\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\n *\n * @example\n * ```ts\n * onCellClick(event: CellClickEvent): boolean | void {\n * if (event.field === '_select') {\n * this.toggleRowSelection(event.rowIndex);\n * return true; // Handled\n * }\n * }\n * ```\n */\n onCellClick?(event: CellClickEvent): boolean | void;\n\n /**\n * Handle row click events.\n * Called when any part of a data row is clicked.\n * Note: This is called in addition to onCellClick, not instead of.\n *\n * @param event - Row click event with row context\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\n *\n * @example\n * ```ts\n * onRowClick(event: RowClickEvent): boolean | void {\n * if (this.config.mode === 'row') {\n * this.selectRow(event.rowIndex, event.originalEvent);\n * return true;\n * }\n * }\n * ```\n */\n onRowClick?(event: RowClickEvent): boolean | void;\n\n /**\n * Handle header click events.\n * Called when a column header is clicked. Commonly used for sorting.\n *\n * @param event - Header click event with column context\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\n *\n * @example\n * ```ts\n * onHeaderClick(event: HeaderClickEvent): boolean | void {\n * if (event.column.sortable !== false) {\n * this.toggleSort(event.field);\n * return true;\n * }\n * }\n * ```\n */\n onHeaderClick?(event: HeaderClickEvent): boolean | void;\n\n /**\n * Handle scroll events on the grid viewport.\n * Called during scrolling. Note: This may be called frequently; debounce if needed.\n *\n * @param event - Scroll event with scroll position and viewport dimensions\n *\n * @example\n * ```ts\n * onScroll(event: ScrollEvent): void {\n * // Update sticky column positions\n * this.updateStickyPositions(event.scrollLeft);\n * }\n * ```\n */\n onScroll?(event: ScrollEvent): void;\n\n /**\n * Handle cell mousedown events.\n * Used for initiating drag operations like range selection or column resize.\n *\n * @param event - Mouse event with cell context\n * @returns `true` to indicate drag started (prevents text selection), `false`/`void` otherwise\n *\n * @example\n * ```ts\n * onCellMouseDown(event: CellMouseEvent): boolean | void {\n * if (event.rowIndex !== undefined && this.config.mode === 'range') {\n * this.startDragSelection(event.rowIndex, event.colIndex);\n * return true; // Prevent text selection\n * }\n * }\n * ```\n */\n onCellMouseDown?(event: CellMouseEvent): boolean | void;\n\n /**\n * Handle cell mousemove events during drag operations.\n * Only called when a drag is in progress (after mousedown returned true).\n *\n * @param event - Mouse event with current cell context\n * @returns `true` to continue handling the drag, `false`/`void` otherwise\n *\n * @example\n * ```ts\n * onCellMouseMove(event: CellMouseEvent): boolean | void {\n * if (this.isDragging && event.rowIndex !== undefined) {\n * this.extendSelection(event.rowIndex, event.colIndex);\n * return true;\n * }\n * }\n * ```\n */\n onCellMouseMove?(event: CellMouseEvent): boolean | void;\n\n /**\n * Handle cell mouseup events to end drag operations.\n *\n * @param event - Mouse event with final cell context\n * @returns `true` if drag was finalized, `false`/`void` otherwise\n *\n * @example\n * ```ts\n * onCellMouseUp(event: CellMouseEvent): boolean | void {\n * if (this.isDragging) {\n * this.finalizeDragSelection();\n * this.isDragging = false;\n * return true;\n * }\n * }\n * ```\n */\n onCellMouseUp?(event: CellMouseEvent): boolean | void;\n\n /**\n * Provide context menu items when right-clicking on the grid.\n * Multiple plugins can contribute items; they are merged into a single menu.\n *\n * @param params - Context about where the menu was triggered (row, column, etc.)\n * @returns Array of menu items to display\n *\n * @example\n * ```ts\n * getContextMenuItems(params: ContextMenuParams): ContextMenuItem[] {\n * if (params.isHeader) {\n * return [\n * { id: 'sort-asc', label: 'Sort Ascending', action: () => this.sortAsc(params.field) },\n * { id: 'sort-desc', label: 'Sort Descending', action: () => this.sortDesc(params.field) },\n * ];\n * }\n * return [\n * { id: 'copy', label: 'Copy Cell', action: () => this.copyCell(params) },\n * ];\n * }\n * ```\n */\n getContextMenuItems?(params: ContextMenuParams): ContextMenuItem[];\n\n // ===== Column State Hooks (override as needed) =====\n\n /**\n * Contribute plugin-specific state for a column.\n * Called by the grid when collecting column state for serialization.\n * Plugins can add their own properties to the column state.\n *\n * @param field - The field name of the column\n * @returns Partial column state with plugin-specific properties, or undefined if no state to contribute\n *\n * @example\n * ```ts\n * getColumnState(field: string): Partial<ColumnState> | undefined {\n * const filterModel = this.filterModels.get(field);\n * if (filterModel) {\n * // Uses module augmentation to add filter property to ColumnState\n * return { filter: filterModel } as Partial<ColumnState>;\n * }\n * return undefined;\n * }\n * ```\n */\n getColumnState?(field: string): Partial<ColumnState> | undefined;\n\n /**\n * Apply plugin-specific state to a column.\n * Called by the grid when restoring column state from serialized data.\n * Plugins should restore their internal state based on the provided state.\n *\n * @param field - The field name of the column\n * @param state - The column state to apply (may contain plugin-specific properties)\n *\n * @example\n * ```ts\n * applyColumnState(field: string, state: ColumnState): void {\n * // Check for filter property added via module augmentation\n * const filter = (state as any).filter;\n * if (filter) {\n * this.filterModels.set(field, filter);\n * this.applyFilter();\n * }\n * }\n * ```\n */\n applyColumnState?(field: string, state: ColumnState): void;\n\n // ===== Shell Integration Hooks (override as needed) =====\n\n /**\n * Register a tool panel for this plugin.\n * Return undefined if plugin has no tool panel.\n * The shell will create a toolbar toggle button and render the panel content\n * when the user opens the panel.\n *\n * @returns Tool panel definition, or undefined if plugin has no panel\n *\n * @example\n * ```ts\n * getToolPanel(): ToolPanelDefinition | undefined {\n * return {\n * id: 'columns',\n * title: 'Columns',\n * icon: '☰',\n * tooltip: 'Show/hide columns',\n * order: 10,\n * render: (container) => {\n * this.renderColumnList(container);\n * return () => this.cleanup();\n * },\n * };\n * }\n * ```\n */\n getToolPanel?(): ToolPanelDefinition | undefined;\n\n /**\n * Register content for the shell header center section.\n * Return undefined if plugin has no header content.\n * Examples: search input, selection summary, status indicators.\n *\n * @returns Header content definition, or undefined if plugin has no header content\n *\n * @example\n * ```ts\n * getHeaderContent(): HeaderContentDefinition | undefined {\n * return {\n * id: 'quick-filter',\n * order: 10,\n * render: (container) => {\n * const input = document.createElement('input');\n * input.type = 'text';\n * input.placeholder = 'Search...';\n * input.addEventListener('input', this.handleInput);\n * container.appendChild(input);\n * return () => input.removeEventListener('input', this.handleInput);\n * },\n * };\n * }\n * ```\n */\n getHeaderContent?(): HeaderContentDefinition | undefined;\n}\n","/**\n * Column Virtualization Core Logic\n *\n * Pure functions for horizontal column virtualization operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ColumnVirtualizationViewport } from './types';\n\n/** Default column width when not specified */\nconst DEFAULT_COLUMN_WIDTH = 100;\n\n/**\n * Parse a column width value to pixels.\n * Handles number (px) and string formats.\n *\n * @param width - The width value from column config\n * @returns Width in pixels\n */\nexport function parseColumnWidth(width: string | number | undefined): number {\n if (width === undefined || width === null) {\n return DEFAULT_COLUMN_WIDTH;\n }\n\n if (typeof width === 'number') {\n return width;\n }\n\n // Handle string values - extract numeric part\n const numeric = parseFloat(width);\n if (!isNaN(numeric)) {\n return numeric;\n }\n\n return DEFAULT_COLUMN_WIDTH;\n}\n\n/**\n * Get array of column widths in pixels.\n *\n * @param columns - Column configurations\n * @returns Array of widths in pixels\n */\nexport function getColumnWidths(columns: readonly ColumnConfig[]): number[] {\n return columns.map((col) => parseColumnWidth(col.width));\n}\n\n/**\n * Compute cumulative left offsets for each column.\n *\n * @param columns - Column configurations\n * @returns Array of left offsets in pixels\n */\nexport function computeColumnOffsets(columns: readonly ColumnConfig[]): number[] {\n const offsets: number[] = [];\n let offset = 0;\n\n for (const col of columns) {\n offsets.push(offset);\n offset += parseColumnWidth(col.width);\n }\n\n return offsets;\n}\n\n/**\n * Compute total width of all columns.\n *\n * @param columns - Column configurations\n * @returns Total width in pixels\n */\nexport function computeTotalWidth(columns: readonly ColumnConfig[]): number {\n return columns.reduce((sum, col) => sum + parseColumnWidth(col.width), 0);\n}\n\n/**\n * Find the visible column range based on scroll position.\n * Uses binary search for efficient lookup in large column sets.\n *\n * @param scrollLeft - Current horizontal scroll position\n * @param viewportWidth - Width of the visible viewport\n * @param columnOffsets - Array of column left offsets\n * @param columnWidths - Array of column widths\n * @param overscan - Number of extra columns to render on each side\n * @returns Viewport information with visible column indices\n */\nexport function getVisibleColumnRange(\n scrollLeft: number,\n viewportWidth: number,\n columnOffsets: number[],\n columnWidths: number[],\n overscan: number\n): ColumnVirtualizationViewport {\n const columnCount = columnOffsets.length;\n\n if (columnCount === 0) {\n return { startCol: 0, endCol: 0, visibleColumns: [] };\n }\n\n // Binary search for first visible column\n let startCol = binarySearchFirstVisible(scrollLeft, columnOffsets, columnWidths);\n startCol = Math.max(0, startCol - overscan);\n\n // Find last visible column (without overscan first)\n const rightEdge = scrollLeft + viewportWidth;\n let endCol = startCol;\n\n for (let i = startCol; i < columnCount; i++) {\n if (columnOffsets[i] >= rightEdge) {\n endCol = i - 1;\n break;\n }\n endCol = i;\n }\n\n // Apply overscan to end (only once)\n endCol = Math.min(columnCount - 1, endCol + overscan);\n\n // Build array of visible column indices\n const visibleColumns: number[] = [];\n for (let i = startCol; i <= endCol; i++) {\n visibleColumns.push(i);\n }\n\n return { startCol, endCol, visibleColumns };\n}\n\n/**\n * Binary search to find the first column that is visible.\n *\n * @param scrollLeft - Current scroll position\n * @param columnOffsets - Array of column offsets\n * @param columnWidths - Array of column widths\n * @returns Index of first visible column\n */\nfunction binarySearchFirstVisible(scrollLeft: number, columnOffsets: number[], columnWidths: number[]): number {\n let low = 0;\n let high = columnOffsets.length - 1;\n\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n const colRight = columnOffsets[mid] + columnWidths[mid];\n\n if (colRight <= scrollLeft) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n\n return low;\n}\n\n/**\n * Determine if column virtualization should be active.\n *\n * @param columnCount - Number of columns\n * @param threshold - Column count threshold\n * @param autoEnable - Whether auto-enable is configured\n * @returns True if virtualization should be active\n */\nexport function shouldVirtualize(columnCount: number, threshold: number, autoEnable: boolean): boolean {\n if (!autoEnable) return false;\n return columnCount > threshold;\n}\n","/**\n * Column Virtualization Plugin (Class-based)\n *\n * Provides horizontal column virtualization for grids with many columns.\n * Significantly improves rendering performance when dealing with >30 columns.\n */\n\nimport { BaseGridPlugin, ScrollEvent } from '../../core/plugin/base-plugin';\nimport type { ColumnConfig } from '../../core/types';\nimport {\n computeColumnOffsets,\n computeTotalWidth,\n getColumnWidths,\n getVisibleColumnRange,\n shouldVirtualize,\n} from './column-virtualization';\nimport type { ColumnVirtualizationConfig } from './types';\n\n/**\n * Column Virtualization Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new ColumnVirtualizationPlugin({ threshold: 30, overscan: 3 })\n * ```\n */\nexport class ColumnVirtualizationPlugin extends BaseGridPlugin<ColumnVirtualizationConfig> {\n readonly name = 'columnVirtualization';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<ColumnVirtualizationConfig> {\n return {\n enabled: true,\n autoEnable: true,\n threshold: 30,\n overscan: 3,\n };\n }\n\n // ===== Internal State =====\n private isVirtualized = false;\n private startCol = 0;\n private endCol = 0;\n private scrollLeft = 0;\n private totalWidth = 0;\n private columnWidths: number[] = [];\n private columnOffsets: number[] = [];\n\n // ===== Lifecycle =====\n\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Initialize state from current columns\n const columns = this.columns;\n this.columnWidths = getColumnWidths(columns);\n this.columnOffsets = computeColumnOffsets(columns);\n this.totalWidth = computeTotalWidth(columns);\n this.endCol = columns.length - 1;\n }\n\n override detach(): void {\n this.columnWidths = [];\n this.columnOffsets = [];\n this.isVirtualized = false;\n this.startCol = 0;\n this.endCol = 0;\n this.scrollLeft = 0;\n this.totalWidth = 0;\n }\n\n // ===== Hooks =====\n\n override processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n const isVirtualized =\n this.config.enabled &&\n shouldVirtualize(columns.length, this.config.threshold ?? 30, this.config.autoEnable ?? true);\n\n // Update state with current column metrics\n this.isVirtualized = isVirtualized ?? false;\n this.columnWidths = getColumnWidths(columns);\n this.columnOffsets = computeColumnOffsets(columns);\n this.totalWidth = computeTotalWidth(columns);\n\n if (!isVirtualized) {\n this.startCol = 0;\n this.endCol = columns.length - 1;\n return [...columns];\n }\n\n // Get viewport width from grid element\n const viewportWidth = (this.grid as unknown as HTMLElement).clientWidth || 800;\n const viewport = getVisibleColumnRange(\n this.scrollLeft,\n viewportWidth,\n this.columnOffsets,\n this.columnWidths,\n this.config.overscan ?? 3\n );\n\n this.startCol = viewport.startCol;\n this.endCol = viewport.endCol;\n\n // Return only visible columns\n return viewport.visibleColumns.map((i) => columns[i]);\n }\n\n override afterRender(): void {\n if (!this.isVirtualized) return;\n\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n // Apply left padding to offset scrolled-out columns\n const leftPadding = this.columnOffsets[this.startCol] ?? 0;\n\n const headerRow = shadowRoot.querySelector('.header-row');\n const bodyRows = shadowRoot.querySelectorAll('.data-grid-row');\n\n if (headerRow) {\n (headerRow as HTMLElement).style.paddingLeft = `${leftPadding}px`;\n }\n\n bodyRows.forEach((row) => {\n (row as HTMLElement).style.paddingLeft = `${leftPadding}px`;\n });\n\n // Set total width for horizontal scrolling on the rows container\n const rowsContainer = shadowRoot.querySelector('.rows-viewport .rows');\n if (rowsContainer) {\n (rowsContainer as HTMLElement).style.width = `${this.totalWidth}px`;\n }\n }\n\n override onScroll(event: ScrollEvent): void {\n if (!this.isVirtualized) return;\n\n // Check if horizontal scroll position changed significantly\n const scrollDelta = Math.abs(event.scrollLeft - this.scrollLeft);\n if (scrollDelta < 1) return;\n\n // Update scroll position\n this.scrollLeft = event.scrollLeft;\n\n // Recalculate visible columns and request re-render\n this.requestRender();\n }\n\n // ===== Public API =====\n\n /**\n * Check if column virtualization is currently active.\n */\n getIsVirtualized(): boolean {\n return this.isVirtualized;\n }\n\n /**\n * Get the current visible column range.\n */\n getVisibleColumnRange(): { start: number; end: number } {\n return { start: this.startCol, end: this.endCol };\n }\n\n /**\n * Scroll the grid to bring a specific column into view.\n * @param columnIndex - Index of the column to scroll to\n */\n scrollToColumn(columnIndex: number): void {\n const offset = this.columnOffsets[columnIndex] ?? 0;\n const gridEl = this.grid as unknown as HTMLElement;\n // Scroll the grid element itself (it's the scroll container)\n gridEl.scrollLeft = offset;\n }\n\n /**\n * Get the left offset for a specific column.\n * @param columnIndex - Index of the column\n */\n getColumnOffset(columnIndex: number): number {\n return this.columnOffsets[columnIndex] ?? 0;\n }\n\n /**\n * Get the total width of all columns.\n */\n getTotalWidth(): number {\n return this.totalWidth;\n }\n}\n"],"names":["BaseGridPlugin","config","grid","PluginClass","eventName","detail","message","DEFAULT_COLUMN_WIDTH","parseColumnWidth","width","numeric","getColumnWidths","columns","col","computeColumnOffsets","offsets","offset","computeTotalWidth","sum","getVisibleColumnRange","scrollLeft","viewportWidth","columnOffsets","columnWidths","overscan","columnCount","startCol","binarySearchFirstVisible","rightEdge","endCol","i","visibleColumns","low","high","mid","shouldVirtualize","threshold","autoEnable","ColumnVirtualizationPlugin","isVirtualized","viewport","shadowRoot","leftPadding","headerRow","bodyRows","row","rowsContainer","event","columnIndex","gridEl"],"mappings":"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"}
|
|
@@ -97,6 +97,26 @@ class v {
|
|
|
97
97
|
get shadowRoot() {
|
|
98
98
|
return this.grid?.shadowRoot ?? null;
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Get the disconnect signal for event listener cleanup.
|
|
102
|
+
* This signal is aborted when the grid disconnects from the DOM.
|
|
103
|
+
* Use this when adding event listeners that should be cleaned up automatically.
|
|
104
|
+
*
|
|
105
|
+
* Best for:
|
|
106
|
+
* - Document/window-level listeners added in attach()
|
|
107
|
+
* - Listeners on the grid element itself
|
|
108
|
+
* - Any listener that should persist across renders
|
|
109
|
+
*
|
|
110
|
+
* Not needed for:
|
|
111
|
+
* - Listeners on elements created in afterRender() (removed with element)
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* element.addEventListener('click', handler, { signal: this.disconnectSignal });
|
|
115
|
+
* document.addEventListener('keydown', handler, { signal: this.disconnectSignal });
|
|
116
|
+
*/
|
|
117
|
+
get disconnectSignal() {
|
|
118
|
+
return this.grid?.disconnectSignal;
|
|
119
|
+
}
|
|
100
120
|
/**
|
|
101
121
|
* Log a warning message.
|
|
102
122
|
*/
|
|
@@ -104,7 +124,7 @@ class v {
|
|
|
104
124
|
console.warn(`[tbw-grid:${this.name}] ${e}`);
|
|
105
125
|
}
|
|
106
126
|
}
|
|
107
|
-
function
|
|
127
|
+
function b(i, e) {
|
|
108
128
|
return (typeof i == "function" ? i(e) : i).filter((n) => !(n.hidden === !0 || typeof n.hidden == "function" && n.hidden(e)));
|
|
109
129
|
}
|
|
110
130
|
function y(i, e) {
|
|
@@ -135,11 +155,11 @@ function x(i, e, l) {
|
|
|
135
155
|
const o = document.createElement("span");
|
|
136
156
|
o.className = "tbw-context-menu-arrow", o.textContent = "▶", s.appendChild(o), s.addEventListener("mouseenter", () => {
|
|
137
157
|
if (s.querySelector(".tbw-context-menu") || !t.subMenu) return;
|
|
138
|
-
const
|
|
158
|
+
const c = b(t.subMenu, e), d = x(c, e, l);
|
|
139
159
|
d.classList.add("tbw-context-submenu"), d.style.position = "absolute", d.style.left = "100%", d.style.top = "0", s.style.position = "relative", s.appendChild(d);
|
|
140
160
|
}), s.addEventListener("mouseleave", () => {
|
|
141
|
-
const
|
|
142
|
-
|
|
161
|
+
const a = s.querySelector(".tbw-context-menu");
|
|
162
|
+
a && a.remove();
|
|
143
163
|
});
|
|
144
164
|
}
|
|
145
165
|
!r && t.action && !t.subMenu && s.addEventListener("click", (o) => {
|
|
@@ -206,7 +226,7 @@ const C = `
|
|
|
206
226
|
background: light-dark(#d0d0d4, #454545);
|
|
207
227
|
margin: 4px 0;
|
|
208
228
|
}
|
|
209
|
-
`,
|
|
229
|
+
`, g = [
|
|
210
230
|
{
|
|
211
231
|
id: "copy",
|
|
212
232
|
name: "Copy",
|
|
@@ -230,7 +250,7 @@ class M extends v {
|
|
|
230
250
|
get defaultConfig() {
|
|
231
251
|
return {
|
|
232
252
|
enabled: !0,
|
|
233
|
-
items:
|
|
253
|
+
items: g
|
|
234
254
|
};
|
|
235
255
|
}
|
|
236
256
|
// ===== Internal State =====
|
|
@@ -266,10 +286,10 @@ class M extends v {
|
|
|
266
286
|
const s = t.target, r = s.closest("[data-row][data-col]"), u = s.closest(".header-cell");
|
|
267
287
|
let o;
|
|
268
288
|
if (r) {
|
|
269
|
-
const
|
|
289
|
+
const c = parseInt(r.getAttribute("data-row") ?? "-1", 10), d = parseInt(r.getAttribute("data-col") ?? "-1", 10), h = this.columns[d], w = this.rows[c];
|
|
270
290
|
o = {
|
|
271
291
|
row: w,
|
|
272
|
-
rowIndex:
|
|
292
|
+
rowIndex: c,
|
|
273
293
|
column: h,
|
|
274
294
|
columnIndex: d,
|
|
275
295
|
field: h?.field ?? "",
|
|
@@ -278,12 +298,12 @@ class M extends v {
|
|
|
278
298
|
event: t
|
|
279
299
|
};
|
|
280
300
|
} else if (u) {
|
|
281
|
-
const
|
|
301
|
+
const c = parseInt(u.getAttribute("data-col") ?? "-1", 10), d = this.columns[c];
|
|
282
302
|
o = {
|
|
283
303
|
row: null,
|
|
284
304
|
rowIndex: -1,
|
|
285
305
|
column: d,
|
|
286
|
-
columnIndex:
|
|
306
|
+
columnIndex: c,
|
|
287
307
|
field: d?.field ?? "",
|
|
288
308
|
value: null,
|
|
289
309
|
isHeader: !0,
|
|
@@ -292,10 +312,10 @@ class M extends v {
|
|
|
292
312
|
} else
|
|
293
313
|
return;
|
|
294
314
|
this.params = o, this.position = { x: t.clientX, y: t.clientY };
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}), document.body.appendChild(this.menuElement), E(this.menuElement, t.clientX, t.clientY), this.isOpen = !0, this.emit("context-menu-open", { params: o, items:
|
|
315
|
+
const a = b(this.config.items ?? g, o);
|
|
316
|
+
a.length && (this.menuElement && this.menuElement.remove(), this.menuElement = x(a, o, (c) => {
|
|
317
|
+
c.action && c.action(o), this.menuElement?.remove(), this.menuElement = null, this.isOpen = !1;
|
|
318
|
+
}), document.body.appendChild(this.menuElement), E(this.menuElement, t.clientX, t.clientY), this.isOpen = !0, this.emit("context-menu-open", { params: o, items: a }));
|
|
299
319
|
}));
|
|
300
320
|
}
|
|
301
321
|
// ===== Public API =====
|
|
@@ -315,7 +335,7 @@ class M extends v {
|
|
|
315
335
|
value: n.value ?? null,
|
|
316
336
|
isHeader: n.isHeader ?? !1,
|
|
317
337
|
event: n.event ?? new MouseEvent("contextmenu")
|
|
318
|
-
}, s =
|
|
338
|
+
}, s = b(this.config.items ?? g, t);
|
|
319
339
|
this.menuElement && this.menuElement.remove(), this.menuElement = x(s, t, (r) => {
|
|
320
340
|
r.action && r.action(t), this.menuElement?.remove(), this.menuElement = null, this.isOpen = !1;
|
|
321
341
|
}), document.body.appendChild(this.menuElement), E(this.menuElement, e, l), this.isOpen = !0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../../../libs/grid/src/lib/core/plugin/base-plugin.ts","../../../../../../libs/grid/src/lib/plugins/context-menu/menu.ts","../../../../../../libs/grid/src/lib/plugins/context-menu/ContextMenuPlugin.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 * Context Menu Rendering Logic\n *\n * Pure functions for building and positioning context menus.\n */\n\nimport type { ContextMenuItem, ContextMenuParams } from './types';\n\n/**\n * Build the visible menu items by resolving dynamic items and filtering hidden ones.\n *\n * @param items - Menu items configuration (array or factory function)\n * @param params - Context menu parameters for evaluating dynamic properties\n * @returns Filtered array of visible menu items\n */\nexport function buildMenuItems(\n items: ContextMenuItem[] | ((params: ContextMenuParams) => ContextMenuItem[]),\n params: ContextMenuParams\n): ContextMenuItem[] {\n const menuItems = typeof items === 'function' ? items(params) : items;\n\n return menuItems.filter((item) => {\n if (item.hidden === true) return false;\n if (typeof item.hidden === 'function' && item.hidden(params)) return false;\n return true;\n });\n}\n\n/**\n * Check if a menu item is disabled.\n *\n * @param item - The menu item to check\n * @param params - Context menu parameters for evaluating dynamic disabled state\n * @returns True if the item is disabled\n */\nexport function isItemDisabled(item: ContextMenuItem, params: ContextMenuParams): boolean {\n if (item.disabled === true) return true;\n if (typeof item.disabled === 'function') return item.disabled(params);\n return false;\n}\n\n/**\n * Create the menu DOM element from a list of menu items.\n *\n * @param items - Array of menu items to render\n * @param params - Context menu parameters for evaluating dynamic properties\n * @param onAction - Callback when a menu item action is triggered\n * @returns The created menu element\n */\nexport function createMenuElement(\n items: ContextMenuItem[],\n params: ContextMenuParams,\n onAction: (item: ContextMenuItem) => void\n): HTMLElement {\n const menu = document.createElement('div');\n menu.className = 'tbw-context-menu';\n menu.setAttribute('role', 'menu');\n\n for (const item of items) {\n if (item.separator) {\n const separator = document.createElement('div');\n separator.className = 'tbw-context-menu-separator';\n separator.setAttribute('role', 'separator');\n menu.appendChild(separator);\n continue;\n }\n\n const menuItem = document.createElement('div');\n menuItem.className = 'tbw-context-menu-item';\n if (item.cssClass) menuItem.classList.add(item.cssClass);\n menuItem.setAttribute('role', 'menuitem');\n menuItem.setAttribute('data-id', item.id);\n\n const disabled = isItemDisabled(item, params);\n if (disabled) {\n menuItem.classList.add('disabled');\n menuItem.setAttribute('aria-disabled', 'true');\n }\n\n if (item.icon) {\n const icon = document.createElement('span');\n icon.className = 'tbw-context-menu-icon';\n icon.innerHTML = item.icon;\n menuItem.appendChild(icon);\n }\n\n const label = document.createElement('span');\n label.className = 'tbw-context-menu-label';\n label.textContent = item.name;\n menuItem.appendChild(label);\n\n if (item.shortcut) {\n const shortcut = document.createElement('span');\n shortcut.className = 'tbw-context-menu-shortcut';\n shortcut.textContent = item.shortcut;\n menuItem.appendChild(shortcut);\n }\n\n if (item.subMenu?.length) {\n const arrow = document.createElement('span');\n arrow.className = 'tbw-context-menu-arrow';\n arrow.textContent = '▶';\n menuItem.appendChild(arrow);\n\n // Add submenu on hover\n menuItem.addEventListener('mouseenter', () => {\n const existingSubMenu = menuItem.querySelector('.tbw-context-menu');\n if (existingSubMenu) return;\n if (!item.subMenu) return;\n\n const subMenuItems = buildMenuItems(item.subMenu, params);\n const subMenu = createMenuElement(subMenuItems, params, onAction);\n subMenu.classList.add('tbw-context-submenu');\n subMenu.style.position = 'absolute';\n subMenu.style.left = '100%';\n subMenu.style.top = '0';\n menuItem.style.position = 'relative';\n menuItem.appendChild(subMenu);\n });\n\n menuItem.addEventListener('mouseleave', () => {\n const subMenu = menuItem.querySelector('.tbw-context-menu');\n if (subMenu) subMenu.remove();\n });\n }\n\n if (!disabled && item.action && !item.subMenu) {\n menuItem.addEventListener('click', (e) => {\n e.stopPropagation();\n onAction(item);\n });\n }\n\n menu.appendChild(menuItem);\n }\n\n return menu;\n}\n\n/**\n * Position the menu at the given viewport coordinates.\n * Menu is rendered in document.body with fixed positioning for top-layer behavior.\n *\n * @param menu - The menu element to position\n * @param x - Desired X coordinate (viewport)\n * @param y - Desired Y coordinate (viewport)\n */\nexport function positionMenu(menu: HTMLElement, x: number, y: number): void {\n // Use fixed positioning for top-layer behavior\n menu.style.position = 'fixed';\n menu.style.left = `${x}px`;\n menu.style.top = `${y}px`;\n menu.style.visibility = 'hidden';\n menu.style.zIndex = '10000';\n\n // Force layout to get dimensions\n const menuRect = menu.getBoundingClientRect();\n\n // Calculate visible area within viewport\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n\n let left = x;\n let top = y;\n\n // Check if menu overflows right edge of viewport\n if (x + menuRect.width > viewportWidth) {\n left = x - menuRect.width;\n }\n // Check if menu overflows bottom edge of viewport\n if (y + menuRect.height > viewportHeight) {\n top = y - menuRect.height;\n }\n\n // Ensure we don't go negative\n left = Math.max(0, left);\n top = Math.max(0, top);\n\n menu.style.left = `${left}px`;\n menu.style.top = `${top}px`;\n menu.style.visibility = 'visible';\n}\n","/**\n * Context Menu Plugin (Class-based)\n *\n * Provides right-click context menu functionality for tbw-grid.\n * Supports custom menu items, submenus, icons, shortcuts, and dynamic item generation.\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport { buildMenuItems, createMenuElement, positionMenu } from './menu';\nimport type { ContextMenuConfig, ContextMenuItem, ContextMenuParams } from './types';\n\n/** Global click handler reference for cleanup */\nlet globalClickHandler: ((e: Event) => void) | null = null;\n/** Global keydown handler reference for cleanup */\nlet globalKeydownHandler: ((e: KeyboardEvent) => void) | null = null;\n/** Global stylesheet for context menu (injected once) */\nlet globalStyleSheet: HTMLStyleElement | null = null;\n\n/** Context menu styles for light DOM rendering */\nconst contextMenuStyles = `\n .tbw-context-menu {\n position: fixed;\n background: light-dark(#f5f5f5, #2a2a2a);\n color: light-dark(#222, #eee);\n border: 1px solid light-dark(#d0d0d4, #454545);\n border-radius: 4px;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);\n min-width: 160px;\n padding: 4px 0;\n z-index: 10000;\n font-size: 13px;\n font-family: system-ui, sans-serif;\n }\n .tbw-context-menu-item {\n display: flex;\n align-items: center;\n padding: 6px 12px;\n cursor: pointer;\n gap: 8px;\n }\n .tbw-context-menu-item:hover:not(.disabled) {\n background: light-dark(#e8e8e8, #3a3a3a);\n }\n .tbw-context-menu-item.disabled {\n opacity: 0.5;\n cursor: default;\n }\n .tbw-context-menu-item.danger {\n color: light-dark(#c00, #f66);\n }\n .tbw-context-menu-icon {\n width: 16px;\n text-align: center;\n }\n .tbw-context-menu-label {\n flex: 1;\n }\n .tbw-context-menu-shortcut {\n color: light-dark(#888, #888);\n font-size: 11px;\n }\n .tbw-context-menu-arrow {\n font-size: 10px;\n color: light-dark(#888, #888);\n }\n .tbw-context-menu-separator {\n height: 1px;\n background: light-dark(#d0d0d4, #454545);\n margin: 4px 0;\n }\n`;\n\n/** Default menu items when none are configured */\nconst defaultItems: ContextMenuItem[] = [\n {\n id: 'copy',\n name: 'Copy',\n shortcut: 'Ctrl+C',\n action: (params) => {\n const grid = (params as ContextMenuParams & { grid?: { plugins?: { clipboard?: { copy?: () => void } } } }).grid;\n grid?.plugins?.clipboard?.copy?.();\n },\n },\n { separator: true, id: 'sep1', name: '' },\n {\n id: 'export-csv',\n name: 'Export CSV',\n action: (params) => {\n const grid = (params as ContextMenuParams & { grid?: { plugins?: { export?: { exportCsv?: () => void } } } })\n .grid;\n grid?.plugins?.export?.exportCsv?.();\n },\n },\n];\n\n/**\n * Context Menu Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new ContextMenuPlugin({\n * enabled: true,\n * items: [\n * { id: 'edit', name: 'Edit', action: (params) => console.log('Edit', params) },\n * { separator: true, id: 'sep', name: '' },\n * { id: 'delete', name: 'Delete', action: (params) => console.log('Delete', params) },\n * ],\n * })\n * ```\n */\nexport class ContextMenuPlugin extends BaseGridPlugin<ContextMenuConfig> {\n readonly name = 'contextMenu';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<ContextMenuConfig> {\n return {\n enabled: true,\n items: defaultItems,\n };\n }\n\n // ===== Internal State =====\n private isOpen = false;\n private position = { x: 0, y: 0 };\n private params: ContextMenuParams | null = null;\n private menuElement: HTMLElement | null = null;\n\n // ===== Lifecycle =====\n\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n this.installGlobalHandlers();\n }\n\n override detach(): void {\n if (this.menuElement) {\n this.menuElement.remove();\n this.menuElement = null;\n }\n this.isOpen = false;\n this.params = null;\n }\n\n // ===== Private Methods =====\n\n private installGlobalHandlers(): void {\n // Inject global stylesheet for context menu (once)\n if (!globalStyleSheet && typeof document !== 'undefined') {\n globalStyleSheet = document.createElement('style');\n globalStyleSheet.id = 'tbw-context-menu-styles';\n globalStyleSheet.textContent = contextMenuStyles;\n document.head.appendChild(globalStyleSheet);\n }\n\n // Close menu on click outside\n if (!globalClickHandler) {\n globalClickHandler = () => {\n const menus = document.querySelectorAll('.tbw-context-menu');\n menus.forEach((menu) => menu.remove());\n };\n document.addEventListener('click', globalClickHandler);\n }\n\n // Close on escape\n if (!globalKeydownHandler) {\n globalKeydownHandler = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n const menus = document.querySelectorAll('.tbw-context-menu');\n menus.forEach((menu) => menu.remove());\n }\n };\n document.addEventListener('keydown', globalKeydownHandler);\n }\n }\n\n // ===== Hooks =====\n\n override afterRender(): void {\n if (!this.config.enabled) return;\n\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n const container = shadowRoot.children[0];\n if (!container) return;\n\n // Check if handler already attached\n if (container.getAttribute('data-context-menu-bound') === 'true') return;\n container.setAttribute('data-context-menu-bound', 'true');\n\n container.addEventListener('contextmenu', (e: Event) => {\n if (!this.config.enabled) return;\n\n const event = e as MouseEvent;\n event.preventDefault();\n\n const target = event.target as HTMLElement;\n const cell = target.closest('[data-row][data-col]');\n const header = target.closest('.header-cell');\n\n let params: ContextMenuParams;\n\n if (cell) {\n const rowIndex = parseInt(cell.getAttribute('data-row') ?? '-1', 10);\n const colIndex = parseInt(cell.getAttribute('data-col') ?? '-1', 10);\n const column = this.columns[colIndex];\n const row = this.rows[rowIndex];\n\n params = {\n row,\n rowIndex,\n column,\n columnIndex: colIndex,\n field: column?.field ?? '',\n value: row?.[column?.field as keyof typeof row] ?? null,\n isHeader: false,\n event,\n };\n } else if (header) {\n const colIndex = parseInt(header.getAttribute('data-col') ?? '-1', 10);\n const column = this.columns[colIndex];\n\n params = {\n row: null,\n rowIndex: -1,\n column,\n columnIndex: colIndex,\n field: column?.field ?? '',\n value: null,\n isHeader: true,\n event,\n };\n } else {\n return;\n }\n\n this.params = params;\n this.position = { x: event.clientX, y: event.clientY };\n\n const items = buildMenuItems(this.config.items ?? defaultItems, params);\n if (!items.length) return;\n\n if (this.menuElement) {\n this.menuElement.remove();\n }\n\n this.menuElement = createMenuElement(items, params, (item) => {\n if (item.action) {\n item.action(params);\n }\n this.menuElement?.remove();\n this.menuElement = null;\n this.isOpen = false;\n });\n\n document.body.appendChild(this.menuElement);\n positionMenu(this.menuElement, event.clientX, event.clientY);\n this.isOpen = true;\n\n this.emit('context-menu-open', { params, items });\n });\n }\n\n // ===== Public API =====\n\n /**\n * Programmatically show the context menu at the specified position.\n * @param x - X coordinate\n * @param y - Y coordinate\n * @param params - Partial context menu parameters\n */\n showMenu(x: number, y: number, params: Partial<ContextMenuParams>): void {\n const fullParams: ContextMenuParams = {\n row: params.row ?? null,\n rowIndex: params.rowIndex ?? -1,\n column: params.column ?? null,\n columnIndex: params.columnIndex ?? -1,\n field: params.field ?? '',\n value: params.value ?? null,\n isHeader: params.isHeader ?? false,\n event: params.event ?? new MouseEvent('contextmenu'),\n };\n\n const items = buildMenuItems(this.config.items ?? defaultItems, fullParams);\n\n if (this.menuElement) {\n this.menuElement.remove();\n }\n\n this.menuElement = createMenuElement(items, fullParams, (item) => {\n if (item.action) item.action(fullParams);\n this.menuElement?.remove();\n this.menuElement = null;\n this.isOpen = false;\n });\n\n document.body.appendChild(this.menuElement);\n positionMenu(this.menuElement, x, y);\n this.isOpen = true;\n }\n\n /**\n * Hide the context menu.\n */\n hideMenu(): void {\n if (this.menuElement) {\n this.menuElement.remove();\n this.menuElement = null;\n this.isOpen = false;\n }\n }\n\n /**\n * Check if the context menu is currently open.\n * @returns Whether the menu is open\n */\n isMenuOpen(): boolean {\n return this.isOpen;\n }\n\n // Styles are injected globally via installGlobalHandlers() since menu renders in document.body\n}\n"],"names":["BaseGridPlugin","config","grid","PluginClass","eventName","detail","message","buildMenuItems","items","params","item","isItemDisabled","createMenuElement","onAction","menu","separator","menuItem","disabled","icon","label","shortcut","arrow","subMenuItems","subMenu","e","positionMenu","x","y","menuRect","viewportWidth","viewportHeight","left","top","globalClickHandler","globalKeydownHandler","globalStyleSheet","contextMenuStyles","defaultItems","ContextMenuPlugin","shadowRoot","container","event","target","cell","header","rowIndex","colIndex","column","row","fullParams"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../../../libs/grid/src/lib/core/plugin/base-plugin.ts","../../../../../../libs/grid/src/lib/plugins/context-menu/menu.ts","../../../../../../libs/grid/src/lib/plugins/context-menu/ContextMenuPlugin.ts"],"sourcesContent":["/**\n * Base Grid Plugin Class\n *\n * All plugins extend this abstract class.\n * Plugins are instantiated per-grid and manage their own state.\n */\n\nimport type { ColumnConfig, ColumnState, HeaderContentDefinition, ToolPanelDefinition } from '../types';\n\n// Forward declare to avoid circular imports\nexport interface GridElement {\n shadowRoot: ShadowRoot | null;\n rows: any[];\n columns: ColumnConfig[];\n gridConfig: any;\n /** AbortSignal that is aborted when the grid disconnects from the DOM */\n disconnectSignal: AbortSignal;\n requestRender(): void;\n requestAfterRender(): void;\n forceLayout(): Promise<void>;\n getPlugin<T extends BaseGridPlugin>(PluginClass: new (...args: any[]) => T): T | undefined;\n getPluginByName(name: string): BaseGridPlugin | undefined;\n dispatchEvent(event: Event): boolean;\n}\n\n/**\n * Keyboard modifier flags\n */\nexport interface KeyboardModifiers {\n ctrl?: boolean;\n shift?: boolean;\n alt?: boolean;\n meta?: boolean;\n}\n\n/**\n * Cell coordinates\n */\nexport interface CellCoords {\n row: number;\n col: number;\n}\n\n/**\n * Cell click event\n */\nexport interface CellClickEvent {\n rowIndex: number;\n colIndex: number;\n field: string;\n value: any;\n row: any;\n cellEl: HTMLElement;\n originalEvent: MouseEvent;\n}\n\n/**\n * Row click event\n */\nexport interface RowClickEvent {\n rowIndex: number;\n row: any;\n rowEl: HTMLElement;\n originalEvent: MouseEvent;\n}\n\n/**\n * Header click event\n */\nexport interface HeaderClickEvent {\n colIndex: number;\n field: string;\n column: ColumnConfig;\n headerEl: HTMLElement;\n originalEvent: MouseEvent;\n}\n\n/**\n * Scroll event\n */\nexport interface ScrollEvent {\n scrollTop: number;\n scrollLeft: number;\n scrollHeight: number;\n scrollWidth: number;\n clientHeight: number;\n clientWidth: number;\n originalEvent?: Event;\n}\n\n/**\n * Cell mouse event (for drag operations, selection, etc.)\n */\nexport interface CellMouseEvent {\n /** Event type: mousedown, mousemove, or mouseup */\n type: 'mousedown' | 'mousemove' | 'mouseup';\n /** Row index, undefined if not over a data cell */\n rowIndex?: number;\n /** Column index, undefined if not over a cell */\n colIndex?: number;\n /** Field name, undefined if not over a cell */\n field?: string;\n /** Cell value, undefined if not over a data cell */\n value?: unknown;\n /** Row data object, undefined if not over a data row */\n row?: unknown;\n /** Column configuration, undefined if not over a column */\n column?: ColumnConfig;\n /** The cell element, undefined if not over a cell */\n cellElement?: HTMLElement;\n /** The row element, undefined if not over a row */\n rowElement?: HTMLElement;\n /** Whether the event is over a header cell */\n isHeader: boolean;\n /** Cell coordinates if over a valid data cell */\n cell?: CellCoords;\n /** The original mouse event */\n originalEvent: MouseEvent;\n}\n\n/**\n * Context menu parameters\n */\nexport interface ContextMenuParams {\n x: number;\n y: number;\n rowIndex?: number;\n colIndex?: number;\n field?: string;\n value?: any;\n row?: any;\n column?: ColumnConfig;\n isHeader?: boolean;\n}\n\n/**\n * Context menu item\n */\nexport interface ContextMenuItem {\n id: string;\n label: string;\n icon?: string;\n disabled?: boolean;\n separator?: boolean;\n children?: ContextMenuItem[];\n action?: (params: ContextMenuParams) => void;\n}\n\n/**\n * Cell render context for plugin cell renderers.\n * Provides full context including position and editing state.\n *\n * Note: This differs from the core `CellRenderContext` in types.ts which is\n * simpler and used for column view renderers. This version provides additional\n * context needed by plugins that register custom cell renderers.\n */\nexport interface PluginCellRenderContext {\n /** The cell value */\n value: any;\n /** The field/column key */\n field: string;\n /** The row data object */\n row: any;\n /** Row index in the data array */\n rowIndex: number;\n /** Column index */\n colIndex: number;\n /** Column configuration */\n column: ColumnConfig;\n /** Whether the cell is currently in edit mode */\n isEditing: boolean;\n}\n\n/**\n * Header render context for plugin header renderers.\n */\nexport interface PluginHeaderRenderContext {\n /** Column configuration */\n column: ColumnConfig;\n /** Column index */\n colIndex: number;\n}\n\n/**\n * Cell renderer function type for plugins.\n */\nexport type CellRenderer = (ctx: PluginCellRenderContext) => string | HTMLElement;\n\n/**\n * Header renderer function type for plugins.\n */\nexport type HeaderRenderer = (ctx: PluginHeaderRenderContext) => string | HTMLElement;\n\n/**\n * Cell editor interface for plugins.\n */\nexport interface CellEditor {\n create(ctx: PluginCellRenderContext, commitFn: (value: any) => void, cancelFn: () => void): HTMLElement;\n getValue?(element: HTMLElement): any;\n focus?(element: HTMLElement): void;\n}\n\n/**\n * Abstract base class for all grid plugins.\n *\n * @template TConfig - Configuration type for the plugin\n */\nexport abstract class BaseGridPlugin<TConfig = unknown> {\n /** Unique plugin identifier (derived from class name by default) */\n abstract readonly name: string;\n\n /** Plugin version - override in subclass if needed */\n readonly version: string = '1.0.0';\n\n /** CSS styles to inject into the grid's shadow DOM */\n readonly styles?: string;\n\n /** Custom cell renderers keyed by type name */\n readonly cellRenderers?: Record<string, CellRenderer>;\n\n /** Custom header renderers keyed by type name */\n readonly headerRenderers?: Record<string, HeaderRenderer>;\n\n /** Custom cell editors keyed by type name */\n readonly cellEditors?: Record<string, CellEditor>;\n\n /** The grid instance this plugin is attached to */\n protected grid!: GridElement;\n\n /** Plugin configuration - merged with defaults in attach() */\n protected config!: TConfig;\n\n /** User-provided configuration from constructor */\n private readonly userConfig: Partial<TConfig>;\n\n /**\n * Default configuration - subclasses should override this getter.\n * Note: This must be a getter (not property initializer) for proper inheritance\n * since property initializers run after parent constructor.\n */\n protected get defaultConfig(): Partial<TConfig> {\n return {};\n }\n\n constructor(config: Partial<TConfig> = {}) {\n this.userConfig = config;\n }\n\n /**\n * Called when the plugin is attached to a grid.\n * Override to set up event listeners, initialize state, etc.\n */\n attach(grid: GridElement): void {\n this.grid = grid;\n // Merge config here (after subclass construction is complete)\n this.config = { ...this.defaultConfig, ...this.userConfig } as TConfig;\n }\n\n /**\n * Called when the plugin is detached from a grid.\n * Override to clean up event listeners, timers, etc.\n */\n detach(): void {\n // Override in subclass\n }\n\n /**\n * Get another plugin instance from the same grid.\n * Use for inter-plugin communication.\n */\n protected getPlugin<T extends BaseGridPlugin>(PluginClass: new (...args: any[]) => T): T | undefined {\n return this.grid?.getPlugin(PluginClass);\n }\n\n /**\n * Emit a custom event from the grid.\n */\n protected emit<T>(eventName: string, detail: T): void {\n this.grid?.dispatchEvent?.(new CustomEvent(eventName, { detail, bubbles: true }));\n }\n\n /**\n * Request a re-render of the grid.\n */\n protected requestRender(): void {\n this.grid?.requestRender?.();\n }\n\n /**\n * Request a lightweight style update without rebuilding DOM.\n * Use this instead of requestRender() when only CSS classes need updating.\n */\n protected requestAfterRender(): void {\n this.grid?.requestAfterRender?.();\n }\n\n /**\n * Get the current rows from the grid.\n */\n protected get rows(): any[] {\n return this.grid?.rows ?? [];\n }\n\n /**\n * Get the original unfiltered/unprocessed rows from the grid.\n * Use this when you need all source data regardless of active filters.\n */\n protected get sourceRows(): any[] {\n return (this.grid as any)?.sourceRows ?? [];\n }\n\n /**\n * Get the current columns from the grid.\n */\n protected get columns(): ColumnConfig[] {\n return this.grid?.columns ?? [];\n }\n\n /**\n * Get only visible columns from the grid (excludes hidden).\n * Use this for rendering that needs to match the grid template.\n */\n protected get visibleColumns(): ColumnConfig[] {\n return (this.grid as any)?.visibleColumns ?? [];\n }\n\n /**\n * Get the shadow root of the grid.\n */\n protected get shadowRoot(): ShadowRoot | null {\n return this.grid?.shadowRoot ?? null;\n }\n\n /**\n * Get the disconnect signal for event listener cleanup.\n * This signal is aborted when the grid disconnects from the DOM.\n * Use this when adding event listeners that should be cleaned up automatically.\n *\n * Best for:\n * - Document/window-level listeners added in attach()\n * - Listeners on the grid element itself\n * - Any listener that should persist across renders\n *\n * Not needed for:\n * - Listeners on elements created in afterRender() (removed with element)\n *\n * @example\n * element.addEventListener('click', handler, { signal: this.disconnectSignal });\n * document.addEventListener('keydown', handler, { signal: this.disconnectSignal });\n */\n protected get disconnectSignal(): AbortSignal {\n return this.grid?.disconnectSignal;\n }\n\n /**\n * Log a warning message.\n */\n protected warn(message: string): void {\n console.warn(`[tbw-grid:${this.name}] ${message}`);\n }\n\n // ===== Lifecycle Hooks (override as needed) =====\n\n /**\n * Transform rows before rendering.\n * Called during each render cycle before rows are rendered to the DOM.\n * Use this to filter, sort, or add computed properties to rows.\n *\n * @param rows - The current rows array (readonly to encourage returning a new array)\n * @returns The modified rows array to render\n *\n * @example\n * ```ts\n * processRows(rows: readonly any[]): any[] {\n * // Filter out hidden rows\n * return rows.filter(row => !row._hidden);\n * }\n * ```\n *\n * @example\n * ```ts\n * processRows(rows: readonly any[]): any[] {\n * // Add computed properties\n * return rows.map(row => ({\n * ...row,\n * _fullName: `${row.firstName} ${row.lastName}`\n * }));\n * }\n * ```\n */\n processRows?(rows: readonly any[]): any[];\n\n /**\n * Transform columns before rendering.\n * Called during each render cycle before column headers and cells are rendered.\n * Use this to add, remove, or modify column definitions.\n *\n * @param columns - The current columns array (readonly to encourage returning a new array)\n * @returns The modified columns array to render\n *\n * @example\n * ```ts\n * processColumns(columns: readonly ColumnConfig[]): ColumnConfig[] {\n * // Add a selection checkbox column\n * return [\n * { field: '_select', header: '', width: 40 },\n * ...columns\n * ];\n * }\n * ```\n */\n processColumns?(columns: readonly ColumnConfig[]): ColumnConfig[];\n\n /**\n * Called before each render cycle begins.\n * Use this to prepare state or cache values needed during rendering.\n *\n * @example\n * ```ts\n * beforeRender(): void {\n * this.visibleRowCount = this.calculateVisibleRows();\n * }\n * ```\n */\n beforeRender?(): void;\n\n /**\n * Called after each render cycle completes.\n * Use this for DOM manipulation, adding event listeners to rendered elements,\n * or applying visual effects like selection highlights.\n *\n * @example\n * ```ts\n * afterRender(): void {\n * // Apply selection styling to rendered rows\n * const rows = this.shadowRoot?.querySelectorAll('.data-row');\n * rows?.forEach((row, i) => {\n * row.classList.toggle('selected', this.selectedRows.has(i));\n * });\n * }\n * ```\n */\n afterRender?(): void;\n\n /**\n * Called after scroll-triggered row rendering completes.\n * This is a lightweight hook for applying visual state to recycled DOM elements.\n * Use this instead of afterRender when you need to reapply styling during scroll.\n *\n * Performance note: This is called frequently during scroll. Keep implementation fast.\n *\n * @example\n * ```ts\n * onScrollRender(): void {\n * // Reapply selection state to visible cells\n * this.applySelectionToVisibleCells();\n * }\n * ```\n */\n onScrollRender?(): void;\n\n /**\n * Render a custom row, bypassing the default row rendering.\n * Use this for special row types like group headers, detail rows, or footers.\n *\n * @param row - The row data object\n * @param rowEl - The row DOM element to render into\n * @param rowIndex - The index of the row in the data array\n * @returns `true` if the plugin handled rendering (prevents default), `false`/`void` for default rendering\n *\n * @example\n * ```ts\n * renderRow(row: any, rowEl: HTMLElement, rowIndex: number): boolean | void {\n * if (row._isGroupHeader) {\n * rowEl.innerHTML = `<div class=\"group-header\">${row._groupLabel}</div>`;\n * return true; // Handled - skip default rendering\n * }\n * // Return void to let default rendering proceed\n * }\n * ```\n */\n renderRow?(row: any, rowEl: HTMLElement, rowIndex: number): boolean | void;\n\n // ===== Interaction Hooks (override as needed) =====\n\n /**\n * Handle keyboard events on the grid.\n * Called when a key is pressed while the grid or a cell has focus.\n *\n * @param event - The native KeyboardEvent\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\n *\n * @example\n * ```ts\n * onKeyDown(event: KeyboardEvent): boolean | void {\n * // Handle Ctrl+A for select all\n * if (event.ctrlKey && event.key === 'a') {\n * this.selectAllRows();\n * return true; // Prevent default browser select-all\n * }\n * }\n * ```\n */\n onKeyDown?(event: KeyboardEvent): boolean | void;\n\n /**\n * Handle cell click events.\n * Called when a data cell is clicked (not headers).\n *\n * @param event - Cell click event with row/column context\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\n *\n * @example\n * ```ts\n * onCellClick(event: CellClickEvent): boolean | void {\n * if (event.field === '_select') {\n * this.toggleRowSelection(event.rowIndex);\n * return true; // Handled\n * }\n * }\n * ```\n */\n onCellClick?(event: CellClickEvent): boolean | void;\n\n /**\n * Handle row click events.\n * Called when any part of a data row is clicked.\n * Note: This is called in addition to onCellClick, not instead of.\n *\n * @param event - Row click event with row context\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\n *\n * @example\n * ```ts\n * onRowClick(event: RowClickEvent): boolean | void {\n * if (this.config.mode === 'row') {\n * this.selectRow(event.rowIndex, event.originalEvent);\n * return true;\n * }\n * }\n * ```\n */\n onRowClick?(event: RowClickEvent): boolean | void;\n\n /**\n * Handle header click events.\n * Called when a column header is clicked. Commonly used for sorting.\n *\n * @param event - Header click event with column context\n * @returns `true` to prevent default behavior and stop propagation, `false`/`void` to allow default\n *\n * @example\n * ```ts\n * onHeaderClick(event: HeaderClickEvent): boolean | void {\n * if (event.column.sortable !== false) {\n * this.toggleSort(event.field);\n * return true;\n * }\n * }\n * ```\n */\n onHeaderClick?(event: HeaderClickEvent): boolean | void;\n\n /**\n * Handle scroll events on the grid viewport.\n * Called during scrolling. Note: This may be called frequently; debounce if needed.\n *\n * @param event - Scroll event with scroll position and viewport dimensions\n *\n * @example\n * ```ts\n * onScroll(event: ScrollEvent): void {\n * // Update sticky column positions\n * this.updateStickyPositions(event.scrollLeft);\n * }\n * ```\n */\n onScroll?(event: ScrollEvent): void;\n\n /**\n * Handle cell mousedown events.\n * Used for initiating drag operations like range selection or column resize.\n *\n * @param event - Mouse event with cell context\n * @returns `true` to indicate drag started (prevents text selection), `false`/`void` otherwise\n *\n * @example\n * ```ts\n * onCellMouseDown(event: CellMouseEvent): boolean | void {\n * if (event.rowIndex !== undefined && this.config.mode === 'range') {\n * this.startDragSelection(event.rowIndex, event.colIndex);\n * return true; // Prevent text selection\n * }\n * }\n * ```\n */\n onCellMouseDown?(event: CellMouseEvent): boolean | void;\n\n /**\n * Handle cell mousemove events during drag operations.\n * Only called when a drag is in progress (after mousedown returned true).\n *\n * @param event - Mouse event with current cell context\n * @returns `true` to continue handling the drag, `false`/`void` otherwise\n *\n * @example\n * ```ts\n * onCellMouseMove(event: CellMouseEvent): boolean | void {\n * if (this.isDragging && event.rowIndex !== undefined) {\n * this.extendSelection(event.rowIndex, event.colIndex);\n * return true;\n * }\n * }\n * ```\n */\n onCellMouseMove?(event: CellMouseEvent): boolean | void;\n\n /**\n * Handle cell mouseup events to end drag operations.\n *\n * @param event - Mouse event with final cell context\n * @returns `true` if drag was finalized, `false`/`void` otherwise\n *\n * @example\n * ```ts\n * onCellMouseUp(event: CellMouseEvent): boolean | void {\n * if (this.isDragging) {\n * this.finalizeDragSelection();\n * this.isDragging = false;\n * return true;\n * }\n * }\n * ```\n */\n onCellMouseUp?(event: CellMouseEvent): boolean | void;\n\n /**\n * Provide context menu items when right-clicking on the grid.\n * Multiple plugins can contribute items; they are merged into a single menu.\n *\n * @param params - Context about where the menu was triggered (row, column, etc.)\n * @returns Array of menu items to display\n *\n * @example\n * ```ts\n * getContextMenuItems(params: ContextMenuParams): ContextMenuItem[] {\n * if (params.isHeader) {\n * return [\n * { id: 'sort-asc', label: 'Sort Ascending', action: () => this.sortAsc(params.field) },\n * { id: 'sort-desc', label: 'Sort Descending', action: () => this.sortDesc(params.field) },\n * ];\n * }\n * return [\n * { id: 'copy', label: 'Copy Cell', action: () => this.copyCell(params) },\n * ];\n * }\n * ```\n */\n getContextMenuItems?(params: ContextMenuParams): ContextMenuItem[];\n\n // ===== Column State Hooks (override as needed) =====\n\n /**\n * Contribute plugin-specific state for a column.\n * Called by the grid when collecting column state for serialization.\n * Plugins can add their own properties to the column state.\n *\n * @param field - The field name of the column\n * @returns Partial column state with plugin-specific properties, or undefined if no state to contribute\n *\n * @example\n * ```ts\n * getColumnState(field: string): Partial<ColumnState> | undefined {\n * const filterModel = this.filterModels.get(field);\n * if (filterModel) {\n * // Uses module augmentation to add filter property to ColumnState\n * return { filter: filterModel } as Partial<ColumnState>;\n * }\n * return undefined;\n * }\n * ```\n */\n getColumnState?(field: string): Partial<ColumnState> | undefined;\n\n /**\n * Apply plugin-specific state to a column.\n * Called by the grid when restoring column state from serialized data.\n * Plugins should restore their internal state based on the provided state.\n *\n * @param field - The field name of the column\n * @param state - The column state to apply (may contain plugin-specific properties)\n *\n * @example\n * ```ts\n * applyColumnState(field: string, state: ColumnState): void {\n * // Check for filter property added via module augmentation\n * const filter = (state as any).filter;\n * if (filter) {\n * this.filterModels.set(field, filter);\n * this.applyFilter();\n * }\n * }\n * ```\n */\n applyColumnState?(field: string, state: ColumnState): void;\n\n // ===== Shell Integration Hooks (override as needed) =====\n\n /**\n * Register a tool panel for this plugin.\n * Return undefined if plugin has no tool panel.\n * The shell will create a toolbar toggle button and render the panel content\n * when the user opens the panel.\n *\n * @returns Tool panel definition, or undefined if plugin has no panel\n *\n * @example\n * ```ts\n * getToolPanel(): ToolPanelDefinition | undefined {\n * return {\n * id: 'columns',\n * title: 'Columns',\n * icon: '☰',\n * tooltip: 'Show/hide columns',\n * order: 10,\n * render: (container) => {\n * this.renderColumnList(container);\n * return () => this.cleanup();\n * },\n * };\n * }\n * ```\n */\n getToolPanel?(): ToolPanelDefinition | undefined;\n\n /**\n * Register content for the shell header center section.\n * Return undefined if plugin has no header content.\n * Examples: search input, selection summary, status indicators.\n *\n * @returns Header content definition, or undefined if plugin has no header content\n *\n * @example\n * ```ts\n * getHeaderContent(): HeaderContentDefinition | undefined {\n * return {\n * id: 'quick-filter',\n * order: 10,\n * render: (container) => {\n * const input = document.createElement('input');\n * input.type = 'text';\n * input.placeholder = 'Search...';\n * input.addEventListener('input', this.handleInput);\n * container.appendChild(input);\n * return () => input.removeEventListener('input', this.handleInput);\n * },\n * };\n * }\n * ```\n */\n getHeaderContent?(): HeaderContentDefinition | undefined;\n}\n","/**\n * Context Menu Rendering Logic\n *\n * Pure functions for building and positioning context menus.\n */\n\nimport type { ContextMenuItem, ContextMenuParams } from './types';\n\n/**\n * Build the visible menu items by resolving dynamic items and filtering hidden ones.\n *\n * @param items - Menu items configuration (array or factory function)\n * @param params - Context menu parameters for evaluating dynamic properties\n * @returns Filtered array of visible menu items\n */\nexport function buildMenuItems(\n items: ContextMenuItem[] | ((params: ContextMenuParams) => ContextMenuItem[]),\n params: ContextMenuParams\n): ContextMenuItem[] {\n const menuItems = typeof items === 'function' ? items(params) : items;\n\n return menuItems.filter((item) => {\n if (item.hidden === true) return false;\n if (typeof item.hidden === 'function' && item.hidden(params)) return false;\n return true;\n });\n}\n\n/**\n * Check if a menu item is disabled.\n *\n * @param item - The menu item to check\n * @param params - Context menu parameters for evaluating dynamic disabled state\n * @returns True if the item is disabled\n */\nexport function isItemDisabled(item: ContextMenuItem, params: ContextMenuParams): boolean {\n if (item.disabled === true) return true;\n if (typeof item.disabled === 'function') return item.disabled(params);\n return false;\n}\n\n/**\n * Create the menu DOM element from a list of menu items.\n *\n * @param items - Array of menu items to render\n * @param params - Context menu parameters for evaluating dynamic properties\n * @param onAction - Callback when a menu item action is triggered\n * @returns The created menu element\n */\nexport function createMenuElement(\n items: ContextMenuItem[],\n params: ContextMenuParams,\n onAction: (item: ContextMenuItem) => void\n): HTMLElement {\n const menu = document.createElement('div');\n menu.className = 'tbw-context-menu';\n menu.setAttribute('role', 'menu');\n\n for (const item of items) {\n if (item.separator) {\n const separator = document.createElement('div');\n separator.className = 'tbw-context-menu-separator';\n separator.setAttribute('role', 'separator');\n menu.appendChild(separator);\n continue;\n }\n\n const menuItem = document.createElement('div');\n menuItem.className = 'tbw-context-menu-item';\n if (item.cssClass) menuItem.classList.add(item.cssClass);\n menuItem.setAttribute('role', 'menuitem');\n menuItem.setAttribute('data-id', item.id);\n\n const disabled = isItemDisabled(item, params);\n if (disabled) {\n menuItem.classList.add('disabled');\n menuItem.setAttribute('aria-disabled', 'true');\n }\n\n if (item.icon) {\n const icon = document.createElement('span');\n icon.className = 'tbw-context-menu-icon';\n icon.innerHTML = item.icon;\n menuItem.appendChild(icon);\n }\n\n const label = document.createElement('span');\n label.className = 'tbw-context-menu-label';\n label.textContent = item.name;\n menuItem.appendChild(label);\n\n if (item.shortcut) {\n const shortcut = document.createElement('span');\n shortcut.className = 'tbw-context-menu-shortcut';\n shortcut.textContent = item.shortcut;\n menuItem.appendChild(shortcut);\n }\n\n if (item.subMenu?.length) {\n const arrow = document.createElement('span');\n arrow.className = 'tbw-context-menu-arrow';\n arrow.textContent = '▶';\n menuItem.appendChild(arrow);\n\n // Add submenu on hover\n menuItem.addEventListener('mouseenter', () => {\n const existingSubMenu = menuItem.querySelector('.tbw-context-menu');\n if (existingSubMenu) return;\n if (!item.subMenu) return;\n\n const subMenuItems = buildMenuItems(item.subMenu, params);\n const subMenu = createMenuElement(subMenuItems, params, onAction);\n subMenu.classList.add('tbw-context-submenu');\n subMenu.style.position = 'absolute';\n subMenu.style.left = '100%';\n subMenu.style.top = '0';\n menuItem.style.position = 'relative';\n menuItem.appendChild(subMenu);\n });\n\n menuItem.addEventListener('mouseleave', () => {\n const subMenu = menuItem.querySelector('.tbw-context-menu');\n if (subMenu) subMenu.remove();\n });\n }\n\n if (!disabled && item.action && !item.subMenu) {\n menuItem.addEventListener('click', (e) => {\n e.stopPropagation();\n onAction(item);\n });\n }\n\n menu.appendChild(menuItem);\n }\n\n return menu;\n}\n\n/**\n * Position the menu at the given viewport coordinates.\n * Menu is rendered in document.body with fixed positioning for top-layer behavior.\n *\n * @param menu - The menu element to position\n * @param x - Desired X coordinate (viewport)\n * @param y - Desired Y coordinate (viewport)\n */\nexport function positionMenu(menu: HTMLElement, x: number, y: number): void {\n // Use fixed positioning for top-layer behavior\n menu.style.position = 'fixed';\n menu.style.left = `${x}px`;\n menu.style.top = `${y}px`;\n menu.style.visibility = 'hidden';\n menu.style.zIndex = '10000';\n\n // Force layout to get dimensions\n const menuRect = menu.getBoundingClientRect();\n\n // Calculate visible area within viewport\n const viewportWidth = window.innerWidth;\n const viewportHeight = window.innerHeight;\n\n let left = x;\n let top = y;\n\n // Check if menu overflows right edge of viewport\n if (x + menuRect.width > viewportWidth) {\n left = x - menuRect.width;\n }\n // Check if menu overflows bottom edge of viewport\n if (y + menuRect.height > viewportHeight) {\n top = y - menuRect.height;\n }\n\n // Ensure we don't go negative\n left = Math.max(0, left);\n top = Math.max(0, top);\n\n menu.style.left = `${left}px`;\n menu.style.top = `${top}px`;\n menu.style.visibility = 'visible';\n}\n","/**\n * Context Menu Plugin (Class-based)\n *\n * Provides right-click context menu functionality for tbw-grid.\n * Supports custom menu items, submenus, icons, shortcuts, and dynamic item generation.\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport { buildMenuItems, createMenuElement, positionMenu } from './menu';\nimport type { ContextMenuConfig, ContextMenuItem, ContextMenuParams } from './types';\n\n/** Global click handler reference for cleanup */\nlet globalClickHandler: ((e: Event) => void) | null = null;\n/** Global keydown handler reference for cleanup */\nlet globalKeydownHandler: ((e: KeyboardEvent) => void) | null = null;\n/** Global stylesheet for context menu (injected once) */\nlet globalStyleSheet: HTMLStyleElement | null = null;\n\n/** Context menu styles for light DOM rendering */\nconst contextMenuStyles = `\n .tbw-context-menu {\n position: fixed;\n background: light-dark(#f5f5f5, #2a2a2a);\n color: light-dark(#222, #eee);\n border: 1px solid light-dark(#d0d0d4, #454545);\n border-radius: 4px;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);\n min-width: 160px;\n padding: 4px 0;\n z-index: 10000;\n font-size: 13px;\n font-family: system-ui, sans-serif;\n }\n .tbw-context-menu-item {\n display: flex;\n align-items: center;\n padding: 6px 12px;\n cursor: pointer;\n gap: 8px;\n }\n .tbw-context-menu-item:hover:not(.disabled) {\n background: light-dark(#e8e8e8, #3a3a3a);\n }\n .tbw-context-menu-item.disabled {\n opacity: 0.5;\n cursor: default;\n }\n .tbw-context-menu-item.danger {\n color: light-dark(#c00, #f66);\n }\n .tbw-context-menu-icon {\n width: 16px;\n text-align: center;\n }\n .tbw-context-menu-label {\n flex: 1;\n }\n .tbw-context-menu-shortcut {\n color: light-dark(#888, #888);\n font-size: 11px;\n }\n .tbw-context-menu-arrow {\n font-size: 10px;\n color: light-dark(#888, #888);\n }\n .tbw-context-menu-separator {\n height: 1px;\n background: light-dark(#d0d0d4, #454545);\n margin: 4px 0;\n }\n`;\n\n/** Default menu items when none are configured */\nconst defaultItems: ContextMenuItem[] = [\n {\n id: 'copy',\n name: 'Copy',\n shortcut: 'Ctrl+C',\n action: (params) => {\n const grid = (params as ContextMenuParams & { grid?: { plugins?: { clipboard?: { copy?: () => void } } } }).grid;\n grid?.plugins?.clipboard?.copy?.();\n },\n },\n { separator: true, id: 'sep1', name: '' },\n {\n id: 'export-csv',\n name: 'Export CSV',\n action: (params) => {\n const grid = (params as ContextMenuParams & { grid?: { plugins?: { export?: { exportCsv?: () => void } } } })\n .grid;\n grid?.plugins?.export?.exportCsv?.();\n },\n },\n];\n\n/**\n * Context Menu Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new ContextMenuPlugin({\n * enabled: true,\n * items: [\n * { id: 'edit', name: 'Edit', action: (params) => console.log('Edit', params) },\n * { separator: true, id: 'sep', name: '' },\n * { id: 'delete', name: 'Delete', action: (params) => console.log('Delete', params) },\n * ],\n * })\n * ```\n */\nexport class ContextMenuPlugin extends BaseGridPlugin<ContextMenuConfig> {\n readonly name = 'contextMenu';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<ContextMenuConfig> {\n return {\n enabled: true,\n items: defaultItems,\n };\n }\n\n // ===== Internal State =====\n private isOpen = false;\n private position = { x: 0, y: 0 };\n private params: ContextMenuParams | null = null;\n private menuElement: HTMLElement | null = null;\n\n // ===== Lifecycle =====\n\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n this.installGlobalHandlers();\n }\n\n override detach(): void {\n if (this.menuElement) {\n this.menuElement.remove();\n this.menuElement = null;\n }\n this.isOpen = false;\n this.params = null;\n }\n\n // ===== Private Methods =====\n\n private installGlobalHandlers(): void {\n // Inject global stylesheet for context menu (once)\n if (!globalStyleSheet && typeof document !== 'undefined') {\n globalStyleSheet = document.createElement('style');\n globalStyleSheet.id = 'tbw-context-menu-styles';\n globalStyleSheet.textContent = contextMenuStyles;\n document.head.appendChild(globalStyleSheet);\n }\n\n // Close menu on click outside\n if (!globalClickHandler) {\n globalClickHandler = () => {\n const menus = document.querySelectorAll('.tbw-context-menu');\n menus.forEach((menu) => menu.remove());\n };\n document.addEventListener('click', globalClickHandler);\n }\n\n // Close on escape\n if (!globalKeydownHandler) {\n globalKeydownHandler = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n const menus = document.querySelectorAll('.tbw-context-menu');\n menus.forEach((menu) => menu.remove());\n }\n };\n document.addEventListener('keydown', globalKeydownHandler);\n }\n }\n\n // ===== Hooks =====\n\n override afterRender(): void {\n if (!this.config.enabled) return;\n\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n const container = shadowRoot.children[0];\n if (!container) return;\n\n // Check if handler already attached\n if (container.getAttribute('data-context-menu-bound') === 'true') return;\n container.setAttribute('data-context-menu-bound', 'true');\n\n container.addEventListener('contextmenu', (e: Event) => {\n if (!this.config.enabled) return;\n\n const event = e as MouseEvent;\n event.preventDefault();\n\n const target = event.target as HTMLElement;\n const cell = target.closest('[data-row][data-col]');\n const header = target.closest('.header-cell');\n\n let params: ContextMenuParams;\n\n if (cell) {\n const rowIndex = parseInt(cell.getAttribute('data-row') ?? '-1', 10);\n const colIndex = parseInt(cell.getAttribute('data-col') ?? '-1', 10);\n const column = this.columns[colIndex];\n const row = this.rows[rowIndex];\n\n params = {\n row,\n rowIndex,\n column,\n columnIndex: colIndex,\n field: column?.field ?? '',\n value: row?.[column?.field as keyof typeof row] ?? null,\n isHeader: false,\n event,\n };\n } else if (header) {\n const colIndex = parseInt(header.getAttribute('data-col') ?? '-1', 10);\n const column = this.columns[colIndex];\n\n params = {\n row: null,\n rowIndex: -1,\n column,\n columnIndex: colIndex,\n field: column?.field ?? '',\n value: null,\n isHeader: true,\n event,\n };\n } else {\n return;\n }\n\n this.params = params;\n this.position = { x: event.clientX, y: event.clientY };\n\n const items = buildMenuItems(this.config.items ?? defaultItems, params);\n if (!items.length) return;\n\n if (this.menuElement) {\n this.menuElement.remove();\n }\n\n this.menuElement = createMenuElement(items, params, (item) => {\n if (item.action) {\n item.action(params);\n }\n this.menuElement?.remove();\n this.menuElement = null;\n this.isOpen = false;\n });\n\n document.body.appendChild(this.menuElement);\n positionMenu(this.menuElement, event.clientX, event.clientY);\n this.isOpen = true;\n\n this.emit('context-menu-open', { params, items });\n });\n }\n\n // ===== Public API =====\n\n /**\n * Programmatically show the context menu at the specified position.\n * @param x - X coordinate\n * @param y - Y coordinate\n * @param params - Partial context menu parameters\n */\n showMenu(x: number, y: number, params: Partial<ContextMenuParams>): void {\n const fullParams: ContextMenuParams = {\n row: params.row ?? null,\n rowIndex: params.rowIndex ?? -1,\n column: params.column ?? null,\n columnIndex: params.columnIndex ?? -1,\n field: params.field ?? '',\n value: params.value ?? null,\n isHeader: params.isHeader ?? false,\n event: params.event ?? new MouseEvent('contextmenu'),\n };\n\n const items = buildMenuItems(this.config.items ?? defaultItems, fullParams);\n\n if (this.menuElement) {\n this.menuElement.remove();\n }\n\n this.menuElement = createMenuElement(items, fullParams, (item) => {\n if (item.action) item.action(fullParams);\n this.menuElement?.remove();\n this.menuElement = null;\n this.isOpen = false;\n });\n\n document.body.appendChild(this.menuElement);\n positionMenu(this.menuElement, x, y);\n this.isOpen = true;\n }\n\n /**\n * Hide the context menu.\n */\n hideMenu(): void {\n if (this.menuElement) {\n this.menuElement.remove();\n this.menuElement = null;\n this.isOpen = false;\n }\n }\n\n /**\n * Check if the context menu is currently open.\n * @returns Whether the menu is open\n */\n isMenuOpen(): boolean {\n return this.isOpen;\n }\n\n // Styles are injected globally via installGlobalHandlers() since menu renders in document.body\n}\n"],"names":["BaseGridPlugin","config","grid","PluginClass","eventName","detail","message","buildMenuItems","items","params","item","isItemDisabled","createMenuElement","onAction","menu","separator","menuItem","disabled","icon","label","shortcut","arrow","subMenuItems","subMenu","e","positionMenu","x","y","menuRect","viewportWidth","viewportHeight","left","top","globalClickHandler","globalKeydownHandler","globalStyleSheet","contextMenuStyles","defaultItems","ContextMenuPlugin","shadowRoot","container","event","target","cell","header","rowIndex","colIndex","column","row","fullParams"],"mappings":"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"}
|