@vueye-table/vue 3.0.0-alpha.1 → 3.0.0-alpha.10
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/dist/index.d.ts +123 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +828 -36
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { computed, getCurrentScope, inject, onScopeDispose, provide, shallowRef, toRaw, toValue, watch } from "vue";
|
|
2
|
-
import { clampPosition, createTable, gridCommand, moveSelection, rangeContains, selectCell, selectionRange } from "@vueye-table/core";
|
|
1
|
+
import { computed, getCurrentInstance, getCurrentScope, inject, nextTick, onMounted, onScopeDispose, provide, shallowReactive, shallowRef, toRaw, toValue, unref, watch, watchEffect } from "vue";
|
|
2
|
+
import { clampPosition, createTable, createVirtualizer, formatValue, getRowItemKey, gridCommand, moveSelection, rangeContains, selectCell, selectionRange, typeOf } from "@vueye-table/core";
|
|
3
3
|
|
|
4
4
|
//#region src/injection.ts
|
|
5
5
|
const tableKey = Symbol("vueye-table");
|
|
@@ -25,19 +25,193 @@ function injectDataGrid() {
|
|
|
25
25
|
return inject(gridKey, void 0);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/row-draft.ts
|
|
30
|
+
function rejectedDraft(rowKey, message) {
|
|
31
|
+
return refused([Object.freeze({
|
|
32
|
+
code: "stale_draft",
|
|
33
|
+
rowKey,
|
|
34
|
+
message
|
|
35
|
+
})]);
|
|
36
|
+
}
|
|
37
|
+
function refused(issues) {
|
|
38
|
+
return Object.freeze({
|
|
39
|
+
status: "rejected",
|
|
40
|
+
changes: Object.freeze([]),
|
|
41
|
+
rowChanges: Object.freeze([]),
|
|
42
|
+
pendingCells: Object.freeze([]),
|
|
43
|
+
issues: Object.freeze([...issues])
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function equal(left, right, seen = /* @__PURE__ */ new WeakMap()) {
|
|
47
|
+
if (Object.is(left, right)) return true;
|
|
48
|
+
if (!left || !right || typeof left !== "object" || typeof right !== "object") return false;
|
|
49
|
+
if (left instanceof Date || right instanceof Date) return left instanceof Date && right instanceof Date && Object.is(left.getTime(), right.getTime());
|
|
50
|
+
if (Array.isArray(left) && Array.isArray(right) && left.length !== right.length) return false;
|
|
51
|
+
if (Object.getPrototypeOf(left) !== Object.getPrototypeOf(right)) return false;
|
|
52
|
+
if (seen.has(left)) return seen.get(left) === right;
|
|
53
|
+
seen.set(left, right);
|
|
54
|
+
if (left instanceof Map && right instanceof Map) return equal([...left], [...right], seen);
|
|
55
|
+
if (left instanceof Set && right instanceof Set) return equal([...left], [...right], seen);
|
|
56
|
+
if (left instanceof RegExp && right instanceof RegExp) return left.source === right.source && left.flags === right.flags;
|
|
57
|
+
if (left instanceof ArrayBuffer && right instanceof ArrayBuffer) return equal([...new Uint8Array(left)], [...new Uint8Array(right)], seen);
|
|
58
|
+
if (ArrayBuffer.isView(left) && ArrayBuffer.isView(right)) return equal([...new Uint8Array(left.buffer, left.byteOffset, left.byteLength)], [...new Uint8Array(right.buffer, right.byteOffset, right.byteLength)], seen);
|
|
59
|
+
const keys = Object.keys(left);
|
|
60
|
+
return keys.length === Object.keys(right).length && keys.every((key) => Object.hasOwn(right, key) && equal(Reflect.get(left, key), Reflect.get(right, key), seen));
|
|
61
|
+
}
|
|
62
|
+
/** Internal factory; disposal is shared with the binding's single engine subscription. */
|
|
63
|
+
function createRowDraft(table, key, register) {
|
|
64
|
+
const snapshot = table.getSnapshot();
|
|
65
|
+
const original = snapshot.getRow(key)?.original;
|
|
66
|
+
const columns = snapshot.allColumns;
|
|
67
|
+
const values = shallowReactive(Object.create(null));
|
|
68
|
+
const initial = /* @__PURE__ */ new Map();
|
|
69
|
+
const inputs = /* @__PURE__ */ new Map();
|
|
70
|
+
const copyIssues = /* @__PURE__ */ new Map();
|
|
71
|
+
const issues = shallowRef([]);
|
|
72
|
+
const status = shallowRef("editing");
|
|
73
|
+
for (const column of columns) {
|
|
74
|
+
if (original === void 0) break;
|
|
75
|
+
try {
|
|
76
|
+
if (!column.isEditable(original)) continue;
|
|
77
|
+
const value = column.getValue(original);
|
|
78
|
+
initial.set(column.id, value);
|
|
79
|
+
values[column.id] = structuredClone(toRaw(value));
|
|
80
|
+
} catch {
|
|
81
|
+
copyIssues.set(column.id, Object.freeze({
|
|
82
|
+
code: "invalid_value",
|
|
83
|
+
rowKey: key,
|
|
84
|
+
column: column.id,
|
|
85
|
+
message: "This value cannot be copied into a row draft. Replace it explicitly before saving."
|
|
86
|
+
}));
|
|
87
|
+
issues.value = [...copyIssues.values()];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
let saving;
|
|
91
|
+
let disposePending;
|
|
92
|
+
let disposed = false;
|
|
93
|
+
const stale = () => rejectedDraft(key, "This draft is closed, or its row or columns changed. Open a new draft.");
|
|
94
|
+
const unregister = register(() => {
|
|
95
|
+
disposed = true;
|
|
96
|
+
const result = stale();
|
|
97
|
+
status.value = "stale";
|
|
98
|
+
issues.value = result.issues;
|
|
99
|
+
disposePending?.(result);
|
|
100
|
+
});
|
|
101
|
+
return Object.freeze({
|
|
102
|
+
rowKey: key,
|
|
103
|
+
values,
|
|
104
|
+
get issues() {
|
|
105
|
+
return issues.value;
|
|
106
|
+
},
|
|
107
|
+
get status() {
|
|
108
|
+
return status.value;
|
|
109
|
+
},
|
|
110
|
+
get pending() {
|
|
111
|
+
return status.value === "saving";
|
|
112
|
+
},
|
|
113
|
+
setInput(column, input) {
|
|
114
|
+
if (status.value === "editing") inputs.set(column, input);
|
|
115
|
+
},
|
|
116
|
+
setValue(column, value) {
|
|
117
|
+
if (status.value === "editing") {
|
|
118
|
+
values[column] = value;
|
|
119
|
+
inputs.delete(column);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
save() {
|
|
123
|
+
if (saving) return saving;
|
|
124
|
+
if (status.value !== "editing" || disposed || original === void 0 || table.getSnapshot().getRow(key)?.original !== original || columns.some((column) => table.getSnapshot().getColumn(column.id)?.definition !== column.definition)) {
|
|
125
|
+
const result = stale();
|
|
126
|
+
issues.value = result.issues;
|
|
127
|
+
status.value = "stale";
|
|
128
|
+
unregister();
|
|
129
|
+
return Promise.resolve(result);
|
|
130
|
+
}
|
|
131
|
+
const edits = [];
|
|
132
|
+
const uncopied = [...copyIssues.values()].filter((problem) => !Object.hasOwn(values, problem.column) && !inputs.has(problem.column));
|
|
133
|
+
if (uncopied.length) {
|
|
134
|
+
issues.value = Object.freeze(uncopied);
|
|
135
|
+
return Promise.resolve(refused(issues.value));
|
|
136
|
+
}
|
|
137
|
+
for (const column of /* @__PURE__ */ new Set([...Object.keys(values), ...inputs.keys()])) try {
|
|
138
|
+
const value = toRaw(values[column]);
|
|
139
|
+
if (inputs.has(column)) edits.push({
|
|
140
|
+
rowKey: key,
|
|
141
|
+
column,
|
|
142
|
+
input: inputs.get(column)
|
|
143
|
+
});
|
|
144
|
+
else if (!initial.has(column) || !equal(value, initial.get(column))) edits.push(typeof value === "string" ? {
|
|
145
|
+
rowKey: key,
|
|
146
|
+
column,
|
|
147
|
+
input: value
|
|
148
|
+
} : {
|
|
149
|
+
rowKey: key,
|
|
150
|
+
column,
|
|
151
|
+
value: value !== null && typeof value === "object" ? structuredClone(value) : value
|
|
152
|
+
});
|
|
153
|
+
} catch {
|
|
154
|
+
issues.value = Object.freeze([Object.freeze({
|
|
155
|
+
code: "invalid_value",
|
|
156
|
+
rowKey: key,
|
|
157
|
+
column,
|
|
158
|
+
message: "This draft value cannot be copied for submission. Replace it before saving."
|
|
159
|
+
})]);
|
|
160
|
+
return Promise.resolve(refused(issues.value));
|
|
161
|
+
}
|
|
162
|
+
status.value = "saving";
|
|
163
|
+
const result = table.edit(edits, { expectedRows: /* @__PURE__ */ new Map([[key, original]]) });
|
|
164
|
+
if (disposed) return Promise.resolve(stale());
|
|
165
|
+
const cancelled = new Promise((resolve) => {
|
|
166
|
+
disposePending = resolve;
|
|
167
|
+
});
|
|
168
|
+
saving = Promise.race([result.completion ?? Promise.resolve(result), cancelled]).then((final) => {
|
|
169
|
+
issues.value = final.issues;
|
|
170
|
+
status.value = disposed || final.issues.some((problem) => problem.code === "stale_draft") ? "stale" : final.status === "rejected" ? "editing" : "saved";
|
|
171
|
+
if (status.value !== "editing") unregister();
|
|
172
|
+
saving = void 0;
|
|
173
|
+
disposePending = void 0;
|
|
174
|
+
return final;
|
|
175
|
+
});
|
|
176
|
+
return saving;
|
|
177
|
+
},
|
|
178
|
+
cancel() {
|
|
179
|
+
if (status.value !== "editing") return false;
|
|
180
|
+
status.value = "cancelled";
|
|
181
|
+
unregister();
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
28
187
|
//#endregion
|
|
29
188
|
//#region src/use-data-grid.ts
|
|
30
|
-
function useDataGrid(table) {
|
|
189
|
+
function useDataGrid(table, gridOptions = {}) {
|
|
31
190
|
const selection = shallowRef();
|
|
32
191
|
const editor = shallowRef();
|
|
33
192
|
const lastResult = shallowRef();
|
|
193
|
+
let editing;
|
|
194
|
+
let resultToken;
|
|
195
|
+
const invalidEditors = /* @__PURE__ */ new WeakSet();
|
|
196
|
+
const editorSpecs = /* @__PURE__ */ new WeakMap();
|
|
197
|
+
let submitting;
|
|
198
|
+
const record = (result) => {
|
|
199
|
+
const token = {};
|
|
200
|
+
resultToken = token;
|
|
201
|
+
lastResult.value = result;
|
|
202
|
+
result.completion?.then((final) => {
|
|
203
|
+
if (resultToken === token) lastResult.value = final;
|
|
204
|
+
});
|
|
205
|
+
return result;
|
|
206
|
+
};
|
|
34
207
|
const bounds = computed(() => ({
|
|
35
208
|
rows: table.rows.length,
|
|
36
209
|
columns: table.columns.length
|
|
37
210
|
}));
|
|
38
211
|
const range = computed(() => selection.value ? selectionRange(selection.value) : void 0);
|
|
39
212
|
const same = (left, right) => left !== void 0 && left.row === right.row && left.column === right.column;
|
|
40
|
-
watch(
|
|
213
|
+
watch(() => table.snapshot, () => {
|
|
214
|
+
const next = bounds.value;
|
|
41
215
|
const current = selection.value;
|
|
42
216
|
if (current && (next.rows === 0 || next.columns === 0)) selection.value = void 0;
|
|
43
217
|
else if (current) {
|
|
@@ -48,9 +222,32 @@ function useDataGrid(table) {
|
|
|
48
222
|
focus
|
|
49
223
|
};
|
|
50
224
|
}
|
|
51
|
-
const
|
|
52
|
-
if (
|
|
53
|
-
|
|
225
|
+
const active = editing;
|
|
226
|
+
if (active && editor.value) {
|
|
227
|
+
const row = table.rows.findIndex((candidate) => candidate.key === active.key);
|
|
228
|
+
const column = table.columns.findIndex((candidate) => candidate.id === active.column.id);
|
|
229
|
+
if (row < 0 || column < 0 || !submitting && table.getRow(active.key)?.original !== active.original || table.getColumn(active.column.id)?.definition !== active.column.definition) {
|
|
230
|
+
editor.value = void 0;
|
|
231
|
+
editing = void 0;
|
|
232
|
+
record(rejectedDraft(active.key, "The edited cell changed or left the visible grid. Start a new edit."));
|
|
233
|
+
} else if (!same(editor.value.position, {
|
|
234
|
+
row,
|
|
235
|
+
column
|
|
236
|
+
})) {
|
|
237
|
+
editor.value = {
|
|
238
|
+
...editor.value,
|
|
239
|
+
position: {
|
|
240
|
+
row,
|
|
241
|
+
column
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
selection.value = selectCell({
|
|
245
|
+
row,
|
|
246
|
+
column
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}, { flush: "sync" });
|
|
54
251
|
const cellAt = (position) => {
|
|
55
252
|
const row = table.rows[position.row];
|
|
56
253
|
const column = table.columns[position.column];
|
|
@@ -68,6 +265,7 @@ function useDataGrid(table) {
|
|
|
68
265
|
return selection.value;
|
|
69
266
|
};
|
|
70
267
|
const grid = {
|
|
268
|
+
table,
|
|
71
269
|
get selection() {
|
|
72
270
|
return selection.value;
|
|
73
271
|
},
|
|
@@ -82,10 +280,60 @@ function useDataGrid(table) {
|
|
|
82
280
|
},
|
|
83
281
|
bounds,
|
|
84
282
|
cellAt,
|
|
283
|
+
editorFor(position) {
|
|
284
|
+
const cell = cellAt(position);
|
|
285
|
+
if (!cell || !cell.column.isEditable(cell.row.original)) return void 0;
|
|
286
|
+
const spec = cell.column.definition.editor;
|
|
287
|
+
const cached = editorSpecs.get(cell.column.definition);
|
|
288
|
+
if (cached) return cached;
|
|
289
|
+
if (spec && [
|
|
290
|
+
"text",
|
|
291
|
+
"number",
|
|
292
|
+
"select",
|
|
293
|
+
"checkbox",
|
|
294
|
+
"date"
|
|
295
|
+
].includes(spec.kind) && (spec.options === void 0 || Array.isArray(spec.options) && spec.options.every((value) => value === null || [
|
|
296
|
+
"string",
|
|
297
|
+
"number",
|
|
298
|
+
"boolean"
|
|
299
|
+
].includes(typeof value)))) {
|
|
300
|
+
const resolved = Object.freeze({
|
|
301
|
+
kind: spec.kind,
|
|
302
|
+
...spec.options ? { options: Object.freeze([...spec.options]) } : {},
|
|
303
|
+
...spec.min !== void 0 ? { min: spec.min } : {},
|
|
304
|
+
...spec.max !== void 0 ? { max: spec.max } : {},
|
|
305
|
+
...spec.maxLength !== void 0 ? { maxLength: spec.maxLength } : {},
|
|
306
|
+
...spec.pattern !== void 0 ? { pattern: spec.pattern } : {}
|
|
307
|
+
});
|
|
308
|
+
editorSpecs.set(cell.column.definition, resolved);
|
|
309
|
+
return resolved;
|
|
310
|
+
}
|
|
311
|
+
if (spec) {
|
|
312
|
+
if (!invalidEditors.has(cell.column.definition)) {
|
|
313
|
+
invalidEditors.add(cell.column.definition);
|
|
314
|
+
record(Object.freeze({
|
|
315
|
+
status: "rejected",
|
|
316
|
+
changes: Object.freeze([]),
|
|
317
|
+
rowChanges: Object.freeze([]),
|
|
318
|
+
pendingCells: Object.freeze([]),
|
|
319
|
+
issues: Object.freeze([Object.freeze({
|
|
320
|
+
code: "invalid_value",
|
|
321
|
+
rowKey: cell.row.key,
|
|
322
|
+
column: cell.column.id,
|
|
323
|
+
message: "Invalid editor metadata; text metadata is used."
|
|
324
|
+
})])
|
|
325
|
+
}));
|
|
326
|
+
}
|
|
327
|
+
return Object.freeze({ kind: "text" });
|
|
328
|
+
}
|
|
329
|
+
const type = cell.column.definition.type ?? typeOf(cell.row.getValue(cell.column.id)) ?? "text";
|
|
330
|
+
return Object.freeze({ kind: type === "boolean" ? "checkbox" : type });
|
|
331
|
+
},
|
|
85
332
|
isFocused: (position) => same(selection.value?.focus, position),
|
|
86
333
|
isSelected: (position) => range.value ? rangeContains(range.value, position) : false,
|
|
87
334
|
isEditing: (position) => same(editor.value?.position, position),
|
|
88
335
|
focusCell(position, { extend = false } = {}) {
|
|
336
|
+
if (submitting) return;
|
|
89
337
|
const target = clampPosition(position, bounds.value);
|
|
90
338
|
if (editor.value && !same(editor.value.position, target)) grid.commitEdit();
|
|
91
339
|
selection.value = extend && selection.value ? {
|
|
@@ -110,12 +358,21 @@ function useDataGrid(table) {
|
|
|
110
358
|
};
|
|
111
359
|
},
|
|
112
360
|
startEdit(initial) {
|
|
361
|
+
if (submitting) return false;
|
|
113
362
|
const focus = ensureSelection()?.focus;
|
|
114
363
|
const cell = focus ? cellAt(focus) : void 0;
|
|
115
364
|
if (!focus || !cell || !cell.column.isEditable(cell.row.original)) return false;
|
|
365
|
+
const value = cell.row.getValue(cell.column.id);
|
|
366
|
+
const kind = grid.editorFor(focus)?.kind;
|
|
367
|
+
const text = kind === "date" && value instanceof Date ? Number.isFinite(value.getTime()) ? value.toISOString().slice(0, 10) : "" : kind && kind !== "text" ? value === null || value === void 0 ? "" : formatValue(value) : cell.row.getDisplay(cell.column.id);
|
|
116
368
|
editor.value = {
|
|
117
369
|
position: focus,
|
|
118
|
-
draft: initial ??
|
|
370
|
+
draft: initial ?? text
|
|
371
|
+
};
|
|
372
|
+
editing = {
|
|
373
|
+
key: cell.row.key,
|
|
374
|
+
column: cell.column,
|
|
375
|
+
original: cell.row.original
|
|
119
376
|
};
|
|
120
377
|
return true;
|
|
121
378
|
},
|
|
@@ -128,43 +385,104 @@ function useDataGrid(table) {
|
|
|
128
385
|
commitEdit(then) {
|
|
129
386
|
const current = editor.value;
|
|
130
387
|
if (!current) return;
|
|
131
|
-
const
|
|
388
|
+
const active = editing;
|
|
132
389
|
editor.value = void 0;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
390
|
+
editing = void 0;
|
|
391
|
+
if (!active) return;
|
|
392
|
+
const result = record(table.edit({
|
|
393
|
+
rowKey: active.key,
|
|
394
|
+
column: active.column.id,
|
|
137
395
|
input: current.draft
|
|
138
|
-
});
|
|
139
|
-
lastResult.value = result;
|
|
396
|
+
}, { expectedRows: /* @__PURE__ */ new Map([[active.key, active.original]]) }));
|
|
140
397
|
if (then) grid.move(then);
|
|
141
398
|
return result;
|
|
142
399
|
},
|
|
400
|
+
submitEdit(edit, then) {
|
|
401
|
+
const current = editor.value;
|
|
402
|
+
const active = editing;
|
|
403
|
+
if (!current || !active || submitting) return void 0;
|
|
404
|
+
const token = {};
|
|
405
|
+
submitting = token;
|
|
406
|
+
const cell = {
|
|
407
|
+
rowKey: active.key,
|
|
408
|
+
column: active.column.id,
|
|
409
|
+
...edit ?? { input: current.draft }
|
|
410
|
+
};
|
|
411
|
+
const result = record(table.edit(cell, { expectedRows: /* @__PURE__ */ new Map([[active.key, active.original]]) }));
|
|
412
|
+
const settle = (final) => {
|
|
413
|
+
if (submitting !== token) return;
|
|
414
|
+
submitting = void 0;
|
|
415
|
+
if (editing !== active) return;
|
|
416
|
+
if (final.status !== "rejected" || final.issues.some((problem) => problem.code === "stale_draft")) {
|
|
417
|
+
editor.value = void 0;
|
|
418
|
+
editing = void 0;
|
|
419
|
+
if (then) grid.move(then);
|
|
420
|
+
} else editing = {
|
|
421
|
+
...active,
|
|
422
|
+
original: table.getRow(active.key)?.original ?? active.original
|
|
423
|
+
};
|
|
424
|
+
};
|
|
425
|
+
if (result.completion) result.completion.then(settle);
|
|
426
|
+
else settle(result);
|
|
427
|
+
return result;
|
|
428
|
+
},
|
|
143
429
|
cancelEdit() {
|
|
144
430
|
editor.value = void 0;
|
|
431
|
+
editing = void 0;
|
|
432
|
+
submitting = void 0;
|
|
145
433
|
},
|
|
146
|
-
|
|
147
|
-
|
|
434
|
+
toggleCheckbox() {
|
|
435
|
+
const focus = ensureSelection()?.focus;
|
|
436
|
+
const cell = focus ? cellAt(focus) : void 0;
|
|
437
|
+
if (!focus || !cell || grid.editorFor(focus)?.kind !== "checkbox") return false;
|
|
438
|
+
record(table.edit({
|
|
439
|
+
rowKey: cell.row.key,
|
|
440
|
+
column: cell.column.id,
|
|
441
|
+
value: cell.row.getValue(cell.column.id) !== true
|
|
442
|
+
}, { expectedRows: /* @__PURE__ */ new Map([[cell.row.key, cell.row.original]]) }));
|
|
443
|
+
return true;
|
|
444
|
+
},
|
|
445
|
+
copy(options) {
|
|
446
|
+
return range.value ? table.copy(range.value, options) : "";
|
|
148
447
|
},
|
|
149
448
|
paste(text) {
|
|
150
449
|
if (!range.value) return;
|
|
151
|
-
|
|
450
|
+
return record(table.paste({
|
|
152
451
|
row: range.value.top,
|
|
153
452
|
column: range.value.left
|
|
154
|
-
}, text);
|
|
155
|
-
lastResult.value = result;
|
|
156
|
-
return result;
|
|
453
|
+
}, text));
|
|
157
454
|
},
|
|
158
455
|
clear() {
|
|
159
456
|
if (!range.value) return;
|
|
160
|
-
|
|
161
|
-
lastResult.value = result;
|
|
162
|
-
return result;
|
|
457
|
+
return record(table.clear(range.value));
|
|
163
458
|
},
|
|
164
459
|
handleKey(event) {
|
|
165
460
|
if (editor.value) return false;
|
|
166
|
-
const
|
|
461
|
+
const current = selection.value?.focus ?? {
|
|
462
|
+
row: 0,
|
|
463
|
+
column: 0
|
|
464
|
+
};
|
|
465
|
+
const row = table.rows[current.row];
|
|
466
|
+
const treeColumn = table.columns.find((column) => column.id === gridOptions.treeColumn?.()) ?? table.columns[0];
|
|
467
|
+
const command = gridCommand(event, table.tree && row && table.columns[current.column] === treeColumn ? {
|
|
468
|
+
canExpand: row.canExpand,
|
|
469
|
+
expanded: row.isExpanded
|
|
470
|
+
} : void 0);
|
|
167
471
|
switch (command?.type) {
|
|
472
|
+
case "tree": {
|
|
473
|
+
if (!row) return false;
|
|
474
|
+
let key = row.key;
|
|
475
|
+
if (command.action === "expand" || command.action === "collapse") table.toggleExpanded(key, command.action === "expand");
|
|
476
|
+
else if (command.action === "siblings") for (const sibling of table.processedRows.filter((candidate) => candidate.parentKey === row.parentKey && candidate.canExpand)) table.toggleExpanded(sibling.key, true);
|
|
477
|
+
else if (command.action === "child") key = table.rows.find((candidate) => candidate.parentKey === row.key)?.key ?? key;
|
|
478
|
+
else key = row.parentKey ?? key;
|
|
479
|
+
const index = table.rows.findIndex((candidate) => candidate.key === key);
|
|
480
|
+
if (index >= 0) grid.focusCell({
|
|
481
|
+
row: index,
|
|
482
|
+
column: current.column
|
|
483
|
+
});
|
|
484
|
+
return true;
|
|
485
|
+
}
|
|
168
486
|
case "move":
|
|
169
487
|
grid.move(command.direction, command.options);
|
|
170
488
|
return true;
|
|
@@ -187,9 +505,437 @@ function useDataGrid(table) {
|
|
|
187
505
|
return grid;
|
|
188
506
|
}
|
|
189
507
|
|
|
508
|
+
//#endregion
|
|
509
|
+
//#region src/use-virtual.ts
|
|
510
|
+
function useVirtualAxis(source, keyOf, estimate, options, horizontal, project) {
|
|
511
|
+
let items = source();
|
|
512
|
+
let mounted = false;
|
|
513
|
+
let disposed = false;
|
|
514
|
+
let element;
|
|
515
|
+
let observer;
|
|
516
|
+
let detach;
|
|
517
|
+
const elements = /* @__PURE__ */ new Map();
|
|
518
|
+
const keysByElement = /* @__PURE__ */ new Map();
|
|
519
|
+
const requestedCount = options.initialCount ?? 10;
|
|
520
|
+
const initialCount = Number.isSafeInteger(requestedCount) && requestedCount >= 0 ? requestedCount : 10;
|
|
521
|
+
const initialIssue = initialCount === requestedCount ? void 0 : Object.freeze({
|
|
522
|
+
code: "invalid_virtual_option",
|
|
523
|
+
message: "Initial count must be a non-negative safe integer; 10 is used."
|
|
524
|
+
});
|
|
525
|
+
const layoutOptions = () => ({
|
|
526
|
+
count: items.length,
|
|
527
|
+
estimateSize: estimate,
|
|
528
|
+
overscan: mounted ? options.overscan : 0,
|
|
529
|
+
getKey: (index) => keyOf(items[index])
|
|
530
|
+
});
|
|
531
|
+
const virtual = createVirtualizer(layoutOptions());
|
|
532
|
+
function seed() {
|
|
533
|
+
let size = 0;
|
|
534
|
+
for (let index = 0; index < Math.min(initialCount, items.length); index += 1) {
|
|
535
|
+
const value = typeof estimate === "number" ? estimate : estimate(index);
|
|
536
|
+
size += Number.isFinite(value) && value > 0 ? value : 40;
|
|
537
|
+
}
|
|
538
|
+
virtual.setViewport(0, size);
|
|
539
|
+
}
|
|
540
|
+
seed();
|
|
541
|
+
const view = shallowRef(virtual.getWindow());
|
|
542
|
+
const projected = computed(() => Object.freeze(view.value.items.map((item) => Object.freeze(project(item, items[item.index])))));
|
|
543
|
+
const unsubscribe = virtual.subscribe((next) => {
|
|
544
|
+
view.value = next;
|
|
545
|
+
});
|
|
546
|
+
const marginIssue = shallowRef();
|
|
547
|
+
function margin() {
|
|
548
|
+
const value = toValue(options.scrollMargin) ?? 0;
|
|
549
|
+
if (Number.isFinite(value) && value >= 0) {
|
|
550
|
+
marginIssue.value = void 0;
|
|
551
|
+
return value;
|
|
552
|
+
}
|
|
553
|
+
marginIssue.value ??= Object.freeze({
|
|
554
|
+
code: "invalid_virtual_option",
|
|
555
|
+
message: "Scroll margin must be non-negative and finite; 0 is used."
|
|
556
|
+
});
|
|
557
|
+
return 0;
|
|
558
|
+
}
|
|
559
|
+
function viewportSize() {
|
|
560
|
+
return element ? Math.max(0, (horizontal ? element.clientWidth : element.clientHeight) - margin()) : virtual.getWindow().viewportSize;
|
|
561
|
+
}
|
|
562
|
+
function move(offset) {
|
|
563
|
+
if (element) {
|
|
564
|
+
if (horizontal) element.scrollLeft = offset === 0 ? 0 : offset + margin();
|
|
565
|
+
else element.scrollTop = offset === 0 ? 0 : offset + margin();
|
|
566
|
+
}
|
|
567
|
+
virtual.setViewport(offset, viewportSize());
|
|
568
|
+
}
|
|
569
|
+
function anchor() {
|
|
570
|
+
const current = virtual.getWindow();
|
|
571
|
+
const first = current.items.find((item) => item.start + item.size > current.offset);
|
|
572
|
+
return first ? {
|
|
573
|
+
key: first.key,
|
|
574
|
+
inset: current.offset - first.start
|
|
575
|
+
} : void 0;
|
|
576
|
+
}
|
|
577
|
+
function restore(saved, fallback) {
|
|
578
|
+
const index = saved ? items.findIndex((item) => keyOf(item) === saved.key) : -1;
|
|
579
|
+
move(index >= 0 && saved ? virtual.getOffsetForIndex(index, "start") + saved.inset : fallback);
|
|
580
|
+
if (element) {
|
|
581
|
+
const offset = virtual.getWindow().offset;
|
|
582
|
+
if (horizontal) element.scrollLeft = offset === 0 ? 0 : offset + margin();
|
|
583
|
+
else element.scrollTop = offset === 0 ? 0 : offset + margin();
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
function measure(target, key) {
|
|
587
|
+
const saved = anchor();
|
|
588
|
+
const offset = virtual.getWindow().offset;
|
|
589
|
+
const box = target.getBoundingClientRect();
|
|
590
|
+
const size = horizontal ? box.width : box.height;
|
|
591
|
+
if (size > 0 && Number.isFinite(size)) {
|
|
592
|
+
const previous = virtual.getWindow();
|
|
593
|
+
virtual.measure(key, size);
|
|
594
|
+
if (virtual.getWindow() === previous) return;
|
|
595
|
+
restore(saved, offset);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function readViewport() {
|
|
599
|
+
if (element) virtual.setViewport(Math.max(0, (horizontal ? element.scrollLeft : element.scrollTop) - margin()), viewportSize());
|
|
600
|
+
}
|
|
601
|
+
const stopSource = watch(source, (next) => {
|
|
602
|
+
const saved = anchor();
|
|
603
|
+
const offset = virtual.getWindow().offset;
|
|
604
|
+
items = next;
|
|
605
|
+
virtual.setOptions(layoutOptions());
|
|
606
|
+
if (mounted) restore(saved, offset);
|
|
607
|
+
else seed();
|
|
608
|
+
}, { flush: "sync" });
|
|
609
|
+
let stopElement;
|
|
610
|
+
if (getCurrentInstance()) onMounted(() => {
|
|
611
|
+
mounted = true;
|
|
612
|
+
virtual.setOptions(layoutOptions());
|
|
613
|
+
stopElement = watch(() => toValue(options.scrollElement), (next) => {
|
|
614
|
+
detach?.();
|
|
615
|
+
observer?.disconnect();
|
|
616
|
+
observer = void 0;
|
|
617
|
+
element = next;
|
|
618
|
+
if (!next) {
|
|
619
|
+
seed();
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
const owner = next.ownerDocument.defaultView;
|
|
623
|
+
const Observer = owner?.ResizeObserver;
|
|
624
|
+
if (Observer) {
|
|
625
|
+
observer = new Observer((entries) => {
|
|
626
|
+
for (const entry of entries) if (entry.target === element) readViewport();
|
|
627
|
+
else {
|
|
628
|
+
const key = keysByElement.get(entry.target);
|
|
629
|
+
if (key !== void 0) measure(entry.target, key);
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
observer.observe(next);
|
|
633
|
+
for (const target of elements.values()) observer.observe(target);
|
|
634
|
+
} else owner?.addEventListener("resize", readViewport);
|
|
635
|
+
next.addEventListener("scroll", readViewport, { passive: true });
|
|
636
|
+
detach = () => {
|
|
637
|
+
next.removeEventListener("scroll", readViewport);
|
|
638
|
+
owner?.removeEventListener("resize", readViewport);
|
|
639
|
+
};
|
|
640
|
+
readViewport();
|
|
641
|
+
for (const [key, target] of elements) measure(target, key);
|
|
642
|
+
}, {
|
|
643
|
+
immediate: true,
|
|
644
|
+
flush: "post"
|
|
645
|
+
});
|
|
646
|
+
});
|
|
647
|
+
if (getCurrentScope()) onScopeDispose(() => {
|
|
648
|
+
disposed = true;
|
|
649
|
+
stopSource();
|
|
650
|
+
stopElement?.();
|
|
651
|
+
detach?.();
|
|
652
|
+
observer?.disconnect();
|
|
653
|
+
unsubscribe();
|
|
654
|
+
elements.clear();
|
|
655
|
+
keysByElement.clear();
|
|
656
|
+
element = void 0;
|
|
657
|
+
});
|
|
658
|
+
margin();
|
|
659
|
+
const binding = {
|
|
660
|
+
getItem(index) {
|
|
661
|
+
view.value;
|
|
662
|
+
const item = virtual.getItem(index);
|
|
663
|
+
return item ? Object.freeze(project(item, items[index])) : void 0;
|
|
664
|
+
},
|
|
665
|
+
scrollToIndex(index, { align = "auto" } = {}) {
|
|
666
|
+
move(virtual.getOffsetForIndex(index, align));
|
|
667
|
+
},
|
|
668
|
+
scrollToKey(key, align) {
|
|
669
|
+
const index = items.findIndex((item) => keyOf(item) === key);
|
|
670
|
+
if (index >= 0) binding.scrollToIndex(index, align);
|
|
671
|
+
},
|
|
672
|
+
measureElement(target, key) {
|
|
673
|
+
if (disposed) return;
|
|
674
|
+
const previous = elements.get(key);
|
|
675
|
+
if (previous === target) return;
|
|
676
|
+
if (previous) {
|
|
677
|
+
observer?.unobserve(previous);
|
|
678
|
+
keysByElement.delete(previous);
|
|
679
|
+
elements.delete(key);
|
|
680
|
+
}
|
|
681
|
+
if (!target || !("getBoundingClientRect" in target)) return;
|
|
682
|
+
elements.set(key, target);
|
|
683
|
+
keysByElement.set(target, key);
|
|
684
|
+
if (mounted) {
|
|
685
|
+
observer?.observe(target);
|
|
686
|
+
nextTick(() => {
|
|
687
|
+
if (!disposed && elements.get(key) === target) measure(target, key);
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
for (const key of Object.keys(view.value)) Object.defineProperty(binding, key, {
|
|
693
|
+
enumerable: true,
|
|
694
|
+
get: () => key === "items" ? projected.value : key === "issues" && (initialIssue || marginIssue.value) ? Object.freeze([
|
|
695
|
+
...view.value.issues,
|
|
696
|
+
...initialIssue ? [initialIssue] : [],
|
|
697
|
+
...marginIssue.value ? [marginIssue.value] : []
|
|
698
|
+
]) : view.value[key]
|
|
699
|
+
});
|
|
700
|
+
return Object.freeze(binding);
|
|
701
|
+
}
|
|
702
|
+
/** Virtualize data and detail render items. scrollToKey accepts the parent data-row key. */
|
|
703
|
+
function useVirtualRows(table, options) {
|
|
704
|
+
const virtual = useVirtualAxis(() => table.renderItems, (item) => item.key, options.estimateRowHeight ?? 40, options, false, (item, renderItem) => ({
|
|
705
|
+
...item,
|
|
706
|
+
renderItem
|
|
707
|
+
}));
|
|
708
|
+
const stopFocus = watch(() => options.grid?.selection?.focus.row, (row) => {
|
|
709
|
+
if (row === void 0) return;
|
|
710
|
+
const direct = table.renderItems[row];
|
|
711
|
+
const index = direct?.kind === "row" && direct.rowIndex === row ? row : table.renderItems.findIndex((item) => item.kind === "row" && item.rowIndex === row);
|
|
712
|
+
if (index >= 0) virtual.scrollToIndex(index);
|
|
713
|
+
}, { flush: "sync" });
|
|
714
|
+
if (getCurrentScope()) onScopeDispose(stopFocus);
|
|
715
|
+
const binding = Object.defineProperties({}, {
|
|
716
|
+
...Object.getOwnPropertyDescriptors(virtual),
|
|
717
|
+
scrollToKey: {
|
|
718
|
+
enumerable: true,
|
|
719
|
+
value: (key, align) => virtual.scrollToKey(getRowItemKey(key), align)
|
|
720
|
+
}
|
|
721
|
+
});
|
|
722
|
+
return Object.freeze(binding);
|
|
723
|
+
}
|
|
724
|
+
/** Virtualize visible columns and bring the active logical column into view. */
|
|
725
|
+
function useVirtualColumns(grid, options) {
|
|
726
|
+
const virtual = useVirtualAxis(() => grid.table.columns, (column) => column.id, options.estimateColumnWidth ?? 120, options, true, (item, column) => ({
|
|
727
|
+
...item,
|
|
728
|
+
column
|
|
729
|
+
}));
|
|
730
|
+
const stopFocus = watch(() => grid.selection?.focus.column, (column) => {
|
|
731
|
+
if (column !== void 0) virtual.scrollToIndex(column);
|
|
732
|
+
}, { flush: "sync" });
|
|
733
|
+
if (getCurrentScope()) onScopeDispose(stopFocus);
|
|
734
|
+
return virtual;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
//#endregion
|
|
738
|
+
//#region src/source.ts
|
|
739
|
+
/** Internal owner of one cancellable source, using the engine's ingestion/conflict boundary. */
|
|
740
|
+
function createSource(table, options, seed, publish) {
|
|
741
|
+
const instance = getCurrentInstance();
|
|
742
|
+
const requestedThreshold = options.endThreshold ?? 5;
|
|
743
|
+
const endThreshold = Number.isSafeInteger(requestedThreshold) && requestedThreshold >= 0 ? requestedThreshold : 5;
|
|
744
|
+
const optionIssues = requestedThreshold === endThreshold ? [] : [Object.freeze({
|
|
745
|
+
code: "invalid_stream_option",
|
|
746
|
+
message: "endThreshold must be a non-negative safe integer; 5 is used."
|
|
747
|
+
})];
|
|
748
|
+
const mode = shallowRef(void 0);
|
|
749
|
+
const more = shallowRef(false);
|
|
750
|
+
const retryVersion = shallowRef(0);
|
|
751
|
+
const query = shallowRef(table.getState());
|
|
752
|
+
let cursor;
|
|
753
|
+
let controller;
|
|
754
|
+
let task;
|
|
755
|
+
let cancelFrame;
|
|
756
|
+
let pending;
|
|
757
|
+
let active = false;
|
|
758
|
+
let disposed = false;
|
|
759
|
+
let mounted = !instance;
|
|
760
|
+
let first = true;
|
|
761
|
+
let cursorFailed = false;
|
|
762
|
+
let generation = 0;
|
|
763
|
+
let stops = [];
|
|
764
|
+
const initialMode = () => unref(options.source) ? "source" : unref(options.loadMore) ? "cursor" : void 0;
|
|
765
|
+
const schedule = options.scheduleFrame ?? ((flush) => {
|
|
766
|
+
const view = (instance?.proxy?.$el)?.ownerDocument?.defaultView;
|
|
767
|
+
if (view) {
|
|
768
|
+
const id = view.requestAnimationFrame(flush);
|
|
769
|
+
return () => view.cancelAnimationFrame(id);
|
|
770
|
+
}
|
|
771
|
+
const id = setTimeout(flush, 16);
|
|
772
|
+
return () => clearTimeout(id);
|
|
773
|
+
});
|
|
774
|
+
const project = (next) => {
|
|
775
|
+
const loadState = !mounted && initialMode() ? "loading" : mode.value === "cursor" && more.value && next.loadState === "done" ? "idle" : next.loadState;
|
|
776
|
+
return loadState === next.loadState && optionIssues.length === 0 ? next : Object.freeze({
|
|
777
|
+
...next,
|
|
778
|
+
loadState,
|
|
779
|
+
issues: Object.freeze([...next.issues, ...optionIssues])
|
|
780
|
+
});
|
|
781
|
+
};
|
|
782
|
+
const flush = () => {
|
|
783
|
+
cancelFrame = void 0;
|
|
784
|
+
if (pending && !disposed) {
|
|
785
|
+
active = pending.loadState === "loading" || pending.loadState === "streaming";
|
|
786
|
+
publish(project(pending));
|
|
787
|
+
}
|
|
788
|
+
pending = void 0;
|
|
789
|
+
};
|
|
790
|
+
const update = (next) => {
|
|
791
|
+
const state = next.state;
|
|
792
|
+
const previous = query.value;
|
|
793
|
+
if (previous.sorting !== state.sorting || previous.search !== state.search || previous.filters !== state.filters) {
|
|
794
|
+
query.value = state;
|
|
795
|
+
next = table.getSnapshot();
|
|
796
|
+
}
|
|
797
|
+
if (active) {
|
|
798
|
+
pending = next;
|
|
799
|
+
cancelFrame ??= schedule(flush);
|
|
800
|
+
} else publish(project(next));
|
|
801
|
+
};
|
|
802
|
+
const stop = () => {
|
|
803
|
+
controller?.abort();
|
|
804
|
+
controller = void 0;
|
|
805
|
+
task = void 0;
|
|
806
|
+
active = false;
|
|
807
|
+
cancelFrame?.();
|
|
808
|
+
cancelFrame = void 0;
|
|
809
|
+
pending = void 0;
|
|
810
|
+
};
|
|
811
|
+
const reset = () => {
|
|
812
|
+
stop();
|
|
813
|
+
cursor = void 0;
|
|
814
|
+
cursorFailed = false;
|
|
815
|
+
if (!first) table.setData(seed());
|
|
816
|
+
first = false;
|
|
817
|
+
};
|
|
818
|
+
const loadNext = () => {
|
|
819
|
+
if (disposed || !mounted || mode.value !== "cursor" || !more.value || cursorFailed) return Promise.resolve();
|
|
820
|
+
if (task) return task;
|
|
821
|
+
const loader = unref(options.loadMore);
|
|
822
|
+
const run = new AbortController();
|
|
823
|
+
controller = run;
|
|
824
|
+
active = true;
|
|
825
|
+
const state = table.getState();
|
|
826
|
+
const previousCursor = cursor;
|
|
827
|
+
const source = { async *[Symbol.asyncIterator]() {
|
|
828
|
+
const result = await loader({
|
|
829
|
+
cursor: previousCursor,
|
|
830
|
+
state,
|
|
831
|
+
signal: run.signal
|
|
832
|
+
});
|
|
833
|
+
if (run.signal.aborted) return;
|
|
834
|
+
if (!Array.isArray(result.rows) || typeof result.done !== "boolean") throw new Error("loadMore must return rows and a boolean done flag.");
|
|
835
|
+
if (!result.done && result.rows.length === 0 && Object.is(result.cursor, previousCursor)) throw new Error("loadMore returned no rows or cursor progress. Retry after correcting the response.");
|
|
836
|
+
cursor = result.cursor;
|
|
837
|
+
more.value = !result.done;
|
|
838
|
+
yield result.rows;
|
|
839
|
+
} };
|
|
840
|
+
task = table.stream(source, {
|
|
841
|
+
...options.streamOptions,
|
|
842
|
+
signal: run.signal
|
|
843
|
+
}).then((result) => {
|
|
844
|
+
if (controller !== run || disposed) return void 0;
|
|
845
|
+
cursorFailed = result.status === "error";
|
|
846
|
+
task = void 0;
|
|
847
|
+
if (!cancelFrame) active = false;
|
|
848
|
+
});
|
|
849
|
+
return task;
|
|
850
|
+
};
|
|
851
|
+
const start = () => {
|
|
852
|
+
if (disposed) return;
|
|
853
|
+
mounted = true;
|
|
854
|
+
stops = [watchEffect((cleanup) => {
|
|
855
|
+
retryVersion.value;
|
|
856
|
+
const source = unref(options.source);
|
|
857
|
+
const loader = unref(options.loadMore);
|
|
858
|
+
const run = new AbortController();
|
|
859
|
+
const ticket = ++generation;
|
|
860
|
+
cleanup(() => run.abort());
|
|
861
|
+
let iterable;
|
|
862
|
+
if (source) {
|
|
863
|
+
try {
|
|
864
|
+
iterable = typeof source === "function" ? source({ signal: run.signal }) : source;
|
|
865
|
+
} catch (error) {
|
|
866
|
+
iterable = { [Symbol.asyncIterator]: () => ({ next: () => Promise.reject(error) }) };
|
|
867
|
+
}
|
|
868
|
+
if (loader) iterable = { [Symbol.asyncIterator]: () => ({ next: () => Promise.reject(/* @__PURE__ */ new Error("Choose source or loadMore, rather than combining them.")) }) };
|
|
869
|
+
}
|
|
870
|
+
queueMicrotask(() => {
|
|
871
|
+
if (disposed || run.signal.aborted || ticket !== generation) return;
|
|
872
|
+
reset();
|
|
873
|
+
mode.value = source ? "source" : loader ? "cursor" : void 0;
|
|
874
|
+
more.value = !!loader && !source;
|
|
875
|
+
if (iterable) {
|
|
876
|
+
controller = run;
|
|
877
|
+
active = true;
|
|
878
|
+
table.stream(iterable, {
|
|
879
|
+
...options.streamOptions,
|
|
880
|
+
signal: run.signal
|
|
881
|
+
}).then(() => {
|
|
882
|
+
if (controller === run && !cancelFrame) active = false;
|
|
883
|
+
});
|
|
884
|
+
} else if (loader) loadNext();
|
|
885
|
+
else update(table.getSnapshot());
|
|
886
|
+
});
|
|
887
|
+
}), watch(query, () => {
|
|
888
|
+
if (mode.value !== "cursor") return;
|
|
889
|
+
reset();
|
|
890
|
+
more.value = true;
|
|
891
|
+
loadNext();
|
|
892
|
+
}, { flush: "sync" })];
|
|
893
|
+
};
|
|
894
|
+
if (instance) onMounted(start);
|
|
895
|
+
else queueMicrotask(start);
|
|
896
|
+
return {
|
|
897
|
+
endThreshold,
|
|
898
|
+
get loadingMode() {
|
|
899
|
+
return mode.value ?? (!mounted ? initialMode() : void 0);
|
|
900
|
+
},
|
|
901
|
+
get canLoadMore() {
|
|
902
|
+
return more.value && !cursorFailed && !task;
|
|
903
|
+
},
|
|
904
|
+
get loadError() {
|
|
905
|
+
const next = table.getSnapshot();
|
|
906
|
+
return next.loadState === "error" ? next.issues.find((item) => item.code === "stream_error") : void 0;
|
|
907
|
+
},
|
|
908
|
+
loadNext,
|
|
909
|
+
retry() {
|
|
910
|
+
if (disposed) return;
|
|
911
|
+
if (mode.value === "cursor" && cursorFailed) {
|
|
912
|
+
cursorFailed = false;
|
|
913
|
+
loadNext();
|
|
914
|
+
} else retryVersion.value++;
|
|
915
|
+
},
|
|
916
|
+
update,
|
|
917
|
+
initial: () => project(table.getSnapshot()),
|
|
918
|
+
dispose() {
|
|
919
|
+
disposed = true;
|
|
920
|
+
stop();
|
|
921
|
+
for (const disposeWatch of stops) disposeWatch();
|
|
922
|
+
stops = [];
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
|
|
190
927
|
//#endregion
|
|
191
928
|
//#region src/use-data-table.ts
|
|
192
929
|
const OPERATIONS = [
|
|
930
|
+
"appendData",
|
|
931
|
+
"upsertData",
|
|
932
|
+
"removeData",
|
|
933
|
+
"stream",
|
|
934
|
+
"insertRows",
|
|
935
|
+
"removeRows",
|
|
936
|
+
"getPendingChanges",
|
|
937
|
+
"markSaved",
|
|
938
|
+
"revert",
|
|
193
939
|
"setState",
|
|
194
940
|
"setData",
|
|
195
941
|
"setColumns",
|
|
@@ -210,6 +956,9 @@ const OPERATIONS = [
|
|
|
210
956
|
"toggleAll",
|
|
211
957
|
"clearSelection",
|
|
212
958
|
"getSelectedRows",
|
|
959
|
+
"toggleExpanded",
|
|
960
|
+
"expandAll",
|
|
961
|
+
"collapseAll",
|
|
213
962
|
"toggleColumn",
|
|
214
963
|
"moveColumn",
|
|
215
964
|
"edit",
|
|
@@ -225,27 +974,31 @@ const OPERATIONS = [
|
|
|
225
974
|
function definedEntries(patch) {
|
|
226
975
|
return Object.fromEntries(Object.entries(patch ?? {}).filter(([, value]) => value !== void 0));
|
|
227
976
|
}
|
|
228
|
-
/**
|
|
229
|
-
* Create a table bound to the current effect scope. The table follows its reactive data,
|
|
230
|
-
* columns, row count, and state, and stops listening when the scope ends.
|
|
231
|
-
*/
|
|
232
977
|
function useDataTable(options) {
|
|
978
|
+
const empty = Object.freeze([]);
|
|
979
|
+
const seed = () => toRaw(toValue(options.data) ?? empty);
|
|
233
980
|
const table = createTable({
|
|
234
981
|
...options,
|
|
235
|
-
|
|
982
|
+
manual: options.manual ?? !!unref(options.loadMore),
|
|
983
|
+
data: seed(),
|
|
236
984
|
columns: toValue(options.columns),
|
|
237
985
|
rowCount: toValue(options.rowCount),
|
|
238
986
|
initialState: {
|
|
239
987
|
...options.initialState,
|
|
240
988
|
...definedEntries(toValue(options.state))
|
|
241
|
-
}
|
|
989
|
+
},
|
|
990
|
+
createChildLoadController: options.createChildLoadController ?? (() => new AbortController())
|
|
242
991
|
});
|
|
992
|
+
const drafts = /* @__PURE__ */ new Set();
|
|
993
|
+
let disposed = false;
|
|
243
994
|
const snapshot = shallowRef(table.getSnapshot());
|
|
244
|
-
const
|
|
995
|
+
const source = createSource(table, options, seed, (next) => {
|
|
245
996
|
snapshot.value = next;
|
|
246
997
|
});
|
|
998
|
+
snapshot.value = source.initial();
|
|
999
|
+
const unsubscribe = table.subscribe(source.update);
|
|
247
1000
|
const stops = [
|
|
248
|
-
watch(() => toValue(options.data), (data) => table.setData(toRaw(data))),
|
|
1001
|
+
watch(() => toValue(options.data), (data) => table.setData(toRaw(data ?? empty))),
|
|
249
1002
|
watch(() => toValue(options.columns), (columns) => table.setColumns(columns)),
|
|
250
1003
|
watch(() => toValue(options.rowCount), (rowCount) => {
|
|
251
1004
|
if (rowCount !== void 0) table.setRowCount(rowCount);
|
|
@@ -254,24 +1007,63 @@ function useDataTable(options) {
|
|
|
254
1007
|
if (state) table.setState(state);
|
|
255
1008
|
}, { deep: true })
|
|
256
1009
|
];
|
|
257
|
-
|
|
1010
|
+
const dispose = () => {
|
|
1011
|
+
if (disposed) return;
|
|
1012
|
+
disposed = true;
|
|
1013
|
+
source.dispose();
|
|
1014
|
+
for (const disposeDraft of drafts) disposeDraft();
|
|
1015
|
+
drafts.clear();
|
|
258
1016
|
for (const stop of stops) stop();
|
|
259
1017
|
unsubscribe();
|
|
260
1018
|
table.destroy();
|
|
261
|
-
}
|
|
1019
|
+
};
|
|
1020
|
+
if (getCurrentScope()) onScopeDispose(dispose);
|
|
262
1021
|
const binding = { table };
|
|
1022
|
+
for (const key of [
|
|
1023
|
+
"endThreshold",
|
|
1024
|
+
"loadingMode",
|
|
1025
|
+
"canLoadMore",
|
|
1026
|
+
"loadError"
|
|
1027
|
+
]) Object.defineProperty(binding, key, {
|
|
1028
|
+
enumerable: true,
|
|
1029
|
+
get: () => {
|
|
1030
|
+
snapshot.value;
|
|
1031
|
+
return source[key];
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
binding["retry"] = source.retry;
|
|
1035
|
+
binding["loadNext"] = source.loadNext;
|
|
263
1036
|
Object.defineProperty(binding, "snapshot", {
|
|
264
1037
|
enumerable: true,
|
|
265
1038
|
get: () => snapshot.value
|
|
266
1039
|
});
|
|
1040
|
+
Object.defineProperty(binding, "expanded", {
|
|
1041
|
+
enumerable: true,
|
|
1042
|
+
get: () => snapshot.value.state.expanded
|
|
1043
|
+
});
|
|
1044
|
+
Object.defineProperty(binding, "pendingChanges", {
|
|
1045
|
+
enumerable: true,
|
|
1046
|
+
get: () => {
|
|
1047
|
+
snapshot.value;
|
|
1048
|
+
return table.getPendingChanges();
|
|
1049
|
+
}
|
|
1050
|
+
});
|
|
1051
|
+
binding["editRow"] = (key) => createRowDraft(table, key, (disposeDraft) => {
|
|
1052
|
+
if (disposed) disposeDraft();
|
|
1053
|
+
else drafts.add(disposeDraft);
|
|
1054
|
+
return () => {
|
|
1055
|
+
drafts.delete(disposeDraft);
|
|
1056
|
+
};
|
|
1057
|
+
});
|
|
267
1058
|
for (const key of Object.keys(snapshot.value)) Object.defineProperty(binding, key, {
|
|
268
1059
|
enumerable: true,
|
|
269
1060
|
get: () => snapshot.value[key]
|
|
270
1061
|
});
|
|
271
1062
|
for (const name of OPERATIONS) binding[name] = table[name];
|
|
1063
|
+
binding["destroy"] = dispose;
|
|
272
1064
|
return Object.freeze(binding);
|
|
273
1065
|
}
|
|
274
1066
|
|
|
275
1067
|
//#endregion
|
|
276
|
-
export { injectDataGrid, injectDataTable, provideDataGrid, provideDataTable, useDataGrid, useDataTable };
|
|
1068
|
+
export { injectDataGrid, injectDataTable, provideDataGrid, provideDataTable, useDataGrid, useDataTable, useVirtualColumns, useVirtualRows };
|
|
277
1069
|
//# sourceMappingURL=index.js.map
|