@toolbox-web/grid 0.0.4 → 0.0.6

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 (64) hide show
  1. package/README.md +24 -0
  2. package/all.d.ts +1709 -135
  3. package/all.js +745 -645
  4. package/all.js.map +1 -1
  5. package/index.d.ts +161 -1
  6. package/index.js +1050 -913
  7. package/index.js.map +1 -1
  8. package/lib/plugins/clipboard/index.js +110 -52
  9. package/lib/plugins/clipboard/index.js.map +1 -1
  10. package/lib/plugins/column-virtualization/index.js +78 -20
  11. package/lib/plugins/column-virtualization/index.js.map +1 -1
  12. package/lib/plugins/context-menu/index.js +163 -95
  13. package/lib/plugins/context-menu/index.js.map +1 -1
  14. package/lib/plugins/export/index.js +93 -35
  15. package/lib/plugins/export/index.js.map +1 -1
  16. package/lib/plugins/filtering/index.js +188 -133
  17. package/lib/plugins/filtering/index.js.map +1 -1
  18. package/lib/plugins/grouping-columns/index.js +69 -11
  19. package/lib/plugins/grouping-columns/index.js.map +1 -1
  20. package/lib/plugins/grouping-rows/index.js +111 -55
  21. package/lib/plugins/grouping-rows/index.js.map +1 -1
  22. package/lib/plugins/master-detail/index.js +196 -51
  23. package/lib/plugins/master-detail/index.js.map +1 -1
  24. package/lib/plugins/multi-sort/index.js +104 -46
  25. package/lib/plugins/multi-sort/index.js.map +1 -1
  26. package/lib/plugins/pinned-columns/index.js +74 -16
  27. package/lib/plugins/pinned-columns/index.js.map +1 -1
  28. package/lib/plugins/pinned-rows/index.js +65 -7
  29. package/lib/plugins/pinned-rows/index.js.map +1 -1
  30. package/lib/plugins/pivot/index.js +117 -59
  31. package/lib/plugins/pivot/index.js.map +1 -1
  32. package/lib/plugins/reorder/index.js +103 -45
  33. package/lib/plugins/reorder/index.js.map +1 -1
  34. package/lib/plugins/selection/index.js +139 -81
  35. package/lib/plugins/selection/index.js.map +1 -1
  36. package/lib/plugins/server-side/index.js +96 -38
  37. package/lib/plugins/server-side/index.js.map +1 -1
  38. package/lib/plugins/tree/index.js +108 -47
  39. package/lib/plugins/tree/index.js.map +1 -1
  40. package/lib/plugins/undo-redo/index.js +70 -12
  41. package/lib/plugins/undo-redo/index.js.map +1 -1
  42. package/lib/plugins/visibility/index.js +82 -24
  43. package/lib/plugins/visibility/index.js.map +1 -1
  44. package/package.json +1 -1
  45. package/umd/grid.all.umd.js +31 -31
  46. package/umd/grid.all.umd.js.map +1 -1
  47. package/umd/grid.umd.js +15 -15
  48. package/umd/grid.umd.js.map +1 -1
  49. package/umd/plugins/context-menu.umd.js +2 -2
  50. package/umd/plugins/context-menu.umd.js.map +1 -1
  51. package/umd/plugins/filtering.umd.js +3 -3
  52. package/umd/plugins/filtering.umd.js.map +1 -1
  53. package/umd/plugins/grouping-rows.umd.js +2 -2
  54. package/umd/plugins/grouping-rows.umd.js.map +1 -1
  55. package/umd/plugins/master-detail.umd.js +2 -2
  56. package/umd/plugins/master-detail.umd.js.map +1 -1
  57. package/umd/plugins/multi-sort.umd.js +1 -1
  58. package/umd/plugins/multi-sort.umd.js.map +1 -1
  59. package/umd/plugins/reorder.umd.js +1 -1
  60. package/umd/plugins/reorder.umd.js.map +1 -1
  61. package/umd/plugins/tree.umd.js +2 -2
  62. package/umd/plugins/tree.umd.js.map +1 -1
  63. package/umd/plugins/visibility.umd.js +1 -1
  64. package/umd/plugins/visibility.umd.js.map +1 -1
@@ -1,4 +1,13 @@
1
- class v {
1
+ const v = {
2
+ expand: "▶",
3
+ collapse: "▼",
4
+ sortAsc: "▲",
5
+ sortDesc: "▼",
6
+ sortNone: "⇅",
7
+ submenuArrow: "▶",
8
+ dragHandle: "⋮⋮"
9
+ };
10
+ class y {
2
11
  /** Plugin version - override in subclass if needed */
3
12
  version = "1.0.0";
4
13
  /** CSS styles to inject into the grid's shadow DOM */
@@ -49,8 +58,8 @@ class v {
49
58
  /**
50
59
  * Emit a custom event from the grid.
51
60
  */
52
- emit(e, l) {
53
- this.grid?.dispatchEvent?.(new CustomEvent(e, { detail: l, bubbles: !0 }));
61
+ emit(e, n) {
62
+ this.grid?.dispatchEvent?.(new CustomEvent(e, { detail: n, bubbles: !0 }));
54
63
  }
55
64
  /**
56
65
  * Request a re-render of the grid.
@@ -97,6 +106,55 @@ class v {
97
106
  get shadowRoot() {
98
107
  return this.grid?.shadowRoot ?? null;
99
108
  }
109
+ /**
110
+ * Get the disconnect signal for event listener cleanup.
111
+ * This signal is aborted when the grid disconnects from the DOM.
112
+ * Use this when adding event listeners that should be cleaned up automatically.
113
+ *
114
+ * Best for:
115
+ * - Document/window-level listeners added in attach()
116
+ * - Listeners on the grid element itself
117
+ * - Any listener that should persist across renders
118
+ *
119
+ * Not needed for:
120
+ * - Listeners on elements created in afterRender() (removed with element)
121
+ *
122
+ * @example
123
+ * element.addEventListener('click', handler, { signal: this.disconnectSignal });
124
+ * document.addEventListener('keydown', handler, { signal: this.disconnectSignal });
125
+ */
126
+ get disconnectSignal() {
127
+ return this.grid?.disconnectSignal;
128
+ }
129
+ /**
130
+ * Get the grid-level icons configuration.
131
+ * Returns merged icons (user config + defaults).
132
+ */
133
+ get gridIcons() {
134
+ const e = this.grid?.gridConfig?.icons ?? {};
135
+ return { ...v, ...e };
136
+ }
137
+ /**
138
+ * Resolve an icon value to string or HTMLElement.
139
+ * Checks plugin config first, then grid-level icons, then defaults.
140
+ *
141
+ * @param iconKey - The icon key in GridIcons (e.g., 'expand', 'collapse')
142
+ * @param pluginOverride - Optional plugin-level override
143
+ * @returns The resolved icon value
144
+ */
145
+ resolveIcon(e, n) {
146
+ return n !== void 0 ? n : this.gridIcons[e];
147
+ }
148
+ /**
149
+ * Set an icon value on an element.
150
+ * Handles both string (text/HTML) and HTMLElement values.
151
+ *
152
+ * @param element - The element to set the icon on
153
+ * @param icon - The icon value (string or HTMLElement)
154
+ */
155
+ setIcon(e, n) {
156
+ typeof n == "string" ? e.innerHTML = n : n instanceof HTMLElement && (e.innerHTML = "", e.appendChild(n.cloneNode(!0)));
157
+ }
100
158
  /**
101
159
  * Log a warning message.
102
160
  */
@@ -104,58 +162,58 @@ class v {
104
162
  console.warn(`[tbw-grid:${this.name}] ${e}`);
105
163
  }
106
164
  }
107
- function g(i, e) {
108
- return (typeof i == "function" ? i(e) : i).filter((n) => !(n.hidden === !0 || typeof n.hidden == "function" && n.hidden(e)));
165
+ function b(s, e) {
166
+ return (typeof s == "function" ? s(e) : s).filter((i) => !(i.hidden === !0 || typeof i.hidden == "function" && i.hidden(e)));
109
167
  }
110
- function y(i, e) {
111
- return i.disabled === !0 ? !0 : typeof i.disabled == "function" ? i.disabled(e) : !1;
168
+ function C(s, e) {
169
+ return s.disabled === !0 ? !0 : typeof s.disabled == "function" ? s.disabled(e) : !1;
112
170
  }
113
- function x(i, e, l) {
114
- const n = document.createElement("div");
115
- n.className = "tbw-context-menu", n.setAttribute("role", "menu");
116
- for (const t of i) {
117
- if (t.separator) {
118
- const o = document.createElement("div");
119
- o.className = "tbw-context-menu-separator", o.setAttribute("role", "separator"), n.appendChild(o);
171
+ function x(s, e, n, i = v.submenuArrow) {
172
+ const l = document.createElement("div");
173
+ l.className = "tbw-context-menu", l.setAttribute("role", "menu");
174
+ for (const o of s) {
175
+ if (o.separator) {
176
+ const r = document.createElement("div");
177
+ r.className = "tbw-context-menu-separator", r.setAttribute("role", "separator"), l.appendChild(r);
120
178
  continue;
121
179
  }
122
- const s = document.createElement("div");
123
- s.className = "tbw-context-menu-item", t.cssClass && s.classList.add(t.cssClass), s.setAttribute("role", "menuitem"), s.setAttribute("data-id", t.id);
124
- const r = y(t, e);
125
- if (r && (s.classList.add("disabled"), s.setAttribute("aria-disabled", "true")), t.icon) {
126
- const o = document.createElement("span");
127
- o.className = "tbw-context-menu-icon", o.innerHTML = t.icon, s.appendChild(o);
180
+ const t = document.createElement("div");
181
+ t.className = "tbw-context-menu-item", o.cssClass && t.classList.add(o.cssClass), t.setAttribute("role", "menuitem"), t.setAttribute("data-id", o.id);
182
+ const c = C(o, e);
183
+ if (c && (t.classList.add("disabled"), t.setAttribute("aria-disabled", "true")), o.icon) {
184
+ const r = document.createElement("span");
185
+ r.className = "tbw-context-menu-icon", r.innerHTML = o.icon, t.appendChild(r);
128
186
  }
129
- const u = document.createElement("span");
130
- if (u.className = "tbw-context-menu-label", u.textContent = t.name, s.appendChild(u), t.shortcut) {
131
- const o = document.createElement("span");
132
- o.className = "tbw-context-menu-shortcut", o.textContent = t.shortcut, s.appendChild(o);
187
+ const d = document.createElement("span");
188
+ if (d.className = "tbw-context-menu-label", d.textContent = o.name, t.appendChild(d), o.shortcut) {
189
+ const r = document.createElement("span");
190
+ r.className = "tbw-context-menu-shortcut", r.textContent = o.shortcut, t.appendChild(r);
133
191
  }
134
- if (t.subMenu?.length) {
135
- const o = document.createElement("span");
136
- o.className = "tbw-context-menu-arrow", o.textContent = "▶", s.appendChild(o), s.addEventListener("mouseenter", () => {
137
- if (s.querySelector(".tbw-context-menu") || !t.subMenu) return;
138
- const a = g(t.subMenu, e), d = x(a, e, l);
139
- d.classList.add("tbw-context-submenu"), d.style.position = "absolute", d.style.left = "100%", d.style.top = "0", s.style.position = "relative", s.appendChild(d);
140
- }), s.addEventListener("mouseleave", () => {
141
- const c = s.querySelector(".tbw-context-menu");
142
- c && c.remove();
192
+ if (o.subMenu?.length) {
193
+ const r = document.createElement("span");
194
+ r.className = "tbw-context-menu-arrow", typeof i == "string" ? r.innerHTML = i : i instanceof HTMLElement && r.appendChild(i.cloneNode(!0)), t.appendChild(r), t.addEventListener("mouseenter", () => {
195
+ if (t.querySelector(".tbw-context-menu") || !o.subMenu) return;
196
+ const m = b(o.subMenu, e), a = x(m, e, n, i);
197
+ a.classList.add("tbw-context-submenu"), a.style.position = "absolute", a.style.left = "100%", a.style.top = "0", t.style.position = "relative", t.appendChild(a);
198
+ }), t.addEventListener("mouseleave", () => {
199
+ const u = t.querySelector(".tbw-context-menu");
200
+ u && u.remove();
143
201
  });
144
202
  }
145
- !r && t.action && !t.subMenu && s.addEventListener("click", (o) => {
146
- o.stopPropagation(), l(t);
147
- }), n.appendChild(s);
203
+ !c && o.action && !o.subMenu && t.addEventListener("click", (r) => {
204
+ r.stopPropagation(), n(o);
205
+ }), l.appendChild(t);
148
206
  }
149
- return n;
207
+ return l;
150
208
  }
151
- function E(i, e, l) {
152
- i.style.position = "fixed", i.style.left = `${e}px`, i.style.top = `${l}px`, i.style.visibility = "hidden", i.style.zIndex = "10000";
153
- const n = i.getBoundingClientRect(), t = window.innerWidth, s = window.innerHeight;
154
- let r = e, u = l;
155
- e + n.width > t && (r = e - n.width), l + n.height > s && (u = l - n.height), r = Math.max(0, r), u = Math.max(0, u), i.style.left = `${r}px`, i.style.top = `${u}px`, i.style.visibility = "visible";
209
+ function E(s, e, n) {
210
+ s.style.position = "fixed", s.style.left = `${e}px`, s.style.top = `${n}px`, s.style.visibility = "hidden", s.style.zIndex = "10000";
211
+ const i = s.getBoundingClientRect(), l = window.innerWidth, o = window.innerHeight;
212
+ let t = e, c = n;
213
+ e + i.width > l && (t = e - i.width), n + i.height > o && (c = n - i.height), t = Math.max(0, t), c = Math.max(0, c), s.style.left = `${t}px`, s.style.top = `${c}px`, s.style.visibility = "visible";
156
214
  }
157
- let f = null, p = null, m = null;
158
- const C = `
215
+ let f = null, p = null, h = null;
216
+ const I = `
159
217
  .tbw-context-menu {
160
218
  position: fixed;
161
219
  background: light-dark(#f5f5f5, #2a2a2a);
@@ -206,31 +264,31 @@ const C = `
206
264
  background: light-dark(#d0d0d4, #454545);
207
265
  margin: 4px 0;
208
266
  }
209
- `, b = [
267
+ `, g = [
210
268
  {
211
269
  id: "copy",
212
270
  name: "Copy",
213
271
  shortcut: "Ctrl+C",
214
- action: (i) => {
215
- i.grid?.plugins?.clipboard?.copy?.();
272
+ action: (s) => {
273
+ s.grid?.plugins?.clipboard?.copy?.();
216
274
  }
217
275
  },
218
276
  { separator: !0, id: "sep1", name: "" },
219
277
  {
220
278
  id: "export-csv",
221
279
  name: "Export CSV",
222
- action: (i) => {
223
- i.grid?.plugins?.export?.exportCsv?.();
280
+ action: (s) => {
281
+ s.grid?.plugins?.export?.exportCsv?.();
224
282
  }
225
283
  }
226
284
  ];
227
- class M extends v {
285
+ class M extends y {
228
286
  name = "contextMenu";
229
287
  version = "1.0.0";
230
288
  get defaultConfig() {
231
289
  return {
232
290
  enabled: !0,
233
- items: b
291
+ items: g
234
292
  };
235
293
  }
236
294
  // ===== Internal State =====
@@ -247,10 +305,10 @@ class M extends v {
247
305
  }
248
306
  // ===== Private Methods =====
249
307
  installGlobalHandlers() {
250
- !m && typeof document < "u" && (m = document.createElement("style"), m.id = "tbw-context-menu-styles", m.textContent = C, document.head.appendChild(m)), f || (f = () => {
251
- document.querySelectorAll(".tbw-context-menu").forEach((l) => l.remove());
308
+ !h && typeof document < "u" && (h = document.createElement("style"), h.id = "tbw-context-menu-styles", h.textContent = I, document.head.appendChild(h)), f || (f = () => {
309
+ document.querySelectorAll(".tbw-context-menu").forEach((n) => n.remove());
252
310
  }, document.addEventListener("click", f)), p || (p = (e) => {
253
- e.key === "Escape" && document.querySelectorAll(".tbw-context-menu").forEach((n) => n.remove());
311
+ e.key === "Escape" && document.querySelectorAll(".tbw-context-menu").forEach((i) => i.remove());
254
312
  }, document.addEventListener("keydown", p));
255
313
  }
256
314
  // ===== Hooks =====
@@ -258,44 +316,49 @@ class M extends v {
258
316
  if (!this.config.enabled) return;
259
317
  const e = this.shadowRoot;
260
318
  if (!e) return;
261
- const l = e.children[0];
262
- l && l.getAttribute("data-context-menu-bound") !== "true" && (l.setAttribute("data-context-menu-bound", "true"), l.addEventListener("contextmenu", (n) => {
319
+ const n = e.children[0];
320
+ n && n.getAttribute("data-context-menu-bound") !== "true" && (n.setAttribute("data-context-menu-bound", "true"), n.addEventListener("contextmenu", (i) => {
263
321
  if (!this.config.enabled) return;
264
- const t = n;
265
- t.preventDefault();
266
- const s = t.target, r = s.closest("[data-row][data-col]"), u = s.closest(".header-cell");
267
- let o;
268
- if (r) {
269
- const a = parseInt(r.getAttribute("data-row") ?? "-1", 10), d = parseInt(r.getAttribute("data-col") ?? "-1", 10), h = this.columns[d], w = this.rows[a];
270
- o = {
322
+ const l = i;
323
+ l.preventDefault();
324
+ const o = l.target, t = o.closest("[data-row][data-col]"), c = o.closest(".header-cell");
325
+ let d;
326
+ if (t) {
327
+ const u = parseInt(t.getAttribute("data-row") ?? "-1", 10), m = parseInt(t.getAttribute("data-col") ?? "-1", 10), a = this.columns[m], w = this.rows[u];
328
+ d = {
271
329
  row: w,
272
- rowIndex: a,
273
- column: h,
274
- columnIndex: d,
275
- field: h?.field ?? "",
276
- value: w?.[h?.field] ?? null,
330
+ rowIndex: u,
331
+ column: a,
332
+ columnIndex: m,
333
+ field: a?.field ?? "",
334
+ value: w?.[a?.field] ?? null,
277
335
  isHeader: !1,
278
- event: t
336
+ event: l
279
337
  };
280
- } else if (u) {
281
- const a = parseInt(u.getAttribute("data-col") ?? "-1", 10), d = this.columns[a];
282
- o = {
338
+ } else if (c) {
339
+ const u = parseInt(c.getAttribute("data-col") ?? "-1", 10), m = this.columns[u];
340
+ d = {
283
341
  row: null,
284
342
  rowIndex: -1,
285
- column: d,
286
- columnIndex: a,
287
- field: d?.field ?? "",
343
+ column: m,
344
+ columnIndex: u,
345
+ field: m?.field ?? "",
288
346
  value: null,
289
347
  isHeader: !0,
290
- event: t
348
+ event: l
291
349
  };
292
350
  } else
293
351
  return;
294
- this.params = o, this.position = { x: t.clientX, y: t.clientY };
295
- const c = g(this.config.items ?? b, o);
296
- c.length && (this.menuElement && this.menuElement.remove(), this.menuElement = x(c, o, (a) => {
297
- a.action && a.action(o), this.menuElement?.remove(), this.menuElement = null, this.isOpen = !1;
298
- }), document.body.appendChild(this.menuElement), E(this.menuElement, t.clientX, t.clientY), this.isOpen = !0, this.emit("context-menu-open", { params: o, items: c }));
352
+ this.params = d, this.position = { x: l.clientX, y: l.clientY };
353
+ const r = b(this.config.items ?? g, d);
354
+ r.length && (this.menuElement && this.menuElement.remove(), this.menuElement = x(
355
+ r,
356
+ d,
357
+ (u) => {
358
+ u.action && u.action(d), this.menuElement?.remove(), this.menuElement = null, this.isOpen = !1;
359
+ },
360
+ this.gridIcons.submenuArrow
361
+ ), document.body.appendChild(this.menuElement), E(this.menuElement, l.clientX, l.clientY), this.isOpen = !0, this.emit("context-menu-open", { params: d, items: r }));
299
362
  }));
300
363
  }
301
364
  // ===== Public API =====
@@ -305,20 +368,25 @@ class M extends v {
305
368
  * @param y - Y coordinate
306
369
  * @param params - Partial context menu parameters
307
370
  */
308
- showMenu(e, l, n) {
309
- const t = {
310
- row: n.row ?? null,
311
- rowIndex: n.rowIndex ?? -1,
312
- column: n.column ?? null,
313
- columnIndex: n.columnIndex ?? -1,
314
- field: n.field ?? "",
315
- value: n.value ?? null,
316
- isHeader: n.isHeader ?? !1,
317
- event: n.event ?? new MouseEvent("contextmenu")
318
- }, s = g(this.config.items ?? b, t);
319
- this.menuElement && this.menuElement.remove(), this.menuElement = x(s, t, (r) => {
320
- r.action && r.action(t), this.menuElement?.remove(), this.menuElement = null, this.isOpen = !1;
321
- }), document.body.appendChild(this.menuElement), E(this.menuElement, e, l), this.isOpen = !0;
371
+ showMenu(e, n, i) {
372
+ const l = {
373
+ row: i.row ?? null,
374
+ rowIndex: i.rowIndex ?? -1,
375
+ column: i.column ?? null,
376
+ columnIndex: i.columnIndex ?? -1,
377
+ field: i.field ?? "",
378
+ value: i.value ?? null,
379
+ isHeader: i.isHeader ?? !1,
380
+ event: i.event ?? new MouseEvent("contextmenu")
381
+ }, o = b(this.config.items ?? g, l);
382
+ this.menuElement && this.menuElement.remove(), this.menuElement = x(
383
+ o,
384
+ l,
385
+ (t) => {
386
+ t.action && t.action(l), this.menuElement?.remove(), this.menuElement = null, this.isOpen = !1;
387
+ },
388
+ this.gridIcons.submenuArrow
389
+ ), document.body.appendChild(this.menuElement), E(this.menuElement, e, n), this.isOpen = !0;
322
390
  }
323
391
  /**
324
392
  * Hide the context menu.