@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 +0,0 @@
1
- {"version":3,"file":"vtable-theme.js","names":[],"sources":["../../src/grid/vtable-theme.ts"],"sourcesContent":["import { themes } from '@visactor/vtable'\n\n/** 行号 / 列头浅底(canvas 主题无法读 CSS 变量,固定色贴近 Excel) */\nconst CHROME_BG = '#F5F5F5'\n/** body 纯白,覆盖 DEFAULT 斑马底色 */\nconst BODY_BG = '#FFF'\n/** 网格线 / 外框:浅灰(勿用 DEFAULTBORDERCOLOR #000) */\nconst GRID_BORDER = '#E1E4E8'\n/** 选区边框:清晰蓝(覆盖 DEFAULT 的 #0000ff) */\nconst SELECTION_BORDER = '#2170E7'\n/** 选区填充 */\nconst SELECTION_BG = 'rgba(33, 112, 231, 0.12)'\n\n/**\n * 单元格内边距 [上, 右, 下, 左]。\n * VTable DEFAULT 为 `[10, 16, 10, 16]`,配合 `defaultRowHeight: 28` 会挤占文字区;\n * 收紧到贴近 Excel 的密度(仍留少量呼吸空间)。\n */\nexport const SHEET_CELL_PADDING = [2, 6, 2, 6] as const\n\n/**\n * Sheet 默认 VTable 主题。\n *\n * 必须基于 `themes.DEFAULT.extends`:传入裸对象时 VTable 不会继承 DEFAULT,\n * 缺省 `borderColor` 会回落到内部 `DEFAULTBORDERCOLOR = '#000'`,出现刺眼黑线。\n */\nexport const sheetVTableTheme = themes.DEFAULT.extends({\n underlayBackgroundColor: BODY_BG,\n defaultStyle: {\n bgColor: CHROME_BG,\n borderColor: GRID_BORDER,\n padding: [...SHEET_CELL_PADDING],\n textOverflow: 'clip'\n },\n headerStyle: { bgColor: CHROME_BG, borderColor: GRID_BORDER, textOverflow: 'clip' },\n cornerHeaderStyle: { bgColor: CHROME_BG, borderColor: GRID_BORDER, textOverflow: 'clip' },\n rowHeaderStyle: { bgColor: CHROME_BG, borderColor: GRID_BORDER, textOverflow: 'clip' },\n bodyStyle: { bgColor: BODY_BG, borderColor: GRID_BORDER, textOverflow: 'clip' },\n frameStyle: {\n borderColor: GRID_BORDER,\n borderLineWidth: 1,\n shadowBlur: 0,\n shadowColor: 'transparent'\n },\n selectionStyle: {\n cellBgColor: SELECTION_BG,\n cellBorderColor: SELECTION_BORDER,\n cellBorderLineWidth: 2\n }\n})\n\n/** 行号列默认样式(与主题 chrome 对齐) */\nexport const sheetRowSeriesNumberStyle = {\n bgColor: CHROME_BG,\n borderColor: GRID_BORDER,\n padding: [...SHEET_CELL_PADDING],\n textOverflow: 'clip' as const\n}\n\n/** 默认行高(比 VTable 默认 40 更接近表格密度;与 SHEET_CELL_PADDING 搭配) */\nexport const SHEET_DEFAULT_ROW_HEIGHT = 28\n"],"mappings":";;;AAGA,MAAM,YAAY;;AAElB,MAAM,UAAU;;AAEhB,MAAM,cAAc;;AAEpB,MAAM,mBAAmB;;AAEzB,MAAM,eAAe;;;;;;AAOrB,MAAa,qBAAqB;CAAC;CAAG;CAAG;CAAG;AAAC;;;;;;;AAQ7C,MAAa,mBAAmB,OAAO,QAAQ,QAAQ;CACrD,yBAAyB;CACzB,cAAc;EACZ,SAAS;EACT,aAAa;EACb,SAAS,CAAC,GAAG,kBAAkB;EAC/B,cAAc;CAChB;CACA,aAAa;EAAE,SAAS;EAAW,aAAa;EAAa,cAAc;CAAO;CAClF,mBAAmB;EAAE,SAAS;EAAW,aAAa;EAAa,cAAc;CAAO;CACxF,gBAAgB;EAAE,SAAS;EAAW,aAAa;EAAa,cAAc;CAAO;CACrF,WAAW;EAAE,SAAS;EAAS,aAAa;EAAa,cAAc;CAAO;CAC9E,YAAY;EACV,aAAa;EACb,iBAAiB;EACjB,YAAY;EACZ,aAAa;CACf;CACA,gBAAgB;EACd,aAAa;EACb,iBAAiB;EACjB,qBAAqB;CACvB;AACF,CAAC;;AAGD,MAAa,4BAA4B;CACvC,SAAS;CACT,aAAa;CACb,SAAS,CAAC,GAAG,kBAAkB;CAC/B,cAAc;AAChB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"builtin.js","names":[],"sources":["../../src/tools/builtin.ts"],"sourcesContent":["import { rangesEqual } from '../core/address'\nimport { registerTool } from './registry'\n\n/**\n * 内置工具(dogfood 扩展机制):undo/redo + 合并/取消合并。\n *\n * 与第三方工具走同一注册通道,可由宿主 unregisterTool 移除或同 id 覆盖。\n * 本模块由 tools/index.ts 引入(与 core/command 的 default-registry 同构):\n * 经包入口导入即完成注册,无头使用 core 深导入不涉及此全局副作用。\n */\n\nregisterTool({\n id: 'undo',\n title: '撤销',\n tooltip: '撤销(Ctrl/Cmd+Z)',\n group: 'history',\n order: 0,\n disabled: (ctx) => !ctx.canUndo,\n onClick: (ctx) => {\n ctx.undo()\n }\n})\n\nregisterTool({\n id: 'redo',\n title: '重做',\n tooltip: '重做(Ctrl/Cmd+Shift+Z 或 Ctrl+Y)',\n group: 'history',\n order: 1,\n disabled: (ctx) => !ctx.canRedo,\n onClick: (ctx) => {\n ctx.redo()\n }\n})\n\nregisterTool({\n id: 'merge',\n title: '合并',\n tooltip: '合并选中区域',\n group: 'cell',\n order: 0,\n disabled: (ctx) => {\n const { activeCell, ranges } = ctx.getSelection()\n const range = ranges[0]\n if (!activeCell || !range) return true\n // 单格无可合并\n if (range.start.row === range.end.row && range.start.col === range.end.col) return true\n // 选区恰好等于既有合并区域 → 无操作必要\n const mergeRange = ctx.getCellInfo(activeCell).mergeRange\n return !!mergeRange && rangesEqual(mergeRange, range)\n },\n onClick: (ctx) => {\n const range = ctx.getSelection().ranges[0]\n if (!range) return\n ctx.mergeCells(range)\n }\n})\n\nregisterTool({\n id: 'unmerge',\n title: '取消合并',\n tooltip: '取消活动格所在合并',\n group: 'cell',\n order: 1,\n disabled: (ctx) => {\n const { activeCell } = ctx.getSelection()\n if (!activeCell) return true\n return !ctx.getCellInfo(activeCell).mergeRange\n },\n onClick: (ctx) => {\n const { activeCell, ranges } = ctx.getSelection()\n if (!activeCell || !ranges[0]) return\n ctx.unmergeCells(ranges[0])\n }\n})\n"],"mappings":";;;;;;;;;;AAWA,aAAa;CACX,IAAI;CACJ,OAAO;CACP,SAAS;CACT,OAAO;CACP,OAAO;CACP,WAAW,QAAQ,CAAC,IAAI;CACxB,UAAU,QAAQ;EAChB,IAAI,KAAK;CACX;AACF,CAAC;AAED,aAAa;CACX,IAAI;CACJ,OAAO;CACP,SAAS;CACT,OAAO;CACP,OAAO;CACP,WAAW,QAAQ,CAAC,IAAI;CACxB,UAAU,QAAQ;EAChB,IAAI,KAAK;CACX;AACF,CAAC;AAED,aAAa;CACX,IAAI;CACJ,OAAO;CACP,SAAS;CACT,OAAO;CACP,OAAO;CACP,WAAW,QAAQ;EACjB,MAAM,EAAE,YAAY,WAAW,IAAI,aAAa;EAChD,MAAM,QAAQ,OAAO;EACrB,IAAI,CAAC,cAAc,CAAC,OAAO,OAAO;EAElC,IAAI,MAAM,MAAM,QAAQ,MAAM,IAAI,OAAO,MAAM,MAAM,QAAQ,MAAM,IAAI,KAAK,OAAO;EAEnF,MAAM,aAAa,IAAI,YAAY,UAAU,CAAC,CAAC;EAC/C,OAAO,CAAC,CAAC,cAAc,YAAY,YAAY,KAAK;CACtD;CACA,UAAU,QAAQ;EAChB,MAAM,QAAQ,IAAI,aAAa,CAAC,CAAC,OAAO;EACxC,IAAI,CAAC,OAAO;EACZ,IAAI,WAAW,KAAK;CACtB;AACF,CAAC;AAED,aAAa;CACX,IAAI;CACJ,OAAO;CACP,SAAS;CACT,OAAO;CACP,OAAO;CACP,WAAW,QAAQ;EACjB,MAAM,EAAE,eAAe,IAAI,aAAa;EACxC,IAAI,CAAC,YAAY,OAAO;EACxB,OAAO,CAAC,IAAI,YAAY,UAAU,CAAC,CAAC;CACtC;CACA,UAAU,QAAQ;EAChB,MAAM,EAAE,YAAY,WAAW,IAAI,aAAa;EAChD,IAAI,CAAC,cAAc,CAAC,OAAO,IAAI;EAC/B,IAAI,aAAa,OAAO,EAAE;CAC5B;AACF,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"context.js","names":[],"sources":["../../src/tools/context.ts"],"sourcesContent":["import type { CellAddress, CellRange } from '../core/address'\nimport type { CellData, CellValue } from '../core/cell-store'\nimport type { HistoryState } from '../core/command/history'\nimport type { SetCellValueItem } from '../core/command/set-cell-value'\nimport type { CellInfo } from '../core/merge-manager'\nimport type { SelectionState } from '../core/selection'\nimport type { Sheet } from '../core/sheet'\n\n/**\n * SheetContext:工具的唯一操作门面。\n *\n * 工具(内置与第三方)对表格的全部操作都经由此门面:\n * - 读方法直达当前活动 sheet(语义与 Sheet 同名方法一致)\n * - 写方法全部走命令系统,天然进入 undo 历史——扩展无法绕过命令系统\n * - 不暴露 Sheet 实例本身,保证门面就是扩展能力的全部边界\n * - USheet 切换 sheet tab 后,同一上下文自动指向新的活动 sheet\n */\nexport interface SheetContext {\n /** 当前活动 sheet 名 */\n readonly sheetName: string\n\n // ─── 选区 ────────────────────────────────────────────────\n getSelection(): SelectionState\n /** 选中单格(被覆盖格自动定位锚点) */\n selectCell(addr: CellAddress): void\n selectRange(range: CellRange): void\n\n // ─── 读取 ────────────────────────────────────────────────\n /** 原始存储语义读取(被合并覆盖格 → undefined) */\n getCellData(addr: CellAddress): CellData | undefined\n /** 锚点解析语义读取(被覆盖格 → 锚点的值) */\n getDisplayValue(addr: CellAddress): CellValue | undefined\n /** 合并语义下的单元格信息(普通格 / 合并锚点 / 被覆盖格) */\n getCellInfo(addr: CellAddress): CellInfo\n\n // ─── 写入(全部经命令系统,可 undo) ───────────────────────\n setCellValue(addr: CellAddress, value: CellValue): void\n setCellFormula(addr: CellAddress, formula: string): void\n /** 批量写入(一次调用 = 一个 undo 单元) */\n setCells(items: SetCellValueItem[]): void\n mergeCells(range: CellRange): CellRange\n unmergeCells(range: CellRange): void\n /** 执行命令注册表中的自定义命令(高级扩展点) */\n executeCommand<R = void>(commandId: string, params: unknown): R | undefined\n\n // ─── 事务(事务内多次写入合并为一个 undo 单元) ─────────────\n beginTransaction(): void\n commit(): void\n rollback(): void\n\n // ─── 历史 ────────────────────────────────────────────────\n undo(): boolean\n redo(): boolean\n readonly canUndo: boolean\n readonly canRedo: boolean\n\n // ─── 事件订阅(订阅时绑定到当前活动 sheet;tab 切换后需重新订阅) ──\n onSelectionChange(handler: (state: SelectionState) => void): () => void\n onHistoryChange(handler: (state: HistoryState) => void): () => void\n}\n\n/**\n * 创建工具上下文。\n * @param resolveSheet 活动 sheet 解析器(USheet 传 `() => activeSheet`,\n * tab 切换后上下文自动指向当前 sheet);也可直接传 Sheet 实例(无头 / 测试场景)\n */\nexport function createSheetContext(resolveSheet: Sheet | (() => Sheet)): SheetContext {\n const sheet = typeof resolveSheet === 'function' ? resolveSheet : () => resolveSheet\n return {\n get sheetName() {\n return sheet().name\n },\n\n getSelection: () => sheet().getSelection(),\n selectCell: (addr) => sheet().selectCell(addr),\n selectRange: (range) => sheet().selectRange(range),\n\n getCellData: (addr) => sheet().getCellData(addr),\n getDisplayValue: (addr) => sheet().getDisplayValue(addr),\n getCellInfo: (addr) => sheet().getCellInfo(addr),\n\n setCellValue: (addr, value) => sheet().setCellValue(addr, value),\n setCellFormula: (addr, formula) => sheet().setCellFormula(addr, formula),\n setCells: (items) => sheet().setCells(items),\n mergeCells: (range) => sheet().mergeCells(range),\n unmergeCells: (range) => sheet().unmergeCells(range),\n executeCommand: <R = void>(commandId: string, params: unknown): R | undefined =>\n sheet().executeCommand<R>(commandId, params),\n\n beginTransaction: () => sheet().beginTransaction(),\n commit: () => sheet().commit(),\n rollback: () => sheet().rollback(),\n\n undo: () => sheet().undo(),\n redo: () => sheet().redo(),\n get canUndo() {\n return sheet().canUndo\n },\n get canRedo() {\n return sheet().canRedo\n },\n\n onSelectionChange: (handler) => sheet().on('selection-change', handler),\n onHistoryChange: (handler) => sheet().on('history-change', handler)\n }\n}\n"],"mappings":";;;;;;AAkEA,SAAgB,mBAAmB,cAAmD;CACpF,MAAM,QAAQ,OAAO,iBAAiB,aAAa,qBAAqB;CACxE,OAAO;EACL,IAAI,YAAY;GACd,OAAO,MAAM,CAAC,CAAC;EACjB;EAEA,oBAAoB,MAAM,CAAC,CAAC,aAAa;EACzC,aAAa,SAAS,MAAM,CAAC,CAAC,WAAW,IAAI;EAC7C,cAAc,UAAU,MAAM,CAAC,CAAC,YAAY,KAAK;EAEjD,cAAc,SAAS,MAAM,CAAC,CAAC,YAAY,IAAI;EAC/C,kBAAkB,SAAS,MAAM,CAAC,CAAC,gBAAgB,IAAI;EACvD,cAAc,SAAS,MAAM,CAAC,CAAC,YAAY,IAAI;EAE/C,eAAe,MAAM,UAAU,MAAM,CAAC,CAAC,aAAa,MAAM,KAAK;EAC/D,iBAAiB,MAAM,YAAY,MAAM,CAAC,CAAC,eAAe,MAAM,OAAO;EACvE,WAAW,UAAU,MAAM,CAAC,CAAC,SAAS,KAAK;EAC3C,aAAa,UAAU,MAAM,CAAC,CAAC,WAAW,KAAK;EAC/C,eAAe,UAAU,MAAM,CAAC,CAAC,aAAa,KAAK;EACnD,iBAA2B,WAAmB,WAC5C,MAAM,CAAC,CAAC,eAAkB,WAAW,MAAM;EAE7C,wBAAwB,MAAM,CAAC,CAAC,iBAAiB;EACjD,cAAc,MAAM,CAAC,CAAC,OAAO;EAC7B,gBAAgB,MAAM,CAAC,CAAC,SAAS;EAEjC,YAAY,MAAM,CAAC,CAAC,KAAK;EACzB,YAAY,MAAM,CAAC,CAAC,KAAK;EACzB,IAAI,UAAU;GACZ,OAAO,MAAM,CAAC,CAAC;EACjB;EACA,IAAI,UAAU;GACZ,OAAO,MAAM,CAAC,CAAC;EACjB;EAEA,oBAAoB,YAAY,MAAM,CAAC,CAAC,GAAG,oBAAoB,OAAO;EACtE,kBAAkB,YAAY,MAAM,CAAC,CAAC,GAAG,kBAAkB,OAAO;CACpE;AACF"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"registry.js","names":[],"sources":["../../src/tools/registry.ts"],"sourcesContent":["import { TypedEventEmitter } from '../core/events'\nimport type { SheetContext } from './context'\n\n/**\n * 工具注册表:USheet 工具栏的扩展点。\n *\n * - 工具是框架无关的纯定义(icon 由 vue 层渲染,故此处类型为 unknown)\n * - 分组渲染:同组工具连续排列,组间渲染分隔符;\n * 组序 = 各组最早注册位置,组内按 (order, 注册序) 升序\n * - 同 id 重复注册 = 替换定义并保留原注册位置(HMR / 覆盖内置工具友好)\n * - 默认注册表全局共享:所有 USheet 实例渲染同一组工具\n * (与 defaultCommandRegistry 同语义;每个实例的 SheetContext 各自绑定当前 sheet)\n */\n\n/** 工具定义 */\nexport interface SheetTool {\n /** 唯一 id;同 id 重复注册视为替换(保留原位置) */\n id: string\n /** 按钮文本 */\n title: string\n /** 图标(Vue 组件,由 USheet 渲染;tools 层不依赖 vue 故为 unknown) */\n icon?: unknown\n /** 悬浮提示;缺省用 title */\n tooltip?: string\n /** 分组名(组间渲染分隔符);缺省 'default' */\n group?: string\n /** 组内排序(升序,相同 order 按注册先后);缺省 0 */\n order?: number\n /** 是否可见(工具栏状态刷新时求值);缺省可见 */\n visible?(ctx: SheetContext): boolean\n /** 是否禁用(工具栏状态刷新时求值);缺省可用 */\n disabled?(ctx: SheetContext): boolean\n /** 点击执行;ctx 是唯一操作入口(写操作全走命令系统,天然可撤销) */\n onClick(ctx: SheetContext): void\n}\n\n/** 分组视图(供工具栏渲染) */\nexport interface SheetToolGroup {\n name: string\n tools: SheetTool[]\n}\n\n/** 缺省分组名 */\nexport const DEFAULT_TOOL_GROUP = 'default'\n\ntype ToolEntry = { tool: SheetTool; seq: number }\n\ntype ToolRegistryEvents = { change: null }\n\nexport class ToolRegistry {\n private entries = new Map<string, ToolEntry>()\n private seq = 0\n private emitter = new TypedEventEmitter<ToolRegistryEvents>()\n\n /** 注册工具;同 id 重复注册 = 替换定义并保留原注册位置 */\n register(tool: SheetTool): void {\n if (!tool.id) throw new Error('工具注册失败:id 不能为空')\n if (!tool.title) throw new Error(`工具注册失败:${tool.id} 缺少 title`)\n if (typeof tool.onClick !== 'function') {\n throw new Error(`工具注册失败:${tool.id} 缺少 onClick`)\n }\n const prev = this.entries.get(tool.id)\n this.entries.set(tool.id, { tool, seq: prev?.seq ?? this.seq++ })\n this.emitter.emit('change', null)\n }\n\n /** 注销工具;id 不存在时返回 false(不触发 change) */\n unregister(id: string): boolean {\n const existed = this.entries.delete(id)\n if (existed) this.emitter.emit('change', null)\n return existed\n }\n\n get(id: string): SheetTool | undefined {\n return this.entries.get(id)?.tool\n }\n\n has(id: string): boolean {\n return this.entries.has(id)\n }\n\n get size(): number {\n return this.entries.size\n }\n\n /** 分组视图:组序 = 各组最早注册位置;组内按 (order, 注册序) 升序 */\n getGroups(): SheetToolGroup[] {\n const groups = new Map<string, { entries: ToolEntry[]; firstSeq: number }>()\n for (const entry of this.entries.values()) {\n const name = entry.tool.group ?? DEFAULT_TOOL_GROUP\n let group = groups.get(name)\n if (!group) {\n group = { entries: [], firstSeq: entry.seq }\n groups.set(name, group)\n }\n group.entries.push(entry)\n }\n return [...groups.entries()]\n .sort((a, b) => a[1].firstSeq - b[1].firstSeq)\n .map(([name, group]) => ({\n name,\n tools: group.entries\n .slice()\n .sort((a, b) => (a.tool.order ?? 0) - (b.tool.order ?? 0) || a.seq - b.seq)\n .map((entry) => entry.tool)\n }))\n }\n\n /** 订阅注册表变化(register / unregister),返回取消订阅函数 */\n onChange(handler: () => void): () => void {\n return this.emitter.on('change', handler)\n }\n}\n\n/**\n * 全局默认注册表(USheet 默认渲染这里的工具;内置工具也注册于此)。\n * 工具是应用级扩展:注册一次,所有 USheet 实例共享。\n */\nexport const defaultToolRegistry = new ToolRegistry()\n\n/** 注册工具到默认注册表 */\nexport function registerTool(tool: SheetTool): void {\n defaultToolRegistry.register(tool)\n}\n\n/** 从默认注册表注销工具;id 不存在时返回 false */\nexport function unregisterTool(id: string): boolean {\n return defaultToolRegistry.unregister(id)\n}\n"],"mappings":";;;AA2CA,MAAa,qBAAqB;AAMlC,IAAa,eAAb,MAA0B;CACxB,0BAAkB,IAAI,IAAuB;CAC7C,MAAc;CACd,UAAkB,IAAI,kBAAsC;;CAG5D,SAAS,MAAuB;EAC9B,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,MAAM,gBAAgB;EAC9C,IAAI,CAAC,KAAK,OAAO,MAAM,IAAI,MAAM,UAAU,KAAK,GAAG,UAAU;EAC7D,IAAI,OAAO,KAAK,YAAY,YAC1B,MAAM,IAAI,MAAM,UAAU,KAAK,GAAG,YAAY;EAEhD,MAAM,OAAO,KAAK,QAAQ,IAAI,KAAK,EAAE;EACrC,KAAK,QAAQ,IAAI,KAAK,IAAI;GAAE;GAAM,KAAK,MAAM,OAAO,KAAK;EAAM,CAAC;EAChE,KAAK,QAAQ,KAAK,UAAU,IAAI;CAClC;;CAGA,WAAW,IAAqB;EAC9B,MAAM,UAAU,KAAK,QAAQ,OAAO,EAAE;EACtC,IAAI,SAAS,KAAK,QAAQ,KAAK,UAAU,IAAI;EAC7C,OAAO;CACT;CAEA,IAAI,IAAmC;EACrC,OAAO,KAAK,QAAQ,IAAI,EAAE,CAAC,EAAE;CAC/B;CAEA,IAAI,IAAqB;EACvB,OAAO,KAAK,QAAQ,IAAI,EAAE;CAC5B;CAEA,IAAI,OAAe;EACjB,OAAO,KAAK,QAAQ;CACtB;;CAGA,YAA8B;EAC5B,MAAM,yBAAS,IAAI,IAAwD;EAC3E,KAAK,MAAM,SAAS,KAAK,QAAQ,OAAO,GAAG;GACzC,MAAM,OAAO,MAAM,KAAK,SAAA;GACxB,IAAI,QAAQ,OAAO,IAAI,IAAI;GAC3B,IAAI,CAAC,OAAO;IACV,QAAQ;KAAE,SAAS,CAAC;KAAG,UAAU,MAAM;IAAI;IAC3C,OAAO,IAAI,MAAM,KAAK;GACxB;GACA,MAAM,QAAQ,KAAK,KAAK;EAC1B;EACA,OAAO,CAAC,GAAG,OAAO,QAAQ,CAAC,CAAC,CACzB,MAAM,GAAG,MAAM,EAAE,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,QAAQ,CAAC,CAC7C,KAAK,CAAC,MAAM,YAAY;GACvB;GACA,OAAO,MAAM,QACV,MAAM,CAAC,CACP,MAAM,GAAG,OAAO,EAAE,KAAK,SAAS,MAAM,EAAE,KAAK,SAAS,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,CAC1E,KAAK,UAAU,MAAM,IAAI;EAC9B,EAAE;CACN;;CAGA,SAAS,SAAiC;EACxC,OAAO,KAAK,QAAQ,GAAG,UAAU,OAAO;CAC1C;AACF;;;;;AAMA,MAAa,sBAAsB,IAAI,aAAa;;AAGpD,SAAgB,aAAa,MAAuB;CAClD,oBAAoB,SAAS,IAAI;AACnC;;AAGA,SAAgB,eAAe,IAAqB;CAClD,OAAO,oBAAoB,WAAW,EAAE;AAC1C"}
@@ -1,2 +0,0 @@
1
- import _default from "./sheet.vue.js";
2
- export { _default as USheet };
@@ -1 +0,0 @@
1
- {"version":3,"file":"sheet.js","names":[],"sources":["../../src/vue/sheet.vue"],"sourcesContent":["<template>\n <div :class=\"cls.b\">\n <div v-if=\"showToolbar\" :class=\"cls.e('toolbar')\">\n <template v-for=\"(group, groupIndex) in toolGroups\" :key=\"group.name\">\n <span v-if=\"groupIndex > 0\" :class=\"cls.e('toolbar-divider')\" />\n <button\n v-for=\"item in group.tools\"\n :key=\"item.tool.id\"\n type=\"button\"\n :class=\"cls.e('tool')\"\n :disabled=\"item.disabled\"\n :title=\"item.tool.tooltip ?? item.tool.title\"\n @click=\"handleToolClick(item.tool)\"\n >\n <component :is=\"item.tool.icon\" v-if=\"item.tool.icon\" :class=\"cls.e('tool-icon')\" />\n <span>{{ item.tool.title }}</span>\n </button>\n </template>\n </div>\n\n <div ref=\"gridRef\" :class=\"cls.e('grid')\" />\n\n <div v-if=\"showTabs\" :class=\"cls.e('tabs')\">\n <button\n v-for=\"(sheet, index) in sheetList\"\n :key=\"sheet.name\"\n type=\"button\"\n :class=\"[cls.e('tab'), bem.is('active', index === activeIndex)]\"\n @click=\"handleTabClick(index)\"\n >\n {{ sheet.name }}\n </button>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { contextmenu } from '@veltra/desktop'\nimport { bem } from '@veltra/utils'\nimport { computed, onBeforeUnmount, onMounted, ref, shallowRef, useTemplateRef, watch } from 'vue'\n\nimport { rangeContainsAddress } from '../core/address'\nimport type { Sheet } from '../core/sheet'\nimport { Workbook } from '../core/workbook'\nimport { SheetGrid, type SheetGridContextMenuInfo } from '../grid/sheet-grid'\nimport { createSheetContext } from '../tools/context'\nimport { defaultToolRegistry, type SheetTool } from '../tools/registry'\nimport type { SheetEmits, SheetProps, _SheetExposed } from '../types'\n\ndefineOptions({ name: 'USheet' })\n\nconst props = withDefaults(defineProps<SheetProps>(), {\n rows: 100,\n cols: 26,\n showToolbar: true,\n showTabs: true\n})\n\nconst emit = defineEmits<SheetEmits>()\n\nconst cls = bem('sheet')\n\n/** 工作簿:外部传入或内部自建(单 sheet) */\nconst internalWorkbook = new Workbook()\nconst workbook = computed(() => props.workbook ?? internalWorkbook)\n\n/** 激活 sheet 索引(镜像 workbook.activeSheetIndex,经事件同步) */\nconst activeIndex = ref(workbook.value.activeSheetIndex)\n/** sheet 列表(tabs 渲染;由 sheets-change 同步) */\nconst sheetList = shallowRef<Sheet[]>(workbook.value.getSheets())\nconst activeSheet = computed(() => sheetList.value[activeIndex.value] ?? workbook.value.activeSheet)\n\n/** 工具上下文:动态解析活动 sheet,tab 切换后自动指向当前 sheet */\nconst context = createSheetContext(() => activeSheet.value)\n\nconst gridRef = useTemplateRef<HTMLElement>('gridRef')\nlet grid: SheetGrid | undefined\n\n// ─── 工具栏状态刷新 ─────────────────────────────────────────\n// 工具 visible/disabled 是 (ctx) => boolean 纯函数,状态源变化时 bump 触发重算\n\nconst stateTick = ref(0)\nconst bump = (): void => {\n stateTick.value++\n}\n\nlet disposeSheetEvents: (() => void)[] = []\n/** 订阅活动 sheet 的状态源(tab 切换 / 工作簿变更时重绑) */\nfunction bindSheetEvents(sheet: Sheet): void {\n for (const dispose of disposeSheetEvents) dispose()\n disposeSheetEvents = [\n sheet.on('selection-change', bump),\n sheet.on('history-change', bump),\n sheet.on('cell-change', bump),\n sheet.on('merge-change', bump)\n ]\n}\n\nlet disposeWorkbookEvents: (() => void)[] = []\nfunction bindWorkbookEvents(wb: Workbook): void {\n for (const dispose of disposeWorkbookEvents) dispose()\n disposeWorkbookEvents = [\n wb.on('sheets-change', ({ sheets }) => {\n sheetList.value = sheets\n bump()\n }),\n wb.on('active-sheet-change', ({ sheet, index }) => {\n activeIndex.value = index\n bindSheetEvents(sheet)\n rebuildGrid()\n bump()\n emit('active-sheet-change', { sheet, index })\n })\n ]\n}\n\nconst offRegistryChange = defaultToolRegistry.onChange(bump)\n\n// ─── 工具栏 ────────────────────────────────────────────────\n\nconst toolGroups = computed(() => {\n void stateTick.value // 依赖状态源:选区 / 历史 / 单元格 / 合并 / 注册表\n return defaultToolRegistry\n .getGroups()\n .map((group) => ({\n name: group.name,\n tools: group.tools\n .map((tool) => ({\n tool,\n visible: tool.visible?.(context) ?? true,\n disabled: tool.disabled?.(context) ?? false\n }))\n .filter((item) => item.visible)\n }))\n .filter((group) => group.tools.length > 0)\n})\n\nfunction handleToolClick(tool: SheetTool): void {\n if (tool.disabled?.(context)) return\n tool.onClick(context)\n}\n\n// ─── sheet tabs ─────────────────────────────────────────────\n\nfunction handleTabClick(index: number): void {\n if (index === activeIndex.value) return\n const sheet = sheetList.value[index]\n if (sheet) workbook.value.activateSheet(sheet.name)\n}\n\n// ─── 右键菜单(合并 / 取消合并,语义对齐内置工具)────────────\n\nfunction handleContextMenu(info: SheetGridContextMenuInfo): void {\n // body 格:若点击落在当前选区外则先选中该格;行号/列头保留当前选区\n if (info.addr) {\n const inSelection = context\n .getSelection()\n .ranges.some((range) => rangeContainsAddress(range, info.addr!))\n if (!inSelection) context.selectCell(info.addr)\n }\n\n const mergeTool = defaultToolRegistry.get('merge')\n const unmergeTool = defaultToolRegistry.get('unmerge')\n\n contextmenu.pop({\n mousePosition: { x: info.x, y: info.y },\n width: 180,\n menus: [\n {\n label: '合并单元格',\n disabled: mergeTool?.disabled?.(context) ?? true,\n callback: () => mergeTool?.onClick(context)\n },\n {\n label: '取消合并单元格',\n disabled: unmergeTool?.disabled?.(context) ?? true,\n callback: () => unmergeTool?.onClick(context)\n }\n ]\n })\n}\n\n// ─── grid 生命周期 ──────────────────────────────────────────\n\nfunction rebuildGrid(): void {\n const container = gridRef.value\n if (!container) return\n grid?.release()\n grid = new SheetGrid({\n container,\n sheet: activeSheet.value,\n rows: props.rows,\n cols: props.cols,\n onContextMenu: handleContextMenu\n })\n}\n\nonMounted(() => {\n bindWorkbookEvents(workbook.value)\n bindSheetEvents(activeSheet.value)\n rebuildGrid()\n})\n\nwatch(() => [props.rows, props.cols], rebuildGrid)\n\nwatch(\n () => props.workbook,\n (wb, prev) => {\n if (wb === prev) return\n activeIndex.value = workbook.value.activeSheetIndex\n sheetList.value = workbook.value.getSheets()\n bindWorkbookEvents(workbook.value)\n bindSheetEvents(activeSheet.value)\n rebuildGrid()\n bump()\n }\n)\n\nonBeforeUnmount(() => {\n offRegistryChange()\n for (const dispose of disposeWorkbookEvents) dispose()\n for (const dispose of disposeSheetEvents) dispose()\n grid?.release()\n grid = undefined\n})\n\nconst exposed: _SheetExposed = {\n workbook,\n getActiveSheet: () => activeSheet.value,\n getContext: () => context,\n getGrid: () => grid\n}\n\ndefineExpose(exposed)\n</script>\n"],"mappings":"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"}
@@ -1,24 +0,0 @@
1
- import { Sheet } from "../core/sheet.js";
2
- import { SheetProps, _SheetExposed } from "../types/sheet.js";
3
- import "../types/index.js";
4
- //#region src/vue/sheet.vue.d.ts
5
- declare const __VLS_export: import("vue").DefineComponent<SheetProps, _SheetExposed, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
6
- "active-sheet-change": (payload: {
7
- sheet: Sheet;
8
- index: number;
9
- }) => any;
10
- }, string, import("vue").PublicProps, Readonly<SheetProps> & Readonly<{
11
- "onActive-sheet-change"?: ((payload: {
12
- sheet: Sheet;
13
- index: number;
14
- }) => any) | undefined;
15
- }>, {
16
- rows: number;
17
- cols: number;
18
- showToolbar: boolean;
19
- showTabs: boolean;
20
- }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
- declare const _default: typeof __VLS_export;
22
- //#endregion
23
- export { _default as default };
24
- //# sourceMappingURL=sheet.vue.d.ts.map
@@ -1,113 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import {
4
- boundingBox,
5
- colIndexToName,
6
- colNameToIndex,
7
- formatAddress,
8
- formatRange,
9
- iterateRange,
10
- parseAddress,
11
- parseRange,
12
- rangeContainsAddress,
13
- rangeContainsRange,
14
- rangesIntersect
15
- } from '../address'
16
-
17
- describe('colIndexToName / colNameToIndex', () => {
18
- it('单列名', () => {
19
- expect(colIndexToName(0)).toBe('A')
20
- expect(colIndexToName(25)).toBe('Z')
21
- })
22
-
23
- it('多列名', () => {
24
- expect(colIndexToName(26)).toBe('AA')
25
- expect(colIndexToName(27)).toBe('AB')
26
- expect(colIndexToName(701)).toBe('ZZ')
27
- expect(colIndexToName(702)).toBe('AAA')
28
- })
29
-
30
- it('列名转列号', () => {
31
- expect(colNameToIndex('A')).toBe(0)
32
- expect(colNameToIndex('Z')).toBe(25)
33
- expect(colNameToIndex('AA')).toBe(26)
34
- expect(colNameToIndex('aA')).toBe(26)
35
- expect(colNameToIndex('1')).toBe(-1)
36
- })
37
- })
38
-
39
- describe('parseAddress / formatAddress', () => {
40
- it('双向转换', () => {
41
- const cases: [string, number, number][] = [
42
- ['A1', 0, 0],
43
- ['Z99', 98, 25],
44
- ['AA10', 9, 26]
45
- ]
46
- for (const [text, row, col] of cases) {
47
- expect(parseAddress(text)).toEqual({ row, col })
48
- expect(formatAddress({ row, col })).toBe(text)
49
- }
50
- })
51
-
52
- it('兼容绝对引用', () => {
53
- expect(parseAddress('$B$2')).toEqual({ row: 1, col: 1 })
54
- })
55
-
56
- it('非法输入返回 null', () => {
57
- expect(parseAddress('A0')).toBeNull()
58
- expect(parseAddress('1A')).toBeNull()
59
- expect(parseAddress('')).toBeNull()
60
- })
61
- })
62
-
63
- describe('parseRange / formatRange', () => {
64
- it('解析并规范化角点顺序', () => {
65
- expect(parseRange('D5:B2')).toEqual({ start: { row: 1, col: 1 }, end: { row: 4, col: 3 } })
66
- })
67
-
68
- it('单格区域', () => {
69
- const range = parseRange('B2')
70
- expect(range).toEqual({ start: { row: 1, col: 1 }, end: { row: 1, col: 1 } })
71
- expect(formatRange(range!)).toBe('B2')
72
- })
73
-
74
- it('多格区域格式化', () => {
75
- expect(formatRange(parseRange('B2:D5')!)).toBe('B2:D5')
76
- })
77
-
78
- it('非法输入返回 null', () => {
79
- expect(parseRange('A1:B2:C3')).toBeNull()
80
- expect(parseRange('A1:')).toBeNull()
81
- })
82
- })
83
-
84
- describe('range 运算', () => {
85
- const b2c3 = parseRange('B2:C3')!
86
-
87
- it('相交判断', () => {
88
- expect(rangesIntersect(b2c3, parseRange('C3:D4')!)).toBe(true)
89
- expect(rangesIntersect(b2c3, parseRange('A1:B1')!)).toBe(false)
90
- // 相邻但不重叠 → 不相交
91
- expect(rangesIntersect(b2c3, parseRange('D2:D3')!)).toBe(false)
92
- expect(rangesIntersect(b2c3, parseRange('B4:C4')!)).toBe(false)
93
- })
94
-
95
- it('包含判断', () => {
96
- expect(rangeContainsAddress(b2c3, { row: 2, col: 2 })).toBe(true)
97
- expect(rangeContainsAddress(b2c3, { row: 3, col: 2 })).toBe(false)
98
- expect(rangeContainsRange(b2c3, parseRange('B2:B2')!)).toBe(true)
99
- expect(rangeContainsRange(b2c3, parseRange('B2:D3')!)).toBe(false)
100
- })
101
-
102
- it('包围盒:跨区域合并场景', () => {
103
- // 已有 B2:C3,再对 C3:D4 合并 → 包围盒 B2:D4
104
- const box = boundingBox([parseRange('C3:D4')!, b2c3])
105
- expect(box).toEqual({ start: { row: 1, col: 1 }, end: { row: 3, col: 3 } })
106
- expect(formatRange(box)).toBe('B2:D4')
107
- })
108
-
109
- it('行主序遍历', () => {
110
- const addrs = [...iterateRange(b2c3)].map(formatAddress)
111
- expect(addrs).toEqual(['B2', 'C2', 'B3', 'C3'])
112
- })
113
- })
@@ -1,97 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { CellStore, inferCellType, isEmptyCellData } from '../cell-store'
4
-
5
- describe('CellData 类型判别', () => {
6
- it('按值推断类型', () => {
7
- expect(inferCellType(42)).toBe('n')
8
- expect(inferCellType('hello')).toBe('s')
9
- expect(inferCellType(true)).toBe('b')
10
- expect(inferCellType(null)).toBeUndefined()
11
- })
12
-
13
- it('写入读出保持类型', () => {
14
- const store = new CellStore()
15
- store.setCellValue({ row: 0, col: 0 }, 42)
16
- store.setCellValue({ row: 0, col: 1 }, 'hello')
17
- store.setCellValue({ row: 0, col: 2 }, true)
18
-
19
- expect(store.getCell({ row: 0, col: 0 })).toEqual({ v: 42, t: 'n' })
20
- expect(store.getCell({ row: 0, col: 1 })).toEqual({ v: 'hello', t: 's' })
21
- expect(store.getCell({ row: 0, col: 2 })).toEqual({ v: true, t: 'b' })
22
- })
23
-
24
- it('空数据判定', () => {
25
- expect(isEmptyCellData(undefined)).toBe(true)
26
- expect(isEmptyCellData({})).toBe(true)
27
- expect(isEmptyCellData({ v: null })).toBe(true)
28
- expect(isEmptyCellData({ v: '' })).toBe(true)
29
- expect(isEmptyCellData({ v: 0 })).toBe(false)
30
- expect(isEmptyCellData({ f: 'SUM(A1:B2)' })).toBe(false)
31
- })
32
- })
33
-
34
- describe('稀疏性', () => {
35
- it('空值写入即删除', () => {
36
- const store = new CellStore()
37
- const addr = { row: 0, col: 0 }
38
- store.setCellValue(addr, 1)
39
- expect(store.size).toBe(1)
40
-
41
- store.setCellValue(addr, null)
42
- expect(store.size).toBe(0)
43
- expect(store.getCell(addr)).toBeUndefined()
44
-
45
- store.setCellValue(addr, 'x')
46
- store.setCellValue(addr, '')
47
- expect(store.size).toBe(0)
48
-
49
- store.setCell(addr, { v: 1 })
50
- store.setCell(addr, {})
51
- expect(store.size).toBe(0)
52
- })
53
-
54
- it('store 大小不随行列数增长', () => {
55
- const store = new CellStore()
56
- store.setCellValue({ row: 99_999, col: 100 }, 'sparse')
57
- expect(store.rowCount).toBe(100_000)
58
- expect(store.colCount).toBe(101)
59
- expect(store.size).toBe(1)
60
- })
61
-
62
- it('10⁵ 行高水位 + 200 个真实格:迭代与序列化只含 200 格', () => {
63
- const store = new CellStore()
64
- store.setCellValue({ row: 99_999, col: 0 }, 'tail')
65
- for (let i = 0; i < 199; i++) {
66
- store.setCellValue({ row: i, col: 0 }, i)
67
- }
68
-
69
- expect(store.rowCount).toBe(100_000)
70
- expect([...store.entries()]).toHaveLength(200)
71
- expect(store.snapshot()).toHaveLength(200)
72
- })
73
- })
74
-
75
- describe('snapshot / restore', () => {
76
- it('往返一致并重算高水位', () => {
77
- const store = new CellStore()
78
- store.setCellValue({ row: 0, col: 0 }, 1)
79
- store.setCell({ row: 2, col: 3 }, { v: 'x', f: 'A1' })
80
-
81
- const restored = new CellStore()
82
- restored.restore(store.snapshot())
83
-
84
- expect(restored.getCell({ row: 0, col: 0 })).toEqual({ v: 1, t: 'n' })
85
- expect(restored.getCell({ row: 2, col: 3 })).toEqual({ v: 'x', f: 'A1' })
86
- expect(restored.rowCount).toBe(3)
87
- expect(restored.colCount).toBe(4)
88
- })
89
-
90
- it('读取返回副本,外部修改不影响存储', () => {
91
- const store = new CellStore()
92
- store.setCell({ row: 0, col: 0 }, { v: 1 })
93
- const data = store.getCell({ row: 0, col: 0 })!
94
- data.v = 999
95
- expect(store.getCell({ row: 0, col: 0 })!.v).toBe(1)
96
- })
97
- })