panelui-native 0.77.0 → 0.79.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/lib/module/components/area-chart/index.js +32 -25
  2. package/lib/module/components/area-chart/index.js.map +1 -1
  3. package/lib/module/components/bottom-sheet/index.js +97 -15
  4. package/lib/module/components/bottom-sheet/index.js.map +1 -1
  5. package/lib/module/components/funnel-chart/index.js +15 -2
  6. package/lib/module/components/funnel-chart/index.js.map +1 -1
  7. package/lib/module/components/heatmap-chart/index.js +19 -27
  8. package/lib/module/components/heatmap-chart/index.js.map +1 -1
  9. package/lib/module/components/hex-chart/index.js +2 -0
  10. package/lib/module/components/hex-chart/index.js.map +1 -1
  11. package/lib/module/components/line-chart/index.js +38 -31
  12. package/lib/module/components/line-chart/index.js.map +1 -1
  13. package/lib/module/components/map/index.js +63 -39
  14. package/lib/module/components/map/index.js.map +1 -1
  15. package/lib/module/components/map/map-children.js +54 -0
  16. package/lib/module/components/map/map-children.js.map +1 -0
  17. package/lib/module/components/map/maplibre.js +31 -3
  18. package/lib/module/components/map/maplibre.js.map +1 -1
  19. package/lib/module/components/markdown-editor/index.js +288 -34
  20. package/lib/module/components/markdown-editor/index.js.map +1 -1
  21. package/lib/module/components/markdown-editor/markdown-transforms.js +272 -9
  22. package/lib/module/components/markdown-editor/markdown-transforms.js.map +1 -1
  23. package/lib/module/components/marquee/index.js +205 -46
  24. package/lib/module/components/marquee/index.js.map +1 -1
  25. package/lib/module/components/panelside/index.js +162 -34
  26. package/lib/module/components/panelside/index.js.map +1 -1
  27. package/lib/module/components/pie-chart/index.js +15 -2
  28. package/lib/module/components/pie-chart/index.js.map +1 -1
  29. package/lib/module/components/plot/index.js +42 -44
  30. package/lib/module/components/plot/index.js.map +1 -1
  31. package/lib/module/components/progress-button/index.js +617 -0
  32. package/lib/module/components/progress-button/index.js.map +1 -0
  33. package/lib/module/components/progress-button/progress-button-hold.js +91 -0
  34. package/lib/module/components/progress-button/progress-button-hold.js.map +1 -0
  35. package/lib/module/components/scatter-chart/index.js +6 -25
  36. package/lib/module/components/scatter-chart/index.js.map +1 -1
  37. package/lib/module/components/section-rail/index.js +40 -17
  38. package/lib/module/components/section-rail/index.js.map +1 -1
  39. package/lib/module/components/split-view/index.js +54 -10
  40. package/lib/module/components/split-view/index.js.map +1 -1
  41. package/lib/module/components/theme-selector/index.js +239 -0
  42. package/lib/module/components/theme-selector/index.js.map +1 -0
  43. package/lib/module/components/theme-selector/theme-preview.js +320 -0
  44. package/lib/module/components/theme-selector/theme-preview.js.map +1 -0
  45. package/lib/module/components/timeline/index.js +145 -14
  46. package/lib/module/components/timeline/index.js.map +1 -1
  47. package/lib/module/components/toast/index.js +45 -11
  48. package/lib/module/components/toast/index.js.map +1 -1
  49. package/lib/module/components/treemap-chart/index.js +63 -6
  50. package/lib/module/components/treemap-chart/index.js.map +1 -1
  51. package/lib/module/components/waterfall-chart/index.js +20 -4
  52. package/lib/module/components/waterfall-chart/index.js.map +1 -1
  53. package/lib/module/hooks/index.js +1 -0
  54. package/lib/module/hooks/index.js.map +1 -1
  55. package/lib/module/hooks/use-scroll-sections.js +44 -2
  56. package/lib/module/hooks/use-scroll-sections.js.map +1 -1
  57. package/lib/module/hooks/use-skeleton-handoff.js +58 -0
  58. package/lib/module/hooks/use-skeleton-handoff.js.map +1 -0
  59. package/lib/module/index.js +2 -0
  60. package/lib/module/index.js.map +1 -1
  61. package/lib/typescript/src/components/area-chart/index.d.ts +1 -1
  62. package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -1
  63. package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
  64. package/lib/typescript/src/components/funnel-chart/index.d.ts.map +1 -1
  65. package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
  66. package/lib/typescript/src/components/hex-chart/index.d.ts.map +1 -1
  67. package/lib/typescript/src/components/line-chart/index.d.ts +1 -1
  68. package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
  69. package/lib/typescript/src/components/map/index.d.ts +8 -4
  70. package/lib/typescript/src/components/map/index.d.ts.map +1 -1
  71. package/lib/typescript/src/components/map/map-children.d.ts +41 -0
  72. package/lib/typescript/src/components/map/map-children.d.ts.map +1 -0
  73. package/lib/typescript/src/components/map/maplibre.d.ts +8 -4
  74. package/lib/typescript/src/components/map/maplibre.d.ts.map +1 -1
  75. package/lib/typescript/src/components/markdown-editor/index.d.ts +64 -5
  76. package/lib/typescript/src/components/markdown-editor/index.d.ts.map +1 -1
  77. package/lib/typescript/src/components/markdown-editor/markdown-transforms.d.ts +33 -5
  78. package/lib/typescript/src/components/markdown-editor/markdown-transforms.d.ts.map +1 -1
  79. package/lib/typescript/src/components/marquee/index.d.ts +55 -2
  80. package/lib/typescript/src/components/marquee/index.d.ts.map +1 -1
  81. package/lib/typescript/src/components/panelside/index.d.ts +79 -4
  82. package/lib/typescript/src/components/panelside/index.d.ts.map +1 -1
  83. package/lib/typescript/src/components/pie-chart/index.d.ts.map +1 -1
  84. package/lib/typescript/src/components/plot/index.d.ts +1 -3
  85. package/lib/typescript/src/components/plot/index.d.ts.map +1 -1
  86. package/lib/typescript/src/components/progress-button/index.d.ts +311 -0
  87. package/lib/typescript/src/components/progress-button/index.d.ts.map +1 -0
  88. package/lib/typescript/src/components/progress-button/progress-button-hold.d.ts +60 -0
  89. package/lib/typescript/src/components/progress-button/progress-button-hold.d.ts.map +1 -0
  90. package/lib/typescript/src/components/scatter-chart/index.d.ts +1 -1
  91. package/lib/typescript/src/components/scatter-chart/index.d.ts.map +1 -1
  92. package/lib/typescript/src/components/section-rail/index.d.ts.map +1 -1
  93. package/lib/typescript/src/components/split-view/index.d.ts +10 -1
  94. package/lib/typescript/src/components/split-view/index.d.ts.map +1 -1
  95. package/lib/typescript/src/components/theme-selector/index.d.ts +107 -0
  96. package/lib/typescript/src/components/theme-selector/index.d.ts.map +1 -0
  97. package/lib/typescript/src/components/theme-selector/theme-preview.d.ts +32 -0
  98. package/lib/typescript/src/components/theme-selector/theme-preview.d.ts.map +1 -0
  99. package/lib/typescript/src/components/timeline/index.d.ts +7 -0
  100. package/lib/typescript/src/components/timeline/index.d.ts.map +1 -1
  101. package/lib/typescript/src/components/toast/index.d.ts.map +1 -1
  102. package/lib/typescript/src/components/treemap-chart/index.d.ts.map +1 -1
  103. package/lib/typescript/src/components/waterfall-chart/index.d.ts.map +1 -1
  104. package/lib/typescript/src/hooks/index.d.ts +1 -0
  105. package/lib/typescript/src/hooks/index.d.ts.map +1 -1
  106. package/lib/typescript/src/hooks/use-scroll-sections.d.ts +1 -0
  107. package/lib/typescript/src/hooks/use-scroll-sections.d.ts.map +1 -1
  108. package/lib/typescript/src/hooks/use-skeleton-handoff.d.ts +11 -0
  109. package/lib/typescript/src/hooks/use-skeleton-handoff.d.ts.map +1 -0
  110. package/lib/typescript/src/index.d.ts +6 -4
  111. package/lib/typescript/src/index.d.ts.map +1 -1
  112. package/package.json +1 -1
  113. package/src/components/area-chart/index.tsx +49 -37
  114. package/src/components/bottom-sheet/index.tsx +109 -11
  115. package/src/components/funnel-chart/index.tsx +10 -2
  116. package/src/components/heatmap-chart/index.tsx +27 -22
  117. package/src/components/hex-chart/index.tsx +22 -6
  118. package/src/components/line-chart/index.tsx +57 -43
  119. package/src/components/map/index.tsx +63 -36
  120. package/src/components/map/map-children.ts +66 -0
  121. package/src/components/map/maplibre.ts +32 -4
  122. package/src/components/markdown-editor/index.tsx +413 -35
  123. package/src/components/markdown-editor/markdown-transforms.ts +274 -10
  124. package/src/components/marquee/index.tsx +254 -58
  125. package/src/components/panelside/index.tsx +179 -40
  126. package/src/components/pie-chart/index.tsx +10 -2
  127. package/src/components/plot/index.tsx +66 -50
  128. package/src/components/progress-button/index.tsx +684 -0
  129. package/src/components/progress-button/progress-button-hold.ts +88 -0
  130. package/src/components/scatter-chart/index.tsx +4 -25
  131. package/src/components/section-rail/index.tsx +39 -19
  132. package/src/components/split-view/index.tsx +53 -8
  133. package/src/components/theme-selector/index.tsx +281 -0
  134. package/src/components/theme-selector/theme-preview.tsx +205 -0
  135. package/src/components/timeline/index.tsx +143 -17
  136. package/src/components/toast/index.tsx +60 -11
  137. package/src/components/treemap-chart/index.tsx +65 -8
  138. package/src/components/waterfall-chart/index.tsx +14 -4
  139. package/src/hooks/index.ts +5 -0
  140. package/src/hooks/use-scroll-sections.ts +52 -3
  141. package/src/hooks/use-skeleton-handoff.ts +69 -0
  142. package/src/index.ts +25 -0
@@ -43,7 +43,7 @@ import {
43
43
  useState,
44
44
  type ReactNode,
45
45
  } from 'react';
46
- import { Pressable, View, type PressableProps, type ViewProps } from 'react-native';
46
+ import { Pressable, StyleSheet, View, type PressableProps, type ViewProps } from 'react-native';
47
47
  import { useCSSVariable } from 'uniwind';
48
48
  import { tv } from 'tailwind-variants';
49
49
  import { CompassIcon, CrosshairIcon, MinusIcon, PlusIcon } from '../../icons';
@@ -62,6 +62,7 @@ import {
62
62
  type LngLat,
63
63
  type LngLatBounds,
64
64
  type MapRef,
65
+ type PixelPoint,
65
66
  type StyleSpecification,
66
67
  type ViewState,
67
68
  } from './maplibre';
@@ -70,9 +71,10 @@ import {
70
71
  type MapFeatureAccessibility,
71
72
  type MapFeatureAccessibilityDescription,
72
73
  } from './map-accessibility';
74
+ import { asMapLayer, partitionMapChildren } from './map-children';
73
75
 
74
76
  export { hasMapLibre, CARTO_SOURCE };
75
- export type { BasemapSource, BasemapTokens, LngLat, LngLatBounds, ViewState };
77
+ export type { BasemapSource, BasemapTokens, LngLat, LngLatBounds, PixelPoint, ViewState };
76
78
  export type { MapFeatureAccessibility } from './map-accessibility';
77
79
 
78
80
  const mapVariants = tv({
@@ -251,8 +253,12 @@ export interface MapProps extends Omit<ViewProps, 'children'> {
251
253
  interactive?: boolean;
252
254
  /** Fires continuously while the map moves. */
253
255
  onViewStateChange?: (state: ViewState) => void;
254
- /** Fires when the map is pressed somewhere that is not a feature. */
255
- onPress?: (lngLat: LngLat) => void;
256
+ /**
257
+ * Fires when the map is pressed somewhere that is not a feature. The second
258
+ * argument is the same press in screen coordinates, for anchoring something
259
+ * of your own to where the finger landed.
260
+ */
261
+ onPress?: (lngLat: LngLat, point: PixelPoint) => void;
256
262
  /** Fires once the style has loaded and the first frame is drawn. */
257
263
  onReady?: () => void;
258
264
  }
@@ -386,9 +392,17 @@ const MapRoot = forwardRef<MapHandle, MapProps>(function MapRoot(
386
392
 
387
393
  const { Map: MapLibreMap, Camera } = MapLibre;
388
394
 
395
+ /*
396
+ * Only what the renderer understands is handed to it. Controls and any other
397
+ * chrome are drawn as a sibling above the map instead, because the native map
398
+ * view lays its own children out and would stretch an ordinary view over the
399
+ * whole surface — see `map-children.ts`.
400
+ */
401
+ const { layers, overlay } = partitionMapChildren(children);
402
+
389
403
  return (
390
- <View className={slots.root({ className })} {...props}>
391
- <MapContext.Provider value={context}>
404
+ <MapContext.Provider value={context}>
405
+ <View className={slots.root({ className })} {...props}>
392
406
  <MapLibreMap
393
407
  ref={mapRef}
394
408
  mapStyle={style}
@@ -417,7 +431,7 @@ const MapRoot = forwardRef<MapHandle, MapProps>(function MapRoot(
417
431
  }
418
432
  onPress={
419
433
  onPress
420
- ? (event) => onPress(event.nativeEvent.coordinates)
434
+ ? (event) => onPress(event.nativeEvent.lngLat, event.nativeEvent.point)
421
435
  : undefined
422
436
  }
423
437
  >
@@ -433,38 +447,43 @@ const MapRoot = forwardRef<MapHandle, MapProps>(function MapRoot(
433
447
  : { center: center ?? [0, 20], zoom, bearing, pitch }
434
448
  }
435
449
  />
436
- {textChildren(children)}
450
+ {layers}
437
451
  </MapLibreMap>
438
- </MapContext.Provider>
439
- <View
440
- accessibilityRole="list"
441
- style={{ position: 'absolute', width: 1, height: 1, overflow: 'hidden' }}
442
- >
443
- {[...accessibleFeatureGroups].flatMap(([groupId, group]) =>
444
- group.features.map(({ feature, label, hint, state }, index) =>
445
- group.onPress ? (
446
- <Pressable
447
- key={`${groupId}-${index}`}
448
- accessibilityRole="button"
449
- accessibilityLabel={label}
450
- accessibilityHint={hint}
451
- accessibilityState={state}
452
- onPress={() => group.onPress?.(feature)}
453
- />
454
- ) : (
455
- <View
456
- key={`${groupId}-${index}`}
457
- accessible
458
- accessibilityRole="text"
459
- accessibilityLabel={label}
460
- accessibilityHint={hint}
461
- accessibilityState={state}
462
- />
452
+ {overlay.length > 0 ? (
453
+ <View style={StyleSheet.absoluteFill} pointerEvents="box-none">
454
+ {textChildren(overlay)}
455
+ </View>
456
+ ) : null}
457
+ <View
458
+ accessibilityRole="list"
459
+ style={{ position: 'absolute', width: 1, height: 1, overflow: 'hidden' }}
460
+ >
461
+ {[...accessibleFeatureGroups].flatMap(([groupId, group]) =>
462
+ group.features.map(({ feature, label, hint, state }, index) =>
463
+ group.onPress ? (
464
+ <Pressable
465
+ key={`${groupId}-${index}`}
466
+ accessibilityRole="button"
467
+ accessibilityLabel={label}
468
+ accessibilityHint={hint}
469
+ accessibilityState={state}
470
+ onPress={() => group.onPress?.(feature)}
471
+ />
472
+ ) : (
473
+ <View
474
+ key={`${groupId}-${index}`}
475
+ accessible
476
+ accessibilityRole="text"
477
+ accessibilityLabel={label}
478
+ accessibilityHint={hint}
479
+ accessibilityState={state}
480
+ />
481
+ )
463
482
  )
464
- )
465
- )}
483
+ )}
484
+ </View>
466
485
  </View>
467
- </View>
486
+ </MapContext.Provider>
468
487
  );
469
488
  });
470
489
  MapRoot.displayName = 'Map';
@@ -578,6 +597,7 @@ function MapMarker({
578
597
  );
579
598
  }
580
599
  MapMarker.displayName = 'Map.Marker';
600
+ asMapLayer(MapMarker);
581
601
 
582
602
  export interface MapLabelProps {
583
603
  children?: ReactNode;
@@ -691,6 +711,7 @@ function MapPopup({ children, className, title, lngLat }: MapPopupProps) {
691
711
  );
692
712
  }
693
713
  MapPopup.displayName = 'Map.Popup';
714
+ asMapLayer(MapPopup);
694
715
 
695
716
  export interface MapControlsProps {
696
717
  /** Which corner the stack sits in. */
@@ -889,6 +910,7 @@ function MapRoute({
889
910
  );
890
911
  }
891
912
  MapRoute.displayName = 'Map.Route';
913
+ asMapLayer(MapRoute);
892
914
 
893
915
  export interface MapArcProps {
894
916
  /** Where the arc starts. */
@@ -968,6 +990,7 @@ function MapArc({
968
990
  );
969
991
  }
970
992
  MapArc.displayName = 'Map.Arc';
993
+ asMapLayer(MapArc);
971
994
 
972
995
  export interface MapGeoJSONProps {
973
996
  /** A Feature, FeatureCollection, or the URL of one. */
@@ -1042,6 +1065,7 @@ function MapGeoJSON({
1042
1065
  );
1043
1066
  }
1044
1067
  MapGeoJSON.displayName = 'Map.GeoJSON';
1068
+ asMapLayer(MapGeoJSON);
1045
1069
 
1046
1070
  export interface MapClusterProps {
1047
1071
  /** Point features to cluster. */
@@ -1140,6 +1164,7 @@ function MapCluster({
1140
1164
  );
1141
1165
  }
1142
1166
  MapCluster.displayName = 'Map.Cluster';
1167
+ asMapLayer(MapCluster);
1143
1168
 
1144
1169
  function useAccessibleMapFeatures(
1145
1170
  id: string,
@@ -1379,6 +1404,7 @@ function MapHeatmap({
1379
1404
  );
1380
1405
  }
1381
1406
  MapHeatmap.displayName = 'Map.Heatmap';
1407
+ asMapLayer(MapHeatmap);
1382
1408
 
1383
1409
  export interface MapUserLocationProps {
1384
1410
  /** Show which way the device is facing, not just where it is. */
@@ -1397,6 +1423,7 @@ function MapUserLocation({
1397
1423
  return <UserLocation heading={heading} accuracy={accuracy} />;
1398
1424
  }
1399
1425
  MapUserLocation.displayName = 'Map.UserLocation';
1426
+ asMapLayer(MapUserLocation);
1400
1427
 
1401
1428
  export const Map = Object.assign(MapRoot, {
1402
1429
  Marker: MapMarker,
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Which of a map's children belong to the renderer, and which belong to us.
3
+ *
4
+ * The native map view is not a React Native container. On Android it is a real
5
+ * `FrameLayout`, and it lays its children out itself — so an ordinary view
6
+ * handed to it loses the frame the layout engine computed, arrives stretched
7
+ * to the full size of the map, and covers the surface the map is drawn on. A
8
+ * control group written to sit in a corner ends up in the top-left, full
9
+ * bleed, with the map invisible behind it.
10
+ *
11
+ * So the children are split before they are mounted. Anything the renderer
12
+ * understands — a marker, a route, a layer — goes inside it. Everything else,
13
+ * including `Map.Controls` and any chrome written at the call site, is drawn
14
+ * as a sibling above the map, where the layout engine still owns it.
15
+ *
16
+ * Parts opt in by carrying `MAP_LAYER`, so the rule is a property of the part
17
+ * rather than a list kept somewhere else that a new part could be left out of.
18
+ */
19
+ import { Children, isValidElement, type ReactNode } from 'react';
20
+
21
+ /** Marks a component that has to be mounted inside the native map view. */
22
+ export const MAP_LAYER = Symbol.for('panelui.map.layer');
23
+
24
+ type Layer = { [MAP_LAYER]?: true };
25
+
26
+ /** Tags a part as belonging inside the renderer, and hands it back. */
27
+ export function asMapLayer<T>(part: T): T {
28
+ (part as T & Layer)[MAP_LAYER] = true;
29
+ return part;
30
+ }
31
+
32
+ /** Whether an element type has been tagged with {@link asMapLayer}. */
33
+ export function isMapLayer(type: unknown): boolean {
34
+ return (
35
+ (typeof type === 'function' || typeof type === 'object') &&
36
+ type !== null &&
37
+ (type as Layer)[MAP_LAYER] === true
38
+ );
39
+ }
40
+
41
+ export interface PartitionedMapChildren {
42
+ /** Mounted inside the native map view. */
43
+ layers: ReactNode[];
44
+ /** Drawn as a sibling above it, where React Native's layout still applies. */
45
+ overlay: ReactNode[];
46
+ }
47
+
48
+ /**
49
+ * Splits a map's children into the two halves above.
50
+ *
51
+ * A child that is not an element at all — a string, a number, a nullish branch
52
+ * — goes to the overlay. The renderer drops what it does not recognise, and
53
+ * drops it *silently*, which also puts its own child bookkeeping out of step
54
+ * with React's.
55
+ */
56
+ export function partitionMapChildren(children: ReactNode): PartitionedMapChildren {
57
+ const layers: ReactNode[] = [];
58
+ const overlay: ReactNode[] = [];
59
+
60
+ Children.forEach(children, (child) => {
61
+ if (isValidElement(child) && isMapLayer(child.type)) layers.push(child);
62
+ else if (child !== null && child !== undefined && child !== false) overlay.push(child);
63
+ });
64
+
65
+ return { layers, overlay };
66
+ }
@@ -27,6 +27,7 @@
27
27
  * below against the package rather than assuming they survived.
28
28
  */
29
29
  import type { ComponentType, ReactElement, ReactNode, Ref } from 'react';
30
+ import { TurboModuleRegistry } from 'react-native';
30
31
 
31
32
  /** `[longitude, latitude]` — the order the style spec uses, not the spoken one. */
32
33
  export type LngLat = [number, number];
@@ -34,6 +35,9 @@ export type LngLat = [number, number];
34
35
  /** `[west, south, east, north]`. */
35
36
  export type LngLatBounds = [number, number, number, number];
36
37
 
38
+ /** `[x, y]` in points, measured from the map's own top-left corner. */
39
+ export type PixelPoint = [number, number];
40
+
37
41
  /**
38
42
  * A MapLibre style document. Left loose on purpose: the spec is large, it is
39
43
  * versioned separately from this library, and the parts of it callers pass
@@ -121,7 +125,7 @@ interface MapLibreModule {
121
125
  touchPitch?: boolean;
122
126
  accessible?: boolean;
123
127
  importantForAccessibility?: 'auto' | 'yes' | 'no' | 'no-hide-descendants';
124
- onPress?: (event: NativeEvent<{ coordinates: LngLat }>) => void;
128
+ onPress?: (event: NativeEvent<{ lngLat: LngLat; point: PixelPoint }>) => void;
125
129
  onDidFinishLoadingMap?: () => void;
126
130
  onRegionDidChange?: (event: NativeEvent<ViewState>) => void;
127
131
  children?: ReactNode;
@@ -172,12 +176,35 @@ interface MapLibreModule {
172
176
  }>;
173
177
  }
174
178
 
179
+ /**
180
+ * Whether the renderer's native half is actually in this binary.
181
+ *
182
+ * The JavaScript resolving is not the question. A project that installed the
183
+ * package and then ran in a client built without it — Expo Go being the one
184
+ * everybody meets — has the JavaScript and none of the native views, and a
185
+ * `require` that succeeds there is a `require` that lies. The component would
186
+ * then try to mount a view nothing has registered, which is a crash in native
187
+ * code where a `try` in JavaScript has nothing to catch.
188
+ *
189
+ * `TurboModuleRegistry.get` answers the real question and returns null instead
190
+ * of throwing, so the graceful fallback the component already draws is reached
191
+ * rather than skipped.
192
+ */
193
+ function nativeRendererPresent(): boolean {
194
+ try {
195
+ return TurboModuleRegistry.get('MLRNMapViewModule') !== null;
196
+ } catch {
197
+ return false;
198
+ }
199
+ }
200
+
175
201
  /**
176
202
  * The renderer, or null when it is not installed. Resolved once at module
177
203
  * load — the require is cheap, and caching it keeps a try/catch out of render.
178
204
  */
179
205
  const maplibre: MapLibreModule | null = (() => {
180
206
  try {
207
+ if (!nativeRendererPresent()) return null;
181
208
  // eslint-disable-next-line @typescript-eslint/no-require-imports
182
209
  return require('@maplibre/maplibre-react-native') as MapLibreModule;
183
210
  } catch {
@@ -188,9 +215,10 @@ const maplibre: MapLibreModule | null = (() => {
188
215
  /**
189
216
  * True when a map can actually be drawn.
190
217
  *
191
- * False in any client that cannot load native modules, and in any project that
192
- * has not installed the renderer. Worth checking before routing somewhere whose
193
- * whole content is a map.
218
+ * False in any client that cannot load native modules — Expo Go included, even
219
+ * with the package installed — and in any project that has not installed the
220
+ * renderer. Worth checking before routing somewhere whose whole content is a
221
+ * map.
194
222
  */
195
223
  export const hasMapLibre = maplibre !== null;
196
224