svelte-pdf-view 0.1.6 → 0.1.8

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 CHANGED
@@ -60,11 +60,13 @@ This is especially important in monorepo setups where Vite's optimizer may incor
60
60
  <div style="height: 100vh;">
61
61
  <PdfViewer src="/document.pdf">
62
62
  <PdfToolbar />
63
- <PdfRenderer src="/document.pdf" />
63
+ <PdfRenderer />
64
64
  </PdfViewer>
65
65
  </div>
66
66
  ```
67
67
 
68
+ The `src` prop is passed to `PdfViewer` and automatically shared with `PdfRenderer` via context - no need to pass it twice!
69
+
68
70
  ### Loading from Different Sources
69
71
 
70
72
  ```svelte
@@ -94,7 +96,7 @@ This is especially important in monorepo setups where Vite's optimizer may incor
94
96
 
95
97
  <PdfViewer src={pdfSource}>
96
98
  <PdfToolbar />
97
- <PdfRenderer src={pdfSource} />
99
+ <PdfRenderer />
98
100
  </PdfViewer>
99
101
  ```
100
102
 
@@ -1,15 +1,20 @@
1
1
  <script lang="ts">
2
2
  import { BROWSER } from 'esm-env';
3
3
  import { onDestroy, onMount } from 'svelte';
4
- import { getPdfViewerContext, type PdfViewerActions } from './pdf-viewer/context.js';
4
+ import {
5
+ getPdfViewerContext,
6
+ type PdfViewerActions,
7
+ type PdfSource
8
+ } from './pdf-viewer/context.js';
9
+ import { getPdfJs } from './pdf-viewer/pdfjs-singleton.js';
5
10
  import { rendererStyles } from './pdf-viewer/renderer-styles.js';
6
11
 
7
- /** PDF source - can be a URL string, ArrayBuffer, Uint8Array, or Blob */
8
- export type PdfSource = string | ArrayBuffer | Uint8Array | Blob;
12
+ // Re-export PdfSource type for convenience
13
+ export type { PdfSource };
9
14
 
10
15
  interface Props {
11
- /** PDF source - URL string, ArrayBuffer, Uint8Array, or Blob */
12
- src: PdfSource;
16
+ /** PDF source - URL string, ArrayBuffer, Uint8Array, or Blob. If not provided, uses src from PdfViewer context. */
17
+ src?: PdfSource;
13
18
  /** Background color of the scroll container */
14
19
  backgroundColor?: string;
15
20
  /** Page shadow style */
@@ -25,7 +30,7 @@
25
30
  }
26
31
 
27
32
  let {
28
- src,
33
+ src: srcProp,
29
34
  backgroundColor = '#e8e8e8',
30
35
  pageShadow = '0 2px 8px rgba(0, 0, 0, 0.12), 0 1px 3px rgba(0, 0, 0, 0.08)',
31
36
  scrollbarTrackColor = '#f1f1f1',
@@ -34,7 +39,10 @@
34
39
  scrollbarWidth = '10px'
35
40
  }: Props = $props();
36
41
 
37
- const { state: viewerState, _registerRenderer } = getPdfViewerContext();
42
+ const { state: viewerState, src: contextSrc, _registerRenderer } = getPdfViewerContext();
43
+
44
+ // Use prop src if provided, otherwise fall back to context src
45
+ let src = $derived(srcProp ?? contextSrc);
38
46
 
39
47
  let hostEl: HTMLDivElement | undefined = $state();
40
48
  let shadowRoot: ShadowRoot | null = null;
@@ -44,29 +52,6 @@
44
52
  // Core instances
45
53
  let viewer: import('./pdf-viewer/PDFViewerCore.js').PDFViewerCore | null = null;
46
54
  let findController: import('./pdf-viewer/FindController.js').FindController | null = null;
47
- let pdfjsLib: typeof import('pdfjs-dist/legacy/build/pdf.mjs') | null = null;
48
- let pdfWorker: import('pdfjs-dist/legacy/build/pdf.mjs').PDFWorker | null = null;
49
- let rawWorker: Worker | null = null;
50
-
51
- async function initPdfJs() {
52
- if (!BROWSER) return null;
53
-
54
- // Return cached instance if already initialized
55
- if (pdfjsLib && pdfWorker) return pdfjsLib;
56
-
57
- pdfjsLib = await import('pdfjs-dist/legacy/build/pdf.mjs');
58
-
59
- // Create worker only once using import.meta.url for proper bundler resolution
60
- rawWorker = new Worker(new URL('pdfjs-dist/legacy/build/pdf.worker.mjs', import.meta.url), {
61
- type: 'module'
62
- });
63
- pdfWorker = new pdfjsLib.PDFWorker({
64
- port: rawWorker as unknown as null
65
- });
66
- pdfjsLib.GlobalWorkerOptions.workerPort = pdfWorker.port;
67
-
68
- return pdfjsLib;
69
- }
70
55
 
71
56
  async function loadPdf(source: PdfSource) {
72
57
  if (!BROWSER || !scrollContainerEl) return;
@@ -75,7 +60,7 @@
75
60
  viewerState.error = null;
76
61
 
77
62
  try {
78
- const pdfjs = await initPdfJs();
63
+ const pdfjs = await getPdfJs();
79
64
  if (!pdfjs) return;
80
65
 
81
66
  const { PDFViewerCore } = await import('./pdf-viewer/PDFViewerCore.js');
@@ -183,7 +168,8 @@
183
168
  viewerState.searchCurrent = 0;
184
169
  viewerState.searchTotal = 0;
185
170
  }
186
- }
171
+ },
172
+ download: () => {} // Download is handled by PdfViewer, not renderer
187
173
  };
188
174
 
189
175
  onMount(async () => {
@@ -234,17 +220,8 @@
234
220
  viewer = null;
235
221
  }
236
222
  findController = null;
237
-
238
- // Cleanup worker to prevent memory leaks
239
- if (pdfWorker) {
240
- pdfWorker.destroy();
241
- pdfWorker = null;
242
- }
243
- if (rawWorker) {
244
- rawWorker.terminate();
245
- rawWorker = null;
246
- }
247
- pdfjsLib = null;
223
+ // Note: Worker is a global singleton, not cleaned up per-component
224
+ // Use destroyPdfJs() from pdfjs-singleton.js if you need to fully cleanup
248
225
  });
249
226
  </script>
250
227
 
@@ -1,8 +1,7 @@
1
- /** PDF source - can be a URL string, ArrayBuffer, Uint8Array, or Blob */
2
- export type PdfSource = string | ArrayBuffer | Uint8Array | Blob;
1
+ import { type PdfSource } from './pdf-viewer/context.js';
3
2
  interface Props {
4
- /** PDF source - URL string, ArrayBuffer, Uint8Array, or Blob */
5
- src: PdfSource;
3
+ /** PDF source - URL string, ArrayBuffer, Uint8Array, or Blob. If not provided, uses src from PdfViewer context. */
4
+ src?: PdfSource;
6
5
  /** Background color of the scroll container */
7
6
  backgroundColor?: string;
8
7
  /** Page shadow style */
@@ -16,6 +15,8 @@ interface Props {
16
15
  /** Scrollbar width */
17
16
  scrollbarWidth?: string;
18
17
  }
19
- declare const PdfRenderer: import("svelte").Component<Props, {}, "">;
18
+ declare const PdfRenderer: import("svelte").Component<Props, {
19
+ PdfSource: typeof PdfSource;
20
+ }, "">;
20
21
  type PdfRenderer = ReturnType<typeof PdfRenderer>;
21
22
  export default PdfRenderer;
@@ -6,7 +6,8 @@
6
6
  RotateCw,
7
7
  Search,
8
8
  ChevronLeft,
9
- ChevronRight
9
+ ChevronRight,
10
+ Download
10
11
  } from '@lucide/svelte';
11
12
  import { getPdfViewerContext } from './pdf-viewer/context.js';
12
13
 
@@ -114,6 +115,13 @@
114
115
  <span class="match-info">{viewerState.searchCurrent}/{viewerState.searchTotal}</span>
115
116
  {/if}
116
117
  </div>
118
+
119
+ <!-- Download -->
120
+ <div class="pdf-toolbar-group">
121
+ <button onclick={() => actions.download()} aria-label="Download PDF" title="Download">
122
+ <Download size={18} />
123
+ </button>
124
+ </div>
117
125
  </div>
118
126
 
119
127
  <style>
@@ -9,9 +9,9 @@
9
9
  import {
10
10
  setPdfViewerContext,
11
11
  type PdfViewerState,
12
- type PdfViewerActions
12
+ type PdfViewerActions,
13
+ type PdfSource
13
14
  } from './pdf-viewer/context.js';
14
- import type { PdfSource } from './PdfRenderer.svelte';
15
15
 
16
16
  interface Props {
17
17
  /** PDF source - URL string, ArrayBuffer, Uint8Array, or Blob */
@@ -43,6 +43,48 @@
43
43
  // Renderer actions - will be populated when renderer mounts
44
44
  let rendererActions: PdfViewerActions | null = null;
45
45
 
46
+ // Download helper function
47
+ function downloadPdf(filename?: string) {
48
+ const downloadName =
49
+ filename ||
50
+ (typeof src === 'string' ? src.split('/').pop() : 'document.pdf') ||
51
+ 'document.pdf';
52
+
53
+ if (typeof src === 'string') {
54
+ // URL - fetch and download
55
+ const link = document.createElement('a');
56
+ link.href = src;
57
+ link.download = downloadName;
58
+ link.click();
59
+ } else if (src instanceof Blob) {
60
+ // Blob - create object URL
61
+ const url = URL.createObjectURL(src);
62
+ const link = document.createElement('a');
63
+ link.href = url;
64
+ link.download = downloadName;
65
+ link.click();
66
+ URL.revokeObjectURL(url);
67
+ } else if (src instanceof ArrayBuffer) {
68
+ // ArrayBuffer
69
+ const blob = new Blob([src], { type: 'application/pdf' });
70
+ const url = URL.createObjectURL(blob);
71
+ const link = document.createElement('a');
72
+ link.href = url;
73
+ link.download = downloadName;
74
+ link.click();
75
+ URL.revokeObjectURL(url);
76
+ } else {
77
+ // Uint8Array - create a copy as ArrayBuffer
78
+ const blob = new Blob([new Uint8Array(src)], { type: 'application/pdf' });
79
+ const url = URL.createObjectURL(blob);
80
+ const link = document.createElement('a');
81
+ link.href = url;
82
+ link.download = downloadName;
83
+ link.click();
84
+ URL.revokeObjectURL(url);
85
+ }
86
+ }
87
+
46
88
  // Actions that proxy to the renderer
47
89
  const actions: PdfViewerActions = {
48
90
  zoomIn: () => rendererActions?.zoomIn(),
@@ -58,13 +100,15 @@
58
100
  },
59
101
  searchNext: () => rendererActions?.searchNext(),
60
102
  searchPrevious: () => rendererActions?.searchPrevious(),
61
- clearSearch: () => rendererActions?.clearSearch()
103
+ clearSearch: () => rendererActions?.clearSearch(),
104
+ download: downloadPdf
62
105
  };
63
106
 
64
107
  // Set up context
65
108
  setPdfViewerContext({
66
109
  state,
67
110
  actions,
111
+ src,
68
112
  _registerRenderer: (renderer: PdfViewerActions) => {
69
113
  rendererActions = renderer;
70
114
  }
@@ -1,7 +1,7 @@
1
1
  export { default as Toolbar } from './PdfToolbar.svelte';
2
2
  export { default as Renderer } from './PdfRenderer.svelte';
3
3
  import type { Snippet } from 'svelte';
4
- import type { PdfSource } from './PdfRenderer.svelte';
4
+ import { type PdfSource } from './pdf-viewer/context.js';
5
5
  interface Props {
6
6
  /** PDF source - URL string, ArrayBuffer, Uint8Array, or Blob */
7
7
  src: PdfSource;
@@ -42,29 +42,6 @@
42
42
  // Core instances (loaded dynamically)
43
43
  let viewer: import('./pdf-viewer/PDFViewerCore.js').PDFViewerCore | null = null;
44
44
  let findController: import('./pdf-viewer/FindController.js').FindController | null = null;
45
- let pdfjsLib: typeof import('pdfjs-dist/legacy/build/pdf.mjs') | null = null;
46
- let pdfWorker: import('pdfjs-dist/legacy/build/pdf.mjs').PDFWorker | null = null;
47
- let rawWorker: Worker | null = null;
48
-
49
- async function initPdfJs() {
50
- if (!BROWSER) return null;
51
-
52
- // Return cached instance if already initialized
53
- if (pdfjsLib && pdfWorker) return pdfjsLib;
54
-
55
- pdfjsLib = await import('pdfjs-dist/legacy/build/pdf.mjs');
56
-
57
- // Create worker only once using import.meta.url
58
- rawWorker = new Worker(new URL('pdfjs-dist/legacy/build/pdf.worker.mjs', import.meta.url), {
59
- type: 'module'
60
- });
61
- pdfWorker = new pdfjsLib.PDFWorker({
62
- port: rawWorker as unknown as null
63
- });
64
- pdfjsLib.GlobalWorkerOptions.workerPort = pdfWorker.port;
65
-
66
- return pdfjsLib;
67
- }
68
45
 
69
46
  async function loadPdf(url: string) {
70
47
  if (!BROWSER || !scrollContainerEl) return;
@@ -73,8 +50,9 @@
73
50
  error = null;
74
51
 
75
52
  try {
76
- // Initialize PDF.js
77
- const pdfjs = await initPdfJs();
53
+ // Get PDF.js from global singleton
54
+ const { getPdfJs } = await import('./pdf-viewer/pdfjs-singleton.js');
55
+ const pdfjs = await getPdfJs();
78
56
  if (!pdfjs) return;
79
57
 
80
58
  // Initialize viewer
@@ -229,17 +207,8 @@
229
207
  viewer = null;
230
208
  }
231
209
  findController = null;
232
-
233
- // Cleanup worker to prevent memory leaks
234
- if (pdfWorker) {
235
- pdfWorker.destroy();
236
- pdfWorker = null;
237
- }
238
- if (rawWorker) {
239
- rawWorker.terminate();
240
- rawWorker = null;
241
- }
242
- pdfjsLib = null;
210
+ // Note: Worker is a global singleton, not cleaned up per-component
211
+ // Use destroyPdfJs() from pdfjs-singleton.js if you need to fully cleanup
243
212
  });
244
213
  </script>
245
214
 
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { default as PdfViewer, Toolbar as PdfToolbar, Renderer as PdfRenderer } from './PdfViewer.svelte';
2
- export type { PdfSource } from './PdfRenderer.svelte';
2
+ export type { PdfSource } from './pdf-viewer/context.js';
3
3
  export { getPdfViewerContext, type PdfViewerState, type PdfViewerActions, type PdfViewerContext } from './pdf-viewer/context.js';
4
+ export { destroyPdfJs } from './pdf-viewer/pdfjs-singleton.js';
package/dist/index.js CHANGED
@@ -2,3 +2,5 @@
2
2
  export { default as PdfViewer, Toolbar as PdfToolbar, Renderer as PdfRenderer } from './PdfViewer.svelte';
3
3
  // Export context for custom toolbars
4
4
  export { getPdfViewerContext } from './pdf-viewer/context.js';
5
+ // Export PDF.js singleton utilities
6
+ export { destroyPdfJs } from './pdf-viewer/pdfjs-singleton.js';
@@ -1,3 +1,5 @@
1
+ /** PDF source - can be a URL string, ArrayBuffer, Uint8Array, or Blob */
2
+ export type PdfSource = string | ArrayBuffer | Uint8Array | Blob;
1
3
  export interface PdfViewerState {
2
4
  loading: boolean;
3
5
  error: string | null;
@@ -21,10 +23,13 @@ export interface PdfViewerActions {
21
23
  searchNext: () => void;
22
24
  searchPrevious: () => void;
23
25
  clearSearch: () => void;
26
+ download: (filename?: string) => void;
24
27
  }
25
28
  export interface PdfViewerContext {
26
29
  state: PdfViewerState;
27
30
  actions: PdfViewerActions;
31
+ /** The PDF source - shared from PdfViewer to PdfRenderer */
32
+ src: PdfSource;
28
33
  _registerRenderer: (renderer: PdfViewerActions) => void;
29
34
  }
30
35
  export declare function setPdfViewerContext(ctx: PdfViewerContext): void;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Get the PDF.js library instance. Creates the worker on first call.
3
+ * Subsequent calls return the cached instance.
4
+ */
5
+ export declare function getPdfJs(): Promise<typeof import('pdfjs-dist/legacy/build/pdf.mjs') | null>;
6
+ /**
7
+ * Destroy the PDF.js worker and cleanup resources.
8
+ * Call this when you're completely done with PDF viewing in your app.
9
+ * Note: After calling this, the next getPdfJs() call will create a new worker.
10
+ */
11
+ export declare function destroyPdfJs(): void;
@@ -0,0 +1,51 @@
1
+ import { BROWSER } from 'esm-env';
2
+ // Global singleton state
3
+ let pdfjsLib = null;
4
+ let pdfWorker = null;
5
+ let rawWorker = null;
6
+ let initPromise = null;
7
+ /**
8
+ * Get the PDF.js library instance. Creates the worker on first call.
9
+ * Subsequent calls return the cached instance.
10
+ */
11
+ export async function getPdfJs() {
12
+ if (!BROWSER)
13
+ return null;
14
+ // Return cached instance
15
+ if (pdfjsLib && pdfWorker)
16
+ return pdfjsLib;
17
+ // If initialization is in progress, wait for it
18
+ if (initPromise)
19
+ return initPromise;
20
+ // Start initialization
21
+ initPromise = (async () => {
22
+ pdfjsLib = await import('pdfjs-dist/legacy/build/pdf.mjs');
23
+ // Create worker only once using import.meta.url for proper bundler resolution
24
+ rawWorker = new Worker(new URL('pdfjs-dist/legacy/build/pdf.worker.mjs', import.meta.url), {
25
+ type: 'module'
26
+ });
27
+ pdfWorker = new pdfjsLib.PDFWorker({
28
+ port: rawWorker
29
+ });
30
+ pdfjsLib.GlobalWorkerOptions.workerPort = pdfWorker.port;
31
+ return pdfjsLib;
32
+ })();
33
+ return initPromise;
34
+ }
35
+ /**
36
+ * Destroy the PDF.js worker and cleanup resources.
37
+ * Call this when you're completely done with PDF viewing in your app.
38
+ * Note: After calling this, the next getPdfJs() call will create a new worker.
39
+ */
40
+ export function destroyPdfJs() {
41
+ if (pdfWorker) {
42
+ pdfWorker.destroy();
43
+ pdfWorker = null;
44
+ }
45
+ if (rawWorker) {
46
+ rawWorker.terminate();
47
+ rawWorker = null;
48
+ }
49
+ pdfjsLib = null;
50
+ initPromise = null;
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-pdf-view",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "A modern, modular PDF viewer component for Svelte 5. Built on PDF.js with TypeScript support",
5
5
  "author": "Louis Li",
6
6
  "license": "Apache-2.0",