@toolbox-web/grid 1.21.0 → 1.21.2

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 (109) hide show
  1. package/all.js +2 -6850
  2. package/all.js.map +1 -1
  3. package/index.js +1 -4352
  4. package/index.js.map +1 -1
  5. package/lib/core/grid.d.ts +22 -6
  6. package/lib/core/grid.d.ts.map +1 -1
  7. package/lib/core/styles/index.d.ts.map +1 -1
  8. package/lib/plugins/clipboard/index.js +1 -733
  9. package/lib/plugins/clipboard/index.js.map +1 -1
  10. package/lib/plugins/column-virtualization/index.js +1 -560
  11. package/lib/plugins/column-virtualization/index.js.map +1 -1
  12. package/lib/plugins/context-menu/index.js +1 -754
  13. package/lib/plugins/context-menu/index.js.map +1 -1
  14. package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
  15. package/lib/plugins/editing/index.d.ts +1 -1
  16. package/lib/plugins/editing/index.d.ts.map +1 -1
  17. package/lib/plugins/editing/index.js +1 -1539
  18. package/lib/plugins/editing/index.js.map +1 -1
  19. package/lib/plugins/editing/types.d.ts +23 -0
  20. package/lib/plugins/editing/types.d.ts.map +1 -1
  21. package/lib/plugins/export/index.js +1 -589
  22. package/lib/plugins/export/index.js.map +1 -1
  23. package/lib/plugins/filtering/FilteringPlugin.d.ts.map +1 -1
  24. package/lib/plugins/filtering/filter-model.d.ts.map +1 -1
  25. package/lib/plugins/filtering/index.js +1 -1283
  26. package/lib/plugins/filtering/index.js.map +1 -1
  27. package/lib/plugins/filtering/types.d.ts +4 -2
  28. package/lib/plugins/filtering/types.d.ts.map +1 -1
  29. package/lib/plugins/grouping-columns/index.js +1 -726
  30. package/lib/plugins/grouping-columns/index.js.map +1 -1
  31. package/lib/plugins/grouping-rows/index.js +2 -905
  32. package/lib/plugins/grouping-rows/index.js.map +1 -1
  33. package/lib/plugins/master-detail/index.js +1 -950
  34. package/lib/plugins/master-detail/index.js.map +1 -1
  35. package/lib/plugins/multi-sort/index.js +1 -553
  36. package/lib/plugins/multi-sort/index.js.map +1 -1
  37. package/lib/plugins/pinned-columns/index.js +1 -688
  38. package/lib/plugins/pinned-columns/index.js.map +1 -1
  39. package/lib/plugins/pinned-rows/index.js +1 -704
  40. package/lib/plugins/pinned-rows/index.js.map +1 -1
  41. package/lib/plugins/pivot/index.js +1 -1191
  42. package/lib/plugins/pivot/index.js.map +1 -1
  43. package/lib/plugins/print/index.js +1 -691
  44. package/lib/plugins/print/index.js.map +1 -1
  45. package/lib/plugins/reorder/index.js +1 -703
  46. package/lib/plugins/reorder/index.js.map +1 -1
  47. package/lib/plugins/responsive/index.js +1 -971
  48. package/lib/plugins/responsive/index.js.map +1 -1
  49. package/lib/plugins/row-reorder/index.js +1 -728
  50. package/lib/plugins/row-reorder/index.js.map +1 -1
  51. package/lib/plugins/selection/index.js +1 -1071
  52. package/lib/plugins/selection/index.js.map +1 -1
  53. package/lib/plugins/server-side/index.js +1 -521
  54. package/lib/plugins/server-side/index.js.map +1 -1
  55. package/lib/plugins/tree/index.js +1 -686
  56. package/lib/plugins/tree/index.js.map +1 -1
  57. package/lib/plugins/undo-redo/index.js +1 -584
  58. package/lib/plugins/undo-redo/index.js.map +1 -1
  59. package/lib/plugins/visibility/index.js +1 -792
  60. package/lib/plugins/visibility/index.js.map +1 -1
  61. package/package.json +4 -5
  62. package/umd/grid.all.umd.js +1 -186
  63. package/umd/grid.all.umd.js.map +1 -1
  64. package/umd/grid.umd.js +1 -90
  65. package/umd/grid.umd.js.map +1 -1
  66. package/umd/plugins/clipboard.umd.js +1 -6
  67. package/umd/plugins/clipboard.umd.js.map +1 -1
  68. package/umd/plugins/column-virtualization.umd.js +1 -1
  69. package/umd/plugins/column-virtualization.umd.js.map +1 -1
  70. package/umd/plugins/context-menu.umd.js +1 -1
  71. package/umd/plugins/context-menu.umd.js.map +1 -1
  72. package/umd/plugins/editing.umd.js +1 -1
  73. package/umd/plugins/editing.umd.js.map +1 -1
  74. package/umd/plugins/export.umd.js +1 -13
  75. package/umd/plugins/export.umd.js.map +1 -1
  76. package/umd/plugins/filtering.umd.js +1 -1
  77. package/umd/plugins/filtering.umd.js.map +1 -1
  78. package/umd/plugins/grouping-columns.umd.js +1 -1
  79. package/umd/plugins/grouping-columns.umd.js.map +1 -1
  80. package/umd/plugins/grouping-rows.umd.js +1 -4
  81. package/umd/plugins/grouping-rows.umd.js.map +1 -1
  82. package/umd/plugins/master-detail.umd.js +1 -1
  83. package/umd/plugins/master-detail.umd.js.map +1 -1
  84. package/umd/plugins/multi-sort.umd.js +1 -1
  85. package/umd/plugins/multi-sort.umd.js.map +1 -1
  86. package/umd/plugins/pinned-columns.umd.js +1 -1
  87. package/umd/plugins/pinned-columns.umd.js.map +1 -1
  88. package/umd/plugins/pinned-rows.umd.js +1 -1
  89. package/umd/plugins/pinned-rows.umd.js.map +1 -1
  90. package/umd/plugins/pivot.umd.js +1 -1
  91. package/umd/plugins/pivot.umd.js.map +1 -1
  92. package/umd/plugins/print.umd.js +1 -75
  93. package/umd/plugins/print.umd.js.map +1 -1
  94. package/umd/plugins/reorder.umd.js +1 -1
  95. package/umd/plugins/reorder.umd.js.map +1 -1
  96. package/umd/plugins/responsive.umd.js +1 -1
  97. package/umd/plugins/responsive.umd.js.map +1 -1
  98. package/umd/plugins/row-reorder.umd.js +1 -1
  99. package/umd/plugins/row-reorder.umd.js.map +1 -1
  100. package/umd/plugins/selection.umd.js +1 -3
  101. package/umd/plugins/selection.umd.js.map +1 -1
  102. package/umd/plugins/server-side.umd.js +1 -1
  103. package/umd/plugins/server-side.umd.js.map +1 -1
  104. package/umd/plugins/tree.umd.js +1 -1
  105. package/umd/plugins/tree.umd.js.map +1 -1
  106. package/umd/plugins/undo-redo.umd.js +1 -1
  107. package/umd/plugins/undo-redo.umd.js.map +1 -1
  108. package/umd/plugins/visibility.umd.js +1 -1
  109. package/umd/plugins/visibility.umd.js.map +1 -1
@@ -1,972 +1,2 @@
1
- const y = /{{\s*([^}]+)\s*}}/g, u = "__DG_EMPTY__", _ = /^[\w$. '?+\-*/%:()!<>=,&|]+$/, C = /__(proto|defineGetter|defineSetter)|constructor|window|globalThis|global|process|Function|import|eval|Reflect|Proxy|Error|arguments|document|location|cookie|localStorage|sessionStorage|indexedDB|fetch|XMLHttpRequest|WebSocket|Worker|SharedWorker|ServiceWorker|opener|parent|top|frames|self|this\b/, E = /* @__PURE__ */ new Set([
2
- "script",
3
- "iframe",
4
- "object",
5
- "embed",
6
- "form",
7
- "input",
8
- "button",
9
- "textarea",
10
- "select",
11
- "link",
12
- "meta",
13
- "base",
14
- "style",
15
- "template",
16
- "slot",
17
- "portal",
18
- "frame",
19
- "frameset",
20
- "applet",
21
- "noscript",
22
- "noembed",
23
- "plaintext",
24
- "xmp",
25
- "listing"
26
- ]), v = /^on\w+$/i, A = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "data", "srcdoc", "xlink:href", "poster", "srcset"]), S = /^\s*(javascript|vbscript|data|blob):/i;
27
- function k(r) {
28
- if (!r || typeof r != "string") return "";
29
- if (r.indexOf("<") === -1) return r;
30
- const t = document.createElement("template");
31
- return t.innerHTML = r, H(t.content), t.innerHTML;
32
- }
33
- function H(r) {
34
- const t = [], e = r.querySelectorAll("*");
35
- for (const i of e) {
36
- const n = i.tagName.toLowerCase();
37
- if (E.has(n)) {
38
- t.push(i);
39
- continue;
40
- }
41
- if ((n === "svg" || i.namespaceURI === "http://www.w3.org/2000/svg") && Array.from(i.attributes).some(
42
- (o) => v.test(o.name) || o.name === "href" || o.name === "xlink:href"
43
- )) {
44
- t.push(i);
45
- continue;
46
- }
47
- const l = [];
48
- for (const s of i.attributes) {
49
- const o = s.name.toLowerCase();
50
- if (v.test(o)) {
51
- l.push(s.name);
52
- continue;
53
- }
54
- if (A.has(o) && S.test(s.value)) {
55
- l.push(s.name);
56
- continue;
57
- }
58
- if (o === "style" && /expression\s*\(|javascript:|behavior\s*:/i.test(s.value)) {
59
- l.push(s.name);
60
- continue;
61
- }
62
- }
63
- l.forEach((s) => i.removeAttribute(s));
64
- }
65
- t.forEach((i) => i.remove());
66
- }
67
- function T(r, t) {
68
- if (!r || r.indexOf("{{") === -1) return r;
69
- const e = [], i = r.replace(y, (o, c) => {
70
- const a = L(c, t);
71
- return e.push({ expr: c.trim(), result: a }), a;
72
- }), n = x(i), l = e.length && e.every((o) => o.result === "" || o.result === u);
73
- return w.test(r) || l ? "" : n;
74
- }
75
- function L(r, t) {
76
- if (r = (r || "").trim(), !r || w.test(r)) return u;
77
- if (r === "value") return t.value == null ? u : String(t.value);
78
- if (r.startsWith("row.") && !/[()?]/.test(r) && !r.includes(":")) {
79
- const i = r.slice(4), n = t.row ? t.row[i] : void 0;
80
- return n == null ? u : String(n);
81
- }
82
- if (r.length > 80 || !_.test(r) || C.test(r)) return u;
83
- const e = r.match(/\./g);
84
- if (e && e.length > 1) return u;
85
- try {
86
- const n = new Function("value", "row", `return (${r});`)(t.value, t.row), l = n == null ? "" : String(n);
87
- return w.test(l) ? u : l || u;
88
- } catch {
89
- return u;
90
- }
91
- }
92
- const w = /Reflect|Proxy|ownKeys/;
93
- function x(r) {
94
- return r && r.replace(new RegExp(u, "g"), "").replace(/Reflect\.[^<>{}\s]+|\bProxy\b|ownKeys\([^)]*\)/g, "");
95
- }
96
- function M(r) {
97
- r && r.querySelectorAll(".cell-focus").forEach((t) => t.classList.remove("cell-focus"));
98
- }
99
- const D = 'input,select,textarea,[contenteditable="true"],[contenteditable=""],[tabindex]:not([tabindex="-1"])', q = document.createElement("template");
100
- q.innerHTML = '<div class="cell" role="gridcell" part="cell"></div>';
101
- const N = document.createElement("template");
102
- N.innerHTML = '<div class="data-grid-row" role="row" part="row"></div>';
103
- function g(r, t) {
104
- if (r._virtualization?.enabled) {
105
- const { rowHeight: s, container: o, viewportEl: c } = r._virtualization, a = o, h = c?.clientHeight ?? a?.clientHeight ?? 0;
106
- if (a && h > 0) {
107
- const d = r._focusRow * s;
108
- d < a.scrollTop ? a.scrollTop = d : d + s > a.scrollTop + h && (a.scrollTop = d - h + s);
109
- }
110
- }
111
- const e = r._activeEditRows !== void 0 && r._activeEditRows !== -1;
112
- e || r.refreshVirtualWindow(!1), M(r._bodyEl), Array.from(r._bodyEl.querySelectorAll('[aria-selected="true"]')).forEach((s) => {
113
- s.setAttribute("aria-selected", "false");
114
- });
115
- const i = r._focusRow, n = r._virtualization.start ?? 0, l = r._virtualization.end ?? r._rows.length;
116
- if (i >= n && i < l) {
117
- const s = r._bodyEl.querySelectorAll(".data-grid-row")[i - n];
118
- let o = s?.children[r._focusCol];
119
- if ((!o || !o.classList?.contains("cell")) && (o = s?.querySelector(`.cell[data-col="${r._focusCol}"]`) ?? s?.querySelector(".cell[data-col]")), o) {
120
- o.classList.add("cell-focus"), o.setAttribute("aria-selected", "true");
121
- const c = r.querySelector(".tbw-scroll-area");
122
- if (c && o && (!e || t?.forceHorizontalScroll)) {
123
- const a = r._getHorizontalScrollOffsets?.(s ?? void 0, o) ?? { left: 0, right: 0 };
124
- if (!a.skipScroll) {
125
- const h = o.getBoundingClientRect(), d = c.getBoundingClientRect(), f = h.left - d.left + c.scrollLeft, m = f + h.width, p = c.scrollLeft + a.left, R = c.scrollLeft + c.clientWidth - a.right;
126
- f < p ? c.scrollLeft = f - a.left : m > R && (c.scrollLeft = m - c.clientWidth + a.right);
127
- }
128
- }
129
- if (e && o.classList.contains("editing")) {
130
- const a = o.querySelector(D);
131
- if (a && document.activeElement !== a)
132
- try {
133
- a.focus({ preventScroll: !0 });
134
- } catch {
135
- }
136
- } else if (e && !o.contains(document.activeElement)) {
137
- o.hasAttribute("tabindex") || o.setAttribute("tabindex", "-1");
138
- try {
139
- o.focus({ preventScroll: !0 });
140
- } catch {
141
- }
142
- } else if (!e) {
143
- const a = r;
144
- document.activeElement !== a && a.focus({ preventScroll: !0 });
145
- }
146
- }
147
- }
148
- }
149
- const b = '<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>', I = {
150
- expand: "▶",
151
- collapse: "▼",
152
- sortAsc: "▲",
153
- sortDesc: "▼",
154
- sortNone: "⇅",
155
- submenuArrow: "▶",
156
- dragHandle: "⋮⋮",
157
- toolPanel: "☰",
158
- filter: b,
159
- filterActive: b,
160
- print: "🖨️"
161
- };
162
- class z {
163
- /**
164
- * Plugin dependencies - declare other plugins this one requires.
165
- *
166
- * Dependencies are validated when the plugin is attached.
167
- * Required dependencies throw an error if missing.
168
- * Optional dependencies log an info message if missing.
169
- *
170
- * @example
171
- * ```typescript
172
- * static readonly dependencies: PluginDependency[] = [
173
- * { name: 'editing', required: true, reason: 'Tracks cell edits for undo/redo' },
174
- * { name: 'selection', required: false, reason: 'Enables selection-based undo' },
175
- * ];
176
- * ```
177
- */
178
- static dependencies;
179
- /**
180
- * Plugin manifest - declares owned properties, config rules, and hook priorities.
181
- *
182
- * This is read by the configuration validator to:
183
- * - Validate that required plugins are loaded when their properties are used
184
- * - Execute configRules to detect invalid/conflicting settings
185
- * - Order hook execution based on priority
186
- *
187
- * @example
188
- * ```typescript
189
- * static override readonly manifest: PluginManifest<MyConfig> = {
190
- * ownedProperties: [
191
- * { property: 'myProp', level: 'column', description: 'the "myProp" column property' },
192
- * ],
193
- * configRules: [
194
- * { id: 'myPlugin/conflict', severity: 'warn', message: '...', check: (c) => c.a && c.b },
195
- * ],
196
- * };
197
- * ```
198
- */
199
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
200
- static manifest;
201
- /**
202
- * Plugin version - defaults to grid version for built-in plugins.
203
- * Third-party plugins can override with their own semver.
204
- */
205
- version = typeof __GRID_VERSION__ < "u" ? __GRID_VERSION__ : "dev";
206
- /** CSS styles to inject into the grid's shadow DOM */
207
- styles;
208
- /** Custom cell renderers keyed by type name */
209
- cellRenderers;
210
- /** Custom header renderers keyed by type name */
211
- headerRenderers;
212
- /** Custom cell editors keyed by type name */
213
- cellEditors;
214
- /** The grid instance this plugin is attached to */
215
- grid;
216
- /** Plugin configuration - merged with defaults in attach() */
217
- config;
218
- /** User-provided configuration from constructor */
219
- userConfig;
220
- /**
221
- * Plugin-level AbortController for event listener cleanup.
222
- * Created fresh in attach(), aborted in detach().
223
- * This ensures event listeners are properly cleaned up when plugins are re-attached.
224
- */
225
- #e;
226
- /**
227
- * Default configuration - subclasses should override this getter.
228
- * Note: This must be a getter (not property initializer) for proper inheritance
229
- * since property initializers run after parent constructor.
230
- */
231
- get defaultConfig() {
232
- return {};
233
- }
234
- constructor(t = {}) {
235
- this.userConfig = t;
236
- }
237
- /**
238
- * Called when the plugin is attached to a grid.
239
- * Override to set up event listeners, initialize state, etc.
240
- *
241
- * @example
242
- * ```ts
243
- * attach(grid: GridElement): void {
244
- * super.attach(grid);
245
- * // Set up document-level listeners with auto-cleanup
246
- * document.addEventListener('keydown', this.handleEscape, {
247
- * signal: this.disconnectSignal
248
- * });
249
- * }
250
- * ```
251
- */
252
- attach(t) {
253
- this.#e?.abort(), this.#e = new AbortController(), this.grid = t, this.config = { ...this.defaultConfig, ...this.userConfig };
254
- }
255
- /**
256
- * Called when the plugin is detached from a grid.
257
- * Override to clean up event listeners, timers, etc.
258
- *
259
- * @example
260
- * ```ts
261
- * detach(): void {
262
- * // Clean up any state not handled by disconnectSignal
263
- * this.selectedRows.clear();
264
- * this.cache = null;
265
- * }
266
- * ```
267
- */
268
- detach() {
269
- this.#e?.abort(), this.#e = void 0;
270
- }
271
- /**
272
- * Get another plugin instance from the same grid.
273
- * Use for inter-plugin communication.
274
- *
275
- * @example
276
- * ```ts
277
- * const selection = this.getPlugin(SelectionPlugin);
278
- * if (selection) {
279
- * const selectedRows = selection.getSelectedRows();
280
- * }
281
- * ```
282
- */
283
- getPlugin(t) {
284
- return this.grid?.getPlugin(t);
285
- }
286
- /**
287
- * Emit a custom event from the grid.
288
- */
289
- emit(t, e) {
290
- this.grid?.dispatchEvent?.(new CustomEvent(t, { detail: e, bubbles: !0 }));
291
- }
292
- /**
293
- * Emit a cancelable custom event from the grid.
294
- * @returns `true` if the event was cancelled (preventDefault called), `false` otherwise
295
- */
296
- emitCancelable(t, e) {
297
- const i = new CustomEvent(t, { detail: e, bubbles: !0, cancelable: !0 });
298
- return this.grid?.dispatchEvent?.(i), i.defaultPrevented;
299
- }
300
- // =========================================================================
301
- // Event Bus - Plugin-to-Plugin Communication
302
- // =========================================================================
303
- /**
304
- * Subscribe to an event from another plugin.
305
- * The subscription is automatically cleaned up when this plugin is detached.
306
- *
307
- * @category Plugin Development
308
- * @param eventType - The event type to listen for (e.g., 'filter-change')
309
- * @param callback - The callback to invoke when the event is emitted
310
- *
311
- * @example
312
- * ```typescript
313
- * // In attach() or other initialization
314
- * this.on('filter-change', (detail) => {
315
- * console.log('Filter changed:', detail);
316
- * });
317
- * ```
318
- */
319
- on(t, e) {
320
- this.grid?._pluginManager?.subscribe(this, t, e);
321
- }
322
- /**
323
- * Unsubscribe from a plugin event.
324
- *
325
- * @category Plugin Development
326
- * @param eventType - The event type to stop listening for
327
- *
328
- * @example
329
- * ```typescript
330
- * this.off('filter-change');
331
- * ```
332
- */
333
- off(t) {
334
- this.grid?._pluginManager?.unsubscribe(this, t);
335
- }
336
- /**
337
- * Emit an event to other plugins via the Event Bus.
338
- * This is for inter-plugin communication only; it does NOT dispatch DOM events.
339
- * Use `emit()` to dispatch DOM events that external consumers can listen to.
340
- *
341
- * @category Plugin Development
342
- * @param eventType - The event type to emit (should be declared in manifest.events)
343
- * @param detail - The event payload
344
- *
345
- * @example
346
- * ```typescript
347
- * // Emit to other plugins (not DOM)
348
- * this.emitPluginEvent('filter-change', { field: 'name', value: 'Alice' });
349
- *
350
- * // For DOM events that consumers can addEventListener to:
351
- * this.emit('filter-change', { field: 'name', value: 'Alice' });
352
- * ```
353
- */
354
- emitPluginEvent(t, e) {
355
- this.grid?._pluginManager?.emitPluginEvent(t, e);
356
- }
357
- /**
358
- * Request a re-render of the grid.
359
- * Uses ROWS phase - does NOT trigger processColumns hooks.
360
- */
361
- requestRender() {
362
- this.grid?.requestRender?.();
363
- }
364
- /**
365
- * Request a columns re-render of the grid.
366
- * Uses COLUMNS phase - triggers processColumns hooks.
367
- * Use this when your plugin needs to reprocess column configuration.
368
- */
369
- requestColumnsRender() {
370
- this.grid?.requestColumnsRender?.();
371
- }
372
- /**
373
- * Request a re-render and restore focus styling afterward.
374
- * Use this when a plugin action (like expand/collapse) triggers a render
375
- * but needs to maintain keyboard navigation focus.
376
- */
377
- requestRenderWithFocus() {
378
- this.grid?.requestRenderWithFocus?.();
379
- }
380
- /**
381
- * Request a lightweight style update without rebuilding DOM.
382
- * Use this instead of requestRender() when only CSS classes need updating.
383
- */
384
- requestAfterRender() {
385
- this.grid?.requestAfterRender?.();
386
- }
387
- /**
388
- * Get the current rows from the grid.
389
- */
390
- get rows() {
391
- return this.grid?.rows ?? [];
392
- }
393
- /**
394
- * Get the original unfiltered/unprocessed rows from the grid.
395
- * Use this when you need all source data regardless of active filters.
396
- */
397
- get sourceRows() {
398
- return this.grid?.sourceRows ?? [];
399
- }
400
- /**
401
- * Get the current columns from the grid.
402
- */
403
- get columns() {
404
- return this.grid?.columns ?? [];
405
- }
406
- /**
407
- * Get only visible columns from the grid (excludes hidden).
408
- * Use this for rendering that needs to match the grid template.
409
- */
410
- get visibleColumns() {
411
- return this.grid?._visibleColumns ?? [];
412
- }
413
- /**
414
- * Get the grid as an HTMLElement for direct DOM operations.
415
- * Use sparingly - prefer the typed GridElementRef API when possible.
416
- *
417
- * @example
418
- * ```ts
419
- * const width = this.gridElement.clientWidth;
420
- * this.gridElement.classList.add('my-plugin-active');
421
- * ```
422
- */
423
- get gridElement() {
424
- return this.grid;
425
- }
426
- /**
427
- * Get the disconnect signal for event listener cleanup.
428
- * This signal is aborted when the grid disconnects from the DOM.
429
- * Use this when adding event listeners that should be cleaned up automatically.
430
- *
431
- * Best for:
432
- * - Document/window-level listeners added in attach()
433
- * - Listeners on the grid element itself
434
- * - Any listener that should persist across renders
435
- *
436
- * Not needed for:
437
- * - Listeners on elements created in afterRender() (removed with element)
438
- *
439
- * @example
440
- * element.addEventListener('click', handler, { signal: this.disconnectSignal });
441
- * document.addEventListener('keydown', handler, { signal: this.disconnectSignal });
442
- */
443
- get disconnectSignal() {
444
- return this.#e?.signal ?? this.grid?.disconnectSignal;
445
- }
446
- /**
447
- * Get the grid-level icons configuration.
448
- * Returns merged icons (user config + defaults).
449
- */
450
- get gridIcons() {
451
- const t = this.grid?.gridConfig?.icons ?? {};
452
- return { ...I, ...t };
453
- }
454
- // #region Animation Helpers
455
- /**
456
- * Check if animations are enabled at the grid level.
457
- * Respects gridConfig.animation.mode and the CSS variable set by the grid.
458
- *
459
- * Plugins should use this to skip animations when:
460
- * - Animation mode is 'off' or `false`
461
- * - User prefers reduced motion and mode is 'reduced-motion' (default)
462
- *
463
- * @example
464
- * ```ts
465
- * private get animationStyle(): 'slide' | 'fade' | false {
466
- * if (!this.isAnimationEnabled) return false;
467
- * return this.config.animation ?? 'slide';
468
- * }
469
- * ```
470
- */
471
- get isAnimationEnabled() {
472
- const t = this.grid?.effectiveConfig?.animation?.mode ?? "reduced-motion";
473
- if (t === !1 || t === "off") return !1;
474
- if (t === !0 || t === "on") return !0;
475
- const e = this.gridElement;
476
- return e ? getComputedStyle(e).getPropertyValue("--tbw-animation-enabled").trim() !== "0" : !0;
477
- }
478
- /**
479
- * Get the animation duration in milliseconds from CSS variable.
480
- * Falls back to 200ms if not set.
481
- *
482
- * Plugins can use this for their animation timing to stay consistent
483
- * with the grid-level animation.duration setting.
484
- *
485
- * @example
486
- * ```ts
487
- * element.animate(keyframes, { duration: this.animationDuration });
488
- * ```
489
- */
490
- get animationDuration() {
491
- const t = this.gridElement;
492
- if (t) {
493
- const e = getComputedStyle(t).getPropertyValue("--tbw-animation-duration").trim(), i = parseInt(e, 10);
494
- if (!isNaN(i)) return i;
495
- }
496
- return 200;
497
- }
498
- // #endregion
499
- /**
500
- * Resolve an icon value to string or HTMLElement.
501
- * Checks plugin config first, then grid-level icons, then defaults.
502
- *
503
- * @param iconKey - The icon key in GridIcons (e.g., 'expand', 'collapse')
504
- * @param pluginOverride - Optional plugin-level override
505
- * @returns The resolved icon value
506
- */
507
- resolveIcon(t, e) {
508
- return e !== void 0 ? e : this.gridIcons[t];
509
- }
510
- /**
511
- * Set an icon value on an element.
512
- * Handles both string (text/HTML) and HTMLElement values.
513
- *
514
- * @param element - The element to set the icon on
515
- * @param icon - The icon value (string or HTMLElement)
516
- */
517
- setIcon(t, e) {
518
- typeof e == "string" ? t.innerHTML = e : e instanceof HTMLElement && (t.innerHTML = "", t.appendChild(e.cloneNode(!0)));
519
- }
520
- /**
521
- * Log a warning message.
522
- */
523
- warn(t) {
524
- console.warn(`[tbw-grid:${this.name}] ${t}`);
525
- }
526
- // #endregion
527
- }
528
- const B = 'tbw-grid[data-responsive-animate] .data-grid-row,tbw-grid[data-responsive-animate] .data-grid-row>.cell{transition:opacity var(--tbw-responsive-duration, .2s) ease-out,transform var(--tbw-responsive-duration, .2s) ease-out}tbw-grid[data-responsive][data-responsive-animate] .data-grid-row{animation:responsive-card-enter var(--tbw-responsive-duration, .2s) ease-out}@keyframes responsive-card-enter{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}tbw-grid[data-responsive] .header{display:none!important}tbw-grid[data-responsive] .footer-row{display:none}tbw-grid[data-responsive] .tbw-scroll-area{overflow-x:hidden;min-width:0!important}tbw-grid[data-responsive] .rows-body-wrapper{min-width:0!important}tbw-grid[data-responsive] .data-grid-row:not(.group-row){display:block!important;grid-template-columns:none!important;padding:var(--tbw-cell-padding);padding-inline-start:var(--tbw-spacing-xl);border-bottom:1px solid var(--tbw-color-border);min-height:auto!important;height:auto!important;contain:none!important;content-visibility:visible!important;background:var(--tbw-color-bg);position:relative}tbw-grid[data-responsive] .data-grid-row:not(.group-row):nth-child(2n){background:var(--tbw-color-row-alt)}tbw-grid[data-responsive] .data-grid-row:not(.group-row):hover{background:var(--tbw-color-row-hover)}tbw-grid[data-responsive] .data-grid-row:not(.group-row)[aria-selected=true]{background:var(--tbw-color-selection)}tbw-grid[data-responsive] .data-grid-row:not(.group-row)[aria-selected=true]:before{content:"";position:absolute;inset-inline-start:0;top:0;bottom:0;width:4px;background:var(--tbw-color-accent)}tbw-grid[data-responsive] .data-grid-row:not(.group-row)>.cell{display:flex!important;justify-content:space-between;align-items:center;padding:var(--tbw-spacing-xs) var(--tbw-spacing-md);width:100%!important;min-width:0!important;min-height:auto!important;height:auto!important;line-height:1.5!important;position:static!important;left:auto!important;right:auto!important;border:none!important;border-bottom:none!important;border-inline-end:none!important;background:transparent!important;white-space:normal!important;overflow:visible!important}tbw-grid[data-responsive] .data-grid-row:not(.group-row)>.cell:before{content:attr(data-header) ": ";font-weight:600;color:var(--tbw-color-header-fg);flex-shrink:0;margin-inline-end:var(--tbw-spacing-md);min-width:100px}tbw-grid[data-responsive] .data-grid-row:not(.group-row)>.cell:after{content:none}tbw-grid[data-responsive] .cell[data-utility]{display:none!important}tbw-grid[data-responsive] .cell[data-responsive-hidden]{display:none!important}tbw-grid[data-responsive] .cell[data-responsive-value-only]{justify-content:flex-start!important;font-weight:500}tbw-grid[data-responsive] .cell[data-responsive-value-only]:before{display:none!important}tbw-grid:not([data-responsive]) .cell[data-responsive-hidden]{display:none!important}tbw-grid[data-responsive] .tbw-footer,tbw-grid[data-responsive] .tbw-pinned-rows,tbw-grid[data-responsive] .tbw-aggregation-rows{display:none!important}tbw-grid[data-responsive] .tbw-pinned-rows,tbw-grid[data-responsive] .tbw-aggregation-rows,tbw-grid[data-responsive] .tbw-aggregation-row{min-width:0!important}tbw-grid[data-responsive] .data-grid-row.responsive-card{display:block!important;padding:var(--tbw-cell-padding);border-bottom:1px solid var(--tbw-color-border)}tbw-grid[data-responsive] .data-grid-row.responsive-card>*{width:100%}tbw-grid[data-responsive] .data-grid-row.responsive-card .cell:before{display:none}';
529
- class G extends z {
530
- name = "responsive";
531
- version = "1.0.0";
532
- styles = B;
533
- /**
534
- * Plugin manifest declaring incompatibilities with other plugins.
535
- */
536
- static manifest = {
537
- incompatibleWith: [
538
- {
539
- name: "groupingRows",
540
- reason: "Responsive card layout does not yet support row grouping. The variable row heights (cards vs group headers) cause scroll calculation issues."
541
- }
542
- ],
543
- queries: [
544
- {
545
- type: "isCardMode",
546
- description: "Returns whether the grid is currently in responsive card mode"
547
- }
548
- ]
549
- };
550
- #e;
551
- #t = !1;
552
- #n;
553
- #v = !1;
554
- #a = 0;
555
- /** Set of column fields to completely hide */
556
- #s = /* @__PURE__ */ new Set();
557
- /** Set of column fields to show value only (no header label) */
558
- #l = /* @__PURE__ */ new Set();
559
- /** Currently active breakpoint, or null if none */
560
- #d = null;
561
- /** Sorted breakpoints from largest to smallest */
562
- #c = [];
563
- /**
564
- * Check if currently in responsive mode.
565
- * @returns `true` if the grid is in card layout mode
566
- */
567
- isResponsive() {
568
- return this.#t;
569
- }
570
- /**
571
- * Force responsive mode regardless of width.
572
- * Useful for testing or manual control.
573
- * @param enabled - Whether to enable responsive mode
574
- */
575
- setResponsive(t) {
576
- t !== this.#t && (this.#t = t, this.#h(), this.emit("responsive-change", {
577
- isResponsive: t,
578
- width: this.#a,
579
- breakpoint: this.config.breakpoint ?? 0
580
- }));
581
- }
582
- /**
583
- * Update breakpoint dynamically.
584
- * @param width - New breakpoint width in pixels
585
- */
586
- setBreakpoint(t) {
587
- this.config.breakpoint = t, this.#b(this.#a);
588
- }
589
- /**
590
- * Set a custom card renderer.
591
- * This allows framework adapters to provide template-based renderers at runtime.
592
- * @param renderer - The card renderer function, or undefined to use default
593
- */
594
- setCardRenderer(t) {
595
- this.config.cardRenderer = t, this.#t && this.requestRender();
596
- }
597
- /**
598
- * Get current grid width.
599
- * @returns Width of the grid element in pixels
600
- */
601
- getWidth() {
602
- return this.#a;
603
- }
604
- /**
605
- * Get the currently active breakpoint config (multi-breakpoint mode only).
606
- * @returns The active BreakpointConfig, or null if no breakpoint is active
607
- */
608
- getActiveBreakpoint() {
609
- return this.#d;
610
- }
611
- attach(t) {
612
- super.attach(t), this.#R(), this.#u(this.config.hiddenColumns), this.config.breakpoints?.length && (this.#c = [...this.config.breakpoints].sort((e, i) => i.maxWidth - e.maxWidth)), this.#e = new ResizeObserver((e) => {
613
- const i = e[0]?.contentRect.width ?? 0;
614
- this.#a = i, clearTimeout(this.#n), this.#n = setTimeout(() => {
615
- this.#b(i);
616
- }, this.config.debounceMs ?? 100);
617
- }), this.#e.observe(this.gridElement);
618
- }
619
- // #region Light DOM Parsing
620
- /**
621
- * Parse `<tbw-grid-responsive-card>` elements from the grid's light DOM.
622
- *
623
- * Allows declarative configuration:
624
- * ```html
625
- * <tbw-grid [rows]="data">
626
- * <tbw-grid-responsive-card breakpoint="500" card-row-height="80">
627
- * <div class="custom-card">
628
- * <strong>{{ row.name }}</strong>
629
- * <span>{{ row.email }}</span>
630
- * </div>
631
- * </tbw-grid-responsive-card>
632
- * </tbw-grid>
633
- * ```
634
- *
635
- * Attributes:
636
- * - `breakpoint`: number - Width threshold for responsive mode
637
- * - `card-row-height`: number | 'auto' - Card height (default: 'auto')
638
- * - `hidden-columns`: string - Comma-separated fields to hide
639
- * - `hide-header`: 'true' | 'false' - Hide header row (default: 'true')
640
- * - `debounce-ms`: number - Resize debounce delay (default: 100)
641
- */
642
- #R() {
643
- const t = this.grid;
644
- if (!t || typeof t.querySelector != "function") return;
645
- const e = t.querySelector("tbw-grid-responsive-card");
646
- if (!e) return;
647
- const i = t;
648
- if (i.__frameworkAdapter?.parseResponsiveCardElement) {
649
- const d = i.__frameworkAdapter.parseResponsiveCardElement(e);
650
- d && (this.config = { ...this.config, cardRenderer: d });
651
- }
652
- const n = e.getAttribute("breakpoint"), l = e.getAttribute("card-row-height"), s = e.getAttribute("hidden-columns"), o = e.getAttribute("hide-header"), c = e.getAttribute("debounce-ms"), a = {};
653
- if (n !== null) {
654
- const d = parseInt(n, 10);
655
- isNaN(d) || (a.breakpoint = d);
656
- }
657
- if (l !== null && (a.cardRowHeight = l === "auto" ? "auto" : parseInt(l, 10)), s !== null && (a.hiddenColumns = s.split(",").map((d) => d.trim()).filter((d) => d.length > 0)), o !== null && (a.hideHeader = o !== "false"), c !== null) {
658
- const d = parseInt(c, 10);
659
- isNaN(d) || (a.debounceMs = d);
660
- }
661
- const h = e.innerHTML.trim();
662
- h && !this.config.cardRenderer && !i.__frameworkAdapter?.parseResponsiveCardElement && (a.cardRenderer = (d) => {
663
- const f = T(h, { value: d, row: d }), m = k(f), p = document.createElement("div");
664
- return p.className = "tbw-responsive-card-content", p.innerHTML = m, p;
665
- }), Object.keys(a).length > 0 && (this.config = { ...this.config, ...a });
666
- }
667
- // #endregion
668
- /**
669
- * Build the hidden and value-only column sets from config.
670
- */
671
- #u(t) {
672
- if (this.#s.clear(), this.#l.clear(), !!t)
673
- for (const e of t)
674
- typeof e == "string" ? this.#s.add(e) : e.showValue ? this.#l.add(e.field) : this.#s.add(e.field);
675
- }
676
- detach() {
677
- this.#e?.disconnect(), this.#e = void 0, clearTimeout(this.#n), this.#n = void 0, this.gridElement && this.gridElement.removeAttribute("data-responsive"), super.detach();
678
- }
679
- /**
680
- * Handle plugin queries.
681
- * @internal
682
- */
683
- handleQuery(t) {
684
- if (t.type === "isCardMode")
685
- return this.#t;
686
- }
687
- /**
688
- * Apply hidden and value-only columns.
689
- * In legacy mode (single breakpoint), only applies when in responsive mode.
690
- * In multi-breakpoint mode, applies whenever there's an active breakpoint.
691
- */
692
- afterRender() {
693
- if (this.#E(), !(this.#c.length > 0 ? this.#d !== null : this.#t))
694
- return;
695
- const e = this.#s.size > 0, i = this.#l.size > 0;
696
- if (!e && !i)
697
- return;
698
- const n = this.gridElement.querySelectorAll(".cell[data-field]");
699
- for (const l of n) {
700
- const s = l.getAttribute("data-field");
701
- s && (this.#s.has(s) ? (l.setAttribute("data-responsive-hidden", ""), l.removeAttribute("data-responsive-value-only")) : this.#l.has(s) ? (l.setAttribute("data-responsive-value-only", ""), l.removeAttribute("data-responsive-hidden")) : (l.removeAttribute("data-responsive-hidden"), l.removeAttribute("data-responsive-value-only")));
702
- }
703
- }
704
- /**
705
- * Check if width has crossed any breakpoint threshold.
706
- * Handles both single breakpoint (legacy) and multi-breakpoint modes.
707
- */
708
- #b(t) {
709
- if (this.#c.length > 0) {
710
- this.#y(t);
711
- return;
712
- }
713
- const e = this.config.breakpoint ?? 0;
714
- e === 0 && !this.#v && (this.#v = !0, console.warn(
715
- "[tbw-grid:ResponsivePlugin] No breakpoint configured. Responsive mode is disabled. Set a breakpoint based on your grid's column count."
716
- ));
717
- const i = e > 0 && t < e;
718
- i !== this.#t && (this.#t = i, this.#h(), this.emit("responsive-change", {
719
- isResponsive: i,
720
- width: t,
721
- breakpoint: e
722
- }), this.requestRender());
723
- }
724
- /**
725
- * Check breakpoints in multi-breakpoint mode.
726
- * Evaluates breakpoints from largest to smallest, applying the first match.
727
- */
728
- #y(t) {
729
- let e = null;
730
- for (const n of this.#c)
731
- t <= n.maxWidth && (e = n);
732
- if (e !== this.#d) {
733
- this.#d = e, e?.hiddenColumns ? this.#u(e.hiddenColumns) : this.#u(this.config.hiddenColumns);
734
- const n = e?.cardLayout === !0;
735
- n !== this.#t && (this.#t = n, this.#h()), this.emit("responsive-change", {
736
- isResponsive: this.#t,
737
- width: t,
738
- breakpoint: e?.maxWidth ?? 0
739
- }), this.requestRender();
740
- }
741
- }
742
- /** Original row height before entering responsive mode, for restoration on exit */
743
- #i;
744
- /**
745
- * Apply the responsive state to the grid element.
746
- * Handles scroll reset when entering responsive mode and row height restoration on exit.
747
- */
748
- #h() {
749
- this.gridElement.toggleAttribute("data-responsive", this.#t);
750
- const t = this.config.animate !== !1;
751
- this.gridElement.toggleAttribute("data-responsive-animate", t), this.config.animationDuration && this.gridElement.style.setProperty("--tbw-responsive-duration", `${this.config.animationDuration}ms`);
752
- const e = this.grid;
753
- if (this.#t) {
754
- e._virtualization && (this.#i = e._virtualization.rowHeight);
755
- const i = this.gridElement.querySelector(".tbw-scroll-area");
756
- i && (i.scrollLeft = 0);
757
- } else {
758
- const i = this.gridElement.querySelectorAll(".data-grid-row");
759
- for (const n of i)
760
- n.style.height = "", n.classList.remove("responsive-card");
761
- this.#i && this.#i > 0 && e._virtualization && (e._virtualization.rowHeight = this.#i, this.#i = void 0), this.#r = void 0, this.#o = void 0, this.#g = void 0;
762
- }
763
- }
764
- /**
765
- * Custom row rendering when cardRenderer is provided and in responsive mode.
766
- *
767
- * When a cardRenderer is configured, this hook takes over row rendering to display
768
- * the custom card layout instead of the default cell structure.
769
- *
770
- * @param row - The row data object
771
- * @param rowEl - The row DOM element to render into
772
- * @param rowIndex - The index of the row in the data array
773
- * @returns `true` if rendered (prevents default), `void` for default rendering
774
- */
775
- renderRow(t, e, i) {
776
- if (!this.#t || !this.config.cardRenderer || t.__isGroupRow)
777
- return;
778
- e.replaceChildren();
779
- const n = this.config.cardRenderer(t, i);
780
- e.className = "data-grid-row responsive-card";
781
- const l = this.config.cardRowHeight ?? "auto";
782
- return l !== "auto" ? e.style.height = `${l}px` : e.style.height = "auto", e.appendChild(n), !0;
783
- }
784
- /**
785
- * Handle keyboard navigation in responsive mode.
786
- *
787
- * In responsive mode, the visual layout is inverted:
788
- * - Cells are stacked vertically within each "card" (row)
789
- * - DOWN/UP visually moves within the card (between fields)
790
- * - Page Down/Page Up or Ctrl+Down/Up moves between cards
791
- *
792
- * For custom cardRenderers, keyboard navigation is disabled entirely
793
- * since the implementor controls the card content and should handle
794
- * navigation via their own event handlers.
795
- *
796
- * @returns `true` if the event was handled and default behavior should be prevented
797
- */
798
- onKeyDown(t) {
799
- if (!this.#t || this.config.cardRenderer && ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(t.key))
800
- return !1;
801
- const e = this.rows.length - 1, i = this.visibleColumns.length - 1;
802
- switch (t.key) {
803
- case "ArrowDown":
804
- if (this.grid._focusCol < i)
805
- return this.grid._focusCol += 1, t.preventDefault(), g(this.grid), !0;
806
- if (this.grid._focusRow < e)
807
- return this.grid._focusRow += 1, this.grid._focusCol = 0, t.preventDefault(), g(this.grid), !0;
808
- break;
809
- case "ArrowUp":
810
- if (this.grid._focusCol > 0)
811
- return this.grid._focusCol -= 1, t.preventDefault(), g(this.grid), !0;
812
- if (this.grid._focusRow > 0)
813
- return this.grid._focusRow -= 1, this.grid._focusCol = i, t.preventDefault(), g(this.grid), !0;
814
- break;
815
- case "ArrowRight":
816
- if (this.grid._focusRow < e)
817
- return this.grid._focusRow += 1, t.preventDefault(), g(this.grid), !0;
818
- break;
819
- case "ArrowLeft":
820
- if (this.grid._focusRow > 0)
821
- return this.grid._focusRow -= 1, t.preventDefault(), g(this.grid), !0;
822
- break;
823
- }
824
- return !1;
825
- }
826
- // ============================================
827
- // Variable Height Support for Mixed Row Types
828
- // ============================================
829
- /** Measured card height from DOM for virtualization calculations */
830
- #r;
831
- /** Measured group row height from DOM for virtualization calculations */
832
- #o;
833
- /** Last known card row count for detecting changes (e.g., group expand/collapse) */
834
- #g;
835
- /**
836
- * Get the effective card height for virtualization calculations.
837
- * Prioritizes DOM-measured height (actual rendered size) over config,
838
- * since content can overflow the configured height.
839
- */
840
- #f() {
841
- if (this.#r && this.#r > 0)
842
- return this.#r;
843
- const t = this.config.cardRowHeight;
844
- return typeof t == "number" && t > 0 ? t : 80;
845
- }
846
- /**
847
- * Get the effective group row height for virtualization calculations.
848
- * Uses DOM-measured height, falling back to original row height.
849
- */
850
- #p() {
851
- return this.#o && this.#o > 0 ? this.#o : this.#i ?? 28;
852
- }
853
- /**
854
- * Check if there are any group rows in the current dataset.
855
- * Used to determine if we have mixed row heights.
856
- */
857
- #m() {
858
- for (const t of this.rows)
859
- if (t.__isGroupRow)
860
- return !0;
861
- return !1;
862
- }
863
- /**
864
- * Count group rows and card rows in the current dataset.
865
- */
866
- #_() {
867
- let t = 0, e = 0;
868
- for (const i of this.rows)
869
- i.__isGroupRow ? t++ : e++;
870
- return { groupCount: t, cardCount: e };
871
- }
872
- /**
873
- * Return total extra height contributed by mixed row heights.
874
- * This is called by the grid's virtualization system to adjust scrollbar height.
875
- *
876
- * The grid calculates: totalRows * baseRowHeight + pluginExtraHeight
877
- *
878
- * For mixed layouts (groups + cards), we need to report the difference between
879
- * actual heights and what the base calculation assumes:
880
- * - Extra for groups: groupCount * (groupHeight - baseHeight)
881
- * - Extra for cards: cardCount * (cardHeight - baseHeight)
882
- *
883
- * @deprecated Use getRowHeight() instead. This hook will be removed in v3.0.
884
- */
885
- getExtraHeight() {
886
- if (!this.#t || !this.config.cardRenderer || !this.#m())
887
- return 0;
888
- const t = this.#i ?? 28, e = this.#p(), i = this.#f(), { groupCount: n, cardCount: l } = this.#_(), s = n * Math.max(0, e - t), o = l * Math.max(0, i - t);
889
- return s + o;
890
- }
891
- /**
892
- * Return extra height that appears before a given row index.
893
- * Used by virtualization to correctly calculate scroll positions.
894
- *
895
- * Like getExtraHeight, this accounts for both group and card row heights.
896
- *
897
- * @deprecated Use getRowHeight() instead. This hook will be removed in v3.0.
898
- */
899
- getExtraHeightBefore(t) {
900
- if (!this.#t || !this.config.cardRenderer || !this.#m())
901
- return 0;
902
- const e = this.#i ?? 28, i = this.#p(), n = this.#f(), l = Math.max(0, i - e), s = Math.max(0, n - e);
903
- let o = 0, c = 0;
904
- const a = this.rows, h = Math.min(t, a.length);
905
- for (let d = 0; d < h; d++)
906
- a[d].__isGroupRow ? o++ : c++;
907
- return o * l + c * s;
908
- }
909
- /**
910
- * Get the height of a specific row based on its type (group row vs card row).
911
- * Returns undefined if not in responsive mode.
912
- *
913
- * @param row - The row data
914
- * @param _index - The row index (unused, but part of the interface)
915
- * @returns The row height in pixels, or undefined if not in responsive mode
916
- */
917
- getRowHeight(t, e) {
918
- if (!(!this.#t || !this.config.cardRenderer))
919
- return t.__isGroupRow ? this.#p() : this.#f();
920
- }
921
- /**
922
- * Count the number of card rows (non-group rows) in the current dataset.
923
- */
924
- #C() {
925
- let t = 0;
926
- for (const e of this.rows)
927
- e.__isGroupRow || t++;
928
- return t;
929
- }
930
- /** Pending refresh scheduled via microtask */
931
- #w = !1;
932
- /**
933
- * Measure card height from DOM after render and detect row count changes.
934
- * Called in afterRender to ensure scroll calculations are accurate.
935
- *
936
- * This handles two scenarios:
937
- * 1. Card height changes (content overflow, dynamic sizing)
938
- * 2. Card row count changes (group expand/collapse)
939
- * 3. Group row height changes
940
- *
941
- * For uniform card layouts (no groups), we update the virtualization row height
942
- * directly to the card height. For mixed layouts (groups + cards), we use the
943
- * getExtraHeight mechanism to report height differences.
944
- *
945
- * The refresh is deferred via microtask to avoid nested render cycles.
946
- */
947
- #E() {
948
- if (!this.#t || !this.config.cardRenderer)
949
- return;
950
- let t = !1;
951
- const e = this.grid, i = this.#m(), n = this.#C();
952
- if (n !== this.#g && (this.#g = n, t = !0), i) {
953
- const s = this.gridElement.querySelector(".data-grid-row.group-row");
954
- if (s) {
955
- const o = s.getBoundingClientRect().height;
956
- o > 0 && o !== this.#o && (this.#o = o, t = !0);
957
- }
958
- }
959
- const l = this.gridElement.querySelector(".data-grid-row.responsive-card");
960
- if (l) {
961
- const s = l.getBoundingClientRect().height;
962
- s > 0 && s !== this.#r && (this.#r = s, t = !0, !i && e._virtualization && (e._virtualization.rowHeight = s));
963
- }
964
- t && !this.#w && (this.#w = !0, queueMicrotask(() => {
965
- this.#w = !1, this.grid && this.#t && this.grid.refreshVirtualWindow?.(!0, !0);
966
- }));
967
- }
968
- }
969
- export {
970
- G as ResponsivePlugin
971
- };
1
+ const e=/{{\s*([^}]+)\s*}}/g,t="__DG_EMPTY__",i=/^[\w$. '?+\-*/%:()!<>=,&|]+$/,r=/__(proto|defineGetter|defineSetter)|constructor|window|globalThis|global|process|Function|import|eval|Reflect|Proxy|Error|arguments|document|location|cookie|localStorage|sessionStorage|indexedDB|fetch|XMLHttpRequest|WebSocket|Worker|SharedWorker|ServiceWorker|opener|parent|top|frames|self|this\b/,o=/* @__PURE__ */new Set(["script","iframe","object","embed","form","input","button","textarea","select","link","meta","base","style","template","slot","portal","frame","frameset","applet","noscript","noembed","plaintext","xmp","listing"]),s=/^on\w+$/i,n=/* @__PURE__ */new Set(["href","src","action","formaction","data","srcdoc","xlink:href","poster","srcset"]),a=/^\s*(javascript|vbscript|data|blob):/i;function d(e){if(!e||"string"!=typeof e)return"";if(-1===e.indexOf("<"))return e;const t=document.createElement("template");return t.innerHTML=e,function(e){const t=[],i=e.querySelectorAll("*");for(const r of i){const e=r.tagName.toLowerCase();if(o.has(e)){t.push(r);continue}if("svg"===e||"http://www.w3.org/2000/svg"===r.namespaceURI){if(Array.from(r.attributes).some(e=>s.test(e.name)||"href"===e.name||"xlink:href"===e.name)){t.push(r);continue}}const i=[];for(const t of r.attributes){const e=t.name.toLowerCase();s.test(e)?i.push(t.name):(n.has(e)&&a.test(t.value)||"style"===e&&/expression\s*\(|javascript:|behavior\s*:/i.test(t.value))&&i.push(t.name)}i.forEach(e=>r.removeAttribute(e))}t.forEach(e=>e.remove())}(t.content),t.innerHTML}function l(o,s){if(!o||-1===o.indexOf("{{"))return o;const n=[],a=o.replace(e,(e,o)=>{const a=function(e,o){if(e=(e||"").trim(),!e)return t;if(c.test(e))return t;if("value"===e)return null==o.value?t:String(o.value);if(e.startsWith("row.")&&!/[()?]/.test(e)&&!e.includes(":")){const i=e.slice(4),r=o.row?o.row[i]:void 0;return null==r?t:String(r)}if(e.length>80)return t;if(!i.test(e)||r.test(e))return t;const s=e.match(/\./g);if(s&&s.length>1)return t;try{const i=new Function("value","row",`return (${e});`)(o.value,o.row),r=null==i?"":String(i);return c.test(r)?t:r||t}catch{return t}}(o,s);return n.push({expr:o.trim(),result:a}),a}),d=(l=a)?l.replace(new RegExp(t,"g"),"").replace(/Reflect\.[^<>{}\s]+|\bProxy\b|ownKeys\([^)]*\)/g,""):l;var l;const h=n.length&&n.every(e=>""===e.result||e.result===t);return c.test(o)||h?"":d}const c=/Reflect|Proxy|ownKeys/;document.createElement("template").innerHTML='<div class="cell" role="gridcell" part="cell"></div>';function h(e,t){if(e._virtualization?.enabled){const{rowHeight:t,container:i,viewportEl:r}=e._virtualization,o=i,s=r?.clientHeight??o?.clientHeight??0;if(o&&s>0){const i=e._focusRow*t;i<o.scrollTop?o.scrollTop=i:i+t>o.scrollTop+s&&(o.scrollTop=i-s+t)}}const i=void 0!==e._activeEditRows&&-1!==e._activeEditRows;var r;i||e.refreshVirtualWindow(!1),(r=e._bodyEl)&&r.querySelectorAll(".cell-focus").forEach(e=>e.classList.remove("cell-focus")),Array.from(e._bodyEl.querySelectorAll('[aria-selected="true"]')).forEach(e=>{e.setAttribute("aria-selected","false")});const o=e._focusRow,s=e._virtualization.start??0,n=e._virtualization.end??e._rows.length;if(o>=s&&o<n){const r=e._bodyEl.querySelectorAll(".data-grid-row")[o-s];let n=r?.children[e._focusCol];if(n&&n.classList?.contains("cell")||(n=r?.querySelector(`.cell[data-col="${e._focusCol}"]`)??r?.querySelector(".cell[data-col]")),n){n.classList.add("cell-focus"),n.setAttribute("aria-selected","true");const o=e.querySelector(".tbw-scroll-area");if(o&&n&&(!i||t?.forceHorizontalScroll)){const t=e._getHorizontalScrollOffsets?.(r??void 0,n)??{left:0,right:0};if(!t.skipScroll){const e=n.getBoundingClientRect(),i=o.getBoundingClientRect(),r=e.left-i.left+o.scrollLeft,s=r+e.width,a=o.scrollLeft+t.left,d=o.scrollLeft+o.clientWidth-t.right;r<a?o.scrollLeft=r-t.left:s>d&&(o.scrollLeft=s-o.clientWidth+t.right)}}if(i&&n.classList.contains("editing")){const e=n.querySelector('input,select,textarea,[contenteditable="true"],[contenteditable=""],[tabindex]:not([tabindex="-1"])');if(e&&document.activeElement!==e)try{e.focus({preventScroll:!0})}catch{}}else if(i&&!n.contains(document.activeElement)){n.hasAttribute("tabindex")||n.setAttribute("tabindex","-1");try{n.focus({preventScroll:!0})}catch{}}else if(!i){const t=e;document.activeElement!==t&&t.focus({preventScroll:!0})}}}}document.createElement("template").innerHTML='<div class="data-grid-row" role="row" part="row"></div>';const u='<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>',g={expand:"▶",collapse:"▼",sortAsc:"▲",sortDesc:"▼",sortNone:"⇅",submenuArrow:"▶",dragHandle:"⋮⋮",toolPanel:"☰",filter:u,filterActive:u,print:"🖨️"};class p{static dependencies;static manifest;version="undefined"!=typeof __GRID_VERSION__?__GRID_VERSION__:"dev";styles;cellRenderers;headerRenderers;cellEditors;grid;config;userConfig;#e;get defaultConfig(){return{}}constructor(e={}){this.userConfig=e}attach(e){this.#e?.abort(),this.#e=new AbortController,this.grid=e,this.config={...this.defaultConfig,...this.userConfig}}detach(){this.#e?.abort(),this.#e=void 0}getPlugin(e){return this.grid?.getPlugin(e)}emit(e,t){this.grid?.dispatchEvent?.(new CustomEvent(e,{detail:t,bubbles:!0}))}emitCancelable(e,t){const i=new CustomEvent(e,{detail:t,bubbles:!0,cancelable:!0});return this.grid?.dispatchEvent?.(i),i.defaultPrevented}on(e,t){this.grid?._pluginManager?.subscribe(this,e,t)}off(e){this.grid?._pluginManager?.unsubscribe(this,e)}emitPluginEvent(e,t){this.grid?._pluginManager?.emitPluginEvent(e,t)}requestRender(){this.grid?.requestRender?.()}requestColumnsRender(){this.grid?.requestColumnsRender?.()}requestRenderWithFocus(){this.grid?.requestRenderWithFocus?.()}requestAfterRender(){this.grid?.requestAfterRender?.()}get rows(){return this.grid?.rows??[]}get sourceRows(){return this.grid?.sourceRows??[]}get columns(){return this.grid?.columns??[]}get visibleColumns(){return this.grid?._visibleColumns??[]}get gridElement(){return this.grid}get disconnectSignal(){return this.#e?.signal??this.grid?.disconnectSignal}get gridIcons(){const e=this.grid?.gridConfig?.icons??{};return{...g,...e}}get isAnimationEnabled(){const e=this.grid?.effectiveConfig?.animation?.mode??"reduced-motion";if(!1===e||"off"===e)return!1;if(!0===e||"on"===e)return!0;const t=this.gridElement;if(t){return"0"!==getComputedStyle(t).getPropertyValue("--tbw-animation-enabled").trim()}return!0}get animationDuration(){const e=this.gridElement;if(e){const t=getComputedStyle(e).getPropertyValue("--tbw-animation-duration").trim(),i=parseInt(t,10);if(!isNaN(i))return i}return 200}resolveIcon(e,t){return void 0!==t?t:this.gridIcons[e]}setIcon(e,t){"string"==typeof t?e.innerHTML=t:t instanceof HTMLElement&&(e.innerHTML="",e.appendChild(t.cloneNode(!0)))}warn(e){console.warn(`[tbw-grid:${this.name}] ${e}`)}}class f extends p{name="responsive";version="1.0.0";styles='tbw-grid[data-responsive-animate] .data-grid-row,tbw-grid[data-responsive-animate] .data-grid-row>.cell{transition:opacity var(--tbw-responsive-duration, .2s) ease-out,transform var(--tbw-responsive-duration, .2s) ease-out}tbw-grid[data-responsive][data-responsive-animate] .data-grid-row{animation:responsive-card-enter var(--tbw-responsive-duration, .2s) ease-out}@keyframes responsive-card-enter{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}tbw-grid[data-responsive] .header{display:none!important}tbw-grid[data-responsive] .footer-row{display:none}tbw-grid[data-responsive] .tbw-scroll-area{overflow-x:hidden;min-width:0!important}tbw-grid[data-responsive] .rows-body-wrapper{min-width:0!important}tbw-grid[data-responsive] .data-grid-row:not(.group-row){display:block!important;grid-template-columns:none!important;padding:var(--tbw-cell-padding);padding-inline-start:var(--tbw-spacing-xl);border-bottom:1px solid var(--tbw-color-border);min-height:auto!important;height:auto!important;contain:none!important;content-visibility:visible!important;background:var(--tbw-color-bg);position:relative}tbw-grid[data-responsive] .data-grid-row:not(.group-row):nth-child(2n){background:var(--tbw-color-row-alt)}tbw-grid[data-responsive] .data-grid-row:not(.group-row):hover{background:var(--tbw-color-row-hover)}tbw-grid[data-responsive] .data-grid-row:not(.group-row)[aria-selected=true]{background:var(--tbw-color-selection)}tbw-grid[data-responsive] .data-grid-row:not(.group-row)[aria-selected=true]:before{content:"";position:absolute;inset-inline-start:0;top:0;bottom:0;width:4px;background:var(--tbw-color-accent)}tbw-grid[data-responsive] .data-grid-row:not(.group-row)>.cell{display:flex!important;justify-content:space-between;align-items:center;padding:var(--tbw-spacing-xs) var(--tbw-spacing-md);width:100%!important;min-width:0!important;min-height:auto!important;height:auto!important;line-height:1.5!important;position:static!important;left:auto!important;right:auto!important;border:none!important;border-bottom:none!important;border-inline-end:none!important;background:transparent!important;white-space:normal!important;overflow:visible!important}tbw-grid[data-responsive] .data-grid-row:not(.group-row)>.cell:before{content:attr(data-header) ": ";font-weight:600;color:var(--tbw-color-header-fg);flex-shrink:0;margin-inline-end:var(--tbw-spacing-md);min-width:100px}tbw-grid[data-responsive] .data-grid-row:not(.group-row)>.cell:after{content:none}tbw-grid[data-responsive] .cell[data-utility]{display:none!important}tbw-grid[data-responsive] .cell[data-responsive-hidden]{display:none!important}tbw-grid[data-responsive] .cell[data-responsive-value-only]{justify-content:flex-start!important;font-weight:500}tbw-grid[data-responsive] .cell[data-responsive-value-only]:before{display:none!important}tbw-grid:not([data-responsive]) .cell[data-responsive-hidden]{display:none!important}tbw-grid[data-responsive] .tbw-footer,tbw-grid[data-responsive] .tbw-pinned-rows,tbw-grid[data-responsive] .tbw-aggregation-rows{display:none!important}tbw-grid[data-responsive] .tbw-pinned-rows,tbw-grid[data-responsive] .tbw-aggregation-rows,tbw-grid[data-responsive] .tbw-aggregation-row{min-width:0!important}tbw-grid[data-responsive] .data-grid-row.responsive-card{display:block!important;padding:var(--tbw-cell-padding);border-bottom:1px solid var(--tbw-color-border)}tbw-grid[data-responsive] .data-grid-row.responsive-card>*{width:100%}tbw-grid[data-responsive] .data-grid-row.responsive-card .cell:before{display:none}';static manifest={incompatibleWith:[{name:"groupingRows",reason:"Responsive card layout does not yet support row grouping. The variable row heights (cards vs group headers) cause scroll calculation issues."}],queries:[{type:"isCardMode",description:"Returns whether the grid is currently in responsive card mode"}]};#t;#i=!1;#r;#o=!1;#s=0;#n=/* @__PURE__ */new Set;#a=/* @__PURE__ */new Set;#d=null;#l=[];isResponsive(){return this.#i}setResponsive(e){e!==this.#i&&(this.#i=e,this.#c(),this.emit("responsive-change",{isResponsive:e,width:this.#s,breakpoint:this.config.breakpoint??0}))}setBreakpoint(e){this.config.breakpoint=e,this.#h(this.#s)}setCardRenderer(e){this.config.cardRenderer=e,this.#i&&this.requestRender()}getWidth(){return this.#s}getActiveBreakpoint(){return this.#d}attach(e){super.attach(e),this.#u(),this.#g(this.config.hiddenColumns),this.config.breakpoints?.length&&(this.#l=[...this.config.breakpoints].sort((e,t)=>t.maxWidth-e.maxWidth)),this.#t=new ResizeObserver(e=>{const t=e[0]?.contentRect.width??0;this.#s=t,clearTimeout(this.#r),this.#r=setTimeout(()=>{this.#h(t)},this.config.debounceMs??100)}),this.#t.observe(this.gridElement)}#u(){const e=this.grid;if(!e||"function"!=typeof e.querySelector)return;const t=e.querySelector("tbw-grid-responsive-card");if(!t)return;const i=e;if(i.__frameworkAdapter?.parseResponsiveCardElement){const e=i.__frameworkAdapter.parseResponsiveCardElement(t);e&&(this.config={...this.config,cardRenderer:e})}const r=t.getAttribute("breakpoint"),o=t.getAttribute("card-row-height"),s=t.getAttribute("hidden-columns"),n=t.getAttribute("hide-header"),a=t.getAttribute("debounce-ms"),c={};if(null!==r){const e=parseInt(r,10);isNaN(e)||(c.breakpoint=e)}if(null!==o&&(c.cardRowHeight="auto"===o?"auto":parseInt(o,10)),null!==s&&(c.hiddenColumns=s.split(",").map(e=>e.trim()).filter(e=>e.length>0)),null!==n&&(c.hideHeader="false"!==n),null!==a){const e=parseInt(a,10);isNaN(e)||(c.debounceMs=e)}const h=t.innerHTML.trim();!h||this.config.cardRenderer||i.__frameworkAdapter?.parseResponsiveCardElement||(c.cardRenderer=e=>{const t=d(l(h,{value:e,row:e})),i=document.createElement("div");return i.className="tbw-responsive-card-content",i.innerHTML=t,i}),Object.keys(c).length>0&&(this.config={...this.config,...c})}#g(e){if(this.#n.clear(),this.#a.clear(),e)for(const t of e)"string"==typeof t?this.#n.add(t):t.showValue?this.#a.add(t.field):this.#n.add(t.field)}detach(){this.#t?.disconnect(),this.#t=void 0,clearTimeout(this.#r),this.#r=void 0,this.gridElement&&this.gridElement.removeAttribute("data-responsive"),super.detach()}handleQuery(e){if("isCardMode"===e.type)return this.#i}afterRender(){this.#p();if(!(this.#l.length>0?null!==this.#d:this.#i))return;const e=this.#n.size>0,t=this.#a.size>0;if(!e&&!t)return;const i=this.gridElement.querySelectorAll(".cell[data-field]");for(const r of i){const e=r.getAttribute("data-field");e&&(this.#n.has(e)?(r.setAttribute("data-responsive-hidden",""),r.removeAttribute("data-responsive-value-only")):this.#a.has(e)?(r.setAttribute("data-responsive-value-only",""),r.removeAttribute("data-responsive-hidden")):(r.removeAttribute("data-responsive-hidden"),r.removeAttribute("data-responsive-value-only")))}}#h(e){if(this.#l.length>0)return void this.#f(e);const t=this.config.breakpoint??0;0!==t||this.#o||(this.#o=!0,console.warn("[tbw-grid:ResponsivePlugin] No breakpoint configured. Responsive mode is disabled. Set a breakpoint based on your grid's column count."));const i=t>0&&e<t;i!==this.#i&&(this.#i=i,this.#c(),this.emit("responsive-change",{isResponsive:i,width:e,breakpoint:t}),this.requestRender())}#f(e){let t=null;for(const i of this.#l)e<=i.maxWidth&&(t=i);if(t!==this.#d){this.#d=t,t?.hiddenColumns?this.#g(t.hiddenColumns):this.#g(this.config.hiddenColumns);const i=!0===t?.cardLayout;i!==this.#i&&(this.#i=i,this.#c()),this.emit("responsive-change",{isResponsive:this.#i,width:e,breakpoint:t?.maxWidth??0}),this.requestRender()}}#w;#c(){this.gridElement.toggleAttribute("data-responsive",this.#i);const e=!1!==this.config.animate;this.gridElement.toggleAttribute("data-responsive-animate",e),this.config.animationDuration&&this.gridElement.style.setProperty("--tbw-responsive-duration",`${this.config.animationDuration}ms`);const t=this.grid;if(this.#i){t._virtualization&&(this.#w=t._virtualization.rowHeight);const e=this.gridElement.querySelector(".tbw-scroll-area");e&&(e.scrollLeft=0)}else{const e=this.gridElement.querySelectorAll(".data-grid-row");for(const t of e)t.style.height="",t.classList.remove("responsive-card");this.#w&&this.#w>0&&t._virtualization&&(t._virtualization.rowHeight=this.#w,this.#w=void 0),this.#m=void 0,this.#v=void 0,this.#b=void 0}}renderRow(e,t,i){if(!this.#i||!this.config.cardRenderer)return;if(e.__isGroupRow)return;t.replaceChildren();const r=this.config.cardRenderer(e,i);t.className="data-grid-row responsive-card";const o=this.config.cardRowHeight??"auto";return t.style.height="auto"!==o?`${o}px`:"auto",t.appendChild(r),!0}onKeyDown(e){if(!this.#i)return!1;if(this.config.cardRenderer){if(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(e.key))return!1}const t=this.rows.length-1,i=this.visibleColumns.length-1;switch(e.key){case"ArrowDown":if(this.grid._focusCol<i)return this.grid._focusCol+=1,e.preventDefault(),h(this.grid),!0;if(this.grid._focusRow<t)return this.grid._focusRow+=1,this.grid._focusCol=0,e.preventDefault(),h(this.grid),!0;break;case"ArrowUp":if(this.grid._focusCol>0)return this.grid._focusCol-=1,e.preventDefault(),h(this.grid),!0;if(this.grid._focusRow>0)return this.grid._focusRow-=1,this.grid._focusCol=i,e.preventDefault(),h(this.grid),!0;break;case"ArrowRight":if(this.grid._focusRow<t)return this.grid._focusRow+=1,e.preventDefault(),h(this.grid),!0;break;case"ArrowLeft":if(this.grid._focusRow>0)return this.grid._focusRow-=1,e.preventDefault(),h(this.grid),!0}return!1}#m;#v;#b;#R(){if(this.#m&&this.#m>0)return this.#m;const e=this.config.cardRowHeight;return"number"==typeof e&&e>0?e:80}#C(){return this.#v&&this.#v>0?this.#v:this.#w??28}#y(){for(const e of this.rows)if(e.__isGroupRow)return!0;return!1}#_(){let e=0,t=0;for(const i of this.rows)i.__isGroupRow?e++:t++;return{groupCount:e,cardCount:t}}getExtraHeight(){if(!this.#i||!this.config.cardRenderer)return 0;if(!this.#y())return 0;const e=this.#w??28,t=this.#C(),i=this.#R(),{groupCount:r,cardCount:o}=this.#_();return r*Math.max(0,t-e)+o*Math.max(0,i-e)}getExtraHeightBefore(e){if(!this.#i||!this.config.cardRenderer)return 0;if(!this.#y())return 0;const t=this.#w??28,i=this.#C(),r=this.#R(),o=Math.max(0,i-t),s=Math.max(0,r-t);let n=0,a=0;const d=this.rows,l=Math.min(e,d.length);for(let c=0;c<l;c++)d[c].__isGroupRow?n++:a++;return n*o+a*s}getRowHeight(e,t){if(this.#i&&this.config.cardRenderer)return e.__isGroupRow?this.#C():this.#R()}#k(){let e=0;for(const t of this.rows)t.__isGroupRow||e++;return e}#H=!1;#p(){if(!this.#i||!this.config.cardRenderer)return;let e=!1;const t=this.grid,i=this.#y(),r=this.#k();if(r!==this.#b&&(this.#b=r,e=!0),i){const t=this.gridElement.querySelector(".data-grid-row.group-row");if(t){const i=t.getBoundingClientRect().height;i>0&&i!==this.#v&&(this.#v=i,e=!0)}}const o=this.gridElement.querySelector(".data-grid-row.responsive-card");if(o){const r=o.getBoundingClientRect().height;r>0&&r!==this.#m&&(this.#m=r,e=!0,!i&&t._virtualization&&(t._virtualization.rowHeight=r))}e&&!this.#H&&(this.#H=!0,queueMicrotask(()=>{this.#H=!1,this.grid&&this.#i&&this.grid.refreshVirtualWindow?.(!0,!0)}))}}export{f as ResponsivePlugin};
972
2
  //# sourceMappingURL=index.js.map