@toolbox-web/grid 1.9.2 → 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 (50) hide show
  1. package/all.js +489 -402
  2. package/all.js.map +1 -1
  3. package/index.js +11 -6
  4. package/index.js.map +1 -1
  5. package/lib/core/grid.d.ts +31 -1
  6. package/lib/core/grid.d.ts.map +1 -1
  7. package/lib/core/types.d.ts +0 -64
  8. package/lib/core/types.d.ts.map +1 -1
  9. package/lib/plugins/clipboard/index.js.map +1 -1
  10. package/lib/plugins/column-virtualization/index.js.map +1 -1
  11. package/lib/plugins/context-menu/index.js.map +1 -1
  12. package/lib/plugins/editing/EditingPlugin.d.ts +69 -0
  13. package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
  14. package/lib/plugins/editing/editors.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 +426 -293
  18. package/lib/plugins/editing/index.js.map +1 -1
  19. package/lib/plugins/editing/types.d.ts +117 -1
  20. package/lib/plugins/editing/types.d.ts.map +1 -1
  21. package/lib/plugins/export/index.js.map +1 -1
  22. package/lib/plugins/filtering/index.js.map +1 -1
  23. package/lib/plugins/grouping-columns/index.js.map +1 -1
  24. package/lib/plugins/grouping-rows/index.js.map +1 -1
  25. package/lib/plugins/master-detail/index.js.map +1 -1
  26. package/lib/plugins/multi-sort/index.js.map +1 -1
  27. package/lib/plugins/pinned-columns/index.js.map +1 -1
  28. package/lib/plugins/pinned-rows/index.js +7 -7
  29. package/lib/plugins/pinned-rows/index.js.map +1 -1
  30. package/lib/plugins/pivot/index.js.map +1 -1
  31. package/lib/plugins/print/index.js.map +1 -1
  32. package/lib/plugins/reorder/index.js.map +1 -1
  33. package/lib/plugins/responsive/index.js.map +1 -1
  34. package/lib/plugins/row-reorder/index.js.map +1 -1
  35. package/lib/plugins/selection/index.js.map +1 -1
  36. package/lib/plugins/server-side/index.js.map +1 -1
  37. package/lib/plugins/tree/index.js.map +1 -1
  38. package/lib/plugins/undo-redo/index.js.map +1 -1
  39. package/lib/plugins/visibility/index.js.map +1 -1
  40. package/package.json +1 -1
  41. package/public.d.ts +1 -1
  42. package/public.d.ts.map +1 -1
  43. package/umd/grid.all.umd.js +21 -21
  44. package/umd/grid.all.umd.js.map +1 -1
  45. package/umd/grid.umd.js +2 -2
  46. package/umd/grid.umd.js.map +1 -1
  47. package/umd/plugins/editing.umd.js +1 -1
  48. package/umd/plugins/editing.umd.js.map +1 -1
  49. package/umd/plugins/pinned-rows.umd.js +1 -1
  50. 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,8 +203,8 @@ 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.
@@ -330,7 +301,7 @@ class O {
330
301
  */
331
302
  get gridIcons() {
332
303
  const e = this.grid?.gridConfig?.icons ?? {};
333
- return { ...q, ...e };
304
+ return { ...L, ...e };
334
305
  }
335
306
  // #region Animation Helpers
336
307
  /**
@@ -353,8 +324,8 @@ class O {
353
324
  const e = this.grid?.effectiveConfig?.animation?.mode ?? "reduced-motion";
354
325
  if (e === !1 || e === "off") return !1;
355
326
  if (e === !0 || e === "on") return !0;
356
- const i = this.gridElement;
357
- 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;
358
329
  }
359
330
  /**
360
331
  * Get the animation duration in milliseconds from CSS variable.
@@ -371,8 +342,8 @@ class O {
371
342
  get animationDuration() {
372
343
  const e = this.gridElement;
373
344
  if (e) {
374
- const i = getComputedStyle(e).getPropertyValue("--tbw-animation-duration").trim(), t = parseInt(i, 10);
375
- 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;
376
347
  }
377
348
  return 200;
378
349
  }
@@ -385,8 +356,8 @@ class O {
385
356
  * @param pluginOverride - Optional plugin-level override
386
357
  * @returns The resolved icon value
387
358
  */
388
- resolveIcon(e, i) {
389
- return i !== void 0 ? i : this.gridIcons[e];
359
+ resolveIcon(e, t) {
360
+ return t !== void 0 ? t : this.gridIcons[e];
390
361
  }
391
362
  /**
392
363
  * Set an icon value on an element.
@@ -395,8 +366,8 @@ class O {
395
366
  * @param element - The element to set the icon on
396
367
  * @param icon - The icon value (string or HTMLElement)
397
368
  */
398
- setIcon(e, i) {
399
- 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)));
400
371
  }
401
372
  /**
402
373
  * Log a warning message.
@@ -406,88 +377,97 @@ class O {
406
377
  }
407
378
  // #endregion
408
379
  }
409
- 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}}";
410
- 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) {
411
382
  const e = s.options;
412
383
  return e ? typeof e == "function" ? e() : e : [];
413
384
  }
414
- function H(s) {
385
+ function T(s) {
415
386
  return (e) => {
416
- const i = s.editorParams, t = document.createElement("input");
417
- 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);
418
- const r = () => e.commit(t.value === "" ? null : Number(t.value));
419
- 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) => {
420
391
  n.key === "Enter" && r(), n.key === "Escape" && e.cancel();
421
- }), t;
392
+ }), i;
422
393
  };
423
394
  }
424
- function G() {
395
+ function q() {
425
396
  return (s) => {
426
397
  const e = document.createElement("input");
427
398
  return e.type = "checkbox", e.checked = !!s.value, e.addEventListener("change", () => s.commit(e.checked)), e;
428
399
  };
429
400
  }
430
- function x(s) {
401
+ function O(s) {
431
402
  return (e) => {
432
- const i = s.editorParams, t = document.createElement("input");
433
- 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) => {
434
- r.key === "Escape" && e.cancel();
435
- }), 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;
436
411
  };
437
412
  }
438
- function $(s) {
413
+ function D(s) {
439
414
  return (e) => {
440
- const i = s.editorParams, t = document.createElement("select");
441
- 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) {
442
417
  const o = document.createElement("option");
443
- o.value = "", o.textContent = i.emptyLabel ?? "", t.appendChild(o);
418
+ o.value = "", o.textContent = t.emptyLabel ?? "", i.appendChild(o);
444
419
  }
445
- M(s).forEach((o) => {
420
+ P(s).forEach((o) => {
446
421
  const a = document.createElement("option");
447
- 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);
448
423
  });
449
424
  const n = () => {
450
425
  if (s.multi) {
451
- const o = Array.from(t.selectedOptions).map((a) => a.value);
426
+ const o = Array.from(i.selectedOptions).map((a) => a.value);
452
427
  e.commit(o);
453
428
  } else
454
- e.commit(t.value);
429
+ e.commit(i.value);
455
430
  };
456
- 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) => {
457
432
  o.key === "Escape" && e.cancel();
458
- }), t;
433
+ }), i;
459
434
  };
460
435
  }
461
- function F(s) {
436
+ function M(s) {
462
437
  return (e) => {
463
- const i = s.editorParams, t = document.createElement("input");
464
- 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) => {
465
- r.key === "Enter" && e.commit(t.value), r.key === "Escape" && e.cancel();
466
- }), 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;
467
447
  };
468
448
  }
469
- function z(s) {
449
+ function N(s) {
470
450
  switch (s.type) {
471
451
  case "number":
472
- return H(s);
452
+ return T(s);
473
453
  case "boolean":
474
- return G();
454
+ return q();
475
455
  case "date":
476
- return x(s);
456
+ return O(s);
477
457
  case "select":
478
- return $(s);
458
+ return D(s);
479
459
  default:
480
- return F(s);
460
+ return M(s);
481
461
  }
482
462
  }
483
- const _ = 'input,select,textarea,[contenteditable="true"],[contenteditable=""],[tabindex]:not([tabindex="-1"])';
484
- function B(s, e) {
463
+ const R = 'input,select,textarea,[contenteditable="true"],[contenteditable=""],[tabindex]:not([tabindex="-1"])';
464
+ function G(s, e) {
485
465
  if (e.editor) return e.editor;
486
466
  if (e.__editorTemplate) return "template";
487
467
  if (!e.type) return;
488
- const t = s.effectiveConfig?.typeDefaults;
489
- if (t?.[e.type]?.editor)
490
- return t[e.type].editor;
468
+ const i = s.effectiveConfig?.typeDefaults;
469
+ if (i?.[e.type]?.editor)
470
+ return i[e.type].editor;
491
471
  const r = s.__frameworkAdapter;
492
472
  if (r?.getTypeDefault) {
493
473
  const n = r.getTypeDefault(e.type);
@@ -495,28 +475,28 @@ function B(s, e) {
495
475
  return n.editor;
496
476
  }
497
477
  }
498
- function v(s) {
478
+ function w(s) {
499
479
  return !(typeof s != "string" || s === "__proto__" || s === "constructor" || s === "prototype");
500
480
  }
501
- function U(s) {
481
+ function H(s) {
502
482
  const e = (s.__editingCellCount ?? 0) + 1;
503
483
  s.__editingCellCount = e, s.setAttribute("data-has-editing", "");
504
484
  }
505
- function j(s) {
485
+ function x(s) {
506
486
  s.__editingCellCount = 0, s.removeAttribute("data-has-editing");
507
487
  }
508
- function E(s, e) {
509
- 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;
510
490
  }
511
491
  function k(s) {
512
492
  }
513
- function W(s, e, i) {
514
- const t = s.querySelector("input,textarea,select");
515
- t && (t.addEventListener("blur", () => {
516
- i(E(t, e));
517
- }), 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))));
518
498
  }
519
- class K extends O {
499
+ class z extends I {
520
500
  /**
521
501
  * Plugin manifest - declares owned properties for configuration validation.
522
502
  * @internal
@@ -556,7 +536,7 @@ class K extends O {
556
536
  /** @internal */
557
537
  name = "editing";
558
538
  /** @internal */
559
- styles = N;
539
+ styles = A;
560
540
  /** @internal */
561
541
  get defaultConfig() {
562
542
  return {
@@ -567,22 +547,29 @@ class K extends O {
567
547
  /** Currently active edit row index, or -1 if not editing */
568
548
  #e = -1;
569
549
  /** Currently active edit column index, or -1 if not editing */
570
- #s = -1;
550
+ #o = -1;
571
551
  /** Snapshots of row data before editing started */
572
- #r = /* @__PURE__ */ new Map();
552
+ #s = /* @__PURE__ */ new Map();
573
553
  /** Set of row IDs that have been modified (ID-based for stability) */
574
- #t = /* @__PURE__ */ new Set();
554
+ #i = /* @__PURE__ */ new Set();
575
555
  /** Set of cells currently in edit mode: "rowIndex:colIndex" */
576
- #n = /* @__PURE__ */ new Set();
556
+ #r = /* @__PURE__ */ new Set();
577
557
  /** Flag to restore focus after next render (used when exiting edit mode) */
578
- #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();
579
566
  // #endregion
580
567
  // #region Lifecycle
581
568
  /** @internal */
582
569
  attach(e) {
583
570
  super.attach(e);
584
- const i = this.disconnectSignal, t = e;
585
- 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", {
586
573
  get: () => this.changedRows,
587
574
  configurable: !0
588
575
  }), Object.defineProperty(e, "changedRowIds", {
@@ -593,24 +580,24 @@ class K extends O {
593
580
  }, document.addEventListener(
594
581
  "keydown",
595
582
  (r) => {
596
- r.key === "Escape" && this.#e !== -1 && this.#i(this.#e, !0);
583
+ r.key === "Escape" && this.#e !== -1 && this.#n(this.#e, !0);
597
584
  },
598
- { capture: !0, signal: i }
585
+ { capture: !0, signal: t }
599
586
  ), document.addEventListener(
600
587
  "mousedown",
601
588
  (r) => {
602
589
  if (this.#e === -1) return;
603
- const n = t.findRenderedRowElement?.(this.#e);
590
+ const n = i.findRenderedRowElement?.(this.#e);
604
591
  !n || (r.composedPath && r.composedPath() || []).includes(n) || queueMicrotask(() => {
605
- this.#e !== -1 && this.#i(this.#e, !1);
592
+ this.#e !== -1 && this.#n(this.#e, !1);
606
593
  });
607
594
  },
608
- { signal: i }
595
+ { signal: t }
609
596
  );
610
597
  }
611
598
  /** @internal */
612
599
  detach() {
613
- 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();
614
601
  }
615
602
  /**
616
603
  * Handle plugin queries.
@@ -629,30 +616,30 @@ class K extends O {
629
616
  * @internal
630
617
  */
631
618
  onCellClick(e) {
632
- const i = this.grid, t = this.config.editOn ?? i.effectiveConfig?.editOn;
633
- 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;
634
621
  const r = e.originalEvent.type === "dblclick";
635
- if (t === "click" && r || t === "dblclick" && !r) return !1;
622
+ if (i === "click" && r || i === "dblclick" && !r) return !1;
636
623
  const { rowIndex: n } = e;
637
- 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;
638
625
  }
639
626
  /**
640
627
  * Handle keyboard events for edit lifecycle.
641
628
  * @internal
642
629
  */
643
630
  onKeyDown(e) {
644
- const i = this.grid;
631
+ const t = this.grid;
645
632
  if (e.key === "Escape" && this.#e !== -1)
646
- return this.#i(this.#e, !0), !0;
633
+ return this.#n(this.#e, !0), !0;
647
634
  if (e.key === " " || e.key === "Spacebar") {
648
- const t = i._focusRow, r = i._focusCol;
649
- if (t >= 0 && r >= 0) {
650
- 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];
651
638
  if (n?.editable && n.type === "boolean" && o) {
652
639
  const a = n.field;
653
- if (v(a)) {
654
- const c = !o[a];
655
- 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;
656
643
  }
657
644
  }
658
645
  }
@@ -661,31 +648,31 @@ class K extends O {
661
648
  if (e.key === "Enter" && !e.shiftKey) {
662
649
  if (this.#e !== -1)
663
650
  return !1;
664
- const t = this.config.editOn ?? i.effectiveConfig?.editOn;
665
- if (t === !1 || t === "manual") return !1;
666
- const r = i._focusRow, n = i._focusCol;
667
- if (r >= 0 && i._columns?.some((a) => a.editable)) {
668
- 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", {
669
656
  cancelable: !0,
670
657
  bubbles: !0,
671
658
  detail: {
672
659
  rowIndex: r,
673
660
  colIndex: n,
674
- field: c,
675
- value: f,
661
+ field: d,
662
+ value: c,
676
663
  row: l,
677
- cellEl: g,
664
+ cellEl: p,
678
665
  trigger: "keyboard",
679
666
  originalEvent: e
680
667
  }
681
668
  });
682
- this.gridElement.dispatchEvent(d);
683
- const h = new CustomEvent("activate-cell", {
669
+ this.gridElement.dispatchEvent(u);
670
+ const m = new CustomEvent("activate-cell", {
684
671
  cancelable: !0,
685
672
  bubbles: !0,
686
673
  detail: { row: r, col: n }
687
674
  });
688
- 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);
689
676
  }
690
677
  return !1;
691
678
  }
@@ -699,11 +686,11 @@ class K extends O {
699
686
  * @internal
700
687
  */
701
688
  processColumns(e) {
702
- const i = this.grid, t = i.effectiveConfig?.typeDefaults, r = i.__frameworkAdapter;
703
- 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) => {
704
691
  if (!n.type) return n;
705
692
  let o;
706
- 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) {
707
694
  const a = r.getTypeDefault(n.type);
708
695
  a?.editorParams && (o = a.editorParams);
709
696
  }
@@ -721,14 +708,18 @@ class K extends O {
721
708
  */
722
709
  afterRender() {
723
710
  const e = this.grid;
724
- if (this.#o && (this.#o = !1, this.#u(e)), this.#n.size !== 0)
725
- for (const i of this.#n) {
726
- 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);
727
718
  if (!a) continue;
728
719
  const l = a.querySelector(`.cell[data-col="${o}"]`);
729
720
  if (!l || l.classList.contains("editing")) continue;
730
- const c = e._rows[n], f = e._visibleColumns[o];
731
- 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);
732
723
  }
733
724
  }
734
725
  /**
@@ -746,9 +737,9 @@ class K extends O {
746
737
  */
747
738
  get changedRows() {
748
739
  const e = [];
749
- for (const i of this.#t) {
750
- const t = this.grid.getRow(i);
751
- t && e.push(t);
740
+ for (const t of this.#i) {
741
+ const i = this.grid.getRow(t);
742
+ i && e.push(i);
752
743
  }
753
744
  return e;
754
745
  }
@@ -756,7 +747,7 @@ class K extends O {
756
747
  * Get IDs of all modified rows.
757
748
  */
758
749
  get changedRowIds() {
759
- return Array.from(this.#t);
750
+ return Array.from(this.#i);
760
751
  }
761
752
  /**
762
753
  * Get the currently active edit row index, or -1 if not editing.
@@ -768,7 +759,7 @@ class K extends O {
768
759
  * Get the currently active edit column index, or -1 if not editing.
769
760
  */
770
761
  get activeEditCol() {
771
- return this.#s;
762
+ return this.#o;
772
763
  }
773
764
  /**
774
765
  * Check if a specific row is currently being edited.
@@ -779,19 +770,19 @@ class K extends O {
779
770
  /**
780
771
  * Check if a specific cell is currently being edited.
781
772
  */
782
- isCellEditing(e, i) {
783
- return this.#n.has(`${e}:${i}`);
773
+ isCellEditing(e, t) {
774
+ return this.#r.has(`${e}:${t}`);
784
775
  }
785
776
  /**
786
777
  * Check if a specific row has been modified.
787
778
  * @param rowIndex - Row index to check (will be converted to ID internally)
788
779
  */
789
780
  isRowChanged(e) {
790
- const i = this.grid, t = i._rows[e];
791
- if (!t) return !1;
781
+ const t = this.grid, i = t._rows[e];
782
+ if (!i) return !1;
792
783
  try {
793
- const r = i.getRowId?.(t);
794
- return r ? this.#t.has(r) : !1;
784
+ const r = t.getRowId?.(i);
785
+ return r ? this.#i.has(r) : !1;
795
786
  } catch {
796
787
  return !1;
797
788
  }
@@ -801,16 +792,136 @@ class K extends O {
801
792
  * @param rowId - Row ID to check
802
793
  */
803
794
  isRowChangedById(e) {
804
- 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
+ }
805
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) ?? []);
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
806
917
  /**
807
918
  * Reset all change tracking.
808
919
  * @param silent - If true, suppresses the `changed-rows-reset` event
809
920
  * @fires changed-rows-reset - Emitted when tracking is reset (unless silent)
810
921
  */
811
922
  resetChangedRows(e) {
812
- const i = this.changedRows, t = this.changedRowIds;
813
- 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"));
814
925
  }
815
926
  /**
816
927
  * Programmatically begin editing a cell.
@@ -818,11 +929,11 @@ class K extends O {
818
929
  * @param field - Field name of the column to edit
819
930
  * @fires cell-commit - Emitted when the cell value is committed (on blur or Enter)
820
931
  */
821
- beginCellEdit(e, i) {
822
- const t = this.grid, r = t._visibleColumns.findIndex((l) => l.field === i);
823
- if (r === -1 || !t._visibleColumns[r]?.editable) return;
824
- const a = t.findRenderedRowElement?.(e)?.querySelector(`.cell[data-col="${r}"]`);
825
- 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);
826
937
  }
827
938
  /**
828
939
  * Programmatically begin editing all editable cells in a row.
@@ -831,21 +942,21 @@ class K extends O {
831
942
  * @fires row-commit - Emitted when focus leaves the row
832
943
  */
833
944
  beginBulkEdit(e) {
834
- const i = this.grid;
835
- if ((this.config.editOn ?? i.effectiveConfig?.editOn) === !1 || !i._columns?.some((a) => a.editable)) return;
836
- 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);
837
948
  if (!n) return;
838
- const o = i._rows[e];
839
- this.#c(e, o), Array.from(n.children).forEach((a, l) => {
840
- const c = i._visibleColumns[l];
841
- if (c?.editable) {
842
- const f = a;
843
- 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);
844
955
  }
845
956
  }), setTimeout(() => {
846
- let a = n.querySelector(`.cell[data-col="${i._focusCol}"]`);
957
+ let a = n.querySelector(`.cell[data-col="${t._focusCol}"]`);
847
958
  if (a?.classList.contains("editing") || (a = n.querySelector(".cell.editing")), a?.classList.contains("editing")) {
848
- const l = a.querySelector(_);
959
+ const l = a.querySelector(R);
849
960
  try {
850
961
  l?.focus({ preventScroll: !0 });
851
962
  } catch {
@@ -858,235 +969,257 @@ class K extends O {
858
969
  * @fires row-commit - Emitted after the row edit is committed
859
970
  */
860
971
  commitActiveRowEdit() {
861
- this.#e !== -1 && this.#i(this.#e, !1);
972
+ this.#e !== -1 && this.#n(this.#e, !1);
862
973
  }
863
974
  /**
864
975
  * Cancel the currently active row edit.
865
976
  */
866
977
  cancelActiveRowEdit() {
867
- this.#e !== -1 && this.#i(this.#e, !0);
978
+ this.#e !== -1 && this.#n(this.#e, !0);
868
979
  }
869
980
  // #endregion
870
981
  // #region Internal Methods
871
982
  /**
872
983
  * Begin editing a single cell.
873
984
  */
874
- #f(e, i, t) {
875
- const r = this.grid, n = r._rows[e], o = r._visibleColumns[i];
876
- !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));
877
988
  }
878
989
  /**
879
990
  * Sync the internal grid state with the plugin's editing state.
880
991
  */
881
- #a() {
992
+ #c() {
882
993
  const e = this.grid;
883
- e._activeEditRows = this.#e, e._rowEditSnapshots = this.#r;
994
+ e._activeEditRows = this.#e, e._rowEditSnapshots = this.#s;
884
995
  }
885
996
  /**
886
997
  * Snapshot original row data and mark as editing.
887
998
  */
888
- #c(e, i) {
889
- 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());
890
1001
  }
891
1002
  /**
892
1003
  * Exit editing for a row.
893
1004
  */
894
- #i(e, i) {
1005
+ #n(e, t) {
895
1006
  if (this.#e !== e) return;
896
- 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);
897
1008
  let a;
898
1009
  if (n)
899
1010
  try {
900
- a = t.getRowId?.(n);
1011
+ a = i.getRowId?.(n);
901
1012
  } catch {
902
1013
  }
903
- if (!i && o && n && o.querySelectorAll(".cell.editing").forEach((c) => {
904
- const f = Number(c.getAttribute("data-col"));
905
- if (isNaN(f)) return;
906
- const g = t._visibleColumns[f];
907
- if (!g) return;
908
- const d = c.querySelector("input,textarea,select");
909
- if (d) {
910
- const h = E(d, g);
911
- 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);
912
1023
  }
913
- }), i && r && n)
1024
+ }), t && r && n)
914
1025
  Object.keys(r).forEach((l) => {
915
1026
  n[l] = r[l];
916
- }), a && this.#t.delete(a);
917
- else if (!i && n) {
918
- const l = a ? this.#t.has(a) : !1;
919
- 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", {
920
1030
  rowIndex: e,
921
1031
  rowId: a ?? "",
922
1032
  row: n,
923
- changed: l,
1033
+ oldValue: r,
1034
+ newValue: n,
1035
+ changed: d,
924
1036
  changedRows: this.changedRows,
925
1037
  changedRowIds: this.changedRowIds
926
- }), 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);
927
1042
  }
928
- this.#r.delete(e), this.#e = -1, this.#s = -1, this.#a();
929
- for (const l of this.#n)
930
- 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);
931
1046
  o && (o.querySelectorAll(".cell.editing").forEach((l) => {
932
- l.classList.remove("editing"), j(l.parentElement);
933
- }), 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);
934
1049
  }
935
1050
  /**
936
1051
  * Commit a single cell value change.
937
1052
  * Uses ID-based change tracking for stability when rows are reordered.
938
1053
  */
939
- #l(e, i, t, r) {
940
- const n = i.field;
941
- if (!v(n)) return;
1054
+ #u(e, t, i, r) {
1055
+ const n = t.field;
1056
+ if (!w(n)) return;
942
1057
  const o = r[n];
943
- if (o === t) return;
1058
+ if (o === i) return;
944
1059
  const a = this.grid;
945
1060
  let l;
946
1061
  try {
947
1062
  l = this.grid.getRowId(r);
948
1063
  } catch {
949
1064
  }
950
- 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
+ };
951
1071
  if (this.emitCancelable("cell-commit", {
952
1072
  row: r,
953
1073
  rowId: l ?? "",
954
1074
  field: n,
955
1075
  oldValue: o,
956
- value: t,
1076
+ value: i,
957
1077
  rowIndex: e,
958
1078
  changedRows: this.changedRows,
959
1079
  changedRowIds: this.changedRowIds,
960
- firstTimeForRow: c,
961
- updateRow: f
1080
+ firstTimeForRow: d,
1081
+ updateRow: c,
1082
+ setInvalid: u
962
1083
  })) return;
963
- 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", {
964
1085
  rowIndex: e,
965
1086
  field: n,
966
1087
  oldValue: o,
967
- newValue: t
1088
+ newValue: i
968
1089
  });
969
- const d = a.findRenderedRowElement?.(e);
970
- d && (d.classList.add("changed"), D(d, "change"));
1090
+ const h = a.findRenderedRowElement?.(e);
1091
+ h && h.classList.add("changed");
971
1092
  }
972
1093
  /**
973
1094
  * Inject an editor into a cell.
974
1095
  */
975
- #d(e, i, t, r, n, o) {
976
- 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;
977
1098
  let a;
978
1099
  try {
979
1100
  a = this.grid.getRowId(e);
980
1101
  } catch {
981
1102
  }
982
- const l = a ? (u) => this.grid.updateRow(a, u, "cascade") : k, c = v(t.field) ? e[t.field] : void 0;
983
- n.classList.add("editing"), this.#n.add(`${i}:${r}`);
984
- const f = n.parentElement;
985
- f && U(f);
986
- let g = !1;
987
- const d = (u) => {
988
- g || this.#e === -1 || this.#l(i, t, u, e);
989
- }, h = () => {
990
- g = !0, v(t.field) && (e[t.field] = c);
991
- }, p = document.createElement("div");
992
- p.className = "tbw-editor-host", n.innerHTML = "", n.appendChild(p), p.addEventListener("keydown", (u) => {
993
- 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));
994
1115
  });
995
- const m = t, y = m.__editorTemplate, b = B(this.grid, m) ?? z(t), C = c;
996
- if (b === "template" && y)
997
- this.#h(p, m, e, c, d, h, o, i);
998
- else if (typeof b == "string") {
999
- const u = document.createElement(b);
1000
- u.value = C, u.addEventListener("change", () => d(u.value)), p.appendChild(u), o || queueMicrotask(() => {
1001
- 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 });
1002
1123
  });
1003
- } else if (typeof b == "function") {
1004
- const u = {
1124
+ } else if (typeof v == "function") {
1125
+ const f = {
1005
1126
  row: e,
1006
1127
  rowId: a ?? "",
1007
1128
  value: C,
1008
- field: t.field,
1009
- column: t,
1010
- commit: d,
1011
- cancel: h,
1129
+ field: i.field,
1130
+ column: i,
1131
+ commit: u,
1132
+ cancel: m,
1012
1133
  updateRow: l
1013
- }, w = b(u);
1014
- typeof w == "string" ? (p.innerHTML = w, W(p, t, d)) : w instanceof Node && p.appendChild(w), o || queueMicrotask(() => {
1015
- 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 });
1016
1137
  });
1017
- } else if (b && typeof b == "object") {
1018
- const u = document.createElement("div");
1019
- u.setAttribute("data-external-editor", ""), u.setAttribute("data-field", t.field), p.appendChild(u);
1020
- 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 = {
1021
1142
  row: e,
1022
1143
  rowId: a ?? "",
1023
1144
  value: C,
1024
- field: t.field,
1025
- column: t,
1026
- commit: d,
1027
- cancel: h,
1145
+ field: i.field,
1146
+ column: i,
1147
+ commit: u,
1148
+ cancel: m,
1028
1149
  updateRow: l
1029
1150
  };
1030
- if (b.mount)
1151
+ if (v.mount)
1031
1152
  try {
1032
- b.mount({ placeholder: u, context: w, spec: b });
1033
- } catch (S) {
1034
- 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}':`, _);
1035
1156
  }
1036
1157
  else
1037
1158
  this.grid.dispatchEvent(
1038
- 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 } })
1039
1160
  );
1040
1161
  }
1041
1162
  }
1042
1163
  /**
1043
1164
  * Render a template-based editor.
1044
1165
  */
1045
- #h(e, i, t, r, n, o, a, l) {
1046
- const c = i.__editorTemplate;
1047
- if (!c) return;
1048
- const f = c.cloneNode(!0), g = i.__compiledEditor;
1049
- g ? f.innerHTML = g({
1050
- 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,
1051
1172
  value: r,
1052
- field: i.field,
1053
- column: i,
1173
+ field: t.field,
1174
+ column: t,
1054
1175
  commit: n,
1055
1176
  cancel: o
1056
- }) : f.querySelectorAll("*").forEach((h) => {
1057
- 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) => {
1058
- if (!v(m)) return "";
1059
- 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];
1060
1181
  return y == null ? "" : String(y);
1061
1182
  }) || "");
1062
1183
  });
1063
- const d = f.querySelector(
1184
+ const u = c.querySelector(
1064
1185
  "input,textarea,select"
1065
1186
  );
1066
- if (d) {
1067
- d instanceof HTMLInputElement && d.type === "checkbox" ? d.checked = !!r : d.value = String(r ?? "");
1068
- let h = !1;
1069
- d.addEventListener("blur", () => {
1070
- h || n(E(d, i));
1071
- }), d.addEventListener("keydown", (p) => {
1072
- const m = p;
1073
- 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));
1074
- }), 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);
1075
1196
  }
1076
- 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;
1077
1210
  }
1078
1211
  /**
1079
1212
  * Restore focus to cell after exiting edit mode.
1080
1213
  */
1081
- #u(e) {
1214
+ #g(e) {
1082
1215
  queueMicrotask(() => {
1083
1216
  try {
1084
- const i = e._focusRow, t = e._focusCol, r = e.findRenderedRowElement?.(i);
1217
+ const t = e._focusRow, i = e._focusCol, r = e.findRenderedRowElement?.(t);
1085
1218
  if (r) {
1086
1219
  Array.from(e._bodyEl.querySelectorAll(".cell-focus")).forEach(
1087
1220
  (o) => o.classList.remove("cell-focus")
1088
1221
  );
1089
- const n = r.querySelector(`.cell[data-row="${i}"][data-col="${t}"]`);
1222
+ const n = r.querySelector(`.cell[data-row="${t}"][data-col="${i}"]`);
1090
1223
  n && (n.classList.add("cell-focus"), n.setAttribute("aria-selected", "true"), n.hasAttribute("tabindex") || n.setAttribute("tabindex", "-1"), n.focus({ preventScroll: !0 }));
1091
1224
  }
1092
1225
  } catch {
@@ -1096,7 +1229,7 @@ class K extends O {
1096
1229
  // #endregion
1097
1230
  }
1098
1231
  export {
1099
- K as EditingPlugin,
1100
- z as defaultEditorFor
1232
+ z as EditingPlugin,
1233
+ N as defaultEditorFor
1101
1234
  };
1102
1235
  //# sourceMappingURL=index.js.map