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