teamsync-pdf-viewer 1.5.0 → 1.6.0

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,26 @@
2
2
 
3
3
  All notable changes to `teamsync-pdf-viewer` are documented here.
4
4
 
5
+ ## [1.6.0] — 2026-09-06
6
+
7
+ ### Added
8
+ - **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.
9
+ - **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.
10
+ - **Page-Scoped Search & Highlighting**:
11
+ - Added additive 5th parameter `pages?: number[]` to `searchPdfText(doc, query, redactions, onProgress, pages)`.
12
+ - Added `options.pages?: number[]` to `instance.searchText(query, options)`.
13
+ - Added `scope?: 'document' | 'page'` to `instance.highlightSnippet(query, options)` allowing strict page restriction when navigating to snippet citations.
14
+ - **Sidebar Highlighting Precision**: Result `matchLength` now reflects the exact consumed raw glyph span, ensuring sidebar bold highlights match rendered page text identically.
15
+ - **Stable Search Results Sorting**: Corrected search result sorting to maintain page index ascending order while preserving intra-page ascending match order.
16
+
17
+ ## [1.5.1] — 2026-09-06
18
+
19
+ ### Added
20
+ - **`redactionsApplied` & `redactionsChanged` Events**: Added typed `redactionsApplied: { redactions: Redaction[] }` and `redactionsChanged: { redactions: Redaction[] }` events to `ViewerEventMap` available via `instance.on()`.
21
+ - **Public Redaction Callbacks**: Added `onRedactionsApplied?: (redactions: Redaction[]) => void` and `onRedactionsChange?: (redactions: Redaction[]) => void` props to `<TeamSyncViewer>` and `WebViewerOptions`.
22
+ - **Apply Confirmation Hook**: Threaded `onRedactionsApplied` through `DocumentViewerProps`, firing once per user click of Apply in the confirmation modal with the full combined list of applied redactions.
23
+ - **Public `getRedactions()` API**: Added `instance.getRedactions(): Redaction[]` to `WebViewerInstance` mirroring `getAnnotations()`, returning the live combined redaction list.
24
+
5
25
  ## [1.5.0] — 2026-09-05
6
26
 
7
27
  ### Added
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  ![TeamSync PDF Viewer UI](https://raw.githubusercontent.com/angelbot-ai/TeamSync-PDF-Viewer/main/docs/images/viewer_ui.png)
6
6
 
7
- ### **100% Open Source. 100% Client-Side. Enterprise PDF Engine.**
7
+ ### **100% Open Source. 100% Client-Side. Enterprise SDK.**
8
8
 
9
9
  [![License: CPAL-1.0](https://img.shields.io/badge/License-CPAL--1.0-blue.svg)](LICENSE)
10
10
  [![Live Demo](https://img.shields.io/badge/Live%20Demo-pdfviewer.teamsync.com-000000.svg)](https://pdfviewer.teamsync.com/)
@@ -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—guaranteeing instant **HIPAA**, **GDPR**, and **FINRA** compliance.
28
+ - ⚡ **100% Client-Side WebAssembly**: 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
 
@@ -84,10 +84,10 @@ Everything you need to build collaborative, secure document workflows.
84
84
  - **Notes & Callouts**: Sticky notes, callout text boxes with directional arrows, and text annotations.
85
85
  - **Interactive Hyperlinks**: Create internal page-jump links (`#page=N`) or external web URL links directly on document selections.
86
86
 
87
- ### 🛡️ Military-Grade Secure Redaction
87
+ ### 🛡️ Secure Redaction
88
88
  - **Binary-Level Data Obliteration**: We don't just place black boxes over text—redactions are permanently rasterized and burned into the underlying PDF vector structure.
89
89
  - **Text Layer Sanitization**: Redacted text is automatically stripped from the DOM `textLayer` and PDF content streams, preventing copy/paste extraction and search indexing.
90
- - **Automatic Regex Redactions**: Programmatically locate and redact sensitive PII (Aadhaar numbers, SSNs, credit cards, dates of birth) in a single click.
90
+ - **Automatic Regex Redactions**: Programmatically locate and redact sensitive PII in a single click.
91
91
  - **Discard Unapplied Redactions**: Easily discard individual pending redactions or bulk-discard all unapplied redaction marks before committing.
92
92
 
93
93
  ### 📐 Dynamic Layout & Viewport Controls
@@ -31,6 +31,8 @@ interface DocumentViewerProps {
31
31
  onAnnotationsChange?: (annotations: Annotation[]) => void;
32
32
  /** Reports the combined (prop + regex + manual) redaction list whenever it changes. */
33
33
  onRedactionsChange?: (redactions: Redaction[]) => void;
34
+ /** Fires once per user click of Apply in the confirmation modal with all applied redactions. */
35
+ onRedactionsApplied?: (redactions: Redaction[]) => void;
34
36
  onDocumentLoaded?: (doc: pdfjsLib.PDFDocumentProxy, url: string) => void;
35
37
  onLoadError?: (error: Error, info: DocumentLoadErrorInfo) => void;
36
38
  /** Fires once per loaded document, after the first page canvas has been painted. */
@@ -55,5 +57,5 @@ interface DocumentViewerProps {
55
57
  defaultTool?: 'select' | 'pan';
56
58
  showSelectionTooltip?: boolean;
57
59
  }
58
- export default function DocumentViewer({ leftSidebarOpen, rightSidebarOpen, activeTab, annotationManager, initialDoc, loadNonce, withCredentials, assets, sidebars, redactions, regexRedactions, scale, setScale, sidebarTab, setSidebarTab, onAnnotationsChange, onRedactionsChange, 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;
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;
59
61
  export {};
@@ -1,7 +1,7 @@
1
1
  import { default as React } from 'react';
2
2
  import { DocumentLoadErrorInfo } from './DocumentViewer';
3
3
  import { WebViewerInstance } from '../core/ViewerInstance';
4
- import { WebViewerOptions, TransientHighlight } from '../core/types';
4
+ import { WebViewerOptions, Redaction, TransientHighlight } from '../core/types';
5
5
  import { Annotation } from '../annotations/types';
6
6
  export interface TeamSyncViewerProps extends Omit<WebViewerOptions, 'path'> {
7
7
  /** URL of the PDF to display. Alias of `initialDoc`; `fileUrl` wins when both are set. */
@@ -33,6 +33,10 @@ export interface TeamSyncViewerProps extends Omit<WebViewerOptions, 'path'> {
33
33
  }) => void;
34
34
  onPageChange?: (pageNumber: number, numPages: number) => void;
35
35
  onAnnotationsChange?: (annotations: Annotation[]) => void;
36
+ /** Fires whenever pending or applied redactions change in the viewer. */
37
+ onRedactionsChange?: (redactions: Redaction[]) => void;
38
+ /** Fires once per user click of Apply in the confirmation modal with all applied redactions. */
39
+ onRedactionsApplied?: (redactions: Redaction[]) => void;
36
40
  /** Fires whenever transient highlights change. */
37
41
  onTransientHighlightsChange?: (highlights: TransientHighlight[]) => void;
38
42
  /** Fires whenever the user selects text within the document. */
@@ -70,8 +70,14 @@ export declare class WebViewerInstance {
70
70
  setCurrentPage(pageNumber: number): void;
71
71
  /**
72
72
  * Search the document for text matches, returning snippets and page bounding boxes.
73
+ *
74
+ * @param query - Search term.
75
+ * @param options - Search options:
76
+ * - `pages`: Specific 1-based page numbers to search. Defaults to all pages.
73
77
  */
74
- searchText(query: string): Promise<SearchResult[]>;
78
+ searchText(query: string, options?: {
79
+ pages?: number[];
80
+ }): Promise<SearchResult[]>;
75
81
  /** Get the current list of transient highlights. */
76
82
  getTransientHighlights(): TransientHighlight[];
77
83
  /**
@@ -87,15 +93,29 @@ export declare class WebViewerInstance {
87
93
  /**
88
94
  * Search for a snippet or citation query, scroll to its page, and highlight it temporarily.
89
95
  * Returns the matched SearchResult or null if not found.
96
+ *
97
+ * @param query - The text snippet to locate and highlight.
98
+ * @param options - Navigation and display options:
99
+ * - `pageIndex`: Preferred page (1-based). When `scope: 'page'`, restricts search to this page.
100
+ * - `scope`: Search scope. `'document'` (default) searches all pages, keeping `pageIndex` as a preference.
101
+ * `'page'` restricts the search strictly to `pageIndex` (avoids full-document walk).
102
+ * If `scope: 'page'` is provided without `pageIndex`, it gracefully degrades to `'document'`.
103
+ * - `pulse`: Animate focus ring (default true).
104
+ * - `scrollTo`: Scroll page into view (default true).
105
+ * - `color`: Background highlight color.
106
+ * - `tooltip`: Tooltip text above the highlight (defaults to matched snippet).
90
107
  */
91
108
  highlightSnippet(query: string, options?: {
92
109
  pageIndex?: number;
110
+ scope?: 'document' | 'page';
93
111
  pulse?: boolean;
94
112
  scrollTo?: boolean;
95
113
  color?: string;
96
114
  tooltip?: string;
97
115
  }): Promise<SearchResult | null>;
98
116
  getAnnotations(): Annotation[];
117
+ /** Get the current list of redactions (pending and applied). */
118
+ getRedactions(): Redaction[];
99
119
  /** Print the exported document (annotations, redactions and watermark baked in). */
100
120
  print(): void;
101
121
  /**
@@ -113,6 +113,10 @@ export interface WebViewerOptions {
113
113
  defaultTool?: 'select' | 'pan';
114
114
  /** Show floating quick-action copy badge above selected text (default true). */
115
115
  showSelectionTooltip?: boolean;
116
+ /** Fires whenever pending or applied redactions change in the viewer. */
117
+ onRedactionsChange?: (redactions: Redaction[]) => void;
118
+ /** Fires once per user click of Apply in the confirmation modal with all applied redactions. */
119
+ onRedactionsApplied?: (redactions: Redaction[]) => void;
116
120
  }
117
121
  /** Events available through `instance.on(type, listener)`. */
118
122
  export interface ViewerEventMap {
@@ -142,6 +146,12 @@ export interface ViewerEventMap {
142
146
  };
143
147
  /** Granular add / modify / delete (with the `imported` flag) from the AnnotationManager. */
144
148
  annotationChanged: AnnotationChangedEvent;
149
+ redactionsChanged: {
150
+ redactions: Redaction[];
151
+ };
152
+ redactionsApplied: {
153
+ redactions: Redaction[];
154
+ };
145
155
  transientHighlightsChanged: {
146
156
  highlights: TransientHighlight[];
147
157
  };
@@ -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[];
@@ -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[];