@veltra/sheet 2.0.1 → 2.1.1

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 (211) hide show
  1. package/dist/index.d.ts +4 -26
  2. package/dist/index.js +3 -23
  3. package/dist/tools/builtin.js +279 -8
  4. package/dist/tools/context.d.ts +54 -11
  5. package/dist/tools/context.js +28 -5
  6. package/dist/tools/download.js +68 -0
  7. package/dist/tools/export.worker.d.ts +16 -0
  8. package/dist/tools/export.worker.js +29 -0
  9. package/dist/tools/registry.d.ts +7 -4
  10. package/dist/tools/registry.js +1 -3
  11. package/dist/types/index.d.ts +1 -0
  12. package/dist/types/sheet.d.ts +6 -5
  13. package/dist/vue/formula-bar.js +393 -0
  14. package/dist/vue/formula-suggest-list.js +66 -0
  15. package/dist/vue/import-file.js +140 -0
  16. package/dist/vue/insert-count-menu-item.js +68 -0
  17. package/dist/vue/insert-image.js +79 -0
  18. package/dist/vue/popup-helpers.js +73 -0
  19. package/dist/vue/popups/border-popup.js +75 -0
  20. package/dist/vue/popups/border-preset-glyph.js +106 -0
  21. package/dist/vue/popups/export-popup.js +51 -0
  22. package/dist/vue/popups/fill-color-popup.js +37 -0
  23. package/dist/vue/popups/find-popup.js +113 -0
  24. package/dist/vue/popups/font-color-popup.js +37 -0
  25. package/dist/vue/popups/font-size-popup.js +63 -0
  26. package/dist/vue/popups/import.worker.d.ts +21 -0
  27. package/dist/vue/popups/import.worker.js +41 -0
  28. package/dist/vue/popups/insert-image-popup.js +38 -0
  29. package/dist/vue/sheet-context-menu.js +200 -0
  30. package/dist/vue/sheet-tabs.js +162 -0
  31. package/dist/vue/sheet-toolbar.js +106 -0
  32. package/dist/vue/sheet.js +144 -164
  33. package/dist/vue/style.d.ts +11 -1
  34. package/dist/vue/style.js +10 -0
  35. package/dist/vue/style2.css +564 -3
  36. package/dist/vue/use-find-replace.js +161 -0
  37. package/dist/vue/use-formula-ref-select.js +37 -0
  38. package/dist/vue/use-formula-suggest.js +126 -0
  39. package/dist/vue/use-sheet-grid.js +180 -0
  40. package/dist/vue/use-sheet-state.js +122 -0
  41. package/dist/vue/use-sheet-tabs-bar.js +90 -0
  42. package/dist/vue/use-tool-groups.js +42 -0
  43. package/dist/vue/use-tool-popup.js +123 -0
  44. package/dist/vue/use-toolbar-scroll.js +72 -0
  45. package/package.json +12 -7
  46. package/src/env.d.ts +11 -0
  47. package/src/index.ts +203 -14
  48. package/src/tools/__test__/builtin.test.ts +208 -66
  49. package/src/tools/__test__/context.test.ts +62 -2
  50. package/src/tools/builtin.ts +350 -6
  51. package/src/tools/context.ts +89 -11
  52. package/src/tools/download.ts +82 -0
  53. package/src/tools/export.worker.ts +61 -0
  54. package/src/tools/registry.ts +16 -1
  55. package/src/types/index.ts +6 -0
  56. package/src/types/sheet.ts +5 -3
  57. package/src/vue/__test__/formula-bar.test.ts +455 -0
  58. package/src/vue/__test__/import-file.test.ts +183 -0
  59. package/src/vue/__test__/insert-count-menu-item.test.ts +123 -0
  60. package/src/vue/__test__/insert-image-popup.test.ts +89 -0
  61. package/src/vue/__test__/insert-image.test.ts +100 -0
  62. package/src/vue/__test__/sheet-component.test.ts +716 -27
  63. package/src/vue/__test__/sheet-context-menu.test.ts +206 -0
  64. package/src/vue/__test__/use-formula-ref-select.test.ts +35 -0
  65. package/src/vue/__test__/use-formula-suggest.test.ts +83 -0
  66. package/src/vue/formula-bar.vue +473 -0
  67. package/src/vue/formula-suggest-list.vue +64 -0
  68. package/src/vue/import-file.ts +173 -0
  69. package/src/vue/insert-count-menu-item.vue +69 -0
  70. package/src/vue/insert-image.ts +90 -0
  71. package/src/vue/parsing.ts +11 -0
  72. package/src/vue/popup-helpers.ts +50 -0
  73. package/src/vue/popups/border-popup.vue +93 -0
  74. package/src/vue/popups/border-preset-glyph.vue +111 -0
  75. package/src/vue/popups/export-popup.vue +52 -0
  76. package/src/vue/popups/fill-color-popup.vue +39 -0
  77. package/src/vue/popups/find-popup.vue +111 -0
  78. package/src/vue/popups/font-color-popup.vue +39 -0
  79. package/src/vue/popups/font-size-popup.vue +52 -0
  80. package/src/vue/popups/import.worker.ts +66 -0
  81. package/src/vue/popups/insert-image-popup.vue +39 -0
  82. package/src/vue/sheet-context-menu.ts +229 -0
  83. package/src/vue/sheet-tabs.vue +162 -0
  84. package/src/vue/sheet-toolbar.vue +91 -0
  85. package/src/vue/sheet.vue +161 -186
  86. package/src/vue/style.scss +646 -3
  87. package/src/vue/style.ts +11 -0
  88. package/src/vue/use-find-replace.ts +200 -0
  89. package/src/vue/use-formula-ref-select.ts +34 -0
  90. package/src/vue/use-formula-suggest.ts +150 -0
  91. package/src/vue/use-sheet-grid.ts +243 -0
  92. package/src/vue/use-sheet-state.ts +161 -0
  93. package/src/vue/use-sheet-tabs-bar.ts +115 -0
  94. package/src/vue/use-tool-groups.ts +61 -0
  95. package/src/vue/use-tool-popup.ts +150 -0
  96. package/src/vue/use-toolbar-scroll.ts +95 -0
  97. package/dist/core/address.d.ts +0 -60
  98. package/dist/core/address.js +0 -142
  99. package/dist/core/address.js.map +0 -1
  100. package/dist/core/cell-store.d.ts +0 -61
  101. package/dist/core/cell-store.js +0 -135
  102. package/dist/core/cell-store.js.map +0 -1
  103. package/dist/core/command/default-registry.d.ts +0 -10
  104. package/dist/core/command/default-registry.js +0 -18
  105. package/dist/core/command/default-registry.js.map +0 -1
  106. package/dist/core/command/history.d.ts +0 -64
  107. package/dist/core/command/history.js +0 -127
  108. package/dist/core/command/history.js.map +0 -1
  109. package/dist/core/command/merge-cells.d.ts +0 -21
  110. package/dist/core/command/merge-cells.js +0 -74
  111. package/dist/core/command/merge-cells.js.map +0 -1
  112. package/dist/core/command/registry.d.ts +0 -17
  113. package/dist/core/command/registry.js +0 -26
  114. package/dist/core/command/registry.js.map +0 -1
  115. package/dist/core/command/set-cell-formula.d.ts +0 -17
  116. package/dist/core/command/set-cell-formula.js +0 -29
  117. package/dist/core/command/set-cell-formula.js.map +0 -1
  118. package/dist/core/command/set-cell-value.d.ts +0 -22
  119. package/dist/core/command/set-cell-value.js +0 -35
  120. package/dist/core/command/set-cell-value.js.map +0 -1
  121. package/dist/core/command/types.d.ts +0 -68
  122. package/dist/core/events.js +0 -29
  123. package/dist/core/events.js.map +0 -1
  124. package/dist/core/fill.d.ts +0 -34
  125. package/dist/core/fill.js +0 -320
  126. package/dist/core/fill.js.map +0 -1
  127. package/dist/core/formula/ast.d.ts +0 -58
  128. package/dist/core/formula/ast.js +0 -38
  129. package/dist/core/formula/ast.js.map +0 -1
  130. package/dist/core/formula/dependency-graph.d.ts +0 -70
  131. package/dist/core/formula/dependency-graph.js +0 -269
  132. package/dist/core/formula/dependency-graph.js.map +0 -1
  133. package/dist/core/formula/errors.d.ts +0 -25
  134. package/dist/core/formula/errors.js +0 -36
  135. package/dist/core/formula/errors.js.map +0 -1
  136. package/dist/core/formula/evaluator.d.ts +0 -41
  137. package/dist/core/formula/evaluator.js +0 -154
  138. package/dist/core/formula/evaluator.js.map +0 -1
  139. package/dist/core/formula/functions.d.ts +0 -30
  140. package/dist/core/formula/functions.js +0 -209
  141. package/dist/core/formula/functions.js.map +0 -1
  142. package/dist/core/formula/parser.d.ts +0 -7
  143. package/dist/core/formula/parser.js +0 -208
  144. package/dist/core/formula/parser.js.map +0 -1
  145. package/dist/core/formula/tokenizer.d.ts +0 -37
  146. package/dist/core/formula/tokenizer.js +0 -146
  147. package/dist/core/formula/tokenizer.js.map +0 -1
  148. package/dist/core/merge-manager.d.ts +0 -64
  149. package/dist/core/merge-manager.js +0 -111
  150. package/dist/core/merge-manager.js.map +0 -1
  151. package/dist/core/selection.d.ts +0 -31
  152. package/dist/core/selection.js +0 -62
  153. package/dist/core/selection.js.map +0 -1
  154. package/dist/core/sheet.d.ts +0 -116
  155. package/dist/core/sheet.js +0 -224
  156. package/dist/core/sheet.js.map +0 -1
  157. package/dist/core/workbook.d.ts +0 -43
  158. package/dist/core/workbook.js +0 -77
  159. package/dist/core/workbook.js.map +0 -1
  160. package/dist/grid/sheet-grid.d.ts +0 -81
  161. package/dist/grid/sheet-grid.js +0 -331
  162. package/dist/grid/sheet-grid.js.map +0 -1
  163. package/dist/grid/vtable-theme.js +0 -80
  164. package/dist/grid/vtable-theme.js.map +0 -1
  165. package/dist/tools/builtin.js.map +0 -1
  166. package/dist/tools/context.js.map +0 -1
  167. package/dist/tools/registry.js.map +0 -1
  168. package/dist/vue/index.d.ts +0 -2
  169. package/dist/vue/sheet.js.map +0 -1
  170. package/dist/vue/sheet.vue.d.ts +0 -24
  171. package/src/core/__test__/address.test.ts +0 -113
  172. package/src/core/__test__/cell-store.test.ts +0 -97
  173. package/src/core/__test__/command.test.ts +0 -377
  174. package/src/core/__test__/fill.test.ts +0 -140
  175. package/src/core/__test__/formula-adversarial.test.ts +0 -265
  176. package/src/core/__test__/formula-engine.test.ts +0 -470
  177. package/src/core/__test__/formula-functions.test.ts +0 -178
  178. package/src/core/__test__/formula-parser.test.ts +0 -160
  179. package/src/core/__test__/history.test.ts +0 -225
  180. package/src/core/__test__/merge-manager.test.ts +0 -106
  181. package/src/core/__test__/sheet.test.ts +0 -142
  182. package/src/core/address.ts +0 -166
  183. package/src/core/cell-store.ts +0 -173
  184. package/src/core/command/default-registry.ts +0 -15
  185. package/src/core/command/history.ts +0 -167
  186. package/src/core/command/index.ts +0 -7
  187. package/src/core/command/merge-cells.ts +0 -74
  188. package/src/core/command/registry.ts +0 -30
  189. package/src/core/command/set-cell-formula.ts +0 -29
  190. package/src/core/command/set-cell-value.ts +0 -38
  191. package/src/core/command/types.ts +0 -73
  192. package/src/core/events.ts +0 -37
  193. package/src/core/fill.ts +0 -314
  194. package/src/core/formula/ast.ts +0 -66
  195. package/src/core/formula/dependency-graph.ts +0 -329
  196. package/src/core/formula/errors.ts +0 -41
  197. package/src/core/formula/evaluator.ts +0 -219
  198. package/src/core/formula/functions.ts +0 -266
  199. package/src/core/formula/index.ts +0 -7
  200. package/src/core/formula/parser.ts +0 -198
  201. package/src/core/formula/tokenizer.ts +0 -160
  202. package/src/core/merge-manager.ts +0 -158
  203. package/src/core/selection.ts +0 -66
  204. package/src/core/sheet.ts +0 -282
  205. package/src/core/workbook.ts +0 -99
  206. package/src/grid/__test__/canvas-mock.ts +0 -49
  207. package/src/grid/__test__/setup.ts +0 -3
  208. package/src/grid/__test__/sheet-grid-formula.test.ts +0 -140
  209. package/src/grid/__test__/sheet-grid.test.ts +0 -275
  210. package/src/grid/sheet-grid.ts +0 -407
  211. package/src/grid/vtable-theme.ts +0 -61
package/src/vue/sheet.vue CHANGED
@@ -1,51 +1,99 @@
1
1
  <template>
2
- <div :class="cls.b">
3
- <div v-if="showToolbar" :class="cls.e('toolbar')">
4
- <template v-for="(group, groupIndex) in toolGroups" :key="group.name">
5
- <span v-if="groupIndex > 0" :class="cls.e('toolbar-divider')" />
6
- <button
7
- v-for="item in group.tools"
8
- :key="item.tool.id"
9
- type="button"
10
- :class="cls.e('tool')"
11
- :disabled="item.disabled"
12
- :title="item.tool.tooltip ?? item.tool.title"
13
- @click="handleToolClick(item.tool)"
14
- >
15
- <component :is="item.tool.icon" v-if="item.tool.icon" :class="cls.e('tool-icon')" />
16
- <span>{{ item.tool.title }}</span>
17
- </button>
18
- </template>
2
+ <div ref="rootRef" :class="cls.b">
3
+ <div :class="cls.e('toolbar-wrap')">
4
+ <u-sheet-toolbar v-if="showToolbar" :groups="toolGroups" @tool-click="handleToolClick" />
5
+
6
+ <!-- 弹层型工具面板(填充/边框/字体色/字号/查找/插入图片/导出):UDropdown(Teleport 到
7
+ #pop-container + floating-ui 定位:锚点跟随触发按钮、自动翻转/边界位移;
8
+ 触发元素滚动/窗口缩放时自动关闭)。面板交互走 SheetContext 命令入口。
9
+ 导入无弹层:点击直接系统文件选择(见 handleToolClick → pickAndImportFile) -->
10
+ <u-dropdown
11
+ ref="popupDropdownRef"
12
+ trigger="custom"
13
+ :visible="popupTool !== null"
14
+ width="auto"
15
+ @update:visible="handlePopupVisible"
16
+ >
17
+ <template #content>
18
+ <div v-if="popupTool" :class="cls.e('popup')" @click.stop>
19
+ <u-sheet-fill-color-popup v-if="popupTool.popup === 'fill-color'" :context="context" />
20
+ <u-sheet-border-popup v-else-if="popupTool.popup === 'border'" :context="context" />
21
+ <u-sheet-font-color-popup
22
+ v-else-if="popupTool.popup === 'font-color'"
23
+ :context="context"
24
+ />
25
+ <u-sheet-font-size-popup
26
+ v-else-if="popupTool.popup === 'font-size'"
27
+ :context="context"
28
+ />
29
+ <u-sheet-find-popup
30
+ v-else-if="popupTool.popup === 'find'"
31
+ :sheet="activeSheet"
32
+ :context="context"
33
+ @close="closePopup"
34
+ />
35
+ <u-sheet-insert-image-popup
36
+ v-else-if="popupTool.popup === 'insert-image'"
37
+ :context="context"
38
+ @close="closePopup"
39
+ />
40
+ <u-sheet-export-popup
41
+ v-else-if="popupTool.popup === 'export'"
42
+ :context="context"
43
+ @close="closePopup"
44
+ />
45
+ </div>
46
+ </template>
47
+ </u-dropdown>
19
48
  </div>
20
49
 
21
- <div ref="gridRef" :class="cls.e('grid')" />
22
-
23
- <div v-if="showTabs" :class="cls.e('tabs')">
24
- <button
25
- v-for="(sheet, index) in sheetList"
26
- :key="sheet.name"
27
- type="button"
28
- :class="[cls.e('tab'), bem.is('active', index === activeIndex)]"
29
- @click="handleTabClick(index)"
30
- >
31
- {{ sheet.name }}
32
- </button>
50
+ <u-formula-bar
51
+ v-if="showFormulaBar"
52
+ ref="formulaBarRef"
53
+ :sheet="activeSheet"
54
+ :context="context"
55
+ />
56
+
57
+ <div ref="gridRef" :class="cls.e('grid')">
58
+ <!-- 导入解析覆盖层(自绘,不动 desktop Loading 组件):遮罩 + 动画 + 文字
59
+ 同一层,动画在上、文字在下,不存在被遮罩掩盖的问题 -->
60
+ <div v-if="parsing" :class="cls.e('loading-mask')">
61
+ <div :class="cls.e('loading-spinner')"></div>
62
+ <div :class="cls.e('loading-text')">{{ parseText }}</div>
63
+ </div>
33
64
  </div>
65
+
66
+ <u-sheet-tabs
67
+ v-if="showTabs"
68
+ :workbook="workbook"
69
+ :sheet-list="sheetList"
70
+ :active-index="activeIndex"
71
+ />
34
72
  </div>
35
73
  </template>
36
74
 
37
75
  <script lang="ts" setup>
38
- import { contextmenu } from '@veltra/desktop'
76
+ import { UDropdown } from '@veltra/desktop'
39
77
  import { bem } from '@veltra/utils'
40
- import { computed, onBeforeUnmount, onMounted, ref, shallowRef, useTemplateRef, watch } from 'vue'
41
-
42
- import { rangeContainsAddress } from '../core/address'
43
- import type { Sheet } from '../core/sheet'
44
- import { Workbook } from '../core/workbook'
45
- import { SheetGrid, type SheetGridContextMenuInfo } from '../grid/sheet-grid'
46
- import { createSheetContext } from '../tools/context'
47
- import { defaultToolRegistry, type SheetTool } from '../tools/registry'
78
+ import { computed, ref, useTemplateRef, watch } from 'vue'
79
+
80
+ import type { SheetTool } from '../tools/registry'
48
81
  import type { SheetEmits, SheetProps, _SheetExposed } from '../types'
82
+ import UFormulaBar from './formula-bar.vue'
83
+ import { pickAndImportFile } from './import-file'
84
+ import USheetBorderPopup from './popups/border-popup.vue'
85
+ import USheetExportPopup from './popups/export-popup.vue'
86
+ import USheetFillColorPopup from './popups/fill-color-popup.vue'
87
+ import USheetFindPopup from './popups/find-popup.vue'
88
+ import USheetFontColorPopup from './popups/font-color-popup.vue'
89
+ import USheetFontSizePopup from './popups/font-size-popup.vue'
90
+ import USheetInsertImagePopup from './popups/insert-image-popup.vue'
91
+ import USheetTabs from './sheet-tabs.vue'
92
+ import USheetToolbar from './sheet-toolbar.vue'
93
+ import { useSheetGrid } from './use-sheet-grid'
94
+ import { useSheetState } from './use-sheet-state'
95
+ import { useToolGroups } from './use-tool-groups'
96
+ import { useToolPopup } from './use-tool-popup'
49
97
 
50
98
  defineOptions({ name: 'USheet' })
51
99
 
@@ -53,6 +101,7 @@ const props = withDefaults(defineProps<SheetProps>(), {
53
101
  rows: 100,
54
102
  cols: 26,
55
103
  showToolbar: true,
104
+ showFormulaBar: true,
56
105
  showTabs: true
57
106
  })
58
107
 
@@ -60,175 +109,101 @@ const emit = defineEmits<SheetEmits>()
60
109
 
61
110
  const cls = bem('sheet')
62
111
 
63
- /** 工作簿:外部传入或内部自建(单 sheet) */
64
- const internalWorkbook = new Workbook()
65
- const workbook = computed(() => props.workbook ?? internalWorkbook)
66
-
67
- /** 激活 sheet 索引(镜像 workbook.activeSheetIndex,经事件同步) */
68
- const activeIndex = ref(workbook.value.activeSheetIndex)
69
- /** sheet 列表(tabs 渲染;由 sheets-change 同步) */
70
- const sheetList = shallowRef<Sheet[]>(workbook.value.getSheets())
71
- const activeSheet = computed(() => sheetList.value[activeIndex.value] ?? workbook.value.activeSheet)
112
+ /** 根容器 ref:Ctrl/Cmd+F 判定焦点是否在本实例内(#6,避免劫持浏览器查找) */
113
+ const rootRef = useTemplateRef<HTMLElement>('rootRef')
72
114
 
73
- /** 工具上下文:动态解析活动 sheet,tab 切换后自动指向当前 sheet */
74
- const context = createSheetContext(() => activeSheet.value)
75
-
76
- const gridRef = useTemplateRef<HTMLElement>('gridRef')
77
- let grid: SheetGrid | undefined
115
+ // ─── 状态源(workbook / sheet 列表 / 活动 sheet / 工具上下文)────────
116
+ // hooks 引用的 closePopup / rebuildGrid 由下方组合提供:箭头函数在事件触发时才
117
+ // 求值(彼时已完成全部 setup),组合声明顺序不构成循环依赖。
78
118
 
79
- // ─── 工具栏状态刷新 ─────────────────────────────────────────
80
- // 工具 visible/disabled 是 (ctx) => boolean 纯函数,状态源变化时 bump 触发重算
81
-
82
- const stateTick = ref(0)
83
- const bump = (): void => {
84
- stateTick.value++
85
- }
119
+ const { workbook, activeIndex, sheetList, activeSheet, context, stateTick, syncFromWorkbook } =
120
+ useSheetState(props, emit, {
121
+ onBeforeSheetChange: () => closePopup(),
122
+ rebuildGrid: () => rebuildGrid(),
123
+ activateGrid: () => activateGrid(),
124
+ pruneCache: (sheets) => pruneCache(sheets)
125
+ })
86
126
 
87
- let disposeSheetEvents: (() => void)[] = []
88
- /** 订阅活动 sheet 的状态源(tab 切换 / 工作簿变更时重绑) */
89
- function bindSheetEvents(sheet: Sheet): void {
90
- for (const dispose of disposeSheetEvents) dispose()
91
- disposeSheetEvents = [
92
- sheet.on('selection-change', bump),
93
- sheet.on('history-change', bump),
94
- sheet.on('cell-change', bump),
95
- sheet.on('merge-change', bump)
96
- ]
97
- }
127
+ // ─── 弹层型工具编排(打开 / 关闭 / 面板事务)─────────────────────
128
+
129
+ const {
130
+ popupTool,
131
+ popupAnchor,
132
+ closePopup,
133
+ handleToolClick: openOrRunTool
134
+ } = useToolPopup(context, rootRef)
135
+ // xlsx 解析中(worker):grid 容器显示自绘覆盖层(import-file 写入 parsing /
136
+ // parseProgress;遮罩 + 动画 + 文字同层——动画在上、文字在下)
137
+ const parsing = ref(false)
138
+ const parseProgress = ref({ done: 0, total: 0 })
139
+ const parseText = computed(() =>
140
+ parseProgress.value.total > 0
141
+ ? `正在解析… ${parseProgress.value.done}/${parseProgress.value.total}`
142
+ : '正在读取文件结构…'
143
+ )
98
144
 
99
- let disposeWorkbookEvents: (() => void)[] = []
100
- function bindWorkbookEvents(wb: Workbook): void {
101
- for (const dispose of disposeWorkbookEvents) dispose()
102
- disposeWorkbookEvents = [
103
- wb.on('sheets-change', ({ sheets }) => {
104
- sheetList.value = sheets
105
- bump()
106
- }),
107
- wb.on('active-sheet-change', ({ sheet, index }) => {
108
- activeIndex.value = index
109
- bindSheetEvents(sheet)
110
- rebuildGrid()
111
- bump()
112
- emit('active-sheet-change', { sheet, index })
145
+ /** 导入无弹层:直接系统文件选择;其余工具走 useToolPopup */
146
+ function handleToolClick(tool: SheetTool, event?: MouseEvent): void {
147
+ if (tool.id === 'import') {
148
+ if (tool.disabled?.(context)) return
149
+ pickAndImportFile({
150
+ workbook: workbook.value,
151
+ activeSheet: activeSheet.value,
152
+ parsing,
153
+ parseProgress,
154
+ onCsvImported: () => rebuildGrid(),
155
+ onWorkbookReplaced: () => syncFromWorkbook()
113
156
  })
114
- ]
157
+ return
158
+ }
159
+ openOrRunTool(tool, event)
115
160
  }
116
161
 
117
- const offRegistryChange = defaultToolRegistry.onChange(bump)
118
-
119
- // ─── 工具栏 ────────────────────────────────────────────────
120
-
121
- const toolGroups = computed(() => {
122
- void stateTick.value // 依赖状态源:选区 / 历史 / 单元格 / 合并 / 注册表
123
- return defaultToolRegistry
124
- .getGroups()
125
- .map((group) => ({
126
- name: group.name,
127
- tools: group.tools
128
- .map((tool) => ({
129
- tool,
130
- visible: tool.visible?.(context) ?? true,
131
- disabled: tool.disabled?.(context) ?? false
132
- }))
133
- .filter((item) => item.visible)
134
- }))
135
- .filter((group) => group.tools.length > 0)
162
+ // ─── UDropdown 面板:锚点跟随触发按钮(floating-ui 定位)──────────
163
+
164
+ const popupDropdownRef = useTemplateRef<InstanceType<typeof UDropdown>>('popupDropdownRef')
165
+
166
+ // popupTool 变化 → 打开(传触发按钮作锚点)/ 关闭。
167
+ // 必须同步调用 open():usePop 在 content 挂载时执行定位与滚动监听,
168
+ // 若等 nextTick 后再设 customTriggerRef,usePop 首次 update 时锚点缺失 → 面板不定位。
169
+ watch(popupTool, () => {
170
+ const dropdown = popupDropdownRef.value
171
+ if (!dropdown) return
172
+ if (popupTool.value) {
173
+ // 触发元素移动(工具栏滚动 / 窗口缩放)由 usePop 监听 → UDropdown 自动关闭
174
+ dropdown.open({ trigger: popupAnchor.value ?? undefined })
175
+ } else {
176
+ dropdown.close()
177
+ }
136
178
  })
137
179
 
138
- function handleToolClick(tool: SheetTool): void {
139
- if (tool.disabled?.(context)) return
140
- tool.onClick(context)
180
+ /** UDropdown 内部关闭(滚动 / resize / transition 结束)→ 同步状态并提交面板事务 */
181
+ function handlePopupVisible(visible: boolean): void {
182
+ if (!visible) closePopup()
141
183
  }
142
184
 
143
- // ─── sheet tabs ─────────────────────────────────────────────
185
+ // ─── 工具栏分组视图模型 ────────────────────────────────────────
144
186
 
145
- function handleTabClick(index: number): void {
146
- if (index === activeIndex.value) return
147
- const sheet = sheetList.value[index]
148
- if (sheet) workbook.value.activateSheet(sheet.name)
149
- }
150
-
151
- // ─── 右键菜单(合并 / 取消合并,语义对齐内置工具)────────────
152
-
153
- function handleContextMenu(info: SheetGridContextMenuInfo): void {
154
- // body 格:若点击落在当前选区外则先选中该格;行号/列头保留当前选区
155
- if (info.addr) {
156
- const inSelection = context
157
- .getSelection()
158
- .ranges.some((range) => rangeContainsAddress(range, info.addr!))
159
- if (!inSelection) context.selectCell(info.addr)
160
- }
187
+ const toolGroups = useToolGroups(context, stateTick)
161
188
 
162
- const mergeTool = defaultToolRegistry.get('merge')
163
- const unmergeTool = defaultToolRegistry.get('unmerge')
164
-
165
- contextmenu.pop({
166
- mousePosition: { x: info.x, y: info.y },
167
- width: 180,
168
- menus: [
169
- {
170
- label: '合并单元格',
171
- disabled: mergeTool?.disabled?.(context) ?? true,
172
- callback: () => mergeTool?.onClick(context)
173
- },
174
- {
175
- label: '取消合并单元格',
176
- disabled: unmergeTool?.disabled?.(context) ?? true,
177
- callback: () => unmergeTool?.onClick(context)
178
- }
179
- ]
180
- })
181
- }
189
+ // ─── grid 生命周期(重建 / 右键菜单 / 公式栏编辑镜像)────────────────
182
190
 
183
- // ─── grid 生命周期 ──────────────────────────────────────────
184
-
185
- function rebuildGrid(): void {
186
- const container = gridRef.value
187
- if (!container) return
188
- grid?.release()
189
- grid = new SheetGrid({
190
- container,
191
- sheet: activeSheet.value,
192
- rows: props.rows,
193
- cols: props.cols,
194
- onContextMenu: handleContextMenu
195
- })
196
- }
197
-
198
- onMounted(() => {
199
- bindWorkbookEvents(workbook.value)
200
- bindSheetEvents(activeSheet.value)
201
- rebuildGrid()
202
- })
203
-
204
- watch(() => [props.rows, props.cols], rebuildGrid)
205
-
206
- watch(
207
- () => props.workbook,
208
- (wb, prev) => {
209
- if (wb === prev) return
210
- activeIndex.value = workbook.value.activeSheetIndex
211
- sheetList.value = workbook.value.getSheets()
212
- bindWorkbookEvents(workbook.value)
213
- bindSheetEvents(activeSheet.value)
214
- rebuildGrid()
215
- bump()
216
- }
217
- )
191
+ const gridRef = useTemplateRef<HTMLElement>('gridRef')
192
+ const formulaBarRef = useTemplateRef<InstanceType<typeof UFormulaBar>>('formulaBarRef')
218
193
 
219
- onBeforeUnmount(() => {
220
- offRegistryChange()
221
- for (const dispose of disposeWorkbookEvents) dispose()
222
- for (const dispose of disposeSheetEvents) dispose()
223
- grid?.release()
224
- grid = undefined
194
+ const { rebuildGrid, activateGrid, pruneCache, getGrid } = useSheetGrid({
195
+ props,
196
+ gridRef,
197
+ getActiveSheet: () => activeSheet.value,
198
+ context,
199
+ formulaBarRef
225
200
  })
226
201
 
227
202
  const exposed: _SheetExposed = {
228
203
  workbook,
229
204
  getActiveSheet: () => activeSheet.value,
230
205
  getContext: () => context,
231
- getGrid: () => grid
206
+ getGrid
232
207
  }
233
208
 
234
209
  defineExpose(exposed)