@toolbox-web/grid 0.0.4 → 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.
Files changed (55) hide show
  1. package/all.d.ts +29 -6
  2. package/all.js +421 -421
  3. package/all.js.map +1 -1
  4. package/index.d.ts +28 -0
  5. package/index.js +774 -726
  6. package/index.js.map +1 -1
  7. package/lib/plugins/clipboard/index.js +55 -35
  8. package/lib/plugins/clipboard/index.js.map +1 -1
  9. package/lib/plugins/column-virtualization/index.js +49 -29
  10. package/lib/plugins/column-virtualization/index.js.map +1 -1
  11. package/lib/plugins/context-menu/index.js +35 -15
  12. package/lib/plugins/context-menu/index.js.map +1 -1
  13. package/lib/plugins/export/index.js +52 -32
  14. package/lib/plugins/export/index.js.map +1 -1
  15. package/lib/plugins/filtering/index.js +116 -99
  16. package/lib/plugins/filtering/index.js.map +1 -1
  17. package/lib/plugins/grouping-columns/index.js +42 -22
  18. package/lib/plugins/grouping-columns/index.js.map +1 -1
  19. package/lib/plugins/grouping-rows/index.js +20 -0
  20. package/lib/plugins/grouping-rows/index.js.map +1 -1
  21. package/lib/plugins/master-detail/index.js +50 -27
  22. package/lib/plugins/master-detail/index.js.map +1 -1
  23. package/lib/plugins/multi-sort/index.js +25 -5
  24. package/lib/plugins/multi-sort/index.js.map +1 -1
  25. package/lib/plugins/pinned-columns/index.js +20 -0
  26. package/lib/plugins/pinned-columns/index.js.map +1 -1
  27. package/lib/plugins/pinned-rows/index.js +20 -0
  28. package/lib/plugins/pinned-rows/index.js.map +1 -1
  29. package/lib/plugins/pivot/index.js +20 -0
  30. package/lib/plugins/pivot/index.js.map +1 -1
  31. package/lib/plugins/reorder/index.js +48 -28
  32. package/lib/plugins/reorder/index.js.map +1 -1
  33. package/lib/plugins/selection/index.js +51 -31
  34. package/lib/plugins/selection/index.js.map +1 -1
  35. package/lib/plugins/server-side/index.js +20 -0
  36. package/lib/plugins/server-side/index.js.map +1 -1
  37. package/lib/plugins/tree/index.js +76 -53
  38. package/lib/plugins/tree/index.js.map +1 -1
  39. package/lib/plugins/undo-redo/index.js +20 -0
  40. package/lib/plugins/undo-redo/index.js.map +1 -1
  41. package/lib/plugins/visibility/index.js +20 -0
  42. package/lib/plugins/visibility/index.js.map +1 -1
  43. package/package.json +1 -1
  44. package/umd/grid.all.umd.js +25 -25
  45. package/umd/grid.all.umd.js.map +1 -1
  46. package/umd/grid.umd.js +12 -12
  47. package/umd/grid.umd.js.map +1 -1
  48. package/umd/plugins/filtering.umd.js +3 -3
  49. package/umd/plugins/filtering.umd.js.map +1 -1
  50. package/umd/plugins/master-detail.umd.js +2 -2
  51. package/umd/plugins/master-detail.umd.js.map +1 -1
  52. package/umd/plugins/reorder.umd.js +1 -1
  53. package/umd/plugins/reorder.umd.js.map +1 -1
  54. package/umd/plugins/tree.umd.js +2 -2
  55. package/umd/plugins/tree.umd.js.map +1 -1
@@ -1,4 +1,4 @@
1
- class b {
1
+ class p {
2
2
  /** Plugin version - override in subclass if needed */
3
3
  version = "1.0.0";
4
4
  /** CSS styles to inject into the grid's shadow DOM */
@@ -97,6 +97,26 @@ class b {
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,19 +124,19 @@ class b {
104
124
  console.warn(`[tbw-grid:${this.name}] ${e}`);
105
125
  }
106
126
  }
107
- function p(i) {
108
- const e = i.sticky;
127
+ function b(d) {
128
+ const e = d.sticky;
109
129
  if (e === "left" || e === "right")
110
130
  return !1;
111
- const t = i.meta ?? {}, n = t.sticky;
131
+ const t = d.meta ?? {}, n = t.sticky;
112
132
  return n === "left" || n === "right" ? !1 : t.lockPosition !== !0 && t.suppressMovable !== !0;
113
133
  }
114
- function h(i, e, t) {
115
- if (e === t || e < 0 || e >= i.length || t < 0 || t > i.length) return i;
116
- const n = [...i], [r] = n.splice(e, 1);
134
+ function h(d, e, t) {
135
+ if (e === t || e < 0 || e >= d.length || t < 0 || t > d.length) return d;
136
+ const n = [...d], [r] = n.splice(e, 1);
117
137
  return n.splice(t, 0, r), n;
118
138
  }
119
- class v extends b {
139
+ class v extends p {
120
140
  name = "reorder";
121
141
  version = "1.0.0";
122
142
  get defaultConfig() {
@@ -131,19 +151,19 @@ class v extends b {
131
151
  draggedField = null;
132
152
  draggedIndex = null;
133
153
  dropIndex = null;
134
- boundReorderRequestHandler = null;
135
154
  // ===== Lifecycle =====
136
155
  attach(e) {
137
- super.attach(e), this.boundReorderRequestHandler = (t) => {
138
- const n = t.detail;
139
- n?.field && typeof n.toIndex == "number" && this.moveColumn(n.field, n.toIndex);
140
- }, e.addEventListener("column-reorder-request", this.boundReorderRequestHandler);
156
+ super.attach(e), e.addEventListener(
157
+ "column-reorder-request",
158
+ (t) => {
159
+ const n = t.detail;
160
+ n?.field && typeof n.toIndex == "number" && this.moveColumn(n.field, n.toIndex);
161
+ },
162
+ { signal: this.disconnectSignal }
163
+ );
141
164
  }
142
165
  detach() {
143
- this.boundReorderRequestHandler && this.grid && (this.grid.removeEventListener(
144
- "column-reorder-request",
145
- this.boundReorderRequestHandler
146
- ), this.boundReorderRequestHandler = null), this.isDragging = !1, this.draggedField = null, this.draggedIndex = null, this.dropIndex = null;
166
+ this.isDragging = !1, this.draggedField = null, this.draggedIndex = null, this.dropIndex = null;
147
167
  }
148
168
  // ===== Hooks =====
149
169
  // Note: No processColumns hook needed - we directly update the grid's column order
@@ -154,26 +174,26 @@ class v extends b {
154
174
  e.querySelectorAll(".header-row > .cell").forEach((n) => {
155
175
  const r = n, s = r.getAttribute("data-field");
156
176
  if (!s) return;
157
- const u = this.columns.find((d) => d.field === s);
158
- if (!u || !p(u)) {
177
+ const u = this.columns.find((i) => i.field === s);
178
+ if (!u || !b(u)) {
159
179
  r.draggable = !1;
160
180
  return;
161
181
  }
162
- r.draggable = !0, !r.getAttribute("data-dragstart-bound") && (r.setAttribute("data-dragstart-bound", "true"), r.addEventListener("dragstart", (d) => {
182
+ r.draggable = !0, !r.getAttribute("data-dragstart-bound") && (r.setAttribute("data-dragstart-bound", "true"), r.addEventListener("dragstart", (i) => {
163
183
  const o = this.getColumnOrder().indexOf(s);
164
- this.isDragging = !0, this.draggedField = s, this.draggedIndex = o, d.dataTransfer && (d.dataTransfer.effectAllowed = "move", d.dataTransfer.setData("text/plain", s)), r.classList.add("dragging");
184
+ this.isDragging = !0, this.draggedField = s, this.draggedIndex = o, i.dataTransfer && (i.dataTransfer.effectAllowed = "move", i.dataTransfer.setData("text/plain", s)), r.classList.add("dragging");
165
185
  }), r.addEventListener("dragend", () => {
166
- this.isDragging = !1, this.draggedField = null, this.draggedIndex = null, this.dropIndex = null, e.querySelectorAll(".header-row > .cell").forEach((d) => {
167
- d.classList.remove("dragging", "drop-target", "drop-before", "drop-after");
186
+ this.isDragging = !1, this.draggedField = null, this.draggedIndex = null, this.dropIndex = null, e.querySelectorAll(".header-row > .cell").forEach((i) => {
187
+ i.classList.remove("dragging", "drop-target", "drop-before", "drop-after");
168
188
  });
169
- }), r.addEventListener("dragover", (d) => {
170
- if (d.preventDefault(), !this.isDragging || this.draggedField === s) return;
189
+ }), r.addEventListener("dragover", (i) => {
190
+ if (i.preventDefault(), !this.isDragging || this.draggedField === s) return;
171
191
  const a = r.getBoundingClientRect(), o = a.left + a.width / 2, g = this.getColumnOrder().indexOf(s);
172
- this.dropIndex = d.clientX < o ? g : g + 1, r.classList.add("drop-target"), r.classList.toggle("drop-before", d.clientX < o), r.classList.toggle("drop-after", d.clientX >= o);
192
+ this.dropIndex = i.clientX < o ? g : g + 1, r.classList.add("drop-target"), r.classList.toggle("drop-before", i.clientX < o), r.classList.toggle("drop-after", i.clientX >= o);
173
193
  }), r.addEventListener("dragleave", () => {
174
194
  r.classList.remove("drop-target", "drop-before", "drop-after");
175
- }), r.addEventListener("drop", (d) => {
176
- d.preventDefault();
195
+ }), r.addEventListener("drop", (i) => {
196
+ i.preventDefault();
177
197
  const a = this.draggedField, o = this.draggedIndex, l = this.dropIndex;
178
198
  if (!this.isDragging || a === null || o === null || l === null)
179
199
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../../../libs/grid/src/lib/core/plugin/base-plugin.ts","../../../../../../libs/grid/src/lib/plugins/reorder/column-drag.ts","../../../../../../libs/grid/src/lib/plugins/reorder/ReorderPlugin.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 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 * 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 Reordering Core Logic\n *\n * Pure functions for column drag and reordering operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\n\n/**\n * Check if a column can be moved.\n * Respects lockPosition, suppressMovable, and sticky properties.\n * Sticky (pinned) columns cannot be reordered as they have fixed positions.\n *\n * @param column - The column configuration to check\n * @returns True if the column can be moved\n */\nexport function canMoveColumn<TRow = unknown>(column: ColumnConfig<TRow>): boolean {\n // Check sticky directly on column config (primary location)\n const sticky = (column as ColumnConfig<TRow> & { sticky?: 'left' | 'right' }).sticky;\n if (sticky === 'left' || sticky === 'right') {\n return false;\n }\n\n // Also check meta.sticky for backwards compatibility\n const meta = column.meta ?? {};\n const metaSticky = (meta as { sticky?: 'left' | 'right' }).sticky;\n if (metaSticky === 'left' || metaSticky === 'right') {\n return false;\n }\n\n // Check for lockPosition or suppressMovable properties in the column config\n return meta.lockPosition !== true && meta.suppressMovable !== true;\n}\n\n/**\n * Move a column from one position to another in the order array.\n *\n * @param columns - Array of field names in current order\n * @param fromIndex - The current index of the column to move\n * @param toIndex - The target index to move the column to\n * @returns New array with updated order\n */\nexport function moveColumn(columns: string[], fromIndex: number, toIndex: number): string[] {\n if (fromIndex === toIndex) return columns;\n if (fromIndex < 0 || fromIndex >= columns.length) return columns;\n if (toIndex < 0 || toIndex > columns.length) return columns;\n\n const result = [...columns];\n const [removed] = result.splice(fromIndex, 1);\n result.splice(toIndex, 0, removed);\n return result;\n}\n\n/**\n * Calculate the drop index based on the current drag position.\n *\n * @param dragX - The current X position of the drag\n * @param headerRect - The bounding rect of the header container\n * @param columnWidths - Array of column widths in order\n * @returns The index where the column should be dropped\n */\nexport function getDropIndex(dragX: number, headerRect: DOMRect, columnWidths: number[]): number {\n let x = headerRect.left;\n\n for (let i = 0; i < columnWidths.length; i++) {\n const mid = x + columnWidths[i] / 2;\n if (dragX < mid) return i;\n x += columnWidths[i];\n }\n\n return columnWidths.length;\n}\n\n/**\n * Reorder columns according to a specified order.\n * Columns not in the order array are appended at the end.\n *\n * @param columns - Array of column configurations\n * @param order - Array of field names specifying the desired order\n * @returns New array of columns in the specified order\n */\nexport function reorderColumns<TRow = unknown>(columns: ColumnConfig<TRow>[], order: string[]): ColumnConfig<TRow>[] {\n const columnMap = new Map<string, ColumnConfig<TRow>>(columns.map((c) => [c.field as string, c]));\n const reordered: ColumnConfig<TRow>[] = [];\n\n // Add columns in specified order\n for (const field of order) {\n const col = columnMap.get(field);\n if (col) {\n reordered.push(col);\n columnMap.delete(field);\n }\n }\n\n // Add any remaining columns not in order\n for (const col of columnMap.values()) {\n reordered.push(col);\n }\n\n return reordered;\n}\n","/**\n * Column Reordering Plugin (Class-based)\n *\n * Provides drag-and-drop column reordering functionality for tbw-grid.\n * Supports keyboard and mouse interactions with visual feedback.\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport { canMoveColumn, moveColumn } from './column-drag';\nimport type { ColumnMoveDetail, ReorderConfig } from './types';\n\n/** Extended grid interface with column order methods */\ninterface GridWithColumnOrder {\n setColumnOrder(order: string[]): void;\n getColumnOrder(): string[];\n requestStateChange?: () => void;\n}\n\n/**\n * Column Reordering Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new ReorderPlugin({\n * enabled: true,\n * animation: true,\n * animationDuration: 200,\n * })\n * ```\n */\nexport class ReorderPlugin extends BaseGridPlugin<ReorderConfig> {\n readonly name = 'reorder';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<ReorderConfig> {\n return {\n enabled: true,\n animation: true,\n animationDuration: 200,\n };\n }\n\n // ===== Internal State =====\n private isDragging = false;\n private draggedField: string | null = null;\n private draggedIndex: number | null = null;\n private dropIndex: number | null = null;\n private boundReorderRequestHandler: ((e: Event) => void) | null = null;\n\n // ===== Lifecycle =====\n\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Listen for reorder requests from other plugins (e.g., VisibilityPlugin)\n this.boundReorderRequestHandler = (e: Event) => {\n const detail = (e as CustomEvent).detail;\n if (detail?.field && typeof detail.toIndex === 'number') {\n this.moveColumn(detail.field, detail.toIndex);\n }\n };\n (grid as unknown as HTMLElement).addEventListener('column-reorder-request', this.boundReorderRequestHandler);\n }\n\n override detach(): void {\n // Remove event listener\n if (this.boundReorderRequestHandler && this.grid) {\n (this.grid as unknown as HTMLElement).removeEventListener(\n 'column-reorder-request',\n this.boundReorderRequestHandler\n );\n this.boundReorderRequestHandler = null;\n }\n\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n }\n\n // ===== Hooks =====\n // Note: No processColumns hook needed - we directly update the grid's column order\n\n override afterRender(): void {\n if (!this.config.enabled) return;\n\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n const headers = shadowRoot.querySelectorAll('.header-row > .cell');\n\n headers.forEach((header) => {\n const headerEl = header as HTMLElement;\n const field = headerEl.getAttribute('data-field');\n if (!field) return;\n\n const column = this.columns.find((c) => c.field === field);\n if (!column || !canMoveColumn(column)) {\n headerEl.draggable = false;\n return;\n }\n\n headerEl.draggable = true;\n\n // Remove existing listeners to prevent duplicates\n if (headerEl.getAttribute('data-dragstart-bound')) return;\n headerEl.setAttribute('data-dragstart-bound', 'true');\n\n headerEl.addEventListener('dragstart', (e: DragEvent) => {\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.isDragging = true;\n this.draggedField = field;\n this.draggedIndex = orderIndex;\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', field);\n }\n\n headerEl.classList.add('dragging');\n });\n\n headerEl.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n\n shadowRoot.querySelectorAll('.header-row > .cell').forEach((h) => {\n h.classList.remove('dragging', 'drop-target', 'drop-before', 'drop-after');\n });\n });\n\n headerEl.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging || this.draggedField === field) return;\n\n const rect = headerEl.getBoundingClientRect();\n const midX = rect.left + rect.width / 2;\n\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.dropIndex = e.clientX < midX ? orderIndex : orderIndex + 1;\n\n headerEl.classList.add('drop-target');\n headerEl.classList.toggle('drop-before', e.clientX < midX);\n headerEl.classList.toggle('drop-after', e.clientX >= midX);\n });\n\n headerEl.addEventListener('dragleave', () => {\n headerEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n headerEl.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n const draggedField = this.draggedField;\n const draggedIndex = this.draggedIndex;\n const dropIndex = this.dropIndex;\n\n if (!this.isDragging || draggedField === null || draggedIndex === null || dropIndex === null) {\n return;\n }\n\n const effectiveToIndex = dropIndex > draggedIndex ? dropIndex - 1 : dropIndex;\n const currentOrder = this.getColumnOrder();\n const newOrder = moveColumn(currentOrder, draggedIndex, effectiveToIndex);\n\n const detail: ColumnMoveDetail = {\n field: draggedField,\n fromIndex: draggedIndex,\n toIndex: effectiveToIndex,\n columnOrder: newOrder,\n };\n\n // Directly update the grid's column order\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(newOrder);\n\n this.emit('column-move', detail);\n // Trigger state change after reorder\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\n });\n });\n }\n\n // ===== Public API =====\n\n /**\n * Get the current column order from the grid.\n * @returns Array of field names in display order\n */\n getColumnOrder(): string[] {\n return (this.grid as unknown as GridWithColumnOrder).getColumnOrder();\n }\n\n /**\n * Move a column to a new position.\n * @param field - The field name of the column to move\n * @param toIndex - The target index\n */\n moveColumn(field: string, toIndex: number): void {\n const currentOrder = this.getColumnOrder();\n const fromIndex = currentOrder.indexOf(field);\n if (fromIndex === -1) return;\n\n const newOrder = moveColumn(currentOrder, fromIndex, toIndex);\n\n // Directly update the grid's column order\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(newOrder);\n\n this.emit<ColumnMoveDetail>('column-move', {\n field,\n fromIndex,\n toIndex,\n columnOrder: newOrder,\n });\n\n // Trigger state change after reorder\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\n }\n\n /**\n * Set a specific column order.\n * @param order - Array of field names in desired order\n */\n setColumnOrder(order: string[]): void {\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(order);\n // Trigger state change after reorder\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\n }\n\n /**\n * Reset column order to the original configuration order.\n */\n resetColumnOrder(): void {\n const originalOrder = this.columns.map((c) => c.field);\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(originalOrder);\n // Trigger state change after reset\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\n }\n\n // ===== Styles =====\n\n override readonly styles = `\n .header-row > .cell[draggable=\"true\"] {\n cursor: grab;\n position: relative;\n }\n .header-row > .cell.dragging {\n opacity: 0.5;\n cursor: grabbing;\n }\n .header-row > .cell.drop-before::before {\n content: '';\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--tbw-reorder-indicator, var(--tbw-color-accent));\n z-index: 1;\n }\n .header-row > .cell.drop-after::after {\n content: '';\n position: absolute;\n right: 0;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--tbw-reorder-indicator, var(--tbw-color-accent));\n z-index: 1;\n }\n `;\n}\n"],"names":["BaseGridPlugin","config","grid","PluginClass","eventName","detail","message","canMoveColumn","column","sticky","meta","metaSticky","moveColumn","columns","fromIndex","toIndex","result","removed","ReorderPlugin","e","shadowRoot","header","headerEl","field","c","orderIndex","h","rect","midX","draggedField","draggedIndex","dropIndex","effectiveToIndex","currentOrder","newOrder","order","originalOrder"],"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/reorder/column-drag.ts","../../../../../../libs/grid/src/lib/plugins/reorder/ReorderPlugin.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 Reordering Core Logic\n *\n * Pure functions for column drag and reordering operations.\n */\n\nimport type { ColumnConfig } from '../../core/types';\n\n/**\n * Check if a column can be moved.\n * Respects lockPosition, suppressMovable, and sticky properties.\n * Sticky (pinned) columns cannot be reordered as they have fixed positions.\n *\n * @param column - The column configuration to check\n * @returns True if the column can be moved\n */\nexport function canMoveColumn<TRow = unknown>(column: ColumnConfig<TRow>): boolean {\n // Check sticky directly on column config (primary location)\n const sticky = (column as ColumnConfig<TRow> & { sticky?: 'left' | 'right' }).sticky;\n if (sticky === 'left' || sticky === 'right') {\n return false;\n }\n\n // Also check meta.sticky for backwards compatibility\n const meta = column.meta ?? {};\n const metaSticky = (meta as { sticky?: 'left' | 'right' }).sticky;\n if (metaSticky === 'left' || metaSticky === 'right') {\n return false;\n }\n\n // Check for lockPosition or suppressMovable properties in the column config\n return meta.lockPosition !== true && meta.suppressMovable !== true;\n}\n\n/**\n * Move a column from one position to another in the order array.\n *\n * @param columns - Array of field names in current order\n * @param fromIndex - The current index of the column to move\n * @param toIndex - The target index to move the column to\n * @returns New array with updated order\n */\nexport function moveColumn(columns: string[], fromIndex: number, toIndex: number): string[] {\n if (fromIndex === toIndex) return columns;\n if (fromIndex < 0 || fromIndex >= columns.length) return columns;\n if (toIndex < 0 || toIndex > columns.length) return columns;\n\n const result = [...columns];\n const [removed] = result.splice(fromIndex, 1);\n result.splice(toIndex, 0, removed);\n return result;\n}\n\n/**\n * Calculate the drop index based on the current drag position.\n *\n * @param dragX - The current X position of the drag\n * @param headerRect - The bounding rect of the header container\n * @param columnWidths - Array of column widths in order\n * @returns The index where the column should be dropped\n */\nexport function getDropIndex(dragX: number, headerRect: DOMRect, columnWidths: number[]): number {\n let x = headerRect.left;\n\n for (let i = 0; i < columnWidths.length; i++) {\n const mid = x + columnWidths[i] / 2;\n if (dragX < mid) return i;\n x += columnWidths[i];\n }\n\n return columnWidths.length;\n}\n\n/**\n * Reorder columns according to a specified order.\n * Columns not in the order array are appended at the end.\n *\n * @param columns - Array of column configurations\n * @param order - Array of field names specifying the desired order\n * @returns New array of columns in the specified order\n */\nexport function reorderColumns<TRow = unknown>(columns: ColumnConfig<TRow>[], order: string[]): ColumnConfig<TRow>[] {\n const columnMap = new Map<string, ColumnConfig<TRow>>(columns.map((c) => [c.field as string, c]));\n const reordered: ColumnConfig<TRow>[] = [];\n\n // Add columns in specified order\n for (const field of order) {\n const col = columnMap.get(field);\n if (col) {\n reordered.push(col);\n columnMap.delete(field);\n }\n }\n\n // Add any remaining columns not in order\n for (const col of columnMap.values()) {\n reordered.push(col);\n }\n\n return reordered;\n}\n","/**\n * Column Reordering Plugin (Class-based)\n *\n * Provides drag-and-drop column reordering functionality for tbw-grid.\n * Supports keyboard and mouse interactions with visual feedback.\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport { canMoveColumn, moveColumn } from './column-drag';\nimport type { ColumnMoveDetail, ReorderConfig } from './types';\n\n/** Extended grid interface with column order methods */\ninterface GridWithColumnOrder {\n setColumnOrder(order: string[]): void;\n getColumnOrder(): string[];\n requestStateChange?: () => void;\n}\n\n/**\n * Column Reordering Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new ReorderPlugin({\n * enabled: true,\n * animation: true,\n * animationDuration: 200,\n * })\n * ```\n */\nexport class ReorderPlugin extends BaseGridPlugin<ReorderConfig> {\n readonly name = 'reorder';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<ReorderConfig> {\n return {\n enabled: true,\n animation: true,\n animationDuration: 200,\n };\n }\n\n // ===== Internal State =====\n private isDragging = false;\n private draggedField: string | null = null;\n private draggedIndex: number | null = null;\n private dropIndex: number | null = null;\n\n // ===== Lifecycle =====\n\n override attach(grid: import('../../core/plugin/base-plugin').GridElement): void {\n super.attach(grid);\n\n // Listen for reorder requests from other plugins (e.g., VisibilityPlugin)\n // Uses disconnectSignal for automatic cleanup - no need for manual removeEventListener\n (grid as unknown as HTMLElement).addEventListener(\n 'column-reorder-request',\n (e: Event) => {\n const detail = (e as CustomEvent).detail;\n if (detail?.field && typeof detail.toIndex === 'number') {\n this.moveColumn(detail.field, detail.toIndex);\n }\n },\n { signal: this.disconnectSignal }\n );\n }\n\n override detach(): void {\n // Event listeners using eventSignal are automatically cleaned up\n // Just reset internal state\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n }\n\n // ===== Hooks =====\n // Note: No processColumns hook needed - we directly update the grid's column order\n\n override afterRender(): void {\n if (!this.config.enabled) return;\n\n const shadowRoot = this.shadowRoot;\n if (!shadowRoot) return;\n\n const headers = shadowRoot.querySelectorAll('.header-row > .cell');\n\n headers.forEach((header) => {\n const headerEl = header as HTMLElement;\n const field = headerEl.getAttribute('data-field');\n if (!field) return;\n\n const column = this.columns.find((c) => c.field === field);\n if (!column || !canMoveColumn(column)) {\n headerEl.draggable = false;\n return;\n }\n\n headerEl.draggable = true;\n\n // Remove existing listeners to prevent duplicates\n if (headerEl.getAttribute('data-dragstart-bound')) return;\n headerEl.setAttribute('data-dragstart-bound', 'true');\n\n headerEl.addEventListener('dragstart', (e: DragEvent) => {\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.isDragging = true;\n this.draggedField = field;\n this.draggedIndex = orderIndex;\n\n if (e.dataTransfer) {\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', field);\n }\n\n headerEl.classList.add('dragging');\n });\n\n headerEl.addEventListener('dragend', () => {\n this.isDragging = false;\n this.draggedField = null;\n this.draggedIndex = null;\n this.dropIndex = null;\n\n shadowRoot.querySelectorAll('.header-row > .cell').forEach((h) => {\n h.classList.remove('dragging', 'drop-target', 'drop-before', 'drop-after');\n });\n });\n\n headerEl.addEventListener('dragover', (e: DragEvent) => {\n e.preventDefault();\n if (!this.isDragging || this.draggedField === field) return;\n\n const rect = headerEl.getBoundingClientRect();\n const midX = rect.left + rect.width / 2;\n\n const currentOrder = this.getColumnOrder();\n const orderIndex = currentOrder.indexOf(field);\n this.dropIndex = e.clientX < midX ? orderIndex : orderIndex + 1;\n\n headerEl.classList.add('drop-target');\n headerEl.classList.toggle('drop-before', e.clientX < midX);\n headerEl.classList.toggle('drop-after', e.clientX >= midX);\n });\n\n headerEl.addEventListener('dragleave', () => {\n headerEl.classList.remove('drop-target', 'drop-before', 'drop-after');\n });\n\n headerEl.addEventListener('drop', (e: DragEvent) => {\n e.preventDefault();\n const draggedField = this.draggedField;\n const draggedIndex = this.draggedIndex;\n const dropIndex = this.dropIndex;\n\n if (!this.isDragging || draggedField === null || draggedIndex === null || dropIndex === null) {\n return;\n }\n\n const effectiveToIndex = dropIndex > draggedIndex ? dropIndex - 1 : dropIndex;\n const currentOrder = this.getColumnOrder();\n const newOrder = moveColumn(currentOrder, draggedIndex, effectiveToIndex);\n\n const detail: ColumnMoveDetail = {\n field: draggedField,\n fromIndex: draggedIndex,\n toIndex: effectiveToIndex,\n columnOrder: newOrder,\n };\n\n // Directly update the grid's column order\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(newOrder);\n\n this.emit('column-move', detail);\n // Trigger state change after reorder\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\n });\n });\n }\n\n // ===== Public API =====\n\n /**\n * Get the current column order from the grid.\n * @returns Array of field names in display order\n */\n getColumnOrder(): string[] {\n return (this.grid as unknown as GridWithColumnOrder).getColumnOrder();\n }\n\n /**\n * Move a column to a new position.\n * @param field - The field name of the column to move\n * @param toIndex - The target index\n */\n moveColumn(field: string, toIndex: number): void {\n const currentOrder = this.getColumnOrder();\n const fromIndex = currentOrder.indexOf(field);\n if (fromIndex === -1) return;\n\n const newOrder = moveColumn(currentOrder, fromIndex, toIndex);\n\n // Directly update the grid's column order\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(newOrder);\n\n this.emit<ColumnMoveDetail>('column-move', {\n field,\n fromIndex,\n toIndex,\n columnOrder: newOrder,\n });\n\n // Trigger state change after reorder\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\n }\n\n /**\n * Set a specific column order.\n * @param order - Array of field names in desired order\n */\n setColumnOrder(order: string[]): void {\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(order);\n // Trigger state change after reorder\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\n }\n\n /**\n * Reset column order to the original configuration order.\n */\n resetColumnOrder(): void {\n const originalOrder = this.columns.map((c) => c.field);\n (this.grid as unknown as GridWithColumnOrder).setColumnOrder(originalOrder);\n // Trigger state change after reset\n (this.grid as unknown as GridWithColumnOrder).requestStateChange?.();\n }\n\n // ===== Styles =====\n\n override readonly styles = `\n .header-row > .cell[draggable=\"true\"] {\n cursor: grab;\n position: relative;\n }\n .header-row > .cell.dragging {\n opacity: 0.5;\n cursor: grabbing;\n }\n .header-row > .cell.drop-before::before {\n content: '';\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--tbw-reorder-indicator, var(--tbw-color-accent));\n z-index: 1;\n }\n .header-row > .cell.drop-after::after {\n content: '';\n position: absolute;\n right: 0;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--tbw-reorder-indicator, var(--tbw-color-accent));\n z-index: 1;\n }\n `;\n}\n"],"names":["BaseGridPlugin","config","grid","PluginClass","eventName","detail","message","canMoveColumn","column","sticky","meta","metaSticky","moveColumn","columns","fromIndex","toIndex","result","removed","ReorderPlugin","e","shadowRoot","header","headerEl","field","c","orderIndex","h","rect","midX","draggedField","draggedIndex","dropIndex","effectiveToIndex","currentOrder","newOrder","order","originalOrder"],"mappings":"AA+MO,MAAeA,EAAkC;AAAA;AAAA,EAK7C,UAAkB;AAAA;AAAA,EAGlB;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGC;AAAA;AAAA,EAGA;AAAA;AAAA,EAGO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOjB,IAAc,gBAAkC;AAC9C,WAAO,CAAA;AAAA,EACT;AAAA,EAEA,YAAYC,IAA2B,IAAI;AACzC,SAAK,aAAaA;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAOC,GAAyB;AAC9B,SAAK,OAAOA,GAEZ,KAAK,SAAS,EAAE,GAAG,KAAK,eAAe,GAAG,KAAK,WAAA;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAAe;AAAA,EAEf;AAAA;AAAA;AAAA;AAAA;AAAA,EAMU,UAAoCC,GAAuD;AACnG,WAAO,KAAK,MAAM,UAAUA,CAAW;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKU,KAAQC,GAAmBC,GAAiB;AACpD,SAAK,MAAM,gBAAgB,IAAI,YAAYD,GAAW,EAAE,QAAAC,GAAQ,SAAS,GAAA,CAAM,CAAC;AAAA,EAClF;AAAA;AAAA;AAAA;AAAA,EAKU,gBAAsB;AAC9B,SAAK,MAAM,gBAAA;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMU,qBAA2B;AACnC,SAAK,MAAM,qBAAA;AAAA,EACb;AAAA;AAAA;AAAA;AAAA,EAKA,IAAc,OAAc;AAC1B,WAAO,KAAK,MAAM,QAAQ,CAAA;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAc,aAAoB;AAChC,WAAQ,KAAK,MAAc,cAAc,CAAA;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKA,IAAc,UAA0B;AACtC,WAAO,KAAK,MAAM,WAAW,CAAA;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAc,iBAAiC;AAC7C,WAAQ,KAAK,MAAc,kBAAkB,CAAA;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA,EAKA,IAAc,aAAgC;AAC5C,WAAO,KAAK,MAAM,cAAc;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBA,IAAc,mBAAgC;AAC5C,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA,EAKU,KAAKC,GAAuB;AACpC,YAAQ,KAAK,aAAa,KAAK,IAAI,KAAKA,CAAO,EAAE;AAAA,EACnD;AAkZF;ACzuBO,SAASC,EAA8BC,GAAqC;AAEjF,QAAMC,IAAUD,EAA8D;AAC9E,MAAIC,MAAW,UAAUA,MAAW;AAClC,WAAO;AAIT,QAAMC,IAAOF,EAAO,QAAQ,CAAA,GACtBG,IAAcD,EAAuC;AAC3D,SAAIC,MAAe,UAAUA,MAAe,UACnC,KAIFD,EAAK,iBAAiB,MAAQA,EAAK,oBAAoB;AAChE;AAUO,SAASE,EAAWC,GAAmBC,GAAmBC,GAA2B;AAG1F,MAFID,MAAcC,KACdD,IAAY,KAAKA,KAAaD,EAAQ,UACtCE,IAAU,KAAKA,IAAUF,EAAQ,OAAQ,QAAOA;AAEpD,QAAMG,IAAS,CAAC,GAAGH,CAAO,GACpB,CAACI,CAAO,IAAID,EAAO,OAAOF,GAAW,CAAC;AAC5C,SAAAE,EAAO,OAAOD,GAAS,GAAGE,CAAO,GAC1BD;AACT;ACrBO,MAAME,UAAsBlB,EAA8B;AAAA,EACtD,OAAO;AAAA,EACE,UAAU;AAAA,EAE5B,IAAuB,gBAAwC;AAC7D,WAAO;AAAA,MACL,SAAS;AAAA,MACT,WAAW;AAAA,MACX,mBAAmB;AAAA,IAAA;AAAA,EAEvB;AAAA;AAAA,EAGQ,aAAa;AAAA,EACb,eAA8B;AAAA,EAC9B,eAA8B;AAAA,EAC9B,YAA2B;AAAA;AAAA,EAI1B,OAAOE,GAAiE;AAC/E,UAAM,OAAOA,CAAI,GAIhBA,EAAgC;AAAA,MAC/B;AAAA,MACA,CAACiB,MAAa;AACZ,cAAMd,IAAUc,EAAkB;AAClC,QAAId,GAAQ,SAAS,OAAOA,EAAO,WAAY,YAC7C,KAAK,WAAWA,EAAO,OAAOA,EAAO,OAAO;AAAA,MAEhD;AAAA,MACA,EAAE,QAAQ,KAAK,iBAAA;AAAA,IAAiB;AAAA,EAEpC;AAAA,EAES,SAAe;AAGtB,SAAK,aAAa,IAClB,KAAK,eAAe,MACpB,KAAK,eAAe,MACpB,KAAK,YAAY;AAAA,EACnB;AAAA;AAAA;AAAA,EAKS,cAAoB;AAC3B,QAAI,CAAC,KAAK,OAAO,QAAS;AAE1B,UAAMe,IAAa,KAAK;AACxB,QAAI,CAACA,EAAY;AAIjB,IAFgBA,EAAW,iBAAiB,qBAAqB,EAEzD,QAAQ,CAACC,MAAW;AAC1B,YAAMC,IAAWD,GACXE,IAAQD,EAAS,aAAa,YAAY;AAChD,UAAI,CAACC,EAAO;AAEZ,YAAMf,IAAS,KAAK,QAAQ,KAAK,CAACgB,MAAMA,EAAE,UAAUD,CAAK;AACzD,UAAI,CAACf,KAAU,CAACD,EAAcC,CAAM,GAAG;AACrC,QAAAc,EAAS,YAAY;AACrB;AAAA,MACF;AAKA,MAHAA,EAAS,YAAY,IAGjB,CAAAA,EAAS,aAAa,sBAAsB,MAChDA,EAAS,aAAa,wBAAwB,MAAM,GAEpDA,EAAS,iBAAiB,aAAa,CAACH,MAAiB;AAEvD,cAAMM,IADe,KAAK,eAAA,EACM,QAAQF,CAAK;AAC7C,aAAK,aAAa,IAClB,KAAK,eAAeA,GACpB,KAAK,eAAeE,GAEhBN,EAAE,iBACJA,EAAE,aAAa,gBAAgB,QAC/BA,EAAE,aAAa,QAAQ,cAAcI,CAAK,IAG5CD,EAAS,UAAU,IAAI,UAAU;AAAA,MACnC,CAAC,GAEDA,EAAS,iBAAiB,WAAW,MAAM;AACzC,aAAK,aAAa,IAClB,KAAK,eAAe,MACpB,KAAK,eAAe,MACpB,KAAK,YAAY,MAEjBF,EAAW,iBAAiB,qBAAqB,EAAE,QAAQ,CAACM,MAAM;AAChE,UAAAA,EAAE,UAAU,OAAO,YAAY,eAAe,eAAe,YAAY;AAAA,QAC3E,CAAC;AAAA,MACH,CAAC,GAEDJ,EAAS,iBAAiB,YAAY,CAACH,MAAiB;AAEtD,YADAA,EAAE,eAAA,GACE,CAAC,KAAK,cAAc,KAAK,iBAAiBI,EAAO;AAErD,cAAMI,IAAOL,EAAS,sBAAA,GAChBM,IAAOD,EAAK,OAAOA,EAAK,QAAQ,GAGhCF,IADe,KAAK,eAAA,EACM,QAAQF,CAAK;AAC7C,aAAK,YAAYJ,EAAE,UAAUS,IAAOH,IAAaA,IAAa,GAE9DH,EAAS,UAAU,IAAI,aAAa,GACpCA,EAAS,UAAU,OAAO,eAAeH,EAAE,UAAUS,CAAI,GACzDN,EAAS,UAAU,OAAO,cAAcH,EAAE,WAAWS,CAAI;AAAA,MAC3D,CAAC,GAEDN,EAAS,iBAAiB,aAAa,MAAM;AAC3C,QAAAA,EAAS,UAAU,OAAO,eAAe,eAAe,YAAY;AAAA,MACtE,CAAC,GAEDA,EAAS,iBAAiB,QAAQ,CAACH,MAAiB;AAClD,QAAAA,EAAE,eAAA;AACF,cAAMU,IAAe,KAAK,cACpBC,IAAe,KAAK,cACpBC,IAAY,KAAK;AAEvB,YAAI,CAAC,KAAK,cAAcF,MAAiB,QAAQC,MAAiB,QAAQC,MAAc;AACtF;AAGF,cAAMC,IAAmBD,IAAYD,IAAeC,IAAY,IAAIA,GAC9DE,IAAe,KAAK,eAAA,GACpBC,IAAWtB,EAAWqB,GAAcH,GAAcE,CAAgB,GAElE3B,IAA2B;AAAA,UAC/B,OAAOwB;AAAA,UACP,WAAWC;AAAA,UACX,SAASE;AAAA,UACT,aAAaE;AAAA,QAAA;AAId,aAAK,KAAwC,eAAeA,CAAQ,GAErE,KAAK,KAAK,eAAe7B,CAAM,GAE9B,KAAK,KAAwC,qBAAA;AAAA,MAChD,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,iBAA2B;AACzB,WAAQ,KAAK,KAAwC,eAAA;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,WAAWkB,GAAeR,GAAuB;AAC/C,UAAMkB,IAAe,KAAK,eAAA,GACpBnB,IAAYmB,EAAa,QAAQV,CAAK;AAC5C,QAAIT,MAAc,GAAI;AAEtB,UAAMoB,IAAWtB,EAAWqB,GAAcnB,GAAWC,CAAO;AAG3D,SAAK,KAAwC,eAAemB,CAAQ,GAErE,KAAK,KAAuB,eAAe;AAAA,MACzC,OAAAX;AAAA,MACA,WAAAT;AAAA,MACA,SAAAC;AAAA,MACA,aAAamB;AAAA,IAAA,CACd,GAGA,KAAK,KAAwC,qBAAA;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAeC,GAAuB;AACnC,SAAK,KAAwC,eAAeA,CAAK,GAEjE,KAAK,KAAwC,qBAAA;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA,EAKA,mBAAyB;AACvB,UAAMC,IAAgB,KAAK,QAAQ,IAAI,CAACZ,MAAMA,EAAE,KAAK;AACpD,SAAK,KAAwC,eAAeY,CAAa,GAEzE,KAAK,KAAwC,qBAAA;AAAA,EAChD;AAAA;AAAA,EAIkB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8B7B;"}
@@ -97,6 +97,26 @@ class f {
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
  */
@@ -197,32 +217,32 @@ class v extends f {
197
217
  }
198
218
  // ===== Event Handlers =====
199
219
  onCellClick(e) {
200
- const { rowIndex: t, colIndex: l, originalEvent: r } = e, { mode: o } = this.config;
201
- if (o === "cell")
220
+ const { rowIndex: t, colIndex: l, originalEvent: r } = e, { mode: n } = this.config;
221
+ if (n === "cell")
202
222
  return this.selectedCell = { row: t, col: l }, this.emit("selection-change", this.#e()), this.requestAfterRender(), !1;
203
- if (o === "row")
223
+ if (n === "row")
204
224
  return this.selected.clear(), this.selected.add(t), this.lastSelected = t, this.emit("selection-change", this.#e()), this.requestAfterRender(), !1;
205
- if (o === "range") {
225
+ if (n === "range") {
206
226
  const a = r.shiftKey, i = r.ctrlKey || r.metaKey;
207
227
  if (a && this.cellAnchor) {
208
- const n = u(this.cellAnchor, { row: t, col: l });
209
- i ? this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] = n : this.ranges.push(n) : this.ranges = [n], this.activeRange = n;
228
+ const o = u(this.cellAnchor, { row: t, col: l });
229
+ i ? this.ranges.length > 0 ? this.ranges[this.ranges.length - 1] = o : this.ranges.push(o) : this.ranges = [o], this.activeRange = o;
210
230
  } else if (i) {
211
- const n = {
231
+ const o = {
212
232
  startRow: t,
213
233
  startCol: l,
214
234
  endRow: t,
215
235
  endCol: l
216
236
  };
217
- this.ranges.push(n), this.activeRange = n, this.cellAnchor = { row: t, col: l };
237
+ this.ranges.push(o), this.activeRange = o, this.cellAnchor = { row: t, col: l };
218
238
  } else {
219
- const n = {
239
+ const o = {
220
240
  startRow: t,
221
241
  startCol: l,
222
242
  endRow: t,
223
243
  endCol: l
224
244
  };
225
- this.ranges = [n], this.activeRange = n, this.cellAnchor = { row: t, col: l };
245
+ this.ranges = [o], this.activeRange = o, this.cellAnchor = { row: t, col: l };
226
246
  }
227
247
  return this.emit("selection-change", this.#e()), this.requestAfterRender(), !1;
228
248
  }
@@ -235,13 +255,13 @@ class v extends f {
235
255
  if (t === "range" && e.key === "a" && (e.ctrlKey || e.metaKey)) {
236
256
  const l = this.rows.length, r = this.columns.length;
237
257
  if (l > 0 && r > 0) {
238
- const o = {
258
+ const n = {
239
259
  startRow: 0,
240
260
  startCol: 0,
241
261
  endRow: l - 1,
242
262
  endCol: r - 1
243
263
  };
244
- return this.ranges = [o], this.activeRange = o, this.emit("selection-change", this.#e()), this.requestAfterRender(), !0;
264
+ return this.ranges = [n], this.activeRange = n, this.emit("selection-change", this.#e()), this.requestAfterRender(), !0;
245
265
  }
246
266
  }
247
267
  return !1;
@@ -252,13 +272,13 @@ class v extends f {
252
272
  this.isDragging = !0;
253
273
  const t = e.rowIndex, l = e.colIndex;
254
274
  this.cellAnchor = { row: t, col: l }, e.originalEvent.ctrlKey || e.originalEvent.metaKey || (this.ranges = []);
255
- const o = {
275
+ const n = {
256
276
  startRow: t,
257
277
  startCol: l,
258
278
  endRow: t,
259
279
  endCol: l
260
280
  };
261
- return this.ranges.push(o), this.activeRange = o, this.emit("selection-change", this.#e()), this.requestAfterRender(), !0;
281
+ return this.ranges.push(n), this.activeRange = n, this.emit("selection-change", this.#e()), this.requestAfterRender(), !0;
262
282
  }
263
283
  onCellMouseMove(e) {
264
284
  if (this.config.mode !== "range" || !this.isDragging || !this.cellAnchor || e.rowIndex === void 0 || e.colIndex === void 0 || e.rowIndex < 0) return;
@@ -277,23 +297,23 @@ class v extends f {
277
297
  const e = this.shadowRoot;
278
298
  if (!e) return;
279
299
  const { mode: t } = this.config;
280
- e.querySelectorAll(".cell").forEach((o) => {
281
- o.classList.remove("selected", "top", "bottom", "first", "last");
300
+ e.querySelectorAll(".cell").forEach((n) => {
301
+ n.classList.remove("selected", "top", "bottom", "first", "last");
282
302
  });
283
303
  const r = e.querySelectorAll(".data-grid-row");
284
- if (r.forEach((o) => {
285
- o.classList.remove("selected", "row-focus");
286
- }), t === "row" && r.forEach((o) => {
287
- const a = o.querySelector(".cell[data-row]"), i = parseInt(a?.getAttribute("data-row") ?? "-1", 10);
288
- i >= 0 && this.selected.has(i) && (o.classList.add("selected", "row-focus"), o.querySelectorAll(".cell-focus").forEach((n) => n.classList.remove("cell-focus")));
304
+ if (r.forEach((n) => {
305
+ n.classList.remove("selected", "row-focus");
306
+ }), t === "row" && r.forEach((n) => {
307
+ const a = n.querySelector(".cell[data-row]"), i = parseInt(a?.getAttribute("data-row") ?? "-1", 10);
308
+ i >= 0 && this.selected.has(i) && (n.classList.add("selected", "row-focus"), n.querySelectorAll(".cell-focus").forEach((o) => o.classList.remove("cell-focus")));
289
309
  }), t === "range" && this.ranges.length > 0) {
290
- const o = this.activeRange ? h(this.activeRange) : null;
310
+ const n = this.activeRange ? h(this.activeRange) : null;
291
311
  e.querySelectorAll(".cell[data-row][data-col]").forEach((i) => {
292
- const n = parseInt(i.getAttribute("data-row") ?? "-1", 10), c = parseInt(i.getAttribute("data-col") ?? "-1", 10);
293
- n >= 0 && c >= 0 && g(n, c, this.ranges) && (i.classList.add("selected"), i.classList.remove("cell-focus"), o && (n === o.startRow && i.classList.add("top"), n === o.endRow && i.classList.add("bottom"), c === o.startCol && i.classList.add("first"), c === o.endCol && i.classList.add("last")));
312
+ const o = parseInt(i.getAttribute("data-row") ?? "-1", 10), c = parseInt(i.getAttribute("data-col") ?? "-1", 10);
313
+ o >= 0 && c >= 0 && g(o, c, this.ranges) && (i.classList.add("selected"), i.classList.remove("cell-focus"), n && (o === n.startRow && i.classList.add("top"), o === n.endRow && i.classList.add("bottom"), c === n.startCol && i.classList.add("first"), c === n.endCol && i.classList.add("last")));
294
314
  });
295
315
  }
296
- t === "cell" && this.selectedCell && e.querySelectorAll(".cell-focus").forEach((o) => o.classList.remove("cell-focus"));
316
+ t === "cell" && this.selectedCell && e.querySelectorAll(".cell-focus").forEach((n) => n.classList.remove("cell-focus"));
297
317
  }
298
318
  afterRender() {
299
319
  const e = this.shadowRoot;
@@ -408,18 +428,18 @@ class v extends f {
408
428
  }
409
429
  function p(s, e, t, l) {
410
430
  const r = new Set(s.selected);
411
- let o = s.anchor;
431
+ let n = s.anchor;
412
432
  if (t === "single")
413
- r.clear(), r.add(e), o = e;
433
+ r.clear(), r.add(e), n = e;
414
434
  else if (t === "multiple") {
415
435
  const a = l.ctrlKey || l.metaKey;
416
436
  if (l.shiftKey && s.anchor !== null) {
417
- const i = Math.min(s.anchor, e), n = Math.max(s.anchor, e);
418
- for (let c = i; c <= n; c++)
437
+ const i = Math.min(s.anchor, e), o = Math.max(s.anchor, e);
438
+ for (let c = i; c <= o; c++)
419
439
  r.add(c);
420
- } else a ? (r.has(e) ? r.delete(e) : r.add(e), o = e) : (r.clear(), r.add(e), o = e);
440
+ } else a ? (r.has(e) ? r.delete(e) : r.add(e), n = e) : (r.clear(), r.add(e), n = e);
421
441
  }
422
- return { selected: r, lastSelected: e, anchor: o };
442
+ return { selected: r, lastSelected: e, anchor: n };
423
443
  }
424
444
  function y(s) {
425
445
  const e = /* @__PURE__ */ new Set();