@dxos/react-ui-geo 0.8.4-main.b97322e → 0.8.4-main.bc2380dfbc

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 (85) 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 +381 -451
  6. package/dist/lib/browser/index.mjs.map +3 -3
  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 +381 -450
  14. package/dist/lib/node-esm/index.mjs.map +3 -3
  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 +6 -4
  25. package/dist/types/src/components/Globe/Globe.d.ts.map +1 -1
  26. package/dist/types/src/components/Globe/Globe.stories.d.ts +27 -9
  27. package/dist/types/src/components/Globe/Globe.stories.d.ts.map +1 -1
  28. package/dist/types/src/components/Map/Map.d.ts +42 -18
  29. package/dist/types/src/components/Map/Map.d.ts.map +1 -1
  30. package/dist/types/src/components/Map/Map.stories.d.ts +14 -8
  31. package/dist/types/src/components/Map/Map.stories.d.ts.map +1 -1
  32. package/dist/types/src/components/Toolbar/Controls.d.ts.map +1 -1
  33. package/dist/types/src/components/index.d.ts +0 -1
  34. package/dist/types/src/components/index.d.ts.map +1 -1
  35. package/dist/types/src/hooks/context.d.ts +6 -8
  36. package/dist/types/src/hooks/context.d.ts.map +1 -1
  37. package/dist/types/src/hooks/useDrag.d.ts.map +1 -1
  38. package/dist/types/src/hooks/useGlobeZoomHandler.d.ts +2 -2
  39. package/dist/types/src/hooks/useGlobeZoomHandler.d.ts.map +1 -1
  40. package/dist/types/src/hooks/useMapZoomHandler.d.ts +2 -2
  41. package/dist/types/src/hooks/useMapZoomHandler.d.ts.map +1 -1
  42. package/dist/types/src/hooks/useSpinner.d.ts +1 -1
  43. package/dist/types/src/hooks/useSpinner.d.ts.map +1 -1
  44. package/dist/types/src/hooks/useTour.d.ts +4 -3
  45. package/dist/types/src/hooks/useTour.d.ts.map +1 -1
  46. package/dist/types/src/index.d.ts +0 -1
  47. package/dist/types/src/index.d.ts.map +1 -1
  48. package/dist/types/src/translations.d.ts +12 -0
  49. package/dist/types/src/translations.d.ts.map +1 -0
  50. package/dist/types/src/types.d.ts +2 -1
  51. package/dist/types/src/types.d.ts.map +1 -1
  52. package/dist/types/src/util/debug.d.ts.map +1 -1
  53. package/dist/types/src/util/inertia.d.ts.map +1 -1
  54. package/dist/types/src/util/path.d.ts +5 -8
  55. package/dist/types/src/util/path.d.ts.map +1 -1
  56. package/dist/types/src/util/render.d.ts +4 -4
  57. package/dist/types/src/util/render.d.ts.map +1 -1
  58. package/dist/types/tsconfig.tsbuildinfo +1 -1
  59. package/package.json +44 -35
  60. package/src/components/Globe/Globe.stories.tsx +82 -35
  61. package/src/components/Globe/Globe.tsx +101 -54
  62. package/src/components/Map/Map.stories.tsx +27 -15
  63. package/src/components/Map/Map.tsx +220 -94
  64. package/src/components/Toolbar/Controls.tsx +14 -20
  65. package/src/components/index.ts +0 -2
  66. package/src/hooks/context.tsx +11 -34
  67. package/src/hooks/useGlobeZoomHandler.ts +9 -3
  68. package/src/hooks/useMapZoomHandler.ts +1 -1
  69. package/src/hooks/useSpinner.ts +1 -1
  70. package/src/hooks/useTour.ts +10 -8
  71. package/src/index.ts +0 -1
  72. package/src/translations.ts +20 -0
  73. package/src/types.ts +3 -1
  74. package/src/util/inertia.ts +1 -1
  75. package/src/util/path.ts +5 -6
  76. package/src/util/render.ts +4 -3
  77. package/dist/lib/browser/chunk-GMWLKTLN.mjs +0 -9
  78. package/dist/lib/browser/chunk-GMWLKTLN.mjs.map +0 -7
  79. package/dist/lib/node-esm/chunk-JODBF4CC.mjs +0 -11
  80. package/dist/lib/node-esm/chunk-JODBF4CC.mjs.map +0 -7
  81. package/dist/types/src/components/types.d.ts +0 -15
  82. package/dist/types/src/components/types.d.ts.map +0 -1
  83. package/src/components/types.ts +0 -19
  84. /package/dist/lib/browser/{countries-110m-ZM3ZIEFS.mjs.map → countries-110m-RE5RNRQG.mjs.map} +0 -0
  85. /package/dist/lib/node-esm/{countries-110m-3SFASWVD.mjs.map → countries-110m-4EDBXSFJ.mjs.map} +0 -0
package/package.json CHANGED
@@ -1,84 +1,93 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-geo",
3
- "version": "0.8.4-main.b97322e",
3
+ "version": "0.8.4-main.bc2380dfbc",
4
4
  "description": "Geo components.",
5
5
  "homepage": "https://github.com/dxos",
6
6
  "bugs": "https://github.com/dxos/issues",
7
- "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
11
+ "license": "FSL-1.1-Apache-2.0",
8
12
  "author": "DXOS.org",
9
13
  "sideEffects": true,
10
14
  "type": "module",
15
+ "imports": {
16
+ "#translations": "./src/translations.ts"
17
+ },
11
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
+ },
12
25
  "./data": {
26
+ "source": "./src/data.ts",
13
27
  "types": "./dist/types/src/data.d.ts",
14
28
  "browser": "./dist/lib/browser/data.mjs",
15
29
  "node": "./dist/lib/node-esm/data.mjs"
16
30
  },
17
- ".": {
18
- "types": "./dist/types/src/index.d.ts",
19
- "browser": "./dist/lib/browser/index.mjs",
20
- "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"
21
36
  }
22
37
  },
23
38
  "types": "dist/types/src/index.d.ts",
24
- "typesVersions": {
25
- "*": {
26
- "data": [
27
- "dist/types/src/data.d.ts"
28
- ]
29
- }
30
- },
31
39
  "files": [
32
40
  "data",
33
41
  "dist",
34
42
  "src"
35
43
  ],
36
44
  "dependencies": {
37
- "@preact-signals/safe-react": "^0.9.0",
45
+ "@radix-ui/react-compose-refs": "1.1.1",
46
+ "@radix-ui/react-context": "1.1.1",
38
47
  "d3": "^7.9.0",
39
48
  "d3-geo-projection": "^4.0.0",
40
49
  "d3-hexbin": "^0.2.2",
41
50
  "geojson": "^0.5.0",
42
51
  "leaflet": "^1.9.4",
43
52
  "lodash.defaultsdeep": "^4.6.1",
44
- "react-leaflet": "^4.2.1",
53
+ "react-leaflet": "^5.0.0",
45
54
  "react-resize-detector": "^11.0.1",
46
55
  "topojson-client": "^3.1.0",
47
56
  "topojson-simplify": "^3.0.3",
48
57
  "versor": "^0.2.0",
49
- "@dxos/async": "0.8.4-main.b97322e",
50
- "@dxos/debug": "0.8.4-main.b97322e",
51
- "@dxos/log": "0.8.4-main.b97322e",
52
- "@dxos/node-std": "0.8.4-main.b97322e",
53
- "@dxos/util": "0.8.4-main.b97322e"
58
+ "@dxos/async": "0.8.4-main.bc2380dfbc",
59
+ "@dxos/log": "0.8.4-main.bc2380dfbc",
60
+ "@dxos/debug": "0.8.4-main.bc2380dfbc",
61
+ "@dxos/node-std": "0.8.4-main.bc2380dfbc",
62
+ "@dxos/util": "0.8.4-main.bc2380dfbc"
54
63
  },
55
64
  "devDependencies": {
56
- "@react-three/drei": "^9.99.0",
57
- "@react-three/fiber": "^8.15.0",
65
+ "@react-three/drei": "^10.7.7",
66
+ "@react-three/fiber": "^9.5.0",
58
67
  "@types/d3": "^7.4.3",
59
68
  "@types/geojson": "^7946.0.14",
60
69
  "@types/leaflet": "^1.9.16",
61
- "@types/react": "~18.2.0",
62
- "@types/react-dom": "~18.2.0",
70
+ "@types/react": "~19.2.7",
71
+ "@types/react-dom": "~19.2.3",
63
72
  "@types/three": "0.165.0",
64
73
  "@types/topojson-client": "^3.1.4",
65
74
  "@types/topojson-simplify": "^3.0.3",
66
75
  "@types/topojson-specification": "^1.0.5",
67
76
  "JSONStream": "^1.3.5",
68
77
  "geojson2h3": "^1.2.0",
69
- "leva": "^0.9.35",
70
- "react": "~18.2.0",
71
- "react-dom": "~18.2.0",
72
- "three": "0.165.0",
73
- "@dxos/react-ui-theme": "0.8.4-main.b97322e",
74
- "@dxos/react-ui": "0.8.4-main.b97322e",
75
- "@dxos/storybook-utils": "0.8.4-main.b97322e"
78
+ "leva": "^0.10.1",
79
+ "react": "~19.2.3",
80
+ "react-dom": "~19.2.3",
81
+ "three": "^0.178.0",
82
+ "@dxos/ui-theme": "0.8.4-main.bc2380dfbc",
83
+ "@dxos/storybook-utils": "0.8.4-main.bc2380dfbc",
84
+ "@dxos/react-ui": "0.8.4-main.bc2380dfbc"
76
85
  },
77
86
  "peerDependencies": {
78
- "react": "~18.2.0",
79
- "react-dom": "~18.2.0",
80
- "@dxos/react-ui": "0.8.4-main.b97322e",
81
- "@dxos/react-ui-theme": "0.8.4-main.b97322e"
87
+ "react": "~19.2.3",
88
+ "react-dom": "~19.2.3",
89
+ "@dxos/react-ui": "0.8.4-main.bc2380dfbc",
90
+ "@dxos/ui-theme": "0.8.4-main.bc2380dfbc"
82
91
  },
83
92
  "publishConfig": {
84
93
  "access": "public"
@@ -2,21 +2,20 @@
2
2
  // Copyright 2018 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
- import { type Meta } from '@storybook/react-vite';
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import { type FeatureCollection, type Geometry, type Position } from 'geojson';
9
7
  import { Leva } from 'leva';
10
8
  import React, { useMemo, useRef, useState } from 'react';
11
9
  import { type Topology } from 'topojson-specification';
12
10
 
13
11
  import { useAsyncState } from '@dxos/react-ui';
14
- import { withTheme, withLayout } from '@dxos/storybook-utils';
12
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
15
13
 
16
- import { Globe, type GlobeCanvasProps, type GlobeController, type GlobeRootProps } from './Globe';
17
- import { useDrag, useGlobeZoomHandler, useSpinner, useTour, type Vector } from '../../hooks';
18
- import { closestPoint, type LatLng, type StyleSet } from '../../util';
14
+ import { type Vector, useDrag, useGlobeZoomHandler, useSpinner, useTour } from '../../hooks';
15
+ import { type LatLngLiteral } from '../../types';
16
+ import { type StyleSet, closestPoint } from '../../util';
19
17
  import { type ControlProps } from '../Toolbar';
18
+ import { Globe, type GlobeCanvasProps, type GlobeController, type GlobeRootProps } from './Globe';
20
19
 
21
20
  // TODO(burdon): Load from JSON at runtime?
22
21
  const useTopology = () => {
@@ -95,8 +94,11 @@ const createTrip = (
95
94
  routes: Record<string, string[]>,
96
95
  points: Position[] = [],
97
96
  ) => {
98
- let previousHub: LatLng;
99
- return Object.entries(routes).reduce<{ points: LatLng[]; lines: { source: LatLng; target: LatLng }[] }>(
97
+ let previousHub: LatLngLiteral;
98
+ return Object.entries(routes).reduce<{
99
+ points: LatLngLiteral[];
100
+ lines: { source: LatLngLiteral; target: LatLngLiteral }[];
101
+ }>(
100
102
  (features, [hub, regional]) => {
101
103
  const hubAirport = airports.features.find(({ properties }) => properties.iata === hub);
102
104
  if (hubAirport) {
@@ -125,7 +127,7 @@ const createTrip = (
125
127
  );
126
128
  };
127
129
 
128
- type StoryProps = Pick<GlobeRootProps, 'scale' | 'translation' | 'rotation'> &
130
+ type DefaultStoryProps = Pick<GlobeRootProps, 'zoom' | 'translation' | 'rotation'> &
129
131
  Pick<GlobeCanvasProps, 'projection' | 'styles'> & {
130
132
  drag?: boolean;
131
133
  spin?: boolean;
@@ -133,8 +135,8 @@ type StoryProps = Pick<GlobeRootProps, 'scale' | 'translation' | 'rotation'> &
133
135
  xAxis?: boolean;
134
136
  };
135
137
 
136
- const Story = ({
137
- scale: _scale = 1,
138
+ const DefaultStory = ({
139
+ zoom: zoomProp = 1,
138
140
  translation,
139
141
  rotation = [0, 0, 0],
140
142
  projection,
@@ -143,7 +145,7 @@ const Story = ({
143
145
  spin = false,
144
146
  tour = false,
145
147
  xAxis = false,
146
- }: StoryProps) => {
148
+ }: DefaultStoryProps) => {
147
149
  const controller = useRef<GlobeController>(null);
148
150
  const [dots] = useAsyncState(async () => {
149
151
  const points = (await import('../../../data/countries-dots-3.ts')).default;
@@ -154,6 +156,7 @@ const Story = ({
154
156
  });
155
157
  const [topology] = useTopology();
156
158
  const [airports] = useAsyncState(async () => (await import('../../../data/airports.ts')).default);
159
+
157
160
  const features = useMemo(() => {
158
161
  return airports ? createTrip(airports, routes, (dots?.objects.dots as any)?.geometries[0].coordinates) : undefined;
159
162
  }, [airports, routes, dots]);
@@ -188,24 +191,24 @@ const Story = ({
188
191
  break;
189
192
  }
190
193
  case 'zoom-in': {
191
- controller.current.setScale((scale) => scale * 1.1);
194
+ controller.current.setZoom((scale) => scale * 1.1);
192
195
  break;
193
196
  }
194
197
  case 'zoom-out': {
195
- controller.current.setScale((scale) => scale * 0.9);
198
+ controller.current.setZoom((scale) => scale * 0.9);
196
199
  break;
197
200
  }
198
201
  }
199
202
  };
200
203
 
201
204
  return (
202
- <Globe.Root classNames='absolute inset-0' scale={_scale} translation={translation} rotation={rotation}>
205
+ <Globe.Root zoom={zoomProp} translation={translation} rotation={rotation}>
203
206
  <Globe.Canvas
204
- ref={controller}
205
207
  topology={styles?.dots ? dots : topology}
206
208
  projection={projection}
207
209
  styles={styles}
208
210
  features={tour ? { points: features?.points ?? [] } : features}
211
+ ref={controller}
209
212
  />
210
213
  <Globe.Zoom onAction={handleAction} />
211
214
  <Globe.Action onAction={handleAction} />
@@ -219,11 +222,15 @@ const Story = ({
219
222
 
220
223
  const initialRotation: Vector = [0, -40, 0];
221
224
 
222
- const meta: Meta = {
225
+ const meta = {
223
226
  title: 'ui/react-ui-geo/Globe',
224
227
  component: Globe.Root,
225
- decorators: [withTheme, withLayout({ fullscreen: true, classNames: 'bg-[#000]' })],
226
- };
228
+ render: DefaultStory,
229
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
230
+ parameters: {
231
+ layout: 'fullscreen',
232
+ },
233
+ } satisfies Meta;
227
234
 
228
235
  export default meta;
229
236
 
@@ -234,7 +241,7 @@ export const Earth1 = () => {
234
241
  useDrag(controller);
235
242
 
236
243
  return (
237
- <Globe.Root scale={1.2} rotation={[Math.random() * 360, 0, 0]}>
244
+ <Globe.Root zoom={1.2} rotation={[Math.random() * 360, 0, 0]}>
238
245
  <Globe.Canvas ref={setController} topology={topology} styles={defaultStyles} />
239
246
  <Globe.Zoom onAction={handleAction} />
240
247
  </Globe.Root>
@@ -249,7 +256,7 @@ export const Earth2 = () => {
249
256
 
250
257
  return (
251
258
  <div className='absolute bottom-0 left-0 right-0 '>
252
- <Globe.Root classNames='h-[400px]' scale={2.8} translation={{ x: 0, y: 400 }}>
259
+ <Globe.Root classNames='h-[400px]' zoom={2.8} translation={{ x: 0, y: 400 }}>
253
260
  <Globe.Canvas ref={setController} topology={topology} styles={defaultStyles} />
254
261
  <Globe.Zoom onAction={handleAction} />
255
262
  </Globe.Root>
@@ -283,33 +290,73 @@ export const Mercator = () => {
283
290
  useDrag(controller);
284
291
 
285
292
  return (
286
- <Globe.Root classNames='flex grow overflow-hidden' scale={0.7} rotation={initialRotation}>
293
+ <Globe.Root classNames='flex grow overflow-hidden' zoom={0.7} rotation={initialRotation}>
287
294
  <Globe.Canvas ref={setController} topology={topology} projection='mercator' styles={monochrome} />
288
295
  <Globe.Zoom onAction={handleAction} />
289
296
  </Globe.Root>
290
297
  );
291
298
  };
292
299
 
293
- export const Globe1 = () => {
294
- return <Story drag projection='mercator' scale={0.8} rotation={initialRotation} styles={defaultStyles} />;
300
+ type Story = StoryObj<typeof DefaultStory>;
301
+
302
+ export const Globe1: Story = {
303
+ args: {
304
+ drag: true,
305
+ projection: 'mercator',
306
+ zoom: 0.8,
307
+ rotation: initialRotation,
308
+ styles: defaultStyles,
309
+ },
295
310
  };
296
311
 
297
- export const Globe2 = () => {
298
- return <Story drag projection='transverse-mercator' scale={0.8} rotation={initialRotation} styles={defaultStyles} />;
312
+ export const Globe2: Story = {
313
+ args: {
314
+ drag: true,
315
+ projection: 'transverse-mercator',
316
+ zoom: 0.8,
317
+ rotation: initialRotation,
318
+ styles: defaultStyles,
319
+ },
299
320
  };
300
321
 
301
- export const Globe3 = () => {
302
- return <Story drag spin scale={1.5} rotation={initialRotation} styles={defaultStyles} />;
322
+ export const Globe3: Story = {
323
+ args: {
324
+ drag: true,
325
+ spin: true,
326
+ zoom: 1.5,
327
+ rotation: initialRotation,
328
+ styles: defaultStyles,
329
+ },
303
330
  };
304
331
 
305
- export const Globe4 = () => {
306
- return <Story drag tour scale={2} rotation={initialRotation} styles={defaultStyles} />;
332
+ export const Globe4: Story = {
333
+ args: {
334
+ drag: true,
335
+ tour: true,
336
+ zoom: 2,
337
+ rotation: initialRotation,
338
+ styles: defaultStyles,
339
+ },
307
340
  };
308
341
 
309
- export const Globe5 = () => {
310
- return <Story drag tour scale={0.9} rotation={initialRotation} styles={dotStyles} />;
342
+ export const Globe5: Story = {
343
+ args: {
344
+ drag: true,
345
+ tour: true,
346
+ zoom: 0.9,
347
+ rotation: initialRotation,
348
+ styles: dotStyles,
349
+ },
311
350
  };
312
351
 
313
- export const Globe6 = () => {
314
- return <Story drag xAxis tour scale={2} translation={{ x: 0, y: 600 }} rotation={[0, -20, 0]} styles={dotStyles} />;
352
+ export const Globe6: Story = {
353
+ args: {
354
+ drag: true,
355
+ xAxis: true,
356
+ tour: true,
357
+ zoom: 2,
358
+ translation: { x: 0, y: 600 },
359
+ rotation: [0, -20, 0],
360
+ styles: dotStyles,
361
+ },
315
362
  };
@@ -4,14 +4,14 @@
4
4
 
5
5
  import {
6
6
  type GeoProjection,
7
+ easeLinear,
8
+ easeSinOut,
7
9
  geoMercator,
8
10
  geoOrthographic,
9
11
  geoPath,
10
12
  geoTransverseMercator,
11
13
  interpolateNumber,
12
14
  transition,
13
- easeLinear,
14
- easeSinOut,
15
15
  } from 'd3';
16
16
  import { type ControlPosition } from 'leaflet';
17
17
  import React, {
@@ -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 ThemedClassName, type ThemeMode, useDynamicRef, useThemeContext } from '@dxos/react-ui';
30
- import { mx } from '@dxos/react-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,
@@ -44,7 +46,7 @@ import {
44
46
  renderLayers,
45
47
  timer,
46
48
  } from '../../util';
47
- import { ZoomControls, ActionControls, type ControlProps, controlPositions } from '../Toolbar';
49
+ import { ActionControls, type ControlProps, ZoomControls, controlPositions } from '../Toolbar';
48
50
 
49
51
  /**
50
52
  * https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute
@@ -103,7 +105,7 @@ const defaultStyles: Record<ThemeMode, StyleSet> = {
103
105
  export type GlobeController = {
104
106
  canvas: HTMLCanvasElement;
105
107
  projection: GeoProjection;
106
- } & Pick<GlobeContextType, 'scale' | 'translation' | 'rotation' | 'setScale' | 'setTranslation' | 'setRotation'>;
108
+ } & Pick<GlobeContextType, 'zoom' | 'translation' | 'rotation' | 'setZoom' | 'setTranslation' | 'setRotation'>;
107
109
 
108
110
  export type ProjectionType = 'orthographic' | 'mercator' | 'transverse-mercator';
109
111
 
@@ -126,18 +128,43 @@ const getProjection = (type: GlobeCanvasProps['projection'] = 'orthographic'): G
126
128
  // Root
127
129
  //
128
130
 
129
- type GlobeRootProps = PropsWithChildren<ThemedClassName<GlobeContextProviderProps>>;
130
-
131
- const GlobeRoot = ({ classNames, children, ...props }: GlobeRootProps) => {
132
- const { ref, width, height } = useResizeDetector<HTMLDivElement>();
133
- return (
134
- <div ref={ref} className={mx('relative flex grow overflow-hidden', classNames)}>
135
- <GlobeContextProvider size={{ width, height }} {...props}>
136
- {children}
137
- </GlobeContextProvider>
138
- </div>
139
- );
140
- };
131
+ type GlobeRootProps = Partial<Pick<GlobeContextType, 'center' | 'zoom' | 'translation' | 'rotation'>>;
132
+
133
+ const GlobeRoot = composable<HTMLDivElement, GlobeRootProps>(
134
+ (
135
+ { children, center: centerProp, zoom: zoomProp, translation: translationProp, rotation: rotationProp, ...props },
136
+ forwardedRef,
137
+ ) => {
138
+ const localRef = useRef<HTMLDivElement>(null);
139
+ const composedRef = useComposedRefs<HTMLDivElement>(localRef, forwardedRef);
140
+ const { width, height } = useResizeDetector<HTMLDivElement>({ targetRef: localRef });
141
+
142
+ const [center, setCenter] = useControlledState(centerProp);
143
+ const [zoom, setZoom] = useControlledState(zoomProp ?? 4);
144
+ const [translation, setTranslation] = useControlledState<Point>(translationProp);
145
+ const [rotation, setRotation] = useControlledState<Vector>(rotationProp);
146
+
147
+ return (
148
+ <GlobeContext.Provider
149
+ value={{
150
+ size: { width, height },
151
+ center,
152
+ zoom,
153
+ translation,
154
+ rotation,
155
+ setCenter,
156
+ setZoom,
157
+ setTranslation,
158
+ setRotation,
159
+ }}
160
+ >
161
+ <div {...composableProps(props, { classNames: 'relative dx-container' })} ref={composedRef}>
162
+ {children}
163
+ </div>
164
+ </GlobeContext.Provider>
165
+ );
166
+ },
167
+ );
141
168
 
142
169
  //
143
170
  // Canvas
@@ -154,17 +181,18 @@ type GlobeCanvasProps = {
154
181
  * Basic globe renderer.
155
182
  * https://github.com/topojson/world-atlas
156
183
  */
184
+ // TODO(burdon): Move controller to root.
157
185
  const GlobeCanvas = forwardRef<GlobeController, GlobeCanvasProps>(
158
- ({ projection: _projection, topology, features, styles: _styles }, forwardRef) => {
186
+ ({ projection: projectionProp, topology, features, styles: stylesProp }, forwardRef) => {
159
187
  const { themeMode } = useThemeContext();
160
- const styles = useMemo(() => _styles ?? defaultStyles[themeMode], [_styles, themeMode]);
188
+ const styles = useMemo(() => stylesProp ?? defaultStyles[themeMode], [stylesProp, themeMode]);
161
189
 
162
190
  // Canvas.
163
191
  const [canvas, setCanvas] = useState<HTMLCanvasElement>(null);
164
192
  const canvasRef = (canvas: HTMLCanvasElement) => setCanvas(canvas);
165
193
 
166
194
  // Projection.
167
- const projection = useMemo(() => getProjection(_projection), [_projection]);
195
+ const projection = useMemo(() => getProjection(projectionProp), [projectionProp]);
168
196
 
169
197
  // Layers.
170
198
  // TODO(burdon): Generate on the fly based on what is visible.
@@ -173,15 +201,14 @@ const GlobeCanvas = forwardRef<GlobeController, GlobeCanvasProps>(
173
201
  }, [topology, features, styles]);
174
202
 
175
203
  // State.
176
- const { size, center, scale, translation, rotation, setCenter, setScale, setTranslation, setRotation } =
204
+ const { size, center, zoom, translation, rotation, setCenter, setZoom, setTranslation, setRotation } =
177
205
  useGlobeContext();
178
-
179
- const scaleRef = useDynamicRef(scale);
206
+ const zoomRef = useDynamicRef(zoom);
180
207
 
181
208
  // Update rotation.
182
209
  useEffect(() => {
183
210
  if (center) {
184
- setScale(1);
211
+ setZoom(1);
185
212
  setRotation(positionToRotation(geoToPosition(center)));
186
213
  }
187
214
  }, [center]);
@@ -193,25 +220,25 @@ const GlobeCanvas = forwardRef<GlobeController, GlobeCanvasProps>(
193
220
  canvas,
194
221
  projection,
195
222
  center,
196
- get scale() {
197
- return scaleRef.current;
223
+ get zoom() {
224
+ return zoomRef.current;
198
225
  },
199
226
  translation,
200
227
  rotation,
201
228
  setCenter,
202
- setScale: (s) => {
203
- if (typeof s === 'function') {
204
- const is = interpolateNumber(scaleRef.current, s(scaleRef.current));
229
+ setZoom: (state) => {
230
+ if (typeof state === 'function') {
231
+ const is = interpolateNumber(zoomRef.current, state(zoomRef.current));
205
232
  // Stop easing if already zooming.
206
233
  transition()
207
234
  .ease(zooming.current ? easeLinear : easeSinOut)
208
235
  .duration(200)
209
- .tween('scale', () => (t) => setScale(is(t)))
236
+ .tween('scale', () => (t) => setZoom(is(t)))
210
237
  .on('end', () => {
211
238
  zooming.current = false;
212
239
  });
213
240
  } else {
214
- setScale(s);
241
+ setZoom(state);
215
242
  }
216
243
  },
217
244
  setTranslation,
@@ -232,14 +259,14 @@ const GlobeCanvas = forwardRef<GlobeController, GlobeCanvasProps>(
232
259
  timer(() => {
233
260
  // https://d3js.org/d3-geo/projection
234
261
  projection
235
- .scale((Math.min(size.width, size.height) / 2) * scale)
262
+ .scale((Math.min(size.width, size.height) / 2) * zoom)
236
263
  .translate([size.width / 2 + (translation?.x ?? 0), size.height / 2 + (translation?.y ?? 0)])
237
264
  .rotate(rotation ?? [0, 0, 0]);
238
265
 
239
- renderLayers(generator, layers, scale, styles);
266
+ renderLayers(generator, layers, zoom, styles);
240
267
  });
241
268
  }
242
- }, [generator, size, scale, translation, rotation, layers]);
269
+ }, [generator, size, zoom, translation, rotation, layers]);
243
270
 
244
271
  if (!size.width || !size.height) {
245
272
  return null;
@@ -249,22 +276,30 @@ const GlobeCanvas = forwardRef<GlobeController, GlobeCanvasProps>(
249
276
  },
250
277
  );
251
278
 
279
+ //
280
+ // Debug
281
+ //
282
+
252
283
  const GlobeDebug = ({ position = 'topleft' }: { position?: ControlPosition }) => {
253
- const { size, scale, translation, rotation } = useGlobeContext();
284
+ const { size, zoom, translation, rotation } = useGlobeContext();
254
285
  return (
255
286
  <div
256
287
  className={mx(
257
- 'z-10 absolute w-96 p-2 overflow-hidden border border-green-700 rounded',
288
+ 'z-10 absolute w-96 p-2 overflow-hidden border border-green-700 rounded-sm',
258
289
  controlPositions[position],
259
290
  )}
260
291
  >
261
292
  <pre className='font-mono text-xs text-green-700'>
262
- {JSON.stringify({ size, scale, translation, rotation }, null, 2)}
293
+ {JSON.stringify({ size, zoom, translation, rotation }, null, 2)}
263
294
  </pre>
264
295
  </div>
265
296
  );
266
297
  };
267
298
 
299
+ //
300
+ // Panel
301
+ //
302
+
268
303
  const GlobePanel = ({
269
304
  position,
270
305
  classNames,
@@ -273,25 +308,37 @@ const GlobePanel = ({
273
308
  return <div className={mx('z-10 absolute overflow-hidden', controlPositions[position], classNames)}>{children}</div>;
274
309
  };
275
310
 
311
+ //
312
+ // Controls
313
+ //
314
+
276
315
  const CustomControl = ({ position, children }: PropsWithChildren<{ position: ControlPosition }>) => {
277
316
  return <div className={mx('z-10 absolute overflow-hidden', controlPositions[position])}>{children}</div>;
278
317
  };
279
318
 
280
319
  type GlobeControlProps = { position?: ControlPosition } & Pick<ControlProps, 'onAction'>;
281
320
 
321
+ const GlobeZoom = ({ onAction, position = 'bottomleft', ...props }: GlobeControlProps) => (
322
+ <CustomControl position={position} {...props}>
323
+ <ZoomControls onAction={onAction} />
324
+ </CustomControl>
325
+ );
326
+
327
+ const GlobeAction = ({ onAction, position = 'bottomright', ...props }: GlobeControlProps) => (
328
+ <CustomControl position={position} {...props}>
329
+ <ActionControls onAction={onAction} />
330
+ </CustomControl>
331
+ );
332
+
333
+ //
334
+ // Globe
335
+ //
336
+
282
337
  export const Globe = {
283
338
  Root: GlobeRoot,
284
339
  Canvas: GlobeCanvas,
285
- Zoom: ({ onAction, position = 'bottomleft', ...props }: GlobeControlProps) => (
286
- <CustomControl position={position} {...props}>
287
- <ZoomControls onAction={onAction} />
288
- </CustomControl>
289
- ),
290
- Action: ({ onAction, position = 'bottomright', ...props }: GlobeControlProps) => (
291
- <CustomControl position={position} {...props}>
292
- <ActionControls onAction={onAction} />
293
- </CustomControl>
294
- ),
340
+ Zoom: GlobeZoom,
341
+ Action: GlobeAction,
295
342
  Debug: GlobeDebug,
296
343
  Panel: GlobePanel,
297
344
  };