@anaralabs/lector 3.14.10 → 3.14.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,44 +1,41 @@
1
- <p align="center">
2
- <p align="center">
3
- <i>Simple primitives to compose powerful PDF viewing experiences.<br>powered by <code><a href="https://mozilla.github.io/pdf.js/">PDF.js</a></code> and <code><a href="https://reactjs.org/">React</a></code></i>
4
- </p>
5
- </p>
1
+ # Lector
6
2
 
7
- # `lector`
3
+ **A headless PDF viewer for React.** Compose pages, text selection, search, and annotations into your own reading experience. Lector handles PDF.js rendering and page virtualization; you control the layout and UI.
8
4
 
9
- A composable, headless PDF viewer toolkit for React applications, powered by `PDF.js`. Build feature-rich PDF viewing experiences with full control over the UI and functionality.
5
+ [Documentation](https://lector-weld.vercel.app/docs) · [Live demo](https://lector-weld.vercel.app) · [npm](https://www.npmjs.com/package/@anaralabs/lector) · [Contributing](https://github.com/anaralabs/lector/blob/main/CONTRIBUTING.md)
10
6
 
11
- [![npm version](https://badge.fury.io/js/@anaralabs%2Flector.svg)](https://www.npmjs.com/package/@anaralabs/lector)
12
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+ ## Get a PDF on screen
13
8
 
14
- ## Installation
9
+ Use React 19+. The current source expects `pdfjs-dist` `^5.5.207`. These docs track `main`; check the [releases](https://github.com/anaralabs/lector/releases) against your installed package version.
15
10
 
16
11
  ```bash
17
- npm install @anaralabs/lector pdfjs-dist
12
+ npm install @anaralabs/lector pdfjs-dist@^5.5.207
13
+ ```
18
14
 
19
- # or with yarn
20
- yarn add @anaralabs/lector pdfjs-dist
15
+ Copy the PDF.js worker from your app's installed dependency to its public assets. Recopy it whenever you update PDF.js so the runtime and worker versions match.
21
16
 
22
- # or with pnpm
23
- pnpm add @anaralabs/lector pdfjs-dist
17
+ ```bash
18
+ mkdir -p public/pdfjs
19
+ cp node_modules/pdfjs-dist/legacy/build/pdf.worker.min.mjs public/pdfjs/
24
20
  ```
25
21
 
26
- ## Basic Usage
27
-
28
- Here's a simple example of how to create a basic PDF viewer:
22
+ Put a PDF at `public/sample.pdf`, then add this browser-rendered component:
29
23
 
30
24
  ```tsx
31
25
  import { CanvasLayer, Page, Pages, Root, TextLayer } from "@anaralabs/lector";
26
+ import { GlobalWorkerOptions } from "pdfjs-dist/legacy/build/pdf.mjs";
32
27
  import "pdfjs-dist/web/pdf_viewer.css";
33
28
 
29
+ GlobalWorkerOptions.workerSrc = "/pdfjs/pdf.worker.min.mjs";
30
+
34
31
  export default function PDFViewer() {
35
32
  return (
36
33
  <Root
37
34
  source="/sample.pdf"
38
- className="w-full h-[500px] border overflow-hidden rounded-lg"
39
- loader={<div className="p-4">Loading...</div>}
35
+ style={{ height: 600 }}
36
+ loader={<p role="status">Loading PDF…</p>}
40
37
  >
41
- <Pages className="p-4">
38
+ <Pages>
42
39
  <Page>
43
40
  <CanvasLayer />
44
41
  <TextLayer />
@@ -49,68 +46,48 @@ export default function PDFViewer() {
49
46
  }
50
47
  ```
51
48
 
52
- ## Local Development using PNPM and Yalc
53
-
54
- When you are using "pnpm link", you are bound to use pnpm on your consumer project when you are developing locally.
55
- With yalc, we are decoupling the need for pnpm and now the package can be tested with any package managers. Any
56
- changes should be automatically published to yalc on save, forcing a rebuilt and updating the consumer project.
57
-
58
- Install yalc globally:
59
-
60
- ```
61
- pnpm i yalc -g
62
- ```
63
-
64
- From lector:
49
+ For **Next.js**, load the viewer through a Client Component using `dynamic(..., { ssr: false })`; keep PDF.js setup inside the dynamically loaded module. See [installation](https://lector-weld.vercel.app/docs/installation) for the full wrapper, Vite worker setup, and deployments below a path prefix.
65
50
 
66
- ```bash
67
- # navigate to lector package folder and install dependencies
68
- pnpm i
69
- # when you first start development, make sure you publish the package locally
70
- yalc publish
71
- # and run the project in development mode to start a watcher that rebuilds the project and pushes the changes locally on save
72
- pnpm dev
73
- ```
51
+ `Root` loads the document and provides its state. `Pages` owns scrolling and clones one `Page` template for visible pages. `CanvasLayer` paints the PDF; `TextLayer` adds selectable text. Give the viewer a definite height and import the PDF.js stylesheet so its layers align.
74
52
 
75
- From consumer project:
76
- (It doesn't really matter what package manager you are using)
53
+ ## Build the reader your app needs
77
54
 
78
- ```bash
79
- # add local package to your package.json of the consumer project using yalc
80
- yalc add @anaralabs/lector
81
- # or if you don't want to add the yalc package in your package.json
82
- yalc link @anaralabs/lector
83
- ```
55
+ | Feature | Start here |
56
+ | --- | --- |
57
+ | Toolbar, page input, and layout | [Your first viewer](https://lector-weld.vercel.app/docs/basic-usage) |
58
+ | Authenticated URLs, local files, errors, and self-hosted assets | [Loading documents](https://lector-weld.vercel.app/docs/document-loading) |
59
+ | Page navigation and fit width | [Navigation](https://lector-weld.vercel.app/docs/code/page-navigation), [zoom](https://lector-weld.vercel.app/docs/code/zoom-control) |
60
+ | Page previews | [Thumbnails](https://lector-weld.vercel.app/docs/code/thumbnails) |
61
+ | Text search and highlighted results | [Search](https://lector-weld.vercel.app/docs/code/search) |
62
+ | Selection and citation regions | [Selection](https://lector-weld.vercel.app/docs/code/select), [highlights](https://lector-weld.vercel.app/docs/code/highlight) |
63
+ | PDF links and editable form fields | [Links](https://lector-weld.vercel.app/docs/code/links), [forms](https://lector-weld.vercel.app/docs/code/pdf-form) |
64
+ | Dark page rendering | [Dark mode](https://lector-weld.vercel.app/docs/dark-mode) |
65
+ | Props, hooks, and defaults | [API reference](https://lector-weld.vercel.app/docs/api) |
84
66
 
85
- ## Features
67
+ Lector is a toolkit rather than a finished toolbar or a PDF editor. Your app supplies accessible controls, error UI, and storage for user annotations. Search requires embedded text; it does not perform OCR. Custom highlight overlays do not automatically modify the PDF file. See [troubleshooting](https://lector-weld.vercel.app/docs/troubleshooting) for worker errors, blank pages, and layout issues.
86
68
 
87
- - 📱 Responsive and mobile-friendly
88
- - 🎨 Fully customizable UI components
89
- - 🔍 Text selection and search functionality
90
- - 📑 Page thumbnails and outline navigation
91
- - 🌗 First-class dark mode support
92
- - 🖱️ Pan and zoom controls
93
- - 📝 Form filling support
94
- - 🔗 Internal and external link handling
69
+ ## Use with a coding assistant
95
70
 
96
- ## Contributing
71
+ Connect an MCP client to `https://lector-weld.vercel.app/mcp` using Streamable HTTP, with no API key. It can search and read every guide through tools and resources. See [AI agents and MCP](https://lector-weld.vercel.app/docs/agents) for setup and example requests.
97
72
 
98
- We welcome contributions! Key areas we're focusing on:
73
+ For direct fetching, start with [llms.txt](https://lector-weld.vercel.app/llms.txt), read [individual Markdown guides](https://lector-weld.vercel.app/docs/installation.md), or use the [complete documentation](https://lector-weld.vercel.app/llms-full.txt). These exports are generated from the same source as the website.
99
74
 
100
- 1. Performance optimizations
101
- 2. Accessibility improvements
102
- 3. Mobile/touch interactions
103
- 4. Documentation and examples
75
+ ## Work on Lector
104
76
 
77
+ From the repository root, with Node.js 22.13+ and pnpm 9.5.0:
105
78
 
79
+ ```bash
80
+ pnpm install --frozen-lockfile
81
+ pnpm --filter @anaralabs/lector build
82
+ pnpm --filter docs dev
83
+ ```
106
84
 
107
- ## Thanks
85
+ Open [localhost:3000/docs](http://localhost:3000/docs). The docs app uses the local workspace package. For watch mode, validation commands, and testing in another application, read [CONTRIBUTING.md](https://github.com/anaralabs/lector/blob/main/CONTRIBUTING.md).
108
86
 
109
- Special thanks to these open-source projects that provided inspiration:
87
+ ## Acknowledgements
110
88
 
111
- - [react-pdf-headless](https://github.com/jkgenser/react-pdf-headless)
112
- - [pdfreader](https://github.com/OnedocLabs/pdfreader)
89
+ Inspired by [react-pdf-headless](https://github.com/jkgenser/react-pdf-headless) and [pdfreader](https://github.com/OnedocLabs/pdfreader). Built on [PDF.js](https://mozilla.github.io/pdf.js/) and [React](https://react.dev/).
113
90
 
114
91
  ## License
115
92
 
116
- MIT © [Anara](https://anara.com)
93
+ [MIT](https://github.com/anaralabs/lector/blob/main/LICENSE) © [Anara](https://anara.com)
@@ -6,6 +6,7 @@ import { FunctionComponent } from 'react';
6
6
  import { HTMLProps } from 'react';
7
7
  import { JSX } from 'react/jsx-runtime';
8
8
  import { JSX as JSX_2 } from 'react';
9
+ import { JSXElementConstructor } from 'react';
9
10
  import { MemoExoticComponent } from 'react';
10
11
  import type { PageViewport } from 'pdfjs-dist';
11
12
  import type { PDFDocumentProxy } from 'pdfjs-dist';
@@ -14,6 +15,7 @@ import type { PDFPageProxy } from 'pdfjs-dist';
14
15
  import { default as React_2 } from 'react';
15
16
  import { ReactElement } from 'react';
16
17
  import { ReactNode } from 'react';
18
+ import { ReactPortal } from 'react';
17
19
  import { RefAttributes } from 'react';
18
20
  import { StoreApi } from 'zustand';
19
21
  import type { TypedArray } from 'pdfjs-dist/types/src/display/api';
@@ -21,6 +23,12 @@ import { useFloating } from '@floating-ui/react';
21
23
  import { useInteractions } from '@floating-ui/react';
22
24
  import type { Virtualizer } from '@tanstack/react-virtual';
23
25
 
26
+ /** Shared indexing with bounded PDF.js work; release when the consumer leaves. */
27
+ export declare function acquireDocumentText(pages: PDFPageProxy[]): {
28
+ promise: Promise<PageText[]>;
29
+ release(): void;
30
+ };
31
+
24
32
  declare interface Annotation {
25
33
  id: string;
26
34
  pageNumber: number;
@@ -153,6 +161,15 @@ declare interface AnnotationTooltipProps {
153
161
  */
154
162
  export declare function applyContextRecolor(ctx: CanvasRenderingContext2D, map: RenderColorMap, options?: RecolorContextOptions): (finalizeRender?: boolean) => void;
155
163
 
164
+ declare interface AsyncSearchOptions extends SearchOptions {
165
+ /** Cancelling rejects with AbortError and never publishes partial results. */
166
+ signal?: AbortSignal;
167
+ /** Approximate work budget between yields; defaults to 8 ms. */
168
+ timeSliceMs?: number;
169
+ }
170
+ export { AsyncSearchOptions }
171
+ export { AsyncSearchOptions as AsyncSearchOptions_alias_1 }
172
+
156
173
  export declare function calculateHighlightRects(pageProxy: PDFPageProxy, textMatch: TextPosition): Promise<HighlightRect[]>;
157
174
 
158
175
  export declare const cancellable: <T extends Promise<unknown>>(promise: T) => {
@@ -208,6 +225,9 @@ export declare function computeBaseScale(dpr: number, zoom: number, pageWidth: n
208
225
 
209
226
  export declare function computeTargetScale(dpr: number, zoom: number): number;
210
227
 
228
+ /** Reuses two rows across candidate windows and only visits the edit band. */
229
+ export declare function createBoundedDistance(query: string, maxDistance: number): (text: string, offset: number) => number;
230
+
211
231
  /**
212
232
  * Builds a memoized color map that flips perceived lightness onto the
213
233
  * background<->foreground ramp while preserving hue and chroma (OKLab).
@@ -247,6 +267,13 @@ export declare function createRecolorCanvasFactory(mapRef: RenderColorMapRef): {
247
267
  };
248
268
  };
249
269
 
270
+ /** Paint native selection once per page; overlapping PDF spans must not
271
+ * composite their translucent backgrounds on top of each other. */
272
+ export declare const createSelectionBackground: () => {
273
+ update: (selection: Selection | null, layers: Iterable<HTMLElement>) => void;
274
+ clear: (layer: HTMLElement) => void;
275
+ };
276
+
250
277
  export declare const CurrentPage: ({ ...props }: HTMLProps<HTMLInputElement>) => JSX.Element;
251
278
 
252
279
  export declare const CurrentZoom: ({ ...props }: HTMLProps<HTMLInputElement>) => JSX.Element;
@@ -285,6 +312,13 @@ export declare const getFitWidthZoom: (containerWidth: number, viewports: PageVi
285
312
 
286
313
  export declare const getMidHeightOfHighlightLine: (selection: ColoredHighlight) => number;
287
314
 
315
+ /**
316
+ * Per-text-node client rects for a selection. Avoids the block-level rects
317
+ * that `range.getClientRects()` returns for `.textLayer` / page wrappers,
318
+ * which would render as full-page highlights on multi-page selections.
319
+ */
320
+ export declare const getTextNodeClientRects: (range: Range) => TextNodeRect[];
321
+
288
322
  export declare const HighlightLayer: ForwardRefExoticComponent<HighlightLayerProps & RefAttributes<HTMLDivElement>>;
289
323
 
290
324
  declare interface HighlightLayerProps extends ComponentPropsWithoutRef<"div"> {
@@ -357,10 +391,15 @@ export declare class LinkService {
357
391
 
358
392
  export declare const loadPdfJs: () => Promise<pdfjsDist>;
359
393
 
394
+ /** Ordered results with bounded in-flight work and cooperative cancellation. */
395
+ export declare function mapConcurrent<T, R>(items: readonly T[], concurrency: number, map: (item: T, index: number) => Promise<R>, isCancelled?: () => boolean): Promise<R[]>;
396
+
360
397
  export declare const MAX_CANVAS_DIMENSION = 32767;
361
398
 
362
399
  export declare const MAX_CANVAS_PIXELS = 16777216;
363
400
 
401
+ export declare const mergeTextRuns: <T extends Rect>(rects: T[]) => T[];
402
+
364
403
  export declare const NextPage: () => void;
365
404
 
366
405
  export declare const Outline: ({ children, ...props }: HTMLProps<HTMLUListElement> & {
@@ -399,6 +438,11 @@ export declare const Pages: ({ children, gap, virtualizerOptions, initialOffset,
399
438
  onOffsetChange?: (offset: number) => void;
400
439
  }) => JSX.Element;
401
440
 
441
+ declare type PageText = {
442
+ pageNumber: number;
443
+ text: string;
444
+ };
445
+
402
446
  export declare interface PdfJsAssetUrls {
403
447
  wasmUrl: string;
404
448
  cMapUrl: string;
@@ -474,6 +518,13 @@ export declare interface RecolorContextOptions {
474
518
  pageArea?: number;
475
519
  }
476
520
 
521
+ declare type Rect = {
522
+ left: number;
523
+ top: number;
524
+ width: number;
525
+ height: number;
526
+ };
527
+
477
528
  /**
478
529
  * Removes any recolor wrapper installed on the context. For long-lived
479
530
  * contexts (the visible canvas in the no-buffer fallback, thumbnails) that
@@ -553,17 +604,33 @@ export declare interface ScanPaperClass {
553
604
  inked: boolean;
554
605
  }
555
606
 
556
- export declare const Search: ({ children, loading }: SearchProps) => ReactNode;
607
+ export declare const Search: ({ children, loading, errorFallback, }: SearchProps) => string | number | bigint | boolean | Iterable<ReactNode> | Promise<string | number | bigint | boolean | ReactPortal | ReactElement<unknown, string | JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | JSX.Element | null | undefined;
608
+
609
+ export declare function searchDocument(pages: SearchPage[], searchText: string, options?: SearchOptions): SearchResults;
610
+
611
+ export declare function searchDocumentAsync(pages: SearchPage[], searchText: string, options?: AsyncSearchOptions): Promise<SearchResults>;
557
612
 
558
613
  declare interface SearchOptions {
559
614
  threshold?: number;
560
615
  limit?: number;
561
616
  textSize?: number;
562
617
  }
618
+ export { SearchOptions }
619
+ export { SearchOptions as SearchOptions_alias_1 }
620
+
621
+ declare type SearchPage = {
622
+ pageNumber: number;
623
+ text: string;
624
+ };
563
625
 
564
626
  declare interface SearchProps {
565
627
  children: React.ReactNode;
566
628
  loading?: React.ReactNode;
629
+ /** Replace the default indexing-error message and retry button. */
630
+ errorFallback?: (state: {
631
+ error: unknown;
632
+ retry: () => void;
633
+ }) => React.ReactNode;
567
634
  }
568
635
 
569
636
  export declare interface SearchResult {
@@ -610,6 +677,11 @@ declare type TextContent = {
610
677
 
611
678
  export declare const TextLayer: MemoExoticComponent<({ className, style, ...props }: HTMLProps<HTMLDivElement>) => JSX.Element>;
612
679
 
680
+ declare type TextNodeRect = {
681
+ rect: DOMRect;
682
+ element: Element | null;
683
+ };
684
+
613
685
  declare interface TextPosition {
614
686
  pageNumber: number;
615
687
  text: string;
@@ -617,19 +689,32 @@ declare interface TextPosition {
617
689
  searchText?: string;
618
690
  }
619
691
 
620
- export declare const Thumbnail: ({ pageNumber, ...props }: HTMLProps<HTMLCanvasElement> & {
692
+ export declare const Thumbnail: ({ pageNumber, eager, ...props }: HTMLProps<HTMLCanvasElement> & {
621
693
  pageNumber?: number;
694
+ eager?: boolean;
622
695
  }) => JSX.Element;
623
696
 
624
- export declare const Thumbnails: ({ children, ...props }: HTMLProps<HTMLDivElement> & {
625
- children: ReactElement<typeof Thumbnail>;
626
- }) => JSX.Element;
697
+ export declare const Thumbnails: ({ children, virtualize, ...props }: ThumbnailsProps) => JSX.Element;
698
+
699
+ declare type ThumbnailsProps = HTMLProps<HTMLDivElement> & {
700
+ children: ReactElement;
701
+ /** Opt in to a bounded list. Give the container a height and each row content that fits itemHeight. */
702
+ virtualize?: ThumbnailVirtualization;
703
+ };
704
+
705
+ declare interface ThumbnailVirtualization {
706
+ /** Fixed row height in CSS pixels, including any desired space between items. */
707
+ itemHeight: number;
708
+ overscan?: number;
709
+ }
627
710
 
628
711
  export declare const TotalPages: ({ ...props }: HTMLProps<HTMLDivElement>) => JSX.Element;
629
712
 
630
713
  export declare const USE_LAYOUT_ZOOM: boolean;
631
714
 
632
- declare const useAnnotations: () => AnnotationState;
715
+ declare function useAnnotations<T>(selector: (state: AnnotationState) => T): T;
716
+
717
+ declare function useAnnotations(): AnnotationState;
633
718
  export { useAnnotations }
634
719
  export { useAnnotations as useAnnotations_alias_1 }
635
720
 
@@ -653,6 +738,10 @@ declare interface UseAnnotationTooltipReturn {
653
738
 
654
739
  export declare const useCreatePDFLinkService: (pdfDocumentProxy: PDFDocumentProxy | null) => LinkService;
655
740
 
741
+ declare function usePageAnnotations(pageNumber: number): Annotation[];
742
+ export { usePageAnnotations }
743
+ export { usePageAnnotations as usePageAnnotations_alias_1 }
744
+
656
745
  declare const usePageRendered: (pageNumber: number) => boolean;
657
746
  export { usePageRendered }
658
747
  export { usePageRendered as usePageRendered_alias_1 }
@@ -697,7 +786,7 @@ export declare interface usePDFDocumentParams {
697
786
  /**
698
787
  * Override or extend the PDF.js DocumentInitParameters passed to getDocument().
699
788
  * These take highest precedence over both the source object and lector's defaults.
700
- * Must be a stable reference (module-level constant or useMemo) to avoid reloading the document.
789
+ * Read when source changes; changing options alone does not reload the document.
701
790
  */
702
791
  documentOptions?: Partial<DocumentInitParameters>;
703
792
  /**
@@ -750,6 +839,9 @@ export declare const useSearch: () => {
750
839
  keywords: string[];
751
840
  searchResults: SearchResults;
752
841
  search: (searchText: string, options?: SearchOptions) => SearchResults;
842
+ searchAsync: (searchText: string, options?: AsyncSearchOptions) => Promise<SearchResults>;
843
+ cancelSearch: () => void;
844
+ isSearching: boolean;
753
845
  };
754
846
 
755
847
  export declare const useSelectionDimensions: () => {
package/dist/index.d.ts CHANGED
@@ -32,6 +32,7 @@ export { calculateHighlightRects } from './_tsup-dts-rollup.js';
32
32
  export { Annotation } from './_tsup-dts-rollup.js';
33
33
  export { AnnotationsStoreProvider } from './_tsup-dts-rollup.js';
34
34
  export { useAnnotations } from './_tsup-dts-rollup.js';
35
+ export { usePageAnnotations } from './_tsup-dts-rollup.js';
35
36
  export { usePageRendered } from './_tsup-dts-rollup.js';
36
37
  export { LinkService } from './_tsup-dts-rollup.js';
37
38
  export { PDFLinkServiceContext } from './_tsup-dts-rollup.js';
@@ -47,3 +48,5 @@ export { createDarkModeColorMap } from './_tsup-dts-rollup.js';
47
48
  export { DarkModeColors } from './_tsup-dts-rollup.js';
48
49
  export { DEFAULT_DARK_MODE_COLORS } from './_tsup-dts-rollup.js';
49
50
  export { RenderColorMap } from './_tsup-dts-rollup.js';
51
+ export { AsyncSearchOptions } from './_tsup-dts-rollup.js';
52
+ export { SearchOptions } from './_tsup-dts-rollup.js';