'use client';
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { jsx as _jsx } from "react/jsx-runtime";
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
import makeEventProps from 'make-event-props';
import makeCancellable from 'make-cancellable-promise';
import clsx from 'clsx';
import invariant from 'tiny-invariant';
import warning from 'warning';
import { dequal } from 'dequal';
import * as pdfjs from 'pdfjs-dist';
import DocumentContext from './DocumentContext.js';
import Message from './Message.js';
import LinkService from './LinkService.js';
import PasswordResponses from './PasswordResponses.js';
import { cancelRunningTask, dataURItoByteString, displayCORSWarning, isArrayBuffer, isBlob, isBrowser, isDataURI, loadFromFile, } from './shared/utils.js';
import useResolver from './shared/hooks/useResolver.js';
const { PDFDataRangeTransport } = pdfjs;
const defaultOnPassword = (callback, reason) => {
switch (reason) {
case PasswordResponses.NEED_PASSWORD: {
const password = prompt('Enter the password to open this PDF file.');
callback(password);
break;
}
case PasswordResponses.INCORRECT_PASSWORD: {
const password = prompt('Invalid password. Please try again.');
callback(password);
break;
}
default:
}
};
function isParameterObject(file) {
return (typeof file === 'object' &&
file !== null &&
('data' in file || 'range' in file || 'url' in file));
}
/**
* Loads a document passed using `file` prop.
*/
const Document = forwardRef(function Document(_a, ref) {
var { children, className, error = 'Failed to load PDF file.', externalLinkRel, externalLinkTarget, file, inputRef, imageResourcesPath, loading = 'Loading PDF…', noData = 'No PDF file specified.', onItemClick, onLoadError: onLoadErrorProps, onLoadProgress, onLoadSuccess: onLoadSuccessProps, onPassword = defaultOnPassword, onSourceError: onSourceErrorProps, onSourceSuccess: onSourceSuccessProps, options, renderMode, rotate } = _a, otherProps = __rest(_a, ["children", "className", "error", "externalLinkRel", "externalLinkTarget", "file", "inputRef", "imageResourcesPath", "loading", "noData", "onItemClick", "onLoadError", "onLoadProgress", "onLoadSuccess", "onPassword", "onSourceError", "onSourceSuccess", "options", "renderMode", "rotate"]);
const [sourceState, sourceDispatch] = useResolver();
const { value: source, error: sourceError } = sourceState;
const [pdfState, pdfDispatch] = useResolver();
const { value: pdf, error: pdfError } = pdfState;
const linkService = useRef(new LinkService());
const pages = useRef([]);
const prevFile = useRef(undefined);
const prevOptions = useRef(undefined);
if (file && file !== prevFile.current && isParameterObject(file)) {
warning(!dequal(file, prevFile.current), `File prop passed to changed, but it's equal to previous one. This might result in unnecessary reloads. Consider memoizing the value passed to "file" prop.`);
prevFile.current = file;
}
// Detect non-memoized changes in options prop
if (options && options !== prevOptions.current) {
warning(!dequal(options, prevOptions.current), `Options prop passed to changed, but it's equal to previous one. This might result in unnecessary reloads. Consider memoizing the value passed to "options" prop.`);
prevOptions.current = options;
}
const viewer = useRef({
// Handling jumping to internal links target
scrollPageIntoView: (args) => {
const { dest, pageNumber, pageIndex = pageNumber - 1 } = args;
// First, check if custom handling of onItemClick was provided
if (onItemClick) {
onItemClick({ dest, pageIndex, pageNumber });
return;
}
// If not, try to look for target page within the .
const page = pages.current[pageIndex];
if (page) {
// Scroll to the page automatically
page.scrollIntoView();
return;
}
warning(false, `An internal link leading to page ${pageNumber} was clicked, but neither was provided with onItemClick nor it was able to find the page within itself. Either provide onItemClick to and handle navigating by yourself or ensure that all pages are rendered within .`);
},
});
useImperativeHandle(ref, () => ({
linkService,
pages,
viewer,
}), []);
/**
* Called when a document source is resolved correctly
*/
function onSourceSuccess() {
if (onSourceSuccessProps) {
onSourceSuccessProps();
}
}
/**
* Called when a document source failed to be resolved correctly
*/
function onSourceError() {
if (!sourceError) {
// Impossible, but TypeScript doesn't know that
return;
}
warning(false, sourceError.toString());
if (onSourceErrorProps) {
onSourceErrorProps(sourceError);
}
}
function resetSource() {
sourceDispatch({ type: 'RESET' });
}
// biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080
useEffect(resetSource, [file, sourceDispatch]);
const findDocumentSource = useCallback(() => __awaiter(this, void 0, void 0, function* () {
if (!file) {
return null;
}
// File is a string
if (typeof file === 'string') {
if (isDataURI(file)) {
const fileByteString = dataURItoByteString(file);
return { data: fileByteString };
}
displayCORSWarning();
return { url: file };
}
// File is PDFDataRangeTransport
if (file instanceof PDFDataRangeTransport) {
return { range: file };
}
// File is an ArrayBuffer
if (isArrayBuffer(file)) {
return { data: file };
}
/**
* The cases below are browser-only.
* If you're running on a non-browser environment, these cases will be of no use.
*/
if (isBrowser) {
// File is a Blob
if (isBlob(file)) {
const data = yield loadFromFile(file);
return { data };
}
}
// At this point, file must be an object
invariant(typeof file === 'object', 'Invalid parameter in file, need either Uint8Array, string or a parameter object');
invariant(isParameterObject(file), 'Invalid parameter object: need either .data, .range or .url');
// File .url is a string
if ('url' in file && typeof file.url === 'string') {
if (isDataURI(file.url)) {
const { url } = file, otherParams = __rest(file, ["url"]);
const fileByteString = dataURItoByteString(url);
return Object.assign({ data: fileByteString }, otherParams);
}
displayCORSWarning();
}
return file;
}), [file]);
useEffect(() => {
const cancellable = makeCancellable(findDocumentSource());
cancellable.promise
.then((nextSource) => {
sourceDispatch({ type: 'RESOLVE', value: nextSource });
})
.catch((error) => {
sourceDispatch({ type: 'REJECT', error });
});
return () => {
cancelRunningTask(cancellable);
};
}, [findDocumentSource, sourceDispatch]);
// biome-ignore lint/correctness/useExhaustiveDependencies: Ommitted callbacks so they are not called every time they change
useEffect(() => {
if (typeof source === 'undefined') {
return;
}
if (source === false) {
onSourceError();
return;
}
onSourceSuccess();
}, [source]);
/**
* Called when a document is read successfully
*/
function onLoadSuccess() {
if (!pdf) {
// Impossible, but TypeScript doesn't know that
return;
}
if (onLoadSuccessProps) {
onLoadSuccessProps(pdf);
}
pages.current = new Array(pdf.numPages);
linkService.current.setDocument(pdf);
}
/**
* Called when a document failed to read successfully
*/
function onLoadError() {
if (!pdfError) {
// Impossible, but TypeScript doesn't know that
return;
}
warning(false, pdfError.toString());
if (onLoadErrorProps) {
onLoadErrorProps(pdfError);
}
}
// biome-ignore lint/correctness/useExhaustiveDependencies: useEffect intentionally triggered on source change
useEffect(function resetDocument() {
pdfDispatch({ type: 'RESET' });
}, [pdfDispatch, source]);
// biome-ignore lint/correctness/useExhaustiveDependencies: Ommitted callbacks so they are not called every time they change
useEffect(function loadDocument() {
if (!source) {
return;
}
const documentInitParams = options
? Object.assign(Object.assign({}, source), options) : source;
const destroyable = pdfjs.getDocument(documentInitParams);
if (onLoadProgress) {
destroyable.onProgress = onLoadProgress;
}
if (onPassword) {
destroyable.onPassword = onPassword;
}
const loadingTask = destroyable;
const loadingPromise = loadingTask.promise
.then((nextPdf) => {
pdfDispatch({ type: 'RESOLVE', value: nextPdf });
})
.catch((error) => {
if (loadingTask.destroyed) {
return;
}
pdfDispatch({ type: 'REJECT', error });
});
return () => {
loadingPromise.finally(() => loadingTask.destroy());
};
}, [options, pdfDispatch, source]);
// biome-ignore lint/correctness/useExhaustiveDependencies: Ommitted callbacks so they are not called every time they change
useEffect(() => {
if (typeof pdf === 'undefined') {
return;
}
if (pdf === false) {
onLoadError();
return;
}
onLoadSuccess();
}, [pdf]);
useEffect(function setupLinkService() {
linkService.current.setViewer(viewer.current);
linkService.current.setExternalLinkRel(externalLinkRel);
linkService.current.setExternalLinkTarget(externalLinkTarget);
}, [externalLinkRel, externalLinkTarget]);
const registerPage = useCallback((pageIndex, ref) => {
pages.current[pageIndex] = ref;
}, []);
const unregisterPage = useCallback((pageIndex) => {
delete pages.current[pageIndex];
}, []);
const childContext = useMemo(() => ({
imageResourcesPath,
linkService: linkService.current,
onItemClick,
pdf,
registerPage,
renderMode,
rotate,
unregisterPage,
}), [imageResourcesPath, onItemClick, pdf, registerPage, renderMode, rotate, unregisterPage]);
const eventProps = useMemo(() => makeEventProps(otherProps, () => pdf),
// biome-ignore lint/correctness/useExhaustiveDependencies: FIXME
[otherProps, pdf]);
function renderChildren() {
return _jsx(DocumentContext.Provider, { value: childContext, children: children });
}
function renderContent() {
if (!file) {
return _jsx(Message, { type: "no-data", children: typeof noData === 'function' ? noData() : noData });
}
if (pdf === undefined || pdf === null) {
return (_jsx(Message, { type: "loading", children: typeof loading === 'function' ? loading() : loading }));
}
if (pdf === false) {
return _jsx(Message, { type: "error", children: typeof error === 'function' ? error() : error });
}
return renderChildren();
}
return (_jsx("div", Object.assign({ className: clsx('react-pdf__Document', className),
// Assertion is needed for React 18 compatibility
ref: inputRef, style: {
['--scale-factor']: '1',
} }, eventProps, { children: renderContent() })));
});
export default Document;