@dxos/react-ui-geo 0.8.3 → 0.8.4-main.1068cf700f

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