@vueye-table/core 3.0.0-alpha.3 → 3.0.0-alpha.5
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 +142 -38
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1476 -48
- 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) {
|
|
@@ -898,6 +931,8 @@ function createRow(original, key, index, columns, canExpand = false) {
|
|
|
898
931
|
childCount: 0,
|
|
899
932
|
childStatus: "loaded",
|
|
900
933
|
selection: "none",
|
|
934
|
+
isDirty: false,
|
|
935
|
+
cellIssues: void 0,
|
|
901
936
|
getValue,
|
|
902
937
|
getDisplay(columnId) {
|
|
903
938
|
const cached = displays.get(columnId);
|
|
@@ -966,6 +1001,522 @@ function freezeState(state, previous) {
|
|
|
966
1001
|
});
|
|
967
1002
|
}
|
|
968
1003
|
|
|
1004
|
+
//#endregion
|
|
1005
|
+
//#region src/validation.ts
|
|
1006
|
+
function isPromise(value) {
|
|
1007
|
+
return typeof value === "object" && value !== null && "then" in value;
|
|
1008
|
+
}
|
|
1009
|
+
/** Validators receive values; their messages remain plain text. */
|
|
1010
|
+
function runValidation(validate, rowKey, column) {
|
|
1011
|
+
const fail = (message, validationCode) => issue("validation_failed", message, {
|
|
1012
|
+
rowKey,
|
|
1013
|
+
column,
|
|
1014
|
+
validationCode
|
|
1015
|
+
});
|
|
1016
|
+
const read = (result) => {
|
|
1017
|
+
if (result === true) return void 0;
|
|
1018
|
+
if (typeof result === "string") return fail(result);
|
|
1019
|
+
if (result && typeof result.message === "string") return fail(result.message, result.code);
|
|
1020
|
+
return fail("The validator returned an invalid result.");
|
|
1021
|
+
};
|
|
1022
|
+
const thrown = (error) => fail(error instanceof Error ? error.message : String(error));
|
|
1023
|
+
try {
|
|
1024
|
+
const result = validate();
|
|
1025
|
+
return isPromise(result) ? Promise.resolve(result).then(read, thrown) : read(result);
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
return thrown(error);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
function editorFailure(spec, value) {
|
|
1031
|
+
if (!spec) return void 0;
|
|
1032
|
+
if (spec.maxLength !== void 0 && (!Number.isSafeInteger(spec.maxLength) || spec.maxLength < 0)) return "Editor maxLength must be a non-negative safe integer.";
|
|
1033
|
+
if (spec.min !== void 0 && !Number.isFinite(spec.min) || spec.max !== void 0 && !Number.isFinite(spec.max) || spec.min !== void 0 && spec.max !== void 0 && spec.min > spec.max) return "Editor bounds must be finite and ordered.";
|
|
1034
|
+
if (spec.options && !spec.options.some((option) => Object.is(option, value))) return "Choose one of the allowed values.";
|
|
1035
|
+
if (spec.maxLength !== void 0 && typeof value === "string" && value.length > spec.maxLength) return `Use at most ${spec.maxLength} characters.`;
|
|
1036
|
+
if (spec.min !== void 0 || spec.max !== void 0) {
|
|
1037
|
+
const number = value instanceof Date ? value.getTime() : value;
|
|
1038
|
+
if (typeof number !== "number" || !Number.isFinite(number)) return "Enter a finite numeric value.";
|
|
1039
|
+
if (spec.min !== void 0 && number < spec.min) return `Use a value of at least ${spec.min}.`;
|
|
1040
|
+
if (spec.max !== void 0 && number > spec.max) return `Use a value of at most ${spec.max}.`;
|
|
1041
|
+
}
|
|
1042
|
+
if (spec.pattern !== void 0) try {
|
|
1043
|
+
if (typeof value !== "string" || !new RegExp(spec.pattern, "u").test(value)) return "The value does not match the required pattern.";
|
|
1044
|
+
} catch {
|
|
1045
|
+
return "The editor pattern is invalid.";
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
//#endregion
|
|
1050
|
+
//#region src/edit-validation.ts
|
|
1051
|
+
function readonlyMap(source) {
|
|
1052
|
+
const copy = new Map(source);
|
|
1053
|
+
const result = {
|
|
1054
|
+
size: copy.size,
|
|
1055
|
+
get: (key) => copy.get(key),
|
|
1056
|
+
has: (key) => copy.has(key),
|
|
1057
|
+
entries: () => copy.entries(),
|
|
1058
|
+
keys: () => copy.keys(),
|
|
1059
|
+
values: () => copy.values(),
|
|
1060
|
+
[Symbol.iterator]: () => copy[Symbol.iterator](),
|
|
1061
|
+
forEach(callback, thisArg) {
|
|
1062
|
+
copy.forEach((value, key) => callback.call(thisArg, value, key, result));
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
return Object.freeze(result);
|
|
1066
|
+
}
|
|
1067
|
+
/** All promises in one edit settle together. Tokens fence every cell against stale results. */
|
|
1068
|
+
function createEditValidation(host) {
|
|
1069
|
+
const tokens = /* @__PURE__ */ new Map();
|
|
1070
|
+
const pending = /* @__PURE__ */ new Map();
|
|
1071
|
+
const problems = /* @__PURE__ */ new Map();
|
|
1072
|
+
const current = (candidate) => tokens.get(candidate.edit.rowKey)?.get(candidate.edit.column) === candidate.token;
|
|
1073
|
+
const tokenFor = (rowKey, column) => {
|
|
1074
|
+
const row = tokens.get(rowKey) ?? /* @__PURE__ */ new Map();
|
|
1075
|
+
const token = {};
|
|
1076
|
+
row.set(column, token);
|
|
1077
|
+
tokens.set(rowKey, row);
|
|
1078
|
+
pending.get(rowKey)?.delete(column);
|
|
1079
|
+
problems.get(rowKey)?.delete(column);
|
|
1080
|
+
return token;
|
|
1081
|
+
};
|
|
1082
|
+
const setIssues = (issues) => {
|
|
1083
|
+
for (const problem of issues) {
|
|
1084
|
+
if (problem.rowKey === void 0 || problem.column === void 0) continue;
|
|
1085
|
+
const row = problems.get(problem.rowKey) ?? /* @__PURE__ */ new Map();
|
|
1086
|
+
row.set(problem.column, problem);
|
|
1087
|
+
problems.set(problem.rowKey, row);
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
function rowValidation(candidates, failures) {
|
|
1091
|
+
if (!host.validateRow) return [...candidates];
|
|
1092
|
+
const rows = /* @__PURE__ */ new Map();
|
|
1093
|
+
for (const candidate of candidates) {
|
|
1094
|
+
const group = rows.get(candidate.edit.rowKey) ?? [];
|
|
1095
|
+
group.push(candidate);
|
|
1096
|
+
rows.set(candidate.edit.rowKey, group);
|
|
1097
|
+
}
|
|
1098
|
+
const results = [];
|
|
1099
|
+
for (const [key, group] of rows) {
|
|
1100
|
+
const previous = group[0].original;
|
|
1101
|
+
let next = previous;
|
|
1102
|
+
try {
|
|
1103
|
+
for (const candidate of group) next = candidate.column.setValue(next, candidate.edit.value) ?? next;
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
for (const candidate of group) failures.push(issue("validation_failed", String(error), {
|
|
1106
|
+
rowKey: key,
|
|
1107
|
+
column: candidate.edit.column
|
|
1108
|
+
}));
|
|
1109
|
+
continue;
|
|
1110
|
+
}
|
|
1111
|
+
const validation = runValidation(() => host.validateRow(next, previous), key);
|
|
1112
|
+
const accept = (problem) => {
|
|
1113
|
+
if (!problem) return group;
|
|
1114
|
+
for (const candidate of group) if (current(candidate)) failures.push(Object.freeze({
|
|
1115
|
+
...problem,
|
|
1116
|
+
column: candidate.edit.column
|
|
1117
|
+
}));
|
|
1118
|
+
return [];
|
|
1119
|
+
};
|
|
1120
|
+
results.push(isPromise(validation) ? validation.then(accept) : accept(validation));
|
|
1121
|
+
}
|
|
1122
|
+
return results.some(isPromise) ? Promise.all(results.map((result) => Promise.resolve(result))).then((groups) => groups.flat()) : results.flat();
|
|
1123
|
+
}
|
|
1124
|
+
function settle(accepted, all, failures, asynchronous) {
|
|
1125
|
+
return host.batch(() => {
|
|
1126
|
+
const live = accepted.filter(current);
|
|
1127
|
+
const liveKeys = new Set(live);
|
|
1128
|
+
const active = all.filter(current);
|
|
1129
|
+
for (const candidate of active) {
|
|
1130
|
+
const key = candidate.edit.rowKey;
|
|
1131
|
+
pending.get(key)?.delete(candidate.edit.column);
|
|
1132
|
+
if (pending.get(key)?.size === 0) pending.delete(key);
|
|
1133
|
+
}
|
|
1134
|
+
const visibleFailures = failures;
|
|
1135
|
+
setIssues(visibleFailures);
|
|
1136
|
+
let result;
|
|
1137
|
+
if (asynchronous && host.optimistic) {
|
|
1138
|
+
const rollback = active.filter((candidate) => !liveKeys.has(candidate)).map((candidate) => ({
|
|
1139
|
+
...candidate.edit,
|
|
1140
|
+
value: candidate.previous
|
|
1141
|
+
}));
|
|
1142
|
+
result = host.apply(rollback, false, visibleFailures);
|
|
1143
|
+
const changes = live.map((candidate) => Object.freeze({
|
|
1144
|
+
rowKey: candidate.edit.rowKey,
|
|
1145
|
+
column: candidate.edit.column,
|
|
1146
|
+
previous: candidate.previous,
|
|
1147
|
+
value: candidate.edit.value,
|
|
1148
|
+
row: host.snapshot().getRow(candidate.edit.rowKey).original
|
|
1149
|
+
}));
|
|
1150
|
+
if (changes.length) host.record(changes);
|
|
1151
|
+
result = Object.freeze({
|
|
1152
|
+
...result,
|
|
1153
|
+
changes: Object.freeze(changes),
|
|
1154
|
+
status: changes.length ? visibleFailures.length ? "partial" : "applied" : visibleFailures.length ? "rejected" : "unchanged"
|
|
1155
|
+
});
|
|
1156
|
+
} else result = host.apply(live.map((candidate) => candidate.edit), true, visibleFailures);
|
|
1157
|
+
setIssues(result.issues);
|
|
1158
|
+
if (!result.changes.length && (all.length || result.issues.length) || asynchronous && host.optimistic) host.notify();
|
|
1159
|
+
if (asynchronous && !result.issues.length && active.length) host.onIssues([]);
|
|
1160
|
+
return result;
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
return {
|
|
1164
|
+
hasPending: (key) => (pending.get(key)?.size ?? 0) > 0,
|
|
1165
|
+
cells: () => Object.freeze([...pending.values()].flatMap((row) => [...row.values()])),
|
|
1166
|
+
problems: () => new Map([...problems].filter(([, row]) => row.size).map(([key, row]) => [key, readonlyMap(row)])),
|
|
1167
|
+
cancel(keys) {
|
|
1168
|
+
if (!keys) {
|
|
1169
|
+
tokens.clear();
|
|
1170
|
+
pending.clear();
|
|
1171
|
+
problems.clear();
|
|
1172
|
+
} else for (const key of keys) {
|
|
1173
|
+
tokens.delete(key);
|
|
1174
|
+
pending.delete(key);
|
|
1175
|
+
problems.delete(key);
|
|
1176
|
+
}
|
|
1177
|
+
},
|
|
1178
|
+
edit(edits, recovered = []) {
|
|
1179
|
+
const metadataChanged = edits.some((edit) => pending.get(edit.rowKey)?.has(edit.column) || problems.get(edit.rowKey)?.has(edit.column) || host.validateRow && pending.get(edit.rowKey)?.size);
|
|
1180
|
+
const snapshot = host.snapshot();
|
|
1181
|
+
const failures = [...recovered];
|
|
1182
|
+
const candidates = [];
|
|
1183
|
+
const drafts = /* @__PURE__ */ new Map();
|
|
1184
|
+
const samples = /* @__PURE__ */ new Map();
|
|
1185
|
+
if (host.validateRow) for (const key of new Set(edits.map((edit) => edit.rowKey))) {
|
|
1186
|
+
tokens.delete(key);
|
|
1187
|
+
pending.delete(key);
|
|
1188
|
+
}
|
|
1189
|
+
for (const edit of edits) {
|
|
1190
|
+
const token = tokenFor(edit.rowKey, edit.column);
|
|
1191
|
+
const row = snapshot.getRow(edit.rowKey);
|
|
1192
|
+
const column = snapshot.getColumn(edit.column);
|
|
1193
|
+
const fail = (code, message) => {
|
|
1194
|
+
failures.push(issue(code, message, {
|
|
1195
|
+
rowKey: edit.rowKey,
|
|
1196
|
+
column: edit.column
|
|
1197
|
+
}));
|
|
1198
|
+
};
|
|
1199
|
+
if (!isSafePath(edit.column)) {
|
|
1200
|
+
fail("unsafe_path", "This path addresses a prototype and cannot be written.");
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
1203
|
+
if (!row) {
|
|
1204
|
+
fail("unknown_row", `No row has the key "${String(edit.rowKey)}".`);
|
|
1205
|
+
continue;
|
|
1206
|
+
}
|
|
1207
|
+
if (!column) {
|
|
1208
|
+
fail("unknown_column", `No column has the id "${edit.column}".`);
|
|
1209
|
+
continue;
|
|
1210
|
+
}
|
|
1211
|
+
const original = drafts.get(row.key) ?? row.original;
|
|
1212
|
+
try {
|
|
1213
|
+
if (!column.isEditable(original)) {
|
|
1214
|
+
fail("read_only_cell", `The "${column.id}" cell is read-only.`);
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
let value;
|
|
1218
|
+
if ("input" in edit) {
|
|
1219
|
+
if (!samples.has(column.id)) samples.set(column.id, snapshot.processedRows.find((candidate) => candidate.getValue(column.id) !== null && candidate.getValue(column.id) !== void 0)?.getValue(column.id));
|
|
1220
|
+
const parsed = column.parse(edit.input, original, samples.get(column.id));
|
|
1221
|
+
if (!parsed.ok) {
|
|
1222
|
+
fail("invalid_value", parsed.message);
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
value = parsed.value;
|
|
1226
|
+
} else value = edit.value;
|
|
1227
|
+
const previous = column.getValue(original);
|
|
1228
|
+
if (Object.is(previous, value)) continue;
|
|
1229
|
+
const constraint = editorFailure(column.definition.editor, value);
|
|
1230
|
+
if (constraint) {
|
|
1231
|
+
fail("validation_failed", constraint);
|
|
1232
|
+
continue;
|
|
1233
|
+
}
|
|
1234
|
+
const next = column.setValue(original, value);
|
|
1235
|
+
if (next === void 0) {
|
|
1236
|
+
fail("read_only_cell", "The column has no way to write a value.");
|
|
1237
|
+
continue;
|
|
1238
|
+
}
|
|
1239
|
+
const writeProblem = host.writeIssue(row, next);
|
|
1240
|
+
if (writeProblem) {
|
|
1241
|
+
failures.push(Object.freeze({
|
|
1242
|
+
...writeProblem,
|
|
1243
|
+
column: column.id
|
|
1244
|
+
}));
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
const validate = column.definition.validate;
|
|
1248
|
+
const validation = validate ? runValidation(() => validate(value, row.original), row.key, column.id) : void 0;
|
|
1249
|
+
if (validation && !isPromise(validation)) {
|
|
1250
|
+
failures.push(validation);
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
drafts.set(row.key, next);
|
|
1254
|
+
candidates.push({
|
|
1255
|
+
edit: {
|
|
1256
|
+
rowKey: row.key,
|
|
1257
|
+
column: column.id,
|
|
1258
|
+
value
|
|
1259
|
+
},
|
|
1260
|
+
previous: column.getValue(row.original),
|
|
1261
|
+
original: row.original,
|
|
1262
|
+
column,
|
|
1263
|
+
validation,
|
|
1264
|
+
token
|
|
1265
|
+
});
|
|
1266
|
+
} catch (error) {
|
|
1267
|
+
fail("invalid_value", error instanceof Error ? error.message : String(error));
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
const asynchronous = candidates.some((candidate) => isPromise(candidate.validation));
|
|
1271
|
+
const validateRows = (accepted) => rowValidation(accepted.filter(current), failures);
|
|
1272
|
+
const outcome = asynchronous ? Promise.all(candidates.map(async (candidate) => {
|
|
1273
|
+
const problem = await candidate.validation;
|
|
1274
|
+
if (problem) {
|
|
1275
|
+
if (current(candidate)) failures.push(problem);
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
return candidate;
|
|
1279
|
+
})).then((accepted) => validateRows(accepted.filter((candidate) => candidate !== void 0))) : validateRows(candidates);
|
|
1280
|
+
if (!isPromise(outcome)) {
|
|
1281
|
+
const result = settle(outcome, candidates, failures, false);
|
|
1282
|
+
if (metadataChanged && !candidates.length && !failures.length) host.notify();
|
|
1283
|
+
return result;
|
|
1284
|
+
}
|
|
1285
|
+
for (const candidate of candidates) {
|
|
1286
|
+
const row = pending.get(candidate.edit.rowKey) ?? /* @__PURE__ */ new Map();
|
|
1287
|
+
row.set(candidate.edit.column, Object.freeze({ ...candidate.edit }));
|
|
1288
|
+
pending.set(candidate.edit.rowKey, row);
|
|
1289
|
+
}
|
|
1290
|
+
setIssues(failures);
|
|
1291
|
+
const optimistic = host.optimistic ? host.apply(candidates.map((candidate) => candidate.edit), false, failures) : void 0;
|
|
1292
|
+
if (!optimistic?.changes.length) host.notify();
|
|
1293
|
+
if (!host.optimistic && failures.length) host.onIssues(failures);
|
|
1294
|
+
outcome.then((accepted) => {
|
|
1295
|
+
if (candidates.some(current)) settle(accepted, candidates, failures, true);
|
|
1296
|
+
});
|
|
1297
|
+
return Object.freeze({
|
|
1298
|
+
status: "pending",
|
|
1299
|
+
changes: optimistic?.changes ?? Object.freeze([]),
|
|
1300
|
+
rowChanges: Object.freeze([]),
|
|
1301
|
+
issues: Object.freeze([...failures]),
|
|
1302
|
+
pendingCells: Object.freeze(candidates.map((candidate) => Object.freeze({ ...candidate.edit })))
|
|
1303
|
+
});
|
|
1304
|
+
}
|
|
1305
|
+
};
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
//#endregion
|
|
1309
|
+
//#region src/pending-changes.ts
|
|
1310
|
+
/** A saved baseline only for touched rows; viewing changes never scans source data. */
|
|
1311
|
+
function createPendingChanges() {
|
|
1312
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1313
|
+
return {
|
|
1314
|
+
entries,
|
|
1315
|
+
snapshot() {
|
|
1316
|
+
const inserted = [];
|
|
1317
|
+
const removed = [];
|
|
1318
|
+
const updated = [];
|
|
1319
|
+
for (const [rowKey, entry] of entries) if (entry.previous === void 0 && entry.row !== void 0) inserted.push(Object.freeze({
|
|
1320
|
+
kind: "inserted",
|
|
1321
|
+
rowKey,
|
|
1322
|
+
row: entry.row,
|
|
1323
|
+
position: entry.position
|
|
1324
|
+
}));
|
|
1325
|
+
else if (entry.row === void 0 && entry.previous !== void 0) removed.push(Object.freeze({
|
|
1326
|
+
kind: "removed",
|
|
1327
|
+
rowKey,
|
|
1328
|
+
row: entry.previous,
|
|
1329
|
+
position: entry.position
|
|
1330
|
+
}));
|
|
1331
|
+
else if (entry.row !== void 0 && entry.previous !== void 0) updated.push(Object.freeze({
|
|
1332
|
+
rowKey,
|
|
1333
|
+
row: entry.row,
|
|
1334
|
+
previous: entry.previous,
|
|
1335
|
+
changes: Object.freeze([...entry.cells.values()])
|
|
1336
|
+
}));
|
|
1337
|
+
return Object.freeze({
|
|
1338
|
+
inserted: Object.freeze(inserted),
|
|
1339
|
+
updated: Object.freeze(updated),
|
|
1340
|
+
removed: Object.freeze(removed)
|
|
1341
|
+
});
|
|
1342
|
+
},
|
|
1343
|
+
record(key, previous, row, position, changes) {
|
|
1344
|
+
const entry = entries.get(key) ?? {
|
|
1345
|
+
previous,
|
|
1346
|
+
row,
|
|
1347
|
+
position: Object.freeze({ ...position }),
|
|
1348
|
+
cells: /* @__PURE__ */ new Map()
|
|
1349
|
+
};
|
|
1350
|
+
entry.row = row;
|
|
1351
|
+
for (const change of changes) {
|
|
1352
|
+
const original = entry.cells.has(change.column) ? entry.cells.get(change.column).previous : change.previous;
|
|
1353
|
+
if (Object.is(original, change.value)) entry.cells.delete(change.column);
|
|
1354
|
+
else entry.cells.set(change.column, Object.freeze({
|
|
1355
|
+
...change,
|
|
1356
|
+
previous: original
|
|
1357
|
+
}));
|
|
1358
|
+
}
|
|
1359
|
+
if (entry.previous === void 0 && row === void 0 || entry.previous !== void 0 && row !== void 0 && entry.cells.size === 0) entries.delete(key);
|
|
1360
|
+
else entries.set(key, entry);
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
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
|
+
|
|
969
1520
|
//#endregion
|
|
970
1521
|
//#region src/tree.ts
|
|
971
1522
|
function buildTree(data, columns, readKey, options, lazy) {
|
|
@@ -1223,10 +1774,13 @@ function createTable(options) {
|
|
|
1223
1774
|
const pendingLoads = /* @__PURE__ */ new Map();
|
|
1224
1775
|
let destroyed = false;
|
|
1225
1776
|
const undoStack = createUndoStack(options.historyLimit);
|
|
1777
|
+
const pendingChanges = createPendingChanges();
|
|
1226
1778
|
const listeners = /* @__PURE__ */ new Set();
|
|
1227
1779
|
const optionIssues = [];
|
|
1228
1780
|
const maxPasteCells = pasteLimit(options.pasteLimit?.maxCells, 1e5, "maxCells", optionIssues);
|
|
1229
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."));
|
|
1230
1784
|
let data = options.data;
|
|
1231
1785
|
let definitions = options.columns;
|
|
1232
1786
|
let rowCount = options.rowCount;
|
|
@@ -1239,6 +1793,49 @@ function createTable(options) {
|
|
|
1239
1793
|
});
|
|
1240
1794
|
let state = initialState;
|
|
1241
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
|
+
});
|
|
1809
|
+
let transaction = false;
|
|
1810
|
+
let deferValidationNotify = false;
|
|
1811
|
+
let validationNotifyRequested = false;
|
|
1812
|
+
const validation = createEditValidation({
|
|
1813
|
+
snapshot: getSnapshot,
|
|
1814
|
+
apply: applyChanges,
|
|
1815
|
+
notify,
|
|
1816
|
+
writeIssue(row, next) {
|
|
1817
|
+
const node = treeEnabled ? treeStage(data, resolveColumns(definitions).byId, lazyVersion).byKey.get(row.key) : void 0;
|
|
1818
|
+
if (node && (!node.writable || node.parent && options.getChildren && !options.setChildren)) return issue("read_only_cell", "This nested row has no immutable writable source path.", { rowKey: row.key });
|
|
1819
|
+
if (readKey(next, row.index) !== readKey(row.original, row.index)) return issue("invalid_row_operation", "Editing a row key is not supported; remove and insert the row instead.", { rowKey: row.key });
|
|
1820
|
+
},
|
|
1821
|
+
batch(run) {
|
|
1822
|
+
deferValidationNotify = true;
|
|
1823
|
+
validationNotifyRequested = false;
|
|
1824
|
+
try {
|
|
1825
|
+
return run();
|
|
1826
|
+
} finally {
|
|
1827
|
+
deferValidationNotify = false;
|
|
1828
|
+
if (validationNotifyRequested) notify();
|
|
1829
|
+
}
|
|
1830
|
+
},
|
|
1831
|
+
record: (changes) => undoStack.record({
|
|
1832
|
+
kind: "cells",
|
|
1833
|
+
changes
|
|
1834
|
+
}),
|
|
1835
|
+
onIssues: (issues) => options.onEditIssues?.(issues),
|
|
1836
|
+
optimistic: options.asyncValidation === "optimistic",
|
|
1837
|
+
validateRow: options.validateRow
|
|
1838
|
+
});
|
|
1242
1839
|
const readKey = (row, index) => {
|
|
1243
1840
|
const { rowKey } = options;
|
|
1244
1841
|
if (typeof rowKey === "function") return rowKey(row, index);
|
|
@@ -1261,25 +1858,46 @@ function createTable(options) {
|
|
|
1261
1858
|
issues
|
|
1262
1859
|
};
|
|
1263
1860
|
});
|
|
1861
|
+
let primedIndex;
|
|
1264
1862
|
const indexRows = memo((rows, byId) => {
|
|
1863
|
+
if (primedIndex?.data === rows && primedIndex.columns === byId) return primedIndex.result;
|
|
1265
1864
|
const byKey = /* @__PURE__ */ new Map();
|
|
1266
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));
|
|
1267
1877
|
return {
|
|
1268
|
-
rows:
|
|
1269
|
-
let key = readKey(original, index);
|
|
1270
|
-
if (byKey.has(key)) {
|
|
1271
|
-
issues.push(issue("duplicate_row_key", `Row ${index} repeats the key "${String(key)}"; it is keyed "${String(key)}#${index}" instead.`, { rowKey: key }));
|
|
1272
|
-
key = `${String(key)}#${index}`;
|
|
1273
|
-
}
|
|
1274
|
-
const row = createRow(original, key, index, byId, options.getRowCanExpand?.(original) ?? false);
|
|
1275
|
-
byKey.set(key, row);
|
|
1276
|
-
return row;
|
|
1277
|
-
}),
|
|
1878
|
+
rows: indexed,
|
|
1278
1879
|
byKey,
|
|
1279
1880
|
issues
|
|
1280
1881
|
};
|
|
1281
1882
|
});
|
|
1282
1883
|
const treeStage = memo((rows, byId, _version) => buildTree(rows, byId, readKey, options, lazyChildren));
|
|
1884
|
+
const treeIndex = memo((model) => ({
|
|
1885
|
+
rows: model.rows,
|
|
1886
|
+
byKey: new Map(model.rows.map((row) => [row.key, row])),
|
|
1887
|
+
issues: model.issues
|
|
1888
|
+
}));
|
|
1889
|
+
const treePositions = memo((model) => {
|
|
1890
|
+
const result = /* @__PURE__ */ new Map();
|
|
1891
|
+
const index = (siblings, parent) => {
|
|
1892
|
+
siblings.forEach((node, position) => result.set(node.row.key, Object.freeze({
|
|
1893
|
+
parent,
|
|
1894
|
+
before: siblings[position + 1]?.row.key
|
|
1895
|
+
})));
|
|
1896
|
+
};
|
|
1897
|
+
index(model.roots);
|
|
1898
|
+
for (const node of model.nodes) index(node.children, node.row.key);
|
|
1899
|
+
return result;
|
|
1900
|
+
});
|
|
1283
1901
|
const processedTreeStage = memo((model, columns, search, filters, sorting) => processTree(model, columns, {
|
|
1284
1902
|
...state,
|
|
1285
1903
|
search,
|
|
@@ -1347,13 +1965,24 @@ function createTable(options) {
|
|
|
1347
1965
|
visible: ordered.filter((column) => !hidden.has(column.id))
|
|
1348
1966
|
};
|
|
1349
1967
|
});
|
|
1350
|
-
|
|
1351
|
-
|
|
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
|
+
}
|
|
1352
1981
|
const selectedSet = memo((selection) => new Set(selection));
|
|
1353
1982
|
const expansionStage = memo((indexed, expanded, selection) => {
|
|
1354
1983
|
const keys = expanded === true ? void 0 : new Set(expanded);
|
|
1355
1984
|
const selected = new Set(selection);
|
|
1356
|
-
const rows = indexed.rows
|
|
1985
|
+
const rows = mapList(indexed.rows, (row) => {
|
|
1357
1986
|
const isExpanded = row.canExpand && (expanded === true || (keys?.has(row.key) ?? false));
|
|
1358
1987
|
return isExpanded || selected.has(row.key) ? Object.freeze({
|
|
1359
1988
|
...row,
|
|
@@ -1363,10 +1992,13 @@ function createTable(options) {
|
|
|
1363
1992
|
});
|
|
1364
1993
|
return {
|
|
1365
1994
|
rows,
|
|
1366
|
-
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
|
+
} }
|
|
1367
1999
|
};
|
|
1368
2000
|
});
|
|
1369
|
-
const expandedOrder = memo((rows, byKey) => rows
|
|
2001
|
+
const expandedOrder = memo((rows, byKey) => mapList(rows, (row) => byKey.get(row.key)));
|
|
1370
2002
|
function stateIssues(byId) {
|
|
1371
2003
|
const issues = [];
|
|
1372
2004
|
if (expandedIssue) issues.push(expandedIssue);
|
|
@@ -1382,6 +2014,13 @@ function createTable(options) {
|
|
|
1382
2014
|
return issues;
|
|
1383
2015
|
}
|
|
1384
2016
|
function computeSnapshot() {
|
|
2017
|
+
const cellProblems = validation.problems();
|
|
2018
|
+
const dirty = new Set(pendingChanges.entries.keys());
|
|
2019
|
+
const metadata = (row) => dirty.has(row.key) || cellProblems.has(row.key) ? Object.freeze({
|
|
2020
|
+
...row,
|
|
2021
|
+
isDirty: dirty.has(row.key),
|
|
2022
|
+
cellIssues: cellProblems.get(row.key)
|
|
2023
|
+
}) : row;
|
|
1385
2024
|
const { columns, byId, issues: columnIssues } = resolveColumns(definitions);
|
|
1386
2025
|
const model = treeEnabled ? treeStage(data, byId, lazyVersion) : void 0;
|
|
1387
2026
|
const indexed = model ? {
|
|
@@ -1392,11 +2031,14 @@ function createTable(options) {
|
|
|
1392
2031
|
const ordered = orderColumns(columns, state.columnOrder, state.hiddenColumns);
|
|
1393
2032
|
const issues = [
|
|
1394
2033
|
...optionIssues,
|
|
2034
|
+
...ingestionIssues,
|
|
2035
|
+
...streamIssues,
|
|
1395
2036
|
...columnIssues,
|
|
1396
2037
|
...indexed.issues,
|
|
1397
2038
|
...stateIssues(byId),
|
|
1398
2039
|
...loadIssues.values(),
|
|
1399
|
-
...Array.from(loadValidationIssues.values()).flat()
|
|
2040
|
+
...Array.from(loadValidationIssues.values()).flat(),
|
|
2041
|
+
...Array.from(cellProblems.values()).flatMap((row) => [...row.values()])
|
|
1400
2042
|
];
|
|
1401
2043
|
const requestedSize = state.pagination.pageSize;
|
|
1402
2044
|
let pageSize = Number.isInteger(requestedSize) && requestedSize >= 1 ? requestedSize : 10;
|
|
@@ -1426,6 +2068,8 @@ function createTable(options) {
|
|
|
1426
2068
|
const canExpand = node.children.length > 0 || status !== "loaded" || (options.getRowCanExpand?.(node.row.original) ?? false);
|
|
1427
2069
|
const result = Object.freeze({
|
|
1428
2070
|
...node.row,
|
|
2071
|
+
isDirty: dirty.has(key),
|
|
2072
|
+
cellIssues: cellProblems.get(key),
|
|
1429
2073
|
depth: node.depth,
|
|
1430
2074
|
parentKey: node.parent?.row.key,
|
|
1431
2075
|
childCount: status === "loaded" ? node.children.length : void 0,
|
|
@@ -1479,21 +2123,46 @@ function createTable(options) {
|
|
|
1479
2123
|
pageCount = 1;
|
|
1480
2124
|
pageSize = Math.max(1, rows.length);
|
|
1481
2125
|
}
|
|
2126
|
+
const flatProjected = /* @__PURE__ */ new Map();
|
|
2127
|
+
if (!model) {
|
|
2128
|
+
const project = (row) => {
|
|
2129
|
+
let result = flatProjected.get(row.key);
|
|
2130
|
+
if (!result) {
|
|
2131
|
+
result = metadata(row);
|
|
2132
|
+
flatProjected.set(row.key, result);
|
|
2133
|
+
}
|
|
2134
|
+
return result;
|
|
2135
|
+
};
|
|
2136
|
+
const shared = rows === processedRows;
|
|
2137
|
+
processedRows = mapList(processedRows, project);
|
|
2138
|
+
rows = shared ? processedRows : rows.map(project);
|
|
2139
|
+
}
|
|
1482
2140
|
const selected = selectedSet(state.selection);
|
|
1483
|
-
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;
|
|
1484
2145
|
const scopeRows = selectScope === "page" || manual ? rows : processedRows;
|
|
1485
|
-
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;
|
|
1486
2147
|
const pageStart = rows.length === 0 ? 0 : (page - 1) * pageSize + 1;
|
|
1487
2148
|
const sortIndex = new Map(state.sorting.map((rule, priority) => [rule.column, {
|
|
1488
2149
|
direction: rule.direction,
|
|
1489
2150
|
priority
|
|
1490
2151
|
}]));
|
|
1491
2152
|
return Object.freeze({
|
|
2153
|
+
loadState,
|
|
2154
|
+
loadedRowCount: indexed.rows.length,
|
|
2155
|
+
expectedRowCount,
|
|
1492
2156
|
state,
|
|
1493
2157
|
columns: ordered.visible,
|
|
1494
2158
|
allColumns: ordered.all,
|
|
1495
2159
|
rows,
|
|
1496
|
-
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) => {
|
|
1497
2166
|
const item = Object.freeze({
|
|
1498
2167
|
kind: "row",
|
|
1499
2168
|
key: getRowItemKey(row.key),
|
|
@@ -1526,10 +2195,11 @@ function createTable(options) {
|
|
|
1526
2195
|
canUndo: undoStack.canUndo,
|
|
1527
2196
|
canRedo: undoStack.canRedo,
|
|
1528
2197
|
issues: Object.freeze(issues),
|
|
2198
|
+
pendingCells: validation.cells(),
|
|
1529
2199
|
isSelected: (key) => selected.has(key),
|
|
1530
2200
|
getSort: (columnId) => sortIndex.get(columnId),
|
|
1531
2201
|
getColumn: (columnId) => byId.get(columnId),
|
|
1532
|
-
getRow: (key) => treeRow ? treeRow(key) : expanded.byKey.get(key)
|
|
2202
|
+
getRow: (key) => treeRow ? treeRow(key) : flatProjected.get(key) ?? (expanded.byKey.get(key) ? metadata(expanded.byKey.get(key)) : void 0)
|
|
1533
2203
|
});
|
|
1534
2204
|
}
|
|
1535
2205
|
function getSnapshot() {
|
|
@@ -1538,6 +2208,11 @@ function createTable(options) {
|
|
|
1538
2208
|
}
|
|
1539
2209
|
function notify() {
|
|
1540
2210
|
snapshot = void 0;
|
|
2211
|
+
if (deferValidationNotify) {
|
|
2212
|
+
validationNotifyRequested = true;
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
if (transaction) return;
|
|
1541
2216
|
if (listeners.size === 0) return;
|
|
1542
2217
|
const next = getSnapshot();
|
|
1543
2218
|
for (const listener of listeners) listener(next);
|
|
@@ -1702,6 +2377,7 @@ function createTable(options) {
|
|
|
1702
2377
|
const working = [...data];
|
|
1703
2378
|
const model = treeEnabled ? treeStage(data, resolveColumns(definitions).byId, lazyVersion) : void 0;
|
|
1704
2379
|
const replacements = /* @__PURE__ */ new Map();
|
|
2380
|
+
const cache = new Map(lazyChildren);
|
|
1705
2381
|
const originalOf = (node) => replacements.get(node) ?? node.row.original;
|
|
1706
2382
|
const issues = [...recoveredIssues];
|
|
1707
2383
|
const changes = [];
|
|
@@ -1777,7 +2453,16 @@ function createTable(options) {
|
|
|
1777
2453
|
} else value = edit.value;
|
|
1778
2454
|
const previous = column.getValue(original);
|
|
1779
2455
|
if (Object.is(previous, value)) continue;
|
|
1780
|
-
|
|
2456
|
+
let updated;
|
|
2457
|
+
try {
|
|
2458
|
+
updated = column.setValue(original, value);
|
|
2459
|
+
} catch (error) {
|
|
2460
|
+
issues.push(issue("invalid_value", error instanceof Error ? error.message : String(error), {
|
|
2461
|
+
rowKey: row.key,
|
|
2462
|
+
column: column.id
|
|
2463
|
+
}));
|
|
2464
|
+
continue;
|
|
2465
|
+
}
|
|
1781
2466
|
if (updated === void 0) {
|
|
1782
2467
|
issues.push(issue("read_only_cell", `The "${column.id}" column has no way to write a value.`, {
|
|
1783
2468
|
rowKey: row.key,
|
|
@@ -1785,20 +2470,37 @@ function createTable(options) {
|
|
|
1785
2470
|
}));
|
|
1786
2471
|
continue;
|
|
1787
2472
|
}
|
|
2473
|
+
if (readKey(updated, row.index) !== readKey(original, row.index)) {
|
|
2474
|
+
issues.push(issue("invalid_row_operation", "Editing a row key is not supported; remove and insert the row instead.", {
|
|
2475
|
+
rowKey: row.key,
|
|
2476
|
+
column: column.id
|
|
2477
|
+
}));
|
|
2478
|
+
continue;
|
|
2479
|
+
}
|
|
1788
2480
|
if (!node) working[row.index] = updated;
|
|
1789
2481
|
else {
|
|
2482
|
+
const planned = /* @__PURE__ */ new Map([[node, updated]]);
|
|
2483
|
+
const plannedCache = /* @__PURE__ */ new Map();
|
|
1790
2484
|
let cursor = node;
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
2485
|
+
try {
|
|
2486
|
+
while (cursor.parent) {
|
|
2487
|
+
const parent = cursor.parent;
|
|
2488
|
+
const children = parent.children.map((child) => planned.get(child) ?? originalOf(child));
|
|
2489
|
+
if (cursor.lazy) plannedCache.set(parent.row.key, children);
|
|
2490
|
+
if (options.setChildren) planned.set(parent, options.setChildren(originalOf(parent), children));
|
|
2491
|
+
cursor = parent;
|
|
2492
|
+
}
|
|
2493
|
+
} catch (error) {
|
|
2494
|
+
issues.push(issue("invalid_value", error instanceof Error ? error.message : String(error), {
|
|
2495
|
+
rowKey: row.key,
|
|
2496
|
+
column: column.id
|
|
2497
|
+
}));
|
|
2498
|
+
continue;
|
|
1799
2499
|
}
|
|
2500
|
+
for (const [item, plannedRow] of planned) replacements.set(item, plannedRow);
|
|
2501
|
+
for (const [key, children] of plannedCache) cache.set(key, children);
|
|
2502
|
+
if (!options.getChildren && !node.lazy) working[node.sourceIndex] = updated;
|
|
1800
2503
|
if (options.getChildren || options.setChildren) working[cursor.sourceIndex] = originalOf(cursor);
|
|
1801
|
-
lazyVersion++;
|
|
1802
2504
|
}
|
|
1803
2505
|
changes.push({
|
|
1804
2506
|
rowKey: row.key,
|
|
@@ -1813,17 +2515,390 @@ function createTable(options) {
|
|
|
1813
2515
|
else {
|
|
1814
2516
|
status = issues.length > 0 ? "partial" : "applied";
|
|
1815
2517
|
data = working;
|
|
1816
|
-
if (
|
|
1817
|
-
|
|
2518
|
+
if (model) {
|
|
2519
|
+
lazyChildren.clear();
|
|
2520
|
+
for (const [key, children] of cache) lazyChildren.set(key, children);
|
|
2521
|
+
lazyVersion++;
|
|
2522
|
+
}
|
|
2523
|
+
if (record) {
|
|
2524
|
+
if (!transaction) undoStack.record({
|
|
2525
|
+
kind: "cells",
|
|
2526
|
+
changes
|
|
2527
|
+
});
|
|
2528
|
+
}
|
|
2529
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2530
|
+
for (const change of changes) {
|
|
2531
|
+
const group = groups.get(change.rowKey) ?? [];
|
|
2532
|
+
group.push(change);
|
|
2533
|
+
groups.set(change.rowKey, group);
|
|
2534
|
+
}
|
|
2535
|
+
for (const [key, group] of groups) pendingChanges.record(key, current.getRow(key).original, group.at(-1).row, positionOf(key), group);
|
|
2536
|
+
if (!transaction) options.onDataChange?.(data, changes);
|
|
1818
2537
|
notify();
|
|
1819
2538
|
}
|
|
1820
|
-
if (issues.length > 0) options.onEditIssues?.(issues);
|
|
2539
|
+
if (issues.length > 0 && !transaction) options.onEditIssues?.(issues);
|
|
1821
2540
|
return Object.freeze({
|
|
1822
2541
|
status,
|
|
1823
2542
|
changes: Object.freeze(changes),
|
|
1824
|
-
issues: Object.freeze(issues)
|
|
2543
|
+
issues: Object.freeze(issues),
|
|
2544
|
+
rowChanges: Object.freeze([]),
|
|
2545
|
+
pendingCells: Object.freeze([])
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
2548
|
+
function sourceRows() {
|
|
2549
|
+
const { byId } = resolveColumns(definitions);
|
|
2550
|
+
const model = treeEnabled ? treeStage(data, byId, lazyVersion) : void 0;
|
|
2551
|
+
return model ? treeIndex(model) : indexRows(data, byId);
|
|
2552
|
+
}
|
|
2553
|
+
function positionOf(key) {
|
|
2554
|
+
const { byId } = resolveColumns(definitions);
|
|
2555
|
+
if (treeEnabled) {
|
|
2556
|
+
const model = treeStage(data, byId, lazyVersion);
|
|
2557
|
+
return treePositions(model).get(key) ?? Object.freeze({});
|
|
2558
|
+
}
|
|
2559
|
+
const indexed = indexRows(data, byId);
|
|
2560
|
+
const row = indexed.byKey.get(key);
|
|
2561
|
+
return Object.freeze({ before: row ? indexed.rows[row.index + 1]?.key : void 0 });
|
|
2562
|
+
}
|
|
2563
|
+
function scene() {
|
|
2564
|
+
return {
|
|
2565
|
+
data,
|
|
2566
|
+
lazy: new Map(lazyChildren)
|
|
2567
|
+
};
|
|
2568
|
+
}
|
|
2569
|
+
function resultForRows(changes, issues, cells = []) {
|
|
2570
|
+
return Object.freeze({
|
|
2571
|
+
status: changes.length || cells.length ? issues.length ? "partial" : "applied" : issues.length ? "rejected" : "unchanged",
|
|
2572
|
+
changes: Object.freeze([...cells]),
|
|
2573
|
+
rowChanges: Object.freeze([...changes]),
|
|
2574
|
+
pendingCells: Object.freeze([]),
|
|
2575
|
+
issues: Object.freeze([...issues])
|
|
2576
|
+
});
|
|
2577
|
+
}
|
|
2578
|
+
function rowProblem(message, rowKey) {
|
|
2579
|
+
return issue("invalid_row_operation", message, { rowKey });
|
|
2580
|
+
}
|
|
2581
|
+
function stableRows(rows) {
|
|
2582
|
+
return options.rowKey !== void 0 || rows.every((row) => {
|
|
2583
|
+
const key = getPath(row, "id");
|
|
2584
|
+
return typeof key === "string" || typeof key === "number";
|
|
2585
|
+
});
|
|
2586
|
+
}
|
|
2587
|
+
function cellDifferences(previous, row, key) {
|
|
2588
|
+
return resolveColumns(definitions).columns.flatMap((column) => {
|
|
2589
|
+
const before = column.getValue(previous);
|
|
2590
|
+
const value = column.getValue(row);
|
|
2591
|
+
return Object.is(before, value) ? [] : [{
|
|
2592
|
+
rowKey: key,
|
|
2593
|
+
column: column.id,
|
|
2594
|
+
previous: before,
|
|
2595
|
+
value,
|
|
2596
|
+
row
|
|
2597
|
+
}];
|
|
2598
|
+
});
|
|
2599
|
+
}
|
|
2600
|
+
function recordRows(previous, keys, positions, baselines = /* @__PURE__ */ new Map()) {
|
|
2601
|
+
const next = sourceRows();
|
|
2602
|
+
const rowChanges = [];
|
|
2603
|
+
for (const key of keys) {
|
|
2604
|
+
const before = previous.byKey.get(key)?.original;
|
|
2605
|
+
const row = next.byKey.get(key)?.original;
|
|
2606
|
+
const position = positions.get(key) ?? positionOf(key);
|
|
2607
|
+
const cells = before !== void 0 && row !== void 0 ? cellDifferences(before, row, key) : [];
|
|
2608
|
+
const baseline = baselines.get(key);
|
|
2609
|
+
const pending = pendingChanges.entries.get(key);
|
|
2610
|
+
if (row === void 0 && baseline !== void 0 && pending?.previous !== void 0) pending.previous = baseline;
|
|
2611
|
+
pendingChanges.record(key, row === void 0 ? baseline ?? before : before, row, position, cells);
|
|
2612
|
+
if (before === void 0 && row !== void 0) rowChanges.push(Object.freeze({
|
|
2613
|
+
kind: "inserted",
|
|
2614
|
+
rowKey: key,
|
|
2615
|
+
row,
|
|
2616
|
+
position: positionOf(key)
|
|
2617
|
+
}));
|
|
2618
|
+
else if (row === void 0 && before !== void 0) rowChanges.push(Object.freeze({
|
|
2619
|
+
kind: "removed",
|
|
2620
|
+
rowKey: key,
|
|
2621
|
+
row: before,
|
|
2622
|
+
position
|
|
2623
|
+
}));
|
|
2624
|
+
}
|
|
2625
|
+
return rowChanges;
|
|
2626
|
+
}
|
|
2627
|
+
function publishRows(before, previous, keys, positions, issues, baselines = /* @__PURE__ */ new Map()) {
|
|
2628
|
+
const rowChanges = recordRows(previous, keys, positions, baselines);
|
|
2629
|
+
if (!transaction) {
|
|
2630
|
+
undoStack.record({
|
|
2631
|
+
kind: "rows",
|
|
2632
|
+
before,
|
|
2633
|
+
after: scene(),
|
|
2634
|
+
keys
|
|
2635
|
+
});
|
|
2636
|
+
options.onDataChange?.(data, []);
|
|
2637
|
+
if (issues.length) options.onEditIssues?.(issues);
|
|
2638
|
+
}
|
|
2639
|
+
notify();
|
|
2640
|
+
return resultForRows(rowChanges, issues);
|
|
2641
|
+
}
|
|
2642
|
+
function restoreScene(entry, direction) {
|
|
2643
|
+
const previous = sourceRows();
|
|
2644
|
+
const baselines = removalBaselines();
|
|
2645
|
+
const positions = new Map(entry.keys.map((key) => [key, positionOf(key)]));
|
|
2646
|
+
const target = direction === "undo" ? entry.before : entry.after;
|
|
2647
|
+
data = target.data;
|
|
2648
|
+
const changed = /* @__PURE__ */ new Set([...entry.before.lazy.keys(), ...entry.after.lazy.keys()]);
|
|
2649
|
+
for (const key of changed) {
|
|
2650
|
+
if (entry.before.lazy.get(key) === entry.after.lazy.get(key)) continue;
|
|
2651
|
+
const rows = target.lazy.get(key);
|
|
2652
|
+
if (rows) lazyChildren.set(key, rows);
|
|
2653
|
+
else lazyChildren.delete(key);
|
|
2654
|
+
}
|
|
2655
|
+
lazyVersion++;
|
|
2656
|
+
recordRows(previous, entry.keys, positions, baselines);
|
|
2657
|
+
options.onDataChange?.(data, []);
|
|
2658
|
+
notify();
|
|
2659
|
+
}
|
|
2660
|
+
function removalBaselines() {
|
|
2661
|
+
const baselines = /* @__PURE__ */ new Map();
|
|
2662
|
+
if (!options.getChildren || !options.setChildren) return baselines;
|
|
2663
|
+
const model = treeStage(data, resolveColumns(definitions).byId, lazyVersion);
|
|
2664
|
+
for (const node of model.nodes.toReversed()) {
|
|
2665
|
+
const entry = pendingChanges.entries.get(node.row.key);
|
|
2666
|
+
if (entry && entry.previous === void 0) continue;
|
|
2667
|
+
let baseline = entry?.previous ?? node.row.original;
|
|
2668
|
+
const children = node.children.filter((child) => !pendingChanges.entries.has(child.row.key) || pendingChanges.entries.get(child.row.key).previous !== void 0).map((child) => baselines.get(child.row.key) ?? child.row.original);
|
|
2669
|
+
const originalChildren = options.getChildren(baseline) ?? [];
|
|
2670
|
+
if (children.length !== originalChildren.length || children.some((child, index) => child !== originalChildren[index])) baseline = options.setChildren(baseline, children);
|
|
2671
|
+
baselines.set(node.row.key, baseline);
|
|
2672
|
+
}
|
|
2673
|
+
return baselines;
|
|
2674
|
+
}
|
|
2675
|
+
function rewriteNested(model, removed, overrides, cache) {
|
|
2676
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
2677
|
+
for (const node of model.nodes.toReversed()) {
|
|
2678
|
+
if (removed.has(node.row.key)) continue;
|
|
2679
|
+
const children = overrides.get(node.row.key) ?? node.children.filter((child) => !removed.has(child.row.key)).map((child) => replacements.get(child) ?? child.row.original);
|
|
2680
|
+
if (!(overrides.has(node.row.key) || children.length !== node.children.length || children.some((child, index) => child !== node.children[index]?.row.original))) continue;
|
|
2681
|
+
if (!options.setChildren) throw new Error("Changing nested children requires setChildren.");
|
|
2682
|
+
replacements.set(node, options.setChildren(node.row.original, children));
|
|
2683
|
+
if (cache.has(node.row.key)) cache.set(node.row.key, children);
|
|
2684
|
+
}
|
|
2685
|
+
const roots = new Map(model.roots.filter((node) => node.writable).map((node) => [node.sourceIndex, node]));
|
|
2686
|
+
return data.flatMap((row, index) => {
|
|
2687
|
+
const node = roots.get(index);
|
|
2688
|
+
return node && removed.has(node.row.key) ? [] : [node ? replacements.get(node) ?? row : row];
|
|
1825
2689
|
});
|
|
1826
2690
|
}
|
|
2691
|
+
function insertRows(rows, at = {}) {
|
|
2692
|
+
const before = scene();
|
|
2693
|
+
const previous = sourceRows();
|
|
2694
|
+
const problems = [];
|
|
2695
|
+
const reject = (problem) => {
|
|
2696
|
+
if (!transaction) options.onEditIssues?.([problem]);
|
|
2697
|
+
return resultForRows([], [problem]);
|
|
2698
|
+
};
|
|
2699
|
+
let incoming;
|
|
2700
|
+
try {
|
|
2701
|
+
incoming = rows ?? (options.createRow ? [options.createRow()] : []);
|
|
2702
|
+
} catch (error) {
|
|
2703
|
+
return reject(rowProblem(error instanceof Error ? error.message : String(error)));
|
|
2704
|
+
}
|
|
2705
|
+
if (rows === void 0 && !options.createRow) return reject(rowProblem("insertRows() requires rows or a createRow callback."));
|
|
2706
|
+
if (!incoming.length) return resultForRows([], []);
|
|
2707
|
+
if (!stableRows(data) || !stableRows(incoming)) return reject(rowProblem("Row operations require stable row keys; supply rowKey or an id on every row."));
|
|
2708
|
+
const { byId } = resolveColumns(definitions);
|
|
2709
|
+
const model = treeEnabled ? treeStage(data, byId, lazyVersion) : void 0;
|
|
2710
|
+
const parent = at.parent === void 0 ? void 0 : model?.byKey.get(at.parent);
|
|
2711
|
+
const sibling = at.before === void 0 ? void 0 : previous.byKey.get(at.before);
|
|
2712
|
+
if (at.parent !== void 0 && !parent) return reject(issue("unknown_row", "The insertion parent does not exist.", { rowKey: at.parent }));
|
|
2713
|
+
if (at.before !== void 0 && !sibling) return reject(issue("unknown_row", "The insertion sibling does not exist.", { rowKey: at.before }));
|
|
2714
|
+
if (sibling && (model?.byKey.get(sibling.key)?.parent?.row.key ?? sibling.parentKey) !== at.parent) return reject(rowProblem("The before row must belong to the requested parent.", sibling.key));
|
|
2715
|
+
if (parent && !parent.writable) return reject(rowProblem("A recovered parent has no writable source path.", parent.row.key));
|
|
2716
|
+
if (parent && getSnapshot().getRow(parent.row.key)?.childStatus !== "loaded") return reject(rowProblem("Load the parent's children before inserting rows.", parent.row.key));
|
|
2717
|
+
const cache = new Map(lazyChildren);
|
|
2718
|
+
let next;
|
|
2719
|
+
try {
|
|
2720
|
+
if (options.getChildren && parent && model) {
|
|
2721
|
+
const children = parent.children.map((child) => child.row.original);
|
|
2722
|
+
const index = at.before === void 0 ? children.length : parent.children.findIndex((child) => child.row.key === at.before);
|
|
2723
|
+
children.splice(index, 0, ...incoming);
|
|
2724
|
+
next = rewriteNested(model, /* @__PURE__ */ new Set(), /* @__PURE__ */ new Map([[parent.row.key, children]]), cache);
|
|
2725
|
+
} else if (parent && cache.has(parent.row.key)) {
|
|
2726
|
+
incoming = incoming.map((row) => options.setParentKey ? options.setParentKey(row, parent.row.key) : row);
|
|
2727
|
+
const children = [...cache.get(parent.row.key)];
|
|
2728
|
+
const index = at.before === void 0 ? children.length : parent.children.findIndex((child) => child.row.key === at.before);
|
|
2729
|
+
children.splice(index, 0, ...incoming);
|
|
2730
|
+
cache.set(parent.row.key, children);
|
|
2731
|
+
next = [...data];
|
|
2732
|
+
} else {
|
|
2733
|
+
if (parent && options.getParentKey) {
|
|
2734
|
+
incoming = incoming.map((row) => options.setParentKey ? options.setParentKey(row, parent.row.key) : row);
|
|
2735
|
+
if (incoming.some((row) => options.getParentKey(row) !== parent.row.key)) return reject(rowProblem("Supply setParentKey or rows already addressed to the requested parent."));
|
|
2736
|
+
}
|
|
2737
|
+
const working = [...data];
|
|
2738
|
+
working.splice(sibling?.index ?? working.length, 0, ...incoming);
|
|
2739
|
+
next = working;
|
|
2740
|
+
}
|
|
2741
|
+
const candidate = treeEnabled ? buildTree(next, byId, readKey, options, cache) : indexRows(next, byId);
|
|
2742
|
+
if (!stableRows(candidate.rows.map((row) => row.original))) return reject(rowProblem("Every inserted descendant must have a stable key."));
|
|
2743
|
+
const baseline = /* @__PURE__ */ new Map();
|
|
2744
|
+
for (const problem of previous.issues) {
|
|
2745
|
+
const key = `${problem.code}:${String(problem.rowKey)}`;
|
|
2746
|
+
baseline.set(key, (baseline.get(key) ?? 0) + 1);
|
|
2747
|
+
}
|
|
2748
|
+
problems.push(...candidate.issues.filter((problem) => {
|
|
2749
|
+
const key = `${problem.code}:${String(problem.rowKey)}`;
|
|
2750
|
+
const remaining = baseline.get(key) ?? 0;
|
|
2751
|
+
if (!remaining) return true;
|
|
2752
|
+
baseline.set(key, remaining - 1);
|
|
2753
|
+
return false;
|
|
2754
|
+
}));
|
|
2755
|
+
if (problems.length) {
|
|
2756
|
+
if (!transaction) options.onEditIssues?.(problems);
|
|
2757
|
+
return resultForRows([], problems);
|
|
2758
|
+
}
|
|
2759
|
+
const keys = candidate.rows.filter((row) => !previous.byKey.has(row.key)).map((row) => row.key);
|
|
2760
|
+
if (!keys.length) return reject(rowProblem("No unique rows can be inserted."));
|
|
2761
|
+
data = next;
|
|
2762
|
+
lazyChildren.clear();
|
|
2763
|
+
for (const [key, value] of cache) lazyChildren.set(key, value);
|
|
2764
|
+
lazyVersion++;
|
|
2765
|
+
return publishRows(before, previous, keys, /* @__PURE__ */ new Map(), []);
|
|
2766
|
+
} catch (error) {
|
|
2767
|
+
return reject(rowProblem(error instanceof Error ? error.message : String(error)));
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
function removeRows(keys) {
|
|
2771
|
+
const before = scene();
|
|
2772
|
+
const previous = sourceRows();
|
|
2773
|
+
const problems = [];
|
|
2774
|
+
const removed = /* @__PURE__ */ new Set();
|
|
2775
|
+
const { byId } = resolveColumns(definitions);
|
|
2776
|
+
const model = treeEnabled ? treeStage(data, byId, lazyVersion) : void 0;
|
|
2777
|
+
if (!stableRows(data)) {
|
|
2778
|
+
const problem = rowProblem("Row operations require stable row keys.");
|
|
2779
|
+
if (!transaction) options.onEditIssues?.([problem]);
|
|
2780
|
+
return resultForRows([], [problem]);
|
|
2781
|
+
}
|
|
2782
|
+
for (const key of keys) {
|
|
2783
|
+
if (!previous.byKey.has(key)) {
|
|
2784
|
+
problems.push(issue("unknown_row", "The row to remove does not exist.", { rowKey: key }));
|
|
2785
|
+
continue;
|
|
2786
|
+
}
|
|
2787
|
+
const node = model?.byKey.get(key);
|
|
2788
|
+
if (node && !node.writable) {
|
|
2789
|
+
problems.push(rowProblem("A recovered row has no writable source path.", key));
|
|
2790
|
+
continue;
|
|
2791
|
+
}
|
|
2792
|
+
const stack = node ? [node] : [];
|
|
2793
|
+
removed.add(key);
|
|
2794
|
+
while (stack.length) {
|
|
2795
|
+
const item = stack.pop();
|
|
2796
|
+
removed.add(item.row.key);
|
|
2797
|
+
for (const child of item.children) stack.push(child);
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
if (!removed.size) {
|
|
2801
|
+
if (problems.length && !transaction) options.onEditIssues?.(problems);
|
|
2802
|
+
return resultForRows([], problems);
|
|
2803
|
+
}
|
|
2804
|
+
const positions = new Map([...removed].map((key) => [key, positionOf(key)]));
|
|
2805
|
+
const cache = new Map(lazyChildren);
|
|
2806
|
+
try {
|
|
2807
|
+
const baselines = removalBaselines();
|
|
2808
|
+
const next = options.getChildren && model ? rewriteNested(model, removed, /* @__PURE__ */ new Map(), cache) : data.filter((row, index) => !removed.has(readKey(row, index)));
|
|
2809
|
+
for (const [key, children] of cache) if (removed.has(key)) cache.delete(key);
|
|
2810
|
+
else cache.set(key, children.filter((row, index) => !removed.has(readKey(row, index))));
|
|
2811
|
+
data = next;
|
|
2812
|
+
lazyChildren.clear();
|
|
2813
|
+
for (const [key, value] of cache) lazyChildren.set(key, value);
|
|
2814
|
+
lazyVersion++;
|
|
2815
|
+
validation.cancel([...removed]);
|
|
2816
|
+
abortLoads();
|
|
2817
|
+
return publishRows(before, previous, [...removed], positions, problems, baselines);
|
|
2818
|
+
} catch (error) {
|
|
2819
|
+
problems.push(rowProblem(error instanceof Error ? error.message : String(error)));
|
|
2820
|
+
if (!transaction) options.onEditIssues?.(problems);
|
|
2821
|
+
return resultForRows([], problems);
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
function revert(keys) {
|
|
2825
|
+
const selected = new Set(keys ?? pendingChanges.entries.keys());
|
|
2826
|
+
const entries = [...pendingChanges.entries].filter(([key]) => selected.has(key)).map(([key, entry]) => [key, {
|
|
2827
|
+
...entry,
|
|
2828
|
+
cells: new Map(entry.cells)
|
|
2829
|
+
}]);
|
|
2830
|
+
if (!entries.length) return resultForRows([], []);
|
|
2831
|
+
const before = scene();
|
|
2832
|
+
const previous = sourceRows();
|
|
2833
|
+
const positions = new Map([...selected].map((key) => [key, positionOf(key)]));
|
|
2834
|
+
const problems = [];
|
|
2835
|
+
const cells = [];
|
|
2836
|
+
transaction = true;
|
|
2837
|
+
try {
|
|
2838
|
+
problems.push(...removeRows(entries.filter(([, entry]) => entry.previous === void 0).map(([key]) => key)).issues);
|
|
2839
|
+
const missing = entries.filter(([, entry]) => entry.previous !== void 0 && entry.row === void 0);
|
|
2840
|
+
for (let pass = 0; missing.length && pass <= entries.length; pass++) {
|
|
2841
|
+
let progress = false;
|
|
2842
|
+
for (let index = missing.length - 1; index >= 0; index--) {
|
|
2843
|
+
const [key, entry] = missing[index];
|
|
2844
|
+
if (sourceRows().byKey.has(key)) {
|
|
2845
|
+
missing.splice(index, 1);
|
|
2846
|
+
progress = true;
|
|
2847
|
+
continue;
|
|
2848
|
+
}
|
|
2849
|
+
if (entry.position.parent !== void 0 && !sourceRows().byKey.has(entry.position.parent)) continue;
|
|
2850
|
+
const at = {
|
|
2851
|
+
...entry.position,
|
|
2852
|
+
before: entry.position.before !== void 0 && sourceRows().byKey.has(entry.position.before) ? entry.position.before : void 0
|
|
2853
|
+
};
|
|
2854
|
+
const result = insertRows([entry.previous], at);
|
|
2855
|
+
problems.push(...result.issues);
|
|
2856
|
+
missing.splice(index, 1);
|
|
2857
|
+
progress = true;
|
|
2858
|
+
}
|
|
2859
|
+
if (!progress) break;
|
|
2860
|
+
}
|
|
2861
|
+
for (const [key] of missing) problems.push(rowProblem("The saved parent is missing; restore it before this child.", key));
|
|
2862
|
+
const result = applyChanges(entries.flatMap(([key, entry]) => entry.previous !== void 0 && sourceRows().byKey.has(key) ? [...entry.cells.values()].map((change) => ({
|
|
2863
|
+
rowKey: key,
|
|
2864
|
+
column: change.column,
|
|
2865
|
+
value: change.previous
|
|
2866
|
+
})) : []), false);
|
|
2867
|
+
cells.push(...result.changes);
|
|
2868
|
+
problems.push(...result.issues);
|
|
2869
|
+
for (const [key] of entries) if (!problems.some((problem) => problem.rowKey === key)) pendingChanges.entries.delete(key);
|
|
2870
|
+
validation.cancel([...selected]);
|
|
2871
|
+
} finally {
|
|
2872
|
+
transaction = false;
|
|
2873
|
+
}
|
|
2874
|
+
const after = scene();
|
|
2875
|
+
if (before.data !== after.data || cells.length) undoStack.record({
|
|
2876
|
+
kind: "rows",
|
|
2877
|
+
before,
|
|
2878
|
+
after,
|
|
2879
|
+
keys: [...selected]
|
|
2880
|
+
});
|
|
2881
|
+
const next = sourceRows();
|
|
2882
|
+
const rowChanges = [...selected].flatMap((key) => {
|
|
2883
|
+
const old = previous.byKey.get(key);
|
|
2884
|
+
const row = next.byKey.get(key);
|
|
2885
|
+
return !old && row ? [{
|
|
2886
|
+
kind: "inserted",
|
|
2887
|
+
rowKey: key,
|
|
2888
|
+
row: row.original,
|
|
2889
|
+
position: positionOf(key)
|
|
2890
|
+
}] : old && !row ? [{
|
|
2891
|
+
kind: "removed",
|
|
2892
|
+
rowKey: key,
|
|
2893
|
+
row: old.original,
|
|
2894
|
+
position: positions.get(key)
|
|
2895
|
+
}] : [];
|
|
2896
|
+
});
|
|
2897
|
+
if (before.data !== after.data) options.onDataChange?.(data, cells);
|
|
2898
|
+
if (problems.length) options.onEditIssues?.(problems);
|
|
2899
|
+
notify();
|
|
2900
|
+
return resultForRows(rowChanges, problems, cells);
|
|
2901
|
+
}
|
|
1827
2902
|
function rangeEdits(origin, matrix) {
|
|
1828
2903
|
const { rows, columns } = getSnapshot();
|
|
1829
2904
|
const edits = [];
|
|
@@ -1848,12 +2923,350 @@ function createTable(options) {
|
|
|
1848
2923
|
value: direction === "undo" ? change.previous : change.value
|
|
1849
2924
|
})), false);
|
|
1850
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
|
+
}
|
|
1851
3241
|
if (expandMode === "single") {
|
|
1852
3242
|
initialState = mergeState(initialState, { expanded: canonicalExpanded(initialState.expanded) });
|
|
1853
3243
|
state = initialState;
|
|
1854
3244
|
snapshot = void 0;
|
|
1855
3245
|
}
|
|
1856
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
|
+
},
|
|
3261
|
+
insertRows,
|
|
3262
|
+
removeRows,
|
|
3263
|
+
revert,
|
|
3264
|
+
getPendingChanges: () => pendingChanges.snapshot(),
|
|
3265
|
+
markSaved(keys) {
|
|
3266
|
+
if (!keys) pendingChanges.entries.clear();
|
|
3267
|
+
else for (const key of keys) pendingChanges.entries.delete(key);
|
|
3268
|
+
notify();
|
|
3269
|
+
},
|
|
1857
3270
|
getSnapshot,
|
|
1858
3271
|
getState: () => state,
|
|
1859
3272
|
subscribe(listener) {
|
|
@@ -1875,6 +3288,13 @@ function createTable(options) {
|
|
|
1875
3288
|
},
|
|
1876
3289
|
setData(next) {
|
|
1877
3290
|
if (next === data) return;
|
|
3291
|
+
validation.cancel();
|
|
3292
|
+
streaming.cancel();
|
|
3293
|
+
loadState = "idle";
|
|
3294
|
+
streamIssues = [];
|
|
3295
|
+
ingestionIssues = [];
|
|
3296
|
+
expectedRowCount = sourceExpected;
|
|
3297
|
+
pendingChanges.entries.clear();
|
|
1878
3298
|
abortLoads(true);
|
|
1879
3299
|
lazyChildren.clear();
|
|
1880
3300
|
childStatuses.clear();
|
|
@@ -1889,6 +3309,7 @@ function createTable(options) {
|
|
|
1889
3309
|
setColumns(next) {
|
|
1890
3310
|
if (next === definitions) return;
|
|
1891
3311
|
definitions = next;
|
|
3312
|
+
validation.cancel();
|
|
1892
3313
|
notify();
|
|
1893
3314
|
},
|
|
1894
3315
|
setRowCount(next) {
|
|
@@ -2024,18 +3445,22 @@ function createTable(options) {
|
|
|
2024
3445
|
commit({ columnOrder: order });
|
|
2025
3446
|
},
|
|
2026
3447
|
edit(edits) {
|
|
2027
|
-
return
|
|
3448
|
+
return validation.edit(Array.isArray(edits) ? edits : [edits]);
|
|
2028
3449
|
},
|
|
2029
3450
|
undo() {
|
|
2030
|
-
const
|
|
2031
|
-
if (!
|
|
2032
|
-
|
|
3451
|
+
const entry = undoStack.undo();
|
|
3452
|
+
if (!entry) return false;
|
|
3453
|
+
validation.cancel();
|
|
3454
|
+
if (entry.kind === "cells") replay(entry.changes, "undo");
|
|
3455
|
+
else restoreScene(entry, "undo");
|
|
2033
3456
|
return true;
|
|
2034
3457
|
},
|
|
2035
3458
|
redo() {
|
|
2036
|
-
const
|
|
2037
|
-
if (!
|
|
2038
|
-
|
|
3459
|
+
const entry = undoStack.redo();
|
|
3460
|
+
if (!entry) return false;
|
|
3461
|
+
validation.cancel();
|
|
3462
|
+
if (entry.kind === "cells") replay(entry.changes, "redo");
|
|
3463
|
+
else restoreScene(entry, "redo");
|
|
2039
3464
|
return true;
|
|
2040
3465
|
},
|
|
2041
3466
|
copy(range, { escapeFormulas = false } = {}) {
|
|
@@ -2057,7 +3482,7 @@ function createTable(options) {
|
|
|
2057
3482
|
return toDelimited(matrix);
|
|
2058
3483
|
},
|
|
2059
3484
|
paste(origin, text) {
|
|
2060
|
-
if (!Number.isInteger(origin.row) || !Number.isInteger(origin.column) || origin.row < 0 || origin.column < 0) return
|
|
3485
|
+
if (!Number.isInteger(origin.row) || !Number.isInteger(origin.column) || origin.row < 0 || origin.column < 0) return validation.edit([], [issue("invalid_value", "The paste origin must use non-negative whole row and column indices.")]);
|
|
2061
3486
|
const { rows, columns } = getSnapshot();
|
|
2062
3487
|
const parsed = readDelimited(text, {
|
|
2063
3488
|
maxRows: Math.max(0, rows.length - origin.row),
|
|
@@ -2066,18 +3491,19 @@ function createTable(options) {
|
|
|
2066
3491
|
maxLength: maxPasteLength
|
|
2067
3492
|
});
|
|
2068
3493
|
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
|
|
3494
|
+
return validation.edit(rangeEdits(origin, parsed.rows), issues);
|
|
2070
3495
|
},
|
|
2071
3496
|
clear(range) {
|
|
2072
3497
|
const matrix = Array.from({ length: range.bottom - range.top + 1 }, () => Array.from({ length: range.right - range.left + 1 }, () => ""));
|
|
2073
3498
|
const current = getSnapshot();
|
|
2074
|
-
|
|
3499
|
+
const edits = rangeEdits({
|
|
2075
3500
|
row: range.top,
|
|
2076
3501
|
column: range.left
|
|
2077
3502
|
}, matrix).filter((edit) => {
|
|
2078
3503
|
const row = current.getRow(edit.rowKey);
|
|
2079
3504
|
return row !== void 0 && current.getColumn(edit.column)?.isEditable(row.original) === true;
|
|
2080
|
-
})
|
|
3505
|
+
});
|
|
3506
|
+
return validation.edit(edits);
|
|
2081
3507
|
},
|
|
2082
3508
|
exportRows({ format = "csv", columns: ids, pageOnly = false, headers = true, escapeFormulas = true, depth = false, indent = "" } = {}) {
|
|
2083
3509
|
const current = getSnapshot();
|
|
@@ -2102,6 +3528,8 @@ function createTable(options) {
|
|
|
2102
3528
|
},
|
|
2103
3529
|
destroy() {
|
|
2104
3530
|
destroyed = true;
|
|
3531
|
+
streaming.cancel();
|
|
3532
|
+
validation.cancel();
|
|
2105
3533
|
abortLoads(true);
|
|
2106
3534
|
listeners.clear();
|
|
2107
3535
|
}
|