Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,15 @@ _Avoid_: Tile source, geocoding provider
**Google Map ID**:
A Google Cloud Map ID used by the Google Maps SDK to apply cloud-based map styling. It is supported only by the `google` map provider and is distinct from the Google Maps API key required to load the SDK.
_Avoid_: API key, style JSON

**Marker**:
A point annotation rendered at a geographic coordinate on the map.
_Avoid_: Pin

**Marker Cluster**:
A grouped marker representation shown when nearby clusterable markers collapse into one map annotation.
_Avoid_: Cluster pin, marker group

**Entering Animation**:
The visual transition used when a marker or marker cluster first appears on the map.
_Avoid_: Appear animation, spawn animation
62 changes: 46 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,24 +140,52 @@ The `google` provider also accepts `googleMapId` for Google Cloud Map ID styling

`googleMapId` is creation-time configuration for native SDK views. Changing it remounts the native map view, matching provider changes.

### Marker entering animations

`MapView` can configure native entering animations for markers and marker clusters:

```tsx
<MapView
style={{ flex: 1 }}
clusteringEnabled
markerEnteringAnimation={{ preset: 'fade-scale', duration: 180 }}
clusterEnteringAnimation={{ preset: 'fade' }}
>
<Marker
coordinate={{ latitude: 52.2297, longitude: 21.0122 }}
enteringAnimation={false}
/>
</MapView>
```

`markerEnteringAnimation` is the map-level default for all markers, including bulk `markers` descriptors. `Marker.enteringAnimation` and bulk marker `enteringAnimation` override that default for one marker; `false` is an explicit opt-out. `clusterEnteringAnimation` applies to marker clusters when clustering is enabled.

When no animation prop is set, the default is `system`: each provider keeps its native entering behavior. Explicit presets (`fade`, `fade-scale`) are the cross-provider contract. `fade-scale` may gracefully fall back to `fade` on SDK marker surfaces that do not support efficient scaling.

Explicit configs use milliseconds. `duration` defaults to `180`, `delay` defaults to `0`, and both values are clamped to `0..3000` before they reach the native provider. `reduceMotion` defaults to `system`, which disables explicit animations when the platform Reduced Motion setting asks for it; use `never` only when the app intentionally ignores that setting for this overlay.

On iOS with `provider="google"`, marker and cluster entering animations can reduce UI-thread frame rate when a large viewport refresh adds many markers at once. The provider caps animated markers per refresh and may show the remaining markers immediately to preserve map gesture performance. For very large marker sets, prefer clustering, shorter durations, or `markerEnteringAnimation={false}` / `clusterEnteringAnimation={false}` when smooth gestures are more important than entrance motion.

### Capability matrix

| Capability | `apple` iOS | `google` iOS | `google` Android | Future providers |
| -------------------- | ----------------------------------------------------------- | ------------------------------------------ | ------------------------------------------ | -------------------- |
| Region / camera | Supported | Supported | Supported | Planned |
| Camera animation | Supported | Supported | Supported | Planned |
| Visible region | Supported | Supported | Supported | Planned |
| Fit to coordinates | Supported | Supported | Supported | Planned |
| Map types | Standard, satellite, hybrid; terrain falls back to standard | Standard, satellite, hybrid, terrain | Standard, satellite, hybrid, terrain | Planned |
| Gestures | Supported | Supported | Supported | Planned |
| User location | Supported; host app owns permission prompt | Supported; host app owns permission prompt | Supported; host app owns permission prompt | Planned |
| Compass | Supported | Supported | Supported | Planned |
| Scale control | Supported | Unsupported | Unsupported | Planned per provider |
| Markers / overlays | Supported | Supported | Supported | Planned |
| Overlay press events | Supported | Supported | Supported | Planned |
| Clustering | Supported | Supported | Supported | Planned per provider |
| Custom styles | Curated subset on iOS 16+ | Google Maps JSON styles | Google Maps JSON styles | Planned per provider |
| Google Map ID | Unsupported | Supported | Supported | Planned per provider |
| Capability | `apple` iOS | `google` iOS | `google` Android | Future providers |
| -------------------------- | ----------------------------------------------------------- | ------------------------------------------ | ------------------------------------------ | -------------------- |
| Region / camera | Supported | Supported | Supported | Planned |
| Camera animation | Supported | Supported | Supported | Planned |
| Visible region | Supported | Supported | Supported | Planned |
| Fit to coordinates | Supported | Supported | Supported | Planned |
| Map types | Standard, satellite, hybrid; terrain falls back to standard | Standard, satellite, hybrid, terrain | Standard, satellite, hybrid, terrain | Planned |
| Gestures | Supported | Supported | Supported | Planned |
| User location | Supported; host app owns permission prompt | Supported; host app owns permission prompt | Supported; host app owns permission prompt | Planned |
| Compass | Supported | Supported | Supported | Planned |
| Scale control | Supported | Unsupported | Unsupported | Planned per provider |
| Markers / overlays | Supported | Supported | Supported | Planned |
| Overlay press events | Supported | Supported | Supported | Planned |
| Marker entering animation | System + `fade`, `fade-scale` | System + `fade`; scale fallback | System + `fade`; scale fallback | Planned per provider |
| Cluster entering animation | System + `fade`, `fade-scale` | System + `fade`; scale fallback | System + `fade`; scale fallback | Planned per provider |
| Clustering | Supported | Supported | Supported | Planned per provider |
| Custom styles | Curated subset on iOS 16+ | Google Maps JSON styles | Google Maps JSON styles | Planned per provider |
| Google Map ID | Unsupported | Supported | Supported | Planned per provider |

## Public API

Expand All @@ -183,7 +211,9 @@ The `google` provider also accepts `googleMapId` for Google Cloud Map ID styling
| `MapViewRef` | Imperative handle for camera control |
| `MapViewProps` | Props for `MapView` |
| `MapViewPropsForProvider` | Provider-specific `MapView` props |
| `MarkerDescriptor` | Bulk marker descriptor |
| `MarkerProps` | Props for `Marker` |
| `OverlayEnteringAnimation` | Marker / marker-cluster entering animation config |
| `PolylineProps` | Props for `Polyline` |
| `PolygonProps` | Props for `Polygon` |
| `CircleProps` | Props for `Circle` |
Expand Down
19 changes: 19 additions & 0 deletions docs/adr/0003-native-overlay-entering-animations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# ADR 0003: Native overlay entering animations

## Status

Accepted

## Context

Markers and marker clusters are rendered from serialized overlay descriptors, not from React Native child views. Reanimated is a good fit for animating real React Native views, but making it the core path for descriptor-backed map overlays would add an optional ecosystem dependency to the package's default API without matching the current rendering model.

## Decision

Implement marker and marker-cluster entering animations as provider-owned native behavior configured through serializable TypeScript props. The core package does not require Reanimated for these animations; Reanimated integrations can be added later behind optional subpath exports when there is a view-backed animation surface.

## Consequences

- Marker and marker-cluster entering animations work for descriptor and bulk-marker APIs without requiring host apps to install Reanimated.
- Providers may keep native `system` defaults while explicit presets define the cross-provider contract.
- Future Reanimated support should be additive and optional, not a replacement for the native descriptor animation path.
6 changes: 6 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ Map and overlay callbacks are wired through Nitro listeners on the HybridView. C
| `googleMapId` | Google Cloud Map ID for the `google` provider. It is creation-time SDK configuration, so changing it remounts the native map view. |
| `clusteringEnabled` | Custom grid-based clustering via `MarkerClusterEngine` on both platforms (viewport-aware, background compute). |
| `Marker.clusterable` | Opt-out per marker (defaults to `true`). Non-clusterable markers always render individually. |
| `markerEnteringAnimation` / `Marker.enteringAnimation` | Native entering animation for newly added marker render elements. Per-marker values override the map-level default; `false` is an explicit opt-out. |
| `clusterEnteringAnimation` | Native entering animation for newly added marker-cluster render elements. Available only on providers with clustering support. |
| `customMapStyle` | JSON string. The `google` provider uses Google Maps JSON styles on iOS and Android. The `apple` provider maps a curated subset to `MKMapConfiguration` on iOS 16+. |
| `showsUserLocation` / `followsUserLocation` | Toggles the native user-location layer. Host app must request location permission (`NSLocationWhenInUseUsageDescription` on iOS; `ACCESS_FINE_LOCATION` on Android). |
| `showsCompass` / `showsScale` | Compass on both platforms. Scale is iOS-only (`showsScale` is a no-op on Android). |
Expand All @@ -99,6 +101,10 @@ Map and overlay callbacks are wired through Nitro listeners on the HybridView. C

`Marker`, `Polyline`, `Polygon`, and `Circle` are overlay components that compose inside `MapView`. Overlay props are collected on the JS side and serialized into descriptor structs passed to the native `HybridMapView` (data-driven architecture).

Marker and marker-cluster entering animations follow the same descriptor model. The public API accepts `false`, `system`, or a serializable preset config; the React wrapper normalizes that into native descriptors. Native provider adapters execute the animation when a marker render element appears in the render diff. Updating animation config for an already retained marker does not restart the animation; the new config is used the next time that marker is added again.

Google Maps on iOS is more sensitive to marker animation churn than MapKit. Large viewport refreshes can add many `GMSMarker` instances on the main thread, so the Google provider limits how many markers animate per refresh and reveals the rest immediately. This keeps gestures responsive, but very large marker sets may still need clustering, disabled entering animations, or a future provider-specific animation strategy.

## Data flow (target state)

```
Expand Down
1 change: 1 addition & 0 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Delivered incrementally during Phases 3–5; polished for platform consistency i
| Overlay architecture | Per-view native vs data-driven descriptors | Data-driven (Option B) |
| Clustering library | Custom vs platform-native | Platform-native (MKClusterAnnotation / maps-utils) |
| Provider architecture | In-place SDK switching vs adapter remount | Provider adapter + React remount |
| Overlay animations | Reanimated core path vs native descriptors | Native descriptor animations; Reanimated optional |
| Offline support | Tile caching strategy | Future consideration |

## Future provider work
Expand Down
Loading
Loading