From a5fbbb136c0f9b0c9c7bf92a0edf0d4893eaf840 Mon Sep 17 00:00:00 2001 From: lodev09 Date: Thu, 26 Feb 2026 06:15:11 +0800 Subject: [PATCH 1/5] feat: add onPress event to Marker component --- AGENTS.md | 1 + .../main/java/com/luggmaps/LuggMarkerView.kt | 6 ++++++ .../com/luggmaps/LuggMarkerViewManager.kt | 3 +++ .../com/luggmaps/core/GoogleMapProvider.kt | 15 +++++++++++++- .../com/luggmaps/events/MarkerPressEvent.kt | 12 +++++++++++ docs/MARKER.md | 1 + ios/LuggMarkerView.h | 1 + ios/LuggMarkerView.mm | 6 ++++++ ios/core/AppleMapProvider.mm | 14 +++++++++++++ ios/core/GoogleMapProvider.mm | 13 ++++++++++++ ios/events/MarkerPressEvent.h | 20 +++++++++++++++++++ src/components/Marker.tsx | 6 ++++++ src/fabric/LuggMarkerViewNativeComponent.ts | 2 ++ 13 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 android/src/main/java/com/luggmaps/events/MarkerPressEvent.kt create mode 100644 ios/events/MarkerPressEvent.h diff --git a/AGENTS.md b/AGENTS.md index b65868d..6a54ac5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,6 +6,7 @@ 2. YOU MUST NOT commit changes yourself until I explicitly tell you to. 3. YOU MUST NOT create summary documents unless you are told to. 4. YOU MUST NOT add code comments that are obvious. +5. Always update relevant docs in `docs/` when making changes to components or APIs. ## Project Overview diff --git a/android/src/main/java/com/luggmaps/LuggMarkerView.kt b/android/src/main/java/com/luggmaps/LuggMarkerView.kt index e9ba860..f81369e 100644 --- a/android/src/main/java/com/luggmaps/LuggMarkerView.kt +++ b/android/src/main/java/com/luggmaps/LuggMarkerView.kt @@ -11,6 +11,8 @@ import com.facebook.react.views.view.ReactViewGroup import com.google.android.gms.maps.model.AdvancedMarker import com.google.android.gms.maps.model.BitmapDescriptor import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.luggmaps.events.MarkerPressEvent +import com.luggmaps.extensions.dispatchEvent interface LuggMarkerViewDelegate { fun markerViewDidUpdate(markerView: LuggMarkerView) @@ -238,6 +240,10 @@ class LuggMarkerView(context: Context) : ReactViewGroup(context) { this.rasterize = rasterize } + fun emitPressEvent() { + dispatchEvent(MarkerPressEvent(this)) + } + fun setName(name: String?) { this.name = name } diff --git a/android/src/main/java/com/luggmaps/LuggMarkerViewManager.kt b/android/src/main/java/com/luggmaps/LuggMarkerViewManager.kt index e330a0d..b6fffad 100644 --- a/android/src/main/java/com/luggmaps/LuggMarkerViewManager.kt +++ b/android/src/main/java/com/luggmaps/LuggMarkerViewManager.kt @@ -20,6 +20,9 @@ class LuggMarkerViewManager : override fun getName(): String = NAME override fun createViewInstance(context: ThemedReactContext): LuggMarkerView = LuggMarkerView(context) + override fun getExportedCustomDirectEventTypeConstants(): Map = + mapOf("topMarkerPress" to mapOf("registrationName" to "onMarkerPress")) + override fun onDropViewInstance(view: LuggMarkerView) { super.onDropViewInstance(view) view.onDropViewInstance() diff --git a/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt b/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt index 9a0f3d1..d47fa58 100644 --- a/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt +++ b/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt @@ -12,6 +12,7 @@ import com.google.android.gms.maps.OnMapReadyCallback import com.google.android.gms.maps.model.AdvancedMarker import com.google.android.gms.maps.model.AdvancedMarkerOptions import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Marker import com.google.android.gms.maps.model.MapColorScheme import com.google.android.gms.maps.model.Polygon import com.google.android.gms.maps.model.PolygonOptions @@ -35,7 +36,8 @@ class GoogleMapProvider(private val context: Context) : GoogleMap.OnCameraIdleListener, GoogleMap.OnMapClickListener, GoogleMap.OnMapLongClickListener, - GoogleMap.OnPolygonClickListener { + GoogleMap.OnPolygonClickListener, + GoogleMap.OnMarkerClickListener { override var delegate: MapProviderDelegate? = null override val isMapReady: Boolean get() = _isMapReady @@ -52,6 +54,7 @@ class GoogleMapProvider(private val context: Context) : private val pendingPolygonViews = mutableSetOf() private val polylineAnimators = mutableMapOf() private val polygonToViewMap = mutableMapOf() + private val markerToViewMap = mutableMapOf() private var tapLocation: LatLng? = null // Initial camera settings @@ -105,6 +108,7 @@ class GoogleMapProvider(private val context: Context) : polylineAnimators.values.forEach { it.destroy() } polylineAnimators.clear() polygonToViewMap.clear() + markerToViewMap.clear() wrapperView?.touchEventHandler = null wrapperView = null googleMap?.setOnCameraMoveStartedListener(null) @@ -113,6 +117,7 @@ class GoogleMapProvider(private val context: Context) : googleMap?.setOnMapClickListener(null) googleMap?.setOnMapLongClickListener(null) googleMap?.setOnPolygonClickListener(null) + googleMap?.setOnMarkerClickListener(null) googleMap?.clear() googleMap = null _isMapReady = false @@ -134,6 +139,7 @@ class GoogleMapProvider(private val context: Context) : map.setOnMapClickListener(this) map.setOnMapLongClickListener(this) map.setOnPolygonClickListener(this) + map.setOnMarkerClickListener(this) wrapperView?.touchEventHandler = { event -> if (event.action == android.view.MotionEvent.ACTION_DOWN) { @@ -201,6 +207,11 @@ class GoogleMapProvider(private val context: Context) : } } + override fun onMarkerClick(marker: Marker): Boolean { + markerToViewMap[marker]?.emitPressEvent() + return false + } + // endregion // region Props @@ -339,6 +350,7 @@ class GoogleMapProvider(private val context: Context) : } override fun removeMarkerView(markerView: LuggMarkerView) { + markerView.marker?.let { markerToViewMap.remove(it) } markerView.marker?.remove() markerView.marker = null } @@ -391,6 +403,7 @@ class GoogleMapProvider(private val context: Context) : marker.rotation = markerView.rotate markerView.marker = marker + markerToViewMap[marker] = markerView markerView.applyIconToMarker() } diff --git a/android/src/main/java/com/luggmaps/events/MarkerPressEvent.kt b/android/src/main/java/com/luggmaps/events/MarkerPressEvent.kt new file mode 100644 index 0000000..8e04d25 --- /dev/null +++ b/android/src/main/java/com/luggmaps/events/MarkerPressEvent.kt @@ -0,0 +1,12 @@ +package com.luggmaps.events + +import android.view.View +import com.facebook.react.bridge.Arguments +import com.facebook.react.uimanager.UIManagerHelper +import com.facebook.react.uimanager.events.Event + +class MarkerPressEvent(view: View) : Event(UIManagerHelper.getSurfaceId(view), view.id) { + override fun getEventName() = "topMarkerPress" + + override fun getEventData() = Arguments.createMap() +} diff --git a/docs/MARKER.md b/docs/MARKER.md index 6d05c5d..c914e3d 100644 --- a/docs/MARKER.md +++ b/docs/MARKER.md @@ -39,6 +39,7 @@ import { MapView, Marker } from '@lugg/maps'; | `rotate` | `number` | `0` | Rotation angle in degrees clockwise from north | | `scale` | `number` | `1` | Scale factor for the marker | | `rasterize` | `boolean` | `true` | Rasterize custom marker view to bitmap (iOS/Android only) | +| `onPress` | `() => void` | - | Called when the marker is pressed | | `children` | `ReactNode` | - | Custom marker view | ## Custom Markers diff --git a/ios/LuggMarkerView.h b/ios/LuggMarkerView.h index 200efa0..c06e44f 100644 --- a/ios/LuggMarkerView.h +++ b/ios/LuggMarkerView.h @@ -32,6 +32,7 @@ NS_ASSUME_NONNULL_BEGIN - (nullable UIImage *)createIconImage; - (nullable UIImage *)createScaledIconImage; - (void)resetIconViewTransform; +- (void)emitPressEvent; @end diff --git a/ios/LuggMarkerView.mm b/ios/LuggMarkerView.mm index dad53a3..df62bd6 100644 --- a/ios/LuggMarkerView.mm +++ b/ios/LuggMarkerView.mm @@ -1,4 +1,5 @@ #import "LuggMarkerView.h" +#import "events/MarkerPressEvent.h" #import #import @@ -8,6 +9,7 @@ #import "RCTFabricComponentsPlugins.h" using namespace facebook::react; +using namespace luggmaps::events; @interface LuggMarkerView () @end @@ -208,6 +210,10 @@ - (UIImage *)createScaledIconImage { }]; } +- (void)emitPressEvent { + MarkerPressEvent::emit(_eventEmitter); +} + - (void)resetIconViewTransform { _iconView.transform = CGAffineTransformIdentity; _iconView.layer.anchorPoint = CGPointMake(0.5, 0.5); diff --git a/ios/core/AppleMapProvider.mm b/ios/core/AppleMapProvider.mm index 6b14a0d..7d72821 100644 --- a/ios/core/AppleMapProvider.mm +++ b/ios/core/AppleMapProvider.mm @@ -461,6 +461,20 @@ - (MKOverlayRenderer *)mapView:(MKMapView *)mapView return nil; } +- (void)mapView:(MKMapView *)mapView + didSelectAnnotationView:(MKAnnotationView *)view { + if (![view.annotation isKindOfClass:[AppleMarkerAnnotation class]]) + return; + + AppleMarkerAnnotation *annotation = + (AppleMarkerAnnotation *)view.annotation; + LuggMarkerView *markerView = annotation.markerView; + + if (markerView) { + [markerView emitPressEvent]; + } +} + #pragma mark - MarkerViewDelegate - (void)markerViewDidLayout:(LuggMarkerView *)markerView { diff --git a/ios/core/GoogleMapProvider.mm b/ios/core/GoogleMapProvider.mm index 5ac252c..a2af235 100644 --- a/ios/core/GoogleMapProvider.mm +++ b/ios/core/GoogleMapProvider.mm @@ -22,6 +22,7 @@ @implementation GoogleMapProvider { NSMutableArray *_pendingPolygonViews; NSMapTable *_polylineAnimators; NSMapTable *_polygonToViewMap; + NSMapTable *_markerToViewMap; // Edge insets animation CADisplayLink *_edgeInsetsDisplayLink; @@ -42,6 +43,7 @@ - (instancetype)init { _pendingPolygonViews = [NSMutableArray array]; _polylineAnimators = [NSMapTable weakToStrongObjectsMapTable]; _polygonToViewMap = [NSMapTable strongToWeakObjectsMapTable]; + _markerToViewMap = [NSMapTable strongToWeakObjectsMapTable]; } return self; } @@ -105,6 +107,7 @@ - (void)destroy { [_pendingPolygonViews removeAllObjects]; [_polylineAnimators removeAllObjects]; [_polygonToViewMap removeAllObjects]; + [_markerToViewMap removeAllObjects]; [_mapView clear]; [_mapView removeFromSuperview]; _mapView = nil; @@ -292,6 +295,14 @@ - (void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSOverlay *)overlay { [polygonView emitPressEvent]; } +- (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker { + LuggMarkerView *markerView = [_markerToViewMap objectForKey:marker]; + if (markerView) { + [markerView emitPressEvent]; + } + return NO; +} + #pragma mark - MarkerViewDelegate - (void)markerViewDidLayout:(LuggMarkerView *)markerView { @@ -324,6 +335,7 @@ - (void)addMarkerView:(LuggMarkerView *)markerView { - (void)removeMarkerView:(LuggMarkerView *)markerView { GMSAdvancedMarker *marker = (GMSAdvancedMarker *)markerView.marker; if (marker) { + [_markerToViewMap removeObjectForKey:marker]; marker.iconView = nil; marker.map = nil; markerView.marker = nil; @@ -376,6 +388,7 @@ - (void)addMarkerViewToMap:(LuggMarkerView *)markerView { marker.map = _mapView; markerView.marker = marker; + [_markerToViewMap setObject:markerView forKey:marker]; } - (void)applyMarkerStyle:(LuggMarkerView *)markerView diff --git a/ios/events/MarkerPressEvent.h b/ios/events/MarkerPressEvent.h new file mode 100644 index 0000000..f100177 --- /dev/null +++ b/ios/events/MarkerPressEvent.h @@ -0,0 +1,20 @@ +#pragma once + +#import + +namespace luggmaps { +namespace events { + +struct MarkerPressEvent { + template + static void emit(const facebook::react::SharedEventEmitter &eventEmitter) { + if (!eventEmitter) + return; + auto emitter = std::static_pointer_cast(eventEmitter); + typename Emitter::OnMarkerPress event; + emitter->onMarkerPress(event); + } +}; + +} // namespace events +} // namespace luggmaps diff --git a/src/components/Marker.tsx b/src/components/Marker.tsx index 1284147..1c75306 100644 --- a/src/components/Marker.tsx +++ b/src/components/Marker.tsx @@ -46,6 +46,10 @@ export interface MarkerProps { * @default true */ rasterize?: boolean; + /** + * Called when the marker is pressed + */ + onPress?: () => void; /** * Custom marker view */ @@ -76,6 +80,7 @@ export class Marker extends React.PureComponent { rotate = 0, scale = 1, rasterize = true, + onPress, children, } = this.props; @@ -90,6 +95,7 @@ export class Marker extends React.PureComponent { rotate={rotate} scale={scale} rasterize={rasterize} + onMarkerPress={onPress} > {children} diff --git a/src/fabric/LuggMarkerViewNativeComponent.ts b/src/fabric/LuggMarkerViewNativeComponent.ts index cd70941..a0f8c26 100644 --- a/src/fabric/LuggMarkerViewNativeComponent.ts +++ b/src/fabric/LuggMarkerViewNativeComponent.ts @@ -3,6 +3,7 @@ import type { ViewProps, HostComponent } from 'react-native'; import type { Double, WithDefault, + DirectEventHandler, } from 'react-native/Libraries/Types/CodegenTypes'; export interface Coordinate { @@ -24,6 +25,7 @@ export interface NativeProps extends ViewProps { rotate?: WithDefault; scale?: WithDefault; rasterize?: WithDefault; + onMarkerPress?: DirectEventHandler; } export default codegenNativeComponent( From 05c799f54d188103c5668f9a871a11cff4c993f9 Mon Sep 17 00:00:00 2001 From: lodev09 Date: Thu, 26 Feb 2026 06:27:26 +0800 Subject: [PATCH 2/5] feat: add PressEventPayload to marker onPress and center camera on Apple Maps --- .../main/java/com/luggmaps/LuggMarkerView.kt | 4 +-- .../com/luggmaps/core/GoogleMapProvider.kt | 5 ++- .../com/luggmaps/events/MarkerPressEvent.kt | 26 ++++++++++++-- docs/MARKER.md | 2 +- example/shared/src/Home.tsx | 1 + example/shared/src/components/Map.tsx | 34 ++++++++++++++++--- ios/LuggMarkerView.h | 2 +- ios/LuggMarkerView.mm | 10 ++++-- ios/core/AppleMapProvider.mm | 5 ++- ios/core/GoogleMapProvider.mm | 3 +- ios/events/MarkerPressEvent.h | 11 +++++- src/components/Marker.tsx | 8 +++-- src/components/index.ts | 2 +- src/fabric/LuggMarkerViewNativeComponent.ts | 11 +++++- src/index.ts | 2 +- 15 files changed, 104 insertions(+), 22 deletions(-) diff --git a/android/src/main/java/com/luggmaps/LuggMarkerView.kt b/android/src/main/java/com/luggmaps/LuggMarkerView.kt index f81369e..087f432 100644 --- a/android/src/main/java/com/luggmaps/LuggMarkerView.kt +++ b/android/src/main/java/com/luggmaps/LuggMarkerView.kt @@ -240,8 +240,8 @@ class LuggMarkerView(context: Context) : ReactViewGroup(context) { this.rasterize = rasterize } - fun emitPressEvent() { - dispatchEvent(MarkerPressEvent(this)) + fun emitPressEvent(x: Float, y: Float) { + dispatchEvent(MarkerPressEvent(this, latitude, longitude, x, y)) } fun setName(name: String?) { diff --git a/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt b/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt index d47fa58..4a559c3 100644 --- a/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt +++ b/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt @@ -208,7 +208,10 @@ class GoogleMapProvider(private val context: Context) : } override fun onMarkerClick(marker: Marker): Boolean { - markerToViewMap[marker]?.emitPressEvent() + markerToViewMap[marker]?.let { view -> + val point = googleMap?.projection?.toScreenLocation(marker.position) + view.emitPressEvent(point?.x?.toFloat() ?: 0f, point?.y?.toFloat() ?: 0f) + } return false } diff --git a/android/src/main/java/com/luggmaps/events/MarkerPressEvent.kt b/android/src/main/java/com/luggmaps/events/MarkerPressEvent.kt index 8e04d25..4bc70de 100644 --- a/android/src/main/java/com/luggmaps/events/MarkerPressEvent.kt +++ b/android/src/main/java/com/luggmaps/events/MarkerPressEvent.kt @@ -5,8 +5,30 @@ import com.facebook.react.bridge.Arguments import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.events.Event -class MarkerPressEvent(view: View) : Event(UIManagerHelper.getSurfaceId(view), view.id) { +class MarkerPressEvent( + view: View, + private val latitude: Double, + private val longitude: Double, + private val x: Float, + private val y: Float +) : Event(UIManagerHelper.getSurfaceId(view), view.id) { override fun getEventName() = "topMarkerPress" - override fun getEventData() = Arguments.createMap() + override fun getEventData() = + Arguments.createMap().apply { + putMap( + "coordinate", + Arguments.createMap().apply { + putDouble("latitude", latitude) + putDouble("longitude", longitude) + } + ) + putMap( + "point", + Arguments.createMap().apply { + putDouble("x", x.toDouble()) + putDouble("y", y.toDouble()) + } + ) + } } diff --git a/docs/MARKER.md b/docs/MARKER.md index c914e3d..fb67c38 100644 --- a/docs/MARKER.md +++ b/docs/MARKER.md @@ -39,7 +39,7 @@ import { MapView, Marker } from '@lugg/maps'; | `rotate` | `number` | `0` | Rotation angle in degrees clockwise from north | | `scale` | `number` | `1` | Scale factor for the marker | | `rasterize` | `boolean` | `true` | Rasterize custom marker view to bitmap (iOS/Android only) | -| `onPress` | `() => void` | - | Called when the marker is pressed | +| `onPress` | `(event: MarkerPressEvent) => void` | - | Called when the marker is pressed. Event includes `coordinate` and `point` | | `children` | `ReactNode` | - | Custom marker view | ## Custom Markers diff --git a/example/shared/src/Home.tsx b/example/shared/src/Home.tsx index 2343f4e..8a8c967 100644 --- a/example/shared/src/Home.tsx +++ b/example/shared/src/Home.tsx @@ -183,6 +183,7 @@ function HomeContent() { onLongPress={(e) => formatPressEvent(e, 'Long press')} onCameraMove={(e) => formatCameraEvent(e, false)} onCameraIdle={(e) => formatCameraEvent(e, true)} + onMarkerPress={(e, m) => formatPressEvent(e, `Marker(${m.name})`)} onPolygonPress={() => setStatusText('Polygon pressed')} /> )} diff --git a/example/shared/src/components/Map.tsx b/example/shared/src/components/Map.tsx index b65ecca..e493021 100644 --- a/example/shared/src/components/Map.tsx +++ b/example/shared/src/components/Map.tsx @@ -6,6 +6,7 @@ import { Polygon, type MapViewProps, type MapCameraEvent, + type MarkerPressEvent, } from '@lugg/maps'; import Animated, { useAnimatedStyle, @@ -23,6 +24,7 @@ interface MapProps extends MapViewProps { markers: MarkerData[]; animatedPosition?: SharedValue; onPolygonPress?: () => void; + onMarkerPress?: (event: MarkerPressEvent, marker: MarkerData) => void; } const INITIAL_ZOOM = 14; @@ -40,7 +42,10 @@ const CIRCLE_COORDS = Array.from({ length: 36 }, (_, i) => { }; }); -const renderMarker = (marker: MarkerData) => { +const renderMarker = ( + marker: MarkerData, + onPress?: (event: MarkerPressEvent, marker: MarkerData) => void +) => { const { id, name, @@ -54,9 +59,20 @@ const renderMarker = (marker: MarkerData) => { imageUrl, } = marker; + const handlePress = onPress + ? (e: MarkerPressEvent) => onPress(e, marker) + : undefined; + switch (type) { case 'icon': - return ; + return ( + + ); case 'text': return ( { coordinate={coordinate} text={text ?? 'X'} color={color} + onPress={handlePress} /> ); case 'image': @@ -74,11 +91,18 @@ const renderMarker = (marker: MarkerData) => { name={name} coordinate={coordinate} source={{ uri: imageUrl }} + onPress={handlePress} /> ); case 'custom': return ( - + @@ -92,6 +116,7 @@ const renderMarker = (marker: MarkerData) => { coordinate={coordinate} title={title} description={description} + onPress={handlePress} /> ); } @@ -108,6 +133,7 @@ export const Map = forwardRef( onPress, onLongPress, onPolygonPress, + onMarkerPress, ...props }, ref @@ -156,7 +182,7 @@ export const Map = forwardRef( onCameraIdle={handleCameraIdle} {...props} > - {markers.map(renderMarker)} + {markers.map((m) => renderMarker(m, onMarkerPress))} (_eventEmitter); +- (void)emitPressEventWithPoint:(CGPoint)point { + MarkerPressEvent event{ + .latitude = _coordinate.latitude, + .longitude = _coordinate.longitude, + .x = point.x, + .y = point.y, + }; + event.emit(_eventEmitter); } - (void)resetIconViewTransform { diff --git a/ios/core/AppleMapProvider.mm b/ios/core/AppleMapProvider.mm index 7d72821..af358e0 100644 --- a/ios/core/AppleMapProvider.mm +++ b/ios/core/AppleMapProvider.mm @@ -471,7 +471,10 @@ - (void)mapView:(MKMapView *)mapView LuggMarkerView *markerView = annotation.markerView; if (markerView) { - [markerView emitPressEvent]; + CGPoint point = [_mapView convertCoordinate:markerView.coordinate + toPointToView:_mapView]; + [markerView emitPressEventWithPoint:point]; + [_mapView setCenterCoordinate:markerView.coordinate animated:YES]; } } diff --git a/ios/core/GoogleMapProvider.mm b/ios/core/GoogleMapProvider.mm index a2af235..67c4fca 100644 --- a/ios/core/GoogleMapProvider.mm +++ b/ios/core/GoogleMapProvider.mm @@ -298,7 +298,8 @@ - (void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSOverlay *)overlay { - (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker { LuggMarkerView *markerView = [_markerToViewMap objectForKey:marker]; if (markerView) { - [markerView emitPressEvent]; + CGPoint point = [_mapView.projection pointForCoordinate:marker.position]; + [markerView emitPressEventWithPoint:point]; } return NO; } diff --git a/ios/events/MarkerPressEvent.h b/ios/events/MarkerPressEvent.h index f100177..a47d523 100644 --- a/ios/events/MarkerPressEvent.h +++ b/ios/events/MarkerPressEvent.h @@ -6,12 +6,21 @@ namespace luggmaps { namespace events { struct MarkerPressEvent { + double latitude; + double longitude; + double x; + double y; + template - static void emit(const facebook::react::SharedEventEmitter &eventEmitter) { + void emit(const facebook::react::SharedEventEmitter &eventEmitter) const { if (!eventEmitter) return; auto emitter = std::static_pointer_cast(eventEmitter); typename Emitter::OnMarkerPress event; + event.coordinate.latitude = latitude; + event.coordinate.longitude = longitude; + event.point.x = x; + event.point.y = y; emitter->onMarkerPress(event); } }; diff --git a/src/components/Marker.tsx b/src/components/Marker.tsx index 1c75306..a5b545d 100644 --- a/src/components/Marker.tsx +++ b/src/components/Marker.tsx @@ -1,8 +1,10 @@ import React from 'react'; import type { ReactNode } from 'react'; -import { StyleSheet } from 'react-native'; +import { StyleSheet, type NativeSyntheticEvent } from 'react-native'; import LuggMarkerViewNativeComponent from '../fabric/LuggMarkerViewNativeComponent'; -import type { Coordinate, Point } from '../types'; +import type { Coordinate, Point, PressEventPayload } from '../types'; + +export type MarkerPressEvent = NativeSyntheticEvent; export interface MarkerProps { /** @@ -49,7 +51,7 @@ export interface MarkerProps { /** * Called when the marker is pressed */ - onPress?: () => void; + onPress?: (event: MarkerPressEvent) => void; /** * Custom marker view */ diff --git a/src/components/index.ts b/src/components/index.ts index 1ab2158..3783ad4 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,5 +1,5 @@ export { Marker } from './Marker'; -export type { MarkerProps } from './Marker'; +export type { MarkerProps, MarkerPressEvent } from './Marker'; export { Polygon } from './Polygon'; export type { PolygonProps } from './Polygon'; export { Polyline } from './Polyline'; diff --git a/src/fabric/LuggMarkerViewNativeComponent.ts b/src/fabric/LuggMarkerViewNativeComponent.ts index a0f8c26..ac5929c 100644 --- a/src/fabric/LuggMarkerViewNativeComponent.ts +++ b/src/fabric/LuggMarkerViewNativeComponent.ts @@ -25,7 +25,16 @@ export interface NativeProps extends ViewProps { rotate?: WithDefault; scale?: WithDefault; rasterize?: WithDefault; - onMarkerPress?: DirectEventHandler; + onMarkerPress?: DirectEventHandler<{ + coordinate: { + latitude: Double; + longitude: Double; + }; + point: { + x: Double; + y: Double; + }; + }>; } export default codegenNativeComponent( diff --git a/src/index.ts b/src/index.ts index 3d1f0f6..b1f7846 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,7 @@ export { MapView } from './MapView'; export { MapProvider } from './MapProvider'; export type { MapProviderProps } from './MapProvider.types'; export { Marker } from './components'; -export type { MarkerProps } from './components'; +export type { MarkerProps, MarkerPressEvent } from './components'; export { Polygon } from './components'; export type { PolygonProps } from './components'; export { Polyline } from './components'; From 08cfb636c20d6216f7c4e57bffa8a70dcb430c43 Mon Sep 17 00:00:00 2001 From: lodev09 Date: Thu, 26 Feb 2026 06:33:02 +0800 Subject: [PATCH 3/5] fix(ios): suppress map press when tapping marker on Apple Maps --- docs/MAPVIEW.md | 18 ++++++++++++++++-- example/shared/src/Home.tsx | 17 +++++++++++++++-- ios/core/AppleMapProvider.mm | 16 ++++++++++++++++ 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/docs/MAPVIEW.md b/docs/MAPVIEW.md index d07df43..1e63515 100644 --- a/docs/MAPVIEW.md +++ b/docs/MAPVIEW.md @@ -35,8 +35,11 @@ import { MapView } from '@lugg/maps'; | `userLocationEnabled` | `boolean` | `false` | Show current user location on the map | | `userLocationButtonEnabled` | `boolean` | `false` | Show native my-location button (Android only) | | `theme` | `'light' \| 'dark' \| 'system'` | `'system'` | Map color theme | -| `onCameraMove` | `(event) => void` | - | Called when camera moves | -| `onCameraIdle` | `(event) => void` | - | Called when camera stops moving | +| `onPress` | `(event: MapPressEvent) => void` | - | Called when the map is pressed | +| `onLongPress` | `(event: MapPressEvent) => void` | - | Called when the map is long pressed | +| `onCameraMove` | `(event: MapCameraEvent) => void` | - | Called when camera moves | +| `onCameraIdle` | `(event: MapCameraEvent) => void` | - | Called when camera stops moving | +| `onReady` | `() => void` | - | Called when map is loaded and ready | ## Ref Methods @@ -108,6 +111,17 @@ interface SetEdgeInsetsOptions { ## Events +### onPress / onLongPress + +Called when the map is pressed or long pressed. Event includes the geographic coordinate and screen point. + +```ts +interface PressEventPayload { + coordinate: Coordinate; + point: Point; +} +``` + ### onCameraMove Called continuously while the camera is moving. diff --git a/example/shared/src/Home.tsx b/example/shared/src/Home.tsx index 8a8c967..8b1d575 100644 --- a/example/shared/src/Home.tsx +++ b/example/shared/src/Home.tsx @@ -66,6 +66,14 @@ function HomeContent() { const [markers, setMarkers] = useState(INITIAL_MARKERS); const [statusText, setStatusText] = useState('Loading...'); const lastCoordinate = useRef({ latitude: 37.78, longitude: -122.43 }); + const statusLockRef = useRef(false); + + const lockStatus = useCallback(() => { + statusLockRef.current = true; + setTimeout(() => { + statusLockRef.current = false; + }, 1000); + }, []); const getSheetBottom = useCallback( (event: DetentChangeEvent) => screenHeight - event.nativeEvent.position, @@ -97,6 +105,7 @@ function HomeContent() { const formatPressEvent = useCallback( (event: MapPressEvent, label: string) => { + lockStatus(); const { coordinate, point } = event.nativeEvent; const lat = coordinate.latitude.toFixed(5); const lng = coordinate.longitude.toFixed(5); @@ -104,13 +113,14 @@ function HomeContent() { const py = point.y.toFixed(0); setStatusText(`${label}: ${lat}, ${lng} (${px}, ${py})`); }, - [] + [lockStatus] ); const formatCameraEvent = useCallback( (event: MapCameraEvent, idle: boolean) => { const { coordinate, zoom, gesture } = event.nativeEvent; lastCoordinate.current = coordinate; + if (statusLockRef.current) return; const pos = `${coordinate.latitude.toFixed( 5 )}, ${coordinate.longitude.toFixed(5)} (z${zoom.toFixed(1)})`; @@ -184,7 +194,10 @@ function HomeContent() { onCameraMove={(e) => formatCameraEvent(e, false)} onCameraIdle={(e) => formatCameraEvent(e, true)} onMarkerPress={(e, m) => formatPressEvent(e, `Marker(${m.name})`)} - onPolygonPress={() => setStatusText('Polygon pressed')} + onPolygonPress={() => { + lockStatus(); + setStatusText('Polygon pressed'); + }} /> )} diff --git a/ios/core/AppleMapProvider.mm b/ios/core/AppleMapProvider.mm index af358e0..c7f9947 100644 --- a/ios/core/AppleMapProvider.mm +++ b/ios/core/AppleMapProvider.mm @@ -296,11 +296,27 @@ - (LuggPolygonView *)hitTestPolygonAtPoint:(CGPoint)point { return nil; } +- (BOOL)hitTestAnnotationAtPoint:(CGPoint)point { + for (id annotation in _mapView.annotations) { + MKAnnotationView *view = [_mapView viewForAnnotation:annotation]; + if (!view) + continue; + CGPoint local = [view convertPoint:point fromView:_mapView]; + if ([view pointInside:local withEvent:nil]) + return YES; + } + return NO; +} + - (void)handleTap:(UITapGestureRecognizer *)gesture { if (gesture.state != UIGestureRecognizerStateEnded) return; CGPoint point = [gesture locationInView:_mapView]; + + if ([self hitTestAnnotationAtPoint:point]) + return; + LuggPolygonView *polygonView = [self hitTestPolygonAtPoint:point]; if (polygonView) { [polygonView emitPressEvent]; From 93d5563cb800f7e373ff60e45e3efeafa4ad3b87 Mon Sep 17 00:00:00 2001 From: lodev09 Date: Thu, 26 Feb 2026 07:09:19 +0800 Subject: [PATCH 4/5] feat(web): add onPress support to Marker.web.tsx --- src/components/Marker.web.tsx | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/components/Marker.web.tsx b/src/components/Marker.web.tsx index 5233cc9..a5385ea 100644 --- a/src/components/Marker.web.tsx +++ b/src/components/Marker.web.tsx @@ -1,3 +1,4 @@ +import { useCallback } from 'react'; import { AdvancedMarker } from '@vis.gl/react-google-maps'; import type { MarkerProps } from './Marker'; @@ -10,12 +11,34 @@ export function Marker({ zIndex, rotate, scale, + onPress, children, }: MarkerProps) { const transforms: string[] = []; if (rotate) transforms.push(`rotate(${rotate}deg)`); if (scale && scale !== 1) transforms.push(`scale(${scale})`); + const handleClick = useCallback( + (e: google.maps.MapMouseEvent) => { + if (!onPress) return; + const latLng = e.latLng; + const domEvent = e.domEvent as MouseEvent; + onPress({ + nativeEvent: { + coordinate: { + latitude: latLng?.lat() ?? coordinate.latitude, + longitude: latLng?.lng() ?? coordinate.longitude, + }, + point: { + x: domEvent?.clientX ?? 0, + y: domEvent?.clientY ?? 0, + }, + }, + } as any); + }, + [onPress, coordinate] + ); + return ( 0 ? { transform: transforms.join(' ') } : undefined } From 6b9ff95e79bc83ac184bbdce933b32b1dc952e92 Mon Sep 17 00:00:00 2001 From: lodev09 Date: Thu, 26 Feb 2026 07:16:42 +0800 Subject: [PATCH 5/5] fix(web): account for edge insets offset when centering on marker Expose moveCamera through MapContext so Marker.web uses offsetCenter instead of raw panTo, matching MapView's moveCamera behavior. --- src/MapProvider.web.tsx | 3 ++- src/MapView.web.tsx | 12 +++++++++++- src/components/Marker.web.tsx | 10 +++++++--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/MapProvider.web.tsx b/src/MapProvider.web.tsx index b05b5c7..a316ef3 100644 --- a/src/MapProvider.web.tsx +++ b/src/MapProvider.web.tsx @@ -5,7 +5,8 @@ import type { MapProviderProps } from './MapProvider.types'; export const MapContext = createContext<{ map: google.maps.Map | null; isDragging: boolean; -}>({ map: null, isDragging: false }); + moveCamera: (coordinate: { latitude: number; longitude: number }) => void; +}>({ map: null, isDragging: false, moveCamera: () => {} }); export const useMapContext = () => useContext(MapContext); diff --git a/src/MapView.web.tsx b/src/MapView.web.tsx index dd13e2d..31f358b 100644 --- a/src/MapView.web.tsx +++ b/src/MapView.web.tsx @@ -189,6 +189,16 @@ export const MapView = forwardRef(function MapView( [map, initialZoom, offsetCenter] ); + const panToCoordinate = useCallback( + (coordinate: Coordinate) => { + if (!map) return; + const zoom = map.getZoom() || initialZoom; + const center = offsetCenter(coordinate, zoom, undefined, false); + map.panTo(center); + }, + [map, initialZoom, offsetCenter] + ); + useImperativeHandle( ref, () => ({ @@ -336,7 +346,7 @@ export const MapView = forwardRef(function MapView( : undefined; return ( - + `-${value * 100}%`; @@ -14,12 +15,15 @@ export function Marker({ onPress, children, }: MarkerProps) { + const { moveCamera } = useMapContext(); const transforms: string[] = []; if (rotate) transforms.push(`rotate(${rotate}deg)`); if (scale && scale !== 1) transforms.push(`scale(${scale})`); const handleClick = useCallback( (e: google.maps.MapMouseEvent) => { + moveCamera(coordinate); + if (!onPress) return; const latLng = e.latLng; const domEvent = e.domEvent as MouseEvent; @@ -36,7 +40,7 @@ export function Marker({ }, } as any); }, - [onPress, coordinate] + [moveCamera, onPress, coordinate] ); return ( @@ -46,8 +50,8 @@ export function Marker({ zIndex={zIndex} anchorLeft={anchor ? toWebAnchor(anchor.x) : undefined} anchorTop={anchor ? toWebAnchor(anchor.y) : undefined} - clickable={!!onPress} - onClick={onPress ? handleClick : undefined} + clickable + onClick={handleClick} style={ transforms.length > 0 ? { transform: transforms.join(' ') } : undefined }