@vueye-table/core 3.0.0-alpha.1 → 3.0.0-alpha.3
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 +4 -0
- package/dist/index.d.ts +184 -69
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1009 -101
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,13 +8,34 @@ function humanize(id) {
|
|
|
8
8
|
return sentence.charAt(0).toUpperCase() + sentence.slice(1);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/issues.ts
|
|
13
|
+
function issue(code, message, details = {}) {
|
|
14
|
+
return Object.freeze({
|
|
15
|
+
code,
|
|
16
|
+
message,
|
|
17
|
+
...details
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
//#endregion
|
|
12
22
|
//#region src/path.ts
|
|
23
|
+
/**
|
|
24
|
+
* Dotted property paths.
|
|
25
|
+
*
|
|
26
|
+
* A column addresses nested data with a path such as `"address.city"`. The types below give every
|
|
27
|
+
* such path its value type, and the functions read and write through a path without mutating.
|
|
28
|
+
*/
|
|
13
29
|
function isRecord$1(value) {
|
|
14
30
|
return typeof value === "object" && value !== null;
|
|
15
31
|
}
|
|
32
|
+
/** Whether every path segment can be read or written without addressing a prototype. */
|
|
33
|
+
function isSafePath(path) {
|
|
34
|
+
return path.split(".").every((segment) => segment !== "__proto__" && segment !== "constructor" && segment !== "prototype");
|
|
35
|
+
}
|
|
16
36
|
/** Read the value at a dotted path, or `undefined` when a step is missing. */
|
|
17
37
|
function getPath(source, path) {
|
|
38
|
+
if (!isSafePath(path)) return;
|
|
18
39
|
let current = source;
|
|
19
40
|
for (const segment of path.split(".")) {
|
|
20
41
|
if (!isRecord$1(current)) return;
|
|
@@ -24,13 +45,18 @@ function getPath(source, path) {
|
|
|
24
45
|
}
|
|
25
46
|
/**
|
|
26
47
|
* Return a copy of `source` with `value` at `path`. Only the objects along the path are copied;
|
|
27
|
-
* everything else is shared. Missing steps are created as plain objects.
|
|
48
|
+
* everything else is shared. Missing steps are created as plain objects. Unsafe paths return
|
|
49
|
+
* `source` unchanged and report an `unsafe_path` issue through `onIssue`, when supplied.
|
|
28
50
|
*/
|
|
29
|
-
function setPath(source, path, value) {
|
|
51
|
+
function setPath(source, path, value, onIssue) {
|
|
52
|
+
if (!isSafePath(path)) {
|
|
53
|
+
onIssue?.(issue("unsafe_path", `The path "${path}" addresses a prototype and cannot be written.`, { column: path }));
|
|
54
|
+
return source;
|
|
55
|
+
}
|
|
30
56
|
const [head, ...rest] = path.split(".");
|
|
31
57
|
const base = isRecord$1(source) ? { ...source } : {};
|
|
32
58
|
if (head === void 0) return source;
|
|
33
|
-
base[head] = rest.length === 0 ? value : setPath(base[head], rest.join("."), value);
|
|
59
|
+
base[head] = rest.length === 0 ? value : setPath(Object.hasOwn(base, head) ? base[head] : void 0, rest.join("."), value);
|
|
34
60
|
return base;
|
|
35
61
|
}
|
|
36
62
|
|
|
@@ -197,14 +223,15 @@ function parseAs(input, type) {
|
|
|
197
223
|
function resolveColumn(definition) {
|
|
198
224
|
const def = definition;
|
|
199
225
|
const { accessor } = def;
|
|
200
|
-
const
|
|
226
|
+
const safe = isSafePath(def.id);
|
|
227
|
+
const getValue = safe ? accessor ?? ((row) => getPath(row, def.id)) : () => void 0;
|
|
201
228
|
const format = def.format ?? ((value) => formatValue(value));
|
|
202
229
|
const compare = def.compare ?? compareValues;
|
|
203
230
|
const customFilter = def.filter;
|
|
204
231
|
const customParse = def.parse;
|
|
205
232
|
const editable = def.editable ?? false;
|
|
206
233
|
const customSetValue = def.setValue;
|
|
207
|
-
const canWrite = accessor === void 0 || customSetValue !== void 0;
|
|
234
|
+
const canWrite = safe && (accessor === void 0 || customSetValue !== void 0);
|
|
208
235
|
return Object.freeze({
|
|
209
236
|
id: def.id,
|
|
210
237
|
header: def.header ?? humanize(def.id),
|
|
@@ -241,6 +268,7 @@ function resolveColumn(definition) {
|
|
|
241
268
|
return parseAs(input, def.type ?? typeOf(getValue(row)) ?? typeOf(sample) ?? "text");
|
|
242
269
|
},
|
|
243
270
|
setValue(row, value) {
|
|
271
|
+
if (!safe) return;
|
|
244
272
|
if (customSetValue) return customSetValue(row, value);
|
|
245
273
|
return accessor === void 0 ? setPath(row, def.id, value) : void 0;
|
|
246
274
|
}
|
|
@@ -258,6 +286,10 @@ function resolveColumn(definition) {
|
|
|
258
286
|
function quote(field, delimiter) {
|
|
259
287
|
return field.includes(delimiter) || field.includes("\"") || field.includes("\n") || field.includes("\r") ? `"${field.replaceAll("\"", "\"\"")}"` : field;
|
|
260
288
|
}
|
|
289
|
+
/** Escape formula-like display text; a finite number formatted as a number stays numeric. */
|
|
290
|
+
function escapeFormula(text, value) {
|
|
291
|
+
return !(typeof value === "number" && Number.isFinite(value) && /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/u.test(text)) && /^[=+\-@\t\r\n=+-@]/u.test(text) ? "'" + text : text;
|
|
292
|
+
}
|
|
261
293
|
/** Write a matrix of text as delimited lines joined with `\n`. */
|
|
262
294
|
function toDelimited(matrix, delimiter = " ") {
|
|
263
295
|
return matrix.map((line) => line.map((field) => quote(field, delimiter)).join(delimiter)).join("\n");
|
|
@@ -267,39 +299,92 @@ function toDelimited(matrix, delimiter = " ") {
|
|
|
267
299
|
* and embedded line breaks, and ignores one trailing line break.
|
|
268
300
|
*/
|
|
269
301
|
function parseDelimited(text, delimiter = " ") {
|
|
302
|
+
return readDelimited(text, {
|
|
303
|
+
maxRows: Infinity,
|
|
304
|
+
maxColumns: Infinity,
|
|
305
|
+
maxCells: Infinity,
|
|
306
|
+
maxLength: Infinity
|
|
307
|
+
}, delimiter).rows;
|
|
308
|
+
}
|
|
309
|
+
/** Read complete fields within a grid and work budget, without materializing discarded fields. */
|
|
310
|
+
function readDelimited(text, limits, delimiter = " ") {
|
|
270
311
|
const rows = [];
|
|
312
|
+
if (limits.maxRows === 0 || limits.maxColumns === 0) return {
|
|
313
|
+
rows,
|
|
314
|
+
truncated: text.length > 0
|
|
315
|
+
};
|
|
271
316
|
let row = [];
|
|
272
317
|
let field = "";
|
|
273
318
|
let quoted = false;
|
|
319
|
+
let started = false;
|
|
274
320
|
let index = 0;
|
|
321
|
+
let column = 0;
|
|
322
|
+
let cells = 0;
|
|
323
|
+
let truncated = false;
|
|
324
|
+
const end = Math.min(text.length, limits.maxLength);
|
|
275
325
|
const endField = () => {
|
|
276
|
-
row.push(field);
|
|
326
|
+
if (column < limits.maxColumns) row.push(field);
|
|
327
|
+
else truncated = true;
|
|
328
|
+
column += 1;
|
|
329
|
+
cells += 1;
|
|
277
330
|
field = "";
|
|
331
|
+
started = false;
|
|
278
332
|
};
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
rows.push(row);
|
|
282
|
-
row = [];
|
|
333
|
+
const append = (char) => {
|
|
334
|
+
if (column < limits.maxColumns) field += char;
|
|
283
335
|
};
|
|
284
|
-
while (index <
|
|
336
|
+
while (index < end) {
|
|
285
337
|
const char = text.charAt(index);
|
|
286
338
|
if (quoted) {
|
|
287
339
|
if (char === "\"") {
|
|
288
|
-
if (text.charAt(index + 1) === "\"") {
|
|
289
|
-
|
|
340
|
+
if (index + 1 < end && text.charAt(index + 1) === "\"") {
|
|
341
|
+
append("\"");
|
|
290
342
|
index += 1;
|
|
291
343
|
} else quoted = false;
|
|
292
|
-
} else
|
|
293
|
-
} else if (char === "\"" &&
|
|
294
|
-
else if (char === delimiter)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
344
|
+
} else append(char);
|
|
345
|
+
} else if (char === "\"" && !started) quoted = true;
|
|
346
|
+
else if (char === delimiter) {
|
|
347
|
+
endField();
|
|
348
|
+
index += 1;
|
|
349
|
+
if (cells >= limits.maxCells || rows.length + 1 >= limits.maxRows && column >= limits.maxColumns) {
|
|
350
|
+
rows.push(row);
|
|
351
|
+
return {
|
|
352
|
+
rows,
|
|
353
|
+
truncated: true
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
continue;
|
|
357
|
+
} else if (char === "\n" || char === "\r") {
|
|
358
|
+
if (char === "\r" && index + 1 < end && text.charAt(index + 1) === "\n") index += 1;
|
|
359
|
+
endField();
|
|
360
|
+
rows.push(row);
|
|
361
|
+
row = [];
|
|
362
|
+
column = 0;
|
|
363
|
+
index += 1;
|
|
364
|
+
if (cells >= limits.maxCells || rows.length >= limits.maxRows) return {
|
|
365
|
+
rows,
|
|
366
|
+
truncated: truncated || index < text.length
|
|
367
|
+
};
|
|
368
|
+
continue;
|
|
369
|
+
} else append(char);
|
|
370
|
+
started = true;
|
|
299
371
|
index += 1;
|
|
300
372
|
}
|
|
301
|
-
if (
|
|
302
|
-
|
|
373
|
+
if (end < text.length) {
|
|
374
|
+
if (row.length > 0) rows.push(row);
|
|
375
|
+
return {
|
|
376
|
+
rows,
|
|
377
|
+
truncated: true
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
if (started || column > 0) {
|
|
381
|
+
endField();
|
|
382
|
+
rows.push(row);
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
rows,
|
|
386
|
+
truncated
|
|
387
|
+
};
|
|
303
388
|
}
|
|
304
389
|
|
|
305
390
|
//#endregion
|
|
@@ -466,6 +551,196 @@ function createUndoStack(limit = 100) {
|
|
|
466
551
|
};
|
|
467
552
|
}
|
|
468
553
|
|
|
554
|
+
//#endregion
|
|
555
|
+
//#region src/virtualizer.ts
|
|
556
|
+
const FALLBACK_SIZE = 40;
|
|
557
|
+
/** Layout arithmetic for either axis. All viewport values and measurements come from the caller. */
|
|
558
|
+
function createVirtualizer(initialOptions) {
|
|
559
|
+
let options = initialOptions;
|
|
560
|
+
let count = 0;
|
|
561
|
+
let overscan = 5;
|
|
562
|
+
let fixedSize;
|
|
563
|
+
let keys = [];
|
|
564
|
+
let byKey = /* @__PURE__ */ new Map();
|
|
565
|
+
let sizes = [];
|
|
566
|
+
let prefix = [0];
|
|
567
|
+
let offset = 0;
|
|
568
|
+
let viewportSize = 0;
|
|
569
|
+
let cached;
|
|
570
|
+
const measured = /* @__PURE__ */ new Map();
|
|
571
|
+
const problems = /* @__PURE__ */ new Map();
|
|
572
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
573
|
+
function recover(code, message) {
|
|
574
|
+
if (!problems.has(code)) {
|
|
575
|
+
const problem = issue(code, message);
|
|
576
|
+
problems.set(code, problem);
|
|
577
|
+
options.onIssue?.(problem);
|
|
578
|
+
cached = void 0;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
function validSize(value) {
|
|
582
|
+
if (Number.isFinite(value) && value > 0) return value;
|
|
583
|
+
recover("invalid_virtual_size", `Item size ${String(value)} is not positive and finite; 40 is used.`);
|
|
584
|
+
return FALLBACK_SIZE;
|
|
585
|
+
}
|
|
586
|
+
function configure(next) {
|
|
587
|
+
options = next;
|
|
588
|
+
problems.clear();
|
|
589
|
+
count = Number.isSafeInteger(next.count) && next.count >= 0 ? next.count : 0;
|
|
590
|
+
if (count !== next.count) recover("invalid_virtual_option", "Virtual item count must be a non-negative safe integer; 0 is used.");
|
|
591
|
+
const requestedOverscan = next.overscan ?? 5;
|
|
592
|
+
overscan = Number.isSafeInteger(requestedOverscan) && requestedOverscan >= 0 ? requestedOverscan : 5;
|
|
593
|
+
if (overscan !== requestedOverscan) recover("invalid_virtual_option", "Overscan must be a non-negative safe integer; 5 is used.");
|
|
594
|
+
const estimate = typeof next.estimateSize === "number" ? validSize(next.estimateSize) : void 0;
|
|
595
|
+
const nextKeys = [];
|
|
596
|
+
const nextSizes = [];
|
|
597
|
+
const nextByKey = /* @__PURE__ */ new Map();
|
|
598
|
+
let firstChanged = count;
|
|
599
|
+
let uniform = estimate !== void 0;
|
|
600
|
+
for (let index = 0; index < count; index += 1) {
|
|
601
|
+
let key = next.getKey ? next.getKey(index) : index;
|
|
602
|
+
if (!(typeof key === "string" || typeof key === "number" && Number.isFinite(key))) {
|
|
603
|
+
recover("invalid_virtual_option", `Item ${index} has an invalid key; its index is used.`);
|
|
604
|
+
key = index;
|
|
605
|
+
}
|
|
606
|
+
if (nextByKey.has(key)) {
|
|
607
|
+
recover("duplicate_virtual_key", `Item ${index} repeats a key; a distinct fallback key is used.`);
|
|
608
|
+
key = JSON.stringify(["virtual", index]);
|
|
609
|
+
while (nextByKey.has(key)) key += ":";
|
|
610
|
+
}
|
|
611
|
+
const size = measured.get(key) ?? estimate ?? validSize(next.estimateSize(index));
|
|
612
|
+
uniform &&= size === estimate;
|
|
613
|
+
if (keys[index] !== key || sizes[index] !== size) firstChanged = Math.min(firstChanged, index);
|
|
614
|
+
nextKeys.push(key);
|
|
615
|
+
nextSizes.push(size);
|
|
616
|
+
nextByKey.set(key, index);
|
|
617
|
+
}
|
|
618
|
+
if (fixedSize !== void 0 || uniform) prefix = [0];
|
|
619
|
+
else prefix.length = Math.min(prefix.length, firstChanged + 1);
|
|
620
|
+
keys = nextKeys;
|
|
621
|
+
sizes = nextSizes;
|
|
622
|
+
byKey = nextByKey;
|
|
623
|
+
fixedSize = uniform ? estimate : void 0;
|
|
624
|
+
cached = void 0;
|
|
625
|
+
}
|
|
626
|
+
function startOf(index) {
|
|
627
|
+
if (fixedSize !== void 0) return index * fixedSize;
|
|
628
|
+
while (prefix.length <= index) {
|
|
629
|
+
const at = prefix.length - 1;
|
|
630
|
+
prefix.push(prefix[at] + sizes[at]);
|
|
631
|
+
}
|
|
632
|
+
return prefix[index];
|
|
633
|
+
}
|
|
634
|
+
function totalSize() {
|
|
635
|
+
return startOf(count);
|
|
636
|
+
}
|
|
637
|
+
function indexAt(position) {
|
|
638
|
+
if (fixedSize !== void 0) return Math.min(count - 1, Math.floor(position / fixedSize));
|
|
639
|
+
let low = 0;
|
|
640
|
+
let high = count - 1;
|
|
641
|
+
while (low < high) {
|
|
642
|
+
const middle = Math.floor((low + high) / 2);
|
|
643
|
+
if (startOf(middle + 1) <= position) low = middle + 1;
|
|
644
|
+
else high = middle;
|
|
645
|
+
}
|
|
646
|
+
return low;
|
|
647
|
+
}
|
|
648
|
+
function getWindow() {
|
|
649
|
+
if (cached) return cached;
|
|
650
|
+
const total = totalSize();
|
|
651
|
+
const clampedOffset = Math.min(offset, Math.max(0, total - viewportSize));
|
|
652
|
+
let first = 0;
|
|
653
|
+
let last = -1;
|
|
654
|
+
if (count > 0 && viewportSize > 0) {
|
|
655
|
+
first = Math.max(0, indexAt(clampedOffset) - overscan);
|
|
656
|
+
const edge = Math.min(total, clampedOffset + viewportSize);
|
|
657
|
+
let visibleEnd = indexAt(edge);
|
|
658
|
+
if (startOf(visibleEnd) >= edge) visibleEnd -= 1;
|
|
659
|
+
last = Math.min(count - 1, visibleEnd + overscan);
|
|
660
|
+
}
|
|
661
|
+
const items = [];
|
|
662
|
+
for (let index = first; index <= last; index += 1) items.push(Object.freeze({
|
|
663
|
+
index,
|
|
664
|
+
key: keys[index],
|
|
665
|
+
start: startOf(index),
|
|
666
|
+
size: sizes[index]
|
|
667
|
+
}));
|
|
668
|
+
const paddingStart = items.length > 0 ? startOf(first) : 0;
|
|
669
|
+
const paddingEnd = items.length > 0 ? Math.max(0, total - startOf(last + 1)) : total;
|
|
670
|
+
cached = Object.freeze({
|
|
671
|
+
items: Object.freeze(items),
|
|
672
|
+
totalSize: total,
|
|
673
|
+
paddingStart,
|
|
674
|
+
paddingEnd,
|
|
675
|
+
startIndex: first,
|
|
676
|
+
endIndex: last,
|
|
677
|
+
offset: clampedOffset,
|
|
678
|
+
viewportSize,
|
|
679
|
+
issues: Object.freeze([...problems.values()])
|
|
680
|
+
});
|
|
681
|
+
return cached;
|
|
682
|
+
}
|
|
683
|
+
function notify() {
|
|
684
|
+
cached = void 0;
|
|
685
|
+
if (listeners.size > 0) {
|
|
686
|
+
const view = getWindow();
|
|
687
|
+
for (const listener of listeners) listener(view);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
configure(initialOptions);
|
|
691
|
+
return {
|
|
692
|
+
setOptions(next) {
|
|
693
|
+
configure(next);
|
|
694
|
+
notify();
|
|
695
|
+
},
|
|
696
|
+
setViewport(nextOffset, nextSize) {
|
|
697
|
+
const issueCount = problems.size;
|
|
698
|
+
const safeOffset = Number.isFinite(nextOffset) && nextOffset >= 0 ? nextOffset : 0;
|
|
699
|
+
const safeSize = Number.isFinite(nextSize) && nextSize >= 0 ? nextSize : 0;
|
|
700
|
+
if (safeOffset !== nextOffset || safeSize !== nextSize) recover("invalid_virtual_viewport", "Viewport offset and size must be non-negative and finite; invalid values use 0.");
|
|
701
|
+
if (offset === safeOffset && viewportSize === safeSize && problems.size === issueCount) return;
|
|
702
|
+
offset = safeOffset;
|
|
703
|
+
viewportSize = safeSize;
|
|
704
|
+
notify();
|
|
705
|
+
},
|
|
706
|
+
measure(key, size) {
|
|
707
|
+
const index = byKey.get(key);
|
|
708
|
+
if (index === void 0) return;
|
|
709
|
+
if (!Number.isFinite(size) || size <= 0) {
|
|
710
|
+
recover("invalid_virtual_size", "Measured size must be positive and finite; the previous size is retained.");
|
|
711
|
+
notify();
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
measured.set(key, size);
|
|
715
|
+
if (sizes[index] === size) return;
|
|
716
|
+
if (fixedSize !== void 0) {
|
|
717
|
+
fixedSize = void 0;
|
|
718
|
+
prefix = [0];
|
|
719
|
+
} else prefix.length = Math.min(prefix.length, index + 1);
|
|
720
|
+
sizes[index] = size;
|
|
721
|
+
notify();
|
|
722
|
+
},
|
|
723
|
+
getWindow,
|
|
724
|
+
getOffsetForIndex(index, align = "auto") {
|
|
725
|
+
if (count === 0) return 0;
|
|
726
|
+
if (!Number.isFinite(index) || !Number.isInteger(index)) recover("invalid_virtual_option", "Scroll index must be a finite integer; it is clamped to a valid item.");
|
|
727
|
+
const target = Math.min(count - 1, Math.max(0, Number.isFinite(index) ? Math.trunc(index) : 0));
|
|
728
|
+
const start = startOf(target);
|
|
729
|
+
const end = start + sizes[target];
|
|
730
|
+
const current = getWindow().offset;
|
|
731
|
+
let result = start;
|
|
732
|
+
if (align === "center") result = start - (viewportSize - sizes[target]) / 2;
|
|
733
|
+
else if (align === "end") result = end - viewportSize;
|
|
734
|
+
else if (align === "auto") result = start < current ? start : end > current + viewportSize ? end - viewportSize : current;
|
|
735
|
+
return Math.min(Math.max(0, totalSize() - viewportSize), Math.max(0, result));
|
|
736
|
+
},
|
|
737
|
+
subscribe(listener) {
|
|
738
|
+
listeners.add(listener);
|
|
739
|
+
return () => listeners.delete(listener);
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
469
744
|
//#endregion
|
|
470
745
|
//#region src/infer.ts
|
|
471
746
|
function isPlainObject(value) {
|
|
@@ -593,10 +868,52 @@ function paginationItems(page, pageCount, siblings = 1) {
|
|
|
593
868
|
return items;
|
|
594
869
|
}
|
|
595
870
|
|
|
871
|
+
//#endregion
|
|
872
|
+
//#region src/row.ts
|
|
873
|
+
/** Disjoint namespaces and typed keys avoid collisions between 1, "1", and detail-like ids. */
|
|
874
|
+
function getRowItemKey(key, kind = "row") {
|
|
875
|
+
return JSON.stringify([
|
|
876
|
+
kind,
|
|
877
|
+
typeof key,
|
|
878
|
+
key
|
|
879
|
+
]);
|
|
880
|
+
}
|
|
881
|
+
function createRow(original, key, index, columns, canExpand = false) {
|
|
882
|
+
const values = /* @__PURE__ */ new Map();
|
|
883
|
+
const displays = /* @__PURE__ */ new Map();
|
|
884
|
+
const getValue = (columnId) => {
|
|
885
|
+
if (values.has(columnId)) return values.get(columnId);
|
|
886
|
+
const value = columns.get(columnId)?.getValue(original);
|
|
887
|
+
values.set(columnId, value);
|
|
888
|
+
return value;
|
|
889
|
+
};
|
|
890
|
+
return Object.freeze({
|
|
891
|
+
key,
|
|
892
|
+
index,
|
|
893
|
+
original,
|
|
894
|
+
canExpand,
|
|
895
|
+
isExpanded: false,
|
|
896
|
+
depth: 0,
|
|
897
|
+
parentKey: void 0,
|
|
898
|
+
childCount: 0,
|
|
899
|
+
childStatus: "loaded",
|
|
900
|
+
selection: "none",
|
|
901
|
+
getValue,
|
|
902
|
+
getDisplay(columnId) {
|
|
903
|
+
const cached = displays.get(columnId);
|
|
904
|
+
if (cached !== void 0) return cached;
|
|
905
|
+
const column = columns.get(columnId);
|
|
906
|
+
const display = column ? column.format(getValue(columnId), original) : "";
|
|
907
|
+
displays.set(columnId, display);
|
|
908
|
+
return display;
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
|
|
596
913
|
//#endregion
|
|
597
914
|
//#region src/state.ts
|
|
598
915
|
const DEFAULT_PAGE_SIZE = 10;
|
|
599
|
-
function createInitialState(patch = {}) {
|
|
916
|
+
function createInitialState(patch = {}, onIssue) {
|
|
600
917
|
return freezeState({
|
|
601
918
|
sorting: patch.sorting ?? [],
|
|
602
919
|
search: patch.search ?? "",
|
|
@@ -606,71 +923,257 @@ function createInitialState(patch = {}) {
|
|
|
606
923
|
pageSize: 10
|
|
607
924
|
},
|
|
608
925
|
selection: patch.selection ?? [],
|
|
926
|
+
expanded: normalizeExpanded(patch.expanded === void 0 ? [] : patch.expanded, onIssue),
|
|
609
927
|
hiddenColumns: patch.hiddenColumns ?? [],
|
|
610
928
|
columnOrder: patch.columnOrder ?? []
|
|
611
929
|
});
|
|
612
930
|
}
|
|
613
|
-
function mergeState(state, patch) {
|
|
931
|
+
function mergeState(state, patch, onIssue) {
|
|
614
932
|
let changed = false;
|
|
615
933
|
const next = { ...state };
|
|
616
|
-
for (const [key,
|
|
934
|
+
for (const [key, input] of Object.entries(patch)) {
|
|
935
|
+
const value = key === "expanded" && input !== void 0 ? normalizeExpanded(input, onIssue) : input;
|
|
617
936
|
if (value === void 0 || Object.is(next[key], value)) continue;
|
|
618
937
|
if (key === "pagination" && samePagination(next[key], value)) continue;
|
|
938
|
+
if (key === "expanded" && sameExpanded(state.expanded, value)) continue;
|
|
619
939
|
next[key] = value;
|
|
620
940
|
changed = true;
|
|
621
941
|
}
|
|
622
|
-
return changed ? freezeState(next) : state;
|
|
942
|
+
return changed ? freezeState(next, state) : state;
|
|
943
|
+
}
|
|
944
|
+
function normalizeExpanded(value, onIssue) {
|
|
945
|
+
if (value === true) return true;
|
|
946
|
+
if (Array.isArray(value) && value.every((key) => typeof key === "string" || typeof key === "number" && Number.isFinite(key))) return [...new Set(value)];
|
|
947
|
+
onIssue?.(issue("invalid_expanded", "Expanded state must be true or an array of string/finite number keys; an empty array is used."));
|
|
948
|
+
return [];
|
|
949
|
+
}
|
|
950
|
+
function sameExpanded(left, right) {
|
|
951
|
+
return left === right || left !== true && right !== true && left.length === right.length && left.every((key, index) => key === right[index]);
|
|
623
952
|
}
|
|
624
953
|
function samePagination(left, right) {
|
|
625
954
|
return left.page === right.page && left.pageSize === right.pageSize;
|
|
626
955
|
}
|
|
627
|
-
function freezeState(state) {
|
|
956
|
+
function freezeState(state, previous) {
|
|
628
957
|
return Object.freeze({
|
|
629
|
-
sorting: Object.freeze(state.sorting.map((rule) => Object.freeze({ ...rule }))),
|
|
958
|
+
sorting: state.sorting === previous?.sorting ? previous.sorting : Object.freeze(state.sorting.map((rule) => Object.freeze({ ...rule }))),
|
|
630
959
|
search: state.search,
|
|
631
|
-
filters: Object.freeze({ ...state.filters }),
|
|
632
|
-
pagination: Object.freeze({ ...state.pagination }),
|
|
633
|
-
selection: Object.freeze([...state.selection]),
|
|
634
|
-
|
|
635
|
-
|
|
960
|
+
filters: state.filters === previous?.filters ? previous.filters : Object.freeze({ ...state.filters }),
|
|
961
|
+
pagination: state.pagination === previous?.pagination ? previous.pagination : Object.freeze({ ...state.pagination }),
|
|
962
|
+
selection: state.selection === previous?.selection ? previous.selection : Object.freeze([...state.selection]),
|
|
963
|
+
expanded: state.expanded === previous?.expanded ? previous.expanded : state.expanded === true ? true : Object.freeze([...state.expanded]),
|
|
964
|
+
hiddenColumns: state.hiddenColumns === previous?.hiddenColumns ? previous.hiddenColumns : Object.freeze([...state.hiddenColumns]),
|
|
965
|
+
columnOrder: state.columnOrder === previous?.columnOrder ? previous.columnOrder : Object.freeze([...state.columnOrder])
|
|
636
966
|
});
|
|
637
967
|
}
|
|
638
968
|
|
|
639
969
|
//#endregion
|
|
640
|
-
//#region src/
|
|
641
|
-
function
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
970
|
+
//#region src/tree.ts
|
|
971
|
+
function buildTree(data, columns, readKey, options, lazy) {
|
|
972
|
+
const nodes = [];
|
|
973
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
974
|
+
const issues = [];
|
|
975
|
+
const nested = options.getChildren !== void 0;
|
|
976
|
+
let maxDepth = options.maxDepth ?? 1e3;
|
|
977
|
+
if (!Number.isSafeInteger(maxDepth) || maxDepth < 1) {
|
|
978
|
+
issues.push(issue("invalid_tree_option", "maxDepth must be a positive safe integer; 1,000 is used."));
|
|
979
|
+
maxDepth = 1e3;
|
|
980
|
+
}
|
|
981
|
+
if (nested && options.getParentKey) issues.push(issue("invalid_tree_option", "Both tree inputs were supplied; getChildren takes precedence."));
|
|
982
|
+
const seen = /* @__PURE__ */ new Set();
|
|
983
|
+
const stack = data.map((original, sourceIndex) => ({
|
|
984
|
+
original,
|
|
985
|
+
sourceIndex,
|
|
986
|
+
parent: void 0,
|
|
987
|
+
lazy: false
|
|
988
|
+
})).reverse();
|
|
989
|
+
while (stack.length) {
|
|
990
|
+
const entry = stack.pop();
|
|
991
|
+
const index = nodes.length;
|
|
992
|
+
let key = readKey(entry.original, index);
|
|
993
|
+
let parent = entry.parent;
|
|
994
|
+
const reference = entry.original !== null && (typeof entry.original === "object" || typeof entry.original === "function");
|
|
995
|
+
const repeated = nested && reference && seen.has(entry.original);
|
|
996
|
+
if (repeated) {
|
|
997
|
+
issues.push(issue("tree_cycle", "A repeated nested value recovers as a root without traversing it again.", { rowKey: key }));
|
|
998
|
+
parent = void 0;
|
|
999
|
+
}
|
|
1000
|
+
if (reference) seen.add(entry.original);
|
|
1001
|
+
if (byKey.has(key)) {
|
|
1002
|
+
issues.push(issue(entry.lazy ? "tree_duplicate_key" : "duplicate_row_key", entry.lazy ? "A loaded child repeats an existing key and is rejected." : "A repeated row key is replaced with a unique key.", { rowKey: key }));
|
|
1003
|
+
if (entry.lazy) continue;
|
|
1004
|
+
const prefix = `${String(key)}#${index}`;
|
|
1005
|
+
key = prefix;
|
|
1006
|
+
let suffix = 0;
|
|
1007
|
+
while (byKey.has(key)) key = `${prefix}#${++suffix}`;
|
|
1008
|
+
}
|
|
1009
|
+
if (parent && parent.depth + 1 > maxDepth) {
|
|
1010
|
+
issues.push(issue("tree_depth_exceeded", "The maximum tree depth was exceeded; this row recovers as a root.", { rowKey: key }));
|
|
1011
|
+
parent = void 0;
|
|
1012
|
+
}
|
|
1013
|
+
const children = repeated ? void 0 : lazy.get(key) ?? options.getChildren?.(entry.original);
|
|
1014
|
+
const node = {
|
|
1015
|
+
row: createRow(entry.original, key, index, columns),
|
|
1016
|
+
parent,
|
|
1017
|
+
children: [],
|
|
1018
|
+
depth: parent ? parent.depth + 1 : 0,
|
|
1019
|
+
sourceIndex: entry.sourceIndex,
|
|
1020
|
+
lazy: entry.lazy,
|
|
1021
|
+
writable: parent?.writable ?? entry.parent === void 0,
|
|
1022
|
+
unloaded: children === void 0 && (options.hasChildren?.(entry.original) ?? false)
|
|
1023
|
+
};
|
|
1024
|
+
nodes.push(node);
|
|
1025
|
+
byKey.set(key, node);
|
|
1026
|
+
if (parent) parent.children.push(node);
|
|
1027
|
+
if (children) for (let i = children.length - 1; i >= 0; i--) stack.push({
|
|
1028
|
+
original: children[i],
|
|
1029
|
+
sourceIndex: i,
|
|
1030
|
+
parent: node,
|
|
1031
|
+
lazy: entry.lazy || lazy.has(key)
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
if (!nested && options.getParentKey) {
|
|
1035
|
+
for (const node of nodes) {
|
|
1036
|
+
if (node.lazy) continue;
|
|
1037
|
+
const key = options.getParentKey(node.row.original);
|
|
1038
|
+
if (key === void 0 || key === null) continue;
|
|
1039
|
+
const parent = byKey.get(key);
|
|
1040
|
+
if (!parent) issues.push(issue("tree_orphan", "The parent is missing; this row recovers as a root.", { rowKey: node.row.key }));
|
|
1041
|
+
else node.parent = parent;
|
|
1042
|
+
}
|
|
1043
|
+
const done = /* @__PURE__ */ new Set();
|
|
1044
|
+
for (const node of nodes) {
|
|
1045
|
+
const path = [];
|
|
1046
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1047
|
+
let cursor = node;
|
|
1048
|
+
while (cursor && !done.has(cursor)) {
|
|
1049
|
+
if (visiting.has(cursor)) {
|
|
1050
|
+
issues.push(issue("tree_cycle", "A cyclic parent reference is detached to a root.", { rowKey: cursor.row.key }));
|
|
1051
|
+
cursor.parent = void 0;
|
|
1052
|
+
break;
|
|
1053
|
+
}
|
|
1054
|
+
visiting.add(cursor);
|
|
1055
|
+
path.push(cursor);
|
|
1056
|
+
cursor = cursor.parent;
|
|
1057
|
+
}
|
|
1058
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
1059
|
+
const current = path[i];
|
|
1060
|
+
done.add(current);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
for (const node of nodes) node.children = [];
|
|
1064
|
+
for (const node of nodes) if (node.parent) node.parent.children.push(node);
|
|
1065
|
+
const pending = nodes.filter((node) => !node.parent);
|
|
1066
|
+
while (pending.length) {
|
|
1067
|
+
const node = pending.pop();
|
|
1068
|
+
node.depth = node.parent ? node.parent.depth + 1 : 0;
|
|
1069
|
+
if (node.depth > maxDepth) {
|
|
1070
|
+
node.parent = void 0;
|
|
1071
|
+
node.depth = 0;
|
|
1072
|
+
issues.push(issue("tree_depth_exceeded", "The maximum tree depth was exceeded; this row recovers as a root.", { rowKey: node.row.key }));
|
|
1073
|
+
}
|
|
1074
|
+
for (const child of node.children) pending.push(child);
|
|
1075
|
+
}
|
|
1076
|
+
for (const node of nodes) node.children = node.children.filter((child) => child.parent === node);
|
|
1077
|
+
}
|
|
1078
|
+
return {
|
|
1079
|
+
nodes,
|
|
1080
|
+
rows: nodes.map((node) => node.row),
|
|
1081
|
+
roots: nodes.filter((node) => !node.parent),
|
|
1082
|
+
byKey,
|
|
1083
|
+
issues
|
|
1084
|
+
};
|
|
647
1085
|
}
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
const
|
|
653
|
-
const
|
|
654
|
-
|
|
655
|
-
if (
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1086
|
+
function processTree(model, columns, state, mode, manual) {
|
|
1087
|
+
const raw = model.nodes.map((node) => node.row);
|
|
1088
|
+
const matched = manual ? raw : filterRows(raw, columns, state.search, state.filters);
|
|
1089
|
+
const active = matched !== raw;
|
|
1090
|
+
const included = new Set(matched.map((row) => row.key));
|
|
1091
|
+
const autoExpanded = /* @__PURE__ */ new Set();
|
|
1092
|
+
if (active && mode !== "strict") {
|
|
1093
|
+
if (mode === "descendants") {
|
|
1094
|
+
const stack = matched.map((row) => model.byKey.get(row.key));
|
|
1095
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1096
|
+
while (stack.length) {
|
|
1097
|
+
const node = stack.pop();
|
|
1098
|
+
if (visited.has(node.row.key)) continue;
|
|
1099
|
+
visited.add(node.row.key);
|
|
1100
|
+
included.add(node.row.key);
|
|
1101
|
+
if (node.children.length) autoExpanded.add(node.row.key);
|
|
1102
|
+
for (const child of node.children) stack.push(child);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
for (const row of matched) {
|
|
1106
|
+
let parent = model.byKey.get(row.key).parent;
|
|
1107
|
+
while (parent && !autoExpanded.has(parent.row.key)) {
|
|
1108
|
+
included.add(parent.row.key);
|
|
1109
|
+
autoExpanded.add(parent.row.key);
|
|
1110
|
+
parent = parent.parent;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
if (active && mode === "strict") return {
|
|
1115
|
+
model,
|
|
1116
|
+
roots: ordered(model.nodes.filter((node) => included.has(node.row.key) && node.children.length === 0 && !node.unloaded)),
|
|
1117
|
+
children: /* @__PURE__ */ new Map(),
|
|
1118
|
+
autoExpanded
|
|
659
1119
|
};
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
1120
|
+
const children = /* @__PURE__ */ new Map();
|
|
1121
|
+
for (const node of model.nodes) if (node.children.length) children.set(node, ordered(node.children.filter((child) => included.has(child.row.key))));
|
|
1122
|
+
return {
|
|
1123
|
+
model,
|
|
1124
|
+
roots: ordered(model.roots.filter((node) => included.has(node.row.key))),
|
|
1125
|
+
children,
|
|
1126
|
+
autoExpanded
|
|
1127
|
+
};
|
|
1128
|
+
function ordered(nodes) {
|
|
1129
|
+
if (manual || !state.sorting.length || nodes.length < 2) return nodes;
|
|
1130
|
+
return sortRows(nodes.map((node) => node.row), columns, state.sorting).map((row) => model.byKey.get(row.key));
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
/** Expansion changes reuse filtering and sibling order, splicing only a changed subtree. */
|
|
1134
|
+
function createTreeFlattener() {
|
|
1135
|
+
let previous;
|
|
1136
|
+
let previousExpanded = [];
|
|
1137
|
+
let visible = [];
|
|
1138
|
+
let previousAncestors;
|
|
1139
|
+
return (tree, expanded, ancestors) => {
|
|
1140
|
+
if (tree === previous && expanded === previousExpanded && ancestors === previousAncestors) return visible;
|
|
1141
|
+
const keys = expanded === true ? void 0 : new Set(expanded);
|
|
1142
|
+
const open = (node) => expanded === true || (keys?.has(node.row.key) ?? false) || ancestors.has(node.row.key) || tree.autoExpanded.has(node.row.key);
|
|
1143
|
+
const walk = (roots) => {
|
|
1144
|
+
const output = [];
|
|
1145
|
+
const stack = [...roots].reverse();
|
|
1146
|
+
while (stack.length) {
|
|
1147
|
+
const node = stack.pop();
|
|
1148
|
+
output.push(node);
|
|
1149
|
+
if (open(node)) {
|
|
1150
|
+
const children = tree.children.get(node) ?? [];
|
|
1151
|
+
for (let i = children.length - 1; i >= 0; i--) stack.push(children[i]);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
return output;
|
|
1155
|
+
};
|
|
1156
|
+
let changed;
|
|
1157
|
+
if (previous === tree && ancestors === previousAncestors && expanded !== true && previousExpanded !== true) {
|
|
1158
|
+
const before = new Set(previousExpanded);
|
|
1159
|
+
const after = new Set(expanded);
|
|
1160
|
+
changed = [...previousExpanded.filter((key) => !after.has(key)), ...expanded.filter((key) => !before.has(key))];
|
|
672
1161
|
}
|
|
673
|
-
|
|
1162
|
+
if (changed?.length === 1 && !tree.autoExpanded.has(changed[0])) {
|
|
1163
|
+
const index = visible.findIndex((node) => node.row.key === changed[0]);
|
|
1164
|
+
if (index >= 0) {
|
|
1165
|
+
const node = visible[index];
|
|
1166
|
+
let end = index + 1;
|
|
1167
|
+
while (end < visible.length && visible[end].depth > node.depth) end++;
|
|
1168
|
+
const inserted = walk(open(node) ? tree.children.get(node) ?? [] : []);
|
|
1169
|
+
visible = visible.slice(0, index + 1).concat(inserted, visible.slice(end));
|
|
1170
|
+
}
|
|
1171
|
+
} else visible = walk(tree.roots);
|
|
1172
|
+
previous = tree;
|
|
1173
|
+
previousExpanded = expanded;
|
|
1174
|
+
previousAncestors = ancestors;
|
|
1175
|
+
return visible;
|
|
1176
|
+
};
|
|
674
1177
|
}
|
|
675
1178
|
|
|
676
1179
|
//#endregion
|
|
@@ -697,19 +1200,42 @@ function coverage(selected, total) {
|
|
|
697
1200
|
if (selected === 0 || total === 0) return "none";
|
|
698
1201
|
return selected === total ? "all" : "some";
|
|
699
1202
|
}
|
|
1203
|
+
function pasteLimit(value, fallback, name, issues) {
|
|
1204
|
+
if (value === void 0) return fallback;
|
|
1205
|
+
if (Number.isSafeInteger(value) && value > 0) return value;
|
|
1206
|
+
issues.push(issue("invalid_paste_limit", `Paste ${name} ${String(value)} is not a positive safe integer; ${fallback} is used.`));
|
|
1207
|
+
return fallback;
|
|
1208
|
+
}
|
|
700
1209
|
/** Create a data table. */
|
|
701
1210
|
function createTable(options) {
|
|
1211
|
+
const paginate = options.paginate ?? true;
|
|
1212
|
+
const expandMode = options.expandMode ?? "multiple";
|
|
702
1213
|
const selectionMode = options.selectionMode ?? "multiple";
|
|
703
1214
|
const selectScope = options.selectScope ?? "all";
|
|
704
1215
|
const manual = options.manual ?? false;
|
|
1216
|
+
const treeEnabled = Boolean(options.getChildren || options.getParentKey || options.hasChildren || options.loadChildren);
|
|
1217
|
+
const rootPagination = treeEnabled && (manual || (options.paginateBy ?? "root") === "root");
|
|
1218
|
+
let lazyVersion = 0;
|
|
1219
|
+
const lazyChildren = /* @__PURE__ */ new Map();
|
|
1220
|
+
const childStatuses = /* @__PURE__ */ new Map();
|
|
1221
|
+
const loadIssues = /* @__PURE__ */ new Map();
|
|
1222
|
+
const loadValidationIssues = /* @__PURE__ */ new Map();
|
|
1223
|
+
const pendingLoads = /* @__PURE__ */ new Map();
|
|
1224
|
+
let destroyed = false;
|
|
705
1225
|
const undoStack = createUndoStack(options.historyLimit);
|
|
706
1226
|
const listeners = /* @__PURE__ */ new Set();
|
|
1227
|
+
const optionIssues = [];
|
|
1228
|
+
const maxPasteCells = pasteLimit(options.pasteLimit?.maxCells, 1e5, "maxCells", optionIssues);
|
|
1229
|
+
const maxPasteLength = pasteLimit(options.pasteLimit?.maxLength, 5e6, "maxLength", optionIssues);
|
|
707
1230
|
let data = options.data;
|
|
708
1231
|
let definitions = options.columns;
|
|
709
1232
|
let rowCount = options.rowCount;
|
|
710
|
-
|
|
1233
|
+
let expandedIssue;
|
|
1234
|
+
let initialState = createInitialState({
|
|
711
1235
|
...options.initialState,
|
|
712
1236
|
hiddenColumns: options.initialState?.hiddenColumns ?? options.columns.filter((column) => column.hidden === true).map((column) => column.id)
|
|
1237
|
+
}, (problem) => {
|
|
1238
|
+
expandedIssue = problem;
|
|
713
1239
|
});
|
|
714
1240
|
let state = initialState;
|
|
715
1241
|
let snapshot;
|
|
@@ -721,10 +1247,18 @@ function createTable(options) {
|
|
|
721
1247
|
return rowKey === void 0 || !isRecord(row) ? index : String(value);
|
|
722
1248
|
};
|
|
723
1249
|
const resolveColumns = memo((defs) => {
|
|
724
|
-
const
|
|
1250
|
+
const issues = [];
|
|
1251
|
+
const columns = defs.flatMap((definition) => {
|
|
1252
|
+
if (!isSafePath(definition.id)) {
|
|
1253
|
+
issues.push(issue("unsafe_path", `The column "${definition.id}" addresses a prototype and is ignored.`, { column: definition.id }));
|
|
1254
|
+
return [];
|
|
1255
|
+
}
|
|
1256
|
+
return [resolveColumn(definition)];
|
|
1257
|
+
});
|
|
725
1258
|
return {
|
|
726
1259
|
columns,
|
|
727
|
-
byId: new Map(columns.map((column) => [column.id, column]))
|
|
1260
|
+
byId: new Map(columns.map((column) => [column.id, column])),
|
|
1261
|
+
issues
|
|
728
1262
|
};
|
|
729
1263
|
});
|
|
730
1264
|
const indexRows = memo((rows, byId) => {
|
|
@@ -737,7 +1271,7 @@ function createTable(options) {
|
|
|
737
1271
|
issues.push(issue("duplicate_row_key", `Row ${index} repeats the key "${String(key)}"; it is keyed "${String(key)}#${index}" instead.`, { rowKey: key }));
|
|
738
1272
|
key = `${String(key)}#${index}`;
|
|
739
1273
|
}
|
|
740
|
-
const row = createRow(original, key, index, byId);
|
|
1274
|
+
const row = createRow(original, key, index, byId, options.getRowCanExpand?.(original) ?? false);
|
|
741
1275
|
byKey.set(key, row);
|
|
742
1276
|
return row;
|
|
743
1277
|
}),
|
|
@@ -745,6 +1279,60 @@ function createTable(options) {
|
|
|
745
1279
|
issues
|
|
746
1280
|
};
|
|
747
1281
|
});
|
|
1282
|
+
const treeStage = memo((rows, byId, _version) => buildTree(rows, byId, readKey, options, lazyChildren));
|
|
1283
|
+
const processedTreeStage = memo((model, columns, search, filters, sorting) => processTree(model, columns, {
|
|
1284
|
+
...state,
|
|
1285
|
+
search,
|
|
1286
|
+
filters,
|
|
1287
|
+
sorting
|
|
1288
|
+
}, options.treeFilter ?? "ancestors", manual));
|
|
1289
|
+
const flattenTree = createTreeFlattener();
|
|
1290
|
+
const emptyAncestors = /* @__PURE__ */ new Set();
|
|
1291
|
+
const singleTreePath = memo((model, expanded) => {
|
|
1292
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
1293
|
+
if (expanded !== true) for (const key of expanded) {
|
|
1294
|
+
let parent = model.byKey.get(key)?.parent;
|
|
1295
|
+
while (parent && !ancestors.has(parent.row.key)) {
|
|
1296
|
+
ancestors.add(parent.row.key);
|
|
1297
|
+
parent = parent.parent;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
return ancestors;
|
|
1301
|
+
});
|
|
1302
|
+
const treeSelection = memo((model, selection) => {
|
|
1303
|
+
const selected = new Set(selection);
|
|
1304
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1305
|
+
const stack = model.roots.map((node) => ({
|
|
1306
|
+
node,
|
|
1307
|
+
exit: false
|
|
1308
|
+
}));
|
|
1309
|
+
while (stack.length) {
|
|
1310
|
+
const { node, exit } = stack.pop();
|
|
1311
|
+
if (!exit) {
|
|
1312
|
+
stack.push({
|
|
1313
|
+
node,
|
|
1314
|
+
exit: true
|
|
1315
|
+
});
|
|
1316
|
+
for (const child of node.children) stack.push({
|
|
1317
|
+
node: child,
|
|
1318
|
+
exit: false
|
|
1319
|
+
});
|
|
1320
|
+
} else {
|
|
1321
|
+
let count = selected.has(node.row.key) ? 1 : 0;
|
|
1322
|
+
let total = 1;
|
|
1323
|
+
for (const child of node.children) {
|
|
1324
|
+
const value = counts.get(child);
|
|
1325
|
+
count += value.selected;
|
|
1326
|
+
total += value.total;
|
|
1327
|
+
}
|
|
1328
|
+
counts.set(node, {
|
|
1329
|
+
selected: count,
|
|
1330
|
+
total
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
return counts;
|
|
1335
|
+
});
|
|
748
1336
|
const orderColumns = memo((columns, columnOrder, hiddenColumns) => {
|
|
749
1337
|
const position = new Map(columnOrder.map((id, index) => [id, index]));
|
|
750
1338
|
const ordered = columns.map((column, index) => ({
|
|
@@ -762,8 +1350,26 @@ function createTable(options) {
|
|
|
762
1350
|
const filterStage = memo(filterRows);
|
|
763
1351
|
const sortStage = memo(sortRows);
|
|
764
1352
|
const selectedSet = memo((selection) => new Set(selection));
|
|
1353
|
+
const expansionStage = memo((indexed, expanded, selection) => {
|
|
1354
|
+
const keys = expanded === true ? void 0 : new Set(expanded);
|
|
1355
|
+
const selected = new Set(selection);
|
|
1356
|
+
const rows = indexed.rows.map((row) => {
|
|
1357
|
+
const isExpanded = row.canExpand && (expanded === true || (keys?.has(row.key) ?? false));
|
|
1358
|
+
return isExpanded || selected.has(row.key) ? Object.freeze({
|
|
1359
|
+
...row,
|
|
1360
|
+
isExpanded,
|
|
1361
|
+
selection: selected.has(row.key) ? "all" : "none"
|
|
1362
|
+
}) : row;
|
|
1363
|
+
});
|
|
1364
|
+
return {
|
|
1365
|
+
rows,
|
|
1366
|
+
byKey: new Map(rows.map((row) => [row.key, row]))
|
|
1367
|
+
};
|
|
1368
|
+
});
|
|
1369
|
+
const expandedOrder = memo((rows, byKey) => rows.map((row) => byKey.get(row.key)));
|
|
765
1370
|
function stateIssues(byId) {
|
|
766
1371
|
const issues = [];
|
|
1372
|
+
if (expandedIssue) issues.push(expandedIssue);
|
|
767
1373
|
const referenced = /* @__PURE__ */ new Set([
|
|
768
1374
|
...state.sorting.map((rule) => rule.column),
|
|
769
1375
|
...Object.keys(state.filters),
|
|
@@ -772,30 +1378,93 @@ function createTable(options) {
|
|
|
772
1378
|
]);
|
|
773
1379
|
for (const id of referenced) if (!byId.has(id)) issues.push(issue("unknown_column", `The state refers to a column "${id}" that is not defined.`, { column: id }));
|
|
774
1380
|
const { pageSize } = state.pagination;
|
|
775
|
-
if (!Number.isInteger(pageSize) || pageSize < 1) issues.push(issue("invalid_page_size", `Page size ${String(pageSize)} is not a positive whole number; ${10} is used.`));
|
|
1381
|
+
if (paginate && (!Number.isInteger(pageSize) || pageSize < 1)) issues.push(issue("invalid_page_size", `Page size ${String(pageSize)} is not a positive whole number; ${10} is used.`));
|
|
776
1382
|
return issues;
|
|
777
1383
|
}
|
|
778
1384
|
function computeSnapshot() {
|
|
779
|
-
const { columns, byId } = resolveColumns(definitions);
|
|
780
|
-
const
|
|
1385
|
+
const { columns, byId, issues: columnIssues } = resolveColumns(definitions);
|
|
1386
|
+
const model = treeEnabled ? treeStage(data, byId, lazyVersion) : void 0;
|
|
1387
|
+
const indexed = model ? {
|
|
1388
|
+
rows: model.rows,
|
|
1389
|
+
issues: model.issues
|
|
1390
|
+
} : indexRows(data, byId);
|
|
1391
|
+
const expanded = model ? void 0 : expansionStage(indexed, state.expanded, state.selection);
|
|
781
1392
|
const ordered = orderColumns(columns, state.columnOrder, state.hiddenColumns);
|
|
782
|
-
const issues = [
|
|
1393
|
+
const issues = [
|
|
1394
|
+
...optionIssues,
|
|
1395
|
+
...columnIssues,
|
|
1396
|
+
...indexed.issues,
|
|
1397
|
+
...stateIssues(byId),
|
|
1398
|
+
...loadIssues.values(),
|
|
1399
|
+
...Array.from(loadValidationIssues.values()).flat()
|
|
1400
|
+
];
|
|
783
1401
|
const requestedSize = state.pagination.pageSize;
|
|
784
|
-
|
|
1402
|
+
let pageSize = Number.isInteger(requestedSize) && requestedSize >= 1 ? requestedSize : 10;
|
|
785
1403
|
let processedRows;
|
|
786
1404
|
let rows;
|
|
787
1405
|
let filteredCount;
|
|
788
1406
|
let page;
|
|
789
1407
|
let pageCount;
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
1408
|
+
let treeRow;
|
|
1409
|
+
let totalCount = indexed.rows.length;
|
|
1410
|
+
if (model) {
|
|
1411
|
+
const processed = processedTreeStage(model, columns, state.search, state.filters, state.sorting);
|
|
1412
|
+
const ancestors = expandMode === "single" ? singleTreePath(model, state.expanded) : emptyAncestors;
|
|
1413
|
+
const visible = flattenTree(processed, state.expanded, ancestors);
|
|
1414
|
+
const expandedState = state.expanded;
|
|
1415
|
+
const keys = state.expanded === true ? void 0 : new Set(state.expanded);
|
|
1416
|
+
const selected = treeSelection(model, state.selection);
|
|
1417
|
+
const projected = /* @__PURE__ */ new Map();
|
|
1418
|
+
const statuses = new Map(childStatuses);
|
|
1419
|
+
treeRow = (key) => {
|
|
1420
|
+
const cached = projected.get(key);
|
|
1421
|
+
if (cached) return cached;
|
|
1422
|
+
const node = model.byKey.get(key);
|
|
1423
|
+
if (!node) return void 0;
|
|
1424
|
+
const status = statuses.get(key) ?? (node.children.length || !node.unloaded ? "loaded" : "idle");
|
|
1425
|
+
const count = selected.get(node);
|
|
1426
|
+
const canExpand = node.children.length > 0 || status !== "loaded" || (options.getRowCanExpand?.(node.row.original) ?? false);
|
|
1427
|
+
const result = Object.freeze({
|
|
1428
|
+
...node.row,
|
|
1429
|
+
depth: node.depth,
|
|
1430
|
+
parentKey: node.parent?.row.key,
|
|
1431
|
+
childCount: status === "loaded" ? node.children.length : void 0,
|
|
1432
|
+
childStatus: status,
|
|
1433
|
+
canExpand,
|
|
1434
|
+
isExpanded: canExpand && (expandedState === true || (keys?.has(key) ?? false) || ancestors.has(key) || processed.autoExpanded.has(key)),
|
|
1435
|
+
selection: coverage(count.selected, count.total)
|
|
1436
|
+
});
|
|
1437
|
+
projected.set(key, result);
|
|
1438
|
+
return result;
|
|
1439
|
+
};
|
|
1440
|
+
processedRows = visible.map((node) => treeRow(node.row.key));
|
|
1441
|
+
filteredCount = manual ? rowCount ?? model.roots.length : rootPagination ? processed.roots.length : visible.length;
|
|
1442
|
+
totalCount = rootPagination ? model.roots.length : model.nodes.length;
|
|
1443
|
+
pageCount = countPages(filteredCount, pageSize);
|
|
1444
|
+
page = clampPage(state.pagination.page, pageCount);
|
|
1445
|
+
if (manual) rows = processedRows;
|
|
1446
|
+
else if (rootPagination) {
|
|
1447
|
+
const roots = new Set(processed.roots.slice((page - 1) * pageSize, page * pageSize));
|
|
1448
|
+
const allRoots = new Set(processed.roots);
|
|
1449
|
+
let root;
|
|
1450
|
+
rows = processedRows.filter((_row, index) => {
|
|
1451
|
+
const node = visible[index];
|
|
1452
|
+
if (allRoots.has(node)) root = node;
|
|
1453
|
+
return root !== void 0 && roots.has(root);
|
|
1454
|
+
});
|
|
1455
|
+
} else rows = paginateRows(processedRows, {
|
|
1456
|
+
page,
|
|
1457
|
+
pageSize
|
|
1458
|
+
});
|
|
1459
|
+
} else if (manual) {
|
|
1460
|
+
processedRows = expanded.rows;
|
|
1461
|
+
rows = expanded.rows;
|
|
793
1462
|
filteredCount = rowCount ?? indexed.rows.length;
|
|
794
1463
|
pageCount = countPages(filteredCount, pageSize);
|
|
795
1464
|
page = clampPage(state.pagination.page, pageCount);
|
|
796
1465
|
} else {
|
|
797
1466
|
const filtered = filterStage(indexed.rows, columns, state.search, state.filters);
|
|
798
|
-
processedRows = sortStage(filtered, columns, state.sorting);
|
|
1467
|
+
processedRows = expandedOrder(sortStage(filtered, columns, state.sorting), expanded.byKey);
|
|
799
1468
|
filteredCount = processedRows.length;
|
|
800
1469
|
pageCount = countPages(filteredCount, pageSize);
|
|
801
1470
|
page = clampPage(state.pagination.page, pageCount);
|
|
@@ -804,6 +1473,12 @@ function createTable(options) {
|
|
|
804
1473
|
pageSize
|
|
805
1474
|
});
|
|
806
1475
|
}
|
|
1476
|
+
if (!paginate) {
|
|
1477
|
+
rows = processedRows;
|
|
1478
|
+
page = 1;
|
|
1479
|
+
pageCount = 1;
|
|
1480
|
+
pageSize = Math.max(1, rows.length);
|
|
1481
|
+
}
|
|
807
1482
|
const selected = selectedSet(state.selection);
|
|
808
1483
|
const pageSelected = rows.filter((row) => selected.has(row.key)).length;
|
|
809
1484
|
const scopeRows = selectScope === "page" || manual ? rows : processedRows;
|
|
@@ -818,19 +1493,34 @@ function createTable(options) {
|
|
|
818
1493
|
columns: ordered.visible,
|
|
819
1494
|
allColumns: ordered.all,
|
|
820
1495
|
rows,
|
|
1496
|
+
renderItems: Object.freeze(rows.flatMap((row, rowIndex) => {
|
|
1497
|
+
const item = Object.freeze({
|
|
1498
|
+
kind: "row",
|
|
1499
|
+
key: getRowItemKey(row.key),
|
|
1500
|
+
row,
|
|
1501
|
+
rowIndex
|
|
1502
|
+
});
|
|
1503
|
+
return row.isExpanded && (!model || (options.getRowCanExpand?.(row.original) ?? false)) ? [item, Object.freeze({
|
|
1504
|
+
kind: "detail",
|
|
1505
|
+
key: getRowItemKey(row.key, "detail"),
|
|
1506
|
+
row,
|
|
1507
|
+
rowIndex
|
|
1508
|
+
})] : [item];
|
|
1509
|
+
})),
|
|
1510
|
+
paginate,
|
|
821
1511
|
processedRows,
|
|
822
1512
|
page,
|
|
823
1513
|
pageSize,
|
|
824
1514
|
pageCount,
|
|
825
1515
|
rowCount: filteredCount,
|
|
826
|
-
totalRowCount: manual ? filteredCount :
|
|
1516
|
+
totalRowCount: manual ? filteredCount : totalCount,
|
|
827
1517
|
pageStart,
|
|
828
|
-
pageEnd: rows.length === 0 ? 0 : pageStart + rows.length - 1,
|
|
1518
|
+
pageEnd: rows.length === 0 ? 0 : rootPagination ? Math.min(filteredCount, page * pageSize) : pageStart + rows.length - 1,
|
|
829
1519
|
pageItems: paginationItems(page, pageCount),
|
|
830
1520
|
canPreviousPage: page > 1,
|
|
831
1521
|
canNextPage: page < pageCount,
|
|
832
1522
|
selectionMode,
|
|
833
|
-
selectedCount: state.selection.length,
|
|
1523
|
+
selectedCount: model ? state.selection.filter((key) => model.byKey.has(key)).length : state.selection.length,
|
|
834
1524
|
pageSelection: coverage(pageSelected, rows.length),
|
|
835
1525
|
allSelection: coverage(scopeSelected, scopeRows.length),
|
|
836
1526
|
canUndo: undoStack.canUndo,
|
|
@@ -839,7 +1529,7 @@ function createTable(options) {
|
|
|
839
1529
|
isSelected: (key) => selected.has(key),
|
|
840
1530
|
getSort: (columnId) => sortIndex.get(columnId),
|
|
841
1531
|
getColumn: (columnId) => byId.get(columnId),
|
|
842
|
-
getRow: (key) =>
|
|
1532
|
+
getRow: (key) => treeRow ? treeRow(key) : expanded.byKey.get(key)
|
|
843
1533
|
});
|
|
844
1534
|
}
|
|
845
1535
|
function getSnapshot() {
|
|
@@ -852,14 +1542,131 @@ function createTable(options) {
|
|
|
852
1542
|
const next = getSnapshot();
|
|
853
1543
|
for (const listener of listeners) listener(next);
|
|
854
1544
|
}
|
|
1545
|
+
function abortLoads(all = false) {
|
|
1546
|
+
for (const [key, controller] of pendingLoads) {
|
|
1547
|
+
const row = all ? void 0 : getSnapshot().getRow(key);
|
|
1548
|
+
const visible = !all && getSnapshot().processedRows.some((candidate) => candidate.key === key);
|
|
1549
|
+
if (all || !row?.isExpanded || !visible) {
|
|
1550
|
+
pendingLoads.delete(key);
|
|
1551
|
+
childStatuses.delete(key);
|
|
1552
|
+
controller.abort();
|
|
1553
|
+
snapshot = void 0;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
function requestChildren(row) {
|
|
1558
|
+
if (!options.loadChildren || destroyed || pendingLoads.has(row.key) || lazyChildren.has(row.key) || row.childStatus === "loaded") return;
|
|
1559
|
+
const key = row.key;
|
|
1560
|
+
if (!options.createChildLoadController) {
|
|
1561
|
+
childStatuses.set(key, "error");
|
|
1562
|
+
loadIssues.set(key, issue("invalid_tree_option", "Lazy children require createChildLoadController from the caller's runtime.", { rowKey: key }));
|
|
1563
|
+
notify();
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
let controller;
|
|
1567
|
+
try {
|
|
1568
|
+
controller = options.createChildLoadController();
|
|
1569
|
+
} catch (error) {
|
|
1570
|
+
loadIssues.set(key, issue("tree_load_error", String(error), { rowKey: key }));
|
|
1571
|
+
childStatuses.set(key, "error");
|
|
1572
|
+
notify();
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
pendingLoads.set(key, controller);
|
|
1576
|
+
childStatuses.set(key, "loading");
|
|
1577
|
+
loadIssues.delete(key);
|
|
1578
|
+
loadValidationIssues.delete(key);
|
|
1579
|
+
notify();
|
|
1580
|
+
Promise.resolve().then(() => controller.signal.aborted ? [] : options.loadChildren(row, controller.signal)).then((children) => {
|
|
1581
|
+
if (controller.signal.aborted || pendingLoads.get(key) !== controller || destroyed) return void 0;
|
|
1582
|
+
const { byId } = resolveColumns(definitions);
|
|
1583
|
+
const existing = treeStage(data, byId, lazyVersion);
|
|
1584
|
+
const candidate = buildTree(children, byId, readKey, {
|
|
1585
|
+
...options,
|
|
1586
|
+
getParentKey: void 0
|
|
1587
|
+
}, /* @__PURE__ */ new Map());
|
|
1588
|
+
pendingLoads.delete(key);
|
|
1589
|
+
loadValidationIssues.set(key, candidate.issues.filter((problem) => problem.code === "tree_cycle" || problem.code === "tree_depth_exceeded"));
|
|
1590
|
+
if (candidate.nodes.filter((node) => existing.byKey.has(node.row.key)).length || candidate.issues.some((problem) => problem.code === "duplicate_row_key")) {
|
|
1591
|
+
childStatuses.set(key, "error");
|
|
1592
|
+
loadIssues.set(key, issue("tree_duplicate_key", "Loaded children collide with existing keys; the batch is rejected.", { rowKey: key }));
|
|
1593
|
+
} else {
|
|
1594
|
+
lazyChildren.set(key, children);
|
|
1595
|
+
childStatuses.set(key, "loaded");
|
|
1596
|
+
lazyVersion++;
|
|
1597
|
+
if (state.selection.includes(key) && selectionMode === "multiple") {
|
|
1598
|
+
const nextModel = treeStage(data, byId, lazyVersion);
|
|
1599
|
+
applySelection([...state.selection, ...selectionKeys([key], nextModel)]);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
notify();
|
|
1603
|
+
startVisibleLoads();
|
|
1604
|
+
}).catch((error) => {
|
|
1605
|
+
if (controller.signal.aborted || pendingLoads.get(key) !== controller || destroyed) return;
|
|
1606
|
+
pendingLoads.delete(key);
|
|
1607
|
+
childStatuses.set(key, "error");
|
|
1608
|
+
loadIssues.set(key, issue("tree_load_error", `Children could not be loaded: ${String(error)}`, { rowKey: key }));
|
|
1609
|
+
notify();
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
function startVisibleLoads() {
|
|
1613
|
+
if (!treeEnabled || !options.loadChildren || destroyed) return;
|
|
1614
|
+
const current = getSnapshot();
|
|
1615
|
+
for (const row of current.rows) if (row.isExpanded && row.childStatus === "idle") requestChildren(row);
|
|
1616
|
+
}
|
|
1617
|
+
function selectionKeys(keys, model) {
|
|
1618
|
+
if (!treeEnabled || selectionMode !== "multiple") return [...keys];
|
|
1619
|
+
model ??= treeStage(data, resolveColumns(definitions).byId, lazyVersion);
|
|
1620
|
+
const result = new Set(keys);
|
|
1621
|
+
const stack = keys.flatMap((key) => {
|
|
1622
|
+
const node = model.byKey.get(key);
|
|
1623
|
+
return node ? [node] : [];
|
|
1624
|
+
});
|
|
1625
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1626
|
+
while (stack.length) {
|
|
1627
|
+
const node = stack.pop();
|
|
1628
|
+
if (visited.has(node.row.key)) continue;
|
|
1629
|
+
visited.add(node.row.key);
|
|
1630
|
+
result.add(node.row.key);
|
|
1631
|
+
for (const child of node.children) stack.push(child);
|
|
1632
|
+
}
|
|
1633
|
+
return [...result];
|
|
1634
|
+
}
|
|
855
1635
|
/** Apply a change made by a table operation. */
|
|
856
1636
|
function commit(patch) {
|
|
857
1637
|
const previous = state;
|
|
858
|
-
const
|
|
859
|
-
|
|
1638
|
+
const previousIssue = expandedIssue;
|
|
1639
|
+
const next = mergePatch(patch);
|
|
1640
|
+
if (next === previous && previousIssue === expandedIssue) return;
|
|
860
1641
|
state = next;
|
|
861
|
-
|
|
1642
|
+
snapshot = void 0;
|
|
1643
|
+
abortLoads();
|
|
1644
|
+
if (next !== previous) options.onStateChange?.(state, previous);
|
|
862
1645
|
notify();
|
|
1646
|
+
startVisibleLoads();
|
|
1647
|
+
}
|
|
1648
|
+
function canonicalExpanded(expanded, candidate = state) {
|
|
1649
|
+
if (expandMode === "multiple") return expanded;
|
|
1650
|
+
if (expanded !== true) return expanded.slice(-1);
|
|
1651
|
+
const { columns, byId } = resolveColumns(definitions);
|
|
1652
|
+
if (treeEnabled) {
|
|
1653
|
+
const model = treeStage(data, byId, lazyVersion);
|
|
1654
|
+
const first = processedTreeStage(model, columns, candidate.search, candidate.filters, candidate.sorting).roots.find((node) => node.children.length > 0 || (options.hasChildren?.(node.row.original) ?? false) || (options.getRowCanExpand?.(node.row.original) ?? false));
|
|
1655
|
+
return first ? [first.row.key] : [];
|
|
1656
|
+
}
|
|
1657
|
+
const indexed = indexRows(data, byId);
|
|
1658
|
+
const first = (manual ? indexed.rows : sortStage(filterStage(indexed.rows, columns, candidate.search, candidate.filters), columns, candidate.sorting)).find((row) => row.canExpand);
|
|
1659
|
+
return first ? [first.key] : [];
|
|
1660
|
+
}
|
|
1661
|
+
function mergePatch(patch) {
|
|
1662
|
+
if (patch.expanded !== void 0) expandedIssue = void 0;
|
|
1663
|
+
const next = mergeState(state, patch, (problem) => {
|
|
1664
|
+
expandedIssue = problem;
|
|
1665
|
+
});
|
|
1666
|
+
return expandMode === "single" && patch.expanded !== void 0 ? mergeState(state, {
|
|
1667
|
+
...patch,
|
|
1668
|
+
expanded: canonicalExpanded(next.expanded, next)
|
|
1669
|
+
}) : next;
|
|
863
1670
|
}
|
|
864
1671
|
const firstPage = () => ({
|
|
865
1672
|
page: 1,
|
|
@@ -890,10 +1697,13 @@ function createTable(options) {
|
|
|
890
1697
|
if (selection.length === current.length && selection.every((key, i) => key === current[i])) return;
|
|
891
1698
|
commit({ selection });
|
|
892
1699
|
}
|
|
893
|
-
function applyChanges(edits, record) {
|
|
1700
|
+
function applyChanges(edits, record, recoveredIssues = []) {
|
|
894
1701
|
const current = getSnapshot();
|
|
895
1702
|
const working = [...data];
|
|
896
|
-
const
|
|
1703
|
+
const model = treeEnabled ? treeStage(data, resolveColumns(definitions).byId, lazyVersion) : void 0;
|
|
1704
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
1705
|
+
const originalOf = (node) => replacements.get(node) ?? node.row.original;
|
|
1706
|
+
const issues = [...recoveredIssues];
|
|
897
1707
|
const changes = [];
|
|
898
1708
|
const samples = /* @__PURE__ */ new Map();
|
|
899
1709
|
const sampleOf = (columnId) => {
|
|
@@ -916,6 +1726,13 @@ function createTable(options) {
|
|
|
916
1726
|
}));
|
|
917
1727
|
continue;
|
|
918
1728
|
}
|
|
1729
|
+
if (!isSafePath(edit.column)) {
|
|
1730
|
+
issues.push(issue("unsafe_path", `The path "${edit.column}" addresses a prototype and cannot be written.`, {
|
|
1731
|
+
rowKey: edit.rowKey,
|
|
1732
|
+
column: edit.column
|
|
1733
|
+
}));
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
919
1736
|
if (!column) {
|
|
920
1737
|
issues.push(issue("unknown_column", `No column has the id "${edit.column}".`, {
|
|
921
1738
|
rowKey: edit.rowKey,
|
|
@@ -923,7 +1740,22 @@ function createTable(options) {
|
|
|
923
1740
|
}));
|
|
924
1741
|
continue;
|
|
925
1742
|
}
|
|
926
|
-
const
|
|
1743
|
+
const node = model?.byKey.get(row.key);
|
|
1744
|
+
const original = node ? originalOf(node) : working[row.index];
|
|
1745
|
+
if (node && !node.writable) {
|
|
1746
|
+
issues.push(issue("read_only_cell", "A recovered nested root has no unambiguous source path for editing.", {
|
|
1747
|
+
rowKey: row.key,
|
|
1748
|
+
column: column.id
|
|
1749
|
+
}));
|
|
1750
|
+
continue;
|
|
1751
|
+
}
|
|
1752
|
+
if (node?.parent && options.getChildren && !options.setChildren) {
|
|
1753
|
+
issues.push(issue("read_only_cell", "Editing a nested child requires an immutable setChildren callback.", {
|
|
1754
|
+
rowKey: row.key,
|
|
1755
|
+
column: column.id
|
|
1756
|
+
}));
|
|
1757
|
+
continue;
|
|
1758
|
+
}
|
|
927
1759
|
if (record && !column.isEditable(original)) {
|
|
928
1760
|
issues.push(issue("read_only_cell", `The "${column.id}" cell of row "${String(row.key)}" is read-only.`, {
|
|
929
1761
|
rowKey: row.key,
|
|
@@ -953,7 +1785,21 @@ function createTable(options) {
|
|
|
953
1785
|
}));
|
|
954
1786
|
continue;
|
|
955
1787
|
}
|
|
956
|
-
working[row.index] = updated;
|
|
1788
|
+
if (!node) working[row.index] = updated;
|
|
1789
|
+
else {
|
|
1790
|
+
let cursor = node;
|
|
1791
|
+
replacements.set(cursor, updated);
|
|
1792
|
+
if (!options.getChildren && !cursor.lazy) working[cursor.sourceIndex] = updated;
|
|
1793
|
+
while (cursor.parent) {
|
|
1794
|
+
const parent = cursor.parent;
|
|
1795
|
+
const children = parent.children.map(originalOf);
|
|
1796
|
+
if (cursor.lazy) lazyChildren.set(parent.row.key, children);
|
|
1797
|
+
if (options.setChildren) replacements.set(parent, options.setChildren(originalOf(parent), children));
|
|
1798
|
+
cursor = parent;
|
|
1799
|
+
}
|
|
1800
|
+
if (options.getChildren || options.setChildren) working[cursor.sourceIndex] = originalOf(cursor);
|
|
1801
|
+
lazyVersion++;
|
|
1802
|
+
}
|
|
957
1803
|
changes.push({
|
|
958
1804
|
rowKey: row.key,
|
|
959
1805
|
column: column.id,
|
|
@@ -996,12 +1842,17 @@ function createTable(options) {
|
|
|
996
1842
|
return edits;
|
|
997
1843
|
}
|
|
998
1844
|
function replay(changes, direction) {
|
|
999
|
-
applyChanges(changes.map((change) => ({
|
|
1845
|
+
applyChanges((direction === "undo" ? changes.toReversed() : changes).map((change) => ({
|
|
1000
1846
|
rowKey: change.rowKey,
|
|
1001
1847
|
column: change.column,
|
|
1002
1848
|
value: direction === "undo" ? change.previous : change.value
|
|
1003
1849
|
})), false);
|
|
1004
1850
|
}
|
|
1851
|
+
if (expandMode === "single") {
|
|
1852
|
+
initialState = mergeState(initialState, { expanded: canonicalExpanded(initialState.expanded) });
|
|
1853
|
+
state = initialState;
|
|
1854
|
+
snapshot = void 0;
|
|
1855
|
+
}
|
|
1005
1856
|
const table = {
|
|
1006
1857
|
getSnapshot,
|
|
1007
1858
|
getState: () => state,
|
|
@@ -1012,17 +1863,28 @@ function createTable(options) {
|
|
|
1012
1863
|
};
|
|
1013
1864
|
},
|
|
1014
1865
|
setState(patch) {
|
|
1015
|
-
const
|
|
1016
|
-
|
|
1866
|
+
const previousIssue = expandedIssue;
|
|
1867
|
+
const next = mergePatch(patch);
|
|
1868
|
+
if (next !== state || previousIssue !== expandedIssue) {
|
|
1017
1869
|
state = next;
|
|
1870
|
+
snapshot = void 0;
|
|
1871
|
+
abortLoads();
|
|
1018
1872
|
notify();
|
|
1873
|
+
startVisibleLoads();
|
|
1019
1874
|
}
|
|
1020
1875
|
},
|
|
1021
1876
|
setData(next) {
|
|
1022
1877
|
if (next === data) return;
|
|
1878
|
+
abortLoads(true);
|
|
1879
|
+
lazyChildren.clear();
|
|
1880
|
+
childStatuses.clear();
|
|
1881
|
+
loadIssues.clear();
|
|
1882
|
+
loadValidationIssues.clear();
|
|
1883
|
+
lazyVersion++;
|
|
1023
1884
|
data = next;
|
|
1024
1885
|
undoStack.clear();
|
|
1025
1886
|
notify();
|
|
1887
|
+
startVisibleLoads();
|
|
1026
1888
|
},
|
|
1027
1889
|
setColumns(next) {
|
|
1028
1890
|
if (next === definitions) return;
|
|
@@ -1069,6 +1931,7 @@ function createTable(options) {
|
|
|
1069
1931
|
});
|
|
1070
1932
|
},
|
|
1071
1933
|
goToPage(page) {
|
|
1934
|
+
if (!paginate) return;
|
|
1072
1935
|
const { pageCount, page: currentPage } = getSnapshot();
|
|
1073
1936
|
const next = clampPage(page, pageCount);
|
|
1074
1937
|
if (next !== currentPage || next !== state.pagination.page) commit({ pagination: {
|
|
@@ -1083,6 +1946,7 @@ function createTable(options) {
|
|
|
1083
1946
|
table.goToPage(getSnapshot().page - 1);
|
|
1084
1947
|
},
|
|
1085
1948
|
setPageSize(pageSize) {
|
|
1949
|
+
if (!paginate) return;
|
|
1086
1950
|
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize === state.pagination.pageSize) return;
|
|
1087
1951
|
const { pageStart } = getSnapshot();
|
|
1088
1952
|
commit({ pagination: {
|
|
@@ -1091,14 +1955,14 @@ function createTable(options) {
|
|
|
1091
1955
|
} });
|
|
1092
1956
|
},
|
|
1093
1957
|
select(keys) {
|
|
1094
|
-
applySelection([...state.selection, ...keys]);
|
|
1958
|
+
applySelection([...state.selection, ...selectionKeys(keys)]);
|
|
1095
1959
|
},
|
|
1096
1960
|
deselect(keys) {
|
|
1097
|
-
const removed = new Set(keys);
|
|
1961
|
+
const removed = new Set(selectionKeys(keys));
|
|
1098
1962
|
applySelection(state.selection.filter((key) => !removed.has(key)));
|
|
1099
1963
|
},
|
|
1100
1964
|
toggleRow(key, selected) {
|
|
1101
|
-
const isSelected = state.selection.includes(key);
|
|
1965
|
+
const isSelected = treeEnabled ? getSnapshot().getRow(key)?.selection === "all" : state.selection.includes(key);
|
|
1102
1966
|
const next = selected ?? !isSelected;
|
|
1103
1967
|
if (next === isSelected) return;
|
|
1104
1968
|
if (next) table.select([key]);
|
|
@@ -1121,6 +1985,27 @@ function createTable(options) {
|
|
|
1121
1985
|
return row ? [row] : [];
|
|
1122
1986
|
});
|
|
1123
1987
|
},
|
|
1988
|
+
toggleExpanded(key, expanded) {
|
|
1989
|
+
const row = getSnapshot().getRow(key);
|
|
1990
|
+
if (!row?.canExpand) return;
|
|
1991
|
+
const open = expanded ?? !row.isExpanded;
|
|
1992
|
+
if (open === row.isExpanded) {
|
|
1993
|
+
if (open && row.childStatus === "error") requestChildren(row);
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
if (expandMode === "single") commit({ expanded: open ? [key] : [] });
|
|
1997
|
+
else if (state.expanded === true) {
|
|
1998
|
+
const { byId } = resolveColumns(definitions);
|
|
1999
|
+
commit({ expanded: (treeEnabled ? treeStage(data, byId, lazyVersion).rows.map((candidate) => getSnapshot().getRow(candidate.key)) : indexRows(data, byId).rows).filter((candidate) => candidate.canExpand && candidate.key !== key).map((candidate) => candidate.key) });
|
|
2000
|
+
} else commit({ expanded: open ? [...state.expanded, key] : state.expanded.filter((candidate) => candidate !== key) });
|
|
2001
|
+
if (open) requestChildren(getSnapshot().getRow(key));
|
|
2002
|
+
},
|
|
2003
|
+
expandAll() {
|
|
2004
|
+
commit({ expanded: true });
|
|
2005
|
+
},
|
|
2006
|
+
collapseAll() {
|
|
2007
|
+
commit({ expanded: [] });
|
|
2008
|
+
},
|
|
1124
2009
|
toggleColumn(columnId, visible) {
|
|
1125
2010
|
if (!getSnapshot().getColumn(columnId)) return;
|
|
1126
2011
|
const hidden = state.hiddenColumns.includes(columnId);
|
|
@@ -1153,7 +2038,7 @@ function createTable(options) {
|
|
|
1153
2038
|
replay(changes, "redo");
|
|
1154
2039
|
return true;
|
|
1155
2040
|
},
|
|
1156
|
-
copy(range) {
|
|
2041
|
+
copy(range, { escapeFormulas = false } = {}) {
|
|
1157
2042
|
const { rows, columns } = getSnapshot();
|
|
1158
2043
|
const matrix = [];
|
|
1159
2044
|
rows.forEach((row, rowIndex) => {
|
|
@@ -1162,14 +2047,26 @@ function createTable(options) {
|
|
|
1162
2047
|
if (rangeContains(range, {
|
|
1163
2048
|
row: rowIndex,
|
|
1164
2049
|
column: columnIndex
|
|
1165
|
-
}))
|
|
2050
|
+
})) {
|
|
2051
|
+
const display = row.getDisplay(column.id);
|
|
2052
|
+
line.push(escapeFormulas ? escapeFormula(display, row.getValue(column.id)) : display);
|
|
2053
|
+
}
|
|
1166
2054
|
});
|
|
1167
2055
|
if (line.length > 0) matrix.push(line);
|
|
1168
2056
|
});
|
|
1169
2057
|
return toDelimited(matrix);
|
|
1170
2058
|
},
|
|
1171
2059
|
paste(origin, text) {
|
|
1172
|
-
|
|
2060
|
+
if (!Number.isInteger(origin.row) || !Number.isInteger(origin.column) || origin.row < 0 || origin.column < 0) return applyChanges([], true, [issue("invalid_value", "The paste origin must use non-negative whole row and column indices.")]);
|
|
2061
|
+
const { rows, columns } = getSnapshot();
|
|
2062
|
+
const parsed = readDelimited(text, {
|
|
2063
|
+
maxRows: Math.max(0, rows.length - origin.row),
|
|
2064
|
+
maxColumns: Math.max(0, columns.length - origin.column),
|
|
2065
|
+
maxCells: maxPasteCells,
|
|
2066
|
+
maxLength: maxPasteLength
|
|
2067
|
+
});
|
|
2068
|
+
const issues = parsed.truncated ? [issue("paste_truncated", "The paste exceeds the visible grid or parsing limits; only complete cells within those limits are applied.")] : [];
|
|
2069
|
+
return applyChanges(rangeEdits(origin, parsed.rows), true, issues);
|
|
1173
2070
|
},
|
|
1174
2071
|
clear(range) {
|
|
1175
2072
|
const matrix = Array.from({ length: range.bottom - range.top + 1 }, () => Array.from({ length: range.right - range.left + 1 }, () => ""));
|
|
@@ -1182,26 +2079,37 @@ function createTable(options) {
|
|
|
1182
2079
|
return row !== void 0 && current.getColumn(edit.column)?.isEditable(row.original) === true;
|
|
1183
2080
|
}), true);
|
|
1184
2081
|
},
|
|
1185
|
-
exportRows({ format = "csv", columns: ids, pageOnly = false, headers = true } = {}) {
|
|
2082
|
+
exportRows({ format = "csv", columns: ids, pageOnly = false, headers = true, escapeFormulas = true, depth = false, indent = "" } = {}) {
|
|
1186
2083
|
const current = getSnapshot();
|
|
1187
2084
|
const columns = ids ? ids.flatMap((id) => {
|
|
1188
2085
|
const column = current.getColumn(id);
|
|
1189
2086
|
return column ? [column] : [];
|
|
1190
2087
|
}) : current.columns;
|
|
1191
|
-
const
|
|
1192
|
-
|
|
2088
|
+
const rows = pageOnly ? current.rows : current.processedRows;
|
|
2089
|
+
const matrix = rows.map((row) => columns.map((column, index) => {
|
|
2090
|
+
const display = (index === 0 ? indent.repeat(row.depth) : "") + row.getDisplay(column.id);
|
|
2091
|
+
return escapeFormulas ? escapeFormula(display, row.getValue(column.id)) : display;
|
|
2092
|
+
}));
|
|
2093
|
+
if (depth) matrix.forEach((line, index) => line.unshift(String(rows[index].depth)));
|
|
2094
|
+
if (headers) {
|
|
2095
|
+
matrix.unshift(columns.map((column) => escapeFormulas ? escapeFormula(column.header) : column.header));
|
|
2096
|
+
if (depth) matrix[0].unshift("Depth");
|
|
2097
|
+
}
|
|
1193
2098
|
return toDelimited(matrix, format === "csv" ? "," : " ");
|
|
1194
2099
|
},
|
|
1195
2100
|
reset() {
|
|
1196
2101
|
commit(initialState);
|
|
1197
2102
|
},
|
|
1198
2103
|
destroy() {
|
|
2104
|
+
destroyed = true;
|
|
2105
|
+
abortLoads(true);
|
|
1199
2106
|
listeners.clear();
|
|
1200
2107
|
}
|
|
1201
2108
|
};
|
|
2109
|
+
startVisibleLoads();
|
|
1202
2110
|
return table;
|
|
1203
2111
|
}
|
|
1204
2112
|
|
|
1205
2113
|
//#endregion
|
|
1206
|
-
export { DEFAULT_PAGE_SIZE, clampPage, clampPosition, columnLabel, compareValues, countPages, createTable, createUndoStack, defineColumns, filterRows, formatValue, fromA1, getPath, gridCommand, humanize, inferColumns, matchesFilter, moveSelection, paginateRows, paginationItems, parseAs, parseDelimited, rangeContains, resolveColumn, selectCell, selectionRange, setPath, sortRows, toA1, toDelimited, typeOf };
|
|
2114
|
+
export { DEFAULT_PAGE_SIZE, clampPage, clampPosition, columnLabel, compareValues, countPages, createTable, createUndoStack, createVirtualizer, defineColumns, filterRows, formatValue, fromA1, getPath, getRowItemKey, gridCommand, humanize, inferColumns, matchesFilter, moveSelection, paginateRows, paginationItems, parseAs, parseDelimited, rangeContains, resolveColumn, selectCell, selectionRange, setPath, sortRows, toA1, toDelimited, typeOf };
|
|
1207
2115
|
//# sourceMappingURL=index.js.map
|