@pixerate/editor 0.21.0 → 0.22.0
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 +20 -11
- package/dist/agent/index.cjs +8 -8
- package/dist/agent/index.d.cts +2 -2
- package/dist/agent/index.d.ts +2 -2
- package/dist/agent/index.js +2 -2
- package/dist/canvas/index.cjs +1269 -13
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +432 -6
- package/dist/canvas/index.d.ts +432 -6
- package/dist/canvas/index.js +1264 -8
- package/dist/canvas/index.js.map +1 -1
- package/dist/{chunk-U6NDQFOK.cjs → chunk-6M3BCTBD.cjs} +427 -138
- package/dist/chunk-6M3BCTBD.cjs.map +1 -0
- package/dist/{chunk-TVRC4KVT.cjs → chunk-AA4ZEVCC.cjs} +845 -90
- package/dist/chunk-AA4ZEVCC.cjs.map +1 -0
- package/dist/{chunk-LA7PEGU3.cjs → chunk-CL37JGFW.cjs} +4 -4
- package/dist/{chunk-LA7PEGU3.cjs.map → chunk-CL37JGFW.cjs.map} +1 -1
- package/dist/{chunk-A7DKIA72.js → chunk-DENSCKFC.js} +642 -69
- package/dist/chunk-DENSCKFC.js.map +1 -0
- package/dist/{chunk-4QPLEUL2.cjs → chunk-GVSMRPHB.cjs} +1046 -54
- package/dist/chunk-GVSMRPHB.cjs.map +1 -0
- package/dist/{chunk-KYRZDWM4.js → chunk-JJDLPEWL.js} +2 -2
- package/dist/{chunk-3TYMNULT.js → chunk-MJZBNWG7.js} +807 -52
- package/dist/chunk-MJZBNWG7.js.map +1 -0
- package/dist/{chunk-CYFJCKC4.js → chunk-QGHGJ56O.js} +997 -5
- package/dist/chunk-QGHGJ56O.js.map +1 -0
- package/dist/{chunk-GVW4P7BG.js → chunk-RU52I7QO.js} +305 -16
- package/dist/chunk-RU52I7QO.js.map +1 -0
- package/dist/{chunk-JYRIOFEN.cjs → chunk-SAZRENEZ.cjs} +647 -74
- package/dist/chunk-SAZRENEZ.cjs.map +1 -0
- package/dist/{controller-OUdxPY-X.d.ts → controller-B7GIybc4.d.cts} +139 -7
- package/dist/{controller-OUdxPY-X.d.cts → controller-B7GIybc4.d.ts} +139 -7
- package/dist/extensions/index.cjs +14 -4
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.d.cts +3 -3
- package/dist/extensions/index.d.ts +3 -3
- package/dist/extensions/index.js +13 -3
- package/dist/{index-k3mFbPx_.d.cts → index-CCr_z9kF.d.cts} +3 -3
- package/dist/{index-Ng1pG7JE.d.ts → index-Ck6ajwXh.d.ts} +3 -3
- package/dist/index.cjs +250 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +294 -8
- package/dist/index.d.ts +294 -8
- package/dist/index.js +241 -7
- package/dist/index.js.map +1 -1
- package/dist/{registry-iAHbqkYt.d.cts → registry-GJI4gToU.d.cts} +15 -4
- package/dist/{registry-iAHbqkYt.d.ts → registry-GJI4gToU.d.ts} +15 -4
- package/dist/serializers/index.cjs +3 -3
- package/dist/serializers/index.d.cts +1 -1
- package/dist/serializers/index.d.ts +1 -1
- package/dist/serializers/index.js +2 -2
- package/dist/spreadsheet/index.cjs +7 -3
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +33 -4
- package/dist/spreadsheet/index.d.ts +33 -4
- package/dist/spreadsheet/index.js +6 -2
- package/dist/{track-changes-G8JcWB8b.d.cts → track-changes-BSF58RK0.d.cts} +92 -9
- package/dist/{track-changes-G8JcWB8b.d.ts → track-changes-BSF58RK0.d.ts} +92 -9
- package/dist/{types-DMe0vFB1.d.cts → types-CJ4U-HHI.d.cts} +1 -1
- package/dist/{types-DMe0vFB1.d.ts → types-CJ4U-HHI.d.ts} +1 -1
- package/package.json +1 -1
- package/dist/chunk-3TYMNULT.js.map +0 -1
- package/dist/chunk-4QPLEUL2.cjs.map +0 -1
- package/dist/chunk-A7DKIA72.js.map +0 -1
- package/dist/chunk-CYFJCKC4.js.map +0 -1
- package/dist/chunk-GVW4P7BG.js.map +0 -1
- package/dist/chunk-JYRIOFEN.cjs.map +0 -1
- package/dist/chunk-TVRC4KVT.cjs.map +0 -1
- package/dist/chunk-U6NDQFOK.cjs.map +0 -1
- /package/dist/{chunk-KYRZDWM4.js.map → chunk-JJDLPEWL.js.map} +0 -0
|
@@ -2,15 +2,17 @@ import {
|
|
|
2
2
|
FormulaEvaluator,
|
|
3
3
|
WHOLE_COLUMN_END_ROW,
|
|
4
4
|
colNameToIndex,
|
|
5
|
+
describeFormulaError,
|
|
5
6
|
getFormulaArgumentSpecIndex,
|
|
6
7
|
getFormulaFunction,
|
|
7
8
|
getFormulaSignature,
|
|
8
9
|
getFormulaSignatureParts,
|
|
9
10
|
indexToColName,
|
|
11
|
+
isFormulaError,
|
|
10
12
|
parseFormula,
|
|
11
13
|
searchFormulaFunctions,
|
|
12
14
|
tokenizeFormula
|
|
13
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-DENSCKFC.js";
|
|
14
16
|
|
|
15
17
|
// src/spreadsheet/formula/dependency-graph.ts
|
|
16
18
|
var columnDependencyKey = (col) => `${col.toUpperCase()}:`;
|
|
@@ -19,6 +21,8 @@ function extractCellDependencies(node) {
|
|
|
19
21
|
const visit = (curr) => {
|
|
20
22
|
if (curr.type === "CellRef") {
|
|
21
23
|
deps.push({ col: curr.col, row: curr.row });
|
|
24
|
+
} else if (curr.type === "SpillRef") {
|
|
25
|
+
deps.push({ col: curr.col, row: curr.row });
|
|
22
26
|
} else if (curr.type === "Range" && curr.endRow >= WHOLE_COLUMN_END_ROW) {
|
|
23
27
|
const startC = colNameToIndex(curr.startCol);
|
|
24
28
|
const endC = colNameToIndex(curr.endCol);
|
|
@@ -54,6 +58,14 @@ var DependencyGraph = class {
|
|
|
54
58
|
dependencies = /* @__PURE__ */ new Map();
|
|
55
59
|
// Map of cellKey (e.g. "A1") -> Set of cellKeys that depend ON this cell (dependents)
|
|
56
60
|
dependents = /* @__PURE__ */ new Map();
|
|
61
|
+
// Dynamic-array edges, set by the controller after evaluating an anchor:
|
|
62
|
+
// spilled cells depend on their anchor, and a blocked (#SPILL!) anchor
|
|
63
|
+
// depends on the cells blocking it. Kept apart from formula dependencies,
|
|
64
|
+
// so re-entering a formula doesn't drop them.
|
|
65
|
+
spillDependencies = /* @__PURE__ */ new Map();
|
|
66
|
+
spillDependents = /* @__PURE__ */ new Map();
|
|
67
|
+
// anchor -> the edges it registered (so they can be replaced).
|
|
68
|
+
spillEdges = /* @__PURE__ */ new Map();
|
|
57
69
|
setCellFormula(cellKey, formulaStr) {
|
|
58
70
|
const key = cellKey.toUpperCase();
|
|
59
71
|
const oldDeps = this.dependencies.get(key) || /* @__PURE__ */ new Set();
|
|
@@ -84,12 +96,49 @@ var DependencyGraph = class {
|
|
|
84
96
|
clear() {
|
|
85
97
|
this.dependencies.clear();
|
|
86
98
|
this.dependents.clear();
|
|
99
|
+
this.spillDependencies.clear();
|
|
100
|
+
this.spillDependents.clear();
|
|
101
|
+
this.spillEdges.clear();
|
|
87
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Records the dynamic-array edges of the anchor `anchorKey`, replacing its
|
|
105
|
+
* previous ones: every key in `spilledKeys` (cells holding its spilled
|
|
106
|
+
* values) depends on the anchor, and the anchor depends on every key in
|
|
107
|
+
* `blockingKeys` (cells that stop it spilling). Pass empty lists to remove
|
|
108
|
+
* the anchor's edges.
|
|
109
|
+
*/
|
|
110
|
+
setSpillEdges(anchorKey, spilledKeys, blockingKeys = []) {
|
|
111
|
+
const anchor = anchorKey.toUpperCase();
|
|
112
|
+
for (const [from, to] of this.spillEdges.get(anchor) ?? []) {
|
|
113
|
+
this.spillDependencies.get(from)?.delete(to);
|
|
114
|
+
this.spillDependents.get(to)?.delete(from);
|
|
115
|
+
}
|
|
116
|
+
const edges = [];
|
|
117
|
+
for (const key of spilledKeys) edges.push([key.toUpperCase(), anchor]);
|
|
118
|
+
for (const key of blockingKeys) edges.push([anchor, key.toUpperCase()]);
|
|
119
|
+
for (const [from, to] of edges) {
|
|
120
|
+
if (!this.spillDependencies.has(from)) this.spillDependencies.set(from, /* @__PURE__ */ new Set());
|
|
121
|
+
this.spillDependencies.get(from).add(to);
|
|
122
|
+
if (!this.spillDependents.has(to)) this.spillDependents.set(to, /* @__PURE__ */ new Set());
|
|
123
|
+
this.spillDependents.get(to).add(from);
|
|
124
|
+
}
|
|
125
|
+
if (edges.length > 0) this.spillEdges.set(anchor, edges);
|
|
126
|
+
else this.spillEdges.delete(anchor);
|
|
127
|
+
}
|
|
128
|
+
/** The spilled cells registered for `anchorKey` by `setSpillEdges`. */
|
|
129
|
+
getSpilledKeys(anchorKey) {
|
|
130
|
+
const anchor = anchorKey.toUpperCase();
|
|
131
|
+
return (this.spillEdges.get(anchor) ?? []).filter(([, to]) => to === anchor).map(([from]) => from);
|
|
132
|
+
}
|
|
133
|
+
/** Formula dependencies plus dynamic-array (spill) dependencies of a cell. */
|
|
88
134
|
getDependencies(cellKey) {
|
|
89
|
-
|
|
135
|
+
const key = cellKey.toUpperCase();
|
|
136
|
+
return unionOf(this.dependencies.get(key), this.spillDependencies.get(key));
|
|
90
137
|
}
|
|
138
|
+
/** Cells depending on `cellKey`, through formulas or spills. */
|
|
91
139
|
getDependents(cellKey) {
|
|
92
|
-
|
|
140
|
+
const key = cellKey.toUpperCase();
|
|
141
|
+
return unionOf(this.dependents.get(key), this.spillDependents.get(key));
|
|
93
142
|
}
|
|
94
143
|
/**
|
|
95
144
|
* Detects if there is a cycle containing cellKey.
|
|
@@ -101,7 +150,7 @@ var DependencyGraph = class {
|
|
|
101
150
|
const dfs = (node) => {
|
|
102
151
|
visited.add(node);
|
|
103
152
|
recStack.add(node);
|
|
104
|
-
const deps = this.
|
|
153
|
+
const deps = this.getDependencies(node);
|
|
105
154
|
for (const neighbor of deps) {
|
|
106
155
|
if (!visited.has(neighbor)) {
|
|
107
156
|
if (dfs(neighbor)) return true;
|
|
@@ -180,6 +229,7 @@ var DependencyGraph = class {
|
|
|
180
229
|
const node = queue.pop();
|
|
181
230
|
const direct = [
|
|
182
231
|
...this.dependents.get(node) ?? [],
|
|
232
|
+
...this.spillDependents.get(node) ?? [],
|
|
183
233
|
...this.dependents.get(columnDependencyKey(node.replace(/\d+$/, ""))) ?? []
|
|
184
234
|
];
|
|
185
235
|
for (const dep of direct) {
|
|
@@ -192,7 +242,7 @@ var DependencyGraph = class {
|
|
|
192
242
|
}
|
|
193
243
|
const neighbors = (node) => {
|
|
194
244
|
const result = [];
|
|
195
|
-
for (const dep of this.
|
|
245
|
+
for (const dep of this.getDependencies(node)) {
|
|
196
246
|
if (dep.endsWith(":")) {
|
|
197
247
|
const col = dep.slice(0, -1);
|
|
198
248
|
for (const candidate of nodes) {
|
|
@@ -258,6 +308,11 @@ var DependencyGraph = class {
|
|
|
258
308
|
return { order, cyclic };
|
|
259
309
|
}
|
|
260
310
|
};
|
|
311
|
+
function unionOf(a, b) {
|
|
312
|
+
if (!b || b.size === 0) return Array.from(a ?? []);
|
|
313
|
+
if (!a || a.size === 0) return Array.from(b);
|
|
314
|
+
return Array.from(/* @__PURE__ */ new Set([...a, ...b]));
|
|
315
|
+
}
|
|
261
316
|
|
|
262
317
|
// src/spreadsheet/formula/references.ts
|
|
263
318
|
var CELL_PART = /^(\$?)([A-Za-z]+)(\$?)(\d+)$/;
|
|
@@ -335,11 +390,17 @@ function mapFormulaReferences(formula, rewrite) {
|
|
|
335
390
|
let cursor = 0;
|
|
336
391
|
for (let i = 0; i < tokens.length; i++) {
|
|
337
392
|
const token = tokens[i];
|
|
338
|
-
if (token.type !== "CELL_REF" && token.type !== "RANGE") continue;
|
|
393
|
+
if (token.type !== "CELL_REF" && token.type !== "RANGE" && token.type !== "SPILL_REF") continue;
|
|
339
394
|
if (tokens[i + 1]?.type === "LPAREN") continue;
|
|
340
395
|
let end = token.position;
|
|
341
396
|
while (end < formula.length && REF_CHAR.test(formula[end])) end++;
|
|
342
397
|
const original = formula.slice(token.position, end);
|
|
398
|
+
if (token.type === "SPILL_REF") {
|
|
399
|
+
const rewritten = rewrite(original);
|
|
400
|
+
result += formula.slice(cursor, token.position) + (rewritten === "#REF!" ? rewritten : `${rewritten}#`);
|
|
401
|
+
cursor = end + 1;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
343
404
|
result += formula.slice(cursor, token.position) + rewrite(original);
|
|
344
405
|
cursor = end;
|
|
345
406
|
}
|
|
@@ -1228,6 +1289,8 @@ function buildSpreadsheetView(doc, getValue) {
|
|
|
1228
1289
|
|
|
1229
1290
|
// src/spreadsheet/controller.ts
|
|
1230
1291
|
var nextIdSequence = 0;
|
|
1292
|
+
var MAX_SPILL_PASSES = 25;
|
|
1293
|
+
var spillCellId = (rowId, colId) => `${rowId}\0${colId}`;
|
|
1231
1294
|
var SpreadsheetController = class {
|
|
1232
1295
|
document;
|
|
1233
1296
|
activeCell = null;
|
|
@@ -1250,6 +1313,18 @@ var SpreadsheetController = class {
|
|
|
1250
1313
|
pendingCommits = [];
|
|
1251
1314
|
// Row being evaluated, so [property] references resolve per row.
|
|
1252
1315
|
evaluatingRowIndex = null;
|
|
1316
|
+
// Cell being evaluated, for implicit intersection (`=@A:A`).
|
|
1317
|
+
evaluatingCell = null;
|
|
1318
|
+
// Dynamic arrays. Spilled values are derived state: they live here, not in
|
|
1319
|
+
// the document, and every recalculation rebuilds them.
|
|
1320
|
+
spills = /* @__PURE__ */ new Map();
|
|
1321
|
+
// anchor key -> spill
|
|
1322
|
+
spilledBy = /* @__PURE__ */ new Map();
|
|
1323
|
+
// spilled cell key -> anchor key
|
|
1324
|
+
spillCells = /* @__PURE__ */ new Map();
|
|
1325
|
+
// spillCellId -> displayed cell
|
|
1326
|
+
// Cells whose spill ownership changed during the current pass.
|
|
1327
|
+
spillChanges = null;
|
|
1253
1328
|
constructor(options = {}) {
|
|
1254
1329
|
const defaultCols = [
|
|
1255
1330
|
{ id: "col_a", key: "A", title: "A", width: 120, type: "freeform" },
|
|
@@ -1283,7 +1358,9 @@ var SpreadsheetController = class {
|
|
|
1283
1358
|
this.evaluator = new FormulaEvaluator({
|
|
1284
1359
|
resolveCell: (colStr, rowNum) => this.resolveCellByCoord(colStr, rowNum),
|
|
1285
1360
|
resolveRange: (sCol, sRow, eCol, eRow) => this.resolveRangeByCoords(sCol, sRow, eCol, eRow),
|
|
1286
|
-
resolveProperty: (prop) => this.resolveCurrentRowProperty(prop)
|
|
1361
|
+
resolveProperty: (prop) => this.resolveCurrentRowProperty(prop),
|
|
1362
|
+
resolveSpill: (colStr, rowNum) => this.resolveSpillByCoord(colStr, rowNum),
|
|
1363
|
+
getFormulaCell: () => this.evaluatingCell
|
|
1287
1364
|
});
|
|
1288
1365
|
this.syncWithDataSource();
|
|
1289
1366
|
this.recalculateAll();
|
|
@@ -1336,6 +1413,11 @@ var SpreadsheetController = class {
|
|
|
1336
1413
|
}
|
|
1337
1414
|
getCell(rowId, colId) {
|
|
1338
1415
|
const existing = this.document.cells[rowId]?.[colId];
|
|
1416
|
+
if (existing && existing.raw !== "") return existing;
|
|
1417
|
+
if (this.spillCells.size > 0) {
|
|
1418
|
+
const spilled = this.spillCells.get(spillCellId(rowId, colId));
|
|
1419
|
+
if (spilled) return spilled;
|
|
1420
|
+
}
|
|
1339
1421
|
if (existing) return existing;
|
|
1340
1422
|
const col = this.document.columns.find((c) => c.id === colId);
|
|
1341
1423
|
const row = this.document.rows.find((r) => r.id === rowId);
|
|
@@ -1408,7 +1490,30 @@ var SpreadsheetController = class {
|
|
|
1408
1490
|
*/
|
|
1409
1491
|
recalculateCell(rowId, colId) {
|
|
1410
1492
|
const cellKey = this.getCellKey(rowId, colId);
|
|
1411
|
-
this.
|
|
1493
|
+
const anchor = this.spilledBy.get(cellKey);
|
|
1494
|
+
this.recalculateKeys(anchor ? [cellKey, anchor] : [cellKey], true);
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Recalculates `keys` (and, with `includeDependents`, their dependents) in
|
|
1498
|
+
* dependency order. Spill ranges are only known after evaluating their
|
|
1499
|
+
* anchors, so when a pass changes which cells hold spilled values (and so
|
|
1500
|
+
* the dependency graph), the dependents of those cells are recalculated in
|
|
1501
|
+
* another pass, until nothing changes.
|
|
1502
|
+
*/
|
|
1503
|
+
recalculateKeys(keys, includeDependents) {
|
|
1504
|
+
let pending = Array.from(keys);
|
|
1505
|
+
let withDependents = includeDependents;
|
|
1506
|
+
for (let pass = 0; pass < MAX_SPILL_PASSES && pending.length > 0; pass++) {
|
|
1507
|
+
const changed = /* @__PURE__ */ new Set();
|
|
1508
|
+
this.spillChanges = changed;
|
|
1509
|
+
try {
|
|
1510
|
+
this.runEvaluationPlan(this.depGraph.getEvaluationPlan(pending, { includeDependents: withDependents }));
|
|
1511
|
+
} finally {
|
|
1512
|
+
this.spillChanges = null;
|
|
1513
|
+
}
|
|
1514
|
+
pending = Array.from(changed);
|
|
1515
|
+
withDependents = true;
|
|
1516
|
+
}
|
|
1412
1517
|
}
|
|
1413
1518
|
/**
|
|
1414
1519
|
* Rebuilds the dependency graph from scratch and recalculates every formula
|
|
@@ -1416,6 +1521,9 @@ var SpreadsheetController = class {
|
|
|
1416
1521
|
*/
|
|
1417
1522
|
recalculateAll() {
|
|
1418
1523
|
this.depGraph.clear();
|
|
1524
|
+
this.spills.clear();
|
|
1525
|
+
this.spilledBy.clear();
|
|
1526
|
+
this.spillCells.clear();
|
|
1419
1527
|
const formulaKeys = [];
|
|
1420
1528
|
for (const row of this.document.rows) {
|
|
1421
1529
|
for (const col of this.document.columns) {
|
|
@@ -1427,7 +1535,7 @@ var SpreadsheetController = class {
|
|
|
1427
1535
|
}
|
|
1428
1536
|
}
|
|
1429
1537
|
}
|
|
1430
|
-
this.
|
|
1538
|
+
this.recalculateKeys(formulaKeys, false);
|
|
1431
1539
|
}
|
|
1432
1540
|
runEvaluationPlan(plan) {
|
|
1433
1541
|
for (const key of plan.order) {
|
|
@@ -1454,7 +1562,10 @@ var SpreadsheetController = class {
|
|
|
1454
1562
|
if (!this.document.cells[row.id]) this.document.cells[row.id] = {};
|
|
1455
1563
|
const deps = this.depGraph.getDependencies(cellKey);
|
|
1456
1564
|
const selfReference = deps.includes(cellKey) || deps.includes(`${col.key.toUpperCase()}:`);
|
|
1457
|
-
const
|
|
1565
|
+
const spilledKeys = this.depGraph.getSpilledKeys(cellKey);
|
|
1566
|
+
const ownSpill = deps.find((dep) => spilledKeys.includes(dep));
|
|
1567
|
+
const errorMessage = selfReference ? "This formula can't reference its own cell." : ownSpill ? `This formula refers to ${ownSpill}, which holds its own spilled results (a circular reference).` : "This formula is part of a circular reference: it depends on its own result through other cells.";
|
|
1568
|
+
this.clearSpill(cellKey.toUpperCase(), true);
|
|
1458
1569
|
this.document.cells[row.id][col.id] = { ...cell, value: "#CYCLE!", error: "#CYCLE!", errorMessage };
|
|
1459
1570
|
}
|
|
1460
1571
|
evalSingleCellByKey(cellKey) {
|
|
@@ -1471,26 +1582,177 @@ var SpreadsheetController = class {
|
|
|
1471
1582
|
cell.error = void 0;
|
|
1472
1583
|
cell.errorMessage = void 0;
|
|
1473
1584
|
}
|
|
1585
|
+
this.clearSpill(`${colName}${rowNum}`);
|
|
1474
1586
|
return;
|
|
1475
1587
|
}
|
|
1476
1588
|
try {
|
|
1477
1589
|
this.evaluatingRowIndex = rowNum - 1;
|
|
1590
|
+
this.evaluatingCell = { col: colName, row: rowNum };
|
|
1478
1591
|
const result = this.evaluator.evaluateWithDiagnostics(cell.raw);
|
|
1479
1592
|
if (!this.document.cells[row.id]) {
|
|
1480
1593
|
this.document.cells[row.id] = {};
|
|
1481
1594
|
}
|
|
1595
|
+
let { value, error, message } = result;
|
|
1596
|
+
if (result.array) {
|
|
1597
|
+
const blocked = this.placeSpill(`${colName}${rowNum}`, rowNum - 1, this.document.columns.indexOf(col), result.array);
|
|
1598
|
+
if (blocked) {
|
|
1599
|
+
value = "#SPILL!";
|
|
1600
|
+
error = "#SPILL!";
|
|
1601
|
+
message = blocked;
|
|
1602
|
+
}
|
|
1603
|
+
} else {
|
|
1604
|
+
this.clearSpill(`${colName}${rowNum}`);
|
|
1605
|
+
}
|
|
1482
1606
|
this.document.cells[row.id][col.id] = {
|
|
1483
1607
|
...cell,
|
|
1484
|
-
value
|
|
1608
|
+
value,
|
|
1485
1609
|
// `#ERROR!` (malformed formula, wrong argument count) keeps its
|
|
1486
1610
|
// message in `error`, as before; other errors store their code.
|
|
1487
|
-
error:
|
|
1488
|
-
errorMessage:
|
|
1611
|
+
error: error === "#ERROR!" ? message : error,
|
|
1612
|
+
errorMessage: message
|
|
1489
1613
|
};
|
|
1490
1614
|
} finally {
|
|
1491
1615
|
this.evaluatingRowIndex = null;
|
|
1616
|
+
this.evaluatingCell = null;
|
|
1492
1617
|
}
|
|
1493
1618
|
}
|
|
1619
|
+
// --- Dynamic arrays (spilling) ---
|
|
1620
|
+
/**
|
|
1621
|
+
* Spills `array` from the anchor at (row, col) into the cells below and to
|
|
1622
|
+
* the right. Returns why it can't (the anchor then shows `#SPILL!`), or
|
|
1623
|
+
* null once placed. The grid never grows to fit an array, as in Excel.
|
|
1624
|
+
*/
|
|
1625
|
+
placeSpill(anchor, row, col, array) {
|
|
1626
|
+
const { rows, cols } = array;
|
|
1627
|
+
const area = { startRow: row, startCol: col, endRow: row + rows - 1, endCol: col + cols - 1 };
|
|
1628
|
+
const label = `${indexToColName(area.startCol)}${area.startRow + 1}:${indexToColName(area.endCol)}${area.endRow + 1}`;
|
|
1629
|
+
let reason = null;
|
|
1630
|
+
const blockers = [];
|
|
1631
|
+
if (area.endRow >= this.document.rows.length || area.endCol >= this.document.columns.length) {
|
|
1632
|
+
reason = `The ${rows}\xD7${cols} result needs ${label}, which extends past the edge of the sheet.`;
|
|
1633
|
+
} else if (this.getMerges().some((m) => rangesIntersect(m, area))) {
|
|
1634
|
+
reason = `The result can't spill into merged cells (it needs ${label}).`;
|
|
1635
|
+
} else {
|
|
1636
|
+
for (let c = area.startCol; c <= area.endCol; c++) {
|
|
1637
|
+
const column = this.document.columns[c];
|
|
1638
|
+
for (let r = area.startRow; r <= area.endRow; r++) {
|
|
1639
|
+
if (r === row && c === col) continue;
|
|
1640
|
+
const rowObj = this.document.rows[r];
|
|
1641
|
+
const key = `${column.key}${r + 1}`.toUpperCase();
|
|
1642
|
+
const stored = this.document.cells[rowObj.id]?.[column.id];
|
|
1643
|
+
const raw = stored ? stored.raw : this.getDerivedCell(rowObj, column).raw;
|
|
1644
|
+
const owner = this.spilledBy.get(key);
|
|
1645
|
+
if (raw !== "") {
|
|
1646
|
+
blockers.push(key);
|
|
1647
|
+
reason ??= `The result needs ${label}, but ${key} isn't empty.`;
|
|
1648
|
+
} else if (owner && owner !== anchor) {
|
|
1649
|
+
blockers.push(key);
|
|
1650
|
+
reason ??= `The result needs ${label}, but ${key} already holds values spilled from ${owner}.`;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
if (reason) {
|
|
1656
|
+
this.clearSpill(anchor);
|
|
1657
|
+
this.depGraph.setSpillEdges(anchor, [], blockers);
|
|
1658
|
+
return reason;
|
|
1659
|
+
}
|
|
1660
|
+
const previous = this.spills.get(anchor);
|
|
1661
|
+
const cells = [];
|
|
1662
|
+
for (let c = 0; c < cols; c++) {
|
|
1663
|
+
const column = this.document.columns[col + c];
|
|
1664
|
+
for (let r = 0; r < rows; r++) {
|
|
1665
|
+
if (r === 0 && c === 0) continue;
|
|
1666
|
+
const rowObj = this.document.rows[row + r];
|
|
1667
|
+
const key = `${column.key}${row + r + 1}`.toUpperCase();
|
|
1668
|
+
const value = array.values[c * rows + r] ?? "";
|
|
1669
|
+
const display = { raw: "", value, format: column.format, spillAnchor: anchor };
|
|
1670
|
+
if (isFormulaError(value)) {
|
|
1671
|
+
display.error = value;
|
|
1672
|
+
display.errorMessage = describeFormulaError(value);
|
|
1673
|
+
}
|
|
1674
|
+
this.spillCells.set(spillCellId(rowObj.id, column.id), display);
|
|
1675
|
+
if (this.spilledBy.get(key) !== anchor) {
|
|
1676
|
+
this.spilledBy.set(key, anchor);
|
|
1677
|
+
this.spillChanges?.add(key);
|
|
1678
|
+
}
|
|
1679
|
+
cells.push({ key, rowId: rowObj.id, colId: column.id });
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
if (previous) {
|
|
1683
|
+
const kept = new Set(cells.map((cell) => cell.key));
|
|
1684
|
+
for (const old of previous.cells) {
|
|
1685
|
+
if (kept.has(old.key)) continue;
|
|
1686
|
+
this.forgetSpilledCell(anchor, old);
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
this.spills.set(anchor, { anchor, row, col, rows, cols, values: array.values, cells });
|
|
1690
|
+
this.depGraph.setSpillEdges(anchor, cells.map((cell) => cell.key));
|
|
1691
|
+
return null;
|
|
1692
|
+
}
|
|
1693
|
+
/** Removes the array spilled from `anchor`. With `keepEdges`, its dependency edges stay. */
|
|
1694
|
+
clearSpill(anchor, keepEdges = false) {
|
|
1695
|
+
const record = this.spills.get(anchor);
|
|
1696
|
+
if (record) {
|
|
1697
|
+
for (const cell of record.cells) this.forgetSpilledCell(anchor, cell);
|
|
1698
|
+
this.spills.delete(anchor);
|
|
1699
|
+
}
|
|
1700
|
+
if (!keepEdges) this.depGraph.setSpillEdges(anchor, []);
|
|
1701
|
+
}
|
|
1702
|
+
forgetSpilledCell(anchor, cell) {
|
|
1703
|
+
if (this.spilledBy.get(cell.key) !== anchor) return;
|
|
1704
|
+
this.spilledBy.delete(cell.key);
|
|
1705
|
+
this.spillCells.delete(spillCellId(cell.rowId, cell.colId));
|
|
1706
|
+
this.spillChanges?.add(cell.key);
|
|
1707
|
+
}
|
|
1708
|
+
resolveSpillByCoord(colStr, rowNum) {
|
|
1709
|
+
const record = this.spills.get(`${colStr}${rowNum}`.toUpperCase());
|
|
1710
|
+
if (record) return { rows: record.rows, cols: record.cols, values: record.values };
|
|
1711
|
+
const value = this.resolveCellByCoord(colStr, rowNum);
|
|
1712
|
+
return isFormulaError(value) ? value : "#REF!";
|
|
1713
|
+
}
|
|
1714
|
+
toSpill(record) {
|
|
1715
|
+
const row = this.document.rows[record.row];
|
|
1716
|
+
const col = this.document.columns[record.col];
|
|
1717
|
+
return {
|
|
1718
|
+
key: record.anchor,
|
|
1719
|
+
anchor: { row: record.row, col: record.col },
|
|
1720
|
+
range: {
|
|
1721
|
+
startRow: record.row,
|
|
1722
|
+
startCol: record.col,
|
|
1723
|
+
endRow: record.row + record.rows - 1,
|
|
1724
|
+
endCol: record.col + record.cols - 1
|
|
1725
|
+
},
|
|
1726
|
+
formula: row && col ? this.getCell(row.id, col.id).raw : ""
|
|
1727
|
+
};
|
|
1728
|
+
}
|
|
1729
|
+
/**
|
|
1730
|
+
* The dynamic array covering (row, col), whether it is the anchor (the cell
|
|
1731
|
+
* holding the formula) or a spilled cell; null when there is none.
|
|
1732
|
+
*/
|
|
1733
|
+
getSpillAt(row, col) {
|
|
1734
|
+
if (this.spills.size === 0) return null;
|
|
1735
|
+
const key = `${indexToColName(col)}${row + 1}`;
|
|
1736
|
+
const record = this.spills.get(key) ?? this.spills.get(this.spilledBy.get(key) ?? "");
|
|
1737
|
+
return record ? this.toSpill(record) : null;
|
|
1738
|
+
}
|
|
1739
|
+
/** Every dynamic array currently spilled. */
|
|
1740
|
+
getSpills() {
|
|
1741
|
+
return Array.from(this.spills.values(), (record) => this.toSpill(record));
|
|
1742
|
+
}
|
|
1743
|
+
/** Whether `range` overlaps a spilled array (its anchor or spilled cells). */
|
|
1744
|
+
intersectsSpill(range) {
|
|
1745
|
+
for (const record of this.spills.values()) {
|
|
1746
|
+
const area = {
|
|
1747
|
+
startRow: record.row,
|
|
1748
|
+
startCol: record.col,
|
|
1749
|
+
endRow: record.row + record.rows - 1,
|
|
1750
|
+
endCol: record.col + record.cols - 1
|
|
1751
|
+
};
|
|
1752
|
+
if (rangesIntersect(area, range)) return true;
|
|
1753
|
+
}
|
|
1754
|
+
return false;
|
|
1755
|
+
}
|
|
1494
1756
|
resolveCellByCoord(colStr, rowNum) {
|
|
1495
1757
|
const col = this.document.columns.find((c) => c.key.toUpperCase() === colStr.toUpperCase());
|
|
1496
1758
|
const row = this.document.rows[rowNum - 1];
|
|
@@ -1861,7 +2123,15 @@ var SpreadsheetController = class {
|
|
|
1861
2123
|
*/
|
|
1862
2124
|
copyToClipboardData(range = this.selectedRange) {
|
|
1863
2125
|
const r = range ? normalizeRange(range) : { startRow: 0, startCol: 0, endRow: this.document.rows.length - 1, endCol: this.document.columns.length - 1 };
|
|
1864
|
-
const cells = this.readMatrix(r).map(
|
|
2126
|
+
const cells = this.readMatrix(r).map(
|
|
2127
|
+
(row) => row.map((cell) => {
|
|
2128
|
+
const value = cell.error || cell.value;
|
|
2129
|
+
if (cell.spillAnchor && !this.spillAnchorInRange(cell.spillAnchor, r)) {
|
|
2130
|
+
return { raw: value === null || value === void 0 ? "" : String(value), value };
|
|
2131
|
+
}
|
|
2132
|
+
return { raw: cell.raw, value };
|
|
2133
|
+
})
|
|
2134
|
+
);
|
|
1865
2135
|
return createSpreadsheetClipboardData(cells, { row: r.startRow, col: r.startCol });
|
|
1866
2136
|
}
|
|
1867
2137
|
/**
|
|
@@ -1884,6 +2154,10 @@ var SpreadsheetController = class {
|
|
|
1884
2154
|
this.selectRange(r);
|
|
1885
2155
|
return true;
|
|
1886
2156
|
}
|
|
2157
|
+
spillAnchorInRange(anchor, range) {
|
|
2158
|
+
const record = this.spills.get(anchor);
|
|
2159
|
+
return !!record && rangeContains(range, record.row, record.col);
|
|
2160
|
+
}
|
|
1887
2161
|
getSelectionTopLeft() {
|
|
1888
2162
|
if (this.selectedRange) {
|
|
1889
2163
|
const r = normalizeRange(this.selectedRange);
|
|
@@ -1979,6 +2253,7 @@ var SpreadsheetController = class {
|
|
|
1979
2253
|
if (keyCols.length === 0 || keyCols.some((k) => k.index < startCol || k.index > endCol)) return false;
|
|
1980
2254
|
const area = { startRow, startCol, endRow, endCol };
|
|
1981
2255
|
if ((this.document.table?.merges ?? []).some((m) => rangesIntersect(m, area))) return false;
|
|
2256
|
+
if (this.intersectsSpill(area)) return false;
|
|
1982
2257
|
const sourceRows = [];
|
|
1983
2258
|
for (let r = startRow; r <= endRow; r++) sourceRows.push(r);
|
|
1984
2259
|
const sorted = sourceRows.map((r) => ({ r, values: keyCols.map((k) => this.getCellValueAt(r, k.index)) })).sort((a, b) => {
|
|
@@ -2186,6 +2461,7 @@ var SpreadsheetController = class {
|
|
|
2186
2461
|
}
|
|
2187
2462
|
const merges = this.getMerges();
|
|
2188
2463
|
if (merges.some((m) => rangesIntersect(m, r) && !rangeContainsRange(r, m))) return false;
|
|
2464
|
+
if (this.intersectsSpill(r)) return false;
|
|
2189
2465
|
this.applyTableChange(() => {
|
|
2190
2466
|
for (let row = r.startRow; row <= r.endRow; row++) {
|
|
2191
2467
|
const rowId = this.document.rows[row].id;
|
|
@@ -2207,7 +2483,7 @@ var SpreadsheetController = class {
|
|
|
2207
2483
|
if (remaining.length === merges.length) return false;
|
|
2208
2484
|
this.applyTableChange(() => {
|
|
2209
2485
|
this.table.merges = remaining;
|
|
2210
|
-
});
|
|
2486
|
+
}, { recalculate: true });
|
|
2211
2487
|
return true;
|
|
2212
2488
|
}
|
|
2213
2489
|
// --- Autofill ---
|
|
@@ -2328,6 +2604,7 @@ var SpreadsheetController = class {
|
|
|
2328
2604
|
const width = s.endCol - s.startCol + 1;
|
|
2329
2605
|
const t = { startRow: s.startRow, startCol: s.startCol, endRow: s.startRow + width - 1, endCol: s.startCol + height - 1 };
|
|
2330
2606
|
if (this.getMerges().some((m) => rangesIntersect(m, s) || rangesIntersect(m, t))) return false;
|
|
2607
|
+
if (this.intersectsSpill(s) || this.intersectsSpill(t)) return false;
|
|
2331
2608
|
this.applyTableChange(() => {
|
|
2332
2609
|
this.growGrid(t.endRow, t.endCol);
|
|
2333
2610
|
this.remapAllFormulas(({ row, col }) => {
|
|
@@ -2412,6 +2689,17 @@ function stepInOrder(order, from, delta) {
|
|
|
2412
2689
|
}
|
|
2413
2690
|
|
|
2414
2691
|
// src/spreadsheet/menu.ts
|
|
2692
|
+
function clampMenuPosition(point, size, viewport, margin = 4) {
|
|
2693
|
+
const place = (start, length, limit) => {
|
|
2694
|
+
if (start + length <= limit - margin) return Math.max(margin, start);
|
|
2695
|
+
const flipped = start - length;
|
|
2696
|
+
return Math.max(margin, flipped >= margin ? flipped : limit - margin - length);
|
|
2697
|
+
};
|
|
2698
|
+
return {
|
|
2699
|
+
x: place(point.x, size.width, viewport.width),
|
|
2700
|
+
y: place(point.y, size.height, viewport.height)
|
|
2701
|
+
};
|
|
2702
|
+
}
|
|
2415
2703
|
var quote = (value) => {
|
|
2416
2704
|
const text = String(value ?? "");
|
|
2417
2705
|
return text.length > 24 ? `"${text.slice(0, 23)}\u2026"` : `"${text}"`;
|
|
@@ -2694,6 +2982,7 @@ export {
|
|
|
2694
2982
|
computeRowGroups,
|
|
2695
2983
|
buildSpreadsheetView,
|
|
2696
2984
|
SpreadsheetController,
|
|
2985
|
+
clampMenuPosition,
|
|
2697
2986
|
getSpreadsheetMenuItems,
|
|
2698
2987
|
SPREADSHEET_TABLE_ACTIONS,
|
|
2699
2988
|
bindSpreadsheetTableActions,
|
|
@@ -2703,4 +2992,4 @@ export {
|
|
|
2703
2992
|
registerSpreadsheetImportAdapters,
|
|
2704
2993
|
importFileIntoSpreadsheet
|
|
2705
2994
|
};
|
|
2706
|
-
//# sourceMappingURL=chunk-
|
|
2995
|
+
//# sourceMappingURL=chunk-RU52I7QO.js.map
|