@lugg/maps 0.2.0-alpha.1 → 0.2.0-alpha.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 (89) hide show
  1. package/README.md +17 -4
  2. package/android/src/main/java/com/luggmaps/LuggGoogleMapView.kt +30 -8
  3. package/android/src/main/java/com/luggmaps/LuggGoogleMapViewManager.kt +10 -2
  4. package/android/src/main/java/com/luggmaps/LuggMarkerView.kt +7 -1
  5. package/android/src/main/java/com/luggmaps/LuggMarkerViewManager.kt +6 -0
  6. package/android/src/main/java/com/luggmaps/LuggPolylineView.kt +7 -0
  7. package/android/src/main/java/com/luggmaps/LuggPolylineViewManager.kt +6 -0
  8. package/android/src/main/java/com/luggmaps/core/PolylineAnimator.kt +89 -43
  9. package/ios/LuggAppleMapView.mm +45 -5
  10. package/ios/LuggGoogleMapView.mm +18 -6
  11. package/ios/LuggMarkerView.h +2 -0
  12. package/ios/LuggMarkerView.mm +13 -0
  13. package/ios/LuggPolylineView.h +1 -0
  14. package/ios/LuggPolylineView.mm +6 -1
  15. package/ios/core/GMSPolylineAnimator.m +85 -36
  16. package/ios/core/MKPolylineAnimator.m +63 -32
  17. package/lib/module/MapProvider.js +13 -0
  18. package/lib/module/MapProvider.js.map +1 -0
  19. package/lib/module/MapProvider.types.js +4 -0
  20. package/lib/module/MapProvider.types.js.map +1 -0
  21. package/lib/module/MapProvider.web.js +14 -0
  22. package/lib/module/MapProvider.web.js.map +1 -0
  23. package/lib/module/MapView.js +8 -2
  24. package/lib/module/MapView.js.map +1 -1
  25. package/lib/module/MapView.web.js +266 -0
  26. package/lib/module/MapView.web.js.map +1 -0
  27. package/lib/module/components/Marker.js +4 -1
  28. package/lib/module/components/Marker.js.map +1 -1
  29. package/lib/module/components/Marker.web.js +34 -0
  30. package/lib/module/components/Marker.web.js.map +1 -0
  31. package/lib/module/components/Polyline.js +5 -2
  32. package/lib/module/components/Polyline.js.map +1 -1
  33. package/lib/module/components/Polyline.web.js +177 -0
  34. package/lib/module/components/Polyline.web.js.map +1 -0
  35. package/lib/module/components/index.js +2 -2
  36. package/lib/module/components/index.js.map +1 -1
  37. package/lib/module/components/index.web.js +5 -0
  38. package/lib/module/components/index.web.js.map +1 -0
  39. package/lib/module/fabric/LuggAppleMapViewNativeComponent.ts +4 -1
  40. package/lib/module/fabric/LuggGoogleMapViewNativeComponent.ts +4 -1
  41. package/lib/module/index.js +3 -2
  42. package/lib/module/index.js.map +1 -1
  43. package/lib/module/index.web.js +6 -0
  44. package/lib/module/index.web.js.map +1 -0
  45. package/lib/typescript/src/MapProvider.d.ts +8 -0
  46. package/lib/typescript/src/MapProvider.d.ts.map +1 -0
  47. package/lib/typescript/src/MapProvider.types.d.ts +16 -0
  48. package/lib/typescript/src/MapProvider.types.d.ts.map +1 -0
  49. package/lib/typescript/src/MapProvider.web.d.ts +3 -0
  50. package/lib/typescript/src/MapProvider.web.d.ts.map +1 -0
  51. package/lib/typescript/src/MapView.d.ts.map +1 -1
  52. package/lib/typescript/src/MapView.types.d.ts +1 -2
  53. package/lib/typescript/src/MapView.types.d.ts.map +1 -1
  54. package/lib/typescript/src/MapView.web.d.ts +12 -0
  55. package/lib/typescript/src/MapView.web.d.ts.map +1 -0
  56. package/lib/typescript/src/components/Marker.d.ts +4 -0
  57. package/lib/typescript/src/components/Marker.d.ts.map +1 -1
  58. package/lib/typescript/src/components/Marker.web.d.ts +6 -0
  59. package/lib/typescript/src/components/Marker.web.d.ts.map +1 -0
  60. package/lib/typescript/src/components/Polyline.d.ts +4 -0
  61. package/lib/typescript/src/components/Polyline.d.ts.map +1 -1
  62. package/lib/typescript/src/components/Polyline.web.d.ts +6 -0
  63. package/lib/typescript/src/components/Polyline.web.d.ts.map +1 -0
  64. package/lib/typescript/src/components/index.web.d.ts +5 -0
  65. package/lib/typescript/src/components/index.web.d.ts.map +1 -0
  66. package/lib/typescript/src/fabric/LuggAppleMapViewNativeComponent.d.ts +1 -1
  67. package/lib/typescript/src/fabric/LuggAppleMapViewNativeComponent.d.ts.map +1 -1
  68. package/lib/typescript/src/fabric/LuggGoogleMapViewNativeComponent.d.ts +1 -1
  69. package/lib/typescript/src/fabric/LuggGoogleMapViewNativeComponent.d.ts.map +1 -1
  70. package/lib/typescript/src/index.d.ts +3 -1
  71. package/lib/typescript/src/index.d.ts.map +1 -1
  72. package/lib/typescript/src/index.web.d.ts +7 -0
  73. package/lib/typescript/src/index.web.d.ts.map +1 -0
  74. package/package.json +15 -2
  75. package/src/MapProvider.tsx +10 -0
  76. package/src/MapProvider.types.ts +16 -0
  77. package/src/MapProvider.web.tsx +6 -0
  78. package/src/MapView.tsx +11 -2
  79. package/src/MapView.types.ts +1 -2
  80. package/src/MapView.web.tsx +319 -0
  81. package/src/components/Marker.tsx +6 -2
  82. package/src/components/Marker.web.tsx +32 -0
  83. package/src/components/Polyline.tsx +6 -1
  84. package/src/components/Polyline.web.tsx +222 -0
  85. package/src/components/index.web.ts +4 -0
  86. package/src/fabric/LuggAppleMapViewNativeComponent.ts +4 -1
  87. package/src/fabric/LuggGoogleMapViewNativeComponent.ts +4 -1
  88. package/src/index.ts +8 -1
  89. package/src/index.web.ts +17 -0
@@ -0,0 +1,319 @@
1
+ import {
2
+ Children,
3
+ Component,
4
+ isValidElement,
5
+ useEffect,
6
+ useRef,
7
+ useState,
8
+ type CSSProperties,
9
+ type ReactElement,
10
+ type ReactNode,
11
+ } from 'react';
12
+ import type { NativeSyntheticEvent, ViewStyle } from 'react-native';
13
+ import { View } from 'react-native';
14
+ import { Map, useMap } from '@vis.gl/react-google-maps';
15
+ import { Marker } from './components/Marker.web';
16
+ import { Polyline } from './components/Polyline.web';
17
+
18
+ import type {
19
+ MapViewProps,
20
+ MapViewRef,
21
+ MoveCameraOptions,
22
+ FitCoordinatesOptions,
23
+ CameraEventPayload,
24
+ } from './MapView.types';
25
+ import type { Coordinate } from './types';
26
+
27
+ // Map-specific component types that render inside the Google Map
28
+ const MAP_COMPONENT_TYPES = new Set([Marker, Polyline]);
29
+
30
+ const isMapComponent = (child: ReactElement): boolean =>
31
+ MAP_COMPONENT_TYPES.has(child.type as typeof Marker | typeof Polyline);
32
+
33
+ const createSyntheticEvent = <T,>(nativeEvent: T): NativeSyntheticEvent<T> =>
34
+ ({
35
+ nativeEvent,
36
+ currentTarget: null,
37
+ target: null,
38
+ bubbles: false,
39
+ cancelable: false,
40
+ defaultPrevented: false,
41
+ eventPhase: 0,
42
+ isTrusted: true,
43
+ preventDefault: () => {},
44
+ stopPropagation: () => {},
45
+ isDefaultPrevented: () => false,
46
+ isPropagationStopped: () => false,
47
+ persist: () => {},
48
+ timeStamp: Date.now(),
49
+ type: '',
50
+ } as unknown as NativeSyntheticEvent<T>);
51
+
52
+ interface MapControllerProps {
53
+ onMapReady: (map: google.maps.Map) => void;
54
+ onCameraMove?: (event: NativeSyntheticEvent<CameraEventPayload>) => void;
55
+ onCameraIdle?: (event: NativeSyntheticEvent<CameraEventPayload>) => void;
56
+ onReady?: () => void;
57
+ }
58
+
59
+ interface UserLocationMarkerProps {
60
+ enabled?: boolean;
61
+ }
62
+
63
+ const userLocationDotStyle: CSSProperties = {
64
+ width: 16,
65
+ height: 16,
66
+ backgroundColor: '#4285F4',
67
+ border: '2px solid white',
68
+ borderRadius: '50%',
69
+ boxShadow: '0 1px 4px rgba(0,0,0,0.3)',
70
+ };
71
+
72
+ function UserLocationMarker({ enabled }: UserLocationMarkerProps) {
73
+ const [coordinate, setCoordinate] = useState<Coordinate | null>(null);
74
+
75
+ useEffect(() => {
76
+ if (!enabled) {
77
+ setCoordinate(null);
78
+ return;
79
+ }
80
+
81
+ let watchId: number | null = null;
82
+
83
+ const updateLocation = (position: GeolocationPosition) => {
84
+ setCoordinate({
85
+ latitude: position.coords.latitude,
86
+ longitude: position.coords.longitude,
87
+ });
88
+ };
89
+
90
+ navigator.geolocation.getCurrentPosition(updateLocation, () => {});
91
+ watchId = navigator.geolocation.watchPosition(updateLocation, () => {});
92
+
93
+ return () => {
94
+ if (watchId !== null) {
95
+ navigator.geolocation.clearWatch(watchId);
96
+ }
97
+ };
98
+ }, [enabled]);
99
+
100
+ if (!coordinate) return null;
101
+
102
+ return (
103
+ <Marker coordinate={coordinate} anchor={{ x: 0.5, y: 0.5 }}>
104
+ <div style={userLocationDotStyle} />
105
+ </Marker>
106
+ );
107
+ }
108
+
109
+ function MapController({
110
+ onMapReady,
111
+ onCameraMove,
112
+ onCameraIdle,
113
+ onReady,
114
+ }: MapControllerProps) {
115
+ const map = useMap();
116
+ const readyFired = useRef(false);
117
+
118
+ useEffect(() => {
119
+ if (!map) return;
120
+ onMapReady(map);
121
+
122
+ if (!readyFired.current) {
123
+ readyFired.current = true;
124
+ onReady?.();
125
+ }
126
+ }, [map, onMapReady, onReady]);
127
+
128
+ useEffect(() => {
129
+ if (!map) return;
130
+
131
+ const createPayload = (gesture: boolean): CameraEventPayload => {
132
+ const center = map.getCenter();
133
+ return {
134
+ coordinate: {
135
+ latitude: center?.lat() ?? 0,
136
+ longitude: center?.lng() ?? 0,
137
+ },
138
+ zoom: map.getZoom() ?? 0,
139
+ gesture,
140
+ };
141
+ };
142
+
143
+ let isDragging = false;
144
+
145
+ const dragStartListener = map.addListener('dragstart', () => {
146
+ isDragging = true;
147
+ });
148
+
149
+ const dragEndListener = map.addListener('dragend', () => {
150
+ isDragging = false;
151
+ });
152
+
153
+ const centerListener = map.addListener('center_changed', () => {
154
+ onCameraMove?.(createSyntheticEvent(createPayload(isDragging)));
155
+ });
156
+
157
+ const idleListener = map.addListener('idle', () => {
158
+ onCameraIdle?.(createSyntheticEvent(createPayload(false)));
159
+ });
160
+
161
+ return () => {
162
+ google.maps.event.removeListener(dragStartListener);
163
+ google.maps.event.removeListener(dragEndListener);
164
+ google.maps.event.removeListener(centerListener);
165
+ google.maps.event.removeListener(idleListener);
166
+ };
167
+ }, [map, onCameraMove, onCameraIdle]);
168
+
169
+ return null;
170
+ }
171
+
172
+ export class MapView extends Component<MapViewProps> implements MapViewRef {
173
+ static defaultProps: Partial<MapViewProps> = {
174
+ provider: 'google',
175
+ mapId: 'DEMO_MAP_ID',
176
+ initialZoom: 10,
177
+ zoomEnabled: true,
178
+ scrollEnabled: true,
179
+ rotateEnabled: true,
180
+ pitchEnabled: true,
181
+ };
182
+
183
+ private mapInstance: google.maps.Map | null = null;
184
+
185
+ private handleMapReady = (map: google.maps.Map) => {
186
+ this.mapInstance = map;
187
+ };
188
+
189
+ moveCamera(coordinate: Coordinate, options: MoveCameraOptions) {
190
+ const map = this.mapInstance;
191
+ if (!map) return;
192
+
193
+ const { zoom, duration = -1 } = options;
194
+ const center = { lat: coordinate.latitude, lng: coordinate.longitude };
195
+
196
+ if (duration === 0) {
197
+ map.moveCamera({ center, zoom });
198
+ } else {
199
+ const currentZoom = map.getZoom();
200
+ const zoomChanged = zoom !== undefined && zoom !== currentZoom;
201
+
202
+ if (zoomChanged) {
203
+ map.setZoom(zoom);
204
+ }
205
+ map.panTo(center);
206
+ }
207
+ }
208
+
209
+ fitCoordinates(coordinates: Coordinate[], options?: FitCoordinatesOptions) {
210
+ const map = this.mapInstance;
211
+ const first = coordinates[0];
212
+ if (!map || !first) return;
213
+
214
+ const { padding, duration = -1 } = options ?? {};
215
+
216
+ if (coordinates.length === 1) {
217
+ const zoom = this.props.initialZoom ?? 10;
218
+ this.moveCamera(first, { zoom, duration });
219
+ return;
220
+ }
221
+
222
+ const bounds = new google.maps.LatLngBounds();
223
+ coordinates.forEach((coord) => {
224
+ bounds.extend({ lat: coord.latitude, lng: coord.longitude });
225
+ });
226
+
227
+ map.fitBounds(bounds, {
228
+ top: padding?.top ?? 0,
229
+ left: padding?.left ?? 0,
230
+ bottom: padding?.bottom ?? 0,
231
+ right: padding?.right ?? 0,
232
+ });
233
+ }
234
+
235
+ render() {
236
+ const {
237
+ mapId,
238
+ initialCoordinate,
239
+ initialZoom,
240
+ minZoom,
241
+ maxZoom,
242
+ zoomEnabled,
243
+ scrollEnabled,
244
+ pitchEnabled,
245
+ padding,
246
+ userLocationEnabled,
247
+ onCameraMove,
248
+ onCameraIdle,
249
+ onReady,
250
+ children,
251
+ style,
252
+ } = this.props;
253
+
254
+ const gestureHandling =
255
+ scrollEnabled === false && zoomEnabled === false
256
+ ? 'none'
257
+ : scrollEnabled === false
258
+ ? 'none'
259
+ : 'auto';
260
+
261
+ const defaultCenter = initialCoordinate
262
+ ? { lat: initialCoordinate.latitude, lng: initialCoordinate.longitude }
263
+ : undefined;
264
+
265
+ // Separate map children (Marker, Polyline) from overlay children (regular Views)
266
+ const mapChildren: ReactNode[] = [];
267
+ const overlayChildren: ReactNode[] = [];
268
+
269
+ Children.forEach(children, (child) => {
270
+ if (!isValidElement(child)) return;
271
+ if (isMapComponent(child)) {
272
+ mapChildren.push(child);
273
+ } else {
274
+ overlayChildren.push(child);
275
+ }
276
+ });
277
+
278
+ const mapContainerStyle: ViewStyle = {
279
+ position: 'absolute',
280
+ top: padding?.top ?? 0,
281
+ left: padding?.left ?? 0,
282
+ right: padding?.right ?? 0,
283
+ bottom: padding?.bottom ?? 0,
284
+ };
285
+
286
+ const mapStyle: CSSProperties = {
287
+ width: '100%',
288
+ height: '100%',
289
+ };
290
+
291
+ return (
292
+ <View style={style}>
293
+ <View style={mapContainerStyle}>
294
+ <Map
295
+ mapId={mapId}
296
+ defaultCenter={defaultCenter}
297
+ defaultZoom={initialZoom}
298
+ minZoom={minZoom}
299
+ maxZoom={maxZoom}
300
+ gestureHandling={gestureHandling}
301
+ disableDefaultUI
302
+ tilt={pitchEnabled === false ? 0 : undefined}
303
+ style={mapStyle}
304
+ >
305
+ <MapController
306
+ onMapReady={this.handleMapReady}
307
+ onCameraMove={onCameraMove}
308
+ onCameraIdle={onCameraIdle}
309
+ onReady={onReady}
310
+ />
311
+ <UserLocationMarker enabled={userLocationEnabled} />
312
+ {mapChildren}
313
+ </Map>
314
+ </View>
315
+ {overlayChildren}
316
+ </View>
317
+ );
318
+ }
319
+ }
@@ -25,6 +25,10 @@ export interface MarkerProps {
25
25
  * Anchor point for custom marker views
26
26
  */
27
27
  anchor?: Point;
28
+ /**
29
+ * Z-index for marker ordering. Higher values render on top.
30
+ */
31
+ zIndex?: number;
28
32
  /**
29
33
  * Custom marker view
30
34
  */
@@ -33,12 +37,12 @@ export interface MarkerProps {
33
37
 
34
38
  export class Marker extends React.Component<MarkerProps> {
35
39
  render() {
36
- const { name, coordinate, title, description, anchor, children } =
40
+ const { name, coordinate, title, description, anchor, zIndex, children } =
37
41
  this.props;
38
42
 
39
43
  return (
40
44
  <LuggMarkerViewNativeComponent
41
- style={styles.marker}
45
+ style={[{ zIndex }, styles.marker]}
42
46
  name={name}
43
47
  coordinate={coordinate}
44
48
  title={title}
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { AdvancedMarker } from '@vis.gl/react-google-maps';
3
+ import type { MarkerProps } from './Marker';
4
+
5
+ /**
6
+ * Converts point to % anchor for web.
7
+ * e.g. `0.5` to `-50%`
8
+ */
9
+ const toWebAnchor = (value: number) => `-${value * 100}%`;
10
+
11
+ export class Marker extends React.Component<MarkerProps> {
12
+ render() {
13
+ const { coordinate, title, anchor, zIndex, children } = this.props;
14
+
15
+ const position = {
16
+ lat: coordinate.latitude,
17
+ lng: coordinate.longitude,
18
+ };
19
+
20
+ return (
21
+ <AdvancedMarker
22
+ position={position}
23
+ title={title}
24
+ zIndex={zIndex}
25
+ anchorLeft={anchor ? toWebAnchor(anchor.x) : undefined}
26
+ anchorTop={anchor ? toWebAnchor(anchor.y) : undefined}
27
+ >
28
+ {children}
29
+ </AdvancedMarker>
30
+ );
31
+ }
32
+ }
@@ -21,6 +21,10 @@ export interface PolylineProps {
21
21
  * Animate the polyline with a snake effect
22
22
  */
23
23
  animated?: boolean;
24
+ /**
25
+ * Z-index for layering polylines
26
+ */
27
+ zIndex?: number;
24
28
  }
25
29
 
26
30
  export class Polyline extends React.Component<PolylineProps> {
@@ -30,11 +34,12 @@ export class Polyline extends React.Component<PolylineProps> {
30
34
  strokeColors,
31
35
  strokeWidth,
32
36
  animated = false,
37
+ zIndex,
33
38
  } = this.props;
34
39
 
35
40
  return (
36
41
  <LuggPolylineViewNativeComponent
37
- style={styles.polyline}
42
+ style={[{ zIndex }, styles.polyline]}
38
43
  coordinates={coordinates}
39
44
  strokeColors={strokeColors}
40
45
  strokeWidth={strokeWidth}
@@ -0,0 +1,222 @@
1
+ import {
2
+ Component,
3
+ useCallback,
4
+ useEffect,
5
+ useMemo,
6
+ useRef,
7
+ useState,
8
+ } from 'react';
9
+ import { useMap } from '@vis.gl/react-google-maps';
10
+ import type { PolylineProps } from './Polyline';
11
+
12
+ const ANIMATION_DURATION = 1500;
13
+
14
+ function interpolateColor(color1: string, color2: string, t: number): string {
15
+ const hex = (c: string) => parseInt(c, 16);
16
+ const r1 = hex(color1.slice(1, 3));
17
+ const g1 = hex(color1.slice(3, 5));
18
+ const b1 = hex(color1.slice(5, 7));
19
+ const r2 = hex(color2.slice(1, 3));
20
+ const g2 = hex(color2.slice(3, 5));
21
+ const b2 = hex(color2.slice(5, 7));
22
+
23
+ const r = Math.round(r1 + (r2 - r1) * t);
24
+ const g = Math.round(g1 + (g2 - g1) * t);
25
+ const b = Math.round(b1 + (b2 - b1) * t);
26
+
27
+ return `#${r.toString(16).padStart(2, '0')}${g
28
+ .toString(16)
29
+ .padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
30
+ }
31
+
32
+ function getGradientColor(colors: string[], position: number): string {
33
+ if (colors.length === 0) return '#000000';
34
+ if (colors.length === 1 || position <= 0) return colors[0]!;
35
+ if (position >= 1) return colors[colors.length - 1]!;
36
+
37
+ const scaledPos = position * (colors.length - 1);
38
+ const index = Math.floor(scaledPos);
39
+ const t = scaledPos - index;
40
+
41
+ return interpolateColor(colors[index]!, colors[index + 1]!, t);
42
+ }
43
+
44
+ function PolylineImpl({
45
+ coordinates,
46
+ strokeColors,
47
+ strokeWidth = 1,
48
+ animated,
49
+ zIndex,
50
+ }: PolylineProps) {
51
+ const resolvedZIndex = zIndex ?? (animated ? 1 : 0);
52
+ const map = useMap();
53
+ const polylinesRef = useRef<google.maps.Polyline[]>([]);
54
+ const animationRef = useRef<number>(0);
55
+
56
+ const colors = useMemo(
57
+ () =>
58
+ strokeColors && strokeColors.length > 0
59
+ ? (strokeColors as string[])
60
+ : ['#000000'],
61
+ [strokeColors]
62
+ );
63
+
64
+ const hasGradient = colors.length > 1;
65
+
66
+ // Refs for animation loop access
67
+ const propsRef = useRef({
68
+ map,
69
+ colors,
70
+ strokeWidth,
71
+ hasGradient,
72
+ zIndex: resolvedZIndex,
73
+ });
74
+ const [mapReady, setMapReady] = useState(!!map);
75
+
76
+ useEffect(() => {
77
+ propsRef.current = {
78
+ map,
79
+ colors,
80
+ strokeWidth,
81
+ hasGradient,
82
+ zIndex: resolvedZIndex,
83
+ };
84
+ if (map && !mapReady) setMapReady(true);
85
+ }, [map, colors, strokeWidth, hasGradient, resolvedZIndex, mapReady]);
86
+
87
+ const updatePath = useCallback((path: google.maps.LatLngLiteral[]) => {
88
+ const {
89
+ map: currentMap,
90
+ colors: currentColors,
91
+ strokeWidth: currentStrokeWidth,
92
+ hasGradient: currentHasGradient,
93
+ zIndex: currentZIndex,
94
+ } = propsRef.current;
95
+ if (!currentMap || path.length < 2) return;
96
+
97
+ const neededSegments = currentHasGradient ? path.length - 1 : 1;
98
+ const existing = polylinesRef.current;
99
+
100
+ // Update or create segments
101
+ for (let i = 0; i < neededSegments; i++) {
102
+ const segmentPath = currentHasGradient ? [path[i]!, path[i + 1]!] : path;
103
+ const color = currentHasGradient
104
+ ? getGradientColor(currentColors, i / (path.length - 1))
105
+ : currentColors[0]!;
106
+
107
+ const segment = existing[i];
108
+ if (segment) {
109
+ segment.setPath(segmentPath);
110
+ segment.setOptions({ strokeColor: color });
111
+ } else {
112
+ existing.push(
113
+ new google.maps.Polyline({
114
+ path: segmentPath,
115
+ strokeColor: color,
116
+ strokeWeight: currentStrokeWidth,
117
+ strokeOpacity: 1,
118
+ zIndex: currentZIndex,
119
+ map: currentMap,
120
+ })
121
+ );
122
+ }
123
+ }
124
+
125
+ // Remove extra segments
126
+ for (let i = neededSegments; i < existing.length; i++) {
127
+ existing[i]?.setMap(null);
128
+ }
129
+ existing.length = neededSegments;
130
+ }, []);
131
+
132
+ // Cleanup on unmount
133
+ useEffect(() => {
134
+ const polylines = polylinesRef.current;
135
+ return () => {
136
+ cancelAnimationFrame(animationRef.current);
137
+ polylines.forEach((p) => p.setMap(null));
138
+ };
139
+ }, []);
140
+
141
+ // Main effect
142
+ useEffect(() => {
143
+ if (!propsRef.current.map || coordinates.length === 0) return;
144
+
145
+ const fullPath = coordinates.map((c) => ({
146
+ lat: c.latitude,
147
+ lng: c.longitude,
148
+ }));
149
+
150
+ cancelAnimationFrame(animationRef.current);
151
+
152
+ if (!animated) {
153
+ updatePath(fullPath);
154
+ return;
155
+ }
156
+
157
+ const totalPoints = fullPath.length;
158
+ const cycleDuration = ANIMATION_DURATION * 2;
159
+
160
+ const animate = (time: number) => {
161
+ const progress = (time % cycleDuration) / ANIMATION_DURATION;
162
+ const startIdx = progress <= 1 ? 0 : (progress - 1) * totalPoints;
163
+ const endIdx = progress <= 1 ? progress * totalPoints : totalPoints;
164
+
165
+ const partialPath: google.maps.LatLngLiteral[] = [];
166
+ const startFloor = Math.floor(startIdx);
167
+ const endFloor = Math.floor(endIdx);
168
+
169
+ // Start point (interpolated)
170
+ if (startFloor < totalPoints) {
171
+ const frac = startIdx - startFloor;
172
+ const from = fullPath[startFloor]!;
173
+ const to = fullPath[Math.min(startFloor + 1, totalPoints - 1)]!;
174
+ partialPath.push(
175
+ frac > 0
176
+ ? {
177
+ lat: from.lat + (to.lat - from.lat) * frac,
178
+ lng: from.lng + (to.lng - from.lng) * frac,
179
+ }
180
+ : from
181
+ );
182
+ }
183
+
184
+ // Middle points
185
+ for (
186
+ let i = startFloor + 1;
187
+ i <= Math.min(endFloor, totalPoints - 1);
188
+ i++
189
+ ) {
190
+ partialPath.push(fullPath[i]!);
191
+ }
192
+
193
+ // End point (interpolated)
194
+ if (endFloor < totalPoints - 1) {
195
+ const frac = endIdx - endFloor;
196
+ const from = fullPath[endFloor]!;
197
+ const to = fullPath[endFloor + 1]!;
198
+ if (frac > 0) {
199
+ partialPath.push({
200
+ lat: from.lat + (to.lat - from.lat) * frac,
201
+ lng: from.lng + (to.lng - from.lng) * frac,
202
+ });
203
+ }
204
+ }
205
+
206
+ updatePath(partialPath);
207
+ animationRef.current = requestAnimationFrame(animate);
208
+ };
209
+
210
+ animationRef.current = requestAnimationFrame(animate);
211
+
212
+ return () => cancelAnimationFrame(animationRef.current);
213
+ }, [coordinates, animated, hasGradient, updatePath, mapReady]);
214
+
215
+ return null;
216
+ }
217
+
218
+ export class Polyline extends Component<PolylineProps> {
219
+ render() {
220
+ return <PolylineImpl {...this.props} />;
221
+ }
222
+ }
@@ -0,0 +1,4 @@
1
+ export { Marker } from './Marker.web';
2
+ export { Polyline } from './Polyline.web';
3
+ export type { MarkerProps } from './Marker';
4
+ export type { PolylineProps } from './Polyline';
@@ -66,7 +66,10 @@ interface NativeCommands {
66
66
  fitCoordinates: (
67
67
  viewRef: React.ElementRef<ComponentType>,
68
68
  coordinates: Coordinate[],
69
- padding: Double,
69
+ paddingTop: Double,
70
+ paddingLeft: Double,
71
+ paddingBottom: Double,
72
+ paddingRight: Double,
70
73
  duration: Double
71
74
  ) => void;
72
75
  }
@@ -67,7 +67,10 @@ interface NativeCommands {
67
67
  fitCoordinates: (
68
68
  viewRef: React.ElementRef<ComponentType>,
69
69
  coordinates: Coordinate[],
70
- padding: Double,
70
+ paddingTop: Double,
71
+ paddingLeft: Double,
72
+ paddingBottom: Double,
73
+ paddingRight: Double,
71
74
  duration: Double
72
75
  ) => void;
73
76
  }
package/src/index.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export { MapView } from './MapView';
2
+ export { MapProvider } from './MapProvider';
3
+ export type { MapProviderProps } from './MapProvider.types';
2
4
  export * from './components';
3
5
  export type {
4
6
  MapViewProps,
@@ -7,4 +9,9 @@ export type {
7
9
  FitCoordinatesOptions,
8
10
  CameraEventPayload,
9
11
  } from './MapView.types';
10
- export type { MapProvider, Coordinate, Point, EdgeInsets } from './types';
12
+ export type {
13
+ MapProvider as MapProviderType,
14
+ Coordinate,
15
+ Point,
16
+ EdgeInsets,
17
+ } from './types';
@@ -0,0 +1,17 @@
1
+ export { MapView } from './MapView.web';
2
+ export { MapProvider } from './MapProvider.web';
3
+ export type { MapProviderProps } from './MapProvider.types';
4
+ export * from './components/index.web';
5
+ export type {
6
+ MapViewProps,
7
+ MapViewRef,
8
+ MoveCameraOptions,
9
+ FitCoordinatesOptions,
10
+ CameraEventPayload,
11
+ } from './MapView.types';
12
+ export type {
13
+ MapProvider as MapProviderType,
14
+ Coordinate,
15
+ Point,
16
+ EdgeInsets,
17
+ } from './types';