@veltra/sheet 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/address.d.ts +60 -0
- package/dist/core/address.js +142 -0
- package/dist/core/address.js.map +1 -0
- package/dist/core/cell-store.d.ts +61 -0
- package/dist/core/cell-store.js +135 -0
- package/dist/core/cell-store.js.map +1 -0
- package/dist/core/command/default-registry.d.ts +10 -0
- package/dist/core/command/default-registry.js +18 -0
- package/dist/core/command/default-registry.js.map +1 -0
- package/dist/core/command/history.d.ts +64 -0
- package/dist/core/command/history.js +127 -0
- package/dist/core/command/history.js.map +1 -0
- package/dist/core/command/merge-cells.d.ts +21 -0
- package/dist/core/command/merge-cells.js +74 -0
- package/dist/core/command/merge-cells.js.map +1 -0
- package/dist/core/command/registry.d.ts +17 -0
- package/dist/core/command/registry.js +26 -0
- package/dist/core/command/registry.js.map +1 -0
- package/dist/core/command/set-cell-formula.d.ts +17 -0
- package/dist/core/command/set-cell-formula.js +29 -0
- package/dist/core/command/set-cell-formula.js.map +1 -0
- package/dist/core/command/set-cell-value.d.ts +22 -0
- package/dist/core/command/set-cell-value.js +35 -0
- package/dist/core/command/set-cell-value.js.map +1 -0
- package/dist/core/command/types.d.ts +68 -0
- package/dist/core/events.js +29 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/formula/ast.d.ts +58 -0
- package/dist/core/formula/ast.js +38 -0
- package/dist/core/formula/ast.js.map +1 -0
- package/dist/core/formula/dependency-graph.d.ts +70 -0
- package/dist/core/formula/dependency-graph.js +269 -0
- package/dist/core/formula/dependency-graph.js.map +1 -0
- package/dist/core/formula/errors.d.ts +25 -0
- package/dist/core/formula/errors.js +36 -0
- package/dist/core/formula/errors.js.map +1 -0
- package/dist/core/formula/evaluator.d.ts +41 -0
- package/dist/core/formula/evaluator.js +154 -0
- package/dist/core/formula/evaluator.js.map +1 -0
- package/dist/core/formula/functions.d.ts +30 -0
- package/dist/core/formula/functions.js +209 -0
- package/dist/core/formula/functions.js.map +1 -0
- package/dist/core/formula/parser.d.ts +7 -0
- package/dist/core/formula/parser.js +208 -0
- package/dist/core/formula/parser.js.map +1 -0
- package/dist/core/formula/tokenizer.d.ts +37 -0
- package/dist/core/formula/tokenizer.js +146 -0
- package/dist/core/formula/tokenizer.js.map +1 -0
- package/dist/core/merge-manager.d.ts +64 -0
- package/dist/core/merge-manager.js +111 -0
- package/dist/core/merge-manager.js.map +1 -0
- package/dist/core/selection.d.ts +31 -0
- package/dist/core/selection.js +62 -0
- package/dist/core/selection.js.map +1 -0
- package/dist/core/sheet.d.ts +105 -0
- package/dist/core/sheet.js +202 -0
- package/dist/core/sheet.js.map +1 -0
- package/dist/core/workbook.d.ts +43 -0
- package/dist/core/workbook.js +77 -0
- package/dist/core/workbook.js.map +1 -0
- package/dist/grid/sheet-grid.d.ts +59 -0
- package/dist/grid/sheet-grid.js +230 -0
- package/dist/grid/sheet-grid.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +25 -0
- package/dist/tools/builtin.js +72 -0
- package/dist/tools/builtin.js.map +1 -0
- package/dist/tools/context.d.ts +57 -0
- package/dist/tools/context.js +43 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/registry.d.ts +68 -0
- package/dist/tools/registry.js +77 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/sheet.d.ts +43 -0
- package/dist/vue/index.d.ts +2 -0
- package/dist/vue/sheet.js +185 -0
- package/dist/vue/sheet.js.map +1 -0
- package/dist/vue/sheet.vue.d.ts +24 -0
- package/dist/vue/style.d.ts +1 -0
- package/dist/vue/style.js +1 -0
- package/dist/vue/style2.css +93 -0
- package/package.json +37 -0
- package/src/core/__test__/address.test.ts +113 -0
- package/src/core/__test__/cell-store.test.ts +97 -0
- package/src/core/__test__/command.test.ts +377 -0
- package/src/core/__test__/formula-adversarial.test.ts +265 -0
- package/src/core/__test__/formula-engine.test.ts +470 -0
- package/src/core/__test__/formula-functions.test.ts +178 -0
- package/src/core/__test__/formula-parser.test.ts +160 -0
- package/src/core/__test__/history.test.ts +225 -0
- package/src/core/__test__/merge-manager.test.ts +106 -0
- package/src/core/__test__/sheet.test.ts +142 -0
- package/src/core/address.ts +166 -0
- package/src/core/cell-store.ts +173 -0
- package/src/core/command/default-registry.ts +15 -0
- package/src/core/command/history.ts +167 -0
- package/src/core/command/index.ts +7 -0
- package/src/core/command/merge-cells.ts +74 -0
- package/src/core/command/registry.ts +30 -0
- package/src/core/command/set-cell-formula.ts +29 -0
- package/src/core/command/set-cell-value.ts +38 -0
- package/src/core/command/types.ts +73 -0
- package/src/core/events.ts +37 -0
- package/src/core/formula/ast.ts +66 -0
- package/src/core/formula/dependency-graph.ts +329 -0
- package/src/core/formula/errors.ts +41 -0
- package/src/core/formula/evaluator.ts +219 -0
- package/src/core/formula/functions.ts +266 -0
- package/src/core/formula/index.ts +7 -0
- package/src/core/formula/parser.ts +198 -0
- package/src/core/formula/tokenizer.ts +160 -0
- package/src/core/merge-manager.ts +158 -0
- package/src/core/selection.ts +66 -0
- package/src/core/sheet.ts +257 -0
- package/src/core/workbook.ts +99 -0
- package/src/env.d.ts +7 -0
- package/src/grid/__test__/canvas-mock.ts +49 -0
- package/src/grid/__test__/setup.ts +3 -0
- package/src/grid/__test__/sheet-grid-formula.test.ts +140 -0
- package/src/grid/__test__/sheet-grid.test.ts +177 -0
- package/src/grid/sheet-grid.ts +283 -0
- package/src/index.ts +16 -0
- package/src/tools/__test__/builtin.test.ts +128 -0
- package/src/tools/__test__/context.test.ts +157 -0
- package/src/tools/__test__/registry.test.ts +102 -0
- package/src/tools/builtin.ts +75 -0
- package/src/tools/context.ts +106 -0
- package/src/tools/index.ts +2 -0
- package/src/tools/registry.ts +129 -0
- package/src/types/index.ts +1 -0
- package/src/types/sheet.ts +41 -0
- package/src/vue/__test__/sheet-component.test.ts +267 -0
- package/src/vue/index.ts +1 -0
- package/src/vue/sheet.vue +195 -0
- package/src/vue/style.scss +94 -0
- package/src/vue/style.ts +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","names":[],"sources":["../../../src/core/command/history.ts"],"sourcesContent":["import { TypedEventEmitter } from '../events'\nimport type { Mutation, Patch, PatchDirection } from './types'\n\n/**\n * HistoryManager:undo/redo 栈 + 事务。\n *\n * - 一个 undo 单元 = 一次命令(或一次事务)的全部 Mutation\n * - 事务:beginTransaction / commit,支持嵌套(深度计数,拍平到最外层);\n * rollback 回滚缓冲区中已应用的变更并放弃事务\n * - 容量上限(默认 200):超出淘汰最旧条目\n * - 新命令入栈即清空 redo 栈\n */\n\n/** 历史栈容量默认值 */\nexport const DEFAULT_HISTORY_CAPACITY = 200\n\n/** 历史状态(history-change 事件负载) */\nexport interface HistoryState {\n canUndo: boolean\n canRedo: boolean\n}\n\ntype HistoryEvents = { change: HistoryState }\n\n/** 一个 undo 单元 = 一组 Mutation */\ntype HistoryEntry = Mutation[]\n\nexport class HistoryManager {\n private undoStack: HistoryEntry[] = []\n private redoStack: HistoryEntry[] = []\n /** 事务深度(>0 表示事务进行中) */\n private transactionDepth = 0\n /** 事务缓冲:commit 时合并为一个 undo 单元 */\n private transactionBuffer: Mutation[] = []\n private emitter = new TypedEventEmitter<HistoryEvents>()\n\n constructor(\n /** 补丁应用器(由 Sheet 注入,命令执行与回放共用) */\n private readonly applyPatch: (patch: Patch, direction: PatchDirection) => void,\n private readonly capacity: number = DEFAULT_HISTORY_CAPACITY\n ) {}\n\n get canUndo(): boolean {\n return this.undoStack.length > 0\n }\n\n get canRedo(): boolean {\n return this.redoStack.length > 0\n }\n\n /** undo 栈深度(调试/测试用) */\n get undoSize(): number {\n return this.undoStack.length\n }\n\n /** redo 栈深度(调试/测试用) */\n get redoSize(): number {\n return this.redoStack.length\n }\n\n /** 是否处于事务中 */\n get inTransaction(): boolean {\n return this.transactionDepth > 0\n }\n\n /** 命令执行产生的 mutation 入栈(事务中则缓冲);空列表直接忽略 */\n push(mutations: Mutation[]): void {\n if (mutations.length === 0) return\n if (this.transactionDepth > 0) {\n this.transactionBuffer.push(...mutations)\n return\n }\n this.pushEntry(mutations)\n }\n\n /** 开启事务(可嵌套,拍平到最外层) */\n beginTransaction(): void {\n this.transactionDepth++\n }\n\n /** 提交事务:缓冲的 mutation 合并为一个 undo 单元入栈 */\n commit(): void {\n if (this.transactionDepth === 0) {\n throw new Error('HistoryManager.commit:没有进行中的事务')\n }\n this.transactionDepth--\n if (this.transactionDepth === 0 && this.transactionBuffer.length > 0) {\n const entry = this.transactionBuffer\n this.transactionBuffer = []\n this.pushEntry(entry)\n }\n }\n\n /**\n * 回滚事务:逆序应用缓冲区中各 mutation 的 undo 补丁还原模型,\n * 丢弃缓冲并结束事务(含嵌套);不动 undo/redo 栈。\n */\n rollback(): void {\n if (this.transactionDepth === 0) return\n const buffer = this.transactionBuffer\n this.transactionBuffer = []\n this.transactionDepth = 0\n for (let i = buffer.length - 1; i >= 0; i--) {\n for (const patch of buffer[i]!.undo) {\n this.applyPatch(patch, 'undo')\n }\n }\n }\n\n /** 撤销一个单元;事务进行中或栈空时返回 false */\n undo(): boolean {\n if (this.transactionDepth > 0) return false\n const entry = this.undoStack.pop()\n if (!entry) return false\n for (let i = entry.length - 1; i >= 0; i--) {\n for (const patch of entry[i]!.undo) {\n this.applyPatch(patch, 'undo')\n }\n }\n this.redoStack.push(entry)\n this.emitChange()\n return true\n }\n\n /** 重做一个单元;事务进行中或栈空时返回 false */\n redo(): boolean {\n if (this.transactionDepth > 0) return false\n const entry = this.redoStack.pop()\n if (!entry) return false\n for (const mutation of entry) {\n for (const patch of mutation.redo) {\n this.applyPatch(patch, 'redo')\n }\n }\n this.undoStack.push(entry)\n this.emitChange()\n return true\n }\n\n /** 清空全部历史(含事务缓冲) */\n clear(): void {\n this.undoStack = []\n this.redoStack = []\n this.transactionBuffer = []\n this.transactionDepth = 0\n this.emitChange()\n }\n\n onChange(handler: (state: HistoryState) => void): () => void {\n return this.emitter.on('change', handler)\n }\n\n private pushEntry(entry: HistoryEntry): void {\n this.undoStack.push(entry)\n if (this.undoStack.length > this.capacity) {\n // 淘汰最旧条目;redo 栈不受影响(淘汰只发生在 undo 栈底部)\n this.undoStack.shift()\n }\n // 新命令清空 redo 栈\n this.redoStack = []\n this.emitChange()\n }\n\n private emitChange(): void {\n this.emitter.emit('change', { canUndo: this.canUndo, canRedo: this.canRedo })\n }\n}\n"],"mappings":";;;;;;;;;;;;AAcA,MAAa,2BAA2B;AAaxC,IAAa,iBAAb,MAA4B;CAWP;CACA;CAXnB,YAAoC,CAAC;CACrC,YAAoC,CAAC;;CAErC,mBAA2B;;CAE3B,oBAAwC,CAAC;CACzC,UAAkB,IAAI,kBAAiC;CAEvD,YAEE,YACA,WAAiB,KACjB;EAFiB,KAAA,aAAA;EACA,KAAA,WAAA;CAChB;CAEH,IAAI,UAAmB;EACrB,OAAO,KAAK,UAAU,SAAS;CACjC;CAEA,IAAI,UAAmB;EACrB,OAAO,KAAK,UAAU,SAAS;CACjC;;CAGA,IAAI,WAAmB;EACrB,OAAO,KAAK,UAAU;CACxB;;CAGA,IAAI,WAAmB;EACrB,OAAO,KAAK,UAAU;CACxB;;CAGA,IAAI,gBAAyB;EAC3B,OAAO,KAAK,mBAAmB;CACjC;;CAGA,KAAK,WAA6B;EAChC,IAAI,UAAU,WAAW,GAAG;EAC5B,IAAI,KAAK,mBAAmB,GAAG;GAC7B,KAAK,kBAAkB,KAAK,GAAG,SAAS;GACxC;EACF;EACA,KAAK,UAAU,SAAS;CAC1B;;CAGA,mBAAyB;EACvB,KAAK;CACP;;CAGA,SAAe;EACb,IAAI,KAAK,qBAAqB,GAC5B,MAAM,IAAI,MAAM,gCAAgC;EAElD,KAAK;EACL,IAAI,KAAK,qBAAqB,KAAK,KAAK,kBAAkB,SAAS,GAAG;GACpE,MAAM,QAAQ,KAAK;GACnB,KAAK,oBAAoB,CAAC;GAC1B,KAAK,UAAU,KAAK;EACtB;CACF;;;;;CAMA,WAAiB;EACf,IAAI,KAAK,qBAAqB,GAAG;EACjC,MAAM,SAAS,KAAK;EACpB,KAAK,oBAAoB,CAAC;EAC1B,KAAK,mBAAmB;EACxB,KAAK,IAAI,IAAI,OAAO,SAAS,GAAG,KAAK,GAAG,KACtC,KAAK,MAAM,SAAS,OAAO,EAAE,CAAE,MAC7B,KAAK,WAAW,OAAO,MAAM;CAGnC;;CAGA,OAAgB;EACd,IAAI,KAAK,mBAAmB,GAAG,OAAO;EACtC,MAAM,QAAQ,KAAK,UAAU,IAAI;EACjC,IAAI,CAAC,OAAO,OAAO;EACnB,KAAK,IAAI,IAAI,MAAM,SAAS,GAAG,KAAK,GAAG,KACrC,KAAK,MAAM,SAAS,MAAM,EAAE,CAAE,MAC5B,KAAK,WAAW,OAAO,MAAM;EAGjC,KAAK,UAAU,KAAK,KAAK;EACzB,KAAK,WAAW;EAChB,OAAO;CACT;;CAGA,OAAgB;EACd,IAAI,KAAK,mBAAmB,GAAG,OAAO;EACtC,MAAM,QAAQ,KAAK,UAAU,IAAI;EACjC,IAAI,CAAC,OAAO,OAAO;EACnB,KAAK,MAAM,YAAY,OACrB,KAAK,MAAM,SAAS,SAAS,MAC3B,KAAK,WAAW,OAAO,MAAM;EAGjC,KAAK,UAAU,KAAK,KAAK;EACzB,KAAK,WAAW;EAChB,OAAO;CACT;;CAGA,QAAc;EACZ,KAAK,YAAY,CAAC;EAClB,KAAK,YAAY,CAAC;EAClB,KAAK,oBAAoB,CAAC;EAC1B,KAAK,mBAAmB;EACxB,KAAK,WAAW;CAClB;CAEA,SAAS,SAAoD;EAC3D,OAAO,KAAK,QAAQ,GAAG,UAAU,OAAO;CAC1C;CAEA,UAAkB,OAA2B;EAC3C,KAAK,UAAU,KAAK,KAAK;EACzB,IAAI,KAAK,UAAU,SAAS,KAAK,UAE/B,KAAK,UAAU,MAAM;EAGvB,KAAK,YAAY,CAAC;EAClB,KAAK,WAAW;CAClB;CAEA,aAA2B;EACzB,KAAK,QAAQ,KAAK,UAAU;GAAE,SAAS,KAAK;GAAS,SAAS,KAAK;EAAQ,CAAC;CAC9E;AACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { CellRange } from "../address.js";
|
|
2
|
+
import { Command } from "./types.js";
|
|
3
|
+
//#region src/core/command/merge-cells.d.ts
|
|
4
|
+
interface MergeCellsParams {
|
|
5
|
+
range: CellRange;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 合并命令。Patch 捕获完整 before 状态:
|
|
9
|
+
* - 被解除的既有合并记录(undo 时逐一恢复)
|
|
10
|
+
* - 包围盒内每一格的原数据(undo 时连被清空的值一起还原)
|
|
11
|
+
* 值保留规则同 Excel/univer:行主序第一个有值格的值写入新锚点,其余清空。
|
|
12
|
+
*/
|
|
13
|
+
declare const MergeCellsCommand: Command<MergeCellsParams, CellRange>;
|
|
14
|
+
interface UnmergeCellsParams {
|
|
15
|
+
range: CellRange;
|
|
16
|
+
}
|
|
17
|
+
/** 取消合并命令:解除与 range 相交的所有合并;undo 恢复合并记录 */
|
|
18
|
+
declare const UnmergeCellsCommand: Command<UnmergeCellsParams>;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { MergeCellsCommand, MergeCellsParams, UnmergeCellsCommand, UnmergeCellsParams };
|
|
21
|
+
//# sourceMappingURL=merge-cells.d.ts.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { iterateRange, rangesIntersect } from "../address.js";
|
|
2
|
+
import { cellDataEqual, isEmptyCellData } from "../cell-store.js";
|
|
3
|
+
//#region src/core/command/merge-cells.ts
|
|
4
|
+
/**
|
|
5
|
+
* 合并命令。Patch 捕获完整 before 状态:
|
|
6
|
+
* - 被解除的既有合并记录(undo 时逐一恢复)
|
|
7
|
+
* - 包围盒内每一格的原数据(undo 时连被清空的值一起还原)
|
|
8
|
+
* 值保留规则同 Excel/univer:行主序第一个有值格的值写入新锚点,其余清空。
|
|
9
|
+
*/
|
|
10
|
+
const MergeCellsCommand = {
|
|
11
|
+
id: "sheet.command.merge-cells",
|
|
12
|
+
handler(ctx, params) {
|
|
13
|
+
const { sheet } = ctx;
|
|
14
|
+
const { range: finalRange, removed } = sheet.merges.computeMerge(params.range);
|
|
15
|
+
const cellPatches = [];
|
|
16
|
+
let retained;
|
|
17
|
+
let anchorPatch;
|
|
18
|
+
for (const addr of iterateRange(finalRange)) {
|
|
19
|
+
const before = sheet.store.getCell(addr);
|
|
20
|
+
if (before && !retained && !isEmptyCellData(before)) retained = before;
|
|
21
|
+
const patch = {
|
|
22
|
+
kind: "cell",
|
|
23
|
+
addr,
|
|
24
|
+
before,
|
|
25
|
+
after: void 0
|
|
26
|
+
};
|
|
27
|
+
if (addr.row === finalRange.start.row && addr.col === finalRange.start.col) anchorPatch = patch;
|
|
28
|
+
cellPatches.push(patch);
|
|
29
|
+
}
|
|
30
|
+
if (retained && anchorPatch) anchorPatch.after = { ...retained };
|
|
31
|
+
const patches = [...[...removed.map((range) => ({
|
|
32
|
+
kind: "merge",
|
|
33
|
+
range,
|
|
34
|
+
before: true,
|
|
35
|
+
after: false
|
|
36
|
+
})), {
|
|
37
|
+
kind: "merge",
|
|
38
|
+
range: finalRange,
|
|
39
|
+
before: false,
|
|
40
|
+
after: true
|
|
41
|
+
}], ...cellPatches.filter((patch) => !cellDataEqual(patch.before, patch.after))];
|
|
42
|
+
for (const patch of patches) ctx.applyPatch(patch, "redo");
|
|
43
|
+
return {
|
|
44
|
+
mutations: [{
|
|
45
|
+
redo: patches,
|
|
46
|
+
undo: [...patches].reverse()
|
|
47
|
+
}],
|
|
48
|
+
result: finalRange
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
/** 取消合并命令:解除与 range 相交的所有合并;undo 恢复合并记录 */
|
|
53
|
+
const UnmergeCellsCommand = {
|
|
54
|
+
id: "sheet.command.unmerge-cells",
|
|
55
|
+
handler(ctx, params) {
|
|
56
|
+
const removed = [];
|
|
57
|
+
for (const range of ctx.sheet.merges.getMerges()) if (rangesIntersect(range, params.range)) removed.push({
|
|
58
|
+
kind: "merge",
|
|
59
|
+
range,
|
|
60
|
+
before: true,
|
|
61
|
+
after: false
|
|
62
|
+
});
|
|
63
|
+
if (removed.length === 0) return { mutations: [] };
|
|
64
|
+
for (const patch of removed) ctx.applyPatch(patch, "redo");
|
|
65
|
+
return { mutations: [{
|
|
66
|
+
redo: removed,
|
|
67
|
+
undo: [...removed].reverse()
|
|
68
|
+
}] };
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
//#endregion
|
|
72
|
+
export { MergeCellsCommand, UnmergeCellsCommand };
|
|
73
|
+
|
|
74
|
+
//# sourceMappingURL=merge-cells.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-cells.js","names":[],"sources":["../../../src/core/command/merge-cells.ts"],"sourcesContent":["import { iterateRange, rangesIntersect, type CellRange } from '../address'\nimport { cellDataEqual, isEmptyCellData, type CellData } from '../cell-store'\nimport type { CellPatch, Command, CommandResult, MergePatch, Patch } from './types'\n\nexport interface MergeCellsParams {\n range: CellRange\n}\n\n/**\n * 合并命令。Patch 捕获完整 before 状态:\n * - 被解除的既有合并记录(undo 时逐一恢复)\n * - 包围盒内每一格的原数据(undo 时连被清空的值一起还原)\n * 值保留规则同 Excel/univer:行主序第一个有值格的值写入新锚点,其余清空。\n */\nexport const MergeCellsCommand: Command<MergeCellsParams, CellRange> = {\n id: 'sheet.command.merge-cells',\n\n handler(ctx, params): CommandResult<CellRange> {\n const { sheet } = ctx\n const { range: finalRange, removed } = sheet.merges.computeMerge(params.range)\n\n // 捕获包围盒内各格 before;同时按行主序找出第一个有值格\n const cellPatches: CellPatch[] = []\n let retained: CellData | undefined\n let anchorPatch: CellPatch | undefined\n for (const addr of iterateRange(finalRange)) {\n const before = sheet.store.getCell(addr)\n if (before && !retained && !isEmptyCellData(before)) retained = before\n const patch: CellPatch = { kind: 'cell', addr, before, after: undefined }\n if (addr.row === finalRange.start.row && addr.col === finalRange.start.col) {\n anchorPatch = patch\n }\n cellPatches.push(patch)\n }\n if (retained && anchorPatch) {\n anchorPatch.after = { ...retained }\n }\n\n const mergePatches: MergePatch[] = [\n ...removed.map((range): MergePatch => ({ kind: 'merge', range, before: true, after: false })),\n { kind: 'merge', range: finalRange, before: false, after: true }\n ]\n\n // 过滤无实际变化的 cell 补丁(如被覆盖格本就为空)\n const patches: Patch[] = [\n ...mergePatches,\n ...cellPatches.filter((patch) => !cellDataEqual(patch.before, patch.after))\n ]\n for (const patch of patches) ctx.applyPatch(patch, 'redo')\n\n return { mutations: [{ redo: patches, undo: [...patches].reverse() }], result: finalRange }\n }\n}\n\nexport interface UnmergeCellsParams {\n range: CellRange\n}\n\n/** 取消合并命令:解除与 range 相交的所有合并;undo 恢复合并记录 */\nexport const UnmergeCellsCommand: Command<UnmergeCellsParams> = {\n id: 'sheet.command.unmerge-cells',\n\n handler(ctx, params): CommandResult {\n const removed: MergePatch[] = []\n for (const range of ctx.sheet.merges.getMerges()) {\n if (rangesIntersect(range, params.range)) {\n removed.push({ kind: 'merge', range, before: true, after: false })\n }\n }\n if (removed.length === 0) return { mutations: [] }\n for (const patch of removed) ctx.applyPatch(patch, 'redo')\n return { mutations: [{ redo: removed, undo: [...removed].reverse() }] }\n }\n}\n"],"mappings":";;;;;;;;;AAcA,MAAa,oBAA0D;CACrE,IAAI;CAEJ,QAAQ,KAAK,QAAkC;EAC7C,MAAM,EAAE,UAAU;EAClB,MAAM,EAAE,OAAO,YAAY,YAAY,MAAM,OAAO,aAAa,OAAO,KAAK;EAG7E,MAAM,cAA2B,CAAC;EAClC,IAAI;EACJ,IAAI;EACJ,KAAK,MAAM,QAAQ,aAAa,UAAU,GAAG;GAC3C,MAAM,SAAS,MAAM,MAAM,QAAQ,IAAI;GACvC,IAAI,UAAU,CAAC,YAAY,CAAC,gBAAgB,MAAM,GAAG,WAAW;GAChE,MAAM,QAAmB;IAAE,MAAM;IAAQ;IAAM;IAAQ,OAAO,KAAA;GAAU;GACxE,IAAI,KAAK,QAAQ,WAAW,MAAM,OAAO,KAAK,QAAQ,WAAW,MAAM,KACrE,cAAc;GAEhB,YAAY,KAAK,KAAK;EACxB;EACA,IAAI,YAAY,aACd,YAAY,QAAQ,EAAE,GAAG,SAAS;EASpC,MAAM,UAAmB,CACvB,GAAG,CANH,GAAG,QAAQ,KAAK,WAAuB;GAAE,MAAM;GAAS;GAAO,QAAQ;GAAM,OAAO;EAAM,EAAE,GAC5F;GAAE,MAAM;GAAS,OAAO;GAAY,QAAQ;GAAO,OAAO;EAAK,CAKjD,GACd,GAAG,YAAY,QAAQ,UAAU,CAAC,cAAc,MAAM,QAAQ,MAAM,KAAK,CAAC,CAC5E;EACA,KAAK,MAAM,SAAS,SAAS,IAAI,WAAW,OAAO,MAAM;EAEzD,OAAO;GAAE,WAAW,CAAC;IAAE,MAAM;IAAS,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,QAAQ;GAAE,CAAC;GAAG,QAAQ;EAAW;CAC5F;AACF;;AAOA,MAAa,sBAAmD;CAC9D,IAAI;CAEJ,QAAQ,KAAK,QAAuB;EAClC,MAAM,UAAwB,CAAC;EAC/B,KAAK,MAAM,SAAS,IAAI,MAAM,OAAO,UAAU,GAC7C,IAAI,gBAAgB,OAAO,OAAO,KAAK,GACrC,QAAQ,KAAK;GAAE,MAAM;GAAS;GAAO,QAAQ;GAAM,OAAO;EAAM,CAAC;EAGrE,IAAI,QAAQ,WAAW,GAAG,OAAO,EAAE,WAAW,CAAC,EAAE;EACjD,KAAK,MAAM,SAAS,SAAS,IAAI,WAAW,OAAO,MAAM;EACzD,OAAO,EAAE,WAAW,CAAC;GAAE,MAAM;GAAS,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,QAAQ;EAAE,CAAC,EAAE;CACxE;AACF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Command, CommandContext, CommandResult } from "./types.js";
|
|
2
|
+
//#region src/core/command/registry.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* 命令注册表:register / execute。
|
|
5
|
+
* 命令无状态,可全局共享(见 default-registry);阶段 4 工具扩展可注册自定义命令。
|
|
6
|
+
*/
|
|
7
|
+
declare class CommandRegistry {
|
|
8
|
+
private commands;
|
|
9
|
+
/** 注册命令;同 id 重复注册视为错误 */
|
|
10
|
+
register(command: Command): void;
|
|
11
|
+
get(id: string): Command | undefined;
|
|
12
|
+
/** 执行命令;未注册的 id 抛错 */
|
|
13
|
+
execute<R>(ctx: CommandContext, id: string, params: unknown): CommandResult<R> | undefined;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { CommandRegistry };
|
|
17
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/core/command/registry.ts
|
|
2
|
+
/**
|
|
3
|
+
* 命令注册表:register / execute。
|
|
4
|
+
* 命令无状态,可全局共享(见 default-registry);阶段 4 工具扩展可注册自定义命令。
|
|
5
|
+
*/
|
|
6
|
+
var CommandRegistry = class {
|
|
7
|
+
commands = /* @__PURE__ */ new Map();
|
|
8
|
+
/** 注册命令;同 id 重复注册视为错误 */
|
|
9
|
+
register(command) {
|
|
10
|
+
if (this.commands.has(command.id)) throw new Error(`命令重复注册: ${command.id}`);
|
|
11
|
+
this.commands.set(command.id, command);
|
|
12
|
+
}
|
|
13
|
+
get(id) {
|
|
14
|
+
return this.commands.get(id);
|
|
15
|
+
}
|
|
16
|
+
/** 执行命令;未注册的 id 抛错 */
|
|
17
|
+
execute(ctx, id, params) {
|
|
18
|
+
const command = this.commands.get(id);
|
|
19
|
+
if (!command) throw new Error(`命令未注册: ${id}`);
|
|
20
|
+
return command.handler(ctx, params);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
export { CommandRegistry };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","names":[],"sources":["../../../src/core/command/registry.ts"],"sourcesContent":["import type { Command, CommandContext, CommandResult } from './types'\n\n/**\n * 命令注册表:register / execute。\n * 命令无状态,可全局共享(见 default-registry);阶段 4 工具扩展可注册自定义命令。\n */\nexport class CommandRegistry {\n private commands = new Map<string, Command>()\n\n /** 注册命令;同 id 重复注册视为错误 */\n register(command: Command): void {\n if (this.commands.has(command.id)) {\n throw new Error(`命令重复注册: ${command.id}`)\n }\n this.commands.set(command.id, command)\n }\n\n get(id: string): Command | undefined {\n return this.commands.get(id)\n }\n\n /** 执行命令;未注册的 id 抛错 */\n execute<R>(ctx: CommandContext, id: string, params: unknown): CommandResult<R> | undefined {\n const command = this.commands.get(id)\n if (!command) {\n throw new Error(`命令未注册: ${id}`)\n }\n return command.handler(ctx, params) as CommandResult<R> | undefined\n }\n}\n"],"mappings":";;;;;AAMA,IAAa,kBAAb,MAA6B;CAC3B,2BAAmB,IAAI,IAAqB;;CAG5C,SAAS,SAAwB;EAC/B,IAAI,KAAK,SAAS,IAAI,QAAQ,EAAE,GAC9B,MAAM,IAAI,MAAM,WAAW,QAAQ,IAAI;EAEzC,KAAK,SAAS,IAAI,QAAQ,IAAI,OAAO;CACvC;CAEA,IAAI,IAAiC;EACnC,OAAO,KAAK,SAAS,IAAI,EAAE;CAC7B;;CAGA,QAAW,KAAqB,IAAY,QAA+C;EACzF,MAAM,UAAU,KAAK,SAAS,IAAI,EAAE;EACpC,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,UAAU,IAAI;EAEhC,OAAO,QAAQ,QAAQ,KAAK,MAAM;CACpC;AACF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CellAddress } from "../address.js";
|
|
2
|
+
import { Command } from "./types.js";
|
|
3
|
+
//#region src/core/command/set-cell-formula.d.ts
|
|
4
|
+
interface SetCellFormulaParams {
|
|
5
|
+
addr: CellAddress;
|
|
6
|
+
/** 公式文本(不含 '=') */
|
|
7
|
+
formula: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 公式写入命令:只登记公式原文(f);计算缓存(v/t)由命令执行后的
|
|
11
|
+
* 增量重算以派生补丁填充(见 Sheet.executeCommand),重算波及的所有变更
|
|
12
|
+
* 与源补丁并入同一 undo 单元。
|
|
13
|
+
*/
|
|
14
|
+
declare const SetCellFormulaCommand: Command<SetCellFormulaParams>;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { SetCellFormulaCommand, SetCellFormulaParams };
|
|
17
|
+
//# sourceMappingURL=set-cell-formula.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/core/command/set-cell-formula.ts
|
|
2
|
+
/**
|
|
3
|
+
* 公式写入命令:只登记公式原文(f);计算缓存(v/t)由命令执行后的
|
|
4
|
+
* 增量重算以派生补丁填充(见 Sheet.executeCommand),重算波及的所有变更
|
|
5
|
+
* 与源补丁并入同一 undo 单元。
|
|
6
|
+
*/
|
|
7
|
+
const SetCellFormulaCommand = {
|
|
8
|
+
id: "sheet.command.set-cell-formula",
|
|
9
|
+
handler(ctx, params) {
|
|
10
|
+
const anchor = ctx.sheet.merges.resolveAnchor(params.addr);
|
|
11
|
+
const before = ctx.sheet.store.getCell(anchor);
|
|
12
|
+
if (before?.f === params.formula) return { mutations: [] };
|
|
13
|
+
const patch = {
|
|
14
|
+
kind: "cell",
|
|
15
|
+
addr: anchor,
|
|
16
|
+
before,
|
|
17
|
+
after: { f: params.formula }
|
|
18
|
+
};
|
|
19
|
+
ctx.applyPatch(patch, "redo");
|
|
20
|
+
return { mutations: [{
|
|
21
|
+
redo: [patch],
|
|
22
|
+
undo: [patch]
|
|
23
|
+
}] };
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
export { SetCellFormulaCommand };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=set-cell-formula.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"set-cell-formula.js","names":[],"sources":["../../../src/core/command/set-cell-formula.ts"],"sourcesContent":["import type { CellAddress } from '../address'\nimport type { CellData } from '../cell-store'\nimport type { CellPatch, Command, CommandResult } from './types'\n\nexport interface SetCellFormulaParams {\n addr: CellAddress\n /** 公式文本(不含 '=') */\n formula: string\n}\n\n/**\n * 公式写入命令:只登记公式原文(f);计算缓存(v/t)由命令执行后的\n * 增量重算以派生补丁填充(见 Sheet.executeCommand),重算波及的所有变更\n * 与源补丁并入同一 undo 单元。\n */\nexport const SetCellFormulaCommand: Command<SetCellFormulaParams> = {\n id: 'sheet.command.set-cell-formula',\n\n handler(ctx, params): CommandResult {\n const anchor = ctx.sheet.merges.resolveAnchor(params.addr)\n const before = ctx.sheet.store.getCell(anchor)\n // 同公式重复输入 = 无实际变更,不入历史\n if (before?.f === params.formula) return { mutations: [] }\n const after: CellData = { f: params.formula }\n const patch: CellPatch = { kind: 'cell', addr: anchor, before, after }\n ctx.applyPatch(patch, 'redo')\n return { mutations: [{ redo: [patch], undo: [patch] }] }\n }\n}\n"],"mappings":";;;;;;AAeA,MAAa,wBAAuD;CAClE,IAAI;CAEJ,QAAQ,KAAK,QAAuB;EAClC,MAAM,SAAS,IAAI,MAAM,OAAO,cAAc,OAAO,IAAI;EACzD,MAAM,SAAS,IAAI,MAAM,MAAM,QAAQ,MAAM;EAE7C,IAAI,QAAQ,MAAM,OAAO,SAAS,OAAO,EAAE,WAAW,CAAC,EAAE;EAEzD,MAAM,QAAmB;GAAE,MAAM;GAAQ,MAAM;GAAQ;GAAQ,OAAA,EADrC,GAAG,OAAO,QAC+B;EAAE;EACrE,IAAI,WAAW,OAAO,MAAM;EAC5B,OAAO,EAAE,WAAW,CAAC;GAAE,MAAM,CAAC,KAAK;GAAG,MAAM,CAAC,KAAK;EAAE,CAAC,EAAE;CACzD;AACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CellAddress } from "../address.js";
|
|
2
|
+
import { CellData } from "../cell-store.js";
|
|
3
|
+
import { Command } from "./types.js";
|
|
4
|
+
//#region src/core/command/set-cell-value.d.ts
|
|
5
|
+
/** SetCellValueCommand 的批量写入项 */
|
|
6
|
+
interface SetCellValueItem {
|
|
7
|
+
addr: CellAddress;
|
|
8
|
+
/** 目标数据;空数据(无公式且 v 为 null/undefined/'')= 清除 */
|
|
9
|
+
data?: CellData;
|
|
10
|
+
}
|
|
11
|
+
interface SetCellValueParams {
|
|
12
|
+
/** 批量写入(供粘贴/填充复用);一次调用 = 一个 undo 单元 */
|
|
13
|
+
items: SetCellValueItem[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 单元格写入命令:逐项解析锚点、捕获 before/after 差量并立即应用。
|
|
17
|
+
* 与 before 相等的项跳过(不产生补丁);全部无变化时不入历史。
|
|
18
|
+
*/
|
|
19
|
+
declare const SetCellValueCommand: Command<SetCellValueParams>;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { SetCellValueCommand, SetCellValueItem, SetCellValueParams };
|
|
22
|
+
//# sourceMappingURL=set-cell-value.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { cellDataEqual, isEmptyCellData } from "../cell-store.js";
|
|
2
|
+
//#region src/core/command/set-cell-value.ts
|
|
3
|
+
/**
|
|
4
|
+
* 单元格写入命令:逐项解析锚点、捕获 before/after 差量并立即应用。
|
|
5
|
+
* 与 before 相等的项跳过(不产生补丁);全部无变化时不入历史。
|
|
6
|
+
*/
|
|
7
|
+
const SetCellValueCommand = {
|
|
8
|
+
id: "sheet.command.set-cell-value",
|
|
9
|
+
handler(ctx, params) {
|
|
10
|
+
const patches = [];
|
|
11
|
+
for (const item of params.items) {
|
|
12
|
+
const anchor = ctx.sheet.merges.resolveAnchor(item.addr);
|
|
13
|
+
const before = ctx.sheet.store.getCell(anchor);
|
|
14
|
+
const after = isEmptyCellData(item.data) ? void 0 : item.data;
|
|
15
|
+
if (cellDataEqual(before, after)) continue;
|
|
16
|
+
const patch = {
|
|
17
|
+
kind: "cell",
|
|
18
|
+
addr: anchor,
|
|
19
|
+
before,
|
|
20
|
+
after
|
|
21
|
+
};
|
|
22
|
+
ctx.applyPatch(patch, "redo");
|
|
23
|
+
patches.push(patch);
|
|
24
|
+
}
|
|
25
|
+
if (patches.length === 0) return { mutations: [] };
|
|
26
|
+
return { mutations: [{
|
|
27
|
+
redo: patches,
|
|
28
|
+
undo: [...patches].reverse()
|
|
29
|
+
}] };
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
export { SetCellValueCommand };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=set-cell-value.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"set-cell-value.js","names":[],"sources":["../../../src/core/command/set-cell-value.ts"],"sourcesContent":["import type { CellAddress } from '../address'\nimport { cellDataEqual, isEmptyCellData, type CellData } from '../cell-store'\nimport type { CellPatch, Command, CommandResult } from './types'\n\n/** SetCellValueCommand 的批量写入项 */\nexport interface SetCellValueItem {\n addr: CellAddress\n /** 目标数据;空数据(无公式且 v 为 null/undefined/'')= 清除 */\n data?: CellData\n}\n\nexport interface SetCellValueParams {\n /** 批量写入(供粘贴/填充复用);一次调用 = 一个 undo 单元 */\n items: SetCellValueItem[]\n}\n\n/**\n * 单元格写入命令:逐项解析锚点、捕获 before/after 差量并立即应用。\n * 与 before 相等的项跳过(不产生补丁);全部无变化时不入历史。\n */\nexport const SetCellValueCommand: Command<SetCellValueParams> = {\n id: 'sheet.command.set-cell-value',\n\n handler(ctx, params): CommandResult {\n const patches: CellPatch[] = []\n for (const item of params.items) {\n const anchor = ctx.sheet.merges.resolveAnchor(item.addr)\n const before = ctx.sheet.store.getCell(anchor)\n const after = isEmptyCellData(item.data) ? undefined : item.data\n if (cellDataEqual(before, after)) continue\n const patch: CellPatch = { kind: 'cell', addr: anchor, before, after }\n ctx.applyPatch(patch, 'redo')\n patches.push(patch)\n }\n if (patches.length === 0) return { mutations: [] }\n return { mutations: [{ redo: patches, undo: [...patches].reverse() }] }\n }\n}\n"],"mappings":";;;;;;AAoBA,MAAa,sBAAmD;CAC9D,IAAI;CAEJ,QAAQ,KAAK,QAAuB;EAClC,MAAM,UAAuB,CAAC;EAC9B,KAAK,MAAM,QAAQ,OAAO,OAAO;GAC/B,MAAM,SAAS,IAAI,MAAM,OAAO,cAAc,KAAK,IAAI;GACvD,MAAM,SAAS,IAAI,MAAM,MAAM,QAAQ,MAAM;GAC7C,MAAM,QAAQ,gBAAgB,KAAK,IAAI,IAAI,KAAA,IAAY,KAAK;GAC5D,IAAI,cAAc,QAAQ,KAAK,GAAG;GAClC,MAAM,QAAmB;IAAE,MAAM;IAAQ,MAAM;IAAQ;IAAQ;GAAM;GACrE,IAAI,WAAW,OAAO,MAAM;GAC5B,QAAQ,KAAK,KAAK;EACpB;EACA,IAAI,QAAQ,WAAW,GAAG,OAAO,EAAE,WAAW,CAAC,EAAE;EACjD,OAAO,EAAE,WAAW,CAAC;GAAE,MAAM;GAAS,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,QAAQ;EAAE,CAAC,EAAE;CACxE;AACF"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { CellAddress, CellRange } from "../address.js";
|
|
2
|
+
import { CellData } from "../cell-store.js";
|
|
3
|
+
import { Sheet } from "../sheet.js";
|
|
4
|
+
//#region src/core/command/types.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* 命令系统类型定义(决策 5:命令 + 逆操作补丁)。
|
|
7
|
+
*
|
|
8
|
+
* - 一切模型变更都是 Command,执行时产出 Mutation
|
|
9
|
+
* - Patch 是受影响单元格 / 合并记录的 before/after 差量,不是全量快照
|
|
10
|
+
* - 同一批 Patch 双向回放:redo 方向应用 after,undo 方向应用 before
|
|
11
|
+
*/
|
|
12
|
+
/** 补丁回放方向 */
|
|
13
|
+
type PatchDirection = 'undo' | 'redo';
|
|
14
|
+
/** 单元格数据差量补丁(原始存储语义;undefined = 该侧无此格) */
|
|
15
|
+
interface CellPatch {
|
|
16
|
+
kind: 'cell';
|
|
17
|
+
addr: CellAddress;
|
|
18
|
+
/**
|
|
19
|
+
* 目标 sheet(跨表公式重算的派生补丁会落在其它 sheet 上);
|
|
20
|
+
* 缺省 = 命令所在 sheet。回放时按此字段路由到目标 sheet 的变更通道。
|
|
21
|
+
*/
|
|
22
|
+
sheet?: Sheet;
|
|
23
|
+
/** 变更前的数据(undefined = 原本为空格) */
|
|
24
|
+
before?: CellData;
|
|
25
|
+
/** 变更后的数据(undefined = 变更后为空格) */
|
|
26
|
+
after?: CellData;
|
|
27
|
+
}
|
|
28
|
+
/** 合并记录差量补丁(before/after = 该侧是否存在此合并) */
|
|
29
|
+
interface MergePatch {
|
|
30
|
+
kind: 'merge';
|
|
31
|
+
range: CellRange;
|
|
32
|
+
before: boolean;
|
|
33
|
+
after: boolean;
|
|
34
|
+
}
|
|
35
|
+
type Patch = CellPatch | MergePatch;
|
|
36
|
+
/**
|
|
37
|
+
* 一次命令执行产生的变更单元。
|
|
38
|
+
* `redo` 按列表顺序应用;`undo` 取同一批补丁的逆序(命令实现负责构造,
|
|
39
|
+
* 保证 undo 先撤销后登记的副作用,例如先移除新合并再恢复旧合并)。
|
|
40
|
+
*/
|
|
41
|
+
interface Mutation {
|
|
42
|
+
redo: Patch[];
|
|
43
|
+
undo: Patch[];
|
|
44
|
+
}
|
|
45
|
+
/** 命令执行结果 */
|
|
46
|
+
interface CommandResult<R = unknown> {
|
|
47
|
+
/** 产生的 mutation 列表(空数组 = 无实际变更,不入历史) */
|
|
48
|
+
mutations: Mutation[];
|
|
49
|
+
/** 命令附带返回值(如 MergeCellsCommand 返回最终生效区域) */
|
|
50
|
+
result?: R;
|
|
51
|
+
}
|
|
52
|
+
/** 命令执行上下文 */
|
|
53
|
+
interface CommandContext {
|
|
54
|
+
sheet: Sheet;
|
|
55
|
+
/**
|
|
56
|
+
* 应用单个补丁。命令 handler 执行时对 redo 补丁逐项调用;
|
|
57
|
+
* HistoryManager 回放 undo/redo 亦走此路径,保证只有一条变更通道。
|
|
58
|
+
*/
|
|
59
|
+
applyPatch(patch: Patch, direction: PatchDirection): void;
|
|
60
|
+
}
|
|
61
|
+
/** 命令:id + handler */
|
|
62
|
+
interface Command<P = unknown, R = unknown> {
|
|
63
|
+
readonly id: string;
|
|
64
|
+
handler(ctx: CommandContext, params: P): CommandResult<R> | undefined;
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { CellPatch, Command, CommandContext, CommandResult, MergePatch, Mutation, Patch, PatchDirection };
|
|
68
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//#region src/core/events.ts
|
|
2
|
+
var TypedEventEmitter = class {
|
|
3
|
+
handlers = /* @__PURE__ */ new Map();
|
|
4
|
+
/** 订阅事件,返回取消订阅函数 */
|
|
5
|
+
on(type, handler) {
|
|
6
|
+
let set = this.handlers.get(type);
|
|
7
|
+
if (!set) {
|
|
8
|
+
set = /* @__PURE__ */ new Set();
|
|
9
|
+
this.handlers.set(type, set);
|
|
10
|
+
}
|
|
11
|
+
set.add(handler);
|
|
12
|
+
return () => this.off(type, handler);
|
|
13
|
+
}
|
|
14
|
+
off(type, handler) {
|
|
15
|
+
this.handlers.get(type)?.delete(handler);
|
|
16
|
+
}
|
|
17
|
+
emit(type, payload) {
|
|
18
|
+
const set = this.handlers.get(type);
|
|
19
|
+
if (!set) return;
|
|
20
|
+
for (const handler of set) handler(payload);
|
|
21
|
+
}
|
|
22
|
+
clear() {
|
|
23
|
+
this.handlers.clear();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
export { TypedEventEmitter };
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","names":[],"sources":["../../src/core/events.ts"],"sourcesContent":["/**\n * 包内轻量类型化事件发射器(core 内部基建,不对外导出实现细节)。\n */\n\nexport type EventHandler<T> = (payload: T) => void\n\nexport class TypedEventEmitter<EventMap extends Record<string, unknown>> {\n private handlers = new Map<keyof EventMap, Set<EventHandler<EventMap[keyof EventMap]>>>()\n\n /** 订阅事件,返回取消订阅函数 */\n on<K extends keyof EventMap>(type: K, handler: EventHandler<EventMap[K]>): () => void {\n let set = this.handlers.get(type)\n if (!set) {\n set = new Set()\n this.handlers.set(type, set)\n }\n set.add(handler as EventHandler<EventMap[keyof EventMap]>)\n return () => this.off(type, handler)\n }\n\n off<K extends keyof EventMap>(type: K, handler: EventHandler<EventMap[K]>): void {\n this.handlers.get(type)?.delete(handler as EventHandler<EventMap[keyof EventMap]>)\n }\n\n emit<K extends keyof EventMap>(type: K, payload: EventMap[K]): void {\n const set = this.handlers.get(type)\n if (!set) return\n // Set 迭代天然容忍遍历中取消订阅\n for (const handler of set) {\n ;(handler as EventHandler<EventMap[K]>)(payload)\n }\n }\n\n clear(): void {\n this.handlers.clear()\n }\n}\n"],"mappings":";AAMA,IAAa,oBAAb,MAAyE;CACvE,2BAAmB,IAAI,IAAiE;;CAGxF,GAA6B,MAAS,SAAgD;EACpF,IAAI,MAAM,KAAK,SAAS,IAAI,IAAI;EAChC,IAAI,CAAC,KAAK;GACR,sBAAM,IAAI,IAAI;GACd,KAAK,SAAS,IAAI,MAAM,GAAG;EAC7B;EACA,IAAI,IAAI,OAAiD;EACzD,aAAa,KAAK,IAAI,MAAM,OAAO;CACrC;CAEA,IAA8B,MAAS,SAA0C;EAC/E,KAAK,SAAS,IAAI,IAAI,CAAC,EAAE,OAAO,OAAiD;CACnF;CAEA,KAA+B,MAAS,SAA4B;EAClE,MAAM,MAAM,KAAK,SAAS,IAAI,IAAI;EAClC,IAAI,CAAC,KAAK;EAEV,KAAK,MAAM,WAAW,KACnB,QAAuC,OAAO;CAEnD;CAEA,QAAc;EACZ,KAAK,SAAS,MAAM;CACtB;AACF"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { CellAddress, CellRange } from "../address.js";
|
|
2
|
+
//#region src/core/formula/ast.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* 公式 AST 节点。
|
|
5
|
+
* 引用节点携带可选 sheet 名(跨表引用);缺省 = 公式所在 sheet。
|
|
6
|
+
*/
|
|
7
|
+
type BinaryOperator = '+' | '-' | '*' | '/' | '^' | '&' | '=' | '<>' | '<' | '<=' | '>' | '>=';
|
|
8
|
+
type AstNode = {
|
|
9
|
+
kind: 'number';
|
|
10
|
+
value: number;
|
|
11
|
+
} | {
|
|
12
|
+
kind: 'string';
|
|
13
|
+
value: string;
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'boolean';
|
|
16
|
+
value: boolean;
|
|
17
|
+
} |
|
|
18
|
+
/** 未知名称(如裸写的 Sheet2 / 未定义命名);求值为 #NAME? */
|
|
19
|
+
{
|
|
20
|
+
kind: 'name';
|
|
21
|
+
name: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'cell';
|
|
24
|
+
sheet?: string;
|
|
25
|
+
addr: CellAddress;
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'range';
|
|
28
|
+
sheet?: string;
|
|
29
|
+
range: CellRange;
|
|
30
|
+
} | {
|
|
31
|
+
kind: 'unary';
|
|
32
|
+
op: '-' | '+';
|
|
33
|
+
operand: AstNode;
|
|
34
|
+
} |
|
|
35
|
+
/** 百分号后缀运算(除以 100) */
|
|
36
|
+
{
|
|
37
|
+
kind: 'percent';
|
|
38
|
+
operand: AstNode;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'binary';
|
|
41
|
+
op: BinaryOperator;
|
|
42
|
+
left: AstNode;
|
|
43
|
+
right: AstNode;
|
|
44
|
+
} | {
|
|
45
|
+
kind: 'call';
|
|
46
|
+
name: string;
|
|
47
|
+
args: AstNode[];
|
|
48
|
+
};
|
|
49
|
+
/** 公式中的引用(依赖图提取用):sheet 缺省由所属公式节点补齐 */
|
|
50
|
+
interface AstReference {
|
|
51
|
+
sheet?: string;
|
|
52
|
+
range: CellRange;
|
|
53
|
+
}
|
|
54
|
+
/** 遍历 AST,收集全部引用(单格视为 1×1 区域) */
|
|
55
|
+
declare function collectReferences(node: AstNode, out?: AstReference[]): AstReference[];
|
|
56
|
+
//#endregion
|
|
57
|
+
export { AstNode, AstReference, BinaryOperator, collectReferences };
|
|
58
|
+
//# sourceMappingURL=ast.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/core/formula/ast.ts
|
|
2
|
+
/** 遍历 AST,收集全部引用(单格视为 1×1 区域) */
|
|
3
|
+
function collectReferences(node, out = []) {
|
|
4
|
+
switch (node.kind) {
|
|
5
|
+
case "cell":
|
|
6
|
+
out.push({
|
|
7
|
+
sheet: node.sheet,
|
|
8
|
+
range: {
|
|
9
|
+
start: node.addr,
|
|
10
|
+
end: node.addr
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
break;
|
|
14
|
+
case "range":
|
|
15
|
+
out.push({
|
|
16
|
+
sheet: node.sheet,
|
|
17
|
+
range: node.range
|
|
18
|
+
});
|
|
19
|
+
break;
|
|
20
|
+
case "unary":
|
|
21
|
+
case "percent":
|
|
22
|
+
collectReferences(node.operand, out);
|
|
23
|
+
break;
|
|
24
|
+
case "binary":
|
|
25
|
+
collectReferences(node.left, out);
|
|
26
|
+
collectReferences(node.right, out);
|
|
27
|
+
break;
|
|
28
|
+
case "call":
|
|
29
|
+
for (const arg of node.args) collectReferences(arg, out);
|
|
30
|
+
break;
|
|
31
|
+
default: break;
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { collectReferences };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=ast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast.js","names":[],"sources":["../../../src/core/formula/ast.ts"],"sourcesContent":["import type { CellAddress, CellRange } from '../address'\n\n/**\n * 公式 AST 节点。\n * 引用节点携带可选 sheet 名(跨表引用);缺省 = 公式所在 sheet。\n */\n\nexport type BinaryOperator =\n | '+'\n | '-'\n | '*'\n | '/'\n | '^'\n | '&'\n | '='\n | '<>'\n | '<'\n | '<='\n | '>'\n | '>='\n\nexport type AstNode =\n | { kind: 'number'; value: number }\n | { kind: 'string'; value: string }\n | { kind: 'boolean'; value: boolean }\n /** 未知名称(如裸写的 Sheet2 / 未定义命名);求值为 #NAME? */\n | { kind: 'name'; name: string }\n | { kind: 'cell'; sheet?: string; addr: CellAddress }\n | { kind: 'range'; sheet?: string; range: CellRange }\n | { kind: 'unary'; op: '-' | '+'; operand: AstNode }\n /** 百分号后缀运算(除以 100) */\n | { kind: 'percent'; operand: AstNode }\n | { kind: 'binary'; op: BinaryOperator; left: AstNode; right: AstNode }\n | { kind: 'call'; name: string; args: AstNode[] }\n\n/** 公式中的引用(依赖图提取用):sheet 缺省由所属公式节点补齐 */\nexport interface AstReference {\n sheet?: string\n range: CellRange\n}\n\n/** 遍历 AST,收集全部引用(单格视为 1×1 区域) */\nexport function collectReferences(node: AstNode, out: AstReference[] = []): AstReference[] {\n switch (node.kind) {\n case 'cell':\n out.push({ sheet: node.sheet, range: { start: node.addr, end: node.addr } })\n break\n case 'range':\n out.push({ sheet: node.sheet, range: node.range })\n break\n case 'unary':\n case 'percent':\n collectReferences(node.operand, out)\n break\n case 'binary':\n collectReferences(node.left, out)\n collectReferences(node.right, out)\n break\n case 'call':\n for (const arg of node.args) collectReferences(arg, out)\n break\n default:\n break\n }\n return out\n}\n"],"mappings":";;AA0CA,SAAgB,kBAAkB,MAAe,MAAsB,CAAC,GAAmB;CACzF,QAAQ,KAAK,MAAb;EACE,KAAK;GACH,IAAI,KAAK;IAAE,OAAO,KAAK;IAAO,OAAO;KAAE,OAAO,KAAK;KAAM,KAAK,KAAK;IAAK;GAAE,CAAC;GAC3E;EACF,KAAK;GACH,IAAI,KAAK;IAAE,OAAO,KAAK;IAAO,OAAO,KAAK;GAAM,CAAC;GACjD;EACF,KAAK;EACL,KAAK;GACH,kBAAkB,KAAK,SAAS,GAAG;GACnC;EACF,KAAK;GACH,kBAAkB,KAAK,MAAM,GAAG;GAChC,kBAAkB,KAAK,OAAO,GAAG;GACjC;EACF,KAAK;GACH,KAAK,MAAM,OAAO,KAAK,MAAM,kBAAkB,KAAK,GAAG;GACvD;EACF,SACE;CACJ;CACA,OAAO;AACT"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { CellAddress, CellRange } from "../address.js";
|
|
2
|
+
import { CellData } from "../cell-store.js";
|
|
3
|
+
import { CellPatch } from "../command/types.js";
|
|
4
|
+
import { AstNode } from "./ast.js";
|
|
5
|
+
import { Sheet } from "../sheet.js";
|
|
6
|
+
//#region src/core/formula/dependency-graph.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* 依赖图(工作簿级计算中枢):
|
|
9
|
+
* - sheet 注册表:跨表引用按名解析(不存在 → #REF!)
|
|
10
|
+
* - 正向索引:公式格 → 引用格集合(节点 deps);反向索引:引用格 → 依赖者
|
|
11
|
+
* (单格引用走 cellKey 精确索引 O(1),区域引用走线性扫描)
|
|
12
|
+
* - 变更时标脏(向上 BFS 收集全部依赖者)+ 拓扑序增量重算(递归向下,memo 去重)
|
|
13
|
+
* - 循环引用检测:求值在途栈遇回边 → 环上所有格 #CYCLE!;依赖环外格只传播错误,
|
|
14
|
+
* 打破循环(编辑环上格)后经标脏重算自动恢复
|
|
15
|
+
*
|
|
16
|
+
* 图状态与单元格存储严格同步:所有 f 变更(命令/undo/redo/rollback 回放)都经
|
|
17
|
+
* Sheet.applyPatch → syncCell 维护节点;重算只发生在命令执行后(见 Sheet.executeCommand),
|
|
18
|
+
* undo/redo 靠派生补丁精确回放缓存值,不重算。
|
|
19
|
+
*/
|
|
20
|
+
/** 公式节点的一个依赖(sheet 名已补齐为具体值) */
|
|
21
|
+
interface FormulaDependency {
|
|
22
|
+
sheetName: string;
|
|
23
|
+
range: CellRange;
|
|
24
|
+
}
|
|
25
|
+
/** 公式节点(依赖图管理的公式格状态;依赖图外部只读使用) */
|
|
26
|
+
interface FormulaNode {
|
|
27
|
+
readonly sheetName: string;
|
|
28
|
+
readonly addr: CellAddress;
|
|
29
|
+
readonly formula: string;
|
|
30
|
+
/** null = 解析失败(求值 #ERROR!,无依赖) */
|
|
31
|
+
readonly ast: AstNode | null;
|
|
32
|
+
readonly deps: FormulaDependency[];
|
|
33
|
+
/** 反向索引清理器(removeNode 时执行) */
|
|
34
|
+
readonly cleanups: (() => void)[];
|
|
35
|
+
}
|
|
36
|
+
declare class DependencyGraph {
|
|
37
|
+
private readonly sheets;
|
|
38
|
+
/** 正向索引宿主:sheetName → cellKey → 公式节点 */
|
|
39
|
+
private readonly nodes;
|
|
40
|
+
/** 反向索引(单格引用):sheetName → cellKey → 依赖者集合 */
|
|
41
|
+
private readonly exact;
|
|
42
|
+
/** 反向索引(区域引用):sheetName → { 区域, 依赖者 } 集合(线性扫描) */
|
|
43
|
+
private readonly ranged;
|
|
44
|
+
registerSheet(sheet: Sheet): void;
|
|
45
|
+
/** 注销 sheet 并移除其全部公式节点(引用它的公式在下次重算时得 #REF!) */
|
|
46
|
+
unregisterSheet(sheet: Sheet): void;
|
|
47
|
+
getSheet(name: string): Sheet | undefined;
|
|
48
|
+
getNode(sheetName: string, addr: CellAddress): FormulaNode | undefined;
|
|
49
|
+
/** 公式节点总数(测试/调试用) */
|
|
50
|
+
get nodeCount(): number;
|
|
51
|
+
/**
|
|
52
|
+
* 单元格补丁后的图同步:f 增/删/改 → 节点增/删/重建。
|
|
53
|
+
* 由 Sheet.applyPatch(唯一变更通道)调用,命令与 undo/redo 回放均覆盖。
|
|
54
|
+
*/
|
|
55
|
+
syncCell(sheet: Sheet, addr: CellAddress, before?: CellData, after?: CellData): void;
|
|
56
|
+
/**
|
|
57
|
+
* 变更集 → 标脏 + 拓扑序重算 → 派生补丁(未应用)。
|
|
58
|
+
* 调用方(Sheet.executeCommand)负责应用补丁并并入同一 undo 单元。
|
|
59
|
+
*/
|
|
60
|
+
recalc(changed: readonly {
|
|
61
|
+
sheet: Sheet;
|
|
62
|
+
addr: CellAddress;
|
|
63
|
+
}[]): CellPatch[];
|
|
64
|
+
private addNode;
|
|
65
|
+
private removeNode;
|
|
66
|
+
private dependentsOf;
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
export { DependencyGraph, FormulaDependency, FormulaNode };
|
|
70
|
+
//# sourceMappingURL=dependency-graph.d.ts.map
|