@veltra/sheet 2.0.0 → 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 (203) hide show
  1. package/dist/index.d.ts +4 -25
  2. package/dist/index.js +3 -22
  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 -138
  33. package/dist/vue/style.d.ts +11 -1
  34. package/dist/vue/style.js +11 -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 -6
  46. package/src/env.d.ts +14 -0
  47. package/src/index.ts +203 -13
  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 +163 -148
  86. package/src/vue/style.scss +646 -3
  87. package/src/vue/style.ts +12 -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/formula/ast.d.ts +0 -58
  125. package/dist/core/formula/ast.js +0 -38
  126. package/dist/core/formula/ast.js.map +0 -1
  127. package/dist/core/formula/dependency-graph.d.ts +0 -70
  128. package/dist/core/formula/dependency-graph.js +0 -269
  129. package/dist/core/formula/dependency-graph.js.map +0 -1
  130. package/dist/core/formula/errors.d.ts +0 -25
  131. package/dist/core/formula/errors.js +0 -36
  132. package/dist/core/formula/errors.js.map +0 -1
  133. package/dist/core/formula/evaluator.d.ts +0 -41
  134. package/dist/core/formula/evaluator.js +0 -154
  135. package/dist/core/formula/evaluator.js.map +0 -1
  136. package/dist/core/formula/functions.d.ts +0 -30
  137. package/dist/core/formula/functions.js +0 -209
  138. package/dist/core/formula/functions.js.map +0 -1
  139. package/dist/core/formula/parser.d.ts +0 -7
  140. package/dist/core/formula/parser.js +0 -208
  141. package/dist/core/formula/parser.js.map +0 -1
  142. package/dist/core/formula/tokenizer.d.ts +0 -37
  143. package/dist/core/formula/tokenizer.js +0 -146
  144. package/dist/core/formula/tokenizer.js.map +0 -1
  145. package/dist/core/merge-manager.d.ts +0 -64
  146. package/dist/core/merge-manager.js +0 -111
  147. package/dist/core/merge-manager.js.map +0 -1
  148. package/dist/core/selection.d.ts +0 -31
  149. package/dist/core/selection.js +0 -62
  150. package/dist/core/selection.js.map +0 -1
  151. package/dist/core/sheet.d.ts +0 -105
  152. package/dist/core/sheet.js +0 -202
  153. package/dist/core/sheet.js.map +0 -1
  154. package/dist/core/workbook.d.ts +0 -43
  155. package/dist/core/workbook.js +0 -77
  156. package/dist/core/workbook.js.map +0 -1
  157. package/dist/grid/sheet-grid.d.ts +0 -59
  158. package/dist/grid/sheet-grid.js +0 -230
  159. package/dist/grid/sheet-grid.js.map +0 -1
  160. package/dist/tools/builtin.js.map +0 -1
  161. package/dist/tools/context.js.map +0 -1
  162. package/dist/tools/registry.js.map +0 -1
  163. package/dist/vue/index.d.ts +0 -2
  164. package/dist/vue/sheet.js.map +0 -1
  165. package/dist/vue/sheet.vue.d.ts +0 -24
  166. package/src/core/__test__/address.test.ts +0 -113
  167. package/src/core/__test__/cell-store.test.ts +0 -97
  168. package/src/core/__test__/command.test.ts +0 -377
  169. package/src/core/__test__/formula-adversarial.test.ts +0 -265
  170. package/src/core/__test__/formula-engine.test.ts +0 -470
  171. package/src/core/__test__/formula-functions.test.ts +0 -178
  172. package/src/core/__test__/formula-parser.test.ts +0 -160
  173. package/src/core/__test__/history.test.ts +0 -225
  174. package/src/core/__test__/merge-manager.test.ts +0 -106
  175. package/src/core/__test__/sheet.test.ts +0 -142
  176. package/src/core/address.ts +0 -166
  177. package/src/core/cell-store.ts +0 -173
  178. package/src/core/command/default-registry.ts +0 -15
  179. package/src/core/command/history.ts +0 -167
  180. package/src/core/command/index.ts +0 -7
  181. package/src/core/command/merge-cells.ts +0 -74
  182. package/src/core/command/registry.ts +0 -30
  183. package/src/core/command/set-cell-formula.ts +0 -29
  184. package/src/core/command/set-cell-value.ts +0 -38
  185. package/src/core/command/types.ts +0 -73
  186. package/src/core/events.ts +0 -37
  187. package/src/core/formula/ast.ts +0 -66
  188. package/src/core/formula/dependency-graph.ts +0 -329
  189. package/src/core/formula/errors.ts +0 -41
  190. package/src/core/formula/evaluator.ts +0 -219
  191. package/src/core/formula/functions.ts +0 -266
  192. package/src/core/formula/index.ts +0 -7
  193. package/src/core/formula/parser.ts +0 -198
  194. package/src/core/formula/tokenizer.ts +0 -160
  195. package/src/core/merge-manager.ts +0 -158
  196. package/src/core/selection.ts +0 -66
  197. package/src/core/sheet.ts +0 -257
  198. package/src/core/workbook.ts +0 -99
  199. package/src/grid/__test__/canvas-mock.ts +0 -49
  200. package/src/grid/__test__/setup.ts +0 -3
  201. package/src/grid/__test__/sheet-grid-formula.test.ts +0 -140
  202. package/src/grid/__test__/sheet-grid.test.ts +0 -177
  203. package/src/grid/sheet-grid.ts +0 -283
@@ -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 };