@sigx/lynx-maps 0.4.1
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.
- package/LICENSE +21 -0
- package/README.md +134 -0
- package/android/com/sigx/maps/SigxMapBehavior.kt +19 -0
- package/android/com/sigx/maps/SigxMapMarkerBehavior.kt +18 -0
- package/android/com/sigx/maps/SigxMapMarkerUI.kt +65 -0
- package/android/com/sigx/maps/SigxMapUI.kt +279 -0
- package/dist/Map.d.ts +41 -0
- package/dist/Map.js +42 -0
- package/dist/MapMarker.d.ts +29 -0
- package/dist/MapMarker.js +30 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -0
- package/dist/jsx-augment.d.ts +109 -0
- package/dist/jsx-augment.js +1 -0
- package/ios/SigxMapMarkerUI.swift +102 -0
- package/ios/SigxMapUI.swift +236 -0
- package/package.json +64 -0
- package/signalx-module.json +30 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Andreas Ekdahl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# @sigx/lynx-maps
|
|
2
|
+
|
|
3
|
+
Native map view for sigx-lynx. Backed by:
|
|
4
|
+
|
|
5
|
+
- **iOS** — `MKMapView` (Apple Maps, no key required).
|
|
6
|
+
- **Android** — `com.google.android.gms.maps.MapView` (Google Maps,
|
|
7
|
+
requires an API key — see [setup](#android-api-key-setup) below).
|
|
8
|
+
|
|
9
|
+
Closes [signalxjs/lynx#84](https://github.com/signalxjs/lynx/issues/84).
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add @sigx/lynx-maps
|
|
15
|
+
pnpm sigx prebuild
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The autolinker picks up `signalx-module.json` from this package; prebuild
|
|
19
|
+
regenerates `GeneratedBehaviors.kt` (Android) and
|
|
20
|
+
`GeneratedComponentRegistry.swift` (iOS) so the `<sigx-map>` and
|
|
21
|
+
`<sigx-map-marker>` tags are bound to their native UI classes.
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { Map, MapMarker } from '@sigx/lynx-maps';
|
|
27
|
+
|
|
28
|
+
const region = {
|
|
29
|
+
latitude: 59.3293,
|
|
30
|
+
longitude: 18.0686,
|
|
31
|
+
latitudeDelta: 0.1,
|
|
32
|
+
longitudeDelta: 0.1,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function MapScreen() {
|
|
36
|
+
return (
|
|
37
|
+
<Map
|
|
38
|
+
region={region}
|
|
39
|
+
showsUserLocation
|
|
40
|
+
mapType="standard"
|
|
41
|
+
class="flex-1"
|
|
42
|
+
onRegionChange={(e) => console.log('region', e.detail.region)}
|
|
43
|
+
onMarkerPress={(e) => console.log('marker', e.detail.id)}
|
|
44
|
+
>
|
|
45
|
+
<MapMarker
|
|
46
|
+
coordinate={{ latitude: 59.3293, longitude: 18.0686 }}
|
|
47
|
+
title="Stockholm"
|
|
48
|
+
description="Sweden's capital"
|
|
49
|
+
id="sthlm"
|
|
50
|
+
/>
|
|
51
|
+
</Map>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Props — `<Map>`
|
|
57
|
+
|
|
58
|
+
| Prop | Type | Notes |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| `region` | `MapRegion` | `{ latitude, longitude, latitudeDelta, longitudeDelta }`. The map snaps to this on every prop change — for "initial region only" semantics, pass it once and keep it in local state. |
|
|
61
|
+
| `showsUserLocation` | `boolean` | Shows the blue location dot. iOS uses `NSLocationWhenInUseUsageDescription` (added automatically); Android requires `ACCESS_FINE_LOCATION` at runtime (declared in the manifest). |
|
|
62
|
+
| `mapType` | `'standard' \| 'satellite' \| 'hybrid'` | Base style. Default `'standard'`. |
|
|
63
|
+
| `onRegionChange` | `(e) => void` | Fires after the user pans/zooms or after a programmatic `region` change. Detail: `{ region }`. |
|
|
64
|
+
| `onPress` | `(e) => void` | User tapped the map (not on a marker). Detail: `{ coordinate }`. |
|
|
65
|
+
| `onMarkerPress` | `(e) => void` | User tapped a marker. Detail: `{ id, coordinate }`. |
|
|
66
|
+
| `class`, `style`, `children` | — | Standard Lynx props. Children should be `<MapMarker>` elements. |
|
|
67
|
+
|
|
68
|
+
## Props — `<MapMarker>`
|
|
69
|
+
|
|
70
|
+
| Prop | Type | Notes |
|
|
71
|
+
| --- | --- | --- |
|
|
72
|
+
| `coordinate` | `{ latitude, longitude }` | Required. |
|
|
73
|
+
| `title` | `string` | Callout title shown on tap. |
|
|
74
|
+
| `description` | `string` | Callout subtitle. |
|
|
75
|
+
| `id` | `string` | Forwarded as `event.detail.id` on the parent's `onMarkerPress`. |
|
|
76
|
+
|
|
77
|
+
## Android API key setup
|
|
78
|
+
|
|
79
|
+
Google Maps requires an API key. Get one at
|
|
80
|
+
[console.cloud.google.com](https://console.cloud.google.com/) → APIs &
|
|
81
|
+
Services → Credentials → "Maps SDK for Android".
|
|
82
|
+
|
|
83
|
+
Once you have a key, add the standard meta-data block inside
|
|
84
|
+
`<application>` in `android/app/src/main/AndroidManifest.xml`:
|
|
85
|
+
|
|
86
|
+
```xml
|
|
87
|
+
<meta-data
|
|
88
|
+
android:name="com.google.android.geo.API_KEY"
|
|
89
|
+
android:value="@string/google_maps_api_key" />
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
…and define the string in `android/app/src/main/res/values/strings.xml`:
|
|
93
|
+
|
|
94
|
+
```xml
|
|
95
|
+
<resources>
|
|
96
|
+
<!-- existing entries -->
|
|
97
|
+
<string name="google_maps_api_key" translatable="false">YOUR_KEY_HERE</string>
|
|
98
|
+
</resources>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
If you don't add the meta-data block, the map still renders — but with
|
|
102
|
+
Google's "For development purposes only" watermark. That's fine for early
|
|
103
|
+
testing; ship a real key before publishing to the Play Store.
|
|
104
|
+
|
|
105
|
+
On `sigx-lynx-go` (the prebuilt sandbox app) the placeholder watermark is
|
|
106
|
+
expected — we can't bundle a per-user API key in a public binary.
|
|
107
|
+
|
|
108
|
+
## Limitations (v1)
|
|
109
|
+
|
|
110
|
+
Tracked as v2 follow-ups:
|
|
111
|
+
|
|
112
|
+
- **Imperative methods** (`animateToRegion`, `fitToCoordinates`) need
|
|
113
|
+
Lynx's `UIMethodInvoker` surface, which isn't wired through sigx-lynx
|
|
114
|
+
yet. Same blocker as `WebView.goBack` / `reload`.
|
|
115
|
+
- **Custom marker icons** — markers use the platform default pin.
|
|
116
|
+
- **Polylines, polygons, circles, ground overlays.**
|
|
117
|
+
- **Clustering.**
|
|
118
|
+
- **Offline tiles / Map snapshots.**
|
|
119
|
+
- **Google Maps on iOS** — iOS stays on MapKit (key-free); a future
|
|
120
|
+
follow-up could let apps opt into the Google Maps SDK for iOS.
|
|
121
|
+
|
|
122
|
+
## Lifecycle notes (Android)
|
|
123
|
+
|
|
124
|
+
`com.google.android.gms.maps.MapView` normally wants Activity lifecycle
|
|
125
|
+
forwarding (`onCreate`/`onResume`/`onPause`/`onDestroy`). v1 calls
|
|
126
|
+
`onCreate` + `onStart` + `onResume` in `createView`, and `onPause` +
|
|
127
|
+
`onStop` in `onDetach`. For a typical screen-level map this works; if the
|
|
128
|
+
host Activity is paused with the map still mounted, tile prefetching
|
|
129
|
+
keeps running until the LynxUI itself detaches. A proper `activityHook`
|
|
130
|
+
plumbing is tracked as a follow-up.
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
MIT
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package com.sigx.maps
|
|
2
|
+
|
|
3
|
+
import com.lynx.tasm.behavior.Behavior
|
|
4
|
+
import com.lynx.tasm.behavior.LynxContext
|
|
5
|
+
import com.lynx.tasm.behavior.ui.LynxUI
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Registers the `<sigx-map>` JSX tag with Lynx's UI registry.
|
|
9
|
+
*
|
|
10
|
+
* Discovered by the autolinker via `signalx-module.json`'s `android.behaviors`
|
|
11
|
+
* field; the generated `GeneratedBehaviors.attachAll(builder)` calls
|
|
12
|
+
* `builder.addBehavior(SigxMapBehavior())` for every `LynxViewBuilder`
|
|
13
|
+
* in the app (production + dev-client path).
|
|
14
|
+
*/
|
|
15
|
+
class SigxMapBehavior : Behavior("sigx-map") {
|
|
16
|
+
override fun createUI(context: LynxContext): LynxUI<*> {
|
|
17
|
+
return SigxMapUI(context)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
package com.sigx.maps
|
|
2
|
+
|
|
3
|
+
import com.lynx.tasm.behavior.Behavior
|
|
4
|
+
import com.lynx.tasm.behavior.LynxContext
|
|
5
|
+
import com.lynx.tasm.behavior.ui.LynxUI
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Registers the `<sigx-map-marker>` JSX tag with Lynx's UI registry. The
|
|
9
|
+
* actual marker is added to the parent `SigxMapUI`'s GoogleMap on attach;
|
|
10
|
+
* the LynxUI's own view is a zero-size hidden placeholder that never gets
|
|
11
|
+
* laid out, because the parent map intercepts `insertChild` and skips the
|
|
12
|
+
* default add-to-view-hierarchy path.
|
|
13
|
+
*/
|
|
14
|
+
class SigxMapMarkerBehavior : Behavior("sigx-map-marker") {
|
|
15
|
+
override fun createUI(context: LynxContext): LynxUI<*> {
|
|
16
|
+
return SigxMapMarkerUI(context)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
package com.sigx.maps
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.view.View
|
|
5
|
+
import com.google.android.gms.maps.model.Marker
|
|
6
|
+
import com.lynx.tasm.behavior.LynxContext
|
|
7
|
+
import com.lynx.tasm.behavior.LynxProp
|
|
8
|
+
import com.lynx.tasm.behavior.ui.LynxUI
|
|
9
|
+
import org.json.JSONObject
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Native UI for the `<sigx-map-marker>` JSX element on Android.
|
|
13
|
+
*
|
|
14
|
+
* A marker doesn't render its own real view — Google Maps tracks pins
|
|
15
|
+
* separately via [com.google.android.gms.maps.GoogleMap.addMarker]. We
|
|
16
|
+
* still need a `LynxUI<View>` because Lynx's UI tree expects every child
|
|
17
|
+
* to be a UI. The native view is a zero-size hidden `View` that never
|
|
18
|
+
* gets laid out because the parent `SigxMapUI` intercepts `insertChild`
|
|
19
|
+
* for markers and skips the default add-to-view-hierarchy path.
|
|
20
|
+
*/
|
|
21
|
+
class SigxMapMarkerUI(context: LynxContext) : LynxUI<View>(context) {
|
|
22
|
+
|
|
23
|
+
internal var owningMap: SigxMapUI? = null
|
|
24
|
+
internal var attachedMarker: Marker? = null
|
|
25
|
+
|
|
26
|
+
internal var coordinateLat: Double = 0.0
|
|
27
|
+
internal var coordinateLng: Double = 0.0
|
|
28
|
+
internal var markerTitle: String? = null
|
|
29
|
+
internal var markerDescription: String? = null
|
|
30
|
+
internal var markerId: String = ""
|
|
31
|
+
|
|
32
|
+
override fun createView(context: Context): View {
|
|
33
|
+
val v = View(context)
|
|
34
|
+
v.visibility = View.GONE
|
|
35
|
+
return v
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── Prop setters ─────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
@LynxProp(name = "coordinate")
|
|
41
|
+
fun setCoordinate(value: String?) {
|
|
42
|
+
if (value.isNullOrEmpty()) return
|
|
43
|
+
val obj = runCatching { JSONObject(value) }.getOrNull() ?: return
|
|
44
|
+
coordinateLat = obj.optDouble("latitude", coordinateLat)
|
|
45
|
+
coordinateLng = obj.optDouble("longitude", coordinateLng)
|
|
46
|
+
owningMap?.markerDidUpdate(this)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@LynxProp(name = "title")
|
|
50
|
+
fun setTitle(value: String?) {
|
|
51
|
+
markerTitle = value
|
|
52
|
+
owningMap?.markerDidUpdate(this)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@LynxProp(name = "description")
|
|
56
|
+
fun setDescription(value: String?) {
|
|
57
|
+
markerDescription = value
|
|
58
|
+
owningMap?.markerDidUpdate(this)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@LynxProp(name = "marker-id")
|
|
62
|
+
fun setMarkerId(value: String?) {
|
|
63
|
+
markerId = value ?: ""
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
package com.sigx.maps
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import com.google.android.gms.maps.CameraUpdateFactory
|
|
5
|
+
import com.google.android.gms.maps.GoogleMap
|
|
6
|
+
import com.google.android.gms.maps.MapView
|
|
7
|
+
import com.google.android.gms.maps.model.CameraPosition
|
|
8
|
+
import com.google.android.gms.maps.model.LatLng
|
|
9
|
+
import com.google.android.gms.maps.model.MarkerOptions
|
|
10
|
+
import com.lynx.tasm.behavior.LynxContext
|
|
11
|
+
import com.lynx.tasm.behavior.LynxProp
|
|
12
|
+
import com.lynx.tasm.behavior.ui.LynxBaseUI
|
|
13
|
+
import com.lynx.tasm.behavior.ui.LynxUI
|
|
14
|
+
import com.lynx.tasm.event.LynxDetailEvent
|
|
15
|
+
import org.json.JSONObject
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Native UI for the `<sigx-map>` JSX element on Android.
|
|
19
|
+
*
|
|
20
|
+
* Prop / event surface (v1):
|
|
21
|
+
* - `region` → JSON-stringified `MapRegion`
|
|
22
|
+
* - `shows-user-location` → enable user-location dot (requires permission)
|
|
23
|
+
* - `map-type` → `"standard" | "satellite" | "hybrid"`
|
|
24
|
+
* - `bindregionchange` → CameraIdleListener
|
|
25
|
+
* - `bindpress` → OnMapClickListener
|
|
26
|
+
* - `bindmarkerpress` → OnMarkerClickListener
|
|
27
|
+
*
|
|
28
|
+
* Imperative methods (animateToRegion / fitToCoordinates) are tracked as a
|
|
29
|
+
* v2 follow-up — same UIMethodInvoker blocker as WebView.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* Google MapView requires Activity lifecycle forwarding (onCreate /
|
|
33
|
+
* onResume / onPause / onDestroy). The v1 implementation calls onCreate +
|
|
34
|
+
* onStart + onResume in [createView] so the map is interactive
|
|
35
|
+
* immediately, and onPause + onStop + onDestroy in [onDetach]. For an
|
|
36
|
+
* app that backgrounds while a map is visible, this is best-effort —
|
|
37
|
+
* most user-visible maps work, but tile prefetching pauses on
|
|
38
|
+
* backgrounding only after the next prebuild wires a proper
|
|
39
|
+
* `activityHook`. Doc'd in README.
|
|
40
|
+
*/
|
|
41
|
+
class SigxMapUI(context: LynxContext) : LynxUI<MapView>(context) {
|
|
42
|
+
|
|
43
|
+
private var googleMap: GoogleMap? = null
|
|
44
|
+
private var pendingRegion: JSONObject? = null
|
|
45
|
+
private var pendingShowUserLocation: Boolean = false
|
|
46
|
+
private var pendingMapType: String? = null
|
|
47
|
+
/** Marker children waiting for GoogleMap to be ready. */
|
|
48
|
+
private val pendingMarkers = mutableListOf<SigxMapMarkerUI>()
|
|
49
|
+
/** All currently-attached markers, by their LynxUI sign. */
|
|
50
|
+
internal val attachedMarkers = mutableMapOf<Int, SigxMapMarkerUI>()
|
|
51
|
+
|
|
52
|
+
override fun createView(context: Context): MapView {
|
|
53
|
+
val map = MapView(context)
|
|
54
|
+
// Bundle=null is the Google-recommended invocation when the host
|
|
55
|
+
// doesn't save/restore MapView state per-instance.
|
|
56
|
+
map.onCreate(null)
|
|
57
|
+
map.onStart()
|
|
58
|
+
map.onResume()
|
|
59
|
+
map.getMapAsync { gmap ->
|
|
60
|
+
googleMap = gmap
|
|
61
|
+
applyPendingState()
|
|
62
|
+
wireListeners(gmap)
|
|
63
|
+
// Attach any markers that arrived before the map was ready.
|
|
64
|
+
for (m in pendingMarkers) attachMarker(m)
|
|
65
|
+
pendingMarkers.clear()
|
|
66
|
+
}
|
|
67
|
+
return map
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
override fun onDetach() {
|
|
71
|
+
super.onDetach()
|
|
72
|
+
// Best-effort lifecycle pairing — see class-level remarks.
|
|
73
|
+
// onDestroy() releases the GL renderer, so call it last to avoid
|
|
74
|
+
// leaking native resources when the host UI tears down.
|
|
75
|
+
runCatching { mView.onPause() }
|
|
76
|
+
runCatching { mView.onStop() }
|
|
77
|
+
runCatching { mView.onDestroy() }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── Child plumbing ───────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
override fun insertChild(child: LynxBaseUI?, index: Int) {
|
|
83
|
+
if (child is SigxMapMarkerUI) {
|
|
84
|
+
attachMarker(child)
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
super.insertChild(child, index)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
override fun removeChild(child: LynxBaseUI?) {
|
|
91
|
+
if (child is SigxMapMarkerUI) {
|
|
92
|
+
detachMarker(child)
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
super.removeChild(child)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
internal fun attachMarker(marker: SigxMapMarkerUI) {
|
|
99
|
+
marker.owningMap = this
|
|
100
|
+
attachedMarkers[marker.sign] = marker
|
|
101
|
+
val gmap = googleMap
|
|
102
|
+
if (gmap == null) {
|
|
103
|
+
pendingMarkers.add(marker)
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
val opts = MarkerOptions()
|
|
107
|
+
.position(LatLng(marker.coordinateLat, marker.coordinateLng))
|
|
108
|
+
.title(marker.markerTitle)
|
|
109
|
+
.snippet(marker.markerDescription)
|
|
110
|
+
val pin = gmap.addMarker(opts)
|
|
111
|
+
if (pin != null) {
|
|
112
|
+
pin.tag = marker.sign
|
|
113
|
+
marker.attachedMarker = pin
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
internal fun detachMarker(marker: SigxMapMarkerUI) {
|
|
118
|
+
attachedMarkers.remove(marker.sign)
|
|
119
|
+
pendingMarkers.remove(marker)
|
|
120
|
+
marker.attachedMarker?.remove()
|
|
121
|
+
marker.attachedMarker = null
|
|
122
|
+
marker.owningMap = null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Re-sync an already-attached marker's coords/title after a prop change. */
|
|
126
|
+
internal fun markerDidUpdate(marker: SigxMapMarkerUI) {
|
|
127
|
+
val pin = marker.attachedMarker ?: return
|
|
128
|
+
pin.position = LatLng(marker.coordinateLat, marker.coordinateLng)
|
|
129
|
+
pin.title = marker.markerTitle
|
|
130
|
+
pin.snippet = marker.markerDescription
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── State application ────────────────────────────────────────────────
|
|
134
|
+
|
|
135
|
+
private fun applyPendingState() {
|
|
136
|
+
val gmap = googleMap ?: return
|
|
137
|
+
pendingRegion?.let { applyRegion(it) }
|
|
138
|
+
pendingRegion = null
|
|
139
|
+
// SecurityException is thrown by the setter when ACCESS_FINE_LOCATION
|
|
140
|
+
// isn't granted, so the try/catch has to wrap the assignment itself
|
|
141
|
+
// (not the RHS) — see PR #93 review.
|
|
142
|
+
try {
|
|
143
|
+
gmap.isMyLocationEnabled = pendingShowUserLocation
|
|
144
|
+
} catch (_: SecurityException) {
|
|
145
|
+
android.util.Log.w(
|
|
146
|
+
"SigxMap",
|
|
147
|
+
"shows-user-location=true but ACCESS_FINE_LOCATION not granted",
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
pendingMapType?.let { applyMapType(it) }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private fun applyRegion(obj: JSONObject) {
|
|
154
|
+
val gmap = googleMap ?: return
|
|
155
|
+
val lat = obj.optDouble("latitude", Double.NaN)
|
|
156
|
+
val lon = obj.optDouble("longitude", Double.NaN)
|
|
157
|
+
val lonD = obj.optDouble("longitudeDelta", Double.NaN)
|
|
158
|
+
if (lat.isNaN() || lon.isNaN()) return
|
|
159
|
+
// Map lonDelta to a Google-Maps zoom level. Google's zoom is
|
|
160
|
+
// logarithmic — zoom n shows 360°/2^n degrees of longitude
|
|
161
|
+
// horizontally. Solve for n from the user's lonDelta: invert and
|
|
162
|
+
// log. Clamp to Google's [2, 21] zoom range. latitudeDelta is
|
|
163
|
+
// ignored because the visible viewport's aspect ratio is fixed by
|
|
164
|
+
// the view size — fitting both deltas would require knowing the
|
|
165
|
+
// pixel dimensions, which is deferred to v2's `fitToCoordinates`.
|
|
166
|
+
val zoom = if (lonD > 0.0) {
|
|
167
|
+
val z = Math.log(360.0 / lonD) / Math.log(2.0)
|
|
168
|
+
z.toFloat().coerceIn(2f, 21f)
|
|
169
|
+
} else {
|
|
170
|
+
12f
|
|
171
|
+
}
|
|
172
|
+
gmap.moveCamera(
|
|
173
|
+
CameraUpdateFactory.newCameraPosition(
|
|
174
|
+
CameraPosition.Builder().target(LatLng(lat, lon)).zoom(zoom).build(),
|
|
175
|
+
),
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private fun applyMapType(value: String) {
|
|
180
|
+
val gmap = googleMap ?: return
|
|
181
|
+
gmap.mapType = when (value) {
|
|
182
|
+
"satellite" -> GoogleMap.MAP_TYPE_SATELLITE
|
|
183
|
+
"hybrid" -> GoogleMap.MAP_TYPE_HYBRID
|
|
184
|
+
else -> GoogleMap.MAP_TYPE_NORMAL
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private fun wireListeners(gmap: GoogleMap) {
|
|
189
|
+
gmap.setOnCameraIdleListener {
|
|
190
|
+
val target = gmap.cameraPosition.target
|
|
191
|
+
// Approximate the LynxRegion shape — Google reports zoom rather
|
|
192
|
+
// than deltas, so back-compute span from the visible bounds.
|
|
193
|
+
val bounds = gmap.projection.visibleRegion.latLngBounds
|
|
194
|
+
val latD = bounds.northeast.latitude - bounds.southwest.latitude
|
|
195
|
+
val lonD = bounds.northeast.longitude - bounds.southwest.longitude
|
|
196
|
+
fireEvent(
|
|
197
|
+
"regionchange",
|
|
198
|
+
mapOf(
|
|
199
|
+
"region" to mapOf(
|
|
200
|
+
"latitude" to target.latitude,
|
|
201
|
+
"longitude" to target.longitude,
|
|
202
|
+
"latitudeDelta" to latD,
|
|
203
|
+
"longitudeDelta" to lonD,
|
|
204
|
+
),
|
|
205
|
+
),
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
gmap.setOnMapClickListener { latLng ->
|
|
209
|
+
fireEvent(
|
|
210
|
+
"press",
|
|
211
|
+
mapOf(
|
|
212
|
+
"coordinate" to mapOf(
|
|
213
|
+
"latitude" to latLng.latitude,
|
|
214
|
+
"longitude" to latLng.longitude,
|
|
215
|
+
),
|
|
216
|
+
),
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
gmap.setOnMarkerClickListener { gMarker ->
|
|
220
|
+
val sign = gMarker.tag as? Int ?: return@setOnMarkerClickListener false
|
|
221
|
+
val marker = attachedMarkers[sign] ?: return@setOnMarkerClickListener false
|
|
222
|
+
fireEvent(
|
|
223
|
+
"markerpress",
|
|
224
|
+
mapOf(
|
|
225
|
+
"id" to marker.markerId,
|
|
226
|
+
"coordinate" to mapOf(
|
|
227
|
+
"latitude" to marker.coordinateLat,
|
|
228
|
+
"longitude" to marker.coordinateLng,
|
|
229
|
+
),
|
|
230
|
+
),
|
|
231
|
+
)
|
|
232
|
+
// Return false so Google's default behaviour (callout + camera
|
|
233
|
+
// re-centre) still runs.
|
|
234
|
+
false
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ── Prop setters ─────────────────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
@LynxProp(name = "region")
|
|
241
|
+
fun setRegion(value: String?) {
|
|
242
|
+
if (value.isNullOrEmpty()) return
|
|
243
|
+
val obj = runCatching { JSONObject(value) }.getOrNull() ?: return
|
|
244
|
+
if (googleMap == null) {
|
|
245
|
+
pendingRegion = obj
|
|
246
|
+
} else {
|
|
247
|
+
applyRegion(obj)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
@LynxProp(name = "shows-user-location")
|
|
252
|
+
fun setShowsUserLocation(value: Boolean) {
|
|
253
|
+
pendingShowUserLocation = value
|
|
254
|
+
val gmap = googleMap ?: return
|
|
255
|
+
try {
|
|
256
|
+
gmap.isMyLocationEnabled = value
|
|
257
|
+
} catch (_: SecurityException) {
|
|
258
|
+
android.util.Log.w(
|
|
259
|
+
"SigxMap",
|
|
260
|
+
"shows-user-location=true but ACCESS_FINE_LOCATION not granted",
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
@LynxProp(name = "map-type")
|
|
266
|
+
fun setMapType(value: String?) {
|
|
267
|
+
val v = value ?: "standard"
|
|
268
|
+
pendingMapType = v
|
|
269
|
+
applyMapType(v)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ── Event firing ─────────────────────────────────────────────────────
|
|
273
|
+
|
|
274
|
+
private fun fireEvent(name: String, params: Map<String, Any?>) {
|
|
275
|
+
val event = LynxDetailEvent(sign, name)
|
|
276
|
+
for ((k, v) in params) event.addDetail(k, v)
|
|
277
|
+
lynxContext.eventEmitter.sendCustomEvent(event)
|
|
278
|
+
}
|
|
279
|
+
}
|
package/dist/Map.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type Define } from '@sigx/lynx';
|
|
2
|
+
import './jsx-augment.js';
|
|
3
|
+
import type { MapRegion, MapType, MapPressEvent, MapRegionChangeEvent, MapMarkerPressEvent } from './jsx-augment.js';
|
|
4
|
+
export type MapProps = Define.Prop<'region', MapRegion, false> & Define.Prop<'showsUserLocation', boolean, false> & Define.Prop<'mapType', MapType, false> & Define.Prop<'class', string, false> & Define.Prop<'style', string | Record<string, string | number>, false> & Define.Prop<'onRegionChange', (e: MapRegionChangeEvent) => void, false> & Define.Prop<'onPress', (e: MapPressEvent) => void, false> & Define.Prop<'onMarkerPress', (e: MapMarkerPressEvent) => void, false> & Define.Prop<'children', unknown, false>;
|
|
5
|
+
/**
|
|
6
|
+
* Native map view.
|
|
7
|
+
*
|
|
8
|
+
* Backed by `MKMapView` on iOS and `com.google.android.gms.maps.MapView`
|
|
9
|
+
* on Android. Pass any number of `<MapMarker>` children to render pins.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* <Map
|
|
14
|
+
* region={{
|
|
15
|
+
* latitude: 59.3293,
|
|
16
|
+
* longitude: 18.0686,
|
|
17
|
+
* latitudeDelta: 0.1,
|
|
18
|
+
* longitudeDelta: 0.1,
|
|
19
|
+
* }}
|
|
20
|
+
* showsUserLocation
|
|
21
|
+
* onRegionChange={(e) => console.log('region', e.detail.region)}
|
|
22
|
+
* onMarkerPress={(e) => console.log('marker', e.detail.id)}
|
|
23
|
+
* >
|
|
24
|
+
* <MapMarker
|
|
25
|
+
* coordinate={{ latitude: 59.3293, longitude: 18.0686 }}
|
|
26
|
+
* title="Stockholm"
|
|
27
|
+
* />
|
|
28
|
+
* </Map>
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* - **Android API key**: Google Maps requires an API key. See this
|
|
33
|
+
* package's README for setup. The placeholder used by `sigx-lynx-go`
|
|
34
|
+
* shows the "For development purposes only" watermark — that's
|
|
35
|
+
* expected until you provide your own key.
|
|
36
|
+
* - **Imperative methods** (`animateToRegion`, `fitToCoordinates`) are
|
|
37
|
+
* tracked as a v2 follow-up — they need the Lynx UIMethodInvoker
|
|
38
|
+
* surface which isn't wired through sigx-lynx yet. The same gap
|
|
39
|
+
* blocks WebView's `goBack` / `reload`.
|
|
40
|
+
*/
|
|
41
|
+
export declare const Map: import("@sigx/runtime-core").ComponentFactory<MapProps, void, {}>;
|
package/dist/Map.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
|
|
2
|
+
import { component } from '@sigx/lynx';
|
|
3
|
+
import './jsx-augment.js';
|
|
4
|
+
/**
|
|
5
|
+
* Native map view.
|
|
6
|
+
*
|
|
7
|
+
* Backed by `MKMapView` on iOS and `com.google.android.gms.maps.MapView`
|
|
8
|
+
* on Android. Pass any number of `<MapMarker>` children to render pins.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <Map
|
|
13
|
+
* region={{
|
|
14
|
+
* latitude: 59.3293,
|
|
15
|
+
* longitude: 18.0686,
|
|
16
|
+
* latitudeDelta: 0.1,
|
|
17
|
+
* longitudeDelta: 0.1,
|
|
18
|
+
* }}
|
|
19
|
+
* showsUserLocation
|
|
20
|
+
* onRegionChange={(e) => console.log('region', e.detail.region)}
|
|
21
|
+
* onMarkerPress={(e) => console.log('marker', e.detail.id)}
|
|
22
|
+
* >
|
|
23
|
+
* <MapMarker
|
|
24
|
+
* coordinate={{ latitude: 59.3293, longitude: 18.0686 }}
|
|
25
|
+
* title="Stockholm"
|
|
26
|
+
* />
|
|
27
|
+
* </Map>
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @remarks
|
|
31
|
+
* - **Android API key**: Google Maps requires an API key. See this
|
|
32
|
+
* package's README for setup. The placeholder used by `sigx-lynx-go`
|
|
33
|
+
* shows the "For development purposes only" watermark — that's
|
|
34
|
+
* expected until you provide your own key.
|
|
35
|
+
* - **Imperative methods** (`animateToRegion`, `fitToCoordinates`) are
|
|
36
|
+
* tracked as a v2 follow-up — they need the Lynx UIMethodInvoker
|
|
37
|
+
* surface which isn't wired through sigx-lynx yet. The same gap
|
|
38
|
+
* blocks WebView's `goBack` / `reload`.
|
|
39
|
+
*/
|
|
40
|
+
export const Map = component(({ props }) => {
|
|
41
|
+
return () => (_jsx("sigx-map", { region: props.region == null ? undefined : JSON.stringify(props.region), "shows-user-location": props.showsUserLocation, "map-type": props.mapType, class: props.class, style: props.style, bindregionchange: props.onRegionChange, bindpress: props.onPress, bindmarkerpress: props.onMarkerPress, children: props.children }));
|
|
42
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Define } from '@sigx/lynx';
|
|
2
|
+
import './jsx-augment.js';
|
|
3
|
+
import type { MapCoordinate } from './jsx-augment.js';
|
|
4
|
+
export type MapMarkerProps = Define.Prop<'coordinate', MapCoordinate, true> & Define.Prop<'title', string, false> & Define.Prop<'description', string, false> & Define.Prop<'id', string, false>;
|
|
5
|
+
/**
|
|
6
|
+
* Marker pin on a `<Map>`.
|
|
7
|
+
*
|
|
8
|
+
* Coordinates are required; `title` / `description` show in the callout
|
|
9
|
+
* when the user taps the marker. `id` is surfaced as
|
|
10
|
+
* `event.detail.id` in the parent map's `onMarkerPress` handler — set
|
|
11
|
+
* one if multiple markers share the same coordinate.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <Map>
|
|
16
|
+
* <MapMarker
|
|
17
|
+
* coordinate={{ latitude: 59.3293, longitude: 18.0686 }}
|
|
18
|
+
* title="Stockholm"
|
|
19
|
+
* description="Sweden's capital"
|
|
20
|
+
* id="sthlm"
|
|
21
|
+
* />
|
|
22
|
+
* </Map>
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @remarks
|
|
26
|
+
* Marker pins are intentionally minimal in v1 — no custom icons, no
|
|
27
|
+
* draggable pins, no callout views. Those land in v2.
|
|
28
|
+
*/
|
|
29
|
+
export declare const MapMarker: import("@sigx/runtime-core").ComponentFactory<MapMarkerProps, void, {}>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx } from "@sigx/lynx/jsx-runtime";
|
|
2
|
+
import { component } from '@sigx/lynx';
|
|
3
|
+
import './jsx-augment.js';
|
|
4
|
+
/**
|
|
5
|
+
* Marker pin on a `<Map>`.
|
|
6
|
+
*
|
|
7
|
+
* Coordinates are required; `title` / `description` show in the callout
|
|
8
|
+
* when the user taps the marker. `id` is surfaced as
|
|
9
|
+
* `event.detail.id` in the parent map's `onMarkerPress` handler — set
|
|
10
|
+
* one if multiple markers share the same coordinate.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <Map>
|
|
15
|
+
* <MapMarker
|
|
16
|
+
* coordinate={{ latitude: 59.3293, longitude: 18.0686 }}
|
|
17
|
+
* title="Stockholm"
|
|
18
|
+
* description="Sweden's capital"
|
|
19
|
+
* id="sthlm"
|
|
20
|
+
* />
|
|
21
|
+
* </Map>
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* Marker pins are intentionally minimal in v1 — no custom icons, no
|
|
26
|
+
* draggable pins, no callout views. Those land in v2.
|
|
27
|
+
*/
|
|
28
|
+
export const MapMarker = component(({ props }) => {
|
|
29
|
+
return () => (_jsx("sigx-map-marker", { coordinate: JSON.stringify(props.coordinate), title: props.title, description: props.description, "marker-id": props.id }));
|
|
30
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import './jsx-augment.js';
|
|
2
|
+
export { Map } from './Map.js';
|
|
3
|
+
export type { MapProps } from './Map.js';
|
|
4
|
+
export { MapMarker } from './MapMarker.js';
|
|
5
|
+
export type { MapMarkerProps } from './MapMarker.js';
|
|
6
|
+
export type { MapRegion, MapCoordinate, MapType, MapRegionChangeEvent, MapRegionChangeEventDetail, MapPressEvent, MapPressEventDetail, MapMarkerPressEvent, MapMarkerPressEventDetail, SigxMapAttributes, SigxMapMarkerAttributes, } from './jsx-augment.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSX intrinsic type augmentation for `<sigx-map>` and `<sigx-map-marker>`.
|
|
3
|
+
*
|
|
4
|
+
* Importing this module registers both tags on `JSX.IntrinsicElements` with
|
|
5
|
+
* the prop + event surface implemented by `SigxMapUI` / `SigxMapMarkerUI`
|
|
6
|
+
* (iOS) and the same-named Kotlin classes (Android). Pulled in automatically
|
|
7
|
+
* by `@sigx/lynx-maps`'s entry point so consumers do not need to import it
|
|
8
|
+
* directly.
|
|
9
|
+
*
|
|
10
|
+
* Element availability requires `sigx prebuild` to have run after adding
|
|
11
|
+
* this package as a dependency — the autolinker emits the `LynxConfig`
|
|
12
|
+
* registration (iOS) and `Behavior` attachment (Android) that bind the tags
|
|
13
|
+
* to the native UI classes.
|
|
14
|
+
*/
|
|
15
|
+
import type { LynxCommonAttributes, LynxEventHandler } from '@sigx/lynx-runtime';
|
|
16
|
+
/**
|
|
17
|
+
* A rectangular region of the map. `latitudeDelta` / `longitudeDelta` define
|
|
18
|
+
* the span (in degrees) visible above/below and left/right of `latitude` /
|
|
19
|
+
* `longitude`. Matches the react-native-maps `Region` shape so docs and
|
|
20
|
+
* coordinate utilities translate cleanly.
|
|
21
|
+
*/
|
|
22
|
+
export interface MapRegion {
|
|
23
|
+
latitude: number;
|
|
24
|
+
longitude: number;
|
|
25
|
+
latitudeDelta: number;
|
|
26
|
+
longitudeDelta: number;
|
|
27
|
+
}
|
|
28
|
+
export interface MapCoordinate {
|
|
29
|
+
latitude: number;
|
|
30
|
+
longitude: number;
|
|
31
|
+
}
|
|
32
|
+
export type MapType = 'standard' | 'satellite' | 'hybrid';
|
|
33
|
+
export interface MapRegionChangeEventDetail {
|
|
34
|
+
region: MapRegion;
|
|
35
|
+
[k: string]: unknown;
|
|
36
|
+
}
|
|
37
|
+
export interface MapRegionChangeEvent {
|
|
38
|
+
type: 'regionchange';
|
|
39
|
+
detail: MapRegionChangeEventDetail;
|
|
40
|
+
}
|
|
41
|
+
export interface MapPressEventDetail {
|
|
42
|
+
coordinate: MapCoordinate;
|
|
43
|
+
[k: string]: unknown;
|
|
44
|
+
}
|
|
45
|
+
export interface MapPressEvent {
|
|
46
|
+
type: 'press';
|
|
47
|
+
detail: MapPressEventDetail;
|
|
48
|
+
}
|
|
49
|
+
export interface MapMarkerPressEventDetail {
|
|
50
|
+
/** Marker's `marker-id` prop, or empty string if none was set. */
|
|
51
|
+
id: string;
|
|
52
|
+
coordinate: MapCoordinate;
|
|
53
|
+
[k: string]: unknown;
|
|
54
|
+
}
|
|
55
|
+
export interface MapMarkerPressEvent {
|
|
56
|
+
type: 'markerpress';
|
|
57
|
+
detail: MapMarkerPressEventDetail;
|
|
58
|
+
}
|
|
59
|
+
export interface SigxMapAttributes extends LynxCommonAttributes {
|
|
60
|
+
/**
|
|
61
|
+
* Visible region. The map snaps to this on every prop change, so
|
|
62
|
+
* authors who only want to set an *initial* region should pass it once
|
|
63
|
+
* and avoid re-passing it on every render (use local state).
|
|
64
|
+
*
|
|
65
|
+
* Serialised as a JSON string on the wire — the native side parses it
|
|
66
|
+
* back into a region struct. Lynx props don't support object types
|
|
67
|
+
* directly today, so JSON-on-the-wire is the standard sigx-lynx idiom.
|
|
68
|
+
*/
|
|
69
|
+
region?: string;
|
|
70
|
+
/** Show the user's current location dot. Requires location permission. */
|
|
71
|
+
'shows-user-location'?: boolean;
|
|
72
|
+
/** Base map style. */
|
|
73
|
+
'map-type'?: MapType;
|
|
74
|
+
/** Visible region changed (either programmatic or user pan/zoom). */
|
|
75
|
+
bindregionchange?: LynxEventHandler<MapRegionChangeEvent>;
|
|
76
|
+
/** User tapped the map (not on a marker). */
|
|
77
|
+
bindpress?: LynxEventHandler<MapPressEvent>;
|
|
78
|
+
/** User tapped a marker. */
|
|
79
|
+
bindmarkerpress?: LynxEventHandler<MapMarkerPressEvent>;
|
|
80
|
+
children?: unknown;
|
|
81
|
+
}
|
|
82
|
+
export interface SigxMapMarkerAttributes extends LynxCommonAttributes {
|
|
83
|
+
/**
|
|
84
|
+
* Marker location. JSON-stringified `{ "latitude": …, "longitude": … }`
|
|
85
|
+
* for the same reason as `region` on `<sigx-map>` — Lynx props don't
|
|
86
|
+
* support nested objects directly. Required: a marker without a
|
|
87
|
+
* coordinate has nowhere to render.
|
|
88
|
+
*/
|
|
89
|
+
coordinate: string;
|
|
90
|
+
/** Callout title shown on tap. */
|
|
91
|
+
title?: string;
|
|
92
|
+
/** Callout subtitle shown below the title. */
|
|
93
|
+
description?: string;
|
|
94
|
+
/**
|
|
95
|
+
* Stable identifier surfaced as `event.detail.id` in `bindmarkerpress`.
|
|
96
|
+
* Useful when many markers share the same coordinate (e.g. clustered
|
|
97
|
+
* entries) and the press handler needs to disambiguate.
|
|
98
|
+
*/
|
|
99
|
+
'marker-id'?: string;
|
|
100
|
+
}
|
|
101
|
+
declare global {
|
|
102
|
+
namespace JSX {
|
|
103
|
+
interface IntrinsicElements {
|
|
104
|
+
'sigx-map': SigxMapAttributes;
|
|
105
|
+
'sigx-map-marker': SigxMapMarkerAttributes;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import MapKit
|
|
4
|
+
import Lynx
|
|
5
|
+
|
|
6
|
+
/// Native UI for the `<sigx-map-marker>` JSX element.
|
|
7
|
+
///
|
|
8
|
+
/// A marker doesn't render its own real view — Google Maps and MapKit both
|
|
9
|
+
/// model pins as data attached to the map. We still need a `LynxUI<UIView>`
|
|
10
|
+
/// because Lynx's UI tree expects every child to be a UI. The native view is
|
|
11
|
+
/// a zero-size hidden `UIView` that never gets laid out: the parent
|
|
12
|
+
/// `SigxMapUI` intercepts `insertChild` for markers and skips calling super,
|
|
13
|
+
/// so the dummy view is created but never attached to the map view hierarchy.
|
|
14
|
+
///
|
|
15
|
+
/// Prop surface (v1):
|
|
16
|
+
/// - `coordinate` → JSON-stringified `{ latitude, longitude }`
|
|
17
|
+
/// - `title` → callout title
|
|
18
|
+
/// - `description` → callout subtitle
|
|
19
|
+
/// - `marker-id` → forwarded as `event.detail.id` on `bindmarkerpress`
|
|
20
|
+
// Class is NOT marked `@objc` — Swift forbids that on generic subclasses
|
|
21
|
+
// of an ObjC lightweight-generic type like `LynxUI<__covariant V>`. Member-
|
|
22
|
+
// level `@objc` / `@objc(name)` annotations still bridge because `LynxUI`
|
|
23
|
+
// itself is `@objc`, so `__lynx_prop_config__*` discovery still works.
|
|
24
|
+
public class SigxMapMarkerUI: LynxUI<UIView> {
|
|
25
|
+
internal weak var owningMap: SigxMapUI?
|
|
26
|
+
|
|
27
|
+
internal var currentCoordinate = CLLocationCoordinate2D(latitude: 0, longitude: 0)
|
|
28
|
+
internal var currentTitle: String?
|
|
29
|
+
internal var currentSubtitle: String?
|
|
30
|
+
internal var markerId: String = ""
|
|
31
|
+
|
|
32
|
+
public override func createView() -> UIView? {
|
|
33
|
+
let v = UIView(frame: .zero)
|
|
34
|
+
v.isHidden = true
|
|
35
|
+
return v
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Explicit prop-setter registration. See SigxWebViewUI for rationale.
|
|
39
|
+
@objc public class func propSetterLookUp() -> NSArray {
|
|
40
|
+
return [
|
|
41
|
+
["coordinate", "setCoordinate:requestReset:"],
|
|
42
|
+
["title", "setTitle:requestReset:"],
|
|
43
|
+
["description", "setDescription:requestReset:"],
|
|
44
|
+
["marker-id", "setMarkerId:requestReset:"],
|
|
45
|
+
] as NSArray
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
internal func makeAnnotation() -> MKPointAnnotation {
|
|
49
|
+
let a = MKPointAnnotation()
|
|
50
|
+
a.coordinate = currentCoordinate
|
|
51
|
+
a.title = currentTitle
|
|
52
|
+
a.subtitle = currentSubtitle
|
|
53
|
+
return a
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// MARK: - Prop setters
|
|
57
|
+
|
|
58
|
+
@objc public func setCoordinate(_ value: NSString?, requestReset: Bool) {
|
|
59
|
+
guard let raw = value as String?, !raw.isEmpty,
|
|
60
|
+
let data = raw.data(using: .utf8),
|
|
61
|
+
let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
62
|
+
let lat = (obj["latitude"] as? NSNumber)?.doubleValue,
|
|
63
|
+
let lon = (obj["longitude"] as? NSNumber)?.doubleValue
|
|
64
|
+
else { return }
|
|
65
|
+
currentCoordinate = CLLocationCoordinate2D(latitude: lat, longitude: lon)
|
|
66
|
+
owningMap?.markerDidUpdate(self)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@objc(__lynx_prop_config__coordinate)
|
|
70
|
+
public class func __lynxPropConfigCoordinate() -> [String] {
|
|
71
|
+
return ["coordinate", "setCoordinate", "NSString *"]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@objc public func setTitle(_ value: NSString?, requestReset: Bool) {
|
|
75
|
+
currentTitle = value as String?
|
|
76
|
+
owningMap?.markerDidUpdate(self)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@objc(__lynx_prop_config__title)
|
|
80
|
+
public class func __lynxPropConfigTitle() -> [String] {
|
|
81
|
+
return ["title", "setTitle", "NSString *"]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@objc public func setDescription(_ value: NSString?, requestReset: Bool) {
|
|
85
|
+
currentSubtitle = value as String?
|
|
86
|
+
owningMap?.markerDidUpdate(self)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@objc(__lynx_prop_config__description)
|
|
90
|
+
public class func __lynxPropConfigDescription() -> [String] {
|
|
91
|
+
return ["description", "setDescription", "NSString *"]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@objc public func setMarkerId(_ value: NSString?, requestReset: Bool) {
|
|
95
|
+
markerId = (value as String?) ?? ""
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@objc(__lynx_prop_config__marker_id)
|
|
99
|
+
public class func __lynxPropConfigMarkerId() -> [String] {
|
|
100
|
+
return ["marker-id", "setMarkerId", "NSString *"]
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import UIKit
|
|
3
|
+
import MapKit
|
|
4
|
+
import Lynx
|
|
5
|
+
|
|
6
|
+
/// Native UI for the `<sigx-map>` JSX element.
|
|
7
|
+
///
|
|
8
|
+
/// Registered via the autolinker — `signalx-module.json`'s `ios.uiComponents`
|
|
9
|
+
/// produces a `config.registerUI(SigxMapUI.self, withName: "sigx-map")` call
|
|
10
|
+
/// in the generated `GeneratedComponentRegistry.swift`.
|
|
11
|
+
///
|
|
12
|
+
/// Prop / event surface (v1):
|
|
13
|
+
/// - `region` → JSON-stringified `MapRegion`
|
|
14
|
+
/// - `shows-user-location` → toggle the user-location dot
|
|
15
|
+
/// - `map-type` → `"standard" | "satellite" | "hybrid"`
|
|
16
|
+
/// - `bindregionchange` → region changed (programmatic or user gesture)
|
|
17
|
+
/// - `bindpress` → user tapped the map (not a marker)
|
|
18
|
+
/// - `bindmarkerpress` → user tapped a marker
|
|
19
|
+
///
|
|
20
|
+
/// Imperative methods (animateToRegion / fitToCoordinates) are tracked as a
|
|
21
|
+
/// v2 follow-up — they need the Lynx UIMethodInvoker surface which isn't
|
|
22
|
+
/// wired through sigx-lynx yet (same blocker as WebView.goBack / reload).
|
|
23
|
+
// Class is NOT marked `@objc` — Swift forbids that on generic subclasses
|
|
24
|
+
// of an ObjC lightweight-generic type like `LynxUI<__covariant V>`. Member-
|
|
25
|
+
// level `@objc` / `@objc(name)` annotations still bridge because `LynxUI`
|
|
26
|
+
// itself is `@objc`, so `__lynx_prop_config__*` and
|
|
27
|
+
// `__lynx_ui_method_config__*` discovery still works.
|
|
28
|
+
public class SigxMapUI: LynxUI<MKMapView> {
|
|
29
|
+
|
|
30
|
+
private lazy var mapDelegate = SigxMapDelegate(owner: self)
|
|
31
|
+
private lazy var tapGesture: UITapGestureRecognizer = {
|
|
32
|
+
let gr = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
|
|
33
|
+
gr.cancelsTouchesInView = false
|
|
34
|
+
return gr
|
|
35
|
+
}()
|
|
36
|
+
/// Tracks the `MKPointAnnotation` per marker child so we can rebuild
|
|
37
|
+
/// callout titles / coordinates in place rather than re-adding pins on
|
|
38
|
+
/// every prop change.
|
|
39
|
+
private var markerAnnotations: [ObjectIdentifier: MKPointAnnotation] = [:]
|
|
40
|
+
/// Reverse index: annotation identity → marker UI, for translating
|
|
41
|
+
/// `didSelect` callbacks back into `bindmarkerpress` events.
|
|
42
|
+
private var markersByAnnotation: [ObjectIdentifier: SigxMapMarkerUI] = [:]
|
|
43
|
+
|
|
44
|
+
// MARK: - LynxUI overrides
|
|
45
|
+
|
|
46
|
+
public override func createView() -> MKMapView? {
|
|
47
|
+
let map = MKMapView(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
|
|
48
|
+
map.translatesAutoresizingMaskIntoConstraints = true
|
|
49
|
+
map.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
50
|
+
map.delegate = mapDelegate
|
|
51
|
+
map.addGestureRecognizer(tapGesture)
|
|
52
|
+
return map
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Explicit prop-setter registration. See SigxWebViewUI for rationale.
|
|
56
|
+
@objc public class func propSetterLookUp() -> NSArray {
|
|
57
|
+
return [
|
|
58
|
+
["region", "setRegion:requestReset:"],
|
|
59
|
+
["shows-user-location", "setShowsUserLocation:requestReset:"],
|
|
60
|
+
["map-type", "setMapType:requestReset:"],
|
|
61
|
+
] as NSArray
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Lynx's child-management hooks live on `LynxComponent<D>` where for
|
|
65
|
+
// `LynxUI` the generic `D` is `LynxUI*`. ObjC lightweight generics
|
|
66
|
+
// import into Swift as the upper bound, so the override signature uses
|
|
67
|
+
// `LynxUI<UIView>` (the declared bound `__covariant V : UIView*`).
|
|
68
|
+
// Both insert and remove take `atIndex` per the LynxComponent.h ABI.
|
|
69
|
+
// Swift imports the ObjC `insertChild:atIndex:` selector as
|
|
70
|
+
// `insertChild(_:at:)` and the same for removeChild. Override matches.
|
|
71
|
+
public override func insertChild(_ child: LynxUI<UIView>!, at index: Int) {
|
|
72
|
+
// Map markers participate in the Lynx UI tree as children of the map,
|
|
73
|
+
// but we don't want their UIViews added to MKMapView — markers
|
|
74
|
+
// render as `MKAnnotation`s on the native side. Intercept marker
|
|
75
|
+
// children, register the annotation, and skip super.
|
|
76
|
+
if let marker = child as? SigxMapMarkerUI {
|
|
77
|
+
attachMarker(marker)
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
super.insertChild(child, at: index)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public override func removeChild(_ child: LynxUI<UIView>!, at index: Int) {
|
|
84
|
+
if let marker = child as? SigxMapMarkerUI {
|
|
85
|
+
detachMarker(marker)
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
super.removeChild(child, at: index)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// MARK: - Marker plumbing
|
|
92
|
+
|
|
93
|
+
func attachMarker(_ marker: SigxMapMarkerUI) {
|
|
94
|
+
marker.owningMap = self
|
|
95
|
+
let annotation = marker.makeAnnotation()
|
|
96
|
+
markerAnnotations[ObjectIdentifier(marker)] = annotation
|
|
97
|
+
markersByAnnotation[ObjectIdentifier(annotation)] = marker
|
|
98
|
+
view().addAnnotation(annotation)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
func detachMarker(_ marker: SigxMapMarkerUI) {
|
|
102
|
+
if let annotation = markerAnnotations.removeValue(forKey: ObjectIdentifier(marker)) {
|
|
103
|
+
markersByAnnotation.removeValue(forKey: ObjectIdentifier(annotation))
|
|
104
|
+
view().removeAnnotation(annotation)
|
|
105
|
+
}
|
|
106
|
+
marker.owningMap = nil
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Called by `SigxMapMarkerUI` when its coordinate / title / description
|
|
110
|
+
/// props change so the on-screen annotation reflects the new values
|
|
111
|
+
/// without re-adding the pin (which would clear selection state).
|
|
112
|
+
func markerDidUpdate(_ marker: SigxMapMarkerUI) {
|
|
113
|
+
guard let annotation = markerAnnotations[ObjectIdentifier(marker)] else { return }
|
|
114
|
+
annotation.coordinate = marker.currentCoordinate
|
|
115
|
+
annotation.title = marker.currentTitle
|
|
116
|
+
annotation.subtitle = marker.currentSubtitle
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// MARK: - Prop setters
|
|
120
|
+
|
|
121
|
+
@objc public func setRegion(_ value: NSString?, requestReset: Bool) {
|
|
122
|
+
guard let raw = value as String?, !raw.isEmpty else { return }
|
|
123
|
+
guard
|
|
124
|
+
let data = raw.data(using: .utf8),
|
|
125
|
+
let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
126
|
+
let lat = (obj["latitude"] as? NSNumber)?.doubleValue,
|
|
127
|
+
let lon = (obj["longitude"] as? NSNumber)?.doubleValue,
|
|
128
|
+
let latD = (obj["latitudeDelta"] as? NSNumber)?.doubleValue,
|
|
129
|
+
let lonD = (obj["longitudeDelta"] as? NSNumber)?.doubleValue
|
|
130
|
+
else {
|
|
131
|
+
NSLog("[SigxMap] Ignoring malformed region prop: \(raw.prefix(64))")
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
let region = MKCoordinateRegion(
|
|
135
|
+
center: CLLocationCoordinate2D(latitude: lat, longitude: lon),
|
|
136
|
+
span: MKCoordinateSpan(latitudeDelta: latD, longitudeDelta: lonD)
|
|
137
|
+
)
|
|
138
|
+
view().setRegion(region, animated: false)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@objc(__lynx_prop_config__region)
|
|
142
|
+
public class func __lynxPropConfigRegion() -> [String] {
|
|
143
|
+
return ["region", "setRegion", "NSString *"]
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@objc public func setShowsUserLocation(_ value: Bool, requestReset: Bool) {
|
|
147
|
+
view().showsUserLocation = value
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@objc(__lynx_prop_config__shows_user_location)
|
|
151
|
+
public class func __lynxPropConfigShowsUserLocation() -> [String] {
|
|
152
|
+
return ["shows-user-location", "setShowsUserLocation", "BOOL"]
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@objc public func setMapType(_ value: NSString?, requestReset: Bool) {
|
|
156
|
+
switch (value as String?) ?? "" {
|
|
157
|
+
case "satellite": view().mapType = .satellite
|
|
158
|
+
case "hybrid": view().mapType = .hybrid
|
|
159
|
+
default: view().mapType = .standard
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@objc(__lynx_prop_config__map_type)
|
|
164
|
+
public class func __lynxPropConfigMapType() -> [String] {
|
|
165
|
+
return ["map-type", "setMapType", "NSString *"]
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// MARK: - Gesture handling
|
|
169
|
+
|
|
170
|
+
@objc private func handleTap(_ gr: UITapGestureRecognizer) {
|
|
171
|
+
let map = view()
|
|
172
|
+
let point = gr.location(in: map)
|
|
173
|
+
// Skip taps on the existing annotations — those are surfaced via
|
|
174
|
+
// didSelect in the delegate as `bindmarkerpress`.
|
|
175
|
+
if map.hitTest(point, with: nil) is MKAnnotationView { return }
|
|
176
|
+
let coord = map.convert(point, toCoordinateFrom: map)
|
|
177
|
+
fireEvent("press", params: [
|
|
178
|
+
"coordinate": [
|
|
179
|
+
"latitude": coord.latitude,
|
|
180
|
+
"longitude": coord.longitude,
|
|
181
|
+
],
|
|
182
|
+
])
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// MARK: - Event firing
|
|
186
|
+
|
|
187
|
+
internal func fireEvent(_ name: String, params: [String: Any]) {
|
|
188
|
+
let event = LynxCustomEvent(name: name, targetSign: sign, params: params)
|
|
189
|
+
// Swift renames the ObjC `sendCustomEvent:` selector to `send(_:)`
|
|
190
|
+
// when LynxCustomEvent is the parameter type — same underlying method.
|
|
191
|
+
context?.eventEmitter?.send(event)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
internal func emitRegionChange(_ region: MKCoordinateRegion) {
|
|
195
|
+
fireEvent("regionchange", params: [
|
|
196
|
+
"region": [
|
|
197
|
+
"latitude": region.center.latitude,
|
|
198
|
+
"longitude": region.center.longitude,
|
|
199
|
+
"latitudeDelta": region.span.latitudeDelta,
|
|
200
|
+
"longitudeDelta": region.span.longitudeDelta,
|
|
201
|
+
],
|
|
202
|
+
])
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
internal func emitMarkerPress(_ marker: SigxMapMarkerUI) {
|
|
206
|
+
fireEvent("markerpress", params: [
|
|
207
|
+
"id": marker.markerId,
|
|
208
|
+
"coordinate": [
|
|
209
|
+
"latitude": marker.currentCoordinate.latitude,
|
|
210
|
+
"longitude": marker.currentCoordinate.longitude,
|
|
211
|
+
],
|
|
212
|
+
])
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
internal func marker(for annotation: MKAnnotation) -> SigxMapMarkerUI? {
|
|
216
|
+
guard let obj = annotation as? AnyObject else { return nil }
|
|
217
|
+
return markersByAnnotation[ObjectIdentifier(obj)]
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/// `MKMapViewDelegate` forwarder — keeps the public class slim and avoids
|
|
222
|
+
/// the `NSObject` Obj-C runtime drag on `SigxMapUI` itself.
|
|
223
|
+
final class SigxMapDelegate: NSObject, MKMapViewDelegate {
|
|
224
|
+
private weak var owner: SigxMapUI?
|
|
225
|
+
|
|
226
|
+
init(owner: SigxMapUI) { self.owner = owner }
|
|
227
|
+
|
|
228
|
+
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
|
|
229
|
+
owner?.emitRegionChange(mapView.region)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
|
|
233
|
+
guard let annotation = view.annotation, let marker = owner?.marker(for: annotation) else { return }
|
|
234
|
+
owner?.emitMarkerPress(marker)
|
|
235
|
+
}
|
|
236
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sigx/lynx-maps",
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"description": "Native map view for sigx-lynx (MKMapView on iOS, Google Maps on Android).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./signalx-module.json": "./signalx-module.json",
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"ios",
|
|
20
|
+
"android",
|
|
21
|
+
"signalx-module.json",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@sigx/lynx": "^0.4.1"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@typescript/native-preview": "7.0.0-dev.20260521.1",
|
|
30
|
+
"typescript": "^6.0.3",
|
|
31
|
+
"vitest": "^4.1.7",
|
|
32
|
+
"@sigx/lynx": "^0.4.1"
|
|
33
|
+
},
|
|
34
|
+
"author": "Andreas Ekdahl",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/signalxjs/lynx.git",
|
|
39
|
+
"directory": "packages/lynx-maps"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/signalxjs/lynx/tree/main/packages/lynx-maps",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/signalxjs/lynx/issues"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"keywords": [
|
|
49
|
+
"signalx",
|
|
50
|
+
"sigx",
|
|
51
|
+
"lynx",
|
|
52
|
+
"maps",
|
|
53
|
+
"mkmapview",
|
|
54
|
+
"google-maps",
|
|
55
|
+
"ios",
|
|
56
|
+
"android"
|
|
57
|
+
],
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "node ../../scripts/clean.mjs dist && tsgo",
|
|
60
|
+
"dev": "tsgo --watch",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"clean": "node ../../scripts/clean.mjs dist .turbo"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Maps",
|
|
3
|
+
"package": "@sigx/lynx-maps",
|
|
4
|
+
"description": "Native map view (MKMapView / Google Maps)",
|
|
5
|
+
"platforms": ["android", "ios"],
|
|
6
|
+
"ios": {
|
|
7
|
+
"sourceDir": "ios",
|
|
8
|
+
"uiComponents": [
|
|
9
|
+
{ "name": "sigx-map", "uiClass": "SigxMapUI" },
|
|
10
|
+
{ "name": "sigx-map-marker", "uiClass": "SigxMapMarkerUI" }
|
|
11
|
+
],
|
|
12
|
+
"usageDescriptions": {
|
|
13
|
+
"NSLocationWhenInUseUsageDescription": "Show your location on the map."
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"android": {
|
|
17
|
+
"sourceDir": "android",
|
|
18
|
+
"behaviors": [
|
|
19
|
+
{ "name": "sigx-map", "behaviorClass": "com.sigx.maps.SigxMapBehavior" },
|
|
20
|
+
{ "name": "sigx-map-marker", "behaviorClass": "com.sigx.maps.SigxMapMarkerBehavior" }
|
|
21
|
+
],
|
|
22
|
+
"permissions": [
|
|
23
|
+
"android.permission.ACCESS_FINE_LOCATION",
|
|
24
|
+
"android.permission.ACCESS_COARSE_LOCATION"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": [
|
|
27
|
+
"com.google.android.gms:play-services-maps:19.0.0"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|