@zerohive/hive-viewer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,897 @@
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.tsx
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ DocumentViewer: () => DocumentViewer
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/components/DocumentViewer.tsx
38
+ var import_react6 = require("react");
39
+
40
+ // src/utils/locale.ts
41
+ var defaultLocale = {
42
+ "loading": "Loading\u2026",
43
+ "error.title": "Error",
44
+ "toolbar.layout.single": "Single page",
45
+ "toolbar.layout.two": "Side-by-side",
46
+ "toolbar.thumbs": "Thumbnails",
47
+ "toolbar.signatures": "Signatures",
48
+ "toolbar.sign": "Sign Document",
49
+ "toolbar.save": "Save",
50
+ "toolbar.exportPdf": "Export as PDF",
51
+ "thumbnails.title": "Thumbnails",
52
+ "thumbnails.page": "Page",
53
+ "signatures.title": "Signatures",
54
+ "signatures.empty": "No signatures",
55
+ "signatures.placeHint": "Click on the document to place the signature.",
56
+ "a11y.viewer": "Document viewer",
57
+ "a11y.ribbon": "Ribbon",
58
+ "a11y.editor": "Document editor"
59
+ };
60
+
61
+ // src/utils/fileSource.ts
62
+ function guessFileType(name, explicit) {
63
+ if (explicit) return explicit;
64
+ const ext = (name?.split(".").pop() || "").toLowerCase();
65
+ const allowed = ["pdf", "md", "docx", "xlsx", "pptx", "txt", "png", "jpg", "svg"];
66
+ return allowed.includes(ext) ? ext : "txt";
67
+ }
68
+ function arrayBufferToBase64(buf) {
69
+ const bytes = new Uint8Array(buf);
70
+ let binary = "";
71
+ const chunk = 32768;
72
+ for (let i = 0; i < bytes.length; i += chunk) {
73
+ binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
74
+ }
75
+ return btoa(binary);
76
+ }
77
+ async function base64ToArrayBuffer(b64) {
78
+ const bin = atob(b64);
79
+ const len = bin.length;
80
+ const bytes = new Uint8Array(len);
81
+ for (let i = 0; i < len; i++) bytes[i] = bin.charCodeAt(i);
82
+ return bytes.buffer;
83
+ }
84
+ async function resolveSource(args) {
85
+ const fileType = guessFileType(args.fileName, args.fileType);
86
+ const fileName = args.fileName ?? `document.${fileType}`;
87
+ if (args.blob) {
88
+ const ab = await args.blob.arrayBuffer();
89
+ const url = URL.createObjectURL(args.blob);
90
+ return { fileType, fileName, arrayBuffer: ab, url };
91
+ }
92
+ if (args.base64) {
93
+ const ab = await base64ToArrayBuffer(args.base64);
94
+ return { fileType, fileName, arrayBuffer: ab };
95
+ }
96
+ if (!args.fileUrl) throw new Error("No file source provided. Use fileUrl, blob, or base64.");
97
+ const res = await fetch(args.fileUrl);
98
+ if (!res.ok) throw new Error(`Failed to fetch file (${res.status})`);
99
+ const total = Number(res.headers.get("content-length") || "") || void 0;
100
+ if (!res.body) {
101
+ const ab = await res.arrayBuffer();
102
+ args.onProgress?.(ab.byteLength, total);
103
+ return { fileType, fileName, arrayBuffer: ab, url: args.fileUrl };
104
+ }
105
+ const reader = res.body.getReader();
106
+ const chunks = [];
107
+ let loaded = 0;
108
+ while (true) {
109
+ const { done, value } = await reader.read();
110
+ if (done) break;
111
+ if (value) {
112
+ chunks.push(value);
113
+ loaded += value.length;
114
+ args.onProgress?.(loaded, total);
115
+ }
116
+ }
117
+ const out = new Uint8Array(loaded);
118
+ let offset = 0;
119
+ for (const c of chunks) {
120
+ out.set(c, offset);
121
+ offset += c.length;
122
+ }
123
+ return { fileType, fileName, arrayBuffer: out.buffer, url: args.fileUrl };
124
+ }
125
+
126
+ // src/components/Toolbar.tsx
127
+ var import_jsx_runtime = require("react/jsx-runtime");
128
+ function Toolbar(props) {
129
+ const t = (k, fallback) => props.locale[k] ?? fallback;
130
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-toolbar", role: "toolbar", "aria-label": t("a11y.toolbar", "Document toolbar"), children: [
131
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-toolbar__left", children: [
132
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "hv-btn", onClick: props.onToggleThumbnails, "aria-pressed": props.showThumbnails, children: t("toolbar.thumbs", "Thumbnails") }),
133
+ props.mode !== "create" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "hv-btn", onClick: props.onToggleSignatures, "aria-pressed": props.showSignatures, children: t("toolbar.signatures", "Signatures") }),
134
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hv-sep" }),
135
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: props.layout === "single" ? "hv-btn hv-btn--active" : "hv-btn", onClick: () => props.onChangeLayout("single"), children: t("toolbar.layout.single", "Single") }),
136
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: props.layout === "side-by-side" ? "hv-btn hv-btn--active" : "hv-btn", onClick: () => props.onChangeLayout("side-by-side"), children: t("toolbar.layout.two", "Two") })
137
+ ] }),
138
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-toolbar__right", children: [
139
+ props.showHeaderFooterToggle && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "hv-toggle", children: [
140
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: props.headerFooterEnabled, onChange: props.onToggleHeaderFooter }),
141
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("toolbar.letterhead", "Letterhead") })
142
+ ] }),
143
+ props.allowSigning && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "hv-btn hv-btn--primary", onClick: props.onSign, disabled: props.signingDisabled, children: t("toolbar.sign", "Sign Document") }),
144
+ props.canExportPdf && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "hv-btn", onClick: props.onExportPdf, children: t("toolbar.exportPdf", "Export as PDF") }),
145
+ props.canSave && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "hv-btn hv-btn--primary", onClick: props.onSave, children: t("toolbar.save", "Save") })
146
+ ] })
147
+ ] });
148
+ }
149
+
150
+ // src/components/ThumbnailsSidebar.tsx
151
+ var import_jsx_runtime2 = require("react/jsx-runtime");
152
+ function ThumbnailsSidebar(props) {
153
+ const t = props.locale["thumbnails.title"] ?? "Thumbnails";
154
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("aside", { className: props.collapsed ? "hv-thumbs hv-thumbs--collapsed" : "hv-thumbs", "aria-label": t, children: [
155
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-thumbs__header", children: [
156
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
157
+ "button",
158
+ {
159
+ type: "button",
160
+ className: "hv-icon",
161
+ onClick: props.onToggle,
162
+ "aria-label": props.collapsed ? props.locale["thumbnails.open"] ?? "Open thumbnails" : props.locale["thumbnails.close"] ?? "Close thumbnails",
163
+ children: props.collapsed ? "\u25B8" : "\u25BE"
164
+ }
165
+ ),
166
+ !props.collapsed ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-thumbs__title", children: t }) : null
167
+ ] }),
168
+ !props.collapsed ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-thumbs__list", role: "list", children: props.thumbnails.map((th, idx) => {
169
+ const p = idx + 1;
170
+ const active = p === props.currentPage;
171
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
172
+ "button",
173
+ {
174
+ type: "button",
175
+ role: "listitem",
176
+ className: active ? "hv-thumb hv-thumb--active" : "hv-thumb",
177
+ onClick: () => props.onSelectPage(p),
178
+ "aria-current": active ? "page" : void 0,
179
+ children: [
180
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-thumb__img", "aria-hidden": true, children: th.dataUrl ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("img", { src: th.dataUrl, alt: "" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-thumb__placeholder" }) }),
181
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-thumb__label", children: th.label })
182
+ ]
183
+ },
184
+ th.id
185
+ );
186
+ }) }) : null
187
+ ] });
188
+ }
189
+
190
+ // src/components/SignaturePanel.tsx
191
+ var import_jsx_runtime3 = require("react/jsx-runtime");
192
+ function SignaturePanel(props) {
193
+ const title = props.locale["signatures.title"] ?? "Signatures";
194
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("aside", { className: props.collapsed ? "hv-side hv-side--collapsed" : "hv-side", "aria-label": title, children: [
195
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-sidebar-header", children: [
196
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: "hv-icon", onClick: props.onToggle, "aria-label": props.locale["toolbar.signatures"] ?? "Signatures", children: "\u270D" }),
197
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-sidebar-title", children: title })
198
+ ] }),
199
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-sidebar-body", children: props.signatures.map((s, idx) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-signature-card", children: [
200
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("img", { src: s.signatureImageUrl, alt: `Signature by ${s.signedBy}`, className: "hv-signature-img" }),
201
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-signature-meta", children: [
202
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-signature-name", children: s.signedBy }),
203
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-signature-date", children: new Date(s.dateSigned).toLocaleString() }),
204
+ s.comment ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-signature-comment", children: s.comment }) : null
205
+ ] })
206
+ ] }, `${s.signedBy}-${s.dateSigned}-${idx}`)) })
207
+ ] });
208
+ }
209
+
210
+ // src/renderers/PdfRenderer.tsx
211
+ var import_react = require("react");
212
+ var import_pdfjs_dist = require("pdfjs-dist");
213
+ var import_jsx_runtime4 = require("react/jsx-runtime");
214
+ var import_meta = {};
215
+ function PdfRenderer(props) {
216
+ const { url, arrayBuffer } = props;
217
+ const [doc, setDoc] = (0, import_react.useState)(null);
218
+ const [pageCount, setPageCount] = (0, import_react.useState)(0);
219
+ const [rendered, setRendered] = (0, import_react.useState)(/* @__PURE__ */ new Map());
220
+ const [thumbs, setThumbs] = (0, import_react.useState)([]);
221
+ const [size, setSize] = (0, import_react.useState)({ w: 840, h: 1188 });
222
+ const containerRef = (0, import_react.useRef)(null);
223
+ (0, import_react.useEffect)(() => {
224
+ try {
225
+ import_pdfjs_dist.GlobalWorkerOptions.workerSrc = new URL("pdfjs-dist/build/pdf.worker.min.mjs", import_meta.url).toString();
226
+ } catch {
227
+ }
228
+ }, []);
229
+ (0, import_react.useEffect)(() => {
230
+ let cancel = false;
231
+ (async () => {
232
+ setDoc(null);
233
+ setRendered(/* @__PURE__ */ new Map());
234
+ setThumbs([]);
235
+ if (!url && !arrayBuffer) return;
236
+ const task = (0, import_pdfjs_dist.getDocument)(url ? { url, rangeChunkSize: 512 * 1024 } : { data: arrayBuffer });
237
+ const pdf = await task.promise;
238
+ if (cancel) return;
239
+ setDoc(pdf);
240
+ setPageCount(pdf.numPages);
241
+ props.onPageCount(pdf.numPages);
242
+ setThumbs(Array.from({ length: pdf.numPages }));
243
+ const p1 = await pdf.getPage(1);
244
+ const base = p1.getViewport({ scale: 1 });
245
+ const w = Math.min(980, Math.max(640, base.width));
246
+ const s = w / base.width;
247
+ const vp = p1.getViewport({ scale: s });
248
+ setSize({ w: Math.round(vp.width), h: Math.round(vp.height) });
249
+ })().catch(() => {
250
+ });
251
+ return () => {
252
+ cancel = true;
253
+ };
254
+ }, [url, arrayBuffer]);
255
+ (0, import_react.useEffect)(() => {
256
+ props.onThumbs(thumbs);
257
+ }, [thumbs]);
258
+ const pagesToShow = (0, import_react.useMemo)(() => {
259
+ if (props.layout === "side-by-side") {
260
+ const left = props.currentPage;
261
+ const right = Math.min(pageCount || left + 1, left + 1);
262
+ return [left, right];
263
+ }
264
+ return [props.currentPage];
265
+ }, [props.currentPage, props.layout, pageCount]);
266
+ (0, import_react.useEffect)(() => {
267
+ if (!doc) return;
268
+ let cancel = false;
269
+ (async () => {
270
+ for (const p of pagesToShow) {
271
+ if (rendered.has(p)) continue;
272
+ const page = await doc.getPage(p);
273
+ if (cancel) return;
274
+ const base = page.getViewport({ scale: 1 });
275
+ const vp = page.getViewport({ scale: size.w / base.width });
276
+ const canvas = document.createElement("canvas");
277
+ canvas.width = Math.round(vp.width);
278
+ canvas.height = Math.round(vp.height);
279
+ const ctx = canvas.getContext("2d", { alpha: false });
280
+ if (!ctx) continue;
281
+ await page.render({ canvasContext: ctx, viewport: vp }).promise;
282
+ if (cancel) return;
283
+ setRendered((prev) => {
284
+ const next = new Map(prev);
285
+ next.set(p, canvas);
286
+ return next;
287
+ });
288
+ try {
289
+ const tw = 140;
290
+ const th = Math.round(tw * (canvas.height / canvas.width));
291
+ const t = document.createElement("canvas");
292
+ t.width = tw;
293
+ t.height = th;
294
+ const tctx = t.getContext("2d");
295
+ if (tctx) {
296
+ tctx.drawImage(canvas, 0, 0, tw, th);
297
+ const url2 = t.toDataURL("image/jpeg", 0.75);
298
+ setThumbs((prev) => {
299
+ const next = prev.slice();
300
+ next[p - 1] = url2;
301
+ return next;
302
+ });
303
+ }
304
+ } catch {
305
+ }
306
+ }
307
+ })();
308
+ return () => {
309
+ cancel = true;
310
+ };
311
+ }, [doc, pagesToShow, size.w, rendered]);
312
+ function onWheel(e) {
313
+ if (!pageCount) return;
314
+ if (Math.abs(e.deltaY) < 10) return;
315
+ const dir = e.deltaY > 0 ? 1 : -1;
316
+ const step = props.layout === "side-by-side" ? 2 : 1;
317
+ const next = Math.max(1, Math.min(pageCount, props.currentPage + dir * step));
318
+ props.onCurrentPageChange(next);
319
+ }
320
+ function clickPlace(e, page) {
321
+ const stamp = props.signatureStamp;
322
+ if (!stamp?.armed) return;
323
+ const rect = e.currentTarget.getBoundingClientRect();
324
+ const x = (e.clientX - rect.left) / rect.width;
325
+ const y = (e.clientY - rect.top) / rect.height;
326
+ stamp.onPlaced({ page, x, y, w: 0.22, h: 0.08 });
327
+ }
328
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hv-doc", ref: containerRef, onWheel, children: [
329
+ !doc ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hv-loading", children: "Loading PDF\u2026" }) : null,
330
+ doc ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: props.layout === "side-by-side" ? "hv-pages hv-pages--two" : "hv-pages", children: pagesToShow.map((p) => {
331
+ const c = rendered.get(p);
332
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hv-page", style: { width: size.w, height: size.h }, onClick: (e) => clickPlace(e, p), children: c ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
333
+ "canvas",
334
+ {
335
+ className: "hv-canvas",
336
+ width: c.width,
337
+ height: c.height,
338
+ ref: (node) => {
339
+ if (!node) return;
340
+ const ctx = node.getContext("2d");
341
+ if (ctx) ctx.drawImage(c, 0, 0);
342
+ }
343
+ }
344
+ ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hv-loading", children: "Rendering\u2026" }) }, p);
345
+ }) }) : null
346
+ ] });
347
+ }
348
+
349
+ // src/editors/RichTextEditor.tsx
350
+ var import_react2 = require("react");
351
+ var import_mammoth = __toESM(require("mammoth"), 1);
352
+ var import_html_to_docx = __toESM(require("html-to-docx"), 1);
353
+ var import_markdown_it = __toESM(require("markdown-it"), 1);
354
+ var import_html2canvas = __toESM(require("html2canvas"), 1);
355
+
356
+ // src/utils/sanitize.ts
357
+ var import_dompurify = __toESM(require("dompurify"), 1);
358
+ function sanitizeHtml(html) {
359
+ return import_dompurify.default.sanitize(html, {
360
+ USE_PROFILES: { html: true },
361
+ ADD_ATTR: ["target", "rel"]
362
+ });
363
+ }
364
+
365
+ // src/editors/RichTextEditor.tsx
366
+ var import_jsx_runtime5 = require("react/jsx-runtime");
367
+ var PAGE_H = 1122;
368
+ var RichTextEditor = (0, import_react2.forwardRef)((props, ref) => {
369
+ const readOnly = props.mode === "view";
370
+ const md = (0, import_react2.useMemo)(() => new import_markdown_it.default({ html: false, linkify: true, breaks: true }), []);
371
+ const scrollerRef = (0, import_react2.useRef)(null);
372
+ const editorRef = (0, import_react2.useRef)(null);
373
+ const captureRef = (0, import_react2.useRef)(null);
374
+ const [html, setHtml] = (0, import_react2.useState)("<p><br/></p>");
375
+ (0, import_react2.useEffect)(() => {
376
+ let cancelled = false;
377
+ (async () => {
378
+ if (props.mode === "create") {
379
+ setHtml("<p><br/></p>");
380
+ return;
381
+ }
382
+ if (!props.arrayBuffer) return;
383
+ if (props.fileType === "docx") {
384
+ const res = await import_mammoth.default.convertToHtml({ arrayBuffer: props.arrayBuffer });
385
+ if (!cancelled) setHtml(sanitizeHtml(res.value || "<p><br/></p>"));
386
+ } else {
387
+ const text = new TextDecoder().decode(props.arrayBuffer);
388
+ if (props.fileType === "md") setHtml(sanitizeHtml(md.render(text)));
389
+ else setHtml(`<pre>${escapeHtml(text)}</pre>`);
390
+ }
391
+ })();
392
+ return () => {
393
+ cancelled = true;
394
+ };
395
+ }, [props.arrayBuffer, props.fileType, props.mode, md]);
396
+ (0, import_react2.useEffect)(() => {
397
+ const el = scrollerRef.current;
398
+ if (!el) return;
399
+ const recompute = () => props.onPageCount(Math.max(1, Math.ceil(el.scrollHeight / PAGE_H)));
400
+ recompute();
401
+ const ro = new ResizeObserver(recompute);
402
+ ro.observe(el);
403
+ return () => ro.disconnect();
404
+ }, [html, props.headerFooterEnabled]);
405
+ function exec(cmd) {
406
+ if (readOnly) return;
407
+ document.execCommand(cmd);
408
+ }
409
+ function onClick(e) {
410
+ if (!props.armedSignatureUrl) return;
411
+ const scroller = scrollerRef.current;
412
+ if (!scroller) return;
413
+ const rect = e.currentTarget.getBoundingClientRect();
414
+ const absY = scroller.scrollTop + (e.clientY - rect.top);
415
+ const page = Math.max(1, Math.floor(absY / PAGE_H) + 1);
416
+ const pageTop = (page - 1) * PAGE_H;
417
+ const x = (e.clientX - rect.left) / rect.width;
418
+ const y = (absY - pageTop) / PAGE_H;
419
+ props.onPlaceSignature({ page, x, y, w: 0.25, h: 0.1 });
420
+ }
421
+ async function requestThumbnail(index) {
422
+ const scroller = scrollerRef.current;
423
+ const capture = captureRef.current;
424
+ if (!scroller || !capture) return void 0;
425
+ const old = scroller.scrollTop;
426
+ scroller.scrollTop = index * PAGE_H;
427
+ await new Promise((r) => requestAnimationFrame(() => r(null)));
428
+ try {
429
+ const canvas = await (0, import_html2canvas.default)(capture, { backgroundColor: null, scale: 0.25, useCORS: true });
430
+ return canvas.toDataURL("image/png");
431
+ } catch {
432
+ return void 0;
433
+ } finally {
434
+ scroller.scrollTop = old;
435
+ }
436
+ }
437
+ async function save(exportPdf) {
438
+ const inner = editorRef.current?.innerHTML ?? html;
439
+ const stitched = `<!doctype html><html><head><meta charset="utf-8" /></head><body>${inner}</body></html>`;
440
+ if (exportPdf) {
441
+ const b642 = btoa(unescape(encodeURIComponent(stitched)));
442
+ props.onSave(b642, { fileName: replaceExt(props.fileName, "html"), fileType: "txt", exportedAsPdf: true, annotations: { signaturePlacements: props.signaturePlacements } });
443
+ return;
444
+ }
445
+ if (props.fileType === "docx") {
446
+ const blob = await (0, import_html_to_docx.default)(stitched);
447
+ const ab = await blob.arrayBuffer();
448
+ props.onSave(arrayBufferToBase64(ab), { fileName: replaceExt(props.fileName, "docx"), fileType: "docx", annotations: { signaturePlacements: props.signaturePlacements } });
449
+ return;
450
+ }
451
+ const text = editorRef.current?.innerText ?? "";
452
+ const b64 = btoa(unescape(encodeURIComponent(text)));
453
+ props.onSave(b64, { fileName: replaceExt(props.fileName, props.fileType), fileType: props.fileType, annotations: { signaturePlacements: props.signaturePlacements } });
454
+ }
455
+ (0, import_react2.useImperativeHandle)(ref, () => ({ save, requestThumbnail }));
456
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hv-doc", children: [
457
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hv-ribbon", role: "toolbar", children: [
458
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { className: "hv-btn", onClick: () => exec("bold"), disabled: readOnly, children: "B" }),
459
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { className: "hv-btn", onClick: () => exec("italic"), disabled: readOnly, children: "I" }),
460
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { className: "hv-btn", onClick: () => exec("underline"), disabled: readOnly, children: "U" }),
461
+ props.armedSignatureUrl ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-hint", children: "Click to place signature" }) : null
462
+ ] }),
463
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-scroll", ref: scrollerRef, onClick, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hv-pageStage", ref: captureRef, children: [
464
+ props.headerFooterEnabled && props.headerComponent ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-letterhead", children: props.headerComponent }) : null,
465
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
466
+ "div",
467
+ {
468
+ ref: editorRef,
469
+ className: readOnly ? "hv-editor hv-editor--ro" : "hv-editor",
470
+ contentEditable: !readOnly,
471
+ suppressContentEditableWarning: true,
472
+ onInput: () => setHtml(editorRef.current?.innerHTML ?? ""),
473
+ dangerouslySetInnerHTML: { __html: html }
474
+ }
475
+ ),
476
+ props.headerFooterEnabled && props.footerComponent ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-letterhead hv-letterhead--footer", children: props.footerComponent }) : null,
477
+ props.mode === "create" && props.signatures.length ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-signatures-inline", children: props.signatures.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hv-sign-inline", children: [
478
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { src: s.signatureImageUrl, alt: "", className: "hv-sign-img" }),
479
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { children: [
480
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-sign-name", children: s.signedBy }),
481
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-sign-date", children: new Date(s.dateSigned).toLocaleString() })
482
+ ] })
483
+ ] }, i)) }) : null
484
+ ] }) })
485
+ ] });
486
+ });
487
+ function replaceExt(name, ext) {
488
+ const base = name.includes(".") ? name.slice(0, name.lastIndexOf(".")) : name;
489
+ return `${base}.${ext}`;
490
+ }
491
+ function escapeHtml(s) {
492
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
493
+ }
494
+
495
+ // src/editors/SpreadsheetEditor.tsx
496
+ var import_react3 = require("react");
497
+ var XLSX = __toESM(require("xlsx"), 1);
498
+ var import_jsx_runtime6 = require("react/jsx-runtime");
499
+ var SpreadsheetEditor = (0, import_react3.forwardRef)(function SpreadsheetEditor2(props, ref) {
500
+ const readonly = props.mode === "view";
501
+ const [grid, setGrid] = (0, import_react3.useState)(() => Array.from({ length: 30 }, () => Array.from({ length: 12 }, () => "")));
502
+ (0, import_react3.useEffect)(() => {
503
+ if (!props.arrayBuffer) return;
504
+ try {
505
+ const wb = XLSX.read(props.arrayBuffer, { type: "array" });
506
+ const name = wb.SheetNames[0];
507
+ const ws = wb.Sheets[name];
508
+ const aoa = XLSX.utils.sheet_to_json(ws, { header: 1, raw: true });
509
+ const rows = Math.max(30, aoa.length);
510
+ const cols2 = Math.max(12, Math.max(...aoa.map((r) => r?.length ?? 0), 0));
511
+ const next = Array.from({ length: rows }, (_, r) => Array.from({ length: cols2 }, (_2, c) => {
512
+ const v = aoa[r]?.[c];
513
+ return v == null ? "" : String(v);
514
+ }));
515
+ setGrid(next);
516
+ } catch {
517
+ }
518
+ }, [props.arrayBuffer]);
519
+ async function save(exportPdf) {
520
+ const ws = XLSX.utils.aoa_to_sheet(grid);
521
+ const wb = XLSX.utils.book_new();
522
+ XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
523
+ const out = XLSX.write(wb, { type: "array", bookType: "xlsx" });
524
+ const b64 = arrayBufferToBase64(out);
525
+ props.onSave(b64, { fileName: ensureExt(props.fileName, "xlsx"), fileType: "xlsx", exportedAsPdf: !!exportPdf });
526
+ }
527
+ (0, import_react3.useImperativeHandle)(ref, () => ({
528
+ save,
529
+ requestThumbnails: async () => void 0
530
+ }));
531
+ const cols = (0, import_react3.useMemo)(() => Array.from({ length: grid[0]?.length ?? 0 }, (_, i) => String.fromCharCode(65 + i % 26)), [grid]);
532
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hv-sheet", children: [
533
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hv-sheetbar", children: [
534
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-sheetbar-title", children: props.fileName }),
535
+ !readonly ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { className: "hv-btn", type: "button", onClick: () => void save(false), children: props.locale["toolbar.save"] ?? "Save" }) : null
536
+ ] }),
537
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hv-sheetgrid", role: "table", "aria-label": "Spreadsheet", children: [
538
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hv-sheetrow hv-sheetrow--header", role: "row", children: [
539
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-sheetcell hv-sheetcell--corner", role: "columnheader" }),
540
+ cols.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-sheetcell hv-sheetcell--header", role: "columnheader", children: c }, i))
541
+ ] }),
542
+ grid.map((row, r) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hv-sheetrow", role: "row", children: [
543
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-sheetcell hv-sheetcell--header", role: "rowheader", children: r + 1 }),
544
+ row.map((val, c) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
545
+ "div",
546
+ {
547
+ className: "hv-sheetcell",
548
+ role: "cell",
549
+ contentEditable: !readonly,
550
+ suppressContentEditableWarning: true,
551
+ onInput: (e) => {
552
+ const text = e.currentTarget.textContent ?? "";
553
+ setGrid((prev) => {
554
+ const next = prev.map((rr) => rr.slice());
555
+ next[r][c] = text;
556
+ return next;
557
+ });
558
+ },
559
+ children: val
560
+ },
561
+ c
562
+ ))
563
+ ] }, r))
564
+ ] })
565
+ ] });
566
+ });
567
+ function ensureExt(name, ext) {
568
+ const base = name.includes(".") ? name.slice(0, name.lastIndexOf(".")) : name;
569
+ return `${base}.${ext}`;
570
+ }
571
+
572
+ // src/renderers/ImageRenderer.tsx
573
+ var import_react4 = require("react");
574
+ var import_jsx_runtime7 = require("react/jsx-runtime");
575
+ function ImageRenderer({ arrayBuffer, fileType, fileName }) {
576
+ const [zoom, setZoom] = (0, import_react4.useState)(1);
577
+ const url = (0, import_react4.useMemo)(() => {
578
+ if (!arrayBuffer) return void 0;
579
+ const mime = fileType === "svg" ? "image/svg+xml" : fileType === "png" ? "image/png" : "image/jpeg";
580
+ return URL.createObjectURL(new Blob([arrayBuffer], { type: mime }));
581
+ }, [arrayBuffer, fileType]);
582
+ (0, import_react4.useEffect)(() => {
583
+ return () => {
584
+ if (url) URL.revokeObjectURL(url);
585
+ };
586
+ }, [url]);
587
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hv-doc", children: [
588
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hv-mini-toolbar", children: [
589
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-title", children: fileName }),
590
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-spacer" }),
591
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "hv-btn", onClick: () => setZoom((z) => Math.max(0.25, z - 0.25)), children: "-" }),
592
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hv-zoom", children: [
593
+ Math.round(zoom * 100),
594
+ "%"
595
+ ] }),
596
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "hv-btn", onClick: () => setZoom((z) => Math.min(4, z + 0.25)), children: "+" })
597
+ ] }),
598
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-center", children: url ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("img", { src: url, alt: fileName, style: { transform: `scale(${zoom})` }, className: "hv-image" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-loading", children: "Loading\u2026" }) })
599
+ ] });
600
+ }
601
+
602
+ // src/renderers/PptxRenderer.tsx
603
+ var import_react5 = require("react");
604
+ var import_jszip = __toESM(require("jszip"), 1);
605
+ var import_jsx_runtime8 = require("react/jsx-runtime");
606
+ function decodeXml(s) {
607
+ return s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
608
+ }
609
+ function extractText(xml) {
610
+ return [...xml.matchAll(/<a:t>(.*?)<\/a:t>/g)].map((m) => decodeXml(m[1] || "")).join(" ").trim();
611
+ }
612
+ function PptxRenderer(props) {
613
+ const [slides, setSlides] = (0, import_react5.useState)([]);
614
+ const [thumbs, setThumbs] = (0, import_react5.useState)([]);
615
+ (0, import_react5.useEffect)(() => {
616
+ let cancelled = false;
617
+ (async () => {
618
+ setSlides([]);
619
+ setThumbs([]);
620
+ if (!props.arrayBuffer) {
621
+ props.onSlideCount(1);
622
+ setSlides([{ index: 1, text: "No content" }]);
623
+ return;
624
+ }
625
+ const zip = await import_jszip.default.loadAsync(props.arrayBuffer);
626
+ const files = Object.keys(zip.files).filter((p) => /^ppt\/slides\/slide\d+\.xml$/.test(p)).sort((a, b) => {
627
+ const na = Number(a.match(/slide(\d+)\.xml/)?.[1] || 0);
628
+ const nb = Number(b.match(/slide(\d+)\.xml/)?.[1] || 0);
629
+ return na - nb;
630
+ });
631
+ const out = [];
632
+ for (let i = 0; i < files.length; i++) {
633
+ const xml = await zip.file(files[i]).async("string");
634
+ out.push({ index: i + 1, text: extractText(xml) });
635
+ }
636
+ if (cancelled) return;
637
+ const count = Math.max(1, out.length);
638
+ props.onSlideCount(count);
639
+ setSlides(out.length ? out : [{ index: 1, text: "(empty)" }]);
640
+ setThumbs(Array.from({ length: count }, (_, i) => `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgThumb(i + 1))}`));
641
+ })().catch(() => {
642
+ props.onSlideCount(1);
643
+ setSlides([{ index: 1, text: "Unable to render this .pptx in-browser." }]);
644
+ setThumbs([void 0]);
645
+ });
646
+ return () => {
647
+ cancelled = true;
648
+ };
649
+ }, [props.arrayBuffer]);
650
+ (0, import_react5.useEffect)(() => {
651
+ props.onThumbs(thumbs);
652
+ }, [thumbs]);
653
+ const pagesToShow = (0, import_react5.useMemo)(() => {
654
+ if (props.layout === "side-by-side") return [props.currentPage, Math.min(slides.length || props.currentPage + 1, props.currentPage + 1)];
655
+ return [props.currentPage];
656
+ }, [props.currentPage, props.layout, slides.length]);
657
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hv-doc", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: props.layout === "side-by-side" ? "hv-pages hv-pages--two" : "hv-pages", children: pagesToShow.map((p) => {
658
+ const s = slides[p - 1];
659
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hv-slide", tabIndex: 0, onFocus: () => props.onCurrentPageChange(p), children: [
660
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hv-slide-title", children: [
661
+ "Slide ",
662
+ p
663
+ ] }),
664
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "hv-slide-text", children: s?.text || "" })
665
+ ] }, p);
666
+ }) }) });
667
+ }
668
+ function svgThumb(n) {
669
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="180" height="100"><rect width="100%" height="100%" rx="12" fill="#111827"/><text x="50%" y="54%" font-size="18" fill="#e5e7eb" text-anchor="middle">${n}</text></svg>`;
670
+ }
671
+
672
+ // src/components/DocumentViewer.tsx
673
+ var import_jsx_runtime9 = require("react/jsx-runtime");
674
+ function DocumentViewer(props) {
675
+ const mode = props.mode ?? "view";
676
+ const theme = props.theme ?? "light";
677
+ const locale = (0, import_react6.useMemo)(() => ({ ...defaultLocale, ...props.locale ?? {} }), [props.locale]);
678
+ const [layout, setLayout] = (0, import_react6.useState)(props.defaultLayout ?? "single");
679
+ const [showThumbnails, setShowThumbnails] = (0, import_react6.useState)(true);
680
+ const [showSignatures, setShowSignatures] = (0, import_react6.useState)(true);
681
+ const [headerFooterEnabled, setHeaderFooterEnabled] = (0, import_react6.useState)(true);
682
+ const allowSigning = props.allowSigning ?? false;
683
+ const [signingBusy, setSigningBusy] = (0, import_react6.useState)(false);
684
+ const [resolved, setResolved] = (0, import_react6.useState)(null);
685
+ const [error, setError] = (0, import_react6.useState)("");
686
+ const [pageCount, setPageCount] = (0, import_react6.useState)(1);
687
+ const [currentPage, setCurrentPage] = (0, import_react6.useState)(1);
688
+ const [thumbs, setThumbs] = (0, import_react6.useState)([]);
689
+ const [localSignatures, setLocalSignatures] = (0, import_react6.useState)(props.signatures ?? []);
690
+ (0, import_react6.useEffect)(() => setLocalSignatures(props.signatures ?? []), [props.signatures]);
691
+ const [sigPlacements, setSigPlacements] = (0, import_react6.useState)([]);
692
+ const [armedSignatureUrl, setArmedSignatureUrl] = (0, import_react6.useState)(null);
693
+ const editorRef = (0, import_react6.useRef)(null);
694
+ (0, import_react6.useEffect)(() => {
695
+ let cancelled = false;
696
+ (async () => {
697
+ setError("");
698
+ setResolved(null);
699
+ setThumbs([]);
700
+ setPageCount(1);
701
+ setCurrentPage(1);
702
+ setSigPlacements([]);
703
+ setArmedSignatureUrl(null);
704
+ if (mode === "create") {
705
+ const ft = props.fileType ?? "docx";
706
+ setResolved({ fileType: ft, fileName: props.fileName ?? `Untitled.${ft}` });
707
+ return;
708
+ }
709
+ try {
710
+ const res = await resolveSource({
711
+ fileUrl: props.fileUrl,
712
+ base64: props.base64,
713
+ blob: props.blob,
714
+ fileName: props.fileName,
715
+ fileType: props.fileType
716
+ });
717
+ if (cancelled) return;
718
+ setResolved({ fileType: res.fileType, fileName: res.fileName, url: res.url, arrayBuffer: res.arrayBuffer });
719
+ } catch (e) {
720
+ if (cancelled) return;
721
+ setError(e instanceof Error ? e.message : String(e));
722
+ }
723
+ })();
724
+ return () => {
725
+ cancelled = true;
726
+ };
727
+ }, [mode, props.fileUrl, props.base64, props.blob, props.fileName, props.fileType]);
728
+ const thumbnails = (0, import_react6.useMemo)(() => {
729
+ const n = Math.max(1, pageCount);
730
+ return Array.from({ length: n }, (_, i) => ({
731
+ id: `p-${i + 1}`,
732
+ label: `${locale["thumbnails.page"] ?? "Page"} ${i + 1}`,
733
+ dataUrl: thumbs[i]
734
+ }));
735
+ }, [pageCount, thumbs, locale]);
736
+ async function handleSignRequest() {
737
+ if (!allowSigning || signingBusy || !props.onSignRequest) return;
738
+ setSigningBusy(true);
739
+ try {
740
+ const sig = await props.onSignRequest();
741
+ setLocalSignatures((prev) => [...prev, sig]);
742
+ setArmedSignatureUrl(sig.signatureImageUrl);
743
+ } finally {
744
+ setSigningBusy(false);
745
+ }
746
+ }
747
+ function placeSignature(p) {
748
+ if (!armedSignatureUrl) return;
749
+ setSigPlacements((prev) => [...prev, { ...p, signatureImageUrl: armedSignatureUrl }]);
750
+ setArmedSignatureUrl(null);
751
+ }
752
+ async function handleSave(exportPdf) {
753
+ if (editorRef.current) {
754
+ await editorRef.current.save(!!exportPdf);
755
+ return;
756
+ }
757
+ if (!resolved?.arrayBuffer) return;
758
+ const b64 = arrayBufferToBase642(resolved.arrayBuffer);
759
+ props.onSave?.(b64, { fileName: resolved.fileName, fileType: resolved.fileType, annotations: { sigPlacements } });
760
+ }
761
+ const canSave = mode === "edit" || mode === "create";
762
+ const canExportPdf = (mode === "edit" || mode === "create") && (resolved?.fileType === "docx" || resolved?.fileType === "md" || resolved?.fileType === "txt" || resolved?.fileType === "xlsx");
763
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: `hv-root`, "data-hv-theme": theme, children: [
764
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
765
+ Toolbar,
766
+ {
767
+ locale,
768
+ mode,
769
+ fileType: resolved?.fileType,
770
+ layout,
771
+ onChangeLayout: setLayout,
772
+ showThumbnails,
773
+ onToggleThumbnails: () => setShowThumbnails((v) => !v),
774
+ showSignatures,
775
+ onToggleSignatures: () => setShowSignatures((v) => !v),
776
+ onSign: () => void handleSignRequest(),
777
+ allowSigning,
778
+ signingDisabled: signingBusy || !props.onSignRequest,
779
+ canSave,
780
+ onSave: () => void handleSave(false),
781
+ canExportPdf,
782
+ onExportPdf: () => void handleSave(true),
783
+ headerFooterEnabled,
784
+ showHeaderFooterToggle: (props.enableHeaderFooterToggle ?? true) && mode === "create",
785
+ onToggleHeaderFooter: () => setHeaderFooterEnabled((v) => !v)
786
+ }
787
+ ),
788
+ error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hv-error", role: "alert", children: [
789
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hv-error-title", children: locale["error.title"] ?? "Error" }),
790
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hv-error-body", children: error })
791
+ ] }) : null,
792
+ !resolved && !error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hv-loading", "aria-busy": "true", children: locale["loading"] ?? "Loading\u2026" }) : null,
793
+ resolved ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hv-shell", children: [
794
+ mode !== "create" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
795
+ ThumbnailsSidebar,
796
+ {
797
+ locale,
798
+ thumbnails,
799
+ currentPage,
800
+ collapsed: !showThumbnails,
801
+ onToggle: () => setShowThumbnails((v) => !v),
802
+ onSelectPage: setCurrentPage
803
+ }
804
+ ) : null,
805
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("main", { className: "hv-main", children: [
806
+ resolved.fileType === "pdf" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
807
+ PdfRenderer,
808
+ {
809
+ url: resolved.url,
810
+ arrayBuffer: resolved.arrayBuffer,
811
+ layout,
812
+ currentPage,
813
+ onCurrentPageChange: setCurrentPage,
814
+ onPageCount: (n) => {
815
+ setPageCount(n);
816
+ setThumbs((prev) => prev.length === n ? prev : Array.from({ length: n }, (_, i) => prev[i]));
817
+ },
818
+ onThumbs: (t) => setThumbs(t),
819
+ signatureStamp: armedSignatureUrl ? { imageUrl: armedSignatureUrl, armed: true, onPlaced: placeSignature } : void 0
820
+ }
821
+ ) : null,
822
+ resolved.fileType === "docx" || resolved.fileType === "md" || resolved.fileType === "txt" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
823
+ RichTextEditor,
824
+ {
825
+ ref: editorRef,
826
+ mode,
827
+ fileType: resolved.fileType,
828
+ fileName: resolved.fileName,
829
+ arrayBuffer: resolved.arrayBuffer,
830
+ headerComponent: props.headerComponent,
831
+ footerComponent: props.footerComponent,
832
+ headerFooterEnabled,
833
+ locale,
834
+ signatures: localSignatures,
835
+ signaturePlacements: sigPlacements,
836
+ onPageCount: (n) => {
837
+ setPageCount(n);
838
+ setThumbs((prev) => prev.length === n ? prev : Array.from({ length: n }, (_, i) => prev[i]));
839
+ },
840
+ onSave: (b64, meta) => props.onSave?.(b64, meta),
841
+ armedSignatureUrl,
842
+ onPlaceSignature: placeSignature
843
+ }
844
+ ) : null,
845
+ resolved.fileType === "xlsx" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
846
+ SpreadsheetEditor,
847
+ {
848
+ ref: editorRef,
849
+ mode,
850
+ fileName: resolved.fileName,
851
+ arrayBuffer: resolved.arrayBuffer,
852
+ locale,
853
+ onSave: (b64, meta) => props.onSave?.(b64, meta)
854
+ }
855
+ ) : null,
856
+ resolved.fileType === "pptx" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
857
+ PptxRenderer,
858
+ {
859
+ arrayBuffer: resolved.arrayBuffer,
860
+ layout,
861
+ currentPage,
862
+ onCurrentPageChange: setCurrentPage,
863
+ onSlideCount: (n) => {
864
+ setPageCount(n);
865
+ setThumbs((prev) => prev.length === n ? prev : Array.from({ length: n }, (_, i) => prev[i]));
866
+ },
867
+ onThumbs: (t) => setThumbs(t)
868
+ }
869
+ ) : null,
870
+ resolved.fileType === "png" || resolved.fileType === "jpg" || resolved.fileType === "svg" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ImageRenderer, { arrayBuffer: resolved.arrayBuffer, fileType: resolved.fileType, fileName: resolved.fileName }) : null
871
+ ] }),
872
+ mode !== "create" && localSignatures.length ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
873
+ SignaturePanel,
874
+ {
875
+ locale,
876
+ signatures: localSignatures,
877
+ collapsed: !showSignatures,
878
+ onToggle: () => setShowSignatures((v) => !v)
879
+ }
880
+ ) : null
881
+ ] }) : null
882
+ ] });
883
+ }
884
+ function arrayBufferToBase642(ab) {
885
+ const bytes = new Uint8Array(ab);
886
+ let binary = "";
887
+ const chunk = 32768;
888
+ for (let i = 0; i < bytes.length; i += chunk) {
889
+ binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
890
+ }
891
+ return btoa(binary);
892
+ }
893
+ // Annotate the CommonJS export names for ESM import in node:
894
+ 0 && (module.exports = {
895
+ DocumentViewer
896
+ });
897
+ //# sourceMappingURL=index.cjs.map