@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,34 @@
1
+ /**
2
+ * On-demand export worker. SheetJS is intentionally isolated from the Univer
3
+ * main thread so large workbook encoding does not freeze scrolling/editing.
4
+ */
5
+
6
+ import { workbookCsvText, workbookXlsxBytes } from './xlsx-export.ts'
7
+
8
+ interface ExportRequest {
9
+ format: 'csv' | 'xlsx'
10
+ snapshot: unknown
11
+ }
12
+
13
+ const workerScope = globalThis as typeof globalThis & {
14
+ onmessage: ((event: MessageEvent<ExportRequest>) => void) | null
15
+ postMessage(message: unknown, transfer?: Transferable[]): void
16
+ }
17
+
18
+ workerScope.onmessage = event => {
19
+ try {
20
+ if (event.data.format === 'csv') {
21
+ const csv = workbookCsvText(event.data.snapshot)
22
+ workerScope.postMessage({ ok: true, format: 'csv', value: csv })
23
+ return
24
+ }
25
+ const bytes = workbookXlsxBytes(event.data.snapshot)
26
+ if (bytes === null) {
27
+ workerScope.postMessage({ ok: true, format: 'xlsx', value: null })
28
+ return
29
+ }
30
+ workerScope.postMessage({ ok: true, format: 'xlsx', value: bytes }, [bytes])
31
+ } catch (error) {
32
+ workerScope.postMessage({ ok: false, error: error instanceof Error ? error.message : String(error) })
33
+ }
34
+ }
@@ -0,0 +1,373 @@
1
+ /**
2
+ * XLSX/CSV exchange adapter for Datasheet.
3
+ *
4
+ * The free Univer presets intentionally do not include Univer PRO's exchange
5
+ * plugin. SheetJS is used here as the file-format adapter, while the stored
6
+ * representation remains a normal Univer snapshot. Values, formulas, sheet
7
+ * names, merges, dimensions, number formats and basic cell styles round-trip
8
+ * without making the host process depend on a browser-only API.
9
+ * @module dsh-data-table/xlsx-export
10
+ */
11
+
12
+ import * as XLSX from 'xlsx'
13
+ import { DEFAULT_SHEET_FONT_FAMILY, DEFAULT_SHEET_ROWS, DEFAULT_SHEET_COLUMNS } from './contract/snapshots.ts'
14
+ import { SHEET_MAX_COLUMNS, SHEET_MAX_PHYSICAL_ROWS, SHEET_MAX_ROWS } from './types.ts'
15
+ import type { WorkbookCellStyle } from './types.ts'
16
+
17
+ interface SnapshotCell {
18
+ v?: unknown
19
+ f?: string
20
+ s?: unknown
21
+ n?: string
22
+ }
23
+
24
+ interface SnapshotSheet {
25
+ id?: string
26
+ name?: string
27
+ rowCount?: number
28
+ columnCount?: number
29
+ cellData?: Record<string, Record<string, SnapshotCell | unknown>>
30
+ mergeData?: unknown
31
+ columnData?: Record<string, unknown>
32
+ rowData?: Record<string, unknown>
33
+ defaultStyle?: unknown
34
+ [key: string]: unknown
35
+ }
36
+
37
+ interface WorkbookSnapshot {
38
+ id?: string
39
+ name?: string
40
+ sheetOrder?: string[]
41
+ sheets?: Record<string, SnapshotSheet>
42
+ styles?: Record<string, unknown>
43
+ }
44
+
45
+ interface SheetEntry {
46
+ id: string
47
+ name: string
48
+ sheet: SnapshotSheet
49
+ }
50
+
51
+ /** Download the first workbook sheet as UTF-8 CSV. */
52
+ export function downloadWorkbookCsv(snapshot: unknown, filenameBase: string): void {
53
+ const csv = workbookCsvText(snapshot)
54
+ if (csv === null) return
55
+ downloadBlob(new Blob([csv], { type: 'text/csv;charset=utf-8' }), `${safeFilename(filenameBase)}.csv`)
56
+ }
57
+
58
+ /** Download all workbook sheets as an XLSX file. */
59
+ export function downloadWorkbookXlsx(snapshot: unknown, filenameBase: string): void {
60
+ const bytes = workbookXlsxBytes(snapshot)
61
+ if (bytes === null) return
62
+ downloadBlob(new Blob([bytes], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }), `${safeFilename(filenameBase)}.xlsx`)
63
+ }
64
+
65
+ /** Encode the first sheet as CSV without touching browser APIs. */
66
+ export function workbookCsvText(snapshot: unknown): string | null {
67
+ const sheet = orderedSheets(snapshot)[0]
68
+ if (sheet === undefined) return null
69
+ const bounds = usedBounds(sheet.sheet)
70
+ const lines: string[] = []
71
+ for (let row = 0; row <= bounds.endRow; row += 1) {
72
+ const values: string[] = []
73
+ for (let column = 0; column <= bounds.endColumn; column += 1) {
74
+ values.push(escapeCsv(displayCellValue(getCell(sheet.sheet, row, column))))
75
+ }
76
+ lines.push(values.join(','))
77
+ }
78
+ return `\uFEFF${lines.join('\n')}`
79
+ }
80
+
81
+ /** Encode all sheets as XLSX without touching browser APIs. */
82
+ export function workbookXlsxBytes(snapshot: unknown): ArrayBuffer | null {
83
+ const workbook = snapshotToXlsx(snapshot)
84
+ if (workbook.SheetNames.length === 0) return null
85
+ return XLSX.write(workbook, { bookType: 'xlsx', type: 'array', cellStyles: true, compression: true }) as ArrayBuffer
86
+ }
87
+
88
+ /** Parse an uploaded XLSX file into a Univer-compatible snapshot. */
89
+ export function readWorkbookXlsx(data: ArrayBuffer, filename = 'workbook'): object {
90
+ const workbook = XLSX.read(data, { type: 'array', cellFormula: true, cellStyles: true, cellDates: true, dense: false })
91
+ if (workbook.SheetNames.length === 0) throw new Error('Excel 文件不包含工作表')
92
+ if (workbook.SheetNames.length > 100) throw new Error('Excel 文件超过 Datasheet 上限(最多 100 个工作表)')
93
+ const sheets: Record<string, SnapshotSheet> = {}
94
+ const sheetOrder: string[] = []
95
+ const styles: Record<string, WorkbookCellStyle> = {}
96
+ const styleIds = new Map<string, string>()
97
+ for (const [index, name] of workbook.SheetNames.entries()) {
98
+ const source = workbook.Sheets[name]
99
+ const ref = source['!ref'] ?? 'A1:A1'
100
+ const range = XLSX.utils.decode_range(ref)
101
+ if (range.s.r < 0 || range.s.c < 0 || range.e.r >= SHEET_MAX_PHYSICAL_ROWS || range.e.c >= SHEET_MAX_COLUMNS) throw new Error(`Excel 文件超过 Datasheet 上限(${SHEET_MAX_ROWS} 条数据行 + 表头 × ${SHEET_MAX_COLUMNS} 列)`)
102
+ const id = `sheet${index + 1}`
103
+ const sheet: SnapshotSheet = {
104
+ id,
105
+ name: safeSheetName(name, index),
106
+ rowCount: Math.max(DEFAULT_SHEET_ROWS, range.e.r + 1),
107
+ columnCount: Math.max(DEFAULT_SHEET_COLUMNS, range.e.c + 1),
108
+ defaultStyle: { ff: DEFAULT_SHEET_FONT_FAMILY },
109
+ cellData: {},
110
+ }
111
+ for (let row = range.s.r; row <= range.e.r; row += 1) {
112
+ for (let column = range.s.c; column <= range.e.c; column += 1) {
113
+ const address = XLSX.utils.encode_cell({ r: row, c: column })
114
+ const sourceCell = source[address] as XLSX.CellObject | undefined
115
+ if (sourceCell === undefined) continue
116
+ const cell: SnapshotCell = { v: normalizeImportedValue(sourceCell.v) }
117
+ if (typeof sourceCell.f === 'string' && sourceCell.f.length > 0) cell.f = sourceCell.f
118
+ const style = xlsxStyleToUniver(sourceCell.s, sourceCell.z)
119
+ if (Object.keys(style).length > 0) {
120
+ const signature = stableStyleSignature(style)
121
+ let styleId = styleIds.get(signature)
122
+ if (styleId === undefined) {
123
+ styleId = `xlsx-style-${styleIds.size + 1}`
124
+ styleIds.set(signature, styleId)
125
+ styles[styleId] = style
126
+ }
127
+ cell.s = styleId
128
+ }
129
+ ;(sheet.cellData ??= {})[String(row)] ??= {}
130
+ sheet.cellData[String(row)][String(column)] = cell
131
+ }
132
+ }
133
+ if (Array.isArray(source['!merges'])) sheet.mergeData = source['!merges'].map(merge => ({
134
+ startRow: merge.s.r,
135
+ startColumn: merge.s.c,
136
+ endRow: merge.e.r,
137
+ endColumn: merge.e.c,
138
+ }))
139
+ if (Array.isArray(source['!cols'])) sheet.columnData = Object.fromEntries(source['!cols'].map((column, columnIndex) => [String(columnIndex), column]))
140
+ if (Array.isArray(source['!rows'])) sheet.rowData = Object.fromEntries(source['!rows'].map((row, rowIndex) => [String(rowIndex), row]))
141
+ sheets[id] = sheet
142
+ sheetOrder.push(id)
143
+ }
144
+ return { id: 'imported', name: filename.replace(/\.[^.]+$/, '') || 'workbook', sheetOrder, sheets, ...(Object.keys(styles).length > 0 ? { styles } : {}) }
145
+ }
146
+
147
+ function snapshotToXlsx(snapshot: unknown): XLSX.WorkBook {
148
+ const root = (typeof snapshot === 'object' && snapshot !== null ? snapshot : {}) as WorkbookSnapshot
149
+ const workbook = XLSX.utils.book_new()
150
+ for (const entry of orderedSheets(root)) {
151
+ const worksheet: XLSX.WorkSheet = {}
152
+ const bounds = usedBounds(entry.sheet)
153
+ for (let row = 0; row <= bounds.endRow; row += 1) {
154
+ for (let column = 0; column <= bounds.endColumn; column += 1) {
155
+ const cell = getCell(entry.sheet, row, column)
156
+ if (cell.v === undefined && cell.f === undefined) continue
157
+ const target = XLSX.utils.encode_cell({ r: row, c: column })
158
+ const output: XLSX.CellObject = { v: cell.v === null ? undefined : cell.v as string | number | boolean | Date | undefined, t: cellType(cell.v) }
159
+ if (typeof cell.f === 'string' && cell.f.length > 0) output.f = cell.f.replace(/^=/, '')
160
+ const style = resolveCellStyle(root, cell)
161
+ const styleNumberFormat = typeof style?.n === 'string' ? style.n : undefined
162
+ if (typeof cell.n === 'string') output.z = cell.n
163
+ else if (styleNumberFormat !== undefined) output.z = styleNumberFormat
164
+ if (style !== undefined) {
165
+ const xlsxStyle = univerStyleToXlsx(style)
166
+ if (Object.keys(xlsxStyle).length > 0) output.s = xlsxStyle
167
+ }
168
+ worksheet[target] = output
169
+ }
170
+ }
171
+ worksheet['!ref'] = `A1:${XLSX.utils.encode_cell({ r: bounds.endRow, c: bounds.endColumn })}`
172
+ const merges = Array.isArray(entry.sheet.mergeData) ? entry.sheet.mergeData : []
173
+ worksheet['!merges'] = merges.filter(isRange).map(range => ({
174
+ s: { r: range.startRow, c: range.startColumn },
175
+ e: { r: range.endRow, c: range.endColumn },
176
+ }))
177
+ if (entry.sheet.columnData !== undefined) worksheet['!cols'] = dimensionArray(entry.sheet.columnData, 'width', 'wch') as NonNullable<XLSX.WorkSheet['!cols']>
178
+ if (entry.sheet.rowData !== undefined) worksheet['!rows'] = dimensionArray(entry.sheet.rowData, 'height', 'hpt') as NonNullable<XLSX.WorkSheet['!rows']>
179
+ XLSX.utils.book_append_sheet(workbook, worksheet, entry.name)
180
+ }
181
+ workbook.Props = { ...(workbook.Props ?? {}), Title: root.name ?? 'Datasheet' }
182
+ return workbook
183
+ }
184
+
185
+ function orderedSheets(snapshot: unknown): SheetEntry[] {
186
+ const root = (typeof snapshot === 'object' && snapshot !== null ? snapshot : {}) as WorkbookSnapshot
187
+ const sheets = root.sheets ?? {}
188
+ const ids = root.sheetOrder?.filter(id => sheets[id] !== undefined) ?? Object.keys(sheets)
189
+ const usedNames = new Set<string>()
190
+ return ids.map((id, index) => ({ id, name: uniqueSheetName(safeSheetName(sheets[id]?.name ?? id, index), usedNames, index), sheet: sheets[id] ?? {} }))
191
+ }
192
+
193
+ function uniqueSheetName(name: string, usedNames: Set<string>, index: number): string {
194
+ let candidate = name
195
+ let suffix = 2
196
+ while (usedNames.has(candidate)) {
197
+ const suffixText = ` (${suffix++})`
198
+ candidate = `${name.slice(0, Math.max(1, 31 - suffixText.length))}${suffixText}`
199
+ }
200
+ usedNames.add(candidate)
201
+ return candidate || `Sheet${index + 1}`
202
+ }
203
+
204
+ function safeSheetName(name: string, index: number): string {
205
+ return name.replace(/[\\/:?*\[\]]/g, ' ').trim().slice(0, 31) || `Sheet${index + 1}`
206
+ }
207
+
208
+ function getCell(sheet: SnapshotSheet, row: number, column: number): SnapshotCell {
209
+ const value = sheet.cellData?.[String(row)]?.[String(column)]
210
+ if (typeof value === 'object' && value !== null) return value as SnapshotCell
211
+ return { v: value }
212
+ }
213
+
214
+ function usedBounds(sheet: SnapshotSheet): { endRow: number; endColumn: number } {
215
+ let endRow = -1
216
+ let endColumn = -1
217
+ for (const [rowKey, row] of Object.entries(sheet.cellData ?? {})) {
218
+ const rowNumber = Number(rowKey)
219
+ if (!Number.isInteger(rowNumber) || rowNumber < 0) continue
220
+ for (const columnKey of Object.keys(row)) {
221
+ const columnNumber = Number(columnKey)
222
+ if (!Number.isInteger(columnNumber) || columnNumber < 0) continue
223
+ endRow = Math.max(endRow, rowNumber)
224
+ endColumn = Math.max(endColumn, columnNumber)
225
+ }
226
+ }
227
+ return { endRow: Math.max(0, endRow), endColumn: Math.max(0, endColumn) }
228
+ }
229
+
230
+ function displayCellValue(cell: SnapshotCell): unknown {
231
+ if (cell.f !== undefined && cell.v === undefined) return `=${cell.f.replace(/^=/, '')}`
232
+ return cell.v ?? ''
233
+ }
234
+
235
+ function normalizeImportedValue(value: unknown): unknown {
236
+ return value instanceof Date ? value.toISOString() : value ?? null
237
+ }
238
+
239
+ function xlsxStyleToUniver(value: unknown, numberFormat: unknown): WorkbookCellStyle {
240
+ const source = asRecord(value)
241
+ const font = asRecord(source.font)
242
+ const fill = asRecord(source.fill)
243
+ const alignment = asRecord(source.alignment)
244
+ const style: WorkbookCellStyle = {}
245
+ if (typeof font.name === 'string' && font.name.length > 0) style.ff = font.name
246
+ if (typeof font.sz === 'number' && Number.isFinite(font.sz)) style.fs = font.sz
247
+ if (font.bold === true) style.bl = true
248
+ if (font.italic === true) style.it = true
249
+ if (font.underline === true || font.underline === 'single') style.ul = true
250
+ if (font.strike === true) style.st = true
251
+ const fontColor = colorValue(font.color)
252
+ if (fontColor !== undefined) style.cl = fontColor
253
+ const fillColor = colorValue(fill.fgColor ?? fill.bgColor)
254
+ if (fillColor !== undefined && fill.patternType !== 'none') style.bg = fillColor
255
+ if (alignment.horizontal === 'left') style.ht = 1
256
+ else if (alignment.horizontal === 'center' || alignment.horizontal === 'centerContinuous') style.ht = 2
257
+ else if (alignment.horizontal === 'right') style.ht = 3
258
+ if (alignment.vertical === 'top') style.vt = 1
259
+ else if (alignment.vertical === 'center') style.vt = 2
260
+ else if (alignment.vertical === 'bottom') style.vt = 3
261
+ if (typeof alignment.textRotation === 'number' && Number.isFinite(alignment.textRotation)) style.tr = alignment.textRotation
262
+ if (typeof numberFormat === 'string' && numberFormat.length > 0 && numberFormat !== 'General') style.n = numberFormat
263
+ return style
264
+ }
265
+
266
+ function resolveCellStyle(root: WorkbookSnapshot, cell: SnapshotCell): WorkbookCellStyle | undefined {
267
+ if (typeof cell.s === 'string') {
268
+ const style = root.styles?.[cell.s]
269
+ return style !== undefined ? style as WorkbookCellStyle : undefined
270
+ }
271
+ return cell.s !== null && typeof cell.s === 'object' ? cell.s as WorkbookCellStyle : undefined
272
+ }
273
+
274
+ function univerStyleToXlsx(style: WorkbookCellStyle): Record<string, unknown> {
275
+ const font: Record<string, unknown> = {}
276
+ if (typeof style.ff === 'string') font.name = style.ff
277
+ if (typeof style.fs === 'number') font.sz = style.fs
278
+ if (style.bl !== undefined) font.bold = style.bl
279
+ if (style.it !== undefined) font.italic = style.it
280
+ if (style.ul !== undefined) font.underline = style.ul ? 'single' : undefined
281
+ if (style.st !== undefined) font.strike = style.st
282
+ const fontColor = colorValue(style.cl)
283
+ if (fontColor !== undefined) font.color = { rgb: toArgb(fontColor) }
284
+ const fillColor = colorValue(style.bg)
285
+ const fill = fillColor === undefined ? undefined : { patternType: 'solid', fgColor: { rgb: toArgb(fillColor) } }
286
+ const alignment: Record<string, unknown> = {}
287
+ if (style.ht === 1) alignment.horizontal = 'left'
288
+ else if (style.ht === 2) alignment.horizontal = 'center'
289
+ else if (style.ht === 3) alignment.horizontal = 'right'
290
+ if (style.vt === 1) alignment.vertical = 'top'
291
+ else if (style.vt === 2) alignment.vertical = 'center'
292
+ else if (style.vt === 3) alignment.vertical = 'bottom'
293
+ if (style.tr !== undefined) alignment.textRotation = style.tr
294
+ return {
295
+ ...(Object.keys(font).length > 0 ? { font } : {}),
296
+ ...(fill === undefined ? {} : { fill }),
297
+ ...(Object.keys(alignment).length > 0 ? { alignment } : {}),
298
+ }
299
+ }
300
+
301
+ function asRecord(value: unknown): Record<string, any> {
302
+ return value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, any> : {}
303
+ }
304
+
305
+ function colorValue(value: unknown): string | undefined {
306
+ const record = asRecord(value)
307
+ const raw = typeof value === 'string' ? value : typeof record.rgb === 'string' ? record.rgb : undefined
308
+ if (raw === undefined || raw.length === 0) return undefined
309
+ const normalized = raw.replace(/^#/, '')
310
+ if (!/^[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/.test(normalized)) return raw
311
+ return `#${normalized.length === 8 ? normalized.slice(2) : normalized}`
312
+ }
313
+
314
+ function toArgb(value: string): string {
315
+ const normalized = value.replace(/^#/, '')
316
+ return normalized.length === 6 ? `FF${normalized}` : normalized
317
+ }
318
+
319
+ function stableStyleSignature(style: WorkbookCellStyle): string {
320
+ return JSON.stringify(Object.entries(style).sort(([left], [right]) => left.localeCompare(right)))
321
+ }
322
+
323
+ function cellType(value: unknown): XLSX.CellObject['t'] {
324
+ if (typeof value === 'number') return 'n'
325
+ if (typeof value === 'boolean') return 'b'
326
+ if (value instanceof Date) return 'd'
327
+ return 's'
328
+ }
329
+
330
+ function isRange(value: unknown): value is { startRow: number; startColumn: number; endRow: number; endColumn: number } {
331
+ if (typeof value !== 'object' || value === null) return false
332
+ const range = value as Record<string, unknown>
333
+ return ['startRow', 'startColumn', 'endRow', 'endColumn'].every(key => typeof range[key] === 'number')
334
+ }
335
+
336
+ function dimensionSize(value: unknown, key: 'width' | 'height'): number | undefined {
337
+ if (typeof value !== 'object' || value === null) return undefined
338
+ const size = (value as Record<string, unknown>)[key]
339
+ return typeof size === 'number' && Number.isFinite(size) ? size : undefined
340
+ }
341
+
342
+ function dimensionArray(data: Record<string, unknown>, sourceKey: 'width' | 'height', targetKey: 'wch' | 'hpt'): Array<Record<string, unknown> | undefined> {
343
+ const indexes = Object.keys(data).map(Number).filter(Number.isInteger)
344
+ const result: Array<Record<string, unknown> | undefined> = []
345
+ for (const index of indexes) {
346
+ const value = data[String(index)]
347
+ result[index] = { ...(typeof value === 'object' && value !== null ? value as object : {}), [targetKey]: dimensionSize(value, sourceKey) }
348
+ }
349
+ return result
350
+ }
351
+
352
+ function escapeCsv(value: unknown): string {
353
+ const raw = value === null || value === undefined ? '' : String(value)
354
+ // Prevent formula injection when a CSV is opened by spreadsheet software.
355
+ // The apostrophe is visible only as a safety marker in Excel/Sheets.
356
+ const text = /^[=+\-@]/.test(raw) ? `'${raw}` : raw
357
+ return /[",\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text
358
+ }
359
+
360
+ function safeFilename(value: string): string {
361
+ return value.replace(/[\\/:*?"<>|]/g, '-').trim() || 'workbook'
362
+ }
363
+
364
+ export function downloadBlob(blob: Blob, filename: string): void {
365
+ const url = URL.createObjectURL(blob)
366
+ const anchor = document.createElement('a')
367
+ anchor.href = url
368
+ anchor.download = filename
369
+ document.body.append(anchor)
370
+ anchor.click()
371
+ anchor.remove()
372
+ window.setTimeout(() => URL.revokeObjectURL(url), 0)
373
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * XLSX import worker. SheetJS parsing is intentionally kept off the Univer
3
+ * interaction thread; the node half serves this file as a static asset.
4
+ */
5
+ import { readWorkbookXlsx } from './xlsx-export.ts'
6
+
7
+ interface ImportRequest {
8
+ data: ArrayBuffer
9
+ filename?: string
10
+ }
11
+
12
+ interface WorkerScope {
13
+ onmessage: ((event: MessageEvent<ImportRequest>) => void) | null
14
+ postMessage(message: unknown): void
15
+ }
16
+
17
+ const workerScope = globalThis as unknown as WorkerScope
18
+
19
+ workerScope.onmessage = event => {
20
+ try {
21
+ const request = event.data
22
+ if (request === undefined || !(request.data instanceof ArrayBuffer)) {
23
+ throw new Error('导入请求数据无效')
24
+ }
25
+ const snapshot = readWorkbookXlsx(request.data, request.filename ?? 'workbook')
26
+ workerScope.postMessage({ ok: true, snapshot })
27
+ } catch (error) {
28
+ workerScope.postMessage({ ok: false, error: error instanceof Error ? error.message : String(error) })
29
+ }
30
+ }