@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
@@ -0,0 +1,29 @@
1
+ //#region src/tools/export.worker.ts
2
+ self.onmessage = (e) => {
3
+ Promise.all([import("@veltra/sheet-core/core/io/export"), import("@veltra/sheet-core/core/workbook")]).then(([{ exportWorkbookXlsx }, { Workbook }]) => {
4
+ const wb = new Workbook();
5
+ const sheets = e.data.sheets;
6
+ for (let i = 0; i < sheets.length; i++) {
7
+ const { name, snapshot } = sheets[i];
8
+ const sheet = i === 0 ? wb.activeSheet : wb.addSheet(name);
9
+ if (i === 0 && sheet.name !== name) wb.renameSheet(sheet.name, name);
10
+ sheet.restore(snapshot);
11
+ }
12
+ wb.activateSheet(wb.getSheets()[Math.min(e.data.activeIndex, wb.sheetCount - 1)].name);
13
+ return exportWorkbookXlsx(wb);
14
+ }).then((buffer) => {
15
+ const exact = buffer.slice();
16
+ const response = {
17
+ ok: true,
18
+ buffer: exact
19
+ };
20
+ self.postMessage(response, [exact.buffer]);
21
+ }).catch((err) => {
22
+ const response = {
23
+ ok: false,
24
+ error: err instanceof Error ? err.message : String(err)
25
+ };
26
+ self.postMessage(response);
27
+ });
28
+ };
29
+ //#endregion
@@ -10,6 +10,8 @@ import { SheetContext } from "./context.js";
10
10
  * - 默认注册表全局共享:所有 USheet 实例渲染同一组工具
11
11
  * (与 defaultCommandRegistry 同语义;每个实例的 SheetContext 各自绑定当前 sheet)
12
12
  */
13
+ /** 弹层型工具类型:vue 层按类型渲染弹层面板(面板交互走 SheetContext 命令入口) */
14
+ type SheetToolPopupType = 'fill-color' | 'border' | 'font-color' | 'font-size' | 'find' | 'insert-image' | 'export';
13
15
  /** 工具定义 */
14
16
  interface SheetTool {
15
17
  /** 唯一 id;同 id 重复注册视为替换(保留原位置) */
@@ -28,6 +30,10 @@ interface SheetTool {
28
30
  visible?(ctx: SheetContext): boolean;
29
31
  /** 是否禁用(工具栏状态刷新时求值);缺省可用 */
30
32
  disabled?(ctx: SheetContext): boolean;
33
+ /** 是否处于激活/高亮状态(工具栏状态刷新时求值,vue 层渲染 is-active);缺省 false */
34
+ active?(ctx: SheetContext): boolean;
35
+ /** 弹层型工具:vue 层渲染弹层面板,onClick 不执行(面板交互走 SheetContext) */
36
+ popup?: SheetToolPopupType;
31
37
  /** 点击执行;ctx 是唯一操作入口(写操作全走命令系统,天然可撤销) */
32
38
  onClick(ctx: SheetContext): void;
33
39
  }
@@ -36,8 +42,6 @@ interface SheetToolGroup {
36
42
  name: string;
37
43
  tools: SheetTool[];
38
44
  }
39
- /** 缺省分组名 */
40
- declare const DEFAULT_TOOL_GROUP = "default";
41
45
  declare class ToolRegistry {
42
46
  private entries;
43
47
  private seq;
@@ -64,5 +68,4 @@ declare function registerTool(tool: SheetTool): void;
64
68
  /** 从默认注册表注销工具;id 不存在时返回 false */
65
69
  declare function unregisterTool(id: string): boolean;
66
70
  //#endregion
67
- export { DEFAULT_TOOL_GROUP, SheetTool, SheetToolGroup, ToolRegistry, defaultToolRegistry, registerTool, unregisterTool };
68
- //# sourceMappingURL=registry.d.ts.map
71
+ export { SheetTool, SheetToolGroup, SheetToolPopupType, ToolRegistry, defaultToolRegistry, registerTool, unregisterTool };
@@ -1,4 +1,4 @@
1
- import { TypedEventEmitter } from "../core/events.js";
1
+ import { TypedEventEmitter } from "@veltra/sheet-core/core/events";
2
2
  //#region src/tools/registry.ts
3
3
  /** 缺省分组名 */
4
4
  const DEFAULT_TOOL_GROUP = "default";
@@ -73,5 +73,3 @@ function unregisterTool(id) {
73
73
  }
74
74
  //#endregion
75
75
  export { DEFAULT_TOOL_GROUP, ToolRegistry, defaultToolRegistry, registerTool, unregisterTool };
76
-
77
- //# sourceMappingURL=registry.js.map
@@ -1,2 +1,3 @@
1
1
  import { SheetEmits, SheetExposed, SheetProps, _SheetExposed } from "./sheet.js";
2
+ import "@veltra/sheet-core/core/image";
2
3
  export { SheetEmits, SheetExposed, SheetProps, _SheetExposed };
@@ -1,9 +1,9 @@
1
- import { Sheet } from "../core/sheet.js";
2
- import { Workbook } from "../core/workbook.js";
3
- import { SheetGrid } from "../grid/sheet-grid.js";
4
1
  import { SheetContext } from "../tools/context.js";
5
2
  import { ComputedRef } from "vue";
6
3
  import { DeconstructValue } from "@veltra/utils";
4
+ import { SheetGrid } from "@veltra/sheet-core/grid/sheet-grid";
5
+ import { Workbook } from "@veltra/sheet-core/core/workbook";
6
+ import { Sheet } from "@veltra/sheet-core/core/sheet";
7
7
  //#region src/types/sheet.d.ts
8
8
  /** 电子表格组件属性 */
9
9
  interface SheetProps {
@@ -15,6 +15,8 @@ interface SheetProps {
15
15
  cols?: number;
16
16
  /** 是否显示工具栏,默认 true */
17
17
  showToolbar?: boolean;
18
+ /** 是否显示顶部公式栏(名称框 + fx 输入栏),默认 true */
19
+ showFormulaBar?: boolean;
18
20
  /** 是否显示底部 sheet 标签栏,默认 true */
19
21
  showTabs?: boolean;
20
22
  }
@@ -39,5 +41,4 @@ interface _SheetExposed {
39
41
  /** 电子表格组件暴露的属性和方法 */
40
42
  type SheetExposed = DeconstructValue<_SheetExposed>;
41
43
  //#endregion
42
- export { SheetEmits, SheetExposed, SheetProps, _SheetExposed };
43
- //# sourceMappingURL=sheet.d.ts.map
44
+ export { SheetEmits, SheetExposed, SheetProps, _SheetExposed };
@@ -0,0 +1,393 @@
1
+ import _sfc_main$1 from "./formula-suggest-list.js";
2
+ import { insertRefText, isRefSelectContext } from "./use-formula-ref-select.js";
3
+ import { applySuggest, filterFormulaSuggestions, getSuggestContext, moveSuggestIndex } from "./use-formula-suggest.js";
4
+ import { formatAddress, formatRange, parseRange } from "@veltra/sheet-core/core/address";
5
+ import { computed, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, nextTick, normalizeClass, onBeforeUnmount, onMounted, openBlock, ref, shallowRef, unref, useTemplateRef, vModelText, watch, withDirectives, withModifiers } from "vue";
6
+ import { message } from "@veltra/desktop";
7
+ import { bem } from "@veltra/utils";
8
+ //#region src/vue/formula-bar.vue
9
+ const _hoisted_1 = ["disabled", "readonly"];
10
+ const _sfc_main = /*@__PURE__*/ defineComponent({
11
+ name: "UFormulaBar",
12
+ __name: "formula-bar",
13
+ props: {
14
+ sheet: {},
15
+ context: {}
16
+ },
17
+ setup(__props, { expose: __expose }) {
18
+ /**
19
+ * 公式栏(名称框 + fx 输入栏),USheet 顶部结构之一。
20
+ *
21
+ * - 名称框:显示当前选区(单格 A1 / 区域 A1:B2);合法地址回车跳转。
22
+ * - fx 输入栏:活动格内容;Enter/✓ 提交、Esc/✗ 取消、失焦提交。
23
+ * - 函数补全:`=` 开头且光标在函数名 token 上时弹出候选(↑↓ / Tab / Enter / 点击)。
24
+ * - 引用选择:光标在运算符/`(`/`,` 后时,画布点选/拖选插入引用(blur 抑制防误提交)。
25
+ * - 网格编辑镜像:只读显示网格编辑器文本。
26
+ */
27
+ const props = __props;
28
+ const cls = bem("sheet");
29
+ const fxRef = useTemplateRef("fxRef");
30
+ const selection = shallowRef({
31
+ activeCell: null,
32
+ ranges: []
33
+ });
34
+ const nameDraft = ref("");
35
+ const nameFocused = ref(false);
36
+ const editing = ref(false);
37
+ let editAddr = null;
38
+ const mirroring = ref(false);
39
+ let mirrorAddr = null;
40
+ const fxDraft = ref("");
41
+ /** 输入面板是否因内容增高而浮出(文档流仍保持单行) */
42
+ const fxExpanded = ref(false);
43
+ /** textarea 光标(selectionStart);输入/点击/方向键后同步 */
44
+ const fxCursor = ref(0);
45
+ /** 画布 pointerdown 置位:下一帧 blur 跳过提交(引用选择模式) */
46
+ let suppressBlurCommit = false;
47
+ const suggestItems = shallowRef([]);
48
+ const suggestIndex = ref(0);
49
+ const suggestOpen = computed(() => suggestItems.value.length > 0);
50
+ const fxDisabled = computed(() => selection.value.activeCell == null);
51
+ /** 编排层查询:fx 编辑中且光标处于可插入引用位置 */
52
+ function isRefSelecting() {
53
+ return editing.value && !mirroring.value && isRefSelectContext(fxDraft.value, fxCursor.value);
54
+ }
55
+ /** 网格 pointerdown(capture)→ 挂起即将到来的 blur 提交 */
56
+ function beginBlurSuppress() {
57
+ if (!isRefSelecting()) return;
58
+ suppressBlurCommit = true;
59
+ }
60
+ /** 选区拦截回调:插入 A1 / A1:B2 到光标处 */
61
+ function handleRefSelect(range) {
62
+ if (!editing.value || mirroring.value) return;
63
+ const rangeText = formatRange(range);
64
+ const selEnd = fxRef.value?.selectionEnd ?? fxCursor.value;
65
+ const result = insertRefText(fxDraft.value, fxCursor.value, rangeText, selEnd);
66
+ fxDraft.value = result.text;
67
+ fxCursor.value = result.cursor;
68
+ closeSuggest();
69
+ nextTick(() => {
70
+ const input = fxRef.value;
71
+ if (!input) return;
72
+ input.focus();
73
+ input.setSelectionRange(result.cursor, result.cursor);
74
+ autosizeFx();
75
+ });
76
+ }
77
+ function nameText(state) {
78
+ const range = state.ranges[0];
79
+ if (range) return formatRange(range);
80
+ return state.activeCell ? formatAddress(state.activeCell) : "";
81
+ }
82
+ function cellText(addr) {
83
+ const data = props.context.getCellData(addr);
84
+ if (data?.f != null) return `=${data.f}`;
85
+ if (data?.v != null) return String(data.v);
86
+ return "";
87
+ }
88
+ function refreshName() {
89
+ nameDraft.value = nameText(selection.value);
90
+ }
91
+ function refreshFx() {
92
+ const active = selection.value.activeCell;
93
+ fxDraft.value = active ? cellText(active) : "";
94
+ fxCursor.value = fxDraft.value.length;
95
+ closeSuggest();
96
+ scheduleAutosize();
97
+ }
98
+ /** 每帧合并一次的 autosizeFx(批量 cell-change 同帧 N 次 nextTick → 1 次强制布局) */
99
+ let autosizePending = false;
100
+ function scheduleAutosize() {
101
+ if (autosizePending) return;
102
+ autosizePending = true;
103
+ nextTick(() => {
104
+ autosizePending = false;
105
+ autosizeFx();
106
+ });
107
+ }
108
+ /**
109
+ * 按内容增高浮出面板,不超过 CSS max-height。
110
+ * 增高走绝对定位,公式栏文档流高度始终单行。
111
+ */
112
+ function autosizeFx() {
113
+ const el = fxRef.value;
114
+ if (!el) return;
115
+ el.style.height = "0px";
116
+ const styles = getComputedStyle(el);
117
+ const max = Number.parseFloat(styles.maxHeight);
118
+ const min = Number.parseFloat(styles.minHeight);
119
+ const content = el.scrollHeight;
120
+ const next = Number.isFinite(max) && max > 0 ? Math.min(content, max) : content;
121
+ el.style.height = `${next}px`;
122
+ fxExpanded.value = Number.isFinite(min) ? next > min + 1 : next > 1;
123
+ }
124
+ function syncFxCursor() {
125
+ const el = fxRef.value;
126
+ if (!el) return;
127
+ fxCursor.value = el.selectionStart;
128
+ refreshSuggest();
129
+ }
130
+ function handleFxInput() {
131
+ syncFxCursor();
132
+ autosizeFx();
133
+ }
134
+ function closeSuggest() {
135
+ suggestItems.value = [];
136
+ suggestIndex.value = 0;
137
+ }
138
+ function refreshSuggest() {
139
+ if (!editing.value || mirroring.value) {
140
+ closeSuggest();
141
+ return;
142
+ }
143
+ const ctx = getSuggestContext(fxDraft.value, fxCursor.value);
144
+ if (!ctx) {
145
+ closeSuggest();
146
+ return;
147
+ }
148
+ const items = filterFormulaSuggestions(ctx.prefix);
149
+ suggestItems.value = items;
150
+ if (suggestIndex.value >= items.length) suggestIndex.value = 0;
151
+ }
152
+ function confirmSuggest(item) {
153
+ const pick = item ?? suggestItems.value[suggestIndex.value];
154
+ if (!pick) return;
155
+ const ctx = getSuggestContext(fxDraft.value, fxCursor.value);
156
+ if (!ctx) return;
157
+ const result = applySuggest(fxDraft.value, ctx.start, ctx.end, pick.name);
158
+ fxDraft.value = result.text;
159
+ fxCursor.value = result.cursor;
160
+ closeSuggest();
161
+ nextTick(() => {
162
+ const el = fxRef.value;
163
+ if (!el) return;
164
+ el.focus();
165
+ el.setSelectionRange(result.cursor, result.cursor);
166
+ autosizeFx();
167
+ refreshSuggest();
168
+ });
169
+ }
170
+ function handleNameKeydown(event) {
171
+ if (event.key === "Enter") {
172
+ event.preventDefault();
173
+ commitName();
174
+ } else if (event.key === "Escape") {
175
+ event.preventDefault();
176
+ refreshName();
177
+ }
178
+ }
179
+ function handleNameBlur() {
180
+ nameFocused.value = false;
181
+ refreshName();
182
+ }
183
+ function commitName() {
184
+ const range = parseRange(nameDraft.value);
185
+ if (!range) {
186
+ message.warn("无效的单元格地址");
187
+ refreshName();
188
+ return;
189
+ }
190
+ if (range.start.row === range.end.row && range.start.col === range.end.col) props.context.selectCell(range.start);
191
+ else props.context.selectRange(range);
192
+ refreshName();
193
+ }
194
+ function handleFxFocus() {
195
+ if (fxDisabled.value) return;
196
+ mirroring.value = false;
197
+ mirrorAddr = null;
198
+ editing.value = true;
199
+ editAddr = selection.value.activeCell;
200
+ syncFxCursor();
201
+ }
202
+ /**
203
+ * 失焦提交;引用选择期间(画布 pointerdown 已挂起)跳过。
204
+ * ✓/✗ / 候选列表用 mousedown.prevent 拦截失焦。
205
+ */
206
+ function handleFxBlur() {
207
+ if (!editing.value) return;
208
+ if (suppressBlurCommit) {
209
+ suppressBlurCommit = false;
210
+ return;
211
+ }
212
+ commitEdit();
213
+ }
214
+ function handleFxKeydown(event) {
215
+ if (suggestOpen.value) {
216
+ if (event.key === "ArrowDown") {
217
+ event.preventDefault();
218
+ suggestIndex.value = moveSuggestIndex(suggestIndex.value, suggestItems.value.length, 1);
219
+ return;
220
+ }
221
+ if (event.key === "ArrowUp") {
222
+ event.preventDefault();
223
+ suggestIndex.value = moveSuggestIndex(suggestIndex.value, suggestItems.value.length, -1);
224
+ return;
225
+ }
226
+ if (event.key === "Enter" || event.key === "Tab") {
227
+ event.preventDefault();
228
+ confirmSuggest();
229
+ return;
230
+ }
231
+ if (event.key === "Escape") {
232
+ event.preventDefault();
233
+ closeSuggest();
234
+ return;
235
+ }
236
+ }
237
+ if (event.key === "Enter" && !event.shiftKey) {
238
+ event.preventDefault();
239
+ commitEdit();
240
+ } else if (event.key === "Escape") {
241
+ event.preventDefault();
242
+ cancelEdit();
243
+ }
244
+ }
245
+ function commitEdit() {
246
+ if (!editing.value) return;
247
+ const addr = editAddr;
248
+ editing.value = false;
249
+ editAddr = null;
250
+ suppressBlurCommit = false;
251
+ closeSuggest();
252
+ if (addr) props.context.setCellValue(addr, fxDraft.value);
253
+ refreshFx();
254
+ }
255
+ function cancelEdit() {
256
+ if (!editing.value) return;
257
+ editing.value = false;
258
+ editAddr = null;
259
+ suppressBlurCommit = false;
260
+ closeSuggest();
261
+ refreshFx();
262
+ }
263
+ function handleSelectionChange(state) {
264
+ selection.value = state;
265
+ if (!nameFocused.value) refreshName();
266
+ if (editing.value) return;
267
+ mirroring.value = false;
268
+ mirrorAddr = null;
269
+ refreshFx();
270
+ }
271
+ function handleCellChange({ addr }) {
272
+ if (editing.value) return;
273
+ if (mirroring.value) {
274
+ if (mirrorAddr && addr.row === mirrorAddr.row && addr.col === mirrorAddr.col) {
275
+ mirroring.value = false;
276
+ mirrorAddr = null;
277
+ refreshFx();
278
+ }
279
+ return;
280
+ }
281
+ const active = selection.value.activeCell;
282
+ if (!active || addr.row !== active.row || addr.col !== active.col) return;
283
+ refreshFx();
284
+ }
285
+ function mirrorGridEdit(addr) {
286
+ if (editing.value || fxDisabled.value) return;
287
+ mirroring.value = true;
288
+ mirrorAddr = addr;
289
+ fxDraft.value = cellText(addr);
290
+ closeSuggest();
291
+ }
292
+ function exitMirror(addr) {
293
+ if (!mirroring.value) return;
294
+ if (mirrorAddr && addr.row === mirrorAddr.row && addr.col === mirrorAddr.col) {
295
+ mirroring.value = false;
296
+ mirrorAddr = null;
297
+ refreshFx();
298
+ }
299
+ }
300
+ __expose({
301
+ mirrorGridEdit,
302
+ exitMirror,
303
+ isRefSelecting,
304
+ beginBlurSuppress,
305
+ handleRefSelect
306
+ });
307
+ let dispose = [];
308
+ function bindSheet(sheet) {
309
+ for (const off of dispose) off();
310
+ dispose = [sheet.on("selection-change", handleSelectionChange), sheet.on("cell-change", handleCellChange)];
311
+ }
312
+ watch(() => props.sheet, (sheet, prev) => {
313
+ if (sheet === prev) return;
314
+ editing.value = false;
315
+ editAddr = null;
316
+ mirroring.value = false;
317
+ mirrorAddr = null;
318
+ suppressBlurCommit = false;
319
+ closeSuggest();
320
+ bindSheet(sheet);
321
+ selection.value = sheet.getSelection();
322
+ refreshName();
323
+ refreshFx();
324
+ });
325
+ onMounted(() => {
326
+ bindSheet(props.sheet);
327
+ selection.value = props.sheet.getSelection();
328
+ refreshName();
329
+ refreshFx();
330
+ });
331
+ onBeforeUnmount(() => {
332
+ for (const off of dispose) off();
333
+ dispose = [];
334
+ });
335
+ return (_ctx, _cache) => {
336
+ return openBlock(), createElementBlock("div", { class: normalizeClass(unref(cls).e("formula-bar")) }, [
337
+ withDirectives(createElementVNode("input", {
338
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => nameDraft.value = $event),
339
+ class: normalizeClass(unref(cls).e("name-box")),
340
+ spellcheck: "false",
341
+ autocomplete: "off",
342
+ title: "单元格地址或区域(如 B3 或 B3:D5),回车跳转",
343
+ onFocus: _cache[1] || (_cache[1] = ($event) => nameFocused.value = true),
344
+ onBlur: handleNameBlur,
345
+ onKeydown: handleNameKeydown
346
+ }, null, 34), [[vModelText, nameDraft.value]]),
347
+ createElementVNode("span", { class: normalizeClass(unref(cls).e("fx-label")) }, "fx", 2),
348
+ createElementVNode("div", { class: normalizeClass(unref(cls).e("fx-editor")) }, [createCommentVNode(" 文档流只占单行;面板绝对定位向下浮起,不撑开公式栏 "), createElementVNode("div", { class: normalizeClass([unref(cls).e("fx-input-panel"), unref(bem).is("expanded", fxExpanded.value)]) }, [withDirectives(createElementVNode("textarea", {
349
+ ref_key: "fxRef",
350
+ ref: fxRef,
351
+ "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => fxDraft.value = $event),
352
+ class: normalizeClass(unref(cls).e("fx-input")),
353
+ disabled: fxDisabled.value,
354
+ readonly: mirroring.value,
355
+ rows: "1",
356
+ spellcheck: "false",
357
+ autocomplete: "off",
358
+ title: "活动单元格内容('=' 开头为公式);Enter 提交,Esc 取消",
359
+ onFocus: handleFxFocus,
360
+ onBlur: handleFxBlur,
361
+ onKeydown: handleFxKeydown,
362
+ onInput: handleFxInput,
363
+ onClick: syncFxCursor,
364
+ onKeyup: syncFxCursor,
365
+ onSelect: syncFxCursor
366
+ }, null, 42, _hoisted_1), [[vModelText, fxDraft.value]]), createVNode(_sfc_main$1, {
367
+ items: suggestItems.value,
368
+ "active-index": suggestIndex.value,
369
+ onSelect: confirmSuggest,
370
+ onHover: _cache[3] || (_cache[3] = (i) => suggestIndex.value = i)
371
+ }, null, 8, ["items", "active-index"])], 2)], 2),
372
+ editing.value ? (openBlock(), createElementBlock("button", {
373
+ key: 0,
374
+ type: "button",
375
+ class: normalizeClass(unref(cls).e("fx-btn")),
376
+ title: "提交(Enter)",
377
+ onMousedown: _cache[4] || (_cache[4] = withModifiers(() => {}, ["prevent"])),
378
+ onClick: commitEdit
379
+ }, " ✓ ", 34)) : createCommentVNode("v-if", true),
380
+ editing.value ? (openBlock(), createElementBlock("button", {
381
+ key: 1,
382
+ type: "button",
383
+ class: normalizeClass(unref(cls).e("fx-btn")),
384
+ title: "取消(Esc)",
385
+ onMousedown: _cache[5] || (_cache[5] = withModifiers(() => {}, ["prevent"])),
386
+ onClick: cancelEdit
387
+ }, " ✗ ", 34)) : createCommentVNode("v-if", true)
388
+ ], 2);
389
+ };
390
+ }
391
+ });
392
+ //#endregion
393
+ export { _sfc_main as default };
@@ -0,0 +1,66 @@
1
+ import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, nextTick, normalizeClass, openBlock, renderList, toDisplayString, unref, watch, withCtx, withModifiers } from "vue";
2
+ import { UScroll } from "@veltra/desktop";
3
+ import { bem } from "@veltra/utils";
4
+ //#region src/vue/formula-suggest-list.vue
5
+ const _hoisted_1 = [
6
+ "aria-selected",
7
+ "onMousedown",
8
+ "onMouseenter"
9
+ ];
10
+ const SUGGEST_MAX_HEIGHT = 240;
11
+ /** 单项约高(含 padding + 签名/描述两行);用于估算 UScroll height */
12
+ const SUGGEST_ITEM_HEIGHT = 48;
13
+ const _sfc_main = /*@__PURE__*/ defineComponent({
14
+ name: "UFormulaSuggestList",
15
+ __name: "formula-suggest-list",
16
+ props: {
17
+ items: {},
18
+ activeIndex: {}
19
+ },
20
+ emits: ["select", "hover"],
21
+ setup(__props, { emit: __emit }) {
22
+ const props = __props;
23
+ const emit = __emit;
24
+ const cls = bem("sheet");
25
+ /** 候选列表最大可视高度;内容更短时收缩,避免空前缀少量项时大片留白 */
26
+ const scrollHeight = computed(() => Math.min(Math.max(props.items.length, 1) * SUGGEST_ITEM_HEIGHT, SUGGEST_MAX_HEIGHT));
27
+ const itemEls = [];
28
+ function setItemRef(index, el) {
29
+ itemEls[index] = el instanceof HTMLElement ? el : null;
30
+ }
31
+ watch(() => [props.activeIndex, props.items], async ([index]) => {
32
+ await nextTick();
33
+ itemEls[index]?.scrollIntoView({ block: "nearest" });
34
+ });
35
+ return (_ctx, _cache) => {
36
+ return __props.items.length > 0 ? (openBlock(), createElementBlock("div", {
37
+ key: 0,
38
+ class: normalizeClass(unref(cls).e("fx-suggest")),
39
+ role: "listbox",
40
+ onMousedown: _cache[0] || (_cache[0] = withModifiers(() => {}, ["prevent"]))
41
+ }, [createCommentVNode(" 滚动用 desktop UScroll(自定义滚动条;原生 overflow 滚动条在浮层里常看不见) "), createVNode(unref(UScroll), {
42
+ height: scrollHeight.value,
43
+ class: normalizeClass(unref(cls).e("fx-suggest-scroll"))
44
+ }, {
45
+ default: withCtx(() => [createElementVNode("ul", { class: normalizeClass(unref(cls).e("fx-suggest-list")) }, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item, index) => {
46
+ return openBlock(), createElementBlock("li", {
47
+ key: item.name,
48
+ ref_for: true,
49
+ ref: (el) => setItemRef(index, el),
50
+ class: normalizeClass([unref(cls).e("fx-suggest-item"), unref(bem).is("active", index === __props.activeIndex)]),
51
+ role: "option",
52
+ "aria-selected": index === __props.activeIndex,
53
+ onMousedown: withModifiers(($event) => emit("select", item), ["prevent"]),
54
+ onMouseenter: ($event) => emit("hover", index)
55
+ }, [createElementVNode("span", { class: normalizeClass(unref(cls).e("fx-suggest-sig")) }, toDisplayString(item.signature), 3), item.description ? (openBlock(), createElementBlock("span", {
56
+ key: 0,
57
+ class: normalizeClass(unref(cls).e("fx-suggest-desc"))
58
+ }, toDisplayString(item.description), 3)) : createCommentVNode("v-if", true)], 42, _hoisted_1);
59
+ }), 128))], 2)]),
60
+ _: 1
61
+ }, 8, ["height", "class"])], 34)) : createCommentVNode("v-if", true);
62
+ };
63
+ }
64
+ });
65
+ //#endregion
66
+ export { _sfc_main as default };