teamsync-pdf-viewer 2.2.2 → 2.2.3

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/CHANGELOG.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  All notable changes to `teamsync-pdf-viewer` are documented here.
4
4
 
5
+ ## [2.2.3] — 2026-11-08
6
+
7
+ ### Performance & Loading Optimizations
8
+ - **Lazy Page-Dimension Scanning**:
9
+ - Deferred scanning page dimensions for pages 2 through $N$ until after Page 1 has completed its first canvas paint (`onFirstPageRendered`).
10
+ - Replaced eager 25-page parallel Promise batches with background idle slices (6 pages per slice via `requestIdleCallback`) to eliminate network contention on document open.
11
+ - Applied slice-level scroll offset compensation (`delta = newTop - oldTop`) in continuous mode to prevent layout jumps when non-uniform pages are measured.
12
+ - **Viewport-Gated Text & Link Extraction**:
13
+ - `fetchText()` and `fetchNativeLinks()` now run only for pages currently in view (`isInViewport`) or that have previously rendered.
14
+ - Off-screen virtualization buffer rows no longer issue concurrent network requests for text content, fonts, and annotation dictionaries on mount.
15
+ - Extracted text and links are cached in component state, eliminating redundant network reads when scrolling back into view.
16
+ - **Shared PDF.js Worker Lifecycle & Pre-Warming**:
17
+ - Added support for passing an explicit `worker?: pdfjsLib.PDFWorker` instance to `<TeamSyncViewer>`, `<DocumentViewer>`, and `createWebViewer()`.
18
+ - Added `createPdfWorker()`, `getSharedPdfWorker()`, and `destroySharedPdfWorker()` helpers in `src/core/pdfAssets.ts` for host idle warm-up.
19
+ - Reused a persistent shared worker singleton when none is passed, eliminating the 1.2 MB worker script re-evaluation overhead on every document load, retry, or split pane.
20
+ - Added self-healing recovery that automatically instantiates a fresh worker if `worker.destroyed` is detected.
21
+ - **Single-Pass Initial Fit Scale**:
22
+ - Pre-calculates target `fit-width` and `fit-page` scale as soon as Page 1 dimensions and container width are retrieved in `loadPdf`.
23
+ - Page 1 paints directly at the fitted scale on its initial canvas render, eliminating double-rendering and scale flicker.
24
+ - **Optimized HTTP/2 Network Defaults**:
25
+ - Defaulted `disableStream: true` in `resolveNetworkParams()`, aborting full-file HTTP 200 download streams after headers to prioritize HTTP 206 range reads for Page 1.
26
+ - Defaulted `rangeChunkSize: 524288` (512 KiB) to minimize round-trip overhead on high-latency links. Explicit host props continue to override defaults.
27
+ - **Load Telemetry Reporting**:
28
+ - Added `LoadProgressInfo` interface reporting `{ bytesLoaded, totalBytes, rangeRequests }`.
29
+ - Hooked `loadingTask.onProgress` to emit progress telemetry through `onLoadProgress` and the event bus (`loadProgress`).
30
+ - Included telemetry payload in `onFirstPageRendered` and `firstPageRendered` event to measure first-page render performance.
31
+
5
32
  ## [2.2.2] — 2026-11-05
6
33
 
7
34
  ### Added & Enhanced
@@ -1,7 +1,7 @@
1
1
  import { default as React } from 'react';
2
2
  import { Annotation } from '../annotations/types';
3
3
  import { AnnotationManager } from '../annotations/AnnotationManager';
4
- import { Redaction, WatermarkOptions, SDKPermissions, PdfAssetPaths, TransientHighlight, InitialScale, LinkClickEvent, WebViewerInstance } from '../core/types';
4
+ import { Redaction, WatermarkOptions, SDKPermissions, PdfAssetPaths, TransientHighlight, InitialScale, LinkClickEvent, WebViewerInstance, LoadProgressInfo } from '../core/types';
5
5
  import { FormManager } from '../forms/FormManager';
6
6
  import * as pdfjsLib from 'pdfjs-dist';
7
7
  export interface DocumentLoadErrorInfo {
@@ -23,14 +23,16 @@ interface DocumentViewerProps {
23
23
  httpHeaders?: Record<string, string>;
24
24
  /** Alias for `httpHeaders` providing an alternative headers option. */
25
25
  customHeaders?: Record<string, string>;
26
- /** Maximum number of bytes fetched per HTTP 206 range request. Default is 65536. */
26
+ /** Maximum number of bytes fetched per HTTP 206 range request. Default is 524288 (512 KB). */
27
27
  rangeChunkSize?: number;
28
- /** Disable chunked streaming of PDF file data. Default false. */
28
+ /** Disable chunked streaming of PDF file data. Default true. */
29
29
  disableStream?: boolean;
30
30
  /** Disable automatic background pre-fetching of remaining PDF file chunks. Default false. */
31
31
  disableAutoFetch?: boolean;
32
32
  /** Disable HTTP 206 range requests entirely, forcing full PDF download. Default false. */
33
33
  disableRange?: boolean;
34
+ /** Optional dedicated or pre-warmed pdf.js worker instance. Defaults to shared worker singleton. */
35
+ worker?: pdfjsLib.PDFWorker;
34
36
  /** Password for password-protected PDFs. */
35
37
  password?: string;
36
38
  /** pdf.js asset locations, applied right before the document is opened. */
@@ -51,7 +53,9 @@ interface DocumentViewerProps {
51
53
  onDocumentLoaded?: (doc: pdfjsLib.PDFDocumentProxy, url: string) => void;
52
54
  onLoadError?: (error: Error, info: DocumentLoadErrorInfo) => void;
53
55
  /** Fires once per loaded document, after the first page canvas has been painted. */
54
- onFirstPageRendered?: (pageNumber: number) => void;
56
+ onFirstPageRendered?: (pageNumber: number, telemetry?: LoadProgressInfo) => void;
57
+ /** Telemetry callback reporting download progress and range requests. */
58
+ onLoadProgress?: (info: LoadProgressInfo) => void;
55
59
  /** Fires whenever any page canvas has finished rendering. */
56
60
  onPageRendered?: (pageNumber: number) => void;
57
61
  onPageChange?: (pageNumber: number, numPages: number) => void;
@@ -92,5 +96,5 @@ interface DocumentViewerProps {
92
96
  onDownloadFilledPdf?: () => void;
93
97
  highlightedRoleId?: string | null;
94
98
  }
95
- export default function DocumentViewer({ leftSidebarOpen, rightSidebarOpen, activeTab, annotationManager, initialDoc, loadNonce, withCredentials, httpHeaders, customHeaders, rangeChunkSize, disableStream, disableAutoFetch, disableRange, password, assets, sidebars, redactions, regexRedactions, scale, setScale, sidebarTab, setSidebarTab, onAnnotationsChange, onRedactionsChange, onRedactionsApplied, onDocumentLoaded, onLoadError, onFirstPageRendered, onPageRendered, onPageChange, pageTransition, pageLayout, rotation, setRotation, watermark, watermarkText, enableAnnotations: _enableAnnotations, initialPage, page, transientHighlights, permissions, hideAnnotationsUntilPageRendered, enableTextSelection, defaultTool, showSelectionTooltip, initialScale, initialWidthRatio, responsive, instance, targetViewer, resolveLinkUrl, onLinkClick, formManager, setActiveTab, onDownloadFilledPdf, highlightedRoleId, }: DocumentViewerProps): React.JSX.Element;
99
+ export default function DocumentViewer({ leftSidebarOpen, rightSidebarOpen, activeTab, annotationManager, initialDoc, loadNonce, withCredentials, httpHeaders, customHeaders, rangeChunkSize, disableStream, disableAutoFetch, disableRange, password, assets, sidebars, redactions, regexRedactions, scale, setScale, sidebarTab, setSidebarTab, onAnnotationsChange, onRedactionsChange, onRedactionsApplied, onDocumentLoaded, onLoadError, onFirstPageRendered, onLoadProgress, onPageRendered, onPageChange, pageTransition, pageLayout, rotation, setRotation, watermark, watermarkText, enableAnnotations: _enableAnnotations, initialPage, page, transientHighlights, permissions, hideAnnotationsUntilPageRendered, enableTextSelection, defaultTool, showSelectionTooltip, initialScale, initialWidthRatio, responsive, instance, targetViewer, resolveLinkUrl, onLinkClick, formManager, setActiveTab, onDownloadFilledPdf, highlightedRoleId, worker, }: DocumentViewerProps): React.JSX.Element;
96
100
  export {};
@@ -50,7 +50,8 @@ interface PageRendererProps {
50
50
  showFlowOrder?: boolean;
51
51
  selectedRoleFilter?: string | null;
52
52
  highlightedRoleId?: string | null;
53
+ isInViewport?: boolean;
53
54
  }
54
- declare function PageRendererComponent({ pageNum, pdfDoc, scale, rotation, containerWidth: _containerWidth, containerHeight: _containerHeight, scrollTop: _scrollTop, scrollLeft: _scrollLeft, pageTop, pageLeft, basePageWidth, basePageHeight, activeTab, activeTool, annotations, selectedAnnotationId, activeSearchResult, transientHighlights, onMouseDown, onMouseMove, onMouseUp, onAnnotationClick, onAnnotationDoubleClick, onStartResize, onStartMove, onAnnotationMouseEnter, onClearSelection, watermark, watermarkText, redactions, onDiscardRedaction, onRendered, hideAnnotationsUntilPageRendered, onLinkClick, formManager, activeFormTool, setActiveFormTool, canFillForms, showFlowOrder, selectedRoleFilter, highlightedRoleId }: PageRendererProps): React.JSX.Element;
55
+ declare function PageRendererComponent({ pageNum, pdfDoc, scale, rotation, containerWidth: _containerWidth, containerHeight: _containerHeight, scrollTop: _scrollTop, scrollLeft: _scrollLeft, pageTop, pageLeft, basePageWidth, basePageHeight, activeTab, activeTool, annotations, selectedAnnotationId, activeSearchResult, transientHighlights, onMouseDown, onMouseMove, onMouseUp, onAnnotationClick, onAnnotationDoubleClick, onStartResize, onStartMove, onAnnotationMouseEnter, onClearSelection, watermark, watermarkText, redactions, onDiscardRedaction, onRendered, hideAnnotationsUntilPageRendered, onLinkClick, formManager, activeFormTool, setActiveFormTool, canFillForms, showFlowOrder, selectedRoleFilter, highlightedRoleId, isInViewport: propIsInViewport }: PageRendererProps): React.JSX.Element;
55
56
  declare const PageRenderer: React.MemoExoticComponent<typeof PageRendererComponent>;
56
57
  export default PageRenderer;
@@ -2,7 +2,7 @@ import { default as React } from 'react';
2
2
  import { DocumentLoadErrorInfo } from './DocumentViewer';
3
3
  import { WebViewerInstance } from '../core/ViewerInstance';
4
4
  import { FormField, FormDataRecord } from '../forms/types';
5
- import { WebViewerOptions, Redaction, TransientHighlight, LinkClickEvent } from '../core/types';
5
+ import { WebViewerOptions, Redaction, TransientHighlight, LinkClickEvent, LoadProgressInfo } from '../core/types';
6
6
  import { Annotation } from '../annotations/types';
7
7
  import { OfficeConverterConfig, OfficeFileType } from '../office';
8
8
  export interface TeamSyncViewerProps extends Omit<WebViewerOptions, 'path'> {
@@ -19,11 +19,16 @@ export interface TeamSyncViewerProps extends Omit<WebViewerOptions, 'path'> {
19
19
  * the previous one was a presigned link that expired.
20
20
  */
21
21
  onDocumentLoadError?: (error: Error, retry: (newUrl?: string) => void, info: DocumentLoadErrorInfo) => void;
22
- /** Fires once per document, after the first page canvas has been painted. */
22
+ /** Fires once per document, after the first page canvas has been painted. Includes load telemetry. */
23
23
  onFirstPageRendered?: (info: {
24
24
  url: string;
25
25
  pageNumber: number;
26
+ bytesLoaded?: number;
27
+ totalBytes?: number;
28
+ rangeRequests?: number;
26
29
  }) => void;
30
+ /** Telemetry callback reporting download progress and range requests. */
31
+ onLoadProgress?: (info: LoadProgressInfo) => void;
27
32
  /** Fires whenever any page canvas has finished rendering. */
28
33
  onPageRendered?: (info: {
29
34
  url: string;
@@ -47,6 +47,10 @@ export interface LoadDocumentOptions {
47
47
  withCredentials?: boolean;
48
48
  /** Password for password-protected PDFs. */
49
49
  password?: string;
50
+ /**
51
+ * Optional dedicated or pre-warmed pdf.js PDFWorker instance.
52
+ */
53
+ worker?: pdfjsLib.PDFWorker;
50
54
  }
51
55
  /** Callbacks the React component installs so the instance can reach live state. */
52
56
  export interface ViewerBinding {
@@ -1,4 +1,25 @@
1
1
  import { DocumentInitParameters } from 'pdfjs-dist/types/src/display/api';
2
+ /**
3
+ * © 2026 AngelBot Ai Pvt Ltd. All rights reserved.
4
+ * pdf.js asset configuration.
5
+ *
6
+ * The library never bundles or inlines the pdf.js worker (that produced a 5 MB bundle with a
7
+ * `data:` URL worker, which strict Content-Security-Policies reject). Hosts serve the worker,
8
+ * CMaps, standard fonts and wasm decoders from their own origin and tell us where they live.
9
+ *
10
+ * Next.js example (copy from node_modules/pdfjs-dist into public/pdfjs/):
11
+ * configurePdfAssets({
12
+ * workerSrc: '/pdfjs/pdf.worker.min.mjs',
13
+ * cMapUrl: '/pdfjs/cmaps/',
14
+ * standardFontDataUrl: '/pdfjs/standard_fonts/',
15
+ * wasmUrl: '/pdfjs/wasm/',
16
+ * });
17
+ *
18
+ * Vite example:
19
+ * import workerSrc from 'pdfjs-dist/build/pdf.worker.min.mjs?url';
20
+ * configurePdfAssets({ workerSrc });
21
+ */
22
+ import * as pdfjsLib from 'pdfjs-dist';
2
23
  export interface PdfAssetPaths {
3
24
  /** URL of `pdf.worker.min.mjs` matching the installed pdfjs-dist version. REQUIRED. */
4
25
  workerSrc?: string;
@@ -25,3 +46,22 @@ export declare function assertWorkerConfigured(): void;
25
46
  export declare function getDocumentParams(): Partial<DocumentInitParameters>;
26
47
  /** pdfjs-dist version this build was compiled against (useful for worker-version mismatch checks). */
27
48
  export declare const pdfjsVersion: string;
49
+ export type PDFWorker = pdfjsLib.PDFWorker;
50
+ /**
51
+ * Creates a new dedicated pdf.js worker instance.
52
+ * Useful for host applications that pre-warm the worker thread during idle periods.
53
+ */
54
+ export declare function createPdfWorker(params?: {
55
+ name?: string;
56
+ port?: any;
57
+ verbosity?: number;
58
+ }): pdfjsLib.PDFWorker;
59
+ /**
60
+ * Returns a shared library-level pdf.js worker singleton.
61
+ * Self-healing: if the worker was destroyed or terminated, instantiates a fresh worker instance.
62
+ */
63
+ export declare function getSharedPdfWorker(): pdfjsLib.PDFWorker | undefined;
64
+ /**
65
+ * Destroys the shared library-level pdf.js worker singleton, releasing its worker thread.
66
+ */
67
+ export declare function destroySharedPdfWorker(): void;
@@ -7,6 +7,7 @@ import { WebViewerInstance } from './ViewerInstance';
7
7
  import { OfficeConverterConfig, OfficeFileType } from '../office/types';
8
8
  import { FormManager } from '../forms/FormManager';
9
9
  import { FormField, FormDataRecord, FormRole, FormFeatureOptions, FormFieldValueChangedEvent, FormSignatureAppliedEvent, FormSignatureClearedEvent, FormFieldAddedEvent, FormFieldRemovedEvent, FormFieldUpdatedEvent } from '../forms/types';
10
+ import type * as pdfjsLib from 'pdfjs-dist';
10
11
  export type { PdfAssetPaths, WebViewerInstance, OfficeConverterConfig, OfficeFileType, FormManager, FormField, FormDataRecord, FormRole, FormFeatureOptions, };
11
12
  export interface SearchBounds {
12
13
  x: number;
@@ -79,6 +80,12 @@ export type ToolMode = 'select' | 'pan' | 'rectangle' | 'ellipse' | 'line' | 'ar
79
80
  * Options accepted by `createWebViewer()` (and the deprecated `WebViewer()` alias).
80
81
  * Every option is also available as a prop on `<TeamSyncViewer>`.
81
82
  */
83
+ /** Loading telemetry reported during PDF document retrieval. */
84
+ export interface LoadProgressInfo {
85
+ bytesLoaded: number;
86
+ totalBytes: number;
87
+ rangeRequests?: number;
88
+ }
82
89
  export interface WebViewerOptions {
83
90
  /** @deprecated Only meaningful for the iframe package (`public/webviewer.js`). */
84
91
  path?: string;
@@ -157,12 +164,12 @@ export interface WebViewerOptions {
157
164
  customHeaders?: Record<string, string>;
158
165
  /**
159
166
  * Maximum number of bytes fetched per HTTP 206 range request.
160
- * Default is 65536 (= 64 KB).
167
+ * Default is 524288 (= 512 KB), optimizing round-trip latency over HTTP/2.
161
168
  */
162
169
  rangeChunkSize?: number;
163
170
  /**
164
171
  * Disable chunked streaming of PDF file data.
165
- * Default is false.
172
+ * Default is true, prioritizing initial page range requests over full-file download streams.
166
173
  */
167
174
  disableStream?: boolean;
168
175
  /**
@@ -177,6 +184,12 @@ export interface WebViewerOptions {
177
184
  * Default is false.
178
185
  */
179
186
  disableRange?: boolean;
187
+ /**
188
+ * Optional dedicated or pre-warmed pdf.js PDFWorker instance.
189
+ * If omitted, a persistent shared library-level worker singleton is used to avoid
190
+ * recreating a 1.2 MB worker thread on every document open or retry.
191
+ */
192
+ worker?: pdfjsLib.PDFWorker;
180
193
  /** Render the top toolbar. Default true. */
181
194
  toolbar?: boolean;
182
195
  /** Render the left (thumbnails) and right (comments/search) panels. Default true. */
@@ -269,7 +282,11 @@ export interface ViewerEventMap {
269
282
  firstPageRendered: {
270
283
  url: string;
271
284
  pageNumber: number;
285
+ bytesLoaded?: number;
286
+ totalBytes?: number;
287
+ rangeRequests?: number;
272
288
  };
289
+ loadProgress: LoadProgressInfo;
273
290
  pageRendered: {
274
291
  url: string;
275
292
  pageNumber: number;
@@ -32,6 +32,10 @@ export declare function clampScale(scale: number, minScale?: number, maxScale?:
32
32
  * optionally scaled by a width ratio (e.g. 0.7 for 70% of available width).
33
33
  */
34
34
  export declare function calculateFitWidthScale(availableWidth: number, pageWidth: number, ratio?: number, minScale?: number, maxScale?: number): number;
35
+ /**
36
+ * Calculates the target scale to fit a given page (both width and height) inside an available container viewport.
37
+ */
38
+ export declare function calculateFitPageScale(availableWidth: number, availableHeight: number, pageWidth: number, pageHeight: number, minScale?: number, maxScale?: number): number;
35
39
  /**
36
40
  * Calculates the safe rasterization scale for PDF.js viewport rendering so that
37
41
  * canvas dimensions and texture memory stay strictly within hardware limits.