@trackunit/react-map-adapter-shared 0.0.4-alpha-9d327375fc1.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.
- package/index.cjs.js +2481 -0
- package/index.d.ts +1 -0
- package/index.esm.js +2397 -0
- package/package.json +21 -0
- package/src/LayerPort.d.ts +234 -0
- package/src/adapterContract.d.ts +189 -0
- package/src/antimeridianMerge.d.ts +22 -0
- package/src/colorUtils.d.ts +39 -0
- package/src/constants.d.ts +58 -0
- package/src/index.d.ts +21 -0
- package/src/initialViewportValidation.d.ts +5 -0
- package/src/interactionTypes.d.ts +79 -0
- package/src/layerApiTypes.d.ts +68 -0
- package/src/layerPortHelpers.d.ts +259 -0
- package/src/mapEventUtils.d.ts +3 -0
- package/src/mapStateUtils.d.ts +11 -0
- package/src/markerDomPortalStacking.d.ts +36 -0
- package/src/markerTypes.d.ts +352 -0
- package/src/primitiveMapTypes.d.ts +152 -0
- package/src/restrictBounds.d.ts +12 -0
- package/src/safeArea/_testUtils.d.ts +8 -0
- package/src/safeArea/attachSafeAreaHoverListeners.d.ts +27 -0
- package/src/safeArea/markerContract.d.ts +37 -0
- package/src/safeArea/polygon.d.ts +34 -0
- package/src/safeArea/safeAreaDebug.d.ts +35 -0
- package/src/safeArea/safePolygon.d.ts +36 -0
- package/src/safeArea/watchSafeAreaLeave.d.ts +23 -0
- package/src/shapeStyleDefaults.d.ts +45 -0
- package/src/types.d.ts +68 -0
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@trackunit/react-map-adapter-shared",
|
|
3
|
+
"version": "0.0.4-alpha-9d327375fc1.0",
|
|
4
|
+
"repository": "https://github.com/Trackunit/manager",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=24.x"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@trackunit/geo-json-utils": "1.14.25-alpha-9d327375fc1.0",
|
|
11
|
+
"@trackunit/ui-design-tokens": "1.13.24-alpha-9d327375fc1.0",
|
|
12
|
+
"es-toolkit": "^1.39.10",
|
|
13
|
+
"zod": "^3.25.76"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"react": "^19.0.0"
|
|
17
|
+
},
|
|
18
|
+
"module": "./index.esm.js",
|
|
19
|
+
"main": "./index.cjs.js",
|
|
20
|
+
"types": "./index.d.ts"
|
|
21
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import type { GeoJsonBbox } from "@trackunit/geo-json-utils";
|
|
2
|
+
import type { Entity } from "./interactionTypes";
|
|
3
|
+
import type { AdaptiveMarkerResolution, ClusterConfig, ClusterRenderConfig, ClusterRenderState, DomRenderState, GeoJsonFeatureCollection, RenderConfig, RouteStyle, ShapeInteractiveMode, ShapeStyle } from "./layerApiTypes";
|
|
4
|
+
type ReactNode = import("react").ReactNode;
|
|
5
|
+
/**
|
|
6
|
+
* Full interactive marker with clustering and adapter-managed entity events.
|
|
7
|
+
*/
|
|
8
|
+
export type InteractiveMarkerConfig = Readonly<{
|
|
9
|
+
kind: "marker";
|
|
10
|
+
id: string;
|
|
11
|
+
features: GeoJsonFeatureCollection;
|
|
12
|
+
markerRender: RenderConfig<unknown>;
|
|
13
|
+
clusterConfig: ClusterConfig<unknown> | null;
|
|
14
|
+
clusterRender: ClusterRenderConfig<unknown, unknown> | null;
|
|
15
|
+
clusterFeatures: GeoJsonFeatureCollection | null;
|
|
16
|
+
/**
|
|
17
|
+
* Populated by `<Layers>` when `markerRender.mode === "adaptive"` so adapters
|
|
18
|
+
* can split symbol vs DOM per feature (plan §8).
|
|
19
|
+
*/
|
|
20
|
+
adaptiveResolution?: AdaptiveMarkerResolution;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Positioned DOM content anchor — adapter only handles positioning,
|
|
24
|
+
* content manages its own interaction.
|
|
25
|
+
*/
|
|
26
|
+
export type AnchoredContentConfig = Readonly<{
|
|
27
|
+
kind: "anchor";
|
|
28
|
+
id: string;
|
|
29
|
+
features: GeoJsonFeatureCollection;
|
|
30
|
+
markerRender: RenderConfig<unknown>;
|
|
31
|
+
}>;
|
|
32
|
+
/**
|
|
33
|
+
* Configuration for a marker source.
|
|
34
|
+
* The adapter uses this to render markers and clusters on the map.
|
|
35
|
+
*
|
|
36
|
+
* - `"marker"` — interactive marker with clustering and entity events
|
|
37
|
+
* - `"anchor"` — positioned DOM content; content handles its own interaction
|
|
38
|
+
*/
|
|
39
|
+
export type MarkerSourceConfig = InteractiveMarkerConfig | AnchoredContentConfig;
|
|
40
|
+
/**
|
|
41
|
+
* Configuration for a shape source.
|
|
42
|
+
* The adapter uses this to render polygons, multipolygons, and lines.
|
|
43
|
+
*/
|
|
44
|
+
export type ShapeSourceConfig = Readonly<{
|
|
45
|
+
id: string;
|
|
46
|
+
features: GeoJsonFeatureCollection;
|
|
47
|
+
style: ShapeStyle;
|
|
48
|
+
/** Per-feature resolved styles, keyed by `String(feature.id)`. */
|
|
49
|
+
featureStyles?: ReadonlyMap<string, ShapeStyle>;
|
|
50
|
+
interactive: ShapeInteractiveMode;
|
|
51
|
+
}>;
|
|
52
|
+
/**
|
|
53
|
+
* Configuration for a route source.
|
|
54
|
+
*/
|
|
55
|
+
export type RouteSourceConfig = Readonly<{
|
|
56
|
+
id: string;
|
|
57
|
+
features: GeoJsonFeatureCollection;
|
|
58
|
+
style: RouteStyle;
|
|
59
|
+
interactive: boolean;
|
|
60
|
+
}>;
|
|
61
|
+
/**
|
|
62
|
+
* Configuration for an image overlay.
|
|
63
|
+
*/
|
|
64
|
+
export type ImageOverlayConfig = Readonly<{
|
|
65
|
+
id: string;
|
|
66
|
+
url: string;
|
|
67
|
+
imageBounds: GeoJsonBbox;
|
|
68
|
+
opacity: number;
|
|
69
|
+
}>;
|
|
70
|
+
/**
|
|
71
|
+
* A typed layer description — the declarative unit passed to `setSnapshot`.
|
|
72
|
+
*
|
|
73
|
+
* Discriminated on `layerType`, matching `LayerHandle.layerType`.
|
|
74
|
+
* Adding a new layer type is additive: extend this union, add a case in each
|
|
75
|
+
* adapter's layer-type handling (for example, its internal snapshot-application
|
|
76
|
+
* switch), and TypeScript's exhaustive check catches any adapter that missed
|
|
77
|
+
* the new type.
|
|
78
|
+
*/
|
|
79
|
+
export type MapLayer = ({
|
|
80
|
+
readonly layerType: "markers";
|
|
81
|
+
} & MarkerSourceConfig) | ({
|
|
82
|
+
readonly layerType: "shapes";
|
|
83
|
+
} & ShapeSourceConfig) | ({
|
|
84
|
+
readonly layerType: "route";
|
|
85
|
+
} & RouteSourceConfig) | ({
|
|
86
|
+
readonly layerType: "image-overlay";
|
|
87
|
+
} & ImageOverlayConfig);
|
|
88
|
+
/**
|
|
89
|
+
* Shape interaction state — which shape feature is currently selected/hovered.
|
|
90
|
+
* Both fields are null when nothing is selected/hovered.
|
|
91
|
+
*/
|
|
92
|
+
export type ShapeInteractionState = Readonly<{
|
|
93
|
+
handleId: string | null;
|
|
94
|
+
featureId: string | null;
|
|
95
|
+
}>;
|
|
96
|
+
/**
|
|
97
|
+
* Full desired rendering state passed to `LayerPort.setSnapshot`.
|
|
98
|
+
*
|
|
99
|
+
* The adapter owns the diff between the previous snapshot and this one,
|
|
100
|
+
* applying only the minimal changes needed to bring the map up to date.
|
|
101
|
+
*/
|
|
102
|
+
export type LayerSnapshot = Readonly<{
|
|
103
|
+
/** All active layers in desired render order. */
|
|
104
|
+
layers: ReadonlyArray<MapLayer>;
|
|
105
|
+
/** Currently selected shape feature. Both fields null when nothing selected. */
|
|
106
|
+
shapeSelection: ShapeInteractionState;
|
|
107
|
+
/** Currently hovered shape feature. Both fields null when nothing hovered. */
|
|
108
|
+
shapeHover: ShapeInteractionState;
|
|
109
|
+
}>;
|
|
110
|
+
/**
|
|
111
|
+
* Entity interaction event types emitted by the map adapter.
|
|
112
|
+
*/
|
|
113
|
+
export type EntityInteractionEvent = Readonly<{
|
|
114
|
+
type: "click";
|
|
115
|
+
entity: Entity;
|
|
116
|
+
}> | Readonly<{
|
|
117
|
+
type: "dblclick";
|
|
118
|
+
entity: Entity;
|
|
119
|
+
}> | Readonly<{
|
|
120
|
+
type: "hover-start";
|
|
121
|
+
entity: Entity;
|
|
122
|
+
}> | Readonly<{
|
|
123
|
+
type: "hover-end";
|
|
124
|
+
entity: Entity;
|
|
125
|
+
}>;
|
|
126
|
+
/**
|
|
127
|
+
* Handler for entity interaction events.
|
|
128
|
+
*/
|
|
129
|
+
export type EntityInteractionHandler = (event: EntityInteractionEvent) => void;
|
|
130
|
+
/**
|
|
131
|
+
* Describes a React portal target for DOM-rendered markers/clusters.
|
|
132
|
+
*
|
|
133
|
+
* The adapter creates an `AdvancedMarkerElement` (or equivalent) with an
|
|
134
|
+
* empty container div as its content. `<Layers>` subscribes to these
|
|
135
|
+
* descriptors and renders user-provided React content into each container
|
|
136
|
+
* via `createPortal()`. Routing to `markerRender` vs `clusterRender` is
|
|
137
|
+
* derived structurally from which {@link DomPortalStore} the descriptor
|
|
138
|
+
* came from (`markerPortals` vs `clusterPortals` on {@link LayerPort}).
|
|
139
|
+
*/
|
|
140
|
+
export type DomPortalDescriptor = Readonly<{
|
|
141
|
+
/** Unique React key for list rendering. Format: `${sourceId}:${featureId}`. */
|
|
142
|
+
key: string;
|
|
143
|
+
/** Owning marker layer id. Avoids parsing composite React keys. */
|
|
144
|
+
sourceId: string;
|
|
145
|
+
/** The marker's content element (portal target) */
|
|
146
|
+
container: HTMLElement;
|
|
147
|
+
/**
|
|
148
|
+
* Optional adapter hook for map-native marker stacking.
|
|
149
|
+
*
|
|
150
|
+
* Some adapters wrap `container` in their own marker object whose z-index
|
|
151
|
+
* controls sibling ordering, so the computed portal z-index must be applied
|
|
152
|
+
* outside the React portal target as well.
|
|
153
|
+
*/
|
|
154
|
+
setZIndex?: (zIndex: number) => void;
|
|
155
|
+
/** Feature ID for computing `DomRenderState` (selected/hovered) */
|
|
156
|
+
featureId: string;
|
|
157
|
+
/** Original data item (TItem or TCluster extracted from properties.__data) */
|
|
158
|
+
sourceData: unknown;
|
|
159
|
+
/**
|
|
160
|
+
* User-provided render function. For marker portals this is a
|
|
161
|
+
* `DomRenderConfig`/`AdaptiveRenderConfig.render` (state =
|
|
162
|
+
* {@link DomRenderState}); for cluster portals it is a
|
|
163
|
+
* {@link ClusterRenderConfig} `render` (state =
|
|
164
|
+
* {@link ClusterRenderState}). `<Layers>` constructs the matching state
|
|
165
|
+
* shape based on which store the descriptor came from (`markerPortals`
|
|
166
|
+
* vs `clusterPortals`) before invoking this function.
|
|
167
|
+
*
|
|
168
|
+
* Method syntax: parameter variance is bivariant so the narrower
|
|
169
|
+
* marker-only and cluster-only render signatures both assign into this
|
|
170
|
+
* field without re-wrapping.
|
|
171
|
+
*/
|
|
172
|
+
renderFn(item: unknown, state: DomRenderState | ClusterRenderState<unknown>): ReactNode;
|
|
173
|
+
}>;
|
|
174
|
+
/**
|
|
175
|
+
* Optional subscription interface for DOM portal rendering.
|
|
176
|
+
* Compatible with `useSyncExternalStore(subscribe, getSnapshot)`.
|
|
177
|
+
*/
|
|
178
|
+
export type DomPortalStore = Readonly<{
|
|
179
|
+
subscribe: (callback: () => void) => () => void;
|
|
180
|
+
getSnapshot: () => ReadonlyArray<DomPortalDescriptor>;
|
|
181
|
+
}>;
|
|
182
|
+
/**
|
|
183
|
+
* The adapter extension point for layer rendering.
|
|
184
|
+
*
|
|
185
|
+
* Each adapter provides a `LayerPort` that the `<Layers>` component uses
|
|
186
|
+
* to communicate the full desired map state. The adapter receives a
|
|
187
|
+
* `LayerSnapshot` describing all active layers and interaction state, and
|
|
188
|
+
* is responsible for diffing against its previous state to apply minimal
|
|
189
|
+
* changes to the underlying map engine.
|
|
190
|
+
*/
|
|
191
|
+
export type LayerPort = Readonly<{
|
|
192
|
+
/**
|
|
193
|
+
* Push the full desired rendering state to the adapter.
|
|
194
|
+
*
|
|
195
|
+
* Called whenever layers or interaction state changes. The adapter diffs
|
|
196
|
+
* the new snapshot against its previous state and applies only the
|
|
197
|
+
* necessary changes to the map engine. Passing an empty `layers` array
|
|
198
|
+
* removes all active sources.
|
|
199
|
+
*/
|
|
200
|
+
setSnapshot: (snapshot: LayerSnapshot) => void;
|
|
201
|
+
/** Subscribe to entity interaction events from the map (click, hover) */
|
|
202
|
+
onEntityInteraction: (handler: EntityInteractionHandler) => () => void;
|
|
203
|
+
/** Subscribe to clicks on the map background (no entity hit). */
|
|
204
|
+
onBackgroundClick: (handler: () => void) => () => void;
|
|
205
|
+
/**
|
|
206
|
+
* Subscribe to know when a specific source has been loaded and is ready
|
|
207
|
+
* for rendering. Used by `<Layers>` to defer showing edge labels until
|
|
208
|
+
* the parent shape source has finished drawing.
|
|
209
|
+
*
|
|
210
|
+
* Mapbox: fires after `sourcedata` with `isSourceLoaded` for the source.
|
|
211
|
+
* Google Maps: fires immediately (shapes are added synchronously).
|
|
212
|
+
*
|
|
213
|
+
* @param configId - The same `id` that was passed in a shape layer's config.
|
|
214
|
+
* The adapter maps this to its internal source identifier.
|
|
215
|
+
* @param callback - Called once when the source data is loaded and ready.
|
|
216
|
+
* @returns Unsubscribe function.
|
|
217
|
+
*/
|
|
218
|
+
onSourceReady: (configId: string, callback: () => void) => () => void;
|
|
219
|
+
/**
|
|
220
|
+
* Subscription for DOM-rendered individual markers (including adaptive DOM
|
|
221
|
+
* features). `<Layers>` routes these descriptors to `markerRender`.
|
|
222
|
+
*
|
|
223
|
+
* Null or undefined if the adapter doesn't support DOM rendering.
|
|
224
|
+
*/
|
|
225
|
+
markerPortals?: DomPortalStore | null;
|
|
226
|
+
/**
|
|
227
|
+
* Subscription for DOM-rendered clusters (server clusters and client-side
|
|
228
|
+
* clusters alike). `<Layers>` routes these descriptors to `clusterRender`.
|
|
229
|
+
*
|
|
230
|
+
* Null or undefined if the adapter doesn't support DOM rendering.
|
|
231
|
+
*/
|
|
232
|
+
clusterPortals?: DomPortalStore | null;
|
|
233
|
+
}>;
|
|
234
|
+
export {};
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { LayerPort } from "./LayerPort";
|
|
2
|
+
import type { CameraState, FitBoundsOptions, GeoJsonBbox, GeoJsonPosition, InitialViewport, MapComponentProps, MapEvent, MapEventHandler, MapStatus, MapTheme, MapType } from "./primitiveMapTypes";
|
|
3
|
+
type ComponentType<TProps> = import("react").ComponentType<TProps>;
|
|
4
|
+
/** Viewport overrides for adapter switching - both adapters support these */
|
|
5
|
+
type ViewportOverrides = Readonly<{
|
|
6
|
+
initialViewport: InitialViewport;
|
|
7
|
+
}>;
|
|
8
|
+
/**
|
|
9
|
+
* Props passed to adapter renderer components
|
|
10
|
+
*/
|
|
11
|
+
export type AdapterRendererProps = MapComponentProps & {
|
|
12
|
+
/** Adapter instance to connect to */
|
|
13
|
+
readonly adapterInstance: AdapterInstance;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Adapter instance - the runtime interface for controlling the map
|
|
17
|
+
* Created by adapter factory, used internally by useMap
|
|
18
|
+
* Generic over the config type for type-safe config access
|
|
19
|
+
*/
|
|
20
|
+
export type AdapterInstance<TConfig extends Record<string, unknown> = Record<string, unknown>> = Readonly<{
|
|
21
|
+
/**
|
|
22
|
+
* Get the adapter configuration
|
|
23
|
+
*/
|
|
24
|
+
getConfig: () => TConfig;
|
|
25
|
+
/**
|
|
26
|
+
* Get current camera state (center, zoom, bounds, isIdle).
|
|
27
|
+
* This snapshot belongs to the high-frequency camera channel and can change
|
|
28
|
+
* continuously during panning/zooming.
|
|
29
|
+
*/
|
|
30
|
+
getCameraState: () => CameraState;
|
|
31
|
+
/**
|
|
32
|
+
* Get current map status (isReady, initializationFailed, appearance, tileSize).
|
|
33
|
+
* This snapshot belongs to the low-frequency status channel used by `api.state`.
|
|
34
|
+
*/
|
|
35
|
+
getStatus: () => MapStatus;
|
|
36
|
+
/**
|
|
37
|
+
* Subscribe to camera state changes only.
|
|
38
|
+
* Adapters should notify this channel only after replacing the cached
|
|
39
|
+
* CameraState snapshot with a field-wise different value.
|
|
40
|
+
*/
|
|
41
|
+
subscribeCamera: (listener: () => void) => () => void;
|
|
42
|
+
/**
|
|
43
|
+
* Subscribe to status changes only.
|
|
44
|
+
* Adapters should notify this channel only after replacing the cached MapStatus
|
|
45
|
+
* snapshot with a field-wise different value.
|
|
46
|
+
*/
|
|
47
|
+
subscribeStatus: (listener: () => void) => () => void;
|
|
48
|
+
/**
|
|
49
|
+
* Set the map center
|
|
50
|
+
*/
|
|
51
|
+
setCenter: (center: GeoJsonPosition) => Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Set the zoom level (absolute)
|
|
54
|
+
*/
|
|
55
|
+
setZoom: (zoom: number) => Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Adjust zoom by delta (relative)
|
|
58
|
+
*/
|
|
59
|
+
zoomBy: (delta: number) => Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Fit the map to bounds
|
|
62
|
+
*/
|
|
63
|
+
fitBounds: (bounds: GeoJsonBbox, options?: FitBoundsOptions) => Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Pan to a position with animation
|
|
66
|
+
*/
|
|
67
|
+
panTo: (center: GeoJsonPosition) => Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Pan the map by pixel offsets (for keyboard navigation)
|
|
70
|
+
* Uses screen coordinate convention where origin (0,0) is top-left:
|
|
71
|
+
*
|
|
72
|
+
* @param deltaX - Horizontal pan amount in pixels (positive = right, negative = left)
|
|
73
|
+
* @param deltaY - Vertical pan amount in pixels (positive = down, negative = up)
|
|
74
|
+
*/
|
|
75
|
+
panBy: (deltaX: number, deltaY: number) => Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Set the map type (base layer)
|
|
78
|
+
*/
|
|
79
|
+
setMapType: (type: MapType) => Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Set the map theme (visual appearance)
|
|
82
|
+
*/
|
|
83
|
+
setTheme: (theme: MapTheme) => Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Set whether road labels are shown over satellite/hybrid imagery
|
|
86
|
+
*/
|
|
87
|
+
setShowRoads: (showRoads: boolean) => Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Subscribe to map events
|
|
90
|
+
*/
|
|
91
|
+
on: <TEventType extends MapEvent["type"]>(event: TEventType, handler: MapEventHandler<TEventType>) => () => void;
|
|
92
|
+
/**
|
|
93
|
+
* Mark map initialization as permanently failed (e.g. provider API could not load).
|
|
94
|
+
* Sets `initializationFailed` on state and `isReady` false, then notifies subscribers.
|
|
95
|
+
*/
|
|
96
|
+
notifyInitializationFailed: () => void;
|
|
97
|
+
/**
|
|
98
|
+
* Cleanup resources
|
|
99
|
+
*/
|
|
100
|
+
destroy: () => void;
|
|
101
|
+
/**
|
|
102
|
+
* Layer rendering capabilities.
|
|
103
|
+
* Null if this adapter does not yet support layer rendering.
|
|
104
|
+
* Adapters can implement this incrementally.
|
|
105
|
+
*/
|
|
106
|
+
layers: LayerPort | null;
|
|
107
|
+
}>;
|
|
108
|
+
/**
|
|
109
|
+
* Pixel insets representing areas reserved by the map provider's own UI
|
|
110
|
+
* (logo, attribution links, etc.). Controls use these to avoid overlapping
|
|
111
|
+
* provider chrome.
|
|
112
|
+
*/
|
|
113
|
+
export type SafeAreaInsets = Readonly<{
|
|
114
|
+
top: number;
|
|
115
|
+
right: number;
|
|
116
|
+
bottom: number;
|
|
117
|
+
left: number;
|
|
118
|
+
}>;
|
|
119
|
+
/**
|
|
120
|
+
* Base adapter configuration fields returned by adapter factory functions.
|
|
121
|
+
* This is the shape that adapter factories produce before `defineAdapter`
|
|
122
|
+
* injects the `derive` method.
|
|
123
|
+
*/
|
|
124
|
+
export type AdapterConfigBase<TConfig extends Record<string, unknown> = Record<string, unknown>> = Readonly<{
|
|
125
|
+
/** Unique name for this adapter */
|
|
126
|
+
name: string;
|
|
127
|
+
/** Adapter-specific configuration */
|
|
128
|
+
config: TConfig;
|
|
129
|
+
/**
|
|
130
|
+
* Safe area insets declared by the map provider.
|
|
131
|
+
* Provide pixel values for sides where the provider renders its own UI,
|
|
132
|
+
* or `null` to indicate no safe area concerns.
|
|
133
|
+
*/
|
|
134
|
+
safeAreaInsets: SafeAreaInsets | null;
|
|
135
|
+
/** Factory function to create the adapter instance */
|
|
136
|
+
createInstance: () => AdapterInstance;
|
|
137
|
+
/** React component that renders the map */
|
|
138
|
+
Renderer: ComponentType<AdapterRendererProps>;
|
|
139
|
+
}>;
|
|
140
|
+
/**
|
|
141
|
+
* Full adapter configuration with the `derive` method for creating variants.
|
|
142
|
+
* Generic over the config type for type-safe adapter-specific options.
|
|
143
|
+
*
|
|
144
|
+
* Use `derive(overrides)` to create a new adapter config with merged settings,
|
|
145
|
+
* e.g. for spawning preview map instances with a different theme or map type.
|
|
146
|
+
*/
|
|
147
|
+
export type AdapterConfig<TConfig extends Record<string, unknown> = Record<string, unknown>> = AdapterConfigBase<TConfig> & {
|
|
148
|
+
/**
|
|
149
|
+
* Create a variant of this adapter config with merged overrides.
|
|
150
|
+
* Returns a new `AdapterConfig` that uses the same adapter factory
|
|
151
|
+
* but with the overridden config values merged in.
|
|
152
|
+
*
|
|
153
|
+
* Uses method syntax intentionally — bivariant method parameters
|
|
154
|
+
* let `AdapterConfig<TConfig>` remain assignable to `AdapterConfig`
|
|
155
|
+
* (default `Record<string, unknown>`) so we can store it on `MapApi`
|
|
156
|
+
* without requiring a generic on `MapApi` itself.
|
|
157
|
+
*
|
|
158
|
+
* @param overrides - Partial config to merge into the existing config
|
|
159
|
+
* @returns A new AdapterConfig with the merged configuration
|
|
160
|
+
* @example
|
|
161
|
+
* ```typescript
|
|
162
|
+
* // Create a dark-themed variant for a preview map
|
|
163
|
+
* const darkPreview = api.adapterConfig.derive({ theme: "dark" });
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
derive(overrides: Partial<TConfig> | ViewportOverrides): AdapterConfig<TConfig>;
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Helper to define an adapter factory with proper type inference.
|
|
170
|
+
*
|
|
171
|
+
* Automatically injects the `derive` method on every `AdapterConfig` it produces.
|
|
172
|
+
* Adapter implementations don't need to handle `derive` -- it's added transparently
|
|
173
|
+
* by closing over the original factory function.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```typescript
|
|
177
|
+
* export const googleMapsAdapter = defineAdapter((config: GoogleMapsConfig) => ({
|
|
178
|
+
* name: "google",
|
|
179
|
+
* config,
|
|
180
|
+
* createInstance: () => createGoogleMapsInstance(config),
|
|
181
|
+
* Renderer: GoogleMapsRenderer,
|
|
182
|
+
* }));
|
|
183
|
+
*
|
|
184
|
+
* // derive is available automatically:
|
|
185
|
+
* const darkAdapter = googleMapsAdapter({ apiKey, theme: "light" }).derive({ theme: "dark" });
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
export declare const defineAdapter: <TConfig extends Record<string, unknown>>(factory: (config: TConfig) => AdapterConfigBase<TConfig>) => ((config: TConfig) => AdapterConfig<TConfig>);
|
|
189
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { GeoJsonFeatureCollection } from "@trackunit/geo-json-utils";
|
|
2
|
+
/**
|
|
3
|
+
* Pre-process a GeoJSON FeatureCollection for antimeridian-aware rendering.
|
|
4
|
+
*
|
|
5
|
+
* - MultiPolygon features that represent a single polygon split at the antimeridian
|
|
6
|
+
* (per RFC 7946 Section 3.1.9) are merged back into single Polygons with
|
|
7
|
+
* unwrapped coordinates (lng values may exceed 180). Interior rings that are also
|
|
8
|
+
* split at ±180° with matching seam latitudes are stitched the same way as exteriors.
|
|
9
|
+
*
|
|
10
|
+
* - MultiLineString features that represent one route split at ±180° are merged
|
|
11
|
+
* into a single LineString with the same unwrapping. This eliminates visible strokes
|
|
12
|
+
* at the antimeridian and ensures hover/selection treats each shape as one entity.
|
|
13
|
+
*
|
|
14
|
+
* The input collection is never mutated. Features that don't need merging
|
|
15
|
+
* are returned by reference.
|
|
16
|
+
*
|
|
17
|
+
* **Important:** The output contains coordinates outside [-180, 180] and is
|
|
18
|
+
* intended only for rendering via Google Maps or Mapbox, which handle
|
|
19
|
+
* world-wrapped coordinates natively. Do not pass the output to GeoJSON
|
|
20
|
+
* validators or store it.
|
|
21
|
+
*/
|
|
22
|
+
export declare const mergeAntimeridianFeatures: (features: GeoJsonFeatureCollection) => GeoJsonFeatureCollection;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mix two CSS colors using the browser's `color-mix(in srgb)` function.
|
|
3
|
+
*
|
|
4
|
+
* @param color1 - First color (any valid CSS color string)
|
|
5
|
+
* @param color2 - Second color (any valid CSS color string)
|
|
6
|
+
* @param percentage - Percentage of `color1` in the mix (0–100)
|
|
7
|
+
* @returns Resolved color as an `rgb()` string, or `color1` if resolution fails
|
|
8
|
+
*/
|
|
9
|
+
export declare const mixColor: (color1: string, color2: string, percentage: number) => string;
|
|
10
|
+
/**
|
|
11
|
+
* Darken a CSS color by mixing it with black.
|
|
12
|
+
*
|
|
13
|
+
* @param color - Any valid CSS color string
|
|
14
|
+
* @param amount - Darkening intensity from 0 (no change) to 100 (pure black)
|
|
15
|
+
*/
|
|
16
|
+
export declare const darkenColor: (color: string, amount: number) => string;
|
|
17
|
+
/**
|
|
18
|
+
* Lighten a CSS color by mixing it with white.
|
|
19
|
+
*
|
|
20
|
+
* @param color - Any valid CSS color string
|
|
21
|
+
* @param amount - Lightening intensity from 0 (no change) to 100 (pure white)
|
|
22
|
+
*/
|
|
23
|
+
export declare const lightenColor: (color: string, amount: number) => string;
|
|
24
|
+
/**
|
|
25
|
+
* Resolve a CSS color and apply an opacity multiplier to its alpha channel.
|
|
26
|
+
*
|
|
27
|
+
* Unlike setting `element.style.opacity`, this only affects the individual
|
|
28
|
+
* color value — useful when fill and stroke need independent opacity.
|
|
29
|
+
*
|
|
30
|
+
* @param color - Any valid CSS color string
|
|
31
|
+
* @param opacity - Opacity multiplier from 0 to 1 (multiplied with existing alpha)
|
|
32
|
+
* @returns `rgba()` string with the combined alpha, or the original color if resolution fails
|
|
33
|
+
*/
|
|
34
|
+
export declare const colorWithOpacity: (color: string, opacity: number) => string;
|
|
35
|
+
/**
|
|
36
|
+
* Clear the resolved-color cache and detach the probe element.
|
|
37
|
+
* Exposed for test teardown only — not part of the public API.
|
|
38
|
+
*/
|
|
39
|
+
export declare const resetColorUtilsForTesting: () => void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* World bounds for Web Mercator projection.
|
|
3
|
+
* GeoJSON format: [minLng, minLat, maxLng, maxLat]
|
|
4
|
+
*/
|
|
5
|
+
export declare const WORLD_BBOX: readonly [-180, number, 180, number];
|
|
6
|
+
/**
|
|
7
|
+
* Default zoom level when not specified
|
|
8
|
+
*/
|
|
9
|
+
export declare const DEFAULT_ZOOM = 2;
|
|
10
|
+
/**
|
|
11
|
+
* Default center when not specified (Atlantic Ocean)
|
|
12
|
+
* Position format: { lat: number, lng: number }
|
|
13
|
+
*/
|
|
14
|
+
export declare const DEFAULT_CENTER: {
|
|
15
|
+
readonly lat: 0;
|
|
16
|
+
readonly lng: 0;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Minimum zoom level supported across providers
|
|
20
|
+
* Both Mapbox and Google Maps support zoom level 0
|
|
21
|
+
*/
|
|
22
|
+
export declare const MIN_ZOOM = 0;
|
|
23
|
+
/**
|
|
24
|
+
* Maximum zoom level supported across providers
|
|
25
|
+
* Both Mapbox and Google Maps support zoom level 22
|
|
26
|
+
*/
|
|
27
|
+
export declare const MAX_ZOOM = 22;
|
|
28
|
+
/**
|
|
29
|
+
* Amount to pan the map when using arrow keys (in pixels)
|
|
30
|
+
*/
|
|
31
|
+
export declare const KEYBOARD_PAN_AMOUNT = 100;
|
|
32
|
+
/**
|
|
33
|
+
* Amount to zoom when using +/- keys
|
|
34
|
+
*/
|
|
35
|
+
export declare const KEYBOARD_ZOOM_AMOUNT = 1;
|
|
36
|
+
/**
|
|
37
|
+
* CSS cursor values used across map adapters.
|
|
38
|
+
* Centralised so both Mapbox and Google stay in sync.
|
|
39
|
+
*/
|
|
40
|
+
export declare const MAP_CURSORS: {
|
|
41
|
+
readonly default: "default";
|
|
42
|
+
readonly interactive: "pointer";
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Estimate zoom level from a bounding box using Web Mercator projection.
|
|
46
|
+
* Both Google Maps and Mapbox use the same 256px tile / 2^zoom formula,
|
|
47
|
+
* so a simple `log2(360 / lonSpan)` gives a close approximation without
|
|
48
|
+
* needing the container size. Used by adapter constructors to provide
|
|
49
|
+
* a reasonable initial zoom before the native map instance is created.
|
|
50
|
+
*/
|
|
51
|
+
export declare const estimateZoomFromBounds: (bounds: readonly [number, number, number, number]) => number;
|
|
52
|
+
/**
|
|
53
|
+
* Compute the visual center of a bounding box in Web Mercator projection.
|
|
54
|
+
* The arithmetic mean of lat/lng gives a wrong center because Mercator
|
|
55
|
+
* stretches latitudes non-linearly. This converts to Mercator Y, averages
|
|
56
|
+
* there, and converts back — matching what Google Maps and Mapbox render.
|
|
57
|
+
*/
|
|
58
|
+
export declare const mercatorCenterFromBounds: (bounds: readonly [number, number, number, number]) => readonly [number, number];
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export * from "./adapterContract";
|
|
2
|
+
export * from "./antimeridianMerge";
|
|
3
|
+
export * from "./colorUtils";
|
|
4
|
+
export * from "./constants";
|
|
5
|
+
export * from "./initialViewportValidation";
|
|
6
|
+
export * from "./interactionTypes";
|
|
7
|
+
export * from "./layerApiTypes";
|
|
8
|
+
export * from "./LayerPort";
|
|
9
|
+
export * from "./layerPortHelpers";
|
|
10
|
+
export * from "./mapEventUtils";
|
|
11
|
+
export * from "./mapStateUtils";
|
|
12
|
+
export * from "./primitiveMapTypes";
|
|
13
|
+
export * from "./restrictBounds";
|
|
14
|
+
export * from "./safeArea/attachSafeAreaHoverListeners";
|
|
15
|
+
export * from "./safeArea/markerContract";
|
|
16
|
+
export * from "./safeArea/polygon";
|
|
17
|
+
export * from "./safeArea/safeAreaDebug";
|
|
18
|
+
export * from "./safeArea/safePolygon";
|
|
19
|
+
export * from "./safeArea/watchSafeAreaLeave";
|
|
20
|
+
export * from "./shapeStyleDefaults";
|
|
21
|
+
export * from "./types";
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { GeoJsonBbox, GeoJsonGeometry, GeoJsonPosition } from "@trackunit/geo-json-utils";
|
|
2
|
+
/**
|
|
3
|
+
* A marker entity -- a single point of interest on the map.
|
|
4
|
+
* May be contained within a cluster when clustering is active.
|
|
5
|
+
*/
|
|
6
|
+
export type MarkerEntity = Readonly<{
|
|
7
|
+
type: "marker";
|
|
8
|
+
id: string;
|
|
9
|
+
position: GeoJsonPosition | null;
|
|
10
|
+
/** If this marker is inside a cluster, the cluster's ID. Null when unclustered. */
|
|
11
|
+
clusterId: string | null;
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* A cluster entity -- a group of markers aggregated into a single point.
|
|
15
|
+
* Produced by either client-side or server-side clustering.
|
|
16
|
+
*/
|
|
17
|
+
export type ClusterEntity = Readonly<{
|
|
18
|
+
type: "cluster";
|
|
19
|
+
id: string;
|
|
20
|
+
position: GeoJsonPosition;
|
|
21
|
+
/** IDs of markers contained in this cluster */
|
|
22
|
+
markerIds: ReadonlyArray<string>;
|
|
23
|
+
/**
|
|
24
|
+
* Geographic extent as GeoJSON bbox when known (eg. server clusters from backend)
|
|
25
|
+
*/
|
|
26
|
+
bbox: GeoJsonBbox | null;
|
|
27
|
+
}>;
|
|
28
|
+
/**
|
|
29
|
+
* The kind of geometry a shape entity represents.
|
|
30
|
+
* Derived from GeoJSON geometry types when constructing entities in adapters.
|
|
31
|
+
*/
|
|
32
|
+
export type ShapeType = "polygon" | "line" | "point";
|
|
33
|
+
/**
|
|
34
|
+
* A shape entity -- a polygon, line, or point on the map.
|
|
35
|
+
* When the interaction originates from an anchor (e.g. edge label),
|
|
36
|
+
* `handleId` carries the layer handle the feature belongs to.
|
|
37
|
+
*/
|
|
38
|
+
export type ShapeEntity = Readonly<{
|
|
39
|
+
type: "shape";
|
|
40
|
+
id: string;
|
|
41
|
+
shapeType: ShapeType;
|
|
42
|
+
handleId?: string;
|
|
43
|
+
}>;
|
|
44
|
+
/**
|
|
45
|
+
* A route entity -- a polyline with waypoints.
|
|
46
|
+
*/
|
|
47
|
+
export type RouteEntity = Readonly<{
|
|
48
|
+
type: "route";
|
|
49
|
+
id: string;
|
|
50
|
+
/** The waypoints that define this route */
|
|
51
|
+
waypoints: ReadonlyArray<GeoJsonPosition>;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Discriminated union of all interactive map entities.
|
|
55
|
+
* Used for hover/select interaction across all layer types.
|
|
56
|
+
*/
|
|
57
|
+
export type Entity = Readonly<MarkerEntity | ClusterEntity | ShapeEntity | RouteEntity>;
|
|
58
|
+
/**
|
|
59
|
+
* Shared interaction state for all layers.
|
|
60
|
+
* At most one entity can be selected and one hovered at any time.
|
|
61
|
+
* Selecting a marker clears any previously selected polygon, and vice versa.
|
|
62
|
+
*/
|
|
63
|
+
export type MapInteractionState = Readonly<{
|
|
64
|
+
selectedEntity: Entity | null;
|
|
65
|
+
hoveredEntity: Entity | null;
|
|
66
|
+
}>;
|
|
67
|
+
/**
|
|
68
|
+
* GeoJSON geometry types excluding GeometryCollection, which is a
|
|
69
|
+
* container of mixed primitives rather than a renderable shape itself.
|
|
70
|
+
*/
|
|
71
|
+
export type GeoJsonPrimitiveGeometryType = Exclude<GeoJsonGeometry["type"], "GeometryCollection">;
|
|
72
|
+
/**
|
|
73
|
+
* Maps a primitive GeoJSON geometry type to the ShapeType discriminant.
|
|
74
|
+
*/
|
|
75
|
+
export declare const geometryTypeToShapeType: (geometryType: GeoJsonPrimitiveGeometryType) => ShapeType;
|
|
76
|
+
/**
|
|
77
|
+
* Initial interaction state -- nothing selected or hovered.
|
|
78
|
+
*/
|
|
79
|
+
export declare const INITIAL_INTERACTION_STATE: MapInteractionState;
|