diff --git a/packages/react-pdf/README.md b/packages/react-pdf/README.md index 811246e61..3676e7dca 100644 --- a/packages/react-pdf/README.md +++ b/packages/react-pdf/README.md @@ -485,7 +485,7 @@ Loads a document passed using `file` prop. ### Page -Displays a page. Should be placed inside ``. Alternatively, it can have `pdf` prop passed, which can be obtained from ``'s `onLoadSuccess` callback function, however some advanced functions like linking between pages inside a document may not be working correctly. +Displays a page. Should be placed inside ``. Alternatively, it can have `pdf` prop passed, which can be obtained from ``'s `onLoadSuccess` callback function, however some advanced functions like rendering annotations and linking between pages inside a document may not be working correctly. #### Props diff --git a/packages/react-pdf/src/Outline.spec.tsx b/packages/react-pdf/src/Outline.spec.tsx index 0641a72b7..74ad1fcbb 100644 --- a/packages/react-pdf/src/Outline.spec.tsx +++ b/packages/react-pdf/src/Outline.spec.tsx @@ -58,7 +58,7 @@ describe('Outline', () => { }); describe('loading', () => { - it('loads an outline and calls onLoadSuccess callback properly', async () => { + it('loads an outline and calls onLoadSuccess callback properly when placed inside Document', async () => { const { func: onLoadSuccess, promise: onLoadSuccessPromise } = makeAsyncCallback(); renderWithContext(, { pdf }); @@ -68,6 +68,16 @@ describe('Outline', () => { await expect(onLoadSuccessPromise).resolves.toMatchObject([desiredLoadedOutline]); }); + it('loads an outline and calls onLoadSuccess callback properly when pdf prop is passed', async () => { + const { func: onLoadSuccess, promise: onLoadSuccessPromise } = makeAsyncCallback(); + + render(); + + expect.assertions(1); + + await expect(onLoadSuccessPromise).resolves.toMatchObject([desiredLoadedOutline]); + }); + it('calls onLoadError when failed to load an outline', async () => { const { func: onLoadError, promise: onLoadErrorPromise } = makeAsyncCallback(); @@ -99,7 +109,7 @@ describe('Outline', () => { await expect(onLoadSuccessPromise2).resolves.toMatchObject([desiredLoadedOutline2]); }); - it('throws an error when placed outside Document', () => { + it('throws an error when placed outside Document without pdf prop passed', () => { muteConsole(); expect(() => render()).toThrow(); diff --git a/packages/react-pdf/src/Outline.tsx b/packages/react-pdf/src/Outline.tsx index 0d253e074..0f8bb09e1 100644 --- a/packages/react-pdf/src/Outline.tsx +++ b/packages/react-pdf/src/Outline.tsx @@ -69,11 +69,6 @@ export type OutlineProps = { const Outline: React.FC = function Outline(props) { const documentContext = useDocumentContext(); - invariant( - documentContext, - 'Unable to find Document context. Did you wrap in ?', - ); - const mergedProps = { ...documentContext, ...props }; const { className, @@ -85,7 +80,10 @@ const Outline: React.FC = function Outline(props) { ...otherProps } = mergedProps; - invariant(pdf, 'Attempted to load an outline, but no document was specified.'); + invariant( + pdf, + 'Attempted to load an outline, but no document was specified. Wrap in a or pass explicit `pdf` prop.', + ); const [outlineState, outlineDispatch] = useResolver(); const { value: outline, error: outlineError } = outlineState; @@ -189,7 +187,11 @@ const Outline: React.FC = function Outline(props) { return (
    {outline.map((item, itemIndex) => ( - + ))}
); diff --git a/packages/react-pdf/src/OutlineItem.tsx b/packages/react-pdf/src/OutlineItem.tsx index 3f41299fd..2ee90d2be 100644 --- a/packages/react-pdf/src/OutlineItem.tsx +++ b/packages/react-pdf/src/OutlineItem.tsx @@ -16,16 +16,12 @@ type PDFOutlineItem = PDFOutline[number]; type OutlineItemProps = { item: PDFOutlineItem; + pdf?: PDFDocumentProxy | false; }; export default function OutlineItem(props: OutlineItemProps) { const documentContext = useDocumentContext(); - invariant( - documentContext, - 'Unable to find Document context. Did you wrap in ?', - ); - const outlineContext = useOutlineContext(); invariant(outlineContext, 'Unable to find Outline context.'); @@ -33,7 +29,10 @@ export default function OutlineItem(props: OutlineItemProps) { const mergedProps = { ...documentContext, ...outlineContext, ...props }; const { item, linkService, onItemClick, pdf, ...otherProps } = mergedProps; - invariant(pdf, 'Attempted to load an outline, but no document was specified.'); + invariant( + pdf, + 'Attempted to load an outline, but no document was specified. Wrap in a or pass explicit `pdf` prop.', + ); const getDestination = useCachedValue(() => { if (typeof item.dest === 'string') { @@ -64,6 +63,11 @@ export default function OutlineItem(props: OutlineItemProps) { function onClick(event: React.MouseEvent) { event.preventDefault(); + invariant( + onItemClick || linkService, + 'Either onItemClick callback or linkService must be defined in order to navigate to an outline item.', + ); + if (onItemClick) { Promise.all([getDestination(), getPageIndex(), getPageNumber()]).then( ([dest, pageIndex, pageNumber]) => { @@ -74,7 +78,7 @@ export default function OutlineItem(props: OutlineItemProps) { }); }, ); - } else { + } else if (linkService) { linkService.goToDestination(item.dest); } } @@ -92,6 +96,7 @@ export default function OutlineItem(props: OutlineItemProps) { ))} diff --git a/packages/react-pdf/src/Page.spec.tsx b/packages/react-pdf/src/Page.spec.tsx index 1ae8c9f90..a5605f329 100644 --- a/packages/react-pdf/src/Page.spec.tsx +++ b/packages/react-pdf/src/Page.spec.tsx @@ -82,7 +82,7 @@ describe('Page', () => { }); describe('loading', () => { - it('loads a page and calls onLoadSuccess callback properly', async () => { + it('loads a page and calls onLoadSuccess callback properly when placed inside Document', async () => { const { func: onLoadSuccess, promise: onLoadSuccessPromise } = makeAsyncCallback(); renderWithContext(, { @@ -95,6 +95,23 @@ describe('Page', () => { await expect(onLoadSuccessPromise).resolves.toMatchObject([desiredLoadedPage]); }); + it('loads a page and calls onLoadSuccess callback properly when pdf prop is passed', async () => { + const { func: onLoadSuccess, promise: onLoadSuccessPromise } = makeAsyncCallback(); + + render( + , + ); + + expect.assertions(1); + + await expect(onLoadSuccessPromise).resolves.toMatchObject([desiredLoadedPage]); + }); + it('returns all desired parameters in onLoadSuccess callback', async () => { const { func: onLoadSuccess, promise: onLoadSuccessPromise } = makeAsyncCallback<[PageCallback]>(); @@ -252,7 +269,7 @@ describe('Page', () => { await expect(onLoadSuccessPromise2).resolves.toMatchObject([desiredLoadedPage2]); }); - it('throws an error when placed outside Document', () => { + it('throws an error when placed outside Document without pdf prop passed', () => { muteConsole(); expect(() => render()).toThrow(); diff --git a/packages/react-pdf/src/Page.tsx b/packages/react-pdf/src/Page.tsx index d9ca45ca5..a2b10ca78 100644 --- a/packages/react-pdf/src/Page.tsx +++ b/packages/react-pdf/src/Page.tsx @@ -317,11 +317,6 @@ export type PageProps = { const Page: React.FC = function Page(props) { const documentContext = useDocumentContext(); - invariant( - documentContext, - 'Unable to find Document context. Did you wrap in ?', - ); - const mergedProps = { ...documentContext, ...props }; const { _className = 'react-pdf__Page', @@ -371,7 +366,10 @@ const Page: React.FC = function Page(props) { const { value: page, error: pageError } = pageState; const pageElement = useRef(null); - invariant(pdf, 'Attempted to load a page, but no document was specified.'); + invariant( + pdf, + 'Attempted to load a page, but no document was specified. Wrap in a or pass explicit `pdf` prop.', + ); const pageIndex = isProvided(pageNumberProps) ? pageNumberProps - 1 : pageIndexProps ?? null; diff --git a/packages/react-pdf/src/Page/AnnotationLayer.tsx b/packages/react-pdf/src/Page/AnnotationLayer.tsx index a1c36f1b7..8b3187bbe 100644 --- a/packages/react-pdf/src/Page/AnnotationLayer.tsx +++ b/packages/react-pdf/src/Page/AnnotationLayer.tsx @@ -17,12 +17,6 @@ import type { Annotations } from '../shared/types.js'; export default function AnnotationLayer() { const documentContext = useDocumentContext(); - - invariant( - documentContext, - 'Unable to find Document context. Did you wrap in ?', - ); - const pageContext = usePageContext(); invariant(pageContext, 'Unable to find Page context.'); @@ -42,7 +36,12 @@ export default function AnnotationLayer() { scale = 1, } = mergedProps; + invariant( + pdf, + 'Attempted to load page annotations, but no document was specified. Wrap in a or pass explicit `pdf` prop.', + ); invariant(page, 'Attempted to load page annotations, but no page was specified.'); + invariant(linkService, 'Attempted to load page annotations, but no linkService was specified.'); const [annotationsState, annotationsDispatch] = useResolver(); const { value: annotations, error: annotationsError } = annotationsState; @@ -147,7 +146,7 @@ export default function AnnotationLayer() { ); function renderAnnotationLayer() { - if (!pdf || !page || !annotations) { + if (!pdf || !page || !linkService || !annotations) { return; } diff --git a/packages/react-pdf/src/Thumbnail.spec.tsx b/packages/react-pdf/src/Thumbnail.spec.tsx index dcd79ad12..f28b2e46b 100644 --- a/packages/react-pdf/src/Thumbnail.spec.tsx +++ b/packages/react-pdf/src/Thumbnail.spec.tsx @@ -68,7 +68,7 @@ describe('Thumbnail', () => { }); describe('loading', () => { - it('loads a page and calls onLoadSuccess callback properly', async () => { + it('loads a page and calls onLoadSuccess callback properly when placed inside Document', async () => { const { func: onLoadSuccess, promise: onLoadSuccessPromise } = makeAsyncCallback(); renderWithContext(, { pdf }); @@ -78,6 +78,16 @@ describe('Thumbnail', () => { await expect(onLoadSuccessPromise).resolves.toMatchObject([desiredLoadedThumbnail]); }); + it('loads a page and calls onLoadSuccess callback properly when pdf prop is passed', async () => { + const { func: onLoadSuccess, promise: onLoadSuccessPromise } = makeAsyncCallback(); + + render(); + + expect.assertions(1); + + await expect(onLoadSuccessPromise).resolves.toMatchObject([desiredLoadedThumbnail]); + }); + it('returns all desired parameters in onLoadSuccess callback', async () => { const { func: onLoadSuccess, promise: onLoadSuccessPromise } = makeAsyncCallback<[PageCallback]>(); @@ -190,7 +200,7 @@ describe('Thumbnail', () => { await expect(onLoadSuccessPromise2).resolves.toMatchObject([desiredLoadedThumbnail2]); }); - it('throws an error when placed outside Document', () => { + it('throws an error when placed outside Document without pdf prop passed', () => { muteConsole(); expect(() => render()).toThrow(); diff --git a/packages/react-pdf/src/Thumbnail.tsx b/packages/react-pdf/src/Thumbnail.tsx index 4184494f7..823bb4113 100644 --- a/packages/react-pdf/src/Thumbnail.tsx +++ b/packages/react-pdf/src/Thumbnail.tsx @@ -52,11 +52,6 @@ export type ThumbnailProps = Omit< const Thumbnail: React.FC = function Thumbnail(props) { const documentContext = useDocumentContext(); - invariant( - documentContext, - 'Unable to find Document context. Did you wrap in ?', - ); - const mergedProps = { ...documentContext, ...props }; const { className, @@ -64,8 +59,14 @@ const Thumbnail: React.FC = function Thumbnail(props) { onItemClick, pageIndex: pageIndexProps, pageNumber: pageNumberProps, + pdf, } = mergedProps; + invariant( + pdf, + 'Attempted to load a thumbnail, but no document was specified. Wrap in a or pass explicit `pdf` prop.', + ); + const pageIndex = isProvided(pageNumberProps) ? pageNumberProps - 1 : pageIndexProps ?? null; const pageNumber = pageNumberProps ?? (isProvided(pageIndexProps) ? pageIndexProps + 1 : null); @@ -77,12 +78,17 @@ const Thumbnail: React.FC = function Thumbnail(props) { return; } + invariant( + onItemClick || linkService, + 'Either onItemClick callback or linkService must be defined in order to navigate to an outline item.', + ); + if (onItemClick) { onItemClick({ pageIndex, pageNumber, }); - } else { + } else if (linkService) { linkService.goToPage(pageNumber); } }