@toolbox-web/grid 0.4.0 → 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 (161) hide show
  1. package/README.md +10 -13
  2. package/all.js +1124 -1047
  3. package/all.js.map +1 -1
  4. package/index.js +688 -515
  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/internal/validate-config.d.ts +14 -0
  14. package/lib/core/internal/validate-config.d.ts.map +1 -1
  15. package/lib/core/plugin/base-plugin.d.ts +105 -1
  16. package/lib/core/plugin/base-plugin.d.ts.map +1 -1
  17. package/lib/core/plugin/expander-column.d.ts +51 -0
  18. package/lib/core/plugin/expander-column.d.ts.map +1 -0
  19. package/lib/core/plugin/index.d.ts +1 -0
  20. package/lib/core/plugin/index.d.ts.map +1 -1
  21. package/lib/core/plugin/plugin-manager.d.ts +1 -1
  22. package/lib/core/plugin/plugin-manager.d.ts.map +1 -1
  23. package/lib/core/plugin/types.d.ts +117 -1
  24. package/lib/core/plugin/types.d.ts.map +1 -1
  25. package/lib/core/types.d.ts +4 -2
  26. package/lib/core/types.d.ts.map +1 -1
  27. package/lib/plugins/clipboard/ClipboardPlugin.d.ts +9 -2
  28. package/lib/plugins/clipboard/ClipboardPlugin.d.ts.map +1 -1
  29. package/lib/plugins/clipboard/index.d.ts +1 -1
  30. package/lib/plugins/clipboard/index.d.ts.map +1 -1
  31. package/lib/plugins/clipboard/index.js +303 -185
  32. package/lib/plugins/clipboard/index.js.map +1 -1
  33. package/lib/plugins/clipboard/types.d.ts +72 -2
  34. package/lib/plugins/clipboard/types.d.ts.map +1 -1
  35. package/lib/plugins/column-virtualization/ColumnVirtualizationPlugin.d.ts +0 -1
  36. package/lib/plugins/column-virtualization/ColumnVirtualizationPlugin.d.ts.map +1 -1
  37. package/lib/plugins/column-virtualization/index.js +116 -24
  38. package/lib/plugins/column-virtualization/index.js.map +1 -1
  39. package/lib/plugins/context-menu/ContextMenuPlugin.d.ts +0 -1
  40. package/lib/plugins/context-menu/ContextMenuPlugin.d.ts.map +1 -1
  41. package/lib/plugins/context-menu/index.js +164 -72
  42. package/lib/plugins/context-menu/index.js.map +1 -1
  43. package/lib/plugins/editing/EditingPlugin.d.ts +1 -7
  44. package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -1
  45. package/lib/plugins/editing/index.js +213 -133
  46. package/lib/plugins/editing/index.js.map +1 -1
  47. package/lib/plugins/export/ExportPlugin.d.ts +0 -1
  48. package/lib/plugins/export/ExportPlugin.d.ts.map +1 -1
  49. package/lib/plugins/export/index.js +195 -103
  50. package/lib/plugins/export/index.js.map +1 -1
  51. package/lib/plugins/filtering/FilteringPlugin.d.ts +5 -2
  52. package/lib/plugins/filtering/FilteringPlugin.d.ts.map +1 -1
  53. package/lib/plugins/filtering/index.js +145 -43
  54. package/lib/plugins/filtering/index.js.map +1 -1
  55. package/lib/plugins/grouping-columns/GroupingColumnsPlugin.d.ts +1 -2
  56. package/lib/plugins/grouping-columns/GroupingColumnsPlugin.d.ts.map +1 -1
  57. package/lib/plugins/grouping-columns/grouping-columns.d.ts +1 -1
  58. package/lib/plugins/grouping-columns/grouping-columns.d.ts.map +1 -1
  59. package/lib/plugins/grouping-columns/index.js +162 -68
  60. package/lib/plugins/grouping-columns/index.js.map +1 -1
  61. package/lib/plugins/grouping-rows/GroupingRowsPlugin.d.ts +14 -2
  62. package/lib/plugins/grouping-rows/GroupingRowsPlugin.d.ts.map +1 -1
  63. package/lib/plugins/grouping-rows/index.js +246 -138
  64. package/lib/plugins/grouping-rows/index.js.map +1 -1
  65. package/lib/plugins/master-detail/MasterDetailPlugin.d.ts +13 -11
  66. package/lib/plugins/master-detail/MasterDetailPlugin.d.ts.map +1 -1
  67. package/lib/plugins/master-detail/index.js +281 -196
  68. package/lib/plugins/master-detail/index.js.map +1 -1
  69. package/lib/plugins/master-detail/types.d.ts +0 -10
  70. package/lib/plugins/master-detail/types.d.ts.map +1 -1
  71. package/lib/plugins/multi-sort/MultiSortPlugin.d.ts +1 -2
  72. package/lib/plugins/multi-sort/MultiSortPlugin.d.ts.map +1 -1
  73. package/lib/plugins/multi-sort/index.js +121 -31
  74. package/lib/plugins/multi-sort/index.js.map +1 -1
  75. package/lib/plugins/pinned-columns/PinnedColumnsPlugin.d.ts +0 -1
  76. package/lib/plugins/pinned-columns/PinnedColumnsPlugin.d.ts.map +1 -1
  77. package/lib/plugins/pinned-columns/index.js +144 -52
  78. package/lib/plugins/pinned-columns/index.js.map +1 -1
  79. package/lib/plugins/pinned-rows/PinnedRowsPlugin.d.ts +1 -2
  80. package/lib/plugins/pinned-rows/PinnedRowsPlugin.d.ts.map +1 -1
  81. package/lib/plugins/pinned-rows/index.js +178 -88
  82. package/lib/plugins/pinned-rows/index.js.map +1 -1
  83. package/lib/plugins/pivot/PivotPlugin.d.ts +26 -4
  84. package/lib/plugins/pivot/PivotPlugin.d.ts.map +1 -1
  85. package/lib/plugins/pivot/index.js +414 -310
  86. package/lib/plugins/pivot/index.js.map +1 -1
  87. package/lib/plugins/pivot/pivot-rows.d.ts +2 -1
  88. package/lib/plugins/pivot/pivot-rows.d.ts.map +1 -1
  89. package/lib/plugins/reorder/ReorderPlugin.d.ts +13 -10
  90. package/lib/plugins/reorder/ReorderPlugin.d.ts.map +1 -1
  91. package/lib/plugins/reorder/index.js +304 -226
  92. package/lib/plugins/reorder/index.js.map +1 -1
  93. package/lib/plugins/selection/SelectionPlugin.d.ts +21 -3
  94. package/lib/plugins/selection/SelectionPlugin.d.ts.map +1 -1
  95. package/lib/plugins/selection/index.d.ts +2 -2
  96. package/lib/plugins/selection/index.d.ts.map +1 -1
  97. package/lib/plugins/selection/index.js +292 -145
  98. package/lib/plugins/selection/index.js.map +1 -1
  99. package/lib/plugins/selection/types.d.ts +24 -0
  100. package/lib/plugins/selection/types.d.ts.map +1 -1
  101. package/lib/plugins/server-side/ServerSidePlugin.d.ts +0 -1
  102. package/lib/plugins/server-side/ServerSidePlugin.d.ts.map +1 -1
  103. package/lib/plugins/server-side/index.js +95 -3
  104. package/lib/plugins/server-side/index.js.map +1 -1
  105. package/lib/plugins/tree/TreePlugin.d.ts +5 -1
  106. package/lib/plugins/tree/TreePlugin.d.ts.map +1 -1
  107. package/lib/plugins/tree/index.js +213 -112
  108. package/lib/plugins/tree/index.js.map +1 -1
  109. package/lib/plugins/tree/types.d.ts +0 -10
  110. package/lib/plugins/tree/types.d.ts.map +1 -1
  111. package/lib/plugins/undo-redo/UndoRedoPlugin.d.ts +7 -2
  112. package/lib/plugins/undo-redo/UndoRedoPlugin.d.ts.map +1 -1
  113. package/lib/plugins/undo-redo/index.js +112 -12
  114. package/lib/plugins/undo-redo/index.js.map +1 -1
  115. package/lib/plugins/visibility/VisibilityPlugin.d.ts +14 -5
  116. package/lib/plugins/visibility/VisibilityPlugin.d.ts.map +1 -1
  117. package/lib/plugins/visibility/index.js +168 -65
  118. package/lib/plugins/visibility/index.js.map +1 -1
  119. package/package.json +1 -1
  120. package/umd/grid.all.umd.js +21 -17
  121. package/umd/grid.all.umd.js.map +1 -1
  122. package/umd/grid.umd.js +14 -8
  123. package/umd/grid.umd.js.map +1 -1
  124. package/umd/plugins/clipboard.umd.js +5 -7
  125. package/umd/plugins/clipboard.umd.js.map +1 -1
  126. package/umd/plugins/column-virtualization.umd.js +1 -1
  127. package/umd/plugins/column-virtualization.umd.js.map +1 -1
  128. package/umd/plugins/context-menu.umd.js +1 -1
  129. package/umd/plugins/context-menu.umd.js.map +1 -1
  130. package/umd/plugins/editing.umd.js +1 -1
  131. package/umd/plugins/editing.umd.js.map +1 -1
  132. package/umd/plugins/export.umd.js +1 -1
  133. package/umd/plugins/export.umd.js.map +1 -1
  134. package/umd/plugins/filtering.umd.js +1 -1
  135. package/umd/plugins/filtering.umd.js.map +1 -1
  136. package/umd/plugins/grouping-columns.umd.js +1 -1
  137. package/umd/plugins/grouping-columns.umd.js.map +1 -1
  138. package/umd/plugins/grouping-rows.umd.js +1 -1
  139. package/umd/plugins/grouping-rows.umd.js.map +1 -1
  140. package/umd/plugins/master-detail.umd.js +1 -1
  141. package/umd/plugins/master-detail.umd.js.map +1 -1
  142. package/umd/plugins/multi-sort.umd.js +1 -1
  143. package/umd/plugins/multi-sort.umd.js.map +1 -1
  144. package/umd/plugins/pinned-columns.umd.js +1 -1
  145. package/umd/plugins/pinned-columns.umd.js.map +1 -1
  146. package/umd/plugins/pinned-rows.umd.js +1 -1
  147. package/umd/plugins/pinned-rows.umd.js.map +1 -1
  148. package/umd/plugins/pivot.umd.js +1 -1
  149. package/umd/plugins/pivot.umd.js.map +1 -1
  150. package/umd/plugins/reorder.umd.js +1 -1
  151. package/umd/plugins/reorder.umd.js.map +1 -1
  152. package/umd/plugins/selection.umd.js +1 -1
  153. package/umd/plugins/selection.umd.js.map +1 -1
  154. package/umd/plugins/server-side.umd.js +1 -1
  155. package/umd/plugins/server-side.umd.js.map +1 -1
  156. package/umd/plugins/tree.umd.js +1 -1
  157. package/umd/plugins/tree.umd.js.map +1 -1
  158. package/umd/plugins/undo-redo.umd.js +1 -1
  159. package/umd/plugins/undo-redo.umd.js.map +1 -1
  160. package/umd/plugins/visibility.umd.js +1 -1
  161. package/umd/plugins/visibility.umd.js.map +1 -1
@@ -1,4 +1,4 @@
1
- const R = {
1
+ const b = {
2
2
  expand: "▶",
3
3
  collapse: "▼",
4
4
  sortAsc: "▲",
@@ -8,9 +8,28 @@ const R = {
8
8
  dragHandle: "⋮⋮",
9
9
  toolPanel: "☰"
10
10
  };
11
- class I {
12
- /** Plugin version - override in subclass if needed */
13
- version = "1.0.0";
11
+ class x {
12
+ /**
13
+ * Plugin dependencies - declare other plugins this one requires.
14
+ *
15
+ * Dependencies are validated when the plugin is attached.
16
+ * Required dependencies throw an error if missing.
17
+ * Optional dependencies log an info message if missing.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * static readonly dependencies: PluginDependency[] = [
22
+ * { name: 'editing', required: true, reason: 'Tracks cell edits for undo/redo' },
23
+ * { name: 'selection', required: false, reason: 'Enables selection-based undo' },
24
+ * ];
25
+ * ```
26
+ */
27
+ static dependencies;
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";
14
33
  /** CSS styles to inject into the grid's shadow DOM */
15
34
  styles;
16
35
  /** Custom cell renderers keyed by type name */
@@ -30,7 +49,7 @@ class I {
30
49
  * Created fresh in attach(), aborted in detach().
31
50
  * This ensures event listeners are properly cleaned up when plugins are re-attached.
32
51
  */
33
- #e;
52
+ #t;
34
53
  /**
35
54
  * Default configuration - subclasses should override this getter.
36
55
  * Note: This must be a getter (not property initializer) for proper inheritance
@@ -39,8 +58,8 @@ class I {
39
58
  get defaultConfig() {
40
59
  return {};
41
60
  }
42
- constructor(e = {}) {
43
- this.userConfig = e;
61
+ constructor(t = {}) {
62
+ this.userConfig = t;
44
63
  }
45
64
  /**
46
65
  * Called when the plugin is attached to a grid.
@@ -57,8 +76,8 @@ class I {
57
76
  * }
58
77
  * ```
59
78
  */
60
- attach(e) {
61
- 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 };
62
81
  }
63
82
  /**
64
83
  * Called when the plugin is detached from a grid.
@@ -74,7 +93,7 @@ class I {
74
93
  * ```
75
94
  */
76
95
  detach() {
77
- this.#e?.abort(), this.#e = void 0;
96
+ this.#t?.abort(), this.#t = void 0;
78
97
  }
79
98
  /**
80
99
  * Get another plugin instance from the same grid.
@@ -88,14 +107,22 @@ class I {
88
107
  * }
89
108
  * ```
90
109
  */
91
- getPlugin(e) {
92
- return this.grid?.getPlugin(e);
110
+ getPlugin(t) {
111
+ return this.grid?.getPlugin(t);
93
112
  }
94
113
  /**
95
114
  * Emit a custom event from the grid.
96
115
  */
97
- emit(e, t) {
98
- 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;
99
126
  }
100
127
  /**
101
128
  * Request a re-render of the grid.
@@ -103,6 +130,14 @@ class I {
103
130
  requestRender() {
104
131
  this.grid?.requestRender?.();
105
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
+ }
106
141
  /**
107
142
  * Request a lightweight style update without rebuilding DOM.
108
143
  * Use this instead of requestRender() when only CSS classes need updating.
@@ -136,6 +171,19 @@ class I {
136
171
  get visibleColumns() {
137
172
  return this.grid?._visibleColumns ?? [];
138
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
+ }
139
187
  /**
140
188
  * Get the shadow root of the grid.
141
189
  */
@@ -160,16 +208,61 @@ class I {
160
208
  * document.addEventListener('keydown', handler, { signal: this.disconnectSignal });
161
209
  */
162
210
  get disconnectSignal() {
163
- return this.#e?.signal ?? this.grid?.disconnectSignal;
211
+ return this.#t?.signal ?? this.grid?.disconnectSignal;
164
212
  }
165
213
  /**
166
214
  * Get the grid-level icons configuration.
167
215
  * Returns merged icons (user config + defaults).
168
216
  */
169
217
  get gridIcons() {
170
- const e = this.grid?.gridConfig?.icons ?? {};
171
- 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;
172
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;
264
+ }
265
+ // #endregion
173
266
  /**
174
267
  * Resolve an icon value to string or HTMLElement.
175
268
  * Checks plugin config first, then grid-level icons, then defaults.
@@ -178,8 +271,8 @@ class I {
178
271
  * @param pluginOverride - Optional plugin-level override
179
272
  * @returns The resolved icon value
180
273
  */
181
- resolveIcon(e, t) {
182
- return t !== void 0 ? t : this.gridIcons[e];
274
+ resolveIcon(t, e) {
275
+ return e !== void 0 ? e : this.gridIcons[t];
183
276
  }
184
277
  /**
185
278
  * Set an icon value on an element.
@@ -188,80 +281,81 @@ class I {
188
281
  * @param element - The element to set the icon on
189
282
  * @param icon - The icon value (string or HTMLElement)
190
283
  */
191
- setIcon(e, t) {
192
- 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)));
193
286
  }
194
287
  /**
195
288
  * Log a warning message.
196
289
  */
197
- warn(e) {
198
- console.warn(`[tbw-grid:${this.name}] ${e}`);
290
+ warn(t) {
291
+ console.warn(`[tbw-grid:${this.name}] ${t}`);
199
292
  }
200
293
  // #endregion
201
294
  }
202
- function T(h, e, t, n) {
203
- if (n.processCell)
204
- return n.processCell(h, e, t);
205
- if (h == null) return "";
206
- if (h instanceof Date) return h.toISOString();
207
- if (typeof h == "object") return JSON.stringify(h);
208
- const i = String(h), l = n.delimiter ?? " ", o = n.newline ?? `
209
- `;
210
- return n.quoteStrings || i.includes(l) || i.includes(o) || i.includes('"') ? `"${i.replace(/"/g, '""')}"` : i;
295
+ function v(h) {
296
+ return h.meta?.utility === !0;
211
297
  }
212
- function p(h) {
213
- const { rows: e, columns: t, selectedIndices: n, config: i } = h, l = i.delimiter ?? " ", o = i.newline ?? `
214
- `, r = t.filter((s) => !s.hidden && !s.field.startsWith("__")), c = [];
215
- if (i.includeHeaders) {
216
- const s = r.map((d) => {
217
- const f = d.header || d.field;
218
- return f.includes(l) || f.includes(o) || f.includes('"') ? `"${f.replace(/"/g, '""')}"` : f;
219
- });
220
- c.push(s.join(l));
221
- }
222
- const u = [...n instanceof Set ? [...n] : n].sort((s, d) => s - d);
223
- for (const s of u) {
224
- const d = e[s];
225
- if (!d) continue;
226
- const f = r.map(
227
- (g) => T(d[g.field], g.field, d, i)
228
- );
229
- c.push(f.join(l));
230
- }
231
- return c.join(o);
232
- }
233
- async function C(h) {
298
+ async function R(h) {
234
299
  try {
235
300
  return await navigator.clipboard.writeText(h), !0;
236
301
  } catch {
237
- const e = document.createElement("textarea");
238
- e.value = h, e.style.position = "fixed", e.style.opacity = "0", e.style.pointerEvents = "none", document.body.appendChild(e), e.select();
239
- const t = document.execCommand("copy");
240
- 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;
241
306
  }
242
307
  }
243
- function y(h, e) {
244
- const t = e.delimiter ?? " ", n = e.newline ?? `
245
- `, 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, `
246
311
  `).replace(/\r/g, `
247
- `), l = [];
248
- let o = [], r = "", c = !1;
249
- for (let a = 0; a < i.length; a++) {
250
- const u = i[a];
251
- 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;
252
317
  }
253
- 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;
254
319
  }
255
- async function x() {
320
+ async function I() {
256
321
  try {
257
322
  return await navigator.clipboard.readText();
258
323
  } catch {
259
324
  return "";
260
325
  }
261
326
  }
262
- class D 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 {
349
+ /**
350
+ * Plugin dependencies - ClipboardPlugin works best with SelectionPlugin.
351
+ *
352
+ * Without SelectionPlugin: copies entire grid, pastes at row 0 col 0.
353
+ * With SelectionPlugin: copies/pastes based on selection.
354
+ */
355
+ static dependencies = [
356
+ { name: "selection", required: !1, reason: "Enables copy/paste of selected cells instead of entire grid" }
357
+ ];
263
358
  name = "clipboard";
264
- version = "1.0.0";
265
359
  get defaultConfig() {
266
360
  return {
267
361
  includeHeaders: !1,
@@ -276,137 +370,153 @@ class D extends I {
276
370
  lastCopied = null;
277
371
  // #endregion
278
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
+ }
279
380
  detach() {
280
381
  this.lastCopied = null;
281
382
  }
282
383
  // #endregion
283
384
  // #region Event Handlers
284
- onKeyDown(e) {
285
- const t = (e.ctrlKey || e.metaKey) && e.key === "c", n = (e.ctrlKey || e.metaKey) && e.key === "v";
286
- 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;
287
387
  }
288
388
  // #endregion
289
389
  // #region Private Methods
290
390
  /**
291
- * Handle copy operation
292
- */
293
- #e(e) {
294
- const t = this.#t(), n = t?.getSelectedRows() ?? [], i = n.length > 0, l = t?.getRanges() ?? [], o = l.length > 0, r = t?.getSelectedCell() != null;
295
- let c, a, u;
296
- if (i && t)
297
- c = p({
298
- rows: this.rows,
299
- columns: [...this.columns],
300
- selectedIndices: n,
301
- config: this.config
302
- }), a = n.length, u = this.columns.filter((s) => !s.hidden && !s.field.startsWith("__")).length;
303
- else if (o && t) {
304
- const s = l[l.length - 1], d = this.#i({
305
- startRow: s.from.row,
306
- startCol: s.from.col,
307
- endRow: s.to.row,
308
- endCol: s.to.col
309
- });
310
- c = d.text, a = d.rowCount, u = d.columnCount;
311
- } else if (r && t) {
312
- const s = t.getSelectedCell(), d = this.#s(s.row, s.col);
313
- if (!d) return;
314
- c = d.text, a = 1, u = 1;
315
- } else {
316
- 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);
317
419
  if (!s) return;
318
- c = s.text, a = 1, u = 1;
420
+ n = { startRow: s.row, startCol: s.col, endRow: s.row, endCol: s.col };
319
421
  }
320
- C(c).then(() => {
321
- 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
+ });
322
429
  });
323
430
  }
324
431
  /**
325
- * 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
326
445
  */
327
- #n() {
328
- x().then((e) => {
329
- if (!e) return;
330
- const t = y(e, this.config);
331
- this.emit("paste", { rows: t, text: e });
332
- });
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.
463
+ */
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);
333
469
  }
334
470
  /**
335
471
  * Get the selection plugin instance if available.
336
472
  */
337
- #t() {
473
+ #e() {
338
474
  try {
339
- const e = this.grid?.getPluginByName("selection");
340
- if (e)
341
- return e;
475
+ const t = this.grid?.getPluginByName("selection");
476
+ if (t)
477
+ return t;
342
478
  } catch {
343
479
  }
344
480
  }
345
- /**
346
- * Build text for a single cell by row/col index.
347
- */
348
- #s(e, t) {
349
- const n = this.rows[e];
350
- if (!n) return null;
351
- const i = this.columns[t];
352
- if (!i) return null;
353
- const l = n[i.field], o = i.header || i.field;
354
- let r;
355
- if (this.config.includeHeaders) {
356
- const c = l == null ? "" : String(l);
357
- r = `${o}: ${c}`;
358
- } else
359
- r = l == null ? "" : String(l);
360
- return { text: r };
361
- }
362
481
  /**
363
482
  * Build text for a rectangular range of cells.
483
+ * Utility columns (like expander columns) are automatically excluded.
364
484
  */
365
- #i(e) {
366
- 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 ?? `
367
- `, 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));
368
488
  if (this.config.includeHeaders) {
369
- const g = f.map((m) => m.header || m.field);
370
- d.push(g.join(u));
489
+ const d = f.map((g) => g.header || g.field);
490
+ u.push(d.join(l));
371
491
  }
372
- for (let g = o; g <= r; g++) {
373
- const m = this.rows[g];
374
- if (!m) continue;
375
- const b = f.map((S) => {
376
- const w = m[S.field];
377
- 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);
378
498
  });
379
- d.push(b.join(u));
499
+ u.push(C.join(l));
380
500
  }
381
501
  return {
382
- text: d.join(s),
383
- rowCount: r - o + 1,
384
- columnCount: a - c + 1
502
+ text: u.join(w),
503
+ rowCount: i - n + 1,
504
+ columnCount: c - s + 1
385
505
  };
386
506
  }
387
507
  /**
388
- * Build text for a single focused cell from DOM.
389
- * Used when selection plugin is not available or no rows are selected.
390
- */
391
- #o(e) {
392
- const t = e.closest("[data-field-cache]");
393
- if (!t) return null;
394
- const n = t.dataset.fieldCache;
395
- if (!n) return null;
396
- const i = t.dataset.row;
397
- if (!i) return null;
398
- const l = parseInt(i, 10);
399
- if (isNaN(l)) return null;
400
- const o = this.rows[l];
401
- if (!o) return null;
402
- const r = o[n], a = this.columns.find((s) => s.field === n)?.header || n;
403
- let u;
404
- if (this.config.includeHeaders) {
405
- const s = r == null ? "" : String(r);
406
- u = `${a}: ${s}`;
407
- } else
408
- u = r == null ? "" : String(r);
409
- 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 };
410
520
  }
411
521
  // #endregion
412
522
  // #region Public API
@@ -415,35 +525,42 @@ class D extends I {
415
525
  * @returns The copied text
416
526
  */
417
527
  async copy() {
418
- const t = this.#t()?.getSelectedRows() ?? [], n = p({
419
- rows: this.rows,
420
- columns: [...this.columns],
421
- selectedIndices: t,
422
- config: this.config
423
- });
424
- 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;
425
541
  }
426
542
  /**
427
543
  * Copy specific rows by index to clipboard.
428
544
  * @param indices - Array of row indices to copy
429
545
  * @returns The copied text
430
546
  */
431
- async copyRows(e) {
432
- const t = p({
433
- rows: this.rows,
434
- columns: [...this.columns],
435
- selectedIndices: e,
436
- config: this.config
437
- });
438
- 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;
439
556
  }
440
557
  /**
441
558
  * Read and parse clipboard content.
442
559
  * @returns Parsed 2D array of cell values, or null if clipboard is empty
443
560
  */
444
561
  async paste() {
445
- const e = await x();
446
- return e ? y(e, this.config) : null;
562
+ const t = await I();
563
+ return t ? y(t, this.config) : null;
447
564
  }
448
565
  /**
449
566
  * Get the last copied text and timestamp.
@@ -455,6 +572,7 @@ class D extends I {
455
572
  // #endregion
456
573
  }
457
574
  export {
458
- D as ClipboardPlugin
575
+ E as ClipboardPlugin,
576
+ P as defaultPasteHandler
459
577
  };
460
578
  //# sourceMappingURL=index.js.map