@zerohive/hive-viewer 0.2.4 → 0.2.6

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
@@ -10,8 +10,7 @@ import {
10
10
  useEffect,
11
11
  useImperativeHandle,
12
12
  useMemo,
13
- useRef,
14
- useState
13
+ useRef
15
14
  } from "react";
16
15
 
17
16
  // src/utils/sanitize.ts
@@ -26,222 +25,122 @@ function sanitizeHtml(html) {
26
25
  // src/editors/RichTextEditor.tsx
27
26
  import { jsx, jsxs } from "react/jsx-runtime";
28
27
  var PAGE_H = 1122;
29
- var RichTextEditor = forwardRef((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 [html, setHtml] = useState("<p><br/></p>");
39
- useEffect(() => {
40
- let cancelled = false;
41
- (async () => {
42
- if (props.mode === "create") {
43
- setHtml("<p><br/></p>");
44
- return;
45
- }
46
- if (!props.arrayBuffer) {
47
- return;
48
- }
49
- if (props.fileType === "docx") {
50
- const res = await mammoth.convertToHtml({
51
- arrayBuffer: props.arrayBuffer
52
- });
53
- if (!cancelled) {
54
- setHtml(sanitizeHtml(res.value || "<p><br/></p>"));
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;
55
46
  }
56
- } else {
57
- const text = new TextDecoder().decode(props.arrayBuffer);
58
- if (props.fileType === "md") {
59
- setHtml(sanitizeHtml(md.render(text)));
47
+ if (!props.arrayBuffer) return;
48
+ if (props.fileType === "docx") {
49
+ const res = await mammoth.convertToHtml({
50
+ arrayBuffer: props.arrayBuffer
51
+ });
52
+ editorRef.current.innerHTML = sanitizeHtml(
53
+ res.value || "<p><br/></p>"
54
+ );
60
55
  } else {
61
- setHtml(`<pre>${escapeHtml(text)}</pre>`);
56
+ const text = new TextDecoder().decode(props.arrayBuffer);
57
+ editorRef.current.innerHTML = props.fileType === "md" ? sanitizeHtml(md.render(text)) : `<pre>${escapeHtml(text)}</pre>`;
62
58
  }
63
- }
64
- })();
65
- return () => {
66
- cancelled = true;
67
- };
68
- }, [props.arrayBuffer, props.fileType, props.mode, md]);
69
- useEffect(() => {
70
- const el = scrollerRef.current;
71
- if (!el) {
72
- return;
73
- }
74
- const recompute = () => props.onPageCount(Math.max(1, Math.ceil(el.scrollHeight / PAGE_H)));
75
- recompute();
76
- const ro = new ResizeObserver(recompute);
77
- ro.observe(el);
78
- return () => ro.disconnect();
79
- }, [html, props.headerFooterEnabled]);
80
- function exec(cmd) {
81
- if (readOnly) {
82
- return;
83
- }
84
- document.execCommand(cmd);
85
- }
86
- function onClick(e) {
87
- if (!props.armedSignatureUrl) {
88
- return;
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();
89
75
  }
90
- const scroller = scrollerRef.current;
91
- if (!scroller) {
92
- return;
93
- }
94
- const rect = e.currentTarget.getBoundingClientRect();
95
- const absY = scroller.scrollTop + (e.clientY - rect.top);
96
- const page = Math.max(1, Math.floor(absY / PAGE_H) + 1);
97
- const pageTop = (page - 1) * PAGE_H;
98
- const x = (e.clientX - rect.left) / rect.width;
99
- const y = (absY - pageTop) / PAGE_H;
100
- props.onPlaceSignature({ page, x, y, w: 0.25, h: 0.1 });
101
- }
102
- async function requestThumbnail(index) {
103
- const scroller = scrollerRef.current;
104
- const capture = captureRef.current;
105
- if (!scroller || !capture) {
106
- return void 0;
107
- }
108
- const old = scroller.scrollTop;
109
- scroller.scrollTop = index * PAGE_H;
110
- await new Promise((r) => requestAnimationFrame(() => r(null)));
111
- try {
112
- const canvas = await html2canvas(capture, {
113
- backgroundColor: null,
114
- scale: 0.25,
115
- useCORS: true
116
- });
117
- return canvas.toDataURL("image/png");
118
- } catch {
119
- return void 0;
120
- } finally {
121
- scroller.scrollTop = old;
122
- }
123
- }
124
- async function save(exportPdf) {
125
- const inner = editorRef.current?.innerHTML ?? html;
126
- const stitched = `<!doctype html><html><head><meta charset="utf-8" /></head><body>${inner}</body></html>`;
127
- if (exportPdf) {
128
- const b642 = btoa(unescape(encodeURIComponent(stitched)));
129
- props.onSave(b642, {
130
- fileName: replaceExt(props.fileName, "html"),
131
- fileType: "txt",
132
- exportedAsPdf: true,
133
- annotations: { signaturePlacements: props.signaturePlacements }
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
134
89
  });
135
- return;
136
90
  }
137
- if (props.fileType === "docx") {
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));
138
98
  try {
139
- const response = await fetch("/api/export-docx", {
140
- method: "POST",
141
- headers: { "Content-Type": "application/json" },
142
- body: JSON.stringify({
143
- html: stitched,
144
- fileName: replaceExt(props.fileName, "docx")
145
- })
99
+ const canvas = await html2canvas(capture, {
100
+ scale: 0.25,
101
+ useCORS: true
146
102
  });
147
- if (!response.ok) {
148
- throw new Error("Failed to generate DOCX");
149
- }
150
- const blob = await response.blob();
151
- const url = window.URL.createObjectURL(blob);
152
- const a = document.createElement("a");
153
- a.href = url;
154
- a.download = replaceExt(props.fileName, "docx");
155
- document.body.appendChild(a);
156
- a.click();
157
- setTimeout(() => {
158
- window.URL.revokeObjectURL(url);
159
- a.remove();
160
- }, 100);
161
- } catch (err) {
162
- alert(
163
- "DOCX export failed: " + (err instanceof Error ? err.message : String(err))
164
- );
103
+ return canvas.toDataURL("image/png");
104
+ } finally {
105
+ scroller.scrollTop = old;
165
106
  }
166
- return;
167
107
  }
168
- const text = editorRef.current?.innerText ?? "";
169
- const b64 = btoa(unescape(encodeURIComponent(text)));
170
- props.onSave(b64, {
171
- fileName: replaceExt(props.fileName, props.fileType),
172
- fileType: props.fileType,
173
- annotations: { signaturePlacements: props.signaturePlacements }
174
- });
175
- }
176
- useImperativeHandle(ref, () => ({ save, requestThumbnail }));
177
- return /* @__PURE__ */ jsxs("div", { className: "hv-doc", children: [
178
- /* @__PURE__ */ jsxs("div", { className: "hv-ribbon", role: "toolbar", children: [
179
- /* @__PURE__ */ jsx(
180
- "button",
181
- {
182
- className: "hv-btn",
183
- onClick: () => exec("bold"),
184
- disabled: readOnly,
185
- children: "B"
186
- }
187
- ),
188
- /* @__PURE__ */ jsx(
189
- "button",
190
- {
191
- className: "hv-btn",
192
- onClick: () => exec("italic"),
193
- disabled: readOnly,
194
- children: "I"
195
- }
196
- ),
197
- /* @__PURE__ */ jsx(
198
- "button",
199
- {
200
- className: "hv-btn",
201
- onClick: () => exec("underline"),
202
- disabled: readOnly,
203
- children: "U"
204
- }
205
- ),
206
- props.armedSignatureUrl ? /* @__PURE__ */ jsx("div", { className: "hv-hint", children: "Click to place signature" }) : null
207
- ] }),
208
- /* @__PURE__ */ jsx("div", { className: "hv-scroll", ref: scrollerRef, onClick, children: /* @__PURE__ */ jsxs("div", { className: "hv-pageStage", ref: captureRef, children: [
209
- props.headerFooterEnabled && props.headerComponent ? /* @__PURE__ */ jsx("div", { className: "hv-letterhead", children: props.headerComponent }) : null,
210
- /* @__PURE__ */ jsx(
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 }
117
+ });
118
+ }
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
+ ] }),
130
+ /* @__PURE__ */ jsx("div", { className: "hv-scroll", ref: scrollerRef, onClick: onClickPage, children: /* @__PURE__ */ jsx("div", { className: "hv-pageStage", ref: captureRef, children: /* @__PURE__ */ jsx(
211
131
  "div",
212
132
  {
213
133
  ref: editorRef,
214
- className: readOnly ? "hv-editor hv-editor--ro" : "hv-editor",
134
+ className: `hv-editor ${readOnly ? "ro" : ""}`,
215
135
  contentEditable: !readOnly,
216
- suppressContentEditableWarning: true,
217
- onInput: () => setHtml(editorRef.current?.innerHTML ?? ""),
218
- dangerouslySetInnerHTML: { __html: html }
136
+ suppressContentEditableWarning: true
219
137
  }
220
- ),
221
- props.headerFooterEnabled && props.footerComponent ? /* @__PURE__ */ jsx("div", { className: "hv-letterhead hv-letterhead--footer", children: props.footerComponent }) : null,
222
- props.mode === "create" && props.signatures.length ? /* @__PURE__ */ jsx("div", { className: "hv-signatures-inline", children: props.signatures.map((s, i) => /* @__PURE__ */ jsxs("div", { className: "hv-sign-inline", children: [
223
- /* @__PURE__ */ jsx(
224
- "img",
225
- {
226
- src: s.signatureImageUrl,
227
- alt: "",
228
- className: "hv-sign-img"
229
- }
230
- ),
231
- /* @__PURE__ */ jsxs("div", { children: [
232
- /* @__PURE__ */ jsx("div", { className: "hv-sign-name", children: s.signedBy }),
233
- /* @__PURE__ */ jsx("div", { className: "hv-sign-date", children: new Date(s.dateSigned).toLocaleString() })
234
- ] })
235
- ] }, i)) }) : null
236
- ] }) })
237
- ] });
238
- });
239
- function replaceExt(name, ext) {
240
- const base = name.includes(".") ? name.slice(0, name.lastIndexOf(".")) : name;
241
- return `${base}.${ext}`;
242
- }
138
+ ) }) })
139
+ ] });
140
+ }
141
+ );
243
142
  function escapeHtml(s) {
244
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
143
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;");
245
144
  }
246
145
 
247
146
  // src/editors/SpreadsheetEditor.tsx
@@ -941,12 +840,12 @@ function Toolbar(props) {
941
840
  role: "toolbar",
942
841
  "aria-label": t("a11y.toolbar", "Document toolbar"),
943
842
  children: [
944
- /* @__PURE__ */ jsxs8("div", { className: "hv-toolbar__left gap-2", children: [
843
+ /* @__PURE__ */ jsxs8("div", { className: "hv-toolbar__left space-x-1", children: [
945
844
  /* @__PURE__ */ jsx8(
946
845
  "button",
947
846
  {
948
847
  type: "button",
949
- className: "hv-btn",
848
+ className: "hv-btn text-sm",
950
849
  onClick: props.onToggleThumbnails,
951
850
  "aria-pressed": props.showThumbnails,
952
851
  children: t("toolbar.thumbs", "Thumbnails")
@@ -956,7 +855,7 @@ function Toolbar(props) {
956
855
  "button",
957
856
  {
958
857
  type: "button",
959
- className: "hv-btn",
858
+ className: "hv-btn text-sm",
960
859
  onClick: props.onToggleSignatures,
961
860
  "aria-pressed": props.showSignatures,
962
861
  children: t("toolbar.signatures", "Signatures")
@@ -967,7 +866,7 @@ function Toolbar(props) {
967
866
  "button",
968
867
  {
969
868
  type: "button",
970
- className: props.layout === "single" ? "hv-btn hv-btn--active" : "hv-btn",
869
+ className: props.layout === "single" ? "hv-btn hv-btn--active text-sm" : "hv-btn text-sm",
971
870
  onClick: () => props.onChangeLayout("single"),
972
871
  children: t("toolbar.layout.single", "Single")
973
872
  }
@@ -976,7 +875,7 @@ function Toolbar(props) {
976
875
  "button",
977
876
  {
978
877
  type: "button",
979
- className: props.layout === "side-by-side" ? "hv-btn hv-btn--active" : "hv-btn",
878
+ className: props.layout === "side-by-side" ? "hv-btn hv-btn--active text-sm" : "hv-btn text-sm",
980
879
  onClick: () => props.onChangeLayout("side-by-side"),
981
880
  children: t("toolbar.layout.two", "Two")
982
881
  }
@@ -998,18 +897,26 @@ function Toolbar(props) {
998
897
  "button",
999
898
  {
1000
899
  type: "button",
1001
- className: "hv-btn hv-btn--primary",
900
+ className: "hv-btn hv-btn--primary text-sm",
1002
901
  onClick: props.onSign,
1003
902
  disabled: props.signingDisabled,
1004
903
  children: t("toolbar.sign", "Sign Document")
1005
904
  }
1006
905
  ),
1007
- props.canExportPdf && /* @__PURE__ */ jsx8("button", { type: "button", className: "hv-btn", onClick: props.onExportPdf, children: t("toolbar.exportPdf", "Export as PDF") }),
906
+ props.canExportPdf && /* @__PURE__ */ jsx8(
907
+ "button",
908
+ {
909
+ type: "button",
910
+ className: "hv-btn text-sm",
911
+ onClick: props.onExportPdf,
912
+ children: t("toolbar.exportPdf", "Export as PDF")
913
+ }
914
+ ),
1008
915
  props.canSave && /* @__PURE__ */ jsx8(
1009
916
  "button",
1010
917
  {
1011
918
  type: "button",
1012
- className: "hv-btn hv-btn--primary",
919
+ className: "hv-btn hv-btn--primary text-sm",
1013
920
  onClick: props.onSave,
1014
921
  children: t("toolbar.save", "Save")
1015
922
  }
@@ -1025,8 +932,13 @@ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1025
932
  function DocumentViewer(props) {
1026
933
  const mode = props.mode ?? "view";
1027
934
  const theme = props.theme ?? "light";
1028
- const locale = useMemo6(() => ({ ...defaultLocale, ...props.locale ?? {} }), [props.locale]);
1029
- const [layout, setLayout] = useState6(props.defaultLayout ?? "single");
935
+ const locale = useMemo6(
936
+ () => ({ ...defaultLocale, ...props.locale ?? {} }),
937
+ [props.locale]
938
+ );
939
+ const [layout, setLayout] = useState6(
940
+ props.defaultLayout ?? "single"
941
+ );
1030
942
  const [showThumbnails, setShowThumbnails] = useState6(true);
1031
943
  const [showSignatures, setShowSignatures] = useState6(true);
1032
944
  const [headerFooterEnabled, setHeaderFooterEnabled] = useState6(true);
@@ -1037,10 +949,17 @@ function DocumentViewer(props) {
1037
949
  const [pageCount, setPageCount] = useState6(1);
1038
950
  const [currentPage, setCurrentPage] = useState6(1);
1039
951
  const [thumbs, setThumbs] = useState6([]);
1040
- const [localSignatures, setLocalSignatures] = useState6(props.signatures ?? []);
1041
- useEffect6(() => setLocalSignatures(props.signatures ?? []), [props.signatures]);
952
+ const [localSignatures, setLocalSignatures] = useState6(
953
+ props.signatures ?? []
954
+ );
955
+ useEffect6(
956
+ () => setLocalSignatures(props.signatures ?? []),
957
+ [props.signatures]
958
+ );
1042
959
  const [sigPlacements, setSigPlacements] = useState6([]);
1043
- const [armedSignatureUrl, setArmedSignatureUrl] = useState6(null);
960
+ const [armedSignatureUrl, setArmedSignatureUrl] = useState6(
961
+ null
962
+ );
1044
963
  const editorRef = useRef3(null);
1045
964
  useEffect6(() => {
1046
965
  let cancelled = false;
@@ -1054,7 +973,10 @@ function DocumentViewer(props) {
1054
973
  setArmedSignatureUrl(null);
1055
974
  if (mode === "create") {
1056
975
  const ft = props.fileType ?? "docx";
1057
- setResolved({ fileType: ft, fileName: props.fileName ?? `Untitled.${ft}` });
976
+ setResolved({
977
+ fileType: ft,
978
+ fileName: props.fileName ?? `Untitled.${ft}`
979
+ });
1058
980
  return;
1059
981
  }
1060
982
  try {
@@ -1068,7 +990,12 @@ function DocumentViewer(props) {
1068
990
  if (cancelled) {
1069
991
  return;
1070
992
  }
1071
- setResolved({ fileType: res.fileType, fileName: res.fileName, url: res.url, arrayBuffer: res.arrayBuffer });
993
+ setResolved({
994
+ fileType: res.fileType,
995
+ fileName: res.fileName,
996
+ url: res.url,
997
+ arrayBuffer: res.arrayBuffer
998
+ });
1072
999
  } catch (e) {
1073
1000
  if (cancelled) {
1074
1001
  return;
@@ -1079,7 +1006,14 @@ function DocumentViewer(props) {
1079
1006
  return () => {
1080
1007
  cancelled = true;
1081
1008
  };
1082
- }, [mode, props.fileUrl, props.base64, props.blob, props.fileName, props.fileType]);
1009
+ }, [
1010
+ mode,
1011
+ props.fileUrl,
1012
+ props.base64,
1013
+ props.blob,
1014
+ props.fileName,
1015
+ props.fileType
1016
+ ]);
1083
1017
  const thumbnails = useMemo6(() => {
1084
1018
  const n = Math.max(1, pageCount);
1085
1019
  return Array.from({ length: n }, (_, i) => ({
@@ -1105,7 +1039,10 @@ function DocumentViewer(props) {
1105
1039
  if (!armedSignatureUrl) {
1106
1040
  return;
1107
1041
  }
1108
- setSigPlacements((prev) => [...prev, { ...p, signatureImageUrl: armedSignatureUrl }]);
1042
+ setSigPlacements((prev) => [
1043
+ ...prev,
1044
+ { ...p, signatureImageUrl: armedSignatureUrl }
1045
+ ]);
1109
1046
  setArmedSignatureUrl(null);
1110
1047
  }
1111
1048
  async function handleSave(exportPdf) {
@@ -1117,7 +1054,11 @@ function DocumentViewer(props) {
1117
1054
  return;
1118
1055
  }
1119
1056
  const b64 = arrayBufferToBase642(resolved.arrayBuffer);
1120
- props.onSave?.(b64, { fileName: resolved.fileName, fileType: resolved.fileType, annotations: { sigPlacements } });
1057
+ props.onSave?.(b64, {
1058
+ fileName: resolved.fileName,
1059
+ fileType: resolved.fileType,
1060
+ annotations: { sigPlacements }
1061
+ });
1121
1062
  }
1122
1063
  const canSave = mode === "edit" || mode === "create";
1123
1064
  const canExportPdf = (mode === "edit" || mode === "create") && (resolved?.fileType === "docx" || resolved?.fileType === "md" || resolved?.fileType === "txt" || resolved?.fileType === "xlsx");
@@ -1174,10 +1115,16 @@ function DocumentViewer(props) {
1174
1115
  onCurrentPageChange: setCurrentPage,
1175
1116
  onPageCount: (n) => {
1176
1117
  setPageCount(n);
1177
- setThumbs((prev) => prev.length === n ? prev : Array.from({ length: n }, (_, i) => prev[i]));
1118
+ setThumbs(
1119
+ (prev) => prev.length === n ? prev : Array.from({ length: n }, (_, i) => prev[i])
1120
+ );
1178
1121
  },
1179
1122
  onThumbs: (t) => setThumbs(t),
1180
- signatureStamp: armedSignatureUrl ? { imageUrl: armedSignatureUrl, armed: true, onPlaced: placeSignature } : void 0
1123
+ signatureStamp: armedSignatureUrl ? {
1124
+ imageUrl: armedSignatureUrl,
1125
+ armed: true,
1126
+ onPlaced: placeSignature
1127
+ } : void 0
1181
1128
  }
1182
1129
  ) : null,
1183
1130
  resolved.fileType === "docx" || resolved.fileType === "md" || resolved.fileType === "txt" ? /* @__PURE__ */ jsx9(
@@ -1196,7 +1143,9 @@ function DocumentViewer(props) {
1196
1143
  signaturePlacements: sigPlacements,
1197
1144
  onPageCount: (n) => {
1198
1145
  setPageCount(n);
1199
- setThumbs((prev) => prev.length === n ? prev : Array.from({ length: n }, (_, i) => prev[i]));
1146
+ setThumbs(
1147
+ (prev) => prev.length === n ? prev : Array.from({ length: n }, (_, i) => prev[i])
1148
+ );
1200
1149
  },
1201
1150
  onSave: (b64, meta) => props.onSave?.(b64, meta),
1202
1151
  armedSignatureUrl,
@@ -1223,12 +1172,21 @@ function DocumentViewer(props) {
1223
1172
  onCurrentPageChange: setCurrentPage,
1224
1173
  onSlideCount: (n) => {
1225
1174
  setPageCount(n);
1226
- setThumbs((prev) => prev.length === n ? prev : Array.from({ length: n }, (_, i) => prev[i]));
1175
+ setThumbs(
1176
+ (prev) => prev.length === n ? prev : Array.from({ length: n }, (_, i) => prev[i])
1177
+ );
1227
1178
  },
1228
1179
  onThumbs: (t) => setThumbs(t)
1229
1180
  }
1230
1181
  ) : null,
1231
- resolved.fileType === "png" || resolved.fileType === "jpg" || resolved.fileType === "svg" ? /* @__PURE__ */ jsx9(ImageRenderer, { arrayBuffer: resolved.arrayBuffer, fileType: resolved.fileType, fileName: resolved.fileName }) : null
1182
+ resolved.fileType === "png" || resolved.fileType === "jpg" || resolved.fileType === "svg" ? /* @__PURE__ */ jsx9(
1183
+ ImageRenderer,
1184
+ {
1185
+ arrayBuffer: resolved.arrayBuffer,
1186
+ fileType: resolved.fileType,
1187
+ fileName: resolved.fileName
1188
+ }
1189
+ ) : null
1232
1190
  ] }),
1233
1191
  mode !== "create" && localSignatures.length ? /* @__PURE__ */ jsx9(
1234
1192
  SignaturePanel,
package/dist/styles.css CHANGED
@@ -1,6 +1,6 @@
1
1
  /* Focus ring for accessibility */
2
2
  .hv-btn:focus, .hv-thumb:focus, .hv-modal-close:focus {
3
- outline: 2px solid #6366f1;
3
+ outline: 1px solid #6366f1;
4
4
  outline-offset: 2px;
5
5
  z-index: 2;
6
6
  }
@@ -134,7 +134,7 @@
134
134
 
135
135
  /* Thumbnails Sidebar Modern Redesign */
136
136
  .hv-thumbs {
137
- width: 240px;
137
+ width: 200px;
138
138
  flex: 0 0 auto;
139
139
  background: var(--hv-panel);
140
140
  border-right: 1px solid var(--hv-border);
@@ -477,3 +477,67 @@
477
477
  .hv-grid td,.hv-grid th{border:1px solid rgba(0,0,0,.12);padding:6px 8px;min-width:90px}
478
478
  .hv-grid th{background:#f1f5f9;font-weight:600;text-align:left}
479
479
  .hv-slide{background:#fff;border-radius:14px;box-shadow:0 10px 30px rgba(0,0,0,.25);padding:18px;max-width:480px;min-height:270px}
480
+
481
+
482
+ .hv-root {
483
+ height: 100%;
484
+ display: flex;
485
+ flex-direction: column;
486
+ }
487
+
488
+ .hv-toolbar {
489
+ position: sticky;
490
+ top: 0;
491
+ z-index: 20;
492
+ display: flex;
493
+ gap: 8px;
494
+ padding: 8px 12px;
495
+ background: #ffffff;
496
+ border-bottom: 1px solid #e5e7eb;
497
+ }
498
+
499
+ .hv-toolbar button {
500
+ padding: 6px 10px;
501
+ border-radius: 6px;
502
+ border: 1px solid #d1d5db;
503
+ background: #fff;
504
+ cursor: pointer;
505
+ }
506
+
507
+ .hv-toolbar button:disabled {
508
+ opacity: 0.5;
509
+ cursor: not-allowed;
510
+ }
511
+
512
+ .hv-hint {
513
+ margin-left: auto;
514
+ font-size: 13px;
515
+ color: #2563eb;
516
+ }
517
+
518
+ .hv-scroll {
519
+ flex: 1;
520
+ overflow: auto;
521
+ padding: 32px 0;
522
+ }
523
+
524
+ .hv-pageStage {
525
+ width: 794px;
526
+ margin: 0 auto;
527
+ background: white;
528
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.08);
529
+ padding: 72px;
530
+ min-height: 1122px;
531
+ }
532
+
533
+ .hv-editor {
534
+ outline: none;
535
+ min-height: 900px;
536
+ font-family: 'Inter', system-ui, sans-serif;
537
+ font-size: 15px;
538
+ line-height: 1.7;
539
+ }
540
+
541
+ .hv-editor.ro {
542
+ cursor: default;
543
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerohive/hive-viewer",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",