teamsync-pdf-viewer 1.5.1 → 1.6.1

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,27 @@
2
2
 
3
3
  All notable changes to `teamsync-pdf-viewer` are documented here.
4
4
 
5
+ ## [1.6.1] — 2026-09-15
6
+
7
+ ### Added
8
+ - **`initialWidthRatio` Option**: Added `initialWidthRatio?: number` (with `widthRatio?: number` as an alias) to `<TeamSyncViewer>` and `WebViewerOptions`, allowing clients to target an exact fraction of the available viewing width on initial load (e.g. `0.7` for 70% width).
9
+ - **Responsive PDF Container Resizing**: Added automatic `ResizeObserver` tracking on the document container to refit the open PDF responsively at 60fps via `requestAnimationFrame` when the viewer page, container, or sidebars resize while in a width-ratio, fit-width, or fit-page mode.
10
+ - **`responsive` Prop**: Added `responsive?: boolean` (defaults to `true`) to `<TeamSyncViewer>` and `WebViewerOptions` for explicit control over auto-refitting on container resize.
11
+ - **Public `calculateFitWidthScale` Utility**: Exported `calculateFitWidthScale(availableWidth, pageWidth, ratio?, minScale?, maxScale?)` from the public package root.
12
+ - **Ratio Support in `fitWidth` & `fitToWidth`**: Updated `instance.UI.fitWidth(ratio?)`, `instance.UI.fitToWidth(ratio?)`, and added direct instance methods `instance.fitWidth(ratio?)`, `instance.fitToWidth(ratio?)`, `instance.fitPage()`, and `instance.fitToPage()`.
13
+
14
+ ## [1.6.0] — 2026-09-06
15
+
16
+ ### Added
17
+ - **Whitespace-Tolerant Text Search**: Search queries now match across line breaks, multiple whitespace runs, tabs, NBSP (`\u00A0`), and other unicode whitespace variants seamlessly. Matches in normalized space while accurately reporting raw source coordinates and bounds.
18
+ - **Shared `pageText` Builder & Bounding Box Interpolator**: Unified raw text extraction and character coordinate mapping between PDF search and regex redactions via internal `buildPageText` and `boundsForRawRange` helpers.
19
+ - **Page-Scoped Search & Highlighting**:
20
+ - Added additive 5th parameter `pages?: number[]` to `searchPdfText(doc, query, redactions, onProgress, pages)`.
21
+ - Added `options.pages?: number[]` to `instance.searchText(query, options)`.
22
+ - Added `scope?: 'document' | 'page'` to `instance.highlightSnippet(query, options)` allowing strict page restriction when navigating to snippet citations.
23
+ - **Sidebar Highlighting Precision**: Result `matchLength` now reflects the exact consumed raw glyph span, ensuring sidebar bold highlights match rendered page text identically.
24
+ - **Stable Search Results Sorting**: Corrected search result sorting to maintain page index ascending order while preserving intra-page ascending match order.
25
+
5
26
  ## [1.5.1] — 2026-09-06
6
27
 
7
28
  ### Added
package/README.md CHANGED
@@ -25,7 +25,7 @@ Try the **[Interactive Live Demo](https://pdfviewer.teamsync.com/)** or integrat
25
25
  Traditional commercial PDF viewers force developers into expensive per-domain licenses, opaque sales calls, and heavy server-side processing dependencies. **TeamSync PDF Viewer** is built from the ground up to solve these pain points.
26
26
 
27
27
  - 💰 **Zero-Cost, Transparent Licensing**: Uncapped usage, no per-server fees, and no commercial paywalls. 100% free and open-source under CPAL 1.0.
28
- - ⚡ **100% Client-Side WebAssembly**: Documents never leave the browser sandbox. Process, redact, and render PDFs entirely on the client.
28
+ - ⚡ **100% Client-Side PDF Processing**: Documents never leave the browser sandbox. Process, redact, and render PDFs entirely on the client.
29
29
  - 🔌 **Universal WebViewer API**: Clean, intuitive `WebViewer({...})` API interface that makes integrating into your web applications effortless.
30
30
  - 🎨 **Headless & Customizable**: Built for modern React 19 SPA lifecycles. No bloated iFrames to fight—customize toolbars, sidebars, context menus, and controls natively.
31
31
 
@@ -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 } from '../core/types';
4
+ import { Redaction, WatermarkOptions, SDKPermissions, PdfAssetPaths, TransientHighlight, InitialScale } from '../core/types';
5
5
  import * as pdfjsLib from 'pdfjs-dist';
6
6
  export interface DocumentLoadErrorInfo {
7
7
  url: string;
@@ -56,6 +56,9 @@ interface DocumentViewerProps {
56
56
  enableTextSelection?: boolean;
57
57
  defaultTool?: 'select' | 'pan';
58
58
  showSelectionTooltip?: boolean;
59
+ initialScale?: InitialScale;
60
+ initialWidthRatio?: number;
61
+ responsive?: boolean;
59
62
  }
60
- export default function DocumentViewer({ leftSidebarOpen, rightSidebarOpen, activeTab, annotationManager, initialDoc, loadNonce, withCredentials, 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 }: DocumentViewerProps): React.JSX.Element;
63
+ export default function DocumentViewer({ leftSidebarOpen, rightSidebarOpen, activeTab, annotationManager, initialDoc, loadNonce, withCredentials, 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, }: DocumentViewerProps): React.JSX.Element;
61
64
  export {};
@@ -68,10 +68,29 @@ export declare class WebViewerInstance {
68
68
  }): void;
69
69
  /** Set the current page number (1-based). Alias of goToPage. */
70
70
  setCurrentPage(pageNumber: number): void;
71
+ /**
72
+ * Scale the document to fit the container width.
73
+ *
74
+ * @param ratio - Optional width ratio fraction (e.g. `0.7` for 70% of available width).
75
+ * Defaults to `1.0` (100% full-width fit).
76
+ */
77
+ fitWidth(ratio?: number): void;
78
+ /** Alias of `fitWidth`. */
79
+ fitToWidth(ratio?: number): void;
80
+ /** Scale the document to fit the entire page within the container viewport. */
81
+ fitPage(): void;
82
+ /** Alias of `fitPage`. */
83
+ fitToPage(): void;
71
84
  /**
72
85
  * Search the document for text matches, returning snippets and page bounding boxes.
86
+ *
87
+ * @param query - Search term.
88
+ * @param options - Search options:
89
+ * - `pages`: Specific 1-based page numbers to search. Defaults to all pages.
73
90
  */
74
- searchText(query: string): Promise<SearchResult[]>;
91
+ searchText(query: string, options?: {
92
+ pages?: number[];
93
+ }): Promise<SearchResult[]>;
75
94
  /** Get the current list of transient highlights. */
76
95
  getTransientHighlights(): TransientHighlight[];
77
96
  /**
@@ -87,9 +106,21 @@ export declare class WebViewerInstance {
87
106
  /**
88
107
  * Search for a snippet or citation query, scroll to its page, and highlight it temporarily.
89
108
  * Returns the matched SearchResult or null if not found.
109
+ *
110
+ * @param query - The text snippet to locate and highlight.
111
+ * @param options - Navigation and display options:
112
+ * - `pageIndex`: Preferred page (1-based). When `scope: 'page'`, restricts search to this page.
113
+ * - `scope`: Search scope. `'document'` (default) searches all pages, keeping `pageIndex` as a preference.
114
+ * `'page'` restricts the search strictly to `pageIndex` (avoids full-document walk).
115
+ * If `scope: 'page'` is provided without `pageIndex`, it gracefully degrades to `'document'`.
116
+ * - `pulse`: Animate focus ring (default true).
117
+ * - `scrollTo`: Scroll page into view (default true).
118
+ * - `color`: Background highlight color.
119
+ * - `tooltip`: Tooltip text above the highlight (defaults to matched snippet).
90
120
  */
91
121
  highlightSnippet(query: string, options?: {
92
122
  pageIndex?: number;
123
+ scope?: 'document' | 'page';
93
124
  pulse?: boolean;
94
125
  scrollTo?: boolean;
95
126
  color?: string;
@@ -127,9 +158,9 @@ export declare class WebViewerInstance {
127
158
  setToolMode: (tool: string | null) => void;
128
159
  getSelectedText: () => string;
129
160
  copySelectedText: () => Promise<boolean>;
130
- fitWidth: () => void;
161
+ fitWidth: (ratio?: number) => void;
131
162
  fitPage: () => void;
132
- fitToWidth: () => void;
163
+ fitToWidth: (ratio?: number) => void;
133
164
  fitToPage: () => void;
134
165
  startCompare: (docA: any, docB: any, options?: any) => void;
135
166
  stopCompare: () => void;
@@ -53,7 +53,10 @@ export interface ViewerUser {
53
53
  id: string;
54
54
  name: string;
55
55
  }
56
- export type InitialScale = number | 'fit-width' | 'fit-page';
56
+ export type InitialScale = number | 'fit-width' | 'fit-page' | {
57
+ type: 'fit-width';
58
+ ratio?: number;
59
+ };
57
60
  export type ToolMode = 'select' | 'pan' | 'rectangle' | 'ellipse' | 'line' | 'arrow' | 'freehand' | 'highlight' | 'underline' | 'strikeout' | 'squiggly' | 'text' | 'eraser' | 'note' | 'callout' | 'signature' | 'digital_signature' | 'link' | 'redaction' | null;
58
61
  /**
59
62
  * Options accepted by `createWebViewer()` (and the deprecated `WebViewer()` alias).
@@ -67,6 +70,18 @@ export interface WebViewerOptions {
67
70
  /** Display name used for downloads. */
68
71
  fileName?: string;
69
72
  initialScale?: InitialScale;
73
+ /**
74
+ * Initial page width target as a ratio of the available viewing container width (e.g. `0.7` for 70%).
75
+ * When provided, the viewer scales the document to occupy this fraction of available width on initial render.
76
+ */
77
+ initialWidthRatio?: number;
78
+ /** Alias of `initialWidthRatio`. */
79
+ widthRatio?: number;
80
+ /**
81
+ * Automatically re-calculate scale when the viewer container or window resizes while in a fit or width-ratio mode.
82
+ * Default: true.
83
+ */
84
+ responsive?: boolean;
70
85
  /** 1-based page to scroll to after load. */
71
86
  initialPage?: number;
72
87
  /** Current 1-based page to display. Changes navigate to the given page. */
@@ -10,10 +10,10 @@ export interface SearchResult {
10
10
  bounds: SearchBounds[];
11
11
  }
12
12
  /**
13
- * Standalone asynchronous text search across all pages of a PDFDocumentProxy.
13
+ * Standalone asynchronous text search across all pages (or specified pages) of a PDFDocumentProxy.
14
14
  * Sanitizes redacted regions and returns matching snippets and bounding boxes.
15
15
  */
16
- export declare function searchPdfText(pdfDoc: pdfjsLib.PDFDocumentProxy, query: string, redactions?: Redaction[], onProgress?: (progress: number) => void): Promise<SearchResult[]>;
16
+ export declare function searchPdfText(pdfDoc: pdfjsLib.PDFDocumentProxy, query: string, redactions?: Redaction[], onProgress?: (progress: number) => void, pages?: number[]): Promise<SearchResult[]>;
17
17
  export declare function usePdfSearch(pdfDoc: pdfjsLib.PDFDocumentProxy | null, redactions?: Redaction[]): {
18
18
  search: (query: string) => Promise<void>;
19
19
  searchResults: SearchResult[];
@@ -33,7 +33,7 @@ export type { PrintOptions } from './core/print';
33
33
  export { usePdfSearch, searchPdfText } from './hooks/usePdfSearch';
34
34
  export type { SearchResult, SearchBounds } from './hooks/usePdfSearch';
35
35
  export type { TransientHighlight } from './core/types';
36
- export { MIN_SCALE, MAX_SCALE, clampScale, calculateNextZoomIn, calculateNextZoomOut, calculateScrollCompensation } from './utils/zoomUtils';
36
+ export { MIN_SCALE, MAX_SCALE, clampScale, calculateNextZoomIn, calculateNextZoomOut, calculateFitWidthScale, calculateScrollCompensation } from './utils/zoomUtils';
37
37
  export type { ScrollCompensationParams, ScrollPosition } from './utils/zoomUtils';
38
38
  export { estimatePageDimensions, computeRowLayout, DEFAULT_FALLBACK_DIMS, hasDimensionMismatch } from './utils/layoutUtils';
39
39
  export type { PageDimension, RowLayout } from './utils/layoutUtils';
@@ -0,0 +1,23 @@
1
+ import { SearchBounds } from '../core/types';
2
+ export interface PageText {
3
+ fullText: string;
4
+ itemIndices: number[];
5
+ charIndices: number[];
6
+ normText: string;
7
+ normToRaw: number[];
8
+ }
9
+ /**
10
+ * Collapses any consecutive whitespace runs to a single space and trims edges.
11
+ */
12
+ export declare function collapseWhitespace(s: string): string;
13
+ /**
14
+ * Builds raw page text with item/char index source-mapping, followed by a second pass
15
+ * creating normalized text (collapsing any maximal whitespace run to a single space)
16
+ * and a 1-to-1 index map back to raw string indices.
17
+ */
18
+ export declare function buildPageText(items: any[]): PageText;
19
+ /**
20
+ * Computes bounding boxes in PDF coordinate points for a matched range [rawStart, rawEndExcl) in fullText.
21
+ * Separator sentinels (-1) are skipped, and character positions are interpolated across each TextItem.
22
+ */
23
+ export declare function boundsForRawRange(items: any[], pageText: Pick<PageText, 'itemIndices' | 'charIndices'>, rawStart: number, rawEndExcl: number): SearchBounds[];
@@ -27,6 +27,11 @@ export declare function calculateNextZoomOut(currentScale: number, minScale?: nu
27
27
  * Clamps and rounds a requested zoom level within [minScale, maxScale].
28
28
  */
29
29
  export declare function clampScale(scale: number, minScale?: number, maxScale?: number): number;
30
+ /**
31
+ * Calculates the target scale to fit a given page width to an available container width,
32
+ * optionally scaled by a width ratio (e.g. 0.7 for 70% of available width).
33
+ */
34
+ export declare function calculateFitWidthScale(availableWidth: number, pageWidth: number, ratio?: number, minScale?: number, maxScale?: number): number;
30
35
  /**
31
36
  * Calculates the safe rasterization scale for PDF.js viewport rendering so that
32
37
  * canvas dimensions and texture memory stay strictly within hardware limits.