ooxml-core 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/dist/{PptxHandler-SJTINPU4.mjs → PptxHandler-ZPJNHEZZ.mjs} +2 -2
  2. package/dist/automation/index.cjs +3294 -661
  3. package/dist/automation/index.mjs +3294 -661
  4. package/dist/chart/bar-cluster-geometry.d.ts +6 -0
  5. package/dist/chart/bar-cluster-geometry.d.ts.map +1 -1
  6. package/dist/chart/index.cjs +5 -2
  7. package/dist/chart/index.mjs +5 -3
  8. package/dist/chart/render/chart-spacing.d.ts.map +1 -1
  9. package/dist/{chunk-TEOAOAIF.mjs → chunk-BB55ASTX.mjs} +3 -3
  10. package/dist/{chunk-CQQAPUAG.mjs → chunk-H4TPDZV4.mjs} +11 -9
  11. package/dist/{chunk-VYTA2OUN.mjs → chunk-IVDC75E5.mjs} +1 -1
  12. package/dist/{chunk-FI4RSJ24.mjs → chunk-QPQDLSJQ.mjs} +1 -1
  13. package/dist/{chunk-XCZ7U3GZ.mjs → chunk-XGLKW3JJ.mjs} +13 -9
  14. package/dist/docx/index.cjs +4 -2
  15. package/dist/docx/index.mjs +4 -2
  16. package/dist/docx/layout/index.cjs +4 -2
  17. package/dist/docx/layout/index.mjs +4 -2
  18. package/dist/docx/ui/index.cjs +4 -2
  19. package/dist/docx/ui/index.mjs +4 -2
  20. package/dist/index.cjs +5 -2
  21. package/dist/index.mjs +5 -2
  22. package/dist/pptx/automation/index.cjs +29 -17
  23. package/dist/pptx/automation/index.mjs +5 -5
  24. package/dist/pptx/chunks/index-EZJZ7USt.d.ts.map +1 -1
  25. package/dist/pptx/cli/index.cjs +29 -17
  26. package/dist/pptx/cli/index.mjs +4 -4
  27. package/dist/pptx/converter/index.mjs +2 -2
  28. package/dist/pptx/index.cjs +29 -17
  29. package/dist/pptx/index.mjs +5 -5
  30. package/dist/teams/index.cjs +176 -92
  31. package/dist/teams/index.mjs +176 -92
  32. package/dist/visio/clipboard-resources.d.ts +11 -0
  33. package/dist/visio/clipboard-resources.d.ts.map +1 -0
  34. package/dist/visio/clipboard-types.d.ts +26 -0
  35. package/dist/visio/clipboard-types.d.ts.map +1 -0
  36. package/dist/visio/clipboard-xml.d.ts +8 -0
  37. package/dist/visio/clipboard-xml.d.ts.map +1 -0
  38. package/dist/visio/clipboard.d.ts +7 -0
  39. package/dist/visio/clipboard.d.ts.map +1 -0
  40. package/dist/visio/create-document.d.ts +8 -0
  41. package/dist/visio/create-document.d.ts.map +1 -0
  42. package/dist/visio/edit-commands.d.ts +19 -2
  43. package/dist/visio/edit-commands.d.ts.map +1 -1
  44. package/dist/visio/edit-delete.d.ts +9 -0
  45. package/dist/visio/edit-delete.d.ts.map +1 -0
  46. package/dist/visio/edit-duplicate-commands.d.ts +14 -0
  47. package/dist/visio/edit-duplicate-commands.d.ts.map +1 -0
  48. package/dist/visio/edit-duplicate-scope.d.ts +4 -0
  49. package/dist/visio/edit-duplicate-scope.d.ts.map +1 -0
  50. package/dist/visio/edit-duplicate.d.ts +10 -0
  51. package/dist/visio/edit-duplicate.d.ts.map +1 -0
  52. package/dist/visio/edit-formatting-cell-admission.d.ts +3 -0
  53. package/dist/visio/edit-formatting-cell-admission.d.ts.map +1 -0
  54. package/dist/visio/edit-formatting-commands.d.ts +46 -0
  55. package/dist/visio/edit-formatting-commands.d.ts.map +1 -0
  56. package/dist/visio/edit-formatting-font.d.ts +6 -0
  57. package/dist/visio/edit-formatting-font.d.ts.map +1 -0
  58. package/dist/visio/edit-formatting-paint-scope.d.ts +6 -0
  59. package/dist/visio/edit-formatting-paint-scope.d.ts.map +1 -0
  60. package/dist/visio/edit-formatting-paint.d.ts +6 -0
  61. package/dist/visio/edit-formatting-paint.d.ts.map +1 -0
  62. package/dist/visio/edit-formatting-rows.d.ts +14 -0
  63. package/dist/visio/edit-formatting-rows.d.ts.map +1 -0
  64. package/dist/visio/edit-formatting-scope.d.ts +6 -0
  65. package/dist/visio/edit-formatting-scope.d.ts.map +1 -0
  66. package/dist/visio/edit-formatting-text.d.ts +8 -0
  67. package/dist/visio/edit-formatting-text.d.ts.map +1 -0
  68. package/dist/visio/edit-formatting.d.ts +12 -0
  69. package/dist/visio/edit-formatting.d.ts.map +1 -0
  70. package/dist/visio/edit-geometry.d.ts.map +1 -1
  71. package/dist/visio/edit-paste-commands.d.ts +15 -0
  72. package/dist/visio/edit-paste-commands.d.ts.map +1 -0
  73. package/dist/visio/edit-paste.d.ts +5 -0
  74. package/dist/visio/edit-paste.d.ts.map +1 -0
  75. package/dist/visio/edit-recalculate.d.ts +3 -1
  76. package/dist/visio/edit-recalculate.d.ts.map +1 -1
  77. package/dist/visio/edit-resize-anchor.d.ts +8 -0
  78. package/dist/visio/edit-resize-anchor.d.ts.map +1 -0
  79. package/dist/visio/edit-scope.d.ts.map +1 -1
  80. package/dist/visio/edit-shape-create.d.ts +1 -1
  81. package/dist/visio/edit-shape-create.d.ts.map +1 -1
  82. package/dist/visio/edit-shape-order.d.ts +7 -0
  83. package/dist/visio/edit-shape-order.d.ts.map +1 -0
  84. package/dist/visio/edit-style-admission.d.ts +17 -0
  85. package/dist/visio/edit-style-admission.d.ts.map +1 -0
  86. package/dist/visio/edit-text-scope.d.ts +7 -0
  87. package/dist/visio/edit-text-scope.d.ts.map +1 -0
  88. package/dist/visio/edit-text.d.ts +1 -1
  89. package/dist/visio/edit-text.d.ts.map +1 -1
  90. package/dist/visio/edit.d.ts +1 -1
  91. package/dist/visio/edit.d.ts.map +1 -1
  92. package/dist/visio/index.cjs +3245 -1077
  93. package/dist/visio/index.d.ts +3 -1
  94. package/dist/visio/index.d.ts.map +1 -1
  95. package/dist/visio/index.mjs +3241 -1079
  96. package/dist/visio/line-pattern.d.ts +1 -1
  97. package/dist/visio/line-pattern.d.ts.map +1 -1
  98. package/dist/visio/line-style.d.ts.map +1 -1
  99. package/dist/visio/model.d.ts +23 -3
  100. package/dist/visio/model.d.ts.map +1 -1
  101. package/dist/visio/parser.d.ts.map +1 -1
  102. package/dist/visio/plain-text.d.ts +5 -0
  103. package/dist/visio/plain-text.d.ts.map +1 -0
  104. package/dist/visio/resize-anchor.d.ts +27 -0
  105. package/dist/visio/resize-anchor.d.ts.map +1 -0
  106. package/dist/visio/source-paint.d.ts +5 -0
  107. package/dist/visio/source-paint.d.ts.map +1 -0
  108. package/dist/visio/style.d.ts.map +1 -1
  109. package/dist/visio/theme-root.d.ts.map +1 -1
  110. package/dist/visio/ui/contract.d.ts +10 -6
  111. package/dist/visio/ui/contract.d.ts.map +1 -1
  112. package/dist/visio/ui/draw-plan.d.ts +32 -0
  113. package/dist/visio/ui/draw-plan.d.ts.map +1 -0
  114. package/dist/visio/ui/edit-commands.d.ts.map +1 -1
  115. package/dist/visio/ui/formatting.d.ts +28 -0
  116. package/dist/visio/ui/formatting.d.ts.map +1 -0
  117. package/dist/visio/ui/index.cjs +1305 -15
  118. package/dist/visio/ui/index.d.ts +15 -0
  119. package/dist/visio/ui/index.d.ts.map +1 -1
  120. package/dist/visio/ui/index.mjs +1261 -16
  121. package/dist/visio/ui/line-dash.d.ts +4 -0
  122. package/dist/visio/ui/line-dash.d.ts.map +1 -0
  123. package/dist/visio/ui/marquee.d.ts +15 -0
  124. package/dist/visio/ui/marquee.d.ts.map +1 -0
  125. package/dist/visio/ui/page-edit.d.ts.map +1 -1
  126. package/dist/visio/ui/scene-validation.d.ts.map +1 -1
  127. package/dist/visio/ui/selection.d.ts +12 -0
  128. package/dist/visio/ui/selection.d.ts.map +1 -0
  129. package/dist/visio/ui/shape-arrange.d.ts +17 -0
  130. package/dist/visio/ui/shape-arrange.d.ts.map +1 -0
  131. package/dist/visio/ui/shape-clipboard.d.ts +10 -0
  132. package/dist/visio/ui/shape-clipboard.d.ts.map +1 -0
  133. package/dist/visio/ui/shape-duplicate.d.ts +5 -0
  134. package/dist/visio/ui/shape-duplicate.d.ts.map +1 -0
  135. package/dist/visio/ui/shape-formatting.d.ts +14 -0
  136. package/dist/visio/ui/shape-formatting.d.ts.map +1 -0
  137. package/dist/visio/ui/shape-move.d.ts +14 -0
  138. package/dist/visio/ui/shape-move.d.ts.map +1 -0
  139. package/dist/visio/ui/shape-order.d.ts +4 -0
  140. package/dist/visio/ui/shape-order.d.ts.map +1 -0
  141. package/dist/visio/ui/shape-resize.d.ts +61 -0
  142. package/dist/visio/ui/shape-resize.d.ts.map +1 -0
  143. package/dist/visio/ui/size-position.d.ts +19 -0
  144. package/dist/visio/ui/size-position.d.ts.map +1 -0
  145. package/dist/visio/ui/snapshot-scene.d.ts.map +1 -1
  146. package/dist/visio/ui/svg-stroke.d.ts +8 -0
  147. package/dist/visio/ui/svg-stroke.d.ts.map +1 -0
  148. package/dist/visio/ui/text-replace-selection.d.ts +9 -0
  149. package/dist/visio/ui/text-replace-selection.d.ts.map +1 -0
  150. package/dist/visio/ui/text-replace-snapshot.d.ts +14 -0
  151. package/dist/visio/ui/text-replace-snapshot.d.ts.map +1 -0
  152. package/dist/visio/ui/text-replace-types.d.ts +48 -0
  153. package/dist/visio/ui/text-replace-types.d.ts.map +1 -0
  154. package/dist/visio/ui/text-replace.d.ts +18 -0
  155. package/dist/visio/ui/text-replace.d.ts.map +1 -0
  156. package/dist/xlsx/edit/context.d.ts +3 -1
  157. package/dist/xlsx/edit/context.d.ts.map +1 -1
  158. package/dist/xlsx/edit/history.d.ts +18 -0
  159. package/dist/xlsx/edit/history.d.ts.map +1 -1
  160. package/dist/xlsx/edit/session-calc.d.ts +30 -8
  161. package/dist/xlsx/edit/session-calc.d.ts.map +1 -1
  162. package/dist/xlsx/edit/session.d.ts.map +1 -1
  163. package/dist/xlsx/edit/sheets.d.ts.map +1 -1
  164. package/dist/xlsx/edit/types.d.ts +23 -3
  165. package/dist/xlsx/edit/types.d.ts.map +1 -1
  166. package/dist/xlsx/formula/engine-core.d.ts.map +1 -1
  167. package/dist/xlsx/formula/engine-host.d.ts +6 -2
  168. package/dist/xlsx/formula/engine-host.d.ts.map +1 -1
  169. package/dist/xlsx/formula/engine-prepare.d.ts +33 -0
  170. package/dist/xlsx/formula/engine-prepare.d.ts.map +1 -0
  171. package/dist/xlsx/formula/engine-structure.d.ts +40 -0
  172. package/dist/xlsx/formula/engine-structure.d.ts.map +1 -0
  173. package/dist/xlsx/formula/engine-types.d.ts +26 -0
  174. package/dist/xlsx/formula/engine-types.d.ts.map +1 -1
  175. package/dist/xlsx/formula/engine.d.ts.map +1 -1
  176. package/dist/xlsx/formula/formula-shape.d.ts +28 -0
  177. package/dist/xlsx/formula/formula-shape.d.ts.map +1 -0
  178. package/dist/xlsx/formula/graph-index.d.ts +3 -1
  179. package/dist/xlsx/formula/graph-index.d.ts.map +1 -1
  180. package/dist/xlsx/formula/graph.d.ts +5 -0
  181. package/dist/xlsx/formula/graph.d.ts.map +1 -1
  182. package/dist/xlsx/formula/lexemes.d.ts.map +1 -1
  183. package/dist/xlsx/formula/parse-cache.d.ts +9 -0
  184. package/dist/xlsx/formula/parse-cache.d.ts.map +1 -0
  185. package/dist/xlsx/formula/parser.d.ts +3 -0
  186. package/dist/xlsx/formula/parser.d.ts.map +1 -1
  187. package/dist/xlsx/formula/ref-text.d.ts +14 -0
  188. package/dist/xlsx/formula/ref-text.d.ts.map +1 -0
  189. package/dist/xlsx/formula/references.d.ts.map +1 -1
  190. package/dist/xlsx/formula/shift-formula.d.ts +19 -0
  191. package/dist/xlsx/formula/shift-formula.d.ts.map +1 -0
  192. package/dist/xlsx/formula/shift-graph.d.ts +42 -0
  193. package/dist/xlsx/formula/shift-graph.d.ts.map +1 -0
  194. package/dist/xlsx/formula/transform.d.ts +2 -23
  195. package/dist/xlsx/formula/transform.d.ts.map +1 -1
  196. package/dist/xlsx/index.cjs +1087 -338
  197. package/dist/xlsx/index.mjs +1087 -339
  198. package/dist/xlsx/layout/index.d.ts +1 -1
  199. package/dist/xlsx/layout/index.d.ts.map +1 -1
  200. package/dist/xlsx/layout/sheet-queries.d.ts +7 -0
  201. package/dist/xlsx/layout/sheet-queries.d.ts.map +1 -1
  202. package/dist/xlsx/load/index.cjs +170 -99
  203. package/dist/xlsx/load/index.mjs +170 -99
  204. package/dist/xlsx/ui/index.cjs +1072 -332
  205. package/dist/xlsx/ui/index.mjs +1072 -332
  206. package/package.json +1 -1
@@ -2040,7 +2040,7 @@ var NAME_RE = /^[A-Za-z_\\\u00A1-\uFFFF][A-Za-z0-9_.?\\\u00A1-\uFFFF]*/;
2040
2040
  var CELL_RE = /^(\$?)([A-Za-z]{1,3})(\$?)(\d{1,7})/;
2041
2041
  var COLS_RE = /^(\$?)([A-Za-z]{1,3}):(\$?)([A-Za-z]{1,3})/;
2042
2042
  var ROWS_RE = /^(\$?)(\d{1,7}):(\$?)(\d{1,7})/;
2043
- var NUMBER_RE2 = /^(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/;
2043
+ var NUMBER_RE2 = /^(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?/;
2044
2044
  var sticky = (re) => new RegExp(re.source.replace(/^\^/, ""), "y");
2045
2045
  var CELL_AT = sticky(CELL_RE);
2046
2046
  var COLS_AT = sticky(COLS_RE);
@@ -2200,14 +2200,13 @@ function splitSheets(text2) {
2200
2200
  if (book !== void 0) out.book = book;
2201
2201
  return out;
2202
2202
  }
2203
- var PREFIX_AT = /(\[\d+\])?([A-Za-z0-9_.\u00A1-\uFFFF]+)(?::([A-Za-z0-9_.\u00A1-\uFFFF]+))?!/y;
2204
2203
  function readPrefix(source, at) {
2205
2204
  const ch = source[at];
2206
2205
  if (ch === "'") {
2207
2206
  const quoted = readQuoted(source, at);
2208
2207
  if (source[at + quoted.length] !== "!") return void 0;
2209
- const length = quoted.length + 1;
2210
- return { prefix: { text: source.slice(at, at + length), ...splitSheets(quoted.name) }, length };
2208
+ const length2 = quoted.length + 1;
2209
+ return { prefix: { text: source.slice(at, at + length2), ...splitSheets(quoted.name) }, length: length2 };
2211
2210
  }
2212
2211
  let i2 = at;
2213
2212
  if (ch === "[") {
@@ -2216,13 +2215,23 @@ function readPrefix(source, at) {
2216
2215
  if (i2 === at + 1 || source[i2] !== "]") return void 0;
2217
2216
  i2++;
2218
2217
  }
2218
+ const sheetFrom = i2;
2219
2219
  while (i2 < source.length && isSheetCharCode(source.charCodeAt(i2))) i2++;
2220
- if (source[i2] !== "!" && source[i2] !== ":") return void 0;
2221
- const m = matchAt(PREFIX_AT, source, at);
2222
- if (!m) return void 0;
2223
- if (!m[1] && /^\d/.test(m[2] ?? "") && !m[3]) return void 0;
2224
- const raw = m[0].slice(0, -1);
2225
- return { prefix: { text: m[0], ...splitSheets(raw) }, length: m[0].length };
2220
+ if (i2 === sheetFrom) return void 0;
2221
+ let lastFrom = -1;
2222
+ if (source[i2] === ":") {
2223
+ lastFrom = i2 + 1;
2224
+ i2 = lastFrom;
2225
+ while (i2 < source.length && isSheetCharCode(source.charCodeAt(i2))) i2++;
2226
+ if (i2 === lastFrom) return void 0;
2227
+ }
2228
+ if (source[i2] !== "!") return void 0;
2229
+ if (ch !== "[" && lastFrom < 0 && isDigitCode(source.charCodeAt(sheetFrom))) return void 0;
2230
+ const length = i2 + 1 - at;
2231
+ return {
2232
+ prefix: { text: source.slice(at, at + length), ...splitSheets(source.slice(at, i2)) },
2233
+ length
2234
+ };
2226
2235
  }
2227
2236
  function numberLiteral(text2) {
2228
2237
  if (text2.length <= 15) return Number(text2);
@@ -2412,7 +2421,9 @@ var PREC_INTERSECT = 9;
2412
2421
  var PREC_RANGE = 10;
2413
2422
  var OPERAND_START = /* @__PURE__ */ new Set(["ref", "name", "func", "open", "structured"]);
2414
2423
  function parseFormula(formula) {
2415
- const all2 = tokenize(formula);
2424
+ return parseTokens(tokenize(formula));
2425
+ }
2426
+ function parseTokens(all2) {
2416
2427
  const tokens = [];
2417
2428
  const wsBefore = [];
2418
2429
  let sawWs = false;
@@ -2697,6 +2708,222 @@ function localDateToSerial(date, date1904 = date1904Mode) {
2697
2708
  return base + (utc - dayStart) / DAY_MS;
2698
2709
  }
2699
2710
 
2711
+ // xlsx/formula/area-index.ts
2712
+ var BUCKET_WIDTH = 64;
2713
+ var CHUNK_SHIFT = 6;
2714
+ var CHUNKS = 64;
2715
+ function push(map, key, value2) {
2716
+ const list = map.get(key);
2717
+ if (list) list.push(value2);
2718
+ else map.set(key, [value2]);
2719
+ }
2720
+ function inner(map, sheet) {
2721
+ let found = map.get(sheet);
2722
+ if (!found) map.set(sheet, found = /* @__PURE__ */ new Map());
2723
+ return found;
2724
+ }
2725
+ var AreaIndex = class {
2726
+ cells = /* @__PURE__ */ new Map();
2727
+ chunked = /* @__PURE__ */ new Map();
2728
+ tall = /* @__PURE__ */ new Map();
2729
+ wide = /* @__PURE__ */ new Map();
2730
+ add(area, node) {
2731
+ const { start, end } = area.range;
2732
+ if (start.row === end.row && end.col - start.col + 1 <= BUCKET_WIDTH) {
2733
+ const cells = inner(this.cells, area.sheet);
2734
+ for (let col = start.col; col <= end.col; col++) push(cells, cellKey(start.row, col), node);
2735
+ return;
2736
+ }
2737
+ const entry = { range: area.range, node };
2738
+ if (end.col - start.col + 1 > BUCKET_WIDTH) {
2739
+ const list = this.wide.get(area.sheet);
2740
+ if (list) list.push(entry);
2741
+ else this.wide.set(area.sheet, [entry]);
2742
+ return;
2743
+ }
2744
+ const first2 = start.row >> CHUNK_SHIFT;
2745
+ const last = end.row >> CHUNK_SHIFT;
2746
+ if (last - first2 + 1 > CHUNKS) {
2747
+ const cols2 = inner(this.tall, area.sheet);
2748
+ for (let c = start.col; c <= end.col; c++) push(cols2, c, entry);
2749
+ return;
2750
+ }
2751
+ const cols = inner(this.chunked, area.sheet);
2752
+ for (let c = start.col; c <= end.col; c++) {
2753
+ let chunks = cols.get(c);
2754
+ if (!chunks) cols.set(c, chunks = /* @__PURE__ */ new Map());
2755
+ for (let k = first2; k <= last; k++) push(chunks, k, entry);
2756
+ }
2757
+ }
2758
+ /** Items whose areas intersect `range` on `sheet` (each once). */
2759
+ dependents(sheet, range2, out) {
2760
+ const found = out instanceof Set ? out : /* @__PURE__ */ new Set();
2761
+ this.collect(sheet, range2, found);
2762
+ if (Array.isArray(out)) for (const node of found) out.push(node);
2763
+ }
2764
+ collect(sheet, range2, out) {
2765
+ const { start, end } = range2;
2766
+ const check3 = (list) => {
2767
+ if (list) {
2768
+ for (const entry of list) if (rangesIntersect(entry.range, range2)) out.add(entry.node);
2769
+ }
2770
+ };
2771
+ const cells = this.cells.get(sheet);
2772
+ if (cells) {
2773
+ const size = (end.row - start.row + 1) * (end.col - start.col + 1);
2774
+ if (size <= 4096) {
2775
+ for (let r = start.row; r <= end.row; r++) {
2776
+ for (let c = start.col; c <= end.col; c++) {
2777
+ const list = cells.get(cellKey(r, c));
2778
+ if (list) for (const node of list) out.add(node);
2779
+ }
2780
+ }
2781
+ } else {
2782
+ for (const [key, list] of cells) {
2783
+ const { row: row2, col } = keyToAddress(key);
2784
+ if (row2 >= start.row && row2 <= end.row && col >= start.col && col <= end.col)
2785
+ for (const node of list) out.add(node);
2786
+ }
2787
+ }
2788
+ }
2789
+ const columns = (map, visit2) => {
2790
+ if (!map) return;
2791
+ if (end.col - start.col + 1 <= map.size) {
2792
+ for (let c = start.col; c <= end.col; c++) if (map.has(c)) visit2(c);
2793
+ } else for (const c of map.keys()) if (c >= start.col && c <= end.col) visit2(c);
2794
+ };
2795
+ const chunked = this.chunked.get(sheet);
2796
+ const first2 = start.row >> CHUNK_SHIFT;
2797
+ const last = end.row >> CHUNK_SHIFT;
2798
+ columns(chunked, (c) => {
2799
+ const chunks = chunked?.get(c);
2800
+ if (last - first2 + 1 <= chunks.size) {
2801
+ for (let k = first2; k <= last; k++) check3(chunks.get(k));
2802
+ } else for (const [k, list] of chunks) if (k >= first2 && k <= last) check3(list);
2803
+ });
2804
+ const tall = this.tall.get(sheet);
2805
+ columns(tall, (c) => check3(tall?.get(c)));
2806
+ check3(this.wide.get(sheet));
2807
+ }
2808
+ };
2809
+
2810
+ // xlsx/formula/graph-index.ts
2811
+ var ColumnIndex = class {
2812
+ sheets = /* @__PURE__ */ new Map();
2813
+ constructor(nodes) {
2814
+ for (const node of nodes) {
2815
+ let cols = this.sheets.get(node.sheet);
2816
+ if (!cols) this.sheets.set(node.sheet, cols = /* @__PURE__ */ new Map());
2817
+ let list = cols.get(node.col);
2818
+ if (!list) cols.set(node.col, list = []);
2819
+ list.push(node);
2820
+ }
2821
+ for (const cols of this.sheets.values())
2822
+ for (const list of cols.values()) list.sort((a, b) => a.row - b.row);
2823
+ }
2824
+ nodesIn(area, out) {
2825
+ const cols = this.sheets.get(area.sheet);
2826
+ if (!cols) return;
2827
+ const { start, end } = area.range;
2828
+ const width = end.col - start.col + 1;
2829
+ const scan2 = (list) => {
2830
+ let lo = 0;
2831
+ let hi = list.length;
2832
+ while (lo < hi) {
2833
+ const mid = lo + hi >> 1;
2834
+ if (list[mid].row < start.row) lo = mid + 1;
2835
+ else hi = mid;
2836
+ }
2837
+ for (let i2 = lo; i2 < list.length; i2++) {
2838
+ const node = list[i2];
2839
+ if (node.row > end.row) break;
2840
+ out.push(node);
2841
+ }
2842
+ };
2843
+ if (width <= cols.size) {
2844
+ for (let c = start.col; c <= end.col; c++) {
2845
+ const list = cols.get(c);
2846
+ if (list) scan2(list);
2847
+ }
2848
+ } else {
2849
+ for (const [c, list] of cols) if (c >= start.col && c <= end.col) scan2(list);
2850
+ }
2851
+ }
2852
+ };
2853
+ var ReverseIndex = class extends AreaIndex {
2854
+ constructor(nodes = []) {
2855
+ super();
2856
+ for (const node of nodes) this.addNode(node);
2857
+ }
2858
+ /** Indexes one more formula's precedents. */
2859
+ addNode(node) {
2860
+ for (const dep of node.deps) this.add(dep, node);
2861
+ for (const dep of node.dynamicDeps) this.add(dep, node);
2862
+ }
2863
+ };
2864
+
2865
+ // xlsx/formula/engine-prepare.ts
2866
+ var CHECK_EVERY = 32;
2867
+ var GraphPreparation = class {
2868
+ constructor(workbook, create) {
2869
+ this.workbook = workbook;
2870
+ this.create = create;
2871
+ }
2872
+ workbook;
2873
+ create;
2874
+ reverse = new ReverseIndex();
2875
+ formulas = { dynamic: [], unvalued: [] };
2876
+ sheet = 0;
2877
+ rows;
2878
+ row = 0;
2879
+ cells;
2880
+ finished = false;
2881
+ /**
2882
+ * Adds formulas to the graph until `timeRemaining` (milliseconds, as an idle deadline reports
2883
+ * them) reaches zero, or until every formula is in. Returns true when the graph is complete.
2884
+ */
2885
+ run(timeRemaining) {
2886
+ let visited = 0;
2887
+ while (!this.finished) {
2888
+ const cells = this.cells ?? this.nextRow();
2889
+ if (!cells) continue;
2890
+ const next = cells.next();
2891
+ if (next.done) {
2892
+ this.cells = void 0;
2893
+ continue;
2894
+ }
2895
+ const [col, cell] = next.value;
2896
+ if (cell.formula) this.add(this.create(this.sheet, this.row, col, cell), cell);
2897
+ if (timeRemaining && ++visited % CHECK_EVERY === 0 && timeRemaining() <= 0) break;
2898
+ }
2899
+ return this.finished;
2900
+ }
2901
+ add(node, cell) {
2902
+ this.reverse.addNode(node);
2903
+ if (!node.legacy && !node.arrayRange) this.formulas.dynamic.push(node);
2904
+ if (cell.value === null && !node.keepCached) this.formulas.unvalued.push(node);
2905
+ }
2906
+ /** Moves to the next stored row (and sheet), or marks the walk finished. */
2907
+ nextRow() {
2908
+ for (; ; ) {
2909
+ const sheet = this.workbook.sheets[this.sheet];
2910
+ if (!sheet) {
2911
+ this.finished = true;
2912
+ return void 0;
2913
+ }
2914
+ this.rows ??= sheet.rows.entries();
2915
+ const next = this.rows.next();
2916
+ if (!next.done) {
2917
+ this.row = next.value[0];
2918
+ this.cells = next.value[1].entries();
2919
+ return this.cells;
2920
+ }
2921
+ this.rows = void 0;
2922
+ this.sheet++;
2923
+ }
2924
+ }
2925
+ };
2926
+
2700
2927
  // xlsx/formula/text-number.ts
2701
2928
  var MONTHS2 = [
2702
2929
  "jan",
@@ -5831,8 +6058,10 @@ var FINANCIAL_FUNCTIONS = [
5831
6058
 
5832
6059
  // xlsx/formula/references.ts
5833
6060
  function sheetIndex(host, name) {
6061
+ const sheets = host.workbook.sheets;
6062
+ for (let i2 = 0; i2 < sheets.length; i2++) if (sheets[i2]?.name === name) return i2;
5834
6063
  const lower2 = name.toLowerCase();
5835
- return host.workbook.sheets.findIndex((sheet) => sheet.name.toLowerCase() === lower2);
6064
+ return sheets.findIndex((sheet) => sheet.name.toLowerCase() === lower2);
5836
6065
  }
5837
6066
  function prefixSheets(host, prefix, current) {
5838
6067
  if (!prefix) return [current];
@@ -7402,9 +7631,9 @@ function readBlock(ctx, area, rows2, cols) {
7402
7631
  if (host.readBlock) return host.readBlock(area.sheet, range2);
7403
7632
  return Matrix.build(rows2, cols, (r, c) => ctx.readCell(area.sheet, row2 + r, col + c));
7404
7633
  }
7405
- function shapedMatrices(ctx, values2, sameShape) {
7634
+ function shapedMatrices(ctx, values2, sameShape2) {
7406
7635
  let { rows: rows2, cols } = shapeOf(values2[0] ?? null);
7407
- if (sameShape) {
7636
+ if (sameShape2) {
7408
7637
  for (const value2 of values2) {
7409
7638
  const shape2 = shapeOf(value2);
7410
7639
  if (shape2.rows !== rows2 || shape2.cols !== cols) fail(ERR.VALUE);
@@ -7430,12 +7659,12 @@ function shapedMatrices(ctx, values2, sameShape) {
7430
7659
  });
7431
7660
  return { matrices, total: total2, block: rows2 * cols };
7432
7661
  }
7433
- function matchingCells(ctx, pairs, target2, sameShape) {
7662
+ function matchingCells(ctx, pairs, target2, sameShape2) {
7434
7663
  const ranges = pairs.map((p) => p.range);
7435
7664
  const { matrices, total: total2, block } = shapedMatrices(
7436
7665
  ctx,
7437
7666
  target2 === void 0 ? ranges : [...ranges, target2],
7438
- sameShape
7667
+ sameShape2
7439
7668
  );
7440
7669
  const tests = pairs.map((p) => makeCriteria(scalar(p.criteria)));
7441
7670
  const values2 = matrices[matrices.length - 1];
@@ -7474,7 +7703,7 @@ function indexedCandidates(pairs, matrices) {
7474
7703
  }
7475
7704
  return void 0;
7476
7705
  }
7477
- var matchingValues = (ctx, pairs, target2, sameShape) => matchingCells(ctx, pairs, target2, sameShape).values;
7706
+ var matchingValues = (ctx, pairs, target2, sameShape2) => matchingCells(ctx, pairs, target2, sameShape2).values;
7478
7707
  function criteriaPairs(args, from) {
7479
7708
  if ((args.length - from) % 2 !== 0) fail(ERR.VALUE);
7480
7709
  const pairs = [];
@@ -11368,7 +11597,7 @@ var REFERENCE_ARGUMENT = /* @__PURE__ */ new Set([
11368
11597
  "AREAS",
11369
11598
  "ISREF"
11370
11599
  ]);
11371
- var push = (c, area) => {
11600
+ var push2 = (c, area) => {
11372
11601
  (c.dynamic > 0 ? c.dynamicDeps : c.deps).push(area);
11373
11602
  };
11374
11603
  function spans(areas) {
@@ -11394,7 +11623,7 @@ function visit(node, c) {
11394
11623
  if (!sheets) return;
11395
11624
  const range2 = specRange(node.ref);
11396
11625
  const target2 = node.spill ? { start: range2.start, end: { ...range2.start } } : range2;
11397
- for (const sheet of sheets) push(c, { sheet, range: target2 });
11626
+ for (const sheet of sheets) push2(c, { sheet, range: target2 });
11398
11627
  return;
11399
11628
  }
11400
11629
  case "name": {
@@ -11468,7 +11697,7 @@ function visitBinary(node, c) {
11468
11697
  const list = c.dynamic > 0 ? c.dynamicDeps : c.deps;
11469
11698
  const l = list[list.length - 2];
11470
11699
  const r = list[list.length - 1];
11471
- if (l && r && l.sheet === r.sheet) push(c, spans([l, r])[0]);
11700
+ if (l && r && l.sheet === r.sheet) push2(c, spans([l, r])[0]);
11472
11701
  continue;
11473
11702
  }
11474
11703
  const mentioned = [...c.deps.slice(staticStart), ...c.dynamicDeps.slice(dynamicStart)];
@@ -11484,7 +11713,7 @@ function addStructured(ref, c) {
11484
11713
  sheet: c.sheet,
11485
11714
  row: c.row,
11486
11715
  col: c.col});
11487
- if (value2 instanceof RefValue) for (const area of value2.areas) push(c, area);
11716
+ if (value2 instanceof RefValue) for (const area of value2.areas) push2(c, area);
11488
11717
  }
11489
11718
  function analyze(ast, host, sheet, row2, col) {
11490
11719
  const c = {
@@ -11508,155 +11737,191 @@ function analyze(ast, host, sheet, row2, col) {
11508
11737
  };
11509
11738
  }
11510
11739
 
11511
- // xlsx/formula/area-index.ts
11512
- var BUCKET_WIDTH = 64;
11513
- var CHUNK_SHIFT = 6;
11514
- var CHUNKS = 64;
11515
- function push2(map, key, value2) {
11516
- const list = map.get(key);
11517
- if (list) list.push(value2);
11518
- else map.set(key, [value2]);
11740
+ // xlsx/formula/formula-shape.ts
11741
+ var isDigit4 = (code) => code >= 48 && code <= 57;
11742
+ function formulaShape(formula) {
11743
+ const starts = [];
11744
+ const ends = [];
11745
+ let hash = 2166136261;
11746
+ for (let i2 = 0; i2 < formula.length; i2++) {
11747
+ let code = formula.charCodeAt(i2);
11748
+ if (isDigit4(code)) {
11749
+ let j = i2 + 1;
11750
+ while (j < formula.length && isDigit4(formula.charCodeAt(j))) j++;
11751
+ starts.push(i2);
11752
+ ends.push(j);
11753
+ i2 = j - 1;
11754
+ code = 0;
11755
+ }
11756
+ hash = Math.imul(hash ^ code, 16777619);
11757
+ }
11758
+ return { hash: hash >>> 0, starts, ends };
11519
11759
  }
11520
- function inner(map, sheet) {
11521
- let found = map.get(sheet);
11522
- if (!found) map.set(sheet, found = /* @__PURE__ */ new Map());
11523
- return found;
11760
+ function sameText(a, from, b, at, length) {
11761
+ for (let k = 0; k < length; k++)
11762
+ if (a.charCodeAt(from + k) !== b.charCodeAt(at + k)) return false;
11763
+ return true;
11524
11764
  }
11525
- var AreaIndex = class {
11526
- cells = /* @__PURE__ */ new Map();
11527
- chunked = /* @__PURE__ */ new Map();
11528
- tall = /* @__PURE__ */ new Map();
11529
- wide = /* @__PURE__ */ new Map();
11530
- add(area, node) {
11531
- const { start, end } = area.range;
11532
- if (start.row === end.row && end.col - start.col + 1 <= BUCKET_WIDTH) {
11533
- const cells = inner(this.cells, area.sheet);
11534
- for (let col = start.col; col <= end.col; col++) push2(cells, cellKey(start.row, col), node);
11535
- return;
11765
+ function sameShape(a, as, b, bs) {
11766
+ const runs = as.starts.length;
11767
+ if (runs !== bs.starts.length) return false;
11768
+ let fromA = 0;
11769
+ let fromB = 0;
11770
+ for (let i2 = 0; i2 <= runs; i2++) {
11771
+ const toA = i2 < runs ? as.starts[i2] : a.length;
11772
+ const toB = i2 < runs ? bs.starts[i2] : b.length;
11773
+ if (toA - fromA !== toB - fromB || !sameText(a, fromA, b, fromB, toA - fromA)) return false;
11774
+ fromA = as.ends[i2] ?? 0;
11775
+ fromB = bs.ends[i2] ?? 0;
11776
+ }
11777
+ return true;
11778
+ }
11779
+ function makeTemplate(formula, shape2) {
11780
+ let tokens;
11781
+ let ast;
11782
+ try {
11783
+ tokens = tokenize(formula);
11784
+ ast = parseTokens(tokens);
11785
+ } catch (e) {
11786
+ if (e instanceof RangeError) return new FormulaError(e.message, -1, "#VALUE!");
11787
+ if (e instanceof FormulaError) return e;
11788
+ throw e;
11789
+ }
11790
+ const { starts } = shape2;
11791
+ const slotted = starts.map(() => false);
11792
+ const refs = /* @__PURE__ */ new Map();
11793
+ let run = 0;
11794
+ for (const token of tokens) {
11795
+ const end = token.start + token.text.length;
11796
+ const first2 = run;
11797
+ while (run < starts.length && starts[run] < end) run++;
11798
+ const ref = token.kind === "ref" ? token.ref : void 0;
11799
+ if (!ref || ref.kind === "cols") continue;
11800
+ const count2 = ref.kind === "cell" ? 1 : 2;
11801
+ if (run - first2 < count2) continue;
11802
+ const at = run - count2;
11803
+ const indexes2 = count2 === 1 ? [at] : [at, at + 1];
11804
+ if (indexes2.some((i2) => formula.charCodeAt(starts[i2]) === 48)) continue;
11805
+ for (const i2 of indexes2) slotted[i2] = true;
11806
+ refs.set(ref, count2 === 1 ? { start: at } : { start: at, end: at + 1 });
11807
+ }
11808
+ return { formula, shape: shape2, ast, slotted, refs };
11809
+ }
11810
+ function rowsFor(t, formula, shape2) {
11811
+ if (!sameShape(t.formula, t.shape, formula, shape2)) return void 0;
11812
+ const rows2 = [];
11813
+ for (let i2 = 0; i2 < shape2.starts.length; i2++) {
11814
+ const start = shape2.starts[i2];
11815
+ const length = shape2.ends[i2] - start;
11816
+ if (!t.slotted[i2]) {
11817
+ const own = t.shape.starts[i2];
11818
+ if (t.shape.ends[i2] - own !== length) return void 0;
11819
+ if (!sameText(t.formula, own, formula, start, length)) return void 0;
11820
+ rows2.push(-1);
11821
+ continue;
11536
11822
  }
11537
- const entry = { range: area.range, node };
11538
- if (end.col - start.col + 1 > BUCKET_WIDTH) {
11539
- const list = this.wide.get(area.sheet);
11540
- if (list) list.push(entry);
11541
- else this.wide.set(area.sheet, [entry]);
11542
- return;
11823
+ if (length > 7 || formula.charCodeAt(start) === 48) return void 0;
11824
+ let row2 = 0;
11825
+ for (let k = start; k < start + length; k++) row2 = row2 * 10 + formula.charCodeAt(k) - 48;
11826
+ if (row2 < 1 || row2 > MAX_ROW + 1) return void 0;
11827
+ rows2.push(row2 - 1);
11828
+ }
11829
+ return rows2;
11830
+ }
11831
+ function shiftSpec(spec2, slots, rows2) {
11832
+ const startRow = rows2[slots.start];
11833
+ const endRow = slots.end === void 0 ? startRow : rows2[slots.end];
11834
+ if (startRow === spec2.start.row && endRow === spec2.end.row) return spec2;
11835
+ const { start: from, end: to } = spec2;
11836
+ const start = { row: startRow, col: from.col, rowAbs: from.rowAbs, colAbs: from.colAbs };
11837
+ if (slots.end === void 0) return { kind: spec2.kind, start, end: start };
11838
+ const end = { row: endRow, col: to.col, rowAbs: to.rowAbs, colAbs: to.colAbs };
11839
+ return { kind: spec2.kind, start, end };
11840
+ }
11841
+ function copyTree(node, t, rows2) {
11842
+ switch (node.type) {
11843
+ case "ref": {
11844
+ const slots = node.ref ? t.refs.get(node.ref) : void 0;
11845
+ if (!slots || !node.ref) return node;
11846
+ const ref = shiftSpec(node.ref, slots, rows2);
11847
+ if (ref === node.ref) return node;
11848
+ const copy2 = { type: "ref" };
11849
+ if (node.prefix) copy2.prefix = node.prefix;
11850
+ copy2.ref = ref;
11851
+ if (node.spill) copy2.spill = true;
11852
+ return copy2;
11543
11853
  }
11544
- const first2 = start.row >> CHUNK_SHIFT;
11545
- const last = end.row >> CHUNK_SHIFT;
11546
- if (last - first2 + 1 > CHUNKS) {
11547
- const cols2 = inner(this.tall, area.sheet);
11548
- for (let c = start.col; c <= end.col; c++) push2(cols2, c, entry);
11549
- return;
11854
+ case "unary":
11855
+ case "percent": {
11856
+ const operand4 = copyTree(node.operand, t, rows2);
11857
+ if (operand4 === node.operand) return node;
11858
+ return node.type === "unary" ? { type: "unary", op: node.op, operand: operand4 } : { type: "percent", operand: operand4 };
11550
11859
  }
11551
- const cols = inner(this.chunked, area.sheet);
11552
- for (let c = start.col; c <= end.col; c++) {
11553
- let chunks = cols.get(c);
11554
- if (!chunks) cols.set(c, chunks = /* @__PURE__ */ new Map());
11555
- for (let k = first2; k <= last; k++) push2(chunks, k, entry);
11860
+ case "binary": {
11861
+ const left2 = copyTree(node.left, t, rows2);
11862
+ const right2 = copyTree(node.right, t, rows2);
11863
+ if (left2 === node.left && right2 === node.right) return node;
11864
+ return { type: "binary", op: node.op, left: left2, right: right2 };
11865
+ }
11866
+ case "call": {
11867
+ const args = copyAll(node.args, t, rows2);
11868
+ if (args === node.args) return node;
11869
+ return { type: "call", name: node.name, rawName: node.rawName, args };
11870
+ }
11871
+ case "invoke": {
11872
+ const callee = copyTree(node.callee, t, rows2);
11873
+ const args = copyAll(node.args, t, rows2);
11874
+ if (callee === node.callee && args === node.args) return node;
11875
+ return { type: "invoke", callee, args };
11556
11876
  }
11877
+ default:
11878
+ return node;
11557
11879
  }
11558
- /** Items whose areas intersect `range` on `sheet` (each once). */
11559
- dependents(sheet, range2, out) {
11560
- const found = out instanceof Set ? out : /* @__PURE__ */ new Set();
11561
- this.collect(sheet, range2, found);
11562
- if (Array.isArray(out)) for (const node of found) out.push(node);
11880
+ }
11881
+ function copyAll(nodes, t, rows2) {
11882
+ let out;
11883
+ for (let i2 = 0; i2 < nodes.length; i2++) {
11884
+ const node = nodes[i2];
11885
+ const copy2 = copyTree(node, t, rows2);
11886
+ if (copy2 !== node && !out) out = nodes.slice(0, i2);
11887
+ out?.push(copy2);
11888
+ }
11889
+ return out ?? nodes;
11890
+ }
11891
+ function fromTemplate(t, formula, shape2) {
11892
+ const rows2 = rowsFor(t, formula, shape2);
11893
+ return rows2 ? copyTree(t.ast, t, rows2) : void 0;
11894
+ }
11895
+
11896
+ // xlsx/formula/parse-cache.ts
11897
+ var MAX_TEMPLATE_LENGTH = 2048;
11898
+ var MAX_ENTRIES = 1e5;
11899
+ var MAX_TEMPLATES = 1e4;
11900
+ var NO_RUNS = { hash: 0, starts: [], ends: [] };
11901
+ var treeOf = (made) => "ast" in made ? made.ast : made;
11902
+ var ParseCache = class {
11903
+ byText = /* @__PURE__ */ new Map();
11904
+ templates = /* @__PURE__ */ new Map();
11905
+ parse(formula) {
11906
+ let ast = this.byText.get(formula);
11907
+ if (ast) return ast;
11908
+ ast = this.parseNew(formula);
11909
+ if (this.byText.size > MAX_ENTRIES) this.byText.clear();
11910
+ this.byText.set(formula, ast);
11911
+ return ast;
11563
11912
  }
11564
- collect(sheet, range2, out) {
11565
- const { start, end } = range2;
11566
- const check3 = (list) => {
11567
- if (list) {
11568
- for (const entry of list) if (rangesIntersect(entry.range, range2)) out.add(entry.node);
11569
- }
11570
- };
11571
- const cells = this.cells.get(sheet);
11572
- if (cells) {
11573
- const size = (end.row - start.row + 1) * (end.col - start.col + 1);
11574
- if (size <= 4096) {
11575
- for (let r = start.row; r <= end.row; r++) {
11576
- for (let c = start.col; c <= end.col; c++) {
11577
- const list = cells.get(cellKey(r, c));
11578
- if (list) for (const node of list) out.add(node);
11579
- }
11580
- }
11581
- } else {
11582
- for (const [key, list] of cells) {
11583
- const { row: row2, col } = keyToAddress(key);
11584
- if (row2 >= start.row && row2 <= end.row && col >= start.col && col <= end.col)
11585
- for (const node of list) out.add(node);
11586
- }
11587
- }
11588
- }
11589
- const columns = (map, visit2) => {
11590
- if (!map) return;
11591
- if (end.col - start.col + 1 <= map.size) {
11592
- for (let c = start.col; c <= end.col; c++) if (map.has(c)) visit2(c);
11593
- } else for (const c of map.keys()) if (c >= start.col && c <= end.col) visit2(c);
11594
- };
11595
- const chunked = this.chunked.get(sheet);
11596
- const first2 = start.row >> CHUNK_SHIFT;
11597
- const last = end.row >> CHUNK_SHIFT;
11598
- columns(chunked, (c) => {
11599
- const chunks = chunked?.get(c);
11600
- if (last - first2 + 1 <= chunks.size) {
11601
- for (let k = first2; k <= last; k++) check3(chunks.get(k));
11602
- } else for (const [k, list] of chunks) if (k >= first2 && k <= last) check3(list);
11603
- });
11604
- const tall = this.tall.get(sheet);
11605
- columns(tall, (c) => check3(tall?.get(c)));
11606
- check3(this.wide.get(sheet));
11607
- }
11608
- };
11609
-
11610
- // xlsx/formula/graph-index.ts
11611
- var ColumnIndex = class {
11612
- sheets = /* @__PURE__ */ new Map();
11613
- constructor(nodes) {
11614
- for (const node of nodes) {
11615
- let cols = this.sheets.get(node.sheet);
11616
- if (!cols) this.sheets.set(node.sheet, cols = /* @__PURE__ */ new Map());
11617
- let list = cols.get(node.col);
11618
- if (!list) cols.set(node.col, list = []);
11619
- list.push(node);
11620
- }
11621
- for (const cols of this.sheets.values())
11622
- for (const list of cols.values()) list.sort((a, b) => a.row - b.row);
11623
- }
11624
- nodesIn(area, out) {
11625
- const cols = this.sheets.get(area.sheet);
11626
- if (!cols) return;
11627
- const { start, end } = area.range;
11628
- const width = end.col - start.col + 1;
11629
- const scan2 = (list) => {
11630
- let lo = 0;
11631
- let hi = list.length;
11632
- while (lo < hi) {
11633
- const mid = lo + hi >> 1;
11634
- if (list[mid].row < start.row) lo = mid + 1;
11635
- else hi = mid;
11636
- }
11637
- for (let i2 = lo; i2 < list.length; i2++) {
11638
- const node = list[i2];
11639
- if (node.row > end.row) break;
11640
- out.push(node);
11641
- }
11642
- };
11643
- if (width <= cols.size) {
11644
- for (let c = start.col; c <= end.col; c++) {
11645
- const list = cols.get(c);
11646
- if (list) scan2(list);
11647
- }
11648
- } else {
11649
- for (const [c, list] of cols) if (c >= start.col && c <= end.col) scan2(list);
11650
- }
11651
- }
11652
- };
11653
- var ReverseIndex = class extends AreaIndex {
11654
- constructor(nodes) {
11655
- super();
11656
- for (const node of nodes) {
11657
- for (const dep of node.deps) this.add(dep, node);
11658
- for (const dep of node.dynamicDeps) this.add(dep, node);
11913
+ parseNew(formula) {
11914
+ if (formula.length > MAX_TEMPLATE_LENGTH) return treeOf(makeTemplate(formula, NO_RUNS));
11915
+ const shape2 = formulaShape(formula);
11916
+ const template = this.templates.get(shape2.hash);
11917
+ const reused = template && fromTemplate(template, formula, shape2);
11918
+ if (reused) return reused;
11919
+ const made = makeTemplate(formula, shape2);
11920
+ if (!template && "ast" in made) {
11921
+ if (this.templates.size > MAX_TEMPLATES) this.templates.clear();
11922
+ this.templates.set(shape2.hash, made);
11659
11923
  }
11924
+ return treeOf(made);
11660
11925
  }
11661
11926
  };
11662
11927
 
@@ -11796,7 +12061,7 @@ var EngineHost = class {
11796
12061
  options;
11797
12062
  nodes = /* @__PURE__ */ new Map();
11798
12063
  footprints = /* @__PURE__ */ new Set();
11799
- astCache = /* @__PURE__ */ new Map();
12064
+ parsed = new ParseCache();
11800
12065
  boundsCache = /* @__PURE__ */ new Map();
11801
12066
  inProgress = /* @__PURE__ */ new Set();
11802
12067
  cycles = /* @__PURE__ */ new Set();
@@ -11820,6 +12085,8 @@ var EngineHost = class {
11820
12085
  evaluations = 0;
11821
12086
  built = false;
11822
12087
  needsFull = false;
12088
+ /** No recalculation has run since the workbook was opened: stored values may stand. */
12089
+ fresh = true;
11823
12090
  // ---- EvalHost ----
11824
12091
  readCell(sheet, row2, col) {
11825
12092
  const cell = this.workbook.sheets[sheet]?.rows.get(row2)?.get(col);
@@ -11908,19 +12175,7 @@ var EngineHost = class {
11908
12175
  return this.workbook.sheets[sheet]?.rows.get(row2)?.get(col)?.formula;
11909
12176
  }
11910
12177
  parse(formula) {
11911
- let ast = this.astCache.get(formula);
11912
- if (!ast) {
11913
- try {
11914
- ast = parseFormula(formula);
11915
- } catch (e) {
11916
- if (e instanceof RangeError) ast = new FormulaError(e.message, -1, "#VALUE!");
11917
- else if (!(e instanceof FormulaError)) throw e;
11918
- else ast = e;
11919
- }
11920
- if (this.astCache.size > 1e5) this.astCache.clear();
11921
- this.astCache.set(formula, ast);
11922
- }
11923
- return ast;
12178
+ return this.parsed.parse(formula);
11924
12179
  }
11925
12180
  now() {
11926
12181
  return this.options.now ? this.options.now() : /* @__PURE__ */ new Date();
@@ -11929,20 +12184,6 @@ var EngineHost = class {
11929
12184
  return this.options.random ? this.options.random() : Math.random();
11930
12185
  }
11931
12186
  // ---- graph ----
11932
- build() {
11933
- this.nodes.clear();
11934
- this.footprints.clear();
11935
- this.footprintIndex = void 0;
11936
- this.workbook.sheets.forEach((sheet, s) => {
11937
- for (const [row2, cells] of sheet.rows) {
11938
- for (const [col, cell] of cells) {
11939
- if (cell.formula) this.createNode(s, row2, col, cell, true);
11940
- }
11941
- }
11942
- });
11943
- this.built = true;
11944
- this.graphChanged();
11945
- }
11946
12187
  createNode(sheet, row2, col, cell, initial) {
11947
12188
  const formula = cell.formula ?? "";
11948
12189
  const parsed = this.parse(formula);
@@ -11971,6 +12212,14 @@ var EngineHost = class {
11971
12212
  map.set(cellKey(row2, col), node);
11972
12213
  return node;
11973
12214
  }
12215
+ /** Parses a node's text again after a structural edit rewrote it (see `FormulaNode.stale`). */
12216
+ refreshAst(node) {
12217
+ const parsed = this.parse(node.formula);
12218
+ delete node.stale;
12219
+ delete node.parseError;
12220
+ node.ast = parsed instanceof FormulaError ? void 0 : parsed;
12221
+ if (parsed instanceof FormulaError && parsed.code) node.parseError = err(parsed.code);
12222
+ }
11974
12223
  removeNode(node, changed) {
11975
12224
  const sheet = this.workbook.sheets[node.sheet];
11976
12225
  if (node.spill && sheet) {
@@ -12401,6 +12650,7 @@ var EngineCore = class extends EngineHost {
12401
12650
  return { host: this, sheet, row: row2, col, scope: void 0, depth: 0, root, legacy };
12402
12651
  }
12403
12652
  evaluateFormula(node) {
12653
+ if (node.stale) this.refreshAst(node);
12404
12654
  if (!node.ast) return node.parseError ?? ERR.NAME;
12405
12655
  const ast = node.ast;
12406
12656
  const frame = this.frameFor(node.sheet, node.row, node.col, ast, node.legacy);
@@ -12418,20 +12668,363 @@ var EngineCore = class extends EngineHost {
12418
12668
  }
12419
12669
  };
12420
12670
 
12671
+ // xlsx/formula/shift-graph.ts
12672
+ var POSITIONAL = /* @__PURE__ */ new Set(["ROW", "COLUMN"]);
12673
+ var DESCRIPTIVE = /* @__PURE__ */ new Set(["FORMULATEXT", "CELL", "SHEET"]);
12674
+ var traitsCache = /* @__PURE__ */ new WeakMap();
12675
+ function astTraits(ast) {
12676
+ const cached = traitsCache.get(ast);
12677
+ if (cached) return cached;
12678
+ const sheets = /* @__PURE__ */ new Set();
12679
+ const traits = { complex: false, opaque: false, positional: false, descriptive: false, sheets };
12680
+ const stack2 = [ast];
12681
+ while (stack2.length) {
12682
+ const node = stack2.pop();
12683
+ switch (node.type) {
12684
+ case "ref":
12685
+ if (node.prefix) {
12686
+ sheets.add(node.prefix.sheet.toLowerCase());
12687
+ if (node.prefix.sheet2 !== void 0) {
12688
+ sheets.add(node.prefix.sheet2.toLowerCase());
12689
+ traits.complex = true;
12690
+ }
12691
+ }
12692
+ break;
12693
+ case "name":
12694
+ if (node.prefix) sheets.add(node.prefix.sheet.toLowerCase());
12695
+ traits.complex = true;
12696
+ break;
12697
+ case "structured":
12698
+ traits.complex = true;
12699
+ break;
12700
+ case "unary":
12701
+ case "percent":
12702
+ stack2.push(node.operand);
12703
+ break;
12704
+ case "binary":
12705
+ stack2.push(node.left, node.right);
12706
+ break;
12707
+ case "call":
12708
+ if (POSITIONAL.has(node.name)) traits.positional = true;
12709
+ if (DESCRIPTIVE.has(node.name)) traits.descriptive = true;
12710
+ if (!getFunction(node.name)) traits.complex = traits.opaque = true;
12711
+ stack2.push(...node.args);
12712
+ break;
12713
+ case "invoke":
12714
+ stack2.push(node.callee, ...node.args);
12715
+ break;
12716
+ }
12717
+ }
12718
+ traitsCache.set(ast, traits);
12719
+ return traits;
12720
+ }
12721
+ function intervalDelta(a, b, shift2) {
12722
+ const { at, count: count2 } = shift2;
12723
+ if (count2 > 0) {
12724
+ if (b < at) return 0;
12725
+ if (a < at) return void 0;
12726
+ return b + count2 > (shift2.axis === "row" ? MAX_ROW : MAX_COL) ? void 0 : count2;
12727
+ }
12728
+ const last = at - count2 - 1;
12729
+ if (b < at) return 0;
12730
+ if (a > last) return count2;
12731
+ return void 0;
12732
+ }
12733
+ function areaDelta(area, sheet, shift2) {
12734
+ if (area.sheet !== sheet) return 0;
12735
+ const { start, end } = area.range;
12736
+ return shift2.axis === "row" ? intervalDelta(start.row, end.row, shift2) : intervalDelta(start.col, end.col, shift2);
12737
+ }
12738
+ function movedRange(range2, axis, delta) {
12739
+ if (delta === 0) return range2;
12740
+ return axis === "row" ? {
12741
+ start: { row: range2.start.row + delta, col: range2.start.col },
12742
+ end: { row: range2.end.row + delta, col: range2.end.col }
12743
+ } : {
12744
+ start: { row: range2.start.row, col: range2.start.col + delta },
12745
+ end: { row: range2.end.row, col: range2.end.col + delta }
12746
+ };
12747
+ }
12748
+ function reachesEdit(areas, sheet, shift2) {
12749
+ for (const area of areas) {
12750
+ if (area.sheet !== sheet) continue;
12751
+ if ((shift2.axis === "row" ? area.range.end.row : area.range.end.col) >= shift2.at) return true;
12752
+ }
12753
+ return false;
12754
+ }
12755
+ function intersectionMoves(area, delta, pos, nodeDelta, axis) {
12756
+ if (delta === nodeDelta) return false;
12757
+ const { start, end } = area.range;
12758
+ const a = axis === "row" ? start.row : start.col;
12759
+ const b = axis === "row" ? end.row : end.col;
12760
+ if (a === b) return false;
12761
+ return pos >= a && pos <= b || pos + nodeDelta >= a + delta && pos + nodeDelta <= b + delta;
12762
+ }
12763
+
12764
+ // xlsx/formula/engine-structure.ts
12765
+ var sameAreas = (a, b) => a.length === b.length && a.every((x, i2) => {
12766
+ const y = b[i2];
12767
+ return x.sheet === y.sheet && sameRange(x.range, y.range);
12768
+ });
12769
+ var EngineStructure = class extends EngineCore {
12770
+ /** Formulas a structural edit left to recalculate (with their dependents) on the next pass. */
12771
+ structuralSeeds = /* @__PURE__ */ new Set();
12772
+ /** Whether the graph describes the workbook as it was before the edit just reported. */
12773
+ graphReady() {
12774
+ return this.built && !this.needsFull;
12775
+ }
12776
+ /** Parses a node's current text again and recomputes its precedents. */
12777
+ reanalyze(node) {
12778
+ const parsed = this.parse(node.formula);
12779
+ delete node.stale;
12780
+ delete node.parseError;
12781
+ if (parsed instanceof FormulaError) {
12782
+ node.ast = void 0;
12783
+ if (parsed.code) node.parseError = err(parsed.code);
12784
+ node.deps = [];
12785
+ node.dynamicDeps = [];
12786
+ return;
12787
+ }
12788
+ node.ast = parsed;
12789
+ const found = analyze(parsed, this, node.sheet, node.row, node.col);
12790
+ node.deps = found.deps;
12791
+ node.dynamicDeps = found.dynamicDeps;
12792
+ node.volatile = found.volatile;
12793
+ node.unsupported = found.unsupported;
12794
+ }
12795
+ /**
12796
+ * Moves the graph for rows or columns inserted or deleted on `sheet`, after the workbook was
12797
+ * changed (cells moved, formulas rewritten). Spills on the sheet, array formulas the edit cuts
12798
+ * and anything unexpected fall back to a rebuild on the next recalculation.
12799
+ */
12800
+ shiftCells(sheet, shift2) {
12801
+ if (!this.graphReady() || !this.shiftable(sheet, shift2)) {
12802
+ this.invalidate();
12803
+ return;
12804
+ }
12805
+ const ws = this.workbook.sheets[sheet];
12806
+ const own = this.nodes.get(sheet);
12807
+ if (!ws) return;
12808
+ const moved = /* @__PURE__ */ new Map();
12809
+ if (own) {
12810
+ const next = /* @__PURE__ */ new Map();
12811
+ for (const node of own.values()) {
12812
+ const pos = shift2.axis === "row" ? node.row : node.col;
12813
+ const delta = intervalDelta(pos, pos, shift2);
12814
+ if (delta === void 0) {
12815
+ this.dropNode(node);
12816
+ continue;
12817
+ }
12818
+ if (delta !== 0) {
12819
+ moved.set(node, delta);
12820
+ if (shift2.axis === "row") node.row += delta;
12821
+ else node.col += delta;
12822
+ }
12823
+ next.set(cellKey(node.row, node.col), node);
12824
+ }
12825
+ this.nodes.set(sheet, next);
12826
+ }
12827
+ for (const map of this.nodes.values())
12828
+ for (const node of map.values()) {
12829
+ if (!this.shiftNode(node, sheet, shift2, moved.get(node) ?? 0)) {
12830
+ this.invalidate();
12831
+ return;
12832
+ }
12833
+ }
12834
+ this.boundsCache.clear();
12835
+ this.graphChanged();
12836
+ }
12837
+ /** No spill or stale spilled value on the sheet, and no array formula the edit cuts. */
12838
+ shiftable(sheet, shift2) {
12839
+ for (const node of this.footprints) {
12840
+ if (node.sheet !== sheet) continue;
12841
+ if (node.spill || node.blockedSpill) return false;
12842
+ if (node.arrayRange && areaDelta({ sheet, range: node.arrayRange }, sheet, shift2) === void 0)
12843
+ return false;
12844
+ }
12845
+ const ws = this.workbook.sheets[sheet];
12846
+ let spilled = false;
12847
+ ws?.rows.forEach((cells) => {
12848
+ if (!spilled) cells.forEach((cell) => spilled ||= isSpilledCell(cell));
12849
+ });
12850
+ return !spilled;
12851
+ }
12852
+ dropNode(node) {
12853
+ this.structuralSeeds.delete(node);
12854
+ this.cycles.delete(node);
12855
+ this.footprints.delete(node);
12856
+ }
12857
+ /** Updates one formula for the edit; false when the graph cannot follow it. */
12858
+ shiftNode(node, sheet, shift2, nodeDelta) {
12859
+ const cell = this.workbook.sheets[node.sheet]?.rows.get(node.row)?.get(node.col);
12860
+ if (cell?.formula === void 0) return false;
12861
+ if (node.arrayRange) {
12862
+ const delta = areaDelta({ sheet: node.sheet, range: node.arrayRange }, sheet, shift2);
12863
+ if (delta === void 0) return false;
12864
+ node.arrayRange = movedRange(node.arrayRange, shift2.axis, delta);
12865
+ if (!cell.arrayRange || !sameRange(node.arrayRange, normalizeRange(cell.arrayRange)))
12866
+ return false;
12867
+ }
12868
+ const changed = cell.formula !== node.formula;
12869
+ const traits = node.ast && astTraits(node.ast);
12870
+ const reached = reachesEdit(node.deps, sheet, shift2) || reachesEdit(node.dynamicDeps, sheet, shift2);
12871
+ if (!changed && !reached && nodeDelta === 0 && !traits?.descriptive && !traits?.opaque)
12872
+ return true;
12873
+ if (traits?.complex) {
12874
+ node.formula = cell.formula;
12875
+ this.reanalyze(node);
12876
+ this.structuralSeeds.add(node);
12877
+ return true;
12878
+ }
12879
+ const pos = shift2.axis === "row" ? node.row - nodeDelta : node.col - nodeDelta;
12880
+ const moves = { seed: !!traits?.descriptive, moved: false, cut: false };
12881
+ const deps = this.moveAreas(node.deps, sheet, shift2, pos, nodeDelta, moves);
12882
+ const dynamicDeps = this.moveAreas(node.dynamicDeps, sheet, shift2, pos, nodeDelta, moves);
12883
+ const { cut, moved: anyMoved } = moves;
12884
+ let seed = moves.seed;
12885
+ if (cut || changed !== anyMoved) {
12886
+ node.formula = cell.formula;
12887
+ this.reanalyze(node);
12888
+ this.structuralSeeds.add(node);
12889
+ return true;
12890
+ }
12891
+ if (traits?.positional && (anyMoved || nodeDelta !== 0)) seed = true;
12892
+ node.deps = deps;
12893
+ node.dynamicDeps = dynamicDeps;
12894
+ if (changed) {
12895
+ node.formula = cell.formula;
12896
+ node.stale = true;
12897
+ }
12898
+ if (seed) this.structuralSeeds.add(node);
12899
+ return true;
12900
+ }
12901
+ /**
12902
+ * Areas moved for the edit (the same list when none moves). Records in `moves` whether one
12903
+ * moved, one was cut (deleted, widened, pushed off the grid) and whether implicit intersection
12904
+ * with one can now pick another cell.
12905
+ */
12906
+ moveAreas(list, sheet, shift2, pos, nodeDelta, moves) {
12907
+ let out;
12908
+ for (let i2 = 0; i2 < list.length; i2++) {
12909
+ const area = list[i2];
12910
+ const delta = areaDelta(area, sheet, shift2);
12911
+ if (delta === void 0) {
12912
+ moves.cut = true;
12913
+ continue;
12914
+ }
12915
+ if (intersectionMoves(area, delta, pos, nodeDelta, shift2.axis)) moves.seed = true;
12916
+ if (delta === 0) continue;
12917
+ moves.moved = true;
12918
+ out ??= list.slice();
12919
+ out[i2] = { sheet: area.sheet, range: movedRange(area.range, shift2.axis, delta) };
12920
+ }
12921
+ return out ?? list;
12922
+ }
12923
+ /**
12924
+ * Follows a sheet rename (`from` to `to`, formulas already rewritten): precedents are kept by
12925
+ * sheet index, so only rewritten formulas need their text parsed again, and only formulas that
12926
+ * name the new sheet without having been rewritten (previously unresolved), describe sheets
12927
+ * (CELL, SHEET, FORMULATEXT) or read defined names whose areas changed are recalculated.
12928
+ */
12929
+ renameSheet(from, to) {
12930
+ if (!this.graphReady()) {
12931
+ this.invalidate();
12932
+ return;
12933
+ }
12934
+ const target2 = to.toLowerCase();
12935
+ let depsChanged = false;
12936
+ for (const map of this.nodes.values())
12937
+ for (const node of map.values()) {
12938
+ const cell = this.workbook.sheets[node.sheet]?.rows.get(node.row)?.get(node.col);
12939
+ if (cell?.formula === void 0) {
12940
+ this.invalidate();
12941
+ return;
12942
+ }
12943
+ const changed = cell.formula !== node.formula;
12944
+ const traits = node.ast && astTraits(node.ast);
12945
+ node.formula = cell.formula;
12946
+ if (traits?.complex || !changed && traits?.sheets.has(target2)) {
12947
+ const deps = node.deps;
12948
+ const dynamicDeps = node.dynamicDeps;
12949
+ this.reanalyze(node);
12950
+ const same4 = sameAreas(deps, node.deps) && sameAreas(dynamicDeps, node.dynamicDeps);
12951
+ if (!same4) depsChanged = true;
12952
+ if (!same4 || !traits?.complex) this.structuralSeeds.add(node);
12953
+ } else if (changed) node.stale = true;
12954
+ if (traits?.descriptive || traits?.opaque) this.structuralSeeds.add(node);
12955
+ }
12956
+ if (depsChanged) this.graphChanged();
12957
+ }
12958
+ /** Takes the formulas queued by structural edits. */
12959
+ takeStructuralSeeds(into) {
12960
+ for (const node of this.structuralSeeds)
12961
+ if (this.nodes.get(node.sheet)?.get(cellKey(node.row, node.col)) === node) into.add(node);
12962
+ this.structuralSeeds.clear();
12963
+ }
12964
+ };
12965
+
12421
12966
  // xlsx/formula/engine.ts
12422
- var Engine = class extends EngineCore {
12967
+ var Engine = class extends EngineStructure {
12968
+ preparation;
12969
+ /** The prepared formulas the first recalculation checks (see `trustsStoredValues`). */
12970
+ prepared;
12971
+ // ---- the graph ----
12972
+ prepare(timeRemaining) {
12973
+ if (this.built || this.needsFull) return true;
12974
+ this.preparation ??= this.startPreparation();
12975
+ if (!this.preparation.run(timeRemaining)) return false;
12976
+ this.finishPreparation(this.preparation);
12977
+ return true;
12978
+ }
12979
+ discardPreparation() {
12980
+ if (!this.fresh || this.needsFull) return;
12981
+ this.preparation = void 0;
12982
+ this.prepared = void 0;
12983
+ this.structuralSeeds.clear();
12984
+ this.nodes.clear();
12985
+ this.footprints.clear();
12986
+ this.graphChanged();
12987
+ this.built = false;
12988
+ }
12989
+ /** Completes the graph, continuing a preparation that is under way. */
12990
+ build() {
12991
+ const preparation = this.preparation ?? this.startPreparation();
12992
+ preparation.run();
12993
+ this.finishPreparation(preparation);
12994
+ }
12995
+ startPreparation() {
12996
+ this.nodes.clear();
12997
+ this.footprints.clear();
12998
+ this.footprintIndex = void 0;
12999
+ this.prepared = void 0;
13000
+ return new GraphPreparation(
13001
+ this.workbook,
13002
+ (s, row2, col, cell) => this.createNode(s, row2, col, cell, true)
13003
+ );
13004
+ }
13005
+ finishPreparation(preparation) {
13006
+ this.preparation = void 0;
13007
+ this.built = true;
13008
+ this.graphChanged();
13009
+ this.reverseIndex = preparation.reverse;
13010
+ this.prepared = preparation.formulas;
13011
+ }
12423
13012
  // ---- recalculation ----
12424
13013
  recalculateAll() {
13014
+ this.fresh = false;
13015
+ this.prepared = void 0;
12425
13016
  if (this.needsFull) this.releaseAllSpills();
12426
13017
  if (!this.built || this.needsFull) this.build();
12427
13018
  this.needsFull = false;
13019
+ this.structuralSeeds.clear();
12428
13020
  this.cycles = /* @__PURE__ */ new Set();
12429
13021
  this.run(new Set(this.allNodes()), true);
12430
13022
  }
12431
13023
  recalculateFrom(changes) {
12432
13024
  const seeds = /* @__PURE__ */ new Set();
12433
- if (!this.built && !this.needsFull) {
12434
- this.build();
13025
+ if (this.fresh && !this.needsFull) {
13026
+ this.fresh = false;
13027
+ if (!this.built) this.build();
12435
13028
  if (!this.trustsStoredValues(changes, seeds)) {
12436
13029
  this.recalculateAll();
12437
13030
  return;
@@ -12475,6 +13068,7 @@ var Engine = class extends EngineCore {
12475
13068
  }
12476
13069
  }
12477
13070
  if (structural) this.graphChanged();
13071
+ this.takeStructuralSeeds(seeds);
12478
13072
  const reverse = this.reverse();
12479
13073
  for (const area of changed) reverse.dependents(area.sheet, area.range, seeds);
12480
13074
  for (const node of this.allNodes()) if (node.volatile) seeds.add(node);
@@ -12486,13 +13080,18 @@ var Engine = class extends EngineCore {
12486
13080
  * without a value into `missing`.
12487
13081
  */
12488
13082
  trustsStoredValues(changes, missing) {
13083
+ const prepared = this.prepared;
13084
+ this.prepared = void 0;
13085
+ if (!prepared) return false;
12489
13086
  const changed = new Set(changes.map((c) => `${c.sheet}:${cellKey(c.row, c.col)}`));
12490
- for (const node of this.allNodes()) {
12491
- const legacyOrArray = node.legacy || node.arrayRange;
12492
- if (!legacyOrArray && !changed.has(`${node.sheet}:${cellKey(node.row, node.col)}`))
13087
+ const current = (node) => this.nodes.get(node.sheet)?.get(cellKey(node.row, node.col)) === node;
13088
+ for (const node of prepared.dynamic) {
13089
+ if (current(node) && !changed.has(`${node.sheet}:${cellKey(node.row, node.col)}`))
12493
13090
  return false;
13091
+ }
13092
+ for (const node of prepared.unvalued) {
12494
13093
  const cell = this.workbook.sheets[node.sheet]?.rows.get(node.row)?.get(node.col);
12495
- if (cell && cell.value === null && !node.keepCached) missing.add(node);
13094
+ if (current(node) && cell && cell.value === null) missing.add(node);
12496
13095
  }
12497
13096
  return true;
12498
13097
  }
@@ -12506,7 +13105,10 @@ var Engine = class extends EngineCore {
12506
13105
  return !!anchor?.spill && rangeHas(anchor.spill, row2, col);
12507
13106
  }
12508
13107
  invalidate() {
13108
+ this.structuralSeeds.clear();
12509
13109
  this.releaseAllSpills();
13110
+ this.preparation = void 0;
13111
+ this.prepared = void 0;
12510
13112
  this.nodes.clear();
12511
13113
  this.footprints.clear();
12512
13114
  this.graphChanged();
@@ -12551,7 +13153,7 @@ function createCalcEngine(workbook, options = {}) {
12551
13153
  return new Engine(workbook, options);
12552
13154
  }
12553
13155
 
12554
- // xlsx/formula/transform.ts
13156
+ // xlsx/formula/ref-text.ts
12555
13157
  function safeTokens(formula) {
12556
13158
  try {
12557
13159
  return tokenize(formula);
@@ -12570,50 +13172,26 @@ function formatRefSpec(spec2) {
12570
13172
  if (spec2.kind === "cell") return a;
12571
13173
  return `${a}:${cornerText(spec2.end, spec2.kind)}`;
12572
13174
  }
12573
- function rewrite(token, ref) {
13175
+ function rewriteRef(token, ref) {
12574
13176
  const prefix = token.prefix?.text ?? "";
12575
13177
  if (!ref) return `${prefix}#REF!`;
12576
13178
  return `${prefix}${formatRefSpec(ref)}${token.spill ? "#" : ""}`;
12577
13179
  }
12578
13180
  var sameSpec = (a, b) => a.kind === b.kind && a.start.row === b.start.row && a.start.col === b.start.col && a.end.row === b.end.row && a.end.col === b.end.col;
12579
- function translateFormula(formula, dRow, dCol) {
12580
- const tokens = safeTokens(formula);
12581
- if (!tokens) return formula;
12582
- for (const token of tokens) {
12583
- if (token.kind !== "ref" || !token.ref) continue;
12584
- const spec2 = token.ref;
12585
- const move = (c) => {
12586
- const row2 = spec2.kind === "cols" || c.rowAbs ? c.row : c.row + dRow;
12587
- const col = spec2.kind === "rows" || c.colAbs ? c.col : c.col + dCol;
12588
- if (row2 < 0 || row2 > MAX_ROW || col < 0 || col > MAX_COL) return void 0;
12589
- return { ...c, row: row2, col };
12590
- };
12591
- const start = move(spec2.start);
12592
- const end = move(spec2.end);
12593
- const next = start && end ? { kind: spec2.kind, start, end } : void 0;
12594
- if (next && sameSpec(next, spec2)) continue;
12595
- token.text = rewrite(token, next);
12596
- }
12597
- return joinTokens(tokens);
13181
+ var needsQuote = (name) => !/^[A-Za-z_¡-￿][A-Za-z0-9_.¡-￿]*$/.test(name) || /^[A-Za-z]{1,3}\d+$/.test(name) || /^(?:R\d*C?\d*|C\d*)$/i.test(name) || /^(?:TRUE|FALSE)$/i.test(name);
13182
+ function prefixText(prefix) {
13183
+ const sheets = prefix.sheet2 !== void 0 ? `${prefix.sheet}:${prefix.sheet2}` : prefix.sheet;
13184
+ const book = prefix.book !== void 0 ? `[${prefix.book}]` : "";
13185
+ const quote = needsQuote(prefix.sheet) || prefix.sheet2 !== void 0 && needsQuote(prefix.sheet2);
13186
+ return quote ? `'${(book + sheets).replace(/'/g, "''")}'!` : `${book}${sheets}!`;
12598
13187
  }
12599
- var sheetMatches = (prefix, formulaSheet, sheet) => {
12600
- if (prefix?.book !== void 0) return false;
12601
- const name = prefix ? prefix.sheet : formulaSheet;
12602
- return name.toLowerCase() === sheet.toLowerCase();
12603
- };
12604
- function shiftInterval(a, b, at, count2, max) {
12605
- if (count2 > 0) {
12606
- const na2 = a >= at ? a + count2 : a;
12607
- const nb2 = b >= at ? b + count2 : b;
12608
- if (nb2 > max) return na2 > max ? void 0 : [na2, max];
12609
- return [na2, nb2];
12610
- }
12611
- const n = -count2;
12612
- const last = at + n - 1;
12613
- if (a >= at && b <= last) return void 0;
12614
- const na = a < at ? a : a <= last ? at : a - n;
12615
- const nb = b < at ? b : b <= last ? at - 1 : b - n;
12616
- return [na, nb];
13188
+
13189
+ // xlsx/formula/shift-formula.ts
13190
+ var templates = /* @__PURE__ */ new Map();
13191
+ var MAX_TEMPLATES2 = 2e5;
13192
+ function remember(text2, template) {
13193
+ if (templates.size >= MAX_TEMPLATES2) templates.clear();
13194
+ templates.set(text2, template);
12617
13195
  }
12618
13196
  function mergeSpacedAreas(tokens) {
12619
13197
  const out = [];
@@ -12640,63 +13218,145 @@ function mergeSpacedAreas(tokens) {
12640
13218
  }
12641
13219
  return out;
12642
13220
  }
12643
- function shiftFormula(formula, formulaSheet, spec2) {
12644
- if (spec2.count === 0) return formula;
13221
+ function templateOf(formula) {
13222
+ const cached = templates.get(formula);
13223
+ if (cached !== void 0) return cached;
12645
13224
  const raw = safeTokens(formula);
12646
- if (!raw) return formula;
12647
- const tokens = mergeSpacedAreas(raw);
12648
- const isRow = spec2.axis === "row";
12649
- for (const token of tokens) {
12650
- if (token.kind !== "ref" || !token.ref || !sheetMatches(token.prefix, formulaSheet, spec2.sheet))
12651
- continue;
12652
- const ref = token.ref;
12653
- if (isRow && ref.kind === "cols" || !isRow && ref.kind === "rows") continue;
12654
- const startFirst = isRow ? ref.start.row <= ref.end.row : ref.start.col <= ref.end.col;
12655
- const lo = startFirst ? ref.start : ref.end;
12656
- const hi = startFirst ? ref.end : ref.start;
12657
- const shifted2 = shiftInterval(
12658
- isRow ? lo.row : lo.col,
12659
- isRow ? hi.row : hi.col,
12660
- spec2.at,
12661
- spec2.count,
12662
- isRow ? MAX_ROW : MAX_COL
12663
- );
12664
- let next;
12665
- if (shifted2) {
12666
- const [a, b] = shifted2;
12667
- const nlo = isRow ? { ...lo, row: a } : { ...lo, col: a };
12668
- const nhi = isRow ? { ...hi, row: b } : { ...hi, col: b };
12669
- next = { kind: ref.kind, start: startFirst ? nlo : nhi, end: startFirst ? nhi : nlo };
12670
- if (ref.kind === "cell") next.end = next.start;
12671
- }
12672
- if (next && sameSpec(next, ref)) continue;
12673
- token.text = rewrite(token, next);
13225
+ let template = null;
13226
+ if (raw) {
13227
+ template = [];
13228
+ let literal2 = "";
13229
+ for (const token of mergeSpacedAreas(raw)) {
13230
+ if (token.kind !== "ref" && !(token.kind === "name" && token.prefix)) {
13231
+ literal2 += token.text;
13232
+ continue;
13233
+ }
13234
+ if (literal2) template.push(literal2);
13235
+ literal2 = "";
13236
+ const part = { kind: token.kind, text: token.text };
13237
+ if (token.prefix) {
13238
+ part.prefix = token.prefix;
13239
+ if (token.prefix.book === void 0) part.sheet = token.prefix.sheet.toLowerCase();
13240
+ }
13241
+ if (token.ref) part.ref = token.ref;
13242
+ if (token.spill) part.spill = true;
13243
+ template.push(part);
13244
+ }
13245
+ if (literal2) template.push(literal2);
13246
+ }
13247
+ remember(formula, template);
13248
+ return template;
13249
+ }
13250
+ function rewriteTemplate(formula, rewrite) {
13251
+ const template = templateOf(formula);
13252
+ if (!template) return formula;
13253
+ let next;
13254
+ for (let i2 = 0; i2 < template.length; i2++) {
13255
+ const part = template[i2];
13256
+ if (typeof part === "string") continue;
13257
+ const changed = rewrite(part);
13258
+ if (!changed) continue;
13259
+ next ??= template.slice();
13260
+ next[i2] = changed;
13261
+ }
13262
+ if (!next) return formula;
13263
+ const text2 = next.map((part) => typeof part === "string" ? part : part.text).join("");
13264
+ remember(text2, next);
13265
+ return text2;
13266
+ }
13267
+ function shiftInterval(a, b, at, count2, max) {
13268
+ if (count2 > 0) {
13269
+ const na2 = a >= at ? a + count2 : a;
13270
+ const nb2 = b >= at ? b + count2 : b;
13271
+ if (nb2 > max) return na2 > max ? void 0 : [na2, max];
13272
+ return [na2, nb2];
12674
13273
  }
12675
- return joinTokens(tokens);
13274
+ const n = -count2;
13275
+ const last = at + n - 1;
13276
+ if (a >= at && b <= last) return void 0;
13277
+ const na = a < at ? a : a <= last ? at : a - n;
13278
+ const nb = b < at ? b : b <= last ? at - 1 : b - n;
13279
+ return [na, nb];
12676
13280
  }
12677
- var needsQuote = (name) => !/^[A-Za-z_\u00A1-\uFFFF][A-Za-z0-9_.\u00A1-\uFFFF]*$/.test(name) || /^[A-Za-z]{1,3}\d+$/.test(name) || /^(?:R\d*C?\d*|C\d*)$/i.test(name) || /^(?:TRUE|FALSE)$/i.test(name);
12678
- function prefixText(prefix) {
12679
- const sheets = prefix.sheet2 !== void 0 ? `${prefix.sheet}:${prefix.sheet2}` : prefix.sheet;
12680
- const book = prefix.book !== void 0 ? `[${prefix.book}]` : "";
12681
- const quote = needsQuote(prefix.sheet) || prefix.sheet2 !== void 0 && needsQuote(prefix.sheet2);
12682
- return quote ? `'${(book + sheets).replace(/'/g, "''")}'!` : `${book}${sheets}!`;
13281
+ function shiftRef(ref, spec2) {
13282
+ const isRow = spec2.axis === "row";
13283
+ if (isRow && ref.kind === "cols" || !isRow && ref.kind === "rows") return ref;
13284
+ const startFirst = isRow ? ref.start.row <= ref.end.row : ref.start.col <= ref.end.col;
13285
+ const lo = startFirst ? ref.start : ref.end;
13286
+ const hi = startFirst ? ref.end : ref.start;
13287
+ const shifted2 = shiftInterval(
13288
+ isRow ? lo.row : lo.col,
13289
+ isRow ? hi.row : hi.col,
13290
+ spec2.at,
13291
+ spec2.count,
13292
+ isRow ? MAX_ROW : MAX_COL
13293
+ );
13294
+ if (!shifted2) return void 0;
13295
+ const [a, b] = shifted2;
13296
+ const nlo = isRow ? { ...lo, row: a } : { ...lo, col: a };
13297
+ const nhi = isRow ? { ...hi, row: b } : { ...hi, col: b };
13298
+ const next = {
13299
+ kind: ref.kind,
13300
+ start: startFirst ? nlo : nhi,
13301
+ end: startFirst ? nhi : nlo
13302
+ };
13303
+ if (ref.kind === "cell") next.end = next.start;
13304
+ return sameSpec(next, ref) ? ref : next;
13305
+ }
13306
+ function shiftFormula(formula, formulaSheet, spec2) {
13307
+ if (spec2.count === 0) return formula;
13308
+ const sheet = spec2.sheet.toLowerCase();
13309
+ const own = formulaSheet.toLowerCase() === sheet;
13310
+ return rewriteTemplate(formula, (part) => {
13311
+ if (part.kind !== "ref" || !part.ref || (part.prefix ? part.sheet !== sheet : !own))
13312
+ return void 0;
13313
+ const next = shiftRef(part.ref, spec2);
13314
+ if (next === part.ref) return void 0;
13315
+ const out = { kind: "ref", text: rewriteRef(part, next) };
13316
+ if (part.prefix) out.prefix = part.prefix;
13317
+ if (part.sheet !== void 0) out.sheet = part.sheet;
13318
+ if (next) out.ref = next;
13319
+ if (part.spill) out.spill = true;
13320
+ return out;
13321
+ });
12683
13322
  }
12684
13323
  function renameSheetInFormula(formula, oldName, newName) {
12685
- const tokens = safeTokens(formula);
12686
- if (!tokens) return formula;
12687
13324
  const lower2 = oldName.toLowerCase();
12688
- for (const token of tokens) {
12689
- const prefix = token.prefix;
12690
- if (!prefix || prefix.book !== void 0) continue;
12691
- const first2 = prefix.sheet.toLowerCase() === lower2;
13325
+ return rewriteTemplate(formula, (part) => {
13326
+ const prefix = part.prefix;
13327
+ if (!prefix || part.sheet === void 0) return void 0;
13328
+ const first2 = part.sheet === lower2;
12692
13329
  const second = prefix.sheet2?.toLowerCase() === lower2;
12693
- if (!first2 && !second) continue;
13330
+ if (!first2 && !second) return void 0;
12694
13331
  const renamed = { ...prefix, sheet: first2 ? newName : prefix.sheet };
12695
13332
  if (prefix.sheet2 !== void 0) renamed.sheet2 = second ? newName : prefix.sheet2;
12696
13333
  const text2 = prefixText(renamed);
12697
- token.text = text2 + token.text.slice(prefix.text.length);
12698
13334
  renamed.text = text2;
12699
- token.prefix = renamed;
13335
+ const out = { ...part, text: text2 + part.text.slice(prefix.text.length) };
13336
+ out.prefix = renamed;
13337
+ out.sheet = renamed.sheet.toLowerCase();
13338
+ return out;
13339
+ });
13340
+ }
13341
+
13342
+ // xlsx/formula/transform.ts
13343
+ function translateFormula(formula, dRow, dCol) {
13344
+ const tokens = safeTokens(formula);
13345
+ if (!tokens) return formula;
13346
+ for (const token of tokens) {
13347
+ if (token.kind !== "ref" || !token.ref) continue;
13348
+ const spec2 = token.ref;
13349
+ const move = (c) => {
13350
+ const row2 = spec2.kind === "cols" || c.rowAbs ? c.row : c.row + dRow;
13351
+ const col = spec2.kind === "rows" || c.colAbs ? c.col : c.col + dCol;
13352
+ if (row2 < 0 || row2 > MAX_ROW || col < 0 || col > MAX_COL) return void 0;
13353
+ return { ...c, row: row2, col };
13354
+ };
13355
+ const start = move(spec2.start);
13356
+ const end = move(spec2.end);
13357
+ const next = start && end ? { kind: spec2.kind, start, end } : void 0;
13358
+ if (next && sameSpec(next, spec2)) continue;
13359
+ token.text = rewriteRef(token, next);
12700
13360
  }
12701
13361
  return joinTokens(tokens);
12702
13362
  }
@@ -13020,13 +13680,13 @@ function dataBarLengths(rule, node) {
13020
13680
  maxLength: rule.maxLength ?? numAttr(bar, "maxLength") ?? 90
13021
13681
  };
13022
13682
  }
13023
- function rewriteConditionalExtension(rule, rewrite2) {
13683
+ function rewriteConditionalExtension(rule, rewrite) {
13024
13684
  if (!rule.extensionXml) return;
13025
13685
  const doc = parseXml(rule.extensionXml);
13026
13686
  let changed = false;
13027
13687
  for (const formula of descendants(doc.documentElement, "f", XM)) {
13028
13688
  const before = formula.textContent ?? "";
13029
- const next = rewrite2(before);
13689
+ const next = rewrite(before);
13030
13690
  if (before !== next) {
13031
13691
  formula.textContent = next;
13032
13692
  changed = true;
@@ -14053,13 +14713,13 @@ function linearTrend(values2) {
14053
14713
  const slope = den ? num2 / den : 0;
14054
14714
  return (k) => Number((meanY + slope * (k - meanX)).toPrecision(15));
14055
14715
  }
14056
- var isDigit4 = (ch) => ch !== void 0 && ch >= "0" && ch <= "9";
14716
+ var isDigit5 = (ch) => ch !== void 0 && ch >= "0" && ch <= "9";
14057
14717
  function matchTextNumber(text2) {
14058
14718
  let end = text2.length;
14059
- while (end > 0 && !isDigit4(text2[end - 1])) end--;
14719
+ while (end > 0 && !isDigit5(text2[end - 1])) end--;
14060
14720
  if (end === 0) return null;
14061
14721
  let start = end;
14062
- while (start > 0 && isDigit4(text2[start - 1])) start--;
14722
+ while (start > 0 && isDigit5(text2[start - 1])) start--;
14063
14723
  return [text2, text2.slice(0, start), text2.slice(start, end), text2.slice(end)];
14064
14724
  }
14065
14725
  var QUARTER = /^(q|qtr|quarter)(\s?)([1-4])$/i;
@@ -14242,10 +14902,10 @@ function removeValidationArea(rules, area) {
14242
14902
  }
14243
14903
 
14244
14904
  // xlsx/edit/conditional-formulas.ts
14245
- function rewriteConditionalRule(rule, rewrite2) {
14905
+ function rewriteConditionalRule(rule, rewrite) {
14246
14906
  const safe2 = (formula) => {
14247
14907
  try {
14248
- return rewrite2(formula);
14908
+ return rewrite(formula);
14249
14909
  } catch {
14250
14910
  return formula;
14251
14911
  }
@@ -14764,7 +15424,7 @@ function shiftPreservedXml(sheet, shift2, moveRange, banded) {
14764
15424
  (xml) => xml.includes("sqref")
14765
15425
  );
14766
15426
  }
14767
- function rewritePreservedFormulas(sheet, rewrite2) {
15427
+ function rewritePreservedFormulas(sheet, rewrite) {
14768
15428
  editEntries(
14769
15429
  sheet,
14770
15430
  "extLst",
@@ -14774,7 +15434,7 @@ function rewritePreservedFormulas(sheet, rewrite2) {
14774
15434
  const text2 = f6.textContent ?? "";
14775
15435
  let next = text2;
14776
15436
  try {
14777
- next = rewrite2(text2, sheet.name);
15437
+ next = rewrite(text2, sheet.name);
14778
15438
  } catch {
14779
15439
  next = text2;
14780
15440
  }
@@ -14789,46 +15449,46 @@ function rewritePreservedFormulas(sheet, rewrite2) {
14789
15449
  }
14790
15450
 
14791
15451
  // xlsx/edit/shift-formulas.ts
14792
- function safe(rewrite2, formula, formulaSheet) {
15452
+ function safe(rewrite, formula, formulaSheet) {
14793
15453
  try {
14794
- return rewrite2(formula, formulaSheet);
15454
+ return rewrite(formula, formulaSheet);
14795
15455
  } catch {
14796
15456
  return formula;
14797
15457
  }
14798
15458
  }
14799
- function rewriteFormulas(workbook, rewrite2) {
14800
- for (const sheet of workbook.sheets) rewriteSheetFormulas(sheet, rewrite2);
15459
+ function rewriteFormulas(workbook, rewrite) {
15460
+ for (const sheet of workbook.sheets) rewriteSheetFormulas(sheet, rewrite);
14801
15461
  for (const definedName of workbook.definedNames) {
14802
15462
  const local = definedName.localSheet === void 0 ? void 0 : workbook.sheets[definedName.localSheet];
14803
- definedName.formula = safe(rewrite2, definedName.formula, local?.name ?? "");
15463
+ definedName.formula = safe(rewrite, definedName.formula, local?.name ?? "");
14804
15464
  }
14805
15465
  }
14806
- function rewriteSheetFormulas(sheet, rewrite2) {
14807
- rewritePreservedFormulas(sheet, rewrite2);
15466
+ function rewriteSheetFormulas(sheet, rewrite) {
15467
+ rewritePreservedFormulas(sheet, rewrite);
14808
15468
  {
14809
15469
  const name = sheet.name;
14810
15470
  for (const table2 of sheet.tables)
14811
15471
  for (const column of table2.columns)
14812
15472
  if (column.calculatedColumnFormula !== void 0)
14813
- column.calculatedColumnFormula = safe(rewrite2, column.calculatedColumnFormula, name);
15473
+ column.calculatedColumnFormula = safe(rewrite, column.calculatedColumnFormula, name);
14814
15474
  for (const cells of sheet.rows.values())
14815
15475
  for (const cell of cells.values())
14816
- if (cell.formula !== void 0) cell.formula = safe(rewrite2, cell.formula, name);
15476
+ if (cell.formula !== void 0) cell.formula = safe(rewrite, cell.formula, name);
14817
15477
  for (const cf of sheet.conditionalFormats)
14818
- for (const rule of cf.rules) rewriteConditionalRule(rule, (f6) => rewrite2(f6, name));
15478
+ for (const rule of cf.rules) rewriteConditionalRule(rule, (f6) => rewrite(f6, name));
14819
15479
  for (const dv of sheet.dataValidations) {
14820
- if (dv.formula1 !== void 0) dv.formula1 = safe(rewrite2, dv.formula1, name);
14821
- if (dv.formula2 !== void 0) dv.formula2 = safe(rewrite2, dv.formula2, name);
15480
+ if (dv.formula1 !== void 0) dv.formula1 = safe(rewrite, dv.formula1, name);
15481
+ if (dv.formula2 !== void 0) dv.formula2 = safe(rewrite, dv.formula2, name);
14822
15482
  }
14823
15483
  for (const link of sheet.hyperlinks)
14824
15484
  if (link.location && /![A-Za-z$]/.test(link.location))
14825
- link.location = safe(rewrite2, link.location, name);
15485
+ link.location = safe(rewrite, link.location, name);
14826
15486
  for (const drawing of sheet.drawings) {
14827
15487
  if (drawing.kind !== "chart") continue;
14828
15488
  for (const series of drawing.series) {
14829
- if (series.nameRef) series.nameRef = safe(rewrite2, series.nameRef, name);
14830
- if (series.categoriesRef) series.categoriesRef = safe(rewrite2, series.categoriesRef, name);
14831
- if (series.valuesRef) series.valuesRef = safe(rewrite2, series.valuesRef, name);
15489
+ if (series.nameRef) series.nameRef = safe(rewrite, series.nameRef, name);
15490
+ if (series.categoriesRef) series.categoriesRef = safe(rewrite, series.categoriesRef, name);
15491
+ if (series.valuesRef) series.valuesRef = safe(rewrite, series.valuesRef, name);
14832
15492
  }
14833
15493
  }
14834
15494
  }
@@ -15015,7 +15675,7 @@ function shiftAxis(ctx, s, axis, at, count2, insert) {
15015
15675
  applyAxisShift(ctx, sheet, shift2);
15016
15676
  if (insert) inheritFormats(sheet, axis, at, count2);
15017
15677
  },
15018
- { sheet: s, structural: true }
15678
+ { sheet: s, structural: true, calc: { kind: "shift", sheet: s, shift: shift2 } }
15019
15679
  );
15020
15680
  }
15021
15681
  var insertRows = (ctx, s, at, count2) => shiftAxis(ctx, s, "row", at, count2, true);
@@ -16829,6 +17489,14 @@ function setCalcMode(ctx, mode) {
16829
17489
 
16830
17490
  // xlsx/edit/session-calc.ts
16831
17491
  var isStructural = (step) => step.structural || step.entries.some((entry) => entry.before.kind !== "cells");
17492
+ function hintFor(step, mode) {
17493
+ const hint = step.calc;
17494
+ if (!hint) return void 0;
17495
+ if (hint.kind === "rename")
17496
+ return mode === "undo" ? { kind: "rename", from: hint.to, to: hint.from } : hint;
17497
+ return mode === "undo" ? void 0 : hint;
17498
+ }
17499
+ var settingsOf = (workbook) => `${workbook.iterate ? `${workbook.iterate.count}:${workbook.iterate.delta}` : ""}|${workbook.date1904}`;
16832
17500
  var SessionCalculator = class {
16833
17501
  constructor(workbook, calc, enabled) {
16834
17502
  this.workbook = workbook;
@@ -16838,43 +17506,113 @@ var SessionCalculator = class {
16838
17506
  workbook;
16839
17507
  calc;
16840
17508
  enabled;
16841
- /** Structural edits made in manual mode leave the dependency graph stale until a full pass. */
17509
+ /** Structural edits the engine could not follow leave the dependency graph stale (manual mode). */
16842
17510
  staleGraph = false;
17511
+ /** Cells changed in manual mode since the last calculation. */
17512
+ dirty = [];
17513
+ /** The settings at the last calculation; undefined before the first. */
17514
+ calculated;
16843
17515
  /** Recalculates after an applied, undone or redone step (failures become warnings). */
16844
- afterStep(step) {
16845
- if (!this.enabled) return;
17516
+ afterStep(step, mode = "apply") {
17517
+ if (!this.enabled) {
17518
+ this.calc.discardPreparation();
17519
+ return;
17520
+ }
17521
+ const structural = isStructural(step);
17522
+ const hint = structural ? hintFor(step, mode) : void 0;
16846
17523
  if (calcModeOf(this.workbook) === "manual") {
16847
- if (isStructural(step)) this.staleGraph = true;
17524
+ if (hint && !this.staleGraph && !this.dirty.length) {
17525
+ this.guard(() => this.follow(hint));
17526
+ return;
17527
+ }
17528
+ this.calc.discardPreparation();
17529
+ if (structural) this.staleGraph = true;
17530
+ else this.dirty.push(...touchedCells(step));
16848
17531
  return;
16849
17532
  }
16850
- this.recalc(isStructural(step), () => touchedCells(step));
17533
+ if (hint && !this.staleGraph) {
17534
+ this.guard(() => {
17535
+ this.follow(hint);
17536
+ this.calc.recalculateFrom(touchedCells(step));
17537
+ this.calculated = settingsOf(this.workbook);
17538
+ });
17539
+ return;
17540
+ }
17541
+ this.recalc(structural, () => touchedCells(step));
16851
17542
  }
16852
17543
  /** Recalculates after a change applied outside the history (a collaborator's edit). */
16853
17544
  afterExternal(structural, cells) {
16854
- if (!this.enabled) return;
16855
- if (calcModeOf(this.workbook) === "manual") {
17545
+ if (!this.enabled || calcModeOf(this.workbook) === "manual") {
17546
+ this.calc.discardPreparation();
17547
+ if (!this.enabled) return;
16856
17548
  if (structural) this.staleGraph = true;
17549
+ else this.dirty.push(...cells);
16857
17550
  return;
16858
17551
  }
16859
17552
  this.recalc(structural, () => cells);
16860
17553
  }
16861
- recalc(structural, cells) {
17554
+ follow(hint) {
17555
+ if (hint.kind === "shift") this.calc.shiftCells(hint.sheet, hint.shift);
17556
+ else this.calc.renameSheet(hint.from, hint.to);
17557
+ }
17558
+ guard(run) {
17559
+ try {
17560
+ run();
17561
+ } catch (error) {
17562
+ const message = `Recalculation failed: ${error instanceof Error ? error.message : String(error)}`;
17563
+ if (!this.workbook.warnings.includes(message)) this.workbook.warnings.push(message);
17564
+ }
17565
+ }
17566
+ /** Builds the dependency graph ahead of the first edit (see `EditSession.prepareCalculation`). */
17567
+ prepare(timeRemaining) {
17568
+ if (!this.enabled || calcModeOf(this.workbook) === "manual" || this.staleGraph) return true;
16862
17569
  try {
17570
+ return this.calc.prepare(timeRemaining);
17571
+ } catch {
17572
+ this.calc.discardPreparation();
17573
+ return true;
17574
+ }
17575
+ }
17576
+ /**
17577
+ * Before a structural edit the graph can follow in place: finishes a graph being prepared (or
17578
+ * builds it) while it still matches the workbook, so the edit moves it instead of rebuilding.
17579
+ */
17580
+ beforeStructural() {
17581
+ if (!this.enabled || calcModeOf(this.workbook) === "manual" || this.staleGraph) return;
17582
+ this.prepare();
17583
+ }
17584
+ recalc(structural, cells) {
17585
+ this.guard(() => {
16863
17586
  if (this.staleGraph || structural) this.full();
16864
17587
  else {
16865
17588
  const changed = cells();
16866
17589
  if (changed.length) this.calc.recalculateFrom(changed);
17590
+ this.calculated = settingsOf(this.workbook);
16867
17591
  }
16868
- } catch (error) {
16869
- const message = `Recalculation failed: ${error instanceof Error ? error.message : String(error)}`;
16870
- if (!this.workbook.warnings.includes(message)) this.workbook.warnings.push(message);
16871
- }
17592
+ });
16872
17593
  }
16873
- /** Rebuilds the dependency graph and recalculates every formula (F9). */
17594
+ /** Rebuilds the dependency graph and recalculates every formula (Ctrl+Alt+F9). */
16874
17595
  full() {
16875
17596
  this.staleGraph = false;
17597
+ this.dirty = [];
16876
17598
  this.calc.invalidate();
16877
17599
  this.calc.recalculateAll();
17600
+ this.calculated = settingsOf(this.workbook);
17601
+ }
17602
+ /**
17603
+ * Calculate Now (F9): the cells changed since the last calculation, the formulas structural
17604
+ * edits queued, their dependents and the volatile functions. Everything when `full`, before
17605
+ * the session's first calculation, after the iteration settings changed or while the graph
17606
+ * is stale.
17607
+ */
17608
+ now(full) {
17609
+ if (full || this.staleGraph || this.calculated !== settingsOf(this.workbook)) {
17610
+ this.full();
17611
+ return;
17612
+ }
17613
+ const changed = this.dirty;
17614
+ this.dirty = [];
17615
+ this.calc.recalculateFrom(changed);
16878
17616
  }
16879
17617
  /**
16880
17618
  * Recalculates one sheet's formulas and their dependents (Shift+F9); after structural edits
@@ -20383,12 +21121,12 @@ function renameSheet(ctx, index2, name) {
20383
21121
  ctx.run(
20384
21122
  "Rename sheet",
20385
21123
  "sheets",
20386
- [{ kind: "workbook" }],
21124
+ [{ kind: "refs" }],
20387
21125
  () => {
20388
21126
  rewriteFormulas(workbook, (formula) => renameSheetInFormula(formula, old, name));
20389
21127
  sheet.name = name;
20390
21128
  },
20391
- { sheet: index2, structural: true }
21129
+ { sheet: index2, structural: true, calc: { kind: "rename", from: old, to: name } }
20392
21130
  );
20393
21131
  }
20394
21132
  function moveSheet(ctx, from, to) {
@@ -20764,13 +21502,12 @@ function createEditSession(workbook, options = {}) {
20764
21502
  for (const listener of [...listeners2]) listener(change);
20765
21503
  };
20766
21504
  const calculator = new SessionCalculator(workbook, calc, autoRecalc);
20767
- const recalc = (step) => calculator.afterStep(step);
20768
- const begin = (label2, kind, info) => ({
20769
- step: { label: label2, entries: [], structural: info.structural ?? false },
20770
- kind,
20771
- sheet: info.sheet,
20772
- ranges: [...info.ranges ?? []]
20773
- });
21505
+ const recalc = (step, mode = "apply") => calculator.afterStep(step, mode);
21506
+ const begin = (label2, kind, info) => {
21507
+ const step = { label: label2, entries: [], structural: info.structural ?? false };
21508
+ if (info.calc) step.calc = info.calc;
21509
+ return { step, kind, sheet: info.sheet, ranges: [...info.ranges ?? []] };
21510
+ };
20774
21511
  const finish = (current) => {
20775
21512
  if (!current.step.entries.length) return;
20776
21513
  history.push(current.step);
@@ -20795,10 +21532,12 @@ function createEditSession(workbook, options = {}) {
20795
21532
  const outer = !open;
20796
21533
  const current = open ?? begin(label2, kind, info);
20797
21534
  if (!outer) {
21535
+ delete current.step.calc;
20798
21536
  if (info.structural) current.step.structural = true;
20799
21537
  if (current.sheet === void 0 && info.sheet !== void 0) current.sheet = info.sheet;
20800
21538
  current.ranges.push(...info.ranges ?? []);
20801
21539
  }
21540
+ if (outer && info.calc) calculator.beforeStructural();
20802
21541
  open = current;
20803
21542
  const mark = current.step.entries.length;
20804
21543
  const befores = scopes.map((scope) => captureScope(workbook, scope));
@@ -20846,7 +21585,7 @@ function createEditSession(workbook, options = {}) {
20846
21585
  if (open) throw new Error(`Cannot ${kind} inside a batch`);
20847
21586
  const step = kind === "undo" ? history.undo(workbook) : history.redo(workbook);
20848
21587
  if (!step) return false;
20849
- recalc(step);
21588
+ recalc(step, kind);
20850
21589
  emit({ kind, label: step.label, structural: step.structural });
20851
21590
  return true;
20852
21591
  };
@@ -20925,10 +21664,11 @@ function createEditSession(workbook, options = {}) {
20925
21664
  setDocumentProperties: (patch) => setDocumentProperties(ctx, patch),
20926
21665
  setAutoRecalc: (enabled) => setCalcMode(ctx, enabled ? "auto" : "manual"),
20927
21666
  autoRecalc: () => autoRecalc && calcModeOf(workbook) === "auto",
20928
- calculateNow() {
20929
- calculator.full();
21667
+ calculateNow(options2) {
21668
+ calculator.now(options2?.full ?? false);
20930
21669
  emit({ kind: "cells", label: "Calculate now", structural: false });
20931
21670
  },
21671
+ prepareCalculation: (opts) => calculator.prepare(opts?.timeRemaining),
20932
21672
  calculateSheet(s) {
20933
21673
  calculator.sheet(s);
20934
21674
  emit({ kind: "cells", label: "Calculate sheet", sheet: s, structural: false });