office-viewer-react 1.0.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 (36) hide show
  1. package/README.md +390 -0
  2. package/dist/client/assets/DocxViewer-NgAiZAEg.css +1 -0
  3. package/dist/client/assets/DocxViewer-gwdjm0mw.js +60 -0
  4. package/dist/client/assets/LogoIcon-BcnkueZW.js +1 -0
  5. package/dist/client/assets/PptxViewer-CLNaZa_4.js +59 -0
  6. package/dist/client/assets/PptxViewer-CYMXzyIj.css +1 -0
  7. package/dist/client/assets/XlsxViewer-BNso6L-X.css +1 -0
  8. package/dist/client/assets/XlsxViewer-C2ErMokS.js +64 -0
  9. package/dist/client/assets/_commonjs-dynamic-modules-DaXrHM_S.js +1 -0
  10. package/dist/client/assets/form-C1byQJR4.js +1 -0
  11. package/dist/client/assets/index-BDMLGHcR.js +2 -0
  12. package/dist/client/assets/index-CKjGwz9R.js +12 -0
  13. package/dist/client/assets/jszip.min-BwIaN_vk.js +2 -0
  14. package/dist/client/assets/login-DEy3R1iD.js +1 -0
  15. package/dist/client/assets/register-CUUVGLJE.js +1 -0
  16. package/dist/client/assets/styles-3a3CPFIV.css +1 -0
  17. package/dist/client/robots.txt +2 -0
  18. package/dist/index.cjs +1806 -0
  19. package/dist/index.d.cts +16 -0
  20. package/dist/index.d.ts +16 -0
  21. package/dist/index.js +1769 -0
  22. package/dist/server/assets/DocxViewer-Bm8UJY-7.js +469 -0
  23. package/dist/server/assets/LogoIcon-Dx0LU3or.js +26 -0
  24. package/dist/server/assets/PptxViewer-DS7Atucw.js +213 -0
  25. package/dist/server/assets/XlsxViewer-jzIgKmN2.js +841 -0
  26. package/dist/server/assets/_tanstack-start-manifest_v-CpFqMvFH.js +4 -0
  27. package/dist/server/assets/empty-plugin-adapters-BFgPZ6_d.js +6 -0
  28. package/dist/server/assets/form-CD9otjw-.js +236 -0
  29. package/dist/server/assets/index-gQHSGxNv.js +365 -0
  30. package/dist/server/assets/login-DvbAXNSQ.js +81 -0
  31. package/dist/server/assets/register-C2G9K9kP.js +102 -0
  32. package/dist/server/assets/router-F5YKPXkV.js +229 -0
  33. package/dist/server/assets/server-6Sfy37dh.js +1523 -0
  34. package/dist/server/assets/start-dMGD6DUy.js +56 -0
  35. package/dist/server/server.js +94 -0
  36. package/package.json +120 -0
package/dist/index.js ADDED
@@ -0,0 +1,1769 @@
1
+ // #style-inject:#style-inject
2
+ function styleInject(css, { insertAt } = {}) {
3
+ if (!css || typeof document === "undefined") return;
4
+ const head = document.head || document.getElementsByTagName("head")[0];
5
+ const style = document.createElement("style");
6
+ style.type = "text/css";
7
+ if (insertAt === "top") {
8
+ if (head.firstChild) {
9
+ head.insertBefore(style, head.firstChild);
10
+ } else {
11
+ head.appendChild(style);
12
+ }
13
+ } else {
14
+ head.appendChild(style);
15
+ }
16
+ if (style.styleSheet) {
17
+ style.styleSheet.cssText = css;
18
+ } else {
19
+ style.appendChild(document.createTextNode(css));
20
+ }
21
+ }
22
+
23
+ // src/styles/OfficeViewer.css
24
+ styleInject(".ov-container {\n --ov-muted-fg: oklch(0.554 0.046 257.417);\n --ov-destructive: oklch(0.577 0.245 27.325);\n --ov-border: oklch(0.929 0.013 255.508);\n --ov-page: oklch(1 0 0);\n --ov-shadow-page: 0 1px 3px oklch(0 0 0 / 0.08), 0 8px 24px oklch(0 0 0 / 0.1);\n --ov-grid-line: oklch(0.88 0.01 256);\n --ov-grid-header: oklch(0.95 0.006 256);\n display: flex;\n flex-direction: column;\n width: 100%;\n}\n@keyframes ov-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.ov-toolbar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 16px;\n background: #ffffff;\n border-bottom: 1px solid var(--ov-border);\n flex-shrink: 0;\n}\n.ov-toolbar-label {\n font-size: 12.5px;\n font-weight: 500;\n color: #94a3b8;\n user-select: none;\n white-space: nowrap;\n}\n.ov-select-wrap {\n position: relative;\n display: inline-flex;\n align-items: center;\n}\n.ov-select {\n appearance: none;\n -webkit-appearance: none;\n cursor: pointer;\n padding: 5px 28px 5px 10px;\n font-size: 13px;\n font-weight: 500;\n color: #334155;\n background: #ffffff;\n border: 1px solid var(--ov-border);\n border-radius: 8px;\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n outline: none;\n transition: border-color 150ms ease, box-shadow 150ms ease;\n}\n.ov-select:hover {\n border-color: #cbd5e1;\n}\n.ov-select:focus {\n border-color: #60a5fa;\n box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.2);\n}\n.ov-select-icon {\n pointer-events: none;\n position: absolute;\n right: 9px;\n width: 10px;\n height: 6px;\n color: #94a3b8;\n flex-shrink: 0;\n}\n.ov-viewer {\n flex: 1;\n min-height: 0;\n overflow: auto;\n}\n.ov-viewer--clip {\n overflow: hidden;\n}\n.ov-loading {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 4rem 1rem;\n color: #94a3b8;\n font-size: 0.875rem;\n}\n.ov-spinner {\n width: 1.5rem;\n height: 1.5rem;\n animation: ov-spin 1s linear infinite;\n}\n.ov-error {\n margin: 3rem auto;\n max-width: 32rem;\n border-radius: 0.5rem;\n border: 1px solid color-mix(in oklch, var(--ov-destructive) 30%, transparent);\n background: color-mix(in oklch, var(--ov-destructive) 5%, transparent);\n padding: 1rem 1.5rem;\n font-size: 0.875rem;\n color: var(--ov-destructive);\n}\n");
25
+
26
+ // src/components/OfficeViewer.tsx
27
+ import { useEffect as useEffect4, useState as useState4 } from "react";
28
+ import { Loader2 as Loader24 } from "lucide-react";
29
+
30
+ // src/styles/DocxViewer.css
31
+ styleInject(".ov-docx {\n display: flex;\n flex-direction: column;\n}\n.ov-docx__loading {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 0.5rem;\n padding-block: 4rem;\n color: var(--ov-muted-fg);\n}\n.ov-docx__loading-icon {\n width: 1.5rem;\n height: 1.5rem;\n animation: ov-spin 1s linear infinite;\n}\n.ov-docx__loading-text {\n font-size: 0.875rem;\n}\n.ov-docx__error {\n margin-inline: auto;\n margin-top: 3rem;\n max-width: 32rem;\n border-radius: 0.5rem;\n border: 1px solid color-mix(in oklch, var(--ov-destructive) 30%, transparent);\n background: color-mix(in oklch, var(--ov-destructive) 5%, transparent);\n padding: 1rem 1.5rem;\n font-size: 0.875rem;\n color: var(--ov-destructive);\n}\n.ov-docx__body {\n position: relative;\n display: flex;\n justify-content: center;\n padding-block: 2rem;\n}\n.ov-docx__render {\n width: 100%;\n}\n.ov-docx__nav {\n position: sticky;\n bottom: 1.5rem;\n z-index: 10;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.75rem;\n padding-bottom: 0.5rem;\n}\n.ov-docx__nav-btn {\n display: flex;\n width: 3.75rem;\n height: 3.75rem;\n align-items: center;\n justify-content: center;\n border-radius: 9999px;\n border: 2px solid #e2e8f0;\n background: #ffffff;\n box-shadow: 0 2px 8px rgba(15, 23, 42, 0.14), 0 1px 3px rgba(15, 23, 42, 0.10);\n transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1), border-color 150ms cubic-bezier(0.4, 0, 0.2, 1);\n cursor: pointer;\n}\n.ov-docx__nav-btn:hover {\n transform: scale(1.05);\n border-color: #cbd5e1;\n}\n.ov-docx__nav-btn:disabled {\n cursor: not-allowed;\n opacity: 0.25;\n}\n.ov-docx__nav-btn-icon {\n width: 1.5rem;\n height: 1.5rem;\n color: #1e293b;\n}\n.ov-docx__nav-counter {\n min-width: 7.5rem;\n border-radius: 9999px;\n border: 1px solid #e2e8f0;\n background: #ffffff;\n padding: 0.4375rem 1.25rem;\n text-align: center;\n box-shadow: 0 1px 3px 0 rgba(15, 23, 42, 0.07);\n}\n.ov-docx__nav-current {\n font-size: 13px;\n font-weight: 600;\n color: #334155;\n}\n.ov-docx__nav-total {\n font-size: 13px;\n color: #94a3b8;\n}\n.ov-container .docx-wrapper {\n background: transparent !important;\n padding: 0 !important;\n}\n.ov-container .docx-wrapper > section.docx {\n box-shadow: var(--ov-shadow-page);\n margin-bottom: 1.5rem !important;\n background: var(--ov-page);\n}\n");
32
+
33
+ // src/viewers/DocxViewer.tsx
34
+ import { useCallback, useEffect, useRef, useState } from "react";
35
+ import { renderAsync } from "docx-preview";
36
+ import { ChevronLeft, ChevronRight, Loader2 } from "lucide-react";
37
+ import JSZip from "jszip";
38
+ import * as echarts from "echarts";
39
+ import { jsx, jsxs } from "react/jsx-runtime";
40
+ var NS = {
41
+ c: "http://schemas.openxmlformats.org/drawingml/2006/chart",
42
+ a: "http://schemas.openxmlformats.org/drawingml/2006/main",
43
+ r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
44
+ pic: "http://schemas.openxmlformats.org/drawingml/2006/picture",
45
+ rel: "http://schemas.openxmlformats.org/package/2006/relationships",
46
+ ct: "http://schemas.openxmlformats.org/package/2006/content-types"
47
+ };
48
+ var EMU_PER_PX = 9525;
49
+ var PALETTE = ["#4472c4", "#ed7d31", "#a5a5a5", "#ffc000", "#5b9bd5", "#70ad47", "#264478", "#9e480e"];
50
+ function localElements(parent, name) {
51
+ if (!parent) return [];
52
+ return Array.from(parent.children).filter((c) => c.localName === name);
53
+ }
54
+ function firstLocal(parent, name) {
55
+ if (!parent) return null;
56
+ return Array.from(parent.children).find((c) => c.localName === name) ?? null;
57
+ }
58
+ function deepFirst(parent, name) {
59
+ if (!parent) return null;
60
+ const list = parent.getElementsByTagNameNS(NS.c, name);
61
+ return list.length ? list[0] : null;
62
+ }
63
+ function readCache(ref) {
64
+ if (!ref) return [];
65
+ const cache = firstLocal(ref, "numCache") || firstLocal(ref, "strCache") || ref;
66
+ const pts = Array.from(cache.getElementsByTagNameNS(NS.c, "pt"));
67
+ const out = [];
68
+ for (const pt of pts) {
69
+ const idx = parseInt(pt.getAttribute("idx") || "0", 10);
70
+ const v = pt.getElementsByTagNameNS(NS.c, "v")[0];
71
+ out[idx] = v ? v.textContent || "" : "";
72
+ }
73
+ for (let i = 0; i < out.length; i++) if (out[i] === void 0) out[i] = "";
74
+ return out;
75
+ }
76
+ function parseSeries(ser) {
77
+ const tx = firstLocal(ser, "tx");
78
+ let name = "";
79
+ if (tx) {
80
+ const strRef = firstLocal(tx, "strRef");
81
+ if (strRef) name = readCache(strRef).find((x) => x) || "";
82
+ else {
83
+ const v = tx.getElementsByTagNameNS(NS.c, "v")[0];
84
+ name = v ? v.textContent || "" : "";
85
+ }
86
+ }
87
+ const catRef = firstLocal(firstLocal(ser, "cat"), "strRef") || firstLocal(firstLocal(ser, "cat"), "numRef");
88
+ const valRef = firstLocal(firstLocal(ser, "val"), "numRef");
89
+ const cats = readCache(catRef);
90
+ const vals = readCache(valRef).map((v) => {
91
+ const n = parseFloat(v);
92
+ return isNaN(n) ? 0 : n;
93
+ });
94
+ return { name, cats, vals };
95
+ }
96
+ function chartTitle(chartEl) {
97
+ const title = deepFirst(chartEl, "title");
98
+ if (!title) return "";
99
+ return Array.from(title.getElementsByTagNameNS(NS.a, "t")).map((t) => t.textContent || "").join("");
100
+ }
101
+ function buildOption(chartSpace) {
102
+ const chartEl = firstLocal(chartSpace, "chart");
103
+ const plotArea = firstLocal(chartEl, "plotArea");
104
+ if (!plotArea) return null;
105
+ const typeEl = Array.from(plotArea.children).find((c) => /Chart$/.test(c.localName));
106
+ if (!typeEl) return null;
107
+ const kind = typeEl.localName;
108
+ const seriesEls = localElements(typeEl, "ser");
109
+ if (!seriesEls.length) return null;
110
+ const series = seriesEls.map(parseSeries);
111
+ const categories = series[0]?.cats ?? [];
112
+ const title = chartTitle(chartEl);
113
+ const baseTitle = title ? { title: { text: title, left: "center", textStyle: { fontSize: 14 } } } : {};
114
+ const legend = series.length > 1 || /pie|doughnut/i.test(kind) ? { legend: { bottom: 0, type: "scroll" } } : {};
115
+ if (/pie|doughnut/i.test(kind)) {
116
+ const s = series[0];
117
+ const radius = /doughnut/i.test(kind) ? ["40%", "70%"] : "70%";
118
+ return {
119
+ color: PALETTE,
120
+ ...baseTitle,
121
+ tooltip: { trigger: "item" },
122
+ legend: { bottom: 0, type: "scroll" },
123
+ series: [{ type: "pie", radius, center: ["50%", "48%"], data: s.cats.map((c, i) => ({ name: c || `#${i + 1}`, value: s.vals[i] ?? 0 })), label: { fontSize: 11 } }]
124
+ };
125
+ }
126
+ if (/scatter/i.test(kind)) {
127
+ return {
128
+ color: PALETTE,
129
+ ...baseTitle,
130
+ ...legend,
131
+ tooltip: { trigger: "item" },
132
+ xAxis: { type: "value" },
133
+ yAxis: { type: "value" },
134
+ series: series.map((s) => ({ name: s.name, type: "scatter", data: s.vals.map((v, i) => [parseFloat(s.cats[i]) || i, v]) }))
135
+ };
136
+ }
137
+ const isBarHorizontal = /barChart/i.test(kind) && firstLocal(typeEl, "barDir")?.getAttribute("val") === "bar";
138
+ const isArea = /areaChart/i.test(kind);
139
+ const isLine = /lineChart/i.test(kind);
140
+ const catAxis = { type: "category", data: categories };
141
+ const valAxis = { type: "value" };
142
+ return {
143
+ color: PALETTE,
144
+ ...baseTitle,
145
+ ...legend,
146
+ grid: { left: 48, right: 24, top: title ? 48 : 24, bottom: series.length > 1 ? 48 : 32, containLabel: true },
147
+ tooltip: { trigger: "axis" },
148
+ xAxis: isBarHorizontal ? valAxis : catAxis,
149
+ yAxis: isBarHorizontal ? catAxis : valAxis,
150
+ series: series.map((s) => ({ name: s.name, type: isLine || isArea ? "line" : "bar", areaStyle: isArea ? {} : void 0, smooth: false, data: s.vals }))
151
+ };
152
+ }
153
+ async function renderChartPng(option, wPx, hPx) {
154
+ const w = Math.max(240, Math.min(1600, Math.round(wPx)));
155
+ const h = Math.max(160, Math.min(1200, Math.round(hPx)));
156
+ const div = document.createElement("div");
157
+ div.style.cssText = `position:absolute;left:-99999px;top:0;width:${w}px;height:${h}px;`;
158
+ document.body.appendChild(div);
159
+ const chart = echarts.init(div, void 0, { renderer: "canvas", width: w, height: h });
160
+ chart.setOption({ animation: false, ...option });
161
+ const url = chart.getDataURL({ type: "png", pixelRatio: 2, backgroundColor: "#ffffff" });
162
+ chart.dispose();
163
+ div.remove();
164
+ return url;
165
+ }
166
+ function dataUrlToBytes(dataUrl) {
167
+ const b64 = dataUrl.split(",")[1];
168
+ const bin = atob(b64);
169
+ const bytes = new Uint8Array(bin.length);
170
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
171
+ return bytes;
172
+ }
173
+ function resolvePath(baseDir, target) {
174
+ const parts = (baseDir + target).split("/");
175
+ const stack = [];
176
+ for (const p of parts) {
177
+ if (p === "..") stack.pop();
178
+ else if (p !== "." && p !== "") stack.push(p);
179
+ }
180
+ return stack.join("/");
181
+ }
182
+ function buildPicNode(doc, relId, cx, cy) {
183
+ const pic = doc.createElementNS(NS.pic, "pic:pic");
184
+ const nvPicPr = doc.createElementNS(NS.pic, "pic:nvPicPr");
185
+ const cNvPr = doc.createElementNS(NS.pic, "pic:cNvPr");
186
+ cNvPr.setAttribute("id", "0");
187
+ cNvPr.setAttribute("name", "chart");
188
+ const cNvPicPr = doc.createElementNS(NS.pic, "pic:cNvPicPr");
189
+ nvPicPr.append(cNvPr, cNvPicPr);
190
+ const blipFill = doc.createElementNS(NS.pic, "pic:blipFill");
191
+ const blip = doc.createElementNS(NS.a, "a:blip");
192
+ blip.setAttributeNS(NS.r, "r:embed", relId);
193
+ const stretch = doc.createElementNS(NS.a, "a:stretch");
194
+ stretch.appendChild(doc.createElementNS(NS.a, "a:fillRect"));
195
+ blipFill.append(blip, stretch);
196
+ const spPr = doc.createElementNS(NS.pic, "pic:spPr");
197
+ const xfrm = doc.createElementNS(NS.a, "a:xfrm");
198
+ const off = doc.createElementNS(NS.a, "a:off");
199
+ off.setAttribute("x", "0");
200
+ off.setAttribute("y", "0");
201
+ const ext = doc.createElementNS(NS.a, "a:ext");
202
+ ext.setAttribute("cx", cx);
203
+ ext.setAttribute("cy", cy);
204
+ xfrm.append(off, ext);
205
+ const prstGeom = doc.createElementNS(NS.a, "a:prstGeom");
206
+ prstGeom.setAttribute("prst", "rect");
207
+ prstGeom.appendChild(doc.createElementNS(NS.a, "a:avLst"));
208
+ spPr.append(xfrm, prstGeom);
209
+ pic.append(nvPicPr, blipFill, spPr);
210
+ return pic;
211
+ }
212
+ function getExtent(chartNode) {
213
+ let el = chartNode;
214
+ while (el && el.localName !== "inline" && el.localName !== "anchor") el = el.parentElement;
215
+ if (el) {
216
+ const ext = firstLocal(el, "extent");
217
+ if (ext) return { cx: ext.getAttribute("cx") || "5486400", cy: ext.getAttribute("cy") || "3200400" };
218
+ }
219
+ return { cx: "5486400", cy: "3200400" };
220
+ }
221
+ async function preprocessDocxCharts(data) {
222
+ let zip;
223
+ try {
224
+ zip = await JSZip.loadAsync(data);
225
+ } catch {
226
+ return data;
227
+ }
228
+ const partNames = Object.keys(zip.files).filter((n) => /^word\/(document|header\d*|footer\d*)\.xml$/.test(n));
229
+ let imgCounter = 0, touched = false;
230
+ const parser = new DOMParser(), serializer = new XMLSerializer();
231
+ for (const partName of partNames) {
232
+ const xmlText = await zip.file(partName).async("string");
233
+ if (!xmlText.includes("drawingml/2006/chart")) continue;
234
+ const doc = parser.parseFromString(xmlText, "application/xml");
235
+ const chartNodes = Array.from(doc.getElementsByTagNameNS(NS.c, "chart")).filter(
236
+ (n) => n.localName === "chart" && n.getAttributeNS(NS.r, "id")
237
+ );
238
+ if (!chartNodes.length) continue;
239
+ const dir = partName.substring(0, partName.lastIndexOf("/") + 1);
240
+ const relsPath = `${dir}_rels/${partName.substring(dir.length)}.rels`;
241
+ const relsFile = zip.file(relsPath);
242
+ if (!relsFile) continue;
243
+ const relsDoc = parser.parseFromString(await relsFile.async("string"), "application/xml");
244
+ const relsRoot = relsDoc.documentElement;
245
+ const relTarget = (id) => {
246
+ const rel = Array.from(relsRoot.children).find((r) => r.getAttribute("Id") === id);
247
+ return rel ? rel.getAttribute("Target") : null;
248
+ };
249
+ const existingIds = new Set(Array.from(relsRoot.children).map((r) => r.getAttribute("Id") || ""));
250
+ const newRelId = () => {
251
+ let i = 1, id = `rIdChart${i}`;
252
+ while (existingIds.has(id)) id = `rIdChart${++i}`;
253
+ existingIds.add(id);
254
+ return id;
255
+ };
256
+ for (const chartNode of chartNodes) {
257
+ const relId = chartNode.getAttributeNS(NS.r, "id");
258
+ const target = relTarget(relId);
259
+ if (!target) continue;
260
+ const chartPath = resolvePath(dir, target);
261
+ const chartFile = zip.file(chartPath);
262
+ if (!chartFile) continue;
263
+ let option = null;
264
+ try {
265
+ const chartDoc = parser.parseFromString(await chartFile.async("string"), "application/xml");
266
+ option = buildOption(chartDoc.documentElement);
267
+ } catch {
268
+ option = null;
269
+ }
270
+ if (!option) continue;
271
+ const { cx, cy } = getExtent(chartNode);
272
+ const wPx = parseInt(cx, 10) / EMU_PER_PX, hPx = parseInt(cy, 10) / EMU_PER_PX;
273
+ let png;
274
+ try {
275
+ const url = await renderChartPng(option, wPx, hPx);
276
+ png = dataUrlToBytes(url);
277
+ } catch {
278
+ continue;
279
+ }
280
+ imgCounter++;
281
+ const imgName = `media/chartimg${imgCounter}.png`;
282
+ zip.file(`word/${imgName}`, png);
283
+ const rId = newRelId();
284
+ const rel = relsDoc.createElementNS(NS.rel, "Relationship");
285
+ rel.setAttribute("Id", rId);
286
+ rel.setAttribute("Type", "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image");
287
+ rel.setAttribute("Target", imgName);
288
+ relsRoot.appendChild(rel);
289
+ const graphicData = chartNode.parentElement;
290
+ if (!graphicData) continue;
291
+ graphicData.setAttribute("uri", NS.pic);
292
+ while (graphicData.firstChild) graphicData.removeChild(graphicData.firstChild);
293
+ graphicData.appendChild(buildPicNode(doc, rId, cx, cy));
294
+ touched = true;
295
+ }
296
+ zip.file(partName, serializer.serializeToString(doc));
297
+ zip.file(relsPath, serializer.serializeToString(relsDoc));
298
+ }
299
+ if (!touched) return data;
300
+ const ctFile = zip.file("[Content_Types].xml");
301
+ if (ctFile) {
302
+ const ctDoc = parser.parseFromString(await ctFile.async("string"), "application/xml");
303
+ const hasPng = Array.from(ctDoc.documentElement.children).some(
304
+ (e) => e.localName === "Default" && e.getAttribute("Extension")?.toLowerCase() === "png"
305
+ );
306
+ if (!hasPng) {
307
+ const def = ctDoc.createElementNS(NS.ct, "Default");
308
+ def.setAttribute("Extension", "png");
309
+ def.setAttribute("ContentType", "image/png");
310
+ ctDoc.documentElement.appendChild(def);
311
+ zip.file("[Content_Types].xml", serializer.serializeToString(ctDoc));
312
+ }
313
+ }
314
+ return zip.generateAsync({ type: "blob" });
315
+ }
316
+ function stampPageNumbers(container) {
317
+ const sections = Array.from(container.querySelectorAll("section"));
318
+ if (sections.length === 0) return { sections: [], labels: [] };
319
+ const total = sections.length;
320
+ const labels = [];
321
+ sections.forEach((section, i) => {
322
+ section.style.boxShadow = "0 1px 4px 0 rgba(15,23,42,0.08), 0 0 0 1px rgba(15,23,42,0.04)";
323
+ const label = document.createElement("div");
324
+ label.setAttribute("aria-hidden", "true");
325
+ label.style.display = "flex";
326
+ label.style.justifyContent = "center";
327
+ label.style.alignItems = "center";
328
+ label.style.padding = "10px 0 26px";
329
+ label.style.fontSize = "12px";
330
+ label.style.color = "#94a3b8";
331
+ label.style.userSelect = "none";
332
+ label.style.letterSpacing = "0.04em";
333
+ label.textContent = `Page ${i + 1} of ${total}`;
334
+ section.insertAdjacentElement("afterend", label);
335
+ labels.push(label);
336
+ });
337
+ return { sections, labels };
338
+ }
339
+ function stylePages(sections, labels, mode, activeIndex) {
340
+ sections.forEach((s, i) => {
341
+ s.style.display = mode === "navigation" && i !== activeIndex ? "none" : "";
342
+ });
343
+ labels.forEach((l) => {
344
+ l.style.display = mode === "navigation" ? "none" : "";
345
+ });
346
+ }
347
+ function DocxViewer({
348
+ data,
349
+ mode = "scroll",
350
+ className,
351
+ onError
352
+ }) {
353
+ const containerRef = useRef(null);
354
+ const sectionsRef = useRef([]);
355
+ const labelsRef = useRef([]);
356
+ const currentPageRef = useRef(0);
357
+ const modeRef = useRef(mode);
358
+ const [loading, setLoading] = useState(true);
359
+ const [error, setError] = useState(null);
360
+ const [currentPage, setCurrentPage] = useState(0);
361
+ const [totalPages, setTotalPages] = useState(0);
362
+ useEffect(() => {
363
+ modeRef.current = mode;
364
+ }, [mode]);
365
+ const isNavMode = mode === "navigation";
366
+ const canPrev = currentPage > 0;
367
+ const canNext = currentPage < totalPages - 1;
368
+ const goToPage = useCallback((index) => {
369
+ const sections = sectionsRef.current;
370
+ if (!sections.length || index < 0 || index >= sections.length) return;
371
+ stylePages(sections, labelsRef.current, "navigation", index);
372
+ currentPageRef.current = index;
373
+ setCurrentPage(index);
374
+ containerRef.current?.scrollIntoView({ block: "start", behavior: "smooth" });
375
+ }, []);
376
+ const applyMode = useCallback((m) => {
377
+ const sections = sectionsRef.current;
378
+ if (!sections.length) return;
379
+ const idx = Math.min(currentPageRef.current, sections.length - 1);
380
+ stylePages(sections, labelsRef.current, m, idx);
381
+ if (m === "navigation") {
382
+ currentPageRef.current = idx;
383
+ setCurrentPage(idx);
384
+ }
385
+ }, []);
386
+ useEffect(() => {
387
+ applyMode(mode);
388
+ }, [mode, applyMode]);
389
+ useEffect(() => {
390
+ if (mode !== "navigation") return;
391
+ const handleKey = (e) => {
392
+ if (e.target instanceof HTMLSelectElement) return;
393
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") {
394
+ e.preventDefault();
395
+ goToPage(currentPageRef.current + 1);
396
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
397
+ e.preventDefault();
398
+ goToPage(currentPageRef.current - 1);
399
+ }
400
+ };
401
+ window.addEventListener("keydown", handleKey);
402
+ return () => window.removeEventListener("keydown", handleKey);
403
+ }, [mode, goToPage]);
404
+ useEffect(() => {
405
+ let cancelled = false;
406
+ setLoading(true);
407
+ setError(null);
408
+ (async () => {
409
+ try {
410
+ const prepared = await preprocessDocxCharts(data.slice(0));
411
+ if (cancelled || !containerRef.current) return;
412
+ containerRef.current.innerHTML = "";
413
+ sectionsRef.current = [];
414
+ labelsRef.current = [];
415
+ await renderAsync(prepared, containerRef.current, void 0, {
416
+ className: "docx",
417
+ inWrapper: true,
418
+ ignoreWidth: false,
419
+ ignoreHeight: false,
420
+ breakPages: true,
421
+ experimental: true,
422
+ renderHeaders: true,
423
+ renderFooters: true,
424
+ renderFootnotes: true,
425
+ renderEndnotes: true,
426
+ useBase64URL: true
427
+ });
428
+ if (!cancelled && containerRef.current) {
429
+ const { sections, labels } = stampPageNumbers(containerRef.current);
430
+ sectionsRef.current = sections;
431
+ labelsRef.current = labels;
432
+ setTotalPages(sections.length);
433
+ const m = modeRef.current;
434
+ const idx = Math.min(currentPageRef.current, sections.length - 1);
435
+ stylePages(sections, labels, m, idx);
436
+ if (m === "navigation") {
437
+ currentPageRef.current = idx;
438
+ setCurrentPage(idx);
439
+ }
440
+ }
441
+ } catch (e) {
442
+ const err = e instanceof Error ? e : new Error("Failed to render document");
443
+ if (!cancelled) {
444
+ setError(err.message);
445
+ onError?.(err);
446
+ }
447
+ } finally {
448
+ if (!cancelled) setLoading(false);
449
+ }
450
+ })();
451
+ return () => {
452
+ cancelled = true;
453
+ };
454
+ }, [data, onError]);
455
+ return /* @__PURE__ */ jsxs("div", { className: `ov-docx${className ? ` ${className}` : ""}`, children: [
456
+ loading && /* @__PURE__ */ jsxs("div", { className: "ov-docx__loading", children: [
457
+ /* @__PURE__ */ jsx(Loader2, { className: "ov-docx__loading-icon" }),
458
+ /* @__PURE__ */ jsx("span", { className: "ov-docx__loading-text", children: "Rendering document\u2026" })
459
+ ] }),
460
+ error && /* @__PURE__ */ jsx("div", { className: "ov-docx__error", children: error }),
461
+ /* @__PURE__ */ jsx(
462
+ "div",
463
+ {
464
+ className: "ov-docx__body",
465
+ style: { visibility: loading ? "hidden" : "visible" },
466
+ children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: "ov-docx__render" })
467
+ }
468
+ ),
469
+ isNavMode && !loading && totalPages > 0 && /* @__PURE__ */ jsxs("div", { className: "ov-docx__nav", children: [
470
+ /* @__PURE__ */ jsx(
471
+ "button",
472
+ {
473
+ onClick: () => goToPage(currentPageRef.current - 1),
474
+ disabled: !canPrev,
475
+ "aria-label": "Previous page",
476
+ className: "ov-docx__nav-btn",
477
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "ov-docx__nav-btn-icon", strokeWidth: 2 })
478
+ }
479
+ ),
480
+ /* @__PURE__ */ jsxs("div", { className: "ov-docx__nav-counter", children: [
481
+ /* @__PURE__ */ jsx("span", { className: "ov-docx__nav-current", children: currentPage + 1 }),
482
+ /* @__PURE__ */ jsxs("span", { className: "ov-docx__nav-total", children: [
483
+ " of ",
484
+ totalPages
485
+ ] })
486
+ ] }),
487
+ /* @__PURE__ */ jsx(
488
+ "button",
489
+ {
490
+ onClick: () => goToPage(currentPageRef.current + 1),
491
+ disabled: !canNext,
492
+ "aria-label": "Next page",
493
+ className: "ov-docx__nav-btn",
494
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "ov-docx__nav-btn-icon", strokeWidth: 2 })
495
+ }
496
+ )
497
+ ] })
498
+ ] });
499
+ }
500
+
501
+ // src/plugins/DocxPlugin.tsx
502
+ import { jsx as jsx2 } from "react/jsx-runtime";
503
+ function DocxPluginComponent({ data, mode, onError }) {
504
+ return /* @__PURE__ */ jsx2(
505
+ DocxViewer,
506
+ {
507
+ data,
508
+ mode: mode || "scroll",
509
+ onError
510
+ }
511
+ );
512
+ }
513
+ var DocxPlugin = {
514
+ id: "docx",
515
+ detect: (ext, mime) => ext === "docx" || mime.includes("wordprocessingml"),
516
+ viewModes: [
517
+ { value: "scroll", label: "Continuous Scroll" },
518
+ { value: "navigation", label: "Page Navigation" }
519
+ ],
520
+ defaultMode: "scroll",
521
+ needsClip: () => false,
522
+ component: DocxPluginComponent
523
+ };
524
+
525
+ // src/styles/PptxViewer.css
526
+ styleInject('.ov-pptx {\n position: relative;\n display: flex;\n flex-direction: column;\n align-items: center;\n padding-inline: 1rem;\n padding-block: 1.5rem;\n}\n.ov-pptx--nav {\n flex-direction: row;\n height: 100%;\n padding: 0;\n justify-content: center;\n}\n.ov-pptx__loading {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 0.5rem;\n color: var(--ov-muted-fg);\n padding-block: 4rem;\n}\n.ov-pptx__loading--nav {\n position: absolute;\n inset: 0;\n justify-content: center;\n padding: 0;\n}\n.ov-pptx__loading-icon {\n width: 1.5rem;\n height: 1.5rem;\n animation: ov-spin 1s linear infinite;\n}\n.ov-pptx__loading-text {\n font-size: 0.875rem;\n}\n.ov-pptx__error {\n margin-top: 1rem;\n border-radius: 0.5rem;\n border: 1px solid color-mix(in oklch, var(--ov-destructive) 30%, transparent);\n background: color-mix(in oklch, var(--ov-destructive) 5%, transparent);\n padding: 1rem 1.5rem;\n font-size: 0.875rem;\n color: var(--ov-destructive);\n}\n.ov-pptx__slide-area {\n width: 100%;\n max-width: 87.5rem;\n}\n.ov-pptx__host {\n width: 100%;\n}\n.ov-pptx__nav-btn {\n position: absolute;\n top: 50%;\n z-index: 10;\n display: flex;\n width: 3.75rem;\n height: 3.75rem;\n transform: translateY(-50%);\n align-items: center;\n justify-content: center;\n border-radius: 9999px;\n border: 2px solid #e2e8f0;\n background: #ffffff;\n box-shadow: 0 2px 8px rgba(15, 23, 42, 0.14), 0 1px 3px rgba(15, 23, 42, 0.10);\n transition: transform 150ms cubic-bezier(0.4, 0, 0.2, 1), border-color 150ms cubic-bezier(0.4, 0, 0.2, 1);\n cursor: pointer;\n}\n.ov-pptx__nav-btn:hover {\n transform: translateY(-50%) scale(1.05);\n border-color: #cbd5e1;\n}\n.ov-pptx__nav-btn:disabled {\n cursor: not-allowed;\n opacity: 0.25;\n}\n.ov-pptx__nav-btn--prev {\n left: 1.25rem;\n}\n.ov-pptx__nav-btn--next {\n right: 1.25rem;\n}\n.ov-pptx__nav-btn-icon {\n width: 1.5rem;\n height: 1.5rem;\n color: #1e293b;\n}\n.ov-pptx__nav-counter {\n position: absolute;\n bottom: 1.5rem;\n left: 50%;\n transform: translateX(-50%);\n min-width: 7.5rem;\n border-radius: 9999px;\n border: 1px solid #e2e8f0;\n background: #ffffff;\n padding: 0.4375rem 1.25rem;\n text-align: center;\n box-shadow: 0 1px 3px 0 rgba(15, 23, 42, 0.07);\n}\n.ov-pptx__nav-current {\n font-size: 13px;\n font-weight: 600;\n color: #334155;\n}\n.ov-pptx__nav-total {\n font-size: 13px;\n color: #94a3b8;\n}\n.ov-container .pptx-host .pptx-preview-wrapper {\n background: transparent !important;\n margin: 0 auto;\n}\n.ov-container .pptx-host .pptx-preview-slide-wrapper {\n box-shadow:\n 0 1px 3px oklch(0 0 0 / 0.1),\n 0 8px 20px oklch(0 0 0 / 0.16),\n 0 24px 48px oklch(0 0 0 / 0.1);\n margin: 0 auto 1.25rem auto !important;\n background: #fff;\n border-radius: 2px;\n overflow: hidden;\n}\n.ov-container .pptx-host img {\n max-width: none;\n height: revert;\n display: inline;\n vertical-align: unset;\n}\n.ov-container .pptx-host .slide-wrapper,\n.ov-container .pptx-host .slide-layout-wrapper {\n font-family:\n "Calibri",\n "Calibri Light",\n Arial,\n Helvetica,\n sans-serif;\n}\n.ov-container .pptx-host .slide-wrapper,\n.ov-container .pptx-host .slide-layout-wrapper {\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-rendering: optimizeLegibility;\n}\n.ov-container .pptx-host .text-wrapper > div {\n margin-top: 0 !important;\n}\n');
527
+
528
+ // src/viewers/PptxViewer.tsx
529
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
530
+ import { init as initPptx } from "pptx-preview";
531
+ import { ChevronLeft as ChevronLeft2, ChevronRight as ChevronRight2, Loader2 as Loader22 } from "lucide-react";
532
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
533
+ function fixImageSizing(host) {
534
+ host.querySelectorAll("img").forEach((img) => {
535
+ const w = img.getAttribute("width");
536
+ const h = img.getAttribute("height");
537
+ if (w && !img.style.width) img.style.setProperty("width", `${w}px`);
538
+ if (h && !img.style.height) img.style.setProperty("height", `${h}px`);
539
+ });
540
+ }
541
+ function fixTextRendering(host) {
542
+ host.querySelectorAll(".text-wrapper span").forEach((span) => {
543
+ if (!span.style.fontFamily && parseFloat(span.style.letterSpacing) < 0) {
544
+ span.style.letterSpacing = "";
545
+ }
546
+ });
547
+ host.querySelectorAll(".text-wrapper p").forEach((p) => {
548
+ if (p.style.lineHeight === "1") p.style.lineHeight = "1.15";
549
+ });
550
+ }
551
+ function styleSlides(slides, mode, activeIndex) {
552
+ slides.forEach((s, i) => {
553
+ if (mode === "navigation") {
554
+ s.style.display = i === activeIndex ? "" : "none";
555
+ s.style.marginBottom = "0";
556
+ s.style.marginTop = "0";
557
+ } else {
558
+ s.style.display = "";
559
+ s.style.marginBottom = "";
560
+ s.style.marginTop = "";
561
+ }
562
+ });
563
+ }
564
+ function PptxViewer({
565
+ data,
566
+ mode = "scroll",
567
+ className,
568
+ onError
569
+ }) {
570
+ const outerRef = useRef2(null);
571
+ const slideAreaRef = useRef2(null);
572
+ const hostRef = useRef2(null);
573
+ const slidesRef = useRef2([]);
574
+ const currentSlideRef = useRef2(0);
575
+ const modeRef = useRef2(mode);
576
+ const [loading, setLoading] = useState2(false);
577
+ const [error, setError] = useState2(null);
578
+ const [currentSlide, setCurrentSlide] = useState2(0);
579
+ const [totalSlides, setTotalSlides] = useState2(0);
580
+ useEffect2(() => {
581
+ modeRef.current = mode;
582
+ }, [mode]);
583
+ const isNavMode = mode === "navigation";
584
+ const canPrev = currentSlide > 0;
585
+ const canNext = currentSlide < totalSlides - 1;
586
+ const goToSlide = useCallback2((index) => {
587
+ const slides = slidesRef.current;
588
+ if (!slides.length || index < 0 || index >= slides.length) return;
589
+ styleSlides(slides, "navigation", index);
590
+ currentSlideRef.current = index;
591
+ setCurrentSlide(index);
592
+ }, []);
593
+ const applyMode = useCallback2((m) => {
594
+ const slides = slidesRef.current;
595
+ if (!slides.length) return;
596
+ const idx = Math.min(currentSlideRef.current, slides.length - 1);
597
+ styleSlides(slides, m, idx);
598
+ if (m === "navigation") {
599
+ currentSlideRef.current = idx;
600
+ setCurrentSlide(idx);
601
+ }
602
+ }, []);
603
+ useEffect2(() => {
604
+ applyMode(mode);
605
+ }, [mode, applyMode]);
606
+ useEffect2(() => {
607
+ if (mode !== "navigation") return;
608
+ const handleKey = (e) => {
609
+ if (e.target instanceof HTMLSelectElement) return;
610
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") {
611
+ e.preventDefault();
612
+ goToSlide(currentSlideRef.current + 1);
613
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
614
+ e.preventDefault();
615
+ goToSlide(currentSlideRef.current - 1);
616
+ }
617
+ };
618
+ window.addEventListener("keydown", handleKey);
619
+ return () => window.removeEventListener("keydown", handleKey);
620
+ }, [mode, goToSlide]);
621
+ const renderPptx = useCallback2(
622
+ async (containerWidth) => {
623
+ const host = hostRef.current;
624
+ if (!host || containerWidth === 0) return;
625
+ setLoading(true);
626
+ setError(null);
627
+ host.innerHTML = "";
628
+ slidesRef.current = [];
629
+ try {
630
+ const previewer = initPptx(host, { width: containerWidth, mode: "list" });
631
+ await previewer.preview(data.slice(0));
632
+ fixImageSizing(host);
633
+ fixTextRendering(host);
634
+ const slides = Array.from(
635
+ host.querySelectorAll(".pptx-preview-slide-wrapper")
636
+ );
637
+ slidesRef.current = slides;
638
+ setTotalSlides(slides.length);
639
+ const m = modeRef.current;
640
+ const idx = Math.min(currentSlideRef.current, slides.length - 1);
641
+ styleSlides(slides, m, idx);
642
+ if (m === "navigation") {
643
+ currentSlideRef.current = idx;
644
+ setCurrentSlide(idx);
645
+ }
646
+ } catch (e) {
647
+ const err = e instanceof Error ? e : new Error("Failed to render presentation");
648
+ setError(err.message);
649
+ onError?.(err);
650
+ } finally {
651
+ setLoading(false);
652
+ }
653
+ },
654
+ [data, onError]
655
+ );
656
+ useEffect2(() => {
657
+ let cancelled = false;
658
+ let lastWidth = 0;
659
+ let ro = null;
660
+ let debounceTimer;
661
+ function triggerRender() {
662
+ if (cancelled || !hostRef.current) return;
663
+ const w = Math.floor(hostRef.current.getBoundingClientRect().width);
664
+ if (w === 0 || w === lastWidth) return;
665
+ lastWidth = w;
666
+ renderPptx(w);
667
+ }
668
+ const rafId = requestAnimationFrame(() => {
669
+ if (cancelled) return;
670
+ triggerRender();
671
+ if (slideAreaRef.current) {
672
+ ro = new ResizeObserver(() => {
673
+ clearTimeout(debounceTimer);
674
+ debounceTimer = setTimeout(triggerRender, 250);
675
+ });
676
+ ro.observe(slideAreaRef.current);
677
+ }
678
+ });
679
+ return () => {
680
+ cancelled = true;
681
+ cancelAnimationFrame(rafId);
682
+ clearTimeout(debounceTimer);
683
+ ro?.disconnect();
684
+ };
685
+ }, [data, renderPptx]);
686
+ return /* @__PURE__ */ jsxs2(
687
+ "div",
688
+ {
689
+ ref: outerRef,
690
+ className: `ov-pptx${isNavMode ? " ov-pptx--nav" : ""}${className ? ` ${className}` : ""}`,
691
+ children: [
692
+ loading && /* @__PURE__ */ jsxs2("div", { className: `ov-pptx__loading${isNavMode ? " ov-pptx__loading--nav" : ""}`, children: [
693
+ /* @__PURE__ */ jsx3(Loader22, { className: "ov-pptx__loading-icon" }),
694
+ /* @__PURE__ */ jsx3("span", { className: "ov-pptx__loading-text", children: "Rendering slides\u2026" })
695
+ ] }),
696
+ error && /* @__PURE__ */ jsx3("div", { className: "ov-pptx__error", children: error }),
697
+ /* @__PURE__ */ jsx3(
698
+ "div",
699
+ {
700
+ ref: slideAreaRef,
701
+ className: "ov-pptx__slide-area",
702
+ style: { visibility: loading ? "hidden" : "visible" },
703
+ children: /* @__PURE__ */ jsx3("div", { ref: hostRef, className: "pptx-host ov-pptx__host" })
704
+ }
705
+ ),
706
+ isNavMode && !loading && totalSlides > 0 && /* @__PURE__ */ jsxs2(Fragment, { children: [
707
+ /* @__PURE__ */ jsx3(
708
+ "button",
709
+ {
710
+ onClick: () => goToSlide(currentSlideRef.current - 1),
711
+ disabled: !canPrev,
712
+ "aria-label": "Previous slide",
713
+ className: "ov-pptx__nav-btn ov-pptx__nav-btn--prev",
714
+ children: /* @__PURE__ */ jsx3(ChevronLeft2, { className: "ov-pptx__nav-btn-icon", strokeWidth: 2 })
715
+ }
716
+ ),
717
+ /* @__PURE__ */ jsx3(
718
+ "button",
719
+ {
720
+ onClick: () => goToSlide(currentSlideRef.current + 1),
721
+ disabled: !canNext,
722
+ "aria-label": "Next slide",
723
+ className: "ov-pptx__nav-btn ov-pptx__nav-btn--next",
724
+ children: /* @__PURE__ */ jsx3(ChevronRight2, { className: "ov-pptx__nav-btn-icon", strokeWidth: 2 })
725
+ }
726
+ ),
727
+ /* @__PURE__ */ jsxs2("div", { className: "ov-pptx__nav-counter", children: [
728
+ /* @__PURE__ */ jsx3("span", { className: "ov-pptx__nav-current", children: currentSlide + 1 }),
729
+ /* @__PURE__ */ jsxs2("span", { className: "ov-pptx__nav-total", children: [
730
+ " of ",
731
+ totalSlides
732
+ ] })
733
+ ] })
734
+ ] })
735
+ ]
736
+ }
737
+ );
738
+ }
739
+
740
+ // src/plugins/PptxPlugin.tsx
741
+ import { jsx as jsx4 } from "react/jsx-runtime";
742
+ function PptxPluginComponent({ data, mode, onError }) {
743
+ return /* @__PURE__ */ jsx4(
744
+ PptxViewer,
745
+ {
746
+ data,
747
+ mode: mode || "scroll",
748
+ onError
749
+ }
750
+ );
751
+ }
752
+ var PptxPlugin = {
753
+ id: "pptx",
754
+ detect: (ext, mime) => ext === "pptx" || mime.includes("presentationml"),
755
+ viewModes: [
756
+ { value: "scroll", label: "Vertical Scroll" },
757
+ { value: "navigation", label: "Slide Navigation" }
758
+ ],
759
+ defaultMode: "scroll",
760
+ needsClip: (mode) => mode === "navigation",
761
+ component: PptxPluginComponent
762
+ };
763
+
764
+ // src/styles/XlsxViewer.css
765
+ styleInject(".ov-xlsx__loading {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 0.5rem;\n padding-block: 6rem;\n color: var(--ov-muted-fg);\n}\n.ov-xlsx__loading-icon {\n width: 1.5rem;\n height: 1.5rem;\n animation: ov-spin 1s linear infinite;\n}\n.ov-xlsx__loading-text {\n font-size: 0.875rem;\n}\n.ov-xlsx__error {\n margin-inline: auto;\n margin-top: 3rem;\n width: fit-content;\n border-radius: 0.5rem;\n border: 1px solid color-mix(in oklch, var(--ov-destructive) 30%, transparent);\n background: color-mix(in oklch, var(--ov-destructive) 5%, transparent);\n padding: 1rem 1.5rem;\n font-size: 0.875rem;\n color: var(--ov-destructive);\n}\n.ov-xlsx {\n display: flex;\n height: 100%;\n flex-direction: column;\n}\n.ov-xlsx__grid-area {\n flex: 1;\n min-height: 0;\n overflow: auto;\n background: var(--ov-page);\n}\n.ov-xlsx__table {\n border-collapse: collapse;\n font-size: 20px;\n}\n.ov-xlsx__corner {\n position: sticky;\n left: 0;\n top: 0;\n z-index: 20;\n height: 2rem;\n border: 1px solid var(--ov-grid-line);\n background: var(--ov-grid-header);\n}\n.ov-xlsx__col-header {\n position: sticky;\n top: 0;\n z-index: 10;\n height: 2rem;\n border: 1px solid var(--ov-grid-line);\n background: var(--ov-grid-header);\n text-align: center;\n font-size: 16px;\n font-weight: 600;\n color: var(--ov-muted-fg);\n}\n.ov-xlsx__row-header {\n position: sticky;\n left: 0;\n z-index: 10;\n border: 1px solid var(--ov-grid-line);\n background: var(--ov-grid-header);\n text-align: center;\n font-size: 16px;\n font-weight: 500;\n color: var(--ov-muted-fg);\n}\n.ov-xlsx__cell {\n overflow: hidden;\n border: 1px solid var(--ov-grid-line);\n padding-inline: 0.375rem;\n vertical-align: middle;\n}\n.ov-container .ov-xlsx__cell[data-ov-bold] {\n font-weight: 600 !important;\n}\n.ov-xlsx__cell-accounting {\n display: flex;\n width: 100%;\n align-items: center;\n overflow: hidden;\n}\n.ov-xlsx__cell-text {\n display: block;\n overflow: hidden;\n}\n.ov-xlsx__tabbar {\n display: flex;\n height: 3rem;\n flex-shrink: 0;\n align-items: flex-end;\n border-top: 1px solid #c0c0c0;\n background: #d6d6d6;\n}\n.ov-xlsx__tabnav {\n display: flex;\n flex-shrink: 0;\n align-self: stretch;\n align-items: center;\n border-right: 1px solid #d0d0d0;\n padding-inline: 0.375rem;\n}\n.ov-xlsx__tabnav-btn {\n display: flex;\n width: 1.75rem;\n height: 1.75rem;\n align-items: center;\n justify-content: center;\n border-radius: 0.25rem;\n color: #666;\n cursor: pointer;\n transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1);\n}\n.ov-xlsx__tabnav-btn:hover {\n background: #e0e0e0;\n}\n.ov-xlsx__tabnav-icon {\n width: 1rem;\n height: 1rem;\n}\n.ov-xlsx__tabs {\n display: flex;\n flex: 1;\n align-items: flex-end;\n overflow-x: auto;\n scrollbar-width: none;\n}\n.ov-xlsx__tab {\n position: relative;\n display: flex;\n flex-shrink: 0;\n max-width: 16rem;\n align-items: center;\n padding-inline: 1.5rem;\n font-size: 15px;\n font-weight: 500;\n user-select: none;\n cursor: pointer;\n height: 2.25rem;\n border: 1px solid #c8c8c8;\n background: #e4e4e4;\n color: #5a5a5a;\n transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1), color 150ms cubic-bezier(0.4, 0, 0.2, 1);\n}\n.ov-xlsx__tab + .ov-xlsx__tab {\n margin-left: -1px;\n}\n.ov-xlsx__tab:hover {\n background: #d8d8d8;\n color: #1a1a1a;\n}\n.ov-xlsx__tab--active {\n margin-top: -1px;\n z-index: 10;\n height: 3rem;\n background: #ffffff;\n font-weight: 600;\n color: #1a1a1a;\n border: 1px solid #a8a8a8;\n border-bottom: 1px solid #ffffff;\n}\n.ov-xlsx__tab-name {\n max-width: 13rem;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n");
766
+
767
+ // src/viewers/XlsxViewer.tsx
768
+ import { useEffect as useEffect3, useMemo, useRef as useRef3, useState as useState3 } from "react";
769
+ import ExcelJS from "exceljs";
770
+ import * as XLSX from "xlsx";
771
+ import { ChevronLeft as ChevronLeft3, ChevronRight as ChevronRight3, Loader2 as Loader23 } from "lucide-react";
772
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
773
+ function colLetter(n) {
774
+ let s = "";
775
+ while (n > 0) {
776
+ const m = (n - 1) % 26;
777
+ s = String.fromCharCode(65 + m) + s;
778
+ n = Math.floor((n - 1) / 26);
779
+ }
780
+ return s;
781
+ }
782
+ function letterToNum(letters) {
783
+ let n = 0;
784
+ for (const ch of letters) n = n * 26 + (ch.charCodeAt(0) - 64);
785
+ return n;
786
+ }
787
+ var THEME_COLORS = [
788
+ "#FFFFFF",
789
+ // 0: lt1 (background)
790
+ "#000000",
791
+ // 1: dk1 (text / borders)
792
+ "#E7E6E6",
793
+ // 2: lt2
794
+ "#44546A",
795
+ // 3: dk2
796
+ "#4472C4",
797
+ // 4: accent1
798
+ "#ED7D31",
799
+ // 5: accent2
800
+ "#A5A5A5",
801
+ // 6: accent3
802
+ "#FFC000",
803
+ // 7: accent4
804
+ "#5B9BD5",
805
+ // 8: accent5
806
+ "#70AD47"
807
+ // 9: accent6
808
+ ];
809
+ var INDEXED_COLORS = [
810
+ "#000000",
811
+ "#FFFFFF",
812
+ "#FF0000",
813
+ "#00FF00",
814
+ "#0000FF",
815
+ "#FFFF00",
816
+ "#FF00FF",
817
+ "#00FFFF",
818
+ "#000000",
819
+ "#FFFFFF",
820
+ "#FF0000",
821
+ "#00FF00",
822
+ "#0000FF",
823
+ "#FFFF00",
824
+ "#FF00FF",
825
+ "#00FFFF",
826
+ "#800000",
827
+ "#008000",
828
+ "#000080",
829
+ "#808000",
830
+ "#800080",
831
+ "#008080",
832
+ "#C0C0C0",
833
+ "#808080",
834
+ "#9999FF",
835
+ "#993366",
836
+ "#FFFFCC",
837
+ "#CCFFFF",
838
+ "#660066",
839
+ "#FF8080",
840
+ "#0066CC",
841
+ "#CCCCFF",
842
+ "#000080",
843
+ "#FF00FF",
844
+ "#FFFF00",
845
+ "#00FFFF",
846
+ "#800080",
847
+ "#800000",
848
+ "#008080",
849
+ "#0000FF",
850
+ "#00CCFF",
851
+ "#CCFFFF",
852
+ "#CCFFCC",
853
+ "#FFFF99",
854
+ "#99CCFF",
855
+ "#FF99CC",
856
+ "#CC99FF",
857
+ "#FFCC99",
858
+ "#3366FF",
859
+ "#33CCCC",
860
+ "#99CC00",
861
+ "#FFCC00",
862
+ "#FF9900",
863
+ "#FF6600",
864
+ "#666699",
865
+ "#969696",
866
+ "#003366",
867
+ "#339966",
868
+ "#003300",
869
+ "#333300",
870
+ "#993300",
871
+ "#993366",
872
+ "#333399",
873
+ "#333333"
874
+ ];
875
+ function argbToCss(color) {
876
+ if (!color) return void 0;
877
+ if (color.argb) {
878
+ const argb = color.argb;
879
+ const hex = argb.length === 8 ? argb.slice(2) : argb;
880
+ if (!/^[0-9a-fA-F]{6}$/.test(hex)) return void 0;
881
+ return `#${hex}`;
882
+ }
883
+ if (color.theme !== void 0) return THEME_COLORS[color.theme];
884
+ if (color.indexed !== void 0) return color.indexed === 64 ? "#000000" : INDEXED_COLORS[color.indexed];
885
+ return void 0;
886
+ }
887
+ var SCALE = 1.5;
888
+ var JS_DATE_TOSTRING_RE = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s/;
889
+ function excelSerialToDate(serial) {
890
+ return new Date(Math.round((serial - 25569) * 864e5));
891
+ }
892
+ function fmtDate(d) {
893
+ return `${d.getUTCMonth() + 1}/${d.getUTCDate()}/${d.getUTCFullYear()}`;
894
+ }
895
+ var MONTH_LONG = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
896
+ var MONTH_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
897
+ var DAY_LONG = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
898
+ var DAY_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
899
+ function serialToDateParts(serial) {
900
+ const frac = serial - Math.floor(serial);
901
+ const totalSec = Math.round(frac * 86400);
902
+ const h = Math.floor(totalSec / 3600);
903
+ const mi = Math.floor(totalSec % 3600 / 60);
904
+ const s = totalSec % 60;
905
+ const ds = Math.floor(serial);
906
+ if (ds <= 0) return { y: 1900, mo: 1, d: ds, dow: 6, h, mi, s };
907
+ if (ds === 60) return { y: 1900, mo: 2, d: 29, dow: 3, h, mi, s };
908
+ const adj = ds < 60 ? ds + 1 : ds;
909
+ const js = new Date(Math.round((adj - 25569) * 864e5));
910
+ return { y: js.getUTCFullYear(), mo: js.getUTCMonth() + 1, d: js.getUTCDate(), dow: js.getUTCDay(), h, mi, s };
911
+ }
912
+ function applyDateFmt(fmt, p) {
913
+ const section = fmt.split(";")[0];
914
+ const unquoted = section.replace(/"[^"]*"/g, "").replace(/\[.*?\]/g, "");
915
+ const hasHour = /h/i.test(unquoted);
916
+ const hasAmPm = /am\/pm|a\/p/i.test(unquoted);
917
+ let out = "", i = 0;
918
+ let prevHour = false;
919
+ while (i < section.length) {
920
+ if (section[i] === "[") {
921
+ const end = section.indexOf("]", i);
922
+ if (end >= 0) {
923
+ i = end + 1;
924
+ continue;
925
+ }
926
+ }
927
+ if (section[i] === "_") {
928
+ i += 2;
929
+ continue;
930
+ }
931
+ if (section[i] === "*") {
932
+ i += 2;
933
+ continue;
934
+ }
935
+ if (section[i] === '"') {
936
+ const end = section.indexOf('"', i + 1);
937
+ if (end < 0) {
938
+ out += section.slice(i + 1);
939
+ break;
940
+ }
941
+ out += section.slice(i + 1, end);
942
+ i = end + 1;
943
+ continue;
944
+ }
945
+ if (section[i] === "\\") {
946
+ out += section[i + 1] ?? "";
947
+ i += 2;
948
+ continue;
949
+ }
950
+ const rest = section.slice(i);
951
+ if (/^AM\/PM/i.test(rest)) {
952
+ out += p.h >= 12 ? "PM" : "AM";
953
+ i += 5;
954
+ prevHour = false;
955
+ continue;
956
+ }
957
+ if (/^A\/P/i.test(rest)) {
958
+ out += p.h >= 12 ? "P" : "A";
959
+ i += 3;
960
+ prevHour = false;
961
+ continue;
962
+ }
963
+ if (/^hh/i.test(rest)) {
964
+ const hr = hasAmPm ? p.h % 12 || 12 : p.h;
965
+ out += String(hr).padStart(2, "0");
966
+ i += 2;
967
+ prevHour = true;
968
+ continue;
969
+ }
970
+ if (/^h/i.test(rest)) {
971
+ const hr = hasAmPm ? p.h % 12 || 12 : p.h;
972
+ out += String(hr);
973
+ i += 1;
974
+ prevHour = true;
975
+ continue;
976
+ }
977
+ if (/^ss/i.test(rest)) {
978
+ out += String(p.s).padStart(2, "0");
979
+ i += 2;
980
+ prevHour = false;
981
+ continue;
982
+ }
983
+ if (/^s/i.test(rest)) {
984
+ out += String(p.s);
985
+ i += 1;
986
+ prevHour = false;
987
+ continue;
988
+ }
989
+ if (/^yyyy/i.test(rest)) {
990
+ out += String(p.y).padStart(4, "0");
991
+ i += 4;
992
+ prevHour = false;
993
+ continue;
994
+ }
995
+ if (/^yy/i.test(rest)) {
996
+ out += String(p.y % 100).padStart(2, "0");
997
+ i += 2;
998
+ prevHour = false;
999
+ continue;
1000
+ }
1001
+ if (/^mmmm/i.test(rest)) {
1002
+ out += MONTH_LONG[p.mo - 1] ?? "";
1003
+ i += 4;
1004
+ prevHour = false;
1005
+ continue;
1006
+ }
1007
+ if (/^mmm/i.test(rest)) {
1008
+ out += MONTH_SHORT[p.mo - 1] ?? "";
1009
+ i += 3;
1010
+ prevHour = false;
1011
+ continue;
1012
+ }
1013
+ if (/^mm/i.test(rest)) {
1014
+ if (hasHour) {
1015
+ out += String(p.mi).padStart(2, "0");
1016
+ } else {
1017
+ out += String(p.mo).padStart(2, "0");
1018
+ }
1019
+ i += 2;
1020
+ prevHour = false;
1021
+ continue;
1022
+ }
1023
+ if (/^m/i.test(rest)) {
1024
+ if (hasHour && prevHour) {
1025
+ out += String(p.mi);
1026
+ } else {
1027
+ out += String(p.mo);
1028
+ }
1029
+ i += 1;
1030
+ prevHour = false;
1031
+ continue;
1032
+ }
1033
+ if (/^dddd/i.test(rest)) {
1034
+ out += DAY_LONG[p.dow] ?? "";
1035
+ i += 4;
1036
+ prevHour = false;
1037
+ continue;
1038
+ }
1039
+ if (/^ddd/i.test(rest)) {
1040
+ out += DAY_SHORT[p.dow] ?? "";
1041
+ i += 3;
1042
+ prevHour = false;
1043
+ continue;
1044
+ }
1045
+ if (/^dd/i.test(rest)) {
1046
+ out += String(p.d).padStart(2, "0");
1047
+ i += 2;
1048
+ prevHour = false;
1049
+ continue;
1050
+ }
1051
+ if (/^d/i.test(rest)) {
1052
+ out += String(p.d);
1053
+ i += 1;
1054
+ prevHour = false;
1055
+ continue;
1056
+ }
1057
+ out += section[i];
1058
+ i++;
1059
+ prevHour = false;
1060
+ }
1061
+ return out;
1062
+ }
1063
+ function fmtExcelDate(d, numFmt) {
1064
+ const serial = d.getTime() / 864e5 + 25569;
1065
+ const parts = serialToDateParts(serial);
1066
+ return numFmt && /[yYmMdDhHsS]/.test(numFmt) ? applyDateFmt(numFmt, parts) : fmtDate(d);
1067
+ }
1068
+ function fmtNumber(value, numFmt) {
1069
+ if (!numFmt || numFmt === "General" || numFmt === "@") return null;
1070
+ if (/[yY]/.test(numFmt) || /mmmm?/i.test(numFmt) || /[hH]/.test(numFmt)) return null;
1071
+ const sections = numFmt.split(";");
1072
+ let si = 0;
1073
+ if (value < 0 && sections.length >= 2) si = 1;
1074
+ else if (value === 0 && sections.length >= 3) si = 2;
1075
+ const sec = sections[si];
1076
+ let isPercent = false;
1077
+ {
1078
+ let inQ = false;
1079
+ for (const ch of sec) {
1080
+ if (ch === '"') inQ = !inQ;
1081
+ else if (!inQ && ch === "%") {
1082
+ isPercent = true;
1083
+ break;
1084
+ }
1085
+ }
1086
+ }
1087
+ let prefix = "", suffix = "", numStr = "";
1088
+ let inNum = false;
1089
+ let i = 0;
1090
+ while (i < sec.length) {
1091
+ if (sec[i] === "[") {
1092
+ const e = sec.indexOf("]", i);
1093
+ i = e >= 0 ? e + 1 : i + 1;
1094
+ continue;
1095
+ }
1096
+ if (sec[i] === "_") {
1097
+ i += 2;
1098
+ continue;
1099
+ }
1100
+ if (sec[i] === "*") {
1101
+ i += 2;
1102
+ continue;
1103
+ }
1104
+ if (sec[i] === '"') {
1105
+ const e = sec.indexOf('"', i + 1);
1106
+ const lit = e >= 0 ? sec.slice(i + 1, e) : sec.slice(i + 1);
1107
+ inNum ? suffix += lit : prefix += lit;
1108
+ i = e >= 0 ? e + 1 : sec.length;
1109
+ continue;
1110
+ }
1111
+ if (sec[i] === "\\") {
1112
+ inNum ? suffix += sec[i + 1] ?? "" : prefix += sec[i + 1] ?? "";
1113
+ i += 2;
1114
+ continue;
1115
+ }
1116
+ const ch = sec[i];
1117
+ if ("$\u20AC\xA3\xA5".includes(ch)) {
1118
+ inNum ? suffix += ch : prefix += ch;
1119
+ i++;
1120
+ continue;
1121
+ }
1122
+ if (ch === "(" || ch === ")") {
1123
+ i++;
1124
+ continue;
1125
+ }
1126
+ if ("#0?.".includes(ch) || ch === "," && inNum) {
1127
+ inNum = true;
1128
+ numStr += ch;
1129
+ i++;
1130
+ continue;
1131
+ }
1132
+ inNum ? suffix += ch : prefix += ch;
1133
+ i++;
1134
+ }
1135
+ if (!/[#0]/.test(numStr)) {
1136
+ const lit = (prefix + suffix).replace(/[$€£¥]/g, "").trim();
1137
+ return lit || null;
1138
+ }
1139
+ const dotIdx = numStr.indexOf(".");
1140
+ const decPlaces = dotIdx >= 0 ? (numStr.slice(dotIdx + 1).match(/^[#0?]+/) ?? [""])[0].length : 0;
1141
+ const hasGroup = /[#0],[#0]/.test(numStr);
1142
+ const absVal = Math.abs(isPercent ? value * 100 : value);
1143
+ const formatted = absVal.toLocaleString("en-US", {
1144
+ minimumFractionDigits: decPlaces,
1145
+ maximumFractionDigits: decPlaces,
1146
+ useGrouping: hasGroup
1147
+ });
1148
+ let result = `${prefix}${formatted}${suffix}`;
1149
+ if (value < 0 && si === 0) result = `-${result}`;
1150
+ else if (value < 0 && si === 1 && (sections[1].includes("(") || sections[1].includes(")"))) {
1151
+ result = `(${result})`;
1152
+ }
1153
+ return result;
1154
+ }
1155
+ function getAccountingCurrency(numFmt) {
1156
+ if (!numFmt) return null;
1157
+ if (!/_\([$€£¥]\*/.test(numFmt) && !/_\(\*[$€£¥]/.test(numFmt)) return null;
1158
+ const m = numFmt.match(/[$€£¥]/);
1159
+ return m ? m[0] : null;
1160
+ }
1161
+ function effectiveValue(cell) {
1162
+ const v = cell.value;
1163
+ if (v !== null && typeof v === "object" && !(v instanceof Date) && ("formula" in v || "sharedFormula" in v)) {
1164
+ return v.result ?? null;
1165
+ }
1166
+ return v;
1167
+ }
1168
+ function getCellText(cell) {
1169
+ const val = effectiveValue(cell);
1170
+ if (val !== null && typeof val === "object" && !(val instanceof Date) && "error" in val) {
1171
+ return val.error ?? "";
1172
+ }
1173
+ if (typeof val === "string" && /^#[A-Z0-9\/]+[!?]?$/.test(val)) {
1174
+ return val;
1175
+ }
1176
+ const text = cell.text ?? "";
1177
+ if (val instanceof Date) {
1178
+ if (isNaN(val.getTime())) return "";
1179
+ return fmtExcelDate(val, cell.numFmt ?? "");
1180
+ }
1181
+ if (JS_DATE_TOSTRING_RE.test(text) || text === "Invalid Date") return "";
1182
+ if (typeof val === "number") {
1183
+ const numFmt = cell.numFmt ?? "";
1184
+ const isDateTimeFmt = numFmt && (/[yY]/.test(numFmt) || /mmmm?/i.test(numFmt) || /[hH]/.test(numFmt) || /[dD]/.test(numFmt));
1185
+ if (isDateTimeFmt) {
1186
+ if (val >= 0) {
1187
+ const d = excelSerialToDate(val);
1188
+ if (!isNaN(d.getTime())) return fmtExcelDate(d, numFmt);
1189
+ }
1190
+ return text;
1191
+ }
1192
+ if (numFmt && numFmt !== "General" && numFmt !== "@") {
1193
+ const formatted = fmtNumber(val, numFmt);
1194
+ if (formatted !== null) return formatted;
1195
+ }
1196
+ if (text && !/^-?\d+\.?\d*$/.test(text)) return text;
1197
+ if (!text) return Number.isInteger(val) ? String(val) : val.toLocaleString("en-US");
1198
+ }
1199
+ return text;
1200
+ }
1201
+ function borderStyle(b) {
1202
+ if (!b?.style) return void 0;
1203
+ const color = argbToCss(b.color) || "#000000";
1204
+ const weight = b.style.includes("thick") || b.style.includes("medium") ? "2px" : "1px";
1205
+ const kind = b.style.includes("dash") ? "dashed" : b.style.includes("dot") ? "dotted" : b.style.includes("double") ? "double" : "solid";
1206
+ return `${weight} ${kind} ${color}`;
1207
+ }
1208
+ function buildRawResultMap(ws) {
1209
+ const map = /* @__PURE__ */ new Map();
1210
+ const sheetModel = ws.model;
1211
+ for (const rawRow of sheetModel?.rows ?? []) {
1212
+ const rn = rawRow?.number;
1213
+ if (!rn || !rawRow.cells) continue;
1214
+ for (const rc of rawRow.cells) {
1215
+ if (!rc?.col) continue;
1216
+ if ((rc.type === 6 || rc.formula !== void 0 || rc.sharedFormula !== void 0) && rc.result !== void 0) {
1217
+ map.set(`${rn}:${rc.col}`, rc.result);
1218
+ }
1219
+ }
1220
+ }
1221
+ return map;
1222
+ }
1223
+ function formatRawResult(result, numFmt) {
1224
+ if (result === null || result === void 0) return "";
1225
+ if (typeof result === "object" && !Array.isArray(result) && !(result instanceof Date)) {
1226
+ if ("error" in result) return result.error ?? "";
1227
+ }
1228
+ if (result instanceof Date) {
1229
+ if (isNaN(result.getTime())) return "";
1230
+ return fmtExcelDate(result, numFmt);
1231
+ }
1232
+ if (typeof result === "number") {
1233
+ if (numFmt && numFmt !== "General" && numFmt !== "@") {
1234
+ const f = fmtNumber(result, numFmt);
1235
+ if (f !== null) return f;
1236
+ }
1237
+ return Number.isInteger(result) ? String(result) : result.toLocaleString("en-US");
1238
+ }
1239
+ if (typeof result === "string") return result;
1240
+ if (typeof result === "boolean") return result ? "TRUE" : "FALSE";
1241
+ return String(result);
1242
+ }
1243
+ function buildSheet(ws, sjSheet) {
1244
+ const sjCells = /* @__PURE__ */ new Map();
1245
+ let sjMaxRow = 0;
1246
+ let sjMaxCol = 0;
1247
+ if (sjSheet) {
1248
+ for (const addr of Object.keys(sjSheet)) {
1249
+ if (addr.startsWith("!")) continue;
1250
+ const sjc = sjSheet[addr];
1251
+ if (!sjc || !sjc.t || sjc.t === "z") continue;
1252
+ const m = addr.match(/^([A-Z]+)(\d+)$/);
1253
+ if (!m) continue;
1254
+ const rowNum = parseInt(m[2], 10);
1255
+ const colNum = letterToNum(m[1]);
1256
+ if (rowNum > sjMaxRow) sjMaxRow = rowNum;
1257
+ if (colNum > sjMaxCol) sjMaxCol = colNum;
1258
+ sjCells.set(`${rowNum}:${colNum}`, sjc);
1259
+ }
1260
+ }
1261
+ let originalCols = Math.max(ws.columnCount, sjMaxCol);
1262
+ let cols = Math.max(originalCols, 26);
1263
+ let rowsN = Math.max(ws.rowCount, 50);
1264
+ const exRawModel = ws.model;
1265
+ if (exRawModel?.rows) {
1266
+ for (const rawRow of exRawModel.rows) {
1267
+ if (rawRow?.number) rowsN = Math.max(rowsN, rawRow.number);
1268
+ }
1269
+ }
1270
+ if (sjMaxRow > 0) rowsN = Math.max(rowsN, sjMaxRow);
1271
+ if (sjMaxCol > 0) {
1272
+ originalCols = Math.max(originalCols, sjMaxCol);
1273
+ cols = Math.max(cols, sjMaxCol);
1274
+ }
1275
+ const rawResults = buildRawResultMap(ws);
1276
+ const merges = {};
1277
+ const covered = /* @__PURE__ */ new Set();
1278
+ const mergeList = ws.model.merges || [];
1279
+ for (const range of mergeList) {
1280
+ const [a, b] = range.split(":");
1281
+ const m1 = a.match(/([A-Z]+)(\d+)/);
1282
+ const m2 = b.match(/([A-Z]+)(\d+)/);
1283
+ if (!m1 || !m2) continue;
1284
+ const c1 = letterToNum(m1[1]);
1285
+ const r1 = parseInt(m1[2], 10);
1286
+ const c2 = letterToNum(m2[1]);
1287
+ const r2 = parseInt(m2[2], 10);
1288
+ merges[`${r1}:${c1}`] = { rowSpan: r2 - r1 + 1, colSpan: c2 - c1 + 1 };
1289
+ for (let r = r1; r <= r2; r++)
1290
+ for (let c = c1; c <= c2; c++) if (!(r === r1 && c === c1)) covered.add(`${r}:${c}`);
1291
+ }
1292
+ const colWidths = [];
1293
+ for (let c = 1; c <= cols; c++) {
1294
+ const col = ws.getColumn(c);
1295
+ if (col.hidden) {
1296
+ colWidths[c - 1] = 0;
1297
+ continue;
1298
+ }
1299
+ const w = col.width;
1300
+ colWidths[c - 1] = w ? Math.max(Math.round((w * 7 + 5) * SCALE), 10) : Math.round(64 * SCALE);
1301
+ }
1302
+ const rowHeights = [];
1303
+ const rows = [];
1304
+ for (let r = 1; r <= rowsN; r++) {
1305
+ const row = ws.getRow(r);
1306
+ rowHeights[r - 1] = row.height ? Math.max(Math.round(row.height * 4 / 3 * SCALE), 6) : Math.round(20 * SCALE);
1307
+ const cells = [];
1308
+ const ccIndices = [];
1309
+ for (let c = 1; c <= cols; c++) {
1310
+ if (covered.has(`${r}:${c}`)) {
1311
+ cells.push(null);
1312
+ continue;
1313
+ }
1314
+ const cell = row.getCell(c);
1315
+ const s = cell.style || {};
1316
+ const style = {};
1317
+ let isCenterContinuous = false;
1318
+ if (s.font) {
1319
+ if (s.font.bold) style.fontWeight = 600;
1320
+ if (s.font.italic) style.fontStyle = "italic";
1321
+ {
1322
+ const dec = [];
1323
+ if (s.font.underline) dec.push("underline");
1324
+ if (s.font.strike) dec.push("line-through");
1325
+ if (dec.length) style.textDecoration = dec.join(" ");
1326
+ }
1327
+ if (s.font.size) style.fontSize = `${Math.round(s.font.size * 4 / 3 * SCALE)}px`;
1328
+ if (s.font.name) style.fontFamily = s.font.name;
1329
+ const fc = argbToCss(s.font.color);
1330
+ if (fc) style.color = fc;
1331
+ }
1332
+ if (s.fill && s.fill.type === "pattern" && s.fill.pattern === "solid") {
1333
+ const bg = argbToCss(s.fill.fgColor);
1334
+ if (bg) style.backgroundColor = bg;
1335
+ }
1336
+ if (s.alignment) {
1337
+ const h = s.alignment.horizontal;
1338
+ if (h) {
1339
+ isCenterContinuous = h === "centerContinuous";
1340
+ style.textAlign = isCenterContinuous || h === "distributed" ? "center" : h === "fill" ? "left" : h;
1341
+ }
1342
+ const v = s.alignment.vertical;
1343
+ if (v) {
1344
+ style.verticalAlign = v === "center" || v === "middle" ? "middle" : v === "top" ? "top" : "bottom";
1345
+ }
1346
+ if (s.alignment.wrapText) style.whiteSpace = "normal";
1347
+ if (s.alignment.indent) style.paddingLeft = `${s.alignment.indent * 16 + 6}px`;
1348
+ }
1349
+ if (s.border) {
1350
+ const t = borderStyle(s.border.top);
1351
+ const l = borderStyle(s.border.left);
1352
+ const bo = borderStyle(s.border.bottom);
1353
+ const ri = borderStyle(s.border.right);
1354
+ if (t) style.borderTop = t;
1355
+ if (l) style.borderLeft = l;
1356
+ if (bo) style.borderBottom = bo;
1357
+ if (ri) style.borderRight = ri;
1358
+ }
1359
+ if (style.textAlign === void 0) {
1360
+ const ev = effectiveValue(cell);
1361
+ if (typeof ev === "number" || ev instanceof Date) style.textAlign = "right";
1362
+ }
1363
+ const merge = merges[`${r}:${c}`];
1364
+ const sj = sjCells.get(`${r}:${c}`);
1365
+ const numFmt = cell.numFmt || sj?.z || "";
1366
+ const accountingCurrency = getAccountingCurrency(numFmt);
1367
+ let text = "";
1368
+ if (sj) {
1369
+ if (sj.t === "e") {
1370
+ text = sj.w ?? (typeof sj.v === "string" ? sj.v : "");
1371
+ } else if (sj.t === "n" && typeof sj.v === "number") {
1372
+ const isDateFmt = numFmt && (/[yY]/.test(numFmt) || /mmmm?/i.test(numFmt) || /[hH]/.test(numFmt) || /[dD]/.test(numFmt));
1373
+ if (isDateFmt && sj.v >= 0) {
1374
+ const d = excelSerialToDate(sj.v);
1375
+ if (!isNaN(d.getTime())) text = fmtExcelDate(d, numFmt);
1376
+ } else if (numFmt && numFmt !== "General" && numFmt !== "@") {
1377
+ const formatted = fmtNumber(sj.v, numFmt);
1378
+ if (formatted !== null) text = formatted;
1379
+ }
1380
+ if (!text) text = sj.w ?? String(sj.v);
1381
+ if (style.textAlign === void 0) style.textAlign = "right";
1382
+ } else if (sj.t === "s") {
1383
+ text = sj.w ?? "";
1384
+ } else if (sj.t === "str") {
1385
+ text = sj.w ?? String(sj.v ?? "");
1386
+ } else if (sj.t === "b") {
1387
+ text = sj.v ? "TRUE" : "FALSE";
1388
+ } else {
1389
+ text = sj.w ?? String(sj.v ?? "");
1390
+ }
1391
+ }
1392
+ if (!text) text = getCellText(cell);
1393
+ if (!text) {
1394
+ const raw = rawResults.get(`${r}:${c}`);
1395
+ if (raw !== void 0) {
1396
+ text = formatRawResult(raw, numFmt);
1397
+ if (style.textAlign === void 0) {
1398
+ if (typeof raw === "number" || raw instanceof Date) style.textAlign = "right";
1399
+ }
1400
+ }
1401
+ }
1402
+ if (accountingCurrency && text) {
1403
+ text = text.replace(/[$€£¥]/g, "").trim() || text;
1404
+ }
1405
+ cells.push({
1406
+ key: `${r}:${c}`,
1407
+ text,
1408
+ accountingPrefix: accountingCurrency && text ? accountingCurrency : void 0,
1409
+ style,
1410
+ rowSpan: merge?.rowSpan ?? 1,
1411
+ colSpan: merge?.colSpan ?? 1
1412
+ });
1413
+ if (isCenterContinuous && text) ccIndices.push(cells.length - 1);
1414
+ }
1415
+ for (const idx of ccIndices) {
1416
+ const cc = cells[idx];
1417
+ for (let cj = idx + 1; cj < originalCols; cj++) {
1418
+ const adj = cells[cj];
1419
+ if (adj === null || adj.text !== "") break;
1420
+ cc.colSpan++;
1421
+ cells[cj] = null;
1422
+ }
1423
+ }
1424
+ for (let idx = 0; idx < cells.length; idx++) {
1425
+ const cell = cells[idx];
1426
+ if (!cell || !cell.text || cell.colSpan > 1 || cell.style.whiteSpace === "normal") continue;
1427
+ const align = cell.style.textAlign;
1428
+ if (align === "right" || align === "center") continue;
1429
+ if (cell.style.borderRight) continue;
1430
+ for (let cj = idx + 1; cj < originalCols; cj++) {
1431
+ const adj = cells[cj];
1432
+ if (!adj || adj.text !== "") break;
1433
+ if (adj.style.borderLeft) break;
1434
+ if (adj.style.borderRight) {
1435
+ cell.style.borderRight = adj.style.borderRight;
1436
+ cells[cj] = null;
1437
+ cell.colSpan++;
1438
+ break;
1439
+ }
1440
+ cells[cj] = null;
1441
+ cell.colSpan++;
1442
+ }
1443
+ }
1444
+ rows.push(cells);
1445
+ }
1446
+ return { name: ws.name, rows, colWidths, rowHeights, cols };
1447
+ }
1448
+ function XlsxViewer({
1449
+ data,
1450
+ className,
1451
+ onError
1452
+ }) {
1453
+ const [sheets, setSheets] = useState3([]);
1454
+ const [active, setActive] = useState3(0);
1455
+ const [loading, setLoading] = useState3(true);
1456
+ const [error, setError] = useState3(null);
1457
+ const tabBarRef = useRef3(null);
1458
+ const scrollTabs = (dir) => {
1459
+ tabBarRef.current?.scrollBy({ left: dir === "left" ? -160 : 160, behavior: "smooth" });
1460
+ };
1461
+ useEffect3(() => {
1462
+ let cancelled = false;
1463
+ setLoading(true);
1464
+ setError(null);
1465
+ (async () => {
1466
+ try {
1467
+ const wb = new ExcelJS.Workbook();
1468
+ await wb.xlsx.load(data.slice(0));
1469
+ let sjSheets = [];
1470
+ try {
1471
+ const sjWb = XLSX.read(new Uint8Array(data), {
1472
+ type: "array",
1473
+ cellFormula: true,
1474
+ cellText: true,
1475
+ cellNF: true,
1476
+ cellDates: false,
1477
+ sheetRows: 0
1478
+ });
1479
+ const visibleWs = wb.worksheets.filter(
1480
+ (ws) => ws.state !== "hidden" && ws.state !== "veryHidden"
1481
+ );
1482
+ sjSheets = visibleWs.map((ws, idx) => {
1483
+ if (sjWb.Sheets[ws.name]) return sjWb.Sheets[ws.name];
1484
+ const lc = ws.name.toLowerCase();
1485
+ const found = sjWb.SheetNames.find((n) => n.toLowerCase() === lc);
1486
+ if (found && sjWb.Sheets[found]) return sjWb.Sheets[found];
1487
+ const sjName = sjWb.SheetNames[idx];
1488
+ return sjName && sjWb.Sheets[sjName] ? sjWb.Sheets[sjName] : void 0;
1489
+ });
1490
+ } catch (sjErr) {
1491
+ console.warn("[XlsxViewer] SheetJS failed, falling back to ExcelJS only:", sjErr);
1492
+ }
1493
+ const visibleWorksheets = wb.worksheets.filter(
1494
+ (ws) => ws.state !== "hidden" && ws.state !== "veryHidden"
1495
+ );
1496
+ const built = visibleWorksheets.map(
1497
+ (ws, idx) => buildSheet(ws, sjSheets[idx])
1498
+ );
1499
+ if (cancelled) return;
1500
+ setSheets(built);
1501
+ setActive(0);
1502
+ } catch (e) {
1503
+ const err = e instanceof Error ? e : new Error("Failed to read spreadsheet");
1504
+ if (!cancelled) {
1505
+ setError(err.message);
1506
+ onError?.(err);
1507
+ }
1508
+ } finally {
1509
+ if (!cancelled) setLoading(false);
1510
+ }
1511
+ })();
1512
+ return () => {
1513
+ cancelled = true;
1514
+ };
1515
+ }, [data, onError]);
1516
+ const sheet = sheets[active];
1517
+ const headerCols = useMemo(() => sheet ? Array.from({ length: sheet.cols }, (_, i) => i + 1) : [], [sheet]);
1518
+ if (loading)
1519
+ return /* @__PURE__ */ jsxs3("div", { className: `ov-xlsx__loading${className ? ` ${className}` : ""}`, children: [
1520
+ /* @__PURE__ */ jsx5(Loader23, { className: "ov-xlsx__loading-icon" }),
1521
+ /* @__PURE__ */ jsx5("span", { className: "ov-xlsx__loading-text", children: "Reading spreadsheet\u2026" })
1522
+ ] });
1523
+ if (error)
1524
+ return /* @__PURE__ */ jsx5("div", { className: `ov-xlsx__error${className ? ` ${className}` : ""}`, children: error });
1525
+ if (!sheet) return null;
1526
+ return /* @__PURE__ */ jsxs3("div", { className: `ov-xlsx${className ? ` ${className}` : ""}`, children: [
1527
+ /* @__PURE__ */ jsx5("div", { className: "ov-xlsx__grid-area", children: /* @__PURE__ */ jsxs3("table", { className: "ov-xlsx__table", style: { tableLayout: "fixed", fontFamily: "Calibri, 'Segoe UI', Arial, sans-serif" }, children: [
1528
+ /* @__PURE__ */ jsxs3("colgroup", { children: [
1529
+ /* @__PURE__ */ jsx5("col", { style: { width: 44, minWidth: 44 } }),
1530
+ headerCols.map((c) => /* @__PURE__ */ jsx5("col", { style: { width: sheet.colWidths[c - 1], minWidth: sheet.colWidths[c - 1] } }, c))
1531
+ ] }),
1532
+ /* @__PURE__ */ jsx5("thead", { children: /* @__PURE__ */ jsxs3("tr", { children: [
1533
+ /* @__PURE__ */ jsx5("th", { className: "ov-xlsx__corner" }),
1534
+ headerCols.map((c) => {
1535
+ const w = sheet.colWidths[c - 1];
1536
+ if (w === 0) return /* @__PURE__ */ jsx5("th", { style: { width: 0, padding: 0, border: "none" } }, c);
1537
+ return /* @__PURE__ */ jsx5("th", { className: "ov-xlsx__col-header", children: colLetter(c) }, c);
1538
+ })
1539
+ ] }) }),
1540
+ /* @__PURE__ */ jsx5("tbody", { children: sheet.rows.map((row, ri) => {
1541
+ const h = sheet.rowHeights[ri];
1542
+ if (h === 0) return null;
1543
+ return /* @__PURE__ */ jsxs3("tr", { style: { height: h }, children: [
1544
+ /* @__PURE__ */ jsx5("td", { className: "ov-xlsx__row-header", children: ri + 1 }),
1545
+ row.map(
1546
+ (cell) => cell === null ? null : /* @__PURE__ */ jsx5(
1547
+ "td",
1548
+ {
1549
+ rowSpan: cell.rowSpan,
1550
+ colSpan: cell.colSpan,
1551
+ className: "ov-xlsx__cell",
1552
+ style: cell.style,
1553
+ "data-ov-bold": cell.style.fontWeight === 600 ? "" : void 0,
1554
+ children: cell.accountingPrefix ? /* @__PURE__ */ jsxs3("span", { className: "ov-xlsx__cell-accounting", style: { whiteSpace: "nowrap" }, children: [
1555
+ /* @__PURE__ */ jsx5("span", { children: cell.accountingPrefix }),
1556
+ /* @__PURE__ */ jsx5("span", { style: { flex: 1, textAlign: "right" }, children: cell.text })
1557
+ ] }) : /* @__PURE__ */ jsx5(
1558
+ "span",
1559
+ {
1560
+ className: "ov-xlsx__cell-text",
1561
+ style: { whiteSpace: cell.style.whiteSpace ?? "nowrap" },
1562
+ children: cell.text
1563
+ }
1564
+ )
1565
+ },
1566
+ cell.key
1567
+ )
1568
+ )
1569
+ ] }, ri);
1570
+ }) })
1571
+ ] }) }),
1572
+ /* @__PURE__ */ jsxs3("div", { className: "ov-xlsx__tabbar", children: [
1573
+ sheets.length > 5 && /* @__PURE__ */ jsxs3("div", { className: "ov-xlsx__tabnav", children: [
1574
+ /* @__PURE__ */ jsx5(
1575
+ "button",
1576
+ {
1577
+ onClick: () => scrollTabs("left"),
1578
+ "aria-label": "Scroll sheet tabs left",
1579
+ className: "ov-xlsx__tabnav-btn",
1580
+ children: /* @__PURE__ */ jsx5(ChevronLeft3, { className: "ov-xlsx__tabnav-icon" })
1581
+ }
1582
+ ),
1583
+ /* @__PURE__ */ jsx5(
1584
+ "button",
1585
+ {
1586
+ onClick: () => scrollTabs("right"),
1587
+ "aria-label": "Scroll sheet tabs right",
1588
+ className: "ov-xlsx__tabnav-btn",
1589
+ children: /* @__PURE__ */ jsx5(ChevronRight3, { className: "ov-xlsx__tabnav-icon" })
1590
+ }
1591
+ )
1592
+ ] }),
1593
+ /* @__PURE__ */ jsx5("div", { ref: tabBarRef, className: "ov-xlsx__tabs", children: sheets.map((s, i) => /* @__PURE__ */ jsx5(
1594
+ "button",
1595
+ {
1596
+ onClick: () => setActive(i),
1597
+ title: s.name,
1598
+ className: `ov-xlsx__tab${i === active ? " ov-xlsx__tab--active" : ""}`,
1599
+ children: /* @__PURE__ */ jsx5("span", { className: "ov-xlsx__tab-name", children: s.name })
1600
+ },
1601
+ i
1602
+ )) })
1603
+ ] })
1604
+ ] });
1605
+ }
1606
+
1607
+ // src/plugins/XlsxPlugin.tsx
1608
+ import { jsx as jsx6 } from "react/jsx-runtime";
1609
+ function XlsxPluginComponent({ data, onError }) {
1610
+ return /* @__PURE__ */ jsx6(XlsxViewer, { data, onError });
1611
+ }
1612
+ var XlsxPlugin = {
1613
+ id: "xlsx",
1614
+ detect: (ext, mime) => ext === "xlsx" || mime.includes("spreadsheetml"),
1615
+ needsClip: () => true,
1616
+ component: XlsxPluginComponent
1617
+ };
1618
+
1619
+ // src/plugins/registry.ts
1620
+ var REGISTRY = [
1621
+ DocxPlugin,
1622
+ PptxPlugin,
1623
+ XlsxPlugin
1624
+ // PdfPlugin, — future
1625
+ // ImagePlugin, — future
1626
+ ];
1627
+ function detectPlugin(file, filename) {
1628
+ const name = file instanceof File ? file.name : filename ?? "";
1629
+ const ext = (name.split(".").pop() ?? "").toLowerCase();
1630
+ const mime = file instanceof Blob && !(file instanceof File) ? file.type : "";
1631
+ return REGISTRY.find((p) => p.detect(ext, mime)) ?? null;
1632
+ }
1633
+
1634
+ // src/components/OfficeViewer.tsx
1635
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
1636
+ var MESSAGES = {
1637
+ LOADING: "Loading\u2026",
1638
+ LOAD_FAILED: "Failed to load file",
1639
+ UNSUPPORTED: "Unsupported file type. Please provide a .docx, .xlsx, or .pptx file."
1640
+ };
1641
+ var LABELS = {
1642
+ VIEW_MODE: "View Mode"
1643
+ };
1644
+ var CLS = {
1645
+ CONTAINER: "ov-container",
1646
+ LOADING: "ov-loading",
1647
+ SPINNER: "ov-spinner",
1648
+ ERROR: "ov-error",
1649
+ TOOLBAR: "ov-toolbar",
1650
+ TOOLBAR_LABEL: "ov-toolbar-label",
1651
+ SELECT_WRAP: "ov-select-wrap",
1652
+ SELECT: "ov-select",
1653
+ SELECT_ICON: "ov-select-icon",
1654
+ VIEWER: "ov-viewer",
1655
+ VIEWER_CLIP: "ov-viewer--clip"
1656
+ };
1657
+ var CHEVRON = {
1658
+ VIEW_BOX: "0 0 10 6",
1659
+ PATH: "M1 1l4 4 4-4",
1660
+ STROKE_WIDTH: "1.5"
1661
+ };
1662
+ async function toBuffer(file) {
1663
+ if (file instanceof ArrayBuffer) return file;
1664
+ return file.arrayBuffer();
1665
+ }
1666
+ function LoadingState() {
1667
+ return /* @__PURE__ */ jsxs4("div", { className: CLS.LOADING, children: [
1668
+ /* @__PURE__ */ jsx7(Loader24, { className: CLS.SPINNER }),
1669
+ /* @__PURE__ */ jsx7("span", { children: MESSAGES.LOADING })
1670
+ ] });
1671
+ }
1672
+ function ErrorState({ message }) {
1673
+ return /* @__PURE__ */ jsx7("div", { className: CLS.ERROR, children: message });
1674
+ }
1675
+ function UnsupportedState() {
1676
+ return /* @__PURE__ */ jsx7("div", { className: CLS.ERROR, children: MESSAGES.UNSUPPORTED });
1677
+ }
1678
+ function OfficeViewer({
1679
+ file,
1680
+ filename,
1681
+ className,
1682
+ onError
1683
+ }) {
1684
+ const [data, setData] = useState4(null);
1685
+ const [plugin, setPlugin] = useState4(null);
1686
+ const [viewMode, setViewMode] = useState4("");
1687
+ const [loading, setLoading] = useState4(true);
1688
+ const [error, setError] = useState4(null);
1689
+ useEffect4(() => {
1690
+ let cancelled = false;
1691
+ setLoading(true);
1692
+ setError(null);
1693
+ const p = detectPlugin(file, filename);
1694
+ setPlugin(p);
1695
+ setViewMode(p?.defaultMode ?? "");
1696
+ toBuffer(file).then((ab) => {
1697
+ if (!cancelled) {
1698
+ setData(ab);
1699
+ setLoading(false);
1700
+ }
1701
+ }).catch((e) => {
1702
+ if (cancelled) return;
1703
+ const err = e instanceof Error ? e : new Error(MESSAGES.LOAD_FAILED);
1704
+ setError(err.message);
1705
+ onError?.(err);
1706
+ setLoading(false);
1707
+ });
1708
+ return () => {
1709
+ cancelled = true;
1710
+ };
1711
+ }, [file, filename, onError]);
1712
+ const handleError = (e) => {
1713
+ setError(e.message);
1714
+ onError?.(e);
1715
+ };
1716
+ const containerCls = `${CLS.CONTAINER}${className ? ` ${className}` : ""}`;
1717
+ if (loading) {
1718
+ return /* @__PURE__ */ jsx7("div", { className: containerCls, children: /* @__PURE__ */ jsx7(LoadingState, {}) });
1719
+ }
1720
+ if (error) {
1721
+ return /* @__PURE__ */ jsx7("div", { className: containerCls, children: /* @__PURE__ */ jsx7(ErrorState, { message: error }) });
1722
+ }
1723
+ if (!plugin) {
1724
+ return /* @__PURE__ */ jsx7("div", { className: containerCls, children: /* @__PURE__ */ jsx7(UnsupportedState, {}) });
1725
+ }
1726
+ if (!data) return null;
1727
+ const needsClip = plugin.needsClip?.(viewMode) ?? false;
1728
+ const viewerCls = `${CLS.VIEWER}${needsClip ? ` ${CLS.VIEWER_CLIP}` : ""}`;
1729
+ const ViewerComponent = plugin.component;
1730
+ return /* @__PURE__ */ jsxs4("div", { className: containerCls, "data-office-viewer": true, children: [
1731
+ plugin.viewModes && plugin.viewModes.length > 0 && /* @__PURE__ */ jsxs4("div", { className: CLS.TOOLBAR, children: [
1732
+ /* @__PURE__ */ jsx7("span", { className: CLS.TOOLBAR_LABEL, children: LABELS.VIEW_MODE }),
1733
+ /* @__PURE__ */ jsxs4("div", { className: CLS.SELECT_WRAP, children: [
1734
+ /* @__PURE__ */ jsx7(
1735
+ "select",
1736
+ {
1737
+ value: viewMode,
1738
+ onChange: (e) => setViewMode(e.target.value),
1739
+ className: CLS.SELECT,
1740
+ children: plugin.viewModes.map(({ value, label }) => /* @__PURE__ */ jsx7("option", { value, children: label }, value))
1741
+ }
1742
+ ),
1743
+ /* @__PURE__ */ jsx7(
1744
+ "svg",
1745
+ {
1746
+ className: CLS.SELECT_ICON,
1747
+ viewBox: CHEVRON.VIEW_BOX,
1748
+ fill: "none",
1749
+ "aria-hidden": "true",
1750
+ children: /* @__PURE__ */ jsx7(
1751
+ "path",
1752
+ {
1753
+ d: CHEVRON.PATH,
1754
+ stroke: "currentColor",
1755
+ strokeWidth: CHEVRON.STROKE_WIDTH,
1756
+ strokeLinecap: "round",
1757
+ strokeLinejoin: "round"
1758
+ }
1759
+ )
1760
+ }
1761
+ )
1762
+ ] })
1763
+ ] }),
1764
+ /* @__PURE__ */ jsx7("div", { className: viewerCls, children: /* @__PURE__ */ jsx7(ViewerComponent, { data, mode: viewMode, onError: handleError }) })
1765
+ ] });
1766
+ }
1767
+ export {
1768
+ OfficeViewer
1769
+ };