@veltra/sheet 2.5.6 → 2.5.8

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 (59) hide show
  1. package/dist/components/sheet/formula-bar.js +115 -15
  2. package/dist/components/sheet/import-file.js +1 -1
  3. package/dist/components/sheet/popup-helpers.js +1 -1
  4. package/dist/components/sheet/popups/border-popup.js +2 -2
  5. package/dist/components/sheet/popups/functions-popup.js +117 -0
  6. package/dist/components/sheet/popups/import.worker.d.ts +3 -4
  7. package/dist/components/sheet/popups/import.worker.js +1 -1
  8. package/dist/components/sheet/sheet-context-menu.js +1 -1
  9. package/dist/components/sheet/sheet.js +33 -23
  10. package/dist/components/sheet/style2.css +100 -0
  11. package/dist/components/sheet/use-find-replace.js +2 -2
  12. package/dist/components/sheet/use-formula-suggest.js +18 -2
  13. package/dist/components/sheet/use-sheet-grid.js +1 -1
  14. package/dist/components/sheet/use-sheet-state.js +1 -1
  15. package/dist/components/sheet/use-tool-popup.js +4 -2
  16. package/dist/tools/builtin.js +12 -2
  17. package/dist/tools/context.d.ts +15 -16
  18. package/dist/tools/download.js +1 -1
  19. package/dist/tools/export.worker.d.ts +4 -5
  20. package/dist/tools/export.worker.js +1 -1
  21. package/dist/tools/registry.d.ts +8 -9
  22. package/dist/tools/registry.js +1 -1
  23. package/dist/types/sheet.d.ts +7 -8
  24. package/package.json +5 -5
  25. package/src/components/sheet/__test__/formula-bar.test.ts +314 -2
  26. package/src/components/sheet/__test__/functions-popup.test.ts +182 -0
  27. package/src/components/sheet/__test__/import-file.test.ts +3 -3
  28. package/src/components/sheet/__test__/insert-image-popup.test.ts +1 -1
  29. package/src/components/sheet/__test__/insert-image.test.ts +1 -1
  30. package/src/components/sheet/__test__/sheet-component.test.ts +2 -1
  31. package/src/components/sheet/__test__/sheet-context-menu.test.ts +2 -2
  32. package/src/components/sheet/__test__/use-formula-suggest.test.ts +5 -4
  33. package/src/components/sheet/formula-bar.vue +134 -9
  34. package/src/components/sheet/import-file.ts +3 -3
  35. package/src/components/sheet/insert-image.ts +1 -1
  36. package/src/components/sheet/popup-helpers.ts +3 -3
  37. package/src/components/sheet/popups/border-popup.vue +3 -3
  38. package/src/components/sheet/popups/find-popup.vue +1 -1
  39. package/src/components/sheet/popups/functions-popup.vue +141 -0
  40. package/src/components/sheet/popups/import.worker.ts +2 -2
  41. package/src/components/sheet/sheet-context-menu.ts +2 -2
  42. package/src/components/sheet/sheet-tabs.vue +2 -2
  43. package/src/components/sheet/sheet.vue +15 -2
  44. package/src/components/sheet/style.scss +117 -0
  45. package/src/components/sheet/use-find-replace.ts +3 -3
  46. package/src/components/sheet/use-formula-suggest.ts +20 -1
  47. package/src/components/sheet/use-sheet-grid.ts +12 -4
  48. package/src/components/sheet/use-sheet-state.ts +2 -2
  49. package/src/components/sheet/use-tool-popup.ts +4 -2
  50. package/src/tools/__test__/apply-style.test.ts +2 -2
  51. package/src/tools/__test__/builtin.test.ts +12 -4
  52. package/src/tools/__test__/context.test.ts +2 -2
  53. package/src/tools/apply-style.ts +3 -3
  54. package/src/tools/builtin.ts +15 -3
  55. package/src/tools/context.ts +11 -11
  56. package/src/tools/download.ts +2 -2
  57. package/src/tools/export.worker.ts +3 -3
  58. package/src/tools/registry.ts +2 -1
  59. package/src/types/sheet.ts +2 -2
@@ -0,0 +1,141 @@
1
+ <template>
2
+ <div ref="panelRef" :class="cls.e('functions-panel')" @keydown="handleKeydown">
3
+ <nav :class="cls.e('functions-nav')">
4
+ <button
5
+ v-for="cat in FUNCTION_POPUP_CATEGORIES"
6
+ :key="cat"
7
+ type="button"
8
+ :class="[cls.e('functions-nav-item'), bem.is('active', cat === activeCategory)]"
9
+ @click="selectCategory(cat)"
10
+ >
11
+ {{ cat }}
12
+ </button>
13
+ </nav>
14
+ <div :class="cls.e('functions-main')">
15
+ <u-input
16
+ v-model="keyword"
17
+ :placeholder="'搜索函数名或描述'"
18
+ size="small"
19
+ :class="cls.e('functions-search')"
20
+ />
21
+ <!-- 滚动用 desktop UScroll(自定义滚动条),同字号面板 -->
22
+ <u-scroll :height="320" :class="cls.e('functions-scroll')">
23
+ <div v-if="visible.length > 0" :class="cls.e('functions-list')">
24
+ <div
25
+ v-for="(fn, index) in visible"
26
+ :key="fn.name"
27
+ :class="[cls.e('functions-item'), bem.is('active', index === activeIndex)]"
28
+ @click="emit('select', fn.name)"
29
+ @mouseenter="activeIndex = index"
30
+ >
31
+ <span :class="cls.e('functions-signature')">{{
32
+ formatFunctionSignature(fn.name, fn.params)
33
+ }}</span>
34
+ <span v-if="fn.description" :class="cls.e('functions-description')">{{
35
+ fn.description
36
+ }}</span>
37
+ </div>
38
+ </div>
39
+ <div v-else :class="cls.e('functions-empty')">无匹配函数</div>
40
+ </u-scroll>
41
+ </div>
42
+ </div>
43
+ </template>
44
+
45
+ <script lang="ts" setup>
46
+ import { UInput, UScroll } from '@veltra/desktop'
47
+ import {
48
+ listFormulaFunctions,
49
+ type FormulaFunctionMeta
50
+ } from '@veltra/sheet-core/core/formula/functions.js'
51
+ import { bem } from '@veltra/utils'
52
+ import { computed, onMounted, ref, useTemplateRef, watch } from 'vue'
53
+
54
+ import {
55
+ COMMON_FORMULA_NAMES,
56
+ formatFunctionSignature,
57
+ FUNCTION_POPUP_CATEGORIES,
58
+ moveSuggestIndex,
59
+ type FunctionPopupCategory
60
+ } from '../use-formula-suggest'
61
+
62
+ defineOptions({ name: 'USheetFunctionsPopup' })
63
+
64
+ /**
65
+ * 函数弹框(分类导航 + 搜索 + 可选择):fx 按钮与工具栏「函数」共用。
66
+ * 列出全部已注册函数(内置 + 宿主经 registerFormulaFunction 注册的自定义函数),
67
+ * 选中仅 emit select(name),由公式栏接管(进入编辑态输出 =NAME()),不写模型。
68
+ * 弹框随 v-if 挂载 / 销毁,每次打开都是全新状态(关键词清空、回到「常用」)。
69
+ */
70
+
71
+ type FunctionItem = { name: string } & FormulaFunctionMeta
72
+
73
+ const emit = defineEmits<{ select: [name: string] }>()
74
+
75
+ const cls = bem('sheet')
76
+
77
+ const panelRef = useTemplateRef<HTMLElement>('panelRef')
78
+
79
+ const keyword = ref('')
80
+ const activeCategory = ref<FunctionPopupCategory>('常用')
81
+ /** 键盘 / 悬停高亮下标(点击即确认不高亮) */
82
+ const activeIndex = ref(0)
83
+
84
+ /** 打开时快照一次注册表(弹框存活期间注册表变化不追踪,重开即刷新) */
85
+ const all: FunctionItem[] = listFormulaFunctions()
86
+
87
+ /** 关键词大小写不敏感子串匹配:名称或中文描述;跨分类(未分类函数也可搜到) */
88
+ function searchResult(key: string): FunctionItem[] {
89
+ return all.filter(
90
+ (fn) => fn.name.toLowerCase().includes(key) || fn.description.toLowerCase().includes(key)
91
+ )
92
+ }
93
+
94
+ /** 当前展示列表:搜索优先;否则按分类导航过滤 */
95
+ const visible = computed<FunctionItem[]>(() => {
96
+ const key = keyword.value.trim().toLowerCase()
97
+ if (key) return searchResult(key)
98
+ if (activeCategory.value === '全部') return all
99
+ if (activeCategory.value === '常用') {
100
+ // 固定清单(顺序即 COMMON_FORMULA_NAMES 顺序);注册表中缺失的名称跳过
101
+ const byName = new Map(all.map((fn) => [fn.name, fn]))
102
+ return COMMON_FORMULA_NAMES.map((name) => byName.get(name)).filter(
103
+ (fn): fn is FunctionItem => fn != null
104
+ )
105
+ }
106
+ // 分类项只列声明了该 category 的函数(未分类函数不进任何分类)
107
+ return all.filter((fn) => fn.category === activeCategory.value)
108
+ })
109
+
110
+ watch(visible, () => {
111
+ activeIndex.value = 0
112
+ })
113
+
114
+ function selectCategory(cat: FunctionPopupCategory): void {
115
+ activeCategory.value = cat
116
+ }
117
+
118
+ /** 键盘路径:↑↓ 移动高亮(循环),Enter 确认选中(Esc 交由外层弹框关闭) */
119
+ function handleKeydown(event: KeyboardEvent): void {
120
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
121
+ event.preventDefault()
122
+ activeIndex.value = moveSuggestIndex(
123
+ activeIndex.value,
124
+ visible.value.length,
125
+ event.key === 'ArrowDown' ? 1 : -1
126
+ )
127
+ return
128
+ }
129
+ if (event.key === 'Enter') {
130
+ const fn = visible.value[activeIndex.value]
131
+ if (!fn) return
132
+ event.preventDefault()
133
+ emit('select', fn.name)
134
+ }
135
+ }
136
+
137
+ // 打开即聚焦搜索框,键盘可直接检索 / 导航(查 DOM 而非组件 expose,stub 环境同样可用)
138
+ onMounted(() => {
139
+ panelRef.value?.querySelector('input')?.focus()
140
+ })
141
+ </script>
@@ -15,7 +15,7 @@
15
15
  * 注意:必须用**运行时动态 import**——worker 顶层静态 import 在 vite dev 的
16
16
  * worker 上下文中会因模块图加载顺序导致 `Workbook is not defined`(实测)。
17
17
  */
18
- import type { SheetSnapshot } from '@veltra/sheet-core/core/sheet'
18
+ import type { SheetSnapshot } from '@veltra/sheet-core/core/sheet.js'
19
19
 
20
20
  export type ImportWorkerResponse =
21
21
  | { type: 'progress'; done: number; total: number }
@@ -31,7 +31,7 @@ self.onmessage = (e: MessageEvent<{ buffer: ArrayBuffer }>): void => {
31
31
  const { buffer } = e.data
32
32
  void (async () => {
33
33
  try {
34
- const { importXlsx } = await import('@veltra/sheet-core/core/io/import')
34
+ const { importXlsx } = await import('@veltra/sheet-core/core/io/import.js')
35
35
  // 分片构建:按 10% 粒度回报进度(避免 196 条/秒的消息风暴与文字跳变过快;
36
36
  // 模型构建段约 1s,10 次更新肉眼可见数字推进)
37
37
  let lastPercent = -1
@@ -1,6 +1,6 @@
1
1
  import type { ContextmenuItem } from '@veltra/desktop'
2
- import { createRange, type CellAddress, type CellRange } from '@veltra/sheet-core/core/address'
3
- import type { NumFmt } from '@veltra/sheet-core/core/style/types'
2
+ import { createRange, type CellAddress, type CellRange } from '@veltra/sheet-core/core/address.js'
3
+ import type { NumFmt } from '@veltra/sheet-core/core/style/types.js'
4
4
  import { defineComponent, h } from 'vue'
5
5
 
6
6
  import type { SheetContext } from '../../tools/context'
@@ -54,8 +54,8 @@
54
54
  <script lang="ts" setup>
55
55
  import { contextmenu, message, messageConfirm, UIcon } from '@veltra/desktop'
56
56
  import { ArrowLeft, ArrowRight } from '@veltra/icons/normal'
57
- import type { Sheet } from '@veltra/sheet-core/core/sheet'
58
- import type { Workbook } from '@veltra/sheet-core/core/workbook'
57
+ import type { Sheet } from '@veltra/sheet-core/core/sheet.js'
58
+ import type { Workbook } from '@veltra/sheet-core/core/workbook.js'
59
59
  import { bem } from '@veltra/utils'
60
60
  import { nextTick, ref, toRef, useTemplateRef } from 'vue'
61
61
 
@@ -3,7 +3,7 @@
3
3
  <div :class="cls.e('toolbar-wrap')">
4
4
  <u-sheet-toolbar v-if="showToolbar" :groups="toolGroups" @tool-click="handleToolClick" />
5
5
 
6
- <!-- 弹层型工具面板(填充/边框/字体色/字号/查找/插入图片/导出):UDropdown(Teleport 到
6
+ <!-- 弹层型工具面板(填充/边框/字体色/字号/查找/函数/插入图片/导出):UDropdown(Teleport 到
7
7
  #pop-container + floating-ui 定位:锚点跟随触发按钮、自动翻转/边界位移;
8
8
  触发元素滚动/窗口缩放时自动关闭)。面板交互走 SheetContext 命令入口。
9
9
  导入无弹层:点击直接系统文件选择(见 handleToolClick → pickAndImportFile) -->
@@ -32,6 +32,10 @@
32
32
  :context="context"
33
33
  @close="closePopup"
34
34
  />
35
+ <u-sheet-functions-popup
36
+ v-else-if="popupTool.popup === 'functions'"
37
+ @select="handleFunctionSelect"
38
+ />
35
39
  <u-sheet-insert-image-popup
36
40
  v-else-if="popupTool.popup === 'insert-image'"
37
41
  :context="context"
@@ -87,6 +91,7 @@ import USheetFillColorPopup from './popups/fill-color-popup.vue'
87
91
  import USheetFindPopup from './popups/find-popup.vue'
88
92
  import USheetFontColorPopup from './popups/font-color-popup.vue'
89
93
  import USheetFontSizePopup from './popups/font-size-popup.vue'
94
+ import USheetFunctionsPopup from './popups/functions-popup.vue'
90
95
  import USheetInsertImagePopup from './popups/insert-image-popup.vue'
91
96
  import USheetTabs from './sheet-tabs.vue'
92
97
  import USheetToolbar from './sheet-toolbar.vue'
@@ -162,6 +167,12 @@ function handleToolClick(tool: SheetTool, event?: MouseEvent): void {
162
167
  openOrRunTool(tool, event)
163
168
  }
164
169
 
170
+ /** 工具栏「函数」弹框选中 → 关弹层并走公式栏统一插入路径(与 fx 按钮一致) */
171
+ function handleFunctionSelect(name: string): void {
172
+ closePopup()
173
+ formulaBarRef.value?.insertFunction(name)
174
+ }
175
+
165
176
  // ─── UDropdown 面板:锚点跟随触发按钮(floating-ui 定位)──────────
166
177
 
167
178
  const popupDropdownRef = useTemplateRef<InstanceType<typeof UDropdown>>('popupDropdownRef')
@@ -199,7 +210,9 @@ const { rebuildGrid, activateGrid, pruneCache, getGrid } = useSheetGrid({
199
210
  gridRef,
200
211
  getActiveSheet: () => activeSheet.value,
201
212
  context,
202
- formulaBarRef
213
+ formulaBarRef,
214
+ // 工具栏「函数」弹框打开期间与 fx 入口一致:画布点击不清除目标格高亮
215
+ isFunctionPopupOpen: () => popupTool.value?.popup === 'functions'
203
216
  })
204
217
 
205
218
  const exposed: _SheetExposed = {
@@ -246,10 +246,25 @@
246
246
  flex-shrink: 0;
247
247
  align-items: center;
248
248
  height: fn.use-var(form-component-height, small);
249
+ padding: 0 4px;
250
+ border: none;
251
+ border-radius: fn.use-var(radius, small);
252
+ background: transparent;
249
253
  color: fn.use-var(text-color, second);
250
254
  font-size: fn.use-var(font-size-main, small);
251
255
  font-style: italic;
252
256
  user-select: none;
257
+ cursor: pointer;
258
+
259
+ &:hover:not(:disabled) {
260
+ background-color: fn.use-var(bg-color, hover);
261
+ color: fn.use-var(text-color, main);
262
+ }
263
+
264
+ &:disabled {
265
+ color: fn.use-var(text-color, disabled);
266
+ cursor: not-allowed;
267
+ }
253
268
  }
254
269
 
255
270
  @include m.e(fx-input) {
@@ -531,6 +546,108 @@
531
546
  }
532
547
  }
533
548
 
549
+ /* ─── 函数弹框(分类导航 + 搜索 + 可选择;fx 按钮与工具栏共用)── */
550
+
551
+ @include m.e(functions-popover) {
552
+ display: flex;
553
+ flex-direction: column;
554
+ }
555
+
556
+ @include m.e(functions-panel) {
557
+ display: flex;
558
+ gap: fn.use-var(gap, small);
559
+ }
560
+
561
+ @include m.e(functions-nav) {
562
+ display: flex;
563
+ flex-direction: column;
564
+ flex-shrink: 0;
565
+ gap: 2px;
566
+ padding: 4px;
567
+ border-right: 1px solid fn.use-var(border, muted-color);
568
+ min-width: 96px;
569
+ }
570
+
571
+ @include m.e(functions-nav-item) {
572
+ padding: 4px 8px;
573
+ border: none;
574
+ border-radius: fn.use-var(radius, small);
575
+ background: transparent;
576
+ color: fn.use-var(text-color, second);
577
+ font-size: fn.use-var(font-size-main, small);
578
+ text-align: left;
579
+ white-space: nowrap;
580
+ cursor: pointer;
581
+
582
+ &:hover {
583
+ background-color: fn.use-var(bg-color, hover);
584
+ }
585
+
586
+ @include m.is(active) {
587
+ background-color: fn.use-var(bg-color, hover);
588
+ color: fn.use-var(color, primary);
589
+ font-weight: 600;
590
+ }
591
+ }
592
+
593
+ @include m.e(functions-main) {
594
+ display: flex;
595
+ flex-direction: column;
596
+ gap: 6px;
597
+ flex: 1;
598
+ min-width: 0;
599
+ }
600
+
601
+ @include m.e(functions-search) {
602
+ width: 320px;
603
+ }
604
+
605
+ @include m.e(functions-scroll) {
606
+ width: 320px;
607
+ }
608
+
609
+ @include m.e(functions-list) {
610
+ display: flex;
611
+ flex-direction: column;
612
+ gap: 2px;
613
+ }
614
+
615
+ @include m.e(functions-item) {
616
+ display: flex;
617
+ flex-direction: column;
618
+ gap: 2px;
619
+ padding: 6px 8px;
620
+ border-radius: fn.use-var(radius, small);
621
+ cursor: pointer;
622
+
623
+ &:hover {
624
+ background-color: fn.use-var(bg-color, hover);
625
+ }
626
+
627
+ @include m.is(active) {
628
+ background-color: fn.use-var(bg-color, hover);
629
+ }
630
+ }
631
+
632
+ @include m.e(functions-signature) {
633
+ color: fn.use-var(text-color, main);
634
+ font-size: fn.use-var(font-size-main, small);
635
+ font-weight: 600;
636
+ font-family: 'JetBrains Mono', Consolas, 'Courier New', monospace;
637
+ }
638
+
639
+ @include m.e(functions-description) {
640
+ color: fn.use-var(text-color, second);
641
+ font-size: fn.use-var(font-size-assist, small);
642
+ }
643
+
644
+ @include m.e(functions-empty) {
645
+ padding: 16px 0;
646
+ color: fn.use-var(text-color, second);
647
+ font-size: fn.use-var(font-size-main, small);
648
+ text-align: center;
649
+ }
650
+
534
651
  @include m.e(insert-image-hint) {
535
652
  padding: 8px 10px;
536
653
  border: 1px dashed fn.use-var(border, muted-color);
@@ -3,15 +3,15 @@ import {
3
3
  inferCellType,
4
4
  normalizeInputValue,
5
5
  type CellData
6
- } from '@veltra/sheet-core/core/cell-store'
6
+ } from '@veltra/sheet-core/core/cell-store.js'
7
7
  import {
8
8
  findAll,
9
9
  findNextFrom,
10
10
  findPrevFrom,
11
11
  type FindMatch,
12
12
  type FindOptions
13
- } from '@veltra/sheet-core/core/find'
14
- import type { Sheet } from '@veltra/sheet-core/core/sheet'
13
+ } from '@veltra/sheet-core/core/find.js'
14
+ import type { Sheet } from '@veltra/sheet-core/core/sheet.js'
15
15
  import { computed, ref, shallowRef, watch } from 'vue'
16
16
 
17
17
  import type { SheetContext } from '../../tools/context'
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  listFormulaFunctions,
3
3
  type FormulaFunctionMeta
4
- } from '@veltra/sheet-core/core/formula/functions'
4
+ } from '@veltra/sheet-core/core/formula/functions.js'
5
5
 
6
6
  /** 补全候选上限 */
7
7
  export const FORMULA_SUGGEST_LIMIT = 10
@@ -23,6 +23,25 @@ export const COMMON_FORMULA_NAMES = [
23
23
  'OR'
24
24
  ] as const
25
25
 
26
+ /**
27
+ * 函数弹框分类导航固定集合(顺序即展示顺序)。
28
+ * 「常用」「全部」是导航概念(非注册表分类):常用 = COMMON_FORMULA_NAMES 固定清单,
29
+ * 全部 = 全部已注册函数;其余与 sheet-core `FormulaFunctionCategory` 对应。
30
+ */
31
+ export const FUNCTION_POPUP_CATEGORIES = [
32
+ '常用',
33
+ '全部',
34
+ '财务',
35
+ '日期与时间',
36
+ '数学',
37
+ '统计',
38
+ '查找与引用',
39
+ '文本',
40
+ '逻辑'
41
+ ] as const
42
+
43
+ export type FunctionPopupCategory = (typeof FUNCTION_POPUP_CATEGORIES)[number]
44
+
26
45
  /** 函数名 token 前合法的「触发」字符(= / 运算符 / ( / ,) */
27
46
  const SUGGEST_TRIGGER_CHARS = new Set(['=', '(', ',', '+', '-', '*', '/', '^', '&', '<', '>'])
28
47
 
@@ -1,6 +1,6 @@
1
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'
2
+ import type { CellAddress, CellRange } from '@veltra/sheet-core/core/address.js'
3
+ import type { Sheet } from '@veltra/sheet-core/core/sheet.js'
4
4
  import { SheetGrid, type SheetGridContextMenuInfo } from '@veltra/sheet-core/grid'
5
5
  import { onBeforeUnmount, onMounted, watch } from 'vue'
6
6
 
@@ -23,6 +23,8 @@ export interface FormulaBarMirror {
23
23
  exitMirror: (addr: CellAddress) => void
24
24
  /** fx 编辑中且处于可插入引用位置 */
25
25
  isRefSelecting: () => boolean
26
+ /** 函数弹框打开中(fx 入口)——画布点击不清除目标格高亮 */
27
+ isFunctionPopupOpen: () => boolean
26
28
  /** 网格 pointerdown → 挂起 blur 提交 */
27
29
  beginBlurSuppress: () => void
28
30
  /** 画布选区 → 插入引用文本 */
@@ -35,6 +37,8 @@ interface UseSheetGridOptions {
35
37
  getActiveSheet: () => Sheet
36
38
  context: SheetContext
37
39
  formulaBarRef: ElRef<FormulaBarMirror>
40
+ /** 函数弹框打开中(宿主侧入口,如工具栏「函数」按钮) */
41
+ isFunctionPopupOpen?: () => boolean
38
42
  }
39
43
 
40
44
  /** 缓存实例(LRU 淘汰):每个 sheet 一个独立容器 div,非激活容器 visibility:hidden 堆叠 */
@@ -125,8 +129,12 @@ export function useSheetGrid(options: UseSheetGridOptions) {
125
129
  onContextMenu: handleContextMenu,
126
130
  onEditStart: (addr) => formulaBarRef.value?.mirrorGridEdit(addr),
127
131
  onEditEnd: (addr) => formulaBarRef.value?.exitMirror(addr),
128
- // 引用选择:不回写模型选区,序列化为 A1 / A1:B2 交给公式栏
129
- interceptSelection: () => formulaBarRef.value?.isRefSelecting() ?? false,
132
+ // 引用选择:不回写模型选区,序列化为 A1 / A1:B2 交给公式栏;
133
+ // 函数弹框打开期间(fx / 工具栏两入口)同样拦截,画布点击不离开目标格
134
+ interceptSelection: () =>
135
+ (formulaBarRef.value?.isRefSelecting() ?? false) ||
136
+ (formulaBarRef.value?.isFunctionPopupOpen() ?? false) ||
137
+ (options.isFunctionPopupOpen?.() ?? false),
130
138
  onSelectionIntercept: (range) => formulaBarRef.value?.handleRefSelect(range)
131
139
  })
132
140
  // 结构变更订阅(vue 层只绑定激活 sheet;隐藏实例必须自持标记,切回时判定过期)
@@ -1,5 +1,5 @@
1
- import type { Sheet } from '@veltra/sheet-core/core/sheet'
2
- import { Workbook } from '@veltra/sheet-core/core/workbook'
1
+ import type { Sheet } from '@veltra/sheet-core/core/sheet.js'
2
+ import { Workbook } from '@veltra/sheet-core/core/workbook.js'
3
3
  import { computed, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'
4
4
 
5
5
  import { createSheetContext } from '../../tools/context'
@@ -9,7 +9,8 @@ type ElRef = { readonly value: HTMLElement | null | undefined }
9
9
  /**
10
10
  * 面板打开期间的写入是否合并为一个 undo 单元(关闭时提交):
11
11
  * 填充颜色 / 边框 / 字体颜色 / 字号参与事务;查找(每次替换独立 undo)、
12
- * 导出(下载侧效应,无模型写入)不参与。导入无弹层,不经此路径。
12
+ * 函数(选中仅进入公式栏编辑态,模型写入待 fx 提交)、导出(下载侧效应,
13
+ * 无模型写入)不参与。导入无弹层,不经此路径。
13
14
  */
14
15
  function joinsTransaction(tool: SheetTool): boolean {
15
16
  return (
@@ -21,12 +22,13 @@ function joinsTransaction(tool: SheetTool): boolean {
21
22
  }
22
23
 
23
24
  /**
24
- * 弹层型工具编排(填充颜色 / 边框 / 查找 / 插入图片 / 导出):
25
+ * 弹层型工具编排(填充颜色 / 边框 / 查找 / 函数 / 插入图片 / 导出):
25
26
  * - popupTool:当前打开的弹层工具(null = 未打开)
26
27
  * - popupAnchor:触发按钮元素(面板定位参照,见 sheet.vue 的 left 计算)
27
28
  * - 打开 / 关闭时的事务包裹(面板期间写入合并为一个 undo 单元,关闭时提交)
28
29
  * - 点击面板外关闭(面板内 @click.stop 不冒泡到 window)
29
30
  * - Ctrl/Cmd+F 开合查找条(与工具按钮同一 toggle 逻辑)
31
+ * 函数面板选中只进入公式栏编辑态(无直接模型写入),与查找 / 导出一样不参与事务。
30
32
  * 导入在 sheet.vue 特殊处理(直接系统文件选择),不经弹层。
31
33
  */
32
34
  export function useToolPopup(context: SheetContext, rootEl: ElRef) {
@@ -1,5 +1,5 @@
1
- import { createRange } from '@veltra/sheet-core/core/address'
2
- import { Sheet } from '@veltra/sheet-core/core/sheet'
1
+ import { createRange } from '@veltra/sheet-core/core/address.js'
2
+ import { Sheet } from '@veltra/sheet-core/core/sheet.js'
3
3
  import { describe, expect, it } from 'vitest'
4
4
 
5
5
  import { classifySelectionStyleTarget, type SelectionStyleTarget } from '../apply-style'
@@ -1,8 +1,8 @@
1
- import { createRange } from '@veltra/sheet-core/core/address'
1
+ import { createRange } from '@veltra/sheet-core/core/address.js'
2
2
 
3
3
  import '../builtin'
4
- import { Sheet } from '@veltra/sheet-core/core/sheet'
5
- import { Workbook } from '@veltra/sheet-core/core/workbook'
4
+ import { Sheet } from '@veltra/sheet-core/core/sheet.js'
5
+ import { Workbook } from '@veltra/sheet-core/core/workbook.js'
6
6
  import { describe, expect, it, vi } from 'vitest'
7
7
 
8
8
  import { createSheetContext } from '../context'
@@ -52,7 +52,7 @@ describe('内置工具(dogfood 扩展机制)', () => {
52
52
  'valign-bottom',
53
53
  'wrap-text'
54
54
  ])
55
- expect(groups[3]!.tools.map((tool) => tool.id)).toEqual(['find'])
55
+ expect(groups[3]!.tools.map((tool) => tool.id)).toEqual(['find', 'functions'])
56
56
  expect(groups[4]!.tools.map((tool) => tool.id)).toEqual(['insert-image'])
57
57
  expect(groups[5]!.tools.map((tool) => tool.id)).toEqual(['import', 'export'])
58
58
 
@@ -214,6 +214,14 @@ describe('内置工具(dogfood 扩展机制)', () => {
214
214
  expect(find.disabled).toBeUndefined()
215
215
  })
216
216
 
217
+ it('函数工具:弹层型声明(popup: functions),组 edit;纯查看不依赖选区', () => {
218
+ const tool = mustGet('functions')
219
+ expect(tool.popup).toBe('functions')
220
+ expect(tool.group).toBe('edit')
221
+ expect(tool.icon).toBeTruthy()
222
+ expect(tool.disabled).toBeUndefined()
223
+ })
224
+
217
225
  it('插入图片工具:弹层型声明(popup: insert-image),组 insert;无选区禁用', () => {
218
226
  const sheet = new Sheet()
219
227
  const ctx = createSheetContext(sheet)
@@ -1,5 +1,5 @@
1
- import { createRange } from '@veltra/sheet-core/core/address'
2
- import { Sheet } from '@veltra/sheet-core/core/sheet'
1
+ import { createRange } from '@veltra/sheet-core/core/address.js'
2
+ import { Sheet } from '@veltra/sheet-core/core/sheet.js'
3
3
  import { describe, expect, it, vi } from 'vitest'
4
4
 
5
5
  import { createSheetContext } from '../context'
@@ -1,6 +1,6 @@
1
- import type { CellRange } from '@veltra/sheet-core/core/address'
2
- import type { SetAxisStyleItem } from '@veltra/sheet-core/core/command/set-axis-style'
3
- import type { CellStylePatch } from '@veltra/sheet-core/core/style/types'
1
+ import type { CellRange } from '@veltra/sheet-core/core/address.js'
2
+ import type { SetAxisStyleItem } from '@veltra/sheet-core/core/command/set-axis-style.js'
3
+ import type { CellStylePatch } from '@veltra/sheet-core/core/style/types.js'
4
4
 
5
5
  /** 样式写入目标:整行 / 整列默认样式,或逐格 */
6
6
  export type SelectionStyleTarget = 'row' | 'col' | 'cell'
@@ -18,13 +18,14 @@ import {
18
18
  Underline,
19
19
  UnmergeCells,
20
20
  Upload,
21
+ Variable,
21
22
  VerticalAlignCenter,
22
23
  VerticalAlignLeft,
23
24
  VerticalAlignRight,
24
25
  Wrap
25
26
  } from '@veltra/icons/normal'
26
- import { rangesEqual } from '@veltra/sheet-core/core/address'
27
- import type { HorizontalAlign, VerticalAlign } from '@veltra/sheet-core/core/style/types'
27
+ import { rangesEqual } from '@veltra/sheet-core/core/address.js'
28
+ import type { HorizontalAlign, VerticalAlign } from '@veltra/sheet-core/core/style/types.js'
28
29
 
29
30
  import type { SheetContext } from './context'
30
31
  import { registerTool } from './registry'
@@ -365,7 +366,7 @@ registerTool({
365
366
  }
366
367
  })
367
368
 
368
- // ─── edit(查找)───────────────────────────────────────────
369
+ // ─── edit(查找 / 函数)──────────────────────────────────────
369
370
 
370
371
  registerTool({
371
372
  id: 'find',
@@ -378,6 +379,17 @@ registerTool({
378
379
  onClick: () => {}
379
380
  })
380
381
 
382
+ registerTool({
383
+ id: 'functions',
384
+ title: '函数',
385
+ icon: Variable,
386
+ tooltip: '按分类浏览并插入函数',
387
+ group: 'edit',
388
+ order: 1,
389
+ popup: 'functions',
390
+ onClick: () => {}
391
+ })
392
+
381
393
  // ─── insert(插入图片)─────────────────────────────────────
382
394
 
383
395
  registerTool({
@@ -1,14 +1,14 @@
1
- import type { CellAddress, CellRange } from '@veltra/sheet-core/core/address'
2
- import type { CellData, CellValue } from '@veltra/sheet-core/core/cell-store'
3
- import type { HistoryState } from '@veltra/sheet-core/core/command/history'
4
- import type { ImageUpdateFields } from '@veltra/sheet-core/core/command/image'
5
- import type { SetCellValueItem } from '@veltra/sheet-core/core/command/set-cell-value'
6
- import type { ImageInput, SheetImage } from '@veltra/sheet-core/core/image'
7
- import type { CellInfo } from '@veltra/sheet-core/core/merge-manager'
8
- import type { SelectionState } from '@veltra/sheet-core/core/selection'
9
- import type { FrozenState, Sheet } from '@veltra/sheet-core/core/sheet'
10
- import type { CellStyle, CellStylePatch } from '@veltra/sheet-core/core/style/types'
11
- import type { Workbook } from '@veltra/sheet-core/core/workbook'
1
+ import type { CellAddress, CellRange } from '@veltra/sheet-core/core/address.js'
2
+ import type { CellData, CellValue } from '@veltra/sheet-core/core/cell-store.js'
3
+ import type { HistoryState } from '@veltra/sheet-core/core/command/history.js'
4
+ import type { ImageUpdateFields } from '@veltra/sheet-core/core/command/image.js'
5
+ import type { SetCellValueItem } from '@veltra/sheet-core/core/command/set-cell-value.js'
6
+ import type { ImageInput, SheetImage } from '@veltra/sheet-core/core/image.js'
7
+ import type { CellInfo } from '@veltra/sheet-core/core/merge-manager.js'
8
+ import type { SelectionState } from '@veltra/sheet-core/core/selection.js'
9
+ import type { FrozenState, Sheet } from '@veltra/sheet-core/core/sheet.js'
10
+ import type { CellStyle, CellStylePatch } from '@veltra/sheet-core/core/style/types.js'
11
+ import type { Workbook } from '@veltra/sheet-core/core/workbook.js'
12
12
 
13
13
  import { axisStyleItemsForRange, classifySelectionStyleTarget } from './apply-style'
14
14
 
@@ -1,6 +1,6 @@
1
1
  import { saveBlob } from '@cat-kit/fe'
2
- import { exportSheetCsv, exportWorkbookXlsx } from '@veltra/sheet-core/core/io/export'
3
- import type { Workbook } from '@veltra/sheet-core/core/workbook'
2
+ import { exportSheetCsv, exportWorkbookXlsx } from '@veltra/sheet-core/core/io/export.js'
3
+ import type { Workbook } from '@veltra/sheet-core/core/workbook.js'
4
4
 
5
5
  import type { SheetContext } from './context'
6
6
  import type { ExportWorkerPayload, ExportWorkerResponse } from './export.worker'