@tileflow/react-native 0.1.0-alpha.1 → 0.1.0-alpha.3

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/README.md CHANGED
@@ -85,6 +85,21 @@ and protected delivery still require HTTPS.
85
85
 
86
86
  `onThemeChange` reports only the safe public selection shape. It does not expose style URLs, style JSON, configuration, session authority or native error messages.
87
87
 
88
+ ## Theme transitions and blends
89
+
90
+ `themeTransition={{duration: 450}}` cross-fades theme changes: a snapshot of the drawn map covers it, follows the camera and fades out once the new theme is drawn. `themeBlend={{themes: ['night', 'dusk', 'day'], position}}` shows a continuous blend of the map's themes instead of `theme`; position 0 is the first theme and 1.5 is halfway from the second to the third:
91
+
92
+ ```tsx
93
+ <Map
94
+ source={source}
95
+ themeTransition={{duration: 450}}
96
+ themeBlend={{themes: ['night', 'dusk', 'day'], position}}
97
+ style={{flex: 1}}
98
+ />
99
+ ```
100
+
101
+ The first blend of a set of themes prepares every theme's style and replaces the native style once; moving `position` later changes the map without loading. Removing `themeBlend` returns to `theme`. Both keep the same native Map, context and session, and `ready` events wait for the fade. The device's reduce-motion setting changes at once. See [theme transitions and blends](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/theme-transitions-and-blends.md#react-native) for what follows the position on native maps.
102
+
88
103
  ## Camera
89
104
 
90
105
  The existing mount-stable camera contract is preserved. Use either an initial camera seed or complete controlled camera ownership for the lifetime of one mounted Map:
@@ -0,0 +1,75 @@
1
+ package dev.tileflow.reactnative
2
+
3
+ import android.animation.ValueAnimator
4
+ import android.content.Context
5
+ import android.graphics.Bitmap
6
+ import android.graphics.Canvas
7
+ import android.graphics.Matrix
8
+ import android.graphics.Paint
9
+ import android.graphics.PorterDuff
10
+ import android.graphics.PorterDuffXfermode
11
+ import android.os.Build
12
+ import android.provider.Settings
13
+ import com.facebook.react.bridge.Dynamic
14
+ import com.facebook.react.bridge.ReadableType
15
+ import kotlin.math.roundToInt
16
+
17
+ /** Pure helpers for theme transitions and blends on MapLibre Android. */
18
+ internal object NativeTheme {
19
+ private const val maximumDepth = 32
20
+
21
+ /** A style-specification value from JavaScript as the plain Java value MapLibre converts. */
22
+ fun value(dynamic: Dynamic, depth: Int = 0): Any? {
23
+ if (depth > maximumDepth) throw IllegalStateException("Native surface operation failed.")
24
+ return when (dynamic.type) {
25
+ ReadableType.Null -> null
26
+ ReadableType.Boolean -> dynamic.asBoolean()
27
+ ReadableType.Number -> dynamic.asDouble().also { if (!it.isFinite()) throw IllegalStateException("Native surface operation failed.") }
28
+ ReadableType.String -> dynamic.asString()
29
+ ReadableType.Array -> {
30
+ val array = dynamic.asArray() ?: throw IllegalStateException("Native surface operation failed.")
31
+ Array(array.size()) { index -> value(array.getDynamic(index), depth + 1) }
32
+ }
33
+ ReadableType.Map -> {
34
+ val map = dynamic.asMap() ?: throw IllegalStateException("Native surface operation failed.")
35
+ val result = HashMap<String, Any?>()
36
+ val keys = map.keySetIterator()
37
+ while (keys.hasNextKey()) {
38
+ val key = keys.nextKey()
39
+ result[key] = value(map.getDynamic(key), depth + 1)
40
+ }
41
+ result
42
+ }
43
+ }
44
+ }
45
+
46
+ /** Whether the device shows animations; the "remove animations" setting turns them off. */
47
+ fun motionEnabled(context: Context): Boolean {
48
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !ValueAnimator.areAnimatorsEnabled()) return false
49
+ return Settings.Global.getFloat(context.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f) != 0f
50
+ }
51
+
52
+ /** Pattern artwork mixed in premultiplied pixels: `from` at 1 - t plus `to` at t. */
53
+ fun mix(from: Bitmap, to: Bitmap, t: Double): Bitmap {
54
+ if (t <= 0) return from
55
+ if (t >= 1) return to
56
+ if (from.width != to.width || from.height != to.height) return if (t < 0.5) from else to
57
+ val result = Bitmap.createBitmap(from.width, from.height, Bitmap.Config.ARGB_8888)
58
+ result.density = from.density
59
+ val canvas = Canvas(result)
60
+ val paint = Paint()
61
+ paint.alpha = ((1 - t) * 255).roundToInt()
62
+ canvas.drawBitmap(from, 0f, 0f, paint)
63
+ paint.alpha = (t * 255).roundToInt()
64
+ paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.ADD)
65
+ canvas.drawBitmap(to, 0f, 0f, paint)
66
+ return result
67
+ }
68
+
69
+ /** The projective transform taking four points to four points, or null when degenerate. */
70
+ fun homography(from: FloatArray, to: FloatArray): Matrix? {
71
+ if (from.size != 8 || to.size != 8 || to.any { !it.isFinite() }) return null
72
+ val matrix = Matrix()
73
+ return if (matrix.setPolyToPoly(from, 0, to, 0, 4)) matrix else null
74
+ }
75
+ }
@@ -1,10 +1,17 @@
1
1
  package dev.tileflow.reactnative
2
2
 
3
+ import android.graphics.Bitmap
4
+ import android.graphics.PointF
3
5
  import android.os.Handler
4
6
  import android.os.Looper
5
7
  import android.view.View
6
8
  import android.view.ViewGroup
9
+ import android.view.PixelCopy
10
+ import android.view.SurfaceView
11
+ import android.view.TextureView
7
12
  import android.view.ViewTreeObserver
13
+ import android.view.animation.AccelerateDecelerateInterpolator
14
+ import android.widget.ImageView
8
15
  import com.facebook.react.bridge.Arguments
9
16
  import com.facebook.react.bridge.LifecycleEventListener
10
17
  import com.facebook.react.bridge.Promise
@@ -19,9 +26,14 @@ import com.facebook.react.uimanager.UIManagerHelper
19
26
  import com.facebook.react.uimanager.common.UIManagerType
20
27
  import java.util.UUID
21
28
  import kotlin.math.abs
29
+ import org.maplibre.android.geometry.LatLng
22
30
  import org.maplibre.android.maps.MapLibreMap
23
31
  import org.maplibre.android.maps.MapView
24
32
  import org.maplibre.android.maps.Style
33
+ import org.maplibre.android.style.layers.Layer
34
+ import org.maplibre.android.style.layers.LayoutPropertyValue
35
+ import org.maplibre.android.style.layers.PaintPropertyValue
36
+ import org.maplibre.android.style.light.Position
25
37
  import org.maplibre.reactnative.components.camera.MLRNCamera
26
38
  import org.maplibre.reactnative.components.mapview.MLRNMapView
27
39
 
@@ -154,6 +166,35 @@ class TileflowNativeSurfaceModule(context: ReactApplicationContext) : ReactConte
154
166
  surfaces[id]?.state?.cancelCommand(integer(sequence))
155
167
  Arguments.makeNativeMap(mapOf("cancelled" to true))
156
168
  }
169
+ private fun duration(value: Double): Long {
170
+ if (!value.isFinite() || value < 0 || value > 5000 || value != value.toLong().toDouble()) invalid()
171
+ return value.toLong()
172
+ }
173
+ @ReactMethod fun coverSurface(id: String, milliseconds: Double, promise: Promise) {
174
+ handler.post {
175
+ try {
176
+ val surface = current(id)
177
+ val covered = { value: Boolean -> promise.resolve(Arguments.makeNativeMap(mapOf("covered" to value))) }
178
+ if (duration(milliseconds) == 0L) covered(false) else surface.cover(covered)
179
+ } catch (_: Exception) { reject(promise) }
180
+ }
181
+ }
182
+ @ReactMethod fun revealSurface(id: String, milliseconds: Double, promise: Promise) {
183
+ handler.post {
184
+ try { current(id, allowFailed = true).reveal(duration(milliseconds), promise) }
185
+ catch (_: Exception) { reject(promise) }
186
+ }
187
+ }
188
+ @ReactMethod fun discardSurfaceCover(id: String, promise: Promise) = action(promise) {
189
+ if (!AdmissionUrl.validToken(id)) invalid()
190
+ surfaces[id]?.discardCovers()
191
+ Arguments.makeNativeMap(mapOf("discarded" to true))
192
+ }
193
+ @ReactMethod fun applyThemeValues(id: String, token: String, values: ReadableMap, promise: Promise) = action(promise) {
194
+ val surface = current(id)
195
+ if (surface.token != token) invalid()
196
+ Arguments.makeNativeMap(mapOf("applied" to surface.applyThemeValues(surface.style() ?: invalid(), values)))
197
+ }
157
198
  private fun nearAngular(a: Double, b: Double): Boolean = abs(((a - b + 540) % 360) - 180) <= 0.000001
158
199
  private fun view(input: ReadableMap): Map<String, Any> {
159
200
  val keys = input.keySetIterator(); var count = 0
@@ -222,6 +263,13 @@ class TileflowNativeSurfaceModule(context: ReactApplicationContext) : ReactConte
222
263
  var retiring = false
223
264
  var deadline: Runnable? = null
224
265
  var layoutWaiter: (() -> Unit)? = null
266
+ private val covers = mutableListOf<Cover>()
267
+ private val reveals = mutableListOf<Reveal>()
268
+ private val settling = mutableListOf<Reveal>()
269
+ private var revealDeadline: Runnable? = null
270
+ private var themedToken: String? = null
271
+ private val themedLayers = HashMap<String, Layer>()
272
+ private val themedImages = HashMap<String, Bitmap>()
225
273
  private var layoutSnapshot: List<Int>? = null
226
274
  fun marker() = "__tileflow_native_style_$token"
227
275
  fun style(): Style? = sdk.style?.takeIf { it.isFullyLoaded && it.getLayer(marker()) != null }
@@ -233,13 +281,16 @@ class TileflowNativeSurfaceModule(context: ReactApplicationContext) : ReactConte
233
281
  }
234
282
  private fun guarded(action: () -> Unit) { if (!retiring) try { action() } catch (_: Exception) { state.fail() } }
235
283
  private val loaded = MapView.OnDidFinishLoadingStyleListener { guarded { style()?.let { state.loaded(token, it) } } }
236
- private val frameStart = MapView.OnWillStartRenderingFrameListener { guarded { sampleLayout(); state.frameStart(style()) } }
237
- private val frameEnd = MapView.OnDidFinishRenderingFrameListener { fully, _, _ -> guarded { state.frameEnd(style(), fully) } }
284
+ private val frameStart = MapView.OnWillStartRenderingFrameListener { guarded { sampleLayout(); state.frameStart(style()); followCovers() } }
285
+ private val frameEnd = MapView.OnDidFinishRenderingFrameListener { fully, _, _ -> guarded {
286
+ state.frameEnd(style(), fully)
287
+ if (fully && reveals.isNotEmpty()) startReveals()
288
+ } }
238
289
  private val cameraStart = MapLibreMap.OnCameraMoveStartedListener { reason -> guarded {
239
290
  if (reason == MapLibreMap.OnCameraMoveStartedListener.REASON_API_GESTURE) state.gestureStart(view())
240
291
  else state.gestureEnd(view())
241
292
  } }
242
- private val cameraMove = MapLibreMap.OnCameraMoveListener { guarded { state.gestureChange(view()) } }
293
+ private val cameraMove = MapLibreMap.OnCameraMoveListener { guarded { state.gestureChange(view()); followCovers() } }
243
294
  private val cameraEnd = MapLibreMap.OnCameraIdleListener { guarded { state.gestureEnd(view()) } }
244
295
  private val layout = View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> guarded { sampleLayout() } }
245
296
  fun sampleLayout() {
@@ -257,6 +308,7 @@ class TileflowNativeSurfaceModule(context: ReactApplicationContext) : ReactConte
257
308
  }
258
309
  fun retire() {
259
310
  if (retiring) return
311
+ discardCovers()
260
312
  retiring = true; state.close(); deadline?.let { handler.removeCallbacks(it) }; deadline = null
261
313
  layoutWaiter?.invoke(); layoutWaiter = null
262
314
  root.removeOnLayoutChangeListener(layout); map.removeOnLayoutChangeListener(layout)
@@ -265,6 +317,162 @@ class TileflowNativeSurfaceModule(context: ReactApplicationContext) : ReactConte
265
317
  sdk.removeOnCameraMoveStartedListener(cameraStart); sdk.removeOnCameraMoveListener(cameraMove); sdk.removeOnCameraIdleListener(cameraEnd)
266
318
  if (!root.isAttachedToWindow) onViewDetachedFromWindow(root)
267
319
  }
320
+ /** A snapshot of one frame over the rendering view that follows four of its ground points. */
321
+ inner class Cover(val view: ImageView, val corners: FloatArray, val anchors: List<LatLng>) { var fading = false }
322
+ inner class Reveal(val duration: Long, val covers: List<Cover>, var promise: Promise?)
323
+
324
+ fun cover(covered: (Boolean) -> Unit) {
325
+ val render = map.renderView
326
+ if (!NativeTheme.motionEnabled(reactApplicationContext) || !foreground || style() == null || render == null ||
327
+ render.width < 1 || render.height < 1 || covers.size >= 4) { covered(false); return }
328
+ if (covers.any { !it.fading }) { covered(true); return }
329
+ // Only the drawn map is copied; logo, attribution and markers stay live above the cover.
330
+ when (render) {
331
+ is TextureView -> covered(render.bitmap?.let { addCover(it) } ?: false)
332
+ is SurfaceView -> {
333
+ val bitmap = Bitmap.createBitmap(render.width, render.height, Bitmap.Config.ARGB_8888)
334
+ PixelCopy.request(render, bitmap, { result ->
335
+ covered(try { result == PixelCopy.SUCCESS && addCover(bitmap) } catch (_: Exception) { false })
336
+ }, handler)
337
+ }
338
+ else -> covered(false)
339
+ }
340
+ }
341
+ private fun addCover(bitmap: Bitmap): Boolean {
342
+ val render = map.renderView
343
+ if (retiring || style() == null || render == null || !NativeTheme.motionEnabled(reactApplicationContext)) return false
344
+ val width = bitmap.width.toFloat(); val height = bitmap.height.toFloat()
345
+ // Ground points below the horizon at any supported pitch, in snapshot pixels.
346
+ val corners = floatArrayOf(width * 0.1f, height * 0.45f, width * 0.9f, height * 0.45f, width * 0.9f, height * 0.9f, width * 0.1f, height * 0.9f)
347
+ val projection = sdk.projection
348
+ val anchors = (0 until 4).map { projection.fromScreenLocation(PointF(corners[2 * it], corners[2 * it + 1])) }
349
+ if (anchors.any { !it.latitude.isFinite() || !it.longitude.isFinite() }) return false
350
+ val view = ImageView(map.context)
351
+ view.scaleType = ImageView.ScaleType.MATRIX
352
+ view.setImageBitmap(bitmap)
353
+ view.isClickable = false; view.isFocusable = false
354
+ view.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
355
+ // Newer covers go below older ones, which keep fading on top.
356
+ map.addView(view, map.indexOfChild(render) + 1, ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
357
+ // React Native lays out only the views it manages, so the cover is sized here.
358
+ view.measure(View.MeasureSpec.makeMeasureSpec(map.width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(map.height, View.MeasureSpec.EXACTLY))
359
+ view.layout(0, 0, map.width, map.height)
360
+ covers.add(Cover(view, corners, anchors))
361
+ followCovers()
362
+ return true
363
+ }
364
+ private fun followCovers() {
365
+ if (covers.isEmpty()) return
366
+ val projection = sdk.projection
367
+ for (cover in covers) {
368
+ val target = FloatArray(8)
369
+ cover.anchors.forEachIndexed { index, anchor ->
370
+ val point = projection.toScreenLocation(anchor)
371
+ target[2 * index] = point.x; target[2 * index + 1] = point.y
372
+ }
373
+ NativeTheme.homography(cover.corners, target)?.let { cover.view.imageMatrix = it }
374
+ }
375
+ }
376
+ fun reveal(duration: Long, promise: Promise) {
377
+ val reveal = Reveal(duration, covers.toList(), promise)
378
+ if (reveal.covers.isEmpty()) { promise.resolve(Arguments.makeNativeMap(mapOf("revealed" to true))); return }
379
+ reveals.add(reveal)
380
+ // Fades start on the next complete frame, or after ten seconds at most.
381
+ if (revealDeadline == null) {
382
+ val timeout = Runnable { revealDeadline = null; startReveals() }
383
+ revealDeadline = timeout; handler.postDelayed(timeout, 10000)
384
+ }
385
+ sdk.triggerRepaint()
386
+ }
387
+ private fun startReveals() {
388
+ revealDeadline?.let { handler.removeCallbacks(it) }; revealDeadline = null
389
+ val pending = reveals.toList(); reveals.clear()
390
+ for (reveal in pending) {
391
+ for (cover in reveal.covers) {
392
+ if (cover.fading) continue
393
+ cover.fading = true
394
+ cover.view.animate().alpha(0f).setDuration(reveal.duration).setInterpolator(AccelerateDecelerateInterpolator())
395
+ .withEndAction { removeCover(cover) }.start()
396
+ }
397
+ settle(reveal)
398
+ }
399
+ }
400
+ private fun removeCover(cover: Cover) {
401
+ cover.view.animate().cancel(); map.removeView(cover.view); covers.remove(cover)
402
+ for (reveal in settling.toList()) settle(reveal)
403
+ }
404
+ // A reveal resolves once every cover that existed when it was requested is gone.
405
+ private fun settle(reveal: Reveal) {
406
+ if (reveal.covers.any { it in covers }) { if (reveal !in settling) settling.add(reveal); return }
407
+ settling.remove(reveal)
408
+ reveal.promise?.resolve(Arguments.makeNativeMap(mapOf("revealed" to true))); reveal.promise = null
409
+ }
410
+ fun discardCovers() {
411
+ revealDeadline?.let { handler.removeCallbacks(it) }; revealDeadline = null
412
+ for (cover in covers.toList()) { cover.view.animate().cancel(); map.removeView(cover.view) }
413
+ covers.clear()
414
+ for (reveal in reveals + settling) { reveal.promise?.resolve(Arguments.makeNativeMap(mapOf("revealed" to true))); reveal.promise = null }
415
+ reveals.clear(); settling.clear()
416
+ }
417
+
418
+ fun applyThemeValues(style: Style, values: ReadableMap): Int {
419
+ if (themedToken != token) {
420
+ // Layers and original artwork belong to one style.
421
+ themedToken = token; themedLayers.clear(); themedImages.clear()
422
+ }
423
+ var applied = 0
424
+ for (group in listOf("paint", "layout")) {
425
+ if (!values.hasKey(group)) continue
426
+ val entries = values.getArray(group) ?: invalid()
427
+ if (entries.size() > 8192) invalid()
428
+ for (index in 0 until entries.size()) {
429
+ val entry = entries.getArray(index) ?: invalid()
430
+ if (entry.size() != 3 || entry.getType(0) != ReadableType.String || entry.getType(1) != ReadableType.String) invalid()
431
+ val identifier = entry.getString(0) ?: invalid()
432
+ val layer = themedLayers[identifier] ?: style.getLayer(identifier)?.also { themedLayers[identifier] = it } ?: continue
433
+ val property = entry.getString(1) ?: invalid()
434
+ val value = NativeTheme.value(entry.getDynamic(2))
435
+ layer.setProperties(if (group == "paint") PaintPropertyValue(property, value) else LayoutPropertyValue(property, value))
436
+ applied++
437
+ }
438
+ }
439
+ if (values.hasKey("images")) {
440
+ val images = values.getArray("images") ?: invalid()
441
+ if (images.size() > 64) invalid()
442
+ // Originals are read before any artwork is replaced, so tuples can share names.
443
+ for (index in 0 until images.size()) {
444
+ val entry = images.getArray(index) ?: invalid()
445
+ if (entry.size() != 4 || (0 until 3).any { entry.getType(it) != ReadableType.String } || entry.getType(3) != ReadableType.Number) invalid()
446
+ for (name in (0 until 3).map { entry.getString(it) ?: invalid() })
447
+ if (!themedImages.containsKey(name)) style.getImage(name)?.let { themedImages[name] = it }
448
+ }
449
+ for (index in 0 until images.size()) {
450
+ val entry = images.getArray(index) ?: invalid()
451
+ val from = themedImages[entry.getString(1)] ?: continue
452
+ val to = themedImages[entry.getString(2)] ?: continue
453
+ val t = entry.getDouble(3)
454
+ if (!t.isFinite() || t < 0 || t > 1) invalid()
455
+ style.addImage(entry.getString(0) ?: invalid(), NativeTheme.mix(from, to, t))
456
+ applied++
457
+ }
458
+ }
459
+ if (values.hasKey("light")) {
460
+ val light = values.getMap("light") ?: invalid()
461
+ val target = style.light
462
+ if (target != null) {
463
+ if (light.hasKey("color") && light.getType("color") == ReadableType.String) target.setColor(light.getString("color") ?: invalid())
464
+ if (light.hasKey("intensity") && light.getType("intensity") == ReadableType.Number) target.intensity = light.getDouble("intensity").toFloat()
465
+ if (light.hasKey("position") && light.getType("position") == ReadableType.Array) {
466
+ val position = light.getArray("position") ?: invalid()
467
+ if (position.size() == 3 && (0 until 3).all { position.getType(it) == ReadableType.Number })
468
+ target.position = Position(position.getDouble(0).toFloat(), position.getDouble(1).toFloat(), position.getDouble(2).toFloat())
469
+ }
470
+ applied++
471
+ }
472
+ }
473
+ return applied
474
+ }
475
+
268
476
  override fun onViewAttachedToWindow(view: View) { guarded { sampleLayout() } }
269
477
  override fun onViewDetachedFromWindow(view: View) {
270
478
  retire(); root.removeOnAttachStateChangeListener(this); surfaces.remove(id)
@@ -27,9 +27,35 @@ type MapColorScheme = NonNullable<TileflowNativeSourceOptions['colorScheme']>;
27
27
  type MapView = TileflowNativeInitialView;
28
28
  /** Portable composition inputs, independent from source acquisition. */
29
29
  type MapInitialViewInputs = TileflowNativeInitialViewOptions;
30
+ /**
31
+ * How a theme change appears. With a `duration`, a snapshot of the current frame covers the map,
32
+ * follows the camera, and fades out once the new theme is drawn. The device's reduce-motion
33
+ * setting changes at once.
34
+ */
35
+ type MapThemeTransition = Readonly<{
36
+ /** Cross-fade length in milliseconds, from 0 (the default, an immediate change) to 5000. */
37
+ duration?: number;
38
+ }>;
39
+ /**
40
+ * A continuous blend of two to eight concrete themes of the map, shown instead of `theme`.
41
+ * `position` runs from 0 (the first theme) to `themes.length - 1`; 1.25 is a quarter of the way
42
+ * from the second theme to the third. Ground colours and widths follow the position; labels and
43
+ * icons cross-fade when the nearest theme changes.
44
+ */
45
+ type MapThemeBlend = Readonly<{
46
+ themes: readonly string[];
47
+ position: number;
48
+ }>;
30
49
  type MapSourceProps = Readonly<{
31
50
  source: MapSource;
32
51
  theme?: MapThemeSelection;
52
+ /** Cross-fades theme changes, including entering and leaving a blend. */
53
+ themeTransition?: MapThemeTransition;
54
+ /**
55
+ * Shows a blend of themes instead of `theme`. The first blend of a set of themes prepares one
56
+ * style that holds all of them; moving `position` later changes the map without loading.
57
+ */
58
+ themeBlend?: MapThemeBlend;
33
59
  }>;
34
60
  type AllowedMapOptions = 'dragPan' | 'touchZoom' | 'doubleTapZoom' | 'doubleTapHoldZoom' | 'touchRotate' | 'touchPitch' | 'compass' | 'compassHiddenFacingNorth' | 'scaleBar';
35
61
  type OwnedMapOptions = Exclude<keyof MapProps$1, AllowedMapOptions> | 'container' | 'styleURL' | 'styleUrl' | 'transformRequest' | 'requestTransform' | 'requestHeaders';
@@ -141,4 +167,4 @@ type MapCameraProps = Readonly<{
141
167
  /** Complete Tileflow source, presentation, mount-stable camera and portable interaction inputs. */
142
168
  type MapProps<TAnnotation extends TileflowAnnotation = TileflowAnnotation> = MapBaseProps & MapCameraProps & MapInteractionProps<TAnnotation>;
143
169
 
144
- export type { MapView as M, MapViewChangeEvent as a, MapInitialViewInputs as b, MapCameraProps as c, MapProps as d, MapBaseProps as e, MapColorScheme as f, MapErrorEvent as g, MapEventHandlers as h, MapInteractionEvent as i, MapInteractionProps as j, MapLoadEvent as k, MapMarkerRenderContext as l, MapMarkerRenderer as m, MapOptions as n, MapPresentationProps as o, MapReadinessChangeEvent as p, MapRef as q, MapSelection as r, MapSource as s, MapSourceError as t, MapSourceProps as u, MapSourceState as v, MapTheme as w, MapThemeChangeEvent as x, MapThemeSelection as y };
170
+ export type { MapThemeTransition as A, MapView as M, MapViewChangeEvent as a, MapInitialViewInputs as b, MapCameraProps as c, MapProps as d, MapBaseProps as e, MapColorScheme as f, MapErrorEvent as g, MapEventHandlers as h, MapInteractionEvent as i, MapInteractionProps as j, MapLoadEvent as k, MapMarkerRenderContext as l, MapMarkerRenderer as m, MapOptions as n, MapPresentationProps as o, MapReadinessChangeEvent as p, MapRef as q, MapSelection as r, MapSource as s, MapSourceError as t, MapSourceProps as u, MapSourceState as v, MapTheme as w, MapThemeBlend as x, MapThemeChangeEvent as y, MapThemeSelection as z };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { ReactElement } from 'react';
2
2
  import { TileflowAnnotation } from '@tileflow/interactions';
3
3
  export { TileflowAnnotation, TileflowInteractionBinding, TileflowInteractionCoordinate, TileflowInteractionDiagnostic, TileflowPoiCategory, TileflowResolvedAnnotationTarget, TileflowResolvedPoiFeatureTarget } from '@tileflow/interactions';
4
- import { d as MapProps } from './contract-DWs-lDPo.js';
5
- export { e as MapBaseProps, c as MapCameraProps, f as MapColorScheme, g as MapErrorEvent, h as MapEventHandlers, b as MapInitialViewInputs, i as MapInteractionEvent, j as MapInteractionProps, k as MapLoadEvent, l as MapMarkerRenderContext, m as MapMarkerRenderer, n as MapOptions, o as MapPresentationProps, p as MapReadinessChangeEvent, q as MapRef, r as MapSelection, s as MapSource, t as MapSourceError, u as MapSourceProps, v as MapSourceState, w as MapTheme, x as MapThemeChangeEvent, y as MapThemeSelection, M as MapView, a as MapViewChangeEvent } from './contract-DWs-lDPo.js';
4
+ import { d as MapProps } from './contract-Bwl-gB-I.js';
5
+ export { e as MapBaseProps, c as MapCameraProps, f as MapColorScheme, g as MapErrorEvent, h as MapEventHandlers, b as MapInitialViewInputs, i as MapInteractionEvent, j as MapInteractionProps, k as MapLoadEvent, l as MapMarkerRenderContext, m as MapMarkerRenderer, n as MapOptions, o as MapPresentationProps, p as MapReadinessChangeEvent, q as MapRef, r as MapSelection, s as MapSource, t as MapSourceError, u as MapSourceProps, v as MapSourceState, w as MapTheme, x as MapThemeBlend, y as MapThemeChangeEvent, z as MapThemeSelection, A as MapThemeTransition, M as MapView, a as MapViewChangeEvent } from './contract-Bwl-gB-I.js';
6
6
  import '@maplibre/maplibre-react-native';
7
7
  import 'react-native';
8
8
  import '@tileflow/core/native';