@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.
Files changed (63) hide show
  1. package/all.d.ts +50 -6
  2. package/all.js +101 -98
  3. package/all.js.map +1 -1
  4. package/index.d.ts +54 -0
  5. package/index.js +793 -692
  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 +56 -33
  32. package/lib/plugins/reorder/index.js.map +1 -1
  33. package/lib/plugins/selection/index.js +138 -100
  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 +4 -1
  44. package/themes/dg-theme-contrast.css +43 -43
  45. package/themes/dg-theme-large.css +54 -54
  46. package/themes/dg-theme-standard.css +19 -19
  47. package/themes/dg-theme-vibrant.css +16 -16
  48. package/umd/grid.all.umd.js +24 -24
  49. package/umd/grid.all.umd.js.map +1 -1
  50. package/umd/grid.umd.js +14 -14
  51. package/umd/grid.umd.js.map +1 -1
  52. package/umd/plugins/filtering.umd.js +3 -3
  53. package/umd/plugins/filtering.umd.js.map +1 -1
  54. package/umd/plugins/master-detail.umd.js +2 -2
  55. package/umd/plugins/master-detail.umd.js.map +1 -1
  56. package/umd/plugins/multi-sort.umd.js.map +1 -1
  57. package/umd/plugins/reorder.umd.js +1 -1
  58. package/umd/plugins/reorder.umd.js.map +1 -1
  59. package/umd/plugins/selection.umd.js +2 -2
  60. package/umd/plugins/selection.umd.js.map +1 -1
  61. package/umd/plugins/tree.umd.js +2 -2
  62. package/umd/plugins/tree.umd.js.map +1 -1
  63. package/umd/plugins/visibility.umd.js.map +1 -1
@@ -1,4 +1,4 @@
1
- class S {
1
+ class b {
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 S {
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
  */
@@ -110,17 +130,17 @@ function I(h, e, t, n) {
110
130
  if (h == null) return "";
111
131
  if (h instanceof Date) return h.toISOString();
112
132
  if (typeof h == "object") return JSON.stringify(h);
113
- const i = String(h), l = n.delimiter ?? " ", o = n.newline ?? `
133
+ const i = String(h), l = n.delimiter ?? " ", r = n.newline ?? `
114
134
  `;
115
- return n.quoteStrings || i.includes(l) || i.includes(o) || i.includes('"') ? `"${i.replace(/"/g, '""')}"` : i;
135
+ return n.quoteStrings || i.includes(l) || i.includes(r) || i.includes('"') ? `"${i.replace(/"/g, '""')}"` : i;
116
136
  }
117
137
  function p(h) {
118
- const { rows: e, columns: t, selectedIndices: n, config: i } = h, l = i.delimiter ?? " ", o = i.newline ?? `
119
- `, r = t.filter((s) => !s.hidden && !s.field.startsWith("__")), c = [];
138
+ const { rows: e, columns: t, selectedIndices: n, config: i } = h, l = i.delimiter ?? " ", r = i.newline ?? `
139
+ `, o = t.filter((s) => !s.hidden && !s.field.startsWith("__")), c = [];
120
140
  if (i.includeHeaders) {
121
- const s = r.map((d) => {
141
+ const s = o.map((d) => {
122
142
  const f = d.header || d.field;
123
- return f.includes(l) || f.includes(o) || f.includes('"') ? `"${f.replace(/"/g, '""')}"` : f;
143
+ return f.includes(l) || f.includes(r) || f.includes('"') ? `"${f.replace(/"/g, '""')}"` : f;
124
144
  });
125
145
  c.push(s.join(l));
126
146
  }
@@ -128,12 +148,12 @@ function p(h) {
128
148
  for (const s of u) {
129
149
  const d = e[s];
130
150
  if (!d) continue;
131
- const f = r.map(
151
+ const f = o.map(
132
152
  (g) => I(d[g.field], g.field, d, i)
133
153
  );
134
154
  c.push(f.join(l));
135
155
  }
136
- return c.join(o);
156
+ return c.join(r);
137
157
  }
138
158
  async function C(h) {
139
159
  try {
@@ -150,12 +170,12 @@ function y(h, e) {
150
170
  `, i = h.replace(/\r\n/g, `
151
171
  `).replace(/\r/g, `
152
172
  `), l = [];
153
- let o = [], r = "", c = !1;
173
+ let r = [], o = "", c = !1;
154
174
  for (let a = 0; a < i.length; a++) {
155
175
  const u = i[a];
156
- u === '"' && !c ? c = !0 : u === '"' && c ? i[a + 1] === '"' ? (r += '"', a++) : c = !1 : u === t && !c ? (o.push(r), r = "") : u === n && !c ? (o.push(r), r = "", (o.length > 1 || o.some((s) => s.trim() !== "")) && l.push(o), o = []) : r += u;
176
+ u === '"' && !c ? c = !0 : u === '"' && c ? i[a + 1] === '"' ? (o += '"', a++) : c = !1 : u === t && !c ? (r.push(o), o = "") : u === n && !c ? (r.push(o), o = "", (r.length > 1 || r.some((s) => s.trim() !== "")) && l.push(r), r = []) : o += u;
157
177
  }
158
- return o.push(r), (o.length > 1 || o.some((a) => a.trim() !== "")) && l.push(o), l;
178
+ return r.push(o), (r.length > 1 || r.some((a) => a.trim() !== "")) && l.push(r), l;
159
179
  }
160
180
  async function x() {
161
181
  try {
@@ -164,7 +184,7 @@ async function x() {
164
184
  return "";
165
185
  }
166
186
  }
167
- class P extends S {
187
+ class P extends b {
168
188
  name = "clipboard";
169
189
  version = "1.0.0";
170
190
  get defaultConfig() {
@@ -195,7 +215,7 @@ class P extends S {
195
215
  * Handle copy operation
196
216
  */
197
217
  #t(e) {
198
- const t = this.#e(), n = t?.getSelectedRows() ?? [], i = n.length > 0, l = t?.getRanges() ?? [], o = l.length > 0, r = t?.getSelectedCell() != null;
218
+ const t = this.#e(), n = t?.getSelectedRows() ?? [], i = n.length > 0, l = t?.getRanges() ?? [], r = l.length > 0, o = t?.getSelectedCell() != null;
199
219
  let c, a, u;
200
220
  if (i && t)
201
221
  c = p({
@@ -204,7 +224,7 @@ class P extends S {
204
224
  selectedIndices: n,
205
225
  config: this.config
206
226
  }), a = n.length, u = this.columns.filter((s) => !s.hidden && !s.field.startsWith("__")).length;
207
- else if (o && t) {
227
+ else if (r && t) {
208
228
  const s = l[l.length - 1], d = this.#i({
209
229
  startRow: s.from.row,
210
230
  startCol: s.from.col,
@@ -212,12 +232,12 @@ class P extends S {
212
232
  endCol: s.to.col
213
233
  });
214
234
  c = d.text, a = d.rowCount, u = d.columnCount;
215
- } else if (r && t) {
235
+ } else if (o && t) {
216
236
  const s = t.getSelectedCell(), d = this.#s(s.row, s.col);
217
237
  if (!d) return;
218
238
  c = d.text, a = 1, u = 1;
219
239
  } else {
220
- const s = this.#o(e);
240
+ const s = this.#r(e);
221
241
  if (!s) return;
222
242
  c = s.text, a = 1, u = 1;
223
243
  }
@@ -257,37 +277,37 @@ class P extends S {
257
277
  if (!n) return null;
258
278
  const i = this.columns[t];
259
279
  if (!i) return null;
260
- const l = n[i.field], o = i.header || i.field;
261
- let r;
280
+ const l = n[i.field], r = i.header || i.field;
281
+ let o;
262
282
  if (this.config.includeHeaders) {
263
283
  const c = l == null ? "" : String(l);
264
- r = `${o}: ${c}`;
284
+ o = `${r}: ${c}`;
265
285
  } else
266
- r = l == null ? "" : String(l);
267
- return { text: r };
286
+ o = l == null ? "" : String(l);
287
+ return { text: o };
268
288
  }
269
289
  /**
270
290
  * Build text for a rectangular range of cells.
271
291
  */
272
292
  #i(e) {
273
- const { startRow: t, startCol: n, endRow: i, endCol: l } = e, o = Math.min(t, i), r = Math.max(t, i), c = Math.min(n, l), a = Math.max(n, l), u = this.config.delimiter ?? " ", s = this.config.newline ?? `
293
+ const { startRow: t, startCol: n, endRow: i, endCol: l } = e, r = Math.min(t, i), o = Math.max(t, i), c = Math.min(n, l), a = Math.max(n, l), u = this.config.delimiter ?? " ", s = this.config.newline ?? `
274
294
  `, d = [], f = this.columns.slice(c, a + 1);
275
295
  if (this.config.includeHeaders) {
276
296
  const g = f.map((m) => m.header || m.field);
277
297
  d.push(g.join(u));
278
298
  }
279
- for (let g = o; g <= r; g++) {
299
+ for (let g = r; g <= o; g++) {
280
300
  const m = this.rows[g];
281
301
  if (!m) continue;
282
- const R = f.map((b) => {
283
- const w = m[b.field];
302
+ const S = f.map((R) => {
303
+ const w = m[R.field];
284
304
  return w == null ? "" : w instanceof Date ? w.toISOString() : String(w);
285
305
  });
286
- d.push(R.join(u));
306
+ d.push(S.join(u));
287
307
  }
288
308
  return {
289
309
  text: d.join(s),
290
- rowCount: r - o + 1,
310
+ rowCount: o - r + 1,
291
311
  columnCount: a - c + 1
292
312
  };
293
313
  }
@@ -295,7 +315,7 @@ class P extends S {
295
315
  * Build text for a single focused cell from DOM.
296
316
  * Used when selection plugin is not available or no rows are selected.
297
317
  */
298
- #o(e) {
318
+ #r(e) {
299
319
  const t = e.closest("[data-field-cache]");
300
320
  if (!t) return null;
301
321
  const n = t.dataset.fieldCache;
@@ -304,16 +324,16 @@ class P extends S {
304
324
  if (!i) return null;
305
325
  const l = parseInt(i, 10);
306
326
  if (isNaN(l)) return null;
307
- const o = this.rows[l];
308
- if (!o) return null;
309
- const r = o[n], a = this.columns.find((s) => s.field === n)?.header || n;
327
+ const r = this.rows[l];
328
+ if (!r) return null;
329
+ const o = r[n], a = this.columns.find((s) => s.field === n)?.header || n;
310
330
  let u;
311
331
  if (this.config.includeHeaders) {
312
- const s = r == null ? "" : String(r);
332
+ const s = o == null ? "" : String(o);
313
333
  u = `${a}: ${s}`;
314
334
  } else
315
- u = r == null ? "" : String(r);
316
- return { text: u, field: n, value: r };
335
+ u = o == null ? "" : String(o);
336
+ return { text: u, field: n, value: o };
317
337
  }
318
338
  // ===== Public API =====
319
339
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../../../libs/grid/src/lib/core/plugin/base-plugin.ts","../../../../../../libs/grid/src/lib/plugins/clipboard/copy.ts","../../../../../../libs/grid/src/lib/plugins/clipboard/paste.ts","../../../../../../libs/grid/src/lib/plugins/clipboard/ClipboardPlugin.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 * Clipboard Copy Logic\n *\n * Pure functions for copying grid data to clipboard.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ClipboardConfig } from './types';\n\n/** Parameters for building clipboard text */\nexport interface CopyParams {\n /** All grid rows */\n rows: unknown[];\n /** Column configurations */\n columns: ColumnConfig[];\n /** Selected row indices */\n selectedIndices: Set<number> | number[];\n /** Clipboard configuration */\n config: ClipboardConfig;\n}\n\n/**\n * Format a cell value for clipboard output.\n *\n * Uses custom processCell if provided, otherwise applies default formatting:\n * - null/undefined → empty string\n * - Date → ISO string\n * - Object → JSON string\n * - Other → String conversion with optional quoting\n *\n * @param value - The cell value to format\n * @param field - The field name\n * @param row - The full row object\n * @param config - Clipboard configuration\n * @returns Formatted string value\n */\nexport function formatCellValue(value: unknown, field: string, row: unknown, config: ClipboardConfig): string {\n if (config.processCell) {\n return config.processCell(value, field, row);\n }\n\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'object') return JSON.stringify(value);\n\n const str = String(value);\n const delimiter = config.delimiter ?? '\\t';\n const newline = config.newline ?? '\\n';\n\n // Quote if contains delimiter, newline, or quotes (or if quoteStrings is enabled)\n if (config.quoteStrings || str.includes(delimiter) || str.includes(newline) || str.includes('\"')) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n\n return str;\n}\n\n/**\n * Build clipboard text from selected rows and columns.\n *\n * @param params - Copy parameters including rows, columns, selection, and config\n * @returns Tab-separated (or custom delimiter) text ready for clipboard\n */\nexport function buildClipboardText(params: CopyParams): string {\n const { rows, columns, selectedIndices, config } = params;\n const delimiter = config.delimiter ?? '\\t';\n const newline = config.newline ?? '\\n';\n\n // Filter to visible columns (not hidden, not internal __ prefixed)\n const visibleColumns = columns.filter((c) => !c.hidden && !c.field.startsWith('__'));\n\n const lines: string[] = [];\n\n // Add header row if configured\n if (config.includeHeaders) {\n const headerCells = visibleColumns.map((c) => {\n const header = c.header || c.field;\n // Quote headers if they contain special characters\n if (header.includes(delimiter) || header.includes(newline) || header.includes('\"')) {\n return `\"${header.replace(/\"/g, '\"\"')}\"`;\n }\n return header;\n });\n lines.push(headerCells.join(delimiter));\n }\n\n // Convert indices to sorted array\n const indices = selectedIndices instanceof Set ? [...selectedIndices] : selectedIndices;\n const sortedIndices = [...indices].sort((a, b) => a - b);\n\n // Build data rows\n for (const idx of sortedIndices) {\n const row = rows[idx];\n if (!row) continue;\n\n const cells = visibleColumns.map((col) =>\n formatCellValue((row as Record<string, unknown>)[col.field], col.field, row, config)\n );\n lines.push(cells.join(delimiter));\n }\n\n return lines.join(newline);\n}\n\n/**\n * Copy text to the system clipboard.\n *\n * Uses the modern Clipboard API when available, with fallback\n * to execCommand for older browsers.\n *\n * @param text - The text to copy\n * @returns Promise resolving to true if successful, false otherwise\n */\nexport async function copyToClipboard(text: string): Promise<boolean> {\n try {\n await navigator.clipboard.writeText(text);\n return true;\n } catch {\n // Fallback for older browsers or when Clipboard API is not available\n const textarea = document.createElement('textarea');\n textarea.value = text;\n textarea.style.position = 'fixed';\n textarea.style.opacity = '0';\n textarea.style.pointerEvents = 'none';\n document.body.appendChild(textarea);\n textarea.select();\n const success = document.execCommand('copy');\n document.body.removeChild(textarea);\n return success;\n }\n}\n","/**\n * Clipboard Paste Logic\n *\n * Pure functions for reading and parsing clipboard data.\n */\n\nimport type { ClipboardConfig } from './types';\n\n/**\n * Parse clipboard text into a 2D array of cell values.\n *\n * Handles:\n * - Quoted values (with escaped double quotes \"\")\n * - Multi-line quoted values (newlines within quotes are preserved)\n * - Custom delimiters and newlines\n * - Empty lines (filtered out only if they contain no data)\n *\n * @param text - Raw clipboard text\n * @param config - Clipboard configuration\n * @returns 2D array where each sub-array is a row of cell values\n */\nexport function parseClipboardText(text: string, config: ClipboardConfig): string[][] {\n const delimiter = config.delimiter ?? '\\t';\n const newline = config.newline ?? '\\n';\n\n // Handle Windows CRLF line endings\n const normalizedText = text.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n\n // Parse the entire text handling quoted fields that may span multiple lines\n const rows: string[][] = [];\n let currentRow: string[] = [];\n let currentCell = '';\n let inQuotes = false;\n\n for (let i = 0; i < normalizedText.length; i++) {\n const char = normalizedText[i];\n\n if (char === '\"' && !inQuotes) {\n // Start of quoted field\n inQuotes = true;\n } else if (char === '\"' && inQuotes) {\n // Check for escaped quote (\"\")\n if (normalizedText[i + 1] === '\"') {\n currentCell += '\"';\n i++; // Skip the second quote\n } else {\n // End of quoted field\n inQuotes = false;\n }\n } else if (char === delimiter && !inQuotes) {\n // Field separator\n currentRow.push(currentCell);\n currentCell = '';\n } else if (char === newline && !inQuotes) {\n // Row separator (only if not inside quotes)\n currentRow.push(currentCell);\n currentCell = '';\n // Only add non-empty rows (at least one non-empty cell or multiple cells)\n if (currentRow.length > 1 || currentRow.some((c) => c.trim() !== '')) {\n rows.push(currentRow);\n }\n currentRow = [];\n } else {\n currentCell += char;\n }\n }\n\n // Handle the last cell and row\n currentRow.push(currentCell);\n if (currentRow.length > 1 || currentRow.some((c) => c.trim() !== '')) {\n rows.push(currentRow);\n }\n\n return rows;\n}\n\n/**\n * Read text from the system clipboard.\n *\n * Uses the modern Clipboard API. Returns empty string if\n * the API is not available or permission is denied.\n *\n * @returns Promise resolving to clipboard text or empty string\n */\nexport async function readFromClipboard(): Promise<string> {\n try {\n return await navigator.clipboard.readText();\n } catch {\n // Permission denied or API not available\n return '';\n }\n}\n","/**\n * Clipboard Plugin (Class-based)\n *\n * Provides copy/paste functionality for tbw-grid.\n * Supports Ctrl+C/Cmd+C for copying and Ctrl+V/Cmd+V for pasting.\n *\n * **With Selection plugin:** Copy selected rows as a range\n * - includeHeaders: true → adds header row at top\n * - includeHeaders: false → data rows only\n *\n * **Without Selection plugin:** Copy focused cell only\n * - includeHeaders: true → \"Header: value\" format\n * - includeHeaders: false → value only\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport { buildClipboardText, copyToClipboard } from './copy';\nimport { parseClipboardText, readFromClipboard } from './paste';\nimport type { ClipboardConfig, CopyDetail, PasteDetail } from './types';\n\n/**\n * Clipboard Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new ClipboardPlugin({ includeHeaders: true })\n * ```\n */\nexport class ClipboardPlugin extends BaseGridPlugin<ClipboardConfig> {\n readonly name = 'clipboard';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<ClipboardConfig> {\n return {\n enabled: true,\n includeHeaders: false,\n delimiter: '\\t',\n newline: '\\n',\n quoteStrings: false,\n };\n }\n\n // ===== Internal State =====\n /** The last copied text (for reference/debugging) */\n private lastCopied: { text: string; timestamp: number } | null = null;\n\n // ===== Lifecycle =====\n\n override detach(): void {\n this.lastCopied = null;\n }\n\n // ===== Event Handlers =====\n\n override onKeyDown(event: KeyboardEvent): boolean {\n if (this.config.enabled === false) return false;\n\n const isCopy = (event.ctrlKey || event.metaKey) && event.key === 'c';\n const isPaste = (event.ctrlKey || event.metaKey) && event.key === 'v';\n\n if (isCopy) {\n this.#handleCopy(event.target as HTMLElement);\n return true; // Prevent default browser behavior\n }\n\n if (isPaste) {\n this.#handlePaste();\n return true; // Prevent default browser behavior\n }\n\n return false;\n }\n\n // ===== Private Methods =====\n\n /**\n * Handle copy operation\n */\n #handleCopy(target: HTMLElement): void {\n // Try to get selection from selection plugin (if available)\n // Use dynamic import to avoid circular dependency issues\n const selectionPlugin = this.#getSelectionPlugin();\n\n // Check for different selection types\n const selectedRows = selectionPlugin?.getSelectedRows() ?? [];\n const hasRowSelection = selectedRows.length > 0;\n const ranges = selectionPlugin?.getRanges() ?? [];\n const hasRangeSelection = ranges.length > 0;\n const hasCellSelection = selectionPlugin?.getSelectedCell() != null;\n\n let text: string;\n let rowCount: number;\n let columnCount: number;\n\n if (hasRowSelection && selectionPlugin) {\n // Row selection mode - copy full rows\n text = buildClipboardText({\n rows: this.rows as unknown[],\n columns: [...this.columns],\n selectedIndices: selectedRows,\n config: this.config,\n });\n rowCount = selectedRows.length;\n columnCount = this.columns.filter((c) => !c.hidden && !c.field.startsWith('__')).length;\n } else if (hasRangeSelection && selectionPlugin) {\n // Range selection mode - copy rectangular range (use last range as active)\n const range = ranges[ranges.length - 1];\n const result = this.#buildRangeText({\n startRow: range.from.row,\n startCol: range.from.col,\n endRow: range.to.row,\n endCol: range.to.col,\n });\n text = result.text;\n rowCount = result.rowCount;\n columnCount = result.columnCount;\n } else if (hasCellSelection && selectionPlugin) {\n // Cell selection mode - copy single cell\n const cell = selectionPlugin.getSelectedCell()!;\n const result = this.#buildCellText(cell.row, cell.col);\n if (!result) return;\n text = result.text;\n rowCount = 1;\n columnCount = 1;\n } else {\n // Fallback: try to find focused cell from DOM\n const result = this.#buildSingleCellText(target);\n if (!result) return;\n text = result.text;\n rowCount = 1;\n columnCount = 1;\n }\n\n copyToClipboard(text).then(() => {\n this.lastCopied = { text, timestamp: Date.now() };\n this.emit<CopyDetail>('copy', { text, rowCount, columnCount });\n });\n }\n\n /**\n * Handle paste operation\n */\n #handlePaste(): void {\n readFromClipboard().then((text) => {\n if (!text) return;\n const parsed = parseClipboardText(text, this.config);\n this.emit<PasteDetail>('paste', { rows: parsed, text });\n });\n }\n\n /**\n * Get the selection plugin instance if available.\n */\n #getSelectionPlugin(): SelectionPluginInterface | undefined {\n // Dynamically get the SelectionPlugin class to avoid import order issues\n try {\n // Use getPlugin with the class - this requires the class to be imported\n // For now, we'll use a duck-typing approach via the grid's plugin registry\n const grid = this.grid as any;\n if (grid?._plugins) {\n for (const plugin of grid._plugins) {\n if (plugin.name === 'selection') {\n return plugin as SelectionPluginInterface;\n }\n }\n }\n } catch {\n // Selection plugin not available\n }\n return undefined;\n }\n\n /**\n * Build text for a single cell by row/col index.\n */\n #buildCellText(rowIndex: number, colIndex: number): { text: string } | null {\n const rowData = this.rows[rowIndex] as Record<string, unknown> | undefined;\n if (!rowData) return null;\n\n const column = this.columns[colIndex];\n if (!column) return null;\n\n const value = rowData[column.field];\n const header = column.header || column.field;\n\n let text: string;\n if (this.config.includeHeaders) {\n const formattedValue = value == null ? '' : String(value);\n text = `${header}: ${formattedValue}`;\n } else {\n text = value == null ? '' : String(value);\n }\n\n return { text };\n }\n\n /**\n * Build text for a rectangular range of cells.\n */\n #buildRangeText(range: { startRow: number; startCol: number; endRow: number; endCol: number }): {\n text: string;\n rowCount: number;\n columnCount: number;\n } {\n const { startRow, startCol, endRow, endCol } = range;\n const minRow = Math.min(startRow, endRow);\n const maxRow = Math.max(startRow, endRow);\n const minCol = Math.min(startCol, endCol);\n const maxCol = Math.max(startCol, endCol);\n\n const delimiter = this.config.delimiter ?? '\\t';\n const newline = this.config.newline ?? '\\n';\n const lines: string[] = [];\n\n // Get columns in the range\n const rangeColumns = this.columns.slice(minCol, maxCol + 1);\n\n // Add header row if configured\n if (this.config.includeHeaders) {\n const headerCells = rangeColumns.map((c) => c.header || c.field);\n lines.push(headerCells.join(delimiter));\n }\n\n // Add data rows\n for (let r = minRow; r <= maxRow; r++) {\n const rowData = this.rows[r] as Record<string, unknown> | undefined;\n if (!rowData) continue;\n\n const cells = rangeColumns.map((col) => {\n const value = rowData[col.field];\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n return String(value);\n });\n lines.push(cells.join(delimiter));\n }\n\n return {\n text: lines.join(newline),\n rowCount: maxRow - minRow + 1,\n columnCount: maxCol - minCol + 1,\n };\n }\n\n /**\n * Build text for a single focused cell from DOM.\n * Used when selection plugin is not available or no rows are selected.\n */\n #buildSingleCellText(target: HTMLElement): { text: string; field: string; value: unknown } | null {\n // Find the cell element - cells use data-field-cache for the field name\n const cell = target.closest('[data-field-cache]') as HTMLElement | null;\n if (!cell) return null;\n\n const field = cell.dataset.fieldCache;\n if (!field) return null;\n\n // Get row index from data-row attribute on the cell\n const rowIndexStr = cell.dataset.row;\n if (!rowIndexStr) return null;\n\n const rowIndex = parseInt(rowIndexStr, 10);\n if (isNaN(rowIndex)) return null;\n\n const rowData = this.rows[rowIndex] as Record<string, unknown> | undefined;\n if (!rowData) return null;\n\n const value = rowData[field];\n const column = this.columns.find((c) => c.field === field);\n const header = column?.header || field;\n\n // Format the text based on includeHeaders config\n let text: string;\n if (this.config.includeHeaders) {\n // Format: \"{header}: {value}\"\n const formattedValue = value == null ? '' : String(value);\n text = `${header}: ${formattedValue}`;\n } else {\n // Just the value\n text = value == null ? '' : String(value);\n }\n\n return { text, field, value };\n }\n\n // ===== Public API =====\n\n /**\n * Copy currently selected rows to clipboard.\n * @returns The copied text\n */\n async copy(): Promise<string> {\n const selectionPlugin = this.#getSelectionPlugin();\n const indices = selectionPlugin?.getSelectedRows() ?? [];\n\n const text = buildClipboardText({\n rows: this.rows as unknown[],\n columns: [...this.columns],\n selectedIndices: indices,\n config: this.config,\n });\n\n await copyToClipboard(text);\n this.lastCopied = { text, timestamp: Date.now() };\n return text;\n }\n\n /**\n * Copy specific rows by index to clipboard.\n * @param indices - Array of row indices to copy\n * @returns The copied text\n */\n async copyRows(indices: number[]): Promise<string> {\n const text = buildClipboardText({\n rows: this.rows as unknown[],\n columns: [...this.columns],\n selectedIndices: indices,\n config: this.config,\n });\n\n await copyToClipboard(text);\n this.lastCopied = { text, timestamp: Date.now() };\n return text;\n }\n\n /**\n * Read and parse clipboard content.\n * @returns Parsed 2D array of cell values, or null if clipboard is empty\n */\n async paste(): Promise<string[][] | null> {\n const text = await readFromClipboard();\n if (!text) return null;\n return parseClipboardText(text, this.config);\n }\n\n /**\n * Get the last copied text and timestamp.\n * @returns The last copied info or null\n */\n getLastCopied(): { text: string; timestamp: number } | null {\n return this.lastCopied;\n }\n}\n\n// ===== Internal Types =====\n\n/**\n * Interface for SelectionPlugin methods we need.\n * This avoids circular imports while providing type safety.\n */\ninterface SelectionPluginInterface {\n name: string;\n /** Get selected row indices (row mode) */\n getSelectedRows(): number[];\n /** Get all selected cell ranges (range mode) */\n getRanges(): Array<{ from: { row: number; col: number }; to: { row: number; col: number } }>;\n /** Get selected cell (cell mode) */\n getSelectedCell(): { row: number; col: number } | null;\n}\n\n// Re-export types\nexport type { ClipboardConfig, CopyDetail, PasteDetail } from './types';\n"],"names":["BaseGridPlugin","config","grid","PluginClass","eventName","detail","message","formatCellValue","value","field","row","str","delimiter","newline","buildClipboardText","params","rows","columns","selectedIndices","visibleColumns","c","lines","headerCells","header","sortedIndices","a","b","idx","cells","col","copyToClipboard","text","textarea","success","parseClipboardText","normalizedText","currentRow","currentCell","inQuotes","i","char","readFromClipboard","ClipboardPlugin","event","isCopy","isPaste","#handleCopy","#handlePaste","target","selectionPlugin","#getSelectionPlugin","selectedRows","hasRowSelection","ranges","hasRangeSelection","hasCellSelection","rowCount","columnCount","range","result","#buildRangeText","cell","#buildCellText","#buildSingleCellText","parsed","plugin","rowIndex","colIndex","rowData","column","formattedValue","startRow","startCol","endRow","endCol","minRow","maxRow","minCol","maxCol","rangeColumns","r","rowIndexStr","indices"],"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/clipboard/copy.ts","../../../../../../libs/grid/src/lib/plugins/clipboard/paste.ts","../../../../../../libs/grid/src/lib/plugins/clipboard/ClipboardPlugin.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 * Clipboard Copy Logic\n *\n * Pure functions for copying grid data to clipboard.\n */\n\nimport type { ColumnConfig } from '../../core/types';\nimport type { ClipboardConfig } from './types';\n\n/** Parameters for building clipboard text */\nexport interface CopyParams {\n /** All grid rows */\n rows: unknown[];\n /** Column configurations */\n columns: ColumnConfig[];\n /** Selected row indices */\n selectedIndices: Set<number> | number[];\n /** Clipboard configuration */\n config: ClipboardConfig;\n}\n\n/**\n * Format a cell value for clipboard output.\n *\n * Uses custom processCell if provided, otherwise applies default formatting:\n * - null/undefined → empty string\n * - Date → ISO string\n * - Object → JSON string\n * - Other → String conversion with optional quoting\n *\n * @param value - The cell value to format\n * @param field - The field name\n * @param row - The full row object\n * @param config - Clipboard configuration\n * @returns Formatted string value\n */\nexport function formatCellValue(value: unknown, field: string, row: unknown, config: ClipboardConfig): string {\n if (config.processCell) {\n return config.processCell(value, field, row);\n }\n\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n if (typeof value === 'object') return JSON.stringify(value);\n\n const str = String(value);\n const delimiter = config.delimiter ?? '\\t';\n const newline = config.newline ?? '\\n';\n\n // Quote if contains delimiter, newline, or quotes (or if quoteStrings is enabled)\n if (config.quoteStrings || str.includes(delimiter) || str.includes(newline) || str.includes('\"')) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n\n return str;\n}\n\n/**\n * Build clipboard text from selected rows and columns.\n *\n * @param params - Copy parameters including rows, columns, selection, and config\n * @returns Tab-separated (or custom delimiter) text ready for clipboard\n */\nexport function buildClipboardText(params: CopyParams): string {\n const { rows, columns, selectedIndices, config } = params;\n const delimiter = config.delimiter ?? '\\t';\n const newline = config.newline ?? '\\n';\n\n // Filter to visible columns (not hidden, not internal __ prefixed)\n const visibleColumns = columns.filter((c) => !c.hidden && !c.field.startsWith('__'));\n\n const lines: string[] = [];\n\n // Add header row if configured\n if (config.includeHeaders) {\n const headerCells = visibleColumns.map((c) => {\n const header = c.header || c.field;\n // Quote headers if they contain special characters\n if (header.includes(delimiter) || header.includes(newline) || header.includes('\"')) {\n return `\"${header.replace(/\"/g, '\"\"')}\"`;\n }\n return header;\n });\n lines.push(headerCells.join(delimiter));\n }\n\n // Convert indices to sorted array\n const indices = selectedIndices instanceof Set ? [...selectedIndices] : selectedIndices;\n const sortedIndices = [...indices].sort((a, b) => a - b);\n\n // Build data rows\n for (const idx of sortedIndices) {\n const row = rows[idx];\n if (!row) continue;\n\n const cells = visibleColumns.map((col) =>\n formatCellValue((row as Record<string, unknown>)[col.field], col.field, row, config)\n );\n lines.push(cells.join(delimiter));\n }\n\n return lines.join(newline);\n}\n\n/**\n * Copy text to the system clipboard.\n *\n * Uses the modern Clipboard API when available, with fallback\n * to execCommand for older browsers.\n *\n * @param text - The text to copy\n * @returns Promise resolving to true if successful, false otherwise\n */\nexport async function copyToClipboard(text: string): Promise<boolean> {\n try {\n await navigator.clipboard.writeText(text);\n return true;\n } catch {\n // Fallback for older browsers or when Clipboard API is not available\n const textarea = document.createElement('textarea');\n textarea.value = text;\n textarea.style.position = 'fixed';\n textarea.style.opacity = '0';\n textarea.style.pointerEvents = 'none';\n document.body.appendChild(textarea);\n textarea.select();\n const success = document.execCommand('copy');\n document.body.removeChild(textarea);\n return success;\n }\n}\n","/**\n * Clipboard Paste Logic\n *\n * Pure functions for reading and parsing clipboard data.\n */\n\nimport type { ClipboardConfig } from './types';\n\n/**\n * Parse clipboard text into a 2D array of cell values.\n *\n * Handles:\n * - Quoted values (with escaped double quotes \"\")\n * - Multi-line quoted values (newlines within quotes are preserved)\n * - Custom delimiters and newlines\n * - Empty lines (filtered out only if they contain no data)\n *\n * @param text - Raw clipboard text\n * @param config - Clipboard configuration\n * @returns 2D array where each sub-array is a row of cell values\n */\nexport function parseClipboardText(text: string, config: ClipboardConfig): string[][] {\n const delimiter = config.delimiter ?? '\\t';\n const newline = config.newline ?? '\\n';\n\n // Handle Windows CRLF line endings\n const normalizedText = text.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n\n // Parse the entire text handling quoted fields that may span multiple lines\n const rows: string[][] = [];\n let currentRow: string[] = [];\n let currentCell = '';\n let inQuotes = false;\n\n for (let i = 0; i < normalizedText.length; i++) {\n const char = normalizedText[i];\n\n if (char === '\"' && !inQuotes) {\n // Start of quoted field\n inQuotes = true;\n } else if (char === '\"' && inQuotes) {\n // Check for escaped quote (\"\")\n if (normalizedText[i + 1] === '\"') {\n currentCell += '\"';\n i++; // Skip the second quote\n } else {\n // End of quoted field\n inQuotes = false;\n }\n } else if (char === delimiter && !inQuotes) {\n // Field separator\n currentRow.push(currentCell);\n currentCell = '';\n } else if (char === newline && !inQuotes) {\n // Row separator (only if not inside quotes)\n currentRow.push(currentCell);\n currentCell = '';\n // Only add non-empty rows (at least one non-empty cell or multiple cells)\n if (currentRow.length > 1 || currentRow.some((c) => c.trim() !== '')) {\n rows.push(currentRow);\n }\n currentRow = [];\n } else {\n currentCell += char;\n }\n }\n\n // Handle the last cell and row\n currentRow.push(currentCell);\n if (currentRow.length > 1 || currentRow.some((c) => c.trim() !== '')) {\n rows.push(currentRow);\n }\n\n return rows;\n}\n\n/**\n * Read text from the system clipboard.\n *\n * Uses the modern Clipboard API. Returns empty string if\n * the API is not available or permission is denied.\n *\n * @returns Promise resolving to clipboard text or empty string\n */\nexport async function readFromClipboard(): Promise<string> {\n try {\n return await navigator.clipboard.readText();\n } catch {\n // Permission denied or API not available\n return '';\n }\n}\n","/**\n * Clipboard Plugin (Class-based)\n *\n * Provides copy/paste functionality for tbw-grid.\n * Supports Ctrl+C/Cmd+C for copying and Ctrl+V/Cmd+V for pasting.\n *\n * **With Selection plugin:** Copy selected rows as a range\n * - includeHeaders: true → adds header row at top\n * - includeHeaders: false → data rows only\n *\n * **Without Selection plugin:** Copy focused cell only\n * - includeHeaders: true → \"Header: value\" format\n * - includeHeaders: false → value only\n */\n\nimport { BaseGridPlugin } from '../../core/plugin/base-plugin';\nimport { buildClipboardText, copyToClipboard } from './copy';\nimport { parseClipboardText, readFromClipboard } from './paste';\nimport type { ClipboardConfig, CopyDetail, PasteDetail } from './types';\n\n/**\n * Clipboard Plugin for tbw-grid\n *\n * @example\n * ```ts\n * new ClipboardPlugin({ includeHeaders: true })\n * ```\n */\nexport class ClipboardPlugin extends BaseGridPlugin<ClipboardConfig> {\n readonly name = 'clipboard';\n override readonly version = '1.0.0';\n\n protected override get defaultConfig(): Partial<ClipboardConfig> {\n return {\n enabled: true,\n includeHeaders: false,\n delimiter: '\\t',\n newline: '\\n',\n quoteStrings: false,\n };\n }\n\n // ===== Internal State =====\n /** The last copied text (for reference/debugging) */\n private lastCopied: { text: string; timestamp: number } | null = null;\n\n // ===== Lifecycle =====\n\n override detach(): void {\n this.lastCopied = null;\n }\n\n // ===== Event Handlers =====\n\n override onKeyDown(event: KeyboardEvent): boolean {\n if (this.config.enabled === false) return false;\n\n const isCopy = (event.ctrlKey || event.metaKey) && event.key === 'c';\n const isPaste = (event.ctrlKey || event.metaKey) && event.key === 'v';\n\n if (isCopy) {\n this.#handleCopy(event.target as HTMLElement);\n return true; // Prevent default browser behavior\n }\n\n if (isPaste) {\n this.#handlePaste();\n return true; // Prevent default browser behavior\n }\n\n return false;\n }\n\n // ===== Private Methods =====\n\n /**\n * Handle copy operation\n */\n #handleCopy(target: HTMLElement): void {\n // Try to get selection from selection plugin (if available)\n // Use dynamic import to avoid circular dependency issues\n const selectionPlugin = this.#getSelectionPlugin();\n\n // Check for different selection types\n const selectedRows = selectionPlugin?.getSelectedRows() ?? [];\n const hasRowSelection = selectedRows.length > 0;\n const ranges = selectionPlugin?.getRanges() ?? [];\n const hasRangeSelection = ranges.length > 0;\n const hasCellSelection = selectionPlugin?.getSelectedCell() != null;\n\n let text: string;\n let rowCount: number;\n let columnCount: number;\n\n if (hasRowSelection && selectionPlugin) {\n // Row selection mode - copy full rows\n text = buildClipboardText({\n rows: this.rows as unknown[],\n columns: [...this.columns],\n selectedIndices: selectedRows,\n config: this.config,\n });\n rowCount = selectedRows.length;\n columnCount = this.columns.filter((c) => !c.hidden && !c.field.startsWith('__')).length;\n } else if (hasRangeSelection && selectionPlugin) {\n // Range selection mode - copy rectangular range (use last range as active)\n const range = ranges[ranges.length - 1];\n const result = this.#buildRangeText({\n startRow: range.from.row,\n startCol: range.from.col,\n endRow: range.to.row,\n endCol: range.to.col,\n });\n text = result.text;\n rowCount = result.rowCount;\n columnCount = result.columnCount;\n } else if (hasCellSelection && selectionPlugin) {\n // Cell selection mode - copy single cell\n const cell = selectionPlugin.getSelectedCell()!;\n const result = this.#buildCellText(cell.row, cell.col);\n if (!result) return;\n text = result.text;\n rowCount = 1;\n columnCount = 1;\n } else {\n // Fallback: try to find focused cell from DOM\n const result = this.#buildSingleCellText(target);\n if (!result) return;\n text = result.text;\n rowCount = 1;\n columnCount = 1;\n }\n\n copyToClipboard(text).then(() => {\n this.lastCopied = { text, timestamp: Date.now() };\n this.emit<CopyDetail>('copy', { text, rowCount, columnCount });\n });\n }\n\n /**\n * Handle paste operation\n */\n #handlePaste(): void {\n readFromClipboard().then((text) => {\n if (!text) return;\n const parsed = parseClipboardText(text, this.config);\n this.emit<PasteDetail>('paste', { rows: parsed, text });\n });\n }\n\n /**\n * Get the selection plugin instance if available.\n */\n #getSelectionPlugin(): SelectionPluginInterface | undefined {\n // Dynamically get the SelectionPlugin class to avoid import order issues\n try {\n // Use getPlugin with the class - this requires the class to be imported\n // For now, we'll use a duck-typing approach via the grid's plugin registry\n const grid = this.grid as any;\n if (grid?._plugins) {\n for (const plugin of grid._plugins) {\n if (plugin.name === 'selection') {\n return plugin as SelectionPluginInterface;\n }\n }\n }\n } catch {\n // Selection plugin not available\n }\n return undefined;\n }\n\n /**\n * Build text for a single cell by row/col index.\n */\n #buildCellText(rowIndex: number, colIndex: number): { text: string } | null {\n const rowData = this.rows[rowIndex] as Record<string, unknown> | undefined;\n if (!rowData) return null;\n\n const column = this.columns[colIndex];\n if (!column) return null;\n\n const value = rowData[column.field];\n const header = column.header || column.field;\n\n let text: string;\n if (this.config.includeHeaders) {\n const formattedValue = value == null ? '' : String(value);\n text = `${header}: ${formattedValue}`;\n } else {\n text = value == null ? '' : String(value);\n }\n\n return { text };\n }\n\n /**\n * Build text for a rectangular range of cells.\n */\n #buildRangeText(range: { startRow: number; startCol: number; endRow: number; endCol: number }): {\n text: string;\n rowCount: number;\n columnCount: number;\n } {\n const { startRow, startCol, endRow, endCol } = range;\n const minRow = Math.min(startRow, endRow);\n const maxRow = Math.max(startRow, endRow);\n const minCol = Math.min(startCol, endCol);\n const maxCol = Math.max(startCol, endCol);\n\n const delimiter = this.config.delimiter ?? '\\t';\n const newline = this.config.newline ?? '\\n';\n const lines: string[] = [];\n\n // Get columns in the range\n const rangeColumns = this.columns.slice(minCol, maxCol + 1);\n\n // Add header row if configured\n if (this.config.includeHeaders) {\n const headerCells = rangeColumns.map((c) => c.header || c.field);\n lines.push(headerCells.join(delimiter));\n }\n\n // Add data rows\n for (let r = minRow; r <= maxRow; r++) {\n const rowData = this.rows[r] as Record<string, unknown> | undefined;\n if (!rowData) continue;\n\n const cells = rangeColumns.map((col) => {\n const value = rowData[col.field];\n if (value == null) return '';\n if (value instanceof Date) return value.toISOString();\n return String(value);\n });\n lines.push(cells.join(delimiter));\n }\n\n return {\n text: lines.join(newline),\n rowCount: maxRow - minRow + 1,\n columnCount: maxCol - minCol + 1,\n };\n }\n\n /**\n * Build text for a single focused cell from DOM.\n * Used when selection plugin is not available or no rows are selected.\n */\n #buildSingleCellText(target: HTMLElement): { text: string; field: string; value: unknown } | null {\n // Find the cell element - cells use data-field-cache for the field name\n const cell = target.closest('[data-field-cache]') as HTMLElement | null;\n if (!cell) return null;\n\n const field = cell.dataset.fieldCache;\n if (!field) return null;\n\n // Get row index from data-row attribute on the cell\n const rowIndexStr = cell.dataset.row;\n if (!rowIndexStr) return null;\n\n const rowIndex = parseInt(rowIndexStr, 10);\n if (isNaN(rowIndex)) return null;\n\n const rowData = this.rows[rowIndex] as Record<string, unknown> | undefined;\n if (!rowData) return null;\n\n const value = rowData[field];\n const column = this.columns.find((c) => c.field === field);\n const header = column?.header || field;\n\n // Format the text based on includeHeaders config\n let text: string;\n if (this.config.includeHeaders) {\n // Format: \"{header}: {value}\"\n const formattedValue = value == null ? '' : String(value);\n text = `${header}: ${formattedValue}`;\n } else {\n // Just the value\n text = value == null ? '' : String(value);\n }\n\n return { text, field, value };\n }\n\n // ===== Public API =====\n\n /**\n * Copy currently selected rows to clipboard.\n * @returns The copied text\n */\n async copy(): Promise<string> {\n const selectionPlugin = this.#getSelectionPlugin();\n const indices = selectionPlugin?.getSelectedRows() ?? [];\n\n const text = buildClipboardText({\n rows: this.rows as unknown[],\n columns: [...this.columns],\n selectedIndices: indices,\n config: this.config,\n });\n\n await copyToClipboard(text);\n this.lastCopied = { text, timestamp: Date.now() };\n return text;\n }\n\n /**\n * Copy specific rows by index to clipboard.\n * @param indices - Array of row indices to copy\n * @returns The copied text\n */\n async copyRows(indices: number[]): Promise<string> {\n const text = buildClipboardText({\n rows: this.rows as unknown[],\n columns: [...this.columns],\n selectedIndices: indices,\n config: this.config,\n });\n\n await copyToClipboard(text);\n this.lastCopied = { text, timestamp: Date.now() };\n return text;\n }\n\n /**\n * Read and parse clipboard content.\n * @returns Parsed 2D array of cell values, or null if clipboard is empty\n */\n async paste(): Promise<string[][] | null> {\n const text = await readFromClipboard();\n if (!text) return null;\n return parseClipboardText(text, this.config);\n }\n\n /**\n * Get the last copied text and timestamp.\n * @returns The last copied info or null\n */\n getLastCopied(): { text: string; timestamp: number } | null {\n return this.lastCopied;\n }\n}\n\n// ===== Internal Types =====\n\n/**\n * Interface for SelectionPlugin methods we need.\n * This avoids circular imports while providing type safety.\n */\ninterface SelectionPluginInterface {\n name: string;\n /** Get selected row indices (row mode) */\n getSelectedRows(): number[];\n /** Get all selected cell ranges (range mode) */\n getRanges(): Array<{ from: { row: number; col: number }; to: { row: number; col: number } }>;\n /** Get selected cell (cell mode) */\n getSelectedCell(): { row: number; col: number } | null;\n}\n\n// Re-export types\nexport type { ClipboardConfig, CopyDetail, PasteDetail } from './types';\n"],"names":["BaseGridPlugin","config","grid","PluginClass","eventName","detail","message","formatCellValue","value","field","row","str","delimiter","newline","buildClipboardText","params","rows","columns","selectedIndices","visibleColumns","c","lines","headerCells","header","sortedIndices","a","b","idx","cells","col","copyToClipboard","text","textarea","success","parseClipboardText","normalizedText","currentRow","currentCell","inQuotes","i","char","readFromClipboard","ClipboardPlugin","event","isCopy","isPaste","#handleCopy","#handlePaste","target","selectionPlugin","#getSelectionPlugin","selectedRows","hasRowSelection","ranges","hasRangeSelection","hasCellSelection","rowCount","columnCount","range","result","#buildRangeText","cell","#buildCellText","#buildSingleCellText","parsed","plugin","rowIndex","colIndex","rowData","column","formattedValue","startRow","startCol","endRow","endCol","minRow","maxRow","minCol","maxCol","rangeColumns","r","rowIndexStr","indices"],"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;ACrtBO,SAASC,EAAgBC,GAAgBC,GAAeC,GAAcT,GAAiC;AAC5G,MAAIA,EAAO;AACT,WAAOA,EAAO,YAAYO,GAAOC,GAAOC,CAAG;AAG7C,MAAIF,KAAS,KAAM,QAAO;AAC1B,MAAIA,aAAiB,KAAM,QAAOA,EAAM,YAAA;AACxC,MAAI,OAAOA,KAAU,SAAU,QAAO,KAAK,UAAUA,CAAK;AAE1D,QAAMG,IAAM,OAAOH,CAAK,GAClBI,IAAYX,EAAO,aAAa,KAChCY,IAAUZ,EAAO,WAAW;AAAA;AAGlC,SAAIA,EAAO,gBAAgBU,EAAI,SAASC,CAAS,KAAKD,EAAI,SAASE,CAAO,KAAKF,EAAI,SAAS,GAAG,IACtF,IAAIA,EAAI,QAAQ,MAAM,IAAI,CAAC,MAG7BA;AACT;AAQO,SAASG,EAAmBC,GAA4B;AAC7D,QAAM,EAAE,MAAAC,GAAM,SAAAC,GAAS,iBAAAC,GAAiB,QAAAjB,MAAWc,GAC7CH,IAAYX,EAAO,aAAa,KAChCY,IAAUZ,EAAO,WAAW;AAAA,GAG5BkB,IAAiBF,EAAQ,OAAO,CAACG,MAAM,CAACA,EAAE,UAAU,CAACA,EAAE,MAAM,WAAW,IAAI,CAAC,GAE7EC,IAAkB,CAAA;AAGxB,MAAIpB,EAAO,gBAAgB;AACzB,UAAMqB,IAAcH,EAAe,IAAI,CAACC,MAAM;AAC5C,YAAMG,IAASH,EAAE,UAAUA,EAAE;AAE7B,aAAIG,EAAO,SAASX,CAAS,KAAKW,EAAO,SAASV,CAAO,KAAKU,EAAO,SAAS,GAAG,IACxE,IAAIA,EAAO,QAAQ,MAAM,IAAI,CAAC,MAEhCA;AAAA,IACT,CAAC;AACD,IAAAF,EAAM,KAAKC,EAAY,KAAKV,CAAS,CAAC;AAAA,EACxC;AAIA,QAAMY,IAAgB,CAAC,GADPN,aAA2B,MAAM,CAAC,GAAGA,CAAe,IAAIA,CACvC,EAAE,KAAK,CAACO,GAAGC,MAAMD,IAAIC,CAAC;AAGvD,aAAWC,KAAOH,GAAe;AAC/B,UAAMd,IAAMM,EAAKW,CAAG;AACpB,QAAI,CAACjB,EAAK;AAEV,UAAMkB,IAAQT,EAAe;AAAA,MAAI,CAACU,MAChCtB,EAAiBG,EAAgCmB,EAAI,KAAK,GAAGA,EAAI,OAAOnB,GAAKT,CAAM;AAAA,IAAA;AAErF,IAAAoB,EAAM,KAAKO,EAAM,KAAKhB,CAAS,CAAC;AAAA,EAClC;AAEA,SAAOS,EAAM,KAAKR,CAAO;AAC3B;AAWA,eAAsBiB,EAAgBC,GAAgC;AACpE,MAAI;AACF,iBAAM,UAAU,UAAU,UAAUA,CAAI,GACjC;AAAA,EACT,QAAQ;AAEN,UAAMC,IAAW,SAAS,cAAc,UAAU;AAClD,IAAAA,EAAS,QAAQD,GACjBC,EAAS,MAAM,WAAW,SAC1BA,EAAS,MAAM,UAAU,KACzBA,EAAS,MAAM,gBAAgB,QAC/B,SAAS,KAAK,YAAYA,CAAQ,GAClCA,EAAS,OAAA;AACT,UAAMC,IAAU,SAAS,YAAY,MAAM;AAC3C,oBAAS,KAAK,YAAYD,CAAQ,GAC3BC;AAAA,EACT;AACF;AC7GO,SAASC,EAAmBH,GAAc9B,GAAqC;AACpF,QAAMW,IAAYX,EAAO,aAAa,KAChCY,IAAUZ,EAAO,WAAW;AAAA,GAG5BkC,IAAiBJ,EAAK,QAAQ,SAAS;AAAA,CAAI,EAAE,QAAQ,OAAO;AAAA,CAAI,GAGhEf,IAAmB,CAAA;AACzB,MAAIoB,IAAuB,CAAA,GACvBC,IAAc,IACdC,IAAW;AAEf,WAASC,IAAI,GAAGA,IAAIJ,EAAe,QAAQI,KAAK;AAC9C,UAAMC,IAAOL,EAAeI,CAAC;AAE7B,IAAIC,MAAS,OAAO,CAACF,IAEnBA,IAAW,KACFE,MAAS,OAAOF,IAErBH,EAAeI,IAAI,CAAC,MAAM,OAC5BF,KAAe,KACfE,OAGAD,IAAW,KAEJE,MAAS5B,KAAa,CAAC0B,KAEhCF,EAAW,KAAKC,CAAW,GAC3BA,IAAc,MACLG,MAAS3B,KAAW,CAACyB,KAE9BF,EAAW,KAAKC,CAAW,GAC3BA,IAAc,KAEVD,EAAW,SAAS,KAAKA,EAAW,KAAK,CAAChB,MAAMA,EAAE,KAAA,MAAW,EAAE,MACjEJ,EAAK,KAAKoB,CAAU,GAEtBA,IAAa,CAAA,KAEbC,KAAeG;AAAA,EAEnB;AAGA,SAAAJ,EAAW,KAAKC,CAAW,IACvBD,EAAW,SAAS,KAAKA,EAAW,KAAK,CAAChB,MAAMA,EAAE,KAAA,MAAW,EAAE,MACjEJ,EAAK,KAAKoB,CAAU,GAGfpB;AACT;AAUA,eAAsByB,IAAqC;AACzD,MAAI;AACF,WAAO,MAAM,UAAU,UAAU,SAAA;AAAA,EACnC,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;AC/DO,MAAMC,UAAwB1C,EAAgC;AAAA,EAC1D,OAAO;AAAA,EACE,UAAU;AAAA,EAE5B,IAAuB,gBAA0C;AAC/D,WAAO;AAAA,MACL,SAAS;AAAA,MACT,gBAAgB;AAAA,MAChB,WAAW;AAAA,MACX,SAAS;AAAA;AAAA,MACT,cAAc;AAAA,IAAA;AAAA,EAElB;AAAA;AAAA;AAAA,EAIQ,aAAyD;AAAA;AAAA,EAIxD,SAAe;AACtB,SAAK,aAAa;AAAA,EACpB;AAAA;AAAA,EAIS,UAAU2C,GAA+B;AAChD,QAAI,KAAK,OAAO,YAAY,GAAO,QAAO;AAE1C,UAAMC,KAAUD,EAAM,WAAWA,EAAM,YAAYA,EAAM,QAAQ,KAC3DE,KAAWF,EAAM,WAAWA,EAAM,YAAYA,EAAM,QAAQ;AAElE,WAAIC,KACF,KAAKE,GAAYH,EAAM,MAAqB,GACrC,MAGLE,KACF,KAAKE,GAAA,GACE,MAGF;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAOAD,GAAYE,GAA2B;AAGrC,UAAMC,IAAkB,KAAKC,GAAA,GAGvBC,IAAeF,GAAiB,gBAAA,KAAqB,CAAA,GACrDG,IAAkBD,EAAa,SAAS,GACxCE,IAASJ,GAAiB,UAAA,KAAe,CAAA,GACzCK,IAAoBD,EAAO,SAAS,GACpCE,IAAmBN,GAAiB,gBAAA,KAAqB;AAE/D,QAAIlB,GACAyB,GACAC;AAEJ,QAAIL,KAAmBH;AAErB,MAAAlB,IAAOjB,EAAmB;AAAA,QACxB,MAAM,KAAK;AAAA,QACX,SAAS,CAAC,GAAG,KAAK,OAAO;AAAA,QACzB,iBAAiBqC;AAAA,QACjB,QAAQ,KAAK;AAAA,MAAA,CACd,GACDK,IAAWL,EAAa,QACxBM,IAAc,KAAK,QAAQ,OAAO,CAACrC,MAAM,CAACA,EAAE,UAAU,CAACA,EAAE,MAAM,WAAW,IAAI,CAAC,EAAE;AAAA,aACxEkC,KAAqBL,GAAiB;AAE/C,YAAMS,IAAQL,EAAOA,EAAO,SAAS,CAAC,GAChCM,IAAS,KAAKC,GAAgB;AAAA,QAClC,UAAUF,EAAM,KAAK;AAAA,QACrB,UAAUA,EAAM,KAAK;AAAA,QACrB,QAAQA,EAAM,GAAG;AAAA,QACjB,QAAQA,EAAM,GAAG;AAAA,MAAA,CAClB;AACD,MAAA3B,IAAO4B,EAAO,MACdH,IAAWG,EAAO,UAClBF,IAAcE,EAAO;AAAA,IACvB,WAAWJ,KAAoBN,GAAiB;AAE9C,YAAMY,IAAOZ,EAAgB,gBAAA,GACvBU,IAAS,KAAKG,GAAeD,EAAK,KAAKA,EAAK,GAAG;AACrD,UAAI,CAACF,EAAQ;AACb,MAAA5B,IAAO4B,EAAO,MACdH,IAAW,GACXC,IAAc;AAAA,IAChB,OAAO;AAEL,YAAME,IAAS,KAAKI,GAAqBf,CAAM;AAC/C,UAAI,CAACW,EAAQ;AACb,MAAA5B,IAAO4B,EAAO,MACdH,IAAW,GACXC,IAAc;AAAA,IAChB;AAEA,IAAA3B,EAAgBC,CAAI,EAAE,KAAK,MAAM;AAC/B,WAAK,aAAa,EAAE,MAAAA,GAAM,WAAW,KAAK,MAAI,GAC9C,KAAK,KAAiB,QAAQ,EAAE,MAAAA,GAAM,UAAAyB,GAAU,aAAAC,GAAa;AAAA,IAC/D,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKAV,KAAqB;AACnB,IAAAN,EAAA,EAAoB,KAAK,CAACV,MAAS;AACjC,UAAI,CAACA,EAAM;AACX,YAAMiC,IAAS9B,EAAmBH,GAAM,KAAK,MAAM;AACnD,WAAK,KAAkB,SAAS,EAAE,MAAMiC,GAAQ,MAAAjC,GAAM;AAAA,IACxD,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKAmB,KAA4D;AAE1D,QAAI;AAGF,YAAMhD,IAAO,KAAK;AAClB,UAAIA,GAAM;AACR,mBAAW+D,KAAU/D,EAAK;AACxB,cAAI+D,EAAO,SAAS;AAClB,mBAAOA;AAAA;AAAA,IAIf,QAAQ;AAAA,IAER;AAAA,EAEF;AAAA;AAAA;AAAA;AAAA,EAKAH,GAAeI,GAAkBC,GAA2C;AAC1E,UAAMC,IAAU,KAAK,KAAKF,CAAQ;AAClC,QAAI,CAACE,EAAS,QAAO;AAErB,UAAMC,IAAS,KAAK,QAAQF,CAAQ;AACpC,QAAI,CAACE,EAAQ,QAAO;AAEpB,UAAM7D,IAAQ4D,EAAQC,EAAO,KAAK,GAC5B9C,IAAS8C,EAAO,UAAUA,EAAO;AAEvC,QAAItC;AACJ,QAAI,KAAK,OAAO,gBAAgB;AAC9B,YAAMuC,IAAiB9D,KAAS,OAAO,KAAK,OAAOA,CAAK;AACxD,MAAAuB,IAAO,GAAGR,CAAM,KAAK+C,CAAc;AAAA,IACrC;AACE,MAAAvC,IAAOvB,KAAS,OAAO,KAAK,OAAOA,CAAK;AAG1C,WAAO,EAAE,MAAAuB,EAAA;AAAA,EACX;AAAA;AAAA;AAAA;AAAA,EAKA6B,GAAgBF,GAId;AACA,UAAM,EAAE,UAAAa,GAAU,UAAAC,GAAU,QAAAC,GAAQ,QAAAC,MAAWhB,GACzCiB,IAAS,KAAK,IAAIJ,GAAUE,CAAM,GAClCG,IAAS,KAAK,IAAIL,GAAUE,CAAM,GAClCI,IAAS,KAAK,IAAIL,GAAUE,CAAM,GAClCI,IAAS,KAAK,IAAIN,GAAUE,CAAM,GAElC9D,IAAY,KAAK,OAAO,aAAa,KACrCC,IAAU,KAAK,OAAO,WAAW;AAAA,GACjCQ,IAAkB,CAAA,GAGlB0D,IAAe,KAAK,QAAQ,MAAMF,GAAQC,IAAS,CAAC;AAG1D,QAAI,KAAK,OAAO,gBAAgB;AAC9B,YAAMxD,IAAcyD,EAAa,IAAI,CAAC3D,MAAMA,EAAE,UAAUA,EAAE,KAAK;AAC/D,MAAAC,EAAM,KAAKC,EAAY,KAAKV,CAAS,CAAC;AAAA,IACxC;AAGA,aAASoE,IAAIL,GAAQK,KAAKJ,GAAQI,KAAK;AACrC,YAAMZ,IAAU,KAAK,KAAKY,CAAC;AAC3B,UAAI,CAACZ,EAAS;AAEd,YAAMxC,IAAQmD,EAAa,IAAI,CAAClD,MAAQ;AACtC,cAAMrB,IAAQ4D,EAAQvC,EAAI,KAAK;AAC/B,eAAIrB,KAAS,OAAa,KACtBA,aAAiB,OAAaA,EAAM,YAAA,IACjC,OAAOA,CAAK;AAAA,MACrB,CAAC;AACD,MAAAa,EAAM,KAAKO,EAAM,KAAKhB,CAAS,CAAC;AAAA,IAClC;AAEA,WAAO;AAAA,MACL,MAAMS,EAAM,KAAKR,CAAO;AAAA,MACxB,UAAU+D,IAASD,IAAS;AAAA,MAC5B,aAAaG,IAASD,IAAS;AAAA,IAAA;AAAA,EAEnC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMAd,GAAqBf,GAA6E;AAEhG,UAAMa,IAAOb,EAAO,QAAQ,oBAAoB;AAChD,QAAI,CAACa,EAAM,QAAO;AAElB,UAAMpD,IAAQoD,EAAK,QAAQ;AAC3B,QAAI,CAACpD,EAAO,QAAO;AAGnB,UAAMwE,IAAcpB,EAAK,QAAQ;AACjC,QAAI,CAACoB,EAAa,QAAO;AAEzB,UAAMf,IAAW,SAASe,GAAa,EAAE;AACzC,QAAI,MAAMf,CAAQ,EAAG,QAAO;AAE5B,UAAME,IAAU,KAAK,KAAKF,CAAQ;AAClC,QAAI,CAACE,EAAS,QAAO;AAErB,UAAM5D,IAAQ4D,EAAQ3D,CAAK,GAErBc,IADS,KAAK,QAAQ,KAAK,CAACH,MAAMA,EAAE,UAAUX,CAAK,GAClC,UAAUA;AAGjC,QAAIsB;AACJ,QAAI,KAAK,OAAO,gBAAgB;AAE9B,YAAMuC,IAAiB9D,KAAS,OAAO,KAAK,OAAOA,CAAK;AACxD,MAAAuB,IAAO,GAAGR,CAAM,KAAK+C,CAAc;AAAA,IACrC;AAEE,MAAAvC,IAAOvB,KAAS,OAAO,KAAK,OAAOA,CAAK;AAG1C,WAAO,EAAE,MAAAuB,GAAM,OAAAtB,GAAO,OAAAD,EAAA;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,OAAwB;AAE5B,UAAM0E,IADkB,KAAKhC,GAAA,GACI,gBAAA,KAAqB,CAAA,GAEhDnB,IAAOjB,EAAmB;AAAA,MAC9B,MAAM,KAAK;AAAA,MACX,SAAS,CAAC,GAAG,KAAK,OAAO;AAAA,MACzB,iBAAiBoE;AAAA,MACjB,QAAQ,KAAK;AAAA,IAAA,CACd;AAED,iBAAMpD,EAAgBC,CAAI,GAC1B,KAAK,aAAa,EAAE,MAAAA,GAAM,WAAW,KAAK,MAAI,GACvCA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,SAASmD,GAAoC;AACjD,UAAMnD,IAAOjB,EAAmB;AAAA,MAC9B,MAAM,KAAK;AAAA,MACX,SAAS,CAAC,GAAG,KAAK,OAAO;AAAA,MACzB,iBAAiBoE;AAAA,MACjB,QAAQ,KAAK;AAAA,IAAA,CACd;AAED,iBAAMpD,EAAgBC,CAAI,GAC1B,KAAK,aAAa,EAAE,MAAAA,GAAM,WAAW,KAAK,MAAI,GACvCA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAoC;AACxC,UAAMA,IAAO,MAAMU,EAAA;AACnB,WAAKV,IACEG,EAAmBH,GAAM,KAAK,MAAM,IADzB;AAAA,EAEpB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,gBAA4D;AAC1D,WAAO,KAAK;AAAA,EACd;AACF;"}
@@ -97,6 +97,26 @@ class m {
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
  */
@@ -105,34 +125,34 @@ class m {
105
125
  }
106
126
  }
107
127
  const d = 100;
108
- function u(s) {
109
- if (s == null)
128
+ function u(i) {
129
+ if (i == null)
110
130
  return d;
111
- if (typeof s == "number")
112
- return s;
113
- const t = parseFloat(s);
131
+ if (typeof i == "number")
132
+ return i;
133
+ const t = parseFloat(i);
114
134
  return isNaN(t) ? d : t;
115
135
  }
116
- function c(s) {
117
- return s.map((t) => u(t.width));
136
+ function c(i) {
137
+ return i.map((t) => u(t.width));
118
138
  }
119
- function f(s) {
139
+ function f(i) {
120
140
  const t = [];
121
141
  let e = 0;
122
- for (const i of s)
123
- t.push(e), e += u(i.width);
142
+ for (const s of i)
143
+ t.push(e), e += u(s.width);
124
144
  return t;
125
145
  }
126
- function g(s) {
127
- return s.reduce((t, e) => t + u(e.width), 0);
146
+ function g(i) {
147
+ return i.reduce((t, e) => t + u(e.width), 0);
128
148
  }
129
- function w(s, t, e, i, o) {
149
+ function w(i, t, e, s, o) {
130
150
  const r = e.length;
131
151
  if (r === 0)
132
152
  return { startCol: 0, endCol: 0, visibleColumns: [] };
133
- let n = b(s, e, i);
153
+ let n = b(i, e, s);
134
154
  n = Math.max(0, n - o);
135
- const C = s + t;
155
+ const C = i + t;
136
156
  let h = n;
137
157
  for (let l = n; l < r; l++) {
138
158
  if (e[l] >= C) {
@@ -147,16 +167,16 @@ function w(s, t, e, i, o) {
147
167
  a.push(l);
148
168
  return { startCol: n, endCol: h, visibleColumns: a };
149
169
  }
150
- function b(s, t, e) {
151
- let i = 0, o = t.length - 1;
152
- for (; i < o; ) {
153
- const r = Math.floor((i + o) / 2);
154
- t[r] + e[r] <= s ? i = r + 1 : o = r;
170
+ function b(i, t, e) {
171
+ let s = 0, o = t.length - 1;
172
+ for (; s < o; ) {
173
+ const r = Math.floor((s + o) / 2);
174
+ t[r] + e[r] <= i ? s = r + 1 : o = r;
155
175
  }
156
- return i;
176
+ return s;
157
177
  }
158
- function p(s, t, e) {
159
- return e ? s > t : !1;
178
+ function p(i, t, e) {
179
+ return e ? i > t : !1;
160
180
  }
161
181
  class R extends m {
162
182
  name = "columnVirtualization";
@@ -191,9 +211,9 @@ class R extends m {
191
211
  const e = this.config.enabled && p(t.length, this.config.threshold ?? 30, this.config.autoEnable ?? !0);
192
212
  if (this.isVirtualized = e ?? !1, this.columnWidths = c(t), this.columnOffsets = f(t), this.totalWidth = g(t), !e)
193
213
  return this.startCol = 0, this.endCol = t.length - 1, [...t];
194
- const i = this.grid.clientWidth || 800, o = w(
214
+ const s = this.grid.clientWidth || 800, o = w(
195
215
  this.scrollLeft,
196
- i,
216
+ s,
197
217
  this.columnOffsets,
198
218
  this.columnWidths,
199
219
  this.config.overscan ?? 3
@@ -204,8 +224,8 @@ class R extends m {
204
224
  if (!this.isVirtualized) return;
205
225
  const t = this.shadowRoot;
206
226
  if (!t) return;
207
- const e = this.columnOffsets[this.startCol] ?? 0, i = t.querySelector(".header-row"), o = t.querySelectorAll(".data-grid-row");
208
- i && (i.style.paddingLeft = `${e}px`), o.forEach((n) => {
227
+ const e = this.columnOffsets[this.startCol] ?? 0, s = t.querySelector(".header-row"), o = t.querySelectorAll(".data-grid-row");
228
+ s && (s.style.paddingLeft = `${e}px`), o.forEach((n) => {
209
229
  n.style.paddingLeft = `${e}px`;
210
230
  });
211
231
  const r = t.querySelector(".rows-viewport .rows");
@@ -232,8 +252,8 @@ class R extends m {
232
252
  * @param columnIndex - Index of the column to scroll to
233
253
  */
234
254
  scrollToColumn(t) {
235
- const e = this.columnOffsets[t] ?? 0, i = this.grid;
236
- i.scrollLeft = e;
255
+ const e = this.columnOffsets[t] ?? 0, s = this.grid;
256
+ s.scrollLeft = e;
237
257
  }
238
258
  /**
239
259
  * Get the left offset for a specific column.