@veltra/sheet 2.0.0 → 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 (203) hide show
  1. package/dist/index.d.ts +4 -25
  2. package/dist/index.js +3 -22
  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 -138
  33. package/dist/vue/style.d.ts +11 -1
  34. package/dist/vue/style.js +11 -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 -6
  46. package/src/env.d.ts +14 -0
  47. package/src/index.ts +203 -13
  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 +163 -148
  86. package/src/vue/style.scss +646 -3
  87. package/src/vue/style.ts +12 -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/formula/ast.d.ts +0 -58
  125. package/dist/core/formula/ast.js +0 -38
  126. package/dist/core/formula/ast.js.map +0 -1
  127. package/dist/core/formula/dependency-graph.d.ts +0 -70
  128. package/dist/core/formula/dependency-graph.js +0 -269
  129. package/dist/core/formula/dependency-graph.js.map +0 -1
  130. package/dist/core/formula/errors.d.ts +0 -25
  131. package/dist/core/formula/errors.js +0 -36
  132. package/dist/core/formula/errors.js.map +0 -1
  133. package/dist/core/formula/evaluator.d.ts +0 -41
  134. package/dist/core/formula/evaluator.js +0 -154
  135. package/dist/core/formula/evaluator.js.map +0 -1
  136. package/dist/core/formula/functions.d.ts +0 -30
  137. package/dist/core/formula/functions.js +0 -209
  138. package/dist/core/formula/functions.js.map +0 -1
  139. package/dist/core/formula/parser.d.ts +0 -7
  140. package/dist/core/formula/parser.js +0 -208
  141. package/dist/core/formula/parser.js.map +0 -1
  142. package/dist/core/formula/tokenizer.d.ts +0 -37
  143. package/dist/core/formula/tokenizer.js +0 -146
  144. package/dist/core/formula/tokenizer.js.map +0 -1
  145. package/dist/core/merge-manager.d.ts +0 -64
  146. package/dist/core/merge-manager.js +0 -111
  147. package/dist/core/merge-manager.js.map +0 -1
  148. package/dist/core/selection.d.ts +0 -31
  149. package/dist/core/selection.js +0 -62
  150. package/dist/core/selection.js.map +0 -1
  151. package/dist/core/sheet.d.ts +0 -105
  152. package/dist/core/sheet.js +0 -202
  153. package/dist/core/sheet.js.map +0 -1
  154. package/dist/core/workbook.d.ts +0 -43
  155. package/dist/core/workbook.js +0 -77
  156. package/dist/core/workbook.js.map +0 -1
  157. package/dist/grid/sheet-grid.d.ts +0 -59
  158. package/dist/grid/sheet-grid.js +0 -230
  159. package/dist/grid/sheet-grid.js.map +0 -1
  160. package/dist/tools/builtin.js.map +0 -1
  161. package/dist/tools/context.js.map +0 -1
  162. package/dist/tools/registry.js.map +0 -1
  163. package/dist/vue/index.d.ts +0 -2
  164. package/dist/vue/sheet.js.map +0 -1
  165. package/dist/vue/sheet.vue.d.ts +0 -24
  166. package/src/core/__test__/address.test.ts +0 -113
  167. package/src/core/__test__/cell-store.test.ts +0 -97
  168. package/src/core/__test__/command.test.ts +0 -377
  169. package/src/core/__test__/formula-adversarial.test.ts +0 -265
  170. package/src/core/__test__/formula-engine.test.ts +0 -470
  171. package/src/core/__test__/formula-functions.test.ts +0 -178
  172. package/src/core/__test__/formula-parser.test.ts +0 -160
  173. package/src/core/__test__/history.test.ts +0 -225
  174. package/src/core/__test__/merge-manager.test.ts +0 -106
  175. package/src/core/__test__/sheet.test.ts +0 -142
  176. package/src/core/address.ts +0 -166
  177. package/src/core/cell-store.ts +0 -173
  178. package/src/core/command/default-registry.ts +0 -15
  179. package/src/core/command/history.ts +0 -167
  180. package/src/core/command/index.ts +0 -7
  181. package/src/core/command/merge-cells.ts +0 -74
  182. package/src/core/command/registry.ts +0 -30
  183. package/src/core/command/set-cell-formula.ts +0 -29
  184. package/src/core/command/set-cell-value.ts +0 -38
  185. package/src/core/command/types.ts +0 -73
  186. package/src/core/events.ts +0 -37
  187. package/src/core/formula/ast.ts +0 -66
  188. package/src/core/formula/dependency-graph.ts +0 -329
  189. package/src/core/formula/errors.ts +0 -41
  190. package/src/core/formula/evaluator.ts +0 -219
  191. package/src/core/formula/functions.ts +0 -266
  192. package/src/core/formula/index.ts +0 -7
  193. package/src/core/formula/parser.ts +0 -198
  194. package/src/core/formula/tokenizer.ts +0 -160
  195. package/src/core/merge-manager.ts +0 -158
  196. package/src/core/selection.ts +0 -66
  197. package/src/core/sheet.ts +0 -257
  198. package/src/core/workbook.ts +0 -99
  199. package/src/grid/__test__/canvas-mock.ts +0 -49
  200. package/src/grid/__test__/setup.ts +0 -3
  201. package/src/grid/__test__/sheet-grid-formula.test.ts +0 -140
  202. package/src/grid/__test__/sheet-grid.test.ts +0 -177
  203. package/src/grid/sheet-grid.ts +0 -283
@@ -1,43 +0,0 @@
1
- import { DependencyGraph } from "./formula/dependency-graph.js";
2
- import { Sheet } from "./sheet.js";
3
- //#region src/core/workbook.d.ts
4
- /**
5
- * Workbook:多 Sheet 管理。
6
- * 所有 sheet 共享一个公式依赖图(跨表引用与增量重算的中枢)。
7
- */
8
- type WorkbookEvents = {
9
- /** 激活 sheet 切换 */
10
- 'active-sheet-change': {
11
- sheet: Sheet;
12
- index: number;
13
- };
14
- /** sheet 增删 */
15
- 'sheets-change': {
16
- sheets: Sheet[];
17
- };
18
- };
19
- declare class Workbook {
20
- private sheets;
21
- private activeIndex;
22
- private emitter;
23
- /** 工作簿级公式依赖图(全部 sheet 共享) */
24
- readonly formulaGraph: DependencyGraph;
25
- constructor();
26
- get sheetCount(): number;
27
- get activeSheet(): Sheet;
28
- get activeSheetIndex(): number;
29
- getSheets(): Sheet[];
30
- getSheet(name: string): Sheet | undefined;
31
- /** 新增 sheet,名称缺省为 Sheet{n}(保证唯一) */
32
- addSheet(name?: string): Sheet;
33
- /** 删除 sheet;至少保留一个。删除激活项时激活相邻 sheet */
34
- removeSheet(name: string): boolean;
35
- /** 激活指定 sheet */
36
- activateSheet(name: string): boolean;
37
- on<K extends keyof WorkbookEvents>(type: K, handler: (payload: WorkbookEvents[K]) => void): () => void;
38
- off<K extends keyof WorkbookEvents>(type: K, handler: (payload: WorkbookEvents[K]) => void): void;
39
- private nextDefaultName;
40
- }
41
- //#endregion
42
- export { Workbook, WorkbookEvents };
43
- //# sourceMappingURL=workbook.d.ts.map
@@ -1,77 +0,0 @@
1
- import { TypedEventEmitter } from "./events.js";
2
- import { DependencyGraph } from "./formula/dependency-graph.js";
3
- import { Sheet } from "./sheet.js";
4
- //#region src/core/workbook.ts
5
- var Workbook = class {
6
- sheets = [];
7
- activeIndex = 0;
8
- emitter = new TypedEventEmitter();
9
- /** 工作簿级公式依赖图(全部 sheet 共享) */
10
- formulaGraph = new DependencyGraph();
11
- constructor() {
12
- this.addSheet();
13
- }
14
- get sheetCount() {
15
- return this.sheets.length;
16
- }
17
- get activeSheet() {
18
- return this.sheets[this.activeIndex];
19
- }
20
- get activeSheetIndex() {
21
- return this.activeIndex;
22
- }
23
- getSheets() {
24
- return [...this.sheets];
25
- }
26
- getSheet(name) {
27
- return this.sheets.find((sheet) => sheet.name === name);
28
- }
29
- /** 新增 sheet,名称缺省为 Sheet{n}(保证唯一) */
30
- addSheet(name) {
31
- const sheet = new Sheet(name ?? this.nextDefaultName(), this.formulaGraph);
32
- this.sheets.push(sheet);
33
- this.emitter.emit("sheets-change", { sheets: this.getSheets() });
34
- return sheet;
35
- }
36
- /** 删除 sheet;至少保留一个。删除激活项时激活相邻 sheet */
37
- removeSheet(name) {
38
- if (this.sheets.length <= 1) return false;
39
- const index = this.sheets.findIndex((sheet) => sheet.name === name);
40
- if (index < 0) return false;
41
- const [removed] = this.sheets.splice(index, 1);
42
- this.formulaGraph.unregisterSheet(removed);
43
- if (this.activeIndex >= this.sheets.length) this.activeIndex = this.sheets.length - 1;
44
- this.emitter.emit("sheets-change", { sheets: this.getSheets() });
45
- this.emitter.emit("active-sheet-change", {
46
- sheet: this.activeSheet,
47
- index: this.activeIndex
48
- });
49
- return true;
50
- }
51
- /** 激活指定 sheet */
52
- activateSheet(name) {
53
- const index = this.sheets.findIndex((sheet) => sheet.name === name);
54
- if (index < 0 || index === this.activeIndex) return false;
55
- this.activeIndex = index;
56
- this.emitter.emit("active-sheet-change", {
57
- sheet: this.activeSheet,
58
- index
59
- });
60
- return true;
61
- }
62
- on(type, handler) {
63
- return this.emitter.on(type, handler);
64
- }
65
- off(type, handler) {
66
- this.emitter.off(type, handler);
67
- }
68
- nextDefaultName() {
69
- let n = this.sheets.length + 1;
70
- while (this.getSheet(`Sheet${n}`)) n++;
71
- return `Sheet${n}`;
72
- }
73
- };
74
- //#endregion
75
- export { Workbook };
76
-
77
- //# sourceMappingURL=workbook.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"workbook.js","names":[],"sources":["../../src/core/workbook.ts"],"sourcesContent":["import { TypedEventEmitter } from './events'\nimport { DependencyGraph } from './formula/dependency-graph'\nimport { Sheet } from './sheet'\n\n/**\n * Workbook:多 Sheet 管理。\n * 所有 sheet 共享一个公式依赖图(跨表引用与增量重算的中枢)。\n */\n\nexport type WorkbookEvents = {\n /** 激活 sheet 切换 */\n 'active-sheet-change': { sheet: Sheet; index: number }\n /** sheet 增删 */\n 'sheets-change': { sheets: Sheet[] }\n}\n\nexport class Workbook {\n private sheets: Sheet[] = []\n private activeIndex = 0\n private emitter = new TypedEventEmitter<WorkbookEvents>()\n /** 工作簿级公式依赖图(全部 sheet 共享) */\n readonly formulaGraph = new DependencyGraph()\n\n constructor() {\n this.addSheet()\n }\n\n get sheetCount(): number {\n return this.sheets.length\n }\n\n get activeSheet(): Sheet {\n return this.sheets[this.activeIndex]!\n }\n\n get activeSheetIndex(): number {\n return this.activeIndex\n }\n\n getSheets(): Sheet[] {\n return [...this.sheets]\n }\n\n getSheet(name: string): Sheet | undefined {\n return this.sheets.find((sheet) => sheet.name === name)\n }\n\n /** 新增 sheet,名称缺省为 Sheet{n}(保证唯一) */\n addSheet(name?: string): Sheet {\n const sheet = new Sheet(name ?? this.nextDefaultName(), this.formulaGraph)\n this.sheets.push(sheet)\n this.emitter.emit('sheets-change', { sheets: this.getSheets() })\n return sheet\n }\n\n /** 删除 sheet;至少保留一个。删除激活项时激活相邻 sheet */\n removeSheet(name: string): boolean {\n if (this.sheets.length <= 1) return false\n const index = this.sheets.findIndex((sheet) => sheet.name === name)\n if (index < 0) return false\n const [removed] = this.sheets.splice(index, 1)\n this.formulaGraph.unregisterSheet(removed!)\n if (this.activeIndex >= this.sheets.length) {\n this.activeIndex = this.sheets.length - 1\n }\n this.emitter.emit('sheets-change', { sheets: this.getSheets() })\n this.emitter.emit('active-sheet-change', { sheet: this.activeSheet, index: this.activeIndex })\n return true\n }\n\n /** 激活指定 sheet */\n activateSheet(name: string): boolean {\n const index = this.sheets.findIndex((sheet) => sheet.name === name)\n if (index < 0 || index === this.activeIndex) return false\n this.activeIndex = index\n this.emitter.emit('active-sheet-change', { sheet: this.activeSheet, index })\n return true\n }\n\n on<K extends keyof WorkbookEvents>(\n type: K,\n handler: (payload: WorkbookEvents[K]) => void\n ): () => void {\n return this.emitter.on(type, handler)\n }\n\n off<K extends keyof WorkbookEvents>(\n type: K,\n handler: (payload: WorkbookEvents[K]) => void\n ): void {\n this.emitter.off(type, handler)\n }\n\n private nextDefaultName(): string {\n let n = this.sheets.length + 1\n while (this.getSheet(`Sheet${n}`)) n++\n return `Sheet${n}`\n }\n}\n"],"mappings":";;;;AAgBA,IAAa,WAAb,MAAsB;CACpB,SAA0B,CAAC;CAC3B,cAAsB;CACtB,UAAkB,IAAI,kBAAkC;;CAExD,eAAwB,IAAI,gBAAgB;CAE5C,cAAc;EACZ,KAAK,SAAS;CAChB;CAEA,IAAI,aAAqB;EACvB,OAAO,KAAK,OAAO;CACrB;CAEA,IAAI,cAAqB;EACvB,OAAO,KAAK,OAAO,KAAK;CAC1B;CAEA,IAAI,mBAA2B;EAC7B,OAAO,KAAK;CACd;CAEA,YAAqB;EACnB,OAAO,CAAC,GAAG,KAAK,MAAM;CACxB;CAEA,SAAS,MAAiC;EACxC,OAAO,KAAK,OAAO,MAAM,UAAU,MAAM,SAAS,IAAI;CACxD;;CAGA,SAAS,MAAsB;EAC7B,MAAM,QAAQ,IAAI,MAAM,QAAQ,KAAK,gBAAgB,GAAG,KAAK,YAAY;EACzE,KAAK,OAAO,KAAK,KAAK;EACtB,KAAK,QAAQ,KAAK,iBAAiB,EAAE,QAAQ,KAAK,UAAU,EAAE,CAAC;EAC/D,OAAO;CACT;;CAGA,YAAY,MAAuB;EACjC,IAAI,KAAK,OAAO,UAAU,GAAG,OAAO;EACpC,MAAM,QAAQ,KAAK,OAAO,WAAW,UAAU,MAAM,SAAS,IAAI;EAClE,IAAI,QAAQ,GAAG,OAAO;EACtB,MAAM,CAAC,WAAW,KAAK,OAAO,OAAO,OAAO,CAAC;EAC7C,KAAK,aAAa,gBAAgB,OAAQ;EAC1C,IAAI,KAAK,eAAe,KAAK,OAAO,QAClC,KAAK,cAAc,KAAK,OAAO,SAAS;EAE1C,KAAK,QAAQ,KAAK,iBAAiB,EAAE,QAAQ,KAAK,UAAU,EAAE,CAAC;EAC/D,KAAK,QAAQ,KAAK,uBAAuB;GAAE,OAAO,KAAK;GAAa,OAAO,KAAK;EAAY,CAAC;EAC7F,OAAO;CACT;;CAGA,cAAc,MAAuB;EACnC,MAAM,QAAQ,KAAK,OAAO,WAAW,UAAU,MAAM,SAAS,IAAI;EAClE,IAAI,QAAQ,KAAK,UAAU,KAAK,aAAa,OAAO;EACpD,KAAK,cAAc;EACnB,KAAK,QAAQ,KAAK,uBAAuB;GAAE,OAAO,KAAK;GAAa;EAAM,CAAC;EAC3E,OAAO;CACT;CAEA,GACE,MACA,SACY;EACZ,OAAO,KAAK,QAAQ,GAAG,MAAM,OAAO;CACtC;CAEA,IACE,MACA,SACM;EACN,KAAK,QAAQ,IAAI,MAAM,OAAO;CAChC;CAEA,kBAAkC;EAChC,IAAI,IAAI,KAAK,OAAO,SAAS;EAC7B,OAAO,KAAK,SAAS,QAAQ,GAAG,GAAG;EACnC,OAAO,QAAQ;CACjB;AACF"}
@@ -1,59 +0,0 @@
1
- import { Sheet } from "../core/sheet.js";
2
- import { ListTable } from "@visactor/vtable";
3
- //#region src/grid/sheet-grid.d.ts
4
- interface SheetGridOptions {
5
- container: HTMLElement;
6
- sheet: Sheet;
7
- /** 渲染行数,默认 100 */
8
- rows?: number;
9
- /** 渲染列数,默认 26(A..Z) */
10
- cols?: number;
11
- }
12
- declare class SheetGrid {
13
- private readonly sheet;
14
- private readonly table;
15
- private readonly container;
16
- private readonly rows;
17
- private readonly cols;
18
- private readonly editorName;
19
- private readonly disposers;
20
- /** 编辑提交期间累积的模型变更(提交结束统一回推表格,覆盖公式派生格) */
21
- private pendingTableSync;
22
- /** 实测坐标偏移(行号列数 / 列头行数),首次使用时测量 */
23
- private offsets?;
24
- constructor(options: SheetGridOptions);
25
- /** 底层 ListTable 实例(调试与测试用) */
26
- getTable(): ListTable;
27
- /** 全量刷新(合并结构变化、批量数据变更后调用) */
28
- refresh(): void;
29
- /** 撤销一步(模型命令历史) */
30
- undo(): boolean;
31
- /** 重做一步(模型命令历史) */
32
- redo(): boolean;
33
- release(): void;
34
- /**
35
- * 实测偏移:列头行数取 columnHeaderLevelCount;
36
- * 行号列不被计入 rowHeaderLevelCount,用 isSeriesNumber 逐列探测。
37
- * 结果缓存(表格实例不变,布局不变)。
38
- */
39
- private getOffsets;
40
- /** VTable 坐标 → 模型地址;行号列/列头返回 null */
41
- private toSheetAddr;
42
- /** 模型地址 → VTable 坐标 */
43
- private toTableCoord;
44
- private buildColumns;
45
- private buildRecords;
46
- private buildOptions;
47
- private bindTableEvents;
48
- private bindSheetEvents;
49
- /** 模型格 → 表格 record(显示值;公式格为计算缓存) */
50
- private pushCellToTable;
51
- /**
52
- * 键盘绑定:Cmd/Ctrl+Z undo,Cmd/Ctrl+Shift+Z 与 Ctrl+Y redo。
53
- * 编辑器打开时(事件来自编辑器 input)不拦截,保留文本编辑自身的撤销行为。
54
- */
55
- private bindKeyboard;
56
- }
57
- //#endregion
58
- export { SheetGrid, SheetGridOptions };
59
- //# sourceMappingURL=sheet-grid.d.ts.map
@@ -1,230 +0,0 @@
1
- import { cellKey, colIndexToName, createRange } from "../core/address.js";
2
- import { ListTable, register } from "@visactor/vtable";
3
- import { InputEditor } from "@visactor/vtable-editors";
4
- //#region src/grid/sheet-grid.ts
5
- /**
6
- * VTable 适配层:数据模型完全自持有,ListTable 只做渲染与输入。
7
- *
8
- * - 模型 → VTable:records 由 store 行视图桥接;customMergeCell 闭包直读 MergeManager
9
- * (VTable 逐格动态求值、无缓存,合并变更后 setRecords 重建场景树即生效)
10
- * - VTable → 模型:change_cell_value 回写 store;selected_cell 经 resolveAnchor 更新选区
11
- * - 公式:公式格 record 存计算缓存(显示值);进入编辑时编辑器显示公式原文('=f')。
12
- * 编辑提交期间模型变更(含公式重算派生格)先入待同步队列,提交结束统一回推表格——
13
- * VTable 自己只更新了被编辑格的 record(还是输入文本),派生格它不知道
14
- * - 键盘:容器 keydown 绑定 undo/redo(Cmd/Ctrl+Z、Cmd/Ctrl+Shift+Z、Ctrl+Y),
15
- * 编辑器 input 打开时不拦截
16
- * - 坐标换算:行号列不计入 rowHeaderLevelCount,偏移量在首个表格实例上
17
- * 用 columnHeaderLevelCount + isSeriesNumber 实测并缓存(见 getOffsets)
18
- */
19
- /** 公式感知编辑器:进入编辑时公式格显示原文(同 Excel),其余格显示当前值 */
20
- var FormulaAwareInputEditor = class extends InputEditor {
21
- /** 由 SheetGrid 注入:返回进入编辑时应显示的文本;undefined = 用 VTable 默认值 */
22
- resolveEditText;
23
- onStart(context) {
24
- const text = this.resolveEditText?.(context.col, context.row);
25
- super.onStart(text === void 0 ? context : {
26
- ...context,
27
- value: text
28
- });
29
- }
30
- };
31
- /** 编辑器按 grid 实例注册(hook 闭包各自 sheet),名称递增防冲突 */
32
- let editorSeq = 0;
33
- var SheetGrid = class {
34
- sheet;
35
- table;
36
- container;
37
- rows;
38
- cols;
39
- editorName;
40
- disposers = [];
41
- /** 编辑提交期间累积的模型变更(提交结束统一回推表格,覆盖公式派生格) */
42
- pendingTableSync = null;
43
- /** 实测坐标偏移(行号列数 / 列头行数),首次使用时测量 */
44
- offsets;
45
- constructor(options) {
46
- this.sheet = options.sheet;
47
- this.container = options.container;
48
- this.rows = options.rows ?? 100;
49
- this.cols = options.cols ?? 26;
50
- this.editorName = `veltra-sheet-input-${editorSeq++}`;
51
- const editor = new FormulaAwareInputEditor();
52
- editor.resolveEditText = (col, row) => {
53
- const addr = this.toSheetAddr(this.table, col, row);
54
- if (!addr) return void 0;
55
- const data = this.sheet.getCellData(this.sheet.merges.resolveAnchor(addr));
56
- return data?.f ? `=${data.f}` : void 0;
57
- };
58
- register.editor(this.editorName, editor);
59
- this.table = new ListTable(options.container, this.buildOptions());
60
- this.bindTableEvents();
61
- this.bindSheetEvents();
62
- this.bindKeyboard();
63
- }
64
- /** 底层 ListTable 实例(调试与测试用) */
65
- getTable() {
66
- return this.table;
67
- }
68
- /** 全量刷新(合并结构变化、批量数据变更后调用) */
69
- refresh() {
70
- this.table.setRecords(this.buildRecords());
71
- }
72
- /** 撤销一步(模型命令历史) */
73
- undo() {
74
- return this.sheet.undo();
75
- }
76
- /** 重做一步(模型命令历史) */
77
- redo() {
78
- return this.sheet.redo();
79
- }
80
- release() {
81
- for (const dispose of this.disposers) dispose();
82
- this.disposers.length = 0;
83
- this.table.release();
84
- }
85
- /**
86
- * 实测偏移:列头行数取 columnHeaderLevelCount;
87
- * 行号列不被计入 rowHeaderLevelCount,用 isSeriesNumber 逐列探测。
88
- * 结果缓存(表格实例不变,布局不变)。
89
- */
90
- getOffsets(table) {
91
- if (!this.offsets) {
92
- const rowOffset = table.columnHeaderLevelCount;
93
- let colOffset = 0;
94
- while (colOffset < table.colCount && table.isSeriesNumber(colOffset, rowOffset)) colOffset++;
95
- this.offsets = {
96
- colOffset,
97
- rowOffset
98
- };
99
- }
100
- return this.offsets;
101
- }
102
- /** VTable 坐标 → 模型地址;行号列/列头返回 null */
103
- toSheetAddr(table, col, row) {
104
- const { colOffset, rowOffset } = this.getOffsets(table);
105
- const addr = {
106
- row: row - rowOffset,
107
- col: col - colOffset
108
- };
109
- if (addr.row < 0 || addr.col < 0) return null;
110
- return addr;
111
- }
112
- /** 模型地址 → VTable 坐标 */
113
- toTableCoord(table, addr) {
114
- const { colOffset, rowOffset } = this.getOffsets(table);
115
- return {
116
- col: addr.col + colOffset,
117
- row: addr.row + rowOffset
118
- };
119
- }
120
- buildColumns() {
121
- return Array.from({ length: this.cols }, (_, col) => ({
122
- field: String(col),
123
- title: colIndexToName(col)
124
- }));
125
- }
126
- buildRecords() {
127
- const records = Array.from({ length: this.rows }, () => ({}));
128
- for (const [addr, data] of this.sheet.store.entries()) if (addr.row < this.rows && addr.col < this.cols && data.v != null) records[addr.row][String(addr.col)] = data.v;
129
- return records;
130
- }
131
- buildOptions() {
132
- return {
133
- records: this.buildRecords(),
134
- columns: this.buildColumns(),
135
- widthMode: "standard",
136
- rowSeriesNumber: { width: 46 },
137
- editor: this.editorName,
138
- editCellTrigger: "doubleclick",
139
- keyboardOptions: {
140
- moveFocusCellOnTab: true,
141
- editCellOnEnter: true,
142
- moveFocusCellOnEnter: true,
143
- moveEditCellOnArrowKeys: true,
144
- selectAllOnCtrlA: true
145
- },
146
- customMergeCell: (col, row, table) => {
147
- const addr = this.toSheetAddr(table, col, row);
148
- if (!addr) return void 0;
149
- const merge = this.sheet.merges.getMergeAt(addr);
150
- if (!merge) return void 0;
151
- const anchorCoord = this.toTableCoord(table, merge.start);
152
- const recordValue = table.getCellOriginValue(anchorCoord.col, anchorCoord.row);
153
- return {
154
- range: {
155
- start: anchorCoord,
156
- end: this.toTableCoord(table, merge.end)
157
- },
158
- text: recordValue == null ? "" : String(recordValue)
159
- };
160
- }
161
- };
162
- }
163
- bindTableEvents() {
164
- this.table.on(ListTable.EVENT_TYPE.CHANGE_CELL_VALUE, (args) => {
165
- const addr = this.toSheetAddr(this.table, args.col, args.row);
166
- if (addr == null) return;
167
- this.pendingTableSync = /* @__PURE__ */ new Map([[cellKey(addr), addr]]);
168
- try {
169
- this.sheet.setCellValue(addr, args.changedValue ?? null);
170
- } finally {
171
- const pending = this.pendingTableSync;
172
- this.pendingTableSync = null;
173
- if (pending) for (const pendingAddr of pending.values()) this.pushCellToTable(pendingAddr);
174
- }
175
- });
176
- this.table.on(ListTable.EVENT_TYPE.SELECTED_CELL, (args) => {
177
- const addr = this.toSheetAddr(this.table, args.col, args.row);
178
- if (addr == null) return;
179
- this.sheet.selectCell(addr);
180
- });
181
- this.table.on(ListTable.EVENT_TYPE.DRAG_SELECT_END, () => {
182
- const range = this.table.getSelectedCellRanges()[0];
183
- if (!range) return;
184
- const start = this.toSheetAddr(this.table, range.start.col, range.start.row);
185
- const end = this.toSheetAddr(this.table, range.end.col, range.end.row);
186
- if (!start || !end) return;
187
- this.sheet.selectRange(createRange(start, end));
188
- });
189
- }
190
- bindSheetEvents() {
191
- this.disposers.push(this.sheet.on("cell-change", ({ addr }) => {
192
- if (this.pendingTableSync) {
193
- this.pendingTableSync.set(cellKey(addr), addr);
194
- return;
195
- }
196
- this.pushCellToTable(addr);
197
- }));
198
- this.disposers.push(this.sheet.on("merge-change", () => {
199
- this.refresh();
200
- }));
201
- }
202
- /** 模型格 → 表格 record(显示值;公式格为计算缓存) */
203
- pushCellToTable(addr) {
204
- const { col, row } = this.toTableCoord(this.table, addr);
205
- const value = this.sheet.getDisplayValue(addr);
206
- this.table.changeCellValue(col, row, value, false, false);
207
- }
208
- /**
209
- * 键盘绑定:Cmd/Ctrl+Z undo,Cmd/Ctrl+Shift+Z 与 Ctrl+Y redo。
210
- * 编辑器打开时(事件来自编辑器 input)不拦截,保留文本编辑自身的撤销行为。
211
- */
212
- bindKeyboard() {
213
- const onKeyDown = (event) => {
214
- if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) return;
215
- if (!(event.metaKey || event.ctrlKey)) return;
216
- const key = event.key.toLowerCase();
217
- if (key === "z" && !event.shiftKey) {
218
- if (this.undo()) event.preventDefault();
219
- } else if (key === "z" && event.shiftKey || key === "y" && event.ctrlKey) {
220
- if (this.redo()) event.preventDefault();
221
- }
222
- };
223
- this.container.addEventListener("keydown", onKeyDown);
224
- this.disposers.push(() => this.container.removeEventListener("keydown", onKeyDown));
225
- }
226
- };
227
- //#endregion
228
- export { SheetGrid };
229
-
230
- //# sourceMappingURL=sheet-grid.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sheet-grid.js","names":[],"sources":["../../src/grid/sheet-grid.ts"],"sourcesContent":["import { ListTable, register } from '@visactor/vtable'\nimport type { ListTableConstructorOptions } from '@visactor/vtable'\nimport { InputEditor } from '@visactor/vtable-editors'\nimport type { EditContext } from '@visactor/vtable-editors'\n\nimport type { CellAddress } from '../core/address'\nimport { cellKey, colIndexToName, createRange } from '../core/address'\nimport type { CellValue } from '../core/cell-store'\nimport type { Sheet } from '../core/sheet'\n\n/**\n * VTable 适配层:数据模型完全自持有,ListTable 只做渲染与输入。\n *\n * - 模型 → VTable:records 由 store 行视图桥接;customMergeCell 闭包直读 MergeManager\n * (VTable 逐格动态求值、无缓存,合并变更后 setRecords 重建场景树即生效)\n * - VTable → 模型:change_cell_value 回写 store;selected_cell 经 resolveAnchor 更新选区\n * - 公式:公式格 record 存计算缓存(显示值);进入编辑时编辑器显示公式原文('=f')。\n * 编辑提交期间模型变更(含公式重算派生格)先入待同步队列,提交结束统一回推表格——\n * VTable 自己只更新了被编辑格的 record(还是输入文本),派生格它不知道\n * - 键盘:容器 keydown 绑定 undo/redo(Cmd/Ctrl+Z、Cmd/Ctrl+Shift+Z、Ctrl+Y),\n * 编辑器 input 打开时不拦截\n * - 坐标换算:行号列不计入 rowHeaderLevelCount,偏移量在首个表格实例上\n * 用 columnHeaderLevelCount + isSeriesNumber 实测并缓存(见 getOffsets)\n */\n\n/** 公式感知编辑器:进入编辑时公式格显示原文(同 Excel),其余格显示当前值 */\nclass FormulaAwareInputEditor extends InputEditor {\n /** 由 SheetGrid 注入:返回进入编辑时应显示的文本;undefined = 用 VTable 默认值 */\n resolveEditText?: (col: number, row: number) => string | undefined\n\n override onStart(context: EditContext<string>): void {\n const text = this.resolveEditText?.(context.col, context.row)\n super.onStart(text === undefined ? context : { ...context, value: text })\n }\n}\n\n/** 编辑器按 grid 实例注册(hook 闭包各自 sheet),名称递增防冲突 */\nlet editorSeq = 0\n\nexport interface SheetGridOptions {\n container: HTMLElement\n sheet: Sheet\n /** 渲染行数,默认 100 */\n rows?: number\n /** 渲染列数,默认 26(A..Z) */\n cols?: number\n}\n\nexport class SheetGrid {\n private readonly sheet: Sheet\n private readonly table: ListTable\n private readonly container: HTMLElement\n private readonly rows: number\n private readonly cols: number\n private readonly editorName: string\n private readonly disposers: (() => void)[] = []\n /** 编辑提交期间累积的模型变更(提交结束统一回推表格,覆盖公式派生格) */\n private pendingTableSync: Map<number, CellAddress> | null = null\n /** 实测坐标偏移(行号列数 / 列头行数),首次使用时测量 */\n private offsets?: { colOffset: number; rowOffset: number }\n\n constructor(options: SheetGridOptions) {\n this.sheet = options.sheet\n this.container = options.container\n this.rows = options.rows ?? 100\n this.cols = options.cols ?? 26\n\n // 每个 grid 实例注册自己的编辑器(hook 闭包本实例的 sheet 与坐标换算)\n this.editorName = `veltra-sheet-input-${editorSeq++}`\n const editor = new FormulaAwareInputEditor()\n editor.resolveEditText = (col, row) => {\n const addr = this.toSheetAddr(this.table, col, row)\n if (!addr) return undefined\n const data = this.sheet.getCellData(this.sheet.merges.resolveAnchor(addr))\n return data?.f ? `=${data.f}` : undefined\n }\n register.editor(this.editorName, editor)\n\n this.table = new ListTable(options.container, this.buildOptions())\n this.bindTableEvents()\n this.bindSheetEvents()\n this.bindKeyboard()\n }\n\n /** 底层 ListTable 实例(调试与测试用) */\n getTable(): ListTable {\n return this.table\n }\n\n /** 全量刷新(合并结构变化、批量数据变更后调用) */\n refresh(): void {\n this.table.setRecords(this.buildRecords())\n }\n\n /** 撤销一步(模型命令历史) */\n undo(): boolean {\n return this.sheet.undo()\n }\n\n /** 重做一步(模型命令历史) */\n redo(): boolean {\n return this.sheet.redo()\n }\n\n release(): void {\n for (const dispose of this.disposers) dispose()\n this.disposers.length = 0\n this.table.release()\n }\n\n // ─── 坐标换算 ─────────────────────────────────────────────\n\n /**\n * 实测偏移:列头行数取 columnHeaderLevelCount;\n * 行号列不被计入 rowHeaderLevelCount,用 isSeriesNumber 逐列探测。\n * 结果缓存(表格实例不变,布局不变)。\n */\n private getOffsets(table: ListTable): { colOffset: number; rowOffset: number } {\n if (!this.offsets) {\n const rowOffset = table.columnHeaderLevelCount\n let colOffset = 0\n while (colOffset < table.colCount && table.isSeriesNumber(colOffset, rowOffset)) colOffset++\n this.offsets = { colOffset, rowOffset }\n }\n return this.offsets\n }\n\n /** VTable 坐标 → 模型地址;行号列/列头返回 null */\n private toSheetAddr(table: ListTable, col: number, row: number): CellAddress | null {\n const { colOffset, rowOffset } = this.getOffsets(table)\n const addr = { row: row - rowOffset, col: col - colOffset }\n if (addr.row < 0 || addr.col < 0) return null\n return addr\n }\n\n /** 模型地址 → VTable 坐标 */\n private toTableCoord(table: ListTable, addr: CellAddress): { col: number; row: number } {\n const { colOffset, rowOffset } = this.getOffsets(table)\n return { col: addr.col + colOffset, row: addr.row + rowOffset }\n }\n\n // ─── 模型 → VTable ────────────────────────────────────────\n\n private buildColumns() {\n return Array.from({ length: this.cols }, (_, col) => ({\n field: String(col),\n title: colIndexToName(col)\n }))\n }\n\n private buildRecords(): Record<string, CellValue>[] {\n const records: Record<string, CellValue>[] = Array.from({ length: this.rows }, () => ({}))\n for (const [addr, data] of this.sheet.store.entries()) {\n if (addr.row < this.rows && addr.col < this.cols && data.v != null) {\n records[addr.row]![String(addr.col)] = data.v\n }\n }\n return records\n }\n\n private buildOptions(): ListTableConstructorOptions {\n return {\n records: this.buildRecords(),\n columns: this.buildColumns(),\n widthMode: 'standard',\n rowSeriesNumber: { width: 46 },\n editor: this.editorName,\n editCellTrigger: 'doubleclick',\n keyboardOptions: {\n moveFocusCellOnTab: true,\n editCellOnEnter: true,\n moveFocusCellOnEnter: true,\n moveEditCellOnArrowKeys: true,\n selectAllOnCtrlA: true\n },\n customMergeCell: (col, row, table) => {\n const addr = this.toSheetAddr(table as ListTable, col, row)\n if (!addr) return undefined\n const merge = this.sheet.merges.getMergeAt(addr)\n if (!merge) return undefined\n const anchorCoord = this.toTableCoord(table as ListTable, merge.start)\n const recordValue = (table as ListTable).getCellOriginValue(\n anchorCoord.col,\n anchorCoord.row\n )\n return {\n range: { start: anchorCoord, end: this.toTableCoord(table as ListTable, merge.end) },\n // 关键 1:必须携带 text。VTable 的 getCellRange 仅在 text/customLayout/\n // customRender 有效时才认这个自定义合并——没有 text 时选区与编辑不会\n // 扩展为整个合并区域,且合并格渲染为空。\n // 关键 2:text 必须读 VTable records 而非模型。编辑提交的顺序是\n // 先更新 record → 重绘 → 最后才发 change_cell_value 回写模型;\n // 若此时读模型,拿到的还是回写前的旧值(合并格显示旧文本)。\n text: recordValue == null ? '' : String(recordValue)\n }\n }\n }\n }\n\n // ─── 事件桥接 ─────────────────────────────────────────────\n\n private bindTableEvents(): void {\n this.table.on(ListTable.EVENT_TYPE.CHANGE_CELL_VALUE, (args) => {\n const addr = this.toSheetAddr(this.table, args.col, args.row)\n if (addr == null) return\n // 编辑提交期间模型变更(含公式重算派生格)先入队列,提交结束统一回推。\n // 被编辑格自身也入队:VTable 已把输入文本写进 record,公式格需回推计算值\n this.pendingTableSync = new Map([[cellKey(addr), addr]])\n try {\n this.sheet.setCellValue(addr, args.changedValue ?? null)\n } finally {\n const pending = this.pendingTableSync\n this.pendingTableSync = null\n if (pending) {\n for (const pendingAddr of pending.values()) this.pushCellToTable(pendingAddr)\n }\n }\n })\n\n this.table.on(ListTable.EVENT_TYPE.SELECTED_CELL, (args) => {\n const addr = this.toSheetAddr(this.table, args.col, args.row)\n if (addr == null) return\n this.sheet.selectCell(addr)\n })\n\n // 拖选结束 → 选区同步为区域(合并等区域操作的前提)\n this.table.on(ListTable.EVENT_TYPE.DRAG_SELECT_END, () => {\n const range = this.table.getSelectedCellRanges()[0]\n if (!range) return\n const start = this.toSheetAddr(this.table, range.start.col, range.start.row)\n const end = this.toSheetAddr(this.table, range.end.col, range.end.row)\n if (!start || !end) return\n this.sheet.selectRange(createRange(start, end))\n })\n }\n\n private bindSheetEvents(): void {\n this.disposers.push(\n this.sheet.on('cell-change', ({ addr }) => {\n if (this.pendingTableSync) {\n this.pendingTableSync.set(cellKey(addr), addr)\n return\n }\n this.pushCellToTable(addr)\n })\n )\n\n this.disposers.push(\n this.sheet.on('merge-change', () => {\n this.refresh()\n })\n )\n }\n\n /** 模型格 → 表格 record(显示值;公式格为计算缓存) */\n private pushCellToTable(addr: CellAddress): void {\n const { col, row } = this.toTableCoord(this.table, addr)\n const value = this.sheet.getDisplayValue(addr)\n this.table.changeCellValue(col, row, value as string | number | null, false, false)\n }\n\n /**\n * 键盘绑定:Cmd/Ctrl+Z undo,Cmd/Ctrl+Shift+Z 与 Ctrl+Y redo。\n * 编辑器打开时(事件来自编辑器 input)不拦截,保留文本编辑自身的撤销行为。\n */\n private bindKeyboard(): void {\n const onKeyDown = (event: KeyboardEvent): void => {\n if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) {\n return\n }\n const mod = event.metaKey || event.ctrlKey\n if (!mod) return\n const key = event.key.toLowerCase()\n if (key === 'z' && !event.shiftKey) {\n if (this.undo()) event.preventDefault()\n } else if ((key === 'z' && event.shiftKey) || (key === 'y' && event.ctrlKey)) {\n if (this.redo()) event.preventDefault()\n }\n }\n this.container.addEventListener('keydown', onKeyDown)\n this.disposers.push(() => this.container.removeEventListener('keydown', onKeyDown))\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA0BA,IAAM,0BAAN,cAAsC,YAAY;;CAEhD;CAEA,QAAiB,SAAoC;EACnD,MAAM,OAAO,KAAK,kBAAkB,QAAQ,KAAK,QAAQ,GAAG;EAC5D,MAAM,QAAQ,SAAS,KAAA,IAAY,UAAU;GAAE,GAAG;GAAS,OAAO;EAAK,CAAC;CAC1E;AACF;;AAGA,IAAI,YAAY;AAWhB,IAAa,YAAb,MAAuB;CACrB;CACA;CACA;CACA;CACA;CACA;CACA,YAA6C,CAAC;;CAE9C,mBAA4D;;CAE5D;CAEA,YAAY,SAA2B;EACrC,KAAK,QAAQ,QAAQ;EACrB,KAAK,YAAY,QAAQ;EACzB,KAAK,OAAO,QAAQ,QAAQ;EAC5B,KAAK,OAAO,QAAQ,QAAQ;EAG5B,KAAK,aAAa,sBAAsB;EACxC,MAAM,SAAS,IAAI,wBAAwB;EAC3C,OAAO,mBAAmB,KAAK,QAAQ;GACrC,MAAM,OAAO,KAAK,YAAY,KAAK,OAAO,KAAK,GAAG;GAClD,IAAI,CAAC,MAAM,OAAO,KAAA;GAClB,MAAM,OAAO,KAAK,MAAM,YAAY,KAAK,MAAM,OAAO,cAAc,IAAI,CAAC;GACzE,OAAO,MAAM,IAAI,IAAI,KAAK,MAAM,KAAA;EAClC;EACA,SAAS,OAAO,KAAK,YAAY,MAAM;EAEvC,KAAK,QAAQ,IAAI,UAAU,QAAQ,WAAW,KAAK,aAAa,CAAC;EACjE,KAAK,gBAAgB;EACrB,KAAK,gBAAgB;EACrB,KAAK,aAAa;CACpB;;CAGA,WAAsB;EACpB,OAAO,KAAK;CACd;;CAGA,UAAgB;EACd,KAAK,MAAM,WAAW,KAAK,aAAa,CAAC;CAC3C;;CAGA,OAAgB;EACd,OAAO,KAAK,MAAM,KAAK;CACzB;;CAGA,OAAgB;EACd,OAAO,KAAK,MAAM,KAAK;CACzB;CAEA,UAAgB;EACd,KAAK,MAAM,WAAW,KAAK,WAAW,QAAQ;EAC9C,KAAK,UAAU,SAAS;EACxB,KAAK,MAAM,QAAQ;CACrB;;;;;;CASA,WAAmB,OAA4D;EAC7E,IAAI,CAAC,KAAK,SAAS;GACjB,MAAM,YAAY,MAAM;GACxB,IAAI,YAAY;GAChB,OAAO,YAAY,MAAM,YAAY,MAAM,eAAe,WAAW,SAAS,GAAG;GACjF,KAAK,UAAU;IAAE;IAAW;GAAU;EACxC;EACA,OAAO,KAAK;CACd;;CAGA,YAAoB,OAAkB,KAAa,KAAiC;EAClF,MAAM,EAAE,WAAW,cAAc,KAAK,WAAW,KAAK;EACtD,MAAM,OAAO;GAAE,KAAK,MAAM;GAAW,KAAK,MAAM;EAAU;EAC1D,IAAI,KAAK,MAAM,KAAK,KAAK,MAAM,GAAG,OAAO;EACzC,OAAO;CACT;;CAGA,aAAqB,OAAkB,MAAiD;EACtF,MAAM,EAAE,WAAW,cAAc,KAAK,WAAW,KAAK;EACtD,OAAO;GAAE,KAAK,KAAK,MAAM;GAAW,KAAK,KAAK,MAAM;EAAU;CAChE;CAIA,eAAuB;EACrB,OAAO,MAAM,KAAK,EAAE,QAAQ,KAAK,KAAK,IAAI,GAAG,SAAS;GACpD,OAAO,OAAO,GAAG;GACjB,OAAO,eAAe,GAAG;EAC3B,EAAE;CACJ;CAEA,eAAoD;EAClD,MAAM,UAAuC,MAAM,KAAK,EAAE,QAAQ,KAAK,KAAK,UAAU,CAAC,EAAE;EACzF,KAAK,MAAM,CAAC,MAAM,SAAS,KAAK,MAAM,MAAM,QAAQ,GAClD,IAAI,KAAK,MAAM,KAAK,QAAQ,KAAK,MAAM,KAAK,QAAQ,KAAK,KAAK,MAC5D,QAAQ,KAAK,IAAI,CAAE,OAAO,KAAK,GAAG,KAAK,KAAK;EAGhD,OAAO;CACT;CAEA,eAAoD;EAClD,OAAO;GACL,SAAS,KAAK,aAAa;GAC3B,SAAS,KAAK,aAAa;GAC3B,WAAW;GACX,iBAAiB,EAAE,OAAO,GAAG;GAC7B,QAAQ,KAAK;GACb,iBAAiB;GACjB,iBAAiB;IACf,oBAAoB;IACpB,iBAAiB;IACjB,sBAAsB;IACtB,yBAAyB;IACzB,kBAAkB;GACpB;GACA,kBAAkB,KAAK,KAAK,UAAU;IACpC,MAAM,OAAO,KAAK,YAAY,OAAoB,KAAK,GAAG;IAC1D,IAAI,CAAC,MAAM,OAAO,KAAA;IAClB,MAAM,QAAQ,KAAK,MAAM,OAAO,WAAW,IAAI;IAC/C,IAAI,CAAC,OAAO,OAAO,KAAA;IACnB,MAAM,cAAc,KAAK,aAAa,OAAoB,MAAM,KAAK;IACrE,MAAM,cAAe,MAAoB,mBACvC,YAAY,KACZ,YAAY,GACd;IACA,OAAO;KACL,OAAO;MAAE,OAAO;MAAa,KAAK,KAAK,aAAa,OAAoB,MAAM,GAAG;KAAE;KAOnF,MAAM,eAAe,OAAO,KAAK,OAAO,WAAW;IACrD;GACF;EACF;CACF;CAIA,kBAAgC;EAC9B,KAAK,MAAM,GAAG,UAAU,WAAW,oBAAoB,SAAS;GAC9D,MAAM,OAAO,KAAK,YAAY,KAAK,OAAO,KAAK,KAAK,KAAK,GAAG;GAC5D,IAAI,QAAQ,MAAM;GAGlB,KAAK,mCAAmB,IAAI,IAAI,CAAC,CAAC,QAAQ,IAAI,GAAG,IAAI,CAAC,CAAC;GACvD,IAAI;IACF,KAAK,MAAM,aAAa,MAAM,KAAK,gBAAgB,IAAI;GACzD,UAAU;IACR,MAAM,UAAU,KAAK;IACrB,KAAK,mBAAmB;IACxB,IAAI,SACF,KAAK,MAAM,eAAe,QAAQ,OAAO,GAAG,KAAK,gBAAgB,WAAW;GAEhF;EACF,CAAC;EAED,KAAK,MAAM,GAAG,UAAU,WAAW,gBAAgB,SAAS;GAC1D,MAAM,OAAO,KAAK,YAAY,KAAK,OAAO,KAAK,KAAK,KAAK,GAAG;GAC5D,IAAI,QAAQ,MAAM;GAClB,KAAK,MAAM,WAAW,IAAI;EAC5B,CAAC;EAGD,KAAK,MAAM,GAAG,UAAU,WAAW,uBAAuB;GACxD,MAAM,QAAQ,KAAK,MAAM,sBAAsB,CAAC,CAAC;GACjD,IAAI,CAAC,OAAO;GACZ,MAAM,QAAQ,KAAK,YAAY,KAAK,OAAO,MAAM,MAAM,KAAK,MAAM,MAAM,GAAG;GAC3E,MAAM,MAAM,KAAK,YAAY,KAAK,OAAO,MAAM,IAAI,KAAK,MAAM,IAAI,GAAG;GACrE,IAAI,CAAC,SAAS,CAAC,KAAK;GACpB,KAAK,MAAM,YAAY,YAAY,OAAO,GAAG,CAAC;EAChD,CAAC;CACH;CAEA,kBAAgC;EAC9B,KAAK,UAAU,KACb,KAAK,MAAM,GAAG,gBAAgB,EAAE,WAAW;GACzC,IAAI,KAAK,kBAAkB;IACzB,KAAK,iBAAiB,IAAI,QAAQ,IAAI,GAAG,IAAI;IAC7C;GACF;GACA,KAAK,gBAAgB,IAAI;EAC3B,CAAC,CACH;EAEA,KAAK,UAAU,KACb,KAAK,MAAM,GAAG,sBAAsB;GAClC,KAAK,QAAQ;EACf,CAAC,CACH;CACF;;CAGA,gBAAwB,MAAyB;EAC/C,MAAM,EAAE,KAAK,QAAQ,KAAK,aAAa,KAAK,OAAO,IAAI;EACvD,MAAM,QAAQ,KAAK,MAAM,gBAAgB,IAAI;EAC7C,KAAK,MAAM,gBAAgB,KAAK,KAAK,OAAiC,OAAO,KAAK;CACpF;;;;;CAMA,eAA6B;EAC3B,MAAM,aAAa,UAA+B;GAChD,IAAI,MAAM,kBAAkB,oBAAoB,MAAM,kBAAkB,qBACtE;GAGF,IAAI,EADQ,MAAM,WAAW,MAAM,UACzB;GACV,MAAM,MAAM,MAAM,IAAI,YAAY;GAClC,IAAI,QAAQ,OAAO,CAAC,MAAM;QACpB,KAAK,KAAK,GAAG,MAAM,eAAe;GAAA,OACjC,IAAK,QAAQ,OAAO,MAAM,YAAc,QAAQ,OAAO,MAAM;QAC9D,KAAK,KAAK,GAAG,MAAM,eAAe;GAAA;EAE1C;EACA,KAAK,UAAU,iBAAiB,WAAW,SAAS;EACpD,KAAK,UAAU,WAAW,KAAK,UAAU,oBAAoB,WAAW,SAAS,CAAC;CACpF;AACF"}
@@ -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 { bem } from '@veltra/utils'\nimport { computed, onBeforeUnmount, onMounted, ref, shallowRef, useTemplateRef, watch } from 'vue'\n\nimport type { Sheet } from '../core/sheet'\nimport { Workbook } from '../core/workbook'\nimport { SheetGrid } 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// ─── grid 生命周期 ──────────────────────────────────────────\n\nfunction rebuildGrid(): void {\n const container = gridRef.value\n if (!container) return\n grid?.release()\n grid = new SheetGrid({ container, sheet: activeSheet.value, rows: props.rows, cols: props.cols })\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiDA,MAAM,QAAQ;EAOd,MAAM,OAAO;EAEb,MAAM,MAAM,IAAI,OAAO;;EAGvB,MAAM,mBAAmB,IAAI,SAAS;EACtC,MAAM,WAAW,eAAe,MAAM,YAAY,gBAAgB;;EAGlE,MAAM,cAAc,IAAI,SAAS,MAAM,gBAAgB;;EAEvD,MAAM,YAAY,WAAoB,SAAS,MAAM,UAAU,CAAC;EAChE,MAAM,cAAc,eAAe,UAAU,MAAM,YAAY,UAAU,SAAS,MAAM,WAAW;;EAGnG,MAAM,UAAU,yBAAyB,YAAY,KAAK;EAE1D,MAAM,UAAU,eAA4B,SAAS;EACrD,IAAI;EAKJ,MAAM,YAAY,IAAI,CAAC;EACvB,MAAM,aAAmB;GACvB,UAAU;EACZ;EAEA,IAAI,qBAAqC,CAAC;;EAE1C,SAAS,gBAAgB,OAAoB;GAC3C,KAAK,MAAM,WAAW,oBAAoB,QAAQ;GAClD,qBAAqB;IACnB,MAAM,GAAG,oBAAoB,IAAI;IACjC,MAAM,GAAG,kBAAkB,IAAI;IAC/B,MAAM,GAAG,eAAe,IAAI;IAC5B,MAAM,GAAG,gBAAgB,IAAI;GAC/B;EACF;EAEA,IAAI,wBAAwC,CAAC;EAC7C,SAAS,mBAAmB,IAAoB;GAC9C,KAAK,MAAM,WAAW,uBAAuB,QAAQ;GACrD,wBAAwB,CACtB,GAAG,GAAG,kBAAkB,EAAE,aAAa;IACrC,UAAU,QAAQ;IAClB,KAAK;GACP,CAAC,GACD,GAAG,GAAG,wBAAwB,EAAE,OAAO,YAAY;IACjD,YAAY,QAAQ;IACpB,gBAAgB,KAAK;IACrB,YAAY;IACZ,KAAK;IACL,KAAK,uBAAuB;KAAE;KAAO;IAAM,CAAC;GAC9C,CAAC,CACH;EACF;EAEA,MAAM,oBAAoB,oBAAoB,SAAS,IAAI;EAI3D,MAAM,aAAa,eAAe;GAChC,UAAe;GACf,OAAO,oBACJ,UAAU,CAAA,CACV,KAAK,WAAW;IACf,MAAM,MAAM;IACZ,OAAO,MAAM,MACV,KAAK,UAAU;KACd;KACA,SAAS,KAAK,UAAU,OAAO,KAAK;KACpC,UAAU,KAAK,WAAW,OAAO,KAAK;IACxC,EAAE,CAAA,CACD,QAAQ,SAAS,KAAK,OAAO;GAClC,EAAE,CAAA,CACD,QAAQ,UAAU,MAAM,MAAM,SAAS,CAAC;EAC7C,CAAC;EAED,SAAS,gBAAgB,MAAuB;GAC9C,IAAI,KAAK,WAAW,OAAO,GAAG;GAC9B,KAAK,QAAQ,OAAO;EACtB;EAIA,SAAS,eAAe,OAAqB;GAC3C,IAAI,UAAU,YAAY,OAAO;GACjC,MAAM,QAAQ,UAAU,MAAM;GAC9B,IAAI,OAAO,SAAS,MAAM,cAAc,MAAM,IAAI;EACpD;EAIA,SAAS,cAAoB;GAC3B,MAAM,YAAY,QAAQ;GAC1B,IAAI,CAAC,WAAW;GAChB,MAAM,QAAQ;GACd,OAAO,IAAI,UAAU;IAAE;IAAW,OAAO,YAAY;IAAO,MAAM,MAAM;IAAM,MAAM,MAAM;GAAK,CAAC;EAClG;EAEA,gBAAgB;GACd,mBAAmB,SAAS,KAAK;GACjC,gBAAgB,YAAY,KAAK;GACjC,YAAY;EACd,CAAC;EAED,YAAY,CAAC,MAAM,MAAM,MAAM,IAAI,GAAG,WAAW;EAEjD,YACQ,MAAM,WACX,IAAI,SAAS;GACZ,IAAI,OAAO,MAAM;GACjB,YAAY,QAAQ,SAAS,MAAM;GACnC,UAAU,QAAQ,SAAS,MAAM,UAAU;GAC3C,mBAAmB,SAAS,KAAK;GACjC,gBAAgB,YAAY,KAAK;GACjC,YAAY;GACZ,KAAK;EACP,CACF;EAEA,sBAAsB;GACpB,kBAAkB;GAClB,KAAK,MAAM,WAAW,uBAAuB,QAAQ;GACrD,KAAK,MAAM,WAAW,oBAAoB,QAAQ;GAClD,MAAM,QAAQ;GACd,OAAO,KAAA;EACT,CAAC;EASD,SAAa;GANX;GACA,sBAAsB,YAAY;GAClC,kBAAkB;GAClB,eAAe;EAGJ,CAAO;;uBAhMlB,mBAgCM,OAAA,EAhCA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,CAAC,EAAA,GAAA;IACL,QAAA,eAAA,UAAA,GAAX,mBAgBM,OAAA;;KAhBmB,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,SAAA,CAAA;0BACnC,mBAcW,UAAA,MAAA,WAd6B,WAAA,QAAtB,OAAO,eAAU;6DAAuB,MAAM,KAAA,GAAA,CAClD,aAAU,KAAA,UAAA,GAAtB,mBAAgE,QAAA;;MAAnC,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,iBAAA,CAAA;wEACzC,mBAWS,UAAA,MAAA,WAVQ,MAAM,QAAd,SAAI;0BADb,mBAWS,UAAA;OATN,KAAK,KAAK,KAAK;OAChB,MAAK;OACJ,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,MAAA,CAAA;OACZ,UAAU,KAAK;OACf,OAAO,KAAK,KAAK,WAAW,KAAK,KAAK;OACtC,UAAK,WAAE,gBAAgB,KAAK,IAAI;UAEK,KAAK,KAAK,QAAA,UAAA,GAAhD,YAAoF,wBAApE,KAAK,KAAK,IAAI,GAAA;;OAAyB,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,WAAA,CAAA;kEACnE,mBAAkC,QAAA,MAAA,gBAAzB,KAAK,KAAK,KAAK,GAAA,CAAA,CAAA,GAAA,IAAA,UAAA;;;IAK9B,mBAA4C,OAAA;cAAnC;KAAJ,KAAI;KAAW,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,MAAA,CAAA;;IAErB,QAAA,YAAA,UAAA,GAAX,mBAUM,OAAA;;KAVgB,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,MAAA,CAAA;0BAChC,mBAQS,UAAA,MAAA,WAPkB,UAAA,QAAjB,OAAO,UAAK;yBADtB,mBAQS,UAAA;MANN,KAAK,MAAM;MACZ,MAAK;MACJ,OAAK,eAAA,CAAG,MAAA,GAAA,CAAG,CAAC,EAAC,KAAA,GAAS,MAAA,GAAA,CAAG,CAAC,GAAE,UAAW,UAAU,YAAA,KAAW,CAAA,CAAA;MAC5D,UAAK,WAAE,eAAe,KAAK;wBAEzB,MAAM,IAAI,GAAA,IAAA,UAAA"}
@@ -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