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