@veltra/sheet 2.8.0 → 3.0.0

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 (67) hide show
  1. package/dist/components/sheet/formula-bar.js +1 -1
  2. package/dist/components/sheet/import-file.js +1 -1
  3. package/dist/components/sheet/insert-image.js +1 -1
  4. package/dist/components/sheet/popup-helpers.js +1 -1
  5. package/dist/components/sheet/popups/border-popup.js +1 -2
  6. package/dist/components/sheet/popups/functions-popup.js +3 -3
  7. package/dist/components/sheet/popups/import.worker.d.ts +1 -1
  8. package/dist/components/sheet/popups/import.worker.js +1 -1
  9. package/dist/components/sheet/sheet-context-menu.js +2 -2
  10. package/dist/components/sheet/sheet.js +12 -2
  11. package/dist/components/sheet/use-find-replace.js +1 -2
  12. package/dist/components/sheet/use-formula-suggest.js +5 -4
  13. package/dist/components/sheet/use-sheet-grid.js +4 -2
  14. package/dist/components/sheet/use-sheet-state.js +1 -1
  15. package/dist/components/sheet/use-tool-popup.js +13 -2
  16. package/dist/index.d.ts +2 -2
  17. package/dist/tools/builtin.js +1 -1
  18. package/dist/tools/context.d.ts +1 -11
  19. package/dist/tools/download.js +3 -3
  20. package/dist/tools/export.worker.d.ts +1 -1
  21. package/dist/tools/export.worker.js +1 -1
  22. package/dist/tools/registry.js +1 -1
  23. package/dist/types/sheet.d.ts +15 -7
  24. package/package.json +2 -3
  25. package/src/components/sheet/__test__/canvas-mock.ts +49 -0
  26. package/src/components/sheet/__test__/find-shortcut-anchor.test.ts +117 -0
  27. package/src/components/sheet/__test__/formula-bar.test.ts +11 -9
  28. package/src/components/sheet/__test__/formula-capability.test.ts +160 -0
  29. package/src/components/sheet/__test__/functions-popup.test.ts +7 -7
  30. package/src/components/sheet/__test__/grid-setup.ts +3 -0
  31. package/src/components/sheet/__test__/grid-test-utils.ts +47 -0
  32. package/src/components/sheet/__test__/import-file.test.ts +11 -7
  33. package/src/components/sheet/__test__/insert-image-popup.test.ts +1 -1
  34. package/src/components/sheet/__test__/insert-image.test.ts +1 -1
  35. package/src/components/sheet/__test__/sheet-component.test.ts +9 -19
  36. package/src/components/sheet/__test__/sheet-context-menu.test.ts +2 -2
  37. package/src/components/sheet/__test__/sheet-grow-on-scroll.test.ts +105 -0
  38. package/src/components/sheet/__test__/sheet-scrollbar.test.ts +137 -0
  39. package/src/components/sheet/__test__/use-formula-suggest.test.ts +1 -1
  40. package/src/components/sheet/formula-bar.vue +4 -4
  41. package/src/components/sheet/import-file.ts +3 -7
  42. package/src/components/sheet/insert-image.ts +2 -2
  43. package/src/components/sheet/popup-helpers.ts +3 -3
  44. package/src/components/sheet/popups/border-popup.vue +4 -4
  45. package/src/components/sheet/popups/export-popup.vue +1 -1
  46. package/src/components/sheet/popups/find-popup.vue +1 -1
  47. package/src/components/sheet/popups/functions-popup.vue +3 -9
  48. package/src/components/sheet/popups/import.worker.ts +3 -3
  49. package/src/components/sheet/sheet-context-menu.ts +4 -4
  50. package/src/components/sheet/sheet-tabs.vue +2 -2
  51. package/src/components/sheet/sheet.vue +12 -3
  52. package/src/components/sheet/use-find-replace.ts +3 -7
  53. package/src/components/sheet/use-formula-suggest.ts +6 -8
  54. package/src/components/sheet/use-sheet-grid.ts +6 -4
  55. package/src/components/sheet/use-sheet-state.ts +2 -2
  56. package/src/components/sheet/use-tool-popup.ts +22 -2
  57. package/src/index.ts +3 -1
  58. package/src/tools/__test__/apply-style.test.ts +2 -2
  59. package/src/tools/__test__/builtin.test.ts +4 -4
  60. package/src/tools/__test__/context.test.ts +2 -2
  61. package/src/tools/apply-style.ts +3 -3
  62. package/src/tools/builtin.ts +2 -2
  63. package/src/tools/context.ts +11 -11
  64. package/src/tools/download.ts +4 -4
  65. package/src/tools/export.worker.ts +4 -7
  66. package/src/tools/registry.ts +1 -1
  67. package/src/types/sheet.ts +19 -8
@@ -2,7 +2,7 @@ import _sfc_main$1 from "./formula-suggest-list.js";
2
2
  import { applySuggest, filterFormulaSuggestions, getSuggestContext, moveSuggestIndex } from "./use-formula-suggest.js";
3
3
  import _sfc_main$2 from "./popups/functions-popup.js";
4
4
  import { insertRefText, isRefSelectContext } from "./use-formula-ref-select.js";
5
- import { formatAddress, formatRange, parseRange } from "@veltra/sheet-core/core/address.js";
5
+ import { formatAddress, formatRange, parseRange } from "infinitable/sheet";
6
6
  import { computed, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, nextTick, normalizeClass, onBeforeUnmount, onMounted, openBlock, ref, shallowRef, unref, useTemplateRef, vModelText, watch, withCtx, withDirectives, withModifiers } from "vue";
7
7
  import { UDropdown, message } from "@veltra/desktop";
8
8
  import { bem } from "@veltra/utils";
@@ -1,6 +1,6 @@
1
+ import { importCsv, importXlsx, replaceWorkbookWithSnapshots } from "infinitable/sheet";
1
2
  import { message, messageConfirm } from "@veltra/desktop";
2
3
  import { nextFrame } from "@veltra/utils";
3
- import { importCsv, importXlsx, replaceWorkbookWithSnapshots } from "@veltra/sheet-core/core/io/import.js";
4
4
  //#region src/components/sheet/import-file.ts
5
5
  /** 导入文件 accept(工具栏直接选文件) */
6
6
  const IMPORT_FILE_ACCEPT = ".xlsx,.csv";
@@ -18,7 +18,7 @@ const EXT_TO_TYPE = {
18
18
  svg: "svg",
19
19
  webp: "webp"
20
20
  };
21
- /** MIME → sheet-core 图片类型(core/image 的 SheetImageType);MIME 为空时回落扩展名 */
21
+ /** MIME → 引擎图片类型(SheetImageType);MIME 为空时回落扩展名 */
22
22
  function resolveImageType(file) {
23
23
  const mime = file.type.trim().toLowerCase();
24
24
  if (mime && MIME_TO_TYPE[mime]) return MIME_TO_TYPE[mime];
@@ -1,4 +1,4 @@
1
- import { BORDER_STYLE_WIDTH } from "@veltra/sheet-core/core/style/types.js";
1
+ import { BORDER_STYLE_WIDTH } from "infinitable/sheet";
2
2
  //#region src/components/sheet/popup-helpers.ts
3
3
  /** 当前选区(ranges[0] 优先;无区域选区时用活动格单格) */
4
4
  function currentRange(context) {
@@ -1,10 +1,9 @@
1
1
  import { BORDER_LINE_STYLES, BORDER_LINE_TITLES, BORDER_PRESETS, currentRange, lineSwatchStyle } from "../popup-helpers.js";
2
2
  import _sfc_main$1 from "./border-preset-glyph.js";
3
+ import { BORDER_STYLE_WIDTH, buildBorderPresetItems } from "infinitable/sheet";
3
4
  import { Fragment, createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, normalizeStyle, openBlock, ref, renderList, unref } from "vue";
4
5
  import { UPalette } from "@veltra/desktop";
5
6
  import { bem } from "@veltra/utils";
6
- import { buildBorderPresetItems } from "@veltra/sheet-core/core/style/border-presets.js";
7
- import { BORDER_STYLE_WIDTH } from "@veltra/sheet-core/core/style/types.js";
8
7
  //#region src/components/sheet/popups/border-popup.vue
9
8
  const _hoisted_1 = ["title", "onClick"];
10
9
  const _hoisted_2 = ["title", "onClick"];
@@ -1,8 +1,8 @@
1
- import { COMMON_FORMULA_NAMES, FUNCTION_POPUP_CATEGORIES, formatFunctionSignature, moveSuggestIndex } from "../use-formula-suggest.js";
1
+ import { COMMON_FORMULA_NAMES, FUNCTION_POPUP_CATEGORIES, moveSuggestIndex } from "../use-formula-suggest.js";
2
2
  import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, normalizeClass, onMounted, openBlock, ref, renderList, toDisplayString, unref, useTemplateRef, watch, withCtx } from "vue";
3
3
  import { UInput, UScroll } from "@veltra/desktop";
4
4
  import { bem } from "@veltra/utils";
5
- import { listFormulaFunctions } from "@veltra/sheet-core/core/formula/functions.js";
5
+ import { listFormulaFunctions } from "infinitable";
6
6
  //#region src/components/sheet/popups/functions-popup.vue
7
7
  const _hoisted_1 = ["onClick"];
8
8
  const _hoisted_2 = ["onClick", "onMouseenter"];
@@ -99,7 +99,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
99
99
  class: normalizeClass([unref(cls).e("functions-item"), unref(bem).is("active", index === activeIndex.value)]),
100
100
  onClick: ($event) => emit("select", fn.name),
101
101
  onMouseenter: ($event) => activeIndex.value = index
102
- }, [createElementVNode("span", { class: normalizeClass(unref(cls).e("functions-signature")) }, toDisplayString(unref(formatFunctionSignature)(fn.name, fn.params)), 3), fn.description ? (openBlock(), createElementBlock("span", {
102
+ }, [createElementVNode("span", { class: normalizeClass(unref(cls).e("functions-signature")) }, toDisplayString(fn.signature), 3), fn.description ? (openBlock(), createElementBlock("span", {
103
103
  key: 0,
104
104
  class: normalizeClass(unref(cls).e("functions-description"))
105
105
  }, toDisplayString(fn.description), 3)) : createCommentVNode("v-if", true)], 42, _hoisted_2);
@@ -1,4 +1,4 @@
1
- import { SheetSnapshot } from "@veltra/sheet-core/core/sheet.js";
1
+ import { SheetSnapshot } from "infinitable/sheet";
2
2
  //#region src/components/sheet/popups/import.worker.d.ts
3
3
  export type ImportWorkerResponse = {
4
4
  type: 'progress';
@@ -3,7 +3,7 @@ self.onmessage = (e) => {
3
3
  const { buffer } = e.data;
4
4
  (async () => {
5
5
  try {
6
- const { importXlsx } = await import("@veltra/sheet-core/core/io/import.js");
6
+ const { importXlsx } = await import("infinitable/sheet");
7
7
  let lastPercent = -1;
8
8
  const wb = await importXlsx(new Uint8Array(buffer), (done, total) => {
9
9
  const percent = total > 0 ? Math.floor(done / total * 10) : 10;
@@ -1,7 +1,7 @@
1
1
  import { defaultToolRegistry } from "../../tools/registry.js";
2
2
  import { pickAndInsertImage } from "./insert-image.js";
3
3
  import _sfc_main from "./insert-count-menu-item.js";
4
- import { createRange } from "@veltra/sheet-core/core/address.js";
4
+ import { createRange } from "infinitable/sheet";
5
5
  import { defineComponent, h } from "vue";
6
6
  const INSERT_COUNT_MAX = 1e3;
7
7
  /** 行高/列宽数值项输入上限(下限复用门面 MIN_ROW_COL_SIZE,对齐引擎 resize 最小值) */
@@ -204,7 +204,7 @@ function buildColHeaderMenus(ctx, options) {
204
204
  }
205
205
  /**
206
206
  * 对当前选区应用 numFmt(经 applyStyle → 命令系统,天然可 undo/redo)。
207
- * 仅影响显示,单元格恒存原始值(见 sheet-core `core/format.ts`)。
207
+ * 仅影响显示,单元格恒存原始值(引擎 numFmt 显示约定)。
208
208
  */
209
209
  function applyNumFmtToSelection(ctx, numFmt) {
210
210
  for (const range of ctx.getSelection().ranges) ctx.applyStyle(range, { numFmt });
@@ -59,6 +59,10 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
59
59
  default: false
60
60
  },
61
61
  scrollbar: {
62
+ type: [Boolean, Object],
63
+ default: { mode: "native" }
64
+ },
65
+ growOnScroll: {
62
66
  type: Boolean,
63
67
  default: true
64
68
  }
@@ -70,6 +74,8 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
70
74
  const cls = bem("sheet");
71
75
  /** 根容器 ref:Ctrl/Cmd+F 判定焦点是否在本实例内(#6,避免劫持浏览器查找) */
72
76
  const rootRef = useTemplateRef("rootRef");
77
+ /** 工具栏容器 ref:工具栏隐藏(showToolbar=false)时查找弹层的回退锚点(0 高贴 sheet 顶) */
78
+ const toolbarWrapRef = useTemplateRef("toolbarWrapRef");
73
79
  const { workbook, activeIndex, sheetList, activeSheet, context, stateTick, syncFromWorkbook } = useSheetState(props, emit, {
74
80
  onBeforeSheetChange: () => closePopup(),
75
81
  rebuildGrid: () => rebuildGrid(),
@@ -77,7 +83,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
77
83
  pruneCache: (sheets) => pruneCache(sheets),
78
84
  syncAxisSizes: (axis, indexes) => getGrid()?.applyAxisSizes(axis, indexes)
79
85
  });
80
- const { popupTool, popupAnchor, closePopup, handleToolClick: openOrRunTool } = useToolPopup(context, rootRef);
86
+ const { popupTool, popupAnchor, closePopup, handleToolClick: openOrRunTool } = useToolPopup(context, rootRef, () => toolbarWrapRef.value ?? null);
81
87
  const parsing = ref(false);
82
88
  const parseProgress = ref({
83
89
  done: 0,
@@ -140,7 +146,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
140
146
  ref: rootRef,
141
147
  class: normalizeClass(unref(cls).b)
142
148
  }, [
143
- createElementVNode("div", { class: normalizeClass(unref(cls).e("toolbar-wrap")) }, [
149
+ createElementVNode("div", {
150
+ ref_key: "toolbarWrapRef",
151
+ ref: toolbarWrapRef,
152
+ class: normalizeClass(unref(cls).e("toolbar-wrap"))
153
+ }, [
144
154
  __props.showToolbar ? (openBlock(), createBlock(_sfc_main$11, {
145
155
  key: 0,
146
156
  groups: unref(toolGroups),
@@ -1,7 +1,6 @@
1
+ import { findAll, findNextFrom, findPrevFrom, inferCellType, normalizeInputValue } from "infinitable/sheet";
1
2
  import { computed, ref, shallowRef, watch } from "vue";
2
3
  import { debounce } from "@cat-kit/core";
3
- import { inferCellType, normalizeInputValue } from "@veltra/sheet-core/core/cell-store.js";
4
- import { findAll, findNextFrom, findPrevFrom } from "@veltra/sheet-core/core/find.js";
5
4
  //#region src/components/sheet/use-find-replace.ts
6
5
  /**
7
6
  * 查找条逻辑(关键词 / 上一个 / 下一个 / 命中计数 / 替换):
@@ -1,4 +1,4 @@
1
- import { listFormulaFunctions } from "@veltra/sheet-core/core/formula/functions.js";
1
+ import { listFormulaFunctions } from "infinitable";
2
2
  /**
3
3
  * 空前缀时优先展示的常用函数(其余按名称升序补齐至上限)。
4
4
  * 纯字典序会把 SUM / OR / ROUND 挤出前 10(ABS…NOT),导致输入 `=` 看不到 SUM。
@@ -18,7 +18,7 @@ const COMMON_FORMULA_NAMES = [
18
18
  /**
19
19
  * 函数弹框分类导航固定集合(顺序即展示顺序)。
20
20
  * 「常用」「全部」是导航概念(非注册表分类):常用 = COMMON_FORMULA_NAMES 固定清单,
21
- * 全部 = 全部已注册函数;其余与 sheet-core `FormulaFunctionCategory` 对应。
21
+ * 全部 = 全部已注册函数;其余与公式注册表 `FormulaFunctionCategory` 对应。
22
22
  */
23
23
  const FUNCTION_POPUP_CATEGORIES = [
24
24
  "常用",
@@ -115,11 +115,12 @@ function filterFormulaSuggestions(prefix) {
115
115
  return items;
116
116
  }
117
117
  function toSuggestItem(fn) {
118
+ const params = fn.params.map((p) => p.name);
118
119
  return {
119
120
  name: fn.name,
120
- params: fn.params,
121
+ params,
121
122
  description: fn.description,
122
- signature: formatFunctionSignature(fn.name, fn.params)
123
+ signature: formatFunctionSignature(fn.name, params)
123
124
  };
124
125
  }
125
126
  /** 将 [start, end) 的 token 替换为 `NAME()`,光标入括号内(免手动补右括号) */
@@ -1,7 +1,7 @@
1
1
  import { buildBodyMenus, buildColHeaderMenus, buildRowHeaderMenus, ensureContextMenuSelection, resolveRenderSize } from "./sheet-context-menu.js";
2
+ import { SheetGrid } from "infinitable/sheet";
2
3
  import { onBeforeUnmount, onMounted, watch } from "vue";
3
4
  import { contextmenu } from "@veltra/desktop";
4
- import { SheetGrid } from "@veltra/sheet-core/grid";
5
5
  //#region src/components/sheet/use-sheet-grid.ts
6
6
  /**
7
7
  * 缓存容量:最近 N 个 sheet 的 SheetGrid 实例常驻(tab 来回切换零重建)。
@@ -73,6 +73,7 @@ function useSheetGrid(options) {
73
73
  editors: props.editors,
74
74
  colResize: props.colResize,
75
75
  scrollbar: props.scrollbar,
76
+ growOnScroll: props.growOnScroll,
76
77
  onContextMenu: handleContextMenu,
77
78
  onEditStart: (addr) => formulaBarRef.value?.mirrorGridEdit(addr),
78
79
  onEditEnd: (addr) => formulaBarRef.value?.exitMirror(addr),
@@ -187,7 +188,8 @@ function useSheetGrid(options) {
187
188
  props.header,
188
189
  props.editors,
189
190
  props.colResize,
190
- props.scrollbar
191
+ props.scrollbar,
192
+ props.growOnScroll
191
193
  ], ([nextRows, nextCols]) => {
192
194
  const sheet = getActiveSheet();
193
195
  if (nextRows != null) sheet.ensureTableSize(nextRows, 0);
@@ -1,8 +1,8 @@
1
1
  import { defaultToolRegistry } from "../../tools/registry.js";
2
2
  import { createSheetContext } from "../../tools/context.js";
3
3
  import { resolveRenderSize } from "./sheet-context-menu.js";
4
+ import { Workbook } from "infinitable/sheet";
4
5
  import { computed, onBeforeUnmount, onMounted, ref, shallowRef, watch } from "vue";
5
- import { Workbook } from "@veltra/sheet-core/core/workbook.js";
6
6
  //#region src/components/sheet/use-sheet-state.ts
7
7
  /**
8
8
  * USheet 的状态源(单一数据源,其余状态由此推导):
@@ -20,7 +20,7 @@ function joinsTransaction(tool) {
20
20
  * 函数面板选中只进入公式栏编辑态(无直接模型写入),与查找 / 导出一样不参与事务。
21
21
  * 导入在 sheet.vue 特殊处理(直接系统文件选择),不经弹层。
22
22
  */
23
- function useToolPopup(context, rootEl) {
23
+ function useToolPopup(context, rootEl, getFallbackAnchor) {
24
24
  /** 当前打开的弹层工具(null = 未打开) */
25
25
  const popupTool = shallowRef(null);
26
26
  /** 触发按钮元素(打开时的 currentTarget;用于面板 left 对齐按钮) */
@@ -83,6 +83,17 @@ function useToolPopup(context, rootEl) {
83
83
  if (!popupTool.value) return;
84
84
  closePopup();
85
85
  }
86
+ /**
87
+ * Ctrl/Cmd+F 的锚点解析:优先工具栏「查找与替换」按钮(与按钮入口完全同锚点,
88
+ * 弹层同样锚定按钮下方);按钮不可达(showToolbar 隐藏)时回退 sheet 顶部容器。
89
+ */
90
+ function resolveFindAnchor() {
91
+ const root = rootEl.value;
92
+ if (!root) return null;
93
+ const button = root.querySelector("[data-tool-id=\"find\"]");
94
+ if (button) return button;
95
+ return getFallbackAnchor?.() ?? null;
96
+ }
86
97
  /** Ctrl/Cmd+F 打开 / 关闭查找条(与工具按钮同一 toggle 逻辑) */
87
98
  function onGlobalKeydown(event) {
88
99
  if (!(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== "f") return;
@@ -98,7 +109,7 @@ function useToolPopup(context, rootEl) {
98
109
  closePopup();
99
110
  return;
100
111
  }
101
- scheduleOpen(findTool);
112
+ scheduleOpen(findTool, resolveFindAnchor());
102
113
  }
103
114
  onMounted(() => {
104
115
  window.addEventListener("click", onWindowClick);
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import _sfc_main from "./components/sheet/sheet.js";
2
- import { SheetContext, createSheetContext } from "./tools/context.js";
2
+ import { SheetContext, SheetContextOptions, createSheetContext } from "./tools/context.js";
3
3
  import { SheetTool, SheetToolGroup, SheetToolPopupType, defaultToolRegistry, registerTool, unregisterTool } from "./tools/registry.js";
4
4
  import { SheetEmits, SheetExposed, SheetProps } from "./types/sheet.js";
5
5
  import "./types/index.js";
6
- export { type SheetContext, type SheetEmits, type SheetExposed, type SheetProps, type SheetTool, type SheetToolGroup, type SheetToolPopupType, _sfc_main as USheet, createSheetContext, defaultToolRegistry, registerTool, unregisterTool };
6
+ export { type SheetContext, type SheetContextOptions, type SheetEmits, type SheetExposed, type SheetProps, type SheetTool, type SheetToolGroup, type SheetToolPopupType, _sfc_main as USheet, createSheetContext, defaultToolRegistry, registerTool, unregisterTool };
@@ -1,7 +1,7 @@
1
1
  import { registerTool } from "./registry.js";
2
2
  import { FontColor } from "@veltra/icons/colorful";
3
3
  import { AlignBottom, AlignCenter, AlignTop, Bold, Border, Download, Fill, FontSize, Italic, MergeCells, PictureRounded, Rollback, Rollfront, Search, Strikethrough, Underline, UnmergeCells, Upload, Variable, VerticalAlignCenter, VerticalAlignLeft, VerticalAlignRight, Wrap } from "@veltra/icons/normal";
4
- import { rangesEqual } from "@veltra/sheet-core/core/address.js";
4
+ import { rangesEqual } from "infinitable/sheet";
5
5
  //#region src/tools/builtin.ts
6
6
  /** 取活动格所在首选区;无选区返回 null */
7
7
  function selectionRange(ctx) {
@@ -1,14 +1,4 @@
1
- import { CellAddress, CellRange } from "@veltra/sheet-core/core/address.js";
2
- import { CellStyle, CellStylePatch } from "@veltra/sheet-core/core/style/types.js";
3
- import { CellData, CellValue } from "@veltra/sheet-core/core/cell-store.js";
4
- import { Workbook } from "@veltra/sheet-core/core/workbook.js";
5
- import { FrozenState, Sheet } from "@veltra/sheet-core/core/sheet.js";
6
- import { HistoryState } from "@veltra/sheet-core/core/command/history.js";
7
- import { ImageUpdateFields } from "@veltra/sheet-core/core/command/image.js";
8
- import { SetCellValueItem } from "@veltra/sheet-core/core/command/set-cell-value.js";
9
- import { ImageInput, SheetImage } from "@veltra/sheet-core/core/image.js";
10
- import { CellInfo } from "@veltra/sheet-core/core/merge-manager.js";
11
- import { SelectionState } from "@veltra/sheet-core/core/selection.js";
1
+ import { CellAddress, CellData, CellInfo, CellRange, CellStyle, CellStylePatch, CellValue, FrozenState, HistoryState, ImageInput, ImageUpdateFields, SelectionState, SetCellValueItem, Sheet, SheetImage, Workbook } from "infinitable/sheet";
12
2
  //#region src/tools/context.d.ts
13
3
  /**
14
4
  * SheetContext:工具的唯一操作门面。
@@ -1,8 +1,8 @@
1
+ import { exportSheetCsv, exportWorkbookXlsx } from "infinitable/sheet";
1
2
  import { saveBlob } from "@cat-kit/fe";
2
- import { exportSheetCsv, exportWorkbookXlsx } from "@veltra/sheet-core/core/io/export.js";
3
3
  //#region src/tools/download.ts
4
4
  /**
5
- * 大工作簿的 XLSX 导出(sheet-core 序列化 + ZIP 压缩为秒级同步重活)移入 Web Worker:
5
+ * 大工作簿的 XLSX 导出(引擎序列化 + ZIP 压缩为秒级同步重活)移入 Web Worker:
6
6
  * 主线程只采集快照(结构化克隆友好的纯数据,行高随 SheetSnapshot.rowHeights
7
7
  * 携带),序列化与压缩在 worker 线程进行。worker 不可用(极端环境 / 打包产物
8
8
  * 缺文件)回退主线程导出——与导入侧 parseXlsxAsync 同一模式。
@@ -48,7 +48,7 @@ async function exportWorkbookXlsxAsync(workbook) {
48
48
  /**
49
49
  * 导出当前工作簿为 .xlsx(无 workbook 时空操作)。
50
50
  * 下载动作复用 @cat-kit/fe 的 saveBlob(#28:不再自研 Object URL + a.click)。
51
- * 导出侧 sheet-core 的 writeXlsx 会校验 sheet 名(Excel 非法字符 / 超长 / 保留名)并抛
51
+ * 导出侧引擎的 writeXlsx 会校验 sheet 名(Excel 非法字符 / 超长 / 保留名)并抛
52
52
  * InvalidArgumentError——失败经返回的 Promise 传播,调用方(导出面板)负责提示。
53
53
  */
54
54
  async function exportWorkbookFile(ctx) {
@@ -1,4 +1,4 @@
1
- import { SheetSnapshot } from "@veltra/sheet-core/core/sheet.js";
1
+ import { SheetSnapshot } from "infinitable/sheet";
2
2
  //#region src/tools/export.worker.d.ts
3
3
  export interface ExportWorkerPayload {
4
4
  sheets: {
@@ -1,6 +1,6 @@
1
1
  //#region src/tools/export.worker.ts
2
2
  self.onmessage = (e) => {
3
- Promise.all([import("@veltra/sheet-core/core/io/export.js"), import("@veltra/sheet-core/core/workbook.js")]).then(([{ exportWorkbookXlsx }, { Workbook }]) => {
3
+ Promise.all([import("infinitable/sheet"), import("infinitable/sheet")]).then(([{ exportWorkbookXlsx }, { Workbook }]) => {
4
4
  const wb = new Workbook();
5
5
  const sheets = e.data.sheets;
6
6
  for (let i = 0; i < sheets.length; i++) {
@@ -1,4 +1,4 @@
1
- import { TypedEventEmitter } from "@veltra/sheet-core/core/events.js";
1
+ import { TypedEventEmitter } from "infinitable/sheet";
2
2
  //#region src/tools/registry.ts
3
3
  /** 缺省分组名 */
4
4
  const DEFAULT_TOOL_GROUP = "default";
@@ -1,9 +1,7 @@
1
1
  import { SheetContext } from "../tools/context.js";
2
+ import { ResolveCellRenderer, ResolveCellStyleHook, ResolveDisplayValue, ScrollbarOptions, Sheet, SheetGrid, SheetGridEditorsOptions, SheetGridHeaderOptions, Workbook } from "infinitable/sheet";
2
3
  import { ComputedRef } from "vue";
3
4
  import { DeconstructValue } from "@veltra/utils";
4
- import { ResolveCellRenderer, ResolveCellStyleHook, ResolveDisplayValue, SheetGrid, SheetGridEditorsOptions, SheetGridHeaderOptions } from "@veltra/sheet-core/grid";
5
- import { Workbook } from "@veltra/sheet-core/core/workbook.js";
6
- import { Sheet } from "@veltra/sheet-core/core/sheet.js";
7
5
  //#region src/types/sheet.d.ts
8
6
  /** 电子表格组件属性 */
9
7
  export interface SheetProps {
@@ -23,7 +21,7 @@ export interface SheetProps {
23
21
  resolveCellStyle?: ResolveCellStyleHook;
24
22
  /**
25
23
  * 动态单元格渲染(ADR-0004):视口布局时按格自定义渲染形态(返回引擎
26
- * `CellRenderer`,见 `@veltra/sheet-core/grid`),返回 undefined 回落默认渲染;
24
+ * `CellRenderer`,见 `infinitable/sheet`),返回 undefined 回落默认渲染;
27
25
  * 不写模型、不进快照
28
26
  */
29
27
  resolveCellRenderer?: ResolveCellRenderer;
@@ -55,10 +53,20 @@ export interface SheetProps {
55
53
  */
56
54
  colResize?: boolean;
57
55
  /**
58
- * 画布滚动条(透传 SheetGrid → 引擎内建):内容溢出的轴在画布右/下缘绘制滚动条
59
- * (可拖拽 / 点按跳转);缺省 true。变化触发网格重建(构造期选项)
56
+ * 滚动条(透传 SheetGrid → 引擎内建,构造期选项):false 整体关闭(不显示任何
57
+ * 滚动条);缺省原生档 `{ mode: 'native' }`——浏览器原生滚动条在独立 gutter
58
+ * 渲染、不遮挡最底行/最右列;true 与对象形态按引擎语义透传(对象可配
59
+ * `mode: 'canvas'` 回画布悬浮滚动条,及 visibility / hideDelay / reserve 等
60
+ * canvas 档显示策略)。对象形态引用更替触发网格重建——沿用 header / editors
61
+ * 的稳定引用约定,宿主勿在模板内联对象字面量
60
62
  */
61
- scrollbar?: boolean;
63
+ scrollbar?: boolean | ScrollbarOptions;
64
+ /**
65
+ * 滚动近端动态增长(透传 SheetGrid,构造期选项):滚动缓冲末端触到当前行列数
66
+ * 时模型与引擎同步扩容(引擎缺省 true);false 为固定尺寸网格——内容定尺寸的
67
+ * 报表 / 表单宿主置 false 后不再滚入空白增长区。变化触发网格重建
68
+ */
69
+ growOnScroll?: boolean;
62
70
  }
63
71
  export interface SheetEmits {
64
72
  /** 激活 sheet 切换(点击 tab 或宿主调用 workbook.activateSheet) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veltra/sheet",
3
- "version": "2.8.0",
3
+ "version": "3.0.0",
4
4
  "homepage": "https://github.com/cabinet-fe/ultra-ui#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/cabinet-fe/ultra-ui/issues"
@@ -34,14 +34,13 @@
34
34
  }
35
35
  },
36
36
  "dependencies": {
37
- "infinitable": "^0.1.2"
37
+ "infinitable": "^0.1.4"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@cat-kit/core": ">=1.2.1",
41
41
  "@cat-kit/fe": ">=1.2.1",
42
42
  "@veltra/desktop": "^1.7.14",
43
43
  "@veltra/icons": "^1.5.0",
44
- "@veltra/sheet-core": "^2.5.7",
45
44
  "@veltra/styles": "^1.7.14",
46
45
  "@veltra/utils": "^1.7.14",
47
46
  "vue": ">=3.5.43"
@@ -0,0 +1,49 @@
1
+ /**
2
+ * happy-dom 不实现 canvas 2d 上下文(getContext 返回 null),
3
+ * 这里用 Proxy 提供最小可用的 mock,让引擎渲染宿主能在无头环境
4
+ * 完成挂载与文本测量调用。仅用于测试(vp test setupFiles)。
5
+ */
6
+
7
+ type Context2dMock = Record<string | symbol, unknown>
8
+
9
+ function createContext2dMock(canvas: HTMLCanvasElement): Context2dMock {
10
+ const store: Context2dMock = { canvas }
11
+
12
+ return new Proxy(store, {
13
+ get(target, prop) {
14
+ if (prop in target) return target[prop]
15
+ if (prop === 'measureText') return (text: unknown) => ({ width: String(text).length * 8 })
16
+ if (prop === 'getImageData')
17
+ return () => ({ data: new Uint8ClampedArray(0), width: 0, height: 0 })
18
+ if (
19
+ prop === 'createLinearGradient' ||
20
+ prop === 'createRadialGradient' ||
21
+ prop === 'createPattern'
22
+ ) {
23
+ return () => ({ addColorStop: () => {} })
24
+ }
25
+ if (prop === 'getLineDash') return () => []
26
+ // 其余一律视为无操作绘制方法
27
+ return () => {}
28
+ },
29
+ set(target, prop, value) {
30
+ target[prop] = value
31
+ return true
32
+ }
33
+ })
34
+ }
35
+
36
+ /** 补丁 HTMLCanvasElement,使 getContext('2d') 返回可用 mock */
37
+ export function setupCanvasMock(): void {
38
+ const getContext = function (this: HTMLCanvasElement, contextId: string): unknown {
39
+ if (contextId !== '2d') return null
40
+ return createContext2dMock(this)
41
+ }
42
+
43
+ HTMLCanvasElement.prototype.getContext =
44
+ getContext as unknown as typeof HTMLCanvasElement.prototype.getContext
45
+
46
+ if (typeof HTMLCanvasElement.prototype.toDataURL !== 'function') {
47
+ HTMLCanvasElement.prototype.toDataURL = () => 'data:,'
48
+ }
49
+ }
@@ -0,0 +1,117 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vite-plus/test'
2
+ import { createApp, h, nextTick, type App } from 'vue'
3
+
4
+ import { USheet } from '../../../index'
5
+
6
+ /** UDropdownStub 的 open 调用 trigger 记录(vi.mock 工厂提升,需 hoisted) */
7
+ const dropdownState = vi.hoisted(() => ({
8
+ openTriggers: [] as Array<HTMLElement | null | undefined>
9
+ }))
10
+
11
+ // 只替换 UDropdown 为记录 open 传参的桩(断言锚定 reference),其余 desktop 组件保持真实实现
12
+ vi.mock('@veltra/desktop', async (importActual) => {
13
+ const actual = await importActual<Record<string, unknown>>()
14
+ const { defineComponent, h } = await import('vue')
15
+ return {
16
+ ...actual,
17
+ UDropdown: defineComponent({
18
+ name: 'UDropdownStub',
19
+ props: ['visible', 'trigger', 'width'],
20
+ setup(props, { expose, slots }) {
21
+ expose({
22
+ open(config?: { trigger?: HTMLElement }) {
23
+ dropdownState.openTriggers.push(config?.trigger ?? null)
24
+ },
25
+ close() {}
26
+ })
27
+ return () => h('div', { class: 'dropdown-stub' }, props.visible ? slots.content?.() : null)
28
+ }
29
+ })
30
+ }
31
+ })
32
+
33
+ const apps: App[] = []
34
+ const containers: HTMLElement[] = []
35
+
36
+ function mount(props: () => Record<string, unknown> = () => ({})) {
37
+ const el = document.createElement('div')
38
+ el.style.width = '800px'
39
+ el.style.height = '600px'
40
+ document.body.appendChild(el)
41
+ containers.push(el)
42
+ const app = createApp({ render: () => h(USheet, props()) })
43
+ app.mount(el)
44
+ apps.push(app)
45
+ return { el }
46
+ }
47
+
48
+ /** 等待 scheduleOpen 的 setTimeout(0) 宏任务(避免同次事件冒泡误关)+ 渲染 */
49
+ async function flushPopup(): Promise<void> {
50
+ await nextTick()
51
+ await new Promise((resolve) => setTimeout(resolve, 0))
52
+ await nextTick()
53
+ }
54
+
55
+ /** 聚焦 grid(事件源在实例容器内)派发 Ctrl+F,冒泡到 window 的 onGlobalKeydown */
56
+ function pressCtrlF(el: HTMLElement): KeyboardEvent {
57
+ const grid = el.querySelector<HTMLElement>('.u-sheet__grid')!
58
+ const event = new KeyboardEvent('keydown', {
59
+ key: 'f',
60
+ ctrlKey: true,
61
+ bubbles: true,
62
+ cancelable: true
63
+ })
64
+ grid.dispatchEvent(event)
65
+ return event
66
+ }
67
+
68
+ function lastOpenTrigger(): HTMLElement | null | undefined {
69
+ return dropdownState.openTriggers[dropdownState.openTriggers.length - 1]
70
+ }
71
+
72
+ afterEach(() => {
73
+ while (apps.length) apps.pop()!.unmount()
74
+ while (containers.length) containers.pop()!.remove()
75
+ dropdownState.openTriggers.length = 0
76
+ })
77
+
78
+ describe('Ctrl+F 查找弹窗锚定', () => {
79
+ it('工具栏可见:聚焦 grid 按 Ctrl+F 打开,锚定「查找与替换」按钮(与按钮入口同锚点);再按关闭', async () => {
80
+ const { el } = mount()
81
+ await nextTick()
82
+
83
+ const event = pressCtrlF(el)
84
+ await flushPopup()
85
+ // 拦截浏览器原生查找 + 查找弹层打开
86
+ expect(event.defaultPrevented).toBe(true)
87
+ expect(el.querySelector('.u-sheet__find-input')).not.toBeNull()
88
+ // 锚定 reference 非空且指向工具栏 find 按钮(HTMLElement,与按钮入口完全同锚点)
89
+ const findButton = el.querySelector<HTMLElement>('[data-tool-id="find"]')!
90
+ expect(findButton).not.toBeNull()
91
+ expect(lastOpenTrigger()).toBe(findButton)
92
+
93
+ // 再按一次 Ctrl+F:toggle 关闭(不回归)
94
+ pressCtrlF(el)
95
+ await nextTick()
96
+ expect(el.querySelector('.u-sheet__find-input')).toBeNull()
97
+ })
98
+
99
+ it('工具栏隐藏(showToolbar=false):Ctrl+F 锚定回退 sheet 顶部容器(锚点非空且在 sheet 根内)', async () => {
100
+ const { el } = mount(() => ({ showToolbar: false }))
101
+ await nextTick()
102
+
103
+ pressCtrlF(el)
104
+ await flushPopup()
105
+ expect(el.querySelector('.u-sheet__find-input')).not.toBeNull()
106
+ // 锚定 reference 非空、指向 sheet 根内元素:工具栏容器(0 高贴 sheet 顶,
107
+ // bottom-start 定位下弹层自 sheet 左上角展开,落在 sheet 视口内而非视口原点)
108
+ const anchor = lastOpenTrigger()
109
+ expect(anchor).toBeInstanceOf(HTMLElement)
110
+ expect(el.contains(anchor as HTMLElement)).toBe(true)
111
+ expect((anchor as HTMLElement).classList.contains('u-sheet__toolbar-wrap')).toBe(true)
112
+
113
+ pressCtrlF(el)
114
+ await nextTick()
115
+ expect(el.querySelector('.u-sheet__find-input')).toBeNull()
116
+ })
117
+ })
@@ -1,6 +1,6 @@
1
- import { parseRange } from '@veltra/sheet-core/core/address.js'
2
- import { Workbook } from '@veltra/sheet-core/core/workbook.js'
3
- import type { SheetGrid } from '@veltra/sheet-core/grid'
1
+ import { parseRange } from 'infinitable/sheet'
2
+ import { Workbook } from 'infinitable/sheet'
3
+ import type { SheetGrid } from 'infinitable/sheet'
4
4
  import { afterEach, describe, expect, it } from 'vite-plus/test'
5
5
  import { createApp, h, nextTick, type App } from 'vue'
6
6
 
@@ -8,6 +8,7 @@ import { USheet } from '../../../index'
8
8
  import { createSheetContext } from '../../../tools/context'
9
9
  import type { SheetExposed } from '../../../types'
10
10
  import UFormulaBar from '../formula-bar.vue'
11
+ import { gridEventTarget } from './grid-test-utils'
11
12
 
12
13
  const apps: App[] = []
13
14
  const containers: HTMLElement[] = []
@@ -551,11 +552,12 @@ describe('USheet 公式栏:函数补全与引用选择', () => {
551
552
  await nextTick()
552
553
 
553
554
  // 画布数据格点选(引擎选区事件管线)→ 引用插入 + blur 挂起(引用选择);
554
- // 引用拾取为手势收敛语义(按下起手记录、抬手一次回交,见 sheet-core
555
- // grid-selection),故插入断言在 pointerup 之后。pointerdown 监听绑定在
556
- // 实例容器(LRU 缓存)。画布几何口径与 sheet-core grid-theme 一致
557
- // (行号列 46、列头 28、列宽 80、行高 28)→ (51, 117) 命中 A4
558
- const gridEl = el.querySelector('.u-sheet__grid-instance')!
555
+ // 引用拾取为手势收敛语义(按下起手记录、抬手一次回交,见引擎
556
+ // grid-selection),故插入断言在 pointerup 之后。指针事件派发到 body 层
557
+ // 画布(canvas 档与原生档的事件源后代,见 gridEventTarget)。画布几何口径
558
+ // 与引擎 grid-theme 一致(行号列 46、列头 28、列宽 80、行高 28)→
559
+ // (51, 117) 命中 A4
560
+ const gridEl = gridEventTarget(el)
559
561
  gridEl.dispatchEvent(
560
562
  new PointerEvent('pointerdown', { clientX: 51, clientY: 117, bubbles: true })
561
563
  )
@@ -576,7 +578,7 @@ describe('USheet 公式栏:函数补全与引用选择', () => {
576
578
  expect(sheet.getCellData({ row: 2, col: 0 })).toBeUndefined()
577
579
  expect(input.value).toBe('=SUM(A4')
578
580
 
579
- // 网格选区拦截 → handleRefSelect 路径(引擎选区事件管线,见 sheet-core grid-selection)
581
+ // 网格选区拦截 → handleRefSelect 路径(引擎选区事件管线,见引擎 grid-selection)
580
582
  const { el: barEl, exposed: barExposed } = mountFormulaBar(sheet)
581
583
  await nextTick()
582
584
  setFxText(barEl, '=SUM(')