@edgepdf/viewer-react 0.0.32 → 0.0.34

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,596 @@ 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
+ offsetLeft?: number;
337
+ offsetRight?: number;
338
+ offsetTop?: number;
339
+ offsetBottom?: number;
340
+ }
341
+ ) => boolean;
342
+ }
343
+ ```
344
+
345
+ **Example:**
346
+
347
+ ```tsx
348
+ function MarkerControls() {
349
+ const {
350
+ markers,
351
+ addMarker,
352
+ removeMarker,
353
+ updateMarker,
354
+ exportMarkers,
355
+ importMarkers,
356
+ clearMarkers,
357
+ focusMarker,
358
+ } = useMarkers();
359
+
360
+ const handleAddMarker = () => {
361
+ addMarker({
362
+ position: [100, 200],
363
+ x: 100,
364
+ y: 200,
365
+ zoom: 1,
366
+ label: 'New Marker',
367
+ });
368
+ };
369
+
370
+ const handleExport = () => {
371
+ const data = exportMarkers();
372
+ console.log(JSON.stringify(data, null, 2));
373
+ };
374
+
375
+ const handleFocus = (markerId: string) => {
376
+ focusMarker(markerId, { zoom: 3, animate: true });
377
+ };
378
+
379
+ return (
380
+ <div>
381
+ <button onClick={handleAddMarker}>Add Marker</button>
382
+ <button onClick={handleExport}>Export Markers</button>
383
+ <button onClick={clearMarkers}>Clear All</button>
384
+ {markers.map((marker) => (
385
+ <div key={marker.id}>
386
+ {marker.label}
387
+ <button onClick={() => removeMarker(marker.id)}>Remove</button>
388
+ <button onClick={() => handleFocus(marker.id)}>Focus</button>
389
+ </div>
390
+ ))}
391
+ </div>
392
+ );
393
+ }
394
+ ```
395
+
396
+ **Methods:**
397
+
398
+ - **`addMarker(options: CreateMarkerOptions): Marker | null`** - Creates a new marker. Returns the created marker or `null` if viewer is not initialized.
399
+
400
+ - **`removeMarker(id: string): boolean`** - Removes a marker by ID. Returns `true` if successful, `false` otherwise.
401
+
402
+ - **`updateMarker(id: string, updates: Partial<Marker>): boolean`** - Updates a marker's properties. Returns `true` if successful, `false` otherwise.
403
+
404
+ - **`getMarker(id: string): Marker | null`** - Gets a marker by ID. Returns the marker or `null` if not found.
405
+
406
+ - **`getAllMarkers(): Marker[]`** - Gets all markers. Returns an array of all markers.
407
+
408
+ - **`exportMarkers(): MarkerData`** - Exports all markers as JSON data. Returns a `MarkerData` object.
409
+
410
+ - **`importMarkers(data: MarkerData): boolean`** - Imports markers from JSON data. Returns `true` if successful, `false` otherwise.
411
+
412
+ - **`clearMarkers(): void`** - Removes all markers.
413
+
414
+ - **`focusMarker(markerOrId: string | Marker, options?: FocusOptions): boolean`** - Focuses on a marker by panning and zooming. Returns `true` if successful, `false` otherwise.
415
+
416
+ **Options:**
417
+ - `zoom?: number` - Target zoom level (uses marker's zoom or default if not provided)
418
+ - `animate?: boolean` - Whether to animate the transition (default: true)
419
+ - `duration?: number` - Animation duration in seconds (default: 0.5)
420
+ - `offsetLeft?: number` - Pixel offset to move focus marker to the left (default: 0)
421
+ - `offsetRight?: number` - Pixel offset to move focus marker to the right (default: 0)
422
+ - `offsetTop?: number` - Pixel offset to move focus marker upward (default: 0)
423
+ - `offsetBottom?: number` - Pixel offset to move focus marker downward (default: 0)
424
+
425
+ **Example:**
426
+ ```tsx
427
+ // Focus with offset to account for overlay
428
+ focusMarker(markerId, { offsetLeft: 100, offsetTop: 50 });
429
+ ```
430
+
431
+ ---
432
+
433
+ #### `useZoom()`
434
+
435
+ Hook to manage zoom in the viewer.
436
+
437
+ **Returns:**
438
+
439
+ ```typescript
440
+ interface UseZoomReturn {
441
+ /** Current zoom state */
442
+ zoomState: ZoomState | null;
443
+ /** Current zoom level */
444
+ currentZoom: number;
445
+ /** Minimum zoom level */
446
+ minZoom: number;
447
+ /** Maximum zoom level */
448
+ maxZoom: number;
449
+ /** Zoom in */
450
+ zoomIn: () => void;
451
+ /** Zoom out */
452
+ zoomOut: () => void;
453
+ /** Set zoom level */
454
+ setZoom: (zoom: number) => void;
455
+ /** Check if can zoom in */
456
+ canZoomIn: () => boolean;
457
+ /** Check if can zoom out */
458
+ canZoomOut: () => boolean;
459
+ }
460
+ ```
461
+
462
+ **Example:**
463
+
464
+ ```tsx
465
+ function ZoomControls() {
466
+ const {
467
+ zoomIn,
468
+ zoomOut,
469
+ setZoom,
470
+ currentZoom,
471
+ minZoom,
472
+ maxZoom,
473
+ canZoomIn,
474
+ canZoomOut,
475
+ } = useZoom();
476
+
477
+ return (
478
+ <div>
479
+ <button onClick={zoomOut} disabled={!canZoomOut()}>
480
+ Zoom Out
481
+ </button>
482
+ <span>
483
+ Zoom: {currentZoom} ({minZoom}-{maxZoom})
484
+ </span>
485
+ <button onClick={zoomIn} disabled={!canZoomIn()}>
486
+ Zoom In
487
+ </button>
488
+ <button onClick={() => setZoom(2)}>Set Zoom to 2</button>
489
+ </div>
490
+ );
491
+ }
492
+ ```
493
+
494
+ **Methods:**
495
+
496
+ - **`zoomIn(): void`** - Zooms in by one level.
497
+
498
+ - **`zoomOut(): void`** - Zooms out by one level.
499
+
500
+ - **`setZoom(zoom: number): void`** - Sets a specific zoom level.
501
+
502
+ - **`canZoomIn(): boolean`** - Checks if can zoom in. Returns `true` if current zoom is less than max zoom.
503
+
504
+ - **`canZoomOut(): boolean`** - Checks if can zoom out. Returns `true` if current zoom is greater than min zoom.
505
+
506
+ ---
507
+
508
+ #### `useViewerContext()`
509
+
510
+ Hook to access viewer context directly. Usually you should use `useViewer()`, `useMarkers()`, or `useZoom()` instead.
511
+
512
+ **Returns:**
513
+
514
+ ```typescript
515
+ interface ViewerContextValue {
516
+ /** Viewer instance */
517
+ viewer: EdgePdfViewer | null;
518
+ /** Whether viewer is initialized */
519
+ isInitialized: boolean;
520
+ /** Current markers */
521
+ markers: Marker[];
522
+ /** Current zoom state */
523
+ zoomState: ZoomState | null;
524
+ /** Internal: Function to update context value */
525
+ setContextValue: (
526
+ updates: Partial<Omit<ViewerContextValue, 'setContextValue'>>
527
+ ) => void;
528
+ }
529
+ ```
530
+
531
+ **Throws:**
532
+
533
+ - `Error` if used outside `ViewerProvider`
534
+
535
+ **Example:**
536
+
537
+ ```tsx
538
+ function MyComponent() {
539
+ const { viewer, isInitialized, markers, zoomState } = useViewerContext();
540
+ // Use context values...
541
+ }
542
+ ```
543
+
544
+ ---
545
+
546
+ ## Type Exports
547
+
548
+ The library also exports TypeScript types for convenience:
549
+
550
+ ```typescript
551
+ import type {
552
+ EdgePDFViewerProps,
553
+ ZoomControlsProps,
554
+ ViewerContextValue,
555
+ ViewerProviderProps,
556
+ UseViewerReturn,
557
+ UseMarkersReturn,
558
+ UseZoomReturn,
559
+ } from '@edgepdf/viewer-react';
560
+ ```
561
+
562
+ ## Examples
563
+
564
+ ### Basic Usage with Markers
565
+
566
+ ```tsx
567
+ import {
568
+ ViewerProvider,
569
+ EdgePDFViewer,
570
+ useMarkers,
571
+ } from '@edgepdf/viewer-react';
572
+
573
+ function MarkerList() {
574
+ const { markers, addMarker, removeMarker } = useMarkers();
575
+
576
+ return (
577
+ <div>
578
+ <h3>Markers ({markers.length})</h3>
579
+ {markers.map((marker) => (
580
+ <div key={marker.id}>
581
+ <strong>{marker.label}</strong>
582
+ <button onClick={() => removeMarker(marker.id)}>Remove</button>
583
+ </div>
584
+ ))}
585
+ </div>
586
+ );
587
+ }
588
+
589
+ function App() {
590
+ const config = {
591
+ tileUrl: 'https://example.com/tiles/{z}/{x}/{y}.png',
592
+ imageInfo: {
593
+ width: 2000,
594
+ height: 3000,
595
+ tileSize: 256,
596
+ maxZoom: 5,
597
+ minZoom: 0,
598
+ },
599
+ };
600
+
601
+ return (
602
+ <ViewerProvider>
603
+ <div style={{ display: 'flex' }}>
604
+ <div style={{ width: '300px' }}>
605
+ <MarkerList />
606
+ </div>
607
+ <div style={{ flex: 1, height: '100vh' }}>
608
+ <EdgePDFViewer config={config} />
609
+ </div>
610
+ </div>
611
+ </ViewerProvider>
612
+ );
613
+ }
614
+ ```
615
+
616
+ ### With Callbacks
617
+
618
+ ```tsx
619
+ function App() {
620
+ const config = {
621
+ tileUrl: 'https://example.com/tiles/{z}/{x}/{y}.png',
622
+ imageInfo: {
623
+ width: 2000,
624
+ height: 3000,
625
+ tileSize: 256,
626
+ maxZoom: 5,
627
+ minZoom: 0,
628
+ },
629
+ };
630
+
631
+ const handleMarkerClick = (marker: Marker) => {
632
+ console.log('Marker clicked:', marker);
633
+ };
144
634
 
145
- Run `nx build viewer-react` to build the library.
635
+ const handlePinsUpdate = (pins: Marker[]) => {
636
+ console.log('Pins updated:', pins);
637
+ // Save to backend, localStorage, etc.
638
+ };
146
639
 
147
- ## Running unit tests
640
+ return (
641
+ <ViewerProvider>
642
+ <EdgePDFViewer
643
+ config={config}
644
+ onMarkerClick={handleMarkerClick}
645
+ onPinsUpdate={handlePinsUpdate}
646
+ enableAnnotation={true}
647
+ />
648
+ </ViewerProvider>
649
+ );
650
+ }
651
+ ```
148
652
 
149
- Run `nx test viewer-react` to execute the unit tests via [Jest](https://jestjs.io).
653
+ ### Custom Zoom Controls
654
+
655
+ ```tsx
656
+ function CustomZoomControls() {
657
+ const { zoomIn, zoomOut, currentZoom, canZoomIn, canZoomOut } = useZoom();
658
+
659
+ return (
660
+ <div style={{ position: 'absolute', top: 10, right: 10, zIndex: 1000 }}>
661
+ <button onClick={zoomOut} disabled={!canZoomOut()}>
662
+
663
+ </button>
664
+ <span>{currentZoom}</span>
665
+ <button onClick={zoomIn} disabled={!canZoomIn()}>
666
+ +
667
+ </button>
668
+ </div>
669
+ );
670
+ }
671
+
672
+ function App() {
673
+ return (
674
+ <ViewerProvider>
675
+ <EdgePDFViewer config={config} showZoomControls={false}>
676
+ <CustomZoomControls />
677
+ </EdgePDFViewer>
678
+ </ViewerProvider>
679
+ );
680
+ }
681
+ ```
682
+
683
+ ### Import/Export Markers
684
+
685
+ ```tsx
686
+ function MarkerManager() {
687
+ const { markers, exportMarkers, importMarkers, clearMarkers } = useMarkers();
688
+
689
+ const handleExport = () => {
690
+ const data = exportMarkers();
691
+ const blob = new Blob([JSON.stringify(data, null, 2)], {
692
+ type: 'application/json',
693
+ });
694
+ const url = URL.createObjectURL(blob);
695
+ const a = document.createElement('a');
696
+ a.href = url;
697
+ a.download = 'markers.json';
698
+ a.click();
699
+ URL.revokeObjectURL(url);
700
+ };
701
+
702
+ const handleImport = (event: React.ChangeEvent<HTMLInputElement>) => {
703
+ const file = event.target.files?.[0];
704
+ if (!file) return;
705
+
706
+ const reader = new FileReader();
707
+ reader.onload = (e) => {
708
+ try {
709
+ const data = JSON.parse(e.target?.result as string);
710
+ importMarkers(data);
711
+ } catch (error) {
712
+ console.error('Failed to import markers:', error);
713
+ }
714
+ };
715
+ reader.readAsText(file);
716
+ };
717
+
718
+ return (
719
+ <div>
720
+ <button onClick={handleExport}>Export Markers</button>
721
+ <input type="file" accept=".json" onChange={handleImport} />
722
+ <button onClick={clearMarkers}>Clear All</button>
723
+ <div>Total markers: {markers.length}</div>
724
+ </div>
725
+ );
726
+ }
727
+ ```
package/dist/index.css CHANGED
@@ -1,3 +1,5 @@
1
+ @import "https://fonts.googleapis.com/css2?family=Lato:wght@300;400;500;600;700&display=swap";
2
+
1
3
  /* packages/viewer-js/dist/index.css */
2
4
  .leaflet-pane,
3
5
  .leaflet-tile,
@@ -671,6 +673,7 @@ svg.leaflet-image-layer.leaflet-interactive path {
671
673
  display: flex;
672
674
  align-items: center;
673
675
  justify-content: center;
676
+ font-family: "Lato", sans-serif;
674
677
  }
675
678
  .edgepdf-marker-edit-popup-overlay {
676
679
  position: absolute;
@@ -678,14 +681,15 @@ svg.leaflet-image-layer.leaflet-interactive path {
678
681
  left: 0;
679
682
  right: 0;
680
683
  bottom: 0;
681
- background-color: rgba(0, 0, 0, 0.5);
684
+ background-color: rgba(0, 0, 0, 0.7);
682
685
  cursor: pointer;
683
686
  }
684
687
  .edgepdf-marker-edit-popup-content {
685
688
  position: relative;
686
- background-color: #fff;
689
+ background-color: #1a1a1a;
687
690
  border-radius: 8px;
688
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
691
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
692
+ border: 1px solid #333;
689
693
  width: 90%;
690
694
  max-width: 500px;
691
695
  max-height: 90vh;
@@ -697,20 +701,20 @@ svg.leaflet-image-layer.leaflet-interactive path {
697
701
  justify-content: space-between;
698
702
  align-items: center;
699
703
  padding: 16px 20px;
700
- border-bottom: 1px solid #e0e0e0;
704
+ border-bottom: 1px solid #333;
701
705
  }
702
706
  .edgepdf-marker-edit-popup-header h3 {
703
707
  margin: 0;
704
708
  font-size: 18px;
705
709
  font-weight: 600;
706
- color: #333;
710
+ color: #fff;
707
711
  }
708
712
  .edgepdf-marker-edit-popup-close {
709
713
  background: none;
710
714
  border: none;
711
715
  font-size: 24px;
712
716
  line-height: 1;
713
- color: #666;
717
+ color: #fff;
714
718
  cursor: pointer;
715
719
  padding: 0;
716
720
  width: 32px;
@@ -722,8 +726,8 @@ svg.leaflet-image-layer.leaflet-interactive path {
722
726
  transition: background-color 0.2s ease;
723
727
  }
724
728
  .edgepdf-marker-edit-popup-close:hover {
725
- background-color: #f0f0f0;
726
- color: #333;
729
+ background-color: #333;
730
+ color: #ffa500;
727
731
  }
728
732
  .edgepdf-marker-edit-popup-body {
729
733
  padding: 20px;
@@ -739,52 +743,57 @@ svg.leaflet-image-layer.leaflet-interactive path {
739
743
  margin-bottom: 8px;
740
744
  font-size: 14px;
741
745
  font-weight: 500;
742
- color: #333;
746
+ color: #fff;
743
747
  }
744
748
  .edgepdf-marker-edit-input {
745
749
  width: 100%;
746
750
  padding: 10px 12px;
747
- border: 1px solid #ccc;
751
+ border: 1px solid #444;
748
752
  border-radius: 4px;
749
753
  font-size: 14px;
754
+ background-color: #2a2a2a;
755
+ color: #fff;
750
756
  box-sizing: border-box;
751
757
  transition: border-color 0.2s ease;
752
758
  }
753
759
  .edgepdf-marker-edit-input:focus {
754
760
  outline: none;
755
- border-color: #1976d2;
756
- box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.1);
761
+ border-color: #ffa500;
762
+ box-shadow: 0 0 0 2px rgba(255, 165, 0, 0.2);
763
+ }
764
+ .edgepdf-marker-edit-input::placeholder {
765
+ color: #888;
757
766
  }
758
767
  .edgepdf-marker-edit-popup-footer {
759
768
  display: flex;
760
769
  justify-content: flex-end;
761
770
  gap: 12px;
762
771
  padding: 16px 20px;
763
- border-top: 1px solid #e0e0e0;
772
+ border-top: 1px solid #333;
764
773
  }
765
774
  .edgepdf-marker-edit-button {
766
775
  padding: 10px 20px;
767
- border: 1px solid #ccc;
776
+ border: 1px solid #444;
768
777
  border-radius: 4px;
769
- background-color: #fff;
770
- color: #333;
778
+ background-color: #2a2a2a;
779
+ color: #fff;
771
780
  cursor: pointer;
772
781
  font-size: 14px;
773
782
  font-weight: 500;
774
783
  transition: all 0.2s ease;
775
784
  }
776
785
  .edgepdf-marker-edit-button-cancel:hover {
777
- background-color: #f0f0f0;
778
- border-color: #999;
786
+ background-color: #333;
787
+ border-color: #555;
779
788
  }
780
789
  .edgepdf-marker-edit-button-save {
781
- background-color: #1976d2;
782
- color: #fff;
783
- border-color: #1976d2;
790
+ background-color: #ffa500;
791
+ color: #000;
792
+ border-color: #ffa500;
784
793
  }
785
794
  .edgepdf-marker-edit-button-save:hover {
786
- background-color: #1565c0;
787
- border-color: #1565c0;
795
+ background-color: #ffd700;
796
+ border-color: #ffd700;
788
797
  }
789
798
  .edgepdf-marker-delete-popup {
790
799
  position: fixed;
@@ -796,6 +805,7 @@ svg.leaflet-image-layer.leaflet-interactive path {
796
805
  display: flex;
797
806
  align-items: center;
798
807
  justify-content: center;
808
+ font-family: "Lato", sans-serif;
799
809
  }
800
810
  .edgepdf-marker-delete-popup-overlay {
801
811
  position: absolute;
@@ -803,14 +813,15 @@ svg.leaflet-image-layer.leaflet-interactive path {
803
813
  left: 0;
804
814
  right: 0;
805
815
  bottom: 0;
806
- background-color: rgba(0, 0, 0, 0.5);
816
+ background-color: rgba(0, 0, 0, 0.7);
807
817
  cursor: pointer;
808
818
  }
809
819
  .edgepdf-marker-delete-popup-content {
810
820
  position: relative;
811
- background-color: #fff;
821
+ background-color: #1a1a1a;
812
822
  border-radius: 8px;
813
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
823
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
824
+ border: 1px solid #333;
814
825
  width: 90%;
815
826
  max-width: 500px;
816
827
  max-height: 90vh;
@@ -822,20 +833,20 @@ svg.leaflet-image-layer.leaflet-interactive path {
822
833
  justify-content: space-between;
823
834
  align-items: center;
824
835
  padding: 16px 20px;
825
- border-bottom: 1px solid #e0e0e0;
836
+ border-bottom: 1px solid #333;
826
837
  }
827
838
  .edgepdf-marker-delete-popup-header h3 {
828
839
  margin: 0;
829
840
  font-size: 18px;
830
841
  font-weight: 600;
831
- color: #333;
842
+ color: #fff;
832
843
  }
833
844
  .edgepdf-marker-delete-popup-close {
834
845
  background: none;
835
846
  border: none;
836
847
  font-size: 24px;
837
848
  line-height: 1;
838
- color: #666;
849
+ color: #fff;
839
850
  cursor: pointer;
840
851
  padding: 0;
841
852
  width: 32px;
@@ -847,8 +858,8 @@ svg.leaflet-image-layer.leaflet-interactive path {
847
858
  transition: background-color 0.2s ease;
848
859
  }
849
860
  .edgepdf-marker-delete-popup-close:hover {
850
- background-color: #f0f0f0;
851
- color: #333;
861
+ background-color: #333;
862
+ color: #ffa500;
852
863
  }
853
864
  .edgepdf-marker-delete-popup-body {
854
865
  padding: 20px;
@@ -856,43 +867,43 @@ svg.leaflet-image-layer.leaflet-interactive path {
856
867
  .edgepdf-marker-delete-popup-body p {
857
868
  margin: 0;
858
869
  font-size: 14px;
859
- color: #333;
870
+ color: #fff;
860
871
  line-height: 1.5;
861
872
  }
862
873
  .edgepdf-marker-delete-popup-body strong {
863
874
  font-weight: 600;
864
- color: #d32f2f;
875
+ color: #ffa500;
865
876
  }
866
877
  .edgepdf-marker-delete-popup-footer {
867
878
  display: flex;
868
879
  justify-content: flex-end;
869
880
  gap: 12px;
870
881
  padding: 16px 20px;
871
- border-top: 1px solid #e0e0e0;
882
+ border-top: 1px solid #333;
872
883
  }
873
884
  .edgepdf-marker-delete-button {
874
885
  padding: 10px 20px;
875
- border: 1px solid #ccc;
886
+ border: 1px solid #444;
876
887
  border-radius: 4px;
877
- background-color: #fff;
878
- color: #333;
888
+ background-color: #2a2a2a;
889
+ color: #fff;
879
890
  cursor: pointer;
880
891
  font-size: 14px;
881
892
  font-weight: 500;
882
893
  transition: all 0.2s ease;
883
894
  }
884
895
  .edgepdf-marker-delete-button-cancel:hover {
885
- background-color: #f0f0f0;
886
- border-color: #999;
896
+ background-color: #333;
897
+ border-color: #555;
887
898
  }
888
899
  .edgepdf-marker-delete-button-confirm {
889
- background-color: #d32f2f;
890
- color: #fff;
891
- border-color: #d32f2f;
900
+ background-color: #ffa500;
901
+ color: #000;
902
+ border-color: #ffa500;
892
903
  }
893
904
  .edgepdf-marker-delete-button-confirm:hover {
894
- background-color: #b71c1c;
895
- border-color: #b71c1c;
905
+ background-color: #ffd700;
906
+ border-color: #ffd700;
896
907
  }
897
908
  .edgepdf-marker-icon-overlay-container {
898
909
  background: transparent !important;
package/dist/index.js CHANGED
@@ -9579,6 +9579,12 @@ var require_leaflet_src = __commonJS({
9579
9579
  var import_leaflet4 = __toESM(require_leaflet_src(), 1);
9580
9580
  var import_leaflet = __toESM(require_leaflet_src(), 1);
9581
9581
  var MAX_ZOOM_LEVEL = 4;
9582
+ var MARKER_SPEC = {
9583
+ WIDTH: 30,
9584
+ HEIGHT: 40,
9585
+ ANCHOR_X: 16,
9586
+ ANCHOR_Y: 36
9587
+ };
9582
9588
  function calculateTileCount(imageSize, tileSize, zoom) {
9583
9589
  if (imageSize <= 0 || tileSize <= 0 || zoom < 0) {
9584
9590
  return 0;
@@ -11254,6 +11260,98 @@ var MarkerManager = class {
11254
11260
  this.updateMarkerIcon(id, iconType);
11255
11261
  });
11256
11262
  }
11263
+ /**
11264
+ * Focuses on a marker by panning and zooming to its position
11265
+ *
11266
+ * This method smoothly animates the map view to center on the specified marker
11267
+ * and optionally sets a specific zoom level. If no zoom level is provided,
11268
+ * it uses the marker's saved zoom level or a reasonable default.
11269
+ *
11270
+ * @param markerOrId - Marker ID string or Marker object
11271
+ * @param options - Optional focus options
11272
+ * @param options.zoom - Target zoom level (uses marker's zoom or default if not provided)
11273
+ * @param options.animate - Whether to animate the transition (default: true)
11274
+ * @param options.duration - Animation duration in seconds (default: 0.5)
11275
+ * @param options.offsetLeft - Pixel offset to move focus marker to the left (default: 0)
11276
+ * @param options.offsetRight - Pixel offset to move focus marker to the right (default: 0)
11277
+ * @param options.offsetTop - Pixel offset to move focus marker upward (default: 0)
11278
+ * @param options.offsetBottom - Pixel offset to move focus marker downward (default: 0)
11279
+ * @returns True if focus was successful, false if marker not found
11280
+ *
11281
+ * @example
11282
+ * ```typescript
11283
+ * // Focus on marker by ID
11284
+ * markerManager.focusMarker('marker-123');
11285
+ *
11286
+ * // Focus with specific zoom level
11287
+ * markerManager.focusMarker('marker-123', { zoom: 3 });
11288
+ *
11289
+ * // Focus without animation
11290
+ * markerManager.focusMarker('marker-123', { animate: false });
11291
+ *
11292
+ * // Focus with custom duration
11293
+ * markerManager.focusMarker('marker-123', { zoom: 2, duration: 1.0 });
11294
+ *
11295
+ * // Focus with offset to account for overlay
11296
+ * markerManager.focusMarker('marker-123', { offsetLeft: 100, offsetTop: 50 });
11297
+ * ```
11298
+ */
11299
+ focusMarker(markerOrId, options) {
11300
+ let marker = null;
11301
+ if (typeof markerOrId === "string") {
11302
+ marker = this.getMarker(markerOrId);
11303
+ } else {
11304
+ marker = markerOrId;
11305
+ }
11306
+ if (!marker) {
11307
+ console.warn(`Marker not found: ${markerOrId}`);
11308
+ return false;
11309
+ }
11310
+ const targetZoom = options?.zoom ?? marker.zoom ?? 2;
11311
+ const minZoom = this.imageInfo.minZoom ?? 0;
11312
+ const maxZoom = this.imageInfo.maxZoom ?? 5;
11313
+ const constrainedZoom = Math.max(minZoom, Math.min(maxZoom, targetZoom));
11314
+ const position = [
11315
+ marker.position[0],
11316
+ marker.position[1]
11317
+ ];
11318
+ const animate = options?.animate !== false;
11319
+ const duration = options?.duration ?? 0.5;
11320
+ const offsetX = (options?.offsetRight ?? 0) - (options?.offsetLeft ?? 0);
11321
+ const offsetY = (options?.offsetBottom ?? 0) - (options?.offsetTop ?? 0);
11322
+ if (offsetX !== 0 || offsetY !== 0) {
11323
+ const currentZoom = this.map.getZoom();
11324
+ const needsZoomChange = currentZoom !== constrainedZoom;
11325
+ if (needsZoomChange) {
11326
+ this.map.setZoom(constrainedZoom, { animate: false });
11327
+ }
11328
+ const markerPoint = this.map.latLngToContainerPoint(position);
11329
+ const offsetPoint = import_leaflet3.default.point(
11330
+ markerPoint.x + offsetX,
11331
+ markerPoint.y + offsetY
11332
+ );
11333
+ const offsetLatLng = this.map.containerPointToLatLng(offsetPoint);
11334
+ if (needsZoomChange && animate) {
11335
+ this.map.setZoom(currentZoom, { animate: false });
11336
+ }
11337
+ if (animate) {
11338
+ this.map.flyTo(offsetLatLng, constrainedZoom, {
11339
+ duration
11340
+ });
11341
+ } else {
11342
+ this.map.setView(offsetLatLng, constrainedZoom);
11343
+ }
11344
+ } else {
11345
+ if (animate) {
11346
+ this.map.flyTo(position, constrainedZoom, {
11347
+ duration
11348
+ });
11349
+ } else {
11350
+ this.map.setView(position, constrainedZoom);
11351
+ }
11352
+ }
11353
+ return true;
11354
+ }
11257
11355
  /**
11258
11356
  * Disposes of the marker manager and cleans up resources
11259
11357
  *
@@ -11276,10 +11374,9 @@ var MarkerManager = class {
11276
11374
  const iconUrl = `${this.iconBasePath}${iconType}.png`;
11277
11375
  return import_leaflet3.default.icon({
11278
11376
  iconUrl,
11279
- iconSize: [30, 40],
11280
- // Default marker size
11281
- iconAnchor: [16, 36],
11282
- // Point of the icon which will correspond to marker's location
11377
+ iconSize: [MARKER_SPEC.WIDTH, MARKER_SPEC.HEIGHT],
11378
+ iconAnchor: [MARKER_SPEC.ANCHOR_X, MARKER_SPEC.ANCHOR_Y],
11379
+ // Anchor point aligns with marker location
11283
11380
  popupAnchor: [0, 0],
11284
11381
  // Point from which the popup should open relative to the iconAnchor
11285
11382
  shadowUrl: void 0,
@@ -11409,8 +11506,8 @@ var MarkerManager = class {
11409
11506
  }
11410
11507
  const overlaySize = 40;
11411
11508
  const borderWidth = 1;
11412
- const overlayAnchorX = 20;
11413
- const overlayAnchorY = 35;
11509
+ const overlayAnchorX = overlaySize / 2 + (MARKER_SPEC.WIDTH / 2 - MARKER_SPEC.ANCHOR_X);
11510
+ const overlayAnchorY = overlaySize / 2 + (MARKER_SPEC.HEIGHT / 2 - MARKER_SPEC.ANCHOR_Y);
11414
11511
  const overlayHTML = `
11415
11512
  <div style="
11416
11513
  width: ${overlaySize}px;
@@ -12363,6 +12460,41 @@ var EdgePdfViewer = class {
12363
12460
  getMarkerManager() {
12364
12461
  return this.markerManager;
12365
12462
  }
12463
+ /**
12464
+ * Focuses on a marker by panning and zooming to its position
12465
+ *
12466
+ * This is a convenience method that delegates to the marker manager's focusMarker method.
12467
+ *
12468
+ * @param markerOrId - Marker ID string or Marker object
12469
+ * @param options - Optional focus options
12470
+ * @param options.zoom - Target zoom level (uses marker's zoom or default if not provided)
12471
+ * @param options.animate - Whether to animate the transition (default: true)
12472
+ * @param options.duration - Animation duration in seconds (default: 0.5)
12473
+ * @param options.offsetLeft - Pixel offset to move focus marker to the left (default: 0)
12474
+ * @param options.offsetRight - Pixel offset to move focus marker to the right (default: 0)
12475
+ * @param options.offsetTop - Pixel offset to move focus marker upward (default: 0)
12476
+ * @param options.offsetBottom - Pixel offset to move focus marker downward (default: 0)
12477
+ * @returns True if focus was successful, false if marker not found or viewer not initialized
12478
+ *
12479
+ * @example
12480
+ * ```typescript
12481
+ * // Focus on marker by ID
12482
+ * viewer.focusMarker('marker-123');
12483
+ *
12484
+ * // Focus with specific zoom level
12485
+ * viewer.focusMarker('marker-123', { zoom: 3 });
12486
+ *
12487
+ * // Focus with offset to account for overlay
12488
+ * viewer.focusMarker('marker-123', { offsetLeft: 100, offsetTop: 50 });
12489
+ * ```
12490
+ */
12491
+ focusMarker(markerOrId, options) {
12492
+ if (!this.markerManager) {
12493
+ console.warn("Marker manager not available");
12494
+ return false;
12495
+ }
12496
+ return this.markerManager.focusMarker(markerOrId, options);
12497
+ }
12366
12498
  /**
12367
12499
  * Disposes of the map instance and cleans up resources
12368
12500
  *
@@ -12721,11 +12853,29 @@ function EdgePDFViewer({
12721
12853
  if (!markerManager) {
12722
12854
  return;
12723
12855
  }
12856
+ let previousMarkers = [];
12857
+ const markersEqual = (a, b) => {
12858
+ if (a.length !== b.length)
12859
+ return false;
12860
+ const bMap = new Map(b.map((m) => [m.id, m]));
12861
+ for (const marker of a) {
12862
+ const other = bMap.get(marker.id);
12863
+ if (!other)
12864
+ return false;
12865
+ if (marker.x !== other.x || marker.y !== other.y || marker.position[0] !== other.position[0] || marker.position[1] !== other.position[1] || marker.iconType !== other.iconType) {
12866
+ return false;
12867
+ }
12868
+ }
12869
+ return true;
12870
+ };
12724
12871
  const updateMarkers = () => {
12725
12872
  const allMarkers = markerManager.getAllMarkers();
12726
- setContextValue({ markers: allMarkers });
12727
- if (onPinsUpdateRef.current) {
12728
- onPinsUpdateRef.current(allMarkers);
12873
+ if (!markersEqual(allMarkers, previousMarkers)) {
12874
+ setContextValue({ markers: allMarkers });
12875
+ if (onPinsUpdateRef.current) {
12876
+ onPinsUpdateRef.current(allMarkers);
12877
+ }
12878
+ previousMarkers = allMarkers;
12729
12879
  }
12730
12880
  };
12731
12881
  const unsubscribeDelete = markerManager.on("delete", updateMarkers);
@@ -12974,6 +13124,21 @@ function useMarkers() {
12974
13124
  console.error("Failed to clear markers:", error);
12975
13125
  }
12976
13126
  }, [viewer, isInitialized]);
13127
+ const focusMarker = useCallback3(
13128
+ (markerOrId, options) => {
13129
+ if (!viewer || !isInitialized) {
13130
+ console.warn("Viewer not initialized");
13131
+ return false;
13132
+ }
13133
+ try {
13134
+ return viewer.focusMarker(markerOrId, options);
13135
+ } catch (error) {
13136
+ console.error("Failed to focus marker:", error);
13137
+ return false;
13138
+ }
13139
+ },
13140
+ [viewer, isInitialized]
13141
+ );
12977
13142
  return {
12978
13143
  markers,
12979
13144
  addMarker,
@@ -12983,7 +13148,8 @@ function useMarkers() {
12983
13148
  getAllMarkers,
12984
13149
  exportMarkers,
12985
13150
  importMarkers,
12986
- clearMarkers
13151
+ clearMarkers,
13152
+ focusMarker
12987
13153
  };
12988
13154
  }
12989
13155
 
@@ -1 +1 @@
1
- {"version":3,"file":"pdf-viewer.d.ts","sourceRoot":"","sources":["../../src/lib/pdf-viewer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGjE,OAAO,KAAK,EACV,YAAY,EACZ,UAAU,EACV,MAAM,EACN,UAAU,EACX,MAAM,gBAAgB,CAAC;AAGxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2BAA2B;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,yCAAyC;IACzC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,qEAAqE;IACrE,oBAAoB,CAAC,EAAE,iBAAiB,CAAC,UAAU,CAAC,CAAC;IACrD,+EAA+E;IAC/E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iEAAiE;IACjE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,0CAA0C;IAC1C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6CAA6C;IAC7C,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACxC,wCAAwC;IACxC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,wCAAwC;IACxC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,wCAAwC;IACxC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,kEAAkE;IAClE,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC;IACpC,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,UAAU,EACV,SAAS,EACT,KAAK,EACL,gBAAuB,EACvB,oBAAkC,EAClC,aAAoB,EACpB,gBAAuB,EACvB,cAAqB,EACrB,gBAAuB,EACvB,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,cAAc,EACd,WAAW,EACX,WAAW,EACX,QAAQ,GACT,EAAE,kBAAkB,GAAG,GAAG,CAAC,OAAO,CA6XlC"}
1
+ {"version":3,"file":"pdf-viewer.d.ts","sourceRoot":"","sources":["../../src/lib/pdf-viewer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGjE,OAAO,KAAK,EACV,YAAY,EACZ,UAAU,EACV,MAAM,EACN,UAAU,EACX,MAAM,gBAAgB,CAAC;AAGxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAE5D;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2BAA2B;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,yCAAyC;IACzC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,qEAAqE;IACrE,oBAAoB,CAAC,EAAE,iBAAiB,CAAC,UAAU,CAAC,CAAC;IACrD,+EAA+E;IAC/E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iEAAiE;IACjE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,0CAA0C;IAC1C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6CAA6C;IAC7C,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACxC,wCAAwC;IACxC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,wCAAwC;IACxC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,wCAAwC;IACxC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,kEAAkE;IAClE,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC;IACpC,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,UAAU,EACV,SAAS,EACT,KAAK,EACL,gBAAuB,EACvB,oBAAkC,EAClC,aAAoB,EACpB,gBAAuB,EACvB,cAAqB,EACrB,gBAAuB,EACvB,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,cAAc,EACd,WAAW,EACX,WAAW,EACX,QAAQ,GACT,EAAE,kBAAkB,GAAG,GAAG,CAAC,OAAO,CAialC"}
@@ -22,6 +22,16 @@ export interface UseMarkersReturn {
22
22
  importMarkers: (data: MarkerData) => boolean;
23
23
  /** Clear all markers */
24
24
  clearMarkers: () => void;
25
+ /** Focus on a marker by panning and zooming to its position */
26
+ focusMarker: (markerOrId: string | Marker, options?: {
27
+ zoom?: number;
28
+ animate?: boolean;
29
+ duration?: number;
30
+ offsetLeft?: number;
31
+ offsetRight?: number;
32
+ offsetTop?: number;
33
+ offsetBottom?: number;
34
+ }) => boolean;
25
35
  }
26
36
  /**
27
37
  * Hook to manage markers in the viewer
@@ -1 +1 @@
1
- {"version":3,"file":"use-markers.d.ts","sourceRoot":"","sources":["../../src/lib/use-markers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAG9D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sBAAsB;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,uBAAuB;IACvB,SAAS,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,MAAM,GAAG,IAAI,CAAC;IAC3D,4BAA4B;IAC5B,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;IACtC,sBAAsB;IACtB,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC;IAChE,yBAAyB;IACzB,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACzC,sBAAsB;IACtB,aAAa,EAAE,MAAM,MAAM,EAAE,CAAC;IAC9B,6BAA6B;IAC7B,aAAa,EAAE,MAAM,UAAU,CAAC;IAChC,+BAA+B;IAC/B,aAAa,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC;IAC7C,wBAAwB;IACxB,YAAY,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,UAAU,IAAI,gBAAgB,CAkN7C"}
1
+ {"version":3,"file":"use-markers.d.ts","sourceRoot":"","sources":["../../src/lib/use-markers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAG9D;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sBAAsB;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,uBAAuB;IACvB,SAAS,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,MAAM,GAAG,IAAI,CAAC;IAC3D,4BAA4B;IAC5B,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;IACtC,sBAAsB;IACtB,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC;IAChE,yBAAyB;IACzB,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACzC,sBAAsB;IACtB,aAAa,EAAE,MAAM,MAAM,EAAE,CAAC;IAC9B,6BAA6B;IAC7B,aAAa,EAAE,MAAM,UAAU,CAAC;IAChC,+BAA+B;IAC/B,aAAa,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC;IAC7C,wBAAwB;IACxB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,+DAA+D;IAC/D,WAAW,EAAE,CACX,UAAU,EAAE,MAAM,GAAG,MAAM,EAC3B,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,KACE,OAAO,CAAC;CACd;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,UAAU,IAAI,gBAAgB,CA+O7C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edgepdf/viewer-react",
3
- "version": "0.0.32",
3
+ "version": "0.0.34",
4
4
  "description": "EdgePDF Viewer - React components for viewing PDF documents with interactive markers and zoom controls",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",