@edgepdf/viewer-js 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
@@ -8,20 +8,19 @@ Core JavaScript library for the EdgePDF viewer, providing Leaflet map integratio
8
8
  npm install @edgepdf/viewer-js
9
9
  # or
10
10
  pnpm add @edgepdf/viewer-js
11
+ # or
12
+ yarn add @edgepdf/viewer-js
11
13
  ```
12
14
 
13
- **Note:** Leaflet is bundled with this library, so you don't need to install it separately.
14
-
15
- **For SSR frameworks (Next.js, Remix, etc.):** This library requires a browser environment. Use dynamic imports or the React component (`@edgepdf/viewer-react`) which handles SSR automatically. See [SSR Support](#ssr-support) section below.
15
+ **Note:** Leaflet is bundled with this library, so you don't need to install it separately. **CSS styles are automatically injected** when you import the library - no manual CSS import needed!
16
16
 
17
- ## Usage
17
+ ## Quick Start
18
18
 
19
19
  ### Basic Example
20
20
 
21
21
  ```typescript
22
22
  import { EdgePdfViewer } from '@edgepdf/viewer-js';
23
23
  import type { ViewerConfig } from '@edgepdf/types';
24
- // CSS is automatically injected - no need to import styles manually!
25
24
 
26
25
  // Get container element
27
26
  const container = document.getElementById('map-container');
@@ -42,6 +41,16 @@ const config: ViewerConfig = {
42
41
  const viewer = new EdgePdfViewer({ container, config });
43
42
  viewer.initialize();
44
43
 
44
+ // Access marker manager
45
+ const markerManager = viewer.getMarkerManager();
46
+ if (markerManager) {
47
+ // Create a marker
48
+ const marker = markerManager.createMarker({
49
+ position: [1500, 1000], // [lat, lng] in Leaflet coordinates
50
+ label: 'My Marker',
51
+ });
52
+ }
53
+
45
54
  // Cleanup when done
46
55
  viewer.dispose();
47
56
  ```
@@ -51,7 +60,6 @@ viewer.dispose();
51
60
  ```typescript
52
61
  import { EdgePdfViewer } from '@edgepdf/viewer-js';
53
62
  import type { ViewerConfig, MapOptions } from '@edgepdf/types';
54
- // CSS is automatically injected - no need to import styles manually!
55
63
 
56
64
  const config: ViewerConfig = {
57
65
  tileUrl: 'https://example.com/tiles/{z}/{x}/{y}.png',
@@ -80,93 +88,6 @@ const viewer = new EdgePdfViewer({
80
88
  viewer.initialize();
81
89
  ```
82
90
 
83
- ## SSR Support
84
-
85
- This library uses Leaflet, which requires a browser environment. For SSR frameworks like Next.js, Remix, or other React frameworks, you have two options:
86
-
87
- ### Option 1: Use the React Component (Recommended)
88
-
89
- The `@edgepdf/viewer-react` package handles SSR automatically with dynamic imports:
90
-
91
- ```tsx
92
- import { EdgePDFViewer } from '@edgepdf/viewer-react';
93
- import { ViewerProvider } from '@edgepdf/viewer-react';
94
-
95
- // In your component
96
- <ViewerProvider>
97
- <EdgePDFViewer config={config} />
98
- </ViewerProvider>;
99
- ```
100
-
101
- ### Option 2: Use Dynamic Imports (Next.js)
102
-
103
- If you need to use `@edgepdf/viewer-js` directly in Next.js, use dynamic imports with `ssr: false`:
104
-
105
- ```tsx
106
- import dynamic from 'next/dynamic';
107
-
108
- const EdgePdfViewer = dynamic(
109
- () => import('@edgepdf/viewer-js').then((mod) => mod.EdgePdfViewer),
110
- { ssr: false }
111
- );
112
-
113
- // Or use it in a client component
114
- ('use client');
115
-
116
- import { useEffect, useRef } from 'react';
117
- import dynamic from 'next/dynamic';
118
-
119
- const EdgePdfViewer = dynamic(
120
- () => import('@edgepdf/viewer-js').then((mod) => mod.EdgePdfViewer),
121
- { ssr: false }
122
- );
123
-
124
- export default function MyComponent() {
125
- const containerRef = useRef<HTMLDivElement>(null);
126
-
127
- useEffect(() => {
128
- if (!containerRef.current) return;
129
-
130
- const { EdgePdfViewer } = require('@edgepdf/viewer-js');
131
- const viewer = new EdgePdfViewer({
132
- container: containerRef.current,
133
- config: yourConfig,
134
- });
135
- viewer.initialize();
136
-
137
- return () => viewer.dispose();
138
- }, []);
139
-
140
- return <div ref={containerRef} style={{ width: '100%', height: '100%' }} />;
141
- }
142
- ```
143
-
144
- ### Next.js Configuration
145
-
146
- If you're still getting build errors, add this to your `next.config.js`:
147
-
148
- ```javascript
149
- /** @type {import('next').NextConfig} */
150
- const nextConfig = {
151
- webpack: (config, { isServer }) => {
152
- if (isServer) {
153
- // Exclude @edgepdf/viewer-js from server-side bundle
154
- config.externals = config.externals || [];
155
- config.externals.push('@edgepdf/viewer-js');
156
- }
157
- return config;
158
- },
159
- };
160
-
161
- module.exports = nextConfig;
162
- ```
163
-
164
- **Note:** You'll also need to import the CSS manually when using dynamic imports:
165
-
166
- ```tsx
167
- import '@edgepdf/viewer-js/dist/styles.css';
168
- ```
169
-
170
91
  ## API Reference
171
92
 
172
93
  ### EdgePdfViewer
@@ -218,10 +139,428 @@ Checks if the map is initialized.
218
139
 
219
140
  **Returns:** `true` if map is initialized, `false` otherwise
220
141
 
142
+ ##### `getTileLayerManager(): TileLayerManager | null`
143
+
144
+ Gets the tile layer manager instance.
145
+
146
+ **Returns:** The tile layer manager, or `null` if not created
147
+
148
+ ##### `getCoordinateMapper(): CoordinateMapper | null`
149
+
150
+ Gets the coordinate mapper instance.
151
+
152
+ **Returns:** The coordinate mapper, or `null` if not created
153
+
154
+ ##### `getZoomController(): ZoomController | null`
155
+
156
+ Gets the zoom controller instance.
157
+
158
+ **Returns:** The zoom controller, or `null` if not created
159
+
160
+ ##### `getMarkerManager(): MarkerManager | null`
161
+
162
+ Gets the marker manager instance.
163
+
164
+ **Returns:** The marker manager, or `null` if not created
165
+
166
+ ##### `focusMarker(markerOrId: string | Marker, options?: FocusOptions): boolean`
167
+
168
+ Focuses on a marker by panning and zooming to its position.
169
+
170
+ **Parameters:**
171
+
172
+ - `markerOrId` - Marker ID string or Marker object
173
+ - `options` - Optional focus options
174
+ - `zoom?: number` - Target zoom level (uses marker's zoom or default if not provided)
175
+ - `animate?: boolean` - Whether to animate the transition (default: true)
176
+ - `duration?: number` - Animation duration in seconds (default: 0.5)
177
+
178
+ **Returns:** `true` if focus was successful, `false` if marker not found or viewer not initialized
179
+
221
180
  ##### `dispose(): void`
222
181
 
223
182
  Disposes of the map instance and cleans up resources. This should be called when the viewer is no longer needed to prevent memory leaks and event listener issues.
224
183
 
184
+ ---
185
+
186
+ ### MarkerManager
187
+
188
+ Manages markers on the Leaflet map. Access via `viewer.getMarkerManager()`.
189
+
190
+ #### Methods
191
+
192
+ ##### `createMarker(options: CreateMarkerOptions): Marker`
193
+
194
+ Creates a new marker from coordinates.
195
+
196
+ **Parameters:**
197
+
198
+ - `options.position?: LeafletCoords` - Leaflet coordinates [lat, lng]
199
+ - `options.imageCoords?: ImageCoords` - Image pixel coordinates {x, y}
200
+ - `options.label?: string` - Marker label/tooltip text
201
+ - `options.description?: string` - Marker description
202
+ - `options.href?: string` - Link URL (optional)
203
+ - `options.target?: string` - Link target (optional)
204
+ - `options.showLabel?: boolean` - Show label/tooltip
205
+ - `options.id?: string` - Custom marker ID (auto-generated if not provided)
206
+ - `options.iconType?: 'pin-gray' | 'pin-yellow'` - Icon type for the marker
207
+ - `options.referenceId?: string` - Reference ID for linking to external systems
208
+ - `options.draggable?: boolean` - Enable dragging for this specific marker
209
+ - `options.editable?: boolean` - Enable editing for this specific marker
210
+ - `options.deletable?: boolean` - Enable deleting for this specific marker
211
+
212
+ **Returns:** The created marker
213
+
214
+ **Throws:**
215
+
216
+ - `Error` if neither position nor imageCoords is provided
217
+ - `Error` if coordinates are invalid or out of bounds
218
+
219
+ ##### `getMarker(id: string): Marker | null`
220
+
221
+ Gets a marker by ID.
222
+
223
+ **Returns:** The marker, or `null` if not found
224
+
225
+ ##### `getAllMarkers(): Marker[]`
226
+
227
+ Gets all markers.
228
+
229
+ **Returns:** Array of all markers
230
+
231
+ ##### `getMarkerCount(): number`
232
+
233
+ Gets the total number of markers.
234
+
235
+ **Returns:** Number of markers
236
+
237
+ ##### `hasMarker(id: string): boolean`
238
+
239
+ Checks if a marker exists.
240
+
241
+ **Returns:** `true` if marker exists, `false` otherwise
242
+
243
+ ##### `updateMarker(id: string, updates: Partial<Marker>): boolean`
244
+
245
+ Updates a marker's properties.
246
+
247
+ **Parameters:**
248
+
249
+ - `id` - Marker ID
250
+ - `updates` - Partial marker object with properties to update
251
+
252
+ **Returns:** `true` if marker was updated, `false` if not found
253
+
254
+ ##### `updateMarkerPosition(id: string, position: LeafletCoords): boolean`
255
+
256
+ Updates a marker's position.
257
+
258
+ **Parameters:**
259
+
260
+ - `id` - Marker ID
261
+ - `position` - New Leaflet coordinates [lat, lng]
262
+
263
+ **Returns:** `true` if marker was updated, `false` if not found
264
+
265
+ ##### `updateMarkerIcon(id: string, iconType: 'pin-gray' | 'pin-yellow'): boolean`
266
+
267
+ Updates the icon type for a specific marker.
268
+
269
+ **Returns:** `true` if marker was updated, `false` if not found
270
+
271
+ ##### `updateAllMarkerIcons(iconType: 'pin-gray' | 'pin-yellow'): void`
272
+
273
+ Updates all markers to use a new icon type.
274
+
275
+ ##### `removeMarker(id: string): boolean`
276
+
277
+ Removes a marker by ID.
278
+
279
+ **Returns:** `true` if marker was removed, `false` if not found
280
+
281
+ ##### `removeAllMarkers(): void`
282
+
283
+ Removes all markers.
284
+
285
+ ##### `deleteMarker(id: string): boolean`
286
+
287
+ Deletes a marker by ID (triggers delete event).
288
+
289
+ **Returns:** `true` if marker was deleted, `false` if not found
290
+
291
+ ##### `exportMarkers(): MarkerData`
292
+
293
+ Exports all markers as JSON data.
294
+
295
+ **Returns:** MarkerData object containing all markers and metadata
296
+
297
+ ##### `importMarkers(data: MarkerData, options?: ImportOptions): ImportResult`
298
+
299
+ Imports markers from MarkerData.
300
+
301
+ **Parameters:**
302
+
303
+ - `data` - MarkerData object to import
304
+ - `options` - Import options
305
+ - `clearExisting?: boolean` - If true, removes all existing markers before import (default: false)
306
+ - `validateCoordinates?: boolean` - If true, validates coordinates are within bounds (default: true)
307
+
308
+ **Returns:** Import result with success status and details
309
+
310
+ ##### `setInteractionConfig(config: Partial<MarkerInteractionConfig>): void`
311
+
312
+ Sets marker interaction configuration.
313
+
314
+ **Parameters:**
315
+
316
+ - `config.draggable?: boolean` - Enable dragging markers
317
+ - `config.selectable?: boolean` - Enable selecting markers
318
+ - `config.showTooltips?: boolean` - Show tooltips on hover
319
+ - `config.showPopups?: boolean` - Show popups on click
320
+ - `config.multiSelect?: boolean` - Allow multiple marker selection
321
+ - `config.showEditButton?: boolean` - Show edit button in popups
322
+ - `config.showDeleteButton?: boolean` - Show delete button in popups
323
+ - `config.onEdit?: (marker: Marker) => Promise<Marker | null>` - Custom edit handler
324
+ - `config.onDelete?: (marker: Marker) => Promise<boolean>` - Custom delete handler
325
+ - `config.onActiveMarkerChange?: (marker: Marker | null) => void` - Active marker change callback
326
+
327
+ ##### `getInteractionConfig(): MarkerInteractionConfig`
328
+
329
+ Gets the current interaction configuration.
330
+
331
+ **Returns:** Current interaction configuration
332
+
333
+ ##### `selectMarker(id: string): boolean`
334
+
335
+ Selects a marker.
336
+
337
+ **Returns:** `true` if marker was selected, `false` if not found
338
+
339
+ ##### `deselectMarker(id: string): boolean`
340
+
341
+ Deselects a marker.
342
+
343
+ **Returns:** `true` if marker was deselected, `false` if not found
344
+
345
+ ##### `deselectAllMarkers(): void`
346
+
347
+ Deselects all markers.
348
+
349
+ ##### `getSelectionState(): MarkerSelectionState`
350
+
351
+ Gets the current selection state.
352
+
353
+ **Returns:** Selection state object with selected IDs and count
354
+
355
+ ##### `isMarkerSelected(id: string): boolean`
356
+
357
+ Checks if a marker is selected.
358
+
359
+ **Returns:** `true` if marker is selected, `false` otherwise
360
+
361
+ ##### `setActiveMarker(id: string | null): boolean`
362
+
363
+ Sets the active marker (for popup management).
364
+
365
+ **Returns:** `true` if active marker was set, `false` if marker not found
366
+
367
+ ##### `getActiveMarker(): Marker | null`
368
+
369
+ Gets the currently active marker.
370
+
371
+ **Returns:** The active marker, or `null` if no marker is active
372
+
373
+ ##### `focusMarker(markerOrId: string | Marker, options?: FocusOptions): boolean`
374
+
375
+ Focuses on a marker by panning and zooming to its position.
376
+
377
+ **Parameters:**
378
+
379
+ - `markerOrId` - Marker ID string or Marker object
380
+ - `options` - Optional focus options
381
+ - `zoom?: number` - Target zoom level
382
+ - `animate?: boolean` - Whether to animate (default: true)
383
+ - `duration?: number` - Animation duration in seconds (default: 0.5)
384
+
385
+ **Returns:** `true` if focus was successful, `false` if marker not found
386
+
387
+ ##### `setDefaultIconType(iconType: 'pin-gray' | 'pin-yellow'): void`
388
+
389
+ Sets the default icon type for new markers.
390
+
391
+ ##### `getDefaultIconType(): 'pin-gray' | 'pin-yellow'`
392
+
393
+ Gets the current default icon type.
394
+
395
+ **Returns:** Current default icon type
396
+
397
+ ##### `setIconBasePath(basePath: string): void`
398
+
399
+ Sets the base path for marker icons.
400
+
401
+ **Example:**
402
+
403
+ ```typescript
404
+ // Use library's built-in icons (default)
405
+ markerManager.setIconBasePath('./images/');
406
+
407
+ // Use custom icons from public folder
408
+ markerManager.setIconBasePath('/');
409
+
410
+ // Use icons from a CDN
411
+ markerManager.setIconBasePath('https://cdn.example.com/icons/');
412
+ ```
413
+
414
+ ##### `getIconBasePath(): string`
415
+
416
+ Gets the current base path for marker icons.
417
+
418
+ **Returns:** The current icon base path
419
+
420
+ ##### `on(eventType: MarkerEventType, callback: (event: MarkerEvent) => void): () => void`
421
+
422
+ Registers an event listener.
423
+
424
+ **Parameters:**
425
+
426
+ - `eventType` - Event type: 'click' | 'dragstart' | 'drag' | 'dragend' | 'edit' | 'delete'
427
+ - `callback` - Callback function
428
+
429
+ **Returns:** Unsubscribe function
430
+
431
+ **Example:**
432
+
433
+ ```typescript
434
+ const unsubscribe = markerManager.on('click', (event) => {
435
+ console.log('Marker clicked:', event.marker.id);
436
+ });
437
+
438
+ // Later, to unsubscribe:
439
+ unsubscribe();
440
+ ```
441
+
442
+ ##### `off(eventType: MarkerEventType, callback: (event: MarkerEvent) => void): void`
443
+
444
+ Unregisters an event listener.
445
+
446
+ ##### `removeAllListeners(eventType?: MarkerEventType): void`
447
+
448
+ Removes all event listeners for a specific event type, or all events if no type is provided.
449
+
450
+ ##### `dispose(): void`
451
+
452
+ Disposes of the marker manager and cleans up resources.
453
+
454
+ ---
455
+
456
+ ### ZoomController
457
+
458
+ Manages zoom state and operations. Access via `viewer.getZoomController()`.
459
+
460
+ #### Methods
461
+
462
+ ##### `zoomIn(options?: { animate?: boolean }): boolean`
463
+
464
+ Zooms in by one level.
465
+
466
+ **Parameters:**
467
+
468
+ - `options.animate?: boolean` - Whether to animate the zoom (default: true)
469
+
470
+ **Returns:** `true` if zoomed in, `false` if already at max zoom
471
+
472
+ ##### `zoomOut(options?: { animate?: boolean }): boolean`
473
+
474
+ Zooms out by one level.
475
+
476
+ **Parameters:**
477
+
478
+ - `options.animate?: boolean` - Whether to animate the zoom (default: true)
479
+
480
+ **Returns:** `true` if zoomed out, `false` if already at min zoom
481
+
482
+ ##### `setZoom(zoom: number, options?: { animate?: boolean }): void`
483
+
484
+ Sets a specific zoom level.
485
+
486
+ **Parameters:**
487
+
488
+ - `zoom` - Target zoom level
489
+ - `options.animate?: boolean` - Whether to animate the zoom (default: true)
490
+
491
+ **Throws:**
492
+
493
+ - `Error` if zoom is not a finite number
494
+
495
+ ##### `getZoom(): number`
496
+
497
+ Gets the current zoom level.
498
+
499
+ **Returns:** Current zoom level
500
+
501
+ ##### `getZoomState(): ZoomState`
502
+
503
+ Gets the current zoom state.
504
+
505
+ **Returns:** Zoom state object with currentZoom, minZoom, maxZoom
506
+
507
+ ##### `getMinZoom(): number`
508
+
509
+ Gets the minimum zoom level.
510
+
511
+ **Returns:** Minimum zoom level
512
+
513
+ ##### `getMaxZoom(): number`
514
+
515
+ Gets the maximum zoom level.
516
+
517
+ **Returns:** Maximum zoom level
518
+
519
+ ##### `canZoomIn(): boolean`
520
+
521
+ Checks if can zoom in.
522
+
523
+ **Returns:** `true` if can zoom in, `false` otherwise
524
+
525
+ ##### `canZoomOut(): boolean`
526
+
527
+ Checks if can zoom out.
528
+
529
+ **Returns:** `true` if can zoom out, `false` otherwise
530
+
531
+ ##### `onZoomChange(listener: (state: ZoomState) => void): () => void`
532
+
533
+ Registers a zoom change listener.
534
+
535
+ **Returns:** Unsubscribe function
536
+
537
+ ##### `removeAllListeners(): void`
538
+
539
+ Removes all zoom change listeners.
540
+
541
+ ##### `dispose(): void`
542
+
543
+ Disposes of the zoom controller and cleans up resources.
544
+
545
+ ---
546
+
547
+ ### Utility Functions
548
+
549
+ #### `createZoomControls(container: HTMLElement, zoomController: ZoomController, options?: ZoomControlsOptions): HTMLElement`
550
+
551
+ Creates zoom control buttons (zoom in/out) in the specified container.
552
+
553
+ **Parameters:**
554
+
555
+ - `container` - Container element
556
+ - `zoomController` - Zoom controller instance
557
+ - `options` - Optional configuration
558
+ - `position?: ZoomControlsPosition` - Position: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' (default: 'top-right')
559
+
560
+ **Returns:** The created controls element
561
+
562
+ ---
563
+
225
564
  ## Configuration
226
565
 
227
566
  ### ViewerConfig
@@ -318,16 +657,77 @@ The viewer uses Leaflet's `CRS.Simple` coordinate system, which treats coordinat
318
657
  - Y-axis: vertical pixel position (0 to image height)
319
658
  - Origin: top-left corner (0, 0)
320
659
 
321
- ## Building
660
+ ## Examples
322
661
 
323
- Run `nx build viewer-js` to build the library.
662
+ ### Working with Markers
324
663
 
325
- ## Running Tests
664
+ ```typescript
665
+ const viewer = new EdgePdfViewer({ container, config });
666
+ viewer.initialize();
667
+
668
+ const markerManager = viewer.getMarkerManager();
669
+ if (!markerManager) return;
670
+
671
+ // Create a marker
672
+ const marker = markerManager.createMarker({
673
+ position: [1500, 1000],
674
+ label: 'My Marker',
675
+ description: 'This is a marker description',
676
+ iconType: 'pin-yellow',
677
+ });
326
678
 
327
- Run `nx test viewer-js` to execute the unit tests via [Jest](https://jestjs.io).
679
+ // Update marker
680
+ markerManager.updateMarker(marker.id, {
681
+ label: 'Updated Label',
682
+ description: 'Updated description',
683
+ });
328
684
 
329
- Run `nx test viewer-js --coverage` to generate coverage reports.
685
+ // Listen to marker events
686
+ markerManager.on('click', (event) => {
687
+ console.log('Marker clicked:', event.marker);
688
+ });
330
689
 
331
- ## License
690
+ markerManager.on('dragend', (event) => {
691
+ console.log('Marker dragged to:', event.marker.position);
692
+ });
332
693
 
333
- MIT
694
+ // Export markers
695
+ const markerData = markerManager.exportMarkers();
696
+ console.log(JSON.stringify(markerData, null, 2));
697
+
698
+ // Import markers
699
+ markerManager.importMarkers(markerData, {
700
+ clearExisting: true,
701
+ validateCoordinates: true,
702
+ });
703
+ ```
704
+
705
+ ### Working with Zoom
706
+
707
+ ```typescript
708
+ const viewer = new EdgePdfViewer({ container, config });
709
+ viewer.initialize();
710
+
711
+ const zoomController = viewer.getZoomController();
712
+ if (!zoomController) return;
713
+
714
+ // Zoom in
715
+ zoomController.zoomIn();
716
+
717
+ // Zoom out
718
+ zoomController.zoomOut();
719
+
720
+ // Set specific zoom level
721
+ zoomController.setZoom(3);
722
+
723
+ // Get zoom state
724
+ const zoomState = zoomController.getZoomState();
725
+ console.log('Current zoom:', zoomState.currentZoom);
726
+ console.log('Min zoom:', zoomState.minZoom);
727
+ console.log('Max zoom:', zoomState.maxZoom);
728
+
729
+ // Listen to zoom changes
730
+ zoomController.onZoomChange((state) => {
731
+ console.log('Zoom changed to:', state.currentZoom);
732
+ });
733
+ ```