@beyondwork/pdf-viewer
v0.0.7
Published
React (web) port of the `pdf-viewer` custom element from `../web-components`.
Readme
@beyondwork/pdf-viewer
React (web) port of the pdf-viewer custom element from ../web-components.
Features parity:
- PDF rendering (pdfjs-dist) with text layer and highlight canvas
- Single
srcor multi-filesrcswith a file switcher - Normalized (0-1) bounding-box highlights, rotation-aware
viewportOnlyHighlightsmode- Text match/search with automatic scroll-to-match
- Zoom (buttons, cmd+wheel, cmd+/-, cmd+0)
- Rotation (0 / 90 / 180 / 270)
- Page navigation (buttons, arrow keys)
- Controlled
pageNumberandrotationprops +onPageChange,onRotationChange,onViewportChangecallbacks - Optional
translateSrcprop to resolve custom blob-reference schemes before pdf.js loads them
Usage
import { PDFViewer } from '@beyondwork/pdf-viewer';
<PDFViewer
src="/my-file.pdf"
pageNumber={1}
rotation={0}
highlights={[
{
pageNumber: 1,
top: 0.1,
left: 0.1,
width: 0.3,
height: 0.05,
color: 'rgba(255, 255, 0, 0.5)',
},
]}
onPageChange={(n) => console.log('page', n)}
/>;Resolving custom src schemes (translateSrc)
If your app uses a non-standard URL scheme (e.g. Beyond Work's ::bw:data
blob references), pass translateSrc to resolve it to a real URL or binary
payload before pdf.js receives it. When omitted, src is passed through to
pdf.js as-is (HTTP URLs, data URIs and blob URLs all work natively).
<PDFViewer
src="::bw:data/some-ref"
translateSrc={async (src) => {
if (src.startsWith('::bw:data')) return window.bw.fetchBlob(src);
return src;
}}
/>Highlighting
The viewer supports two independent highlighting mechanisms:
1. Text-match highlighting (match prop)
Finds every occurrence of a string in the PDF's text layer and paints them yellow. Match is diacritic-insensitive and case-insensitive. Automatically scrolls to the first hit.
import { PDFViewer } from '@beyondwork/pdf-viewer';
import { useState } from 'react';
function Invoice() {
const [match, setMatch] = useState<string | undefined>();
return (
<>
<div>
<button onClick={() => setMatch('total')}>Highlight "total"</button>
<button onClick={() => setMatch('invoice number')}>Highlight "invoice number"</button>
<button onClick={() => setMatch(undefined)}>Clear</button>
</div>
<PDFViewer src="/invoice.pdf" match={match} />
</>
);
}2. Bounding-box highlighting (highlights prop)
Paints colored rectangles at arbitrary normalized coordinates. Use this when you know
exactly where to draw (e.g. coordinates returned from a document-AI model). Coords are
0..1 relative to the unrotated page — the viewer transforms them for the current rotation.
import { BoundingBoxHighlight, PDFViewer } from '@beyondwork/pdf-viewer';
import { useState } from 'react';
const FIELDS: Record<string, BoundingBoxHighlight> = {
amount: {
pageNumber: 1,
top: 0.42,
left: 0.62,
width: 0.22,
height: 0.04,
color: 'rgba(0, 200, 120, 0.35)',
},
dueDate: {
pageNumber: 1,
top: 0.18,
left: 0.12,
width: 0.3,
height: 0.04,
color: 'rgba(255, 180, 0, 0.4)',
},
};
function Invoice() {
const [highlights, setHighlights] = useState<BoundingBoxHighlight[]>([]);
return (
<>
<div>
<button onClick={() => setHighlights([FIELDS.amount])}>Show amount</button>
<button onClick={() => setHighlights([FIELDS.dueDate])}>Show due date</button>
<button onClick={() => setHighlights(Object.values(FIELDS))}>Show all</button>
<button onClick={() => setHighlights([])}>Clear</button>
</div>
<PDFViewer src="/invoice.pdf" highlights={highlights} />
</>
);
}Tips
- Set
viewportOnlyHighlightstotrueto suppress auto-scroll-to-highlight and only render highlights that are currently in view (useful when you have many highlights and want to keep the user's scroll position stable). - Use
onViewportChangeto get a list of currently-visible highlights — handy for syncing a sidebar/legend with what's on-screen. highlightsandmatchcan be used together; clearingmatch(set toundefinedor'') restores the bounding-box highlights.
Styling (Tailwind or any CSS framework)
Pass classNames to override any internal slot. User classes are appended after
the built-in pdfv-* classes, so Tailwind utilities win by default CSS cascade.
<PDFViewer
src="/file.pdf"
classNames={{
root: 'rounded-xl border border-zinc-200',
container: 'bg-white',
pageControls: 'bottom-6',
pageControlsButton: 'hover:bg-blue-50',
pageControlsCounter: 'text-blue-600',
zoomControls: 'bottom-6 right-6',
zoomControlsButton: 'hover:bg-blue-50',
srcControls: 'top-6 left-6',
}}
/>Available slots: root, container, pdfContainer, errorMessage,
noMatchesMessage, spinner, srcControls, pageControls, pageControlsButton,
pageControlsCounter, zoomControls, zoomControlsButton.
Development
pnpm dev # demo at http://localhost:3006
pnpm build
pnpm test
pnpm lint
pnpm check-types