svelte-pdf-view 0.1.12 → 0.2.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/README.md +45 -18
- package/dist/PdfRenderer.svelte +36 -6
- package/dist/PdfToolbar.svelte +10 -2
- package/dist/PdfViewer.svelte +36 -11
- package/dist/PdfViewer.svelte.d.ts +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/pdf-viewer/PdfPresentationMode.d.ts +96 -0
- package/dist/pdf-viewer/PdfPresentationMode.js +437 -0
- package/dist/pdf-viewer/context.d.ts +14 -0
- package/dist/pdf-viewer/context.js +8 -0
- package/package.json +88 -90
package/README.md
CHANGED
|
@@ -112,12 +112,37 @@ The main container component that provides context for toolbar and renderer.
|
|
|
112
112
|
</PdfViewer>
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
| Prop | Type
|
|
116
|
-
| ------------------ |
|
|
117
|
-
| `src` | `PdfSource`
|
|
118
|
-
| `scale` | `number`
|
|
119
|
-
| `downloadFilename` | `string`
|
|
120
|
-
| `
|
|
115
|
+
| Prop | Type | Default | Description |
|
|
116
|
+
| ------------------ | ------------------------- | -------- | ---------------------------------------------------------------------- |
|
|
117
|
+
| `src` | `PdfSource` | required | PDF source (URL, ArrayBuffer, Uint8Array, or Blob) |
|
|
118
|
+
| `scale` | `number` | `1.0` | Initial zoom scale |
|
|
119
|
+
| `downloadFilename` | `string` | - | Custom filename for PDF download (default: from URL or 'document.pdf') |
|
|
120
|
+
| `onerror` | `(error: string) => void` | - | Callback when PDF fails to load |
|
|
121
|
+
| `class` | `string` | `''` | CSS class for the container |
|
|
122
|
+
|
|
123
|
+
#### Error Handling
|
|
124
|
+
|
|
125
|
+
```svelte
|
|
126
|
+
<script lang="ts">
|
|
127
|
+
import { PdfViewer, PdfToolbar, PdfRenderer } from 'svelte-pdf-view';
|
|
128
|
+
|
|
129
|
+
let errorMessage = $state<string | null>(null);
|
|
130
|
+
|
|
131
|
+
function handleError(error: string) {
|
|
132
|
+
errorMessage = error;
|
|
133
|
+
console.error('PDF failed to load:', error);
|
|
134
|
+
}
|
|
135
|
+
</script>
|
|
136
|
+
|
|
137
|
+
{#if errorMessage}
|
|
138
|
+
<div class="error-banner">Failed to load PDF: {errorMessage}</div>
|
|
139
|
+
{/if}
|
|
140
|
+
|
|
141
|
+
<PdfViewer src="/document.pdf" onerror={handleError}>
|
|
142
|
+
<PdfToolbar />
|
|
143
|
+
<PdfRenderer />
|
|
144
|
+
</PdfViewer>
|
|
145
|
+
```
|
|
121
146
|
|
|
122
147
|
### `<PdfToolbar>`
|
|
123
148
|
|
|
@@ -213,18 +238,19 @@ You can create your own toolbar using the context API:
|
|
|
213
238
|
|
|
214
239
|
#### `actions: PdfViewerActions`
|
|
215
240
|
|
|
216
|
-
| Method
|
|
217
|
-
|
|
|
218
|
-
| `zoomIn()`
|
|
219
|
-
| `zoomOut()`
|
|
220
|
-
| `setScale(scale: number)`
|
|
221
|
-
| `rotateClockwise()`
|
|
222
|
-
| `rotateCounterClockwise()`
|
|
223
|
-
| `goToPage(page: number)`
|
|
224
|
-
| `search(query: string)`
|
|
225
|
-
| `searchNext()`
|
|
226
|
-
| `searchPrevious()`
|
|
227
|
-
| `clearSearch()`
|
|
241
|
+
| Method | Description |
|
|
242
|
+
| ----------------------------- | ---------------------------- |
|
|
243
|
+
| `zoomIn()` | Increase zoom level |
|
|
244
|
+
| `zoomOut()` | Decrease zoom level |
|
|
245
|
+
| `setScale(scale: number)` | Set specific zoom scale |
|
|
246
|
+
| `rotateClockwise()` | Rotate 90° clockwise |
|
|
247
|
+
| `rotateCounterClockwise()` | Rotate 90° counter-clockwise |
|
|
248
|
+
| `goToPage(page: number)` | Navigate to specific page |
|
|
249
|
+
| `search(query: string)` | Search for text |
|
|
250
|
+
| `searchNext()` | Go to next search match |
|
|
251
|
+
| `searchPrevious()` | Go to previous search match |
|
|
252
|
+
| `clearSearch()` | Clear search highlights |
|
|
253
|
+
| `download(filename?: string)` | Download the PDF |
|
|
228
254
|
|
|
229
255
|
## Types
|
|
230
256
|
|
|
@@ -256,6 +282,7 @@ interface PdfViewerActions {
|
|
|
256
282
|
searchNext: () => void;
|
|
257
283
|
searchPrevious: () => void;
|
|
258
284
|
clearSearch: () => void;
|
|
285
|
+
download: (filename?: string) => Promise<void>;
|
|
259
286
|
}
|
|
260
287
|
```
|
|
261
288
|
|
package/dist/PdfRenderer.svelte
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
type PdfSource
|
|
8
8
|
} from './pdf-viewer/context.js';
|
|
9
9
|
import { getPdfJs } from './pdf-viewer/pdfjs-singleton.js';
|
|
10
|
+
import { PdfPresentationMode } from './pdf-viewer/PdfPresentationMode.js';
|
|
10
11
|
import { rendererStyles } from './pdf-viewer/renderer-styles.js';
|
|
11
12
|
|
|
12
13
|
interface Props {
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
}: Props = $props();
|
|
38
39
|
|
|
39
40
|
const context = getPdfViewerContext();
|
|
40
|
-
const { state: viewerState, _registerRenderer } = context;
|
|
41
|
+
const { state: viewerState, _registerRenderer, _setSrcDataForDownload } = context;
|
|
41
42
|
|
|
42
43
|
// Use prop src if provided, otherwise fall back to context src (via getter for reactivity)
|
|
43
44
|
let src = $derived(srcProp ?? context.src);
|
|
@@ -51,6 +52,17 @@
|
|
|
51
52
|
let viewer: import('./pdf-viewer/PDFViewerCore.js').PDFViewerCore | null = null;
|
|
52
53
|
let findController: import('./pdf-viewer/FindController.js').FindController | null = null;
|
|
53
54
|
|
|
55
|
+
// Presentation mode
|
|
56
|
+
const presentationMode = new PdfPresentationMode({
|
|
57
|
+
onStateChange: (newState) => {
|
|
58
|
+
viewerState.presentationMode = newState;
|
|
59
|
+
},
|
|
60
|
+
onPageChange: (pageNumber) => {
|
|
61
|
+
// Sync page number back to main viewer when changed in presentation mode
|
|
62
|
+
viewer?.scrollToPage(pageNumber);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
54
66
|
async function loadPdf(source: PdfSource) {
|
|
55
67
|
if (!BROWSER || !scrollContainerEl) return;
|
|
56
68
|
|
|
@@ -110,29 +122,39 @@
|
|
|
110
122
|
if (typeof source === 'string') {
|
|
111
123
|
// URL string
|
|
112
124
|
documentSource = source;
|
|
125
|
+
_setSrcDataForDownload(null); // URL doesn't need copying
|
|
113
126
|
} else if (source instanceof Blob) {
|
|
114
127
|
// Convert Blob to ArrayBuffer
|
|
115
128
|
const arrayBuffer = await source.arrayBuffer();
|
|
129
|
+
_setSrcDataForDownload(arrayBuffer.slice(0)); // Store a copy for download
|
|
116
130
|
documentSource = { data: arrayBuffer };
|
|
117
131
|
} else if (source instanceof ArrayBuffer) {
|
|
132
|
+
_setSrcDataForDownload(source.slice(0)); // Store a copy before PDF.js detaches it
|
|
118
133
|
documentSource = { data: source };
|
|
119
134
|
} else if (source instanceof Uint8Array) {
|
|
135
|
+
_setSrcDataForDownload(new Uint8Array(source).buffer.slice(0) as ArrayBuffer); // Store a copy for download
|
|
120
136
|
documentSource = { data: source };
|
|
121
137
|
} else {
|
|
122
138
|
throw new Error('Invalid PDF source type');
|
|
123
139
|
}
|
|
124
140
|
|
|
125
141
|
const loadingTask = pdfjs.getDocument(documentSource);
|
|
126
|
-
const
|
|
142
|
+
const loadedPdfDocument = await loadingTask.promise;
|
|
127
143
|
|
|
128
|
-
await newViewer.setDocument(
|
|
129
|
-
findController.setDocument(
|
|
144
|
+
await newViewer.setDocument(loadedPdfDocument);
|
|
145
|
+
findController.setDocument(loadedPdfDocument);
|
|
146
|
+
|
|
147
|
+
// Set document on presentation mode
|
|
148
|
+
presentationMode.setDocument(loadedPdfDocument);
|
|
130
149
|
|
|
131
150
|
viewer = newViewer;
|
|
132
151
|
viewerState.loading = false;
|
|
133
152
|
} catch (e) {
|
|
134
|
-
|
|
153
|
+
const errorMessage = e instanceof Error ? e.message : 'Failed to load PDF';
|
|
154
|
+
viewerState.error = errorMessage;
|
|
135
155
|
viewerState.loading = false;
|
|
156
|
+
// Call the error callback if provided
|
|
157
|
+
context._onerror?.(errorMessage);
|
|
136
158
|
}
|
|
137
159
|
}
|
|
138
160
|
|
|
@@ -167,7 +189,14 @@
|
|
|
167
189
|
viewerState.searchTotal = 0;
|
|
168
190
|
}
|
|
169
191
|
},
|
|
170
|
-
download: async () => {} // Download is handled by PdfViewer, not renderer
|
|
192
|
+
download: async () => {}, // Download is handled by PdfViewer, not renderer
|
|
193
|
+
enterPresentationMode: async () => {
|
|
194
|
+
presentationMode.setCurrentPage(viewerState.currentPage);
|
|
195
|
+
return presentationMode.request();
|
|
196
|
+
},
|
|
197
|
+
exitPresentationMode: async () => {
|
|
198
|
+
await presentationMode.exit();
|
|
199
|
+
}
|
|
171
200
|
};
|
|
172
201
|
|
|
173
202
|
onMount(async () => {
|
|
@@ -218,6 +247,7 @@
|
|
|
218
247
|
viewer = null;
|
|
219
248
|
}
|
|
220
249
|
findController = null;
|
|
250
|
+
presentationMode.destroy();
|
|
221
251
|
// Note: Worker is a global singleton, not cleaned up per-component
|
|
222
252
|
// Use destroyPdfJs() from pdfjs-singleton.js if you need to fully cleanup
|
|
223
253
|
});
|
package/dist/PdfToolbar.svelte
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
Search,
|
|
8
8
|
ChevronLeft,
|
|
9
9
|
ChevronRight,
|
|
10
|
-
Download
|
|
10
|
+
Download,
|
|
11
|
+
Presentation
|
|
11
12
|
} from '@lucide/svelte';
|
|
12
13
|
import { getPdfViewerContext } from './pdf-viewer/context.js';
|
|
13
14
|
|
|
@@ -116,8 +117,15 @@
|
|
|
116
117
|
{/if}
|
|
117
118
|
</div>
|
|
118
119
|
|
|
119
|
-
<!-- Download -->
|
|
120
|
+
<!-- Presentation & Download -->
|
|
120
121
|
<div class="pdf-toolbar-group">
|
|
122
|
+
<button
|
|
123
|
+
onclick={() => actions.enterPresentationMode()}
|
|
124
|
+
aria-label="Presentation Mode"
|
|
125
|
+
title="Presentation Mode"
|
|
126
|
+
>
|
|
127
|
+
<Presentation size={18} />
|
|
128
|
+
</button>
|
|
121
129
|
<button onclick={() => actions.download()} aria-label="Download PDF" title="Download">
|
|
122
130
|
<Download size={18} />
|
|
123
131
|
</button>
|
package/dist/PdfViewer.svelte
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import type { Snippet } from 'svelte';
|
|
9
9
|
import {
|
|
10
10
|
setPdfViewerContext,
|
|
11
|
+
PresentationModeState,
|
|
11
12
|
type PdfViewerState,
|
|
12
13
|
type PdfViewerActions,
|
|
13
14
|
type PdfSource
|
|
@@ -20,6 +21,8 @@
|
|
|
20
21
|
scale?: number;
|
|
21
22
|
/** Custom filename for PDF download (default: extracted from URL or 'document.pdf') */
|
|
22
23
|
downloadFilename?: string;
|
|
24
|
+
/** Callback when PDF fails to load */
|
|
25
|
+
onerror?: (error: string) => void;
|
|
23
26
|
/** CSS class for the container */
|
|
24
27
|
class?: string;
|
|
25
28
|
/** Children (toolbar and renderer) */
|
|
@@ -30,12 +33,17 @@
|
|
|
30
33
|
src,
|
|
31
34
|
scale: initialScale = 1.0,
|
|
32
35
|
downloadFilename,
|
|
36
|
+
onerror,
|
|
33
37
|
class: className = '',
|
|
34
38
|
children
|
|
35
39
|
}: Props = $props();
|
|
36
40
|
|
|
41
|
+
// Keep a copy of binary source data for download (PDF.js transfers/detaches ArrayBuffers)
|
|
42
|
+
// This is set by PdfRenderer before it passes data to PDF.js
|
|
43
|
+
let srcDataForDownload = $state<ArrayBuffer | null>(null);
|
|
44
|
+
|
|
37
45
|
// Reactive state that will be shared via context
|
|
38
|
-
let
|
|
46
|
+
let viewerState = $state<PdfViewerState>({
|
|
39
47
|
loading: true,
|
|
40
48
|
error: null,
|
|
41
49
|
totalPages: 0,
|
|
@@ -45,7 +53,8 @@
|
|
|
45
53
|
searchQuery: '',
|
|
46
54
|
searchCurrent: 0,
|
|
47
55
|
searchTotal: 0,
|
|
48
|
-
isSearching: false
|
|
56
|
+
isSearching: false,
|
|
57
|
+
presentationMode: PresentationModeState.NORMAL
|
|
49
58
|
});
|
|
50
59
|
|
|
51
60
|
// Renderer actions - will be populated when renderer mounts
|
|
@@ -77,11 +86,12 @@
|
|
|
77
86
|
}
|
|
78
87
|
} else if (src instanceof Blob) {
|
|
79
88
|
blob = src;
|
|
80
|
-
} else if (
|
|
81
|
-
|
|
89
|
+
} else if (srcDataForDownload) {
|
|
90
|
+
// Use the pre-copied data (original ArrayBuffer/Uint8Array gets detached by PDF.js)
|
|
91
|
+
blob = new Blob([srcDataForDownload], { type: 'application/pdf' });
|
|
82
92
|
} else {
|
|
83
|
-
|
|
84
|
-
|
|
93
|
+
console.error('Cannot download: no valid source data available');
|
|
94
|
+
return;
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
const url = URL.createObjectURL(blob);
|
|
@@ -108,27 +118,42 @@
|
|
|
108
118
|
searchNext: () => rendererActions?.searchNext(),
|
|
109
119
|
searchPrevious: () => rendererActions?.searchPrevious(),
|
|
110
120
|
clearSearch: () => rendererActions?.clearSearch(),
|
|
111
|
-
download: downloadPdf
|
|
121
|
+
download: downloadPdf,
|
|
122
|
+
enterPresentationMode: async () => {
|
|
123
|
+
if (rendererActions) {
|
|
124
|
+
return rendererActions.enterPresentationMode();
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
},
|
|
128
|
+
exitPresentationMode: async () => {
|
|
129
|
+
if (rendererActions) {
|
|
130
|
+
await rendererActions.exitPresentationMode();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
112
133
|
};
|
|
113
134
|
|
|
114
135
|
// Set up context
|
|
115
136
|
setPdfViewerContext({
|
|
116
|
-
state,
|
|
137
|
+
state: viewerState,
|
|
117
138
|
actions,
|
|
118
139
|
get src() {
|
|
119
140
|
return src;
|
|
120
141
|
},
|
|
121
142
|
_registerRenderer: (renderer: PdfViewerActions) => {
|
|
122
143
|
rendererActions = renderer;
|
|
144
|
+
},
|
|
145
|
+
_onerror: onerror,
|
|
146
|
+
_setSrcDataForDownload: (data: ArrayBuffer | null) => {
|
|
147
|
+
srcDataForDownload = data;
|
|
123
148
|
}
|
|
124
149
|
});
|
|
125
150
|
</script>
|
|
126
151
|
|
|
127
152
|
<div class="pdf-viewer-container {className}">
|
|
128
|
-
{#if
|
|
153
|
+
{#if viewerState.loading}
|
|
129
154
|
<div class="pdf-loading">Loading PDF...</div>
|
|
130
|
-
{:else if
|
|
131
|
-
<div class="pdf-error">Error: {
|
|
155
|
+
{:else if viewerState.error}
|
|
156
|
+
<div class="pdf-error">Error: {viewerState.error}</div>
|
|
132
157
|
{/if}
|
|
133
158
|
|
|
134
159
|
{#if children}
|
|
@@ -9,6 +9,8 @@ interface Props {
|
|
|
9
9
|
scale?: number;
|
|
10
10
|
/** Custom filename for PDF download (default: extracted from URL or 'document.pdf') */
|
|
11
11
|
downloadFilename?: string;
|
|
12
|
+
/** Callback when PDF fails to load */
|
|
13
|
+
onerror?: (error: string) => void;
|
|
12
14
|
/** CSS class for the container */
|
|
13
15
|
class?: string;
|
|
14
16
|
/** Children (toolbar and renderer) */
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { default as PdfViewer, Toolbar as PdfToolbar, Renderer as PdfRenderer } from './PdfViewer.svelte';
|
|
2
2
|
export type { PdfSource } from './pdf-viewer/context.js';
|
|
3
|
-
export { getPdfViewerContext, type PdfViewerState, type PdfViewerActions, type PdfViewerContext } from './pdf-viewer/context.js';
|
|
3
|
+
export { getPdfViewerContext, PresentationModeState, type PdfViewerState, type PdfViewerActions, type PdfViewerContext } from './pdf-viewer/context.js';
|
|
4
4
|
export { destroyPdfJs } from './pdf-viewer/pdfjs-singleton.js';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Reexport your entry components here
|
|
2
2
|
export { default as PdfViewer, Toolbar as PdfToolbar, Renderer as PdfRenderer } from './PdfViewer.svelte';
|
|
3
3
|
// Export context for custom toolbars
|
|
4
|
-
export { getPdfViewerContext } from './pdf-viewer/context.js';
|
|
4
|
+
export { getPdfViewerContext, PresentationModeState } from './pdf-viewer/context.js';
|
|
5
5
|
// Export PDF.js singleton utilities
|
|
6
6
|
export { destroyPdfJs } from './pdf-viewer/pdfjs-singleton.js';
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PdfPresentationMode - Fullscreen presentation mode for PDF viewing.
|
|
3
|
+
* This is a derivative work based on PDF.js pdf_presentation_mode.js
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Browser fullscreen mode
|
|
7
|
+
* - Black background
|
|
8
|
+
* - Single page view scaled to fit screen
|
|
9
|
+
* - No text layer or toolbar
|
|
10
|
+
* - Mouse/keyboard/touch navigation
|
|
11
|
+
*/
|
|
12
|
+
import type { PDFDocumentProxy } from 'pdfjs-dist/legacy/build/pdf.mjs';
|
|
13
|
+
export declare enum PresentationModeState {
|
|
14
|
+
UNKNOWN = 0,
|
|
15
|
+
NORMAL = 1,
|
|
16
|
+
CHANGING = 2,
|
|
17
|
+
FULLSCREEN = 3
|
|
18
|
+
}
|
|
19
|
+
export interface PresentationModeCallbacks {
|
|
20
|
+
onStateChange?: (state: PresentationModeState) => void;
|
|
21
|
+
onPageChange?: (pageNumber: number) => void;
|
|
22
|
+
}
|
|
23
|
+
export declare class PdfPresentationMode {
|
|
24
|
+
private state;
|
|
25
|
+
private pdfDocument;
|
|
26
|
+
private currentPageNumber;
|
|
27
|
+
private totalPages;
|
|
28
|
+
private container;
|
|
29
|
+
private canvas;
|
|
30
|
+
private callbacks;
|
|
31
|
+
private fullscreenChangeAbortController;
|
|
32
|
+
private windowAbortController;
|
|
33
|
+
private mouseScrollTimeStamp;
|
|
34
|
+
private mouseScrollDelta;
|
|
35
|
+
private touchSwipeState;
|
|
36
|
+
private renderingPage;
|
|
37
|
+
constructor(callbacks?: PresentationModeCallbacks);
|
|
38
|
+
/**
|
|
39
|
+
* Set the PDF document for presentation
|
|
40
|
+
*/
|
|
41
|
+
setDocument(pdfDocument: PDFDocumentProxy | null): void;
|
|
42
|
+
/**
|
|
43
|
+
* Set the current page number (used when entering presentation mode)
|
|
44
|
+
*/
|
|
45
|
+
setCurrentPage(pageNumber: number): void;
|
|
46
|
+
/**
|
|
47
|
+
* Check if presentation mode is active
|
|
48
|
+
*/
|
|
49
|
+
get active(): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Get current state
|
|
52
|
+
*/
|
|
53
|
+
get currentState(): PresentationModeState;
|
|
54
|
+
/**
|
|
55
|
+
* Request entering fullscreen presentation mode
|
|
56
|
+
*/
|
|
57
|
+
request(): Promise<boolean>;
|
|
58
|
+
/**
|
|
59
|
+
* Exit presentation mode
|
|
60
|
+
*/
|
|
61
|
+
exit(): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Go to next page
|
|
64
|
+
*/
|
|
65
|
+
nextPage(): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Go to previous page
|
|
68
|
+
*/
|
|
69
|
+
previousPage(): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Go to a specific page
|
|
72
|
+
*/
|
|
73
|
+
goToPage(pageNumber: number): boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Destroy and cleanup
|
|
76
|
+
*/
|
|
77
|
+
destroy(): void;
|
|
78
|
+
private createPresentationContainer;
|
|
79
|
+
private destroyPresentationContainer;
|
|
80
|
+
private renderCurrentPage;
|
|
81
|
+
private notifyStateChange;
|
|
82
|
+
private enter;
|
|
83
|
+
private doExit;
|
|
84
|
+
private handleMouseWheel;
|
|
85
|
+
private normalizeWheelDelta;
|
|
86
|
+
private handleMouseDown;
|
|
87
|
+
private handleKeyDown;
|
|
88
|
+
private handleContextMenu;
|
|
89
|
+
private handleTouchSwipe;
|
|
90
|
+
private handleResize;
|
|
91
|
+
private resetMouseScrollState;
|
|
92
|
+
private addWindowListeners;
|
|
93
|
+
private removeWindowListeners;
|
|
94
|
+
private addFullscreenChangeListeners;
|
|
95
|
+
private removeFullscreenChangeListeners;
|
|
96
|
+
}
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
/* Copyright 2024 Mozilla Foundation
|
|
2
|
+
*
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*/
|
|
15
|
+
export var PresentationModeState;
|
|
16
|
+
(function (PresentationModeState) {
|
|
17
|
+
PresentationModeState[PresentationModeState["UNKNOWN"] = 0] = "UNKNOWN";
|
|
18
|
+
PresentationModeState[PresentationModeState["NORMAL"] = 1] = "NORMAL";
|
|
19
|
+
PresentationModeState[PresentationModeState["CHANGING"] = 2] = "CHANGING";
|
|
20
|
+
PresentationModeState[PresentationModeState["FULLSCREEN"] = 3] = "FULLSCREEN";
|
|
21
|
+
})(PresentationModeState || (PresentationModeState = {}));
|
|
22
|
+
const MOUSE_SCROLL_COOLDOWN_TIME = 50; // in ms
|
|
23
|
+
const PAGE_SWITCH_THRESHOLD = 0.1;
|
|
24
|
+
const SWIPE_MIN_DISTANCE_THRESHOLD = 50;
|
|
25
|
+
const SWIPE_ANGLE_THRESHOLD = Math.PI / 6;
|
|
26
|
+
export class PdfPresentationMode {
|
|
27
|
+
state = PresentationModeState.UNKNOWN;
|
|
28
|
+
pdfDocument = null;
|
|
29
|
+
currentPageNumber = 1;
|
|
30
|
+
totalPages = 0;
|
|
31
|
+
container = null;
|
|
32
|
+
canvas = null;
|
|
33
|
+
callbacks;
|
|
34
|
+
fullscreenChangeAbortController = null;
|
|
35
|
+
windowAbortController = null;
|
|
36
|
+
mouseScrollTimeStamp = 0;
|
|
37
|
+
mouseScrollDelta = 0;
|
|
38
|
+
touchSwipeState = null;
|
|
39
|
+
renderingPage = false;
|
|
40
|
+
constructor(callbacks = {}) {
|
|
41
|
+
this.callbacks = callbacks;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Set the PDF document for presentation
|
|
45
|
+
*/
|
|
46
|
+
setDocument(pdfDocument) {
|
|
47
|
+
this.pdfDocument = pdfDocument;
|
|
48
|
+
this.totalPages = pdfDocument?.numPages ?? 0;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Set the current page number (used when entering presentation mode)
|
|
52
|
+
*/
|
|
53
|
+
setCurrentPage(pageNumber) {
|
|
54
|
+
this.currentPageNumber = Math.max(1, Math.min(pageNumber, this.totalPages));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if presentation mode is active
|
|
58
|
+
*/
|
|
59
|
+
get active() {
|
|
60
|
+
return (this.state === PresentationModeState.CHANGING ||
|
|
61
|
+
this.state === PresentationModeState.FULLSCREEN);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get current state
|
|
65
|
+
*/
|
|
66
|
+
get currentState() {
|
|
67
|
+
return this.state;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Request entering fullscreen presentation mode
|
|
71
|
+
*/
|
|
72
|
+
async request() {
|
|
73
|
+
if (this.active || !this.pdfDocument || this.totalPages === 0) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
// Create the presentation container
|
|
77
|
+
this.createPresentationContainer();
|
|
78
|
+
if (!this.container) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
this.addFullscreenChangeListeners();
|
|
82
|
+
this.notifyStateChange(PresentationModeState.CHANGING);
|
|
83
|
+
try {
|
|
84
|
+
await this.container.requestFullscreen();
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
this.removeFullscreenChangeListeners();
|
|
89
|
+
this.notifyStateChange(PresentationModeState.NORMAL);
|
|
90
|
+
this.destroyPresentationContainer();
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Exit presentation mode
|
|
96
|
+
*/
|
|
97
|
+
async exit() {
|
|
98
|
+
if (!this.active) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (document.fullscreenElement) {
|
|
102
|
+
await document.exitFullscreen();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Go to next page
|
|
107
|
+
*/
|
|
108
|
+
nextPage() {
|
|
109
|
+
if (this.currentPageNumber >= this.totalPages) {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
this.currentPageNumber++;
|
|
113
|
+
this.renderCurrentPage();
|
|
114
|
+
this.callbacks.onPageChange?.(this.currentPageNumber);
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Go to previous page
|
|
119
|
+
*/
|
|
120
|
+
previousPage() {
|
|
121
|
+
if (this.currentPageNumber <= 1) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
this.currentPageNumber--;
|
|
125
|
+
this.renderCurrentPage();
|
|
126
|
+
this.callbacks.onPageChange?.(this.currentPageNumber);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Go to a specific page
|
|
131
|
+
*/
|
|
132
|
+
goToPage(pageNumber) {
|
|
133
|
+
if (pageNumber < 1 || pageNumber > this.totalPages) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
this.currentPageNumber = pageNumber;
|
|
137
|
+
this.renderCurrentPage();
|
|
138
|
+
this.callbacks.onPageChange?.(this.currentPageNumber);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Destroy and cleanup
|
|
143
|
+
*/
|
|
144
|
+
destroy() {
|
|
145
|
+
this.exit();
|
|
146
|
+
this.removeWindowListeners();
|
|
147
|
+
this.removeFullscreenChangeListeners();
|
|
148
|
+
this.destroyPresentationContainer();
|
|
149
|
+
}
|
|
150
|
+
// Private methods
|
|
151
|
+
createPresentationContainer() {
|
|
152
|
+
// Create fullscreen container
|
|
153
|
+
this.container = document.createElement('div');
|
|
154
|
+
this.container.className = 'pdf-presentation-mode';
|
|
155
|
+
this.container.style.cssText = `
|
|
156
|
+
position: fixed;
|
|
157
|
+
top: 0;
|
|
158
|
+
left: 0;
|
|
159
|
+
width: 100%;
|
|
160
|
+
height: 100%;
|
|
161
|
+
background-color: #000;
|
|
162
|
+
display: flex;
|
|
163
|
+
align-items: center;
|
|
164
|
+
justify-content: center;
|
|
165
|
+
z-index: 999999;
|
|
166
|
+
`;
|
|
167
|
+
// Create canvas for rendering
|
|
168
|
+
this.canvas = document.createElement('canvas');
|
|
169
|
+
this.canvas.style.cssText = `
|
|
170
|
+
max-width: 100%;
|
|
171
|
+
max-height: 100%;
|
|
172
|
+
object-fit: contain;
|
|
173
|
+
`;
|
|
174
|
+
this.container.appendChild(this.canvas);
|
|
175
|
+
document.body.appendChild(this.container);
|
|
176
|
+
}
|
|
177
|
+
destroyPresentationContainer() {
|
|
178
|
+
if (this.container) {
|
|
179
|
+
this.container.remove();
|
|
180
|
+
this.container = null;
|
|
181
|
+
this.canvas = null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async renderCurrentPage() {
|
|
185
|
+
if (!this.pdfDocument || !this.canvas || !this.container || this.renderingPage) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
this.renderingPage = true;
|
|
189
|
+
try {
|
|
190
|
+
const page = await this.pdfDocument.getPage(this.currentPageNumber);
|
|
191
|
+
// Calculate scale to fit the screen while maintaining aspect ratio
|
|
192
|
+
const containerWidth = window.innerWidth;
|
|
193
|
+
const containerHeight = window.innerHeight;
|
|
194
|
+
const viewport = page.getViewport({ scale: 1, rotation: 0 });
|
|
195
|
+
const pageWidth = viewport.width;
|
|
196
|
+
const pageHeight = viewport.height;
|
|
197
|
+
// Calculate scale to fit
|
|
198
|
+
const scaleX = containerWidth / pageWidth;
|
|
199
|
+
const scaleY = containerHeight / pageHeight;
|
|
200
|
+
const scale = Math.min(scaleX, scaleY);
|
|
201
|
+
const scaledViewport = page.getViewport({ scale, rotation: 0 });
|
|
202
|
+
// Set canvas size
|
|
203
|
+
this.canvas.width = scaledViewport.width;
|
|
204
|
+
this.canvas.height = scaledViewport.height;
|
|
205
|
+
const context = this.canvas.getContext('2d');
|
|
206
|
+
if (!context) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
// Clear and render
|
|
210
|
+
context.fillStyle = '#fff';
|
|
211
|
+
context.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
212
|
+
await page.render({
|
|
213
|
+
canvasContext: context,
|
|
214
|
+
viewport: scaledViewport,
|
|
215
|
+
canvas: this.canvas
|
|
216
|
+
}).promise;
|
|
217
|
+
}
|
|
218
|
+
catch (e) {
|
|
219
|
+
console.error('Failed to render presentation page:', e);
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
this.renderingPage = false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
notifyStateChange(newState) {
|
|
226
|
+
this.state = newState;
|
|
227
|
+
this.callbacks.onStateChange?.(newState);
|
|
228
|
+
}
|
|
229
|
+
enter() {
|
|
230
|
+
this.notifyStateChange(PresentationModeState.FULLSCREEN);
|
|
231
|
+
this.addWindowListeners();
|
|
232
|
+
this.renderCurrentPage();
|
|
233
|
+
// Clear any text selection
|
|
234
|
+
document.getSelection()?.empty();
|
|
235
|
+
}
|
|
236
|
+
doExit() {
|
|
237
|
+
this.removeWindowListeners();
|
|
238
|
+
this.destroyPresentationContainer();
|
|
239
|
+
this.resetMouseScrollState();
|
|
240
|
+
this.removeFullscreenChangeListeners();
|
|
241
|
+
this.notifyStateChange(PresentationModeState.NORMAL);
|
|
242
|
+
}
|
|
243
|
+
handleMouseWheel = (evt) => {
|
|
244
|
+
if (!this.active) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
evt.preventDefault();
|
|
248
|
+
const delta = this.normalizeWheelDelta(evt);
|
|
249
|
+
const currentTime = Date.now();
|
|
250
|
+
const storedTime = this.mouseScrollTimeStamp;
|
|
251
|
+
// Cooldown to prevent accidental double-switching
|
|
252
|
+
if (currentTime > storedTime && currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
// Reset if direction changed
|
|
256
|
+
if ((this.mouseScrollDelta > 0 && delta < 0) || (this.mouseScrollDelta < 0 && delta > 0)) {
|
|
257
|
+
this.resetMouseScrollState();
|
|
258
|
+
}
|
|
259
|
+
this.mouseScrollDelta += delta;
|
|
260
|
+
if (Math.abs(this.mouseScrollDelta) >= PAGE_SWITCH_THRESHOLD) {
|
|
261
|
+
const totalDelta = this.mouseScrollDelta;
|
|
262
|
+
this.resetMouseScrollState();
|
|
263
|
+
const success = totalDelta > 0 ? this.previousPage() : this.nextPage();
|
|
264
|
+
if (success) {
|
|
265
|
+
this.mouseScrollTimeStamp = currentTime;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
normalizeWheelDelta(evt) {
|
|
270
|
+
let delta = Math.hypot(evt.deltaX, evt.deltaY);
|
|
271
|
+
const angle = Math.atan2(evt.deltaY, evt.deltaX);
|
|
272
|
+
if (-0.25 * Math.PI < angle && angle < 0.75 * Math.PI) {
|
|
273
|
+
delta = -delta;
|
|
274
|
+
}
|
|
275
|
+
if (evt.deltaMode === WheelEvent.DOM_DELTA_LINE) {
|
|
276
|
+
delta *= 30;
|
|
277
|
+
}
|
|
278
|
+
else if (evt.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
|
|
279
|
+
delta *= 30 * 10;
|
|
280
|
+
}
|
|
281
|
+
return delta / 30;
|
|
282
|
+
}
|
|
283
|
+
handleMouseDown = (evt) => {
|
|
284
|
+
// Left click (0) = next page, Right click (2) = previous page
|
|
285
|
+
if (evt.button === 0) {
|
|
286
|
+
evt.preventDefault();
|
|
287
|
+
if (evt.shiftKey) {
|
|
288
|
+
this.previousPage();
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
this.nextPage();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
else if (evt.button === 2) {
|
|
295
|
+
evt.preventDefault();
|
|
296
|
+
this.previousPage();
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
handleKeyDown = (evt) => {
|
|
300
|
+
if (!this.active) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
this.resetMouseScrollState();
|
|
304
|
+
switch (evt.key) {
|
|
305
|
+
case 'ArrowRight':
|
|
306
|
+
case 'ArrowDown':
|
|
307
|
+
case ' ':
|
|
308
|
+
case 'PageDown':
|
|
309
|
+
case 'Enter':
|
|
310
|
+
evt.preventDefault();
|
|
311
|
+
this.nextPage();
|
|
312
|
+
break;
|
|
313
|
+
case 'ArrowLeft':
|
|
314
|
+
case 'ArrowUp':
|
|
315
|
+
case 'PageUp':
|
|
316
|
+
case 'Backspace':
|
|
317
|
+
evt.preventDefault();
|
|
318
|
+
this.previousPage();
|
|
319
|
+
break;
|
|
320
|
+
case 'Home':
|
|
321
|
+
evt.preventDefault();
|
|
322
|
+
this.goToPage(1);
|
|
323
|
+
break;
|
|
324
|
+
case 'End':
|
|
325
|
+
evt.preventDefault();
|
|
326
|
+
this.goToPage(this.totalPages);
|
|
327
|
+
break;
|
|
328
|
+
case 'Escape':
|
|
329
|
+
// Escape is handled by the browser for fullscreen exit
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
handleContextMenu = (evt) => {
|
|
334
|
+
// Prevent context menu - right-click is handled in handleMouseDown
|
|
335
|
+
evt.preventDefault();
|
|
336
|
+
};
|
|
337
|
+
handleTouchSwipe = (evt) => {
|
|
338
|
+
if (!this.active) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (evt.touches.length > 1) {
|
|
342
|
+
this.touchSwipeState = null;
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
switch (evt.type) {
|
|
346
|
+
case 'touchstart':
|
|
347
|
+
this.touchSwipeState = {
|
|
348
|
+
startX: evt.touches[0].pageX,
|
|
349
|
+
startY: evt.touches[0].pageY,
|
|
350
|
+
endX: evt.touches[0].pageX,
|
|
351
|
+
endY: evt.touches[0].pageY
|
|
352
|
+
};
|
|
353
|
+
break;
|
|
354
|
+
case 'touchmove':
|
|
355
|
+
if (this.touchSwipeState === null) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
this.touchSwipeState.endX = evt.touches[0].pageX;
|
|
359
|
+
this.touchSwipeState.endY = evt.touches[0].pageY;
|
|
360
|
+
evt.preventDefault();
|
|
361
|
+
break;
|
|
362
|
+
case 'touchend': {
|
|
363
|
+
if (this.touchSwipeState === null) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const dx = this.touchSwipeState.endX - this.touchSwipeState.startX;
|
|
367
|
+
const dy = this.touchSwipeState.endY - this.touchSwipeState.startY;
|
|
368
|
+
const absAngle = Math.abs(Math.atan2(dy, dx));
|
|
369
|
+
let delta = 0;
|
|
370
|
+
if (Math.abs(dx) > SWIPE_MIN_DISTANCE_THRESHOLD &&
|
|
371
|
+
(absAngle <= SWIPE_ANGLE_THRESHOLD || absAngle >= Math.PI - SWIPE_ANGLE_THRESHOLD)) {
|
|
372
|
+
// Horizontal swipe
|
|
373
|
+
delta = dx;
|
|
374
|
+
}
|
|
375
|
+
else if (Math.abs(dy) > SWIPE_MIN_DISTANCE_THRESHOLD &&
|
|
376
|
+
Math.abs(absAngle - Math.PI / 2) <= SWIPE_ANGLE_THRESHOLD) {
|
|
377
|
+
// Vertical swipe
|
|
378
|
+
delta = dy;
|
|
379
|
+
}
|
|
380
|
+
if (delta > 0) {
|
|
381
|
+
this.previousPage();
|
|
382
|
+
}
|
|
383
|
+
else if (delta < 0) {
|
|
384
|
+
this.nextPage();
|
|
385
|
+
}
|
|
386
|
+
this.touchSwipeState = null;
|
|
387
|
+
break;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
handleResize = () => {
|
|
392
|
+
if (this.active) {
|
|
393
|
+
this.renderCurrentPage();
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
resetMouseScrollState() {
|
|
397
|
+
this.mouseScrollTimeStamp = 0;
|
|
398
|
+
this.mouseScrollDelta = 0;
|
|
399
|
+
}
|
|
400
|
+
addWindowListeners() {
|
|
401
|
+
if (this.windowAbortController) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
this.windowAbortController = new AbortController();
|
|
405
|
+
const { signal } = this.windowAbortController;
|
|
406
|
+
window.addEventListener('mousedown', this.handleMouseDown, { signal });
|
|
407
|
+
window.addEventListener('wheel', this.handleMouseWheel, { passive: false, signal });
|
|
408
|
+
window.addEventListener('keydown', this.handleKeyDown, { signal });
|
|
409
|
+
window.addEventListener('contextmenu', this.handleContextMenu, { signal });
|
|
410
|
+
window.addEventListener('touchstart', this.handleTouchSwipe, { signal });
|
|
411
|
+
window.addEventListener('touchmove', this.handleTouchSwipe, { passive: false, signal });
|
|
412
|
+
window.addEventListener('touchend', this.handleTouchSwipe, { signal });
|
|
413
|
+
window.addEventListener('resize', this.handleResize, { signal });
|
|
414
|
+
}
|
|
415
|
+
removeWindowListeners() {
|
|
416
|
+
this.windowAbortController?.abort();
|
|
417
|
+
this.windowAbortController = null;
|
|
418
|
+
}
|
|
419
|
+
addFullscreenChangeListeners() {
|
|
420
|
+
if (this.fullscreenChangeAbortController) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
this.fullscreenChangeAbortController = new AbortController();
|
|
424
|
+
window.addEventListener('fullscreenchange', () => {
|
|
425
|
+
if (document.fullscreenElement) {
|
|
426
|
+
this.enter();
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
this.doExit();
|
|
430
|
+
}
|
|
431
|
+
}, { signal: this.fullscreenChangeAbortController.signal });
|
|
432
|
+
}
|
|
433
|
+
removeFullscreenChangeListeners() {
|
|
434
|
+
this.fullscreenChangeAbortController?.abort();
|
|
435
|
+
this.fullscreenChangeAbortController = null;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
/** PDF source - can be a URL string, ArrayBuffer, Uint8Array, or Blob */
|
|
2
2
|
export type PdfSource = string | ArrayBuffer | Uint8Array | Blob;
|
|
3
|
+
/** Presentation mode state */
|
|
4
|
+
export declare enum PresentationModeState {
|
|
5
|
+
UNKNOWN = 0,
|
|
6
|
+
NORMAL = 1,
|
|
7
|
+
CHANGING = 2,
|
|
8
|
+
FULLSCREEN = 3
|
|
9
|
+
}
|
|
3
10
|
export interface PdfViewerState {
|
|
4
11
|
loading: boolean;
|
|
5
12
|
error: string | null;
|
|
@@ -11,6 +18,7 @@ export interface PdfViewerState {
|
|
|
11
18
|
searchCurrent: number;
|
|
12
19
|
searchTotal: number;
|
|
13
20
|
isSearching: boolean;
|
|
21
|
+
presentationMode: PresentationModeState;
|
|
14
22
|
}
|
|
15
23
|
export interface PdfViewerActions {
|
|
16
24
|
zoomIn: () => void;
|
|
@@ -24,6 +32,10 @@ export interface PdfViewerActions {
|
|
|
24
32
|
searchPrevious: () => void;
|
|
25
33
|
clearSearch: () => void;
|
|
26
34
|
download: (filename?: string) => Promise<void>;
|
|
35
|
+
/** Enter fullscreen presentation mode */
|
|
36
|
+
enterPresentationMode: () => Promise<boolean>;
|
|
37
|
+
/** Exit fullscreen presentation mode */
|
|
38
|
+
exitPresentationMode: () => Promise<void>;
|
|
27
39
|
}
|
|
28
40
|
export interface PdfViewerContext {
|
|
29
41
|
state: PdfViewerState;
|
|
@@ -31,6 +43,8 @@ export interface PdfViewerContext {
|
|
|
31
43
|
/** The PDF source - shared from PdfViewer to PdfRenderer */
|
|
32
44
|
src: PdfSource;
|
|
33
45
|
_registerRenderer: (renderer: PdfViewerActions) => void;
|
|
46
|
+
_onerror?: (error: string) => void;
|
|
47
|
+
_setSrcDataForDownload: (data: ArrayBuffer | null) => void;
|
|
34
48
|
}
|
|
35
49
|
export declare function setPdfViewerContext(ctx: PdfViewerContext): void;
|
|
36
50
|
export declare function getPdfViewerContext(): PdfViewerContext;
|
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { getContext, setContext } from 'svelte';
|
|
5
5
|
const PDF_VIEWER_CONTEXT_KEY = Symbol('pdf-viewer');
|
|
6
|
+
/** Presentation mode state */
|
|
7
|
+
export var PresentationModeState;
|
|
8
|
+
(function (PresentationModeState) {
|
|
9
|
+
PresentationModeState[PresentationModeState["UNKNOWN"] = 0] = "UNKNOWN";
|
|
10
|
+
PresentationModeState[PresentationModeState["NORMAL"] = 1] = "NORMAL";
|
|
11
|
+
PresentationModeState[PresentationModeState["CHANGING"] = 2] = "CHANGING";
|
|
12
|
+
PresentationModeState[PresentationModeState["FULLSCREEN"] = 3] = "FULLSCREEN";
|
|
13
|
+
})(PresentationModeState || (PresentationModeState = {}));
|
|
6
14
|
export function setPdfViewerContext(ctx) {
|
|
7
15
|
setContext(PDF_VIEWER_CONTEXT_KEY, ctx);
|
|
8
16
|
}
|
package/package.json
CHANGED
|
@@ -1,91 +1,89 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
}
|
|
2
|
+
"name": "svelte-pdf-view",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A modern, modular PDF viewer component for Svelte 5. Built on PDF.js with TypeScript support",
|
|
5
|
+
"author": "Louis Li",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/nullpointerexceptionkek/svelte-pdf-view.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/nullpointerexceptionkek/svelte-pdf-view/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/nullpointerexceptionkek/svelte-pdf-view#readme",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"!dist/**/*.test.*",
|
|
18
|
+
"!dist/**/*.spec.*",
|
|
19
|
+
"LICENSE.md",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"sideEffects": [
|
|
23
|
+
"**/*.css"
|
|
24
|
+
],
|
|
25
|
+
"svelte": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"type": "module",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"svelte": "./dist/index.js"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@lucide/svelte": ">=0.500.0",
|
|
36
|
+
"pdfjs-dist": "^5.0.0",
|
|
37
|
+
"svelte": "^5.0.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"@lucide/svelte": {
|
|
41
|
+
"optional": true
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@eslint/compat": "^1.4.0",
|
|
46
|
+
"@eslint/js": "^9.39.1",
|
|
47
|
+
"@sveltejs/adapter-auto": "^7.0.0",
|
|
48
|
+
"@sveltejs/adapter-static": "^3.0.10",
|
|
49
|
+
"@sveltejs/kit": "^2.49.1",
|
|
50
|
+
"@sveltejs/package": "^2.5.7",
|
|
51
|
+
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
|
52
|
+
"@types/node": "^22",
|
|
53
|
+
"eslint": "^9.39.1",
|
|
54
|
+
"eslint-config-prettier": "^10.1.8",
|
|
55
|
+
"eslint-plugin-svelte": "^3.13.0",
|
|
56
|
+
"globals": "^16.5.0",
|
|
57
|
+
"pdfjs-dist": "^5.4.394",
|
|
58
|
+
"prettier": "^3.6.2",
|
|
59
|
+
"prettier-plugin-svelte": "^3.4.0",
|
|
60
|
+
"publint": "^0.3.15",
|
|
61
|
+
"svelte": "^5.45.6",
|
|
62
|
+
"svelte-check": "^4.3.4",
|
|
63
|
+
"typescript": "^5.9.3",
|
|
64
|
+
"typescript-eslint": "^8.47.0",
|
|
65
|
+
"vite": "^7.2.2"
|
|
66
|
+
},
|
|
67
|
+
"keywords": [
|
|
68
|
+
"svelte",
|
|
69
|
+
"svelte5",
|
|
70
|
+
"pdf",
|
|
71
|
+
"pdf-viewer",
|
|
72
|
+
"pdfjs",
|
|
73
|
+
"pdf.js",
|
|
74
|
+
"document-viewer",
|
|
75
|
+
"svelte-component"
|
|
76
|
+
],
|
|
77
|
+
"dependencies": {
|
|
78
|
+
"esm-env": "^1.2.2"
|
|
79
|
+
},
|
|
80
|
+
"scripts": {
|
|
81
|
+
"dev": "vite dev",
|
|
82
|
+
"build": "vite build && npm run prepack",
|
|
83
|
+
"preview": "vite preview",
|
|
84
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
85
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
86
|
+
"format": "prettier --write .",
|
|
87
|
+
"lint": "prettier --check . && eslint ."
|
|
88
|
+
}
|
|
89
|
+
}
|