@jacques_gordon/expo-mapbox-navigation 2.1.5 → 2.1.6

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.
@@ -15,7 +15,8 @@ class ExpoMapboxNavigationModule : Module() {
15
15
  "onNavigationFinished",
16
16
  "onNavigationCancelled",
17
17
  "onRoutesFailed",
18
- "onArrival"
18
+ "onArrival",
19
+ "onManeuverBannerPressed"
19
20
  )
20
21
 
21
22
  Prop("coordinates") { view: ExpoMapboxNavigationView, coordinates: List<Map<String, Double>> ->
@@ -93,6 +93,11 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
93
93
  private val onNavigationCancelled by EventDispatcher()
94
94
  private val onRoutesFailed by EventDispatcher()
95
95
  private val onArrival by EventDispatcher()
96
+ // Feature: emits the full list of turn-by-turn steps when the instruction
97
+ // banner is tapped, so the RN layer can render a steps list (bottom
98
+ // sheet/modal). Payload: { steps: [{ instruction, distanceMeters,
99
+ // maneuverType, maneuverModifier, laneInstructions }] }
100
+ private val onManeuverBannerPressed by EventDispatcher()
96
101
 
97
102
  // ── Views ─────────────────────────────────────────────────────────────────
98
103
  private val mapView: MapView = MapView(context)
@@ -135,11 +140,15 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
135
140
  private val dp = context.resources.displayMetrics.density
136
141
 
137
142
  // ── Viewport padding ──────────────────────────────────────────────────────
143
+ // ── Viewport padding — EXACT official Mapbox values from the camera guide ──
144
+ // Previous bottom=300dp pushed the focal point too far up, causing
145
+ // the puck to visibly jump/drift between location updates.
146
+ // Official guide value: top=180, left=40, bottom=150, right=40
138
147
  private val followingPadding by lazy {
139
- EdgeInsets(180.0 * dp, 40.0 * dp, 300.0 * dp, 40.0 * dp)
148
+ EdgeInsets(180.0 * dp, 40.0 * dp, 150.0 * dp, 40.0 * dp)
140
149
  }
141
150
  private val overviewPadding by lazy {
142
- EdgeInsets(120.0 * dp, 40.0 * dp, 120.0 * dp, 40.0 * dp)
151
+ EdgeInsets(140.0 * dp, 40.0 * dp, 120.0 * dp, 40.0 * dp)
143
152
  }
144
153
 
145
154
  // ── Props ─────────────────────────────────────────────────────────────────
@@ -167,43 +176,47 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
167
176
  // Draw icons programmatically (matching screenshot: speaker, route, arrow)
168
177
  // ─────────────────────────────────────────────────────────────────────────
169
178
 
179
+ // ── Modern Google Maps / Waze style icons ──────────────────────────────────
180
+
170
181
  private fun drawSpeakerIcon(muted: Boolean): Bitmap {
171
182
  val size = (44 * dp).toInt()
172
183
  val bmp = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
173
184
  val c = Canvas(bmp)
185
+ val s = size.toFloat()
186
+ val color = if (muted) Color.parseColor("#5F6368") else Color.parseColor("#1A73E8")
174
187
  val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
175
- color = Color.BLACK
188
+ this.color = color
176
189
  style = Paint.Style.FILL
177
190
  }
178
- val s = size.toFloat()
179
- // Speaker body (trapezoid)
191
+ // Speaker cone (rounded, modern Material style)
180
192
  val body = Path().apply {
181
- moveTo(s * 0.18f, s * 0.38f)
182
- lineTo(s * 0.38f, s * 0.38f)
183
- lineTo(s * 0.58f, s * 0.22f)
184
- lineTo(s * 0.58f, s * 0.78f)
185
- lineTo(s * 0.38f, s * 0.62f)
186
- lineTo(s * 0.18f, s * 0.62f)
193
+ moveTo(s * 0.16f, s * 0.40f)
194
+ lineTo(s * 0.34f, s * 0.40f)
195
+ lineTo(s * 0.56f, s * 0.20f)
196
+ quadTo(s * 0.60f, s * 0.17f, s * 0.60f, s * 0.22f)
197
+ lineTo(s * 0.60f, s * 0.78f)
198
+ quadTo(s * 0.60f, s * 0.83f, s * 0.56f, s * 0.80f)
199
+ lineTo(s * 0.34f, s * 0.60f)
200
+ lineTo(s * 0.16f, s * 0.60f)
201
+ quadTo(s * 0.12f, s * 0.60f, s * 0.12f, s * 0.56f)
202
+ lineTo(s * 0.12f, s * 0.44f)
203
+ quadTo(s * 0.12f, s * 0.40f, s * 0.16f, s * 0.40f)
187
204
  close()
188
205
  }
189
206
  c.drawPath(body, paint)
190
207
  if (!muted) {
191
- // Sound waves arcs
192
208
  paint.style = Paint.Style.STROKE
193
- paint.strokeWidth = s * 0.065f
209
+ paint.strokeWidth = s * 0.06f
194
210
  paint.strokeCap = Paint.Cap.ROUND
195
- // Inner arc
196
- c.drawArc(RectF(s*0.60f, s*0.33f, s*0.80f, s*0.67f), -60f, 120f, false, paint)
197
- // Outer arc
198
- c.drawArc(RectF(s*0.64f, s*0.24f, s*0.92f, s*0.76f), -55f, 110f, false, paint)
211
+ c.drawArc(RectF(s*0.62f, s*0.34f, s*0.80f, s*0.66f), -45f, 90f, false, paint)
212
+ c.drawArc(RectF(s*0.66f, s*0.22f, s*0.92f, s*0.78f), -40f, 80f, false, paint)
199
213
  } else {
200
- // X mark for muted
201
214
  paint.style = Paint.Style.STROKE
202
- paint.strokeWidth = s * 0.07f
215
+ paint.strokeWidth = s * 0.065f
203
216
  paint.strokeCap = Paint.Cap.ROUND
204
- paint.color = Color.parseColor("#FF4444")
205
- c.drawLine(s*0.65f, s*0.32f, s*0.88f, s*0.68f, paint)
206
- c.drawLine(s*0.88f, s*0.32f, s*0.65f, s*0.68f, paint)
217
+ paint.color = Color.parseColor("#EA4335")
218
+ c.drawLine(s*0.66f, s*0.34f, s*0.88f, s*0.66f, paint)
219
+ c.drawLine(s*0.88f, s*0.34f, s*0.66f, s*0.66f, paint)
207
220
  }
208
221
  return bmp
209
222
  }
@@ -212,34 +225,29 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
212
225
  val size = (44 * dp).toInt()
213
226
  val bmp = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
214
227
  val c = Canvas(bmp)
228
+ val s = size.toFloat()
215
229
  val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
216
- color = Color.BLACK
217
- style = Paint.Style.STROKE
218
- strokeWidth = size * 0.08f
219
- strokeCap = Paint.Cap.ROUND
220
- strokeJoin = Paint.Join.ROUND
230
+ color = Color.parseColor("#1A73E8")
221
231
  }
222
- val s = size.toFloat()
223
- // Route line with S-curve (like the image: start dot → curve → end pin)
224
- // Start dot
225
- paint.style = Paint.Style.FILL
226
- c.drawCircle(s * 0.28f, s * 0.75f, s * 0.07f, paint)
227
- // Route path
232
+ // Map/overview icon: stacked layers (like Google Maps "map view" icon)
228
233
  paint.style = Paint.Style.STROKE
229
- val routePath = Path().apply {
230
- moveTo(s * 0.28f, s * 0.75f)
231
- cubicTo(
232
- s * 0.28f, s * 0.45f,
233
- s * 0.72f, s * 0.55f,
234
- s * 0.72f, s * 0.25f
235
- )
234
+ paint.strokeWidth = s * 0.07f
235
+ paint.strokeJoin = Paint.Join.ROUND
236
+ paint.strokeCap = Paint.Cap.ROUND
237
+ // Outer diamond/map frame
238
+ val frame = Path().apply {
239
+ moveTo(s * 0.50f, s * 0.14f)
240
+ lineTo(s * 0.84f, s * 0.34f)
241
+ lineTo(s * 0.84f, s * 0.66f)
242
+ lineTo(s * 0.50f, s * 0.86f)
243
+ lineTo(s * 0.16f, s * 0.66f)
244
+ lineTo(s * 0.16f, s * 0.34f)
245
+ close()
236
246
  }
237
- c.drawPath(routePath, paint)
238
- // End pin (circle with dot)
239
- paint.style = Paint.Style.STROKE
240
- c.drawCircle(s * 0.72f, s * 0.22f, s * 0.10f, paint)
241
- paint.style = Paint.Style.FILL
242
- c.drawCircle(s * 0.72f, s * 0.22f, s * 0.04f, paint)
247
+ c.drawPath(frame, paint)
248
+ // Two horizontal fold lines (classic map icon)
249
+ c.drawLine(s*0.36f, s*0.22f, s*0.36f, s*0.78f, paint)
250
+ c.drawLine(s*0.64f, s*0.22f, s*0.64f, s*0.78f, paint)
243
251
  return bmp
244
252
  }
245
253
 
@@ -247,17 +255,17 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
247
255
  val size = (44 * dp).toInt()
248
256
  val bmp = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
249
257
  val c = Canvas(bmp)
258
+ val s = size.toFloat()
250
259
  val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
251
- color = Color.BLACK
260
+ color = Color.parseColor("#1A73E8")
252
261
  style = Paint.Style.FILL
253
262
  }
254
- val s = size.toFloat()
255
- // Navigation arrow pointing up (filled chevron/arrow like Waze)
263
+ // Navigation/recenter compass arrow (Google Maps "my location, follow" style)
256
264
  val arrow = Path().apply {
257
- moveTo(s * 0.50f, s * 0.14f) // tip
258
- lineTo(s * 0.78f, s * 0.76f) // bottom-right
259
- lineTo(s * 0.50f, s * 0.60f) // bottom-center indent
260
- lineTo(s * 0.22f, s * 0.76f) // bottom-left
265
+ moveTo(s * 0.50f, s * 0.12f) // tip
266
+ lineTo(s * 0.80f, s * 0.82f) // bottom-right
267
+ lineTo(s * 0.50f, s * 0.64f) // notch center
268
+ lineTo(s * 0.20f, s * 0.82f) // bottom-left
261
269
  close()
262
270
  }
263
271
  c.drawPath(arrow, paint)
@@ -295,12 +303,21 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
295
303
  ))
296
304
 
297
305
  // ── ManeuverView — top ─────────────────────────────────────────────────
298
- val mv = MapboxManeuverView(context)
306
+ // FIX: apply custom style with autoSizeTextType so long street names
307
+ // (e.g. "Wulmersumsesteenweg") shrink to fit instead of being clipped.
308
+ val mv = MapboxManeuverView(
309
+ android.view.ContextThemeWrapper(context, R.style.MapboxCustomManeuverViewStyle)
310
+ )
299
311
  root.addView(mv as View, FrameLayout.LayoutParams(
300
312
  FrameLayout.LayoutParams.MATCH_PARENT,
301
313
  FrameLayout.LayoutParams.WRAP_CONTENT
302
314
  ).also { it.gravity = Gravity.TOP })
303
315
  mv.visibility = View.INVISIBLE
316
+ // Feature: tap the instruction banner to see the full list of upcoming
317
+ // turn-by-turn steps. We emit the data via event so the RN/JS layer can
318
+ // render a bottom sheet or modal using its own native UI components —
319
+ // consistent with how all other navigation events are surfaced.
320
+ mv.setOnClickListener { emitFullRouteSteps() }
304
321
  maneuverView = mv
305
322
 
306
323
  // ── Side buttons — RIGHT side, just below maneuver banner ─────────────
@@ -336,17 +353,21 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
336
353
  })
337
354
  btnOverviewView = overviewBtn
338
355
 
339
- // Button 3: Recenter / Navigation arrow (hidden until in overview mode)
356
+ // Button 3: Recenter / return to follow mode ALWAYS visible, below button 2
357
+ // Greyed out (lower opacity) when already following; full opacity in overview mode
340
358
  val recenterBtn = makeIconButton(drawNavigationArrowIcon()) { recenterCamera() }
341
- recenterBtn.visibility = View.GONE
359
+ recenterBtn.alpha = 0.4f // dimmed by default (already following)
342
360
  sideCol.addView(recenterBtn, LinearLayout.LayoutParams(btnSize, btnSize))
343
361
  btnRecenterView = recenterBtn
344
362
 
345
363
  // ── SpeedInfoView — bottom-left, above ETA bar ────────────────────────
364
+ // FIX: use WRAP_CONTENT instead of fixed 72x72dp — the native Mapbox
365
+ // component renders both the posted speed limit sign AND the current
366
+ // vehicle speed side-by-side, which needs flexible width
346
367
  val siv = MapboxSpeedInfoView(context)
347
368
  root.addView(siv as View, FrameLayout.LayoutParams(
348
- (72 * dp).toInt(),
349
- (72 * dp).toInt()
369
+ FrameLayout.LayoutParams.WRAP_CONTENT,
370
+ FrameLayout.LayoutParams.WRAP_CONTENT
350
371
  ).also {
351
372
  it.gravity = Gravity.BOTTOM or Gravity.START
352
373
  it.setMargins((12 * dp).toInt(), 0, 0, (88 * dp).toInt())
@@ -492,6 +513,11 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
492
513
  viewportDataSource.followingPadding = followingPadding
493
514
  viewportDataSource.overviewPadding = overviewPadding
494
515
 
516
+ // Waze-style tilted 3D following view — also helps stabilize the
517
+ // visual framing since pitch reduces perceived jitter from GPS noise
518
+ viewportDataSource.options.followingFrameOptions.defaultPitch = 45.0
519
+ viewportDataSource.options.followingFrameOptions.maxZoom = 17.0
520
+
495
521
  navigationCamera = NavigationCamera(
496
522
  mapView.mapboxMap,
497
523
  mapView.camera,
@@ -601,7 +627,15 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
601
627
  override fun onNewRawLocation(rawLocation: Location) {}
602
628
  override fun onNewLocationMatcherResult(result: LocationMatcherResult) {
603
629
  val loc = result.enhancedLocation
604
- navigationLocationProvider.changePosition(location = loc, keyPoints = result.keyPoints)
630
+ // ── FIX: Puck jitter / drift ──────────────────────────────────────
631
+ // Root cause (confirmed via Mapbox GitHub issue #4140):
632
+ // When keyPoints are passed to changePosition(), the puck animator
633
+ // splits the 1-second transition EVENLY across all keypoints in time,
634
+ // but the keypoints are NOT evenly spaced in distance. This causes the
635
+ // puck to speed up/slow down and visibly drift left/right off the
636
+ // route line, even on a perfectly straight road.
637
+ // Official Mapbox workaround: pass emptyList() instead of keyPoints.
638
+ navigationLocationProvider.changePosition(location = loc, keyPoints = emptyList())
605
639
  viewportDataSource.onLocationChanged(loc)
606
640
  viewportDataSource.evaluate()
607
641
 
@@ -610,6 +644,11 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
610
644
  safeCameraFollowing()
611
645
  }
612
646
 
647
+ // ── Speed limit (issue #4: was not displaying) ──────────────────
648
+ // updatePostedAndCurrentSpeed returns null when:
649
+ // 1. The route response has no `maxspeed` annotation for this segment
650
+ // 2. GPS speed is unavailable
651
+ // We log this so issues can be diagnosed via `adb logcat`
613
652
  val fmtOptions = DistanceFormatterOptions.Builder(context).build()
614
653
  val speedInfo = speedInfoApi.updatePostedAndCurrentSpeed(result, fmtOptions)
615
654
  if (speedInfo != null) {
@@ -617,6 +656,8 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
617
656
  speedInfoView?.render(speedInfo)
618
657
  } else {
619
658
  speedInfoView?.visibility = View.GONE
659
+ Log.d(TAG, "Speed info unavailable for this location/route segment " +
660
+ "(no maxspeed annotation or GPS speed not ready yet)")
620
661
  }
621
662
 
622
663
  checkAndSwitchDayNight()
@@ -643,7 +684,8 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
643
684
  recenterCamera()
644
685
  } else {
645
686
  isOverviewMode = true
646
- btnRecenterView?.visibility = View.VISIBLE
687
+ // Button 3 lights up (full opacity) to invite the user to tap it
688
+ btnRecenterView?.alpha = 1f
647
689
  try {
648
690
  navigationCamera.requestNavigationCameraToOverview()
649
691
  } catch (e: Exception) {
@@ -654,7 +696,8 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
654
696
 
655
697
  private fun recenterCamera() {
656
698
  isOverviewMode = false
657
- btnRecenterView?.visibility = View.GONE
699
+ // Button 3 dims again — already in following mode
700
+ btnRecenterView?.alpha = 0.4f
658
701
  safeCameraFollowing()
659
702
  }
660
703
 
@@ -713,7 +756,53 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
713
756
  }
714
757
  }
715
758
 
716
- private fun cancelNavigation() {
759
+ // ─────────────────────────────────────────────────────────────────────────
760
+ // Feature: full route steps list (triggered by tapping the instruction
761
+ // banner). Extracts every LegStep across all legs of the active route,
762
+ // including lane guidance data when present on that step's BannerInstructions.
763
+ // ─────────────────────────────────────────────────────────────────────────
764
+ private fun emitFullRouteSteps() {
765
+ val routes = mapboxNavigation?.getNavigationRoutes()
766
+ val activeRoute = routes?.firstOrNull() ?: run {
767
+ onManeuverBannerPressed(mapOf("steps" to emptyList<Map<String, Any>>()))
768
+ return
769
+ }
770
+
771
+ val stepsPayload = mutableListOf<Map<String, Any>>()
772
+
773
+ activeRoute.directionsRoute.legs()?.forEach { leg ->
774
+ leg.steps()?.forEach { step ->
775
+ val maneuver = step.maneuver()
776
+ val firstBanner = step.bannerInstructions()?.firstOrNull()
777
+
778
+ // Lane guidance: present on BannerInstructions.sub() when type == "lane"
779
+ val laneData = firstBanner?.sub()?.components()
780
+ ?.filter { it.type() == "lane" }
781
+ ?.map { component ->
782
+ mapOf(
783
+ "active" to (component.active() ?: false),
784
+ "directions" to (component.directions() ?: emptyList<String>())
785
+ )
786
+ } ?: emptyList()
787
+
788
+ stepsPayload.add(
789
+ mapOf(
790
+ "instruction" to (maneuver?.instruction() ?: ""),
791
+ "distanceMeters" to (step.distance() ?: 0.0),
792
+ "durationSeconds" to (step.duration() ?: 0.0),
793
+ "maneuverType" to (maneuver?.type() ?: ""),
794
+ "maneuverModifier" to (maneuver?.modifier() ?: ""),
795
+ "roadName" to (step.name() ?: ""),
796
+ "laneInstructions" to laneData
797
+ )
798
+ )
799
+ }
800
+ }
801
+
802
+ onManeuverBannerPressed(mapOf("steps" to stepsPayload))
803
+ }
804
+
805
+
717
806
  speechApi.cancel()
718
807
  voiceInstructionsPlayer.clear()
719
808
  mapboxNavigation?.setNavigationRoutes(listOf())
@@ -760,6 +849,21 @@ class ExpoMapboxNavigationView(context: Context, appContext: AppContext) :
760
849
  .voiceUnits(resolveVoiceUnits())
761
850
  .coordinatesList(points)
762
851
  .annotations("maxspeed,congestion,duration,speed")
852
+ // ── FIX: Lane guidance was not displaying ───────────────────────────
853
+ // Root cause: BannerInstructions.sub() (which carries lane data) is
854
+ // only returned by the Directions API when explicitly requested.
855
+ // applyDefaultNavigationOptions() does set bannerInstructions(true)
856
+ // internally, but several confirmed cases (Mapbox GitHub issue #7377,
857
+ // "Lane guidance not showing, using Navigation Drop-in UI") show this
858
+ // needs to be set explicitly to reliably trigger lane data in the
859
+ // response — official Mapbox examples do the same as a defensive fix.
860
+ .bannerInstructions(true)
861
+ // steps(true) is required for any BannerInstructions/VoiceInstructions
862
+ // to be present in the response at all.
863
+ .steps(true)
864
+ // roundaboutExits(true) ensures lane + exit guidance is generated for
865
+ // roundabout maneuvers specifically (separate flag from bannerInstructions).
866
+ .roundaboutExits(true)
763
867
  waypointIndices?.let { builder.waypointIndicesList(it) }
764
868
  navigationProfile?.let { builder.profile(if (it.startsWith("mapbox/")) it else "mapbox/$it") }
765
869
  excludeTypes?.takeIf { it.isNotEmpty() }?.let { builder.exclude(it.joinToString(",")) }
@@ -0,0 +1,40 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <resources>
3
+
4
+ <!--
5
+ FIX: Street/road name text was being cut off on the instruction banner
6
+ (e.g. "Wulmersumsesteenwe" instead of "Wulmersumsesteenweg") on
7
+ narrower or smaller-DPI screens.
8
+
9
+ Root cause: MapboxPrimaryManeuver (the road name text view inside
10
+ MapboxManeuverView) uses a fixed text size by default with no
11
+ auto-shrink behavior, so long street names get clipped by ellipsis
12
+ instead of adapting to the available banner width.
13
+
14
+ Fix: define a custom text appearance with Android's built-in
15
+ autoSizeTextType="uniform", which lets the system shrink the text
16
+ size step-by-step (within autoSizeMinTextSize..autoSizeMaxTextSize)
17
+ until the full string fits the view width — exactly like Waze/Google
18
+ Maps adapt their instruction banner text across different phone
19
+ screen sizes.
20
+ -->
21
+ <style name="MapboxCustomManeuverTextStyle" parent="MapboxStylePrimaryManeuver">
22
+ <item name="android:autoSizeTextType">uniform</item>
23
+ <item name="android:autoSizeMinTextSize">16sp</item>
24
+ <item name="android:autoSizeMaxTextSize">28sp</item>
25
+ <item name="android:autoSizeStepGranularity">1sp</item>
26
+ <item name="android:maxLines">2</item>
27
+ <item name="android:ellipsize">end</item>
28
+ </style>
29
+
30
+ <!--
31
+ Custom MapboxManeuverView style that:
32
+ 1. Applies the auto-sizing primary maneuver text style above.
33
+ 2. Keeps default backgrounds/colors (day mode handled by Nav SDK
34
+ style switching already implemented in ExpoMapboxNavigationView).
35
+ -->
36
+ <style name="MapboxCustomManeuverViewStyle" parent="MapboxStyleManeuverView">
37
+ <item name="primaryManeuverTextAppearance">@style/MapboxCustomManeuverTextStyle</item>
38
+ </style>
39
+
40
+ </resources>
package/build/index.d.ts CHANGED
@@ -84,6 +84,14 @@ export interface MapboxNavigationViewProps {
84
84
  onArrival?: (event: {
85
85
  nativeEvent: {};
86
86
  }) => void;
87
+ /**
88
+ * Fired when the user taps the turn-by-turn instruction banner.
89
+ * Use this to open a bottom sheet / modal listing every upcoming step
90
+ * of the route, including lane guidance data where available.
91
+ */
92
+ onManeuverBannerPressed?: (event: {
93
+ nativeEvent: ManeuverBannerPressedEvent;
94
+ }) => void;
87
95
  style?: ViewStyle;
88
96
  }
89
97
  export interface RouteProgressEvent {
@@ -98,6 +106,33 @@ export interface RoutesReadyEvent {
98
106
  distanceMeters: number;
99
107
  durationSeconds: number;
100
108
  }
109
+ /** A single lane's directional options at an upcoming intersection. */
110
+ export interface LaneInstruction {
111
+ /** Whether this lane is the recommended lane for the upcoming maneuver. */
112
+ active: boolean;
113
+ /** Directions this lane allows, e.g. ["straight"], ["left"], ["straight", "right"]. */
114
+ directions: string[];
115
+ }
116
+ /** One step (maneuver) along the route, used for the full-route steps list. */
117
+ export interface RouteStep {
118
+ /** Human-readable instruction text, e.g. "Turn left onto Main St". */
119
+ instruction: string;
120
+ /** Distance in metres for this step. */
121
+ distanceMeters: number;
122
+ /** Duration in seconds for this step. */
123
+ durationSeconds: number;
124
+ /** Directions API maneuver type, e.g. "turn", "merge", "roundabout". */
125
+ maneuverType: string;
126
+ /** Directions API maneuver modifier, e.g. "left", "right", "straight". */
127
+ maneuverModifier: string;
128
+ /** Name of the road for this step, if available. */
129
+ roadName: string;
130
+ /** Lane guidance for this step's upcoming maneuver, if available. */
131
+ laneInstructions: LaneInstruction[];
132
+ }
133
+ export interface ManeuverBannerPressedEvent {
134
+ steps: RouteStep[];
135
+ }
101
136
  /**
102
137
  * MapboxNavigationView
103
138
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jacques_gordon/expo-mapbox-navigation",
3
- "version": "2.1.5",
3
+ "version": "2.1.6",
4
4
  "description": "Expo module for Mapbox Navigation SDK with 16KB page size support, NDK27, and Mapbox Maps v11.11.0+",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
package/src/index.tsx CHANGED
@@ -104,6 +104,13 @@ export interface MapboxNavigationViewProps {
104
104
  onRoutesFailed?: (event: { nativeEvent: { message: string } }) => void;
105
105
  onArrival?: (event: { nativeEvent: {} }) => void;
106
106
 
107
+ /**
108
+ * Fired when the user taps the turn-by-turn instruction banner.
109
+ * Use this to open a bottom sheet / modal listing every upcoming step
110
+ * of the route, including lane guidance data where available.
111
+ */
112
+ onManeuverBannerPressed?: (event: { nativeEvent: ManeuverBannerPressedEvent }) => void;
113
+
107
114
  style?: ViewStyle;
108
115
  }
109
116
 
@@ -121,6 +128,36 @@ export interface RoutesReadyEvent {
121
128
  durationSeconds: number;
122
129
  }
123
130
 
131
+ /** A single lane's directional options at an upcoming intersection. */
132
+ export interface LaneInstruction {
133
+ /** Whether this lane is the recommended lane for the upcoming maneuver. */
134
+ active: boolean;
135
+ /** Directions this lane allows, e.g. ["straight"], ["left"], ["straight", "right"]. */
136
+ directions: string[];
137
+ }
138
+
139
+ /** One step (maneuver) along the route, used for the full-route steps list. */
140
+ export interface RouteStep {
141
+ /** Human-readable instruction text, e.g. "Turn left onto Main St". */
142
+ instruction: string;
143
+ /** Distance in metres for this step. */
144
+ distanceMeters: number;
145
+ /** Duration in seconds for this step. */
146
+ durationSeconds: number;
147
+ /** Directions API maneuver type, e.g. "turn", "merge", "roundabout". */
148
+ maneuverType: string;
149
+ /** Directions API maneuver modifier, e.g. "left", "right", "straight". */
150
+ maneuverModifier: string;
151
+ /** Name of the road for this step, if available. */
152
+ roadName: string;
153
+ /** Lane guidance for this step's upcoming maneuver, if available. */
154
+ laneInstructions: LaneInstruction[];
155
+ }
156
+
157
+ export interface ManeuverBannerPressedEvent {
158
+ steps: RouteStep[];
159
+ }
160
+
124
161
  // ─────────────────────────────────────────────────────────────────────────────
125
162
  // Native view
126
163
  // ─────────────────────────────────────────────────────────────────────────────