react-pdf-highlighter-plus 1.0.8 → 1.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/esm/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import debounce from "lodash.debounce";
3
3
  import React6, {
4
4
  useLayoutEffect as useLayoutEffect2,
5
+ useMemo,
5
6
  useRef as useRef5,
6
7
  useState as useState5
7
8
  } from "react";
@@ -1120,6 +1121,21 @@ var PDFViewer;
1120
1121
  var SCROLL_MARGIN = 10;
1121
1122
  var DEFAULT_SCALE_VALUE = "auto";
1122
1123
  var DEFAULT_TEXT_SELECTION_COLOR = "rgba(153,193,218,255)";
1124
+ var defaultLightTheme = {
1125
+ mode: "light",
1126
+ containerBackgroundColor: "#e5e5e5",
1127
+ scrollbarThumbColor: "#9f9f9f",
1128
+ scrollbarTrackColor: "#d1d1d1",
1129
+ darkModeInvertIntensity: 0.9
1130
+ };
1131
+ var defaultDarkTheme = {
1132
+ mode: "dark",
1133
+ containerBackgroundColor: "#3a3a3a",
1134
+ // Lighter than PDF page (~#1a1a1a) for contrast
1135
+ scrollbarThumbColor: "#6b6b6b",
1136
+ scrollbarTrackColor: "#2c2c2c",
1137
+ darkModeInvertIntensity: 0.9
1138
+ };
1123
1139
  var findOrCreateHighlightLayer = (textLayer) => {
1124
1140
  return findOrCreateContainerLayer(
1125
1141
  textLayer,
@@ -1158,8 +1174,14 @@ var PdfHighlighter = ({
1158
1174
  onShapeComplete,
1159
1175
  onShapeCancel,
1160
1176
  shapeStrokeColor = "#000000",
1161
- shapeStrokeWidth = 2
1177
+ shapeStrokeWidth = 2,
1178
+ theme: userTheme
1162
1179
  }) => {
1180
+ const resolvedTheme = useMemo(() => {
1181
+ const mode = userTheme?.mode ?? "light";
1182
+ const defaults = mode === "light" ? defaultLightTheme : defaultDarkTheme;
1183
+ return { ...defaults, ...userTheme, mode };
1184
+ }, [userTheme]);
1163
1185
  const [tip, setTip] = useState5(null);
1164
1186
  const [isViewerReady, setIsViewerReady] = useState5(false);
1165
1187
  const containerNodeRef = useRef5(null);
@@ -1457,17 +1479,27 @@ var PdfHighlighter = ({
1457
1479
  getViewer: () => viewerRef.current,
1458
1480
  getTip: () => tip,
1459
1481
  setTip,
1460
- updateTipPosition: updateTipPositionRef.current
1482
+ updateTipPosition: updateTipPositionRef.current,
1483
+ getLinkService: () => linkServiceRef.current,
1484
+ getEventBus: () => eventBusRef.current,
1485
+ goToPage: (pageNumber) => {
1486
+ viewerRef.current?.scrollPageIntoView({ pageNumber });
1487
+ }
1461
1488
  };
1462
1489
  utilsRef(pdfHighlighterUtils);
1463
1490
  const isFreetextMode = enableFreetextCreation?.({}) ?? false;
1464
1491
  const isImageMode = enableImageCreation?.({}) ?? false;
1465
1492
  let containerClassName = "PdfHighlighter";
1493
+ if (resolvedTheme.mode === "dark") containerClassName += " PdfHighlighter--dark";
1466
1494
  if (isFreetextMode) containerClassName += " PdfHighlighter--freetext-mode";
1467
1495
  if (isImageMode) containerClassName += " PdfHighlighter--image-mode";
1468
1496
  if (enableDrawingMode) containerClassName += " PdfHighlighter--drawing-mode";
1469
1497
  if (enableShapeMode) containerClassName += " PdfHighlighter--shape-mode";
1470
1498
  if (areaSelectionMode) containerClassName += " PdfHighlighter--area-mode";
1499
+ const containerStyle = {
1500
+ ...style,
1501
+ backgroundColor: resolvedTheme.containerBackgroundColor
1502
+ };
1471
1503
  return /* @__PURE__ */ React6.createElement(PdfHighlighterContext.Provider, { value: pdfHighlighterUtils }, /* @__PURE__ */ React6.createElement(
1472
1504
  "div",
1473
1505
  {
@@ -1475,13 +1507,28 @@ var PdfHighlighter = ({
1475
1507
  className: containerClassName,
1476
1508
  onPointerDown: handleMouseDown,
1477
1509
  onPointerUp: handleMouseUp,
1478
- style
1510
+ style: containerStyle
1479
1511
  },
1480
1512
  /* @__PURE__ */ React6.createElement("div", { className: "pdfViewer" }),
1481
1513
  /* @__PURE__ */ React6.createElement("style", null, `
1482
1514
  .textLayer ::selection {
1483
1515
  background: ${textSelectionColor};
1484
1516
  }
1517
+ .PdfHighlighter::-webkit-scrollbar-thumb {
1518
+ background-color: ${resolvedTheme.scrollbarThumbColor};
1519
+ }
1520
+ .PdfHighlighter::-webkit-scrollbar-track,
1521
+ .PdfHighlighter::-webkit-scrollbar-track-piece {
1522
+ background-color: ${resolvedTheme.scrollbarTrackColor};
1523
+ }
1524
+ ${resolvedTheme.mode === "dark" ? `
1525
+ .PdfHighlighter--dark .page {
1526
+ filter: invert(${resolvedTheme.darkModeInvertIntensity}) hue-rotate(180deg) brightness(1.05);
1527
+ }
1528
+ .PdfHighlighter--dark .PdfHighlighter__highlight-layer {
1529
+ filter: invert(${resolvedTheme.darkModeInvertIntensity}) hue-rotate(180deg) brightness(0.95);
1530
+ }
1531
+ ` : ""}
1485
1532
  `),
1486
1533
  isViewerReady && /* @__PURE__ */ React6.createElement(
1487
1534
  TipContainer,
@@ -2445,8 +2492,7 @@ var SignaturePad = ({
2445
2492
  ctx.lineWidth = 2;
2446
2493
  ctx.lineCap = "round";
2447
2494
  ctx.lineJoin = "round";
2448
- ctx.fillStyle = "white";
2449
- ctx.fillRect(0, 0, width, height);
2495
+ ctx.clearRect(0, 0, width, height);
2450
2496
  }, [isOpen, width, height]);
2451
2497
  const getPosition = useCallback3(
2452
2498
  (e) => {
@@ -2528,8 +2574,7 @@ var SignaturePad = ({
2528
2574
  const canvas = canvasRef.current;
2529
2575
  const ctx = canvas?.getContext("2d");
2530
2576
  if (!ctx || !canvas) return;
2531
- ctx.fillStyle = "white";
2532
- ctx.fillRect(0, 0, width, height);
2577
+ ctx.clearRect(0, 0, width, height);
2533
2578
  };
2534
2579
  const handleDone = () => {
2535
2580
  const canvas = canvasRef.current;
@@ -3536,23 +3581,1250 @@ async function exportPdf(pdfSource, highlights, options = {}) {
3536
3581
  }
3537
3582
  return pdfDoc.save();
3538
3583
  }
3584
+
3585
+ // src/components/leftpanel/LeftPanel.tsx
3586
+ import React21, { useState as useState17, useMemo as useMemo4, useCallback as useCallback8 } from "react";
3587
+ import { List, FileText, ChevronLeft, ChevronRight as ChevronRight2 } from "lucide-react";
3588
+
3589
+ // src/contexts/LeftPanelContext.ts
3590
+ import { createContext as createContext3, useContext as useContext3 } from "react";
3591
+ var LeftPanelContext = createContext3(
3592
+ void 0
3593
+ );
3594
+ var useLeftPanelContext = () => {
3595
+ const context = useContext3(LeftPanelContext);
3596
+ if (context === void 0) {
3597
+ throw new Error("useLeftPanelContext must be used within LeftPanel");
3598
+ }
3599
+ return context;
3600
+ };
3601
+
3602
+ // src/hooks/useDocumentOutline.ts
3603
+ import { useState as useState12, useEffect as useEffect12, useCallback as useCallback5, useMemo as useMemo2 } from "react";
3604
+ async function processOutlineItems(pdfDocument, items, level) {
3605
+ const processed = [];
3606
+ for (let i = 0; i < items.length; i++) {
3607
+ const item = items[i];
3608
+ let pageNumber = 1;
3609
+ if (item.dest) {
3610
+ try {
3611
+ const dest = typeof item.dest === "string" ? await pdfDocument.getDestination(item.dest) : item.dest;
3612
+ if (dest && Array.isArray(dest) && dest[0]) {
3613
+ const pageIndex = await pdfDocument.getPageIndex(dest[0]);
3614
+ pageNumber = pageIndex + 1;
3615
+ }
3616
+ } catch {
3617
+ console.log(`Failed to resolve destination for outline item: ${item.title}`);
3618
+ }
3619
+ }
3620
+ const children = item.items?.length ? await processOutlineItems(pdfDocument, item.items, level + 1) : [];
3621
+ processed.push({
3622
+ id: `outline-${level}-${i}`,
3623
+ title: item.title,
3624
+ pageNumber,
3625
+ dest: item.dest,
3626
+ level,
3627
+ bold: item.bold,
3628
+ italic: item.italic,
3629
+ children
3630
+ });
3631
+ }
3632
+ return processed;
3633
+ }
3634
+ function flattenOutline(items) {
3635
+ const result = [];
3636
+ for (const item of items) {
3637
+ result.push(item);
3638
+ if (item.children.length) {
3639
+ result.push(...flattenOutline(item.children));
3640
+ }
3641
+ }
3642
+ return result;
3643
+ }
3644
+ function useDocumentOutline(options) {
3645
+ const { pdfDocument, linkService } = options;
3646
+ const [outline, setOutline] = useState12(null);
3647
+ const [isLoading, setIsLoading] = useState12(true);
3648
+ const [error, setError] = useState12(null);
3649
+ useEffect12(() => {
3650
+ let cancelled = false;
3651
+ async function fetchOutline() {
3652
+ try {
3653
+ setIsLoading(true);
3654
+ setError(null);
3655
+ const rawOutline = await pdfDocument.getOutline();
3656
+ console.log("Raw outline from PDF:", rawOutline);
3657
+ if (cancelled) return;
3658
+ if (!rawOutline || rawOutline.length === 0) {
3659
+ setOutline([]);
3660
+ return;
3661
+ }
3662
+ const processedOutline = await processOutlineItems(
3663
+ pdfDocument,
3664
+ rawOutline,
3665
+ 0
3666
+ );
3667
+ if (cancelled) return;
3668
+ console.log("Processed outline:", processedOutline);
3669
+ setOutline(processedOutline);
3670
+ } catch (err) {
3671
+ if (cancelled) return;
3672
+ console.error("Failed to load outline:", err);
3673
+ setError(err instanceof Error ? err : new Error("Failed to load outline"));
3674
+ } finally {
3675
+ if (!cancelled) {
3676
+ setIsLoading(false);
3677
+ }
3678
+ }
3679
+ }
3680
+ fetchOutline();
3681
+ return () => {
3682
+ cancelled = true;
3683
+ };
3684
+ }, [pdfDocument]);
3685
+ const navigateToItem = useCallback5(
3686
+ (item) => {
3687
+ console.log("Navigating to outline item:", item);
3688
+ if (linkService && item.dest && typeof linkService === "object" && "goToDestination" in linkService) {
3689
+ linkService.goToDestination(item.dest);
3690
+ }
3691
+ },
3692
+ [linkService]
3693
+ );
3694
+ const flatOutline = useMemo2(() => {
3695
+ if (!outline) return [];
3696
+ return flattenOutline(outline);
3697
+ }, [outline]);
3698
+ const hasOutline = useMemo2(() => {
3699
+ return outline !== null && outline.length > 0;
3700
+ }, [outline]);
3701
+ return { outline, isLoading, error, navigateToItem, flatOutline, hasOutline };
3702
+ }
3703
+
3704
+ // src/hooks/useThumbnails.ts
3705
+ import { useState as useState13, useCallback as useCallback6, useRef as useRef15, useEffect as useEffect13 } from "react";
3706
+ function useThumbnails(options) {
3707
+ const { pdfDocument, thumbnailWidth = 200, cacheSize = 50, preloadAll = true } = options;
3708
+ const [thumbnails, setThumbnails] = useState13(
3709
+ /* @__PURE__ */ new Map()
3710
+ );
3711
+ const loadingRef = useRef15(/* @__PURE__ */ new Set());
3712
+ const loadedRef = useRef15(/* @__PURE__ */ new Set());
3713
+ const cacheOrderRef = useRef15([]);
3714
+ const totalPages = pdfDocument.numPages;
3715
+ const loadThumbnail = useCallback6(
3716
+ async (pageNumber) => {
3717
+ if (loadingRef.current.has(pageNumber) || loadedRef.current.has(pageNumber)) {
3718
+ return;
3719
+ }
3720
+ loadingRef.current.add(pageNumber);
3721
+ setThumbnails((prev) => {
3722
+ const next = new Map(prev);
3723
+ next.set(pageNumber, {
3724
+ pageNumber,
3725
+ dataUrl: null,
3726
+ isLoading: true
3727
+ });
3728
+ return next;
3729
+ });
3730
+ try {
3731
+ const page = await pdfDocument.getPage(pageNumber);
3732
+ const viewport = page.getViewport({ scale: 1 });
3733
+ const scale = thumbnailWidth / viewport.width;
3734
+ const scaledViewport = page.getViewport({ scale });
3735
+ const canvas = document.createElement("canvas");
3736
+ canvas.width = scaledViewport.width;
3737
+ canvas.height = scaledViewport.height;
3738
+ const context = canvas.getContext("2d");
3739
+ await page.render({
3740
+ canvasContext: context,
3741
+ viewport: scaledViewport
3742
+ }).promise;
3743
+ const dataUrl = canvas.toDataURL("image/png");
3744
+ cacheOrderRef.current = cacheOrderRef.current.filter(
3745
+ (p) => p !== pageNumber
3746
+ );
3747
+ cacheOrderRef.current.push(pageNumber);
3748
+ if (cacheOrderRef.current.length > cacheSize) {
3749
+ const toEvict = cacheOrderRef.current.shift();
3750
+ setThumbnails((prev) => {
3751
+ const next = new Map(prev);
3752
+ next.delete(toEvict);
3753
+ return next;
3754
+ });
3755
+ }
3756
+ loadedRef.current.add(pageNumber);
3757
+ setThumbnails((prev) => {
3758
+ const next = new Map(prev);
3759
+ next.set(pageNumber, {
3760
+ pageNumber,
3761
+ dataUrl,
3762
+ isLoading: false
3763
+ });
3764
+ return next;
3765
+ });
3766
+ } catch (error) {
3767
+ console.error(`Failed to load thumbnail for page ${pageNumber}:`, error);
3768
+ setThumbnails((prev) => {
3769
+ const next = new Map(prev);
3770
+ next.set(pageNumber, {
3771
+ pageNumber,
3772
+ dataUrl: null,
3773
+ isLoading: false,
3774
+ error: error instanceof Error ? error.message : "Failed to load"
3775
+ });
3776
+ return next;
3777
+ });
3778
+ } finally {
3779
+ loadingRef.current.delete(pageNumber);
3780
+ }
3781
+ },
3782
+ [pdfDocument, thumbnailWidth, cacheSize]
3783
+ );
3784
+ const getThumbnail = useCallback6(
3785
+ (pageNumber) => {
3786
+ return thumbnails.get(pageNumber) || {
3787
+ pageNumber,
3788
+ dataUrl: null,
3789
+ isLoading: false
3790
+ };
3791
+ },
3792
+ [thumbnails]
3793
+ );
3794
+ const preloadThumbnails = useCallback6(
3795
+ (pageNumbers) => {
3796
+ pageNumbers.forEach((pageNumber) => {
3797
+ loadThumbnail(pageNumber);
3798
+ });
3799
+ },
3800
+ [loadThumbnail]
3801
+ );
3802
+ const clearCache = useCallback6(() => {
3803
+ setThumbnails(/* @__PURE__ */ new Map());
3804
+ cacheOrderRef.current = [];
3805
+ loadingRef.current.clear();
3806
+ loadedRef.current.clear();
3807
+ }, []);
3808
+ useEffect13(() => {
3809
+ if (preloadAll && totalPages > 0) {
3810
+ const pageNumbers = Array.from({ length: totalPages }, (_, i) => i + 1);
3811
+ pageNumbers.forEach((pageNumber) => {
3812
+ loadThumbnail(pageNumber);
3813
+ });
3814
+ }
3815
+ }, [preloadAll, totalPages, loadThumbnail]);
3816
+ useEffect13(() => {
3817
+ return () => {
3818
+ loadingRef.current.clear();
3819
+ };
3820
+ }, []);
3821
+ return {
3822
+ getThumbnail,
3823
+ loadThumbnail,
3824
+ preloadThumbnails,
3825
+ clearCache,
3826
+ totalPages,
3827
+ thumbnails
3828
+ };
3829
+ }
3830
+
3831
+ // src/hooks/usePageNavigation.ts
3832
+ import { useState as useState14, useEffect as useEffect14, useCallback as useCallback7 } from "react";
3833
+ function usePageNavigation(options) {
3834
+ const { viewer, eventBus } = options;
3835
+ const [currentPage, setCurrentPage] = useState14(1);
3836
+ const isEventBus = (obj) => {
3837
+ return obj !== null && typeof obj === "object" && "on" in obj && "off" in obj;
3838
+ };
3839
+ const isViewer = (obj) => {
3840
+ return obj !== null && typeof obj === "object" && "scrollPageIntoView" in obj;
3841
+ };
3842
+ useEffect14(() => {
3843
+ if (!eventBus || !isEventBus(eventBus)) return;
3844
+ const handlePageChange = (evt) => {
3845
+ setCurrentPage(evt.pageNumber);
3846
+ };
3847
+ eventBus.on("pagechanging", handlePageChange);
3848
+ return () => {
3849
+ eventBus.off("pagechanging", handlePageChange);
3850
+ };
3851
+ }, [eventBus]);
3852
+ useEffect14(() => {
3853
+ if (viewer && isViewer(viewer) && viewer.currentPageNumber) {
3854
+ setCurrentPage(viewer.currentPageNumber);
3855
+ }
3856
+ }, [viewer]);
3857
+ const goToPage = useCallback7(
3858
+ (pageNumber) => {
3859
+ if (viewer && isViewer(viewer)) {
3860
+ const totalPages2 = viewer.pagesCount || 0;
3861
+ if (pageNumber >= 1 && pageNumber <= totalPages2) {
3862
+ viewer.scrollPageIntoView({ pageNumber });
3863
+ return;
3864
+ }
3865
+ }
3866
+ const pageElement = document.querySelector(
3867
+ `.page[data-page-number="${pageNumber}"]`
3868
+ );
3869
+ if (pageElement) {
3870
+ pageElement.scrollIntoView({ behavior: "smooth", block: "start" });
3871
+ setCurrentPage(pageNumber);
3872
+ }
3873
+ },
3874
+ [viewer]
3875
+ );
3876
+ const totalPages = viewer && isViewer(viewer) ? viewer.pagesCount || 0 : 0;
3877
+ return {
3878
+ currentPage,
3879
+ totalPages,
3880
+ goToPage
3881
+ };
3882
+ }
3883
+
3884
+ // src/components/leftpanel/DocumentOutline.tsx
3885
+ import React18 from "react";
3886
+ import { Loader2, FileQuestion } from "lucide-react";
3887
+
3888
+ // src/components/leftpanel/OutlineItem.tsx
3889
+ import React17, { useState as useState15 } from "react";
3890
+ import { ChevronRight, ChevronDown } from "lucide-react";
3891
+ var OutlineItem = ({
3892
+ item,
3893
+ currentPage,
3894
+ onNavigate,
3895
+ defaultExpanded = true,
3896
+ showExpandIcons = true,
3897
+ maxDepth = 10,
3898
+ styles,
3899
+ classNames,
3900
+ renderItem
3901
+ }) => {
3902
+ const [isExpanded, setIsExpanded] = useState15(defaultExpanded);
3903
+ const [isHovered, setIsHovered] = useState15(false);
3904
+ const hasChildren = item.children && item.children.length > 0;
3905
+ const isActive = currentPage === item.pageNumber;
3906
+ const canShowChildren = item.level < maxDepth;
3907
+ const handleToggle = () => {
3908
+ if (hasChildren) {
3909
+ setIsExpanded(!isExpanded);
3910
+ }
3911
+ };
3912
+ const handleNavigate = () => {
3913
+ onNavigate(item);
3914
+ };
3915
+ const renderProps = {
3916
+ isExpanded,
3917
+ isActive,
3918
+ level: item.level,
3919
+ hasChildren,
3920
+ onToggle: handleToggle,
3921
+ onNavigate: handleNavigate
3922
+ };
3923
+ if (renderItem) {
3924
+ return /* @__PURE__ */ React17.createElement(React17.Fragment, null, renderItem(item, renderProps));
3925
+ }
3926
+ const indent = 12 + item.level * 16;
3927
+ const defaultContainerStyle = {
3928
+ display: "flex",
3929
+ alignItems: "center",
3930
+ gap: "6px",
3931
+ padding: "6px 12px",
3932
+ paddingLeft: `${indent}px`,
3933
+ cursor: "pointer",
3934
+ position: "relative",
3935
+ transition: "background-color 0.15s ease",
3936
+ backgroundColor: "transparent",
3937
+ borderRadius: "4px",
3938
+ margin: "1px 4px"
3939
+ };
3940
+ const defaultExpandButtonStyle = {
3941
+ padding: "2px",
3942
+ border: "none",
3943
+ background: "transparent",
3944
+ cursor: "pointer",
3945
+ display: "flex",
3946
+ alignItems: "center",
3947
+ justifyContent: "center",
3948
+ borderRadius: "3px",
3949
+ flexShrink: 0,
3950
+ transition: "background-color 0.15s ease"
3951
+ };
3952
+ const defaultExpandIconStyle = {
3953
+ width: 12,
3954
+ height: 12,
3955
+ color: "#64748b"
3956
+ };
3957
+ const defaultActiveIndicatorStyle = {
3958
+ width: 6,
3959
+ height: 6,
3960
+ borderRadius: "50%",
3961
+ backgroundColor: "#3b82f6",
3962
+ flexShrink: 0
3963
+ };
3964
+ const defaultTitleStyle = {
3965
+ flex: 1,
3966
+ overflow: "hidden",
3967
+ textOverflow: "ellipsis",
3968
+ whiteSpace: "nowrap",
3969
+ fontSize: "13px",
3970
+ fontWeight: 400,
3971
+ fontStyle: item.italic ? "italic" : "normal",
3972
+ color: "#475569",
3973
+ lineHeight: 1.4
3974
+ };
3975
+ const defaultTitleActiveStyle = {
3976
+ fontWeight: 500,
3977
+ color: "#1e40af"
3978
+ };
3979
+ const defaultPageNumberStyle = {
3980
+ fontSize: "11px",
3981
+ color: "#94a3b8",
3982
+ flexShrink: 0,
3983
+ fontVariantNumeric: "tabular-nums",
3984
+ minWidth: "20px",
3985
+ textAlign: "right"
3986
+ };
3987
+ const defaultPageNumberActiveStyle = {
3988
+ color: "#3b82f6"
3989
+ };
3990
+ const defaultChildrenContainerStyle = {
3991
+ position: "relative"
3992
+ };
3993
+ const containerStyle = {
3994
+ ...defaultContainerStyle,
3995
+ ...styles?.container,
3996
+ ...isHovered && !isActive ? { backgroundColor: "#f8fafc", ...styles?.containerHover } : {},
3997
+ ...isActive ? styles?.containerActive : {}
3998
+ };
3999
+ const expandButtonStyle = {
4000
+ ...defaultExpandButtonStyle,
4001
+ ...styles?.expandButton
4002
+ };
4003
+ const expandIconStyle = {
4004
+ ...defaultExpandIconStyle,
4005
+ ...styles?.expandIcon
4006
+ };
4007
+ const activeIndicatorStyle = {
4008
+ ...defaultActiveIndicatorStyle,
4009
+ ...styles?.activeIndicator
4010
+ };
4011
+ const titleStyle = {
4012
+ ...defaultTitleStyle,
4013
+ ...styles?.title,
4014
+ ...isActive ? { ...defaultTitleActiveStyle, ...styles?.titleActive } : {}
4015
+ };
4016
+ const pageNumberStyle = {
4017
+ ...defaultPageNumberStyle,
4018
+ ...styles?.pageNumber,
4019
+ ...isActive ? { ...defaultPageNumberActiveStyle, ...styles?.pageNumberActive } : {}
4020
+ };
4021
+ const childrenContainerStyle = {
4022
+ ...defaultChildrenContainerStyle,
4023
+ ...styles?.childrenContainer
4024
+ };
4025
+ const containerClassName = [
4026
+ classNames?.container,
4027
+ isHovered && !isActive ? classNames?.containerHover : "",
4028
+ isActive ? classNames?.containerActive : ""
4029
+ ].filter(Boolean).join(" ");
4030
+ const titleClassName = [
4031
+ classNames?.title,
4032
+ isActive ? classNames?.titleActive : ""
4033
+ ].filter(Boolean).join(" ");
4034
+ const pageNumberClassName = [
4035
+ classNames?.pageNumber,
4036
+ isActive ? classNames?.pageNumberActive : ""
4037
+ ].filter(Boolean).join(" ");
4038
+ return /* @__PURE__ */ React17.createElement("div", { className: "outline-item" }, /* @__PURE__ */ React17.createElement(
4039
+ "div",
4040
+ {
4041
+ className: containerClassName || void 0,
4042
+ style: containerStyle,
4043
+ onClick: handleNavigate,
4044
+ onMouseEnter: () => setIsHovered(true),
4045
+ onMouseLeave: () => setIsHovered(false),
4046
+ role: "button",
4047
+ tabIndex: 0,
4048
+ onKeyDown: (e) => {
4049
+ if (e.key === "Enter" || e.key === " ") {
4050
+ e.preventDefault();
4051
+ handleNavigate();
4052
+ }
4053
+ }
4054
+ },
4055
+ showExpandIcons && hasChildren && canShowChildren ? /* @__PURE__ */ React17.createElement(
4056
+ "button",
4057
+ {
4058
+ className: classNames?.expandButton,
4059
+ onClick: (e) => {
4060
+ e.stopPropagation();
4061
+ handleToggle();
4062
+ },
4063
+ style: expandButtonStyle,
4064
+ onMouseEnter: (e) => {
4065
+ e.currentTarget.style.backgroundColor = "#e5e7eb";
4066
+ },
4067
+ onMouseLeave: (e) => {
4068
+ e.currentTarget.style.backgroundColor = "transparent";
4069
+ },
4070
+ "aria-label": isExpanded ? "Collapse" : "Expand"
4071
+ },
4072
+ isExpanded ? /* @__PURE__ */ React17.createElement(ChevronDown, { className: classNames?.expandIcon, style: expandIconStyle }) : /* @__PURE__ */ React17.createElement(ChevronRight, { className: classNames?.expandIcon, style: expandIconStyle })
4073
+ ) : /* @__PURE__ */ React17.createElement("div", { style: { width: 16, flexShrink: 0 } }),
4074
+ isActive && /* @__PURE__ */ React17.createElement("div", { className: classNames?.activeIndicator, style: activeIndicatorStyle }),
4075
+ /* @__PURE__ */ React17.createElement("span", { className: titleClassName || void 0, style: titleStyle, title: item.title }, item.title),
4076
+ /* @__PURE__ */ React17.createElement("span", { className: pageNumberClassName || void 0, style: pageNumberStyle }, item.pageNumber)
4077
+ ), hasChildren && isExpanded && canShowChildren && /* @__PURE__ */ React17.createElement("div", { className: classNames?.childrenContainer, style: childrenContainerStyle }, item.children.map((child) => /* @__PURE__ */ React17.createElement(
4078
+ OutlineItem,
4079
+ {
4080
+ key: child.id,
4081
+ item: child,
4082
+ currentPage,
4083
+ onNavigate,
4084
+ defaultExpanded,
4085
+ showExpandIcons,
4086
+ maxDepth,
4087
+ styles,
4088
+ classNames,
4089
+ renderItem
4090
+ }
4091
+ ))));
4092
+ };
4093
+
4094
+ // src/components/leftpanel/DocumentOutline.tsx
4095
+ var DocumentOutline = ({
4096
+ outline,
4097
+ isLoading = false,
4098
+ currentPage = 1,
4099
+ onNavigate,
4100
+ showExpandIcons = true,
4101
+ defaultExpanded = true,
4102
+ maxDepth = 10,
4103
+ className = "",
4104
+ styles,
4105
+ classNames,
4106
+ itemStyles,
4107
+ itemClassNames,
4108
+ renderItem,
4109
+ emptyContent,
4110
+ loadingContent
4111
+ }) => {
4112
+ const defaultLoadingContainerStyle = {
4113
+ display: "flex",
4114
+ flexDirection: "column",
4115
+ alignItems: "center",
4116
+ justifyContent: "center",
4117
+ padding: "48px 16px"
4118
+ };
4119
+ const defaultLoadingSpinnerStyle = {
4120
+ width: 28,
4121
+ height: 28,
4122
+ color: "#94a3b8",
4123
+ marginBottom: 12,
4124
+ animation: "spin 1s linear infinite"
4125
+ };
4126
+ const defaultLoadingTextStyle = {
4127
+ fontSize: 13,
4128
+ color: "#64748b"
4129
+ };
4130
+ const defaultEmptyContainerStyle = {
4131
+ display: "flex",
4132
+ flexDirection: "column",
4133
+ alignItems: "center",
4134
+ justifyContent: "center",
4135
+ padding: "48px 20px"
4136
+ };
4137
+ const defaultEmptyIconContainerStyle = {
4138
+ width: 56,
4139
+ height: 56,
4140
+ borderRadius: "50%",
4141
+ backgroundColor: "#f1f5f9",
4142
+ display: "flex",
4143
+ alignItems: "center",
4144
+ justifyContent: "center",
4145
+ marginBottom: 16
4146
+ };
4147
+ const defaultEmptyIconStyle = {
4148
+ width: 28,
4149
+ height: 28,
4150
+ color: "#94a3b8"
4151
+ };
4152
+ const defaultEmptyTitleStyle = {
4153
+ fontSize: 14,
4154
+ fontWeight: 500,
4155
+ color: "#475569",
4156
+ marginBottom: 4
4157
+ };
4158
+ const defaultEmptyDescriptionStyle = {
4159
+ fontSize: 13,
4160
+ color: "#94a3b8",
4161
+ textAlign: "center"
4162
+ };
4163
+ const defaultContainerStyle = {
4164
+ paddingTop: "8px",
4165
+ paddingBottom: "8px"
4166
+ };
4167
+ if (isLoading) {
4168
+ return /* @__PURE__ */ React18.createElement(
4169
+ "div",
4170
+ {
4171
+ className: [className, classNames?.loadingContainer].filter(Boolean).join(" ") || void 0,
4172
+ style: { ...defaultLoadingContainerStyle, ...styles?.loadingContainer }
4173
+ },
4174
+ loadingContent || /* @__PURE__ */ React18.createElement(React18.Fragment, null, /* @__PURE__ */ React18.createElement(Loader2, { className: classNames?.loadingSpinner, style: { ...defaultLoadingSpinnerStyle, ...styles?.loadingSpinner } }), /* @__PURE__ */ React18.createElement("p", { className: classNames?.loadingText, style: { ...defaultLoadingTextStyle, ...styles?.loadingText } }, "Loading outline..."))
4175
+ );
4176
+ }
4177
+ if (!outline || outline.length === 0) {
4178
+ return /* @__PURE__ */ React18.createElement(
4179
+ "div",
4180
+ {
4181
+ className: [className, classNames?.emptyContainer].filter(Boolean).join(" ") || void 0,
4182
+ style: { ...defaultEmptyContainerStyle, ...styles?.emptyContainer }
4183
+ },
4184
+ emptyContent || /* @__PURE__ */ React18.createElement(React18.Fragment, null, /* @__PURE__ */ React18.createElement("div", { className: classNames?.emptyIconContainer, style: { ...defaultEmptyIconContainerStyle, ...styles?.emptyIconContainer } }, /* @__PURE__ */ React18.createElement(FileQuestion, { className: classNames?.emptyIcon, style: { ...defaultEmptyIconStyle, ...styles?.emptyIcon } })), /* @__PURE__ */ React18.createElement("p", { className: classNames?.emptyTitle, style: { ...defaultEmptyTitleStyle, ...styles?.emptyTitle } }, "No outline available"), /* @__PURE__ */ React18.createElement("p", { className: classNames?.emptyDescription, style: { ...defaultEmptyDescriptionStyle, ...styles?.emptyDescription } }, "This document doesn't have a table of contents"))
4185
+ );
4186
+ }
4187
+ return /* @__PURE__ */ React18.createElement(
4188
+ "div",
4189
+ {
4190
+ className: ["document-outline", className, classNames?.container].filter(Boolean).join(" "),
4191
+ style: { ...defaultContainerStyle, ...styles?.container }
4192
+ },
4193
+ outline.map((item) => /* @__PURE__ */ React18.createElement(
4194
+ OutlineItem,
4195
+ {
4196
+ key: item.id,
4197
+ item,
4198
+ currentPage,
4199
+ onNavigate,
4200
+ defaultExpanded,
4201
+ showExpandIcons,
4202
+ maxDepth,
4203
+ styles: itemStyles,
4204
+ classNames: itemClassNames,
4205
+ renderItem
4206
+ }
4207
+ ))
4208
+ );
4209
+ };
4210
+
4211
+ // src/components/leftpanel/ThumbnailPanel.tsx
4212
+ import React20, { useMemo as useMemo3 } from "react";
4213
+
4214
+ // src/components/leftpanel/ThumbnailItem.tsx
4215
+ import React19, { useEffect as useEffect15, useRef as useRef16, useState as useState16 } from "react";
4216
+ import { Loader2 as Loader22, AlertCircle } from "lucide-react";
4217
+ var ThumbnailItem = ({
4218
+ pageNumber,
4219
+ thumbnail,
4220
+ isActive,
4221
+ onLoad,
4222
+ onClick,
4223
+ showPageNumber = true,
4224
+ className = ""
4225
+ }) => {
4226
+ const containerRef = useRef16(null);
4227
+ const hasLoadedRef = useRef16(false);
4228
+ const [isHovered, setIsHovered] = useState16(false);
4229
+ useEffect15(() => {
4230
+ const element = containerRef.current;
4231
+ if (!element || hasLoadedRef.current) return;
4232
+ const observer = new IntersectionObserver(
4233
+ (entries) => {
4234
+ entries.forEach((entry) => {
4235
+ if (entry.isIntersecting && !hasLoadedRef.current) {
4236
+ hasLoadedRef.current = true;
4237
+ onLoad(pageNumber);
4238
+ observer.disconnect();
4239
+ }
4240
+ });
4241
+ },
4242
+ {
4243
+ rootMargin: "200px",
4244
+ threshold: 0.1
4245
+ }
4246
+ );
4247
+ observer.observe(element);
4248
+ return () => {
4249
+ observer.disconnect();
4250
+ };
4251
+ }, [pageNumber, onLoad]);
4252
+ const handleClick = () => {
4253
+ onClick(pageNumber);
4254
+ };
4255
+ const containerStyle = {
4256
+ display: "flex",
4257
+ flexDirection: "column",
4258
+ alignItems: "center",
4259
+ padding: "8px",
4260
+ cursor: "pointer",
4261
+ borderRadius: "8px",
4262
+ transition: "all 0.15s ease",
4263
+ backgroundColor: isActive ? "rgba(59, 130, 246, 0.08)" : isHovered ? "rgba(0, 0, 0, 0.03)" : "transparent",
4264
+ border: isActive ? "2px solid #3b82f6" : "2px solid transparent"
4265
+ };
4266
+ const imageContainerStyle = {
4267
+ position: "relative",
4268
+ width: "100%",
4269
+ backgroundColor: "#ffffff",
4270
+ borderRadius: "4px",
4271
+ overflow: "hidden",
4272
+ boxShadow: isActive ? "0 4px 12px rgba(59, 130, 246, 0.25)" : isHovered ? "0 4px 12px rgba(0, 0, 0, 0.12)" : "0 1px 3px rgba(0, 0, 0, 0.08)",
4273
+ aspectRatio: "8.5 / 11",
4274
+ transition: "box-shadow 0.15s ease"
4275
+ };
4276
+ const pageNumberStyle = {
4277
+ marginTop: "8px",
4278
+ fontSize: "12px",
4279
+ fontWeight: 500,
4280
+ color: isActive ? "#3b82f6" : "#6b7280",
4281
+ transition: "color 0.15s ease"
4282
+ };
4283
+ return /* @__PURE__ */ React19.createElement(
4284
+ "div",
4285
+ {
4286
+ ref: containerRef,
4287
+ className: `thumbnail-item ${className}`,
4288
+ style: containerStyle,
4289
+ onClick: handleClick,
4290
+ onMouseEnter: () => setIsHovered(true),
4291
+ onMouseLeave: () => setIsHovered(false),
4292
+ role: "button",
4293
+ tabIndex: 0,
4294
+ onKeyDown: (e) => {
4295
+ if (e.key === "Enter" || e.key === " ") {
4296
+ e.preventDefault();
4297
+ handleClick();
4298
+ }
4299
+ },
4300
+ "aria-label": `Page ${pageNumber}${isActive ? " (current)" : ""}`,
4301
+ "aria-current": isActive ? "page" : void 0
4302
+ },
4303
+ /* @__PURE__ */ React19.createElement("div", { style: imageContainerStyle }, thumbnail.isLoading && /* @__PURE__ */ React19.createElement(
4304
+ "div",
4305
+ {
4306
+ style: {
4307
+ position: "absolute",
4308
+ inset: 0,
4309
+ display: "flex",
4310
+ alignItems: "center",
4311
+ justifyContent: "center",
4312
+ backgroundColor: "#f9fafb"
4313
+ }
4314
+ },
4315
+ /* @__PURE__ */ React19.createElement(
4316
+ Loader22,
4317
+ {
4318
+ style: {
4319
+ width: 24,
4320
+ height: 24,
4321
+ color: "#9ca3af",
4322
+ animation: "spin 1s linear infinite"
4323
+ }
4324
+ }
4325
+ )
4326
+ ), thumbnail.error && !thumbnail.isLoading && /* @__PURE__ */ React19.createElement(
4327
+ "div",
4328
+ {
4329
+ style: {
4330
+ position: "absolute",
4331
+ inset: 0,
4332
+ display: "flex",
4333
+ flexDirection: "column",
4334
+ alignItems: "center",
4335
+ justifyContent: "center",
4336
+ backgroundColor: "#fef2f2"
4337
+ }
4338
+ },
4339
+ /* @__PURE__ */ React19.createElement(AlertCircle, { style: { width: 20, height: 20, color: "#f87171", marginBottom: 4 } }),
4340
+ /* @__PURE__ */ React19.createElement("span", { style: { fontSize: 10, color: "#9ca3af" } }, "Failed")
4341
+ ), thumbnail.dataUrl && !thumbnail.isLoading && /* @__PURE__ */ React19.createElement(
4342
+ "img",
4343
+ {
4344
+ src: thumbnail.dataUrl,
4345
+ alt: `Page ${pageNumber}`,
4346
+ style: {
4347
+ width: "100%",
4348
+ height: "100%",
4349
+ objectFit: "contain"
4350
+ },
4351
+ draggable: false
4352
+ }
4353
+ ), !thumbnail.dataUrl && !thumbnail.isLoading && !thumbnail.error && /* @__PURE__ */ React19.createElement(
4354
+ "div",
4355
+ {
4356
+ style: {
4357
+ position: "absolute",
4358
+ inset: 0,
4359
+ display: "flex",
4360
+ alignItems: "center",
4361
+ justifyContent: "center",
4362
+ backgroundColor: "#f9fafb"
4363
+ }
4364
+ },
4365
+ /* @__PURE__ */ React19.createElement(
4366
+ "span",
4367
+ {
4368
+ style: {
4369
+ fontSize: 24,
4370
+ fontWeight: 300,
4371
+ color: "#d1d5db"
4372
+ }
4373
+ },
4374
+ pageNumber
4375
+ )
4376
+ )),
4377
+ showPageNumber && /* @__PURE__ */ React19.createElement("div", { style: pageNumberStyle }, pageNumber)
4378
+ );
4379
+ };
4380
+
4381
+ // src/components/leftpanel/ThumbnailPanel.tsx
4382
+ var ThumbnailPanel = ({
4383
+ totalPages,
4384
+ currentPage,
4385
+ getThumbnail,
4386
+ loadThumbnail,
4387
+ onPageSelect,
4388
+ showPageNumbers = true,
4389
+ className = "",
4390
+ renderThumbnail
4391
+ }) => {
4392
+ const pageNumbers = useMemo3(() => {
4393
+ return Array.from({ length: totalPages }, (_, i) => i + 1);
4394
+ }, [totalPages]);
4395
+ if (totalPages === 0) {
4396
+ return /* @__PURE__ */ React20.createElement(
4397
+ "div",
4398
+ {
4399
+ className,
4400
+ style: {
4401
+ display: "flex",
4402
+ alignItems: "center",
4403
+ justifyContent: "center",
4404
+ padding: "48px 16px"
4405
+ }
4406
+ },
4407
+ /* @__PURE__ */ React20.createElement("p", { style: { fontSize: 13, color: "#9ca3af" } }, "No pages to display")
4408
+ );
4409
+ }
4410
+ return /* @__PURE__ */ React20.createElement(
4411
+ "div",
4412
+ {
4413
+ className: `thumbnail-panel ${className}`,
4414
+ style: {
4415
+ padding: "12px"
4416
+ }
4417
+ },
4418
+ /* @__PURE__ */ React20.createElement(
4419
+ "div",
4420
+ {
4421
+ style: {
4422
+ display: "flex",
4423
+ flexDirection: "column",
4424
+ gap: "8px"
4425
+ }
4426
+ },
4427
+ pageNumbers.map((pageNumber) => {
4428
+ const thumbnail = getThumbnail(pageNumber);
4429
+ const isActive = currentPage === pageNumber;
4430
+ if (renderThumbnail) {
4431
+ return /* @__PURE__ */ React20.createElement(
4432
+ "div",
4433
+ {
4434
+ key: pageNumber,
4435
+ onClick: () => onPageSelect(pageNumber),
4436
+ style: { cursor: "pointer" }
4437
+ },
4438
+ renderThumbnail(pageNumber, thumbnail, isActive)
4439
+ );
4440
+ }
4441
+ return /* @__PURE__ */ React20.createElement(
4442
+ ThumbnailItem,
4443
+ {
4444
+ key: pageNumber,
4445
+ pageNumber,
4446
+ thumbnail,
4447
+ isActive,
4448
+ onLoad: loadThumbnail,
4449
+ onClick: onPageSelect,
4450
+ showPageNumber: showPageNumbers
4451
+ }
4452
+ );
4453
+ })
4454
+ )
4455
+ );
4456
+ };
4457
+
4458
+ // src/components/leftpanel/LeftPanel.tsx
4459
+ var defaultTheme = {
4460
+ backgroundColor: "#ffffff",
4461
+ borderColor: "#e5e7eb",
4462
+ accentColor: "#3b82f6",
4463
+ textColor: "#374151",
4464
+ mutedTextColor: "#6b7280",
4465
+ hoverBackgroundColor: "#f9fafb"
4466
+ };
4467
+ var LeftPanel = ({
4468
+ pdfDocument,
4469
+ viewer = null,
4470
+ linkService = null,
4471
+ eventBus = null,
4472
+ isOpen: controlledIsOpen,
4473
+ onOpenChange,
4474
+ defaultTab = "thumbnails",
4475
+ tabs = ["outline", "thumbnails"],
4476
+ width = 260,
4477
+ className = "",
4478
+ style,
4479
+ renderOutlineItem,
4480
+ renderThumbnail,
4481
+ onPageSelect,
4482
+ thumbnailWidth = 180,
4483
+ children,
4484
+ theme: userTheme,
4485
+ showFooter = true,
4486
+ showToggleButton = true,
4487
+ tabStyles,
4488
+ tabClassNames,
4489
+ footerStyles,
4490
+ footerClassNames,
4491
+ toggleButtonStyles,
4492
+ toggleButtonClassNames,
4493
+ outlineStyles,
4494
+ outlineClassNames,
4495
+ outlineItemStyles,
4496
+ outlineItemClassNames
4497
+ }) => {
4498
+ const theme = useMemo4(() => ({ ...defaultTheme, ...userTheme }), [userTheme]);
4499
+ const [internalIsOpen, setInternalIsOpen] = useState17(true);
4500
+ const [activeTab, setActiveTab] = useState17(defaultTab);
4501
+ const isOpen = controlledIsOpen !== void 0 ? controlledIsOpen : internalIsOpen;
4502
+ const setIsOpen = useCallback8(
4503
+ (open) => {
4504
+ if (onOpenChange) {
4505
+ onOpenChange(open);
4506
+ } else {
4507
+ setInternalIsOpen(open);
4508
+ }
4509
+ },
4510
+ [onOpenChange]
4511
+ );
4512
+ const {
4513
+ outline,
4514
+ isLoading: isOutlineLoading,
4515
+ hasOutline,
4516
+ navigateToItem
4517
+ } = useDocumentOutline({
4518
+ pdfDocument,
4519
+ linkService
4520
+ });
4521
+ const { getThumbnail, loadThumbnail, totalPages } = useThumbnails({
4522
+ pdfDocument,
4523
+ thumbnailWidth
4524
+ });
4525
+ const { currentPage, goToPage } = usePageNavigation({
4526
+ viewer,
4527
+ eventBus
4528
+ });
4529
+ const handlePageSelect = useCallback8(
4530
+ (pageNumber) => {
4531
+ goToPage(pageNumber);
4532
+ onPageSelect?.(pageNumber);
4533
+ },
4534
+ [goToPage, onPageSelect]
4535
+ );
4536
+ const handleOutlineNavigate = useCallback8(
4537
+ (item) => {
4538
+ navigateToItem(item);
4539
+ onPageSelect?.(item.pageNumber);
4540
+ },
4541
+ [navigateToItem, onPageSelect]
4542
+ );
4543
+ const contextValue = useMemo4(
4544
+ () => ({
4545
+ currentPage,
4546
+ totalPages,
4547
+ goToPage: handlePageSelect,
4548
+ goToOutlineItem: handleOutlineNavigate,
4549
+ pdfDocument,
4550
+ outline,
4551
+ hasOutline,
4552
+ isOutlineLoading,
4553
+ getThumbnail,
4554
+ loadThumbnail,
4555
+ activeTab,
4556
+ setActiveTab,
4557
+ isOpen,
4558
+ setIsOpen
4559
+ }),
4560
+ [
4561
+ currentPage,
4562
+ totalPages,
4563
+ handlePageSelect,
4564
+ handleOutlineNavigate,
4565
+ pdfDocument,
4566
+ outline,
4567
+ hasOutline,
4568
+ isOutlineLoading,
4569
+ getThumbnail,
4570
+ loadThumbnail,
4571
+ activeTab,
4572
+ isOpen,
4573
+ setIsOpen
4574
+ ]
4575
+ );
4576
+ const panelWidth = typeof width === "number" ? `${width}px` : width;
4577
+ const cssVars = {
4578
+ "--lp-bg": theme.backgroundColor,
4579
+ "--lp-border": theme.borderColor,
4580
+ "--lp-accent": theme.accentColor,
4581
+ "--lp-text": theme.textColor,
4582
+ "--lp-muted": theme.mutedTextColor,
4583
+ "--lp-hover": theme.hoverBackgroundColor
4584
+ };
4585
+ return /* @__PURE__ */ React21.createElement(LeftPanelContext.Provider, { value: contextValue }, showToggleButton && /* @__PURE__ */ React21.createElement(
4586
+ "button",
4587
+ {
4588
+ className: toggleButtonClassNames?.button,
4589
+ onClick: () => setIsOpen(!isOpen),
4590
+ style: {
4591
+ position: "absolute",
4592
+ top: "50%",
4593
+ transform: "translateY(-50%)",
4594
+ left: isOpen ? `calc(${panelWidth} - 1px)` : "0",
4595
+ zIndex: 20,
4596
+ width: "24px",
4597
+ height: "48px",
4598
+ backgroundColor: theme.backgroundColor,
4599
+ border: `1px solid ${theme.borderColor}`,
4600
+ borderLeft: "none",
4601
+ borderRadius: "0 6px 6px 0",
4602
+ display: "flex",
4603
+ alignItems: "center",
4604
+ justifyContent: "center",
4605
+ cursor: "pointer",
4606
+ boxShadow: "2px 0 8px rgba(0,0,0,0.08)",
4607
+ transition: "left 0.2s ease-in-out, background-color 0.15s ease",
4608
+ ...toggleButtonStyles?.button
4609
+ },
4610
+ onMouseEnter: (e) => {
4611
+ e.currentTarget.style.backgroundColor = theme.hoverBackgroundColor || "#f9fafb";
4612
+ },
4613
+ onMouseLeave: (e) => {
4614
+ e.currentTarget.style.backgroundColor = theme.backgroundColor || "#ffffff";
4615
+ },
4616
+ "aria-label": isOpen ? "Close panel" : "Open panel"
4617
+ },
4618
+ isOpen ? /* @__PURE__ */ React21.createElement(ChevronLeft, { className: toggleButtonClassNames?.icon, style: { width: 14, height: 14, color: theme.mutedTextColor, ...toggleButtonStyles?.icon } }) : /* @__PURE__ */ React21.createElement(ChevronRight2, { className: toggleButtonClassNames?.icon, style: { width: 14, height: 14, color: theme.mutedTextColor, ...toggleButtonStyles?.icon } })
4619
+ ), /* @__PURE__ */ React21.createElement(
4620
+ "div",
4621
+ {
4622
+ className: `left-panel ${className}`,
4623
+ style: {
4624
+ display: "flex",
4625
+ flexDirection: "column",
4626
+ height: "100%",
4627
+ backgroundColor: theme.backgroundColor,
4628
+ borderRight: `1px solid ${theme.borderColor}`,
4629
+ transition: "width 0.2s ease-in-out, min-width 0.2s ease-in-out",
4630
+ position: "relative",
4631
+ width: isOpen ? panelWidth : "0px",
4632
+ minWidth: isOpen ? panelWidth : "0px",
4633
+ overflow: "hidden",
4634
+ ...cssVars,
4635
+ ...style
4636
+ }
4637
+ },
4638
+ /* @__PURE__ */ React21.createElement(
4639
+ "div",
4640
+ {
4641
+ style: {
4642
+ display: "flex",
4643
+ flexDirection: "column",
4644
+ height: "100%",
4645
+ overflow: "hidden",
4646
+ width: panelWidth,
4647
+ minWidth: panelWidth
4648
+ }
4649
+ },
4650
+ tabs.length > 1 && /* @__PURE__ */ React21.createElement(
4651
+ "div",
4652
+ {
4653
+ className: tabClassNames?.container,
4654
+ style: {
4655
+ display: "flex",
4656
+ borderBottom: `1px solid ${theme.borderColor}`,
4657
+ flexShrink: 0,
4658
+ ...tabStyles?.container
4659
+ }
4660
+ },
4661
+ tabs.includes("outline") && /* @__PURE__ */ React21.createElement(
4662
+ "button",
4663
+ {
4664
+ className: [tabClassNames?.tab, activeTab === "outline" ? tabClassNames?.tabActive : ""].filter(Boolean).join(" ") || void 0,
4665
+ onClick: () => setActiveTab("outline"),
4666
+ style: {
4667
+ flex: 1,
4668
+ display: "flex",
4669
+ alignItems: "center",
4670
+ justifyContent: "center",
4671
+ gap: "6px",
4672
+ padding: "12px 16px",
4673
+ fontSize: "13px",
4674
+ fontWeight: 500,
4675
+ color: activeTab === "outline" ? theme.accentColor : theme.mutedTextColor,
4676
+ backgroundColor: activeTab === "outline" ? `${theme.accentColor}08` : "transparent",
4677
+ borderBottom: activeTab === "outline" ? `2px solid ${theme.accentColor}` : "2px solid transparent",
4678
+ border: "none",
4679
+ cursor: "pointer",
4680
+ transition: "all 0.15s ease",
4681
+ ...tabStyles?.tab,
4682
+ ...activeTab === "outline" ? tabStyles?.tabActive : {}
4683
+ }
4684
+ },
4685
+ /* @__PURE__ */ React21.createElement(FileText, { className: tabClassNames?.tabIcon, style: { width: 15, height: 15, ...tabStyles?.tabIcon } }),
4686
+ /* @__PURE__ */ React21.createElement("span", { className: tabClassNames?.tabText, style: tabStyles?.tabText }, "Outline")
4687
+ ),
4688
+ tabs.includes("thumbnails") && /* @__PURE__ */ React21.createElement(
4689
+ "button",
4690
+ {
4691
+ className: [tabClassNames?.tab, activeTab === "thumbnails" ? tabClassNames?.tabActive : ""].filter(Boolean).join(" ") || void 0,
4692
+ onClick: () => setActiveTab("thumbnails"),
4693
+ style: {
4694
+ flex: 1,
4695
+ display: "flex",
4696
+ alignItems: "center",
4697
+ justifyContent: "center",
4698
+ gap: "6px",
4699
+ padding: "12px 16px",
4700
+ fontSize: "13px",
4701
+ fontWeight: 500,
4702
+ color: activeTab === "thumbnails" ? theme.accentColor : theme.mutedTextColor,
4703
+ backgroundColor: activeTab === "thumbnails" ? `${theme.accentColor}08` : "transparent",
4704
+ borderBottom: activeTab === "thumbnails" ? `2px solid ${theme.accentColor}` : "2px solid transparent",
4705
+ border: "none",
4706
+ cursor: "pointer",
4707
+ transition: "all 0.15s ease",
4708
+ ...tabStyles?.tab,
4709
+ ...activeTab === "thumbnails" ? tabStyles?.tabActive : {}
4710
+ }
4711
+ },
4712
+ /* @__PURE__ */ React21.createElement(List, { className: tabClassNames?.tabIcon, style: { width: 15, height: 15, ...tabStyles?.tabIcon } }),
4713
+ /* @__PURE__ */ React21.createElement("span", { className: tabClassNames?.tabText, style: tabStyles?.tabText }, "Pages")
4714
+ )
4715
+ ),
4716
+ tabs.length === 1 && /* @__PURE__ */ React21.createElement(
4717
+ "div",
4718
+ {
4719
+ className: tabClassNames?.container,
4720
+ style: {
4721
+ display: "flex",
4722
+ alignItems: "center",
4723
+ gap: "8px",
4724
+ padding: "12px 16px",
4725
+ borderBottom: `1px solid ${theme.borderColor}`,
4726
+ flexShrink: 0,
4727
+ ...tabStyles?.container
4728
+ }
4729
+ },
4730
+ tabs[0] === "outline" ? /* @__PURE__ */ React21.createElement(React21.Fragment, null, /* @__PURE__ */ React21.createElement(FileText, { className: tabClassNames?.tabIcon, style: { width: 15, height: 15, color: theme.mutedTextColor, ...tabStyles?.tabIcon } }), /* @__PURE__ */ React21.createElement("span", { className: tabClassNames?.tabText, style: { fontSize: "13px", fontWeight: 500, color: theme.textColor, ...tabStyles?.tabText } }, "Outline")) : /* @__PURE__ */ React21.createElement(React21.Fragment, null, /* @__PURE__ */ React21.createElement(List, { className: tabClassNames?.tabIcon, style: { width: 15, height: 15, color: theme.mutedTextColor, ...tabStyles?.tabIcon } }), /* @__PURE__ */ React21.createElement("span", { className: tabClassNames?.tabText, style: { fontSize: "13px", fontWeight: 500, color: theme.textColor, ...tabStyles?.tabText } }, "Pages"))
4731
+ ),
4732
+ /* @__PURE__ */ React21.createElement(
4733
+ "div",
4734
+ {
4735
+ style: {
4736
+ flex: 1,
4737
+ minHeight: 0,
4738
+ overflowY: "auto",
4739
+ overflowX: "hidden"
4740
+ }
4741
+ },
4742
+ activeTab === "outline" && tabs.includes("outline") && /* @__PURE__ */ React21.createElement(
4743
+ DocumentOutline,
4744
+ {
4745
+ outline,
4746
+ isLoading: isOutlineLoading,
4747
+ currentPage,
4748
+ onNavigate: handleOutlineNavigate,
4749
+ renderItem: renderOutlineItem,
4750
+ styles: outlineStyles,
4751
+ classNames: outlineClassNames,
4752
+ itemStyles: outlineItemStyles,
4753
+ itemClassNames: outlineItemClassNames
4754
+ }
4755
+ ),
4756
+ activeTab === "thumbnails" && tabs.includes("thumbnails") && /* @__PURE__ */ React21.createElement(
4757
+ ThumbnailPanel,
4758
+ {
4759
+ totalPages,
4760
+ currentPage,
4761
+ getThumbnail,
4762
+ loadThumbnail,
4763
+ onPageSelect: handlePageSelect,
4764
+ renderThumbnail
4765
+ }
4766
+ ),
4767
+ children
4768
+ ),
4769
+ showFooter && /* @__PURE__ */ React21.createElement(
4770
+ "div",
4771
+ {
4772
+ className: footerClassNames?.container,
4773
+ style: {
4774
+ flexShrink: 0,
4775
+ borderTop: `1px solid ${theme.borderColor}`,
4776
+ padding: "10px 16px",
4777
+ backgroundColor: theme.hoverBackgroundColor,
4778
+ ...footerStyles?.container
4779
+ }
4780
+ },
4781
+ /* @__PURE__ */ React21.createElement(
4782
+ "div",
4783
+ {
4784
+ className: footerClassNames?.text,
4785
+ style: {
4786
+ fontSize: "12px",
4787
+ color: theme.mutedTextColor,
4788
+ textAlign: "center",
4789
+ fontWeight: 500,
4790
+ ...footerStyles?.text
4791
+ }
4792
+ },
4793
+ "Page ",
4794
+ currentPage,
4795
+ " of ",
4796
+ totalPages
4797
+ )
4798
+ )
4799
+ )
4800
+ ));
4801
+ };
3539
4802
  export {
3540
4803
  AreaHighlight,
4804
+ DocumentOutline,
3541
4805
  DrawingCanvas,
3542
4806
  DrawingHighlight,
3543
4807
  FreetextHighlight,
3544
4808
  ImageHighlight,
4809
+ LeftPanel,
3545
4810
  MonitoredHighlightContainer,
4811
+ OutlineItem,
3546
4812
  PdfHighlighter,
3547
4813
  PdfLoader,
3548
4814
  ShapeCanvas,
3549
4815
  ShapeHighlight,
3550
4816
  SignaturePad,
3551
4817
  TextHighlight,
4818
+ ThumbnailItem,
4819
+ ThumbnailPanel,
3552
4820
  exportPdf,
3553
4821
  scaledPositionToViewport,
4822
+ useDocumentOutline,
3554
4823
  useHighlightContainerContext,
4824
+ useLeftPanelContext,
4825
+ usePageNavigation,
3555
4826
  usePdfHighlighterContext,
4827
+ useThumbnails,
3556
4828
  viewportPositionToScaled
3557
4829
  };
3558
4830
  //# sourceMappingURL=index.js.map