@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,113 @@
1
+ import { useFindReplace } from "../use-find-replace.js";
2
+ import { Fragment, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, isRef, normalizeClass, openBlock, toDisplayString, unref, vModelCheckbox, vModelSelect, withDirectives } from "vue";
3
+ import { UInput } from "@veltra/desktop";
4
+ import { bem } from "@veltra/utils";
5
+ //#region src/vue/popups/find-popup.vue
6
+ const _hoisted_1 = ["disabled"];
7
+ const _hoisted_2 = ["disabled"];
8
+ const _hoisted_3 = ["disabled"];
9
+ const _hoisted_4 = ["disabled"];
10
+ const _sfc_main = /*@__PURE__*/ defineComponent({
11
+ name: "USheetFindPopup",
12
+ __name: "find-popup",
13
+ props: {
14
+ sheet: {},
15
+ context: {}
16
+ },
17
+ emits: ["close"],
18
+ setup(__props, { emit: __emit }) {
19
+ /**
20
+ * 查找条(关键词 / 上一个 / 下一个 / 命中计数 / 关闭 + 替换 / 全部替换)。
21
+ * 不参与面板事务:每次替换独立为一个 undo 单元;全部替换 = 一次批量 = 单 undo 单元。
22
+ * 弹层随 v-if 挂载 / 销毁,每次打开都是全新状态(关键词清空)。
23
+ */
24
+ const props = __props;
25
+ const emit = __emit;
26
+ const cls = bem("sheet");
27
+ const { findQuery, findReplace, caseSensitive, wholeCell, searchIn, canFind, canReplace, findCountText, findForward, findPrevious, handleFindKeydown, replaceCurrent, replaceAll } = useFindReplace({
28
+ getSheet: () => props.sheet,
29
+ context: props.context
30
+ });
31
+ return (_ctx, _cache) => {
32
+ return openBlock(), createElementBlock(Fragment, null, [
33
+ createElementVNode("div", { class: normalizeClass(unref(cls).e("find-row")) }, [
34
+ createVNode(unref(UInput), {
35
+ modelValue: unref(findQuery),
36
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => isRef(findQuery) ? findQuery.value = $event : null),
37
+ placeholder: "查找内容",
38
+ size: "small",
39
+ class: normalizeClass(unref(cls).e("find-input")),
40
+ onKeydown: unref(handleFindKeydown)
41
+ }, null, 8, [
42
+ "modelValue",
43
+ "class",
44
+ "onKeydown"
45
+ ]),
46
+ createElementVNode("span", { class: normalizeClass(unref(cls).e("find-count")) }, toDisplayString(unref(findCountText)), 3),
47
+ createElementVNode("button", {
48
+ type: "button",
49
+ class: normalizeClass(unref(cls).e("find-nav")),
50
+ disabled: !unref(canFind),
51
+ title: "上一个(Shift+Enter)",
52
+ onClick: _cache[1] || (_cache[1] = (...args) => unref(findPrevious) && unref(findPrevious)(...args))
53
+ }, " ↑ ", 10, _hoisted_1),
54
+ createElementVNode("button", {
55
+ type: "button",
56
+ class: normalizeClass(unref(cls).e("find-nav")),
57
+ disabled: !unref(canFind),
58
+ title: "下一个(Enter)",
59
+ onClick: _cache[2] || (_cache[2] = (...args) => unref(findForward) && unref(findForward)(...args))
60
+ }, " ↓ ", 10, _hoisted_2),
61
+ createElementVNode("button", {
62
+ type: "button",
63
+ class: normalizeClass(unref(cls).e("find-close")),
64
+ title: "关闭",
65
+ onClick: _cache[3] || (_cache[3] = ($event) => emit("close"))
66
+ }, " ✕ ", 2)
67
+ ], 2),
68
+ createElementVNode("div", { class: normalizeClass(unref(cls).e("find-row")) }, [
69
+ createVNode(unref(UInput), {
70
+ modelValue: unref(findReplace),
71
+ "onUpdate:modelValue": _cache[4] || (_cache[4] = ($event) => isRef(findReplace) ? findReplace.value = $event : null),
72
+ placeholder: "替换为",
73
+ size: "small",
74
+ class: normalizeClass(unref(cls).e("find-input")),
75
+ onKeydown: unref(handleFindKeydown)
76
+ }, null, 8, [
77
+ "modelValue",
78
+ "class",
79
+ "onKeydown"
80
+ ]),
81
+ createElementVNode("button", {
82
+ type: "button",
83
+ class: normalizeClass(unref(cls).e("find-btn")),
84
+ disabled: !unref(canReplace),
85
+ onClick: _cache[5] || (_cache[5] = (...args) => unref(replaceCurrent) && unref(replaceCurrent)(...args))
86
+ }, " 替换 ", 10, _hoisted_3),
87
+ createElementVNode("button", {
88
+ type: "button",
89
+ class: normalizeClass(unref(cls).e("find-btn")),
90
+ disabled: !unref(canReplace),
91
+ onClick: _cache[6] || (_cache[6] = (...args) => unref(replaceAll) && unref(replaceAll)(...args))
92
+ }, " 全部替换 ", 10, _hoisted_4)
93
+ ], 2),
94
+ createElementVNode("div", { class: normalizeClass(unref(cls).e("find-row")) }, [
95
+ createElementVNode("label", { class: normalizeClass(unref(cls).e("find-option")) }, [withDirectives(createElementVNode("input", {
96
+ "onUpdate:modelValue": _cache[7] || (_cache[7] = ($event) => isRef(caseSensitive) ? caseSensitive.value = $event : null),
97
+ type: "checkbox"
98
+ }, null, 512), [[vModelCheckbox, unref(caseSensitive)]]), _cache[10] || (_cache[10] = createTextVNode("区分大小写 ", -1))], 2),
99
+ createElementVNode("label", { class: normalizeClass(unref(cls).e("find-option")) }, [withDirectives(createElementVNode("input", {
100
+ "onUpdate:modelValue": _cache[8] || (_cache[8] = ($event) => isRef(wholeCell) ? wholeCell.value = $event : null),
101
+ type: "checkbox"
102
+ }, null, 512), [[vModelCheckbox, unref(wholeCell)]]), _cache[11] || (_cache[11] = createTextVNode("整格匹配 ", -1))], 2),
103
+ createElementVNode("label", { class: normalizeClass(unref(cls).e("find-option")) }, [_cache[13] || (_cache[13] = createTextVNode(" 查找 ", -1)), withDirectives(createElementVNode("select", {
104
+ "onUpdate:modelValue": _cache[9] || (_cache[9] = ($event) => isRef(searchIn) ? searchIn.value = $event : null),
105
+ class: normalizeClass(unref(cls).e("find-select"))
106
+ }, [..._cache[12] || (_cache[12] = [createElementVNode("option", { value: "value" }, "按显示值", -1), createElementVNode("option", { value: "formula" }, "按公式", -1)])], 2), [[vModelSelect, unref(searchIn)]])], 2)
107
+ ], 2)
108
+ ], 64);
109
+ };
110
+ }
111
+ });
112
+ //#endregion
113
+ export { _sfc_main as default };
@@ -0,0 +1,37 @@
1
+ import { currentRange } from "../popup-helpers.js";
2
+ import { createBlock, defineComponent, onMounted, openBlock, ref, unref } from "vue";
3
+ import { UPalette } from "@veltra/desktop";
4
+ //#region src/vue/popups/font-color-popup.vue
5
+ const _sfc_main = /*@__PURE__*/ defineComponent({
6
+ name: "USheetFontColorPopup",
7
+ __name: "font-color-popup",
8
+ props: { context: {} },
9
+ setup(__props) {
10
+ /**
11
+ * 字体颜色面板(UPalette;空值 = 清除 color 保留其它字体字段)。
12
+ * 面板打开期间的写入由 use-tool-popup 事务包裹为一个 undo 单元。
13
+ */
14
+ const props = __props;
15
+ /** 字体颜色('' = 自动/默认) */
16
+ const fontColor = ref("");
17
+ onMounted(() => {
18
+ const active = props.context.getSelection().activeCell;
19
+ const style = active ? props.context.getCellStyle(active) : void 0;
20
+ fontColor.value = style?.font?.color ?? "";
21
+ });
22
+ /** 字体颜色变化('' = 清除 color) */
23
+ function applyFontColor(color) {
24
+ const range = currentRange(props.context);
25
+ if (!range) return;
26
+ props.context.setCellStyle(range, color ? { font: { color } } : { font: { color: null } });
27
+ }
28
+ return (_ctx, _cache) => {
29
+ return openBlock(), createBlock(unref(UPalette), {
30
+ "model-value": fontColor.value,
31
+ "onUpdate:modelValue": applyFontColor
32
+ }, null, 8, ["model-value"]);
33
+ };
34
+ }
35
+ });
36
+ //#endregion
37
+ export { _sfc_main as default };
@@ -0,0 +1,63 @@
1
+ import { currentRange } from "../popup-helpers.js";
2
+ import { Fragment, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, onMounted, openBlock, ref, renderList, toDisplayString, unref, withCtx } from "vue";
3
+ import { UScroll } from "@veltra/desktop";
4
+ import { bem } from "@veltra/utils";
5
+ //#region src/vue/popups/font-size-popup.vue
6
+ const _hoisted_1 = ["onClick"];
7
+ const _sfc_main = /*@__PURE__*/ defineComponent({
8
+ name: "USheetFontSizePopup",
9
+ __name: "font-size-popup",
10
+ props: { context: {} },
11
+ setup(__props) {
12
+ /** 预设字号列表(pt,与 Excel 常用档对齐) */
13
+ const FONT_SIZES = [
14
+ 9,
15
+ 10,
16
+ 11,
17
+ 12,
18
+ 14,
19
+ 16,
20
+ 18,
21
+ 20,
22
+ 24,
23
+ 28,
24
+ 32
25
+ ];
26
+ /**
27
+ * 字号面板(预设列表;点击写入 font.size)。
28
+ * 面板打开期间的写入由 use-tool-popup 事务包裹为一个 undo 单元。
29
+ */
30
+ const props = __props;
31
+ const cls = bem("sheet");
32
+ const currentSize = ref(void 0);
33
+ onMounted(() => {
34
+ const active = props.context.getSelection().activeCell;
35
+ const style = active ? props.context.getCellStyle(active) : void 0;
36
+ currentSize.value = style?.font?.size;
37
+ });
38
+ function applyFontSize(size) {
39
+ const range = currentRange(props.context);
40
+ if (!range) return;
41
+ currentSize.value = size;
42
+ props.context.setCellStyle(range, { font: { size } });
43
+ }
44
+ return (_ctx, _cache) => {
45
+ return openBlock(), createElementBlock(Fragment, null, [createCommentVNode(" 下拉排列:每行一个字号;滚动用 desktop UScroll(自定义滚动条) "), createVNode(unref(UScroll), {
46
+ height: 280,
47
+ class: normalizeClass(unref(cls).e("popup-size-scroll"))
48
+ }, {
49
+ default: withCtx(() => [createElementVNode("div", { class: normalizeClass(unref(cls).e("popup-size-list")) }, [(openBlock(), createElementBlock(Fragment, null, renderList(FONT_SIZES, (size) => {
50
+ return createElementVNode("button", {
51
+ key: size,
52
+ type: "button",
53
+ class: normalizeClass([unref(cls).e("popup-size-item"), unref(bem).is("active", currentSize.value === size)]),
54
+ onClick: ($event) => applyFontSize(size)
55
+ }, toDisplayString(size), 11, _hoisted_1);
56
+ }), 64))], 2)]),
57
+ _: 1
58
+ }, 8, ["class"])], 2112);
59
+ };
60
+ }
61
+ });
62
+ //#endregion
63
+ export { _sfc_main as default };
@@ -0,0 +1,21 @@
1
+ import { SheetSnapshot } from "@veltra/sheet-core/core/sheet";
2
+ //#region src/vue/popups/import.worker.d.ts
3
+ type ImportWorkerResponse = {
4
+ type: 'progress';
5
+ done: number;
6
+ total: number;
7
+ } | {
8
+ type: 'done';
9
+ ok: true;
10
+ sheets: {
11
+ name: string;
12
+ snapshot: SheetSnapshot;
13
+ }[];
14
+ activeIndex: number;
15
+ } | {
16
+ type: 'done';
17
+ ok: false;
18
+ error: string;
19
+ };
20
+ //#endregion
21
+ export { ImportWorkerResponse };
@@ -0,0 +1,41 @@
1
+ //#region src/vue/popups/import.worker.ts
2
+ self.onmessage = (e) => {
3
+ const { buffer } = e.data;
4
+ (async () => {
5
+ try {
6
+ const [{ readXlsx }, { buildWorkbookFromHucre }] = await Promise.all([import("hucre/xlsx"), import("@veltra/sheet-core/core/io/import")]);
7
+ const hucreWb = await readXlsx(new Uint8Array(buffer), { readStyles: true });
8
+ let lastPercent = -1;
9
+ const wb = buildWorkbookFromHucre(hucreWb, (done, total) => {
10
+ const percent = total > 0 ? Math.floor(done / total * 10) : 10;
11
+ if (percent !== lastPercent || done === total) {
12
+ lastPercent = percent;
13
+ const progress = {
14
+ type: "progress",
15
+ done,
16
+ total
17
+ };
18
+ self.postMessage(progress);
19
+ }
20
+ });
21
+ const response = {
22
+ type: "done",
23
+ ok: true,
24
+ sheets: wb.getSheets().map((s) => ({
25
+ name: s.name,
26
+ snapshot: s.snapshot()
27
+ })),
28
+ activeIndex: wb.activeSheetIndex
29
+ };
30
+ self.postMessage(response);
31
+ } catch (err) {
32
+ const response = {
33
+ type: "done",
34
+ ok: false,
35
+ error: err instanceof Error ? err.message : String(err)
36
+ };
37
+ self.postMessage(response);
38
+ }
39
+ })();
40
+ };
41
+ //#endregion
@@ -0,0 +1,38 @@
1
+ import { INSERT_IMAGE_ACCEPT, insertImageFromFile } from "../insert-image.js";
2
+ import { createBlock, createElementVNode, createTextVNode, defineComponent, normalizeClass, openBlock, unref, withCtx } from "vue";
3
+ import { UFilePicker } from "@veltra/desktop";
4
+ import { bem } from "@veltra/utils";
5
+ //#region src/vue/popups/insert-image-popup.vue
6
+ const _sfc_main = /*@__PURE__*/ defineComponent({
7
+ name: "USheetInsertImagePopup",
8
+ __name: "insert-image-popup",
9
+ props: { context: {} },
10
+ emits: ["close"],
11
+ setup(__props, { emit: __emit }) {
12
+ /**
13
+ * 插入图片面板(UFilePicker):选文件后走共享 insertImageFromFile,锚定当前选区活动格。
14
+ * 不参与面板事务(单次插入 = 一个 undo 单元)。
15
+ */
16
+ const props = __props;
17
+ const emit = __emit;
18
+ const cls = bem("sheet");
19
+ function handlePick(files) {
20
+ const file = files[0];
21
+ if (!file) return;
22
+ emit("close");
23
+ insertImageFromFile(props.context, file);
24
+ }
25
+ return (_ctx, _cache) => {
26
+ return openBlock(), createBlock(unref(UFilePicker), {
27
+ accept: unref(INSERT_IMAGE_ACCEPT),
28
+ class: normalizeClass(unref(cls).e("insert-image-picker")),
29
+ onPick: handlePick
30
+ }, {
31
+ default: withCtx(() => [createElementVNode("div", { class: normalizeClass(unref(cls).e("insert-image-hint")) }, [_cache[0] || (_cache[0] = createTextVNode(" 选择图片文件 ", -1)), createElementVNode("div", { class: normalizeClass(unref(cls).e("insert-image-sub")) }, "支持 png / jpeg / gif / svg / webp", 2)], 2)]),
32
+ _: 1
33
+ }, 8, ["accept", "class"]);
34
+ };
35
+ }
36
+ });
37
+ //#endregion
38
+ export { _sfc_main as default };
@@ -0,0 +1,200 @@
1
+ import { defaultToolRegistry } from "../tools/registry.js";
2
+ import { pickAndInsertImage } from "./insert-image.js";
3
+ import _sfc_main from "./insert-count-menu-item.js";
4
+ import { createRange } from "@veltra/sheet-core/core/address";
5
+ import { defineComponent, h } from "vue";
6
+ const INSERT_COUNT_MAX = 1e3;
7
+ /** 选区覆盖行/列数 → 插入默认 N(min 1 / max 1000) */
8
+ function defaultInsertCount(range, axis) {
9
+ if (!range) return 1;
10
+ const n = axis === "row" ? range.end.row - range.start.row + 1 : range.end.col - range.start.col + 1;
11
+ return Math.min(INSERT_COUNT_MAX, Math.max(1, n));
12
+ }
13
+ /** 点击行是否落在任一选区内(按行号覆盖) */
14
+ function isRowInSelection(ranges, row) {
15
+ return ranges.some((r) => r.start.row <= row && row <= r.end.row);
16
+ }
17
+ /** 点击列是否落在任一选区内(按列号覆盖) */
18
+ function isColInSelection(ranges, col) {
19
+ return ranges.some((r) => r.start.col <= col && col <= r.end.col);
20
+ }
21
+ /** 整行选区(列跨渲染宽度) */
22
+ function wholeRowRange(row, colCount) {
23
+ return createRange({
24
+ row,
25
+ col: 0
26
+ }, {
27
+ row,
28
+ col: Math.max(colCount - 1, 0)
29
+ });
30
+ }
31
+ /** 整列选区(行跨渲染高度) */
32
+ function wholeColRange(col, rowCount) {
33
+ return createRange({
34
+ row: 0,
35
+ col
36
+ }, {
37
+ row: Math.max(rowCount - 1, 0),
38
+ col
39
+ });
40
+ }
41
+ /** 渲染尺寸 = max(props, sheet.rows/cols),与 SheetGrid 一致 */
42
+ function resolveRenderSize(propsRows, propsCols, sheetRows, sheetCols) {
43
+ return {
44
+ rows: Math.max(propsRows ?? 100, sheetRows),
45
+ cols: Math.max(propsCols ?? 26, sheetCols)
46
+ };
47
+ }
48
+ function insertCountRender(options) {
49
+ return defineComponent({
50
+ name: "SheetInsertCountMenuRender",
51
+ setup() {
52
+ return () => h(_sfc_main, {
53
+ prefix: options.prefix,
54
+ suffix: options.suffix,
55
+ defaultValue: options.defaultValue,
56
+ min: 1,
57
+ max: INSERT_COUNT_MAX,
58
+ onConfirm: options.onConfirm
59
+ });
60
+ }
61
+ });
62
+ }
63
+ function primaryRange(ctx) {
64
+ return ctx.getSelection().ranges[0] ?? null;
65
+ }
66
+ /** 行号右键菜单:插入×2 / 删除 / divider / 冻结 / 取消冻结 */
67
+ function buildRowHeaderMenus(ctx) {
68
+ const range = primaryRange(ctx);
69
+ const startRow = range?.start.row ?? 0;
70
+ const endRow = range?.end.row ?? startRow;
71
+ const rowCount = defaultInsertCount(range, "row");
72
+ const deleteCount = range ? endRow - startRow + 1 : 1;
73
+ const frozen = ctx.frozen;
74
+ const freezeActive = frozen.rows === endRow + 1;
75
+ return [
76
+ {
77
+ label: "在上方插入行",
78
+ keepOpen: true,
79
+ render: insertCountRender({
80
+ prefix: "在上方插入",
81
+ suffix: "行",
82
+ defaultValue: rowCount,
83
+ onConfirm: (n) => ctx.insertRows(startRow, n)
84
+ })
85
+ },
86
+ {
87
+ label: "在下方插入行",
88
+ keepOpen: true,
89
+ render: insertCountRender({
90
+ prefix: "在下方插入",
91
+ suffix: "行",
92
+ defaultValue: rowCount,
93
+ onConfirm: (n) => ctx.insertRows(endRow + 1, n)
94
+ })
95
+ },
96
+ {
97
+ label: "删除行",
98
+ callback: () => ctx.deleteRows(startRow, deleteCount)
99
+ },
100
+ { divider: true },
101
+ {
102
+ label: freezeActive ? "✓ 冻结到当前行" : "冻结到当前行",
103
+ callback: () => ctx.setFrozen(endRow + 1, frozen.cols)
104
+ },
105
+ {
106
+ label: "取消冻结",
107
+ disabled: frozen.rows === 0 && frozen.cols === 0,
108
+ callback: () => ctx.setFrozen(0, 0)
109
+ }
110
+ ];
111
+ }
112
+ /** 列头右键菜单:插入×2 / 删除 / divider / 冻结 / 取消冻结 */
113
+ function buildColHeaderMenus(ctx) {
114
+ const range = primaryRange(ctx);
115
+ const startCol = range?.start.col ?? 0;
116
+ const endCol = range?.end.col ?? startCol;
117
+ const colCount = defaultInsertCount(range, "col");
118
+ const deleteCount = range ? endCol - startCol + 1 : 1;
119
+ const frozen = ctx.frozen;
120
+ const freezeActive = frozen.cols === endCol + 1;
121
+ return [
122
+ {
123
+ label: "在左侧插入列",
124
+ keepOpen: true,
125
+ render: insertCountRender({
126
+ prefix: "在左侧插入",
127
+ suffix: "列",
128
+ defaultValue: colCount,
129
+ onConfirm: (n) => ctx.insertCols(startCol, n)
130
+ })
131
+ },
132
+ {
133
+ label: "在右侧插入列",
134
+ keepOpen: true,
135
+ render: insertCountRender({
136
+ prefix: "在右侧插入",
137
+ suffix: "列",
138
+ defaultValue: colCount,
139
+ onConfirm: (n) => ctx.insertCols(endCol + 1, n)
140
+ })
141
+ },
142
+ {
143
+ label: "删除列",
144
+ callback: () => ctx.deleteCols(startCol, deleteCount)
145
+ },
146
+ { divider: true },
147
+ {
148
+ label: freezeActive ? "✓ 冻结到当前列" : "冻结到当前列",
149
+ callback: () => ctx.setFrozen(frozen.rows, endCol + 1)
150
+ },
151
+ {
152
+ label: "取消冻结",
153
+ disabled: frozen.rows === 0 && frozen.cols === 0,
154
+ callback: () => ctx.setFrozen(0, 0)
155
+ }
156
+ ];
157
+ }
158
+ /** body 格右键菜单:合并/取消合并 + 插入图片(行列插入/删除仅行号/列头菜单) */
159
+ function buildBodyMenus(ctx) {
160
+ const mergeTool = defaultToolRegistry.get("merge");
161
+ const unmergeTool = defaultToolRegistry.get("unmerge");
162
+ const active = ctx.getSelection().activeCell;
163
+ return [
164
+ {
165
+ label: "合并单元格",
166
+ disabled: mergeTool?.disabled?.(ctx) ?? true,
167
+ callback: () => mergeTool?.onClick(ctx)
168
+ },
169
+ {
170
+ label: "取消合并单元格",
171
+ disabled: unmergeTool?.disabled?.(ctx) ?? true,
172
+ callback: () => unmergeTool?.onClick(ctx)
173
+ },
174
+ {
175
+ label: "插入图片",
176
+ disabled: !active,
177
+ callback: () => pickAndInsertImage(ctx)
178
+ }
179
+ ];
180
+ }
181
+ /**
182
+ * 右键落点预处理:body 格落在选区外 → 选中该格;
183
+ * 行号/列头落在选区外 → 选中整行/整列(渲染尺寸)。
184
+ */
185
+ function ensureContextMenuSelection(ctx, info, renderSize) {
186
+ const ranges = ctx.getSelection().ranges;
187
+ if (info.kind === "body" && info.addr) {
188
+ if (!ranges.some((range) => range.start.row <= info.addr.row && info.addr.row <= range.end.row && range.start.col <= info.addr.col && info.addr.col <= range.end.col)) ctx.selectCell(info.addr);
189
+ return;
190
+ }
191
+ if (info.kind === "row-header" && info.row != null) {
192
+ if (!isRowInSelection(ranges, info.row)) ctx.selectRange(wholeRowRange(info.row, renderSize.cols));
193
+ return;
194
+ }
195
+ if (info.kind === "col-header" && info.col != null) {
196
+ if (!isColInSelection(ranges, info.col)) ctx.selectRange(wholeColRange(info.col, renderSize.rows));
197
+ }
198
+ }
199
+ //#endregion
200
+ export { INSERT_COUNT_MAX, buildBodyMenus, buildColHeaderMenus, buildRowHeaderMenus, defaultInsertCount, ensureContextMenuSelection, isColInSelection, isRowInSelection, resolveRenderSize, wholeColRange, wholeRowRange };
@@ -0,0 +1,162 @@
1
+ import { useSheetTabsBar } from "./use-sheet-tabs-bar.js";
2
+ import { ArrowLeft, ArrowRight } from "@veltra/icons/normal";
3
+ import { Fragment, createElementBlock, createElementVNode, createVNode, defineComponent, nextTick, normalizeClass, openBlock, ref, renderList, toDisplayString, toRef, unref, useTemplateRef, vModelText, vShow, withCtx, withDirectives, withKeys, withModifiers } from "vue";
4
+ import { UIcon, contextmenu, message, messageConfirm } from "@veltra/desktop";
5
+ import { bem } from "@veltra/utils";
6
+ //#region src/vue/sheet-tabs.vue
7
+ const _hoisted_1 = ["disabled"];
8
+ const _hoisted_2 = ["onClick", "onContextmenu"];
9
+ const _hoisted_3 = ["onKeydown"];
10
+ const _hoisted_4 = { key: 1 };
11
+ const _hoisted_5 = ["disabled"];
12
+ const _sfc_main = /*@__PURE__*/ defineComponent({
13
+ name: "USheetTabs",
14
+ __name: "sheet-tabs",
15
+ props: {
16
+ workbook: {},
17
+ sheetList: {},
18
+ activeIndex: {}
19
+ },
20
+ setup(__props) {
21
+ /**
22
+ * 底部 sheet tabs(可滚动视口 + 左右导航;「+」钉在视口外):
23
+ * - tab 点击切换;末尾「+」addSheet + 自动激活新表(激活事件驱动 grid 重建)
24
+ * - tab 右键菜单「重命名 / 删除」:重命名进入行内输入(Enter 提交、Esc 取消、失焦提交;
25
+ * 冲突提示且不写入),删除二次确认(最后一个 sheet 的删除项禁用)
26
+ * - sheet 增删改名是工作簿级结构操作(不走 undo),直接调用 Workbook——
27
+ * 不经 SheetContext(见 AGENTS.md 门面边界);UI 刷新由 workbook 事件驱动
28
+ */
29
+ const props = __props;
30
+ const cls = bem("sheet");
31
+ const tabsViewportRef = useTemplateRef("tabsViewportRef");
32
+ const tabsListRef = useTemplateRef("tabsListRef");
33
+ const { showNav, canPrev, canNext, scrollByStep } = useSheetTabsBar({
34
+ viewportRef: tabsViewportRef,
35
+ listRef: tabsListRef,
36
+ sheetList: toRef(props, "sheetList"),
37
+ activeIndex: toRef(props, "activeIndex")
38
+ });
39
+ function handleTabClick(index) {
40
+ if (index === props.activeIndex) return;
41
+ const sheet = props.sheetList[index];
42
+ if (sheet) props.workbook.activateSheet(sheet.name);
43
+ }
44
+ /** 「+」添加 sheet:自动激活新表(激活事件驱动 grid 重建) */
45
+ function handleAddSheet() {
46
+ const sheet = props.workbook.addSheet();
47
+ props.workbook.activateSheet(sheet.name);
48
+ }
49
+ /** 正在行内重命名的 tab 下标(-1 = 无) */
50
+ const renamingIndex = ref(-1);
51
+ const renameDraft = ref("");
52
+ const renameInputRef = useTemplateRef("renameInputRef");
53
+ function handleTabContextMenu(event, sheet, index) {
54
+ contextmenu.pop({
55
+ mousePosition: {
56
+ x: event.clientX,
57
+ y: event.clientY
58
+ },
59
+ width: 160,
60
+ menus: [{
61
+ label: "重命名",
62
+ callback: () => startRename(index)
63
+ }, {
64
+ label: "删除",
65
+ disabled: props.sheetList.length <= 1,
66
+ callback: () => confirmRemoveSheet(sheet)
67
+ }]
68
+ });
69
+ }
70
+ /** 进入行内重命名(预填当前名并全选) */
71
+ function startRename(index) {
72
+ renamingIndex.value = index;
73
+ renameDraft.value = props.sheetList[index]?.name ?? "";
74
+ nextTick(() => renameInputRef.value?.[0]?.select());
75
+ }
76
+ /** 提交重命名:空名 / 重名(含大小写变体)由 Workbook 拒绝,提示且不写入 */
77
+ function commitRename() {
78
+ const index = renamingIndex.value;
79
+ if (index < 0) return;
80
+ renamingIndex.value = -1;
81
+ const sheet = props.sheetList[index];
82
+ if (!sheet) return;
83
+ const next = renameDraft.value.trim();
84
+ if (next === "" || next === sheet.name) return;
85
+ if (!props.workbook.renameSheet(sheet.name, next)) message.warn(`无法重命名:名称“${next}”无效或已被占用`);
86
+ }
87
+ function cancelRename() {
88
+ renamingIndex.value = -1;
89
+ }
90
+ /** 删除确认(message-confirm);最后一个 sheet 在菜单层已禁用,这里再兜底 */
91
+ function confirmRemoveSheet(sheet) {
92
+ if (props.workbook.sheetCount <= 1) return;
93
+ messageConfirm.danger(`确定删除工作表“${sheet.name}”吗?删除后不可恢复。`, {
94
+ confirmButtonText: "删除",
95
+ onClosed: (action) => {
96
+ if (action !== "confirm") return;
97
+ props.workbook.removeSheet(sheet.name);
98
+ }
99
+ });
100
+ }
101
+ return (_ctx, _cache) => {
102
+ return openBlock(), createElementBlock("div", { class: normalizeClass(unref(cls).e("tabs")) }, [
103
+ withDirectives(createElementVNode("button", {
104
+ type: "button",
105
+ class: normalizeClass(unref(cls).e("tabs-nav")),
106
+ disabled: !unref(canPrev),
107
+ title: "向左滚动",
108
+ onClick: _cache[0] || (_cache[0] = ($event) => unref(scrollByStep)(-1))
109
+ }, [createVNode(unref(UIcon), null, {
110
+ default: withCtx(() => [createVNode(unref(ArrowLeft))]),
111
+ _: 1
112
+ })], 10, _hoisted_1), [[vShow, unref(showNav)]]),
113
+ createElementVNode("div", {
114
+ ref_key: "tabsViewportRef",
115
+ ref: tabsViewportRef,
116
+ class: normalizeClass(unref(cls).e("tabs-viewport"))
117
+ }, [createElementVNode("div", {
118
+ ref_key: "tabsListRef",
119
+ ref: tabsListRef,
120
+ class: normalizeClass(unref(cls).e("tabs-list"))
121
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.sheetList, (sheet, index) => {
122
+ return openBlock(), createElementBlock("div", {
123
+ key: sheet,
124
+ class: normalizeClass([unref(cls).e("tab"), unref(bem).is("active", index === __props.activeIndex)]),
125
+ title: "右键重命名 / 删除",
126
+ onClick: ($event) => handleTabClick(index),
127
+ onContextmenu: withModifiers(($event) => handleTabContextMenu($event, sheet, index), ["prevent"])
128
+ }, [renamingIndex.value === index ? withDirectives((openBlock(), createElementBlock("input", {
129
+ key: 0,
130
+ ref_for: true,
131
+ ref_key: "renameInputRef",
132
+ ref: renameInputRef,
133
+ "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => renameDraft.value = $event),
134
+ class: normalizeClass(unref(cls).e("tab-rename-input")),
135
+ maxlength: 31,
136
+ onClick: _cache[2] || (_cache[2] = withModifiers(() => {}, ["stop"])),
137
+ onKeydown: [withKeys(withModifiers(commitRename, ["prevent"]), ["enter"]), withKeys(withModifiers(cancelRename, ["prevent"]), ["esc"])],
138
+ onBlur: commitRename
139
+ }, null, 42, _hoisted_3)), [[vModelText, renameDraft.value]]) : (openBlock(), createElementBlock("span", _hoisted_4, toDisplayString(sheet.name), 1))], 42, _hoisted_2);
140
+ }), 128))], 2)], 2),
141
+ withDirectives(createElementVNode("button", {
142
+ type: "button",
143
+ class: normalizeClass(unref(cls).e("tabs-nav")),
144
+ disabled: !unref(canNext),
145
+ title: "向右滚动",
146
+ onClick: _cache[3] || (_cache[3] = ($event) => unref(scrollByStep)(1))
147
+ }, [createVNode(unref(UIcon), null, {
148
+ default: withCtx(() => [createVNode(unref(ArrowRight))]),
149
+ _: 1
150
+ })], 10, _hoisted_5), [[vShow, unref(showNav)]]),
151
+ createElementVNode("button", {
152
+ type: "button",
153
+ class: normalizeClass(unref(cls).e("tab-add")),
154
+ title: "添加工作表",
155
+ onClick: handleAddSheet
156
+ }, " + ", 2)
157
+ ], 2);
158
+ };
159
+ }
160
+ });
161
+ //#endregion
162
+ export { _sfc_main as default };