gistda-sphere-react 1.0.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 (41) hide show
  1. package/README.md +827 -0
  2. package/dist/index.d.mts +1081 -0
  3. package/dist/index.d.ts +1081 -0
  4. package/dist/index.js +2057 -0
  5. package/dist/index.mjs +2013 -0
  6. package/package.json +70 -0
  7. package/src/__tests__/Layer.test.tsx +133 -0
  8. package/src/__tests__/Marker.test.tsx +183 -0
  9. package/src/__tests__/SphereContext.test.tsx +120 -0
  10. package/src/__tests__/SphereMap.test.tsx +240 -0
  11. package/src/__tests__/geometry.test.tsx +454 -0
  12. package/src/__tests__/hooks.test.tsx +173 -0
  13. package/src/__tests__/setup.ts +204 -0
  14. package/src/__tests__/useMapControls.test.tsx +168 -0
  15. package/src/__tests__/useOverlays.test.tsx +265 -0
  16. package/src/__tests__/useRoute.test.tsx +219 -0
  17. package/src/__tests__/useSearch.test.tsx +205 -0
  18. package/src/__tests__/useTags.test.tsx +179 -0
  19. package/src/components/Circle.tsx +189 -0
  20. package/src/components/Dot.tsx +150 -0
  21. package/src/components/Layer.tsx +177 -0
  22. package/src/components/Marker.tsx +204 -0
  23. package/src/components/Polygon.tsx +223 -0
  24. package/src/components/Polyline.tsx +211 -0
  25. package/src/components/Popup.tsx +130 -0
  26. package/src/components/Rectangle.tsx +194 -0
  27. package/src/components/SphereMap.tsx +315 -0
  28. package/src/components/index.ts +18 -0
  29. package/src/context/MapContext.tsx +41 -0
  30. package/src/context/SphereContext.tsx +348 -0
  31. package/src/context/index.ts +15 -0
  32. package/src/hooks/index.ts +42 -0
  33. package/src/hooks/useMapEvent.ts +66 -0
  34. package/src/hooks/useOverlays.ts +278 -0
  35. package/src/hooks/useRoute.ts +232 -0
  36. package/src/hooks/useSearch.ts +143 -0
  37. package/src/hooks/useSphere.ts +18 -0
  38. package/src/hooks/useTags.ts +129 -0
  39. package/src/index.ts +124 -0
  40. package/src/types/index.ts +1 -0
  41. package/src/types/sphere.ts +671 -0
@@ -0,0 +1,130 @@
1
+ import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
2
+ import { useMapContext } from "../context/MapContext";
3
+ import { useSphereContext } from "../context/SphereContext";
4
+ import type { Location, PopupOptions, Size, SpherePopup } from "../types";
5
+
6
+ export interface PopupProps {
7
+ position: Location;
8
+ title?: string;
9
+ detail?: string;
10
+ loadDetail?: (element: HTMLElement) => void;
11
+ html?: string;
12
+ loadHtml?: (element: HTMLElement) => void;
13
+ size?: Size;
14
+ closable?: boolean;
15
+ onClose?: (popup: SpherePopup) => void;
16
+ }
17
+
18
+ export interface PopupRef {
19
+ getPopup(): SpherePopup | null;
20
+ setPosition(location: Location): void;
21
+ setTitle(title: string): void;
22
+ setDetail(detail: string): void;
23
+ getElement(): HTMLElement | null;
24
+ }
25
+
26
+ export const Popup = forwardRef<PopupRef, PopupProps>(function Popup(
27
+ {
28
+ position,
29
+ title,
30
+ detail,
31
+ loadDetail,
32
+ html,
33
+ loadHtml,
34
+ size,
35
+ closable = true,
36
+ onClose,
37
+ },
38
+ ref
39
+ ) {
40
+ const { map, isReady } = useMapContext();
41
+ const { sphere } = useSphereContext();
42
+ const popupRef = useRef<SpherePopup | null>(null);
43
+ const onCloseRef = useRef(onClose);
44
+
45
+ useEffect(() => {
46
+ onCloseRef.current = onClose;
47
+ }, [onClose]);
48
+
49
+ useEffect(() => {
50
+ if (!(isReady && map && sphere)) {
51
+ return;
52
+ }
53
+
54
+ const options: PopupOptions = {
55
+ closable,
56
+ };
57
+
58
+ if (title) {
59
+ options.title = title;
60
+ }
61
+ if (detail) {
62
+ options.detail = detail;
63
+ }
64
+ if (loadDetail) {
65
+ options.loadDetail = loadDetail;
66
+ }
67
+ if (html) {
68
+ options.html = html;
69
+ }
70
+ if (loadHtml) {
71
+ options.loadHtml = loadHtml;
72
+ }
73
+ if (size) {
74
+ options.size = size;
75
+ }
76
+
77
+ const popup = new sphere.Popup(position, options);
78
+ popupRef.current = popup;
79
+
80
+ map.Overlays.add(popup);
81
+
82
+ const handlePopupClose = (closedPopup: SpherePopup) => {
83
+ if (closedPopup === popup) {
84
+ onCloseRef.current?.(popup);
85
+ }
86
+ };
87
+
88
+ map.Event.bind("popupClose", handlePopupClose);
89
+
90
+ return () => {
91
+ map.Event.unbind("popupClose", handlePopupClose);
92
+ map.Overlays.remove(popup);
93
+ popupRef.current = null;
94
+ };
95
+ }, [
96
+ isReady,
97
+ map,
98
+ sphere,
99
+ position,
100
+ title,
101
+ detail,
102
+ loadDetail,
103
+ html,
104
+ loadHtml,
105
+ size,
106
+ closable,
107
+ ]);
108
+
109
+ useImperativeHandle(
110
+ ref,
111
+ () => ({
112
+ getPopup: () => popupRef.current,
113
+ setPosition: (location: Location) => {
114
+ popupRef.current?.location(location);
115
+ },
116
+ setTitle: (newTitle: string) => {
117
+ popupRef.current?.title(newTitle);
118
+ },
119
+ setDetail: (newDetail: string) => {
120
+ popupRef.current?.detail(newDetail);
121
+ },
122
+ getElement: () => popupRef.current?.element() ?? null,
123
+ }),
124
+ []
125
+ );
126
+
127
+ return null;
128
+ });
129
+
130
+ export default Popup;
@@ -0,0 +1,194 @@
1
+ import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
2
+ import { useMapContext } from "../context/MapContext";
3
+ import { useSphereContext } from "../context/SphereContext";
4
+ import type {
5
+ Bound,
6
+ GeometryOptions,
7
+ LineStyleType,
8
+ Location,
9
+ PopupOptions,
10
+ Range,
11
+ Size,
12
+ SphereRectangle,
13
+ } from "../types";
14
+
15
+ export interface RectangleProps {
16
+ position: Location;
17
+ size: Size | Location;
18
+ title?: string;
19
+ detail?: string;
20
+ popup?: PopupOptions;
21
+ visibleRange?: Range;
22
+ lineWidth?: number;
23
+ lineColor?: string;
24
+ fillColor?: string;
25
+ lineStyle?: LineStyleType;
26
+ clickable?: boolean;
27
+ draggable?: boolean;
28
+ editable?: boolean;
29
+ zIndex?: number;
30
+ onClick?: (rectangle: SphereRectangle) => void;
31
+ onDrag?: (rectangle: SphereRectangle) => void;
32
+ onDrop?: (rectangle: SphereRectangle) => void;
33
+ }
34
+
35
+ export interface RectangleRef {
36
+ getRectangle(): SphereRectangle | null;
37
+ togglePopup(show?: boolean, location?: Location): void;
38
+ getBound(): Bound | null;
39
+ getArea(language?: string): number | string | null;
40
+ updateStyle(options: Partial<GeometryOptions>): void;
41
+ }
42
+
43
+ export const Rectangle = forwardRef<RectangleRef, RectangleProps>(
44
+ function Rectangle(
45
+ {
46
+ position,
47
+ size,
48
+ title,
49
+ detail,
50
+ popup,
51
+ visibleRange,
52
+ lineWidth,
53
+ lineColor,
54
+ fillColor,
55
+ lineStyle,
56
+ clickable,
57
+ draggable,
58
+ editable,
59
+ zIndex,
60
+ onClick,
61
+ onDrag,
62
+ onDrop,
63
+ },
64
+ ref
65
+ ) {
66
+ const { map, isReady } = useMapContext();
67
+ const { sphere } = useSphereContext();
68
+ const rectangleRef = useRef<SphereRectangle | null>(null);
69
+ const callbacksRef = useRef({ onClick, onDrag, onDrop });
70
+
71
+ useEffect(() => {
72
+ callbacksRef.current = { onClick, onDrag, onDrop };
73
+ }, [onClick, onDrag, onDrop]);
74
+
75
+ useEffect(() => {
76
+ if (!(isReady && map && sphere)) {
77
+ return;
78
+ }
79
+
80
+ const options: GeometryOptions = {};
81
+
82
+ if (title) {
83
+ options.title = title;
84
+ }
85
+ if (detail) {
86
+ options.detail = detail;
87
+ }
88
+ if (popup) {
89
+ options.popup = popup;
90
+ }
91
+ if (visibleRange) {
92
+ options.visibleRange = visibleRange;
93
+ }
94
+ if (typeof lineWidth === "number") {
95
+ options.lineWidth = lineWidth;
96
+ }
97
+ if (lineColor) {
98
+ options.lineColor = lineColor;
99
+ }
100
+ if (fillColor) {
101
+ options.fillColor = fillColor;
102
+ }
103
+ if (lineStyle) {
104
+ options.lineStyle = lineStyle;
105
+ }
106
+ if (typeof clickable === "boolean") {
107
+ options.clickable = clickable;
108
+ }
109
+ if (typeof draggable === "boolean") {
110
+ options.draggable = draggable;
111
+ }
112
+ if (typeof editable === "boolean") {
113
+ options.editable = editable;
114
+ }
115
+ if (typeof zIndex === "number") {
116
+ options.zIndex = zIndex;
117
+ }
118
+
119
+ const rectangle = new sphere.Rectangle(position, size, options);
120
+ rectangleRef.current = rectangle;
121
+
122
+ map.Overlays.add(rectangle);
123
+
124
+ const handleOverlayClick = (data: { overlay: SphereRectangle }) => {
125
+ if (data.overlay === rectangle) {
126
+ callbacksRef.current.onClick?.(rectangle);
127
+ }
128
+ };
129
+
130
+ const handleOverlayDrag = (overlay: SphereRectangle) => {
131
+ if (overlay === rectangle) {
132
+ callbacksRef.current.onDrag?.(rectangle);
133
+ }
134
+ };
135
+
136
+ const handleOverlayDrop = (overlay: SphereRectangle) => {
137
+ if (overlay === rectangle) {
138
+ callbacksRef.current.onDrop?.(rectangle);
139
+ }
140
+ };
141
+
142
+ map.Event.bind("overlayClick", handleOverlayClick);
143
+ map.Event.bind("overlayDrag", handleOverlayDrag);
144
+ map.Event.bind("overlayDrop", handleOverlayDrop);
145
+
146
+ return () => {
147
+ map.Event.unbind("overlayClick", handleOverlayClick);
148
+ map.Event.unbind("overlayDrag", handleOverlayDrag);
149
+ map.Event.unbind("overlayDrop", handleOverlayDrop);
150
+ map.Overlays.remove(rectangle);
151
+ rectangleRef.current = null;
152
+ };
153
+ }, [
154
+ isReady,
155
+ map,
156
+ sphere,
157
+ position,
158
+ size,
159
+ title,
160
+ detail,
161
+ popup,
162
+ visibleRange,
163
+ lineWidth,
164
+ lineColor,
165
+ fillColor,
166
+ lineStyle,
167
+ clickable,
168
+ draggable,
169
+ editable,
170
+ zIndex,
171
+ ]);
172
+
173
+ useImperativeHandle(
174
+ ref,
175
+ () => ({
176
+ getRectangle: () => rectangleRef.current,
177
+ togglePopup: (show?: boolean, location?: Location) => {
178
+ rectangleRef.current?.pop(show, location);
179
+ },
180
+ getBound: () => rectangleRef.current?.bound() ?? null,
181
+ getArea: (language?: string) =>
182
+ rectangleRef.current?.size(language) ?? null,
183
+ updateStyle: (options: Partial<GeometryOptions>) => {
184
+ rectangleRef.current?.update(options);
185
+ },
186
+ }),
187
+ []
188
+ );
189
+
190
+ return null;
191
+ }
192
+ );
193
+
194
+ export default Rectangle;
@@ -0,0 +1,315 @@
1
+ import {
2
+ type CSSProperties,
3
+ forwardRef,
4
+ type ReactNode,
5
+ useEffect,
6
+ useImperativeHandle,
7
+ useRef,
8
+ useState,
9
+ } from "react";
10
+ import { MapProvider } from "../context/MapContext";
11
+ import { useSphereContext } from "../context/SphereContext";
12
+ import type {
13
+ Bound,
14
+ FilterType,
15
+ FlyToOptions,
16
+ Location,
17
+ MapOptions,
18
+ Range,
19
+ SphereMap as SphereMapInstance,
20
+ } from "../types";
21
+
22
+ export interface SphereMapProps {
23
+ children?: ReactNode;
24
+ zoom?: number;
25
+ zoomRange?: Range;
26
+ center?: Location;
27
+ language?: string;
28
+ input?: boolean;
29
+ lastView?: boolean;
30
+ ui?: "Full" | "Mobile" | "None";
31
+ filter?: FilterType;
32
+ rotate?: number;
33
+ pitch?: number;
34
+ className?: string;
35
+ style?: CSSProperties;
36
+ id?: string;
37
+ onReady?: (map: SphereMapInstance) => void;
38
+ onZoom?: (zoom: number) => void;
39
+ onLocation?: (location: Location) => void;
40
+ onClick?: (location: Location) => void;
41
+ onDoubleClick?: (location: Location) => void;
42
+ onRotate?: (angle: number) => void;
43
+ onPitch?: (angle: number) => void;
44
+ onDrag?: () => void;
45
+ onDrop?: () => void;
46
+ onIdle?: () => void;
47
+ onMouseMove?: (location: Location) => void;
48
+ onError?: (error: Error) => void;
49
+ }
50
+
51
+ export interface SphereMapRef {
52
+ getMap(): SphereMapInstance | null;
53
+ setZoom(zoom: number, animate?: boolean): void;
54
+ setCenter(location: Location, animate?: boolean): void;
55
+ setBound(bound: Bound, options?: object): void;
56
+ goTo(target: FlyToOptions, animate?: boolean): void;
57
+ setRotate(angle: number, animate?: boolean): void;
58
+ setPitch(angle: number): void;
59
+ setFilter(filter: FilterType): void;
60
+ resize(): void;
61
+ repaint(): void;
62
+ }
63
+
64
+ export const SphereMap = forwardRef<SphereMapRef, SphereMapProps>(
65
+ function SphereMap(
66
+ {
67
+ children,
68
+ zoom = 7,
69
+ zoomRange,
70
+ center,
71
+ language = "th",
72
+ input = true,
73
+ lastView = false,
74
+ ui,
75
+ filter,
76
+ rotate,
77
+ pitch,
78
+ className,
79
+ style,
80
+ id,
81
+ onReady,
82
+ onZoom,
83
+ onLocation,
84
+ onClick,
85
+ onDoubleClick,
86
+ onRotate,
87
+ onPitch,
88
+ onDrag,
89
+ onDrop,
90
+ onIdle,
91
+ onMouseMove,
92
+ onError,
93
+ },
94
+ ref
95
+ ) {
96
+ const containerRef = useRef<HTMLDivElement>(null);
97
+ const mapRef = useRef<SphereMapInstance | null>(null);
98
+ const [isReady, setIsReady] = useState(false);
99
+ const { sphere, isLoaded, registerMap, unregisterMap } = useSphereContext();
100
+
101
+ const callbacksRef = useRef({
102
+ onReady,
103
+ onZoom,
104
+ onLocation,
105
+ onClick,
106
+ onDoubleClick,
107
+ onRotate,
108
+ onPitch,
109
+ onDrag,
110
+ onDrop,
111
+ onIdle,
112
+ onMouseMove,
113
+ onError,
114
+ });
115
+
116
+ useEffect(() => {
117
+ callbacksRef.current = {
118
+ onReady,
119
+ onZoom,
120
+ onLocation,
121
+ onClick,
122
+ onDoubleClick,
123
+ onRotate,
124
+ onPitch,
125
+ onDrag,
126
+ onDrop,
127
+ onIdle,
128
+ onMouseMove,
129
+ onError,
130
+ };
131
+ });
132
+
133
+ const initialPropsRef = useRef({
134
+ zoom,
135
+ zoomRange,
136
+ center,
137
+ language,
138
+ input,
139
+ lastView,
140
+ ui,
141
+ filter,
142
+ rotate,
143
+ pitch,
144
+ });
145
+
146
+ useEffect(() => {
147
+ if (!(isLoaded && sphere && containerRef.current)) {
148
+ return;
149
+ }
150
+
151
+ const initialProps = initialPropsRef.current;
152
+
153
+ try {
154
+ const options: MapOptions = {
155
+ placeholder: containerRef.current,
156
+ zoom: initialProps.zoom,
157
+ language: initialProps.language,
158
+ input: initialProps.input,
159
+ lastView: initialProps.lastView,
160
+ };
161
+
162
+ if (initialProps.zoomRange) {
163
+ options.zoomRange = initialProps.zoomRange;
164
+ }
165
+
166
+ if (initialProps.center) {
167
+ options.location = initialProps.center;
168
+ }
169
+
170
+ if (initialProps.ui) {
171
+ options.ui = initialProps.ui;
172
+ }
173
+
174
+ const map = new sphere.Map(options);
175
+ mapRef.current = map;
176
+
177
+ map.Event.bind("ready", () => {
178
+ setIsReady(true);
179
+ registerMap(map);
180
+
181
+ if (initialProps.filter && window.sphere) {
182
+ map.enableFilter(window.sphere.Filter[initialProps.filter]);
183
+ }
184
+
185
+ if (typeof initialProps.rotate === "number") {
186
+ map.rotate(initialProps.rotate, false);
187
+ }
188
+
189
+ if (typeof initialProps.pitch === "number") {
190
+ map.pitch(initialProps.pitch);
191
+ }
192
+
193
+ callbacksRef.current.onReady?.(map);
194
+ });
195
+
196
+ map.Event.bind("zoom", () => {
197
+ const currentZoom = map.zoom() as number;
198
+ callbacksRef.current.onZoom?.(currentZoom);
199
+ });
200
+
201
+ map.Event.bind("location", () => {
202
+ const currentLocation = map.location() as Location;
203
+ callbacksRef.current.onLocation?.(currentLocation);
204
+ });
205
+
206
+ map.Event.bind("click", (location: Location) => {
207
+ callbacksRef.current.onClick?.(location);
208
+ });
209
+
210
+ map.Event.bind("doubleClick", (location: Location) => {
211
+ callbacksRef.current.onDoubleClick?.(location);
212
+ });
213
+
214
+ map.Event.bind("rotate", () => {
215
+ const angle = map.rotate() as number;
216
+ callbacksRef.current.onRotate?.(angle);
217
+ });
218
+
219
+ map.Event.bind("pitch", () => {
220
+ const angle = map.pitch() as number;
221
+ callbacksRef.current.onPitch?.(angle);
222
+ });
223
+
224
+ map.Event.bind("drag", () => {
225
+ callbacksRef.current.onDrag?.();
226
+ });
227
+
228
+ map.Event.bind("drop", () => {
229
+ callbacksRef.current.onDrop?.();
230
+ });
231
+
232
+ map.Event.bind("idle", () => {
233
+ callbacksRef.current.onIdle?.();
234
+ });
235
+
236
+ map.Event.bind("mousemove", (location: Location) => {
237
+ callbacksRef.current.onMouseMove?.(location);
238
+ });
239
+
240
+ map.Event.bind("error", (error: Error) => {
241
+ callbacksRef.current.onError?.(error);
242
+ });
243
+ } catch (error) {
244
+ callbacksRef.current.onError?.(
245
+ error instanceof Error ? error : new Error(String(error))
246
+ );
247
+ }
248
+
249
+ return () => {
250
+ unregisterMap();
251
+ mapRef.current = null;
252
+ setIsReady(false);
253
+ };
254
+ // eslint-disable-next-line react-hooks/exhaustive-deps
255
+ }, [isLoaded, sphere, registerMap, unregisterMap]);
256
+
257
+ useImperativeHandle(
258
+ ref,
259
+ () => ({
260
+ getMap: () => mapRef.current,
261
+ setZoom: (newZoom: number, animate = true) => {
262
+ mapRef.current?.zoom(newZoom, animate);
263
+ },
264
+ setCenter: (location: Location, animate = true) => {
265
+ mapRef.current?.location(location, animate);
266
+ },
267
+ setBound: (bound: Bound, options?: object) => {
268
+ mapRef.current?.bound(bound, options);
269
+ },
270
+ goTo: (target: FlyToOptions, animate = true) => {
271
+ mapRef.current?.goTo(target, animate);
272
+ },
273
+ setRotate: (angle: number, animate = true) => {
274
+ mapRef.current?.rotate(angle, animate);
275
+ },
276
+ setPitch: (angle: number) => {
277
+ mapRef.current?.pitch(angle);
278
+ },
279
+ setFilter: (newFilter: FilterType) => {
280
+ if (mapRef.current && window.sphere) {
281
+ mapRef.current.enableFilter(window.sphere.Filter[newFilter]);
282
+ }
283
+ },
284
+ resize: () => {
285
+ mapRef.current?.resize();
286
+ },
287
+ repaint: () => {
288
+ mapRef.current?.repaint();
289
+ },
290
+ }),
291
+ []
292
+ );
293
+
294
+ const containerStyle: CSSProperties = {
295
+ width: "100%",
296
+ height: "100%",
297
+ ...style,
298
+ };
299
+
300
+ return (
301
+ <div
302
+ className={className}
303
+ id={id}
304
+ ref={containerRef}
305
+ style={containerStyle}
306
+ >
307
+ <MapProvider isReady={isReady} map={mapRef.current}>
308
+ {isReady && children}
309
+ </MapProvider>
310
+ </div>
311
+ );
312
+ }
313
+ );
314
+
315
+ export default SphereMap;
@@ -0,0 +1,18 @@
1
+ export type { CircleProps, CircleRef } from "./Circle";
2
+ export { Circle } from "./Circle";
3
+ export type { DotProps, DotRef } from "./Dot";
4
+ export { Dot } from "./Dot";
5
+ export type { LayerProps } from "./Layer";
6
+ export { Layer } from "./Layer";
7
+ export type { MarkerProps, MarkerRef } from "./Marker";
8
+ export { Marker } from "./Marker";
9
+ export type { PolygonProps, PolygonRef } from "./Polygon";
10
+ export { Polygon } from "./Polygon";
11
+ export type { PolylineProps, PolylineRef } from "./Polyline";
12
+ export { Polyline } from "./Polyline";
13
+ export type { PopupProps, PopupRef } from "./Popup";
14
+ export { Popup } from "./Popup";
15
+ export type { RectangleProps, RectangleRef } from "./Rectangle";
16
+ export { Rectangle } from "./Rectangle";
17
+ export type { SphereMapProps, SphereMapRef } from "./SphereMap";
18
+ export { default as Map, SphereMap } from "./SphereMap";
@@ -0,0 +1,41 @@
1
+ import { createContext, type ReactNode, useContext, useMemo } from "react";
2
+ import type { SphereMap } from "../types";
3
+
4
+ interface MapContextValue {
5
+ map: SphereMap | null;
6
+ isReady: boolean;
7
+ }
8
+
9
+ const MapContext = createContext<MapContextValue | null>(null);
10
+
11
+ interface MapProviderProps {
12
+ map: SphereMap | null;
13
+ isReady: boolean;
14
+ children: ReactNode;
15
+ }
16
+
17
+ export function MapProvider({
18
+ map,
19
+ isReady,
20
+ children,
21
+ }: MapProviderProps): ReactNode {
22
+ const value = useMemo<MapContextValue>(
23
+ () => ({ map, isReady }),
24
+ [map, isReady]
25
+ );
26
+
27
+ return <MapContext.Provider value={value}>{children}</MapContext.Provider>;
28
+ }
29
+
30
+ export function useMapContext(): MapContextValue {
31
+ const context = useContext(MapContext);
32
+
33
+ if (!context) {
34
+ throw new Error("useMapContext must be used within a SphereMap component");
35
+ }
36
+
37
+ return context;
38
+ }
39
+
40
+ export { MapContext };
41
+ export type { MapContextValue };