@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
@@ -1,38 +0,0 @@
1
- import type { CellAddress } from '../address'
2
- import { cellDataEqual, isEmptyCellData, type CellData } from '../cell-store'
3
- import type { CellPatch, Command, CommandResult } from './types'
4
-
5
- /** SetCellValueCommand 的批量写入项 */
6
- export interface SetCellValueItem {
7
- addr: CellAddress
8
- /** 目标数据;空数据(无公式且 v 为 null/undefined/'')= 清除 */
9
- data?: CellData
10
- }
11
-
12
- export interface SetCellValueParams {
13
- /** 批量写入(供粘贴/填充复用);一次调用 = 一个 undo 单元 */
14
- items: SetCellValueItem[]
15
- }
16
-
17
- /**
18
- * 单元格写入命令:逐项解析锚点、捕获 before/after 差量并立即应用。
19
- * 与 before 相等的项跳过(不产生补丁);全部无变化时不入历史。
20
- */
21
- export const SetCellValueCommand: Command<SetCellValueParams> = {
22
- id: 'sheet.command.set-cell-value',
23
-
24
- handler(ctx, params): CommandResult {
25
- const patches: CellPatch[] = []
26
- for (const item of params.items) {
27
- const anchor = ctx.sheet.merges.resolveAnchor(item.addr)
28
- const before = ctx.sheet.store.getCell(anchor)
29
- const after = isEmptyCellData(item.data) ? undefined : item.data
30
- if (cellDataEqual(before, after)) continue
31
- const patch: CellPatch = { kind: 'cell', addr: anchor, before, after }
32
- ctx.applyPatch(patch, 'redo')
33
- patches.push(patch)
34
- }
35
- if (patches.length === 0) return { mutations: [] }
36
- return { mutations: [{ redo: patches, undo: [...patches].reverse() }] }
37
- }
38
- }
@@ -1,73 +0,0 @@
1
- import type { CellAddress, CellRange } from '../address'
2
- import type { CellData } from '../cell-store'
3
- import type { Sheet } from '../sheet'
4
-
5
- /**
6
- * 命令系统类型定义(决策 5:命令 + 逆操作补丁)。
7
- *
8
- * - 一切模型变更都是 Command,执行时产出 Mutation
9
- * - Patch 是受影响单元格 / 合并记录的 before/after 差量,不是全量快照
10
- * - 同一批 Patch 双向回放:redo 方向应用 after,undo 方向应用 before
11
- */
12
-
13
- /** 补丁回放方向 */
14
- export type PatchDirection = 'undo' | 'redo'
15
-
16
- /** 单元格数据差量补丁(原始存储语义;undefined = 该侧无此格) */
17
- export interface CellPatch {
18
- kind: 'cell'
19
- addr: CellAddress
20
- /**
21
- * 目标 sheet(跨表公式重算的派生补丁会落在其它 sheet 上);
22
- * 缺省 = 命令所在 sheet。回放时按此字段路由到目标 sheet 的变更通道。
23
- */
24
- sheet?: Sheet
25
- /** 变更前的数据(undefined = 原本为空格) */
26
- before?: CellData
27
- /** 变更后的数据(undefined = 变更后为空格) */
28
- after?: CellData
29
- }
30
-
31
- /** 合并记录差量补丁(before/after = 该侧是否存在此合并) */
32
- export interface MergePatch {
33
- kind: 'merge'
34
- range: CellRange
35
- before: boolean
36
- after: boolean
37
- }
38
-
39
- export type Patch = CellPatch | MergePatch
40
-
41
- /**
42
- * 一次命令执行产生的变更单元。
43
- * `redo` 按列表顺序应用;`undo` 取同一批补丁的逆序(命令实现负责构造,
44
- * 保证 undo 先撤销后登记的副作用,例如先移除新合并再恢复旧合并)。
45
- */
46
- export interface Mutation {
47
- redo: Patch[]
48
- undo: Patch[]
49
- }
50
-
51
- /** 命令执行结果 */
52
- export interface CommandResult<R = unknown> {
53
- /** 产生的 mutation 列表(空数组 = 无实际变更,不入历史) */
54
- mutations: Mutation[]
55
- /** 命令附带返回值(如 MergeCellsCommand 返回最终生效区域) */
56
- result?: R
57
- }
58
-
59
- /** 命令执行上下文 */
60
- export interface CommandContext {
61
- sheet: Sheet
62
- /**
63
- * 应用单个补丁。命令 handler 执行时对 redo 补丁逐项调用;
64
- * HistoryManager 回放 undo/redo 亦走此路径,保证只有一条变更通道。
65
- */
66
- applyPatch(patch: Patch, direction: PatchDirection): void
67
- }
68
-
69
- /** 命令:id + handler */
70
- export interface Command<P = unknown, R = unknown> {
71
- readonly id: string
72
- handler(ctx: CommandContext, params: P): CommandResult<R> | undefined
73
- }
@@ -1,37 +0,0 @@
1
- /**
2
- * 包内轻量类型化事件发射器(core 内部基建,不对外导出实现细节)。
3
- */
4
-
5
- export type EventHandler<T> = (payload: T) => void
6
-
7
- export class TypedEventEmitter<EventMap extends Record<string, unknown>> {
8
- private handlers = new Map<keyof EventMap, Set<EventHandler<EventMap[keyof EventMap]>>>()
9
-
10
- /** 订阅事件,返回取消订阅函数 */
11
- on<K extends keyof EventMap>(type: K, handler: EventHandler<EventMap[K]>): () => void {
12
- let set = this.handlers.get(type)
13
- if (!set) {
14
- set = new Set()
15
- this.handlers.set(type, set)
16
- }
17
- set.add(handler as EventHandler<EventMap[keyof EventMap]>)
18
- return () => this.off(type, handler)
19
- }
20
-
21
- off<K extends keyof EventMap>(type: K, handler: EventHandler<EventMap[K]>): void {
22
- this.handlers.get(type)?.delete(handler as EventHandler<EventMap[keyof EventMap]>)
23
- }
24
-
25
- emit<K extends keyof EventMap>(type: K, payload: EventMap[K]): void {
26
- const set = this.handlers.get(type)
27
- if (!set) return
28
- // Set 迭代天然容忍遍历中取消订阅
29
- for (const handler of set) {
30
- ;(handler as EventHandler<EventMap[K]>)(payload)
31
- }
32
- }
33
-
34
- clear(): void {
35
- this.handlers.clear()
36
- }
37
- }
package/src/core/fill.ts DELETED
@@ -1,314 +0,0 @@
1
- import type { CellAddress, CellRange } from './address'
2
- import { colIndexToName, colNameToIndex, createRange, iterateRange } from './address'
3
- import type { CellData, CellType } from './cell-store'
4
- import type { SetCellValueItem } from './command/set-cell-value'
5
-
6
- /** 填充柄拖拽方向(与 VTable drag_fill_handle_end 一致) */
7
- export type FillDirection = 'top' | 'bottom' | 'left' | 'right'
8
-
9
- export interface GenerateFillOptions {
10
- /** 拖拽前的源选区(模型坐标) */
11
- source: CellRange
12
- /** 需要写入的目标区(不含源区,模型坐标) */
13
- target: CellRange
14
- /** 填充方向 */
15
- direction: FillDirection
16
- /** 读取源区单元格(原始存储语义) */
17
- getCellData: (addr: CellAddress) => CellData | undefined
18
- }
19
-
20
- /**
21
- * 由源选区 + 拖拽后扩展选区 + 方向,计算需要填充的目标区(模型坐标)。
22
- * 算法对齐 VTable 官方 fill-handle 示例(扩展选区含源区,目标为扩展部分)。
23
- */
24
- export function computeFillTargetRange(
25
- source: CellRange,
26
- direction: FillDirection,
27
- expanded: CellRange
28
- ): CellRange | null {
29
- const src = createRange(source.start, source.end)
30
- const exp = createRange(expanded.start, expanded.end)
31
-
32
- if (direction === 'bottom') {
33
- if (exp.end.row <= src.end.row) return null
34
- return {
35
- start: { row: src.end.row + 1, col: src.start.col },
36
- end: { row: exp.end.row, col: src.end.col }
37
- }
38
- }
39
- if (direction === 'top') {
40
- if (exp.start.row >= src.start.row) return null
41
- return {
42
- start: { row: exp.start.row, col: src.start.col },
43
- end: { row: src.start.row - 1, col: src.end.col }
44
- }
45
- }
46
- if (direction === 'right') {
47
- if (exp.end.col <= src.end.col) return null
48
- return {
49
- start: { row: src.start.row, col: src.end.col + 1 },
50
- end: { row: src.end.row, col: exp.end.col }
51
- }
52
- }
53
- // left
54
- if (exp.start.col >= src.start.col) return null
55
- return {
56
- start: { row: src.start.row, col: exp.start.col },
57
- end: { row: src.end.row, col: src.start.col - 1 }
58
- }
59
- }
60
-
61
- /**
62
- * 生成填充写入项(tile / 数字日期等差 / 公式相对引用位移)。
63
- * 调用方一次 `sheet.setCells(items)` 即可(单 undo 单元)。
64
- */
65
- export function generateFill(options: GenerateFillOptions): SetCellValueItem[] {
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
-
73
- const seriesByLane = new Map<number, SeriesInfo | null>()
74
- if (vertical) {
75
- for (let col = src.start.col; col <= src.end.col; col++) {
76
- const values: number[] = []
77
- let cellType: CellType | undefined
78
- let ok = true
79
- for (let row = src.start.row; row <= src.end.row; row++) {
80
- const data = getCellData({ row, col })
81
- const num = readSeriesNumber(data)
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 {
92
- for (let row = src.start.row; row <= src.end.row; row++) {
93
- const values: number[] = []
94
- let cellType: CellType | undefined
95
- let ok = true
96
- for (let col = src.start.col; col <= src.end.col; col++) {
97
- const data = getCellData({ row, col })
98
- const num = readSeriesNumber(data)
99
- if (num == null) {
100
- ok = false
101
- break
102
- }
103
- values.push(num.value)
104
- cellType = num.t
105
- }
106
- seriesByLane.set(row, ok ? makeSeriesInfo(values, cellType) : null)
107
- }
108
- }
109
-
110
- const items: SetCellValueItem[] = []
111
- for (const addr of iterateRange(tgt)) {
112
- const rowOffset = positiveMod(addr.row - src.start.row, srcRows)
113
- const colOffset = positiveMod(addr.col - src.start.col, srcCols)
114
- const srcAddr: CellAddress = { row: src.start.row + rowOffset, col: src.start.col + colOffset }
115
- const srcData = getCellData(srcAddr)
116
-
117
- if (srcData?.f != null && srcData.f !== '') {
118
- const deltaRow = addr.row - srcAddr.row
119
- const deltaCol = addr.col - srcAddr.col
120
- items.push({ addr, data: { f: shiftFormulaRefs(srcData.f, deltaRow, deltaCol) } })
121
- continue
122
- }
123
-
124
- const lane = vertical ? srcAddr.col : srcAddr.row
125
- const series = seriesByLane.get(lane)
126
- if (series) {
127
- const index = vertical ? addr.row - src.start.row : addr.col - src.start.col
128
- const value = series.first + index * series.delta
129
- items.push({ addr, data: { v: value, t: series.t ?? 'n' } })
130
- continue
131
- }
132
-
133
- // tile 复制(含空格 → 清除)
134
- items.push({ addr, data: cloneCellData(srcData) })
135
- }
136
- return items
137
- }
138
-
139
- interface SeriesInfo {
140
- first: number
141
- delta: number
142
- t?: CellType
143
- }
144
-
145
- function makeSeriesInfo(values: number[], t?: CellType): SeriesInfo {
146
- const first = values[0]!
147
- if (values.length === 1) {
148
- return { first, delta: 1, t }
149
- }
150
- const delta = (values[values.length - 1]! - first) / (values.length - 1)
151
- return { first, delta, t }
152
- }
153
-
154
- /** 可参与等差序列的数字/日期格;公式格与其它类型返回 null */
155
- function readSeriesNumber(data: CellData | undefined): { value: number; t?: CellType } | null {
156
- if (!data) return null
157
- if (data.f != null && data.f !== '') return null
158
- if (typeof data.v !== 'number' || !Number.isFinite(data.v)) return null
159
- if (data.t != null && data.t !== 'n' && data.t !== 'd') return null
160
- return { value: data.v, t: data.t }
161
- }
162
-
163
- function cloneCellData(data: CellData | undefined): CellData | undefined {
164
- if (!data) return undefined
165
- const cloned: CellData = {}
166
- if (data.v !== undefined) cloned.v = data.v
167
- if (data.t !== undefined) cloned.t = data.t
168
- if (data.f !== undefined) cloned.f = data.f
169
- return cloned
170
- }
171
-
172
- function positiveMod(n: number, mod: number): number {
173
- return ((n % mod) + mod) % mod
174
- }
175
-
176
- /**
177
- * 公式引用字符串级位移:尊重 `$` 绝对行列;出界 → `#REF!`。
178
- * 跳过双引号字符串字面量,避免改写文本中的 A1 片段。
179
- */
180
- export function shiftFormulaRefs(formula: string, deltaRow: number, deltaCol: number): string {
181
- if (deltaRow === 0 && deltaCol === 0) return formula
182
-
183
- let result = ''
184
- let i = 0
185
- while (i < formula.length) {
186
- const ch = formula[i]!
187
- if (ch === '"') {
188
- const end = skipExcelString(formula, i)
189
- result += formula.slice(i, end)
190
- i = end
191
- continue
192
- }
193
-
194
- const sheetMatch = matchSheetPrefix(formula, i)
195
- const addrStart = sheetMatch ? sheetMatch.end : i
196
- const addrMatch = matchCellOrRange(formula, addrStart)
197
- if (addrMatch && (sheetMatch || isRefBoundary(formula, i - 1))) {
198
- const sheetPrefix = sheetMatch ? formula.slice(sheetMatch.start, sheetMatch.end) : ''
199
- // 有 sheet 前缀时,前缀前也需是边界
200
- if (sheetMatch && !isRefBoundary(formula, sheetMatch.start - 1)) {
201
- result += ch
202
- i++
203
- continue
204
- }
205
- const shifted = shiftAddrPart(addrMatch.text, deltaRow, deltaCol)
206
- result += sheetPrefix + shifted
207
- i = addrMatch.end
208
- continue
209
- }
210
-
211
- result += ch
212
- i++
213
- }
214
- return result
215
- }
216
-
217
- function skipExcelString(text: string, start: number): number {
218
- // start 指向开引号;Excel 字符串内 "" 为转义
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
-
233
- function matchSheetPrefix(text: string, start: number): { start: number; end: number } | null {
234
- if (text[start] === "'") {
235
- let i = start + 1
236
- while (i < text.length) {
237
- if (text[i] === "'") {
238
- if (text[i + 1] === "'") {
239
- i += 2
240
- continue
241
- }
242
- if (text[i + 1] === '!') {
243
- return { start, end: i + 2 }
244
- }
245
- return null
246
- }
247
- i++
248
- }
249
- return null
250
- }
251
-
252
- // 简单表名:字母/下划线开头
253
- if (!/[A-Za-z_]/.test(text[start] ?? '')) return null
254
- let i = start + 1
255
- while (i < text.length && /[\w.]/.test(text[i]!)) i++
256
- if (text[i] === '!') return { start, end: i + 1 }
257
- return null
258
- }
259
-
260
- const CELL_REF_RE = /^(\$?)([A-Za-z]+)(\$?)([1-9]\d*)/
261
-
262
- function matchCellOrRange(text: string, start: number): { text: string; end: number } | null {
263
- const first = matchOneCell(text, start)
264
- if (!first) return null
265
- if (text[first.end] === ':') {
266
- const second = matchOneCell(text, first.end + 1)
267
- if (!second) return { text: first.text, end: first.end }
268
- return { text: text.slice(start, second.end), end: second.end }
269
- }
270
- return first
271
- }
272
-
273
- function matchOneCell(text: string, start: number): { text: string; end: number } | null {
274
- const slice = text.slice(start)
275
- const m = CELL_REF_RE.exec(slice)
276
- if (!m) return null
277
- const end = start + m[0].length
278
- const next = text[end]
279
- // 行号后紧跟字母 → 不是 A1;紧跟 `(` → 函数名(如 LOG10()
280
- if (next && (/[A-Za-z]/.test(next) || next === '(')) return null
281
- return { text: m[0], end }
282
- }
283
-
284
- function isRefBoundary(text: string, index: number): boolean {
285
- if (index < 0) return true
286
- const ch = text[index]!
287
- // 引用前不能是字母/数字(避免 FOOA1);`!` 已由 sheet 前缀消费
288
- return !/[A-Za-z0-9_$]/.test(ch)
289
- }
290
-
291
- function shiftAddrPart(part: string, deltaRow: number, deltaCol: number): string {
292
- if (part.includes(':')) {
293
- const [a, b] = part.split(':')
294
- const left = shiftOneRef(a!, deltaRow, deltaCol)
295
- const right = shiftOneRef(b!, deltaRow, deltaCol)
296
- if (left === '#REF!' || right === '#REF!') return '#REF!'
297
- return `${left}:${right}`
298
- }
299
- return shiftOneRef(part, deltaRow, deltaCol)
300
- }
301
-
302
- function shiftOneRef(ref: string, deltaRow: number, deltaCol: number): string {
303
- const m = CELL_REF_RE.exec(ref)
304
- if (!m) return ref
305
- const colAbs = m[1] === '$'
306
- const rowAbs = m[3] === '$'
307
- let col = colNameToIndex(m[2]!)
308
- let row = Number.parseInt(m[4]!, 10) - 1
309
- if (col < 0 || row < 0) return '#REF!'
310
- if (!colAbs) col += deltaCol
311
- if (!rowAbs) row += deltaRow
312
- if (col < 0 || row < 0) return '#REF!'
313
- return `${colAbs ? '$' : ''}${colIndexToName(col)}${rowAbs ? '$' : ''}${row + 1}`
314
- }
@@ -1,66 +0,0 @@
1
- import type { CellAddress, CellRange } from '../address'
2
-
3
- /**
4
- * 公式 AST 节点。
5
- * 引用节点携带可选 sheet 名(跨表引用);缺省 = 公式所在 sheet。
6
- */
7
-
8
- export type BinaryOperator =
9
- | '+'
10
- | '-'
11
- | '*'
12
- | '/'
13
- | '^'
14
- | '&'
15
- | '='
16
- | '<>'
17
- | '<'
18
- | '<='
19
- | '>'
20
- | '>='
21
-
22
- export type AstNode =
23
- | { kind: 'number'; value: number }
24
- | { kind: 'string'; value: string }
25
- | { kind: 'boolean'; value: boolean }
26
- /** 未知名称(如裸写的 Sheet2 / 未定义命名);求值为 #NAME? */
27
- | { kind: 'name'; name: string }
28
- | { kind: 'cell'; sheet?: string; addr: CellAddress }
29
- | { kind: 'range'; sheet?: string; range: CellRange }
30
- | { kind: 'unary'; op: '-' | '+'; operand: AstNode }
31
- /** 百分号后缀运算(除以 100) */
32
- | { kind: 'percent'; operand: AstNode }
33
- | { kind: 'binary'; op: BinaryOperator; left: AstNode; right: AstNode }
34
- | { kind: 'call'; name: string; args: AstNode[] }
35
-
36
- /** 公式中的引用(依赖图提取用):sheet 缺省由所属公式节点补齐 */
37
- export interface AstReference {
38
- sheet?: string
39
- range: CellRange
40
- }
41
-
42
- /** 遍历 AST,收集全部引用(单格视为 1×1 区域) */
43
- export function collectReferences(node: AstNode, out: AstReference[] = []): AstReference[] {
44
- switch (node.kind) {
45
- case 'cell':
46
- out.push({ sheet: node.sheet, range: { start: node.addr, end: node.addr } })
47
- break
48
- case 'range':
49
- out.push({ sheet: node.sheet, range: node.range })
50
- break
51
- case 'unary':
52
- case 'percent':
53
- collectReferences(node.operand, out)
54
- break
55
- case 'binary':
56
- collectReferences(node.left, out)
57
- collectReferences(node.right, out)
58
- break
59
- case 'call':
60
- for (const arg of node.args) collectReferences(arg, out)
61
- break
62
- default:
63
- break
64
- }
65
- return out
66
- }