@pixerate/editor 0.25.0 → 0.26.1
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 +30 -0
- package/THIRD_PARTY_NOTICES.md +11 -0
- package/dist/agent/index.cjs +174 -11
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +18 -4
- package/dist/agent/index.d.ts +18 -4
- package/dist/agent/index.js +167 -4
- package/dist/agent/index.js.map +1 -1
- package/dist/{arrangeShortcuts-B8O3f8-P.d.cts → arrange-BICutYPk.d.cts} +243 -29
- package/dist/{arrangeShortcuts-sLlD2rSX.d.ts → arrange-BICutYPk.d.ts} +243 -29
- package/dist/builtins-CdPcLwwF.d.cts +176 -0
- package/dist/builtins-Chj6zv_O.d.ts +176 -0
- package/dist/canvas/index.cjs +3917 -19
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +1232 -7
- package/dist/canvas/index.d.ts +1232 -7
- package/dist/canvas/index.js +3916 -18
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-3CC7OPUQ.cjs +831 -0
- package/dist/chunk-3CC7OPUQ.cjs.map +1 -0
- package/dist/{chunk-7WISYZFO.cjs → chunk-66IMF6OD.cjs} +174 -67
- package/dist/chunk-66IMF6OD.cjs.map +1 -0
- package/dist/{chunk-AL6JMGWW.cjs → chunk-CS6NZWWA.cjs} +24 -24
- package/dist/{chunk-AL6JMGWW.cjs.map → chunk-CS6NZWWA.cjs.map} +1 -1
- package/dist/{chunk-C7P22VIP.cjs → chunk-EWCOFSCQ.cjs} +17 -6
- package/dist/chunk-EWCOFSCQ.cjs.map +1 -0
- package/dist/chunk-HDYMLS3X.cjs +409 -0
- package/dist/chunk-HDYMLS3X.cjs.map +1 -0
- package/dist/{chunk-V3CHHWOE.cjs → chunk-HNDSPZ5T.cjs} +4 -4
- package/dist/{chunk-V3CHHWOE.cjs.map → chunk-HNDSPZ5T.cjs.map} +1 -1
- package/dist/{chunk-X4N4WUKB.cjs → chunk-IFHKKVXH.cjs} +150 -38
- package/dist/chunk-IFHKKVXH.cjs.map +1 -0
- package/dist/chunk-KRGXUQ2Y.js +1696 -0
- package/dist/chunk-KRGXUQ2Y.js.map +1 -0
- package/dist/{chunk-PO6A6QQH.cjs → chunk-LN2POBHT.cjs} +742 -970
- package/dist/chunk-LN2POBHT.cjs.map +1 -0
- package/dist/chunk-MR2764X2.js +831 -0
- package/dist/chunk-MR2764X2.js.map +1 -0
- package/dist/{chunk-WSQKHXW7.cjs → chunk-NQF32I6Z.cjs} +1209 -457
- package/dist/chunk-NQF32I6Z.cjs.map +1 -0
- package/dist/{chunk-NAXVVS5I.js → chunk-QJSOZFCZ.js} +2 -2
- package/dist/{chunk-UAKDG62Z.js → chunk-SWE3NPOU.js} +116 -4
- package/dist/chunk-SWE3NPOU.js.map +1 -0
- package/dist/chunk-TXXNBDQX.cjs +1696 -0
- package/dist/chunk-TXXNBDQX.cjs.map +1 -0
- package/dist/chunk-UBCFIABU.js +409 -0
- package/dist/chunk-UBCFIABU.js.map +1 -0
- package/dist/{chunk-ECCCYKGL.js → chunk-UKI64KUD.js} +147 -40
- package/dist/chunk-UKI64KUD.js.map +1 -0
- package/dist/{chunk-QGYDWPRM.js → chunk-WON34HKM.js} +2 -2
- package/dist/{chunk-RR7F5GUM.js → chunk-X7HXL2SB.js} +941 -189
- package/dist/chunk-X7HXL2SB.js.map +1 -0
- package/dist/{chunk-IXHSYOVY.js → chunk-XQFHLT53.js} +638 -866
- package/dist/chunk-XQFHLT53.js.map +1 -0
- package/dist/{chunk-FH6F67DN.js → chunk-ZRNHGAQT.js} +17 -6
- package/dist/{chunk-FH6F67DN.js.map → chunk-ZRNHGAQT.js.map} +1 -1
- package/dist/commands/index.cjs +352 -4
- package/dist/commands/index.cjs.map +1 -1
- package/dist/commands/index.d.cts +37 -108
- package/dist/commands/index.d.ts +37 -108
- package/dist/commands/index.js +351 -3
- package/dist/commands/index.js.map +1 -1
- package/dist/compare/index.cjs +3 -3
- package/dist/compare/index.js +2 -2
- package/dist/{controller-BIJiNoVs.d.cts → controller-CxXGOEmy.d.cts} +209 -13
- package/dist/{controller-BIJiNoVs.d.ts → controller-CxXGOEmy.d.ts} +209 -13
- package/dist/export/index.cjs +36 -0
- package/dist/export/index.cjs.map +1 -0
- package/dist/export/index.d.cts +243 -0
- package/dist/export/index.d.ts +243 -0
- package/dist/export/index.js +36 -0
- package/dist/export/index.js.map +1 -0
- package/dist/extensions/index.cjs +54 -6
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.d.cts +7 -2
- package/dist/extensions/index.d.ts +7 -2
- package/dist/extensions/index.js +59 -11
- package/dist/{index-7a6bHTLy.d.ts → index-B273Zllk.d.ts} +211 -207
- package/dist/{index-Csd5qI8u.d.cts → index-jcJwmBYt.d.cts} +211 -207
- package/dist/index.cjs +146 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +50 -14
- package/dist/index.d.ts +50 -14
- package/dist/index.js +138 -24
- package/dist/index.js.map +1 -1
- package/dist/registry-CED5C0lA.d.cts +204 -0
- package/dist/registry-CED5C0lA.d.ts +204 -0
- package/dist/{registry-D32LpIc4.d.cts → registry-D0GADKc7.d.cts} +1 -1
- package/dist/{registry-evKTP4ls.d.ts → registry-DhVUZioj.d.ts} +1 -1
- package/dist/serializers/index.cjs +6 -4
- package/dist/serializers/index.cjs.map +1 -1
- package/dist/serializers/index.d.cts +15 -4
- package/dist/serializers/index.d.ts +15 -4
- package/dist/serializers/index.js +5 -3
- package/dist/spreadsheet/index.cjs +33 -3
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +106 -3
- package/dist/spreadsheet/index.d.ts +106 -3
- package/dist/spreadsheet/index.js +32 -2
- package/package.json +10 -1
- package/dist/chunk-7WISYZFO.cjs.map +0 -1
- package/dist/chunk-C7P22VIP.cjs.map +0 -1
- package/dist/chunk-ECCCYKGL.js.map +0 -1
- package/dist/chunk-IXHSYOVY.js.map +0 -1
- package/dist/chunk-PO6A6QQH.cjs.map +0 -1
- package/dist/chunk-RR7F5GUM.js.map +0 -1
- package/dist/chunk-UAKDG62Z.js.map +0 -1
- package/dist/chunk-WSQKHXW7.cjs.map +0 -1
- package/dist/chunk-X4N4WUKB.cjs.map +0 -1
- package/dist/types-DX4Mh4SL.d.cts +0 -217
- package/dist/types-DX4Mh4SL.d.ts +0 -217
- /package/dist/{chunk-NAXVVS5I.js.map → chunk-QJSOZFCZ.js.map} +0 -0
- /package/dist/{chunk-QGYDWPRM.js.map → chunk-WON34HKM.js.map} +0 -0
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
toBoolean,
|
|
32
32
|
toText,
|
|
33
33
|
tokenizeFormula
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-ZRNHGAQT.js";
|
|
35
35
|
|
|
36
36
|
// src/spreadsheet/formula/dependency-graph.ts
|
|
37
37
|
function extractNameDependencies(node) {
|
|
@@ -105,11 +105,178 @@ function extractCellDependencies(node) {
|
|
|
105
105
|
visit(node);
|
|
106
106
|
return deps;
|
|
107
107
|
}
|
|
108
|
+
function extractReferenceDependencies(node) {
|
|
109
|
+
const deps = [];
|
|
110
|
+
const stack = [node];
|
|
111
|
+
while (stack.length > 0) {
|
|
112
|
+
const curr = stack.pop();
|
|
113
|
+
const sheet = "sheet" in curr && curr.sheet !== void 0 ? "sheetEnd" in curr && curr.sheetEnd !== void 0 ? { sheet: curr.sheet, sheetEnd: curr.sheetEnd } : { sheet: curr.sheet } : {};
|
|
114
|
+
switch (curr.type) {
|
|
115
|
+
case "CellRef":
|
|
116
|
+
case "SpillRef":
|
|
117
|
+
deps.push({ kind: "cell", col: curr.col.toUpperCase(), row: curr.row, ...sheet });
|
|
118
|
+
break;
|
|
119
|
+
case "Range": {
|
|
120
|
+
const startC = colNameToIndex(curr.startCol);
|
|
121
|
+
const endC = colNameToIndex(curr.endCol);
|
|
122
|
+
const whole = curr.endRow >= WHOLE_COLUMN_END_ROW;
|
|
123
|
+
deps.push({
|
|
124
|
+
kind: "range",
|
|
125
|
+
startCol: indexToColName(Math.min(startC, endC)),
|
|
126
|
+
endCol: indexToColName(Math.max(startC, endC)),
|
|
127
|
+
startRow: whole ? 1 : Math.min(curr.startRow, curr.endRow),
|
|
128
|
+
endRow: whole ? WHOLE_COLUMN_END_ROW : Math.max(curr.startRow, curr.endRow),
|
|
129
|
+
...sheet
|
|
130
|
+
});
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case "BinaryOp":
|
|
134
|
+
stack.push(curr.right, curr.left);
|
|
135
|
+
break;
|
|
136
|
+
case "UnaryOp":
|
|
137
|
+
stack.push(curr.argument);
|
|
138
|
+
break;
|
|
139
|
+
case "FunctionCall":
|
|
140
|
+
for (let i = curr.args.length - 1; i >= 0; i--) stack.push(curr.args[i]);
|
|
141
|
+
break;
|
|
142
|
+
case "Invoke":
|
|
143
|
+
for (let i = curr.args.length - 1; i >= 0; i--) stack.push(curr.args[i]);
|
|
144
|
+
stack.push(curr.callee);
|
|
145
|
+
break;
|
|
146
|
+
default:
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return deps;
|
|
151
|
+
}
|
|
152
|
+
function rangeDependencyKey(startCol, startRow, endCol, endRow, sheetPrefix = "") {
|
|
153
|
+
const prefix = sheetPrefix === "" ? "" : `${sheetPrefix}!`;
|
|
154
|
+
return `${prefix}${startCol}${startRow}:${endCol}${endRow}`.toUpperCase();
|
|
155
|
+
}
|
|
156
|
+
var WIDE_RANGE_COLUMNS = 64;
|
|
157
|
+
var PENDING_INTERVALS = 32;
|
|
158
|
+
var IntervalIndex = class {
|
|
159
|
+
items = [];
|
|
160
|
+
dead = new Uint8Array(0);
|
|
161
|
+
deadCount = 0;
|
|
162
|
+
maxEnd = new Float64Array(0);
|
|
163
|
+
leaves = 0;
|
|
164
|
+
position = /* @__PURE__ */ new Map();
|
|
165
|
+
pending = /* @__PURE__ */ new Map();
|
|
166
|
+
/** Live entries. */
|
|
167
|
+
get size() {
|
|
168
|
+
return this.items.length - this.deadCount + this.pending.size;
|
|
169
|
+
}
|
|
170
|
+
/** Adds (or revives) the entry `id`. An id always names the same interval. */
|
|
171
|
+
insert(r0, r1, id, payload) {
|
|
172
|
+
const at = this.position.get(id);
|
|
173
|
+
if (at !== void 0) {
|
|
174
|
+
if (this.dead[at]) {
|
|
175
|
+
this.dead[at] = 0;
|
|
176
|
+
this.deadCount--;
|
|
177
|
+
this.items[at].payload = payload;
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
this.pending.set(id, { r0, r1, id, payload });
|
|
182
|
+
}
|
|
183
|
+
remove(id) {
|
|
184
|
+
const at = this.position.get(id);
|
|
185
|
+
if (at !== void 0) {
|
|
186
|
+
if (!this.dead[at]) {
|
|
187
|
+
this.dead[at] = 1;
|
|
188
|
+
this.deadCount++;
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
this.pending.delete(id);
|
|
193
|
+
}
|
|
194
|
+
/** Merges pending insertions and drops dead entries when there are enough of them. */
|
|
195
|
+
settle() {
|
|
196
|
+
if (this.pending.size <= PENDING_INTERVALS && this.deadCount <= Math.max(this.items.length / 2, PENDING_INTERVALS)) return;
|
|
197
|
+
const all = [];
|
|
198
|
+
this.items.forEach((item, i) => {
|
|
199
|
+
if (!this.dead[i]) all.push(item);
|
|
200
|
+
});
|
|
201
|
+
for (const item of this.pending.values()) all.push(item);
|
|
202
|
+
all.sort((a, b) => a.r0 - b.r0 || a.r1 - b.r1 || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
203
|
+
let leaves = 1;
|
|
204
|
+
while (leaves < all.length) leaves *= 2;
|
|
205
|
+
const maxEnd = new Float64Array(2 * leaves).fill(-1);
|
|
206
|
+
all.forEach((item, i) => {
|
|
207
|
+
maxEnd[leaves + i] = item.r1;
|
|
208
|
+
});
|
|
209
|
+
for (let i = leaves - 1; i >= 1; i--) maxEnd[i] = Math.max(maxEnd[2 * i], maxEnd[2 * i + 1]);
|
|
210
|
+
this.items = all;
|
|
211
|
+
this.dead = new Uint8Array(all.length);
|
|
212
|
+
this.deadCount = 0;
|
|
213
|
+
this.maxEnd = maxEnd;
|
|
214
|
+
this.leaves = leaves;
|
|
215
|
+
this.position = new Map(all.map((item, i) => [item.id, i]));
|
|
216
|
+
this.pending = /* @__PURE__ */ new Map();
|
|
217
|
+
}
|
|
218
|
+
/** Calls `fn` for every live interval overlapping rows `a..b` (`r0 <= b && r1 >= a`). */
|
|
219
|
+
query(a, b, fn) {
|
|
220
|
+
this.settle();
|
|
221
|
+
for (const item of this.pending.values()) {
|
|
222
|
+
if (item.r0 <= b && item.r1 >= a) fn(item.payload, item);
|
|
223
|
+
}
|
|
224
|
+
let lo = 0;
|
|
225
|
+
let hi = this.items.length;
|
|
226
|
+
while (lo < hi) {
|
|
227
|
+
const mid = lo + hi >>> 1;
|
|
228
|
+
if (this.items[mid].r0 <= b) lo = mid + 1;
|
|
229
|
+
else hi = mid;
|
|
230
|
+
}
|
|
231
|
+
if (lo === 0) return;
|
|
232
|
+
this.walk(1, 0, this.leaves, lo, a, fn);
|
|
233
|
+
}
|
|
234
|
+
walk(node, lo, hi, p, a, fn) {
|
|
235
|
+
if (lo >= p || this.maxEnd[node] < a) return;
|
|
236
|
+
if (hi - lo <= 1) {
|
|
237
|
+
const item = this.items[lo];
|
|
238
|
+
if (item && !this.dead[lo] && item.r1 >= a) fn(item.payload, item);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const mid = lo + (hi - lo >>> 1);
|
|
242
|
+
this.walk(2 * node, lo, mid, p, a, fn);
|
|
243
|
+
this.walk(2 * node + 1, mid, hi, p, a, fn);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
var CELL_KEY = /^([A-Z]+)(\d+)$/;
|
|
247
|
+
var RANGE_KEY = /^([A-Z]+)(\d+):([A-Z]+)(\d+)$/;
|
|
248
|
+
var COLUMN_KEY = /^([A-Z]+):$/;
|
|
249
|
+
function parseKey(key) {
|
|
250
|
+
const bang = key.lastIndexOf("!");
|
|
251
|
+
const sheet = bang < 0 ? "" : key.slice(0, bang);
|
|
252
|
+
const ref = bang < 0 ? key : key.slice(bang + 1);
|
|
253
|
+
let m = CELL_KEY.exec(ref);
|
|
254
|
+
if (m) return { kind: "cell", sheet, col: colNameToIndex(m[1]), row: Number(m[2]) };
|
|
255
|
+
m = RANGE_KEY.exec(ref);
|
|
256
|
+
if (m) {
|
|
257
|
+
const ca = colNameToIndex(m[1]);
|
|
258
|
+
const cb = colNameToIndex(m[3]);
|
|
259
|
+
const ra = Number(m[2]);
|
|
260
|
+
const rb = Number(m[4]);
|
|
261
|
+
return { kind: "range", sheet, c0: Math.min(ca, cb), c1: Math.max(ca, cb), r0: Math.min(ra, rb), r1: Math.max(ra, rb) };
|
|
262
|
+
}
|
|
263
|
+
m = COLUMN_KEY.exec(ref);
|
|
264
|
+
if (m) {
|
|
265
|
+
const c = colNameToIndex(m[1]);
|
|
266
|
+
return { kind: "range", sheet, c0: c, c1: c, r0: 1, r1: WHOLE_COLUMN_END_ROW };
|
|
267
|
+
}
|
|
268
|
+
return { kind: "opaque" };
|
|
269
|
+
}
|
|
270
|
+
var VIRTUAL_PREFIX = "\0";
|
|
108
271
|
var DependencyGraph = class {
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
//
|
|
112
|
-
|
|
272
|
+
// Formula cell -> what it reads (single cells and whole ranges).
|
|
273
|
+
nodes = /* @__PURE__ */ new Map();
|
|
274
|
+
// Cell -> formulas reading it as a single cell.
|
|
275
|
+
cellDependents = /* @__PURE__ */ new Map();
|
|
276
|
+
// Sheet ('' for unqualified keys) -> interval index of the ranges formulas read.
|
|
277
|
+
rangeIndex = /* @__PURE__ */ new Map();
|
|
278
|
+
// Formulas recalculated on every recalculation (NOW, RAND, …).
|
|
279
|
+
volatile = /* @__PURE__ */ new Set();
|
|
113
280
|
// Dynamic-array edges, set by the controller after evaluating an anchor:
|
|
114
281
|
// spilled cells depend on their anchor, and a blocked (#SPILL!) anchor
|
|
115
282
|
// depends on the cells blocking it. Kept apart from formula dependencies,
|
|
@@ -118,60 +285,145 @@ var DependencyGraph = class {
|
|
|
118
285
|
spillDependents = /* @__PURE__ */ new Map();
|
|
119
286
|
// anchor -> the edges it registered (so they can be replaced).
|
|
120
287
|
spillEdges = /* @__PURE__ */ new Map();
|
|
288
|
+
// Blocked anchor -> its blockers. These edges only propagate changes
|
|
289
|
+
// (clearing a blocker recalculates the anchor): whether a cell blocks
|
|
290
|
+
// depends on its input, not its value, so they don't order evaluation and
|
|
291
|
+
// can't close a cycle.
|
|
292
|
+
blockingEdges = /* @__PURE__ */ new Map();
|
|
293
|
+
parsed = /* @__PURE__ */ new Map();
|
|
294
|
+
parse(key) {
|
|
295
|
+
let parsed = this.parsed.get(key);
|
|
296
|
+
if (!parsed) {
|
|
297
|
+
parsed = parseKey(key);
|
|
298
|
+
if (this.parsed.size > 2e5) this.parsed.clear();
|
|
299
|
+
this.parsed.set(key, parsed);
|
|
300
|
+
}
|
|
301
|
+
return parsed;
|
|
302
|
+
}
|
|
303
|
+
sheetIndex(sheet) {
|
|
304
|
+
let index = this.rangeIndex.get(sheet);
|
|
305
|
+
if (!index) {
|
|
306
|
+
index = { cols: /* @__PURE__ */ new Map(), wide: new IntervalIndex() };
|
|
307
|
+
this.rangeIndex.set(sheet, index);
|
|
308
|
+
}
|
|
309
|
+
return index;
|
|
310
|
+
}
|
|
311
|
+
removeNode(key) {
|
|
312
|
+
const node = this.nodes.get(key);
|
|
313
|
+
if (!node) return;
|
|
314
|
+
for (const dep of node.cells) {
|
|
315
|
+
const set = this.cellDependents.get(dep);
|
|
316
|
+
if (!set) continue;
|
|
317
|
+
set.delete(key);
|
|
318
|
+
if (set.size === 0) this.cellDependents.delete(dep);
|
|
319
|
+
}
|
|
320
|
+
for (const range of node.ranges) {
|
|
321
|
+
const index = this.rangeIndex.get(range.sheet);
|
|
322
|
+
if (!index) continue;
|
|
323
|
+
if (range.c1 - range.c0 + 1 > WIDE_RANGE_COLUMNS) {
|
|
324
|
+
index.wide.remove(`${range.r0}:${range.r1}:${range.c0}:${range.c1}:${key}`);
|
|
325
|
+
} else {
|
|
326
|
+
for (let c = range.c0; c <= range.c1; c++) index.cols.get(c)?.remove(`${range.r0}:${range.r1}:${key}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
this.nodes.delete(key);
|
|
330
|
+
}
|
|
121
331
|
/**
|
|
122
|
-
* Sets
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
332
|
+
* Sets what `cellKey` depends on directly, replacing its previous
|
|
333
|
+
* dependencies. Keys are single cells (`B2`), rectangles (`A1:B9`) or
|
|
334
|
+
* whole columns (`A:`, see `columnDependencyKey`), each optionally
|
|
335
|
+
* sheet-qualified (`SHEET!A1:A9`). Ranges are stored whole in an interval
|
|
336
|
+
* index, never expanded cell by cell. `setCellFormula` derives the keys
|
|
337
|
+
* from a formula.
|
|
126
338
|
*/
|
|
127
339
|
setCellDependencies(cellKey2, dependencyKeys) {
|
|
128
340
|
const key = cellKey2.toUpperCase();
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
this.dependencies.delete(key);
|
|
133
|
-
const deps = /* @__PURE__ */ new Set();
|
|
341
|
+
this.removeNode(key);
|
|
342
|
+
const node = { cells: /* @__PURE__ */ new Set(), ranges: [] };
|
|
343
|
+
const seenRanges = /* @__PURE__ */ new Set();
|
|
134
344
|
for (const raw of dependencyKeys) {
|
|
135
345
|
const dep = raw.toUpperCase();
|
|
136
|
-
|
|
137
|
-
if (!
|
|
138
|
-
|
|
346
|
+
const parsed = this.parse(dep);
|
|
347
|
+
if (parsed.kind === "range" && !(parsed.c0 === parsed.c1 && parsed.r0 === parsed.r1)) {
|
|
348
|
+
if (seenRanges.has(dep)) continue;
|
|
349
|
+
seenRanges.add(dep);
|
|
350
|
+
node.ranges.push({ key: dep, sheet: parsed.sheet, c0: parsed.c0, c1: parsed.c1, r0: parsed.r0, r1: parsed.r1 });
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
const cell = parsed.kind === "range" ? `${parsed.sheet === "" ? "" : `${parsed.sheet}!`}${indexToColName(parsed.c0)}${parsed.r0}` : dep;
|
|
354
|
+
node.cells.add(cell);
|
|
355
|
+
}
|
|
356
|
+
if (node.cells.size === 0 && node.ranges.length === 0) {
|
|
357
|
+
this.volatile.delete(key);
|
|
358
|
+
return;
|
|
139
359
|
}
|
|
140
|
-
|
|
360
|
+
for (const dep of node.cells) {
|
|
361
|
+
let set = this.cellDependents.get(dep);
|
|
362
|
+
if (!set) this.cellDependents.set(dep, set = /* @__PURE__ */ new Set());
|
|
363
|
+
set.add(key);
|
|
364
|
+
}
|
|
365
|
+
for (const range of node.ranges) {
|
|
366
|
+
const index = this.sheetIndex(range.sheet);
|
|
367
|
+
if (range.c1 - range.c0 + 1 > WIDE_RANGE_COLUMNS) {
|
|
368
|
+
index.wide.insert(range.r0, range.r1, `${range.r0}:${range.r1}:${range.c0}:${range.c1}:${key}`, {
|
|
369
|
+
c0: range.c0,
|
|
370
|
+
c1: range.c1,
|
|
371
|
+
key
|
|
372
|
+
});
|
|
373
|
+
} else {
|
|
374
|
+
for (let c = range.c0; c <= range.c1; c++) {
|
|
375
|
+
let col = index.cols.get(c);
|
|
376
|
+
if (!col) index.cols.set(c, col = new IntervalIndex());
|
|
377
|
+
col.insert(range.r0, range.r1, `${range.r0}:${range.r1}:${key}`, key);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
this.nodes.set(key, node);
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Marks `cellKey` as volatile (its formula uses NOW, RAND, …): it is
|
|
385
|
+
* recalculated by every plan made with `includeVolatile`, whatever changed.
|
|
386
|
+
*/
|
|
387
|
+
setVolatile(cellKey2, volatile) {
|
|
388
|
+
const key = cellKey2.toUpperCase();
|
|
389
|
+
if (volatile) this.volatile.add(key);
|
|
390
|
+
else this.volatile.delete(key);
|
|
391
|
+
}
|
|
392
|
+
/** Cells whose formulas are volatile. */
|
|
393
|
+
getVolatileKeys() {
|
|
394
|
+
return Array.from(this.volatile);
|
|
141
395
|
}
|
|
142
396
|
setCellFormula(cellKey2, formulaStr) {
|
|
143
397
|
const key = cellKey2.toUpperCase();
|
|
144
|
-
const oldDeps = this.dependencies.get(key) || /* @__PURE__ */ new Set();
|
|
145
|
-
for (const dep of oldDeps) {
|
|
146
|
-
this.dependents.get(dep)?.delete(key);
|
|
147
|
-
}
|
|
148
|
-
this.dependencies.delete(key);
|
|
149
398
|
if (!formulaStr || !formulaStr.startsWith("=")) {
|
|
399
|
+
this.setCellDependencies(key, []);
|
|
150
400
|
return;
|
|
151
401
|
}
|
|
402
|
+
let ast;
|
|
152
403
|
try {
|
|
153
|
-
|
|
154
|
-
const deps = extractCellDependencies(ast);
|
|
155
|
-
const newDeps = /* @__PURE__ */ new Set();
|
|
156
|
-
for (const d of deps) {
|
|
157
|
-
const depKey = d.row === 0 ? columnDependencyKey(d.col) : `${d.col}${d.row}`.toUpperCase();
|
|
158
|
-
newDeps.add(depKey);
|
|
159
|
-
if (!this.dependents.has(depKey)) {
|
|
160
|
-
this.dependents.set(depKey, /* @__PURE__ */ new Set());
|
|
161
|
-
}
|
|
162
|
-
this.dependents.get(depKey).add(key);
|
|
163
|
-
}
|
|
164
|
-
this.dependencies.set(key, newDeps);
|
|
404
|
+
ast = parseFormula(formulaStr);
|
|
165
405
|
} catch {
|
|
406
|
+
this.setCellDependencies(key, []);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const keys = [];
|
|
410
|
+
for (const dep of extractReferenceDependencies(ast)) {
|
|
411
|
+
keys.push(
|
|
412
|
+
dep.kind === "cell" ? `${dep.col}${dep.row}` : dep.endRow >= WHOLE_COLUMN_END_ROW && dep.startCol === dep.endCol ? columnDependencyKey(dep.startCol) : rangeDependencyKey(dep.startCol, dep.startRow, dep.endCol, dep.endRow)
|
|
413
|
+
);
|
|
166
414
|
}
|
|
415
|
+
this.setCellDependencies(key, keys);
|
|
167
416
|
}
|
|
168
417
|
/** Removes every registered formula and dependency. */
|
|
169
418
|
clear() {
|
|
170
|
-
this.
|
|
171
|
-
this.
|
|
419
|
+
this.nodes.clear();
|
|
420
|
+
this.cellDependents.clear();
|
|
421
|
+
this.rangeIndex.clear();
|
|
422
|
+
this.volatile.clear();
|
|
172
423
|
this.spillDependencies.clear();
|
|
173
424
|
this.spillDependents.clear();
|
|
174
425
|
this.spillEdges.clear();
|
|
426
|
+
this.blockingEdges.clear();
|
|
175
427
|
}
|
|
176
428
|
/**
|
|
177
429
|
* Records the dynamic-array edges of the anchor `anchorKey`, replacing its
|
|
@@ -186,9 +438,15 @@ var DependencyGraph = class {
|
|
|
186
438
|
this.spillDependencies.get(from)?.delete(to);
|
|
187
439
|
this.spillDependents.get(to)?.delete(from);
|
|
188
440
|
}
|
|
441
|
+
this.blockingEdges.delete(anchor);
|
|
189
442
|
const edges = [];
|
|
190
443
|
for (const key of spilledKeys) edges.push([key.toUpperCase(), anchor]);
|
|
191
|
-
|
|
444
|
+
const blockers = /* @__PURE__ */ new Set();
|
|
445
|
+
for (const key of blockingKeys) {
|
|
446
|
+
edges.push([anchor, key.toUpperCase()]);
|
|
447
|
+
blockers.add(key.toUpperCase());
|
|
448
|
+
}
|
|
449
|
+
if (blockers.size > 0) this.blockingEdges.set(anchor, blockers);
|
|
192
450
|
for (const [from, to] of edges) {
|
|
193
451
|
if (!this.spillDependencies.has(from)) this.spillDependencies.set(from, /* @__PURE__ */ new Set());
|
|
194
452
|
this.spillDependencies.get(from).add(to);
|
|
@@ -198,133 +456,201 @@ var DependencyGraph = class {
|
|
|
198
456
|
if (edges.length > 0) this.spillEdges.set(anchor, edges);
|
|
199
457
|
else this.spillEdges.delete(anchor);
|
|
200
458
|
}
|
|
459
|
+
/**
|
|
460
|
+
* Drops the edge from a spilled cell to the anchor whose values it holds,
|
|
461
|
+
* for a cell that just got input of its own: its value no longer comes
|
|
462
|
+
* from the anchor (which recalculates and becomes `#SPILL!`), and the
|
|
463
|
+
* stale edge could otherwise close a false cycle with the cell's new
|
|
464
|
+
* formula before the anchor is evaluated again.
|
|
465
|
+
*/
|
|
466
|
+
detachSpilledCell(cellKey2) {
|
|
467
|
+
const key = cellKey2.toUpperCase();
|
|
468
|
+
const anchors = this.spillDependencies.get(key);
|
|
469
|
+
if (!anchors) return;
|
|
470
|
+
for (const anchor of Array.from(anchors)) {
|
|
471
|
+
if (this.blockingEdges.get(key)?.has(anchor)) continue;
|
|
472
|
+
const edges = this.spillEdges.get(anchor);
|
|
473
|
+
if (!edges?.some(([from, to]) => from === key && to === anchor)) continue;
|
|
474
|
+
anchors.delete(anchor);
|
|
475
|
+
this.spillDependents.get(anchor)?.delete(key);
|
|
476
|
+
const kept = edges.filter(([from, to]) => !(from === key && to === anchor));
|
|
477
|
+
if (kept.length > 0) this.spillEdges.set(anchor, kept);
|
|
478
|
+
else this.spillEdges.delete(anchor);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
201
481
|
/** The spilled cells registered for `anchorKey` by `setSpillEdges`. */
|
|
202
482
|
getSpilledKeys(anchorKey) {
|
|
203
483
|
const anchor = anchorKey.toUpperCase();
|
|
204
484
|
return (this.spillEdges.get(anchor) ?? []).filter(([, to]) => to === anchor).map(([from]) => from);
|
|
205
485
|
}
|
|
206
|
-
/**
|
|
486
|
+
/**
|
|
487
|
+
* What a cell depends on directly: single cells, ranges (as registered,
|
|
488
|
+
* e.g. `A1:A9` or `A:`, not expanded) and dynamic-array (spill) edges.
|
|
489
|
+
*/
|
|
207
490
|
getDependencies(cellKey2) {
|
|
208
491
|
const key = cellKey2.toUpperCase();
|
|
209
|
-
|
|
492
|
+
const node = this.nodes.get(key);
|
|
493
|
+
const own = node ? [...node.cells, ...node.ranges.map((r) => r.key)] : [];
|
|
494
|
+
const spill = this.spillDependencies.get(key);
|
|
495
|
+
if (!spill || spill.size === 0) return own;
|
|
496
|
+
return Array.from(/* @__PURE__ */ new Set([...own, ...spill]));
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Whether the formula in `formulaKey` reads `cellKey` directly: as a cell,
|
|
500
|
+
* inside one of its ranges, or through a spill edge.
|
|
501
|
+
*/
|
|
502
|
+
dependsOn(formulaKey, cellKey2) {
|
|
503
|
+
const key = formulaKey.toUpperCase();
|
|
504
|
+
const target = cellKey2.toUpperCase();
|
|
505
|
+
if (this.spillDependencies.get(key)?.has(target)) return true;
|
|
506
|
+
const node = this.nodes.get(key);
|
|
507
|
+
if (!node) return false;
|
|
508
|
+
if (node.cells.has(target)) return true;
|
|
509
|
+
const parsed = this.parse(target);
|
|
510
|
+
if (parsed.kind !== "cell") return false;
|
|
511
|
+
return node.ranges.some(
|
|
512
|
+
(r) => r.sheet === parsed.sheet && parsed.col >= r.c0 && parsed.col <= r.c1 && parsed.row >= r.r0 && parsed.row <= r.r1
|
|
513
|
+
);
|
|
210
514
|
}
|
|
211
|
-
/**
|
|
515
|
+
/**
|
|
516
|
+
* Formulas (and spilled cells) reading `cellKey` directly, through formulas
|
|
517
|
+
* or spills. Found in O(log n + k) through the range index.
|
|
518
|
+
*/
|
|
212
519
|
getDependents(cellKey2) {
|
|
213
|
-
const
|
|
214
|
-
|
|
520
|
+
const out = /* @__PURE__ */ new Set();
|
|
521
|
+
this.collectDependents(cellKey2.toUpperCase(), (key) => out.add(key));
|
|
522
|
+
return Array.from(out);
|
|
523
|
+
}
|
|
524
|
+
collectDependents(key, add) {
|
|
525
|
+
const cells = this.cellDependents.get(key);
|
|
526
|
+
if (cells) for (const dep of cells) add(dep);
|
|
527
|
+
const spill = this.spillDependents.get(key);
|
|
528
|
+
if (spill) for (const dep of spill) add(dep);
|
|
529
|
+
if (this.rangeIndex.size === 0) return;
|
|
530
|
+
const parsed = this.parse(key);
|
|
531
|
+
if (parsed.kind !== "cell") return;
|
|
532
|
+
const index = this.rangeIndex.get(parsed.sheet);
|
|
533
|
+
if (!index) return;
|
|
534
|
+
index.cols.get(parsed.col)?.query(parsed.row, parsed.row, add);
|
|
535
|
+
if (index.wide.size > 0) {
|
|
536
|
+
index.wide.query(parsed.row, parsed.row, (wide) => {
|
|
537
|
+
if (parsed.col >= wide.c0 && parsed.col <= wide.c1) add(wide.key);
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
/** Sizes of the index: ranges count once (per column), not once per cell. */
|
|
542
|
+
getIndexStats() {
|
|
543
|
+
let cellEdges = 0;
|
|
544
|
+
let rangeEntries = 0;
|
|
545
|
+
for (const node of this.nodes.values()) {
|
|
546
|
+
cellEdges += node.cells.size;
|
|
547
|
+
for (const r of node.ranges) rangeEntries += r.c1 - r.c0 + 1 > WIDE_RANGE_COLUMNS ? 1 : r.c1 - r.c0 + 1;
|
|
548
|
+
}
|
|
549
|
+
return { formulas: this.nodes.size, cellEdges, rangeEntries, volatile: this.volatile.size };
|
|
215
550
|
}
|
|
216
551
|
/**
|
|
217
552
|
* Detects if there is a cycle containing cellKey.
|
|
218
553
|
*/
|
|
219
554
|
hasCycle(cellKey2) {
|
|
220
555
|
const key = cellKey2.toUpperCase();
|
|
221
|
-
|
|
222
|
-
const recStack = /* @__PURE__ */ new Set();
|
|
223
|
-
const dfs = (node) => {
|
|
224
|
-
visited.add(node);
|
|
225
|
-
recStack.add(node);
|
|
226
|
-
const deps = this.getDependencies(node);
|
|
227
|
-
for (const neighbor of deps) {
|
|
228
|
-
if (!visited.has(neighbor)) {
|
|
229
|
-
if (dfs(neighbor)) return true;
|
|
230
|
-
} else if (recStack.has(neighbor)) {
|
|
231
|
-
return true;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
recStack.delete(node);
|
|
235
|
-
return false;
|
|
236
|
-
};
|
|
237
|
-
return dfs(key);
|
|
556
|
+
return this.getEvaluationPlan([key], { includeDependents: true }).cyclic.has(key);
|
|
238
557
|
}
|
|
239
558
|
/**
|
|
240
559
|
* Returns cells affected by updating cellKey in topological order.
|
|
241
560
|
*/
|
|
242
561
|
getEvaluationOrder(startCellKey) {
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
const recStack = /* @__PURE__ */ new Set();
|
|
246
|
-
const order = [];
|
|
247
|
-
let cycleDetected = false;
|
|
248
|
-
const affected = /* @__PURE__ */ new Set();
|
|
249
|
-
const collectAffected = (node) => {
|
|
250
|
-
affected.add(node);
|
|
251
|
-
const directDependents = this.dependents.get(node) || /* @__PURE__ */ new Set();
|
|
252
|
-
for (const dep of directDependents) {
|
|
253
|
-
if (!affected.has(dep)) {
|
|
254
|
-
collectAffected(dep);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
collectAffected(start);
|
|
259
|
-
const visit = (node) => {
|
|
260
|
-
if (recStack.has(node)) {
|
|
261
|
-
cycleDetected = true;
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
if (visited.has(node)) return;
|
|
265
|
-
recStack.add(node);
|
|
266
|
-
visited.add(node);
|
|
267
|
-
const deps = this.dependencies.get(node) || /* @__PURE__ */ new Set();
|
|
268
|
-
for (const neighbor of deps) {
|
|
269
|
-
if (affected.has(neighbor)) {
|
|
270
|
-
visit(neighbor);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
recStack.delete(node);
|
|
274
|
-
order.push(node);
|
|
275
|
-
};
|
|
276
|
-
for (const node of affected) {
|
|
277
|
-
if (!visited.has(node)) {
|
|
278
|
-
visit(node);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
return { order, hasCycle: cycleDetected };
|
|
562
|
+
const plan = this.getEvaluationPlan([startCellKey], { includeDependents: true });
|
|
563
|
+
return { order: plan.order, hasCycle: plan.cyclic.size > 0 };
|
|
282
564
|
}
|
|
283
565
|
/**
|
|
284
566
|
* Plans a recalculation over `cellKeys` (and, with `includeDependents`, every
|
|
285
|
-
* cell that transitively depends on them
|
|
567
|
+
* cell that transitively depends on them; with `includeVolatile`, every
|
|
568
|
+
* volatile formula and its dependents too). Returns the keys in evaluation
|
|
286
569
|
* order (dependencies first) and the set of keys that are part of a cycle,
|
|
287
570
|
* which must be marked `#CYCLE!` instead of evaluated. Every member of a
|
|
288
571
|
* cycle is reported, not just the cell that closed it.
|
|
572
|
+
*
|
|
573
|
+
* Only the planned cells are ordered: a range a formula reads becomes one
|
|
574
|
+
* node whose edges are the planned cells inside it (found by binary
|
|
575
|
+
* search), so a range shared by many formulas is resolved once.
|
|
289
576
|
*/
|
|
290
577
|
getEvaluationPlan(cellKeys, options = {}) {
|
|
291
578
|
const nodes = /* @__PURE__ */ new Set();
|
|
292
579
|
const queue = [];
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
queue.push(upper);
|
|
580
|
+
const visit = (key) => {
|
|
581
|
+
if (!nodes.has(key)) {
|
|
582
|
+
nodes.add(key);
|
|
583
|
+
queue.push(key);
|
|
298
584
|
}
|
|
299
|
-
}
|
|
585
|
+
};
|
|
586
|
+
for (const key of cellKeys) visit(key.toUpperCase());
|
|
587
|
+
if (options.includeVolatile) for (const key of this.volatile) visit(key);
|
|
300
588
|
if (options.includeDependents) {
|
|
301
|
-
while (queue.length > 0)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
589
|
+
while (queue.length > 0) this.collectDependents(queue.pop(), visit);
|
|
590
|
+
}
|
|
591
|
+
let positions = null;
|
|
592
|
+
const planPositions = () => {
|
|
593
|
+
if (positions) return positions;
|
|
594
|
+
positions = /* @__PURE__ */ new Map();
|
|
595
|
+
for (const key of nodes) {
|
|
596
|
+
const parsed = this.parse(key);
|
|
597
|
+
if (parsed.kind !== "cell") continue;
|
|
598
|
+
let sheet = positions.get(parsed.sheet);
|
|
599
|
+
if (!sheet) positions.set(parsed.sheet, sheet = /* @__PURE__ */ new Map());
|
|
600
|
+
let cells = sheet.get(parsed.col);
|
|
601
|
+
if (!cells) sheet.set(parsed.col, cells = []);
|
|
602
|
+
cells.push([parsed.row, key]);
|
|
603
|
+
}
|
|
604
|
+
for (const sheet of positions.values()) for (const cells of sheet.values()) cells.sort((a, b) => a[0] - b[0]);
|
|
605
|
+
return positions;
|
|
606
|
+
};
|
|
607
|
+
const rangeMembers = /* @__PURE__ */ new Map();
|
|
608
|
+
const rangeNode = (range) => {
|
|
609
|
+
const id = `${VIRTUAL_PREFIX}${range.sheet}!${range.c0}:${range.c1}:${range.r0}:${range.r1}`;
|
|
610
|
+
let members = rangeMembers.get(id);
|
|
611
|
+
if (!members) {
|
|
612
|
+
const found = [];
|
|
613
|
+
const sheet = planPositions().get(range.sheet);
|
|
614
|
+
const scan = (cells) => {
|
|
615
|
+
let lo = 0;
|
|
616
|
+
let hi = cells.length;
|
|
617
|
+
while (lo < hi) {
|
|
618
|
+
const mid = lo + hi >>> 1;
|
|
619
|
+
if (cells[mid][0] < range.r0) lo = mid + 1;
|
|
620
|
+
else hi = mid;
|
|
621
|
+
}
|
|
622
|
+
for (let i = lo; i < cells.length && cells[i][0] <= range.r1; i++) found.push(cells[i][1]);
|
|
623
|
+
};
|
|
624
|
+
if (sheet) {
|
|
625
|
+
if (range.c1 - range.c0 + 1 <= sheet.size) {
|
|
626
|
+
for (let c = range.c0; c <= range.c1; c++) {
|
|
627
|
+
const cells = sheet.get(c);
|
|
628
|
+
if (cells) scan(cells);
|
|
629
|
+
}
|
|
630
|
+
} else {
|
|
631
|
+
for (const [c, cells] of sheet) if (c >= range.c0 && c <= range.c1) scan(cells);
|
|
312
632
|
}
|
|
313
633
|
}
|
|
634
|
+
rangeMembers.set(id, members = found);
|
|
314
635
|
}
|
|
315
|
-
|
|
636
|
+
return members.length > 0 ? id : null;
|
|
637
|
+
};
|
|
316
638
|
const neighbors = (node) => {
|
|
639
|
+
if (node.startsWith(VIRTUAL_PREFIX)) return rangeMembers.get(node) ?? [];
|
|
317
640
|
const result = [];
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
} else if (nodes.has(dep)) {
|
|
325
|
-
result.push(dep);
|
|
641
|
+
const formula = this.nodes.get(node);
|
|
642
|
+
if (formula) {
|
|
643
|
+
for (const dep of formula.cells) if (nodes.has(dep)) result.push(dep);
|
|
644
|
+
for (const range of formula.ranges) {
|
|
645
|
+
const id = rangeNode(range);
|
|
646
|
+
if (id) result.push(id);
|
|
326
647
|
}
|
|
327
648
|
}
|
|
649
|
+
const spill = this.spillDependencies.get(node);
|
|
650
|
+
if (spill) {
|
|
651
|
+
const blockers = this.blockingEdges.get(node);
|
|
652
|
+
for (const dep of spill) if (nodes.has(dep) && !blockers?.has(dep)) result.push(dep);
|
|
653
|
+
}
|
|
328
654
|
return result;
|
|
329
655
|
};
|
|
330
656
|
const order = [];
|
|
@@ -372,6 +698,7 @@ var DependencyGraph = class {
|
|
|
372
698
|
} while (member !== frame.node);
|
|
373
699
|
const isCycle = component.length > 1 || frame.edges.includes(frame.node);
|
|
374
700
|
for (const key of component) {
|
|
701
|
+
if (key.startsWith(VIRTUAL_PREFIX)) continue;
|
|
375
702
|
order.push(key);
|
|
376
703
|
if (isCycle) cyclic.add(key);
|
|
377
704
|
}
|
|
@@ -381,11 +708,6 @@ var DependencyGraph = class {
|
|
|
381
708
|
return { order, cyclic };
|
|
382
709
|
}
|
|
383
710
|
};
|
|
384
|
-
function unionOf(a, b) {
|
|
385
|
-
if (!b || b.size === 0) return Array.from(a ?? []);
|
|
386
|
-
if (!a || a.size === 0) return Array.from(b);
|
|
387
|
-
return Array.from(/* @__PURE__ */ new Set([...a, ...b]));
|
|
388
|
-
}
|
|
389
711
|
|
|
390
712
|
// src/spreadsheet/formula/references.ts
|
|
391
713
|
var CELL_PART = /^(\$?)([A-Za-z]+)(\$?)(\d+)$/;
|
|
@@ -2505,8 +2827,35 @@ var createRuntime = (doc) => ({
|
|
|
2505
2827
|
});
|
|
2506
2828
|
var sameName3 = (a, b) => a.toUpperCase() === b.toUpperCase();
|
|
2507
2829
|
var DEFAULT_MAX_HISTORY_BYTES = 64 * 1024 * 1024;
|
|
2830
|
+
var AST_CACHE_SIZE = 1e5;
|
|
2831
|
+
var RANGE_CACHE_MIN_CELLS = 256;
|
|
2508
2832
|
var MAX_SPILL_PASSES = 25;
|
|
2509
2833
|
var spillCellId = (rowId, colId) => `${rowId}\0${colId}`;
|
|
2834
|
+
var PositionCache = class {
|
|
2835
|
+
map = /* @__PURE__ */ new Map();
|
|
2836
|
+
scanned = 0;
|
|
2837
|
+
find(list, id) {
|
|
2838
|
+
const pos = this.map.get(id);
|
|
2839
|
+
if (pos !== void 0 && list[pos]?.id === id) return pos;
|
|
2840
|
+
if (pos === void 0 && this.scanned <= list.length) {
|
|
2841
|
+
for (let i = this.scanned; i < list.length; i++) this.map.set(list[i].id, i);
|
|
2842
|
+
this.scanned = list.length;
|
|
2843
|
+
const found2 = this.map.get(id);
|
|
2844
|
+
if (found2 !== void 0 && list[found2]?.id === id) return found2;
|
|
2845
|
+
}
|
|
2846
|
+
this.map.clear();
|
|
2847
|
+
list.forEach((item, i) => this.map.set(item.id, i));
|
|
2848
|
+
this.scanned = list.length;
|
|
2849
|
+
const found = this.map.get(id);
|
|
2850
|
+
return found !== void 0 ? found : -1;
|
|
2851
|
+
}
|
|
2852
|
+
};
|
|
2853
|
+
var positionCaches = /* @__PURE__ */ new WeakMap();
|
|
2854
|
+
function positionOf(list, id) {
|
|
2855
|
+
let cache = positionCaches.get(list);
|
|
2856
|
+
if (!cache) positionCaches.set(list, cache = new PositionCache());
|
|
2857
|
+
return cache.find(list, id);
|
|
2858
|
+
}
|
|
2510
2859
|
var SpreadsheetController = class {
|
|
2511
2860
|
activeCell = null;
|
|
2512
2861
|
selectedRange = null;
|
|
@@ -2546,6 +2895,10 @@ var SpreadsheetController = class {
|
|
|
2546
2895
|
// runtimes, not in the documents, and every recalculation rebuilds them.
|
|
2547
2896
|
// Cells (sheet-qualified keys) whose spill ownership changed during the current pass.
|
|
2548
2897
|
spillChanges = null;
|
|
2898
|
+
recalcStats = { evaluated: 0, planned: 0, passes: 0 };
|
|
2899
|
+
// Values of large ranges read while running an evaluation plan (see runEvaluationPlan).
|
|
2900
|
+
astCache = /* @__PURE__ */ new Map();
|
|
2901
|
+
rangeCache = null;
|
|
2549
2902
|
/** The active sheet's document (the sheet the editor shows). */
|
|
2550
2903
|
get document() {
|
|
2551
2904
|
return this.ctx.doc;
|
|
@@ -2617,7 +2970,7 @@ var SpreadsheetController = class {
|
|
|
2617
2970
|
}
|
|
2618
2971
|
this.evaluator = new FormulaEvaluator({
|
|
2619
2972
|
resolveCell: (colStr, rowNum, sheet) => this.onSheet(sheet, () => this.resolveCellByCoord(colStr, rowNum)),
|
|
2620
|
-
resolveRange: (sCol, sRow, eCol, eRow, sheet) => this.onSheet(sheet, () => this.resolveRangeByCoords(sCol, sRow, eCol, eRow)),
|
|
2973
|
+
resolveRange: (sCol, sRow, eCol, eRow, sheet, options2) => this.onSheet(sheet, () => this.resolveRangeByCoords(sCol, sRow, eCol, eRow, options2)),
|
|
2621
2974
|
resolveProperty: (prop) => this.resolveCurrentRowProperty(prop),
|
|
2622
2975
|
resolveSpill: (colStr, rowNum, sheet) => this.onSheet(sheet, () => this.resolveSpillByCoord(colStr, rowNum)),
|
|
2623
2976
|
resolveSheetSpan: (first3, last) => this.resolveSheetSpan(first3, last),
|
|
@@ -2877,6 +3230,8 @@ var SpreadsheetController = class {
|
|
|
2877
3230
|
});
|
|
2878
3231
|
}
|
|
2879
3232
|
getColumn(colIdOrKey) {
|
|
3233
|
+
const byId = this.findColumn(colIdOrKey);
|
|
3234
|
+
if (byId) return byId;
|
|
2880
3235
|
return this.document.columns.find(
|
|
2881
3236
|
(c) => c.id === colIdOrKey || c.key.toUpperCase() === colIdOrKey.toUpperCase()
|
|
2882
3237
|
);
|
|
@@ -2885,7 +3240,24 @@ var SpreadsheetController = class {
|
|
|
2885
3240
|
if (typeof rowIdOrIndex === "number") {
|
|
2886
3241
|
return this.document.rows[rowIdOrIndex];
|
|
2887
3242
|
}
|
|
2888
|
-
return this.
|
|
3243
|
+
return this.findRow(rowIdOrIndex);
|
|
3244
|
+
}
|
|
3245
|
+
/** The row with this id on the current sheet (constant time, see `PositionCache`). */
|
|
3246
|
+
findRow(rowId) {
|
|
3247
|
+
const pos = positionOf(this.document.rows, rowId);
|
|
3248
|
+
return pos < 0 ? void 0 : this.document.rows[pos];
|
|
3249
|
+
}
|
|
3250
|
+
/** The column with this id on the current sheet. */
|
|
3251
|
+
findColumn(colId) {
|
|
3252
|
+
const pos = positionOf(this.document.columns, colId);
|
|
3253
|
+
return pos < 0 ? void 0 : this.document.columns[pos];
|
|
3254
|
+
}
|
|
3255
|
+
/** The column whose key is `key` (`A`, `B`, …): keys follow column positions. */
|
|
3256
|
+
columnByKey(key) {
|
|
3257
|
+
const upper = key.toUpperCase();
|
|
3258
|
+
const col = this.document.columns[colNameToIndex(upper)];
|
|
3259
|
+
if (col && col.key.toUpperCase() === upper) return col;
|
|
3260
|
+
return this.document.columns.find((c) => c.key.toUpperCase() === upper);
|
|
2889
3261
|
}
|
|
2890
3262
|
getCell(rowId, colId) {
|
|
2891
3263
|
const existing = this.document.cells[rowId]?.[colId];
|
|
@@ -2895,8 +3267,21 @@ var SpreadsheetController = class {
|
|
|
2895
3267
|
if (spilled) return spilled;
|
|
2896
3268
|
}
|
|
2897
3269
|
if (existing && !existing.explicitNumberFormat) return existing;
|
|
2898
|
-
|
|
2899
|
-
|
|
3270
|
+
return this.finishCell(existing, this.findRow(rowId), this.findColumn(colId));
|
|
3271
|
+
}
|
|
3272
|
+
/** `getCell` for a row and column already looked up. */
|
|
3273
|
+
cellOf(row, col) {
|
|
3274
|
+
const existing = this.document.cells[row.id]?.[col.id];
|
|
3275
|
+
if (existing && existing.raw !== "") return existing;
|
|
3276
|
+
if (this.spillCells.size > 0) {
|
|
3277
|
+
const spilled = this.spillCells.get(spillCellId(row.id, col.id));
|
|
3278
|
+
if (spilled) return spilled;
|
|
3279
|
+
}
|
|
3280
|
+
if (existing && !existing.explicitNumberFormat) return existing;
|
|
3281
|
+
return this.finishCell(existing, row, col);
|
|
3282
|
+
}
|
|
3283
|
+
/** A cell with no value of its own: derived (bound, column formula, blank), keeping an explicit number format. */
|
|
3284
|
+
finishCell(existing, row, col) {
|
|
2900
3285
|
const derived = this.getDerivedCell(row, col);
|
|
2901
3286
|
if (!existing) return derived;
|
|
2902
3287
|
if (derived.raw === "") return existing;
|
|
@@ -2928,12 +3313,12 @@ var SpreadsheetController = class {
|
|
|
2928
3313
|
return { raw: "", value: "", format: col?.format, ...this.fixedNumberFormatFields(col, void 0) };
|
|
2929
3314
|
}
|
|
2930
3315
|
setCellValue(rowId, colId, raw) {
|
|
2931
|
-
this.
|
|
3316
|
+
this.recordCellSnapshot(rowId, colId);
|
|
2932
3317
|
if (!this.document.cells[rowId]) {
|
|
2933
3318
|
this.document.cells[rowId] = {};
|
|
2934
3319
|
}
|
|
2935
|
-
const col = this.
|
|
2936
|
-
const row = this.
|
|
3320
|
+
const col = this.findColumn(colId);
|
|
3321
|
+
const row = this.findRow(rowId);
|
|
2937
3322
|
const isFormula = raw.startsWith("=");
|
|
2938
3323
|
const existing = this.document.cells[rowId][colId];
|
|
2939
3324
|
let cell;
|
|
@@ -2948,6 +3333,7 @@ var SpreadsheetController = class {
|
|
|
2948
3333
|
}
|
|
2949
3334
|
const cellKey2 = this.getCellKey(rowId, colId);
|
|
2950
3335
|
this.registerFormula(cellKey2, isFormula ? raw : null);
|
|
3336
|
+
this.dropStaleSpillEdges(cellKey2, raw);
|
|
2951
3337
|
this.document.cells[rowId][colId] = cell;
|
|
2952
3338
|
this.document.updatedAt = Date.now();
|
|
2953
3339
|
if (this.batching) {
|
|
@@ -2966,8 +3352,8 @@ var SpreadsheetController = class {
|
|
|
2966
3352
|
this.onCellCommit?.(rowId, colId, raw, evaluatedVal);
|
|
2967
3353
|
}
|
|
2968
3354
|
getCellKey(rowId, colId) {
|
|
2969
|
-
const row = this.
|
|
2970
|
-
const col = this.
|
|
3355
|
+
const row = this.findRow(rowId);
|
|
3356
|
+
const col = this.findColumn(colId);
|
|
2971
3357
|
if (!row || !col) return `${colId}_${rowId}`;
|
|
2972
3358
|
return `${col.key}${row.index + 1}`.toUpperCase();
|
|
2973
3359
|
}
|
|
@@ -2990,22 +3376,57 @@ var SpreadsheetController = class {
|
|
|
2990
3376
|
const key = this.qualify(cellKey2);
|
|
2991
3377
|
if (!formula || !formula.startsWith("=")) {
|
|
2992
3378
|
this.depGraph.setCellDependencies(key, []);
|
|
3379
|
+
this.depGraph.setVolatile(key, false);
|
|
2993
3380
|
return;
|
|
2994
3381
|
}
|
|
2995
3382
|
let ast;
|
|
2996
3383
|
try {
|
|
2997
|
-
ast =
|
|
3384
|
+
ast = this.parseCached(formula);
|
|
2998
3385
|
} catch {
|
|
2999
3386
|
this.depGraph.setCellDependencies(key, []);
|
|
3387
|
+
this.depGraph.setVolatile(key, false);
|
|
3000
3388
|
return;
|
|
3001
3389
|
}
|
|
3002
3390
|
const deps = /* @__PURE__ */ new Set();
|
|
3003
|
-
|
|
3391
|
+
const volatile = { found: false };
|
|
3392
|
+
this.collectDependencies(ast, this.ctx, deps, /* @__PURE__ */ new Set(), volatile);
|
|
3004
3393
|
this.depGraph.setCellDependencies(key, deps);
|
|
3394
|
+
this.depGraph.setVolatile(key, volatile.found);
|
|
3395
|
+
}
|
|
3396
|
+
/**
|
|
3397
|
+
* Spill edges made stale by new input in `cellKey`: the edges of the array
|
|
3398
|
+
* it anchored (its new formula re-spills when evaluated; spill changes
|
|
3399
|
+
* are recalculated in a further pass) and, when it now holds input, its
|
|
3400
|
+
* edge to the anchor whose values it held. Left in place they can close
|
|
3401
|
+
* false cycles with the new formulas before the anchors are evaluated.
|
|
3402
|
+
*/
|
|
3403
|
+
dropStaleSpillEdges(cellKey2, raw) {
|
|
3404
|
+
const key = this.qualify(cellKey2);
|
|
3405
|
+
this.depGraph.setSpillEdges(key, []);
|
|
3406
|
+
if (raw !== "") this.depGraph.detachSpilledCell(key);
|
|
3407
|
+
}
|
|
3408
|
+
/**
|
|
3409
|
+
* The parsed formula, shared by dependency registration and evaluation (so
|
|
3410
|
+
* a load parses each formula once). ASTs are never mutated.
|
|
3411
|
+
*/
|
|
3412
|
+
parseCached(formula) {
|
|
3413
|
+
let ast = this.astCache.get(formula);
|
|
3414
|
+
if (!ast) {
|
|
3415
|
+
ast = parseFormula(formula);
|
|
3416
|
+
if (this.astCache.size >= AST_CACHE_SIZE) this.astCache.clear();
|
|
3417
|
+
this.astCache.set(formula, ast);
|
|
3418
|
+
}
|
|
3419
|
+
return ast;
|
|
3005
3420
|
}
|
|
3006
|
-
/**
|
|
3007
|
-
|
|
3008
|
-
|
|
3421
|
+
/**
|
|
3422
|
+
* Adds the qualified keys `ast` depends on, with unqualified references on
|
|
3423
|
+
* `home`: cells, and ranges kept whole (`SHEETID!A1:A50000`), which the
|
|
3424
|
+
* graph indexes as intervals instead of one key per cell. Sets
|
|
3425
|
+
* `volatile.found` when a volatile function (NOW, RAND, …) is used,
|
|
3426
|
+
* directly or through a defined name.
|
|
3427
|
+
*/
|
|
3428
|
+
collectDependencies(ast, home, out, seenNames, volatile = { found: false }) {
|
|
3429
|
+
for (const dep of extractReferenceDependencies(ast)) {
|
|
3009
3430
|
let sheets;
|
|
3010
3431
|
if (dep.sheet === void 0) {
|
|
3011
3432
|
sheets = [home];
|
|
@@ -3017,11 +3438,19 @@ var SpreadsheetController = class {
|
|
|
3017
3438
|
sheets = Array.isArray(span) ? span.map((name) => this.findRuntimeByName(name)) : [];
|
|
3018
3439
|
}
|
|
3019
3440
|
for (const rt of sheets) {
|
|
3020
|
-
out.add(
|
|
3441
|
+
out.add(
|
|
3442
|
+
dep.kind === "cell" ? this.qualify(`${dep.col}${dep.row}`, rt) : rangeDependencyKey(dep.startCol, dep.startRow, dep.endCol, dep.endRow, rt.doc.id)
|
|
3443
|
+
);
|
|
3021
3444
|
}
|
|
3022
3445
|
}
|
|
3023
3446
|
for (const used of extractNameDependencies(ast)) {
|
|
3024
|
-
if (used.call
|
|
3447
|
+
if (used.call) {
|
|
3448
|
+
const fn = getFormulaFunction(used.name);
|
|
3449
|
+
if (fn) {
|
|
3450
|
+
if (fn.volatile) volatile.found = true;
|
|
3451
|
+
continue;
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3025
3454
|
const lookupSheet = used.sheet === void 0 ? home : this.findRuntimeByName(used.sheet);
|
|
3026
3455
|
if (!lookupSheet) continue;
|
|
3027
3456
|
const def = this.findName(used.name, lookupSheet);
|
|
@@ -3032,7 +3461,7 @@ var SpreadsheetController = class {
|
|
|
3032
3461
|
if (seenNames.has(seenKey)) continue;
|
|
3033
3462
|
seenNames.add(seenKey);
|
|
3034
3463
|
try {
|
|
3035
|
-
this.collectDependencies(parseFormula(def.refersTo), nameHome, out, seenNames);
|
|
3464
|
+
this.collectDependencies(parseFormula(def.refersTo), nameHome, out, seenNames, volatile);
|
|
3036
3465
|
} catch {
|
|
3037
3466
|
}
|
|
3038
3467
|
}
|
|
@@ -3047,18 +3476,49 @@ var SpreadsheetController = class {
|
|
|
3047
3476
|
recalculateKeys(keys, includeDependents) {
|
|
3048
3477
|
let pending = Array.from(keys);
|
|
3049
3478
|
let withDependents = includeDependents;
|
|
3050
|
-
|
|
3479
|
+
let withVolatile = includeDependents;
|
|
3480
|
+
const stats = { evaluated: 0, planned: 0, passes: 0 };
|
|
3481
|
+
this.recalcStats = stats;
|
|
3482
|
+
for (let pass = 0; pass < MAX_SPILL_PASSES && (pending.length > 0 || withVolatile); pass++) {
|
|
3051
3483
|
const changed = /* @__PURE__ */ new Set();
|
|
3052
3484
|
this.spillChanges = changed;
|
|
3053
3485
|
try {
|
|
3054
|
-
this.
|
|
3486
|
+
const plan = this.depGraph.getEvaluationPlan(pending, { includeDependents: withDependents, includeVolatile: withVolatile });
|
|
3487
|
+
stats.planned += plan.order.length;
|
|
3488
|
+
stats.passes++;
|
|
3489
|
+
this.runEvaluationPlan(plan);
|
|
3055
3490
|
} finally {
|
|
3056
3491
|
this.spillChanges = null;
|
|
3057
3492
|
}
|
|
3058
3493
|
pending = Array.from(changed);
|
|
3059
3494
|
withDependents = true;
|
|
3495
|
+
withVolatile = false;
|
|
3060
3496
|
}
|
|
3061
3497
|
}
|
|
3498
|
+
/**
|
|
3499
|
+
* Recalculates cells of the current sheet whose stored input changed (their
|
|
3500
|
+
* formulas already registered), with their dependents, in one plan.
|
|
3501
|
+
*/
|
|
3502
|
+
recalculateWrittenCells(cells) {
|
|
3503
|
+
const keys = /* @__PURE__ */ new Set();
|
|
3504
|
+
for (const [rowId, colId] of cells) {
|
|
3505
|
+
const cellKey2 = this.getCellKey(rowId, colId);
|
|
3506
|
+
keys.add(this.qualify(cellKey2));
|
|
3507
|
+
const anchor = this.spilledBy.get(cellKey2);
|
|
3508
|
+
if (anchor) keys.add(this.qualify(anchor));
|
|
3509
|
+
}
|
|
3510
|
+
this.recalculateKeys(keys, true);
|
|
3511
|
+
}
|
|
3512
|
+
/**
|
|
3513
|
+
* Work done by the last recalculation (an edit, paste, import, load or
|
|
3514
|
+
* full recalculation): formulas evaluated, cells planned (formulas, their
|
|
3515
|
+
* dependents and spilled cells) and passes (more than one when spill
|
|
3516
|
+
* ranges changed). Deterministic, unlike timings: use it to check that an
|
|
3517
|
+
* edit only recalculates what depends on it.
|
|
3518
|
+
*/
|
|
3519
|
+
getRecalculationStats() {
|
|
3520
|
+
return { ...this.recalcStats };
|
|
3521
|
+
}
|
|
3062
3522
|
/**
|
|
3063
3523
|
* Rebuilds the dependency graph from scratch and recalculates every formula
|
|
3064
3524
|
* in dependency order.
|
|
@@ -3072,10 +3532,14 @@ var SpreadsheetController = class {
|
|
|
3072
3532
|
this.spills.clear();
|
|
3073
3533
|
this.spilledBy.clear();
|
|
3074
3534
|
this.spillCells.clear();
|
|
3535
|
+
const columns = this.document.columns;
|
|
3536
|
+
const deriving = columns.map((col) => col.type === "bound" || col.type === "formula");
|
|
3075
3537
|
for (const row of this.document.rows) {
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3538
|
+
const stored = this.document.cells[row.id];
|
|
3539
|
+
for (let c = 0; c < columns.length; c++) {
|
|
3540
|
+
const col = columns[c];
|
|
3541
|
+
const cell = stored?.[col.id] ?? (deriving[c] ? this.getDerivedCell(row, col) : null);
|
|
3542
|
+
if (cell && typeof cell.raw === "string" && cell.raw.startsWith("=")) {
|
|
3079
3543
|
const cellKey2 = `${col.key}${row.index + 1}`.toUpperCase();
|
|
3080
3544
|
this.registerFormula(cellKey2, cell.raw);
|
|
3081
3545
|
formulaKeys.push(this.qualify(cellKey2));
|
|
@@ -3087,25 +3551,45 @@ var SpreadsheetController = class {
|
|
|
3087
3551
|
this.recalculateKeys(formulaKeys, false);
|
|
3088
3552
|
}
|
|
3089
3553
|
runEvaluationPlan(plan) {
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
this.
|
|
3098
|
-
|
|
3099
|
-
this.
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3554
|
+
this.rangeCache = /* @__PURE__ */ new Map();
|
|
3555
|
+
try {
|
|
3556
|
+
for (const qualified of plan.order) {
|
|
3557
|
+
const located = this.unqualify(qualified);
|
|
3558
|
+
if (!located) continue;
|
|
3559
|
+
const onActive = located.rt === this.active;
|
|
3560
|
+
this.withSheet(located.rt, () => {
|
|
3561
|
+
const hadSpill = this.spills.has(located.key);
|
|
3562
|
+
const spillChanges = this.spillChanges?.size ?? 0;
|
|
3563
|
+
if (onActive) this.markValueChanged(located.key);
|
|
3564
|
+
if (plan.cyclic.has(qualified)) {
|
|
3565
|
+
this.markCycle(located.key);
|
|
3566
|
+
} else {
|
|
3567
|
+
this.evalSingleCellByKey(located.key);
|
|
3568
|
+
}
|
|
3569
|
+
if (onActive) this.markValueChanged(located.key);
|
|
3570
|
+
const spilled = hadSpill || this.spills.has(located.key) || (this.spillChanges?.size ?? 0) !== spillChanges;
|
|
3571
|
+
this.invalidateRangeCache(located.rt.doc.id, spilled ? null : colNameToIndex(located.key.replace(/\d+$/, "")));
|
|
3572
|
+
});
|
|
3573
|
+
}
|
|
3574
|
+
} finally {
|
|
3575
|
+
this.rangeCache = null;
|
|
3576
|
+
}
|
|
3577
|
+
}
|
|
3578
|
+
/**
|
|
3579
|
+
* Drops cached ranges of `sheetId` that cover column `col` (whose cell was
|
|
3580
|
+
* just evaluated), or all of the sheet's when `col` is null (spilled
|
|
3581
|
+
* values moved or changed).
|
|
3582
|
+
*/
|
|
3583
|
+
invalidateRangeCache(sheetId, col) {
|
|
3584
|
+
if (!this.rangeCache || this.rangeCache.size === 0) return;
|
|
3585
|
+
for (const [key, entry] of this.rangeCache) {
|
|
3586
|
+
if (entry.sheet === sheetId && (col === null || col >= entry.c0 && col <= entry.c1)) this.rangeCache.delete(key);
|
|
3103
3587
|
}
|
|
3104
3588
|
}
|
|
3105
3589
|
locateCellKey(cellKey2) {
|
|
3106
3590
|
const match = cellKey2.match(/^([A-Za-z]+)(\d+)$/);
|
|
3107
3591
|
if (!match) return null;
|
|
3108
|
-
const col = this.
|
|
3592
|
+
const col = this.columnByKey(match[1]);
|
|
3109
3593
|
const row = this.document.rows[parseInt(match[2], 10) - 1];
|
|
3110
3594
|
return col && row ? { row, col } : null;
|
|
3111
3595
|
}
|
|
@@ -3117,10 +3601,8 @@ var SpreadsheetController = class {
|
|
|
3117
3601
|
if (!cell.raw.startsWith("=")) return;
|
|
3118
3602
|
if (!this.document.cells[row.id]) this.document.cells[row.id] = {};
|
|
3119
3603
|
const qualified = this.qualify(cellKey2);
|
|
3120
|
-
const
|
|
3121
|
-
const
|
|
3122
|
-
const spilledKeys = this.depGraph.getSpilledKeys(qualified);
|
|
3123
|
-
const ownSpill = deps.find((dep) => spilledKeys.includes(dep));
|
|
3604
|
+
const selfReference = this.depGraph.dependsOn(qualified, qualified);
|
|
3605
|
+
const ownSpill = selfReference ? void 0 : this.depGraph.getSpilledKeys(qualified).find((key) => this.depGraph.dependsOn(qualified, key));
|
|
3124
3606
|
const errorMessage = selfReference ? "This formula can't reference its own cell." : ownSpill ? `This formula refers to ${ownSpill.slice(ownSpill.lastIndexOf("!") + 1)}, 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.";
|
|
3125
3607
|
this.clearSpill(cellKey2.toUpperCase(), true);
|
|
3126
3608
|
this.document.cells[row.id][col.id] = { ...cell, value: "#CYCLE!", error: "#CYCLE!", errorMessage };
|
|
@@ -3130,7 +3612,7 @@ var SpreadsheetController = class {
|
|
|
3130
3612
|
if (!match) return;
|
|
3131
3613
|
const colName = match[1].toUpperCase();
|
|
3132
3614
|
const rowNum = parseInt(match[2], 10);
|
|
3133
|
-
const col = this.
|
|
3615
|
+
const col = this.columnByKey(colName);
|
|
3134
3616
|
const row = this.document.rows[rowNum - 1];
|
|
3135
3617
|
if (!col || !row) return;
|
|
3136
3618
|
const cell = this.getCell(row.id, col.id);
|
|
@@ -3145,13 +3627,19 @@ var SpreadsheetController = class {
|
|
|
3145
3627
|
try {
|
|
3146
3628
|
this.evaluatingRowIndex = rowNum - 1;
|
|
3147
3629
|
this.evaluatingCell = { col: colName, row: rowNum };
|
|
3148
|
-
|
|
3630
|
+
this.recalcStats.evaluated++;
|
|
3631
|
+
let parsed = cell.raw;
|
|
3632
|
+
try {
|
|
3633
|
+
parsed = this.parseCached(cell.raw);
|
|
3634
|
+
} catch {
|
|
3635
|
+
}
|
|
3636
|
+
const result = this.evaluator.evaluateWithDiagnostics(parsed);
|
|
3149
3637
|
if (!this.document.cells[row.id]) {
|
|
3150
3638
|
this.document.cells[row.id] = {};
|
|
3151
3639
|
}
|
|
3152
3640
|
let { value, error, message } = result;
|
|
3153
3641
|
if (result.array) {
|
|
3154
|
-
const blocked = this.placeSpill(`${colName}${rowNum}`, rowNum - 1, this.document.columns.
|
|
3642
|
+
const blocked = this.placeSpill(`${colName}${rowNum}`, rowNum - 1, positionOf(this.document.columns, col.id), result.array);
|
|
3155
3643
|
if (blocked) {
|
|
3156
3644
|
value = "#SPILL!";
|
|
3157
3645
|
error = "#SPILL!";
|
|
@@ -3215,6 +3703,7 @@ var SpreadsheetController = class {
|
|
|
3215
3703
|
return reason;
|
|
3216
3704
|
}
|
|
3217
3705
|
const previous = this.spills.get(anchor);
|
|
3706
|
+
const linked = new Set(this.depGraph.getSpilledKeys(this.qualify(anchor)));
|
|
3218
3707
|
const cells = [];
|
|
3219
3708
|
for (let c = 0; c < cols; c++) {
|
|
3220
3709
|
const column = this.document.columns[col + c];
|
|
@@ -3238,6 +3727,8 @@ var SpreadsheetController = class {
|
|
|
3238
3727
|
if (this.spilledBy.get(key) !== anchor) {
|
|
3239
3728
|
this.spilledBy.set(key, anchor);
|
|
3240
3729
|
this.spillChanges?.add(this.qualify(key));
|
|
3730
|
+
} else if (!linked.has(this.qualify(key))) {
|
|
3731
|
+
this.spillChanges?.add(this.qualify(key));
|
|
3241
3732
|
}
|
|
3242
3733
|
cells.push({ key, rowId: rowObj.id, colId: column.id });
|
|
3243
3734
|
}
|
|
@@ -3317,36 +3808,65 @@ var SpreadsheetController = class {
|
|
|
3317
3808
|
return false;
|
|
3318
3809
|
}
|
|
3319
3810
|
resolveCellByCoord(colStr, rowNum) {
|
|
3320
|
-
const col = this.
|
|
3811
|
+
const col = this.columnByKey(colStr);
|
|
3321
3812
|
const row = this.document.rows[rowNum - 1];
|
|
3322
3813
|
if (!col || !row) return 0;
|
|
3323
3814
|
const override = this.valueOverride;
|
|
3324
3815
|
if (override && override.row === rowNum - 1 && this.document.columns[override.col] === col) return override.value;
|
|
3325
|
-
return this.
|
|
3816
|
+
return this.cellOf(row, col).value ?? "";
|
|
3326
3817
|
}
|
|
3327
|
-
|
|
3818
|
+
/**
|
|
3819
|
+
* Values of a range, column by column. With `skipBlanks` (functions that
|
|
3820
|
+
* ignore blank cells: SUM, COUNT, AVERAGE, …) blank cells are left out, so
|
|
3821
|
+
* a sparse column costs one lookup per row and no array slot.
|
|
3822
|
+
*/
|
|
3823
|
+
resolveRangeByCoords(sCol, sRow, eCol, eRow, options) {
|
|
3328
3824
|
const startC = colNameToIndex(sCol);
|
|
3329
3825
|
const endC = colNameToIndex(eCol);
|
|
3330
3826
|
const minC = Math.min(startC, endC);
|
|
3331
3827
|
const maxC = Math.max(startC, endC);
|
|
3332
3828
|
const minR = Math.min(sRow, eRow);
|
|
3333
3829
|
const maxR = Math.min(Math.max(sRow, eRow), WHOLE_COLUMN_END_ROW, this.document.rows.length);
|
|
3830
|
+
const skipBlanks = options?.skipBlanks === true;
|
|
3831
|
+
const cacheable = this.rangeCache !== null && !this.valueOverride && (maxC - minC + 1) * (maxR - minR + 1) >= RANGE_CACHE_MIN_CELLS;
|
|
3832
|
+
const cacheKey = cacheable ? `${this.document.id}|${minC}|${maxC}|${minR}|${maxR}|${skipBlanks ? 1 : 0}` : "";
|
|
3833
|
+
if (cacheable) {
|
|
3834
|
+
const hit = this.rangeCache.get(cacheKey);
|
|
3835
|
+
if (hit) return hit.values.slice();
|
|
3836
|
+
}
|
|
3837
|
+
const rows = this.document.rows;
|
|
3838
|
+
const cells = this.document.cells;
|
|
3839
|
+
const spills = this.spillCells.size > 0 ? this.spillCells : null;
|
|
3334
3840
|
const results = [];
|
|
3335
3841
|
for (let c = minC; c <= maxC; c++) {
|
|
3336
|
-
const
|
|
3337
|
-
const col = this.document.columns.find((colDef) => colDef.key.toUpperCase() === colName);
|
|
3842
|
+
const col = this.columnByKey(indexToColName(c));
|
|
3338
3843
|
if (!col) continue;
|
|
3844
|
+
const plain = col.type !== "bound" && col.type !== "formula";
|
|
3845
|
+
const colId = col.id;
|
|
3339
3846
|
const override = this.valueOverride && this.document.columns[this.valueOverride.col] === col ? this.valueOverride : null;
|
|
3340
3847
|
for (let r = minR; r <= maxR; r++) {
|
|
3341
|
-
const row =
|
|
3848
|
+
const row = rows[r - 1];
|
|
3342
3849
|
if (!row) continue;
|
|
3850
|
+
let value;
|
|
3343
3851
|
if (override && override.row === r - 1) {
|
|
3344
|
-
|
|
3345
|
-
|
|
3852
|
+
value = override.value;
|
|
3853
|
+
} else {
|
|
3854
|
+
const stored = cells[row.id]?.[colId];
|
|
3855
|
+
if (stored && stored.raw !== "") value = stored.value;
|
|
3856
|
+
else {
|
|
3857
|
+
const spilled = spills?.get(spillCellId(row.id, colId));
|
|
3858
|
+
if (spilled) value = spilled.value;
|
|
3859
|
+
else if (stored && (plain || !stored.explicitNumberFormat)) value = stored.value;
|
|
3860
|
+
else if (plain) value = "";
|
|
3861
|
+
else value = this.cellOf(row, col).value;
|
|
3862
|
+
}
|
|
3346
3863
|
}
|
|
3347
|
-
|
|
3864
|
+
if (value === void 0 || value === null) value = "";
|
|
3865
|
+
if (skipBlanks && value === "") continue;
|
|
3866
|
+
results.push(value);
|
|
3348
3867
|
}
|
|
3349
3868
|
}
|
|
3869
|
+
if (cacheable) this.rangeCache.set(cacheKey, { sheet: this.document.id, c0: minC, c1: maxC, values: results.slice() });
|
|
3350
3870
|
return results;
|
|
3351
3871
|
}
|
|
3352
3872
|
resolveCurrentRowProperty(prop) {
|
|
@@ -3438,8 +3958,8 @@ var SpreadsheetController = class {
|
|
|
3438
3958
|
*/
|
|
3439
3959
|
commitCellInput(rowId, colId, raw) {
|
|
3440
3960
|
if (this.validationPrompt) return { status: "pending", prompt: this.validationPrompt };
|
|
3441
|
-
const row = this.document.rows
|
|
3442
|
-
const col = this.document.columns
|
|
3961
|
+
const row = positionOf(this.document.rows, rowId);
|
|
3962
|
+
const col = positionOf(this.document.columns, colId);
|
|
3443
3963
|
if (row === -1 || col === -1) return { status: "none" };
|
|
3444
3964
|
const failed = this.checkInput(rowId, colId, row, col, raw, false);
|
|
3445
3965
|
if (failed) return failed;
|
|
@@ -3708,6 +4228,8 @@ var SpreadsheetController = class {
|
|
|
3708
4228
|
*/
|
|
3709
4229
|
runBatch(fn) {
|
|
3710
4230
|
this.recordSnapshot();
|
|
4231
|
+
const rowCount = this.document.rows.length;
|
|
4232
|
+
const colCount = this.document.columns.length;
|
|
3711
4233
|
this.batching = true;
|
|
3712
4234
|
this.pendingCommits = [];
|
|
3713
4235
|
try {
|
|
@@ -3715,8 +4237,14 @@ var SpreadsheetController = class {
|
|
|
3715
4237
|
} finally {
|
|
3716
4238
|
this.batching = false;
|
|
3717
4239
|
}
|
|
4240
|
+
const grew = this.document.rows.length !== rowCount || this.document.columns.length !== colCount;
|
|
3718
4241
|
this.recomputeRowIndices();
|
|
3719
|
-
|
|
4242
|
+
if (grew) {
|
|
4243
|
+
this.recalculateAll();
|
|
4244
|
+
} else {
|
|
4245
|
+
this.cfDirty = "all";
|
|
4246
|
+
this.recalculateWrittenCells(this.pendingCommits.map(([rowId, colId]) => [rowId, colId]));
|
|
4247
|
+
}
|
|
3720
4248
|
this.document.updatedAt = Date.now();
|
|
3721
4249
|
this.notifyChange();
|
|
3722
4250
|
const commits = this.pendingCommits;
|
|
@@ -4742,6 +5270,17 @@ var SpreadsheetController = class {
|
|
|
4742
5270
|
if (this.isApplyingHistory || this.batching) return;
|
|
4743
5271
|
this.pushUndo({ kind: "sheet", sheetId: this.ctx.doc.id, data: JSON.stringify(this.ctx.doc) });
|
|
4744
5272
|
}
|
|
5273
|
+
/** Records the stored state of one cell, for undoing a single-cell edit. */
|
|
5274
|
+
recordCellSnapshot(rowId, colId) {
|
|
5275
|
+
if (this.isApplyingHistory || this.batching) return;
|
|
5276
|
+
this.pushUndo({ kind: "cells", sheetId: this.ctx.doc.id, data: JSON.stringify(this.cellHistory(this.ctx.doc, [[rowId, colId]])) });
|
|
5277
|
+
}
|
|
5278
|
+
cellHistory(doc, cells) {
|
|
5279
|
+
return {
|
|
5280
|
+
cells: cells.map(([rowId, colId]) => [rowId, colId, doc.cells[rowId]?.[colId] ?? null, doc.cells[rowId] !== void 0]),
|
|
5281
|
+
updatedAt: doc.updatedAt
|
|
5282
|
+
};
|
|
5283
|
+
}
|
|
4745
5284
|
recordWorkbookSnapshot() {
|
|
4746
5285
|
if (this.isApplyingHistory || this.batching) return;
|
|
4747
5286
|
this.pushUndo({ kind: "workbook", data: JSON.stringify(this.workbookSnapshot()) });
|
|
@@ -4783,6 +5322,37 @@ var SpreadsheetController = class {
|
|
|
4783
5322
|
if (!entry) return;
|
|
4784
5323
|
this.isApplyingHistory = true;
|
|
4785
5324
|
try {
|
|
5325
|
+
if (entry.kind === "cells") {
|
|
5326
|
+
const rt = this.findRuntimeById(entry.sheetId);
|
|
5327
|
+
if (!rt) return;
|
|
5328
|
+
const saved = JSON.parse(entry.data);
|
|
5329
|
+
const positions = saved.cells.map(([rowId, colId]) => [rowId, colId]);
|
|
5330
|
+
to.push({ kind: "cells", sheetId: rt.doc.id, data: JSON.stringify(this.cellHistory(rt.doc, positions)) });
|
|
5331
|
+
for (const [rowId, colId, cell, rowExisted] of saved.cells) {
|
|
5332
|
+
if (cell) {
|
|
5333
|
+
(rt.doc.cells[rowId] ??= {})[colId] = cell;
|
|
5334
|
+
} else if (rt.doc.cells[rowId]) {
|
|
5335
|
+
delete rt.doc.cells[rowId][colId];
|
|
5336
|
+
if (!rowExisted && Object.keys(rt.doc.cells[rowId]).length === 0) delete rt.doc.cells[rowId];
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5339
|
+
if (saved.updatedAt !== void 0) rt.doc.updatedAt = saved.updatedAt;
|
|
5340
|
+
if (rt !== this.active) {
|
|
5341
|
+
this.ctx = this.active;
|
|
5342
|
+
this.activateRuntime(rt);
|
|
5343
|
+
}
|
|
5344
|
+
this.withSheet(rt, () => {
|
|
5345
|
+
for (const [rowId, colId] of positions) {
|
|
5346
|
+
const raw = rt.doc.cells[rowId]?.[colId]?.raw;
|
|
5347
|
+
const cellKey2 = this.getCellKey(rowId, colId);
|
|
5348
|
+
this.registerFormula(cellKey2, raw?.startsWith("=") ? raw : null);
|
|
5349
|
+
this.dropStaleSpillEdges(cellKey2, raw ?? "");
|
|
5350
|
+
}
|
|
5351
|
+
this.recalculateWrittenCells(positions);
|
|
5352
|
+
});
|
|
5353
|
+
this.notifyChange();
|
|
5354
|
+
return;
|
|
5355
|
+
}
|
|
4786
5356
|
if (entry.kind === "sheet") {
|
|
4787
5357
|
const rt = this.findRuntimeById(entry.sheetId);
|
|
4788
5358
|
if (!rt) return;
|
|
@@ -4999,7 +5569,7 @@ var SpreadsheetController = class {
|
|
|
4999
5569
|
const changed = [];
|
|
5000
5570
|
for (const key of this.cfDirty) {
|
|
5001
5571
|
const located = this.locateCellKey(key);
|
|
5002
|
-
if (located) changed.push({ row: located.row.index, col: this.document.columns
|
|
5572
|
+
if (located) changed.push({ row: located.row.index, col: positionOf(this.document.columns, located.col.id) });
|
|
5003
5573
|
}
|
|
5004
5574
|
this.cfEngine.setSheet(sheet);
|
|
5005
5575
|
this.cfEngine.update(rules, changed);
|
|
@@ -5606,10 +6176,180 @@ function getNameManagerRows(controller) {
|
|
|
5606
6176
|
}).sort((a, b) => a.name.localeCompare(b.name) || a.scopeLabel.localeCompare(b.scopeLabel));
|
|
5607
6177
|
}
|
|
5608
6178
|
|
|
6179
|
+
// src/spreadsheet/viewport.ts
|
|
6180
|
+
var SPREADSHEET_COLUMN_HEADER_HEIGHT = 28;
|
|
6181
|
+
var SPREADSHEET_ROW_HEADER_WIDTH = 48;
|
|
6182
|
+
var SPREADSHEET_GROUP_ROW_HEIGHT = 28;
|
|
6183
|
+
var SPREADSHEET_VIRTUAL_ROW_THRESHOLD = 200;
|
|
6184
|
+
var SPREADSHEET_VIRTUAL_COLUMN_THRESHOLD = 60;
|
|
6185
|
+
var SPREADSHEET_DEFAULT_VIEWPORT = { width: 1200, height: 600 };
|
|
6186
|
+
function measureSpreadsheetView(view) {
|
|
6187
|
+
const rowTops = new Float64Array(view.rows.length + 1);
|
|
6188
|
+
const rowPosition = /* @__PURE__ */ new Map();
|
|
6189
|
+
let frozenRows = 0;
|
|
6190
|
+
let top = 0;
|
|
6191
|
+
view.rows.forEach((viewRow, i) => {
|
|
6192
|
+
rowTops[i] = top;
|
|
6193
|
+
if (viewRow.kind === "row") {
|
|
6194
|
+
rowPosition.set(viewRow.index, i);
|
|
6195
|
+
if (viewRow.frozen && frozenRows === i) frozenRows = i + 1;
|
|
6196
|
+
top += viewRow.row.height || DEFAULT_ROW_HEIGHT;
|
|
6197
|
+
} else {
|
|
6198
|
+
top += SPREADSHEET_GROUP_ROW_HEIGHT;
|
|
6199
|
+
}
|
|
6200
|
+
});
|
|
6201
|
+
rowTops[view.rows.length] = top;
|
|
6202
|
+
const columnLefts = new Float64Array(view.columns.length + 1);
|
|
6203
|
+
const columnPosition = /* @__PURE__ */ new Map();
|
|
6204
|
+
let frozenColumns = 0;
|
|
6205
|
+
let left = 0;
|
|
6206
|
+
view.columns.forEach((viewCol, i) => {
|
|
6207
|
+
columnLefts[i] = left;
|
|
6208
|
+
columnPosition.set(viewCol.index, i);
|
|
6209
|
+
if (viewCol.frozen && frozenColumns === i) frozenColumns = i + 1;
|
|
6210
|
+
left += viewCol.column.width || DEFAULT_COLUMN_WIDTH;
|
|
6211
|
+
});
|
|
6212
|
+
columnLefts[view.columns.length] = left;
|
|
6213
|
+
return { rowTops, columnLefts, frozenRows, frozenColumns, rowPosition, columnPosition };
|
|
6214
|
+
}
|
|
6215
|
+
function shouldVirtualizeSpreadsheet(view, mode = "auto") {
|
|
6216
|
+
if (mode !== "auto") return mode;
|
|
6217
|
+
return view.rows.length > SPREADSHEET_VIRTUAL_ROW_THRESHOLD || view.columns.length > SPREADSHEET_VIRTUAL_COLUMN_THRESHOLD;
|
|
6218
|
+
}
|
|
6219
|
+
function fullSpreadsheetViewWindow(metrics) {
|
|
6220
|
+
return {
|
|
6221
|
+
virtual: false,
|
|
6222
|
+
frozenRows: metrics.frozenRows,
|
|
6223
|
+
rowStart: metrics.frozenRows,
|
|
6224
|
+
rowEnd: metrics.rowTops.length - 1,
|
|
6225
|
+
topSpace: 0,
|
|
6226
|
+
bottomSpace: 0,
|
|
6227
|
+
frozenColumns: metrics.frozenColumns,
|
|
6228
|
+
colStart: metrics.frozenColumns,
|
|
6229
|
+
colEnd: metrics.columnLefts.length - 1,
|
|
6230
|
+
leftSpace: 0,
|
|
6231
|
+
rightSpace: 0
|
|
6232
|
+
};
|
|
6233
|
+
}
|
|
6234
|
+
function entryAt(edges, value, lo, hi) {
|
|
6235
|
+
while (lo < hi) {
|
|
6236
|
+
const mid = lo + hi >>> 1;
|
|
6237
|
+
if (edges[mid + 1] <= value) lo = mid + 1;
|
|
6238
|
+
else hi = mid;
|
|
6239
|
+
}
|
|
6240
|
+
return lo;
|
|
6241
|
+
}
|
|
6242
|
+
function visibleSlice(edges, frozen, from, to, overscan) {
|
|
6243
|
+
const count = edges.length - 1;
|
|
6244
|
+
if (count <= frozen) return [frozen, frozen];
|
|
6245
|
+
const first2 = entryAt(edges, from, frozen, count);
|
|
6246
|
+
const last = entryAt(edges, Math.max(from, to - 1), first2, count);
|
|
6247
|
+
return [Math.max(frozen, first2 - overscan), Math.min(count, last + 1 + overscan)];
|
|
6248
|
+
}
|
|
6249
|
+
function getSpreadsheetViewWindow(metrics, viewport, options = {}) {
|
|
6250
|
+
const overscanRows = options.overscanRows ?? 8;
|
|
6251
|
+
const overscanColumns = options.overscanColumns ?? 2;
|
|
6252
|
+
const height = viewport.height > 0 ? viewport.height : SPREADSHEET_DEFAULT_VIEWPORT.height;
|
|
6253
|
+
const width = viewport.width > 0 ? viewport.width : SPREADSHEET_DEFAULT_VIEWPORT.width;
|
|
6254
|
+
const { rowTops, columnLefts, frozenRows, frozenColumns } = metrics;
|
|
6255
|
+
const rowCount = rowTops.length - 1;
|
|
6256
|
+
const colCount = columnLefts.length - 1;
|
|
6257
|
+
const top = Math.max(0, viewport.scrollTop);
|
|
6258
|
+
let [rowStart, rowEnd] = visibleSlice(rowTops, frozenRows, top, top + Math.max(0, height - SPREADSHEET_COLUMN_HEADER_HEIGHT), overscanRows);
|
|
6259
|
+
const left = Math.max(0, viewport.scrollLeft);
|
|
6260
|
+
let [colStart, colEnd] = visibleSlice(columnLefts, frozenColumns, left, left + Math.max(0, width - SPREADSHEET_ROW_HEADER_WIDTH), overscanColumns);
|
|
6261
|
+
const merges = options.merges ?? [];
|
|
6262
|
+
for (let changed = merges.length > 0, guard = 0; changed && guard < 8; guard++) {
|
|
6263
|
+
changed = false;
|
|
6264
|
+
for (const raw of merges) {
|
|
6265
|
+
const merge = normalizeRange(raw);
|
|
6266
|
+
const rows = displayedSpan(metrics.rowPosition, merge.startRow, merge.endRow);
|
|
6267
|
+
const cols = displayedSpan(metrics.columnPosition, merge.startCol, merge.endCol);
|
|
6268
|
+
if (!rows || !cols) continue;
|
|
6269
|
+
const rowsMeet = rows[1] >= rowStart && rows[0] < rowEnd;
|
|
6270
|
+
const colsMeet = cols[1] >= colStart && cols[0] < colEnd;
|
|
6271
|
+
if (!rowsMeet && !(rows[1] < frozenRows)) continue;
|
|
6272
|
+
if (!colsMeet && !(cols[1] < frozenColumns)) continue;
|
|
6273
|
+
if (rowsMeet) {
|
|
6274
|
+
const s = Math.max(frozenRows, rows[0]);
|
|
6275
|
+
const e = rows[1] + 1;
|
|
6276
|
+
if (s < rowStart || e > rowEnd) {
|
|
6277
|
+
rowStart = Math.min(rowStart, s);
|
|
6278
|
+
rowEnd = Math.max(rowEnd, e);
|
|
6279
|
+
changed = true;
|
|
6280
|
+
}
|
|
6281
|
+
}
|
|
6282
|
+
if (colsMeet) {
|
|
6283
|
+
const s = Math.max(frozenColumns, cols[0]);
|
|
6284
|
+
const e = cols[1] + 1;
|
|
6285
|
+
if (s < colStart || e > colEnd) {
|
|
6286
|
+
colStart = Math.min(colStart, s);
|
|
6287
|
+
colEnd = Math.max(colEnd, e);
|
|
6288
|
+
changed = true;
|
|
6289
|
+
}
|
|
6290
|
+
}
|
|
6291
|
+
}
|
|
6292
|
+
}
|
|
6293
|
+
return {
|
|
6294
|
+
virtual: true,
|
|
6295
|
+
frozenRows,
|
|
6296
|
+
rowStart,
|
|
6297
|
+
rowEnd,
|
|
6298
|
+
topSpace: rowTops[rowStart] - rowTops[frozenRows],
|
|
6299
|
+
bottomSpace: rowTops[rowCount] - rowTops[rowEnd],
|
|
6300
|
+
frozenColumns,
|
|
6301
|
+
colStart,
|
|
6302
|
+
colEnd,
|
|
6303
|
+
leftSpace: columnLefts[colStart] - columnLefts[frozenColumns],
|
|
6304
|
+
rightSpace: columnLefts[colCount] - columnLefts[colEnd]
|
|
6305
|
+
};
|
|
6306
|
+
}
|
|
6307
|
+
function displayedSpan(positions, from, to) {
|
|
6308
|
+
let lo = Infinity;
|
|
6309
|
+
let hi = -Infinity;
|
|
6310
|
+
for (let i = from; i <= to; i++) {
|
|
6311
|
+
const pos = positions.get(i);
|
|
6312
|
+
if (pos === void 0) continue;
|
|
6313
|
+
if (pos < lo) lo = pos;
|
|
6314
|
+
if (pos > hi) hi = pos;
|
|
6315
|
+
}
|
|
6316
|
+
return lo === Infinity ? null : [lo, hi];
|
|
6317
|
+
}
|
|
6318
|
+
function getSpreadsheetScrollToCell(metrics, viewport, cell) {
|
|
6319
|
+
const height = viewport.height > 0 ? viewport.height : SPREADSHEET_DEFAULT_VIEWPORT.height;
|
|
6320
|
+
const width = viewport.width > 0 ? viewport.width : SPREADSHEET_DEFAULT_VIEWPORT.width;
|
|
6321
|
+
const result = {};
|
|
6322
|
+
const rowPos = metrics.rowPosition.get(cell.row);
|
|
6323
|
+
if (rowPos !== void 0 && rowPos >= metrics.frozenRows) {
|
|
6324
|
+
const frozenHeight = metrics.rowTops[metrics.frozenRows];
|
|
6325
|
+
const top = metrics.rowTops[rowPos];
|
|
6326
|
+
const bottom = metrics.rowTops[rowPos + 1];
|
|
6327
|
+
const visibleHeight = height - SPREADSHEET_COLUMN_HEADER_HEIGHT;
|
|
6328
|
+
if (top < viewport.scrollTop + frozenHeight) result.scrollTop = Math.max(0, top - frozenHeight);
|
|
6329
|
+
else if (bottom > viewport.scrollTop + visibleHeight) result.scrollTop = Math.max(0, bottom - visibleHeight);
|
|
6330
|
+
}
|
|
6331
|
+
const colPos = metrics.columnPosition.get(cell.col);
|
|
6332
|
+
if (colPos !== void 0 && colPos >= metrics.frozenColumns) {
|
|
6333
|
+
const frozenWidth = metrics.columnLefts[metrics.frozenColumns];
|
|
6334
|
+
const left = metrics.columnLefts[colPos];
|
|
6335
|
+
const right = metrics.columnLefts[colPos + 1];
|
|
6336
|
+
const visibleWidth = width - SPREADSHEET_ROW_HEADER_WIDTH;
|
|
6337
|
+
if (left < viewport.scrollLeft + frozenWidth) result.scrollLeft = Math.max(0, left - frozenWidth);
|
|
6338
|
+
else if (right > viewport.scrollLeft + visibleWidth) result.scrollLeft = Math.max(0, right - visibleWidth);
|
|
6339
|
+
}
|
|
6340
|
+
return result.scrollTop === void 0 && result.scrollLeft === void 0 ? null : result;
|
|
6341
|
+
}
|
|
6342
|
+
function sameSpreadsheetViewWindow(a, b) {
|
|
6343
|
+
return a.virtual === b.virtual && a.rowStart === b.rowStart && a.rowEnd === b.rowEnd && a.colStart === b.colStart && a.colEnd === b.colEnd && a.topSpace === b.topSpace && a.bottomSpace === b.bottomSpace && a.leftSpace === b.leftSpace && a.rightSpace === b.rightSpace;
|
|
6344
|
+
}
|
|
6345
|
+
|
|
5609
6346
|
export {
|
|
5610
6347
|
extractNameDependencies,
|
|
5611
6348
|
columnDependencyKey,
|
|
5612
6349
|
extractCellDependencies,
|
|
6350
|
+
extractReferenceDependencies,
|
|
6351
|
+
rangeDependencyKey,
|
|
6352
|
+
IntervalIndex,
|
|
5613
6353
|
DependencyGraph,
|
|
5614
6354
|
shiftFormulaReferences,
|
|
5615
6355
|
mapFormulaReferences,
|
|
@@ -5721,6 +6461,18 @@ export {
|
|
|
5721
6461
|
importFileIntoSpreadsheet,
|
|
5722
6462
|
getSheetTabMenuItems,
|
|
5723
6463
|
sheetDropIndex,
|
|
5724
|
-
getNameManagerRows
|
|
6464
|
+
getNameManagerRows,
|
|
6465
|
+
SPREADSHEET_COLUMN_HEADER_HEIGHT,
|
|
6466
|
+
SPREADSHEET_ROW_HEADER_WIDTH,
|
|
6467
|
+
SPREADSHEET_GROUP_ROW_HEIGHT,
|
|
6468
|
+
SPREADSHEET_VIRTUAL_ROW_THRESHOLD,
|
|
6469
|
+
SPREADSHEET_VIRTUAL_COLUMN_THRESHOLD,
|
|
6470
|
+
SPREADSHEET_DEFAULT_VIEWPORT,
|
|
6471
|
+
measureSpreadsheetView,
|
|
6472
|
+
shouldVirtualizeSpreadsheet,
|
|
6473
|
+
fullSpreadsheetViewWindow,
|
|
6474
|
+
getSpreadsheetViewWindow,
|
|
6475
|
+
getSpreadsheetScrollToCell,
|
|
6476
|
+
sameSpreadsheetViewWindow
|
|
5725
6477
|
};
|
|
5726
|
-
//# sourceMappingURL=chunk-
|
|
6478
|
+
//# sourceMappingURL=chunk-X7HXL2SB.js.map
|