@veltra/sheet 2.5.8 → 2.7.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.
- package/dist/components/sheet/formula-bar.js +6 -1
- package/dist/components/sheet/sheet-context-menu.js +41 -5
- package/dist/components/sheet/sheet.js +19 -6
- package/dist/components/sheet/style2.css +1 -1
- package/dist/components/sheet/use-sheet-grid.js +20 -7
- package/dist/components/sheet/use-sheet-state.js +4 -1
- package/dist/components/sheet/use-tool-popup.js +1 -2
- package/dist/index.d.ts +2 -2
- package/dist/tools/context.d.ts +15 -0
- package/dist/tools/context.js +22 -1
- package/dist/types/sheet.d.ts +25 -5
- package/package.json +5 -2
- package/src/components/sheet/__test__/formula-bar.test.ts +52 -42
- package/src/components/sheet/__test__/functions-popup.test.ts +1 -1
- package/src/components/sheet/__test__/import-file.test.ts +1 -1
- package/src/components/sheet/__test__/insert-count-menu-item.test.ts +1 -1
- 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 +357 -24
- package/src/components/sheet/__test__/sheet-context-menu.test.ts +32 -3
- package/src/components/sheet/__test__/use-formula-ref-select.test.ts +1 -1
- package/src/components/sheet/__test__/use-formula-suggest.test.ts +1 -1
- package/src/components/sheet/formula-bar.vue +14 -1
- package/src/components/sheet/sheet-context-menu.ts +61 -4
- package/src/components/sheet/sheet.vue +9 -4
- package/src/components/sheet/style.scss +2 -2
- package/src/components/sheet/use-sheet-grid.ts +36 -9
- package/src/components/sheet/use-sheet-state.ts +5 -1
- package/src/components/sheet/use-tool-popup.ts +5 -4
- package/src/index.ts +1 -1
- package/src/tools/__test__/apply-style.test.ts +1 -1
- package/src/tools/__test__/builtin.test.ts +1 -1
- package/src/tools/__test__/context.test.ts +66 -2
- package/src/tools/__test__/registry.test.ts +1 -1
- package/src/tools/context.ts +47 -0
- package/src/types/sheet.ts +24 -5
- package/src/components/sheet/parsing.ts +0 -11
|
@@ -4,6 +4,7 @@ import type { NumFmt } from '@veltra/sheet-core/core/style/types.js'
|
|
|
4
4
|
import { defineComponent, h } from 'vue'
|
|
5
5
|
|
|
6
6
|
import type { SheetContext } from '../../tools/context'
|
|
7
|
+
import { MIN_ROW_COL_SIZE } from '../../tools/context'
|
|
7
8
|
import { defaultToolRegistry } from '../../tools/registry'
|
|
8
9
|
import InsertCountMenuItem from './insert-count-menu-item.vue'
|
|
9
10
|
import { pickAndInsertImage } from './insert-image'
|
|
@@ -16,6 +17,13 @@ export const INSERT_COUNT_MAX = 1000
|
|
|
16
17
|
export const DECIMAL_PLACES_MIN = 0
|
|
17
18
|
export const DECIMAL_PLACES_MAX = 10
|
|
18
19
|
|
|
20
|
+
/** 行高/列宽数值项输入上限(下限复用门面 MIN_ROW_COL_SIZE,对齐引擎 resize 最小值) */
|
|
21
|
+
export const AXIS_SIZE_MAX = 1000
|
|
22
|
+
|
|
23
|
+
/** 数值项默认值回落:未设置自定义尺寸时展示引擎默认行高/列宽(对齐 sheet-core grid-theme) */
|
|
24
|
+
export const FALLBACK_ROW_HEIGHT = 28
|
|
25
|
+
export const FALLBACK_COL_WIDTH = 80
|
|
26
|
+
|
|
19
27
|
/** 选区覆盖行/列数 → 插入默认 N(min 1 / max 1000) */
|
|
20
28
|
export function defaultInsertCount(range: CellRange | null, axis: 'row' | 'col'): number {
|
|
21
29
|
if (!range) return INSERT_COUNT_MIN
|
|
@@ -94,12 +102,46 @@ function insertCountRender(options: {
|
|
|
94
102
|
return menuNumberRender({ ...options, min: INSERT_COUNT_MIN, max: INSERT_COUNT_MAX })
|
|
95
103
|
}
|
|
96
104
|
|
|
105
|
+
/** 行号/列头菜单选项(readonly 时尺寸项禁用) */
|
|
106
|
+
export interface HeaderMenuOptions {
|
|
107
|
+
/** 只读模式:行高/列宽项禁用(尺寸写入属编辑操作) */
|
|
108
|
+
readonly?: boolean
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 行高/列宽菜单项:内嵌数值输入,确认经门面批量应用到选区覆盖行/列。
|
|
113
|
+
* readonly 时降级为禁用文本项——render 内嵌组件自带点击路径,disabled 拦不住。
|
|
114
|
+
*/
|
|
115
|
+
function axisSizeMenuItem(options: {
|
|
116
|
+
label: string
|
|
117
|
+
disabled: boolean
|
|
118
|
+
defaultValue: number
|
|
119
|
+
onConfirm: (n: number) => void
|
|
120
|
+
}): ContextmenuItem {
|
|
121
|
+
if (options.disabled) return { label: options.label, disabled: true }
|
|
122
|
+
return {
|
|
123
|
+
label: options.label,
|
|
124
|
+
keepOpen: true,
|
|
125
|
+
render: menuNumberRender({
|
|
126
|
+
prefix: options.label,
|
|
127
|
+
suffix: '像素',
|
|
128
|
+
defaultValue: options.defaultValue,
|
|
129
|
+
min: MIN_ROW_COL_SIZE,
|
|
130
|
+
max: AXIS_SIZE_MAX,
|
|
131
|
+
onConfirm: options.onConfirm
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
97
136
|
function primaryRange(ctx: SheetContext): CellRange | null {
|
|
98
137
|
return ctx.getSelection().ranges[0] ?? null
|
|
99
138
|
}
|
|
100
139
|
|
|
101
|
-
/** 行号右键菜单:插入×2 / 删除 / divider / 冻结 / 取消冻结 */
|
|
102
|
-
export function buildRowHeaderMenus(
|
|
140
|
+
/** 行号右键菜单:插入×2 / 删除 / 行高 / divider / 冻结 / 取消冻结 */
|
|
141
|
+
export function buildRowHeaderMenus(
|
|
142
|
+
ctx: SheetContext,
|
|
143
|
+
options?: HeaderMenuOptions
|
|
144
|
+
): ContextmenuItem[] {
|
|
103
145
|
const range = primaryRange(ctx)
|
|
104
146
|
const startRow = range?.start.row ?? 0
|
|
105
147
|
const endRow = range?.end.row ?? startRow
|
|
@@ -130,6 +172,12 @@ export function buildRowHeaderMenus(ctx: SheetContext): ContextmenuItem[] {
|
|
|
130
172
|
})
|
|
131
173
|
},
|
|
132
174
|
{ label: '删除行', callback: () => ctx.deleteRows(startRow, deleteCount) },
|
|
175
|
+
axisSizeMenuItem({
|
|
176
|
+
label: '行高',
|
|
177
|
+
disabled: options?.readonly === true,
|
|
178
|
+
defaultValue: ctx.getRowHeight(startRow) ?? FALLBACK_ROW_HEIGHT,
|
|
179
|
+
onConfirm: (n) => ctx.setRowHeightBySelection(n)
|
|
180
|
+
}),
|
|
133
181
|
{ divider: true },
|
|
134
182
|
{
|
|
135
183
|
label: freezeActive ? '✓ 冻结到当前行' : '冻结到当前行',
|
|
@@ -143,8 +191,11 @@ export function buildRowHeaderMenus(ctx: SheetContext): ContextmenuItem[] {
|
|
|
143
191
|
]
|
|
144
192
|
}
|
|
145
193
|
|
|
146
|
-
/** 列头右键菜单:插入×2 / 删除 / divider / 冻结 / 取消冻结 */
|
|
147
|
-
export function buildColHeaderMenus(
|
|
194
|
+
/** 列头右键菜单:插入×2 / 删除 / 列宽 / divider / 冻结 / 取消冻结 */
|
|
195
|
+
export function buildColHeaderMenus(
|
|
196
|
+
ctx: SheetContext,
|
|
197
|
+
options?: HeaderMenuOptions
|
|
198
|
+
): ContextmenuItem[] {
|
|
148
199
|
const range = primaryRange(ctx)
|
|
149
200
|
const startCol = range?.start.col ?? 0
|
|
150
201
|
const endCol = range?.end.col ?? startCol
|
|
@@ -175,6 +226,12 @@ export function buildColHeaderMenus(ctx: SheetContext): ContextmenuItem[] {
|
|
|
175
226
|
})
|
|
176
227
|
},
|
|
177
228
|
{ label: '删除列', callback: () => ctx.deleteCols(startCol, deleteCount) },
|
|
229
|
+
axisSizeMenuItem({
|
|
230
|
+
label: '列宽',
|
|
231
|
+
disabled: options?.readonly === true,
|
|
232
|
+
defaultValue: ctx.getColWidth(startCol) ?? FALLBACK_COL_WIDTH,
|
|
233
|
+
onConfirm: (n) => ctx.setColWidthBySelection(n)
|
|
234
|
+
}),
|
|
178
235
|
{ divider: true },
|
|
179
236
|
{
|
|
180
237
|
label: freezeActive ? '✓ 冻结到当前列' : '冻结到当前列',
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
ref="formulaBarRef"
|
|
57
57
|
:sheet="activeSheet"
|
|
58
58
|
:context="context"
|
|
59
|
+
:set-selection-anchor="setSelectionAnchor"
|
|
59
60
|
/>
|
|
60
61
|
|
|
61
62
|
<div ref="gridRef" :class="cls.e('grid')">
|
|
@@ -110,7 +111,8 @@ const props = withDefaults(defineProps<SheetProps>(), {
|
|
|
110
111
|
showTabs: true,
|
|
111
112
|
showRowHeader: true,
|
|
112
113
|
showColHeader: true,
|
|
113
|
-
readonly: false
|
|
114
|
+
readonly: false,
|
|
115
|
+
colResize: false
|
|
114
116
|
})
|
|
115
117
|
|
|
116
118
|
const emit = defineEmits<SheetEmits>()
|
|
@@ -129,7 +131,9 @@ const { workbook, activeIndex, sheetList, activeSheet, context, stateTick, syncF
|
|
|
129
131
|
onBeforeSheetChange: () => closePopup(),
|
|
130
132
|
rebuildGrid: () => rebuildGrid(),
|
|
131
133
|
activateGrid: () => activateGrid(),
|
|
132
|
-
pruneCache: (sheets) => pruneCache(sheets)
|
|
134
|
+
pruneCache: (sheets) => pruneCache(sheets),
|
|
135
|
+
// 行高/列宽菜单确认 → 活动 grid 引擎尺寸即时生效(getGrid 声明于下方,事件时才求值)
|
|
136
|
+
syncAxisSizes: (axis, indexes) => getGrid()?.applyAxisSizes(axis, indexes)
|
|
133
137
|
})
|
|
134
138
|
|
|
135
139
|
// ─── 弹层型工具编排(打开 / 关闭 / 面板事务)─────────────────────
|
|
@@ -205,14 +209,15 @@ const toolGroups = useToolGroups(context, stateTick)
|
|
|
205
209
|
const gridRef = useTemplateRef<HTMLElement>('gridRef')
|
|
206
210
|
const formulaBarRef = useTemplateRef<InstanceType<typeof UFormulaBar>>('formulaBarRef')
|
|
207
211
|
|
|
208
|
-
const { rebuildGrid, activateGrid, pruneCache, getGrid } = useSheetGrid({
|
|
212
|
+
const { rebuildGrid, activateGrid, pruneCache, getGrid, setSelectionAnchor } = useSheetGrid({
|
|
209
213
|
props,
|
|
210
214
|
gridRef,
|
|
211
215
|
getActiveSheet: () => activeSheet.value,
|
|
212
216
|
context,
|
|
213
217
|
formulaBarRef,
|
|
214
218
|
// 工具栏「函数」弹框打开期间与 fx 入口一致:画布点击不清除目标格高亮
|
|
215
|
-
isFunctionPopupOpen: () => popupTool.value?.popup === 'functions'
|
|
219
|
+
isFunctionPopupOpen: () => popupTool.value?.popup === 'functions',
|
|
220
|
+
onColResizeEnd: (event) => emit('col-resize-end', event)
|
|
216
221
|
})
|
|
217
222
|
|
|
218
223
|
const exposed: _SheetExposed = {
|
|
@@ -147,7 +147,7 @@
|
|
|
147
147
|
gap: fn.use-var(gap, small);
|
|
148
148
|
padding: fn.use-var(gap, small);
|
|
149
149
|
border-bottom: 1px solid fn.use-var(border, muted-color);
|
|
150
|
-
// 浮起的 fx 输入面板 / 候选列表需盖住下方 grid
|
|
150
|
+
// 浮起的 fx 输入面板 / 候选列表需盖住下方 grid 与浮动图片叠层(引擎画布分层 z 序)
|
|
151
151
|
position: relative;
|
|
152
152
|
z-index: 30;
|
|
153
153
|
}
|
|
@@ -717,7 +717,7 @@
|
|
|
717
717
|
}
|
|
718
718
|
|
|
719
719
|
/* LRU 缓存实例容器:absolute 堆叠于 grid 区,非激活实例 visibility:hidden
|
|
720
|
-
|
|
720
|
+
(保持尺寸不触发引擎表格 resize,切回仅翻转可见性 + 选区回驱) */
|
|
721
721
|
@include m.e(grid-instance) {
|
|
722
722
|
position: absolute;
|
|
723
723
|
inset: 0;
|
|
@@ -39,6 +39,8 @@ interface UseSheetGridOptions {
|
|
|
39
39
|
formulaBarRef: ElRef<FormulaBarMirror>
|
|
40
40
|
/** 函数弹框打开中(宿主侧入口,如工具栏「函数」按钮) */
|
|
41
41
|
isFunctionPopupOpen?: () => boolean
|
|
42
|
+
/** 列宽拖拽落定(SheetGrid 透传 → 宿主 emit col-resize-end) */
|
|
43
|
+
onColResizeEnd?: (event: { col: number; width: number }) => void
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
/** 缓存实例(LRU 淘汰):每个 sheet 一个独立容器 div,非激活容器 visibility:hidden 堆叠 */
|
|
@@ -55,9 +57,9 @@ interface CachedGrid {
|
|
|
55
57
|
|
|
56
58
|
/**
|
|
57
59
|
* 缓存容量:最近 N 个 sheet 的 SheetGrid 实例常驻(tab 来回切换零重建)。
|
|
58
|
-
* 大文件多 sheet
|
|
60
|
+
* 大文件多 sheet 场景下切换卡顿的主因是每次重建引擎表格(实例创建 + 画布分层
|
|
59
61
|
* 首屏构建 ~300ms);缓存命中时仅切换容器可见性 + 选区回驱(≈0)。
|
|
60
|
-
*
|
|
62
|
+
* 每实例持有一组 [data-layer-kind] 分层画布,容量即内存上限(含隐藏实例的渲染开销)。
|
|
61
63
|
*/
|
|
62
64
|
const GRID_CACHE_CAPACITY = 3
|
|
63
65
|
|
|
@@ -84,9 +86,9 @@ export function useSheetGrid(options: UseSheetGridOptions) {
|
|
|
84
86
|
|
|
85
87
|
const menus =
|
|
86
88
|
info.kind === 'row-header'
|
|
87
|
-
? buildRowHeaderMenus(context)
|
|
89
|
+
? buildRowHeaderMenus(context, { readonly: props.readonly })
|
|
88
90
|
: info.kind === 'col-header'
|
|
89
|
-
? buildColHeaderMenus(context)
|
|
91
|
+
? buildColHeaderMenus(context, { readonly: props.readonly })
|
|
90
92
|
: buildBodyMenus(context)
|
|
91
93
|
|
|
92
94
|
// 最小宽度兜底;实际宽度由最长菜单项撑开(勿固定 240 导致空白过大)
|
|
@@ -126,9 +128,13 @@ export function useSheetGrid(options: UseSheetGridOptions) {
|
|
|
126
128
|
resolveDisplayValue: props.resolveDisplayValue,
|
|
127
129
|
resolveCellStyle: props.resolveCellStyle,
|
|
128
130
|
resolveCellRenderer: props.resolveCellRenderer,
|
|
131
|
+
header: props.header,
|
|
132
|
+
editors: props.editors,
|
|
133
|
+
colResize: props.colResize,
|
|
129
134
|
onContextMenu: handleContextMenu,
|
|
130
135
|
onEditStart: (addr) => formulaBarRef.value?.mirrorGridEdit(addr),
|
|
131
136
|
onEditEnd: (addr) => formulaBarRef.value?.exitMirror(addr),
|
|
137
|
+
onColResizeEnd: options.onColResizeEnd,
|
|
132
138
|
// 引用选择:不回写模型选区,序列化为 A1 / A1:B2 交给公式栏;
|
|
133
139
|
// 函数弹框打开期间(fx / 工具栏两入口)同样拦截,画布点击不离开目标格
|
|
134
140
|
interceptSelection: () =>
|
|
@@ -198,13 +204,18 @@ export function useSheetGrid(options: UseSheetGridOptions) {
|
|
|
198
204
|
* 命中时校验实例是否过期(隐藏期间程序化 structure-change——插入/删除/undo
|
|
199
205
|
* 行列——vue 层只绑定激活 sheet 的 structure-change,隐藏实例靠自持订阅标记
|
|
200
206
|
* dirty);过期则释放重建。命中且未过期只翻可见性:隐藏实例的 cell/merge/
|
|
201
|
-
* content-reset 已在
|
|
202
|
-
*
|
|
207
|
+
* content-reset 已在 SheetGrid 内部置脏,setVisible(true) 时一次性全量同步,
|
|
208
|
+
* 无需宿主补推送。
|
|
203
209
|
*/
|
|
204
210
|
function activateGrid(): void {
|
|
205
211
|
const sheet = getActiveSheet()
|
|
206
212
|
const host = gridRef.value
|
|
207
213
|
if (!host) return
|
|
214
|
+
// 换实例前清旧实例的选区锚点:fx 编辑会话随切 sheet 结束(公式栏同步复位
|
|
215
|
+
// 编辑态),缓存实例复用时不得残留上次会话的高亮
|
|
216
|
+
if (active && active.sheet !== sheet) {
|
|
217
|
+
active.grid.setSelectionAnchor(null)
|
|
218
|
+
}
|
|
208
219
|
const cached = cache.get(sheet)
|
|
209
220
|
if (cached && !cached.dirty) {
|
|
210
221
|
cached.lastUsed = ++seq
|
|
@@ -249,10 +260,21 @@ export function useSheetGrid(options: UseSheetGridOptions) {
|
|
|
249
260
|
|
|
250
261
|
onMounted(activateGrid)
|
|
251
262
|
|
|
252
|
-
// props
|
|
263
|
+
// props 尺寸显式增大时先扩张模型,再重建(删行缩小不走这条路径);
|
|
264
|
+
// header / editors 按引用更替判定(机制对象构造期装配,变化即重建——宿主用
|
|
265
|
+
// computed 持稳定引用,勿在模板内联对象字面量)
|
|
253
266
|
watch(
|
|
254
267
|
() =>
|
|
255
|
-
[
|
|
268
|
+
[
|
|
269
|
+
props.rows,
|
|
270
|
+
props.cols,
|
|
271
|
+
props.readonly,
|
|
272
|
+
props.showRowHeader,
|
|
273
|
+
props.showColHeader,
|
|
274
|
+
props.header,
|
|
275
|
+
props.editors,
|
|
276
|
+
props.colResize
|
|
277
|
+
] as const,
|
|
256
278
|
([nextRows, nextCols]) => {
|
|
257
279
|
const sheet = getActiveSheet()
|
|
258
280
|
if (nextRows != null) sheet.ensureTableSize(nextRows, 0)
|
|
@@ -265,5 +287,10 @@ export function useSheetGrid(options: UseSheetGridOptions) {
|
|
|
265
287
|
invalidateAll()
|
|
266
288
|
})
|
|
267
289
|
|
|
268
|
-
|
|
290
|
+
/** fx 引用拾取会话置/清选区锚点(公式栏 → 活动实例透传;无活动实例时静默) */
|
|
291
|
+
function setSelectionAnchor(addr: CellAddress | null): void {
|
|
292
|
+
active?.grid.setSelectionAnchor(addr)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return { rebuildGrid, activateGrid, pruneCache, getGrid: () => active?.grid, setSelectionAnchor }
|
|
269
296
|
}
|
|
@@ -21,6 +21,8 @@ export interface SheetStateHooks {
|
|
|
21
21
|
activateGrid: () => void
|
|
22
22
|
/** sheet 列表变化(增删)后清理已删除 sheet 的缓存实例 */
|
|
23
23
|
pruneCache?: (sheets: ReadonlyArray<Sheet>) => void
|
|
24
|
+
/** 行列尺寸经门面写模型后同步活动 grid 引擎 table(菜单数值项画面即时生效) */
|
|
25
|
+
syncAxisSizes: (axis: 'row' | 'col', indexes: number[]) => void
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
type Emit = (event: 'active-sheet-change', payload: { sheet: Sheet; index: number }) => void
|
|
@@ -51,7 +53,9 @@ export function useSheetState(props: SheetProps, emit: Emit, hooks: SheetStateHo
|
|
|
51
53
|
() => workbook.value,
|
|
52
54
|
{
|
|
53
55
|
resolveGridSize: () =>
|
|
54
|
-
resolveRenderSize(props.rows, props.cols, activeSheet.value.rows, activeSheet.value.cols)
|
|
56
|
+
resolveRenderSize(props.rows, props.cols, activeSheet.value.rows, activeSheet.value.cols),
|
|
57
|
+
// 行高/列宽菜单写入模型后同步活动 grid 引擎尺寸(Sheet 尺寸 API 不发事件)
|
|
58
|
+
syncAxisSizes: (axis, indexes) => hooks.syncAxisSizes(axis, indexes)
|
|
55
59
|
}
|
|
56
60
|
)
|
|
57
61
|
|
|
@@ -107,15 +107,16 @@ export function useToolPopup(context: SheetContext, rootEl: ElRef) {
|
|
|
107
107
|
/** Ctrl/Cmd+F 打开 / 关闭查找条(与工具按钮同一 toggle 逻辑) */
|
|
108
108
|
function onGlobalKeydown(event: KeyboardEvent): void {
|
|
109
109
|
if (!(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== 'f') return
|
|
110
|
-
//
|
|
110
|
+
// 仅当事件源落在本实例容器内才响应(#6):容器外不得 preventDefault,
|
|
111
111
|
// 否则劫持浏览器原生查找(页面任意位置按 Ctrl+F 都被屏蔽);同页多实例
|
|
112
112
|
// 也各只响应自己容器内的按键,不再每个实例都弹出查找条
|
|
113
113
|
const root = rootEl.value
|
|
114
114
|
if (root) {
|
|
115
|
+
// 只认事件源:真实键盘事件 target 恒为焦点元素;activeElement 兜底会把
|
|
116
|
+
// 「容器程序化聚焦后(SheetGrid 挂载即聚焦)的异源合成按键」误判为本实例
|
|
117
|
+
// 按键,重新劫持容器外的浏览器查找
|
|
115
118
|
const target = event.target instanceof Node ? event.target : null
|
|
116
|
-
const
|
|
117
|
-
const inside =
|
|
118
|
-
(target !== null && root.contains(target)) || (active !== null && root.contains(active))
|
|
119
|
+
const inside = target !== null && root.contains(target)
|
|
119
120
|
// 例外:弹层 Teleport 到 body 级 #pop-container(不在 root 内),本实例弹层
|
|
120
121
|
// 打开期间焦点在弹层输入框里(如查找条内再按 Ctrl+F)——同样视为本实例的
|
|
121
122
|
// 按键,否则查找条无法 toggle 关闭,且放行浏览器原生查找盖在上面。
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,6 @@ export {
|
|
|
11
11
|
type SheetToolGroup
|
|
12
12
|
} from './tools/registry'
|
|
13
13
|
|
|
14
|
-
export { type SheetProps, type SheetEmits, type
|
|
14
|
+
export { type SheetProps, type SheetEmits, type SheetExposed } from './types'
|
|
15
15
|
|
|
16
16
|
export { USheet } from './components/sheet'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRange } from '@veltra/sheet-core/core/address.js'
|
|
2
2
|
import { Sheet } from '@veltra/sheet-core/core/sheet.js'
|
|
3
|
-
import { describe, expect, it } from '
|
|
3
|
+
import { describe, expect, it } from 'vite-plus/test'
|
|
4
4
|
|
|
5
5
|
import { classifySelectionStyleTarget, type SelectionStyleTarget } from '../apply-style'
|
|
6
6
|
import { createSheetContext } from '../context'
|
|
@@ -3,7 +3,7 @@ import { createRange } from '@veltra/sheet-core/core/address.js'
|
|
|
3
3
|
import '../builtin'
|
|
4
4
|
import { Sheet } from '@veltra/sheet-core/core/sheet.js'
|
|
5
5
|
import { Workbook } from '@veltra/sheet-core/core/workbook.js'
|
|
6
|
-
import { describe, expect, it, vi } from '
|
|
6
|
+
import { describe, expect, it, vi } from 'vite-plus/test'
|
|
7
7
|
|
|
8
8
|
import { createSheetContext } from '../context'
|
|
9
9
|
import { exportSheetCsvFile, exportWorkbookFile } from '../download'
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createRange } from '@veltra/sheet-core/core/address.js'
|
|
2
2
|
import { Sheet } from '@veltra/sheet-core/core/sheet.js'
|
|
3
|
-
import { describe, expect, it, vi } from '
|
|
3
|
+
import { describe, expect, it, vi } from 'vite-plus/test'
|
|
4
4
|
|
|
5
|
-
import { createSheetContext } from '../context'
|
|
5
|
+
import { createSheetContext, MIN_ROW_COL_SIZE } from '../context'
|
|
6
6
|
import type { SheetTool } from '../registry'
|
|
7
7
|
|
|
8
8
|
describe('SheetContext', () => {
|
|
@@ -180,6 +180,70 @@ describe('SheetContext', () => {
|
|
|
180
180
|
expect(sheet.getCellData({ row: 0, col: 0 })).toEqual({ v: 'keep', t: 's' })
|
|
181
181
|
})
|
|
182
182
|
|
|
183
|
+
it('行列尺寸门面:读取 + 按选区覆盖行/列批量应用,不进 undo', () => {
|
|
184
|
+
const sheet = new Sheet()
|
|
185
|
+
const ctx = createSheetContext(sheet)
|
|
186
|
+
|
|
187
|
+
// 未设置自定义尺寸时读取为 undefined(视图层用默认行高/列宽)
|
|
188
|
+
expect(ctx.getRowHeight(1)).toBeUndefined()
|
|
189
|
+
expect(ctx.getColWidth(2)).toBeUndefined()
|
|
190
|
+
|
|
191
|
+
ctx.selectRange(createRange({ row: 1, col: 2 }, { row: 3, col: 4 }))
|
|
192
|
+
ctx.setRowHeightBySelection(40)
|
|
193
|
+
ctx.setColWidthBySelection(120)
|
|
194
|
+
// 选区覆盖行 1..3 全部生效,行 0 不受影响
|
|
195
|
+
expect(ctx.getRowHeight(1)).toBe(40)
|
|
196
|
+
expect(ctx.getRowHeight(2)).toBe(40)
|
|
197
|
+
expect(ctx.getRowHeight(3)).toBe(40)
|
|
198
|
+
expect(ctx.getRowHeight(0)).toBeUndefined()
|
|
199
|
+
// 选区覆盖列 2..4 全部生效,列 5 不受影响
|
|
200
|
+
expect(ctx.getColWidth(2)).toBe(120)
|
|
201
|
+
expect(ctx.getColWidth(3)).toBe(120)
|
|
202
|
+
expect(ctx.getColWidth(4)).toBe(120)
|
|
203
|
+
expect(ctx.getColWidth(5)).toBeUndefined()
|
|
204
|
+
|
|
205
|
+
// 同 rowHeights/冻结先例:尺寸写入不进 undo 历史
|
|
206
|
+
expect(ctx.canUndo).toBe(false)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('行列尺寸门面:下限钳制到引擎最小值 20;空选区无操作', () => {
|
|
210
|
+
const sheet = new Sheet()
|
|
211
|
+
const ctx = createSheetContext(sheet)
|
|
212
|
+
|
|
213
|
+
ctx.selectRange(createRange({ row: 2, col: 3 }, { row: 2, col: 4 }))
|
|
214
|
+
ctx.setRowHeightBySelection(10)
|
|
215
|
+
ctx.setColWidthBySelection(5)
|
|
216
|
+
expect(ctx.getRowHeight(2)).toBe(MIN_ROW_COL_SIZE)
|
|
217
|
+
expect(ctx.getColWidth(3)).toBe(MIN_ROW_COL_SIZE)
|
|
218
|
+
expect(ctx.getColWidth(4)).toBe(MIN_ROW_COL_SIZE)
|
|
219
|
+
|
|
220
|
+
// 空选区:无写入目标,不产生任何尺寸
|
|
221
|
+
sheet.selection.clear()
|
|
222
|
+
ctx.setRowHeightBySelection(60)
|
|
223
|
+
ctx.setColWidthBySelection(200)
|
|
224
|
+
expect(ctx.getRowHeight(0)).toBeUndefined()
|
|
225
|
+
expect(ctx.getColWidth(0)).toBeUndefined()
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('行列尺寸门面:写入后经 syncAxisSizes 同步钩子(携带选区覆盖行/列;空选区不触发)', () => {
|
|
229
|
+
const sheet = new Sheet()
|
|
230
|
+
const syncAxisSizes = vi.fn()
|
|
231
|
+
const ctx = createSheetContext(sheet, undefined, { syncAxisSizes })
|
|
232
|
+
|
|
233
|
+
ctx.selectRange(createRange({ row: 1, col: 2 }, { row: 2, col: 3 }))
|
|
234
|
+
ctx.setRowHeightBySelection(40)
|
|
235
|
+
expect(syncAxisSizes).toHaveBeenCalledWith('row', [1, 2])
|
|
236
|
+
ctx.setColWidthBySelection(120)
|
|
237
|
+
expect(syncAxisSizes).toHaveBeenCalledWith('col', [2, 3])
|
|
238
|
+
|
|
239
|
+
// 空选区:模型无写入,同步钩子不触发
|
|
240
|
+
syncAxisSizes.mockClear()
|
|
241
|
+
sheet.selection.clear()
|
|
242
|
+
ctx.setRowHeightBySelection(60)
|
|
243
|
+
ctx.setColWidthBySelection(60)
|
|
244
|
+
expect(syncAxisSizes).not.toHaveBeenCalled()
|
|
245
|
+
})
|
|
246
|
+
|
|
183
247
|
it('图片门面:insertImage / removeImage / updateImage / getImages / onImageChange,经命令可 undo', () => {
|
|
184
248
|
const sheet = new Sheet()
|
|
185
249
|
const ctx = createSheetContext(sheet)
|
package/src/tools/context.ts
CHANGED
|
@@ -12,6 +12,9 @@ import type { Workbook } from '@veltra/sheet-core/core/workbook.js'
|
|
|
12
12
|
|
|
13
13
|
import { axisStyleItemsForRange, classifySelectionStyleTarget } from './apply-style'
|
|
14
14
|
|
|
15
|
+
/** 行列尺寸下限:对齐引擎 resize 最小值(`MIN_ROW_HEIGHT` / `MIN_COL_WIDTH` = 20) */
|
|
16
|
+
export const MIN_ROW_COL_SIZE = 20
|
|
17
|
+
|
|
15
18
|
/**
|
|
16
19
|
* SheetContext:工具的唯一操作门面。
|
|
17
20
|
*
|
|
@@ -100,6 +103,16 @@ export interface SheetContext {
|
|
|
100
103
|
/** 设置冻结行列数(不进 undo,同 rowHeights 先例) */
|
|
101
104
|
setFrozen(rows: number, cols: number): void
|
|
102
105
|
|
|
106
|
+
// ─── 行列尺寸(模型状态,不进 undo;随快照序列化) ─────────
|
|
107
|
+
/** 读取自定义行高;未设置返回 undefined(视图层用默认行高) */
|
|
108
|
+
getRowHeight(row: number): number | undefined
|
|
109
|
+
/** 读取自定义列宽;未设置返回 undefined(视图层用默认列宽) */
|
|
110
|
+
getColWidth(col: number): number | undefined
|
|
111
|
+
/** 按选区覆盖行批量设置行高(下限钳到 MIN_ROW_COL_SIZE;不进 undo,同 rowHeights 先例) */
|
|
112
|
+
setRowHeightBySelection(height: number): void
|
|
113
|
+
/** 按选区覆盖列批量设置列宽(下限钳到 MIN_ROW_COL_SIZE;不进 undo,同 colWidths 先例) */
|
|
114
|
+
setColWidthBySelection(width: number): void
|
|
115
|
+
|
|
103
116
|
// ─── 行列插入/删除(结构变更,可 undo) ──────────────────
|
|
104
117
|
/** 插入 count 行到 at 行之前 */
|
|
105
118
|
insertRows(at: number, count?: number): void
|
|
@@ -135,6 +148,12 @@ export interface SheetContextOptions {
|
|
|
135
148
|
* applyStyle / clearStyle 用其判定整行/整列;缺省时不走行列默认样式。
|
|
136
149
|
*/
|
|
137
150
|
resolveGridSize?: () => { rows: number; cols: number }
|
|
151
|
+
/**
|
|
152
|
+
* 行列尺寸写模型后的即时同步钩子:`Sheet.setRowHeight`/`setColWidth` 不发事件,
|
|
153
|
+
* 宿主经此把尺寸落到活动 SheetGrid 引擎 table(菜单数值项画面即时生效);
|
|
154
|
+
* 缺省(无头 / 测试直挂 Sheet)时仅写模型。
|
|
155
|
+
*/
|
|
156
|
+
syncAxisSizes?: (axis: 'row' | 'col', indexes: number[]) => void
|
|
138
157
|
}
|
|
139
158
|
|
|
140
159
|
/**
|
|
@@ -146,6 +165,7 @@ export interface SheetContextOptions {
|
|
|
146
165
|
* 上下文始终解析到**当前**工作簿(#2:旧实现取构造时值快照,宿主切换
|
|
147
166
|
* workbook prop 后导出工具仍导出旧工作簿)
|
|
148
167
|
* @param options.resolveGridSize 渲染尺寸解析器(整行/整列样式路由)
|
|
168
|
+
* @param options.syncAxisSizes 行列尺寸写模型后的即时同步钩子(USheet 接活动 grid)
|
|
149
169
|
*/
|
|
150
170
|
export function createSheetContext(
|
|
151
171
|
resolveSheet: Sheet | (() => Sheet),
|
|
@@ -156,6 +176,7 @@ export function createSheetContext(
|
|
|
156
176
|
const resolveWorkbook =
|
|
157
177
|
typeof workbook === 'function' ? workbook : workbook != null ? () => workbook : undefined
|
|
158
178
|
const resolveGridSize = options?.resolveGridSize
|
|
179
|
+
const syncAxisSizes = options?.syncAxisSizes
|
|
159
180
|
const resolveTarget = (range: CellRange) => {
|
|
160
181
|
const size = resolveGridSize?.()
|
|
161
182
|
return size ? classifySelectionStyleTarget(range, size.rows, size.cols) : 'cell'
|
|
@@ -216,6 +237,32 @@ export function createSheetContext(
|
|
|
216
237
|
return sheet().frozen
|
|
217
238
|
},
|
|
218
239
|
setFrozen: (rows, cols) => sheet().setFrozen(rows, cols),
|
|
240
|
+
|
|
241
|
+
getRowHeight: (row) => sheet().getRowHeight(row),
|
|
242
|
+
getColWidth: (col) => sheet().getColWidth(col),
|
|
243
|
+
setRowHeightBySelection: (height) => {
|
|
244
|
+
const size = Math.max(MIN_ROW_COL_SIZE, height)
|
|
245
|
+
const rows: number[] = []
|
|
246
|
+
for (const range of sheet().getSelection().ranges) {
|
|
247
|
+
for (let row = range.start.row; row <= range.end.row; row++) {
|
|
248
|
+
sheet().setRowHeight(row, size)
|
|
249
|
+
rows.push(row)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// 写模型后即时同步活动 grid(Sheet.setRowHeight 不发事件)
|
|
253
|
+
if (rows.length > 0) syncAxisSizes?.('row', rows)
|
|
254
|
+
},
|
|
255
|
+
setColWidthBySelection: (width) => {
|
|
256
|
+
const size = Math.max(MIN_ROW_COL_SIZE, width)
|
|
257
|
+
const cols: number[] = []
|
|
258
|
+
for (const range of sheet().getSelection().ranges) {
|
|
259
|
+
for (let col = range.start.col; col <= range.end.col; col++) {
|
|
260
|
+
sheet().setColWidth(col, size)
|
|
261
|
+
cols.push(col)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (cols.length > 0) syncAxisSizes?.('col', cols)
|
|
265
|
+
},
|
|
219
266
|
insertRows: (at, count) => sheet().insertRows(at, count),
|
|
220
267
|
insertCols: (at, count) => sheet().insertCols(at, count),
|
|
221
268
|
deleteRows: (at, count) => sheet().deleteRows(at, count),
|
package/src/types/sheet.ts
CHANGED
|
@@ -4,7 +4,9 @@ import type {
|
|
|
4
4
|
ResolveCellRenderer,
|
|
5
5
|
ResolveCellStyleHook,
|
|
6
6
|
ResolveDisplayValue,
|
|
7
|
-
SheetGrid
|
|
7
|
+
SheetGrid,
|
|
8
|
+
SheetGridEditorsOptions,
|
|
9
|
+
SheetGridHeaderOptions
|
|
8
10
|
} from '@veltra/sheet-core/grid'
|
|
9
11
|
import type { DeconstructValue } from '@veltra/utils'
|
|
10
12
|
import type { ComputedRef } from 'vue'
|
|
@@ -20,7 +22,7 @@ export interface SheetProps {
|
|
|
20
22
|
/** 渲染列数,默认 26(A..Z) */
|
|
21
23
|
cols?: number
|
|
22
24
|
/**
|
|
23
|
-
* 显示值覆盖(设计态 Binding Placeholder
|
|
25
|
+
* 显示值覆盖(设计态 Binding Placeholder 等):覆盖引擎视口显示文本,不写 CellData.v
|
|
24
26
|
*/
|
|
25
27
|
resolveDisplayValue?: ResolveDisplayValue
|
|
26
28
|
/**
|
|
@@ -28,11 +30,21 @@ export interface SheetProps {
|
|
|
28
30
|
*/
|
|
29
31
|
resolveCellStyle?: ResolveCellStyleHook
|
|
30
32
|
/**
|
|
31
|
-
* 动态单元格渲染(ADR-0004
|
|
32
|
-
*
|
|
33
|
-
*
|
|
33
|
+
* 动态单元格渲染(ADR-0004):视口布局时按格自定义渲染形态(返回引擎
|
|
34
|
+
* `CellRenderer`,见 `@veltra/sheet-core/grid`),返回 undefined 回落默认渲染;
|
|
35
|
+
* 不写模型、不进快照
|
|
34
36
|
*/
|
|
35
37
|
resolveCellRenderer?: ResolveCellRenderer
|
|
38
|
+
/**
|
|
39
|
+
* 列头机制(透传 SheetGrid):按列列头标题与表头自定义 DOM 渲染,不传保持
|
|
40
|
+
* 缺省字母表头;传值变化(引用更替)触发网格重建
|
|
41
|
+
*/
|
|
42
|
+
header?: SheetGridHeaderOptions
|
|
43
|
+
/**
|
|
44
|
+
* 类型化编辑器机制(透传 SheetGrid):多编辑器注册与按格路由,不传保持统一
|
|
45
|
+
* 文本编辑器;readonly 下忽略;传值变化(引用更替)触发网格重建
|
|
46
|
+
*/
|
|
47
|
+
editors?: SheetGridEditorsOptions
|
|
36
48
|
/** 是否显示工具栏,默认 true */
|
|
37
49
|
showToolbar?: boolean
|
|
38
50
|
/** 是否显示顶部公式栏(名称框 + fx 输入栏),默认 true */
|
|
@@ -45,11 +57,18 @@ export interface SheetProps {
|
|
|
45
57
|
showColHeader?: boolean
|
|
46
58
|
/** 只读预览(关闭编辑回写、填充柄等写入口) */
|
|
47
59
|
readonly?: boolean
|
|
60
|
+
/**
|
|
61
|
+
* 列宽拖拽(透传 SheetGrid):readonly 下置 true 仅放开列头 resize 手柄,编辑仍
|
|
62
|
+
* 关闭;缺省 false 行为不变。变化触发网格重建(构造期选项)
|
|
63
|
+
*/
|
|
64
|
+
colResize?: boolean
|
|
48
65
|
}
|
|
49
66
|
|
|
50
67
|
export interface SheetEmits {
|
|
51
68
|
/** 激活 sheet 切换(点击 tab 或宿主调用 workbook.activateSheet) */
|
|
52
69
|
(name: 'active-sheet-change', payload: { sheet: Sheet; index: number }): void
|
|
70
|
+
/** 列宽拖拽落定(写模型之后):载荷含列索引与最终宽度 */
|
|
71
|
+
(name: 'col-resize-end', payload: { col: number; width: number }): void
|
|
53
72
|
}
|
|
54
73
|
|
|
55
74
|
/** 在组件内部引用 */
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { InjectionKey, Ref } from 'vue'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* xlsx 解析中状态(历史 inject key,现由 import-file 直接写 sheet.vue 持有的 ref)。
|
|
5
|
-
* 保留导出以便测试或扩展仍可 provide/inject。
|
|
6
|
-
*/
|
|
7
|
-
export const SHEET_PARSING_KEY: InjectionKey<Ref<boolean>> = Symbol('sheet-parsing')
|
|
8
|
-
|
|
9
|
-
/** 解析进度(worker 分片构建期间 done/total;readXlsx 同步解析段无进度) */
|
|
10
|
-
export const SHEET_PARSE_PROGRESS_KEY: InjectionKey<Ref<{ done: number; total: number }>> =
|
|
11
|
-
Symbol('sheet-parse-progress')
|