@veltra/sheet 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/fill.d.ts +34 -0
- package/dist/core/fill.js +320 -0
- package/dist/core/fill.js.map +1 -0
- package/dist/core/formula/dependency-graph.d.ts +1 -1
- package/dist/core/sheet.d.ts +13 -2
- package/dist/core/sheet.js +22 -0
- package/dist/core/sheet.js.map +1 -1
- package/dist/grid/sheet-grid.d.ts +23 -1
- package/dist/grid/sheet-grid.js +109 -8
- package/dist/grid/sheet-grid.js.map +1 -1
- package/dist/grid/vtable-theme.js +80 -0
- package/dist/grid/vtable-theme.js.map +1 -0
- package/dist/index.d.ts +7 -6
- package/dist/index.js +2 -1
- package/dist/tools/context.d.ts +2 -2
- package/dist/vue/sheet.js +27 -1
- package/dist/vue/sheet.js.map +1 -1
- package/dist/vue/style.d.ts +1 -1
- package/dist/vue/style.js +1 -0
- package/package.json +2 -1
- package/src/core/__test__/fill.test.ts +140 -0
- package/src/core/fill.ts +314 -0
- package/src/core/sheet.ts +25 -0
- package/src/env.d.ts +3 -0
- package/src/grid/__test__/sheet-grid.test.ts +98 -0
- package/src/grid/sheet-grid.ts +133 -9
- package/src/grid/vtable-theme.ts +61 -0
- package/src/index.ts +1 -0
- package/src/vue/sheet.vue +42 -2
- package/src/vue/style.ts +1 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { CellAddress, CellRange } from "./address.js";
|
|
2
|
+
import { CellData } from "./cell-store.js";
|
|
3
|
+
import { SetCellValueItem } from "./command/set-cell-value.js";
|
|
4
|
+
//#region src/core/fill.d.ts
|
|
5
|
+
/** 填充柄拖拽方向(与 VTable drag_fill_handle_end 一致) */
|
|
6
|
+
type FillDirection = 'top' | 'bottom' | 'left' | 'right';
|
|
7
|
+
interface GenerateFillOptions {
|
|
8
|
+
/** 拖拽前的源选区(模型坐标) */
|
|
9
|
+
source: CellRange;
|
|
10
|
+
/** 需要写入的目标区(不含源区,模型坐标) */
|
|
11
|
+
target: CellRange;
|
|
12
|
+
/** 填充方向 */
|
|
13
|
+
direction: FillDirection;
|
|
14
|
+
/** 读取源区单元格(原始存储语义) */
|
|
15
|
+
getCellData: (addr: CellAddress) => CellData | undefined;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 由源选区 + 拖拽后扩展选区 + 方向,计算需要填充的目标区(模型坐标)。
|
|
19
|
+
* 算法对齐 VTable 官方 fill-handle 示例(扩展选区含源区,目标为扩展部分)。
|
|
20
|
+
*/
|
|
21
|
+
declare function computeFillTargetRange(source: CellRange, direction: FillDirection, expanded: CellRange): CellRange | null;
|
|
22
|
+
/**
|
|
23
|
+
* 生成填充写入项(tile / 数字日期等差 / 公式相对引用位移)。
|
|
24
|
+
* 调用方一次 `sheet.setCells(items)` 即可(单 undo 单元)。
|
|
25
|
+
*/
|
|
26
|
+
declare function generateFill(options: GenerateFillOptions): SetCellValueItem[];
|
|
27
|
+
/**
|
|
28
|
+
* 公式引用字符串级位移:尊重 `$` 绝对行列;出界 → `#REF!`。
|
|
29
|
+
* 跳过双引号字符串字面量,避免改写文本中的 A1 片段。
|
|
30
|
+
*/
|
|
31
|
+
declare function shiftFormulaRefs(formula: string, deltaRow: number, deltaCol: number): string;
|
|
32
|
+
//#endregion
|
|
33
|
+
export { FillDirection, GenerateFillOptions, computeFillTargetRange, generateFill, shiftFormulaRefs };
|
|
34
|
+
//# sourceMappingURL=fill.d.ts.map
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { colIndexToName, colNameToIndex, createRange, iterateRange } from "./address.js";
|
|
2
|
+
//#region src/core/fill.ts
|
|
3
|
+
/**
|
|
4
|
+
* 由源选区 + 拖拽后扩展选区 + 方向,计算需要填充的目标区(模型坐标)。
|
|
5
|
+
* 算法对齐 VTable 官方 fill-handle 示例(扩展选区含源区,目标为扩展部分)。
|
|
6
|
+
*/
|
|
7
|
+
function computeFillTargetRange(source, direction, expanded) {
|
|
8
|
+
const src = createRange(source.start, source.end);
|
|
9
|
+
const exp = createRange(expanded.start, expanded.end);
|
|
10
|
+
if (direction === "bottom") {
|
|
11
|
+
if (exp.end.row <= src.end.row) return null;
|
|
12
|
+
return {
|
|
13
|
+
start: {
|
|
14
|
+
row: src.end.row + 1,
|
|
15
|
+
col: src.start.col
|
|
16
|
+
},
|
|
17
|
+
end: {
|
|
18
|
+
row: exp.end.row,
|
|
19
|
+
col: src.end.col
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (direction === "top") {
|
|
24
|
+
if (exp.start.row >= src.start.row) return null;
|
|
25
|
+
return {
|
|
26
|
+
start: {
|
|
27
|
+
row: exp.start.row,
|
|
28
|
+
col: src.start.col
|
|
29
|
+
},
|
|
30
|
+
end: {
|
|
31
|
+
row: src.start.row - 1,
|
|
32
|
+
col: src.end.col
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (direction === "right") {
|
|
37
|
+
if (exp.end.col <= src.end.col) return null;
|
|
38
|
+
return {
|
|
39
|
+
start: {
|
|
40
|
+
row: src.start.row,
|
|
41
|
+
col: src.end.col + 1
|
|
42
|
+
},
|
|
43
|
+
end: {
|
|
44
|
+
row: src.end.row,
|
|
45
|
+
col: exp.end.col
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (exp.start.col >= src.start.col) return null;
|
|
50
|
+
return {
|
|
51
|
+
start: {
|
|
52
|
+
row: src.start.row,
|
|
53
|
+
col: exp.start.col
|
|
54
|
+
},
|
|
55
|
+
end: {
|
|
56
|
+
row: src.end.row,
|
|
57
|
+
col: src.start.col - 1
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 生成填充写入项(tile / 数字日期等差 / 公式相对引用位移)。
|
|
63
|
+
* 调用方一次 `sheet.setCells(items)` 即可(单 undo 单元)。
|
|
64
|
+
*/
|
|
65
|
+
function generateFill(options) {
|
|
66
|
+
const { source, target, direction, getCellData } = options;
|
|
67
|
+
const src = createRange(source.start, source.end);
|
|
68
|
+
const tgt = createRange(target.start, target.end);
|
|
69
|
+
const srcRows = src.end.row - src.start.row + 1;
|
|
70
|
+
const srcCols = src.end.col - src.start.col + 1;
|
|
71
|
+
const vertical = direction === "top" || direction === "bottom";
|
|
72
|
+
const seriesByLane = /* @__PURE__ */ new Map();
|
|
73
|
+
if (vertical) for (let col = src.start.col; col <= src.end.col; col++) {
|
|
74
|
+
const values = [];
|
|
75
|
+
let cellType;
|
|
76
|
+
let ok = true;
|
|
77
|
+
for (let row = src.start.row; row <= src.end.row; row++) {
|
|
78
|
+
const num = readSeriesNumber(getCellData({
|
|
79
|
+
row,
|
|
80
|
+
col
|
|
81
|
+
}));
|
|
82
|
+
if (num == null) {
|
|
83
|
+
ok = false;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
values.push(num.value);
|
|
87
|
+
cellType = num.t;
|
|
88
|
+
}
|
|
89
|
+
seriesByLane.set(col, ok ? makeSeriesInfo(values, cellType) : null);
|
|
90
|
+
}
|
|
91
|
+
else for (let row = src.start.row; row <= src.end.row; row++) {
|
|
92
|
+
const values = [];
|
|
93
|
+
let cellType;
|
|
94
|
+
let ok = true;
|
|
95
|
+
for (let col = src.start.col; col <= src.end.col; col++) {
|
|
96
|
+
const num = readSeriesNumber(getCellData({
|
|
97
|
+
row,
|
|
98
|
+
col
|
|
99
|
+
}));
|
|
100
|
+
if (num == null) {
|
|
101
|
+
ok = false;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
values.push(num.value);
|
|
105
|
+
cellType = num.t;
|
|
106
|
+
}
|
|
107
|
+
seriesByLane.set(row, ok ? makeSeriesInfo(values, cellType) : null);
|
|
108
|
+
}
|
|
109
|
+
const items = [];
|
|
110
|
+
for (const addr of iterateRange(tgt)) {
|
|
111
|
+
const rowOffset = positiveMod(addr.row - src.start.row, srcRows);
|
|
112
|
+
const colOffset = positiveMod(addr.col - src.start.col, srcCols);
|
|
113
|
+
const srcAddr = {
|
|
114
|
+
row: src.start.row + rowOffset,
|
|
115
|
+
col: src.start.col + colOffset
|
|
116
|
+
};
|
|
117
|
+
const srcData = getCellData(srcAddr);
|
|
118
|
+
if (srcData?.f != null && srcData.f !== "") {
|
|
119
|
+
const deltaRow = addr.row - srcAddr.row;
|
|
120
|
+
const deltaCol = addr.col - srcAddr.col;
|
|
121
|
+
items.push({
|
|
122
|
+
addr,
|
|
123
|
+
data: { f: shiftFormulaRefs(srcData.f, deltaRow, deltaCol) }
|
|
124
|
+
});
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const lane = vertical ? srcAddr.col : srcAddr.row;
|
|
128
|
+
const series = seriesByLane.get(lane);
|
|
129
|
+
if (series) {
|
|
130
|
+
const index = vertical ? addr.row - src.start.row : addr.col - src.start.col;
|
|
131
|
+
const value = series.first + index * series.delta;
|
|
132
|
+
items.push({
|
|
133
|
+
addr,
|
|
134
|
+
data: {
|
|
135
|
+
v: value,
|
|
136
|
+
t: series.t ?? "n"
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
items.push({
|
|
142
|
+
addr,
|
|
143
|
+
data: cloneCellData(srcData)
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return items;
|
|
147
|
+
}
|
|
148
|
+
function makeSeriesInfo(values, t) {
|
|
149
|
+
const first = values[0];
|
|
150
|
+
if (values.length === 1) return {
|
|
151
|
+
first,
|
|
152
|
+
delta: 1,
|
|
153
|
+
t
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
first,
|
|
157
|
+
delta: (values[values.length - 1] - first) / (values.length - 1),
|
|
158
|
+
t
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** 可参与等差序列的数字/日期格;公式格与其它类型返回 null */
|
|
162
|
+
function readSeriesNumber(data) {
|
|
163
|
+
if (!data) return null;
|
|
164
|
+
if (data.f != null && data.f !== "") return null;
|
|
165
|
+
if (typeof data.v !== "number" || !Number.isFinite(data.v)) return null;
|
|
166
|
+
if (data.t != null && data.t !== "n" && data.t !== "d") return null;
|
|
167
|
+
return {
|
|
168
|
+
value: data.v,
|
|
169
|
+
t: data.t
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function cloneCellData(data) {
|
|
173
|
+
if (!data) return void 0;
|
|
174
|
+
const cloned = {};
|
|
175
|
+
if (data.v !== void 0) cloned.v = data.v;
|
|
176
|
+
if (data.t !== void 0) cloned.t = data.t;
|
|
177
|
+
if (data.f !== void 0) cloned.f = data.f;
|
|
178
|
+
return cloned;
|
|
179
|
+
}
|
|
180
|
+
function positiveMod(n, mod) {
|
|
181
|
+
return (n % mod + mod) % mod;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* 公式引用字符串级位移:尊重 `$` 绝对行列;出界 → `#REF!`。
|
|
185
|
+
* 跳过双引号字符串字面量,避免改写文本中的 A1 片段。
|
|
186
|
+
*/
|
|
187
|
+
function shiftFormulaRefs(formula, deltaRow, deltaCol) {
|
|
188
|
+
if (deltaRow === 0 && deltaCol === 0) return formula;
|
|
189
|
+
let result = "";
|
|
190
|
+
let i = 0;
|
|
191
|
+
while (i < formula.length) {
|
|
192
|
+
const ch = formula[i];
|
|
193
|
+
if (ch === "\"") {
|
|
194
|
+
const end = skipExcelString(formula, i);
|
|
195
|
+
result += formula.slice(i, end);
|
|
196
|
+
i = end;
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const sheetMatch = matchSheetPrefix(formula, i);
|
|
200
|
+
const addrMatch = matchCellOrRange(formula, sheetMatch ? sheetMatch.end : i);
|
|
201
|
+
if (addrMatch && (sheetMatch || isRefBoundary(formula, i - 1))) {
|
|
202
|
+
const sheetPrefix = sheetMatch ? formula.slice(sheetMatch.start, sheetMatch.end) : "";
|
|
203
|
+
if (sheetMatch && !isRefBoundary(formula, sheetMatch.start - 1)) {
|
|
204
|
+
result += ch;
|
|
205
|
+
i++;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const shifted = shiftAddrPart(addrMatch.text, deltaRow, deltaCol);
|
|
209
|
+
result += sheetPrefix + shifted;
|
|
210
|
+
i = addrMatch.end;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
result += ch;
|
|
214
|
+
i++;
|
|
215
|
+
}
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
function skipExcelString(text, start) {
|
|
219
|
+
let i = start + 1;
|
|
220
|
+
while (i < text.length) {
|
|
221
|
+
if (text[i] === "\"") {
|
|
222
|
+
if (text[i + 1] === "\"") {
|
|
223
|
+
i += 2;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
return i + 1;
|
|
227
|
+
}
|
|
228
|
+
i++;
|
|
229
|
+
}
|
|
230
|
+
return text.length;
|
|
231
|
+
}
|
|
232
|
+
function matchSheetPrefix(text, start) {
|
|
233
|
+
if (text[start] === "'") {
|
|
234
|
+
let i = start + 1;
|
|
235
|
+
while (i < text.length) {
|
|
236
|
+
if (text[i] === "'") {
|
|
237
|
+
if (text[i + 1] === "'") {
|
|
238
|
+
i += 2;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (text[i + 1] === "!") return {
|
|
242
|
+
start,
|
|
243
|
+
end: i + 2
|
|
244
|
+
};
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
i++;
|
|
248
|
+
}
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
if (!/[A-Za-z_]/.test(text[start] ?? "")) return null;
|
|
252
|
+
let i = start + 1;
|
|
253
|
+
while (i < text.length && /[\w.]/.test(text[i])) i++;
|
|
254
|
+
if (text[i] === "!") return {
|
|
255
|
+
start,
|
|
256
|
+
end: i + 1
|
|
257
|
+
};
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
const CELL_REF_RE = /^(\$?)([A-Za-z]+)(\$?)([1-9]\d*)/;
|
|
261
|
+
function matchCellOrRange(text, start) {
|
|
262
|
+
const first = matchOneCell(text, start);
|
|
263
|
+
if (!first) return null;
|
|
264
|
+
if (text[first.end] === ":") {
|
|
265
|
+
const second = matchOneCell(text, first.end + 1);
|
|
266
|
+
if (!second) return {
|
|
267
|
+
text: first.text,
|
|
268
|
+
end: first.end
|
|
269
|
+
};
|
|
270
|
+
return {
|
|
271
|
+
text: text.slice(start, second.end),
|
|
272
|
+
end: second.end
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
return first;
|
|
276
|
+
}
|
|
277
|
+
function matchOneCell(text, start) {
|
|
278
|
+
const slice = text.slice(start);
|
|
279
|
+
const m = CELL_REF_RE.exec(slice);
|
|
280
|
+
if (!m) return null;
|
|
281
|
+
const end = start + m[0].length;
|
|
282
|
+
const next = text[end];
|
|
283
|
+
if (next && (/[A-Za-z]/.test(next) || next === "(")) return null;
|
|
284
|
+
return {
|
|
285
|
+
text: m[0],
|
|
286
|
+
end
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function isRefBoundary(text, index) {
|
|
290
|
+
if (index < 0) return true;
|
|
291
|
+
const ch = text[index];
|
|
292
|
+
return !/[A-Za-z0-9_$]/.test(ch);
|
|
293
|
+
}
|
|
294
|
+
function shiftAddrPart(part, deltaRow, deltaCol) {
|
|
295
|
+
if (part.includes(":")) {
|
|
296
|
+
const [a, b] = part.split(":");
|
|
297
|
+
const left = shiftOneRef(a, deltaRow, deltaCol);
|
|
298
|
+
const right = shiftOneRef(b, deltaRow, deltaCol);
|
|
299
|
+
if (left === "#REF!" || right === "#REF!") return "#REF!";
|
|
300
|
+
return `${left}:${right}`;
|
|
301
|
+
}
|
|
302
|
+
return shiftOneRef(part, deltaRow, deltaCol);
|
|
303
|
+
}
|
|
304
|
+
function shiftOneRef(ref, deltaRow, deltaCol) {
|
|
305
|
+
const m = CELL_REF_RE.exec(ref);
|
|
306
|
+
if (!m) return ref;
|
|
307
|
+
const colAbs = m[1] === "$";
|
|
308
|
+
const rowAbs = m[3] === "$";
|
|
309
|
+
let col = colNameToIndex(m[2]);
|
|
310
|
+
let row = Number.parseInt(m[4], 10) - 1;
|
|
311
|
+
if (col < 0 || row < 0) return "#REF!";
|
|
312
|
+
if (!colAbs) col += deltaCol;
|
|
313
|
+
if (!rowAbs) row += deltaRow;
|
|
314
|
+
if (col < 0 || row < 0) return "#REF!";
|
|
315
|
+
return `${colAbs ? "$" : ""}${colIndexToName(col)}${rowAbs ? "$" : ""}${row + 1}`;
|
|
316
|
+
}
|
|
317
|
+
//#endregion
|
|
318
|
+
export { computeFillTargetRange, generateFill, shiftFormulaRefs };
|
|
319
|
+
|
|
320
|
+
//# sourceMappingURL=fill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fill.js","names":[],"sources":["../../src/core/fill.ts"],"sourcesContent":["import type { CellAddress, CellRange } from './address'\nimport { colIndexToName, colNameToIndex, createRange, iterateRange } from './address'\nimport type { CellData, CellType } from './cell-store'\nimport type { SetCellValueItem } from './command/set-cell-value'\n\n/** 填充柄拖拽方向(与 VTable drag_fill_handle_end 一致) */\nexport type FillDirection = 'top' | 'bottom' | 'left' | 'right'\n\nexport interface GenerateFillOptions {\n /** 拖拽前的源选区(模型坐标) */\n source: CellRange\n /** 需要写入的目标区(不含源区,模型坐标) */\n target: CellRange\n /** 填充方向 */\n direction: FillDirection\n /** 读取源区单元格(原始存储语义) */\n getCellData: (addr: CellAddress) => CellData | undefined\n}\n\n/**\n * 由源选区 + 拖拽后扩展选区 + 方向,计算需要填充的目标区(模型坐标)。\n * 算法对齐 VTable 官方 fill-handle 示例(扩展选区含源区,目标为扩展部分)。\n */\nexport function computeFillTargetRange(\n source: CellRange,\n direction: FillDirection,\n expanded: CellRange\n): CellRange | null {\n const src = createRange(source.start, source.end)\n const exp = createRange(expanded.start, expanded.end)\n\n if (direction === 'bottom') {\n if (exp.end.row <= src.end.row) return null\n return {\n start: { row: src.end.row + 1, col: src.start.col },\n end: { row: exp.end.row, col: src.end.col }\n }\n }\n if (direction === 'top') {\n if (exp.start.row >= src.start.row) return null\n return {\n start: { row: exp.start.row, col: src.start.col },\n end: { row: src.start.row - 1, col: src.end.col }\n }\n }\n if (direction === 'right') {\n if (exp.end.col <= src.end.col) return null\n return {\n start: { row: src.start.row, col: src.end.col + 1 },\n end: { row: src.end.row, col: exp.end.col }\n }\n }\n // left\n if (exp.start.col >= src.start.col) return null\n return {\n start: { row: src.start.row, col: exp.start.col },\n end: { row: src.end.row, col: src.start.col - 1 }\n }\n}\n\n/**\n * 生成填充写入项(tile / 数字日期等差 / 公式相对引用位移)。\n * 调用方一次 `sheet.setCells(items)` 即可(单 undo 单元)。\n */\nexport function generateFill(options: GenerateFillOptions): SetCellValueItem[] {\n const { source, target, direction, getCellData } = options\n const src = createRange(source.start, source.end)\n const tgt = createRange(target.start, target.end)\n const srcRows = src.end.row - src.start.row + 1\n const srcCols = src.end.col - src.start.col + 1\n const vertical = direction === 'top' || direction === 'bottom'\n\n const seriesByLane = new Map<number, SeriesInfo | null>()\n if (vertical) {\n for (let col = src.start.col; col <= src.end.col; col++) {\n const values: number[] = []\n let cellType: CellType | undefined\n let ok = true\n for (let row = src.start.row; row <= src.end.row; row++) {\n const data = getCellData({ row, col })\n const num = readSeriesNumber(data)\n if (num == null) {\n ok = false\n break\n }\n values.push(num.value)\n cellType = num.t\n }\n seriesByLane.set(col, ok ? makeSeriesInfo(values, cellType) : null)\n }\n } else {\n for (let row = src.start.row; row <= src.end.row; row++) {\n const values: number[] = []\n let cellType: CellType | undefined\n let ok = true\n for (let col = src.start.col; col <= src.end.col; col++) {\n const data = getCellData({ row, col })\n const num = readSeriesNumber(data)\n if (num == null) {\n ok = false\n break\n }\n values.push(num.value)\n cellType = num.t\n }\n seriesByLane.set(row, ok ? makeSeriesInfo(values, cellType) : null)\n }\n }\n\n const items: SetCellValueItem[] = []\n for (const addr of iterateRange(tgt)) {\n const rowOffset = positiveMod(addr.row - src.start.row, srcRows)\n const colOffset = positiveMod(addr.col - src.start.col, srcCols)\n const srcAddr: CellAddress = { row: src.start.row + rowOffset, col: src.start.col + colOffset }\n const srcData = getCellData(srcAddr)\n\n if (srcData?.f != null && srcData.f !== '') {\n const deltaRow = addr.row - srcAddr.row\n const deltaCol = addr.col - srcAddr.col\n items.push({ addr, data: { f: shiftFormulaRefs(srcData.f, deltaRow, deltaCol) } })\n continue\n }\n\n const lane = vertical ? srcAddr.col : srcAddr.row\n const series = seriesByLane.get(lane)\n if (series) {\n const index = vertical ? addr.row - src.start.row : addr.col - src.start.col\n const value = series.first + index * series.delta\n items.push({ addr, data: { v: value, t: series.t ?? 'n' } })\n continue\n }\n\n // tile 复制(含空格 → 清除)\n items.push({ addr, data: cloneCellData(srcData) })\n }\n return items\n}\n\ninterface SeriesInfo {\n first: number\n delta: number\n t?: CellType\n}\n\nfunction makeSeriesInfo(values: number[], t?: CellType): SeriesInfo {\n const first = values[0]!\n if (values.length === 1) {\n return { first, delta: 1, t }\n }\n const delta = (values[values.length - 1]! - first) / (values.length - 1)\n return { first, delta, t }\n}\n\n/** 可参与等差序列的数字/日期格;公式格与其它类型返回 null */\nfunction readSeriesNumber(data: CellData | undefined): { value: number; t?: CellType } | null {\n if (!data) return null\n if (data.f != null && data.f !== '') return null\n if (typeof data.v !== 'number' || !Number.isFinite(data.v)) return null\n if (data.t != null && data.t !== 'n' && data.t !== 'd') return null\n return { value: data.v, t: data.t }\n}\n\nfunction cloneCellData(data: CellData | undefined): CellData | undefined {\n if (!data) return undefined\n const cloned: CellData = {}\n if (data.v !== undefined) cloned.v = data.v\n if (data.t !== undefined) cloned.t = data.t\n if (data.f !== undefined) cloned.f = data.f\n return cloned\n}\n\nfunction positiveMod(n: number, mod: number): number {\n return ((n % mod) + mod) % mod\n}\n\n/**\n * 公式引用字符串级位移:尊重 `$` 绝对行列;出界 → `#REF!`。\n * 跳过双引号字符串字面量,避免改写文本中的 A1 片段。\n */\nexport function shiftFormulaRefs(formula: string, deltaRow: number, deltaCol: number): string {\n if (deltaRow === 0 && deltaCol === 0) return formula\n\n let result = ''\n let i = 0\n while (i < formula.length) {\n const ch = formula[i]!\n if (ch === '\"') {\n const end = skipExcelString(formula, i)\n result += formula.slice(i, end)\n i = end\n continue\n }\n\n const sheetMatch = matchSheetPrefix(formula, i)\n const addrStart = sheetMatch ? sheetMatch.end : i\n const addrMatch = matchCellOrRange(formula, addrStart)\n if (addrMatch && (sheetMatch || isRefBoundary(formula, i - 1))) {\n const sheetPrefix = sheetMatch ? formula.slice(sheetMatch.start, sheetMatch.end) : ''\n // 有 sheet 前缀时,前缀前也需是边界\n if (sheetMatch && !isRefBoundary(formula, sheetMatch.start - 1)) {\n result += ch\n i++\n continue\n }\n const shifted = shiftAddrPart(addrMatch.text, deltaRow, deltaCol)\n result += sheetPrefix + shifted\n i = addrMatch.end\n continue\n }\n\n result += ch\n i++\n }\n return result\n}\n\nfunction skipExcelString(text: string, start: number): number {\n // start 指向开引号;Excel 字符串内 \"\" 为转义\n let i = start + 1\n while (i < text.length) {\n if (text[i] === '\"') {\n if (text[i + 1] === '\"') {\n i += 2\n continue\n }\n return i + 1\n }\n i++\n }\n return text.length\n}\n\nfunction matchSheetPrefix(text: string, start: number): { start: number; end: number } | null {\n if (text[start] === \"'\") {\n let i = start + 1\n while (i < text.length) {\n if (text[i] === \"'\") {\n if (text[i + 1] === \"'\") {\n i += 2\n continue\n }\n if (text[i + 1] === '!') {\n return { start, end: i + 2 }\n }\n return null\n }\n i++\n }\n return null\n }\n\n // 简单表名:字母/下划线开头\n if (!/[A-Za-z_]/.test(text[start] ?? '')) return null\n let i = start + 1\n while (i < text.length && /[\\w.]/.test(text[i]!)) i++\n if (text[i] === '!') return { start, end: i + 1 }\n return null\n}\n\nconst CELL_REF_RE = /^(\\$?)([A-Za-z]+)(\\$?)([1-9]\\d*)/\n\nfunction matchCellOrRange(text: string, start: number): { text: string; end: number } | null {\n const first = matchOneCell(text, start)\n if (!first) return null\n if (text[first.end] === ':') {\n const second = matchOneCell(text, first.end + 1)\n if (!second) return { text: first.text, end: first.end }\n return { text: text.slice(start, second.end), end: second.end }\n }\n return first\n}\n\nfunction matchOneCell(text: string, start: number): { text: string; end: number } | null {\n const slice = text.slice(start)\n const m = CELL_REF_RE.exec(slice)\n if (!m) return null\n const end = start + m[0].length\n const next = text[end]\n // 行号后紧跟字母 → 不是 A1;紧跟 `(` → 函数名(如 LOG10()\n if (next && (/[A-Za-z]/.test(next) || next === '(')) return null\n return { text: m[0], end }\n}\n\nfunction isRefBoundary(text: string, index: number): boolean {\n if (index < 0) return true\n const ch = text[index]!\n // 引用前不能是字母/数字(避免 FOOA1);`!` 已由 sheet 前缀消费\n return !/[A-Za-z0-9_$]/.test(ch)\n}\n\nfunction shiftAddrPart(part: string, deltaRow: number, deltaCol: number): string {\n if (part.includes(':')) {\n const [a, b] = part.split(':')\n const left = shiftOneRef(a!, deltaRow, deltaCol)\n const right = shiftOneRef(b!, deltaRow, deltaCol)\n if (left === '#REF!' || right === '#REF!') return '#REF!'\n return `${left}:${right}`\n }\n return shiftOneRef(part, deltaRow, deltaCol)\n}\n\nfunction shiftOneRef(ref: string, deltaRow: number, deltaCol: number): string {\n const m = CELL_REF_RE.exec(ref)\n if (!m) return ref\n const colAbs = m[1] === '$'\n const rowAbs = m[3] === '$'\n let col = colNameToIndex(m[2]!)\n let row = Number.parseInt(m[4]!, 10) - 1\n if (col < 0 || row < 0) return '#REF!'\n if (!colAbs) col += deltaCol\n if (!rowAbs) row += deltaRow\n if (col < 0 || row < 0) return '#REF!'\n return `${colAbs ? '$' : ''}${colIndexToName(col)}${rowAbs ? '$' : ''}${row + 1}`\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { CellAddress, CellRange } from "../address.js";
|
|
2
2
|
import { CellData } from "../cell-store.js";
|
|
3
|
-
import { CellPatch } from "../command/types.js";
|
|
4
3
|
import { AstNode } from "./ast.js";
|
|
5
4
|
import { Sheet } from "../sheet.js";
|
|
5
|
+
import { CellPatch } from "../command/types.js";
|
|
6
6
|
//#region src/core/formula/dependency-graph.d.ts
|
|
7
7
|
/**
|
|
8
8
|
* 依赖图(工作簿级计算中枢):
|
package/dist/core/sheet.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { CellAddress, CellRange } from "./address.js";
|
|
2
2
|
import { CellData, CellStore, CellValue } from "./cell-store.js";
|
|
3
|
+
import { HistoryManager, HistoryState } from "./command/history.js";
|
|
4
|
+
import { DependencyGraph } from "./formula/dependency-graph.js";
|
|
3
5
|
import { CellInfo, MergeManager } from "./merge-manager.js";
|
|
4
6
|
import { SelectionModel, SelectionState } from "./selection.js";
|
|
5
|
-
import { HistoryManager, HistoryState } from "./command/history.js";
|
|
6
7
|
import { SetCellValueItem } from "./command/set-cell-value.js";
|
|
7
|
-
import { DependencyGraph } from "./formula/dependency-graph.js";
|
|
8
8
|
//#region src/core/sheet.d.ts
|
|
9
9
|
/**
|
|
10
10
|
* Sheet = cell-store + merge-manager + selection + history 的组合,统一操作入口。
|
|
@@ -40,11 +40,22 @@ declare class Sheet {
|
|
|
40
40
|
readonly history: HistoryManager;
|
|
41
41
|
/** 公式依赖图(工作簿内多 sheet 共享;独立 Sheet 自建) */
|
|
42
42
|
readonly formulaGraph: DependencyGraph;
|
|
43
|
+
/**
|
|
44
|
+
* 稀疏行高(模型行号 → 像素高度)。
|
|
45
|
+
* 供 SheetGrid 在 tab 切换重建时还原;本期不进 undo。
|
|
46
|
+
*/
|
|
47
|
+
private readonly rowHeights;
|
|
43
48
|
name: string;
|
|
44
49
|
private emitter;
|
|
45
50
|
constructor(name?: string, formulaGraph?: DependencyGraph);
|
|
46
51
|
get rowCount(): number;
|
|
47
52
|
get colCount(): number;
|
|
53
|
+
/** 读取自定义行高;未设置返回 undefined(由视图层用默认行高) */
|
|
54
|
+
getRowHeight(row: number): number | undefined;
|
|
55
|
+
/** 设置自定义行高(不进 undo);height ≤ 0 时清除自定义高度 */
|
|
56
|
+
setRowHeight(row: number, height: number): void;
|
|
57
|
+
/** 遍历已设置的自定义行高(SheetGrid 重建还原用) */
|
|
58
|
+
getRowHeights(): ReadonlyMap<number, number>;
|
|
48
59
|
/** 原始存储语义读取(被覆盖格 → undefined) */
|
|
49
60
|
getCellData(addr: CellAddress): CellData | undefined;
|
|
50
61
|
/** 锚点解析语义读取(被覆盖格 → 锚点的值) */
|
package/dist/core/sheet.js
CHANGED
|
@@ -17,6 +17,11 @@ var Sheet = class {
|
|
|
17
17
|
history;
|
|
18
18
|
/** 公式依赖图(工作簿内多 sheet 共享;独立 Sheet 自建) */
|
|
19
19
|
formulaGraph;
|
|
20
|
+
/**
|
|
21
|
+
* 稀疏行高(模型行号 → 像素高度)。
|
|
22
|
+
* 供 SheetGrid 在 tab 切换重建时还原;本期不进 undo。
|
|
23
|
+
*/
|
|
24
|
+
rowHeights = /* @__PURE__ */ new Map();
|
|
20
25
|
name;
|
|
21
26
|
emitter = new TypedEventEmitter();
|
|
22
27
|
constructor(name = "Sheet1", formulaGraph) {
|
|
@@ -34,6 +39,23 @@ var Sheet = class {
|
|
|
34
39
|
get colCount() {
|
|
35
40
|
return this.store.colCount;
|
|
36
41
|
}
|
|
42
|
+
/** 读取自定义行高;未设置返回 undefined(由视图层用默认行高) */
|
|
43
|
+
getRowHeight(row) {
|
|
44
|
+
return this.rowHeights.get(row);
|
|
45
|
+
}
|
|
46
|
+
/** 设置自定义行高(不进 undo);height ≤ 0 时清除自定义高度 */
|
|
47
|
+
setRowHeight(row, height) {
|
|
48
|
+
if (!Number.isInteger(row) || row < 0) return;
|
|
49
|
+
if (!Number.isFinite(height) || height <= 0) {
|
|
50
|
+
this.rowHeights.delete(row);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.rowHeights.set(row, height);
|
|
54
|
+
}
|
|
55
|
+
/** 遍历已设置的自定义行高(SheetGrid 重建还原用) */
|
|
56
|
+
getRowHeights() {
|
|
57
|
+
return this.rowHeights;
|
|
58
|
+
}
|
|
37
59
|
/** 原始存储语义读取(被覆盖格 → undefined) */
|
|
38
60
|
getCellData(addr) {
|
|
39
61
|
return this.store.getCell(addr);
|
package/dist/core/sheet.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sheet.js","names":[],"sources":["../../src/core/sheet.ts"],"sourcesContent":["import type { CellAddress, CellRange } from './address'\nimport { inferCellType, CellStore, type CellData, type CellValue } from './cell-store'\nimport { defaultCommandRegistry } from './command/default-registry'\nimport { HistoryManager, type HistoryState } from './command/history'\nimport { MergeCellsCommand, UnmergeCellsCommand } from './command/merge-cells'\nimport { SetCellFormulaCommand } from './command/set-cell-formula'\nimport { SetCellValueCommand, type SetCellValueItem } from './command/set-cell-value'\nimport type { Mutation, Patch, PatchDirection } from './command/types'\nimport { TypedEventEmitter } from './events'\nimport { DependencyGraph } from './formula/dependency-graph'\nimport { MergeManager, type CellInfo } from './merge-manager'\nimport { SelectionModel, type SelectionState } from './selection'\n\n/**\n * Sheet = cell-store + merge-manager + selection + history 的组合,统一操作入口。\n *\n * 语义约定:\n * - `getCellData`:原始存储语义,被合并覆盖的非锚点格 → undefined\n * - `getDisplayValue`:锚点解析语义,被覆盖格返回锚点的值\n * - `setCellValue` / `selectCell`:内部先 resolveAnchor(用户操作永远落锚点)\n * - 一切写操作(setCellValue / setCell / setCells / setCellFormula / mergeCells / unmergeCells)\n * 都经命令系统执行(applyPatch 是唯一变更通道),天然获得 undo/redo 能力\n * - 公式:`setCellValue` 识别 '=' 前缀走 `setCellFormula`;命令执行后自动增量重算,\n * 重算派生变更(含跨表)并入同一 undo 单元\n */\n\nexport type SheetEvents = {\n /** 单元格数据变化(含删除) */\n 'cell-change': { addr: CellAddress }\n /** 合并结构变化(合并/取消合并) */\n 'merge-change': { range: CellRange }\n /** 选区变化 */\n 'selection-change': SelectionState\n /** 历史栈变化(undo/redo 可用状态,供工具栏按钮置灰) */\n 'history-change': HistoryState\n}\n\nexport class Sheet {\n readonly store = new CellStore()\n readonly merges = new MergeManager()\n readonly selection: SelectionModel\n /** undo/redo 历史(命令系统) */\n readonly history: HistoryManager\n /** 公式依赖图(工作簿内多 sheet 共享;独立 Sheet 自建) */\n readonly formulaGraph: DependencyGraph\n\n name: string\n\n private emitter = new TypedEventEmitter<SheetEvents>()\n\n constructor(name = 'Sheet1', formulaGraph?: DependencyGraph) {\n this.name = name\n this.selection = new SelectionModel((addr) => this.merges.resolveAnchor(addr))\n this.selection.on((state) => this.emitter.emit('selection-change', state))\n this.history = new HistoryManager(this.boundApplyPatch)\n this.history.onChange((state) => this.emitter.emit('history-change', state))\n this.formulaGraph = formulaGraph ?? new DependencyGraph()\n this.formulaGraph.registerSheet(this)\n }\n\n get rowCount(): number {\n return this.store.rowCount\n }\n\n get colCount(): number {\n return this.store.colCount\n }\n\n // ─── 单元格数据 ────────────────────────────────────────────\n\n /** 原始存储语义读取(被覆盖格 → undefined) */\n getCellData(addr: CellAddress): CellData | undefined {\n return this.store.getCell(addr)\n }\n\n /** 锚点解析语义读取(被覆盖格 → 锚点的值) */\n getDisplayValue(addr: CellAddress): CellValue | undefined {\n const anchor = this.merges.resolveAnchor(addr)\n return this.store.getCell(anchor)?.v ?? undefined\n }\n\n /** 写入原始值(解析到锚点;空值 = 清除);'=' 前缀按公式处理;经命令执行,可撤销 */\n setCellValue(addr: CellAddress, value: CellValue): void {\n if (typeof value === 'string' && value.startsWith('=')) {\n this.setCellFormula(addr, value)\n return\n }\n const data = value == null || value === '' ? undefined : { v: value, t: inferCellType(value) }\n this.setCells([{ addr, data }])\n }\n\n /** 写入完整 CellData(解析到锚点;空数据 = 清除);经命令执行,可撤销 */\n setCell(addr: CellAddress, data?: CellData): void {\n this.setCells([{ addr, data }])\n }\n\n /**\n * 写入公式(可带 '=' 前缀;空白公式 = 清除);经命令执行,可撤销。\n * 公式原文存 CellData.f,计算缓存(v/t)由增量重算填充;解析失败 → #ERROR!。\n */\n setCellFormula(addr: CellAddress, formula: string): void {\n const text = formula.startsWith('=') ? formula.slice(1) : formula\n if (text.trim() === '') {\n this.setCells([{ addr, data: undefined }])\n return\n }\n this.executeCommand(SetCellFormulaCommand.id, { addr, formula: text })\n }\n\n /** 批量写入(一次调用 = 一个 undo 单元,供粘贴/填充复用) */\n setCells(items: SetCellValueItem[]): void {\n this.executeCommand(SetCellValueCommand.id, { items })\n }\n\n // ─── 合并 ────────────────────────────────────────────────\n\n /** 合并语义下的单元格信息(普通格 / 合并锚点 / 被覆盖格) */\n getCellInfo(addr: CellAddress): CellInfo {\n return this.merges.getCellInfo(addr)\n }\n\n /**\n * 合并区域。值保留规则(同 Excel/univer):\n * 仅保留包围盒内按行主序第一个有值格的值,写入新锚点,其余被覆盖格清空。\n * @returns 最终生效的合并区域(与既有合并相交时取包围盒)\n */\n mergeCells(range: CellRange): CellRange {\n return this.executeCommand<CellRange>(MergeCellsCommand.id, { range })!\n }\n\n /** 解除与 range 相交的所有合并;仅原锚点保留值(被覆盖格本就无值) */\n unmergeCells(range: CellRange): void {\n this.executeCommand(UnmergeCellsCommand.id, { range })\n }\n\n // ─── 命令与历史 ──────────────────────────────────────────\n\n /** 经默认注册表执行命令;产生的 mutation 与公式重算派生 mutation 一并推入历史 */\n executeCommand<R = void>(commandId: string, params: unknown): R | undefined {\n const result = defaultCommandRegistry.execute<R>(\n { sheet: this, applyPatch: this.boundApplyPatch },\n commandId,\n params\n )\n if (result && result.mutations.length > 0) {\n const mutations = [...result.mutations]\n const recalcMutation = this.recalcAfterCommand(result.mutations)\n if (recalcMutation) mutations.push(recalcMutation)\n this.history.push(mutations)\n }\n return result?.result\n }\n\n /** 开启事务(事务内所有命令合并为一个 undo 单元;可嵌套,拍平到最外层) */\n beginTransaction(): void {\n this.history.beginTransaction()\n }\n\n /** 提交事务 */\n commit(): void {\n this.history.commit()\n }\n\n /** 回滚事务:还原缓冲中已应用的变更并放弃事务 */\n rollback(): void {\n this.history.rollback()\n }\n\n /** 撤销一步;无可撤销时返回 false */\n undo(): boolean {\n return this.history.undo()\n }\n\n /** 重做一步;无可重做时返回 false */\n redo(): boolean {\n return this.history.redo()\n }\n\n get canUndo(): boolean {\n return this.history.canUndo\n }\n\n get canRedo(): boolean {\n return this.history.canRedo\n }\n\n // ─── 选区 ────────────────────────────────────────────────\n\n getSelection(): SelectionState {\n return this.selection.getState()\n }\n\n /** 选中单格(被覆盖格自动定位锚点) */\n selectCell(addr: CellAddress): void {\n this.selection.selectCell(addr)\n }\n\n selectRange(range: CellRange): void {\n this.selection.selectRange(range)\n }\n\n // ─── 事件 ────────────────────────────────────────────────\n\n on<K extends keyof SheetEvents>(type: K, handler: (payload: SheetEvents[K]) => void): () => void {\n return this.emitter.on(type, handler)\n }\n\n off<K extends keyof SheetEvents>(type: K, handler: (payload: SheetEvents[K]) => void): void {\n this.emitter.off(type, handler)\n }\n\n // ─── 内部 ────────────────────────────────────────────────\n\n private readonly boundApplyPatch = (patch: Patch, direction: PatchDirection): void => {\n // 跨表重算的派生补丁路由到目标 sheet(回放经源 sheet 历史时同样按此路由)\n const target = patch.kind === 'cell' ? (patch.sheet ?? this) : this\n target.applyPatch(patch, direction)\n }\n\n /** 应用补丁:命令执行与 undo/redo 回放共用的唯一变更通道 */\n private applyPatch(patch: Patch, direction: PatchDirection): void {\n if (patch.kind === 'cell') {\n const data = direction === 'redo' ? patch.after : patch.before\n const previous = direction === 'redo' ? patch.before : patch.after\n this.store.setCell(patch.addr, data)\n // 公式依赖图同步(f 增/删/改 → 节点增/删/重建;undo/redo 回放同样覆盖)\n this.formulaGraph.syncCell(this, patch.addr, previous, data)\n this.emitter.emit('cell-change', { addr: patch.addr })\n return\n }\n const exists = direction === 'redo' ? patch.after : patch.before\n if (exists) {\n this.merges.addMerge(patch.range)\n } else {\n this.merges.removeMerge(patch.range)\n }\n this.emitter.emit('merge-change', { range: patch.range })\n }\n\n /**\n * 命令执行后的公式增量重算:从命令 mutation 提取变更格 → 依赖图标脏 +\n * 拓扑序重算 → 派生补丁立即应用,作为附加 mutation 并入同一 undo 单元。\n * undo/redo 纯补丁回放(不重算),图状态由 applyPatch 内的 syncCell 维持。\n */\n private recalcAfterCommand(mutations: Mutation[]): Mutation | undefined {\n const changed: { sheet: Sheet; addr: CellAddress }[] = []\n for (const mutation of mutations) {\n for (const patch of mutation.redo) {\n if (patch.kind === 'cell') changed.push({ sheet: patch.sheet ?? this, addr: patch.addr })\n }\n }\n const derived = this.formulaGraph.recalc(changed)\n if (derived.length === 0) return undefined\n for (const patch of derived) this.boundApplyPatch(patch, 'redo')\n return { redo: derived, undo: [...derived].reverse() }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sheet.js","names":[],"sources":["../../src/core/sheet.ts"],"sourcesContent":["import type { CellAddress, CellRange } from './address'\nimport { inferCellType, CellStore, type CellData, type CellValue } from './cell-store'\nimport { defaultCommandRegistry } from './command/default-registry'\nimport { HistoryManager, type HistoryState } from './command/history'\nimport { MergeCellsCommand, UnmergeCellsCommand } from './command/merge-cells'\nimport { SetCellFormulaCommand } from './command/set-cell-formula'\nimport { SetCellValueCommand, type SetCellValueItem } from './command/set-cell-value'\nimport type { Mutation, Patch, PatchDirection } from './command/types'\nimport { TypedEventEmitter } from './events'\nimport { DependencyGraph } from './formula/dependency-graph'\nimport { MergeManager, type CellInfo } from './merge-manager'\nimport { SelectionModel, type SelectionState } from './selection'\n\n/**\n * Sheet = cell-store + merge-manager + selection + history 的组合,统一操作入口。\n *\n * 语义约定:\n * - `getCellData`:原始存储语义,被合并覆盖的非锚点格 → undefined\n * - `getDisplayValue`:锚点解析语义,被覆盖格返回锚点的值\n * - `setCellValue` / `selectCell`:内部先 resolveAnchor(用户操作永远落锚点)\n * - 一切写操作(setCellValue / setCell / setCells / setCellFormula / mergeCells / unmergeCells)\n * 都经命令系统执行(applyPatch 是唯一变更通道),天然获得 undo/redo 能力\n * - 公式:`setCellValue` 识别 '=' 前缀走 `setCellFormula`;命令执行后自动增量重算,\n * 重算派生变更(含跨表)并入同一 undo 单元\n */\n\nexport type SheetEvents = {\n /** 单元格数据变化(含删除) */\n 'cell-change': { addr: CellAddress }\n /** 合并结构变化(合并/取消合并) */\n 'merge-change': { range: CellRange }\n /** 选区变化 */\n 'selection-change': SelectionState\n /** 历史栈变化(undo/redo 可用状态,供工具栏按钮置灰) */\n 'history-change': HistoryState\n}\n\nexport class Sheet {\n readonly store = new CellStore()\n readonly merges = new MergeManager()\n readonly selection: SelectionModel\n /** undo/redo 历史(命令系统) */\n readonly history: HistoryManager\n /** 公式依赖图(工作簿内多 sheet 共享;独立 Sheet 自建) */\n readonly formulaGraph: DependencyGraph\n /**\n * 稀疏行高(模型行号 → 像素高度)。\n * 供 SheetGrid 在 tab 切换重建时还原;本期不进 undo。\n */\n private readonly rowHeights = new Map<number, number>()\n\n name: string\n\n private emitter = new TypedEventEmitter<SheetEvents>()\n\n constructor(name = 'Sheet1', formulaGraph?: DependencyGraph) {\n this.name = name\n this.selection = new SelectionModel((addr) => this.merges.resolveAnchor(addr))\n this.selection.on((state) => this.emitter.emit('selection-change', state))\n this.history = new HistoryManager(this.boundApplyPatch)\n this.history.onChange((state) => this.emitter.emit('history-change', state))\n this.formulaGraph = formulaGraph ?? new DependencyGraph()\n this.formulaGraph.registerSheet(this)\n }\n\n get rowCount(): number {\n return this.store.rowCount\n }\n\n get colCount(): number {\n return this.store.colCount\n }\n\n /** 读取自定义行高;未设置返回 undefined(由视图层用默认行高) */\n getRowHeight(row: number): number | undefined {\n return this.rowHeights.get(row)\n }\n\n /** 设置自定义行高(不进 undo);height ≤ 0 时清除自定义高度 */\n setRowHeight(row: number, height: number): void {\n if (!Number.isInteger(row) || row < 0) return\n if (!Number.isFinite(height) || height <= 0) {\n this.rowHeights.delete(row)\n return\n }\n this.rowHeights.set(row, height)\n }\n\n /** 遍历已设置的自定义行高(SheetGrid 重建还原用) */\n getRowHeights(): ReadonlyMap<number, number> {\n return this.rowHeights\n }\n\n // ─── 单元格数据 ────────────────────────────────────────────\n\n /** 原始存储语义读取(被覆盖格 → undefined) */\n getCellData(addr: CellAddress): CellData | undefined {\n return this.store.getCell(addr)\n }\n\n /** 锚点解析语义读取(被覆盖格 → 锚点的值) */\n getDisplayValue(addr: CellAddress): CellValue | undefined {\n const anchor = this.merges.resolveAnchor(addr)\n return this.store.getCell(anchor)?.v ?? undefined\n }\n\n /** 写入原始值(解析到锚点;空值 = 清除);'=' 前缀按公式处理;经命令执行,可撤销 */\n setCellValue(addr: CellAddress, value: CellValue): void {\n if (typeof value === 'string' && value.startsWith('=')) {\n this.setCellFormula(addr, value)\n return\n }\n const data = value == null || value === '' ? undefined : { v: value, t: inferCellType(value) }\n this.setCells([{ addr, data }])\n }\n\n /** 写入完整 CellData(解析到锚点;空数据 = 清除);经命令执行,可撤销 */\n setCell(addr: CellAddress, data?: CellData): void {\n this.setCells([{ addr, data }])\n }\n\n /**\n * 写入公式(可带 '=' 前缀;空白公式 = 清除);经命令执行,可撤销。\n * 公式原文存 CellData.f,计算缓存(v/t)由增量重算填充;解析失败 → #ERROR!。\n */\n setCellFormula(addr: CellAddress, formula: string): void {\n const text = formula.startsWith('=') ? formula.slice(1) : formula\n if (text.trim() === '') {\n this.setCells([{ addr, data: undefined }])\n return\n }\n this.executeCommand(SetCellFormulaCommand.id, { addr, formula: text })\n }\n\n /** 批量写入(一次调用 = 一个 undo 单元,供粘贴/填充复用) */\n setCells(items: SetCellValueItem[]): void {\n this.executeCommand(SetCellValueCommand.id, { items })\n }\n\n // ─── 合并 ────────────────────────────────────────────────\n\n /** 合并语义下的单元格信息(普通格 / 合并锚点 / 被覆盖格) */\n getCellInfo(addr: CellAddress): CellInfo {\n return this.merges.getCellInfo(addr)\n }\n\n /**\n * 合并区域。值保留规则(同 Excel/univer):\n * 仅保留包围盒内按行主序第一个有值格的值,写入新锚点,其余被覆盖格清空。\n * @returns 最终生效的合并区域(与既有合并相交时取包围盒)\n */\n mergeCells(range: CellRange): CellRange {\n return this.executeCommand<CellRange>(MergeCellsCommand.id, { range })!\n }\n\n /** 解除与 range 相交的所有合并;仅原锚点保留值(被覆盖格本就无值) */\n unmergeCells(range: CellRange): void {\n this.executeCommand(UnmergeCellsCommand.id, { range })\n }\n\n // ─── 命令与历史 ──────────────────────────────────────────\n\n /** 经默认注册表执行命令;产生的 mutation 与公式重算派生 mutation 一并推入历史 */\n executeCommand<R = void>(commandId: string, params: unknown): R | undefined {\n const result = defaultCommandRegistry.execute<R>(\n { sheet: this, applyPatch: this.boundApplyPatch },\n commandId,\n params\n )\n if (result && result.mutations.length > 0) {\n const mutations = [...result.mutations]\n const recalcMutation = this.recalcAfterCommand(result.mutations)\n if (recalcMutation) mutations.push(recalcMutation)\n this.history.push(mutations)\n }\n return result?.result\n }\n\n /** 开启事务(事务内所有命令合并为一个 undo 单元;可嵌套,拍平到最外层) */\n beginTransaction(): void {\n this.history.beginTransaction()\n }\n\n /** 提交事务 */\n commit(): void {\n this.history.commit()\n }\n\n /** 回滚事务:还原缓冲中已应用的变更并放弃事务 */\n rollback(): void {\n this.history.rollback()\n }\n\n /** 撤销一步;无可撤销时返回 false */\n undo(): boolean {\n return this.history.undo()\n }\n\n /** 重做一步;无可重做时返回 false */\n redo(): boolean {\n return this.history.redo()\n }\n\n get canUndo(): boolean {\n return this.history.canUndo\n }\n\n get canRedo(): boolean {\n return this.history.canRedo\n }\n\n // ─── 选区 ────────────────────────────────────────────────\n\n getSelection(): SelectionState {\n return this.selection.getState()\n }\n\n /** 选中单格(被覆盖格自动定位锚点) */\n selectCell(addr: CellAddress): void {\n this.selection.selectCell(addr)\n }\n\n selectRange(range: CellRange): void {\n this.selection.selectRange(range)\n }\n\n // ─── 事件 ────────────────────────────────────────────────\n\n on<K extends keyof SheetEvents>(type: K, handler: (payload: SheetEvents[K]) => void): () => void {\n return this.emitter.on(type, handler)\n }\n\n off<K extends keyof SheetEvents>(type: K, handler: (payload: SheetEvents[K]) => void): void {\n this.emitter.off(type, handler)\n }\n\n // ─── 内部 ────────────────────────────────────────────────\n\n private readonly boundApplyPatch = (patch: Patch, direction: PatchDirection): void => {\n // 跨表重算的派生补丁路由到目标 sheet(回放经源 sheet 历史时同样按此路由)\n const target = patch.kind === 'cell' ? (patch.sheet ?? this) : this\n target.applyPatch(patch, direction)\n }\n\n /** 应用补丁:命令执行与 undo/redo 回放共用的唯一变更通道 */\n private applyPatch(patch: Patch, direction: PatchDirection): void {\n if (patch.kind === 'cell') {\n const data = direction === 'redo' ? patch.after : patch.before\n const previous = direction === 'redo' ? patch.before : patch.after\n this.store.setCell(patch.addr, data)\n // 公式依赖图同步(f 增/删/改 → 节点增/删/重建;undo/redo 回放同样覆盖)\n this.formulaGraph.syncCell(this, patch.addr, previous, data)\n this.emitter.emit('cell-change', { addr: patch.addr })\n return\n }\n const exists = direction === 'redo' ? patch.after : patch.before\n if (exists) {\n this.merges.addMerge(patch.range)\n } else {\n this.merges.removeMerge(patch.range)\n }\n this.emitter.emit('merge-change', { range: patch.range })\n }\n\n /**\n * 命令执行后的公式增量重算:从命令 mutation 提取变更格 → 依赖图标脏 +\n * 拓扑序重算 → 派生补丁立即应用,作为附加 mutation 并入同一 undo 单元。\n * undo/redo 纯补丁回放(不重算),图状态由 applyPatch 内的 syncCell 维持。\n */\n private recalcAfterCommand(mutations: Mutation[]): Mutation | undefined {\n const changed: { sheet: Sheet; addr: CellAddress }[] = []\n for (const mutation of mutations) {\n for (const patch of mutation.redo) {\n if (patch.kind === 'cell') changed.push({ sheet: patch.sheet ?? this, addr: patch.addr })\n }\n }\n const derived = this.formulaGraph.recalc(changed)\n if (derived.length === 0) return undefined\n for (const patch of derived) this.boundApplyPatch(patch, 'redo')\n return { redo: derived, undo: [...derived].reverse() }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
import { CellAddress } from "../core/address.js";
|
|
1
2
|
import { Sheet } from "../core/sheet.js";
|
|
2
3
|
import { ListTable } from "@visactor/vtable";
|
|
3
4
|
//#region src/grid/sheet-grid.d.ts
|
|
5
|
+
/** 右键菜单回调参数(vue 层弹 UContextmenu;grid 不依赖 desktop) */
|
|
6
|
+
interface SheetGridContextMenuInfo {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
/** body 格地址;行号/列头为 null */
|
|
10
|
+
addr: CellAddress | null;
|
|
11
|
+
}
|
|
4
12
|
interface SheetGridOptions {
|
|
5
13
|
container: HTMLElement;
|
|
6
14
|
sheet: Sheet;
|
|
@@ -8,6 +16,8 @@ interface SheetGridOptions {
|
|
|
8
16
|
rows?: number;
|
|
9
17
|
/** 渲染列数,默认 26(A..Z) */
|
|
10
18
|
cols?: number;
|
|
19
|
+
/** 单元格右键(已 preventDefault);由 vue 层弹出菜单 */
|
|
20
|
+
onContextMenu?: (info: SheetGridContextMenuInfo) => void;
|
|
11
21
|
}
|
|
12
22
|
declare class SheetGrid {
|
|
13
23
|
private readonly sheet;
|
|
@@ -16,11 +26,14 @@ declare class SheetGrid {
|
|
|
16
26
|
private readonly rows;
|
|
17
27
|
private readonly cols;
|
|
18
28
|
private readonly editorName;
|
|
29
|
+
private readonly onContextMenu?;
|
|
19
30
|
private readonly disposers;
|
|
20
31
|
/** 编辑提交期间累积的模型变更(提交结束统一回推表格,覆盖公式派生格) */
|
|
21
32
|
private pendingTableSync;
|
|
22
33
|
/** 实测坐标偏移(行号列数 / 列头行数),首次使用时测量 */
|
|
23
34
|
private offsets?;
|
|
35
|
+
/** 填充柄:mousedown 时的源选区(模型坐标) */
|
|
36
|
+
private fillSourceRange;
|
|
24
37
|
constructor(options: SheetGridOptions);
|
|
25
38
|
/** 底层 ListTable 实例(调试与测试用) */
|
|
26
39
|
getTable(): ListTable;
|
|
@@ -44,7 +57,16 @@ declare class SheetGrid {
|
|
|
44
57
|
private buildColumns;
|
|
45
58
|
private buildRecords;
|
|
46
59
|
private buildOptions;
|
|
60
|
+
/**
|
|
61
|
+
* 行高只允许在行号列拖拽(Excel 语义)。
|
|
62
|
+
* VTable 无 canResizeRow,且 'header' 不含 rowSeriesNumber body,故包装 _canResizeRow。
|
|
63
|
+
*/
|
|
64
|
+
private restrictRowResizeToSeriesNumber;
|
|
65
|
+
/** 按 Sheet 稀疏行高还原到 VTable(tab 切换重建后保留) */
|
|
66
|
+
private applyStoredRowHeights;
|
|
47
67
|
private bindTableEvents;
|
|
68
|
+
/** 当前 VTable 选区 → 规范化模型区域;无有效 body 选区返回 null */
|
|
69
|
+
private readSelectedModelRange;
|
|
48
70
|
private bindSheetEvents;
|
|
49
71
|
/** 模型格 → 表格 record(显示值;公式格为计算缓存) */
|
|
50
72
|
private pushCellToTable;
|
|
@@ -55,5 +77,5 @@ declare class SheetGrid {
|
|
|
55
77
|
private bindKeyboard;
|
|
56
78
|
}
|
|
57
79
|
//#endregion
|
|
58
|
-
export { SheetGrid, SheetGridOptions };
|
|
80
|
+
export { SheetGrid, SheetGridContextMenuInfo, SheetGridOptions };
|
|
59
81
|
//# sourceMappingURL=sheet-grid.d.ts.map
|