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