@dxos/react-ui-geo 0.8.4-main.fd6878d → 0.8.4-staging.60fe92afc8

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 (121) hide show
  1. package/LICENSE +102 -5
  2. package/data/countries-10m.ts +12 -0
  3. package/data/countries-110m.ts +4 -10579
  4. package/data/countries-50m.ts +12 -0
  5. package/dist/lib/browser/chunk-SC2FBYFU.mjs +17 -0
  6. package/dist/lib/browser/chunk-SC2FBYFU.mjs.map +7 -0
  7. package/dist/lib/browser/countries-10m-CWWDOKH7.mjs +6 -0
  8. package/dist/lib/browser/countries-10m-CWWDOKH7.mjs.map +7 -0
  9. package/dist/lib/browser/countries-110m-72QBAA5E.mjs +6 -0
  10. package/dist/lib/browser/countries-110m-72QBAA5E.mjs.map +7 -0
  11. package/dist/lib/browser/countries-50m-H7SL7KVF.mjs +6 -0
  12. package/dist/lib/browser/countries-50m-H7SL7KVF.mjs.map +7 -0
  13. package/dist/lib/browser/data.mjs +1 -1
  14. package/dist/lib/browser/index.mjs +1054 -578
  15. package/dist/lib/browser/index.mjs.map +4 -4
  16. package/dist/lib/browser/meta.json +1 -1
  17. package/dist/lib/browser/translations.mjs +19 -0
  18. package/dist/lib/browser/translations.mjs.map +7 -0
  19. package/dist/lib/node-esm/chunk-VZENBYLJ.mjs +19 -0
  20. package/dist/lib/node-esm/chunk-VZENBYLJ.mjs.map +7 -0
  21. package/dist/lib/node-esm/countries-10m-DJZV66KG.mjs +8 -0
  22. package/dist/lib/node-esm/countries-10m-DJZV66KG.mjs.map +7 -0
  23. package/dist/lib/node-esm/countries-110m-H3WY6K4Q.mjs +8 -0
  24. package/dist/lib/node-esm/countries-110m-H3WY6K4Q.mjs.map +7 -0
  25. package/dist/lib/node-esm/countries-50m-ZY7Z3IWD.mjs +8 -0
  26. package/dist/lib/node-esm/countries-50m-ZY7Z3IWD.mjs.map +7 -0
  27. package/dist/lib/node-esm/data.mjs +1 -1
  28. package/dist/lib/node-esm/index.mjs +1054 -578
  29. package/dist/lib/node-esm/index.mjs.map +4 -4
  30. package/dist/lib/node-esm/meta.json +1 -1
  31. package/dist/lib/node-esm/translations.mjs +21 -0
  32. package/dist/lib/node-esm/translations.mjs.map +7 -0
  33. package/dist/types/data/airports.d.ts +4 -4
  34. package/dist/types/data/airports.d.ts.map +1 -1
  35. package/dist/types/data/cities.d.ts.map +1 -1
  36. package/dist/types/data/countries-10m.d.ts +8 -0
  37. package/dist/types/data/countries-10m.d.ts.map +1 -0
  38. package/dist/types/data/countries-110m.d.ts +2 -30
  39. package/dist/types/data/countries-110m.d.ts.map +1 -1
  40. package/dist/types/data/countries-50m.d.ts +8 -0
  41. package/dist/types/data/countries-50m.d.ts.map +1 -0
  42. package/dist/types/data/countries-dots-3.d.ts.map +1 -1
  43. package/dist/types/data/countries-dots-4.d.ts.map +1 -1
  44. package/dist/types/src/components/Globe/Globe.d.ts +19 -9
  45. package/dist/types/src/components/Globe/Globe.d.ts.map +1 -1
  46. package/dist/types/src/components/Globe/Globe.stories.d.ts +37 -11
  47. package/dist/types/src/components/Globe/Globe.stories.d.ts.map +1 -1
  48. package/dist/types/src/components/Map/Map.d.ts +55 -16
  49. package/dist/types/src/components/Map/Map.d.ts.map +1 -1
  50. package/dist/types/src/components/Map/Map.stories.d.ts +19 -9
  51. package/dist/types/src/components/Map/Map.stories.d.ts.map +1 -1
  52. package/dist/types/src/components/Toolbar/Controls.d.ts.map +1 -1
  53. package/dist/types/src/data.d.ts +9 -1
  54. package/dist/types/src/data.d.ts.map +1 -1
  55. package/dist/types/src/hooks/context.d.ts +38 -3
  56. package/dist/types/src/hooks/context.d.ts.map +1 -1
  57. package/dist/types/src/hooks/index.d.ts +3 -0
  58. package/dist/types/src/hooks/index.d.ts.map +1 -1
  59. package/dist/types/src/hooks/useDrag.d.ts +22 -2
  60. package/dist/types/src/hooks/useDrag.d.ts.map +1 -1
  61. package/dist/types/src/hooks/useGlobeZoomHandler.d.ts +3 -2
  62. package/dist/types/src/hooks/useGlobeZoomHandler.d.ts.map +1 -1
  63. package/dist/types/src/hooks/useMapZoomHandler.d.ts +1 -1
  64. package/dist/types/src/hooks/useMapZoomHandler.d.ts.map +1 -1
  65. package/dist/types/src/hooks/useSimplifiedTopology.d.ts +32 -0
  66. package/dist/types/src/hooks/useSimplifiedTopology.d.ts.map +1 -0
  67. package/dist/types/src/hooks/useSpinner.d.ts +1 -1
  68. package/dist/types/src/hooks/useSpinner.d.ts.map +1 -1
  69. package/dist/types/src/hooks/useTopology.d.ts +26 -0
  70. package/dist/types/src/hooks/useTopology.d.ts.map +1 -0
  71. package/dist/types/src/hooks/useTour.d.ts +3 -2
  72. package/dist/types/src/hooks/useTour.d.ts.map +1 -1
  73. package/dist/types/src/hooks/useWheel.d.ts +24 -0
  74. package/dist/types/src/hooks/useWheel.d.ts.map +1 -0
  75. package/dist/types/src/index.d.ts +0 -1
  76. package/dist/types/src/index.d.ts.map +1 -1
  77. package/dist/types/src/translations.d.ts +12 -0
  78. package/dist/types/src/translations.d.ts.map +1 -0
  79. package/dist/types/src/util/animation.d.ts +16 -0
  80. package/dist/types/src/util/animation.d.ts.map +1 -0
  81. package/dist/types/src/util/debug.d.ts.map +1 -1
  82. package/dist/types/src/util/index.d.ts +2 -0
  83. package/dist/types/src/util/index.d.ts.map +1 -1
  84. package/dist/types/src/util/inertia.d.ts.map +1 -1
  85. package/dist/types/src/util/path.d.ts.map +1 -1
  86. package/dist/types/src/util/render.d.ts +25 -1
  87. package/dist/types/src/util/render.d.ts.map +1 -1
  88. package/dist/types/src/util/styles.d.ts +4 -0
  89. package/dist/types/src/util/styles.d.ts.map +1 -0
  90. package/dist/types/tsconfig.tsbuildinfo +1 -1
  91. package/package.json +43 -37
  92. package/src/components/Globe/Globe.stories.tsx +201 -82
  93. package/src/components/Globe/Globe.tsx +263 -119
  94. package/src/components/Map/Map.stories.tsx +66 -17
  95. package/src/components/Map/Map.tsx +333 -120
  96. package/src/components/Toolbar/Controls.tsx +12 -14
  97. package/src/data.ts +19 -2
  98. package/src/hooks/context.tsx +49 -28
  99. package/src/hooks/index.ts +3 -0
  100. package/src/hooks/useDrag.ts +33 -5
  101. package/src/hooks/useGlobeZoomHandler.ts +10 -3
  102. package/src/hooks/useSimplifiedTopology.ts +81 -0
  103. package/src/hooks/useSpinner.ts +1 -2
  104. package/src/hooks/useTopology.ts +95 -0
  105. package/src/hooks/useTour.ts +71 -81
  106. package/src/hooks/useWheel.ts +83 -0
  107. package/src/index.ts +0 -1
  108. package/src/translations.ts +20 -0
  109. package/src/util/animation.ts +35 -0
  110. package/src/util/index.ts +2 -0
  111. package/src/util/inertia.ts +87 -4
  112. package/src/util/render.ts +105 -17
  113. package/src/util/styles.ts +62 -0
  114. package/dist/lib/browser/chunk-GMWLKTLN.mjs +0 -9
  115. package/dist/lib/browser/chunk-GMWLKTLN.mjs.map +0 -7
  116. package/dist/lib/browser/countries-110m-ZM3ZIEFS.mjs +0 -37859
  117. package/dist/lib/browser/countries-110m-ZM3ZIEFS.mjs.map +0 -7
  118. package/dist/lib/node-esm/chunk-JODBF4CC.mjs +0 -11
  119. package/dist/lib/node-esm/chunk-JODBF4CC.mjs.map +0 -7
  120. package/dist/lib/node-esm/countries-110m-3SFASWVD.mjs +0 -37861
  121. package/dist/lib/node-esm/countries-110m-3SFASWVD.mjs.map +0 -7
@@ -1,46 +1,20 @@
1
1
  import { createRequire } from 'node:module';const require = createRequire(import.meta.url);
2
2
  import {
3
3
  loadTopology
4
- } from "./chunk-JODBF4CC.mjs";
4
+ } from "./chunk-VZENBYLJ.mjs";
5
5
 
6
6
  // src/components/Globe/Globe.tsx
7
- import { useSignals as _useSignals3 } from "@preact-signals/safe-react/tracking";
8
- import { easeLinear, easeSinOut, geoMercator, geoOrthographic, geoPath as geoPath2, geoTransverseMercator, interpolateNumber, transition } from "d3";
9
- import React3, { forwardRef, useEffect as useEffect4, useImperativeHandle, useMemo as useMemo2, useRef, useState as useState3 } from "react";
7
+ import { selection as d3Selection, easeLinear, easeSinOut, geoMercator, geoOrthographic, geoPath as geoPath2, geoTransverseMercator, interpolateNumber, transition } from "d3";
8
+ import React2, { forwardRef, useCallback as useCallback3, useEffect as useEffect6, useId, useImperativeHandle, useMemo as useMemo2, useRef, useState as useState4 } from "react";
10
9
  import { useResizeDetector } from "react-resize-detector";
11
- import { useDynamicRef, useThemeContext } from "@dxos/react-ui";
12
- import { mx } from "@dxos/react-ui-theme";
10
+ import { useComposedRefs, useControlledState, useDynamicRef, useThemeContext } from "@dxos/react-ui";
11
+ import { composable, composableProps } from "@dxos/react-ui";
12
+ import { mx } from "@dxos/ui-theme";
13
13
 
14
14
  // src/hooks/context.tsx
15
- import { useSignals as _useSignals } from "@preact-signals/safe-react/tracking";
16
- import React, { createContext, useContext } from "react";
15
+ import { createContext, useContext } from "react";
17
16
  import { raise } from "@dxos/debug";
18
- import { useControlledState } from "@dxos/react-ui";
19
17
  var GlobeContext = /* @__PURE__ */ createContext(void 0);
20
- var GlobeContextProvider = ({ children, size, center: _center, zoom: _zoom, translation: _translation, rotation: _rotation }) => {
21
- var _effect = _useSignals();
22
- try {
23
- const [center, setCenter] = useControlledState(_center);
24
- const [zoom, setZoom] = useControlledState(_zoom);
25
- const [translation, setTranslation] = useControlledState(_translation);
26
- const [rotation, setRotation] = useControlledState(_rotation);
27
- return /* @__PURE__ */ React.createElement(GlobeContext.Provider, {
28
- value: {
29
- size,
30
- center,
31
- zoom,
32
- translation,
33
- rotation,
34
- setCenter,
35
- setZoom,
36
- setTranslation,
37
- setRotation
38
- }
39
- }, children);
40
- } finally {
41
- _effect.f();
42
- }
43
- };
44
18
  var useGlobeContext = () => {
45
19
  return useContext(GlobeContext) ?? raise(new Error("Missing GlobeContext"));
46
20
  };
@@ -49,6 +23,18 @@ var useGlobeContext = () => {
49
23
  import { select as select2 } from "d3";
50
24
  import { useEffect } from "react";
51
25
 
26
+ // src/util/animation.ts
27
+ import { geoDistance } from "d3";
28
+ import versor from "versor";
29
+ var flyDuration = (p1, p2, base, scale) => Math.max(base, geoDistance(p1, p2) * scale);
30
+ var createRotationTween = (projection, setRotation, r1, r2) => {
31
+ const iv = versor.interpolate(r1, r2);
32
+ return (t) => {
33
+ projection.rotate(iv(t));
34
+ setRotation(projection.rotate());
35
+ };
36
+ };
37
+
52
38
  // src/util/debug.ts
53
39
  var debug = false;
54
40
  var timer = (cb) => {
@@ -66,7 +52,7 @@ var timer = (cb) => {
66
52
 
67
53
  // src/util/inertia.ts
68
54
  import { drag, select, timer as timer2 } from "d3";
69
- import versor from "versor";
55
+ import versor2 from "versor";
70
56
  var restrictAxis = (axis) => (original, current) => current.map((d, i) => axis[i] ? d : original[i]);
71
57
  var geoInertiaDrag = (target, render, projection, options) => {
72
58
  if (!options) {
@@ -76,21 +62,23 @@ var geoInertiaDrag = (target, render, projection, options) => {
76
62
  target = target.node();
77
63
  }
78
64
  target = select(target);
79
- const inertia = geoInertiaDragHelper({
65
+ const linear = (options.mode ?? "linear") === "linear";
66
+ const axis = restrictAxis(options.lockTilt ? [
67
+ true,
68
+ false,
69
+ false
70
+ ] : [
71
+ true,
72
+ true,
73
+ true
74
+ ]);
75
+ const sharedHandlers = {
80
76
  projection,
81
77
  render: (rotation) => {
82
78
  projection.rotate(rotation);
83
79
  render && render();
84
80
  },
85
- axis: restrictAxis(options.xAxis ? [
86
- true,
87
- false,
88
- false
89
- ] : [
90
- true,
91
- true,
92
- true
93
- ]),
81
+ axis,
94
82
  start: options.start,
95
83
  move: options.move,
96
84
  end: options.end,
@@ -98,7 +86,12 @@ var geoInertiaDrag = (target, render, projection, options) => {
98
86
  finish: options.finish,
99
87
  time: options.time,
100
88
  hold: options.hold
101
- });
89
+ };
90
+ const inertia = linear ? geoInertiaDragLinearHelper({
91
+ ...sharedHandlers,
92
+ sensitivity: options.sensitivity,
93
+ getZoom: options.getZoom
94
+ }) : geoInertiaDragHelper(sharedHandlers);
102
95
  target.call(drag().on("start", inertia.start).on("drag", inertia.move).on("end", inertia.end));
103
96
  return inertia;
104
97
  };
@@ -113,9 +106,9 @@ var geoInertiaDragHelper = (opt) => {
113
106
  const inertia = inertiaHelper({
114
107
  axis: opt.axis,
115
108
  start: () => {
116
- v0 = versor.cartesian(projection.invert(inertia.position));
109
+ v0 = versor2.cartesian(projection.invert(inertia.position));
117
110
  r0 = projection.rotate();
118
- q0 = versor(r0);
111
+ q0 = versor2(r0);
119
112
  opt.start && opt.start();
120
113
  },
121
114
  move: () => {
@@ -123,23 +116,23 @@ var geoInertiaDragHelper = (opt) => {
123
116
  if (isNaN(inv[0])) {
124
117
  return;
125
118
  }
126
- const v1 = versor.cartesian(inv);
127
- const q1 = versor.multiply(q0, versor.delta(v0, v1));
128
- const r1 = versor.rotation(q1);
119
+ const v1 = versor2.cartesian(inv);
120
+ const q1 = versor2.multiply(q0, versor2.delta(v0, v1));
121
+ const r1 = versor2.rotation(q1);
129
122
  const r2 = opt.axis(r0, r1);
130
123
  opt.render(r2);
131
124
  opt.move && opt.move();
132
125
  },
133
126
  end: () => {
134
- v10 = versor.cartesian(projection.invert(inertia.position.map((d, i) => d - inertia.velocity[i] / 1e3)));
135
- q10 = versor(projection.rotate());
136
- v11 = versor.cartesian(projection.invert(inertia.position));
127
+ v10 = versor2.cartesian(projection.invert(inertia.position.map((d, i) => d - inertia.velocity[i] / 1e3)));
128
+ q10 = versor2(projection.rotate());
129
+ v11 = versor2.cartesian(projection.invert(inertia.position));
137
130
  opt.end && opt.end();
138
131
  },
139
132
  stop: opt.stop,
140
133
  finish: opt.finish,
141
134
  render: (t) => {
142
- const r1 = versor.rotation(versor.multiply(q10, versor.delta(v10, v11, t * 1e3)));
135
+ const r1 = versor2.rotation(versor2.multiply(q10, versor2.delta(v10, v11, t * 1e3)));
143
136
  const r2 = opt.axis(r0, r1);
144
137
  opt.render && opt.render(r2);
145
138
  },
@@ -147,6 +140,62 @@ var geoInertiaDragHelper = (opt) => {
147
140
  });
148
141
  return inertia;
149
142
  };
143
+ var DEFAULT_LINEAR_SENSITIVITY = 0.25;
144
+ var geoInertiaDragLinearHelper = (opt) => {
145
+ const projection = opt.projection;
146
+ const sensitivity = opt.sensitivity ?? DEFAULT_LINEAR_SENSITIVITY;
147
+ const gain = () => sensitivity / Math.max(opt.getZoom?.() ?? 1, 0.1);
148
+ let r0;
149
+ let p0;
150
+ let kStart;
151
+ let rEnd;
152
+ let vEnd;
153
+ const inertia = inertiaHelper({
154
+ axis: opt.axis,
155
+ start: () => {
156
+ r0 = projection.rotate();
157
+ p0 = [
158
+ inertia.position[0],
159
+ inertia.position[1]
160
+ ];
161
+ kStart = gain();
162
+ opt.start && opt.start();
163
+ },
164
+ move: () => {
165
+ const dx = inertia.position[0] - p0[0];
166
+ const dy = inertia.position[1] - p0[1];
167
+ const r1 = [
168
+ r0[0] + dx * kStart,
169
+ r0[1] - dy * kStart,
170
+ 0
171
+ ];
172
+ const r2 = opt.axis(r0, r1);
173
+ opt.render(r2);
174
+ opt.move && opt.move();
175
+ },
176
+ end: () => {
177
+ rEnd = projection.rotate();
178
+ vEnd = [
179
+ inertia.velocity[0],
180
+ inertia.velocity[1]
181
+ ];
182
+ opt.end && opt.end();
183
+ },
184
+ stop: opt.stop,
185
+ finish: opt.finish,
186
+ render: (t) => {
187
+ const r1 = [
188
+ rEnd[0] + vEnd[0] * kStart * t,
189
+ rEnd[1] - vEnd[1] * kStart * t,
190
+ 0
191
+ ];
192
+ const r2 = opt.axis(rEnd, r1);
193
+ opt.render && opt.render(r2);
194
+ },
195
+ time: opt.time
196
+ });
197
+ return inertia;
198
+ };
150
199
  function inertiaHelper(opt) {
151
200
  const A = opt.time || 5e3;
152
201
  const limit = 1.0001;
@@ -250,9 +299,9 @@ var geoToPosition = ({ lat, lng }) => [
250
299
  lng,
251
300
  lat
252
301
  ];
253
- var geoPoint = (point) => ({
302
+ var geoPoint = (point2) => ({
254
303
  type: "Point",
255
- coordinates: geoToPosition(point)
304
+ coordinates: geoToPosition(point2)
256
305
  });
257
306
  var geoCircle = ({ lat, lng }, radius) => d3GeoCircle().radius(radius).center([
258
307
  lng,
@@ -277,11 +326,11 @@ var closestPoint = (points, target) => {
277
326
  }
278
327
  let closestPoint2 = points[0];
279
328
  let minDistance = getDistance(points[0], target);
280
- for (const point of points) {
281
- const distance = getDistance(point, target);
329
+ for (const point2 of points) {
330
+ const distance = getDistance(point2, target);
282
331
  if (distance < minDistance) {
283
332
  minDistance = distance;
284
- closestPoint2 = point;
333
+ closestPoint2 = point2;
285
334
  }
286
335
  }
287
336
  return closestPoint2;
@@ -293,8 +342,48 @@ var getDistance = (point1, point2) => {
293
342
  };
294
343
 
295
344
  // src/util/render.ts
296
- import { geoGraticule } from "d3";
345
+ import { geoBounds, geoCentroid, geoDistance as geoDistance2, geoGraticule } from "d3";
297
346
  import { feature, mesh } from "topojson-client";
347
+ var RAD_TO_DEG = 180 / Math.PI;
348
+ var computeBounds = (geometry) => {
349
+ const feat = {
350
+ type: "Feature",
351
+ geometry,
352
+ properties: {}
353
+ };
354
+ const centroid = geoCentroid(feat);
355
+ const [[w, s], [e, n]] = geoBounds(feat);
356
+ const corners = [
357
+ [
358
+ w,
359
+ s
360
+ ],
361
+ [
362
+ w,
363
+ n
364
+ ],
365
+ [
366
+ e,
367
+ s
368
+ ],
369
+ [
370
+ e,
371
+ n
372
+ ]
373
+ ];
374
+ let radius = 0;
375
+ for (const corner of corners) {
376
+ const d = geoDistance2(centroid, corner) * RAD_TO_DEG;
377
+ if (d > radius) {
378
+ radius = d;
379
+ }
380
+ }
381
+ return {
382
+ geometry,
383
+ centroid,
384
+ radius
385
+ };
386
+ };
298
387
  var createLayers = (topology, features, styles) => {
299
388
  const layers = [];
300
389
  if (styles.water) {
@@ -315,11 +404,25 @@ var createLayers = (topology, features, styles) => {
315
404
  });
316
405
  }
317
406
  if (topology) {
318
- if (topology.objects.land && styles.land) {
319
- layers.push({
320
- styles: styles.land,
321
- path: feature(topology, topology.objects.land)
322
- });
407
+ if (styles.land) {
408
+ if (topology.objects.countries) {
409
+ const fc = feature(topology, topology.objects.countries);
410
+ const memberGeoms = fc.features.map((f) => f.geometry);
411
+ const bounds = memberGeoms.map(computeBounds);
412
+ layers.push({
413
+ styles: styles.land,
414
+ path: {
415
+ type: "GeometryCollection",
416
+ geometries: memberGeoms
417
+ },
418
+ cullable: bounds
419
+ });
420
+ } else if (topology.objects.land) {
421
+ layers.push({
422
+ styles: styles.land,
423
+ path: feature(topology, topology.objects.land)
424
+ });
425
+ }
323
426
  }
324
427
  if (topology.objects.countries && styles.border) {
325
428
  layers.push({
@@ -336,28 +439,28 @@ var createLayers = (topology, features, styles) => {
336
439
  }
337
440
  if (features) {
338
441
  const { points, lines } = features;
339
- if (points && styles.point) {
442
+ if (lines && styles.line) {
340
443
  layers.push({
341
- styles: styles.point,
444
+ styles: styles.line,
342
445
  path: {
343
446
  type: "GeometryCollection",
344
- geometries: points.map((point) => geoPoint(point))
447
+ geometries: lines.map(({ source, target }) => geoLine(source, target))
345
448
  }
346
449
  });
347
450
  }
348
- if (lines && styles.line) {
451
+ if (points && styles.point) {
349
452
  layers.push({
350
- styles: styles.line,
453
+ styles: styles.point,
351
454
  path: {
352
455
  type: "GeometryCollection",
353
- geometries: lines.map(({ source, target }) => geoLine(source, target))
456
+ geometries: points.map((point2) => geoPoint(point2))
354
457
  }
355
458
  });
356
459
  }
357
460
  }
358
461
  return layers;
359
462
  };
360
- var renderLayers = (generator, layers = [], scale, styles) => {
463
+ var renderLayers = (generator, layers = [], scale, styles, viewCenter) => {
361
464
  const context = generator.context();
362
465
  const { canvas: { width, height } } = context;
363
466
  context.reset();
@@ -367,7 +470,8 @@ var renderLayers = (generator, layers = [], scale, styles) => {
367
470
  } else {
368
471
  context.clearRect(0, 0, width, height);
369
472
  }
370
- layers.forEach(({ path, styles: styles2 }) => {
473
+ layers.forEach((layer) => {
474
+ const { path, styles: styles2, cullable } = layer;
371
475
  context.save();
372
476
  let fill = false;
373
477
  let stroke = false;
@@ -382,8 +486,23 @@ var renderLayers = (generator, layers = [], scale, styles) => {
382
486
  }
383
487
  });
384
488
  }
489
+ let renderPath = path;
490
+ if (cullable && viewCenter) {
491
+ const geometries = [];
492
+ for (let index = 0; index < cullable.length; index++) {
493
+ const bounds = cullable[index];
494
+ const angularDistance = geoDistance2(viewCenter, bounds.centroid) * RAD_TO_DEG;
495
+ if (angularDistance < 90 + bounds.radius) {
496
+ geometries.push(bounds.geometry);
497
+ }
498
+ }
499
+ renderPath = {
500
+ type: "GeometryCollection",
501
+ geometries
502
+ };
503
+ }
385
504
  context.beginPath();
386
- generator(path);
505
+ generator(renderPath);
387
506
  fill && context.fill();
388
507
  stroke && context.stroke();
389
508
  context.restore();
@@ -391,6 +510,57 @@ var renderLayers = (generator, layers = [], scale, styles) => {
391
510
  return context;
392
511
  };
393
512
 
513
+ // src/util/styles.ts
514
+ var POINT_COLOR = "rgb(220, 38, 38)";
515
+ var LINE_COLOR = "rgba(220, 38, 38, 0.5)";
516
+ var globeStyles = (themeMode) => themeMode === "dark" ? {
517
+ water: {
518
+ fillStyle: "#191919"
519
+ },
520
+ land: {
521
+ fillStyle: "#444",
522
+ strokeStyle: "#222"
523
+ },
524
+ border: {
525
+ strokeStyle: "#111"
526
+ },
527
+ graticule: {
528
+ strokeStyle: "#111"
529
+ },
530
+ line: {
531
+ lineWidth: 1.5,
532
+ lineDash: [
533
+ 4,
534
+ 16
535
+ ],
536
+ strokeStyle: LINE_COLOR
537
+ },
538
+ point: {
539
+ radius: 0.2,
540
+ fillStyle: POINT_COLOR
541
+ }
542
+ } : {
543
+ water: {
544
+ fillStyle: "#C0DAE4"
545
+ },
546
+ land: {
547
+ fillStyle: "#C2D8B4",
548
+ strokeStyle: "#A6C291"
549
+ },
550
+ line: {
551
+ lineWidth: 1.5,
552
+ lineDash: [
553
+ 4,
554
+ 16
555
+ ],
556
+ strokeStyle: LINE_COLOR
557
+ },
558
+ point: {
559
+ radius: 0.2,
560
+ fillStyle: POINT_COLOR
561
+ }
562
+ };
563
+
394
564
  // src/hooks/useDrag.ts
395
565
  var useDrag = (controller, options = {}) => {
396
566
  useEffect(() => {
@@ -398,14 +568,19 @@ var useDrag = (controller, options = {}) => {
398
568
  if (!canvas || options.disabled) {
399
569
  return;
400
570
  }
401
- geoInertiaDrag(select2(canvas), () => {
571
+ const inertia = geoInertiaDrag(select2(canvas), () => {
402
572
  controller.setRotation(controller.projection.rotate());
403
573
  options.onUpdate?.({
404
574
  type: "move",
405
575
  controller
406
576
  });
407
577
  }, controller.projection, {
408
- xAxis: options.xAxis,
578
+ lockTilt: options.lockTilt,
579
+ mode: options.mode,
580
+ sensitivity: options.sensitivity,
581
+ // Zoom-driven gain: matches useWheel — degrees-per-pixel shrinks as the
582
+ // globe gets larger on screen so the drag feel is consistent across zoom.
583
+ getZoom: () => controller.zoom,
409
584
  time: 3e3,
410
585
  start: () => options.onUpdate?.({
411
586
  type: "start",
@@ -418,6 +593,7 @@ var useDrag = (controller, options = {}) => {
418
593
  });
419
594
  return () => {
420
595
  cancelDrag(select2(canvas));
596
+ inertia?.timer?.stop();
421
597
  };
422
598
  }, [
423
599
  controller,
@@ -428,6 +604,7 @@ var cancelDrag = (node) => node.on(".drag", null);
428
604
 
429
605
  // src/hooks/useGlobeZoomHandler.ts
430
606
  import { useCallback } from "react";
607
+ var ZOOM_FACTOR = 0.1;
431
608
  var useGlobeZoomHandler = (controller) => {
432
609
  return useCallback((event) => {
433
610
  if (!controller) {
@@ -435,11 +612,15 @@ var useGlobeZoomHandler = (controller) => {
435
612
  }
436
613
  switch (event) {
437
614
  case "zoom-in": {
438
- controller.setZoom((zoom) => zoom * 1.1);
615
+ controller.setZoom((zoom) => {
616
+ return zoom * (1 + ZOOM_FACTOR);
617
+ });
439
618
  break;
440
619
  }
441
620
  case "zoom-out": {
442
- controller.setZoom((zoom) => zoom * 0.9);
621
+ controller.setZoom((zoom) => {
622
+ return zoom * (1 - ZOOM_FACTOR);
623
+ });
443
624
  break;
444
625
  }
445
626
  }
@@ -470,6 +651,61 @@ var useMapZoomHandler = (controller) => {
470
651
  ]);
471
652
  };
472
653
 
654
+ // src/hooks/useSimplifiedTopology.ts
655
+ import { useMemo } from "react";
656
+ import { presimplify, quantile, simplify } from "topojson-simplify";
657
+ var DEFAULT_TIERS = [
658
+ {
659
+ minZoom: 0,
660
+ percentile: 0.95
661
+ },
662
+ {
663
+ minZoom: 2,
664
+ percentile: 0.85
665
+ },
666
+ {
667
+ minZoom: 4,
668
+ percentile: 0.6
669
+ },
670
+ {
671
+ minZoom: 7,
672
+ percentile: 0.3
673
+ },
674
+ {
675
+ minZoom: 12,
676
+ percentile: 0
677
+ }
678
+ ];
679
+ var pickTier = (zoom, tiers) => {
680
+ let match = tiers[0];
681
+ for (const tier of tiers) {
682
+ if (zoom >= tier.minZoom) {
683
+ match = tier;
684
+ }
685
+ }
686
+ return match;
687
+ };
688
+ var useSimplifiedTopology = (topology, zoom, options = {}) => {
689
+ const { tiers = DEFAULT_TIERS } = options;
690
+ const presimplified = useMemo(() => topology ? presimplify(topology) : void 0, [
691
+ topology
692
+ ]);
693
+ const tier = pickTier(zoom, tiers);
694
+ return useMemo(() => {
695
+ if (!presimplified) {
696
+ return void 0;
697
+ }
698
+ if (tier.percentile <= 0) {
699
+ return presimplified;
700
+ }
701
+ const minWeight = quantile(presimplified, tier.percentile);
702
+ return simplify(presimplified, minWeight);
703
+ }, [
704
+ presimplified,
705
+ tier
706
+ ]);
707
+ };
708
+
473
709
  // src/hooks/useSpinner.ts
474
710
  import { timer as d3Timer } from "d3";
475
711
  import { useEffect as useEffect2, useState } from "react";
@@ -523,91 +759,139 @@ var useSpinner = (controller, options = {}) => {
523
759
  ];
524
760
  };
525
761
 
762
+ // src/hooks/useTopology.ts
763
+ import { useEffect as useEffect3, useState as useState2 } from "react";
764
+ import { log } from "@dxos/log";
765
+ var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-geo/src/hooks/useTopology.ts";
766
+ var DEFAULT_TIERS2 = [
767
+ {
768
+ minZoom: 0,
769
+ level: "110m"
770
+ }
771
+ ];
772
+ var pickTier2 = (zoom, tiers) => {
773
+ let match = tiers[0];
774
+ for (const tier of tiers) {
775
+ if (zoom >= tier.minZoom) {
776
+ match = tier;
777
+ }
778
+ }
779
+ return match;
780
+ };
781
+ var topologyCache = /* @__PURE__ */ new Map();
782
+ var useTopology = (zoom, options = {}) => {
783
+ const { tiers = DEFAULT_TIERS2 } = options;
784
+ const level = zoom === void 0 ? "110m" : pickTier2(zoom, tiers).level;
785
+ const [topology, setTopology] = useState2(() => topologyCache.get(level));
786
+ useEffect3(() => {
787
+ const cached = topologyCache.get(level);
788
+ if (cached) {
789
+ setTopology(cached);
790
+ return;
791
+ }
792
+ let disposed = false;
793
+ void loadTopology(level).then((loaded) => {
794
+ topologyCache.set(level, loaded);
795
+ if (!disposed) {
796
+ setTopology(loaded);
797
+ }
798
+ }).catch((err) => {
799
+ if (!disposed) {
800
+ log.warn("failed to load topology", {
801
+ level,
802
+ err
803
+ }, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 52, S: void 0 });
804
+ }
805
+ });
806
+ return () => {
807
+ disposed = true;
808
+ };
809
+ }, [
810
+ level
811
+ ]);
812
+ return topology;
813
+ };
814
+
526
815
  // src/hooks/useTour.ts
527
- import { selection as d3Selection, geoDistance, geoInterpolate, geoPath } from "d3";
528
- import { useEffect as useEffect3, useMemo, useState as useState2 } from "react";
529
- import versor2 from "versor";
530
- var TRANSITION_NAME = "globe-tour";
816
+ import { geoInterpolate, geoPath } from "d3";
817
+ import { useEffect as useEffect4, useState as useState3 } from "react";
531
818
  var defaultDuration = 1500;
532
819
  var useTour = (controller, points, options = {}) => {
533
- const selection = useMemo(() => d3Selection(), []);
534
- const [running, setRunning] = useState2(options.running ?? false);
535
- useEffect3(() => {
536
- if (!running) {
537
- selection.interrupt(TRANSITION_NAME);
820
+ const [running, setRunning] = useState3(options.running ?? false);
821
+ useEffect4(() => {
822
+ if (!controller || !running) {
538
823
  return;
539
824
  }
540
- let t;
541
- if (controller && running) {
542
- t = setTimeout(async () => {
543
- const { canvas, projection, setRotation } = controller;
544
- const context = canvas.getContext("2d", {
545
- alpha: false
546
- });
547
- const path = geoPath(projection, context).pointRadius(2);
548
- const tilt = options.tilt ?? 0;
825
+ let cancelled = false;
826
+ const t = setTimeout(async () => {
827
+ const { canvas, projection } = controller;
828
+ const context = canvas.getContext("2d", {
829
+ alpha: false
830
+ });
831
+ const path = geoPath(projection, context).pointRadius(2);
832
+ try {
833
+ const tourPoints = [
834
+ ...points
835
+ ];
836
+ if (options.loop) {
837
+ tourPoints.push(tourPoints[0]);
838
+ }
549
839
  let last;
550
- try {
551
- const p = [
552
- ...points
553
- ];
554
- if (options.loop) {
555
- p.push(p[0]);
840
+ for (const next of tourPoints) {
841
+ if (cancelled) {
842
+ break;
556
843
  }
557
- for (const next of p) {
558
- if (!running) {
559
- break;
560
- }
561
- const p1 = last ? geoToPosition(last) : void 0;
562
- const p2 = geoToPosition(next);
563
- const ip = geoInterpolate(p1 || p2, p2);
564
- const distance = geoDistance(p1 || p2, p2);
565
- const r1 = p1 ? positionToRotation(p1, tilt) : controller.projection.rotate();
566
- const r2 = positionToRotation(p2, tilt);
567
- const iv = versor2.interpolate(r1, r2);
568
- const transition2 = selection.transition(TRANSITION_NAME).duration(Math.max(options.duration ?? defaultDuration, distance * 2e3)).tween("render", () => (t2) => {
569
- const t1 = Math.max(0, Math.min(1, t2 * 2 - 1));
570
- const t22 = Math.min(1, t2 * 2);
571
- context.save();
572
- {
573
- context.beginPath();
574
- context.strokeStyle = options?.styles?.arc?.strokeStyle ?? "yellow";
575
- context.lineWidth = (options?.styles?.arc?.lineWidth ?? 1.5) * (controller?.zoom ?? 1);
576
- context.setLineDash(options?.styles?.arc?.lineDash ?? []);
577
- path({
578
- type: "LineString",
579
- coordinates: [
580
- ip(t1),
581
- ip(t22)
582
- ]
583
- });
584
- context.stroke();
585
- context.beginPath();
586
- context.fillStyle = options?.styles?.cursor?.fillStyle ?? "orange";
587
- path.pointRadius((options?.styles?.cursor?.pointRadius ?? 2) * (controller?.zoom ?? 1));
588
- path({
589
- type: "Point",
590
- coordinates: ip(t22)
591
- });
592
- context.fill();
593
- }
844
+ const p1 = last ? geoToPosition(last) : void 0;
845
+ const p2 = geoToPosition(next);
846
+ const ip = geoInterpolate(p1 ?? p2, p2);
847
+ const onTick = (t2) => {
848
+ const t1 = Math.max(0, Math.min(1, t2 * 2 - 1));
849
+ const t22 = Math.min(1, t2 * 2);
850
+ context.save();
851
+ try {
852
+ context.beginPath();
853
+ context.strokeStyle = options.styles?.arc?.strokeStyle ?? "yellow";
854
+ context.lineWidth = (options.styles?.arc?.lineWidth ?? 1.5) * (controller.zoom ?? 1);
855
+ context.setLineDash(options.styles?.arc?.lineDash ?? []);
856
+ path({
857
+ type: "LineString",
858
+ coordinates: [
859
+ ip(t1),
860
+ ip(t22)
861
+ ]
862
+ });
863
+ context.stroke();
864
+ context.beginPath();
865
+ context.fillStyle = options.styles?.cursor?.fillStyle ?? "orange";
866
+ path.pointRadius((options.styles?.cursor?.pointRadius ?? 2) * (controller.zoom ?? 1));
867
+ path({
868
+ type: "Point",
869
+ coordinates: ip(t22)
870
+ });
871
+ context.fill();
872
+ } finally {
594
873
  context.restore();
595
- projection.rotate(iv(t2));
596
- setRotation(projection.rotate());
597
- });
598
- await transition2.end();
599
- last = next;
600
- }
601
- } catch {
602
- } finally {
874
+ }
875
+ };
876
+ await controller.flyTo(next, {
877
+ duration: options.duration ?? defaultDuration,
878
+ tilt: options.tilt ?? 0,
879
+ onTick
880
+ });
881
+ last = next;
882
+ }
883
+ } catch {
884
+ } finally {
885
+ if (!cancelled) {
603
886
  setRunning(false);
604
887
  }
605
- });
606
- return () => {
607
- clearTimeout(t);
608
- selection.interrupt(TRANSITION_NAME);
609
- };
610
- }
888
+ }
889
+ });
890
+ return () => {
891
+ cancelled = true;
892
+ clearTimeout(t);
893
+ controller.cancelFlyTo();
894
+ };
611
895
  }, [
612
896
  controller,
613
897
  running,
@@ -619,10 +903,55 @@ var useTour = (controller, points, options = {}) => {
619
903
  ];
620
904
  };
621
905
 
906
+ // src/hooks/useWheel.ts
907
+ import { useEffect as useEffect5 } from "react";
908
+ var DEFAULT_SENSITIVITY = 0.25;
909
+ var DEFAULT_ZOOM_SENSITIVITY = 0.01;
910
+ var useWheel = (controller, options = {}) => {
911
+ useEffect5(() => {
912
+ const canvas = controller?.canvas;
913
+ if (!canvas || options.disabled) {
914
+ return;
915
+ }
916
+ const sensitivity = options.sensitivity ?? DEFAULT_SENSITIVITY;
917
+ const zoomSensitivity = options.zoomSensitivity ?? DEFAULT_ZOOM_SENSITIVITY;
918
+ const handleWheel = (event) => {
919
+ event.preventDefault();
920
+ if (event.ctrlKey) {
921
+ const factor = Math.exp(-event.deltaY * zoomSensitivity);
922
+ controller.setZoom(controller.zoom * factor);
923
+ } else {
924
+ const [lambda, phi, gamma] = controller.projection.rotate();
925
+ const k = sensitivity / Math.max(controller.zoom, 0.1);
926
+ const next = [
927
+ lambda - event.deltaX * k,
928
+ phi + event.deltaY * k,
929
+ gamma
930
+ ];
931
+ controller.projection.rotate(next);
932
+ controller.setRotation(controller.projection.rotate());
933
+ }
934
+ options.onUpdate?.(controller);
935
+ };
936
+ canvas.addEventListener("wheel", handleWheel, {
937
+ passive: false
938
+ });
939
+ return () => {
940
+ canvas.removeEventListener("wheel", handleWheel);
941
+ };
942
+ }, [
943
+ controller,
944
+ options.disabled,
945
+ options.sensitivity,
946
+ options.zoomSensitivity,
947
+ options.onUpdate
948
+ ]);
949
+ };
950
+
622
951
  // src/components/Toolbar/Controls.tsx
623
- import { useSignals as _useSignals2 } from "@preact-signals/safe-react/tracking";
624
- import React2 from "react";
625
- import { IconButton, Toolbar } from "@dxos/react-ui";
952
+ import React from "react";
953
+ import { IconButton, Toolbar, useTranslation } from "@dxos/react-ui";
954
+ import { translationKey } from "#translations";
626
955
  var controlPositions = {
627
956
  topleft: "top-2 left-2",
628
957
  topright: "top-2 right-2",
@@ -630,58 +959,42 @@ var controlPositions = {
630
959
  bottomright: "bottom-2 right-2"
631
960
  };
632
961
  var ZoomControls = ({ classNames, onAction }) => {
633
- var _effect = _useSignals2();
634
- try {
635
- return /* @__PURE__ */ React2.createElement(Toolbar.Root, {
636
- classNames: [
637
- "gap-2",
638
- classNames
639
- ]
640
- }, /* @__PURE__ */ React2.createElement(IconButton, {
641
- icon: "ph--plus--regular",
642
- label: "zoom in",
643
- iconOnly: true,
644
- size: 5,
645
- classNames: "px-0 aspect-square",
646
- onClick: () => onAction?.("zoom-in")
647
- }), /* @__PURE__ */ React2.createElement(IconButton, {
648
- icon: "ph--minus--regular",
649
- label: "zoom out",
650
- iconOnly: true,
651
- size: 5,
652
- classNames: "px-0 aspect-square",
653
- onClick: () => onAction?.("zoom-out")
654
- }));
655
- } finally {
656
- _effect.f();
657
- }
962
+ const { t } = useTranslation(translationKey);
963
+ return /* @__PURE__ */ React.createElement(Toolbar.Root, {
964
+ classNames: [
965
+ "gap-2",
966
+ classNames
967
+ ]
968
+ }, /* @__PURE__ */ React.createElement(IconButton, {
969
+ icon: "ph--plus--regular",
970
+ iconOnly: true,
971
+ label: t("zoom-in-icon.button"),
972
+ onClick: () => onAction?.("zoom-in")
973
+ }), /* @__PURE__ */ React.createElement(IconButton, {
974
+ icon: "ph--minus--regular",
975
+ iconOnly: true,
976
+ label: t("zoom-out-icon.button"),
977
+ onClick: () => onAction?.("zoom-out")
978
+ }));
658
979
  };
659
980
  var ActionControls = ({ classNames, onAction }) => {
660
- var _effect = _useSignals2();
661
- try {
662
- return /* @__PURE__ */ React2.createElement(Toolbar.Root, {
663
- classNames: [
664
- "gap-2",
665
- classNames
666
- ]
667
- }, /* @__PURE__ */ React2.createElement(IconButton, {
668
- icon: "ph--play--regular",
669
- label: "start",
670
- iconOnly: true,
671
- size: 5,
672
- classNames: "px-0 aspect-square",
673
- onClick: () => onAction?.("start")
674
- }), /* @__PURE__ */ React2.createElement(IconButton, {
675
- icon: "ph--globe-hemisphere-west--regular",
676
- label: "toggle",
677
- iconOnly: true,
678
- size: 5,
679
- classNames: "px-0 aspect-square",
680
- onClick: () => onAction?.("toggle")
681
- }));
682
- } finally {
683
- _effect.f();
684
- }
981
+ const { t } = useTranslation(translationKey);
982
+ return /* @__PURE__ */ React.createElement(Toolbar.Root, {
983
+ classNames: [
984
+ "gap-2",
985
+ classNames
986
+ ]
987
+ }, /* @__PURE__ */ React.createElement(IconButton, {
988
+ icon: "ph--path--regular",
989
+ iconOnly: true,
990
+ label: t("start-icon.button"),
991
+ onClick: () => onAction?.("start")
992
+ }), /* @__PURE__ */ React.createElement(IconButton, {
993
+ icon: "ph--globe-hemisphere-west--regular",
994
+ iconOnly: true,
995
+ label: t("toggle-icon.button"),
996
+ onClick: () => onAction?.("toggle")
997
+ }));
685
998
  };
686
999
 
687
1000
  // src/components/Globe/Globe.tsx
@@ -739,188 +1052,245 @@ var getProjection = (type = "orthographic") => {
739
1052
  }
740
1053
  return type ?? geoOrthographic();
741
1054
  };
742
- var GlobeRoot = ({ classNames, children, ...props }) => {
743
- var _effect = _useSignals3();
744
- try {
745
- const { ref, width, height } = useResizeDetector();
746
- return /* @__PURE__ */ React3.createElement("div", {
747
- ref,
748
- className: mx("relative flex grow overflow-hidden", classNames)
749
- }, /* @__PURE__ */ React3.createElement(GlobeContextProvider, {
750
- size: {
751
- width,
752
- height
753
- },
754
- ...props
755
- }, children));
756
- } finally {
757
- _effect.f();
758
- }
759
- };
760
- var GlobeCanvas = /* @__PURE__ */ forwardRef(({ projection: _projection, topology, features, styles: _styles }, forwardRef3) => {
761
- var _effect = _useSignals3();
762
- try {
763
- const { themeMode } = useThemeContext();
764
- const styles = useMemo2(() => _styles ?? defaultStyles[themeMode], [
765
- _styles,
766
- themeMode
767
- ]);
768
- const [canvas, setCanvas] = useState3(null);
769
- const canvasRef = (canvas2) => setCanvas(canvas2);
770
- const projection = useMemo2(() => getProjection(_projection), [
771
- _projection
772
- ]);
773
- const layers = useMemo2(() => {
774
- return timer(() => createLayers(topology, features, styles));
775
- }, [
776
- topology,
777
- features,
778
- styles
779
- ]);
780
- const { size, center, zoom, translation, rotation, setCenter, setZoom, setTranslation, setRotation } = useGlobeContext();
781
- const zoomRef = useDynamicRef(zoom);
782
- useEffect4(() => {
783
- if (center) {
784
- setZoom(1);
785
- setRotation(positionToRotation(geoToPosition(center)));
786
- }
787
- }, [
788
- center
789
- ]);
790
- const zooming = useRef(false);
791
- useImperativeHandle(forwardRef3, () => {
792
- return {
793
- canvas,
794
- projection,
795
- center,
796
- get zoom() {
797
- return zoomRef.current;
798
- },
799
- translation,
800
- rotation,
801
- setCenter,
802
- setZoom: (s) => {
803
- if (typeof s === "function") {
804
- const is = interpolateNumber(zoomRef.current, s(zoomRef.current));
805
- transition().ease(zooming.current ? easeLinear : easeSinOut).duration(200).tween("scale", () => (t) => setZoom(is(t))).on("end", () => {
806
- zooming.current = false;
807
- });
808
- } else {
809
- setZoom(s);
810
- }
811
- },
812
- setTranslation,
813
- setRotation
814
- };
815
- }, [
816
- canvas
817
- ]);
818
- const generator = useMemo2(() => canvas && projection && geoPath2(projection, canvas.getContext("2d", {
819
- alpha: false
820
- })), [
821
- canvas,
822
- projection
823
- ]);
824
- useEffect4(() => {
825
- if (canvas && projection) {
826
- timer(() => {
827
- projection.scale(Math.min(size.width, size.height) / 2 * zoom).translate([
828
- size.width / 2 + (translation?.x ?? 0),
829
- size.height / 2 + (translation?.y ?? 0)
830
- ]).rotate(rotation ?? [
831
- 0,
832
- 0,
833
- 0
834
- ]);
835
- renderLayers(generator, layers, zoom, styles);
836
- });
837
- }
838
- }, [
839
- generator,
1055
+ var DEFAULT_ZOOM = 1.5;
1056
+ var GlobeRoot = /* @__PURE__ */ forwardRef(({ children, center: centerProp, zoom: zoomProp = DEFAULT_ZOOM, translation: translationProp, rotation: rotationProp }, forwardedRef) => {
1057
+ const [size, setSize] = useState4({
1058
+ width: 0,
1059
+ height: 0
1060
+ });
1061
+ const [center, setCenter] = useControlledState(centerProp);
1062
+ const [zoom, setZoom] = useControlledState(zoomProp);
1063
+ const [translation, setTranslation] = useControlledState(translationProp);
1064
+ const [rotation, setRotation] = useControlledState(rotationProp);
1065
+ const [controller, setController] = useState4(null);
1066
+ const registerController = useCallback3((next) => setController(next), []);
1067
+ useImperativeHandle(forwardedRef, () => controller, [
1068
+ controller
1069
+ ]);
1070
+ return /* @__PURE__ */ React2.createElement(GlobeContext.Provider, {
1071
+ value: {
840
1072
  size,
1073
+ center,
841
1074
  zoom,
842
1075
  translation,
843
1076
  rotation,
844
- layers
845
- ]);
846
- if (!size.width || !size.height) {
847
- return null;
1077
+ setSize,
1078
+ setCenter,
1079
+ setZoom,
1080
+ setTranslation,
1081
+ setRotation,
1082
+ registerController
848
1083
  }
849
- return /* @__PURE__ */ React3.createElement("canvas", {
850
- ref: canvasRef,
851
- width: size.width,
852
- height: size.height
1084
+ }, children);
1085
+ });
1086
+ GlobeRoot.displayName = "Globe.Root";
1087
+ var GlobeViewport = composable(({ children, ...props }, forwardedRef) => {
1088
+ const { setSize } = useGlobeContext();
1089
+ const localRef = useRef(null);
1090
+ const composedRef = useComposedRefs(localRef, forwardedRef);
1091
+ const { width, height } = useResizeDetector({
1092
+ targetRef: localRef
1093
+ });
1094
+ useEffect6(() => {
1095
+ setSize({
1096
+ width: width ?? 0,
1097
+ height: height ?? 0
853
1098
  });
854
- } finally {
855
- _effect.f();
856
- }
1099
+ }, [
1100
+ width,
1101
+ height,
1102
+ setSize
1103
+ ]);
1104
+ return /* @__PURE__ */ React2.createElement("div", {
1105
+ ...composableProps(props, {
1106
+ classNames: "relative dx-container"
1107
+ }),
1108
+ ref: composedRef
1109
+ }, children);
857
1110
  });
858
- var GlobeDebug = ({ position = "topleft" }) => {
859
- var _effect = _useSignals3();
860
- try {
861
- const { size, zoom, translation, rotation } = useGlobeContext();
862
- return /* @__PURE__ */ React3.createElement("div", {
863
- className: mx("z-10 absolute w-96 p-2 overflow-hidden border border-green-700 rounded", controlPositions[position])
864
- }, /* @__PURE__ */ React3.createElement("pre", {
865
- className: "font-mono text-xs text-green-700"
866
- }, JSON.stringify({
867
- size,
868
- zoom,
1111
+ GlobeViewport.displayName = "Globe.Viewport";
1112
+ var GlobeCanvas = ({ projection: projectionProp, topology, features, styles: stylesProp }) => {
1113
+ const { themeMode } = useThemeContext();
1114
+ const styles = useMemo2(() => stylesProp ?? defaultStyles[themeMode], [
1115
+ stylesProp,
1116
+ themeMode
1117
+ ]);
1118
+ const { size, center, zoom, translation, rotation, setZoom, setTranslation, setRotation, registerController } = useGlobeContext();
1119
+ const zoomRef = useDynamicRef(zoom);
1120
+ const [canvas, setCanvas] = useState4(null);
1121
+ const canvasRef = (canvas2) => setCanvas(canvas2);
1122
+ const projection = useMemo2(() => getProjection(projectionProp), [
1123
+ projectionProp
1124
+ ]);
1125
+ const layers = useMemo2(() => {
1126
+ return timer(() => createLayers(topology, features, styles));
1127
+ }, [
1128
+ topology,
1129
+ features,
1130
+ styles
1131
+ ]);
1132
+ useEffect6(() => {
1133
+ if (center) {
1134
+ setRotation(positionToRotation(geoToPosition(center)));
1135
+ }
1136
+ }, [
1137
+ center
1138
+ ]);
1139
+ const flyToSelection = useMemo2(() => d3Selection(), []);
1140
+ const flyToTransitionName = `globe-fly-to-${useId()}`;
1141
+ useEffect6(() => () => {
1142
+ flyToSelection.interrupt(flyToTransitionName);
1143
+ }, [
1144
+ flyToSelection,
1145
+ flyToTransitionName
1146
+ ]);
1147
+ const zooming = useRef(false);
1148
+ const controller = useMemo2(() => {
1149
+ return {
1150
+ canvas,
1151
+ projection,
1152
+ get zoom() {
1153
+ return zoomRef.current;
1154
+ },
869
1155
  translation,
870
- rotation
871
- }, null, 2)));
872
- } finally {
873
- _effect.f();
1156
+ rotation,
1157
+ setZoom: (state) => {
1158
+ if (typeof state === "function") {
1159
+ const is = interpolateNumber(zoomRef.current, state(zoomRef.current));
1160
+ transition().ease(zooming.current ? easeLinear : easeSinOut).duration(200).tween("scale", () => (t) => setZoom(is(t))).on("end", () => {
1161
+ zooming.current = false;
1162
+ });
1163
+ } else {
1164
+ setZoom(state);
1165
+ }
1166
+ },
1167
+ setTranslation,
1168
+ setRotation,
1169
+ flyTo: (target, options = {}) => {
1170
+ const { duration = 1200, tilt = 0, onTick } = options;
1171
+ const p2 = geoToPosition(target);
1172
+ const r1 = projection.rotate();
1173
+ const r2 = positionToRotation(p2, tilt);
1174
+ const p1 = [
1175
+ -r1[0],
1176
+ -r1[1]
1177
+ ];
1178
+ const rotationTween = createRotationTween(projection, setRotation, r1, r2);
1179
+ const iz = target.zoom !== void 0 ? interpolateNumber(zoomRef.current, target.zoom) : void 0;
1180
+ flyToSelection.interrupt(flyToTransitionName);
1181
+ const tx = flyToSelection.transition(flyToTransitionName).duration(flyDuration(p1, p2, duration, 1500));
1182
+ if (onTick) {
1183
+ tx.tween("flyToOnTick", () => onTick);
1184
+ }
1185
+ tx.tween("flyToRotation", () => rotationTween);
1186
+ if (iz) {
1187
+ tx.tween("flyToZoom", () => (t) => setZoom(iz(t)));
1188
+ }
1189
+ return tx.end();
1190
+ },
1191
+ cancelFlyTo: () => {
1192
+ flyToSelection.interrupt(flyToTransitionName);
1193
+ }
1194
+ };
1195
+ }, [
1196
+ canvas,
1197
+ projection,
1198
+ flyToSelection,
1199
+ flyToTransitionName
1200
+ ]);
1201
+ useEffect6(() => {
1202
+ registerController(controller);
1203
+ return () => registerController(null);
1204
+ }, [
1205
+ registerController,
1206
+ controller
1207
+ ]);
1208
+ const generator = useMemo2(() => canvas && projection && geoPath2(projection, canvas.getContext("2d")), [
1209
+ canvas,
1210
+ projection
1211
+ ]);
1212
+ useEffect6(() => {
1213
+ if (canvas && projection) {
1214
+ timer(() => {
1215
+ projection.scale(Math.min(size.width, size.height) / 2 * zoom).translate([
1216
+ size.width / 2 + (translation?.x ?? 0),
1217
+ size.height / 2 + (translation?.y ?? 0)
1218
+ ]).rotate(rotation ?? [
1219
+ 0,
1220
+ 0,
1221
+ 0
1222
+ ]);
1223
+ const isOrthographic = !projectionProp || projectionProp === "orthographic";
1224
+ const [lambda, phi] = rotation ?? [
1225
+ 0,
1226
+ 0,
1227
+ 0
1228
+ ];
1229
+ const viewCenter = isOrthographic ? [
1230
+ -lambda,
1231
+ -phi
1232
+ ] : void 0;
1233
+ renderLayers(generator, layers, zoom, styles, viewCenter);
1234
+ });
1235
+ }
1236
+ }, [
1237
+ generator,
1238
+ size,
1239
+ zoom,
1240
+ translation,
1241
+ rotation,
1242
+ layers,
1243
+ projectionProp
1244
+ ]);
1245
+ if (!size.width || !size.height) {
1246
+ return null;
874
1247
  }
1248
+ return /* @__PURE__ */ React2.createElement("canvas", {
1249
+ ref: canvasRef,
1250
+ className: "bg-base-surface",
1251
+ width: size.width,
1252
+ height: size.height
1253
+ });
1254
+ };
1255
+ GlobeCanvas.displayName = "Globe.Canvas";
1256
+ var GlobeDebug = ({ position = "topleft" }) => {
1257
+ const { size, zoom, translation, rotation } = useGlobeContext();
1258
+ return /* @__PURE__ */ React2.createElement("div", {
1259
+ className: mx("z-10 absolute w-96 p-2 overflow-hidden border border-green-700 rounded-sm", controlPositions[position])
1260
+ }, /* @__PURE__ */ React2.createElement("pre", {
1261
+ className: "font-mono text-xs text-green-700"
1262
+ }, JSON.stringify({
1263
+ size,
1264
+ zoom,
1265
+ translation,
1266
+ rotation
1267
+ }, null, 2)));
875
1268
  };
876
1269
  var GlobePanel = ({ position, classNames, children }) => {
877
- var _effect = _useSignals3();
878
- try {
879
- return /* @__PURE__ */ React3.createElement("div", {
880
- className: mx("z-10 absolute overflow-hidden", controlPositions[position], classNames)
881
- }, children);
882
- } finally {
883
- _effect.f();
884
- }
1270
+ return /* @__PURE__ */ React2.createElement("div", {
1271
+ className: mx("z-10 absolute overflow-hidden", controlPositions[position], classNames)
1272
+ }, children);
885
1273
  };
886
1274
  var CustomControl = ({ position, children }) => {
887
- var _effect = _useSignals3();
888
- try {
889
- return /* @__PURE__ */ React3.createElement("div", {
890
- className: mx("z-10 absolute overflow-hidden", controlPositions[position])
891
- }, children);
892
- } finally {
893
- _effect.f();
894
- }
895
- };
896
- var GlobeZoom = ({ onAction, position = "bottomleft", ...props }) => {
897
- var _effect = _useSignals3();
898
- try {
899
- return /* @__PURE__ */ React3.createElement(CustomControl, {
900
- position,
901
- ...props
902
- }, /* @__PURE__ */ React3.createElement(ZoomControls, {
903
- onAction
904
- }));
905
- } finally {
906
- _effect.f();
907
- }
908
- };
909
- var GlobeAction = ({ onAction, position = "bottomright", ...props }) => {
910
- var _effect = _useSignals3();
911
- try {
912
- return /* @__PURE__ */ React3.createElement(CustomControl, {
913
- position,
914
- ...props
915
- }, /* @__PURE__ */ React3.createElement(ActionControls, {
916
- onAction
917
- }));
918
- } finally {
919
- _effect.f();
920
- }
1275
+ return /* @__PURE__ */ React2.createElement("div", {
1276
+ className: mx("z-10 absolute overflow-hidden", controlPositions[position])
1277
+ }, children);
921
1278
  };
1279
+ var GlobeZoom = ({ onAction, position = "bottomleft", ...props }) => /* @__PURE__ */ React2.createElement(CustomControl, {
1280
+ position,
1281
+ ...props
1282
+ }, /* @__PURE__ */ React2.createElement(ZoomControls, {
1283
+ onAction
1284
+ }));
1285
+ var GlobeAction = ({ onAction, position = "bottomright", ...props }) => /* @__PURE__ */ React2.createElement(CustomControl, {
1286
+ position,
1287
+ ...props
1288
+ }, /* @__PURE__ */ React2.createElement(ActionControls, {
1289
+ onAction
1290
+ }));
922
1291
  var Globe = {
923
1292
  Root: GlobeRoot,
1293
+ Viewport: GlobeViewport,
924
1294
  Canvas: GlobeCanvas,
925
1295
  Zoom: GlobeZoom,
926
1296
  Action: GlobeAction,
@@ -929,16 +1299,15 @@ var Globe = {
929
1299
  };
930
1300
 
931
1301
  // src/components/Map/Map.tsx
932
- import { useSignals as _useSignals4 } from "@preact-signals/safe-react/tracking";
933
1302
  import "leaflet/dist/leaflet.css";
934
1303
  import { createContext as createContext2 } from "@radix-ui/react-context";
935
- import L, { Control, DomEvent, DomUtil, latLngBounds } from "leaflet";
936
- import React4, { forwardRef as forwardRef2, useEffect as useEffect5, useImperativeHandle as useImperativeHandle2, useRef as useRef2, useState as useState4 } from "react";
1304
+ import L, { Control, DomEvent, DomUtil, point, latLngBounds } from "leaflet";
1305
+ import React3, { forwardRef as forwardRef2, useCallback as useCallback4, useEffect as useEffect7, useImperativeHandle as useImperativeHandle2, useRef as useRef2, useState as useState5 } from "react";
937
1306
  import { createRoot } from "react-dom/client";
938
- import { MapContainer, Marker, Popup, TileLayer, useMap } from "react-leaflet";
939
- import { debounce } from "@dxos/async";
1307
+ import { MapContainer, Marker, Polyline, Popup, TileLayer, useMap, useMapEvents } from "react-leaflet";
940
1308
  import { ThemeProvider, Tooltip } from "@dxos/react-ui";
941
- import { defaultTx, mx as mx2 } from "@dxos/react-ui-theme";
1309
+ import { composable as composable2, composableProps as composableProps2, defaultTx } from "@dxos/react-ui";
1310
+ import { mx as mx2 } from "@dxos/ui-theme";
942
1311
  var defaults = {
943
1312
  center: {
944
1313
  lat: 51,
@@ -946,237 +1315,341 @@ var defaults = {
946
1315
  },
947
1316
  zoom: 4
948
1317
  };
949
- var [MapContextProvier, useMapContext] = createContext2("Map");
950
- var MapRoot = /* @__PURE__ */ forwardRef2(({ classNames, scrollWheelZoom = true, doubleClickZoom = true, touchZoom = true, center = defaults.center, zoom = defaults.zoom, onChange, ...props }, forwardedRef) => {
951
- var _effect = _useSignals4();
952
- try {
953
- const [attention, setAttention] = useState4(false);
954
- const mapRef = useRef2(null);
955
- const map = mapRef.current;
956
- useImperativeHandle2(forwardedRef, () => ({
957
- setCenter: (center2, zoom2) => {
958
- mapRef.current?.setView(center2, zoom2);
959
- },
960
- setZoom: (cb) => {
961
- mapRef.current?.setZoom(cb(mapRef.current?.getZoom() ?? 0));
962
- }
963
- }), []);
964
- useEffect5(() => {
965
- if (!map) {
1318
+ var [MapContextProvider, useMapContext] = createContext2("Map");
1319
+ var MapRoot = /* @__PURE__ */ forwardRef2(({ children, onChange }, forwardedRef) => {
1320
+ const mapRef = useRef2(null);
1321
+ const registerMap = useCallback4((map) => {
1322
+ mapRef.current = map;
1323
+ }, []);
1324
+ useImperativeHandle2(forwardedRef, () => ({
1325
+ getCenter: () => {
1326
+ const center = mapRef.current?.getCenter();
1327
+ return center ? {
1328
+ lat: center.lat,
1329
+ lng: center.lng
1330
+ } : void 0;
1331
+ },
1332
+ getZoom: () => mapRef.current?.getZoom(),
1333
+ setCenter: (center, zoom) => {
1334
+ mapRef.current?.setView(center, zoom);
1335
+ },
1336
+ setZoom: (cb) => {
1337
+ mapRef.current?.setZoom(cb(mapRef.current?.getZoom() ?? 0));
1338
+ }
1339
+ }), []);
1340
+ const attention = false;
1341
+ return /* @__PURE__ */ React3.createElement(MapContextProvider, {
1342
+ attention,
1343
+ onChange,
1344
+ registerMap
1345
+ }, children);
1346
+ });
1347
+ MapRoot.displayName = "Map.Root";
1348
+ var MAP_VIEWPORT_NAME = "Map.Viewport";
1349
+ var MapResize = () => {
1350
+ const map = useMap();
1351
+ useEffect7(() => {
1352
+ const container = map.getContainer();
1353
+ let frame = 0;
1354
+ const observer = new ResizeObserver(() => {
1355
+ cancelAnimationFrame(frame);
1356
+ frame = requestAnimationFrame(() => map.invalidateSize());
1357
+ });
1358
+ observer.observe(container);
1359
+ return () => {
1360
+ cancelAnimationFrame(frame);
1361
+ observer.disconnect();
1362
+ };
1363
+ }, [
1364
+ map
1365
+ ]);
1366
+ return null;
1367
+ };
1368
+ var PINCH_ZOOM_SENSITIVITY = 0.03;
1369
+ var MapPinchZoom = () => {
1370
+ const map = useMap();
1371
+ useEffect7(() => {
1372
+ const container = map.getContainer();
1373
+ let frame = 0;
1374
+ let point2;
1375
+ let target;
1376
+ const onWheel = (event) => {
1377
+ if (!event.ctrlKey) {
966
1378
  return;
967
1379
  }
968
- const handler = debounce(() => {
969
- setAttention(true);
970
- onChange?.({
971
- center: map.getCenter(),
972
- zoom: map.getZoom()
1380
+ event.preventDefault();
1381
+ const rect = container.getBoundingClientRect();
1382
+ point2 = L.point(event.clientX - rect.left, event.clientY - rect.top);
1383
+ target = (target ?? map.getZoom()) - event.deltaY * PINCH_ZOOM_SENSITIVITY;
1384
+ if (!frame) {
1385
+ frame = requestAnimationFrame(() => {
1386
+ frame = 0;
1387
+ if (target !== void 0 && point2) {
1388
+ map.setZoomAround(point2, target, {
1389
+ animate: false
1390
+ });
1391
+ target = void 0;
1392
+ }
973
1393
  });
974
- }, 100);
975
- map.on("move", handler);
976
- map.on("zoom", handler);
977
- map.on("focus", () => setAttention(true));
978
- map.on("blur", () => setAttention(false));
979
- return () => {
980
- map.off("move");
981
- map.off("zoom");
982
- map.off("focus");
983
- map.off("blur");
984
- };
985
- }, [
986
- map,
987
- onChange
988
- ]);
989
- useEffect5(() => {
990
- if (!map) {
991
- return;
992
- }
993
- if (attention) {
994
- map.scrollWheelZoom.enable();
995
- } else {
996
- map.scrollWheelZoom.disable();
997
1394
  }
998
- }, [
999
- map,
1000
- attention
1001
- ]);
1002
- return /* @__PURE__ */ React4.createElement(MapContextProvier, {
1003
- attention
1004
- }, /* @__PURE__ */ React4.createElement(MapContainer, {
1005
- ...props,
1006
- ref: mapRef,
1007
- className: mx2("group relative grid bs-full is-full !bg-baseSurface dx-focus-ring-inset", classNames),
1008
- attributionControl: false,
1009
- zoomControl: false,
1010
- scrollWheelZoom,
1011
- doubleClickZoom,
1012
- touchZoom,
1013
- center,
1014
- zoom
1015
- }));
1016
- } finally {
1017
- _effect.f();
1018
- }
1395
+ };
1396
+ container.addEventListener("wheel", onWheel, {
1397
+ passive: false
1398
+ });
1399
+ return () => {
1400
+ container.removeEventListener("wheel", onWheel);
1401
+ cancelAnimationFrame(frame);
1402
+ };
1403
+ }, [
1404
+ map
1405
+ ]);
1406
+ return null;
1407
+ };
1408
+ var MapViewport = composable2((props, _forwardedRef) => {
1409
+ const { scrollWheelZoom = true, doubleClickZoom = true, touchZoom = true, center, zoom, whenReady, children, ...rest } = props;
1410
+ const { attention, registerMap } = useMapContext(MAP_VIEWPORT_NAME);
1411
+ const [map, setMap] = useState5(null);
1412
+ const setMapRef = useCallback4((next) => {
1413
+ setMap(next);
1414
+ registerMap(next);
1415
+ }, [
1416
+ registerMap
1417
+ ]);
1418
+ useEffect7(() => {
1419
+ if (!map) {
1420
+ return;
1421
+ }
1422
+ if (attention) {
1423
+ map.scrollWheelZoom.enable();
1424
+ } else {
1425
+ map.scrollWheelZoom.disable();
1426
+ }
1427
+ }, [
1428
+ map,
1429
+ attention
1430
+ ]);
1431
+ return /* @__PURE__ */ React3.createElement(MapContainer, {
1432
+ ...composableProps2(rest, {
1433
+ // Frame classes (formerly on Map.Root): focusable grid container.
1434
+ classNames: "dx-container group relative grid dx-focus-ring-inset bg-base-surface!"
1435
+ }),
1436
+ attributionControl: false,
1437
+ zoomControl: false,
1438
+ scrollWheelZoom,
1439
+ doubleClickZoom,
1440
+ touchZoom,
1441
+ // Allow fractional zoom so trackpad pinch (small ctrl+wheel deltas) isn't rounded away.
1442
+ zoomSnap: 0,
1443
+ center: center ?? defaults.center,
1444
+ zoom: zoom ?? defaults.zoom,
1445
+ whenReady,
1446
+ ref: setMapRef
1447
+ }, /* @__PURE__ */ React3.createElement(MapResize, null), /* @__PURE__ */ React3.createElement(MapPinchZoom, null), children);
1019
1448
  });
1020
- MapRoot.displayName = "Map.Root";
1021
- var MapTiles = (_props) => {
1022
- var _effect = _useSignals4();
1023
- try {
1024
- const ref = useRef2(null);
1025
- const { attention } = useMapContext(MapTiles.displayName);
1026
- useEffect5(() => {
1027
- if (ref.current) {
1028
- ref.current.getContainer().dataset.attention = attention ? "1" : "0";
1029
- }
1030
- }, [
1031
- attention
1032
- ]);
1033
- return /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(TileLayer, {
1034
- ref,
1035
- "data-attention": attention,
1036
- detectRetina: true,
1037
- className: 'dark:grayscale dark:invert data-[attention="0"]:!opacity-80',
1038
- url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
1039
- keepBuffer: 4
1040
- }));
1041
- } finally {
1042
- _effect.f();
1043
- }
1449
+ MapViewport.displayName = "Map.Viewport";
1450
+ var MAP_TILES_NAME = "Map.Tiles";
1451
+ var DEFAULT_TILE_URL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
1452
+ var MapTiles = ({ url = DEFAULT_TILE_URL }) => {
1453
+ const ref = useRef2(null);
1454
+ const { onChange } = useMapContext(MAP_TILES_NAME);
1455
+ useMapEvents({
1456
+ moveend: (ev) => {
1457
+ onChange?.({
1458
+ center: ev.target.getCenter(),
1459
+ zoom: ev.target.getZoom()
1460
+ });
1461
+ }
1462
+ });
1463
+ const { attention } = useMapContext(MAP_TILES_NAME);
1464
+ useEffect7(() => {
1465
+ if (ref.current) {
1466
+ ref.current.getContainer().dataset.attention = attention ? "1" : "0";
1467
+ }
1468
+ }, [
1469
+ attention
1470
+ ]);
1471
+ return /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(TileLayer, {
1472
+ ref,
1473
+ "data-attention": attention,
1474
+ detectRetina: true,
1475
+ className: 'dark:grayscale dark:invert data-[attention="0"]:!opacity-80',
1476
+ url,
1477
+ keepBuffer: 4
1478
+ }));
1044
1479
  };
1045
- MapTiles.displayName = "Map.Tiles";
1046
- var MapMarkers = ({ selected, markers }) => {
1047
- var _effect = _useSignals4();
1048
- try {
1049
- const map = useMap();
1050
- useEffect5(() => {
1051
- if (markers.length > 0) {
1052
- const bounds = latLngBounds(markers.map((marker) => marker.location));
1053
- map.fitBounds(bounds);
1054
- } else {
1055
- map.setView(defaults.center, defaults.zoom);
1056
- }
1057
- }, [
1058
- markers
1059
- ]);
1060
- return /* @__PURE__ */ React4.createElement(React4.Fragment, null, markers?.map(({ id, title, location: { lat, lng } }) => {
1061
- return /* @__PURE__ */ React4.createElement(Marker, {
1062
- key: id,
1063
- position: {
1064
- lat,
1065
- lng
1066
- },
1067
- icon: (
1068
- // TODO(burdon): Create custom icon from bundled assets.
1069
- // TODO(burdon): Selection state.
1070
- new L.Icon({
1071
- iconUrl: "https://dxos.network/marker-icon.png",
1072
- iconRetinaUrl: "https://dxos.network/marker-icon-2x.png",
1073
- shadowUrl: "https://dxos.network/marker-shadow.png",
1074
- iconSize: [
1075
- 25,
1076
- 41
1077
- ],
1078
- iconAnchor: [
1079
- 12,
1080
- 41
1081
- ],
1082
- popupAnchor: [
1083
- 1,
1084
- -34
1085
- ],
1086
- shadowSize: [
1087
- 41,
1088
- 41
1089
- ]
1090
- })
1091
- )
1092
- }, title && /* @__PURE__ */ React4.createElement(Popup, null, title));
1093
- }));
1094
- } finally {
1095
- _effect.f();
1096
- }
1480
+ MapTiles.displayName = MAP_TILES_NAME;
1481
+ var MapMarkers = ({ selected, markers, lines, onSelect }) => {
1482
+ const map = useMap();
1483
+ useEffect7(() => {
1484
+ const points = [
1485
+ ...markers?.map((marker) => marker.location) ?? [],
1486
+ ...lines?.flatMap((line) => [
1487
+ line.source,
1488
+ line.target
1489
+ ]) ?? []
1490
+ ];
1491
+ if (points.length > 0) {
1492
+ const bounds = latLngBounds(points);
1493
+ const size = map.getSize();
1494
+ const padding = Math.max(48, Math.min(size.x, size.y) / 6);
1495
+ map.fitBounds(bounds, {
1496
+ padding: point(padding, padding),
1497
+ animate: false
1498
+ });
1499
+ }
1500
+ }, [
1501
+ markers,
1502
+ lines,
1503
+ map
1504
+ ]);
1505
+ return /* @__PURE__ */ React3.createElement(React3.Fragment, null, markers?.map(({ id, title, location: { lat, lng } }) => {
1506
+ return /* @__PURE__ */ React3.createElement(Marker, {
1507
+ key: id,
1508
+ position: {
1509
+ lat,
1510
+ lng
1511
+ },
1512
+ eventHandlers: onSelect ? {
1513
+ click: () => onSelect(id)
1514
+ } : void 0,
1515
+ icon: (
1516
+ // TODO(burdon): Create custom icon from bundled assets.
1517
+ // TODO(burdon): Selection state.
1518
+ new L.Icon({
1519
+ iconUrl: "https://dxos.network/marker-icon.png",
1520
+ iconRetinaUrl: "https://dxos.network/marker-icon-2x.png",
1521
+ shadowUrl: "https://dxos.network/marker-shadow.png",
1522
+ iconSize: [
1523
+ 25,
1524
+ 41
1525
+ ],
1526
+ iconAnchor: [
1527
+ 12,
1528
+ 41
1529
+ ],
1530
+ popupAnchor: [
1531
+ 1,
1532
+ -34
1533
+ ],
1534
+ shadowSize: [
1535
+ 41,
1536
+ 41
1537
+ ]
1538
+ })
1539
+ )
1540
+ }, title && /* @__PURE__ */ React3.createElement(Popup, null, title));
1541
+ }));
1097
1542
  };
1098
1543
  MapMarkers.displayName = "Map.Markers";
1099
- var CustomControl2 = ({ position, children }) => {
1100
- var _effect = _useSignals4();
1101
- try {
1102
- const map = useMap();
1103
- useEffect5(() => {
1104
- const control = new Control({
1105
- position
1106
- });
1107
- control.onAdd = () => {
1108
- const container = DomUtil.create("div", mx2("!m-0", controlPositions[position]));
1109
- DomEvent.disableClickPropagation(container);
1110
- DomEvent.disableScrollPropagation(container);
1111
- const root = createRoot(container);
1112
- root.render(/* @__PURE__ */ React4.createElement(ThemeProvider, {
1113
- tx: defaultTx
1114
- }, /* @__PURE__ */ React4.createElement(Tooltip.Provider, null, children)));
1115
- return container;
1116
- };
1117
- control.addTo(map);
1118
- return () => {
1119
- control.remove();
1120
- };
1121
- }, [
1122
- map,
1123
- position,
1124
- children
1125
- ]);
1544
+ var MapLines = ({ lines }) => {
1545
+ if (!lines || lines.length === 0) {
1126
1546
  return null;
1127
- } finally {
1128
- _effect.f();
1129
1547
  }
1130
- };
1131
- var MapZoom = ({ onAction, position = "bottomleft", ...props }) => {
1132
- var _effect = _useSignals4();
1133
- try {
1134
- return /* @__PURE__ */ React4.createElement(CustomControl2, {
1135
- position,
1136
- ...props
1137
- }, /* @__PURE__ */ React4.createElement(ZoomControls, {
1138
- onAction
1139
- }));
1140
- } finally {
1141
- _effect.f();
1548
+ const polylines = [];
1549
+ for (const { source, target, color } of lines) {
1550
+ const last = polylines[polylines.length - 1];
1551
+ const lastPos = last?.positions[last.positions.length - 1];
1552
+ if (last && last.color === color && lastPos?.lat === source.lat && lastPos?.lng === source.lng) {
1553
+ last.positions.push(target);
1554
+ } else {
1555
+ polylines.push({
1556
+ positions: [
1557
+ source,
1558
+ target
1559
+ ],
1560
+ color
1561
+ });
1562
+ }
1142
1563
  }
1564
+ return /* @__PURE__ */ React3.createElement(React3.Fragment, null, polylines.map(({ positions, color }, index) => /* @__PURE__ */ React3.createElement(Polyline, {
1565
+ key: index,
1566
+ positions,
1567
+ pathOptions: {
1568
+ color,
1569
+ weight: 4,
1570
+ opacity: 0.8
1571
+ }
1572
+ })));
1143
1573
  };
1144
- var MapAction = ({ onAction, position = "bottomright", ...props }) => {
1145
- var _effect = _useSignals4();
1146
- try {
1147
- return /* @__PURE__ */ React4.createElement(CustomControl2, {
1148
- position,
1149
- ...props
1150
- }, /* @__PURE__ */ React4.createElement(ActionControls, {
1151
- onAction
1152
- }));
1153
- } finally {
1154
- _effect.f();
1155
- }
1574
+ MapLines.displayName = "Map.Lines";
1575
+ var CustomControl2 = ({ position, children }) => {
1576
+ const map = useMap();
1577
+ const rootRef = useRef2(void 0);
1578
+ useEffect7(() => {
1579
+ const control = new Control({
1580
+ position
1581
+ });
1582
+ control.onAdd = () => {
1583
+ const container = DomUtil.create("div", mx2("m-0!", controlPositions[position]));
1584
+ DomEvent.disableClickPropagation(container);
1585
+ DomEvent.disableScrollPropagation(container);
1586
+ const root = createRoot(container);
1587
+ rootRef.current = root;
1588
+ root.render(/* @__PURE__ */ React3.createElement(ThemeProvider, {
1589
+ tx: defaultTx
1590
+ }, /* @__PURE__ */ React3.createElement(Tooltip.Provider, null, children)));
1591
+ return container;
1592
+ };
1593
+ control.addTo(map);
1594
+ return () => {
1595
+ control.remove();
1596
+ const root = rootRef.current;
1597
+ rootRef.current = void 0;
1598
+ queueMicrotask(() => root?.unmount());
1599
+ };
1600
+ }, [
1601
+ map,
1602
+ position
1603
+ ]);
1604
+ useEffect7(() => {
1605
+ rootRef.current?.render(/* @__PURE__ */ React3.createElement(ThemeProvider, {
1606
+ tx: defaultTx
1607
+ }, /* @__PURE__ */ React3.createElement(Tooltip.Provider, null, children)));
1608
+ }, [
1609
+ children
1610
+ ]);
1611
+ return null;
1156
1612
  };
1157
- var Map = {
1613
+ var MapZoom = ({ onAction, position = "bottomleft", ...props }) => /* @__PURE__ */ React3.createElement(CustomControl2, {
1614
+ position,
1615
+ ...props
1616
+ }, /* @__PURE__ */ React3.createElement(ZoomControls, {
1617
+ onAction
1618
+ }));
1619
+ var MapAction = ({ onAction, position = "bottomright", ...props }) => /* @__PURE__ */ React3.createElement(CustomControl2, {
1620
+ position,
1621
+ ...props
1622
+ }, /* @__PURE__ */ React3.createElement(ActionControls, {
1623
+ onAction
1624
+ }));
1625
+ var Map2 = {
1158
1626
  Root: MapRoot,
1627
+ Viewport: MapViewport,
1159
1628
  Tiles: MapTiles,
1160
1629
  Markers: MapMarkers,
1630
+ Lines: MapLines,
1161
1631
  Zoom: MapZoom,
1162
1632
  Action: MapAction
1163
1633
  };
1164
1634
  export {
1165
1635
  ActionControls,
1636
+ DEFAULT_TILE_URL,
1166
1637
  Globe,
1167
- GlobeContextProvider,
1168
- Map,
1638
+ GlobeContext,
1639
+ Map2 as Map,
1169
1640
  ZoomControls,
1170
1641
  closestPoint,
1171
1642
  controlPositions,
1172
1643
  createLayers,
1644
+ createRotationTween,
1645
+ flyDuration,
1173
1646
  geoCircle,
1174
1647
  geoInertiaDrag,
1175
1648
  geoLine,
1176
1649
  geoPoint,
1177
1650
  geoToPosition,
1178
1651
  getDistance,
1179
- loadTopology,
1652
+ globeStyles,
1180
1653
  positionToRotation,
1181
1654
  renderLayers,
1182
1655
  restrictAxis,
@@ -1185,7 +1658,10 @@ export {
1185
1658
  useGlobeContext,
1186
1659
  useGlobeZoomHandler,
1187
1660
  useMapZoomHandler,
1661
+ useSimplifiedTopology,
1188
1662
  useSpinner,
1189
- useTour
1663
+ useTopology,
1664
+ useTour,
1665
+ useWheel
1190
1666
  };
1191
1667
  //# sourceMappingURL=index.mjs.map