@toolbox-web/grid 1.21.1 → 1.22.0

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 (155) hide show
  1. package/README.md +6 -8
  2. package/all.js +2 -6850
  3. package/all.js.map +1 -1
  4. package/index.js +1 -4352
  5. package/index.js.map +1 -1
  6. package/lib/core/grid.d.ts +75 -8
  7. package/lib/core/grid.d.ts.map +1 -1
  8. package/lib/core/internal/rows.d.ts.map +1 -1
  9. package/lib/core/plugin/base-plugin.d.ts +8 -2
  10. package/lib/core/plugin/base-plugin.d.ts.map +1 -1
  11. package/lib/core/styles/index.d.ts.map +1 -1
  12. package/lib/core/types.d.ts +84 -2
  13. package/lib/core/types.d.ts.map +1 -1
  14. package/lib/plugins/clipboard/index.js +1 -733
  15. package/lib/plugins/clipboard/index.js.map +1 -1
  16. package/lib/plugins/clipboard/types.d.ts +5 -0
  17. package/lib/plugins/clipboard/types.d.ts.map +1 -1
  18. package/lib/plugins/column-virtualization/index.js +1 -560
  19. package/lib/plugins/column-virtualization/index.js.map +1 -1
  20. package/lib/plugins/column-virtualization/types.d.ts +5 -0
  21. package/lib/plugins/column-virtualization/types.d.ts.map +1 -1
  22. package/lib/plugins/context-menu/index.js +1 -754
  23. package/lib/plugins/context-menu/index.js.map +1 -1
  24. package/lib/plugins/context-menu/types.d.ts +5 -0
  25. package/lib/plugins/context-menu/types.d.ts.map +1 -1
  26. package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
  27. package/lib/plugins/editing/index.d.ts +1 -1
  28. package/lib/plugins/editing/index.d.ts.map +1 -1
  29. package/lib/plugins/editing/index.js +1 -1539
  30. package/lib/plugins/editing/index.js.map +1 -1
  31. package/lib/plugins/editing/types.d.ts +49 -0
  32. package/lib/plugins/editing/types.d.ts.map +1 -1
  33. package/lib/plugins/export/index.js +1 -589
  34. package/lib/plugins/export/index.js.map +1 -1
  35. package/lib/plugins/export/types.d.ts +5 -0
  36. package/lib/plugins/export/types.d.ts.map +1 -1
  37. package/lib/plugins/filtering/FilteringPlugin.d.ts.map +1 -1
  38. package/lib/plugins/filtering/filter-model.d.ts.map +1 -1
  39. package/lib/plugins/filtering/index.js +1 -1283
  40. package/lib/plugins/filtering/index.js.map +1 -1
  41. package/lib/plugins/filtering/types.d.ts +7 -2
  42. package/lib/plugins/filtering/types.d.ts.map +1 -1
  43. package/lib/plugins/grouping-columns/index.js +1 -726
  44. package/lib/plugins/grouping-columns/index.js.map +1 -1
  45. package/lib/plugins/grouping-columns/types.d.ts +3 -0
  46. package/lib/plugins/grouping-columns/types.d.ts.map +1 -1
  47. package/lib/plugins/grouping-rows/index.js +2 -905
  48. package/lib/plugins/grouping-rows/index.js.map +1 -1
  49. package/lib/plugins/grouping-rows/types.d.ts +5 -0
  50. package/lib/plugins/grouping-rows/types.d.ts.map +1 -1
  51. package/lib/plugins/master-detail/index.js +1 -950
  52. package/lib/plugins/master-detail/index.js.map +1 -1
  53. package/lib/plugins/master-detail/types.d.ts +5 -0
  54. package/lib/plugins/master-detail/types.d.ts.map +1 -1
  55. package/lib/plugins/multi-sort/index.js +1 -553
  56. package/lib/plugins/multi-sort/index.js.map +1 -1
  57. package/lib/plugins/multi-sort/types.d.ts +5 -0
  58. package/lib/plugins/multi-sort/types.d.ts.map +1 -1
  59. package/lib/plugins/pinned-columns/index.js +1 -688
  60. package/lib/plugins/pinned-columns/index.js.map +1 -1
  61. package/lib/plugins/pinned-columns/types.d.ts +3 -0
  62. package/lib/plugins/pinned-columns/types.d.ts.map +1 -1
  63. package/lib/plugins/pinned-rows/index.js +1 -704
  64. package/lib/plugins/pinned-rows/index.js.map +1 -1
  65. package/lib/plugins/pinned-rows/types.d.ts +5 -0
  66. package/lib/plugins/pinned-rows/types.d.ts.map +1 -1
  67. package/lib/plugins/pivot/index.js +1 -1191
  68. package/lib/plugins/pivot/index.js.map +1 -1
  69. package/lib/plugins/pivot/types.d.ts +5 -0
  70. package/lib/plugins/pivot/types.d.ts.map +1 -1
  71. package/lib/plugins/print/index.js +1 -691
  72. package/lib/plugins/print/index.js.map +1 -1
  73. package/lib/plugins/print/types.d.ts +3 -0
  74. package/lib/plugins/print/types.d.ts.map +1 -1
  75. package/lib/plugins/reorder/index.js +1 -703
  76. package/lib/plugins/reorder/index.js.map +1 -1
  77. package/lib/plugins/reorder/types.d.ts +5 -0
  78. package/lib/plugins/reorder/types.d.ts.map +1 -1
  79. package/lib/plugins/responsive/index.js +1 -971
  80. package/lib/plugins/responsive/index.js.map +1 -1
  81. package/lib/plugins/responsive/types.d.ts +5 -0
  82. package/lib/plugins/responsive/types.d.ts.map +1 -1
  83. package/lib/plugins/row-reorder/index.js +1 -728
  84. package/lib/plugins/row-reorder/index.js.map +1 -1
  85. package/lib/plugins/row-reorder/types.d.ts +5 -0
  86. package/lib/plugins/row-reorder/types.d.ts.map +1 -1
  87. package/lib/plugins/selection/index.js +1 -1071
  88. package/lib/plugins/selection/index.js.map +1 -1
  89. package/lib/plugins/selection/types.d.ts +3 -0
  90. package/lib/plugins/selection/types.d.ts.map +1 -1
  91. package/lib/plugins/server-side/index.js +1 -521
  92. package/lib/plugins/server-side/index.js.map +1 -1
  93. package/lib/plugins/server-side/types.d.ts +5 -0
  94. package/lib/plugins/server-side/types.d.ts.map +1 -1
  95. package/lib/plugins/tree/index.js +1 -686
  96. package/lib/plugins/tree/index.js.map +1 -1
  97. package/lib/plugins/tree/types.d.ts +5 -0
  98. package/lib/plugins/tree/types.d.ts.map +1 -1
  99. package/lib/plugins/undo-redo/index.js +1 -584
  100. package/lib/plugins/undo-redo/index.js.map +1 -1
  101. package/lib/plugins/undo-redo/types.d.ts +5 -0
  102. package/lib/plugins/undo-redo/types.d.ts.map +1 -1
  103. package/lib/plugins/visibility/index.js +1 -792
  104. package/lib/plugins/visibility/index.js.map +1 -1
  105. package/lib/plugins/visibility/types.d.ts +5 -0
  106. package/lib/plugins/visibility/types.d.ts.map +1 -1
  107. package/package.json +1 -1
  108. package/umd/grid.all.umd.js +1 -186
  109. package/umd/grid.all.umd.js.map +1 -1
  110. package/umd/grid.umd.js +1 -90
  111. package/umd/grid.umd.js.map +1 -1
  112. package/umd/plugins/clipboard.umd.js +1 -6
  113. package/umd/plugins/clipboard.umd.js.map +1 -1
  114. package/umd/plugins/column-virtualization.umd.js +1 -1
  115. package/umd/plugins/column-virtualization.umd.js.map +1 -1
  116. package/umd/plugins/context-menu.umd.js +1 -1
  117. package/umd/plugins/context-menu.umd.js.map +1 -1
  118. package/umd/plugins/editing.umd.js +1 -1
  119. package/umd/plugins/editing.umd.js.map +1 -1
  120. package/umd/plugins/export.umd.js +1 -13
  121. package/umd/plugins/export.umd.js.map +1 -1
  122. package/umd/plugins/filtering.umd.js +1 -1
  123. package/umd/plugins/filtering.umd.js.map +1 -1
  124. package/umd/plugins/grouping-columns.umd.js +1 -1
  125. package/umd/plugins/grouping-columns.umd.js.map +1 -1
  126. package/umd/plugins/grouping-rows.umd.js +1 -4
  127. package/umd/plugins/grouping-rows.umd.js.map +1 -1
  128. package/umd/plugins/master-detail.umd.js +1 -1
  129. package/umd/plugins/master-detail.umd.js.map +1 -1
  130. package/umd/plugins/multi-sort.umd.js +1 -1
  131. package/umd/plugins/multi-sort.umd.js.map +1 -1
  132. package/umd/plugins/pinned-columns.umd.js +1 -1
  133. package/umd/plugins/pinned-columns.umd.js.map +1 -1
  134. package/umd/plugins/pinned-rows.umd.js +1 -1
  135. package/umd/plugins/pinned-rows.umd.js.map +1 -1
  136. package/umd/plugins/pivot.umd.js +1 -1
  137. package/umd/plugins/pivot.umd.js.map +1 -1
  138. package/umd/plugins/print.umd.js +1 -75
  139. package/umd/plugins/print.umd.js.map +1 -1
  140. package/umd/plugins/reorder.umd.js +1 -1
  141. package/umd/plugins/reorder.umd.js.map +1 -1
  142. package/umd/plugins/responsive.umd.js +1 -1
  143. package/umd/plugins/responsive.umd.js.map +1 -1
  144. package/umd/plugins/row-reorder.umd.js +1 -1
  145. package/umd/plugins/row-reorder.umd.js.map +1 -1
  146. package/umd/plugins/selection.umd.js +1 -3
  147. package/umd/plugins/selection.umd.js.map +1 -1
  148. package/umd/plugins/server-side.umd.js +1 -1
  149. package/umd/plugins/server-side.umd.js.map +1 -1
  150. package/umd/plugins/tree.umd.js +1 -1
  151. package/umd/plugins/tree.umd.js.map +1 -1
  152. package/umd/plugins/undo-redo.umd.js +1 -1
  153. package/umd/plugins/undo-redo.umd.js.map +1 -1
  154. package/umd/plugins/visibility.umd.js +1 -1
  155. package/umd/plugins/visibility.umd.js.map +1 -1
@@ -1,951 +1,2 @@
1
- const y = /{{\s*([^}]+)\s*}}/g, g = "__DG_EMPTY__", D = /^[\w$. '?+\-*/%:()!<>=,&|]+$/, S = /__(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/, H = /* @__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
- ]), x = /^on\w+$/i, T = /* @__PURE__ */ new Set(["href", "src", "action", "formaction", "data", "srcdoc", "xlink:href", "poster", "srcset"]), L = /^\s*(javascript|vbscript|data|blob):/i;
27
- function I(n) {
28
- if (!n || typeof n != "string") return "";
29
- if (n.indexOf("<") === -1) return n;
30
- const e = document.createElement("template");
31
- return e.innerHTML = n, k(e.content), e.innerHTML;
32
- }
33
- function k(n) {
34
- const e = [], t = n.querySelectorAll("*");
35
- for (const i of t) {
36
- const s = i.tagName.toLowerCase();
37
- if (H.has(s)) {
38
- e.push(i);
39
- continue;
40
- }
41
- if ((s === "svg" || i.namespaceURI === "http://www.w3.org/2000/svg") && Array.from(i.attributes).some(
42
- (o) => x.test(o.name) || o.name === "href" || o.name === "xlink:href"
43
- )) {
44
- e.push(i);
45
- continue;
46
- }
47
- const r = [];
48
- for (const a of i.attributes) {
49
- const o = a.name.toLowerCase();
50
- if (x.test(o)) {
51
- r.push(a.name);
52
- continue;
53
- }
54
- if (T.has(o) && L.test(a.value)) {
55
- r.push(a.name);
56
- continue;
57
- }
58
- if (o === "style" && /expression\s*\(|javascript:|behavior\s*:/i.test(a.value)) {
59
- r.push(a.name);
60
- continue;
61
- }
62
- }
63
- r.forEach((a) => i.removeAttribute(a));
64
- }
65
- e.forEach((i) => i.remove());
66
- }
67
- function O(n, e) {
68
- if (!n || n.indexOf("{{") === -1) return n;
69
- const t = [], i = n.replace(y, (o, c) => {
70
- const d = N(c, e);
71
- return t.push({ expr: c.trim(), result: d }), d;
72
- }), s = q(i), r = t.length && t.every((o) => o.result === "" || o.result === g);
73
- return w.test(n) || r ? "" : s;
74
- }
75
- function N(n, e) {
76
- if (n = (n || "").trim(), !n || w.test(n)) return g;
77
- if (n === "value") return e.value == null ? g : String(e.value);
78
- if (n.startsWith("row.") && !/[()?]/.test(n) && !n.includes(":")) {
79
- const i = n.slice(4), s = e.row ? e.row[i] : void 0;
80
- return s == null ? g : String(s);
81
- }
82
- if (n.length > 80 || !D.test(n) || S.test(n)) return g;
83
- const t = n.match(/\./g);
84
- if (t && t.length > 1) return g;
85
- try {
86
- const s = new Function("value", "row", `return (${n});`)(e.value, e.row), r = s == null ? "" : String(s);
87
- return w.test(r) ? g : r || g;
88
- } catch {
89
- return g;
90
- }
91
- }
92
- const w = /Reflect|Proxy|ownKeys/;
93
- function q(n) {
94
- return n && n.replace(new RegExp(g, "g"), "").replace(/Reflect\.[^<>{}\s]+|\bProxy\b|ownKeys\([^)]*\)/g, "");
95
- }
96
- const R = '<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>', M = {
97
- expand: "▶",
98
- collapse: "▼",
99
- sortAsc: "▲",
100
- sortDesc: "▼",
101
- sortNone: "⇅",
102
- submenuArrow: "▶",
103
- dragHandle: "⋮⋮",
104
- toolPanel: "☰",
105
- filter: R,
106
- filterActive: R,
107
- print: "🖨️"
108
- };
109
- class P {
110
- /**
111
- * Plugin dependencies - declare other plugins this one requires.
112
- *
113
- * Dependencies are validated when the plugin is attached.
114
- * Required dependencies throw an error if missing.
115
- * Optional dependencies log an info message if missing.
116
- *
117
- * @example
118
- * ```typescript
119
- * static readonly dependencies: PluginDependency[] = [
120
- * { name: 'editing', required: true, reason: 'Tracks cell edits for undo/redo' },
121
- * { name: 'selection', required: false, reason: 'Enables selection-based undo' },
122
- * ];
123
- * ```
124
- */
125
- static dependencies;
126
- /**
127
- * Plugin manifest - declares owned properties, config rules, and hook priorities.
128
- *
129
- * This is read by the configuration validator to:
130
- * - Validate that required plugins are loaded when their properties are used
131
- * - Execute configRules to detect invalid/conflicting settings
132
- * - Order hook execution based on priority
133
- *
134
- * @example
135
- * ```typescript
136
- * static override readonly manifest: PluginManifest<MyConfig> = {
137
- * ownedProperties: [
138
- * { property: 'myProp', level: 'column', description: 'the "myProp" column property' },
139
- * ],
140
- * configRules: [
141
- * { id: 'myPlugin/conflict', severity: 'warn', message: '...', check: (c) => c.a && c.b },
142
- * ],
143
- * };
144
- * ```
145
- */
146
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
147
- static manifest;
148
- /**
149
- * Plugin version - defaults to grid version for built-in plugins.
150
- * Third-party plugins can override with their own semver.
151
- */
152
- version = typeof __GRID_VERSION__ < "u" ? __GRID_VERSION__ : "dev";
153
- /** CSS styles to inject into the grid's shadow DOM */
154
- styles;
155
- /** Custom cell renderers keyed by type name */
156
- cellRenderers;
157
- /** Custom header renderers keyed by type name */
158
- headerRenderers;
159
- /** Custom cell editors keyed by type name */
160
- cellEditors;
161
- /** The grid instance this plugin is attached to */
162
- grid;
163
- /** Plugin configuration - merged with defaults in attach() */
164
- config;
165
- /** User-provided configuration from constructor */
166
- userConfig;
167
- /**
168
- * Plugin-level AbortController for event listener cleanup.
169
- * Created fresh in attach(), aborted in detach().
170
- * This ensures event listeners are properly cleaned up when plugins are re-attached.
171
- */
172
- #e;
173
- /**
174
- * Default configuration - subclasses should override this getter.
175
- * Note: This must be a getter (not property initializer) for proper inheritance
176
- * since property initializers run after parent constructor.
177
- */
178
- get defaultConfig() {
179
- return {};
180
- }
181
- constructor(e = {}) {
182
- this.userConfig = e;
183
- }
184
- /**
185
- * Called when the plugin is attached to a grid.
186
- * Override to set up event listeners, initialize state, etc.
187
- *
188
- * @example
189
- * ```ts
190
- * attach(grid: GridElement): void {
191
- * super.attach(grid);
192
- * // Set up document-level listeners with auto-cleanup
193
- * document.addEventListener('keydown', this.handleEscape, {
194
- * signal: this.disconnectSignal
195
- * });
196
- * }
197
- * ```
198
- */
199
- attach(e) {
200
- this.#e?.abort(), this.#e = new AbortController(), this.grid = e, this.config = { ...this.defaultConfig, ...this.userConfig };
201
- }
202
- /**
203
- * Called when the plugin is detached from a grid.
204
- * Override to clean up event listeners, timers, etc.
205
- *
206
- * @example
207
- * ```ts
208
- * detach(): void {
209
- * // Clean up any state not handled by disconnectSignal
210
- * this.selectedRows.clear();
211
- * this.cache = null;
212
- * }
213
- * ```
214
- */
215
- detach() {
216
- this.#e?.abort(), this.#e = void 0;
217
- }
218
- /**
219
- * Get another plugin instance from the same grid.
220
- * Use for inter-plugin communication.
221
- *
222
- * @example
223
- * ```ts
224
- * const selection = this.getPlugin(SelectionPlugin);
225
- * if (selection) {
226
- * const selectedRows = selection.getSelectedRows();
227
- * }
228
- * ```
229
- */
230
- getPlugin(e) {
231
- return this.grid?.getPlugin(e);
232
- }
233
- /**
234
- * Emit a custom event from the grid.
235
- */
236
- emit(e, t) {
237
- this.grid?.dispatchEvent?.(new CustomEvent(e, { detail: t, bubbles: !0 }));
238
- }
239
- /**
240
- * Emit a cancelable custom event from the grid.
241
- * @returns `true` if the event was cancelled (preventDefault called), `false` otherwise
242
- */
243
- emitCancelable(e, t) {
244
- const i = new CustomEvent(e, { detail: t, bubbles: !0, cancelable: !0 });
245
- return this.grid?.dispatchEvent?.(i), i.defaultPrevented;
246
- }
247
- // =========================================================================
248
- // Event Bus - Plugin-to-Plugin Communication
249
- // =========================================================================
250
- /**
251
- * Subscribe to an event from another plugin.
252
- * The subscription is automatically cleaned up when this plugin is detached.
253
- *
254
- * @category Plugin Development
255
- * @param eventType - The event type to listen for (e.g., 'filter-change')
256
- * @param callback - The callback to invoke when the event is emitted
257
- *
258
- * @example
259
- * ```typescript
260
- * // In attach() or other initialization
261
- * this.on('filter-change', (detail) => {
262
- * console.log('Filter changed:', detail);
263
- * });
264
- * ```
265
- */
266
- on(e, t) {
267
- this.grid?._pluginManager?.subscribe(this, e, t);
268
- }
269
- /**
270
- * Unsubscribe from a plugin event.
271
- *
272
- * @category Plugin Development
273
- * @param eventType - The event type to stop listening for
274
- *
275
- * @example
276
- * ```typescript
277
- * this.off('filter-change');
278
- * ```
279
- */
280
- off(e) {
281
- this.grid?._pluginManager?.unsubscribe(this, e);
282
- }
283
- /**
284
- * Emit an event to other plugins via the Event Bus.
285
- * This is for inter-plugin communication only; it does NOT dispatch DOM events.
286
- * Use `emit()` to dispatch DOM events that external consumers can listen to.
287
- *
288
- * @category Plugin Development
289
- * @param eventType - The event type to emit (should be declared in manifest.events)
290
- * @param detail - The event payload
291
- *
292
- * @example
293
- * ```typescript
294
- * // Emit to other plugins (not DOM)
295
- * this.emitPluginEvent('filter-change', { field: 'name', value: 'Alice' });
296
- *
297
- * // For DOM events that consumers can addEventListener to:
298
- * this.emit('filter-change', { field: 'name', value: 'Alice' });
299
- * ```
300
- */
301
- emitPluginEvent(e, t) {
302
- this.grid?._pluginManager?.emitPluginEvent(e, t);
303
- }
304
- /**
305
- * Request a re-render of the grid.
306
- * Uses ROWS phase - does NOT trigger processColumns hooks.
307
- */
308
- requestRender() {
309
- this.grid?.requestRender?.();
310
- }
311
- /**
312
- * Request a columns re-render of the grid.
313
- * Uses COLUMNS phase - triggers processColumns hooks.
314
- * Use this when your plugin needs to reprocess column configuration.
315
- */
316
- requestColumnsRender() {
317
- this.grid?.requestColumnsRender?.();
318
- }
319
- /**
320
- * Request a re-render and restore focus styling afterward.
321
- * Use this when a plugin action (like expand/collapse) triggers a render
322
- * but needs to maintain keyboard navigation focus.
323
- */
324
- requestRenderWithFocus() {
325
- this.grid?.requestRenderWithFocus?.();
326
- }
327
- /**
328
- * Request a lightweight style update without rebuilding DOM.
329
- * Use this instead of requestRender() when only CSS classes need updating.
330
- */
331
- requestAfterRender() {
332
- this.grid?.requestAfterRender?.();
333
- }
334
- /**
335
- * Get the current rows from the grid.
336
- */
337
- get rows() {
338
- return this.grid?.rows ?? [];
339
- }
340
- /**
341
- * Get the original unfiltered/unprocessed rows from the grid.
342
- * Use this when you need all source data regardless of active filters.
343
- */
344
- get sourceRows() {
345
- return this.grid?.sourceRows ?? [];
346
- }
347
- /**
348
- * Get the current columns from the grid.
349
- */
350
- get columns() {
351
- return this.grid?.columns ?? [];
352
- }
353
- /**
354
- * Get only visible columns from the grid (excludes hidden).
355
- * Use this for rendering that needs to match the grid template.
356
- */
357
- get visibleColumns() {
358
- return this.grid?._visibleColumns ?? [];
359
- }
360
- /**
361
- * Get the grid as an HTMLElement for direct DOM operations.
362
- * Use sparingly - prefer the typed GridElementRef API when possible.
363
- *
364
- * @example
365
- * ```ts
366
- * const width = this.gridElement.clientWidth;
367
- * this.gridElement.classList.add('my-plugin-active');
368
- * ```
369
- */
370
- get gridElement() {
371
- return this.grid;
372
- }
373
- /**
374
- * Get the disconnect signal for event listener cleanup.
375
- * This signal is aborted when the grid disconnects from the DOM.
376
- * Use this when adding event listeners that should be cleaned up automatically.
377
- *
378
- * Best for:
379
- * - Document/window-level listeners added in attach()
380
- * - Listeners on the grid element itself
381
- * - Any listener that should persist across renders
382
- *
383
- * Not needed for:
384
- * - Listeners on elements created in afterRender() (removed with element)
385
- *
386
- * @example
387
- * element.addEventListener('click', handler, { signal: this.disconnectSignal });
388
- * document.addEventListener('keydown', handler, { signal: this.disconnectSignal });
389
- */
390
- get disconnectSignal() {
391
- return this.#e?.signal ?? this.grid?.disconnectSignal;
392
- }
393
- /**
394
- * Get the grid-level icons configuration.
395
- * Returns merged icons (user config + defaults).
396
- */
397
- get gridIcons() {
398
- const e = this.grid?.gridConfig?.icons ?? {};
399
- return { ...M, ...e };
400
- }
401
- // #region Animation Helpers
402
- /**
403
- * Check if animations are enabled at the grid level.
404
- * Respects gridConfig.animation.mode and the CSS variable set by the grid.
405
- *
406
- * Plugins should use this to skip animations when:
407
- * - Animation mode is 'off' or `false`
408
- * - User prefers reduced motion and mode is 'reduced-motion' (default)
409
- *
410
- * @example
411
- * ```ts
412
- * private get animationStyle(): 'slide' | 'fade' | false {
413
- * if (!this.isAnimationEnabled) return false;
414
- * return this.config.animation ?? 'slide';
415
- * }
416
- * ```
417
- */
418
- get isAnimationEnabled() {
419
- const e = this.grid?.effectiveConfig?.animation?.mode ?? "reduced-motion";
420
- if (e === !1 || e === "off") return !1;
421
- if (e === !0 || e === "on") return !0;
422
- const t = this.gridElement;
423
- return t ? getComputedStyle(t).getPropertyValue("--tbw-animation-enabled").trim() !== "0" : !0;
424
- }
425
- /**
426
- * Get the animation duration in milliseconds from CSS variable.
427
- * Falls back to 200ms if not set.
428
- *
429
- * Plugins can use this for their animation timing to stay consistent
430
- * with the grid-level animation.duration setting.
431
- *
432
- * @example
433
- * ```ts
434
- * element.animate(keyframes, { duration: this.animationDuration });
435
- * ```
436
- */
437
- get animationDuration() {
438
- const e = this.gridElement;
439
- if (e) {
440
- const t = getComputedStyle(e).getPropertyValue("--tbw-animation-duration").trim(), i = parseInt(t, 10);
441
- if (!isNaN(i)) return i;
442
- }
443
- return 200;
444
- }
445
- // #endregion
446
- /**
447
- * Resolve an icon value to string or HTMLElement.
448
- * Checks plugin config first, then grid-level icons, then defaults.
449
- *
450
- * @param iconKey - The icon key in GridIcons (e.g., 'expand', 'collapse')
451
- * @param pluginOverride - Optional plugin-level override
452
- * @returns The resolved icon value
453
- */
454
- resolveIcon(e, t) {
455
- return t !== void 0 ? t : this.gridIcons[e];
456
- }
457
- /**
458
- * Set an icon value on an element.
459
- * Handles both string (text/HTML) and HTMLElement values.
460
- *
461
- * @param element - The element to set the icon on
462
- * @param icon - The icon value (string or HTMLElement)
463
- */
464
- setIcon(e, t) {
465
- typeof t == "string" ? e.innerHTML = t : t instanceof HTMLElement && (e.innerHTML = "", e.appendChild(t.cloneNode(!0)));
466
- }
467
- /**
468
- * Log a warning message.
469
- */
470
- warn(e) {
471
- console.warn(`[tbw-grid:${this.name}] ${e}`);
472
- }
473
- // #endregion
474
- }
475
- const v = "__tbw_expander", F = 32;
476
- function C(n) {
477
- return n.field === v;
478
- }
479
- function U(n) {
480
- return n.find(C);
481
- }
482
- function z(n) {
483
- return {
484
- field: v,
485
- header: "",
486
- // No header text - visually merges with next column
487
- width: F,
488
- resizable: !1,
489
- sortable: !1,
490
- filterable: !1,
491
- // No filter button for expander column
492
- meta: {
493
- lockPosition: !0,
494
- suppressMovable: !0,
495
- expanderColumn: !0,
496
- expanderPlugin: n,
497
- utility: !0
498
- // Marks this as a utility column (excluded from selection, clipboard, etc.)
499
- }
500
- };
501
- }
502
- function E(n, e) {
503
- const t = new Set(n);
504
- return t.has(e) ? t.delete(e) : t.add(e), t;
505
- }
506
- function G(n, e) {
507
- const t = new Set(n);
508
- return t.add(e), t;
509
- }
510
- function W(n, e) {
511
- const t = new Set(n);
512
- return t.delete(e), t;
513
- }
514
- function $(n, e) {
515
- return n.has(e);
516
- }
517
- function j(n, e, t, i) {
518
- const s = document.createElement("div");
519
- s.className = "master-detail-row", s.setAttribute("data-detail-for", String(e)), s.setAttribute("role", "row");
520
- const r = document.createElement("div");
521
- r.className = "master-detail-cell", r.setAttribute("role", "cell"), r.style.gridColumn = `1 / ${i + 1}`;
522
- const a = t(n, e);
523
- return typeof a == "string" ? r.innerHTML = a : a instanceof HTMLElement && r.appendChild(a), s.appendChild(r), s;
524
- }
525
- const B = "@layer tbw-plugins{tbw-grid .cell[data-field=__tbw_expander]{border-right:none!important;padding:0;display:flex;align-items:center;justify-content:center}tbw-grid .header-row .cell[data-field=__tbw_expander]{display:none}tbw-grid .header-row .cell[data-field=__tbw_expander]+.cell{grid-column:1 / 3}tbw-grid .master-detail-expander{display:flex;align-items:center;justify-content:center;width:100%;height:100%}tbw-grid .master-detail-toggle{cursor:pointer;opacity:.7;-webkit-user-select:none;user-select:none;display:inline-flex;align-items:center;justify-content:center}tbw-grid .master-detail-toggle:hover{opacity:1}tbw-grid .master-detail-row{grid-column:1 / -1;display:grid;background:var(--tbw-master-detail-bg, var(--tbw-color-row-alt));border-bottom:1px solid var(--tbw-master-detail-border, var(--tbw-color-border));overflow:hidden}tbw-grid .master-detail-cell{padding:var(--tbw-detail-padding, var(--tbw-spacing-xl, 1rem));overflow:auto}tbw-grid .master-detail-row.tbw-expanding{animation:tbw-detail-expand var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}tbw-grid .master-detail-row.tbw-collapsing{animation:tbw-detail-collapse var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-detail-expand{0%{opacity:0;max-height:0;padding-top:0;padding-bottom:0}to{opacity:1;max-height:var(--tbw-detail-max-height, 31.25rem);padding-top:var(--tbw-detail-padding, var(--tbw-spacing-xl, 1rem));padding-bottom:var(--tbw-detail-padding, var(--tbw-spacing-xl, 1rem))}}@keyframes tbw-detail-collapse{0%{opacity:1;max-height:var(--tbw-detail-max-height, 31.25rem)}to{opacity:0;max-height:0}}}";
526
- class A extends P {
527
- /** @internal */
528
- name = "masterDetail";
529
- /** @internal */
530
- styles = B;
531
- /** @internal */
532
- get defaultConfig() {
533
- return {
534
- detailHeight: "auto",
535
- expandOnRowClick: !1,
536
- collapseOnClickOutside: !1,
537
- // Note: showExpandColumn is intentionally NOT defaulted here.
538
- // If undefined, processColumns() adds expander only when detailRenderer is provided.
539
- // Set to true for framework adapters that register renderers asynchronously.
540
- animation: "slide"
541
- // Plugin's own default
542
- };
543
- }
544
- // #region Light DOM Parsing
545
- /**
546
- * Called when plugin is attached to the grid.
547
- * Parses light DOM for `<tbw-grid-detail>` elements to configure detail templates.
548
- * @internal
549
- */
550
- attach(e) {
551
- super.attach(e), this.parseLightDomDetail();
552
- }
553
- /**
554
- * Parse `<tbw-grid-detail>` elements from the grid's light DOM.
555
- *
556
- * Allows declarative configuration:
557
- * ```html
558
- * <tbw-grid [rows]="data">
559
- * <tbw-grid-detail>
560
- * <div class="detail-content">
561
- * <p>Name: {{ row.name }}</p>
562
- * <p>Email: {{ row.email }}</p>
563
- * </div>
564
- * </tbw-grid-detail>
565
- * </tbw-grid>
566
- * ```
567
- *
568
- * Attributes:
569
- * - `animation`: 'slide' | 'fade' | 'false' (default: 'slide')
570
- * - `show-expand-column`: 'true' | 'false' (default: 'true')
571
- * - `expand-on-row-click`: 'true' | 'false' (default: 'false')
572
- * - `collapse-on-click-outside`: 'true' | 'false' (default: 'false')
573
- * - `height`: number (pixels) or 'auto' (default: 'auto')
574
- */
575
- parseLightDomDetail() {
576
- const e = this.grid;
577
- if (!e || typeof e.querySelector != "function") return;
578
- const t = e.querySelector("tbw-grid-detail");
579
- if (!t) return;
580
- const i = e;
581
- if (i.__frameworkAdapter?.parseDetailElement) {
582
- const u = i.__frameworkAdapter.parseDetailElement(t);
583
- if (u) {
584
- this.config = { ...this.config, detailRenderer: u };
585
- return;
586
- }
587
- }
588
- const s = t.getAttribute("animation"), r = t.getAttribute("show-expand-column"), a = t.getAttribute("expand-on-row-click"), o = t.getAttribute("collapse-on-click-outside"), c = t.getAttribute("height"), d = {};
589
- s !== null && (d.animation = s === "false" ? !1 : s), r !== null && (d.showExpandColumn = r !== "false"), a !== null && (d.expandOnRowClick = a === "true"), o !== null && (d.collapseOnClickOutside = o === "true"), c !== null && (d.detailHeight = c === "auto" ? "auto" : parseInt(c, 10));
590
- const l = t.innerHTML.trim();
591
- l && !this.config.detailRenderer && (d.detailRenderer = (u, h) => {
592
- const f = O(l, { value: u, row: u });
593
- return I(f);
594
- }), Object.keys(d).length > 0 && (this.config = { ...this.config, ...d });
595
- }
596
- // #endregion
597
- // #region Animation Helpers
598
- /**
599
- * Get expand/collapse animation style from plugin config.
600
- * Uses base class isAnimationEnabled to respect grid-level settings.
601
- */
602
- get animationStyle() {
603
- return this.isAnimationEnabled ? this.config.animation ?? "slide" : !1;
604
- }
605
- /**
606
- * Apply expand animation to a detail element.
607
- * Returns true if animation was applied, false if skipped.
608
- * When animated, height measurement is deferred to animationend to avoid
609
- * measuring during the max-height: 0 CSS animation constraint.
610
- */
611
- animateExpand(e, t, i) {
612
- if (!this.isAnimationEnabled || this.animationStyle === !1) return !1;
613
- e.classList.add("tbw-expanding");
614
- let s = !1;
615
- const r = () => {
616
- s || (s = !0, e.classList.remove("tbw-expanding"), t !== void 0 && i !== void 0 && this.#e(e, t, i));
617
- };
618
- return e.addEventListener("animationend", r, { once: !0 }), setTimeout(r, this.animationDuration + 50), !0;
619
- }
620
- /**
621
- * Apply collapse animation to a detail element and remove after animation.
622
- */
623
- animateCollapse(e, t) {
624
- if (!this.isAnimationEnabled || this.animationStyle === !1) {
625
- t();
626
- return;
627
- }
628
- e.classList.add("tbw-collapsing");
629
- const i = () => {
630
- e.classList.remove("tbw-collapsing"), t();
631
- };
632
- e.addEventListener("animationend", i, { once: !0 }), setTimeout(i, this.animationDuration + 50);
633
- }
634
- /**
635
- * Measure a detail element's height and update the position cache if it changed.
636
- * Used after layout settles (RAF) or after animation completes (animationend).
637
- */
638
- #e(e, t, i) {
639
- if (!e.isConnected) return;
640
- const s = e.offsetHeight;
641
- if (s > 0) {
642
- const r = this.measuredDetailHeights.get(t);
643
- this.measuredDetailHeights.set(t, s), r !== s && this.grid.invalidateRowHeight(i);
644
- }
645
- }
646
- // #endregion
647
- // #region Internal State
648
- expandedRows = /* @__PURE__ */ new Set();
649
- detailElements = /* @__PURE__ */ new Map();
650
- /** Cached measured heights - persists even when elements are virtualized out */
651
- measuredDetailHeights = /* @__PURE__ */ new Map();
652
- /** Rows that were just expanded by user action and should animate.
653
- * Prevents re-animation when rows scroll back into the virtual window. */
654
- rowsToAnimate = /* @__PURE__ */ new Set();
655
- /** Default height for detail rows when not configured */
656
- static DEFAULT_DETAIL_HEIGHT = 150;
657
- /**
658
- * Get the estimated height for a detail row.
659
- * Uses cached measured height when available (survives virtualization).
660
- * Avoids reading offsetHeight during CSS animations to prevent poisoning the cache.
661
- */
662
- getDetailHeight(e) {
663
- const t = this.detailElements.get(e);
664
- if (t && !(t.classList.contains("tbw-expanding") || t.classList.contains("tbw-collapsing"))) {
665
- const r = t.offsetHeight;
666
- if (r > 0)
667
- return this.measuredDetailHeights.set(e, r), r;
668
- }
669
- const i = this.measuredDetailHeights.get(e);
670
- return i && i > 0 ? i : typeof this.config?.detailHeight == "number" ? this.config.detailHeight : A.DEFAULT_DETAIL_HEIGHT;
671
- }
672
- /**
673
- * Toggle a row's detail and emit event.
674
- */
675
- toggleAndEmit(e, t) {
676
- this.expandedRows = E(this.expandedRows, e);
677
- const i = this.expandedRows.has(e);
678
- i && this.rowsToAnimate.add(e), this.emit("detail-expand", {
679
- rowIndex: t,
680
- row: e,
681
- expanded: i
682
- }), this.requestRender();
683
- }
684
- // #endregion
685
- // #region Lifecycle
686
- /** @internal */
687
- detach() {
688
- this.expandedRows.clear(), this.detailElements.clear(), this.measuredDetailHeights.clear(), this.rowsToAnimate.clear();
689
- }
690
- // #endregion
691
- // #region Hooks
692
- /** @internal */
693
- processColumns(e) {
694
- if (!(this.config.showExpandColumn === !0 || this.config.showExpandColumn !== !1 && !!this.config.detailRenderer))
695
- return [...e];
696
- const i = [...e];
697
- if (U(i))
698
- return i;
699
- const r = z(this.name);
700
- return r.viewRenderer = (a) => {
701
- const { row: o } = a, c = this.expandedRows.has(o), d = document.createElement("span");
702
- d.className = "master-detail-expander expander-cell";
703
- const l = document.createElement("span");
704
- return l.className = `master-detail-toggle${c ? " expanded" : ""}`, this.setIcon(l, this.resolveIcon(c ? "collapse" : "expand")), l.setAttribute("role", "button"), l.setAttribute("tabindex", "0"), l.setAttribute("aria-expanded", String(c)), l.setAttribute("aria-label", c ? "Collapse details" : "Expand details"), d.appendChild(l), d;
705
- }, [r, ...i];
706
- }
707
- /** @internal */
708
- onRowClick(e) {
709
- if (!(!this.config.expandOnRowClick || !this.config.detailRenderer))
710
- return this.toggleAndEmit(e.row, e.rowIndex), !1;
711
- }
712
- /** @internal */
713
- onCellClick(e) {
714
- if (e.originalEvent?.target?.classList.contains("master-detail-toggle"))
715
- return this.toggleAndEmit(e.row, e.rowIndex), !0;
716
- this.expandedRows.size > 0 && queueMicrotask(() => this.#t());
717
- }
718
- /** @internal */
719
- onKeyDown(e) {
720
- if (e.key !== " ") return;
721
- const t = this.grid._focusCol, i = this.grid._focusRow, s = this.visibleColumns[t];
722
- if (!s || !C(s)) return;
723
- const r = this.rows[i];
724
- if (r)
725
- return e.preventDefault(), this.toggleAndEmit(r, i), this.requestRenderWithFocus(), !0;
726
- }
727
- /** @internal */
728
- afterRender() {
729
- this.#t();
730
- }
731
- /**
732
- * Called on scroll to sync detail elements with visible rows.
733
- * Removes details for rows that scrolled out of view and reattaches for visible rows.
734
- * @internal
735
- */
736
- onScrollRender() {
737
- !this.config.detailRenderer || this.expandedRows.size === 0 || this.#t();
738
- }
739
- /**
740
- * Full sync of detail rows - cleans up stale elements and creates new ones.
741
- * Detail rows are inserted as siblings AFTER their master row to survive row rebuilds.
742
- *
743
- * PERF: Uses the grid's row pool (_rowPool) and virtual window (_virtualization.start/end)
744
- * to avoid querySelectorAll on every scroll frame. The pool is index-aligned with the
745
- * virtual window, so pool[i] corresponds to row index (start + i).
746
- */
747
- #t() {
748
- if (!this.config.detailRenderer) return;
749
- const e = this.gridElement?.querySelector(".rows");
750
- if (!e) return;
751
- const t = this.grid, i = t._rowPool, s = t._virtualization?.start ?? 0, r = t._virtualization?.end ?? 0, a = this.columns.length, o = s, c = r, d = /* @__PURE__ */ new Map();
752
- if (i) {
753
- const l = Math.min(i.length, c - o);
754
- for (let u = 0; u < l; u++) {
755
- const h = i[u];
756
- h.parentNode === e && d.set(o + u, h);
757
- }
758
- } else {
759
- const l = e.querySelectorAll(".data-grid-row");
760
- for (const u of l) {
761
- const h = u.querySelector(".cell[data-row]"), f = h ? parseInt(h.getAttribute("data-row") ?? "-1", 10) : -1;
762
- f >= 0 && d.set(f, u);
763
- }
764
- }
765
- for (const [l, u] of this.detailElements) {
766
- const h = this.rows.indexOf(l), f = this.expandedRows.has(l), m = h >= 0 && d.has(h);
767
- if (!f || !m) {
768
- const p = this.grid.__frameworkAdapter;
769
- if (p?.unmount) {
770
- const b = u.querySelector(".master-detail-cell")?.firstElementChild;
771
- b && p.unmount(b);
772
- }
773
- u.parentNode && u.remove(), this.detailElements.delete(l);
774
- }
775
- }
776
- for (const [l, u] of d) {
777
- const h = this.rows[l];
778
- if (!h || !this.expandedRows.has(h)) continue;
779
- const f = this.detailElements.get(h);
780
- if (f) {
781
- f.previousElementSibling !== u && u.after(f);
782
- continue;
783
- }
784
- const m = j(h, l, this.config.detailRenderer, a);
785
- typeof this.config.detailHeight == "number" && (m.style.height = `${this.config.detailHeight}px`), u.after(m), this.detailElements.set(h, m);
786
- const p = this.rowsToAnimate.has(h);
787
- p && this.rowsToAnimate.delete(h), p && this.animateExpand(m, h, l) || requestAnimationFrame(() => {
788
- this.#e(m, h, l);
789
- });
790
- }
791
- }
792
- /**
793
- * Return total extra height from all expanded detail rows.
794
- * Used by grid virtualization to adjust scrollbar height.
795
- *
796
- * @deprecated Use getRowHeight() instead. This hook will be removed in v3.0.
797
- */
798
- getExtraHeight() {
799
- let e = 0;
800
- for (const t of this.expandedRows)
801
- e += this.getDetailHeight(t);
802
- return e;
803
- }
804
- /**
805
- * Return extra height that appears before a given row index.
806
- * This is the sum of heights of all expanded details whose parent row is before the given index.
807
- *
808
- * @deprecated Use getRowHeight() instead. This hook will be removed in v3.0.
809
- */
810
- getExtraHeightBefore(e) {
811
- let t = 0;
812
- for (const i of this.expandedRows) {
813
- const s = this.rows.indexOf(i);
814
- s >= 0 && s < e && (t += this.getDetailHeight(i));
815
- }
816
- return t;
817
- }
818
- /**
819
- * Get the height of a specific row, including any expanded detail content.
820
- * Always returns a height to ensure the position cache uses plugin-controlled values
821
- * rather than stale DOM measurements.
822
- *
823
- * @param row - The row data
824
- * @param _index - The row index (unused, but part of the interface)
825
- * @returns The row height in pixels (base height for collapsed, base + detail for expanded)
826
- */
827
- getRowHeight(e, t) {
828
- if (!this.expandedRows.has(e))
829
- return;
830
- const s = this.grid.defaultRowHeight ?? 28, r = this.getDetailHeight(e);
831
- return s + r;
832
- }
833
- /**
834
- * Adjust the virtualization start index to keep expanded row visible while its detail is visible.
835
- * This ensures the detail scrolls smoothly out of view instead of disappearing abruptly.
836
- */
837
- adjustVirtualStart(e, t, i) {
838
- if (this.expandedRows.size === 0) return e;
839
- const s = this.grid?._virtualization?.positionCache;
840
- let r = e;
841
- if (s && s.length > 0)
842
- for (const a of this.expandedRows) {
843
- const o = this.rows.indexOf(a);
844
- if (o < 0 || o >= e) continue;
845
- s[o].offset + s[o].height > t && o < r && (r = o);
846
- }
847
- else {
848
- const a = [];
849
- for (const c of this.expandedRows) {
850
- const d = this.rows.indexOf(c);
851
- d >= 0 && a.push({ index: d, row: c });
852
- }
853
- a.sort((c, d) => c.index - d.index);
854
- let o = 0;
855
- for (const { index: c, row: d } of a) {
856
- const l = c * i + o, u = this.getDetailHeight(d), h = l + i + u;
857
- o += u, !(c >= e) && h > t && c < r && (r = c);
858
- }
859
- }
860
- return r;
861
- }
862
- // #endregion
863
- // #region Public API
864
- /**
865
- * Expand the detail row at the given index.
866
- * @param rowIndex - Index of the row to expand
867
- */
868
- expand(e) {
869
- const t = this.rows[e];
870
- t && (this.rowsToAnimate.add(t), this.expandedRows = G(this.expandedRows, t), this.requestRender());
871
- }
872
- /**
873
- * Collapse the detail row at the given index.
874
- * @param rowIndex - Index of the row to collapse
875
- */
876
- collapse(e) {
877
- const t = this.rows[e];
878
- t && (this.expandedRows = W(this.expandedRows, t), this.requestRender());
879
- }
880
- /**
881
- * Toggle the detail row at the given index.
882
- * @param rowIndex - Index of the row to toggle
883
- */
884
- toggle(e) {
885
- const t = this.rows[e];
886
- t && (this.expandedRows = E(this.expandedRows, t), this.expandedRows.has(t) && this.rowsToAnimate.add(t), this.requestRender());
887
- }
888
- /**
889
- * Check if the detail row at the given index is expanded.
890
- * @param rowIndex - Index of the row to check
891
- * @returns Whether the detail row is expanded
892
- */
893
- isExpanded(e) {
894
- const t = this.rows[e];
895
- return t ? $(this.expandedRows, t) : !1;
896
- }
897
- /**
898
- * Expand all detail rows.
899
- */
900
- expandAll() {
901
- for (const e of this.rows)
902
- this.rowsToAnimate.add(e), this.expandedRows.add(e);
903
- this.requestRender();
904
- }
905
- /**
906
- * Collapse all detail rows.
907
- */
908
- collapseAll() {
909
- this.expandedRows.clear(), this.requestRender();
910
- }
911
- /**
912
- * Get the indices of all expanded rows.
913
- * @returns Array of row indices that are expanded
914
- */
915
- getExpandedRows() {
916
- const e = [];
917
- for (const t of this.expandedRows) {
918
- const i = this.rows.indexOf(t);
919
- i >= 0 && e.push(i);
920
- }
921
- return e;
922
- }
923
- /**
924
- * Get the detail element for a specific row.
925
- * @param rowIndex - Index of the row
926
- * @returns The detail HTMLElement or undefined
927
- */
928
- getDetailElement(e) {
929
- const t = this.rows[e];
930
- return t ? this.detailElements.get(t) : void 0;
931
- }
932
- /**
933
- * Re-parse light DOM to refresh the detail renderer.
934
- * Call this after framework templates are registered (e.g., Angular ngAfterContentInit).
935
- *
936
- * This allows frameworks to register templates asynchronously and then
937
- * update the plugin's detailRenderer.
938
- */
939
- refreshDetailRenderer() {
940
- const e = this.config.detailRenderer;
941
- if (this.config = { ...this.config, detailRenderer: void 0 }, this.parseLightDomDetail(), !this.config.detailRenderer && e && (this.config = { ...this.config, detailRenderer: e }), this.config.detailRenderer) {
942
- const t = this.grid;
943
- typeof t.refreshColumns == "function" ? t.refreshColumns() : this.requestRender();
944
- }
945
- }
946
- // #endregion
947
- }
948
- export {
949
- A as MasterDetailPlugin
950
- };
1
+ const e=/{{\s*([^}]+)\s*}}/g,t="__DG_EMPTY__",i=/^[\w$. '?+\-*/%:()!<>=,&|]+$/,n=/__(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/,r=/* @__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,o=/* @__PURE__ */new Set(["href","src","action","formaction","data","srcdoc","xlink:href","poster","srcset"]),a=/^\s*(javascript|vbscript|data|blob):/i;function l(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 n of i){const e=n.tagName.toLowerCase();if(r.has(e)){t.push(n);continue}if("svg"===e||"http://www.w3.org/2000/svg"===n.namespaceURI){if(Array.from(n.attributes).some(e=>s.test(e.name)||"href"===e.name||"xlink:href"===e.name)){t.push(n);continue}}const i=[];for(const t of n.attributes){const e=t.name.toLowerCase();s.test(e)?i.push(t.name):(o.has(e)&&a.test(t.value)||"style"===e&&/expression\s*\(|javascript:|behavior\s*:/i.test(t.value))&&i.push(t.name)}i.forEach(e=>n.removeAttribute(e))}t.forEach(e=>e.remove())}(t.content),t.innerHTML}function d(r,s){if(!r||-1===r.indexOf("{{"))return r;const o=[],a=r.replace(e,(e,r)=>{const a=function(e,r){if(e=(e||"").trim(),!e)return t;if(c.test(e))return t;if("value"===e)return null==r.value?t:String(r.value);if(e.startsWith("row.")&&!/[()?]/.test(e)&&!e.includes(":")){const i=e.slice(4),n=r.row?r.row[i]:void 0;return null==n?t:String(n)}if(e.length>80)return t;if(!i.test(e)||n.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});`)(r.value,r.row),n=null==i?"":String(i);return c.test(n)?t:n||t}catch{return t}}(r,s);return o.push({expr:r.trim(),result:a}),a}),l=(d=a)?d.replace(new RegExp(t,"g"),"").replace(/Reflect\.[^<>{}\s]+|\bProxy\b|ownKeys\([^)]*\)/g,""):d;var d;const h=o.length&&o.every(e=>""===e.result||e.result===t);return c.test(r)||h?"":l}const c=/Reflect|Proxy|ownKeys/;const h='<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>',u={expand:"▶",collapse:"▼",sortAsc:"▲",sortDesc:"▼",sortNone:"⇅",submenuArrow:"▶",dragHandle:"⋮⋮",toolPanel:"☰",filter:h,filterActive:h,print:"🖨️"};class g{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{...u,...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}`)}}const f="__tbw_expander";function m(e){return e.field===f}function p(e,t){const i=new Set(e);return i.has(t)?i.delete(t):i.add(t),i}function w(e,t,i,n){const r=document.createElement("div");r.className="master-detail-row",r.setAttribute("data-detail-for",String(t)),r.setAttribute("role","row");const s=document.createElement("div");s.className="master-detail-cell",s.setAttribute("role","cell"),s.style.gridColumn=`1 / ${n+1}`;const o=i(e,t);return"string"==typeof o?s.innerHTML=o:o instanceof HTMLElement&&s.appendChild(o),r.appendChild(s),r}class b extends g{name="masterDetail";styles="@layer tbw-plugins{tbw-grid .cell[data-field=__tbw_expander]{border-right:none!important;padding:0;display:flex;align-items:center;justify-content:center}tbw-grid .header-row .cell[data-field=__tbw_expander]{display:none}tbw-grid .header-row .cell[data-field=__tbw_expander]+.cell{grid-column:1 / 3}tbw-grid .master-detail-expander{display:flex;align-items:center;justify-content:center;width:100%;height:100%}tbw-grid .master-detail-toggle{cursor:pointer;opacity:.7;-webkit-user-select:none;user-select:none;display:inline-flex;align-items:center;justify-content:center}tbw-grid .master-detail-toggle:hover{opacity:1}tbw-grid .master-detail-row{grid-column:1 / -1;display:grid;background:var(--tbw-master-detail-bg, var(--tbw-color-row-alt));border-bottom:1px solid var(--tbw-master-detail-border, var(--tbw-color-border));overflow:hidden}tbw-grid .master-detail-cell{padding:var(--tbw-detail-padding, var(--tbw-spacing-xl, 1rem));overflow:auto}tbw-grid .master-detail-row.tbw-expanding{animation:tbw-detail-expand var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}tbw-grid .master-detail-row.tbw-collapsing{animation:tbw-detail-collapse var(--tbw-animation-duration, .2s) var(--tbw-animation-easing, ease-out) forwards}@keyframes tbw-detail-expand{0%{opacity:0;max-height:0;padding-top:0;padding-bottom:0}to{opacity:1;max-height:var(--tbw-detail-max-height, 31.25rem);padding-top:var(--tbw-detail-padding, var(--tbw-spacing-xl, 1rem));padding-bottom:var(--tbw-detail-padding, var(--tbw-spacing-xl, 1rem))}}@keyframes tbw-detail-collapse{0%{opacity:1;max-height:var(--tbw-detail-max-height, 31.25rem)}to{opacity:0;max-height:0}}}";get defaultConfig(){return{detailHeight:"auto",expandOnRowClick:!1,collapseOnClickOutside:!1,animation:"slide"}}attach(e){super.attach(e),this.parseLightDomDetail()}parseLightDomDetail(){const e=this.grid;if(!e||"function"!=typeof e.querySelector)return;const t=e.querySelector("tbw-grid-detail");if(!t)return;const i=e;if(i.__frameworkAdapter?.parseDetailElement){const e=i.__frameworkAdapter.parseDetailElement(t);if(e)return void(this.config={...this.config,detailRenderer:e})}const n=t.getAttribute("animation"),r=t.getAttribute("show-expand-column"),s=t.getAttribute("expand-on-row-click"),o=t.getAttribute("collapse-on-click-outside"),a=t.getAttribute("height"),c={};null!==n&&(c.animation="false"!==n&&n),null!==r&&(c.showExpandColumn="false"!==r),null!==s&&(c.expandOnRowClick="true"===s),null!==o&&(c.collapseOnClickOutside="true"===o),null!==a&&(c.detailHeight="auto"===a?"auto":parseInt(a,10));const h=t.innerHTML.trim();h&&!this.config.detailRenderer&&(c.detailRenderer=(e,t)=>l(d(h,{value:e,row:e}))),Object.keys(c).length>0&&(this.config={...this.config,...c})}get animationStyle(){return!!this.isAnimationEnabled&&(this.config.animation??"slide")}animateExpand(e,t,i){if(!this.isAnimationEnabled||!1===this.animationStyle)return!1;e.classList.add("tbw-expanding");let n=!1;const r=()=>{n||(n=!0,e.classList.remove("tbw-expanding"),void 0!==t&&void 0!==i&&this.#t(e,t,i))};return e.addEventListener("animationend",r,{once:!0}),setTimeout(r,this.animationDuration+50),!0}animateCollapse(e,t){if(!this.isAnimationEnabled||!1===this.animationStyle)return void t();e.classList.add("tbw-collapsing");const i=()=>{e.classList.remove("tbw-collapsing"),t()};e.addEventListener("animationend",i,{once:!0}),setTimeout(i,this.animationDuration+50)}#t(e,t,i){if(!e.isConnected)return;const n=e.offsetHeight;if(n>0){const e=this.measuredDetailHeights.get(t);this.measuredDetailHeights.set(t,n),e!==n&&this.grid.invalidateRowHeight(i)}}expandedRows=/* @__PURE__ */new Set;detailElements=/* @__PURE__ */new Map;measuredDetailHeights=/* @__PURE__ */new Map;rowsToAnimate=/* @__PURE__ */new Set;static DEFAULT_DETAIL_HEIGHT=150;getDetailHeight(e){const t=this.detailElements.get(e);if(t){if(!(t.classList.contains("tbw-expanding")||t.classList.contains("tbw-collapsing"))){const i=t.offsetHeight;if(i>0)return this.measuredDetailHeights.set(e,i),i}}const i=this.measuredDetailHeights.get(e);return i&&i>0?i:"number"==typeof this.config?.detailHeight?this.config.detailHeight:b.DEFAULT_DETAIL_HEIGHT}toggleAndEmit(e,t){this.expandedRows=p(this.expandedRows,e);const i=this.expandedRows.has(e);i&&this.rowsToAnimate.add(e),this.emit("detail-expand",{rowIndex:t,row:e,expanded:i}),this.requestRender()}detach(){this.expandedRows.clear(),this.detailElements.clear(),this.measuredDetailHeights.clear(),this.rowsToAnimate.clear()}processColumns(e){if(!(!0===this.config.showExpandColumn||!1!==this.config.showExpandColumn&&!!this.config.detailRenderer))return[...e];const t=[...e],i=function(e){return e.find(m)}(t);if(i)return t;const n=(r=this.name,{field:f,header:"",width:32,resizable:!1,sortable:!1,filterable:!1,meta:{lockPosition:!0,suppressMovable:!0,expanderColumn:!0,expanderPlugin:r,utility:!0}});var r;return n.viewRenderer=e=>{const{row:t}=e,i=this.expandedRows.has(t),n=document.createElement("span");n.className="master-detail-expander expander-cell";const r=document.createElement("span");return r.className="master-detail-toggle"+(i?" expanded":""),this.setIcon(r,this.resolveIcon(i?"collapse":"expand")),r.setAttribute("role","button"),r.setAttribute("tabindex","0"),r.setAttribute("aria-expanded",String(i)),r.setAttribute("aria-label",i?"Collapse details":"Expand details"),n.appendChild(r),n},[n,...t]}onRowClick(e){if(this.config.expandOnRowClick&&this.config.detailRenderer)return this.toggleAndEmit(e.row,e.rowIndex),!1}onCellClick(e){const t=e.originalEvent?.target;if(t?.classList.contains("master-detail-toggle"))return this.toggleAndEmit(e.row,e.rowIndex),!0;this.expandedRows.size>0&&queueMicrotask(()=>this.#i())}onKeyDown(e){if(" "!==e.key)return;const t=this.grid._focusCol,i=this.grid._focusRow,n=this.visibleColumns[t];if(!n||!m(n))return;const r=this.rows[i];return r?(e.preventDefault(),this.toggleAndEmit(r,i),this.requestRenderWithFocus(),!0):void 0}afterRender(){this.#i()}onScrollRender(){this.config.detailRenderer&&0!==this.expandedRows.size&&this.#i()}#i(){if(!this.config.detailRenderer)return;const e=this.gridElement?.querySelector(".rows");if(!e)return;const t=this.grid,i=t._rowPool,n=t._virtualization?.start??0,r=t._virtualization?.end??0,s=this.columns.length,o=n,a=r,l=/* @__PURE__ */new Map;if(i){const t=Math.min(i.length,a-o);for(let n=0;n<t;n++){const t=i[n];t.parentNode===e&&l.set(o+n,t)}}else{const t=e.querySelectorAll(".data-grid-row");for(const e of t){const t=e.querySelector(".cell[data-row]"),i=t?parseInt(t.getAttribute("data-row")??"-1",10):-1;i>=0&&l.set(i,e)}}for(const[d,c]of this.detailElements){const e=this.rows.indexOf(d),t=this.expandedRows.has(d),i=e>=0&&l.has(e);if(!t||!i){const e=this.grid.__frameworkAdapter;if(e?.unmount){const t=c.querySelector(".master-detail-cell"),i=t?.firstElementChild;i&&e.unmount(i)}c.parentNode&&c.remove(),this.detailElements.delete(d)}}for(const[d,c]of l){const e=this.rows[d];if(!e||!this.expandedRows.has(e))continue;const t=this.detailElements.get(e);if(t){t.previousElementSibling!==c&&c.after(t);continue}const i=w(e,d,this.config.detailRenderer,s);"number"==typeof this.config.detailHeight&&(i.style.height=`${this.config.detailHeight}px`),c.after(i),this.detailElements.set(e,i);const n=this.rowsToAnimate.has(e);n&&this.rowsToAnimate.delete(e);n&&this.animateExpand(i,e,d)||requestAnimationFrame(()=>{this.#t(i,e,d)})}}getExtraHeight(){let e=0;for(const t of this.expandedRows)e+=this.getDetailHeight(t);return e}getExtraHeightBefore(e){let t=0;for(const i of this.expandedRows){const n=this.rows.indexOf(i);n>=0&&n<e&&(t+=this.getDetailHeight(i))}return t}getRowHeight(e,t){if(!this.expandedRows.has(e))return;return(this.grid.defaultRowHeight??28)+this.getDetailHeight(e)}adjustVirtualStart(e,t,i){if(0===this.expandedRows.size)return e;const n=this.grid?._virtualization?.positionCache;let r=e;if(n&&n.length>0)for(const s of this.expandedRows){const i=this.rows.indexOf(s);if(i<0||i>=e)continue;n[i].offset+n[i].height>t&&i<r&&(r=i)}else{const n=[];for(const e of this.expandedRows){const t=this.rows.indexOf(e);t>=0&&n.push({index:t,row:e})}n.sort((e,t)=>e.index-t.index);let s=0;for(const{index:o,row:a}of n){const n=o*i+s,l=this.getDetailHeight(a);s+=l,o>=e||n+i+l>t&&o<r&&(r=o)}}return r}expand(e){const t=this.rows[e];t&&(this.rowsToAnimate.add(t),this.expandedRows=function(e,t){const i=new Set(e);return i.add(t),i}(this.expandedRows,t),this.requestRender())}collapse(e){const t=this.rows[e];t&&(this.expandedRows=function(e,t){const i=new Set(e);return i.delete(t),i}(this.expandedRows,t),this.requestRender())}toggle(e){const t=this.rows[e];t&&(this.expandedRows=p(this.expandedRows,t),this.expandedRows.has(t)&&this.rowsToAnimate.add(t),this.requestRender())}isExpanded(e){const t=this.rows[e];return!!t&&function(e,t){return e.has(t)}(this.expandedRows,t)}expandAll(){for(const e of this.rows)this.rowsToAnimate.add(e),this.expandedRows.add(e);this.requestRender()}collapseAll(){this.expandedRows.clear(),this.requestRender()}getExpandedRows(){const e=[];for(const t of this.expandedRows){const i=this.rows.indexOf(t);i>=0&&e.push(i)}return e}getDetailElement(e){const t=this.rows[e];return t?this.detailElements.get(t):void 0}refreshDetailRenderer(){const e=this.config.detailRenderer;if(this.config={...this.config,detailRenderer:void 0},this.parseLightDomDetail(),!this.config.detailRenderer&&e&&(this.config={...this.config,detailRenderer:e}),this.config.detailRenderer){const e=this.grid;"function"==typeof e.refreshColumns?e.refreshColumns():this.requestRender()}}}export{b as MasterDetailPlugin};
951
2
  //# sourceMappingURL=index.js.map