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