@veltra/sheet 2.0.1 → 2.2.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 (339) hide show
  1. package/dist/components/report/binding-badge.js +76 -0
  2. package/dist/components/report/designer/cell-coords.js +46 -0
  3. package/dist/components/report/designer/col-widths.js +15 -0
  4. package/dist/components/report/designer/conditional-rules/helpers.js +139 -0
  5. package/dist/components/report/designer/drop-highlight-overlay.js +76 -0
  6. package/dist/components/report/designer/float-panel.js +385 -0
  7. package/dist/components/report/designer/role.js +17 -0
  8. package/dist/components/report/designer/rule-preview.js +33 -0
  9. package/dist/components/report/designer/rule-row.js +269 -0
  10. package/dist/components/report/designer/rules-dialog.js +135 -0
  11. package/dist/components/report/designer/topology-overlay.js +122 -0
  12. package/dist/components/report/designer/topology.js +64 -0
  13. package/dist/components/report/designer/use-grid-overlay.js +75 -0
  14. package/dist/components/report/designer-hub.js +177 -0
  15. package/dist/components/report/field-panel-helpers.js +33 -0
  16. package/dist/components/report/field-panel.js +132 -0
  17. package/dist/components/report/filter-bar.js +74 -0
  18. package/dist/components/report/hub-connection-form.js +145 -0
  19. package/dist/components/report/hub-dataset-editor.js +394 -0
  20. package/dist/components/report/report-designer.js +343 -0
  21. package/dist/components/report/report-viewer.js +111 -0
  22. package/dist/components/report/style.d.ts +17 -0
  23. package/dist/components/report/style.js +18 -0
  24. package/dist/components/report/style2.css +869 -0
  25. package/dist/components/report/use-report-designer.js +360 -0
  26. package/dist/components/report/use-report-viewer.js +50 -0
  27. package/dist/components/sheet/formula-bar.js +393 -0
  28. package/dist/components/sheet/formula-suggest-list.js +66 -0
  29. package/dist/components/sheet/import-file.js +140 -0
  30. package/dist/components/sheet/insert-count-menu-item.js +68 -0
  31. package/dist/components/sheet/insert-image.js +79 -0
  32. package/dist/components/sheet/popup-helpers.js +73 -0
  33. package/dist/components/sheet/popups/border-popup.js +75 -0
  34. package/dist/components/sheet/popups/border-preset-glyph.js +106 -0
  35. package/dist/components/sheet/popups/export-popup.js +51 -0
  36. package/dist/components/sheet/popups/fill-color-popup.js +37 -0
  37. package/dist/components/sheet/popups/find-popup.js +113 -0
  38. package/dist/components/sheet/popups/font-color-popup.js +37 -0
  39. package/dist/components/sheet/popups/font-size-popup.js +63 -0
  40. package/dist/components/sheet/popups/import.worker.d.ts +21 -0
  41. package/dist/components/sheet/popups/import.worker.js +41 -0
  42. package/dist/components/sheet/popups/insert-image-popup.js +38 -0
  43. package/dist/components/sheet/sheet-context-menu.js +200 -0
  44. package/dist/components/sheet/sheet-tabs.js +162 -0
  45. package/dist/components/sheet/sheet-toolbar.js +106 -0
  46. package/dist/components/sheet/sheet.js +198 -0
  47. package/dist/components/sheet/style.d.ts +11 -0
  48. package/dist/components/sheet/style.js +12 -0
  49. package/dist/components/sheet/style2.css +654 -0
  50. package/dist/components/sheet/use-find-replace.js +161 -0
  51. package/dist/components/sheet/use-formula-ref-select.js +37 -0
  52. package/dist/components/sheet/use-formula-suggest.js +126 -0
  53. package/dist/components/sheet/use-sheet-grid.js +188 -0
  54. package/dist/components/sheet/use-sheet-state.js +122 -0
  55. package/dist/components/sheet/use-sheet-tabs-bar.js +90 -0
  56. package/dist/components/sheet/use-tool-groups.js +42 -0
  57. package/dist/components/sheet/use-tool-popup.js +123 -0
  58. package/dist/components/sheet/use-toolbar-scroll.js +72 -0
  59. package/dist/index.d.ts +18 -26
  60. package/dist/index.js +14 -24
  61. package/dist/report/binding.d.ts +45 -0
  62. package/dist/report/binding.js +175 -0
  63. package/dist/report/connector.d.ts +66 -0
  64. package/dist/report/connector.js +159 -0
  65. package/dist/report/export-xlsx.d.ts +19 -0
  66. package/dist/report/export-xlsx.js +95 -0
  67. package/dist/report/filter-bar.d.ts +10 -0
  68. package/dist/report/filter-bar.js +31 -0
  69. package/dist/report/index.d.ts +12 -0
  70. package/dist/report/params.d.ts +13 -0
  71. package/dist/report/params.js +61 -0
  72. package/dist/report/render/aggregate.js +157 -0
  73. package/dist/report/render/builder.d.ts +12 -0
  74. package/dist/report/render/builder.js +139 -0
  75. package/dist/report/render/coordinate.d.ts +25 -0
  76. package/dist/report/render/coordinate.js +733 -0
  77. package/dist/report/render/index.d.ts +14 -0
  78. package/dist/report/render/index.js +21 -0
  79. package/dist/report/render/style-resolver.js +50 -0
  80. package/dist/report/render/template-index.d.ts +24 -0
  81. package/dist/report/render/template-index.js +137 -0
  82. package/dist/report/rules/evaluate.d.ts +25 -0
  83. package/dist/report/rules/evaluate.js +82 -0
  84. package/dist/report/rules/index.d.ts +2 -0
  85. package/dist/report/template.d.ts +59 -0
  86. package/dist/report/template.js +80 -0
  87. package/dist/report/types.d.ts +85 -0
  88. package/dist/tools/builtin.js +279 -8
  89. package/dist/tools/context.d.ts +54 -11
  90. package/dist/tools/context.js +28 -5
  91. package/dist/tools/download.js +68 -0
  92. package/dist/tools/export.worker.d.ts +16 -0
  93. package/dist/tools/export.worker.js +29 -0
  94. package/dist/tools/registry.d.ts +7 -4
  95. package/dist/tools/registry.js +1 -3
  96. package/dist/types/index.d.ts +3 -1
  97. package/dist/types/report-designer.d.ts +36 -0
  98. package/dist/types/report-viewer.d.ts +38 -0
  99. package/dist/types/sheet.d.ts +22 -5
  100. package/package.json +10 -7
  101. package/src/components/report/__test__/conditional-rules-helpers.test.ts +87 -0
  102. package/src/components/report/__test__/field-panel-helpers.test.ts +65 -0
  103. package/src/components/report/__test__/float-panel-position.test.ts +45 -0
  104. package/src/components/report/__test__/report-designer.test.ts +388 -0
  105. package/src/components/report/__test__/report-viewer.test.ts +334 -0
  106. package/src/components/report/__test__/topology.test.ts +143 -0
  107. package/src/components/report/__test__/use-report-designer.test.ts +565 -0
  108. package/src/components/report/binding-badge.ts +73 -0
  109. package/src/components/report/designer/cell-coords.ts +94 -0
  110. package/src/components/report/designer/col-widths.ts +33 -0
  111. package/src/components/report/designer/conditional-rules/helpers.ts +186 -0
  112. package/src/components/report/designer/drop-highlight-overlay.vue +74 -0
  113. package/src/components/report/designer/float-panel.vue +387 -0
  114. package/src/components/report/designer/role.ts +32 -0
  115. package/src/components/report/designer/rule-preview.vue +40 -0
  116. package/src/components/report/designer/rule-row.vue +252 -0
  117. package/src/components/report/designer/rules-dialog.vue +126 -0
  118. package/src/components/report/designer/topology-overlay.vue +127 -0
  119. package/src/components/report/designer/topology.ts +124 -0
  120. package/src/components/report/designer/use-grid-overlay.ts +113 -0
  121. package/src/components/report/designer-hub.vue +198 -0
  122. package/src/components/report/field-panel-helpers.ts +44 -0
  123. package/src/components/report/field-panel.vue +136 -0
  124. package/src/components/report/filter-bar.vue +77 -0
  125. package/src/components/report/hub-connection-form.vue +124 -0
  126. package/src/components/report/hub-dataset-editor.vue +369 -0
  127. package/src/components/report/index.ts +2 -0
  128. package/src/components/report/report-designer.vue +356 -0
  129. package/src/components/report/report-viewer.vue +128 -0
  130. package/src/components/report/style-designer.scss +583 -0
  131. package/src/components/report/style-hub.scss +292 -0
  132. package/src/components/report/style-viewer.scss +104 -0
  133. package/src/components/report/style.scss +3 -0
  134. package/src/components/report/style.ts +20 -0
  135. package/src/components/report/use-report-designer.ts +547 -0
  136. package/src/components/report/use-report-viewer.ts +79 -0
  137. package/src/components/sheet/__test__/formula-bar.test.ts +474 -0
  138. package/src/components/sheet/__test__/import-file.test.ts +183 -0
  139. package/src/components/sheet/__test__/insert-count-menu-item.test.ts +123 -0
  140. package/src/components/sheet/__test__/insert-image-popup.test.ts +89 -0
  141. package/src/components/sheet/__test__/insert-image.test.ts +100 -0
  142. package/src/components/sheet/__test__/sheet-component.test.ts +993 -0
  143. package/src/components/sheet/__test__/sheet-context-menu.test.ts +206 -0
  144. package/src/components/sheet/__test__/use-formula-ref-select.test.ts +35 -0
  145. package/src/components/sheet/__test__/use-formula-suggest.test.ts +83 -0
  146. package/src/components/sheet/formula-bar.vue +473 -0
  147. package/src/components/sheet/formula-suggest-list.vue +64 -0
  148. package/src/components/sheet/import-file.ts +173 -0
  149. package/src/components/sheet/insert-count-menu-item.vue +69 -0
  150. package/src/components/sheet/insert-image.ts +90 -0
  151. package/src/components/sheet/parsing.ts +11 -0
  152. package/src/components/sheet/popup-helpers.ts +50 -0
  153. package/src/components/sheet/popups/border-popup.vue +93 -0
  154. package/src/components/sheet/popups/border-preset-glyph.vue +111 -0
  155. package/src/components/sheet/popups/export-popup.vue +52 -0
  156. package/src/components/sheet/popups/fill-color-popup.vue +39 -0
  157. package/src/components/sheet/popups/find-popup.vue +111 -0
  158. package/src/components/sheet/popups/font-color-popup.vue +39 -0
  159. package/src/components/sheet/popups/font-size-popup.vue +52 -0
  160. package/src/components/sheet/popups/import.worker.ts +66 -0
  161. package/src/components/sheet/popups/insert-image-popup.vue +39 -0
  162. package/src/components/sheet/sheet-context-menu.ts +229 -0
  163. package/src/components/sheet/sheet-tabs.vue +162 -0
  164. package/src/components/sheet/sheet-toolbar.vue +91 -0
  165. package/src/components/sheet/sheet.vue +211 -0
  166. package/src/components/sheet/style.scss +737 -0
  167. package/src/components/sheet/style.ts +13 -0
  168. package/src/components/sheet/use-find-replace.ts +200 -0
  169. package/src/components/sheet/use-formula-ref-select.ts +34 -0
  170. package/src/components/sheet/use-formula-suggest.ts +150 -0
  171. package/src/components/sheet/use-sheet-grid.ts +247 -0
  172. package/src/components/sheet/use-sheet-state.ts +161 -0
  173. package/src/components/sheet/use-sheet-tabs-bar.ts +115 -0
  174. package/src/components/sheet/use-tool-groups.ts +61 -0
  175. package/src/components/sheet/use-tool-popup.ts +150 -0
  176. package/src/components/sheet/use-toolbar-scroll.ts +95 -0
  177. package/src/env.d.ts +11 -0
  178. package/src/index.ts +39 -14
  179. package/src/report/__test__/binding.test.ts +218 -0
  180. package/src/report/__test__/connector.test.ts +218 -0
  181. package/src/report/__test__/export-xlsx.test.ts +118 -0
  182. package/src/report/__test__/filter-bar.test.ts +58 -0
  183. package/src/report/__test__/params.test.ts +156 -0
  184. package/src/report/__test__/render.test.ts +728 -0
  185. package/src/report/__test__/rules.test.ts +131 -0
  186. package/src/report/__test__/template.test.ts +355 -0
  187. package/src/report/binding.ts +243 -0
  188. package/src/report/connector.ts +205 -0
  189. package/src/report/export-xlsx.ts +118 -0
  190. package/src/report/filter-bar.ts +36 -0
  191. package/src/report/index.ts +13 -0
  192. package/src/report/params.ts +96 -0
  193. package/src/report/render/__test__/aggregate.test.ts +16 -0
  194. package/src/report/render/__test__/coordinate.test.ts +405 -0
  195. package/src/report/render/aggregate.ts +240 -0
  196. package/src/report/render/builder.ts +211 -0
  197. package/src/report/render/coordinate.ts +1239 -0
  198. package/src/report/render/index.ts +30 -0
  199. package/src/report/render/style-resolver.ts +68 -0
  200. package/src/report/render/template-index.ts +200 -0
  201. package/src/report/rules/evaluate.ts +113 -0
  202. package/src/report/rules/index.ts +7 -0
  203. package/src/report/template.ts +141 -0
  204. package/src/report/types.ts +96 -0
  205. package/src/tools/__test__/builtin.test.ts +208 -66
  206. package/src/tools/__test__/context.test.ts +62 -2
  207. package/src/tools/builtin.ts +350 -6
  208. package/src/tools/context.ts +89 -11
  209. package/src/tools/download.ts +82 -0
  210. package/src/tools/export.worker.ts +61 -0
  211. package/src/tools/registry.ts +16 -1
  212. package/src/types/index.ts +2 -0
  213. package/src/types/report-designer.ts +36 -0
  214. package/src/types/report-viewer.ts +37 -0
  215. package/src/types/sheet.ts +26 -3
  216. package/dist/core/address.d.ts +0 -60
  217. package/dist/core/address.js +0 -142
  218. package/dist/core/address.js.map +0 -1
  219. package/dist/core/cell-store.d.ts +0 -61
  220. package/dist/core/cell-store.js +0 -135
  221. package/dist/core/cell-store.js.map +0 -1
  222. package/dist/core/command/default-registry.d.ts +0 -10
  223. package/dist/core/command/default-registry.js +0 -18
  224. package/dist/core/command/default-registry.js.map +0 -1
  225. package/dist/core/command/history.d.ts +0 -64
  226. package/dist/core/command/history.js +0 -127
  227. package/dist/core/command/history.js.map +0 -1
  228. package/dist/core/command/merge-cells.d.ts +0 -21
  229. package/dist/core/command/merge-cells.js +0 -74
  230. package/dist/core/command/merge-cells.js.map +0 -1
  231. package/dist/core/command/registry.d.ts +0 -17
  232. package/dist/core/command/registry.js +0 -26
  233. package/dist/core/command/registry.js.map +0 -1
  234. package/dist/core/command/set-cell-formula.d.ts +0 -17
  235. package/dist/core/command/set-cell-formula.js +0 -29
  236. package/dist/core/command/set-cell-formula.js.map +0 -1
  237. package/dist/core/command/set-cell-value.d.ts +0 -22
  238. package/dist/core/command/set-cell-value.js +0 -35
  239. package/dist/core/command/set-cell-value.js.map +0 -1
  240. package/dist/core/command/types.d.ts +0 -68
  241. package/dist/core/events.js +0 -29
  242. package/dist/core/events.js.map +0 -1
  243. package/dist/core/fill.d.ts +0 -34
  244. package/dist/core/fill.js +0 -320
  245. package/dist/core/fill.js.map +0 -1
  246. package/dist/core/formula/ast.d.ts +0 -58
  247. package/dist/core/formula/ast.js +0 -38
  248. package/dist/core/formula/ast.js.map +0 -1
  249. package/dist/core/formula/dependency-graph.d.ts +0 -70
  250. package/dist/core/formula/dependency-graph.js +0 -269
  251. package/dist/core/formula/dependency-graph.js.map +0 -1
  252. package/dist/core/formula/errors.d.ts +0 -25
  253. package/dist/core/formula/errors.js +0 -36
  254. package/dist/core/formula/errors.js.map +0 -1
  255. package/dist/core/formula/evaluator.d.ts +0 -41
  256. package/dist/core/formula/evaluator.js +0 -154
  257. package/dist/core/formula/evaluator.js.map +0 -1
  258. package/dist/core/formula/functions.d.ts +0 -30
  259. package/dist/core/formula/functions.js +0 -209
  260. package/dist/core/formula/functions.js.map +0 -1
  261. package/dist/core/formula/parser.d.ts +0 -7
  262. package/dist/core/formula/parser.js +0 -208
  263. package/dist/core/formula/parser.js.map +0 -1
  264. package/dist/core/formula/tokenizer.d.ts +0 -37
  265. package/dist/core/formula/tokenizer.js +0 -146
  266. package/dist/core/formula/tokenizer.js.map +0 -1
  267. package/dist/core/merge-manager.d.ts +0 -64
  268. package/dist/core/merge-manager.js +0 -111
  269. package/dist/core/merge-manager.js.map +0 -1
  270. package/dist/core/selection.d.ts +0 -31
  271. package/dist/core/selection.js +0 -62
  272. package/dist/core/selection.js.map +0 -1
  273. package/dist/core/sheet.d.ts +0 -116
  274. package/dist/core/sheet.js +0 -224
  275. package/dist/core/sheet.js.map +0 -1
  276. package/dist/core/workbook.d.ts +0 -43
  277. package/dist/core/workbook.js +0 -77
  278. package/dist/core/workbook.js.map +0 -1
  279. package/dist/grid/sheet-grid.d.ts +0 -81
  280. package/dist/grid/sheet-grid.js +0 -331
  281. package/dist/grid/sheet-grid.js.map +0 -1
  282. package/dist/grid/vtable-theme.js +0 -80
  283. package/dist/grid/vtable-theme.js.map +0 -1
  284. package/dist/tools/builtin.js.map +0 -1
  285. package/dist/tools/context.js.map +0 -1
  286. package/dist/tools/registry.js.map +0 -1
  287. package/dist/vue/index.d.ts +0 -2
  288. package/dist/vue/sheet.js +0 -211
  289. package/dist/vue/sheet.js.map +0 -1
  290. package/dist/vue/sheet.vue.d.ts +0 -24
  291. package/dist/vue/style.d.ts +0 -1
  292. package/dist/vue/style.js +0 -2
  293. package/dist/vue/style2.css +0 -93
  294. package/src/core/__test__/address.test.ts +0 -113
  295. package/src/core/__test__/cell-store.test.ts +0 -97
  296. package/src/core/__test__/command.test.ts +0 -377
  297. package/src/core/__test__/fill.test.ts +0 -140
  298. package/src/core/__test__/formula-adversarial.test.ts +0 -265
  299. package/src/core/__test__/formula-engine.test.ts +0 -470
  300. package/src/core/__test__/formula-functions.test.ts +0 -178
  301. package/src/core/__test__/formula-parser.test.ts +0 -160
  302. package/src/core/__test__/history.test.ts +0 -225
  303. package/src/core/__test__/merge-manager.test.ts +0 -106
  304. package/src/core/__test__/sheet.test.ts +0 -142
  305. package/src/core/address.ts +0 -166
  306. package/src/core/cell-store.ts +0 -173
  307. package/src/core/command/default-registry.ts +0 -15
  308. package/src/core/command/history.ts +0 -167
  309. package/src/core/command/index.ts +0 -7
  310. package/src/core/command/merge-cells.ts +0 -74
  311. package/src/core/command/registry.ts +0 -30
  312. package/src/core/command/set-cell-formula.ts +0 -29
  313. package/src/core/command/set-cell-value.ts +0 -38
  314. package/src/core/command/types.ts +0 -73
  315. package/src/core/events.ts +0 -37
  316. package/src/core/fill.ts +0 -314
  317. package/src/core/formula/ast.ts +0 -66
  318. package/src/core/formula/dependency-graph.ts +0 -329
  319. package/src/core/formula/errors.ts +0 -41
  320. package/src/core/formula/evaluator.ts +0 -219
  321. package/src/core/formula/functions.ts +0 -266
  322. package/src/core/formula/index.ts +0 -7
  323. package/src/core/formula/parser.ts +0 -198
  324. package/src/core/formula/tokenizer.ts +0 -160
  325. package/src/core/merge-manager.ts +0 -158
  326. package/src/core/selection.ts +0 -66
  327. package/src/core/sheet.ts +0 -282
  328. package/src/core/workbook.ts +0 -99
  329. package/src/grid/__test__/canvas-mock.ts +0 -49
  330. package/src/grid/__test__/setup.ts +0 -3
  331. package/src/grid/__test__/sheet-grid-formula.test.ts +0 -140
  332. package/src/grid/__test__/sheet-grid.test.ts +0 -275
  333. package/src/grid/sheet-grid.ts +0 -407
  334. package/src/grid/vtable-theme.ts +0 -61
  335. package/src/vue/__test__/sheet-component.test.ts +0 -267
  336. package/src/vue/sheet.vue +0 -235
  337. package/src/vue/style.scss +0 -94
  338. package/src/vue/style.ts +0 -2
  339. /package/src/{vue → components/sheet}/index.ts +0 -0
@@ -1,329 +0,0 @@
1
- import { cellKey, rangeContainsAddress, type CellAddress, type CellRange } from '../address'
2
- import type { CellData, CellType, CellValue } from '../cell-store'
3
- import type { CellPatch } from '../command/types'
4
- import type { Sheet } from '../sheet'
5
- import { collectReferences, type AstNode } from './ast'
6
- import { formulaError, isFormulaError, isFormulaErrorCode, type FormulaError } from './errors'
7
- import { evaluateAst, type ScalarValue } from './evaluator'
8
- import { invokeFormulaFunction } from './functions'
9
- import { parseFormula } from './parser'
10
- import { FormulaParseError } from './tokenizer'
11
-
12
- /**
13
- * 依赖图(工作簿级计算中枢):
14
- * - sheet 注册表:跨表引用按名解析(不存在 → #REF!)
15
- * - 正向索引:公式格 → 引用格集合(节点 deps);反向索引:引用格 → 依赖者
16
- * (单格引用走 cellKey 精确索引 O(1),区域引用走线性扫描)
17
- * - 变更时标脏(向上 BFS 收集全部依赖者)+ 拓扑序增量重算(递归向下,memo 去重)
18
- * - 循环引用检测:求值在途栈遇回边 → 环上所有格 #CYCLE!;依赖环外格只传播错误,
19
- * 打破循环(编辑环上格)后经标脏重算自动恢复
20
- *
21
- * 图状态与单元格存储严格同步:所有 f 变更(命令/undo/redo/rollback 回放)都经
22
- * Sheet.applyPatch → syncCell 维护节点;重算只发生在命令执行后(见 Sheet.executeCommand),
23
- * undo/redo 靠派生补丁精确回放缓存值,不重算。
24
- */
25
-
26
- /** 公式节点的一个依赖(sheet 名已补齐为具体值) */
27
- export interface FormulaDependency {
28
- sheetName: string
29
- range: CellRange
30
- }
31
-
32
- /** 公式节点(依赖图管理的公式格状态;依赖图外部只读使用) */
33
- export interface FormulaNode {
34
- readonly sheetName: string
35
- readonly addr: CellAddress
36
- readonly formula: string
37
- /** null = 解析失败(求值 #ERROR!,无依赖) */
38
- readonly ast: AstNode | null
39
- readonly deps: FormulaDependency[]
40
- /** 反向索引清理器(removeNode 时执行) */
41
- readonly cleanups: (() => void)[]
42
- }
43
-
44
- export class DependencyGraph {
45
- private readonly sheets = new Map<string, Sheet>()
46
- /** 正向索引宿主:sheetName → cellKey → 公式节点 */
47
- private readonly nodes = new Map<string, Map<number, FormulaNode>>()
48
- /** 反向索引(单格引用):sheetName → cellKey → 依赖者集合 */
49
- private readonly exact = new Map<string, Map<number, Set<FormulaNode>>>()
50
- /** 反向索引(区域引用):sheetName → { 区域, 依赖者 } 集合(线性扫描) */
51
- private readonly ranged = new Map<string, Set<{ range: CellRange; node: FormulaNode }>>()
52
-
53
- // ─── sheet 注册表 ─────────────────────────────────────────
54
-
55
- registerSheet(sheet: Sheet): void {
56
- this.sheets.set(sheet.name, sheet)
57
- }
58
-
59
- /** 注销 sheet 并移除其全部公式节点(引用它的公式在下次重算时得 #REF!) */
60
- unregisterSheet(sheet: Sheet): void {
61
- if (this.sheets.get(sheet.name) !== sheet) return
62
- this.sheets.delete(sheet.name)
63
- const sheetNodes = this.nodes.get(sheet.name)
64
- if (sheetNodes) {
65
- for (const node of sheetNodes.values()) this.removeNode(node)
66
- }
67
- }
68
-
69
- getSheet(name: string): Sheet | undefined {
70
- return this.sheets.get(name)
71
- }
72
-
73
- // ─── 节点查询 ─────────────────────────────────────────────
74
-
75
- getNode(sheetName: string, addr: CellAddress): FormulaNode | undefined {
76
- return this.nodes.get(sheetName)?.get(cellKey(addr))
77
- }
78
-
79
- /** 公式节点总数(测试/调试用) */
80
- get nodeCount(): number {
81
- let count = 0
82
- for (const sheetNodes of this.nodes.values()) count += sheetNodes.size
83
- return count
84
- }
85
-
86
- // ─── 图同步 ───────────────────────────────────────────────
87
-
88
- /**
89
- * 单元格补丁后的图同步:f 增/删/改 → 节点增/删/重建。
90
- * 由 Sheet.applyPatch(唯一变更通道)调用,命令与 undo/redo 回放均覆盖。
91
- */
92
- syncCell(sheet: Sheet, addr: CellAddress, before?: CellData, after?: CellData): void {
93
- const beforeF = before?.f
94
- const afterF = after?.f
95
- if (beforeF === afterF) return
96
- const existing = this.getNode(sheet.name, addr)
97
- if (existing) this.removeNode(existing)
98
- if (afterF != null && afterF !== '') this.addNode(sheet, addr, afterF)
99
- }
100
-
101
- // ─── 增量重算 ─────────────────────────────────────────────
102
-
103
- /**
104
- * 变更集 → 标脏 + 拓扑序重算 → 派生补丁(未应用)。
105
- * 调用方(Sheet.executeCommand)负责应用补丁并并入同一 undo 单元。
106
- */
107
- recalc(changed: readonly { sheet: Sheet; addr: CellAddress }[]): CellPatch[] {
108
- // 1. 标脏:变更格自身(若是公式)+ 向上 BFS 全部传递依赖者
109
- const dirty = new Set<FormulaNode>()
110
- const queue: FormulaNode[] = []
111
- const mark = (node: FormulaNode | undefined): void => {
112
- if (node && !dirty.has(node)) {
113
- dirty.add(node)
114
- queue.push(node)
115
- }
116
- }
117
- for (const { sheet, addr } of changed) {
118
- mark(this.getNode(sheet.name, addr))
119
- for (const dep of this.dependentsOf(sheet.name, addr)) mark(dep)
120
- }
121
- for (let i = 0; i < queue.length; i++) {
122
- const node = queue[i]!
123
- for (const dep of this.dependentsOf(node.sheetName, node.addr)) mark(dep)
124
- }
125
- if (dirty.size === 0) return []
126
-
127
- // 2. 拓扑序求值:递归向下(依赖先于使用求值),memo 保证每格最多算一次;
128
- // 在途栈遇回边 → 栈中自回边点起全部入环(#CYCLE!)
129
- const passValues = new Map<FormulaNode, ScalarValue | FormulaError>()
130
- const inProgress = new Set<FormulaNode>()
131
- const stack: FormulaNode[] = []
132
- const cycleMembers = new Set<FormulaNode>()
133
- const cycleError = formulaError('#CYCLE!')
134
-
135
- const evaluateNode = (node: FormulaNode): ScalarValue | FormulaError => {
136
- const cached = passValues.get(node)
137
- if (cached !== undefined) return cached
138
- if (inProgress.has(node)) {
139
- const start = stack.indexOf(node)
140
- for (let i = start; i < stack.length; i++) cycleMembers.add(stack[i]!)
141
- return cycleError
142
- }
143
- if (!node.ast) {
144
- const error = formulaError('#ERROR!')
145
- passValues.set(node, error)
146
- return error
147
- }
148
- inProgress.add(node)
149
- stack.push(node)
150
- let value: ScalarValue | FormulaError | (ScalarValue | FormulaError)[]
151
- try {
152
- value = evaluateAst(node.ast, {
153
- currentSheet: node.sheetName,
154
- readCell,
155
- readRange,
156
- callFunction: invokeFormulaFunction
157
- })
158
- } catch {
159
- // 求值期异常(如自定义函数抛错)不应击穿重算 → 记为 #ERROR!
160
- value = formulaError('#ERROR!')
161
- }
162
- inProgress.delete(node)
163
- stack.pop()
164
- // 区域直接作为公式结果('=A1:B2')→ #VALUE!
165
- if (Array.isArray(value)) value = formulaError('#VALUE!')
166
- if (cycleMembers.has(node)) value = cycleError
167
- passValues.set(node, value)
168
- return value
169
- }
170
-
171
- const readCell = (sheetName: string, addr: CellAddress): ScalarValue | FormulaError => {
172
- const sheet = this.sheets.get(sheetName)
173
- if (!sheet) return formulaError('#REF!')
174
- const dep = this.getNode(sheetName, addr)
175
- if (dep && dirty.has(dep)) return evaluateNode(dep)
176
- return cellDataToScalar(sheet.store.getCell(addr))
177
- }
178
-
179
- const readRange = (
180
- sheetName: string,
181
- range: CellRange
182
- ): (ScalarValue | FormulaError)[] | FormulaError => {
183
- const sheet = this.sheets.get(sheetName)
184
- if (!sheet) return formulaError('#REF!')
185
- const values: (ScalarValue | FormulaError)[] = []
186
- // 只迭代稀疏存在的格
187
- for (const [addr, data] of sheet.store.entriesInRange(range)) {
188
- const dep = this.getNode(sheetName, addr)
189
- values.push(dep && dirty.has(dep) ? evaluateNode(dep) : cellDataToScalar(data))
190
- }
191
- return values
192
- }
193
-
194
- for (const node of dirty) evaluateNode(node)
195
-
196
- // 3. 派生补丁:缓存值与重算结果一致的跳过(收敛时不产生冗余变更)
197
- const patches: CellPatch[] = []
198
- for (const node of dirty) {
199
- const value = passValues.get(node)!
200
- const { v, t } = serializeResult(value)
201
- const sheet = this.sheets.get(node.sheetName)
202
- if (!sheet) continue
203
- const before = sheet.store.getCell(node.addr)
204
- if (before && before.f === node.formula && before.v === v && before.t === t) continue
205
- patches.push({
206
- kind: 'cell',
207
- sheet,
208
- addr: node.addr,
209
- before,
210
- after: { f: node.formula, v, t }
211
- })
212
- }
213
- return patches
214
- }
215
-
216
- // ─── 内部 ─────────────────────────────────────────────────
217
-
218
- private addNode(sheet: Sheet, addr: CellAddress, formula: string): void {
219
- let ast: AstNode | null = null
220
- try {
221
- ast = parseFormula(formula)
222
- } catch (error) {
223
- if (!(error instanceof FormulaParseError)) throw error
224
- // 解析失败:ast 保持 null(求值 #ERROR!,无依赖)
225
- }
226
- const deps: FormulaDependency[] = []
227
- if (ast) {
228
- const seen = new Set<string>()
229
- for (const ref of collectReferences(ast)) {
230
- const sheetName = ref.sheet ?? sheet.name
231
- const { start, end } = ref.range
232
- const key = `${sheetName}|${start.row},${start.col},${end.row},${end.col}`
233
- if (seen.has(key)) continue
234
- seen.add(key)
235
- deps.push({ sheetName, range: ref.range })
236
- }
237
- }
238
- const node: FormulaNode = {
239
- sheetName: sheet.name,
240
- addr: { ...addr },
241
- formula,
242
- ast,
243
- deps,
244
- cleanups: []
245
- }
246
- let sheetNodes = this.nodes.get(sheet.name)
247
- if (!sheetNodes) {
248
- sheetNodes = new Map()
249
- this.nodes.set(sheet.name, sheetNodes)
250
- }
251
- sheetNodes.set(cellKey(addr), node)
252
-
253
- for (const dep of deps) {
254
- const isSingleCell =
255
- dep.range.start.row === dep.range.end.row && dep.range.start.col === dep.range.end.col
256
- if (isSingleCell) {
257
- let sheetExact = this.exact.get(dep.sheetName)
258
- if (!sheetExact) {
259
- sheetExact = new Map()
260
- this.exact.set(dep.sheetName, sheetExact)
261
- }
262
- const key = cellKey(dep.range.start)
263
- let set = sheetExact.get(key)
264
- if (!set) {
265
- set = new Set()
266
- sheetExact.set(key, set)
267
- }
268
- set.add(node)
269
- node.cleanups.push(() => {
270
- set.delete(node)
271
- if (set.size === 0) sheetExact.delete(key)
272
- })
273
- } else {
274
- let set = this.ranged.get(dep.sheetName)
275
- if (!set) {
276
- set = new Set()
277
- this.ranged.set(dep.sheetName, set)
278
- }
279
- const entry = { range: dep.range, node }
280
- set.add(entry)
281
- node.cleanups.push(() => set.delete(entry))
282
- }
283
- }
284
- }
285
-
286
- private removeNode(node: FormulaNode): void {
287
- for (const cleanup of node.cleanups) cleanup()
288
- const sheetNodes = this.nodes.get(node.sheetName)
289
- if (!sheetNodes) return
290
- sheetNodes.delete(cellKey(node.addr))
291
- if (sheetNodes.size === 0) this.nodes.delete(node.sheetName)
292
- }
293
-
294
- private dependentsOf(sheetName: string, addr: CellAddress): Set<FormulaNode> {
295
- const result = new Set<FormulaNode>()
296
- const exactSet = this.exact.get(sheetName)?.get(cellKey(addr))
297
- if (exactSet) for (const node of exactSet) result.add(node)
298
- const rangedSet = this.ranged.get(sheetName)
299
- if (rangedSet) {
300
- for (const entry of rangedSet) {
301
- if (rangeContainsAddress(entry.range, addr)) result.add(entry.node)
302
- }
303
- }
304
- return result
305
- }
306
- }
307
-
308
- /** 单元格存储 → 求值标量(t='e' 还原为错误标记;空 → null) */
309
- function cellDataToScalar(data: CellData | undefined): ScalarValue | FormulaError {
310
- if (!data || data.v == null) return null
311
- if (data.t === 'e') {
312
- return formulaError(isFormulaErrorCode(data.v) ? data.v : '#ERROR!')
313
- }
314
- return data.v
315
- }
316
-
317
- /** 求值结果 → 存储形态(str = 公式结果字符串,e = 错误;与决策 2 一致) */
318
- function serializeResult(value: ScalarValue | FormulaError): { v: CellValue; t: CellType } {
319
- if (isFormulaError(value)) return { v: value.code, t: 'e' }
320
- if (value === null) return { v: 0, t: 'n' } // 防御:公式结果不会是 null
321
- switch (typeof value) {
322
- case 'number':
323
- return { v: value, t: 'n' }
324
- case 'boolean':
325
- return { v: value, t: 'b' }
326
- default:
327
- return { v: value, t: 'str' }
328
- }
329
- }
@@ -1,41 +0,0 @@
1
- /**
2
- * 公式错误值体系。
3
- *
4
- * 求值器内部以 FormulaError 标记对象传递错误(随运算传播);
5
- * 写入单元格时序列化为 `v = 错误码, t = 'e'`(决策 2 的 CellData 格式)。
6
- */
7
-
8
- /** 公式错误码(Excel 子集 + 解析失败 #ERROR!、循环引用 #CYCLE!) */
9
- export const FORMULA_ERROR_CODES = [
10
- '#DIV/0!',
11
- '#VALUE!',
12
- '#NAME?',
13
- '#REF!',
14
- '#ERROR!',
15
- '#CYCLE!'
16
- ] as const
17
-
18
- export type FormulaErrorCode = (typeof FORMULA_ERROR_CODES)[number]
19
-
20
- const ERROR_BRAND: unique symbol = Symbol('veltra-sheet.formula-error')
21
-
22
- /** 求值过程中的错误标记(不参与普通值运算,遇运算即传播) */
23
- export interface FormulaError {
24
- readonly [ERROR_BRAND]: true
25
- readonly code: FormulaErrorCode
26
- }
27
-
28
- /** 构造错误标记 */
29
- export function formulaError(code: FormulaErrorCode): FormulaError {
30
- return { [ERROR_BRAND]: true, code }
31
- }
32
-
33
- /** 判定错误标记 */
34
- export function isFormulaError(value: unknown): value is FormulaError {
35
- return typeof value === 'object' && value !== null && ERROR_BRAND in value
36
- }
37
-
38
- /** 判定合法错误码(读取 t='e' 的单元格时校验) */
39
- export function isFormulaErrorCode(value: unknown): value is FormulaErrorCode {
40
- return typeof value === 'string' && (FORMULA_ERROR_CODES as readonly string[]).includes(value)
41
- }
@@ -1,219 +0,0 @@
1
- import type { CellAddress, CellRange } from '../address'
2
- import type { AstNode, BinaryOperator } from './ast'
3
- import { formulaError, isFormulaError, type FormulaError } from './errors'
4
-
5
- /**
6
- * AST 求值器(纯函数,不持有状态;单元格读取经 FormulaEvalContext 注入)。
7
- *
8
- * 空格(null)参与运算的规则同 Excel:
9
- * - 数字上下文按 0(空单元格 +1 = 1);空字符串字面量参与算术 → #VALUE!
10
- * - 文本上下文按 '';布尔上下文按 FALSE
11
- * - 比较时空格归一为对方类型的零值
12
- * 错误值遇运算即传播(左操作数优先)。
13
- */
14
-
15
- /** 标量值(单元格/字面量可取的全集;null = 空单元格) */
16
- export type ScalarValue = number | string | boolean | null
17
-
18
- /** 求值结果:标量 / 错误 / 区域展开数组(数组仅作为函数参数形态出现) */
19
- export type EvalValue = ScalarValue | FormulaError | (ScalarValue | FormulaError)[]
20
-
21
- /** 求值上下文(由依赖图按公式节点注入) */
22
- export interface FormulaEvalContext {
23
- /** 当前公式所在 sheet(裸引用缺省表) */
24
- readonly currentSheet: string
25
- /** 读取单格(原始存储语义;表不存在 → #REF!) */
26
- readCell(sheet: string, addr: CellAddress): ScalarValue | FormulaError
27
- /** 读取区域(只含稀疏存在的格;表不存在 → #REF!) */
28
- readRange(sheet: string, range: CellRange): (ScalarValue | FormulaError)[] | FormulaError
29
- /** 调用函数(名称未知 → #NAME?;参数个数非法 → #VALUE!) */
30
- callFunction(name: string, nodes: AstNode[], evalNode: (node: AstNode) => EvalValue): EvalValue
31
- }
32
-
33
- const NUMERIC_TEXT_RE = /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/
34
-
35
- /** 强转数字:null→0,布尔→1/0,数字文本→数字,其余文本→#VALUE!,错误传播 */
36
- export function coerceToNumber(value: EvalValue): number | FormulaError {
37
- if (isFormulaError(value)) return value
38
- if (Array.isArray(value)) return formulaError('#VALUE!')
39
- if (value === null) return 0
40
- switch (typeof value) {
41
- case 'number':
42
- return value
43
- case 'boolean':
44
- return value ? 1 : 0
45
- case 'string': {
46
- const text = value.trim()
47
- if (text === '') return formulaError('#VALUE!')
48
- if (text.toUpperCase() === 'TRUE') return 1
49
- if (text.toUpperCase() === 'FALSE') return 0
50
- if (NUMERIC_TEXT_RE.test(text)) return Number.parseFloat(text)
51
- return formulaError('#VALUE!')
52
- }
53
- }
54
- }
55
-
56
- /** 强转文本:null→'',布尔→TRUE/FALSE,错误传播 */
57
- export function coerceToText(value: EvalValue): string | FormulaError {
58
- if (isFormulaError(value)) return value
59
- if (Array.isArray(value)) return formulaError('#VALUE!')
60
- if (value === null) return ''
61
- switch (typeof value) {
62
- case 'string':
63
- return value
64
- case 'number':
65
- return String(value)
66
- case 'boolean':
67
- return value ? 'TRUE' : 'FALSE'
68
- }
69
- }
70
-
71
- /** 强转布尔:null→FALSE,数字≠0,TRUE/FALSE 文本,其余文本→#VALUE!,错误传播 */
72
- export function coerceToBoolean(value: EvalValue): boolean | FormulaError {
73
- if (isFormulaError(value)) return value
74
- if (Array.isArray(value)) return formulaError('#VALUE!')
75
- if (value === null) return false
76
- switch (typeof value) {
77
- case 'boolean':
78
- return value
79
- case 'number':
80
- return value !== 0
81
- case 'string': {
82
- const text = value.trim().toUpperCase()
83
- if (text === 'TRUE') return true
84
- if (text === 'FALSE') return false
85
- return formulaError('#VALUE!')
86
- }
87
- }
88
- }
89
-
90
- /** 比较:同类型按类型规则(文本大小写不敏感);混合类型 数字 < 文本 < 布尔;null 归一为对方零值 */
91
- export function compareScalars(left: ScalarValue, right: ScalarValue): number {
92
- if (left === null || right === null) {
93
- if (left === null && right === null) return 0
94
- if (left === null) return compareScalars(zeroLike(right), right)
95
- return compareScalars(left, zeroLike(left))
96
- }
97
- const leftType = typeof left
98
- const rightType = typeof right
99
- if (leftType === 'number' && rightType === 'number') {
100
- return left < right ? -1 : left > right ? 1 : 0
101
- }
102
- if (leftType === 'string' && rightType === 'string') {
103
- const a = (left as string).toUpperCase()
104
- const b = (right as string).toUpperCase()
105
- return a < b ? -1 : a > b ? 1 : 0
106
- }
107
- if (leftType === 'boolean' && rightType === 'boolean') {
108
- return left === right ? 0 : left ? 1 : -1
109
- }
110
- return typeRank(leftType) - typeRank(rightType)
111
- }
112
-
113
- function zeroLike(value: ScalarValue): ScalarValue {
114
- if (typeof value === 'number') return 0
115
- if (typeof value === 'string') return ''
116
- return false
117
- }
118
-
119
- function typeRank(type: string): number {
120
- if (type === 'number') return 0
121
- if (type === 'string') return 1
122
- return 2
123
- }
124
-
125
- const COMPARISON_OPS = new Set(['=', '<>', '<', '<=', '>', '>='])
126
-
127
- /** AST 求值 */
128
- export function evaluateAst(node: AstNode, ctx: FormulaEvalContext): EvalValue {
129
- switch (node.kind) {
130
- case 'number':
131
- case 'string':
132
- case 'boolean':
133
- return node.value
134
- case 'name':
135
- return formulaError('#NAME?')
136
- case 'cell':
137
- return ctx.readCell(node.sheet ?? ctx.currentSheet, node.addr)
138
- case 'range':
139
- return ctx.readRange(node.sheet ?? ctx.currentSheet, node.range)
140
- case 'unary': {
141
- const n = coerceToNumber(evaluateAst(node.operand, ctx))
142
- if (isFormulaError(n)) return n
143
- return node.op === '-' ? -n : n
144
- }
145
- case 'percent': {
146
- const n = coerceToNumber(evaluateAst(node.operand, ctx))
147
- if (isFormulaError(n)) return n
148
- return n / 100
149
- }
150
- case 'binary':
151
- return evaluateBinary(node.op, node.left, node.right, ctx)
152
- case 'call':
153
- return ctx.callFunction(node.name, node.args, (arg) => evaluateAst(arg, ctx))
154
- }
155
- }
156
-
157
- function evaluateBinary(
158
- op: BinaryOperator,
159
- leftNode: AstNode,
160
- rightNode: AstNode,
161
- ctx: FormulaEvalContext
162
- ): EvalValue {
163
- const left = evaluateAst(leftNode, ctx)
164
- if (isFormulaError(left)) return left
165
- const right = evaluateAst(rightNode, ctx)
166
- if (isFormulaError(right)) return right
167
-
168
- if (op === '&') {
169
- const l = coerceToText(left)
170
- if (isFormulaError(l)) return l
171
- const r = coerceToText(right)
172
- if (isFormulaError(r)) return r
173
- return l + r
174
- }
175
-
176
- if (COMPARISON_OPS.has(op)) {
177
- if (Array.isArray(left) || Array.isArray(right)) return formulaError('#VALUE!')
178
- const cmp = compareScalars(left, right)
179
- switch (op) {
180
- case '=':
181
- return cmp === 0
182
- case '<>':
183
- return cmp !== 0
184
- case '<':
185
- return cmp < 0
186
- case '<=':
187
- return cmp <= 0
188
- case '>':
189
- return cmp > 0
190
- default:
191
- return cmp >= 0
192
- }
193
- }
194
-
195
- const l = coerceToNumber(left)
196
- if (isFormulaError(l)) return l
197
- const r = coerceToNumber(right)
198
- if (isFormulaError(r)) return r
199
-
200
- switch (op) {
201
- case '+':
202
- return l + r
203
- case '-':
204
- return l - r
205
- case '*':
206
- return l * r
207
- case '/':
208
- if (r === 0) return formulaError('#DIV/0!')
209
- return l / r
210
- case '^': {
211
- if (l === 0 && r < 0) return formulaError('#DIV/0!')
212
- const value = Math.pow(l, r)
213
- return Number.isFinite(value) ? value : formulaError('#VALUE!')
214
- }
215
- // & 与比较运算已在上方提前返回
216
- default:
217
- return formulaError('#ERROR!')
218
- }
219
- }