@toolbox-web/grid 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/README.md +10 -13
  2. package/all.js +1101 -1048
  3. package/all.js.map +1 -1
  4. package/index.js +245 -137
  5. package/index.js.map +1 -1
  6. package/lib/core/grid.d.ts +10 -0
  7. package/lib/core/grid.d.ts.map +1 -1
  8. package/lib/core/internal/config-manager.d.ts +1 -0
  9. package/lib/core/internal/config-manager.d.ts.map +1 -1
  10. package/lib/core/internal/keyboard.d.ts.map +1 -1
  11. package/lib/core/internal/utils.d.ts +1 -0
  12. package/lib/core/internal/utils.d.ts.map +1 -1
  13. package/lib/core/plugin/base-plugin.d.ts +57 -1
  14. package/lib/core/plugin/base-plugin.d.ts.map +1 -1
  15. package/lib/core/plugin/expander-column.d.ts +51 -0
  16. package/lib/core/plugin/expander-column.d.ts.map +1 -0
  17. package/lib/core/plugin/types.d.ts +117 -1
  18. package/lib/core/plugin/types.d.ts.map +1 -1
  19. package/lib/core/types.d.ts +4 -2
  20. package/lib/core/types.d.ts.map +1 -1
  21. package/lib/plugins/clipboard/ClipboardPlugin.d.ts +5 -4
  22. package/lib/plugins/clipboard/ClipboardPlugin.d.ts.map +1 -1
  23. package/lib/plugins/clipboard/index.d.ts +1 -1
  24. package/lib/plugins/clipboard/index.d.ts.map +1 -1
  25. package/lib/plugins/clipboard/index.js +282 -188
  26. package/lib/plugins/clipboard/index.js.map +1 -1
  27. package/lib/plugins/clipboard/types.d.ts +72 -2
  28. package/lib/plugins/clipboard/types.d.ts.map +1 -1
  29. package/lib/plugins/column-virtualization/ColumnVirtualizationPlugin.d.ts +0 -1
  30. package/lib/plugins/column-virtualization/ColumnVirtualizationPlugin.d.ts.map +1 -1
  31. package/lib/plugins/column-virtualization/index.js +102 -26
  32. package/lib/plugins/column-virtualization/index.js.map +1 -1
  33. package/lib/plugins/context-menu/ContextMenuPlugin.d.ts +0 -1
  34. package/lib/plugins/context-menu/ContextMenuPlugin.d.ts.map +1 -1
  35. package/lib/plugins/context-menu/index.js +154 -78
  36. package/lib/plugins/context-menu/index.js.map +1 -1
  37. package/lib/plugins/editing/EditingPlugin.d.ts +1 -7
  38. package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
  39. package/lib/plugins/editing/index.js +200 -136
  40. package/lib/plugins/editing/index.js.map +1 -1
  41. package/lib/plugins/export/ExportPlugin.d.ts +0 -1
  42. package/lib/plugins/export/ExportPlugin.d.ts.map +1 -1
  43. package/lib/plugins/export/index.js +175 -99
  44. package/lib/plugins/export/index.js.map +1 -1
  45. package/lib/plugins/filtering/FilteringPlugin.d.ts +5 -2
  46. package/lib/plugins/filtering/FilteringPlugin.d.ts.map +1 -1
  47. package/lib/plugins/filtering/index.js +129 -43
  48. package/lib/plugins/filtering/index.js.map +1 -1
  49. package/lib/plugins/grouping-columns/GroupingColumnsPlugin.d.ts +1 -2
  50. package/lib/plugins/grouping-columns/GroupingColumnsPlugin.d.ts.map +1 -1
  51. package/lib/plugins/grouping-columns/grouping-columns.d.ts +1 -1
  52. package/lib/plugins/grouping-columns/grouping-columns.d.ts.map +1 -1
  53. package/lib/plugins/grouping-columns/index.js +144 -66
  54. package/lib/plugins/grouping-columns/index.js.map +1 -1
  55. package/lib/plugins/grouping-rows/GroupingRowsPlugin.d.ts +14 -2
  56. package/lib/plugins/grouping-rows/GroupingRowsPlugin.d.ts.map +1 -1
  57. package/lib/plugins/grouping-rows/index.js +230 -138
  58. package/lib/plugins/grouping-rows/index.js.map +1 -1
  59. package/lib/plugins/master-detail/MasterDetailPlugin.d.ts +13 -11
  60. package/lib/plugins/master-detail/MasterDetailPlugin.d.ts.map +1 -1
  61. package/lib/plugins/master-detail/index.js +265 -196
  62. package/lib/plugins/master-detail/index.js.map +1 -1
  63. package/lib/plugins/master-detail/types.d.ts +0 -10
  64. package/lib/plugins/master-detail/types.d.ts.map +1 -1
  65. package/lib/plugins/multi-sort/MultiSortPlugin.d.ts +1 -2
  66. package/lib/plugins/multi-sort/MultiSortPlugin.d.ts.map +1 -1
  67. package/lib/plugins/multi-sort/index.js +105 -31
  68. package/lib/plugins/multi-sort/index.js.map +1 -1
  69. package/lib/plugins/pinned-columns/PinnedColumnsPlugin.d.ts +0 -1
  70. package/lib/plugins/pinned-columns/PinnedColumnsPlugin.d.ts.map +1 -1
  71. package/lib/plugins/pinned-columns/index.js +128 -52
  72. package/lib/plugins/pinned-columns/index.js.map +1 -1
  73. package/lib/plugins/pinned-rows/PinnedRowsPlugin.d.ts +1 -2
  74. package/lib/plugins/pinned-rows/PinnedRowsPlugin.d.ts.map +1 -1
  75. package/lib/plugins/pinned-rows/index.js +162 -88
  76. package/lib/plugins/pinned-rows/index.js.map +1 -1
  77. package/lib/plugins/pivot/PivotPlugin.d.ts +26 -4
  78. package/lib/plugins/pivot/PivotPlugin.d.ts.map +1 -1
  79. package/lib/plugins/pivot/index.js +398 -310
  80. package/lib/plugins/pivot/index.js.map +1 -1
  81. package/lib/plugins/pivot/pivot-rows.d.ts +2 -1
  82. package/lib/plugins/pivot/pivot-rows.d.ts.map +1 -1
  83. package/lib/plugins/reorder/ReorderPlugin.d.ts +13 -10
  84. package/lib/plugins/reorder/ReorderPlugin.d.ts.map +1 -1
  85. package/lib/plugins/reorder/index.js +288 -226
  86. package/lib/plugins/reorder/index.js.map +1 -1
  87. package/lib/plugins/selection/SelectionPlugin.d.ts +21 -3
  88. package/lib/plugins/selection/SelectionPlugin.d.ts.map +1 -1
  89. package/lib/plugins/selection/index.d.ts +2 -2
  90. package/lib/plugins/selection/index.d.ts.map +1 -1
  91. package/lib/plugins/selection/index.js +276 -145
  92. package/lib/plugins/selection/index.js.map +1 -1
  93. package/lib/plugins/selection/types.d.ts +24 -0
  94. package/lib/plugins/selection/types.d.ts.map +1 -1
  95. package/lib/plugins/server-side/ServerSidePlugin.d.ts +0 -1
  96. package/lib/plugins/server-side/ServerSidePlugin.d.ts.map +1 -1
  97. package/lib/plugins/server-side/index.js +83 -7
  98. package/lib/plugins/server-side/index.js.map +1 -1
  99. package/lib/plugins/tree/TreePlugin.d.ts +5 -1
  100. package/lib/plugins/tree/TreePlugin.d.ts.map +1 -1
  101. package/lib/plugins/tree/index.js +197 -112
  102. package/lib/plugins/tree/index.js.map +1 -1
  103. package/lib/plugins/tree/types.d.ts +0 -10
  104. package/lib/plugins/tree/types.d.ts.map +1 -1
  105. package/lib/plugins/undo-redo/UndoRedoPlugin.d.ts +0 -1
  106. package/lib/plugins/undo-redo/UndoRedoPlugin.d.ts.map +1 -1
  107. package/lib/plugins/undo-redo/index.js +93 -17
  108. package/lib/plugins/undo-redo/index.js.map +1 -1
  109. package/lib/plugins/visibility/VisibilityPlugin.d.ts +7 -4
  110. package/lib/plugins/visibility/VisibilityPlugin.d.ts.map +1 -1
  111. package/lib/plugins/visibility/index.js +144 -65
  112. package/lib/plugins/visibility/index.js.map +1 -1
  113. package/package.json +1 -1
  114. package/umd/grid.all.umd.js +17 -19
  115. package/umd/grid.all.umd.js.map +1 -1
  116. package/umd/grid.umd.js +7 -7
  117. package/umd/grid.umd.js.map +1 -1
  118. package/umd/plugins/clipboard.umd.js +5 -7
  119. package/umd/plugins/clipboard.umd.js.map +1 -1
  120. package/umd/plugins/column-virtualization.umd.js +1 -1
  121. package/umd/plugins/column-virtualization.umd.js.map +1 -1
  122. package/umd/plugins/context-menu.umd.js +1 -1
  123. package/umd/plugins/context-menu.umd.js.map +1 -1
  124. package/umd/plugins/editing.umd.js +1 -1
  125. package/umd/plugins/editing.umd.js.map +1 -1
  126. package/umd/plugins/export.umd.js +1 -1
  127. package/umd/plugins/export.umd.js.map +1 -1
  128. package/umd/plugins/filtering.umd.js +1 -1
  129. package/umd/plugins/filtering.umd.js.map +1 -1
  130. package/umd/plugins/grouping-columns.umd.js +1 -1
  131. package/umd/plugins/grouping-columns.umd.js.map +1 -1
  132. package/umd/plugins/grouping-rows.umd.js +1 -1
  133. package/umd/plugins/grouping-rows.umd.js.map +1 -1
  134. package/umd/plugins/master-detail.umd.js +1 -1
  135. package/umd/plugins/master-detail.umd.js.map +1 -1
  136. package/umd/plugins/multi-sort.umd.js +1 -1
  137. package/umd/plugins/multi-sort.umd.js.map +1 -1
  138. package/umd/plugins/pinned-columns.umd.js +1 -1
  139. package/umd/plugins/pinned-columns.umd.js.map +1 -1
  140. package/umd/plugins/pinned-rows.umd.js +1 -1
  141. package/umd/plugins/pinned-rows.umd.js.map +1 -1
  142. package/umd/plugins/pivot.umd.js +1 -1
  143. package/umd/plugins/pivot.umd.js.map +1 -1
  144. package/umd/plugins/reorder.umd.js +1 -1
  145. package/umd/plugins/reorder.umd.js.map +1 -1
  146. package/umd/plugins/selection.umd.js +1 -1
  147. package/umd/plugins/selection.umd.js.map +1 -1
  148. package/umd/plugins/server-side.umd.js +1 -1
  149. package/umd/plugins/server-side.umd.js.map +1 -1
  150. package/umd/plugins/tree.umd.js +1 -1
  151. package/umd/plugins/tree.umd.js.map +1 -1
  152. package/umd/plugins/undo-redo.umd.js +1 -1
  153. package/umd/plugins/undo-redo.umd.js.map +1 -1
  154. package/umd/plugins/visibility.umd.js +1 -1
  155. package/umd/plugins/visibility.umd.js.map +1 -1
  156. package/lib/core/internal/editing.d.ts +0 -76
  157. package/lib/core/internal/editing.d.ts.map +0 -1
@@ -1,4 +1,4 @@
1
- const R = {
1
+ const b = {
2
2
  expand: "▶",
3
3
  collapse: "▼",
4
4
  sortAsc: "▲",
@@ -8,7 +8,7 @@ const R = {
8
8
  dragHandle: "⋮⋮",
9
9
  toolPanel: "☰"
10
10
  };
11
- class I {
11
+ class x {
12
12
  /**
13
13
  * Plugin dependencies - declare other plugins this one requires.
14
14
  *
@@ -25,8 +25,11 @@ class I {
25
25
  * ```
26
26
  */
27
27
  static dependencies;
28
- /** Plugin version - override in subclass if needed */
29
- version = "1.0.0";
28
+ /**
29
+ * Plugin version - defaults to grid version for built-in plugins.
30
+ * Third-party plugins can override with their own semver.
31
+ */
32
+ version = typeof __GRID_VERSION__ < "u" ? __GRID_VERSION__ : "dev";
30
33
  /** CSS styles to inject into the grid's shadow DOM */
31
34
  styles;
32
35
  /** Custom cell renderers keyed by type name */
@@ -46,7 +49,7 @@ class I {
46
49
  * Created fresh in attach(), aborted in detach().
47
50
  * This ensures event listeners are properly cleaned up when plugins are re-attached.
48
51
  */
49
- #e;
52
+ #t;
50
53
  /**
51
54
  * Default configuration - subclasses should override this getter.
52
55
  * Note: This must be a getter (not property initializer) for proper inheritance
@@ -55,8 +58,8 @@ class I {
55
58
  get defaultConfig() {
56
59
  return {};
57
60
  }
58
- constructor(e = {}) {
59
- this.userConfig = e;
61
+ constructor(t = {}) {
62
+ this.userConfig = t;
60
63
  }
61
64
  /**
62
65
  * Called when the plugin is attached to a grid.
@@ -73,8 +76,8 @@ class I {
73
76
  * }
74
77
  * ```
75
78
  */
76
- attach(e) {
77
- this.#e?.abort(), this.#e = new AbortController(), this.grid = e, this.config = { ...this.defaultConfig, ...this.userConfig };
79
+ attach(t) {
80
+ this.#t?.abort(), this.#t = new AbortController(), this.grid = t, this.config = { ...this.defaultConfig, ...this.userConfig };
78
81
  }
79
82
  /**
80
83
  * Called when the plugin is detached from a grid.
@@ -90,7 +93,7 @@ class I {
90
93
  * ```
91
94
  */
92
95
  detach() {
93
- this.#e?.abort(), this.#e = void 0;
96
+ this.#t?.abort(), this.#t = void 0;
94
97
  }
95
98
  /**
96
99
  * Get another plugin instance from the same grid.
@@ -104,14 +107,22 @@ class I {
104
107
  * }
105
108
  * ```
106
109
  */
107
- getPlugin(e) {
108
- return this.grid?.getPlugin(e);
110
+ getPlugin(t) {
111
+ return this.grid?.getPlugin(t);
109
112
  }
110
113
  /**
111
114
  * Emit a custom event from the grid.
112
115
  */
113
- emit(e, t) {
114
- this.grid?.dispatchEvent?.(new CustomEvent(e, { detail: t, bubbles: !0 }));
116
+ emit(t, e) {
117
+ this.grid?.dispatchEvent?.(new CustomEvent(t, { detail: e, bubbles: !0 }));
118
+ }
119
+ /**
120
+ * Emit a cancelable custom event from the grid.
121
+ * @returns `true` if the event was cancelled (preventDefault called), `false` otherwise
122
+ */
123
+ emitCancelable(t, e) {
124
+ const o = new CustomEvent(t, { detail: e, bubbles: !0, cancelable: !0 });
125
+ return this.grid?.dispatchEvent?.(o), o.defaultPrevented;
115
126
  }
116
127
  /**
117
128
  * Request a re-render of the grid.
@@ -119,6 +130,14 @@ class I {
119
130
  requestRender() {
120
131
  this.grid?.requestRender?.();
121
132
  }
133
+ /**
134
+ * Request a re-render and restore focus styling afterward.
135
+ * Use this when a plugin action (like expand/collapse) triggers a render
136
+ * but needs to maintain keyboard navigation focus.
137
+ */
138
+ requestRenderWithFocus() {
139
+ this.grid?.requestRenderWithFocus?.();
140
+ }
122
141
  /**
123
142
  * Request a lightweight style update without rebuilding DOM.
124
143
  * Use this instead of requestRender() when only CSS classes need updating.
@@ -152,6 +171,19 @@ class I {
152
171
  get visibleColumns() {
153
172
  return this.grid?._visibleColumns ?? [];
154
173
  }
174
+ /**
175
+ * Get the grid as an HTMLElement for direct DOM operations.
176
+ * Use sparingly - prefer the typed GridElementRef API when possible.
177
+ *
178
+ * @example
179
+ * ```ts
180
+ * const width = this.gridElement.clientWidth;
181
+ * this.gridElement.classList.add('my-plugin-active');
182
+ * ```
183
+ */
184
+ get gridElement() {
185
+ return this.grid;
186
+ }
155
187
  /**
156
188
  * Get the shadow root of the grid.
157
189
  */
@@ -176,16 +208,61 @@ class I {
176
208
  * document.addEventListener('keydown', handler, { signal: this.disconnectSignal });
177
209
  */
178
210
  get disconnectSignal() {
179
- return this.#e?.signal ?? this.grid?.disconnectSignal;
211
+ return this.#t?.signal ?? this.grid?.disconnectSignal;
180
212
  }
181
213
  /**
182
214
  * Get the grid-level icons configuration.
183
215
  * Returns merged icons (user config + defaults).
184
216
  */
185
217
  get gridIcons() {
186
- const e = this.grid?.gridConfig?.icons ?? {};
187
- return { ...R, ...e };
218
+ const t = this.grid?.gridConfig?.icons ?? {};
219
+ return { ...b, ...t };
220
+ }
221
+ // #region Animation Helpers
222
+ /**
223
+ * Check if animations are enabled at the grid level.
224
+ * Respects gridConfig.animation.mode and the CSS variable set by the grid.
225
+ *
226
+ * Plugins should use this to skip animations when:
227
+ * - Animation mode is 'off' or `false`
228
+ * - User prefers reduced motion and mode is 'reduced-motion' (default)
229
+ *
230
+ * @example
231
+ * ```ts
232
+ * private get animationStyle(): 'slide' | 'fade' | false {
233
+ * if (!this.isAnimationEnabled) return false;
234
+ * return this.config.animation ?? 'slide';
235
+ * }
236
+ * ```
237
+ */
238
+ get isAnimationEnabled() {
239
+ const t = this.grid?.effectiveConfig?.animation?.mode ?? "reduced-motion";
240
+ if (t === !1 || t === "off") return !1;
241
+ if (t === !0 || t === "on") return !0;
242
+ const e = this.shadowRoot?.host;
243
+ return e ? getComputedStyle(e).getPropertyValue("--tbw-animation-enabled").trim() !== "0" : !0;
244
+ }
245
+ /**
246
+ * Get the animation duration in milliseconds from CSS variable.
247
+ * Falls back to 200ms if not set.
248
+ *
249
+ * Plugins can use this for their animation timing to stay consistent
250
+ * with the grid-level animation.duration setting.
251
+ *
252
+ * @example
253
+ * ```ts
254
+ * element.animate(keyframes, { duration: this.animationDuration });
255
+ * ```
256
+ */
257
+ get animationDuration() {
258
+ const t = this.shadowRoot?.host;
259
+ if (t) {
260
+ const e = getComputedStyle(t).getPropertyValue("--tbw-animation-duration").trim(), o = parseInt(e, 10);
261
+ if (!isNaN(o)) return o;
262
+ }
263
+ return 200;
188
264
  }
265
+ // #endregion
189
266
  /**
190
267
  * Resolve an icon value to string or HTMLElement.
191
268
  * Checks plugin config first, then grid-level icons, then defaults.
@@ -194,8 +271,8 @@ class I {
194
271
  * @param pluginOverride - Optional plugin-level override
195
272
  * @returns The resolved icon value
196
273
  */
197
- resolveIcon(e, t) {
198
- return t !== void 0 ? t : this.gridIcons[e];
274
+ resolveIcon(t, e) {
275
+ return e !== void 0 ? e : this.gridIcons[t];
199
276
  }
200
277
  /**
201
278
  * Set an icon value on an element.
@@ -204,88 +281,81 @@ class I {
204
281
  * @param element - The element to set the icon on
205
282
  * @param icon - The icon value (string or HTMLElement)
206
283
  */
207
- setIcon(e, t) {
208
- typeof t == "string" ? e.innerHTML = t : t instanceof HTMLElement && (e.innerHTML = "", e.appendChild(t.cloneNode(!0)));
284
+ setIcon(t, e) {
285
+ typeof e == "string" ? t.innerHTML = e : e instanceof HTMLElement && (t.innerHTML = "", t.appendChild(e.cloneNode(!0)));
209
286
  }
210
287
  /**
211
288
  * Log a warning message.
212
289
  */
213
- warn(e) {
214
- console.warn(`[tbw-grid:${this.name}] ${e}`);
290
+ warn(t) {
291
+ console.warn(`[tbw-grid:${this.name}] ${t}`);
215
292
  }
216
293
  // #endregion
217
294
  }
218
- function P(h, e, t, n) {
219
- if (n.processCell)
220
- return n.processCell(h, e, t);
221
- if (h == null) return "";
222
- if (h instanceof Date) return h.toISOString();
223
- if (typeof h == "object") return JSON.stringify(h);
224
- const i = String(h), l = n.delimiter ?? " ", o = n.newline ?? `
225
- `;
226
- return n.quoteStrings || i.includes(l) || i.includes(o) || i.includes('"') ? `"${i.replace(/"/g, '""')}"` : i;
227
- }
228
- function p(h) {
229
- const { rows: e, columns: t, selectedIndices: n, config: i } = h, l = i.delimiter ?? " ", o = i.newline ?? `
230
- `, r = t.filter((s) => !s.hidden && !s.field.startsWith("__")), c = [];
231
- if (i.includeHeaders) {
232
- const s = r.map((d) => {
233
- const f = d.header || d.field;
234
- return f.includes(l) || f.includes(o) || f.includes('"') ? `"${f.replace(/"/g, '""')}"` : f;
235
- });
236
- c.push(s.join(l));
237
- }
238
- const u = [...n instanceof Set ? [...n] : n].sort((s, d) => s - d);
239
- for (const s of u) {
240
- const d = e[s];
241
- if (!d) continue;
242
- const f = r.map(
243
- (g) => P(d[g.field], g.field, d, i)
244
- );
245
- c.push(f.join(l));
246
- }
247
- return c.join(o);
295
+ function v(h) {
296
+ return h.meta?.utility === !0;
248
297
  }
249
- async function C(h) {
298
+ async function R(h) {
250
299
  try {
251
300
  return await navigator.clipboard.writeText(h), !0;
252
301
  } catch {
253
- const e = document.createElement("textarea");
254
- e.value = h, e.style.position = "fixed", e.style.opacity = "0", e.style.pointerEvents = "none", document.body.appendChild(e), e.select();
255
- const t = document.execCommand("copy");
256
- return document.body.removeChild(e), t;
302
+ const t = document.createElement("textarea");
303
+ t.value = h, t.style.position = "fixed", t.style.opacity = "0", t.style.pointerEvents = "none", document.body.appendChild(t), t.select();
304
+ const e = document.execCommand("copy");
305
+ return document.body.removeChild(t), e;
257
306
  }
258
307
  }
259
- function y(h, e) {
260
- const t = e.delimiter ?? " ", n = e.newline ?? `
261
- `, i = h.replace(/\r\n/g, `
308
+ function y(h, t) {
309
+ const e = t.delimiter ?? " ", o = t.newline ?? `
310
+ `, a = h.replace(/\r\n/g, `
262
311
  `).replace(/\r/g, `
263
- `), l = [];
264
- let o = [], r = "", c = !1;
265
- for (let a = 0; a < i.length; a++) {
266
- const u = i[a];
267
- u === '"' && !c ? c = !0 : u === '"' && c ? i[a + 1] === '"' ? (r += '"', a++) : c = !1 : u === t && !c ? (o.push(r), r = "") : u === n && !c ? (o.push(r), r = "", (o.length > 1 || o.some((s) => s.trim() !== "")) && l.push(o), o = []) : r += u;
312
+ `), r = [];
313
+ let n = [], i = "", s = !1;
314
+ for (let c = 0; c < a.length; c++) {
315
+ const l = a[c];
316
+ l === '"' && !s ? s = !0 : l === '"' && s ? a[c + 1] === '"' ? (i += '"', c++) : s = !1 : l === e && !s ? (n.push(i), i = "") : l === o && !s ? (n.push(i), i = "", (n.length > 1 || n.some((w) => w.trim() !== "")) && r.push(n), n = []) : i += l;
268
317
  }
269
- return o.push(r), (o.length > 1 || o.some((a) => a.trim() !== "")) && l.push(o), l;
318
+ return n.push(i), (n.length > 1 || n.some((c) => c.trim() !== "")) && r.push(n), r;
270
319
  }
271
- async function x() {
320
+ async function I() {
272
321
  try {
273
322
  return await navigator.clipboard.readText();
274
323
  } catch {
275
324
  return "";
276
325
  }
277
326
  }
278
- class T extends I {
327
+ function P(h, t) {
328
+ const { rows: e, target: o, fields: a } = h;
329
+ if (!o) return;
330
+ const r = t.rows, i = (t.effectiveConfig.columns ?? []).map((l) => l.field), s = [...r], c = o.bounds ? o.bounds.endRow : 1 / 0;
331
+ e.forEach((l, w) => {
332
+ const u = o.row + w;
333
+ if (!(u > c)) {
334
+ if (o.bounds) {
335
+ if (u >= s.length)
336
+ return;
337
+ } else for (; u >= s.length; ) {
338
+ const f = {};
339
+ i.forEach((d) => f[d] = ""), s.push(f);
340
+ }
341
+ s[u] = { ...s[u] }, l.forEach((f, d) => {
342
+ const g = a[d];
343
+ g && (s[u][g] = f);
344
+ });
345
+ }
346
+ }), t.rows = s;
347
+ }
348
+ class E extends x {
279
349
  /**
280
- * Plugin dependencies - ClipboardPlugin requires SelectionPlugin to know what to copy.
350
+ * Plugin dependencies - ClipboardPlugin works best with SelectionPlugin.
281
351
  *
282
- * The SelectionPlugin must be loaded BEFORE this plugin in the plugins array.
352
+ * Without SelectionPlugin: copies entire grid, pastes at row 0 col 0.
353
+ * With SelectionPlugin: copies/pastes based on selection.
283
354
  */
284
355
  static dependencies = [
285
- { name: "selection", required: !0, reason: "ClipboardPlugin needs selection to determine what cells to copy" }
356
+ { name: "selection", required: !1, reason: "Enables copy/paste of selected cells instead of entire grid" }
286
357
  ];
287
358
  name = "clipboard";
288
- version = "1.0.0";
289
359
  get defaultConfig() {
290
360
  return {
291
361
  includeHeaders: !1,
@@ -300,137 +370,153 @@ class T extends I {
300
370
  lastCopied = null;
301
371
  // #endregion
302
372
  // #region Lifecycle
373
+ attach(t) {
374
+ super.attach(t), t.addEventListener(
375
+ "paste",
376
+ (e) => this.#o(e),
377
+ { signal: this.disconnectSignal }
378
+ );
379
+ }
303
380
  detach() {
304
381
  this.lastCopied = null;
305
382
  }
306
383
  // #endregion
307
384
  // #region Event Handlers
308
- onKeyDown(e) {
309
- const t = (e.ctrlKey || e.metaKey) && e.key === "c", n = (e.ctrlKey || e.metaKey) && e.key === "v";
310
- return t ? (this.#e(e.target), !0) : n ? (this.#n(), !0) : !1;
385
+ onKeyDown(t) {
386
+ return (t.ctrlKey || t.metaKey) && t.key === "c" ? (this.#t(t.target), !0) : !1;
311
387
  }
312
388
  // #endregion
313
389
  // #region Private Methods
314
390
  /**
315
- * Handle copy operation
316
- */
317
- #e(e) {
318
- const t = this.#t(), n = t?.getSelectedRows() ?? [], i = n.length > 0, l = t?.getRanges() ?? [], o = l.length > 0, r = t?.getSelectedCell() != null;
319
- let c, a, u;
320
- if (i && t)
321
- c = p({
322
- rows: this.rows,
323
- columns: [...this.columns],
324
- selectedIndices: n,
325
- config: this.config
326
- }), a = n.length, u = this.columns.filter((s) => !s.hidden && !s.field.startsWith("__")).length;
327
- else if (o && t) {
328
- const s = l[l.length - 1], d = this.#i({
329
- startRow: s.from.row,
330
- startCol: s.from.col,
331
- endRow: s.to.row,
332
- endCol: s.to.col
333
- });
334
- c = d.text, a = d.rowCount, u = d.columnCount;
335
- } else if (r && t) {
336
- const s = t.getSelectedCell(), d = this.#s(s.row, s.col);
337
- if (!d) return;
338
- c = d.text, a = 1, u = 1;
339
- } else {
340
- const s = this.#o(e);
391
+ * Handle copy operation.
392
+ *
393
+ * Everything is treated as a range:
394
+ * - With selection: copies the selected range
395
+ * - Row mode: range spanning all columns for selected rows
396
+ * - No selection plugin: entire grid as a range
397
+ * - No selection: try to get focused cell from DOM as 1x1 range
398
+ */
399
+ #t(t) {
400
+ const e = this.#e(), o = e?.getSelection(), a = this.columns.length - 1, r = this.rows.length - 1;
401
+ let n;
402
+ if (o && o.ranges.length > 0) {
403
+ const { mode: s, ranges: c } = o, l = c[c.length - 1];
404
+ s === "row" ? n = {
405
+ startRow: l.from.row,
406
+ startCol: 0,
407
+ endRow: l.to.row,
408
+ endCol: a
409
+ } : n = {
410
+ startRow: l.from.row,
411
+ startCol: l.from.col,
412
+ endRow: l.to.row,
413
+ endCol: l.to.col
414
+ };
415
+ } else if (!e)
416
+ n = { startRow: 0, startCol: 0, endRow: r, endCol: a };
417
+ else {
418
+ const s = this.#r(t);
341
419
  if (!s) return;
342
- c = s.text, a = 1, u = 1;
420
+ n = { startRow: s.row, startCol: s.col, endRow: s.row, endCol: s.col };
343
421
  }
344
- C(c).then(() => {
345
- this.lastCopied = { text: c, timestamp: Date.now() }, this.emit("copy", { text: c, rowCount: a, columnCount: u });
422
+ const i = this.#n(n);
423
+ R(i.text).then(() => {
424
+ this.lastCopied = { text: i.text, timestamp: Date.now() }, this.emit("copy", {
425
+ text: i.text,
426
+ rowCount: i.rowCount,
427
+ columnCount: i.columnCount
428
+ });
346
429
  });
347
430
  }
348
431
  /**
349
- * Handle paste operation
432
+ * Handle native paste event (preferred method - works in iframes).
433
+ * Uses synchronous clipboardData from the native paste event.
434
+ *
435
+ * Flow:
436
+ * 1. Parse clipboard text
437
+ * 2. Build target/fields info from selection
438
+ * 3. Emit 'paste' event (for listeners)
439
+ * 4. Call paste handler (if configured) to apply data to grid
440
+ *
441
+ * Selection behavior:
442
+ * - Single cell: paste starts at cell, expands freely
443
+ * - Range/row: paste is clipped to fit within selection bounds
444
+ * - No selection: paste starts at row 0, col 0
445
+ */
446
+ #o(t) {
447
+ const e = t.clipboardData?.getData("text/plain");
448
+ if (!e) return;
449
+ t.preventDefault();
450
+ const o = y(e, this.config), r = this.#e()?.getSelection(), n = r?.ranges?.[0], i = n?.from.row ?? 0, s = n?.from.col ?? 0, l = n && (r?.mode === "range" || r?.mode === "row") && (n.from.row !== n.to.row || n.from.col !== n.to.col) ? { endRow: n.to.row, endCol: n.to.col } : null, w = l?.endCol ?? this.columns.length - 1, u = this.columns[s], f = u ? { row: i, col: s, field: u.field, bounds: l } : null, d = [], g = o[0]?.length ?? 0;
451
+ for (let p = 0; p < g && s + p <= w; p++) {
452
+ const m = this.columns[s + p];
453
+ m && !m.hidden && d.push(m.field);
454
+ }
455
+ const C = { rows: o, text: e, target: f, fields: d };
456
+ this.emit("paste", C), this.#s(C);
457
+ }
458
+ /**
459
+ * Apply the paste handler to update grid data.
460
+ *
461
+ * Uses the configured `pasteHandler`, or the default handler if not specified.
462
+ * Set `pasteHandler: null` in config to disable auto-paste.
350
463
  */
351
- #n() {
352
- x().then((e) => {
353
- if (!e) return;
354
- const t = y(e, this.config);
355
- this.emit("paste", { rows: t, text: e });
356
- });
464
+ #s(t) {
465
+ if (!this.grid) return;
466
+ const { pasteHandler: e } = this.config;
467
+ if (e === null) return;
468
+ (e ?? P)(t, this.grid);
357
469
  }
358
470
  /**
359
471
  * Get the selection plugin instance if available.
360
472
  */
361
- #t() {
473
+ #e() {
362
474
  try {
363
- const e = this.grid?.getPluginByName("selection");
364
- if (e)
365
- return e;
475
+ const t = this.grid?.getPluginByName("selection");
476
+ if (t)
477
+ return t;
366
478
  } catch {
367
479
  }
368
480
  }
369
- /**
370
- * Build text for a single cell by row/col index.
371
- */
372
- #s(e, t) {
373
- const n = this.rows[e];
374
- if (!n) return null;
375
- const i = this.columns[t];
376
- if (!i) return null;
377
- const l = n[i.field], o = i.header || i.field;
378
- let r;
379
- if (this.config.includeHeaders) {
380
- const c = l == null ? "" : String(l);
381
- r = `${o}: ${c}`;
382
- } else
383
- r = l == null ? "" : String(l);
384
- return { text: r };
385
- }
386
481
  /**
387
482
  * Build text for a rectangular range of cells.
483
+ * Utility columns (like expander columns) are automatically excluded.
388
484
  */
389
- #i(e) {
390
- const { startRow: t, startCol: n, endRow: i, endCol: l } = e, o = Math.min(t, i), r = Math.max(t, i), c = Math.min(n, l), a = Math.max(n, l), u = this.config.delimiter ?? " ", s = this.config.newline ?? `
391
- `, d = [], f = this.columns.slice(c, a + 1);
485
+ #n(t) {
486
+ const { startRow: e, startCol: o, endRow: a, endCol: r } = t, n = Math.min(e, a), i = Math.max(e, a), s = Math.min(o, r), c = Math.max(o, r), l = this.config.delimiter ?? " ", w = this.config.newline ?? `
487
+ `, u = [], f = this.columns.slice(s, c + 1).filter((d) => !v(d));
392
488
  if (this.config.includeHeaders) {
393
- const g = f.map((m) => m.header || m.field);
394
- d.push(g.join(u));
489
+ const d = f.map((g) => g.header || g.field);
490
+ u.push(d.join(l));
395
491
  }
396
- for (let g = o; g <= r; g++) {
397
- const m = this.rows[g];
398
- if (!m) continue;
399
- const b = f.map((S) => {
400
- const w = m[S.field];
401
- return w == null ? "" : w instanceof Date ? w.toISOString() : String(w);
492
+ for (let d = n; d <= i; d++) {
493
+ const g = this.rows[d];
494
+ if (!g) continue;
495
+ const C = f.map((p) => {
496
+ const m = g[p.field];
497
+ return m == null ? "" : m instanceof Date ? m.toISOString() : String(m);
402
498
  });
403
- d.push(b.join(u));
499
+ u.push(C.join(l));
404
500
  }
405
501
  return {
406
- text: d.join(s),
407
- rowCount: r - o + 1,
408
- columnCount: a - c + 1
502
+ text: u.join(w),
503
+ rowCount: i - n + 1,
504
+ columnCount: c - s + 1
409
505
  };
410
506
  }
411
507
  /**
412
- * Build text for a single focused cell from DOM.
413
- * Used when selection plugin is not available or no rows are selected.
414
- */
415
- #o(e) {
416
- const t = e.closest("[data-field-cache]");
417
- if (!t) return null;
418
- const n = t.dataset.fieldCache;
419
- if (!n) return null;
420
- const i = t.dataset.row;
421
- if (!i) return null;
422
- const l = parseInt(i, 10);
423
- if (isNaN(l)) return null;
424
- const o = this.rows[l];
425
- if (!o) return null;
426
- const r = o[n], a = this.columns.find((s) => s.field === n)?.header || n;
427
- let u;
428
- if (this.config.includeHeaders) {
429
- const s = r == null ? "" : String(r);
430
- u = `${a}: ${s}`;
431
- } else
432
- u = r == null ? "" : String(r);
433
- return { text: u, field: n, value: r };
508
+ * Get focused cell coordinates from DOM.
509
+ * Used as fallback when SelectionPlugin has no selection.
510
+ */
511
+ #r(t) {
512
+ const e = t.closest("[data-field-cache]");
513
+ if (!e) return null;
514
+ const o = e.dataset.fieldCache, a = e.dataset.row;
515
+ if (!o || !a) return null;
516
+ const r = parseInt(a, 10);
517
+ if (isNaN(r)) return null;
518
+ const n = this.columns.findIndex((i) => i.field === o);
519
+ return n === -1 ? null : { row: r, col: n };
434
520
  }
435
521
  // #endregion
436
522
  // #region Public API
@@ -439,35 +525,42 @@ class T extends I {
439
525
  * @returns The copied text
440
526
  */
441
527
  async copy() {
442
- const t = this.#t()?.getSelectedRows() ?? [], n = p({
443
- rows: this.rows,
444
- columns: [...this.columns],
445
- selectedIndices: t,
446
- config: this.config
447
- });
448
- return await C(n), this.lastCopied = { text: n, timestamp: Date.now() }, n;
528
+ const e = this.#e()?.getSelection(), o = this.columns.length - 1;
529
+ let a = { startRow: 0, startCol: 0, endRow: this.rows.length - 1, endCol: o };
530
+ if (e && e.ranges.length > 0) {
531
+ const n = e.ranges[e.ranges.length - 1];
532
+ e.mode === "row" ? a = { startRow: n.from.row, startCol: 0, endRow: n.to.row, endCol: o } : a = {
533
+ startRow: n.from.row,
534
+ startCol: n.from.col,
535
+ endRow: n.to.row,
536
+ endCol: n.to.col
537
+ };
538
+ }
539
+ const r = this.#n(a);
540
+ return await R(r.text), this.lastCopied = { text: r.text, timestamp: Date.now() }, r.text;
449
541
  }
450
542
  /**
451
543
  * Copy specific rows by index to clipboard.
452
544
  * @param indices - Array of row indices to copy
453
545
  * @returns The copied text
454
546
  */
455
- async copyRows(e) {
456
- const t = p({
457
- rows: this.rows,
458
- columns: [...this.columns],
459
- selectedIndices: e,
460
- config: this.config
461
- });
462
- return await C(t), this.lastCopied = { text: t, timestamp: Date.now() }, t;
547
+ async copyRows(t) {
548
+ if (t.length === 0) return "";
549
+ const e = [...t].sort((n, i) => n - i), o = this.columns.length - 1, a = {
550
+ startRow: e[0],
551
+ startCol: 0,
552
+ endRow: e[e.length - 1],
553
+ endCol: o
554
+ }, r = this.#n(a);
555
+ return await R(r.text), this.lastCopied = { text: r.text, timestamp: Date.now() }, r.text;
463
556
  }
464
557
  /**
465
558
  * Read and parse clipboard content.
466
559
  * @returns Parsed 2D array of cell values, or null if clipboard is empty
467
560
  */
468
561
  async paste() {
469
- const e = await x();
470
- return e ? y(e, this.config) : null;
562
+ const t = await I();
563
+ return t ? y(t, this.config) : null;
471
564
  }
472
565
  /**
473
566
  * Get the last copied text and timestamp.
@@ -479,6 +572,7 @@ class T extends I {
479
572
  // #endregion
480
573
  }
481
574
  export {
482
- T as ClipboardPlugin
575
+ E as ClipboardPlugin,
576
+ P as defaultPasteHandler
483
577
  };
484
578
  //# sourceMappingURL=index.js.map