@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,1540 +1,2 @@
1
- function P(o) {
2
- o && o.querySelectorAll(".cell-focus").forEach((e) => e.classList.remove("cell-focus"));
3
- }
4
- const y = 'input,select,textarea,[contenteditable="true"],[contenteditable=""],[tabindex]:not([tabindex="-1"])', D = document.createElement("template");
5
- D.innerHTML = '<div class="cell" role="gridcell" part="cell"></div>';
6
- const H = document.createElement("template");
7
- H.innerHTML = '<div class="data-grid-row" role="row" part="row"></div>';
8
- function L(o, e) {
9
- if (o._virtualization?.enabled) {
10
- const { rowHeight: n, container: l, viewportEl: c } = o._virtualization, a = l, d = c?.clientHeight ?? a?.clientHeight ?? 0;
11
- if (a && d > 0) {
12
- const g = o._focusRow * n;
13
- g < a.scrollTop ? a.scrollTop = g : g + n > a.scrollTop + d && (a.scrollTop = g - d + n);
14
- }
15
- }
16
- const t = o._activeEditRows !== void 0 && o._activeEditRows !== -1;
17
- t || o.refreshVirtualWindow(!1), P(o._bodyEl), Array.from(o._bodyEl.querySelectorAll('[aria-selected="true"]')).forEach((n) => {
18
- n.setAttribute("aria-selected", "false");
19
- });
20
- const i = o._focusRow, r = o._virtualization.start ?? 0, s = o._virtualization.end ?? o._rows.length;
21
- if (i >= r && i < s) {
22
- const n = o._bodyEl.querySelectorAll(".data-grid-row")[i - r];
23
- let l = n?.children[o._focusCol];
24
- if ((!l || !l.classList?.contains("cell")) && (l = n?.querySelector(`.cell[data-col="${o._focusCol}"]`) ?? n?.querySelector(".cell[data-col]")), l) {
25
- l.classList.add("cell-focus"), l.setAttribute("aria-selected", "true");
26
- const c = o.querySelector(".tbw-scroll-area");
27
- if (c && l && (!t || e?.forceHorizontalScroll))
28
- if (e?.forceScrollLeft)
29
- c.scrollLeft = 0;
30
- else if (e?.forceScrollRight)
31
- c.scrollLeft = c.scrollWidth - c.clientWidth;
32
- else {
33
- const a = o._getHorizontalScrollOffsets?.(n ?? void 0, l) ?? { left: 0, right: 0 };
34
- if (!a.skipScroll) {
35
- const d = l.getBoundingClientRect(), g = c.getBoundingClientRect(), f = d.left - g.left + c.scrollLeft, E = f + d.width, p = c.scrollLeft + a.left, m = c.scrollLeft + c.clientWidth - a.right;
36
- f < p ? c.scrollLeft = f - a.left : E > m && (c.scrollLeft = E - c.clientWidth + a.right);
37
- }
38
- }
39
- if (t && l.classList.contains("editing")) {
40
- const a = l.querySelector(y);
41
- if (a && document.activeElement !== a)
42
- try {
43
- a.focus({ preventScroll: !0 });
44
- } catch {
45
- }
46
- } else if (t && !l.contains(document.activeElement)) {
47
- l.hasAttribute("tabindex") || l.setAttribute("tabindex", "-1");
48
- try {
49
- l.focus({ preventScroll: !0 });
50
- } catch {
51
- }
52
- } else if (!t) {
53
- const a = o;
54
- document.activeElement !== a && a.focus({ preventScroll: !0 });
55
- }
56
- }
57
- }
58
- }
59
- const I = '<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>', O = {
60
- expand: "▶",
61
- collapse: "▼",
62
- sortAsc: "▲",
63
- sortDesc: "▼",
64
- sortNone: "⇅",
65
- submenuArrow: "▶",
66
- dragHandle: "⋮⋮",
67
- toolPanel: "☰",
68
- filter: I,
69
- filterActive: I,
70
- print: "🖨️"
71
- };
72
- class B {
73
- /**
74
- * Plugin dependencies - declare other plugins this one requires.
75
- *
76
- * Dependencies are validated when the plugin is attached.
77
- * Required dependencies throw an error if missing.
78
- * Optional dependencies log an info message if missing.
79
- *
80
- * @example
81
- * ```typescript
82
- * static readonly dependencies: PluginDependency[] = [
83
- * { name: 'editing', required: true, reason: 'Tracks cell edits for undo/redo' },
84
- * { name: 'selection', required: false, reason: 'Enables selection-based undo' },
85
- * ];
86
- * ```
87
- */
88
- static dependencies;
89
- /**
90
- * Plugin manifest - declares owned properties, config rules, and hook priorities.
91
- *
92
- * This is read by the configuration validator to:
93
- * - Validate that required plugins are loaded when their properties are used
94
- * - Execute configRules to detect invalid/conflicting settings
95
- * - Order hook execution based on priority
96
- *
97
- * @example
98
- * ```typescript
99
- * static override readonly manifest: PluginManifest<MyConfig> = {
100
- * ownedProperties: [
101
- * { property: 'myProp', level: 'column', description: 'the "myProp" column property' },
102
- * ],
103
- * configRules: [
104
- * { id: 'myPlugin/conflict', severity: 'warn', message: '...', check: (c) => c.a && c.b },
105
- * ],
106
- * };
107
- * ```
108
- */
109
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
110
- static manifest;
111
- /**
112
- * Plugin version - defaults to grid version for built-in plugins.
113
- * Third-party plugins can override with their own semver.
114
- */
115
- version = typeof __GRID_VERSION__ < "u" ? __GRID_VERSION__ : "dev";
116
- /** CSS styles to inject into the grid's shadow DOM */
117
- styles;
118
- /** Custom cell renderers keyed by type name */
119
- cellRenderers;
120
- /** Custom header renderers keyed by type name */
121
- headerRenderers;
122
- /** Custom cell editors keyed by type name */
123
- cellEditors;
124
- /** The grid instance this plugin is attached to */
125
- grid;
126
- /** Plugin configuration - merged with defaults in attach() */
127
- config;
128
- /** User-provided configuration from constructor */
129
- userConfig;
130
- /**
131
- * Plugin-level AbortController for event listener cleanup.
132
- * Created fresh in attach(), aborted in detach().
133
- * This ensures event listeners are properly cleaned up when plugins are re-attached.
134
- */
135
- #t;
136
- /**
137
- * Default configuration - subclasses should override this getter.
138
- * Note: This must be a getter (not property initializer) for proper inheritance
139
- * since property initializers run after parent constructor.
140
- */
141
- get defaultConfig() {
142
- return {};
143
- }
144
- constructor(e = {}) {
145
- this.userConfig = e;
146
- }
147
- /**
148
- * Called when the plugin is attached to a grid.
149
- * Override to set up event listeners, initialize state, etc.
150
- *
151
- * @example
152
- * ```ts
153
- * attach(grid: GridElement): void {
154
- * super.attach(grid);
155
- * // Set up document-level listeners with auto-cleanup
156
- * document.addEventListener('keydown', this.handleEscape, {
157
- * signal: this.disconnectSignal
158
- * });
159
- * }
160
- * ```
161
- */
162
- attach(e) {
163
- this.#t?.abort(), this.#t = new AbortController(), this.grid = e, this.config = { ...this.defaultConfig, ...this.userConfig };
164
- }
165
- /**
166
- * Called when the plugin is detached from a grid.
167
- * Override to clean up event listeners, timers, etc.
168
- *
169
- * @example
170
- * ```ts
171
- * detach(): void {
172
- * // Clean up any state not handled by disconnectSignal
173
- * this.selectedRows.clear();
174
- * this.cache = null;
175
- * }
176
- * ```
177
- */
178
- detach() {
179
- this.#t?.abort(), this.#t = void 0;
180
- }
181
- /**
182
- * Get another plugin instance from the same grid.
183
- * Use for inter-plugin communication.
184
- *
185
- * @example
186
- * ```ts
187
- * const selection = this.getPlugin(SelectionPlugin);
188
- * if (selection) {
189
- * const selectedRows = selection.getSelectedRows();
190
- * }
191
- * ```
192
- */
193
- getPlugin(e) {
194
- return this.grid?.getPlugin(e);
195
- }
196
- /**
197
- * Emit a custom event from the grid.
198
- */
199
- emit(e, t) {
200
- this.grid?.dispatchEvent?.(new CustomEvent(e, { detail: t, bubbles: !0 }));
201
- }
202
- /**
203
- * Emit a cancelable custom event from the grid.
204
- * @returns `true` if the event was cancelled (preventDefault called), `false` otherwise
205
- */
206
- emitCancelable(e, t) {
207
- const i = new CustomEvent(e, { detail: t, bubbles: !0, cancelable: !0 });
208
- return this.grid?.dispatchEvent?.(i), i.defaultPrevented;
209
- }
210
- // =========================================================================
211
- // Event Bus - Plugin-to-Plugin Communication
212
- // =========================================================================
213
- /**
214
- * Subscribe to an event from another plugin.
215
- * The subscription is automatically cleaned up when this plugin is detached.
216
- *
217
- * @category Plugin Development
218
- * @param eventType - The event type to listen for (e.g., 'filter-change')
219
- * @param callback - The callback to invoke when the event is emitted
220
- *
221
- * @example
222
- * ```typescript
223
- * // In attach() or other initialization
224
- * this.on('filter-change', (detail) => {
225
- * console.log('Filter changed:', detail);
226
- * });
227
- * ```
228
- */
229
- on(e, t) {
230
- this.grid?._pluginManager?.subscribe(this, e, t);
231
- }
232
- /**
233
- * Unsubscribe from a plugin event.
234
- *
235
- * @category Plugin Development
236
- * @param eventType - The event type to stop listening for
237
- *
238
- * @example
239
- * ```typescript
240
- * this.off('filter-change');
241
- * ```
242
- */
243
- off(e) {
244
- this.grid?._pluginManager?.unsubscribe(this, e);
245
- }
246
- /**
247
- * Emit an event to other plugins via the Event Bus.
248
- * This is for inter-plugin communication only; it does NOT dispatch DOM events.
249
- * Use `emit()` to dispatch DOM events that external consumers can listen to.
250
- *
251
- * @category Plugin Development
252
- * @param eventType - The event type to emit (should be declared in manifest.events)
253
- * @param detail - The event payload
254
- *
255
- * @example
256
- * ```typescript
257
- * // Emit to other plugins (not DOM)
258
- * this.emitPluginEvent('filter-change', { field: 'name', value: 'Alice' });
259
- *
260
- * // For DOM events that consumers can addEventListener to:
261
- * this.emit('filter-change', { field: 'name', value: 'Alice' });
262
- * ```
263
- */
264
- emitPluginEvent(e, t) {
265
- this.grid?._pluginManager?.emitPluginEvent(e, t);
266
- }
267
- /**
268
- * Request a re-render of the grid.
269
- * Uses ROWS phase - does NOT trigger processColumns hooks.
270
- */
271
- requestRender() {
272
- this.grid?.requestRender?.();
273
- }
274
- /**
275
- * Request a columns re-render of the grid.
276
- * Uses COLUMNS phase - triggers processColumns hooks.
277
- * Use this when your plugin needs to reprocess column configuration.
278
- */
279
- requestColumnsRender() {
280
- this.grid?.requestColumnsRender?.();
281
- }
282
- /**
283
- * Request a re-render and restore focus styling afterward.
284
- * Use this when a plugin action (like expand/collapse) triggers a render
285
- * but needs to maintain keyboard navigation focus.
286
- */
287
- requestRenderWithFocus() {
288
- this.grid?.requestRenderWithFocus?.();
289
- }
290
- /**
291
- * Request a lightweight style update without rebuilding DOM.
292
- * Use this instead of requestRender() when only CSS classes need updating.
293
- */
294
- requestAfterRender() {
295
- this.grid?.requestAfterRender?.();
296
- }
297
- /**
298
- * Get the current rows from the grid.
299
- */
300
- get rows() {
301
- return this.grid?.rows ?? [];
302
- }
303
- /**
304
- * Get the original unfiltered/unprocessed rows from the grid.
305
- * Use this when you need all source data regardless of active filters.
306
- */
307
- get sourceRows() {
308
- return this.grid?.sourceRows ?? [];
309
- }
310
- /**
311
- * Get the current columns from the grid.
312
- */
313
- get columns() {
314
- return this.grid?.columns ?? [];
315
- }
316
- /**
317
- * Get only visible columns from the grid (excludes hidden).
318
- * Use this for rendering that needs to match the grid template.
319
- */
320
- get visibleColumns() {
321
- return this.grid?._visibleColumns ?? [];
322
- }
323
- /**
324
- * Get the grid as an HTMLElement for direct DOM operations.
325
- * Use sparingly - prefer the typed GridElementRef API when possible.
326
- *
327
- * @example
328
- * ```ts
329
- * const width = this.gridElement.clientWidth;
330
- * this.gridElement.classList.add('my-plugin-active');
331
- * ```
332
- */
333
- get gridElement() {
334
- return this.grid;
335
- }
336
- /**
337
- * Get the disconnect signal for event listener cleanup.
338
- * This signal is aborted when the grid disconnects from the DOM.
339
- * Use this when adding event listeners that should be cleaned up automatically.
340
- *
341
- * Best for:
342
- * - Document/window-level listeners added in attach()
343
- * - Listeners on the grid element itself
344
- * - Any listener that should persist across renders
345
- *
346
- * Not needed for:
347
- * - Listeners on elements created in afterRender() (removed with element)
348
- *
349
- * @example
350
- * element.addEventListener('click', handler, { signal: this.disconnectSignal });
351
- * document.addEventListener('keydown', handler, { signal: this.disconnectSignal });
352
- */
353
- get disconnectSignal() {
354
- return this.#t?.signal ?? this.grid?.disconnectSignal;
355
- }
356
- /**
357
- * Get the grid-level icons configuration.
358
- * Returns merged icons (user config + defaults).
359
- */
360
- get gridIcons() {
361
- const e = this.grid?.gridConfig?.icons ?? {};
362
- return { ...O, ...e };
363
- }
364
- // #region Animation Helpers
365
- /**
366
- * Check if animations are enabled at the grid level.
367
- * Respects gridConfig.animation.mode and the CSS variable set by the grid.
368
- *
369
- * Plugins should use this to skip animations when:
370
- * - Animation mode is 'off' or `false`
371
- * - User prefers reduced motion and mode is 'reduced-motion' (default)
372
- *
373
- * @example
374
- * ```ts
375
- * private get animationStyle(): 'slide' | 'fade' | false {
376
- * if (!this.isAnimationEnabled) return false;
377
- * return this.config.animation ?? 'slide';
378
- * }
379
- * ```
380
- */
381
- get isAnimationEnabled() {
382
- const e = this.grid?.effectiveConfig?.animation?.mode ?? "reduced-motion";
383
- if (e === !1 || e === "off") return !1;
384
- if (e === !0 || e === "on") return !0;
385
- const t = this.gridElement;
386
- return t ? getComputedStyle(t).getPropertyValue("--tbw-animation-enabled").trim() !== "0" : !0;
387
- }
388
- /**
389
- * Get the animation duration in milliseconds from CSS variable.
390
- * Falls back to 200ms if not set.
391
- *
392
- * Plugins can use this for their animation timing to stay consistent
393
- * with the grid-level animation.duration setting.
394
- *
395
- * @example
396
- * ```ts
397
- * element.animate(keyframes, { duration: this.animationDuration });
398
- * ```
399
- */
400
- get animationDuration() {
401
- const e = this.gridElement;
402
- if (e) {
403
- const t = getComputedStyle(e).getPropertyValue("--tbw-animation-duration").trim(), i = parseInt(t, 10);
404
- if (!isNaN(i)) return i;
405
- }
406
- return 200;
407
- }
408
- // #endregion
409
- /**
410
- * Resolve an icon value to string or HTMLElement.
411
- * Checks plugin config first, then grid-level icons, then defaults.
412
- *
413
- * @param iconKey - The icon key in GridIcons (e.g., 'expand', 'collapse')
414
- * @param pluginOverride - Optional plugin-level override
415
- * @returns The resolved icon value
416
- */
417
- resolveIcon(e, t) {
418
- return t !== void 0 ? t : this.gridIcons[e];
419
- }
420
- /**
421
- * Set an icon value on an element.
422
- * Handles both string (text/HTML) and HTMLElement values.
423
- *
424
- * @param element - The element to set the icon on
425
- * @param icon - The icon value (string or HTMLElement)
426
- */
427
- setIcon(e, t) {
428
- typeof t == "string" ? e.innerHTML = t : t instanceof HTMLElement && (e.innerHTML = "", e.appendChild(t.cloneNode(!0)));
429
- }
430
- /**
431
- * Log a warning message.
432
- */
433
- warn(e) {
434
- console.warn(`[tbw-grid:${this.name}] ${e}`);
435
- }
436
- // #endregion
437
- }
438
- const x = "@layer tbw-plugins{tbw-grid{--tbw-editing-bg: var(--tbw-color-selection);--tbw-editing-row-bg: var(--tbw-editing-bg);--tbw-editing-border: var(--tbw-border-input, 1px solid var(--tbw-color-border-strong));--tbw-padding-editing-input: var(--tbw-cell-padding-input, 2px 6px);--tbw-font-size-editor: inherit;--tbw-editing-row-outline-color: var(--tbw-color-accent);--tbw-editing-row-outline-width: 1px;--tbw-invalid-bg: light-dark(#fef2f2, #450a0a);--tbw-invalid-border-color: light-dark(#ef4444, #f87171)}tbw-grid:not(.tbw-grid-mode) .data-grid-row:has(.editing){background:var(--tbw-editing-row-bg);outline:var(--tbw-editing-row-outline-width) solid var(--tbw-editing-row-outline-color);outline-offset:calc(-1 * var(--tbw-editing-row-outline-width))}tbw-grid .data-grid-row>.cell.editing{overflow:hidden;padding:0;display:flex;min-height:calc(var(--tbw-row-height) + 2px);align-items:center;justify-content:center}tbw-grid .data-grid-row>.cell.editing input:not([type=checkbox]),tbw-grid .data-grid-row>.cell.editing select,tbw-grid .data-grid-row>.cell.editing textarea{width:100%;height:100%;flex:1 1 auto;min-width:0;border:var(--tbw-editing-border);padding:var(--tbw-padding-editing-input);font-size:var(--tbw-font-size-editor)}tbw-grid .tbw-editor-host{display:contents}tbw-grid .data-grid-row>.cell[data-invalid=true]{background:var(--tbw-invalid-bg);outline:1px solid var(--tbw-invalid-border-color);outline-offset:-1px}}";
439
- function G(o) {
440
- const e = o.options;
441
- return e ? typeof e == "function" ? e() : e : [];
442
- }
443
- function N(o) {
444
- return (e) => {
445
- const t = o.editorParams, i = document.createElement("input");
446
- i.type = "number", i.value = e.value != null ? String(e.value) : "", t?.min !== void 0 && (i.min = String(t.min)), t?.max !== void 0 && (i.max = String(t.max)), t?.step !== void 0 && (i.step = String(t.step)), t?.placeholder && (i.placeholder = t.placeholder);
447
- const r = () => e.commit(i.value === "" ? null : Number(i.value));
448
- return i.addEventListener("blur", r), i.addEventListener("keydown", (s) => {
449
- s.key === "Enter" && r(), s.key === "Escape" && e.cancel();
450
- }), i;
451
- };
452
- }
453
- function $() {
454
- return (o) => {
455
- const e = document.createElement("input");
456
- return e.type = "checkbox", e.checked = !!o.value, e.addEventListener("change", () => o.commit(e.checked)), e;
457
- };
458
- }
459
- function z(o) {
460
- return (e) => {
461
- const t = o.editorParams, i = document.createElement("input");
462
- i.type = "date", e.value instanceof Date ? i.valueAsDate = e.value : typeof e.value == "string" && e.value && (i.value = e.value.split("T")[0]), t?.min && (i.min = t.min), t?.max && (i.max = t.max), t?.placeholder && (i.placeholder = t.placeholder);
463
- const r = () => {
464
- typeof e.value == "string" ? e.commit(i.value) : e.commit(i.valueAsDate);
465
- };
466
- return i.addEventListener("change", r), i.addEventListener("keydown", (s) => {
467
- s.key === "Escape" && e.cancel();
468
- }), i;
469
- };
470
- }
471
- function F(o) {
472
- return (e) => {
473
- const t = o.editorParams, i = document.createElement("select");
474
- if (o.multi && (i.multiple = !0), t?.includeEmpty) {
475
- const n = document.createElement("option");
476
- n.value = "", n.textContent = t.emptyLabel ?? "", i.appendChild(n);
477
- }
478
- G(o).forEach((n) => {
479
- const l = document.createElement("option");
480
- l.value = String(n.value), l.textContent = n.label, (o.multi && Array.isArray(e.value) && e.value.includes(n.value) || !o.multi && e.value === n.value) && (l.selected = !0), i.appendChild(l);
481
- });
482
- const s = () => {
483
- if (o.multi) {
484
- const n = Array.from(i.selectedOptions).map((l) => l.value);
485
- e.commit(n);
486
- } else
487
- e.commit(i.value);
488
- };
489
- return i.addEventListener("change", s), i.addEventListener("blur", s), i.addEventListener("keydown", (n) => {
490
- n.key === "Escape" && e.cancel();
491
- }), i;
492
- };
493
- }
494
- function K(o) {
495
- return (e) => {
496
- const t = o.editorParams, i = document.createElement("input");
497
- i.type = "text", i.value = e.value != null ? String(e.value) : "", t?.maxLength !== void 0 && (i.maxLength = t.maxLength), t?.pattern && (i.pattern = t.pattern), t?.placeholder && (i.placeholder = t.placeholder);
498
- const r = () => {
499
- const s = i.value;
500
- (e.value === null || e.value === void 0) && s === "" || typeof e.value == "string" && s === e.value.replace(/[\n\r]/g, "") || (typeof e.value == "number" && s !== "" ? e.commit(Number(s)) : e.commit(s));
501
- };
502
- return i.addEventListener("blur", r), i.addEventListener("keydown", (s) => {
503
- s.key === "Enter" && r(), s.key === "Escape" && e.cancel();
504
- }), i;
505
- };
506
- }
507
- function j(o) {
508
- switch (o.type) {
509
- case "number":
510
- return N(o);
511
- case "boolean":
512
- return $();
513
- case "date":
514
- return z(o);
515
- case "select":
516
- return F(o);
517
- default:
518
- return K(o);
519
- }
520
- }
521
- function W(o, e) {
522
- if (e.editor) return e.editor;
523
- if (e.__editorTemplate) return "template";
524
- if (!e.type) return;
525
- const i = o.effectiveConfig?.typeDefaults;
526
- if (i?.[e.type]?.editor)
527
- return i[e.type].editor;
528
- const r = o.__frameworkAdapter;
529
- if (r?.getTypeDefault) {
530
- const s = r.getTypeDefault(e.type);
531
- if (s?.editor)
532
- return s.editor;
533
- }
534
- }
535
- function S(o) {
536
- return !(typeof o != "string" || o === "__proto__" || o === "constructor" || o === "prototype");
537
- }
538
- function U(o) {
539
- const e = (o.__editingCellCount ?? 0) + 1;
540
- o.__editingCellCount = e, o.setAttribute("data-has-editing", "");
541
- }
542
- function Q(o) {
543
- o.__editingCellCount = 0, o.removeAttribute("data-has-editing");
544
- }
545
- function _(o, e, t) {
546
- return o instanceof HTMLInputElement ? o.type === "checkbox" ? o.checked : o.type === "number" ? o.value === "" ? null : Number(o.value) : o.type === "date" ? typeof t == "string" ? o.value : o.valueAsDate : typeof t == "number" ? o.value === "" ? null : Number(o.value) : t == null && o.value === "" || typeof t == "string" && o.value === t.replace(/[\n\r]/g, "") ? t : o.value : e?.type === "number" && o.value !== "" || typeof t == "number" && o.value !== "" ? Number(o.value) : t == null && o.value === "" ? t : o.value;
547
- }
548
- function T(o) {
549
- }
550
- function X(o, e, t, i) {
551
- const r = o.querySelector("input,textarea,select");
552
- r && (r.addEventListener("blur", () => {
553
- t(_(r, e, i));
554
- }), r instanceof HTMLInputElement && r.type === "checkbox" ? r.addEventListener("change", () => t(r.checked)) : r instanceof HTMLSelectElement && r.addEventListener("change", () => t(_(r, e, i))));
555
- }
556
- class Z extends B {
557
- /**
558
- * Plugin manifest - declares owned properties for configuration validation.
559
- * @internal
560
- */
561
- static manifest = {
562
- ownedProperties: [
563
- {
564
- property: "editable",
565
- level: "column",
566
- description: 'the "editable" column property',
567
- isUsed: (e) => e === !0
568
- },
569
- {
570
- property: "editor",
571
- level: "column",
572
- description: 'the "editor" column property'
573
- },
574
- {
575
- property: "editorParams",
576
- level: "column",
577
- description: 'the "editorParams" column property'
578
- }
579
- ],
580
- events: [
581
- {
582
- type: "cell-edit-committed",
583
- description: "Emitted when a cell edit is committed (for plugin-to-plugin coordination)"
584
- }
585
- ],
586
- queries: [
587
- {
588
- type: "isEditing",
589
- description: "Returns whether any cell is currently being edited"
590
- }
591
- ]
592
- };
593
- /** @internal */
594
- name = "editing";
595
- /** @internal */
596
- styles = x;
597
- /** @internal */
598
- get defaultConfig() {
599
- return {
600
- mode: "row",
601
- editOn: "click"
602
- };
603
- }
604
- /**
605
- * Whether the grid is in 'grid' mode (all cells always editable).
606
- */
607
- get #t() {
608
- return this.config.mode === "grid";
609
- }
610
- // #region Editing State (fully owned by plugin)
611
- /** Currently active edit row index, or -1 if not editing */
612
- #e = -1;
613
- /** Row ID of the currently active edit row (stable across _rows replacement) */
614
- #l;
615
- /** Reference to the row object at edit-open time. Used as fallback in
616
- * #exitRowEdit when no row ID is available (prevents stale-index access). */
617
- #f;
618
- /** Currently active edit column index, or -1 if not editing */
619
- #h = -1;
620
- /** Snapshots of row data before editing started */
621
- #c = /* @__PURE__ */ new Map();
622
- /** Set of row IDs that have been modified (ID-based for stability) */
623
- #r = /* @__PURE__ */ new Set();
624
- /** Set of cells currently in edit mode: "rowIndex:colIndex" */
625
- #o = /* @__PURE__ */ new Set();
626
- /**
627
- * Value-change callbacks for active editors.
628
- * Keyed by "rowIndex:field" → callback that pushes updated values to the editor.
629
- * Populated during #injectEditor, cleaned up when editors are removed.
630
- */
631
- #a = /* @__PURE__ */ new Map();
632
- /** Flag to restore focus after next render (used when exiting edit mode) */
633
- #g = !1;
634
- /** Row index pending animation after render, or -1 if none */
635
- #p = -1;
636
- /**
637
- * Invalid cell tracking: Map<rowId, Map<field, message>>
638
- * Used for validation feedback without canceling edits.
639
- */
640
- #i = /* @__PURE__ */ new Map();
641
- /**
642
- * In grid mode, tracks whether an input field is currently focused.
643
- * When true: arrow keys work within input (edit mode).
644
- * When false: arrow keys navigate between cells (navigation mode).
645
- * Escape switches to navigation mode, Enter switches to edit mode.
646
- */
647
- #n = !1;
648
- /**
649
- * In grid mode, when true, prevents inputs from auto-focusing.
650
- * This is set when Escape is pressed (navigation mode) and cleared
651
- * when Enter is pressed or user explicitly clicks an input.
652
- */
653
- #d = !1;
654
- /**
655
- * When true, only a single cell is being edited (triggered by F2 or `beginCellEdit`).
656
- * Tab and Arrow keys commit and close the editor instead of navigating to adjacent cells.
657
- */
658
- #u = !1;
659
- // #endregion
660
- // #region Lifecycle
661
- /** @internal */
662
- attach(e) {
663
- super.attach(e);
664
- const t = this.disconnectSignal, i = e;
665
- i._activeEditRows = -1, i._rowEditSnapshots = /* @__PURE__ */ new Map(), Object.defineProperty(e, "changedRows", {
666
- get: () => this.changedRows,
667
- configurable: !0
668
- }), Object.defineProperty(e, "changedRowIds", {
669
- get: () => this.changedRowIds,
670
- configurable: !0
671
- }), e.resetChangedRows = (r) => this.resetChangedRows(r), e.beginBulkEdit = (r, s) => {
672
- s && this.beginCellEdit(r, s);
673
- }, document.addEventListener(
674
- "keydown",
675
- (r) => {
676
- if (!this.#t && r.key === "Escape" && this.#e !== -1) {
677
- if (this.config.onBeforeEditClose && this.config.onBeforeEditClose(r) === !1)
678
- return;
679
- this.#s(this.#e, !0);
680
- }
681
- },
682
- { capture: !0, signal: t }
683
- ), document.addEventListener(
684
- "mousedown",
685
- (r) => {
686
- if (this.#t || this.#e === -1) return;
687
- const s = i.findRenderedRowElement?.(this.#e);
688
- !s || (r.composedPath && r.composedPath() || []).includes(s) || this.config.onBeforeEditClose && this.config.onBeforeEditClose(r) === !1 || queueMicrotask(() => {
689
- this.#e !== -1 && this.#s(this.#e, !1);
690
- });
691
- },
692
- { signal: t }
693
- ), this.gridElement.addEventListener(
694
- "cell-change",
695
- (r) => {
696
- const s = r.detail;
697
- if (s.source === "user") return;
698
- const n = `${s.rowIndex}:${s.field}`, l = this.#a.get(n);
699
- l && l(s.newValue);
700
- },
701
- { signal: t }
702
- ), this.#t && (i._isGridEditMode = !0, this.gridElement.classList.add("tbw-grid-mode"), this.requestRender(), this.gridElement.addEventListener(
703
- "focusin",
704
- (r) => {
705
- const s = r.target;
706
- if (s.matches(y)) {
707
- if (this.#d) {
708
- s.blur(), this.gridElement.focus();
709
- return;
710
- }
711
- this.#n = !0;
712
- }
713
- },
714
- { signal: t }
715
- ), this.gridElement.addEventListener(
716
- "focusout",
717
- (r) => {
718
- const s = r.relatedTarget;
719
- (!s || !this.gridElement.contains(s) || !s.matches(y)) && (this.#n = !1);
720
- },
721
- { signal: t }
722
- ), this.gridElement.addEventListener(
723
- "keydown",
724
- (r) => {
725
- if (r.key === "Escape" && this.#n) {
726
- if (this.config.onBeforeEditClose && this.config.onBeforeEditClose(r) === !1)
727
- return;
728
- const s = document.activeElement;
729
- s && this.gridElement.contains(s) && (s.blur(), this.gridElement.focus()), this.#n = !1, this.#d = !0, r.preventDefault(), r.stopPropagation();
730
- }
731
- },
732
- { capture: !0, signal: t }
733
- ), this.gridElement.addEventListener(
734
- "mousedown",
735
- (r) => {
736
- r.target.matches(y) && (this.#d = !1);
737
- },
738
- { signal: t }
739
- ));
740
- }
741
- /** @internal */
742
- detach() {
743
- const e = this.gridElement;
744
- e._isGridEditMode = !1, this.gridElement.classList.remove("tbw-grid-mode"), this.#e = -1, this.#l = void 0, this.#f = void 0, this.#h = -1, this.#c.clear(), this.#r.clear(), this.#o.clear(), this.#a.clear(), this.#n = !1, this.#d = !1, this.#u = !1, super.detach();
745
- }
746
- /**
747
- * Handle plugin queries.
748
- * @internal
749
- */
750
- handleQuery(e) {
751
- if (e.type === "isEditing")
752
- return this.#t || this.#e !== -1;
753
- }
754
- // #endregion
755
- // #region Event Handlers (event distribution)
756
- /**
757
- * Handle cell clicks - start editing if configured for click mode.
758
- * Both click and dblclick events come through this handler.
759
- * Starts row-based editing (all editable cells in the row get editors).
760
- * @internal
761
- */
762
- onCellClick(e) {
763
- if (this.#t) return !1;
764
- const t = this.grid, i = this.config.editOn ?? t.effectiveConfig?.editOn;
765
- if (i === !1 || i === "manual" || i !== "click" && i !== "dblclick") return !1;
766
- const r = e.originalEvent.type === "dblclick";
767
- if (i === "click" && r || i === "dblclick" && !r) return !1;
768
- const { rowIndex: s } = e;
769
- return t._columns?.some((l) => l.editable) ? (e.originalEvent.stopPropagation(), this.beginBulkEdit(s), !0) : !1;
770
- }
771
- /**
772
- * Handle keyboard events for edit lifecycle.
773
- * @internal
774
- */
775
- onKeyDown(e) {
776
- const t = this.grid;
777
- if (e.key === "Escape") {
778
- if (this.#t && this.#n) {
779
- if (this.config.onBeforeEditClose && this.config.onBeforeEditClose(e) === !1)
780
- return !0;
781
- const i = document.activeElement;
782
- return i && this.gridElement.contains(i) && i.blur(), this.#n = !1, this.requestAfterRender(), !0;
783
- }
784
- if (this.#e !== -1 && !this.#t)
785
- return this.config.onBeforeEditClose && this.config.onBeforeEditClose(e) === !1 || this.#s(this.#e, !0), !0;
786
- }
787
- if (this.#t && !this.#n && (e.key === "ArrowUp" || e.key === "ArrowDown" || e.key === "ArrowLeft" || e.key === "ArrowRight"))
788
- return !1;
789
- if (this.#t && this.#n && (e.key === "ArrowUp" || e.key === "ArrowDown"))
790
- return !0;
791
- if ((e.key === "ArrowUp" || e.key === "ArrowDown") && this.#e !== -1 && !this.#t) {
792
- if (this.config.onBeforeEditClose && this.config.onBeforeEditClose(e) === !1)
793
- return !0;
794
- const i = t._rows.length - 1, r = this.#e;
795
- return this.#s(r, !1), e.key === "ArrowDown" ? t._focusRow = Math.min(i, t._focusRow + 1) : t._focusRow = Math.max(0, t._focusRow - 1), e.preventDefault(), L(t), this.requestAfterRender(), !0;
796
- }
797
- if (e.key === "Tab" && (this.#e !== -1 || this.#t)) {
798
- if (e.preventDefault(), this.#u)
799
- return this.#s(this.#e, !1), !0;
800
- const i = !e.shiftKey;
801
- return this.#_(i), !0;
802
- }
803
- if (e.key === " " || e.key === "Spacebar") {
804
- if (this.#e !== -1)
805
- return !1;
806
- const i = t._focusRow, r = t._focusCol;
807
- if (i >= 0 && r >= 0) {
808
- const s = t._visibleColumns[r], n = t._rows[i];
809
- if (s?.editable && s.type === "boolean" && n) {
810
- const l = s.field;
811
- if (S(l)) {
812
- const a = !n[l];
813
- return this.#v(i, s, a, n), e.preventDefault(), this.requestRender(), !0;
814
- }
815
- }
816
- }
817
- return !1;
818
- }
819
- if (e.key === "Enter" && !e.shiftKey && !e.ctrlKey && !e.altKey && !e.metaKey) {
820
- if (this.#t && !this.#n)
821
- return this.#R(), !0;
822
- if (this.#e !== -1)
823
- return !!(this.config.onBeforeEditClose && this.config.onBeforeEditClose(e) === !1);
824
- const i = this.config.editOn ?? t.effectiveConfig?.editOn;
825
- if (i === !1 || i === "manual") return !1;
826
- const r = t._focusRow, s = t._focusCol;
827
- if (r >= 0 && t._columns?.some((l) => l.editable)) {
828
- const l = t._visibleColumns[s], c = t._rows[r], a = l?.field ?? "", d = a && c ? c[a] : void 0, g = this.gridElement.querySelector(`[data-row="${r}"][data-col="${s}"]`), f = new CustomEvent("cell-activate", {
829
- cancelable: !0,
830
- bubbles: !0,
831
- detail: {
832
- rowIndex: r,
833
- colIndex: s,
834
- field: a,
835
- value: d,
836
- row: c,
837
- cellEl: g,
838
- trigger: "keyboard",
839
- originalEvent: e
840
- }
841
- });
842
- this.gridElement.dispatchEvent(f);
843
- const E = new CustomEvent("activate-cell", {
844
- cancelable: !0,
845
- bubbles: !0,
846
- detail: { row: r, col: s }
847
- });
848
- return this.gridElement.dispatchEvent(E), f.defaultPrevented || E.defaultPrevented ? (e.preventDefault(), !0) : (this.beginBulkEdit(r), !0);
849
- }
850
- return !1;
851
- }
852
- if (e.key === "F2") {
853
- if (this.#e !== -1 || this.#t || (this.config.editOn ?? t.effectiveConfig?.editOn) === !1) return !1;
854
- const r = t._focusRow, s = t._focusCol;
855
- if (r >= 0 && s >= 0) {
856
- const n = t._visibleColumns[s];
857
- if (n?.editable && n.field)
858
- return e.preventDefault(), this.beginCellEdit(r, n.field), !0;
859
- }
860
- return !1;
861
- }
862
- return !1;
863
- }
864
- // #endregion
865
- // #region Render Hooks
866
- /**
867
- * Process columns to merge type-level editorParams with column-level.
868
- * Column-level params take precedence.
869
- * @internal
870
- */
871
- processColumns(e) {
872
- const t = this.grid, i = t.effectiveConfig?.typeDefaults, r = t.__frameworkAdapter;
873
- return !i && !r?.getTypeDefault ? e : e.map((s) => {
874
- if (!s.type) return s;
875
- let n;
876
- if (i?.[s.type]?.editorParams && (n = i[s.type].editorParams), !n && r?.getTypeDefault) {
877
- const l = r.getTypeDefault(s.type);
878
- l?.editorParams && (n = l.editorParams);
879
- }
880
- return n ? {
881
- ...s,
882
- editorParams: { ...n, ...s.editorParams }
883
- } : s;
884
- });
885
- }
886
- /**
887
- * After render, reapply editors to cells in edit mode.
888
- * This handles virtualization - when a row scrolls back into view,
889
- * we need to re-inject the editor.
890
- * @internal
891
- */
892
- afterRender() {
893
- const e = this.grid;
894
- if (this.#g && (this.#g = !1, this.#y(e)), this.#p !== -1) {
895
- const t = this.#p;
896
- this.#p = -1, e.animateRow?.(t, "change");
897
- }
898
- if (!this.#t && this.#o.size !== 0)
899
- for (const t of this.#o) {
900
- const [i, r] = t.split(":"), s = parseInt(i, 10), n = parseInt(r, 10), l = e.findRenderedRowElement?.(s);
901
- if (!l) continue;
902
- const c = l.querySelector(`.cell[data-col="${n}"]`);
903
- if (!c || c.classList.contains("editing")) continue;
904
- const a = e._rows[s], d = e._visibleColumns[n];
905
- a && d && this.#w(a, s, d, n, c, !0);
906
- }
907
- }
908
- /**
909
- * Hook called after each cell is rendered.
910
- * In grid mode, injects editors into editable cells during render (no DOM queries needed).
911
- * @internal
912
- */
913
- afterCellRender(e) {
914
- if (!this.#t) return;
915
- const { row: t, rowIndex: i, column: r, colIndex: s, cellElement: n } = e;
916
- r.editable && (n.classList.contains("editing") || this.#w(t, i, r, s, n, !0));
917
- }
918
- /**
919
- * On scroll render, reapply editors to recycled cells.
920
- * @internal
921
- */
922
- onScrollRender() {
923
- this.afterRender();
924
- }
925
- // #endregion
926
- // #region Public API
927
- /**
928
- * Get all rows that have been modified.
929
- * Uses ID-based lookup for stability when rows are reordered.
930
- */
931
- get changedRows() {
932
- const e = [];
933
- for (const t of this.#r) {
934
- const i = this.grid.getRow(t);
935
- i && e.push(i);
936
- }
937
- return e;
938
- }
939
- /**
940
- * Get IDs of all modified rows.
941
- */
942
- get changedRowIds() {
943
- return Array.from(this.#r);
944
- }
945
- /**
946
- * Get the currently active edit row index, or -1 if not editing.
947
- */
948
- get activeEditRow() {
949
- return this.#e;
950
- }
951
- /**
952
- * Get the currently active edit column index, or -1 if not editing.
953
- */
954
- get activeEditCol() {
955
- return this.#h;
956
- }
957
- /**
958
- * Check if a specific row is currently being edited.
959
- */
960
- isRowEditing(e) {
961
- return this.#e === e;
962
- }
963
- /**
964
- * Check if a specific cell is currently being edited.
965
- */
966
- isCellEditing(e, t) {
967
- return this.#o.has(`${e}:${t}`);
968
- }
969
- /**
970
- * Check if a specific row has been modified.
971
- * @param rowIndex - Row index to check (will be converted to ID internally)
972
- */
973
- isRowChanged(e) {
974
- const t = this.grid, i = t._rows[e];
975
- if (!i) return !1;
976
- try {
977
- const r = t.getRowId?.(i);
978
- return r ? this.#r.has(r) : !1;
979
- } catch {
980
- return !1;
981
- }
982
- }
983
- /**
984
- * Check if a row with the given ID has been modified.
985
- * @param rowId - Row ID to check
986
- */
987
- isRowChangedById(e) {
988
- return this.#r.has(e);
989
- }
990
- // #region Cell Validation
991
- /**
992
- * Mark a cell as invalid with an optional validation message.
993
- * Invalid cells are marked with a `data-invalid` attribute for styling.
994
- *
995
- * @param rowId - The row ID (from getRowId)
996
- * @param field - The field name
997
- * @param message - Optional validation message (for tooltips or display)
998
- *
999
- * @example
1000
- * ```typescript
1001
- * // In cell-commit handler:
1002
- * grid.addEventListener('cell-commit', (e) => {
1003
- * if (e.detail.field === 'email' && !isValidEmail(e.detail.value)) {
1004
- * e.detail.setInvalid('Invalid email format');
1005
- * }
1006
- * });
1007
- *
1008
- * // Or programmatically:
1009
- * editingPlugin.setInvalid('row-123', 'email', 'Invalid email format');
1010
- * ```
1011
- */
1012
- setInvalid(e, t, i = "") {
1013
- let r = this.#i.get(e);
1014
- r || (r = /* @__PURE__ */ new Map(), this.#i.set(e, r)), r.set(t, i), this.#m(e, t, !0);
1015
- }
1016
- /**
1017
- * Clear the invalid state for a specific cell.
1018
- *
1019
- * @param rowId - The row ID (from getRowId)
1020
- * @param field - The field name
1021
- */
1022
- clearInvalid(e, t) {
1023
- const i = this.#i.get(e);
1024
- i && (i.delete(t), i.size === 0 && this.#i.delete(e)), this.#m(e, t, !1);
1025
- }
1026
- /**
1027
- * Clear all invalid cells for a specific row.
1028
- *
1029
- * @param rowId - The row ID (from getRowId)
1030
- */
1031
- clearRowInvalid(e) {
1032
- const t = this.#i.get(e);
1033
- if (t) {
1034
- const i = Array.from(t.keys());
1035
- this.#i.delete(e), i.forEach((r) => this.#m(e, r, !1));
1036
- }
1037
- }
1038
- /**
1039
- * Clear all invalid cell states across all rows.
1040
- */
1041
- clearAllInvalid() {
1042
- const e = Array.from(this.#i.entries());
1043
- this.#i.clear(), e.forEach(([t, i]) => {
1044
- i.forEach((r, s) => this.#m(t, s, !1));
1045
- });
1046
- }
1047
- /**
1048
- * Check if a specific cell is marked as invalid.
1049
- *
1050
- * @param rowId - The row ID (from getRowId)
1051
- * @param field - The field name
1052
- * @returns True if the cell is marked as invalid
1053
- */
1054
- isCellInvalid(e, t) {
1055
- return this.#i.get(e)?.has(t) ?? !1;
1056
- }
1057
- /**
1058
- * Get the validation message for an invalid cell.
1059
- *
1060
- * @param rowId - The row ID (from getRowId)
1061
- * @param field - The field name
1062
- * @returns The validation message, or undefined if cell is valid
1063
- */
1064
- getInvalidMessage(e, t) {
1065
- return this.#i.get(e)?.get(t);
1066
- }
1067
- /**
1068
- * Check if a row has any invalid cells.
1069
- *
1070
- * @param rowId - The row ID (from getRowId)
1071
- * @returns True if the row has at least one invalid cell
1072
- */
1073
- hasInvalidCells(e) {
1074
- const t = this.#i.get(e);
1075
- return t ? t.size > 0 : !1;
1076
- }
1077
- /**
1078
- * Get all invalid fields for a row.
1079
- *
1080
- * @param rowId - The row ID (from getRowId)
1081
- * @returns Map of field names to validation messages
1082
- */
1083
- getInvalidFields(e) {
1084
- return new Map(this.#i.get(e) ?? []);
1085
- }
1086
- /**
1087
- * Sync the data-invalid attribute on a cell element.
1088
- */
1089
- #m(e, t, i) {
1090
- const r = this.grid, s = r._visibleColumns?.findIndex((d) => d.field === t);
1091
- if (s === -1 || s === void 0) return;
1092
- const l = r._rows?.findIndex((d) => {
1093
- try {
1094
- return r.getRowId?.(d) === e;
1095
- } catch {
1096
- return !1;
1097
- }
1098
- });
1099
- if (l === -1 || l === void 0) return;
1100
- const a = r.findRenderedRowElement?.(l)?.querySelector(`.cell[data-col="${s}"]`);
1101
- if (a)
1102
- if (i) {
1103
- a.setAttribute("data-invalid", "true");
1104
- const d = this.#i.get(e)?.get(t);
1105
- d && a.setAttribute("title", d);
1106
- } else
1107
- a.removeAttribute("data-invalid"), a.removeAttribute("title");
1108
- }
1109
- // #endregion
1110
- /**
1111
- * Reset all change tracking.
1112
- * @param silent - If true, suppresses the `changed-rows-reset` event
1113
- * @fires changed-rows-reset - Emitted when tracking is reset (unless silent)
1114
- */
1115
- resetChangedRows(e) {
1116
- const t = this.changedRows, i = this.changedRowIds;
1117
- this.#r.clear(), this.#E(), e || this.emit("changed-rows-reset", { rows: t, ids: i }), this.grid._rowPool?.forEach((s) => s.classList.remove("changed"));
1118
- }
1119
- /**
1120
- * Programmatically begin editing a cell.
1121
- * @param rowIndex - Index of the row to edit
1122
- * @param field - Field name of the column to edit
1123
- * @fires cell-commit - Emitted when the cell value is committed (on blur or Enter)
1124
- */
1125
- beginCellEdit(e, t) {
1126
- const i = this.grid, r = i._visibleColumns.findIndex((c) => c.field === t);
1127
- if (r === -1 || !i._visibleColumns[r]?.editable) return;
1128
- const l = i.findRenderedRowElement?.(e)?.querySelector(`.cell[data-col="${r}"]`);
1129
- l && (this.#u = !0, this.#C(e, r, l));
1130
- }
1131
- /**
1132
- * Programmatically begin editing all editable cells in a row.
1133
- * @param rowIndex - Index of the row to edit
1134
- * @fires cell-commit - Emitted for each cell value that is committed
1135
- * @fires row-commit - Emitted when focus leaves the row
1136
- */
1137
- beginBulkEdit(e) {
1138
- const t = this.grid;
1139
- if ((this.config.editOn ?? t.effectiveConfig?.editOn) === !1 || !t._columns?.some((l) => l.editable)) return;
1140
- const s = t.findRenderedRowElement?.(e);
1141
- if (!s) return;
1142
- this.#u = !1;
1143
- const n = t._rows[e];
1144
- this.#b(e, n), Array.from(s.children).forEach((l, c) => {
1145
- const a = t._visibleColumns[c];
1146
- if (a?.editable) {
1147
- const d = l;
1148
- d.classList.contains("editing") || this.#w(n, e, a, c, d, !0);
1149
- }
1150
- }), setTimeout(() => {
1151
- let l = s.querySelector(`.cell[data-col="${t._focusCol}"]`);
1152
- if (l?.classList.contains("editing") || (l = s.querySelector(".cell.editing")), l?.classList.contains("editing")) {
1153
- const c = l.querySelector(y);
1154
- try {
1155
- c?.focus({ preventScroll: !0 });
1156
- } catch {
1157
- }
1158
- }
1159
- }, 0);
1160
- }
1161
- /**
1162
- * Commit the currently active row edit.
1163
- * @fires row-commit - Emitted after the row edit is committed
1164
- */
1165
- commitActiveRowEdit() {
1166
- this.#e !== -1 && this.#s(this.#e, !1);
1167
- }
1168
- /**
1169
- * Cancel the currently active row edit.
1170
- */
1171
- cancelActiveRowEdit() {
1172
- this.#e !== -1 && this.#s(this.#e, !0);
1173
- }
1174
- // #endregion
1175
- // #region Internal Methods
1176
- /**
1177
- * Begin editing a single cell.
1178
- */
1179
- #C(e, t, i) {
1180
- const r = this.grid, s = r._rows[e], n = r._visibleColumns[t];
1181
- !s || !n?.editable || i.classList.contains("editing") || (this.#e !== e && this.#b(e, s), this.#h = t, this.#w(s, e, n, t, i, !1));
1182
- }
1183
- /**
1184
- * Focus the editor input in the currently focused cell (grid mode only).
1185
- * Used when pressing Enter to enter edit mode from navigation mode.
1186
- */
1187
- #R() {
1188
- const e = this.grid, t = e._focusRow, i = e._focusCol;
1189
- if (t < 0 || i < 0) return;
1190
- const s = e.findRenderedRowElement?.(t)?.querySelector(`.cell[data-col="${i}"]`);
1191
- if (s?.classList.contains("editing")) {
1192
- const n = s.querySelector(y);
1193
- n && (this.#d = !1, n.focus(), this.#n = !0, n instanceof HTMLInputElement && (n.type === "text" || n.type === "number") && n.select());
1194
- }
1195
- }
1196
- /**
1197
- * Handle Tab/Shift+Tab navigation while editing.
1198
- * Moves to next/previous editable cell, staying in edit mode.
1199
- * Wraps to next/previous row when reaching row boundaries.
1200
- */
1201
- #_(e) {
1202
- const t = this.grid, i = t._rows, r = this.#t ? t._focusRow : this.#e, s = t._visibleColumns.map((a, d) => a.editable ? d : -1).filter((a) => a >= 0);
1203
- if (s.length === 0) return;
1204
- const l = s.indexOf(t._focusCol) + (e ? 1 : -1);
1205
- if (l >= 0 && l < s.length) {
1206
- t._focusCol = s[l];
1207
- const d = t.findRenderedRowElement?.(r)?.querySelector(`.cell[data-col="${s[l]}"]`);
1208
- d?.classList.contains("editing") && d.querySelector(y)?.focus({ preventScroll: !0 }), L(t, { forceHorizontalScroll: !0 });
1209
- return;
1210
- }
1211
- const c = r + (e ? 1 : -1);
1212
- c >= 0 && c < i.length && (this.#t ? (t._focusRow = c, t._focusCol = e ? s[0] : s[s.length - 1], L(t, { forceHorizontalScroll: !0 }), this.requestAfterRender(), setTimeout(() => {
1213
- const d = t.findRenderedRowElement?.(c)?.querySelector(`.cell[data-col="${t._focusCol}"]`);
1214
- d?.classList.contains("editing") && d.querySelector(y)?.focus({ preventScroll: !0 });
1215
- }, 0)) : (this.#s(r, !1), t._focusRow = c, t._focusCol = e ? s[0] : s[s.length - 1], this.beginBulkEdit(c), L(t, { forceHorizontalScroll: !0 })));
1216
- }
1217
- /**
1218
- * Sync the internal grid state with the plugin's editing state.
1219
- */
1220
- #E() {
1221
- const e = this.grid;
1222
- e._activeEditRows = this.#e, e._rowEditSnapshots = this.#c;
1223
- }
1224
- /**
1225
- * Snapshot original row data and mark as editing.
1226
- */
1227
- #b(e, t) {
1228
- if (this.#e !== e) {
1229
- this.#c.set(e, { ...t }), this.#e = e, this.#f = t;
1230
- const i = this.grid;
1231
- try {
1232
- this.#l = i.getRowId?.(t) ?? void 0;
1233
- } catch {
1234
- this.#l = void 0;
1235
- }
1236
- this.#E(), this.#t || this.emit("edit-open", {
1237
- rowIndex: e,
1238
- rowId: this.#l ?? "",
1239
- row: t
1240
- });
1241
- }
1242
- }
1243
- /**
1244
- * Exit editing for a row.
1245
- */
1246
- #s(e, t) {
1247
- if (this.#e !== e) return;
1248
- const i = this.grid, r = this.#c.get(e), s = i.findRenderedRowElement?.(e);
1249
- let n = this.#l;
1250
- const c = (n ? i._getRowEntry(n) : void 0)?.row ?? this.#f ?? i._rows[e];
1251
- if (!n && c)
1252
- try {
1253
- n = i.getRowId?.(c);
1254
- } catch {
1255
- }
1256
- if (!t && s && c && s.querySelectorAll(".cell.editing").forEach((d) => {
1257
- const g = Number(d.getAttribute("data-col"));
1258
- if (isNaN(g)) return;
1259
- const f = i._visibleColumns[g];
1260
- if (!f || d.hasAttribute("data-editor-managed"))
1261
- return;
1262
- const E = d.querySelector("input,textarea,select");
1263
- if (E) {
1264
- const p = f.field, m = c[p], C = _(E, f, m);
1265
- m !== C && this.#v(e, f, C, c);
1266
- }
1267
- }), t && r && c)
1268
- Object.keys(r).forEach((a) => {
1269
- c[a] = r[a];
1270
- }), n && (this.#r.delete(n), this.clearRowInvalid(n));
1271
- else if (!t && c) {
1272
- const a = this.#L(r, c), d = n ? this.#r.has(n) : a, g = this.emitCancelable("row-commit", {
1273
- rowIndex: e,
1274
- rowId: n ?? "",
1275
- row: c,
1276
- oldValue: r,
1277
- newValue: c,
1278
- changed: d,
1279
- changedRows: this.changedRows,
1280
- changedRowIds: this.changedRowIds
1281
- });
1282
- g && r ? (Object.keys(r).forEach((f) => {
1283
- c[f] = r[f];
1284
- }), n && (this.#r.delete(n), this.clearRowInvalid(n))) : !g && a && this.isAnimationEnabled && (this.#p = e);
1285
- }
1286
- this.#c.delete(e), this.#e = -1, this.#l = void 0, this.#f = void 0, this.#h = -1, this.#u = !1, this.#E();
1287
- for (const a of this.#o)
1288
- a.startsWith(`${e}:`) && this.#o.delete(a);
1289
- for (const a of this.#a.keys())
1290
- a.startsWith(`${e}:`) && this.#a.delete(a);
1291
- this.#g = !0, s ? (s.querySelectorAll(".cell.editing").forEach((a) => {
1292
- a.classList.remove("editing"), Q(a.parentElement);
1293
- }), i.refreshVirtualWindow(!0)) : (this.#y(i), this.#g = !1), !this.#t && c && this.emit("edit-close", {
1294
- rowIndex: e,
1295
- rowId: n ?? "",
1296
- row: c,
1297
- reverted: t
1298
- });
1299
- }
1300
- /**
1301
- * Commit a single cell value change.
1302
- * Uses ID-based change tracking for stability when rows are reordered.
1303
- */
1304
- #v(e, t, i, r) {
1305
- const s = t.field;
1306
- if (!S(s)) return;
1307
- const n = r[s];
1308
- if (n === i) return;
1309
- const l = this.grid;
1310
- let c;
1311
- try {
1312
- c = this.grid.getRowId(r);
1313
- } catch {
1314
- }
1315
- const a = c ? !this.#r.has(c) : !0, d = c ? (m) => this.grid.updateRow(c, m, "cascade") : T;
1316
- let g = !1;
1317
- const f = c ? (m) => {
1318
- g = !0, this.setInvalid(c, s, m ?? "");
1319
- } : () => {
1320
- };
1321
- if (this.emitCancelable("cell-commit", {
1322
- row: r,
1323
- rowId: c ?? "",
1324
- field: s,
1325
- oldValue: n,
1326
- value: i,
1327
- rowIndex: e,
1328
- changedRows: this.changedRows,
1329
- changedRowIds: this.changedRowIds,
1330
- firstTimeForRow: a,
1331
- updateRow: d,
1332
- setInvalid: f
1333
- })) return;
1334
- c && !g && this.isCellInvalid(c, s) && this.clearInvalid(c, s), r[s] = i, c && this.#r.add(c), this.#E(), this.emitPluginEvent("cell-edit-committed", {
1335
- rowIndex: e,
1336
- field: s,
1337
- oldValue: n,
1338
- newValue: i
1339
- });
1340
- const p = l.findRenderedRowElement?.(e);
1341
- p && p.classList.add("changed");
1342
- }
1343
- /**
1344
- * Inject an editor into a cell.
1345
- */
1346
- #w(e, t, i, r, s, n) {
1347
- if (!i.editable || s.classList.contains("editing")) return;
1348
- let l;
1349
- try {
1350
- l = this.grid.getRowId(e);
1351
- } catch {
1352
- }
1353
- const c = l ? (u) => this.grid.updateRow(l, u, "cascade") : T, a = S(i.field) ? e[i.field] : void 0;
1354
- s.classList.add("editing"), this.#o.add(`${t}:${r}`);
1355
- const d = s.parentElement;
1356
- d && U(d);
1357
- let g = !1;
1358
- const f = (u) => {
1359
- if (g || !this.#t && this.#e === -1) return;
1360
- const h = this.grid, v = l ? h._getRowEntry(l) : void 0, b = v?.row ?? e, M = v?.index ?? t;
1361
- this.#v(M, i, u, b);
1362
- }, E = () => {
1363
- if (g = !0, S(i.field)) {
1364
- const u = this.grid, v = (l ? u._getRowEntry(l) : void 0)?.row ?? e;
1365
- v[i.field] = a;
1366
- }
1367
- }, p = document.createElement("div");
1368
- p.className = "tbw-editor-host", s.innerHTML = "", s.appendChild(p), p.addEventListener("keydown", (u) => {
1369
- if (u.key === "Enter") {
1370
- if (this.#t) {
1371
- u.stopPropagation(), u.preventDefault();
1372
- const h = p.querySelector("input,textarea,select");
1373
- h && f(_(h, i, a));
1374
- return;
1375
- }
1376
- if (this.config.onBeforeEditClose && this.config.onBeforeEditClose(u) === !1)
1377
- return;
1378
- u.stopPropagation(), u.preventDefault(), g = !0, this.#s(t, !1);
1379
- }
1380
- if (u.key === "Escape") {
1381
- if (this.#t) {
1382
- u.stopPropagation(), u.preventDefault();
1383
- return;
1384
- }
1385
- if (this.config.onBeforeEditClose && this.config.onBeforeEditClose(u) === !1)
1386
- return;
1387
- u.stopPropagation(), u.preventDefault(), E(), this.#s(t, !0);
1388
- }
1389
- });
1390
- const m = i, C = m.__editorTemplate, w = W(this.grid, m) ?? j(i), k = a, q = `${t}:${i.field}`, A = [];
1391
- this.#a.set(q, (u) => {
1392
- for (const h of A) h(u);
1393
- });
1394
- const R = (u) => {
1395
- A.push(u);
1396
- };
1397
- if (w === "template" && C)
1398
- this.#S(p, m, e, a, f, E, n, t), R((u) => {
1399
- const h = p.querySelector(
1400
- "input,textarea,select"
1401
- );
1402
- h && (h instanceof HTMLInputElement && h.type === "checkbox" ? h.checked = !!u : h.value = String(u ?? ""));
1403
- });
1404
- else if (typeof w == "string") {
1405
- const u = document.createElement(w);
1406
- u.value = k, u.addEventListener("change", () => f(u.value)), R((h) => {
1407
- u.value = h;
1408
- }), p.appendChild(u), n || queueMicrotask(() => {
1409
- p.querySelector(y)?.focus({ preventScroll: !0 });
1410
- });
1411
- } else if (typeof w == "function") {
1412
- const u = {
1413
- row: e,
1414
- rowId: l ?? "",
1415
- value: k,
1416
- field: i.field,
1417
- column: i,
1418
- commit: f,
1419
- cancel: E,
1420
- updateRow: c,
1421
- onValueChange: R
1422
- }, h = w(u);
1423
- typeof h == "string" ? (p.innerHTML = h, X(p, i, f, a), R((v) => {
1424
- const b = p.querySelector(
1425
- "input,textarea,select"
1426
- );
1427
- b && (b instanceof HTMLInputElement && b.type === "checkbox" ? b.checked = !!v : b.value = String(v ?? ""));
1428
- })) : h instanceof Node && (p.appendChild(h), h instanceof HTMLInputElement || h instanceof HTMLSelectElement || h instanceof HTMLTextAreaElement ? R((b) => {
1429
- h instanceof HTMLInputElement && h.type === "checkbox" ? h.checked = !!b : h.value = String(b ?? "");
1430
- }) : s.setAttribute("data-editor-managed", "")), n || queueMicrotask(() => {
1431
- p.querySelector(y)?.focus({ preventScroll: !0 });
1432
- });
1433
- } else if (w && typeof w == "object") {
1434
- const u = document.createElement("div");
1435
- u.setAttribute("data-external-editor", ""), u.setAttribute("data-field", i.field), p.appendChild(u), s.setAttribute("data-editor-managed", "");
1436
- const h = {
1437
- row: e,
1438
- rowId: l ?? "",
1439
- value: k,
1440
- field: i.field,
1441
- column: i,
1442
- commit: f,
1443
- cancel: E,
1444
- updateRow: c,
1445
- onValueChange: R
1446
- };
1447
- if (w.mount)
1448
- try {
1449
- w.mount({ placeholder: u, context: h, spec: w });
1450
- } catch (v) {
1451
- console.warn(`[tbw-grid] External editor mount error for column '${i.field}':`, v);
1452
- }
1453
- else
1454
- this.grid.dispatchEvent(
1455
- new CustomEvent("mount-external-editor", { detail: { placeholder: u, spec: w, context: h } })
1456
- );
1457
- }
1458
- }
1459
- /**
1460
- * Render a template-based editor.
1461
- */
1462
- #S(e, t, i, r, s, n, l, c) {
1463
- const a = t.__editorTemplate;
1464
- if (!a) return;
1465
- const d = a.cloneNode(!0), g = t.__compiledEditor;
1466
- g ? d.innerHTML = g({
1467
- row: i,
1468
- value: r,
1469
- field: t.field,
1470
- column: t,
1471
- commit: s,
1472
- cancel: n
1473
- }) : d.querySelectorAll("*").forEach((E) => {
1474
- E.childNodes.length === 1 && E.firstChild?.nodeType === Node.TEXT_NODE && (E.textContent = E.textContent?.replace(/{{\s*value\s*}}/g, r == null ? "" : String(r)).replace(/{{\s*row\.([a-zA-Z0-9_]+)\s*}}/g, (p, m) => {
1475
- if (!S(m)) return "";
1476
- const C = i[m];
1477
- return C == null ? "" : String(C);
1478
- }) || "");
1479
- });
1480
- const f = d.querySelector(
1481
- "input,textarea,select"
1482
- );
1483
- if (f) {
1484
- f instanceof HTMLInputElement && f.type === "checkbox" ? f.checked = !!r : f.value = String(r ?? "");
1485
- let E = !1;
1486
- f.addEventListener("blur", () => {
1487
- E || s(_(f, t, r));
1488
- }), f.addEventListener("keydown", (p) => {
1489
- const m = p;
1490
- if (m.key === "Enter") {
1491
- if (this.config.onBeforeEditClose && this.config.onBeforeEditClose(m) === !1)
1492
- return;
1493
- m.stopPropagation(), m.preventDefault(), E = !0, s(_(f, t, r)), this.#s(c, !1);
1494
- }
1495
- if (m.key === "Escape") {
1496
- if (this.config.onBeforeEditClose && this.config.onBeforeEditClose(m) === !1)
1497
- return;
1498
- m.stopPropagation(), m.preventDefault(), n(), this.#s(c, !0);
1499
- }
1500
- }), f instanceof HTMLInputElement && f.type === "checkbox" && f.addEventListener("change", () => s(f.checked)), l || setTimeout(() => f.focus({ preventScroll: !0 }), 0);
1501
- }
1502
- e.appendChild(d);
1503
- }
1504
- /**
1505
- * Compare snapshot vs current row to detect if any values changed during this edit session.
1506
- * Uses shallow comparison of all properties.
1507
- */
1508
- #L(e, t) {
1509
- if (!e) return !1;
1510
- const i = e, r = t, s = /* @__PURE__ */ new Set([...Object.keys(i), ...Object.keys(r)]);
1511
- for (const n of s)
1512
- if (i[n] !== r[n])
1513
- return !0;
1514
- return !1;
1515
- }
1516
- /**
1517
- * Restore focus to cell after exiting edit mode.
1518
- */
1519
- #y(e) {
1520
- queueMicrotask(() => {
1521
- try {
1522
- const t = e._focusRow, i = e._focusCol, r = e.findRenderedRowElement?.(t);
1523
- if (r) {
1524
- Array.from(e._bodyEl.querySelectorAll(".cell-focus")).forEach(
1525
- (n) => n.classList.remove("cell-focus")
1526
- );
1527
- const s = r.querySelector(`.cell[data-row="${t}"][data-col="${i}"]`);
1528
- s && (s.classList.add("cell-focus"), s.setAttribute("aria-selected", "true"), s.hasAttribute("tabindex") || s.setAttribute("tabindex", "-1"), s.focus({ preventScroll: !0 }));
1529
- }
1530
- } catch {
1531
- }
1532
- });
1533
- }
1534
- // #endregion
1535
- }
1536
- export {
1537
- Z as EditingPlugin,
1538
- j as defaultEditorFor
1539
- };
1
+ const e='input,select,textarea,[contenteditable="true"],[contenteditable=""],[tabindex]:not([tabindex="-1"])';document.createElement("template").innerHTML='<div class="cell" role="gridcell" part="cell"></div>';function t(t,i){if(t._virtualization?.enabled){const{rowHeight:e,container:i,viewportEl:n}=t._virtualization,o=i,r=n?.clientHeight??o?.clientHeight??0;if(o&&r>0){const i=t._focusRow*e;i<o.scrollTop?o.scrollTop=i:i+e>o.scrollTop+r&&(o.scrollTop=i-r+e)}}const n=void 0!==t._activeEditRows&&-1!==t._activeEditRows;var o;n||t.refreshVirtualWindow(!1),(o=t._bodyEl)&&o.querySelectorAll(".cell-focus").forEach(e=>e.classList.remove("cell-focus")),Array.from(t._bodyEl.querySelectorAll('[aria-selected="true"]')).forEach(e=>{e.setAttribute("aria-selected","false")});const r=t._focusRow,s=t._virtualization.start??0,d=t._virtualization.end??t._rows.length;if(r>=s&&r<d){const o=t._bodyEl.querySelectorAll(".data-grid-row")[r-s];let d=o?.children[t._focusCol];if(d&&d.classList?.contains("cell")||(d=o?.querySelector(`.cell[data-col="${t._focusCol}"]`)??o?.querySelector(".cell[data-col]")),d){d.classList.add("cell-focus"),d.setAttribute("aria-selected","true");const r=t.querySelector(".tbw-scroll-area");if(r&&d&&(!n||i?.forceHorizontalScroll))if(i?.forceScrollLeft)r.scrollLeft=0;else if(i?.forceScrollRight)r.scrollLeft=r.scrollWidth-r.clientWidth;else{const e=t._getHorizontalScrollOffsets?.(o??void 0,d)??{left:0,right:0};if(!e.skipScroll){const t=d.getBoundingClientRect(),i=r.getBoundingClientRect(),n=t.left-i.left+r.scrollLeft,o=n+t.width,s=r.scrollLeft+e.left,l=r.scrollLeft+r.clientWidth-e.right;n<s?r.scrollLeft=n-e.left:o>l&&(r.scrollLeft=o-r.clientWidth+e.right)}}if(n&&d.classList.contains("editing")){const t=d.querySelector(e);if(t&&document.activeElement!==t)try{t.focus({preventScroll:!0})}catch{}}else if(n&&!d.contains(document.activeElement)){d.hasAttribute("tabindex")||d.setAttribute("tabindex","-1");try{d.focus({preventScroll:!0})}catch{}}else if(!n){const e=t;document.activeElement!==e&&e.focus({preventScroll:!0})}}}}document.createElement("template").innerHTML='<div class="data-grid-row" role="row" part="row"></div>';const i='<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>',n={expand:"▶",collapse:"▼",sortAsc:"▲",sortDesc:"▼",sortNone:"⇅",submenuArrow:"▶",dragHandle:"⋮⋮",toolPanel:"☰",filter:i,filterActive:i,print:"🖨️"};class o{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{...n,...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}`)}}function r(e){switch(e.type){case"number":return function(e){return t=>{const i=e.editorParams,n=document.createElement("input");n.type="number",n.value=null!=t.value?String(t.value):"",void 0!==i?.min&&(n.min=String(i.min)),void 0!==i?.max&&(n.max=String(i.max)),void 0!==i?.step&&(n.step=String(i.step)),i?.placeholder&&(n.placeholder=i.placeholder);const o=()=>t.commit(""===n.value?null:Number(n.value));return n.addEventListener("blur",o),n.addEventListener("keydown",e=>{"Enter"===e.key&&o(),"Escape"===e.key&&t.cancel()}),n}}(e);case"boolean":return e=>{const t=document.createElement("input");return t.type="checkbox",t.checked=!!e.value,t.addEventListener("change",()=>e.commit(t.checked)),t};case"date":return function(e){return t=>{const i=e.editorParams,n=document.createElement("input");return n.type="date",t.value instanceof Date?n.valueAsDate=t.value:"string"==typeof t.value&&t.value&&(n.value=t.value.split("T")[0]),i?.min&&(n.min=i.min),i?.max&&(n.max=i.max),i?.placeholder&&(n.placeholder=i.placeholder),n.addEventListener("change",()=>{"string"==typeof t.value?t.commit(n.value):t.commit(n.valueAsDate)}),n.addEventListener("keydown",e=>{"Escape"===e.key&&t.cancel()}),n}}(e);case"select":return function(e){return t=>{const i=e.editorParams,n=document.createElement("select");if(e.multi&&(n.multiple=!0),i?.includeEmpty){const e=document.createElement("option");e.value="",e.textContent=i.emptyLabel??"",n.appendChild(e)}const o=function(e){const t=e.options;return t?"function"==typeof t?t():t:[]}(e);o.forEach(i=>{const o=document.createElement("option");o.value=String(i.value),o.textContent=i.label,e.multi&&Array.isArray(t.value)&&t.value.includes(i.value)?o.selected=!0:e.multi||t.value!==i.value||(o.selected=!0),n.appendChild(o)});const r=()=>{if(e.multi){const e=Array.from(n.selectedOptions).map(e=>e.value);t.commit(e)}else t.commit(n.value)};return n.addEventListener("change",r),n.addEventListener("blur",r),n.addEventListener("keydown",e=>{"Escape"===e.key&&t.cancel()}),n}}(e);default:return function(e){return t=>{const i=e.editorParams,n=document.createElement("input");n.type="text",n.value=null!=t.value?String(t.value):"",void 0!==i?.maxLength&&(n.maxLength=i.maxLength),i?.pattern&&(n.pattern=i.pattern),i?.placeholder&&(n.placeholder=i.placeholder);const o=()=>{const e=n.value;(null!==t.value&&void 0!==t.value||""!==e)&&("string"==typeof t.value&&e===t.value.replace(/[\n\r]/g,"")||("number"==typeof t.value&&""!==e?t.commit(Number(e)):t.commit(e)))};return n.addEventListener("blur",o),n.addEventListener("keydown",e=>{"Enter"===e.key&&o(),"Escape"===e.key&&t.cancel()}),n}}(e)}}function s(e){return"string"==typeof e&&("__proto__"!==e&&"constructor"!==e&&"prototype"!==e)}function d(e,t,i){return e instanceof HTMLInputElement?"checkbox"===e.type?e.checked:"number"===e.type?""===e.value?null:Number(e.value):"date"===e.type?"string"==typeof i?e.value:e.valueAsDate:"number"==typeof i?""===e.value?null:Number(e.value):null==i&&""===e.value||"string"==typeof i&&e.value===i.replace(/[\n\r]/g,"")?i:e.value:"number"===t?.type&&""!==e.value||"number"==typeof i&&""!==e.value?Number(e.value):null==i&&""===e.value?i:e.value}function l(e){}class c extends o{static manifest={ownedProperties:[{property:"editable",level:"column",description:'the "editable" column property',isUsed:e=>!0===e},{property:"editor",level:"column",description:'the "editor" column property'},{property:"editorParams",level:"column",description:'the "editorParams" column property'}],events:[{type:"cell-edit-committed",description:"Emitted when a cell edit is committed (for plugin-to-plugin coordination)"}],queries:[{type:"isEditing",description:"Returns whether any cell is currently being edited"}]};name="editing";styles="@layer tbw-plugins{tbw-grid{--tbw-editing-bg: var(--tbw-color-selection);--tbw-editing-row-bg: var(--tbw-editing-bg);--tbw-editing-border: var(--tbw-border-input, 1px solid var(--tbw-color-border-strong));--tbw-padding-editing-input: var(--tbw-cell-padding-input, 2px 6px);--tbw-font-size-editor: inherit;--tbw-editing-row-outline-color: var(--tbw-color-accent);--tbw-editing-row-outline-width: 1px;--tbw-invalid-bg: light-dark(#fef2f2, #450a0a);--tbw-invalid-border-color: light-dark(#ef4444, #f87171)}tbw-grid:not(.tbw-grid-mode) .data-grid-row:has(.editing){background:var(--tbw-editing-row-bg);outline:var(--tbw-editing-row-outline-width) solid var(--tbw-editing-row-outline-color);outline-offset:calc(-1 * var(--tbw-editing-row-outline-width))}tbw-grid .data-grid-row>.cell.editing{overflow:hidden;padding:0;display:flex;min-height:calc(var(--tbw-row-height) + 2px);align-items:center;justify-content:center}tbw-grid .data-grid-row>.cell.editing input:not([type=checkbox]),tbw-grid .data-grid-row>.cell.editing select,tbw-grid .data-grid-row>.cell.editing textarea{width:100%;height:100%;flex:1 1 auto;min-width:0;border:var(--tbw-editing-border);padding:var(--tbw-padding-editing-input);font-size:var(--tbw-font-size-editor)}tbw-grid .tbw-editor-host{display:contents}tbw-grid .data-grid-row>.cell[data-invalid=true]{background:var(--tbw-invalid-bg);outline:1px solid var(--tbw-invalid-border-color);outline-offset:-1px}}";get defaultConfig(){return{mode:"row",editOn:"click"}}get#t(){return"grid"===this.config.mode}#i=-1;#n;#o;#r=-1;#s=/* @__PURE__ */new Map;#d=/* @__PURE__ */new Set;#l=/* @__PURE__ */new Set;#c=/* @__PURE__ */new Map;#a=!1;#u=-1;#h=/* @__PURE__ */new Map;#f=!1;#g=!1;#w=!1;attach(t){super.attach(t);const i=this.disconnectSignal,n=t;n._activeEditRows=-1,n._rowEditSnapshots=/* @__PURE__ */new Map,Object.defineProperty(t,"changedRows",{get:()=>this.changedRows,configurable:!0}),Object.defineProperty(t,"changedRowIds",{get:()=>this.changedRowIds,configurable:!0}),t.resetChangedRows=e=>this.resetChangedRows(e),t.beginBulkEdit=(e,t)=>{t&&this.beginCellEdit(e,t)},document.addEventListener("keydown",e=>{if(!this.#t&&"Escape"===e.key&&-1!==this.#i){if(this.config.onBeforeEditClose){if(!1===this.config.onBeforeEditClose(e))return}this.#v(this.#i,!0)}},{capture:!0,signal:i}),document.addEventListener("mousedown",e=>{if(this.#t)return;if(-1===this.#i)return;const t=n.findRenderedRowElement?.(this.#i);if(!t)return;if(!(e.composedPath&&e.composedPath()||[]).includes(t)){if(this.config.onBeforeEditClose){if(!1===this.config.onBeforeEditClose(e))return}queueMicrotask(()=>{-1!==this.#i&&this.#v(this.#i,!1)})}},{signal:i}),this.gridElement.addEventListener("cell-change",e=>{const t=e.detail;if("user"===t.source)return;const i=`${t.rowIndex}:${t.field}`,n=this.#c.get(i);n&&n(t.newValue)},{signal:i}),this.#t&&(n._isGridEditMode=!0,this.gridElement.classList.add("tbw-grid-mode"),this.requestRender(),this.gridElement.addEventListener("focusin",t=>{const i=t.target;if(i.matches(e)){if(this.#g)return i.blur(),void this.gridElement.focus();this.#f=!0}},{signal:i}),this.gridElement.addEventListener("focusout",t=>{const i=t.relatedTarget;i&&this.gridElement.contains(i)&&i.matches(e)||(this.#f=!1)},{signal:i}),this.gridElement.addEventListener("keydown",e=>{if("Escape"===e.key&&this.#f){if(this.config.onBeforeEditClose){if(!1===this.config.onBeforeEditClose(e))return}const t=document.activeElement;t&&this.gridElement.contains(t)&&(t.blur(),this.gridElement.focus()),this.#f=!1,this.#g=!0,e.preventDefault(),e.stopPropagation()}},{capture:!0,signal:i}),this.gridElement.addEventListener("mousedown",t=>{t.target.matches(e)&&(this.#g=!1)},{signal:i}))}detach(){this.gridElement._isGridEditMode=!1,this.gridElement.classList.remove("tbw-grid-mode"),this.#i=-1,this.#n=void 0,this.#o=void 0,this.#r=-1,this.#s.clear(),this.#d.clear(),this.#l.clear(),this.#c.clear(),this.#f=!1,this.#g=!1,this.#w=!1,super.detach()}handleQuery(e){if("isEditing"===e.type)return this.#t||-1!==this.#i}onCellClick(e){if(this.#t)return!1;const t=this.grid,i=this.config.editOn??t.effectiveConfig?.editOn;if(!1===i||"manual"===i)return!1;if("click"!==i&&"dblclick"!==i)return!1;const n="dblclick"===e.originalEvent.type;if("click"===i&&n)return!1;if("dblclick"===i&&!n)return!1;const{rowIndex:o}=e,r=t._columns?.some(e=>e.editable);return!!r&&(e.originalEvent.stopPropagation(),this.beginBulkEdit(o),!0)}onKeyDown(e){const i=this.grid;if("Escape"===e.key){if(this.#t&&this.#f){if(this.config.onBeforeEditClose){if(!1===this.config.onBeforeEditClose(e))return!0}const t=document.activeElement;return t&&this.gridElement.contains(t)&&t.blur(),this.#f=!1,this.requestAfterRender(),!0}if(-1!==this.#i&&!this.#t){if(this.config.onBeforeEditClose){if(!1===this.config.onBeforeEditClose(e))return!0}return this.#v(this.#i,!0),!0}}if(this.#t&&!this.#f&&("ArrowUp"===e.key||"ArrowDown"===e.key||"ArrowLeft"===e.key||"ArrowRight"===e.key))return!1;if(this.#t&&this.#f&&("ArrowUp"===e.key||"ArrowDown"===e.key))return!0;if(("ArrowUp"===e.key||"ArrowDown"===e.key)&&-1!==this.#i&&!this.#t){if(this.config.onBeforeEditClose){if(!1===this.config.onBeforeEditClose(e))return!0}const n=i._rows.length-1,o=this.#i;return this.#v(o,!1),"ArrowDown"===e.key?i._focusRow=Math.min(n,i._focusRow+1):i._focusRow=Math.max(0,i._focusRow-1),e.preventDefault(),t(i),this.requestAfterRender(),!0}if("Tab"===e.key&&(-1!==this.#i||this.#t)){if(e.preventDefault(),this.#w)return this.#v(this.#i,!1),!0;const t=!e.shiftKey;return this.#p(t),!0}if(" "===e.key||"Spacebar"===e.key){if(-1!==this.#i)return!1;const t=i._focusRow,n=i._focusCol;if(t>=0&&n>=0){const o=i._visibleColumns[n],r=i._rows[t];if(o?.editable&&"boolean"===o.type&&r){const i=o.field;if(s(i)){const n=!r[i];return this.#m(t,o,n,r),e.preventDefault(),this.requestRender(),!0}}}return!1}if(!("Enter"!==e.key||e.shiftKey||e.ctrlKey||e.altKey||e.metaKey)){if(this.#t&&!this.#f)return this.#E(),!0;if(-1!==this.#i){if(this.config.onBeforeEditClose){if(!1===this.config.onBeforeEditClose(e))return!0}return!1}const t=this.config.editOn??i.effectiveConfig?.editOn;if(!1===t||"manual"===t)return!1;const n=i._focusRow,o=i._focusCol;if(n>=0){const t=i._columns?.some(e=>e.editable);if(t){const t=i._visibleColumns[o],r=i._rows[n],s=t?.field??"",d=s&&r?r[s]:void 0,l=this.gridElement.querySelector(`[data-row="${n}"][data-col="${o}"]`),c=new CustomEvent("cell-activate",{cancelable:!0,bubbles:!0,detail:{rowIndex:n,colIndex:o,field:s,value:d,row:r,cellEl:l,trigger:"keyboard",originalEvent:e}});this.gridElement.dispatchEvent(c);const a=new CustomEvent("activate-cell",{cancelable:!0,bubbles:!0,detail:{row:n,col:o}});return this.gridElement.dispatchEvent(a),c.defaultPrevented||a.defaultPrevented?(e.preventDefault(),!0):(this.beginBulkEdit(n),!0)}}return!1}if("F2"===e.key){if(-1!==this.#i||this.#t)return!1;if(!1===(this.config.editOn??i.effectiveConfig?.editOn))return!1;const t=i._focusRow,n=i._focusCol;if(t>=0&&n>=0){const o=i._visibleColumns[n];if(o?.editable&&o.field)return e.preventDefault(),this.beginCellEdit(t,o.field),!0}return!1}return!1}processColumns(e){const t=this.grid,i=t.effectiveConfig?.typeDefaults,n=t.__frameworkAdapter;return i||n?.getTypeDefault?e.map(e=>{if(!e.type)return e;let t;if(i?.[e.type]?.editorParams&&(t=i[e.type].editorParams),!t&&n?.getTypeDefault){const i=n.getTypeDefault(e.type);i?.editorParams&&(t=i.editorParams)}return t?{...e,editorParams:{...t,...e.editorParams}}:e}):e}afterRender(){const e=this.grid;if(this.#a&&(this.#a=!1,this.#b(e)),-1!==this.#u){const t=this.#u;this.#u=-1,e.animateRow?.(t,"change")}if(!this.#t&&0!==this.#l.size)for(const t of this.#l){const[i,n]=t.split(":"),o=parseInt(i,10),r=parseInt(n,10),s=e.findRenderedRowElement?.(o);if(!s)continue;const d=s.querySelector(`.cell[data-col="${r}"]`);if(!d||d.classList.contains("editing"))continue;const l=e._rows[o],c=e._visibleColumns[r];l&&c&&this.#R(l,o,c,r,d,!0)}}afterCellRender(e){if(!this.#t)return;const{row:t,rowIndex:i,column:n,colIndex:o,cellElement:r}=e;n.editable&&(r.classList.contains("editing")||this.#R(t,i,n,o,r,!0))}onScrollRender(){this.afterRender()}get changedRows(){const e=[];for(const t of this.#d){const i=this.grid.getRow(t);i&&e.push(i)}return e}get changedRowIds(){return Array.from(this.#d)}get activeEditRow(){return this.#i}get activeEditCol(){return this.#r}isRowEditing(e){return this.#i===e}isCellEditing(e,t){return this.#l.has(`${e}:${t}`)}isRowChanged(e){const t=this.grid,i=t._rows[e];if(!i)return!1;try{const e=t.getRowId?.(i);return!!e&&this.#d.has(e)}catch{return!1}}isRowChangedById(e){return this.#d.has(e)}setInvalid(e,t,i=""){let n=this.#h.get(e);n||(n=/* @__PURE__ */new Map,this.#h.set(e,n)),n.set(t,i),this.#y(e,t,!0)}clearInvalid(e,t){const i=this.#h.get(e);i&&(i.delete(t),0===i.size&&this.#h.delete(e)),this.#y(e,t,!1)}clearRowInvalid(e){const t=this.#h.get(e);if(t){const i=Array.from(t.keys());this.#h.delete(e),i.forEach(t=>this.#y(e,t,!1))}}clearAllInvalid(){const e=Array.from(this.#h.entries());this.#h.clear(),e.forEach(([e,t])=>{t.forEach((t,i)=>this.#y(e,i,!1))})}isCellInvalid(e,t){return this.#h.get(e)?.has(t)??!1}getInvalidMessage(e,t){return this.#h.get(e)?.get(t)}hasInvalidCells(e){const t=this.#h.get(e);return!!t&&t.size>0}getInvalidFields(e){return new Map(this.#h.get(e)??[])}#y(e,t,i){const n=this.grid,o=n._visibleColumns?.findIndex(e=>e.field===t);if(-1===o||void 0===o)return;const r=n._rows,s=r?.findIndex(t=>{try{return n.getRowId?.(t)===e}catch{return!1}});if(-1===s||void 0===s)return;const d=n.findRenderedRowElement?.(s),l=d?.querySelector(`.cell[data-col="${o}"]`);if(l)if(i){l.setAttribute("data-invalid","true");const i=this.#h.get(e)?.get(t);i&&l.setAttribute("title",i)}else l.removeAttribute("data-invalid"),l.removeAttribute("title")}resetChangedRows(e){const t=this.changedRows,i=this.changedRowIds;this.#d.clear(),this.#C(),e||this.emit("changed-rows-reset",{rows:t,ids:i});const n=this.grid;n._rowPool?.forEach(e=>e.classList.remove("changed"))}beginCellEdit(e,t){const i=this.grid,n=i._visibleColumns.findIndex(e=>e.field===t);if(-1===n)return;const o=i._visibleColumns[n];if(!o?.editable)return;const r=i.findRenderedRowElement?.(e),s=r?.querySelector(`.cell[data-col="${n}"]`);s&&(this.#w=!0,this.#_(e,n,s))}beginBulkEdit(t){const i=this.grid;if(!1===(this.config.editOn??i.effectiveConfig?.editOn))return;const n=i._columns?.some(e=>e.editable);if(!n)return;const o=i.findRenderedRowElement?.(t);if(!o)return;this.#w=!1;const r=i._rows[t];this.#I(t,r),Array.from(o.children).forEach((e,n)=>{const o=i._visibleColumns[n];if(o?.editable){const i=e;i.classList.contains("editing")||this.#R(r,t,o,n,i,!0)}}),setTimeout(()=>{let t=o.querySelector(`.cell[data-col="${i._focusCol}"]`);if(t?.classList.contains("editing")||(t=o.querySelector(".cell.editing")),t?.classList.contains("editing")){const i=t.querySelector(e);try{i?.focus({preventScroll:!0})}catch{}}},0)}commitActiveRowEdit(){-1!==this.#i&&this.#v(this.#i,!1)}cancelActiveRowEdit(){-1!==this.#i&&this.#v(this.#i,!0)}#_(e,t,i){const n=this.grid,o=n._rows[e],r=n._visibleColumns[t];o&&r?.editable&&(i.classList.contains("editing")||(this.#i!==e&&this.#I(e,o),this.#r=t,this.#R(o,e,r,t,i,!1)))}#E(){const t=this.grid,i=t._focusRow,n=t._focusCol;if(i<0||n<0)return;const o=t.findRenderedRowElement?.(i),r=o?.querySelector(`.cell[data-col="${n}"]`);if(r?.classList.contains("editing")){const t=r.querySelector(e);t&&(this.#g=!1,t.focus(),this.#f=!0,t instanceof HTMLInputElement&&("text"===t.type||"number"===t.type)&&t.select())}}#p(i){const n=this.grid,o=n._rows,r=this.#t?n._focusRow:this.#i,s=n._visibleColumns.map((e,t)=>e.editable?t:-1).filter(e=>e>=0);if(0===s.length)return;const d=s.indexOf(n._focusCol)+(i?1:-1);if(d>=0&&d<s.length){n._focusCol=s[d];const i=n.findRenderedRowElement?.(r),o=i?.querySelector(`.cell[data-col="${s[d]}"]`);if(o?.classList.contains("editing")){const t=o.querySelector(e);t?.focus({preventScroll:!0})}return void t(n,{forceHorizontalScroll:!0})}const l=r+(i?1:-1);l>=0&&l<o.length&&(this.#t?(n._focusRow=l,n._focusCol=i?s[0]:s[s.length-1],t(n,{forceHorizontalScroll:!0}),this.requestAfterRender(),setTimeout(()=>{const t=n.findRenderedRowElement?.(l),i=t?.querySelector(`.cell[data-col="${n._focusCol}"]`);if(i?.classList.contains("editing")){const t=i.querySelector(e);t?.focus({preventScroll:!0})}},0)):(this.#v(r,!1),n._focusRow=l,n._focusCol=i?s[0]:s[s.length-1],this.beginBulkEdit(l),t(n,{forceHorizontalScroll:!0})))}#C(){const e=this.grid;e._activeEditRows=this.#i,e._rowEditSnapshots=this.#s}#I(e,t){if(this.#i!==e){this.#s.set(e,{...t}),this.#i=e,this.#o=t;const i=this.grid;try{this.#n=i.getRowId?.(t)??void 0}catch{this.#n=void 0}this.#C(),this.#t||this.emit("edit-open",{rowIndex:e,rowId:this.#n??"",row:t})}}#v(e,t){if(this.#i!==e)return;const i=this.grid,n=this.#s.get(e),o=i.findRenderedRowElement?.(e);let r=this.#n;const s=r?i._getRowEntry(r):void 0,l=s?.row??this.#o??i._rows[e];if(!r&&l)try{r=i.getRowId?.(l)}catch{}if(!t&&o&&l){o.querySelectorAll(".cell.editing").forEach(t=>{const n=Number(t.getAttribute("data-col"));if(isNaN(n))return;const o=i._visibleColumns[n];if(!o)return;if(t.hasAttribute("data-editor-managed"))return;const r=t.querySelector("input,textarea,select");if(r){const t=o.field,i=l[t],n=d(r,o,i);i!==n&&this.#m(e,o,n,l)}})}if(t||this.#t||!l||this.emit("before-edit-close",{rowIndex:e,rowId:r??"",row:l}),t&&n&&l)Object.keys(n).forEach(e=>{l[e]=n[e]}),r&&(this.#d.delete(r),this.clearRowInvalid(r));else if(!t&&l){const t=this.#k(n,l),i=r?this.#d.has(r):t,o=this.emitCancelable("row-commit",{rowIndex:e,rowId:r??"",row:l,oldValue:n,newValue:l,changed:i,changedRows:this.changedRows,changedRowIds:this.changedRowIds});o&&n?(Object.keys(n).forEach(e=>{l[e]=n[e]}),r&&(this.#d.delete(r),this.clearRowInvalid(r))):!o&&t&&this.isAnimationEnabled&&(this.#u=e)}this.#s.delete(e),this.#i=-1,this.#n=void 0,this.#o=void 0,this.#r=-1,this.#w=!1,this.#C();for(const d of this.#l)d.startsWith(`${e}:`)&&this.#l.delete(d);for(const d of this.#c.keys())d.startsWith(`${e}:`)&&this.#c.delete(d);this.#a=!0,o?(o.querySelectorAll(".cell.editing").forEach(e=>{e.classList.remove("editing"),function(e){e.__editingCellCount=0,e.removeAttribute("data-has-editing")}(e.parentElement)}),i.refreshVirtualWindow(!0)):(this.#b(i),this.#a=!1),!this.#t&&l&&this.emit("edit-close",{rowIndex:e,rowId:r??"",row:l,reverted:t})}#m(e,t,i,n){const o=t.field;if(!s(o))return;const r=n[o];if(r===i)return;const d=this.grid;let c;try{c=this.grid.getRowId(n)}catch{}const a=!c||!this.#d.has(c),u=c?e=>this.grid.updateRow(c,e,"cascade"):l;let h=!1;const f=c?e=>{h=!0,this.setInvalid(c,o,e??"")}:()=>{};if(this.emitCancelable("cell-commit",{row:n,rowId:c??"",field:o,oldValue:r,value:i,rowIndex:e,changedRows:this.changedRows,changedRowIds:this.changedRowIds,firstTimeForRow:a,updateRow:u,setInvalid:f}))return;c&&!h&&this.isCellInvalid(c,o)&&this.clearInvalid(c,o),n[o]=i,c&&this.#d.add(c),this.#C(),this.emitPluginEvent("cell-edit-committed",{rowIndex:e,field:o,oldValue:r,newValue:i});const g=d.findRenderedRowElement?.(e);g&&g.classList.add("changed")}#R(t,i,n,o,c,a){if(!n.editable)return;if(c.classList.contains("editing"))return;let u;try{u=this.grid.getRowId(t)}catch{}const h=u?e=>this.grid.updateRow(u,e,"cascade"):l,f=s(n.field)?t[n.field]:void 0;c.classList.add("editing"),this.#l.add(`${i}:${o}`);const g=c.parentElement;g&&function(e){const t=(e.__editingCellCount??0)+1;e.__editingCellCount=t,e.setAttribute("data-has-editing","")}(g);let w=!1;const v=e=>{if(w||!this.#t&&-1===this.#i)return;const o=this.grid,r=u?o._getRowEntry(u):void 0,s=r?.row??t,d=r?.index??i;this.#m(d,n,e,s)},p=()=>{if(w=!0,s(n.field)){const e=this.grid,i=u?e._getRowEntry(u):void 0;(i?.row??t)[n.field]=f}},m=document.createElement("div");m.className="tbw-editor-host",c.innerHTML="",c.appendChild(m),m.addEventListener("keydown",e=>{if("Enter"===e.key){if(this.#t){e.stopPropagation(),e.preventDefault();const t=m.querySelector("input,textarea,select");return void(t&&v(d(t,n,f)))}if(this.config.onBeforeEditClose){if(!1===this.config.onBeforeEditClose(e))return}e.stopPropagation(),e.preventDefault(),w=!0,this.#v(i,!1)}if("Escape"===e.key){if(this.#t)return e.stopPropagation(),void e.preventDefault();if(this.config.onBeforeEditClose){if(!1===this.config.onBeforeEditClose(e))return}e.stopPropagation(),e.preventDefault(),p(),this.#v(i,!0)}});const E=n,b=E.__editorTemplate,R=function(e,t){if(t.editor)return t.editor;if(t.__editorTemplate)return"template";if(!t.type)return;const i=e.effectiveConfig?.typeDefaults;if(i?.[t.type]?.editor)return i[t.type].editor;const n=e.__frameworkAdapter;if(n?.getTypeDefault){const e=n.getTypeDefault(t.type);if(e?.editor)return e.editor}}(this.grid,E)??r(n),y=f,C=`${i}:${n.field}`,_=[];this.#c.set(C,e=>{for(const t of _)t(e)});const I=e=>{_.push(e)};if("template"===R&&b)this.#S(m,E,t,f,v,p,a,i),I(e=>{const t=m.querySelector("input,textarea,select");t&&(t instanceof HTMLInputElement&&"checkbox"===t.type?t.checked=!!e:t.value=String(e??""))});else if("string"==typeof R){const t=document.createElement(R);t.value=y,t.addEventListener("change",()=>v(t.value)),I(e=>{t.value=e}),m.appendChild(t),a||queueMicrotask(()=>{const t=m.querySelector(e);t?.focus({preventScroll:!0})})}else if("function"==typeof R){const i=R({row:t,rowId:u??"",value:y,field:n.field,column:n,commit:v,cancel:p,updateRow:h,onValueChange:I});if("string"==typeof i)m.innerHTML=i,function(e,t,i,n){const o=e.querySelector("input,textarea,select");o&&(o.addEventListener("blur",()=>{i(d(o,t,n))}),o instanceof HTMLInputElement&&"checkbox"===o.type?o.addEventListener("change",()=>i(o.checked)):o instanceof HTMLSelectElement&&o.addEventListener("change",()=>i(d(o,t,n))))}(m,n,v,f),I(e=>{const t=m.querySelector("input,textarea,select");t&&(t instanceof HTMLInputElement&&"checkbox"===t.type?t.checked=!!e:t.value=String(e??""))});else if(i instanceof Node){m.appendChild(i);i instanceof HTMLInputElement||i instanceof HTMLSelectElement||i instanceof HTMLTextAreaElement?I(e=>{i instanceof HTMLInputElement&&"checkbox"===i.type?i.checked=!!e:i.value=String(e??"")}):c.setAttribute("data-editor-managed","")}a||queueMicrotask(()=>{const t=m.querySelector(e);t?.focus({preventScroll:!0})})}else if(R&&"object"==typeof R){const e=document.createElement("div");e.setAttribute("data-external-editor",""),e.setAttribute("data-field",n.field),m.appendChild(e),c.setAttribute("data-editor-managed","");const i={row:t,rowId:u??"",value:y,field:n.field,column:n,commit:v,cancel:p,updateRow:h,onValueChange:I};if(R.mount)try{R.mount({placeholder:e,context:i,spec:R})}catch(k){console.warn(`[tbw-grid] External editor mount error for column '${n.field}':`,k)}else this.grid.dispatchEvent(new CustomEvent("mount-external-editor",{detail:{placeholder:e,spec:R,context:i}}))}}#S(e,t,i,n,o,r,l,c){const a=t.__editorTemplate;if(!a)return;const u=a.cloneNode(!0),h=t.__compiledEditor;h?u.innerHTML=h({row:i,value:n,field:t.field,column:t,commit:o,cancel:r}):u.querySelectorAll("*").forEach(e=>{1===e.childNodes.length&&e.firstChild?.nodeType===Node.TEXT_NODE&&(e.textContent=e.textContent?.replace(/{{\s*value\s*}}/g,null==n?"":String(n)).replace(/{{\s*row\.([a-zA-Z0-9_]+)\s*}}/g,(e,t)=>{if(!s(t))return"";const n=i[t];return null==n?"":String(n)})||"")});const f=u.querySelector("input,textarea,select");if(f){f instanceof HTMLInputElement&&"checkbox"===f.type?f.checked=!!n:f.value=String(n??"");let e=!1;f.addEventListener("blur",()=>{e||o(d(f,t,n))}),f.addEventListener("keydown",i=>{const s=i;if("Enter"===s.key){if(this.config.onBeforeEditClose){if(!1===this.config.onBeforeEditClose(s))return}s.stopPropagation(),s.preventDefault(),e=!0,o(d(f,t,n)),this.#v(c,!1)}if("Escape"===s.key){if(this.config.onBeforeEditClose){if(!1===this.config.onBeforeEditClose(s))return}s.stopPropagation(),s.preventDefault(),r(),this.#v(c,!0)}}),f instanceof HTMLInputElement&&"checkbox"===f.type&&f.addEventListener("change",()=>o(f.checked)),l||setTimeout(()=>f.focus({preventScroll:!0}),0)}e.appendChild(u)}#k(e,t){if(!e)return!1;const i=e,n=t,o=/* @__PURE__ */new Set([...Object.keys(i),...Object.keys(n)]);for(const r of o)if(i[r]!==n[r])return!0;return!1}#b(e){queueMicrotask(()=>{try{const t=e._focusRow,i=e._focusCol,n=e.findRenderedRowElement?.(t);if(n){Array.from(e._bodyEl.querySelectorAll(".cell-focus")).forEach(e=>e.classList.remove("cell-focus"));const o=n.querySelector(`.cell[data-row="${t}"][data-col="${i}"]`);o&&(o.classList.add("cell-focus"),o.setAttribute("aria-selected","true"),o.hasAttribute("tabindex")||o.setAttribute("tabindex","-1"),o.focus({preventScroll:!0}))}}catch{}})}}export{c as EditingPlugin,r as defaultEditorFor};
1540
2
  //# sourceMappingURL=index.js.map