@dxos/react-ui-geo 0.8.4-main.c85a9c8dae → 0.8.4-main.d05539e30a

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 (66) hide show
  1. package/LICENSE +102 -5
  2. package/dist/lib/browser/{countries-110m-ZM3ZIEFS.mjs → countries-110m-RE5RNRQG.mjs} +1 -1
  3. package/dist/lib/browser/data.mjs +4 -3
  4. package/dist/lib/browser/data.mjs.map +4 -4
  5. package/dist/lib/browser/index.mjs +111 -120
  6. package/dist/lib/browser/index.mjs.map +4 -4
  7. package/dist/lib/browser/meta.json +1 -1
  8. package/dist/lib/browser/translations.mjs +19 -0
  9. package/dist/lib/browser/translations.mjs.map +7 -0
  10. package/dist/lib/node-esm/{countries-110m-3SFASWVD.mjs → countries-110m-4EDBXSFJ.mjs} +1 -1
  11. package/dist/lib/node-esm/data.mjs +5 -3
  12. package/dist/lib/node-esm/data.mjs.map +4 -4
  13. package/dist/lib/node-esm/index.mjs +111 -119
  14. package/dist/lib/node-esm/index.mjs.map +4 -4
  15. package/dist/lib/node-esm/meta.json +1 -1
  16. package/dist/lib/node-esm/translations.mjs +21 -0
  17. package/dist/lib/node-esm/translations.mjs.map +7 -0
  18. package/dist/types/data/airports.d.ts +4 -4
  19. package/dist/types/data/airports.d.ts.map +1 -1
  20. package/dist/types/data/cities.d.ts.map +1 -1
  21. package/dist/types/data/countries-110m.d.ts.map +1 -1
  22. package/dist/types/data/countries-dots-3.d.ts.map +1 -1
  23. package/dist/types/data/countries-dots-4.d.ts.map +1 -1
  24. package/dist/types/src/components/Globe/Globe.d.ts +5 -3
  25. package/dist/types/src/components/Globe/Globe.d.ts.map +1 -1
  26. package/dist/types/src/components/Globe/Globe.stories.d.ts +6 -4
  27. package/dist/types/src/components/Globe/Globe.stories.d.ts.map +1 -1
  28. package/dist/types/src/components/Map/Map.d.ts +20 -4
  29. package/dist/types/src/components/Map/Map.d.ts.map +1 -1
  30. package/dist/types/src/components/Map/Map.stories.d.ts.map +1 -1
  31. package/dist/types/src/components/Toolbar/Controls.d.ts.map +1 -1
  32. package/dist/types/src/hooks/context.d.ts +1 -3
  33. package/dist/types/src/hooks/context.d.ts.map +1 -1
  34. package/dist/types/src/hooks/useDrag.d.ts.map +1 -1
  35. package/dist/types/src/hooks/useGlobeZoomHandler.d.ts +1 -1
  36. package/dist/types/src/hooks/useGlobeZoomHandler.d.ts.map +1 -1
  37. package/dist/types/src/hooks/useMapZoomHandler.d.ts +1 -1
  38. package/dist/types/src/hooks/useMapZoomHandler.d.ts.map +1 -1
  39. package/dist/types/src/hooks/useSpinner.d.ts.map +1 -1
  40. package/dist/types/src/hooks/useTour.d.ts.map +1 -1
  41. package/dist/types/src/index.d.ts +0 -2
  42. package/dist/types/src/index.d.ts.map +1 -1
  43. package/dist/types/src/translations.d.ts +4 -4
  44. package/dist/types/src/translations.d.ts.map +1 -1
  45. package/dist/types/src/util/debug.d.ts.map +1 -1
  46. package/dist/types/src/util/inertia.d.ts.map +1 -1
  47. package/dist/types/src/util/path.d.ts.map +1 -1
  48. package/dist/types/src/util/render.d.ts.map +1 -1
  49. package/dist/types/tsconfig.tsbuildinfo +1 -1
  50. package/package.json +27 -24
  51. package/src/components/Globe/Globe.stories.tsx +5 -6
  52. package/src/components/Globe/Globe.tsx +56 -21
  53. package/src/components/Map/Map.stories.tsx +7 -6
  54. package/src/components/Map/Map.tsx +76 -31
  55. package/src/components/Toolbar/Controls.tsx +5 -5
  56. package/src/hooks/context.tsx +5 -34
  57. package/src/hooks/useSpinner.ts +0 -1
  58. package/src/index.ts +0 -2
  59. package/src/translations.ts +4 -4
  60. package/src/util/render.ts +0 -1
  61. package/dist/lib/browser/chunk-GMWLKTLN.mjs +0 -9
  62. package/dist/lib/browser/chunk-GMWLKTLN.mjs.map +0 -7
  63. package/dist/lib/node-esm/chunk-JODBF4CC.mjs +0 -11
  64. package/dist/lib/node-esm/chunk-JODBF4CC.mjs.map +0 -7
  65. /package/dist/lib/browser/{countries-110m-ZM3ZIEFS.mjs.map → countries-110m-RE5RNRQG.mjs.map} +0 -0
  66. /package/dist/lib/node-esm/{countries-110m-3SFASWVD.mjs.map → countries-110m-4EDBXSFJ.mjs.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-geo",
3
- "version": "0.8.4-main.c85a9c8dae",
3
+ "version": "0.8.4-main.d05539e30a",
4
4
  "description": "Geo components.",
5
5
  "homepage": "https://github.com/dxos",
6
6
  "bugs": "https://github.com/dxos/issues",
@@ -8,38 +8,41 @@
8
8
  "type": "git",
9
9
  "url": "https://github.com/dxos/dxos"
10
10
  },
11
- "license": "MIT",
11
+ "license": "FSL-1.1-Apache-2.0",
12
12
  "author": "DXOS.org",
13
13
  "sideEffects": true,
14
14
  "type": "module",
15
+ "imports": {
16
+ "#translations": "./src/translations.ts"
17
+ },
15
18
  "exports": {
19
+ ".": {
20
+ "source": "./src/index.ts",
21
+ "types": "./dist/types/src/index.d.ts",
22
+ "browser": "./dist/lib/browser/index.mjs",
23
+ "node": "./dist/lib/node-esm/index.mjs"
24
+ },
16
25
  "./data": {
17
26
  "source": "./src/data.ts",
18
27
  "types": "./dist/types/src/data.d.ts",
19
28
  "browser": "./dist/lib/browser/data.mjs",
20
29
  "node": "./dist/lib/node-esm/data.mjs"
21
30
  },
22
- ".": {
23
- "source": "./src/index.ts",
24
- "types": "./dist/types/src/index.d.ts",
25
- "browser": "./dist/lib/browser/index.mjs",
26
- "node": "./dist/lib/node-esm/index.mjs"
31
+ "./translations": {
32
+ "source": "./src/translations.ts",
33
+ "types": "./dist/types/src/translations.d.ts",
34
+ "browser": "./dist/lib/browser/translations.mjs",
35
+ "node": "./dist/lib/node-esm/translations.mjs"
27
36
  }
28
37
  },
29
38
  "types": "dist/types/src/index.d.ts",
30
- "typesVersions": {
31
- "*": {
32
- "data": [
33
- "dist/types/src/data.d.ts"
34
- ]
35
- }
36
- },
37
39
  "files": [
38
40
  "data",
39
41
  "dist",
40
42
  "src"
41
43
  ],
42
44
  "dependencies": {
45
+ "@radix-ui/react-compose-refs": "1.1.1",
43
46
  "@radix-ui/react-context": "1.1.1",
44
47
  "d3": "^7.9.0",
45
48
  "d3-geo-projection": "^4.0.0",
@@ -52,11 +55,11 @@
52
55
  "topojson-client": "^3.1.0",
53
56
  "topojson-simplify": "^3.0.3",
54
57
  "versor": "^0.2.0",
55
- "@dxos/debug": "0.8.4-main.c85a9c8dae",
56
- "@dxos/log": "0.8.4-main.c85a9c8dae",
57
- "@dxos/async": "0.8.4-main.c85a9c8dae",
58
- "@dxos/node-std": "0.8.4-main.c85a9c8dae",
59
- "@dxos/util": "0.8.4-main.c85a9c8dae"
58
+ "@dxos/async": "0.8.4-main.d05539e30a",
59
+ "@dxos/log": "0.8.4-main.d05539e30a",
60
+ "@dxos/debug": "0.8.4-main.d05539e30a",
61
+ "@dxos/node-std": "0.8.4-main.d05539e30a",
62
+ "@dxos/util": "0.8.4-main.d05539e30a"
60
63
  },
61
64
  "devDependencies": {
62
65
  "@react-three/drei": "^10.7.7",
@@ -76,15 +79,15 @@
76
79
  "react": "~19.2.3",
77
80
  "react-dom": "~19.2.3",
78
81
  "three": "^0.178.0",
79
- "@dxos/react-ui": "0.8.4-main.c85a9c8dae",
80
- "@dxos/ui-theme": "0.8.4-main.c85a9c8dae",
81
- "@dxos/storybook-utils": "0.8.4-main.c85a9c8dae"
82
+ "@dxos/react-ui": "0.8.4-main.d05539e30a",
83
+ "@dxos/ui-theme": "0.8.4-main.d05539e30a",
84
+ "@dxos/storybook-utils": "0.8.4-main.d05539e30a"
82
85
  },
83
86
  "peerDependencies": {
84
87
  "react": "~19.2.3",
85
88
  "react-dom": "~19.2.3",
86
- "@dxos/react-ui": "0.8.4-main.c85a9c8dae",
87
- "@dxos/ui-theme": "0.8.4-main.c85a9c8dae"
89
+ "@dxos/ui-theme": "0.8.4-main.d05539e30a",
90
+ "@dxos/react-ui": "0.8.4-main.d05539e30a"
88
91
  },
89
92
  "publishConfig": {
90
93
  "access": "public"
@@ -15,7 +15,6 @@ import { type Vector, useDrag, useGlobeZoomHandler, useSpinner, useTour } from '
15
15
  import { type LatLngLiteral } from '../../types';
16
16
  import { type StyleSet, closestPoint } from '../../util';
17
17
  import { type ControlProps } from '../Toolbar';
18
-
19
18
  import { Globe, type GlobeCanvasProps, type GlobeController, type GlobeRootProps } from './Globe';
20
19
 
21
20
  // TODO(burdon): Load from JSON at runtime?
@@ -128,7 +127,7 @@ const createTrip = (
128
127
  );
129
128
  };
130
129
 
131
- type StoryProps = Pick<GlobeRootProps, 'zoom' | 'translation' | 'rotation'> &
130
+ type DefaultStoryProps = Pick<GlobeRootProps, 'zoom' | 'translation' | 'rotation'> &
132
131
  Pick<GlobeCanvasProps, 'projection' | 'styles'> & {
133
132
  drag?: boolean;
134
133
  spin?: boolean;
@@ -137,7 +136,7 @@ type StoryProps = Pick<GlobeRootProps, 'zoom' | 'translation' | 'rotation'> &
137
136
  };
138
137
 
139
138
  const DefaultStory = ({
140
- zoom: _zoom = 1,
139
+ zoom: zoomProp = 1,
141
140
  translation,
142
141
  rotation = [0, 0, 0],
143
142
  projection,
@@ -146,7 +145,7 @@ const DefaultStory = ({
146
145
  spin = false,
147
146
  tour = false,
148
147
  xAxis = false,
149
- }: StoryProps) => {
148
+ }: DefaultStoryProps) => {
150
149
  const controller = useRef<GlobeController>(null);
151
150
  const [dots] = useAsyncState(async () => {
152
151
  const points = (await import('../../../data/countries-dots-3.ts')).default;
@@ -203,13 +202,13 @@ const DefaultStory = ({
203
202
  };
204
203
 
205
204
  return (
206
- <Globe.Root classNames='absolute inset-0' zoom={_zoom} translation={translation} rotation={rotation}>
205
+ <Globe.Root zoom={zoomProp} translation={translation} rotation={rotation}>
207
206
  <Globe.Canvas
208
- ref={controller}
209
207
  topology={styles?.dots ? dots : topology}
210
208
  projection={projection}
211
209
  styles={styles}
212
210
  features={tour ? { points: features?.points ?? [] } : features}
211
+ ref={controller}
213
212
  />
214
213
  <Globe.Zoom onAction={handleAction} />
215
214
  <Globe.Action onAction={handleAction} />
@@ -26,15 +26,17 @@ import React, {
26
26
  import { useResizeDetector } from 'react-resize-detector';
27
27
  import { type Topology } from 'topojson-specification';
28
28
 
29
- import { type ThemeMode, type ThemedClassName, useDynamicRef, useThemeContext } from '@dxos/react-ui';
30
- import { mx } from '@dxos/ui-theme';
31
-
32
29
  import {
33
- GlobeContextProvider,
34
- type GlobeContextProviderProps,
35
- type GlobeContextType,
36
- useGlobeContext,
37
- } from '../../hooks';
30
+ type ThemeMode,
31
+ type ThemedClassName,
32
+ useComposedRefs,
33
+ useControlledState,
34
+ useDynamicRef,
35
+ useThemeContext,
36
+ } from '@dxos/react-ui';
37
+ import { composable, composableProps, mx } from '@dxos/ui-theme';
38
+
39
+ import { GlobeContext, type GlobeContextType, type Point, type Vector, useGlobeContext } from '../../hooks';
38
40
  import {
39
41
  type Features,
40
42
  type StyleSet,
@@ -126,19 +128,52 @@ const getProjection = (type: GlobeCanvasProps['projection'] = 'orthographic'): G
126
128
  // Root
127
129
  //
128
130
 
129
- type GlobeRootProps = PropsWithChildren<ThemedClassName<GlobeContextProviderProps>>;
131
+ const DEFAULT_ZOOM = 1.5;
130
132
 
131
- const GlobeRoot = ({ classNames, children, ...props }: GlobeRootProps) => {
132
- const { ref, width, height } = useResizeDetector<HTMLDivElement>();
133
+ type GlobeRootProps = Partial<Pick<GlobeContextType, 'center' | 'zoom' | 'translation' | 'rotation'>>;
133
134
 
134
- return (
135
- <div ref={ref} className={mx('relative flex grow overflow-hidden', classNames)}>
136
- <GlobeContextProvider size={{ width, height }} {...props}>
137
- {children}
138
- </GlobeContextProvider>
139
- </div>
140
- );
141
- };
135
+ const GlobeRoot = composable<HTMLDivElement, GlobeRootProps>(
136
+ (
137
+ {
138
+ children,
139
+ center: centerProp,
140
+ zoom: zoomProp = DEFAULT_ZOOM,
141
+ translation: translationProp,
142
+ rotation: rotationProp,
143
+ ...props
144
+ },
145
+ forwardedRef,
146
+ ) => {
147
+ const localRef = useRef<HTMLDivElement>(null);
148
+ const composedRef = useComposedRefs<HTMLDivElement>(localRef, forwardedRef);
149
+ const { width, height } = useResizeDetector<HTMLDivElement>({ targetRef: localRef });
150
+
151
+ const [center, setCenter] = useControlledState(centerProp);
152
+ const [zoom, setZoom] = useControlledState(zoomProp);
153
+ const [translation, setTranslation] = useControlledState<Point>(translationProp);
154
+ const [rotation, setRotation] = useControlledState<Vector>(rotationProp);
155
+
156
+ return (
157
+ <GlobeContext.Provider
158
+ value={{
159
+ size: { width, height },
160
+ center,
161
+ zoom,
162
+ translation,
163
+ rotation,
164
+ setCenter,
165
+ setZoom,
166
+ setTranslation,
167
+ setRotation,
168
+ }}
169
+ >
170
+ <div {...composableProps(props, { classNames: 'relative dx-container' })} ref={composedRef}>
171
+ {children}
172
+ </div>
173
+ </GlobeContext.Provider>
174
+ );
175
+ },
176
+ );
142
177
 
143
178
  //
144
179
  // Canvas
@@ -179,10 +214,10 @@ const GlobeCanvas = forwardRef<GlobeController, GlobeCanvasProps>(
179
214
  useGlobeContext();
180
215
  const zoomRef = useDynamicRef(zoom);
181
216
 
182
- // Update rotation.
217
+ // Update rotation when the center changes. Preserve current zoom — callers can set zoom
218
+ // independently via the `zoom` prop or `setZoom` on the controller.
183
219
  useEffect(() => {
184
220
  if (center) {
185
- setZoom(1);
186
221
  setRotation(positionToRotation(geoToPosition(center)));
187
222
  }
188
223
  }, [center]);
@@ -9,7 +9,6 @@ import { withLayout, withTheme } from '@dxos/react-ui/testing';
9
9
 
10
10
  import { useMapZoomHandler } from '../../hooks';
11
11
  import { type GeoMarker } from '../../types';
12
-
13
12
  import { Map, type MapController } from './Map';
14
13
 
15
14
  const DefaultStory = ({ markers = [] }: { markers?: GeoMarker[] }) => {
@@ -17,11 +16,13 @@ const DefaultStory = ({ markers = [] }: { markers?: GeoMarker[] }) => {
17
16
  const handleZoomAction = useMapZoomHandler(controller);
18
17
 
19
18
  return (
20
- <Map.Root ref={setController}>
21
- <Map.Tiles />
22
- <Map.Markers markers={markers} />
23
- <Map.Zoom position='bottomleft' onAction={handleZoomAction} />
24
- <Map.Action position='bottomright' />
19
+ <Map.Root>
20
+ <Map.Content ref={setController}>
21
+ <Map.Tiles />
22
+ <Map.Markers markers={markers} />
23
+ <Map.Zoom position='bottomleft' onAction={handleZoomAction} />
24
+ <Map.Action position='bottomright' />
25
+ </Map.Content>
25
26
  </Map.Root>
26
27
  );
27
28
  };
@@ -6,12 +6,12 @@ import 'leaflet/dist/leaflet.css';
6
6
 
7
7
  import { createContext } from '@radix-ui/react-context';
8
8
  import L, { Control, type ControlPosition, DomEvent, DomUtil, type LatLngLiteral, latLngBounds } from 'leaflet';
9
- import React, { type PropsWithChildren, forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
9
+ import React, { type PropsWithChildren, forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
10
10
  import { createRoot } from 'react-dom/client';
11
11
  import { MapContainer, type MapContainerProps, Marker, Popup, TileLayer, useMap, useMapEvents } from 'react-leaflet';
12
12
 
13
- import { ThemeProvider, type ThemedClassName, Tooltip } from '@dxos/react-ui';
14
- import { defaultTx, mx } from '@dxos/ui-theme';
13
+ import { type ThemedClassName, ThemeProvider, Tooltip } from '@dxos/react-ui';
14
+ import { composable, composableProps, defaultTx, mx } from '@dxos/ui-theme';
15
15
 
16
16
  import { type GeoMarker } from '../../types';
17
17
  import { ActionControls, type ControlProps, ZoomControls, controlPositions } from '../Toolbar';
@@ -30,6 +30,8 @@ const defaults = {
30
30
  //
31
31
 
32
32
  type MapController = {
33
+ getCenter: () => LatLngLiteral | undefined;
34
+ getZoom: () => number | undefined;
33
35
  setCenter: (center: LatLngLiteral, zoom?: number) => void;
34
36
  setZoom: (cb: (zoom: number) => number) => void;
35
37
  };
@@ -43,29 +45,65 @@ type MapContextValue = {
43
45
  onChange?: (ev: { center: LatLngLiteral; zoom: number }) => void;
44
46
  };
45
47
 
46
- const [MapContextProvier, useMapContext] = createContext<MapContextValue>('Map');
48
+ const [MapContextProvider, useMapContext] = createContext<MapContextValue>('Map');
47
49
 
48
50
  //
49
51
  // Root
50
52
  //
51
53
 
52
- type MapRootProps = ThemedClassName<MapContainerProps & Pick<MapContextValue, 'onChange'>>;
54
+ type MapRootProps = Pick<MapContextValue, 'onChange'>;
55
+
56
+ /**
57
+ * Context provider for the map. Must wrap Map.Content.
58
+ */
59
+ const MapRoot = composable<HTMLDivElement, MapRootProps>(({ children, onChange, ...props }, forwardedRef) => {
60
+ // TODO(burdon): Use attention: const [attention, setAttention] = useState(false);
61
+ const attention = false;
62
+ return (
63
+ <MapContextProvider attention={attention} onChange={onChange}>
64
+ <div
65
+ {...composableProps(props, {
66
+ role: 'none',
67
+ classNames: 'dx-container grid dx-focus-ring-inset',
68
+ })}
69
+ ref={forwardedRef}
70
+ >
71
+ {children}
72
+ </div>
73
+ </MapContextProvider>
74
+ );
75
+ });
76
+
77
+ MapRoot.displayName = 'Map.Root';
78
+
79
+ //
80
+ // Content
81
+ //
82
+
83
+ type MapContentProps = ThemedClassName<Omit<MapContainerProps, 'children'> & PropsWithChildren>;
53
84
 
54
85
  /**
55
86
  * https://react-leaflet.js.org/docs/api-map
56
87
  */
57
- const MapRoot = forwardRef<MapController, MapRootProps>(
88
+ const MAP_CONTENT_NAME = 'Map.Content';
89
+
90
+ const MapContent = forwardRef<MapController, MapContentProps>(
58
91
  (
59
- { classNames, scrollWheelZoom = true, doubleClickZoom = true, touchZoom = true, center, zoom, onChange, ...props },
92
+ { classNames, scrollWheelZoom = true, doubleClickZoom = true, touchZoom = true, center, zoom, children, ...props },
60
93
  forwardedRef,
61
94
  ) => {
62
- const [attention, setAttention] = useState(false);
95
+ const { attention } = useMapContext(MAP_CONTENT_NAME);
63
96
  const mapRef = useRef<L.Map>(null);
64
97
  const map = mapRef.current;
65
98
 
66
99
  useImperativeHandle(
67
100
  forwardedRef,
68
101
  () => ({
102
+ getCenter: () => {
103
+ const center = mapRef.current?.getCenter();
104
+ return center ? { lat: center.lat, lng: center.lng } : undefined;
105
+ },
106
+ getZoom: () => mapRef.current?.getZoom(),
69
107
  setCenter: (center: LatLngLiteral, zoom?: number) => {
70
108
  mapRef.current?.setView(center, zoom);
71
109
  },
@@ -92,26 +130,26 @@ const MapRoot = forwardRef<MapController, MapRootProps>(
92
130
  }, [map, attention]);
93
131
 
94
132
  return (
95
- <MapContextProvier attention={attention} onChange={onChange}>
96
- <MapContainer
97
- {...props}
98
- ref={mapRef}
99
- className={mx('group relative grid h-full w-full !bg-base-surface dx-focus-ring-inset', classNames)}
100
- attributionControl={false}
101
- zoomControl={false}
102
- scrollWheelZoom={scrollWheelZoom}
103
- doubleClickZoom={doubleClickZoom}
104
- touchZoom={touchZoom}
105
- center={center ?? defaults.center}
106
- zoom={zoom ?? defaults.zoom}
107
- // whenReady={() => {}}
108
- />
109
- </MapContextProvier>
133
+ <MapContainer
134
+ {...props}
135
+ className={mx('group relative grid bg-base-surface!', classNames)}
136
+ attributionControl={false}
137
+ zoomControl={false}
138
+ scrollWheelZoom={scrollWheelZoom}
139
+ doubleClickZoom={doubleClickZoom}
140
+ touchZoom={touchZoom}
141
+ center={center ?? defaults.center}
142
+ zoom={zoom ?? defaults.zoom}
143
+ whenReady={() => {}}
144
+ ref={mapRef}
145
+ >
146
+ {children}
147
+ </MapContainer>
110
148
  );
111
149
  },
112
150
  );
113
151
 
114
- MapRoot.displayName = 'Map.Root';
152
+ MapContent.displayName = 'Map.Content';
115
153
 
116
154
  //
117
155
  // Tiles
@@ -127,7 +165,7 @@ const MapTiles = (_props: MapTilesProps) => {
127
165
  const { onChange } = useMapContext(MAP_TILES_NAME);
128
166
 
129
167
  useMapEvents({
130
- zoomstart: (ev) => {
168
+ moveend: (ev) => {
131
169
  onChange?.({
132
170
  center: ev.target.getCenter(),
133
171
  zoom: ev.target.getZoom(),
@@ -192,15 +230,14 @@ type MapMarkersProps = {
192
230
  const MapMarkers = ({ selected, markers }: MapMarkersProps) => {
193
231
  const map = useMap();
194
232
 
195
- // Set the viewport around the markers, or show the whole world map if `markers` is empty.
233
+ // Fit the viewport around the markers. When there are no markers, leave the current view alone
234
+ // so caller-provided center/zoom (or the user's prior interaction) is preserved.
196
235
  useEffect(() => {
197
- if (markers.length > 0) {
236
+ if (markers && markers.length > 0) {
198
237
  const bounds = latLngBounds(markers.map((marker) => marker.location));
199
238
  map.fitBounds(bounds);
200
- } else {
201
- map.setView(defaults.center, defaults.zoom);
202
239
  }
203
- }, [markers]);
240
+ }, [markers, map]);
204
241
 
205
242
  return (
206
243
  <>
@@ -292,10 +329,18 @@ const MapAction = ({ onAction, position = 'bottomright', ...props }: MapControlP
292
329
 
293
330
  export const Map = {
294
331
  Root: MapRoot,
332
+ Content: MapContent,
295
333
  Tiles: MapTiles,
296
334
  Markers: MapMarkers,
297
335
  Zoom: MapZoom,
298
336
  Action: MapAction,
299
337
  };
300
338
 
301
- export { type MapController, type MapRootProps, type MapTilesProps, type MapMarkersProps, type MapControlProps };
339
+ export {
340
+ type MapController,
341
+ type MapRootProps,
342
+ type MapContentProps,
343
+ type MapTilesProps,
344
+ type MapMarkersProps,
345
+ type MapControlProps,
346
+ };
@@ -7,7 +7,7 @@ import React from 'react';
7
7
 
8
8
  import { IconButton, type ThemedClassName, Toolbar, useTranslation } from '@dxos/react-ui';
9
9
 
10
- import { translationKey } from '../../translations';
10
+ import { translationKey } from '#translations';
11
11
 
12
12
  export type ControlAction = 'toggle' | 'start' | 'zoom-in' | 'zoom-out';
13
13
 
@@ -30,13 +30,13 @@ export const ZoomControls = ({ classNames, onAction }: ControlProps) => {
30
30
  <IconButton
31
31
  icon='ph--plus--regular'
32
32
  iconOnly
33
- label={t('zoom in icon button')}
33
+ label={t('zoom-in-icon.button')}
34
34
  onClick={() => onAction?.('zoom-in')}
35
35
  />
36
36
  <IconButton
37
37
  icon='ph--minus--regular'
38
38
  iconOnly
39
- label={t('zoom out icon button')}
39
+ label={t('zoom-out-icon.button')}
40
40
  onClick={() => onAction?.('zoom-out')}
41
41
  />
42
42
  </Toolbar.Root>
@@ -51,13 +51,13 @@ export const ActionControls = ({ classNames, onAction }: ControlProps) => {
51
51
  <IconButton
52
52
  icon='ph--path--regular'
53
53
  iconOnly
54
- label={t('start icon button')}
54
+ label={t('start-icon.button')}
55
55
  onClick={() => onAction?.('start')}
56
56
  />
57
57
  <IconButton
58
58
  icon='ph--globe-hemisphere-west--regular'
59
59
  iconOnly
60
- label={t('toggle icon button')}
60
+ label={t('toggle-icon.button')}
61
61
  onClick={() => onAction?.('toggle')}
62
62
  />
63
63
  </Toolbar.Root>
@@ -2,16 +2,17 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { type Dispatch, type PropsWithChildren, type SetStateAction, createContext, useContext } from 'react';
5
+ import { type Dispatch, type SetStateAction, createContext, useContext } from 'react';
6
6
 
7
7
  import { raise } from '@dxos/debug';
8
- import { useControlledState } from '@dxos/react-ui';
9
8
 
10
9
  import { type LatLngLiteral } from '../types';
11
10
 
12
11
  // TODO(burdon): Factor out common geometry types.
13
12
  export type Size = { width: number; height: number };
13
+
14
14
  export type Point = { x: number; y: number };
15
+
15
16
  export type Vector = [number, number, number];
16
17
 
17
18
  export type GlobeContextType = {
@@ -26,39 +27,9 @@ export type GlobeContextType = {
26
27
  setRotation: Dispatch<SetStateAction<Vector>>;
27
28
  };
28
29
 
29
- const defaults = {
30
- center: { lat: 51, lng: 0 } as LatLngLiteral,
31
- zoom: 4,
32
- } as const;
33
-
30
+ /** @internal */
34
31
  // TODO(burdon): Replace with radix.
35
- const GlobeContext = createContext<GlobeContextType>(undefined);
36
-
37
- export type GlobeContextProviderProps = PropsWithChildren<
38
- Partial<Pick<GlobeContextType, 'size' | 'center' | 'zoom' | 'translation' | 'rotation'>>
39
- >;
40
-
41
- export const GlobeContextProvider = ({
42
- children,
43
- size,
44
- center: centerProp = defaults.center,
45
- zoom: zoomProp = defaults.zoom,
46
- translation: translationProp,
47
- rotation: rotationProp,
48
- }: GlobeContextProviderProps) => {
49
- const [center, setCenter] = useControlledState(centerProp);
50
- const [zoom, setZoom] = useControlledState(zoomProp);
51
- const [translation, setTranslation] = useControlledState<Point>(translationProp);
52
- const [rotation, setRotation] = useControlledState<Vector>(rotationProp);
53
-
54
- return (
55
- <GlobeContext.Provider
56
- value={{ size, center, zoom, translation, rotation, setCenter, setZoom, setTranslation, setRotation }}
57
- >
58
- {children}
59
- </GlobeContext.Provider>
60
- );
61
- };
32
+ export const GlobeContext = createContext<GlobeContextType>(undefined);
62
33
 
63
34
  export const useGlobeContext = () => {
64
35
  return useContext(GlobeContext) ?? raise(new Error('Missing GlobeContext'));
@@ -7,7 +7,6 @@ import { type Timer } from 'd3';
7
7
  import { useEffect, useState } from 'react';
8
8
 
9
9
  import { type GlobeController } from '../components';
10
-
11
10
  import { type Vector } from './context';
12
11
 
13
12
  export type SpinnerOptions = {
package/src/index.ts CHANGED
@@ -3,8 +3,6 @@
3
3
  //
4
4
 
5
5
  export * from './components';
6
- export * from './data';
7
6
  export * from './hooks';
8
- export * from './translations';
9
7
  export type * from './types';
10
8
  export * from './util';
@@ -10,10 +10,10 @@ export const translations = [
10
10
  {
11
11
  'en-US': {
12
12
  [translationKey]: {
13
- 'zoom in icon button': 'Zoom in',
14
- 'zoom out icon button': 'Zoom out',
15
- 'start icon button': 'Start',
16
- 'toggle icon button': 'Toggle',
13
+ 'zoom-in-icon.button': 'Zoom in',
14
+ 'zoom-out-icon.button': 'Zoom out',
15
+ 'start-icon.button': 'Start',
16
+ 'toggle-icon.button': 'Toggle',
17
17
  },
18
18
  },
19
19
  },
@@ -7,7 +7,6 @@ import { feature, mesh } from 'topojson-client';
7
7
  import { type Topology } from 'topojson-specification';
8
8
 
9
9
  import { type LatLngLiteral } from '../types';
10
-
11
10
  import { geoLine, geoPoint } from './path';
12
11
 
13
12
  export type Styles = Record<string, any>;
@@ -1,9 +0,0 @@
1
- // src/data.ts
2
- var loadTopology = async () => {
3
- return (await import("./countries-110m-ZM3ZIEFS.mjs")).default;
4
- };
5
-
6
- export {
7
- loadTopology
8
- };
9
- //# sourceMappingURL=chunk-GMWLKTLN.mjs.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/data.ts"],
4
- "sourcesContent": ["//\n// Copyright 2024 DXOS.org\n//\n\nimport { type Topology } from 'topojson-specification';\n\nexport const loadTopology = async (): Promise<Topology> => {\n return (await import('../data/countries-110m.ts')).default;\n};\n"],
5
- "mappings": ";AAMO,IAAMA,eAAe,YAAA;AAC1B,UAAQ,MAAM,OAAO,+BAAA,GAA8BC;AACrD;",
6
- "names": ["loadTopology", "default"]
7
- }
@@ -1,11 +0,0 @@
1
- import { createRequire } from 'node:module';const require = createRequire(import.meta.url);
2
-
3
- // src/data.ts
4
- var loadTopology = async () => {
5
- return (await import("./countries-110m-3SFASWVD.mjs")).default;
6
- };
7
-
8
- export {
9
- loadTopology
10
- };
11
- //# sourceMappingURL=chunk-JODBF4CC.mjs.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/data.ts"],
4
- "sourcesContent": ["//\n// Copyright 2024 DXOS.org\n//\n\nimport { type Topology } from 'topojson-specification';\n\nexport const loadTopology = async (): Promise<Topology> => {\n return (await import('../data/countries-110m.ts')).default;\n};\n"],
5
- "mappings": ";;;AAMO,IAAMA,eAAe,YAAA;AAC1B,UAAQ,MAAM,OAAO,+BAAA,GAA8BC;AACrD;",
6
- "names": ["loadTopology", "default"]
7
- }