User Interaction Events
The User Interaction Events system in the TomTom Maps SDK provides enhanced event handling capabilities that build upon MapLibre’s native events. This system simplifies complex user interactions and provides consistent event handling across different map elements and modules, enabling rich interactive mapping experiences.
Event System Architecture
The TomTom SDK’s event system addresses common MapLibre event handling challenges while maintaining full compatibility with the underlying MapLibre event infrastructure. This dual-layer approach provides both familiar MapLibre patterns and enhanced TomTom-specific functionality.
Enhanced Event Capabilities
Problems Solved by TomTom Events:
- Complex hover implementation: MapLibre hover events require manual timing, state management, and precision handling
- Event conflicts: Multiple interactive sources can interfere with each other without proper coordination
- Data transformation: Raw MapLibre events need conversion to application-specific formats with relevant context
- Precision issues: Accurate event detection with appropriate tolerance is difficult to implement consistently
TomTom SDK Solutions:
- Unified event API: Consistent event handling patterns across all SDK modules through dedicated
eventsobjects - Built-in hover support: Automatic hover detection with configurable delays, thresholds, and state management
- Conflict resolution: Smart event routing between different map modules and layers prevents interference
- Rich data integration: Events include relevant TomTom data and contextual information automatically
Module-Specific Event Systems
Each TomTom module provides its own dedicated event system through an events object, eliminating the need to work directly with MapLibre events for module-specific interactions:
import { PlacesModule, TrafficIncidentsModule, RoutingModule } from '@tomtom-org/maps-sdk/map';
// Each module exposes its own events objectconst placesModule = await PlacesModule.get(map);const trafficModule = await TrafficIncidentsModule.get(map);const routesModule = await RoutingModule.get(map);
// Access module-specific events through the events objectplacesModule.events.on('click', handlePlaceClick);trafficModule.events.on('hover', handleTrafficHover);routesModule.events.on('click', handleRouteClick);Basic Event Handling
Standard MapLibre Event Integration
The SDK maintains full compatibility with MapLibre events for general map interactions:
import { TomTomMap } from '@tomtom-org/maps-sdk/map';
const map = new TomTomMap({ mapLibre: { container: 'map' } });
// Standard MapLibre events for general map interactionmap.mapLibreMap.on('click', (event) => { console.log('Map clicked at:', event.lngLat);});
map.mapLibreMap.on('moveend', () => { const bounds = map.mapLibreMap.getBounds(); console.log('New map bounds:', bounds);});TomTom Module Event Handling
Places Module Events
The Places Module provides rich event handling for place interactions through its dedicated events system:
import { PlacesModule } from '@tomtom-org/maps-sdk/map';
const placesModule = await PlacesModule.get(map);
// Use the module's events object for TomTom-specific interactionsplacesModule.events.on('click', (feature, lngLat) => { console.log('Place clicked:', feature.properties.name); console.log('Click coordinates:', lngLat); displayPlaceDetails(feature.properties);});
placesModule.events.on('hover', (feature) => { console.log('Place hovered:', feature.properties.name); showPlacePreview(feature.properties);});Traffic Module Events
Traffic incidents and flow data provide specialized event handling for traffic-related interactions:
import { TrafficIncidentsModule } from '@tomtom-org/maps-sdk/map';
const trafficIncidentsModule = await TrafficIncidentsModule.get(map);
// Traffic-specific events with enriched datatrafficIncidentsModule.events.on('click', (feature, lngLat) => { displayTrafficIncident({ type: feature.properties.type, severity: feature.properties.severity, description: feature.properties.description, delay: feature.properties.delay, location: lngLat });});
trafficModule.events.on('long-hover', (feature, lngLat) => { showTrafficTooltip(feature.properties, lngLat);});Routing Module Events
Route interactions provide detailed information about route segments and waypoints:
import { RoutingModule } from '@tomtom-org/maps-sdk/map';
const routesModule = await RoutingModule.get(map);
routesModule.events.on('click', (feature) => { console.log('Route clicked:', feature.properties.routeId); console.log('Route segment:', feature.properties.segmentIndex); showRouteDetails(feature.properties);});Advanced Event Types
Hover Events
The SDK introduces sophisticated hover detection that addresses the complexity of implementing reliable hover functionality with MapLibre. Traditional hover implementation requires managing mouse enter/leave events, dealing with timing issues, and handling state management across different map features.
Hover Event Challenges:
- Timing complexity: Raw mouse events require debouncing and timing management
- State management: Tracking which features are currently hovered requires careful state handling
- Performance concerns: Frequent mouse events can impact application performance
- Cross-layer conflicts: Multiple interactive layers can interfere with hover detection
TomTom Hover Solutions: The SDK provides automatic hover detection with built-in timing, state management, and performance optimization:
// Standard hover: Immediate response to mouse enterplacesModule.events.on('hover', (feature) => { showQuickPreview(feature.properties);});Long-Hover Events
Long-hover events are particularly valuable for displaying detailed information without cluttering the interface with immediate popups. This pattern is ideal for progressive disclosure interfaces where users can get quick information by hovering briefly, or detailed information by maintaining hover focus.
Long-Hover Implementation Benefits:
- Prevents accidental triggers: Users must maintain focus for a specified duration
- Reduces UI noise: Detailed information appears only when users demonstrate intent
- Improves performance: Expensive operations (like API calls) are triggered only after sustained interest
- Enhanced UX: Creates a natural progression from quick glance to detailed exploration
Configurable Long-Hover Timing: The long-hover delay is configurable to match your application’s UX requirements:
// Long hover with default timing (typically 800ms)trafficModule.events.on('long-hover', (feature, lngLat) => { fetchDetailedTrafficInfo(feature.properties.incidentId) .then(details => showDetailedIncident(details, lngLat));});Event Timing and Configuration
Different modules may have different timing requirements based on the type of interaction and data complexity:
// Example of how timing might vary by module type// (Note: Actual configuration methods may vary by implementation)trafficModule.events.on('long-hover', (feature) => { // Triggered after longer delay for complex traffic data displayComprehensiveTrafficAnalysis(feature.properties);});
placesModule.events.on('hover', (feature) => { // Triggered quickly for simple place information showPlaceBasicInfo(feature.properties);});Event Data Structure
Enhanced Event Payloads
TomTom module events provide enriched data compared to raw MapLibre events.
Modules where service data is added (places, routes, geometries) will map their user events to the original added data.
placesModule.events.on('click', (feature, lngLat, allFeatures, source) => { // feature: GeoJSON feature with TomTom-specific properties console.log('Feature data:', feature.properties);
// lngLat: Precise click coordinates console.log('Click location:', lngLat);
// allFeatures: All features at the event location (array of MapGeoJSONFeature) console.log('All features at location:', allFeatures.length);
// source: Source and layer configuration for the feature console.log('Source:', source);});Event Configuration
Map-Level Configuration (MapEventsConfig)
Configure global event behavior when initializing the map through the events option. These settings affect all interactive modules and layers:
import { TomTomMap } from '@tomtom-org/maps-sdk/map';
const map = new TomTomMap({ mapLibre: { container: 'map' }, events: { precisionMode: 'box', paddingBoxPx: 10, cursorOnHover: 'pointer', longHoverDelayAfterMapMoveMS: 800, longHoverDelayOnStillMapMS: 300 }});Precision Configuration
Control how accurately events detect features:
// Standard: Easier clicking with 5px tolerance (default)events: { precisionMode: 'box', paddingBoxPx: 5}
// Mobile: Larger touch targetsevents: { precisionMode: 'box', paddingBoxPx: 15}
// Precise: Exact pixel matching for dense dataevents: { precisionMode: 'point'}Hover Timing Configuration
Adjust long-hover delays to match your UX requirements:
// Responsive: Quick tooltips after map settlesevents: { longHoverDelayAfterMapMoveMS: 500, // After panning longHoverDelayOnStillMapMS: 200 // Subsequent hovers}
// Conservative: Prevent accidental triggersevents: { longHoverDelayAfterMapMoveMS: 1200, longHoverDelayOnStillMapMS: 500}Cursor Styling
Customize cursor appearance for different interaction states:
events: { cursorOnMap: 'grab', // Default cursor cursorOnHover: 'pointer', // When hovering features cursorOnMouseDown: 'grabbing' // During click/drag}Module-Level Configuration (EventHandlerConfig)
Individual modules can override cursor behavior for their specific features:
import { PlacesModule } from '@tomtom-org/maps-sdk/map';
const placesModule = await PlacesModule.get(map, { events: { cursorOnHover: 'help' // Custom cursor for places }});
// Different cursor for different modulesconst trafficModule = await TrafficIncidentsModule.get(map, { events: { cursorOnHover: 'crosshair' // Different cursor for traffic }});Handling Events on the Rest of the Map
Detecting Clicks Outside Interactive Features
When building interactive maps, you often need to detect when users interact with the base map itself—areas outside of your custom features like places, routes, or traffic incidents. This is essential for:
- Clearing selections: Deselecting features when users click empty map areas
- Resetting UI state: Hiding popups or info panels when focus moves away
- Triggering map-wide actions: Performing reverse geocoding on arbitrary map locations
- Managing interaction modes: Switching between different interaction states
The BaseMapModule provides a solution for detecting these “rest of the map” interactions through its event system.
Using BaseMapModule for Background Interactions
The BaseMapModule manages all fundamental map layers (roads, buildings, land, water, etc.). By using its event handlers, you can detect user interactions with the underlying map:
import { BaseMapModule } from '@tomtom-org/maps-sdk/map';
const baseMap = await BaseMapModule.get(map);
// Detect clicks on the base mapbaseMap.events.on('click', (feature, lngLat) => { console.log('Map clicked at:', lngLat); clearAllSelections(); performReverseGeocoding(lngLat);});
// Detect hovers over the base mapbaseMap.events.on('hover', (feature, lngLat) => { console.log('Hovering over:', feature.properties); hideAllPopups();});Separating Interactive and Background Layers
For more sophisticated applications, you can create separate BaseMapModule instances to differentiate between interactive features and the “rest” of the map. This pattern is particularly useful when you want certain map layers to be interactive while treating others as background.
Pattern: Interactive vs. Background Modules
This approach uses layer group filtering to create two distinct modules:
- Interactive module: Handles specific layer groups you want users to interact with
- Background module: Handles everything else, representing the “rest of the map”
import { BaseMapModule } from '@tomtom-org/maps-sdk/map';
// Define which layer groups should be interactiveconst interactiveLayerGroups = ['roadLines', 'roadLabels', 'buildings3D'];
// Module for interactive featuresconst interactiveLayers = await BaseMapModule.get(map, { layerGroupsFilter: { mode: 'include', names: interactiveLayerGroups }});
// Module for the rest of the map (background)const restOfTheMap = await BaseMapModule.get(map, { layerGroupsFilter: { mode: 'exclude', names: interactiveLayerGroups }, events: { cursorOnHover: 'default' // Keep default cursor on background }});
// Handle interactive layer clicksinteractiveLayers.events.on('click', (feature, lngLat) => { console.log('Interactive feature clicked:', feature.properties); showFeatureDetails(feature);});
// Handle background clicksrestOfTheMap.events.on('click', (feature, lngLat) => { console.log('Background clicked'); clearAllSelections();});Practical Example: Clearing Selections
A common use case is clearing feature selections when users click outside interactive elements:
import { PlacesModule, BaseMapModule } from '@tomtom-org/maps-sdk/map';
const placesModule = await PlacesModule.get(map);const baseMap = await BaseMapModule.get(map, { events: { cursorOnHover: 'default' }});
let selectedPlace = null;
// Select places on clickplacesModule.events.on('click', (feature, lngLat) => { selectedPlace = feature; highlightPlace(feature); showPlaceDetails(feature);});
// Clear selection when clicking the base mapbaseMap.events.on('click', () => { if (selectedPlace) { selectedPlace = null; clearHighlights(); hidePlaceDetails(); }});See the Rest of the Map Click example for a complete working implementation of this pattern.
Cursor Behavior for Background Interactions
When handling background map interactions, it’s important to configure the cursor appropriately to provide proper visual feedback to users.
Why Set cursorOnHover: 'default'?
By default, when hovering over any interactive module features, the cursor changes to indicate interactivity (typically to 'pointer'). However, for background map layers, you usually want to maintain the default cursor to signal that these elements are not primary interactive targets:
// Background module with default cursorconst restOfTheMap = await BaseMapModule.get(map, { layerGroupsFilter: { mode: 'exclude', names: ['places', 'traffic'] }, events: { cursorOnHover: 'default' // Prevents pointer cursor on hover }});Cursor Configuration Patterns:
// Pattern 1: Interactive features with pointer, background with defaultconst interactiveFeatures = await BaseMapModule.get(map, { layerGroupsFilter: { mode: 'include', names: ['roadLines'] }, events: { cursorOnHover: 'pointer' } // Shows as clickable});
const background = await BaseMapModule.get(map, { layerGroupsFilter: { mode: 'exclude', names: ['roadLines'] }, events: { cursorOnHover: 'default' } // Shows as not interactive});
// Pattern 2: Map-wide configuration with module overridesconst map = new TomTomMap({ mapLibre: { container: 'map' }, events: { cursorOnHover: 'pointer' // Default for all modules }});
// Override for background to use default cursorconst background = await BaseMapModule.get(map, { events: { cursorOnHover: 'default' }});Complete Example: Multi-Module Interaction
Here’s a comprehensive example showing how different modules work together with background detection:
import { BaseMapModule, PlacesModule, TrafficIncidentsModule} from '@tomtom-org/maps-sdk/map';
// Initialize modulesconst placesModule = await PlacesModule.get(map);const trafficModule = await TrafficIncidentsModule.get(map);const baseMap = await BaseMapModule.get(map, { events: { cursorOnHover: 'default' }});
let activePopup = null;
// Places interactionsplacesModule.events.on('click', (feature, lngLat) => { activePopup = showPopup('place', feature, lngLat);});
// Traffic interactionstrafficModule.events.on('click', (feature, lngLat) => { activePopup = showPopup('traffic', feature, lngLat);});
// Base map hover - clear popups when moving to backgroundbaseMap.events.on('hover', () => { if (activePopup) { activePopup.remove(); activePopup = null; }});
// Base map click - perform reverse geocodingbaseMap.events.on('click', async (feature, lngLat) => { if (activePopup) { activePopup.remove(); activePopup = null; }
const result = await reverseGeocode({ position: [lngLat.lng, lngLat.lat] });
showAddressInfo(result, lngLat);});Event Priority and Layer Order
Event priority is determined by layer rendering order - whichever layer is rendered on top receives the event first. This follows the natural visual stacking of map elements.
How Event Priority Works:
The module whose layers are rendered highest (top-most) in the layer stack receives the event:
const placesModule = await PlacesModule.get(map);const baseMap = await BaseMapModule.get(map);
// Click on a place marker: placesModule receives the event// Click on a road: baseMap receives the eventMultiple Module Instances:
When creating multiple instances of the same module type, later instances are typically rendered on top:
const roads = await BaseMapModule.get(map, { layerGroupsFilter: { mode: 'include', names: ['roadLines'] }});
const buildings = await BaseMapModule.get(map, { layerGroupsFilter: { mode: 'include', names: ['buildings3D'] }});
// buildings module is on top - receives events first for overlapping areasKey Principles:
- Layer-based priority: Top-most visible layer receives the event
- No event bubbling: Events don’t propagate to lower layers
- Visual hierarchy: What users see on top gets the interaction
- Module independence: Each module’s events operate separately
Related Guides and Examples
Services Integration
- Reverse Geocoding - Use user click events to trigger reverse geocoding and display address information
- Search Services - Handle user interactions to trigger place searches and display results
- Geocoding - Process user input events to geocode addresses and show locations
Related Examples
- Rest of the Map Click - Simple example showing how to detect clicks on places vs. the rest of the map
- Map Events - Comprehensive map event handling examples with different interaction patterns
- Pin Interaction - Interactive markers with click and hover events
- Reverse Geocoding with Map Display - Click events triggering reverse geocoding with visual feedback
- Places Customize - Custom event handling for place interactions
Related Map Modules
- Places Module - Handle user interactions with places and markers
- Routing Module - Manage user interactions with routes and route segments
- Traffic Incidents - Handle user interactions with traffic data and incidents