UNPKG

@letter-ai/lector

Version:

Headless PDF viewer for React

1,700 lines (1,685 loc) 122 kB
'use client'; import { AnnotationLayer, TextLayer, getDocument } from 'pdfjs-dist'; import React, { createContext, forwardRef, useContext, useCallback, cloneElement, useState, useRef, useEffect, createRef, useLayoutEffect } from 'react'; import { useVirtualizer, elementScroll, debounce } from '@tanstack/react-virtual'; import { useStore, createStore } from 'zustand'; import { jsx, Fragment, jsxs } from 'react/jsx-runtime'; import { useDebounce } from 'use-debounce'; import { Slot } from '@radix-ui/react-slot'; import { useFloating, autoUpdate, offset, shift, useDismiss, useInteractions } from '@floating-ui/react'; // ../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs function r(e) { var t, f, n = ""; if ("string" == typeof e || "number" == typeof e) n += e; else if ("object" == typeof e) if (Array.isArray(e)) { var o = e.length; for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f); } else for (f in e) e[f] && (n && (n += " "), n += f); return n; } function clsx() { for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t); return n; } var clsx_default = clsx; // src/lib/clamp.ts var clamp = (value, min, max) => { return Math.min(Math.max(value, min), max); }; // src/lib/zoom.ts var getFitWidthZoom = (containerWidth, viewports, zoomOptions) => { const { minZoom, maxZoom } = zoomOptions; const maxPageWidth = Math.max(...viewports.map((viewport) => viewport.width)); const targetZoom = containerWidth / maxPageWidth; const clampedZoom = Math.min(Math.max(targetZoom, minZoom), maxZoom); return clampedZoom; }; var createZustandContext = (getStore) => { const Context = React.createContext(null); const Provider = (props) => { const [store] = React.useState(() => getStore(props.initialValue)); return /* @__PURE__ */ jsx(Context.Provider, { value: store, children: props.children }); }; return { useContext: () => React.useContext(Context), Context, Provider }; }; // src/internal.ts var PDFStore = createZustandContext( (initialState) => { return createStore((set, get) => ({ pdfDocumentProxy: initialState.pdfDocumentProxy, zoom: initialState.zoom, isZoomFitWidth: initialState.isZoomFitWidth ?? false, zoomOptions: { minZoom: initialState.zoomOptions?.minZoom ?? 0.5, maxZoom: initialState.zoomOptions?.maxZoom ?? 10 }, viewportRef: createRef(), viewports: initialState.viewports, updateZoom: (zoom, isZoomFitWidth = false) => { const { minZoom, maxZoom } = get().zoomOptions; set((state) => { if (typeof zoom === "function") { const newZoom2 = clamp(zoom(state.zoom), minZoom, maxZoom); return { zoom: newZoom2, isZoomFitWidth }; } const newZoom = clamp(zoom, minZoom, maxZoom); return { zoom: newZoom, isZoomFitWidth }; }); }, zoomFitWidth: () => { const { viewportRef, zoomOptions, viewports } = get(); if (!viewportRef.current) return; const clampedZoom = getFitWidthZoom( viewportRef.current.clientWidth, viewports, zoomOptions ); set({ zoom: clampedZoom, isZoomFitWidth: true }); return clampedZoom; }, currentPage: 1, setCurrentPage: (val) => { set({ currentPage: val }); }, isPinching: false, setIsPinching: (val) => { set({ isPinching: val }); }, virtualizer: null, setVirtualizer: (val) => { set({ virtualizer: val }); }, pageProxies: initialState.pageProxies, getPdfPageProxy: (pageNumber) => { const proxy = get().pageProxies[pageNumber - 1]; if (!proxy) throw new Error(`Page ${pageNumber} does not exist`); return proxy; }, textContent: [], setTextContent: (val) => { set({ textContent: val }); }, highlights: [], setHighlight: (val) => { set({ highlights: val }); }, customSelectionRects: [], setCustomSelectionRects: (val) => { set({ customSelectionRects: val }); } })); } ); var usePdf = (selector) => useStore(PDFStore.useContext(), selector); // src/lib/cancellable.ts var cancellable = (promise) => { let isCancelled = false; const wrappedPromise = new Promise((resolve, reject) => { promise.then( (value) => { if (!isCancelled) { resolve(value); } }, (error) => { if (!isCancelled) { reject(error); } } ); }); return { promise: wrappedPromise, cancel() { isCancelled = true; } }; }; var LinkService = class { _pdfDocumentProxy; externalLinkEnabled = true; isInPresentationMode = false; _currentPageNumber = 0; _pageNavigationCallback; get pdfDocumentProxy() { if (!this._pdfDocumentProxy) { throw new Error("pdfDocumentProxy is not set"); } return this._pdfDocumentProxy; } constructor() { } get pagesCount() { return this._pdfDocumentProxy?.numPages || 0; } get page() { return this._currentPageNumber; } set page(value) { this._currentPageNumber = value; if (this._pageNavigationCallback) { this._pageNavigationCallback(value); } } // Required for link annotations to work setDocument(pdfDocument) { this._pdfDocumentProxy = pdfDocument; } setViewer() { } getDestinationHash(dest) { if (!dest) return ""; const destRef = dest[0]; if (dest.length > 1 && typeof dest[1] === "object" && dest[1] !== null && "url" in dest[1]) { const urlDest = dest[1]; return urlDest.url; } if (destRef && typeof destRef === "object") { if ("num" in destRef) { const numRef = destRef; return `#page=${numRef.num + 1}`; } if ("gen" in destRef) { const genRef = destRef; const refNum = genRef.num ?? 0; return `#page=${refNum + 1}`; } } if (typeof destRef === "number") { return `#page=${destRef + 1}`; } return `#dest-${String(dest)}`; } getAnchorUrl(hash) { if (hash.startsWith("http://") || hash.startsWith("https://")) { return hash; } return `#${hash}`; } addLinkAttributes(link, url, newWindow) { if (!link) return; const isExternalLink = url.startsWith("http://") || url.startsWith("https://"); if (isExternalLink && this.externalLinkEnabled) { link.href = url; link.target = newWindow === false ? "" : "_blank"; link.rel = "noopener noreferrer"; } else if (!isExternalLink) { link.href = url; link.target = ""; } else { link.href = "#"; link.target = ""; } } async goToDestination(dest) { let explicitDest; if (typeof dest === "string") { explicitDest = await this.pdfDocumentProxy.getDestination(dest); } else if (Array.isArray(dest)) { explicitDest = dest; } else { explicitDest = await dest; } if (!explicitDest) { return; } if (explicitDest.length > 1 && typeof explicitDest[1] === "object" && explicitDest[1] !== null && "url" in explicitDest[1]) { return; } const destRef = explicitDest[0]; let pageIndex; if (destRef && typeof destRef === "object") { if ("num" in destRef) { try { const refProxy = destRef; pageIndex = await this.pdfDocumentProxy.getPageIndex(refProxy); } catch (error) { return; } } else { return; } } else if (typeof destRef === "number") { pageIndex = destRef; } else { return; } const pageNumber = pageIndex + 1; if (this._pageNavigationCallback) { this._pageNavigationCallback(pageNumber); } } executeNamedAction() { } navigateTo(dest) { this.goToDestination(dest); } get rotation() { return 0; } set rotation(value) { } goToPage(pageNumber) { if (pageNumber >= 1 && pageNumber <= this.pagesCount) { if (this._pageNavigationCallback) { this._pageNavigationCallback(pageNumber); } } } setHash(hash) { if (hash.startsWith("#page=")) { const pageNumber = parseInt(hash.substring(6), 10); if (!isNaN(pageNumber)) { this.goToPage(pageNumber); } } } executeSetOCGState() { } // Method to register navigation callback registerPageNavigationCallback(callback) { this._pageNavigationCallback = callback; } // Method to unregister navigation callback unregisterPageNavigationCallback() { this._pageNavigationCallback = void 0; } }; var defaultLinkService = new LinkService(); var PDFLinkServiceContext = createContext(defaultLinkService); var usePDFLinkService = () => { return useContext(PDFLinkServiceContext); }; var PDFPageNumberContext = createContext(0); var usePDFPageNumber = () => { return useContext(PDFPageNumberContext); }; var useVisibility = ({ elementRef }) => { const [visible, setVisible] = useState(false); useEffect(() => { if (!elementRef.current) { return; } const observer = new IntersectionObserver(([entry]) => { setVisible(entry?.isIntersecting ?? false); }); observer.observe(elementRef.current); return () => { observer.disconnect(); }; }, [elementRef]); return { visible }; }; // src/hooks/pages/usePdfJump.tsx var usePdfJump = () => { const virtualizer = usePdf((state) => state.virtualizer); const setHighlight = usePdf((state) => state.setHighlight); const jumpToPage = (pageIndex, options) => { if (!virtualizer) return; const defaultOptions = { align: "start", behavior: "smooth" }; const finalOptions = { ...defaultOptions, ...options }; virtualizer.scrollToIndex(pageIndex - 1, finalOptions); }; const jumpToOffset = (offset2) => { if (!virtualizer) return; virtualizer.scrollToOffset(offset2, { align: "start", behavior: "smooth" }); }; const jumpToHighlightRects = (rects, type, align = "start", additionalOffset = 0) => { if (!virtualizer) return; setHighlight(rects); const firstPage = Math.min(...rects.map((rect) => rect.pageNumber)); const pageOffset = virtualizer.getOffsetForIndex(firstPage - 1, "start"); if (pageOffset === null) return; const targetRect = rects.find((rect) => rect.pageNumber === firstPage); if (!targetRect) return; const isNumber = pageOffset?.[0] != null; if (!isNumber) return; const pageStart = pageOffset[0] ?? 0; let rectTop; let rectHeight; if (type === "percent") { const estimatePageHeight = virtualizer.options.estimateSize( firstPage - 1 ); rectTop = targetRect.top / 100 * estimatePageHeight; rectHeight = targetRect.height / 100 * estimatePageHeight; } else { rectTop = targetRect.top; rectHeight = targetRect.height; } let scrollOffset; if (align === "center") { const viewportHeight = virtualizer.scrollElement?.clientHeight || 0; const rectCenter = pageStart + rectTop + rectHeight / 2; scrollOffset = rectCenter - viewportHeight / 2; } else { scrollOffset = pageStart + rectTop; } scrollOffset += additionalOffset; const adjustedOffset = Math.max(0, scrollOffset); virtualizer.scrollToOffset(adjustedOffset, { align: "start", // Always use start when we've calculated our own centering behavior: "smooth" }); }; return { jumpToPage, jumpToOffset, jumpToHighlightRects }; }; // src/hooks/layers/useAnnotationLayer.tsx var defaultAnnotationLayerParams = { renderForms: true, externalLinksEnabled: true, jumpOptions: { behavior: "smooth", align: "start" } }; var useAnnotationLayer = (params) => { const mergedParams = { ...defaultAnnotationLayerParams, ...params }; const annotationLayerRef = useRef(null); const annotationLayerObjectRef = useRef(null); const linkService = usePDFLinkService(); const { visible } = useVisibility({ elementRef: annotationLayerRef }); const pageNumber = usePDFPageNumber(); const pdfPageProxy = usePdf((state) => state.getPdfPageProxy(pageNumber)); const pdfDocumentProxy = usePdf((state) => state.pdfDocumentProxy); useEffect(() => { linkService.externalLinkEnabled = mergedParams.externalLinksEnabled; }, [linkService, mergedParams.externalLinksEnabled]); const { jumpToPage } = usePdfJump(); useEffect(() => { if (!jumpToPage) return; const handlePageNavigation = (pageNumber2) => { jumpToPage(pageNumber2, mergedParams.jumpOptions); }; linkService.registerPageNavigationCallback(handlePageNavigation); return () => { linkService.unregisterPageNavigationCallback(); }; }, [jumpToPage, linkService, mergedParams.jumpOptions]); useEffect(() => { const style = document.createElement("style"); style.textContent = ` .annotationLayer { position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; opacity: 1; z-index: 3; } .annotationLayer section { position: absolute; } .annotationLayer .linkAnnotation > a, .annotationLayer .buttonWidgetAnnotation.pushButton > a { position: absolute; font-size: 1em; top: 0; left: 0; width: 100%; height: 100%; background: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7") 0 0 repeat; cursor: pointer; } .annotationLayer .linkAnnotation > a:hover, .annotationLayer .buttonWidgetAnnotation.pushButton > a:hover { opacity: 0.2; background: rgba(255, 255, 0, 1); box-shadow: 0 2px 10px rgba(255, 255, 0, 1); } `; document.head.appendChild(style); return () => { document.head.removeChild(style); }; }, []); useEffect(() => { if (!annotationLayerRef.current) return; const element = annotationLayerRef.current; const handleLinkClick = (e) => { if (!e.target || !(e.target instanceof HTMLAnchorElement)) return; const target = e.target; const href = target.getAttribute("href") || ""; if (href.startsWith("#page=")) { e.preventDefault(); const pageNumber2 = parseInt(href.substring(6), 10); if (!isNaN(pageNumber2)) { linkService.goToPage(pageNumber2); } } }; element.addEventListener("click", handleLinkClick); return () => { element.removeEventListener("click", handleLinkClick); }; }, [linkService]); useEffect(() => { if (!annotationLayerRef.current) { return; } if (visible) { annotationLayerRef.current.style.contentVisibility = "visible"; } else { annotationLayerRef.current.style.contentVisibility = "hidden"; } }, [visible]); useEffect(() => { if (!annotationLayerRef.current || !pdfPageProxy || !pdfDocumentProxy) { return; } if (linkService._pdfDocumentProxy !== pdfDocumentProxy) { linkService.setDocument(pdfDocumentProxy); } annotationLayerRef.current.innerHTML = ""; annotationLayerRef.current.className = "annotationLayer"; const viewport = pdfPageProxy.getViewport({ scale: 1 }); const annotationLayerConfig = { div: annotationLayerRef.current, viewport, page: pdfPageProxy, linkService, accessibilityManager: void 0, annotationCanvasMap: void 0, annotationEditorUIManager: void 0, structTreeLayer: void 0 }; const annotationLayer = new AnnotationLayer(annotationLayerConfig); annotationLayerObjectRef.current = annotationLayer; const { cancel } = cancellable( (async () => { try { const annotations = await pdfPageProxy.getAnnotations(); await annotationLayer.render({ ...annotationLayerConfig, ...mergedParams, annotations, linkService }); } catch (error) { } })() ); return () => { cancel(); }; }, [pdfPageProxy, pdfDocumentProxy, mergedParams, linkService]); return { annotationLayerRef }; }; var AnnotationLayer2 = ({ renderForms = true, externalLinksEnabled = true, jumpOptions = { behavior: "smooth", align: "start" }, className, style, ...props }) => { const { annotationLayerRef } = useAnnotationLayer({ renderForms, externalLinksEnabled, jumpOptions }); return /* @__PURE__ */ jsx( "div", { className: clsx_default("annotationLayer", className), style: { ...style, position: "absolute", top: 0, left: 0 }, ...props, ref: annotationLayerRef } ); }; var useDpr = () => { const [dpr, setDPR] = useState( !window ? 1 : Math.min(window.devicePixelRatio, 2) ); useEffect(() => { if (!window) { return; } const handleDPRChange = () => { setDPR(window.devicePixelRatio); }; const windowMatch = window.matchMedia( `screen and (min-resolution: ${dpr}dppx)` ); windowMatch.addEventListener("change", handleDPRChange); return () => { windowMatch.removeEventListener("change", handleDPRChange); }; }, []); return dpr; }; // src/hooks/layers/useCanvasLayer.tsx var useCanvasLayer = () => { const canvasRef = useRef(null); const pageNumber = usePDFPageNumber(); const dpr = useDpr(); const bouncyZoom = usePdf((state) => state.zoom); const pdfPageProxy = usePdf((state) => state.getPdfPageProxy(pageNumber)); const [zoom] = useDebounce(bouncyZoom, 100); useLayoutEffect(() => { if (!canvasRef.current) { return; } const viewport = pdfPageProxy.getViewport({ scale: 1 }); const canvas = canvasRef.current; const scale = dpr * zoom; canvas.height = viewport.height * scale; canvas.width = viewport.width * scale; canvas.style.height = `${viewport.height}px`; canvas.style.width = `${viewport.width}px`; const canvasContext = canvas.getContext("2d"); canvasContext.scale(scale, scale); const renderingTask = pdfPageProxy.render({ canvasContext, viewport }); renderingTask.promise.catch((error) => { if (error.name === "RenderingCancelledException") { return; } throw error; }); return () => { void renderingTask.cancel(); }; }, [pdfPageProxy, dpr, zoom]); return { canvasRef }; }; var CanvasLayer = ({ style, ...props }) => { const { canvasRef } = useCanvasLayer(); return /* @__PURE__ */ jsx( "canvas", { style: { ...style, position: "absolute", top: 0, left: 0, width: "100%", height: "100%" }, ...props, ref: canvasRef } ); }; // src/components/layers/custom-layer.tsx var CustomLayer = ({ children }) => { const pageNumber = usePDFPageNumber(); return children(pageNumber); }; var convertToPercentString = (rect) => { return { top: `${rect.top}%`, left: `${rect.left}%`, height: `${rect.height}%`, width: `${rect.width}%` }; }; var HighlightLayer = forwardRef(({ asChild, className, style, ...props }, ref) => { const pageNumber = usePDFPageNumber(); const highlights = usePdf((state) => state.highlights); const Comp = asChild ? Slot : "div"; const rects = highlights.filter((area) => area.pageNumber === pageNumber); if (!rects?.length) return null; return /* @__PURE__ */ jsx(Fragment, { children: rects.map((rect, index) => { const { pageNumber: pageNumber2, type, ...coordinates } = rect; let dimensions = coordinates; if (type === "percent") { dimensions = convertToPercentString(rect); } return /* @__PURE__ */ jsx( Comp, { ref, className, style: { position: "absolute", ...dimensions, pointerEvents: "none", zIndex: 30, ...style }, ...props, children: props.children }, `highlight-${pageNumber2}-${index}` ); }) }); }); HighlightLayer.displayName = "HighlightLayer"; var useTextLayer = () => { const textContainerRef = useRef(null); const pageNumber = usePDFPageNumber(); const pdfPageProxy = usePdf((state) => state.getPdfPageProxy(pageNumber)); useEffect(() => { if (!textContainerRef.current) { return; } const textLayer = new TextLayer({ textContentSource: pdfPageProxy.streamTextContent(), container: textContainerRef.current, viewport: pdfPageProxy.getViewport({ scale: 1 }) }); void textLayer.render(); return () => { textLayer.cancel(); }; }, [pdfPageProxy]); return { textContainerRef, pageNumber: pdfPageProxy.pageNumber }; }; var TextLayer2 = ({ className, style, ...props }) => { const { textContainerRef, pageNumber } = useTextLayer(); return /* @__PURE__ */ jsx( "div", { className: clsx_default("textLayer", className), style: { ...style, position: "absolute", top: 0, left: 0 }, ...props, ...{ ["data-page-number"]: pageNumber }, ref: textContainerRef } ); }; var usePDFOutline = () => { const pdfDocumentProxy = usePdf((state) => state.pdfDocumentProxy); const [outline, setOutline] = useState(); useEffect(() => { const { promise: outline2, cancel } = cancellable( pdfDocumentProxy.getOutline() ); outline2.then( (result) => { setOutline(result); }, () => { } ); return () => { cancel(); }; }, [pdfDocumentProxy]); return outline; }; var HTMLTags = [ "a", "button", "div", "aside", "section", "main", "ul", "li", "input", "canvas" ]; var makePrimitive = (htmlTag) => { const primitive = forwardRef( (props, ref) => { const Renderer = htmlTag; return /* @__PURE__ */ jsx(Renderer, { ...props, ref }); } ); primitive.displayName = `PDFReader.${htmlTag}`; return primitive; }; var Primitive = HTMLTags.reduce((acc, tag) => { acc[tag] = makePrimitive(tag); return acc; }, {}); var OutlineChildItems = ({ ...props }) => { return /* @__PURE__ */ jsx(Primitive.ul, { ...props }); }; var OutlineItem = ({ level = 0, item, children, outlineItem, ...props }) => { if (!item || !outlineItem || !children) { throw new Error("Outline item is required"); } const pdfDocumentProxy = usePdf((state) => state.pdfDocumentProxy); const { jumpToPage } = usePdfJump(); const getDestinationPage = useCallback( async (dest) => { let explicitDest; if (typeof dest === "string") { explicitDest = await pdfDocumentProxy.getDestination(dest); } else if (Array.isArray(dest)) { explicitDest = dest; } else { explicitDest = await dest; } if (!explicitDest) { return; } const explicitRef = explicitDest[0]; const page = await pdfDocumentProxy.getPageIndex(explicitRef); return page; }, [pdfDocumentProxy] ); const navigate = useCallback(() => { if (!item.dest) { return; } getDestinationPage(item.dest).then((page) => { if (!page) { return; } jumpToPage(page, { behavior: "smooth" }); }); }, [item.dest, jumpToPage, getDestinationPage]); return /* @__PURE__ */ jsxs(Primitive.li, { ...props, children: [ /* @__PURE__ */ jsx( "a", { role: "button", tabIndex: 0, onClick: navigate, onKeyUp: (e) => { if (e.key === "Enter") { navigate(); } }, "data-level": level, children: item.title } ), item.items && item.items.length > 0 && cloneElement(children, { // @ts-expect-error we are missing the corect props types children: item.items.map( (item2, index) => cloneElement(outlineItem, { // @ts-expect-error we are missing the corect props types level: level + 1, item: item2, outlineItem, key: index }) ) }) ] }); }; var Outline = ({ children, ...props }) => { const outline = usePDFOutline(); return /* @__PURE__ */ jsx(Primitive.ul, { ...props, children: outline && outline.map((item, idx) => { return cloneElement(children, { key: idx, item, outlineItem: children }); }) }); }; var Page = ({ children, pageNumber = 1, style, ...props }) => { const pdfPageProxy = usePdf((state) => state.getPdfPageProxy(pageNumber)); const width = (pdfPageProxy.view[2] ?? 0) - (pdfPageProxy.view[0] ?? 0); const height = (pdfPageProxy.view[3] ?? 0) - (pdfPageProxy.view[1] ?? 0); return /* @__PURE__ */ jsx(PDFPageNumberContext.Provider, { value: pdfPageProxy.pageNumber, children: /* @__PURE__ */ jsx( Primitive.div, { style: { display: "block" }, children: /* @__PURE__ */ jsx( "div", { style: { ...style, "--scale-factor": 1, position: "relative", width, height }, ...props, children } ) } ) }); }; var NextPage = () => { }; var PreviousPage = () => { }; var CurrentPage = ({ ...props }) => { const currentPage = usePdf((state) => state.currentPage); const pages = usePdf((state) => state.pdfDocumentProxy.numPages); const [pageNumber, setPageNumber] = useState(currentPage); const isSelected = useRef(false); const { jumpToPage } = usePdfJump(); useEffect(() => { if (isSelected.current) { return; } setPageNumber(currentPage); }, [currentPage]); return /* @__PURE__ */ jsx( "input", { type: "number", ...props, style: { ...props.style, appearance: "textfield", MozAppearance: "textfield", WebkitAppearance: "none" }, value: pageNumber, onChange: (e) => { setPageNumber(e.target.value); }, onClick: () => isSelected.current = true, onBlur: (e) => { if (currentPage !== Number(e.target.value)) { jumpToPage(Number(e.target.value), { behavior: "auto" }); } isSelected.current = false; }, onKeyDown: (e) => { e.key === "Enter" && e.currentTarget.blur(); }, min: 1, max: pages } ); }; var TotalPages = ({ ...props }) => { const pages = usePdf((state) => state.pdfDocumentProxy.numPages); return /* @__PURE__ */ jsx("div", { ...props, children: pages }); }; var useFitWidth = ({ viewportRef }) => { const viewports = usePdf((state) => state.viewports); const zoomOptions = usePdf((state) => state.zoomOptions); const updateZoom = usePdf((state) => state.updateZoom); const store = PDFStore.useContext(); useLayoutEffect(() => { if (viewportRef.current === null) return; const resizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { const isFitWidth = store.getState().isZoomFitWidth; if (entry.target === viewportRef.current && isFitWidth) { const containerWidth = entry.contentRect.width; const newZoom = getFitWidthZoom( containerWidth, viewports, zoomOptions ); updateZoom(newZoom, true); } } }); resizeObserver.observe(viewportRef.current); return () => { resizeObserver.disconnect(); }; }, [store, updateZoom, viewportRef, viewports, zoomOptions]); return null; }; var supportsScrollend = typeof window == "undefined" ? true : "onscrollend" in window; var addEventListenerOptions = { passive: true }; var useObserveElement = () => { const store = PDFStore.useContext(); const observeElementOffset = (instance, cb) => { const element = instance.scrollElement; if (!element) { return; } const targetWindow = instance.targetWindow; if (!targetWindow) { return; } let offset2 = 0; const fallback = instance.options.useScrollendEvent && supportsScrollend ? () => void 0 : debounce( targetWindow, () => { cb(offset2, false); }, instance.options.isScrollingResetDelay ); const createHandler = (isScrolling) => () => { const { horizontal, isRtl } = instance.options; offset2 = horizontal ? element["scrollLeft"] * (isRtl && -1 || 1) : element["scrollTop"]; const zoom = store.getState().zoom; offset2 = offset2 / zoom; fallback(); cb(offset2, isScrolling); }; const handler = createHandler(true); const endHandler = createHandler(false); endHandler(); element.addEventListener("scroll", handler, addEventListenerOptions); element.addEventListener("scrollend", endHandler, addEventListenerOptions); return () => { element.removeEventListener("scroll", handler); element.removeEventListener("scrollend", endHandler); }; }; return { observeElementOffset }; }; var useScrollFn = () => { const store = PDFStore.useContext(); const scrollToFn = useCallback( (_offset, canSmooth, instance) => { const zoom = store.getState().zoom; const offset2 = _offset * zoom; elementScroll(offset2, canSmooth, instance); }, [store] ); return { scrollToFn }; }; var useVisiblePage = ({ items }) => { const zoomLevel = usePdf((state) => state.zoom); const isPinching = usePdf((state) => state.isPinching); const setCurrentPage = usePdf((state) => state.setCurrentPage); const scrollElement = usePdf((state) => state.viewportRef?.current); const calculateVisiblePageIndex = useCallback( (virtualItems) => { if (!scrollElement || virtualItems.length === 0) return 0; const scrollTop = scrollElement.scrollTop / zoomLevel; const viewportHeight = scrollElement.clientHeight / zoomLevel; const viewportCenter = scrollTop + viewportHeight / 2; let closestIndex = 0; let smallestDistance = Infinity; for (const item of virtualItems) { const itemCenter = item.start + item.size / 2; const distance = Math.abs(itemCenter - viewportCenter); if (distance < smallestDistance * 0.8) { smallestDistance = distance; closestIndex = item.index; } } return closestIndex; }, [scrollElement, zoomLevel] ); useEffect(() => { if (!isPinching && items.length > 0) { const mostVisibleIndex = calculateVisiblePageIndex(items); setCurrentPage?.(mostVisibleIndex + 1); } }, [items, isPinching, calculateVisiblePageIndex, setCurrentPage]); return null; }; // ../../node_modules/.pnpm/@use-gesture+core@10.3.1/node_modules/@use-gesture/core/dist/maths-0ab39ae9.esm.js function clamp2(v, min, max) { return Math.max(min, Math.min(v, max)); } var V = { toVector(v, fallback) { if (v === void 0) v = fallback; return Array.isArray(v) ? v : [v, v]; }, add(v1, v2) { return [v1[0] + v2[0], v1[1] + v2[1]]; }, sub(v1, v2) { return [v1[0] - v2[0], v1[1] - v2[1]]; }, addTo(v1, v2) { v1[0] += v2[0]; v1[1] += v2[1]; }, subTo(v1, v2) { v1[0] -= v2[0]; v1[1] -= v2[1]; } }; function rubberband(distance, dimension, constant) { if (dimension === 0 || Math.abs(dimension) === Infinity) return Math.pow(distance, constant * 5); return distance * dimension * constant / (dimension + constant * distance); } function rubberbandIfOutOfBounds(position, min, max, constant = 0.15) { if (constant === 0) return clamp2(position, min, max); if (position < min) return -rubberband(min - position, max - min, constant) + min; if (position > max) return +rubberband(position - max, max - min, constant) + max; return position; } function computeRubberband(bounds, [Vx, Vy], [Rx, Ry]) { const [[X0, X1], [Y0, Y1]] = bounds; return [rubberbandIfOutOfBounds(Vx, X0, X1, Rx), rubberbandIfOutOfBounds(Vy, Y0, Y1, Ry)]; } // ../../node_modules/.pnpm/@use-gesture+core@10.3.1/node_modules/@use-gesture/core/dist/actions-fe213e88.esm.js function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== void 0) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function ownKeys(e, r2) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r2 && (o = o.filter(function(r3) { return Object.getOwnPropertyDescriptor(e, r3).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread2(e) { for (var r2 = 1; r2 < arguments.length; r2++) { var t = null != arguments[r2] ? arguments[r2] : {}; r2 % 2 ? ownKeys(Object(t), true).forEach(function(r3) { _defineProperty(e, r3, t[r3]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function(r3) { Object.defineProperty(e, r3, Object.getOwnPropertyDescriptor(t, r3)); }); } return e; } var EVENT_TYPE_MAP = { pointer: { start: "down", change: "move", end: "up" }, mouse: { start: "down", change: "move", end: "up" }, touch: { start: "start", change: "move", end: "end" }, gesture: { start: "start", change: "change", end: "end" } }; function capitalize(string) { if (!string) return ""; return string[0].toUpperCase() + string.slice(1); } var actionsWithoutCaptureSupported = ["enter", "leave"]; function hasCapture(capture = false, actionKey) { return capture && !actionsWithoutCaptureSupported.includes(actionKey); } function toHandlerProp(device, action = "", capture = false) { const deviceProps = EVENT_TYPE_MAP[device]; const actionKey = deviceProps ? deviceProps[action] || action : action; return "on" + capitalize(device) + capitalize(actionKey) + (hasCapture(capture, actionKey) ? "Capture" : ""); } var pointerCaptureEvents = ["gotpointercapture", "lostpointercapture"]; function parseProp(prop) { let eventKey = prop.substring(2).toLowerCase(); const passive = !!~eventKey.indexOf("passive"); if (passive) eventKey = eventKey.replace("passive", ""); const captureKey = pointerCaptureEvents.includes(eventKey) ? "capturecapture" : "capture"; const capture = !!~eventKey.indexOf(captureKey); if (capture) eventKey = eventKey.replace("capture", ""); return { device: eventKey, capture, passive }; } function toDomEventType(device, action = "") { const deviceProps = EVENT_TYPE_MAP[device]; const actionKey = deviceProps ? deviceProps[action] || action : action; return device + actionKey; } function isTouch(event) { return "touches" in event; } function getPointerType(event) { if (isTouch(event)) return "touch"; if ("pointerType" in event) return event.pointerType; return "mouse"; } function getCurrentTargetTouchList(event) { return Array.from(event.touches).filter((e) => { var _event$currentTarget, _event$currentTarget$; return e.target === event.currentTarget || ((_event$currentTarget = event.currentTarget) === null || _event$currentTarget === void 0 || (_event$currentTarget$ = _event$currentTarget.contains) === null || _event$currentTarget$ === void 0 ? void 0 : _event$currentTarget$.call(_event$currentTarget, e.target)); }); } function getTouchList(event) { return event.type === "touchend" || event.type === "touchcancel" ? event.changedTouches : event.targetTouches; } function getValueEvent(event) { return isTouch(event) ? getTouchList(event)[0] : event; } function distanceAngle(P1, P2) { try { const dx = P2.clientX - P1.clientX; const dy = P2.clientY - P1.clientY; const cx = (P2.clientX + P1.clientX) / 2; const cy = (P2.clientY + P1.clientY) / 2; const distance = Math.hypot(dx, dy); const angle = -(Math.atan2(dx, dy) * 180) / Math.PI; const origin = [cx, cy]; return { angle, distance, origin }; } catch (_unused) { } return null; } function touchIds(event) { return getCurrentTargetTouchList(event).map((touch) => touch.identifier); } function touchDistanceAngle(event, ids) { const [P1, P2] = Array.from(event.touches).filter((touch) => ids.includes(touch.identifier)); return distanceAngle(P1, P2); } function pointerId(event) { const valueEvent = getValueEvent(event); return isTouch(event) ? valueEvent.identifier : valueEvent.pointerId; } function pointerValues(event) { const valueEvent = getValueEvent(event); return [valueEvent.clientX, valueEvent.clientY]; } var LINE_HEIGHT = 40; var PAGE_HEIGHT = 800; function wheelValues(event) { let { deltaX, deltaY, deltaMode } = event; if (deltaMode === 1) { deltaX *= LINE_HEIGHT; deltaY *= LINE_HEIGHT; } else if (deltaMode === 2) { deltaX *= PAGE_HEIGHT; deltaY *= PAGE_HEIGHT; } return [deltaX, deltaY]; } function scrollValues(event) { var _ref, _ref2; const { scrollX, scrollY, scrollLeft, scrollTop } = event.currentTarget; return [(_ref = scrollX !== null && scrollX !== void 0 ? scrollX : scrollLeft) !== null && _ref !== void 0 ? _ref : 0, (_ref2 = scrollY !== null && scrollY !== void 0 ? scrollY : scrollTop) !== null && _ref2 !== void 0 ? _ref2 : 0]; } function getEventDetails(event) { const payload = {}; if ("buttons" in event) payload.buttons = event.buttons; if ("shiftKey" in event) { const { shiftKey, altKey, metaKey, ctrlKey } = event; Object.assign(payload, { shiftKey, altKey, metaKey, ctrlKey }); } return payload; } function call(v, ...args) { if (typeof v === "function") { return v(...args); } else { return v; } } function noop() { } function chain(...fns) { if (fns.length === 0) return noop; if (fns.length === 1) return fns[0]; return function() { let result; for (const fn of fns) { result = fn.apply(this, arguments) || result; } return result; }; } function assignDefault(value, fallback) { return Object.assign({}, fallback, value || {}); } var BEFORE_LAST_KINEMATICS_DELAY = 32; var Engine = class { constructor(ctrl, args, key) { this.ctrl = ctrl; this.args = args; this.key = key; if (!this.state) { this.state = {}; this.computeValues([0, 0]); this.computeInitial(); if (this.init) this.init(); this.reset(); } } get state() { return this.ctrl.state[this.key]; } set state(state) { this.ctrl.state[this.key] = state; } get shared() { return this.ctrl.state.shared; } get eventStore() { return this.ctrl.gestureEventStores[this.key]; } get timeoutStore() { return this.ctrl.gestureTimeoutStores[this.key]; } get config() { return this.ctrl.config[this.key]; } get sharedConfig() { return this.ctrl.config.shared; } get handler() { return this.ctrl.handlers[this.key]; } reset() { const { state, shared, ingKey, args } = this; shared[ingKey] = state._active = state.active = state._blocked = state._force = false; state._step = [false, false]; state.intentional = false; state._movement = [0, 0]; state._distance = [0, 0]; state._direction = [0, 0]; state._delta = [0, 0]; state._bounds = [[-Infinity, Infinity], [-Infinity, Infinity]]; state.args = args; state.axis = void 0; state.memo = void 0; state.elapsedTime = state.timeDelta = 0; state.direction = [0, 0]; state.distance = [0, 0]; state.overflow = [0, 0]; state._movementBound = [false, false]; state.velocity = [0, 0]; state.movement = [0, 0]; state.delta = [0, 0]; state.timeStamp = 0; } start(event) { const state = this.state; const config = this.config; if (!state._active) { this.reset(); this.computeInitial(); state._active = true; state.target = event.target; state.currentTarget = event.currentTarget; state.lastOffset = config.from ? call(config.from, state) : state.offset; state.offset = state.lastOffset; state.startTime = state.timeStamp = event.timeStamp; } } computeValues(values) { const state = this.state; state._values = values; state.values = this.config.transform(values); } computeInitial() { const state = this.state; state._initial = state._values; state.initial = state.values; } compute(event) { const { state, config, shared } = this; state.args = this.args; let dt = 0; if (event) { state.event = event; if (config.preventDefault && event.cancelable) state.event.preventDefault(); state.type = event.type; shared.touches = this.ctrl.pointerIds.size || this.ctrl.touchIds.size; shared.locked = !!document.pointerLockElement; Object.assign(shared, getEventDetails(event)); shared.down = shared.pressed = shared.buttons % 2 === 1 || shared.touches > 0; dt = event.timeStamp - state.timeStamp; state.timeStamp = event.timeStamp; state.elapsedTime = state.timeStamp - state.startTime; } if (state._active) { const _absoluteDelta = state._delta.map(Math.abs); V.addTo(state._distance, _absoluteDelta); } if (this.axisIntent) this.axisIntent(event); const [_m0, _m1] = state._movement; const [t0, t1] = config.threshold; const { _step, values } = state; if (config.hasCustomTransform) { if (_step[0] === false) _step[0] = Math.abs(_m0) >= t0 && values[0]; if (_step[1] === false) _step[1] = Math.abs(_m1) >= t1 && values[1]; } else { if (_step[0] === false) _step[0] = Math.abs(_m0) >= t0 && Math.sign(_m0) * t0; if (_step[1] === false) _step[1] = Math.abs(_m1) >= t1 && Math.sign(_m1) * t1; } state.intentional = _step[0] !== false || _step[1] !== false; if (!state.intentional) return; const movement = [0, 0]; if (config.hasCustomTransform) { const [v0, v1] = values; movement[0] = _step[0] !== false ? v0 - _step[0] : 0; movement[1] = _step[1] !== false ? v1 - _step[1] : 0; } else { movement[0] = _step[0] !== false ? _m0 - _step[0] : 0; movement[1] = _step[1] !== false ? _m1 - _step[1] : 0; } if (this.restrictToAxis && !state._blocked) this.restrictToAxis(movement); const previousOffset = state.offset; const gestureIsActive = state._active && !state._blocked || state.active; if (gestureIsActive) { state.first = state._active && !state.active; state.last = !state._active && state.active; state.active = shared[this.ingKey] = state._active; if (event) { if (state.first) { if ("bounds" in config) state._bounds = call(config.bounds, state); if (this.setup) this.setup(); } state.movement = movement; this.computeOffset(); } } const [ox, oy] = state.offset; const [[x0, x1], [y0, y1]] = state._bounds; state.overflow = [ox < x0 ? -1 : ox > x1 ? 1 : 0, oy < y0 ? -1 : oy > y1 ? 1 : 0]; state._movementBound[0] = state.overflow[0] ? state._movementBound[0] === false ? state._movement[0] : state._movementBound[0] : false; state._movementBound[1] = state.overflow[1] ? state._movementBound[1] === false ? state._movement[1] : state._movementBound[1] : false; const rubberband2 = state._active ? config.rubberband || [0, 0] : [0, 0]; state.offset = computeRubberband(state._bounds, state.offset, rubberband2); state.delta = V.sub(state.offset, previousOffset); this.computeMovement(); if (gestureIsActive && (!state.last || dt > BEFORE_LAST_KINEMATICS_DELAY)) { state.delta = V.sub(state.offset, previousOffset); const absoluteDelta = state.delta.map(Math.abs); V.addTo(state.distance, absoluteDelta); state.direction = state.delta.map(Math.sign); state._direction = state._delta.map(Math.sign); if (!state.first && dt > 0) { state.velocity = [absoluteDelta[0] / dt, absoluteDelta[1] / dt]; state.timeDelta = dt; } } } emit() { const state = this.state; const shared = this.shared; const config = this.config; if (!state._active) this.clean(); if ((state._blocked || !state.intentional) && !state._force && !config.triggerAllEvents) return; const memo = this.handler(_objectSpread2(_objectSpread2(_objectSpread2({}, shared), state), {}, { [this.aliasKey]: state.values })); if (memo !== void 0) state.memo = memo; } clean() { this.eventStore.clean(); this.timeoutStore.clean(); } }; function selectAxis([dx, dy], threshold) { const absDx = Math.abs(dx); const absDy = Math.abs(dy); if (absDx > absDy && absDx > threshold) { return "x"; } if (absDy > absDx && absDy > threshold) { return "y"; } return void 0; } var CoordinatesEngine = class extends Engine { constructor(...args) { super(...args); _defineProperty(this, "aliasKey", "xy"); } reset() { super.reset(); this.state.axis = void 0; } init() { this.state.offset = [0, 0]; this.state.lastOffset = [0, 0]; } computeOffset() { this.state.offset = V.add(this.state.lastOffset, this.state.movement); } computeMovement() { this.state.movement = V.sub(this.state.offset, this.state.lastOffset); } axisIntent(event) { const state = this.state; const config = this.config; if (!state.axis && event) { const threshold = typeof config.axisThreshold === "object" ? config.axisThreshold[getPointerType(event)] : config.axisThreshold; state.axis = selectAxis(state._movement, threshold); } state._blocked = (config.lockDirection || !!config.axis) && !state.axis || !!config.axis && config.axis !== state.axis; } restrictToAxis(v) { if (this.config.axis || this.config.lockDirection) { switch (this.state.axis) { case "x": v[1] = 0; break; case "y": v[0] = 0; break; } } } }; var identity = (v) => v; var DEFAULT_RUBBERBAND = 0.15; var commonConfigResolver = { enabled(value = true) { return value; }, eventOptions(value, _k, config) { return _objectSpread2(_objectSpread2({}, config.shared.eventOptions), value); }, preventDefault(value = false) { return value; }, triggerAllEvents(value = false) { return value; }, rubberband(value = 0) { switch (value) { case true: return [DEFAULT_RUBBERBAND, DEFAULT_RUBBERBAND]; case false: return [0, 0]; default: return V.toVector(value); } }, from(value) { if (typeof value === "function") return value; if (value != null) return V.toVector(value); }, transform(value, _k, config) { const transform = value || config.shared.transform; this.hasCustomTransform = !!transform; if (process.env.NODE_ENV === "development") { const originalTransform = transform || identity; return (v) => { const r2 = originalTransform(v); if (!isFinite(r2[0]) || !isFinite(r2[1])) { console.warn(`[@use-gesture]: config.transform() must produce a valid result, but it was: [${r2[0]},${[1]}]`); } return r2; }; } return transform || identity; }, threshold(value) { return V.toVector(value, 0); } }; if (process.env.NODE_ENV === "development") { Object.assign(commonConfigResolver, { domTarget(value) { if (value !== void 0) { throw Error(`[@use-gesture]: \`domTarget\` option has been renamed to \`target\`.`); } return NaN; }, lockDirection(value) { if (value !== void 0) { throw Error(`[@use-gesture]: \`lockDirection\` option has been merged with \`axis\`. Use it as in \`{ axis: 'lock' }\``); } return NaN; }, initial(value) { if (value !== void 0) { throw Error(`[@use-gesture]: \`initial\` option has been renamed to \`from\`.`); } return NaN; } }); } var DEFAULT_AXIS_THRESHOLD = 0; var coordinatesConfigResolver = _objectSpread2(_objectSpread2({}, commonConfigResolver), {}, { axis(_v, _k, { axis }) { this.lockDirection = axis === "lock"; if (!this.lockDirection) return axis; }, axisThreshold(value = DEFAULT_AXIS_THRESHOLD) { return value; }, bounds(value = {}) { if (typeof value === "function") { return (state) => coordinatesConfigResolver.bounds(value(state)); } if ("current" in value) { return () => value.current; } if (typeof HTMLElement === "function" && value instanceof HTMLElement) { return value; } const { left = -Infinity, right = Infinity, top = -Infinity, bottom = Infinity } = value; return [[left, right], [