@vueye-table/core 3.0.0-alpha.2 → 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 +48 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +518 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -893,6 +893,11 @@ function createRow(original, key, index, columns, canExpand = false) {
|
|
|
893
893
|
original,
|
|
894
894
|
canExpand,
|
|
895
895
|
isExpanded: false,
|
|
896
|
+
depth: 0,
|
|
897
|
+
parentKey: void 0,
|
|
898
|
+
childCount: 0,
|
|
899
|
+
childStatus: "loaded",
|
|
900
|
+
selection: "none",
|
|
896
901
|
getValue,
|
|
897
902
|
getDisplay(columnId) {
|
|
898
903
|
const cached = displays.get(columnId);
|
|
@@ -961,6 +966,216 @@ function freezeState(state, previous) {
|
|
|
961
966
|
});
|
|
962
967
|
}
|
|
963
968
|
|
|
969
|
+
//#endregion
|
|
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
|
+
};
|
|
1085
|
+
}
|
|
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
|
|
1119
|
+
};
|
|
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))];
|
|
1161
|
+
}
|
|
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
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
|
|
964
1179
|
//#endregion
|
|
965
1180
|
//#region src/table.ts
|
|
966
1181
|
function isRecord(value) {
|
|
@@ -998,6 +1213,15 @@ function createTable(options) {
|
|
|
998
1213
|
const selectionMode = options.selectionMode ?? "multiple";
|
|
999
1214
|
const selectScope = options.selectScope ?? "all";
|
|
1000
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;
|
|
1001
1225
|
const undoStack = createUndoStack(options.historyLimit);
|
|
1002
1226
|
const listeners = /* @__PURE__ */ new Set();
|
|
1003
1227
|
const optionIssues = [];
|
|
@@ -1055,6 +1279,60 @@ function createTable(options) {
|
|
|
1055
1279
|
issues
|
|
1056
1280
|
};
|
|
1057
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
|
+
});
|
|
1058
1336
|
const orderColumns = memo((columns, columnOrder, hiddenColumns) => {
|
|
1059
1337
|
const position = new Map(columnOrder.map((id, index) => [id, index]));
|
|
1060
1338
|
const ordered = columns.map((column, index) => ({
|
|
@@ -1072,12 +1350,17 @@ function createTable(options) {
|
|
|
1072
1350
|
const filterStage = memo(filterRows);
|
|
1073
1351
|
const sortStage = memo(sortRows);
|
|
1074
1352
|
const selectedSet = memo((selection) => new Set(selection));
|
|
1075
|
-
const expansionStage = memo((indexed, expanded) => {
|
|
1353
|
+
const expansionStage = memo((indexed, expanded, selection) => {
|
|
1076
1354
|
const keys = expanded === true ? void 0 : new Set(expanded);
|
|
1077
|
-
const
|
|
1078
|
-
|
|
1079
|
-
isExpanded
|
|
1080
|
-
|
|
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
|
+
});
|
|
1081
1364
|
return {
|
|
1082
1365
|
rows,
|
|
1083
1366
|
byKey: new Map(rows.map((row) => [row.key, row]))
|
|
@@ -1100,14 +1383,20 @@ function createTable(options) {
|
|
|
1100
1383
|
}
|
|
1101
1384
|
function computeSnapshot() {
|
|
1102
1385
|
const { columns, byId, issues: columnIssues } = resolveColumns(definitions);
|
|
1103
|
-
const
|
|
1104
|
-
const
|
|
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);
|
|
1105
1392
|
const ordered = orderColumns(columns, state.columnOrder, state.hiddenColumns);
|
|
1106
1393
|
const issues = [
|
|
1107
1394
|
...optionIssues,
|
|
1108
1395
|
...columnIssues,
|
|
1109
1396
|
...indexed.issues,
|
|
1110
|
-
...stateIssues(byId)
|
|
1397
|
+
...stateIssues(byId),
|
|
1398
|
+
...loadIssues.values(),
|
|
1399
|
+
...Array.from(loadValidationIssues.values()).flat()
|
|
1111
1400
|
];
|
|
1112
1401
|
const requestedSize = state.pagination.pageSize;
|
|
1113
1402
|
let pageSize = Number.isInteger(requestedSize) && requestedSize >= 1 ? requestedSize : 10;
|
|
@@ -1116,7 +1405,58 @@ function createTable(options) {
|
|
|
1116
1405
|
let filteredCount;
|
|
1117
1406
|
let page;
|
|
1118
1407
|
let pageCount;
|
|
1119
|
-
|
|
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) {
|
|
1120
1460
|
processedRows = expanded.rows;
|
|
1121
1461
|
rows = expanded.rows;
|
|
1122
1462
|
filteredCount = rowCount ?? indexed.rows.length;
|
|
@@ -1160,7 +1500,7 @@ function createTable(options) {
|
|
|
1160
1500
|
row,
|
|
1161
1501
|
rowIndex
|
|
1162
1502
|
});
|
|
1163
|
-
return row.isExpanded ? [item, Object.freeze({
|
|
1503
|
+
return row.isExpanded && (!model || (options.getRowCanExpand?.(row.original) ?? false)) ? [item, Object.freeze({
|
|
1164
1504
|
kind: "detail",
|
|
1165
1505
|
key: getRowItemKey(row.key, "detail"),
|
|
1166
1506
|
row,
|
|
@@ -1173,14 +1513,14 @@ function createTable(options) {
|
|
|
1173
1513
|
pageSize,
|
|
1174
1514
|
pageCount,
|
|
1175
1515
|
rowCount: filteredCount,
|
|
1176
|
-
totalRowCount: manual ? filteredCount :
|
|
1516
|
+
totalRowCount: manual ? filteredCount : totalCount,
|
|
1177
1517
|
pageStart,
|
|
1178
|
-
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,
|
|
1179
1519
|
pageItems: paginationItems(page, pageCount),
|
|
1180
1520
|
canPreviousPage: page > 1,
|
|
1181
1521
|
canNextPage: page < pageCount,
|
|
1182
1522
|
selectionMode,
|
|
1183
|
-
selectedCount: state.selection.length,
|
|
1523
|
+
selectedCount: model ? state.selection.filter((key) => model.byKey.has(key)).length : state.selection.length,
|
|
1184
1524
|
pageSelection: coverage(pageSelected, rows.length),
|
|
1185
1525
|
allSelection: coverage(scopeSelected, scopeRows.length),
|
|
1186
1526
|
canUndo: undoStack.canUndo,
|
|
@@ -1189,7 +1529,7 @@ function createTable(options) {
|
|
|
1189
1529
|
isSelected: (key) => selected.has(key),
|
|
1190
1530
|
getSort: (columnId) => sortIndex.get(columnId),
|
|
1191
1531
|
getColumn: (columnId) => byId.get(columnId),
|
|
1192
|
-
getRow: (key) => expanded.byKey.get(key)
|
|
1532
|
+
getRow: (key) => treeRow ? treeRow(key) : expanded.byKey.get(key)
|
|
1193
1533
|
});
|
|
1194
1534
|
}
|
|
1195
1535
|
function getSnapshot() {
|
|
@@ -1202,6 +1542,96 @@ function createTable(options) {
|
|
|
1202
1542
|
const next = getSnapshot();
|
|
1203
1543
|
for (const listener of listeners) listener(next);
|
|
1204
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
|
+
}
|
|
1205
1635
|
/** Apply a change made by a table operation. */
|
|
1206
1636
|
function commit(patch) {
|
|
1207
1637
|
const previous = state;
|
|
@@ -1209,13 +1639,21 @@ function createTable(options) {
|
|
|
1209
1639
|
const next = mergePatch(patch);
|
|
1210
1640
|
if (next === previous && previousIssue === expandedIssue) return;
|
|
1211
1641
|
state = next;
|
|
1642
|
+
snapshot = void 0;
|
|
1643
|
+
abortLoads();
|
|
1212
1644
|
if (next !== previous) options.onStateChange?.(state, previous);
|
|
1213
1645
|
notify();
|
|
1646
|
+
startVisibleLoads();
|
|
1214
1647
|
}
|
|
1215
1648
|
function canonicalExpanded(expanded, candidate = state) {
|
|
1216
1649
|
if (expandMode === "multiple") return expanded;
|
|
1217
1650
|
if (expanded !== true) return expanded.slice(-1);
|
|
1218
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
|
+
}
|
|
1219
1657
|
const indexed = indexRows(data, byId);
|
|
1220
1658
|
const first = (manual ? indexed.rows : sortStage(filterStage(indexed.rows, columns, candidate.search, candidate.filters), columns, candidate.sorting)).find((row) => row.canExpand);
|
|
1221
1659
|
return first ? [first.key] : [];
|
|
@@ -1262,6 +1700,9 @@ function createTable(options) {
|
|
|
1262
1700
|
function applyChanges(edits, record, recoveredIssues = []) {
|
|
1263
1701
|
const current = getSnapshot();
|
|
1264
1702
|
const working = [...data];
|
|
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;
|
|
1265
1706
|
const issues = [...recoveredIssues];
|
|
1266
1707
|
const changes = [];
|
|
1267
1708
|
const samples = /* @__PURE__ */ new Map();
|
|
@@ -1299,7 +1740,22 @@ function createTable(options) {
|
|
|
1299
1740
|
}));
|
|
1300
1741
|
continue;
|
|
1301
1742
|
}
|
|
1302
|
-
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
|
+
}
|
|
1303
1759
|
if (record && !column.isEditable(original)) {
|
|
1304
1760
|
issues.push(issue("read_only_cell", `The "${column.id}" cell of row "${String(row.key)}" is read-only.`, {
|
|
1305
1761
|
rowKey: row.key,
|
|
@@ -1329,7 +1785,21 @@ function createTable(options) {
|
|
|
1329
1785
|
}));
|
|
1330
1786
|
continue;
|
|
1331
1787
|
}
|
|
1332
|
-
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
|
+
}
|
|
1333
1803
|
changes.push({
|
|
1334
1804
|
rowKey: row.key,
|
|
1335
1805
|
column: column.id,
|
|
@@ -1372,7 +1842,7 @@ function createTable(options) {
|
|
|
1372
1842
|
return edits;
|
|
1373
1843
|
}
|
|
1374
1844
|
function replay(changes, direction) {
|
|
1375
|
-
applyChanges(changes.map((change) => ({
|
|
1845
|
+
applyChanges((direction === "undo" ? changes.toReversed() : changes).map((change) => ({
|
|
1376
1846
|
rowKey: change.rowKey,
|
|
1377
1847
|
column: change.column,
|
|
1378
1848
|
value: direction === "undo" ? change.previous : change.value
|
|
@@ -1397,14 +1867,24 @@ function createTable(options) {
|
|
|
1397
1867
|
const next = mergePatch(patch);
|
|
1398
1868
|
if (next !== state || previousIssue !== expandedIssue) {
|
|
1399
1869
|
state = next;
|
|
1870
|
+
snapshot = void 0;
|
|
1871
|
+
abortLoads();
|
|
1400
1872
|
notify();
|
|
1873
|
+
startVisibleLoads();
|
|
1401
1874
|
}
|
|
1402
1875
|
},
|
|
1403
1876
|
setData(next) {
|
|
1404
1877
|
if (next === data) return;
|
|
1878
|
+
abortLoads(true);
|
|
1879
|
+
lazyChildren.clear();
|
|
1880
|
+
childStatuses.clear();
|
|
1881
|
+
loadIssues.clear();
|
|
1882
|
+
loadValidationIssues.clear();
|
|
1883
|
+
lazyVersion++;
|
|
1405
1884
|
data = next;
|
|
1406
1885
|
undoStack.clear();
|
|
1407
1886
|
notify();
|
|
1887
|
+
startVisibleLoads();
|
|
1408
1888
|
},
|
|
1409
1889
|
setColumns(next) {
|
|
1410
1890
|
if (next === definitions) return;
|
|
@@ -1475,14 +1955,14 @@ function createTable(options) {
|
|
|
1475
1955
|
} });
|
|
1476
1956
|
},
|
|
1477
1957
|
select(keys) {
|
|
1478
|
-
applySelection([...state.selection, ...keys]);
|
|
1958
|
+
applySelection([...state.selection, ...selectionKeys(keys)]);
|
|
1479
1959
|
},
|
|
1480
1960
|
deselect(keys) {
|
|
1481
|
-
const removed = new Set(keys);
|
|
1961
|
+
const removed = new Set(selectionKeys(keys));
|
|
1482
1962
|
applySelection(state.selection.filter((key) => !removed.has(key)));
|
|
1483
1963
|
},
|
|
1484
1964
|
toggleRow(key, selected) {
|
|
1485
|
-
const isSelected = state.selection.includes(key);
|
|
1965
|
+
const isSelected = treeEnabled ? getSnapshot().getRow(key)?.selection === "all" : state.selection.includes(key);
|
|
1486
1966
|
const next = selected ?? !isSelected;
|
|
1487
1967
|
if (next === isSelected) return;
|
|
1488
1968
|
if (next) table.select([key]);
|
|
@@ -1509,12 +1989,16 @@ function createTable(options) {
|
|
|
1509
1989
|
const row = getSnapshot().getRow(key);
|
|
1510
1990
|
if (!row?.canExpand) return;
|
|
1511
1991
|
const open = expanded ?? !row.isExpanded;
|
|
1512
|
-
if (open === row.isExpanded)
|
|
1992
|
+
if (open === row.isExpanded) {
|
|
1993
|
+
if (open && row.childStatus === "error") requestChildren(row);
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1513
1996
|
if (expandMode === "single") commit({ expanded: open ? [key] : [] });
|
|
1514
1997
|
else if (state.expanded === true) {
|
|
1515
1998
|
const { byId } = resolveColumns(definitions);
|
|
1516
|
-
commit({ expanded: indexRows(data, byId).rows.filter((candidate) => candidate.canExpand && candidate.key !== key).map((candidate) => candidate.key) });
|
|
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) });
|
|
1517
2000
|
} else commit({ expanded: open ? [...state.expanded, key] : state.expanded.filter((candidate) => candidate !== key) });
|
|
2001
|
+
if (open) requestChildren(getSnapshot().getRow(key));
|
|
1518
2002
|
},
|
|
1519
2003
|
expandAll() {
|
|
1520
2004
|
commit({ expanded: true });
|
|
@@ -1595,26 +2079,34 @@ function createTable(options) {
|
|
|
1595
2079
|
return row !== void 0 && current.getColumn(edit.column)?.isEditable(row.original) === true;
|
|
1596
2080
|
}), true);
|
|
1597
2081
|
},
|
|
1598
|
-
exportRows({ format = "csv", columns: ids, pageOnly = false, headers = true, escapeFormulas = true } = {}) {
|
|
2082
|
+
exportRows({ format = "csv", columns: ids, pageOnly = false, headers = true, escapeFormulas = true, depth = false, indent = "" } = {}) {
|
|
1599
2083
|
const current = getSnapshot();
|
|
1600
2084
|
const columns = ids ? ids.flatMap((id) => {
|
|
1601
2085
|
const column = current.getColumn(id);
|
|
1602
2086
|
return column ? [column] : [];
|
|
1603
2087
|
}) : current.columns;
|
|
1604
|
-
const
|
|
1605
|
-
|
|
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);
|
|
1606
2091
|
return escapeFormulas ? escapeFormula(display, row.getValue(column.id)) : display;
|
|
1607
2092
|
}));
|
|
1608
|
-
if (
|
|
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
|
+
}
|
|
1609
2098
|
return toDelimited(matrix, format === "csv" ? "," : " ");
|
|
1610
2099
|
},
|
|
1611
2100
|
reset() {
|
|
1612
2101
|
commit(initialState);
|
|
1613
2102
|
},
|
|
1614
2103
|
destroy() {
|
|
2104
|
+
destroyed = true;
|
|
2105
|
+
abortLoads(true);
|
|
1615
2106
|
listeners.clear();
|
|
1616
2107
|
}
|
|
1617
2108
|
};
|
|
2109
|
+
startVisibleLoads();
|
|
1618
2110
|
return table;
|
|
1619
2111
|
}
|
|
1620
2112
|
|