@anaralabs/lector 3.14.9 → 3.14.11

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).
@@ -357,6 +377,9 @@ export declare class LinkService {
357
377
 
358
378
  export declare const loadPdfJs: () => Promise<pdfjsDist>;
359
379
 
380
+ /** Ordered results with bounded in-flight work and cooperative cancellation. */
381
+ export declare function mapConcurrent<T, R>(items: readonly T[], concurrency: number, map: (item: T, index: number) => Promise<R>, isCancelled?: () => boolean): Promise<R[]>;
382
+
360
383
  export declare const MAX_CANVAS_DIMENSION = 32767;
361
384
 
362
385
  export declare const MAX_CANVAS_PIXELS = 16777216;
@@ -399,6 +422,11 @@ export declare const Pages: ({ children, gap, virtualizerOptions, initialOffset,
399
422
  onOffsetChange?: (offset: number) => void;
400
423
  }) => JSX.Element;
401
424
 
425
+ declare type PageText = {
426
+ pageNumber: number;
427
+ text: string;
428
+ };
429
+
402
430
  export declare interface PdfJsAssetUrls {
403
431
  wasmUrl: string;
404
432
  cMapUrl: string;
@@ -553,17 +581,33 @@ export declare interface ScanPaperClass {
553
581
  inked: boolean;
554
582
  }
555
583
 
556
- export declare const Search: ({ children, loading }: SearchProps) => ReactNode;
584
+ 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;
585
+
586
+ export declare function searchDocument(pages: SearchPage[], searchText: string, options?: SearchOptions): SearchResults;
587
+
588
+ export declare function searchDocumentAsync(pages: SearchPage[], searchText: string, options?: AsyncSearchOptions): Promise<SearchResults>;
557
589
 
558
590
  declare interface SearchOptions {
559
591
  threshold?: number;
560
592
  limit?: number;
561
593
  textSize?: number;
562
594
  }
595
+ export { SearchOptions }
596
+ export { SearchOptions as SearchOptions_alias_1 }
597
+
598
+ declare type SearchPage = {
599
+ pageNumber: number;
600
+ text: string;
601
+ };
563
602
 
564
603
  declare interface SearchProps {
565
604
  children: React.ReactNode;
566
605
  loading?: React.ReactNode;
606
+ /** Replace the default indexing-error message and retry button. */
607
+ errorFallback?: (state: {
608
+ error: unknown;
609
+ retry: () => void;
610
+ }) => React.ReactNode;
567
611
  }
568
612
 
569
613
  export declare interface SearchResult {
@@ -617,19 +661,32 @@ declare interface TextPosition {
617
661
  searchText?: string;
618
662
  }
619
663
 
620
- export declare const Thumbnail: ({ pageNumber, ...props }: HTMLProps<HTMLCanvasElement> & {
664
+ export declare const Thumbnail: ({ pageNumber, eager, ...props }: HTMLProps<HTMLCanvasElement> & {
621
665
  pageNumber?: number;
666
+ eager?: boolean;
622
667
  }) => JSX.Element;
623
668
 
624
- export declare const Thumbnails: ({ children, ...props }: HTMLProps<HTMLDivElement> & {
625
- children: ReactElement<typeof Thumbnail>;
626
- }) => JSX.Element;
669
+ export declare const Thumbnails: ({ children, virtualize, ...props }: ThumbnailsProps) => JSX.Element;
670
+
671
+ declare type ThumbnailsProps = HTMLProps<HTMLDivElement> & {
672
+ children: ReactElement;
673
+ /** Opt in to a bounded list. Give the container a height and each row content that fits itemHeight. */
674
+ virtualize?: ThumbnailVirtualization;
675
+ };
676
+
677
+ declare interface ThumbnailVirtualization {
678
+ /** Fixed row height in CSS pixels, including any desired space between items. */
679
+ itemHeight: number;
680
+ overscan?: number;
681
+ }
627
682
 
628
683
  export declare const TotalPages: ({ ...props }: HTMLProps<HTMLDivElement>) => JSX.Element;
629
684
 
630
685
  export declare const USE_LAYOUT_ZOOM: boolean;
631
686
 
632
- declare const useAnnotations: () => AnnotationState;
687
+ declare function useAnnotations<T>(selector: (state: AnnotationState) => T): T;
688
+
689
+ declare function useAnnotations(): AnnotationState;
633
690
  export { useAnnotations }
634
691
  export { useAnnotations as useAnnotations_alias_1 }
635
692
 
@@ -653,6 +710,10 @@ declare interface UseAnnotationTooltipReturn {
653
710
 
654
711
  export declare const useCreatePDFLinkService: (pdfDocumentProxy: PDFDocumentProxy | null) => LinkService;
655
712
 
713
+ declare function usePageAnnotations(pageNumber: number): Annotation[];
714
+ export { usePageAnnotations }
715
+ export { usePageAnnotations as usePageAnnotations_alias_1 }
716
+
656
717
  declare const usePageRendered: (pageNumber: number) => boolean;
657
718
  export { usePageRendered }
658
719
  export { usePageRendered as usePageRendered_alias_1 }
@@ -697,7 +758,7 @@ export declare interface usePDFDocumentParams {
697
758
  /**
698
759
  * Override or extend the PDF.js DocumentInitParameters passed to getDocument().
699
760
  * 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.
761
+ * Read when source changes; changing options alone does not reload the document.
701
762
  */
702
763
  documentOptions?: Partial<DocumentInitParameters>;
703
764
  /**
@@ -750,6 +811,9 @@ export declare const useSearch: () => {
750
811
  keywords: string[];
751
812
  searchResults: SearchResults;
752
813
  search: (searchText: string, options?: SearchOptions) => SearchResults;
814
+ searchAsync: (searchText: string, options?: AsyncSearchOptions) => Promise<SearchResults>;
815
+ cancelSearch: () => void;
816
+ isSearching: boolean;
753
817
  };
754
818
 
755
819
  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';