@edgepdf/viewer-react 0.0.31 → 0.0.33

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
@@ -12,7 +12,7 @@ pnpm add @edgepdf/viewer-react @edgepdf/viewer-js
12
12
  yarn add @edgepdf/viewer-react @edgepdf/viewer-js
13
13
  ```
14
14
 
15
- ## Usage
15
+ ## Quick Start
16
16
 
17
17
  ### Next.js
18
18
 
@@ -98,6 +98,7 @@ import {
98
98
  EdgePDFViewer,
99
99
  useMarkers,
100
100
  useViewer,
101
+ useZoom,
101
102
  } from '@edgepdf/viewer-react';
102
103
  import type { ViewerConfig } from '@edgepdf/types';
103
104
 
@@ -113,16 +114,17 @@ const config: ViewerConfig = {
113
114
  };
114
115
 
115
116
  function MarkerPanel() {
116
- const { markers } = useMarkers();
117
+ const { markers, addMarker, removeMarker } = useMarkers();
117
118
  const { viewer, isInitialized } = useViewer();
118
-
119
- // You can interact with viewer + markers here,
120
- // independent of where <EdgePDFViewer /> is rendered.
119
+ const { zoomIn, zoomOut, currentZoom } = useZoom();
121
120
 
122
121
  return (
123
122
  <div>
124
123
  <div>Initialized: {isInitialized ? 'yes' : 'no'}</div>
125
124
  <div>Total markers: {markers.length}</div>
125
+ <div>Current zoom: {currentZoom}</div>
126
+ <button onClick={zoomIn}>Zoom In</button>
127
+ <button onClick={zoomOut}>Zoom Out</button>
126
128
  </div>
127
129
  );
128
130
  }
@@ -130,20 +132,577 @@ function MarkerPanel() {
130
132
  function App() {
131
133
  return (
132
134
  <ViewerProvider>
133
- {/* This can be on one route/screen */}
134
135
  <MarkerPanel />
135
-
136
- {/* And EdgePDFViewer can be on another */}
137
136
  <EdgePDFViewer config={config} />
138
137
  </ViewerProvider>
139
138
  );
140
139
  }
141
140
  ```
142
141
 
143
- ## Building
142
+ ## API Reference
143
+
144
+ ### Components
145
+
146
+ #### `<ViewerProvider>`
147
+
148
+ Provides viewer context to child components. Must wrap all components that use viewer hooks.
149
+
150
+ **Props:**
151
+
152
+ ```typescript
153
+ interface ViewerProviderProps {
154
+ children: ReactNode;
155
+ }
156
+ ```
157
+
158
+ **Example:**
159
+
160
+ ```tsx
161
+ <ViewerProvider>
162
+ <App />
163
+ </ViewerProvider>
164
+ ```
165
+
166
+ ---
167
+
168
+ #### `<EdgePDFViewer>`
169
+
170
+ Main React component for the EdgePDF viewer. Must be used within a `ViewerProvider`.
171
+
172
+ **Props:**
173
+
174
+ ```typescript
175
+ interface EdgePDFViewerProps {
176
+ /** Viewer configuration */
177
+ config: ViewerConfig;
178
+ /** Optional map options */
179
+ mapOptions?: MapOptions;
180
+ /** Optional className for the container */
181
+ className?: string;
182
+ /** Optional style for the container */
183
+ style?: React.CSSProperties;
184
+ /** Show zoom controls (default: true) */
185
+ showZoomControls?: boolean;
186
+ /** Zoom controls position (only used if showZoomControls is true) */
187
+ zoomControlsPosition?:
188
+ | 'top-left'
189
+ | 'top-right'
190
+ | 'bottom-left'
191
+ | 'bottom-right';
192
+ /** Show zoom level in zoom controls (only used if showZoomControls is true) */
193
+ showZoomLevel?: boolean;
194
+ /** Enable annotation functionality (default: true) */
195
+ enableAnnotation?: boolean;
196
+ /** Show edit button in marker action controls (default: true) */
197
+ showEditButton?: boolean;
198
+ /** Show delete button in marker action controls (default: true) */
199
+ showDeleteButton?: boolean;
200
+ /** Default zoom level for initial view */
201
+ defaultZoomLevel?: number;
202
+ /** Callback when markers/pins are updated */
203
+ onPinsUpdate?: (pins: Marker[]) => void;
204
+ /** Callback when a marker is clicked */
205
+ onMarkerClick?: (marker: Marker) => void;
206
+ /** Callback when a marker is updated */
207
+ onMarkerUpdate?: (marker: Marker) => void;
208
+ /** Callback when a marker is deleted */
209
+ onMarkerDelete?: (marker: Marker) => void;
210
+ /** Callback when a marker is added (created by user tap/click) */
211
+ onMarkerAdd?: (marker: Marker) => void;
212
+ /** Default pins to preload (uses internal import) */
213
+ defaultPins?: Marker[] | MarkerData;
214
+ /** Children components */
215
+ children?: ReactNode;
216
+ }
217
+ ```
218
+
219
+ **Example:**
220
+
221
+ ```tsx
222
+ <EdgePDFViewer
223
+ config={config}
224
+ showZoomControls={true}
225
+ zoomControlsPosition="top-right"
226
+ enableAnnotation={true}
227
+ onMarkerClick={(marker) => console.log('Marker clicked:', marker)}
228
+ onPinsUpdate={(pins) => console.log('Pins updated:', pins)}
229
+ />
230
+ ```
231
+
232
+ ---
233
+
234
+ #### `<ZoomControls>`
235
+
236
+ React component for zoom controls. Uses the JS library's zoom controls internally.
237
+
238
+ **Props:**
239
+
240
+ ```typescript
241
+ interface ZoomControlsProps {
242
+ /** Optional className for the container */
243
+ className?: string;
244
+ /** Optional style for the container */
245
+ style?: React.CSSProperties;
246
+ /** Position of the controls */
247
+ position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
248
+ /** Show zoom level display */
249
+ showZoomLevel?: boolean;
250
+ }
251
+ ```
252
+
253
+ **Example:**
254
+
255
+ ```tsx
256
+ <EdgePDFViewer config={config}>
257
+ <ZoomControls position="top-right" showZoomLevel={true} />
258
+ </EdgePDFViewer>
259
+ ```
260
+
261
+ ---
262
+
263
+ ### Hooks
264
+
265
+ #### `useViewer()`
266
+
267
+ Hook to access and interact with the viewer instance.
268
+
269
+ **Returns:**
270
+
271
+ ```typescript
272
+ interface UseViewerReturn {
273
+ /** Viewer instance */
274
+ viewer: EdgePdfViewer | null;
275
+ /** Whether viewer is initialized */
276
+ isInitialized: boolean;
277
+ /** Get the Leaflet map instance */
278
+ getMap: () => L.Map | null;
279
+ /** Check if viewer is initialized */
280
+ checkInitialized: () => boolean;
281
+ }
282
+ ```
283
+
284
+ **Example:**
285
+
286
+ ```tsx
287
+ function MyComponent() {
288
+ const { viewer, isInitialized, getMap } = useViewer();
289
+
290
+ useEffect(() => {
291
+ if (isInitialized && viewer) {
292
+ const map = getMap();
293
+ // Use map...
294
+ }
295
+ }, [isInitialized, viewer, getMap]);
296
+
297
+ return <div>Viewer is {isInitialized ? 'ready' : 'loading'}</div>;
298
+ }
299
+ ```
300
+
301
+ ---
302
+
303
+ #### `useMarkers()`
304
+
305
+ Hook to manage markers in the viewer.
306
+
307
+ **Returns:**
308
+
309
+ ```typescript
310
+ interface UseMarkersReturn {
311
+ /** Current markers */
312
+ markers: Marker[];
313
+ /** Add a new marker */
314
+ addMarker: (options: CreateMarkerOptions) => Marker | null;
315
+ /** Remove a marker by ID */
316
+ removeMarker: (id: string) => boolean;
317
+ /** Update a marker */
318
+ updateMarker: (id: string, updates: Partial<Marker>) => boolean;
319
+ /** Get a marker by ID */
320
+ getMarker: (id: string) => Marker | null;
321
+ /** Get all markers */
322
+ getAllMarkers: () => Marker[];
323
+ /** Export markers as JSON */
324
+ exportMarkers: () => MarkerData;
325
+ /** Import markers from JSON */
326
+ importMarkers: (data: MarkerData) => boolean;
327
+ /** Clear all markers */
328
+ clearMarkers: () => void;
329
+ /** Focus on a marker by panning and zooming to its position */
330
+ focusMarker: (
331
+ markerOrId: string | Marker,
332
+ options?: {
333
+ zoom?: number;
334
+ animate?: boolean;
335
+ duration?: number;
336
+ }
337
+ ) => boolean;
338
+ }
339
+ ```
340
+
341
+ **Example:**
342
+
343
+ ```tsx
344
+ function MarkerControls() {
345
+ const {
346
+ markers,
347
+ addMarker,
348
+ removeMarker,
349
+ updateMarker,
350
+ exportMarkers,
351
+ importMarkers,
352
+ clearMarkers,
353
+ focusMarker,
354
+ } = useMarkers();
355
+
356
+ const handleAddMarker = () => {
357
+ addMarker({
358
+ position: [100, 200],
359
+ x: 100,
360
+ y: 200,
361
+ zoom: 1,
362
+ label: 'New Marker',
363
+ });
364
+ };
365
+
366
+ const handleExport = () => {
367
+ const data = exportMarkers();
368
+ console.log(JSON.stringify(data, null, 2));
369
+ };
370
+
371
+ const handleFocus = (markerId: string) => {
372
+ focusMarker(markerId, { zoom: 3, animate: true });
373
+ };
374
+
375
+ return (
376
+ <div>
377
+ <button onClick={handleAddMarker}>Add Marker</button>
378
+ <button onClick={handleExport}>Export Markers</button>
379
+ <button onClick={clearMarkers}>Clear All</button>
380
+ {markers.map((marker) => (
381
+ <div key={marker.id}>
382
+ {marker.label}
383
+ <button onClick={() => removeMarker(marker.id)}>Remove</button>
384
+ <button onClick={() => handleFocus(marker.id)}>Focus</button>
385
+ </div>
386
+ ))}
387
+ </div>
388
+ );
389
+ }
390
+ ```
391
+
392
+ **Methods:**
393
+
394
+ - **`addMarker(options: CreateMarkerOptions): Marker | null`** - Creates a new marker. Returns the created marker or `null` if viewer is not initialized.
395
+
396
+ - **`removeMarker(id: string): boolean`** - Removes a marker by ID. Returns `true` if successful, `false` otherwise.
397
+
398
+ - **`updateMarker(id: string, updates: Partial<Marker>): boolean`** - Updates a marker's properties. Returns `true` if successful, `false` otherwise.
399
+
400
+ - **`getMarker(id: string): Marker | null`** - Gets a marker by ID. Returns the marker or `null` if not found.
401
+
402
+ - **`getAllMarkers(): Marker[]`** - Gets all markers. Returns an array of all markers.
403
+
404
+ - **`exportMarkers(): MarkerData`** - Exports all markers as JSON data. Returns a `MarkerData` object.
405
+
406
+ - **`importMarkers(data: MarkerData): boolean`** - Imports markers from JSON data. Returns `true` if successful, `false` otherwise.
407
+
408
+ - **`clearMarkers(): void`** - Removes all markers.
409
+
410
+ - **`focusMarker(markerOrId: string | Marker, options?: FocusOptions): boolean`** - Focuses on a marker by panning and zooming. Returns `true` if successful, `false` otherwise.
411
+
412
+ ---
413
+
414
+ #### `useZoom()`
415
+
416
+ Hook to manage zoom in the viewer.
417
+
418
+ **Returns:**
419
+
420
+ ```typescript
421
+ interface UseZoomReturn {
422
+ /** Current zoom state */
423
+ zoomState: ZoomState | null;
424
+ /** Current zoom level */
425
+ currentZoom: number;
426
+ /** Minimum zoom level */
427
+ minZoom: number;
428
+ /** Maximum zoom level */
429
+ maxZoom: number;
430
+ /** Zoom in */
431
+ zoomIn: () => void;
432
+ /** Zoom out */
433
+ zoomOut: () => void;
434
+ /** Set zoom level */
435
+ setZoom: (zoom: number) => void;
436
+ /** Check if can zoom in */
437
+ canZoomIn: () => boolean;
438
+ /** Check if can zoom out */
439
+ canZoomOut: () => boolean;
440
+ }
441
+ ```
442
+
443
+ **Example:**
444
+
445
+ ```tsx
446
+ function ZoomControls() {
447
+ const {
448
+ zoomIn,
449
+ zoomOut,
450
+ setZoom,
451
+ currentZoom,
452
+ minZoom,
453
+ maxZoom,
454
+ canZoomIn,
455
+ canZoomOut,
456
+ } = useZoom();
457
+
458
+ return (
459
+ <div>
460
+ <button onClick={zoomOut} disabled={!canZoomOut()}>
461
+ Zoom Out
462
+ </button>
463
+ <span>
464
+ Zoom: {currentZoom} ({minZoom}-{maxZoom})
465
+ </span>
466
+ <button onClick={zoomIn} disabled={!canZoomIn()}>
467
+ Zoom In
468
+ </button>
469
+ <button onClick={() => setZoom(2)}>Set Zoom to 2</button>
470
+ </div>
471
+ );
472
+ }
473
+ ```
474
+
475
+ **Methods:**
476
+
477
+ - **`zoomIn(): void`** - Zooms in by one level.
478
+
479
+ - **`zoomOut(): void`** - Zooms out by one level.
480
+
481
+ - **`setZoom(zoom: number): void`** - Sets a specific zoom level.
482
+
483
+ - **`canZoomIn(): boolean`** - Checks if can zoom in. Returns `true` if current zoom is less than max zoom.
484
+
485
+ - **`canZoomOut(): boolean`** - Checks if can zoom out. Returns `true` if current zoom is greater than min zoom.
486
+
487
+ ---
488
+
489
+ #### `useViewerContext()`
490
+
491
+ Hook to access viewer context directly. Usually you should use `useViewer()`, `useMarkers()`, or `useZoom()` instead.
492
+
493
+ **Returns:**
494
+
495
+ ```typescript
496
+ interface ViewerContextValue {
497
+ /** Viewer instance */
498
+ viewer: EdgePdfViewer | null;
499
+ /** Whether viewer is initialized */
500
+ isInitialized: boolean;
501
+ /** Current markers */
502
+ markers: Marker[];
503
+ /** Current zoom state */
504
+ zoomState: ZoomState | null;
505
+ /** Internal: Function to update context value */
506
+ setContextValue: (
507
+ updates: Partial<Omit<ViewerContextValue, 'setContextValue'>>
508
+ ) => void;
509
+ }
510
+ ```
511
+
512
+ **Throws:**
513
+
514
+ - `Error` if used outside `ViewerProvider`
515
+
516
+ **Example:**
517
+
518
+ ```tsx
519
+ function MyComponent() {
520
+ const { viewer, isInitialized, markers, zoomState } = useViewerContext();
521
+ // Use context values...
522
+ }
523
+ ```
524
+
525
+ ---
526
+
527
+ ## Type Exports
528
+
529
+ The library also exports TypeScript types for convenience:
530
+
531
+ ```typescript
532
+ import type {
533
+ EdgePDFViewerProps,
534
+ ZoomControlsProps,
535
+ ViewerContextValue,
536
+ ViewerProviderProps,
537
+ UseViewerReturn,
538
+ UseMarkersReturn,
539
+ UseZoomReturn,
540
+ } from '@edgepdf/viewer-react';
541
+ ```
542
+
543
+ ## Examples
544
+
545
+ ### Basic Usage with Markers
546
+
547
+ ```tsx
548
+ import {
549
+ ViewerProvider,
550
+ EdgePDFViewer,
551
+ useMarkers,
552
+ } from '@edgepdf/viewer-react';
553
+
554
+ function MarkerList() {
555
+ const { markers, addMarker, removeMarker } = useMarkers();
556
+
557
+ return (
558
+ <div>
559
+ <h3>Markers ({markers.length})</h3>
560
+ {markers.map((marker) => (
561
+ <div key={marker.id}>
562
+ <strong>{marker.label}</strong>
563
+ <button onClick={() => removeMarker(marker.id)}>Remove</button>
564
+ </div>
565
+ ))}
566
+ </div>
567
+ );
568
+ }
569
+
570
+ function App() {
571
+ const config = {
572
+ tileUrl: 'https://example.com/tiles/{z}/{x}/{y}.png',
573
+ imageInfo: {
574
+ width: 2000,
575
+ height: 3000,
576
+ tileSize: 256,
577
+ maxZoom: 5,
578
+ minZoom: 0,
579
+ },
580
+ };
581
+
582
+ return (
583
+ <ViewerProvider>
584
+ <div style={{ display: 'flex' }}>
585
+ <div style={{ width: '300px' }}>
586
+ <MarkerList />
587
+ </div>
588
+ <div style={{ flex: 1, height: '100vh' }}>
589
+ <EdgePDFViewer config={config} />
590
+ </div>
591
+ </div>
592
+ </ViewerProvider>
593
+ );
594
+ }
595
+ ```
596
+
597
+ ### With Callbacks
598
+
599
+ ```tsx
600
+ function App() {
601
+ const config = {
602
+ tileUrl: 'https://example.com/tiles/{z}/{x}/{y}.png',
603
+ imageInfo: {
604
+ width: 2000,
605
+ height: 3000,
606
+ tileSize: 256,
607
+ maxZoom: 5,
608
+ minZoom: 0,
609
+ },
610
+ };
611
+
612
+ const handleMarkerClick = (marker: Marker) => {
613
+ console.log('Marker clicked:', marker);
614
+ };
144
615
 
145
- Run `nx build viewer-react` to build the library.
616
+ const handlePinsUpdate = (pins: Marker[]) => {
617
+ console.log('Pins updated:', pins);
618
+ // Save to backend, localStorage, etc.
619
+ };
146
620
 
147
- ## Running unit tests
621
+ return (
622
+ <ViewerProvider>
623
+ <EdgePDFViewer
624
+ config={config}
625
+ onMarkerClick={handleMarkerClick}
626
+ onPinsUpdate={handlePinsUpdate}
627
+ enableAnnotation={true}
628
+ />
629
+ </ViewerProvider>
630
+ );
631
+ }
632
+ ```
148
633
 
149
- Run `nx test viewer-react` to execute the unit tests via [Jest](https://jestjs.io).
634
+ ### Custom Zoom Controls
635
+
636
+ ```tsx
637
+ function CustomZoomControls() {
638
+ const { zoomIn, zoomOut, currentZoom, canZoomIn, canZoomOut } = useZoom();
639
+
640
+ return (
641
+ <div style={{ position: 'absolute', top: 10, right: 10, zIndex: 1000 }}>
642
+ <button onClick={zoomOut} disabled={!canZoomOut()}>
643
+
644
+ </button>
645
+ <span>{currentZoom}</span>
646
+ <button onClick={zoomIn} disabled={!canZoomIn()}>
647
+ +
648
+ </button>
649
+ </div>
650
+ );
651
+ }
652
+
653
+ function App() {
654
+ return (
655
+ <ViewerProvider>
656
+ <EdgePDFViewer config={config} showZoomControls={false}>
657
+ <CustomZoomControls />
658
+ </EdgePDFViewer>
659
+ </ViewerProvider>
660
+ );
661
+ }
662
+ ```
663
+
664
+ ### Import/Export Markers
665
+
666
+ ```tsx
667
+ function MarkerManager() {
668
+ const { markers, exportMarkers, importMarkers, clearMarkers } = useMarkers();
669
+
670
+ const handleExport = () => {
671
+ const data = exportMarkers();
672
+ const blob = new Blob([JSON.stringify(data, null, 2)], {
673
+ type: 'application/json',
674
+ });
675
+ const url = URL.createObjectURL(blob);
676
+ const a = document.createElement('a');
677
+ a.href = url;
678
+ a.download = 'markers.json';
679
+ a.click();
680
+ URL.revokeObjectURL(url);
681
+ };
682
+
683
+ const handleImport = (event: React.ChangeEvent<HTMLInputElement>) => {
684
+ const file = event.target.files?.[0];
685
+ if (!file) return;
686
+
687
+ const reader = new FileReader();
688
+ reader.onload = (e) => {
689
+ try {
690
+ const data = JSON.parse(e.target?.result as string);
691
+ importMarkers(data);
692
+ } catch (error) {
693
+ console.error('Failed to import markers:', error);
694
+ }
695
+ };
696
+ reader.readAsText(file);
697
+ };
698
+
699
+ return (
700
+ <div>
701
+ <button onClick={handleExport}>Export Markers</button>
702
+ <input type="file" accept=".json" onChange={handleImport} />
703
+ <button onClick={clearMarkers}>Clear All</button>
704
+ <div>Total markers: {markers.length}</div>
705
+ </div>
706
+ );
707
+ }
708
+ ```