@zerohive/hive-viewer 0.2.6 → 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.
package/dist/index.cjs DELETED
@@ -1,1244 +0,0 @@
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_react7 = require("react");
39
-
40
- // src/editors/RichTextEditor.tsx
41
- var import_html2canvas = __toESM(require("html2canvas"), 1);
42
- var import_mammoth = __toESM(require("mammoth"), 1);
43
- var import_markdown_it = __toESM(require("markdown-it"), 1);
44
- var import_react = require("react");
45
-
46
- // src/utils/sanitize.ts
47
- var import_dompurify = __toESM(require("dompurify"), 1);
48
- function sanitizeHtml(html) {
49
- return import_dompurify.default.sanitize(html, {
50
- USE_PROFILES: { html: true },
51
- ADD_ATTR: ["target", "rel"]
52
- });
53
- }
54
-
55
- // src/editors/RichTextEditor.tsx
56
- var import_jsx_runtime = require("react/jsx-runtime");
57
- var PAGE_H = 1122;
58
- var RichTextEditor = (0, import_react.forwardRef)(
59
- (props, ref) => {
60
- const readOnly = props.mode === "view";
61
- const md = (0, import_react.useMemo)(
62
- () => new import_markdown_it.default({ html: false, linkify: true, breaks: true }),
63
- []
64
- );
65
- const scrollerRef = (0, import_react.useRef)(null);
66
- const editorRef = (0, import_react.useRef)(null);
67
- const captureRef = (0, import_react.useRef)(null);
68
- const initialized = (0, import_react.useRef)(false);
69
- (0, import_react.useEffect)(() => {
70
- if (initialized.current) return;
71
- (async () => {
72
- if (props.mode === "create") {
73
- editorRef.current.innerHTML = "<p><br/></p>";
74
- initialized.current = true;
75
- return;
76
- }
77
- if (!props.arrayBuffer) return;
78
- if (props.fileType === "docx") {
79
- const res = await import_mammoth.default.convertToHtml({
80
- arrayBuffer: props.arrayBuffer
81
- });
82
- editorRef.current.innerHTML = sanitizeHtml(
83
- res.value || "<p><br/></p>"
84
- );
85
- } else {
86
- const text = new TextDecoder().decode(props.arrayBuffer);
87
- editorRef.current.innerHTML = props.fileType === "md" ? sanitizeHtml(md.render(text)) : `<pre>${escapeHtml(text)}</pre>`;
88
- }
89
- initialized.current = true;
90
- })();
91
- }, [props.arrayBuffer, props.fileType, props.mode, md]);
92
- (0, import_react.useEffect)(() => {
93
- const el = scrollerRef.current;
94
- if (!el) return;
95
- const recompute = () => props.onPageCount(Math.max(1, Math.ceil(el.scrollHeight / PAGE_H)));
96
- recompute();
97
- const ro = new ResizeObserver(recompute);
98
- ro.observe(el);
99
- return () => ro.disconnect();
100
- }, [props.headerFooterEnabled]);
101
- function exec(cmd) {
102
- if (readOnly) return;
103
- document.execCommand(cmd);
104
- editorRef.current?.focus();
105
- }
106
- function onClickPage(e) {
107
- if (!props.armedSignatureUrl) return;
108
- const scroller = scrollerRef.current;
109
- if (!scroller) return;
110
- const rect = e.currentTarget.getBoundingClientRect();
111
- const absY = scroller.scrollTop + (e.clientY - rect.top);
112
- const page = Math.floor(absY / PAGE_H) + 1;
113
- props.onPlaceSignature({
114
- page,
115
- x: (e.clientX - rect.left) / rect.width,
116
- y: absY % PAGE_H / PAGE_H,
117
- w: 0.25,
118
- h: 0.1
119
- });
120
- }
121
- async function requestThumbnail(index) {
122
- const scroller = scrollerRef.current;
123
- const capture = captureRef.current;
124
- if (!scroller || !capture) return;
125
- const old = scroller.scrollTop;
126
- scroller.scrollTop = index * PAGE_H;
127
- await new Promise((r) => requestAnimationFrame(r));
128
- try {
129
- const canvas = await (0, import_html2canvas.default)(capture, {
130
- scale: 0.25,
131
- useCORS: true
132
- });
133
- return canvas.toDataURL("image/png");
134
- } finally {
135
- scroller.scrollTop = old;
136
- }
137
- }
138
- async function save(exportPdf) {
139
- const html = editorRef.current?.innerHTML ?? "";
140
- const stitched = `<!doctype html><html><body>${html}</body></html>`;
141
- const b64 = btoa(unescape(encodeURIComponent(stitched)));
142
- props.onSave(b64, {
143
- fileName: props.fileName,
144
- fileType: props.fileType,
145
- exportedAsPdf: exportPdf,
146
- annotations: { signaturePlacements: props.signaturePlacements }
147
- });
148
- }
149
- (0, import_react.useImperativeHandle)(ref, () => ({
150
- save,
151
- requestThumbnail
152
- }));
153
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-root", children: [
154
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "hv-toolbar", children: [
155
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => exec("bold"), disabled: readOnly, children: "B" }),
156
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => exec("italic"), disabled: readOnly, children: "I" }),
157
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => exec("underline"), disabled: readOnly, children: "U" }),
158
- props.armedSignatureUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hv-hint", children: "Click page to place signature" })
159
- ] }),
160
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hv-scroll", ref: scrollerRef, onClick: onClickPage, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "hv-pageStage", ref: captureRef, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
161
- "div",
162
- {
163
- ref: editorRef,
164
- className: `hv-editor ${readOnly ? "ro" : ""}`,
165
- contentEditable: !readOnly,
166
- suppressContentEditableWarning: true
167
- }
168
- ) }) })
169
- ] });
170
- }
171
- );
172
- function escapeHtml(s) {
173
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;");
174
- }
175
-
176
- // src/editors/SpreadsheetEditor.tsx
177
- var import_react2 = require("react");
178
- var XLSX = __toESM(require("xlsx"), 1);
179
-
180
- // src/utils/fileSource.ts
181
- function guessFileType(name, explicit) {
182
- if (explicit) {
183
- return explicit;
184
- }
185
- const ext = (name?.split(".").pop() || "").toLowerCase();
186
- const allowed = [
187
- "pdf",
188
- "md",
189
- "docx",
190
- "xlsx",
191
- "pptx",
192
- "txt",
193
- "png",
194
- "jpg",
195
- "svg"
196
- ];
197
- return allowed.includes(ext) ? ext : "txt";
198
- }
199
- function arrayBufferToBase64(buf) {
200
- const bytes = new Uint8Array(buf);
201
- let binary = "";
202
- const chunk = 32768;
203
- for (let i = 0; i < bytes.length; i += chunk) {
204
- binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
205
- }
206
- return btoa(binary);
207
- }
208
- async function base64ToArrayBuffer(b64) {
209
- const bin = atob(b64);
210
- const len = bin.length;
211
- const bytes = new Uint8Array(len);
212
- for (let i = 0; i < len; i++) {
213
- bytes[i] = bin.charCodeAt(i);
214
- }
215
- return bytes.buffer;
216
- }
217
- async function resolveSource(args) {
218
- const fileType = guessFileType(args.fileName, args.fileType);
219
- const fileName = args.fileName ?? `document.${fileType}`;
220
- if (args.blob) {
221
- const ab = await args.blob.arrayBuffer();
222
- const url = URL.createObjectURL(args.blob);
223
- return { fileType, fileName, arrayBuffer: ab, url };
224
- }
225
- if (args.base64) {
226
- const ab = await base64ToArrayBuffer(args.base64);
227
- return { fileType, fileName, arrayBuffer: ab };
228
- }
229
- if (!args.fileUrl) {
230
- throw new Error("No file source provided. Use fileUrl, blob, or base64.");
231
- }
232
- const res = await fetch(args.fileUrl);
233
- if (!res.ok) {
234
- throw new Error(`Failed to fetch file (${res.status})`);
235
- }
236
- const total = Number(res.headers.get("content-length") || "") || void 0;
237
- if (!res.body) {
238
- const ab = await res.arrayBuffer();
239
- args.onProgress?.(ab.byteLength, total);
240
- return { fileType, fileName, arrayBuffer: ab, url: args.fileUrl };
241
- }
242
- const reader = res.body.getReader();
243
- const chunks = [];
244
- let loaded = 0;
245
- while (true) {
246
- const { done, value } = await reader.read();
247
- if (done) {
248
- break;
249
- }
250
- if (value) {
251
- chunks.push(value);
252
- loaded += value.length;
253
- args.onProgress?.(loaded, total);
254
- }
255
- }
256
- const out = new Uint8Array(loaded);
257
- let offset = 0;
258
- for (const c of chunks) {
259
- out.set(c, offset);
260
- offset += c.length;
261
- }
262
- return { fileType, fileName, arrayBuffer: out.buffer, url: args.fileUrl };
263
- }
264
-
265
- // src/editors/SpreadsheetEditor.tsx
266
- var import_jsx_runtime2 = require("react/jsx-runtime");
267
- var SpreadsheetEditor = (0, import_react2.forwardRef)(function SpreadsheetEditor2(props, ref) {
268
- const readonly = props.mode === "view";
269
- const [grid, setGrid] = (0, import_react2.useState)(() => Array.from({ length: 30 }, () => Array.from({ length: 12 }, () => "")));
270
- (0, import_react2.useEffect)(() => {
271
- if (!props.arrayBuffer) {
272
- return;
273
- }
274
- try {
275
- const wb = XLSX.read(props.arrayBuffer, { type: "array" });
276
- const name = wb.SheetNames[0];
277
- const ws = wb.Sheets[name];
278
- const aoa = XLSX.utils.sheet_to_json(ws, { header: 1, raw: true });
279
- const rows = Math.max(30, aoa.length);
280
- const cols2 = Math.max(12, Math.max(...aoa.map((r) => r?.length ?? 0), 0));
281
- const next = Array.from({ length: rows }, (_, r) => Array.from({ length: cols2 }, (_2, c) => {
282
- const v = aoa[r]?.[c];
283
- return v == null ? "" : String(v);
284
- }));
285
- setGrid(next);
286
- } catch {
287
- }
288
- }, [props.arrayBuffer]);
289
- async function save(exportPdf) {
290
- const ws = XLSX.utils.aoa_to_sheet(grid);
291
- const wb = XLSX.utils.book_new();
292
- XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
293
- const out = XLSX.write(wb, { type: "array", bookType: "xlsx" });
294
- const b64 = arrayBufferToBase64(out);
295
- props.onSave(b64, { fileName: ensureExt(props.fileName, "xlsx"), fileType: "xlsx", exportedAsPdf: !!exportPdf });
296
- }
297
- (0, import_react2.useImperativeHandle)(ref, () => ({
298
- save,
299
- requestThumbnails: async () => void 0
300
- }));
301
- const cols = (0, import_react2.useMemo)(() => Array.from({ length: grid[0]?.length ?? 0 }, (_, i) => String.fromCharCode(65 + i % 26)), [grid]);
302
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-sheet", children: [
303
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-sheetbar", children: [
304
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-sheetbar-title", children: props.fileName }),
305
- !readonly ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "hv-btn", type: "button", onClick: () => void save(false), children: props.locale["toolbar.save"] ?? "Save" }) : null
306
- ] }),
307
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-sheetgrid", role: "table", "aria-label": "Spreadsheet", children: [
308
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-sheetrow hv-sheetrow--header", role: "row", children: [
309
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-sheetcell hv-sheetcell--corner", role: "columnheader" }),
310
- cols.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-sheetcell hv-sheetcell--header", role: "columnheader", children: c }, i))
311
- ] }),
312
- grid.map((row, r) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "hv-sheetrow", role: "row", children: [
313
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "hv-sheetcell hv-sheetcell--header", role: "rowheader", children: r + 1 }),
314
- row.map((val, c) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
315
- "div",
316
- {
317
- className: "hv-sheetcell",
318
- role: "cell",
319
- contentEditable: !readonly,
320
- suppressContentEditableWarning: true,
321
- onInput: (e) => {
322
- const text = e.currentTarget.textContent ?? "";
323
- setGrid((prev) => {
324
- const next = prev.map((rr) => rr.slice());
325
- next[r][c] = text;
326
- return next;
327
- });
328
- },
329
- children: val
330
- },
331
- c
332
- ))
333
- ] }, r))
334
- ] })
335
- ] });
336
- });
337
- function ensureExt(name, ext) {
338
- const base = name.includes(".") ? name.slice(0, name.lastIndexOf(".")) : name;
339
- return `${base}.${ext}`;
340
- }
341
-
342
- // src/renderers/ImageRenderer.tsx
343
- var import_react3 = require("react");
344
- var import_jsx_runtime3 = require("react/jsx-runtime");
345
- function ImageRenderer({
346
- arrayBuffer,
347
- fileType,
348
- fileName
349
- }) {
350
- const [zoom, setZoom] = (0, import_react3.useState)(1);
351
- const url = (0, import_react3.useMemo)(() => {
352
- if (!arrayBuffer) {
353
- return void 0;
354
- }
355
- const mime = fileType === "svg" ? "image/svg+xml" : fileType === "png" ? "image/png" : "image/jpeg";
356
- return URL.createObjectURL(new Blob([arrayBuffer], { type: mime }));
357
- }, [arrayBuffer, fileType]);
358
- (0, import_react3.useEffect)(() => {
359
- return () => {
360
- if (url) {
361
- URL.revokeObjectURL(url);
362
- }
363
- };
364
- }, [url]);
365
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-doc", children: [
366
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-mini-toolbar", children: [
367
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-title", children: fileName }),
368
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-spacer" }),
369
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
370
- "button",
371
- {
372
- type: "button",
373
- className: "hv-btn",
374
- onClick: () => setZoom((z) => Math.max(0.25, z - 0.25)),
375
- children: "-"
376
- }
377
- ),
378
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-zoom", children: [
379
- Math.round(zoom * 100),
380
- "%"
381
- ] }),
382
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
383
- "button",
384
- {
385
- type: "button",
386
- className: "hv-btn",
387
- onClick: () => setZoom((z) => Math.min(4, z + 0.25)),
388
- children: "+"
389
- }
390
- )
391
- ] }),
392
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "hv-center", children: [
393
- !arrayBuffer && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-error", children: "No image data provided." }),
394
- arrayBuffer && !url && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "hv-error", children: "Failed to load image." }),
395
- url && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
396
- "img",
397
- {
398
- src: url,
399
- alt: fileName,
400
- style: { transform: `scale(${zoom})` },
401
- className: "hv-image"
402
- }
403
- )
404
- ] })
405
- ] });
406
- }
407
-
408
- // src/renderers/PdfRenderer.tsx
409
- var import_pdfjs_dist = require("pdfjs-dist");
410
- var import_react4 = require("react");
411
- var import_jsx_runtime4 = require("react/jsx-runtime");
412
- var import_meta = {};
413
- function PdfRenderer(props) {
414
- const { url, arrayBuffer } = props;
415
- const [doc, setDoc] = (0, import_react4.useState)(null);
416
- const [pageCount, setPageCount] = (0, import_react4.useState)(0);
417
- const [rendered, setRendered] = (0, import_react4.useState)(
418
- /* @__PURE__ */ new Map()
419
- );
420
- const [thumbs, setThumbs] = (0, import_react4.useState)([]);
421
- const [size, setSize] = (0, import_react4.useState)({ w: 840, h: 1188 });
422
- const [error, setError] = (0, import_react4.useState)(null);
423
- const [loading, setLoading] = (0, import_react4.useState)(false);
424
- const containerRef = (0, import_react4.useRef)(null);
425
- (0, import_react4.useEffect)(() => {
426
- try {
427
- import_pdfjs_dist.GlobalWorkerOptions.workerSrc = new URL(
428
- "pdfjs-dist/build/pdf.worker.min.mjs",
429
- import_meta.url
430
- ).toString();
431
- } catch {
432
- }
433
- }, []);
434
- (0, import_react4.useEffect)(() => {
435
- let cancel = false;
436
- setError(null);
437
- setLoading(true);
438
- (async () => {
439
- setDoc(null);
440
- setRendered(/* @__PURE__ */ new Map());
441
- setThumbs([]);
442
- if (!url && !arrayBuffer) {
443
- setError("No PDF source provided.");
444
- setLoading(false);
445
- return;
446
- }
447
- try {
448
- const task = (0, import_pdfjs_dist.getDocument)(
449
- url ? { url, rangeChunkSize: 512 * 1024 } : { data: arrayBuffer }
450
- );
451
- const pdf = await task.promise;
452
- if (cancel) {
453
- return;
454
- }
455
- setDoc(pdf);
456
- setPageCount(pdf.numPages);
457
- props.onPageCount(pdf.numPages);
458
- const p1 = await pdf.getPage(1);
459
- const base = p1.getViewport({ scale: 1 });
460
- const w = Math.min(980, Math.max(640, base.width));
461
- const s = w / base.width;
462
- const vp = p1.getViewport({ scale: s });
463
- setSize({ w: Math.round(vp.width), h: Math.round(vp.height) });
464
- const thumbWidth = 56;
465
- const thumbsArr = [];
466
- for (let i = 1; i <= pdf.numPages; i++) {
467
- const page = await pdf.getPage(i);
468
- const pageBase = page.getViewport({ scale: 1 });
469
- const thumbScale = thumbWidth / pageBase.width;
470
- const thumbVp = page.getViewport({ scale: thumbScale });
471
- const thumbCanvas = document.createElement("canvas");
472
- thumbCanvas.width = Math.round(thumbVp.width);
473
- thumbCanvas.height = Math.round(thumbVp.height);
474
- const thumbCtx = thumbCanvas.getContext("2d", { alpha: false });
475
- if (thumbCtx) {
476
- await page.render({ canvasContext: thumbCtx, viewport: thumbVp }).promise;
477
- thumbsArr.push(thumbCanvas.toDataURL("image/png"));
478
- } else {
479
- thumbsArr.push(void 0);
480
- }
481
- }
482
- setThumbs(thumbsArr);
483
- } catch (e) {
484
- setError(
485
- "Failed to load PDF. " + (e instanceof Error ? e.message : "")
486
- );
487
- } finally {
488
- setLoading(false);
489
- }
490
- })();
491
- return () => {
492
- cancel = true;
493
- };
494
- }, [url, arrayBuffer]);
495
- (0, import_react4.useEffect)(() => {
496
- props.onThumbs(thumbs);
497
- }, [thumbs]);
498
- const pagesToShow = (0, import_react4.useMemo)(() => {
499
- if (props.layout === "side-by-side" && pageCount > 1) {
500
- const left = Math.max(1, Math.min(props.currentPage, pageCount));
501
- const right = Math.max(1, Math.min(left + 1, pageCount));
502
- return left === right ? [left] : [left, right];
503
- }
504
- return [Math.max(1, Math.min(props.currentPage, pageCount))];
505
- }, [props.currentPage, props.layout, pageCount]);
506
- (0, import_react4.useEffect)(() => {
507
- if (!doc) {
508
- return;
509
- }
510
- let cancel = false;
511
- (async () => {
512
- for (const p of pagesToShow) {
513
- if (rendered.has(p)) {
514
- continue;
515
- }
516
- try {
517
- const page = await doc.getPage(p);
518
- if (cancel) {
519
- return;
520
- }
521
- const base = page.getViewport({ scale: 1 });
522
- const vp = page.getViewport({ scale: size.w / base.width });
523
- const canvas = document.createElement("canvas");
524
- canvas.width = Math.round(vp.width);
525
- canvas.height = Math.round(vp.height);
526
- const ctx = canvas.getContext("2d", { alpha: false });
527
- if (!ctx) {
528
- continue;
529
- }
530
- await page.render({ canvasContext: ctx, viewport: vp }).promise;
531
- if (cancel) {
532
- return;
533
- }
534
- setRendered((prev) => {
535
- const next = new Map(prev);
536
- next.set(p, canvas);
537
- return next;
538
- });
539
- } catch {
540
- }
541
- }
542
- })();
543
- return () => {
544
- cancel = true;
545
- };
546
- }, [doc, pagesToShow, size.w, rendered]);
547
- function onWheel(e) {
548
- if (!pageCount) {
549
- return;
550
- }
551
- if (Math.abs(e.deltaY) < 10) {
552
- return;
553
- }
554
- const dir = e.deltaY > 0 ? 1 : -1;
555
- const step = props.layout === "side-by-side" ? 2 : 1;
556
- const next = Math.max(
557
- 1,
558
- Math.min(pageCount, props.currentPage + dir * step)
559
- );
560
- props.onCurrentPageChange(next);
561
- }
562
- function clickPlace(e, page) {
563
- const stamp = props.signatureStamp;
564
- if (!stamp?.armed) {
565
- return;
566
- }
567
- const rect = e.currentTarget.getBoundingClientRect();
568
- const x = (e.clientX - rect.left) / rect.width;
569
- const y = (e.clientY - rect.top) / rect.height;
570
- stamp.onPlaced({ page, x, y, w: 0.22, h: 0.08 });
571
- }
572
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "hv-doc", ref: containerRef, onWheel, children: [
573
- !doc ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hv-loading", children: "Loading PDF\u2026" }) : null,
574
- doc ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
575
- "div",
576
- {
577
- className: props.layout === "side-by-side" ? "hv-pages hv-pages--two" : "hv-pages",
578
- children: pagesToShow.map((p) => {
579
- const c = rendered.get(p);
580
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
581
- "div",
582
- {
583
- className: "hv-page",
584
- style: { width: size.w, height: size.h },
585
- onClick: (e) => clickPlace(e, p),
586
- children: c ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
587
- "canvas",
588
- {
589
- className: "hv-canvas",
590
- width: c.width,
591
- height: c.height,
592
- ref: (node) => {
593
- if (!node) {
594
- return;
595
- }
596
- const ctx = node.getContext("2d");
597
- if (ctx) {
598
- ctx.drawImage(c, 0, 0);
599
- }
600
- }
601
- }
602
- ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "hv-loading", children: "Rendering\u2026" })
603
- },
604
- p
605
- );
606
- })
607
- }
608
- ) : null
609
- ] });
610
- }
611
-
612
- // src/renderers/PptxRenderer.tsx
613
- var import_react5 = require("react");
614
- var import_jszip = __toESM(require("jszip"), 1);
615
- var import_jsx_runtime5 = require("react/jsx-runtime");
616
- function decodeXml(s) {
617
- return s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
618
- }
619
- function extractText(xml) {
620
- return [...xml.matchAll(/<a:t>(.*?)<\/a:t>/g)].map((m) => decodeXml(m[1] || "")).join(" ").trim();
621
- }
622
- function PptxRenderer(props) {
623
- const [slides, setSlides] = (0, import_react5.useState)([]);
624
- const [thumbs, setThumbs] = (0, import_react5.useState)([]);
625
- const [error, setError] = (0, import_react5.useState)(null);
626
- const [loading, setLoading] = (0, import_react5.useState)(false);
627
- (0, import_react5.useEffect)(() => {
628
- let cancel = false;
629
- setError(null);
630
- setLoading(true);
631
- (async () => {
632
- setSlides([]);
633
- setThumbs([]);
634
- if (!props.arrayBuffer) {
635
- setError("No PPTX source provided.");
636
- setLoading(false);
637
- return;
638
- }
639
- try {
640
- const zip = await import_jszip.default.loadAsync(props.arrayBuffer);
641
- const slidePaths = Object.keys(zip.files).filter((p) => /^ppt\/slides\/slide\d+\.xml$/.test(p)).sort();
642
- const slidesOut = [];
643
- for (let i = 0; i < slidePaths.length; i++) {
644
- const xml = await zip.files[slidePaths[i]].async("string");
645
- slidesOut.push({ index: i + 1, text: extractText(xml) });
646
- }
647
- if (cancel) return;
648
- setSlides(
649
- slidesOut.length ? slidesOut : [{ index: 1, text: "(empty)" }]
650
- );
651
- props.onSlideCount(slidesOut.length || 1);
652
- const thumbWidth = 56;
653
- const thumbsArr = [];
654
- for (let i = 0; i < (slidesOut.length || 1); i++) {
655
- thumbsArr.push(
656
- `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgThumb(i + 1))}`
657
- );
658
- }
659
- setThumbs(thumbsArr);
660
- } catch (e) {
661
- setSlides([
662
- { index: 1, text: "Unable to render this .pptx in-browser." }
663
- ]);
664
- setThumbs([void 0]);
665
- setError(
666
- "Failed to load PPTX. " + (e instanceof Error ? e.message : "")
667
- );
668
- } finally {
669
- setLoading(false);
670
- }
671
- })();
672
- return () => {
673
- cancel = true;
674
- };
675
- }, [props.arrayBuffer]);
676
- (0, import_react5.useEffect)(() => {
677
- props.onThumbs(thumbs);
678
- }, [thumbs]);
679
- const pagesToShow = (0, import_react5.useMemo)(() => {
680
- const total = slides.length;
681
- if (props.layout === "side-by-side" && total > 1) {
682
- const left = Math.max(1, Math.min(props.currentPage, total));
683
- const right = Math.max(1, Math.min(left + 1, total));
684
- return left === right ? [left] : [left, right];
685
- }
686
- return [Math.max(1, Math.min(props.currentPage, total))];
687
- }, [props.currentPage, props.layout, slides.length]);
688
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hv-doc", children: [
689
- loading && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-loading", children: "Loading PPTX\u2026" }),
690
- error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-error", children: error }),
691
- !loading && !error && (!slides || slides.length === 0) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-error", children: "No slides to display." }),
692
- !error && slides && slides.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
693
- "div",
694
- {
695
- className: props.layout === "side-by-side" ? "hv-pages hv-pages--two" : "hv-pages",
696
- children: pagesToShow.map((p) => {
697
- const s = slides[p - 1];
698
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
699
- "div",
700
- {
701
- className: "hv-slide",
702
- tabIndex: 0,
703
- onFocus: () => props.onCurrentPageChange(p),
704
- children: [
705
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "hv-slide-title", children: [
706
- "Slide ",
707
- p
708
- ] }),
709
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "hv-slide-text", children: s?.text || "" })
710
- ]
711
- },
712
- p
713
- );
714
- })
715
- }
716
- )
717
- ] });
718
- }
719
- function svgThumb(n) {
720
- 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>`;
721
- }
722
-
723
- // src/utils/locale.ts
724
- var defaultLocale = {
725
- "loading": "Loading\u2026",
726
- "error.title": "Error",
727
- "toolbar.layout.single": "Single page",
728
- "toolbar.layout.two": "Side-by-side",
729
- "toolbar.thumbs": "Thumbnails",
730
- "toolbar.signatures": "Signatures",
731
- "toolbar.sign": "Sign Document",
732
- "toolbar.save": "Save",
733
- "toolbar.exportPdf": "Export as PDF",
734
- "thumbnails.title": "Thumbnails",
735
- "thumbnails.page": "Page",
736
- "signatures.title": "Signatures",
737
- "signatures.empty": "No signatures",
738
- "signatures.placeHint": "Click on the document to place the signature.",
739
- "a11y.viewer": "Document viewer",
740
- "a11y.ribbon": "Ribbon",
741
- "a11y.editor": "Document editor"
742
- };
743
-
744
- // src/components/SignaturePanel.tsx
745
- var import_react6 = __toESM(require("react"), 1);
746
- var import_jsx_runtime6 = require("react/jsx-runtime");
747
- function SignaturePanel(props) {
748
- const title = props.locale["signatures.title"] ?? "Signatures";
749
- const deduped = import_react6.default.useMemo(() => {
750
- const seen = /* @__PURE__ */ new Set();
751
- return props.signatures.filter((s) => {
752
- const key = `${s.signedBy}|${s.dateSigned}|${s.signatureImageUrl}`;
753
- if (seen.has(key)) return false;
754
- seen.add(key);
755
- return true;
756
- });
757
- }, [props.signatures]);
758
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
759
- "aside",
760
- {
761
- className: props.collapsed ? "hv-side hv-side--collapsed" : "hv-side",
762
- "aria-label": title,
763
- children: [
764
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hv-sidebar-header", children: [
765
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
766
- "button",
767
- {
768
- type: "button",
769
- className: "hv-icon",
770
- onClick: props.onToggle,
771
- "aria-label": props.locale["toolbar.signatures"] ?? "Signatures",
772
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { "aria-hidden": true, children: "\u270D" })
773
- }
774
- ),
775
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-sidebar-title", children: title })
776
- ] }),
777
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hv-sidebar-body", children: [
778
- deduped.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-signature-empty", "aria-live": "polite", children: props.locale["signatures.empty"] ?? "No signatures yet." }),
779
- deduped.map((s, idx) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
780
- "div",
781
- {
782
- className: "hv-signature-card",
783
- tabIndex: 0,
784
- "aria-label": `Signature by ${s.signedBy}`,
785
- children: [
786
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
787
- "img",
788
- {
789
- src: s.signatureImageUrl,
790
- alt: props.locale["signatures.imgAlt"] ? props.locale["signatures.imgAlt"].replace(
791
- "{name}",
792
- s.signedBy
793
- ) : `Signature by ${s.signedBy}`,
794
- className: "hv-signature-img"
795
- }
796
- ),
797
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "hv-signature-meta", children: [
798
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-signature-name", children: s.signedBy }),
799
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-signature-date", children: new Date(s.dateSigned).toLocaleString() }),
800
- s.comment ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "hv-signature-comment", children: s.comment }) : null
801
- ] })
802
- ]
803
- },
804
- `${s.signedBy}-${s.dateSigned}-${s.signatureImageUrl}`
805
- ))
806
- ] })
807
- ]
808
- }
809
- );
810
- }
811
-
812
- // src/components/ThumbnailsSidebar.tsx
813
- var import_jsx_runtime7 = require("react/jsx-runtime");
814
- function ThumbnailsSidebar(props) {
815
- const t = props.locale["thumbnails.title"] ?? "Thumbnails";
816
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
817
- "aside",
818
- {
819
- className: props.collapsed ? "hv-thumbs hv-thumbs--collapsed" : "hv-thumbs",
820
- "aria-label": t,
821
- children: [
822
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "hv-thumbs-header", children: [
823
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
824
- "button",
825
- {
826
- type: "button",
827
- className: "hv-thumbs-toggle",
828
- onClick: props.onToggle,
829
- "aria-label": props.collapsed ? props.locale["thumbnails.open"] ?? "Open thumbnails" : props.locale["thumbnails.close"] ?? "Close thumbnails",
830
- children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "hv-thumbs-toggle-icon", children: props.collapsed ? "\u25B8" : "\u25BE" })
831
- }
832
- ),
833
- !props.collapsed && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-thumbs-title", children: t })
834
- ] }),
835
- !props.collapsed && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-thumbs-list", role: "list", children: props.thumbnails.map((th, idx) => {
836
- const p = idx + 1;
837
- const active = p === props.currentPage;
838
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
839
- "button",
840
- {
841
- type: "button",
842
- role: "listitem",
843
- className: active ? "hv-thumb hv-thumb--active" : "hv-thumb",
844
- onClick: () => props.onSelectPage(p),
845
- "aria-current": active ? "page" : void 0,
846
- tabIndex: 0,
847
- children: [
848
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-thumb-img", "aria-hidden": true, children: th.dataUrl ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("img", { src: th.dataUrl, alt: "" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-thumb-placeholder" }) }),
849
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "hv-thumb-label", children: th.label })
850
- ]
851
- },
852
- th.id
853
- );
854
- }) })
855
- ]
856
- }
857
- );
858
- }
859
-
860
- // src/components/Toolbar.tsx
861
- var import_jsx_runtime8 = require("react/jsx-runtime");
862
- function Toolbar(props) {
863
- const t = (k, fallback) => props.locale[k] ?? fallback;
864
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
865
- "div",
866
- {
867
- className: "hv-toolbar",
868
- role: "toolbar",
869
- "aria-label": t("a11y.toolbar", "Document toolbar"),
870
- children: [
871
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hv-toolbar__left space-x-1", children: [
872
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
873
- "button",
874
- {
875
- type: "button",
876
- className: "hv-btn text-sm",
877
- onClick: props.onToggleThumbnails,
878
- "aria-pressed": props.showThumbnails,
879
- children: t("toolbar.thumbs", "Thumbnails")
880
- }
881
- ),
882
- props.mode !== "create" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
883
- "button",
884
- {
885
- type: "button",
886
- className: "hv-btn text-sm",
887
- onClick: props.onToggleSignatures,
888
- "aria-pressed": props.showSignatures,
889
- children: t("toolbar.signatures", "Signatures")
890
- }
891
- ),
892
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "hv-sep" }),
893
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
894
- "button",
895
- {
896
- type: "button",
897
- className: props.layout === "single" ? "hv-btn hv-btn--active text-sm" : "hv-btn text-sm",
898
- onClick: () => props.onChangeLayout("single"),
899
- children: t("toolbar.layout.single", "Single")
900
- }
901
- ),
902
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
903
- "button",
904
- {
905
- type: "button",
906
- className: props.layout === "side-by-side" ? "hv-btn hv-btn--active text-sm" : "hv-btn text-sm",
907
- onClick: () => props.onChangeLayout("side-by-side"),
908
- children: t("toolbar.layout.two", "Two")
909
- }
910
- )
911
- ] }),
912
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "hv-toolbar__right", children: [
913
- props.showHeaderFooterToggle && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { className: "hv-toggle", children: [
914
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
915
- "input",
916
- {
917
- type: "checkbox",
918
- checked: props.headerFooterEnabled,
919
- onChange: props.onToggleHeaderFooter
920
- }
921
- ),
922
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: t("toolbar.letterhead", "Letterhead") })
923
- ] }),
924
- props.allowSigning && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
925
- "button",
926
- {
927
- type: "button",
928
- className: "hv-btn hv-btn--primary text-sm",
929
- onClick: props.onSign,
930
- disabled: props.signingDisabled,
931
- children: t("toolbar.sign", "Sign Document")
932
- }
933
- ),
934
- props.canExportPdf && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
935
- "button",
936
- {
937
- type: "button",
938
- className: "hv-btn text-sm",
939
- onClick: props.onExportPdf,
940
- children: t("toolbar.exportPdf", "Export as PDF")
941
- }
942
- ),
943
- props.canSave && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
944
- "button",
945
- {
946
- type: "button",
947
- className: "hv-btn hv-btn--primary text-sm",
948
- onClick: props.onSave,
949
- children: t("toolbar.save", "Save")
950
- }
951
- )
952
- ] })
953
- ]
954
- }
955
- );
956
- }
957
-
958
- // src/components/DocumentViewer.tsx
959
- var import_jsx_runtime9 = require("react/jsx-runtime");
960
- function DocumentViewer(props) {
961
- const mode = props.mode ?? "view";
962
- const theme = props.theme ?? "light";
963
- const locale = (0, import_react7.useMemo)(
964
- () => ({ ...defaultLocale, ...props.locale ?? {} }),
965
- [props.locale]
966
- );
967
- const [layout, setLayout] = (0, import_react7.useState)(
968
- props.defaultLayout ?? "single"
969
- );
970
- const [showThumbnails, setShowThumbnails] = (0, import_react7.useState)(true);
971
- const [showSignatures, setShowSignatures] = (0, import_react7.useState)(true);
972
- const [headerFooterEnabled, setHeaderFooterEnabled] = (0, import_react7.useState)(true);
973
- const allowSigning = props.allowSigning ?? false;
974
- const [signingBusy, setSigningBusy] = (0, import_react7.useState)(false);
975
- const [resolved, setResolved] = (0, import_react7.useState)(null);
976
- const [error, setError] = (0, import_react7.useState)("");
977
- const [pageCount, setPageCount] = (0, import_react7.useState)(1);
978
- const [currentPage, setCurrentPage] = (0, import_react7.useState)(1);
979
- const [thumbs, setThumbs] = (0, import_react7.useState)([]);
980
- const [localSignatures, setLocalSignatures] = (0, import_react7.useState)(
981
- props.signatures ?? []
982
- );
983
- (0, import_react7.useEffect)(
984
- () => setLocalSignatures(props.signatures ?? []),
985
- [props.signatures]
986
- );
987
- const [sigPlacements, setSigPlacements] = (0, import_react7.useState)([]);
988
- const [armedSignatureUrl, setArmedSignatureUrl] = (0, import_react7.useState)(
989
- null
990
- );
991
- const editorRef = (0, import_react7.useRef)(null);
992
- (0, import_react7.useEffect)(() => {
993
- let cancelled = false;
994
- (async () => {
995
- setError("");
996
- setResolved(null);
997
- setThumbs([]);
998
- setPageCount(1);
999
- setCurrentPage(1);
1000
- setSigPlacements([]);
1001
- setArmedSignatureUrl(null);
1002
- if (mode === "create") {
1003
- const ft = props.fileType ?? "docx";
1004
- setResolved({
1005
- fileType: ft,
1006
- fileName: props.fileName ?? `Untitled.${ft}`
1007
- });
1008
- return;
1009
- }
1010
- try {
1011
- const res = await resolveSource({
1012
- fileUrl: props.fileUrl,
1013
- base64: props.base64,
1014
- blob: props.blob,
1015
- fileName: props.fileName,
1016
- fileType: props.fileType
1017
- });
1018
- if (cancelled) {
1019
- return;
1020
- }
1021
- setResolved({
1022
- fileType: res.fileType,
1023
- fileName: res.fileName,
1024
- url: res.url,
1025
- arrayBuffer: res.arrayBuffer
1026
- });
1027
- } catch (e) {
1028
- if (cancelled) {
1029
- return;
1030
- }
1031
- setError(e instanceof Error ? e.message : String(e));
1032
- }
1033
- })();
1034
- return () => {
1035
- cancelled = true;
1036
- };
1037
- }, [
1038
- mode,
1039
- props.fileUrl,
1040
- props.base64,
1041
- props.blob,
1042
- props.fileName,
1043
- props.fileType
1044
- ]);
1045
- const thumbnails = (0, import_react7.useMemo)(() => {
1046
- const n = Math.max(1, pageCount);
1047
- return Array.from({ length: n }, (_, i) => ({
1048
- id: `p-${i + 1}`,
1049
- label: `${locale["thumbnails.page"] ?? "Page"} ${i + 1}`,
1050
- dataUrl: thumbs[i]
1051
- }));
1052
- }, [pageCount, thumbs, locale]);
1053
- async function handleSignRequest() {
1054
- if (!allowSigning || signingBusy || !props.onSignRequest) {
1055
- return;
1056
- }
1057
- setSigningBusy(true);
1058
- try {
1059
- const sig = await props.onSignRequest();
1060
- setLocalSignatures((prev) => [...prev, sig]);
1061
- setArmedSignatureUrl(sig.signatureImageUrl);
1062
- } finally {
1063
- setSigningBusy(false);
1064
- }
1065
- }
1066
- function placeSignature(p) {
1067
- if (!armedSignatureUrl) {
1068
- return;
1069
- }
1070
- setSigPlacements((prev) => [
1071
- ...prev,
1072
- { ...p, signatureImageUrl: armedSignatureUrl }
1073
- ]);
1074
- setArmedSignatureUrl(null);
1075
- }
1076
- async function handleSave(exportPdf) {
1077
- if (editorRef.current) {
1078
- await editorRef.current.save(!!exportPdf);
1079
- return;
1080
- }
1081
- if (!resolved?.arrayBuffer) {
1082
- return;
1083
- }
1084
- const b64 = arrayBufferToBase642(resolved.arrayBuffer);
1085
- props.onSave?.(b64, {
1086
- fileName: resolved.fileName,
1087
- fileType: resolved.fileType,
1088
- annotations: { sigPlacements }
1089
- });
1090
- }
1091
- const canSave = mode === "edit" || mode === "create";
1092
- const canExportPdf = (mode === "edit" || mode === "create") && (resolved?.fileType === "docx" || resolved?.fileType === "md" || resolved?.fileType === "txt" || resolved?.fileType === "xlsx");
1093
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: `hv-root`, "data-hv-theme": theme, children: [
1094
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1095
- Toolbar,
1096
- {
1097
- locale,
1098
- mode,
1099
- fileType: resolved?.fileType,
1100
- layout,
1101
- onChangeLayout: setLayout,
1102
- showThumbnails,
1103
- onToggleThumbnails: () => setShowThumbnails((v) => !v),
1104
- showSignatures,
1105
- onToggleSignatures: () => setShowSignatures((v) => !v),
1106
- onSign: () => void handleSignRequest(),
1107
- allowSigning,
1108
- signingDisabled: signingBusy || !props.onSignRequest,
1109
- canSave,
1110
- onSave: () => void handleSave(false),
1111
- canExportPdf,
1112
- onExportPdf: () => void handleSave(true),
1113
- headerFooterEnabled,
1114
- showHeaderFooterToggle: (props.enableHeaderFooterToggle ?? true) && mode === "create",
1115
- onToggleHeaderFooter: () => setHeaderFooterEnabled((v) => !v)
1116
- }
1117
- ),
1118
- error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hv-error", role: "alert", children: [
1119
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hv-error-title", children: locale["error.title"] ?? "Error" }),
1120
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hv-error-body", children: error })
1121
- ] }) : null,
1122
- !resolved && !error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "hv-loading", "aria-busy": "true", children: locale.loading ?? "Loading\u2026" }) : null,
1123
- resolved ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "hv-shell", children: [
1124
- mode !== "create" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1125
- ThumbnailsSidebar,
1126
- {
1127
- locale,
1128
- thumbnails,
1129
- currentPage,
1130
- collapsed: !showThumbnails,
1131
- onToggle: () => setShowThumbnails((v) => !v),
1132
- onSelectPage: setCurrentPage
1133
- }
1134
- ) : null,
1135
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("main", { className: "hv-main", children: [
1136
- resolved.fileType === "pdf" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1137
- PdfRenderer,
1138
- {
1139
- url: resolved.url,
1140
- arrayBuffer: resolved.arrayBuffer,
1141
- layout,
1142
- currentPage,
1143
- onCurrentPageChange: setCurrentPage,
1144
- onPageCount: (n) => {
1145
- setPageCount(n);
1146
- setThumbs(
1147
- (prev) => prev.length === n ? prev : Array.from({ length: n }, (_, i) => prev[i])
1148
- );
1149
- },
1150
- onThumbs: (t) => setThumbs(t),
1151
- signatureStamp: armedSignatureUrl ? {
1152
- imageUrl: armedSignatureUrl,
1153
- armed: true,
1154
- onPlaced: placeSignature
1155
- } : void 0
1156
- }
1157
- ) : null,
1158
- resolved.fileType === "docx" || resolved.fileType === "md" || resolved.fileType === "txt" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1159
- RichTextEditor,
1160
- {
1161
- ref: editorRef,
1162
- mode,
1163
- fileType: resolved.fileType,
1164
- fileName: resolved.fileName,
1165
- arrayBuffer: resolved.arrayBuffer,
1166
- headerComponent: props.headerComponent,
1167
- footerComponent: props.footerComponent,
1168
- headerFooterEnabled,
1169
- locale,
1170
- signatures: localSignatures,
1171
- signaturePlacements: sigPlacements,
1172
- onPageCount: (n) => {
1173
- setPageCount(n);
1174
- setThumbs(
1175
- (prev) => prev.length === n ? prev : Array.from({ length: n }, (_, i) => prev[i])
1176
- );
1177
- },
1178
- onSave: (b64, meta) => props.onSave?.(b64, meta),
1179
- armedSignatureUrl,
1180
- onPlaceSignature: placeSignature
1181
- }
1182
- ) : null,
1183
- resolved.fileType === "xlsx" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1184
- SpreadsheetEditor,
1185
- {
1186
- ref: editorRef,
1187
- mode,
1188
- fileName: resolved.fileName,
1189
- arrayBuffer: resolved.arrayBuffer,
1190
- locale,
1191
- onSave: (b64, meta) => props.onSave?.(b64, meta)
1192
- }
1193
- ) : null,
1194
- resolved.fileType === "pptx" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1195
- PptxRenderer,
1196
- {
1197
- arrayBuffer: resolved.arrayBuffer,
1198
- layout,
1199
- currentPage,
1200
- onCurrentPageChange: setCurrentPage,
1201
- onSlideCount: (n) => {
1202
- setPageCount(n);
1203
- setThumbs(
1204
- (prev) => prev.length === n ? prev : Array.from({ length: n }, (_, i) => prev[i])
1205
- );
1206
- },
1207
- onThumbs: (t) => setThumbs(t)
1208
- }
1209
- ) : null,
1210
- resolved.fileType === "png" || resolved.fileType === "jpg" || resolved.fileType === "svg" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1211
- ImageRenderer,
1212
- {
1213
- arrayBuffer: resolved.arrayBuffer,
1214
- fileType: resolved.fileType,
1215
- fileName: resolved.fileName
1216
- }
1217
- ) : null
1218
- ] }),
1219
- mode !== "create" && localSignatures.length ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1220
- SignaturePanel,
1221
- {
1222
- locale,
1223
- signatures: localSignatures,
1224
- collapsed: !showSignatures,
1225
- onToggle: () => setShowSignatures((v) => !v)
1226
- }
1227
- ) : null
1228
- ] }) : null
1229
- ] });
1230
- }
1231
- function arrayBufferToBase642(ab) {
1232
- const bytes = new Uint8Array(ab);
1233
- let binary = "";
1234
- const chunk = 32768;
1235
- for (let i = 0; i < bytes.length; i += chunk) {
1236
- binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
1237
- }
1238
- return btoa(binary);
1239
- }
1240
- // Annotate the CommonJS export names for ESM import in node:
1241
- 0 && (module.exports = {
1242
- DocumentViewer
1243
- });
1244
- //# sourceMappingURL=index.cjs.map