teamsync-pdf-viewer 1.3.4 → 1.4.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,31 @@
2
2
 
3
3
  All notable changes to `teamsync-pdf-viewer` are documented here.
4
4
 
5
+ ## [1.4.0] — 2026-09-04
6
+
7
+ ### Added
8
+ - **Text Selection & Copying Support**: Users can now select and copy text directly from PDF pages in the viewer.
9
+ - **Select & Pan Tool Modes**: Added `'select'` as a first-class `ToolMode`. Defaulted `activeTool` to `'select'` (matching Adobe Acrobat, Chrome, and PDF.js). Added "Select Text" (`MousePointer`) and "Pan Tool" (`Hand`) toggle buttons to the top toolbar. Added `defaultTool?: 'select' | 'pan'` and `enableTextSelection?: boolean` props to `WebViewerOptions` and `<TeamSyncViewer>`.
10
+ - **Triple-Layer Copy Workflows**:
11
+ - **Keyboard**: `Cmd+C` (macOS) / `Ctrl+C` (Windows/Linux) copies selected text to the clipboard.
12
+ - **Native Context Menu**: Right-clicking selected text preserves the native browser context menu ("Copy", "Look Up", "Search", "Translate").
13
+ - **Floating Quick-Copy Tooltip**: Rendered a sleek floating pill above active text selections with a `[Copy]` button and `✓ Copied!` visual feedback.
14
+ - **Public SDK APIs & Events**: Added `instance.getSelectedText(): string` and `instance.copySelectedText(): Promise<boolean>`, Apryse tool aliases in `instance.UI.setToolMode('select' | 'textSelect' | 'pan')`, and `textSelected` and `textCopied` events to `ViewerEventMap`.
15
+ - **Cross-Environment Clipboard Utility**: Exported `copyTextToClipboard` with modern `navigator.clipboard.writeText` and a hidden `document.execCommand('copy')` fallback for non-HTTPS and iframe environments.
16
+
17
+ ### Fixed
18
+ - **Panning Drag Interference**: Drag-panning is now strictly scoped to `activeTool === 'pan'`, ensuring text selection click-and-drag is never hijacked.
19
+ - **SVG Overlay Pointer Events**: Configured SVG overlay to `pointerEvents: 'none'` in select and pan modes so clicks reach the text layer, while preserving `pointerEvents: 'all'` on individual annotation elements.
20
+ - **PDF.js v6 Text Layer CSS Variables**: Declared `--total-scale-factor: 1; --scale-factor: 1; --user-unit: 1; --min-font-size: 1; --scale-round-x: 1px; --scale-round-y: 1px;` on `.textLayer`, resolving collapsed 0px font sizes and heights. Styled `.textLayer span::selection` with transparent text so the underlying canvas raster remains crisp without double glyphs.
21
+
22
+ ## [1.3.5] — 2026-09-04
23
+
24
+ ### Fixed
25
+ - **Annotations Before Document Canvas**: Fixed bug where the SVG annotation overlay and transient citation highlights rendered synchronously over a still-blank white container on initial load, virtualized scroll (pages 2+), and post-open fit re-renders. Annotations now wait until the underlying page canvas has finished rendering its pixels before becoming visible.
26
+ - **`hideAnnotationsUntilPageRendered` Option**: Added `hideAnnotationsUntilPageRendered?: boolean` (defaults to `true`) across `WebViewerOptions`, `TeamSyncViewerProps`, and `DocumentViewerProps`.
27
+ - **`pageRendered` Lifecycle Event**: Added `pageRendered: { url: string; pageNumber: number }` to `ViewerEventMap` and `onPageRendered` callback to `TeamSyncViewerProps` and `DocumentViewerProps`, giving consumers a lifecycle hook whenever any page finishes canvas rendering.
28
+ - **Capped Zoom Level to 800%**: Changed `MAX_SCALE = 8.0` (800%), updated zoom stepping and boundary checks, and updated toolbar preset dropdown to cap at 800%.
29
+
5
30
  ## [1.3.4] — 2026-09-04
6
31
 
7
32
  ### Fixed
@@ -35,6 +35,8 @@ interface DocumentViewerProps {
35
35
  onLoadError?: (error: Error, info: DocumentLoadErrorInfo) => void;
36
36
  /** Fires once per loaded document, after the first page canvas has been painted. */
37
37
  onFirstPageRendered?: (pageNumber: number) => void;
38
+ /** Fires whenever any page canvas has finished rendering. */
39
+ onPageRendered?: (pageNumber: number) => void;
38
40
  onPageChange?: (pageNumber: number, numPages: number) => void;
39
41
  pageTransition: 'continuous' | 'page-by-page';
40
42
  pageLayout: 'single' | 'double' | 'cover-facing';
@@ -47,6 +49,11 @@ interface DocumentViewerProps {
47
49
  page?: number;
48
50
  transientHighlights?: TransientHighlight[];
49
51
  permissions?: SDKPermissions;
52
+ /** Whether to hide annotations and transient highlights until the page canvas has finished rendering. Default: true. */
53
+ hideAnnotationsUntilPageRendered?: boolean;
54
+ enableTextSelection?: boolean;
55
+ defaultTool?: 'select' | 'pan';
56
+ showSelectionTooltip?: boolean;
50
57
  }
51
- export default function DocumentViewer({ leftSidebarOpen, rightSidebarOpen, activeTab, annotationManager, initialDoc, loadNonce, withCredentials, assets, sidebars, redactions, regexRedactions, scale, setScale, sidebarTab, setSidebarTab, onAnnotationsChange, onRedactionsChange, onDocumentLoaded, onLoadError, onFirstPageRendered, onPageChange, pageTransition, pageLayout, rotation, setRotation, watermark, watermarkText, enableAnnotations: _enableAnnotations, initialPage, page, transientHighlights, permissions }: DocumentViewerProps): React.JSX.Element;
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;
52
59
  export {};
@@ -35,7 +35,9 @@ interface PageRendererProps {
35
35
  onDiscardRedaction?: (redaction: Redaction) => void;
36
36
  /** Called after the page canvas has been painted (used for first-page-rendered telemetry). */
37
37
  onRendered?: (pageNum: number) => void;
38
+ /** Whether to hide annotations and transient highlights until the page canvas has finished rendering. Default: true. */
39
+ hideAnnotationsUntilPageRendered?: boolean;
38
40
  }
39
- 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, onAnnotationMouseEnter, onClearSelection, watermark, watermarkText, redactions, onDiscardRedaction, onRendered }: PageRendererProps): React.JSX.Element;
41
+ 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, onAnnotationMouseEnter, onClearSelection, watermark, watermarkText, redactions, onDiscardRedaction, onRendered, hideAnnotationsUntilPageRendered }: PageRendererProps): React.JSX.Element;
40
42
  declare const PageRenderer: React.MemoExoticComponent<typeof PageRendererComponent>;
41
43
  export default PageRenderer;
@@ -24,6 +24,7 @@ interface SideBySideViewerProps {
24
24
  selectedDiffId?: string | null;
25
25
  onSelectDiff?: (id: string) => void;
26
26
  onScrollSync?: (scrollTop: number, scrollLeft: number) => void;
27
+ hideAnnotationsUntilPageRendered?: boolean;
27
28
  }
28
- export default function SideBySideViewer({ pdfDocA, pdfDocB, pageNum, scale, rotation, basePageDims, activeTab, activeTool, annotations, watermark, watermarkText, redactions, diffsA, diffsB, selectedDiffId, onSelectDiff }: SideBySideViewerProps): React.JSX.Element;
29
+ export default function SideBySideViewer({ pdfDocA, pdfDocB, pageNum, scale, rotation, basePageDims, activeTab, activeTool, annotations, watermark, watermarkText, redactions, diffsA, diffsB, selectedDiffId, onSelectDiff, hideAnnotationsUntilPageRendered }: SideBySideViewerProps): React.JSX.Element;
29
30
  export {};
@@ -22,6 +22,11 @@ export interface TeamSyncViewerProps extends Omit<WebViewerOptions, 'path'> {
22
22
  url: string;
23
23
  pageNumber: number;
24
24
  }) => void;
25
+ /** Fires whenever any page canvas has finished rendering. */
26
+ onPageRendered?: (info: {
27
+ url: string;
28
+ pageNumber: number;
29
+ }) => void;
25
30
  /** Fires (in addition to onDocumentLoadError) when the document is password protected. */
26
31
  onPasswordRequired?: (info: {
27
32
  url: string;
@@ -30,6 +35,14 @@ export interface TeamSyncViewerProps extends Omit<WebViewerOptions, 'path'> {
30
35
  onAnnotationsChange?: (annotations: Annotation[]) => void;
31
36
  /** Fires whenever transient highlights change. */
32
37
  onTransientHighlightsChange?: (highlights: TransientHighlight[]) => void;
38
+ /** Fires whenever the user selects text within the document. */
39
+ onTextSelected?: (info: {
40
+ text: string;
41
+ }) => void;
42
+ /** Fires whenever text is copied from the document. */
43
+ onTextCopied?: (info: {
44
+ text: string;
45
+ }) => void;
33
46
  className?: string;
34
47
  style?: React.CSSProperties;
35
48
  }
@@ -0,0 +1,7 @@
1
+ import { default as React } from 'react';
2
+ interface TextSelectionTooltipProps {
3
+ containerRef: React.RefObject<HTMLDivElement | null>;
4
+ onCopy?: (text: string) => void;
5
+ }
6
+ export declare const TextSelectionTooltip: React.FC<TextSelectionTooltipProps>;
7
+ export default TextSelectionTooltip;
@@ -103,6 +103,15 @@ export declare class WebViewerInstance {
103
103
  * Uses the bytes of the loaded document; never re-fetches the URL.
104
104
  */
105
105
  getFileData(options?: ExportOptions): Promise<Uint8Array>;
106
+ /**
107
+ * Returns the currently selected text string from the document viewer, or an empty string if nothing is selected.
108
+ */
109
+ getSelectedText(): string;
110
+ /**
111
+ * Copies the currently selected text to the system clipboard.
112
+ * Resolves to true if copying succeeded, false otherwise.
113
+ */
114
+ copySelectedText(): Promise<boolean>;
106
115
  readonly UI: {
107
116
  /** Theming is not implemented; kept for API compatibility. */
108
117
  setTheme: (_theme: string) => void;
@@ -114,6 +123,8 @@ export declare class WebViewerInstance {
114
123
  disableElements: (elements: string[]) => void;
115
124
  setActiveLeftPanel: (panel: string) => void;
116
125
  setToolMode: (tool: string | null) => void;
126
+ getSelectedText: () => string;
127
+ copySelectedText: () => Promise<boolean>;
117
128
  fitWidth: () => void;
118
129
  fitPage: () => void;
119
130
  fitToWidth: () => void;
@@ -54,6 +54,7 @@ export interface ViewerUser {
54
54
  name: string;
55
55
  }
56
56
  export type InitialScale = number | 'fit-width' | 'fit-page';
57
+ export type ToolMode = 'select' | 'pan' | 'rectangle' | 'ellipse' | 'line' | 'arrow' | 'freehand' | 'highlight' | 'text' | 'eraser' | 'note' | 'callout' | 'signature' | 'digital_signature' | 'link' | 'redaction' | null;
57
58
  /**
58
59
  * Options accepted by `createWebViewer()` (and the deprecated `WebViewer()` alias).
59
60
  * Every option is also available as a prop on `<TeamSyncViewer>`.
@@ -100,6 +101,18 @@ export interface WebViewerOptions {
100
101
  leftPanelOpen?: boolean;
101
102
  /** Focus the viewer on mount so keyboard shortcuts work immediately. Default false. */
102
103
  autoFocus?: boolean;
104
+ /**
105
+ * Hide SVG annotation overlays and transient highlights until the underlying page canvas has finished rendering.
106
+ * Prevents annotations from rendering over a blank white page container on initial load, scroll, or fit re-render.
107
+ * Default true.
108
+ */
109
+ hideAnnotationsUntilPageRendered?: boolean;
110
+ /** Allow selecting text from PDF pages (default true). */
111
+ enableTextSelection?: boolean;
112
+ /** Initial tool mode on load. Defaults to 'select'. */
113
+ defaultTool?: 'select' | 'pan';
114
+ /** Show floating quick-action copy badge above selected text (default true). */
115
+ showSelectionTooltip?: boolean;
103
116
  }
104
117
  /** Events available through `instance.on(type, listener)`. */
105
118
  export interface ViewerEventMap {
@@ -116,6 +129,10 @@ export interface ViewerEventMap {
116
129
  url: string;
117
130
  pageNumber: number;
118
131
  };
132
+ pageRendered: {
133
+ url: string;
134
+ pageNumber: number;
135
+ };
119
136
  pageChanged: {
120
137
  pageNumber: number;
121
138
  numPages: number;
@@ -131,6 +148,12 @@ export interface ViewerEventMap {
131
148
  toolChanged: {
132
149
  tool: string | null;
133
150
  };
151
+ textSelected: {
152
+ text: string;
153
+ };
154
+ textCopied: {
155
+ text: string;
156
+ };
134
157
  destroy: Record<string, never>;
135
158
  }
136
159
  export type ViewerEventType = keyof ViewerEventMap;
@@ -17,7 +17,9 @@ export { ViewerBus } from './core/eventBus';
17
17
  export type { BusListener } from './core/eventBus';
18
18
  export { buildPdfBytes, MAX_EXPORT_BYTES } from './core/export';
19
19
  export type { ExportInput, ExportOptions } from './core/export';
20
- export type { WebViewerOptions, Redaction, WatermarkOptions, SDKPermissions, ViewerUser, InitialScale, ViewerEventMap, ViewerEventType, } from './core/types';
20
+ export type { WebViewerOptions, Redaction, WatermarkOptions, SDKPermissions, ViewerUser, InitialScale, ViewerEventMap, ViewerEventType, ToolMode, } from './core/types';
21
+ export { copyTextToClipboard } from './utils/clipboardUtils';
22
+ export { TextSelectionTooltip } from './components/TextSelectionTooltip';
21
23
  export type { Annotation, AnnotationType, AnnotationPoint, AnnotationInput } from './annotations/types';
22
24
  export { AnnotationManager } from './annotations/AnnotationManager';
23
25
  export type { AnnotationAction, AnnotationChangedEvent, CommitOptions, ViewerUserInfo } from './annotations/AnnotationManager';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * © 2026 AngelBot Ai Pvt Ltd. All rights reserved.
3
+ * Cross-environment clipboard utility with secure and fallback execCommand copying.
4
+ */
5
+ export declare function copyTextToClipboard(text: string): Promise<boolean>;
@@ -2,7 +2,7 @@
2
2
  * © 2026 AngelBot Ai Pvt Ltd. All rights reserved.
3
3
  */
4
4
  export declare const MIN_SCALE = 0.1;
5
- export declare const MAX_SCALE = 10;
5
+ export declare const MAX_SCALE = 8;
6
6
  export declare const isMobileOrTablet: () => boolean;
7
7
  export declare const isSafariOrMobile: () => boolean;
8
8
  export declare const MAX_CANVAS_DIM = 4096;
@@ -13,7 +13,7 @@ export declare const MAX_CANVAS_PIXELS_MOBILE = 4194304;
13
13
  * Calculates the next step when zooming in.
14
14
  * - Below 200%: steps by +25%
15
15
  * - 200% to 500%: steps by +50%
16
- * - 500% to 1000%: steps by +100%
16
+ * - 500% to 800%: steps by +100%
17
17
  */
18
18
  export declare function calculateNextZoomIn(currentScale: number, maxScale?: number): number;
19
19
  /**