@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.
Files changed (113) hide show
  1. package/README.md +30 -0
  2. package/THIRD_PARTY_NOTICES.md +11 -0
  3. package/dist/agent/index.cjs +174 -11
  4. package/dist/agent/index.cjs.map +1 -1
  5. package/dist/agent/index.d.cts +18 -4
  6. package/dist/agent/index.d.ts +18 -4
  7. package/dist/agent/index.js +167 -4
  8. package/dist/agent/index.js.map +1 -1
  9. package/dist/{arrangeShortcuts-B8O3f8-P.d.cts → arrange-BICutYPk.d.cts} +243 -29
  10. package/dist/{arrangeShortcuts-sLlD2rSX.d.ts → arrange-BICutYPk.d.ts} +243 -29
  11. package/dist/builtins-CdPcLwwF.d.cts +176 -0
  12. package/dist/builtins-Chj6zv_O.d.ts +176 -0
  13. package/dist/canvas/index.cjs +3917 -19
  14. package/dist/canvas/index.cjs.map +1 -1
  15. package/dist/canvas/index.d.cts +1232 -7
  16. package/dist/canvas/index.d.ts +1232 -7
  17. package/dist/canvas/index.js +3916 -18
  18. package/dist/canvas/index.js.map +1 -1
  19. package/dist/chunk-3CC7OPUQ.cjs +831 -0
  20. package/dist/chunk-3CC7OPUQ.cjs.map +1 -0
  21. package/dist/{chunk-7WISYZFO.cjs → chunk-66IMF6OD.cjs} +174 -67
  22. package/dist/chunk-66IMF6OD.cjs.map +1 -0
  23. package/dist/{chunk-AL6JMGWW.cjs → chunk-CS6NZWWA.cjs} +24 -24
  24. package/dist/{chunk-AL6JMGWW.cjs.map → chunk-CS6NZWWA.cjs.map} +1 -1
  25. package/dist/{chunk-C7P22VIP.cjs → chunk-EWCOFSCQ.cjs} +17 -6
  26. package/dist/chunk-EWCOFSCQ.cjs.map +1 -0
  27. package/dist/chunk-HDYMLS3X.cjs +409 -0
  28. package/dist/chunk-HDYMLS3X.cjs.map +1 -0
  29. package/dist/{chunk-V3CHHWOE.cjs → chunk-HNDSPZ5T.cjs} +4 -4
  30. package/dist/{chunk-V3CHHWOE.cjs.map → chunk-HNDSPZ5T.cjs.map} +1 -1
  31. package/dist/{chunk-X4N4WUKB.cjs → chunk-IFHKKVXH.cjs} +150 -38
  32. package/dist/chunk-IFHKKVXH.cjs.map +1 -0
  33. package/dist/chunk-KRGXUQ2Y.js +1696 -0
  34. package/dist/chunk-KRGXUQ2Y.js.map +1 -0
  35. package/dist/{chunk-PO6A6QQH.cjs → chunk-LN2POBHT.cjs} +742 -970
  36. package/dist/chunk-LN2POBHT.cjs.map +1 -0
  37. package/dist/chunk-MR2764X2.js +831 -0
  38. package/dist/chunk-MR2764X2.js.map +1 -0
  39. package/dist/{chunk-WSQKHXW7.cjs → chunk-NQF32I6Z.cjs} +1209 -457
  40. package/dist/chunk-NQF32I6Z.cjs.map +1 -0
  41. package/dist/{chunk-NAXVVS5I.js → chunk-QJSOZFCZ.js} +2 -2
  42. package/dist/{chunk-UAKDG62Z.js → chunk-SWE3NPOU.js} +116 -4
  43. package/dist/chunk-SWE3NPOU.js.map +1 -0
  44. package/dist/chunk-TXXNBDQX.cjs +1696 -0
  45. package/dist/chunk-TXXNBDQX.cjs.map +1 -0
  46. package/dist/chunk-UBCFIABU.js +409 -0
  47. package/dist/chunk-UBCFIABU.js.map +1 -0
  48. package/dist/{chunk-ECCCYKGL.js → chunk-UKI64KUD.js} +147 -40
  49. package/dist/chunk-UKI64KUD.js.map +1 -0
  50. package/dist/{chunk-QGYDWPRM.js → chunk-WON34HKM.js} +2 -2
  51. package/dist/{chunk-RR7F5GUM.js → chunk-X7HXL2SB.js} +941 -189
  52. package/dist/chunk-X7HXL2SB.js.map +1 -0
  53. package/dist/{chunk-IXHSYOVY.js → chunk-XQFHLT53.js} +638 -866
  54. package/dist/chunk-XQFHLT53.js.map +1 -0
  55. package/dist/{chunk-FH6F67DN.js → chunk-ZRNHGAQT.js} +17 -6
  56. package/dist/{chunk-FH6F67DN.js.map → chunk-ZRNHGAQT.js.map} +1 -1
  57. package/dist/commands/index.cjs +352 -4
  58. package/dist/commands/index.cjs.map +1 -1
  59. package/dist/commands/index.d.cts +37 -108
  60. package/dist/commands/index.d.ts +37 -108
  61. package/dist/commands/index.js +351 -3
  62. package/dist/commands/index.js.map +1 -1
  63. package/dist/compare/index.cjs +3 -3
  64. package/dist/compare/index.js +2 -2
  65. package/dist/{controller-BIJiNoVs.d.cts → controller-CxXGOEmy.d.cts} +209 -13
  66. package/dist/{controller-BIJiNoVs.d.ts → controller-CxXGOEmy.d.ts} +209 -13
  67. package/dist/export/index.cjs +36 -0
  68. package/dist/export/index.cjs.map +1 -0
  69. package/dist/export/index.d.cts +243 -0
  70. package/dist/export/index.d.ts +243 -0
  71. package/dist/export/index.js +36 -0
  72. package/dist/export/index.js.map +1 -0
  73. package/dist/extensions/index.cjs +54 -6
  74. package/dist/extensions/index.cjs.map +1 -1
  75. package/dist/extensions/index.d.cts +7 -2
  76. package/dist/extensions/index.d.ts +7 -2
  77. package/dist/extensions/index.js +59 -11
  78. package/dist/{index-7a6bHTLy.d.ts → index-B273Zllk.d.ts} +211 -207
  79. package/dist/{index-Csd5qI8u.d.cts → index-jcJwmBYt.d.cts} +211 -207
  80. package/dist/index.cjs +146 -32
  81. package/dist/index.cjs.map +1 -1
  82. package/dist/index.d.cts +50 -14
  83. package/dist/index.d.ts +50 -14
  84. package/dist/index.js +138 -24
  85. package/dist/index.js.map +1 -1
  86. package/dist/registry-CED5C0lA.d.cts +204 -0
  87. package/dist/registry-CED5C0lA.d.ts +204 -0
  88. package/dist/{registry-D32LpIc4.d.cts → registry-D0GADKc7.d.cts} +1 -1
  89. package/dist/{registry-evKTP4ls.d.ts → registry-DhVUZioj.d.ts} +1 -1
  90. package/dist/serializers/index.cjs +6 -4
  91. package/dist/serializers/index.cjs.map +1 -1
  92. package/dist/serializers/index.d.cts +15 -4
  93. package/dist/serializers/index.d.ts +15 -4
  94. package/dist/serializers/index.js +5 -3
  95. package/dist/spreadsheet/index.cjs +33 -3
  96. package/dist/spreadsheet/index.cjs.map +1 -1
  97. package/dist/spreadsheet/index.d.cts +106 -3
  98. package/dist/spreadsheet/index.d.ts +106 -3
  99. package/dist/spreadsheet/index.js +32 -2
  100. package/package.json +10 -1
  101. package/dist/chunk-7WISYZFO.cjs.map +0 -1
  102. package/dist/chunk-C7P22VIP.cjs.map +0 -1
  103. package/dist/chunk-ECCCYKGL.js.map +0 -1
  104. package/dist/chunk-IXHSYOVY.js.map +0 -1
  105. package/dist/chunk-PO6A6QQH.cjs.map +0 -1
  106. package/dist/chunk-RR7F5GUM.js.map +0 -1
  107. package/dist/chunk-UAKDG62Z.js.map +0 -1
  108. package/dist/chunk-WSQKHXW7.cjs.map +0 -1
  109. package/dist/chunk-X4N4WUKB.cjs.map +0 -1
  110. package/dist/types-DX4Mh4SL.d.cts +0 -217
  111. package/dist/types-DX4Mh4SL.d.ts +0 -217
  112. /package/dist/{chunk-NAXVVS5I.js.map → chunk-QJSOZFCZ.js.map} +0 -0
  113. /package/dist/{chunk-QGYDWPRM.js.map → chunk-WON34HKM.js.map} +0 -0
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } function _optionalChainDelete(ops) { const result = _optionalChain(ops); return result == null ? true : result; } var _class; var _class2; var _class3; var _class4;
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } function _optionalChainDelete(ops) { const result = _optionalChain(ops); return result == null ? true : result; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6;
2
2
 
3
3
 
4
4
 
@@ -31,7 +31,7 @@ var _chunkTNNJXF46cjs = require('./chunk-TNNJXF46.cjs');
31
31
 
32
32
 
33
33
 
34
- var _chunkC7P22VIPcjs = require('./chunk-C7P22VIP.cjs');
34
+ var _chunkEWCOFSCQcjs = require('./chunk-EWCOFSCQ.cjs');
35
35
 
36
36
  // src/spreadsheet/formula/dependency-graph.ts
37
37
  function extractNameDependencies(node) {
@@ -72,22 +72,22 @@ function extractCellDependencies(node) {
72
72
  deps.push({ col: curr.col, row: curr.row, ...sheet });
73
73
  } else if (curr.type === "SpillRef") {
74
74
  deps.push({ col: curr.col, row: curr.row, ...sheet });
75
- } else if (curr.type === "Range" && curr.endRow >= _chunkC7P22VIPcjs.WHOLE_COLUMN_END_ROW) {
76
- const startC = _chunkC7P22VIPcjs.colNameToIndex.call(void 0, curr.startCol);
77
- const endC = _chunkC7P22VIPcjs.colNameToIndex.call(void 0, curr.endCol);
75
+ } else if (curr.type === "Range" && curr.endRow >= _chunkEWCOFSCQcjs.WHOLE_COLUMN_END_ROW) {
76
+ const startC = _chunkEWCOFSCQcjs.colNameToIndex.call(void 0, curr.startCol);
77
+ const endC = _chunkEWCOFSCQcjs.colNameToIndex.call(void 0, curr.endCol);
78
78
  for (let c = Math.min(startC, endC); c <= Math.max(startC, endC); c++) {
79
- deps.push({ col: _chunkC7P22VIPcjs.indexToColName.call(void 0, c), row: 0, ...sheet });
79
+ deps.push({ col: _chunkEWCOFSCQcjs.indexToColName.call(void 0, c), row: 0, ...sheet });
80
80
  }
81
81
  } else if (curr.type === "Range") {
82
- const startC = _chunkC7P22VIPcjs.colNameToIndex.call(void 0, curr.startCol);
83
- const endC = _chunkC7P22VIPcjs.colNameToIndex.call(void 0, curr.endCol);
82
+ const startC = _chunkEWCOFSCQcjs.colNameToIndex.call(void 0, curr.startCol);
83
+ const endC = _chunkEWCOFSCQcjs.colNameToIndex.call(void 0, curr.endCol);
84
84
  const minC = Math.min(startC, endC);
85
85
  const maxC = Math.max(startC, endC);
86
86
  const minR = Math.min(curr.startRow, curr.endRow);
87
87
  const maxR = Math.max(curr.startRow, curr.endRow);
88
88
  for (let c = minC; c <= maxC; c++) {
89
89
  for (let r = minR; r <= maxR; r++) {
90
- deps.push({ col: _chunkC7P22VIPcjs.indexToColName.call(void 0, c), row: r, ...sheet });
90
+ deps.push({ col: _chunkEWCOFSCQcjs.indexToColName.call(void 0, c), row: r, ...sheet });
91
91
  }
92
92
  }
93
93
  } else if (curr.type === "BinaryOp") {
@@ -105,73 +105,325 @@ function extractCellDependencies(node) {
105
105
  visit(node);
106
106
  return deps;
107
107
  }
108
- var DependencyGraph = (_class = class {constructor() { _class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this); }
109
- // Map of cellKey (e.g. "B2") -> Set of cellKeys that this cell depends ON
110
- __init() {this.dependencies = /* @__PURE__ */ new Map()}
111
- // Map of cellKey (e.g. "A1") -> Set of cellKeys that depend ON this cell (dependents)
112
- __init2() {this.dependents = /* @__PURE__ */ new Map()}
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 = _chunkEWCOFSCQcjs.colNameToIndex.call(void 0, curr.startCol);
121
+ const endC = _chunkEWCOFSCQcjs.colNameToIndex.call(void 0, curr.endCol);
122
+ const whole = curr.endRow >= _chunkEWCOFSCQcjs.WHOLE_COLUMN_END_ROW;
123
+ deps.push({
124
+ kind: "range",
125
+ startCol: _chunkEWCOFSCQcjs.indexToColName.call(void 0, Math.min(startC, endC)),
126
+ endCol: _chunkEWCOFSCQcjs.indexToColName.call(void 0, Math.max(startC, endC)),
127
+ startRow: whole ? 1 : Math.min(curr.startRow, curr.endRow),
128
+ endRow: whole ? _chunkEWCOFSCQcjs.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 = class {constructor() { _class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this); }
159
+ __init() {this.items = []}
160
+ __init2() {this.dead = new Uint8Array(0)}
161
+ __init3() {this.deadCount = 0}
162
+ __init4() {this.maxEnd = new Float64Array(0)}
163
+ __init5() {this.leaves = 0}
164
+ __init6() {this.position = /* @__PURE__ */ new Map()}
165
+ __init7() {this.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
+ }, _class);
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: _chunkEWCOFSCQcjs.colNameToIndex.call(void 0, m[1]), row: Number(m[2]) };
255
+ m = RANGE_KEY.exec(ref);
256
+ if (m) {
257
+ const ca = _chunkEWCOFSCQcjs.colNameToIndex.call(void 0, m[1]);
258
+ const cb = _chunkEWCOFSCQcjs.colNameToIndex.call(void 0, 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 = _chunkEWCOFSCQcjs.colNameToIndex.call(void 0, m[1]);
266
+ return { kind: "range", sheet, c0: c, c1: c, r0: 1, r1: _chunkEWCOFSCQcjs.WHOLE_COLUMN_END_ROW };
267
+ }
268
+ return { kind: "opaque" };
269
+ }
270
+ var VIRTUAL_PREFIX = "\0";
271
+ var DependencyGraph = (_class2 = class {constructor() { _class2.prototype.__init8.call(this);_class2.prototype.__init9.call(this);_class2.prototype.__init10.call(this);_class2.prototype.__init11.call(this);_class2.prototype.__init12.call(this);_class2.prototype.__init13.call(this);_class2.prototype.__init14.call(this);_class2.prototype.__init15.call(this);_class2.prototype.__init16.call(this); }
272
+ // Formula cell -> what it reads (single cells and whole ranges).
273
+ __init8() {this.nodes = /* @__PURE__ */ new Map()}
274
+ // Cell -> formulas reading it as a single cell.
275
+ __init9() {this.cellDependents = /* @__PURE__ */ new Map()}
276
+ // Sheet ('' for unqualified keys) -> interval index of the ranges formulas read.
277
+ __init10() {this.rangeIndex = /* @__PURE__ */ new Map()}
278
+ // Formulas recalculated on every recalculation (NOW, RAND, …).
279
+ __init11() {this.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,
116
283
  // so re-entering a formula doesn't drop them.
117
- __init3() {this.spillDependencies = /* @__PURE__ */ new Map()}
118
- __init4() {this.spillDependents = /* @__PURE__ */ new Map()}
284
+ __init12() {this.spillDependencies = /* @__PURE__ */ new Map()}
285
+ __init13() {this.spillDependents = /* @__PURE__ */ new Map()}
119
286
  // anchor -> the edges it registered (so they can be replaced).
120
- __init5() {this.spillEdges = /* @__PURE__ */ new Map()}
287
+ __init14() {this.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
+ __init15() {this.blockingEdges = /* @__PURE__ */ new Map()}
293
+ __init16() {this.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++) _optionalChain([index, 'access', _2 => _2.cols, 'access', _3 => _3.get, 'call', _4 => _4(c), 'optionalAccess', _5 => _5.remove, 'call', _6 => _6(`${range.r0}:${range.r1}:${key}`)]);
327
+ }
328
+ }
329
+ this.nodes.delete(key);
330
+ }
121
331
  /**
122
- * Sets the cells `cellKey` depends on directly (keys such as `B2`, or a
123
- * column key from `columnDependencyKey`), replacing its previous
124
- * dependencies. Used by spreadsheets whose keys carry more than A1 text
125
- * (e.g. sheet-qualified keys); `setCellFormula` derives them from a formula.
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
- for (const dep of _nullishCoalesce(this.dependencies.get(key), () => ( []))) {
130
- _optionalChain([this, 'access', _2 => _2.dependents, 'access', _3 => _3.get, 'call', _4 => _4(dep), 'optionalAccess', _5 => _5.delete, 'call', _6 => _6(key)]);
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
- deps.add(dep);
137
- if (!this.dependents.has(dep)) this.dependents.set(dep, /* @__PURE__ */ new Set());
138
- this.dependents.get(dep).add(key);
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}!`}${_chunkEWCOFSCQcjs.indexToColName.call(void 0, parsed.c0)}${parsed.r0}` : dep;
354
+ node.cells.add(cell);
139
355
  }
140
- if (deps.size > 0) this.dependencies.set(key, deps);
356
+ if (node.cells.size === 0 && node.ranges.length === 0) {
357
+ this.volatile.delete(key);
358
+ return;
359
+ }
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
- _optionalChain([this, 'access', _7 => _7.dependents, 'access', _8 => _8.get, 'call', _9 => _9(dep), 'optionalAccess', _10 => _10.delete, 'call', _11 => _11(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
- const ast = _chunkC7P22VIPcjs.parseFormula.call(void 0, formulaStr);
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);
165
- } catch (e) {
404
+ ast = _chunkEWCOFSCQcjs.parseFormula.call(void 0, formulaStr);
405
+ } catch (e2) {
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 >= _chunkEWCOFSCQcjs.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.dependencies.clear();
171
- this.dependents.clear();
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
@@ -183,12 +435,18 @@ var DependencyGraph = (_class = class {constructor() { _class.prototype.__init.c
183
435
  setSpillEdges(anchorKey, spilledKeys, blockingKeys = []) {
184
436
  const anchor = anchorKey.toUpperCase();
185
437
  for (const [from, to] of _nullishCoalesce(this.spillEdges.get(anchor), () => ( []))) {
186
- _optionalChain([this, 'access', _12 => _12.spillDependencies, 'access', _13 => _13.get, 'call', _14 => _14(from), 'optionalAccess', _15 => _15.delete, 'call', _16 => _16(to)]);
187
- _optionalChain([this, 'access', _17 => _17.spillDependents, 'access', _18 => _18.get, 'call', _19 => _19(to), 'optionalAccess', _20 => _20.delete, 'call', _21 => _21(from)]);
438
+ _optionalChain([this, 'access', _7 => _7.spillDependencies, 'access', _8 => _8.get, 'call', _9 => _9(from), 'optionalAccess', _10 => _10.delete, 'call', _11 => _11(to)]);
439
+ _optionalChain([this, 'access', _12 => _12.spillDependents, 'access', _13 => _13.get, 'call', _14 => _14(to), 'optionalAccess', _15 => _15.delete, 'call', _16 => _16(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
- for (const key of blockingKeys) edges.push([anchor, key.toUpperCase()]);
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 = class {constructor() { _class.prototype.__init.c
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 (_optionalChain([this, 'access', _17 => _17.blockingEdges, 'access', _18 => _18.get, 'call', _19 => _19(key), 'optionalAccess', _20 => _20.has, 'call', _21 => _21(anchor)])) continue;
472
+ const edges = this.spillEdges.get(anchor);
473
+ if (!_optionalChain([edges, 'optionalAccess', _22 => _22.some, 'call', _23 => _23(([from, to]) => from === key && to === anchor)])) continue;
474
+ anchors.delete(anchor);
475
+ _optionalChain([this, 'access', _24 => _24.spillDependents, 'access', _25 => _25.get, 'call', _26 => _26(anchor), 'optionalAccess', _27 => _27.delete, 'call', _28 => _28(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 (_nullishCoalesce(this.spillEdges.get(anchor), () => ( []))).filter(([, to]) => to === anchor).map(([from]) => from);
205
485
  }
206
- /** Formula dependencies plus dynamic-array (spill) dependencies of a cell. */
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
- return unionOf(this.dependencies.get(key), this.spillDependencies.get(key));
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 (_optionalChain([this, 'access', _29 => _29.spillDependencies, 'access', _30 => _30.get, 'call', _31 => _31(key), 'optionalAccess', _32 => _32.has, 'call', _33 => _33(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
- /** Cells depending on `cellKey`, through formulas or spills. */
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 key = cellKey2.toUpperCase();
214
- return unionOf(this.dependents.get(key), this.spillDependents.get(key));
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
+ _optionalChain([index, 'access', _34 => _34.cols, 'access', _35 => _35.get, 'call', _36 => _36(parsed.col), 'optionalAccess', _37 => _37.query, 'call', _38 => _38(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
- const visited = /* @__PURE__ */ new Set();
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 start = startCellKey.toUpperCase();
244
- const visited = /* @__PURE__ */ new Set();
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). Returns the keys in evaluation
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
- for (const key of cellKeys) {
294
- const upper = key.toUpperCase();
295
- if (!nodes.has(upper)) {
296
- nodes.add(upper);
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
- const node = queue.pop();
303
- const direct = [
304
- ..._nullishCoalesce(this.dependents.get(node), () => ( [])),
305
- ..._nullishCoalesce(this.spillDependents.get(node), () => ( [])),
306
- ..._nullishCoalesce(this.dependents.get(columnDependencyKey(node.replace(/\d+$/, ""))), () => ( []))
307
- ];
308
- for (const dep of direct) {
309
- if (!nodes.has(dep)) {
310
- nodes.add(dep);
311
- queue.push(dep);
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 _nullishCoalesce(rangeMembers.get(node), () => ( []));
317
640
  const result = [];
318
- for (const dep of this.getDependencies(node)) {
319
- if (dep.endsWith(":")) {
320
- const col = dep.slice(0, -1);
321
- for (const candidate of nodes) {
322
- if (candidate.replace(/\d+$/, "") === col) result.push(candidate);
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) && !_optionalChain([blockers, 'optionalAccess', _39 => _39.has, 'call', _40 => _40(dep)])) result.push(dep);
653
+ }
328
654
  return result;
329
655
  };
330
656
  const order = [];
@@ -372,6 +698,7 @@ var DependencyGraph = (_class = class {constructor() { _class.prototype.__init.c
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
  }
@@ -380,12 +707,7 @@ var DependencyGraph = (_class = class {constructor() { _class.prototype.__init.c
380
707
  }
381
708
  return { order, cyclic };
382
709
  }
383
- }, _class);
384
- function unionOf(a, b) {
385
- if (!b || b.size === 0) return Array.from(_nullishCoalesce(a, () => ( [])));
386
- if (!a || a.size === 0) return Array.from(b);
387
- return Array.from(/* @__PURE__ */ new Set([...a, ...b]));
388
- }
710
+ }, _class2);
389
711
 
390
712
  // src/spreadsheet/formula/references.ts
391
713
  var CELL_PART = /^(\$?)([A-Za-z]+)(\$?)(\d+)$/;
@@ -396,19 +718,19 @@ function parsePart(text) {
396
718
  if (cell) {
397
719
  return {
398
720
  colAbs: cell[1],
399
- col: _chunkC7P22VIPcjs.colNameToIndex.call(void 0, cell[2]),
721
+ col: _chunkEWCOFSCQcjs.colNameToIndex.call(void 0, cell[2]),
400
722
  rowAbs: cell[3],
401
723
  row: parseInt(cell[4], 10) - 1
402
724
  };
403
725
  }
404
726
  const column = COLUMN_PART.exec(text);
405
727
  if (column) {
406
- return { colAbs: column[1], col: _chunkC7P22VIPcjs.colNameToIndex.call(void 0, column[2]), rowAbs: "", row: null };
728
+ return { colAbs: column[1], col: _chunkEWCOFSCQcjs.colNameToIndex.call(void 0, column[2]), rowAbs: "", row: null };
407
729
  }
408
730
  return null;
409
731
  }
410
732
  function formatPart(part) {
411
- const col = `${part.colAbs}${_chunkC7P22VIPcjs.indexToColName.call(void 0, part.col)}`;
733
+ const col = `${part.colAbs}${_chunkEWCOFSCQcjs.indexToColName.call(void 0, part.col)}`;
412
734
  return part.row === null ? col : `${col}${part.rowAbs}${part.row + 1}`;
413
735
  }
414
736
  function shiftCoordinate(value, change) {
@@ -458,8 +780,8 @@ function mapFormulaReferences(formula, rewrite) {
458
780
  if (!formula.startsWith("=")) return formula;
459
781
  let tokens;
460
782
  try {
461
- tokens = _chunkC7P22VIPcjs.tokenizeFormula.call(void 0, formula);
462
- } catch (e2) {
783
+ tokens = _chunkEWCOFSCQcjs.tokenizeFormula.call(void 0, formula);
784
+ } catch (e3) {
463
785
  return formula;
464
786
  }
465
787
  let result = "";
@@ -467,7 +789,7 @@ function mapFormulaReferences(formula, rewrite) {
467
789
  for (let i = 0; i < tokens.length; i++) {
468
790
  const token = tokens[i];
469
791
  if (token.type !== "CELL_REF" && token.type !== "RANGE" && token.type !== "SPILL_REF") continue;
470
- if (_optionalChain([tokens, 'access', _22 => _22[i + 1], 'optionalAccess', _23 => _23.type]) === "LPAREN") continue;
792
+ if (_optionalChain([tokens, 'access', _41 => _41[i + 1], 'optionalAccess', _42 => _42.type]) === "LPAREN") continue;
471
793
  const refStart = _nullishCoalesce(token.refStart, () => ( token.position));
472
794
  let end = refStart;
473
795
  while (end < formula.length && REF_CHAR.test(formula[end])) end++;
@@ -566,8 +888,8 @@ function formatSheetPrefix(sheet, sheetEnd) {
566
888
  function tokensOf(formula) {
567
889
  if (!formula.startsWith("=")) return null;
568
890
  try {
569
- return _chunkC7P22VIPcjs.tokenizeFormula.call(void 0, formula);
570
- } catch (e3) {
891
+ return _chunkEWCOFSCQcjs.tokenizeFormula.call(void 0, formula);
892
+ } catch (e4) {
571
893
  return null;
572
894
  }
573
895
  }
@@ -631,7 +953,7 @@ function formulaIdentifiers(formula) {
631
953
  const out = [];
632
954
  tokens.forEach((token, i) => {
633
955
  if (token.type !== "IDENTIFIER") return;
634
- const call = _optionalChain([tokens, 'access', _24 => _24[i + 1], 'optionalAccess', _25 => _25.type]) === "LPAREN";
956
+ const call = _optionalChain([tokens, 'access', _43 => _43[i + 1], 'optionalAccess', _44 => _44.type]) === "LPAREN";
635
957
  out.push(token.sheet === void 0 ? { name: token.value, call } : { name: token.value, sheet: token.sheet, call });
636
958
  });
637
959
  return out;
@@ -696,7 +1018,7 @@ function innermostCall(stack) {
696
1018
  }
697
1019
  var toSuggestion = (def) => ({
698
1020
  name: def.name,
699
- signature: _chunkC7P22VIPcjs.getFormulaSignature.call(void 0, def),
1021
+ signature: _chunkEWCOFSCQcjs.getFormulaSignature.call(void 0, def),
700
1022
  description: def.description,
701
1023
  category: def.category
702
1024
  });
@@ -715,21 +1037,21 @@ function getFormulaHints(input, caret = input.length, options = {}) {
715
1037
  }));
716
1038
  suggestions = [
717
1039
  ...names,
718
- ..._chunkC7P22VIPcjs.searchFormulaFunctions.call(void 0, query, { limit: void 0 }).filter((def) => def.name.startsWith(query) && !names.some((n) => n.name.toUpperCase() === def.name)).map(toSuggestion)
1040
+ ..._chunkEWCOFSCQcjs.searchFormulaFunctions.call(void 0, query, { limit: void 0 }).filter((def) => def.name.startsWith(query) && !names.some((n) => n.name.toUpperCase() === def.name)).map(toSuggestion)
719
1041
  ].slice(0, _nullishCoalesce(options.limit, () => ( 8)));
720
1042
  const followedByParen = input.slice(context.prefix.end).trimStart().startsWith("(");
721
1043
  if (followedByParen || suggestions.length === 1 && suggestions[0].name.toUpperCase() === query) suggestions = [];
722
1044
  }
723
1045
  let activeCall = null;
724
1046
  if (context.call) {
725
- const def = _chunkC7P22VIPcjs.getFormulaFunction.call(void 0, context.call.name);
1047
+ const def = _chunkEWCOFSCQcjs.getFormulaFunction.call(void 0, context.call.name);
726
1048
  if (def) {
727
- const specIndex = _chunkC7P22VIPcjs.getFormulaArgumentSpecIndex.call(void 0, def, context.call.argIndex);
1049
+ const specIndex = _chunkEWCOFSCQcjs.getFormulaArgumentSpecIndex.call(void 0, def, context.call.argIndex);
728
1050
  activeCall = {
729
1051
  name: def.name,
730
- signature: _chunkC7P22VIPcjs.getFormulaSignature.call(void 0, def),
1052
+ signature: _chunkEWCOFSCQcjs.getFormulaSignature.call(void 0, def),
731
1053
  description: def.description,
732
- parts: _chunkC7P22VIPcjs.getFormulaSignatureParts.call(void 0, def, context.call.argIndex),
1054
+ parts: _chunkEWCOFSCQcjs.getFormulaSignatureParts.call(void 0, def, context.call.argIndex),
733
1055
  argIndex: context.call.argIndex,
734
1056
  argument: specIndex >= 0 ? def.args[specIndex] : null
735
1057
  };
@@ -739,8 +1061,8 @@ function getFormulaHints(input, caret = input.length, options = {}) {
739
1061
  }
740
1062
  function applyFormulaSuggestion(input, caret, suggestion, options = {}) {
741
1063
  const context = getFormulaCaretContext(input, caret);
742
- const start = _nullishCoalesce(_optionalChain([context, 'access', _26 => _26.prefix, 'optionalAccess', _27 => _27.start]), () => ( caret));
743
- const end = _nullishCoalesce(_optionalChain([context, 'access', _28 => _28.prefix, 'optionalAccess', _29 => _29.end]), () => ( caret));
1064
+ const start = _nullishCoalesce(_optionalChain([context, 'access', _45 => _45.prefix, 'optionalAccess', _46 => _46.start]), () => ( caret));
1065
+ const end = _nullishCoalesce(_optionalChain([context, 'access', _47 => _47.prefix, 'optionalAccess', _48 => _48.end]), () => ( caret));
744
1066
  const definedName = typeof suggestion !== "string" && suggestion.kind === "name";
745
1067
  const name = typeof suggestion === "string" ? suggestion : suggestion.name;
746
1068
  const call = _nullishCoalesce(options.call, () => ( (definedName ? !!suggestion.isLambda : true)));
@@ -777,7 +1099,7 @@ function numberFormatForCellFormat(format) {
777
1099
  }
778
1100
  }
779
1101
  function cellFormatForNumberFormat(code) {
780
- switch (_chunkC7P22VIPcjs.NumberFormat.parseCached(code).kind()) {
1102
+ switch (_chunkEWCOFSCQcjs.NumberFormat.parseCached(code).kind()) {
781
1103
  case "number":
782
1104
  case "fraction":
783
1105
  case "scientific":
@@ -796,7 +1118,7 @@ function cellFormatForNumberFormat(code) {
796
1118
  return void 0;
797
1119
  }
798
1120
  }
799
- var isGeneralNumberFormat = (code) => code === void 0 || _chunkC7P22VIPcjs.NumberFormat.parseCached(code).isGeneral();
1121
+ var isGeneralNumberFormat = (code) => code === void 0 || _chunkEWCOFSCQcjs.NumberFormat.parseCached(code).isGeneral();
800
1122
 
801
1123
  // src/spreadsheet/formula/format.ts
802
1124
  function formatCellForDisplay(cell) {
@@ -804,22 +1126,22 @@ function formatCellForDisplay(cell) {
804
1126
  if (cell.error) return { text: cell.error, numeric: false };
805
1127
  const { value } = cell;
806
1128
  const code = _nullishCoalesce(cell.numberFormat, () => ( (typeof value === "number" ? numberFormatForCellFormat(cell.format) : void 0)));
807
- if (code !== void 0 && !isGeneralNumberFormat(code) && !_chunkC7P22VIPcjs.isBlank.call(void 0, value) && typeof value !== "boolean") {
808
- return _chunkC7P22VIPcjs.NumberFormat.parseCached(code).format(value);
1129
+ if (code !== void 0 && !isGeneralNumberFormat(code) && !_chunkEWCOFSCQcjs.isBlank.call(void 0, value) && typeof value !== "boolean") {
1130
+ return _chunkEWCOFSCQcjs.NumberFormat.parseCached(code).format(value);
809
1131
  }
810
- return { text: _chunkC7P22VIPcjs.toText.call(void 0, value), numeric: typeof value === "number" };
1132
+ return { text: _chunkEWCOFSCQcjs.toText.call(void 0, value), numeric: typeof value === "number" };
811
1133
  }
812
1134
  function formatCellDisplay(cell) {
813
1135
  return formatCellForDisplay(cell).text;
814
1136
  }
815
1137
  function formatValueWithCode(value, format) {
816
- const result = _chunkC7P22VIPcjs.formatTextFunction.call(void 0, value, format);
1138
+ const result = _chunkEWCOFSCQcjs.formatTextFunction.call(void 0, value, format);
817
1139
  return "text" in result ? result.text : null;
818
1140
  }
819
1141
 
820
1142
  // src/spreadsheet/datasource/index.ts
821
- var DataSourceBinder = (_class2 = class {constructor() { _class2.prototype.__init6.call(this); }
822
- __init6() {this.dataSources = /* @__PURE__ */ new Map()}
1143
+ var DataSourceBinder = (_class3 = class {constructor() { _class3.prototype.__init17.call(this); }
1144
+ __init17() {this.dataSources = /* @__PURE__ */ new Map()}
823
1145
  register(dataSource) {
824
1146
  this.dataSources.set(dataSource.id, dataSource);
825
1147
  }
@@ -850,10 +1172,10 @@ var DataSourceBinder = (_class2 = class {constructor() { _class2.prototype.__ini
850
1172
  for (const record of records) {
851
1173
  const existing = existingRecordRowMap.get(record.id);
852
1174
  nextRows.push({
853
- ..._optionalChain([existing, 'optionalAccess', _30 => _30.hidden]) ? { hidden: true } : {},
854
- id: _optionalChain([existing, 'optionalAccess', _31 => _31.id]) || `row_${record.id}`,
1175
+ ..._optionalChain([existing, 'optionalAccess', _49 => _49.hidden]) ? { hidden: true } : {},
1176
+ id: _optionalChain([existing, 'optionalAccess', _50 => _50.id]) || `row_${record.id}`,
855
1177
  index: rowIndex++,
856
- height: _optionalChain([existing, 'optionalAccess', _32 => _32.height]) || 32,
1178
+ height: _optionalChain([existing, 'optionalAccess', _51 => _51.height]) || 32,
857
1179
  recordId: record.id,
858
1180
  type: "record"
859
1181
  });
@@ -885,7 +1207,7 @@ var DataSourceBinder = (_class2 = class {constructor() { _class2.prototype.__ini
885
1207
  if (!ds) return;
886
1208
  await ds.updateRecord(recordId, field, value);
887
1209
  }
888
- }, _class2);
1210
+ }, _class3);
889
1211
 
890
1212
  // src/spreadsheet/workbook.ts
891
1213
  var MAX_SHEET_NAME_LENGTH = 31;
@@ -991,28 +1313,28 @@ function compareCellValues(a, b, direction = "asc") {
991
1313
  const bBlank = isBlankValue(b);
992
1314
  if (aBlank || bBlank) return aBlank === bBlank ? 0 : aBlank ? 1 : -1;
993
1315
  let result;
994
- if (isPlainText(a) && isPlainText(b) && !(_chunkC7P22VIPcjs.parseIsoDate.call(void 0, a) && _chunkC7P22VIPcjs.parseIsoDate.call(void 0, b))) {
1316
+ if (isPlainText(a) && isPlainText(b) && !(_chunkEWCOFSCQcjs.parseIsoDate.call(void 0, a) && _chunkEWCOFSCQcjs.parseIsoDate.call(void 0, b))) {
995
1317
  result = compareText(a, b);
996
1318
  } else {
997
- result = _chunkC7P22VIPcjs.sortCompareValues.call(void 0, a, b);
1319
+ result = _chunkEWCOFSCQcjs.sortCompareValues.call(void 0, a, b);
998
1320
  }
999
1321
  if (result === 0) return 0;
1000
1322
  return direction === "desc" ? -Math.sign(result) : Math.sign(result);
1001
1323
  }
1002
- var isPlainText = (value) => typeof value === "string" && !_chunkC7P22VIPcjs.isFormulaError.call(void 0, value);
1324
+ var isPlainText = (value) => typeof value === "string" && !_chunkEWCOFSCQcjs.isFormulaError.call(void 0, value);
1003
1325
  function filterValue(value) {
1004
- if (isBlankValue(value) || _chunkC7P22VIPcjs.isFormulaError.call(void 0, value)) return null;
1326
+ if (isBlankValue(value) || _chunkEWCOFSCQcjs.isFormulaError.call(void 0, value)) return null;
1005
1327
  if (typeof value === "number") return Number.isFinite(value) ? { value, kind: "number" } : null;
1006
1328
  if (typeof value === "boolean") return { value, kind: "boolean" };
1007
- const num = _chunkC7P22VIPcjs.numericValue.call(void 0, value);
1329
+ const num = _chunkEWCOFSCQcjs.numericValue.call(void 0, value);
1008
1330
  if (num !== null) return { value: num, kind: "number" };
1009
1331
  const text = String(value);
1010
- return { value: text, kind: _chunkC7P22VIPcjs.parseIsoDate.call(void 0, text) ? "number" : "text" };
1332
+ return { value: text, kind: _chunkEWCOFSCQcjs.parseIsoDate.call(void 0, text) ? "number" : "text" };
1011
1333
  }
1012
1334
  function filterOperand(operand) {
1013
1335
  if (typeof operand !== "string") return filterValue(operand);
1014
1336
  if (operand.trim() === "") return null;
1015
- const parsed = _chunkC7P22VIPcjs.parseCellInput.call(void 0, operand.trim());
1337
+ const parsed = _chunkEWCOFSCQcjs.parseCellInput.call(void 0, operand.trim());
1016
1338
  if (parsed.type === "number" || parsed.type === "date" || parsed.type === "boolean") return filterValue(parsed.value);
1017
1339
  return { value: operand, kind: "text" };
1018
1340
  }
@@ -1020,7 +1342,7 @@ function compareForFilter(value, operand) {
1020
1342
  const a = filterValue(value);
1021
1343
  const b = filterOperand(operand);
1022
1344
  if (!a || !b || a.kind !== b.kind) return null;
1023
- return _chunkC7P22VIPcjs.compareValues.call(void 0, a.value, b.value);
1345
+ return _chunkEWCOFSCQcjs.compareValues.call(void 0, a.value, b.value);
1024
1346
  }
1025
1347
  function looselyEquals(value, operand) {
1026
1348
  const c = compareForFilter(value, operand);
@@ -1099,7 +1421,7 @@ function classifyFillValue(raw) {
1099
1421
  if (raw.trim() === "") return { kind: "blank" };
1100
1422
  const num = toNumber(raw);
1101
1423
  if (num !== null) return { kind: "number", value: num };
1102
- const typed = _chunkC7P22VIPcjs.parseCellInput.call(void 0, raw);
1424
+ const typed = _chunkEWCOFSCQcjs.parseCellInput.call(void 0, raw);
1103
1425
  const date = ISO_DATE_PATTERN.exec(typed.type === "date" ? String(typed.value) : raw.trim());
1104
1426
  if (date) {
1105
1427
  const [year, month, day] = [Number(date[1]), Number(date[2]), Number(date[3])];
@@ -1271,8 +1593,8 @@ function parseHtmlTable(html) {
1271
1593
  while (cellMatch = cellPattern.exec(rowMatch[1])) {
1272
1594
  while (taken.has(col)) row[col++] = "";
1273
1595
  const attrs = _nullishCoalesce(cellMatch[2], () => ( ""));
1274
- const colspan = Math.max(1, parseInt(_nullishCoalesce(_optionalChain([/colspan\s*=\s*["']?(\d+)/i, 'access', _33 => _33.exec, 'call', _34 => _34(attrs), 'optionalAccess', _35 => _35[1]]), () => ( "1")), 10));
1275
- const rowspan = Math.max(1, parseInt(_nullishCoalesce(_optionalChain([/rowspan\s*=\s*["']?(\d+)/i, 'access', _36 => _36.exec, 'call', _37 => _37(attrs), 'optionalAccess', _38 => _38[1]]), () => ( "1")), 10));
1596
+ const colspan = Math.max(1, parseInt(_nullishCoalesce(_optionalChain([/colspan\s*=\s*["']?(\d+)/i, 'access', _52 => _52.exec, 'call', _53 => _53(attrs), 'optionalAccess', _54 => _54[1]]), () => ( "1")), 10));
1597
+ const rowspan = Math.max(1, parseInt(_nullishCoalesce(_optionalChain([/rowspan\s*=\s*["']?(\d+)/i, 'access', _55 => _55.exec, 'call', _56 => _56(attrs), 'optionalAccess', _57 => _57[1]]), () => ( "1")), 10));
1276
1598
  row[col] = cellHtmlToText(cellMatch[3]);
1277
1599
  for (let c = col; c < col + colspan; c++) {
1278
1600
  if (c > col) row[c] = "";
@@ -1309,18 +1631,18 @@ function readFormat(source, format) {
1309
1631
  return _nullishCoalesce(source.getData(format), () => ( ""));
1310
1632
  }
1311
1633
  return _nullishCoalesce(source[format], () => ( ""));
1312
- } catch (e4) {
1634
+ } catch (e5) {
1313
1635
  return "";
1314
1636
  }
1315
1637
  }
1316
1638
  function parsePayload(json) {
1317
1639
  try {
1318
1640
  const payload = JSON.parse(json);
1319
- if (_optionalChain([payload, 'optionalAccess', _39 => _39.type]) !== "pixerate-spreadsheet" || !Array.isArray(payload.cells) || !payload.cells.every((row) => Array.isArray(row)) || typeof _optionalChain([payload, 'access', _40 => _40.origin, 'optionalAccess', _41 => _41.row]) !== "number" || typeof _optionalChain([payload, 'access', _42 => _42.origin, 'optionalAccess', _43 => _43.col]) !== "number") {
1641
+ if (_optionalChain([payload, 'optionalAccess', _58 => _58.type]) !== "pixerate-spreadsheet" || !Array.isArray(payload.cells) || !payload.cells.every((row) => Array.isArray(row)) || typeof _optionalChain([payload, 'access', _59 => _59.origin, 'optionalAccess', _60 => _60.row]) !== "number" || typeof _optionalChain([payload, 'access', _61 => _61.origin, 'optionalAccess', _62 => _62.col]) !== "number") {
1320
1642
  return null;
1321
1643
  }
1322
1644
  return payload;
1323
- } catch (e5) {
1645
+ } catch (e6) {
1324
1646
  return null;
1325
1647
  }
1326
1648
  }
@@ -1330,7 +1652,7 @@ function readSpreadsheetClipboard(source) {
1330
1652
  return {
1331
1653
  format: "internal",
1332
1654
  origin: payload.origin,
1333
- rows: payload.cells.map((row) => row.map((cell) => typeof _optionalChain([cell, 'optionalAccess', _44 => _44.raw]) === "string" ? cell.raw : displayText(_optionalChain([cell, 'optionalAccess', _45 => _45.value]))))
1655
+ rows: payload.cells.map((row) => row.map((cell) => typeof _optionalChain([cell, 'optionalAccess', _63 => _63.raw]) === "string" ? cell.raw : displayText(_optionalChain([cell, 'optionalAccess', _64 => _64.value]))))
1334
1656
  };
1335
1657
  }
1336
1658
  const html = readFormat(source, "text/html");
@@ -1437,8 +1759,8 @@ function forEachRangeCell(ranges, rowCount, colCount, fn) {
1437
1759
  function formatRuleRanges(ranges) {
1438
1760
  return ranges.map((range) => {
1439
1761
  const r = normalizeRange(range);
1440
- const start = `${_chunkC7P22VIPcjs.indexToColName.call(void 0, r.startCol)}${r.startRow + 1}`;
1441
- const end = `${_chunkC7P22VIPcjs.indexToColName.call(void 0, r.endCol)}${r.endRow + 1}`;
1762
+ const start = `${_chunkEWCOFSCQcjs.indexToColName.call(void 0, r.startCol)}${r.startRow + 1}`;
1763
+ const end = `${_chunkEWCOFSCQcjs.indexToColName.call(void 0, r.endCol)}${r.endRow + 1}`;
1442
1764
  return start === end ? start : `${start}:${end}`;
1443
1765
  }).join(", ");
1444
1766
  }
@@ -1451,9 +1773,9 @@ function parseRuleRanges(text) {
1451
1773
  const m = A1_RANGE.exec(part);
1452
1774
  if (!m) return null;
1453
1775
  const startRow = Number(m[2]) - 1;
1454
- const startCol = _chunkC7P22VIPcjs.colNameToIndex.call(void 0, m[1]);
1776
+ const startCol = _chunkEWCOFSCQcjs.colNameToIndex.call(void 0, m[1]);
1455
1777
  const endRow = m[4] ? Number(m[4]) - 1 : startRow;
1456
- const endCol = m[3] ? _chunkC7P22VIPcjs.colNameToIndex.call(void 0, m[3]) : startCol;
1778
+ const endCol = m[3] ? _chunkEWCOFSCQcjs.colNameToIndex.call(void 0, m[3]) : startCol;
1457
1779
  if (startRow < 0 || endRow < 0 || startCol < 0 || endCol < 0) return null;
1458
1780
  out.push(normalizeRange({ startRow, startCol, endRow, endCol }));
1459
1781
  }
@@ -1513,7 +1835,7 @@ function conditionalFormatNumber(value) {
1513
1835
  if (typeof value !== "string" || value.length < 8 || value.length > 19) return null;
1514
1836
  const m = ISO_DATE.exec(value);
1515
1837
  if (!m) return null;
1516
- const serial = _chunkC7P22VIPcjs.serialFromYmd.call(void 0, Number(m[1]), Number(m[2]), Number(m[3]));
1838
+ const serial = _chunkEWCOFSCQcjs.serialFromYmd.call(void 0, Number(m[1]), Number(m[2]), Number(m[3]));
1517
1839
  if (serial === null) return null;
1518
1840
  const time = m[4] ? (Number(m[4]) * 3600 + Number(m[5]) * 60 + Number(_nullishCoalesce(m[6], () => ( 0)))) / 86400 : 0;
1519
1841
  return serial + time;
@@ -1521,7 +1843,7 @@ function conditionalFormatNumber(value) {
1521
1843
  function valueText(value) {
1522
1844
  if (isBlank2(value)) return "";
1523
1845
  if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
1524
- if (typeof value === "number") return _chunkC7P22VIPcjs.numberToText.call(void 0, value);
1846
+ if (typeof value === "number") return _chunkEWCOFSCQcjs.numberToText.call(void 0, value);
1525
1847
  return String(value);
1526
1848
  }
1527
1849
  function firstValue(value) {
@@ -1565,9 +1887,9 @@ function usesFormulas(rule) {
1565
1887
  case "colorScale":
1566
1888
  return rule.stops.some((s) => s.value.type === "formula");
1567
1889
  case "dataBar":
1568
- return _optionalChain([rule, 'access', _46 => _46.min, 'optionalAccess', _47 => _47.type]) === "formula" || _optionalChain([rule, 'access', _48 => _48.max, 'optionalAccess', _49 => _49.type]) === "formula";
1890
+ return _optionalChain([rule, 'access', _65 => _65.min, 'optionalAccess', _66 => _66.type]) === "formula" || _optionalChain([rule, 'access', _67 => _67.max, 'optionalAccess', _68 => _68.type]) === "formula";
1569
1891
  case "iconSet":
1570
- return !!_optionalChain([rule, 'access', _50 => _50.thresholds, 'optionalAccess', _51 => _51.some, 'call', _52 => _52((t) => t.type === "formula")]);
1892
+ return !!_optionalChain([rule, 'access', _69 => _69.thresholds, 'optionalAccess', _70 => _70.some, 'call', _71 => _71((t) => t.type === "formula")]);
1571
1893
  default:
1572
1894
  return false;
1573
1895
  }
@@ -1602,15 +1924,15 @@ var Overlay = class {
1602
1924
  }
1603
1925
  };
1604
1926
  var EMPTY_CONDITIONAL_FORMAT_OVERLAY = new Overlay(/* @__PURE__ */ new Map());
1605
- var ConditionalFormatEngine = (_class3 = class {
1606
- constructor(sheet) {;_class3.prototype.__init7.call(this);_class3.prototype.__init8.call(this);_class3.prototype.__init9.call(this);_class3.prototype.__init10.call(this);
1927
+ var ConditionalFormatEngine = (_class4 = class {
1928
+ constructor(sheet) {;_class4.prototype.__init18.call(this);_class4.prototype.__init19.call(this);_class4.prototype.__init20.call(this);_class4.prototype.__init21.call(this);
1607
1929
  this.sheet = sheet;
1608
1930
  }
1609
1931
 
1610
- __init7() {this.cells = /* @__PURE__ */ new Map()}
1611
- __init8() {this.stats = /* @__PURE__ */ new Map()}
1612
- __init9() {this.rulesKey = null}
1613
- __init10() {this.current = EMPTY_CONDITIONAL_FORMAT_OVERLAY}
1932
+ __init18() {this.cells = /* @__PURE__ */ new Map()}
1933
+ __init19() {this.stats = /* @__PURE__ */ new Map()}
1934
+ __init20() {this.rulesKey = null}
1935
+ __init21() {this.current = EMPTY_CONDITIONAL_FORMAT_OVERLAY}
1614
1936
  /** Replaces the sheet accessor (e.g. after rows or columns were added). */
1615
1937
  setSheet(sheet) {
1616
1938
  this.sheet = sheet;
@@ -1707,7 +2029,7 @@ var ConditionalFormatEngine = (_class3 = class {
1707
2029
  if (!this.sheet.evaluateFormula) return "#NAME?";
1708
2030
  try {
1709
2031
  return firstValue(this.sheet.evaluateFormula(ruleFormulaAt(formula, ruleAnchor(rule.ranges), row, col), row, col));
1710
- } catch (e6) {
2032
+ } catch (e7) {
1711
2033
  return "#ERROR!";
1712
2034
  }
1713
2035
  }
@@ -1735,7 +2057,7 @@ var ConditionalFormatEngine = (_class3 = class {
1735
2057
  operand(text, rule, row, col) {
1736
2058
  if (text === void 0) return "";
1737
2059
  if (isFormulaText(text)) return this.evalFormula(text, rule, row, col);
1738
- return _chunkC7P22VIPcjs.parseCellInput.call(void 0, text).value;
2060
+ return _chunkEWCOFSCQcjs.parseCellInput.call(void 0, text).value;
1739
2061
  }
1740
2062
  /** Combines every rule that applies to (row, col). */
1741
2063
  computeLook(rules, row, col) {
@@ -1767,7 +2089,7 @@ var ConditionalFormatEngine = (_class3 = class {
1767
2089
  * scale fill, bar or icon on `out`, or null.
1768
2090
  */
1769
2091
  matchRule(rule, v, row, col, out) {
1770
- const error = _chunkC7P22VIPcjs.isFormulaError.call(void 0, v);
2092
+ const error = _chunkEWCOFSCQcjs.isFormulaError.call(void 0, v);
1771
2093
  switch (rule.type) {
1772
2094
  case "cellIs": {
1773
2095
  if (error) return null;
@@ -1775,18 +2097,18 @@ var ConditionalFormatEngine = (_class3 = class {
1775
2097
  let ok;
1776
2098
  if (rule.operator === "between" || rule.operator === "notBetween") {
1777
2099
  const b = this.operand(rule.value2, rule, row, col);
1778
- const [lo, hi] = _chunkC7P22VIPcjs.compareValues.call(void 0, a, b) > 0 ? [b, a] : [a, b];
1779
- const inside = _chunkC7P22VIPcjs.compareValues.call(void 0, v, lo) >= 0 && _chunkC7P22VIPcjs.compareValues.call(void 0, v, hi) <= 0;
2100
+ const [lo, hi] = _chunkEWCOFSCQcjs.compareValues.call(void 0, a, b) > 0 ? [b, a] : [a, b];
2101
+ const inside = _chunkEWCOFSCQcjs.compareValues.call(void 0, v, lo) >= 0 && _chunkEWCOFSCQcjs.compareValues.call(void 0, v, hi) <= 0;
1780
2102
  ok = rule.operator === "between" ? inside : !inside;
1781
2103
  } else {
1782
- if (_chunkC7P22VIPcjs.isFormulaError.call(void 0, a)) return null;
1783
- const cmp = _chunkC7P22VIPcjs.compareValues.call(void 0, v, a);
2104
+ if (_chunkEWCOFSCQcjs.isFormulaError.call(void 0, a)) return null;
2105
+ const cmp = _chunkEWCOFSCQcjs.compareValues.call(void 0, v, a);
1784
2106
  ok = rule.operator === "equal" ? cmp === 0 : rule.operator === "notEqual" ? cmp !== 0 : rule.operator === "greaterThan" ? cmp > 0 : rule.operator === "lessThan" ? cmp < 0 : rule.operator === "greaterThanOrEqual" ? cmp >= 0 : cmp <= 0;
1785
2107
  }
1786
2108
  return ok ? rule.style : null;
1787
2109
  }
1788
2110
  case "expression": {
1789
- const result = _chunkC7P22VIPcjs.toBoolean.call(void 0, this.evalFormula(rule.formula, rule, row, col));
2111
+ const result = _chunkEWCOFSCQcjs.toBoolean.call(void 0, this.evalFormula(rule.formula, rule, row, col));
1790
2112
  return result === true ? rule.style : null;
1791
2113
  }
1792
2114
  case "containsText":
@@ -1833,7 +2155,7 @@ var ConditionalFormatEngine = (_class3 = class {
1833
2155
  case "timePeriod": {
1834
2156
  const n = conditionalFormatNumber(v);
1835
2157
  if (n === null) return null;
1836
- return matchesTimePeriod(rule.period, Math.floor(n), _nullishCoalesce(_optionalChain([this, 'access', _53 => _53.sheet, 'access', _54 => _54.today, 'optionalCall', _55 => _55()]), () => ( /* @__PURE__ */ new Date()))) ? rule.style : null;
2158
+ return matchesTimePeriod(rule.period, Math.floor(n), _nullishCoalesce(_optionalChain([this, 'access', _72 => _72.sheet, 'access', _73 => _73.today, 'optionalCall', _74 => _74()]), () => ( /* @__PURE__ */ new Date()))) ? rule.style : null;
1837
2159
  }
1838
2160
  case "colorScale": {
1839
2161
  const n = conditionalFormatNumber(v);
@@ -1888,12 +2210,12 @@ var ConditionalFormatEngine = (_class3 = class {
1888
2210
  }
1889
2211
  return null;
1890
2212
  }
1891
- }, _class3);
2213
+ }, _class4);
1892
2214
  function matchesTimePeriod(period, d, today) {
1893
- const t = _chunkC7P22VIPcjs.serialFromYmd.call(void 0, today.getFullYear(), today.getMonth() + 1, today.getDate());
2215
+ const t = _chunkEWCOFSCQcjs.serialFromYmd.call(void 0, today.getFullYear(), today.getMonth() + 1, today.getDate());
1894
2216
  if (t === null) return false;
1895
2217
  const weekStart = t - today.getDay();
1896
- const date = _chunkC7P22VIPcjs.serialToDate.call(void 0, d);
2218
+ const date = _chunkEWCOFSCQcjs.serialToDate.call(void 0, d);
1897
2219
  const monthOffset = (k) => {
1898
2220
  if (!date) return false;
1899
2221
  const m = today.getMonth() + k;
@@ -2196,7 +2518,7 @@ var isBlank3 = (v) => v === "" || v === null || v === void 0;
2196
2518
  function valueText2(value) {
2197
2519
  if (isBlank3(value)) return "";
2198
2520
  if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
2199
- if (typeof value === "number") return _chunkC7P22VIPcjs.numberToText.call(void 0, value);
2521
+ if (typeof value === "number") return _chunkEWCOFSCQcjs.numberToText.call(void 0, value);
2200
2522
  return String(value);
2201
2523
  }
2202
2524
  function findDataValidation(rules, row, col) {
@@ -2208,7 +2530,7 @@ function evaluate(sheet, rule, formula, row, col, entered) {
2208
2530
  if (!sheet.evaluateFormula) return "#NAME?";
2209
2531
  try {
2210
2532
  return sheet.evaluateFormula(ruleFormulaAt(formula, ruleAnchor(rule.ranges), row, col), row, col, entered);
2211
- } catch (e7) {
2533
+ } catch (e8) {
2212
2534
  return "#ERROR!";
2213
2535
  }
2214
2536
  }
@@ -2221,7 +2543,7 @@ function getValidationListValues(sheet, rule) {
2221
2543
  const result = evaluate(sheet, rule, f, anchor.row, anchor.col);
2222
2544
  const values = Array.isArray(result) ? result : [result];
2223
2545
  const out = [];
2224
- for (const v of values) if (!isBlank3(v) && !_chunkC7P22VIPcjs.isFormulaError.call(void 0, v)) out.push(valueText2(v));
2546
+ for (const v of values) if (!isBlank3(v) && !_chunkEWCOFSCQcjs.isFormulaError.call(void 0, v)) out.push(valueText2(v));
2225
2547
  return out;
2226
2548
  }
2227
2549
  const unquoted = f.length >= 2 && f.startsWith('"') && f.endsWith('"') ? f.slice(1, -1) : f;
@@ -2250,7 +2572,7 @@ function compareOp(op, n, a, b) {
2250
2572
  }
2251
2573
  function bound(sheet, rule, text, row, col, entered) {
2252
2574
  if (text === void 0 || text.trim() === "") return null;
2253
- const value = text.trim().startsWith("=") ? first(evaluate(sheet, rule, text, row, col, entered)) : _chunkC7P22VIPcjs.parseCellInput.call(void 0, text.trim()).value;
2575
+ const value = text.trim().startsWith("=") ? first(evaluate(sheet, rule, text, row, col, entered)) : _chunkEWCOFSCQcjs.parseCellInput.call(void 0, text.trim()).value;
2254
2576
  return conditionalFormatNumber(value);
2255
2577
  }
2256
2578
  function satisfies(sheet, rule, row, col, value, text, entered) {
@@ -2261,18 +2583,18 @@ function satisfies(sheet, rule, row, col, value, text, entered) {
2261
2583
  case "list": {
2262
2584
  const typed = text.trim().toLowerCase();
2263
2585
  return getValidationListValues(sheet, rule).some(
2264
- (item) => item.toLowerCase() === typed || _chunkC7P22VIPcjs.compareValues.call(void 0, _chunkC7P22VIPcjs.parseCellInput.call(void 0, item).value, value) === 0
2586
+ (item) => item.toLowerCase() === typed || _chunkEWCOFSCQcjs.compareValues.call(void 0, _chunkEWCOFSCQcjs.parseCellInput.call(void 0, item).value, value) === 0
2265
2587
  );
2266
2588
  }
2267
2589
  case "custom":
2268
- return _chunkC7P22VIPcjs.toBoolean.call(void 0, first(evaluate(sheet, rule, _nullishCoalesce(rule.formula1, () => ( "")), row, col, entered))) === true;
2590
+ return _chunkEWCOFSCQcjs.toBoolean.call(void 0, first(evaluate(sheet, rule, _nullishCoalesce(rule.formula1, () => ( "")), row, col, entered))) === true;
2269
2591
  case "textLength":
2270
2592
  return compareOp(op, Array.from(text).length, bound(sheet, rule, rule.formula1, row, col, entered), bound(sheet, rule, rule.formula2, row, col, entered));
2271
2593
  case "whole":
2272
2594
  case "decimal":
2273
2595
  case "date":
2274
2596
  case "time": {
2275
- if (_chunkC7P22VIPcjs.isFormulaError.call(void 0, value)) return false;
2597
+ if (_chunkEWCOFSCQcjs.isFormulaError.call(void 0, value)) return false;
2276
2598
  let n = conditionalFormatNumber(value);
2277
2599
  if (n === null) return false;
2278
2600
  if (rule.type === "whole" && !Number.isInteger(n)) return false;
@@ -2301,7 +2623,7 @@ function validateCellInput(sheet, rules, row, col, input) {
2301
2623
  value = first(sheet.evaluateFormula ? safeEval(sheet, input, row, col) : "#NAME?");
2302
2624
  text = valueText2(value);
2303
2625
  } else {
2304
- value = _chunkC7P22VIPcjs.parseCellInput.call(void 0, input).value;
2626
+ value = _chunkEWCOFSCQcjs.parseCellInput.call(void 0, input).value;
2305
2627
  if (input.startsWith("'")) text = input.slice(1);
2306
2628
  }
2307
2629
  const entered = { row, col, value };
@@ -2310,13 +2632,13 @@ function validateCellInput(sheet, rules, row, col, input) {
2310
2632
  function safeEval(sheet, formula, row, col) {
2311
2633
  try {
2312
2634
  return sheet.evaluateFormula(formula, row, col);
2313
- } catch (e8) {
2635
+ } catch (e9) {
2314
2636
  return "#ERROR!";
2315
2637
  }
2316
2638
  }
2317
2639
  function findInvalidCells(sheet, rules) {
2318
2640
  const out = [];
2319
- if (!_optionalChain([rules, 'optionalAccess', _56 => _56.length])) return out;
2641
+ if (!_optionalChain([rules, 'optionalAccess', _75 => _75.length])) return out;
2320
2642
  const seen = /* @__PURE__ */ new Set();
2321
2643
  for (const rule of rules) {
2322
2644
  if (rule.type === "any") continue;
@@ -2327,7 +2649,7 @@ function findInvalidCells(sheet, rules) {
2327
2649
  if (findDataValidation(rules, row, col) !== rule) return;
2328
2650
  const value = sheet.getValue(row, col);
2329
2651
  if (isBlank3(value)) return;
2330
- const raw = _optionalChain([sheet, 'access', _57 => _57.getInput, 'optionalCall', _58 => _58(row, col)]);
2652
+ const raw = _optionalChain([sheet, 'access', _76 => _76.getInput, 'optionalCall', _77 => _77(row, col)]);
2331
2653
  const text = raw !== void 0 && raw !== "" && !raw.startsWith("=") ? raw.startsWith("'") ? raw.slice(1) : raw : valueText2(value);
2332
2654
  if (!satisfies(sheet, rule, row, col, value, text)) out.push({ row, col });
2333
2655
  });
@@ -2341,8 +2663,8 @@ var DEFAULT_COLUMN_WIDTH = 130;
2341
2663
  var clampCount = (value, max) => Math.max(0, Math.min(Math.floor(_nullishCoalesce(value, () => ( 0))), max));
2342
2664
  function getTableSections(doc) {
2343
2665
  const total = doc.rows.length;
2344
- const headerRows = clampCount(_optionalChain([doc, 'access', _59 => _59.table, 'optionalAccess', _60 => _60.headerRowCount]), total);
2345
- const footerRows = clampCount(_optionalChain([doc, 'access', _61 => _61.table, 'optionalAccess', _62 => _62.footerRowCount]), total - headerRows);
2666
+ const headerRows = clampCount(_optionalChain([doc, 'access', _78 => _78.table, 'optionalAccess', _79 => _79.headerRowCount]), total);
2667
+ const footerRows = clampCount(_optionalChain([doc, 'access', _80 => _80.table, 'optionalAccess', _81 => _81.footerRowCount]), total - headerRows);
2346
2668
  return { headerRows, footerRows };
2347
2669
  }
2348
2670
  function getRowSection(doc, rowIndex) {
@@ -2352,8 +2674,8 @@ function getRowSection(doc, rowIndex) {
2352
2674
  return "body";
2353
2675
  }
2354
2676
  function rowPassesFilters(doc, rowIndex, getValue) {
2355
- const filter = _optionalChain([doc, 'access', _63 => _63.table, 'optionalAccess', _64 => _64.filter]);
2356
- if (!_optionalChain([filter, 'optionalAccess', _65 => _65.enabled]) || filter.rules.length === 0) return true;
2677
+ const filter = _optionalChain([doc, 'access', _82 => _82.table, 'optionalAccess', _83 => _83.filter]);
2678
+ if (!_optionalChain([filter, 'optionalAccess', _84 => _84.enabled]) || filter.rules.length === 0) return true;
2357
2679
  if (getRowSection(doc, rowIndex) !== "body") return true;
2358
2680
  return filter.rules.every((rule) => {
2359
2681
  const colIndex = doc.columns.findIndex((c) => c.id === rule.colId);
@@ -2362,7 +2684,7 @@ function rowPassesFilters(doc, rowIndex, getValue) {
2362
2684
  });
2363
2685
  }
2364
2686
  function computeRowGroups(doc, getValue) {
2365
- const grouping = _optionalChain([doc, 'access', _66 => _66.table, 'optionalAccess', _67 => _67.grouping]);
2687
+ const grouping = _optionalChain([doc, 'access', _85 => _85.table, 'optionalAccess', _86 => _86.grouping]);
2366
2688
  if (!grouping) return [];
2367
2689
  const groupCol = doc.columns.findIndex((c) => c.id === grouping.colId);
2368
2690
  if (groupCol === -1) return [];
@@ -2392,9 +2714,9 @@ function computeRowGroups(doc, getValue) {
2392
2714
  }
2393
2715
  function buildSpreadsheetView(doc, getValue) {
2394
2716
  const { headerRows, footerRows } = getTableSections(doc);
2395
- const frozenRows = clampCount(_optionalChain([doc, 'access', _68 => _68.table, 'optionalAccess', _69 => _69.frozenRowCount]), doc.rows.length);
2396
- const frozenCols = clampCount(_optionalChain([doc, 'access', _70 => _70.table, 'optionalAccess', _71 => _71.frozenColumnCount]), doc.columns.length);
2397
- const headerCols = clampCount(_optionalChain([doc, 'access', _72 => _72.table, 'optionalAccess', _73 => _73.headerColumnCount]), doc.columns.length);
2717
+ const frozenRows = clampCount(_optionalChain([doc, 'access', _87 => _87.table, 'optionalAccess', _88 => _88.frozenRowCount]), doc.rows.length);
2718
+ const frozenCols = clampCount(_optionalChain([doc, 'access', _89 => _89.table, 'optionalAccess', _90 => _90.frozenColumnCount]), doc.columns.length);
2719
+ const headerCols = clampCount(_optionalChain([doc, 'access', _91 => _91.table, 'optionalAccess', _92 => _92.headerColumnCount]), doc.columns.length);
2398
2720
  const columns = [];
2399
2721
  let left = 0;
2400
2722
  let hiddenColumnCount = 0;
@@ -2424,7 +2746,7 @@ function buildSpreadsheetView(doc, getValue) {
2424
2746
  if (doc.rows[r].hidden) hiddenRowCount++;
2425
2747
  else if (!rowPassesFilters(doc, r, getValue)) filteredOutCount++;
2426
2748
  }
2427
- const grouping = _optionalChain([doc, 'access', _74 => _74.table, 'optionalAccess', _75 => _75.grouping]);
2749
+ const grouping = _optionalChain([doc, 'access', _93 => _93.table, 'optionalAccess', _94 => _94.grouping]);
2428
2750
  if (grouping && doc.columns.some((c) => c.id === grouping.colId)) {
2429
2751
  for (const group of groups) {
2430
2752
  rows.push({ kind: "group", group });
@@ -2458,7 +2780,7 @@ function buildSpreadsheetView(doc, getValue) {
2458
2780
  while (run < visible.length && positions.get(visible[run]) === positions.get(visible[0]) + run) run++;
2459
2781
  return { first: visible[0], run };
2460
2782
  };
2461
- for (const raw of _nullishCoalesce(_optionalChain([doc, 'access', _76 => _76.table, 'optionalAccess', _77 => _77.merges]), () => ( []))) {
2783
+ for (const raw of _nullishCoalesce(_optionalChain([doc, 'access', _95 => _95.table, 'optionalAccess', _96 => _96.merges]), () => ( []))) {
2462
2784
  const merge = normalizeRange(raw);
2463
2785
  const rowIdx = [];
2464
2786
  const colIdx = [];
@@ -2505,15 +2827,42 @@ 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}`;
2510
- var SpreadsheetController = (_class4 = class {
2511
- __init11() {this.activeCell = null}
2512
- __init12() {this.selectedRange = null}
2513
- __init13() {this.editingCell = null}
2514
- __init14() {this.draftValue = ""}
2515
- __init15() {this.binder = new DataSourceBinder()}
2516
- __init16() {this.depGraph = new DependencyGraph()}
2834
+ var PositionCache = (_class5 = class {constructor() { _class5.prototype.__init22.call(this);_class5.prototype.__init23.call(this); }
2835
+ __init22() {this.map = /* @__PURE__ */ new Map()}
2836
+ __init23() {this.scanned = 0}
2837
+ find(list, id) {
2838
+ const pos = this.map.get(id);
2839
+ if (pos !== void 0 && _optionalChain([list, 'access', _97 => _97[pos], 'optionalAccess', _98 => _98.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 && _optionalChain([list, 'access', _99 => _99[found2], 'optionalAccess', _100 => _100.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
+ }, _class5);
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
+ }
2859
+ var SpreadsheetController = (_class6 = class {
2860
+ __init24() {this.activeCell = null}
2861
+ __init25() {this.selectedRange = null}
2862
+ __init26() {this.editingCell = null}
2863
+ __init27() {this.draftValue = ""}
2864
+ __init28() {this.binder = new DataSourceBinder()}
2865
+ __init29() {this.depGraph = new DependencyGraph()}
2517
2866
 
2518
2867
 
2519
2868
 
@@ -2522,30 +2871,34 @@ var SpreadsheetController = (_class4 = class {
2522
2871
  // Workbook: sheets in tab order, the active one, and the one being worked
2523
2872
  // on (the active sheet, or another sheet while one of its cells is read or
2524
2873
  // evaluated). Everything that uses `this.document` works on `ctx`.
2525
- __init17() {this.runtimes = []}
2874
+ __init30() {this.runtimes = []}
2526
2875
 
2527
2876
 
2528
2877
  // The sheet bound to the primary data source (the first sheet).
2529
- __init18() {this.boundSheetId = null}
2530
- __init19() {this.names = []}
2878
+ __init31() {this.boundSheetId = null}
2879
+ __init32() {this.names = []}
2531
2880
 
2532
2881
 
2533
2882
  // History stack for undo/redo
2534
- __init20() {this.undoStack = []}
2535
- __init21() {this.redoStack = []}
2536
- __init22() {this.isApplyingHistory = false}
2883
+ __init33() {this.undoStack = []}
2884
+ __init34() {this.redoStack = []}
2885
+ __init35() {this.isApplyingHistory = false}
2537
2886
 
2538
2887
  // Set while importing TSV: history and recalculation are done once at the end.
2539
- __init23() {this.batching = false}
2540
- __init24() {this.pendingCommits = []}
2888
+ __init36() {this.batching = false}
2889
+ __init37() {this.pendingCommits = []}
2541
2890
  // Row being evaluated, so [property] references resolve per row.
2542
- __init25() {this.evaluatingRowIndex = null}
2891
+ __init38() {this.evaluatingRowIndex = null}
2543
2892
  // Cell being evaluated, for implicit intersection (`=@A:A`).
2544
- __init26() {this.evaluatingCell = null}
2893
+ __init39() {this.evaluatingCell = null}
2545
2894
  // Dynamic arrays. Spilled values are derived state: they live in the sheet
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
- __init27() {this.spillChanges = null}
2897
+ __init40() {this.spillChanges = null}
2898
+ __init41() {this.recalcStats = { evaluated: 0, planned: 0, passes: 0 }}
2899
+ // Values of large ranges read while running an evaluation plan (see runEvaluationPlan).
2900
+ __init42() {this.astCache = /* @__PURE__ */ new Map()}
2901
+ __init43() {this.rangeCache = null}
2549
2902
  /** The active sheet's document (the sheet the editor shows). */
2550
2903
  get document() {
2551
2904
  return this.ctx.doc;
@@ -2564,18 +2917,18 @@ var SpreadsheetController = (_class4 = class {
2564
2917
  }
2565
2918
  // Conditional formatting is evaluated lazily (getConditionalFormatOverlay).
2566
2919
  // Cell keys whose values changed since, or 'all' to evaluate from scratch.
2567
- __init28() {this.cfEngine = null}
2568
- __init29() {this.cfDirty = "all"}
2920
+ __init44() {this.cfEngine = null}
2921
+ __init45() {this.cfDirty = "all"}
2569
2922
  // Set while a single cell edit notifies, so the change stays incremental.
2570
- __init30() {this.cfIncremental = false}
2923
+ __init46() {this.cfIncremental = false}
2571
2924
  // Value being entered, seen by validation formulas instead of the cell's value.
2572
- __init31() {this.valueOverride = null}
2925
+ __init47() {this.valueOverride = null}
2573
2926
  // "Circle invalid data": on/off and the cached cells.
2574
- __init32() {this.circlesShown = false}
2575
- __init33() {this.invalidCircles = null}
2927
+ __init48() {this.circlesShown = false}
2928
+ __init49() {this.invalidCircles = null}
2576
2929
  /** Invalid input waiting for the user's answer (see `resolveValidationPrompt`). */
2577
- __init34() {this.validationPrompt = null}
2578
- constructor(options = {}) {;_class4.prototype.__init11.call(this);_class4.prototype.__init12.call(this);_class4.prototype.__init13.call(this);_class4.prototype.__init14.call(this);_class4.prototype.__init15.call(this);_class4.prototype.__init16.call(this);_class4.prototype.__init17.call(this);_class4.prototype.__init18.call(this);_class4.prototype.__init19.call(this);_class4.prototype.__init20.call(this);_class4.prototype.__init21.call(this);_class4.prototype.__init22.call(this);_class4.prototype.__init23.call(this);_class4.prototype.__init24.call(this);_class4.prototype.__init25.call(this);_class4.prototype.__init26.call(this);_class4.prototype.__init27.call(this);_class4.prototype.__init28.call(this);_class4.prototype.__init29.call(this);_class4.prototype.__init30.call(this);_class4.prototype.__init31.call(this);_class4.prototype.__init32.call(this);_class4.prototype.__init33.call(this);_class4.prototype.__init34.call(this);_class4.prototype.__init35.call(this);
2930
+ __init50() {this.validationPrompt = null}
2931
+ constructor(options = {}) {;_class6.prototype.__init24.call(this);_class6.prototype.__init25.call(this);_class6.prototype.__init26.call(this);_class6.prototype.__init27.call(this);_class6.prototype.__init28.call(this);_class6.prototype.__init29.call(this);_class6.prototype.__init30.call(this);_class6.prototype.__init31.call(this);_class6.prototype.__init32.call(this);_class6.prototype.__init33.call(this);_class6.prototype.__init34.call(this);_class6.prototype.__init35.call(this);_class6.prototype.__init36.call(this);_class6.prototype.__init37.call(this);_class6.prototype.__init38.call(this);_class6.prototype.__init39.call(this);_class6.prototype.__init40.call(this);_class6.prototype.__init41.call(this);_class6.prototype.__init42.call(this);_class6.prototype.__init43.call(this);_class6.prototype.__init44.call(this);_class6.prototype.__init45.call(this);_class6.prototype.__init46.call(this);_class6.prototype.__init47.call(this);_class6.prototype.__init48.call(this);_class6.prototype.__init49.call(this);_class6.prototype.__init50.call(this);_class6.prototype.__init51.call(this);
2579
2932
  const defaultCols = [
2580
2933
  { id: "col_a", key: "A", title: "A", width: 120, type: "freeform" },
2581
2934
  { id: "col_b", key: "B", title: "B", width: 120, type: "freeform" },
@@ -2589,14 +2942,14 @@ var SpreadsheetController = (_class4 = class {
2589
2942
  type: "freeform"
2590
2943
  }));
2591
2944
  const first2 = {
2592
- id: _optionalChain([options, 'access', _78 => _78.document, 'optionalAccess', _79 => _79.id]) || `sheet_${Date.now()}`,
2593
- name: _optionalChain([options, 'access', _80 => _80.document, 'optionalAccess', _81 => _81.name]) || "Sheet 1",
2594
- columns: _optionalChain([options, 'access', _82 => _82.document, 'optionalAccess', _83 => _83.columns]) || defaultCols,
2595
- rows: _optionalChain([options, 'access', _84 => _84.document, 'optionalAccess', _85 => _85.rows]) || defaultRows,
2596
- cells: _optionalChain([options, 'access', _86 => _86.document, 'optionalAccess', _87 => _87.cells]) || {},
2597
- createdAt: _optionalChain([options, 'access', _88 => _88.document, 'optionalAccess', _89 => _89.createdAt]) || Date.now(),
2598
- updatedAt: _optionalChain([options, 'access', _90 => _90.document, 'optionalAccess', _91 => _91.updatedAt]) || Date.now(),
2599
- ..._optionalChain([options, 'access', _92 => _92.document, 'optionalAccess', _93 => _93.table]) ? { table: options.document.table } : {}
2945
+ id: _optionalChain([options, 'access', _101 => _101.document, 'optionalAccess', _102 => _102.id]) || `sheet_${Date.now()}`,
2946
+ name: _optionalChain([options, 'access', _103 => _103.document, 'optionalAccess', _104 => _104.name]) || "Sheet 1",
2947
+ columns: _optionalChain([options, 'access', _105 => _105.document, 'optionalAccess', _106 => _106.columns]) || defaultCols,
2948
+ rows: _optionalChain([options, 'access', _107 => _107.document, 'optionalAccess', _108 => _108.rows]) || defaultRows,
2949
+ cells: _optionalChain([options, 'access', _109 => _109.document, 'optionalAccess', _110 => _110.cells]) || {},
2950
+ createdAt: _optionalChain([options, 'access', _111 => _111.document, 'optionalAccess', _112 => _112.createdAt]) || Date.now(),
2951
+ updatedAt: _optionalChain([options, 'access', _113 => _113.document, 'optionalAccess', _114 => _114.updatedAt]) || Date.now(),
2952
+ ..._optionalChain([options, 'access', _115 => _115.document, 'optionalAccess', _116 => _116.table]) ? { table: options.document.table } : {}
2600
2953
  };
2601
2954
  if (options.workbook && options.workbook.sheets.length > 0) {
2602
2955
  this.setWorkbookState(options.workbook, false);
@@ -2615,9 +2968,9 @@ var SpreadsheetController = (_class4 = class {
2615
2968
  if (options.dataSources) {
2616
2969
  options.dataSources.forEach((ds) => this.binder.register(ds));
2617
2970
  }
2618
- this.evaluator = new (0, _chunkC7P22VIPcjs.FormulaEvaluator)({
2971
+ this.evaluator = new (0, _chunkEWCOFSCQcjs.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),
@@ -2744,7 +3097,7 @@ var SpreadsheetController = (_class4 = class {
2744
3097
  for (const rt of this.runtimes) this.withSheet(rt, () => this.rederiveTypedValues());
2745
3098
  this.recalculateAll();
2746
3099
  this.notifyChange();
2747
- _optionalChain([this, 'access', _94 => _94.onSelectionChange, 'optionalCall', _95 => _95(this.activeCell, this.selectedRange)]);
3100
+ _optionalChain([this, 'access', _117 => _117.onSelectionChange, 'optionalCall', _118 => _118(this.activeCell, this.selectedRange)]);
2748
3101
  }
2749
3102
  workbookSnapshot() {
2750
3103
  return {
@@ -2810,13 +3163,13 @@ var SpreadsheetController = (_class4 = class {
2810
3163
  * number) when `format` is unset or `text` (the default of new columns).
2811
3164
  */
2812
3165
  typedCell(raw, format, col, existing) {
2813
- const parsed = _chunkC7P22VIPcjs.parseCellInput.call(void 0, raw);
3166
+ const parsed = _chunkEWCOFSCQcjs.parseCellInput.call(void 0, raw);
2814
3167
  const fixed = this.fixedNumberFormat(col, existing);
2815
3168
  const numberFormat = _nullishCoalesce(fixed, () => ( parsed.numberFormat));
2816
3169
  return {
2817
3170
  value: parsed.value,
2818
3171
  ...numberFormat ? { numberFormat } : {},
2819
- ..._optionalChain([existing, 'optionalAccess', _96 => _96.explicitNumberFormat]) ? { explicitNumberFormat: true } : {},
3172
+ ..._optionalChain([existing, 'optionalAccess', _119 => _119.explicitNumberFormat]) ? { explicitNumberFormat: true } : {},
2820
3173
  format: fixed === void 0 && parsed.cellFormat && (!format || format === "text") ? parsed.cellFormat : format
2821
3174
  };
2822
3175
  }
@@ -2825,14 +3178,14 @@ var SpreadsheetController = (_class4 = class {
2825
3178
  * one, else its column's. Undefined lets typed input choose one.
2826
3179
  */
2827
3180
  fixedNumberFormat(col, existing) {
2828
- if (_optionalChain([existing, 'optionalAccess', _97 => _97.explicitNumberFormat]) && existing.numberFormat !== void 0) return existing.numberFormat;
2829
- return _optionalChain([col, 'optionalAccess', _98 => _98.numberFormat]) && !isGeneralNumberFormat(col.numberFormat) ? col.numberFormat : void 0;
3181
+ if (_optionalChain([existing, 'optionalAccess', _120 => _120.explicitNumberFormat]) && existing.numberFormat !== void 0) return existing.numberFormat;
3182
+ return _optionalChain([col, 'optionalAccess', _121 => _121.numberFormat]) && !isGeneralNumberFormat(col.numberFormat) ? col.numberFormat : void 0;
2830
3183
  }
2831
3184
  /** `numberFormat` / `explicitNumberFormat` fields for a formula, bound or spilled cell. */
2832
3185
  fixedNumberFormatFields(col, existing) {
2833
3186
  const numberFormat = this.fixedNumberFormat(col, existing);
2834
3187
  if (numberFormat === void 0) return {};
2835
- return _optionalChain([existing, 'optionalAccess', _99 => _99.explicitNumberFormat]) ? { numberFormat, explicitNumberFormat: true } : { numberFormat };
3188
+ return _optionalChain([existing, 'optionalAccess', _122 => _122.explicitNumberFormat]) ? { numberFormat, explicitNumberFormat: true } : { numberFormat };
2836
3189
  }
2837
3190
  /**
2838
3191
  * Typed input for a bound field, coerced by the field's declared type:
@@ -2842,9 +3195,9 @@ var SpreadsheetController = (_class4 = class {
2842
3195
  */
2843
3196
  boundInputValue(dataSourceId, field, raw) {
2844
3197
  if (raw.startsWith("=")) return raw;
2845
- const type = _optionalChain([this, 'access', _100 => _100.binder, 'access', _101 => _101.getDataSource, 'call', _102 => _102(dataSourceId), 'optionalAccess', _103 => _103.getFields, 'call', _104 => _104(), 'access', _105 => _105.find, 'call', _106 => _106((f) => f.key === field), 'optionalAccess', _107 => _107.type]);
3198
+ const type = _optionalChain([this, 'access', _123 => _123.binder, 'access', _124 => _124.getDataSource, 'call', _125 => _125(dataSourceId), 'optionalAccess', _126 => _126.getFields, 'call', _127 => _127(), 'access', _128 => _128.find, 'call', _129 => _129((f) => f.key === field), 'optionalAccess', _130 => _130.type]);
2846
3199
  if (type !== "number" && type !== "boolean" && type !== "date") return raw;
2847
- const parsed = _chunkC7P22VIPcjs.parseCellInput.call(void 0, raw);
3200
+ const parsed = _chunkEWCOFSCQcjs.parseCellInput.call(void 0, raw);
2848
3201
  if (type === "number" && parsed.type === "number") return parsed.value;
2849
3202
  if (type === "boolean" && parsed.type === "boolean") return parsed.value;
2850
3203
  if (type === "date" && parsed.type === "date") return parsed.value;
@@ -2873,10 +3226,12 @@ var SpreadsheetController = (_class4 = class {
2873
3226
  r.index = idx;
2874
3227
  });
2875
3228
  this.document.columns.forEach((c, idx) => {
2876
- c.key = _chunkC7P22VIPcjs.indexToColName.call(void 0, idx);
3229
+ c.key = _chunkEWCOFSCQcjs.indexToColName.call(void 0, idx);
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,18 +3240,48 @@ var SpreadsheetController = (_class4 = class {
2885
3240
  if (typeof rowIdOrIndex === "number") {
2886
3241
  return this.document.rows[rowIdOrIndex];
2887
3242
  }
2888
- return this.document.rows.find((r) => r.id === rowIdOrIndex);
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[_chunkEWCOFSCQcjs.colNameToIndex.call(void 0, 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
- const existing = _optionalChain([this, 'access', _108 => _108.document, 'access', _109 => _109.cells, 'access', _110 => _110[rowId], 'optionalAccess', _111 => _111[colId]]);
3263
+ const existing = _optionalChain([this, 'access', _131 => _131.document, 'access', _132 => _132.cells, 'access', _133 => _133[rowId], 'optionalAccess', _134 => _134[colId]]);
2892
3264
  if (existing && existing.raw !== "") return existing;
2893
3265
  if (this.spillCells.size > 0) {
2894
3266
  const spilled = this.spillCells.get(spillCellId(rowId, colId));
2895
3267
  if (spilled) return spilled;
2896
3268
  }
2897
3269
  if (existing && !existing.explicitNumberFormat) return existing;
2898
- const col = this.document.columns.find((c) => c.id === colId);
2899
- const row = this.document.rows.find((r) => r.id === rowId);
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 = _optionalChain([this, 'access', _135 => _135.document, 'access', _136 => _136.cells, 'access', _137 => _137[row.id], 'optionalAccess', _138 => _138[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;
@@ -2925,29 +3310,30 @@ var SpreadsheetController = (_class4 = class {
2925
3310
  ...this.fixedNumberFormatFields(col, void 0)
2926
3311
  };
2927
3312
  }
2928
- return { raw: "", value: "", format: _optionalChain([col, 'optionalAccess', _112 => _112.format]), ...this.fixedNumberFormatFields(col, void 0) };
3313
+ return { raw: "", value: "", format: _optionalChain([col, 'optionalAccess', _139 => _139.format]), ...this.fixedNumberFormatFields(col, void 0) };
2929
3314
  }
2930
3315
  setCellValue(rowId, colId, raw) {
2931
- this.recordSnapshot();
3316
+ this.recordCellSnapshot(rowId, colId);
2932
3317
  if (!this.document.cells[rowId]) {
2933
3318
  this.document.cells[rowId] = {};
2934
3319
  }
2935
- const col = this.document.columns.find((c) => c.id === colId);
2936
- const row = this.document.rows.find((r) => r.id === rowId);
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;
2940
- if (_optionalChain([col, 'optionalAccess', _113 => _113.type]) === "bound" && col.binding && _optionalChain([row, 'optionalAccess', _114 => _114.recordId])) {
3325
+ if (_optionalChain([col, 'optionalAccess', _140 => _140.type]) === "bound" && col.binding && _optionalChain([row, 'optionalAccess', _141 => _141.recordId])) {
2941
3326
  const value = this.boundInputValue(col.binding.dataSource, col.binding.field, raw);
2942
3327
  this.binder.commitBoundValue(col.binding.dataSource, row.recordId, col.binding.field, value);
2943
3328
  cell = { raw, value, format: col.format, ...this.fixedNumberFormatFields(col, existing) };
2944
3329
  } else if (isFormula) {
2945
- cell = { raw, value: raw, format: _optionalChain([col, 'optionalAccess', _115 => _115.format]), ...this.fixedNumberFormatFields(col, existing) };
3330
+ cell = { raw, value: raw, format: _optionalChain([col, 'optionalAccess', _142 => _142.format]), ...this.fixedNumberFormatFields(col, existing) };
2946
3331
  } else {
2947
- cell = { raw, ...this.typedCell(raw, _optionalChain([col, 'optionalAccess', _116 => _116.format]), col, existing) };
3332
+ cell = { raw, ...this.typedCell(raw, _optionalChain([col, 'optionalAccess', _143 => _143.format]), col, existing) };
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) {
@@ -2962,12 +3348,12 @@ var SpreadsheetController = (_class4 = class {
2962
3348
  } finally {
2963
3349
  this.cfIncremental = false;
2964
3350
  }
2965
- const evaluatedVal = _optionalChain([this, 'access', _117 => _117.document, 'access', _118 => _118.cells, 'access', _119 => _119[rowId], 'access', _120 => _120[colId], 'optionalAccess', _121 => _121.value]);
2966
- _optionalChain([this, 'access', _122 => _122.onCellCommit, 'optionalCall', _123 => _123(rowId, colId, raw, evaluatedVal)]);
3351
+ const evaluatedVal = _optionalChain([this, 'access', _144 => _144.document, 'access', _145 => _145.cells, 'access', _146 => _146[rowId], 'access', _147 => _147[colId], 'optionalAccess', _148 => _148.value]);
3352
+ _optionalChain([this, 'access', _149 => _149.onCellCommit, 'optionalCall', _150 => _150(rowId, colId, raw, evaluatedVal)]);
2967
3353
  }
2968
3354
  getCellKey(rowId, colId) {
2969
- const row = this.document.rows.find((r) => r.id === rowId);
2970
- const col = this.document.columns.find((c) => c.id === colId);
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 = (_class4 = 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 = _chunkC7P22VIPcjs.parseFormula.call(void 0, formula);
2998
- } catch (e9) {
3384
+ ast = this.parseCached(formula);
3385
+ } catch (e10) {
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
- this.collectDependencies(ast, this.ctx, deps, /* @__PURE__ */ new Set());
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 = _chunkEWCOFSCQcjs.parseFormula.call(void 0, 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
- /** Adds the qualified keys `ast` depends on, with unqualified references on `home`. */
3007
- collectDependencies(ast, home, out, seenNames) {
3008
- for (const dep of extractCellDependencies(ast)) {
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 = (_class4 = 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(dep.row === 0 ? columnDependencyKey(`${rt.doc.id}!${dep.col}`) : this.qualify(`${dep.col}${dep.row}`, rt));
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 && _chunkC7P22VIPcjs.getFormulaFunction.call(void 0, used.name)) continue;
3447
+ if (used.call) {
3448
+ const fn = _chunkEWCOFSCQcjs.getFormulaFunction.call(void 0, 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,8 +3461,8 @@ var SpreadsheetController = (_class4 = class {
3032
3461
  if (seenNames.has(seenKey)) continue;
3033
3462
  seenNames.add(seenKey);
3034
3463
  try {
3035
- this.collectDependencies(_chunkC7P22VIPcjs.parseFormula.call(void 0, def.refersTo), nameHome, out, seenNames);
3036
- } catch (e10) {
3464
+ this.collectDependencies(_chunkEWCOFSCQcjs.parseFormula.call(void 0, def.refersTo), nameHome, out, seenNames, volatile);
3465
+ } catch (e11) {
3037
3466
  }
3038
3467
  }
3039
3468
  }
@@ -3047,18 +3476,49 @@ var SpreadsheetController = (_class4 = class {
3047
3476
  recalculateKeys(keys, includeDependents) {
3048
3477
  let pending = Array.from(keys);
3049
3478
  let withDependents = includeDependents;
3050
- for (let pass = 0; pass < MAX_SPILL_PASSES && pending.length > 0; pass++) {
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.runEvaluationPlan(this.depGraph.getEvaluationPlan(pending, { includeDependents: withDependents }));
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 = (_class4 = 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
- for (const col of this.document.columns) {
3077
- const cell = _nullishCoalesce(_optionalChain([this, 'access', _124 => _124.document, 'access', _125 => _125.cells, 'access', _126 => _126[row.id], 'optionalAccess', _127 => _127[col.id]]), () => ( this.getDerivedCell(row, col)));
3078
- if (cell.raw.startsWith("=")) {
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 = _nullishCoalesce(_optionalChain([stored, 'optionalAccess', _151 => _151[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 = (_class4 = class {
3087
3551
  this.recalculateKeys(formulaKeys, false);
3088
3552
  }
3089
3553
  runEvaluationPlan(plan) {
3090
- for (const qualified of plan.order) {
3091
- const located = this.unqualify(qualified);
3092
- if (!located) continue;
3093
- const onActive = located.rt === this.active;
3094
- this.withSheet(located.rt, () => {
3095
- if (onActive) this.markValueChanged(located.key);
3096
- if (plan.cyclic.has(qualified)) {
3097
- this.markCycle(located.key);
3098
- } else {
3099
- this.evalSingleCellByKey(located.key);
3100
- }
3101
- if (onActive) this.markValueChanged(located.key);
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 = _nullishCoalesce(_optionalChain([this, 'access', _152 => _152.spillChanges, 'optionalAccess', _153 => _153.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) || (_nullishCoalesce(_optionalChain([this, 'access', _154 => _154.spillChanges, 'optionalAccess', _155 => _155.size]), () => ( 0))) !== spillChanges;
3571
+ this.invalidateRangeCache(located.rt.doc.id, spilled ? null : _chunkEWCOFSCQcjs.colNameToIndex.call(void 0, 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.document.columns.find((c) => c.key.toUpperCase() === match[1].toUpperCase());
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 = (_class4 = 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 deps = this.depGraph.getDependencies(qualified);
3121
- const selfReference = deps.includes(qualified) || deps.includes(columnDependencyKey(`${this.document.id}!${col.key}`));
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 = (_class4 = 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.document.columns.find((c) => c.key.toUpperCase() === colName);
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 = (_class4 = class {
3145
3627
  try {
3146
3628
  this.evaluatingRowIndex = rowNum - 1;
3147
3629
  this.evaluatingCell = { col: colName, row: rowNum };
3148
- const result = this.evaluator.evaluateWithDiagnostics(cell.raw);
3630
+ this.recalcStats.evaluated++;
3631
+ let parsed = cell.raw;
3632
+ try {
3633
+ parsed = this.parseCached(cell.raw);
3634
+ } catch (e12) {
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.indexOf(col), result.array);
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!";
@@ -3182,7 +3670,7 @@ var SpreadsheetController = (_class4 = class {
3182
3670
  placeSpill(anchor, row, col, array) {
3183
3671
  const { rows, cols } = array;
3184
3672
  const area = { startRow: row, startCol: col, endRow: row + rows - 1, endCol: col + cols - 1 };
3185
- const label = `${_chunkC7P22VIPcjs.indexToColName.call(void 0, area.startCol)}${area.startRow + 1}:${_chunkC7P22VIPcjs.indexToColName.call(void 0, area.endCol)}${area.endRow + 1}`;
3673
+ const label = `${_chunkEWCOFSCQcjs.indexToColName.call(void 0, area.startCol)}${area.startRow + 1}:${_chunkEWCOFSCQcjs.indexToColName.call(void 0, area.endCol)}${area.endRow + 1}`;
3186
3674
  let reason = null;
3187
3675
  const blockers = [];
3188
3676
  if (area.endRow >= this.document.rows.length || area.endCol >= this.document.columns.length) {
@@ -3196,7 +3684,7 @@ var SpreadsheetController = (_class4 = class {
3196
3684
  if (r === row && c === col) continue;
3197
3685
  const rowObj = this.document.rows[r];
3198
3686
  const key = `${column.key}${r + 1}`.toUpperCase();
3199
- const stored = _optionalChain([this, 'access', _128 => _128.document, 'access', _129 => _129.cells, 'access', _130 => _130[rowObj.id], 'optionalAccess', _131 => _131[column.id]]);
3687
+ const stored = _optionalChain([this, 'access', _156 => _156.document, 'access', _157 => _157.cells, 'access', _158 => _158[rowObj.id], 'optionalAccess', _159 => _159[column.id]]);
3200
3688
  const raw = stored ? stored.raw : this.getDerivedCell(rowObj, column).raw;
3201
3689
  const owner = this.spilledBy.get(key);
3202
3690
  if (raw !== "") {
@@ -3215,6 +3703,7 @@ var SpreadsheetController = (_class4 = 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];
@@ -3228,16 +3717,18 @@ var SpreadsheetController = (_class4 = class {
3228
3717
  value,
3229
3718
  format: column.format,
3230
3719
  spillAnchor: anchor,
3231
- ...this.fixedNumberFormatFields(column, _optionalChain([this, 'access', _132 => _132.document, 'access', _133 => _133.cells, 'access', _134 => _134[rowObj.id], 'optionalAccess', _135 => _135[column.id]]))
3720
+ ...this.fixedNumberFormatFields(column, _optionalChain([this, 'access', _160 => _160.document, 'access', _161 => _161.cells, 'access', _162 => _162[rowObj.id], 'optionalAccess', _163 => _163[column.id]]))
3232
3721
  };
3233
- if (_chunkC7P22VIPcjs.isFormulaError.call(void 0, value)) {
3722
+ if (_chunkEWCOFSCQcjs.isFormulaError.call(void 0, value)) {
3234
3723
  display.error = value;
3235
- display.errorMessage = _chunkC7P22VIPcjs.describeFormulaError.call(void 0, value);
3724
+ display.errorMessage = _chunkEWCOFSCQcjs.describeFormulaError.call(void 0, value);
3236
3725
  }
3237
3726
  this.spillCells.set(spillCellId(rowObj.id, column.id), display);
3238
3727
  if (this.spilledBy.get(key) !== anchor) {
3239
3728
  this.spilledBy.set(key, anchor);
3240
- _optionalChain([this, 'access', _136 => _136.spillChanges, 'optionalAccess', _137 => _137.add, 'call', _138 => _138(this.qualify(key))]);
3729
+ _optionalChain([this, 'access', _164 => _164.spillChanges, 'optionalAccess', _165 => _165.add, 'call', _166 => _166(this.qualify(key))]);
3730
+ } else if (!linked.has(this.qualify(key))) {
3731
+ _optionalChain([this, 'access', _167 => _167.spillChanges, 'optionalAccess', _168 => _168.add, 'call', _169 => _169(this.qualify(key))]);
3241
3732
  }
3242
3733
  cells.push({ key, rowId: rowObj.id, colId: column.id });
3243
3734
  }
@@ -3266,13 +3757,13 @@ var SpreadsheetController = (_class4 = class {
3266
3757
  if (this.spilledBy.get(cell.key) !== anchor) return;
3267
3758
  this.spilledBy.delete(cell.key);
3268
3759
  this.spillCells.delete(spillCellId(cell.rowId, cell.colId));
3269
- _optionalChain([this, 'access', _139 => _139.spillChanges, 'optionalAccess', _140 => _140.add, 'call', _141 => _141(this.qualify(cell.key))]);
3760
+ _optionalChain([this, 'access', _170 => _170.spillChanges, 'optionalAccess', _171 => _171.add, 'call', _172 => _172(this.qualify(cell.key))]);
3270
3761
  }
3271
3762
  resolveSpillByCoord(colStr, rowNum) {
3272
3763
  const record = this.spills.get(`${colStr}${rowNum}`.toUpperCase());
3273
3764
  if (record) return { rows: record.rows, cols: record.cols, values: record.values };
3274
3765
  const value = this.resolveCellByCoord(colStr, rowNum);
3275
- return _chunkC7P22VIPcjs.isFormulaError.call(void 0, value) ? value : "#REF!";
3766
+ return _chunkEWCOFSCQcjs.isFormulaError.call(void 0, value) ? value : "#REF!";
3276
3767
  }
3277
3768
  toSpill(record) {
3278
3769
  const row = this.document.rows[record.row];
@@ -3295,7 +3786,7 @@ var SpreadsheetController = (_class4 = class {
3295
3786
  */
3296
3787
  getSpillAt(row, col) {
3297
3788
  if (this.spills.size === 0) return null;
3298
- const key = `${_chunkC7P22VIPcjs.indexToColName.call(void 0, col)}${row + 1}`;
3789
+ const key = `${_chunkEWCOFSCQcjs.indexToColName.call(void 0, col)}${row + 1}`;
3299
3790
  const record = _nullishCoalesce(this.spills.get(key), () => ( this.spills.get(_nullishCoalesce(this.spilledBy.get(key), () => ( "")))));
3300
3791
  return record ? this.toSpill(record) : null;
3301
3792
  }
@@ -3317,40 +3808,69 @@ var SpreadsheetController = (_class4 = class {
3317
3808
  return false;
3318
3809
  }
3319
3810
  resolveCellByCoord(colStr, rowNum) {
3320
- const col = this.document.columns.find((c) => c.key.toUpperCase() === colStr.toUpperCase());
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 _nullishCoalesce(this.getCell(row.id, col.id).value, () => ( ""));
3816
+ return _nullishCoalesce(this.cellOf(row, col).value, () => ( ""));
3326
3817
  }
3327
- resolveRangeByCoords(sCol, sRow, eCol, eRow) {
3328
- const startC = _chunkC7P22VIPcjs.colNameToIndex.call(void 0, sCol);
3329
- const endC = _chunkC7P22VIPcjs.colNameToIndex.call(void 0, eCol);
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) {
3824
+ const startC = _chunkEWCOFSCQcjs.colNameToIndex.call(void 0, sCol);
3825
+ const endC = _chunkEWCOFSCQcjs.colNameToIndex.call(void 0, 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
- const maxR = Math.min(Math.max(sRow, eRow), _chunkC7P22VIPcjs.WHOLE_COLUMN_END_ROW, this.document.rows.length);
3829
+ const maxR = Math.min(Math.max(sRow, eRow), _chunkEWCOFSCQcjs.WHOLE_COLUMN_END_ROW, this.document.rows.length);
3830
+ const skipBlanks = _optionalChain([options, 'optionalAccess', _173 => _173.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 colName = _chunkC7P22VIPcjs.indexToColName.call(void 0, c);
3337
- const col = this.document.columns.find((colDef) => colDef.key.toUpperCase() === colName);
3842
+ const col = this.columnByKey(_chunkEWCOFSCQcjs.indexToColName.call(void 0, 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 = this.document.rows[r - 1];
3848
+ const row = rows[r - 1];
3342
3849
  if (!row) continue;
3850
+ let value;
3343
3851
  if (override && override.row === r - 1) {
3344
- results.push(override.value);
3345
- continue;
3852
+ value = override.value;
3853
+ } else {
3854
+ const stored = _optionalChain([cells, 'access', _174 => _174[row.id], 'optionalAccess', _175 => _175[colId]]);
3855
+ if (stored && stored.raw !== "") value = stored.value;
3856
+ else {
3857
+ const spilled = _optionalChain([spills, 'optionalAccess', _176 => _176.get, 'call', _177 => _177(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
- results.push(_nullishCoalesce(this.getCell(row.id, col.id).value, () => ( "")));
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) {
3353
- const rowIndex = _nullishCoalesce(this.evaluatingRowIndex, () => ( _optionalChain([this, 'access', _142 => _142.activeCell, 'optionalAccess', _143 => _143.row])));
3873
+ const rowIndex = _nullishCoalesce(this.evaluatingRowIndex, () => ( _optionalChain([this, 'access', _178 => _178.activeCell, 'optionalAccess', _179 => _179.row])));
3354
3874
  if (rowIndex === void 0 || rowIndex === null) return 0;
3355
3875
  const row = this.document.rows[rowIndex];
3356
3876
  if (!row || !row.recordId || !this.primaryDataSourceId) return 0;
@@ -3368,8 +3888,8 @@ var SpreadsheetController = (_class4 = class {
3368
3888
  this.activeCell = {
3369
3889
  row: activeRow,
3370
3890
  col: activeCol,
3371
- rowId: _optionalChain([rowObj, 'optionalAccess', _144 => _144.id]),
3372
- colId: _optionalChain([colObj, 'optionalAccess', _145 => _145.id])
3891
+ rowId: _optionalChain([rowObj, 'optionalAccess', _180 => _180.id]),
3892
+ colId: _optionalChain([colObj, 'optionalAccess', _181 => _181.id])
3373
3893
  };
3374
3894
  if (extendSelection && this.selectedRange) {
3375
3895
  this.selectedRange = this.expandRangeToMerges({
@@ -3388,16 +3908,16 @@ var SpreadsheetController = (_class4 = class {
3388
3908
  endCol: clampedCol
3389
3909
  };
3390
3910
  }
3391
- _optionalChain([this, 'access', _146 => _146.onSelectionChange, 'optionalCall', _147 => _147(this.activeCell, this.selectedRange)]);
3911
+ _optionalChain([this, 'access', _182 => _182.onSelectionChange, 'optionalCall', _183 => _183(this.activeCell, this.selectedRange)]);
3392
3912
  }
3393
3913
  selectRange(range) {
3394
3914
  this.selectedRange = range;
3395
- _optionalChain([this, 'access', _148 => _148.onSelectionChange, 'optionalCall', _149 => _149(this.activeCell, this.selectedRange)]);
3915
+ _optionalChain([this, 'access', _184 => _184.onSelectionChange, 'optionalCall', _185 => _185(this.activeCell, this.selectedRange)]);
3396
3916
  }
3397
3917
  startEditing(row, col, initialValue) {
3398
- this.selectCell(_nullishCoalesce(_nullishCoalesce(row, () => ( _optionalChain([this, 'access', _150 => _150.activeCell, 'optionalAccess', _151 => _151.row]))), () => ( 0)), _nullishCoalesce(_nullishCoalesce(col, () => ( _optionalChain([this, 'access', _152 => _152.activeCell, 'optionalAccess', _153 => _153.col]))), () => ( 0)));
3399
- const targetRow = _nullishCoalesce(_optionalChain([this, 'access', _154 => _154.activeCell, 'optionalAccess', _155 => _155.row]), () => ( 0));
3400
- const targetCol = _nullishCoalesce(_optionalChain([this, 'access', _156 => _156.activeCell, 'optionalAccess', _157 => _157.col]), () => ( 0));
3918
+ this.selectCell(_nullishCoalesce(_nullishCoalesce(row, () => ( _optionalChain([this, 'access', _186 => _186.activeCell, 'optionalAccess', _187 => _187.row]))), () => ( 0)), _nullishCoalesce(_nullishCoalesce(col, () => ( _optionalChain([this, 'access', _188 => _188.activeCell, 'optionalAccess', _189 => _189.col]))), () => ( 0)));
3919
+ const targetRow = _nullishCoalesce(_optionalChain([this, 'access', _190 => _190.activeCell, 'optionalAccess', _191 => _191.row]), () => ( 0));
3920
+ const targetCol = _nullishCoalesce(_optionalChain([this, 'access', _192 => _192.activeCell, 'optionalAccess', _193 => _193.col]), () => ( 0));
3401
3921
  const rowObj = this.document.rows[targetRow];
3402
3922
  const colObj = this.document.columns[targetCol];
3403
3923
  if (!rowObj || !colObj) return;
@@ -3438,8 +3958,8 @@ var SpreadsheetController = (_class4 = 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.findIndex((r) => r.id === rowId);
3442
- const col = this.document.columns.findIndex((c) => c.id === colId);
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;
@@ -3448,7 +3968,7 @@ var SpreadsheetController = (_class4 = class {
3448
3968
  }
3449
3969
  /** Validates input; when it fails, records and returns the prompt. */
3450
3970
  checkInput(rowId, colId, row, col, raw, editing) {
3451
- if (!_optionalChain([this, 'access', _158 => _158.document, 'access', _159 => _159.table, 'optionalAccess', _160 => _160.dataValidations, 'optionalAccess', _161 => _161.length])) return null;
3971
+ if (!_optionalChain([this, 'access', _194 => _194.document, 'access', _195 => _195.table, 'optionalAccess', _196 => _196.dataValidations, 'optionalAccess', _197 => _197.length])) return null;
3452
3972
  if (this.getCell(rowId, colId).raw === raw) return null;
3453
3973
  const result = this.validateCellInput(row, col, raw);
3454
3974
  if (result.valid) return null;
@@ -3465,7 +3985,7 @@ var SpreadsheetController = (_class4 = class {
3465
3985
  const prompt = this.validationPrompt;
3466
3986
  if (!prompt) return;
3467
3987
  this.validationPrompt = null;
3468
- const editingSameCell = prompt.editing && _optionalChain([this, 'access', _162 => _162.editingCell, 'optionalAccess', _163 => _163.rowId]) === prompt.rowId && _optionalChain([this, 'access', _164 => _164.editingCell, 'optionalAccess', _165 => _165.colId]) === prompt.colId;
3988
+ const editingSameCell = prompt.editing && _optionalChain([this, 'access', _198 => _198.editingCell, 'optionalAccess', _199 => _199.rowId]) === prompt.rowId && _optionalChain([this, 'access', _200 => _200.editingCell, 'optionalAccess', _201 => _201.colId]) === prompt.colId;
3469
3989
  if (action === "accept" && prompt.result.errorStyle !== "stop") {
3470
3990
  this.setCellValue(prompt.rowId, prompt.colId, prompt.raw);
3471
3991
  if (editingSameCell) {
@@ -3512,18 +4032,18 @@ var SpreadsheetController = (_class4 = class {
3512
4032
  // --- Grid Structure Mutations ---
3513
4033
  insertColumn(index, config) {
3514
4034
  this.recordSnapshot();
3515
- const newId = _optionalChain([config, 'optionalAccess', _166 => _166.id]) || this.generateId("col");
4035
+ const newId = _optionalChain([config, 'optionalAccess', _202 => _202.id]) || this.generateId("col");
3516
4036
  const newCol = {
3517
4037
  id: newId,
3518
- key: _chunkC7P22VIPcjs.indexToColName.call(void 0, index),
3519
- title: _optionalChain([config, 'optionalAccess', _167 => _167.title]) || _chunkC7P22VIPcjs.indexToColName.call(void 0, index),
3520
- width: _optionalChain([config, 'optionalAccess', _168 => _168.width]) || 130,
3521
- type: _optionalChain([config, 'optionalAccess', _169 => _169.type]) || "freeform",
3522
- binding: _optionalChain([config, 'optionalAccess', _170 => _170.binding]),
3523
- formula: _optionalChain([config, 'optionalAccess', _171 => _171.formula]),
3524
- format: _optionalChain([config, 'optionalAccess', _172 => _172.format]) || "text",
3525
- ..._optionalChain([config, 'optionalAccess', _173 => _173.numberFormat]) ? { numberFormat: config.numberFormat } : {},
3526
- readOnly: _optionalChain([config, 'optionalAccess', _174 => _174.readOnly])
4038
+ key: _chunkEWCOFSCQcjs.indexToColName.call(void 0, index),
4039
+ title: _optionalChain([config, 'optionalAccess', _203 => _203.title]) || _chunkEWCOFSCQcjs.indexToColName.call(void 0, index),
4040
+ width: _optionalChain([config, 'optionalAccess', _204 => _204.width]) || 130,
4041
+ type: _optionalChain([config, 'optionalAccess', _205 => _205.type]) || "freeform",
4042
+ binding: _optionalChain([config, 'optionalAccess', _206 => _206.binding]),
4043
+ formula: _optionalChain([config, 'optionalAccess', _207 => _207.formula]),
4044
+ format: _optionalChain([config, 'optionalAccess', _208 => _208.format]) || "text",
4045
+ ..._optionalChain([config, 'optionalAccess', _209 => _209.numberFormat]) ? { numberFormat: config.numberFormat } : {},
4046
+ readOnly: _optionalChain([config, 'optionalAccess', _210 => _210.readOnly])
3527
4047
  };
3528
4048
  this.shiftReferences({ axis: "col", index, type: "insert" });
3529
4049
  this.adjustTableForStructuralChange({ axis: "col", index, type: "insert" });
@@ -3548,13 +4068,13 @@ var SpreadsheetController = (_class4 = class {
3548
4068
  }
3549
4069
  insertRow(index, config) {
3550
4070
  this.recordSnapshot();
3551
- const newId = _optionalChain([config, 'optionalAccess', _175 => _175.id]) || this.generateId("row");
4071
+ const newId = _optionalChain([config, 'optionalAccess', _211 => _211.id]) || this.generateId("row");
3552
4072
  const newRow = {
3553
4073
  id: newId,
3554
4074
  index,
3555
- height: _optionalChain([config, 'optionalAccess', _176 => _176.height]) || 32,
3556
- recordId: _optionalChain([config, 'optionalAccess', _177 => _177.recordId]),
3557
- type: _optionalChain([config, 'optionalAccess', _178 => _178.type]) || "freeform"
4075
+ height: _optionalChain([config, 'optionalAccess', _212 => _212.height]) || 32,
4076
+ recordId: _optionalChain([config, 'optionalAccess', _213 => _213.recordId]),
4077
+ type: _optionalChain([config, 'optionalAccess', _214 => _214.type]) || "freeform"
3558
4078
  };
3559
4079
  this.shiftReferences({ axis: "row", index, type: "insert" });
3560
4080
  this.adjustTableForStructuralChange({ axis: "row", index, type: "insert" });
@@ -3588,14 +4108,14 @@ var SpreadsheetController = (_class4 = class {
3588
4108
  const filter = this.referencesTo(changed, rt);
3589
4109
  for (const rowCells of Object.values(rt.doc.cells)) {
3590
4110
  for (const cell of Object.values(rowCells)) {
3591
- if (_optionalChain([cell, 'access', _179 => _179.raw, 'optionalAccess', _180 => _180.startsWith, 'call', _181 => _181("=")])) {
4111
+ if (_optionalChain([cell, 'access', _215 => _215.raw, 'optionalAccess', _216 => _216.startsWith, 'call', _217 => _217("=")])) {
3592
4112
  cell.raw = shiftFormulaReferences(cell.raw, change, filter);
3593
4113
  }
3594
4114
  }
3595
4115
  }
3596
4116
  if (change.axis === "col") {
3597
4117
  for (const col of rt.doc.columns) {
3598
- if (_optionalChain([col, 'access', _182 => _182.formula, 'optionalAccess', _183 => _183.startsWith, 'call', _184 => _184("=")])) {
4118
+ if (_optionalChain([col, 'access', _218 => _218.formula, 'optionalAccess', _219 => _219.startsWith, 'call', _220 => _220("=")])) {
3599
4119
  col.formula = shiftFormulaReferences(col.formula, change, filter);
3600
4120
  }
3601
4121
  }
@@ -3675,7 +4195,7 @@ var SpreadsheetController = (_class4 = class {
3675
4195
  this.document.rows.push({ id: this.generateId("row"), index, height: 32, type: "freeform" });
3676
4196
  }
3677
4197
  while (colIndex >= this.document.columns.length) {
3678
- const key = _chunkC7P22VIPcjs.indexToColName.call(void 0, this.document.columns.length);
4198
+ const key = _chunkEWCOFSCQcjs.indexToColName.call(void 0, this.document.columns.length);
3679
4199
  this.document.columns.push({
3680
4200
  id: this.generateId("col"),
3681
4201
  key,
@@ -3708,6 +4228,8 @@ var SpreadsheetController = (_class4 = 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,14 +4237,20 @@ var SpreadsheetController = (_class4 = 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
- this.recalculateAll();
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;
3723
4251
  this.pendingCommits = [];
3724
4252
  for (const [rowId, colId, raw] of commits) {
3725
- _optionalChain([this, 'access', _185 => _185.onCellCommit, 'optionalCall', _186 => _186(rowId, colId, raw, _optionalChain([this, 'access', _187 => _187.document, 'access', _188 => _188.cells, 'access', _189 => _189[rowId], 'optionalAccess', _190 => _190[colId], 'optionalAccess', _191 => _191.value]))]);
4253
+ _optionalChain([this, 'access', _221 => _221.onCellCommit, 'optionalCall', _222 => _222(rowId, colId, raw, _optionalChain([this, 'access', _223 => _223.document, 'access', _224 => _224.cells, 'access', _225 => _225[rowId], 'optionalAccess', _226 => _226[colId], 'optionalAccess', _227 => _227.value]))]);
3726
4254
  }
3727
4255
  }
3728
4256
  // --- CSV ---
@@ -3821,7 +4349,7 @@ var SpreadsheetController = (_class4 = class {
3821
4349
  const r = normalizeRange(this.selectedRange);
3822
4350
  return { row: r.startRow, col: r.startCol };
3823
4351
  }
3824
- return { row: _nullishCoalesce(_optionalChain([this, 'access', _192 => _192.activeCell, 'optionalAccess', _193 => _193.row]), () => ( 0)), col: _nullishCoalesce(_optionalChain([this, 'access', _194 => _194.activeCell, 'optionalAccess', _195 => _195.col]), () => ( 0)) };
4352
+ return { row: _nullishCoalesce(_optionalChain([this, 'access', _228 => _228.activeCell, 'optionalAccess', _229 => _229.row]), () => ( 0)), col: _nullishCoalesce(_optionalChain([this, 'access', _230 => _230.activeCell, 'optionalAccess', _231 => _231.col]), () => ( 0)) };
3825
4353
  }
3826
4354
  // --- Table operations: shared helpers ---
3827
4355
  /** The table settings object, created on first use. */
@@ -3845,7 +4373,7 @@ var SpreadsheetController = (_class4 = class {
3845
4373
  const { headerRows, footerRows } = getTableSections(this.document);
3846
4374
  return { start: headerRows, end: this.document.rows.length - footerRows - 1 };
3847
4375
  }
3848
- __init35() {this.getCellValueAt = (rowIndex, colIndex) => {
4376
+ __init51() {this.getCellValueAt = (rowIndex, colIndex) => {
3849
4377
  const row = this.document.rows[rowIndex];
3850
4378
  const col = this.document.columns[colIndex];
3851
4379
  if (!row || !col) return "";
@@ -3858,7 +4386,7 @@ var SpreadsheetController = (_class4 = class {
3858
4386
  const filter = this.referencesTo(moved, rt);
3859
4387
  for (const rowCells of Object.values(rt.doc.cells)) {
3860
4388
  for (const cell of Object.values(rowCells)) {
3861
- if (_optionalChain([cell, 'access', _196 => _196.raw, 'optionalAccess', _197 => _197.startsWith, 'call', _198 => _198("=")])) {
4389
+ if (_optionalChain([cell, 'access', _232 => _232.raw, 'optionalAccess', _233 => _233.startsWith, 'call', _234 => _234("=")])) {
3862
4390
  cell.raw = remapFormulaReferences(cell.raw, moveCell, filter);
3863
4391
  }
3864
4392
  }
@@ -3872,8 +4400,8 @@ var SpreadsheetController = (_class4 = class {
3872
4400
  */
3873
4401
  adjustTableForStructuralChange(change) {
3874
4402
  this.adjustRulesForStructuralChange(change);
3875
- const merges = _optionalChain([this, 'access', _199 => _199.document, 'access', _200 => _200.table, 'optionalAccess', _201 => _201.merges]);
3876
- if (!_optionalChain([merges, 'optionalAccess', _202 => _202.length])) return;
4403
+ const merges = _optionalChain([this, 'access', _235 => _235.document, 'access', _236 => _236.table, 'optionalAccess', _237 => _237.merges]);
4404
+ if (!_optionalChain([merges, 'optionalAccess', _238 => _238.length])) return;
3877
4405
  const startKey = change.axis === "row" ? "startRow" : "startCol";
3878
4406
  const endKey = change.axis === "row" ? "endRow" : "endCol";
3879
4407
  const next = [];
@@ -3904,8 +4432,8 @@ var SpreadsheetController = (_class4 = class {
3904
4432
  const colCount = this.document.columns.length;
3905
4433
  const body = this.getBodyRowBounds();
3906
4434
  const range = options.range ? normalizeRange(options.range) : null;
3907
- const startRow = Math.max(_nullishCoalesce(_optionalChain([range, 'optionalAccess', _203 => _203.startRow]), () => ( body.start)), body.start);
3908
- const endRow = Math.min(_nullishCoalesce(_optionalChain([range, 'optionalAccess', _204 => _204.endRow]), () => ( body.end)), body.end);
4435
+ const startRow = Math.max(_nullishCoalesce(_optionalChain([range, 'optionalAccess', _239 => _239.startRow]), () => ( body.start)), body.start);
4436
+ const endRow = Math.min(_nullishCoalesce(_optionalChain([range, 'optionalAccess', _240 => _240.endRow]), () => ( body.end)), body.end);
3909
4437
  const startCol = range ? Math.max(0, range.startCol) : 0;
3910
4438
  const endCol = range ? Math.min(colCount - 1, range.endCol) : colCount - 1;
3911
4439
  if (endRow - startRow < 1 || startCol > endCol) return false;
@@ -3915,7 +4443,7 @@ var SpreadsheetController = (_class4 = class {
3915
4443
  }));
3916
4444
  if (keyCols.length === 0 || keyCols.some((k) => k.index < startCol || k.index > endCol)) return false;
3917
4445
  const area = { startRow, startCol, endRow, endCol };
3918
- if ((_nullishCoalesce(_optionalChain([this, 'access', _205 => _205.document, 'access', _206 => _206.table, 'optionalAccess', _207 => _207.merges]), () => ( []))).some((m) => rangesIntersect(m, area))) return false;
4446
+ if ((_nullishCoalesce(_optionalChain([this, 'access', _241 => _241.document, 'access', _242 => _242.table, 'optionalAccess', _243 => _243.merges]), () => ( []))).some((m) => rangesIntersect(m, area))) return false;
3919
4447
  if (this.intersectsSpill(area)) return false;
3920
4448
  const sourceRows = [];
3921
4449
  for (let r = startRow; r <= endRow; r++) sourceRows.push(r);
@@ -3943,7 +4471,7 @@ var SpreadsheetController = (_class4 = class {
3943
4471
  const colIds = this.document.columns.slice(startCol, endCol + 1).map((c) => c.id);
3944
4472
  const snapshot = sorted.map((oldRow) => {
3945
4473
  const rowId = this.document.rows[oldRow].id;
3946
- return colIds.map((colId) => _optionalChain([this, 'access', _208 => _208.document, 'access', _209 => _209.cells, 'access', _210 => _210[rowId], 'optionalAccess', _211 => _211[colId]]));
4474
+ return colIds.map((colId) => _optionalChain([this, 'access', _244 => _244.document, 'access', _245 => _245.cells, 'access', _246 => _246[rowId], 'optionalAccess', _247 => _247[colId]]));
3947
4475
  });
3948
4476
  snapshot.forEach((cells, i) => {
3949
4477
  const rowId = this.document.rows[startRow + i].id;
@@ -3959,7 +4487,7 @@ var SpreadsheetController = (_class4 = class {
3959
4487
  }
3960
4488
  // --- Filters ---
3961
4489
  getFilterState() {
3962
- return _nullishCoalesce(_optionalChain([this, 'access', _212 => _212.document, 'access', _213 => _213.table, 'optionalAccess', _214 => _214.filter]), () => ( { enabled: false, rules: [] }));
4490
+ return _nullishCoalesce(_optionalChain([this, 'access', _248 => _248.document, 'access', _249 => _249.table, 'optionalAccess', _250 => _250.filter]), () => ( { enabled: false, rules: [] }));
3963
4491
  }
3964
4492
  /** Replaces all filter rules (and turns filters on when there are any). */
3965
4493
  setFilterRules(rules) {
@@ -4036,7 +4564,7 @@ var SpreadsheetController = (_class4 = class {
4036
4564
  }
4037
4565
  setTableCount(key, count, max) {
4038
4566
  const value = Math.max(0, Math.min(Math.floor(count), max));
4039
- if ((_nullishCoalesce(_optionalChain([this, 'access', _215 => _215.document, 'access', _216 => _216.table, 'optionalAccess', _217 => _217[key]]), () => ( 0))) === value) return;
4567
+ if ((_nullishCoalesce(_optionalChain([this, 'access', _251 => _251.document, 'access', _252 => _252.table, 'optionalAccess', _253 => _253[key]]), () => ( 0))) === value) return;
4040
4568
  this.applyTableChange(() => {
4041
4569
  this.table[key] = value;
4042
4570
  });
@@ -4066,7 +4594,7 @@ var SpreadsheetController = (_class4 = class {
4066
4594
  const cells = this.document.cells[row.id] ??= {};
4067
4595
  const existing = cells[col.id];
4068
4596
  if (code === null) {
4069
- if (!_optionalChain([existing, 'optionalAccess', _218 => _218.explicitNumberFormat])) continue;
4597
+ if (!_optionalChain([existing, 'optionalAccess', _254 => _254.explicitNumberFormat])) continue;
4070
4598
  if (existing.raw === "" && existing.value === "" && !existing.error) {
4071
4599
  delete cells[col.id];
4072
4600
  } else {
@@ -4076,7 +4604,7 @@ var SpreadsheetController = (_class4 = class {
4076
4604
  } else {
4077
4605
  cells[col.id] = {
4078
4606
  ..._nullishCoalesce(existing, () => ( { raw: "", value: "" })),
4079
- format: _nullishCoalesce(_nullishCoalesce(kind, () => ( _optionalChain([existing, 'optionalAccess', _219 => _219.format]))), () => ( col.format)),
4607
+ format: _nullishCoalesce(_nullishCoalesce(kind, () => ( _optionalChain([existing, 'optionalAccess', _255 => _255.format]))), () => ( col.format)),
4080
4608
  numberFormat: code,
4081
4609
  explicitNumberFormat: true
4082
4610
  };
@@ -4102,7 +4630,7 @@ var SpreadsheetController = (_class4 = class {
4102
4630
  else col.numberFormat = code;
4103
4631
  for (const row of this.document.rows) {
4104
4632
  const cells = this.document.cells[row.id];
4105
- const cell = _optionalChain([cells, 'optionalAccess', _220 => _220[col.id]]);
4633
+ const cell = _optionalChain([cells, 'optionalAccess', _256 => _256[col.id]]);
4106
4634
  if (cells && cell && !cell.explicitNumberFormat) {
4107
4635
  cells[col.id] = this.withNumberFormat(cell, this.implicitNumberFormat(cell, col, row));
4108
4636
  }
@@ -4114,7 +4642,7 @@ var SpreadsheetController = (_class4 = class {
4114
4642
  /** The column's format, or the one typed input chose (typed cells only). */
4115
4643
  implicitNumberFormat(cell, col, row) {
4116
4644
  const typed = cell.raw !== "" && !cell.raw.startsWith("=") && !(col.type === "bound" && col.binding && row.recordId);
4117
- return _nullishCoalesce(this.fixedNumberFormat(col, void 0), () => ( (typed ? _chunkC7P22VIPcjs.parseCellInput.call(void 0, cell.raw).numberFormat : void 0)));
4645
+ return _nullishCoalesce(this.fixedNumberFormat(col, void 0), () => ( (typed ? _chunkEWCOFSCQcjs.parseCellInput.call(void 0, cell.raw).numberFormat : void 0)));
4118
4646
  }
4119
4647
  /** A copy of `cell` with `numberFormat` set, or removed when undefined. */
4120
4648
  withNumberFormat(cell, numberFormat) {
@@ -4127,7 +4655,7 @@ var SpreadsheetController = (_class4 = class {
4127
4655
  const spilled = this.spillCells.get(id);
4128
4656
  if (!spilled) return;
4129
4657
  const { numberFormat: _old, explicitNumberFormat: _explicit, ...rest } = spilled;
4130
- this.spillCells.set(id, { ...rest, ...this.fixedNumberFormatFields(col, _optionalChain([this, 'access', _221 => _221.document, 'access', _222 => _222.cells, 'access', _223 => _223[row.id], 'optionalAccess', _224 => _224[col.id]])) });
4658
+ this.spillCells.set(id, { ...rest, ...this.fixedNumberFormatFields(col, _optionalChain([this, 'access', _257 => _257.document, 'access', _258 => _258.cells, 'access', _259 => _259[row.id], 'optionalAccess', _260 => _260[col.id]])) });
4131
4659
  }
4132
4660
  // --- Hide / unhide ---
4133
4661
  hideRows(indices) {
@@ -4165,7 +4693,7 @@ var SpreadsheetController = (_class4 = class {
4165
4693
  }
4166
4694
  // --- Merged cells ---
4167
4695
  getMerges() {
4168
- return _nullishCoalesce(_optionalChain([this, 'access', _225 => _225.document, 'access', _226 => _226.table, 'optionalAccess', _227 => _227.merges]), () => ( []));
4696
+ return _nullishCoalesce(_optionalChain([this, 'access', _261 => _261.document, 'access', _262 => _262.table, 'optionalAccess', _263 => _263.merges]), () => ( []));
4169
4697
  }
4170
4698
  /** The merge containing (row, col), or null. */
4171
4699
  getMergeAt(row, col) {
@@ -4218,7 +4746,7 @@ var SpreadsheetController = (_class4 = class {
4218
4746
  const rowId = this.document.rows[row].id;
4219
4747
  for (let col = r.startCol; col <= r.endCol; col++) {
4220
4748
  if (row === r.startRow && col === r.startCol) continue;
4221
- _optionalChainDelete([this, 'access', _228 => _228.document, 'access', _229 => _229.cells, 'access', _230 => _230[rowId], 'optionalAccess', _231 => _231[this.document.columns[col].id]]);
4749
+ _optionalChainDelete([this, 'access', _264 => _264.document, 'access', _265 => _265.cells, 'access', _266 => _266[rowId], 'optionalAccess', _267 => _267[this.document.columns[col].id]]);
4222
4750
  }
4223
4751
  }
4224
4752
  this.table.merges = [...merges.filter((m) => !rangeContainsRange(r, m)), r];
@@ -4304,14 +4832,14 @@ var SpreadsheetController = (_class4 = class {
4304
4832
  });
4305
4833
  }
4306
4834
  ungroupRows() {
4307
- if (!_optionalChain([this, 'access', _232 => _232.document, 'access', _233 => _233.table, 'optionalAccess', _234 => _234.grouping])) return;
4835
+ if (!_optionalChain([this, 'access', _268 => _268.document, 'access', _269 => _269.table, 'optionalAccess', _270 => _270.grouping])) return;
4308
4836
  this.applyTableChange(() => {
4309
4837
  delete this.table.grouping;
4310
4838
  });
4311
4839
  }
4312
4840
  /** Sets (or with `null`, removes) the group summary for a column. */
4313
4841
  setGroupSummary(colId, aggregate) {
4314
- const grouping = _optionalChain([this, 'access', _235 => _235.document, 'access', _236 => _236.table, 'optionalAccess', _237 => _237.grouping]);
4842
+ const grouping = _optionalChain([this, 'access', _271 => _271.document, 'access', _272 => _272.table, 'optionalAccess', _273 => _273.grouping]);
4315
4843
  if (!grouping) return;
4316
4844
  this.applyTableChange(() => {
4317
4845
  const others = grouping.summaries.filter((s) => s.colId !== colId);
@@ -4322,7 +4850,7 @@ var SpreadsheetController = (_class4 = class {
4322
4850
  });
4323
4851
  }
4324
4852
  setGroupCollapsed(key, collapsed) {
4325
- const grouping = _optionalChain([this, 'access', _238 => _238.document, 'access', _239 => _239.table, 'optionalAccess', _240 => _240.grouping]);
4853
+ const grouping = _optionalChain([this, 'access', _274 => _274.document, 'access', _275 => _275.table, 'optionalAccess', _276 => _276.grouping]);
4326
4854
  if (!grouping || grouping.collapsed.includes(key) === collapsed) return;
4327
4855
  this.applyTableChange(() => {
4328
4856
  this.table.grouping = {
@@ -4332,7 +4860,7 @@ var SpreadsheetController = (_class4 = class {
4332
4860
  });
4333
4861
  }
4334
4862
  toggleGroupCollapsed(key) {
4335
- const grouping = _optionalChain([this, 'access', _241 => _241.document, 'access', _242 => _242.table, 'optionalAccess', _243 => _243.grouping]);
4863
+ const grouping = _optionalChain([this, 'access', _277 => _277.document, 'access', _278 => _278.table, 'optionalAccess', _279 => _279.grouping]);
4336
4864
  if (!grouping) return;
4337
4865
  this.setGroupCollapsed(key, !grouping.collapsed.includes(key));
4338
4866
  }
@@ -4370,9 +4898,9 @@ var SpreadsheetController = (_class4 = class {
4370
4898
  moved.push({
4371
4899
  row: s.startRow + (c - s.startCol),
4372
4900
  col: s.startCol + (r - s.startRow),
4373
- cell: _optionalChain([this, 'access', _244 => _244.document, 'access', _245 => _245.cells, 'access', _246 => _246[rowId(r)], 'optionalAccess', _247 => _247[colId(c)]])
4901
+ cell: _optionalChain([this, 'access', _280 => _280.document, 'access', _281 => _281.cells, 'access', _282 => _282[rowId(r)], 'optionalAccess', _283 => _283[colId(c)]])
4374
4902
  });
4375
- _optionalChainDelete([this, 'access', _248 => _248.document, 'access', _249 => _249.cells, 'access', _250 => _250[rowId(r)], 'optionalAccess', _251 => _251[colId(c)]]);
4903
+ _optionalChainDelete([this, 'access', _284 => _284.document, 'access', _285 => _285.cells, 'access', _286 => _286[rowId(r)], 'optionalAccess', _287 => _287[colId(c)]]);
4376
4904
  }
4377
4905
  }
4378
4906
  for (const { row, col, cell } of moved) {
@@ -4404,7 +4932,7 @@ var SpreadsheetController = (_class4 = class {
4404
4932
  }
4405
4933
  /** A sheet's document by id or name (live: don't mutate it directly). */
4406
4934
  getSheetDocument(idOrName) {
4407
- return _optionalChain([this, 'access', _252 => _252.findRuntime, 'call', _253 => _253(idOrName), 'optionalAccess', _254 => _254.doc]);
4935
+ return _optionalChain([this, 'access', _288 => _288.findRuntime, 'call', _289 => _289(idOrName), 'optionalAccess', _290 => _290.doc]);
4408
4936
  }
4409
4937
  /** Why `name` can't name a sheet (or null): see `validateSheetName`. `exceptSheet` is the sheet being renamed. */
4410
4938
  validateSheetName(name, exceptSheet) {
@@ -4438,8 +4966,8 @@ var SpreadsheetController = (_class4 = class {
4438
4966
  this.selectedRange = rt.selectedRange;
4439
4967
  this.editingCell = null;
4440
4968
  this.draftValue = "";
4441
- _optionalChain([this, 'access', _255 => _255.onSelectionChange, 'optionalCall', _256 => _256(this.activeCell, this.selectedRange)]);
4442
- _optionalChain([this, 'access', _257 => _257.onActiveSheetChange, 'optionalCall', _258 => _258(rt.doc.id)]);
4969
+ _optionalChain([this, 'access', _291 => _291.onSelectionChange, 'optionalCall', _292 => _292(this.activeCell, this.selectedRange)]);
4970
+ _optionalChain([this, 'access', _293 => _293.onActiveSheetChange, 'optionalCall', _294 => _294(rt.doc.id)]);
4443
4971
  }
4444
4972
  /**
4445
4973
  * Adds a sheet (default: `Sheet N`, empty, after the last sheet) and
@@ -4448,16 +4976,16 @@ var SpreadsheetController = (_class4 = class {
4448
4976
  */
4449
4977
  addSheet(options = {}) {
4450
4978
  const existing = this.runtimes.map((rt2) => rt2.doc.name);
4451
- const name = _nullishCoalesce(_nullishCoalesce(options.name, () => ( _optionalChain([options, 'access', _259 => _259.document, 'optionalAccess', _260 => _260.name]))), () => ( nextSheetName(existing)));
4979
+ const name = _nullishCoalesce(_nullishCoalesce(options.name, () => ( _optionalChain([options, 'access', _295 => _295.document, 'optionalAccess', _296 => _296.name]))), () => ( nextSheetName(existing)));
4452
4980
  const invalid2 = validateSheetName(name, existing);
4453
4981
  if (invalid2) throw new Error(invalid2);
4454
4982
  this.recordWorkbookSnapshot();
4455
- const id = _optionalChain([options, 'access', _261 => _261.document, 'optionalAccess', _262 => _262.id]) && !this.findRuntimeById(options.document.id) ? options.document.id : this.generateSheetId();
4983
+ const id = _optionalChain([options, 'access', _297 => _297.document, 'optionalAccess', _298 => _298.id]) && !this.findRuntimeById(options.document.id) ? options.document.id : this.generateSheetId();
4456
4984
  const doc = {
4457
4985
  columns: Array.from({ length: 4 }, (_, i) => ({
4458
4986
  id: `col_${String.fromCharCode(97 + i)}`,
4459
- key: _chunkC7P22VIPcjs.indexToColName.call(void 0, i),
4460
- title: _chunkC7P22VIPcjs.indexToColName.call(void 0, i),
4987
+ key: _chunkEWCOFSCQcjs.indexToColName.call(void 0, i),
4988
+ title: _chunkEWCOFSCQcjs.indexToColName.call(void 0, i),
4461
4989
  width: 120,
4462
4990
  type: "freeform"
4463
4991
  })),
@@ -4571,13 +5099,13 @@ var SpreadsheetController = (_class4 = class {
4571
5099
  for (const rt of this.runtimes) {
4572
5100
  for (const [rowId, rowCells] of Object.entries(rt.doc.cells)) {
4573
5101
  for (const [colId, cell] of Object.entries(rowCells)) {
4574
- if (!_optionalChain([cell, 'access', _263 => _263.raw, 'optionalAccess', _264 => _264.startsWith, 'call', _265 => _265("=")])) continue;
5102
+ if (!_optionalChain([cell, 'access', _299 => _299.raw, 'optionalAccess', _300 => _300.startsWith, 'call', _301 => _301("=")])) continue;
4575
5103
  const raw = rewrite(cell.raw);
4576
5104
  if (raw !== cell.raw) rt.doc.cells[rowId][colId] = { ...cell, raw };
4577
5105
  }
4578
5106
  }
4579
5107
  for (const col of rt.doc.columns) {
4580
- if (_optionalChain([col, 'access', _266 => _266.formula, 'optionalAccess', _267 => _267.startsWith, 'call', _268 => _268("=")])) col.formula = rewrite(col.formula);
5108
+ if (_optionalChain([col, 'access', _302 => _302.formula, 'optionalAccess', _303 => _303.startsWith, 'call', _304 => _304("=")])) col.formula = rewrite(col.formula);
4581
5109
  }
4582
5110
  }
4583
5111
  for (const n of this.names) n.refersTo = rewrite(n.refersTo);
@@ -4599,8 +5127,8 @@ var SpreadsheetController = (_class4 = class {
4599
5127
  }
4600
5128
  /** The defined name `name` visible from the active sheet (its own sheet-scoped name first), or undefined. */
4601
5129
  getName(name, scope) {
4602
- const rt = _optionalChain([scope, 'optionalAccess', _269 => _269.sheet]) !== void 0 ? this.findRuntime(scope.sheet) : void 0;
4603
- const def = _optionalChain([scope, 'optionalAccess', _270 => _270.sheet]) !== void 0 ? rt ? this.names.find((n) => n.sheetId === rt.doc.id && sameName3(n.name, name)) : void 0 : this.findName(name, this.active);
5130
+ const rt = _optionalChain([scope, 'optionalAccess', _305 => _305.sheet]) !== void 0 ? this.findRuntime(scope.sheet) : void 0;
5131
+ const def = _optionalChain([scope, 'optionalAccess', _306 => _306.sheet]) !== void 0 ? rt ? this.names.find((n) => n.sheetId === rt.doc.id && sameName3(n.name, name)) : void 0 : this.findName(name, this.active);
4604
5132
  return def ? this.getNames().find((n) => n.name === def.name && n.sheetId === def.sheetId) : void 0;
4605
5133
  }
4606
5134
  /** Why `name` can't be a defined name (or null): see `validateDefinedName`. */
@@ -4608,7 +5136,7 @@ var SpreadsheetController = (_class4 = class {
4608
5136
  return validateDefinedName(name);
4609
5137
  }
4610
5138
  scopeRuntime(scope) {
4611
- if (_optionalChain([scope, 'optionalAccess', _271 => _271.sheet]) === void 0) return void 0;
5139
+ if (_optionalChain([scope, 'optionalAccess', _307 => _307.sheet]) === void 0) return void 0;
4612
5140
  const rt = this.findRuntime(scope.sheet);
4613
5141
  if (!rt) throw new Error(`There is no sheet "${scope.sheet}".`);
4614
5142
  return rt;
@@ -4625,13 +5153,13 @@ var SpreadsheetController = (_class4 = class {
4625
5153
  if (invalid2) throw new Error(invalid2);
4626
5154
  const formula = refersTo.trim().startsWith("=") ? refersTo.trim() : `=${refersTo.trim()}`;
4627
5155
  try {
4628
- _chunkC7P22VIPcjs.parseFormula.call(void 0, formula);
5156
+ _chunkEWCOFSCQcjs.parseFormula.call(void 0, formula);
4629
5157
  } catch (err) {
4630
- throw new Error(`There's a problem with this formula: ${_nullishCoalesce(_optionalChain([err, 'optionalAccess', _272 => _272.message]), () => ( formula))}`);
5158
+ throw new Error(`There's a problem with this formula: ${_nullishCoalesce(_optionalChain([err, 'optionalAccess', _308 => _308.message]), () => ( formula))}`);
4631
5159
  }
4632
5160
  const rt = this.scopeRuntime(options);
4633
5161
  this.recordWorkbookSnapshot();
4634
- const sheetId = _optionalChain([rt, 'optionalAccess', _273 => _273.doc, 'access', _274 => _274.id]);
5162
+ const sheetId = _optionalChain([rt, 'optionalAccess', _309 => _309.doc, 'access', _310 => _310.id]);
4635
5163
  const index = this.names.findIndex((n) => n.sheetId === sheetId && sameName3(n.name, name));
4636
5164
  const def = {
4637
5165
  name,
@@ -4652,7 +5180,7 @@ var SpreadsheetController = (_class4 = class {
4652
5180
  */
4653
5181
  renameName(oldName, newName, options = {}) {
4654
5182
  const rt = this.scopeRuntime(options);
4655
- const sheetId = _optionalChain([rt, 'optionalAccess', _275 => _275.doc, 'access', _276 => _276.id]);
5183
+ const sheetId = _optionalChain([rt, 'optionalAccess', _311 => _311.doc, 'access', _312 => _312.id]);
4656
5184
  const def = this.names.find((n) => n.sheetId === sheetId && sameName3(n.name, oldName));
4657
5185
  if (!def) throw new Error(`There is no name "${oldName}"${rt ? ` on sheet ${rt.doc.name}` : ""}.`);
4658
5186
  if (def.name === newName) return;
@@ -4663,19 +5191,19 @@ var SpreadsheetController = (_class4 = class {
4663
5191
  }
4664
5192
  this.recordWorkbookSnapshot();
4665
5193
  const shadowedOn = new Set(this.names.filter((n) => n.sheetId !== void 0 && sameName3(n.name, oldName)).map((n) => n.sheetId));
4666
- const appliesOn = (sheet) => sheetId !== void 0 ? _optionalChain([sheet, 'optionalAccess', _277 => _277.doc, 'access', _278 => _278.id]) === sheetId : !sheet || !shadowedOn.has(sheet.doc.id);
5194
+ const appliesOn = (sheet) => sheetId !== void 0 ? _optionalChain([sheet, 'optionalAccess', _313 => _313.doc, 'access', _314 => _314.id]) === sheetId : !sheet || !shadowedOn.has(sheet.doc.id);
4667
5195
  const appliesToSheetName = (sheetName) => appliesOn(this.findRuntimeByName(sheetName));
4668
5196
  for (const sheet of this.runtimes) {
4669
5197
  const options2 = { appliesTo: appliesToSheetName, qualifiedOnly: !appliesOn(sheet) };
4670
5198
  for (const [rowId, rowCells] of Object.entries(sheet.doc.cells)) {
4671
5199
  for (const [colId, cell] of Object.entries(rowCells)) {
4672
- if (!_optionalChain([cell, 'access', _279 => _279.raw, 'optionalAccess', _280 => _280.startsWith, 'call', _281 => _281("=")])) continue;
5200
+ if (!_optionalChain([cell, 'access', _315 => _315.raw, 'optionalAccess', _316 => _316.startsWith, 'call', _317 => _317("=")])) continue;
4673
5201
  const raw = renameNameInFormula(cell.raw, oldName, newName, options2);
4674
5202
  if (raw !== cell.raw) sheet.doc.cells[rowId][colId] = { ...cell, raw };
4675
5203
  }
4676
5204
  }
4677
5205
  for (const col of sheet.doc.columns) {
4678
- if (_optionalChain([col, 'access', _282 => _282.formula, 'optionalAccess', _283 => _283.startsWith, 'call', _284 => _284("=")])) col.formula = renameNameInFormula(col.formula, oldName, newName, options2);
5206
+ if (_optionalChain([col, 'access', _318 => _318.formula, 'optionalAccess', _319 => _319.startsWith, 'call', _320 => _320("=")])) col.formula = renameNameInFormula(col.formula, oldName, newName, options2);
4679
5207
  }
4680
5208
  }
4681
5209
  for (const n of this.names) {
@@ -4695,7 +5223,7 @@ var SpreadsheetController = (_class4 = class {
4695
5223
  */
4696
5224
  deleteName(name, options = {}) {
4697
5225
  const rt = this.scopeRuntime(options);
4698
- const sheetId = _optionalChain([rt, 'optionalAccess', _285 => _285.doc, 'access', _286 => _286.id]);
5226
+ const sheetId = _optionalChain([rt, 'optionalAccess', _321 => _321.doc, 'access', _322 => _322.id]);
4699
5227
  const index = this.names.findIndex((n) => n.sheetId === sheetId && sameName3(n.name, name));
4700
5228
  if (index < 0) return false;
4701
5229
  this.recordWorkbookSnapshot();
@@ -4742,6 +5270,17 @@ var SpreadsheetController = (_class4 = 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, _nullishCoalesce(_optionalChain([doc, 'access', _323 => _323.cells, 'access', _324 => _324[rowId], 'optionalAccess', _325 => _325[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 = (_class4 = 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 = _optionalChain([rt, 'access', _326 => _326.doc, 'access', _327 => _327.cells, 'access', _328 => _328[rowId], 'optionalAccess', _329 => _329[colId], 'optionalAccess', _330 => _330.raw]);
5347
+ const cellKey2 = this.getCellKey(rowId, colId);
5348
+ this.registerFormula(cellKey2, _optionalChain([raw, 'optionalAccess', _331 => _331.startsWith, 'call', _332 => _332("=")]) ? raw : null);
5349
+ this.dropStaleSpillEdges(cellKey2, _nullishCoalesce(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;
@@ -4811,8 +5381,8 @@ var SpreadsheetController = (_class4 = class {
4811
5381
  } else {
4812
5382
  this.activeCell = this.active.activeCell;
4813
5383
  this.selectedRange = this.active.selectedRange;
4814
- _optionalChain([this, 'access', _287 => _287.onSelectionChange, 'optionalCall', _288 => _288(this.activeCell, this.selectedRange)]);
4815
- _optionalChain([this, 'access', _289 => _289.onActiveSheetChange, 'optionalCall', _290 => _290(this.active.doc.id)]);
5384
+ _optionalChain([this, 'access', _333 => _333.onSelectionChange, 'optionalCall', _334 => _334(this.activeCell, this.selectedRange)]);
5385
+ _optionalChain([this, 'access', _335 => _335.onActiveSheetChange, 'optionalCall', _336 => _336(this.active.doc.id)]);
4816
5386
  }
4817
5387
  }
4818
5388
  this.recalculateAll();
@@ -4824,7 +5394,7 @@ var SpreadsheetController = (_class4 = class {
4824
5394
  notifyChange() {
4825
5395
  if (!this.cfIncremental) this.cfDirty = "all";
4826
5396
  this.invalidCircles = null;
4827
- _optionalChain([this, 'access', _291 => _291.onDocumentChange, 'optionalCall', _292 => _292(this.active.doc)]);
5397
+ _optionalChain([this, 'access', _337 => _337.onDocumentChange, 'optionalCall', _338 => _338(this.active.doc)]);
4828
5398
  if (this.onWorkbookChange) {
4829
5399
  const snapshot = this.workbookSnapshot();
4830
5400
  this.onWorkbookChange({ sheets: snapshot.sheets, activeSheetId: snapshot.activeSheetId, names: snapshot.names });
@@ -4854,7 +5424,7 @@ var SpreadsheetController = (_class4 = class {
4854
5424
  evaluateFormulaAt(formula, row, col, entered) {
4855
5425
  const saved = { rowIndex: this.evaluatingRowIndex, cell: this.evaluatingCell, override: this.valueOverride };
4856
5426
  this.evaluatingRowIndex = row;
4857
- this.evaluatingCell = { col: _nullishCoalesce(_optionalChain([this, 'access', _293 => _293.document, 'access', _294 => _294.columns, 'access', _295 => _295[col], 'optionalAccess', _296 => _296.key]), () => ( _chunkC7P22VIPcjs.indexToColName.call(void 0, col))), row: row + 1 };
5427
+ this.evaluatingCell = { col: _nullishCoalesce(_optionalChain([this, 'access', _339 => _339.document, 'access', _340 => _340.columns, 'access', _341 => _341[col], 'optionalAccess', _342 => _342.key]), () => ( _chunkEWCOFSCQcjs.indexToColName.call(void 0, col))), row: row + 1 };
4858
5428
  this.valueOverride = _nullishCoalesce(entered, () => ( null));
4859
5429
  try {
4860
5430
  const result = this.evaluator.evaluateWithDiagnostics(formula.startsWith("=") ? formula : `=${formula}`);
@@ -4881,7 +5451,7 @@ var SpreadsheetController = (_class4 = class {
4881
5451
  if (!table) return;
4882
5452
  const shiftIfFormula = (text) => text !== void 0 && text.trimStart().startsWith("=") ? shiftRuleFormula(text, change) : text;
4883
5453
  const shiftAnchor = (value) => value.type === "formula" ? { ...value, formula: shiftRuleFormula(value.formula, change) } : value;
4884
- if (_optionalChain([table, 'access', _297 => _297.conditionalFormats, 'optionalAccess', _298 => _298.length])) {
5454
+ if (_optionalChain([table, 'access', _343 => _343.conditionalFormats, 'optionalAccess', _344 => _344.length])) {
4885
5455
  table.conditionalFormats = table.conditionalFormats.map((rule) => {
4886
5456
  const next = { ...rule, ranges: adjustRangesForStructuralChange(rule.ranges, change) };
4887
5457
  if (next.type === "expression") next.formula = shiftRuleFormula(next.formula, change);
@@ -4898,7 +5468,7 @@ var SpreadsheetController = (_class4 = class {
4898
5468
  return next;
4899
5469
  }).filter((rule) => rule.ranges.length > 0);
4900
5470
  }
4901
- if (_optionalChain([table, 'access', _299 => _299.dataValidations, 'optionalAccess', _300 => _300.length])) {
5471
+ if (_optionalChain([table, 'access', _345 => _345.dataValidations, 'optionalAccess', _346 => _346.length])) {
4902
5472
  table.dataValidations = table.dataValidations.map((rule) => {
4903
5473
  const next = { ...rule, ranges: adjustRangesForStructuralChange(rule.ranges, change) };
4904
5474
  if (rule.formula1 !== void 0) next.formula1 = shiftIfFormula(rule.formula1);
@@ -4909,7 +5479,7 @@ var SpreadsheetController = (_class4 = class {
4909
5479
  }
4910
5480
  /** Conditional formats of the sheet, highest priority first. */
4911
5481
  getConditionalFormats() {
4912
- return _nullishCoalesce(_optionalChain([this, 'access', _301 => _301.document, 'access', _302 => _302.table, 'optionalAccess', _303 => _303.conditionalFormats]), () => ( []));
5482
+ return _nullishCoalesce(_optionalChain([this, 'access', _347 => _347.document, 'access', _348 => _348.table, 'optionalAccess', _349 => _349.conditionalFormats]), () => ( []));
4913
5483
  }
4914
5484
  /**
4915
5485
  * Adds a conditional format (one undo step). New rules get the highest
@@ -4929,7 +5499,7 @@ var SpreadsheetController = (_class4 = class {
4929
5499
  }
4930
5500
  /** Changes a conditional format (its condition, ranges or stop-if-true). Returns false when not found. */
4931
5501
  updateConditionalFormat(id, changes) {
4932
- const rules = _nullishCoalesce(_optionalChain([this, 'access', _304 => _304.document, 'access', _305 => _305.table, 'optionalAccess', _306 => _306.conditionalFormats]), () => ( []));
5502
+ const rules = _nullishCoalesce(_optionalChain([this, 'access', _350 => _350.document, 'access', _351 => _351.table, 'optionalAccess', _352 => _352.conditionalFormats]), () => ( []));
4933
5503
  const index = rules.findIndex((r) => r.id === id);
4934
5504
  if (index === -1) return false;
4935
5505
  this.applyTableChange(() => {
@@ -4943,7 +5513,7 @@ var SpreadsheetController = (_class4 = class {
4943
5513
  }
4944
5514
  /** Removes a conditional format. Returns false when not found. */
4945
5515
  deleteConditionalFormat(id) {
4946
- const rules = _nullishCoalesce(_optionalChain([this, 'access', _307 => _307.document, 'access', _308 => _308.table, 'optionalAccess', _309 => _309.conditionalFormats]), () => ( []));
5516
+ const rules = _nullishCoalesce(_optionalChain([this, 'access', _353 => _353.document, 'access', _354 => _354.table, 'optionalAccess', _355 => _355.conditionalFormats]), () => ( []));
4947
5517
  if (!rules.some((r) => r.id === id)) return false;
4948
5518
  this.applyTableChange(() => {
4949
5519
  this.table.conditionalFormats = rules.filter((r) => r.id !== id);
@@ -4955,7 +5525,7 @@ var SpreadsheetController = (_class4 = class {
4955
5525
  * `{ by: -1 }` (up) / `{ by: 1 }` (down). Returns false when nothing moved.
4956
5526
  */
4957
5527
  moveConditionalFormat(id, to) {
4958
- const rules = _nullishCoalesce(_optionalChain([this, 'access', _310 => _310.document, 'access', _311 => _311.table, 'optionalAccess', _312 => _312.conditionalFormats]), () => ( []));
5528
+ const rules = _nullishCoalesce(_optionalChain([this, 'access', _356 => _356.document, 'access', _357 => _357.table, 'optionalAccess', _358 => _358.conditionalFormats]), () => ( []));
4959
5529
  const from = rules.findIndex((r) => r.id === id);
4960
5530
  if (from === -1) return false;
4961
5531
  const target = Math.max(0, Math.min(rules.length - 1, typeof to === "number" ? to : from + to.by));
@@ -4974,7 +5544,7 @@ var SpreadsheetController = (_class4 = class {
4974
5544
  * when `range` is null.
4975
5545
  */
4976
5546
  clearConditionalFormats(range = null) {
4977
- const rules = _nullishCoalesce(_optionalChain([this, 'access', _313 => _313.document, 'access', _314 => _314.table, 'optionalAccess', _315 => _315.conditionalFormats]), () => ( []));
5547
+ const rules = _nullishCoalesce(_optionalChain([this, 'access', _359 => _359.document, 'access', _360 => _360.table, 'optionalAccess', _361 => _361.conditionalFormats]), () => ( []));
4978
5548
  if (rules.length === 0) return;
4979
5549
  this.applyTableChange(() => {
4980
5550
  this.table.conditionalFormats = range ? rules.map((rule) => ({ ...rule, ranges: subtractRangeFromRanges(rule.ranges, range) })).filter((rule) => rule.ranges.length > 0) : [];
@@ -4985,7 +5555,7 @@ var SpreadsheetController = (_class4 = class {
4985
5555
  * cached: after a cell edit only the affected cells are re-evaluated.
4986
5556
  */
4987
5557
  getConditionalFormatOverlay() {
4988
- const rules = _nullishCoalesce(_optionalChain([this, 'access', _316 => _316.document, 'access', _317 => _317.table, 'optionalAccess', _318 => _318.conditionalFormats]), () => ( []));
5558
+ const rules = _nullishCoalesce(_optionalChain([this, 'access', _362 => _362.document, 'access', _363 => _363.table, 'optionalAccess', _364 => _364.conditionalFormats]), () => ( []));
4989
5559
  if (rules.length === 0) {
4990
5560
  this.cfEngine = null;
4991
5561
  this.cfDirty = "all";
@@ -4999,7 +5569,7 @@ var SpreadsheetController = (_class4 = 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.indexOf(located.col) });
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);
@@ -5013,11 +5583,11 @@ var SpreadsheetController = (_class4 = class {
5013
5583
  }
5014
5584
  /** Data validation rules of the sheet. */
5015
5585
  getDataValidations() {
5016
- return _nullishCoalesce(_optionalChain([this, 'access', _319 => _319.document, 'access', _320 => _320.table, 'optionalAccess', _321 => _321.dataValidations]), () => ( []));
5586
+ return _nullishCoalesce(_optionalChain([this, 'access', _365 => _365.document, 'access', _366 => _366.table, 'optionalAccess', _367 => _367.dataValidations]), () => ( []));
5017
5587
  }
5018
5588
  /** The rule that applies to (row, col), if any. */
5019
5589
  getDataValidationAt(row, col) {
5020
- return findDataValidation(_optionalChain([this, 'access', _322 => _322.document, 'access', _323 => _323.table, 'optionalAccess', _324 => _324.dataValidations]), row, col);
5590
+ return findDataValidation(_optionalChain([this, 'access', _368 => _368.document, 'access', _369 => _369.table, 'optionalAccess', _370 => _370.dataValidations]), row, col);
5021
5591
  }
5022
5592
  /**
5023
5593
  * Adds a data validation rule (one undo step). Its cells are first
@@ -5037,7 +5607,7 @@ var SpreadsheetController = (_class4 = class {
5037
5607
  }
5038
5608
  /** Changes a data validation rule. Returns false when not found. */
5039
5609
  updateDataValidation(id, changes) {
5040
- const rules = _nullishCoalesce(_optionalChain([this, 'access', _325 => _325.document, 'access', _326 => _326.table, 'optionalAccess', _327 => _327.dataValidations]), () => ( []));
5610
+ const rules = _nullishCoalesce(_optionalChain([this, 'access', _371 => _371.document, 'access', _372 => _372.table, 'optionalAccess', _373 => _373.dataValidations]), () => ( []));
5041
5611
  const index = rules.findIndex((r) => r.id === id);
5042
5612
  if (index === -1) return false;
5043
5613
  this.applyTableChange(() => {
@@ -5051,7 +5621,7 @@ var SpreadsheetController = (_class4 = class {
5051
5621
  }
5052
5622
  /** Removes a data validation rule. Returns false when not found. */
5053
5623
  deleteDataValidation(id) {
5054
- const rules = _nullishCoalesce(_optionalChain([this, 'access', _328 => _328.document, 'access', _329 => _329.table, 'optionalAccess', _330 => _330.dataValidations]), () => ( []));
5624
+ const rules = _nullishCoalesce(_optionalChain([this, 'access', _374 => _374.document, 'access', _375 => _375.table, 'optionalAccess', _376 => _376.dataValidations]), () => ( []));
5055
5625
  if (!rules.some((r) => r.id === id)) return false;
5056
5626
  this.applyTableChange(() => {
5057
5627
  this.table.dataValidations = rules.filter((r) => r.id !== id);
@@ -5060,7 +5630,7 @@ var SpreadsheetController = (_class4 = class {
5060
5630
  }
5061
5631
  /** Removes validation from the cells of `range`, or every rule when `range` is null. */
5062
5632
  clearDataValidation(range = null) {
5063
- const rules = _nullishCoalesce(_optionalChain([this, 'access', _331 => _331.document, 'access', _332 => _332.table, 'optionalAccess', _333 => _333.dataValidations]), () => ( []));
5633
+ const rules = _nullishCoalesce(_optionalChain([this, 'access', _377 => _377.document, 'access', _378 => _378.table, 'optionalAccess', _379 => _379.dataValidations]), () => ( []));
5064
5634
  if (rules.length === 0) return;
5065
5635
  this.applyTableChange(() => {
5066
5636
  this.table.dataValidations = range ? rules.map((r) => ({ ...r, ranges: subtractRangeFromRanges(r.ranges, range) })).filter((r) => r.ranges.length > 0) : [];
@@ -5068,7 +5638,7 @@ var SpreadsheetController = (_class4 = class {
5068
5638
  }
5069
5639
  /** Checks input for (row, col) against its data validation, without committing it. */
5070
5640
  validateCellInput(row, col, raw) {
5071
- return validateCellInput(this.ruleSheet(), _optionalChain([this, 'access', _334 => _334.document, 'access', _335 => _335.table, 'optionalAccess', _336 => _336.dataValidations]), row, col, raw);
5641
+ return validateCellInput(this.ruleSheet(), _optionalChain([this, 'access', _380 => _380.document, 'access', _381 => _381.table, 'optionalAccess', _382 => _382.dataValidations]), row, col, raw);
5072
5642
  }
5073
5643
  /**
5074
5644
  * Dropdown items of a list-validated cell, or null when the cell has no
@@ -5081,7 +5651,7 @@ var SpreadsheetController = (_class4 = class {
5081
5651
  }
5082
5652
  /** Cells whose current value breaks their validation rule. */
5083
5653
  findInvalidCells() {
5084
- return findInvalidCells(this.ruleSheet(), _optionalChain([this, 'access', _337 => _337.document, 'access', _338 => _338.table, 'optionalAccess', _339 => _339.dataValidations]));
5654
+ return findInvalidCells(this.ruleSheet(), _optionalChain([this, 'access', _383 => _383.document, 'access', _384 => _384.table, 'optionalAccess', _385 => _385.dataValidations]));
5085
5655
  }
5086
5656
  /**
5087
5657
  * Turns "circle invalid data" on or off. View state, not part of the
@@ -5092,7 +5662,7 @@ var SpreadsheetController = (_class4 = class {
5092
5662
  if (this.circlesShown === show) return;
5093
5663
  this.circlesShown = show;
5094
5664
  this.invalidCircles = null;
5095
- _optionalChain([this, 'access', _340 => _340.onDocumentChange, 'optionalCall', _341 => _341(this.document)]);
5665
+ _optionalChain([this, 'access', _386 => _386.onDocumentChange, 'optionalCall', _387 => _387(this.document)]);
5096
5666
  }
5097
5667
  /** Whether invalid cells are circled. */
5098
5668
  getInvalidDataCircles() {
@@ -5106,7 +5676,7 @@ var SpreadsheetController = (_class4 = class {
5106
5676
  }
5107
5677
  return this.invalidCircles.has(`${row}:${col}`);
5108
5678
  }
5109
- }, _class4);
5679
+ }, _class6);
5110
5680
  function stepInOrder(order, from, delta) {
5111
5681
  if (order.length === 0) return from;
5112
5682
  const pos = order.indexOf(from);
@@ -5155,7 +5725,7 @@ function getSpreadsheetMenuItems(controller, cell) {
5155
5725
  const row = doc.rows[cell.row];
5156
5726
  if (!column || !row) return [];
5157
5727
  const selection = controller.selectedRange && rangeContains(controller.selectedRange, cell.row, cell.col) ? normalizeRange(controller.selectedRange) : { startRow: cell.row, startCol: cell.col, endRow: cell.row, endCol: cell.col };
5158
- const colName = _chunkC7P22VIPcjs.indexToColName.call(void 0, cell.col);
5728
+ const colName = _chunkEWCOFSCQcjs.indexToColName.call(void 0, cell.col);
5159
5729
  const cellData = controller.getCell(row.id, column.id);
5160
5730
  const value = cellData.value;
5161
5731
  const table = _nullishCoalesce(doc.table, () => ( {}));
@@ -5524,7 +6094,7 @@ function getSheetTabMenuItems(controller, sheetId, options = {}) {
5524
6094
  fn();
5525
6095
  } catch (err) {
5526
6096
  if (!options.onError) throw err;
5527
- options.onError(_nullishCoalesce(_optionalChain([err, 'optionalAccess', _342 => _342.message]), () => ( String(err))));
6097
+ options.onError(_nullishCoalesce(_optionalChain([err, 'optionalAccess', _388 => _388.message]), () => ( String(err))));
5528
6098
  }
5529
6099
  };
5530
6100
  return [
@@ -5592,7 +6162,7 @@ function getNameManagerRows(controller) {
5592
6162
  value = "[LAMBDA]";
5593
6163
  } else {
5594
6164
  const result = controller.evaluateFormula(`=${n.name}`, n.sheetId ? { sheet: n.sheetId } : {});
5595
- value = result.array ? `{${result.array.rows}\xD7${result.array.cols} array}` : _chunkC7P22VIPcjs.isFormulaError.call(void 0, result.value) ? String(result.value) : formatCellDisplay({ value: result.value });
6165
+ value = result.array ? `{${result.array.rows}\xD7${result.array.cols} array}` : _chunkEWCOFSCQcjs.isFormulaError.call(void 0, result.value) ? String(result.value) : formatCellDisplay({ value: result.value });
5596
6166
  }
5597
6167
  return {
5598
6168
  name: n.name,
@@ -5606,6 +6176,188 @@ 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 = _nullishCoalesce(options.overscanRows, () => ( 8));
6251
+ const overscanColumns = _nullishCoalesce(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 = _nullishCoalesce(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
+
6346
+
6347
+
6348
+
6349
+
6350
+
6351
+
6352
+
6353
+
6354
+
6355
+
6356
+
6357
+
6358
+
6359
+
6360
+
5609
6361
 
5610
6362
 
5611
6363
 
@@ -5722,5 +6474,5 @@ function getNameManagerRows(controller) {
5722
6474
 
5723
6475
 
5724
6476
 
5725
- exports.extractNameDependencies = extractNameDependencies; exports.columnDependencyKey = columnDependencyKey; exports.extractCellDependencies = extractCellDependencies; exports.DependencyGraph = DependencyGraph; exports.shiftFormulaReferences = shiftFormulaReferences; exports.mapFormulaReferences = mapFormulaReferences; exports.offsetFormulaReferences = offsetFormulaReferences; exports.remapFormulaReferences = remapFormulaReferences; exports.sheetNameNeedsQuotes = sheetNameNeedsQuotes; exports.quoteSheetName = quoteSheetName; exports.formatSheetPrefix = formatSheetPrefix; exports.renameSheetInFormula = renameSheetInFormula; exports.deleteSheetInFormula = deleteSheetInFormula; exports.renameNameInFormula = renameNameInFormula; exports.formulaIdentifiers = formulaIdentifiers; exports.getFormulaCaretContext = getFormulaCaretContext; exports.getFormulaHints = getFormulaHints; exports.applyFormulaSuggestion = applyFormulaSuggestion; exports.NUMBER_FORMAT_PRESETS = NUMBER_FORMAT_PRESETS; exports.numberFormatForCellFormat = numberFormatForCellFormat; exports.cellFormatForNumberFormat = cellFormatForNumberFormat; exports.isGeneralNumberFormat = isGeneralNumberFormat; exports.formatCellForDisplay = formatCellForDisplay; exports.formatCellDisplay = formatCellDisplay; exports.formatValueWithCode = formatValueWithCode; exports.DataSourceBinder = DataSourceBinder; exports.MAX_SHEET_NAME_LENGTH = MAX_SHEET_NAME_LENGTH; exports.MAX_DEFINED_NAME_LENGTH = MAX_DEFINED_NAME_LENGTH; exports.validateSheetName = validateSheetName; exports.nextSheetName = nextSheetName; exports.copySheetName = copySheetName; exports.validateDefinedName = validateDefinedName; exports.isLambdaDefinition = isLambdaDefinition; exports.normalizeRange = normalizeRange; exports.rangeContains = rangeContains; exports.rangesIntersect = rangesIntersect; exports.rangeContainsRange = rangeContainsRange; exports.isSingleCellRange = isSingleCellRange; exports.isBlankValue = isBlankValue; exports.toNumber = toNumber; exports.compareCellValues = compareCellValues; exports.matchesFilterRule = matchesFilterRule; exports.aggregateValues = aggregateValues; exports.roundFloat = roundFloat; exports.fillSeries = fillSeries; exports.computeFillTarget = computeFillTarget; exports.getFillDirection = getFillDirection; exports.SPREADSHEET_CLIPBOARD_MIME = SPREADSHEET_CLIPBOARD_MIME; exports.serializeHtmlTable = serializeHtmlTable; exports.parseHtmlTable = parseHtmlTable; exports.createSpreadsheetClipboardData = createSpreadsheetClipboardData; exports.readSpreadsheetClipboard = readSpreadsheetClipboard; exports.rebaseClipboardRows = rebaseClipboardRows; exports.adjustRangeForStructuralChange = adjustRangeForStructuralChange; exports.adjustRangesForStructuralChange = adjustRangesForStructuralChange; exports.subtractRange = subtractRange; exports.subtractRangeFromRanges = subtractRangeFromRanges; exports.rangesContain = rangesContain; exports.ruleAnchor = ruleAnchor; exports.ruleFormulaAt = ruleFormulaAt; exports.shiftRuleFormula = shiftRuleFormula; exports.forEachRangeCell = forEachRangeCell; exports.formatRuleRanges = formatRuleRanges; exports.parseRuleRanges = parseRuleRanges; exports.CONDITIONAL_FORMAT_ICON_SETS = CONDITIONAL_FORMAT_ICON_SETS; exports.conditionalFormatIconCount = conditionalFormatIconCount; exports.getConditionalFormatIcon = getConditionalFormatIcon; exports.conditionalFormatNumber = conditionalFormatNumber; exports.blendConditionalFormatColors = blendConditionalFormatColors; exports.defaultIconThresholds = defaultIconThresholds; exports.EMPTY_CONDITIONAL_FORMAT_OVERLAY = EMPTY_CONDITIONAL_FORMAT_OVERLAY; exports.ConditionalFormatEngine = ConditionalFormatEngine; exports.matchesTimePeriod = matchesTimePeriod; exports.evaluateConditionalFormats = evaluateConditionalFormats; exports.describeConditionalFormat = describeConditionalFormat; exports.describeDataValidation = describeDataValidation; exports.CONDITIONAL_FORMAT_STYLE_PRESETS = CONDITIONAL_FORMAT_STYLE_PRESETS; exports.DEFAULT_CONDITIONAL_FORMAT_STYLE = DEFAULT_CONDITIONAL_FORMAT_STYLE; exports.CONDITIONAL_FORMAT_PRESETS = CONDITIONAL_FORMAT_PRESETS; exports.CONDITIONAL_FORMAT_TIME_PERIODS = CONDITIONAL_FORMAT_TIME_PERIODS; exports.createConditionalFormatRule = createConditionalFormatRule; exports.DATA_VALIDATION_TYPE_OPTIONS = DATA_VALIDATION_TYPE_OPTIONS; exports.DATA_VALIDATION_OPERATOR_OPTIONS = DATA_VALIDATION_OPERATOR_OPTIONS; exports.DATA_VALIDATION_ERROR_STYLE_OPTIONS = DATA_VALIDATION_ERROR_STYLE_OPTIONS; exports.conditionalFormatContrastColor = conditionalFormatContrastColor; exports.getConditionalFormatCss = getConditionalFormatCss; exports.getDataBarCss = getDataBarCss; exports.conditionalFormatStyleString = conditionalFormatStyleString; exports.DEFAULT_DATA_VALIDATION_MESSAGE = DEFAULT_DATA_VALIDATION_MESSAGE; exports.DEFAULT_DATA_VALIDATION_TITLE = DEFAULT_DATA_VALIDATION_TITLE; exports.findDataValidation = findDataValidation; exports.getValidationListValues = getValidationListValues; exports.validateCellInput = validateCellInput; exports.findInvalidCells = findInvalidCells; exports.DEFAULT_ROW_HEIGHT = DEFAULT_ROW_HEIGHT; exports.DEFAULT_COLUMN_WIDTH = DEFAULT_COLUMN_WIDTH; exports.getTableSections = getTableSections; exports.getRowSection = getRowSection; exports.rowPassesFilters = rowPassesFilters; exports.computeRowGroups = computeRowGroups; exports.buildSpreadsheetView = buildSpreadsheetView; exports.SpreadsheetController = SpreadsheetController; exports.runSpreadsheetMenuItem = runSpreadsheetMenuItem; exports.clampMenuPosition = clampMenuPosition; exports.getSpreadsheetMenuItems = getSpreadsheetMenuItems; exports.SPREADSHEET_TABLE_ACTIONS = SPREADSHEET_TABLE_ACTIONS; exports.bindSpreadsheetTableActions = bindSpreadsheetTableActions; exports.csvImportAdapter = csvImportAdapter; exports.tsvImportAdapter = tsvImportAdapter; exports.SPREADSHEET_IMPORT_ADAPTERS = SPREADSHEET_IMPORT_ADAPTERS; exports.registerSpreadsheetImportAdapters = registerSpreadsheetImportAdapters; exports.importFileIntoSpreadsheet = importFileIntoSpreadsheet; exports.getSheetTabMenuItems = getSheetTabMenuItems; exports.sheetDropIndex = sheetDropIndex; exports.getNameManagerRows = getNameManagerRows;
5726
- //# sourceMappingURL=chunk-WSQKHXW7.cjs.map
6477
+ exports.extractNameDependencies = extractNameDependencies; exports.columnDependencyKey = columnDependencyKey; exports.extractCellDependencies = extractCellDependencies; exports.extractReferenceDependencies = extractReferenceDependencies; exports.rangeDependencyKey = rangeDependencyKey; exports.IntervalIndex = IntervalIndex; exports.DependencyGraph = DependencyGraph; exports.shiftFormulaReferences = shiftFormulaReferences; exports.mapFormulaReferences = mapFormulaReferences; exports.offsetFormulaReferences = offsetFormulaReferences; exports.remapFormulaReferences = remapFormulaReferences; exports.sheetNameNeedsQuotes = sheetNameNeedsQuotes; exports.quoteSheetName = quoteSheetName; exports.formatSheetPrefix = formatSheetPrefix; exports.renameSheetInFormula = renameSheetInFormula; exports.deleteSheetInFormula = deleteSheetInFormula; exports.renameNameInFormula = renameNameInFormula; exports.formulaIdentifiers = formulaIdentifiers; exports.getFormulaCaretContext = getFormulaCaretContext; exports.getFormulaHints = getFormulaHints; exports.applyFormulaSuggestion = applyFormulaSuggestion; exports.NUMBER_FORMAT_PRESETS = NUMBER_FORMAT_PRESETS; exports.numberFormatForCellFormat = numberFormatForCellFormat; exports.cellFormatForNumberFormat = cellFormatForNumberFormat; exports.isGeneralNumberFormat = isGeneralNumberFormat; exports.formatCellForDisplay = formatCellForDisplay; exports.formatCellDisplay = formatCellDisplay; exports.formatValueWithCode = formatValueWithCode; exports.DataSourceBinder = DataSourceBinder; exports.MAX_SHEET_NAME_LENGTH = MAX_SHEET_NAME_LENGTH; exports.MAX_DEFINED_NAME_LENGTH = MAX_DEFINED_NAME_LENGTH; exports.validateSheetName = validateSheetName; exports.nextSheetName = nextSheetName; exports.copySheetName = copySheetName; exports.validateDefinedName = validateDefinedName; exports.isLambdaDefinition = isLambdaDefinition; exports.normalizeRange = normalizeRange; exports.rangeContains = rangeContains; exports.rangesIntersect = rangesIntersect; exports.rangeContainsRange = rangeContainsRange; exports.isSingleCellRange = isSingleCellRange; exports.isBlankValue = isBlankValue; exports.toNumber = toNumber; exports.compareCellValues = compareCellValues; exports.matchesFilterRule = matchesFilterRule; exports.aggregateValues = aggregateValues; exports.roundFloat = roundFloat; exports.fillSeries = fillSeries; exports.computeFillTarget = computeFillTarget; exports.getFillDirection = getFillDirection; exports.SPREADSHEET_CLIPBOARD_MIME = SPREADSHEET_CLIPBOARD_MIME; exports.serializeHtmlTable = serializeHtmlTable; exports.parseHtmlTable = parseHtmlTable; exports.createSpreadsheetClipboardData = createSpreadsheetClipboardData; exports.readSpreadsheetClipboard = readSpreadsheetClipboard; exports.rebaseClipboardRows = rebaseClipboardRows; exports.adjustRangeForStructuralChange = adjustRangeForStructuralChange; exports.adjustRangesForStructuralChange = adjustRangesForStructuralChange; exports.subtractRange = subtractRange; exports.subtractRangeFromRanges = subtractRangeFromRanges; exports.rangesContain = rangesContain; exports.ruleAnchor = ruleAnchor; exports.ruleFormulaAt = ruleFormulaAt; exports.shiftRuleFormula = shiftRuleFormula; exports.forEachRangeCell = forEachRangeCell; exports.formatRuleRanges = formatRuleRanges; exports.parseRuleRanges = parseRuleRanges; exports.CONDITIONAL_FORMAT_ICON_SETS = CONDITIONAL_FORMAT_ICON_SETS; exports.conditionalFormatIconCount = conditionalFormatIconCount; exports.getConditionalFormatIcon = getConditionalFormatIcon; exports.conditionalFormatNumber = conditionalFormatNumber; exports.blendConditionalFormatColors = blendConditionalFormatColors; exports.defaultIconThresholds = defaultIconThresholds; exports.EMPTY_CONDITIONAL_FORMAT_OVERLAY = EMPTY_CONDITIONAL_FORMAT_OVERLAY; exports.ConditionalFormatEngine = ConditionalFormatEngine; exports.matchesTimePeriod = matchesTimePeriod; exports.evaluateConditionalFormats = evaluateConditionalFormats; exports.describeConditionalFormat = describeConditionalFormat; exports.describeDataValidation = describeDataValidation; exports.CONDITIONAL_FORMAT_STYLE_PRESETS = CONDITIONAL_FORMAT_STYLE_PRESETS; exports.DEFAULT_CONDITIONAL_FORMAT_STYLE = DEFAULT_CONDITIONAL_FORMAT_STYLE; exports.CONDITIONAL_FORMAT_PRESETS = CONDITIONAL_FORMAT_PRESETS; exports.CONDITIONAL_FORMAT_TIME_PERIODS = CONDITIONAL_FORMAT_TIME_PERIODS; exports.createConditionalFormatRule = createConditionalFormatRule; exports.DATA_VALIDATION_TYPE_OPTIONS = DATA_VALIDATION_TYPE_OPTIONS; exports.DATA_VALIDATION_OPERATOR_OPTIONS = DATA_VALIDATION_OPERATOR_OPTIONS; exports.DATA_VALIDATION_ERROR_STYLE_OPTIONS = DATA_VALIDATION_ERROR_STYLE_OPTIONS; exports.conditionalFormatContrastColor = conditionalFormatContrastColor; exports.getConditionalFormatCss = getConditionalFormatCss; exports.getDataBarCss = getDataBarCss; exports.conditionalFormatStyleString = conditionalFormatStyleString; exports.DEFAULT_DATA_VALIDATION_MESSAGE = DEFAULT_DATA_VALIDATION_MESSAGE; exports.DEFAULT_DATA_VALIDATION_TITLE = DEFAULT_DATA_VALIDATION_TITLE; exports.findDataValidation = findDataValidation; exports.getValidationListValues = getValidationListValues; exports.validateCellInput = validateCellInput; exports.findInvalidCells = findInvalidCells; exports.DEFAULT_ROW_HEIGHT = DEFAULT_ROW_HEIGHT; exports.DEFAULT_COLUMN_WIDTH = DEFAULT_COLUMN_WIDTH; exports.getTableSections = getTableSections; exports.getRowSection = getRowSection; exports.rowPassesFilters = rowPassesFilters; exports.computeRowGroups = computeRowGroups; exports.buildSpreadsheetView = buildSpreadsheetView; exports.SpreadsheetController = SpreadsheetController; exports.runSpreadsheetMenuItem = runSpreadsheetMenuItem; exports.clampMenuPosition = clampMenuPosition; exports.getSpreadsheetMenuItems = getSpreadsheetMenuItems; exports.SPREADSHEET_TABLE_ACTIONS = SPREADSHEET_TABLE_ACTIONS; exports.bindSpreadsheetTableActions = bindSpreadsheetTableActions; exports.csvImportAdapter = csvImportAdapter; exports.tsvImportAdapter = tsvImportAdapter; exports.SPREADSHEET_IMPORT_ADAPTERS = SPREADSHEET_IMPORT_ADAPTERS; exports.registerSpreadsheetImportAdapters = registerSpreadsheetImportAdapters; exports.importFileIntoSpreadsheet = importFileIntoSpreadsheet; exports.getSheetTabMenuItems = getSheetTabMenuItems; exports.sheetDropIndex = sheetDropIndex; exports.getNameManagerRows = getNameManagerRows; exports.SPREADSHEET_COLUMN_HEADER_HEIGHT = SPREADSHEET_COLUMN_HEADER_HEIGHT; exports.SPREADSHEET_ROW_HEADER_WIDTH = SPREADSHEET_ROW_HEADER_WIDTH; exports.SPREADSHEET_GROUP_ROW_HEIGHT = SPREADSHEET_GROUP_ROW_HEIGHT; exports.SPREADSHEET_VIRTUAL_ROW_THRESHOLD = SPREADSHEET_VIRTUAL_ROW_THRESHOLD; exports.SPREADSHEET_VIRTUAL_COLUMN_THRESHOLD = SPREADSHEET_VIRTUAL_COLUMN_THRESHOLD; exports.SPREADSHEET_DEFAULT_VIEWPORT = SPREADSHEET_DEFAULT_VIEWPORT; exports.measureSpreadsheetView = measureSpreadsheetView; exports.shouldVirtualizeSpreadsheet = shouldVirtualizeSpreadsheet; exports.fullSpreadsheetViewWindow = fullSpreadsheetViewWindow; exports.getSpreadsheetViewWindow = getSpreadsheetViewWindow; exports.getSpreadsheetScrollToCell = getSpreadsheetScrollToCell; exports.sameSpreadsheetViewWindow = sameSpreadsheetViewWindow;
6478
+ //# sourceMappingURL=chunk-NQF32I6Z.cjs.map