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