@vueye-table/core 3.0.0-alpha.4 → 3.0.0-alpha.6
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 +2 -0
- package/dist/index.d.ts +45 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +592 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -547,6 +547,16 @@ function createUndoStack(limit = 100) {
|
|
|
547
547
|
clear() {
|
|
548
548
|
past.length = 0;
|
|
549
549
|
future.length = 0;
|
|
550
|
+
},
|
|
551
|
+
transform(map) {
|
|
552
|
+
const update = (entries) => entries.flatMap((entry) => {
|
|
553
|
+
const value = map(entry);
|
|
554
|
+
return value === void 0 ? [] : [value];
|
|
555
|
+
});
|
|
556
|
+
const nextPast = update(past);
|
|
557
|
+
const nextFuture = update(future);
|
|
558
|
+
past.splice(0, past.length, ...nextPast);
|
|
559
|
+
future.splice(0, future.length, ...nextFuture);
|
|
550
560
|
}
|
|
551
561
|
};
|
|
552
562
|
}
|
|
@@ -721,6 +731,14 @@ function createVirtualizer(initialOptions) {
|
|
|
721
731
|
notify();
|
|
722
732
|
},
|
|
723
733
|
getWindow,
|
|
734
|
+
getItem(index) {
|
|
735
|
+
return Number.isSafeInteger(index) && index >= 0 && index < count ? Object.freeze({
|
|
736
|
+
index,
|
|
737
|
+
key: keys[index],
|
|
738
|
+
start: startOf(index),
|
|
739
|
+
size: sizes[index]
|
|
740
|
+
}) : void 0;
|
|
741
|
+
},
|
|
724
742
|
getOffsetForIndex(index, align = "auto") {
|
|
725
743
|
if (count === 0) return 0;
|
|
726
744
|
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.");
|
|
@@ -801,6 +819,10 @@ function filterRows(rows, columns, search, filters) {
|
|
|
801
819
|
}
|
|
802
820
|
/** Sort rows by each rule in turn. The sort is stable: ties keep their incoming order. */
|
|
803
821
|
function sortRows(rows, columns, sorting) {
|
|
822
|
+
if (!sorting.some((rule) => columns.some((column) => column.id === rule.column))) return rows;
|
|
823
|
+
return rows.toSorted(compareRows(columns, sorting));
|
|
824
|
+
}
|
|
825
|
+
function compareRows(columns, sorting) {
|
|
804
826
|
const byId = new Map(columns.map((column) => [column.id, column]));
|
|
805
827
|
const rules = sorting.flatMap((rule) => {
|
|
806
828
|
const column = byId.get(rule.column);
|
|
@@ -809,8 +831,7 @@ function sortRows(rows, columns, sorting) {
|
|
|
809
831
|
sign: rule.direction === "desc" ? -1 : 1
|
|
810
832
|
}] : [];
|
|
811
833
|
});
|
|
812
|
-
|
|
813
|
-
return rows.toSorted((left, right) => {
|
|
834
|
+
return (left, right) => {
|
|
814
835
|
for (const { column, sign } of rules) {
|
|
815
836
|
const leftValue = left.getValue(column.id);
|
|
816
837
|
const rightValue = right.getValue(column.id);
|
|
@@ -820,7 +841,19 @@ function sortRows(rows, columns, sorting) {
|
|
|
820
841
|
if (order !== 0) return order;
|
|
821
842
|
}
|
|
822
843
|
return 0;
|
|
823
|
-
}
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
/** Stable merge: an existing row wins a tie against an incoming row. */
|
|
847
|
+
function mergeRows(previous, incoming, columns, sorting) {
|
|
848
|
+
const compare = compareRows(columns, sorting);
|
|
849
|
+
const added = sortRows(incoming, columns, sorting);
|
|
850
|
+
const result = [];
|
|
851
|
+
let left = 0;
|
|
852
|
+
let right = 0;
|
|
853
|
+
while (left < previous.length && right < added.length) result.push(compare(previous[left], added[right]) <= 0 ? previous[left++] : added[right++]);
|
|
854
|
+
while (left < previous.length) result.push(previous[left++]);
|
|
855
|
+
while (right < added.length) result.push(added[right++]);
|
|
856
|
+
return result;
|
|
824
857
|
}
|
|
825
858
|
/** The number of pages for a row count. An empty table still has one page. */
|
|
826
859
|
function countPages(rowCount, pageSize) {
|
|
@@ -1128,6 +1161,7 @@ function createEditValidation(host) {
|
|
|
1128
1161
|
});
|
|
1129
1162
|
}
|
|
1130
1163
|
return {
|
|
1164
|
+
hasPending: (key) => (pending.get(key)?.size ?? 0) > 0,
|
|
1131
1165
|
cells: () => Object.freeze([...pending.values()].flatMap((row) => [...row.values()])),
|
|
1132
1166
|
problems: () => new Map([...problems].filter(([, row]) => row.size).map(([key, row]) => [key, readonlyMap(row)])),
|
|
1133
1167
|
cancel(keys) {
|
|
@@ -1328,6 +1362,161 @@ function createPendingChanges() {
|
|
|
1328
1362
|
};
|
|
1329
1363
|
}
|
|
1330
1364
|
|
|
1365
|
+
//#endregion
|
|
1366
|
+
//#region src/row-list.ts
|
|
1367
|
+
/** Read-only array views keep append publication independent of existing list length. */
|
|
1368
|
+
function indexOf(property) {
|
|
1369
|
+
if (typeof property !== "string" || !/^(0|[1-9]\d*)$/u.test(property)) return void 0;
|
|
1370
|
+
return Number(property);
|
|
1371
|
+
}
|
|
1372
|
+
function rowList(length, read) {
|
|
1373
|
+
return new Proxy([], {
|
|
1374
|
+
get(target, property, receiver) {
|
|
1375
|
+
if (property === "length") return length;
|
|
1376
|
+
const index = indexOf(property);
|
|
1377
|
+
return index === void 0 ? Reflect.get(target, property, receiver) : index < length ? read(index) : void 0;
|
|
1378
|
+
},
|
|
1379
|
+
has(target, property) {
|
|
1380
|
+
const index = indexOf(property);
|
|
1381
|
+
return index === void 0 ? Reflect.has(target, property) : index < length;
|
|
1382
|
+
},
|
|
1383
|
+
ownKeys: () => [...Array.from({ length }, (_, index) => String(index)), "length"],
|
|
1384
|
+
getOwnPropertyDescriptor(target, property) {
|
|
1385
|
+
if (property === "length") return {
|
|
1386
|
+
...Reflect.getOwnPropertyDescriptor(target, property),
|
|
1387
|
+
value: length
|
|
1388
|
+
};
|
|
1389
|
+
const index = indexOf(property);
|
|
1390
|
+
return index === void 0 ? Reflect.getOwnPropertyDescriptor(target, property) : index < length ? {
|
|
1391
|
+
configurable: true,
|
|
1392
|
+
enumerable: true,
|
|
1393
|
+
writable: false,
|
|
1394
|
+
value: read(index)
|
|
1395
|
+
} : void 0;
|
|
1396
|
+
},
|
|
1397
|
+
set: () => false,
|
|
1398
|
+
deleteProperty: () => false,
|
|
1399
|
+
defineProperty: () => false,
|
|
1400
|
+
preventExtensions: () => false,
|
|
1401
|
+
setPrototypeOf: () => false
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
const stores = /* @__PURE__ */ new WeakMap();
|
|
1405
|
+
function appendList(previous, incoming) {
|
|
1406
|
+
if (!incoming.length) return previous;
|
|
1407
|
+
let store = stores.get(previous);
|
|
1408
|
+
const last = store?.chunks.at(-1);
|
|
1409
|
+
if (!store || last && last.start + last.rows.length !== previous.length) store = { chunks: [{
|
|
1410
|
+
start: 0,
|
|
1411
|
+
rows: previous
|
|
1412
|
+
}] };
|
|
1413
|
+
store.chunks.push({
|
|
1414
|
+
start: previous.length,
|
|
1415
|
+
rows: incoming
|
|
1416
|
+
});
|
|
1417
|
+
const chunks = store.chunks;
|
|
1418
|
+
const result = rowList(previous.length + incoming.length, (index) => {
|
|
1419
|
+
let low = 0;
|
|
1420
|
+
let high = chunks.length - 1;
|
|
1421
|
+
while (low < high) {
|
|
1422
|
+
const mid = Math.ceil((low + high) / 2);
|
|
1423
|
+
if (chunks[mid].start <= index) low = mid;
|
|
1424
|
+
else high = mid - 1;
|
|
1425
|
+
}
|
|
1426
|
+
const chunk = chunks[low];
|
|
1427
|
+
return chunk.rows[index - chunk.start];
|
|
1428
|
+
});
|
|
1429
|
+
stores.set(result, store);
|
|
1430
|
+
return result;
|
|
1431
|
+
}
|
|
1432
|
+
function mapList(rows, map) {
|
|
1433
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1434
|
+
return rowList(rows.length, (index) => {
|
|
1435
|
+
if (!cache.has(index)) cache.set(index, map(rows[index], index));
|
|
1436
|
+
return cache.get(index);
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
//#endregion
|
|
1441
|
+
//#region src/stream.ts
|
|
1442
|
+
function createStream(host) {
|
|
1443
|
+
let active;
|
|
1444
|
+
return {
|
|
1445
|
+
cancel() {
|
|
1446
|
+
active?.cancel();
|
|
1447
|
+
active = void 0;
|
|
1448
|
+
},
|
|
1449
|
+
async run(source, options = {}) {
|
|
1450
|
+
active?.cancel();
|
|
1451
|
+
const problems = [];
|
|
1452
|
+
const requested = options.batchSize ?? 1e3;
|
|
1453
|
+
const batchSize = Number.isSafeInteger(requested) && requested > 0 ? requested : 1e3;
|
|
1454
|
+
if (batchSize !== requested) problems.push(issue("invalid_stream_option", "batchSize must be a positive safe integer; 1,000 is used."));
|
|
1455
|
+
const requestedCount = options.expectedRowCount;
|
|
1456
|
+
const expected = requestedCount === void 0 || Number.isSafeInteger(requestedCount) && requestedCount >= 0 ? requestedCount : void 0;
|
|
1457
|
+
if (expected !== requestedCount) problems.push(issue("invalid_stream_option", "expectedRowCount must be a non-negative safe integer; it is unknown instead."));
|
|
1458
|
+
let cancelled = false;
|
|
1459
|
+
let wake = () => void 0;
|
|
1460
|
+
const cancellation = new Promise((resolve) => {
|
|
1461
|
+
wake = () => resolve(void 0);
|
|
1462
|
+
});
|
|
1463
|
+
const cancel = () => {
|
|
1464
|
+
cancelled = true;
|
|
1465
|
+
wake();
|
|
1466
|
+
};
|
|
1467
|
+
const run = { cancel };
|
|
1468
|
+
active = run;
|
|
1469
|
+
const signal = options.signal;
|
|
1470
|
+
signal?.addEventListener?.("abort", cancel, { once: true });
|
|
1471
|
+
if (signal?.aborted) run.cancel();
|
|
1472
|
+
let receivedRowCount = 0;
|
|
1473
|
+
let status = "done";
|
|
1474
|
+
let iterator;
|
|
1475
|
+
host.update(cancelled ? "aborted" : "loading", expected, problems, true);
|
|
1476
|
+
try {
|
|
1477
|
+
if (!cancelled) iterator = source[Symbol.asyncIterator]();
|
|
1478
|
+
while (true) {
|
|
1479
|
+
if (cancelled || !iterator) break;
|
|
1480
|
+
const next = await Promise.race([iterator.next(), cancellation]);
|
|
1481
|
+
if (!next || cancelled || signal?.aborted) {
|
|
1482
|
+
run.cancel();
|
|
1483
|
+
break;
|
|
1484
|
+
}
|
|
1485
|
+
if (next.done) break;
|
|
1486
|
+
const rows = Array.isArray(next.value) ? next.value : [next.value];
|
|
1487
|
+
for (let start = 0; start < rows.length; start += batchSize) {
|
|
1488
|
+
await Promise.resolve();
|
|
1489
|
+
if (cancelled || signal?.aborted) {
|
|
1490
|
+
run.cancel();
|
|
1491
|
+
break;
|
|
1492
|
+
}
|
|
1493
|
+
host.update("streaming", expected, problems, false);
|
|
1494
|
+
const result = host.ingest(rows.slice(start, start + batchSize), options.mode ?? "append");
|
|
1495
|
+
receivedRowCount += result.appended + result.replaced;
|
|
1496
|
+
problems.push(...result.issues);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
} catch (error) {
|
|
1500
|
+
status = "error";
|
|
1501
|
+
problems.push(issue("stream_error", error instanceof Error ? error.message : String(error)));
|
|
1502
|
+
} finally {
|
|
1503
|
+
signal?.removeEventListener?.("abort", cancel);
|
|
1504
|
+
if (cancelled) status = "aborted";
|
|
1505
|
+
if (active === run) {
|
|
1506
|
+
active = void 0;
|
|
1507
|
+
host.update(status, expected, problems, true);
|
|
1508
|
+
}
|
|
1509
|
+
if (status !== "done" && iterator?.return) Promise.resolve().then(() => iterator.return()).catch(() => void 0);
|
|
1510
|
+
}
|
|
1511
|
+
return Object.freeze({
|
|
1512
|
+
status,
|
|
1513
|
+
receivedRowCount,
|
|
1514
|
+
issues: Object.freeze([...problems])
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1331
1520
|
//#endregion
|
|
1332
1521
|
//#region src/tree.ts
|
|
1333
1522
|
function buildTree(data, columns, readKey, options, lazy) {
|
|
@@ -1590,6 +1779,8 @@ function createTable(options) {
|
|
|
1590
1779
|
const optionIssues = [];
|
|
1591
1780
|
const maxPasteCells = pasteLimit(options.pasteLimit?.maxCells, 1e5, "maxCells", optionIssues);
|
|
1592
1781
|
const maxPasteLength = pasteLimit(options.pasteLimit?.maxLength, 5e6, "maxLength", optionIssues);
|
|
1782
|
+
const sourceExpected = options.expectedRowCount === void 0 || Number.isSafeInteger(options.expectedRowCount) && options.expectedRowCount >= 0 ? options.expectedRowCount : void 0;
|
|
1783
|
+
if (sourceExpected !== options.expectedRowCount) optionIssues.push(issue("invalid_stream_option", "expectedRowCount must be a non-negative safe integer; it is unknown instead."));
|
|
1593
1784
|
let data = options.data;
|
|
1594
1785
|
let definitions = options.columns;
|
|
1595
1786
|
let rowCount = options.rowCount;
|
|
@@ -1602,6 +1793,19 @@ function createTable(options) {
|
|
|
1602
1793
|
});
|
|
1603
1794
|
let state = initialState;
|
|
1604
1795
|
let snapshot;
|
|
1796
|
+
let loadState = "idle";
|
|
1797
|
+
let expectedRowCount = sourceExpected;
|
|
1798
|
+
let ingestionIssues = [];
|
|
1799
|
+
let streamIssues = [];
|
|
1800
|
+
const streaming = createStream({
|
|
1801
|
+
ingest: (rows, mode) => ingestData(rows, mode),
|
|
1802
|
+
update(next, expected, problems, publish) {
|
|
1803
|
+
loadState = next;
|
|
1804
|
+
expectedRowCount = expected;
|
|
1805
|
+
streamIssues = [...problems];
|
|
1806
|
+
if (publish) notify();
|
|
1807
|
+
}
|
|
1808
|
+
});
|
|
1605
1809
|
let transaction = false;
|
|
1606
1810
|
let deferValidationNotify = false;
|
|
1607
1811
|
let validationNotifyRequested = false;
|
|
@@ -1654,20 +1858,24 @@ function createTable(options) {
|
|
|
1654
1858
|
issues
|
|
1655
1859
|
};
|
|
1656
1860
|
});
|
|
1861
|
+
let primedIndex;
|
|
1657
1862
|
const indexRows = memo((rows, byId) => {
|
|
1863
|
+
if (primedIndex?.data === rows && primedIndex.columns === byId) return primedIndex.result;
|
|
1658
1864
|
const byKey = /* @__PURE__ */ new Map();
|
|
1659
1865
|
const issues = [];
|
|
1866
|
+
const indexed = rows.map((original, index) => {
|
|
1867
|
+
let key = readKey(original, index);
|
|
1868
|
+
if (byKey.has(key)) {
|
|
1869
|
+
issues.push(issue("duplicate_row_key", `Row ${index} repeats the key "${String(key)}"; it is keyed "${String(key)}#${index}" instead.`, { rowKey: key }));
|
|
1870
|
+
key = `${String(key)}#${index}`;
|
|
1871
|
+
}
|
|
1872
|
+
const row = createRow(original, key, index, byId, options.getRowCanExpand?.(original) ?? false);
|
|
1873
|
+
byKey.set(key, row);
|
|
1874
|
+
return row;
|
|
1875
|
+
});
|
|
1876
|
+
stableSource.set(rows, stableRows(rows));
|
|
1660
1877
|
return {
|
|
1661
|
-
rows:
|
|
1662
|
-
let key = readKey(original, index);
|
|
1663
|
-
if (byKey.has(key)) {
|
|
1664
|
-
issues.push(issue("duplicate_row_key", `Row ${index} repeats the key "${String(key)}"; it is keyed "${String(key)}#${index}" instead.`, { rowKey: key }));
|
|
1665
|
-
key = `${String(key)}#${index}`;
|
|
1666
|
-
}
|
|
1667
|
-
const row = createRow(original, key, index, byId, options.getRowCanExpand?.(original) ?? false);
|
|
1668
|
-
byKey.set(key, row);
|
|
1669
|
-
return row;
|
|
1670
|
-
}),
|
|
1878
|
+
rows: indexed,
|
|
1671
1879
|
byKey,
|
|
1672
1880
|
issues
|
|
1673
1881
|
};
|
|
@@ -1757,13 +1965,24 @@ function createTable(options) {
|
|
|
1757
1965
|
visible: ordered.filter((column) => !hidden.has(column.id))
|
|
1758
1966
|
};
|
|
1759
1967
|
});
|
|
1760
|
-
|
|
1761
|
-
|
|
1968
|
+
let primedFilter;
|
|
1969
|
+
let primedSort;
|
|
1970
|
+
const filterStage = memo((rows, columns, search, filters) => primedFilter?.rows === rows && primedFilter.columns === columns && primedFilter.search === search && primedFilter.filters === filters ? primedFilter.result : filterRows(rows, columns, search, filters));
|
|
1971
|
+
const sortStage = memo((rows, columns, sorting) => primedSort?.rows === rows && primedSort.columns === columns && primedSort.sorting === sorting ? primedSort.result : sortRows(rows, columns, sorting));
|
|
1972
|
+
const rowMembership = /* @__PURE__ */ new WeakMap();
|
|
1973
|
+
function membership(rows) {
|
|
1974
|
+
let keys = rowMembership.get(rows);
|
|
1975
|
+
if (!keys) {
|
|
1976
|
+
keys = new Set(rows.map((row) => row.key));
|
|
1977
|
+
rowMembership.set(rows, keys);
|
|
1978
|
+
}
|
|
1979
|
+
return keys;
|
|
1980
|
+
}
|
|
1762
1981
|
const selectedSet = memo((selection) => new Set(selection));
|
|
1763
1982
|
const expansionStage = memo((indexed, expanded, selection) => {
|
|
1764
1983
|
const keys = expanded === true ? void 0 : new Set(expanded);
|
|
1765
1984
|
const selected = new Set(selection);
|
|
1766
|
-
const rows = indexed.rows
|
|
1985
|
+
const rows = mapList(indexed.rows, (row) => {
|
|
1767
1986
|
const isExpanded = row.canExpand && (expanded === true || (keys?.has(row.key) ?? false));
|
|
1768
1987
|
return isExpanded || selected.has(row.key) ? Object.freeze({
|
|
1769
1988
|
...row,
|
|
@@ -1773,10 +1992,13 @@ function createTable(options) {
|
|
|
1773
1992
|
});
|
|
1774
1993
|
return {
|
|
1775
1994
|
rows,
|
|
1776
|
-
byKey:
|
|
1995
|
+
byKey: { get(key) {
|
|
1996
|
+
const row = indexed.byKey.get(key);
|
|
1997
|
+
return row && row.index < rows.length ? rows[row.index] : void 0;
|
|
1998
|
+
} }
|
|
1777
1999
|
};
|
|
1778
2000
|
});
|
|
1779
|
-
const expandedOrder = memo((rows, byKey) => rows
|
|
2001
|
+
const expandedOrder = memo((rows, byKey) => mapList(rows, (row) => byKey.get(row.key)));
|
|
1780
2002
|
function stateIssues(byId) {
|
|
1781
2003
|
const issues = [];
|
|
1782
2004
|
if (expandedIssue) issues.push(expandedIssue);
|
|
@@ -1809,6 +2031,8 @@ function createTable(options) {
|
|
|
1809
2031
|
const ordered = orderColumns(columns, state.columnOrder, state.hiddenColumns);
|
|
1810
2032
|
const issues = [
|
|
1811
2033
|
...optionIssues,
|
|
2034
|
+
...ingestionIssues,
|
|
2035
|
+
...streamIssues,
|
|
1812
2036
|
...columnIssues,
|
|
1813
2037
|
...indexed.issues,
|
|
1814
2038
|
...stateIssues(byId),
|
|
@@ -1910,24 +2134,35 @@ function createTable(options) {
|
|
|
1910
2134
|
return result;
|
|
1911
2135
|
};
|
|
1912
2136
|
const shared = rows === processedRows;
|
|
1913
|
-
processedRows = processedRows
|
|
2137
|
+
processedRows = mapList(processedRows, project);
|
|
1914
2138
|
rows = shared ? processedRows : rows.map(project);
|
|
1915
2139
|
}
|
|
1916
2140
|
const selected = selectedSet(state.selection);
|
|
1917
|
-
const
|
|
2141
|
+
const allData = model ? model.byKey : indexed.byKey;
|
|
2142
|
+
const allProcessedKeys = model ? void 0 : manual ? void 0 : membership(filterStage(indexed.rows, columns, state.search, state.filters));
|
|
2143
|
+
const selectedInProcessed = state.selection.filter((key) => allData.has(key) && (!allProcessedKeys || allProcessedKeys.has(key))).length;
|
|
2144
|
+
const pageSelected = rows === processedRows ? selectedInProcessed : rows.filter((row) => selected.has(row.key)).length;
|
|
1918
2145
|
const scopeRows = selectScope === "page" || manual ? rows : processedRows;
|
|
1919
|
-
const scopeSelected = scopeRows === rows ? pageSelected : scopeRows.filter((row) => selected.has(row.key)).length;
|
|
2146
|
+
const scopeSelected = scopeRows === rows ? pageSelected : model ? scopeRows.filter((row) => selected.has(row.key)).length : selectedInProcessed;
|
|
1920
2147
|
const pageStart = rows.length === 0 ? 0 : (page - 1) * pageSize + 1;
|
|
1921
2148
|
const sortIndex = new Map(state.sorting.map((rule, priority) => [rule.column, {
|
|
1922
2149
|
direction: rule.direction,
|
|
1923
2150
|
priority
|
|
1924
2151
|
}]));
|
|
1925
2152
|
return Object.freeze({
|
|
2153
|
+
loadState,
|
|
2154
|
+
loadedRowCount: indexed.rows.length,
|
|
2155
|
+
expectedRowCount,
|
|
1926
2156
|
state,
|
|
1927
2157
|
columns: ordered.visible,
|
|
1928
2158
|
allColumns: ordered.all,
|
|
1929
2159
|
rows,
|
|
1930
|
-
renderItems:
|
|
2160
|
+
renderItems: !model && state.expanded !== true && state.expanded.length === 0 ? mapList(rows, (row, rowIndex) => Object.freeze({
|
|
2161
|
+
kind: "row",
|
|
2162
|
+
key: getRowItemKey(row.key),
|
|
2163
|
+
row,
|
|
2164
|
+
rowIndex
|
|
2165
|
+
})) : Object.freeze(rows.flatMap((row, rowIndex) => {
|
|
1931
2166
|
const item = Object.freeze({
|
|
1932
2167
|
kind: "row",
|
|
1933
2168
|
key: getRowItemKey(row.key),
|
|
@@ -2688,12 +2923,341 @@ function createTable(options) {
|
|
|
2688
2923
|
value: direction === "undo" ? change.previous : change.value
|
|
2689
2924
|
})), false);
|
|
2690
2925
|
}
|
|
2926
|
+
function ingestionResult(appended, replaced, removed, problems) {
|
|
2927
|
+
return Object.freeze({
|
|
2928
|
+
status: appended + replaced + removed ? problems.length ? "partial" : "applied" : problems.length ? "rejected" : "unchanged",
|
|
2929
|
+
appended,
|
|
2930
|
+
replaced,
|
|
2931
|
+
removed,
|
|
2932
|
+
issues: Object.freeze([...problems])
|
|
2933
|
+
});
|
|
2934
|
+
}
|
|
2935
|
+
const stableSource = /* @__PURE__ */ new WeakMap();
|
|
2936
|
+
function ingestionKeys(rows) {
|
|
2937
|
+
let valid = stableSource.get(rows);
|
|
2938
|
+
if (valid === void 0) {
|
|
2939
|
+
valid = stableRows(rows);
|
|
2940
|
+
stableSource.set(rows, valid);
|
|
2941
|
+
}
|
|
2942
|
+
return valid;
|
|
2943
|
+
}
|
|
2944
|
+
function isProtectedSource(key) {
|
|
2945
|
+
return pendingChanges.entries.has(key) || validation.hasPending(key);
|
|
2946
|
+
}
|
|
2947
|
+
function affectedBranch(model, key) {
|
|
2948
|
+
const result = [key];
|
|
2949
|
+
const node = model?.byKey.get(key);
|
|
2950
|
+
if (!node) return result;
|
|
2951
|
+
let parent = node.parent;
|
|
2952
|
+
while (parent) {
|
|
2953
|
+
result.push(parent.row.key);
|
|
2954
|
+
parent = parent.parent;
|
|
2955
|
+
}
|
|
2956
|
+
const stack = [...node.children];
|
|
2957
|
+
while (stack.length) {
|
|
2958
|
+
const child = stack.pop();
|
|
2959
|
+
result.push(child.row.key);
|
|
2960
|
+
for (const descendant of child.children) stack.push(descendant);
|
|
2961
|
+
}
|
|
2962
|
+
return result;
|
|
2963
|
+
}
|
|
2964
|
+
function transformScene(source, updates, removed) {
|
|
2965
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2966
|
+
const replace = (rows) => rows.flatMap((row, index) => {
|
|
2967
|
+
const key = readKey(row, index);
|
|
2968
|
+
seen.add(key);
|
|
2969
|
+
return removed.has(key) ? [] : [updates.get(key) ?? row];
|
|
2970
|
+
});
|
|
2971
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2972
|
+
for (const [key, children] of source.lazy) if (!removed.has(key)) cache.set(key, replace(children));
|
|
2973
|
+
let next;
|
|
2974
|
+
if (!options.getChildren) next = replace(source.data);
|
|
2975
|
+
else {
|
|
2976
|
+
const model = buildTree(source.data, resolveColumns(definitions).byId, readKey, options, source.lazy);
|
|
2977
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
2978
|
+
for (const node of model.nodes.toReversed()) {
|
|
2979
|
+
const key = node.row.key;
|
|
2980
|
+
seen.add(key);
|
|
2981
|
+
if (removed.has(key)) continue;
|
|
2982
|
+
let row = updates.get(key) ?? node.row.original;
|
|
2983
|
+
const supplied = updates.has(key) ? options.getChildren(row) : void 0;
|
|
2984
|
+
if (supplied !== void 0) cache.delete(key);
|
|
2985
|
+
const children = supplied ?? node.children.filter((child) => !removed.has(child.row.key)).map((child) => replacements.get(child) ?? child.row.original);
|
|
2986
|
+
const old = options.getChildren(row) ?? node.children.map((child) => child.row.original);
|
|
2987
|
+
if (supplied === void 0 && (updates.has(key) && node.children.length > 0 && !cache.has(key) || children.length !== old.length || children.some((child, index) => child !== old[index]))) {
|
|
2988
|
+
if (!options.setChildren) throw new Error("Updating nested source children requires setChildren.");
|
|
2989
|
+
row = options.setChildren(row, children);
|
|
2990
|
+
if (cache.has(key)) cache.set(key, children);
|
|
2991
|
+
}
|
|
2992
|
+
replacements.set(node, row);
|
|
2993
|
+
}
|
|
2994
|
+
const roots = new Map(model.roots.filter((node) => node.writable).map((node) => [node.sourceIndex, node]));
|
|
2995
|
+
next = source.data.flatMap((row, index) => {
|
|
2996
|
+
const node = roots.get(index);
|
|
2997
|
+
return node && removed.has(node.row.key) ? [] : [node ? replacements.get(node) ?? row : row];
|
|
2998
|
+
});
|
|
2999
|
+
}
|
|
3000
|
+
const additions = [...updates].filter(([key]) => !seen.has(key) && !removed.has(key)).map(([, row]) => row);
|
|
3001
|
+
return {
|
|
3002
|
+
data: appendList(next, additions),
|
|
3003
|
+
lazy: cache
|
|
3004
|
+
};
|
|
3005
|
+
}
|
|
3006
|
+
function rebaseBatches(transform, removed) {
|
|
3007
|
+
undoStack.transform((entry) => {
|
|
3008
|
+
if (entry.kind === "cells") {
|
|
3009
|
+
if (!removed.size) return entry;
|
|
3010
|
+
const changes = entry.changes.filter((change) => !removed.has(change.rowKey));
|
|
3011
|
+
return changes.length ? {
|
|
3012
|
+
...entry,
|
|
3013
|
+
changes
|
|
3014
|
+
} : void 0;
|
|
3015
|
+
}
|
|
3016
|
+
const keys = entry.keys.filter((key) => !removed.has(key));
|
|
3017
|
+
return keys.length ? {
|
|
3018
|
+
...entry,
|
|
3019
|
+
keys,
|
|
3020
|
+
before: transform(entry.before),
|
|
3021
|
+
after: transform(entry.after)
|
|
3022
|
+
} : void 0;
|
|
3023
|
+
});
|
|
3024
|
+
}
|
|
3025
|
+
function newTreeIssues(previous, next) {
|
|
3026
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3027
|
+
for (const problem of previous) {
|
|
3028
|
+
const key = JSON.stringify([problem.code, problem.rowKey]);
|
|
3029
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
3030
|
+
}
|
|
3031
|
+
return next.filter((problem) => {
|
|
3032
|
+
const key = JSON.stringify([problem.code, problem.rowKey]);
|
|
3033
|
+
const remaining = counts.get(key) ?? 0;
|
|
3034
|
+
if (!remaining) return true;
|
|
3035
|
+
counts.set(key, remaining - 1);
|
|
3036
|
+
return false;
|
|
3037
|
+
});
|
|
3038
|
+
}
|
|
3039
|
+
function ingestData(incoming, mode) {
|
|
3040
|
+
const problems = [];
|
|
3041
|
+
const reject = (problem) => {
|
|
3042
|
+
ingestionIssues = [problem];
|
|
3043
|
+
notify();
|
|
3044
|
+
return ingestionResult(0, 0, 0, ingestionIssues);
|
|
3045
|
+
};
|
|
3046
|
+
if (destroyed) return reject(issue("invalid_ingestion", "A disposed table cannot receive source data."));
|
|
3047
|
+
if (!incoming.length) return ingestionResult(0, 0, 0, []);
|
|
3048
|
+
try {
|
|
3049
|
+
if (!ingestionKeys(data) || !stableRows(incoming)) return reject(issue("invalid_ingestion", "Source ingestion requires stable rowKey values or ids on every row."));
|
|
3050
|
+
} catch (error) {
|
|
3051
|
+
return reject(issue("invalid_ingestion", error instanceof Error ? error.message : String(error)));
|
|
3052
|
+
}
|
|
3053
|
+
const previous = sourceRows();
|
|
3054
|
+
const { byId, columns } = resolveColumns(definitions);
|
|
3055
|
+
const model = treeEnabled ? treeStage(data, byId, lazyVersion) : void 0;
|
|
3056
|
+
const seenBatch = /* @__PURE__ */ new Set();
|
|
3057
|
+
const updates = /* @__PURE__ */ new Map();
|
|
3058
|
+
for (const row of incoming) try {
|
|
3059
|
+
const key = readKey(row, previous.rows.length + updates.size);
|
|
3060
|
+
if (typeof key !== "string" && (typeof key !== "number" || !Number.isFinite(key))) {
|
|
3061
|
+
problems.push(issue("invalid_ingestion", "An incoming row key must be a string or finite number."));
|
|
3062
|
+
continue;
|
|
3063
|
+
}
|
|
3064
|
+
const existing = previous.byKey.get(key);
|
|
3065
|
+
if (seenBatch.has(key) || mode === "append" && existing) {
|
|
3066
|
+
problems.push(issue("duplicate_row_key", "An incoming source row repeats an existing key and is refused.", { rowKey: key }));
|
|
3067
|
+
continue;
|
|
3068
|
+
}
|
|
3069
|
+
seenBatch.add(key);
|
|
3070
|
+
const parent = options.getParentKey?.(row);
|
|
3071
|
+
const affected = affectedBranch(model, key);
|
|
3072
|
+
if (parent !== void 0 && parent !== null) affected.push(...affectedBranch(model, parent));
|
|
3073
|
+
if (affected.some(isProtectedSource)) {
|
|
3074
|
+
problems.push(issue("ingestion_conflict", "Incoming source data would overwrite a local change or pending validation; retry after save or revert.", { rowKey: key }));
|
|
3075
|
+
continue;
|
|
3076
|
+
}
|
|
3077
|
+
const node = model?.byKey.get(key);
|
|
3078
|
+
if (node && (!node.writable || node.lazy && options.getParentKey && options.getParentKey(row) !== node.parent?.row.key)) {
|
|
3079
|
+
problems.push(issue("invalid_ingestion", "This recovered or lazy tree row has no writable source position.", { rowKey: key }));
|
|
3080
|
+
continue;
|
|
3081
|
+
}
|
|
3082
|
+
if (existing?.original !== row) updates.set(key, row);
|
|
3083
|
+
} catch (error) {
|
|
3084
|
+
problems.push(issue("invalid_ingestion", error instanceof Error ? error.message : String(error)));
|
|
3085
|
+
}
|
|
3086
|
+
if (!updates.size) {
|
|
3087
|
+
ingestionIssues = problems;
|
|
3088
|
+
notify();
|
|
3089
|
+
return ingestionResult(0, 0, 0, problems);
|
|
3090
|
+
}
|
|
3091
|
+
const replaced = [...updates.keys()].filter((key) => previous.byKey.has(key)).length;
|
|
3092
|
+
const appended = updates.size - replaced;
|
|
3093
|
+
const previousState = state;
|
|
3094
|
+
try {
|
|
3095
|
+
if (!model && !replaced) {
|
|
3096
|
+
const added = Object.freeze([...updates.values()]);
|
|
3097
|
+
const indexed = added.map((row, offset) => createRow(row, readKey(row, previous.rows.length + offset), previous.rows.length + offset, byId, options.getRowCanExpand?.(row) ?? false));
|
|
3098
|
+
const filtered = manual ? previous.rows : filterStage(previous.rows, columns, state.search, state.filters);
|
|
3099
|
+
const addedFiltered = manual ? indexed : filterRows(indexed, columns, state.search, state.filters);
|
|
3100
|
+
const allRows = appendList(previous.rows, indexed);
|
|
3101
|
+
const allFiltered = appendList(filtered, addedFiltered);
|
|
3102
|
+
const keys = membership(filtered);
|
|
3103
|
+
for (const row of addedFiltered) keys.add(row.key);
|
|
3104
|
+
rowMembership.set(allFiltered, keys);
|
|
3105
|
+
const sorted = manual || !state.sorting.some((rule) => columns.some((column) => column.id === rule.column)) ? allFiltered : mergeRows(sortStage(filtered, columns, state.sorting), addedFiltered, columns, state.sorting);
|
|
3106
|
+
const next = appendList(data, added);
|
|
3107
|
+
rebaseBatches((source) => ({
|
|
3108
|
+
...source,
|
|
3109
|
+
data: appendList(source.data, added)
|
|
3110
|
+
}), /* @__PURE__ */ new Set());
|
|
3111
|
+
for (const row of indexed) previous.byKey.set(row.key, row);
|
|
3112
|
+
primedIndex = {
|
|
3113
|
+
data: next,
|
|
3114
|
+
columns: byId,
|
|
3115
|
+
result: {
|
|
3116
|
+
rows: allRows,
|
|
3117
|
+
byKey: previous.byKey,
|
|
3118
|
+
issues: previous.issues
|
|
3119
|
+
}
|
|
3120
|
+
};
|
|
3121
|
+
primedFilter = {
|
|
3122
|
+
rows: allRows,
|
|
3123
|
+
columns,
|
|
3124
|
+
search: state.search,
|
|
3125
|
+
filters: state.filters,
|
|
3126
|
+
result: allFiltered
|
|
3127
|
+
};
|
|
3128
|
+
primedSort = {
|
|
3129
|
+
rows: allFiltered,
|
|
3130
|
+
columns,
|
|
3131
|
+
sorting: state.sorting,
|
|
3132
|
+
result: sorted
|
|
3133
|
+
};
|
|
3134
|
+
stableSource.set(next, true);
|
|
3135
|
+
data = next;
|
|
3136
|
+
} else {
|
|
3137
|
+
const next = transformScene(scene(), updates, /* @__PURE__ */ new Set());
|
|
3138
|
+
const candidate = treeEnabled ? buildTree(next.data, byId, readKey, options, next.lazy) : indexRows(next.data, byId);
|
|
3139
|
+
const fresh = newTreeIssues(previous.issues, candidate.issues);
|
|
3140
|
+
if (fresh.length || !stableRows(candidate.rows.map((row) => row.original))) {
|
|
3141
|
+
problems.push(...fresh, ...!stableRows(candidate.rows.map((row) => row.original)) ? [issue("invalid_ingestion", "Every incoming descendant needs a stable key.")] : []);
|
|
3142
|
+
ingestionIssues = problems;
|
|
3143
|
+
notify();
|
|
3144
|
+
return ingestionResult(0, 0, 0, problems);
|
|
3145
|
+
}
|
|
3146
|
+
const nextKeys = new Set(candidate.rows.map((row) => row.key));
|
|
3147
|
+
const removedKeys = new Set(previous.rows.filter((row) => !nextKeys.has(row.key)).map((row) => row.key));
|
|
3148
|
+
rebaseBatches((source) => transformScene(source, updates, removedKeys), removedKeys);
|
|
3149
|
+
data = next.data;
|
|
3150
|
+
lazyChildren.clear();
|
|
3151
|
+
for (const [key, rows] of next.lazy) lazyChildren.set(key, rows);
|
|
3152
|
+
lazyVersion++;
|
|
3153
|
+
for (const key of [...updates.keys(), ...removedKeys]) {
|
|
3154
|
+
pendingLoads.get(key)?.abort();
|
|
3155
|
+
pendingLoads.delete(key);
|
|
3156
|
+
childStatuses.delete(key);
|
|
3157
|
+
loadIssues.delete(key);
|
|
3158
|
+
loadValidationIssues.delete(key);
|
|
3159
|
+
}
|
|
3160
|
+
validation.cancel([...removedKeys]);
|
|
3161
|
+
const selection = state.selection.filter((key) => !removedKeys.has(key));
|
|
3162
|
+
state = mergeState(state, {
|
|
3163
|
+
selection: selection.length === state.selection.length ? state.selection : selection,
|
|
3164
|
+
expanded: state.expanded === true ? true : state.expanded.filter((key) => !removedKeys.has(key))
|
|
3165
|
+
});
|
|
3166
|
+
}
|
|
3167
|
+
validation.cancel([...updates.keys()]);
|
|
3168
|
+
ingestionIssues = problems;
|
|
3169
|
+
} catch (error) {
|
|
3170
|
+
return reject(issue("invalid_ingestion", error instanceof Error ? error.message : String(error)));
|
|
3171
|
+
}
|
|
3172
|
+
if (state !== previousState) options.onStateChange?.(state, previousState);
|
|
3173
|
+
options.onDataChange?.(data, []);
|
|
3174
|
+
notify();
|
|
3175
|
+
startVisibleLoads();
|
|
3176
|
+
return ingestionResult(appended, replaced, 0, problems);
|
|
3177
|
+
}
|
|
3178
|
+
function removeData(keys) {
|
|
3179
|
+
const previousState = state;
|
|
3180
|
+
const problems = [];
|
|
3181
|
+
const removed = /* @__PURE__ */ new Set();
|
|
3182
|
+
const previous = sourceRows();
|
|
3183
|
+
const model = treeEnabled ? treeStage(data, resolveColumns(definitions).byId, lazyVersion) : void 0;
|
|
3184
|
+
if (destroyed || !ingestionKeys(data)) problems.push(issue("invalid_ingestion", "Source removal requires a live table and stable row keys."));
|
|
3185
|
+
else for (const key of new Set(keys)) {
|
|
3186
|
+
if (!previous.byKey.has(key)) {
|
|
3187
|
+
problems.push(issue("unknown_row", "The source row to remove does not exist.", { rowKey: key }));
|
|
3188
|
+
continue;
|
|
3189
|
+
}
|
|
3190
|
+
if (affectedBranch(model, key).some(isProtectedSource)) {
|
|
3191
|
+
problems.push(issue("ingestion_conflict", "Source removal would discard local changes or pending validation.", { rowKey: key }));
|
|
3192
|
+
continue;
|
|
3193
|
+
}
|
|
3194
|
+
const node = model?.byKey.get(key);
|
|
3195
|
+
if (node && !node.writable) {
|
|
3196
|
+
problems.push(issue("invalid_ingestion", "A recovered tree row has no writable source position.", { rowKey: key }));
|
|
3197
|
+
continue;
|
|
3198
|
+
}
|
|
3199
|
+
removed.add(key);
|
|
3200
|
+
const stack = node ? [...node.children] : [];
|
|
3201
|
+
while (stack.length) {
|
|
3202
|
+
const child = stack.pop();
|
|
3203
|
+
removed.add(child.row.key);
|
|
3204
|
+
for (const descendant of child.children) stack.push(descendant);
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
try {
|
|
3208
|
+
if (removed.size) {
|
|
3209
|
+
const next = transformScene(scene(), /* @__PURE__ */ new Map(), removed);
|
|
3210
|
+
rebaseBatches((source) => transformScene(source, /* @__PURE__ */ new Map(), removed), removed);
|
|
3211
|
+
data = next.data;
|
|
3212
|
+
lazyChildren.clear();
|
|
3213
|
+
for (const [key, rows] of next.lazy) lazyChildren.set(key, rows);
|
|
3214
|
+
lazyVersion++;
|
|
3215
|
+
validation.cancel([...removed]);
|
|
3216
|
+
for (const key of removed) {
|
|
3217
|
+
pendingLoads.get(key)?.abort();
|
|
3218
|
+
pendingLoads.delete(key);
|
|
3219
|
+
childStatuses.delete(key);
|
|
3220
|
+
loadIssues.delete(key);
|
|
3221
|
+
loadValidationIssues.delete(key);
|
|
3222
|
+
}
|
|
3223
|
+
const selection = state.selection.filter((key) => !removed.has(key));
|
|
3224
|
+
state = mergeState(state, {
|
|
3225
|
+
selection: selection.length === state.selection.length ? state.selection : selection,
|
|
3226
|
+
expanded: state.expanded === true ? true : state.expanded.filter((key) => !removed.has(key))
|
|
3227
|
+
});
|
|
3228
|
+
}
|
|
3229
|
+
ingestionIssues = problems;
|
|
3230
|
+
} catch (error) {
|
|
3231
|
+
ingestionIssues = [issue("invalid_ingestion", error instanceof Error ? error.message : String(error))];
|
|
3232
|
+
notify();
|
|
3233
|
+
return ingestionResult(0, 0, 0, ingestionIssues);
|
|
3234
|
+
}
|
|
3235
|
+
if (state !== previousState) options.onStateChange?.(state, previousState);
|
|
3236
|
+
if (removed.size) options.onDataChange?.(data, []);
|
|
3237
|
+
notify();
|
|
3238
|
+
startVisibleLoads();
|
|
3239
|
+
return ingestionResult(0, 0, removed.size, problems);
|
|
3240
|
+
}
|
|
2691
3241
|
if (expandMode === "single") {
|
|
2692
3242
|
initialState = mergeState(initialState, { expanded: canonicalExpanded(initialState.expanded) });
|
|
2693
3243
|
state = initialState;
|
|
2694
3244
|
snapshot = void 0;
|
|
2695
3245
|
}
|
|
2696
3246
|
const table = {
|
|
3247
|
+
appendData: (rows) => ingestData(rows, "append"),
|
|
3248
|
+
upsertData: (rows) => ingestData(rows, "upsert"),
|
|
3249
|
+
removeData,
|
|
3250
|
+
stream(source, streamOptions) {
|
|
3251
|
+
if (destroyed) return Promise.resolve(Object.freeze({
|
|
3252
|
+
status: "aborted",
|
|
3253
|
+
receivedRowCount: 0,
|
|
3254
|
+
issues: Object.freeze([issue("invalid_ingestion", "A disposed table cannot start a source.")])
|
|
3255
|
+
}));
|
|
3256
|
+
return streaming.run(source, {
|
|
3257
|
+
expectedRowCount: sourceExpected,
|
|
3258
|
+
...streamOptions
|
|
3259
|
+
});
|
|
3260
|
+
},
|
|
2697
3261
|
insertRows,
|
|
2698
3262
|
removeRows,
|
|
2699
3263
|
revert,
|
|
@@ -2725,6 +3289,11 @@ function createTable(options) {
|
|
|
2725
3289
|
setData(next) {
|
|
2726
3290
|
if (next === data) return;
|
|
2727
3291
|
validation.cancel();
|
|
3292
|
+
streaming.cancel();
|
|
3293
|
+
loadState = "idle";
|
|
3294
|
+
streamIssues = [];
|
|
3295
|
+
ingestionIssues = [];
|
|
3296
|
+
expectedRowCount = sourceExpected;
|
|
2728
3297
|
pendingChanges.entries.clear();
|
|
2729
3298
|
abortLoads(true);
|
|
2730
3299
|
lazyChildren.clear();
|
|
@@ -2959,6 +3528,7 @@ function createTable(options) {
|
|
|
2959
3528
|
},
|
|
2960
3529
|
destroy() {
|
|
2961
3530
|
destroyed = true;
|
|
3531
|
+
streaming.cancel();
|
|
2962
3532
|
validation.cancel();
|
|
2963
3533
|
abortLoads(true);
|
|
2964
3534
|
listeners.clear();
|