@lugg/maps 1.0.0-beta.0 → 1.0.0-beta.10

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.
Files changed (186) hide show
  1. package/README.md +4 -1
  2. package/android/build.gradle +1 -1
  3. package/android/src/main/java/com/luggmaps/LuggCalloutView.kt +79 -0
  4. package/android/src/main/java/com/luggmaps/LuggCalloutViewManager.kt +39 -0
  5. package/android/src/main/java/com/luggmaps/LuggCircleView.kt +85 -0
  6. package/android/src/main/java/com/luggmaps/LuggCircleViewManager.kt +79 -0
  7. package/android/src/main/java/com/luggmaps/LuggGroundOverlayView.kt +81 -0
  8. package/android/src/main/java/com/luggmaps/LuggGroundOverlayViewManager.kt +82 -0
  9. package/android/src/main/java/com/luggmaps/LuggMapView.kt +49 -0
  10. package/android/src/main/java/com/luggmaps/LuggMapViewManager.kt +31 -0
  11. package/android/src/main/java/com/luggmaps/LuggMapWrapperView.kt +0 -11
  12. package/android/src/main/java/com/luggmaps/LuggMarkerView.kt +99 -51
  13. package/android/src/main/java/com/luggmaps/LuggMarkerViewManager.kt +13 -0
  14. package/android/src/main/java/com/luggmaps/LuggPackage.kt +11 -1
  15. package/android/src/main/java/com/luggmaps/LuggPolylineView.kt +1 -22
  16. package/android/src/main/java/com/luggmaps/LuggTileOverlayView.kt +92 -0
  17. package/android/src/main/java/com/luggmaps/LuggTileOverlayViewManager.kt +80 -0
  18. package/android/src/main/java/com/luggmaps/core/GoogleMapProvider.kt +628 -18
  19. package/android/src/main/java/com/luggmaps/core/MapProviderDelegate.kt +14 -0
  20. package/android/src/main/java/com/luggmaps/core/PolylineAnimator.kt +12 -7
  21. package/android/src/main/java/com/luggmaps/events/CirclePressEvent.kt +12 -0
  22. package/android/src/main/java/com/luggmaps/events/GroundOverlayPressEvent.kt +12 -0
  23. package/android/src/main/java/com/luggmaps/events/TileOverlayPressEvent.kt +12 -0
  24. package/android/src/main/java/com/luggmaps/extensions/ViewExtensions.kt +11 -0
  25. package/ios/LuggCalloutView.h +23 -0
  26. package/ios/LuggCalloutView.mm +155 -0
  27. package/ios/LuggCircleView.h +31 -0
  28. package/ios/LuggCircleView.mm +133 -0
  29. package/ios/LuggGroundOverlayView.h +30 -0
  30. package/ios/LuggGroundOverlayView.mm +125 -0
  31. package/ios/LuggMapView.mm +84 -2
  32. package/ios/LuggMarkerView.h +5 -0
  33. package/ios/LuggMarkerView.mm +40 -2
  34. package/ios/LuggTileOverlayView.h +31 -0
  35. package/ios/LuggTileOverlayView.mm +130 -0
  36. package/ios/core/AppleMapProvider.mm +838 -34
  37. package/ios/core/GMSPolylineAnimator.m +6 -3
  38. package/ios/core/GoogleMapProvider.mm +506 -19
  39. package/ios/core/LuggAnnotationView.h +12 -0
  40. package/ios/core/LuggAnnotationView.mm +55 -0
  41. package/ios/core/MapProviderDelegate.h +20 -1
  42. package/ios/events/CirclePressEvent.h +20 -0
  43. package/ios/events/GroundOverlayPressEvent.h +20 -0
  44. package/ios/events/TileOverlayPressEvent.h +20 -0
  45. package/lib/module/MapProvider.web.js +7 -2
  46. package/lib/module/MapProvider.web.js.map +1 -1
  47. package/lib/module/MapView.js +12 -1
  48. package/lib/module/MapView.js.map +1 -1
  49. package/lib/module/MapView.web.js +36 -20
  50. package/lib/module/MapView.web.js.map +1 -1
  51. package/lib/module/components/Circle.js +46 -0
  52. package/lib/module/components/Circle.js.map +1 -0
  53. package/lib/module/components/Circle.types.js +4 -0
  54. package/lib/module/components/Circle.types.js.map +1 -0
  55. package/lib/module/components/Circle.web.js +79 -0
  56. package/lib/module/components/Circle.web.js.map +1 -0
  57. package/lib/module/components/GeoJson.js +3 -3
  58. package/lib/module/components/GeoJson.js.map +1 -1
  59. package/lib/module/components/GroundOverlay.js +45 -0
  60. package/lib/module/components/GroundOverlay.js.map +1 -0
  61. package/lib/module/components/GroundOverlay.types.js +4 -0
  62. package/lib/module/components/GroundOverlay.types.js.map +1 -0
  63. package/lib/module/components/GroundOverlay.web.js +77 -0
  64. package/lib/module/components/GroundOverlay.web.js.map +1 -0
  65. package/lib/module/components/Marker.js +41 -6
  66. package/lib/module/components/Marker.js.map +1 -1
  67. package/lib/module/components/Marker.web.js +103 -26
  68. package/lib/module/components/Marker.web.js.map +1 -1
  69. package/lib/module/components/Polygon.web.js +3 -3
  70. package/lib/module/components/Polygon.web.js.map +1 -1
  71. package/lib/module/components/Polyline.web.js +6 -12
  72. package/lib/module/components/Polyline.web.js.map +1 -1
  73. package/lib/module/components/TileOverlay.js +44 -0
  74. package/lib/module/components/TileOverlay.js.map +1 -0
  75. package/lib/module/components/TileOverlay.types.js +4 -0
  76. package/lib/module/components/TileOverlay.types.js.map +1 -0
  77. package/lib/module/components/TileOverlay.web.js +69 -0
  78. package/lib/module/components/TileOverlay.web.js.map +1 -0
  79. package/lib/module/components/index.js +3 -0
  80. package/lib/module/components/index.js.map +1 -1
  81. package/lib/module/components/index.web.js +3 -0
  82. package/lib/module/components/index.web.js.map +1 -1
  83. package/lib/module/fabric/LuggCalloutViewNativeComponent.ts +20 -0
  84. package/lib/module/fabric/LuggCircleViewNativeComponent.ts +25 -0
  85. package/lib/module/fabric/LuggGroundOverlayViewNativeComponent.ts +29 -0
  86. package/lib/module/fabric/LuggMapViewNativeComponent.ts +13 -5
  87. package/lib/module/fabric/LuggMarkerViewNativeComponent.ts +14 -2
  88. package/lib/module/fabric/LuggTileOverlayViewNativeComponent.ts +30 -0
  89. package/lib/module/index.js +3 -0
  90. package/lib/module/index.js.map +1 -1
  91. package/lib/module/index.web.js +3 -0
  92. package/lib/module/index.web.js.map +1 -1
  93. package/lib/typescript/src/MapProvider.web.d.ts +9 -2
  94. package/lib/typescript/src/MapProvider.web.d.ts.map +1 -1
  95. package/lib/typescript/src/MapView.d.ts +1 -1
  96. package/lib/typescript/src/MapView.d.ts.map +1 -1
  97. package/lib/typescript/src/MapView.types.d.ts +36 -1
  98. package/lib/typescript/src/MapView.types.d.ts.map +1 -1
  99. package/lib/typescript/src/MapView.web.d.ts +1 -1
  100. package/lib/typescript/src/MapView.web.d.ts.map +1 -1
  101. package/lib/typescript/src/components/Circle.d.ts +10 -0
  102. package/lib/typescript/src/components/Circle.d.ts.map +1 -0
  103. package/lib/typescript/src/components/Circle.types.d.ts +33 -0
  104. package/lib/typescript/src/components/Circle.types.d.ts.map +1 -0
  105. package/lib/typescript/src/components/Circle.web.d.ts +3 -0
  106. package/lib/typescript/src/components/Circle.web.d.ts.map +1 -0
  107. package/lib/typescript/src/components/GeoJson.d.ts +2 -1
  108. package/lib/typescript/src/components/GeoJson.d.ts.map +1 -1
  109. package/lib/typescript/src/components/GeoJson.types.d.ts +11 -1
  110. package/lib/typescript/src/components/GeoJson.types.d.ts.map +1 -1
  111. package/lib/typescript/src/components/GroundOverlay.d.ts +10 -0
  112. package/lib/typescript/src/components/GroundOverlay.d.ts.map +1 -0
  113. package/lib/typescript/src/components/GroundOverlay.types.d.ts +35 -0
  114. package/lib/typescript/src/components/GroundOverlay.types.d.ts.map +1 -0
  115. package/lib/typescript/src/components/GroundOverlay.web.d.ts +3 -0
  116. package/lib/typescript/src/components/GroundOverlay.web.d.ts.map +1 -0
  117. package/lib/typescript/src/components/Marker.d.ts +6 -3
  118. package/lib/typescript/src/components/Marker.d.ts.map +1 -1
  119. package/lib/typescript/src/components/Marker.types.d.ts +40 -2
  120. package/lib/typescript/src/components/Marker.types.d.ts.map +1 -1
  121. package/lib/typescript/src/components/Marker.web.d.ts +3 -2
  122. package/lib/typescript/src/components/Marker.web.d.ts.map +1 -1
  123. package/lib/typescript/src/components/Polygon.web.d.ts +1 -1
  124. package/lib/typescript/src/components/Polygon.web.d.ts.map +1 -1
  125. package/lib/typescript/src/components/Polyline.web.d.ts +1 -1
  126. package/lib/typescript/src/components/Polyline.web.d.ts.map +1 -1
  127. package/lib/typescript/src/components/TileOverlay.d.ts +10 -0
  128. package/lib/typescript/src/components/TileOverlay.d.ts.map +1 -0
  129. package/lib/typescript/src/components/TileOverlay.types.d.ts +36 -0
  130. package/lib/typescript/src/components/TileOverlay.types.d.ts.map +1 -0
  131. package/lib/typescript/src/components/TileOverlay.web.d.ts +3 -0
  132. package/lib/typescript/src/components/TileOverlay.web.d.ts.map +1 -0
  133. package/lib/typescript/src/components/index.d.ts +7 -1
  134. package/lib/typescript/src/components/index.d.ts.map +1 -1
  135. package/lib/typescript/src/components/index.web.d.ts +7 -1
  136. package/lib/typescript/src/components/index.web.d.ts.map +1 -1
  137. package/lib/typescript/src/fabric/LuggCalloutViewNativeComponent.d.ts +13 -0
  138. package/lib/typescript/src/fabric/LuggCalloutViewNativeComponent.d.ts.map +1 -0
  139. package/lib/typescript/src/fabric/LuggCircleViewNativeComponent.d.ts +18 -0
  140. package/lib/typescript/src/fabric/LuggCircleViewNativeComponent.d.ts.map +1 -0
  141. package/lib/typescript/src/fabric/LuggGroundOverlayViewNativeComponent.d.ts +21 -0
  142. package/lib/typescript/src/fabric/LuggGroundOverlayViewNativeComponent.d.ts.map +1 -0
  143. package/lib/typescript/src/fabric/LuggMapViewNativeComponent.d.ts +10 -5
  144. package/lib/typescript/src/fabric/LuggMapViewNativeComponent.d.ts.map +1 -1
  145. package/lib/typescript/src/fabric/LuggMarkerViewNativeComponent.d.ts +8 -1
  146. package/lib/typescript/src/fabric/LuggMarkerViewNativeComponent.d.ts.map +1 -1
  147. package/lib/typescript/src/fabric/LuggTileOverlayViewNativeComponent.d.ts +21 -0
  148. package/lib/typescript/src/fabric/LuggTileOverlayViewNativeComponent.d.ts.map +1 -0
  149. package/lib/typescript/src/index.d.ts +10 -4
  150. package/lib/typescript/src/index.d.ts.map +1 -1
  151. package/lib/typescript/src/index.web.d.ts +9 -3
  152. package/lib/typescript/src/index.web.d.ts.map +1 -1
  153. package/lib/typescript/src/types.d.ts +0 -4
  154. package/lib/typescript/src/types.d.ts.map +1 -1
  155. package/package.json +6 -2
  156. package/src/MapProvider.web.tsx +15 -3
  157. package/src/MapView.tsx +12 -1
  158. package/src/MapView.types.ts +118 -1
  159. package/src/MapView.web.tsx +379 -322
  160. package/src/components/Circle.tsx +52 -0
  161. package/src/components/Circle.types.ts +33 -0
  162. package/src/components/Circle.web.tsx +95 -0
  163. package/src/components/GeoJson.tsx +3 -3
  164. package/src/components/GeoJson.types.ts +12 -1
  165. package/src/components/GroundOverlay.tsx +47 -0
  166. package/src/components/GroundOverlay.types.ts +36 -0
  167. package/src/components/GroundOverlay.web.tsx +81 -0
  168. package/src/components/Marker.tsx +59 -5
  169. package/src/components/Marker.types.ts +42 -2
  170. package/src/components/Marker.web.tsx +235 -99
  171. package/src/components/Polygon.web.tsx +93 -89
  172. package/src/components/Polyline.web.tsx +235 -235
  173. package/src/components/TileOverlay.tsx +44 -0
  174. package/src/components/TileOverlay.types.ts +37 -0
  175. package/src/components/TileOverlay.web.tsx +97 -0
  176. package/src/components/index.ts +11 -0
  177. package/src/components/index.web.ts +10 -1
  178. package/src/fabric/LuggCalloutViewNativeComponent.ts +20 -0
  179. package/src/fabric/LuggCircleViewNativeComponent.ts +25 -0
  180. package/src/fabric/LuggGroundOverlayViewNativeComponent.ts +29 -0
  181. package/src/fabric/LuggMapViewNativeComponent.ts +13 -5
  182. package/src/fabric/LuggMarkerViewNativeComponent.ts +14 -2
  183. package/src/fabric/LuggTileOverlayViewNativeComponent.ts +30 -0
  184. package/src/index.ts +14 -1
  185. package/src/index.web.ts +17 -1
  186. package/src/types.ts +0 -5
@@ -0,0 +1,52 @@
1
+ import React from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import LuggCircleViewNativeComponent from '../fabric/LuggCircleViewNativeComponent';
4
+ import type { CircleProps } from './Circle.types';
5
+
6
+ export type { CircleProps } from './Circle.types';
7
+
8
+ export class Circle extends React.PureComponent<CircleProps> {
9
+ private _cachedZIndex: number | undefined;
10
+ private _cachedStyle: any;
11
+
12
+ private getStyle(zIndex: number | undefined) {
13
+ if (zIndex == null) return styles.circle;
14
+ if (zIndex !== this._cachedZIndex) {
15
+ this._cachedZIndex = zIndex;
16
+ this._cachedStyle = [{ zIndex }, styles.circle];
17
+ }
18
+ return this._cachedStyle!;
19
+ }
20
+
21
+ render() {
22
+ const {
23
+ center,
24
+ radius,
25
+ strokeColor,
26
+ strokeWidth,
27
+ fillColor,
28
+ zIndex,
29
+ onPress,
30
+ } = this.props;
31
+
32
+ return (
33
+ <LuggCircleViewNativeComponent
34
+ style={this.getStyle(zIndex)}
35
+ center={center}
36
+ radius={radius}
37
+ strokeColor={strokeColor}
38
+ strokeWidth={strokeWidth}
39
+ fillColor={fillColor}
40
+ tappable={!!onPress}
41
+ onCirclePress={onPress}
42
+ />
43
+ );
44
+ }
45
+ }
46
+
47
+ const styles = StyleSheet.create({
48
+ circle: {
49
+ position: 'absolute',
50
+ pointerEvents: 'none',
51
+ },
52
+ });
@@ -0,0 +1,33 @@
1
+ import type { ColorValue } from 'react-native';
2
+ import type { Coordinate } from '../types';
3
+
4
+ export interface CircleProps {
5
+ /**
6
+ * Center coordinate of the circle
7
+ */
8
+ center: Coordinate;
9
+ /**
10
+ * Radius in meters
11
+ */
12
+ radius: number;
13
+ /**
14
+ * Stroke (outline) color
15
+ */
16
+ strokeColor?: ColorValue;
17
+ /**
18
+ * Stroke width in points
19
+ */
20
+ strokeWidth?: number;
21
+ /**
22
+ * Fill color of the circle
23
+ */
24
+ fillColor?: ColorValue;
25
+ /**
26
+ * Z-index for layering
27
+ */
28
+ zIndex?: number;
29
+ /**
30
+ * Called when the circle is tapped
31
+ */
32
+ onPress?: () => void;
33
+ }
@@ -0,0 +1,95 @@
1
+ import { memo, useCallback, useEffect, useRef } from 'react';
2
+ import { useMapContext } from '../MapProvider.web';
3
+ import type { CircleProps } from './Circle.types';
4
+
5
+ export const Circle = memo(
6
+ ({
7
+ center,
8
+ radius,
9
+ strokeColor = '#000000',
10
+ strokeWidth = 1,
11
+ fillColor = 'rgba(0, 0, 0, 0.3)',
12
+ zIndex = 0,
13
+ onPress,
14
+ }: CircleProps) => {
15
+ const { map } = useMapContext();
16
+ const circleRef = useRef<google.maps.Circle | null>(null);
17
+ const listenersRef = useRef<google.maps.MapsEventListener[]>([]);
18
+
19
+ const handleClick = useCallback(() => {
20
+ onPress?.();
21
+ }, [onPress]);
22
+
23
+ useEffect(() => {
24
+ return () => {
25
+ listenersRef.current.forEach((l) => l.remove());
26
+ listenersRef.current = [];
27
+ circleRef.current?.setMap(null);
28
+ circleRef.current = null;
29
+ };
30
+ }, []);
31
+
32
+ useEffect(() => {
33
+ const circle = circleRef.current;
34
+ if (!circle) return;
35
+
36
+ listenersRef.current.forEach((l) => l.remove());
37
+ listenersRef.current = [];
38
+
39
+ if (onPress) {
40
+ listenersRef.current.push(circle.addListener('click', handleClick));
41
+ }
42
+ circle.set('clickable', !!onPress);
43
+ }, [onPress, handleClick]);
44
+
45
+ useEffect(() => {
46
+ if (!map) {
47
+ circleRef.current?.setMap(null);
48
+ return;
49
+ }
50
+
51
+ const googleCenter = { lat: center.latitude, lng: center.longitude };
52
+
53
+ if (circleRef.current) {
54
+ circleRef.current.setCenter(googleCenter);
55
+ circleRef.current.setRadius(radius);
56
+ circleRef.current.setOptions({
57
+ strokeColor: strokeColor as string,
58
+ strokeWeight: strokeWidth,
59
+ fillColor: fillColor as string,
60
+ zIndex,
61
+ });
62
+ } else {
63
+ const circle = new google.maps.Circle({
64
+ center: googleCenter,
65
+ radius,
66
+ strokeColor: strokeColor as string,
67
+ strokeWeight: strokeWidth,
68
+ strokeOpacity: 1,
69
+ fillColor: fillColor as string,
70
+ fillOpacity: 1,
71
+ zIndex,
72
+ clickable: !!onPress,
73
+ map,
74
+ });
75
+ circleRef.current = circle;
76
+
77
+ if (onPress) {
78
+ listenersRef.current.push(circle.addListener('click', handleClick));
79
+ }
80
+ }
81
+ }, [
82
+ map,
83
+ center,
84
+ radius,
85
+ strokeColor,
86
+ strokeWidth,
87
+ fillColor,
88
+ zIndex,
89
+ onPress,
90
+ handleClick,
91
+ ]);
92
+
93
+ return null;
94
+ }
95
+ );
@@ -1,4 +1,4 @@
1
- import React, { useMemo, type ReactElement } from 'react';
1
+ import React, { memo, useMemo, type ReactElement } from 'react';
2
2
  import type {
3
3
  Feature,
4
4
  FeatureCollection,
@@ -199,7 +199,7 @@ const renderGeometry = (
199
199
  return elements;
200
200
  };
201
201
 
202
- export const GeoJson = (props: GeoJsonProps) => {
202
+ export const GeoJson = memo((props: GeoJsonProps) => {
203
203
  const { geojson } = props;
204
204
 
205
205
  const elements = useMemo(() => {
@@ -218,4 +218,4 @@ export const GeoJson = (props: GeoJsonProps) => {
218
218
  }, [geojson, props]);
219
219
 
220
220
  return <>{elements}</>;
221
- };
221
+ });
@@ -56,11 +56,22 @@ export type Geometry =
56
56
  | MultiPolygon
57
57
  | GeometryCollection;
58
58
 
59
+ export interface FeatureProperties {
60
+ 'title'?: string;
61
+ 'description'?: string;
62
+ 'fill'?: string;
63
+ 'fill-opacity'?: number;
64
+ 'stroke'?: string;
65
+ 'stroke-width'?: number;
66
+ 'stroke-opacity'?: number;
67
+ [key: string]: unknown;
68
+ }
69
+
59
70
  export interface Feature<G extends Geometry = Geometry> {
60
71
  type: 'Feature';
61
72
  id?: string | number;
62
73
  geometry: G;
63
- properties: Record<string, any> | null;
74
+ properties: FeatureProperties | null;
64
75
  }
65
76
 
66
77
  export interface FeatureCollection<G extends Geometry = Geometry> {
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import { Image, StyleSheet } from 'react-native';
3
+ import LuggGroundOverlayViewNativeComponent from '../fabric/LuggGroundOverlayViewNativeComponent';
4
+ import type { GroundOverlayProps } from './GroundOverlay.types';
5
+
6
+ export type {
7
+ GroundOverlayProps,
8
+ GroundOverlayBounds,
9
+ } from './GroundOverlay.types';
10
+
11
+ export class GroundOverlay extends React.PureComponent<GroundOverlayProps> {
12
+ private _cachedZIndex: number | undefined;
13
+ private _cachedStyle: any;
14
+
15
+ private getStyle(zIndex: number | undefined) {
16
+ if (zIndex == null) return styles.overlay;
17
+ if (zIndex !== this._cachedZIndex) {
18
+ this._cachedZIndex = zIndex;
19
+ this._cachedStyle = [{ zIndex }, styles.overlay];
20
+ }
21
+ return this._cachedStyle!;
22
+ }
23
+
24
+ render() {
25
+ const { image, bounds, opacity, bearing, zIndex, onPress } = this.props;
26
+ const resolved = Image.resolveAssetSource(image);
27
+
28
+ return (
29
+ <LuggGroundOverlayViewNativeComponent
30
+ style={this.getStyle(zIndex)}
31
+ image={resolved?.uri ?? ''}
32
+ bounds={bounds}
33
+ opacity={opacity}
34
+ bearing={bearing}
35
+ tappable={!!onPress}
36
+ onGroundOverlayPress={onPress}
37
+ />
38
+ );
39
+ }
40
+ }
41
+
42
+ const styles = StyleSheet.create({
43
+ overlay: {
44
+ position: 'absolute',
45
+ pointerEvents: 'none',
46
+ },
47
+ });
@@ -0,0 +1,36 @@
1
+ import type { ImageSourcePropType } from 'react-native';
2
+ import type { Coordinate } from '../types';
3
+
4
+ export interface GroundOverlayBounds {
5
+ northeast: Coordinate;
6
+ southwest: Coordinate;
7
+ }
8
+
9
+ export interface GroundOverlayProps {
10
+ /**
11
+ * Image to overlay on the map
12
+ */
13
+ image: ImageSourcePropType;
14
+ /**
15
+ * Geographic bounds for the overlay
16
+ */
17
+ bounds: GroundOverlayBounds;
18
+ /**
19
+ * Opacity of the overlay (0-1)
20
+ */
21
+ opacity?: number;
22
+ /**
23
+ * Bearing (rotation) in degrees clockwise from north.
24
+ * Only supported on Google Maps.
25
+ * @platform google
26
+ */
27
+ bearing?: number;
28
+ /**
29
+ * Z-index for layering
30
+ */
31
+ zIndex?: number;
32
+ /**
33
+ * Called when the overlay is tapped
34
+ */
35
+ onPress?: () => void;
36
+ }
@@ -0,0 +1,81 @@
1
+ import { memo, useCallback, useEffect, useRef } from 'react';
2
+ import { useMapContext } from '../MapProvider.web';
3
+ import type { GroundOverlayProps } from './GroundOverlay.types';
4
+
5
+ export const GroundOverlay = memo(
6
+ ({ image, bounds, opacity = 1, zIndex = 0, onPress }: GroundOverlayProps) => {
7
+ const { map } = useMapContext();
8
+ const overlayRef = useRef<google.maps.GroundOverlay | null>(null);
9
+ const listenersRef = useRef<google.maps.MapsEventListener[]>([]);
10
+
11
+ const handleClick = useCallback(() => {
12
+ onPress?.();
13
+ }, [onPress]);
14
+
15
+ // Cleanup on unmount
16
+ useEffect(() => {
17
+ return () => {
18
+ listenersRef.current.forEach((l) => l.remove());
19
+ listenersRef.current = [];
20
+ overlayRef.current?.setMap(null);
21
+ overlayRef.current = null;
22
+ };
23
+ }, []);
24
+
25
+ // Sync listeners
26
+ useEffect(() => {
27
+ const overlay = overlayRef.current;
28
+ if (!overlay) return;
29
+
30
+ listenersRef.current.forEach((l) => l.remove());
31
+ listenersRef.current = [];
32
+
33
+ if (onPress) {
34
+ listenersRef.current.push(overlay.addListener('click', handleClick));
35
+ }
36
+ overlay.set('clickable', !!onPress);
37
+ }, [onPress, handleClick]);
38
+
39
+ // Sync overlay with props
40
+ useEffect(() => {
41
+ if (!map) {
42
+ overlayRef.current?.setMap(null);
43
+ return;
44
+ }
45
+
46
+ const source =
47
+ typeof image === 'number'
48
+ ? null
49
+ : Array.isArray(image)
50
+ ? image[0]
51
+ : image;
52
+ const imageUrl = source?.uri ?? '';
53
+ if (!imageUrl) return;
54
+
55
+ const latLngBounds = new google.maps.LatLngBounds(
56
+ { lat: bounds.southwest.latitude, lng: bounds.southwest.longitude },
57
+ { lat: bounds.northeast.latitude, lng: bounds.northeast.longitude }
58
+ );
59
+
60
+ // GroundOverlay bounds are immutable — recreate if bounds change
61
+ overlayRef.current?.setMap(null);
62
+ listenersRef.current.forEach((l) => l.remove());
63
+ listenersRef.current = [];
64
+
65
+ const overlay = new google.maps.GroundOverlay(imageUrl, latLngBounds, {
66
+ opacity,
67
+ clickable: !!onPress,
68
+ map,
69
+ });
70
+
71
+ overlay.set('zIndex', zIndex);
72
+ overlayRef.current = overlay;
73
+
74
+ if (onPress) {
75
+ listenersRef.current.push(overlay.addListener('click', handleClick));
76
+ }
77
+ }, [map, image, bounds, opacity, zIndex, onPress, handleClick]);
78
+
79
+ return null;
80
+ }
81
+ );
@@ -1,15 +1,40 @@
1
- import React from 'react';
1
+ import React, { isValidElement } from 'react';
2
2
  import { StyleSheet } from 'react-native';
3
- import LuggMarkerViewNativeComponent from '../fabric/LuggMarkerViewNativeComponent';
4
- import type { MarkerProps } from './Marker.types';
3
+ import LuggMarkerViewNativeComponent, {
4
+ Commands,
5
+ } from '../fabric/LuggMarkerViewNativeComponent';
6
+ import LuggCalloutViewNativeComponent from '../fabric/LuggCalloutViewNativeComponent';
7
+ import type { MarkerProps, MarkerRef } from './Marker.types';
8
+ import type { Point } from '../types';
5
9
 
6
10
  export type {
11
+ CalloutOptions,
12
+ MarkerRef,
7
13
  MarkerProps,
8
14
  MarkerPressEvent,
9
15
  MarkerDragEvent,
10
16
  } from './Marker.types';
11
17
 
12
- export class Marker extends React.PureComponent<MarkerProps> {
18
+ const DEFAULT_ANCHOR: Point = { x: 0.5, y: 1 };
19
+
20
+ export class Marker
21
+ extends React.PureComponent<MarkerProps>
22
+ implements MarkerRef
23
+ {
24
+ private nativeRef = React.createRef<any>();
25
+
26
+ showCallout() {
27
+ const ref = this.nativeRef.current;
28
+ if (!ref) return;
29
+ Commands.showCallout(ref);
30
+ }
31
+
32
+ hideCallout() {
33
+ const ref = this.nativeRef.current;
34
+ if (!ref) return;
35
+ Commands.hideCallout(ref);
36
+ }
37
+
13
38
  private getStyle(zIndex: number | undefined) {
14
39
  if (zIndex == null) return styles.marker;
15
40
  if (zIndex !== this._cachedZIndex) {
@@ -33,25 +58,38 @@ export class Marker extends React.PureComponent<MarkerProps> {
33
58
  rotate = 0,
34
59
  scale = 1,
35
60
  rasterize = true,
61
+ centerOnPress = true,
36
62
  draggable = false,
37
63
  onPress,
38
64
  onDragStart,
39
65
  onDragChange,
40
66
  onDragEnd,
67
+ callout,
68
+ calloutOptions,
41
69
  children,
42
70
  } = this.props;
43
71
 
72
+ const calloutContent = callout
73
+ ? isValidElement(callout)
74
+ ? callout
75
+ : React.createElement(callout)
76
+ : null;
77
+ const calloutBubbled = calloutOptions?.bubbled ?? true;
78
+ const calloutOffset = calloutOptions?.offset;
79
+
44
80
  return (
45
81
  <LuggMarkerViewNativeComponent
82
+ ref={this.nativeRef}
46
83
  style={this.getStyle(zIndex)}
47
84
  name={name}
48
85
  coordinate={coordinate}
49
86
  title={title}
50
87
  description={description}
51
- anchor={anchor}
88
+ anchor={anchor ?? DEFAULT_ANCHOR}
52
89
  rotate={rotate}
53
90
  scale={scale}
54
91
  rasterize={rasterize}
92
+ centerOnPress={centerOnPress}
55
93
  draggable={draggable}
56
94
  onMarkerPress={onPress}
57
95
  onMarkerDragStart={onDragStart}
@@ -59,6 +97,15 @@ export class Marker extends React.PureComponent<MarkerProps> {
59
97
  onMarkerDragEnd={onDragEnd}
60
98
  >
61
99
  {children}
100
+ {calloutContent && (
101
+ <LuggCalloutViewNativeComponent
102
+ style={calloutStyles.callout}
103
+ bubbled={calloutBubbled}
104
+ offset={calloutOffset}
105
+ >
106
+ {calloutContent}
107
+ </LuggCalloutViewNativeComponent>
108
+ )}
62
109
  </LuggMarkerViewNativeComponent>
63
110
  );
64
111
  }
@@ -70,3 +117,10 @@ const styles = StyleSheet.create({
70
117
  pointerEvents: 'box-none',
71
118
  },
72
119
  });
120
+
121
+ const calloutStyles = StyleSheet.create({
122
+ callout: {
123
+ position: 'absolute',
124
+ pointerEvents: 'box-none',
125
+ },
126
+ });
@@ -1,10 +1,33 @@
1
- import type { ReactNode } from 'react';
1
+ import type { ComponentType, ReactElement, ReactNode } from 'react';
2
2
  import type { NativeSyntheticEvent } from 'react-native';
3
3
  import type { Coordinate, Point, PressEventPayload } from '../types';
4
4
 
5
5
  export type MarkerPressEvent = NativeSyntheticEvent<PressEventPayload>;
6
6
  export type MarkerDragEvent = NativeSyntheticEvent<PressEventPayload>;
7
7
 
8
+ export interface CalloutOptions {
9
+ /**
10
+ * Whether to wrap the callout in the native platform bubble.
11
+ * Set to `false` to render custom content directly without the native callout chrome.
12
+ *
13
+ * @default true
14
+ */
15
+ bubbled?: boolean;
16
+ /**
17
+ * Pixel offset for non-bubbled callouts from their default centered position above the marker.
18
+ * Positive `x` moves right, positive `y` moves down.
19
+ * Only applies when `bubbled` is `false`.
20
+ *
21
+ * @default {x: 0, y: 0}
22
+ */
23
+ offset?: Point;
24
+ }
25
+
26
+ export interface MarkerRef {
27
+ showCallout(): void;
28
+ hideCallout(): void;
29
+ }
30
+
8
31
  export interface MarkerProps {
9
32
  /**
10
33
  * Name used for debugging purposes
@@ -23,7 +46,10 @@ export interface MarkerProps {
23
46
  */
24
47
  description?: string;
25
48
  /**
26
- * Anchor point for custom marker views
49
+ * Anchor point for custom marker views.
50
+ * `{x: 0.5, y: 1}` places the bottom-center of the marker at the coordinate.
51
+ *
52
+ * @default {x: 0.5, y: 1}
27
53
  */
28
54
  anchor?: Point;
29
55
  /**
@@ -47,6 +73,11 @@ export interface MarkerProps {
47
73
  * @default true
48
74
  */
49
75
  rasterize?: boolean;
76
+ /**
77
+ * Whether the map centers on the marker when pressed.
78
+ * @default true
79
+ */
80
+ centerOnPress?: boolean;
50
81
  /**
51
82
  * Whether the marker can be dragged by the user.
52
83
  * @default false
@@ -68,6 +99,15 @@ export interface MarkerProps {
68
99
  * Called when marker drag ends
69
100
  */
70
101
  onDragEnd?: (event: MarkerDragEvent) => void;
102
+ /**
103
+ * Callout content displayed when marker is tapped.
104
+ * Pass a `ReactElement` for inline content or a `ComponentType` to be instantiated.
105
+ */
106
+ callout?: ComponentType<unknown> | ReactElement;
107
+ /**
108
+ * Callout configuration options.
109
+ */
110
+ calloutOptions?: CalloutOptions;
71
111
  /**
72
112
  * Custom marker view
73
113
  */