@casualoffice/sheets 0.9.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 (92) hide show
  1. package/LICENSE +200 -0
  2. package/dist/embed/embed-runtime.js +537 -0
  3. package/dist/embed/embed.html +29 -0
  4. package/dist/embed/parser.worker.js +48474 -0
  5. package/dist/embed.cjs +225 -0
  6. package/dist/embed.cjs.map +1 -0
  7. package/dist/embed.d.cts +100 -0
  8. package/dist/embed.d.ts +100 -0
  9. package/dist/embed.js +204 -0
  10. package/dist/embed.js.map +1 -0
  11. package/dist/index.cjs +1549 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.d.cts +9 -0
  14. package/dist/index.d.ts +9 -0
  15. package/dist/index.js +1530 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/parser.worker.cjs +48469 -0
  18. package/dist/parser.worker.cjs.map +1 -0
  19. package/dist/parser.worker.js +48474 -0
  20. package/dist/parser.worker.js.map +1 -0
  21. package/dist/protocol--KyBQUjU.d.cts +171 -0
  22. package/dist/protocol-cEzy7S0i.d.ts +171 -0
  23. package/dist/sheets.cjs +677 -0
  24. package/dist/sheets.cjs.map +1 -0
  25. package/dist/sheets.d.cts +177 -0
  26. package/dist/sheets.d.ts +177 -0
  27. package/dist/sheets.js +658 -0
  28. package/dist/sheets.js.map +1 -0
  29. package/dist/signing.cjs +706 -0
  30. package/dist/signing.cjs.map +1 -0
  31. package/dist/signing.d.cts +141 -0
  32. package/dist/signing.d.ts +141 -0
  33. package/dist/signing.js +683 -0
  34. package/dist/signing.js.map +1 -0
  35. package/dist/styles.cjs +10 -0
  36. package/dist/styles.cjs.map +1 -0
  37. package/dist/styles.d.cts +2 -0
  38. package/dist/styles.d.ts +2 -0
  39. package/dist/styles.js +8 -0
  40. package/dist/styles.js.map +1 -0
  41. package/dist/types-s_O0u6Cg.d.cts +90 -0
  42. package/dist/types-s_O0u6Cg.d.ts +90 -0
  43. package/dist/univer.cjs +220 -0
  44. package/dist/univer.cjs.map +1 -0
  45. package/dist/univer.d.cts +60 -0
  46. package/dist/univer.d.ts +60 -0
  47. package/dist/univer.js +187 -0
  48. package/dist/univer.js.map +1 -0
  49. package/dist/xlsx.cjs +3388 -0
  50. package/dist/xlsx.cjs.map +1 -0
  51. package/dist/xlsx.d.cts +383 -0
  52. package/dist/xlsx.d.ts +383 -0
  53. package/dist/xlsx.js +3383 -0
  54. package/dist/xlsx.js.map +1 -0
  55. package/package.json +293 -0
  56. package/src/embed/EmbedHostTransport.ts +226 -0
  57. package/src/embed/EmbedTransport.ts +323 -0
  58. package/src/embed/EmbedTransport.unit.test.ts +161 -0
  59. package/src/embed/index.ts +40 -0
  60. package/src/embed/protocol.ts +258 -0
  61. package/src/embed-runtime/embed.html +29 -0
  62. package/src/embed-runtime/index.tsx +440 -0
  63. package/src/index.ts +16 -0
  64. package/src/sheets/CasualSheets.tsx +319 -0
  65. package/src/sheets/CasualSheetsIframe.tsx +220 -0
  66. package/src/sheets/api.ts +108 -0
  67. package/src/sheets/index.ts +11 -0
  68. package/src/signing/SigningPane.tsx +374 -0
  69. package/src/signing/SigningProvider.tsx +126 -0
  70. package/src/signing/captures.tsx +316 -0
  71. package/src/signing/controller.ts +151 -0
  72. package/src/signing/controller.unit.test.ts +133 -0
  73. package/src/signing/index.ts +44 -0
  74. package/src/signing/types.ts +89 -0
  75. package/src/styles.ts +16 -0
  76. package/src/univer/index.ts +17 -0
  77. package/src/univer/lazy-plugins.ts +280 -0
  78. package/src/xlsx/_perf.ts +14 -0
  79. package/src/xlsx/_snapshot-constants.ts +15 -0
  80. package/src/xlsx/comments-resource.ts +209 -0
  81. package/src/xlsx/constants.ts +9 -0
  82. package/src/xlsx/data-validation-resource.ts +219 -0
  83. package/src/xlsx/import.ts +35 -0
  84. package/src/xlsx/index.ts +40 -0
  85. package/src/xlsx/page-setup-resource.ts +205 -0
  86. package/src/xlsx/parse-impl.ts +418 -0
  87. package/src/xlsx/parse-in-worker.ts +82 -0
  88. package/src/xlsx/parser.worker.ts +39 -0
  89. package/src/xlsx/passthrough-resource.ts +175 -0
  90. package/src/xlsx/pivot-passthrough.ts +359 -0
  91. package/src/xlsx/style-mapping.ts +171 -0
  92. package/src/xlsx/tables-resource.ts +211 -0
@@ -0,0 +1,171 @@
1
+ import type { IStyleData } from '@univerjs/core';
2
+ import type * as ExcelJS from 'exceljs';
3
+
4
+ /**
5
+ * Mappings between ExcelJS and Univer style models.
6
+ *
7
+ * Univer's `IStyleData` uses two-letter compact keys (bl, it, ff, fs, cl, bg,
8
+ * ht, vt, n, ul, bd) plus numeric enums for alignment. ExcelJS uses verbose
9
+ * keys (font.bold, alignment.horizontal, numFmt, etc.). Round-trip preserves
10
+ * the basics; advanced styling (gradients, patterns, complex borders) falls
11
+ * back to defaults.
12
+ */
13
+
14
+ const ARGB_RX = /^#?([0-9A-Fa-f]{6,8})$/;
15
+
16
+ function normalizeColor(argb: string | undefined): string | undefined {
17
+ if (!argb) return undefined;
18
+ const m = ARGB_RX.exec(argb);
19
+ if (!m) return undefined;
20
+ const hex = m[1];
21
+ // ExcelJS uses ARGB; drop alpha if present.
22
+ const rgb = hex.length === 8 ? hex.slice(2) : hex;
23
+ return `#${rgb.toLowerCase()}`;
24
+ }
25
+
26
+ // Accept Univer's Nullable<string> (string | null | void) by treating any
27
+ // non-string input as undefined.
28
+ function toARGB(rgb: unknown): string | undefined {
29
+ if (typeof rgb !== 'string' || !rgb) return undefined;
30
+ const m = ARGB_RX.exec(rgb);
31
+ if (!m) return undefined;
32
+ const hex = m[1];
33
+ const norm = hex.length === 8 ? hex : `FF${hex}`;
34
+ return norm.toUpperCase();
35
+ }
36
+
37
+ // Univer HorizontalAlign: 0 = default, 1 = LEFT, 2 = CENTER, 3 = RIGHT
38
+ const H_ALIGN_FROM_EXCEL: Record<string, number> = {
39
+ left: 1,
40
+ center: 2,
41
+ right: 3,
42
+ };
43
+ const H_ALIGN_TO_EXCEL: Record<number, string> = {
44
+ 1: 'left',
45
+ 2: 'center',
46
+ 3: 'right',
47
+ };
48
+
49
+ // Univer VerticalAlign: 0 = default, 1 = TOP, 2 = MIDDLE, 3 = BOTTOM
50
+ const V_ALIGN_FROM_EXCEL: Record<string, number> = {
51
+ top: 1,
52
+ middle: 2,
53
+ bottom: 3,
54
+ };
55
+ const V_ALIGN_TO_EXCEL: Record<number, string> = {
56
+ 1: 'top',
57
+ 2: 'middle',
58
+ 3: 'bottom',
59
+ };
60
+
61
+ export function excelStyleToUniver(cell: ExcelJS.Cell): IStyleData | undefined {
62
+ const s: IStyleData = {};
63
+
64
+ if (cell.font) {
65
+ if (cell.font.name) s.ff = cell.font.name;
66
+ if (cell.font.size) s.fs = cell.font.size;
67
+ if (cell.font.bold) s.bl = 1;
68
+ if (cell.font.italic) s.it = 1;
69
+ if (cell.font.underline) s.ul = { s: 1 };
70
+ const fc = normalizeColor((cell.font.color as { argb?: string } | undefined)?.argb);
71
+ if (fc) s.cl = { rgb: fc };
72
+ }
73
+
74
+ if (cell.fill && cell.fill.type === 'pattern' && cell.fill.pattern === 'solid') {
75
+ const bg = normalizeColor((cell.fill.fgColor as { argb?: string } | undefined)?.argb);
76
+ if (bg) s.bg = { rgb: bg };
77
+ }
78
+
79
+ if (cell.alignment) {
80
+ const ha = cell.alignment.horizontal;
81
+ if (ha && H_ALIGN_FROM_EXCEL[ha] !== undefined) s.ht = H_ALIGN_FROM_EXCEL[ha];
82
+ const va = cell.alignment.vertical;
83
+ if (va && V_ALIGN_FROM_EXCEL[va] !== undefined) s.vt = V_ALIGN_FROM_EXCEL[va];
84
+ if (cell.alignment.wrapText) s.tb = 3; // WrapStrategy.WRAP
85
+ }
86
+
87
+ if (cell.numFmt) s.n = { pattern: cell.numFmt };
88
+
89
+ // Borders — keep simple: any side present => thin border, color from spec.
90
+ if (cell.border) {
91
+ const bd: NonNullable<IStyleData['bd']> = {};
92
+ const sides: Array<['t' | 'b' | 'l' | 'r', 'top' | 'bottom' | 'left' | 'right']> = [
93
+ ['t', 'top'],
94
+ ['b', 'bottom'],
95
+ ['l', 'left'],
96
+ ['r', 'right'],
97
+ ];
98
+ for (const [k, key] of sides) {
99
+ const side = (cell.border as Record<string, unknown>)[key] as
100
+ | { style?: string; color?: { argb?: string } }
101
+ | undefined;
102
+ if (side?.style && side.style !== 'none') {
103
+ bd[k] = { s: 1, cl: { rgb: normalizeColor(side.color?.argb) ?? '#666666' } };
104
+ }
105
+ }
106
+ if (Object.keys(bd).length > 0) s.bd = bd;
107
+ }
108
+
109
+ return Object.keys(s).length > 0 ? s : undefined;
110
+ }
111
+
112
+ export function univerStyleToExcel(style: IStyleData): Partial<ExcelJS.Style> {
113
+ const out: Partial<ExcelJS.Style> = {};
114
+
115
+ const font: ExcelJS.Style['font'] = {};
116
+ if (style.ff) font.name = style.ff;
117
+ if (style.fs) font.size = style.fs;
118
+ if (style.bl === 1) font.bold = true;
119
+ if (style.it === 1) font.italic = true;
120
+ if (style.ul?.s === 1) font.underline = true;
121
+ if (style.cl && typeof style.cl === 'object' && 'rgb' in style.cl) {
122
+ const argb = toARGB(style.cl.rgb);
123
+ if (argb) font.color = { argb };
124
+ }
125
+ if (Object.keys(font).length > 0) out.font = font;
126
+
127
+ if (style.bg && typeof style.bg === 'object' && 'rgb' in style.bg) {
128
+ const argb = toARGB(style.bg.rgb);
129
+ if (argb) {
130
+ out.fill = {
131
+ type: 'pattern',
132
+ pattern: 'solid',
133
+ fgColor: { argb },
134
+ };
135
+ }
136
+ }
137
+
138
+ const alignment: ExcelJS.Style['alignment'] = {};
139
+ if (style.ht && H_ALIGN_TO_EXCEL[style.ht]) {
140
+ alignment.horizontal = H_ALIGN_TO_EXCEL[style.ht] as ExcelJS.Style['alignment']['horizontal'];
141
+ }
142
+ if (style.vt && V_ALIGN_TO_EXCEL[style.vt]) {
143
+ alignment.vertical = V_ALIGN_TO_EXCEL[style.vt] as ExcelJS.Style['alignment']['vertical'];
144
+ }
145
+ if (style.tb === 3) alignment.wrapText = true;
146
+ if (Object.keys(alignment).length > 0) out.alignment = alignment;
147
+
148
+ if (style.n?.pattern) out.numFmt = style.n.pattern;
149
+
150
+ if (style.bd) {
151
+ const border: ExcelJS.Style['border'] = {};
152
+ const sides: Array<['t' | 'b' | 'l' | 'r', 'top' | 'bottom' | 'left' | 'right']> = [
153
+ ['t', 'top'],
154
+ ['b', 'bottom'],
155
+ ['l', 'left'],
156
+ ['r', 'right'],
157
+ ];
158
+ for (const [k, key] of sides) {
159
+ const side = style.bd[k];
160
+ if (!side) continue;
161
+ const color = side.cl && 'rgb' in side.cl ? toARGB(side.cl.rgb) : undefined;
162
+ (border as Record<string, unknown>)[key] = {
163
+ style: 'thin',
164
+ ...(color ? { color: { argb: color } } : {}),
165
+ };
166
+ }
167
+ if (Object.keys(border).length > 0) out.border = border;
168
+ }
169
+
170
+ return out;
171
+ }
@@ -0,0 +1,211 @@
1
+ import type ExcelJS from 'exceljs';
2
+ import type { IWorkbookData } from '@univerjs/core';
3
+
4
+ /**
5
+ * Passthrough preservation for xlsx ListObjects (a.k.a. Excel Tables).
6
+ * Univer doesn't model tables as first-class objects — autofilters and
7
+ * structured-reference parsing aren't there yet — so without a
8
+ * passthrough, opening an xlsx that ships a defined table and saving
9
+ * it again would silently strip the table back to plain cells.
10
+ *
11
+ * Data lives in our own sidecar (`__casual_sheets_tables__`); on
12
+ * export we replay `ws.addTable(...)` so the saved file shows the
13
+ * table to any reader (real Excel, gsheets, LibreOffice).
14
+ *
15
+ * When/if Univer grows native table support, this resource is the
16
+ * place to switch from sidecar to plugin shape — readers of older
17
+ * files keep working because the sidecar is forward-compatible.
18
+ */
19
+
20
+ export const TABLES_RESOURCE = '__casual_sheets_tables__';
21
+
22
+ export type SynthTableColumn = {
23
+ name: string;
24
+ filterButton?: boolean;
25
+ totalsRowFunction?: string;
26
+ totalsRowLabel?: string;
27
+ totalsRowFormula?: string;
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ style?: any;
30
+ };
31
+
32
+ export type SynthTableV1 = {
33
+ name: string;
34
+ ref: string; // 'A1' or 'A1:C4'
35
+ displayName?: string;
36
+ headerRow?: boolean;
37
+ totalsRow?: boolean;
38
+ style?: {
39
+ theme?: string;
40
+ name?: string;
41
+ showRowStripes?: boolean;
42
+ showColumnStripes?: boolean;
43
+ showFirstColumn?: boolean;
44
+ showLastColumn?: boolean;
45
+ };
46
+ columns: SynthTableColumn[];
47
+ rows: unknown[][];
48
+ };
49
+
50
+ type Payload = {
51
+ v: 1;
52
+ sheets: Record<string, SynthTableV1[]>;
53
+ };
54
+
55
+ /**
56
+ * Walk every worksheet and lift any defined tables into the sidecar
57
+ * payload. ExcelJS stores tables on `ws.tables` keyed by table name;
58
+ * each value is a Table *instance* whose actual fields hang off
59
+ * `t.model` (`node_modules/exceljs/lib/doc/table.js:275`). Two
60
+ * gotchas the parser must handle:
61
+ *
62
+ * 1. After `xlsx.load`, the model uses `tableRef` (full range) and
63
+ * drops `ref` (the original top-left author hint). We coalesce
64
+ * onto `ref` as a single canonical full-range string.
65
+ * 2. The loaded model never carries `rows` — table cell data is
66
+ * already on the worksheet. We don't need rows to round-trip.
67
+ */
68
+ export function readTablesFromXlsx(
69
+ wb: ExcelJS.Workbook,
70
+ sheetIdForExcel: (excelId: number) => string,
71
+ ): Record<string, SynthTableV1[]> {
72
+ const out: Record<string, SynthTableV1[]> = {};
73
+ for (const ws of wb.worksheets) {
74
+ const tableMap = (ws as unknown as { tables?: Record<string, unknown> | Map<string, unknown> })
75
+ .tables;
76
+ if (!tableMap) continue;
77
+ const entries: unknown[] = tableMap instanceof Map
78
+ ? Array.from(tableMap.values())
79
+ : Object.values(tableMap);
80
+ if (entries.length === 0) continue;
81
+
82
+ const tables: SynthTableV1[] = [];
83
+ for (const tUnknown of entries) {
84
+ const tInstance = tUnknown as { model?: unknown };
85
+ const modelUnknown = (tInstance && typeof tInstance === 'object' && 'model' in tInstance)
86
+ ? tInstance.model
87
+ : tUnknown;
88
+ const model = modelUnknown as Record<string, unknown>;
89
+ if (!model || typeof model !== 'object') continue;
90
+ const name = model.name;
91
+ // Prefer the post-load `tableRef` (full range, eg `'A1:B3'`).
92
+ // Fall back to `ref` for tables that were `addTable`d in-process
93
+ // and never made the round-trip through xform load.
94
+ const refStr = typeof model.tableRef === 'string'
95
+ ? model.tableRef
96
+ : typeof model.ref === 'string'
97
+ ? model.ref
98
+ : null;
99
+ if (typeof name !== 'string' || !refStr) continue;
100
+
101
+ const cols: SynthTableColumn[] = Array.isArray(model.columns)
102
+ ? (model.columns as unknown[]).map((cUnknown) => {
103
+ const c = cUnknown as Record<string, unknown>;
104
+ return {
105
+ name: typeof c?.name === 'string' ? c.name : '',
106
+ ...(typeof c?.filterButton === 'boolean' ? { filterButton: c.filterButton } : {}),
107
+ ...(typeof c?.totalsRowFunction === 'string'
108
+ ? { totalsRowFunction: c.totalsRowFunction }
109
+ : {}),
110
+ ...(typeof c?.totalsRowLabel === 'string'
111
+ ? { totalsRowLabel: c.totalsRowLabel }
112
+ : {}),
113
+ ...(typeof c?.totalsRowFormula === 'string'
114
+ ? { totalsRowFormula: c.totalsRowFormula }
115
+ : {}),
116
+ };
117
+ })
118
+ : [];
119
+
120
+ tables.push({
121
+ name,
122
+ ref: refStr,
123
+ ...(typeof model.displayName === 'string' ? { displayName: model.displayName } : {}),
124
+ ...(typeof model.headerRow === 'boolean' ? { headerRow: model.headerRow } : {}),
125
+ ...(typeof model.totalsRow === 'boolean' ? { totalsRow: model.totalsRow } : {}),
126
+ ...(model.style && typeof model.style === 'object'
127
+ ? { style: model.style as SynthTableV1['style'] }
128
+ : {}),
129
+ columns: cols,
130
+ rows: [],
131
+ });
132
+ }
133
+ if (tables.length > 0) out[sheetIdForExcel(ws.id)] = tables;
134
+ }
135
+ return out;
136
+ }
137
+
138
+ /** Merge a synthesised table map into the snapshot resources. */
139
+ export function mergeTablesIntoResources(
140
+ resources: IWorkbookData['resources'],
141
+ payload: Record<string, SynthTableV1[]>,
142
+ ): IWorkbookData['resources'] {
143
+ if (Object.keys(payload).length === 0) return resources;
144
+ const existing = resources?.find((r) => r.name === TABLES_RESOURCE);
145
+ if (existing) return resources;
146
+ const body: Payload = { v: 1, sheets: payload };
147
+ const next = [...(resources ?? [])];
148
+ next.push({ name: TABLES_RESOURCE, data: JSON.stringify(body) });
149
+ return next;
150
+ }
151
+
152
+ /** Read the tables resource off a snapshot. */
153
+ export function readTablesFromSnapshot(
154
+ data: IWorkbookData,
155
+ ): Record<string, SynthTableV1[]> {
156
+ const entry = data.resources?.find((r) => r.name === TABLES_RESOURCE);
157
+ if (!entry?.data) return {};
158
+ try {
159
+ const parsed = JSON.parse(entry.data) as Partial<Payload>;
160
+ if (parsed?.v !== 1 || !parsed.sheets) return {};
161
+ return parsed.sheets;
162
+ } catch {
163
+ return {};
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Apply a per-sheet table set onto an ExcelJS worksheet.
169
+ *
170
+ * Why this bypasses `ws.addTable(...)`:
171
+ *
172
+ * `Table.validate()` (`node_modules/exceljs/lib/doc/table.js:129`)
173
+ * throws when `table.rows` is missing — but our round-trip case
174
+ * doesn't carry rows (cell data already lives in `cellData`). And
175
+ * `Table.store()` would *re-write* every cell from the rows
176
+ * array, clobbering whatever the user had set after the table was
177
+ * created. So instead of going through `addTable`, we attach the
178
+ * load-shape model directly the same way ExcelJS's worksheet xform
179
+ * setter does on load (`node_modules/exceljs/lib/doc/worksheet.js:
180
+ * 917`). The serializer then walks `Object.values(ws.tables).map(t
181
+ * => t.model)` (`worksheet.js:856`) and writes the table parts —
182
+ * `target` + `id` are filled in by `xlsx.js:632` during prepare.
183
+ */
184
+ export function applyTablesToXlsxWorksheet(
185
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
186
+ ws: any,
187
+ tables: SynthTableV1[],
188
+ ): void {
189
+ if (!tables?.length) return;
190
+ ws.tables = ws.tables ?? {};
191
+ for (const t of tables) {
192
+ if (!t?.name || !t?.ref || !Array.isArray(t.columns) || t.columns.length === 0) continue;
193
+ const model: Record<string, unknown> = {
194
+ name: t.name,
195
+ displayName: t.displayName ?? t.name,
196
+ tableRef: t.ref,
197
+ autoFilterRef: t.ref,
198
+ headerRow: t.headerRow ?? true,
199
+ totalsRow: t.totalsRow ?? false,
200
+ columns: t.columns.map((c) => ({
201
+ name: c.name,
202
+ ...(c.filterButton !== undefined ? { filterButton: c.filterButton } : {}),
203
+ ...(c.totalsRowFunction ? { totalsRowFunction: c.totalsRowFunction } : {}),
204
+ ...(c.totalsRowLabel ? { totalsRowLabel: c.totalsRowLabel } : {}),
205
+ ...(c.totalsRowFormula ? { totalsRowFormula: c.totalsRowFormula } : {}),
206
+ })),
207
+ style: t.style ?? {},
208
+ };
209
+ ws.tables[t.name] = { model };
210
+ }
211
+ }