@toolbox-web/grid 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/all.d.ts +3518 -0
  2. package/all.js +3762 -0
  3. package/all.js.map +1 -0
  4. package/index.d.ts +2367 -0
  5. package/index.js +3105 -0
  6. package/index.js.map +1 -0
  7. package/lib/plugins/clipboard/index.js +365 -0
  8. package/lib/plugins/clipboard/index.js.map +1 -0
  9. package/lib/plugins/column-virtualization/index.js +255 -0
  10. package/lib/plugins/column-virtualization/index.js.map +1 -0
  11. package/lib/plugins/context-menu/index.js +341 -0
  12. package/lib/plugins/context-menu/index.js.map +1 -0
  13. package/lib/plugins/export/index.js +305 -0
  14. package/lib/plugins/export/index.js.map +1 -0
  15. package/lib/plugins/filtering/index.js +759 -0
  16. package/lib/plugins/filtering/index.js.map +1 -0
  17. package/lib/plugins/grouping-columns/index.js +283 -0
  18. package/lib/plugins/grouping-columns/index.js.map +1 -0
  19. package/lib/plugins/grouping-rows/index.js +494 -0
  20. package/lib/plugins/grouping-rows/index.js.map +1 -0
  21. package/lib/plugins/master-detail/index.js +303 -0
  22. package/lib/plugins/master-detail/index.js.map +1 -0
  23. package/lib/plugins/multi-sort/index.js +270 -0
  24. package/lib/plugins/multi-sort/index.js.map +1 -0
  25. package/lib/plugins/pinned-columns/index.js +221 -0
  26. package/lib/plugins/pinned-columns/index.js.map +1 -0
  27. package/lib/plugins/pinned-rows/index.js +459 -0
  28. package/lib/plugins/pinned-rows/index.js.map +1 -0
  29. package/lib/plugins/pivot/index.js +326 -0
  30. package/lib/plugins/pivot/index.js.map +1 -0
  31. package/lib/plugins/reorder/index.js +260 -0
  32. package/lib/plugins/reorder/index.js.map +1 -0
  33. package/lib/plugins/selection/index.js +426 -0
  34. package/lib/plugins/selection/index.js.map +1 -0
  35. package/lib/plugins/server-side/index.js +241 -0
  36. package/lib/plugins/server-side/index.js.map +1 -0
  37. package/lib/plugins/tree/index.js +383 -0
  38. package/lib/plugins/tree/index.js.map +1 -0
  39. package/lib/plugins/undo-redo/index.js +289 -0
  40. package/lib/plugins/undo-redo/index.js.map +1 -0
  41. package/lib/plugins/visibility/index.js +430 -0
  42. package/lib/plugins/visibility/index.js.map +1 -0
  43. package/package.json +53 -0
  44. package/themes/dg-theme-contrast.css +43 -0
  45. package/themes/dg-theme-large.css +54 -0
  46. package/themes/dg-theme-standard.css +19 -0
  47. package/themes/dg-theme-vibrant.css +16 -0
  48. package/umd/grid.all.umd.js +660 -0
  49. package/umd/grid.all.umd.js.map +1 -0
  50. package/umd/grid.umd.js +105 -0
  51. package/umd/grid.umd.js.map +1 -0
  52. package/umd/plugins/clipboard.umd.js +9 -0
  53. package/umd/plugins/clipboard.umd.js.map +1 -0
  54. package/umd/plugins/column-virtualization.umd.js +2 -0
  55. package/umd/plugins/column-virtualization.umd.js.map +1 -0
  56. package/umd/plugins/context-menu.umd.js +53 -0
  57. package/umd/plugins/context-menu.umd.js.map +1 -0
  58. package/umd/plugins/export.umd.js +14 -0
  59. package/umd/plugins/export.umd.js.map +1 -0
  60. package/umd/plugins/filtering.umd.js +175 -0
  61. package/umd/plugins/filtering.umd.js.map +1 -0
  62. package/umd/plugins/grouping-columns.umd.js +29 -0
  63. package/umd/plugins/grouping-columns.umd.js.map +1 -0
  64. package/umd/plugins/grouping-rows.umd.js +40 -0
  65. package/umd/plugins/grouping-rows.umd.js.map +1 -0
  66. package/umd/plugins/master-detail.umd.js +27 -0
  67. package/umd/plugins/master-detail.umd.js.map +1 -0
  68. package/umd/plugins/multi-sort.umd.js +26 -0
  69. package/umd/plugins/multi-sort.umd.js.map +1 -0
  70. package/umd/plugins/pinned-columns.umd.js +2 -0
  71. package/umd/plugins/pinned-columns.umd.js.map +1 -0
  72. package/umd/plugins/pinned-rows.umd.js +73 -0
  73. package/umd/plugins/pinned-rows.umd.js.map +1 -0
  74. package/umd/plugins/pivot.umd.js +8 -0
  75. package/umd/plugins/pivot.umd.js.map +1 -0
  76. package/umd/plugins/reorder.umd.js +31 -0
  77. package/umd/plugins/reorder.umd.js.map +1 -0
  78. package/umd/plugins/selection.umd.js +34 -0
  79. package/umd/plugins/selection.umd.js.map +1 -0
  80. package/umd/plugins/server-side.umd.js +2 -0
  81. package/umd/plugins/server-side.umd.js.map +1 -0
  82. package/umd/plugins/tree.umd.js +11 -0
  83. package/umd/plugins/tree.umd.js.map +1 -0
  84. package/umd/plugins/undo-redo.umd.js +2 -0
  85. package/umd/plugins/undo-redo.umd.js.map +1 -0
  86. package/umd/plugins/visibility.umd.js +94 -0
  87. package/umd/plugins/visibility.umd.js.map +1 -0
package/all.js ADDED
@@ -0,0 +1,3762 @@
1
+ import { BaseGridPlugin as C, runAggregator as X, getAggregator as xe } from "./index.js";
2
+ import { DGEvents as Pt, DataGridElement as Dt, FitModeEnum as Bt, GridCSSVars as Ht, GridClasses as Ot, GridDataAttrs as Wt, DataGridElement as Kt, GridSelectors as Vt, PluginEvents as Gt, SelectionPlugin as zt, TreePlugin as $t } from "./index.js";
3
+ function ve(i, e, t, n) {
4
+ if (n.processCell)
5
+ return n.processCell(i, e, t);
6
+ if (i == null) return "";
7
+ if (i instanceof Date) return i.toISOString();
8
+ if (typeof i == "object") return JSON.stringify(i);
9
+ const o = String(i), r = n.delimiter ?? " ", s = n.newline ?? `
10
+ `;
11
+ return n.quoteStrings || o.includes(r) || o.includes(s) || o.includes('"') ? `"${o.replace(/"/g, '""')}"` : o;
12
+ }
13
+ function F(i) {
14
+ const { rows: e, columns: t, selectedIndices: n, config: o } = i, r = o.delimiter ?? " ", s = o.newline ?? `
15
+ `, l = t.filter((u) => !u.hidden && !u.field.startsWith("__")), a = [];
16
+ if (o.includeHeaders) {
17
+ const u = l.map((h) => {
18
+ const f = h.header || h.field;
19
+ return f.includes(r) || f.includes(s) || f.includes('"') ? `"${f.replace(/"/g, '""')}"` : f;
20
+ });
21
+ a.push(u.join(r));
22
+ }
23
+ const d = [...n instanceof Set ? [...n] : n].sort((u, h) => u - h);
24
+ for (const u of d) {
25
+ const h = e[u];
26
+ if (!h) continue;
27
+ const f = l.map(
28
+ (g) => ve(h[g.field], g.field, h, o)
29
+ );
30
+ a.push(f.join(r));
31
+ }
32
+ return a.join(s);
33
+ }
34
+ async function N(i) {
35
+ try {
36
+ return await navigator.clipboard.writeText(i), !0;
37
+ } catch {
38
+ const e = document.createElement("textarea");
39
+ e.value = i, e.style.position = "fixed", e.style.opacity = "0", e.style.pointerEvents = "none", document.body.appendChild(e), e.select();
40
+ const t = document.execCommand("copy");
41
+ return document.body.removeChild(e), t;
42
+ }
43
+ }
44
+ function J(i, e) {
45
+ const t = e.delimiter ?? " ", n = e.newline ?? `
46
+ `, o = i.replace(/\r\n/g, `
47
+ `).replace(/\r/g, `
48
+ `), r = [];
49
+ let s = [], l = "", a = !1;
50
+ for (let c = 0; c < o.length; c++) {
51
+ const d = o[c];
52
+ d === '"' && !a ? a = !0 : d === '"' && a ? o[c + 1] === '"' ? (l += '"', c++) : a = !1 : d === t && !a ? (s.push(l), l = "") : d === n && !a ? (s.push(l), l = "", (s.length > 1 || s.some((u) => u.trim() !== "")) && r.push(s), s = []) : l += d;
53
+ }
54
+ return s.push(l), (s.length > 1 || s.some((c) => c.trim() !== "")) && r.push(s), r;
55
+ }
56
+ async function Q() {
57
+ try {
58
+ return await navigator.clipboard.readText();
59
+ } catch {
60
+ return "";
61
+ }
62
+ }
63
+ class vt extends C {
64
+ name = "clipboard";
65
+ version = "1.0.0";
66
+ get defaultConfig() {
67
+ return {
68
+ enabled: !0,
69
+ includeHeaders: !1,
70
+ delimiter: " ",
71
+ newline: `
72
+ `,
73
+ quoteStrings: !1
74
+ };
75
+ }
76
+ // ===== Internal State =====
77
+ /** The last copied text (for reference/debugging) */
78
+ lastCopied = null;
79
+ // ===== Lifecycle =====
80
+ detach() {
81
+ this.lastCopied = null;
82
+ }
83
+ // ===== Event Handlers =====
84
+ onKeyDown(e) {
85
+ if (this.config.enabled === !1) return !1;
86
+ const t = (e.ctrlKey || e.metaKey) && e.key === "c", n = (e.ctrlKey || e.metaKey) && e.key === "v";
87
+ return t ? (this.#t(e.target), !0) : n ? (this.#n(), !0) : !1;
88
+ }
89
+ // ===== Private Methods =====
90
+ /**
91
+ * Handle copy operation
92
+ */
93
+ #t(e) {
94
+ const t = this.#e(), n = t?.getSelectedRows() ?? [], o = n.length > 0, r = t?.getRanges() ?? [], s = r.length > 0, l = t?.getSelectedCell() != null;
95
+ let a, c, d;
96
+ if (o && t)
97
+ a = F({
98
+ rows: this.rows,
99
+ columns: [...this.columns],
100
+ selectedIndices: n,
101
+ config: this.config
102
+ }), c = n.length, d = this.columns.filter((u) => !u.hidden && !u.field.startsWith("__")).length;
103
+ else if (s && t) {
104
+ const u = r[r.length - 1], h = this.#r({
105
+ startRow: u.from.row,
106
+ startCol: u.from.col,
107
+ endRow: u.to.row,
108
+ endCol: u.to.col
109
+ });
110
+ a = h.text, c = h.rowCount, d = h.columnCount;
111
+ } else if (l && t) {
112
+ const u = t.getSelectedCell(), h = this.#o(u.row, u.col);
113
+ if (!h) return;
114
+ a = h.text, c = 1, d = 1;
115
+ } else {
116
+ const u = this.#i(e);
117
+ if (!u) return;
118
+ a = u.text, c = 1, d = 1;
119
+ }
120
+ N(a).then(() => {
121
+ this.lastCopied = { text: a, timestamp: Date.now() }, this.emit("copy", { text: a, rowCount: c, columnCount: d });
122
+ });
123
+ }
124
+ /**
125
+ * Handle paste operation
126
+ */
127
+ #n() {
128
+ Q().then((e) => {
129
+ if (!e) return;
130
+ const t = J(e, this.config);
131
+ this.emit("paste", { rows: t, text: e });
132
+ });
133
+ }
134
+ /**
135
+ * Get the selection plugin instance if available.
136
+ */
137
+ #e() {
138
+ try {
139
+ const e = this.grid;
140
+ if (e?._plugins) {
141
+ for (const t of e._plugins)
142
+ if (t.name === "selection")
143
+ return t;
144
+ }
145
+ } catch {
146
+ }
147
+ }
148
+ /**
149
+ * Build text for a single cell by row/col index.
150
+ */
151
+ #o(e, t) {
152
+ const n = this.rows[e];
153
+ if (!n) return null;
154
+ const o = this.columns[t];
155
+ if (!o) return null;
156
+ const r = n[o.field], s = o.header || o.field;
157
+ let l;
158
+ if (this.config.includeHeaders) {
159
+ const a = r == null ? "" : String(r);
160
+ l = `${s}: ${a}`;
161
+ } else
162
+ l = r == null ? "" : String(r);
163
+ return { text: l };
164
+ }
165
+ /**
166
+ * Build text for a rectangular range of cells.
167
+ */
168
+ #r(e) {
169
+ const { startRow: t, startCol: n, endRow: o, endCol: r } = e, s = Math.min(t, o), l = Math.max(t, o), a = Math.min(n, r), c = Math.max(n, r), d = this.config.delimiter ?? " ", u = this.config.newline ?? `
170
+ `, h = [], f = this.columns.slice(a, c + 1);
171
+ if (this.config.includeHeaders) {
172
+ const g = f.map((m) => m.header || m.field);
173
+ h.push(g.join(d));
174
+ }
175
+ for (let g = s; g <= l; g++) {
176
+ const m = this.rows[g];
177
+ if (!m) continue;
178
+ const b = f.map((S) => {
179
+ const R = m[S.field];
180
+ return R == null ? "" : R instanceof Date ? R.toISOString() : String(R);
181
+ });
182
+ h.push(b.join(d));
183
+ }
184
+ return {
185
+ text: h.join(u),
186
+ rowCount: l - s + 1,
187
+ columnCount: c - a + 1
188
+ };
189
+ }
190
+ /**
191
+ * Build text for a single focused cell from DOM.
192
+ * Used when selection plugin is not available or no rows are selected.
193
+ */
194
+ #i(e) {
195
+ const t = e.closest("[data-field-cache]");
196
+ if (!t) return null;
197
+ const n = t.dataset.fieldCache;
198
+ if (!n) return null;
199
+ const o = t.dataset.row;
200
+ if (!o) return null;
201
+ const r = parseInt(o, 10);
202
+ if (isNaN(r)) return null;
203
+ const s = this.rows[r];
204
+ if (!s) return null;
205
+ const l = s[n], c = this.columns.find((u) => u.field === n)?.header || n;
206
+ let d;
207
+ if (this.config.includeHeaders) {
208
+ const u = l == null ? "" : String(l);
209
+ d = `${c}: ${u}`;
210
+ } else
211
+ d = l == null ? "" : String(l);
212
+ return { text: d, field: n, value: l };
213
+ }
214
+ // ===== Public API =====
215
+ /**
216
+ * Copy currently selected rows to clipboard.
217
+ * @returns The copied text
218
+ */
219
+ async copy() {
220
+ const t = this.#e()?.getSelectedRows() ?? [], n = F({
221
+ rows: this.rows,
222
+ columns: [...this.columns],
223
+ selectedIndices: t,
224
+ config: this.config
225
+ });
226
+ return await N(n), this.lastCopied = { text: n, timestamp: Date.now() }, n;
227
+ }
228
+ /**
229
+ * Copy specific rows by index to clipboard.
230
+ * @param indices - Array of row indices to copy
231
+ * @returns The copied text
232
+ */
233
+ async copyRows(e) {
234
+ const t = F({
235
+ rows: this.rows,
236
+ columns: [...this.columns],
237
+ selectedIndices: e,
238
+ config: this.config
239
+ });
240
+ return await N(t), this.lastCopied = { text: t, timestamp: Date.now() }, t;
241
+ }
242
+ /**
243
+ * Read and parse clipboard content.
244
+ * @returns Parsed 2D array of cell values, or null if clipboard is empty
245
+ */
246
+ async paste() {
247
+ const e = await Q();
248
+ return e ? J(e, this.config) : null;
249
+ }
250
+ /**
251
+ * Get the last copied text and timestamp.
252
+ * @returns The last copied info or null
253
+ */
254
+ getLastCopied() {
255
+ return this.lastCopied;
256
+ }
257
+ }
258
+ const Z = 100;
259
+ function G(i) {
260
+ if (i == null)
261
+ return Z;
262
+ if (typeof i == "number")
263
+ return i;
264
+ const e = parseFloat(i);
265
+ return isNaN(e) ? Z : e;
266
+ }
267
+ function ee(i) {
268
+ return i.map((e) => G(e.width));
269
+ }
270
+ function te(i) {
271
+ const e = [];
272
+ let t = 0;
273
+ for (const n of i)
274
+ e.push(t), t += G(n.width);
275
+ return e;
276
+ }
277
+ function ne(i) {
278
+ return i.reduce((e, t) => e + G(t.width), 0);
279
+ }
280
+ function Ce(i, e, t, n, o) {
281
+ const r = t.length;
282
+ if (r === 0)
283
+ return { startCol: 0, endCol: 0, visibleColumns: [] };
284
+ let s = ye(i, t, n);
285
+ s = Math.max(0, s - o);
286
+ const l = i + e;
287
+ let a = s;
288
+ for (let d = s; d < r; d++) {
289
+ if (t[d] >= l) {
290
+ a = d - 1;
291
+ break;
292
+ }
293
+ a = d;
294
+ }
295
+ a = Math.min(r - 1, a + o);
296
+ const c = [];
297
+ for (let d = s; d <= a; d++)
298
+ c.push(d);
299
+ return { startCol: s, endCol: a, visibleColumns: c };
300
+ }
301
+ function ye(i, e, t) {
302
+ let n = 0, o = e.length - 1;
303
+ for (; n < o; ) {
304
+ const r = Math.floor((n + o) / 2);
305
+ e[r] + t[r] <= i ? n = r + 1 : o = r;
306
+ }
307
+ return n;
308
+ }
309
+ function Se(i, e, t) {
310
+ return t ? i > e : !1;
311
+ }
312
+ class Ct extends C {
313
+ name = "columnVirtualization";
314
+ version = "1.0.0";
315
+ get defaultConfig() {
316
+ return {
317
+ enabled: !0,
318
+ autoEnable: !0,
319
+ threshold: 30,
320
+ overscan: 3
321
+ };
322
+ }
323
+ // ===== Internal State =====
324
+ isVirtualized = !1;
325
+ startCol = 0;
326
+ endCol = 0;
327
+ scrollLeft = 0;
328
+ totalWidth = 0;
329
+ columnWidths = [];
330
+ columnOffsets = [];
331
+ // ===== Lifecycle =====
332
+ attach(e) {
333
+ super.attach(e);
334
+ const t = this.columns;
335
+ this.columnWidths = ee(t), this.columnOffsets = te(t), this.totalWidth = ne(t), this.endCol = t.length - 1;
336
+ }
337
+ detach() {
338
+ this.columnWidths = [], this.columnOffsets = [], this.isVirtualized = !1, this.startCol = 0, this.endCol = 0, this.scrollLeft = 0, this.totalWidth = 0;
339
+ }
340
+ // ===== Hooks =====
341
+ processColumns(e) {
342
+ const t = this.config.enabled && Se(e.length, this.config.threshold ?? 30, this.config.autoEnable ?? !0);
343
+ if (this.isVirtualized = t ?? !1, this.columnWidths = ee(e), this.columnOffsets = te(e), this.totalWidth = ne(e), !t)
344
+ return this.startCol = 0, this.endCol = e.length - 1, [...e];
345
+ const n = this.grid.clientWidth || 800, o = Ce(
346
+ this.scrollLeft,
347
+ n,
348
+ this.columnOffsets,
349
+ this.columnWidths,
350
+ this.config.overscan ?? 3
351
+ );
352
+ return this.startCol = o.startCol, this.endCol = o.endCol, o.visibleColumns.map((r) => e[r]);
353
+ }
354
+ afterRender() {
355
+ if (!this.isVirtualized) return;
356
+ const e = this.shadowRoot;
357
+ if (!e) return;
358
+ const t = this.columnOffsets[this.startCol] ?? 0, n = e.querySelector(".header-row"), o = e.querySelectorAll(".data-grid-row");
359
+ n && (n.style.paddingLeft = `${t}px`), o.forEach((s) => {
360
+ s.style.paddingLeft = `${t}px`;
361
+ });
362
+ const r = e.querySelector(".rows-viewport .rows");
363
+ r && (r.style.width = `${this.totalWidth}px`);
364
+ }
365
+ onScroll(e) {
366
+ !this.isVirtualized || Math.abs(e.scrollLeft - this.scrollLeft) < 1 || (this.scrollLeft = e.scrollLeft, this.requestRender());
367
+ }
368
+ // ===== Public API =====
369
+ /**
370
+ * Check if column virtualization is currently active.
371
+ */
372
+ getIsVirtualized() {
373
+ return this.isVirtualized;
374
+ }
375
+ /**
376
+ * Get the current visible column range.
377
+ */
378
+ getVisibleColumnRange() {
379
+ return { start: this.startCol, end: this.endCol };
380
+ }
381
+ /**
382
+ * Scroll the grid to bring a specific column into view.
383
+ * @param columnIndex - Index of the column to scroll to
384
+ */
385
+ scrollToColumn(e) {
386
+ const t = this.columnOffsets[e] ?? 0, n = this.grid;
387
+ n.scrollLeft = t;
388
+ }
389
+ /**
390
+ * Get the left offset for a specific column.
391
+ * @param columnIndex - Index of the column
392
+ */
393
+ getColumnOffset(e) {
394
+ return this.columnOffsets[e] ?? 0;
395
+ }
396
+ /**
397
+ * Get the total width of all columns.
398
+ */
399
+ getTotalWidth() {
400
+ return this.totalWidth;
401
+ }
402
+ }
403
+ function K(i, e) {
404
+ return (typeof i == "function" ? i(e) : i).filter((n) => !(n.hidden === !0 || typeof n.hidden == "function" && n.hidden(e)));
405
+ }
406
+ function Re(i, e) {
407
+ return i.disabled === !0 ? !0 : typeof i.disabled == "function" ? i.disabled(e) : !1;
408
+ }
409
+ function V(i, e, t) {
410
+ const n = document.createElement("div");
411
+ n.className = "tbw-context-menu", n.setAttribute("role", "menu");
412
+ for (const o of i) {
413
+ if (o.separator) {
414
+ const a = document.createElement("div");
415
+ a.className = "tbw-context-menu-separator", a.setAttribute("role", "separator"), n.appendChild(a);
416
+ continue;
417
+ }
418
+ const r = document.createElement("div");
419
+ r.className = "tbw-context-menu-item", o.cssClass && r.classList.add(o.cssClass), r.setAttribute("role", "menuitem"), r.setAttribute("data-id", o.id);
420
+ const s = Re(o, e);
421
+ if (s && (r.classList.add("disabled"), r.setAttribute("aria-disabled", "true")), o.icon) {
422
+ const a = document.createElement("span");
423
+ a.className = "tbw-context-menu-icon", a.innerHTML = o.icon, r.appendChild(a);
424
+ }
425
+ const l = document.createElement("span");
426
+ if (l.className = "tbw-context-menu-label", l.textContent = o.name, r.appendChild(l), o.shortcut) {
427
+ const a = document.createElement("span");
428
+ a.className = "tbw-context-menu-shortcut", a.textContent = o.shortcut, r.appendChild(a);
429
+ }
430
+ if (o.subMenu?.length) {
431
+ const a = document.createElement("span");
432
+ a.className = "tbw-context-menu-arrow", a.textContent = "▶", r.appendChild(a), r.addEventListener("mouseenter", () => {
433
+ if (r.querySelector(".tbw-context-menu") || !o.subMenu) return;
434
+ const d = K(o.subMenu, e), u = V(d, e, t);
435
+ u.classList.add("tbw-context-submenu"), u.style.position = "absolute", u.style.left = "100%", u.style.top = "0", r.style.position = "relative", r.appendChild(u);
436
+ }), r.addEventListener("mouseleave", () => {
437
+ const c = r.querySelector(".tbw-context-menu");
438
+ c && c.remove();
439
+ });
440
+ }
441
+ !s && o.action && !o.subMenu && r.addEventListener("click", (a) => {
442
+ a.stopPropagation(), t(o);
443
+ }), n.appendChild(r);
444
+ }
445
+ return n;
446
+ }
447
+ function oe(i, e, t) {
448
+ i.style.position = "fixed", i.style.left = `${e}px`, i.style.top = `${t}px`, i.style.visibility = "hidden", i.style.zIndex = "10000";
449
+ const n = i.getBoundingClientRect(), o = window.innerWidth, r = window.innerHeight;
450
+ let s = e, l = t;
451
+ e + n.width > o && (s = e - n.width), t + n.height > r && (l = t - n.height), s = Math.max(0, s), l = Math.max(0, l), i.style.left = `${s}px`, i.style.top = `${l}px`, i.style.visibility = "visible";
452
+ }
453
+ let P = null, D = null, A = null;
454
+ const ke = `
455
+ .tbw-context-menu {
456
+ position: fixed;
457
+ background: light-dark(#f5f5f5, #2a2a2a);
458
+ color: light-dark(#222, #eee);
459
+ border: 1px solid light-dark(#d0d0d4, #454545);
460
+ border-radius: 4px;
461
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
462
+ min-width: 160px;
463
+ padding: 4px 0;
464
+ z-index: 10000;
465
+ font-size: 13px;
466
+ font-family: system-ui, sans-serif;
467
+ }
468
+ .tbw-context-menu-item {
469
+ display: flex;
470
+ align-items: center;
471
+ padding: 6px 12px;
472
+ cursor: pointer;
473
+ gap: 8px;
474
+ }
475
+ .tbw-context-menu-item:hover:not(.disabled) {
476
+ background: light-dark(#e8e8e8, #3a3a3a);
477
+ }
478
+ .tbw-context-menu-item.disabled {
479
+ opacity: 0.5;
480
+ cursor: default;
481
+ }
482
+ .tbw-context-menu-item.danger {
483
+ color: light-dark(#c00, #f66);
484
+ }
485
+ .tbw-context-menu-icon {
486
+ width: 16px;
487
+ text-align: center;
488
+ }
489
+ .tbw-context-menu-label {
490
+ flex: 1;
491
+ }
492
+ .tbw-context-menu-shortcut {
493
+ color: light-dark(#888, #888);
494
+ font-size: 11px;
495
+ }
496
+ .tbw-context-menu-arrow {
497
+ font-size: 10px;
498
+ color: light-dark(#888, #888);
499
+ }
500
+ .tbw-context-menu-separator {
501
+ height: 1px;
502
+ background: light-dark(#d0d0d4, #454545);
503
+ margin: 4px 0;
504
+ }
505
+ `, B = [
506
+ {
507
+ id: "copy",
508
+ name: "Copy",
509
+ shortcut: "Ctrl+C",
510
+ action: (i) => {
511
+ i.grid?.plugins?.clipboard?.copy?.();
512
+ }
513
+ },
514
+ { separator: !0, id: "sep1", name: "" },
515
+ {
516
+ id: "export-csv",
517
+ name: "Export CSV",
518
+ action: (i) => {
519
+ i.grid?.plugins?.export?.exportCsv?.();
520
+ }
521
+ }
522
+ ];
523
+ class yt extends C {
524
+ name = "contextMenu";
525
+ version = "1.0.0";
526
+ get defaultConfig() {
527
+ return {
528
+ enabled: !0,
529
+ items: B
530
+ };
531
+ }
532
+ // ===== Internal State =====
533
+ isOpen = !1;
534
+ position = { x: 0, y: 0 };
535
+ params = null;
536
+ menuElement = null;
537
+ // ===== Lifecycle =====
538
+ attach(e) {
539
+ super.attach(e), this.installGlobalHandlers();
540
+ }
541
+ detach() {
542
+ this.menuElement && (this.menuElement.remove(), this.menuElement = null), this.isOpen = !1, this.params = null;
543
+ }
544
+ // ===== Private Methods =====
545
+ installGlobalHandlers() {
546
+ !A && typeof document < "u" && (A = document.createElement("style"), A.id = "tbw-context-menu-styles", A.textContent = ke, document.head.appendChild(A)), P || (P = () => {
547
+ document.querySelectorAll(".tbw-context-menu").forEach((t) => t.remove());
548
+ }, document.addEventListener("click", P)), D || (D = (e) => {
549
+ e.key === "Escape" && document.querySelectorAll(".tbw-context-menu").forEach((n) => n.remove());
550
+ }, document.addEventListener("keydown", D));
551
+ }
552
+ // ===== Hooks =====
553
+ afterRender() {
554
+ if (!this.config.enabled) return;
555
+ const e = this.shadowRoot;
556
+ if (!e) return;
557
+ const t = e.children[0];
558
+ t && t.getAttribute("data-context-menu-bound") !== "true" && (t.setAttribute("data-context-menu-bound", "true"), t.addEventListener("contextmenu", (n) => {
559
+ if (!this.config.enabled) return;
560
+ const o = n;
561
+ o.preventDefault();
562
+ const r = o.target, s = r.closest("[data-row][data-col]"), l = r.closest(".header-cell");
563
+ let a;
564
+ if (s) {
565
+ const d = parseInt(s.getAttribute("data-row") ?? "-1", 10), u = parseInt(s.getAttribute("data-col") ?? "-1", 10), h = this.columns[u], f = this.rows[d];
566
+ a = {
567
+ row: f,
568
+ rowIndex: d,
569
+ column: h,
570
+ columnIndex: u,
571
+ field: h?.field ?? "",
572
+ value: f?.[h?.field] ?? null,
573
+ isHeader: !1,
574
+ event: o
575
+ };
576
+ } else if (l) {
577
+ const d = parseInt(l.getAttribute("data-col") ?? "-1", 10), u = this.columns[d];
578
+ a = {
579
+ row: null,
580
+ rowIndex: -1,
581
+ column: u,
582
+ columnIndex: d,
583
+ field: u?.field ?? "",
584
+ value: null,
585
+ isHeader: !0,
586
+ event: o
587
+ };
588
+ } else
589
+ return;
590
+ this.params = a, this.position = { x: o.clientX, y: o.clientY };
591
+ const c = K(this.config.items ?? B, a);
592
+ c.length && (this.menuElement && this.menuElement.remove(), this.menuElement = V(c, a, (d) => {
593
+ d.action && d.action(a), this.menuElement?.remove(), this.menuElement = null, this.isOpen = !1;
594
+ }), document.body.appendChild(this.menuElement), oe(this.menuElement, o.clientX, o.clientY), this.isOpen = !0, this.emit("context-menu-open", { params: a, items: c }));
595
+ }));
596
+ }
597
+ // ===== Public API =====
598
+ /**
599
+ * Programmatically show the context menu at the specified position.
600
+ * @param x - X coordinate
601
+ * @param y - Y coordinate
602
+ * @param params - Partial context menu parameters
603
+ */
604
+ showMenu(e, t, n) {
605
+ const o = {
606
+ row: n.row ?? null,
607
+ rowIndex: n.rowIndex ?? -1,
608
+ column: n.column ?? null,
609
+ columnIndex: n.columnIndex ?? -1,
610
+ field: n.field ?? "",
611
+ value: n.value ?? null,
612
+ isHeader: n.isHeader ?? !1,
613
+ event: n.event ?? new MouseEvent("contextmenu")
614
+ }, r = K(this.config.items ?? B, o);
615
+ this.menuElement && this.menuElement.remove(), this.menuElement = V(r, o, (s) => {
616
+ s.action && s.action(o), this.menuElement?.remove(), this.menuElement = null, this.isOpen = !1;
617
+ }), document.body.appendChild(this.menuElement), oe(this.menuElement, e, t), this.isOpen = !0;
618
+ }
619
+ /**
620
+ * Hide the context menu.
621
+ */
622
+ hideMenu() {
623
+ this.menuElement && (this.menuElement.remove(), this.menuElement = null, this.isOpen = !1);
624
+ }
625
+ /**
626
+ * Check if the context menu is currently open.
627
+ * @returns Whether the menu is open
628
+ */
629
+ isMenuOpen() {
630
+ return this.isOpen;
631
+ }
632
+ // Styles are injected globally via installGlobalHandlers() since menu renders in document.body
633
+ }
634
+ function re(i, e = !0) {
635
+ if (i == null) return "";
636
+ if (i instanceof Date) return i.toISOString();
637
+ if (typeof i == "object") return JSON.stringify(i);
638
+ const t = String(i);
639
+ return e && (t.includes(",") || t.includes('"') || t.includes(`
640
+ `) || t.includes("\r")) ? `"${t.replace(/"/g, '""')}"` : t;
641
+ }
642
+ function Ee(i, e, t, n = {}) {
643
+ const o = n.delimiter ?? ",", r = n.newline ?? `
644
+ `, s = [], l = n.bom ? "\uFEFF" : "";
645
+ if (t.includeHeaders !== !1) {
646
+ const a = e.map((c) => {
647
+ const d = c.header || c.field, u = t.processHeader ? t.processHeader(d, c.field) : d;
648
+ return re(u);
649
+ });
650
+ s.push(a.join(o));
651
+ }
652
+ for (const a of i) {
653
+ const c = e.map((d) => {
654
+ let u = a[d.field];
655
+ return t.processCell && (u = t.processCell(u, d.field, a)), re(u);
656
+ });
657
+ s.push(c.join(o));
658
+ }
659
+ return l + s.join(r);
660
+ }
661
+ function z(i, e) {
662
+ const t = URL.createObjectURL(i), n = document.createElement("a");
663
+ n.href = t, n.download = e, n.style.display = "none", document.body.appendChild(n), n.click(), document.body.removeChild(n), URL.revokeObjectURL(t);
664
+ }
665
+ function Ae(i, e) {
666
+ const t = new Blob([i], { type: "text/csv;charset=utf-8;" });
667
+ z(t, e);
668
+ }
669
+ function ie(i) {
670
+ return i.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
671
+ }
672
+ function _e(i, e, t) {
673
+ let n = `<?xml version="1.0" encoding="UTF-8"?>
674
+ <?mso-application progid="Excel.Sheet"?>
675
+ <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
676
+ xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
677
+ <Worksheet ss:Name="Sheet1">
678
+ <Table>`;
679
+ if (t.includeHeaders !== !1) {
680
+ n += `
681
+ <Row>`;
682
+ for (const o of e) {
683
+ const r = o.header || o.field, s = t.processHeader ? t.processHeader(r, o.field) : r;
684
+ n += `<Cell><Data ss:Type="String">${ie(s)}</Data></Cell>`;
685
+ }
686
+ n += "</Row>";
687
+ }
688
+ for (const o of i) {
689
+ n += `
690
+ <Row>`;
691
+ for (const r of e) {
692
+ let s = o[r.field];
693
+ t.processCell && (s = t.processCell(s, r.field, o));
694
+ let l = "String", a = "";
695
+ s == null ? a = "" : typeof s == "number" && !isNaN(s) ? (l = "Number", a = String(s)) : s instanceof Date ? (l = "DateTime", a = s.toISOString()) : a = ie(String(s)), n += `<Cell><Data ss:Type="${l}">${a}</Data></Cell>`;
696
+ }
697
+ n += "</Row>";
698
+ }
699
+ return n += `
700
+ </Table>
701
+ </Worksheet>
702
+ </Workbook>`, n;
703
+ }
704
+ function Ie(i, e) {
705
+ const t = e.endsWith(".xls") ? e : `${e}.xls`, n = new Blob([i], {
706
+ type: "application/vnd.ms-excel;charset=utf-8;"
707
+ });
708
+ z(n, t);
709
+ }
710
+ class St extends C {
711
+ name = "export";
712
+ version = "1.0.0";
713
+ get defaultConfig() {
714
+ return {
715
+ enabled: !0,
716
+ fileName: "export",
717
+ includeHeaders: !0,
718
+ onlyVisible: !0,
719
+ onlySelected: !1
720
+ };
721
+ }
722
+ // ===== Internal State =====
723
+ isExportingFlag = !1;
724
+ lastExportInfo = null;
725
+ // ===== Private Methods =====
726
+ performExport(e, t) {
727
+ const n = this.config, o = {
728
+ format: e,
729
+ fileName: t?.fileName ?? n.fileName ?? "export",
730
+ includeHeaders: t?.includeHeaders ?? n.includeHeaders,
731
+ processCell: t?.processCell,
732
+ processHeader: t?.processHeader,
733
+ columns: t?.columns,
734
+ rowIndices: t?.rowIndices
735
+ };
736
+ let r = [...this.columns];
737
+ if (n.onlyVisible && (r = r.filter((a) => !a.hidden && !a.field.startsWith("__"))), t?.columns) {
738
+ const a = new Set(t.columns);
739
+ r = r.filter((c) => a.has(c.field));
740
+ }
741
+ let s = [...this.rows];
742
+ if (n.onlySelected) {
743
+ const a = this.getSelectionState();
744
+ a?.selected?.size && (s = [...a.selected].sort((d, u) => d - u).map((d) => this.rows[d]).filter(Boolean));
745
+ }
746
+ t?.rowIndices && (s = t.rowIndices.map((a) => this.rows[a]).filter(Boolean)), this.isExportingFlag = !0;
747
+ let l = o.fileName;
748
+ try {
749
+ switch (e) {
750
+ case "csv": {
751
+ const a = Ee(s, r, o, { bom: !0 });
752
+ l = l.endsWith(".csv") ? l : `${l}.csv`, Ae(a, l);
753
+ break;
754
+ }
755
+ case "excel": {
756
+ const a = _e(s, r, o);
757
+ l = l.endsWith(".xls") ? l : `${l}.xls`, Ie(a, l);
758
+ break;
759
+ }
760
+ case "json": {
761
+ const a = s.map((u) => {
762
+ const h = {};
763
+ for (const f of r) {
764
+ let g = u[f.field];
765
+ o.processCell && (g = o.processCell(g, f.field, u)), h[f.field] = g;
766
+ }
767
+ return h;
768
+ }), c = JSON.stringify(a, null, 2);
769
+ l = l.endsWith(".json") ? l : `${l}.json`;
770
+ const d = new Blob([c], { type: "application/json" });
771
+ z(d, l);
772
+ break;
773
+ }
774
+ }
775
+ this.lastExportInfo = { format: e, timestamp: /* @__PURE__ */ new Date() }, this.emit("export-complete", {
776
+ format: e,
777
+ fileName: l,
778
+ rowCount: s.length,
779
+ columnCount: r.length
780
+ });
781
+ } finally {
782
+ this.isExportingFlag = !1;
783
+ }
784
+ }
785
+ getSelectionState() {
786
+ try {
787
+ return this.grid?.getPluginState?.("selection") ?? null;
788
+ } catch {
789
+ return null;
790
+ }
791
+ }
792
+ // ===== Public API =====
793
+ /**
794
+ * Export data to CSV format.
795
+ * @param params - Optional export parameters
796
+ */
797
+ exportCsv(e) {
798
+ this.performExport("csv", e);
799
+ }
800
+ /**
801
+ * Export data to Excel format (XML Spreadsheet).
802
+ * @param params - Optional export parameters
803
+ */
804
+ exportExcel(e) {
805
+ this.performExport("excel", e);
806
+ }
807
+ /**
808
+ * Export data to JSON format.
809
+ * @param params - Optional export parameters
810
+ */
811
+ exportJson(e) {
812
+ this.performExport("json", e);
813
+ }
814
+ /**
815
+ * Check if an export is currently in progress.
816
+ * @returns Whether export is in progress
817
+ */
818
+ isExporting() {
819
+ return this.isExportingFlag;
820
+ }
821
+ /**
822
+ * Get information about the last export.
823
+ * @returns Export info or null if no export has occurred
824
+ */
825
+ getLastExport() {
826
+ return this.lastExportInfo;
827
+ }
828
+ }
829
+ function Te(i) {
830
+ const { totalRows: e, viewportHeight: t, scrollTop: n, rowHeight: o, overscan: r } = i, s = Math.ceil(t / o);
831
+ let l = Math.floor(n / o) - r;
832
+ l < 0 && (l = 0);
833
+ let a = l + s + r * 2;
834
+ return a > e && (a = e), a === e && l > 0 && (l = Math.max(0, a - s - r * 2)), {
835
+ start: l,
836
+ end: a,
837
+ offsetY: l * o,
838
+ totalHeight: e * o
839
+ };
840
+ }
841
+ function Le(i, e) {
842
+ return i <= e;
843
+ }
844
+ function Me(i, e, t = !1) {
845
+ const n = i[e.field];
846
+ if (e.operator === "blank")
847
+ return n == null || n === "";
848
+ if (e.operator === "notBlank")
849
+ return n != null && n !== "";
850
+ if (n == null) return !1;
851
+ const o = String(n), r = t ? o : o.toLowerCase(), s = t ? String(e.value) : String(e.value).toLowerCase();
852
+ switch (e.operator) {
853
+ // Text operators
854
+ case "contains":
855
+ return r.includes(s);
856
+ case "notContains":
857
+ return !r.includes(s);
858
+ case "equals":
859
+ return r === s;
860
+ case "notEquals":
861
+ return r !== s;
862
+ case "startsWith":
863
+ return r.startsWith(s);
864
+ case "endsWith":
865
+ return r.endsWith(s);
866
+ // Number/Date operators (use raw numeric values)
867
+ case "lessThan":
868
+ return Number(n) < Number(e.value);
869
+ case "lessThanOrEqual":
870
+ return Number(n) <= Number(e.value);
871
+ case "greaterThan":
872
+ return Number(n) > Number(e.value);
873
+ case "greaterThanOrEqual":
874
+ return Number(n) >= Number(e.value);
875
+ case "between":
876
+ return Number(n) >= Number(e.value) && Number(n) <= Number(e.valueTo);
877
+ // Set operators
878
+ case "in":
879
+ return Array.isArray(e.value) && e.value.includes(n);
880
+ case "notIn":
881
+ return Array.isArray(e.value) && !e.value.includes(n);
882
+ default:
883
+ return !0;
884
+ }
885
+ }
886
+ function qe(i, e, t = !1) {
887
+ return e.length ? i.filter((n) => e.every((o) => Me(n, o, t))) : i;
888
+ }
889
+ function Fe(i) {
890
+ return JSON.stringify(
891
+ i.map((e) => ({
892
+ field: e.field,
893
+ operator: e.operator,
894
+ value: e.value,
895
+ valueTo: e.valueTo
896
+ }))
897
+ );
898
+ }
899
+ function se(i, e) {
900
+ const t = /* @__PURE__ */ new Set();
901
+ for (const n of i) {
902
+ const o = n[e];
903
+ o != null && t.add(o);
904
+ }
905
+ return [...t].sort((n, o) => typeof n == "number" && typeof o == "number" ? n - o : String(n).localeCompare(String(o)));
906
+ }
907
+ const Ne = `
908
+ .tbw-filter-panel {
909
+ position: fixed;
910
+ background: var(--tbw-filter-panel-bg, var(--tbw-color-panel-bg, light-dark(#eeeeee, #222222)));
911
+ color: var(--tbw-filter-panel-fg, var(--tbw-color-fg, light-dark(#222222, #eeeeee)));
912
+ border: 1px solid var(--tbw-filter-panel-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));
913
+ border-radius: var(--tbw-filter-panel-radius, var(--tbw-border-radius, 4px));
914
+ box-shadow: 0 4px 16px var(--tbw-filter-panel-shadow, var(--tbw-color-shadow, light-dark(rgba(0,0,0,0.1), rgba(0,0,0,0.3))));
915
+ padding: 12px;
916
+ z-index: 10000;
917
+ min-width: 200px;
918
+ max-width: 280px;
919
+ max-height: 350px;
920
+ display: flex;
921
+ flex-direction: column;
922
+ font-family: var(--tbw-font-family, system-ui, sans-serif);
923
+ font-size: var(--tbw-font-size, 13px);
924
+ }
925
+
926
+ .tbw-filter-search {
927
+ margin-bottom: 8px;
928
+ }
929
+
930
+ .tbw-filter-search-input {
931
+ width: 100%;
932
+ padding: 6px 10px;
933
+ background: var(--tbw-filter-input-bg, var(--tbw-color-bg, transparent));
934
+ color: inherit;
935
+ border: 1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));
936
+ border-radius: var(--tbw-filter-input-radius, 4px);
937
+ font-size: inherit;
938
+ box-sizing: border-box;
939
+ }
940
+
941
+ .tbw-filter-search-input:focus {
942
+ outline: none;
943
+ border-color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));
944
+ box-shadow: 0 0 0 2px rgba(from var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6)) r g b / 15%);
945
+ }
946
+
947
+ .tbw-filter-actions {
948
+ display: flex;
949
+ padding: 4px 2px;
950
+ margin-bottom: 8px;
951
+ border-bottom: 1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));
952
+ }
953
+
954
+ .tbw-filter-action-btn {
955
+ background: transparent;
956
+ border: none;
957
+ color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));
958
+ cursor: pointer;
959
+ font-size: 12px;
960
+ padding: 2px 0;
961
+ }
962
+
963
+ .tbw-filter-action-btn:hover {
964
+ text-decoration: underline;
965
+ }
966
+
967
+ .tbw-filter-values {
968
+ flex: 1;
969
+ overflow-y: auto;
970
+ margin-bottom: 8px;
971
+ max-height: 180px;
972
+ position: relative;
973
+ }
974
+
975
+ .tbw-filter-values-spacer {
976
+ width: 1px;
977
+ }
978
+
979
+ .tbw-filter-values-content {
980
+ position: absolute;
981
+ top: 0;
982
+ left: 0;
983
+ right: 0;
984
+ }
985
+
986
+ .tbw-filter-value-item {
987
+ display: flex;
988
+ align-items: center;
989
+ gap: 8px;
990
+ padding: 4px 2px;
991
+ cursor: pointer;
992
+ border-radius: 3px;
993
+ }
994
+
995
+ .tbw-filter-value-item:hover {
996
+ background: var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)));
997
+ }
998
+
999
+ .tbw-filter-checkbox {
1000
+ margin: 0;
1001
+ cursor: pointer;
1002
+ accent-color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));
1003
+ }
1004
+
1005
+ .tbw-filter-no-match {
1006
+ color: var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));
1007
+ padding: 8px 0;
1008
+ text-align: center;
1009
+ font-style: italic;
1010
+ }
1011
+
1012
+ .tbw-filter-buttons {
1013
+ display: flex;
1014
+ gap: 8px;
1015
+ padding-top: 8px;
1016
+ border-top: 1px solid var(--tbw-filter-divider, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));
1017
+ }
1018
+
1019
+ .tbw-filter-apply-btn {
1020
+ flex: 1;
1021
+ padding: 6px 12px;
1022
+ background: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));
1023
+ color: var(--tbw-filter-accent-fg, var(--tbw-color-accent-fg, light-dark(#ffffff, #000000)));
1024
+ border: none;
1025
+ border-radius: 4px;
1026
+ cursor: pointer;
1027
+ font-size: 13px;
1028
+ }
1029
+
1030
+ .tbw-filter-apply-btn:hover {
1031
+ filter: brightness(0.9);
1032
+ }
1033
+
1034
+ .tbw-filter-clear-btn {
1035
+ flex: 1;
1036
+ padding: 6px 12px;
1037
+ background: transparent;
1038
+ color: var(--tbw-filter-muted, var(--tbw-color-fg-muted, light-dark(#555555, #aaaaaa)));
1039
+ border: 1px solid var(--tbw-filter-input-border, var(--tbw-color-border, light-dark(#d0d0d4, #454545)));
1040
+ border-radius: 4px;
1041
+ cursor: pointer;
1042
+ font-size: 13px;
1043
+ }
1044
+
1045
+ .tbw-filter-clear-btn:hover {
1046
+ background: var(--tbw-filter-hover, var(--tbw-color-row-hover, light-dark(#f0f6ff, #1c1c1c)));
1047
+ }
1048
+ `;
1049
+ class E extends C {
1050
+ name = "filtering";
1051
+ version = "1.0.0";
1052
+ get defaultConfig() {
1053
+ return {
1054
+ enabled: !0,
1055
+ debounceMs: 300,
1056
+ caseSensitive: !1,
1057
+ trimInput: !0,
1058
+ useWorker: !0
1059
+ };
1060
+ }
1061
+ // ===== Internal State =====
1062
+ filters = /* @__PURE__ */ new Map();
1063
+ cachedResult = null;
1064
+ cacheKey = null;
1065
+ openPanelField = null;
1066
+ panelElement = null;
1067
+ searchText = /* @__PURE__ */ new Map();
1068
+ excludedValues = /* @__PURE__ */ new Map();
1069
+ documentClickHandler = null;
1070
+ globalStylesInjected = !1;
1071
+ // Virtualization constants for filter value list
1072
+ static LIST_ITEM_HEIGHT = 28;
1073
+ static LIST_OVERSCAN = 3;
1074
+ static LIST_BYPASS_THRESHOLD = 50;
1075
+ // Don't virtualize if < 50 items
1076
+ // ===== Lifecycle =====
1077
+ attach(e) {
1078
+ super.attach(e), this.injectGlobalStyles();
1079
+ }
1080
+ detach() {
1081
+ this.filters.clear(), this.cachedResult = null, this.cacheKey = null, this.openPanelField = null, this.panelElement && (this.panelElement.remove(), this.panelElement = null), this.searchText.clear(), this.excludedValues.clear(), this.removeDocumentClickHandler();
1082
+ }
1083
+ // ===== Hooks =====
1084
+ processRows(e) {
1085
+ const t = [...this.filters.values()];
1086
+ if (!t.length) return [...e];
1087
+ const n = Fe(t);
1088
+ if (this.cacheKey === n && this.cachedResult)
1089
+ return this.cachedResult;
1090
+ const o = qe([...e], t, this.config.caseSensitive);
1091
+ return this.cachedResult = o, this.cacheKey = n, o;
1092
+ }
1093
+ afterRender() {
1094
+ if (!this.config.enabled) return;
1095
+ const e = this.shadowRoot;
1096
+ if (!e) return;
1097
+ e.querySelectorAll('[part~="header-cell"]').forEach((n) => {
1098
+ const o = n.getAttribute("data-col");
1099
+ if (o === null) return;
1100
+ const r = this.columns[parseInt(o, 10)];
1101
+ if (!r || r.filterable === !1 || n.querySelector(".tbw-filter-btn")) return;
1102
+ const s = r.field;
1103
+ if (!s) return;
1104
+ const l = document.createElement("button");
1105
+ l.className = "tbw-filter-btn", l.setAttribute("aria-label", `Filter ${r.header ?? s}`), l.innerHTML = '<svg viewBox="0 0 16 16" width="12" height="12"><path fill="currentColor" d="M6 10.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-2-3a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5z"/></svg>', this.filters.has(s) && (l.classList.add("active"), n.classList.add("filtered")), l.addEventListener("click", (a) => {
1106
+ a.stopPropagation(), this.toggleFilterPanel(s, r, l);
1107
+ }), n.appendChild(l);
1108
+ });
1109
+ }
1110
+ // ===== Public API =====
1111
+ /**
1112
+ * Set a filter on a specific field.
1113
+ * Pass null to remove the filter.
1114
+ */
1115
+ setFilter(e, t) {
1116
+ t === null ? this.filters.delete(e) : this.filters.set(e, { ...t, field: e }), this.cachedResult = null, this.cacheKey = null, this.emit("filter-change", {
1117
+ filters: [...this.filters.values()],
1118
+ filteredRowCount: 0
1119
+ // Will be accurate after processRows
1120
+ }), this.requestRender();
1121
+ }
1122
+ /**
1123
+ * Get the current filter for a field.
1124
+ */
1125
+ getFilter(e) {
1126
+ return this.filters.get(e);
1127
+ }
1128
+ /**
1129
+ * Get all active filters.
1130
+ */
1131
+ getFilters() {
1132
+ return [...this.filters.values()];
1133
+ }
1134
+ /**
1135
+ * Alias for getFilters() to match functional API naming.
1136
+ */
1137
+ getFilterModel() {
1138
+ return this.getFilters();
1139
+ }
1140
+ /**
1141
+ * Set filters from an array (replaces all existing filters).
1142
+ */
1143
+ setFilterModel(e) {
1144
+ this.filters.clear();
1145
+ for (const t of e)
1146
+ this.filters.set(t.field, t);
1147
+ this.cachedResult = null, this.cacheKey = null, this.emit("filter-change", {
1148
+ filters: [...this.filters.values()],
1149
+ filteredRowCount: 0
1150
+ }), this.requestRender();
1151
+ }
1152
+ /**
1153
+ * Clear all filters.
1154
+ */
1155
+ clearAllFilters() {
1156
+ this.filters.clear(), this.excludedValues.clear(), this.searchText.clear(), this.cachedResult = null, this.cacheKey = null, this.emit("filter-change", {
1157
+ filters: [],
1158
+ filteredRowCount: this.rows.length
1159
+ }), this.requestRender();
1160
+ }
1161
+ /**
1162
+ * Clear filter for a specific field.
1163
+ */
1164
+ clearFieldFilter(e) {
1165
+ this.filters.delete(e), this.excludedValues.delete(e), this.searchText.delete(e), this.cachedResult = null, this.cacheKey = null, this.emit("filter-change", {
1166
+ filters: [...this.filters.values()],
1167
+ filteredRowCount: 0
1168
+ }), this.requestRender();
1169
+ }
1170
+ /**
1171
+ * Check if a field has an active filter.
1172
+ */
1173
+ isFieldFiltered(e) {
1174
+ return this.filters.has(e);
1175
+ }
1176
+ /**
1177
+ * Get the count of filtered rows (from cache).
1178
+ */
1179
+ getFilteredRowCount() {
1180
+ return this.cachedResult?.length ?? this.rows.length;
1181
+ }
1182
+ /**
1183
+ * Get all active filters (alias for getFilters).
1184
+ */
1185
+ getActiveFilters() {
1186
+ return this.getFilters();
1187
+ }
1188
+ /**
1189
+ * Get unique values for a field (for set filter dropdowns).
1190
+ * Uses sourceRows to include all values regardless of current filter.
1191
+ */
1192
+ getUniqueValues(e) {
1193
+ return se(this.sourceRows, e);
1194
+ }
1195
+ // ===== Private Methods =====
1196
+ /**
1197
+ * Inject global styles for filter panel (rendered in document.body)
1198
+ */
1199
+ injectGlobalStyles() {
1200
+ if (this.globalStylesInjected) return;
1201
+ if (document.getElementById("tbw-filter-panel-styles")) {
1202
+ this.globalStylesInjected = !0;
1203
+ return;
1204
+ }
1205
+ const e = document.createElement("style");
1206
+ e.id = "tbw-filter-panel-styles", e.textContent = Ne, document.head.appendChild(e), this.globalStylesInjected = !0;
1207
+ }
1208
+ /**
1209
+ * Toggle the filter panel for a field
1210
+ */
1211
+ toggleFilterPanel(e, t, n) {
1212
+ if (this.openPanelField === e) {
1213
+ this.closeFilterPanel();
1214
+ return;
1215
+ }
1216
+ this.closeFilterPanel();
1217
+ const o = document.createElement("div");
1218
+ o.className = "tbw-filter-panel", this.panelElement = o, this.openPanelField = e;
1219
+ const r = se(this.sourceRows, e);
1220
+ let s = this.excludedValues.get(e);
1221
+ s || (s = /* @__PURE__ */ new Set(), this.excludedValues.set(e, s));
1222
+ const l = this.searchText.get(e) ?? "", a = {
1223
+ field: e,
1224
+ column: t,
1225
+ uniqueValues: r,
1226
+ excludedValues: s,
1227
+ searchText: l,
1228
+ applySetFilter: (u) => {
1229
+ this.applySetFilter(e, u), this.closeFilterPanel();
1230
+ },
1231
+ applyTextFilter: (u, h, f) => {
1232
+ this.applyTextFilter(e, u, h, f), this.closeFilterPanel();
1233
+ },
1234
+ clearFilter: () => {
1235
+ this.clearFieldFilter(e), this.closeFilterPanel();
1236
+ },
1237
+ closePanel: () => this.closeFilterPanel()
1238
+ };
1239
+ let c = !1;
1240
+ this.config.filterPanelRenderer && (this.config.filterPanelRenderer(o, a), c = o.children.length > 0), c || this.renderDefaultFilterPanel(o, a, r, s), document.body.appendChild(o), this.positionPanel(o, n);
1241
+ const d = (u) => {
1242
+ !o.contains(u.target) && u.target !== n && this.closeFilterPanel();
1243
+ };
1244
+ this.documentClickHandler = d, setTimeout(() => {
1245
+ document.addEventListener("click", d);
1246
+ }, 0);
1247
+ }
1248
+ /**
1249
+ * Close the filter panel
1250
+ */
1251
+ closeFilterPanel() {
1252
+ this.panelElement && (this.panelElement.remove(), this.panelElement = null), this.openPanelField = null, this.removeDocumentClickHandler();
1253
+ }
1254
+ /**
1255
+ * Remove the document click handler
1256
+ */
1257
+ removeDocumentClickHandler() {
1258
+ this.documentClickHandler && (document.removeEventListener("click", this.documentClickHandler), this.documentClickHandler = null);
1259
+ }
1260
+ /**
1261
+ * Position the panel below the button
1262
+ */
1263
+ positionPanel(e, t) {
1264
+ const n = t.getBoundingClientRect();
1265
+ e.style.position = "fixed", e.style.top = `${n.bottom + 4}px`, e.style.left = `${n.left}px`, requestAnimationFrame(() => {
1266
+ const o = e.getBoundingClientRect();
1267
+ o.right > window.innerWidth - 8 && (e.style.left = `${window.innerWidth - o.width - 8}px`), o.bottom > window.innerHeight - 8 && (e.style.top = `${n.top - o.height - 4}px`);
1268
+ });
1269
+ }
1270
+ /**
1271
+ * Render the default filter panel content
1272
+ */
1273
+ renderDefaultFilterPanel(e, t, n, o) {
1274
+ const { field: r } = t, s = document.createElement("div");
1275
+ s.className = "tbw-filter-search";
1276
+ const l = document.createElement("input");
1277
+ l.type = "text", l.placeholder = "Search...", l.className = "tbw-filter-search-input", l.value = this.searchText.get(r) ?? "", s.appendChild(l), e.appendChild(s);
1278
+ const a = document.createElement("div");
1279
+ a.className = "tbw-filter-actions";
1280
+ const c = document.createElement("label");
1281
+ c.className = "tbw-filter-value-item", c.style.padding = "0", c.style.margin = "0";
1282
+ const d = document.createElement("input");
1283
+ d.type = "checkbox", d.className = "tbw-filter-checkbox";
1284
+ const u = document.createElement("span");
1285
+ u.textContent = "Select All", c.appendChild(d), c.appendChild(u), a.appendChild(c);
1286
+ const h = () => {
1287
+ const p = [...b.values()], v = p.every((x) => x), y = p.every((x) => !x);
1288
+ d.checked = v, d.indeterminate = !v && !y;
1289
+ };
1290
+ d.addEventListener("change", () => {
1291
+ const p = d.checked;
1292
+ for (const v of b.keys())
1293
+ b.set(v, p);
1294
+ h(), q();
1295
+ }), e.appendChild(a);
1296
+ const f = document.createElement("div");
1297
+ f.className = "tbw-filter-values";
1298
+ const g = document.createElement("div");
1299
+ g.className = "tbw-filter-values-spacer", f.appendChild(g);
1300
+ const m = document.createElement("div");
1301
+ m.className = "tbw-filter-values-content", f.appendChild(m);
1302
+ const b = /* @__PURE__ */ new Map();
1303
+ n.forEach((p) => {
1304
+ const v = p == null ? "__null__" : String(p);
1305
+ b.set(v, !o.has(p));
1306
+ }), h();
1307
+ let S = [];
1308
+ const R = (p, v) => {
1309
+ const y = p == null ? "(Blank)" : String(p), x = p == null ? "__null__" : String(p), w = document.createElement("label");
1310
+ w.className = "tbw-filter-value-item", w.style.position = "absolute", w.style.top = `${v * E.LIST_ITEM_HEIGHT}px`, w.style.left = "0", w.style.right = "0", w.style.height = `${E.LIST_ITEM_HEIGHT}px`, w.style.boxSizing = "border-box";
1311
+ const k = document.createElement("input");
1312
+ k.type = "checkbox", k.className = "tbw-filter-checkbox", k.checked = b.get(x) ?? !0, k.dataset.value = x, k.addEventListener("change", () => {
1313
+ b.set(x, k.checked), h();
1314
+ });
1315
+ const Y = document.createElement("span");
1316
+ return Y.textContent = y, w.appendChild(k), w.appendChild(Y), w;
1317
+ }, q = () => {
1318
+ const p = S.length, v = f.clientHeight, y = f.scrollTop;
1319
+ if (g.style.height = `${p * E.LIST_ITEM_HEIGHT}px`, Le(p, E.LIST_BYPASS_THRESHOLD / 3)) {
1320
+ m.innerHTML = "", m.style.transform = "translateY(0px)", S.forEach((w, k) => {
1321
+ m.appendChild(R(w, k));
1322
+ });
1323
+ return;
1324
+ }
1325
+ const x = Te({
1326
+ totalRows: p,
1327
+ viewportHeight: v,
1328
+ scrollTop: y,
1329
+ rowHeight: E.LIST_ITEM_HEIGHT,
1330
+ overscan: E.LIST_OVERSCAN
1331
+ });
1332
+ m.style.transform = `translateY(${x.offsetY}px)`, m.innerHTML = "";
1333
+ for (let w = x.start; w < x.end; w++)
1334
+ m.appendChild(R(S[w], w - x.start));
1335
+ }, j = (p) => {
1336
+ const v = p.toLowerCase();
1337
+ if (S = n.filter((y) => {
1338
+ const x = y == null ? "(Blank)" : String(y);
1339
+ return !p || x.toLowerCase().includes(v);
1340
+ }), S.length === 0) {
1341
+ g.style.height = "0px", m.innerHTML = "";
1342
+ const y = document.createElement("div");
1343
+ y.className = "tbw-filter-no-match", y.textContent = "No matching values", m.appendChild(y);
1344
+ return;
1345
+ }
1346
+ q();
1347
+ };
1348
+ f.addEventListener(
1349
+ "scroll",
1350
+ () => {
1351
+ S.length > 0 && q();
1352
+ },
1353
+ { passive: !0 }
1354
+ ), j(l.value), e.appendChild(f);
1355
+ let U;
1356
+ l.addEventListener("input", () => {
1357
+ clearTimeout(U), U = setTimeout(() => {
1358
+ this.searchText.set(r, l.value), j(l.value);
1359
+ }, this.config.debounceMs ?? 150);
1360
+ });
1361
+ const I = document.createElement("div");
1362
+ I.className = "tbw-filter-buttons";
1363
+ const T = document.createElement("button");
1364
+ T.className = "tbw-filter-apply-btn", T.textContent = "Apply", T.addEventListener("click", () => {
1365
+ const p = [];
1366
+ for (const [v, y] of b)
1367
+ if (!y)
1368
+ if (v === "__null__")
1369
+ p.push(null);
1370
+ else {
1371
+ const x = n.find((w) => String(w) === v);
1372
+ p.push(x !== void 0 ? x : v);
1373
+ }
1374
+ t.applySetFilter(p);
1375
+ }), I.appendChild(T);
1376
+ const L = document.createElement("button");
1377
+ L.className = "tbw-filter-clear-btn", L.textContent = "Clear Filter", L.addEventListener("click", () => {
1378
+ t.clearFilter();
1379
+ }), I.appendChild(L), e.appendChild(I);
1380
+ }
1381
+ /**
1382
+ * Apply a set filter (exclude values)
1383
+ */
1384
+ applySetFilter(e, t) {
1385
+ this.excludedValues.set(e, new Set(t)), t.length === 0 ? this.filters.delete(e) : this.filters.set(e, {
1386
+ field: e,
1387
+ type: "set",
1388
+ operator: "notIn",
1389
+ value: t
1390
+ }), this.cachedResult = null, this.cacheKey = null, this.emit("filter-change", {
1391
+ filters: [...this.filters.values()],
1392
+ filteredRowCount: 0
1393
+ }), this.requestRender();
1394
+ }
1395
+ /**
1396
+ * Apply a text filter
1397
+ */
1398
+ applyTextFilter(e, t, n, o) {
1399
+ this.filters.set(e, {
1400
+ field: e,
1401
+ type: "text",
1402
+ operator: t,
1403
+ value: n,
1404
+ valueTo: o
1405
+ }), this.cachedResult = null, this.cacheKey = null, this.emit("filter-change", {
1406
+ filters: [...this.filters.values()],
1407
+ filteredRowCount: 0
1408
+ }), this.requestRender();
1409
+ }
1410
+ // ===== Column State Hooks =====
1411
+ /**
1412
+ * Return filter state for a column if it has an active filter.
1413
+ */
1414
+ getColumnState(e) {
1415
+ const t = this.filters.get(e);
1416
+ if (t)
1417
+ return {
1418
+ filter: {
1419
+ type: t.type,
1420
+ operator: t.operator,
1421
+ value: t.value,
1422
+ valueTo: t.valueTo
1423
+ }
1424
+ };
1425
+ }
1426
+ /**
1427
+ * Apply filter state from column state.
1428
+ */
1429
+ applyColumnState(e, t) {
1430
+ if (!t.filter) {
1431
+ this.filters.delete(e);
1432
+ return;
1433
+ }
1434
+ const n = {
1435
+ field: e,
1436
+ type: t.filter.type,
1437
+ operator: t.filter.operator,
1438
+ value: t.filter.value,
1439
+ valueTo: t.filter.valueTo
1440
+ };
1441
+ this.filters.set(e, n), this.cachedResult = null, this.cacheKey = null;
1442
+ }
1443
+ // ===== Styles =====
1444
+ styles = `
1445
+ .header-cell.filtered::before {
1446
+ content: '';
1447
+ position: absolute;
1448
+ top: 4px;
1449
+ right: 4px;
1450
+ width: 6px;
1451
+ height: 6px;
1452
+ background: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));
1453
+ border-radius: 50%;
1454
+ }
1455
+ .tbw-filter-btn {
1456
+ display: inline-flex;
1457
+ align-items: center;
1458
+ justify-content: center;
1459
+ background: transparent;
1460
+ border: none;
1461
+ cursor: pointer;
1462
+ padding: 2px;
1463
+ margin-left: 4px;
1464
+ opacity: 0.4;
1465
+ transition: opacity 0.15s;
1466
+ color: inherit;
1467
+ vertical-align: middle;
1468
+ }
1469
+ .tbw-filter-btn:hover,
1470
+ .tbw-filter-btn.active {
1471
+ opacity: 1;
1472
+ }
1473
+ .tbw-filter-btn.active {
1474
+ color: var(--tbw-filter-accent, var(--tbw-color-accent, #3b82f6));
1475
+ }
1476
+ `;
1477
+ }
1478
+ function Pe(i) {
1479
+ if (!i.length) return [];
1480
+ const e = /* @__PURE__ */ new Map(), t = [], n = (s, l) => {
1481
+ if (!l.length) return;
1482
+ const a = t[t.length - 1];
1483
+ if (a && a.implicit && a.firstIndex + a.columns.length === s) {
1484
+ a.columns.push(...l);
1485
+ return;
1486
+ }
1487
+ t.push({
1488
+ id: "__implicit__" + s,
1489
+ label: void 0,
1490
+ columns: l,
1491
+ firstIndex: s,
1492
+ implicit: !0
1493
+ });
1494
+ };
1495
+ let o = [], r = 0;
1496
+ return i.forEach((s, l) => {
1497
+ const a = s.group;
1498
+ if (!a) {
1499
+ o.length === 0 && (r = l), o.push(s);
1500
+ return;
1501
+ }
1502
+ o.length && (n(r, o.slice()), o = []);
1503
+ const c = typeof a == "string" ? a : a.id;
1504
+ let d = e.get(c);
1505
+ d || (d = {
1506
+ id: c,
1507
+ label: typeof a == "string" ? void 0 : a.label,
1508
+ columns: [],
1509
+ firstIndex: l
1510
+ }, e.set(c, d), t.push(d)), d.columns.push(s);
1511
+ }), o.length && n(r, o), t.length === 1 && t[0].implicit && t[0].columns.length === i.length ? [] : t;
1512
+ }
1513
+ function De(i, e, t) {
1514
+ if (!e.length || !i) return;
1515
+ const n = /* @__PURE__ */ new Map();
1516
+ for (const r of e)
1517
+ for (const s of r.columns)
1518
+ s?.field && n.set(s.field, r.id);
1519
+ const o = Array.from(i.querySelectorAll(".cell[data-field]"));
1520
+ o.forEach((r) => {
1521
+ const s = r.getAttribute("data-field") || "", l = n.get(s);
1522
+ l && (r.classList.add("grouped"), r.getAttribute("data-group") || r.setAttribute("data-group", l));
1523
+ });
1524
+ for (const r of e) {
1525
+ const s = r.columns[r.columns.length - 1], l = o.find((a) => a.getAttribute("data-field") === s.field);
1526
+ l && l.classList.add("group-end");
1527
+ }
1528
+ }
1529
+ function Be(i, e) {
1530
+ if (i.length === 0) return null;
1531
+ const t = document.createElement("div");
1532
+ t.className = "header-group-row", t.setAttribute("role", "row");
1533
+ for (const n of i) {
1534
+ const o = n.firstIndex != null ? n.firstIndex : e.findIndex((a) => n.columns.includes(a)), r = String(n.id).startsWith("__implicit__"), s = r ? "" : n.label || n.id, l = document.createElement("div");
1535
+ l.className = "cell header-group-cell", r && l.classList.add("implicit-group"), l.setAttribute("data-group", String(n.id)), l.style.gridColumn = `${o + 1} / span ${n.columns.length}`, l.textContent = s, t.appendChild(l);
1536
+ }
1537
+ return t;
1538
+ }
1539
+ function He(i) {
1540
+ return i.some((e) => e.group != null);
1541
+ }
1542
+ class Rt extends C {
1543
+ name = "groupingColumns";
1544
+ version = "1.0.0";
1545
+ get defaultConfig() {
1546
+ return {
1547
+ enabled: !0,
1548
+ showGroupBorders: !0
1549
+ };
1550
+ }
1551
+ // ===== Internal State =====
1552
+ groups = [];
1553
+ isActive = !1;
1554
+ // ===== Lifecycle =====
1555
+ detach() {
1556
+ this.groups = [], this.isActive = !1;
1557
+ }
1558
+ // ===== Static Detection =====
1559
+ /**
1560
+ * Auto-detect column groups from column configuration.
1561
+ */
1562
+ static detect(e, t) {
1563
+ const n = t?.columns;
1564
+ return Array.isArray(n) ? He(n) : !1;
1565
+ }
1566
+ // ===== Hooks =====
1567
+ processColumns(e) {
1568
+ if (!this.config.enabled)
1569
+ return this.isActive = !1, this.groups = [], [...e];
1570
+ const t = Pe(e);
1571
+ return t.length === 0 ? (this.isActive = !1, this.groups = [], [...e]) : (this.isActive = !0, this.groups = t, [...e]);
1572
+ }
1573
+ afterRender() {
1574
+ if (!this.isActive || this.groups.length === 0) {
1575
+ const s = this.shadowRoot?.querySelector(".header")?.querySelector(".header-group-row");
1576
+ s && s.remove();
1577
+ return;
1578
+ }
1579
+ const e = this.shadowRoot?.querySelector(".header");
1580
+ if (!e) return;
1581
+ const t = e.querySelector(".header-group-row");
1582
+ t && t.remove();
1583
+ const n = Be(this.groups, this.columns);
1584
+ if (n) {
1585
+ const r = e.querySelector(".header-row");
1586
+ r ? e.insertBefore(n, r) : e.appendChild(n);
1587
+ }
1588
+ const o = e.querySelector(".header-row");
1589
+ o && De(o, this.groups, this.columns);
1590
+ }
1591
+ // ===== Public API =====
1592
+ /**
1593
+ * Check if column groups are active.
1594
+ * @returns Whether grouping is active
1595
+ */
1596
+ isGroupingActive() {
1597
+ return this.isActive;
1598
+ }
1599
+ /**
1600
+ * Get the computed column groups.
1601
+ * @returns Array of column groups
1602
+ */
1603
+ getGroups() {
1604
+ return this.groups;
1605
+ }
1606
+ /**
1607
+ * Get columns in a specific group.
1608
+ * @param groupId - The group ID to find
1609
+ * @returns Array of columns in the group
1610
+ */
1611
+ getGroupColumns(e) {
1612
+ const t = this.groups.find((n) => n.id === e);
1613
+ return t ? t.columns : [];
1614
+ }
1615
+ /**
1616
+ * Refresh column groups (recompute from current columns).
1617
+ */
1618
+ refresh() {
1619
+ this.requestRender();
1620
+ }
1621
+ // ===== Styles =====
1622
+ styles = `
1623
+ .header-group-row {
1624
+ display: grid;
1625
+ grid-auto-flow: column;
1626
+ background: var(--tbw-grouping-columns-header-bg, var(--tbw-color-header-bg));
1627
+ border-bottom: 1px solid var(--tbw-grouping-columns-border, var(--tbw-color-border));
1628
+ }
1629
+ .header-group-cell {
1630
+ display: flex;
1631
+ align-items: center;
1632
+ justify-content: center;
1633
+ padding: 4px 8px;
1634
+ font-weight: 600;
1635
+ font-size: 0.9em;
1636
+ text-transform: uppercase;
1637
+ letter-spacing: 0.5px;
1638
+ border-right: 1px solid var(--tbw-grouping-columns-border, var(--tbw-color-border));
1639
+ }
1640
+ .header-group-cell:last-child {
1641
+ border-right: none;
1642
+ }
1643
+ .header-row .cell.grouped {
1644
+ border-top: none;
1645
+ }
1646
+ .header-row .cell.group-end {
1647
+ border-right: 2px solid var(--tbw-grouping-columns-separator, var(--tbw-color-border-strong));
1648
+ }
1649
+ `;
1650
+ }
1651
+ function Oe({ rows: i, config: e, expanded: t }) {
1652
+ const n = e.groupOn;
1653
+ if (!e.enabled || typeof n != "function")
1654
+ return [];
1655
+ const o = { key: "__root__", value: null, depth: -1, rows: [], children: /* @__PURE__ */ new Map() };
1656
+ if (i.forEach((l) => {
1657
+ let a = n(l);
1658
+ a == null || a === !1 ? a = ["__ungrouped__"] : Array.isArray(a) || (a = [a]);
1659
+ let c = o;
1660
+ a.forEach((d, u) => {
1661
+ const h = d == null ? "∅" : String(d), f = c.key === "__root__" ? h : c.key + "||" + h;
1662
+ let g = c.children.get(h);
1663
+ g || (g = { key: f, value: d, depth: u, rows: [], children: /* @__PURE__ */ new Map(), parent: c }, c.children.set(h, g)), c = g;
1664
+ }), c.rows.push(l);
1665
+ }), o.children.size === 1 && o.children.has("__ungrouped__") && o.children.get("__ungrouped__").rows.length === i.length)
1666
+ return [];
1667
+ const r = [], s = (l) => {
1668
+ if (l === o) {
1669
+ l.children.forEach((c) => s(c));
1670
+ return;
1671
+ }
1672
+ const a = t.has(l.key);
1673
+ r.push({
1674
+ kind: "group",
1675
+ key: l.key,
1676
+ value: l.value,
1677
+ depth: l.depth,
1678
+ rows: l.rows,
1679
+ expanded: a
1680
+ }), a && (l.children.size ? l.children.forEach((c) => s(c)) : l.rows.forEach((c) => r.push({ kind: "data", row: c, rowIndex: i.indexOf(c) })));
1681
+ };
1682
+ return s(o), r;
1683
+ }
1684
+ function We(i, e) {
1685
+ const t = new Set(i);
1686
+ return t.has(e) ? t.delete(e) : t.add(e), t;
1687
+ }
1688
+ function Ke(i) {
1689
+ const e = /* @__PURE__ */ new Set();
1690
+ for (const t of i)
1691
+ t.kind === "group" && e.add(t.key);
1692
+ return e;
1693
+ }
1694
+ function Ve() {
1695
+ return /* @__PURE__ */ new Set();
1696
+ }
1697
+ function Ge(i) {
1698
+ return i.kind !== "group" ? 0 : i.rows.length;
1699
+ }
1700
+ class kt extends C {
1701
+ name = "groupingRows";
1702
+ version = "1.0.0";
1703
+ get defaultConfig() {
1704
+ return {
1705
+ enabled: !0,
1706
+ defaultExpanded: !1,
1707
+ showRowCount: !0,
1708
+ indentWidth: 20,
1709
+ aggregators: {}
1710
+ };
1711
+ }
1712
+ // ===== Internal State =====
1713
+ expandedKeys = /* @__PURE__ */ new Set();
1714
+ flattenedRows = [];
1715
+ isActive = !1;
1716
+ // ===== Lifecycle =====
1717
+ detach() {
1718
+ this.expandedKeys.clear(), this.flattenedRows = [], this.isActive = !1;
1719
+ }
1720
+ // ===== Hooks =====
1721
+ /**
1722
+ * Auto-detect grouping configuration from grid config.
1723
+ * Called by plugin system to determine if plugin should activate.
1724
+ */
1725
+ static detect(e, t) {
1726
+ return typeof t?.groupOn == "function" || typeof t?.enableRowGrouping == "boolean";
1727
+ }
1728
+ processRows(e) {
1729
+ const t = this.config;
1730
+ if (!t.enabled || typeof t.groupOn != "function")
1731
+ return this.isActive = !1, this.flattenedRows = [], [...e];
1732
+ const n = Oe({
1733
+ rows: e,
1734
+ config: t,
1735
+ expanded: this.expandedKeys
1736
+ });
1737
+ return n.length === 0 ? (this.isActive = !1, this.flattenedRows = [], [...e]) : (this.isActive = !0, this.flattenedRows = n, n.map((o) => o.kind === "group" ? {
1738
+ __isGroupRow: !0,
1739
+ __groupKey: o.key,
1740
+ __groupValue: o.value,
1741
+ __groupDepth: o.depth,
1742
+ __groupRows: o.rows,
1743
+ __groupExpanded: o.expanded,
1744
+ __groupRowCount: Ge(o)
1745
+ } : o.row));
1746
+ }
1747
+ onCellClick(e) {
1748
+ const t = e.row;
1749
+ if (t?.__isGroupRow && e.originalEvent.target?.closest(".group-toggle"))
1750
+ return this.toggle(t.__groupKey), !0;
1751
+ }
1752
+ /**
1753
+ * Render a row. Returns true if we handled the row (group row), false otherwise.
1754
+ */
1755
+ renderRow(e, t, n) {
1756
+ if (!e?.__isGroupRow)
1757
+ return !1;
1758
+ const o = this.config;
1759
+ if (o.groupRowRenderer) {
1760
+ const l = () => {
1761
+ this.toggle(e.__groupKey);
1762
+ }, a = o.groupRowRenderer({
1763
+ key: e.__groupKey,
1764
+ value: e.__groupValue,
1765
+ depth: e.__groupDepth,
1766
+ rows: e.__groupRows,
1767
+ expanded: e.__groupExpanded,
1768
+ toggleExpand: l
1769
+ });
1770
+ if (a)
1771
+ return t.className = "group-row", t.__isCustomRow = !0, t.setAttribute("data-group-depth", String(e.__groupDepth)), typeof a == "string" ? t.innerHTML = a : (t.innerHTML = "", t.appendChild(a)), !0;
1772
+ }
1773
+ const r = () => {
1774
+ this.toggle(e.__groupKey);
1775
+ };
1776
+ return t.className = "group-row", t.__isCustomRow = !0, t.setAttribute("data-group-depth", String(e.__groupDepth)), t.setAttribute("role", "row"), t.setAttribute("aria-expanded", String(e.__groupExpanded)), t.style.paddingLeft = `${(e.__groupDepth || 0) * (o.indentWidth ?? 20)}px`, t.innerHTML = "", o.fullWidth !== !1 ? this.renderFullWidthGroupRow(e, t, r) : this.renderPerColumnGroupRow(e, t, r), !0;
1777
+ }
1778
+ afterRender() {
1779
+ }
1780
+ // ===== Private Rendering Helpers =====
1781
+ renderFullWidthGroupRow(e, t, n) {
1782
+ const o = this.config, r = document.createElement("div");
1783
+ r.className = "cell group-full", r.style.gridColumn = "1 / -1", r.setAttribute("role", "gridcell");
1784
+ const s = document.createElement("button");
1785
+ s.type = "button", s.className = "group-toggle", s.setAttribute("aria-label", e.__groupExpanded ? "Collapse group" : "Expand group"), s.textContent = e.__groupExpanded ? "▾" : "▸", s.addEventListener("click", (c) => {
1786
+ c.stopPropagation(), n();
1787
+ }), r.appendChild(s);
1788
+ const l = document.createElement("span");
1789
+ l.className = "group-label";
1790
+ const a = o.formatLabel ? o.formatLabel(e.__groupValue, e.__groupDepth || 0, e.__groupKey) : String(e.__groupValue);
1791
+ if (l.textContent = a, r.appendChild(l), o.showRowCount !== !1) {
1792
+ const c = document.createElement("span");
1793
+ c.className = "group-count", c.textContent = `(${e.__groupRowCount ?? e.__groupRows?.length ?? 0})`, r.appendChild(c);
1794
+ }
1795
+ t.appendChild(r);
1796
+ }
1797
+ renderPerColumnGroupRow(e, t, n) {
1798
+ const o = this.config, r = o.aggregators ?? {}, s = this.columns, l = e.__groupRows ?? [];
1799
+ this.grid;
1800
+ const c = this.shadowRoot?.querySelector(".body")?.style.gridTemplateColumns || "";
1801
+ c && (t.style.display = "grid", t.style.gridTemplateColumns = c), s.forEach((d, u) => {
1802
+ const h = document.createElement("div");
1803
+ if (h.className = "cell group-cell", h.setAttribute("data-col", String(u)), h.setAttribute("role", "gridcell"), u === 0) {
1804
+ const f = document.createElement("button");
1805
+ f.type = "button", f.className = "group-toggle", f.textContent = e.__groupExpanded ? "▾" : "▸", f.addEventListener("click", (b) => {
1806
+ b.stopPropagation(), n();
1807
+ }), h.appendChild(f);
1808
+ const g = document.createElement("span"), m = r[d.field];
1809
+ if (m) {
1810
+ const b = X(m, l, d.field, d);
1811
+ g.textContent = b != null ? String(b) : String(e.__groupValue);
1812
+ } else {
1813
+ const b = o.formatLabel ? o.formatLabel(e.__groupValue, e.__groupDepth || 0, e.__groupKey) : String(e.__groupValue);
1814
+ g.textContent = b;
1815
+ }
1816
+ if (h.appendChild(g), o.showRowCount !== !1) {
1817
+ const b = document.createElement("span");
1818
+ b.className = "group-count", b.textContent = ` (${l.length})`, h.appendChild(b);
1819
+ }
1820
+ } else {
1821
+ const f = r[d.field];
1822
+ if (f) {
1823
+ const g = X(f, l, d.field, d);
1824
+ h.textContent = g != null ? String(g) : "";
1825
+ } else
1826
+ h.textContent = "";
1827
+ }
1828
+ t.appendChild(h);
1829
+ });
1830
+ }
1831
+ // ===== Public API =====
1832
+ /**
1833
+ * Expand all groups.
1834
+ */
1835
+ expandAll() {
1836
+ this.expandedKeys = Ke(this.flattenedRows), this.requestRender();
1837
+ }
1838
+ /**
1839
+ * Collapse all groups.
1840
+ */
1841
+ collapseAll() {
1842
+ this.expandedKeys = Ve(), this.requestRender();
1843
+ }
1844
+ /**
1845
+ * Toggle expansion of a specific group.
1846
+ * @param key - The group key to toggle
1847
+ */
1848
+ toggle(e) {
1849
+ this.expandedKeys = We(this.expandedKeys, e);
1850
+ const t = this.flattenedRows.find((n) => n.kind === "group" && n.key === e);
1851
+ this.emit("group-toggle", {
1852
+ key: e,
1853
+ expanded: this.expandedKeys.has(e),
1854
+ value: t?.value,
1855
+ depth: t?.depth ?? 0
1856
+ }), this.requestRender();
1857
+ }
1858
+ /**
1859
+ * Check if a specific group is expanded.
1860
+ * @param key - The group key to check
1861
+ * @returns Whether the group is expanded
1862
+ */
1863
+ isExpanded(e) {
1864
+ return this.expandedKeys.has(e);
1865
+ }
1866
+ /**
1867
+ * Expand a specific group.
1868
+ * @param key - The group key to expand
1869
+ */
1870
+ expand(e) {
1871
+ this.expandedKeys.has(e) || (this.expandedKeys = /* @__PURE__ */ new Set([...this.expandedKeys, e]), this.requestRender());
1872
+ }
1873
+ /**
1874
+ * Collapse a specific group.
1875
+ * @param key - The group key to collapse
1876
+ */
1877
+ collapse(e) {
1878
+ if (this.expandedKeys.has(e)) {
1879
+ const t = new Set(this.expandedKeys);
1880
+ t.delete(e), this.expandedKeys = t, this.requestRender();
1881
+ }
1882
+ }
1883
+ /**
1884
+ * Get the current group state.
1885
+ * @returns Group state information
1886
+ */
1887
+ getGroupState() {
1888
+ const e = this.flattenedRows.filter((t) => t.kind === "group");
1889
+ return {
1890
+ isActive: this.isActive,
1891
+ expandedCount: this.expandedKeys.size,
1892
+ totalGroups: e.length,
1893
+ expandedKeys: [...this.expandedKeys]
1894
+ };
1895
+ }
1896
+ /**
1897
+ * Get the total count of visible rows (including group headers).
1898
+ * @returns Number of visible rows
1899
+ */
1900
+ getRowCount() {
1901
+ return this.flattenedRows.length;
1902
+ }
1903
+ /**
1904
+ * Refresh the grouped row model.
1905
+ * Call this after modifying groupOn or other config options.
1906
+ */
1907
+ refreshGroups() {
1908
+ this.requestRender();
1909
+ }
1910
+ /**
1911
+ * Get current expanded group keys.
1912
+ * @returns Array of expanded group keys
1913
+ */
1914
+ getExpandedGroups() {
1915
+ return [...this.expandedKeys];
1916
+ }
1917
+ /**
1918
+ * Get the flattened row model.
1919
+ * @returns Array of render rows (groups + data rows)
1920
+ */
1921
+ getFlattenedRows() {
1922
+ return this.flattenedRows;
1923
+ }
1924
+ /**
1925
+ * Check if grouping is currently active.
1926
+ * @returns Whether grouping is active
1927
+ */
1928
+ isGroupingActive() {
1929
+ return this.isActive;
1930
+ }
1931
+ /**
1932
+ * Set the groupOn function dynamically.
1933
+ * @param fn - The groupOn function or undefined to disable
1934
+ */
1935
+ setGroupOn(e) {
1936
+ this.config.groupOn = e, this.requestRender();
1937
+ }
1938
+ // ===== Styles =====
1939
+ styles = `
1940
+ .group-row {
1941
+ background: var(--tbw-grouping-rows-bg, var(--tbw-color-panel-bg));
1942
+ font-weight: 500;
1943
+ }
1944
+ .group-row:hover {
1945
+ background: var(--tbw-grouping-rows-bg-hover, var(--tbw-color-row-hover));
1946
+ }
1947
+ .group-toggle {
1948
+ cursor: pointer;
1949
+ user-select: none;
1950
+ display: inline-flex;
1951
+ align-items: center;
1952
+ justify-content: center;
1953
+ width: 20px;
1954
+ height: 20px;
1955
+ margin-right: 4px;
1956
+ font-size: 10px;
1957
+ }
1958
+ .group-toggle:hover {
1959
+ background: var(--tbw-grouping-rows-toggle-hover, var(--tbw-color-row-hover));
1960
+ border-radius: 2px;
1961
+ }
1962
+ .group-label {
1963
+ display: inline-flex;
1964
+ align-items: center;
1965
+ gap: 8px;
1966
+ }
1967
+ .group-count {
1968
+ color: var(--tbw-grouping-rows-count-color, var(--tbw-color-fg-muted));
1969
+ font-size: 0.85em;
1970
+ font-weight: normal;
1971
+ }
1972
+ [data-group-depth="0"] .group-label { padding-left: 0; }
1973
+ [data-group-depth="1"] .group-label { padding-left: 20px; }
1974
+ [data-group-depth="2"] .group-label { padding-left: 40px; }
1975
+ [data-group-depth="3"] .group-label { padding-left: 60px; }
1976
+ [data-group-depth="4"] .group-label { padding-left: 80px; }
1977
+ `;
1978
+ }
1979
+ function H(i, e) {
1980
+ const t = new Set(i);
1981
+ return t.has(e) ? t.delete(e) : t.add(e), t;
1982
+ }
1983
+ function ze(i, e) {
1984
+ const t = new Set(i);
1985
+ return t.add(e), t;
1986
+ }
1987
+ function $e(i, e) {
1988
+ const t = new Set(i);
1989
+ return t.delete(e), t;
1990
+ }
1991
+ function je(i, e) {
1992
+ return i.has(e);
1993
+ }
1994
+ function Ue(i, e, t, n) {
1995
+ const o = document.createElement("div");
1996
+ o.className = "master-detail-row", o.setAttribute("data-detail-for", String(e)), o.setAttribute("role", "row");
1997
+ const r = document.createElement("div");
1998
+ r.className = "master-detail-cell", r.setAttribute("role", "cell"), r.style.gridColumn = `1 / ${n + 1}`;
1999
+ const s = t(i, e);
2000
+ return typeof s == "string" ? r.innerHTML = s : s instanceof HTMLElement && r.appendChild(s), o.appendChild(r), o;
2001
+ }
2002
+ class Et extends C {
2003
+ name = "masterDetail";
2004
+ version = "1.0.0";
2005
+ get defaultConfig() {
2006
+ return {
2007
+ enabled: !0,
2008
+ detailHeight: "auto",
2009
+ expandOnRowClick: !1,
2010
+ collapseOnClickOutside: !1,
2011
+ showExpandColumn: !0
2012
+ };
2013
+ }
2014
+ // ===== Internal State =====
2015
+ expandedRows = /* @__PURE__ */ new Set();
2016
+ detailElements = /* @__PURE__ */ new Map();
2017
+ // ===== Lifecycle =====
2018
+ detach() {
2019
+ this.expandedRows.clear(), this.detailElements.clear();
2020
+ }
2021
+ // ===== Hooks =====
2022
+ processColumns(e) {
2023
+ if (!this.config.detailRenderer)
2024
+ return [...e];
2025
+ const t = [...e];
2026
+ if (t.length > 0) {
2027
+ const n = { ...t[0] }, o = n.viewRenderer;
2028
+ n.viewRenderer = (r) => {
2029
+ const { value: s, row: l } = r, a = this.expandedRows.has(l), c = document.createElement("span");
2030
+ c.className = "master-detail-cell-wrapper";
2031
+ const d = document.createElement("span");
2032
+ d.className = "master-detail-toggle", d.textContent = a ? "▼" : "▶", d.setAttribute("aria-expanded", String(a)), d.setAttribute("aria-label", a ? "Collapse details" : "Expand details"), d.addEventListener("click", (h) => {
2033
+ h.stopPropagation();
2034
+ const f = this.rows.indexOf(l);
2035
+ this.expandedRows = H(this.expandedRows, l), this.emit("detail-expand", {
2036
+ rowIndex: f,
2037
+ row: l,
2038
+ expanded: this.expandedRows.has(l)
2039
+ }), this.requestRender();
2040
+ }), c.appendChild(d);
2041
+ const u = document.createElement("span");
2042
+ if (o) {
2043
+ const h = o(r);
2044
+ h instanceof Node ? u.appendChild(h) : u.textContent = String(h ?? s ?? "");
2045
+ } else
2046
+ u.textContent = String(s ?? "");
2047
+ return c.appendChild(u), c;
2048
+ }, t[0] = n;
2049
+ }
2050
+ return t;
2051
+ }
2052
+ onRowClick(e) {
2053
+ if (!(!this.config.expandOnRowClick || !this.config.detailRenderer))
2054
+ return this.expandedRows = H(this.expandedRows, e.row), this.emit("detail-expand", {
2055
+ rowIndex: e.rowIndex,
2056
+ row: e.row,
2057
+ expanded: this.expandedRows.has(e.row)
2058
+ }), this.requestRender(), !1;
2059
+ }
2060
+ afterRender() {
2061
+ if (!this.config.detailRenderer) return;
2062
+ const e = this.shadowRoot?.querySelector(".rows");
2063
+ if (!e) return;
2064
+ e.querySelectorAll(".master-detail-row").forEach((o) => o.remove()), this.detailElements.clear();
2065
+ const t = e.querySelectorAll(".data-grid-row"), n = this.columns.length;
2066
+ for (const o of t) {
2067
+ const r = o.querySelector(".cell[data-row]"), s = r ? parseInt(r.getAttribute("data-row") ?? "-1", 10) : -1;
2068
+ if (s < 0) continue;
2069
+ const l = this.rows[s];
2070
+ if (!l || !this.expandedRows.has(l)) continue;
2071
+ const a = Ue(l, s, this.config.detailRenderer, n);
2072
+ typeof this.config.detailHeight == "number" && (a.style.height = `${this.config.detailHeight}px`), o.appendChild(a), this.detailElements.set(l, a);
2073
+ }
2074
+ }
2075
+ // ===== Public API =====
2076
+ /**
2077
+ * Expand the detail row at the given index.
2078
+ * @param rowIndex - Index of the row to expand
2079
+ */
2080
+ expand(e) {
2081
+ const t = this.rows[e];
2082
+ t && (this.expandedRows = ze(this.expandedRows, t), this.requestRender());
2083
+ }
2084
+ /**
2085
+ * Collapse the detail row at the given index.
2086
+ * @param rowIndex - Index of the row to collapse
2087
+ */
2088
+ collapse(e) {
2089
+ const t = this.rows[e];
2090
+ t && (this.expandedRows = $e(this.expandedRows, t), this.requestRender());
2091
+ }
2092
+ /**
2093
+ * Toggle the detail row at the given index.
2094
+ * @param rowIndex - Index of the row to toggle
2095
+ */
2096
+ toggle(e) {
2097
+ const t = this.rows[e];
2098
+ t && (this.expandedRows = H(this.expandedRows, t), this.requestRender());
2099
+ }
2100
+ /**
2101
+ * Check if the detail row at the given index is expanded.
2102
+ * @param rowIndex - Index of the row to check
2103
+ * @returns Whether the detail row is expanded
2104
+ */
2105
+ isExpanded(e) {
2106
+ const t = this.rows[e];
2107
+ return t ? je(this.expandedRows, t) : !1;
2108
+ }
2109
+ /**
2110
+ * Expand all detail rows.
2111
+ */
2112
+ expandAll() {
2113
+ for (const e of this.rows)
2114
+ this.expandedRows.add(e);
2115
+ this.requestRender();
2116
+ }
2117
+ /**
2118
+ * Collapse all detail rows.
2119
+ */
2120
+ collapseAll() {
2121
+ this.expandedRows.clear(), this.requestRender();
2122
+ }
2123
+ /**
2124
+ * Get the indices of all expanded rows.
2125
+ * @returns Array of row indices that are expanded
2126
+ */
2127
+ getExpandedRows() {
2128
+ const e = [];
2129
+ for (const t of this.expandedRows) {
2130
+ const n = this.rows.indexOf(t);
2131
+ n >= 0 && e.push(n);
2132
+ }
2133
+ return e;
2134
+ }
2135
+ /**
2136
+ * Get the detail element for a specific row.
2137
+ * @param rowIndex - Index of the row
2138
+ * @returns The detail HTMLElement or undefined
2139
+ */
2140
+ getDetailElement(e) {
2141
+ const t = this.rows[e];
2142
+ return t ? this.detailElements.get(t) : void 0;
2143
+ }
2144
+ // ===== Styles =====
2145
+ styles = `
2146
+ .master-detail-cell-wrapper {
2147
+ display: flex;
2148
+ align-items: center;
2149
+ gap: 4px;
2150
+ }
2151
+ .master-detail-toggle {
2152
+ cursor: pointer;
2153
+ font-size: 10px;
2154
+ opacity: 0.7;
2155
+ user-select: none;
2156
+ }
2157
+ .master-detail-toggle:hover {
2158
+ opacity: 1;
2159
+ }
2160
+ .master-detail-row {
2161
+ grid-column: 1 / -1;
2162
+ display: grid;
2163
+ background: var(--tbw-master-detail-bg, var(--tbw-color-row-alt));
2164
+ border-bottom: 1px solid var(--tbw-master-detail-border, var(--tbw-color-border));
2165
+ }
2166
+ .master-detail-cell {
2167
+ padding: 16px;
2168
+ overflow: auto;
2169
+ }
2170
+ `;
2171
+ }
2172
+ function Ye(i, e, t) {
2173
+ return e.length ? [...i].sort((n, o) => {
2174
+ for (const r of e) {
2175
+ const l = t.find((u) => u.field === r.field)?.sortComparator ?? Xe, a = n[r.field], c = o[r.field], d = l(a, c, n, o);
2176
+ if (d !== 0)
2177
+ return r.direction === "asc" ? d : -d;
2178
+ }
2179
+ return 0;
2180
+ }) : [...i];
2181
+ }
2182
+ function Xe(i, e) {
2183
+ return i == null && e == null ? 0 : i == null ? 1 : e == null ? -1 : typeof i == "number" && typeof e == "number" ? i - e : i instanceof Date && e instanceof Date ? i.getTime() - e.getTime() : typeof i == "boolean" && typeof e == "boolean" ? i === e ? 0 : i ? -1 : 1 : String(i).localeCompare(String(e));
2184
+ }
2185
+ function Je(i, e, t, n) {
2186
+ const o = i.find((r) => r.field === e);
2187
+ return t ? o ? o.direction === "asc" ? i.map((r) => r.field === e ? { ...r, direction: "desc" } : r) : i.filter((r) => r.field !== e) : i.length < n ? [...i, { field: e, direction: "asc" }] : i : o?.direction === "asc" ? [{ field: e, direction: "desc" }] : o?.direction === "desc" ? [] : [{ field: e, direction: "asc" }];
2188
+ }
2189
+ function le(i, e) {
2190
+ const t = i.findIndex((n) => n.field === e);
2191
+ return t >= 0 ? t + 1 : void 0;
2192
+ }
2193
+ function ae(i, e) {
2194
+ return i.find((t) => t.field === e)?.direction;
2195
+ }
2196
+ class At extends C {
2197
+ name = "multiSort";
2198
+ version = "1.0.0";
2199
+ get defaultConfig() {
2200
+ return {
2201
+ enabled: !0,
2202
+ maxSortColumns: 3,
2203
+ showSortIndex: !0
2204
+ };
2205
+ }
2206
+ // ===== Internal State =====
2207
+ sortModel = [];
2208
+ // ===== Lifecycle =====
2209
+ detach() {
2210
+ this.sortModel = [];
2211
+ }
2212
+ // ===== Hooks =====
2213
+ processRows(e) {
2214
+ return this.sortModel.length === 0 ? [...e] : Ye([...e], this.sortModel, [...this.columns]);
2215
+ }
2216
+ onHeaderClick(e) {
2217
+ if (!this.columns.find((r) => r.field === e.field)?.sortable) return !1;
2218
+ const n = e.originalEvent.shiftKey, o = this.config.maxSortColumns ?? 3;
2219
+ return this.sortModel = Je(this.sortModel, e.field, n, o), this.emit("sort-change", { sortModel: [...this.sortModel] }), this.requestRender(), !0;
2220
+ }
2221
+ afterRender() {
2222
+ const e = this.shadowRoot;
2223
+ if (!e) return;
2224
+ const t = this.config.showSortIndex !== !1;
2225
+ e.querySelectorAll(".header-row .cell[data-field]").forEach((o) => {
2226
+ const r = o.getAttribute("data-field");
2227
+ if (!r) return;
2228
+ const s = le(this.sortModel, r), l = ae(this.sortModel, r);
2229
+ if (o.querySelector(".sort-index")?.remove(), l) {
2230
+ o.querySelector('[part~="sort-indicator"], .sort-indicator')?.remove(), o.setAttribute("data-sort", l);
2231
+ const d = document.createElement("span");
2232
+ if (d.className = "sort-indicator", d.style.marginLeft = "4px", d.style.opacity = "0.8", d.textContent = l === "asc" ? "▲" : "▼", o.appendChild(d), t && this.sortModel.length > 1 && s !== void 0) {
2233
+ const u = document.createElement("span");
2234
+ u.className = "sort-index", u.textContent = String(s), o.appendChild(u);
2235
+ }
2236
+ } else
2237
+ o.removeAttribute("data-sort");
2238
+ });
2239
+ }
2240
+ // ===== Public API =====
2241
+ /**
2242
+ * Get the current sort model.
2243
+ * @returns Copy of the current sort model
2244
+ */
2245
+ getSortModel() {
2246
+ return [...this.sortModel];
2247
+ }
2248
+ /**
2249
+ * Set the sort model programmatically.
2250
+ * @param model - New sort model to apply
2251
+ */
2252
+ setSortModel(e) {
2253
+ this.sortModel = [...e], this.emit("sort-change", { sortModel: [...e] }), this.requestRender();
2254
+ }
2255
+ /**
2256
+ * Clear all sorting.
2257
+ */
2258
+ clearSort() {
2259
+ this.sortModel = [], this.emit("sort-change", { sortModel: [] }), this.requestRender();
2260
+ }
2261
+ /**
2262
+ * Get the sort index (1-based) for a specific field.
2263
+ * @param field - Field to check
2264
+ * @returns 1-based index or undefined if not sorted
2265
+ */
2266
+ getSortIndex(e) {
2267
+ return le(this.sortModel, e);
2268
+ }
2269
+ /**
2270
+ * Get the sort direction for a specific field.
2271
+ * @param field - Field to check
2272
+ * @returns Sort direction or undefined if not sorted
2273
+ */
2274
+ getSortDirection(e) {
2275
+ return ae(this.sortModel, e);
2276
+ }
2277
+ // ===== Column State Hooks =====
2278
+ /**
2279
+ * Return sort state for a column if it's in the sort model.
2280
+ */
2281
+ getColumnState(e) {
2282
+ const t = this.sortModel.findIndex((o) => o.field === e);
2283
+ return t === -1 ? void 0 : {
2284
+ sort: {
2285
+ direction: this.sortModel[t].direction,
2286
+ priority: t
2287
+ }
2288
+ };
2289
+ }
2290
+ /**
2291
+ * Apply sort state from column state.
2292
+ * Rebuilds the sort model from all column states.
2293
+ */
2294
+ applyColumnState(e, t) {
2295
+ if (!t.sort) {
2296
+ this.sortModel = this.sortModel.filter((r) => r.field !== e);
2297
+ return;
2298
+ }
2299
+ const n = this.sortModel.findIndex((r) => r.field === e), o = {
2300
+ field: e,
2301
+ direction: t.sort.direction
2302
+ };
2303
+ n !== -1 ? this.sortModel[n] = o : this.sortModel.splice(t.sort.priority, 0, o);
2304
+ }
2305
+ // ===== Styles =====
2306
+ styles = `
2307
+ .header-cell[data-sort="asc"]::after {
2308
+ content: '↑';
2309
+ margin-left: 4px;
2310
+ opacity: 0.8;
2311
+ }
2312
+ .header-cell[data-sort="desc"]::after {
2313
+ content: '↓';
2314
+ margin-left: 4px;
2315
+ opacity: 0.8;
2316
+ }
2317
+ .sort-index {
2318
+ font-size: 10px;
2319
+ background: var(--tbw-multi-sort-badge-bg, var(--tbw-color-panel-bg));
2320
+ color: var(--tbw-multi-sort-badge-color, var(--tbw-color-fg));
2321
+ border-radius: 50%;
2322
+ width: 14px;
2323
+ height: 14px;
2324
+ display: inline-flex;
2325
+ align-items: center;
2326
+ justify-content: center;
2327
+ margin-left: 2px;
2328
+ font-weight: 600;
2329
+ }
2330
+ `;
2331
+ }
2332
+ function Qe(i) {
2333
+ return i.filter((e) => e.sticky === "left");
2334
+ }
2335
+ function Ze(i) {
2336
+ return i.filter((e) => e.sticky === "right");
2337
+ }
2338
+ function O(i) {
2339
+ return i.some((e) => e.sticky === "left" || e.sticky === "right");
2340
+ }
2341
+ function de(i, e) {
2342
+ const t = i.shadowRoot;
2343
+ if (!t) return;
2344
+ const n = Array.from(t.querySelectorAll(".header-row .cell"));
2345
+ if (!n.length) return;
2346
+ const o = /* @__PURE__ */ new Map();
2347
+ e.forEach((l, a) => {
2348
+ l.field && o.set(l.field, a);
2349
+ });
2350
+ let r = 0;
2351
+ for (const l of e)
2352
+ if (l.sticky === "left") {
2353
+ const a = o.get(l.field), c = n.find((d) => d.getAttribute("data-field") === l.field);
2354
+ c && (c.classList.add("sticky-left"), c.style.left = r + "px", a !== void 0 && t.querySelectorAll(`.data-grid-row .cell[data-col="${a}"]`).forEach((d) => {
2355
+ d.classList.add("sticky-left"), d.style.left = r + "px";
2356
+ }), r += c.offsetWidth);
2357
+ }
2358
+ let s = 0;
2359
+ for (const l of [...e].reverse())
2360
+ if (l.sticky === "right") {
2361
+ const a = o.get(l.field), c = n.find((d) => d.getAttribute("data-field") === l.field);
2362
+ c && (c.classList.add("sticky-right"), c.style.right = s + "px", a !== void 0 && t.querySelectorAll(`.data-grid-row .cell[data-col="${a}"]`).forEach((d) => {
2363
+ d.classList.add("sticky-right"), d.style.right = s + "px";
2364
+ }), s += c.offsetWidth);
2365
+ }
2366
+ }
2367
+ function ce(i) {
2368
+ const e = i.shadowRoot;
2369
+ if (!e) return;
2370
+ e.querySelectorAll(".sticky-left, .sticky-right").forEach((n) => {
2371
+ n.classList.remove("sticky-left", "sticky-right"), n.style.left = "", n.style.right = "";
2372
+ });
2373
+ }
2374
+ class _t extends C {
2375
+ name = "pinnedColumns";
2376
+ version = "1.0.0";
2377
+ get defaultConfig() {
2378
+ return {
2379
+ enabled: !0
2380
+ };
2381
+ }
2382
+ // ===== Internal State =====
2383
+ isApplied = !1;
2384
+ leftOffsets = /* @__PURE__ */ new Map();
2385
+ rightOffsets = /* @__PURE__ */ new Map();
2386
+ // ===== Lifecycle =====
2387
+ detach() {
2388
+ this.leftOffsets.clear(), this.rightOffsets.clear(), this.isApplied = !1;
2389
+ }
2390
+ // ===== Detection =====
2391
+ /**
2392
+ * Auto-detect sticky columns from column configuration.
2393
+ */
2394
+ static detect(e, t) {
2395
+ const n = t?.columns;
2396
+ return Array.isArray(n) ? O(n) : !1;
2397
+ }
2398
+ // ===== Hooks =====
2399
+ processColumns(e) {
2400
+ return this.config.enabled ? (this.isApplied = O([...e]), [...e]) : (this.isApplied = !1, [...e]);
2401
+ }
2402
+ afterRender() {
2403
+ if (!this.config.enabled || !this.isApplied)
2404
+ return;
2405
+ const e = this.grid, t = [...this.columns];
2406
+ if (!O(t)) {
2407
+ ce(e), this.isApplied = !1;
2408
+ return;
2409
+ }
2410
+ queueMicrotask(() => {
2411
+ de(e, t);
2412
+ });
2413
+ }
2414
+ // ===== Public API =====
2415
+ /**
2416
+ * Re-apply sticky offsets (e.g., after column resize).
2417
+ */
2418
+ refreshStickyOffsets() {
2419
+ const e = [...this.columns];
2420
+ de(this.grid, e);
2421
+ }
2422
+ /**
2423
+ * Get columns pinned to the left.
2424
+ */
2425
+ getLeftPinnedColumns() {
2426
+ const e = [...this.columns];
2427
+ return Qe(e);
2428
+ }
2429
+ /**
2430
+ * Get columns pinned to the right.
2431
+ */
2432
+ getRightPinnedColumns() {
2433
+ const e = [...this.columns];
2434
+ return Ze(e);
2435
+ }
2436
+ /**
2437
+ * Clear all sticky positioning.
2438
+ */
2439
+ clearStickyPositions() {
2440
+ ce(this.grid);
2441
+ }
2442
+ }
2443
+ function W(i, e) {
2444
+ const t = document.createElement("div");
2445
+ t.className = "tbw-pinned-rows", t.setAttribute("role", "status"), t.setAttribute("aria-live", "polite");
2446
+ const n = document.createElement("div");
2447
+ n.className = "tbw-pinned-rows-left";
2448
+ const o = document.createElement("div");
2449
+ o.className = "tbw-pinned-rows-center";
2450
+ const r = document.createElement("div");
2451
+ if (r.className = "tbw-pinned-rows-right", i.showRowCount !== !1) {
2452
+ const s = document.createElement("span");
2453
+ s.className = "tbw-status-panel tbw-status-panel-row-count", s.textContent = `Total: ${e.totalRows} rows`, n.appendChild(s);
2454
+ }
2455
+ if (i.showFilteredCount && e.filteredRows !== e.totalRows) {
2456
+ const s = document.createElement("span");
2457
+ s.className = "tbw-status-panel tbw-status-panel-filtered-count", s.textContent = `Filtered: ${e.filteredRows}`, n.appendChild(s);
2458
+ }
2459
+ if (i.showSelectedCount && e.selectedRows > 0) {
2460
+ const s = document.createElement("span");
2461
+ s.className = "tbw-status-panel tbw-status-panel-selected-count", s.textContent = `Selected: ${e.selectedRows}`, r.appendChild(s);
2462
+ }
2463
+ if (i.customPanels)
2464
+ for (const s of i.customPanels) {
2465
+ const l = et(s, e);
2466
+ switch (s.position) {
2467
+ case "left":
2468
+ n.appendChild(l);
2469
+ break;
2470
+ case "center":
2471
+ o.appendChild(l);
2472
+ break;
2473
+ case "right":
2474
+ r.appendChild(l);
2475
+ break;
2476
+ }
2477
+ }
2478
+ return t.appendChild(n), t.appendChild(o), t.appendChild(r), t;
2479
+ }
2480
+ function ue(i) {
2481
+ const e = document.createElement("div");
2482
+ return e.className = `tbw-aggregation-rows tbw-aggregation-rows-${i}`, e.setAttribute("role", "rowgroup"), e;
2483
+ }
2484
+ function he(i, e, t, n) {
2485
+ i.innerHTML = "";
2486
+ for (const o of e) {
2487
+ const r = document.createElement("div");
2488
+ if (r.className = "tbw-aggregation-row", r.setAttribute("role", "row"), o.id && r.setAttribute("data-aggregation-id", o.id), o.fullWidth) {
2489
+ const s = document.createElement("div");
2490
+ s.className = "tbw-aggregation-cell tbw-aggregation-cell-full", s.style.gridColumn = "1 / -1", s.textContent = o.label || "", r.appendChild(s);
2491
+ } else
2492
+ for (const s of t) {
2493
+ const l = document.createElement("div");
2494
+ l.className = "tbw-aggregation-cell", l.setAttribute("data-field", s.field);
2495
+ let a;
2496
+ const c = o.aggregators?.[s.field];
2497
+ if (c) {
2498
+ const d = xe(c);
2499
+ d && (a = d(n, s.field, s));
2500
+ } else if (o.cells && Object.prototype.hasOwnProperty.call(o.cells, s.field)) {
2501
+ const d = o.cells[s.field];
2502
+ typeof d == "function" ? a = d(n, s.field, s) : a = d;
2503
+ }
2504
+ l.textContent = a != null ? String(a) : "", r.appendChild(l);
2505
+ }
2506
+ i.appendChild(r);
2507
+ }
2508
+ }
2509
+ function et(i, e) {
2510
+ const t = document.createElement("div");
2511
+ t.className = "tbw-status-panel tbw-status-panel-custom", t.id = `status-panel-${i.id}`;
2512
+ const n = i.render(e);
2513
+ return typeof n == "string" ? t.innerHTML = n : t.appendChild(n), t;
2514
+ }
2515
+ function fe(i, e, t, n, o) {
2516
+ return {
2517
+ totalRows: i.length,
2518
+ filteredRows: o?.cachedResult?.length ?? i.length,
2519
+ selectedRows: n?.selected?.size ?? 0,
2520
+ columns: e,
2521
+ rows: i,
2522
+ grid: t
2523
+ };
2524
+ }
2525
+ class It extends C {
2526
+ name = "pinnedRows";
2527
+ version = "1.0.0";
2528
+ get defaultConfig() {
2529
+ return {
2530
+ enabled: !0,
2531
+ position: "bottom",
2532
+ showRowCount: !0,
2533
+ showSelectedCount: !0,
2534
+ showFilteredCount: !0
2535
+ };
2536
+ }
2537
+ // ===== Internal State =====
2538
+ infoBarElement = null;
2539
+ topAggregationContainer = null;
2540
+ bottomAggregationContainer = null;
2541
+ footerWrapper = null;
2542
+ // ===== Lifecycle =====
2543
+ detach() {
2544
+ this.infoBarElement && (this.infoBarElement.remove(), this.infoBarElement = null), this.topAggregationContainer && (this.topAggregationContainer.remove(), this.topAggregationContainer = null), this.bottomAggregationContainer && (this.bottomAggregationContainer.remove(), this.bottomAggregationContainer = null), this.footerWrapper && (this.footerWrapper.remove(), this.footerWrapper = null);
2545
+ }
2546
+ // ===== Hooks =====
2547
+ afterRender() {
2548
+ if (!this.config.enabled) {
2549
+ this.cleanup();
2550
+ return;
2551
+ }
2552
+ const e = this.shadowRoot;
2553
+ if (!e) return;
2554
+ const t = e.querySelector(".tbw-scroll-area") ?? e.querySelector(".tbw-grid-content") ?? e.children[0];
2555
+ if (!t) return;
2556
+ const n = this.getSelectionState(), o = this.getFilterState(), r = fe(
2557
+ this.rows,
2558
+ this.columns,
2559
+ this.grid,
2560
+ n,
2561
+ o
2562
+ ), s = this.config.aggregationRows || [], l = s.filter((h) => h.position === "top"), a = s.filter((h) => h.position !== "top");
2563
+ if (l.length > 0) {
2564
+ if (!this.topAggregationContainer) {
2565
+ this.topAggregationContainer = ue("top");
2566
+ const h = e.querySelector(".header");
2567
+ h && h.nextSibling ? t.insertBefore(this.topAggregationContainer, h.nextSibling) : t.appendChild(this.topAggregationContainer);
2568
+ }
2569
+ he(
2570
+ this.topAggregationContainer,
2571
+ l,
2572
+ this.visibleColumns,
2573
+ this.rows
2574
+ );
2575
+ } else this.topAggregationContainer && (this.topAggregationContainer.remove(), this.topAggregationContainer = null);
2576
+ const c = this.config.showRowCount !== !1 || this.config.showSelectedCount && r.selectedRows > 0 || this.config.showFilteredCount && r.filteredRows !== r.totalRows || this.config.customPanels && this.config.customPanels.length > 0, d = c && this.config.position !== "top", u = a.length > 0 || d;
2577
+ if (c && this.config.position === "top")
2578
+ if (!this.infoBarElement)
2579
+ this.infoBarElement = W(this.config, r), t.insertBefore(this.infoBarElement, t.firstChild);
2580
+ else {
2581
+ const h = W(this.config, r);
2582
+ this.infoBarElement.replaceWith(h), this.infoBarElement = h;
2583
+ }
2584
+ else this.config.position === "top" && this.infoBarElement && (this.infoBarElement.remove(), this.infoBarElement = null);
2585
+ u ? (this.footerWrapper || (this.footerWrapper = document.createElement("div"), this.footerWrapper.className = "tbw-footer", t.appendChild(this.footerWrapper)), this.footerWrapper.innerHTML = "", a.length > 0 && (this.bottomAggregationContainer || (this.bottomAggregationContainer = ue("bottom")), this.footerWrapper.appendChild(this.bottomAggregationContainer), he(
2586
+ this.bottomAggregationContainer,
2587
+ a,
2588
+ this.visibleColumns,
2589
+ this.rows
2590
+ )), d && (this.infoBarElement = W(this.config, r), this.footerWrapper.appendChild(this.infoBarElement))) : this.cleanupFooter();
2591
+ }
2592
+ // ===== Private Methods =====
2593
+ cleanup() {
2594
+ this.infoBarElement && (this.infoBarElement.remove(), this.infoBarElement = null), this.topAggregationContainer && (this.topAggregationContainer.remove(), this.topAggregationContainer = null), this.bottomAggregationContainer && (this.bottomAggregationContainer.remove(), this.bottomAggregationContainer = null), this.footerWrapper && (this.footerWrapper.remove(), this.footerWrapper = null);
2595
+ }
2596
+ cleanupFooter() {
2597
+ this.footerWrapper && (this.footerWrapper.remove(), this.footerWrapper = null), this.bottomAggregationContainer && (this.bottomAggregationContainer.remove(), this.bottomAggregationContainer = null), this.infoBarElement && this.config.position !== "top" && (this.infoBarElement.remove(), this.infoBarElement = null);
2598
+ }
2599
+ getSelectionState() {
2600
+ try {
2601
+ return this.grid?.getPluginState?.("selection") ?? null;
2602
+ } catch {
2603
+ return null;
2604
+ }
2605
+ }
2606
+ getFilterState() {
2607
+ try {
2608
+ return this.grid?.getPluginState?.("filtering") ?? null;
2609
+ } catch {
2610
+ return null;
2611
+ }
2612
+ }
2613
+ // ===== Public API =====
2614
+ /**
2615
+ * Refresh the status bar to reflect current grid state.
2616
+ */
2617
+ refresh() {
2618
+ this.requestRender();
2619
+ }
2620
+ /**
2621
+ * Get the current status bar context.
2622
+ * @returns The context with row counts and other info
2623
+ */
2624
+ getContext() {
2625
+ const e = this.getSelectionState(), t = this.getFilterState();
2626
+ return fe(
2627
+ this.rows,
2628
+ this.columns,
2629
+ this.grid,
2630
+ e,
2631
+ t
2632
+ );
2633
+ }
2634
+ /**
2635
+ * Add a custom panel to the info bar.
2636
+ * @param panel - The panel configuration to add
2637
+ */
2638
+ addPanel(e) {
2639
+ this.config.customPanels || (this.config.customPanels = []), this.config.customPanels.push(e), this.requestRender();
2640
+ }
2641
+ /**
2642
+ * Remove a custom panel by ID.
2643
+ * @param id - The panel ID to remove
2644
+ */
2645
+ removePanel(e) {
2646
+ this.config.customPanels && (this.config.customPanels = this.config.customPanels.filter((t) => t.id !== e), this.requestRender());
2647
+ }
2648
+ /**
2649
+ * Add an aggregation row.
2650
+ * @param row - The aggregation row configuration
2651
+ */
2652
+ addAggregationRow(e) {
2653
+ this.config.aggregationRows || (this.config.aggregationRows = []), this.config.aggregationRows.push(e), this.requestRender();
2654
+ }
2655
+ /**
2656
+ * Remove an aggregation row by ID.
2657
+ * @param id - The aggregation row ID to remove
2658
+ */
2659
+ removeAggregationRow(e) {
2660
+ this.config.aggregationRows && (this.config.aggregationRows = this.config.aggregationRows.filter((t) => t.id !== e), this.requestRender());
2661
+ }
2662
+ // ===== Styles =====
2663
+ styles = `
2664
+ .tbw-footer {
2665
+ position: sticky;
2666
+ bottom: 0;
2667
+ z-index: var(--tbw-z-layer-pinned-rows, 20);
2668
+ background: var(--tbw-color-panel-bg);
2669
+ }
2670
+
2671
+ .tbw-pinned-rows {
2672
+ display: flex;
2673
+ align-items: center;
2674
+ justify-content: space-between;
2675
+ padding: 8px 12px;
2676
+ background: var(--tbw-pinned-rows-bg, var(--tbw-color-panel-bg));
2677
+ border-top: 1px solid var(--tbw-pinned-rows-border, var(--tbw-color-border));
2678
+ font-size: 12px;
2679
+ color: var(--tbw-pinned-rows-color, var(--tbw-color-fg-muted));
2680
+ min-height: 32px;
2681
+ box-sizing: border-box;
2682
+ min-width: fit-content;
2683
+ }
2684
+ .tbw-pinned-rows-left,
2685
+ .tbw-pinned-rows-center,
2686
+ .tbw-pinned-rows-right {
2687
+ display: flex;
2688
+ align-items: center;
2689
+ gap: 16px;
2690
+ }
2691
+ .tbw-pinned-rows-left {
2692
+ justify-content: flex-start;
2693
+ }
2694
+ .tbw-pinned-rows-center {
2695
+ justify-content: center;
2696
+ flex: 1;
2697
+ }
2698
+ .tbw-pinned-rows-right {
2699
+ justify-content: flex-end;
2700
+ }
2701
+ .tbw-status-panel {
2702
+ white-space: nowrap;
2703
+ }
2704
+
2705
+ .tbw-aggregation-rows {
2706
+ min-width: fit-content;
2707
+ background: var(--tbw-aggregation-bg, var(--tbw-color-header-bg));
2708
+ }
2709
+ .tbw-aggregation-rows-top {
2710
+ border-bottom: 1px solid var(--tbw-aggregation-border, var(--tbw-color-border));
2711
+ }
2712
+ .tbw-aggregation-rows-bottom {
2713
+ border-top: 1px solid var(--tbw-aggregation-border, var(--tbw-color-border));
2714
+ }
2715
+ .tbw-aggregation-row {
2716
+ display: grid;
2717
+ grid-template-columns: var(--tbw-column-template);
2718
+ font-weight: var(--tbw-aggregation-font-weight, 600);
2719
+ }
2720
+ .tbw-aggregation-cell {
2721
+ padding: var(--tbw-cell-padding, 2px 8px);
2722
+ min-height: var(--tbw-row-height, 28px);
2723
+ display: flex;
2724
+ align-items: center;
2725
+ border-right: 1px solid var(--tbw-color-border-cell);
2726
+ }
2727
+ .tbw-aggregation-cell:last-child {
2728
+ border-right: 0;
2729
+ }
2730
+ .tbw-aggregation-cell-full {
2731
+ grid-column: 1 / -1;
2732
+ border-right: 0;
2733
+ }
2734
+ `;
2735
+ }
2736
+ function tt(i) {
2737
+ const e = [];
2738
+ return !i.rowGroupFields?.length && !i.columnGroupFields?.length && e.push("At least one row or column group field is required"), i.valueFields?.length || e.push("At least one value field is required"), e;
2739
+ }
2740
+ function nt(i) {
2741
+ switch (i) {
2742
+ case "sum":
2743
+ return (e) => e.reduce((t, n) => t + n, 0);
2744
+ case "avg":
2745
+ return (e) => e.length ? e.reduce((t, n) => t + n, 0) / e.length : 0;
2746
+ case "count":
2747
+ return (e) => e.length;
2748
+ case "min":
2749
+ return (e) => e.length ? Math.min(...e) : 0;
2750
+ case "max":
2751
+ return (e) => e.length ? Math.max(...e) : 0;
2752
+ case "first":
2753
+ return (e) => e[0] ?? 0;
2754
+ case "last":
2755
+ return (e) => e[e.length - 1] ?? 0;
2756
+ default:
2757
+ return (e) => e.reduce((t, n) => t + n, 0);
2758
+ }
2759
+ }
2760
+ function $(i, e) {
2761
+ return [...i, e].join("|");
2762
+ }
2763
+ function ot(i, e) {
2764
+ const t = e.rowGroupFields ?? [], n = e.columnGroupFields ?? [], o = e.valueFields ?? [], r = rt(i, n), s = it(i, t), l = st(s, n, r, o, 0), a = lt(l, r, o), c = Object.values(a).reduce((d, u) => d + u, 0);
2765
+ return {
2766
+ rows: l,
2767
+ columnKeys: r,
2768
+ totals: a,
2769
+ grandTotal: c
2770
+ };
2771
+ }
2772
+ function rt(i, e) {
2773
+ if (e.length === 0) return ["value"];
2774
+ const t = /* @__PURE__ */ new Set();
2775
+ for (const n of i) {
2776
+ const o = e.map((r) => String(n[r] ?? "")).join("|");
2777
+ t.add(o);
2778
+ }
2779
+ return [...t].sort();
2780
+ }
2781
+ function it(i, e) {
2782
+ const t = /* @__PURE__ */ new Map();
2783
+ for (const n of i) {
2784
+ const o = e.map((s) => String(n[s] ?? "")).join("|");
2785
+ t.has(o) || t.set(o, []);
2786
+ const r = t.get(o);
2787
+ r && r.push(n);
2788
+ }
2789
+ return t;
2790
+ }
2791
+ function st(i, e, t, n, o) {
2792
+ const r = [];
2793
+ for (const [s, l] of i) {
2794
+ const a = {};
2795
+ let c = 0;
2796
+ for (const d of t)
2797
+ for (const u of n) {
2798
+ const f = (e.length > 0 ? l.filter((S) => e.map((R) => String(S[R] ?? "")).join("|") === d) : l).map((S) => Number(S[u.field]) || 0), g = nt(u.aggFunc), m = f.length > 0 ? g(f) : null, b = $([d], u.field);
2799
+ a[b] = m, m !== null && (c += m);
2800
+ }
2801
+ r.push({
2802
+ rowKey: s,
2803
+ rowLabel: s || "(blank)",
2804
+ depth: o,
2805
+ values: a,
2806
+ total: c,
2807
+ isGroup: !1
2808
+ });
2809
+ }
2810
+ return r;
2811
+ }
2812
+ function lt(i, e, t) {
2813
+ const n = {};
2814
+ for (const o of e)
2815
+ for (const r of t) {
2816
+ const s = $([o], r.field);
2817
+ n[s] = i.reduce((l, a) => l + (a.values[s] ?? 0), 0);
2818
+ }
2819
+ return n;
2820
+ }
2821
+ function at(i) {
2822
+ const e = [];
2823
+ function t(n) {
2824
+ if (e.push(n), n.children)
2825
+ for (const o of n.children)
2826
+ t(o);
2827
+ }
2828
+ for (const n of i)
2829
+ t(n);
2830
+ return e;
2831
+ }
2832
+ class Tt extends C {
2833
+ name = "pivot";
2834
+ version = "1.0.0";
2835
+ get defaultConfig() {
2836
+ return {
2837
+ enabled: !0,
2838
+ showTotals: !0,
2839
+ showGrandTotal: !0
2840
+ };
2841
+ }
2842
+ // ===== Internal State =====
2843
+ isActive = !1;
2844
+ pivotResult = null;
2845
+ columnHeaders = [];
2846
+ rowHeaders = [];
2847
+ // ===== Lifecycle =====
2848
+ detach() {
2849
+ this.isActive = !1, this.pivotResult = null, this.columnHeaders = [], this.rowHeaders = [];
2850
+ }
2851
+ // ===== Hooks =====
2852
+ processRows(e) {
2853
+ if (!this.config.enabled || !this.isActive)
2854
+ return [...e];
2855
+ const t = tt(this.config);
2856
+ return t.length > 0 ? (this.warn(`Config errors: ${t.join(", ")}`), [...e]) : (this.pivotResult = ot(e, this.config), at(this.pivotResult.rows).map((n) => ({
2857
+ __pivotRowKey: n.rowKey,
2858
+ __pivotLabel: n.rowLabel,
2859
+ __pivotDepth: n.depth,
2860
+ __pivotIsGroup: n.isGroup,
2861
+ __pivotTotal: n.total,
2862
+ ...n.values
2863
+ })));
2864
+ }
2865
+ processColumns(e) {
2866
+ if (!this.config.enabled || !this.isActive || !this.pivotResult)
2867
+ return [...e];
2868
+ const t = [];
2869
+ t.push({
2870
+ field: "__pivotLabel",
2871
+ header: this.config.rowGroupFields?.join(" / ") ?? "Group",
2872
+ width: 200
2873
+ });
2874
+ for (const n of this.pivotResult.columnKeys)
2875
+ for (const o of this.config.valueFields ?? []) {
2876
+ const r = $([n], o.field);
2877
+ t.push({
2878
+ field: r,
2879
+ header: `${n} - ${o.header || o.field} (${o.aggFunc})`,
2880
+ width: 120,
2881
+ type: "number"
2882
+ });
2883
+ }
2884
+ return this.config.showTotals && t.push({
2885
+ field: "__pivotTotal",
2886
+ header: "Total",
2887
+ width: 100,
2888
+ type: "number"
2889
+ }), t;
2890
+ }
2891
+ // ===== Public API =====
2892
+ /**
2893
+ * Enable pivot mode.
2894
+ */
2895
+ enablePivot() {
2896
+ this.isActive = !0, this.requestRender();
2897
+ }
2898
+ /**
2899
+ * Disable pivot mode and return to normal grid view.
2900
+ */
2901
+ disablePivot() {
2902
+ this.isActive = !1, this.pivotResult = null, this.requestRender();
2903
+ }
2904
+ /**
2905
+ * Check if pivot mode is currently active.
2906
+ */
2907
+ isPivotActive() {
2908
+ return this.isActive;
2909
+ }
2910
+ /**
2911
+ * Get the current pivot result.
2912
+ */
2913
+ getPivotResult() {
2914
+ return this.pivotResult;
2915
+ }
2916
+ /**
2917
+ * Set the row group fields for pivoting.
2918
+ * @param fields - Array of field names to group rows by
2919
+ */
2920
+ setRowGroupFields(e) {
2921
+ this.config.rowGroupFields = e, this.requestRender();
2922
+ }
2923
+ /**
2924
+ * Set the column group fields for pivoting.
2925
+ * @param fields - Array of field names to create columns from
2926
+ */
2927
+ setColumnGroupFields(e) {
2928
+ this.config.columnGroupFields = e, this.requestRender();
2929
+ }
2930
+ /**
2931
+ * Set the value fields with aggregation functions.
2932
+ * @param fields - Array of value field configurations
2933
+ */
2934
+ setValueFields(e) {
2935
+ this.config.valueFields = e, this.requestRender();
2936
+ }
2937
+ /**
2938
+ * Refresh the pivot by clearing cached results.
2939
+ */
2940
+ refresh() {
2941
+ this.pivotResult = null, this.requestRender();
2942
+ }
2943
+ // ===== Styles =====
2944
+ styles = `
2945
+ [data-pivot-depth="1"] { padding-left: 20px; }
2946
+ [data-pivot-depth="2"] { padding-left: 40px; }
2947
+ [data-pivot-depth="3"] { padding-left: 60px; }
2948
+ .pivot-group-row { font-weight: bold; background: var(--tbw-pivot-group-bg, var(--tbw-color-panel-bg)); }
2949
+ .pivot-total-row { font-weight: bold; border-top: 2px solid var(--tbw-pivot-border, var(--tbw-color-border-strong)); }
2950
+ `;
2951
+ }
2952
+ function dt(i) {
2953
+ const e = i.meta ?? {}, t = e.sticky;
2954
+ return t === "left" || t === "right" ? !1 : e.lockPosition !== !0 && e.suppressMovable !== !0;
2955
+ }
2956
+ function ge(i, e, t) {
2957
+ if (e === t || e < 0 || e >= i.length || t < 0 || t > i.length) return i;
2958
+ const n = [...i], [o] = n.splice(e, 1);
2959
+ return n.splice(t, 0, o), n;
2960
+ }
2961
+ class Lt extends C {
2962
+ name = "reorder";
2963
+ version = "1.0.0";
2964
+ get defaultConfig() {
2965
+ return {
2966
+ enabled: !0,
2967
+ animation: !0,
2968
+ animationDuration: 200
2969
+ };
2970
+ }
2971
+ // ===== Internal State =====
2972
+ isDragging = !1;
2973
+ draggedField = null;
2974
+ draggedIndex = null;
2975
+ dropIndex = null;
2976
+ boundReorderRequestHandler = null;
2977
+ // ===== Lifecycle =====
2978
+ attach(e) {
2979
+ super.attach(e), this.boundReorderRequestHandler = (t) => {
2980
+ const n = t.detail;
2981
+ n?.field && typeof n.toIndex == "number" && this.moveColumn(n.field, n.toIndex);
2982
+ }, e.addEventListener("column-reorder-request", this.boundReorderRequestHandler);
2983
+ }
2984
+ detach() {
2985
+ this.boundReorderRequestHandler && this.grid && (this.grid.removeEventListener(
2986
+ "column-reorder-request",
2987
+ this.boundReorderRequestHandler
2988
+ ), this.boundReorderRequestHandler = null), this.isDragging = !1, this.draggedField = null, this.draggedIndex = null, this.dropIndex = null;
2989
+ }
2990
+ // ===== Hooks =====
2991
+ // Note: No processColumns hook needed - we directly update the grid's column order
2992
+ afterRender() {
2993
+ if (!this.config.enabled) return;
2994
+ const e = this.shadowRoot;
2995
+ if (!e) return;
2996
+ e.querySelectorAll(".header-row > .cell").forEach((n) => {
2997
+ const o = n, r = o.getAttribute("data-field");
2998
+ if (!r) return;
2999
+ const s = this.columns.find((l) => l.field === r);
3000
+ if (!s || !dt(s)) {
3001
+ o.draggable = !1;
3002
+ return;
3003
+ }
3004
+ o.draggable = !0, !o.getAttribute("data-dragstart-bound") && (o.setAttribute("data-dragstart-bound", "true"), o.addEventListener("dragstart", (l) => {
3005
+ const c = this.getColumnOrder().indexOf(r);
3006
+ this.isDragging = !0, this.draggedField = r, this.draggedIndex = c, l.dataTransfer && (l.dataTransfer.effectAllowed = "move", l.dataTransfer.setData("text/plain", r)), o.classList.add("dragging");
3007
+ }), o.addEventListener("dragend", () => {
3008
+ this.isDragging = !1, this.draggedField = null, this.draggedIndex = null, this.dropIndex = null, e.querySelectorAll(".header-row > .cell").forEach((l) => {
3009
+ l.classList.remove("dragging", "drop-target", "drop-before", "drop-after");
3010
+ });
3011
+ }), o.addEventListener("dragover", (l) => {
3012
+ if (l.preventDefault(), !this.isDragging || this.draggedField === r) return;
3013
+ const a = o.getBoundingClientRect(), c = a.left + a.width / 2, u = this.getColumnOrder().indexOf(r);
3014
+ this.dropIndex = l.clientX < c ? u : u + 1, o.classList.add("drop-target"), o.classList.toggle("drop-before", l.clientX < c), o.classList.toggle("drop-after", l.clientX >= c);
3015
+ }), o.addEventListener("dragleave", () => {
3016
+ o.classList.remove("drop-target", "drop-before", "drop-after");
3017
+ }), o.addEventListener("drop", (l) => {
3018
+ l.preventDefault();
3019
+ const a = this.draggedField, c = this.draggedIndex, d = this.dropIndex;
3020
+ if (!this.isDragging || a === null || c === null || d === null)
3021
+ return;
3022
+ const u = d > c ? d - 1 : d, h = this.getColumnOrder(), f = ge(h, c, u), g = {
3023
+ field: a,
3024
+ fromIndex: c,
3025
+ toIndex: u,
3026
+ columnOrder: f
3027
+ };
3028
+ this.grid.setColumnOrder(f), this.emit("column-move", g), this.grid.requestStateChange?.();
3029
+ }));
3030
+ });
3031
+ }
3032
+ // ===== Public API =====
3033
+ /**
3034
+ * Get the current column order from the grid.
3035
+ * @returns Array of field names in display order
3036
+ */
3037
+ getColumnOrder() {
3038
+ return this.grid.getColumnOrder();
3039
+ }
3040
+ /**
3041
+ * Move a column to a new position.
3042
+ * @param field - The field name of the column to move
3043
+ * @param toIndex - The target index
3044
+ */
3045
+ moveColumn(e, t) {
3046
+ const n = this.getColumnOrder(), o = n.indexOf(e);
3047
+ if (o === -1) return;
3048
+ const r = ge(n, o, t);
3049
+ this.grid.setColumnOrder(r), this.emit("column-move", {
3050
+ field: e,
3051
+ fromIndex: o,
3052
+ toIndex: t,
3053
+ columnOrder: r
3054
+ }), this.grid.requestStateChange?.();
3055
+ }
3056
+ /**
3057
+ * Set a specific column order.
3058
+ * @param order - Array of field names in desired order
3059
+ */
3060
+ setColumnOrder(e) {
3061
+ this.grid.setColumnOrder(e), this.grid.requestStateChange?.();
3062
+ }
3063
+ /**
3064
+ * Reset column order to the original configuration order.
3065
+ */
3066
+ resetColumnOrder() {
3067
+ const e = this.columns.map((t) => t.field);
3068
+ this.grid.setColumnOrder(e), this.grid.requestStateChange?.();
3069
+ }
3070
+ // ===== Styles =====
3071
+ styles = `
3072
+ .header-row > .cell[draggable="true"] {
3073
+ cursor: grab;
3074
+ position: relative;
3075
+ }
3076
+ .header-row > .cell.dragging {
3077
+ opacity: 0.5;
3078
+ cursor: grabbing;
3079
+ }
3080
+ .header-row > .cell.drop-before::before {
3081
+ content: '';
3082
+ position: absolute;
3083
+ left: 0;
3084
+ top: 0;
3085
+ bottom: 0;
3086
+ width: 2px;
3087
+ background: var(--tbw-reorder-indicator, var(--tbw-color-accent));
3088
+ z-index: 1;
3089
+ }
3090
+ .header-row > .cell.drop-after::after {
3091
+ content: '';
3092
+ position: absolute;
3093
+ right: 0;
3094
+ top: 0;
3095
+ bottom: 0;
3096
+ width: 2px;
3097
+ background: var(--tbw-reorder-indicator, var(--tbw-color-accent));
3098
+ z-index: 1;
3099
+ }
3100
+ `;
3101
+ }
3102
+ function M(i, e) {
3103
+ return Math.floor(i / e);
3104
+ }
3105
+ function ct(i, e) {
3106
+ return {
3107
+ start: i * e,
3108
+ end: (i + 1) * e
3109
+ };
3110
+ }
3111
+ function ut(i, e, t) {
3112
+ const n = M(i, t), o = M(e - 1, t), r = [];
3113
+ for (let s = n; s <= o; s++)
3114
+ r.push(s);
3115
+ return r;
3116
+ }
3117
+ async function pe(i, e, t, n) {
3118
+ const o = ct(e, t);
3119
+ return i.getRows({
3120
+ startRow: o.start,
3121
+ endRow: o.end,
3122
+ sortModel: n.sortModel,
3123
+ filterModel: n.filterModel
3124
+ });
3125
+ }
3126
+ function ht(i, e, t) {
3127
+ const n = M(i, e), o = t.get(n);
3128
+ if (!o) return;
3129
+ const r = i % e;
3130
+ return o[r];
3131
+ }
3132
+ const ft = 100;
3133
+ class Mt extends C {
3134
+ name = "serverSide";
3135
+ version = "1.0.0";
3136
+ get defaultConfig() {
3137
+ return {
3138
+ enabled: !0,
3139
+ pageSize: 100,
3140
+ cacheBlockSize: 100,
3141
+ maxConcurrentRequests: 2
3142
+ };
3143
+ }
3144
+ // ===== Internal State =====
3145
+ dataSource = null;
3146
+ totalRowCount = 0;
3147
+ loadedBlocks = /* @__PURE__ */ new Map();
3148
+ loadingBlocks = /* @__PURE__ */ new Set();
3149
+ lastRequestId = 0;
3150
+ scrollDebounceTimer;
3151
+ // ===== Lifecycle =====
3152
+ detach() {
3153
+ this.dataSource = null, this.totalRowCount = 0, this.loadedBlocks.clear(), this.loadingBlocks.clear(), this.lastRequestId = 0, this.scrollDebounceTimer && (clearTimeout(this.scrollDebounceTimer), this.scrollDebounceTimer = void 0);
3154
+ }
3155
+ // ===== Private Methods =====
3156
+ /**
3157
+ * Check current viewport and load any missing blocks.
3158
+ */
3159
+ loadRequiredBlocks() {
3160
+ if (!this.dataSource) return;
3161
+ const e = this.grid, t = this.config.cacheBlockSize ?? 100, n = { startRow: e.virtualization.start, endRow: e.virtualization.end }, o = ut(n.startRow, n.endRow, t);
3162
+ for (const r of o)
3163
+ if (!(this.loadedBlocks.has(r) || this.loadingBlocks.has(r))) {
3164
+ if (this.loadingBlocks.size >= (this.config.maxConcurrentRequests ?? 2))
3165
+ break;
3166
+ this.loadingBlocks.add(r), pe(this.dataSource, r, t, {}).then((s) => {
3167
+ this.loadedBlocks.set(r, s.rows), this.totalRowCount = s.totalRowCount, this.loadingBlocks.delete(r), this.requestRender(), this.loadRequiredBlocks();
3168
+ }).catch(() => {
3169
+ this.loadingBlocks.delete(r);
3170
+ });
3171
+ }
3172
+ }
3173
+ // ===== Hooks =====
3174
+ processRows(e) {
3175
+ if (!this.dataSource) return [...e];
3176
+ const t = [];
3177
+ for (let n = 0; n < this.totalRowCount; n++) {
3178
+ const o = ht(n, this.config.cacheBlockSize ?? 100, this.loadedBlocks);
3179
+ t.push(o ?? { __loading: !0, __index: n });
3180
+ }
3181
+ return t;
3182
+ }
3183
+ onScroll(e) {
3184
+ this.dataSource && (this.loadRequiredBlocks(), this.scrollDebounceTimer && clearTimeout(this.scrollDebounceTimer), this.scrollDebounceTimer = setTimeout(() => {
3185
+ this.loadRequiredBlocks();
3186
+ }, ft));
3187
+ }
3188
+ // ===== Public API =====
3189
+ /**
3190
+ * Set the data source for server-side loading.
3191
+ * @param dataSource - Data source implementing the getRows method
3192
+ */
3193
+ setDataSource(e) {
3194
+ this.dataSource = e, this.loadedBlocks.clear(), this.loadingBlocks.clear();
3195
+ const t = this.config.cacheBlockSize ?? 100;
3196
+ pe(e, 0, t, {}).then((n) => {
3197
+ this.loadedBlocks.set(0, n.rows), this.totalRowCount = n.totalRowCount, this.requestRender();
3198
+ });
3199
+ }
3200
+ /**
3201
+ * Refresh all data from the server.
3202
+ */
3203
+ refresh() {
3204
+ this.dataSource && (this.loadedBlocks.clear(), this.loadingBlocks.clear(), this.requestRender());
3205
+ }
3206
+ /**
3207
+ * Clear all cached data without refreshing.
3208
+ */
3209
+ purgeCache() {
3210
+ this.loadedBlocks.clear();
3211
+ }
3212
+ /**
3213
+ * Get the total row count from the server.
3214
+ */
3215
+ getTotalRowCount() {
3216
+ return this.totalRowCount;
3217
+ }
3218
+ /**
3219
+ * Check if a specific row is loaded in the cache.
3220
+ * @param rowIndex - Row index to check
3221
+ */
3222
+ isRowLoaded(e) {
3223
+ const t = this.config.cacheBlockSize ?? 100, n = M(e, t);
3224
+ return this.loadedBlocks.has(n);
3225
+ }
3226
+ /**
3227
+ * Get the number of loaded cache blocks.
3228
+ */
3229
+ getLoadedBlockCount() {
3230
+ return this.loadedBlocks.size;
3231
+ }
3232
+ }
3233
+ function gt(i, e, t) {
3234
+ const n = [...i.undoStack, e];
3235
+ for (; n.length > t; )
3236
+ n.shift();
3237
+ return {
3238
+ undoStack: n,
3239
+ redoStack: []
3240
+ // Clear redo on new action
3241
+ };
3242
+ }
3243
+ function me(i) {
3244
+ if (i.undoStack.length === 0)
3245
+ return { newState: i, action: null };
3246
+ const e = [...i.undoStack], t = e.pop();
3247
+ return t ? {
3248
+ newState: {
3249
+ undoStack: e,
3250
+ redoStack: [...i.redoStack, t]
3251
+ },
3252
+ action: t
3253
+ } : { newState: i, action: null };
3254
+ }
3255
+ function be(i) {
3256
+ if (i.redoStack.length === 0)
3257
+ return { newState: i, action: null };
3258
+ const e = [...i.redoStack], t = e.pop();
3259
+ return t ? {
3260
+ newState: {
3261
+ undoStack: [...i.undoStack, t],
3262
+ redoStack: e
3263
+ },
3264
+ action: t
3265
+ } : { newState: i, action: null };
3266
+ }
3267
+ function pt(i) {
3268
+ return i.undoStack.length > 0;
3269
+ }
3270
+ function mt(i) {
3271
+ return i.redoStack.length > 0;
3272
+ }
3273
+ function bt() {
3274
+ return { undoStack: [], redoStack: [] };
3275
+ }
3276
+ function wt(i, e, t, n) {
3277
+ return {
3278
+ type: "cell-edit",
3279
+ rowIndex: i,
3280
+ field: e,
3281
+ oldValue: t,
3282
+ newValue: n,
3283
+ timestamp: Date.now()
3284
+ };
3285
+ }
3286
+ class qt extends C {
3287
+ name = "undoRedo";
3288
+ version = "1.0.0";
3289
+ get defaultConfig() {
3290
+ return {
3291
+ enabled: !0,
3292
+ maxHistorySize: 100
3293
+ };
3294
+ }
3295
+ // State as class properties
3296
+ undoStack = [];
3297
+ redoStack = [];
3298
+ /**
3299
+ * Clean up state when plugin is detached.
3300
+ */
3301
+ detach() {
3302
+ this.undoStack = [], this.redoStack = [];
3303
+ }
3304
+ /**
3305
+ * Handle keyboard shortcuts for undo/redo.
3306
+ * - Ctrl+Z / Cmd+Z: Undo
3307
+ * - Ctrl+Y / Cmd+Y / Ctrl+Shift+Z / Cmd+Shift+Z: Redo
3308
+ */
3309
+ onKeyDown(e) {
3310
+ if (!this.config.enabled) return !1;
3311
+ const t = (e.ctrlKey || e.metaKey) && e.key === "z" && !e.shiftKey, n = (e.ctrlKey || e.metaKey) && (e.key === "y" || e.key === "z" && e.shiftKey);
3312
+ if (t) {
3313
+ const o = me({ undoStack: this.undoStack, redoStack: this.redoStack });
3314
+ if (o.action) {
3315
+ const r = this.rows;
3316
+ r[o.action.rowIndex] && (r[o.action.rowIndex][o.action.field] = o.action.oldValue), this.undoStack = o.newState.undoStack, this.redoStack = o.newState.redoStack, this.emit("undo", {
3317
+ action: o.action,
3318
+ type: "undo"
3319
+ }), this.requestRender();
3320
+ }
3321
+ return !0;
3322
+ }
3323
+ if (n) {
3324
+ const o = be({ undoStack: this.undoStack, redoStack: this.redoStack });
3325
+ if (o.action) {
3326
+ const r = this.rows;
3327
+ r[o.action.rowIndex] && (r[o.action.rowIndex][o.action.field] = o.action.newValue), this.undoStack = o.newState.undoStack, this.redoStack = o.newState.redoStack, this.emit("redo", {
3328
+ action: o.action,
3329
+ type: "redo"
3330
+ }), this.requestRender();
3331
+ }
3332
+ return !0;
3333
+ }
3334
+ return !1;
3335
+ }
3336
+ // ===== Public API Methods =====
3337
+ /**
3338
+ * Record a cell edit for undo/redo tracking.
3339
+ * Call this when a cell value changes.
3340
+ *
3341
+ * @param rowIndex - The row index where the edit occurred
3342
+ * @param field - The field (column key) that was edited
3343
+ * @param oldValue - The value before the edit
3344
+ * @param newValue - The value after the edit
3345
+ */
3346
+ recordEdit(e, t, n, o) {
3347
+ const r = wt(e, t, n, o), s = gt(
3348
+ { undoStack: this.undoStack, redoStack: this.redoStack },
3349
+ r,
3350
+ this.config.maxHistorySize ?? 100
3351
+ );
3352
+ this.undoStack = s.undoStack, this.redoStack = s.redoStack;
3353
+ }
3354
+ /**
3355
+ * Programmatically undo the last action.
3356
+ *
3357
+ * @returns The undone action, or null if nothing to undo
3358
+ */
3359
+ undo() {
3360
+ const e = me({ undoStack: this.undoStack, redoStack: this.redoStack });
3361
+ if (e.action) {
3362
+ const t = this.rows;
3363
+ t[e.action.rowIndex] && (t[e.action.rowIndex][e.action.field] = e.action.oldValue), this.undoStack = e.newState.undoStack, this.redoStack = e.newState.redoStack, this.requestRender();
3364
+ }
3365
+ return e.action;
3366
+ }
3367
+ /**
3368
+ * Programmatically redo the last undone action.
3369
+ *
3370
+ * @returns The redone action, or null if nothing to redo
3371
+ */
3372
+ redo() {
3373
+ const e = be({ undoStack: this.undoStack, redoStack: this.redoStack });
3374
+ if (e.action) {
3375
+ const t = this.rows;
3376
+ t[e.action.rowIndex] && (t[e.action.rowIndex][e.action.field] = e.action.newValue), this.undoStack = e.newState.undoStack, this.redoStack = e.newState.redoStack, this.requestRender();
3377
+ }
3378
+ return e.action;
3379
+ }
3380
+ /**
3381
+ * Check if there are any actions that can be undone.
3382
+ */
3383
+ canUndo() {
3384
+ return pt({ undoStack: this.undoStack, redoStack: this.redoStack });
3385
+ }
3386
+ /**
3387
+ * Check if there are any actions that can be redone.
3388
+ */
3389
+ canRedo() {
3390
+ return mt({ undoStack: this.undoStack, redoStack: this.redoStack });
3391
+ }
3392
+ /**
3393
+ * Clear all undo/redo history.
3394
+ */
3395
+ clearHistory() {
3396
+ const e = bt();
3397
+ this.undoStack = e.undoStack, this.redoStack = e.redoStack;
3398
+ }
3399
+ /**
3400
+ * Get a copy of the current undo stack.
3401
+ */
3402
+ getUndoStack() {
3403
+ return [...this.undoStack];
3404
+ }
3405
+ /**
3406
+ * Get a copy of the current redo stack.
3407
+ */
3408
+ getRedoStack() {
3409
+ return [...this.redoStack];
3410
+ }
3411
+ }
3412
+ function we(i) {
3413
+ const e = i.meta ?? {};
3414
+ return e.lockPosition !== !0 && e.suppressMovable !== !0;
3415
+ }
3416
+ class _ extends C {
3417
+ name = "visibility";
3418
+ version = "1.0.0";
3419
+ /** Tool panel ID for shell integration */
3420
+ static PANEL_ID = "columns";
3421
+ get defaultConfig() {
3422
+ return {
3423
+ enabled: !0,
3424
+ allowHideAll: !1
3425
+ };
3426
+ }
3427
+ // ===== Internal State =====
3428
+ columnListElement = null;
3429
+ // Drag state for reorder integration
3430
+ isDragging = !1;
3431
+ draggedField = null;
3432
+ draggedIndex = null;
3433
+ dropIndex = null;
3434
+ // ===== Lifecycle =====
3435
+ detach() {
3436
+ this.columnListElement = null, this.isDragging = !1, this.draggedField = null, this.draggedIndex = null, this.dropIndex = null;
3437
+ }
3438
+ // ===== Shell Integration =====
3439
+ /**
3440
+ * Register the column visibility tool panel with the shell.
3441
+ */
3442
+ getToolPanel() {
3443
+ if (this.config.enabled)
3444
+ return {
3445
+ id: _.PANEL_ID,
3446
+ title: "Columns",
3447
+ icon: "☰",
3448
+ tooltip: "Column visibility",
3449
+ order: 100,
3450
+ // High order so it appears last
3451
+ render: (e) => this.renderPanelContent(e)
3452
+ };
3453
+ }
3454
+ // ===== Public API =====
3455
+ /**
3456
+ * Show the visibility sidebar panel.
3457
+ */
3458
+ show() {
3459
+ this.grid.openToolPanel(_.PANEL_ID);
3460
+ }
3461
+ /**
3462
+ * Hide the visibility sidebar panel.
3463
+ */
3464
+ hide() {
3465
+ this.grid.closeToolPanel();
3466
+ }
3467
+ /**
3468
+ * Toggle the visibility sidebar panel.
3469
+ */
3470
+ toggle() {
3471
+ this.grid.toggleToolPanel(_.PANEL_ID);
3472
+ }
3473
+ /**
3474
+ * Check if a specific column is visible.
3475
+ * Delegates to grid.isColumnVisible().
3476
+ * @param field - The field name to check
3477
+ * @returns True if the column is visible
3478
+ */
3479
+ isColumnVisible(e) {
3480
+ return this.grid.isColumnVisible(e);
3481
+ }
3482
+ /**
3483
+ * Set visibility for a specific column.
3484
+ * Delegates to grid.setColumnVisible().
3485
+ * @param field - The field name of the column
3486
+ * @param visible - Whether the column should be visible
3487
+ */
3488
+ setColumnVisible(e, t) {
3489
+ this.grid.setColumnVisible(e, t);
3490
+ }
3491
+ /**
3492
+ * Get list of all visible column fields.
3493
+ * @returns Array of visible field names
3494
+ */
3495
+ getVisibleColumns() {
3496
+ return this.grid.getAllColumns().filter((t) => t.visible).map((t) => t.field);
3497
+ }
3498
+ /**
3499
+ * Get list of all hidden column fields.
3500
+ * @returns Array of hidden field names
3501
+ */
3502
+ getHiddenColumns() {
3503
+ return this.grid.getAllColumns().filter((t) => !t.visible).map((t) => t.field);
3504
+ }
3505
+ /**
3506
+ * Show all columns.
3507
+ * Delegates to grid.showAllColumns().
3508
+ */
3509
+ showAll() {
3510
+ this.grid.showAllColumns();
3511
+ }
3512
+ /**
3513
+ * Toggle visibility for a specific column.
3514
+ * Delegates to grid.toggleColumnVisibility().
3515
+ * @param field - The field name of the column
3516
+ */
3517
+ toggleColumn(e) {
3518
+ this.grid.toggleColumnVisibility(e);
3519
+ }
3520
+ /**
3521
+ * Show a specific column.
3522
+ * Delegates to grid.setColumnVisible().
3523
+ * @param field - The field name of the column to show
3524
+ */
3525
+ showColumn(e) {
3526
+ this.grid.setColumnVisible(e, !0);
3527
+ }
3528
+ /**
3529
+ * Hide a specific column.
3530
+ * Delegates to grid.setColumnVisible().
3531
+ * @param field - The field name of the column to hide
3532
+ */
3533
+ hideColumn(e) {
3534
+ this.grid.setColumnVisible(e, !1);
3535
+ }
3536
+ /**
3537
+ * Get all columns with their visibility status.
3538
+ * Useful for building visibility UI.
3539
+ * @returns Array of column info with visibility status
3540
+ */
3541
+ getAllColumns() {
3542
+ return this.grid.getAllColumns();
3543
+ }
3544
+ /**
3545
+ * Check if the sidebar panel is currently open.
3546
+ * @returns True if the panel is open
3547
+ */
3548
+ isPanelVisible() {
3549
+ return this.grid.activeToolPanel === _.PANEL_ID;
3550
+ }
3551
+ // ===== Private Methods =====
3552
+ /**
3553
+ * Render the panel content into the shell's tool panel container.
3554
+ * Returns a cleanup function.
3555
+ */
3556
+ renderPanelContent(e) {
3557
+ const t = this.grid, n = document.createElement("div");
3558
+ n.className = "tbw-visibility-content";
3559
+ const o = document.createElement("div");
3560
+ o.className = "tbw-visibility-list", n.appendChild(o);
3561
+ const r = document.createElement("button");
3562
+ return r.className = "tbw-visibility-show-all", r.textContent = "Show All", r.addEventListener("click", () => {
3563
+ t.showAllColumns(), this.rebuildToggles(o);
3564
+ }), n.appendChild(r), this.columnListElement = o, this.rebuildToggles(o), e.appendChild(n), () => {
3565
+ this.columnListElement = null, n.remove();
3566
+ };
3567
+ }
3568
+ /**
3569
+ * Check if a reorder plugin is present (by name to avoid static import).
3570
+ */
3571
+ hasReorderPlugin() {
3572
+ const e = this.grid?.getPluginByName?.("reorder");
3573
+ return !!(e && typeof e.moveColumn == "function");
3574
+ }
3575
+ /**
3576
+ * Build the column toggle checkboxes.
3577
+ * When a reorder plugin is present, adds drag handles for reordering.
3578
+ */
3579
+ rebuildToggles(e) {
3580
+ const t = this.grid, n = this.hasReorderPlugin();
3581
+ e.innerHTML = "";
3582
+ const o = t.getAllColumns();
3583
+ for (let r = 0; r < o.length; r++) {
3584
+ const s = o[r], l = s.header || s.field, a = document.createElement("div");
3585
+ a.className = s.lockVisible ? "tbw-visibility-row locked" : "tbw-visibility-row", a.setAttribute("data-field", s.field), a.setAttribute("data-index", String(r)), n && we(s) && (a.draggable = !0, a.classList.add("reorderable"), this.setupDragListeners(a, s.field, r, e));
3586
+ const c = document.createElement("label");
3587
+ c.className = "tbw-visibility-label";
3588
+ const d = document.createElement("input");
3589
+ d.type = "checkbox", d.checked = s.visible, d.disabled = s.lockVisible ?? !1, d.addEventListener("change", () => {
3590
+ t.toggleColumnVisibility(s.field), setTimeout(() => this.rebuildToggles(e), 0);
3591
+ });
3592
+ const u = document.createElement("span");
3593
+ if (u.textContent = l, c.appendChild(d), c.appendChild(u), n && we(s)) {
3594
+ const h = document.createElement("span");
3595
+ h.className = "tbw-visibility-handle", h.textContent = "⋮⋮", h.title = "Drag to reorder", a.appendChild(h);
3596
+ }
3597
+ a.appendChild(c), e.appendChild(a);
3598
+ }
3599
+ }
3600
+ /**
3601
+ * Set up drag-and-drop event listeners for a row.
3602
+ * On drop, emits a 'column-reorder-request' event for other plugins to handle.
3603
+ */
3604
+ setupDragListeners(e, t, n, o) {
3605
+ e.addEventListener("dragstart", (r) => {
3606
+ this.isDragging = !0, this.draggedField = t, this.draggedIndex = n, r.dataTransfer && (r.dataTransfer.effectAllowed = "move", r.dataTransfer.setData("text/plain", t)), e.classList.add("dragging");
3607
+ }), e.addEventListener("dragend", () => {
3608
+ this.isDragging = !1, this.draggedField = null, this.draggedIndex = null, this.dropIndex = null, o.querySelectorAll(".tbw-visibility-row").forEach((r) => {
3609
+ r.classList.remove("dragging", "drop-target", "drop-before", "drop-after");
3610
+ });
3611
+ }), e.addEventListener("dragover", (r) => {
3612
+ if (r.preventDefault(), !this.isDragging || this.draggedField === t) return;
3613
+ const s = e.getBoundingClientRect(), l = s.top + s.height / 2;
3614
+ this.dropIndex = r.clientY < l ? n : n + 1, o.querySelectorAll(".tbw-visibility-row").forEach((a) => {
3615
+ a !== e && a.classList.remove("drop-target", "drop-before", "drop-after");
3616
+ }), e.classList.add("drop-target"), e.classList.toggle("drop-before", r.clientY < l), e.classList.toggle("drop-after", r.clientY >= l);
3617
+ }), e.addEventListener("dragleave", () => {
3618
+ e.classList.remove("drop-target", "drop-before", "drop-after");
3619
+ }), e.addEventListener("drop", (r) => {
3620
+ r.preventDefault();
3621
+ const s = this.draggedField, l = this.draggedIndex, a = this.dropIndex;
3622
+ if (!this.isDragging || s === null || l === null || a === null)
3623
+ return;
3624
+ const c = a > l ? a - 1 : a;
3625
+ if (c !== l) {
3626
+ const d = {
3627
+ field: s,
3628
+ fromIndex: l,
3629
+ toIndex: c
3630
+ };
3631
+ this.emit("column-reorder-request", d), setTimeout(() => {
3632
+ this.rebuildToggles(o);
3633
+ }, 0);
3634
+ }
3635
+ });
3636
+ }
3637
+ // ===== Styles =====
3638
+ styles = `
3639
+ .tbw-visibility-content {
3640
+ display: flex;
3641
+ flex-direction: column;
3642
+ height: 100%;
3643
+ }
3644
+
3645
+ .tbw-visibility-list {
3646
+ flex: 1;
3647
+ overflow-y: auto;
3648
+ padding: 8px;
3649
+ }
3650
+
3651
+ .tbw-visibility-row {
3652
+ display: flex;
3653
+ align-items: center;
3654
+ gap: 8px;
3655
+ padding: 6px 4px;
3656
+ cursor: pointer;
3657
+ font-size: 13px;
3658
+ border-radius: var(--tbw-border-radius, 4px);
3659
+ position: relative;
3660
+ }
3661
+ .tbw-visibility-row:hover {
3662
+ background: var(--tbw-visibility-hover, var(--tbw-color-row-hover, #f3f4f6));
3663
+ }
3664
+ .tbw-visibility-row input[type="checkbox"] {
3665
+ cursor: pointer;
3666
+ }
3667
+ .tbw-visibility-row.locked span {
3668
+ color: var(--tbw-color-fg-muted, #888);
3669
+ }
3670
+
3671
+ /* Drag handle */
3672
+ .tbw-visibility-handle {
3673
+ cursor: grab;
3674
+ color: var(--tbw-color-fg-muted, #888);
3675
+ font-size: 10px;
3676
+ letter-spacing: -2px;
3677
+ user-select: none;
3678
+ flex-shrink: 0;
3679
+ }
3680
+ .tbw-visibility-row.reorderable:hover .tbw-visibility-handle {
3681
+ color: var(--tbw-color-fg, #1f2937);
3682
+ }
3683
+
3684
+ /* Label wrapper for checkbox and text */
3685
+ .tbw-visibility-label {
3686
+ display: flex;
3687
+ align-items: center;
3688
+ gap: 8px;
3689
+ flex: 1;
3690
+ cursor: pointer;
3691
+ }
3692
+
3693
+ /* Drag states */
3694
+ .tbw-visibility-row.dragging {
3695
+ opacity: 0.5;
3696
+ cursor: grabbing;
3697
+ }
3698
+ .tbw-visibility-row.drop-before::before {
3699
+ content: '';
3700
+ position: absolute;
3701
+ left: 0;
3702
+ right: 0;
3703
+ top: 0;
3704
+ height: 2px;
3705
+ background: var(--tbw-reorder-indicator, var(--tbw-color-accent, #3b82f6));
3706
+ }
3707
+ .tbw-visibility-row.drop-after::after {
3708
+ content: '';
3709
+ position: absolute;
3710
+ left: 0;
3711
+ right: 0;
3712
+ bottom: 0;
3713
+ height: 2px;
3714
+ background: var(--tbw-reorder-indicator, var(--tbw-color-accent, #3b82f6));
3715
+ }
3716
+
3717
+ .tbw-visibility-show-all {
3718
+ margin: 8px;
3719
+ padding: 8px 12px;
3720
+ border: 1px solid var(--tbw-visibility-border, var(--tbw-color-border, #e5e7eb));
3721
+ border-radius: var(--tbw-border-radius, 4px);
3722
+ background: var(--tbw-visibility-btn-bg, var(--tbw-color-header-bg, #f9fafb));
3723
+ color: var(--tbw-color-fg, #1f2937);
3724
+ cursor: pointer;
3725
+ font-size: 13px;
3726
+ }
3727
+ .tbw-visibility-show-all:hover {
3728
+ background: var(--tbw-visibility-hover, var(--tbw-color-row-hover, #f3f4f6));
3729
+ }
3730
+ `;
3731
+ }
3732
+ export {
3733
+ C as BaseGridPlugin,
3734
+ vt as ClipboardPlugin,
3735
+ Ct as ColumnVirtualizationPlugin,
3736
+ yt as ContextMenuPlugin,
3737
+ Pt as DGEvents,
3738
+ Dt as DataGridElement,
3739
+ St as ExportPlugin,
3740
+ E as FilteringPlugin,
3741
+ Bt as FitModeEnum,
3742
+ Ht as GridCSSVars,
3743
+ Ot as GridClasses,
3744
+ Wt as GridDataAttrs,
3745
+ Kt as GridElement,
3746
+ Vt as GridSelectors,
3747
+ Rt as GroupingColumnsPlugin,
3748
+ kt as GroupingRowsPlugin,
3749
+ Et as MasterDetailPlugin,
3750
+ At as MultiSortPlugin,
3751
+ _t as PinnedColumnsPlugin,
3752
+ It as PinnedRowsPlugin,
3753
+ Tt as PivotPlugin,
3754
+ Gt as PluginEvents,
3755
+ Lt as ReorderPlugin,
3756
+ zt as SelectionPlugin,
3757
+ Mt as ServerSidePlugin,
3758
+ $t as TreePlugin,
3759
+ qt as UndoRedoPlugin,
3760
+ _ as VisibilityPlugin
3761
+ };
3762
+ //# sourceMappingURL=all.js.map