import L from '@edgepdf/core'; import type { Marker, MarkerData, ViewerCoords, ImageInfo, MarkerEvent, MarkerEventType, MarkerInteractionConfig, MarkerSelectionState } from '@edgepdf/types'; import { CoordinateMapper } from './coordinate-mapper.js'; /** * Options for creating a marker */ export interface CreateMarkerOptions { /** Leaflet coordinates [lat, lng] */ position?: ViewerCoords; /** Image pixel X coordinate */ x?: number; /** Image pixel Y coordinate */ y?: number; /** Marker label/tooltip text */ label?: string; /** Marker description */ description?: string; /** Link URL (optional) */ href?: string; /** Link target (optional) */ target?: string; /** Show label/tooltip */ showLabel?: boolean; /** Custom marker ID (auto-generated if not provided) */ id?: string; /** Icon type for the marker */ iconType?: 'pin-gray' | 'pin-yellow'; /** Reference ID for linking to external systems */ referenceId?: string; /** Enable dragging for this specific marker (overrides global draggable setting) */ draggable?: boolean; /** Enable editing for this specific marker (overrides global showEditButton setting) */ editable?: boolean; /** Enable deleting for this specific marker (overrides global showDeleteButton setting) */ deletable?: boolean; } /** * MarkerManager - Manages markers on the Leaflet map * * This class handles: * - Marker creation from coordinates (Leaflet or image pixel coordinates) * - Marker positioning and coordinate storage * - Marker lifecycle management (add, remove, update, get) * - Marker validation * - Marker interactions (dragging, selection, tooltips, popups) * - Event handling for marker actions * * @example * ```typescript * const markerManager = new MarkerManager({ * map: leafletMap, * coordinateMapper: coordinateMapper, * imageInfo: imageInfo * }); * * // Enable interactions * markerManager.setInteractionConfig({ * draggable: true, * selectable: true, * showTooltips: true, * showPopups: true * }); * * // Listen to marker events * markerManager.on('dragend', (event) => { * console.log('Marker dragged:', event.marker.id); * }); * * // Create marker from Leaflet coordinates * const marker = markerManager.createMarker({ * position: [1500, 1000], * label: 'My Marker' * }); * * // Get all markers * const allMarkers = markerManager.getAllMarkers(); * ``` */ export declare class MarkerManager { private map; private coordinateMapper; private imageInfo; private markers; private leafletMarkers; private markerLayerGroup; private eventListeners; private selectedIds; private activeMarkerId; private activeMarkerOverlays; private interactionConfig; private defaultIconType; private iconBasePath; private iconDataUrls; /** * Creates a new MarkerManager instance * * @param options - Configuration options * @param options.map - Leaflet map instance * @param options.coordinateMapper - Coordinate mapper for coordinate conversion * @param options.imageInfo - Image information for validation * * @throws {Error} If map is not provided or invalid * @throws {Error} If coordinateMapper is not provided * @throws {Error} If imageInfo is not provided */ constructor(options: { map: L.Map; coordinateMapper: CoordinateMapper; imageInfo: ImageInfo; }); /** * Creates a new marker from coordinates * * Either position (Leaflet coordinates) or x/y pixel coordinates must be provided. * If both are provided, x/y takes precedence — pixel coordinates are the source of * truth and remain valid across tile grid changes (e.g. after migrating from the * legacy doubled-grid to the standard grid). Always pass x/y when loading stored * annotations to ensure correct placement regardless of the tile system. * * @param options - Marker creation options * @param options.position - Leaflet coordinates [lat, lng] * @param options.x - Image pixel X coordinate * @param options.y - Image pixel Y coordinate * @param options.label - Marker label/tooltip text * @param options.description - Marker description * @param options.href - Link URL (optional) * @param options.target - Link target (optional) * @param options.showLabel - Show label/tooltip * @param options.id - Custom marker ID (auto-generated if not provided) * * @returns The created marker * * @throws {Error} If neither position nor x/y is provided * @throws {Error} If coordinates are invalid or out of bounds */ createMarker(options: CreateMarkerOptions): Marker; /** * Gets a marker by ID * * @param id - Marker ID * @returns The marker, or undefined if not found */ getMarker(id: string): Marker | null; /** * Gets all markers * * @returns Array of all markers */ getAllMarkers(): Marker[]; /** * Exports all markers as JSON data * * Serializes all markers into a MarkerData object that can be saved to a file * or transmitted. The exported data includes metadata like export timestamp * and version. * * @returns MarkerData object containing all markers and metadata * * @example * ```typescript * const markerData = markerManager.exportMarkers(); * const jsonString = JSON.stringify(markerData, null, 2); * // Save to file or send to server * ``` */ exportMarkers(): MarkerData; /** * Imports markers from MarkerData * * Validates and imports markers from a MarkerData object. This method: * - Validates the data structure * - Validates each marker's coordinates are within bounds * - Creates markers using the imported data * - Handles coordinate compatibility (ensures both Leaflet and image coordinates are valid) * * @param data - MarkerData object to import * @param options - Import options * @param options.clearExisting - If true, removes all existing markers before import (default: false) * @param options.validateCoordinates - If true, validates coordinates are within bounds (default: true) * * @returns Import result with success status and details * * @throws {Error} If data structure is invalid * @throws {Error} If marker validation fails * * @example * ```typescript * try { * const result = markerManager.importMarkers(markerData, { * clearExisting: true, * validateCoordinates: true * }); * console.log(`Imported ${result.importedCount} markers`); * } catch (error) { * console.error('Import failed:', error); * } * ``` */ importMarkers(data: MarkerData, options?: { clearExisting?: boolean; validateCoordinates?: boolean; }): { success: boolean; importedCount: number; errors: string[]; }; /** * Removes a marker by ID * * @param id - Marker ID * @returns True if marker was removed, false if not found */ removeMarker(id: string): boolean; /** * Removes all markers */ removeAllMarkers(): void; /** * Updates a marker's position * * @param id - Marker ID * @param position - New Leaflet coordinates [lat, lng] * @returns True if marker was updated, false if not found * * @throws {Error} If coordinates are invalid or out of bounds */ updateMarkerPosition(id: string, position: ViewerCoords): boolean; /** * Updates a marker's properties (excluding position) * * @param id - Marker ID * @param updates - Partial marker properties to update * @returns True if marker was updated, false if not found */ updateMarker(id: string, updates: Partial>): boolean; /** * Gets the number of markers * * @returns Number of markers */ getMarkerCount(): number; /** * Checks if a marker exists * * @param id - Marker ID * @returns True if marker exists, false otherwise */ hasMarker(id: string): boolean; /** * Sets the interaction configuration for markers * * @param config - Interaction configuration */ setInteractionConfig(config: Partial): void; /** * Gets the current interaction configuration * * @returns Current interaction configuration */ getInteractionConfig(): MarkerInteractionConfig; /** * Selects a marker by ID * * @param id - Marker ID to select * @returns True if marker was selected, false if not found */ selectMarker(id: string): boolean; /** * Deselects a marker by ID * * @param id - Marker ID to deselect * @returns True if marker was deselected, false if not found */ deselectMarker(id: string): boolean; /** * Deselects all markers */ deselectAllMarkers(): void; /** * Gets the current selection state * * @returns Selection state */ getSelectionState(): MarkerSelectionState; /** * Checks if a marker is selected * * @param id - Marker ID * @returns True if marker is selected */ isMarkerSelected(id: string): boolean; /** * Sets the active marker programmatically * * @param id - Marker ID to set as active, or null to clear active marker * @returns True if marker was set as active, false if not found */ setActiveMarker(id: string | null): boolean; /** * Gets the currently active marker * * @returns The active marker, or null if no marker is active */ getActiveMarker(): Marker | null; /** * Deletes a marker by ID (triggers delete event) * * @param id - Marker ID to delete * @returns True if marker was deleted, false if not found */ deleteMarker(id: string): boolean; /** * Registers an event listener * * @param eventType - Event type to listen for * @param callback - Callback function * @returns Unsubscribe function */ on(eventType: MarkerEventType, callback: (event: MarkerEvent) => void): () => void; /** * Unregisters an event listener * * @param eventType - Event type * @param callback - Callback function to remove */ off(eventType: MarkerEventType, callback: (event: MarkerEvent) => void): void; /** * Removes all event listeners for a specific event type, or all events if no type is provided * * @param eventType - Optional event type to clear */ removeAllListeners(eventType?: MarkerEventType): void; /** * Sets the default icon type for new markers * * @param iconType - Icon type to use as default */ setDefaultIconType(iconType: 'pin-gray' | 'pin-yellow'): void; /** * Gets the current default icon type * * @returns Current default icon type */ getDefaultIconType(): 'pin-gray' | 'pin-yellow'; /** * Sets the base path for marker icons * * @param basePath - Base path for icon files (default: './images/') * * @example * ```typescript * // Use library's built-in icons (default) * markerManager.setIconBasePath('./images/'); * * // Use custom icons from public folder * markerManager.setIconBasePath('/'); * * // Use icons from a CDN * markerManager.setIconBasePath('https://cdn.example.com/icons/'); * ``` */ setIconBasePath(basePath: string): void; /** * Gets the current base path for marker icons * * @returns The current icon base path */ getIconBasePath(): string; /** * Sets data URI overrides for marker icons by icon type. * When set, these take precedence over iconBasePath for the specified types. * Useful for offline/embedded environments where relative URLs can't be resolved. * * @param urls - Map of icon type → data URI (e.g. `{ 'pin-gray': 'data:image/png;base64,...' }`) */ setIconDataUrls(urls: Record): void; /** * Updates the icon type for a specific marker * * @param id - Marker ID * @param iconType - New icon type * @returns True if marker was updated, false if not found */ updateMarkerIcon(id: string, iconType: 'pin-gray' | 'pin-yellow'): boolean; /** * Updates all markers to use a new icon type * * @param iconType - Icon type to apply to all markers */ updateAllMarkerIcons(iconType: 'pin-gray' | 'pin-yellow'): void; /** * Focuses on a marker by panning and zooming to its position * * This method smoothly animates the map view to center on the specified marker * and optionally sets a specific zoom level. If no zoom level is provided, * it uses the marker's saved zoom level or a reasonable default. * * @param markerOrId - Marker ID string or Marker object * @param options - Optional focus options * @param options.zoom - Target zoom level (uses marker's zoom or default if not provided) * @param options.animate - Whether to animate the transition (default: true) * @param options.duration - Animation duration in seconds (default: 0.5) * @param options.offsetLeft - Pixel offset to move focus marker to the left (default: 0) * @param options.offsetRight - Pixel offset to move focus marker to the right (default: 0) * @param options.offsetTop - Pixel offset to move focus marker upward (default: 0) * @param options.offsetBottom - Pixel offset to move focus marker downward (default: 0) * @returns True if focus was successful, false if marker not found * * @example * ```typescript * // Focus on marker by ID * markerManager.focusMarker('marker-123'); * * // Focus with specific zoom level * markerManager.focusMarker('marker-123', { zoom: 3 }); * * // Focus without animation * markerManager.focusMarker('marker-123', { animate: false }); * * // Focus with custom duration * markerManager.focusMarker('marker-123', { zoom: 2, duration: 1.0 }); * * // Focus with offset to account for overlay * markerManager.focusMarker('marker-123', { offsetLeft: 100, offsetTop: 50 }); * ``` */ focusMarker(markerOrId: string | Marker, options?: { zoom?: number; animate?: boolean; duration?: number; offsetLeft?: number; offsetRight?: number; offsetTop?: number; offsetBottom?: number; }): boolean; /** * Disposes of the marker manager and cleans up resources * * This should be called when the marker manager is no longer needed. */ dispose(): void; /** * Creates a custom icon for a marker * * @param iconType - Icon type to use * @returns Leaflet icon instance */ private createCustomIcon; /** * Creates a Leaflet marker from marker data * * @param marker - Marker data * @returns Leaflet marker instance */ private createLeafletMarker; /** * Sets up interaction handlers for a marker * * @param leafletMarker - Leaflet marker instance * @param marker - Marker data */ private setupMarkerInteractions; /** * Handles marker click events * * @param marker - Marker that was clicked */ private handleMarkerClick; /** * Updates the visual appearance of a marker based on selection state * * @param _id - Marker ID (unused - kept for API compatibility) * @param _selected - Whether marker is selected (unused - kept for API compatibility) */ private updateMarkerSelectionVisual; /** * Adds a blue border overlay to an active marker * * @param id - Marker ID */ private addActiveMarkerOverlay; /** * Removes the blue border overlay from a marker * * @param id - Marker ID */ private removeActiveMarkerOverlay; /** * Emits an event to all registered listeners * * @param eventType - Event type * @param marker - Marker that triggered the event * @param coordinates - Optional coordinates */ private emitEvent; /** * Escapes HTML to prevent XSS * * @param text - Text to escape * @returns Escaped HTML string */ private escapeHtml; /** * Creates popup content HTML for a marker * Note: Edit/delete buttons are now shown as icon overlays, not in popup * * @param marker - Marker data * @returns HTML string for popup content */ private createPopupContent; /** * Updates Leaflet marker display (tooltip/popup) * * @param leafletMarker - Leaflet marker instance * @param marker - Marker data */ private updateLeafletMarkerDisplay; /** * Handles marker edit action * * @param marker - Marker to edit */ private handleEdit; /** * Handles marker delete action * * @param marker - Marker to delete */ private handleDelete; /** * Handles marker edit action (called from external controls) * * @param marker - Marker to edit */ handleMarkerEdit(marker: Marker): void; /** * Handles marker delete action (called from external controls) * * @param marker - Marker to delete */ handleMarkerDelete(marker: Marker): void; /** * Validates a marker * * @param marker - Marker to validate * @throws {Error} If marker is invalid */ private validateMarker; /** * Validates marker data structure for import * * @param markerData - Marker data to validate * @throws {Error} If marker data is invalid */ private validateMarkerData; /** * Validates marker coordinates are within bounds * * @param markerData - Marker data to validate * @throws {Error} If coordinates are out of bounds */ private validateMarkerCoordinates; /** * Generates a unique marker ID * * @returns Unique marker ID */ private generateMarkerId; } //# sourceMappingURL=marker-manager.d.ts.map