@veltra/sheet 2.0.1 → 2.1.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.
Files changed (211) hide show
  1. package/dist/index.d.ts +4 -26
  2. package/dist/index.js +3 -23
  3. package/dist/tools/builtin.js +279 -8
  4. package/dist/tools/context.d.ts +54 -11
  5. package/dist/tools/context.js +28 -5
  6. package/dist/tools/download.js +68 -0
  7. package/dist/tools/export.worker.d.ts +16 -0
  8. package/dist/tools/export.worker.js +29 -0
  9. package/dist/tools/registry.d.ts +7 -4
  10. package/dist/tools/registry.js +1 -3
  11. package/dist/types/index.d.ts +1 -0
  12. package/dist/types/sheet.d.ts +6 -5
  13. package/dist/vue/formula-bar.js +393 -0
  14. package/dist/vue/formula-suggest-list.js +66 -0
  15. package/dist/vue/import-file.js +140 -0
  16. package/dist/vue/insert-count-menu-item.js +68 -0
  17. package/dist/vue/insert-image.js +79 -0
  18. package/dist/vue/popup-helpers.js +73 -0
  19. package/dist/vue/popups/border-popup.js +75 -0
  20. package/dist/vue/popups/border-preset-glyph.js +106 -0
  21. package/dist/vue/popups/export-popup.js +51 -0
  22. package/dist/vue/popups/fill-color-popup.js +37 -0
  23. package/dist/vue/popups/find-popup.js +113 -0
  24. package/dist/vue/popups/font-color-popup.js +37 -0
  25. package/dist/vue/popups/font-size-popup.js +63 -0
  26. package/dist/vue/popups/import.worker.d.ts +21 -0
  27. package/dist/vue/popups/import.worker.js +41 -0
  28. package/dist/vue/popups/insert-image-popup.js +38 -0
  29. package/dist/vue/sheet-context-menu.js +200 -0
  30. package/dist/vue/sheet-tabs.js +162 -0
  31. package/dist/vue/sheet-toolbar.js +106 -0
  32. package/dist/vue/sheet.js +144 -164
  33. package/dist/vue/style.d.ts +11 -1
  34. package/dist/vue/style.js +10 -0
  35. package/dist/vue/style2.css +564 -3
  36. package/dist/vue/use-find-replace.js +161 -0
  37. package/dist/vue/use-formula-ref-select.js +37 -0
  38. package/dist/vue/use-formula-suggest.js +126 -0
  39. package/dist/vue/use-sheet-grid.js +180 -0
  40. package/dist/vue/use-sheet-state.js +122 -0
  41. package/dist/vue/use-sheet-tabs-bar.js +90 -0
  42. package/dist/vue/use-tool-groups.js +42 -0
  43. package/dist/vue/use-tool-popup.js +123 -0
  44. package/dist/vue/use-toolbar-scroll.js +72 -0
  45. package/package.json +12 -7
  46. package/src/env.d.ts +11 -0
  47. package/src/index.ts +203 -14
  48. package/src/tools/__test__/builtin.test.ts +208 -66
  49. package/src/tools/__test__/context.test.ts +62 -2
  50. package/src/tools/builtin.ts +350 -6
  51. package/src/tools/context.ts +89 -11
  52. package/src/tools/download.ts +82 -0
  53. package/src/tools/export.worker.ts +61 -0
  54. package/src/tools/registry.ts +16 -1
  55. package/src/types/index.ts +6 -0
  56. package/src/types/sheet.ts +5 -3
  57. package/src/vue/__test__/formula-bar.test.ts +455 -0
  58. package/src/vue/__test__/import-file.test.ts +183 -0
  59. package/src/vue/__test__/insert-count-menu-item.test.ts +123 -0
  60. package/src/vue/__test__/insert-image-popup.test.ts +89 -0
  61. package/src/vue/__test__/insert-image.test.ts +100 -0
  62. package/src/vue/__test__/sheet-component.test.ts +716 -27
  63. package/src/vue/__test__/sheet-context-menu.test.ts +206 -0
  64. package/src/vue/__test__/use-formula-ref-select.test.ts +35 -0
  65. package/src/vue/__test__/use-formula-suggest.test.ts +83 -0
  66. package/src/vue/formula-bar.vue +473 -0
  67. package/src/vue/formula-suggest-list.vue +64 -0
  68. package/src/vue/import-file.ts +173 -0
  69. package/src/vue/insert-count-menu-item.vue +69 -0
  70. package/src/vue/insert-image.ts +90 -0
  71. package/src/vue/parsing.ts +11 -0
  72. package/src/vue/popup-helpers.ts +50 -0
  73. package/src/vue/popups/border-popup.vue +93 -0
  74. package/src/vue/popups/border-preset-glyph.vue +111 -0
  75. package/src/vue/popups/export-popup.vue +52 -0
  76. package/src/vue/popups/fill-color-popup.vue +39 -0
  77. package/src/vue/popups/find-popup.vue +111 -0
  78. package/src/vue/popups/font-color-popup.vue +39 -0
  79. package/src/vue/popups/font-size-popup.vue +52 -0
  80. package/src/vue/popups/import.worker.ts +66 -0
  81. package/src/vue/popups/insert-image-popup.vue +39 -0
  82. package/src/vue/sheet-context-menu.ts +229 -0
  83. package/src/vue/sheet-tabs.vue +162 -0
  84. package/src/vue/sheet-toolbar.vue +91 -0
  85. package/src/vue/sheet.vue +161 -186
  86. package/src/vue/style.scss +646 -3
  87. package/src/vue/style.ts +11 -0
  88. package/src/vue/use-find-replace.ts +200 -0
  89. package/src/vue/use-formula-ref-select.ts +34 -0
  90. package/src/vue/use-formula-suggest.ts +150 -0
  91. package/src/vue/use-sheet-grid.ts +243 -0
  92. package/src/vue/use-sheet-state.ts +161 -0
  93. package/src/vue/use-sheet-tabs-bar.ts +115 -0
  94. package/src/vue/use-tool-groups.ts +61 -0
  95. package/src/vue/use-tool-popup.ts +150 -0
  96. package/src/vue/use-toolbar-scroll.ts +95 -0
  97. package/dist/core/address.d.ts +0 -60
  98. package/dist/core/address.js +0 -142
  99. package/dist/core/address.js.map +0 -1
  100. package/dist/core/cell-store.d.ts +0 -61
  101. package/dist/core/cell-store.js +0 -135
  102. package/dist/core/cell-store.js.map +0 -1
  103. package/dist/core/command/default-registry.d.ts +0 -10
  104. package/dist/core/command/default-registry.js +0 -18
  105. package/dist/core/command/default-registry.js.map +0 -1
  106. package/dist/core/command/history.d.ts +0 -64
  107. package/dist/core/command/history.js +0 -127
  108. package/dist/core/command/history.js.map +0 -1
  109. package/dist/core/command/merge-cells.d.ts +0 -21
  110. package/dist/core/command/merge-cells.js +0 -74
  111. package/dist/core/command/merge-cells.js.map +0 -1
  112. package/dist/core/command/registry.d.ts +0 -17
  113. package/dist/core/command/registry.js +0 -26
  114. package/dist/core/command/registry.js.map +0 -1
  115. package/dist/core/command/set-cell-formula.d.ts +0 -17
  116. package/dist/core/command/set-cell-formula.js +0 -29
  117. package/dist/core/command/set-cell-formula.js.map +0 -1
  118. package/dist/core/command/set-cell-value.d.ts +0 -22
  119. package/dist/core/command/set-cell-value.js +0 -35
  120. package/dist/core/command/set-cell-value.js.map +0 -1
  121. package/dist/core/command/types.d.ts +0 -68
  122. package/dist/core/events.js +0 -29
  123. package/dist/core/events.js.map +0 -1
  124. package/dist/core/fill.d.ts +0 -34
  125. package/dist/core/fill.js +0 -320
  126. package/dist/core/fill.js.map +0 -1
  127. package/dist/core/formula/ast.d.ts +0 -58
  128. package/dist/core/formula/ast.js +0 -38
  129. package/dist/core/formula/ast.js.map +0 -1
  130. package/dist/core/formula/dependency-graph.d.ts +0 -70
  131. package/dist/core/formula/dependency-graph.js +0 -269
  132. package/dist/core/formula/dependency-graph.js.map +0 -1
  133. package/dist/core/formula/errors.d.ts +0 -25
  134. package/dist/core/formula/errors.js +0 -36
  135. package/dist/core/formula/errors.js.map +0 -1
  136. package/dist/core/formula/evaluator.d.ts +0 -41
  137. package/dist/core/formula/evaluator.js +0 -154
  138. package/dist/core/formula/evaluator.js.map +0 -1
  139. package/dist/core/formula/functions.d.ts +0 -30
  140. package/dist/core/formula/functions.js +0 -209
  141. package/dist/core/formula/functions.js.map +0 -1
  142. package/dist/core/formula/parser.d.ts +0 -7
  143. package/dist/core/formula/parser.js +0 -208
  144. package/dist/core/formula/parser.js.map +0 -1
  145. package/dist/core/formula/tokenizer.d.ts +0 -37
  146. package/dist/core/formula/tokenizer.js +0 -146
  147. package/dist/core/formula/tokenizer.js.map +0 -1
  148. package/dist/core/merge-manager.d.ts +0 -64
  149. package/dist/core/merge-manager.js +0 -111
  150. package/dist/core/merge-manager.js.map +0 -1
  151. package/dist/core/selection.d.ts +0 -31
  152. package/dist/core/selection.js +0 -62
  153. package/dist/core/selection.js.map +0 -1
  154. package/dist/core/sheet.d.ts +0 -116
  155. package/dist/core/sheet.js +0 -224
  156. package/dist/core/sheet.js.map +0 -1
  157. package/dist/core/workbook.d.ts +0 -43
  158. package/dist/core/workbook.js +0 -77
  159. package/dist/core/workbook.js.map +0 -1
  160. package/dist/grid/sheet-grid.d.ts +0 -81
  161. package/dist/grid/sheet-grid.js +0 -331
  162. package/dist/grid/sheet-grid.js.map +0 -1
  163. package/dist/grid/vtable-theme.js +0 -80
  164. package/dist/grid/vtable-theme.js.map +0 -1
  165. package/dist/tools/builtin.js.map +0 -1
  166. package/dist/tools/context.js.map +0 -1
  167. package/dist/tools/registry.js.map +0 -1
  168. package/dist/vue/index.d.ts +0 -2
  169. package/dist/vue/sheet.js.map +0 -1
  170. package/dist/vue/sheet.vue.d.ts +0 -24
  171. package/src/core/__test__/address.test.ts +0 -113
  172. package/src/core/__test__/cell-store.test.ts +0 -97
  173. package/src/core/__test__/command.test.ts +0 -377
  174. package/src/core/__test__/fill.test.ts +0 -140
  175. package/src/core/__test__/formula-adversarial.test.ts +0 -265
  176. package/src/core/__test__/formula-engine.test.ts +0 -470
  177. package/src/core/__test__/formula-functions.test.ts +0 -178
  178. package/src/core/__test__/formula-parser.test.ts +0 -160
  179. package/src/core/__test__/history.test.ts +0 -225
  180. package/src/core/__test__/merge-manager.test.ts +0 -106
  181. package/src/core/__test__/sheet.test.ts +0 -142
  182. package/src/core/address.ts +0 -166
  183. package/src/core/cell-store.ts +0 -173
  184. package/src/core/command/default-registry.ts +0 -15
  185. package/src/core/command/history.ts +0 -167
  186. package/src/core/command/index.ts +0 -7
  187. package/src/core/command/merge-cells.ts +0 -74
  188. package/src/core/command/registry.ts +0 -30
  189. package/src/core/command/set-cell-formula.ts +0 -29
  190. package/src/core/command/set-cell-value.ts +0 -38
  191. package/src/core/command/types.ts +0 -73
  192. package/src/core/events.ts +0 -37
  193. package/src/core/fill.ts +0 -314
  194. package/src/core/formula/ast.ts +0 -66
  195. package/src/core/formula/dependency-graph.ts +0 -329
  196. package/src/core/formula/errors.ts +0 -41
  197. package/src/core/formula/evaluator.ts +0 -219
  198. package/src/core/formula/functions.ts +0 -266
  199. package/src/core/formula/index.ts +0 -7
  200. package/src/core/formula/parser.ts +0 -198
  201. package/src/core/formula/tokenizer.ts +0 -160
  202. package/src/core/merge-manager.ts +0 -158
  203. package/src/core/selection.ts +0 -66
  204. package/src/core/sheet.ts +0 -282
  205. package/src/core/workbook.ts +0 -99
  206. package/src/grid/__test__/canvas-mock.ts +0 -49
  207. package/src/grid/__test__/setup.ts +0 -3
  208. package/src/grid/__test__/sheet-grid-formula.test.ts +0 -140
  209. package/src/grid/__test__/sheet-grid.test.ts +0 -275
  210. package/src/grid/sheet-grid.ts +0 -407
  211. package/src/grid/vtable-theme.ts +0 -61
package/dist/core/fill.js DELETED
@@ -1,320 +0,0 @@
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
@@ -1 +0,0 @@
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,58 +0,0 @@
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
@@ -1,38 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,70 +0,0 @@
1
- import { CellAddress, CellRange } from "../address.js";
2
- import { CellData } from "../cell-store.js";
3
- import { AstNode } from "./ast.js";
4
- import { Sheet } from "../sheet.js";
5
- import { CellPatch } from "../command/types.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