@matangot/react-pdf-viewer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2148 @@
1
+ // src/components/Root.tsx
2
+ import { useRef as useRef3 } from "react";
3
+
4
+ // src/context.tsx
5
+ import {
6
+ createContext,
7
+ useContext,
8
+ useState as useState2,
9
+ useCallback,
10
+ useMemo,
11
+ useRef as useRef2
12
+ } from "react";
13
+
14
+ // src/hooks/use-pdf-document.ts
15
+ import { useState, useEffect, useRef } from "react";
16
+ import { getDocument } from "pdfjs-dist";
17
+ function usePdfDocument(src, onDocumentLoad) {
18
+ const [document, setDocument] = useState(null);
19
+ const [isLoading, setIsLoading] = useState(true);
20
+ const [error, setError] = useState(null);
21
+ const prevSrcRef = useRef(null);
22
+ const objectUrlRef = useRef(null);
23
+ useEffect(() => {
24
+ if (src === prevSrcRef.current) return;
25
+ prevSrcRef.current = src;
26
+ if (objectUrlRef.current) {
27
+ URL.revokeObjectURL(objectUrlRef.current);
28
+ objectUrlRef.current = null;
29
+ }
30
+ let cancelled = false;
31
+ setIsLoading(true);
32
+ setError(null);
33
+ const init = getDocumentInit(src);
34
+ if (src instanceof File) {
35
+ objectUrlRef.current = init.url;
36
+ }
37
+ const loadingTask = getDocument(init);
38
+ loadingTask.promise.then(async (doc) => {
39
+ if (cancelled) {
40
+ doc.destroy();
41
+ return;
42
+ }
43
+ setDocument(doc);
44
+ setIsLoading(false);
45
+ const metadata = await doc.getMetadata();
46
+ const info = metadata.info;
47
+ onDocumentLoad?.({
48
+ numPages: doc.numPages,
49
+ title: info?.Title,
50
+ author: info?.Author
51
+ });
52
+ }).catch((err) => {
53
+ if (cancelled) return;
54
+ setError(err instanceof Error ? err : new Error(String(err)));
55
+ setIsLoading(false);
56
+ });
57
+ return () => {
58
+ cancelled = true;
59
+ if (objectUrlRef.current) {
60
+ URL.revokeObjectURL(objectUrlRef.current);
61
+ objectUrlRef.current = null;
62
+ }
63
+ };
64
+ }, [src]);
65
+ return { document, isLoading, error };
66
+ }
67
+ function getDocumentInit(src) {
68
+ if (typeof src === "string") {
69
+ return { url: src };
70
+ }
71
+ if (src instanceof File) {
72
+ return { url: URL.createObjectURL(src) };
73
+ }
74
+ return { data: src };
75
+ }
76
+
77
+ // src/constants.ts
78
+ var DEFAULT_ZOOM = 1;
79
+ var MIN_ZOOM = 0.25;
80
+ var MAX_ZOOM = 4;
81
+ var ZOOM_STEP = 0.25;
82
+ var VIRTUALIZATION_BUFFER = 2;
83
+ var THUMBNAIL_SCALE = 0.2;
84
+
85
+ // src/context.tsx
86
+ import { jsx } from "react/jsx-runtime";
87
+ var PdfViewerContext = createContext(null);
88
+ function usePdfViewerContext() {
89
+ const ctx = useContext(PdfViewerContext);
90
+ if (!ctx) {
91
+ throw new Error(
92
+ "usePdfViewerContext must be used within a PdfViewerProvider"
93
+ );
94
+ }
95
+ return ctx;
96
+ }
97
+ function PdfViewerProvider({
98
+ src,
99
+ defaultPage = 1,
100
+ defaultZoom = DEFAULT_ZOOM,
101
+ defaultCursorMode = "select",
102
+ defaultSidebarOpen = false,
103
+ onPageChange,
104
+ onDocumentLoad,
105
+ children
106
+ }) {
107
+ const { document, isLoading, error } = usePdfDocument(src, onDocumentLoad);
108
+ const totalPages = document?.numPages ?? 0;
109
+ const containerRef = useRef2(null);
110
+ const scrollToPageRef = useRef2(null);
111
+ const [currentPage, setCurrentPage] = useState2(defaultPage);
112
+ const isMobile = typeof window !== "undefined" && window.innerWidth <= 640;
113
+ const [zoomLevel, setZoomLevel] = useState2(
114
+ typeof defaultZoom === "number" ? defaultZoom : DEFAULT_ZOOM
115
+ );
116
+ const [zoomMode, setZoomMode] = useState2(
117
+ typeof defaultZoom === "string" ? defaultZoom : isMobile ? "fit-width" : null
118
+ );
119
+ const [rotation, setRotation] = useState2(0);
120
+ const [isThumbnailsOpen, setIsThumbnailsOpen] = useState2(defaultSidebarOpen);
121
+ const [searchQuery, setSearchQuery] = useState2("");
122
+ const [searchMatches, setSearchMatches] = useState2([]);
123
+ const [currentMatchIndex, setCurrentMatchIndex] = useState2(-1);
124
+ const [cursorMode, setCursorModeState] = useState2(defaultCursorMode);
125
+ const [viewMode, setViewModeState] = useState2("single");
126
+ const [scrollMode, setScrollModeState] = useState2("vertical");
127
+ const [isDocPropertiesOpen, setIsDocPropertiesOpen] = useState2(false);
128
+ const [docProperties, setDocProperties] = useState2(null);
129
+ const [isPrinting, setIsPrinting] = useState2(false);
130
+ const currentPageRef = useRef2(defaultPage);
131
+ const _setCurrentPage = useCallback(
132
+ (page) => {
133
+ const clamped = Math.max(1, Math.min(page, totalPages));
134
+ if (currentPageRef.current === clamped) return;
135
+ currentPageRef.current = clamped;
136
+ setCurrentPage(clamped);
137
+ onPageChange?.(clamped);
138
+ },
139
+ [totalPages, onPageChange]
140
+ );
141
+ const goToPage = useCallback(
142
+ (page) => {
143
+ const clamped = Math.max(1, Math.min(page, totalPages));
144
+ currentPageRef.current = clamped;
145
+ setCurrentPage(clamped);
146
+ onPageChange?.(clamped);
147
+ scrollToPageRef.current?.(clamped);
148
+ },
149
+ [totalPages, onPageChange]
150
+ );
151
+ const nextPage = useCallback(() => {
152
+ goToPage(currentPageRef.current + 1);
153
+ }, [goToPage]);
154
+ const prevPage = useCallback(() => {
155
+ goToPage(currentPageRef.current - 1);
156
+ }, [goToPage]);
157
+ const zoomIn = useCallback(() => {
158
+ setZoomLevel((prev) => Math.min(prev + ZOOM_STEP, MAX_ZOOM));
159
+ setZoomMode(null);
160
+ }, []);
161
+ const zoomOut = useCallback(() => {
162
+ setZoomLevel((prev) => Math.max(prev - ZOOM_STEP, MIN_ZOOM));
163
+ setZoomMode(null);
164
+ }, []);
165
+ const zoomTo = useCallback((value2) => {
166
+ if (typeof value2 === "number") {
167
+ setZoomLevel(Math.max(MIN_ZOOM, Math.min(value2, MAX_ZOOM)));
168
+ setZoomMode(null);
169
+ } else {
170
+ setZoomMode(value2);
171
+ }
172
+ }, []);
173
+ const _setZoomLevel = useCallback((level) => {
174
+ setZoomLevel(Math.max(MIN_ZOOM, Math.min(level, MAX_ZOOM)));
175
+ }, []);
176
+ const rotate = useCallback((degrees = 90) => {
177
+ setRotation((prev) => (prev + degrees) % 360);
178
+ }, []);
179
+ const toggleThumbnails = useCallback(() => {
180
+ setIsThumbnailsOpen((prev) => !prev);
181
+ }, []);
182
+ const search = useCallback(async (query) => {
183
+ setSearchQuery(query);
184
+ if (!query || !document) {
185
+ setSearchMatches([]);
186
+ setCurrentMatchIndex(-1);
187
+ return;
188
+ }
189
+ const matches = [];
190
+ const lowerQuery = query.toLowerCase();
191
+ for (let i = 1; i <= document.numPages; i++) {
192
+ const page = await document.getPage(i);
193
+ const textContent = await page.getTextContent();
194
+ const pageText = textContent.items.map((item) => item.str).join(" ").toLowerCase();
195
+ let startIndex = 0;
196
+ let matchIdx = 0;
197
+ while ((startIndex = pageText.indexOf(lowerQuery, startIndex)) !== -1) {
198
+ matches.push({ pageIndex: i - 1, matchIndex: matchIdx++ });
199
+ startIndex += lowerQuery.length;
200
+ }
201
+ }
202
+ setSearchMatches(matches);
203
+ setCurrentMatchIndex(matches.length > 0 ? 0 : -1);
204
+ }, [document]);
205
+ const navigateToMatch = useCallback((matchIndex) => {
206
+ if (matchIndex < 0 || matchIndex >= searchMatches.length) return;
207
+ const match = searchMatches[matchIndex];
208
+ goToPage(match.pageIndex + 1);
209
+ requestAnimationFrame(() => {
210
+ requestAnimationFrame(() => {
211
+ const el = containerRef?.current?.querySelector(".pdf-viewer__search-hit--current");
212
+ el?.scrollIntoView({ block: "center", behavior: "instant" });
213
+ });
214
+ });
215
+ }, [searchMatches, goToPage, containerRef]);
216
+ const nextMatch = useCallback(() => {
217
+ if (searchMatches.length === 0) return;
218
+ setCurrentMatchIndex((prev) => {
219
+ const next = (prev + 1) % searchMatches.length;
220
+ navigateToMatch(next);
221
+ return next;
222
+ });
223
+ }, [searchMatches.length, navigateToMatch]);
224
+ const prevMatch = useCallback(() => {
225
+ if (searchMatches.length === 0) return;
226
+ setCurrentMatchIndex((prev) => {
227
+ const next = (prev - 1 + searchMatches.length) % searchMatches.length;
228
+ navigateToMatch(next);
229
+ return next;
230
+ });
231
+ }, [searchMatches.length, navigateToMatch]);
232
+ const clearSearch = useCallback(() => {
233
+ setSearchQuery("");
234
+ setSearchMatches([]);
235
+ setCurrentMatchIndex(-1);
236
+ }, []);
237
+ const download = useCallback(
238
+ async (fileName) => {
239
+ if (!document) return;
240
+ const data = await document.getData();
241
+ const blob = new Blob([data], { type: "application/pdf" });
242
+ const url = URL.createObjectURL(blob);
243
+ const a = window.document.createElement("a");
244
+ a.href = url;
245
+ a.download = fileName ?? "document.pdf";
246
+ window.document.body.appendChild(a);
247
+ a.click();
248
+ window.document.body.removeChild(a);
249
+ URL.revokeObjectURL(url);
250
+ },
251
+ [document]
252
+ );
253
+ const print = useCallback(async () => {
254
+ if (!document) return;
255
+ const total = document.numPages;
256
+ setIsPrinting(true);
257
+ await new Promise((resolve) => {
258
+ const checkInterval = setInterval(() => {
259
+ const container2 = containerRef.current;
260
+ if (!container2) return;
261
+ const canvases2 = container2.querySelectorAll(".pdf-viewer__page-canvas");
262
+ let rendered = 0;
263
+ canvases2.forEach((c) => {
264
+ const canvas = c;
265
+ if (canvas.width > 0 && canvas.height > 0) rendered++;
266
+ });
267
+ if (rendered >= total) {
268
+ clearInterval(checkInterval);
269
+ resolve();
270
+ }
271
+ }, 100);
272
+ setTimeout(() => {
273
+ clearInterval(checkInterval);
274
+ resolve();
275
+ }, 1e4);
276
+ });
277
+ const container = containerRef.current;
278
+ if (!container) {
279
+ setIsPrinting(false);
280
+ return;
281
+ }
282
+ const canvases = Array.from(container.querySelectorAll(".pdf-viewer__page-canvas"));
283
+ const iframe = window.document.createElement("iframe");
284
+ iframe.style.position = "fixed";
285
+ iframe.style.top = "-10000px";
286
+ iframe.style.left = "-10000px";
287
+ iframe.style.width = "0";
288
+ iframe.style.height = "0";
289
+ window.document.body.appendChild(iframe);
290
+ const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
291
+ if (!iframeDoc) {
292
+ setIsPrinting(false);
293
+ return;
294
+ }
295
+ iframeDoc.open();
296
+ iframeDoc.write(`<!DOCTYPE html><html><head><style>
297
+ * { margin: 0; padding: 0; }
298
+ body { background: white; }
299
+ img { display: block; width: 100%; height: auto; page-break-after: always; }
300
+ img:last-child { page-break-after: auto; }
301
+ </style></head><body></body></html>`);
302
+ iframeDoc.close();
303
+ for (const canvas of canvases) {
304
+ const img = iframeDoc.createElement("img");
305
+ img.src = canvas.toDataURL("image/png");
306
+ iframeDoc.body.appendChild(img);
307
+ }
308
+ await new Promise((resolve) => {
309
+ const imgs = iframeDoc.querySelectorAll("img");
310
+ let loaded = 0;
311
+ const onLoad = () => {
312
+ loaded++;
313
+ if (loaded >= imgs.length) resolve();
314
+ };
315
+ imgs.forEach((img) => {
316
+ if (img.complete) {
317
+ loaded++;
318
+ } else {
319
+ img.onload = onLoad;
320
+ }
321
+ });
322
+ if (loaded >= imgs.length) resolve();
323
+ });
324
+ iframe.contentWindow?.print();
325
+ setTimeout(() => {
326
+ window.document.body.removeChild(iframe);
327
+ setIsPrinting(false);
328
+ }, 500);
329
+ }, [document]);
330
+ const setCursorMode = useCallback((mode) => {
331
+ setCursorModeState(mode);
332
+ }, []);
333
+ const toggleCursorMode = useCallback(() => {
334
+ setCursorModeState((prev) => prev === "select" ? "hand" : "select");
335
+ }, []);
336
+ const goToFirstPage = useCallback(() => {
337
+ goToPage(1);
338
+ }, [goToPage]);
339
+ const goToLastPage = useCallback(() => {
340
+ goToPage(totalPages);
341
+ }, [goToPage, totalPages]);
342
+ const setViewMode = useCallback((mode) => {
343
+ setViewModeState(mode);
344
+ if (mode === "dual") {
345
+ setScrollModeState((prev) => prev === "horizontal" ? "vertical" : prev);
346
+ }
347
+ }, []);
348
+ const setScrollMode = useCallback((mode) => {
349
+ setScrollModeState(mode);
350
+ if (mode === "horizontal") {
351
+ setViewModeState("single");
352
+ }
353
+ }, []);
354
+ const layoutMode = scrollMode === "horizontal" ? "horizontal" : viewMode === "dual" ? "dual" : "single";
355
+ const setLayoutMode = useCallback((mode) => {
356
+ if (mode === "horizontal") {
357
+ setScrollModeState("horizontal");
358
+ setViewModeState("single");
359
+ } else if (mode === "dual") {
360
+ setViewModeState("dual");
361
+ setScrollModeState((prev) => prev === "horizontal" ? "vertical" : prev);
362
+ } else {
363
+ setViewModeState("single");
364
+ }
365
+ }, []);
366
+ const openDocProperties = useCallback(async () => {
367
+ if (!document) return;
368
+ try {
369
+ const metadata = await document.getMetadata();
370
+ const info = metadata?.info ?? {};
371
+ const page = await document.getPage(1);
372
+ const viewport = page.getViewport({ scale: 1 });
373
+ const widthIn = (viewport.width / 72).toFixed(1);
374
+ const heightIn = (viewport.height / 72).toFixed(1);
375
+ const widthMm = (viewport.width / 72 * 25.4).toFixed(0);
376
+ const heightMm = (viewport.height / 72 * 25.4).toFixed(0);
377
+ const formatDate = (raw) => {
378
+ if (!raw) return "-";
379
+ const match = raw.match(/D:(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/);
380
+ if (match) {
381
+ const [, y, m, d, h, min, s] = match;
382
+ return (/* @__PURE__ */ new Date(`${y}-${m}-${d}T${h}:${min}:${s}`)).toLocaleString();
383
+ }
384
+ return raw;
385
+ };
386
+ const formatFileSize = (bytes) => {
387
+ if (bytes < 1024) return `${bytes} B`;
388
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
389
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
390
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
391
+ };
392
+ const data = await document.getData();
393
+ let fileName = "-";
394
+ if (typeof src === "string") {
395
+ const urlPath = src.split("?")[0].split("#")[0];
396
+ const lastSegment = urlPath.split("/").pop();
397
+ if (lastSegment) fileName = decodeURIComponent(lastSegment);
398
+ } else if (src instanceof File) {
399
+ fileName = src.name;
400
+ }
401
+ setDocProperties({
402
+ fileName,
403
+ fileSize: formatFileSize(data.byteLength),
404
+ title: info.Title || "-",
405
+ author: info.Author || "-",
406
+ subject: info.Subject || "-",
407
+ creator: info.Creator || "-",
408
+ producer: info.Producer || "-",
409
+ creationDate: formatDate(info.CreationDate),
410
+ modificationDate: formatDate(info.ModDate),
411
+ pageCount: document.numPages,
412
+ pageSize: `${widthIn} \xD7 ${heightIn} in (${widthMm} \xD7 ${heightMm} mm)`
413
+ });
414
+ setIsDocPropertiesOpen(true);
415
+ } catch {
416
+ }
417
+ }, [document, src]);
418
+ const closeDocProperties = useCallback(() => {
419
+ setIsDocPropertiesOpen(false);
420
+ }, []);
421
+ const toggleFullScreen = useCallback(() => {
422
+ if (!window.document.fullscreenElement) {
423
+ const el = containerRef.current?.closest(".pdf-viewer") ?? window.document.documentElement;
424
+ el.requestFullscreen?.();
425
+ } else {
426
+ window.document.exitFullscreen?.();
427
+ }
428
+ }, []);
429
+ const value = useMemo(
430
+ () => ({
431
+ document,
432
+ isLoading,
433
+ error,
434
+ currentPage,
435
+ totalPages,
436
+ zoomLevel,
437
+ zoomMode,
438
+ rotation,
439
+ isThumbnailsOpen,
440
+ searchQuery,
441
+ searchMatches,
442
+ currentMatchIndex,
443
+ isPrinting,
444
+ containerRef,
445
+ scrollToPageRef,
446
+ _setCurrentPage,
447
+ goToPage,
448
+ nextPage,
449
+ prevPage,
450
+ zoomIn,
451
+ zoomOut,
452
+ zoomTo,
453
+ _setZoomLevel,
454
+ rotate,
455
+ toggleThumbnails,
456
+ search,
457
+ nextMatch,
458
+ prevMatch,
459
+ clearSearch,
460
+ download,
461
+ print,
462
+ toggleFullScreen,
463
+ cursorMode,
464
+ setCursorMode,
465
+ toggleCursorMode,
466
+ viewMode,
467
+ scrollMode,
468
+ layoutMode,
469
+ setViewMode,
470
+ setScrollMode,
471
+ setLayoutMode,
472
+ isDocPropertiesOpen,
473
+ docProperties,
474
+ goToFirstPage,
475
+ goToLastPage,
476
+ openDocProperties,
477
+ closeDocProperties
478
+ }),
479
+ [
480
+ document,
481
+ isLoading,
482
+ error,
483
+ currentPage,
484
+ totalPages,
485
+ zoomLevel,
486
+ zoomMode,
487
+ rotation,
488
+ isThumbnailsOpen,
489
+ searchQuery,
490
+ searchMatches,
491
+ currentMatchIndex,
492
+ isPrinting,
493
+ _setCurrentPage,
494
+ goToPage,
495
+ nextPage,
496
+ prevPage,
497
+ zoomIn,
498
+ zoomOut,
499
+ zoomTo,
500
+ _setZoomLevel,
501
+ rotate,
502
+ toggleThumbnails,
503
+ search,
504
+ nextMatch,
505
+ prevMatch,
506
+ clearSearch,
507
+ download,
508
+ print,
509
+ toggleFullScreen,
510
+ cursorMode,
511
+ setCursorMode,
512
+ toggleCursorMode,
513
+ viewMode,
514
+ scrollMode,
515
+ layoutMode,
516
+ setViewMode,
517
+ setScrollMode,
518
+ setLayoutMode,
519
+ isDocPropertiesOpen,
520
+ docProperties,
521
+ goToFirstPage,
522
+ goToLastPage,
523
+ openDocProperties,
524
+ closeDocProperties
525
+ ]
526
+ );
527
+ return /* @__PURE__ */ jsx(PdfViewerContext.Provider, { value, children });
528
+ }
529
+
530
+ // src/hooks/use-keyboard-shortcuts.ts
531
+ import { useEffect as useEffect2 } from "react";
532
+ function useKeyboardShortcuts(containerRef, ctx) {
533
+ useEffect2(() => {
534
+ const el = containerRef.current;
535
+ if (!el) return;
536
+ const handleKeyDown = (e) => {
537
+ const target = e.target;
538
+ const isInput = target.tagName === "INPUT" || target.tagName === "TEXTAREA";
539
+ if (!isInput) {
540
+ if (e.key === "ArrowRight" || e.key === "PageDown") {
541
+ e.preventDefault();
542
+ ctx.nextPage();
543
+ }
544
+ if (e.key === "ArrowLeft" || e.key === "PageUp") {
545
+ e.preventDefault();
546
+ ctx.prevPage();
547
+ }
548
+ }
549
+ if (!isInput && e.key === "h") {
550
+ e.preventDefault();
551
+ ctx.toggleCursorMode();
552
+ }
553
+ if (e.ctrlKey || e.metaKey) {
554
+ if (e.key === "=" || e.key === "+") {
555
+ e.preventDefault();
556
+ ctx.zoomIn();
557
+ }
558
+ if (e.key === "-") {
559
+ e.preventDefault();
560
+ ctx.zoomOut();
561
+ }
562
+ if (e.key === "0") {
563
+ e.preventDefault();
564
+ ctx.zoomTo(1);
565
+ }
566
+ if (e.key === "f") {
567
+ e.preventDefault();
568
+ const searchInput = el.querySelector(".pdf-viewer__search-input");
569
+ searchInput?.focus();
570
+ }
571
+ }
572
+ };
573
+ el.addEventListener("keydown", handleKeyDown);
574
+ return () => el.removeEventListener("keydown", handleKeyDown);
575
+ }, [containerRef, ctx]);
576
+ }
577
+
578
+ // src/components/Root.tsx
579
+ import { jsx as jsx2 } from "react/jsx-runtime";
580
+ function RootInner({ className, theme, children }) {
581
+ const containerRef = useRef3(null);
582
+ const ctx = usePdfViewerContext();
583
+ useKeyboardShortcuts(containerRef, ctx);
584
+ const classNames = [
585
+ "pdf-viewer",
586
+ ctx.isLoading && "pdf-viewer--loading",
587
+ ctx.error && "pdf-viewer--error",
588
+ className
589
+ ].filter(Boolean).join(" ");
590
+ return /* @__PURE__ */ jsx2(
591
+ "div",
592
+ {
593
+ ref: containerRef,
594
+ className: classNames,
595
+ "data-theme": theme,
596
+ tabIndex: -1,
597
+ children: ctx.error ? /* @__PURE__ */ jsx2("div", { className: "pdf-viewer__error", children: /* @__PURE__ */ jsx2("p", { children: ctx.error.message }) }) : children
598
+ }
599
+ );
600
+ }
601
+ function Root({
602
+ src,
603
+ defaultPage,
604
+ defaultZoom,
605
+ defaultCursorMode,
606
+ defaultSidebarOpen,
607
+ theme = "system",
608
+ onPageChange,
609
+ onDocumentLoad,
610
+ className,
611
+ children
612
+ }) {
613
+ return /* @__PURE__ */ jsx2(
614
+ PdfViewerProvider,
615
+ {
616
+ src,
617
+ defaultPage,
618
+ defaultZoom,
619
+ defaultCursorMode,
620
+ defaultSidebarOpen,
621
+ onPageChange,
622
+ onDocumentLoad,
623
+ children: /* @__PURE__ */ jsx2(RootInner, { className, theme, children })
624
+ }
625
+ );
626
+ }
627
+
628
+ // src/components/Toolbar.tsx
629
+ import { jsx as jsx3 } from "react/jsx-runtime";
630
+ function Toolbar({ className, children }) {
631
+ const classNames = ["pdf-viewer__toolbar", className].filter(Boolean).join(" ");
632
+ return /* @__PURE__ */ jsx3("div", { className: classNames, role: "toolbar", children });
633
+ }
634
+
635
+ // src/components/Navigation.tsx
636
+ import { useState as useState3, useEffect as useEffect3, useCallback as useCallback2 } from "react";
637
+
638
+ // src/icons.tsx
639
+ import { jsx as jsx4, jsxs } from "react/jsx-runtime";
640
+ var defaults = {
641
+ width: 18,
642
+ height: 18,
643
+ viewBox: "0 0 24 24",
644
+ fill: "none",
645
+ stroke: "currentColor",
646
+ strokeWidth: 2,
647
+ strokeLinecap: "round",
648
+ strokeLinejoin: "round"
649
+ };
650
+ function Icon(props) {
651
+ const { children, ...rest } = props;
652
+ return /* @__PURE__ */ jsx4("svg", { ...defaults, ...rest, children });
653
+ }
654
+ function ChevronLeft(props) {
655
+ return /* @__PURE__ */ jsx4(Icon, { ...props, children: /* @__PURE__ */ jsx4("path", { d: "m15 18-6-6 6-6" }) });
656
+ }
657
+ function ChevronRight(props) {
658
+ return /* @__PURE__ */ jsx4(Icon, { ...props, children: /* @__PURE__ */ jsx4("path", { d: "m9 18 6-6-6-6" }) });
659
+ }
660
+ function ChevronUp(props) {
661
+ return /* @__PURE__ */ jsx4(Icon, { ...props, children: /* @__PURE__ */ jsx4("path", { d: "m18 15-6-6-6 6" }) });
662
+ }
663
+ function ChevronDown(props) {
664
+ return /* @__PURE__ */ jsx4(Icon, { ...props, children: /* @__PURE__ */ jsx4("path", { d: "m6 9 6 6 6-6" }) });
665
+ }
666
+ function Minus(props) {
667
+ return /* @__PURE__ */ jsx4(Icon, { ...props, children: /* @__PURE__ */ jsx4("path", { d: "M5 12h14" }) });
668
+ }
669
+ function Plus(props) {
670
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
671
+ /* @__PURE__ */ jsx4("path", { d: "M5 12h14" }),
672
+ /* @__PURE__ */ jsx4("path", { d: "M12 5v14" })
673
+ ] });
674
+ }
675
+ function ArrowLeftRight(props) {
676
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
677
+ /* @__PURE__ */ jsx4("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
678
+ /* @__PURE__ */ jsx4("path", { d: "M21 8V5a2 2 0 0 0-2-2h-3" }),
679
+ /* @__PURE__ */ jsx4("path", { d: "M3 16v3a2 2 0 0 0 2 2h3" }),
680
+ /* @__PURE__ */ jsx4("path", { d: "M16 21h3a2 2 0 0 0 2-2v-3" }),
681
+ /* @__PURE__ */ jsx4("path", { d: "M7 12h10" })
682
+ ] });
683
+ }
684
+ function RectangleVertical(props) {
685
+ return /* @__PURE__ */ jsx4(Icon, { ...props, children: /* @__PURE__ */ jsx4("rect", { width: "12", height: "18", x: "6", y: "3", rx: "2" }) });
686
+ }
687
+ function SearchIcon(props) {
688
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
689
+ /* @__PURE__ */ jsx4("circle", { cx: "11", cy: "11", r: "8" }),
690
+ /* @__PURE__ */ jsx4("path", { d: "m21 21-4.3-4.3" })
691
+ ] });
692
+ }
693
+ function X(props) {
694
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
695
+ /* @__PURE__ */ jsx4("path", { d: "M18 6 6 18" }),
696
+ /* @__PURE__ */ jsx4("path", { d: "m6 6 12 12" })
697
+ ] });
698
+ }
699
+ function RotateCw(props) {
700
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
701
+ /* @__PURE__ */ jsx4("path", { d: "M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" }),
702
+ /* @__PURE__ */ jsx4("path", { d: "M21 3v5h-5" })
703
+ ] });
704
+ }
705
+ function RotateCcw(props) {
706
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
707
+ /* @__PURE__ */ jsx4("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }),
708
+ /* @__PURE__ */ jsx4("path", { d: "M3 3v5h5" })
709
+ ] });
710
+ }
711
+ function Download(props) {
712
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
713
+ /* @__PURE__ */ jsx4("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
714
+ /* @__PURE__ */ jsx4("polyline", { points: "7 10 12 15 17 10" }),
715
+ /* @__PURE__ */ jsx4("line", { x1: "12", x2: "12", y1: "15", y2: "3" })
716
+ ] });
717
+ }
718
+ function Printer(props) {
719
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
720
+ /* @__PURE__ */ jsx4("path", { d: "M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2" }),
721
+ /* @__PURE__ */ jsx4("path", { d: "M6 9V3h12v6" }),
722
+ /* @__PURE__ */ jsx4("rect", { width: "12", height: "8", x: "6", y: "14" })
723
+ ] });
724
+ }
725
+ function Maximize(props) {
726
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
727
+ /* @__PURE__ */ jsx4("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
728
+ /* @__PURE__ */ jsx4("path", { d: "M21 8V5a2 2 0 0 0-2-2h-3" }),
729
+ /* @__PURE__ */ jsx4("path", { d: "M3 16v3a2 2 0 0 0 2 2h3" }),
730
+ /* @__PURE__ */ jsx4("path", { d: "M16 21h3a2 2 0 0 0 2-2v-3" })
731
+ ] });
732
+ }
733
+ function Expand(props) {
734
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
735
+ /* @__PURE__ */ jsx4("path", { d: "m21 21-6-6m6 6v-4.8m0 4.8h-4.8" }),
736
+ /* @__PURE__ */ jsx4("path", { d: "M3 16.2V21m0 0h4.8M3 21l6-6" }),
737
+ /* @__PURE__ */ jsx4("path", { d: "M21 7.8V3m0 0h-4.8M21 3l-6 6" }),
738
+ /* @__PURE__ */ jsx4("path", { d: "M3 7.8V3m0 0h4.8M3 3l6 6" })
739
+ ] });
740
+ }
741
+ function Hand(props) {
742
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
743
+ /* @__PURE__ */ jsx4("path", { d: "M18 11V6a2 2 0 0 0-2-2 2 2 0 0 0-2 2" }),
744
+ /* @__PURE__ */ jsx4("path", { d: "M14 10V4a2 2 0 0 0-2-2 2 2 0 0 0-2 2v2" }),
745
+ /* @__PURE__ */ jsx4("path", { d: "M10 10.5V6a2 2 0 0 0-2-2 2 2 0 0 0-2 2v8" }),
746
+ /* @__PURE__ */ jsx4("path", { d: "M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15" })
747
+ ] });
748
+ }
749
+ function MousePointer(props) {
750
+ return /* @__PURE__ */ jsx4(Icon, { ...props, children: /* @__PURE__ */ jsx4("path", { d: "M3 3l7.07 17 2.51-7.39L20 10.07z" }) });
751
+ }
752
+ function StickyNote(props) {
753
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
754
+ /* @__PURE__ */ jsx4("path", { d: "M15.5 3H5a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h14a2 2 0 0 0 2-2V8.5L15.5 3Z" }),
755
+ /* @__PURE__ */ jsx4("path", { d: "M14 3v4a2 2 0 0 0 2 2h4" })
756
+ ] });
757
+ }
758
+ function Columns2(props) {
759
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
760
+ /* @__PURE__ */ jsx4("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }),
761
+ /* @__PURE__ */ jsx4("path", { d: "M12 3v18" })
762
+ ] });
763
+ }
764
+ function GalleryVertical(props) {
765
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
766
+ /* @__PURE__ */ jsx4("path", { d: "M3 2h18" }),
767
+ /* @__PURE__ */ jsx4("rect", { width: "18", height: "12", x: "3", y: "6", rx: "2" }),
768
+ /* @__PURE__ */ jsx4("path", { d: "M3 22h18" })
769
+ ] });
770
+ }
771
+ function GalleryHorizontal(props) {
772
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
773
+ /* @__PURE__ */ jsx4("path", { d: "M2 3v18" }),
774
+ /* @__PURE__ */ jsx4("rect", { width: "12", height: "18", x: "6", y: "3", rx: "2" }),
775
+ /* @__PURE__ */ jsx4("path", { d: "M22 3v18" })
776
+ ] });
777
+ }
778
+ function GalleryThumbnails(props) {
779
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
780
+ /* @__PURE__ */ jsx4("rect", { width: "18", height: "14", x: "3", y: "2", rx: "2" }),
781
+ /* @__PURE__ */ jsx4("rect", { width: "4", height: "2", x: "4", y: "18", rx: "1" }),
782
+ /* @__PURE__ */ jsx4("rect", { width: "4", height: "2", x: "10", y: "18", rx: "1" }),
783
+ /* @__PURE__ */ jsx4("rect", { width: "4", height: "2", x: "16", y: "18", rx: "1" })
784
+ ] });
785
+ }
786
+ function Check(props) {
787
+ return /* @__PURE__ */ jsx4(Icon, { ...props, children: /* @__PURE__ */ jsx4("path", { d: "M20 6 9 17l-5-5" }) });
788
+ }
789
+ function EllipsisVertical(props) {
790
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
791
+ /* @__PURE__ */ jsx4("circle", { cx: "12", cy: "12", r: "1" }),
792
+ /* @__PURE__ */ jsx4("circle", { cx: "12", cy: "5", r: "1" }),
793
+ /* @__PURE__ */ jsx4("circle", { cx: "12", cy: "19", r: "1" })
794
+ ] });
795
+ }
796
+ function Info(props) {
797
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
798
+ /* @__PURE__ */ jsx4("circle", { cx: "12", cy: "12", r: "10" }),
799
+ /* @__PURE__ */ jsx4("path", { d: "M12 16v-4" }),
800
+ /* @__PURE__ */ jsx4("path", { d: "M12 8h.01" })
801
+ ] });
802
+ }
803
+ function PanelLeft(props) {
804
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
805
+ /* @__PURE__ */ jsx4("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }),
806
+ /* @__PURE__ */ jsx4("path", { d: "M9 3v18" })
807
+ ] });
808
+ }
809
+ function ArrowDownToLine(props) {
810
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
811
+ /* @__PURE__ */ jsx4("path", { d: "M12 17V3" }),
812
+ /* @__PURE__ */ jsx4("path", { d: "m6 11 6 6 6-6" }),
813
+ /* @__PURE__ */ jsx4("path", { d: "M19 21H5" })
814
+ ] });
815
+ }
816
+
817
+ // src/components/Navigation.tsx
818
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
819
+ function Navigation({ className }) {
820
+ const { currentPage, totalPages, goToPage, nextPage, prevPage } = usePdfViewerContext();
821
+ const [inputValue, setInputValue] = useState3(String(currentPage));
822
+ useEffect3(() => {
823
+ setInputValue(String(currentPage));
824
+ }, [currentPage]);
825
+ const handleInputChange = useCallback2((e) => {
826
+ setInputValue(e.target.value);
827
+ }, []);
828
+ const handleKeyDown = useCallback2(
829
+ (e) => {
830
+ if (e.key === "Enter") {
831
+ const page = parseInt(inputValue, 10);
832
+ if (!isNaN(page)) {
833
+ goToPage(page);
834
+ }
835
+ }
836
+ },
837
+ [inputValue, goToPage]
838
+ );
839
+ const handleBlur = useCallback2(() => {
840
+ setInputValue(String(currentPage));
841
+ }, [currentPage]);
842
+ const classNames = ["pdf-viewer__navigation", className].filter(Boolean).join(" ");
843
+ return /* @__PURE__ */ jsxs2("div", { className: classNames, children: [
844
+ /* @__PURE__ */ jsx5(
845
+ "button",
846
+ {
847
+ className: "pdf-viewer__btn",
848
+ onClick: prevPage,
849
+ disabled: currentPage <= 1,
850
+ title: "Previous page",
851
+ "aria-label": "Previous page",
852
+ children: /* @__PURE__ */ jsx5(ChevronLeft, {})
853
+ }
854
+ ),
855
+ /* @__PURE__ */ jsx5(
856
+ "input",
857
+ {
858
+ className: "pdf-viewer__page-input",
859
+ type: "text",
860
+ value: inputValue,
861
+ onChange: handleInputChange,
862
+ onKeyDown: handleKeyDown,
863
+ onBlur: handleBlur,
864
+ "aria-label": "Page number"
865
+ }
866
+ ),
867
+ /* @__PURE__ */ jsxs2("span", { className: "pdf-viewer__page-count", children: [
868
+ /* @__PURE__ */ jsxs2("span", { className: "pdf-viewer__page-count-desktop", children: [
869
+ "of ",
870
+ totalPages
871
+ ] }),
872
+ /* @__PURE__ */ jsxs2("span", { className: "pdf-viewer__page-count-mobile", children: [
873
+ "/",
874
+ totalPages
875
+ ] })
876
+ ] }),
877
+ /* @__PURE__ */ jsx5(
878
+ "button",
879
+ {
880
+ className: "pdf-viewer__btn",
881
+ onClick: nextPage,
882
+ disabled: currentPage >= totalPages,
883
+ title: "Next page",
884
+ "aria-label": "Next page",
885
+ children: /* @__PURE__ */ jsx5(ChevronRight, {})
886
+ }
887
+ )
888
+ ] });
889
+ }
890
+
891
+ // src/components/DropdownMenu.tsx
892
+ import { useState as useState4, useRef as useRef4, useEffect as useEffect4, useCallback as useCallback3 } from "react";
893
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
894
+ function DropdownMenu({ trigger, children, className, align = "right", title = "More actions", triggerClassName }) {
895
+ const [open, setOpen] = useState4(false);
896
+ const menuRef = useRef4(null);
897
+ const close = useCallback3(() => setOpen(false), []);
898
+ useEffect4(() => {
899
+ if (!open) return;
900
+ const handleClickOutside = (e) => {
901
+ if (menuRef.current && !menuRef.current.contains(e.target)) {
902
+ close();
903
+ }
904
+ };
905
+ const handleEscape = (e) => {
906
+ if (e.key === "Escape") close();
907
+ };
908
+ window.document.addEventListener("mousedown", handleClickOutside);
909
+ window.document.addEventListener("keydown", handleEscape);
910
+ return () => {
911
+ window.document.removeEventListener("mousedown", handleClickOutside);
912
+ window.document.removeEventListener("keydown", handleEscape);
913
+ };
914
+ }, [open, close]);
915
+ const classNames = ["pdf-viewer__dropdown", className].filter(Boolean).join(" ");
916
+ return /* @__PURE__ */ jsxs3("div", { ref: menuRef, className: classNames, children: [
917
+ /* @__PURE__ */ jsx6(
918
+ "button",
919
+ {
920
+ className: triggerClassName ?? "pdf-viewer__btn",
921
+ onClick: () => setOpen((prev) => !prev),
922
+ "aria-haspopup": "menu",
923
+ "aria-expanded": open,
924
+ title,
925
+ "aria-label": title,
926
+ children: trigger
927
+ }
928
+ ),
929
+ open && /* @__PURE__ */ jsx6(
930
+ "div",
931
+ {
932
+ className: `pdf-viewer__dropdown-content pdf-viewer__dropdown-content--${align}`,
933
+ role: "menu",
934
+ onClick: close,
935
+ children
936
+ }
937
+ )
938
+ ] });
939
+ }
940
+ function DropdownMenuItem({ icon, label, onClick, disabled, active }) {
941
+ const classNames = [
942
+ "pdf-viewer__dropdown-item",
943
+ active && "pdf-viewer__dropdown-item--active",
944
+ disabled && "pdf-viewer__dropdown-item--disabled"
945
+ ].filter(Boolean).join(" ");
946
+ return /* @__PURE__ */ jsxs3(
947
+ "button",
948
+ {
949
+ className: classNames,
950
+ role: "menuitem",
951
+ onClick: disabled ? void 0 : onClick,
952
+ disabled,
953
+ children: [
954
+ icon && /* @__PURE__ */ jsx6("span", { className: "pdf-viewer__dropdown-item-icon", children: icon }),
955
+ /* @__PURE__ */ jsx6("span", { className: "pdf-viewer__dropdown-item-label", children: label }),
956
+ active && /* @__PURE__ */ jsx6("span", { className: "pdf-viewer__dropdown-item-check", children: /* @__PURE__ */ jsx6(Check, {}) })
957
+ ]
958
+ }
959
+ );
960
+ }
961
+ function DropdownMenuSeparator() {
962
+ return /* @__PURE__ */ jsx6("div", { className: "pdf-viewer__dropdown-separator", role: "separator" });
963
+ }
964
+
965
+ // src/components/Zoom.tsx
966
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
967
+ var ZOOM_PRESETS = [0.5, 0.75, 1, 1.25, 1.5, 2, 3];
968
+ function Zoom({ className }) {
969
+ const { zoomLevel, zoomMode, zoomIn, zoomOut, zoomTo } = usePdfViewerContext();
970
+ const classNames = ["pdf-viewer__zoom", className].filter(Boolean).join(" ");
971
+ const displayPercent = `${Math.round(zoomLevel * 20) * 5}%`;
972
+ const isPresetActive = (preset) => !zoomMode && Math.abs(zoomLevel - preset) < 0.01;
973
+ return /* @__PURE__ */ jsxs4("div", { className: classNames, children: [
974
+ /* @__PURE__ */ jsx7(
975
+ "button",
976
+ {
977
+ className: "pdf-viewer__btn",
978
+ onClick: zoomOut,
979
+ title: "Zoom out",
980
+ "aria-label": "Zoom out",
981
+ children: /* @__PURE__ */ jsx7(Minus, {})
982
+ }
983
+ ),
984
+ /* @__PURE__ */ jsxs4(
985
+ DropdownMenu,
986
+ {
987
+ trigger: /* @__PURE__ */ jsx7("span", { className: "pdf-viewer__zoom-level-text", children: displayPercent }),
988
+ triggerClassName: "pdf-viewer__zoom-trigger",
989
+ title: "Zoom level",
990
+ align: "center",
991
+ className: "pdf-viewer__zoom-dropdown",
992
+ children: [
993
+ ZOOM_PRESETS.map((preset) => /* @__PURE__ */ jsx7(
994
+ DropdownMenuItem,
995
+ {
996
+ label: `${Math.round(preset * 100)}%`,
997
+ onClick: () => zoomTo(preset),
998
+ active: isPresetActive(preset)
999
+ },
1000
+ preset
1001
+ )),
1002
+ /* @__PURE__ */ jsx7(DropdownMenuSeparator, {}),
1003
+ /* @__PURE__ */ jsx7(
1004
+ DropdownMenuItem,
1005
+ {
1006
+ icon: /* @__PURE__ */ jsx7(ArrowLeftRight, {}),
1007
+ label: "Fit Width",
1008
+ onClick: () => zoomTo("fit-width"),
1009
+ active: zoomMode === "fit-width"
1010
+ }
1011
+ ),
1012
+ /* @__PURE__ */ jsx7(
1013
+ DropdownMenuItem,
1014
+ {
1015
+ icon: /* @__PURE__ */ jsx7(RectangleVertical, {}),
1016
+ label: "Fit Page",
1017
+ onClick: () => zoomTo("fit-page"),
1018
+ active: zoomMode === "fit-page"
1019
+ }
1020
+ )
1021
+ ]
1022
+ }
1023
+ ),
1024
+ /* @__PURE__ */ jsx7(
1025
+ "button",
1026
+ {
1027
+ className: "pdf-viewer__btn",
1028
+ onClick: zoomIn,
1029
+ title: "Zoom in",
1030
+ "aria-label": "Zoom in",
1031
+ children: /* @__PURE__ */ jsx7(Plus, {})
1032
+ }
1033
+ )
1034
+ ] });
1035
+ }
1036
+
1037
+ // src/components/Search.tsx
1038
+ import { useState as useState5, useCallback as useCallback4, useRef as useRef5, useEffect as useEffect5 } from "react";
1039
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1040
+ function Search({ className }) {
1041
+ const {
1042
+ searchQuery,
1043
+ searchMatches,
1044
+ currentMatchIndex,
1045
+ search,
1046
+ nextMatch,
1047
+ prevMatch,
1048
+ clearSearch
1049
+ } = usePdfViewerContext();
1050
+ const [open, setOpen] = useState5(false);
1051
+ const panelRef = useRef5(null);
1052
+ const inputRef = useRef5(null);
1053
+ const handleOpen = useCallback4(() => {
1054
+ setOpen(true);
1055
+ }, []);
1056
+ const handleClose = useCallback4(() => {
1057
+ setOpen(false);
1058
+ clearSearch();
1059
+ }, [clearSearch]);
1060
+ const inputCallbackRef = useCallback4((node) => {
1061
+ inputRef.current = node;
1062
+ node?.focus();
1063
+ }, []);
1064
+ useEffect5(() => {
1065
+ if (!open) return;
1066
+ const handleEscape = (e) => {
1067
+ if (e.key === "Escape") handleClose();
1068
+ };
1069
+ window.document.addEventListener("keydown", handleEscape);
1070
+ return () => window.document.removeEventListener("keydown", handleEscape);
1071
+ }, [open, handleClose]);
1072
+ const handleChange = useCallback4(
1073
+ (e) => {
1074
+ search(e.target.value);
1075
+ },
1076
+ [search]
1077
+ );
1078
+ const handleKeyDown = useCallback4(
1079
+ (e) => {
1080
+ if (e.key === "Enter") {
1081
+ if (e.shiftKey) {
1082
+ prevMatch();
1083
+ } else {
1084
+ nextMatch();
1085
+ }
1086
+ }
1087
+ },
1088
+ [nextMatch, prevMatch]
1089
+ );
1090
+ const classNames = ["pdf-viewer__search-toggle", className].filter(Boolean).join(" ");
1091
+ return /* @__PURE__ */ jsxs5("div", { className: classNames, ref: panelRef, children: [
1092
+ /* @__PURE__ */ jsx8(
1093
+ "button",
1094
+ {
1095
+ className: "pdf-viewer__btn",
1096
+ onClick: handleOpen,
1097
+ title: "Search",
1098
+ "aria-label": "Search in document",
1099
+ children: /* @__PURE__ */ jsx8(SearchIcon, {})
1100
+ }
1101
+ ),
1102
+ open && /* @__PURE__ */ jsxs5("div", { className: "pdf-viewer__search-panel", children: [
1103
+ /* @__PURE__ */ jsxs5("div", { className: "pdf-viewer__search-input-wrapper", children: [
1104
+ /* @__PURE__ */ jsx8("div", { className: "pdf-viewer__search-input-icon", children: /* @__PURE__ */ jsx8(SearchIcon, {}) }),
1105
+ /* @__PURE__ */ jsx8(
1106
+ "input",
1107
+ {
1108
+ ref: inputCallbackRef,
1109
+ className: "pdf-viewer__search-panel-input",
1110
+ type: "text",
1111
+ placeholder: "Search...",
1112
+ "aria-label": "Search in document",
1113
+ value: searchQuery,
1114
+ onChange: handleChange,
1115
+ onKeyDown: handleKeyDown
1116
+ }
1117
+ )
1118
+ ] }),
1119
+ /* @__PURE__ */ jsx8("span", { className: "pdf-viewer__search-count", children: searchQuery && searchMatches.length > 0 ? `${currentMatchIndex + 1}/${searchMatches.length}` : "0/0" }),
1120
+ /* @__PURE__ */ jsx8(
1121
+ "button",
1122
+ {
1123
+ className: "pdf-viewer__btn pdf-viewer__btn--small",
1124
+ onClick: prevMatch,
1125
+ disabled: searchMatches.length === 0,
1126
+ title: "Previous match",
1127
+ "aria-label": "Previous match",
1128
+ children: /* @__PURE__ */ jsx8(ChevronUp, {})
1129
+ }
1130
+ ),
1131
+ /* @__PURE__ */ jsx8(
1132
+ "button",
1133
+ {
1134
+ className: "pdf-viewer__btn pdf-viewer__btn--small",
1135
+ onClick: nextMatch,
1136
+ disabled: searchMatches.length === 0,
1137
+ title: "Next match",
1138
+ "aria-label": "Next match",
1139
+ children: /* @__PURE__ */ jsx8(ChevronDown, {})
1140
+ }
1141
+ ),
1142
+ /* @__PURE__ */ jsx8(
1143
+ "button",
1144
+ {
1145
+ className: "pdf-viewer__btn pdf-viewer__btn--small",
1146
+ onClick: handleClose,
1147
+ title: "Close search",
1148
+ "aria-label": "Close search",
1149
+ children: /* @__PURE__ */ jsx8(X, {})
1150
+ }
1151
+ )
1152
+ ] })
1153
+ ] });
1154
+ }
1155
+
1156
+ // src/components/Separator.tsx
1157
+ import { jsx as jsx9 } from "react/jsx-runtime";
1158
+ function Separator({ className }) {
1159
+ const classNames = ["pdf-viewer__separator", className].filter(Boolean).join(" ");
1160
+ return /* @__PURE__ */ jsx9("div", { className: classNames, role: "separator" });
1161
+ }
1162
+
1163
+ // src/components/Rotate.tsx
1164
+ import { jsx as jsx10 } from "react/jsx-runtime";
1165
+ function Rotate({ className }) {
1166
+ const { rotate } = usePdfViewerContext();
1167
+ const classNames = ["pdf-viewer__btn", className].filter(Boolean).join(" ");
1168
+ return /* @__PURE__ */ jsx10(
1169
+ "button",
1170
+ {
1171
+ className: classNames,
1172
+ onClick: () => rotate(),
1173
+ title: "Rotate",
1174
+ "aria-label": "Rotate clockwise",
1175
+ children: /* @__PURE__ */ jsx10(RotateCw, {})
1176
+ }
1177
+ );
1178
+ }
1179
+
1180
+ // src/components/Download.tsx
1181
+ import { jsx as jsx11 } from "react/jsx-runtime";
1182
+ function Download2({ fileName, className }) {
1183
+ const { download } = usePdfViewerContext();
1184
+ const classNames = ["pdf-viewer__btn", className].filter(Boolean).join(" ");
1185
+ return /* @__PURE__ */ jsx11(
1186
+ "button",
1187
+ {
1188
+ className: classNames,
1189
+ onClick: () => download(fileName),
1190
+ title: "Download",
1191
+ "aria-label": "Download PDF",
1192
+ children: /* @__PURE__ */ jsx11(Download, {})
1193
+ }
1194
+ );
1195
+ }
1196
+
1197
+ // src/components/Print.tsx
1198
+ import { jsx as jsx12 } from "react/jsx-runtime";
1199
+ function Print({ className }) {
1200
+ const { print } = usePdfViewerContext();
1201
+ const classNames = ["pdf-viewer__btn", className].filter(Boolean).join(" ");
1202
+ return /* @__PURE__ */ jsx12(
1203
+ "button",
1204
+ {
1205
+ className: classNames,
1206
+ onClick: print,
1207
+ title: "Print",
1208
+ "aria-label": "Print PDF",
1209
+ children: /* @__PURE__ */ jsx12(Printer, {})
1210
+ }
1211
+ );
1212
+ }
1213
+
1214
+ // src/components/FullScreen.tsx
1215
+ import { jsx as jsx13 } from "react/jsx-runtime";
1216
+ function FullScreen({ className }) {
1217
+ const { toggleFullScreen } = usePdfViewerContext();
1218
+ const classNames = ["pdf-viewer__btn", className].filter(Boolean).join(" ");
1219
+ return /* @__PURE__ */ jsx13(
1220
+ "button",
1221
+ {
1222
+ className: classNames,
1223
+ onClick: toggleFullScreen,
1224
+ title: "Full screen",
1225
+ "aria-label": "Toggle full screen",
1226
+ children: /* @__PURE__ */ jsx13(Expand, {})
1227
+ }
1228
+ );
1229
+ }
1230
+
1231
+ // src/components/ThumbnailToggle.tsx
1232
+ import { jsx as jsx14 } from "react/jsx-runtime";
1233
+ function ThumbnailToggle({ className }) {
1234
+ const { toggleThumbnails, isThumbnailsOpen } = usePdfViewerContext();
1235
+ const classNames = [
1236
+ "pdf-viewer__btn",
1237
+ isThumbnailsOpen ? "pdf-viewer__btn--active" : "",
1238
+ className
1239
+ ].filter(Boolean).join(" ");
1240
+ return /* @__PURE__ */ jsx14(
1241
+ "button",
1242
+ {
1243
+ className: classNames,
1244
+ onClick: toggleThumbnails,
1245
+ title: "Toggle sidebar",
1246
+ "aria-label": "Toggle thumbnails",
1247
+ "aria-pressed": isThumbnailsOpen,
1248
+ children: /* @__PURE__ */ jsx14(PanelLeft, {})
1249
+ }
1250
+ );
1251
+ }
1252
+
1253
+ // src/components/CursorModeToggle.tsx
1254
+ import { jsx as jsx15 } from "react/jsx-runtime";
1255
+ function CursorModeToggle({ className }) {
1256
+ const { cursorMode, toggleCursorMode } = usePdfViewerContext();
1257
+ const classNames = ["pdf-viewer__btn", className].filter(Boolean).join(" ");
1258
+ return /* @__PURE__ */ jsx15(
1259
+ "button",
1260
+ {
1261
+ className: classNames,
1262
+ onClick: toggleCursorMode,
1263
+ title: cursorMode === "select" ? "Hand Tool" : "Selection Mode",
1264
+ "aria-label": cursorMode === "select" ? "Hand Tool" : "Selection Mode",
1265
+ children: cursorMode === "select" ? /* @__PURE__ */ jsx15(Hand, {}) : /* @__PURE__ */ jsx15(MousePointer, {})
1266
+ }
1267
+ );
1268
+ }
1269
+
1270
+ // src/components/ThumbnailSidebar.tsx
1271
+ import { useEffect as useEffect6, useRef as useRef6, useState as useState6, useCallback as useCallback5 } from "react";
1272
+ import { jsx as jsx16, jsxs as jsxs6 } from "react/jsx-runtime";
1273
+ function ThumbnailSidebar({ className }) {
1274
+ const { document, totalPages, currentPage, goToPage, isThumbnailsOpen, rotation } = usePdfViewerContext();
1275
+ const containerRef = useRef6(null);
1276
+ const [visibleThumbnails, setVisibleThumbnails] = useState6(/* @__PURE__ */ new Set());
1277
+ const [renderedThumbnails, setRenderedThumbnails] = useState6(/* @__PURE__ */ new Set());
1278
+ const canvasRefs = useRef6(/* @__PURE__ */ new Map());
1279
+ const renderingPages = useRef6(/* @__PURE__ */ new Set());
1280
+ const handleIntersection = useCallback5(
1281
+ (entries) => {
1282
+ setVisibleThumbnails((prev) => {
1283
+ const next = new Set(prev);
1284
+ for (const entry of entries) {
1285
+ const pageNum = Number(
1286
+ entry.target.dataset.thumbnailPage
1287
+ );
1288
+ if (entry.isIntersecting) {
1289
+ next.add(pageNum);
1290
+ } else {
1291
+ next.delete(pageNum);
1292
+ }
1293
+ }
1294
+ return next;
1295
+ });
1296
+ },
1297
+ []
1298
+ );
1299
+ useEffect6(() => {
1300
+ const container = containerRef.current;
1301
+ if (!container || !isThumbnailsOpen) return;
1302
+ const observer = new IntersectionObserver(handleIntersection, {
1303
+ root: container,
1304
+ rootMargin: "100px 0px"
1305
+ });
1306
+ const items = container.querySelectorAll("[data-thumbnail-page]");
1307
+ items.forEach((el) => observer.observe(el));
1308
+ return () => observer.disconnect();
1309
+ }, [totalPages, isThumbnailsOpen, handleIntersection]);
1310
+ useEffect6(() => {
1311
+ if (!document || !isThumbnailsOpen) return;
1312
+ let cancelled = false;
1313
+ async function renderQueue() {
1314
+ for (const pageNum of visibleThumbnails) {
1315
+ if (cancelled) break;
1316
+ if (renderedThumbnails.has(pageNum)) continue;
1317
+ if (renderingPages.current.has(pageNum)) continue;
1318
+ const canvas = canvasRefs.current.get(pageNum);
1319
+ if (!canvas) continue;
1320
+ renderingPages.current.add(pageNum);
1321
+ try {
1322
+ const page = await document.getPage(pageNum);
1323
+ if (cancelled) break;
1324
+ const viewport = page.getViewport({ scale: THUMBNAIL_SCALE, rotation });
1325
+ const context = canvas.getContext("2d");
1326
+ if (!context) {
1327
+ renderingPages.current.delete(pageNum);
1328
+ continue;
1329
+ }
1330
+ canvas.width = viewport.width;
1331
+ canvas.height = viewport.height;
1332
+ canvas.style.width = `${viewport.width}px`;
1333
+ canvas.style.height = `${viewport.height}px`;
1334
+ await page.render({ canvasContext: context, viewport }).promise;
1335
+ if (!cancelled) {
1336
+ setRenderedThumbnails((prev) => new Set(prev).add(pageNum));
1337
+ }
1338
+ } catch {
1339
+ } finally {
1340
+ renderingPages.current.delete(pageNum);
1341
+ }
1342
+ }
1343
+ }
1344
+ renderQueue();
1345
+ return () => {
1346
+ cancelled = true;
1347
+ };
1348
+ }, [document, visibleThumbnails, renderedThumbnails, isThumbnailsOpen, rotation]);
1349
+ const lastClickedRef = useRef6(null);
1350
+ useEffect6(() => {
1351
+ const container = containerRef.current;
1352
+ if (!container || !isThumbnailsOpen) return;
1353
+ if (lastClickedRef.current === currentPage) {
1354
+ lastClickedRef.current = null;
1355
+ return;
1356
+ }
1357
+ const active = container.querySelector(`[data-thumbnail-page="${currentPage}"]`);
1358
+ if (!active) return;
1359
+ const containerTop = container.scrollTop;
1360
+ const containerBottom = containerTop + container.clientHeight;
1361
+ const elTop = active.offsetTop - container.offsetTop;
1362
+ const elBottom = elTop + active.offsetHeight;
1363
+ if (elTop < containerTop || elBottom > containerBottom) {
1364
+ container.scrollTo({ top: Math.max(0, elTop - 8) });
1365
+ }
1366
+ }, [currentPage, isThumbnailsOpen]);
1367
+ useEffect6(() => {
1368
+ setRenderedThumbnails(/* @__PURE__ */ new Set());
1369
+ }, [rotation]);
1370
+ if (!isThumbnailsOpen) return null;
1371
+ const classNames = ["pdf-viewer__sidebar", className].filter(Boolean).join(" ");
1372
+ return /* @__PURE__ */ jsx16("div", { ref: containerRef, className: classNames, children: Array.from({ length: totalPages }, (_, i) => {
1373
+ const pageNum = i + 1;
1374
+ return /* @__PURE__ */ jsxs6(
1375
+ "button",
1376
+ {
1377
+ className: `pdf-viewer__thumbnail${currentPage === pageNum ? " pdf-viewer__thumbnail--active" : ""}`,
1378
+ "data-thumbnail-page": pageNum,
1379
+ onClick: () => {
1380
+ lastClickedRef.current = pageNum;
1381
+ goToPage(pageNum);
1382
+ },
1383
+ children: [
1384
+ /* @__PURE__ */ jsx16(
1385
+ "canvas",
1386
+ {
1387
+ ref: (el) => {
1388
+ if (el) {
1389
+ canvasRefs.current.set(pageNum, el);
1390
+ } else {
1391
+ canvasRefs.current.delete(pageNum);
1392
+ }
1393
+ }
1394
+ }
1395
+ ),
1396
+ /* @__PURE__ */ jsx16("span", { children: pageNum })
1397
+ ]
1398
+ },
1399
+ pageNum
1400
+ );
1401
+ }) });
1402
+ }
1403
+
1404
+ // src/components/Pages.tsx
1405
+ import { useCallback as useCallback7, useEffect as useEffect8, useRef as useRef8, useState as useState7 } from "react";
1406
+
1407
+ // src/components/Page.tsx
1408
+ import { useEffect as useEffect7, useRef as useRef7, useCallback as useCallback6 } from "react";
1409
+ import { jsx as jsx17, jsxs as jsxs7 } from "react/jsx-runtime";
1410
+ function Page({ pageNumber, className }) {
1411
+ const { document, zoomLevel, rotation, searchQuery, searchMatches, currentMatchIndex } = usePdfViewerContext();
1412
+ const canvasRef = useRef7(null);
1413
+ const textLayerRef = useRef7(null);
1414
+ const renderTaskRef = useRef7(null);
1415
+ const applyHighlightsRef = useRef7(null);
1416
+ const renderPage = useCallback6(async () => {
1417
+ if (!document || !canvasRef.current) return;
1418
+ if (renderTaskRef.current) {
1419
+ renderTaskRef.current.cancel();
1420
+ renderTaskRef.current = null;
1421
+ }
1422
+ try {
1423
+ const page = await document.getPage(pageNumber);
1424
+ const viewport = page.getViewport({ scale: zoomLevel, rotation });
1425
+ const canvas = canvasRef.current;
1426
+ const context = canvas.getContext("2d");
1427
+ if (!context) return;
1428
+ const dpr = window.devicePixelRatio || 1;
1429
+ canvas.width = viewport.width * dpr;
1430
+ canvas.height = viewport.height * dpr;
1431
+ canvas.style.width = `${viewport.width}px`;
1432
+ canvas.style.height = `${viewport.height}px`;
1433
+ context.scale(dpr, dpr);
1434
+ const renderTask = page.render({
1435
+ canvasContext: context,
1436
+ viewport
1437
+ });
1438
+ renderTaskRef.current = renderTask;
1439
+ await renderTask.promise;
1440
+ if (textLayerRef.current) {
1441
+ const textLayerDiv = textLayerRef.current;
1442
+ textLayerDiv.innerHTML = "";
1443
+ textLayerDiv.style.setProperty("--scale-factor", String(viewport.scale));
1444
+ const textContent = await page.getTextContent();
1445
+ const { TextLayer } = await import("pdfjs-dist");
1446
+ const textLayer = new TextLayer({
1447
+ textContentSource: textContent,
1448
+ container: textLayerDiv,
1449
+ viewport
1450
+ });
1451
+ await textLayer.render();
1452
+ requestAnimationFrame(() => applyHighlightsRef.current?.());
1453
+ }
1454
+ } catch (err) {
1455
+ if (err && typeof err === "object" && "name" in err && err.name === "RenderingCancelledException") return;
1456
+ }
1457
+ }, [document, pageNumber, zoomLevel, rotation]);
1458
+ const applyHighlights = useCallback6(() => {
1459
+ const textLayer = textLayerRef.current;
1460
+ if (!textLayer) return;
1461
+ textLayer.querySelectorAll("mark.pdf-viewer__search-hit").forEach((mark) => {
1462
+ const parent = mark.parentNode;
1463
+ if (parent) {
1464
+ parent.replaceChild(window.document.createTextNode(mark.textContent || ""), mark);
1465
+ parent.normalize();
1466
+ }
1467
+ });
1468
+ if (!searchQuery || searchMatches.length === 0) return;
1469
+ const pageMatches = searchMatches.filter((m) => m.pageIndex === pageNumber - 1);
1470
+ if (pageMatches.length === 0) return;
1471
+ const lowerQuery = searchQuery.toLowerCase();
1472
+ const queryLen = searchQuery.length;
1473
+ const spans = Array.from(textLayer.querySelectorAll("span"));
1474
+ let pageMatchIdx = 0;
1475
+ for (const span of spans) {
1476
+ const text = span.textContent || "";
1477
+ const lowerText = text.toLowerCase();
1478
+ let idx = lowerText.indexOf(lowerQuery);
1479
+ if (idx === -1) continue;
1480
+ const frag = window.document.createDocumentFragment();
1481
+ let lastIdx = 0;
1482
+ while (idx !== -1) {
1483
+ if (idx > lastIdx) {
1484
+ frag.appendChild(window.document.createTextNode(text.slice(lastIdx, idx)));
1485
+ }
1486
+ const mark = window.document.createElement("mark");
1487
+ mark.className = "pdf-viewer__search-hit";
1488
+ mark.textContent = text.slice(idx, idx + queryLen);
1489
+ if (pageMatchIdx < pageMatches.length) {
1490
+ const globalIdx = searchMatches.indexOf(pageMatches[pageMatchIdx]);
1491
+ if (globalIdx === currentMatchIndex) {
1492
+ mark.classList.add("pdf-viewer__search-hit--current");
1493
+ }
1494
+ }
1495
+ pageMatchIdx++;
1496
+ frag.appendChild(mark);
1497
+ lastIdx = idx + queryLen;
1498
+ idx = lowerText.indexOf(lowerQuery, lastIdx);
1499
+ }
1500
+ if (lastIdx < text.length) {
1501
+ frag.appendChild(window.document.createTextNode(text.slice(lastIdx)));
1502
+ }
1503
+ span.textContent = "";
1504
+ span.appendChild(frag);
1505
+ }
1506
+ }, [searchQuery, searchMatches, currentMatchIndex, pageNumber]);
1507
+ applyHighlightsRef.current = applyHighlights;
1508
+ useEffect7(() => {
1509
+ applyHighlights();
1510
+ }, [applyHighlights]);
1511
+ useEffect7(() => {
1512
+ renderPage();
1513
+ return () => {
1514
+ if (renderTaskRef.current) {
1515
+ renderTaskRef.current.cancel();
1516
+ renderTaskRef.current = null;
1517
+ }
1518
+ };
1519
+ }, [renderPage]);
1520
+ const classNames = ["pdf-viewer__page", className].filter(Boolean).join(" ");
1521
+ return /* @__PURE__ */ jsxs7("div", { className: classNames, "data-page-number": pageNumber, children: [
1522
+ /* @__PURE__ */ jsx17("canvas", { ref: canvasRef, className: "pdf-viewer__page-canvas" }),
1523
+ /* @__PURE__ */ jsx17("div", { ref: textLayerRef, className: "pdf-viewer__page-text-layer" })
1524
+ ] });
1525
+ }
1526
+
1527
+ // src/components/Pages.tsx
1528
+ import { jsx as jsx18 } from "react/jsx-runtime";
1529
+ function Pages({ className }) {
1530
+ const { totalPages, currentPage, _setCurrentPage, zoomMode, zoomLevel, _setZoomLevel, zoomTo, rotation, document: pdfDoc, containerRef: ctxContainerRef, scrollToPageRef, cursorMode, viewMode, scrollMode, isPrinting } = usePdfViewerContext();
1531
+ const containerRef = useRef8(null);
1532
+ const visiblePagesRef = useRef8(/* @__PURE__ */ new Set([1]));
1533
+ const [baseDims, setBaseDims] = useState7(null);
1534
+ const navigatingRef = useRef8(false);
1535
+ const lastReportedPageRef = useRef8(1);
1536
+ const pageUpdateRafRef = useRef8(0);
1537
+ const [renderGeneration, setRenderGeneration] = useState7(0);
1538
+ const computeRenderSet = useCallback7((visible) => {
1539
+ const result = /* @__PURE__ */ new Set();
1540
+ for (const v of visible) {
1541
+ for (let offset = -VIRTUALIZATION_BUFFER; offset <= VIRTUALIZATION_BUFFER; offset++) {
1542
+ const p = v + offset;
1543
+ if (p >= 1 && p <= totalPages) result.add(p);
1544
+ }
1545
+ }
1546
+ return result;
1547
+ }, [totalPages]);
1548
+ const lastRenderSetRef = useRef8(computeRenderSet(/* @__PURE__ */ new Set([1])));
1549
+ const _setCurrentPageRef = useRef8(_setCurrentPage);
1550
+ _setCurrentPageRef.current = _setCurrentPage;
1551
+ const handleIntersection = useCallback7(
1552
+ (entries) => {
1553
+ const prev = visiblePagesRef.current;
1554
+ for (const entry of entries) {
1555
+ const pageNum = Number(
1556
+ entry.target.dataset.pageNumber
1557
+ );
1558
+ if (entry.isIntersecting) {
1559
+ prev.add(pageNum);
1560
+ } else {
1561
+ prev.delete(pageNum);
1562
+ }
1563
+ }
1564
+ const newRenderSet = computeRenderSet(prev);
1565
+ const oldRenderSet = lastRenderSetRef.current;
1566
+ if (newRenderSet.size !== oldRenderSet.size || [...newRenderSet].some((p) => !oldRenderSet.has(p))) {
1567
+ lastRenderSetRef.current = newRenderSet;
1568
+ setRenderGeneration((g) => g + 1);
1569
+ }
1570
+ if (navigatingRef.current) return;
1571
+ cancelAnimationFrame(pageUpdateRafRef.current);
1572
+ pageUpdateRafRef.current = requestAnimationFrame(() => {
1573
+ const visible = visiblePagesRef.current;
1574
+ if (visible.size > 0) {
1575
+ const topmost = Math.min(...visible);
1576
+ if (topmost !== lastReportedPageRef.current) {
1577
+ lastReportedPageRef.current = topmost;
1578
+ _setCurrentPageRef.current(topmost);
1579
+ }
1580
+ }
1581
+ });
1582
+ },
1583
+ [computeRenderSet]
1584
+ );
1585
+ useEffect8(() => {
1586
+ if (!pdfDoc) return;
1587
+ pdfDoc.getPage(1).then((page) => {
1588
+ const vp = page.getViewport({ scale: 1, rotation });
1589
+ setBaseDims({ width: vp.width, height: vp.height });
1590
+ });
1591
+ }, [pdfDoc, rotation]);
1592
+ useEffect8(() => {
1593
+ const container = containerRef.current;
1594
+ if (!container) return;
1595
+ const observer = new IntersectionObserver(handleIntersection, {
1596
+ root: container,
1597
+ rootMargin: scrollMode === "horizontal" ? "0px 200px" : "200px 0px"
1598
+ });
1599
+ const wrappers = container.querySelectorAll("[data-page-number]");
1600
+ wrappers.forEach((el) => observer.observe(el));
1601
+ return () => observer.disconnect();
1602
+ }, [totalPages, handleIntersection, scrollMode]);
1603
+ const navigatingTimerRef = useRef8(0);
1604
+ useEffect8(() => {
1605
+ const container = containerRef.current;
1606
+ if (!container) return;
1607
+ const handleScrollEnd = () => {
1608
+ if (navigatingRef.current) {
1609
+ clearTimeout(navigatingTimerRef.current);
1610
+ navigatingTimerRef.current = window.setTimeout(() => {
1611
+ navigatingRef.current = false;
1612
+ }, 100);
1613
+ }
1614
+ };
1615
+ container.addEventListener("scrollend", handleScrollEnd);
1616
+ return () => {
1617
+ container.removeEventListener("scrollend", handleScrollEnd);
1618
+ clearTimeout(navigatingTimerRef.current);
1619
+ };
1620
+ }, []);
1621
+ useEffect8(() => {
1622
+ if (!scrollToPageRef) return;
1623
+ scrollToPageRef.current = (page) => {
1624
+ lastReportedPageRef.current = page;
1625
+ if (scrollMode === "page") {
1626
+ navigatingRef.current = false;
1627
+ return;
1628
+ }
1629
+ const container = containerRef.current;
1630
+ if (!container) return;
1631
+ const wrapper = container.querySelector(`[data-page-number="${page}"]`);
1632
+ if (!wrapper) return;
1633
+ const target = (viewMode === "dual" ? wrapper.closest(".pdf-viewer__page-pair") : wrapper) ?? wrapper;
1634
+ navigatingRef.current = true;
1635
+ clearTimeout(navigatingTimerRef.current);
1636
+ navigatingTimerRef.current = window.setTimeout(() => {
1637
+ navigatingRef.current = false;
1638
+ }, 150);
1639
+ if (scrollMode === "horizontal") {
1640
+ container.scrollTo({ left: target.offsetLeft - container.offsetLeft });
1641
+ } else {
1642
+ container.scrollTo({ top: target.offsetTop - container.offsetTop });
1643
+ }
1644
+ };
1645
+ }, [scrollToPageRef, scrollMode, viewMode]);
1646
+ useEffect8(() => {
1647
+ if (containerRef.current && ctxContainerRef) {
1648
+ ctxContainerRef.current = containerRef.current;
1649
+ }
1650
+ });
1651
+ useEffect8(() => {
1652
+ if (!zoomMode || !pdfDoc || !containerRef.current) return;
1653
+ const computeFitZoom = () => {
1654
+ if (!containerRef.current || !pdfDoc) return;
1655
+ pdfDoc.getPage(1).then((page) => {
1656
+ const viewport = page.getViewport({ scale: 1, rotation: 0 });
1657
+ const padding = 32;
1658
+ const gap = 16;
1659
+ const containerWidth = containerRef.current.clientWidth - padding;
1660
+ const containerHeight = containerRef.current.clientHeight - padding;
1661
+ const availableWidth = viewMode === "dual" ? (containerWidth - gap) / 2 : containerWidth;
1662
+ if (zoomMode === "fit-width") {
1663
+ const scale = availableWidth / viewport.width;
1664
+ _setZoomLevel(scale);
1665
+ } else if (zoomMode === "fit-page") {
1666
+ const scaleW = availableWidth / viewport.width;
1667
+ const scaleH = containerHeight / viewport.height;
1668
+ _setZoomLevel(Math.min(scaleW, scaleH));
1669
+ }
1670
+ });
1671
+ };
1672
+ computeFitZoom();
1673
+ const resizeObserver = new ResizeObserver(() => {
1674
+ computeFitZoom();
1675
+ });
1676
+ resizeObserver.observe(containerRef.current);
1677
+ return () => resizeObserver.disconnect();
1678
+ }, [zoomMode, pdfDoc, _setZoomLevel, viewMode]);
1679
+ const isDraggingRef = useRef8(false);
1680
+ const dragStartRef = useRef8({ x: 0, y: 0, scrollLeft: 0, scrollTop: 0 });
1681
+ const handleMouseDown = useCallback7((e) => {
1682
+ if (cursorMode !== "hand") return;
1683
+ const container = containerRef.current;
1684
+ if (!container) return;
1685
+ isDraggingRef.current = true;
1686
+ dragStartRef.current = {
1687
+ x: e.clientX,
1688
+ y: e.clientY,
1689
+ scrollLeft: container.scrollLeft,
1690
+ scrollTop: container.scrollTop
1691
+ };
1692
+ container.style.cursor = "grabbing";
1693
+ e.preventDefault();
1694
+ }, [cursorMode]);
1695
+ useEffect8(() => {
1696
+ if (cursorMode !== "hand") return;
1697
+ const container = containerRef.current;
1698
+ if (!container) return;
1699
+ const handleMouseMove = (e) => {
1700
+ if (!isDraggingRef.current) return;
1701
+ const dx = e.clientX - dragStartRef.current.x;
1702
+ const dy = e.clientY - dragStartRef.current.y;
1703
+ container.scrollLeft = dragStartRef.current.scrollLeft - dx;
1704
+ container.scrollTop = dragStartRef.current.scrollTop - dy;
1705
+ };
1706
+ const handleMouseUp = () => {
1707
+ isDraggingRef.current = false;
1708
+ container.style.cursor = "";
1709
+ };
1710
+ container.addEventListener("mousemove", handleMouseMove);
1711
+ container.addEventListener("mouseup", handleMouseUp);
1712
+ container.addEventListener("mouseleave", handleMouseUp);
1713
+ return () => {
1714
+ container.removeEventListener("mousemove", handleMouseMove);
1715
+ container.removeEventListener("mouseup", handleMouseUp);
1716
+ container.removeEventListener("mouseleave", handleMouseUp);
1717
+ };
1718
+ }, [cursorMode]);
1719
+ const touchStateRef = useRef8(null);
1720
+ const zoomLevelRef = useRef8(zoomLevel);
1721
+ zoomLevelRef.current = zoomLevel;
1722
+ useEffect8(() => {
1723
+ const container = containerRef.current;
1724
+ if (!container) return;
1725
+ const getDistance = (t1, t2) => Math.hypot(t2.clientX - t1.clientX, t2.clientY - t1.clientY);
1726
+ const getMidpoint = (t1, t2) => ({
1727
+ x: (t1.clientX + t2.clientX) / 2,
1728
+ y: (t1.clientY + t2.clientY) / 2
1729
+ });
1730
+ const handleTouchStart = (e) => {
1731
+ if (e.touches.length === 2) {
1732
+ e.preventDefault();
1733
+ const dist = getDistance(e.touches[0], e.touches[1]);
1734
+ const mid = getMidpoint(e.touches[0], e.touches[1]);
1735
+ const rect = container.getBoundingClientRect();
1736
+ touchStateRef.current = {
1737
+ startDistance: dist,
1738
+ startZoom: zoomLevelRef.current,
1739
+ startX: 0,
1740
+ startY: 0,
1741
+ startTime: 0,
1742
+ isPinching: true,
1743
+ lastScale: zoomLevelRef.current,
1744
+ centerX: mid.x - rect.left,
1745
+ centerY: mid.y - rect.top,
1746
+ scrollLeft: container.scrollLeft,
1747
+ scrollTop: container.scrollTop
1748
+ };
1749
+ } else if (e.touches.length === 1) {
1750
+ touchStateRef.current = {
1751
+ startDistance: 0,
1752
+ startZoom: zoomLevelRef.current,
1753
+ startX: e.touches[0].clientX,
1754
+ startY: e.touches[0].clientY,
1755
+ startTime: Date.now(),
1756
+ isPinching: false,
1757
+ lastScale: zoomLevelRef.current,
1758
+ centerX: 0,
1759
+ centerY: 0,
1760
+ scrollLeft: 0,
1761
+ scrollTop: 0
1762
+ };
1763
+ }
1764
+ };
1765
+ const handleTouchMove = (e) => {
1766
+ if (!touchStateRef.current) return;
1767
+ if (e.touches.length === 2 && touchStateRef.current.isPinching) {
1768
+ e.preventDefault();
1769
+ const dist = getDistance(e.touches[0], e.touches[1]);
1770
+ const scale = dist / touchStateRef.current.startDistance;
1771
+ const clamped = Math.max(0.25, Math.min(4, touchStateRef.current.startZoom * scale));
1772
+ const visualScale = clamped / touchStateRef.current.startZoom;
1773
+ touchStateRef.current.lastScale = clamped;
1774
+ container.style.transformOrigin = `${touchStateRef.current.centerX}px ${touchStateRef.current.centerY}px`;
1775
+ container.style.transform = `scale(${visualScale})`;
1776
+ }
1777
+ };
1778
+ const handleTouchEnd = (e) => {
1779
+ if (!touchStateRef.current) return;
1780
+ if (touchStateRef.current.isPinching) {
1781
+ const { startZoom, lastScale, centerX, centerY, scrollLeft, scrollTop } = touchStateRef.current;
1782
+ container.style.transform = "";
1783
+ container.style.transformOrigin = "";
1784
+ touchStateRef.current = null;
1785
+ const zoomRatio = lastScale / startZoom;
1786
+ const contentX = scrollLeft + centerX;
1787
+ const contentY = scrollTop + centerY;
1788
+ const newScrollLeft = contentX * zoomRatio - centerX;
1789
+ const newScrollTop = contentY * zoomRatio - centerY;
1790
+ navigatingRef.current = true;
1791
+ zoomTo(lastScale);
1792
+ requestAnimationFrame(() => {
1793
+ requestAnimationFrame(() => {
1794
+ container.scrollLeft = Math.max(0, newScrollLeft);
1795
+ container.scrollTop = Math.max(0, newScrollTop);
1796
+ navigatingRef.current = false;
1797
+ });
1798
+ });
1799
+ return;
1800
+ }
1801
+ touchStateRef.current = null;
1802
+ };
1803
+ container.addEventListener("touchstart", handleTouchStart, { passive: false });
1804
+ container.addEventListener("touchmove", handleTouchMove, { passive: false });
1805
+ container.addEventListener("touchend", handleTouchEnd);
1806
+ return () => {
1807
+ container.removeEventListener("touchstart", handleTouchStart);
1808
+ container.removeEventListener("touchmove", handleTouchMove);
1809
+ container.removeEventListener("touchend", handleTouchEnd);
1810
+ };
1811
+ }, [zoomTo, scrollMode]);
1812
+ const isPageScroll = scrollMode === "page";
1813
+ const shouldRenderPage = (pageNum) => {
1814
+ if (isPrinting) return true;
1815
+ if (isPageScroll) {
1816
+ return Math.abs(pageNum - currentPage) <= VIRTUALIZATION_BUFFER;
1817
+ }
1818
+ void renderGeneration;
1819
+ return lastRenderSetRef.current.has(pageNum);
1820
+ };
1821
+ const classNames = [
1822
+ "pdf-viewer__pages",
1823
+ `pdf-viewer__pages--scroll-${scrollMode}`,
1824
+ viewMode === "dual" && "pdf-viewer__pages--dual",
1825
+ cursorMode === "hand" && "pdf-viewer__pages--hand",
1826
+ className
1827
+ ].filter(Boolean).join(" ");
1828
+ const renderPageWrapper = (pageNum) => {
1829
+ const isActive = isPageScroll && pageNum === currentPage;
1830
+ const wrapperClass = [
1831
+ "pdf-viewer__page-wrapper",
1832
+ isActive && "pdf-viewer__page-wrapper--active"
1833
+ ].filter(Boolean).join(" ");
1834
+ return /* @__PURE__ */ jsx18(
1835
+ "div",
1836
+ {
1837
+ className: wrapperClass,
1838
+ "data-page-number": pageNum,
1839
+ children: shouldRenderPage(pageNum) ? /* @__PURE__ */ jsx18(Page, { pageNumber: pageNum }) : /* @__PURE__ */ jsx18(
1840
+ "div",
1841
+ {
1842
+ className: "pdf-viewer__page-placeholder",
1843
+ style: baseDims ? {
1844
+ width: `${baseDims.width * zoomLevel}px`,
1845
+ height: `${baseDims.height * zoomLevel}px`
1846
+ } : void 0
1847
+ }
1848
+ )
1849
+ },
1850
+ pageNum
1851
+ );
1852
+ };
1853
+ const renderContent = () => {
1854
+ const pages = Array.from({ length: totalPages }, (_, i) => i + 1);
1855
+ if (viewMode === "dual") {
1856
+ const pairs = [];
1857
+ for (let i = 0; i < pages.length; i += 2) {
1858
+ pairs.push(pages.slice(i, i + 2));
1859
+ }
1860
+ return pairs.map((pair) => {
1861
+ const isActive = isPageScroll && pair.includes(currentPage);
1862
+ const pairClass = [
1863
+ "pdf-viewer__page-pair",
1864
+ isActive && "pdf-viewer__page-pair--active"
1865
+ ].filter(Boolean).join(" ");
1866
+ return /* @__PURE__ */ jsx18("div", { className: pairClass, children: pair.map(renderPageWrapper) }, pair[0]);
1867
+ });
1868
+ }
1869
+ return pages.map(renderPageWrapper);
1870
+ };
1871
+ return /* @__PURE__ */ jsx18("div", { ref: containerRef, className: classNames, onMouseDown: handleMouseDown, children: renderContent() });
1872
+ }
1873
+
1874
+ // src/components/MoreMenu.tsx
1875
+ import { jsx as jsx19, jsxs as jsxs8 } from "react/jsx-runtime";
1876
+ function MoreMenu({ className }) {
1877
+ const {
1878
+ currentPage,
1879
+ totalPages,
1880
+ rotate,
1881
+ viewMode,
1882
+ scrollMode,
1883
+ setViewMode,
1884
+ setScrollMode,
1885
+ goToFirstPage,
1886
+ goToLastPage,
1887
+ openDocProperties,
1888
+ download,
1889
+ print,
1890
+ toggleFullScreen
1891
+ } = usePdfViewerContext();
1892
+ return /* @__PURE__ */ jsxs8(DropdownMenu, { trigger: /* @__PURE__ */ jsx19(EllipsisVertical, {}), className, align: "right", children: [
1893
+ /* @__PURE__ */ jsxs8("div", { className: "pdf-viewer__show-mobile", children: [
1894
+ /* @__PURE__ */ jsx19(
1895
+ DropdownMenuItem,
1896
+ {
1897
+ icon: /* @__PURE__ */ jsx19(Download, {}),
1898
+ label: "Download",
1899
+ onClick: () => download()
1900
+ }
1901
+ ),
1902
+ /* @__PURE__ */ jsx19(
1903
+ DropdownMenuItem,
1904
+ {
1905
+ icon: /* @__PURE__ */ jsx19(Printer, {}),
1906
+ label: "Print",
1907
+ onClick: print
1908
+ }
1909
+ ),
1910
+ /* @__PURE__ */ jsx19(
1911
+ DropdownMenuItem,
1912
+ {
1913
+ icon: /* @__PURE__ */ jsx19(Maximize, {}),
1914
+ label: "Full Screen",
1915
+ onClick: toggleFullScreen
1916
+ }
1917
+ ),
1918
+ /* @__PURE__ */ jsx19(DropdownMenuSeparator, {})
1919
+ ] }),
1920
+ /* @__PURE__ */ jsx19(
1921
+ DropdownMenuItem,
1922
+ {
1923
+ icon: /* @__PURE__ */ jsx19(ArrowDownToLine, { style: { transform: "rotate(180deg)" } }),
1924
+ label: "First Page",
1925
+ onClick: goToFirstPage,
1926
+ disabled: currentPage <= 1
1927
+ }
1928
+ ),
1929
+ /* @__PURE__ */ jsx19(
1930
+ DropdownMenuItem,
1931
+ {
1932
+ icon: /* @__PURE__ */ jsx19(ArrowDownToLine, {}),
1933
+ label: "Last Page",
1934
+ onClick: goToLastPage,
1935
+ disabled: currentPage >= totalPages
1936
+ }
1937
+ ),
1938
+ /* @__PURE__ */ jsx19(DropdownMenuSeparator, {}),
1939
+ /* @__PURE__ */ jsx19(
1940
+ DropdownMenuItem,
1941
+ {
1942
+ icon: /* @__PURE__ */ jsx19(RotateCw, {}),
1943
+ label: "Rotate Clockwise",
1944
+ onClick: () => rotate(90)
1945
+ }
1946
+ ),
1947
+ /* @__PURE__ */ jsx19(
1948
+ DropdownMenuItem,
1949
+ {
1950
+ icon: /* @__PURE__ */ jsx19(RotateCcw, {}),
1951
+ label: "Rotate Counterclockwise",
1952
+ onClick: () => rotate(-90)
1953
+ }
1954
+ ),
1955
+ /* @__PURE__ */ jsx19(DropdownMenuSeparator, {}),
1956
+ /* @__PURE__ */ jsx19(
1957
+ DropdownMenuItem,
1958
+ {
1959
+ icon: /* @__PURE__ */ jsx19(StickyNote, {}),
1960
+ label: "Single Page",
1961
+ onClick: () => setViewMode("single"),
1962
+ active: viewMode === "single"
1963
+ }
1964
+ ),
1965
+ /* @__PURE__ */ jsx19(
1966
+ DropdownMenuItem,
1967
+ {
1968
+ icon: /* @__PURE__ */ jsx19(Columns2, {}),
1969
+ label: "Dual Page",
1970
+ onClick: () => setViewMode("dual"),
1971
+ active: viewMode === "dual",
1972
+ disabled: scrollMode === "horizontal"
1973
+ }
1974
+ ),
1975
+ /* @__PURE__ */ jsx19(DropdownMenuSeparator, {}),
1976
+ /* @__PURE__ */ jsx19(
1977
+ DropdownMenuItem,
1978
+ {
1979
+ icon: /* @__PURE__ */ jsx19(GalleryThumbnails, {}),
1980
+ label: "Page Scrolling",
1981
+ onClick: () => setScrollMode("page"),
1982
+ active: scrollMode === "page"
1983
+ }
1984
+ ),
1985
+ /* @__PURE__ */ jsx19(
1986
+ DropdownMenuItem,
1987
+ {
1988
+ icon: /* @__PURE__ */ jsx19(GalleryVertical, {}),
1989
+ label: "Vertical Scrolling",
1990
+ onClick: () => setScrollMode("vertical"),
1991
+ active: scrollMode === "vertical"
1992
+ }
1993
+ ),
1994
+ /* @__PURE__ */ jsx19(
1995
+ DropdownMenuItem,
1996
+ {
1997
+ icon: /* @__PURE__ */ jsx19(GalleryHorizontal, {}),
1998
+ label: "Horizontal Scrolling",
1999
+ onClick: () => setScrollMode("horizontal"),
2000
+ active: scrollMode === "horizontal",
2001
+ disabled: viewMode === "dual"
2002
+ }
2003
+ ),
2004
+ /* @__PURE__ */ jsx19(DropdownMenuSeparator, {}),
2005
+ /* @__PURE__ */ jsx19(
2006
+ DropdownMenuItem,
2007
+ {
2008
+ icon: /* @__PURE__ */ jsx19(Info, {}),
2009
+ label: "Document Properties",
2010
+ onClick: openDocProperties
2011
+ }
2012
+ )
2013
+ ] });
2014
+ }
2015
+
2016
+ // src/components/DocumentPropertiesModal.tsx
2017
+ import { useEffect as useEffect9, useRef as useRef9 } from "react";
2018
+ import { jsx as jsx20, jsxs as jsxs9 } from "react/jsx-runtime";
2019
+ function DocumentPropertiesModal() {
2020
+ const { isDocPropertiesOpen, docProperties, closeDocProperties } = usePdfViewerContext();
2021
+ const backdropRef = useRef9(null);
2022
+ useEffect9(() => {
2023
+ if (!isDocPropertiesOpen) return;
2024
+ const handleEscape = (e) => {
2025
+ if (e.key === "Escape") closeDocProperties();
2026
+ };
2027
+ window.document.addEventListener("keydown", handleEscape);
2028
+ return () => window.document.removeEventListener("keydown", handleEscape);
2029
+ }, [isDocPropertiesOpen, closeDocProperties]);
2030
+ if (!isDocPropertiesOpen || !docProperties) return null;
2031
+ const rows = [
2032
+ ["Title", docProperties.title],
2033
+ ["Author", docProperties.author],
2034
+ ["Subject", docProperties.subject],
2035
+ ["Creator", docProperties.creator],
2036
+ ["Producer", docProperties.producer],
2037
+ ["Created", docProperties.creationDate],
2038
+ ["Modified", docProperties.modificationDate],
2039
+ ["Pages", String(docProperties.pageCount)],
2040
+ ["Page Size", docProperties.pageSize]
2041
+ ];
2042
+ return /* @__PURE__ */ jsx20(
2043
+ "div",
2044
+ {
2045
+ ref: backdropRef,
2046
+ className: "pdf-viewer__modal-backdrop",
2047
+ onClick: (e) => {
2048
+ if (e.target === backdropRef.current) closeDocProperties();
2049
+ },
2050
+ children: /* @__PURE__ */ jsxs9("div", { className: "pdf-viewer__modal", role: "dialog", "aria-label": "Document Properties", children: [
2051
+ /* @__PURE__ */ jsxs9("div", { className: "pdf-viewer__modal-header", children: [
2052
+ /* @__PURE__ */ jsx20("h2", { className: "pdf-viewer__modal-title", children: "Document Properties" }),
2053
+ /* @__PURE__ */ jsx20("button", { className: "pdf-viewer__btn", onClick: closeDocProperties, "aria-label": "Close", title: "Close", children: /* @__PURE__ */ jsx20(X, {}) })
2054
+ ] }),
2055
+ /* @__PURE__ */ jsxs9("div", { className: "pdf-viewer__modal-body", children: [
2056
+ /* @__PURE__ */ jsxs9("div", { className: "pdf-viewer__props-file-info", children: [
2057
+ /* @__PURE__ */ jsx20("span", { className: "pdf-viewer__props-file-name", children: docProperties.fileName }),
2058
+ /* @__PURE__ */ jsx20("span", { className: "pdf-viewer__props-file-size", children: docProperties.fileSize })
2059
+ ] }),
2060
+ /* @__PURE__ */ jsx20("table", { className: "pdf-viewer__props-table", children: /* @__PURE__ */ jsx20("tbody", { children: rows.map(([label, value]) => /* @__PURE__ */ jsxs9("tr", { children: [
2061
+ /* @__PURE__ */ jsx20("td", { className: "pdf-viewer__props-label", children: label }),
2062
+ /* @__PURE__ */ jsx20("td", { className: "pdf-viewer__props-value", children: value })
2063
+ ] }, label)) }) })
2064
+ ] })
2065
+ ] })
2066
+ }
2067
+ );
2068
+ }
2069
+
2070
+ // src/components/PdfViewer.tsx
2071
+ import { jsx as jsx21, jsxs as jsxs10 } from "react/jsx-runtime";
2072
+ function PdfViewer(props) {
2073
+ const { className, ...rootProps } = props;
2074
+ return /* @__PURE__ */ jsxs10(Root, { className, ...rootProps, children: [
2075
+ /* @__PURE__ */ jsxs10(Toolbar, { children: [
2076
+ /* @__PURE__ */ jsx21(Navigation, {}),
2077
+ /* @__PURE__ */ jsx21(Separator, {}),
2078
+ /* @__PURE__ */ jsx21(Zoom, {}),
2079
+ /* @__PURE__ */ jsx21(ThumbnailToggle, {}),
2080
+ /* @__PURE__ */ jsxs10("div", { className: "pdf-viewer__toolbar-extras", children: [
2081
+ /* @__PURE__ */ jsx21(Separator, {}),
2082
+ /* @__PURE__ */ jsx21(Search, {}),
2083
+ /* @__PURE__ */ jsx21(Separator, {}),
2084
+ /* @__PURE__ */ jsx21(Rotate, {}),
2085
+ /* @__PURE__ */ jsx21(Download2, {}),
2086
+ /* @__PURE__ */ jsx21(Print, {}),
2087
+ /* @__PURE__ */ jsx21(FullScreen, {}),
2088
+ /* @__PURE__ */ jsx21(CursorModeToggle, {})
2089
+ ] }),
2090
+ /* @__PURE__ */ jsx21(MoreMenu, {})
2091
+ ] }),
2092
+ /* @__PURE__ */ jsxs10("div", { className: "pdf-viewer__body", children: [
2093
+ /* @__PURE__ */ jsx21(ThumbnailSidebar, {}),
2094
+ /* @__PURE__ */ jsx21(Pages, {})
2095
+ ] }),
2096
+ /* @__PURE__ */ jsx21(DocumentPropertiesModal, {})
2097
+ ] });
2098
+ }
2099
+ PdfViewer.Root = Root;
2100
+ PdfViewer.Toolbar = Toolbar;
2101
+ PdfViewer.Navigation = Navigation;
2102
+ PdfViewer.Zoom = Zoom;
2103
+ PdfViewer.Search = Search;
2104
+ PdfViewer.Separator = Separator;
2105
+ PdfViewer.Rotate = Rotate;
2106
+ PdfViewer.Download = Download2;
2107
+ PdfViewer.Print = Print;
2108
+ PdfViewer.FullScreen = FullScreen;
2109
+ PdfViewer.ThumbnailToggle = ThumbnailToggle;
2110
+ PdfViewer.ThumbnailSidebar = ThumbnailSidebar;
2111
+ PdfViewer.Pages = Pages;
2112
+ PdfViewer.Page = Page;
2113
+ PdfViewer.CursorModeToggle = CursorModeToggle;
2114
+ PdfViewer.MoreMenu = MoreMenu;
2115
+ PdfViewer.DropdownMenu = DropdownMenu;
2116
+ PdfViewer.DropdownMenuItem = DropdownMenuItem;
2117
+ PdfViewer.DropdownMenuSeparator = DropdownMenuSeparator;
2118
+ PdfViewer.DocumentPropertiesModal = DocumentPropertiesModal;
2119
+
2120
+ // src/hooks/use-pdf-viewer.ts
2121
+ function usePdfViewer() {
2122
+ return usePdfViewerContext();
2123
+ }
2124
+ export {
2125
+ CursorModeToggle,
2126
+ DocumentPropertiesModal,
2127
+ Download2 as Download,
2128
+ DropdownMenu,
2129
+ DropdownMenuItem,
2130
+ DropdownMenuSeparator,
2131
+ FullScreen,
2132
+ MoreMenu,
2133
+ Navigation,
2134
+ Page,
2135
+ Pages,
2136
+ PdfViewer,
2137
+ Print,
2138
+ Root,
2139
+ Rotate,
2140
+ Search,
2141
+ Separator,
2142
+ ThumbnailSidebar,
2143
+ ThumbnailToggle,
2144
+ Toolbar,
2145
+ Zoom,
2146
+ usePdfViewer
2147
+ };
2148
+ //# sourceMappingURL=index.js.map