@tita-agent/dsh-data-table 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -2
  3. package/cordis.patch.yml +7 -0
  4. package/lib/client.js +16766 -0
  5. package/lib/contract-envelope.d.ts +91 -0
  6. package/lib/contract-envelope.js +106 -0
  7. package/lib/contract-layout.d.ts +130 -0
  8. package/lib/contract-layout.js +138 -0
  9. package/lib/contract-refs.d.ts +39 -0
  10. package/lib/contract-refs.js +9 -0
  11. package/lib/contract-version.d.ts +311 -0
  12. package/lib/contract-version.js +270 -0
  13. package/lib/contract-wire.d.ts +147 -0
  14. package/lib/contract-wire.js +99 -0
  15. package/lib/contracts.d.ts +12 -0
  16. package/lib/contracts.js +12 -0
  17. package/lib/types/audit.d.ts +42 -0
  18. package/lib/types/audit.js +39 -0
  19. package/lib/types/client/ProjectImportMenu.d.ts +30 -0
  20. package/lib/types/client/ProjectImportMenu.js +70 -0
  21. package/lib/types/client/SheetPanel.d.ts +33 -0
  22. package/lib/types/client/SheetPanel.js +1766 -0
  23. package/lib/types/client/SheetToggle.d.ts +18 -0
  24. package/lib/types/client/SheetToggle.js +26 -0
  25. package/lib/types/client/index.d.ts +15 -0
  26. package/lib/types/client/index.js +110 -0
  27. package/lib/types/client/persistence/changes.d.ts +15 -0
  28. package/lib/types/client/persistence/changes.js +51 -0
  29. package/lib/types/client/persistence/ids.d.ts +34 -0
  30. package/lib/types/client/persistence/ids.js +48 -0
  31. package/lib/types/client/persistence/merge.d.ts +35 -0
  32. package/lib/types/client/persistence/merge.js +362 -0
  33. package/lib/types/client/store.d.ts +61 -0
  34. package/lib/types/client/store.js +152 -0
  35. package/lib/types/client/surface-context.d.ts +27 -0
  36. package/lib/types/client/surface-context.js +104 -0
  37. package/lib/types/client/surface-control.d.ts +2 -0
  38. package/lib/types/client/surface-control.js +12 -0
  39. package/lib/types/client/transport/workbookApi.d.ts +159 -0
  40. package/lib/types/client/transport/workbookApi.js +246 -0
  41. package/lib/types/client/univer/StandaloneWorkbookPage.d.ts +5 -0
  42. package/lib/types/client/univer/StandaloneWorkbookPage.js +7 -0
  43. package/lib/types/client/univer/WorkbookCanvas.d.ts +40 -0
  44. package/lib/types/client/univer/WorkbookCanvas.js +87 -0
  45. package/lib/types/client/univer/WorkbookView.d.ts +3 -0
  46. package/lib/types/client/univer/WorkbookView.js +2 -0
  47. package/lib/types/client/univer/loader.d.ts +18 -0
  48. package/lib/types/client/univer/loader.js +81 -0
  49. package/lib/types/client/univer/standalone-entry.d.ts +1 -0
  50. package/lib/types/client/univer/standalone-entry.js +19 -0
  51. package/lib/types/client/versions/VersionChrome.d.ts +33 -0
  52. package/lib/types/client/versions/VersionChrome.js +21 -0
  53. package/lib/types/client/versions/inspect.d.ts +70 -0
  54. package/lib/types/client/versions/inspect.js +88 -0
  55. package/lib/types/client/versions/preview-decoration.d.ts +11 -0
  56. package/lib/types/client/versions/preview-decoration.js +54 -0
  57. package/lib/types/client/versions/records.d.ts +19 -0
  58. package/lib/types/client/versions/records.js +70 -0
  59. package/lib/types/client/workspace-host.d.ts +61 -0
  60. package/lib/types/client/workspace-host.js +124 -0
  61. package/lib/types/collaboration-hub.d.ts +31 -0
  62. package/lib/types/collaboration-hub.js +69 -0
  63. package/lib/types/contract/ids.d.ts +24 -0
  64. package/lib/types/contract/ids.js +46 -0
  65. package/lib/types/contract/mutations.d.ts +25 -0
  66. package/lib/types/contract/mutations.js +782 -0
  67. package/lib/types/contract/operations.d.ts +54 -0
  68. package/lib/types/contract/operations.js +203 -0
  69. package/lib/types/contract/snapshots.d.ts +176 -0
  70. package/lib/types/contract/snapshots.js +351 -0
  71. package/lib/types/contract/univer-api-catalog.d.ts +29 -0
  72. package/lib/types/contract/univer-api-catalog.js +264 -0
  73. package/lib/types/errors.d.ts +8 -0
  74. package/lib/types/errors.js +12 -0
  75. package/lib/types/http/assets-routes.d.ts +3 -0
  76. package/lib/types/http/assets-routes.js +169 -0
  77. package/lib/types/http/collaboration.d.ts +10 -0
  78. package/lib/types/http/collaboration.js +154 -0
  79. package/lib/types/http/history.d.ts +15 -0
  80. package/lib/types/http/history.js +134 -0
  81. package/lib/types/http/project-files.d.ts +79 -0
  82. package/lib/types/http/project-files.js +206 -0
  83. package/lib/types/http/project-scope.d.ts +50 -0
  84. package/lib/types/http/project-scope.js +52 -0
  85. package/lib/types/http/routes.d.ts +98 -0
  86. package/lib/types/http/routes.js +323 -0
  87. package/lib/types/http/standalone.d.ts +9 -0
  88. package/lib/types/http/standalone.js +31 -0
  89. package/lib/types/http/trunk.d.ts +10 -0
  90. package/lib/types/http/trunk.js +322 -0
  91. package/lib/types/http/worktree.d.ts +9 -0
  92. package/lib/types/http/worktree.js +145 -0
  93. package/lib/types/index.d.ts +18 -0
  94. package/lib/types/index.js +156 -0
  95. package/lib/types/invariant.d.ts +15 -0
  96. package/lib/types/invariant.js +15 -0
  97. package/lib/types/request-params.d.ts +21 -0
  98. package/lib/types/request-params.js +76 -0
  99. package/lib/types/store/paths.d.ts +68 -0
  100. package/lib/types/store/paths.js +148 -0
  101. package/lib/types/store/project.d.ts +88 -0
  102. package/lib/types/store/project.js +120 -0
  103. package/lib/types/store/read.d.ts +110 -0
  104. package/lib/types/store/read.js +404 -0
  105. package/lib/types/store/retention.d.ts +27 -0
  106. package/lib/types/store/retention.js +111 -0
  107. package/lib/types/store/write.d.ts +22 -0
  108. package/lib/types/store/write.js +210 -0
  109. package/lib/types/tools/arguments.d.ts +40 -0
  110. package/lib/types/tools/arguments.js +130 -0
  111. package/lib/types/tools/cells.d.ts +50 -0
  112. package/lib/types/tools/cells.js +298 -0
  113. package/lib/types/tools/index.d.ts +2 -0
  114. package/lib/types/tools/index.js +250 -0
  115. package/lib/types/tools/render.d.ts +47 -0
  116. package/lib/types/tools/render.js +290 -0
  117. package/lib/types/tools/univer-api.d.ts +18 -0
  118. package/lib/types/tools/univer-api.js +198 -0
  119. package/lib/types/tools/workbook-catalog.d.ts +3 -0
  120. package/lib/types/tools/workbook-catalog.js +83 -0
  121. package/lib/types/tools/workbook-edit.d.ts +192 -0
  122. package/lib/types/tools/workbook-edit.js +912 -0
  123. package/lib/types/tools/workbook-find.d.ts +11 -0
  124. package/lib/types/tools/workbook-find.js +89 -0
  125. package/lib/types/tools/workbook-history.d.ts +2 -0
  126. package/lib/types/tools/workbook-history.js +59 -0
  127. package/lib/types/tools/workbook-read.d.ts +21 -0
  128. package/lib/types/tools/workbook-read.js +204 -0
  129. package/lib/types/types.d.ts +377 -0
  130. package/lib/types/types.js +34 -0
  131. package/lib/types/univer/apply-operation.d.ts +14 -0
  132. package/lib/types/univer/apply-operation.js +398 -0
  133. package/lib/types/univer/chunk.d.ts +68 -0
  134. package/lib/types/univer/chunk.js +16 -0
  135. package/lib/types/univer/decoration.d.ts +34 -0
  136. package/lib/types/univer/decoration.js +88 -0
  137. package/lib/types/univer/diagnostics.d.ts +31 -0
  138. package/lib/types/univer/diagnostics.js +141 -0
  139. package/lib/types/univer/engine.d.ts +33 -0
  140. package/lib/types/univer/engine.js +338 -0
  141. package/lib/types/univer/entry.d.ts +8 -0
  142. package/lib/types/univer/entry.js +11 -0
  143. package/lib/types/univer/facade.d.ts +107 -0
  144. package/lib/types/univer/facade.js +1 -0
  145. package/lib/types/univer/fonts.d.ts +202 -0
  146. package/lib/types/univer/fonts.js +140 -0
  147. package/lib/types/univer/plugins/collaboration.d.ts +23 -0
  148. package/lib/types/univer/plugins/collaboration.js +47 -0
  149. package/lib/types/univer/plugins/export.d.ts +39 -0
  150. package/lib/types/univer/plugins/export.js +246 -0
  151. package/lib/types/univer/plugins/history.d.ts +24 -0
  152. package/lib/types/univer/plugins/history.js +50 -0
  153. package/lib/types/univer/plugins/version.d.ts +13 -0
  154. package/lib/types/univer/plugins/version.js +13 -0
  155. package/lib/types/version/diff.d.ts +36 -0
  156. package/lib/types/version/diff.js +156 -0
  157. package/lib/types/version/grouping.d.ts +7 -0
  158. package/lib/types/version/grouping.js +94 -0
  159. package/lib/types/version/records.d.ts +17 -0
  160. package/lib/types/version/records.js +102 -0
  161. package/lib/types/version/session.d.ts +36 -0
  162. package/lib/types/version/session.js +89 -0
  163. package/lib/types/version/summary.d.ts +24 -0
  164. package/lib/types/version/summary.js +111 -0
  165. package/lib/types/workbook-manager.d.ts +191 -0
  166. package/lib/types/workbook-manager.js +935 -0
  167. package/lib/types/xlsx-export-worker.d.ts +5 -0
  168. package/lib/types/xlsx-export-worker.js +24 -0
  169. package/lib/types/xlsx-export.d.ts +21 -0
  170. package/lib/types/xlsx-export.js +371 -0
  171. package/lib/types/xlsx-import-worker.d.ts +1 -0
  172. package/lib/types/xlsx-import-worker.js +19 -0
  173. package/lib/univer-client.js +690 -0
  174. package/lib/workbook-page.js +23826 -0
  175. package/lib/xlsx-export-worker.js +104 -0
  176. package/lib/xlsx-import-worker.js +46 -0
  177. package/package.json +106 -3
  178. package/src/audit.ts +58 -0
  179. package/src/client/ProjectImportMenu.tsx +100 -0
  180. package/src/client/SheetPanel.module.css +1578 -0
  181. package/src/client/SheetPanel.tsx +1934 -0
  182. package/src/client/SheetToggle.module.css +73 -0
  183. package/src/client/SheetToggle.tsx +65 -0
  184. package/src/client/contract.d.ts +19 -0
  185. package/src/client/index.ts +131 -0
  186. package/src/client/persistence/changes.ts +58 -0
  187. package/src/client/persistence/ids.ts +60 -0
  188. package/src/client/persistence/merge.ts +392 -0
  189. package/src/client/store.ts +188 -0
  190. package/src/client/surface-context.ts +123 -0
  191. package/src/client/surface-control.ts +13 -0
  192. package/src/client/transport/workbookApi.ts +355 -0
  193. package/src/client/univer/SheetView.module.css +110 -0
  194. package/src/client/univer/StandaloneWorkbookPage.module.css +8 -0
  195. package/src/client/univer/StandaloneWorkbookPage.tsx +15 -0
  196. package/src/client/univer/WorkbookCanvas.tsx +128 -0
  197. package/src/client/univer/WorkbookView.module.css +30 -0
  198. package/src/client/univer/WorkbookView.tsx +3 -0
  199. package/src/client/univer/loader.ts +90 -0
  200. package/src/client/univer/react-dom-client.d.ts +10 -0
  201. package/src/client/univer/standalone-entry.tsx +19 -0
  202. package/src/client/univer/standalone.css +12 -0
  203. package/src/client/univer/univer-locales.d.ts +89 -0
  204. package/src/client/univer/univer-modules.d.ts +26 -0
  205. package/src/client/versions/VersionChrome.tsx +114 -0
  206. package/src/client/versions/inspect.ts +138 -0
  207. package/src/client/versions/preview-decoration.ts +54 -0
  208. package/src/client/versions/records.ts +78 -0
  209. package/src/client/workspace-host.ts +147 -0
  210. package/src/collaboration-hub.ts +83 -0
  211. package/src/contract/ids.ts +50 -0
  212. package/src/contract/mutations.ts +791 -0
  213. package/src/contract/operations.ts +221 -0
  214. package/src/contract/snapshots.ts +383 -0
  215. package/src/contract/univer-api-catalog.ts +289 -0
  216. package/src/css-modules.d.ts +6 -0
  217. package/src/errors.ts +25 -0
  218. package/src/http/assets-routes.ts +177 -0
  219. package/src/http/collaboration.ts +144 -0
  220. package/src/http/history.ts +151 -0
  221. package/src/http/project-files.ts +227 -0
  222. package/src/http/project-scope.ts +77 -0
  223. package/src/http/routes.ts +364 -0
  224. package/src/http/standalone.ts +32 -0
  225. package/src/http/trunk.ts +322 -0
  226. package/src/http/worktree.ts +119 -0
  227. package/src/index.ts +165 -0
  228. package/src/invariant.ts +17 -0
  229. package/src/request-params.ts +92 -0
  230. package/src/store/paths.ts +165 -0
  231. package/src/store/project.ts +137 -0
  232. package/src/store/read.ts +454 -0
  233. package/src/store/retention.ts +106 -0
  234. package/src/store/write.ts +230 -0
  235. package/src/tools/arguments.ts +128 -0
  236. package/src/tools/cells.ts +298 -0
  237. package/src/tools/index.ts +274 -0
  238. package/src/tools/render.ts +301 -0
  239. package/src/tools/univer-api.ts +243 -0
  240. package/src/tools/workbook-catalog.ts +94 -0
  241. package/src/tools/workbook-edit.ts +987 -0
  242. package/src/tools/workbook-find.ts +80 -0
  243. package/src/tools/workbook-history.ts +63 -0
  244. package/src/tools/workbook-read.ts +210 -0
  245. package/src/types.ts +432 -0
  246. package/src/univer/apply-operation.ts +369 -0
  247. package/src/univer/chunk.ts +77 -0
  248. package/src/univer/decoration.ts +111 -0
  249. package/src/univer/diagnostics.ts +136 -0
  250. package/src/univer/engine.ts +370 -0
  251. package/src/univer/entry.ts +14 -0
  252. package/src/univer/facade.ts +91 -0
  253. package/src/univer/fonts.ts +148 -0
  254. package/src/univer/overrides.css +33 -0
  255. package/src/univer/plugins/collaboration.ts +47 -0
  256. package/src/univer/plugins/export.ts +236 -0
  257. package/src/univer/plugins/history.ts +49 -0
  258. package/src/univer/plugins/version.ts +13 -0
  259. package/src/version/diff.ts +169 -0
  260. package/src/version/grouping.ts +96 -0
  261. package/src/version/records.ts +113 -0
  262. package/src/version/session.ts +90 -0
  263. package/src/version/summary.ts +117 -0
  264. package/src/workbook-manager.ts +1111 -0
  265. package/src/xlsx-export-worker.ts +34 -0
  266. package/src/xlsx-export.ts +373 -0
  267. package/src/xlsx-import-worker.ts +30 -0
@@ -0,0 +1,369 @@
1
+ /**
2
+ * Applying a workbook operation to the live canvas (docs/issue/0022 P4).
3
+ *
4
+ * This is the *canvas* half of an operation: the Host has already decided the
5
+ * operation is durable and recorded it, and this turns it into Univer commands
6
+ * so the browser sees the same result without reloading a snapshot. The two
7
+ * halves share one vocabulary (`contract/mutations.ts` applies an operation to a
8
+ * snapshot) and must agree: a command this file cannot express is a bug the user
9
+ * sees as "my edit did not appear".
10
+ *
11
+ * Built as a factory because it needs the mount's facade and its sheet resolver,
12
+ * and because `applyingRemote` has to be visible to the engine's mutation guard:
13
+ * a remote operation we apply ourselves is not a user edit.
14
+ */
15
+ import { APPEND_HEADER_MISMATCH } from '../contract/operations.ts'
16
+ import { DEFAULT_SHEET_ROWS, DEFAULT_SHEET_COLUMNS, normalizeCellFormula } from '../contract/snapshots.ts'
17
+
18
+ import type { SheetColumn, WorkbookCellStyle, WorkbookCellUpdate, WorkbookOperation, WorkbookOperationPayload } from '../types.ts'
19
+ import type { SheetsApi, SheetResolver, UniverRange, UniverSheet } from './facade.ts'
20
+
21
+
22
+ /** Default sheet extent (Univer's own default), so data lands on a full-size grid. */
23
+
24
+ export interface OperationApplier {
25
+ apply(operation: WorkbookOperation): Promise<void>
26
+ /** True while an operation from elsewhere is being written to the canvas. */
27
+ isApplyingRemote(): boolean
28
+ /** Sheet metadata that only exists in the live canvas (not in the snapshot). */
29
+ liveSheetMetadata: Map<string, unknown>
30
+ }
31
+
32
+ export function createOperationApplier(deps: { sheetsApi: SheetsApi; getSheet: SheetResolver }): OperationApplier {
33
+ const { sheetsApi, getSheet } = deps
34
+ let applyingRemote = false
35
+
36
+ const liveSheetMetadata = new Map<string, unknown>()
37
+
38
+ const coerceAppendValue = (column: SheetColumn, value: unknown): string | number | boolean | null => {
39
+ if (value === undefined || value === null) return null
40
+ if (column.type === 'number' || column.type === 'currency') {
41
+ const numeric = typeof value === 'number' ? value : Number(value)
42
+ return Number.isFinite(numeric) ? numeric : String(value)
43
+ }
44
+ if (column.type === 'boolean') return typeof value === 'boolean' ? value : String(value) === 'true'
45
+ if (column.type === 'date') return value instanceof Date ? value.toISOString().slice(0, 10) : String(value)
46
+ return String(value)
47
+ }
48
+
49
+ /** Append tabular data with Univer's range mutation APIs. */
50
+ const applyAppendRows = (operation: WorkbookOperation, payload: WorkbookOperationPayload): void => {
51
+ const sheetRef = payload.sheetId ?? payload.sheetName
52
+ const columns = payload.columns ?? []
53
+ const rows = payload.rows ?? []
54
+ if (columns.length === 0) throw new Error('至少需要一列')
55
+ const headerRowIndex = payload.headerRowIndex ?? 0
56
+ // Mirrors the Host snapshot check, so a batch can never pass here and fail
57
+ // there. A report sheet whose real header is not row 0 is addressed
58
+ // explicitly instead of being rejected.
59
+ const verifyHeader = payload.verifyHeader !== false
60
+
61
+ let sheet = getSheet(sheetRef)
62
+ if (sheet === null) {
63
+ if (sheetRef === undefined) throw new Error('Univer sheet 不存在')
64
+ // Keep an implicitly created sheet addressable by the same stable
65
+ // reference that the Host snapshot uses. This matters when an append is
66
+ // followed by another command in the same Agent batch.
67
+ const generatedSheetId = sheetRef ?? `sheet-${operation.opId.slice(0, 12)}`
68
+ const activeWorkbook = sheetsApi.getActiveWorkbook()
69
+ if (activeWorkbook === null) throw new Error('Univer workbook 未挂载')
70
+ activeWorkbook.create(payload.name ?? sheetRef ?? 'Sheet', DEFAULT_SHEET_ROWS, DEFAULT_SHEET_COLUMNS, { sheet: { id: generatedSheetId } })
71
+ sheet = getSheet(generatedSheetId) ?? getSheet(sheetRef)
72
+ }
73
+ if (sheet === null || sheet.getRange === undefined) throw new Error('Univer sheet 创建失败')
74
+
75
+ const headerRange = sheet.getRange(headerRowIndex, 0, 1, columns.length)
76
+ const existingHeader = columns.map((_column, index) => sheet?.getRange?.(headerRowIndex, index).getValue?.())
77
+ const hasExistingHeader = existingHeader.some(value => value !== undefined && value !== null && String(value).length > 0)
78
+ if (verifyHeader && hasExistingHeader && columns.some((column, index) => String(existingHeader[index] ?? '') !== column.label)) {
79
+ throw new Error(APPEND_HEADER_MISMATCH)
80
+ }
81
+ if (!hasExistingHeader) headerRange.setValues([columns.map(column => column.label)])
82
+
83
+ const nextRow = Math.max(headerRowIndex + 1, (sheet.getLastRow?.() ?? 0) + 1)
84
+ if (rows.length > 0) {
85
+ const values = rows.map(row => columns.map(column => coerceAppendValue(column, row[column.key])))
86
+ sheet.getRange(nextRow, 0, values.length, columns.length).setValues(values)
87
+ }
88
+ if (sheet.getMaxColumns?.() !== undefined && sheet.setColumnCount !== undefined) {
89
+ sheet.setColumnCount(Math.max(sheet.getMaxColumns?.() ?? DEFAULT_SHEET_COLUMNS, columns.length))
90
+ }
91
+ if (sheet.getMaxRows?.() !== undefined && sheet.setRowCount !== undefined) {
92
+ sheet.setRowCount(Math.max(sheet.getMaxRows?.() ?? DEFAULT_SHEET_ROWS, nextRow + rows.length))
93
+ }
94
+ const sheetId = sheet.getSheetId?.()
95
+ if (payload.meta !== undefined && sheetId !== undefined) liveSheetMetadata.set(sheetId, payload.meta)
96
+ }
97
+
98
+ /** Sort through the open-source sort preset's Facade mixin. */
99
+ const applySortRange = (payload: WorkbookOperationPayload): void => {
100
+ const sheet = getSheet(payload.sheetId ?? payload.sheetName)
101
+ const range = payload.range
102
+ const sort = payload.sort
103
+ if (sheet === null || range === undefined || sort === undefined) throw new Error('sort_range 参数无效')
104
+ const target = sheet.getRange(range.startRow, range.startColumn, range.endRow - range.startRow + 1, range.endColumn - range.startColumn + 1)
105
+ if (target.sort === undefined) throw new Error('当前 Univer Sort Facade 不可用')
106
+ target.sort({ column: sort.column - range.startColumn, ascending: sort.direction !== 'desc' })
107
+ }
108
+
109
+ /** Apply the JSON-safe Datasheet style subset through Univer's Facade. */
110
+ const applyCellStyle = async (sheet: UniverSheet, range: UniverRange, target: { startRow: number; startColumn: number; endRow: number; endColumn: number }, style: WorkbookCellStyle): Promise<void> => {
111
+ if (style.bg !== undefined) {
112
+ if (range.setBackgroundColor === undefined) throw new Error('当前 Univer Facade 不支持背景色')
113
+ range.setBackgroundColor(style.bg)
114
+ }
115
+ if (style.cl !== undefined) {
116
+ if (range.setFontColor === undefined) throw new Error('当前 Univer Facade 不支持字体颜色')
117
+ range.setFontColor(style.cl)
118
+ }
119
+ if (style.ff !== undefined) {
120
+ if (range.setFontFamily === undefined) throw new Error('当前 Univer Facade 不支持字体')
121
+ range.setFontFamily(style.ff)
122
+ }
123
+ if (style.fs !== undefined) {
124
+ if (range.setFontSize === undefined) throw new Error('当前 Univer Facade 不支持字号')
125
+ range.setFontSize(style.fs)
126
+ }
127
+ if (style.bl !== undefined) {
128
+ if (range.setFontWeight === undefined) throw new Error('当前 Univer Facade 不支持粗体')
129
+ range.setFontWeight(style.bl ? 'bold' : 'normal')
130
+ }
131
+ if (style.it !== undefined) {
132
+ if (range.setFontStyle === undefined) throw new Error('当前 Univer Facade 不支持斜体')
133
+ range.setFontStyle(style.it ? 'italic' : 'normal')
134
+ }
135
+ if (style.ul !== undefined) {
136
+ if (range.setFontLine === undefined) throw new Error('当前 Univer Facade 不支持下划线')
137
+ range.setFontLine(style.ul ? 'underline' : 'none')
138
+ }
139
+ if (style.st !== undefined) {
140
+ if (range.setFontLine === undefined) throw new Error('当前 Univer Facade 不支持删除线')
141
+ range.setFontLine(style.st ? 'line-through' : 'none')
142
+ }
143
+ if (style.ht !== undefined) {
144
+ if (range.setHorizontalAlignment === undefined) throw new Error('当前 Univer Facade 不支持水平对齐')
145
+ const alignment = style.ht === 1 ? 'left' : style.ht === 2 ? 'center' : 'normal'
146
+ range.setHorizontalAlignment(alignment)
147
+ }
148
+ if (style.vt !== undefined) {
149
+ if (range.setVerticalAlignment === undefined) throw new Error('当前 Univer Facade 不支持垂直对齐')
150
+ const alignment = style.vt === 1 ? 'top' : style.vt === 2 ? 'middle' : 'bottom'
151
+ range.setVerticalAlignment(alignment)
152
+ }
153
+ if (style.tr !== undefined) {
154
+ if (range.setTextRotation === undefined) throw new Error('当前 Univer Facade 不支持文字旋转')
155
+ range.setTextRotation(style.tr)
156
+ }
157
+ // Univer 0.25 exposes number format through the native SetStyleCommand;
158
+ // use the command bridge for this one style that the Facade does not wrap.
159
+ if (style.n !== undefined) {
160
+ await sheetsApi.executeCommand('sheet.command.set-style', {
161
+ unitId: sheetsApi.getActiveWorkbook()?.getId(),
162
+ subUnitId: sheet.getSheetId?.(),
163
+ range: target,
164
+ style: { type: 'n', value: style.n },
165
+ }, { fromCollab: true })
166
+ }
167
+ }
168
+ const applyOperation = async (operation: WorkbookOperation): Promise<void> => {
169
+ const workbook = sheetsApi.getActiveWorkbook()
170
+ if (workbook === null) throw new Error('Univer workbook 未挂载')
171
+ const payload = operation.payload
172
+ applyingRemote = true
173
+ try {
174
+ switch (operation.type) {
175
+ case 'update_cells': {
176
+ const sheet = getSheet(payload.sheetId ?? payload.sheetName)
177
+ if (sheet === null) throw new Error(`Univer sheet 不存在:${payload.sheetId ?? payload.sheetName ?? ''}`)
178
+ const cells = payload.cells ?? []
179
+ if (cells.length === 0) return
180
+ const unitId = workbook.getId()
181
+ const sheetId = sheet.getSheetId?.()
182
+ if (sheetId === undefined) throw new Error('Univer sheet id 缺失')
183
+ const value: Record<string, Record<string, { v: WorkbookCellUpdate['value']; f?: string }>> = {}
184
+ const clearCells: WorkbookCellUpdate[] = []
185
+ for (const cell of cells) {
186
+ // The same normalization the snapshot applies: the canvas and the
187
+ // stored snapshot are two views of one workbook.
188
+ const formula = normalizeCellFormula(cell.formula)
189
+ if (cell.value === null && formula === undefined) {
190
+ clearCells.push(cell)
191
+ continue
192
+ }
193
+ value[String(cell.row)] ??= {}
194
+ value[String(cell.row)][String(cell.column)] = { v: cell.value, ...(formula === undefined ? {} : { f: formula }) }
195
+ }
196
+ if (Object.keys(value).length > 0) {
197
+ await sheetsApi.executeCommand('sheet.command.set-range-values', { unitId, subUnitId: sheetId, value }, { fromCollab: true })
198
+ }
199
+ // A null value is a content clear rather than a value assignment in
200
+ // Univer. Keep clears grouped after the fast sparse value command.
201
+ for (const cell of clearCells) sheet.getRange(cell.row, cell.column).clearContent()
202
+ return
203
+ }
204
+ case 'clear_range': {
205
+ const sheet = getSheet(payload.sheetId ?? payload.sheetName)
206
+ const range = payload.range
207
+ if (sheet === null || range === undefined) throw new Error('clear_range 参数无效')
208
+ sheet.getRange(range.startRow, range.startColumn, range.endRow - range.startRow + 1, range.endColumn - range.startColumn + 1).clearContent()
209
+ return
210
+ }
211
+ case 'set_hyperlink': {
212
+ const sheet = getSheet(payload.sheetId ?? payload.sheetName)
213
+ const range = payload.range
214
+ if (sheet === null || range === undefined || payload.url === undefined) throw new Error('set_hyperlink 参数无效')
215
+ const target = sheet.getRange(range.startRow, range.startColumn, range.endRow - range.startRow + 1, range.endColumn - range.startColumn + 1)
216
+ if (target.setHyperLink === undefined) throw new Error('当前 Univer HyperLink Facade 不可用')
217
+ const current = target.getValue?.()
218
+ const label = payload.label ?? (current === undefined || current === null || String(current).length === 0 ? undefined : String(current))
219
+ await target.setHyperLink(payload.url, label)
220
+ return
221
+ }
222
+ case 'remove_hyperlink': {
223
+ const sheet = getSheet(payload.sheetId ?? payload.sheetName)
224
+ const range = payload.range
225
+ if (sheet === null || range === undefined) throw new Error('remove_hyperlink 参数无效')
226
+ // DSH's operation contract mirrors set_hyperlink and targets only the
227
+ // top-left cell. A multi-cell range must not remove unrelated links.
228
+ const target = sheet.getRange(range.startRow, range.startColumn)
229
+ if (target.cancelHyperLink === undefined) throw new Error('当前 Univer HyperLink Facade 不可用')
230
+ target.cancelHyperLink()
231
+ return
232
+ }
233
+ case 'delete_cells': {
234
+ const sheet = getSheet(payload.sheetId ?? payload.sheetName)
235
+ const range = payload.range
236
+ const shift = payload.shift ?? 'clear'
237
+ if (sheet === null || range === undefined) throw new Error('delete_cells 参数无效')
238
+ const target = sheet.getRange(range.startRow, range.startColumn, range.endRow - range.startRow + 1, range.endColumn - range.startColumn + 1)
239
+ if (shift === 'clear') {
240
+ target.clearContent()
241
+ return
242
+ }
243
+ if (target.deleteCells === undefined) throw new Error('当前 Univer Facade 不支持结构性删除单元格')
244
+ target.deleteCells(shift === 'left' ? 0 : 1)
245
+ return
246
+ }
247
+ case 'insert_rows': {
248
+ const sheet = getSheet(payload.sheetId ?? payload.sheetName)
249
+ if (sheet?.insertRows === undefined || payload.range === undefined) throw new Error('insert_rows 参数无效')
250
+ sheet.insertRows(payload.range.startRow, payload.count ?? 1)
251
+ return
252
+ }
253
+ case 'delete_rows': {
254
+ const sheet = getSheet(payload.sheetId ?? payload.sheetName)
255
+ if (sheet?.deleteRows === undefined || payload.range === undefined) throw new Error('delete_rows 参数无效')
256
+ sheet.deleteRows(payload.range.startRow, payload.count ?? 1)
257
+ return
258
+ }
259
+ case 'insert_columns': {
260
+ const sheet = getSheet(payload.sheetId ?? payload.sheetName)
261
+ if (sheet?.insertColumns === undefined || payload.range === undefined) throw new Error('insert_columns 参数无效')
262
+ sheet.insertColumns(payload.range.startColumn, payload.count ?? 1)
263
+ return
264
+ }
265
+ case 'delete_columns': {
266
+ const sheet = getSheet(payload.sheetId ?? payload.sheetName)
267
+ if (sheet?.deleteColumns === undefined || payload.range === undefined) throw new Error('delete_columns 参数无效')
268
+ sheet.deleteColumns(payload.range.startColumn, payload.count ?? 1)
269
+ return
270
+ }
271
+ case 'create_sheet':
272
+ workbook.create(payload.name ?? payload.sheetId ?? 'Sheet', DEFAULT_SHEET_ROWS, DEFAULT_SHEET_COLUMNS, {
273
+ sheet: { id: payload.sheetId ?? `sheet-${operation.opId.slice(0, 12)}` },
274
+ })
275
+ return
276
+ case 'rename_sheet': {
277
+ const sheet = getSheet(payload.sheetId ?? payload.sheetName)
278
+ if (sheet?.setName === undefined || payload.name === undefined) throw new Error('rename_sheet 参数无效')
279
+ sheet.setName(payload.name)
280
+ return
281
+ }
282
+ case 'delete_sheet': {
283
+ if (workbook.deleteSheet === undefined) throw new Error('当前 Univer Facade 不支持删除 sheet')
284
+ const sheet = getSheet(payload.sheetId ?? payload.sheetName)
285
+ if (sheet === null) throw new Error(`Univer sheet 不存在:${payload.sheetId ?? payload.sheetName ?? ''}`)
286
+ // Pass the Facade sheet object so a name-based Agent operation is
287
+ // resolved to the actual sheet id before Univer executes the command.
288
+ workbook.deleteSheet(sheet)
289
+ return
290
+ }
291
+ case 'format_cells': {
292
+ const sheet = getSheet(payload.sheetId ?? payload.sheetName)
293
+ const range = payload.range
294
+ if (sheet === null || range === undefined || payload.style === undefined) throw new Error('format_cells 参数无效')
295
+ const target = sheet.getRange(range.startRow, range.startColumn, range.endRow - range.startRow + 1, range.endColumn - range.startColumn + 1)
296
+ await applyCellStyle(sheet, target, range, payload.style)
297
+ return
298
+ }
299
+ case 'merge_cells': {
300
+ const sheet = getSheet(payload.sheetId ?? payload.sheetName)
301
+ const range = payload.range ?? payload.merge
302
+ if (sheet === null || range === undefined) throw new Error('merge_cells 参数无效')
303
+ const target = sheet.getRange(range.startRow, range.startColumn, range.endRow - range.startRow + 1, range.endColumn - range.startColumn + 1)
304
+ if (target.merge === undefined) throw new Error('当前 Univer Facade 不支持合并单元格')
305
+ target.merge({ defaultMerge: true, isForceMerge: true })
306
+ return
307
+ }
308
+ case 'unmerge_cells': {
309
+ const sheet = getSheet(payload.sheetId ?? payload.sheetName)
310
+ const range = payload.range ?? payload.merge
311
+ if (sheet === null || range === undefined) throw new Error('unmerge_cells 参数无效')
312
+ const target = sheet.getRange(range.startRow, range.startColumn, range.endRow - range.startRow + 1, range.endColumn - range.startColumn + 1)
313
+ if (target.breakApart === undefined) throw new Error('当前 Univer Facade 不支持取消合并')
314
+ target.breakApart()
315
+ return
316
+ }
317
+ case 'freeze_panes': {
318
+ const sheet = getSheet(payload.sheetId ?? payload.sheetName)
319
+ const freeze = payload.freeze
320
+ if (sheet === null || freeze === undefined || sheet.setFreeze === undefined || sheet.cancelFreeze === undefined) throw new Error('freeze_panes 参数无效')
321
+ const rows = Math.max(0, freeze.rows ?? 0)
322
+ const columns = Math.max(0, freeze.columns ?? 0)
323
+ if (rows === 0 && columns === 0) sheet.cancelFreeze()
324
+ else sheet.setFreeze({ startRow: rows, startColumn: columns, xSplit: columns, ySplit: rows })
325
+ return
326
+ }
327
+ case 'set_column_width': {
328
+ const sheet = getSheet(payload.sheetId ?? payload.sheetName)
329
+ const index = payload.range?.startColumn
330
+ if (sheet === null || index === undefined || payload.size === undefined || sheet.setColumnWidth === undefined) throw new Error('set_column_width 参数无效')
331
+ sheet.setColumnWidth(index, payload.size)
332
+ return
333
+ }
334
+ case 'set_row_height': {
335
+ const sheet = getSheet(payload.sheetId ?? payload.sheetName)
336
+ const index = payload.range?.startRow
337
+ if (sheet === null || index === undefined || payload.size === undefined || sheet.setRowHeight === undefined) throw new Error('set_row_height 参数无效')
338
+ sheet.setRowHeight(index, payload.size)
339
+ return
340
+ }
341
+ case 'append_rows':
342
+ applyAppendRows(operation, payload)
343
+ return
344
+ case 'sort_range':
345
+ applySortRange(payload)
346
+ return
347
+ case 'batch': {
348
+ for (const child of payload.operations ?? []) {
349
+ if (child.type === 'batch' || child.type === 'snapshot_replace') throw new Error('batch 不支持嵌套 batch 或 snapshot_replace')
350
+ // Re-enable the guard after each recursive call. The child call
351
+ // owns the same operation adapter but resets the guard in finally.
352
+ applyingRemote = true
353
+ await applyOperation({ ...operation, type: child.type, payload: child.payload })
354
+ }
355
+ return
356
+ }
357
+ case 'snapshot_replace':
358
+ // An arbitrary snapshot has no equivalent single Univer command.
359
+ throw new Error(`operation ${operation.type} 需要重新加载 snapshot`)
360
+ default:
361
+ throw new Error(`未知 workbook operation:${String(operation.type)}`)
362
+ }
363
+ } finally {
364
+ applyingRemote = false
365
+ }
366
+ }
367
+
368
+ return { apply: applyOperation, isApplyingRemote: () => applyingRemote, liveSheetMetadata }
369
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The engine-chunk contract, declared once (docs/issue/0002 PRD v3.2 §7.2).
3
+ *
4
+ * Two bundles have to agree on this shape without importing each other:
5
+ * `src/univer/entry.ts` builds the ~16MB Univer IIFE, and
6
+ * `src/client/univer/loader.ts` loads it lazily on first sheet render. Keeping the interface *and* the
7
+ * `window` global here means a signature change cannot be made on one side only
8
+ * — which is exactly how the preview's `readOnly` option would otherwise have
9
+ * been silently dropped by the loader.
10
+ *
11
+ * Type-only: this module emits no runtime code, so it is erased from both
12
+ * bundles and cannot pull Univer into the boot bundle.
13
+ *
14
+ * @module dsh-data-table/univer/chunk
15
+ */
16
+
17
+ import type { WorkbookFocusRange, WorkbookLocalChange, WorkbookOperation, WorkbookPresencePayload, WorkbookSelectionRange } from '../types.ts'
18
+
19
+ /** Mounted Univer residency: dispose releases the engine. */
20
+ export interface WorkbookMount {
21
+ save(): unknown
22
+ applyOperation(operation: WorkbookOperation): Promise<void>
23
+ /** Focus one cell range in the active workbook. */
24
+ focusRange(range: WorkbookFocusRange): boolean
25
+ /**
26
+ * Show a different workbook in the same residency (same Univer instance, new
27
+ * unit). Returns false when the swap was impossible, so the caller can fall
28
+ * back to a full remount. PRD v3.5 §7.2.
29
+ */
30
+ swapWorkbook(next: object, options?: { decoration?: WorkbookDecoration }): boolean
31
+ dispose(): void
32
+ }
33
+
34
+ /** Which cells a preview paints, and why (docs/issue/0002 PRD v3.3 §7.2). */
35
+ export type WorkbookDecorationTone = 'add' | 'remove' | 'modify'
36
+
37
+ export interface WorkbookDecorationCell {
38
+ sheetRef?: string
39
+ row: number
40
+ column: number
41
+ tone: WorkbookDecorationTone
42
+ }
43
+
44
+ export interface WorkbookDecoration {
45
+ cells: WorkbookDecorationCell[]
46
+ /** Cell to select so the viewport lands on what changed. */
47
+ focus?: { sheetRef?: string; row: number; column: number }
48
+ }
49
+
50
+ /**
51
+ * Mount options forwarded to the engine. `readOnly` mounts a preview residency:
52
+ * the snapshot is a past Version, so the canvas reports no edits, drops the
53
+ * editing chrome and turns Univer's workbook-level edit permission off
54
+ * (PRD v3.2 §7.2). `decoration` is the diff overlay (PRD v3.3 §7.2).
55
+ */
56
+ export interface WorkbookMountOptions {
57
+ readOnly?: boolean
58
+ decoration?: WorkbookDecoration
59
+ }
60
+
61
+ /** The engine surface the chunk exposes. */
62
+ export interface UniverChunkApi {
63
+ mountWorkbook(
64
+ container: HTMLElement,
65
+ workbookData: object,
66
+ onDirty?: (change?: WorkbookLocalChange) => void,
67
+ onPresence?: (presence: WorkbookPresencePayload) => void,
68
+ options?: WorkbookMountOptions,
69
+ onSelection?: (ranges: readonly WorkbookSelectionRange[]) => void,
70
+ ): WorkbookMount
71
+ }
72
+
73
+ declare global {
74
+ interface Window {
75
+ __bestExcelUniver?: UniverChunkApi
76
+ }
77
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * The diff overlay and the read-only lock (docs/issue/0022 P4, PRD v3.3 §7.2).
3
+ *
4
+ * Order matters twice over: the tint is written through `SetStyleCommand`, which
5
+ * the workbook's editable permission gates, so it has to land **before**
6
+ * `setEditable(false)`; and the selection has to be made before the lock too, so
7
+ * stepping a Version moves the viewport onto the cell that changed instead of
8
+ * leaving the reader to hunt for it.
9
+ *
10
+ * Fire-and-forget: `mountWorkbook` returns synchronously and the canvas is usable
11
+ * immediately — the lock is a frame or two behind (see {@link lockWorkbook}),
12
+ * and the mutation guard in the engine covers that window.
13
+ *
14
+ * The three tints are the contract with `VersionInspectBar`'s legend: the swatch
15
+ * a reader sees beside `新增 2` is painted with the same hex the engine writes
16
+ * into the cell.
17
+ */
18
+ import type { WorkbookDecoration } from './chunk.ts'
19
+ import type { SheetsApi, SheetResolver } from './facade.ts'
20
+
21
+ /** Tint per change direction; shared with the comparison bar's legend. */
22
+ export const DECORATION_TINT = { add: '#e6f7ee', modify: '#fff5e0', remove: '#fdeceb' } as const
23
+
24
+ export interface DecorationApplier {
25
+ /** Paint a diff and, for a read-only residency, lock the workbook. */
26
+ apply(plan: WorkbookDecoration | undefined): void
27
+ }
28
+
29
+ /**
30
+ * Univer's permission service re-creates the workbook's permission points
31
+ * **after** the mount, with their defaults — so a lock applied at mount is
32
+ * silently undone, and a preview stayed editable (GUI feedback 2026-09-23:
33
+ * "why can I still type into a comparison?").
34
+ *
35
+ * Measured against the real engine, mounted with `readOnly: true`:
36
+ *
37
+ * t=0 setEditable(false) → canEdit() false (the lock lands)
38
+ * t=100ms canEdit() true ← the service re-added it
39
+ * t=200ms setEditable(false) → canEdit() false (and it stays off)
40
+ *
41
+ * `WorkbookPermissionService._init` iterates the existing sheet units and calls
42
+ * `addPermissionPoint` for each point, and `addPermissionPoint` takes the
43
+ * point's *default* value — `true` for `WorkbookEdit`. That write is what undid
44
+ * the mount-time lock. It happens once, in the first ~100ms; the lock is
45
+ * therefore re-asserted a few times over the first second rather than once.
46
+ *
47
+ * The lock is Univer's own workbook permission — the mechanism a protected
48
+ * sheet uses — and with it off the cell editor refuses to open too
49
+ * (`sheet.operation.set-cell-edit-visible` returns false), so a preview cannot
50
+ * be typed into at all, not merely have its edits refused on commit.
51
+ *
52
+ * Delays rather than animation frames: a frame loop stops while the page is
53
+ * hidden, and the point of these attempts is to land *after* a write that
54
+ * happens on Univer's own schedule.
55
+ */
56
+ const LOCK_RETRY_DELAYS_MS = [0, 150, 400, 900] as const
57
+
58
+ function lockWorkbook(sheetsApi: SheetsApi): void {
59
+ const attempt = (index: number): void => {
60
+ try {
61
+ sheetsApi.getActiveWorkbook()?.setEditable?.(false)
62
+ } catch {
63
+ // Permission point unavailable: the mutation guard in the engine still
64
+ // refuses to report an edit from a read-only residency.
65
+ return
66
+ }
67
+ const next = index + 1
68
+ if (next < LOCK_RETRY_DELAYS_MS.length) {
69
+ setTimeout(() => attempt(next), LOCK_RETRY_DELAYS_MS[next])
70
+ }
71
+ }
72
+ attempt(0)
73
+ }
74
+
75
+ export function createDecorationApplier(deps: { sheetsApi: SheetsApi; getSheet: SheetResolver; readOnly: boolean }): DecorationApplier {
76
+ const { sheetsApi, getSheet, readOnly } = deps
77
+ return {
78
+ apply: (plan: WorkbookDecoration | undefined): void => {
79
+ void (async () => {
80
+ const workbook = sheetsApi.getActiveWorkbook()
81
+ if (plan !== undefined) {
82
+ for (const cell of plan.cells) {
83
+ // Paint every cell the caller sent: which tones exist in the shown
84
+ // snapshot is the caller's decision, because the same diff has a
85
+ // different paintable side depending on which Version is on screen.
86
+ try {
87
+ await getSheet(cell.sheetRef)?.getRange(cell.row, cell.column)?.setBackgroundColor?.(DECORATION_TINT[cell.tone])
88
+ } catch {
89
+ // A cell outside the sheet's bounds: skip it, keep the rest.
90
+ }
91
+ }
92
+ }
93
+ const focus = plan?.focus
94
+ if (focus !== undefined && workbook !== null) {
95
+ try {
96
+ const sheet = getSheet(focus.sheetRef)
97
+ const range = sheet?.getRange(focus.row, focus.column)
98
+ if (sheet !== null && range !== undefined) {
99
+ const sheetId = sheet.getSheetId?.()
100
+ if (sheetId !== undefined) workbook.setActiveSheet?.(sheetId)
101
+ workbook.setActiveRange?.(range)
102
+ }
103
+ } catch {
104
+ // Selection is an affordance, not a correctness requirement.
105
+ }
106
+ }
107
+ if (readOnly) lockWorkbook(sheetsApi)
108
+ })()
109
+ },
110
+ }
111
+ }