@toolbox-web/grid 0.3.2 → 0.4.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.
- package/README.md +5 -4
- package/all.d.ts +19 -19
- package/all.d.ts.map +1 -1
- package/all.js +1775 -1202
- package/all.js.map +1 -1
- package/index.js +2143 -2015
- package/index.js.map +1 -1
- package/lib/core/grid.d.ts +22 -12
- package/lib/core/grid.d.ts.map +1 -1
- package/lib/core/internal/columns.d.ts +0 -9
- package/lib/core/internal/columns.d.ts.map +1 -1
- package/lib/core/internal/config-manager.d.ts +236 -0
- package/lib/core/internal/config-manager.d.ts.map +1 -0
- package/lib/core/internal/event-delegation.d.ts.map +1 -1
- package/lib/core/internal/header.d.ts.map +1 -1
- package/lib/core/internal/keyboard.d.ts.map +1 -1
- package/lib/core/internal/render-scheduler.d.ts +123 -0
- package/lib/core/internal/render-scheduler.d.ts.map +1 -0
- package/lib/core/internal/rows.d.ts +8 -3
- package/lib/core/internal/rows.d.ts.map +1 -1
- package/lib/core/internal/sanitize.d.ts +10 -2
- package/lib/core/internal/sanitize.d.ts.map +1 -1
- package/lib/core/internal/shell.d.ts +40 -2
- package/lib/core/internal/shell.d.ts.map +1 -1
- package/lib/core/internal/validate-config.d.ts +11 -0
- package/lib/core/internal/validate-config.d.ts.map +1 -0
- package/lib/core/plugin/base-plugin.d.ts +70 -0
- package/lib/core/plugin/base-plugin.d.ts.map +1 -1
- package/lib/core/plugin/plugin-manager.d.ts +13 -2
- package/lib/core/plugin/plugin-manager.d.ts.map +1 -1
- package/lib/core/plugin/types.d.ts +17 -3
- package/lib/core/plugin/types.d.ts.map +1 -1
- package/lib/core/types.d.ts +112 -12
- package/lib/core/types.d.ts.map +1 -1
- package/lib/plugins/clipboard/ClipboardPlugin.d.ts.map +1 -1
- package/lib/plugins/clipboard/index.js +50 -18
- package/lib/plugins/clipboard/index.js.map +1 -1
- package/lib/plugins/column-virtualization/index.js +60 -25
- package/lib/plugins/column-virtualization/index.js.map +1 -1
- package/lib/plugins/context-menu/index.js +51 -16
- package/lib/plugins/context-menu/index.js.map +1 -1
- package/lib/plugins/editing/EditingPlugin.d.ts +117 -0
- package/lib/plugins/editing/EditingPlugin.d.ts.map +1 -0
- package/lib/{core/internal → plugins/editing}/editors.d.ts +1 -1
- package/lib/plugins/editing/editors.d.ts.map +1 -0
- package/lib/plugins/editing/index.d.ts +8 -0
- package/lib/plugins/editing/index.d.ts.map +1 -0
- package/lib/plugins/editing/index.js +705 -0
- package/lib/plugins/editing/index.js.map +1 -0
- package/lib/plugins/editing/types.d.ts +45 -0
- package/lib/plugins/editing/types.d.ts.map +1 -0
- package/lib/plugins/export/ExportPlugin.d.ts.map +1 -1
- package/lib/plugins/export/index.js +74 -39
- package/lib/plugins/export/index.js.map +1 -1
- package/lib/plugins/filtering/FilteringPlugin.d.ts.map +1 -1
- package/lib/plugins/filtering/index.js +87 -50
- package/lib/plugins/filtering/index.js.map +1 -1
- package/lib/plugins/grouping-columns/grouping-columns.d.ts +4 -4
- package/lib/plugins/grouping-columns/grouping-columns.d.ts.map +1 -1
- package/lib/plugins/grouping-columns/index.js +59 -24
- package/lib/plugins/grouping-columns/index.js.map +1 -1
- package/lib/plugins/grouping-rows/GroupingRowsPlugin.d.ts.map +1 -1
- package/lib/plugins/grouping-rows/grouping-rows.d.ts.map +1 -1
- package/lib/plugins/grouping-rows/index.js +46 -11
- package/lib/plugins/grouping-rows/index.js.map +1 -1
- package/lib/plugins/master-detail/MasterDetailPlugin.d.ts +2 -2
- package/lib/plugins/master-detail/MasterDetailPlugin.d.ts.map +1 -1
- package/lib/plugins/master-detail/index.js +140 -102
- package/lib/plugins/master-detail/index.js.map +1 -1
- package/lib/plugins/master-detail/types.d.ts +12 -2
- package/lib/plugins/master-detail/types.d.ts.map +1 -1
- package/lib/plugins/multi-sort/MultiSortPlugin.d.ts.map +1 -1
- package/lib/plugins/multi-sort/index.js +59 -22
- package/lib/plugins/multi-sort/index.js.map +1 -1
- package/lib/plugins/pinned-columns/index.js +41 -6
- package/lib/plugins/pinned-columns/index.js.map +1 -1
- package/lib/plugins/pinned-rows/PinnedRowsPlugin.d.ts.map +1 -1
- package/lib/plugins/pinned-rows/index.js +45 -9
- package/lib/plugins/pinned-rows/index.js.map +1 -1
- package/lib/plugins/pivot/index.js +42 -7
- package/lib/plugins/pivot/index.js.map +1 -1
- package/lib/plugins/reorder/ReorderPlugin.d.ts.map +1 -1
- package/lib/plugins/reorder/index.js +59 -19
- package/lib/plugins/reorder/index.js.map +1 -1
- package/lib/plugins/selection/index.js +41 -6
- package/lib/plugins/selection/index.js.map +1 -1
- package/lib/plugins/server-side/index.js +48 -13
- package/lib/plugins/server-side/index.js.map +1 -1
- package/lib/plugins/tree/TreePlugin.d.ts +3 -3
- package/lib/plugins/tree/TreePlugin.d.ts.map +1 -1
- package/lib/plugins/tree/index.js +165 -126
- package/lib/plugins/tree/index.js.map +1 -1
- package/lib/plugins/tree/tree-data.d.ts +6 -6
- package/lib/plugins/tree/tree-data.d.ts.map +1 -1
- package/lib/plugins/tree/tree-detect.d.ts +5 -9
- package/lib/plugins/tree/tree-detect.d.ts.map +1 -1
- package/lib/plugins/tree/types.d.ts +16 -4
- package/lib/plugins/tree/types.d.ts.map +1 -1
- package/lib/plugins/undo-redo/index.js +46 -11
- package/lib/plugins/undo-redo/index.js.map +1 -1
- package/lib/plugins/visibility/index.js +37 -2
- package/lib/plugins/visibility/index.js.map +1 -1
- package/package.json +1 -1
- package/public.d.ts +104 -13
- package/public.d.ts.map +1 -1
- package/umd/grid.all.umd.js +31 -19
- package/umd/grid.all.umd.js.map +1 -1
- package/umd/grid.umd.js +18 -6
- package/umd/grid.umd.js.map +1 -1
- package/umd/plugins/clipboard.umd.js +1 -1
- package/umd/plugins/clipboard.umd.js.map +1 -1
- package/umd/plugins/editing.umd.js +2 -0
- package/umd/plugins/editing.umd.js.map +1 -0
- package/umd/plugins/export.umd.js +2 -2
- package/umd/plugins/export.umd.js.map +1 -1
- package/umd/plugins/filtering.umd.js +1 -1
- package/umd/plugins/filtering.umd.js.map +1 -1
- package/umd/plugins/grouping-columns.umd.js +1 -1
- package/umd/plugins/grouping-columns.umd.js.map +1 -1
- package/umd/plugins/grouping-rows.umd.js +1 -1
- package/umd/plugins/grouping-rows.umd.js.map +1 -1
- package/umd/plugins/master-detail.umd.js +1 -1
- package/umd/plugins/master-detail.umd.js.map +1 -1
- package/umd/plugins/multi-sort.umd.js +1 -1
- package/umd/plugins/multi-sort.umd.js.map +1 -1
- package/umd/plugins/pinned-rows.umd.js +1 -1
- package/umd/plugins/pinned-rows.umd.js.map +1 -1
- package/umd/plugins/reorder.umd.js +1 -1
- package/umd/plugins/reorder.umd.js.map +1 -1
- package/umd/plugins/tree.umd.js +1 -1
- package/umd/plugins/tree.umd.js.map +1 -1
- package/lib/core/internal/column-state.d.ts +0 -124
- package/lib/core/internal/column-state.d.ts.map +0 -1
- package/lib/core/internal/editing.d.ts +0 -76
- package/lib/core/internal/editing.d.ts.map +0 -1
- package/lib/core/internal/editors.d.ts.map +0 -1
- package/lib/core/internal/grid-internals.d.ts +0 -83
- package/lib/core/internal/grid-internals.d.ts.map +0 -1
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
const R = {
|
|
2
|
+
expand: "▶",
|
|
3
|
+
collapse: "▼",
|
|
4
|
+
sortAsc: "▲",
|
|
5
|
+
sortDesc: "▼",
|
|
6
|
+
sortNone: "⇅",
|
|
7
|
+
submenuArrow: "▶",
|
|
8
|
+
dragHandle: "⋮⋮",
|
|
9
|
+
toolPanel: "☰"
|
|
10
|
+
};
|
|
11
|
+
class _ {
|
|
12
|
+
/** Plugin version - override in subclass if needed */
|
|
13
|
+
version = "1.0.0";
|
|
14
|
+
/** CSS styles to inject into the grid's shadow DOM */
|
|
15
|
+
styles;
|
|
16
|
+
/** Custom cell renderers keyed by type name */
|
|
17
|
+
cellRenderers;
|
|
18
|
+
/** Custom header renderers keyed by type name */
|
|
19
|
+
headerRenderers;
|
|
20
|
+
/** Custom cell editors keyed by type name */
|
|
21
|
+
cellEditors;
|
|
22
|
+
/** The grid instance this plugin is attached to */
|
|
23
|
+
grid;
|
|
24
|
+
/** Plugin configuration - merged with defaults in attach() */
|
|
25
|
+
config;
|
|
26
|
+
/** User-provided configuration from constructor */
|
|
27
|
+
userConfig;
|
|
28
|
+
/**
|
|
29
|
+
* Plugin-level AbortController for event listener cleanup.
|
|
30
|
+
* Created fresh in attach(), aborted in detach().
|
|
31
|
+
* This ensures event listeners are properly cleaned up when plugins are re-attached.
|
|
32
|
+
*/
|
|
33
|
+
#e;
|
|
34
|
+
/**
|
|
35
|
+
* Default configuration - subclasses should override this getter.
|
|
36
|
+
* Note: This must be a getter (not property initializer) for proper inheritance
|
|
37
|
+
* since property initializers run after parent constructor.
|
|
38
|
+
*/
|
|
39
|
+
get defaultConfig() {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
constructor(e = {}) {
|
|
43
|
+
this.userConfig = e;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Called when the plugin is attached to a grid.
|
|
47
|
+
* Override to set up event listeners, initialize state, etc.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* attach(grid: GridElement): void {
|
|
52
|
+
* super.attach(grid);
|
|
53
|
+
* // Set up document-level listeners with auto-cleanup
|
|
54
|
+
* document.addEventListener('keydown', this.handleEscape, {
|
|
55
|
+
* signal: this.disconnectSignal
|
|
56
|
+
* });
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
attach(e) {
|
|
61
|
+
this.#e?.abort(), this.#e = new AbortController(), this.grid = e, this.config = { ...this.defaultConfig, ...this.userConfig };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Called when the plugin is detached from a grid.
|
|
65
|
+
* Override to clean up event listeners, timers, etc.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* detach(): void {
|
|
70
|
+
* // Clean up any state not handled by disconnectSignal
|
|
71
|
+
* this.selectedRows.clear();
|
|
72
|
+
* this.cache = null;
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
detach() {
|
|
77
|
+
this.#e?.abort(), this.#e = void 0;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get another plugin instance from the same grid.
|
|
81
|
+
* Use for inter-plugin communication.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* const selection = this.getPlugin(SelectionPlugin);
|
|
86
|
+
* if (selection) {
|
|
87
|
+
* const selectedRows = selection.getSelectedRows();
|
|
88
|
+
* }
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
getPlugin(e) {
|
|
92
|
+
return this.grid?.getPlugin(e);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Emit a custom event from the grid.
|
|
96
|
+
*/
|
|
97
|
+
emit(e, t) {
|
|
98
|
+
this.grid?.dispatchEvent?.(new CustomEvent(e, { detail: t, bubbles: !0 }));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Request a re-render of the grid.
|
|
102
|
+
*/
|
|
103
|
+
requestRender() {
|
|
104
|
+
this.grid?.requestRender?.();
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Request a lightweight style update without rebuilding DOM.
|
|
108
|
+
* Use this instead of requestRender() when only CSS classes need updating.
|
|
109
|
+
*/
|
|
110
|
+
requestAfterRender() {
|
|
111
|
+
this.grid?.requestAfterRender?.();
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get the current rows from the grid.
|
|
115
|
+
*/
|
|
116
|
+
get rows() {
|
|
117
|
+
return this.grid?.rows ?? [];
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get the original unfiltered/unprocessed rows from the grid.
|
|
121
|
+
* Use this when you need all source data regardless of active filters.
|
|
122
|
+
*/
|
|
123
|
+
get sourceRows() {
|
|
124
|
+
return this.grid?.sourceRows ?? [];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get the current columns from the grid.
|
|
128
|
+
*/
|
|
129
|
+
get columns() {
|
|
130
|
+
return this.grid?.columns ?? [];
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get only visible columns from the grid (excludes hidden).
|
|
134
|
+
* Use this for rendering that needs to match the grid template.
|
|
135
|
+
*/
|
|
136
|
+
get visibleColumns() {
|
|
137
|
+
return this.grid?._visibleColumns ?? [];
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Get the shadow root of the grid.
|
|
141
|
+
*/
|
|
142
|
+
get shadowRoot() {
|
|
143
|
+
return this.grid?.shadowRoot ?? null;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Get the disconnect signal for event listener cleanup.
|
|
147
|
+
* This signal is aborted when the grid disconnects from the DOM.
|
|
148
|
+
* Use this when adding event listeners that should be cleaned up automatically.
|
|
149
|
+
*
|
|
150
|
+
* Best for:
|
|
151
|
+
* - Document/window-level listeners added in attach()
|
|
152
|
+
* - Listeners on the grid element itself
|
|
153
|
+
* - Any listener that should persist across renders
|
|
154
|
+
*
|
|
155
|
+
* Not needed for:
|
|
156
|
+
* - Listeners on elements created in afterRender() (removed with element)
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* element.addEventListener('click', handler, { signal: this.disconnectSignal });
|
|
160
|
+
* document.addEventListener('keydown', handler, { signal: this.disconnectSignal });
|
|
161
|
+
*/
|
|
162
|
+
get disconnectSignal() {
|
|
163
|
+
return this.#e?.signal ?? this.grid?.disconnectSignal;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get the grid-level icons configuration.
|
|
167
|
+
* Returns merged icons (user config + defaults).
|
|
168
|
+
*/
|
|
169
|
+
get gridIcons() {
|
|
170
|
+
const e = this.grid?.gridConfig?.icons ?? {};
|
|
171
|
+
return { ...R, ...e };
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Resolve an icon value to string or HTMLElement.
|
|
175
|
+
* Checks plugin config first, then grid-level icons, then defaults.
|
|
176
|
+
*
|
|
177
|
+
* @param iconKey - The icon key in GridIcons (e.g., 'expand', 'collapse')
|
|
178
|
+
* @param pluginOverride - Optional plugin-level override
|
|
179
|
+
* @returns The resolved icon value
|
|
180
|
+
*/
|
|
181
|
+
resolveIcon(e, t) {
|
|
182
|
+
return t !== void 0 ? t : this.gridIcons[e];
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Set an icon value on an element.
|
|
186
|
+
* Handles both string (text/HTML) and HTMLElement values.
|
|
187
|
+
*
|
|
188
|
+
* @param element - The element to set the icon on
|
|
189
|
+
* @param icon - The icon value (string or HTMLElement)
|
|
190
|
+
*/
|
|
191
|
+
setIcon(e, t) {
|
|
192
|
+
typeof t == "string" ? e.innerHTML = t : t instanceof HTMLElement && (e.innerHTML = "", e.appendChild(t.cloneNode(!0)));
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Log a warning message.
|
|
196
|
+
*/
|
|
197
|
+
warn(e) {
|
|
198
|
+
console.warn(`[tbw-grid:${this.name}] ${e}`);
|
|
199
|
+
}
|
|
200
|
+
// #endregion
|
|
201
|
+
}
|
|
202
|
+
function k(f) {
|
|
203
|
+
switch (f.type) {
|
|
204
|
+
case "number":
|
|
205
|
+
return (e) => {
|
|
206
|
+
const t = document.createElement("input");
|
|
207
|
+
return t.type = "number", t.value = e.value != null ? String(e.value) : "", t.addEventListener("blur", () => e.commit(t.value === "" ? null : Number(t.value))), t.addEventListener("keydown", (i) => {
|
|
208
|
+
i.key === "Enter" && e.commit(t.value === "" ? null : Number(t.value)), i.key === "Escape" && e.cancel();
|
|
209
|
+
}), t;
|
|
210
|
+
};
|
|
211
|
+
case "boolean":
|
|
212
|
+
return (e) => {
|
|
213
|
+
const t = document.createElement("input");
|
|
214
|
+
return t.type = "checkbox", t.checked = !!e.value, t.addEventListener("change", () => e.commit(t.checked)), t;
|
|
215
|
+
};
|
|
216
|
+
case "date":
|
|
217
|
+
return (e) => {
|
|
218
|
+
const t = document.createElement("input");
|
|
219
|
+
return t.type = "date", e.value instanceof Date && (t.valueAsDate = e.value), t.addEventListener("change", () => e.commit(t.valueAsDate)), t.addEventListener("keydown", (i) => {
|
|
220
|
+
i.key === "Escape" && e.cancel();
|
|
221
|
+
}), t;
|
|
222
|
+
};
|
|
223
|
+
case "select":
|
|
224
|
+
case "typeahead":
|
|
225
|
+
return (e) => {
|
|
226
|
+
const t = document.createElement("select"), i = e.column;
|
|
227
|
+
i.multi && (t.multiple = !0);
|
|
228
|
+
const s = i.options;
|
|
229
|
+
(typeof s == "function" ? s() : s || []).forEach((r) => {
|
|
230
|
+
const c = document.createElement("option");
|
|
231
|
+
c.value = String(r.value), c.textContent = r.label, (i.multi && Array.isArray(e.value) && e.value.includes(r.value) || !i.multi && e.value === r.value) && (c.selected = !0), t.appendChild(c);
|
|
232
|
+
});
|
|
233
|
+
const l = () => {
|
|
234
|
+
if (i.multi) {
|
|
235
|
+
const r = [];
|
|
236
|
+
Array.from(t.selectedOptions).forEach((c) => {
|
|
237
|
+
r.push(c.value);
|
|
238
|
+
}), e.commit(r);
|
|
239
|
+
} else
|
|
240
|
+
e.commit(t.value);
|
|
241
|
+
};
|
|
242
|
+
return t.addEventListener("change", l), t.addEventListener("blur", l), t.addEventListener("keydown", (r) => {
|
|
243
|
+
r.key === "Escape" && e.cancel();
|
|
244
|
+
}), t;
|
|
245
|
+
};
|
|
246
|
+
default:
|
|
247
|
+
return (e) => {
|
|
248
|
+
const t = document.createElement("input");
|
|
249
|
+
return t.type = "text", t.value = e.value != null ? String(e.value) : "", t.addEventListener("blur", () => e.commit(t.value)), t.addEventListener("keydown", (i) => {
|
|
250
|
+
i.key === "Enter" && e.commit(t.value), i.key === "Escape" && e.cancel();
|
|
251
|
+
}), t;
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const C = 'input,select,textarea,[contenteditable="true"],[contenteditable=""],[tabindex]:not([tabindex="-1"])';
|
|
256
|
+
function b(f) {
|
|
257
|
+
return !(typeof f != "string" || f === "__proto__" || f === "constructor" || f === "prototype");
|
|
258
|
+
}
|
|
259
|
+
function T(f) {
|
|
260
|
+
return (f.__editingCellCount ?? 0) > 0;
|
|
261
|
+
}
|
|
262
|
+
function L(f) {
|
|
263
|
+
const e = (f.__editingCellCount ?? 0) + 1;
|
|
264
|
+
f.__editingCellCount = e, f.setAttribute("data-has-editing", "");
|
|
265
|
+
}
|
|
266
|
+
function S(f) {
|
|
267
|
+
f.__editingCellCount = 0, f.removeAttribute("data-has-editing");
|
|
268
|
+
}
|
|
269
|
+
function A(f, e, t) {
|
|
270
|
+
const i = f.querySelector("input,textarea,select");
|
|
271
|
+
if (!i) return;
|
|
272
|
+
const s = () => i instanceof HTMLInputElement ? i.type === "checkbox" ? i.checked : i.type === "number" ? i.value === "" ? null : Number(i.value) : i.type === "date" ? i.valueAsDate : i.value : e.type === "number" && i.value !== "" ? Number(i.value) : i.value;
|
|
273
|
+
i.addEventListener("blur", () => {
|
|
274
|
+
t(s());
|
|
275
|
+
}), i instanceof HTMLInputElement && i.type === "checkbox" ? i.addEventListener("change", () => t(i.checked)) : i instanceof HTMLSelectElement && i.addEventListener("change", () => t(s()));
|
|
276
|
+
}
|
|
277
|
+
class q extends _ {
|
|
278
|
+
name = "editing";
|
|
279
|
+
version = "1.0.0";
|
|
280
|
+
get defaultConfig() {
|
|
281
|
+
return {
|
|
282
|
+
editOn: "click"
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
// #region Editing State (fully owned by plugin)
|
|
286
|
+
/** Currently active edit row index, or -1 if not editing */
|
|
287
|
+
#e = -1;
|
|
288
|
+
/** Currently active edit column index, or -1 if not editing */
|
|
289
|
+
#r = -1;
|
|
290
|
+
/** Snapshots of row data before editing started */
|
|
291
|
+
#s = /* @__PURE__ */ new Map();
|
|
292
|
+
/** Set of row indices that have been modified */
|
|
293
|
+
#t = /* @__PURE__ */ new Set();
|
|
294
|
+
/** Set of cells currently in edit mode: "rowIndex:colIndex" */
|
|
295
|
+
#n = /* @__PURE__ */ new Set();
|
|
296
|
+
/** Flag to restore focus after next render (used when exiting edit mode) */
|
|
297
|
+
#o = !1;
|
|
298
|
+
// #endregion
|
|
299
|
+
// #region Lifecycle
|
|
300
|
+
attach(e) {
|
|
301
|
+
super.attach(e);
|
|
302
|
+
const t = this.disconnectSignal, i = e;
|
|
303
|
+
i._activeEditRows = -1, i._rowEditSnapshots = /* @__PURE__ */ new Map(), i._changedRowIndices = /* @__PURE__ */ new Set(), Object.defineProperty(e, "changedRows", {
|
|
304
|
+
get: () => this.changedRows,
|
|
305
|
+
configurable: !0
|
|
306
|
+
}), Object.defineProperty(e, "changedRowIndices", {
|
|
307
|
+
get: () => this.changedRowIndices,
|
|
308
|
+
configurable: !0
|
|
309
|
+
}), e.resetChangedRows = (s) => this.resetChangedRows(s), e.beginBulkEdit = (s, n) => {
|
|
310
|
+
n && this.beginCellEdit(s, n);
|
|
311
|
+
}, document.addEventListener(
|
|
312
|
+
"keydown",
|
|
313
|
+
(s) => {
|
|
314
|
+
s.key === "Escape" && this.#e !== -1 && this.#i(this.#e, !0);
|
|
315
|
+
},
|
|
316
|
+
{ capture: !0, signal: t }
|
|
317
|
+
), document.addEventListener(
|
|
318
|
+
"mousedown",
|
|
319
|
+
(s) => {
|
|
320
|
+
if (this.#e === -1) return;
|
|
321
|
+
const n = i.findRenderedRowElement?.(this.#e);
|
|
322
|
+
!n || (s.composedPath && s.composedPath() || []).includes(n) || this.#i(this.#e, !1);
|
|
323
|
+
},
|
|
324
|
+
{ signal: t }
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
detach() {
|
|
328
|
+
this.#e = -1, this.#r = -1, this.#s.clear(), this.#t.clear(), this.#n.clear(), super.detach();
|
|
329
|
+
}
|
|
330
|
+
// #endregion
|
|
331
|
+
// #region Config Augmentation (processColumns hook)
|
|
332
|
+
/**
|
|
333
|
+
* Augment columns with editing metadata.
|
|
334
|
+
* This enables the grid to recognize editable columns without core knowledge.
|
|
335
|
+
*/
|
|
336
|
+
processColumns(e) {
|
|
337
|
+
return e;
|
|
338
|
+
}
|
|
339
|
+
// #endregion
|
|
340
|
+
// #region Event Handlers (event distribution)
|
|
341
|
+
/**
|
|
342
|
+
* Handle cell clicks - start editing if configured for click mode.
|
|
343
|
+
* Both click and dblclick events come through this handler.
|
|
344
|
+
* Starts row-based editing (all editable cells in the row get editors).
|
|
345
|
+
*/
|
|
346
|
+
onCellClick(e) {
|
|
347
|
+
const t = this.grid, i = this.config.editOn ?? t.effectiveConfig?.editOn;
|
|
348
|
+
if (i === !1 || i === "manual" || i !== "click" && i !== "dblclick") return !1;
|
|
349
|
+
const s = e.originalEvent.type === "dblclick";
|
|
350
|
+
if (i === "click" && s || i === "dblclick" && !s) return !1;
|
|
351
|
+
const { rowIndex: n } = e;
|
|
352
|
+
return t._columns?.some((r) => r.editable) ? (e.originalEvent.stopPropagation(), this.beginBulkEdit(n), !0) : !1;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Handle keyboard events for edit lifecycle.
|
|
356
|
+
*/
|
|
357
|
+
onKeyDown(e) {
|
|
358
|
+
const t = this.grid;
|
|
359
|
+
if (e.key === "Escape" && this.#e !== -1)
|
|
360
|
+
return this.#i(this.#e, !0), !0;
|
|
361
|
+
if (e.key === " " || e.key === "Spacebar") {
|
|
362
|
+
const i = t._focusRow, s = t._focusCol;
|
|
363
|
+
if (i >= 0 && s >= 0) {
|
|
364
|
+
const n = t._visibleColumns[s], l = t._rows[i];
|
|
365
|
+
if (n?.editable && n.type === "boolean" && l) {
|
|
366
|
+
const r = n.field;
|
|
367
|
+
if (b(r)) {
|
|
368
|
+
const u = !l[r];
|
|
369
|
+
return this.#c(i, n, u, l), e.preventDefault(), this.requestRender(), !0;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return !1;
|
|
374
|
+
}
|
|
375
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
376
|
+
if (this.#e !== -1)
|
|
377
|
+
return !1;
|
|
378
|
+
const i = this.config.editOn ?? t.effectiveConfig?.editOn;
|
|
379
|
+
if (i === !1 || i === "manual") return !1;
|
|
380
|
+
const s = t._focusRow;
|
|
381
|
+
return s >= 0 && t._columns?.some((l) => l.editable) ? (this.beginBulkEdit(s), !0) : !1;
|
|
382
|
+
}
|
|
383
|
+
return !1;
|
|
384
|
+
}
|
|
385
|
+
// #endregion
|
|
386
|
+
// #region Render Hooks
|
|
387
|
+
/**
|
|
388
|
+
* After render, reapply editors to cells in edit mode.
|
|
389
|
+
* This handles virtualization - when a row scrolls back into view,
|
|
390
|
+
* we need to re-inject the editor.
|
|
391
|
+
*/
|
|
392
|
+
afterRender() {
|
|
393
|
+
const e = this.grid;
|
|
394
|
+
if (this.#o && (this.#o = !1, this.#u(e)), this.#n.size !== 0)
|
|
395
|
+
for (const t of this.#n) {
|
|
396
|
+
const [i, s] = t.split(":"), n = parseInt(i, 10), l = parseInt(s, 10), r = e.findRenderedRowElement?.(n);
|
|
397
|
+
if (!r) continue;
|
|
398
|
+
const c = r.querySelector(`.cell[data-col="${l}"]`);
|
|
399
|
+
if (!c || c.classList.contains("editing")) continue;
|
|
400
|
+
const u = e._rows[n], a = e._visibleColumns[l];
|
|
401
|
+
u && a && this.#a(u, n, a, l, c, !0);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* On scroll render, reapply editors to recycled cells.
|
|
406
|
+
*/
|
|
407
|
+
onScrollRender() {
|
|
408
|
+
this.afterRender();
|
|
409
|
+
}
|
|
410
|
+
// #endregion
|
|
411
|
+
// #region Public API
|
|
412
|
+
/**
|
|
413
|
+
* Get all rows that have been modified.
|
|
414
|
+
*/
|
|
415
|
+
get changedRows() {
|
|
416
|
+
const e = this.grid;
|
|
417
|
+
return Array.from(this.#t).map((t) => e._rows[t]);
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Get indices of all modified rows.
|
|
421
|
+
*/
|
|
422
|
+
get changedRowIndices() {
|
|
423
|
+
return Array.from(this.#t);
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Get the currently active edit row index, or -1 if not editing.
|
|
427
|
+
*/
|
|
428
|
+
get activeEditRow() {
|
|
429
|
+
return this.#e;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Get the currently active edit column index, or -1 if not editing.
|
|
433
|
+
*/
|
|
434
|
+
get activeEditCol() {
|
|
435
|
+
return this.#r;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Check if a specific row is currently being edited.
|
|
439
|
+
*/
|
|
440
|
+
isRowEditing(e) {
|
|
441
|
+
return this.#e === e;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Check if a specific cell is currently being edited.
|
|
445
|
+
*/
|
|
446
|
+
isCellEditing(e, t) {
|
|
447
|
+
return this.#n.has(`${e}:${t}`);
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Check if a specific row has been modified.
|
|
451
|
+
*/
|
|
452
|
+
isRowChanged(e) {
|
|
453
|
+
return this.#t.has(e);
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Reset all change tracking.
|
|
457
|
+
*/
|
|
458
|
+
resetChangedRows(e) {
|
|
459
|
+
const t = this.changedRows, i = this.changedRowIndices;
|
|
460
|
+
this.#t.clear(), this.#l(), e || this.emit("changed-rows-reset", { rows: t, indices: i }), this.grid._rowPool?.forEach((n) => n.classList.remove("changed"));
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Programmatically begin editing a cell.
|
|
464
|
+
*/
|
|
465
|
+
beginCellEdit(e, t) {
|
|
466
|
+
const i = this.grid, s = i._visibleColumns.findIndex((c) => c.field === t);
|
|
467
|
+
if (s === -1 || !i._visibleColumns[s]?.editable) return;
|
|
468
|
+
const r = i.findRenderedRowElement?.(e)?.querySelector(`.cell[data-col="${s}"]`);
|
|
469
|
+
r && this.#f(e, s, r);
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Programmatically begin editing all editable cells in a row.
|
|
473
|
+
*/
|
|
474
|
+
beginBulkEdit(e) {
|
|
475
|
+
const t = this.grid;
|
|
476
|
+
if ((this.config.editOn ?? t.effectiveConfig?.editOn) === !1 || !t._columns?.some((r) => r.editable)) return;
|
|
477
|
+
const n = t.findRenderedRowElement?.(e);
|
|
478
|
+
if (!n) return;
|
|
479
|
+
const l = t._rows[e];
|
|
480
|
+
this.#d(e, l), Array.from(n.children).forEach((r, c) => {
|
|
481
|
+
const u = t._visibleColumns[c];
|
|
482
|
+
if (u?.editable) {
|
|
483
|
+
const a = r;
|
|
484
|
+
a.classList.contains("editing") || this.#a(l, e, u, c, a, !0);
|
|
485
|
+
}
|
|
486
|
+
}), setTimeout(() => {
|
|
487
|
+
let r = n.querySelector(`.cell[data-col="${t._focusCol}"]`);
|
|
488
|
+
if (r?.classList.contains("editing") || (r = n.querySelector(".cell.editing")), r?.classList.contains("editing")) {
|
|
489
|
+
const c = r.querySelector(C);
|
|
490
|
+
try {
|
|
491
|
+
c?.focus({ preventScroll: !0 });
|
|
492
|
+
} catch {
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}, 0);
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Commit the currently active row edit.
|
|
499
|
+
*/
|
|
500
|
+
commitActiveRowEdit() {
|
|
501
|
+
this.#e !== -1 && this.#i(this.#e, !1);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Cancel the currently active row edit.
|
|
505
|
+
*/
|
|
506
|
+
cancelActiveRowEdit() {
|
|
507
|
+
this.#e !== -1 && this.#i(this.#e, !0);
|
|
508
|
+
}
|
|
509
|
+
// #endregion
|
|
510
|
+
// #region Internal Methods
|
|
511
|
+
/**
|
|
512
|
+
* Begin editing a single cell.
|
|
513
|
+
*/
|
|
514
|
+
#f(e, t, i) {
|
|
515
|
+
const s = this.grid, n = s._rows[e], l = s._visibleColumns[t];
|
|
516
|
+
!n || !l?.editable || i.classList.contains("editing") || (this.#e !== e && this.#d(e, n), this.#r = t, this.#a(n, e, l, t, i, !1));
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Sync the internal grid state with the plugin's editing state.
|
|
520
|
+
*/
|
|
521
|
+
#l() {
|
|
522
|
+
const e = this.grid;
|
|
523
|
+
e._activeEditRows = this.#e, e._rowEditSnapshots = this.#s, e._changedRowIndices = this.#t;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Snapshot original row data and mark as editing.
|
|
527
|
+
*/
|
|
528
|
+
#d(e, t) {
|
|
529
|
+
this.#e !== e && (this.#s.set(e, { ...t }), this.#e = e, this.#l());
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Exit editing for a row.
|
|
533
|
+
*/
|
|
534
|
+
#i(e, t) {
|
|
535
|
+
if (this.#e !== e) return;
|
|
536
|
+
const i = this.grid, s = this.#s.get(e), n = i._rows[e], l = i.findRenderedRowElement?.(e);
|
|
537
|
+
if (!t && l && n && l.querySelectorAll(".cell.editing").forEach((c) => {
|
|
538
|
+
const u = Number(c.getAttribute("data-col"));
|
|
539
|
+
if (isNaN(u)) return;
|
|
540
|
+
const a = i._visibleColumns[u];
|
|
541
|
+
if (!a) return;
|
|
542
|
+
const g = c.querySelector("input,textarea,select");
|
|
543
|
+
if (g) {
|
|
544
|
+
let o;
|
|
545
|
+
g instanceof HTMLInputElement && g.type === "checkbox" ? o = g.checked : (o = g.value, a.type === "number" && o !== "" && (o = Number(o))), n[a.field] !== o && this.#c(e, a, o, n);
|
|
546
|
+
}
|
|
547
|
+
}), t && s && n)
|
|
548
|
+
Object.keys(s).forEach((r) => {
|
|
549
|
+
n[r] = s[r];
|
|
550
|
+
}), this.#t.delete(e);
|
|
551
|
+
else if (!t) {
|
|
552
|
+
const r = this.#t.has(e);
|
|
553
|
+
this.emit("row-commit", {
|
|
554
|
+
rowIndex: e,
|
|
555
|
+
row: n,
|
|
556
|
+
changed: r,
|
|
557
|
+
changedRows: this.changedRows,
|
|
558
|
+
changedRowIndices: this.changedRowIndices
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
this.#s.delete(e), this.#e = -1, this.#r = -1, this.#l();
|
|
562
|
+
for (const r of this.#n)
|
|
563
|
+
r.startsWith(`${e}:`) && this.#n.delete(r);
|
|
564
|
+
l && (l.querySelectorAll(".cell.editing").forEach((r) => {
|
|
565
|
+
r.classList.remove("editing"), S(r.parentElement);
|
|
566
|
+
}), this.requestRender()), this.#o = !0, l || (this.#u(i), this.#o = !1);
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Commit a single cell value change.
|
|
570
|
+
*/
|
|
571
|
+
#c(e, t, i, s) {
|
|
572
|
+
const n = t.field;
|
|
573
|
+
if (!b(n) || s[n] === i) return;
|
|
574
|
+
s[n] = i;
|
|
575
|
+
const r = !this.#t.has(e);
|
|
576
|
+
this.#t.add(e), this.#l();
|
|
577
|
+
const u = this.grid.findRenderedRowElement?.(e);
|
|
578
|
+
u && u.classList.add("changed"), this.emit("cell-commit", {
|
|
579
|
+
row: s,
|
|
580
|
+
field: n,
|
|
581
|
+
value: i,
|
|
582
|
+
rowIndex: e,
|
|
583
|
+
changedRows: this.changedRows,
|
|
584
|
+
changedRowIndices: this.changedRowIndices,
|
|
585
|
+
firstTimeForRow: r
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Inject an editor into a cell.
|
|
590
|
+
*/
|
|
591
|
+
#a(e, t, i, s, n, l) {
|
|
592
|
+
if (!i.editable || n.classList.contains("editing")) return;
|
|
593
|
+
const r = b(i.field) ? e[i.field] : void 0;
|
|
594
|
+
n.classList.add("editing"), this.#n.add(`${t}:${s}`);
|
|
595
|
+
const c = n.parentElement;
|
|
596
|
+
c && L(c);
|
|
597
|
+
let u = !1;
|
|
598
|
+
const a = (h) => {
|
|
599
|
+
u || this.#e === -1 || this.#c(t, i, h, e);
|
|
600
|
+
}, g = () => {
|
|
601
|
+
u = !0, b(i.field) && (e[i.field] = r);
|
|
602
|
+
}, o = document.createElement("div");
|
|
603
|
+
o.style.display = "contents", n.innerHTML = "", n.appendChild(o), o.addEventListener("keydown", (h) => {
|
|
604
|
+
h.key === "Enter" && (h.stopPropagation(), h.preventDefault(), u = !0, this.#i(t, !1)), h.key === "Escape" && (h.stopPropagation(), h.preventDefault(), g(), this.#i(t, !0));
|
|
605
|
+
});
|
|
606
|
+
const p = i, v = p.__editorTemplate, d = p.editor || (v ? "template" : k(i)), m = r;
|
|
607
|
+
if (d === "template" && v)
|
|
608
|
+
this.#h(o, p, e, r, a, g, l, t);
|
|
609
|
+
else if (typeof d == "string") {
|
|
610
|
+
const h = document.createElement(d);
|
|
611
|
+
h.value = m, h.addEventListener("change", () => a(h.value)), o.appendChild(h), l || queueMicrotask(() => {
|
|
612
|
+
o.querySelector(C)?.focus({ preventScroll: !0 });
|
|
613
|
+
});
|
|
614
|
+
} else if (typeof d == "function") {
|
|
615
|
+
const h = { row: e, value: m, field: i.field, column: i, commit: a, cancel: g }, E = d(h);
|
|
616
|
+
typeof E == "string" ? (o.innerHTML = E, A(o, i, a)) : E instanceof Node && o.appendChild(E), l || queueMicrotask(() => {
|
|
617
|
+
o.querySelector(C)?.focus({ preventScroll: !0 });
|
|
618
|
+
});
|
|
619
|
+
} else if (d && typeof d == "object") {
|
|
620
|
+
const h = this.grid, E = document.createElement("div");
|
|
621
|
+
E.setAttribute("data-external-editor", ""), E.setAttribute("data-field", i.field), o.appendChild(E);
|
|
622
|
+
const y = { row: e, value: m, field: i.field, column: i, commit: a, cancel: g };
|
|
623
|
+
if (d.mount)
|
|
624
|
+
try {
|
|
625
|
+
d.mount({ placeholder: E, context: y, spec: d });
|
|
626
|
+
} catch (w) {
|
|
627
|
+
console.warn(`[tbw-grid] External editor mount error for column '${i.field}':`, w);
|
|
628
|
+
}
|
|
629
|
+
else
|
|
630
|
+
h.dispatchEvent(
|
|
631
|
+
new CustomEvent("mount-external-editor", { detail: { placeholder: E, spec: d, context: y } })
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Render a template-based editor.
|
|
637
|
+
*/
|
|
638
|
+
#h(e, t, i, s, n, l, r, c) {
|
|
639
|
+
const u = t.__editorTemplate;
|
|
640
|
+
if (!u) return;
|
|
641
|
+
const a = u.cloneNode(!0), g = t.__compiledEditor;
|
|
642
|
+
g ? a.innerHTML = g({
|
|
643
|
+
row: i,
|
|
644
|
+
value: s,
|
|
645
|
+
field: t.field,
|
|
646
|
+
column: t,
|
|
647
|
+
commit: n,
|
|
648
|
+
cancel: l
|
|
649
|
+
}) : a.querySelectorAll("*").forEach((p) => {
|
|
650
|
+
p.childNodes.length === 1 && p.firstChild?.nodeType === Node.TEXT_NODE && (p.textContent = p.textContent?.replace(/{{\s*value\s*}}/g, s == null ? "" : String(s)).replace(/{{\s*row\.([a-zA-Z0-9_]+)\s*}}/g, (v, d) => {
|
|
651
|
+
if (!b(d)) return "";
|
|
652
|
+
const m = i[d];
|
|
653
|
+
return m == null ? "" : String(m);
|
|
654
|
+
}) || "");
|
|
655
|
+
});
|
|
656
|
+
const o = a.querySelector(
|
|
657
|
+
"input,textarea,select"
|
|
658
|
+
);
|
|
659
|
+
if (o) {
|
|
660
|
+
o instanceof HTMLInputElement && o.type === "checkbox" ? o.checked = !!s : o.value = String(s ?? "");
|
|
661
|
+
let p = !1;
|
|
662
|
+
o.addEventListener("blur", () => {
|
|
663
|
+
if (p) return;
|
|
664
|
+
const v = o instanceof HTMLInputElement && o.type === "checkbox" ? o.checked : o.value;
|
|
665
|
+
n(v);
|
|
666
|
+
}), o.addEventListener("keydown", (v) => {
|
|
667
|
+
const d = v;
|
|
668
|
+
if (d.key === "Enter") {
|
|
669
|
+
d.stopPropagation(), d.preventDefault(), p = !0;
|
|
670
|
+
const m = o instanceof HTMLInputElement && o.type === "checkbox" ? o.checked : o.value;
|
|
671
|
+
n(m), this.#i(c, !1);
|
|
672
|
+
}
|
|
673
|
+
d.key === "Escape" && (d.stopPropagation(), d.preventDefault(), l(), this.#i(c, !0));
|
|
674
|
+
}), o instanceof HTMLInputElement && o.type === "checkbox" && o.addEventListener("change", () => n(o.checked)), r || setTimeout(() => o.focus({ preventScroll: !0 }), 0);
|
|
675
|
+
}
|
|
676
|
+
e.appendChild(a);
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Restore focus to cell after exiting edit mode.
|
|
680
|
+
*/
|
|
681
|
+
#u(e) {
|
|
682
|
+
queueMicrotask(() => {
|
|
683
|
+
try {
|
|
684
|
+
const t = e._focusRow, i = e._focusCol, s = e.findRenderedRowElement?.(t);
|
|
685
|
+
if (s) {
|
|
686
|
+
Array.from(e._bodyEl.querySelectorAll(".cell-focus")).forEach(
|
|
687
|
+
(l) => l.classList.remove("cell-focus")
|
|
688
|
+
);
|
|
689
|
+
const n = s.querySelector(`.cell[data-row="${t}"][data-col="${i}"]`);
|
|
690
|
+
n && (n.classList.add("cell-focus"), n.setAttribute("aria-selected", "true"), n.hasAttribute("tabindex") || n.setAttribute("tabindex", "-1"), n.focus({ preventScroll: !0 }));
|
|
691
|
+
}
|
|
692
|
+
} catch {
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
// #endregion
|
|
697
|
+
}
|
|
698
|
+
export {
|
|
699
|
+
q as EditingPlugin,
|
|
700
|
+
C as FOCUSABLE_EDITOR_SELECTOR,
|
|
701
|
+
S as clearEditingState,
|
|
702
|
+
k as defaultEditorFor,
|
|
703
|
+
T as hasEditingCells
|
|
704
|
+
};
|
|
705
|
+
//# sourceMappingURL=index.js.map
|