@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
@@ -0,0 +1,13 @@
1
+ // USheet UI 实际渲染的 desktop 组件样式(#15:缺项会导致仅依赖本入口的宿主拿到无样式组件)
2
+ import '@veltra/desktop/components/contextmenu/style'
3
+ import '@veltra/desktop/components/dropdown/style'
4
+ import '@veltra/desktop/components/number-input/style'
5
+ import '@veltra/desktop/components/palette/style'
6
+ import '@veltra/desktop/components/scroll/style'
7
+ import '@veltra/desktop/components/tip/style'
8
+ import '@veltra/desktop/components/loading/style' // v-loading(sheet.vue grid 容器)
9
+ import '@veltra/desktop/components/message/style' // message / messageConfirm(弹层、tabs、公式栏)
10
+ import '@veltra/desktop/components/input/style' // UInput(find-popup)
11
+ import '@veltra/desktop/components/file-picker/style' // UFilePicker(insert-image-popup)
12
+ import '@veltra/desktop/components/icon/style' // UIcon(sheet-toolbar / sheet-tabs)
13
+ import './style.scss'
@@ -0,0 +1,200 @@
1
+ import { debounce } from '@cat-kit/core'
2
+ import {
3
+ inferCellType,
4
+ normalizeInputValue,
5
+ type CellData
6
+ } from '@veltra/sheet-core/core/cell-store'
7
+ import {
8
+ findAll,
9
+ findNextFrom,
10
+ findPrevFrom,
11
+ type FindMatch,
12
+ type FindOptions
13
+ } from '@veltra/sheet-core/core/find'
14
+ import type { Sheet } from '@veltra/sheet-core/core/sheet'
15
+ import { computed, ref, shallowRef, watch } from 'vue'
16
+
17
+ import type { SheetContext } from '../../tools/context'
18
+
19
+ interface UseFindReplaceOptions {
20
+ /** 目标 sheet(弹层存活期间固定;tab 切换会关闭并销毁弹层) */
21
+ getSheet: () => Sheet
22
+ context: SheetContext
23
+ }
24
+
25
+ /**
26
+ * 查找条逻辑(关键词 / 上一个 / 下一个 / 命中计数 / 替换):
27
+ * - 关键词 / 选项变化 → 重新查找并定位第一个命中
28
+ * - Enter = 下一个、Shift+Enter = 上一个(行主序、到边界循环)
29
+ * - 替换当前 = 一次 setCells(一个 undo 单元);全部替换 = 一次批量 = 单 undo 单元
30
+ * - 公式格不参与替换(写入 {v,t} 会覆盖公式原文 f,导致公式丢失)
31
+ */
32
+ export function useFindReplace(options: UseFindReplaceOptions) {
33
+ const { getSheet, context } = options
34
+
35
+ const findQuery = ref('')
36
+ const findReplace = ref('')
37
+ const caseSensitive = ref(false)
38
+ const wholeCell = ref(false)
39
+ const searchIn = ref<'value' | 'formula'>('value')
40
+ /** 全部命中(行主序;core/find 纯逻辑,无头可测) */
41
+ const findMatches = shallowRef<FindMatch[]>([])
42
+ /** 当前命中在 findMatches 中的下标;-1 = 无命中 */
43
+ const findCursor = ref(-1)
44
+
45
+ const canFind = computed(() => findMatches.value.length > 0)
46
+ const canReplace = computed(() => canFind.value && findCursor.value >= 0)
47
+
48
+ const findCountText = computed(() => {
49
+ const total = findMatches.value.length
50
+ if (total === 0) return '0 / 0'
51
+ return `${Math.min(findCursor.value + 1, total)} / ${total}`
52
+ })
53
+
54
+ function findOptions(): FindOptions {
55
+ return {
56
+ caseSensitive: caseSensitive.value,
57
+ wholeCell: wholeCell.value,
58
+ searchIn: searchIn.value
59
+ }
60
+ }
61
+
62
+ /** 上次全扫时的关键词 / 选项快照(识别防抖窗口内的脏缓存,见 ensureFreshMatches) */
63
+ let scannedQuery = ''
64
+ let scannedOptions = ''
65
+
66
+ function optionsSnapshot(): string {
67
+ return `${caseSensitive.value}|${wholeCell.value}|${searchIn.value}`
68
+ }
69
+
70
+ /** 关键词或选项变化:重新查找并定位第一个命中(否则保持当前位置附近) */
71
+ function refreshFind(initial: boolean): void {
72
+ const matches = findAll(getSheet(), findQuery.value, findOptions())
73
+ scannedQuery = findQuery.value
74
+ scannedOptions = optionsSnapshot()
75
+ findMatches.value = matches
76
+ if (matches.length === 0) {
77
+ findCursor.value = -1
78
+ return
79
+ }
80
+ if (initial) {
81
+ findCursor.value = 0
82
+ context.selectCell(matches[0]!.addr)
83
+ } else {
84
+ findCursor.value = Math.min(findCursor.value, matches.length - 1)
85
+ }
86
+ }
87
+
88
+ /**
89
+ * 防抖窗口内(关键词已输入、尚未触发全扫)Enter 导航 / 替换仍持有旧关键词的
90
+ * 命中缓存——先同步补扫一次,保证导航与替换作用于当前输入框里的关键词
91
+ * (否则「全部替换」会把旧关键词的命中集批量改写,与屏幕上看到的查找词不符)
92
+ */
93
+ function ensureFreshMatches(): void {
94
+ if (findQuery.value !== scannedQuery || optionsSnapshot() !== scannedOptions) {
95
+ refreshFind(false)
96
+ }
97
+ }
98
+
99
+ /** 定位到 findMatches 中与目标地址相同的命中(找不到则保持) */
100
+ function locateMatch(match: FindMatch): void {
101
+ const index = findMatches.value.findIndex(
102
+ (item) => item.addr.row === match.addr.row && item.addr.col === match.addr.col
103
+ )
104
+ if (index >= 0) findCursor.value = index
105
+ context.selectCell(match.addr)
106
+ }
107
+
108
+ function findForward(): void {
109
+ ensureFreshMatches()
110
+ const matches = findMatches.value
111
+ if (matches.length === 0) return
112
+ const active = context.getSelection().activeCell
113
+ // 在已缓存的全量命中数组上二分导航——原 findNext 每次重跑 findAll 全表扫描(#9)
114
+ const next = active ? findNextFrom(matches, active) : matches[0]
115
+ if (!next) return
116
+ locateMatch(next)
117
+ }
118
+
119
+ function findPrevious(): void {
120
+ ensureFreshMatches()
121
+ const matches = findMatches.value
122
+ if (matches.length === 0) return
123
+ const active = context.getSelection().activeCell
124
+ const prev = active ? findPrevFrom(matches, active) : matches[matches.length - 1]
125
+ if (!prev) return
126
+ locateMatch(prev)
127
+ }
128
+
129
+ /** Enter = 下一个;Shift+Enter = 上一个(UInput 的 keydown 透传到外层容器,冒泡可达) */
130
+ function handleFindKeydown(event: KeyboardEvent): void {
131
+ if (event.key !== 'Enter') return
132
+ event.preventDefault()
133
+ if (event.shiftKey) {
134
+ findPrevious()
135
+ } else {
136
+ findForward()
137
+ }
138
+ }
139
+
140
+ /** 替换文本 → CellData(空字符串 = 清除单元格;数字/布尔文本规范化后推断类型) */
141
+ function replaceData(): CellData | undefined {
142
+ if (findReplace.value === '') return undefined
143
+ const normalized = normalizeInputValue(findReplace.value)
144
+ const t = inferCellType(normalized)
145
+ return t ? { v: normalized, t } : { v: normalized }
146
+ }
147
+
148
+ /** 公式格不参与替换(写入 {v,t} 会覆盖公式原文 f,导致公式丢失) */
149
+ function isReplaceable(match: FindMatch): boolean {
150
+ return !context.getCellData(match.addr)?.f
151
+ }
152
+
153
+ /** 替换当前命中格(一次 setCells = 一个 undo 单元);替换后重新查找 */
154
+ function replaceCurrent(): void {
155
+ ensureFreshMatches()
156
+ const match = findMatches.value[findCursor.value]
157
+ if (!match || !isReplaceable(match)) return
158
+ context.setCells([{ addr: match.addr, data: replaceData() }])
159
+ refreshFind(false)
160
+ }
161
+
162
+ /** 全部替换(一次 setCells 批量 = 单 undo 单元,undo 一次全部还原);公式格自动跳过 */
163
+ function replaceAll(): void {
164
+ ensureFreshMatches()
165
+ const matches = findMatches.value.filter(isReplaceable)
166
+ if (matches.length === 0) return
167
+ context.setCells(matches.map((match) => ({ addr: match.addr, data: replaceData() })))
168
+ refreshFind(false)
169
+ }
170
+
171
+ // 关键词 / 选项变化 → 重新查找(定位第一个命中)。
172
+ // 每击键全扫 440ms 级:连续击键时防抖合并(300ms 停键才扫,immediate=false 即
173
+ // 首键不抢扫,#28),避免每键一次全表扫描把 UI 卡死;若停键前已 Enter / 替换
174
+ // (ensureFreshMatches 同步补扫),快照已新鲜则跳过多余的补扫
175
+ const refreshFindDebounced = debounce(
176
+ () => {
177
+ if (findQuery.value === scannedQuery && optionsSnapshot() === scannedOptions) return
178
+ refreshFind(true)
179
+ },
180
+ 300,
181
+ false
182
+ )
183
+ watch([findQuery, caseSensitive, wholeCell, searchIn], refreshFindDebounced)
184
+
185
+ return {
186
+ findQuery,
187
+ findReplace,
188
+ caseSensitive,
189
+ wholeCell,
190
+ searchIn,
191
+ canFind,
192
+ canReplace,
193
+ findCountText,
194
+ findForward,
195
+ findPrevious,
196
+ handleFindKeydown,
197
+ replaceCurrent,
198
+ replaceAll
199
+ }
200
+ }
@@ -0,0 +1,34 @@
1
+ /** 引用选择上下文:光标前一个非空字符属于此集合时可插入单元格引用 */
2
+ const REF_TRIGGER_CHARS = new Set(['=', '(', ',', '+', '-', '*', '/', '^', '&', '<', '>'])
3
+
4
+ /**
5
+ * 判定是否处于「可插入引用」位置(无状态:每次按文本 + 光标重算)。
6
+ * `-` 的一元/二元歧义基础版不区分(`A1-` 后框选 → `A1-B2`,合法且误判无害)。
7
+ */
8
+ export function isRefSelectContext(text: string, cursor: number): boolean {
9
+ if (cursor < 0 || cursor > text.length) return false
10
+ // 非公式编辑不进入引用选择(普通文本失焦应提交)
11
+ if (!text.startsWith('=')) return false
12
+ const before = text.slice(0, cursor).replace(/\s+$/, '')
13
+ if (before.length === 0) return false
14
+ return REF_TRIGGER_CHARS.has(before[before.length - 1]!)
15
+ }
16
+
17
+ export interface InsertRefResult {
18
+ text: string
19
+ /** 插入后光标位置(落在引用文本之后) */
20
+ cursor: number
21
+ }
22
+
23
+ /** 在光标处插入引用文本(替换空选区;有选区时覆盖选区) */
24
+ export function insertRefText(
25
+ text: string,
26
+ cursor: number,
27
+ rangeText: string,
28
+ selectionEnd: number = cursor
29
+ ): InsertRefResult {
30
+ const start = Math.min(cursor, selectionEnd)
31
+ const end = Math.max(cursor, selectionEnd)
32
+ const next = text.slice(0, start) + rangeText + text.slice(end)
33
+ return { text: next, cursor: start + rangeText.length }
34
+ }
@@ -0,0 +1,150 @@
1
+ import {
2
+ listFormulaFunctions,
3
+ type FormulaFunctionMeta
4
+ } from '@veltra/sheet-core/core/formula/functions'
5
+
6
+ /** 补全候选上限 */
7
+ export const FORMULA_SUGGEST_LIMIT = 10
8
+
9
+ /**
10
+ * 空前缀时优先展示的常用函数(其余按名称升序补齐至上限)。
11
+ * 纯字典序会把 SUM / OR / ROUND 挤出前 10(ABS…NOT),导致输入 `=` 看不到 SUM。
12
+ */
13
+ export const COMMON_FORMULA_NAMES = [
14
+ 'SUM',
15
+ 'AVERAGE',
16
+ 'COUNT',
17
+ 'MAX',
18
+ 'MIN',
19
+ 'IF',
20
+ 'ROUND',
21
+ 'ABS',
22
+ 'AND',
23
+ 'OR'
24
+ ] as const
25
+
26
+ /** 函数名 token 前合法的「触发」字符(= / 运算符 / ( / ,) */
27
+ const SUGGEST_TRIGGER_CHARS = new Set(['=', '(', ',', '+', '-', '*', '/', '^', '&', '<', '>'])
28
+
29
+ export interface FormulaSuggestItem {
30
+ name: string
31
+ params: string[]
32
+ description: string
33
+ /** 签名展示:`SUM(number1, number2, ...)`;无 params 时仅名称 */
34
+ signature: string
35
+ }
36
+
37
+ export interface FormulaSuggestContext {
38
+ /** 当前前缀(可能为空:紧跟 `=` 后) */
39
+ prefix: string
40
+ /** 前缀在文本中的起始下标(含) */
41
+ start: number
42
+ /** 光标位置(= 前缀结束下标) */
43
+ end: number
44
+ }
45
+
46
+ /** 格式化函数签名;无 params 时仅返回名称 */
47
+ export function formatFunctionSignature(name: string, params: string[]): string {
48
+ if (params.length === 0) return name
49
+ return `${name}(${params.join(', ')})`
50
+ }
51
+
52
+ /**
53
+ * 判定补全上下文:光标前向匹配函数名 token(或紧跟 `=` 的空前缀),
54
+ * 且 token 前一个非空字符 ∈ 触发字符集。
55
+ * 镜像只读期由调用方跳过,本函数不做只读判断。
56
+ */
57
+ export function getSuggestContext(text: string, cursor: number): FormulaSuggestContext | null {
58
+ if (cursor < 0 || cursor > text.length) return null
59
+ const before = text.slice(0, cursor)
60
+ const tokenMatch = /[A-Za-z][A-Za-z0-9_.]*$/.exec(before)
61
+ const prefix = tokenMatch?.[0] ?? ''
62
+ const start = tokenMatch ? cursor - prefix.length : cursor
63
+
64
+ // 空前缀仅在紧跟 `=` 后弹出(验证清单:输入 `=` → 候选出现)。
65
+ // `<=` / `>=` 的尾随 `=` 是比较运算符一部分,不是函数名起点。
66
+ if (!tokenMatch) {
67
+ const trimmed = before.replace(/\s+$/, '')
68
+ if (!trimmed.endsWith('=')) return null
69
+ if (trimmed.endsWith('<=') || trimmed.endsWith('>=')) return null
70
+ return { prefix: '', start, end: cursor }
71
+ }
72
+
73
+ const beforeToken = before.slice(0, start).replace(/\s+$/, '')
74
+ if (beforeToken.length === 0) return null
75
+ const last = beforeToken[beforeToken.length - 1]!
76
+ if (!SUGGEST_TRIGGER_CHARS.has(last)) return null
77
+ return { prefix, start, end: cursor }
78
+ }
79
+
80
+ /**
81
+ * 前缀过滤函数表(大小写不敏感),上限 FORMULA_SUGGEST_LIMIT。
82
+ * 空前缀:常用函数优先,其余名称升序补齐;有前缀:名称升序。
83
+ */
84
+ export function filterFormulaSuggestions(prefix: string): FormulaSuggestItem[] {
85
+ const upper = prefix.toUpperCase()
86
+ const all = listFormulaFunctions()
87
+ if (!upper) {
88
+ const byName = new Map(all.map((fn) => [fn.name, fn]))
89
+ const items: FormulaSuggestItem[] = []
90
+ const used = new Set<string>()
91
+ for (const name of COMMON_FORMULA_NAMES) {
92
+ const fn = byName.get(name)
93
+ if (!fn) continue
94
+ items.push(toSuggestItem(fn))
95
+ used.add(name)
96
+ if (items.length >= FORMULA_SUGGEST_LIMIT) return items
97
+ }
98
+ for (const fn of all) {
99
+ if (used.has(fn.name)) continue
100
+ items.push(toSuggestItem(fn))
101
+ if (items.length >= FORMULA_SUGGEST_LIMIT) break
102
+ }
103
+ return items
104
+ }
105
+
106
+ const items: FormulaSuggestItem[] = []
107
+ for (const fn of all) {
108
+ if (!fn.name.startsWith(upper)) continue
109
+ items.push(toSuggestItem(fn))
110
+ if (items.length >= FORMULA_SUGGEST_LIMIT) break
111
+ }
112
+ return items
113
+ }
114
+
115
+ function toSuggestItem(fn: { name: string } & FormulaFunctionMeta): FormulaSuggestItem {
116
+ return {
117
+ name: fn.name,
118
+ params: fn.params,
119
+ description: fn.description,
120
+ signature: formatFunctionSignature(fn.name, fn.params)
121
+ }
122
+ }
123
+
124
+ export interface ApplySuggestResult {
125
+ text: string
126
+ /** 替换后光标位置(落在 `NAME()` 的括号内) */
127
+ cursor: number
128
+ }
129
+
130
+ /** 将 [start, end) 的 token 替换为 `NAME()`,光标入括号内(免手动补右括号) */
131
+ export function applySuggest(
132
+ text: string,
133
+ start: number,
134
+ end: number,
135
+ name: string
136
+ ): ApplySuggestResult {
137
+ const insert = `${name}()`
138
+ const next = text.slice(0, start) + insert + text.slice(end)
139
+ return { text: next, cursor: start + name.length + 1 }
140
+ }
141
+
142
+ /**
143
+ * 键盘导航:↑↓ 循环;返回新的高亮下标。
144
+ * activeIndex 越界时按 0 处理。
145
+ */
146
+ export function moveSuggestIndex(activeIndex: number, length: number, delta: 1 | -1): number {
147
+ if (length <= 0) return 0
148
+ const cur = ((activeIndex % length) + length) % length
149
+ return (cur + delta + length) % length
150
+ }
@@ -0,0 +1,247 @@
1
+ import { contextmenu } from '@veltra/desktop'
2
+ import type { CellAddress, CellRange } from '@veltra/sheet-core/core/address'
3
+ import type { Sheet } from '@veltra/sheet-core/core/sheet'
4
+ import { SheetGrid, type SheetGridContextMenuInfo } from '@veltra/sheet-core/grid/sheet-grid'
5
+ import { onBeforeUnmount, onMounted, watch } from 'vue'
6
+
7
+ import type { SheetContext } from '../../tools/context'
8
+ import type { SheetProps } from '../../types'
9
+ import {
10
+ buildBodyMenus,
11
+ buildColHeaderMenus,
12
+ buildRowHeaderMenus,
13
+ ensureContextMenuSelection,
14
+ resolveRenderSize
15
+ } from './sheet-context-menu'
16
+
17
+ /** 模板 ref(useTemplateRef 的只读形态) */
18
+ type ElRef<T> = { readonly value: T | null | undefined }
19
+
20
+ /** 公式栏暴露接口(镜像 + 引用选择) */
21
+ export interface FormulaBarMirror {
22
+ mirrorGridEdit: (addr: CellAddress) => void
23
+ exitMirror: (addr: CellAddress) => void
24
+ /** fx 编辑中且处于可插入引用位置 */
25
+ isRefSelecting: () => boolean
26
+ /** 网格 pointerdown → 挂起 blur 提交 */
27
+ beginBlurSuppress: () => void
28
+ /** 画布选区 → 插入引用文本 */
29
+ handleRefSelect: (range: CellRange) => void
30
+ }
31
+
32
+ interface UseSheetGridOptions {
33
+ props: SheetProps
34
+ gridRef: ElRef<HTMLElement>
35
+ getActiveSheet: () => Sheet
36
+ context: SheetContext
37
+ formulaBarRef: ElRef<FormulaBarMirror>
38
+ }
39
+
40
+ /** 缓存实例(LRU 淘汰):每个 sheet 一个独立容器 div,非激活容器 visibility:hidden 堆叠 */
41
+ interface CachedGrid {
42
+ sheet: Sheet
43
+ grid: SheetGrid
44
+ el: HTMLDivElement
45
+ lastUsed: number
46
+ /** 释放实例:取消 structure-change 订阅 + grid.release + 移除容器 */
47
+ release: () => void
48
+ /** 隐藏期间发生 structure-change(程序化行列变更)→ 切回时需重建 */
49
+ dirty: boolean
50
+ }
51
+
52
+ /**
53
+ * 缓存容量:最近 N 个 sheet 的 SheetGrid 实例常驻(tab 来回切换零重建)。
54
+ * 大文件多 sheet 场景下切换卡顿的主因是每次重建 VTable(实例创建 + scenegraph
55
+ * 首屏构建 ~300ms);缓存命中时仅切换容器可见性 + 选区回驱(≈0)。
56
+ * 每实例持有一个 canvas + scenegraph,容量即内存上限(含隐藏实例的渲染开销)。
57
+ */
58
+ const GRID_CACHE_CAPACITY = 3
59
+
60
+ /**
61
+ * SheetGrid 生命周期与网格右键菜单(LRU 实例缓存):
62
+ * - activateGrid:tab 切换入口——命中缓存 → 显示 + 模型同步(零重建);
63
+ * 未命中 → 新建实例入缓存,超容量淘汰最久未用(release + 移除容器)
64
+ * - rebuildGrid:强制重建激活实例并清空缓存(structure-change / props 尺寸
65
+ * 变化 / 工作簿切换 / 导入替换——这些场景下缓存实例的 columns / 数据源失效)
66
+ * - 右键菜单:body / 行号 / 列头
67
+ * - 公式栏引用选择:interceptSelection 拦截选区回写 + pointerdown 挂起 blur
68
+ */
69
+ export function useSheetGrid(options: UseSheetGridOptions) {
70
+ const { props, gridRef, getActiveSheet, context, formulaBarRef } = options
71
+ const cache = new Map<Sheet, CachedGrid>()
72
+ let active: CachedGrid | undefined
73
+ let seq = 0
74
+ let detachPointerDown: (() => void) | undefined
75
+
76
+ function handleContextMenu(info: SheetGridContextMenuInfo): void {
77
+ const sheet = getActiveSheet()
78
+ const renderSize = resolveRenderSize(props.rows, props.cols, sheet.rows, sheet.cols)
79
+ ensureContextMenuSelection(context, info, renderSize)
80
+
81
+ const menus =
82
+ info.kind === 'row-header'
83
+ ? buildRowHeaderMenus(context)
84
+ : info.kind === 'col-header'
85
+ ? buildColHeaderMenus(context)
86
+ : buildBodyMenus(context)
87
+
88
+ // 最小宽度兜底;实际宽度由最长菜单项撑开(勿固定 240 导致空白过大)
89
+ contextmenu.pop({ mousePosition: { x: info.x, y: info.y }, width: 'max-content', menus })
90
+ }
91
+
92
+ function bindGridPointerDown(container: HTMLElement): void {
93
+ detachPointerDown?.()
94
+ const onPointerDown = () => {
95
+ formulaBarRef.value?.beginBlurSuppress()
96
+ }
97
+ // capture:赶在 textarea blur 之前挂起提交
98
+ container.addEventListener('pointerdown', onPointerDown, true)
99
+ detachPointerDown = () => {
100
+ container.removeEventListener('pointerdown', onPointerDown, true)
101
+ detachPointerDown = undefined
102
+ }
103
+ }
104
+
105
+ /** 新建实例:创建独立容器 div(absolute 堆叠于 grid 区内)并构造 SheetGrid */
106
+ function createGrid(sheet: Sheet): CachedGrid {
107
+ const host = gridRef.value
108
+ if (!host) throw new Error('useSheetGrid: grid 容器未挂载')
109
+ const el = document.createElement('div')
110
+ el.className = 'u-sheet__grid-instance'
111
+ host.appendChild(el)
112
+ const grid = new SheetGrid({
113
+ container: el,
114
+ sheet,
115
+ rows: props.rows,
116
+ cols: props.cols,
117
+ readonly: props.readonly,
118
+ resolveDisplayValue: props.resolveDisplayValue,
119
+ resolveCellStyle: props.resolveCellStyle,
120
+ resolveCellRenderer: props.resolveCellRenderer,
121
+ onContextMenu: handleContextMenu,
122
+ onEditStart: (addr) => formulaBarRef.value?.mirrorGridEdit(addr),
123
+ onEditEnd: (addr) => formulaBarRef.value?.exitMirror(addr),
124
+ // 引用选择:不回写模型选区,序列化为 A1 / A1:B2 交给公式栏
125
+ interceptSelection: () => formulaBarRef.value?.isRefSelecting() ?? false,
126
+ onSelectionIntercept: (range) => formulaBarRef.value?.handleRefSelect(range)
127
+ })
128
+ // 结构变更订阅(vue 层只绑定激活 sheet;隐藏实例必须自持标记,切回时判定过期)
129
+ let dirty = false
130
+ const offStructureChange = sheet.on('structure-change', () => {
131
+ dirty = true
132
+ })
133
+ const release = (): void => {
134
+ offStructureChange()
135
+ grid.release()
136
+ el.remove()
137
+ }
138
+ return {
139
+ sheet,
140
+ grid,
141
+ el,
142
+ lastUsed: ++seq,
143
+ release,
144
+ get dirty() {
145
+ return dirty
146
+ }
147
+ }
148
+ }
149
+
150
+ /** 切换激活实例:显示目标、隐藏其余(visibility:hidden 保持尺寸,canvas 不重建) */
151
+ function showOnly(target: CachedGrid): void {
152
+ for (const item of cache.values()) {
153
+ const on = item === target
154
+ item.el.style.visibility = on ? 'visible' : 'hidden'
155
+ // 隐藏实例停用视图同步(只置脏),激活实例立即同步挂起变更(#12)
156
+ item.grid.setVisible(on)
157
+ }
158
+ }
159
+
160
+ /** 淘汰最久未用的缓存实例(保留激活实例) */
161
+ function evict(): void {
162
+ while (cache.size > GRID_CACHE_CAPACITY) {
163
+ let oldest: CachedGrid | undefined
164
+ for (const item of cache.values()) {
165
+ if (item === active) continue
166
+ if (!oldest || item.lastUsed < oldest.lastUsed) oldest = item
167
+ }
168
+ if (!oldest) break
169
+ cache.delete(oldest.sheet)
170
+ oldest.release()
171
+ }
172
+ }
173
+
174
+ /** 清空全部缓存实例(workbook 切换 / 导入替换 / 渲染尺寸变化 / 结构变更) */
175
+ function invalidateAll(): void {
176
+ for (const item of cache.values()) {
177
+ item.release()
178
+ }
179
+ cache.clear()
180
+ active = undefined
181
+ detachPointerDown?.()
182
+ }
183
+
184
+ /**
185
+ * tab 切换:优先复用缓存实例(零重建)。
186
+ * 命中时校验实例是否过期(隐藏期间程序化 structure-change——插入/删除/undo
187
+ * 行列——vue 层只绑定激活 sheet 的 structure-change,隐藏实例靠自持订阅标记
188
+ * dirty);过期则释放重建,否则同步模型状态(选区回驱 + 冻结校正;数据/样式/
189
+ * 行高由实例常驻的 sheet 事件订阅持续同步,隐藏期间跨表重算等模型变更不丢)。
190
+ */
191
+ function activateGrid(): void {
192
+ const sheet = getActiveSheet()
193
+ const host = gridRef.value
194
+ if (!host) return
195
+ const cached = cache.get(sheet)
196
+ if (cached && !cached.dirty) {
197
+ cached.lastUsed = ++seq
198
+ cached.grid.syncFromModel()
199
+ showOnly(cached)
200
+ active = cached
201
+ bindGridPointerDown(cached.el)
202
+ return
203
+ }
204
+ if (cached) {
205
+ // 隐藏期间结构变更:实例过期,释放后重建(走下方新建路径)
206
+ cache.delete(sheet)
207
+ cached.release()
208
+ }
209
+ const created = createGrid(sheet)
210
+ cache.set(sheet, created)
211
+ created.lastUsed = ++seq
212
+ showOnly(created)
213
+ active = created
214
+ bindGridPointerDown(created.el)
215
+ evict()
216
+ }
217
+
218
+ /**
219
+ * 清理已不存在的 sheet 的缓存实例(workbook 删除 sheet 后及时释放,
220
+ * 避免条目与事件订阅残留到容量淘汰)。
221
+ */
222
+ function pruneCache(sheets: ReadonlyArray<Sheet>): void {
223
+ const alive = new Set(sheets)
224
+ for (const [sheet, item] of cache) {
225
+ // 存活或激活的实例保留;已删除的 sheet 释放(含事件订阅)
226
+ if (alive.has(sheet) || item === active) continue
227
+ cache.delete(sheet)
228
+ item.release()
229
+ }
230
+ }
231
+
232
+ /** 强制重建激活实例并清空缓存(structure-change / props / 工作簿替换) */
233
+ function rebuildGrid(): void {
234
+ invalidateAll()
235
+ activateGrid()
236
+ }
237
+
238
+ onMounted(activateGrid)
239
+
240
+ watch(() => [props.rows, props.cols, props.readonly], rebuildGrid)
241
+
242
+ onBeforeUnmount(() => {
243
+ invalidateAll()
244
+ })
245
+
246
+ return { rebuildGrid, activateGrid, pruneCache, getGrid: () => active?.grid }
247
+ }