@goway.to/maps 0.0.0-stage → 0.1.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 (125) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +676 -0
  3. package/NOTICE +25 -0
  4. package/README.md +59 -2
  5. package/app.plugin.js +19 -0
  6. package/dist/CameraBinding.web.d.ts +24 -0
  7. package/dist/CameraBinding.web.js +46 -0
  8. package/dist/DefaultMapMarker.d.ts +8 -0
  9. package/dist/DefaultMapMarker.js +30 -0
  10. package/dist/GeoJSONSource.d.ts +2 -0
  11. package/dist/GeoJSONSource.js +17 -0
  12. package/dist/GeoJSONSource.web.d.ts +2 -0
  13. package/dist/GeoJSONSource.web.js +68 -0
  14. package/dist/InteractionBinding.web.d.ts +7 -0
  15. package/dist/InteractionBinding.web.js +35 -0
  16. package/dist/Layer.d.ts +2 -0
  17. package/dist/Layer.js +21 -0
  18. package/dist/Layer.web.d.ts +2 -0
  19. package/dist/Layer.web.js +46 -0
  20. package/dist/LayerStyle.web.d.ts +10 -0
  21. package/dist/LayerStyle.web.js +45 -0
  22. package/dist/LightBinding.web.d.ts +5 -0
  23. package/dist/LightBinding.web.js +19 -0
  24. package/dist/MapAttribution.d.ts +27 -0
  25. package/dist/MapAttribution.js +149 -0
  26. package/dist/MapBrand.d.ts +22 -0
  27. package/dist/MapBrand.js +91 -0
  28. package/dist/MapErrorState.d.ts +9 -0
  29. package/dist/MapErrorState.js +58 -0
  30. package/dist/MapPlugin.d.ts +6 -0
  31. package/dist/MapPlugin.js +3 -0
  32. package/dist/MapPlugin.web.d.ts +2 -0
  33. package/dist/MapPlugin.web.js +43 -0
  34. package/dist/MapView.d.ts +2 -0
  35. package/dist/MapView.js +420 -0
  36. package/dist/MapView.web.d.ts +5 -0
  37. package/dist/MapView.web.js +413 -0
  38. package/dist/Marker.d.ts +13 -0
  39. package/dist/Marker.js +11 -0
  40. package/dist/Marker.web.d.ts +15 -0
  41. package/dist/Marker.web.js +51 -0
  42. package/dist/Markers.d.ts +2 -0
  43. package/dist/Markers.js +13 -0
  44. package/dist/Markers.web.d.ts +2 -0
  45. package/dist/Markers.web.js +13 -0
  46. package/dist/PluginProps.web.d.ts +7 -0
  47. package/dist/PluginProps.web.js +19 -0
  48. package/dist/RasterDEMSource.d.ts +2 -0
  49. package/dist/RasterDEMSource.js +7 -0
  50. package/dist/RasterDEMSource.web.d.ts +2 -0
  51. package/dist/RasterDEMSource.web.js +44 -0
  52. package/dist/SourceData.web.d.ts +6 -0
  53. package/dist/SourceData.web.js +14 -0
  54. package/dist/UserLocation.d.ts +2 -0
  55. package/dist/UserLocation.js +19 -0
  56. package/dist/UserLocation.web.d.ts +2 -0
  57. package/dist/UserLocation.web.js +21 -0
  58. package/dist/brand/BrandedChrome.d.ts +47 -0
  59. package/dist/brand/BrandedChrome.js +51 -0
  60. package/dist/brand/GowayLogo.d.ts +28 -0
  61. package/dist/brand/GowayLogo.js +50 -0
  62. package/dist/brand/artwork.d.ts +130 -0
  63. package/dist/brand/artwork.js +176 -0
  64. package/dist/cameraReports.web.d.ts +50 -0
  65. package/dist/cameraReports.web.js +117 -0
  66. package/dist/cameraTarget.d.ts +14 -0
  67. package/dist/cameraTarget.js +8 -0
  68. package/dist/chromeLift.d.ts +15 -0
  69. package/dist/chromeLift.js +15 -0
  70. package/dist/clusters.d.ts +57 -0
  71. package/dist/clusters.js +102 -0
  72. package/dist/commands.web.d.ts +16 -0
  73. package/dist/commands.web.js +158 -0
  74. package/dist/compactCredit.d.ts +52 -0
  75. package/dist/compactCredit.js +40 -0
  76. package/dist/creditHeight.d.ts +3 -0
  77. package/dist/creditHeight.js +12 -0
  78. package/dist/creditLabels.d.ts +30 -0
  79. package/dist/creditLabels.js +30 -0
  80. package/dist/creditRise.d.ts +28 -0
  81. package/dist/creditRise.js +33 -0
  82. package/dist/dragAxes.web.d.ts +65 -0
  83. package/dist/dragAxes.web.js +70 -0
  84. package/dist/engineContext.web.d.ts +13 -0
  85. package/dist/engineContext.web.js +12 -0
  86. package/dist/index.d.ts +12 -0
  87. package/dist/index.js +12 -0
  88. package/dist/index.web.d.ts +13 -0
  89. package/dist/index.web.js +13 -0
  90. package/dist/labels.d.ts +202 -0
  91. package/dist/labels.js +451 -0
  92. package/dist/loadedCamera.d.ts +64 -0
  93. package/dist/loadedCamera.js +56 -0
  94. package/dist/locationStore.d.ts +16 -0
  95. package/dist/locationStore.js +132 -0
  96. package/dist/mapRef.d.ts +4 -0
  97. package/dist/mapRef.js +13 -0
  98. package/dist/moveMarkers.d.ts +27 -0
  99. package/dist/moveMarkers.js +22 -0
  100. package/dist/nativeCameraReports.d.ts +44 -0
  101. package/dist/nativeCameraReports.js +73 -0
  102. package/dist/plugin.d.ts +63 -0
  103. package/dist/plugin.js +2 -0
  104. package/dist/queryLayers.d.ts +19 -0
  105. package/dist/queryLayers.js +54 -0
  106. package/dist/shared-exports.d.ts +17 -0
  107. package/dist/shared-exports.js +13 -0
  108. package/dist/shared.d.ts +170 -0
  109. package/dist/shared.js +337 -0
  110. package/dist/sourceContext.d.ts +2 -0
  111. package/dist/sourceContext.js +9 -0
  112. package/dist/styleUrl.d.ts +19 -0
  113. package/dist/styleUrl.js +22 -0
  114. package/dist/testing.d.ts +41 -0
  115. package/dist/testing.js +80 -0
  116. package/dist/types.d.ts +348 -0
  117. package/dist/types.js +1 -0
  118. package/dist/useMarkerSlots.d.ts +6 -0
  119. package/dist/useMarkerSlots.js +56 -0
  120. package/dist/useUserLocation.d.ts +44 -0
  121. package/dist/useUserLocation.js +247 -0
  122. package/dist/zoom.d.ts +2 -0
  123. package/dist/zoom.js +10 -0
  124. package/metro.cjs +70 -0
  125. package/package.json +115 -4
@@ -0,0 +1,149 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Map data credit — rendered by `MapView` on every map, identical on web and
4
+ * native, with no prop to turn it off.
5
+ *
6
+ * Both engines ship an attribution ornament of their own (maplibre-gl's
7
+ * `AttributionControl`, MapLibre Native's "i" button); `MapView` disables both
8
+ * and draws this instead, so the credit sits inside the app's layout (chrome
9
+ * can be positioned against it) and looks the same on both platforms.
10
+ *
11
+ * It POSITIONS itself. A credit pinned 4 px off the window edge disappears the
12
+ * moment anything parks at that edge — a half-sheet, a tab bar — and it
13
+ * disappears silently, which for a licence obligation is the worst way to
14
+ * fail. Reading Bloom's bottom-edge registry here means the credit rises above
15
+ * whatever is claimed without either knowing about the other.
16
+ *
17
+ * ## On the right, after the mark
18
+ *
19
+ * The GoWay mark (`MapBrand`) takes the bottom-left corner and the credit the
20
+ * bottom-right, starting after {@link MAP_BRAND_CLEARANCE} so a credit that
21
+ * wraps on a narrow phone never runs under the mark.
22
+ *
23
+ * ## Compact, as the OSMF guidelines allow
24
+ *
25
+ * The OSMF Attribution Guidelines
26
+ * (https://osmfoundation.org/wiki/Licence/Attribution_Guidelines) accept a
27
+ * credit that is visible when the map loads and then collapses — after a few
28
+ * seconds or once the reader interacts — into a control that shows it again.
29
+ * That is this component's two states (`compactCredit.ts` holds the rule and
30
+ * its test): spelled out, with an ⓘ at the end to fold it; or just the ⓘ, in
31
+ * the same corner, which opens it. The links stay links.
32
+ *
33
+ * ## A disclosure rides here
34
+ *
35
+ * While an app draws something the reader must not mistake for data (GoWay's
36
+ * simulated traffic), the credit says so: spelled out, its first line is the
37
+ * whole sentence; folded, a word sits beside the ⓘ. Labelled in BOTH states,
38
+ * for exactly as long as it is drawn.
39
+ */
40
+ import { memo, useCallback } from 'react';
41
+ import { Linking, Pressable, StyleSheet, View, } from 'react-native';
42
+ import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated';
43
+ import { localizedLabel } from '@goway.to/sdk';
44
+ import { useBloomLocale } from '@oxy.so/bloom/locale';
45
+ import { useBottomEdgeInset } from '@oxy.so/bloom/layout';
46
+ import { RiInformationLine } from '@oxy.so/bloom/icons/RiInformationLine';
47
+ import { useTheme } from '@oxy.so/bloom/theme';
48
+ import { Text } from '@oxy.so/bloom/typography';
49
+ import { useBrandedChrome } from './brand/BrandedChrome';
50
+ import { useMapChromeLift } from './chromeLift';
51
+ import { creditDisclosure } from './compactCredit';
52
+ import { useMapCreditHeight } from './creditHeight';
53
+ import { CREDIT_LABELS } from './creditLabels';
54
+ import { CREDIT_GAP, MAP_CREDIT_CLEARANCE } from './creditRise';
55
+ import { MAP_BRAND_CLEARANCE } from './MapBrand';
56
+ /** Breathing room between the credit and whatever is below it. */
57
+ const ATTRIBUTION_GAP = CREDIT_GAP;
58
+ /** From the left edge when no mark is drawn there: the plate's own right inset. */
59
+ const CREDIT_SIDE_INSET = 8;
60
+ /**
61
+ * How much of the bottom-right the credit occupies above the bottom edge when
62
+ * it is one line (`creditRise.ts`). Bottom-right chrome a screen floats there
63
+ * starts above it and rises by the credit's measured extra height
64
+ * (`creditHeight.ts`), so the two never overlap.
65
+ */
66
+ export { MAP_CREDIT_CLEARANCE };
67
+ /**
68
+ * The credit GoWay's tiles oblige: OpenMapTiles' schema and OpenStreetMap's
69
+ * data. Always shown, before anything an app adds.
70
+ */
71
+ export const MAP_DATA_CREDITS = [
72
+ { label: '© OpenMapTiles', href: 'https://www.openmaptiles.org/' },
73
+ { label: '© OpenStreetMap', href: 'https://www.openstreetmap.org/copyright' },
74
+ ];
75
+ const NO_EXTRA = [];
76
+ /**
77
+ * @param extra - credits owed while something the app draws is on screen,
78
+ * after the basemap's own.
79
+ */
80
+ function MapAttributionComponent({ extra = NO_EXTRA, disclosure = null, expanded, onToggle, }) {
81
+ const locale = useBloomLocale();
82
+ const theme = useTheme();
83
+ const toggleLabel = localizedLabel(expanded ? CREDIT_LABELS.hide : CREDIT_LABELS.show, locale ?? 'en');
84
+ const links = extra.length > 0 ? [...MAP_DATA_CREDITS, ...extra] : MAP_DATA_CREDITS;
85
+ const shown = creditDisclosure(expanded, disclosure);
86
+ const bottomEdge = useBottomEdgeInset();
87
+ const chromeLift = useMapChromeLift();
88
+ // The mark is not drawn while the app's own chrome carries it, so the
89
+ // credit takes the corner's full width.
90
+ const brandDrawn = !useBrandedChrome();
91
+ const heightOut = useMapCreditHeight();
92
+ const measure = useCallback((event) => {
93
+ heightOut?.set(withTiming(event.nativeEvent.layout.height, { duration: 160 }));
94
+ }, [heightOut]);
95
+ const open = useCallback((href) => {
96
+ void Linking.openURL(href).catch(() => {
97
+ // A link the platform will not open costs the tap, not the map.
98
+ });
99
+ }, []);
100
+ const secondary = { color: theme.colors.textSecondary };
101
+ const primary = { color: theme.colors.text };
102
+ return (_jsx(CreditBox, { lift: chromeLift, style: {
103
+ bottom: bottomEdge > 0 ? bottomEdge + ATTRIBUTION_GAP : ATTRIBUTION_GAP,
104
+ left: brandDrawn ? MAP_BRAND_CLEARANCE : CREDIT_SIDE_INSET,
105
+ }, children: _jsxs(View, { style: styles.plate, onLayout: heightOut ? measure : undefined, testID: "map-credit", children: [_jsx(View, { pointerEvents: "none", style: [StyleSheet.absoluteFill, styles.fill, { backgroundColor: theme.colors.card }] }), shown.line && disclosure ? (_jsx(Text, { variant: "caption-1-semibold", accessibilityLabel: `${shown.line}. ${disclosure.detail}`, style: [styles.disclosure, primary], testID: "map-credit-simulated", children: shown.line })) : null, _jsxs(View, { style: styles.row, children: [expanded
106
+ ? links.map((link) => (_jsx(Pressable, { accessibilityRole: "link", accessibilityLabel: link.label, onPress: () => open(link.href), hitSlop: 6, children: _jsx(Text, { variant: "caption-1-regular", style: [styles.link, secondary], children: link.label }) }, `${link.label}|${link.href}`)))
107
+ : null, _jsxs(Pressable, { accessibilityRole: "button", accessibilityLabel: shown.tag && disclosure ? `${disclosure.text}. ${toggleLabel}` : toggleLabel, "aria-expanded": expanded, onPress: onToggle, hitSlop: 10, style: ({ pressed }) => [styles.toggle, pressed ? styles.pressed : null], testID: "map-credit-toggle", children: [shown.tag ? (_jsx(Text, { variant: "caption-1-semibold", style: primary, testID: "map-credit-simulated-short", children: shown.tag })) : null, _jsx(RiInformationLine, { width: 18, height: 18, fill: theme.colors.textSecondary })] })] })] }) }));
108
+ }
109
+ function CreditBox({ lift, style, children, }) {
110
+ if (!lift) {
111
+ return (_jsx(View, { pointerEvents: "box-none", style: [styles.box, style], children: children }));
112
+ }
113
+ return (_jsx(LiftedCreditBox, { lift: lift, style: style, children: children }));
114
+ }
115
+ function LiftedCreditBox({ lift, style, children, }) {
116
+ const { lift: rise, opacity } = lift;
117
+ const animated = useAnimatedStyle(() => ({ transform: [{ translateY: -rise.value }], opacity: opacity.value }), [rise, opacity]);
118
+ return (_jsx(Animated.View, { pointerEvents: "box-none", style: [styles.box, style, animated], children: children }));
119
+ }
120
+ export const MapAttribution = memo(MapAttributionComponent);
121
+ const styles = StyleSheet.create({
122
+ box: { position: 'absolute', right: 8, alignItems: 'flex-end' },
123
+ plate: {
124
+ alignItems: 'flex-end',
125
+ gap: 2,
126
+ borderRadius: 8,
127
+ overflow: 'hidden',
128
+ paddingHorizontal: 8,
129
+ paddingVertical: 2,
130
+ },
131
+ fill: { opacity: 0.8 },
132
+ disclosure: { textAlign: 'right' },
133
+ row: {
134
+ flexDirection: 'row',
135
+ flexWrap: 'wrap',
136
+ alignItems: 'center',
137
+ justifyContent: 'flex-end',
138
+ gap: 4,
139
+ },
140
+ link: { textDecorationLine: 'underline' },
141
+ toggle: {
142
+ flexDirection: 'row',
143
+ alignItems: 'center',
144
+ justifyContent: 'center',
145
+ gap: 4,
146
+ borderRadius: 9999,
147
+ },
148
+ pressed: { opacity: 0.7 },
149
+ });
@@ -0,0 +1,22 @@
1
+ /**
2
+ * How wide the logo draws on the map.
3
+ *
4
+ * 76px puts the lockup at 48px tall, which is the smallest it stays legible at
5
+ * — the wordmark's two rows mean its cap height is roughly a fifth of its
6
+ * width, so it dies much earlier than a single-line logo would. Google's
7
+ * bottom-left badge is 66px wide and one row tall; this is the two-row
8
+ * equivalent, not a bigger claim on the canvas.
9
+ */
10
+ export declare const MAP_BRAND_WIDTH = 76;
11
+ /**
12
+ * Where the credit has to start so it cannot run under the mark.
13
+ *
14
+ * Exported because `MapAttribution` is the one that has to respect it, and the
15
+ * two must not each hold a private copy of the other's geometry.
16
+ */
17
+ export declare const MAP_BRAND_CLEARANCE: number;
18
+ /** How tall the mark draws. Layout that has to clear it reads this. */
19
+ export declare const MAP_BRAND_HEIGHT: number;
20
+ declare function MapBrandComponent(): import("react").JSX.Element | null;
21
+ export declare const MapBrand: import("react").MemoExoticComponent<typeof MapBrandComponent>;
22
+ export {};
@@ -0,0 +1,91 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * The GoWay mark on the map — bottom-left, on every map GoWay draws.
4
+ *
5
+ * Google's logo is in the bottom-left corner of every Google map, in their app,
6
+ * in a `<GoogleMap>` in somebody's React page and in a bare `<iframe>` of
7
+ * maps.google.com; Apple's wordmark is in the bottom-left corner of every Apple
8
+ * map. Neither is a decoration and neither is optional, because a map platform
9
+ * that is invisible inside other people's pages is a map platform nobody has
10
+ * heard of. This is GoWay's.
11
+ *
12
+ * ## Why `MapView` renders it and no screen can
13
+ *
14
+ * Exactly the reasoning in `MapAttribution` — a thing that must be on every map
15
+ * cannot be something a screen remembers to add. `MapView` renders both, both
16
+ * are bare components with no `enabled` prop, and no screen can turn either
17
+ * off — the same rule Mapbox keeps for its wordmark.
18
+ *
19
+ * One exception exists, and it is not a prop. A surface whose own chrome
20
+ * already shows the GoWay logo says so through `BrandedChromeProvider`, and
21
+ * the badge stands down. The direction is what keeps it safe — a surface opts
22
+ * IN to being branded already, so forgetting shows the badge rather than
23
+ * hiding it. See `brand/BrandedChrome.tsx`.
24
+ *
25
+ * ## Why there is no plate behind it
26
+ *
27
+ * Because the artwork already is one. GoWay's logo is a sticker: every letter
28
+ * carries a heavy `#004aad` outline, and that outline is what separates it from
29
+ * whatever it is standing on. It was checked rather than assumed — rendered at
30
+ * 90px over the eight colours that actually cover a GoWay map: light land `#f6f4eb`, built-up `#fef4df`, roads
31
+ * `#fefefe`, light water `#8ddbf6`, dark land `#34445b`, dark built-up
32
+ * `#45476e`, dark water `#1c347a`, and black. It reads on all eight, and it
33
+ * reads for two different reasons: on the light four the blue outline carries
34
+ * it, and on the dark four the outline recedes into the basemap and the bright
35
+ * `#acd0ff` and `#ffffff` counters carry it instead. A card behind it would add
36
+ * a rectangle to the map and buy nothing.
37
+ *
38
+ * Which also means it needs no light/dark variant. One drawing, both
39
+ * appearances — and no dependence on the map's appearance, which matters
40
+ * because the map's appearance and the app's theme are separate settings and
41
+ * this component can see only one of them.
42
+ *
43
+ * ## It is not a link, on purpose
44
+ *
45
+ * Inside somebody else's page it would be a click target the page did not
46
+ * ask for, and in GoWay's own app it would go where the reader already is.
47
+ */
48
+ import { memo } from 'react';
49
+ import { View } from 'react-native';
50
+ import { useBottomEdgeInset } from '@oxy.so/bloom/layout';
51
+ import { useBrandedChrome } from './brand/BrandedChrome';
52
+ import { GowayLogo, gowayLogoHeight } from './brand/GowayLogo';
53
+ /**
54
+ * How wide the logo draws on the map.
55
+ *
56
+ * 76px puts the lockup at 48px tall, which is the smallest it stays legible at
57
+ * — the wordmark's two rows mean its cap height is roughly a fifth of its
58
+ * width, so it dies much earlier than a single-line logo would. Google's
59
+ * bottom-left badge is 66px wide and one row tall; this is the two-row
60
+ * equivalent, not a bigger claim on the canvas.
61
+ */
62
+ export const MAP_BRAND_WIDTH = 76;
63
+ /** Breathing room between the mark and whatever is below it. */
64
+ const BRAND_GAP = 4;
65
+ /**
66
+ * Where the credit has to start so it cannot run under the mark.
67
+ *
68
+ * Exported because `MapAttribution` is the one that has to respect it, and the
69
+ * two must not each hold a private copy of the other's geometry.
70
+ */
71
+ export const MAP_BRAND_CLEARANCE = MAP_BRAND_WIDTH + 16;
72
+ /** How tall the mark draws. Layout that has to clear it reads this. */
73
+ export const MAP_BRAND_HEIGHT = gowayLogoHeight(MAP_BRAND_WIDTH);
74
+ function MapBrandComponent() {
75
+ const bottomEdge = useBottomEdgeInset();
76
+ const chromeIsBranded = useBrandedChrome();
77
+ // A surface whose own chrome carries the logo above the map. Drawing it here
78
+ // too is the brand twice on one screen, the second copy on the cartography.
79
+ if (chromeIsBranded)
80
+ return null;
81
+ return (_jsx(View, { pointerEvents: "none",
82
+ // Same rule as the credit: a claim on the bottom edge already folds in
83
+ // the safe area, so the plain gap is added to it and not to the
84
+ // safe-area-aware one.
85
+ style: {
86
+ position: 'absolute',
87
+ left: 8,
88
+ bottom: bottomEdge > 0 ? bottomEdge + BRAND_GAP : BRAND_GAP,
89
+ }, testID: "goway-map-brand", children: _jsx(GowayLogo, { width: MAP_BRAND_WIDTH }) }));
90
+ }
91
+ export const MapBrand = memo(MapBrandComponent);
@@ -0,0 +1,9 @@
1
+ import type { MapError } from './types';
2
+ export interface MapErrorStateProps {
3
+ error: MapError;
4
+ onRetry?: () => void;
5
+ retryLabel?: string;
6
+ }
7
+ declare function MapErrorStateComponent({ error, onRetry, retryLabel }: MapErrorStateProps): import("react").JSX.Element;
8
+ export declare const MapErrorState: import("react").MemoExoticComponent<typeof MapErrorStateComponent>;
9
+ export {};
@@ -0,0 +1,58 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * The map's degraded state.
4
+ *
5
+ * A MapLibre canvas whose style or tiles never arrive renders a plain coloured
6
+ * rectangle and reports nothing — indistinguishable from an app that is
7
+ * simply slow, forever. So both forks watch for the two failures a hosted
8
+ * source can produce (the style document, and the tiles it points at) and
9
+ * cover the CANVAS — only the canvas — with this. Search, saved places, the sheet and every other piece of
10
+ * chrome stay mounted and usable above it, because they are siblings of the
11
+ * canvas, not children.
12
+ */
13
+ import { memo } from 'react';
14
+ import { StyleSheet, View } from 'react-native';
15
+ import { Button } from '@oxy.so/bloom/button';
16
+ import { Text } from '@oxy.so/bloom/typography';
17
+ import { RiRefreshLine } from '@oxy.so/bloom/icons/RiRefreshLine';
18
+ import { RiErrorWarningLine } from '@oxy.so/bloom/icons/RiErrorWarningLine';
19
+ import { useTheme } from '@oxy.so/bloom/theme';
20
+ /** One sentence per reason — what happened, in the user's terms, not ours. */
21
+ const MESSAGES = {
22
+ style: {
23
+ title: 'Map unavailable',
24
+ body: "We couldn't load the map's design. Everything else still works.",
25
+ },
26
+ tiles: {
27
+ title: 'Map data unavailable',
28
+ body: "We couldn't reach the map data for this area. Check your connection and try again.",
29
+ },
30
+ unsupported: {
31
+ title: 'Map not supported here',
32
+ body: 'This build has no map renderer for the current platform.',
33
+ },
34
+ runtime: {
35
+ title: 'Map stopped',
36
+ body: 'The map ran into a problem. Reloading it usually fixes it.',
37
+ },
38
+ };
39
+ function MapErrorStateComponent({ error, onRetry, retryLabel = 'Try again' }) {
40
+ const theme = useTheme();
41
+ const copy = MESSAGES[error.reason] ?? MESSAGES.runtime;
42
+ return (_jsxs(View, { accessibilityRole: "alert", style: [styles.cover, { backgroundColor: theme.colors.backgroundSecondary }], testID: "map-error-state", children: [_jsx(RiErrorWarningLine, { width: 28, height: 28, fill: theme.colors.textSecondary }), _jsx(Text, { variant: "headline-semibold", style: [styles.centred, { color: theme.colors.text }], children: copy.title }), _jsx(Text, { variant: "body-2-regular", style: [styles.centred, { color: theme.colors.textSecondary }], children: copy.body }), onRetry ? (_jsx(Button, { appearance: "outline", tone: "neutral", size: "sm", leadingIcon: RiRefreshLine, onPress: onRetry, children: retryLabel })) : null] }));
43
+ }
44
+ export const MapErrorState = memo(MapErrorStateComponent);
45
+ const styles = StyleSheet.create({
46
+ cover: {
47
+ position: 'absolute',
48
+ top: 0,
49
+ right: 0,
50
+ bottom: 0,
51
+ left: 0,
52
+ alignItems: 'center',
53
+ justifyContent: 'center',
54
+ gap: 12,
55
+ paddingHorizontal: 24,
56
+ },
57
+ centred: { textAlign: 'center' },
58
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * `<MapPlugin>`, native — nothing. MapLibre Native has no custom-layer API, so
3
+ * an engine plugin is a web-only extension (`plugin.ts`).
4
+ */
5
+ import type { MapPluginProps } from './plugin';
6
+ export declare function MapPlugin<Props>(_props: MapPluginProps<Props>): null;
@@ -0,0 +1,3 @@
1
+ export function MapPlugin(_props) {
2
+ return null;
3
+ }
@@ -0,0 +1,2 @@
1
+ import type { MapPluginProps } from './plugin';
2
+ export declare function MapPlugin<Props>(props: MapPluginProps<Props>): import("react").JSX.Element | null;
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * `<MapPlugin>`, web — the one place an app's engine plugin is bound to the
4
+ * engine (`plugin.ts`).
5
+ *
6
+ * Attached once per engine and plugin (this file's binding), handed every new
7
+ * `props` through `update` (`PluginProps.web.tsx`), detached on unmount or
8
+ * when the engine is rebuilt. While attached it is consulted for presses, the
9
+ * cursor and the errors of the sources it owns.
10
+ */
11
+ import { useContext, useLayoutEffect, useState } from 'react';
12
+ import { WebEngineContext } from './engineContext.web';
13
+ import { PluginProps } from './PluginProps.web';
14
+ import { reportMapDefect } from './shared';
15
+ export function MapPlugin(props) {
16
+ const engine = useContext(WebEngineContext);
17
+ return engine ? _jsx(AttachedPlugin, { engine: engine, ...props }) : null;
18
+ }
19
+ function AttachedPlugin({ engine, plugin, props, }) {
20
+ const [attachment, setAttachment] = useState(null);
21
+ // Binding: `plugin` is attached to the engine for as long as both are.
22
+ // biome-ignore lint/correctness/useExhaustiveDependencies: a plugin is attached with the props of the render that attached it; later props reach it through `PluginProps`, never by re-attaching
23
+ useLayoutEffect(() => {
24
+ let attached = null;
25
+ try {
26
+ attached = plugin.attach(engine.map, props, { container: engine.container });
27
+ }
28
+ catch (cause) {
29
+ // A plugin that cannot attach costs the plugin, not the map.
30
+ reportMapDefect('plugin:attach', `A map plugin failed to attach: ${String(cause)}`);
31
+ return;
32
+ }
33
+ const registered = attached;
34
+ engine.plugins.add(registered);
35
+ setAttachment({ handle: attached, props });
36
+ return () => {
37
+ engine.plugins.delete(registered);
38
+ setAttachment(null);
39
+ attached?.detach();
40
+ };
41
+ }, [engine, plugin]);
42
+ return attachment ? (_jsx(PluginProps, { handle: attachment.handle, attachedWith: attachment.props, props: props })) : null;
43
+ }
@@ -0,0 +1,2 @@
1
+ import type { MapViewProps, MapViewRef } from './types';
2
+ export declare const MapView: import("react").ForwardRefExoticComponent<MapViewProps & import("react").RefAttributes<MapViewRef>>;