@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.
- package/LICENSE +200 -0
- package/dist/embed/embed-runtime.js +537 -0
- package/dist/embed/embed.html +29 -0
- package/dist/embed/parser.worker.js +48474 -0
- package/dist/embed.cjs +225 -0
- package/dist/embed.cjs.map +1 -0
- package/dist/embed.d.cts +100 -0
- package/dist/embed.d.ts +100 -0
- package/dist/embed.js +204 -0
- package/dist/embed.js.map +1 -0
- package/dist/index.cjs +1549 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +1530 -0
- package/dist/index.js.map +1 -0
- package/dist/parser.worker.cjs +48469 -0
- package/dist/parser.worker.cjs.map +1 -0
- package/dist/parser.worker.js +48474 -0
- package/dist/parser.worker.js.map +1 -0
- package/dist/protocol--KyBQUjU.d.cts +171 -0
- package/dist/protocol-cEzy7S0i.d.ts +171 -0
- package/dist/sheets.cjs +677 -0
- package/dist/sheets.cjs.map +1 -0
- package/dist/sheets.d.cts +177 -0
- package/dist/sheets.d.ts +177 -0
- package/dist/sheets.js +658 -0
- package/dist/sheets.js.map +1 -0
- package/dist/signing.cjs +706 -0
- package/dist/signing.cjs.map +1 -0
- package/dist/signing.d.cts +141 -0
- package/dist/signing.d.ts +141 -0
- package/dist/signing.js +683 -0
- package/dist/signing.js.map +1 -0
- package/dist/styles.cjs +10 -0
- package/dist/styles.cjs.map +1 -0
- package/dist/styles.d.cts +2 -0
- package/dist/styles.d.ts +2 -0
- package/dist/styles.js +8 -0
- package/dist/styles.js.map +1 -0
- package/dist/types-s_O0u6Cg.d.cts +90 -0
- package/dist/types-s_O0u6Cg.d.ts +90 -0
- package/dist/univer.cjs +220 -0
- package/dist/univer.cjs.map +1 -0
- package/dist/univer.d.cts +60 -0
- package/dist/univer.d.ts +60 -0
- package/dist/univer.js +187 -0
- package/dist/univer.js.map +1 -0
- package/dist/xlsx.cjs +3388 -0
- package/dist/xlsx.cjs.map +1 -0
- package/dist/xlsx.d.cts +383 -0
- package/dist/xlsx.d.ts +383 -0
- package/dist/xlsx.js +3383 -0
- package/dist/xlsx.js.map +1 -0
- package/package.json +293 -0
- package/src/embed/EmbedHostTransport.ts +226 -0
- package/src/embed/EmbedTransport.ts +323 -0
- package/src/embed/EmbedTransport.unit.test.ts +161 -0
- package/src/embed/index.ts +40 -0
- package/src/embed/protocol.ts +258 -0
- package/src/embed-runtime/embed.html +29 -0
- package/src/embed-runtime/index.tsx +440 -0
- package/src/index.ts +16 -0
- package/src/sheets/CasualSheets.tsx +319 -0
- package/src/sheets/CasualSheetsIframe.tsx +220 -0
- package/src/sheets/api.ts +108 -0
- package/src/sheets/index.ts +11 -0
- package/src/signing/SigningPane.tsx +374 -0
- package/src/signing/SigningProvider.tsx +126 -0
- package/src/signing/captures.tsx +316 -0
- package/src/signing/controller.ts +151 -0
- package/src/signing/controller.unit.test.ts +133 -0
- package/src/signing/index.ts +44 -0
- package/src/signing/types.ts +89 -0
- package/src/styles.ts +16 -0
- package/src/univer/index.ts +17 -0
- package/src/univer/lazy-plugins.ts +280 -0
- package/src/xlsx/_perf.ts +14 -0
- package/src/xlsx/_snapshot-constants.ts +15 -0
- package/src/xlsx/comments-resource.ts +209 -0
- package/src/xlsx/constants.ts +9 -0
- package/src/xlsx/data-validation-resource.ts +219 -0
- package/src/xlsx/import.ts +35 -0
- package/src/xlsx/index.ts +40 -0
- package/src/xlsx/page-setup-resource.ts +205 -0
- package/src/xlsx/parse-impl.ts +418 -0
- package/src/xlsx/parse-in-worker.ts +82 -0
- package/src/xlsx/parser.worker.ts +39 -0
- package/src/xlsx/passthrough-resource.ts +175 -0
- package/src/xlsx/pivot-passthrough.ts +359 -0
- package/src/xlsx/style-mapping.ts +171 -0
- 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
|
+
}
|