@svgrid/enterprise 3.0.2 → 3.1.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 (364) hide show
  1. package/LICENSE +94 -24
  2. package/README.md +36 -4
  3. package/dist/SvAdvancedFilter.svelte +1 -1
  4. package/dist/SvAlertsManager.svelte +2 -1
  5. package/dist/SvBoard.svelte +2 -1
  6. package/dist/SvExportMenu.svelte +4 -1
  7. package/dist/SvExportMenu.svelte.d.ts +0 -13
  8. package/dist/SvExpressionEditor.svelte +3 -3
  9. package/dist/SvFormulaBar.svelte +94 -20
  10. package/dist/SvFormulaBar.svelte.d.ts +7 -0
  11. package/dist/SvGridAlerts.svelte +49 -9
  12. package/dist/SvGridBoard.svelte +13 -8
  13. package/dist/SvGridBulkEditDrawer.svelte +26 -2
  14. package/dist/SvGridEditPanel.svelte +3 -0
  15. package/dist/SvGridScheduler.svelte +69 -40
  16. package/dist/SvGridSelectionBar.svelte +10 -3
  17. package/dist/SvPivotDesigner.svelte +198 -40
  18. package/dist/SvPivotDesigner.svelte.d.ts +27 -20
  19. package/dist/SvSheet.svelte +4043 -243
  20. package/dist/SvSheet.svelte.d.ts +76 -3
  21. package/dist/SvSheetChartSetup.svelte +151 -0
  22. package/dist/SvSheetChartSetup.svelte.d.ts +18 -0
  23. package/dist/SvSheetComment.svelte +216 -31
  24. package/dist/SvSheetComment.svelte.d.ts +11 -11
  25. package/dist/SvSheetConditionalFormat.svelte +28 -31
  26. package/dist/SvSheetDataValidation.svelte +66 -42
  27. package/dist/SvSheetEditRanges.svelte +217 -0
  28. package/dist/SvSheetEditRanges.svelte.d.ts +15 -0
  29. package/dist/SvSheetErrors.svelte +125 -0
  30. package/dist/SvSheetErrors.svelte.d.ts +20 -0
  31. package/dist/SvSheetEvaluate.svelte +133 -0
  32. package/dist/SvSheetEvaluate.svelte.d.ts +15 -0
  33. package/dist/SvSheetFilterMenu.svelte +137 -34
  34. package/dist/SvSheetFilterMenu.svelte.d.ts +5 -0
  35. package/dist/SvSheetFindReplace.svelte +19 -19
  36. package/dist/SvSheetFormatCells.svelte +125 -81
  37. package/dist/SvSheetGoalSeek.svelte +17 -16
  38. package/dist/SvSheetInsertFunction.svelte +15 -13
  39. package/dist/SvSheetIteration.svelte +81 -0
  40. package/dist/SvSheetIteration.svelte.d.ts +10 -0
  41. package/dist/SvSheetLink.svelte +99 -0
  42. package/dist/SvSheetLink.svelte.d.ts +17 -0
  43. package/dist/SvSheetListPicker.svelte +4 -2
  44. package/dist/SvSheetListPicker.svelte.d.ts +0 -6
  45. package/dist/SvSheetManageRules.svelte +20 -18
  46. package/dist/SvSheetNameManager.svelte +17 -15
  47. package/dist/SvSheetPageSetup.svelte +159 -0
  48. package/dist/SvSheetPageSetup.svelte.d.ts +13 -0
  49. package/dist/SvSheetPasteSpecial.svelte +22 -20
  50. package/dist/SvSheetPivot.svelte +220 -0
  51. package/dist/SvSheetPivot.svelte.d.ts +21 -0
  52. package/dist/SvSheetProtectSheet.svelte +81 -0
  53. package/dist/SvSheetProtectSheet.svelte.d.ts +11 -0
  54. package/dist/SvSheetRemoveDuplicates.svelte +13 -10
  55. package/dist/SvSheetRibbon.svelte +104 -51
  56. package/dist/SvSheetSizeDialog.svelte +4 -2
  57. package/dist/SvSheetSort.svelte +218 -0
  58. package/dist/SvSheetSort.svelte.d.ts +28 -0
  59. package/dist/SvSheetSparklines.svelte +135 -0
  60. package/dist/SvSheetSparklines.svelte.d.ts +14 -0
  61. package/dist/SvSheetTable.svelte +160 -0
  62. package/dist/SvSheetTable.svelte.d.ts +24 -0
  63. package/dist/SvSheetTabs.svelte +161 -31
  64. package/dist/SvSheetTabs.svelte.d.ts +22 -8
  65. package/dist/SvSheetTextToColumns.svelte +20 -20
  66. package/dist/SvSheetValidationAlert.svelte +8 -6
  67. package/dist/advanced-filter-enable.js +1 -1
  68. package/dist/alerts/alert-engine.d.ts +6 -0
  69. package/dist/alerts/alert-engine.js +21 -4
  70. package/dist/alerts/alert-formats.js +2 -0
  71. package/dist/alerts/alert-types.d.ts +1 -1
  72. package/dist/alerts.d.ts +1 -1
  73. package/dist/alerts.js +2 -2
  74. package/dist/board.js +2 -2
  75. package/dist/bulk-edit.d.ts +3 -1
  76. package/dist/bulk-edit.js +1 -1
  77. package/dist/cdn/svgrid-enterprise.svelte-external.js +39827 -16429
  78. package/dist/designer/assets/GridMenus-8gTNssgV.js +8 -0
  79. package/dist/designer/assets/{SvChartMenu-D9hEeHar.js → SvChartMenu-BDoeqGz6.js} +1 -1
  80. package/dist/designer/assets/{SvDateTimePicker-DARvKUL2.js → SvDateTimePicker-DCPg7WGN.js} +1 -1
  81. package/dist/designer/assets/SvGridCellEditor-DMoWJjEI.js +4 -0
  82. package/dist/designer/assets/{SvGridChart-B8j1SSy3.js → SvGridChart-Cy1ATrWh.js} +2 -2
  83. package/dist/designer/assets/SvGridChartBuilder-eQ3mn0L9.js +1 -0
  84. package/dist/designer/assets/{SvGridChartPanel-pT6ITc2P.js → SvGridChartPanel-CATdVc9X.js} +3 -3
  85. package/dist/designer/assets/{SvGridChartView-bg281UA1.js → SvGridChartView-CmsZtCED.js} +1 -1
  86. package/dist/designer/assets/SvGridDropdown-Cjz0H-fI.js +1 -0
  87. package/dist/designer/assets/{SvTabs-UFCMTHXS.js → SvTabs-2mHC6oaz.js} +1 -1
  88. package/dist/designer/assets/cell-formatting-BbubJTVz.js +1 -0
  89. package/dist/designer/assets/{chart-cIPUsbM5.js → chart-Cn7MnNqA.js} +1 -1
  90. package/dist/designer/assets/{chart-panel-messages-DtTxpBUU.js → chart-panel-messages-Dz7PTIqT.js} +1 -1
  91. package/dist/designer/assets/{chart-summary-Czu_5epb.js → chart-summary-BD1qYB2R.js} +1 -1
  92. package/dist/designer/assets/{chart-trend-D5I5VEe8.js → chart-trend-B8xr5LuD.js} +1 -1
  93. package/dist/designer/assets/{dismissable-BO547MH9.js → dismissable-ChWRPkoa.js} +1 -1
  94. package/dist/designer/assets/{export-format-D-eP4b2l.js → export-format-DGckmgKr.js} +1 -1
  95. package/dist/designer/assets/{focus-trap-CAreAhlU.js → focus-trap-V2-6Fy9I.js} +1 -1
  96. package/dist/designer/assets/{index-DwU7CAMm.js → index-BSjv6Qec.js} +317 -315
  97. package/dist/designer/assets/index-_JVFxt02.css +1 -0
  98. package/dist/designer/assets/row-resize-BInFWCpr.js +1 -0
  99. package/dist/designer/assets/server-block-cache-CXJKkJpy.js +1 -0
  100. package/dist/designer/assets/src-DUQ4xE3x.js +58 -0
  101. package/dist/designer/assets/validate-C3B8QKsM.js +1 -0
  102. package/dist/designer/index.html +17 -16
  103. package/dist/export-pdf.d.ts +75 -0
  104. package/dist/export-pdf.js +216 -15
  105. package/dist/export.js +10 -2
  106. package/dist/gantt/SvGridGantt.svelte +2890 -0
  107. package/dist/gantt/SvGridGantt.svelte.d.ts +30 -0
  108. package/dist/gantt/gantt-config.d.ts +73 -0
  109. package/dist/gantt/gantt-config.js +1 -0
  110. package/dist/gantt/gantt-critical-path.d.ts +67 -0
  111. package/dist/gantt/gantt-critical-path.js +207 -0
  112. package/dist/gantt/gantt-model.d.ts +257 -0
  113. package/dist/gantt/gantt-model.js +651 -0
  114. package/dist/gantt/gantt-resources.d.ts +59 -0
  115. package/dist/gantt/gantt-resources.js +71 -0
  116. package/dist/gantt/gantt.d.ts +7 -0
  117. package/dist/gantt/gantt.js +36 -0
  118. package/dist/gantt/index.d.ts +15 -0
  119. package/dist/gantt/index.js +22 -0
  120. package/dist/gantt/timeline-arrows.d.ts +102 -0
  121. package/dist/gantt/timeline-arrows.js +181 -0
  122. package/dist/import.d.ts +2 -2
  123. package/dist/import.js +1 -1
  124. package/dist/index.d.ts +34 -7
  125. package/dist/index.js +44 -7
  126. package/dist/install.js +8 -1
  127. package/dist/license-core.d.ts +17 -1
  128. package/dist/license-core.js +16 -5
  129. package/dist/license.d.ts +18 -2
  130. package/dist/license.js +71 -6
  131. package/dist/node/studio.js +140 -8
  132. package/dist/pivot-enable.js +2 -2
  133. package/dist/scheduler-dependencies.d.ts +35 -0
  134. package/dist/scheduler-dependencies.js +36 -1
  135. package/dist/scheduler.js +2 -2
  136. package/dist/selection-bar.js +2 -2
  137. package/dist/server/SvGroupCell.svelte +213 -0
  138. package/dist/server/SvGroupCell.svelte.d.ts +62 -0
  139. package/dist/server/SvRowGroupPanel.svelte +322 -0
  140. package/dist/server/SvRowGroupPanel.svelte.d.ts +37 -0
  141. package/dist/server/enable.d.ts +17 -0
  142. package/dist/server/enable.js +49 -0
  143. package/dist/server/index.d.ts +22 -0
  144. package/dist/server/index.js +24 -0
  145. package/dist/server/messages.d.ts +45 -0
  146. package/dist/server/messages.js +35 -0
  147. package/dist/server/server-group-model.d.ts +104 -0
  148. package/dist/server/server-group-model.js +272 -0
  149. package/dist/server/server-pivot.d.ts +46 -0
  150. package/dist/server/server-pivot.js +57 -0
  151. package/dist/server/server-row-model.d.ts +547 -0
  152. package/dist/server/server-row-model.js +1551 -0
  153. package/dist/server/server-selection.d.ts +99 -0
  154. package/dist/server/server-selection.js +215 -0
  155. package/dist/server/svgrid-adapter.d.ts +155 -0
  156. package/dist/server/svgrid-adapter.js +202 -0
  157. package/dist/sheet/ast.d.ts +45 -1
  158. package/dist/sheet/ast.js +14 -0
  159. package/dist/sheet/auto-filter.d.ts +58 -4
  160. package/dist/sheet/auto-filter.js +159 -11
  161. package/dist/sheet/autocomplete.d.ts +9 -0
  162. package/dist/sheet/autocomplete.js +90 -4
  163. package/dist/sheet/cell-images.d.ts +39 -0
  164. package/dist/sheet/cell-images.js +90 -0
  165. package/dist/sheet/cell-types.d.ts +58 -0
  166. package/dist/sheet/cell-types.js +96 -0
  167. package/dist/sheet/coerce.d.ts +40 -0
  168. package/dist/sheet/coerce.js +170 -3
  169. package/dist/sheet/comments.d.ts +43 -9
  170. package/dist/sheet/comments.js +85 -27
  171. package/dist/sheet/conditional-formats.d.ts +29 -6
  172. package/dist/sheet/conditional-formats.js +37 -12
  173. package/dist/sheet/csv.d.ts +32 -0
  174. package/dist/sheet/csv.js +158 -0
  175. package/dist/sheet/culture.d.ts +87 -0
  176. package/dist/sheet/culture.js +247 -0
  177. package/dist/sheet/delta.d.ts +102 -0
  178. package/dist/sheet/delta.js +203 -0
  179. package/dist/sheet/deps.d.ts +45 -1
  180. package/dist/sheet/deps.js +77 -2
  181. package/dist/sheet/document.d.ts +106 -4
  182. package/dist/sheet/document.js +204 -3
  183. package/dist/sheet/engine.d.ts +67 -0
  184. package/dist/sheet/engine.js +19 -0
  185. package/dist/sheet/entry.d.ts +8 -4
  186. package/dist/sheet/entry.js +171 -21
  187. package/dist/sheet/error-check.d.ts +49 -0
  188. package/dist/sheet/error-check.js +83 -0
  189. package/dist/sheet/evaluate-steps.d.ts +48 -0
  190. package/dist/sheet/evaluate-steps.js +196 -0
  191. package/dist/sheet/evaluate.d.ts +67 -0
  192. package/dist/sheet/evaluate.js +725 -13
  193. package/dist/sheet/filter-files.d.ts +37 -0
  194. package/dist/sheet/filter-files.js +465 -0
  195. package/dist/sheet/format-store.d.ts +15 -0
  196. package/dist/sheet/format-store.js +11 -1
  197. package/dist/sheet/from-rows.d.ts +14 -0
  198. package/dist/sheet/from-rows.js +50 -0
  199. package/dist/sheet/function-catalog.d.ts +1 -1
  200. package/dist/sheet/function-catalog.js +296 -5
  201. package/dist/sheet/functions.js +363 -91
  202. package/dist/sheet/index.d.ts +35 -8
  203. package/dist/sheet/index.js +35 -8
  204. package/dist/sheet/links.d.ts +57 -0
  205. package/dist/sheet/links.js +174 -0
  206. package/dist/sheet/messages.d.ts +778 -0
  207. package/dist/sheet/messages.js +806 -0
  208. package/dist/sheet/number-format.d.ts +52 -5
  209. package/dist/sheet/number-format.js +465 -68
  210. package/dist/sheet/objects.d.ts +106 -0
  211. package/dist/sheet/objects.js +149 -0
  212. package/dist/sheet/ods-document.d.ts +44 -0
  213. package/dist/sheet/ods-document.js +1275 -0
  214. package/dist/sheet/open-file.d.ts +26 -0
  215. package/dist/sheet/open-file.js +56 -0
  216. package/dist/sheet/outline.d.ts +82 -0
  217. package/dist/sheet/outline.js +228 -0
  218. package/dist/sheet/packs/array.d.ts +16 -0
  219. package/dist/sheet/packs/array.js +172 -0
  220. package/dist/sheet/packs/database.d.ts +42 -0
  221. package/dist/sheet/packs/database.js +142 -0
  222. package/dist/sheet/packs/engineering.d.ts +2 -0
  223. package/dist/sheet/packs/engineering.js +296 -0
  224. package/dist/sheet/packs/financial.d.ts +5 -0
  225. package/dist/sheet/packs/financial.js +220 -0
  226. package/dist/sheet/packs/math-stats.d.ts +2 -0
  227. package/dist/sheet/packs/math-stats.js +372 -0
  228. package/dist/sheet/packs/special.d.ts +44 -0
  229. package/dist/sheet/packs/special.js +187 -0
  230. package/dist/sheet/packs/stats-dist.d.ts +2 -0
  231. package/dist/sheet/packs/stats-dist.js +785 -0
  232. package/dist/sheet/packs/text-date.d.ts +2 -0
  233. package/dist/sheet/packs/text-date.js +332 -0
  234. package/dist/sheet/packs/trig.d.ts +2 -0
  235. package/dist/sheet/packs/trig.js +73 -0
  236. package/dist/sheet/page-setup.d.ts +73 -0
  237. package/dist/sheet/page-setup.js +73 -0
  238. package/dist/sheet/parse.js +42 -10
  239. package/dist/sheet/paste-special.d.ts +22 -0
  240. package/dist/sheet/paste-special.js +14 -5
  241. package/dist/sheet/pivot-range.d.ts +160 -0
  242. package/dist/sheet/pivot-range.js +333 -0
  243. package/dist/sheet/presence.d.ts +73 -0
  244. package/dist/sheet/presence.js +79 -0
  245. package/dist/sheet/print.d.ts +104 -0
  246. package/dist/sheet/print.js +169 -0
  247. package/dist/sheet/protection.d.ts +55 -1
  248. package/dist/sheet/protection.js +55 -2
  249. package/dist/sheet/rects.d.ts +1 -1
  250. package/dist/sheet/refs.d.ts +48 -0
  251. package/dist/sheet/refs.js +155 -8
  252. package/dist/sheet/ribbon-icons.d.ts +1 -1
  253. package/dist/sheet/ribbon-icons.js +186 -0
  254. package/dist/sheet/ribbon.d.ts +1 -1
  255. package/dist/sheet/ribbon.js +324 -23
  256. package/dist/sheet/sheet-kinds.d.ts +74 -0
  257. package/dist/sheet/sheet-kinds.js +91 -0
  258. package/dist/sheet/shortcuts.d.ts +13 -3
  259. package/dist/sheet/shortcuts.js +63 -12
  260. package/dist/sheet/sort.d.ts +51 -0
  261. package/dist/sheet/sort.js +107 -0
  262. package/dist/sheet/sparklines.d.ts +111 -0
  263. package/dist/sheet/sparklines.js +204 -0
  264. package/dist/sheet/table-styles.d.ts +74 -0
  265. package/dist/sheet/table-styles.js +152 -0
  266. package/dist/sheet/tables.d.ts +19 -0
  267. package/dist/sheet/tables.js +31 -0
  268. package/dist/sheet/tokenize.d.ts +18 -1
  269. package/dist/sheet/tokenize.js +60 -9
  270. package/dist/sheet/validation.d.ts +16 -0
  271. package/dist/sheet/validation.js +37 -0
  272. package/dist/sheet/workbook.d.ts +148 -3
  273. package/dist/sheet/workbook.js +742 -31
  274. package/dist/sheet/xls-cfb.d.ts +32 -0
  275. package/dist/sheet/xls-cfb.js +290 -0
  276. package/dist/sheet/xls-document.d.ts +10 -0
  277. package/dist/sheet/xls-document.js +1243 -0
  278. package/dist/sheet/xls-formula.d.ts +40 -0
  279. package/dist/sheet/xls-formula.js +569 -0
  280. package/dist/sheet/xlsx-document.d.ts +108 -0
  281. package/dist/sheet/xlsx-document.js +1849 -0
  282. package/dist/sheet/xlsx-drawing.d.ts +99 -0
  283. package/dist/sheet/xlsx-drawing.js +420 -0
  284. package/dist/sheet-enable.js +6 -7
  285. package/dist/sheet-text.d.ts +7 -0
  286. package/dist/sheet-text.js +29 -0
  287. package/dist/sources/auth-supabase.js +2 -2
  288. package/dist/sources/realtime-supabase.js +2 -2
  289. package/dist/sources/rest.d.ts +6 -2
  290. package/dist/sources/rest.js +42 -7
  291. package/dist/sources/supabase.d.ts +10 -1
  292. package/dist/sources/supabase.js +73 -40
  293. package/dist/studio/emit-project.js +14 -1
  294. package/dist/studio/index.js +1 -1
  295. package/dist/studio/project.d.ts +11 -2
  296. package/dist/studio/project.js +5 -2
  297. package/dist/studio/ui-components.generated.js +107 -2
  298. package/dist/sveltekit/in-memory.d.ts +2 -2
  299. package/dist/sveltekit/in-memory.js +125 -4
  300. package/dist/sveltekit/index.d.ts +1 -1
  301. package/dist/sveltekit/index.js +1 -1
  302. package/dist/sveltekit/query-plan.d.ts +50 -4
  303. package/dist/sveltekit/query-plan.js +25 -3
  304. package/dist/sveltekit/sql-source.d.ts +6 -0
  305. package/dist/sveltekit/sql-source.js +34 -6
  306. package/dist/sveltekit/sql.d.ts +31 -0
  307. package/dist/sveltekit/sql.js +135 -25
  308. package/dist/sveltekit/transport.d.ts +10 -2
  309. package/dist/sveltekit/transport.js +30 -6
  310. package/dist/upgrade-prompt.d.ts +1 -1
  311. package/dist/version.d.ts +1 -1
  312. package/dist/version.js +1 -1
  313. package/dist/watermark.js +35 -2
  314. package/dist/wc/GridMenus-By9vPuq0.js +1088 -0
  315. package/dist/wc/SvChartMenu-CQNrQGT2.js +308 -0
  316. package/dist/wc/SvDateRangeInput-DR6m7bSM.js +199 -0
  317. package/dist/wc/SvDateTimePicker-D-vRnaMo.js +771 -0
  318. package/dist/wc/SvField-B_kurx_P.js +149 -0
  319. package/dist/wc/SvGrid.helpers-BdVDtjUm.js +135 -0
  320. package/dist/wc/SvGridCellEditor-Bnf1Kr5C.js +560 -0
  321. package/dist/wc/SvGridChart-BqUJh2pG.js +3572 -0
  322. package/dist/wc/SvGridChartBuilder-CGycC5cn.js +988 -0
  323. package/dist/wc/SvGridChartPanel-CRnizsHD.js +1746 -0
  324. package/dist/wc/SvGridChartView-D1pa_xMy.js +47 -0
  325. package/dist/wc/SvGridDropdown-45WsObLL.js +259 -0
  326. package/dist/wc/SvModal-DrfOYYyX.js +189 -0
  327. package/dist/wc/cell-formatting-CBfgahv1.js +86 -0
  328. package/dist/wc/chart-6OXlybVT.js +4341 -0
  329. package/dist/wc/chart-export-CeSADGfi.js +135 -0
  330. package/dist/wc/chart-export-pdf-BGko7MER.js +104 -0
  331. package/dist/wc/chart-summary-2m3ia1-n.js +137 -0
  332. package/dist/wc/chart-trend-CQOQtTMl.js +50 -0
  333. package/dist/wc/chart-validate-CD6ptiwg.js +180 -0
  334. package/dist/wc/client-DQIzjDBG.js +3064 -0
  335. package/dist/wc/column-resize-DUPgynvc.js +101 -0
  336. package/dist/wc/date-format-DS-z_h9v.js +1130 -0
  337. package/dist/wc/dismissable-CQ6XFXsk.js +45 -0
  338. package/dist/wc/editor-contract-peBDxir_.js +22 -0
  339. package/dist/wc/editor-registry-DxGKqcvx.js +24 -0
  340. package/dist/wc/focus-trap-Dbb2tJxb.js +65 -0
  341. package/dist/wc/list-option-DrRyivWp.js +82 -0
  342. package/dist/wc/popover-WsGlvtQv.js +69 -0
  343. package/dist/wc/positioning-BiKLfu1E.js +112 -0
  344. package/dist/wc/react.d.ts +41 -0
  345. package/dist/wc/react.js +90 -0
  346. package/dist/wc/rolldown-runtime-Dy4uBu1J.js +11 -0
  347. package/dist/wc/row-drag-touch-CycRR65n.js +93 -0
  348. package/dist/wc/row-resize-limG_N1z.js +111 -0
  349. package/dist/wc/sv-sheet-element.d.ts +97 -0
  350. package/dist/wc/sv-sheet-element.js +44213 -0
  351. package/dist/wc/svelte-virtualizer.svelte-CXtQy2TQ.js +530 -0
  352. package/dist/wc/validate-BqDthkNr.js +88 -0
  353. package/dist/wc/vue.d.ts +31 -0
  354. package/dist/wc/vue.js +75 -0
  355. package/package.json +30 -5
  356. package/dist/designer/assets/GridMenus-BlsvL_aU.js +0 -8
  357. package/dist/designer/assets/SvGridCellEditor-C7uQXYds.js +0 -4
  358. package/dist/designer/assets/SvGridChartBuilder-kvyIU_Rl.js +0 -1
  359. package/dist/designer/assets/SvGridDropdown-Bb3HH2Aj.js +0 -1
  360. package/dist/designer/assets/cell-formatting-wr5wm7US.js +0 -1
  361. package/dist/designer/assets/index-CMFgAoVV.css +0 -1
  362. package/dist/designer/assets/row-resize-DLktcqUK.js +0 -1
  363. package/dist/designer/assets/src-EdI3Xqds.js +0 -58
  364. package/dist/designer/assets/validate-h-aslRV9.js +0 -1
@@ -12,12 +12,21 @@
12
12
  * disagree with the formula above it.
13
13
  */
14
14
  import { parseFormula } from './parse.js';
15
- import { evaluate, rangeValues } from './evaluate.js';
15
+ import { evaluate, rangeValues, tableRectOf } from './evaluate.js';
16
16
  import { withCustomFunctions } from './functions.js';
17
- import { isError } from './ast.js';
18
- import { createDependencyGraph, precedentsOf, cellKey } from './deps.js';
17
+ import { isError, typedError } from './ast.js';
18
+ import { createDependencyGraph, precedentsOf, isVolatile, cellKey, parseCellKey } from './deps.js';
19
+ import { createTableRegistry, isValidTableName, shiftTables } from './tables.js';
19
20
  import { createNames } from './names.js';
20
- import { fixupReferences, renameSheetReferences } from './refs.js';
21
+ import { fixupReferences, renameSheetReferences, repointReferences } from './refs.js';
22
+ import { builtinEngine } from './engine.js';
23
+ export const DEFAULT_ITERATION = { enabled: false, maxIterations: 100, maxChange: 0.001 };
24
+ /** Excel's text prefix. A cell typed as `'007` holds the text `007`: the
25
+ * apostrophe says "what follows is text", and it is not part of the value,
26
+ * so it is never displayed, printed, exported or compared. */
27
+ export const TEXT_PREFIX = "'";
28
+ /** The value a typed text stands for once the prefix is taken off. */
29
+ export const withoutTextPrefix = (text) => text.startsWith(TEXT_PREFIX) ? text.slice(1) : text;
21
30
  /** A name Excel would accept for a sheet. */
22
31
  export function isValidSheetName(name) {
23
32
  if (name.trim() === '' || name.length > 31)
@@ -34,17 +43,106 @@ function nextSheetName(taken) {
34
43
  return candidate;
35
44
  }
36
45
  }
46
+ /** Iteration settings with the defaults filled in and the numbers sane. */
47
+ export function cleanIteration(next) {
48
+ const passes = Math.round(Number(next?.maxIterations ?? DEFAULT_ITERATION.maxIterations));
49
+ const change = Number(next?.maxChange ?? DEFAULT_ITERATION.maxChange);
50
+ return {
51
+ enabled: next?.enabled === true,
52
+ maxIterations: Number.isFinite(passes) ? Math.min(Math.max(passes, 1), 32767) : DEFAULT_ITERATION.maxIterations,
53
+ maxChange: Number.isFinite(change) && change >= 0 ? change : DEFAULT_ITERATION.maxChange,
54
+ };
55
+ }
56
+ /**
57
+ * How far a cell moved between two passes. Two numbers are their distance;
58
+ * anything else has moved either not at all or immeasurably, and an
59
+ * immeasurable move keeps the loop going until the passes run out.
60
+ */
61
+ function moved(before, after) {
62
+ if (typeof before === 'number' && typeof after === 'number')
63
+ return Math.abs(after - before);
64
+ if (before === undefined)
65
+ return Infinity;
66
+ if (isError(before) || isError(after)) {
67
+ return isError(before) && isError(after) && before.error === after.error ? 0 : Infinity;
68
+ }
69
+ return before === after ? 0 : Infinity;
70
+ }
37
71
  export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], options = {}) {
38
72
  const order = [];
73
+ /** What the shell is hiding, for SUBTOTAL. Null until it says. */
74
+ let hiddenRows = null;
75
+ /** Whether any cell holds a SUBTOTAL, so a filter click can skip the
76
+ * recalculation when nothing reads what is hidden. Null when unknown. */
77
+ let subtotalsHere = null;
39
78
  const byName = new Map();
40
79
  const graph = createDependencyGraph();
80
+ /**
81
+ * The workbook's tables. The parser has always read a structured
82
+ * reference (`Orders[Amount]`, `[@Qty]`); this is what makes one resolve,
83
+ * because a reference like that means nothing until something says where
84
+ * the table is.
85
+ */
86
+ const rawTables = createTableRegistry(options.tables);
87
+ /**
88
+ * The registry as the workbook hands it out: every change drops the
89
+ * cached values and settles again.
90
+ *
91
+ * A table decides what `Orders[Amount]` means, so defining one, removing
92
+ * one or growing one changes the answer of every formula that mentions
93
+ * it. Without this, a table defined after the cells were read leaves each
94
+ * of those formulas showing the `#REF!` it worked out when there was no
95
+ * table, which is the first thing anyone hits.
96
+ */
97
+ const tables = {
98
+ get: (name) => rawTables.get(name),
99
+ list: () => rawTables.list(),
100
+ at: (sheet, row, col) => rawTables.at(sheet, row, col),
101
+ define(table) { rawTables.define(table); afterTables(); },
102
+ remove(name) { const gone = rawTables.remove(name); if (gone)
103
+ afterTables(); return gone; },
104
+ growToInclude(sheet, row, col) { const grew = rawTables.growToInclude(sheet, row, col); if (grew)
105
+ afterTables(); return grew; },
106
+ clear() { rawTables.clear(); afterTables(); },
107
+ };
108
+ /**
109
+ * `Orders2`, `Orders3`, ...: a table name nothing has taken yet.
110
+ *
111
+ * A short name plus a number can read as a cell reference (`T2`), which
112
+ * Excel refuses as a table name and so does the registry, so those fall
113
+ * back to Excel's own `Table2`.
114
+ */
115
+ function freeTableName(base) {
116
+ const taken = new Set(rawTables.list().map((t) => t.name.toLowerCase()));
117
+ const root = base.replace(/\d+$/, '') || 'Table';
118
+ for (let i = 2;; i += 1) {
119
+ for (const candidate of [`${root}${i}`, `Table${i}`]) {
120
+ if (!taken.has(candidate.toLowerCase()) && isValidTableName(candidate))
121
+ return candidate;
122
+ }
123
+ }
124
+ }
125
+ function afterTables() {
126
+ dropAll();
127
+ loadEngine();
128
+ settleAll();
129
+ }
41
130
  const functions = withCustomFunctions(options.functions);
131
+ const engine = options.engine ?? builtinEngine();
42
132
  /** Computed values, keyed the same way the graph is. Rebuilt lazily and
43
133
  * invalidated by every write, so it can never hold a value whose formula
44
134
  * has since changed. */
45
135
  const values = new Map();
46
136
  /** Cells currently being resolved, for cycle detection. */
47
137
  const visiting = new Set();
138
+ /** Iterative calculation, and the value each cell had on the pass before:
139
+ * what a cell in a cycle reads for itself while iteration is on, and what
140
+ * the convergence test measures against. */
141
+ let iteration = cleanIteration(options.iteration);
142
+ const previous = new Map();
143
+ /** Cells whose formula is volatile (INDIRECT, OFFSET, RAND, NOW ...):
144
+ * recomputed on every write, since the graph cannot see what they read. */
145
+ const volatile = new Set();
48
146
  /** Parsed formulas, keyed by their TEXT rather than their position: a
49
147
  * column of =A1*2 filled down is one parse, not one per row. */
50
148
  const astCache = new Map();
@@ -52,9 +150,23 @@ export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], option
52
150
  // the dependency graph only knows the CELLS a name pointed at when the
53
151
  // formula was last evaluated. Dropping the cache is the honest answer:
54
152
  // names change rarely, and a full recompute costs less than one stale total.
153
+ /**
154
+ * Dynamic arrays. A formula whose answer is a grid spills it over the
155
+ * cells below and to the right: `spills` keeps each anchor's rectangle,
156
+ * `spilledBy` points every covered cell back at its anchor, and a covered
157
+ * cell reads its value from the anchor's grid while its own text stays
158
+ * blank, as in Excel. An anchor whose rectangle runs into a cell that
159
+ * holds text, or into another spill, shows #SPILL! and sits in `blocked`
160
+ * until a write frees the way.
161
+ */
162
+ const spills = new Map();
163
+ const spilledBy = new Map();
164
+ const blocked = new Set();
165
+ /** Cells whose value a spill just changed under them, to compute once the write settles. */
166
+ const pending = new Set();
55
167
  const names = (() => {
56
168
  const inner = createNames();
57
- const drop = () => { values.clear(); graph.clear(); };
169
+ const drop = () => { dropAll(); settleAll(); };
58
170
  return {
59
171
  ...inner,
60
172
  define: (name, refersTo) => { inner.define(name, refersTo); drop(); },
@@ -72,6 +184,16 @@ export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], option
72
184
  function resolveSheetName(sheet, self) {
73
185
  return sheet ?? self;
74
186
  }
187
+ /** The sheets from `from` to `to` in tab order, inclusive, for a 3D
188
+ * reference; null when either name is not a sheet. */
189
+ function sheetsInRange(from, to) {
190
+ const lower = order.map((n) => n.toLowerCase());
191
+ const i = lower.indexOf(from.toLowerCase());
192
+ const j = lower.indexOf(to.toLowerCase());
193
+ if (i < 0 || j < 0)
194
+ return null;
195
+ return i <= j ? order.slice(i, j + 1) : order.slice(j, i + 1);
196
+ }
75
197
  function parseCached(text) {
76
198
  if (astCache.has(text))
77
199
  return astCache.get(text) ?? null;
@@ -85,8 +207,9 @@ export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], option
85
207
  astCache.set(text, ast);
86
208
  return ast;
87
209
  }
88
- function contextFor(self, override) {
210
+ function contextFor(self, override, at) {
89
211
  return {
212
+ currentCell: at ? { sheet: self, row: at.row, col: at.col } : undefined,
90
213
  resolve: (sheet, row, col) => {
91
214
  const name = resolveSheetName(sheet, self);
92
215
  // A validation rule reads the cell it is checking as the text being
@@ -96,10 +219,273 @@ export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], option
96
219
  return compute(name, row, col);
97
220
  },
98
221
  lastRow: (sheet) => Math.max(rowCount(resolveSheetName(sheet, self)) - 1, 0),
222
+ spillRect: (sheet, row, col) => {
223
+ // The array anchored here, computed first so a formula that reads
224
+ // `A1#` sees the spill even on the pass that establishes it. Only an
225
+ // anchor has an entry; a covered cell or a plain value gives null,
226
+ // which the operator reads as #REF!.
227
+ const name = resolveSheetName(sheet, self);
228
+ compute(name, row, col);
229
+ const rect = spills.get(cellKey(name, row, col));
230
+ return rect ? [rect.r1, rect.c1, rect.r2, rect.c2] : null;
231
+ },
232
+ sheetsBetween: (from, to) => sheetsInRange(from, to),
99
233
  resolveNameNode: (name) => names.resolve(name),
234
+ findTable: (name) => rawTables.get(name),
235
+ tableAt: (sheet, row, col) => rawTables.at(resolveSheetName(sheet, self), row, col),
236
+ hiddenRow: hiddenRows ? (sheet, row) => hiddenRows(resolveSheetName(sheet, self), row) : undefined,
237
+ isSubtotal: (sheet, row, col) => {
238
+ const text = sheetCells(resolveSheetName(sheet, self))?.[row]?.[col] ?? '';
239
+ return /^\s*=\s*SUBTOTAL\s*\(/i.test(text);
240
+ },
100
241
  functions,
101
242
  };
102
243
  }
244
+ /** The engine's copy of the cells, for an engine that keeps one. */
245
+ function loadEngine() {
246
+ engine.load?.(order.map((name) => ({ name, cells: sheetCells(name).map((r) => [...r]) })));
247
+ }
248
+ /** Forget every derived thing: values, edges, volatility, spills. */
249
+ function dropAll() {
250
+ // Cells may have been replaced wholesale, so the SUBTOTAL flag is stale.
251
+ subtotalsHere = null;
252
+ values.clear();
253
+ previous.clear();
254
+ graph.clear();
255
+ volatile.clear();
256
+ spills.clear();
257
+ spilledBy.clear();
258
+ blocked.clear();
259
+ pending.clear();
260
+ }
261
+ const SUBTOTAL_TEXT = /^\s*=[\s\S]*SUBTOTAL\s*\(/i;
262
+ /** Whether any cell in the workbook holds a SUBTOTAL. Worked out once and
263
+ * forgotten again whenever a write could change the answer. */
264
+ function hasSubtotal() {
265
+ if (subtotalsHere !== null)
266
+ return subtotalsHere;
267
+ let found = false;
268
+ for (const sheet of order) {
269
+ const cells = sheetCells(sheet);
270
+ for (const line of cells) {
271
+ for (const text of line) {
272
+ if (text && SUBTOTAL_TEXT.test(text)) {
273
+ found = true;
274
+ break;
275
+ }
276
+ }
277
+ if (found)
278
+ break;
279
+ }
280
+ if (found)
281
+ break;
282
+ }
283
+ subtotalsHere = found;
284
+ return found;
285
+ }
286
+ /** Whether a key holds a formula, for the settling passes. */
287
+ function isFormulaKey(key) {
288
+ const at = parseCellKey(key);
289
+ return (sheetCells(at.sheet ?? '')?.[at.row]?.[at.col] ?? '').trim().startsWith('=');
290
+ }
291
+ /**
292
+ * Compute every formula in the workbook, so every spill is on record
293
+ * before any cell it covers is read. Lazy evaluation alone would leave a
294
+ * covered cell blank when it is read before its anchor, which a repaint
295
+ * from the top of a scrolled sheet does.
296
+ */
297
+ function settleAll() {
298
+ for (const other of order) {
299
+ const cells = sheetCells(other) ?? [];
300
+ for (let r = 0; r < cells.length; r += 1) {
301
+ const line = cells[r];
302
+ for (let c = 0; c < line.length; c += 1)
303
+ if (line[c].trim().startsWith('='))
304
+ compute(other, r, c);
305
+ }
306
+ }
307
+ settlePending();
308
+ runIterations();
309
+ }
310
+ /**
311
+ * Run the cycles until they stop moving.
312
+ *
313
+ * Everything in a cycle, and everything downstream of one, is recomputed
314
+ * from the values it last had; a pass that moves no cell by more than
315
+ * `maxChange` is the last one, and so is pass number `maxIterations`. This
316
+ * is Excel's model exactly: it does not promise convergence, it promises a
317
+ * bounded number of tries and whatever they reached.
318
+ */
319
+ function runIterations() {
320
+ if (!iteration.enabled)
321
+ return [];
322
+ const seeds = graph.cycles();
323
+ if (seeds.length === 0)
324
+ return [];
325
+ // Cycle members are downstream of each other, so `dirtyFrom` already
326
+ // includes them; the union is for a seed whose readers were never
327
+ // evaluated.
328
+ const affected = [...new Set([...seeds, ...graph.dirtyFrom(seeds)])];
329
+ for (let pass = 0; pass < Math.max(1, iteration.maxIterations); pass += 1) {
330
+ const before = new Map();
331
+ for (const key of affected) {
332
+ before.set(key, values.get(key) ?? previous.get(key));
333
+ values.delete(key);
334
+ }
335
+ let worst = 0;
336
+ for (const key of affected) {
337
+ const at = parseCellKey(key);
338
+ const after = compute(at.sheet ?? '', at.row, at.col);
339
+ worst = Math.max(worst, moved(before.get(key), after));
340
+ }
341
+ if (worst <= iteration.maxChange)
342
+ break;
343
+ }
344
+ return affected;
345
+ }
346
+ /** Compute what a spill changed under other formulas, until nothing is left. */
347
+ function settlePending() {
348
+ const done = [];
349
+ while (pending.size) {
350
+ const batch = [...pending];
351
+ pending.clear();
352
+ for (const key of batch) {
353
+ const at = parseCellKey(key);
354
+ compute(at.sheet ?? '', at.row, at.col);
355
+ done.push(key);
356
+ }
357
+ }
358
+ return done;
359
+ }
360
+ /** Drop an anchor's spill; the covered cells' values and edges go with it. */
361
+ function clearSpill(anchor) {
362
+ const rect = spills.get(anchor);
363
+ if (!rect)
364
+ return [];
365
+ spills.delete(anchor);
366
+ const at = parseCellKey(anchor);
367
+ const covered = [];
368
+ for (let r = rect.r1; r <= rect.r2; r += 1) {
369
+ for (let c = rect.c1; c <= rect.c2; c += 1) {
370
+ if (r === at.row && c === at.col)
371
+ continue;
372
+ const key = cellKey(at.sheet, r, c);
373
+ if (spilledBy.get(key) !== anchor)
374
+ continue;
375
+ spilledBy.delete(key);
376
+ values.delete(key);
377
+ graph.setPrecedents(key, null);
378
+ covered.push(key);
379
+ }
380
+ }
381
+ return covered;
382
+ }
383
+ /**
384
+ * Record an anchor's grid over its rectangle, or refuse it. Returns the
385
+ * anchor's own value: the grid's top-left cell, or #SPILL! when a cell
386
+ * in the way holds text or belongs to another spill.
387
+ */
388
+ function recordSpill(anchor, sheet, row, col, grid) {
389
+ const cells = sheetCells(sheet);
390
+ const rect = { r1: row, c1: col, r2: row + grid.length - 1, c2: col + (grid[0]?.length ?? 1) - 1 };
391
+ for (let r = rect.r1; r <= rect.r2; r += 1) {
392
+ for (let c = rect.c1; c <= rect.c2; c += 1) {
393
+ if (r === row && c === col)
394
+ continue;
395
+ const key = cellKey(sheet, r, c);
396
+ const owner = spilledBy.get(key);
397
+ if ((cells[r]?.[c] ?? '').trim() !== '' || (owner !== undefined && owner !== anchor)) {
398
+ for (const k of clearSpill(anchor))
399
+ pending.add(k);
400
+ blocked.add(anchor);
401
+ return { error: '#SPILL!' };
402
+ }
403
+ }
404
+ }
405
+ blocked.delete(anchor);
406
+ const before = spills.get(anchor);
407
+ const stillCovered = new Set();
408
+ for (let r = rect.r1; r <= rect.r2; r += 1) {
409
+ for (let c = rect.c1; c <= rect.c2; c += 1) {
410
+ if (r === row && c === col)
411
+ continue;
412
+ const key = cellKey(sheet, r, c);
413
+ stillCovered.add(key);
414
+ const fresh = spilledBy.get(key) !== anchor;
415
+ spilledBy.set(key, anchor);
416
+ values.set(key, grid[r - row]?.[c - col] ?? '');
417
+ graph.setPrecedents(key, [anchor]);
418
+ // Reported as changed, and a formula that read this cell while it
419
+ // was blank has a stale value.
420
+ pending.add(key);
421
+ if (fresh)
422
+ for (const k of graph.dirtyFrom([key]))
423
+ if (k !== key) {
424
+ values.delete(k);
425
+ pending.add(k);
426
+ }
427
+ }
428
+ }
429
+ // The cells the old rectangle covered and the new one does not read blank again.
430
+ if (before) {
431
+ for (let r = before.r1; r <= before.r2; r += 1) {
432
+ for (let c = before.c1; c <= before.c2; c += 1) {
433
+ const key = cellKey(sheet, r, c);
434
+ if (stillCovered.has(key) || spilledBy.get(key) !== anchor)
435
+ continue;
436
+ spilledBy.delete(key);
437
+ values.delete(key);
438
+ graph.setPrecedents(key, null);
439
+ pending.add(key);
440
+ for (const k of graph.dirtyFrom([key]))
441
+ if (k !== key) {
442
+ values.delete(k);
443
+ pending.add(k);
444
+ }
445
+ }
446
+ }
447
+ }
448
+ spills.set(anchor, rect);
449
+ return grid[0]?.[0] ?? '';
450
+ }
451
+ /**
452
+ * How deep a cell's value may be resolved by recursion before the chain is
453
+ * primed from the far end instead. A running-balance column is a chain as
454
+ * long as the sheet, and evaluating the last cell of one by recursion
455
+ * overflows the JavaScript stack somewhere past a thousand rows, which the
456
+ * evaluator turns into a `#NUM!`. Priming keeps the depth flat, so the
457
+ * only limit left is the sheet's own size.
458
+ */
459
+ const DEEP = 150;
460
+ let depth = 0;
461
+ /**
462
+ * Compute what a cell reads, furthest first, so the cell itself resolves
463
+ * against cached values rather than by recursing through the chain. The
464
+ * precedents are the ones the graph recorded at the last evaluation, which
465
+ * is exactly the chain a re-evaluation is about to walk again.
466
+ */
467
+ function primeChain(from) {
468
+ const seen = new Set([from]);
469
+ const chain = [from];
470
+ const stack = [from];
471
+ while (stack.length) {
472
+ const cur = stack.pop();
473
+ for (const p of graph.precedentsOf(cur)) {
474
+ if (seen.has(p) || values.get(p) !== undefined)
475
+ continue;
476
+ seen.add(p);
477
+ chain.push(p);
478
+ stack.push(p);
479
+ }
480
+ }
481
+ for (let i = chain.length - 1; i >= 1; i -= 1) {
482
+ const key = chain[i];
483
+ if (values.get(key) !== undefined)
484
+ continue;
485
+ const at = parseCellKey(key);
486
+ compute(at.sheet ?? '', at.row, at.col);
487
+ }
488
+ }
103
489
  function compute(sheet, row, col) {
104
490
  const cells = sheetCells(sheet);
105
491
  if (!cells)
@@ -114,39 +500,123 @@ export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], option
114
500
  // #REF! is reserved for the two cases that really are broken: a sheet
115
501
  // that does not exist, and a negative index, which is what a reference
116
502
  // shifted off the top by a delete becomes.
117
- if (row >= cells.length || col >= (cells[row]?.length ?? 0))
118
- return '';
119
503
  const key = cellKey(sheet, row, col);
120
504
  const cached = values.get(key);
121
505
  if (cached !== undefined)
122
506
  return cached;
123
- if (visiting.has(key))
124
- return { error: '#CYCLE!' };
507
+ if (row >= cells.length || col >= (cells[row]?.length ?? 0)) {
508
+ // Past the written area, unless a spill reaches here.
509
+ const anchor = spilledBy.get(key);
510
+ if (anchor === undefined)
511
+ return '';
512
+ const at = parseCellKey(anchor);
513
+ compute(at.sheet ?? sheet, at.row, at.col);
514
+ return values.get(key) ?? '';
515
+ }
516
+ if (visiting.has(key)) {
517
+ // The cell is being asked for itself. Without iteration that is the
518
+ // error; with it, the loop is meant, and the answer is what this cell
519
+ // was worth on the last pass, starting from 0 as Excel does.
520
+ if (!iteration.enabled)
521
+ return { error: '#CYCLE!' };
522
+ return previous.get(key) ?? 0;
523
+ }
524
+ // Deep enough that recursion is a risk: resolve what this cell reads from
525
+ // the far end first, and come back to it with everything cached.
526
+ if (depth >= DEEP)
527
+ primeChain(key);
125
528
  visiting.add(key);
529
+ depth += 1;
126
530
  const text = (cells[row]?.[col] ?? '').trim();
127
531
  let value;
128
532
  if (text === '') {
533
+ // A blank cell shows what an array formula spills into it.
534
+ const anchor = spilledBy.get(key);
535
+ if (anchor !== undefined) {
536
+ const at = parseCellKey(anchor);
537
+ compute(at.sheet ?? sheet, at.row, at.col);
538
+ const spilled = values.get(key);
539
+ if (spilled !== undefined) {
540
+ visiting.delete(key);
541
+ depth -= 1;
542
+ return spilled;
543
+ }
544
+ }
129
545
  value = '';
130
546
  }
131
547
  else if (text.startsWith('=')) {
132
548
  const ast = parseCached(text);
133
- if (!ast)
134
- value = { error: '#PARSE!' };
549
+ // The graph, the volatile set and the spill ranges are read off the
550
+ // reference grammar, which is Excel's whatever evaluates it, so they
551
+ // stay right for any engine. Only the value and the grid are asked for.
552
+ const result = engine.evaluate(text, { sheet, row, col }, {
553
+ context: contextFor(sheet, undefined, { row, col }),
554
+ parse: parseCached,
555
+ });
556
+ value = result.value;
557
+ const grid = isError(value) ? null : result.spill ?? null;
558
+ if (grid)
559
+ value = recordSpill(key, sheet, row, col, grid);
560
+ else if (spills.has(key))
561
+ for (const k of clearSpill(key))
562
+ pending.add(k);
563
+ if (ast) {
564
+ if (isVolatile(ast))
565
+ volatile.add(key);
566
+ else
567
+ volatile.delete(key);
568
+ graph.setPrecedents(key, precedentsOf(ast, { sheet }, (s) => Math.max(rowCount(s ?? sheet) - 1, 0), (name) => names.resolve(name),
569
+ // A structured reference's cells, so a total over a table is
570
+ // recalculated when a cell inside the table is typed. Best effort:
571
+ // a reference that cannot be resolved records no precedents rather
572
+ // than taking the cell's value down with it, since the value has
573
+ // already been worked out by the time this runs.
574
+ (node) => {
575
+ try {
576
+ return tableRectOf(node, contextFor(sheet, undefined, { row, col }));
577
+ }
578
+ catch {
579
+ return null;
580
+ }
581
+ },
582
+ // The cells a spilled-range operator covers right now. The anchor
583
+ // has just been computed by this same pass, so its rectangle is on
584
+ // record; reading it here (rather than recomputing) keeps the
585
+ // dependency extraction free of side effects.
586
+ (node) => {
587
+ const name = resolveSheetName(node.ref.sheet, sheet);
588
+ const rect = spills.get(cellKey(name, node.ref.row ?? 0, node.ref.col));
589
+ return rect ? { sheet: name, r1: rect.r1, c1: rect.c1, r2: rect.r2, c2: rect.c2 } : null;
590
+ },
591
+ // The sheets a 3D reference spans, so it recalculates when the cell
592
+ // on any of them is typed into.
593
+ (from, to) => sheetsInRange(from, to)));
594
+ }
135
595
  else {
136
- value = evaluate(ast, contextFor(sheet));
137
- graph.setPrecedents(key, precedentsOf(ast, { sheet }, (s) => Math.max(rowCount(s ?? sheet) - 1, 0), (name) => names.resolve(name)));
596
+ graph.setPrecedents(key, null);
597
+ volatile.delete(key);
138
598
  }
139
599
  }
600
+ else if (text.startsWith(TEXT_PREFIX)) {
601
+ // Excel's apostrophe: what follows is text, whatever it looks like,
602
+ // and the apostrophe itself is not part of it. It is the only way to
603
+ // keep a leading zero, a part number that reads as a date, or the
604
+ // text of a formula in a cell.
605
+ value = text.slice(1);
606
+ }
140
607
  else {
141
608
  const n = Number(text);
142
609
  const upper = text.toUpperCase();
143
610
  value = text !== '' && Number.isFinite(n) ? n
144
611
  : upper === 'TRUE' ? true
145
612
  : upper === 'FALSE' ? false
146
- : text;
613
+ : typedError(text) ?? text;
147
614
  }
148
615
  visiting.delete(key);
616
+ depth -= 1;
149
617
  values.set(key, value);
618
+ if (iteration.enabled)
619
+ previous.set(key, value);
150
620
  return value;
151
621
  }
152
622
  /** What typed text is worth when it is not a formula: a number, a boolean or itself. */
@@ -154,11 +624,16 @@ export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], option
154
624
  const t = text.trim();
155
625
  if (t === '')
156
626
  return '';
627
+ if (t.startsWith(TEXT_PREFIX))
628
+ return t.slice(1);
157
629
  if (t.startsWith('='))
158
630
  return t;
159
631
  const n = Number(t);
160
632
  const upper = t.toUpperCase();
161
- return Number.isFinite(n) ? n : upper === 'TRUE' ? true : upper === 'FALSE' ? false : t;
633
+ return Number.isFinite(n) ? n
634
+ : upper === 'TRUE' ? true
635
+ : upper === 'FALSE' ? false
636
+ : typedError(t) ?? t;
162
637
  }
163
638
  function rowCount(sheet) {
164
639
  return sheetCells(sheet)?.length ?? 0;
@@ -169,17 +644,29 @@ export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], option
169
644
  return 0;
170
645
  return cells.reduce((max, row) => Math.max(max, row.length), 0);
171
646
  }
172
- /** Drop cached values for a cell and everything downstream of it. */
647
+ /** Drop cached values for a cell and everything downstream of it, and
648
+ * for every volatile cell and its readers, which any write may affect. */
173
649
  function invalidate(keys) {
174
650
  const touched = [...keys];
175
651
  for (const key of keys)
176
652
  values.delete(key);
177
- for (const key of graph.dirtyFrom(keys)) {
653
+ for (const key of volatile) {
654
+ if (values.delete(key))
655
+ touched.push(key);
656
+ }
657
+ for (const key of graph.dirtyFrom([...keys, ...volatile])) {
178
658
  values.delete(key);
179
659
  touched.push(key);
180
660
  }
181
661
  return touched;
182
662
  }
663
+ /** Every listener on raw writes, plus the option that is one of them. */
664
+ const writeListeners = new Set();
665
+ function emitWrite(change) {
666
+ options.onWrite?.(change);
667
+ for (const listener of writeListeners)
668
+ listener(change);
669
+ }
183
670
  function reportRecalc(keys) {
184
671
  const cb = options.onRecalc;
185
672
  if (!cb || keys.length === 0)
@@ -197,6 +684,7 @@ export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], option
197
684
  get sheets() { return [...order]; },
198
685
  get active() { return active; },
199
686
  get names() { return names; },
687
+ get tables() { return tables; },
200
688
  setActive(name) {
201
689
  if (sheetCells(name))
202
690
  active = order.find((n) => n.toLowerCase() === name.toLowerCase()) ?? active;
@@ -212,6 +700,7 @@ export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], option
212
700
  const index = at === undefined ? order.length : Math.max(0, Math.min(at, order.length));
213
701
  order.splice(index, 0, chosen);
214
702
  active = chosen;
703
+ loadEngine();
215
704
  return chosen;
216
705
  },
217
706
  removeSheet(name) {
@@ -222,12 +711,19 @@ export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], option
222
711
  return false;
223
712
  const [removed] = order.splice(index, 1);
224
713
  byName.delete(removed.toLowerCase());
714
+ // The tables that lived on it go with it: a table on a sheet that is
715
+ // gone resolves to cells that are gone.
716
+ for (const table of rawTables.list()) {
717
+ if (table.sheet.toLowerCase() === removed.toLowerCase())
718
+ rawTables.remove(table.name);
719
+ }
225
720
  // Every cached value may have read the sheet that just went.
226
- values.clear();
227
- graph.clear();
721
+ dropAll();
722
+ loadEngine();
228
723
  if (active.toLowerCase() === removed.toLowerCase()) {
229
724
  active = order[Math.min(index, order.length - 1)] ?? order[0];
230
725
  }
726
+ settleAll();
231
727
  return true;
232
728
  },
233
729
  renameSheet(from, to) {
@@ -260,8 +756,16 @@ export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], option
260
756
  if (typeof next === 'string' && next !== entry.refersTo)
261
757
  names.define(entry.name, next);
262
758
  }
263
- values.clear();
264
- graph.clear();
759
+ // A table names the sheet it sits on, and a structured reference
760
+ // resolves through that name. Leaving it behind turned every
761
+ // `=SUM(Orders[Amount])` into #REF! the moment the sheet was renamed.
762
+ for (const table of rawTables.list()) {
763
+ if (table.sheet.toLowerCase() === from.toLowerCase())
764
+ rawTables.define({ ...table, sheet: to });
765
+ }
766
+ dropAll();
767
+ loadEngine();
768
+ settleAll();
265
769
  return true;
266
770
  },
267
771
  moveSheet(name, to) {
@@ -272,6 +776,46 @@ export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], option
272
776
  order.splice(Math.max(0, Math.min(to, order.length)), 0, moved);
273
777
  return true;
274
778
  },
779
+ copySheet(from, to) {
780
+ const index = order.findIndex((n) => n.toLowerCase() === from.toLowerCase());
781
+ const source = sheetCells(from);
782
+ if (index < 0 || !source)
783
+ return null;
784
+ let name = to;
785
+ if (name === undefined) {
786
+ const base = order[index];
787
+ for (let i = 2;; i += 1) {
788
+ const candidate = `${base} (${i})`;
789
+ if (!sheetCells(candidate) && isValidSheetName(candidate)) {
790
+ name = candidate;
791
+ break;
792
+ }
793
+ if (i > 1000)
794
+ return null;
795
+ }
796
+ }
797
+ if (!isValidSheetName(name) || sheetCells(name))
798
+ return null;
799
+ byName.set(name.toLowerCase(), source.map((line) => [...line]));
800
+ order.splice(index + 1, 0, name);
801
+ active = name;
802
+ // A table belongs to its sheet, so the copy gets one of its own, as
803
+ // Excel's Move or Copy does. Without it the copied cells keep their
804
+ // `[@Qty]` formulas with no table to resolve them, and every one of
805
+ // them reads #REF!.
806
+ for (const table of rawTables.list()) {
807
+ if (table.sheet.toLowerCase() !== from.toLowerCase())
808
+ continue;
809
+ rawTables.define({ ...table, sheet: name, name: freeTableName(table.name) });
810
+ }
811
+ // Nothing cached read the new sheet yet, but a formula elsewhere
812
+ // that reads a whole column of it by name cannot exist either, so
813
+ // only the graph needs the new cells' precedents, which compute
814
+ // records on first read. An engine with a copy of the cells does
815
+ // need the new sheet.
816
+ loadEngine();
817
+ return name;
818
+ },
275
819
  getRaw(sheet, row, col) {
276
820
  return sheetCells(sheet)?.[row]?.[col] ?? '';
277
821
  },
@@ -286,17 +830,41 @@ export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], option
286
830
  line.push('');
287
831
  if (line[col] === text)
288
832
  return;
833
+ if (SUBTOTAL_TEXT.test(text) || SUBTOTAL_TEXT.test(line[col] ?? ''))
834
+ subtotalsHere = null;
289
835
  line[col] = text;
836
+ engine.write?.(sheet, row, col, text);
837
+ emitWrite({ sheet: order.find((n) => n.toLowerCase() === sheet.toLowerCase()) ?? sheet, row, col, text });
290
838
  const key = cellKey(sheet, row, col);
291
- // A cell that is no longer a formula reads nothing.
292
- if (!text.trim().startsWith('='))
839
+ const keys = [key];
840
+ // A cell that is no longer a formula reads nothing, and spills nothing.
841
+ if (!text.trim().startsWith('=')) {
293
842
  graph.setPrecedents(key, null);
294
- reportRecalc(invalidate([key]));
843
+ volatile.delete(key);
844
+ }
845
+ if (spills.has(key))
846
+ keys.push(...clearSpill(key));
847
+ // Text landing in a spilled cell blocks its anchor; a cell going
848
+ // blank may free one that was blocked.
849
+ const anchor = spilledBy.get(key);
850
+ if (anchor !== undefined)
851
+ keys.push(anchor);
852
+ keys.push(...blocked);
853
+ const touched = invalidate(keys);
854
+ // The written cell and the anchors it touched compute now, so a new
855
+ // or changed spill is on record before anything reads under it;
856
+ // plain dependents wait for their next read, as before.
857
+ for (const k of keys)
858
+ if (isFormulaKey(k)) {
859
+ const at = parseCellKey(k);
860
+ compute(at.sheet ?? sheet, at.row, at.col);
861
+ }
862
+ reportRecalc([...new Set([...touched, ...settlePending(), ...runIterations()])]);
295
863
  },
296
864
  getValue(sheet, row, col) {
297
865
  return compute(sheet, row, col);
298
866
  },
299
- evaluateText(sheet, text, override) {
867
+ evaluateText(sheet, text, override, at) {
300
868
  const t = text.trim();
301
869
  if (t === '')
302
870
  return '';
@@ -304,7 +872,9 @@ export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], option
304
872
  if (!sheetCells(sheet))
305
873
  return { error: '#REF!' };
306
874
  const ast = parseCached(t);
307
- return ast ? evaluate(ast, contextFor(sheet, override)) : { error: '#PARSE!' };
875
+ // The cell the text is read for, so ROW() and COLUMN() and a
876
+ // [@Column] reference mean something in a rule's formula.
877
+ return ast ? evaluate(ast, contextFor(sheet, override, at ?? (override ? { row: override.row, col: override.col } : undefined))) : { error: '#PARSE!' };
308
878
  }
309
879
  return literal(t);
310
880
  },
@@ -327,10 +897,55 @@ export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], option
327
897
  }
328
898
  return out;
329
899
  },
900
+ repointAfterMove(move) {
901
+ if (!sheetCells(move.sheet) || !sheetCells(move.toSheet))
902
+ return;
903
+ if (move.dRow === 0 && move.dCol === 0 && move.sheet === move.toSheet)
904
+ return;
905
+ let touched = false;
906
+ // The whole workbook: another sheet's =Orders!A5 names a moved cell
907
+ // just as a local reference does.
908
+ for (const other of order) {
909
+ const cells = sheetCells(other);
910
+ for (let r = 0; r < cells.length; r += 1) {
911
+ const line = cells[r];
912
+ for (let c = 0; c < line.length; c += 1) {
913
+ const text = line[c] ?? '';
914
+ if (!text.startsWith('='))
915
+ continue;
916
+ const next = repointReferences(text, move, other);
917
+ if (typeof next === 'string' && next !== text) {
918
+ line[c] = next;
919
+ touched = true;
920
+ }
921
+ }
922
+ }
923
+ }
924
+ // A name has no home sheet, so only a qualified reference can match.
925
+ for (const entry of names.list()) {
926
+ const next = repointReferences(entry.refersTo, move, null);
927
+ if (typeof next === 'string' && next !== entry.refersTo) {
928
+ names.define(entry.name, next);
929
+ touched = true;
930
+ }
931
+ }
932
+ if (!touched)
933
+ return;
934
+ dropAll();
935
+ astCache.clear();
936
+ loadEngine();
937
+ settleAll();
938
+ },
330
939
  applyStructuralEdit(sheet, edit) {
331
940
  const target = sheetCells(sheet);
332
941
  if (!target || edit.count <= 0)
333
942
  return;
943
+ // The tables move with their cells, so a structured reference keeps
944
+ // meaning the same column after a row is inserted above the table.
945
+ const moved = shiftTables(rawTables.list(), sheet, edit);
946
+ rawTables.clear();
947
+ for (const table of moved)
948
+ rawTables.define(table);
334
949
  // Rewrite every formula in the WORKBOOK, not just this sheet: another
335
950
  // sheet may hold =Orders!A5, and inserting a row in Orders has to move
336
951
  // it exactly as if it were local.
@@ -381,21 +996,115 @@ export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], option
381
996
  line.splice(edit.at, edit.count);
382
997
  }
383
998
  }
384
- values.clear();
385
- graph.clear();
999
+ dropAll();
386
1000
  astCache.clear();
1001
+ loadEngine();
1002
+ settleAll();
1003
+ },
1004
+ precedents(sheet, row, col) {
1005
+ const name = order.find((n) => n.toLowerCase() === sheet.toLowerCase());
1006
+ if (!name)
1007
+ return [];
1008
+ // Computing the cell records what it reads.
1009
+ compute(name, row, col);
1010
+ return graph.precedentsOf(cellKey(name, row, col)).map((key) => {
1011
+ const at = parseCellKey(key);
1012
+ return { sheet: at.sheet ?? name, row: at.row, col: at.col };
1013
+ });
1014
+ },
1015
+ dependents(sheet, row, col) {
1016
+ const name = order.find((n) => n.toLowerCase() === sheet.toLowerCase());
1017
+ if (!name)
1018
+ return [];
1019
+ // A formula that was never evaluated has no edge yet: evaluate every
1020
+ // formula once so the graph is whole.
1021
+ for (const other of order) {
1022
+ const cells = sheetCells(other) ?? [];
1023
+ for (let r = 0; r < cells.length; r += 1) {
1024
+ const line = cells[r];
1025
+ for (let c = 0; c < line.length; c += 1)
1026
+ if (line[c].trim().startsWith('='))
1027
+ compute(other, r, c);
1028
+ }
1029
+ }
1030
+ return graph.dependentsOf(cellKey(name, row, col)).map((key) => {
1031
+ const at = parseCellKey(key);
1032
+ return { sheet: at.sheet ?? name, row: at.row, col: at.col };
1033
+ });
1034
+ },
1035
+ subscribeWrites(listener) {
1036
+ writeListeners.add(listener);
1037
+ return () => { writeListeners.delete(listener); };
1038
+ },
1039
+ setHiddenRows(source) {
1040
+ hiddenRows = source;
1041
+ // Only SUBTOTAL reads what is hidden, and nothing in the dependency
1042
+ // graph can see that it changed - so everything is recomputed, but
1043
+ // only when there is a SUBTOTAL to recompute.
1044
+ if (!hasSubtotal())
1045
+ return;
1046
+ dropAll();
1047
+ settleAll();
387
1048
  },
388
1049
  recalculate() {
389
- values.clear();
390
- graph.clear();
1050
+ dropAll();
1051
+ loadEngine();
1052
+ settleAll();
1053
+ },
1054
+ get iteration() { return { ...iteration }; },
1055
+ setIteration(next) {
1056
+ const wanted = cleanIteration({ ...iteration, ...next });
1057
+ if (wanted.enabled === iteration.enabled && wanted.maxIterations === iteration.maxIterations && wanted.maxChange === iteration.maxChange)
1058
+ return;
1059
+ iteration = wanted;
1060
+ // Turning it on turns #CYCLE! into a number, and turning it off turns
1061
+ // the number back into #CYCLE!, so every cached value is suspect.
1062
+ dropAll();
1063
+ settleAll();
1064
+ },
1065
+ spillOf(sheet, row, col) {
1066
+ const name = order.find((n) => n.toLowerCase() === sheet.toLowerCase());
1067
+ if (!name)
1068
+ return null;
1069
+ compute(name, row, col);
1070
+ const key = cellKey(name, row, col);
1071
+ const anchor = spills.has(key) ? key : spilledBy.get(key);
1072
+ if (anchor === undefined)
1073
+ return null;
1074
+ const rect = spills.get(anchor);
1075
+ if (!rect)
1076
+ return null;
1077
+ const at = parseCellKey(anchor);
1078
+ return { anchor: { row: at.row, col: at.col }, rect: [rect.r1, rect.c1, rect.r2, rect.c2] };
1079
+ },
1080
+ spills(sheet) {
1081
+ const name = order.find((n) => n.toLowerCase() === sheet.toLowerCase());
1082
+ if (!name)
1083
+ return [];
1084
+ settleAll();
1085
+ const out = [];
1086
+ for (const [anchor, rect] of spills) {
1087
+ const at = parseCellKey(anchor);
1088
+ if ((at.sheet ?? '').toLowerCase() !== name.toLowerCase())
1089
+ continue;
1090
+ out.push({ row: at.row, col: at.col, rect: [rect.r1, rect.c1, rect.r2, rect.c2] });
1091
+ }
1092
+ return out.sort((a, b) => a.row - b.row || a.col - b.col);
391
1093
  },
392
1094
  rowCount,
393
1095
  colCount,
394
1096
  serialize() {
1097
+ const list = rawTables.list();
395
1098
  return {
396
1099
  sheets: order.map((name) => ({ name, cells: sheetCells(name).map((r) => [...r]) })),
397
1100
  active,
398
1101
  names: names.serialize(),
1102
+ // Absent rather than empty in a workbook with no tables, so a saved
1103
+ // document from before they existed reads the same either way.
1104
+ ...(list.length ? { tables: list.map((t) => ({ ...t })) } : {}),
1105
+ // Written only when it is on, so a document saved by a workbook that
1106
+ // never heard of iteration reads back identically.
1107
+ ...(iteration.enabled ? { iteration: { ...iteration } } : {}),
399
1108
  };
400
1109
  },
401
1110
  };
@@ -404,6 +1113,8 @@ export function createWorkbook(initial = [{ name: 'Sheet1', cells: [] }], option
404
1113
  order.push(sheet.name);
405
1114
  }
406
1115
  active = order[0] ?? '';
1116
+ loadEngine();
1117
+ settleAll();
407
1118
  return workbook;
408
1119
  }
409
1120
  export { isError };