@veltra/sheet 2.5.6 → 2.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/sheet/formula-bar.js +115 -15
- package/dist/components/sheet/import-file.js +1 -1
- package/dist/components/sheet/popup-helpers.js +1 -1
- package/dist/components/sheet/popups/border-popup.js +2 -2
- package/dist/components/sheet/popups/functions-popup.js +117 -0
- package/dist/components/sheet/popups/import.worker.d.ts +3 -4
- package/dist/components/sheet/popups/import.worker.js +1 -1
- package/dist/components/sheet/sheet-context-menu.js +1 -1
- package/dist/components/sheet/sheet.js +33 -23
- package/dist/components/sheet/style2.css +100 -0
- package/dist/components/sheet/use-find-replace.js +2 -2
- package/dist/components/sheet/use-formula-suggest.js +18 -2
- package/dist/components/sheet/use-sheet-grid.js +1 -1
- package/dist/components/sheet/use-sheet-state.js +1 -1
- package/dist/components/sheet/use-tool-popup.js +4 -2
- package/dist/tools/builtin.js +12 -2
- package/dist/tools/context.d.ts +15 -16
- package/dist/tools/download.js +1 -1
- package/dist/tools/export.worker.d.ts +4 -5
- package/dist/tools/export.worker.js +1 -1
- package/dist/tools/registry.d.ts +8 -9
- package/dist/tools/registry.js +1 -1
- package/dist/types/sheet.d.ts +7 -8
- package/package.json +5 -5
- package/src/components/sheet/__test__/formula-bar.test.ts +314 -2
- package/src/components/sheet/__test__/functions-popup.test.ts +182 -0
- package/src/components/sheet/__test__/import-file.test.ts +3 -3
- package/src/components/sheet/__test__/insert-image-popup.test.ts +1 -1
- package/src/components/sheet/__test__/insert-image.test.ts +1 -1
- package/src/components/sheet/__test__/sheet-component.test.ts +2 -1
- package/src/components/sheet/__test__/sheet-context-menu.test.ts +2 -2
- package/src/components/sheet/__test__/use-formula-suggest.test.ts +5 -4
- package/src/components/sheet/formula-bar.vue +134 -9
- package/src/components/sheet/import-file.ts +3 -3
- package/src/components/sheet/insert-image.ts +1 -1
- package/src/components/sheet/popup-helpers.ts +3 -3
- package/src/components/sheet/popups/border-popup.vue +3 -3
- package/src/components/sheet/popups/find-popup.vue +1 -1
- package/src/components/sheet/popups/functions-popup.vue +141 -0
- package/src/components/sheet/popups/import.worker.ts +2 -2
- package/src/components/sheet/sheet-context-menu.ts +2 -2
- package/src/components/sheet/sheet-tabs.vue +2 -2
- package/src/components/sheet/sheet.vue +15 -2
- package/src/components/sheet/style.scss +117 -0
- package/src/components/sheet/use-find-replace.ts +3 -3
- package/src/components/sheet/use-formula-suggest.ts +20 -1
- package/src/components/sheet/use-sheet-grid.ts +12 -4
- package/src/components/sheet/use-sheet-state.ts +2 -2
- package/src/components/sheet/use-tool-popup.ts +4 -2
- package/src/tools/__test__/apply-style.test.ts +2 -2
- package/src/tools/__test__/builtin.test.ts +12 -4
- package/src/tools/__test__/context.test.ts +2 -2
- package/src/tools/apply-style.ts +3 -3
- package/src/tools/builtin.ts +15 -3
- package/src/tools/context.ts +11 -11
- package/src/tools/download.ts +2 -2
- package/src/tools/export.worker.ts +3 -3
- package/src/tools/registry.ts +2 -1
- package/src/types/sheet.ts +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { listFormulaFunctions } from "@veltra/sheet-core/core/formula/functions";
|
|
1
|
+
import { listFormulaFunctions } from "@veltra/sheet-core/core/formula/functions.js";
|
|
2
2
|
/**
|
|
3
3
|
* 空前缀时优先展示的常用函数(其余按名称升序补齐至上限)。
|
|
4
4
|
* 纯字典序会把 SUM / OR / ROUND 挤出前 10(ABS…NOT),导致输入 `=` 看不到 SUM。
|
|
@@ -15,6 +15,22 @@ const COMMON_FORMULA_NAMES = [
|
|
|
15
15
|
"AND",
|
|
16
16
|
"OR"
|
|
17
17
|
];
|
|
18
|
+
/**
|
|
19
|
+
* 函数弹框分类导航固定集合(顺序即展示顺序)。
|
|
20
|
+
* 「常用」「全部」是导航概念(非注册表分类):常用 = COMMON_FORMULA_NAMES 固定清单,
|
|
21
|
+
* 全部 = 全部已注册函数;其余与 sheet-core `FormulaFunctionCategory` 对应。
|
|
22
|
+
*/
|
|
23
|
+
const FUNCTION_POPUP_CATEGORIES = [
|
|
24
|
+
"常用",
|
|
25
|
+
"全部",
|
|
26
|
+
"财务",
|
|
27
|
+
"日期与时间",
|
|
28
|
+
"数学",
|
|
29
|
+
"统计",
|
|
30
|
+
"查找与引用",
|
|
31
|
+
"文本",
|
|
32
|
+
"逻辑"
|
|
33
|
+
];
|
|
18
34
|
/** 函数名 token 前合法的「触发」字符(= / 运算符 / ( / ,) */
|
|
19
35
|
const SUGGEST_TRIGGER_CHARS = /* @__PURE__ */ new Set([
|
|
20
36
|
"=",
|
|
@@ -123,4 +139,4 @@ function moveSuggestIndex(activeIndex, length, delta) {
|
|
|
123
139
|
return ((activeIndex % length + length) % length + delta + length) % length;
|
|
124
140
|
}
|
|
125
141
|
//#endregion
|
|
126
|
-
export { COMMON_FORMULA_NAMES, applySuggest, filterFormulaSuggestions, formatFunctionSignature, getSuggestContext, moveSuggestIndex };
|
|
142
|
+
export { COMMON_FORMULA_NAMES, FUNCTION_POPUP_CATEGORIES, applySuggest, filterFormulaSuggestions, formatFunctionSignature, getSuggestContext, moveSuggestIndex };
|
|
@@ -72,7 +72,7 @@ function useSheetGrid(options) {
|
|
|
72
72
|
onContextMenu: handleContextMenu,
|
|
73
73
|
onEditStart: (addr) => formulaBarRef.value?.mirrorGridEdit(addr),
|
|
74
74
|
onEditEnd: (addr) => formulaBarRef.value?.exitMirror(addr),
|
|
75
|
-
interceptSelection: () => formulaBarRef.value?.isRefSelecting() ?? false,
|
|
75
|
+
interceptSelection: () => (formulaBarRef.value?.isRefSelecting() ?? false) || (formulaBarRef.value?.isFunctionPopupOpen() ?? false) || (options.isFunctionPopupOpen?.() ?? false),
|
|
76
76
|
onSelectionIntercept: (range) => formulaBarRef.value?.handleRefSelect(range)
|
|
77
77
|
});
|
|
78
78
|
let dirty = false;
|
|
@@ -2,7 +2,7 @@ import { defaultToolRegistry } from "../../tools/registry.js";
|
|
|
2
2
|
import { createSheetContext } from "../../tools/context.js";
|
|
3
3
|
import { resolveRenderSize } from "./sheet-context-menu.js";
|
|
4
4
|
import { computed, onBeforeUnmount, onMounted, ref, shallowRef, watch } from "vue";
|
|
5
|
-
import { Workbook } from "@veltra/sheet-core/core/workbook";
|
|
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 的状态源(单一数据源,其余状态由此推导):
|
|
@@ -4,18 +4,20 @@ import { onBeforeUnmount, onMounted, shallowRef } from "vue";
|
|
|
4
4
|
/**
|
|
5
5
|
* 面板打开期间的写入是否合并为一个 undo 单元(关闭时提交):
|
|
6
6
|
* 填充颜色 / 边框 / 字体颜色 / 字号参与事务;查找(每次替换独立 undo)、
|
|
7
|
-
*
|
|
7
|
+
* 函数(选中仅进入公式栏编辑态,模型写入待 fx 提交)、导出(下载侧效应,
|
|
8
|
+
* 无模型写入)不参与。导入无弹层,不经此路径。
|
|
8
9
|
*/
|
|
9
10
|
function joinsTransaction(tool) {
|
|
10
11
|
return tool.popup === "fill-color" || tool.popup === "border" || tool.popup === "font-color" || tool.popup === "font-size";
|
|
11
12
|
}
|
|
12
13
|
/**
|
|
13
|
-
* 弹层型工具编排(填充颜色 / 边框 / 查找 / 插入图片 / 导出):
|
|
14
|
+
* 弹层型工具编排(填充颜色 / 边框 / 查找 / 函数 / 插入图片 / 导出):
|
|
14
15
|
* - popupTool:当前打开的弹层工具(null = 未打开)
|
|
15
16
|
* - popupAnchor:触发按钮元素(面板定位参照,见 sheet.vue 的 left 计算)
|
|
16
17
|
* - 打开 / 关闭时的事务包裹(面板期间写入合并为一个 undo 单元,关闭时提交)
|
|
17
18
|
* - 点击面板外关闭(面板内 @click.stop 不冒泡到 window)
|
|
18
19
|
* - Ctrl/Cmd+F 开合查找条(与工具按钮同一 toggle 逻辑)
|
|
20
|
+
* 函数面板选中只进入公式栏编辑态(无直接模型写入),与查找 / 导出一样不参与事务。
|
|
19
21
|
* 导入在 sheet.vue 特殊处理(直接系统文件选择),不经弹层。
|
|
20
22
|
*/
|
|
21
23
|
function useToolPopup(context, rootEl) {
|
package/dist/tools/builtin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { registerTool } from "./registry.js";
|
|
2
2
|
import { FontColor } from "@veltra/icons/colorful";
|
|
3
|
-
import { AlignBottom, AlignCenter, AlignTop, Bold, Border, Download, Fill, FontSize, Italic, MergeCells, PictureRounded, Rollback, Rollfront, Search, Strikethrough, Underline, UnmergeCells, Upload, VerticalAlignCenter, VerticalAlignLeft, VerticalAlignRight, Wrap } from "@veltra/icons/normal";
|
|
4
|
-
import { rangesEqual } from "@veltra/sheet-core/core/address";
|
|
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";
|
|
5
5
|
//#region src/tools/builtin.ts
|
|
6
6
|
/** 取活动格所在首选区;无选区返回 null */
|
|
7
7
|
function selectionRange(ctx) {
|
|
@@ -310,6 +310,16 @@ registerTool({
|
|
|
310
310
|
popup: "find",
|
|
311
311
|
onClick: () => {}
|
|
312
312
|
});
|
|
313
|
+
registerTool({
|
|
314
|
+
id: "functions",
|
|
315
|
+
title: "函数",
|
|
316
|
+
icon: Variable,
|
|
317
|
+
tooltip: "按分类浏览并插入函数",
|
|
318
|
+
group: "edit",
|
|
319
|
+
order: 1,
|
|
320
|
+
popup: "functions",
|
|
321
|
+
onClick: () => {}
|
|
322
|
+
});
|
|
313
323
|
registerTool({
|
|
314
324
|
id: "insert-image",
|
|
315
325
|
title: "插入图片",
|
package/dist/tools/context.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { CellAddress, CellRange } from "@veltra/sheet-core/core/address";
|
|
2
|
-
import { CellStyle, CellStylePatch } from "@veltra/sheet-core/core/style/types";
|
|
3
|
-
import { CellData, CellValue } from "@veltra/sheet-core/core/cell-store";
|
|
4
|
-
import { Workbook } from "@veltra/sheet-core/core/workbook";
|
|
5
|
-
import { FrozenState, Sheet } from "@veltra/sheet-core/core/sheet";
|
|
6
|
-
import { HistoryState } from "@veltra/sheet-core/core/command/history";
|
|
7
|
-
import { ImageUpdateFields } from "@veltra/sheet-core/core/command/image";
|
|
8
|
-
import { SetCellValueItem } from "@veltra/sheet-core/core/command/set-cell-value";
|
|
9
|
-
import { ImageInput, SheetImage } from "@veltra/sheet-core/core/image";
|
|
10
|
-
import { CellInfo } from "@veltra/sheet-core/core/merge-manager";
|
|
11
|
-
import { SelectionState } from "@veltra/sheet-core/core/selection";
|
|
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";
|
|
12
12
|
//#region src/tools/context.d.ts
|
|
13
13
|
/**
|
|
14
14
|
* SheetContext:工具的唯一操作门面。
|
|
@@ -19,7 +19,7 @@ import { SelectionState } from "@veltra/sheet-core/core/selection";
|
|
|
19
19
|
* - 不暴露 Sheet 实例本身,保证门面就是扩展能力的全部边界
|
|
20
20
|
* - USheet 切换 sheet tab 后,同一上下文自动指向新的活动 sheet
|
|
21
21
|
*/
|
|
22
|
-
interface SheetContext {
|
|
22
|
+
export interface SheetContext {
|
|
23
23
|
/** 当前活动 sheet 名 */
|
|
24
24
|
readonly sheetName: string;
|
|
25
25
|
/**
|
|
@@ -108,7 +108,7 @@ interface SheetContext {
|
|
|
108
108
|
}) => void): () => void;
|
|
109
109
|
}
|
|
110
110
|
/** createSheetContext 可选配置 */
|
|
111
|
-
interface SheetContextOptions {
|
|
111
|
+
export interface SheetContextOptions {
|
|
112
112
|
/**
|
|
113
113
|
* 当前渲染网格尺寸(与 SheetGrid / resolveRenderSize 一致)。
|
|
114
114
|
* applyStyle / clearStyle 用其判定整行/整列;缺省时不走行列默认样式。
|
|
@@ -128,6 +128,5 @@ interface SheetContextOptions {
|
|
|
128
128
|
* workbook prop 后导出工具仍导出旧工作簿)
|
|
129
129
|
* @param options.resolveGridSize 渲染尺寸解析器(整行/整列样式路由)
|
|
130
130
|
*/
|
|
131
|
-
declare function createSheetContext(resolveSheet: Sheet | (() => Sheet), workbook?: Workbook | (() => Workbook), options?: SheetContextOptions): SheetContext;
|
|
132
|
-
//#endregion
|
|
133
|
-
export { SheetContext, SheetContextOptions, createSheetContext };
|
|
131
|
+
export declare function createSheetContext(resolveSheet: Sheet | (() => Sheet), workbook?: Workbook | (() => Workbook), options?: SheetContextOptions): SheetContext;
|
|
132
|
+
//#endregion
|
package/dist/tools/download.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { saveBlob } from "@cat-kit/fe";
|
|
2
|
-
import { exportSheetCsv, exportWorkbookXlsx } from "@veltra/sheet-core/core/io/export";
|
|
2
|
+
import { exportSheetCsv, exportWorkbookXlsx } from "@veltra/sheet-core/core/io/export.js";
|
|
3
3
|
//#region src/tools/download.ts
|
|
4
4
|
/**
|
|
5
5
|
* 大工作簿的 XLSX 导出(sheet-core 序列化 + ZIP 压缩为秒级同步重活)移入 Web Worker:
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { SheetSnapshot } from "@veltra/sheet-core/core/sheet";
|
|
1
|
+
import { SheetSnapshot } from "@veltra/sheet-core/core/sheet.js";
|
|
2
2
|
//#region src/tools/export.worker.d.ts
|
|
3
|
-
interface ExportWorkerPayload {
|
|
3
|
+
export interface ExportWorkerPayload {
|
|
4
4
|
sheets: {
|
|
5
5
|
name: string;
|
|
6
6
|
snapshot: SheetSnapshot;
|
|
7
7
|
}[];
|
|
8
8
|
activeIndex: number;
|
|
9
9
|
}
|
|
10
|
-
interface ExportWorkerResponse {
|
|
10
|
+
export interface ExportWorkerResponse {
|
|
11
11
|
ok: boolean;
|
|
12
12
|
buffer?: Uint8Array;
|
|
13
13
|
error?: string;
|
|
14
14
|
}
|
|
15
|
-
//#endregion
|
|
16
|
-
export { ExportWorkerPayload, ExportWorkerResponse };
|
|
15
|
+
//#endregion
|
|
@@ -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"), import("@veltra/sheet-core/core/workbook")]).then(([{ exportWorkbookXlsx }, { Workbook }]) => {
|
|
3
|
+
Promise.all([import("@veltra/sheet-core/core/io/export.js"), import("@veltra/sheet-core/core/workbook.js")]).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++) {
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -11,9 +11,9 @@ import { SheetContext } from "./context.js";
|
|
|
11
11
|
* (与 defaultCommandRegistry 同语义;每个实例的 SheetContext 各自绑定当前 sheet)
|
|
12
12
|
*/
|
|
13
13
|
/** 弹层型工具类型:vue 层按类型渲染弹层面板(面板交互走 SheetContext 命令入口) */
|
|
14
|
-
type SheetToolPopupType = 'fill-color' | 'border' | 'font-color' | 'font-size' | 'find' | 'insert-image' | 'export';
|
|
14
|
+
export type SheetToolPopupType = 'fill-color' | 'border' | 'font-color' | 'font-size' | 'find' | 'functions' | 'insert-image' | 'export';
|
|
15
15
|
/** 工具定义 */
|
|
16
|
-
interface SheetTool {
|
|
16
|
+
export interface SheetTool {
|
|
17
17
|
/** 唯一 id;同 id 重复注册视为替换(保留原位置) */
|
|
18
18
|
id: string;
|
|
19
19
|
/** 按钮文本 */
|
|
@@ -38,11 +38,11 @@ interface SheetTool {
|
|
|
38
38
|
onClick(ctx: SheetContext): void;
|
|
39
39
|
}
|
|
40
40
|
/** 分组视图(供工具栏渲染) */
|
|
41
|
-
interface SheetToolGroup {
|
|
41
|
+
export interface SheetToolGroup {
|
|
42
42
|
name: string;
|
|
43
43
|
tools: SheetTool[];
|
|
44
44
|
}
|
|
45
|
-
declare class ToolRegistry {
|
|
45
|
+
export declare class ToolRegistry {
|
|
46
46
|
private entries;
|
|
47
47
|
private seq;
|
|
48
48
|
private emitter;
|
|
@@ -62,10 +62,9 @@ declare class ToolRegistry {
|
|
|
62
62
|
* 全局默认注册表(USheet 默认渲染这里的工具;内置工具也注册于此)。
|
|
63
63
|
* 工具是应用级扩展:注册一次,所有 USheet 实例共享。
|
|
64
64
|
*/
|
|
65
|
-
declare const defaultToolRegistry: ToolRegistry;
|
|
65
|
+
export declare const defaultToolRegistry: ToolRegistry;
|
|
66
66
|
/** 注册工具到默认注册表 */
|
|
67
|
-
declare function registerTool(tool: SheetTool): void;
|
|
67
|
+
export declare function registerTool(tool: SheetTool): void;
|
|
68
68
|
/** 从默认注册表注销工具;id 不存在时返回 false */
|
|
69
|
-
declare function unregisterTool(id: string): boolean;
|
|
70
|
-
//#endregion
|
|
71
|
-
export { SheetTool, SheetToolGroup, SheetToolPopupType, ToolRegistry, defaultToolRegistry, registerTool, unregisterTool };
|
|
69
|
+
export declare function unregisterTool(id: string): boolean;
|
|
70
|
+
//#endregion
|
package/dist/tools/registry.js
CHANGED
package/dist/types/sheet.d.ts
CHANGED
|
@@ -2,11 +2,11 @@ import { SheetContext } from "../tools/context.js";
|
|
|
2
2
|
import { ComputedRef } from "vue";
|
|
3
3
|
import { DeconstructValue } from "@veltra/utils";
|
|
4
4
|
import { ResolveCellRenderer, ResolveCellStyleHook, ResolveDisplayValue, SheetGrid } from "@veltra/sheet-core/grid";
|
|
5
|
-
import { Workbook } from "@veltra/sheet-core/core/workbook";
|
|
6
|
-
import { Sheet } from "@veltra/sheet-core/core/sheet";
|
|
5
|
+
import { Workbook } from "@veltra/sheet-core/core/workbook.js";
|
|
6
|
+
import { Sheet } from "@veltra/sheet-core/core/sheet.js";
|
|
7
7
|
//#region src/types/sheet.d.ts
|
|
8
8
|
/** 电子表格组件属性 */
|
|
9
|
-
interface SheetProps {
|
|
9
|
+
export interface SheetProps {
|
|
10
10
|
/** 工作簿实例(多 sheet / 跨表公式的载体);缺省内部自建(单 sheet) */
|
|
11
11
|
workbook?: Workbook;
|
|
12
12
|
/** 渲染行数,默认 100 */
|
|
@@ -40,7 +40,7 @@ interface SheetProps {
|
|
|
40
40
|
/** 只读预览(关闭编辑回写、填充柄等写入口) */
|
|
41
41
|
readonly?: boolean;
|
|
42
42
|
}
|
|
43
|
-
interface SheetEmits {
|
|
43
|
+
export interface SheetEmits {
|
|
44
44
|
/** 激活 sheet 切换(点击 tab 或宿主调用 workbook.activateSheet) */
|
|
45
45
|
(name: 'active-sheet-change', payload: {
|
|
46
46
|
sheet: Sheet;
|
|
@@ -48,7 +48,7 @@ interface SheetEmits {
|
|
|
48
48
|
}): void;
|
|
49
49
|
}
|
|
50
50
|
/** 在组件内部引用 */
|
|
51
|
-
interface _SheetExposed {
|
|
51
|
+
export interface _SheetExposed {
|
|
52
52
|
/** 当前工作簿(props.workbook 缺省时为内部自建实例) */
|
|
53
53
|
workbook: ComputedRef<Workbook>;
|
|
54
54
|
/** 当前活动 sheet */
|
|
@@ -59,6 +59,5 @@ interface _SheetExposed {
|
|
|
59
59
|
getGrid: () => SheetGrid | undefined;
|
|
60
60
|
}
|
|
61
61
|
/** 电子表格组件暴露的属性和方法 */
|
|
62
|
-
type SheetExposed = DeconstructValue<_SheetExposed>;
|
|
63
|
-
//#endregion
|
|
64
|
-
export { SheetEmits, SheetExposed, SheetProps, _SheetExposed };
|
|
62
|
+
export type SheetExposed = DeconstructValue<_SheetExposed>;
|
|
63
|
+
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veltra/sheet",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.8",
|
|
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"
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"@cat-kit/core": ">=1.2.1",
|
|
38
38
|
"@cat-kit/fe": ">=1.2.1",
|
|
39
|
-
"@veltra/desktop": "^1.7.
|
|
39
|
+
"@veltra/desktop": "^1.7.14",
|
|
40
40
|
"@veltra/icons": "^1.5.0",
|
|
41
|
-
"@veltra/sheet-core": "^2.5.
|
|
42
|
-
"@veltra/styles": "^1.7.
|
|
43
|
-
"@veltra/utils": "^1.7.
|
|
41
|
+
"@veltra/sheet-core": "^2.5.7",
|
|
42
|
+
"@veltra/styles": "^1.7.14",
|
|
43
|
+
"@veltra/utils": "^1.7.14",
|
|
44
44
|
"vue": ">=3.5.42"
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { parseRange } from '@veltra/sheet-core/core/address'
|
|
2
|
-
import { Workbook } from '@veltra/sheet-core/core/workbook'
|
|
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'
|
|
3
4
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
4
5
|
import { createApp, h, nextTick, type App } from 'vue'
|
|
5
6
|
|
|
@@ -342,6 +343,145 @@ function setFxText(el: HTMLElement, text: string): HTMLTextAreaElement {
|
|
|
342
343
|
return input
|
|
343
344
|
}
|
|
344
345
|
|
|
346
|
+
function fxButton(el: HTMLElement): HTMLButtonElement {
|
|
347
|
+
return el.querySelector<HTMLButtonElement>('button.u-sheet__fx-label')!
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** 函数弹框 Teleport 到 body 级 #pop-container(fx 按钮与工具栏共用同一弹框组件) */
|
|
351
|
+
function functionsPanel(): HTMLElement | null {
|
|
352
|
+
return document.querySelector<HTMLElement>('#pop-container .u-sheet__functions-panel')
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** 等待弹层渲染:watch flush + Teleport 挂载 */
|
|
356
|
+
async function flushPopup(): Promise<void> {
|
|
357
|
+
await nextTick()
|
|
358
|
+
await nextTick()
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
describe('USheet 公式栏:fx 函数弹框', () => {
|
|
362
|
+
it('fx 渲染为可点击 button;无活动单元格时禁用且不打开弹框', async () => {
|
|
363
|
+
const sheet = new Workbook().activeSheet
|
|
364
|
+
const { el } = mountFormulaBar(sheet)
|
|
365
|
+
await nextTick()
|
|
366
|
+
|
|
367
|
+
// 清空选区(Sheet 构造默认选 A1):与 fx 输入框同一禁用条件
|
|
368
|
+
sheet.selection.clear()
|
|
369
|
+
await nextTick()
|
|
370
|
+
const button = fxButton(el)
|
|
371
|
+
expect(button.tagName).toBe('BUTTON')
|
|
372
|
+
expect(button.disabled).toBe(true)
|
|
373
|
+
expect(fxInput(el).disabled).toBe(true)
|
|
374
|
+
|
|
375
|
+
button.click()
|
|
376
|
+
await flushPopup()
|
|
377
|
+
expect(functionsPanel()).toBeNull()
|
|
378
|
+
|
|
379
|
+
// 有活动格 → 可点击
|
|
380
|
+
sheet.selectCell({ row: 0, col: 0 })
|
|
381
|
+
await nextTick()
|
|
382
|
+
expect(button.disabled).toBe(false)
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
it('点击 fx 开弹框;再点 fx / 点击弹框外 / Esc 关闭;开关不改选区、单元格内容与草稿', async () => {
|
|
386
|
+
const sheet = new Workbook().activeSheet
|
|
387
|
+
sheet.setCellValue({ row: 0, col: 0 }, 'keep')
|
|
388
|
+
const { el } = mountFormulaBar(sheet)
|
|
389
|
+
await nextTick()
|
|
390
|
+
sheet.selectCell({ row: 2, col: 0 })
|
|
391
|
+
await nextTick()
|
|
392
|
+
|
|
393
|
+
// 挂起草稿编辑(focus + 输入,不提交)
|
|
394
|
+
const input = setFxText(el, 'draft')
|
|
395
|
+
const button = fxButton(el)
|
|
396
|
+
const selectionBefore = sheet.getSelection()
|
|
397
|
+
|
|
398
|
+
// 点击 fx → 弹框打开
|
|
399
|
+
button.click()
|
|
400
|
+
await flushPopup()
|
|
401
|
+
expect(functionsPanel()).not.toBeNull()
|
|
402
|
+
// 开弹框不改选区 / 不写单元格 / 草稿保留
|
|
403
|
+
expect(sheet.getSelection()).toEqual(selectionBefore)
|
|
404
|
+
expect(sheet.getCellData({ row: 2, col: 0 })).toBeUndefined()
|
|
405
|
+
expect(input.value).toBe('draft')
|
|
406
|
+
|
|
407
|
+
// 再次点击 fx → 关闭
|
|
408
|
+
button.click()
|
|
409
|
+
await flushPopup()
|
|
410
|
+
expect(functionsPanel()).toBeNull()
|
|
411
|
+
expect(input.value).toBe('draft')
|
|
412
|
+
|
|
413
|
+
// 点击弹框外 → 关闭
|
|
414
|
+
button.click()
|
|
415
|
+
await flushPopup()
|
|
416
|
+
window.dispatchEvent(new MouseEvent('click'))
|
|
417
|
+
await flushPopup()
|
|
418
|
+
expect(functionsPanel()).toBeNull()
|
|
419
|
+
expect(input.value).toBe('draft')
|
|
420
|
+
|
|
421
|
+
// Esc → 关闭;全程单元格未被写入
|
|
422
|
+
button.click()
|
|
423
|
+
await flushPopup()
|
|
424
|
+
functionsPanel()!.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))
|
|
425
|
+
await flushPopup()
|
|
426
|
+
expect(functionsPanel()).toBeNull()
|
|
427
|
+
expect(input.value).toBe('draft')
|
|
428
|
+
expect(sheet.getCellData({ row: 2, col: 0 })).toBeUndefined()
|
|
429
|
+
expect(sheet.getCellData({ row: 0, col: 0 })).toMatchObject({ v: 'keep' })
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
it('选中函数:进入编辑态输出 =SUM(),光标在括号内、焦点在 fx 输入框;Enter 提交到活动格', async () => {
|
|
433
|
+
const sheet = new Workbook().activeSheet
|
|
434
|
+
const { el } = mountFormulaBar(sheet)
|
|
435
|
+
await nextTick()
|
|
436
|
+
sheet.selectCell({ row: 2, col: 0 })
|
|
437
|
+
await nextTick()
|
|
438
|
+
|
|
439
|
+
fxButton(el).click()
|
|
440
|
+
await flushPopup()
|
|
441
|
+
// 默认「常用」首项 SUM
|
|
442
|
+
document.querySelector<HTMLElement>('#pop-container .u-sheet__functions-item')!.click()
|
|
443
|
+
await nextTick()
|
|
444
|
+
await nextTick()
|
|
445
|
+
|
|
446
|
+
const input = fxInput(el)
|
|
447
|
+
expect(input.value).toBe('=SUM()')
|
|
448
|
+
expect(input.selectionStart).toBe('=SUM()'.length - 1)
|
|
449
|
+
expect(input.selectionEnd).toBe('=SUM()'.length - 1)
|
|
450
|
+
expect(document.activeElement).toBe(input)
|
|
451
|
+
expect(el.querySelector('.u-sheet__fx-suggest')).toBeNull()
|
|
452
|
+
|
|
453
|
+
// 提交 → 写入进入编辑时的目标格(当前活动格 A3)
|
|
454
|
+
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }))
|
|
455
|
+
await nextTick()
|
|
456
|
+
expect(sheet.getCellData({ row: 2, col: 0 })).toMatchObject({ f: 'SUM()' })
|
|
457
|
+
expect(sheet.getSelection().activeCell).toEqual({ row: 2, col: 0 })
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
it('工具栏「函数」按钮打开同一弹框,选中行为与 fx 按钮一致', async () => {
|
|
461
|
+
const workbook = new Workbook()
|
|
462
|
+
const { el } = mount(() => ({ workbook, rows: 20, cols: 8 }))
|
|
463
|
+
await nextTick()
|
|
464
|
+
|
|
465
|
+
const toolbarButton = el.querySelector<HTMLButtonElement>('[data-tool-id="functions"]')!
|
|
466
|
+
toolbarButton.click()
|
|
467
|
+
await nextTick()
|
|
468
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
469
|
+
await flushPopup()
|
|
470
|
+
expect(functionsPanel()).not.toBeNull()
|
|
471
|
+
|
|
472
|
+
document.querySelector<HTMLElement>('#pop-container .u-sheet__functions-item')!.click()
|
|
473
|
+
await nextTick()
|
|
474
|
+
await nextTick()
|
|
475
|
+
// 公式栏进入编辑态(默认选区 A1)
|
|
476
|
+
const input = fxInput(el)
|
|
477
|
+
expect(input.value).toBe('=SUM()')
|
|
478
|
+
expect(input.selectionStart).toBe('=SUM()'.length - 1)
|
|
479
|
+
expect(document.activeElement).toBe(input)
|
|
480
|
+
// 弹框随选中关闭
|
|
481
|
+
expect(functionsPanel()).toBeNull()
|
|
482
|
+
})
|
|
483
|
+
})
|
|
484
|
+
|
|
345
485
|
function suggestList(el: HTMLElement): HTMLElement | null {
|
|
346
486
|
return el.querySelector('.u-sheet__fx-suggest')
|
|
347
487
|
}
|
|
@@ -501,3 +641,175 @@ describe('USheet 公式栏:函数补全与引用选择', () => {
|
|
|
501
641
|
expect(input.style.height).toBe('')
|
|
502
642
|
})
|
|
503
643
|
})
|
|
644
|
+
|
|
645
|
+
// ─── 目标格选区高亮保持(弹框打开与参数点选期间)──────────────
|
|
646
|
+
|
|
647
|
+
/** VTable 事件名(ListTable.EVENT_TYPE 常量值;@veltra/sheet 不直接依赖 @visactor/vtable,不做运行时引入) */
|
|
648
|
+
const EVT_SELECTED_CELL = 'selected_cell'
|
|
649
|
+
const EVT_DRAG_SELECT_END = 'drag_select_end'
|
|
650
|
+
|
|
651
|
+
type GridTable = ReturnType<SheetGrid['getTable']>
|
|
652
|
+
|
|
653
|
+
/** 模拟画布点选格(VTable 时序:selectCells → SELECTED_CELL) */
|
|
654
|
+
function canvasClickCell(table: GridTable, col: number, row: number): void {
|
|
655
|
+
table.selectCells([{ start: { col, row }, end: { col, row } }])
|
|
656
|
+
table.fireListeners(EVT_SELECTED_CELL, { col, row })
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/** 画布当前选区末尾项(表格坐标;VTable 范围对象含额外内部字段,按坐标匹配) */
|
|
660
|
+
function lastCanvasRange(table: GridTable) {
|
|
661
|
+
return table.getSelectedCellRanges().at(-1)
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
describe('USheet 公式栏:目标格选区高亮保持', () => {
|
|
665
|
+
it('fx 弹框打开期间点选画布:目标格高亮与名称框保持、模型选区不变,不插入引用文本', async () => {
|
|
666
|
+
const workbook = new Workbook()
|
|
667
|
+
const sheet = workbook.activeSheet
|
|
668
|
+
const exposed: { value: SheetExposed | undefined } = { value: undefined }
|
|
669
|
+
const { el } = mount(() => ({ workbook, rows: 20, cols: 8 }), exposed)
|
|
670
|
+
await nextTick()
|
|
671
|
+
sheet.selectCell({ row: 2, col: 0 })
|
|
672
|
+
await nextTick()
|
|
673
|
+
|
|
674
|
+
fxButton(el).click()
|
|
675
|
+
await flushPopup()
|
|
676
|
+
expect(functionsPanel()).not.toBeNull()
|
|
677
|
+
|
|
678
|
+
// 弹框打开期间点选画布 B2(表格坐标 (2,2))
|
|
679
|
+
const table = exposed.value!.getGrid()!.getTable()
|
|
680
|
+
canvasClickCell(table, 2, 2)
|
|
681
|
+
|
|
682
|
+
expect(sheet.getSelection().activeCell).toEqual({ row: 2, col: 0 })
|
|
683
|
+
expect(sheet.getSelection().ranges[0]).toEqual({
|
|
684
|
+
start: { row: 2, col: 0 },
|
|
685
|
+
end: { row: 2, col: 0 }
|
|
686
|
+
})
|
|
687
|
+
// 画布选区回推为目标格 A3(表格坐标 (1,3)),名称框仍显示 A3
|
|
688
|
+
expect(lastCanvasRange(table)).toMatchObject({
|
|
689
|
+
start: { col: 1, row: 3 },
|
|
690
|
+
end: { col: 1, row: 3 }
|
|
691
|
+
})
|
|
692
|
+
expect(nameBox(el).value).toBe('A3')
|
|
693
|
+
// 未处于引用选择上下文:不插入引用文本
|
|
694
|
+
expect(fxInput(el).value).toBe('')
|
|
695
|
+
})
|
|
696
|
+
|
|
697
|
+
it('选中函数后点选画布参数格:只插入引用文本、目标格选区不变;提交后选区与名称框仍指向目标格', async () => {
|
|
698
|
+
const workbook = new Workbook()
|
|
699
|
+
const sheet = workbook.activeSheet
|
|
700
|
+
sheet.setCellValue({ row: 0, col: 0 }, 1)
|
|
701
|
+
const exposed: { value: SheetExposed | undefined } = { value: undefined }
|
|
702
|
+
const { el } = mount(() => ({ workbook, rows: 20, cols: 8 }), exposed)
|
|
703
|
+
await nextTick()
|
|
704
|
+
sheet.selectCell({ row: 2, col: 0 })
|
|
705
|
+
await nextTick()
|
|
706
|
+
|
|
707
|
+
// fx 弹框选中 SUM → 编辑态 =SUM()(目标 = 活动格 A3)
|
|
708
|
+
fxButton(el).click()
|
|
709
|
+
await flushPopup()
|
|
710
|
+
document.querySelector<HTMLElement>('#pop-container .u-sheet__functions-item')!.click()
|
|
711
|
+
await nextTick()
|
|
712
|
+
await nextTick()
|
|
713
|
+
const input = fxInput(el)
|
|
714
|
+
expect(input.value).toBe('=SUM()')
|
|
715
|
+
|
|
716
|
+
// 点选参数格 A1(表格坐标 (1,1))→ 只插入引用文本(括号随 =SUM() 模板闭合),目标格选区不变
|
|
717
|
+
const table = exposed.value!.getGrid()!.getTable()
|
|
718
|
+
canvasClickCell(table, 1, 1)
|
|
719
|
+
await nextTick()
|
|
720
|
+
expect(input.value).toBe('=SUM(A1)')
|
|
721
|
+
expect(sheet.getSelection().activeCell).toEqual({ row: 2, col: 0 })
|
|
722
|
+
expect(lastCanvasRange(table)).toMatchObject({
|
|
723
|
+
start: { col: 1, row: 3 },
|
|
724
|
+
end: { col: 1, row: 3 }
|
|
725
|
+
})
|
|
726
|
+
expect(nameBox(el).value).toBe('A3')
|
|
727
|
+
|
|
728
|
+
// 提交 → 写入目标格 A3,选区与名称框仍指向 A3
|
|
729
|
+
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }))
|
|
730
|
+
await nextTick()
|
|
731
|
+
expect(sheet.getCellData({ row: 2, col: 0 })).toMatchObject({ f: 'SUM(A1)', v: 1 })
|
|
732
|
+
expect(sheet.getSelection().activeCell).toEqual({ row: 2, col: 0 })
|
|
733
|
+
expect(nameBox(el).value).toBe('A3')
|
|
734
|
+
expect(lastCanvasRange(table)).toMatchObject({
|
|
735
|
+
start: { col: 1, row: 3 },
|
|
736
|
+
end: { col: 1, row: 3 }
|
|
737
|
+
})
|
|
738
|
+
})
|
|
739
|
+
|
|
740
|
+
it('工具栏「函数」入口与 fx 一致:弹框期间点选画布保持目标格;拖选参数后提交仍指向目标格', async () => {
|
|
741
|
+
const workbook = new Workbook()
|
|
742
|
+
const sheet = workbook.activeSheet
|
|
743
|
+
sheet.setCellValue({ row: 0, col: 0 }, 1)
|
|
744
|
+
sheet.setCellValue({ row: 1, col: 0 }, 2)
|
|
745
|
+
const exposed: { value: SheetExposed | undefined } = { value: undefined }
|
|
746
|
+
const { el } = mount(() => ({ workbook, rows: 20, cols: 8 }), exposed)
|
|
747
|
+
await nextTick()
|
|
748
|
+
sheet.selectCell({ row: 2, col: 0 })
|
|
749
|
+
await nextTick()
|
|
750
|
+
|
|
751
|
+
el.querySelector<HTMLButtonElement>('[data-tool-id="functions"]')!.click()
|
|
752
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
753
|
+
await flushPopup()
|
|
754
|
+
expect(functionsPanel()).not.toBeNull()
|
|
755
|
+
|
|
756
|
+
// 弹框打开期间点选画布 B2:目标格 A3 高亮保持、模型选区不变、不插入文本
|
|
757
|
+
const table = exposed.value!.getGrid()!.getTable()
|
|
758
|
+
canvasClickCell(table, 2, 2)
|
|
759
|
+
expect(sheet.getSelection().activeCell).toEqual({ row: 2, col: 0 })
|
|
760
|
+
expect(lastCanvasRange(table)).toMatchObject({
|
|
761
|
+
start: { col: 1, row: 3 },
|
|
762
|
+
end: { col: 1, row: 3 }
|
|
763
|
+
})
|
|
764
|
+
expect(fxInput(el).value).toBe('')
|
|
765
|
+
|
|
766
|
+
// 选中 SUM → 拖选参数区域 A1:A2(SELECTED_CELL → DRAG_SELECT_END 完整时序)
|
|
767
|
+
document.querySelector<HTMLElement>('#pop-container .u-sheet__functions-item')!.click()
|
|
768
|
+
await nextTick()
|
|
769
|
+
await nextTick()
|
|
770
|
+
const input = fxInput(el)
|
|
771
|
+
expect(input.value).toBe('=SUM()')
|
|
772
|
+
table.selectCells([{ start: { col: 1, row: 1 }, end: { col: 1, row: 2 } }])
|
|
773
|
+
table.fireListeners(EVT_SELECTED_CELL, { col: 1, row: 2 })
|
|
774
|
+
table.fireListeners(EVT_DRAG_SELECT_END, {})
|
|
775
|
+
await nextTick()
|
|
776
|
+
expect(input.value).toBe('=SUM(A1:A2)')
|
|
777
|
+
expect(sheet.getSelection().activeCell).toEqual({ row: 2, col: 0 })
|
|
778
|
+
expect(nameBox(el).value).toBe('A3')
|
|
779
|
+
|
|
780
|
+
// 提交 → 选区与名称框仍指向目标格 A3
|
|
781
|
+
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }))
|
|
782
|
+
await nextTick()
|
|
783
|
+
expect(sheet.getCellData({ row: 2, col: 0 })).toMatchObject({ f: 'SUM(A1:A2)', v: 3 })
|
|
784
|
+
expect(sheet.getSelection().activeCell).toEqual({ row: 2, col: 0 })
|
|
785
|
+
expect(nameBox(el).value).toBe('A3')
|
|
786
|
+
})
|
|
787
|
+
|
|
788
|
+
it('公式提交后选区与名称框仍指向目标格(编辑期间名称框跳转离开时回推)', async () => {
|
|
789
|
+
const workbook = new Workbook()
|
|
790
|
+
const sheet = workbook.activeSheet
|
|
791
|
+
const exposed: { value: SheetExposed | undefined } = { value: undefined }
|
|
792
|
+
const { el } = mount(() => ({ workbook, rows: 20, cols: 8 }), exposed)
|
|
793
|
+
await nextTick()
|
|
794
|
+
sheet.selectCell({ row: 2, col: 0 })
|
|
795
|
+
await nextTick()
|
|
796
|
+
|
|
797
|
+
// 编辑目标 A3;期间经名称框跳转到 B5(模型选区离开目标格)
|
|
798
|
+
const input = setFxText(el, '=1+1')
|
|
799
|
+
typeName(el, 'B5')
|
|
800
|
+
await nextTick()
|
|
801
|
+
expect(sheet.getSelection().activeCell).toEqual({ row: 4, col: 1 })
|
|
802
|
+
expect(nameBox(el).value).toBe('B5')
|
|
803
|
+
|
|
804
|
+
// 提交 → 写入 A3,模型选区 / 名称框 / 画布高亮回推目标格
|
|
805
|
+
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }))
|
|
806
|
+
await nextTick()
|
|
807
|
+
expect(sheet.getCellData({ row: 2, col: 0 })).toMatchObject({ f: '1+1', v: 2 })
|
|
808
|
+
expect(sheet.getSelection().activeCell).toEqual({ row: 2, col: 0 })
|
|
809
|
+
expect(nameBox(el).value).toBe('A3')
|
|
810
|
+
expect(lastCanvasRange(exposed.value!.getGrid()!.getTable())).toMatchObject({
|
|
811
|
+
start: { col: 1, row: 3 },
|
|
812
|
+
end: { col: 1, row: 3 }
|
|
813
|
+
})
|
|
814
|
+
})
|
|
815
|
+
})
|