@zerohive/hive-viewer 0.2.7 → 1.0.1

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.mjs CHANGED
@@ -1,17 +1,10 @@
1
1
  // src/components/DocumentViewer.tsx
2
- import { useEffect as useEffect6, useMemo as useMemo6, useRef as useRef3, useState as useState6 } from "react";
2
+ import { useEffect as useEffect7, useState as useState7 } from "react";
3
3
 
4
4
  // src/editors/RichTextEditor.tsx
5
- import html2canvas from "html2canvas";
6
5
  import mammoth from "mammoth";
6
+ import { useEffect, useRef, useState } from "react";
7
7
  import MarkdownIt from "markdown-it";
8
- import {
9
- forwardRef,
10
- useEffect,
11
- useImperativeHandle,
12
- useMemo,
13
- useRef
14
- } from "react";
15
8
 
16
9
  // src/utils/sanitize.ts
17
10
  import DOMPurify from "dompurify";
@@ -23,129 +16,796 @@ function sanitizeHtml(html) {
23
16
  }
24
17
 
25
18
  // src/editors/RichTextEditor.tsx
26
- import { jsx, jsxs } from "react/jsx-runtime";
27
- var PAGE_H = 1122;
28
- var RichTextEditor = forwardRef(
29
- (props, ref) => {
30
- const readOnly = props.mode === "view";
31
- const md = useMemo(
32
- () => new MarkdownIt({ html: false, linkify: true, breaks: true }),
33
- []
34
- );
35
- const scrollerRef = useRef(null);
36
- const editorRef = useRef(null);
37
- const captureRef = useRef(null);
38
- const initialized = useRef(false);
39
- useEffect(() => {
40
- if (initialized.current) return;
41
- (async () => {
42
- if (props.mode === "create") {
43
- editorRef.current.innerHTML = "<p><br/></p>";
44
- initialized.current = true;
45
- return;
46
- }
47
- if (!props.arrayBuffer) return;
48
- if (props.fileType === "docx") {
49
- const res = await mammoth.convertToHtml({
19
+ import { jsx } from "react/jsx-runtime";
20
+ function RichTextEditor(props) {
21
+ const editorRef = useRef(null);
22
+ const [contentHtml, setContentHtml] = useState("");
23
+ const [loading, setLoading] = useState(false);
24
+ const mdParser = useRef(new MarkdownIt({ html: true, linkify: true }));
25
+ useEffect(() => {
26
+ const loadDoc = async () => {
27
+ if (!props.arrayBuffer) return;
28
+ setLoading(true);
29
+ try {
30
+ if (props.fileName.endsWith(".docx") || props.fileType === "docx") {
31
+ const result = await mammoth.convertToHtml({
50
32
  arrayBuffer: props.arrayBuffer
51
33
  });
52
- editorRef.current.innerHTML = sanitizeHtml(
53
- res.value || "<p><br/></p>"
54
- );
34
+ setContentHtml(sanitizeHtml(result.value));
55
35
  } else {
56
- const text = new TextDecoder().decode(props.arrayBuffer);
57
- editorRef.current.innerHTML = props.fileType === "md" ? sanitizeHtml(md.render(text)) : `<pre>${escapeHtml(text)}</pre>`;
36
+ const decoder = new TextDecoder("utf-8");
37
+ const text = decoder.decode(props.arrayBuffer);
38
+ if (props.fileName.endsWith(".md") || props.fileType === "md") {
39
+ const html = mdParser.current.render(text);
40
+ setContentHtml(sanitizeHtml(html));
41
+ } else {
42
+ setContentHtml(
43
+ `<pre style="white-space: pre-wrap; font-family: monospace;">${text}</pre>`
44
+ );
45
+ }
58
46
  }
59
- initialized.current = true;
60
- })();
61
- }, [props.arrayBuffer, props.fileType, props.mode, md]);
62
- useEffect(() => {
63
- const el = scrollerRef.current;
64
- if (!el) return;
65
- const recompute = () => props.onPageCount(Math.max(1, Math.ceil(el.scrollHeight / PAGE_H)));
66
- recompute();
67
- const ro = new ResizeObserver(recompute);
68
- ro.observe(el);
69
- return () => ro.disconnect();
70
- }, [props.headerFooterEnabled]);
71
- function exec(cmd) {
72
- if (readOnly) return;
73
- document.execCommand(cmd);
74
- editorRef.current?.focus();
75
- }
76
- function onClickPage(e) {
77
- if (!props.armedSignatureUrl) return;
78
- const scroller = scrollerRef.current;
79
- if (!scroller) return;
80
- const rect = e.currentTarget.getBoundingClientRect();
81
- const absY = scroller.scrollTop + (e.clientY - rect.top);
82
- const page = Math.floor(absY / PAGE_H) + 1;
83
- props.onPlaceSignature({
84
- page,
85
- x: (e.clientX - rect.left) / rect.width,
86
- y: absY % PAGE_H / PAGE_H,
87
- w: 0.25,
88
- h: 0.1
89
- });
90
- }
91
- async function requestThumbnail(index) {
92
- const scroller = scrollerRef.current;
93
- const capture = captureRef.current;
94
- if (!scroller || !capture) return;
95
- const old = scroller.scrollTop;
96
- scroller.scrollTop = index * PAGE_H;
97
- await new Promise((r) => requestAnimationFrame(r));
98
- try {
99
- const canvas = await html2canvas(capture, {
100
- scale: 0.25,
101
- useCORS: true
102
- });
103
- return canvas.toDataURL("image/png");
47
+ props.onPageCount(1);
48
+ } catch (err) {
49
+ console.error("Doc Conversion failed", err);
50
+ setContentHtml("<p style='color:red'>Error parsing document.</p>");
104
51
  } finally {
105
- scroller.scrollTop = old;
52
+ setLoading(false);
106
53
  }
54
+ };
55
+ loadDoc();
56
+ }, [props.arrayBuffer, props.fileName, props.fileType]);
57
+ return /* @__PURE__ */ jsx("div", { className: "hv-view-single", children: /* @__PURE__ */ jsx("div", { className: "hv-page-container", children: loading ? /* @__PURE__ */ jsx("div", { className: "p-12 text-center text-gray-400", children: "Processing text..." }) : /* @__PURE__ */ jsx(
58
+ "div",
59
+ {
60
+ ref: editorRef,
61
+ className: "hv-docx-content",
62
+ contentEditable: props.mode === "edit" || props.mode === "create",
63
+ dangerouslySetInnerHTML: { __html: contentHtml },
64
+ suppressContentEditableWarning: true
107
65
  }
108
- async function save(exportPdf) {
109
- const html = editorRef.current?.innerHTML ?? "";
110
- const stitched = `<!doctype html><html><body>${html}</body></html>`;
111
- const b64 = btoa(unescape(encodeURIComponent(stitched)));
112
- props.onSave(b64, {
113
- fileName: props.fileName,
114
- fileType: props.fileType,
115
- exportedAsPdf: exportPdf,
116
- annotations: { signaturePlacements: props.signaturePlacements }
66
+ ) }) });
67
+ }
68
+
69
+ // src/editors/SpreadsheetEditor.tsx
70
+ import {
71
+ forwardRef,
72
+ useEffect as useEffect2,
73
+ useImperativeHandle,
74
+ useState as useState2
75
+ } from "react";
76
+ import * as XLSX from "xlsx";
77
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
78
+ var SpreadsheetEditor = forwardRef(function SpreadsheetEditor2(props, ref) {
79
+ const readonly = props.mode === "view";
80
+ const [data, setData] = useState2([]);
81
+ const [cols, setCols] = useState2([]);
82
+ useEffect2(() => {
83
+ if (!props.arrayBuffer) return;
84
+ try {
85
+ const wb = XLSX.read(props.arrayBuffer, { type: "array" });
86
+ const wsName = wb.SheetNames[0];
87
+ const ws = wb.Sheets[wsName];
88
+ const jsonData = XLSX.utils.sheet_to_json(ws, {
89
+ header: 1,
90
+ defval: ""
91
+ });
92
+ const minRows = 40;
93
+ const minCols = 15;
94
+ const rows = Math.max(minRows, jsonData.length);
95
+ const colCount = Math.max(minCols, jsonData[0]?.length || 0);
96
+ const normalized = Array.from({ length: rows }, (_, r) => {
97
+ const row = jsonData[r] || [];
98
+ return Array.from(
99
+ { length: colCount },
100
+ (_2, c) => row[c] !== void 0 && row[c] !== null ? String(row[c]) : ""
101
+ );
102
+ });
103
+ setData(normalized);
104
+ const headers = Array.from({ length: colCount }, (_, i) => {
105
+ let letter = "";
106
+ let temp = i;
107
+ while (temp >= 0) {
108
+ letter = String.fromCharCode(temp % 26 + 65) + letter;
109
+ temp = Math.floor(temp / 26) - 1;
110
+ }
111
+ return letter;
117
112
  });
113
+ setCols(headers);
114
+ } catch (e) {
115
+ console.error("Spreadsheet load error:", e);
116
+ }
117
+ }, [props.arrayBuffer]);
118
+ async function save(exportPdf) {
119
+ if (!props.onSave) return;
120
+ const ws = XLSX.utils.aoa_to_sheet(data);
121
+ const wb = XLSX.utils.book_new();
122
+ XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
123
+ const out = XLSX.write(wb, { type: "array", bookType: "xlsx" });
124
+ const bytes = new Uint8Array(out);
125
+ let binary = "";
126
+ for (let i = 0; i < bytes.byteLength; i++) {
127
+ binary += String.fromCharCode(bytes[i]);
118
128
  }
119
- useImperativeHandle(ref, () => ({
120
- save,
121
- requestThumbnail
122
- }));
123
- return /* @__PURE__ */ jsxs("div", { className: "hv-root", children: [
124
- /* @__PURE__ */ jsxs("div", { className: "hv-toolbar", children: [
125
- /* @__PURE__ */ jsx("button", { onClick: () => exec("bold"), disabled: readOnly, children: "B" }),
126
- /* @__PURE__ */ jsx("button", { onClick: () => exec("italic"), disabled: readOnly, children: "I" }),
127
- /* @__PURE__ */ jsx("button", { onClick: () => exec("underline"), disabled: readOnly, children: "U" }),
128
- props.armedSignatureUrl && /* @__PURE__ */ jsx("span", { className: "hv-hint", children: "Click page to place signature" })
129
+ props.onSave(btoa(binary), {
130
+ fileName: props.fileName,
131
+ fileType: "xlsx",
132
+ exportedAsPdf: !!exportPdf
133
+ });
134
+ }
135
+ useImperativeHandle(ref, () => ({ save }));
136
+ return /* @__PURE__ */ jsx2("div", { className: "hv-view-single", children: /* @__PURE__ */ jsxs(
137
+ "div",
138
+ {
139
+ className: "hv-page-container",
140
+ style: {
141
+ width: "auto",
142
+ maxWidth: "95vw",
143
+ minWidth: "800px",
144
+ padding: 0,
145
+ overflow: "hidden",
146
+ display: "flex",
147
+ flexDirection: "column"
148
+ },
149
+ children: [
150
+ /* @__PURE__ */ jsxs(
151
+ "div",
152
+ {
153
+ style: {
154
+ background: "#f8f9fa",
155
+ borderBottom: "1px solid #e2e8f0",
156
+ padding: "8px 16px",
157
+ fontSize: "12px",
158
+ color: "#64748b",
159
+ display: "flex",
160
+ gap: "12px",
161
+ flexShrink: 0
162
+ },
163
+ children: [
164
+ /* @__PURE__ */ jsx2("span", { children: "fx" }),
165
+ /* @__PURE__ */ jsx2(
166
+ "div",
167
+ {
168
+ style: {
169
+ background: "white",
170
+ border: "1px solid #cbd5e1",
171
+ flex: 1,
172
+ height: "18px",
173
+ borderRadius: "2px"
174
+ }
175
+ }
176
+ )
177
+ ]
178
+ }
179
+ ),
180
+ /* @__PURE__ */ jsx2(
181
+ "div",
182
+ {
183
+ style: {
184
+ overflow: "auto",
185
+ // FIX: Dynamic height ensures bottom isn't cut off on small screens
186
+ maxHeight: "calc(100vh - 140px)",
187
+ minHeight: "400px",
188
+ position: "relative",
189
+ // FIX: Padding prevents border clipping (first row/col edges)
190
+ padding: "1px"
191
+ },
192
+ children: /* @__PURE__ */ jsxs(
193
+ "table",
194
+ {
195
+ style: {
196
+ borderCollapse: "collapse",
197
+ minWidth: "100%",
198
+ tableLayout: "fixed"
199
+ },
200
+ children: [
201
+ /* @__PURE__ */ jsx2("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
202
+ /* @__PURE__ */ jsx2(
203
+ "th",
204
+ {
205
+ style: {
206
+ width: "40px",
207
+ background: "#f1f5f9",
208
+ border: "1px solid #cbd5e1",
209
+ position: "sticky",
210
+ top: 0,
211
+ left: 0,
212
+ zIndex: 30
213
+ // Increased zIndex
214
+ }
215
+ }
216
+ ),
217
+ cols.map((col, i) => /* @__PURE__ */ jsx2(
218
+ "th",
219
+ {
220
+ style: {
221
+ minWidth: "100px",
222
+ background: "#f1f5f9",
223
+ border: "1px solid #cbd5e1",
224
+ color: "#475569",
225
+ fontSize: "11px",
226
+ fontWeight: 600,
227
+ padding: "4px",
228
+ position: "sticky",
229
+ top: 0,
230
+ zIndex: 20
231
+ // Increased zIndex
232
+ },
233
+ children: col
234
+ },
235
+ i
236
+ ))
237
+ ] }) }),
238
+ /* @__PURE__ */ jsx2("tbody", { children: data.map((row, r) => /* @__PURE__ */ jsxs("tr", { children: [
239
+ /* @__PURE__ */ jsx2(
240
+ "td",
241
+ {
242
+ style: {
243
+ background: "#f1f5f9",
244
+ border: "1px solid #cbd5e1",
245
+ textAlign: "center",
246
+ fontSize: "11px",
247
+ color: "#64748b",
248
+ position: "sticky",
249
+ left: 0,
250
+ zIndex: 20
251
+ // Increased zIndex to stay above cells
252
+ },
253
+ children: r + 1
254
+ }
255
+ ),
256
+ row.map((cell, c) => /* @__PURE__ */ jsx2(
257
+ "td",
258
+ {
259
+ contentEditable: !readonly,
260
+ suppressContentEditableWarning: true,
261
+ onBlur: (e) => {
262
+ const val = e.currentTarget.innerText;
263
+ setData((prev) => {
264
+ const next = [...prev];
265
+ next[r] = [...next[r]];
266
+ next[r][c] = val;
267
+ return next;
268
+ });
269
+ },
270
+ style: {
271
+ border: "1px solid #e2e8f0",
272
+ padding: "4px 8px",
273
+ fontSize: "13px",
274
+ color: "#1e293b",
275
+ minWidth: "100px",
276
+ whiteSpace: "nowrap",
277
+ overflow: "hidden",
278
+ outline: "none",
279
+ zIndex: 1
280
+ },
281
+ children: cell
282
+ },
283
+ c
284
+ ))
285
+ ] }, r)) })
286
+ ]
287
+ }
288
+ )
289
+ }
290
+ )
291
+ ]
292
+ }
293
+ ) });
294
+ });
295
+
296
+ // src/renderers/ImageRenderer.tsx
297
+ import { useEffect as useEffect3, useMemo as useMemo2, useState as useState3 } from "react";
298
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
299
+ function ImageRenderer({
300
+ arrayBuffer,
301
+ fileType,
302
+ fileName
303
+ }) {
304
+ const [zoom, setZoom] = useState3(1);
305
+ const url = useMemo2(() => {
306
+ if (!arrayBuffer) {
307
+ return void 0;
308
+ }
309
+ const mime = fileType === "svg" ? "image/svg+xml" : fileType === "png" ? "image/png" : "image/jpeg";
310
+ return URL.createObjectURL(new Blob([arrayBuffer], { type: mime }));
311
+ }, [arrayBuffer, fileType]);
312
+ useEffect3(() => {
313
+ return () => {
314
+ if (url) {
315
+ URL.revokeObjectURL(url);
316
+ }
317
+ };
318
+ }, [url]);
319
+ return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col h-full bg-gray-50", children: [
320
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between px-6 py-4 bg-white border-b border-gray-200", children: [
321
+ /* @__PURE__ */ jsx3("h2", { className: "text-sm font-medium text-gray-700 truncate max-w-md", children: fileName }),
322
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-3 bg-gray-100 rounded-lg p-1", children: [
323
+ /* @__PURE__ */ jsx3(
324
+ "button",
325
+ {
326
+ type: "button",
327
+ onClick: () => setZoom((z) => Math.max(0.25, z - 0.25)),
328
+ className: "w-9 h-9 flex items-center justify-center rounded-md bg-white hover:bg-gray-50 text-gray-700 transition-all shadow-sm hover:shadow",
329
+ "aria-label": "Zoom out",
330
+ children: /* @__PURE__ */ jsx3(
331
+ "svg",
332
+ {
333
+ className: "w-4 h-4",
334
+ fill: "none",
335
+ viewBox: "0 0 24 24",
336
+ stroke: "currentColor",
337
+ children: /* @__PURE__ */ jsx3(
338
+ "path",
339
+ {
340
+ strokeLinecap: "round",
341
+ strokeLinejoin: "round",
342
+ strokeWidth: 2,
343
+ d: "M20 12H4"
344
+ }
345
+ )
346
+ }
347
+ )
348
+ }
349
+ ),
350
+ /* @__PURE__ */ jsxs2("span", { className: "text-sm font-semibold text-gray-700 min-w-[3.5rem] text-center px-2", children: [
351
+ Math.round(zoom * 100),
352
+ "%"
353
+ ] }),
354
+ /* @__PURE__ */ jsx3(
355
+ "button",
356
+ {
357
+ type: "button",
358
+ onClick: () => setZoom((z) => Math.min(4, z + 0.25)),
359
+ className: "w-9 h-9 flex items-center justify-center rounded-md bg-white hover:bg-gray-50 text-gray-700 transition-all shadow-sm hover:shadow",
360
+ "aria-label": "Zoom in",
361
+ children: /* @__PURE__ */ jsx3(
362
+ "svg",
363
+ {
364
+ className: "w-4 h-4",
365
+ fill: "none",
366
+ viewBox: "0 0 24 24",
367
+ stroke: "currentColor",
368
+ children: /* @__PURE__ */ jsx3(
369
+ "path",
370
+ {
371
+ strokeLinecap: "round",
372
+ strokeLinejoin: "round",
373
+ strokeWidth: 2,
374
+ d: "M12 4v16m8-8H4"
375
+ }
376
+ )
377
+ }
378
+ )
379
+ }
380
+ )
381
+ ] })
382
+ ] }),
383
+ /* @__PURE__ */ jsxs2("div", { className: "flex-1 overflow-auto flex items-center justify-center p-8", children: [
384
+ !arrayBuffer && /* @__PURE__ */ jsxs2("div", { className: "text-center", children: [
385
+ /* @__PURE__ */ jsx3("div", { className: "w-16 h-16 mx-auto mb-3 rounded-full bg-gray-200 flex items-center justify-center", children: /* @__PURE__ */ jsx3(
386
+ "svg",
387
+ {
388
+ className: "w-8 h-8 text-gray-400",
389
+ fill: "none",
390
+ viewBox: "0 0 24 24",
391
+ stroke: "currentColor",
392
+ children: /* @__PURE__ */ jsx3(
393
+ "path",
394
+ {
395
+ strokeLinecap: "round",
396
+ strokeLinejoin: "round",
397
+ strokeWidth: 2,
398
+ d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
399
+ }
400
+ )
401
+ }
402
+ ) }),
403
+ /* @__PURE__ */ jsx3("p", { className: "text-sm text-gray-500", children: "No image data provided" })
129
404
  ] }),
130
- /* @__PURE__ */ jsx("div", { className: "hv-scroll", ref: scrollerRef, onClick: onClickPage, children: /* @__PURE__ */ jsx("div", { className: "hv-pageStage", ref: captureRef, children: /* @__PURE__ */ jsx(
405
+ arrayBuffer && !url && /* @__PURE__ */ jsxs2("div", { className: "text-center", children: [
406
+ /* @__PURE__ */ jsx3("div", { className: "w-16 h-16 mx-auto mb-3 rounded-full bg-red-100 flex items-center justify-center", children: /* @__PURE__ */ jsx3(
407
+ "svg",
408
+ {
409
+ className: "w-8 h-8 text-red-500",
410
+ fill: "none",
411
+ viewBox: "0 0 24 24",
412
+ stroke: "currentColor",
413
+ children: /* @__PURE__ */ jsx3(
414
+ "path",
415
+ {
416
+ strokeLinecap: "round",
417
+ strokeLinejoin: "round",
418
+ strokeWidth: 2,
419
+ d: "M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
420
+ }
421
+ )
422
+ }
423
+ ) }),
424
+ /* @__PURE__ */ jsx3("p", { className: "text-sm text-gray-600", children: "Failed to load image" })
425
+ ] }),
426
+ url && /* @__PURE__ */ jsx3(
131
427
  "div",
132
428
  {
133
- ref: editorRef,
134
- className: `hv-editor ${readOnly ? "ro" : ""}`,
135
- contentEditable: !readOnly,
136
- suppressContentEditableWarning: true
429
+ style: { transform: `scale(${zoom})` },
430
+ className: "transition-transform duration-200 origin-center",
431
+ children: /* @__PURE__ */ jsx3(
432
+ "img",
433
+ {
434
+ src: url,
435
+ alt: fileName,
436
+ style: { transform: `scale(${zoom})` },
437
+ className: "max-w-full h-auto rounded-lg shadow-lg transition-transform duration-200"
438
+ }
439
+ )
440
+ }
441
+ )
442
+ ] })
443
+ ] });
444
+ }
445
+
446
+ // src/renderers/PdfRenderer.tsx
447
+ import {
448
+ getDocument,
449
+ GlobalWorkerOptions
450
+ } from "pdfjs-dist";
451
+ import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef2, useState as useState4 } from "react";
452
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
453
+ var PDF_WORKER_URL = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.10.38/pdf.worker.min.mjs";
454
+ function PdfRenderer(props) {
455
+ const { url, arrayBuffer, layout, currentPage } = props;
456
+ const [doc, setDoc] = useState4(null);
457
+ const [error, setError] = useState4(null);
458
+ useEffect4(() => {
459
+ if (!GlobalWorkerOptions.workerSrc) {
460
+ GlobalWorkerOptions.workerSrc = PDF_WORKER_URL;
461
+ }
462
+ let active = true;
463
+ const loadPdf = async () => {
464
+ if (!url && !arrayBuffer) return;
465
+ setError(null);
466
+ try {
467
+ const dataSource = arrayBuffer ? { data: arrayBuffer.slice(0) } : { url };
468
+ const loadingTask = getDocument(dataSource);
469
+ const pdf = await loadingTask.promise;
470
+ if (active) {
471
+ setDoc(pdf);
472
+ props.onPageCount(pdf.numPages);
473
+ generateThumbnails(pdf);
474
+ }
475
+ } catch (err) {
476
+ console.error("PDF Load Error:", err);
477
+ if (active) setError(err.message || "Failed to load PDF");
478
+ }
479
+ };
480
+ loadPdf();
481
+ return () => {
482
+ active = false;
483
+ };
484
+ }, [url, arrayBuffer]);
485
+ const generateThumbnails = async (pdf) => {
486
+ try {
487
+ const thumbs = [];
488
+ const num = Math.min(pdf.numPages, 5);
489
+ for (let i = 1; i <= num; i++) {
490
+ const page = await pdf.getPage(i);
491
+ const viewport = page.getViewport({ scale: 0.2 });
492
+ const canvas = document.createElement("canvas");
493
+ canvas.width = viewport.width;
494
+ canvas.height = viewport.height;
495
+ const ctx = canvas.getContext("2d");
496
+ if (ctx) {
497
+ await page.render({ canvasContext: ctx, viewport }).promise;
498
+ thumbs.push(canvas.toDataURL());
137
499
  }
138
- ) }) })
139
- ] });
500
+ }
501
+ props.onThumbs(thumbs);
502
+ } catch (e) {
503
+ }
504
+ };
505
+ const pagesToRender = useMemo3(() => {
506
+ if (!doc) return [];
507
+ const p = Math.max(1, Math.min(currentPage, doc.numPages));
508
+ if (layout === "side-by-side" && doc.numPages > 1) {
509
+ if (p === 1) return [1];
510
+ const left = p % 2 === 0 ? p : p - 1;
511
+ return left + 1 <= doc.numPages ? [left, left + 1] : [left];
512
+ }
513
+ return [p];
514
+ }, [doc, currentPage, layout]);
515
+ if (error) {
516
+ return /* @__PURE__ */ jsxs3(
517
+ "div",
518
+ {
519
+ className: "hv-page-container",
520
+ style: { padding: "32px", textAlign: "center", color: "#dc2626" },
521
+ children: [
522
+ /* @__PURE__ */ jsx4("strong", { children: "Error loading PDF" }),
523
+ /* @__PURE__ */ jsx4("p", { className: "text-sm mt-2", children: error })
524
+ ]
525
+ }
526
+ );
140
527
  }
141
- );
142
- function escapeHtml(s) {
143
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;");
528
+ return /* @__PURE__ */ jsx4(
529
+ "div",
530
+ {
531
+ className: `hv-doc-scroll ${layout === "side-by-side" ? "hv-view-double" : "hv-view-single"}`,
532
+ children: pagesToRender.map((page) => /* @__PURE__ */ jsx4(PdfPage, { doc, pageNum: page }, page))
533
+ }
534
+ );
535
+ }
536
+ function PdfPage({
537
+ doc,
538
+ pageNum
539
+ }) {
540
+ const canvasRef = useRef2(null);
541
+ useEffect4(() => {
542
+ if (!doc || !canvasRef.current) return;
543
+ let active = true;
544
+ const render = async () => {
545
+ try {
546
+ const page = await doc.getPage(pageNum);
547
+ if (!active) return;
548
+ const scale = 1.5;
549
+ const viewport = page.getViewport({ scale });
550
+ const canvas = canvasRef.current;
551
+ const ctx = canvas.getContext("2d");
552
+ if (!ctx) return;
553
+ canvas.width = viewport.width;
554
+ canvas.height = viewport.height;
555
+ canvas.style.width = `${viewport.width / scale}px`;
556
+ canvas.style.height = `${viewport.height / scale}px`;
557
+ await page.render({ canvasContext: ctx, viewport }).promise;
558
+ } catch (e) {
559
+ console.error("Page render error:", e);
560
+ }
561
+ };
562
+ render();
563
+ return () => {
564
+ active = false;
565
+ };
566
+ }, [doc, pageNum]);
567
+ return /* @__PURE__ */ jsx4(
568
+ "div",
569
+ {
570
+ className: "hv-page-container",
571
+ style: {
572
+ minHeight: "600px",
573
+ display: "flex",
574
+ alignItems: "center",
575
+ justifyContent: "center"
576
+ },
577
+ children: /* @__PURE__ */ jsx4("canvas", { ref: canvasRef, className: "hv-pdf-canvas" })
578
+ }
579
+ );
144
580
  }
145
581
 
146
- // src/editors/SpreadsheetEditor.tsx
147
- import { forwardRef as forwardRef2, useEffect as useEffect2, useImperativeHandle as useImperativeHandle2, useMemo as useMemo2, useState as useState2 } from "react";
148
- import * as XLSX from "xlsx";
582
+ // src/renderers/PptxRenderer.tsx
583
+ import { useEffect as useEffect5, useMemo as useMemo4, useState as useState5 } from "react";
584
+ import JSZip from "jszip";
585
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
586
+ var NS_P = "http://schemas.openxmlformats.org/presentationml/2006/main";
587
+ var NS_A = "http://schemas.openxmlformats.org/drawingml/2006/main";
588
+ function parseColor(solidFill) {
589
+ if (!solidFill) return void 0;
590
+ const srgbClr = solidFill.getElementsByTagNameNS(NS_A, "srgbClr")[0];
591
+ if (srgbClr) {
592
+ const val = srgbClr.getAttribute("val");
593
+ return val ? `#${val}` : void 0;
594
+ }
595
+ const schemeClr = solidFill.getElementsByTagNameNS(NS_A, "schemeClr")[0];
596
+ if (schemeClr) {
597
+ const val = schemeClr.getAttribute("val");
598
+ const colorMap = {
599
+ tx1: "#000000",
600
+ bg1: "#FFFFFF",
601
+ tx2: "#1F1F1F",
602
+ accent1: "#4472C4",
603
+ accent2: "#ED7D31",
604
+ accent3: "#A5A5A5",
605
+ accent4: "#FFC000",
606
+ accent5: "#5B9BD5",
607
+ accent6: "#70AD47"
608
+ };
609
+ return colorMap[val || ""] || "#000000";
610
+ }
611
+ return void 0;
612
+ }
613
+ function parseSlideXml(xml) {
614
+ const parser = new DOMParser();
615
+ const doc = parser.parseFromString(xml, "application/xml");
616
+ let title;
617
+ let titleColor;
618
+ let bgColor;
619
+ const body = [];
620
+ const bgElements = doc.getElementsByTagNameNS(NS_P, "bg");
621
+ if (bgElements.length > 0) {
622
+ const bgPr = bgElements[0].getElementsByTagNameNS(NS_P, "bgPr")[0];
623
+ if (bgPr) {
624
+ const solidFill = bgPr.getElementsByTagNameNS(NS_A, "solidFill")[0];
625
+ bgColor = parseColor(solidFill);
626
+ }
627
+ }
628
+ const shapes = Array.from(doc.getElementsByTagNameNS(NS_P, "sp"));
629
+ shapes.forEach((shape) => {
630
+ const nvSpPr = shape.getElementsByTagNameNS(NS_P, "nvSpPr")[0];
631
+ const cNvPr = nvSpPr?.getElementsByTagNameNS(NS_P, "cNvPr")[0];
632
+ const name = cNvPr?.getAttribute("name")?.toLowerCase() || "";
633
+ const isTitle = name.includes("title") || name.includes("header");
634
+ const txBody = shape.getElementsByTagNameNS(NS_P, "txBody")[0];
635
+ if (!txBody) return;
636
+ const paragraphs = Array.from(txBody.getElementsByTagNameNS(NS_A, "p"));
637
+ paragraphs.forEach((p) => {
638
+ const runs = Array.from(p.getElementsByTagNameNS(NS_A, "r"));
639
+ runs.forEach((run) => {
640
+ const textEl = run.getElementsByTagNameNS(NS_A, "t")[0];
641
+ const text = textEl?.textContent?.trim() || "";
642
+ if (!text) return;
643
+ const rPr = run.getElementsByTagNameNS(NS_A, "rPr")[0];
644
+ let color;
645
+ let isBold = false;
646
+ let isItalic = false;
647
+ if (rPr) {
648
+ isBold = rPr.getAttribute("b") === "1";
649
+ isItalic = rPr.getAttribute("i") === "1";
650
+ color = parseColor(rPr.getElementsByTagNameNS(NS_A, "solidFill")[0]);
651
+ }
652
+ if (isTitle && !title) {
653
+ title = text;
654
+ titleColor = color;
655
+ } else {
656
+ body.push({ text, color, isBold, isItalic });
657
+ }
658
+ });
659
+ });
660
+ });
661
+ return { title, titleColor, body, bgColor };
662
+ }
663
+ function PptxRenderer(props) {
664
+ const [slides, setSlides] = useState5([]);
665
+ const [error, setError] = useState5(null);
666
+ useEffect5(() => {
667
+ if (!props.arrayBuffer) return;
668
+ const loadPptx = async () => {
669
+ try {
670
+ const zip = await JSZip.loadAsync(props.arrayBuffer);
671
+ const slidePaths = Object.keys(zip.files).filter((p) => /^ppt\/slides\/slide\d+\.xml$/.test(p)).sort((a, b) => {
672
+ const numA = parseInt(a.match(/\d+/)[0]);
673
+ const numB = parseInt(b.match(/\d+/)[0]);
674
+ return numA - numB;
675
+ });
676
+ const parsedSlides = [];
677
+ for (const path of slidePaths) {
678
+ const xml = await zip.files[path].async("string");
679
+ parsedSlides.push(parseSlideXml(xml));
680
+ }
681
+ setSlides(parsedSlides);
682
+ props.onPageCount(parsedSlides.length);
683
+ const thumbs = parsedSlides.map(
684
+ (_, i) => `data:image/svg+xml;charset=utf-8,${encodeURIComponent(
685
+ `<svg xmlns="http://www.w3.org/2000/svg" width="100" height="56"><rect width="100%" height="100%" fill="#4f46e5"/><text x="50%" y="50%" fill="white" font-size="20" text-anchor="middle" dy=".3em">${i + 1}</text></svg>`
686
+ )}`
687
+ );
688
+ props.onThumbs(thumbs);
689
+ } catch (err) {
690
+ setError(err.message || "Failed to parse PPTX");
691
+ }
692
+ };
693
+ loadPptx();
694
+ }, [props.arrayBuffer]);
695
+ const pagesToShow = useMemo4(() => {
696
+ if (slides.length === 0) return [];
697
+ const validPage = Math.max(1, Math.min(props.currentPage, slides.length));
698
+ if (props.layout === "side-by-side" && slides.length > 1) {
699
+ if (validPage === 1) return [1];
700
+ const left = validPage % 2 === 0 ? validPage : validPage - 1;
701
+ return [left, left + 1].filter((p) => p <= slides.length);
702
+ }
703
+ return [validPage];
704
+ }, [slides.length, props.currentPage, props.layout]);
705
+ if (error) return /* @__PURE__ */ jsx5("div", { className: "text-red-500 p-8 text-center", children: error });
706
+ return /* @__PURE__ */ jsx5(
707
+ "div",
708
+ {
709
+ className: props.layout === "side-by-side" ? "hv-view-double" : "hv-view-single",
710
+ children: pagesToShow.map((p) => {
711
+ const slide = slides[p - 1];
712
+ if (!slide) return null;
713
+ return /* @__PURE__ */ jsxs4(
714
+ "div",
715
+ {
716
+ className: "hv-page-container",
717
+ style: {
718
+ width: "960px",
719
+ // Standard HD slide width
720
+ aspectRatio: "16/9",
721
+ padding: "48px",
722
+ backgroundColor: slide.bgColor || "#ffffff",
723
+ display: "flex",
724
+ flexDirection: "column",
725
+ justifyContent: "center",
726
+ position: "relative"
727
+ // For absolute positioning if we added it later
728
+ },
729
+ children: [
730
+ slide.title && /* @__PURE__ */ jsx5(
731
+ "h1",
732
+ {
733
+ style: {
734
+ fontSize: "42px",
735
+ marginBottom: "32px",
736
+ fontWeight: "bold",
737
+ color: slide.titleColor || "#1a202c",
738
+ lineHeight: 1.2
739
+ },
740
+ children: slide.title
741
+ }
742
+ ),
743
+ /* @__PURE__ */ jsx5(
744
+ "div",
745
+ {
746
+ style: { display: "flex", flexDirection: "column", gap: "16px" },
747
+ children: slide.body.map((item, i) => /* @__PURE__ */ jsxs4(
748
+ "div",
749
+ {
750
+ style: {
751
+ display: "flex",
752
+ alignItems: "flex-start",
753
+ gap: "12px"
754
+ },
755
+ children: [
756
+ /* @__PURE__ */ jsx5(
757
+ "div",
758
+ {
759
+ style: {
760
+ width: "8px",
761
+ height: "8px",
762
+ background: item.color || "#cbd5e1",
763
+ borderRadius: "50%",
764
+ marginTop: "12px",
765
+ flexShrink: 0
766
+ }
767
+ }
768
+ ),
769
+ /* @__PURE__ */ jsx5(
770
+ "p",
771
+ {
772
+ style: {
773
+ fontSize: "24px",
774
+ color: item.color || "#4a5568",
775
+ fontWeight: item.isBold ? "bold" : "normal",
776
+ fontStyle: item.isItalic ? "italic" : "normal",
777
+ margin: 0
778
+ },
779
+ children: item.text
780
+ }
781
+ )
782
+ ]
783
+ },
784
+ i
785
+ ))
786
+ }
787
+ ),
788
+ /* @__PURE__ */ jsx5(
789
+ "div",
790
+ {
791
+ style: {
792
+ position: "absolute",
793
+ bottom: "20px",
794
+ right: "30px",
795
+ color: "#cbd5e1",
796
+ fontWeight: "bold"
797
+ },
798
+ children: p
799
+ }
800
+ )
801
+ ]
802
+ },
803
+ p
804
+ );
805
+ })
806
+ }
807
+ );
808
+ }
149
809
 
150
810
  // src/utils/fileSource.ts
151
811
  function guessFileType(name, explicit) {
@@ -166,15 +826,6 @@ function guessFileType(name, explicit) {
166
826
  ];
167
827
  return allowed.includes(ext) ? ext : "txt";
168
828
  }
169
- function arrayBufferToBase64(buf) {
170
- const bytes = new Uint8Array(buf);
171
- let binary = "";
172
- const chunk = 32768;
173
- for (let i = 0; i < bytes.length; i += chunk) {
174
- binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
175
- }
176
- return btoa(binary);
177
- }
178
829
  async function base64ToArrayBuffer(b64) {
179
830
  const bin = atob(b64);
180
831
  const len = bin.length;
@@ -232,965 +883,672 @@ async function resolveSource(args) {
232
883
  return { fileType, fileName, arrayBuffer: out.buffer, url: args.fileUrl };
233
884
  }
234
885
 
235
- // src/editors/SpreadsheetEditor.tsx
236
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
237
- var SpreadsheetEditor = forwardRef2(function SpreadsheetEditor2(props, ref) {
238
- const readonly = props.mode === "view";
239
- const [grid, setGrid] = useState2(() => Array.from({ length: 30 }, () => Array.from({ length: 12 }, () => "")));
240
- useEffect2(() => {
241
- if (!props.arrayBuffer) {
242
- return;
243
- }
244
- try {
245
- const wb = XLSX.read(props.arrayBuffer, { type: "array" });
246
- const name = wb.SheetNames[0];
247
- const ws = wb.Sheets[name];
248
- const aoa = XLSX.utils.sheet_to_json(ws, { header: 1, raw: true });
249
- const rows = Math.max(30, aoa.length);
250
- const cols2 = Math.max(12, Math.max(...aoa.map((r) => r?.length ?? 0), 0));
251
- const next = Array.from({ length: rows }, (_, r) => Array.from({ length: cols2 }, (_2, c) => {
252
- const v = aoa[r]?.[c];
253
- return v == null ? "" : String(v);
254
- }));
255
- setGrid(next);
256
- } catch {
886
+ // src/components/ThumbnailsSidebar.tsx
887
+ import { useEffect as useEffect6, useRef as useRef3 } from "react";
888
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
889
+ function ThumbnailsSidebar(props) {
890
+ const { isOpen, thumbnails, currentPage, onSelectPage } = props;
891
+ const activeRef = useRef3(null);
892
+ useEffect6(() => {
893
+ if (activeRef.current) {
894
+ activeRef.current.scrollIntoView({
895
+ behavior: "smooth",
896
+ block: "nearest"
897
+ });
257
898
  }
258
- }, [props.arrayBuffer]);
259
- async function save(exportPdf) {
260
- const ws = XLSX.utils.aoa_to_sheet(grid);
261
- const wb = XLSX.utils.book_new();
262
- XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
263
- const out = XLSX.write(wb, { type: "array", bookType: "xlsx" });
264
- const b64 = arrayBufferToBase64(out);
265
- props.onSave(b64, { fileName: ensureExt(props.fileName, "xlsx"), fileType: "xlsx", exportedAsPdf: !!exportPdf });
266
- }
267
- useImperativeHandle2(ref, () => ({
268
- save,
269
- requestThumbnails: async () => void 0
270
- }));
271
- const cols = useMemo2(() => Array.from({ length: grid[0]?.length ?? 0 }, (_, i) => String.fromCharCode(65 + i % 26)), [grid]);
272
- return /* @__PURE__ */ jsxs2("div", { className: "hv-sheet", children: [
273
- /* @__PURE__ */ jsxs2("div", { className: "hv-sheetbar", children: [
274
- /* @__PURE__ */ jsx2("div", { className: "hv-sheetbar-title", children: props.fileName }),
275
- !readonly ? /* @__PURE__ */ jsx2("button", { className: "hv-btn", type: "button", onClick: () => void save(false), children: props.locale["toolbar.save"] ?? "Save" }) : null
276
- ] }),
277
- /* @__PURE__ */ jsxs2("div", { className: "hv-sheetgrid", role: "table", "aria-label": "Spreadsheet", children: [
278
- /* @__PURE__ */ jsxs2("div", { className: "hv-sheetrow hv-sheetrow--header", role: "row", children: [
279
- /* @__PURE__ */ jsx2("div", { className: "hv-sheetcell hv-sheetcell--corner", role: "columnheader" }),
280
- cols.map((c, i) => /* @__PURE__ */ jsx2("div", { className: "hv-sheetcell hv-sheetcell--header", role: "columnheader", children: c }, i))
281
- ] }),
282
- grid.map((row, r) => /* @__PURE__ */ jsxs2("div", { className: "hv-sheetrow", role: "row", children: [
283
- /* @__PURE__ */ jsx2("div", { className: "hv-sheetcell hv-sheetcell--header", role: "rowheader", children: r + 1 }),
284
- row.map((val, c) => /* @__PURE__ */ jsx2(
285
- "div",
286
- {
287
- className: "hv-sheetcell",
288
- role: "cell",
289
- contentEditable: !readonly,
290
- suppressContentEditableWarning: true,
291
- onInput: (e) => {
292
- const text = e.currentTarget.textContent ?? "";
293
- setGrid((prev) => {
294
- const next = prev.map((rr) => rr.slice());
295
- next[r][c] = text;
296
- return next;
297
- });
298
- },
299
- children: val
300
- },
301
- c
302
- ))
303
- ] }, r))
304
- ] })
305
- ] });
306
- });
307
- function ensureExt(name, ext) {
308
- const base = name.includes(".") ? name.slice(0, name.lastIndexOf(".")) : name;
309
- return `${base}.${ext}`;
899
+ }, [currentPage, isOpen]);
900
+ return /* @__PURE__ */ jsx6("div", { className: `hv-sidebar ${!isOpen ? "collapsed" : ""}`, children: /* @__PURE__ */ jsxs5("div", { className: "hv-thumb-list", children: [
901
+ thumbnails.map((src, index) => {
902
+ const pageNum = index + 1;
903
+ const isActive = pageNum === currentPage;
904
+ return /* @__PURE__ */ jsxs5(
905
+ "div",
906
+ {
907
+ ref: isActive ? activeRef : null,
908
+ className: `hv-thumb-item ${isActive ? "active" : ""}`,
909
+ onClick: () => onSelectPage(pageNum),
910
+ children: [
911
+ /* @__PURE__ */ jsx6("div", { className: "hv-thumb-preview", children: src ? /* @__PURE__ */ jsx6(
912
+ "img",
913
+ {
914
+ src,
915
+ alt: `Page ${pageNum}`,
916
+ className: "hv-thumb-img"
917
+ }
918
+ ) : (
919
+ // Skeleton loader state for thumbnail
920
+ /* @__PURE__ */ jsx6("div", { className: "w-full h-full bg-gray-100 animate-pulse flex items-center justify-center", children: /* @__PURE__ */ jsx6("span", { className: "text-xs text-gray-300", children: "..." }) })
921
+ ) }),
922
+ /* @__PURE__ */ jsxs5("span", { className: "hv-thumb-label", children: [
923
+ "Page ",
924
+ pageNum
925
+ ] })
926
+ ]
927
+ },
928
+ pageNum
929
+ );
930
+ }),
931
+ thumbnails.length === 0 && /* @__PURE__ */ jsx6("div", { className: "text-center p-4 text-xs text-gray-400", children: "No previews available" })
932
+ ] }) });
310
933
  }
311
934
 
312
- // src/renderers/ImageRenderer.tsx
313
- import { useEffect as useEffect3, useMemo as useMemo3, useState as useState3 } from "react";
314
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
315
- function ImageRenderer({
316
- arrayBuffer,
317
- fileType,
318
- fileName
319
- }) {
320
- const [zoom, setZoom] = useState3(1);
321
- const url = useMemo3(() => {
322
- if (!arrayBuffer) {
323
- return void 0;
935
+ // src/components/Toolbar.tsx
936
+ import {
937
+ ChevronLeft,
938
+ ChevronRight,
939
+ Grid2X2,
940
+ LayoutTemplate,
941
+ PanelLeftClose,
942
+ PanelLeftOpen,
943
+ PenLine
944
+ } from "lucide-react";
945
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
946
+ function Toolbar(props) {
947
+ const {
948
+ fileName,
949
+ pageCount,
950
+ currentPage,
951
+ onPageChange,
952
+ layout,
953
+ onLayoutChange
954
+ } = props;
955
+ const handlePrev = () => {
956
+ if (currentPage > 1) onPageChange(currentPage - 1);
957
+ };
958
+ const handleNext = () => {
959
+ if (currentPage < pageCount) onPageChange(currentPage + 1);
960
+ };
961
+ const handleInput = (e) => {
962
+ const val = parseInt(e.target.value);
963
+ if (!isNaN(val) && val >= 1 && val <= pageCount) {
964
+ onPageChange(val);
324
965
  }
325
- const mime = fileType === "svg" ? "image/svg+xml" : fileType === "png" ? "image/png" : "image/jpeg";
326
- return URL.createObjectURL(new Blob([arrayBuffer], { type: mime }));
327
- }, [arrayBuffer, fileType]);
328
- useEffect3(() => {
329
- return () => {
330
- if (url) {
331
- URL.revokeObjectURL(url);
332
- }
333
- };
334
- }, [url]);
335
- return /* @__PURE__ */ jsxs3("div", { className: "hv-doc", children: [
336
- /* @__PURE__ */ jsxs3("div", { className: "hv-mini-toolbar", children: [
337
- /* @__PURE__ */ jsx3("div", { className: "hv-title", children: fileName }),
338
- /* @__PURE__ */ jsx3("div", { className: "hv-spacer" }),
339
- /* @__PURE__ */ jsx3(
966
+ };
967
+ return /* @__PURE__ */ jsxs6("div", { className: "hv-toolbar", children: [
968
+ /* @__PURE__ */ jsxs6("div", { className: "hv-toolbar-group", children: [
969
+ /* @__PURE__ */ jsx7(
970
+ "button",
971
+ {
972
+ className: `hv-btn ${props.showThumbnails ? "hv-btn-active" : ""}`,
973
+ onClick: props.onToggleThumbnails,
974
+ title: "Toggle Thumbnails",
975
+ children: props.showThumbnails ? /* @__PURE__ */ jsx7(PanelLeftClose, { size: 20 }) : /* @__PURE__ */ jsx7(PanelLeftOpen, { size: 20 })
976
+ }
977
+ ),
978
+ /* @__PURE__ */ jsx7(
979
+ "div",
980
+ {
981
+ className: "hv-sep",
982
+ style: {
983
+ width: 1,
984
+ height: 24,
985
+ background: "var(--hv-border)",
986
+ margin: "0 8px"
987
+ }
988
+ }
989
+ ),
990
+ /* @__PURE__ */ jsx7(
991
+ "span",
992
+ {
993
+ style: {
994
+ fontWeight: 600,
995
+ fontSize: "14px",
996
+ maxWidth: 200,
997
+ overflow: "hidden",
998
+ textOverflow: "ellipsis",
999
+ whiteSpace: "nowrap"
1000
+ },
1001
+ children: fileName || "Document"
1002
+ }
1003
+ )
1004
+ ] }),
1005
+ /* @__PURE__ */ jsxs6("div", { className: "hv-toolbar-group", children: [
1006
+ /* @__PURE__ */ jsx7(
340
1007
  "button",
341
1008
  {
342
- type: "button",
343
1009
  className: "hv-btn",
344
- onClick: () => setZoom((z) => Math.max(0.25, z - 0.25)),
345
- children: "-"
1010
+ disabled: currentPage <= 1,
1011
+ onClick: handlePrev,
1012
+ children: /* @__PURE__ */ jsx7(ChevronLeft, { size: 20 })
346
1013
  }
347
1014
  ),
348
- /* @__PURE__ */ jsxs3("div", { className: "hv-zoom", children: [
349
- Math.round(zoom * 100),
350
- "%"
1015
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 text-sm font-medium text-gray-600", children: [
1016
+ /* @__PURE__ */ jsx7(
1017
+ "input",
1018
+ {
1019
+ type: "number",
1020
+ className: "w-12 text-center border rounded py-1 bg-gray-50 focus:bg-white focus:ring-2 focus:ring-indigo-500 outline-none transition-all",
1021
+ value: currentPage,
1022
+ onChange: handleInput,
1023
+ min: 1,
1024
+ max: pageCount
1025
+ }
1026
+ ),
1027
+ /* @__PURE__ */ jsx7("span", { className: "text-gray-400", children: "/" }),
1028
+ /* @__PURE__ */ jsx7("span", { children: pageCount })
351
1029
  ] }),
352
- /* @__PURE__ */ jsx3(
1030
+ /* @__PURE__ */ jsx7(
353
1031
  "button",
354
1032
  {
355
- type: "button",
356
1033
  className: "hv-btn",
357
- onClick: () => setZoom((z) => Math.min(4, z + 0.25)),
358
- children: "+"
1034
+ disabled: currentPage >= pageCount,
1035
+ onClick: handleNext,
1036
+ children: /* @__PURE__ */ jsx7(ChevronRight, { size: 20 })
359
1037
  }
360
1038
  )
361
1039
  ] }),
362
- /* @__PURE__ */ jsxs3("div", { className: "hv-center", children: [
363
- !arrayBuffer && /* @__PURE__ */ jsx3("div", { className: "hv-error", children: "No image data provided." }),
364
- arrayBuffer && !url && /* @__PURE__ */ jsx3("div", { className: "hv-error", children: "Failed to load image." }),
365
- url && /* @__PURE__ */ jsx3(
366
- "img",
1040
+ /* @__PURE__ */ jsxs6("div", { className: "hv-toolbar-group", children: [
1041
+ /* @__PURE__ */ jsx7(
1042
+ "button",
367
1043
  {
368
- src: url,
369
- alt: fileName,
370
- style: { transform: `scale(${zoom})` },
371
- className: "hv-image"
1044
+ className: `hv-btn ${layout === "single" ? "hv-btn-active text-indigo-600 bg-indigo-50" : ""}`,
1045
+ onClick: () => onLayoutChange("single"),
1046
+ title: "Single Page View",
1047
+ children: /* @__PURE__ */ jsx7(LayoutTemplate, { size: 18 })
1048
+ }
1049
+ ),
1050
+ /* @__PURE__ */ jsx7(
1051
+ "button",
1052
+ {
1053
+ className: `hv-btn ${layout === "side-by-side" ? "hv-btn-active text-indigo-600 bg-indigo-50" : ""}`,
1054
+ onClick: () => onLayoutChange("side-by-side"),
1055
+ title: "Two Page View",
1056
+ children: /* @__PURE__ */ jsx7(Grid2X2, { size: 18 })
1057
+ }
1058
+ ),
1059
+ /* @__PURE__ */ jsx7(
1060
+ "div",
1061
+ {
1062
+ className: "hv-sep",
1063
+ style: {
1064
+ width: 1,
1065
+ height: 24,
1066
+ background: "var(--hv-border)",
1067
+ margin: "0 8px"
1068
+ }
1069
+ }
1070
+ ),
1071
+ !props.disableSigning && /* @__PURE__ */ jsxs6(
1072
+ "button",
1073
+ {
1074
+ className: `hv-btn hv-btn-primary ${props.showSignatures ? "ring-2 ring-indigo-300" : ""}`,
1075
+ onClick: props.onToggleSignatures,
1076
+ title: "Sign Document",
1077
+ children: [
1078
+ /* @__PURE__ */ jsx7(PenLine, { size: 18, className: "mr-2" }),
1079
+ /* @__PURE__ */ jsx7("span", { className: "hidden sm:inline", children: "Sign" })
1080
+ ]
372
1081
  }
373
1082
  )
374
1083
  ] })
375
1084
  ] });
376
1085
  }
377
1086
 
378
- // src/renderers/PdfRenderer.tsx
379
- import {
380
- getDocument,
381
- GlobalWorkerOptions
382
- } from "pdfjs-dist";
383
- import { useEffect as useEffect4, useMemo as useMemo4, useRef as useRef2, useState as useState4 } from "react";
384
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
385
- function PdfRenderer(props) {
386
- const { url, arrayBuffer } = props;
387
- const [doc, setDoc] = useState4(null);
388
- const [pageCount, setPageCount] = useState4(0);
389
- const [rendered, setRendered] = useState4(
390
- /* @__PURE__ */ new Map()
391
- );
392
- const [thumbs, setThumbs] = useState4([]);
393
- const [size, setSize] = useState4({ w: 840, h: 1188 });
394
- const [error, setError] = useState4(null);
395
- const [loading, setLoading] = useState4(false);
396
- const containerRef = useRef2(null);
397
- useEffect4(() => {
398
- try {
399
- GlobalWorkerOptions.workerSrc = new URL(
400
- "pdfjs-dist/build/pdf.worker.min.mjs",
401
- import.meta.url
402
- ).toString();
403
- } catch {
404
- }
405
- }, []);
406
- useEffect4(() => {
407
- let cancel = false;
408
- setError(null);
409
- setLoading(true);
410
- (async () => {
411
- setDoc(null);
412
- setRendered(/* @__PURE__ */ new Map());
413
- setThumbs([]);
414
- if (!url && !arrayBuffer) {
415
- setError("No PDF source provided.");
416
- setLoading(false);
417
- return;
418
- }
1087
+ // src/components/SignaturePanel.tsx
1088
+ import { Plus, Trash2, X } from "lucide-react";
1089
+ import { useState as useState6, useRef as useRef4 } from "react";
1090
+ import { Fragment, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1091
+ function SignaturePanel(props) {
1092
+ const {
1093
+ isOpen,
1094
+ onClose,
1095
+ onSelectSignature,
1096
+ externalSignatures = [],
1097
+ onSignRequest
1098
+ } = props;
1099
+ const [localSignatures, setLocalSignatures] = useState6([]);
1100
+ const [showModal, setShowModal] = useState6(false);
1101
+ const signatures = [...externalSignatures, ...localSignatures];
1102
+ const canvasRef = useRef4(null);
1103
+ const [isDrawing, setIsDrawing] = useState6(false);
1104
+ const handleCreateClick = async () => {
1105
+ if (onSignRequest) {
419
1106
  try {
420
- const task = getDocument(
421
- url ? { url, rangeChunkSize: 512 * 1024 } : { data: arrayBuffer }
422
- );
423
- const pdf = await task.promise;
424
- if (cancel) {
425
- return;
1107
+ const newSig = await onSignRequest();
1108
+ if (newSig) {
1109
+ setLocalSignatures((prev) => [...prev, newSig]);
1110
+ onSelectSignature(newSig);
426
1111
  }
427
- setDoc(pdf);
428
- setPageCount(pdf.numPages);
429
- props.onPageCount(pdf.numPages);
430
- const p1 = await pdf.getPage(1);
431
- const base = p1.getViewport({ scale: 1 });
432
- const w = Math.min(980, Math.max(640, base.width));
433
- const s = w / base.width;
434
- const vp = p1.getViewport({ scale: s });
435
- setSize({ w: Math.round(vp.width), h: Math.round(vp.height) });
436
- const thumbWidth = 56;
437
- const thumbsArr = [];
438
- for (let i = 1; i <= pdf.numPages; i++) {
439
- const page = await pdf.getPage(i);
440
- const pageBase = page.getViewport({ scale: 1 });
441
- const thumbScale = thumbWidth / pageBase.width;
442
- const thumbVp = page.getViewport({ scale: thumbScale });
443
- const thumbCanvas = document.createElement("canvas");
444
- thumbCanvas.width = Math.round(thumbVp.width);
445
- thumbCanvas.height = Math.round(thumbVp.height);
446
- const thumbCtx = thumbCanvas.getContext("2d", { alpha: false });
447
- if (thumbCtx) {
448
- await page.render({ canvasContext: thumbCtx, viewport: thumbVp }).promise;
449
- thumbsArr.push(thumbCanvas.toDataURL("image/png"));
450
- } else {
451
- thumbsArr.push(void 0);
452
- }
453
- }
454
- setThumbs(thumbsArr);
455
- } catch (e) {
456
- setError(
457
- "Failed to load PDF. " + (e instanceof Error ? e.message : "")
458
- );
459
- } finally {
460
- setLoading(false);
1112
+ } catch (err) {
1113
+ console.error("Custom sign request failed", err);
461
1114
  }
462
- })();
463
- return () => {
464
- cancel = true;
465
- };
466
- }, [url, arrayBuffer]);
467
- useEffect4(() => {
468
- props.onThumbs(thumbs);
469
- }, [thumbs]);
470
- const pagesToShow = useMemo4(() => {
471
- if (props.layout === "side-by-side" && pageCount > 1) {
472
- const left = Math.max(1, Math.min(props.currentPage, pageCount));
473
- const right = Math.max(1, Math.min(left + 1, pageCount));
474
- return left === right ? [left] : [left, right];
1115
+ } else {
1116
+ openCreateModal();
475
1117
  }
476
- return [Math.max(1, Math.min(props.currentPage, pageCount))];
477
- }, [props.currentPage, props.layout, pageCount]);
478
- useEffect4(() => {
479
- if (!doc) {
480
- return;
481
- }
482
- let cancel = false;
483
- (async () => {
484
- for (const p of pagesToShow) {
485
- if (rendered.has(p)) {
486
- continue;
487
- }
488
- try {
489
- const page = await doc.getPage(p);
490
- if (cancel) {
491
- return;
492
- }
493
- const base = page.getViewport({ scale: 1 });
494
- const vp = page.getViewport({ scale: size.w / base.width });
495
- const canvas = document.createElement("canvas");
496
- canvas.width = Math.round(vp.width);
497
- canvas.height = Math.round(vp.height);
498
- const ctx = canvas.getContext("2d", { alpha: false });
499
- if (!ctx) {
500
- continue;
501
- }
502
- await page.render({ canvasContext: ctx, viewport: vp }).promise;
503
- if (cancel) {
504
- return;
505
- }
506
- setRendered((prev) => {
507
- const next = new Map(prev);
508
- next.set(p, canvas);
509
- return next;
510
- });
511
- } catch {
512
- }
1118
+ };
1119
+ const openCreateModal = () => {
1120
+ setShowModal(true);
1121
+ setTimeout(() => {
1122
+ const canvas = canvasRef.current;
1123
+ if (canvas) {
1124
+ const ctx = canvas.getContext("2d");
1125
+ ctx?.clearRect(0, 0, canvas.width, canvas.height);
513
1126
  }
514
- })();
515
- return () => {
516
- cancel = true;
1127
+ }, 100);
1128
+ };
1129
+ const saveSignature = () => {
1130
+ const canvas = canvasRef.current;
1131
+ if (!canvas) return;
1132
+ const newSig = {
1133
+ id: Date.now().toString(),
1134
+ signatureImageUrl: canvas.toDataURL("image/png"),
1135
+ signedBy: "Me",
1136
+ dateSigned: (/* @__PURE__ */ new Date()).toISOString()
517
1137
  };
518
- }, [doc, pagesToShow, size.w, rendered]);
519
- function onWheel(e) {
520
- if (!pageCount) {
521
- return;
522
- }
523
- if (Math.abs(e.deltaY) < 10) {
524
- return;
525
- }
526
- const dir = e.deltaY > 0 ? 1 : -1;
527
- const step = props.layout === "side-by-side" ? 2 : 1;
528
- const next = Math.max(
529
- 1,
530
- Math.min(pageCount, props.currentPage + dir * step)
531
- );
532
- props.onCurrentPageChange(next);
533
- }
534
- function clickPlace(e, page) {
535
- const stamp = props.signatureStamp;
536
- if (!stamp?.armed) {
537
- return;
538
- }
539
- const rect = e.currentTarget.getBoundingClientRect();
540
- const x = (e.clientX - rect.left) / rect.width;
541
- const y = (e.clientY - rect.top) / rect.height;
542
- stamp.onPlaced({ page, x, y, w: 0.22, h: 0.08 });
543
- }
544
- return /* @__PURE__ */ jsxs4("div", { className: "hv-doc", ref: containerRef, onWheel, children: [
545
- !doc ? /* @__PURE__ */ jsx4("div", { className: "hv-loading", children: "Loading PDF\u2026" }) : null,
546
- doc ? /* @__PURE__ */ jsx4(
1138
+ setLocalSignatures([...localSignatures, newSig]);
1139
+ setShowModal(false);
1140
+ onSelectSignature(newSig);
1141
+ };
1142
+ const startDraw = (e) => {
1143
+ setIsDrawing(true);
1144
+ draw(e);
1145
+ };
1146
+ const stopDraw = () => setIsDrawing(false);
1147
+ const draw = (e) => {
1148
+ if (!isDrawing || !canvasRef.current) return;
1149
+ const canvas = canvasRef.current;
1150
+ const ctx = canvas.getContext("2d");
1151
+ if (!ctx) return;
1152
+ const rect = canvas.getBoundingClientRect();
1153
+ const clientX = "touches" in e ? e.touches[0].clientX : e.clientX;
1154
+ const clientY = "touches" in e ? e.touches[0].clientY : e.clientY;
1155
+ const x = clientX - rect.left;
1156
+ const y = clientY - rect.top;
1157
+ ctx.lineWidth = 2;
1158
+ ctx.lineCap = "round";
1159
+ ctx.lineTo(x, y);
1160
+ ctx.stroke();
1161
+ ctx.beginPath();
1162
+ ctx.moveTo(x, y);
1163
+ };
1164
+ return /* @__PURE__ */ jsxs7(Fragment, { children: [
1165
+ /* @__PURE__ */ jsxs7(
547
1166
  "div",
548
1167
  {
549
- className: props.layout === "side-by-side" ? "hv-pages hv-pages--two" : "hv-pages",
550
- children: pagesToShow.map((p) => {
551
- const c = rendered.get(p);
552
- return /* @__PURE__ */ jsx4(
1168
+ className: `hv-sidebar hv-sidebar-right ${!isOpen ? "collapsed" : ""}`,
1169
+ style: { width: isOpen ? "280px" : "0" },
1170
+ children: [
1171
+ /* @__PURE__ */ jsxs7(
553
1172
  "div",
554
1173
  {
555
- className: "hv-page",
556
- style: { width: size.w, height: size.h },
557
- onClick: (e) => clickPlace(e, p),
558
- children: c ? /* @__PURE__ */ jsx4(
559
- "canvas",
560
- {
561
- className: "hv-canvas",
562
- width: c.width,
563
- height: c.height,
564
- ref: (node) => {
565
- if (!node) {
566
- return;
567
- }
568
- const ctx = node.getContext("2d");
569
- if (ctx) {
570
- ctx.drawImage(c, 0, 0);
571
- }
1174
+ className: "hv-sidebar-header",
1175
+ style: { justifyContent: "space-between", padding: "12px 16px" },
1176
+ children: [
1177
+ /* @__PURE__ */ jsx8("h3", { style: { margin: 0, fontSize: "15px", fontWeight: 600 }, children: "Signatures" }),
1178
+ /* @__PURE__ */ jsx8(
1179
+ "button",
1180
+ {
1181
+ onClick: onClose,
1182
+ className: "hv-btn",
1183
+ style: { padding: "4px", border: "none" },
1184
+ children: /* @__PURE__ */ jsx8(X, { size: 18 })
572
1185
  }
573
- }
574
- ) : /* @__PURE__ */ jsx4("div", { className: "hv-loading", children: "Rendering\u2026" })
575
- },
576
- p
577
- );
578
- })
579
- }
580
- ) : null
581
- ] });
582
- }
583
-
584
- // src/renderers/PptxRenderer.tsx
585
- import { useEffect as useEffect5, useMemo as useMemo5, useState as useState5 } from "react";
586
- import JSZip from "jszip";
587
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
588
- function decodeXml(s) {
589
- return s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'");
590
- }
591
- function extractText(xml) {
592
- return [...xml.matchAll(/<a:t>(.*?)<\/a:t>/g)].map((m) => decodeXml(m[1] || "")).join(" ").trim();
593
- }
594
- function PptxRenderer(props) {
595
- const [slides, setSlides] = useState5([]);
596
- const [thumbs, setThumbs] = useState5([]);
597
- const [error, setError] = useState5(null);
598
- const [loading, setLoading] = useState5(false);
599
- useEffect5(() => {
600
- let cancel = false;
601
- setError(null);
602
- setLoading(true);
603
- (async () => {
604
- setSlides([]);
605
- setThumbs([]);
606
- if (!props.arrayBuffer) {
607
- setError("No PPTX source provided.");
608
- setLoading(false);
609
- return;
610
- }
611
- try {
612
- const zip = await JSZip.loadAsync(props.arrayBuffer);
613
- const slidePaths = Object.keys(zip.files).filter((p) => /^ppt\/slides\/slide\d+\.xml$/.test(p)).sort();
614
- const slidesOut = [];
615
- for (let i = 0; i < slidePaths.length; i++) {
616
- const xml = await zip.files[slidePaths[i]].async("string");
617
- slidesOut.push({ index: i + 1, text: extractText(xml) });
618
- }
619
- if (cancel) return;
620
- setSlides(
621
- slidesOut.length ? slidesOut : [{ index: 1, text: "(empty)" }]
622
- );
623
- props.onSlideCount(slidesOut.length || 1);
624
- const thumbWidth = 56;
625
- const thumbsArr = [];
626
- for (let i = 0; i < (slidesOut.length || 1); i++) {
627
- thumbsArr.push(
628
- `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgThumb(i + 1))}`
629
- );
630
- }
631
- setThumbs(thumbsArr);
632
- } catch (e) {
633
- setSlides([
634
- { index: 1, text: "Unable to render this .pptx in-browser." }
635
- ]);
636
- setThumbs([void 0]);
637
- setError(
638
- "Failed to load PPTX. " + (e instanceof Error ? e.message : "")
639
- );
640
- } finally {
641
- setLoading(false);
1186
+ )
1187
+ ]
1188
+ }
1189
+ ),
1190
+ /* @__PURE__ */ jsxs7("div", { className: "hv-thumb-list", children: [
1191
+ /* @__PURE__ */ jsxs7(
1192
+ "button",
1193
+ {
1194
+ onClick: handleCreateClick,
1195
+ className: "hv-btn",
1196
+ style: {
1197
+ width: "100%",
1198
+ justifyContent: "center",
1199
+ border: "2px dashed var(--hv-border)",
1200
+ marginBottom: "16px",
1201
+ color: "var(--hv-primary)"
1202
+ },
1203
+ children: [
1204
+ /* @__PURE__ */ jsx8(Plus, { size: 18, style: { marginRight: "8px" } }),
1205
+ "New Signature"
1206
+ ]
1207
+ }
1208
+ ),
1209
+ signatures.map((sig, idx) => {
1210
+ const isLocal = localSignatures.some((s) => s.id === sig.id);
1211
+ const showDelete = isLocal;
1212
+ return /* @__PURE__ */ jsxs7(
1213
+ "div",
1214
+ {
1215
+ className: "hv-thumb-item",
1216
+ style: {
1217
+ position: "relative",
1218
+ padding: "12px",
1219
+ background: "var(--hv-bg)",
1220
+ borderRadius: "8px",
1221
+ border: "1px solid var(--hv-border)"
1222
+ },
1223
+ onClick: () => onSelectSignature(sig),
1224
+ children: [
1225
+ /* @__PURE__ */ jsx8(
1226
+ "img",
1227
+ {
1228
+ src: sig.signatureImageUrl,
1229
+ alt: "Signature",
1230
+ style: { height: "40px", objectFit: "contain" }
1231
+ }
1232
+ ),
1233
+ /* @__PURE__ */ jsxs7(
1234
+ "div",
1235
+ {
1236
+ style: {
1237
+ fontSize: "11px",
1238
+ color: "var(--hv-muted)",
1239
+ marginTop: "4px",
1240
+ textAlign: "center"
1241
+ },
1242
+ children: [
1243
+ sig.signedBy || "User",
1244
+ " \u2022",
1245
+ " ",
1246
+ new Date(sig.dateSigned).toLocaleDateString(),
1247
+ sig.comment && /* @__PURE__ */ jsx8("div", { className: "text-xs text-gray-500 mt-1 italic", children: sig.comment })
1248
+ ]
1249
+ }
1250
+ ),
1251
+ showDelete && /* @__PURE__ */ jsx8(
1252
+ "button",
1253
+ {
1254
+ onClick: (e) => {
1255
+ e.stopPropagation();
1256
+ setLocalSignatures(
1257
+ localSignatures.filter((s) => s.id !== sig.id)
1258
+ );
1259
+ },
1260
+ className: "hv-btn",
1261
+ style: {
1262
+ position: "absolute",
1263
+ top: "4px",
1264
+ right: "4px",
1265
+ padding: "4px",
1266
+ color: "#ef4444",
1267
+ border: "none",
1268
+ background: "white"
1269
+ },
1270
+ children: /* @__PURE__ */ jsx8(Trash2, { size: 12 })
1271
+ }
1272
+ )
1273
+ ]
1274
+ },
1275
+ sig.id || idx
1276
+ );
1277
+ })
1278
+ ] })
1279
+ ]
642
1280
  }
643
- })();
644
- return () => {
645
- cancel = true;
646
- };
647
- }, [props.arrayBuffer]);
648
- useEffect5(() => {
649
- props.onThumbs(thumbs);
650
- }, [thumbs]);
651
- const pagesToShow = useMemo5(() => {
652
- const total = slides.length;
653
- if (props.layout === "side-by-side" && total > 1) {
654
- const left = Math.max(1, Math.min(props.currentPage, total));
655
- const right = Math.max(1, Math.min(left + 1, total));
656
- return left === right ? [left] : [left, right];
657
- }
658
- return [Math.max(1, Math.min(props.currentPage, total))];
659
- }, [props.currentPage, props.layout, slides.length]);
660
- return /* @__PURE__ */ jsxs5("div", { className: "hv-doc", children: [
661
- loading && /* @__PURE__ */ jsx5("div", { className: "hv-loading", children: "Loading PPTX\u2026" }),
662
- error && /* @__PURE__ */ jsx5("div", { className: "hv-error", children: error }),
663
- !loading && !error && (!slides || slides.length === 0) && /* @__PURE__ */ jsx5("div", { className: "hv-error", children: "No slides to display." }),
664
- !error && slides && slides.length > 0 && /* @__PURE__ */ jsx5(
1281
+ ),
1282
+ showModal && /* @__PURE__ */ jsx8("div", { className: "hv-modal-overlay", children: /* @__PURE__ */ jsxs7(
665
1283
  "div",
666
1284
  {
667
- className: props.layout === "side-by-side" ? "hv-pages hv-pages--two" : "hv-pages",
668
- children: pagesToShow.map((p) => {
669
- const s = slides[p - 1];
670
- return /* @__PURE__ */ jsxs5(
1285
+ className: "hv-modal",
1286
+ style: { width: "450px", maxWidth: "90vw" },
1287
+ children: [
1288
+ /* @__PURE__ */ jsxs7(
671
1289
  "div",
672
1290
  {
673
- className: "hv-slide",
674
- tabIndex: 0,
675
- onFocus: () => props.onCurrentPageChange(p),
1291
+ style: {
1292
+ padding: "16px 24px",
1293
+ borderBottom: "1px solid var(--hv-border)",
1294
+ display: "flex",
1295
+ justifyContent: "space-between",
1296
+ alignItems: "center"
1297
+ },
676
1298
  children: [
677
- /* @__PURE__ */ jsxs5("div", { className: "hv-slide-title", children: [
678
- "Slide ",
679
- p
680
- ] }),
681
- /* @__PURE__ */ jsx5("div", { className: "hv-slide-text", children: s?.text || "" })
1299
+ /* @__PURE__ */ jsx8("h3", { style: { margin: 0, fontSize: "18px", fontWeight: 600 }, children: "Draw Signature" }),
1300
+ /* @__PURE__ */ jsx8(
1301
+ "button",
1302
+ {
1303
+ onClick: () => setShowModal(false),
1304
+ className: "hv-btn",
1305
+ style: { border: "none" },
1306
+ children: /* @__PURE__ */ jsx8(X, { size: 20 })
1307
+ }
1308
+ )
682
1309
  ]
683
- },
684
- p
685
- );
686
- })
687
- }
688
- )
689
- ] });
690
- }
691
- function svgThumb(n) {
692
- 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>`;
693
- }
694
-
695
- // src/utils/locale.ts
696
- var defaultLocale = {
697
- "loading": "Loading\u2026",
698
- "error.title": "Error",
699
- "toolbar.layout.single": "Single page",
700
- "toolbar.layout.two": "Side-by-side",
701
- "toolbar.thumbs": "Thumbnails",
702
- "toolbar.signatures": "Signatures",
703
- "toolbar.sign": "Sign Document",
704
- "toolbar.save": "Save",
705
- "toolbar.exportPdf": "Export as PDF",
706
- "thumbnails.title": "Thumbnails",
707
- "thumbnails.page": "Page",
708
- "signatures.title": "Signatures",
709
- "signatures.empty": "No signatures",
710
- "signatures.placeHint": "Click on the document to place the signature.",
711
- "a11y.viewer": "Document viewer",
712
- "a11y.ribbon": "Ribbon",
713
- "a11y.editor": "Document editor"
714
- };
715
-
716
- // src/components/SignaturePanel.tsx
717
- import React6 from "react";
718
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
719
- function SignaturePanel(props) {
720
- const title = props.locale["signatures.title"] ?? "Signatures";
721
- const deduped = React6.useMemo(() => {
722
- const seen = /* @__PURE__ */ new Set();
723
- return props.signatures.filter((s) => {
724
- const key = `${s.signedBy}|${s.dateSigned}|${s.signatureImageUrl}`;
725
- if (seen.has(key)) return false;
726
- seen.add(key);
727
- return true;
728
- });
729
- }, [props.signatures]);
730
- return /* @__PURE__ */ jsxs6(
731
- "aside",
732
- {
733
- className: props.collapsed ? "hv-side hv-side--collapsed" : "hv-side",
734
- "aria-label": title,
735
- children: [
736
- /* @__PURE__ */ jsxs6("div", { className: "hv-sidebar-header", children: [
737
- /* @__PURE__ */ jsx6(
738
- "button",
739
- {
740
- type: "button",
741
- className: "hv-icon",
742
- onClick: props.onToggle,
743
- "aria-label": props.locale["toolbar.signatures"] ?? "Signatures",
744
- children: /* @__PURE__ */ jsx6("span", { "aria-hidden": true, children: "\u270D" })
745
1310
  }
746
1311
  ),
747
- /* @__PURE__ */ jsx6("div", { className: "hv-sidebar-title", children: title })
748
- ] }),
749
- /* @__PURE__ */ jsxs6("div", { className: "hv-sidebar-body", children: [
750
- deduped.length === 0 && /* @__PURE__ */ jsx6("div", { className: "hv-signature-empty", "aria-live": "polite", children: props.locale["signatures.empty"] ?? "No signatures yet." }),
751
- deduped.map((s, idx) => /* @__PURE__ */ jsxs6(
1312
+ /* @__PURE__ */ jsxs7(
752
1313
  "div",
753
1314
  {
754
- className: "hv-signature-card",
755
- tabIndex: 0,
756
- "aria-label": `Signature by ${s.signedBy}`,
1315
+ style: {
1316
+ padding: "24px",
1317
+ background: "#f9fafb",
1318
+ display: "flex",
1319
+ flexDirection: "column",
1320
+ alignItems: "center"
1321
+ },
757
1322
  children: [
758
- /* @__PURE__ */ jsx6(
759
- "img",
1323
+ /* @__PURE__ */ jsx8(
1324
+ "div",
760
1325
  {
761
- src: s.signatureImageUrl,
762
- alt: props.locale["signatures.imgAlt"] ? props.locale["signatures.imgAlt"].replace(
763
- "{name}",
764
- s.signedBy
765
- ) : `Signature by ${s.signedBy}`,
766
- className: "hv-signature-img"
1326
+ style: {
1327
+ background: "white",
1328
+ border: "1px solid #e5e7eb",
1329
+ borderRadius: "8px",
1330
+ overflow: "hidden",
1331
+ boxShadow: "inset 0 2px 4px 0 rgba(0,0,0,0.05)"
1332
+ },
1333
+ children: /* @__PURE__ */ jsx8(
1334
+ "canvas",
1335
+ {
1336
+ ref: canvasRef,
1337
+ width: 400,
1338
+ height: 200,
1339
+ style: {
1340
+ display: "block",
1341
+ cursor: "crosshair",
1342
+ touchAction: "none"
1343
+ },
1344
+ onMouseDown: startDraw,
1345
+ onMouseUp: stopDraw,
1346
+ onMouseMove: draw,
1347
+ onTouchStart: startDraw,
1348
+ onTouchEnd: stopDraw,
1349
+ onTouchMove: draw
1350
+ }
1351
+ )
767
1352
  }
768
1353
  ),
769
- /* @__PURE__ */ jsxs6("div", { className: "hv-signature-meta", children: [
770
- /* @__PURE__ */ jsx6("div", { className: "hv-signature-name", children: s.signedBy }),
771
- /* @__PURE__ */ jsx6("div", { className: "hv-signature-date", children: new Date(s.dateSigned).toLocaleString() }),
772
- s.comment ? /* @__PURE__ */ jsx6("div", { className: "hv-signature-comment", children: s.comment }) : null
773
- ] })
1354
+ /* @__PURE__ */ jsx8(
1355
+ "p",
1356
+ {
1357
+ style: {
1358
+ fontSize: "12px",
1359
+ color: "var(--hv-muted)",
1360
+ marginTop: "8px"
1361
+ },
1362
+ children: "Sign above using your mouse or finger"
1363
+ }
1364
+ )
774
1365
  ]
775
- },
776
- `${s.signedBy}-${s.dateSigned}-${s.signatureImageUrl}`
777
- ))
778
- ] })
779
- ]
780
- }
781
- );
782
- }
783
-
784
- // src/components/ThumbnailsSidebar.tsx
785
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
786
- function ThumbnailsSidebar(props) {
787
- const t = props.locale["thumbnails.title"] ?? "Thumbnails";
788
- return /* @__PURE__ */ jsxs7(
789
- "aside",
790
- {
791
- className: props.collapsed ? "hv-thumbs hv-thumbs--collapsed" : "hv-thumbs",
792
- "aria-label": t,
793
- children: [
794
- /* @__PURE__ */ jsxs7("div", { className: "hv-thumbs-header", children: [
795
- /* @__PURE__ */ jsx7(
796
- "button",
797
- {
798
- type: "button",
799
- className: "hv-thumbs-toggle",
800
- onClick: props.onToggle,
801
- "aria-label": props.collapsed ? props.locale["thumbnails.open"] ?? "Open thumbnails" : props.locale["thumbnails.close"] ?? "Close thumbnails",
802
- children: /* @__PURE__ */ jsx7("span", { className: "hv-thumbs-toggle-icon", children: props.collapsed ? "\u25B8" : "\u25BE" })
803
1366
  }
804
1367
  ),
805
- !props.collapsed && /* @__PURE__ */ jsx7("div", { className: "hv-thumbs-title", children: t })
806
- ] }),
807
- !props.collapsed && /* @__PURE__ */ jsx7("div", { className: "hv-thumbs-list", role: "list", children: props.thumbnails.map((th, idx) => {
808
- const p = idx + 1;
809
- const active = p === props.currentPage;
810
- return /* @__PURE__ */ jsxs7(
811
- "button",
1368
+ /* @__PURE__ */ jsxs7(
1369
+ "div",
812
1370
  {
813
- type: "button",
814
- role: "listitem",
815
- className: active ? "hv-thumb hv-thumb--active" : "hv-thumb",
816
- onClick: () => props.onSelectPage(p),
817
- "aria-current": active ? "page" : void 0,
818
- tabIndex: 0,
1371
+ style: {
1372
+ padding: "16px 24px",
1373
+ borderTop: "1px solid var(--hv-border)",
1374
+ display: "flex",
1375
+ justifyContent: "flex-end",
1376
+ gap: "12px"
1377
+ },
819
1378
  children: [
820
- /* @__PURE__ */ jsx7("div", { className: "hv-thumb-img", "aria-hidden": true, children: th.dataUrl ? /* @__PURE__ */ jsx7("img", { src: th.dataUrl, alt: "" }) : /* @__PURE__ */ jsx7("div", { className: "hv-thumb-placeholder" }) }),
821
- /* @__PURE__ */ jsx7("div", { className: "hv-thumb-label", children: th.label })
1379
+ /* @__PURE__ */ jsx8(
1380
+ "button",
1381
+ {
1382
+ onClick: () => {
1383
+ const ctx = canvasRef.current?.getContext("2d");
1384
+ ctx?.clearRect(0, 0, 400, 200);
1385
+ },
1386
+ className: "hv-btn",
1387
+ children: "Clear"
1388
+ }
1389
+ ),
1390
+ /* @__PURE__ */ jsx8("button", { onClick: saveSignature, className: "hv-btn hv-btn-primary", children: "Create & Use" })
822
1391
  ]
823
- },
824
- th.id
825
- );
826
- }) })
827
- ]
828
- }
829
- );
830
- }
831
-
832
- // src/components/Toolbar.tsx
833
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
834
- function Toolbar(props) {
835
- const t = (k, fallback) => props.locale[k] ?? fallback;
836
- return /* @__PURE__ */ jsxs8(
837
- "div",
838
- {
839
- className: "hv-toolbar",
840
- role: "toolbar",
841
- "aria-label": t("a11y.toolbar", "Document toolbar"),
842
- children: [
843
- /* @__PURE__ */ jsxs8("div", { className: "hv-toolbar__group", children: [
844
- /* @__PURE__ */ jsx8(
845
- "button",
846
- {
847
- className: `hv-btn ${props.showThumbnails ? "hv-btn--active" : ""}`,
848
- onClick: props.onToggleThumbnails,
849
- "aria-pressed": props.showThumbnails,
850
- children: "Thumbnails"
851
- }
852
- ),
853
- props.mode !== "create" && /* @__PURE__ */ jsx8(
854
- "button",
855
- {
856
- className: `hv-btn ${props.showSignatures ? "hv-btn--active" : ""}`,
857
- onClick: props.onToggleSignatures,
858
- "aria-pressed": props.showSignatures,
859
- children: "Signatures"
860
1392
  }
861
1393
  )
862
- ] }),
863
- /* @__PURE__ */ jsxs8("div", { className: "hv-toolbar__group hv-segment", children: [
864
- /* @__PURE__ */ jsx8(
865
- "button",
866
- {
867
- className: `hv-btn ${props.layout === "single" ? "hv-btn--active" : ""}`,
868
- onClick: () => props.onChangeLayout("single"),
869
- children: "Single page"
870
- }
871
- ),
872
- /* @__PURE__ */ jsx8(
873
- "button",
874
- {
875
- className: `hv-btn ${props.layout === "side-by-side" ? "hv-btn--active" : ""}`,
876
- onClick: () => props.onChangeLayout("side-by-side"),
877
- children: "Side-by-side"
878
- }
879
- )
880
- ] }),
881
- /* @__PURE__ */ jsxs8("div", { className: "hv-toolbar__group hv-toolbar__actions", children: [
882
- props.showHeaderFooterToggle && /* @__PURE__ */ jsxs8("label", { className: "hv-switch", children: [
883
- /* @__PURE__ */ jsx8(
884
- "input",
885
- {
886
- type: "checkbox",
887
- checked: props.headerFooterEnabled,
888
- onChange: props.onToggleHeaderFooter
889
- }
890
- ),
891
- /* @__PURE__ */ jsx8("span", { className: "hv-switch__slider" }),
892
- /* @__PURE__ */ jsx8("span", { className: "hv-switch__label", children: t("toolbar.letterhead", "Letterhead") })
893
- ] }),
894
- props.allowSigning && /* @__PURE__ */ jsx8(
895
- "button",
896
- {
897
- className: "hv-btn hv-btn--primary",
898
- onClick: props.onSign,
899
- disabled: props.signingDisabled,
900
- children: "Sign document"
901
- }
902
- ),
903
- props.canExportPdf && /* @__PURE__ */ jsx8("button", { className: "hv-btn", onClick: props.onExportPdf, children: "Export PDF" }),
904
- props.canSave && /* @__PURE__ */ jsx8("button", { className: "hv-btn hv-btn--primary", onClick: props.onSave, children: "Save" })
905
- ] })
906
- ]
907
- }
908
- );
1394
+ ]
1395
+ }
1396
+ ) })
1397
+ ] });
909
1398
  }
910
1399
 
911
1400
  // src/components/DocumentViewer.tsx
912
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1401
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
913
1402
  function DocumentViewer(props) {
914
1403
  const mode = props.mode ?? "view";
915
1404
  const theme = props.theme ?? "light";
916
- const locale = useMemo6(
917
- () => ({ ...defaultLocale, ...props.locale ?? {} }),
918
- [props.locale]
919
- );
920
- const [layout, setLayout] = useState6(
1405
+ const [layout, setLayout] = useState7(
921
1406
  props.defaultLayout ?? "single"
922
1407
  );
923
- const [showThumbnails, setShowThumbnails] = useState6(true);
924
- const [showSignatures, setShowSignatures] = useState6(true);
925
- const [headerFooterEnabled, setHeaderFooterEnabled] = useState6(true);
926
- const allowSigning = props.allowSigning ?? false;
927
- const [signingBusy, setSigningBusy] = useState6(false);
928
- const [resolved, setResolved] = useState6(null);
929
- const [error, setError] = useState6("");
930
- const [pageCount, setPageCount] = useState6(1);
931
- const [currentPage, setCurrentPage] = useState6(1);
932
- const [thumbs, setThumbs] = useState6([]);
933
- const [localSignatures, setLocalSignatures] = useState6(
934
- props.signatures ?? []
1408
+ const [showThumbnails, setShowThumbnails] = useState7(
1409
+ props.defaultShowThumbnails ?? true
935
1410
  );
936
- useEffect6(
937
- () => setLocalSignatures(props.signatures ?? []),
938
- [props.signatures]
939
- );
940
- const [sigPlacements, setSigPlacements] = useState6([]);
941
- const [armedSignatureUrl, setArmedSignatureUrl] = useState6(
942
- null
943
- );
944
- const editorRef = useRef3(null);
945
- useEffect6(() => {
946
- let cancelled = false;
947
- (async () => {
1411
+ const [showSignatures, setShowSignatures] = useState7(false);
1412
+ const [resolved, setResolved] = useState7(null);
1413
+ const [loading, setLoading] = useState7(true);
1414
+ const [error, setError] = useState7("");
1415
+ const [pageCount, setPageCount] = useState7(1);
1416
+ const [currentPage, setCurrentPage] = useState7(1);
1417
+ const [thumbnails, setThumbnails] = useState7([]);
1418
+ useEffect7(() => {
1419
+ let active = true;
1420
+ const loadFile = async () => {
1421
+ setLoading(true);
948
1422
  setError("");
949
1423
  setResolved(null);
950
- setThumbs([]);
951
- setPageCount(1);
952
- setCurrentPage(1);
953
- setSigPlacements([]);
954
- setArmedSignatureUrl(null);
955
- if (mode === "create") {
956
- const ft = props.fileType ?? "docx";
957
- setResolved({
958
- fileType: ft,
959
- fileName: props.fileName ?? `Untitled.${ft}`
960
- });
961
- return;
962
- }
963
1424
  try {
964
- const res = await resolveSource({
965
- fileUrl: props.fileUrl,
966
- base64: props.base64,
967
- blob: props.blob,
968
- fileName: props.fileName,
969
- fileType: props.fileType
970
- });
971
- if (cancelled) {
972
- return;
973
- }
974
- setResolved({
975
- fileType: res.fileType,
976
- fileName: res.fileName,
977
- url: res.url,
978
- arrayBuffer: res.arrayBuffer
979
- });
980
- } catch (e) {
981
- if (cancelled) {
982
- return;
1425
+ if (mode === "create") {
1426
+ setResolved({
1427
+ fileType: props.fileType ?? "docx",
1428
+ fileName: props.fileName ?? "Untitled"
1429
+ });
1430
+ } else {
1431
+ const res = await resolveSource({
1432
+ fileUrl: props.fileUrl,
1433
+ base64: props.base64,
1434
+ blob: props.blob,
1435
+ fileName: props.fileName,
1436
+ fileType: props.fileType
1437
+ });
1438
+ if (active) setResolved(res);
983
1439
  }
984
- setError(e instanceof Error ? e.message : String(e));
1440
+ } catch (err) {
1441
+ if (active) setError(err.message || "Failed to load document");
1442
+ } finally {
1443
+ if (active) setLoading(false);
985
1444
  }
986
- })();
1445
+ };
1446
+ loadFile();
987
1447
  return () => {
988
- cancelled = true;
1448
+ active = false;
989
1449
  };
990
- }, [
991
- mode,
992
- props.fileUrl,
993
- props.base64,
994
- props.blob,
995
- props.fileName,
996
- props.fileType
997
- ]);
998
- const thumbnails = useMemo6(() => {
999
- const n = Math.max(1, pageCount);
1000
- return Array.from({ length: n }, (_, i) => ({
1001
- id: `p-${i + 1}`,
1002
- label: `${locale["thumbnails.page"] ?? "Page"} ${i + 1}`,
1003
- dataUrl: thumbs[i]
1004
- }));
1005
- }, [pageCount, thumbs, locale]);
1006
- async function handleSignRequest() {
1007
- if (!allowSigning || signingBusy || !props.onSignRequest) {
1008
- return;
1450
+ }, [props.fileUrl, props.base64, props.blob, mode]);
1451
+ const handleSignatureSelect = (sig) => {
1452
+ if (props.onSign) {
1453
+ props.onSign(sig);
1009
1454
  }
1010
- setSigningBusy(true);
1011
- try {
1012
- const sig = await props.onSignRequest();
1013
- setLocalSignatures((prev) => [...prev, sig]);
1014
- setArmedSignatureUrl(sig.signatureImageUrl);
1015
- } finally {
1016
- setSigningBusy(false);
1455
+ console.log("Signature selected:", sig);
1456
+ };
1457
+ const renderContent = () => {
1458
+ if (error) {
1459
+ return /* @__PURE__ */ jsxs8("div", { className: "hv-error-banner", children: [
1460
+ /* @__PURE__ */ jsx9("strong", { children: "Error loading document" }),
1461
+ /* @__PURE__ */ jsx9("p", { children: error })
1462
+ ] });
1017
1463
  }
1018
- }
1019
- function placeSignature(p) {
1020
- if (!armedSignatureUrl) {
1021
- return;
1022
- }
1023
- setSigPlacements((prev) => [
1024
- ...prev,
1025
- { ...p, signatureImageUrl: armedSignatureUrl }
1026
- ]);
1027
- setArmedSignatureUrl(null);
1028
- }
1029
- async function handleSave(exportPdf) {
1030
- if (editorRef.current) {
1031
- await editorRef.current.save(!!exportPdf);
1032
- return;
1033
- }
1034
- if (!resolved?.arrayBuffer) {
1035
- return;
1464
+ if (loading || !resolved) {
1465
+ return /* @__PURE__ */ jsxs8("div", { className: "hv-loader", children: [
1466
+ /* @__PURE__ */ jsx9("div", { className: "hv-spinner" }),
1467
+ /* @__PURE__ */ jsx9("span", { children: "Loading Document..." })
1468
+ ] });
1036
1469
  }
1037
- const b64 = arrayBufferToBase642(resolved.arrayBuffer);
1038
- props.onSave?.(b64, {
1470
+ const commonProps = {
1471
+ arrayBuffer: resolved.arrayBuffer,
1039
1472
  fileName: resolved.fileName,
1040
1473
  fileType: resolved.fileType,
1041
- annotations: { sigPlacements }
1042
- });
1043
- }
1044
- const canSave = mode === "edit" || mode === "create";
1045
- const canExportPdf = (mode === "edit" || mode === "create") && (resolved?.fileType === "docx" || resolved?.fileType === "md" || resolved?.fileType === "txt" || resolved?.fileType === "xlsx");
1046
- return /* @__PURE__ */ jsxs9("div", { className: `hv-root`, "data-hv-theme": theme, children: [
1474
+ layout,
1475
+ currentPage,
1476
+ onPageCount: setPageCount,
1477
+ onCurrentPageChange: setCurrentPage,
1478
+ onThumbs: setThumbnails
1479
+ };
1480
+ switch (resolved.fileType) {
1481
+ case "pdf":
1482
+ return /* @__PURE__ */ jsx9(PdfRenderer, { url: resolved.url, ...commonProps });
1483
+ case "docx":
1484
+ case "doc":
1485
+ case "rtf":
1486
+ case "txt":
1487
+ case "md":
1488
+ return /* @__PURE__ */ jsx9(RichTextEditor, { mode, ...commonProps });
1489
+ case "xlsx":
1490
+ case "csv":
1491
+ case "xls":
1492
+ return /* @__PURE__ */ jsx9(SpreadsheetEditor, { mode, ...commonProps });
1493
+ case "pptx":
1494
+ case "ppt":
1495
+ return /* @__PURE__ */ jsx9(PptxRenderer, { ...commonProps });
1496
+ case "jpg":
1497
+ case "jpeg":
1498
+ case "png":
1499
+ case "gif":
1500
+ case "bmp":
1501
+ case "svg":
1502
+ return /* @__PURE__ */ jsx9(ImageRenderer, { ...commonProps, fileType: resolved.fileType });
1503
+ default:
1504
+ return /* @__PURE__ */ jsxs8("div", { className: "hv-error-banner", children: [
1505
+ "Unsupported file type: ",
1506
+ resolved.fileType
1507
+ ] });
1508
+ }
1509
+ };
1510
+ return /* @__PURE__ */ jsxs8("div", { className: "hv-root", "data-hv-theme": theme, children: [
1047
1511
  /* @__PURE__ */ jsx9(
1048
1512
  Toolbar,
1049
1513
  {
1050
- locale,
1051
- mode,
1052
- fileType: resolved?.fileType,
1514
+ fileName: resolved?.fileName,
1515
+ pageCount,
1516
+ currentPage,
1517
+ onPageChange: setCurrentPage,
1053
1518
  layout,
1054
- onChangeLayout: setLayout,
1519
+ onLayoutChange: setLayout,
1055
1520
  showThumbnails,
1056
- onToggleThumbnails: () => setShowThumbnails((v) => !v),
1521
+ onToggleThumbnails: () => setShowThumbnails(!showThumbnails),
1057
1522
  showSignatures,
1058
- onToggleSignatures: () => setShowSignatures((v) => !v),
1059
- onSign: () => void handleSignRequest(),
1060
- allowSigning,
1061
- signingDisabled: signingBusy || !props.onSignRequest,
1062
- canSave,
1063
- onSave: () => void handleSave(false),
1064
- canExportPdf,
1065
- onExportPdf: () => void handleSave(true),
1066
- headerFooterEnabled,
1067
- showHeaderFooterToggle: (props.enableHeaderFooterToggle ?? true) && mode === "create",
1068
- onToggleHeaderFooter: () => setHeaderFooterEnabled((v) => !v)
1523
+ onToggleSignatures: () => setShowSignatures(!showSignatures),
1524
+ disableSigning: props.disableSigning
1069
1525
  }
1070
1526
  ),
1071
- error ? /* @__PURE__ */ jsxs9("div", { className: "hv-error", role: "alert", children: [
1072
- /* @__PURE__ */ jsx9("div", { className: "hv-error-title", children: locale["error.title"] ?? "Error" }),
1073
- /* @__PURE__ */ jsx9("div", { className: "hv-error-body", children: error })
1074
- ] }) : null,
1075
- !resolved && !error ? /* @__PURE__ */ jsx9("div", { className: "hv-loading", "aria-busy": "true", children: locale.loading ?? "Loading\u2026" }) : null,
1076
- resolved ? /* @__PURE__ */ jsxs9("div", { className: "hv-shell", children: [
1077
- mode !== "create" ? /* @__PURE__ */ jsx9(
1527
+ /* @__PURE__ */ jsxs8("div", { className: "hv-shell", children: [
1528
+ /* @__PURE__ */ jsx9(
1078
1529
  ThumbnailsSidebar,
1079
1530
  {
1080
- locale,
1531
+ isOpen: showThumbnails,
1081
1532
  thumbnails,
1082
1533
  currentPage,
1083
- collapsed: !showThumbnails,
1084
- onToggle: () => setShowThumbnails((v) => !v),
1085
1534
  onSelectPage: setCurrentPage
1086
1535
  }
1087
- ) : null,
1088
- /* @__PURE__ */ jsxs9("main", { className: "hv-main", children: [
1089
- resolved.fileType === "pdf" ? /* @__PURE__ */ jsx9(
1090
- PdfRenderer,
1091
- {
1092
- url: resolved.url,
1093
- arrayBuffer: resolved.arrayBuffer,
1094
- layout,
1095
- currentPage,
1096
- onCurrentPageChange: setCurrentPage,
1097
- onPageCount: (n) => {
1098
- setPageCount(n);
1099
- setThumbs(
1100
- (prev) => prev.length === n ? prev : Array.from({ length: n }, (_, i) => prev[i])
1101
- );
1102
- },
1103
- onThumbs: (t) => setThumbs(t),
1104
- signatureStamp: armedSignatureUrl ? {
1105
- imageUrl: armedSignatureUrl,
1106
- armed: true,
1107
- onPlaced: placeSignature
1108
- } : void 0
1109
- }
1110
- ) : null,
1111
- resolved.fileType === "docx" || resolved.fileType === "md" || resolved.fileType === "txt" ? /* @__PURE__ */ jsx9(
1112
- RichTextEditor,
1113
- {
1114
- ref: editorRef,
1115
- mode,
1116
- fileType: resolved.fileType,
1117
- fileName: resolved.fileName,
1118
- arrayBuffer: resolved.arrayBuffer,
1119
- headerComponent: props.headerComponent,
1120
- footerComponent: props.footerComponent,
1121
- headerFooterEnabled,
1122
- locale,
1123
- signatures: localSignatures,
1124
- signaturePlacements: sigPlacements,
1125
- onPageCount: (n) => {
1126
- setPageCount(n);
1127
- setThumbs(
1128
- (prev) => prev.length === n ? prev : Array.from({ length: n }, (_, i) => prev[i])
1129
- );
1130
- },
1131
- onSave: (b64, meta) => props.onSave?.(b64, meta),
1132
- armedSignatureUrl,
1133
- onPlaceSignature: placeSignature
1134
- }
1135
- ) : null,
1136
- resolved.fileType === "xlsx" ? /* @__PURE__ */ jsx9(
1137
- SpreadsheetEditor,
1138
- {
1139
- ref: editorRef,
1140
- mode,
1141
- fileName: resolved.fileName,
1142
- arrayBuffer: resolved.arrayBuffer,
1143
- locale,
1144
- onSave: (b64, meta) => props.onSave?.(b64, meta)
1145
- }
1146
- ) : null,
1147
- resolved.fileType === "pptx" ? /* @__PURE__ */ jsx9(
1148
- PptxRenderer,
1149
- {
1150
- arrayBuffer: resolved.arrayBuffer,
1151
- layout,
1152
- currentPage,
1153
- onCurrentPageChange: setCurrentPage,
1154
- onSlideCount: (n) => {
1155
- setPageCount(n);
1156
- setThumbs(
1157
- (prev) => prev.length === n ? prev : Array.from({ length: n }, (_, i) => prev[i])
1158
- );
1159
- },
1160
- onThumbs: (t) => setThumbs(t)
1161
- }
1162
- ) : null,
1163
- resolved.fileType === "png" || resolved.fileType === "jpg" || resolved.fileType === "svg" ? /* @__PURE__ */ jsx9(
1164
- ImageRenderer,
1165
- {
1166
- arrayBuffer: resolved.arrayBuffer,
1167
- fileType: resolved.fileType,
1168
- fileName: resolved.fileName
1169
- }
1170
- ) : null
1171
- ] }),
1172
- mode !== "create" && localSignatures.length ? /* @__PURE__ */ jsx9(
1536
+ ),
1537
+ /* @__PURE__ */ jsx9("main", { className: "hv-main", children: renderContent() }),
1538
+ /* @__PURE__ */ jsx9(
1173
1539
  SignaturePanel,
1174
1540
  {
1175
- locale,
1176
- signatures: localSignatures,
1177
- collapsed: !showSignatures,
1178
- onToggle: () => setShowSignatures((v) => !v)
1541
+ isOpen: showSignatures,
1542
+ onClose: () => setShowSignatures(false),
1543
+ onSelectSignature: handleSignatureSelect,
1544
+ externalSignatures: props.signatures,
1545
+ onSignRequest: props.onSignRequest
1179
1546
  }
1180
- ) : null
1181
- ] }) : null
1547
+ )
1548
+ ] })
1182
1549
  ] });
1183
1550
  }
1184
- function arrayBufferToBase642(ab) {
1185
- const bytes = new Uint8Array(ab);
1186
- let binary = "";
1187
- const chunk = 32768;
1188
- for (let i = 0; i < bytes.length; i += chunk) {
1189
- binary += String.fromCharCode(...bytes.subarray(i, i + chunk));
1190
- }
1191
- return btoa(binary);
1192
- }
1193
1551
  export {
1194
1552
  DocumentViewer
1195
1553
  };
1196
- //# sourceMappingURL=index.js.map
1554
+ //# sourceMappingURL=index.mjs.map