@toolbox-web/grid 1.9.1 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/all.js +1082 -975
  2. package/all.js.map +1 -1
  3. package/index.js +350 -324
  4. package/index.js.map +1 -1
  5. package/lib/core/grid.d.ts +40 -1
  6. package/lib/core/grid.d.ts.map +1 -1
  7. package/lib/core/plugin/base-plugin.d.ts +11 -0
  8. package/lib/core/plugin/base-plugin.d.ts.map +1 -1
  9. package/lib/core/types.d.ts +0 -64
  10. package/lib/core/types.d.ts.map +1 -1
  11. package/lib/plugins/clipboard/index.js +9 -0
  12. package/lib/plugins/clipboard/index.js.map +1 -1
  13. package/lib/plugins/column-virtualization/ColumnVirtualizationPlugin.d.ts +3 -0
  14. package/lib/plugins/column-virtualization/ColumnVirtualizationPlugin.d.ts.map +1 -1
  15. package/lib/plugins/column-virtualization/index.js +90 -57
  16. package/lib/plugins/column-virtualization/index.js.map +1 -1
  17. package/lib/plugins/context-menu/index.js +9 -0
  18. package/lib/plugins/context-menu/index.js.map +1 -1
  19. package/lib/plugins/editing/EditingPlugin.d.ts +69 -0
  20. package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
  21. package/lib/plugins/editing/editors.d.ts.map +1 -1
  22. package/lib/plugins/editing/index.d.ts +1 -1
  23. package/lib/plugins/editing/index.d.ts.map +1 -1
  24. package/lib/plugins/editing/index.js +435 -293
  25. package/lib/plugins/editing/index.js.map +1 -1
  26. package/lib/plugins/editing/types.d.ts +117 -1
  27. package/lib/plugins/editing/types.d.ts.map +1 -1
  28. package/lib/plugins/export/index.js +38 -29
  29. package/lib/plugins/export/index.js.map +1 -1
  30. package/lib/plugins/filtering/index.js +14 -5
  31. package/lib/plugins/filtering/index.js.map +1 -1
  32. package/lib/plugins/grouping-columns/index.js +9 -0
  33. package/lib/plugins/grouping-columns/index.js.map +1 -1
  34. package/lib/plugins/grouping-rows/index.js +63 -54
  35. package/lib/plugins/grouping-rows/index.js.map +1 -1
  36. package/lib/plugins/master-detail/index.js +25 -16
  37. package/lib/plugins/master-detail/index.js.map +1 -1
  38. package/lib/plugins/multi-sort/index.js +13 -4
  39. package/lib/plugins/multi-sort/index.js.map +1 -1
  40. package/lib/plugins/pinned-columns/index.js +13 -4
  41. package/lib/plugins/pinned-columns/index.js.map +1 -1
  42. package/lib/plugins/pinned-rows/index.js +16 -7
  43. package/lib/plugins/pinned-rows/index.js.map +1 -1
  44. package/lib/plugins/pivot/index.js +13 -4
  45. package/lib/plugins/pivot/index.js.map +1 -1
  46. package/lib/plugins/print/index.js +9 -0
  47. package/lib/plugins/print/index.js.map +1 -1
  48. package/lib/plugins/reorder/index.js +13 -4
  49. package/lib/plugins/reorder/index.js.map +1 -1
  50. package/lib/plugins/responsive/index.js +42 -33
  51. package/lib/plugins/responsive/index.js.map +1 -1
  52. package/lib/plugins/row-reorder/index.js +9 -0
  53. package/lib/plugins/row-reorder/index.js.map +1 -1
  54. package/lib/plugins/selection/index.js +9 -0
  55. package/lib/plugins/selection/index.js.map +1 -1
  56. package/lib/plugins/server-side/index.js +29 -20
  57. package/lib/plugins/server-side/index.js.map +1 -1
  58. package/lib/plugins/tree/index.js +20 -11
  59. package/lib/plugins/tree/index.js.map +1 -1
  60. package/lib/plugins/undo-redo/index.js +15 -6
  61. package/lib/plugins/undo-redo/index.js.map +1 -1
  62. package/lib/plugins/visibility/index.js +9 -0
  63. package/lib/plugins/visibility/index.js.map +1 -1
  64. package/package.json +1 -1
  65. package/public.d.ts +1 -1
  66. package/public.d.ts.map +1 -1
  67. package/umd/grid.all.umd.js +20 -20
  68. package/umd/grid.all.umd.js.map +1 -1
  69. package/umd/grid.umd.js +14 -14
  70. package/umd/grid.umd.js.map +1 -1
  71. package/umd/plugins/column-virtualization.umd.js +1 -1
  72. package/umd/plugins/column-virtualization.umd.js.map +1 -1
  73. package/umd/plugins/editing.umd.js +1 -1
  74. package/umd/plugins/editing.umd.js.map +1 -1
  75. package/umd/plugins/pinned-rows.umd.js +1 -1
  76. package/umd/plugins/pinned-rows.umd.js.map +1 -1
@@ -1,33 +1,4 @@
1
- const R = "data-animating", A = {
2
- change: "--tbw-row-change-duration",
3
- insert: "--tbw-row-insert-duration",
4
- remove: "--tbw-row-remove-duration"
5
- }, P = {
6
- change: 500,
7
- insert: 300,
8
- remove: 200
9
- };
10
- function T(s) {
11
- const e = s.trim().toLowerCase();
12
- return e.endsWith("ms") ? parseFloat(e) : e.endsWith("s") ? parseFloat(e) * 1e3 : parseFloat(e);
13
- }
14
- function I(s, e) {
15
- const i = A[e], t = getComputedStyle(s).getPropertyValue(i);
16
- if (t) {
17
- const r = T(t);
18
- if (!isNaN(r) && r > 0)
19
- return r;
20
- }
21
- return P[e];
22
- }
23
- function D(s, e, i) {
24
- s.removeAttribute(R), s.offsetWidth, s.setAttribute(R, e);
25
- const t = I(s, e);
26
- setTimeout(() => {
27
- s.removeAttribute(R);
28
- }, t);
29
- }
30
- const L = '<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>', q = {
1
+ const S = '<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>', L = {
31
2
  expand: "▶",
32
3
  collapse: "▼",
33
4
  sortAsc: "▲",
@@ -36,11 +7,11 @@ const L = '<svg viewBox="0 0 16 16" width="12" height="12"><path fill="currentCo
36
7
  submenuArrow: "▶",
37
8
  dragHandle: "⋮⋮",
38
9
  toolPanel: "☰",
39
- filter: L,
40
- filterActive: L,
10
+ filter: S,
11
+ filterActive: S,
41
12
  print: "🖨️"
42
13
  };
43
- class O {
14
+ class I {
44
15
  /**
45
16
  * Plugin dependencies - declare other plugins this one requires.
46
17
  *
@@ -167,16 +138,16 @@ class O {
167
138
  /**
168
139
  * Emit a custom event from the grid.
169
140
  */
170
- emit(e, i) {
171
- this.grid?.dispatchEvent?.(new CustomEvent(e, { detail: i, bubbles: !0 }));
141
+ emit(e, t) {
142
+ this.grid?.dispatchEvent?.(new CustomEvent(e, { detail: t, bubbles: !0 }));
172
143
  }
173
144
  /**
174
145
  * Emit a cancelable custom event from the grid.
175
146
  * @returns `true` if the event was cancelled (preventDefault called), `false` otherwise
176
147
  */
177
- emitCancelable(e, i) {
178
- const t = new CustomEvent(e, { detail: i, bubbles: !0, cancelable: !0 });
179
- return this.grid?.dispatchEvent?.(t), t.defaultPrevented;
148
+ emitCancelable(e, t) {
149
+ const i = new CustomEvent(e, { detail: t, bubbles: !0, cancelable: !0 });
150
+ return this.grid?.dispatchEvent?.(i), i.defaultPrevented;
180
151
  }
181
152
  // =========================================================================
182
153
  // Event Bus - Plugin-to-Plugin Communication
@@ -197,8 +168,8 @@ class O {
197
168
  * });
198
169
  * ```
199
170
  */
200
- on(e, i) {
201
- this.grid?._pluginManager?.subscribe(this, e, i);
171
+ on(e, t) {
172
+ this.grid?._pluginManager?.subscribe(this, e, t);
202
173
  }
203
174
  /**
204
175
  * Unsubscribe from a plugin event.
@@ -232,15 +203,24 @@ class O {
232
203
  * this.emit('filter-change', { field: 'name', value: 'Alice' });
233
204
  * ```
234
205
  */
235
- emitPluginEvent(e, i) {
236
- this.grid?._pluginManager?.emitPluginEvent(e, i);
206
+ emitPluginEvent(e, t) {
207
+ this.grid?._pluginManager?.emitPluginEvent(e, t);
237
208
  }
238
209
  /**
239
210
  * Request a re-render of the grid.
211
+ * Uses ROWS phase - does NOT trigger processColumns hooks.
240
212
  */
241
213
  requestRender() {
242
214
  this.grid?.requestRender?.();
243
215
  }
216
+ /**
217
+ * Request a columns re-render of the grid.
218
+ * Uses COLUMNS phase - triggers processColumns hooks.
219
+ * Use this when your plugin needs to reprocess column configuration.
220
+ */
221
+ requestColumnsRender() {
222
+ this.grid?.requestColumnsRender?.();
223
+ }
244
224
  /**
245
225
  * Request a re-render and restore focus styling afterward.
246
226
  * Use this when a plugin action (like expand/collapse) triggers a render
@@ -321,7 +301,7 @@ class O {
321
301
  */
322
302
  get gridIcons() {
323
303
  const e = this.grid?.gridConfig?.icons ?? {};
324
- return { ...q, ...e };
304
+ return { ...L, ...e };
325
305
  }
326
306
  // #region Animation Helpers
327
307
  /**
@@ -344,8 +324,8 @@ class O {
344
324
  const e = this.grid?.effectiveConfig?.animation?.mode ?? "reduced-motion";
345
325
  if (e === !1 || e === "off") return !1;
346
326
  if (e === !0 || e === "on") return !0;
347
- const i = this.gridElement;
348
- return i ? getComputedStyle(i).getPropertyValue("--tbw-animation-enabled").trim() !== "0" : !0;
327
+ const t = this.gridElement;
328
+ return t ? getComputedStyle(t).getPropertyValue("--tbw-animation-enabled").trim() !== "0" : !0;
349
329
  }
350
330
  /**
351
331
  * Get the animation duration in milliseconds from CSS variable.
@@ -362,8 +342,8 @@ class O {
362
342
  get animationDuration() {
363
343
  const e = this.gridElement;
364
344
  if (e) {
365
- const i = getComputedStyle(e).getPropertyValue("--tbw-animation-duration").trim(), t = parseInt(i, 10);
366
- if (!isNaN(t)) return t;
345
+ const t = getComputedStyle(e).getPropertyValue("--tbw-animation-duration").trim(), i = parseInt(t, 10);
346
+ if (!isNaN(i)) return i;
367
347
  }
368
348
  return 200;
369
349
  }
@@ -376,8 +356,8 @@ class O {
376
356
  * @param pluginOverride - Optional plugin-level override
377
357
  * @returns The resolved icon value
378
358
  */
379
- resolveIcon(e, i) {
380
- return i !== void 0 ? i : this.gridIcons[e];
359
+ resolveIcon(e, t) {
360
+ return t !== void 0 ? t : this.gridIcons[e];
381
361
  }
382
362
  /**
383
363
  * Set an icon value on an element.
@@ -386,8 +366,8 @@ class O {
386
366
  * @param element - The element to set the icon on
387
367
  * @param icon - The icon value (string or HTMLElement)
388
368
  */
389
- setIcon(e, i) {
390
- typeof i == "string" ? e.innerHTML = i : i instanceof HTMLElement && (e.innerHTML = "", e.appendChild(i.cloneNode(!0)));
369
+ setIcon(e, t) {
370
+ typeof t == "string" ? e.innerHTML = t : t instanceof HTMLElement && (e.innerHTML = "", e.appendChild(t.cloneNode(!0)));
391
371
  }
392
372
  /**
393
373
  * Log a warning message.
@@ -397,88 +377,97 @@ class O {
397
377
  }
398
378
  // #endregion
399
379
  }
400
- const N = "@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-grid .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}}";
401
- function M(s) {
380
+ const A = "@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 .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}}";
381
+ function P(s) {
402
382
  const e = s.options;
403
383
  return e ? typeof e == "function" ? e() : e : [];
404
384
  }
405
- function H(s) {
385
+ function T(s) {
406
386
  return (e) => {
407
- const i = s.editorParams, t = document.createElement("input");
408
- t.type = "number", t.value = e.value != null ? String(e.value) : "", i?.min !== void 0 && (t.min = String(i.min)), i?.max !== void 0 && (t.max = String(i.max)), i?.step !== void 0 && (t.step = String(i.step)), i?.placeholder && (t.placeholder = i.placeholder);
409
- const r = () => e.commit(t.value === "" ? null : Number(t.value));
410
- return t.addEventListener("blur", r), t.addEventListener("keydown", (n) => {
387
+ const t = s.editorParams, i = document.createElement("input");
388
+ 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);
389
+ const r = () => e.commit(i.value === "" ? null : Number(i.value));
390
+ return i.addEventListener("blur", r), i.addEventListener("keydown", (n) => {
411
391
  n.key === "Enter" && r(), n.key === "Escape" && e.cancel();
412
- }), t;
392
+ }), i;
413
393
  };
414
394
  }
415
- function G() {
395
+ function q() {
416
396
  return (s) => {
417
397
  const e = document.createElement("input");
418
398
  return e.type = "checkbox", e.checked = !!s.value, e.addEventListener("change", () => s.commit(e.checked)), e;
419
399
  };
420
400
  }
421
- function x(s) {
401
+ function O(s) {
422
402
  return (e) => {
423
- const i = s.editorParams, t = document.createElement("input");
424
- return t.type = "date", e.value instanceof Date && (t.valueAsDate = e.value), i?.min && (t.min = i.min), i?.max && (t.max = i.max), i?.placeholder && (t.placeholder = i.placeholder), t.addEventListener("change", () => e.commit(t.valueAsDate)), t.addEventListener("keydown", (r) => {
425
- r.key === "Escape" && e.cancel();
426
- }), t;
403
+ const t = s.editorParams, i = document.createElement("input");
404
+ 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);
405
+ const r = () => {
406
+ typeof e.value == "string" ? e.commit(i.value) : e.commit(i.valueAsDate);
407
+ };
408
+ return i.addEventListener("change", r), i.addEventListener("keydown", (n) => {
409
+ n.key === "Escape" && e.cancel();
410
+ }), i;
427
411
  };
428
412
  }
429
- function $(s) {
413
+ function D(s) {
430
414
  return (e) => {
431
- const i = s.editorParams, t = document.createElement("select");
432
- if (s.multi && (t.multiple = !0), i?.includeEmpty) {
415
+ const t = s.editorParams, i = document.createElement("select");
416
+ if (s.multi && (i.multiple = !0), t?.includeEmpty) {
433
417
  const o = document.createElement("option");
434
- o.value = "", o.textContent = i.emptyLabel ?? "", t.appendChild(o);
418
+ o.value = "", o.textContent = t.emptyLabel ?? "", i.appendChild(o);
435
419
  }
436
- M(s).forEach((o) => {
420
+ P(s).forEach((o) => {
437
421
  const a = document.createElement("option");
438
- a.value = String(o.value), a.textContent = o.label, (s.multi && Array.isArray(e.value) && e.value.includes(o.value) || !s.multi && e.value === o.value) && (a.selected = !0), t.appendChild(a);
422
+ a.value = String(o.value), a.textContent = o.label, (s.multi && Array.isArray(e.value) && e.value.includes(o.value) || !s.multi && e.value === o.value) && (a.selected = !0), i.appendChild(a);
439
423
  });
440
424
  const n = () => {
441
425
  if (s.multi) {
442
- const o = Array.from(t.selectedOptions).map((a) => a.value);
426
+ const o = Array.from(i.selectedOptions).map((a) => a.value);
443
427
  e.commit(o);
444
428
  } else
445
- e.commit(t.value);
429
+ e.commit(i.value);
446
430
  };
447
- return t.addEventListener("change", n), t.addEventListener("blur", n), t.addEventListener("keydown", (o) => {
431
+ return i.addEventListener("change", n), i.addEventListener("blur", n), i.addEventListener("keydown", (o) => {
448
432
  o.key === "Escape" && e.cancel();
449
- }), t;
433
+ }), i;
450
434
  };
451
435
  }
452
- function F(s) {
436
+ function M(s) {
453
437
  return (e) => {
454
- const i = s.editorParams, t = document.createElement("input");
455
- return t.type = "text", t.value = e.value != null ? String(e.value) : "", i?.maxLength !== void 0 && (t.maxLength = i.maxLength), i?.pattern && (t.pattern = i.pattern), i?.placeholder && (t.placeholder = i.placeholder), t.addEventListener("blur", () => e.commit(t.value)), t.addEventListener("keydown", (r) => {
456
- r.key === "Enter" && e.commit(t.value), r.key === "Escape" && e.cancel();
457
- }), t;
438
+ const t = s.editorParams, i = document.createElement("input");
439
+ 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);
440
+ const r = () => {
441
+ const n = i.value;
442
+ typeof e.value == "number" && n !== "" ? e.commit(Number(n)) : e.commit(n);
443
+ };
444
+ return i.addEventListener("blur", r), i.addEventListener("keydown", (n) => {
445
+ n.key === "Enter" && r(), n.key === "Escape" && e.cancel();
446
+ }), i;
458
447
  };
459
448
  }
460
- function z(s) {
449
+ function N(s) {
461
450
  switch (s.type) {
462
451
  case "number":
463
- return H(s);
452
+ return T(s);
464
453
  case "boolean":
465
- return G();
454
+ return q();
466
455
  case "date":
467
- return x(s);
456
+ return O(s);
468
457
  case "select":
469
- return $(s);
458
+ return D(s);
470
459
  default:
471
- return F(s);
460
+ return M(s);
472
461
  }
473
462
  }
474
- const _ = 'input,select,textarea,[contenteditable="true"],[contenteditable=""],[tabindex]:not([tabindex="-1"])';
475
- function B(s, e) {
463
+ const R = 'input,select,textarea,[contenteditable="true"],[contenteditable=""],[tabindex]:not([tabindex="-1"])';
464
+ function G(s, e) {
476
465
  if (e.editor) return e.editor;
477
466
  if (e.__editorTemplate) return "template";
478
467
  if (!e.type) return;
479
- const t = s.effectiveConfig?.typeDefaults;
480
- if (t?.[e.type]?.editor)
481
- return t[e.type].editor;
468
+ const i = s.effectiveConfig?.typeDefaults;
469
+ if (i?.[e.type]?.editor)
470
+ return i[e.type].editor;
482
471
  const r = s.__frameworkAdapter;
483
472
  if (r?.getTypeDefault) {
484
473
  const n = r.getTypeDefault(e.type);
@@ -486,28 +475,28 @@ function B(s, e) {
486
475
  return n.editor;
487
476
  }
488
477
  }
489
- function v(s) {
478
+ function w(s) {
490
479
  return !(typeof s != "string" || s === "__proto__" || s === "constructor" || s === "prototype");
491
480
  }
492
- function U(s) {
481
+ function H(s) {
493
482
  const e = (s.__editingCellCount ?? 0) + 1;
494
483
  s.__editingCellCount = e, s.setAttribute("data-has-editing", "");
495
484
  }
496
- function j(s) {
485
+ function x(s) {
497
486
  s.__editingCellCount = 0, s.removeAttribute("data-has-editing");
498
487
  }
499
- function E(s, e) {
500
- return s instanceof HTMLInputElement ? s.type === "checkbox" ? s.checked : s.type === "number" ? s.value === "" ? null : Number(s.value) : s.type === "date" ? s.valueAsDate : s.value : e?.type === "number" && s.value !== "" ? Number(s.value) : s.value;
488
+ function E(s, e, t) {
489
+ return s instanceof HTMLInputElement ? s.type === "checkbox" ? s.checked : s.type === "number" ? s.value === "" ? null : Number(s.value) : s.type === "date" ? typeof t == "string" ? s.value : s.valueAsDate : typeof t == "number" ? s.value === "" ? null : Number(s.value) : s.value : e?.type === "number" && s.value !== "" || typeof t == "number" && s.value !== "" ? Number(s.value) : s.value;
501
490
  }
502
491
  function k(s) {
503
492
  }
504
- function W(s, e, i) {
505
- const t = s.querySelector("input,textarea,select");
506
- t && (t.addEventListener("blur", () => {
507
- i(E(t, e));
508
- }), t instanceof HTMLInputElement && t.type === "checkbox" ? t.addEventListener("change", () => i(t.checked)) : t instanceof HTMLSelectElement && t.addEventListener("change", () => i(E(t, e))));
493
+ function $(s, e, t, i) {
494
+ const r = s.querySelector("input,textarea,select");
495
+ r && (r.addEventListener("blur", () => {
496
+ t(E(r, e, i));
497
+ }), r instanceof HTMLInputElement && r.type === "checkbox" ? r.addEventListener("change", () => t(r.checked)) : r instanceof HTMLSelectElement && r.addEventListener("change", () => t(E(r, e, i))));
509
498
  }
510
- class K extends O {
499
+ class z extends I {
511
500
  /**
512
501
  * Plugin manifest - declares owned properties for configuration validation.
513
502
  * @internal
@@ -547,7 +536,7 @@ class K extends O {
547
536
  /** @internal */
548
537
  name = "editing";
549
538
  /** @internal */
550
- styles = N;
539
+ styles = A;
551
540
  /** @internal */
552
541
  get defaultConfig() {
553
542
  return {
@@ -558,22 +547,29 @@ class K extends O {
558
547
  /** Currently active edit row index, or -1 if not editing */
559
548
  #e = -1;
560
549
  /** Currently active edit column index, or -1 if not editing */
561
- #s = -1;
550
+ #o = -1;
562
551
  /** Snapshots of row data before editing started */
563
- #r = /* @__PURE__ */ new Map();
552
+ #s = /* @__PURE__ */ new Map();
564
553
  /** Set of row IDs that have been modified (ID-based for stability) */
565
- #t = /* @__PURE__ */ new Set();
554
+ #i = /* @__PURE__ */ new Set();
566
555
  /** Set of cells currently in edit mode: "rowIndex:colIndex" */
567
- #n = /* @__PURE__ */ new Set();
556
+ #r = /* @__PURE__ */ new Set();
568
557
  /** Flag to restore focus after next render (used when exiting edit mode) */
569
- #o = !1;
558
+ #a = !1;
559
+ /** Row index pending animation after render, or -1 if none */
560
+ #l = -1;
561
+ /**
562
+ * Invalid cell tracking: Map<rowId, Map<field, message>>
563
+ * Used for validation feedback without canceling edits.
564
+ */
565
+ #t = /* @__PURE__ */ new Map();
570
566
  // #endregion
571
567
  // #region Lifecycle
572
568
  /** @internal */
573
569
  attach(e) {
574
570
  super.attach(e);
575
- const i = this.disconnectSignal, t = e;
576
- t._activeEditRows = -1, t._rowEditSnapshots = /* @__PURE__ */ new Map(), Object.defineProperty(e, "changedRows", {
571
+ const t = this.disconnectSignal, i = e;
572
+ i._activeEditRows = -1, i._rowEditSnapshots = /* @__PURE__ */ new Map(), Object.defineProperty(e, "changedRows", {
577
573
  get: () => this.changedRows,
578
574
  configurable: !0
579
575
  }), Object.defineProperty(e, "changedRowIds", {
@@ -584,24 +580,24 @@ class K extends O {
584
580
  }, document.addEventListener(
585
581
  "keydown",
586
582
  (r) => {
587
- r.key === "Escape" && this.#e !== -1 && this.#i(this.#e, !0);
583
+ r.key === "Escape" && this.#e !== -1 && this.#n(this.#e, !0);
588
584
  },
589
- { capture: !0, signal: i }
585
+ { capture: !0, signal: t }
590
586
  ), document.addEventListener(
591
587
  "mousedown",
592
588
  (r) => {
593
589
  if (this.#e === -1) return;
594
- const n = t.findRenderedRowElement?.(this.#e);
590
+ const n = i.findRenderedRowElement?.(this.#e);
595
591
  !n || (r.composedPath && r.composedPath() || []).includes(n) || queueMicrotask(() => {
596
- this.#e !== -1 && this.#i(this.#e, !1);
592
+ this.#e !== -1 && this.#n(this.#e, !1);
597
593
  });
598
594
  },
599
- { signal: i }
595
+ { signal: t }
600
596
  );
601
597
  }
602
598
  /** @internal */
603
599
  detach() {
604
- this.#e = -1, this.#s = -1, this.#r.clear(), this.#t.clear(), this.#n.clear(), super.detach();
600
+ this.#e = -1, this.#o = -1, this.#s.clear(), this.#i.clear(), this.#r.clear(), super.detach();
605
601
  }
606
602
  /**
607
603
  * Handle plugin queries.
@@ -620,30 +616,30 @@ class K extends O {
620
616
  * @internal
621
617
  */
622
618
  onCellClick(e) {
623
- const i = this.grid, t = this.config.editOn ?? i.effectiveConfig?.editOn;
624
- if (t === !1 || t === "manual" || t !== "click" && t !== "dblclick") return !1;
619
+ const t = this.grid, i = this.config.editOn ?? t.effectiveConfig?.editOn;
620
+ if (i === !1 || i === "manual" || i !== "click" && i !== "dblclick") return !1;
625
621
  const r = e.originalEvent.type === "dblclick";
626
- if (t === "click" && r || t === "dblclick" && !r) return !1;
622
+ if (i === "click" && r || i === "dblclick" && !r) return !1;
627
623
  const { rowIndex: n } = e;
628
- return i._columns?.some((a) => a.editable) ? (e.originalEvent.stopPropagation(), this.beginBulkEdit(n), !0) : !1;
624
+ return t._columns?.some((a) => a.editable) ? (e.originalEvent.stopPropagation(), this.beginBulkEdit(n), !0) : !1;
629
625
  }
630
626
  /**
631
627
  * Handle keyboard events for edit lifecycle.
632
628
  * @internal
633
629
  */
634
630
  onKeyDown(e) {
635
- const i = this.grid;
631
+ const t = this.grid;
636
632
  if (e.key === "Escape" && this.#e !== -1)
637
- return this.#i(this.#e, !0), !0;
633
+ return this.#n(this.#e, !0), !0;
638
634
  if (e.key === " " || e.key === "Spacebar") {
639
- const t = i._focusRow, r = i._focusCol;
640
- if (t >= 0 && r >= 0) {
641
- const n = i._visibleColumns[r], o = i._rows[t];
635
+ const i = t._focusRow, r = t._focusCol;
636
+ if (i >= 0 && r >= 0) {
637
+ const n = t._visibleColumns[r], o = t._rows[i];
642
638
  if (n?.editable && n.type === "boolean" && o) {
643
639
  const a = n.field;
644
- if (v(a)) {
645
- const c = !o[a];
646
- return this.#l(t, n, c, o), e.preventDefault(), this.requestRender(), !0;
640
+ if (w(a)) {
641
+ const d = !o[a];
642
+ return this.#u(i, n, d, o), e.preventDefault(), this.requestRender(), !0;
647
643
  }
648
644
  }
649
645
  }
@@ -652,31 +648,31 @@ class K extends O {
652
648
  if (e.key === "Enter" && !e.shiftKey) {
653
649
  if (this.#e !== -1)
654
650
  return !1;
655
- const t = this.config.editOn ?? i.effectiveConfig?.editOn;
656
- if (t === !1 || t === "manual") return !1;
657
- const r = i._focusRow, n = i._focusCol;
658
- if (r >= 0 && i._columns?.some((a) => a.editable)) {
659
- const a = i._visibleColumns[n], l = i._rows[r], c = a?.field ?? "", f = c && l ? l[c] : void 0, g = this.gridElement.querySelector(`[data-row="${r}"][data-col="${n}"]`), d = new CustomEvent("cell-activate", {
651
+ const i = this.config.editOn ?? t.effectiveConfig?.editOn;
652
+ if (i === !1 || i === "manual") return !1;
653
+ const r = t._focusRow, n = t._focusCol;
654
+ if (r >= 0 && t._columns?.some((a) => a.editable)) {
655
+ const a = t._visibleColumns[n], l = t._rows[r], d = a?.field ?? "", c = d && l ? l[d] : void 0, p = this.gridElement.querySelector(`[data-row="${r}"][data-col="${n}"]`), u = new CustomEvent("cell-activate", {
660
656
  cancelable: !0,
661
657
  bubbles: !0,
662
658
  detail: {
663
659
  rowIndex: r,
664
660
  colIndex: n,
665
- field: c,
666
- value: f,
661
+ field: d,
662
+ value: c,
667
663
  row: l,
668
- cellEl: g,
664
+ cellEl: p,
669
665
  trigger: "keyboard",
670
666
  originalEvent: e
671
667
  }
672
668
  });
673
- this.gridElement.dispatchEvent(d);
674
- const h = new CustomEvent("activate-cell", {
669
+ this.gridElement.dispatchEvent(u);
670
+ const m = new CustomEvent("activate-cell", {
675
671
  cancelable: !0,
676
672
  bubbles: !0,
677
673
  detail: { row: r, col: n }
678
674
  });
679
- return this.gridElement.dispatchEvent(h), d.defaultPrevented || h.defaultPrevented ? (e.preventDefault(), !0) : (this.beginBulkEdit(r), !0);
675
+ return this.gridElement.dispatchEvent(m), u.defaultPrevented || m.defaultPrevented ? (e.preventDefault(), !0) : (this.beginBulkEdit(r), !0);
680
676
  }
681
677
  return !1;
682
678
  }
@@ -690,11 +686,11 @@ class K extends O {
690
686
  * @internal
691
687
  */
692
688
  processColumns(e) {
693
- const i = this.grid, t = i.effectiveConfig?.typeDefaults, r = i.__frameworkAdapter;
694
- return !t && !r?.getTypeDefault ? e : e.map((n) => {
689
+ const t = this.grid, i = t.effectiveConfig?.typeDefaults, r = t.__frameworkAdapter;
690
+ return !i && !r?.getTypeDefault ? e : e.map((n) => {
695
691
  if (!n.type) return n;
696
692
  let o;
697
- if (t?.[n.type]?.editorParams && (o = t[n.type].editorParams), !o && r?.getTypeDefault) {
693
+ if (i?.[n.type]?.editorParams && (o = i[n.type].editorParams), !o && r?.getTypeDefault) {
698
694
  const a = r.getTypeDefault(n.type);
699
695
  a?.editorParams && (o = a.editorParams);
700
696
  }
@@ -712,14 +708,18 @@ class K extends O {
712
708
  */
713
709
  afterRender() {
714
710
  const e = this.grid;
715
- if (this.#o && (this.#o = !1, this.#u(e)), this.#n.size !== 0)
716
- for (const i of this.#n) {
717
- const [t, r] = i.split(":"), n = parseInt(t, 10), o = parseInt(r, 10), a = e.findRenderedRowElement?.(n);
711
+ if (this.#a && (this.#a = !1, this.#g(e)), this.#l !== -1) {
712
+ const t = this.#l;
713
+ this.#l = -1, e.animateRow?.(t, "change");
714
+ }
715
+ if (this.#r.size !== 0)
716
+ for (const t of this.#r) {
717
+ const [i, r] = t.split(":"), n = parseInt(i, 10), o = parseInt(r, 10), a = e.findRenderedRowElement?.(n);
718
718
  if (!a) continue;
719
719
  const l = a.querySelector(`.cell[data-col="${o}"]`);
720
720
  if (!l || l.classList.contains("editing")) continue;
721
- const c = e._rows[n], f = e._visibleColumns[o];
722
- c && f && this.#d(c, n, f, o, l, !0);
721
+ const d = e._rows[n], c = e._visibleColumns[o];
722
+ d && c && this.#f(d, n, c, o, l, !0);
723
723
  }
724
724
  }
725
725
  /**
@@ -737,9 +737,9 @@ class K extends O {
737
737
  */
738
738
  get changedRows() {
739
739
  const e = [];
740
- for (const i of this.#t) {
741
- const t = this.grid.getRow(i);
742
- t && e.push(t);
740
+ for (const t of this.#i) {
741
+ const i = this.grid.getRow(t);
742
+ i && e.push(i);
743
743
  }
744
744
  return e;
745
745
  }
@@ -747,7 +747,7 @@ class K extends O {
747
747
  * Get IDs of all modified rows.
748
748
  */
749
749
  get changedRowIds() {
750
- return Array.from(this.#t);
750
+ return Array.from(this.#i);
751
751
  }
752
752
  /**
753
753
  * Get the currently active edit row index, or -1 if not editing.
@@ -759,7 +759,7 @@ class K extends O {
759
759
  * Get the currently active edit column index, or -1 if not editing.
760
760
  */
761
761
  get activeEditCol() {
762
- return this.#s;
762
+ return this.#o;
763
763
  }
764
764
  /**
765
765
  * Check if a specific row is currently being edited.
@@ -770,19 +770,19 @@ class K extends O {
770
770
  /**
771
771
  * Check if a specific cell is currently being edited.
772
772
  */
773
- isCellEditing(e, i) {
774
- return this.#n.has(`${e}:${i}`);
773
+ isCellEditing(e, t) {
774
+ return this.#r.has(`${e}:${t}`);
775
775
  }
776
776
  /**
777
777
  * Check if a specific row has been modified.
778
778
  * @param rowIndex - Row index to check (will be converted to ID internally)
779
779
  */
780
780
  isRowChanged(e) {
781
- const i = this.grid, t = i._rows[e];
782
- if (!t) return !1;
781
+ const t = this.grid, i = t._rows[e];
782
+ if (!i) return !1;
783
783
  try {
784
- const r = i.getRowId?.(t);
785
- return r ? this.#t.has(r) : !1;
784
+ const r = t.getRowId?.(i);
785
+ return r ? this.#i.has(r) : !1;
786
786
  } catch {
787
787
  return !1;
788
788
  }
@@ -792,16 +792,136 @@ class K extends O {
792
792
  * @param rowId - Row ID to check
793
793
  */
794
794
  isRowChangedById(e) {
795
- return this.#t.has(e);
795
+ return this.#i.has(e);
796
+ }
797
+ // #region Cell Validation
798
+ /**
799
+ * Mark a cell as invalid with an optional validation message.
800
+ * Invalid cells are marked with a `data-invalid` attribute for styling.
801
+ *
802
+ * @param rowId - The row ID (from getRowId)
803
+ * @param field - The field name
804
+ * @param message - Optional validation message (for tooltips or display)
805
+ *
806
+ * @example
807
+ * ```typescript
808
+ * // In cell-commit handler:
809
+ * grid.addEventListener('cell-commit', (e) => {
810
+ * if (e.detail.field === 'email' && !isValidEmail(e.detail.value)) {
811
+ * e.detail.setInvalid('Invalid email format');
812
+ * }
813
+ * });
814
+ *
815
+ * // Or programmatically:
816
+ * editingPlugin.setInvalid('row-123', 'email', 'Invalid email format');
817
+ * ```
818
+ */
819
+ setInvalid(e, t, i = "") {
820
+ let r = this.#t.get(e);
821
+ r || (r = /* @__PURE__ */ new Map(), this.#t.set(e, r)), r.set(t, i), this.#d(e, t, !0);
822
+ }
823
+ /**
824
+ * Clear the invalid state for a specific cell.
825
+ *
826
+ * @param rowId - The row ID (from getRowId)
827
+ * @param field - The field name
828
+ */
829
+ clearInvalid(e, t) {
830
+ const i = this.#t.get(e);
831
+ i && (i.delete(t), i.size === 0 && this.#t.delete(e)), this.#d(e, t, !1);
832
+ }
833
+ /**
834
+ * Clear all invalid cells for a specific row.
835
+ *
836
+ * @param rowId - The row ID (from getRowId)
837
+ */
838
+ clearRowInvalid(e) {
839
+ const t = this.#t.get(e);
840
+ if (t) {
841
+ const i = Array.from(t.keys());
842
+ this.#t.delete(e), i.forEach((r) => this.#d(e, r, !1));
843
+ }
844
+ }
845
+ /**
846
+ * Clear all invalid cell states across all rows.
847
+ */
848
+ clearAllInvalid() {
849
+ const e = Array.from(this.#t.entries());
850
+ this.#t.clear(), e.forEach(([t, i]) => {
851
+ i.forEach((r, n) => this.#d(t, n, !1));
852
+ });
853
+ }
854
+ /**
855
+ * Check if a specific cell is marked as invalid.
856
+ *
857
+ * @param rowId - The row ID (from getRowId)
858
+ * @param field - The field name
859
+ * @returns True if the cell is marked as invalid
860
+ */
861
+ isCellInvalid(e, t) {
862
+ return this.#t.get(e)?.has(t) ?? !1;
863
+ }
864
+ /**
865
+ * Get the validation message for an invalid cell.
866
+ *
867
+ * @param rowId - The row ID (from getRowId)
868
+ * @param field - The field name
869
+ * @returns The validation message, or undefined if cell is valid
870
+ */
871
+ getInvalidMessage(e, t) {
872
+ return this.#t.get(e)?.get(t);
873
+ }
874
+ /**
875
+ * Check if a row has any invalid cells.
876
+ *
877
+ * @param rowId - The row ID (from getRowId)
878
+ * @returns True if the row has at least one invalid cell
879
+ */
880
+ hasInvalidCells(e) {
881
+ const t = this.#t.get(e);
882
+ return t ? t.size > 0 : !1;
883
+ }
884
+ /**
885
+ * Get all invalid fields for a row.
886
+ *
887
+ * @param rowId - The row ID (from getRowId)
888
+ * @returns Map of field names to validation messages
889
+ */
890
+ getInvalidFields(e) {
891
+ return new Map(this.#t.get(e) ?? []);
796
892
  }
893
+ /**
894
+ * Sync the data-invalid attribute on a cell element.
895
+ */
896
+ #d(e, t, i) {
897
+ const r = this.grid, n = r._visibleColumns?.findIndex((c) => c.field === t);
898
+ if (n === -1 || n === void 0) return;
899
+ const a = r._rows?.findIndex((c) => {
900
+ try {
901
+ return r.getRowId?.(c) === e;
902
+ } catch {
903
+ return !1;
904
+ }
905
+ });
906
+ if (a === -1 || a === void 0) return;
907
+ const d = r.findRenderedRowElement?.(a)?.querySelector(`.cell[data-col="${n}"]`);
908
+ if (d)
909
+ if (i) {
910
+ d.setAttribute("data-invalid", "true");
911
+ const c = this.#t.get(e)?.get(t);
912
+ c && d.setAttribute("title", c);
913
+ } else
914
+ d.removeAttribute("data-invalid"), d.removeAttribute("title");
915
+ }
916
+ // #endregion
797
917
  /**
798
918
  * Reset all change tracking.
799
919
  * @param silent - If true, suppresses the `changed-rows-reset` event
800
920
  * @fires changed-rows-reset - Emitted when tracking is reset (unless silent)
801
921
  */
802
922
  resetChangedRows(e) {
803
- const i = this.changedRows, t = this.changedRowIds;
804
- this.#t.clear(), this.#a(), e || this.emit("changed-rows-reset", { rows: i, ids: t }), this.grid._rowPool?.forEach((n) => n.classList.remove("changed"));
923
+ const t = this.changedRows, i = this.changedRowIds;
924
+ this.#i.clear(), this.#c(), e || this.emit("changed-rows-reset", { rows: t, ids: i }), this.grid._rowPool?.forEach((n) => n.classList.remove("changed"));
805
925
  }
806
926
  /**
807
927
  * Programmatically begin editing a cell.
@@ -809,11 +929,11 @@ class K extends O {
809
929
  * @param field - Field name of the column to edit
810
930
  * @fires cell-commit - Emitted when the cell value is committed (on blur or Enter)
811
931
  */
812
- beginCellEdit(e, i) {
813
- const t = this.grid, r = t._visibleColumns.findIndex((l) => l.field === i);
814
- if (r === -1 || !t._visibleColumns[r]?.editable) return;
815
- const a = t.findRenderedRowElement?.(e)?.querySelector(`.cell[data-col="${r}"]`);
816
- a && this.#f(e, r, a);
932
+ beginCellEdit(e, t) {
933
+ const i = this.grid, r = i._visibleColumns.findIndex((l) => l.field === t);
934
+ if (r === -1 || !i._visibleColumns[r]?.editable) return;
935
+ const a = i.findRenderedRowElement?.(e)?.querySelector(`.cell[data-col="${r}"]`);
936
+ a && this.#p(e, r, a);
817
937
  }
818
938
  /**
819
939
  * Programmatically begin editing all editable cells in a row.
@@ -822,21 +942,21 @@ class K extends O {
822
942
  * @fires row-commit - Emitted when focus leaves the row
823
943
  */
824
944
  beginBulkEdit(e) {
825
- const i = this.grid;
826
- if ((this.config.editOn ?? i.effectiveConfig?.editOn) === !1 || !i._columns?.some((a) => a.editable)) return;
827
- const n = i.findRenderedRowElement?.(e);
945
+ const t = this.grid;
946
+ if ((this.config.editOn ?? t.effectiveConfig?.editOn) === !1 || !t._columns?.some((a) => a.editable)) return;
947
+ const n = t.findRenderedRowElement?.(e);
828
948
  if (!n) return;
829
- const o = i._rows[e];
830
- this.#c(e, o), Array.from(n.children).forEach((a, l) => {
831
- const c = i._visibleColumns[l];
832
- if (c?.editable) {
833
- const f = a;
834
- f.classList.contains("editing") || this.#d(o, e, c, l, f, !0);
949
+ const o = t._rows[e];
950
+ this.#h(e, o), Array.from(n.children).forEach((a, l) => {
951
+ const d = t._visibleColumns[l];
952
+ if (d?.editable) {
953
+ const c = a;
954
+ c.classList.contains("editing") || this.#f(o, e, d, l, c, !0);
835
955
  }
836
956
  }), setTimeout(() => {
837
- let a = n.querySelector(`.cell[data-col="${i._focusCol}"]`);
957
+ let a = n.querySelector(`.cell[data-col="${t._focusCol}"]`);
838
958
  if (a?.classList.contains("editing") || (a = n.querySelector(".cell.editing")), a?.classList.contains("editing")) {
839
- const l = a.querySelector(_);
959
+ const l = a.querySelector(R);
840
960
  try {
841
961
  l?.focus({ preventScroll: !0 });
842
962
  } catch {
@@ -849,235 +969,257 @@ class K extends O {
849
969
  * @fires row-commit - Emitted after the row edit is committed
850
970
  */
851
971
  commitActiveRowEdit() {
852
- this.#e !== -1 && this.#i(this.#e, !1);
972
+ this.#e !== -1 && this.#n(this.#e, !1);
853
973
  }
854
974
  /**
855
975
  * Cancel the currently active row edit.
856
976
  */
857
977
  cancelActiveRowEdit() {
858
- this.#e !== -1 && this.#i(this.#e, !0);
978
+ this.#e !== -1 && this.#n(this.#e, !0);
859
979
  }
860
980
  // #endregion
861
981
  // #region Internal Methods
862
982
  /**
863
983
  * Begin editing a single cell.
864
984
  */
865
- #f(e, i, t) {
866
- const r = this.grid, n = r._rows[e], o = r._visibleColumns[i];
867
- !n || !o?.editable || t.classList.contains("editing") || (this.#e !== e && this.#c(e, n), this.#s = i, this.#d(n, e, o, i, t, !1));
985
+ #p(e, t, i) {
986
+ const r = this.grid, n = r._rows[e], o = r._visibleColumns[t];
987
+ !n || !o?.editable || i.classList.contains("editing") || (this.#e !== e && this.#h(e, n), this.#o = t, this.#f(n, e, o, t, i, !1));
868
988
  }
869
989
  /**
870
990
  * Sync the internal grid state with the plugin's editing state.
871
991
  */
872
- #a() {
992
+ #c() {
873
993
  const e = this.grid;
874
- e._activeEditRows = this.#e, e._rowEditSnapshots = this.#r;
994
+ e._activeEditRows = this.#e, e._rowEditSnapshots = this.#s;
875
995
  }
876
996
  /**
877
997
  * Snapshot original row data and mark as editing.
878
998
  */
879
- #c(e, i) {
880
- this.#e !== e && (this.#r.set(e, { ...i }), this.#e = e, this.#a());
999
+ #h(e, t) {
1000
+ this.#e !== e && (this.#s.set(e, { ...t }), this.#e = e, this.#c());
881
1001
  }
882
1002
  /**
883
1003
  * Exit editing for a row.
884
1004
  */
885
- #i(e, i) {
1005
+ #n(e, t) {
886
1006
  if (this.#e !== e) return;
887
- const t = this.grid, r = this.#r.get(e), n = t._rows[e], o = t.findRenderedRowElement?.(e);
1007
+ const i = this.grid, r = this.#s.get(e), n = i._rows[e], o = i.findRenderedRowElement?.(e);
888
1008
  let a;
889
1009
  if (n)
890
1010
  try {
891
- a = t.getRowId?.(n);
1011
+ a = i.getRowId?.(n);
892
1012
  } catch {
893
1013
  }
894
- if (!i && o && n && o.querySelectorAll(".cell.editing").forEach((c) => {
895
- const f = Number(c.getAttribute("data-col"));
896
- if (isNaN(f)) return;
897
- const g = t._visibleColumns[f];
898
- if (!g) return;
899
- const d = c.querySelector("input,textarea,select");
900
- if (d) {
901
- const h = E(d, g);
902
- n[g.field] !== h && this.#l(e, g, h, n);
1014
+ if (!t && o && n && o.querySelectorAll(".cell.editing").forEach((d) => {
1015
+ const c = Number(d.getAttribute("data-col"));
1016
+ if (isNaN(c)) return;
1017
+ const p = i._visibleColumns[c];
1018
+ if (!p) return;
1019
+ const u = d.querySelector("input,textarea,select");
1020
+ if (u) {
1021
+ const m = p.field, h = n[m], g = E(u, p, h);
1022
+ h !== g && this.#u(e, p, g, n);
903
1023
  }
904
- }), i && r && n)
1024
+ }), t && r && n)
905
1025
  Object.keys(r).forEach((l) => {
906
1026
  n[l] = r[l];
907
- }), a && this.#t.delete(a);
908
- else if (!i && n) {
909
- const l = a ? this.#t.has(a) : !1;
910
- this.emit("row-commit", {
1027
+ }), a && (this.#i.delete(a), this.clearRowInvalid(a));
1028
+ else if (!t && n) {
1029
+ const l = this.#v(r, n), d = a ? this.#i.has(a) : l, c = this.emitCancelable("row-commit", {
911
1030
  rowIndex: e,
912
1031
  rowId: a ?? "",
913
1032
  row: n,
914
- changed: l,
1033
+ oldValue: r,
1034
+ newValue: n,
1035
+ changed: d,
915
1036
  changedRows: this.changedRows,
916
1037
  changedRowIds: this.changedRowIds
917
- }), l && this.isAnimationEnabled && t.animateRow?.(e, "change");
1038
+ });
1039
+ c && r ? (Object.keys(r).forEach((p) => {
1040
+ n[p] = r[p];
1041
+ }), a && (this.#i.delete(a), this.clearRowInvalid(a))) : !c && l && this.isAnimationEnabled && (this.#l = e);
918
1042
  }
919
- this.#r.delete(e), this.#e = -1, this.#s = -1, this.#a();
920
- for (const l of this.#n)
921
- l.startsWith(`${e}:`) && this.#n.delete(l);
1043
+ this.#s.delete(e), this.#e = -1, this.#o = -1, this.#c();
1044
+ for (const l of this.#r)
1045
+ l.startsWith(`${e}:`) && this.#r.delete(l);
922
1046
  o && (o.querySelectorAll(".cell.editing").forEach((l) => {
923
- l.classList.remove("editing"), j(l.parentElement);
924
- }), this.requestRender()), this.#o = !0, o || (this.#u(t), this.#o = !1);
1047
+ l.classList.remove("editing"), x(l.parentElement);
1048
+ }), this.requestRender()), this.#a = !0, o || (this.#g(i), this.#a = !1);
925
1049
  }
926
1050
  /**
927
1051
  * Commit a single cell value change.
928
1052
  * Uses ID-based change tracking for stability when rows are reordered.
929
1053
  */
930
- #l(e, i, t, r) {
931
- const n = i.field;
932
- if (!v(n)) return;
1054
+ #u(e, t, i, r) {
1055
+ const n = t.field;
1056
+ if (!w(n)) return;
933
1057
  const o = r[n];
934
- if (o === t) return;
1058
+ if (o === i) return;
935
1059
  const a = this.grid;
936
1060
  let l;
937
1061
  try {
938
1062
  l = this.grid.getRowId(r);
939
1063
  } catch {
940
1064
  }
941
- const c = l ? !this.#t.has(l) : !0, f = l ? (h) => this.grid.updateRow(l, h, "cascade") : k;
1065
+ const d = l ? !this.#i.has(l) : !0, c = l ? (g) => this.grid.updateRow(l, g, "cascade") : k;
1066
+ let p = !1;
1067
+ const u = l ? (g) => {
1068
+ p = !0, this.setInvalid(l, n, g ?? "");
1069
+ } : () => {
1070
+ };
942
1071
  if (this.emitCancelable("cell-commit", {
943
1072
  row: r,
944
1073
  rowId: l ?? "",
945
1074
  field: n,
946
1075
  oldValue: o,
947
- value: t,
1076
+ value: i,
948
1077
  rowIndex: e,
949
1078
  changedRows: this.changedRows,
950
1079
  changedRowIds: this.changedRowIds,
951
- firstTimeForRow: c,
952
- updateRow: f
1080
+ firstTimeForRow: d,
1081
+ updateRow: c,
1082
+ setInvalid: u
953
1083
  })) return;
954
- r[n] = t, l && this.#t.add(l), this.#a(), this.emitPluginEvent("cell-edit-committed", {
1084
+ l && !p && this.isCellInvalid(l, n) && this.clearInvalid(l, n), r[n] = i, l && this.#i.add(l), this.#c(), this.emitPluginEvent("cell-edit-committed", {
955
1085
  rowIndex: e,
956
1086
  field: n,
957
1087
  oldValue: o,
958
- newValue: t
1088
+ newValue: i
959
1089
  });
960
- const d = a.findRenderedRowElement?.(e);
961
- d && (d.classList.add("changed"), D(d, "change"));
1090
+ const h = a.findRenderedRowElement?.(e);
1091
+ h && h.classList.add("changed");
962
1092
  }
963
1093
  /**
964
1094
  * Inject an editor into a cell.
965
1095
  */
966
- #d(e, i, t, r, n, o) {
967
- if (!t.editable || n.classList.contains("editing")) return;
1096
+ #f(e, t, i, r, n, o) {
1097
+ if (!i.editable || n.classList.contains("editing")) return;
968
1098
  let a;
969
1099
  try {
970
1100
  a = this.grid.getRowId(e);
971
1101
  } catch {
972
1102
  }
973
- const l = a ? (u) => this.grid.updateRow(a, u, "cascade") : k, c = v(t.field) ? e[t.field] : void 0;
974
- n.classList.add("editing"), this.#n.add(`${i}:${r}`);
975
- const f = n.parentElement;
976
- f && U(f);
977
- let g = !1;
978
- const d = (u) => {
979
- g || this.#e === -1 || this.#l(i, t, u, e);
980
- }, h = () => {
981
- g = !0, v(t.field) && (e[t.field] = c);
982
- }, p = document.createElement("div");
983
- p.className = "tbw-editor-host", n.innerHTML = "", n.appendChild(p), p.addEventListener("keydown", (u) => {
984
- u.key === "Enter" && (u.stopPropagation(), u.preventDefault(), g = !0, this.#i(i, !1)), u.key === "Escape" && (u.stopPropagation(), u.preventDefault(), h(), this.#i(i, !0));
1103
+ const l = a ? (f) => this.grid.updateRow(a, f, "cascade") : k, d = w(i.field) ? e[i.field] : void 0;
1104
+ n.classList.add("editing"), this.#r.add(`${t}:${r}`);
1105
+ const c = n.parentElement;
1106
+ c && H(c);
1107
+ let p = !1;
1108
+ const u = (f) => {
1109
+ p || this.#e === -1 || this.#u(t, i, f, e);
1110
+ }, m = () => {
1111
+ p = !0, w(i.field) && (e[i.field] = d);
1112
+ }, h = document.createElement("div");
1113
+ h.className = "tbw-editor-host", n.innerHTML = "", n.appendChild(h), h.addEventListener("keydown", (f) => {
1114
+ f.key === "Enter" && (f.stopPropagation(), f.preventDefault(), p = !0, this.#n(t, !1)), f.key === "Escape" && (f.stopPropagation(), f.preventDefault(), m(), this.#n(t, !0));
985
1115
  });
986
- const m = t, y = m.__editorTemplate, b = B(this.grid, m) ?? z(t), C = c;
987
- if (b === "template" && y)
988
- this.#h(p, m, e, c, d, h, o, i);
989
- else if (typeof b == "string") {
990
- const u = document.createElement(b);
991
- u.value = C, u.addEventListener("change", () => d(u.value)), p.appendChild(u), o || queueMicrotask(() => {
992
- p.querySelector(_)?.focus({ preventScroll: !0 });
1116
+ const g = i, y = g.__editorTemplate, v = G(this.grid, g) ?? N(i), C = d;
1117
+ if (v === "template" && y)
1118
+ this.#m(h, g, e, d, u, m, o, t);
1119
+ else if (typeof v == "string") {
1120
+ const f = document.createElement(v);
1121
+ f.value = C, f.addEventListener("change", () => u(f.value)), h.appendChild(f), o || queueMicrotask(() => {
1122
+ h.querySelector(R)?.focus({ preventScroll: !0 });
993
1123
  });
994
- } else if (typeof b == "function") {
995
- const u = {
1124
+ } else if (typeof v == "function") {
1125
+ const f = {
996
1126
  row: e,
997
1127
  rowId: a ?? "",
998
1128
  value: C,
999
- field: t.field,
1000
- column: t,
1001
- commit: d,
1002
- cancel: h,
1129
+ field: i.field,
1130
+ column: i,
1131
+ commit: u,
1132
+ cancel: m,
1003
1133
  updateRow: l
1004
- }, w = b(u);
1005
- typeof w == "string" ? (p.innerHTML = w, W(p, t, d)) : w instanceof Node && p.appendChild(w), o || queueMicrotask(() => {
1006
- p.querySelector(_)?.focus({ preventScroll: !0 });
1134
+ }, b = v(f);
1135
+ typeof b == "string" ? (h.innerHTML = b, $(h, i, u, d)) : b instanceof Node && h.appendChild(b), o || queueMicrotask(() => {
1136
+ h.querySelector(R)?.focus({ preventScroll: !0 });
1007
1137
  });
1008
- } else if (b && typeof b == "object") {
1009
- const u = document.createElement("div");
1010
- u.setAttribute("data-external-editor", ""), u.setAttribute("data-field", t.field), p.appendChild(u);
1011
- const w = {
1138
+ } else if (v && typeof v == "object") {
1139
+ const f = document.createElement("div");
1140
+ f.setAttribute("data-external-editor", ""), f.setAttribute("data-field", i.field), h.appendChild(f);
1141
+ const b = {
1012
1142
  row: e,
1013
1143
  rowId: a ?? "",
1014
1144
  value: C,
1015
- field: t.field,
1016
- column: t,
1017
- commit: d,
1018
- cancel: h,
1145
+ field: i.field,
1146
+ column: i,
1147
+ commit: u,
1148
+ cancel: m,
1019
1149
  updateRow: l
1020
1150
  };
1021
- if (b.mount)
1151
+ if (v.mount)
1022
1152
  try {
1023
- b.mount({ placeholder: u, context: w, spec: b });
1024
- } catch (S) {
1025
- console.warn(`[tbw-grid] External editor mount error for column '${t.field}':`, S);
1153
+ v.mount({ placeholder: f, context: b, spec: v });
1154
+ } catch (_) {
1155
+ console.warn(`[tbw-grid] External editor mount error for column '${i.field}':`, _);
1026
1156
  }
1027
1157
  else
1028
1158
  this.grid.dispatchEvent(
1029
- new CustomEvent("mount-external-editor", { detail: { placeholder: u, spec: b, context: w } })
1159
+ new CustomEvent("mount-external-editor", { detail: { placeholder: f, spec: v, context: b } })
1030
1160
  );
1031
1161
  }
1032
1162
  }
1033
1163
  /**
1034
1164
  * Render a template-based editor.
1035
1165
  */
1036
- #h(e, i, t, r, n, o, a, l) {
1037
- const c = i.__editorTemplate;
1038
- if (!c) return;
1039
- const f = c.cloneNode(!0), g = i.__compiledEditor;
1040
- g ? f.innerHTML = g({
1041
- row: t,
1166
+ #m(e, t, i, r, n, o, a, l) {
1167
+ const d = t.__editorTemplate;
1168
+ if (!d) return;
1169
+ const c = d.cloneNode(!0), p = t.__compiledEditor;
1170
+ p ? c.innerHTML = p({
1171
+ row: i,
1042
1172
  value: r,
1043
- field: i.field,
1044
- column: i,
1173
+ field: t.field,
1174
+ column: t,
1045
1175
  commit: n,
1046
1176
  cancel: o
1047
- }) : f.querySelectorAll("*").forEach((h) => {
1048
- h.childNodes.length === 1 && h.firstChild?.nodeType === Node.TEXT_NODE && (h.textContent = h.textContent?.replace(/{{\s*value\s*}}/g, r == null ? "" : String(r)).replace(/{{\s*row\.([a-zA-Z0-9_]+)\s*}}/g, (p, m) => {
1049
- if (!v(m)) return "";
1050
- const y = t[m];
1177
+ }) : c.querySelectorAll("*").forEach((m) => {
1178
+ m.childNodes.length === 1 && m.firstChild?.nodeType === Node.TEXT_NODE && (m.textContent = m.textContent?.replace(/{{\s*value\s*}}/g, r == null ? "" : String(r)).replace(/{{\s*row\.([a-zA-Z0-9_]+)\s*}}/g, (h, g) => {
1179
+ if (!w(g)) return "";
1180
+ const y = i[g];
1051
1181
  return y == null ? "" : String(y);
1052
1182
  }) || "");
1053
1183
  });
1054
- const d = f.querySelector(
1184
+ const u = c.querySelector(
1055
1185
  "input,textarea,select"
1056
1186
  );
1057
- if (d) {
1058
- d instanceof HTMLInputElement && d.type === "checkbox" ? d.checked = !!r : d.value = String(r ?? "");
1059
- let h = !1;
1060
- d.addEventListener("blur", () => {
1061
- h || n(E(d, i));
1062
- }), d.addEventListener("keydown", (p) => {
1063
- const m = p;
1064
- m.key === "Enter" && (m.stopPropagation(), m.preventDefault(), h = !0, n(E(d, i)), this.#i(l, !1)), m.key === "Escape" && (m.stopPropagation(), m.preventDefault(), o(), this.#i(l, !0));
1065
- }), d instanceof HTMLInputElement && d.type === "checkbox" && d.addEventListener("change", () => n(d.checked)), a || setTimeout(() => d.focus({ preventScroll: !0 }), 0);
1187
+ if (u) {
1188
+ u instanceof HTMLInputElement && u.type === "checkbox" ? u.checked = !!r : u.value = String(r ?? "");
1189
+ let m = !1;
1190
+ u.addEventListener("blur", () => {
1191
+ m || n(E(u, t, r));
1192
+ }), u.addEventListener("keydown", (h) => {
1193
+ const g = h;
1194
+ g.key === "Enter" && (g.stopPropagation(), g.preventDefault(), m = !0, n(E(u, t, r)), this.#n(l, !1)), g.key === "Escape" && (g.stopPropagation(), g.preventDefault(), o(), this.#n(l, !0));
1195
+ }), u instanceof HTMLInputElement && u.type === "checkbox" && u.addEventListener("change", () => n(u.checked)), a || setTimeout(() => u.focus({ preventScroll: !0 }), 0);
1066
1196
  }
1067
- e.appendChild(f);
1197
+ e.appendChild(c);
1198
+ }
1199
+ /**
1200
+ * Compare snapshot vs current row to detect if any values changed during this edit session.
1201
+ * Uses shallow comparison of all properties.
1202
+ */
1203
+ #v(e, t) {
1204
+ if (!e) return !1;
1205
+ const i = e, r = t, n = /* @__PURE__ */ new Set([...Object.keys(i), ...Object.keys(r)]);
1206
+ for (const o of n)
1207
+ if (i[o] !== r[o])
1208
+ return !0;
1209
+ return !1;
1068
1210
  }
1069
1211
  /**
1070
1212
  * Restore focus to cell after exiting edit mode.
1071
1213
  */
1072
- #u(e) {
1214
+ #g(e) {
1073
1215
  queueMicrotask(() => {
1074
1216
  try {
1075
- const i = e._focusRow, t = e._focusCol, r = e.findRenderedRowElement?.(i);
1217
+ const t = e._focusRow, i = e._focusCol, r = e.findRenderedRowElement?.(t);
1076
1218
  if (r) {
1077
1219
  Array.from(e._bodyEl.querySelectorAll(".cell-focus")).forEach(
1078
1220
  (o) => o.classList.remove("cell-focus")
1079
1221
  );
1080
- const n = r.querySelector(`.cell[data-row="${i}"][data-col="${t}"]`);
1222
+ const n = r.querySelector(`.cell[data-row="${t}"][data-col="${i}"]`);
1081
1223
  n && (n.classList.add("cell-focus"), n.setAttribute("aria-selected", "true"), n.hasAttribute("tabindex") || n.setAttribute("tabindex", "-1"), n.focus({ preventScroll: !0 }));
1082
1224
  }
1083
1225
  } catch {
@@ -1087,7 +1229,7 @@ class K extends O {
1087
1229
  // #endregion
1088
1230
  }
1089
1231
  export {
1090
- K as EditingPlugin,
1091
- z as defaultEditorFor
1232
+ z as EditingPlugin,
1233
+ N as defaultEditorFor
1092
1234
  };
1093
1235
  //# sourceMappingURL=index.js.map