@letter-ai/lector
Version:
Headless PDF viewer for React
1,700 lines (1,685 loc) • 122 kB
JavaScript
'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], [