@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,53 +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
- var defaults = {
20
- center: {
21
- lat: 51,
22
- lng: 0
23
- },
24
- zoom: 4
25
- };
26
17
  var GlobeContext = /* @__PURE__ */ createContext(void 0);
27
- var GlobeContextProvider = ({ children, size, center: centerParam = defaults.center, zoom: zoomParam = defaults.zoom, translation: translationParam, rotation: rotationParam }) => {
28
- var _effect = _useSignals();
29
- try {
30
- const [center, setCenter] = useControlledState(centerParam);
31
- const [zoom, setZoom] = useControlledState(zoomParam);
32
- const [translation, setTranslation] = useControlledState(translationParam);
33
- const [rotation, setRotation] = useControlledState(rotationParam);
34
- return /* @__PURE__ */ React.createElement(GlobeContext.Provider, {
35
- value: {
36
- size,
37
- center,
38
- zoom,
39
- translation,
40
- rotation,
41
- setCenter,
42
- setZoom,
43
- setTranslation,
44
- setRotation
45
- }
46
- }, children);
47
- } finally {
48
- _effect.f();
49
- }
50
- };
51
18
  var useGlobeContext = () => {
52
19
  return useContext(GlobeContext) ?? raise(new Error("Missing GlobeContext"));
53
20
  };
@@ -56,6 +23,18 @@ var useGlobeContext = () => {
56
23
  import { select as select2 } from "d3";
57
24
  import { useEffect } from "react";
58
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
+
59
38
  // src/util/debug.ts
60
39
  var debug = false;
61
40
  var timer = (cb) => {
@@ -73,7 +52,7 @@ var timer = (cb) => {
73
52
 
74
53
  // src/util/inertia.ts
75
54
  import { drag, select, timer as timer2 } from "d3";
76
- import versor from "versor";
55
+ import versor2 from "versor";
77
56
  var restrictAxis = (axis) => (original, current) => current.map((d, i) => axis[i] ? d : original[i]);
78
57
  var geoInertiaDrag = (target, render, projection, options) => {
79
58
  if (!options) {
@@ -83,21 +62,23 @@ var geoInertiaDrag = (target, render, projection, options) => {
83
62
  target = target.node();
84
63
  }
85
64
  target = select(target);
86
- 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 = {
87
76
  projection,
88
77
  render: (rotation) => {
89
78
  projection.rotate(rotation);
90
79
  render && render();
91
80
  },
92
- axis: restrictAxis(options.xAxis ? [
93
- true,
94
- false,
95
- false
96
- ] : [
97
- true,
98
- true,
99
- true
100
- ]),
81
+ axis,
101
82
  start: options.start,
102
83
  move: options.move,
103
84
  end: options.end,
@@ -105,7 +86,12 @@ var geoInertiaDrag = (target, render, projection, options) => {
105
86
  finish: options.finish,
106
87
  time: options.time,
107
88
  hold: options.hold
108
- });
89
+ };
90
+ const inertia = linear ? geoInertiaDragLinearHelper({
91
+ ...sharedHandlers,
92
+ sensitivity: options.sensitivity,
93
+ getZoom: options.getZoom
94
+ }) : geoInertiaDragHelper(sharedHandlers);
109
95
  target.call(drag().on("start", inertia.start).on("drag", inertia.move).on("end", inertia.end));
110
96
  return inertia;
111
97
  };
@@ -120,9 +106,9 @@ var geoInertiaDragHelper = (opt) => {
120
106
  const inertia = inertiaHelper({
121
107
  axis: opt.axis,
122
108
  start: () => {
123
- v0 = versor.cartesian(projection.invert(inertia.position));
109
+ v0 = versor2.cartesian(projection.invert(inertia.position));
124
110
  r0 = projection.rotate();
125
- q0 = versor(r0);
111
+ q0 = versor2(r0);
126
112
  opt.start && opt.start();
127
113
  },
128
114
  move: () => {
@@ -130,23 +116,23 @@ var geoInertiaDragHelper = (opt) => {
130
116
  if (isNaN(inv[0])) {
131
117
  return;
132
118
  }
133
- const v1 = versor.cartesian(inv);
134
- const q1 = versor.multiply(q0, versor.delta(v0, v1));
135
- 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);
136
122
  const r2 = opt.axis(r0, r1);
137
123
  opt.render(r2);
138
124
  opt.move && opt.move();
139
125
  },
140
126
  end: () => {
141
- v10 = versor.cartesian(projection.invert(inertia.position.map((d, i) => d - inertia.velocity[i] / 1e3)));
142
- q10 = versor(projection.rotate());
143
- 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));
144
130
  opt.end && opt.end();
145
131
  },
146
132
  stop: opt.stop,
147
133
  finish: opt.finish,
148
134
  render: (t) => {
149
- 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)));
150
136
  const r2 = opt.axis(r0, r1);
151
137
  opt.render && opt.render(r2);
152
138
  },
@@ -154,6 +140,62 @@ var geoInertiaDragHelper = (opt) => {
154
140
  });
155
141
  return inertia;
156
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
+ };
157
199
  function inertiaHelper(opt) {
158
200
  const A = opt.time || 5e3;
159
201
  const limit = 1.0001;
@@ -257,9 +299,9 @@ var geoToPosition = ({ lat, lng }) => [
257
299
  lng,
258
300
  lat
259
301
  ];
260
- var geoPoint = (point) => ({
302
+ var geoPoint = (point2) => ({
261
303
  type: "Point",
262
- coordinates: geoToPosition(point)
304
+ coordinates: geoToPosition(point2)
263
305
  });
264
306
  var geoCircle = ({ lat, lng }, radius) => d3GeoCircle().radius(radius).center([
265
307
  lng,
@@ -284,11 +326,11 @@ var closestPoint = (points, target) => {
284
326
  }
285
327
  let closestPoint2 = points[0];
286
328
  let minDistance = getDistance(points[0], target);
287
- for (const point of points) {
288
- const distance = getDistance(point, target);
329
+ for (const point2 of points) {
330
+ const distance = getDistance(point2, target);
289
331
  if (distance < minDistance) {
290
332
  minDistance = distance;
291
- closestPoint2 = point;
333
+ closestPoint2 = point2;
292
334
  }
293
335
  }
294
336
  return closestPoint2;
@@ -300,8 +342,48 @@ var getDistance = (point1, point2) => {
300
342
  };
301
343
 
302
344
  // src/util/render.ts
303
- import { geoGraticule } from "d3";
345
+ import { geoBounds, geoCentroid, geoDistance as geoDistance2, geoGraticule } from "d3";
304
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
+ };
305
387
  var createLayers = (topology, features, styles) => {
306
388
  const layers = [];
307
389
  if (styles.water) {
@@ -322,11 +404,25 @@ var createLayers = (topology, features, styles) => {
322
404
  });
323
405
  }
324
406
  if (topology) {
325
- if (topology.objects.land && styles.land) {
326
- layers.push({
327
- styles: styles.land,
328
- path: feature(topology, topology.objects.land)
329
- });
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
+ }
330
426
  }
331
427
  if (topology.objects.countries && styles.border) {
332
428
  layers.push({
@@ -343,28 +439,28 @@ var createLayers = (topology, features, styles) => {
343
439
  }
344
440
  if (features) {
345
441
  const { points, lines } = features;
346
- if (points && styles.point) {
442
+ if (lines && styles.line) {
347
443
  layers.push({
348
- styles: styles.point,
444
+ styles: styles.line,
349
445
  path: {
350
446
  type: "GeometryCollection",
351
- geometries: points.map((point) => geoPoint(point))
447
+ geometries: lines.map(({ source, target }) => geoLine(source, target))
352
448
  }
353
449
  });
354
450
  }
355
- if (lines && styles.line) {
451
+ if (points && styles.point) {
356
452
  layers.push({
357
- styles: styles.line,
453
+ styles: styles.point,
358
454
  path: {
359
455
  type: "GeometryCollection",
360
- geometries: lines.map(({ source, target }) => geoLine(source, target))
456
+ geometries: points.map((point2) => geoPoint(point2))
361
457
  }
362
458
  });
363
459
  }
364
460
  }
365
461
  return layers;
366
462
  };
367
- var renderLayers = (generator, layers = [], scale, styles) => {
463
+ var renderLayers = (generator, layers = [], scale, styles, viewCenter) => {
368
464
  const context = generator.context();
369
465
  const { canvas: { width, height } } = context;
370
466
  context.reset();
@@ -374,7 +470,8 @@ var renderLayers = (generator, layers = [], scale, styles) => {
374
470
  } else {
375
471
  context.clearRect(0, 0, width, height);
376
472
  }
377
- layers.forEach(({ path, styles: styles2 }) => {
473
+ layers.forEach((layer) => {
474
+ const { path, styles: styles2, cullable } = layer;
378
475
  context.save();
379
476
  let fill = false;
380
477
  let stroke = false;
@@ -389,8 +486,23 @@ var renderLayers = (generator, layers = [], scale, styles) => {
389
486
  }
390
487
  });
391
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
+ }
392
504
  context.beginPath();
393
- generator(path);
505
+ generator(renderPath);
394
506
  fill && context.fill();
395
507
  stroke && context.stroke();
396
508
  context.restore();
@@ -398,6 +510,57 @@ var renderLayers = (generator, layers = [], scale, styles) => {
398
510
  return context;
399
511
  };
400
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
+
401
564
  // src/hooks/useDrag.ts
402
565
  var useDrag = (controller, options = {}) => {
403
566
  useEffect(() => {
@@ -405,14 +568,19 @@ var useDrag = (controller, options = {}) => {
405
568
  if (!canvas || options.disabled) {
406
569
  return;
407
570
  }
408
- geoInertiaDrag(select2(canvas), () => {
571
+ const inertia = geoInertiaDrag(select2(canvas), () => {
409
572
  controller.setRotation(controller.projection.rotate());
410
573
  options.onUpdate?.({
411
574
  type: "move",
412
575
  controller
413
576
  });
414
577
  }, controller.projection, {
415
- 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,
416
584
  time: 3e3,
417
585
  start: () => options.onUpdate?.({
418
586
  type: "start",
@@ -425,6 +593,7 @@ var useDrag = (controller, options = {}) => {
425
593
  });
426
594
  return () => {
427
595
  cancelDrag(select2(canvas));
596
+ inertia?.timer?.stop();
428
597
  };
429
598
  }, [
430
599
  controller,
@@ -482,6 +651,61 @@ var useMapZoomHandler = (controller) => {
482
651
  ]);
483
652
  };
484
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
+
485
709
  // src/hooks/useSpinner.ts
486
710
  import { timer as d3Timer } from "d3";
487
711
  import { useEffect as useEffect2, useState } from "react";
@@ -535,91 +759,139 @@ var useSpinner = (controller, options = {}) => {
535
759
  ];
536
760
  };
537
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
+
538
815
  // src/hooks/useTour.ts
539
- import { selection as d3Selection, geoDistance, geoInterpolate, geoPath } from "d3";
540
- import { useEffect as useEffect3, useMemo, useState as useState2 } from "react";
541
- import versor2 from "versor";
542
- var TRANSITION_NAME = "globe-tour";
816
+ import { geoInterpolate, geoPath } from "d3";
817
+ import { useEffect as useEffect4, useState as useState3 } from "react";
543
818
  var defaultDuration = 1500;
544
819
  var useTour = (controller, points, options = {}) => {
545
- const selection = useMemo(() => d3Selection(), []);
546
- const [running, setRunning] = useState2(options.running ?? false);
547
- useEffect3(() => {
548
- if (!running) {
549
- selection.interrupt(TRANSITION_NAME);
820
+ const [running, setRunning] = useState3(options.running ?? false);
821
+ useEffect4(() => {
822
+ if (!controller || !running) {
550
823
  return;
551
824
  }
552
- let t;
553
- if (controller && running) {
554
- t = setTimeout(async () => {
555
- const { canvas, projection, setRotation } = controller;
556
- const context = canvas.getContext("2d", {
557
- alpha: false
558
- });
559
- const path = geoPath(projection, context).pointRadius(2);
560
- 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
+ }
561
839
  let last;
562
- try {
563
- const p = [
564
- ...points
565
- ];
566
- if (options.loop) {
567
- p.push(p[0]);
840
+ for (const next of tourPoints) {
841
+ if (cancelled) {
842
+ break;
568
843
  }
569
- for (const next of p) {
570
- if (!running) {
571
- break;
572
- }
573
- const p1 = last ? geoToPosition(last) : void 0;
574
- const p2 = geoToPosition(next);
575
- const ip = geoInterpolate(p1 || p2, p2);
576
- const distance = geoDistance(p1 || p2, p2);
577
- const r1 = p1 ? positionToRotation(p1, tilt) : controller.projection.rotate();
578
- const r2 = positionToRotation(p2, tilt);
579
- const iv = versor2.interpolate(r1, r2);
580
- const transition2 = selection.transition(TRANSITION_NAME).duration(Math.max(options.duration ?? defaultDuration, distance * 2e3)).tween("render", () => (t2) => {
581
- const t1 = Math.max(0, Math.min(1, t2 * 2 - 1));
582
- const t22 = Math.min(1, t2 * 2);
583
- context.save();
584
- {
585
- context.beginPath();
586
- context.strokeStyle = options?.styles?.arc?.strokeStyle ?? "yellow";
587
- context.lineWidth = (options?.styles?.arc?.lineWidth ?? 1.5) * (controller?.zoom ?? 1);
588
- context.setLineDash(options?.styles?.arc?.lineDash ?? []);
589
- path({
590
- type: "LineString",
591
- coordinates: [
592
- ip(t1),
593
- ip(t22)
594
- ]
595
- });
596
- context.stroke();
597
- context.beginPath();
598
- context.fillStyle = options?.styles?.cursor?.fillStyle ?? "orange";
599
- path.pointRadius((options?.styles?.cursor?.pointRadius ?? 2) * (controller?.zoom ?? 1));
600
- path({
601
- type: "Point",
602
- coordinates: ip(t22)
603
- });
604
- context.fill();
605
- }
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 {
606
873
  context.restore();
607
- projection.rotate(iv(t2));
608
- setRotation(projection.rotate());
609
- });
610
- await transition2.end();
611
- last = next;
612
- }
613
- } catch {
614
- } 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) {
615
886
  setRunning(false);
616
887
  }
617
- });
618
- return () => {
619
- clearTimeout(t);
620
- selection.interrupt(TRANSITION_NAME);
621
- };
622
- }
888
+ }
889
+ });
890
+ return () => {
891
+ cancelled = true;
892
+ clearTimeout(t);
893
+ controller.cancelFlyTo();
894
+ };
623
895
  }, [
624
896
  controller,
625
897
  running,
@@ -631,27 +903,55 @@ var useTour = (controller, points, options = {}) => {
631
903
  ];
632
904
  };
633
905
 
634
- // src/components/Toolbar/Controls.tsx
635
- import { useSignals as _useSignals2 } from "@preact-signals/safe-react/tracking";
636
- import React2 from "react";
637
- import { IconButton, Toolbar, useTranslation } from "@dxos/react-ui";
638
-
639
- // src/translations.ts
640
- var translationKey = "react-ui-geo";
641
- var translations = [
642
- {
643
- "en-US": {
644
- [translationKey]: {
645
- "zoom in icon button": "Zoom in",
646
- "zoom out icon button": "Zoom out",
647
- "start icon button": "Start",
648
- "toggle icon button": "Toggle"
649
- }
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;
650
915
  }
651
- }
652
- ];
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
+ };
653
950
 
654
951
  // src/components/Toolbar/Controls.tsx
952
+ import React from "react";
953
+ import { IconButton, Toolbar, useTranslation } from "@dxos/react-ui";
954
+ import { translationKey } from "#translations";
655
955
  var controlPositions = {
656
956
  topleft: "top-2 left-2",
657
957
  topright: "top-2 right-2",
@@ -659,52 +959,42 @@ var controlPositions = {
659
959
  bottomright: "bottom-2 right-2"
660
960
  };
661
961
  var ZoomControls = ({ classNames, onAction }) => {
662
- var _effect = _useSignals2();
663
- try {
664
- const { t } = useTranslation(translationKey);
665
- return /* @__PURE__ */ React2.createElement(Toolbar.Root, {
666
- classNames: [
667
- "gap-2",
668
- classNames
669
- ]
670
- }, /* @__PURE__ */ React2.createElement(IconButton, {
671
- icon: "ph--plus--regular",
672
- iconOnly: true,
673
- label: t("zoom in icon button"),
674
- onClick: () => onAction?.("zoom-in")
675
- }), /* @__PURE__ */ React2.createElement(IconButton, {
676
- icon: "ph--minus--regular",
677
- iconOnly: true,
678
- label: t("zoom out icon button"),
679
- onClick: () => onAction?.("zoom-out")
680
- }));
681
- } finally {
682
- _effect.f();
683
- }
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
+ }));
684
979
  };
685
980
  var ActionControls = ({ classNames, onAction }) => {
686
- var _effect = _useSignals2();
687
- try {
688
- const { t } = useTranslation(translationKey);
689
- return /* @__PURE__ */ React2.createElement(Toolbar.Root, {
690
- classNames: [
691
- "gap-2",
692
- classNames
693
- ]
694
- }, /* @__PURE__ */ React2.createElement(IconButton, {
695
- icon: "ph--path--regular",
696
- iconOnly: true,
697
- label: t("start icon button"),
698
- onClick: () => onAction?.("start")
699
- }), /* @__PURE__ */ React2.createElement(IconButton, {
700
- icon: "ph--globe-hemisphere-west--regular",
701
- iconOnly: true,
702
- label: t("toggle icon button"),
703
- onClick: () => onAction?.("toggle")
704
- }));
705
- } finally {
706
- _effect.f();
707
- }
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
+ }));
708
998
  };
709
999
 
710
1000
  // src/components/Globe/Globe.tsx
@@ -762,188 +1052,245 @@ var getProjection = (type = "orthographic") => {
762
1052
  }
763
1053
  return type ?? geoOrthographic();
764
1054
  };
765
- var GlobeRoot = ({ classNames, children, ...props }) => {
766
- var _effect = _useSignals3();
767
- try {
768
- const { ref, width, height } = useResizeDetector();
769
- return /* @__PURE__ */ React3.createElement("div", {
770
- ref,
771
- className: mx("relative flex grow overflow-hidden", classNames)
772
- }, /* @__PURE__ */ React3.createElement(GlobeContextProvider, {
773
- size: {
774
- width,
775
- height
776
- },
777
- ...props
778
- }, children));
779
- } finally {
780
- _effect.f();
781
- }
782
- };
783
- var GlobeCanvas = /* @__PURE__ */ forwardRef(({ projection: projectionParam, topology, features, styles: stylesParam }, forwardRef3) => {
784
- var _effect = _useSignals3();
785
- try {
786
- const { themeMode } = useThemeContext();
787
- const styles = useMemo2(() => stylesParam ?? defaultStyles[themeMode], [
788
- stylesParam,
789
- themeMode
790
- ]);
791
- const [canvas, setCanvas] = useState3(null);
792
- const canvasRef = (canvas2) => setCanvas(canvas2);
793
- const projection = useMemo2(() => getProjection(projectionParam), [
794
- projectionParam
795
- ]);
796
- const layers = useMemo2(() => {
797
- return timer(() => createLayers(topology, features, styles));
798
- }, [
799
- topology,
800
- features,
801
- styles
802
- ]);
803
- const { size, center, zoom, translation, rotation, setCenter, setZoom, setTranslation, setRotation } = useGlobeContext();
804
- const zoomRef = useDynamicRef(zoom);
805
- useEffect4(() => {
806
- if (center) {
807
- setZoom(1);
808
- setRotation(positionToRotation(geoToPosition(center)));
809
- }
810
- }, [
811
- center
812
- ]);
813
- const zooming = useRef(false);
814
- useImperativeHandle(forwardRef3, () => {
815
- return {
816
- canvas,
817
- projection,
818
- center,
819
- get zoom() {
820
- return zoomRef.current;
821
- },
822
- translation,
823
- rotation,
824
- setCenter,
825
- setZoom: (s) => {
826
- if (typeof s === "function") {
827
- const is = interpolateNumber(zoomRef.current, s(zoomRef.current));
828
- transition().ease(zooming.current ? easeLinear : easeSinOut).duration(200).tween("scale", () => (t) => setZoom(is(t))).on("end", () => {
829
- zooming.current = false;
830
- });
831
- } else {
832
- setZoom(s);
833
- }
834
- },
835
- setTranslation,
836
- setRotation
837
- };
838
- }, [
839
- canvas
840
- ]);
841
- const generator = useMemo2(() => canvas && projection && geoPath2(projection, canvas.getContext("2d", {
842
- alpha: false
843
- })), [
844
- canvas,
845
- projection
846
- ]);
847
- useEffect4(() => {
848
- if (canvas && projection) {
849
- timer(() => {
850
- projection.scale(Math.min(size.width, size.height) / 2 * zoom).translate([
851
- size.width / 2 + (translation?.x ?? 0),
852
- size.height / 2 + (translation?.y ?? 0)
853
- ]).rotate(rotation ?? [
854
- 0,
855
- 0,
856
- 0
857
- ]);
858
- renderLayers(generator, layers, zoom, styles);
859
- });
860
- }
861
- }, [
862
- 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: {
863
1072
  size,
1073
+ center,
864
1074
  zoom,
865
1075
  translation,
866
1076
  rotation,
867
- layers
868
- ]);
869
- if (!size.width || !size.height) {
870
- return null;
1077
+ setSize,
1078
+ setCenter,
1079
+ setZoom,
1080
+ setTranslation,
1081
+ setRotation,
1082
+ registerController
871
1083
  }
872
- return /* @__PURE__ */ React3.createElement("canvas", {
873
- ref: canvasRef,
874
- width: size.width,
875
- 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
876
1098
  });
877
- } finally {
878
- _effect.f();
879
- }
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);
880
1110
  });
881
- var GlobeDebug = ({ position = "topleft" }) => {
882
- var _effect = _useSignals3();
883
- try {
884
- const { size, zoom, translation, rotation } = useGlobeContext();
885
- return /* @__PURE__ */ React3.createElement("div", {
886
- className: mx("z-10 absolute is-96 p-2 overflow-hidden border border-green-700 rounded", controlPositions[position])
887
- }, /* @__PURE__ */ React3.createElement("pre", {
888
- className: "font-mono text-xs text-green-700"
889
- }, JSON.stringify({
890
- size,
891
- 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
+ },
892
1155
  translation,
893
- rotation
894
- }, null, 2)));
895
- } finally {
896
- _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;
897
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)));
898
1268
  };
899
1269
  var GlobePanel = ({ position, classNames, children }) => {
900
- var _effect = _useSignals3();
901
- try {
902
- return /* @__PURE__ */ React3.createElement("div", {
903
- className: mx("z-10 absolute overflow-hidden", controlPositions[position], classNames)
904
- }, children);
905
- } finally {
906
- _effect.f();
907
- }
1270
+ return /* @__PURE__ */ React2.createElement("div", {
1271
+ className: mx("z-10 absolute overflow-hidden", controlPositions[position], classNames)
1272
+ }, children);
908
1273
  };
909
1274
  var CustomControl = ({ position, children }) => {
910
- var _effect = _useSignals3();
911
- try {
912
- return /* @__PURE__ */ React3.createElement("div", {
913
- className: mx("z-10 absolute overflow-hidden", controlPositions[position])
914
- }, children);
915
- } finally {
916
- _effect.f();
917
- }
918
- };
919
- var GlobeZoom = ({ onAction, position = "bottomleft", ...props }) => {
920
- var _effect = _useSignals3();
921
- try {
922
- return /* @__PURE__ */ React3.createElement(CustomControl, {
923
- position,
924
- ...props
925
- }, /* @__PURE__ */ React3.createElement(ZoomControls, {
926
- onAction
927
- }));
928
- } finally {
929
- _effect.f();
930
- }
931
- };
932
- var GlobeAction = ({ onAction, position = "bottomright", ...props }) => {
933
- var _effect = _useSignals3();
934
- try {
935
- return /* @__PURE__ */ React3.createElement(CustomControl, {
936
- position,
937
- ...props
938
- }, /* @__PURE__ */ React3.createElement(ActionControls, {
939
- onAction
940
- }));
941
- } finally {
942
- _effect.f();
943
- }
1275
+ return /* @__PURE__ */ React2.createElement("div", {
1276
+ className: mx("z-10 absolute overflow-hidden", controlPositions[position])
1277
+ }, children);
944
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
+ }));
945
1291
  var Globe = {
946
1292
  Root: GlobeRoot,
1293
+ Viewport: GlobeViewport,
947
1294
  Canvas: GlobeCanvas,
948
1295
  Zoom: GlobeZoom,
949
1296
  Action: GlobeAction,
@@ -952,249 +1299,369 @@ var Globe = {
952
1299
  };
953
1300
 
954
1301
  // src/components/Map/Map.tsx
955
- import { useSignals as _useSignals4 } from "@preact-signals/safe-react/tracking";
956
1302
  import "leaflet/dist/leaflet.css";
957
1303
  import { createContext as createContext2 } from "@radix-ui/react-context";
958
- import L, { Control, DomEvent, DomUtil, latLngBounds } from "leaflet";
959
- 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";
960
1306
  import { createRoot } from "react-dom/client";
961
- import { MapContainer, Marker, Popup, TileLayer, useMap, useMapEvents } from "react-leaflet";
1307
+ import { MapContainer, Marker, Polyline, Popup, TileLayer, useMap, useMapEvents } from "react-leaflet";
962
1308
  import { ThemeProvider, Tooltip } from "@dxos/react-ui";
963
- import { defaultTx, mx as mx2 } from "@dxos/react-ui-theme";
964
- var defaults2 = {
1309
+ import { composable as composable2, composableProps as composableProps2, defaultTx } from "@dxos/react-ui";
1310
+ import { mx as mx2 } from "@dxos/ui-theme";
1311
+ var defaults = {
965
1312
  center: {
966
1313
  lat: 51,
967
1314
  lng: 0
968
1315
  },
969
1316
  zoom: 4
970
1317
  };
971
- var [MapContextProvier, useMapContext] = createContext2("Map");
972
- var MapRoot = /* @__PURE__ */ forwardRef2(({ classNames, scrollWheelZoom = true, doubleClickZoom = true, touchZoom = true, center, zoom, onChange, ...props }, forwardedRef) => {
973
- var _effect = _useSignals4();
974
- try {
975
- const [attention, setAttention] = useState4(false);
976
- const mapRef = useRef2(null);
977
- const map = mapRef.current;
978
- useImperativeHandle2(forwardedRef, () => ({
979
- setCenter: (center2, zoom2) => {
980
- mapRef.current?.setView(center2, zoom2);
981
- },
982
- setZoom: (cb) => {
983
- mapRef.current?.setZoom(cb(mapRef.current?.getZoom() ?? 0));
984
- }
985
- }), []);
986
- useEffect5(() => {
987
- if (!map) {
988
- return;
989
- }
990
- if (attention) {
991
- map.scrollWheelZoom.enable();
992
- } else {
993
- map.scrollWheelZoom.disable();
994
- }
995
- }, [
996
- map,
997
- attention
998
- ]);
999
- return /* @__PURE__ */ React4.createElement(MapContextProvier, {
1000
- attention,
1001
- onChange
1002
- }, /* @__PURE__ */ React4.createElement(MapContainer, {
1003
- ...props,
1004
- ref: mapRef,
1005
- className: mx2("group relative grid bs-full is-full !bg-baseSurface dx-focus-ring-inset", classNames),
1006
- attributionControl: false,
1007
- zoomControl: false,
1008
- scrollWheelZoom,
1009
- doubleClickZoom,
1010
- touchZoom,
1011
- center: center ?? defaults2.center,
1012
- zoom: zoom ?? defaults2.zoom
1013
- }));
1014
- } finally {
1015
- _effect.f();
1016
- }
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);
1017
1346
  });
1018
1347
  MapRoot.displayName = "Map.Root";
1019
- var MapTiles = (_props) => {
1020
- var _effect = _useSignals4();
1021
- try {
1022
- const ref = useRef2(null);
1023
- const { onChange } = useMapContext(MapTiles.displayName);
1024
- useMapEvents({
1025
- zoomstart: (ev) => {
1026
- onChange?.({
1027
- center: ev.target.getCenter(),
1028
- zoom: ev.target.getZoom()
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) {
1378
+ return;
1379
+ }
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
+ }
1029
1393
  });
1030
1394
  }
1395
+ };
1396
+ container.addEventListener("wheel", onWheel, {
1397
+ passive: false
1031
1398
  });
1032
- const { attention } = useMapContext(MapTiles.displayName);
1033
- useEffect5(() => {
1034
- if (ref.current) {
1035
- ref.current.getContainer().dataset.attention = attention ? "1" : "0";
1036
- }
1037
- }, [
1038
- attention
1039
- ]);
1040
- return /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(TileLayer, {
1041
- ref,
1042
- "data-attention": attention,
1043
- detectRetina: true,
1044
- className: 'dark:grayscale dark:invert data-[attention="0"]:!opacity-80',
1045
- url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
1046
- keepBuffer: 4
1047
- }));
1048
- } finally {
1049
- _effect.f();
1050
- }
1399
+ return () => {
1400
+ container.removeEventListener("wheel", onWheel);
1401
+ cancelAnimationFrame(frame);
1402
+ };
1403
+ }, [
1404
+ map
1405
+ ]);
1406
+ return null;
1051
1407
  };
1052
- MapTiles.displayName = "Map.Tiles";
1053
- var MapMarkers = ({ selected, markers }) => {
1054
- var _effect = _useSignals4();
1055
- try {
1056
- const map = useMap();
1057
- useEffect5(() => {
1058
- if (markers.length > 0) {
1059
- const bounds = latLngBounds(markers.map((marker) => marker.location));
1060
- map.fitBounds(bounds);
1061
- } else {
1062
- map.setView(defaults2.center, defaults2.zoom);
1063
- }
1064
- }, [
1065
- markers
1066
- ]);
1067
- return /* @__PURE__ */ React4.createElement(React4.Fragment, null, markers?.map(({ id, title, location: { lat, lng } }) => {
1068
- return /* @__PURE__ */ React4.createElement(Marker, {
1069
- key: id,
1070
- position: {
1071
- lat,
1072
- lng
1073
- },
1074
- icon: (
1075
- // TODO(burdon): Create custom icon from bundled assets.
1076
- // TODO(burdon): Selection state.
1077
- new L.Icon({
1078
- iconUrl: "https://dxos.network/marker-icon.png",
1079
- iconRetinaUrl: "https://dxos.network/marker-icon-2x.png",
1080
- shadowUrl: "https://dxos.network/marker-shadow.png",
1081
- iconSize: [
1082
- 25,
1083
- 41
1084
- ],
1085
- iconAnchor: [
1086
- 12,
1087
- 41
1088
- ],
1089
- popupAnchor: [
1090
- 1,
1091
- -34
1092
- ],
1093
- shadowSize: [
1094
- 41,
1095
- 41
1096
- ]
1097
- })
1098
- )
1099
- }, title && /* @__PURE__ */ React4.createElement(Popup, null, title));
1100
- }));
1101
- } finally {
1102
- _effect.f();
1103
- }
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);
1448
+ });
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
+ }));
1104
1479
  };
1105
- MapMarkers.displayName = "Map.Markers";
1106
- var CustomControl2 = ({ position, children }) => {
1107
- var _effect = _useSignals4();
1108
- try {
1109
- const map = useMap();
1110
- useEffect5(() => {
1111
- const control = new Control({
1112
- position
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
1113
1498
  });
1114
- control.onAdd = () => {
1115
- const container = DomUtil.create("div", mx2("!m-0", controlPositions[position]));
1116
- DomEvent.disableClickPropagation(container);
1117
- DomEvent.disableScrollPropagation(container);
1118
- const root = createRoot(container);
1119
- root.render(/* @__PURE__ */ React4.createElement(ThemeProvider, {
1120
- tx: defaultTx
1121
- }, /* @__PURE__ */ React4.createElement(Tooltip.Provider, null, children)));
1122
- return container;
1123
- };
1124
- control.addTo(map);
1125
- return () => {
1126
- control.remove();
1127
- };
1128
- }, [
1129
- map,
1130
- position,
1131
- children
1132
- ]);
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
+ }));
1542
+ };
1543
+ MapMarkers.displayName = "Map.Markers";
1544
+ var MapLines = ({ lines }) => {
1545
+ if (!lines || lines.length === 0) {
1133
1546
  return null;
1134
- } finally {
1135
- _effect.f();
1136
1547
  }
1137
- };
1138
- var MapZoom = ({ onAction, position = "bottomleft", ...props }) => {
1139
- var _effect = _useSignals4();
1140
- try {
1141
- return /* @__PURE__ */ React4.createElement(CustomControl2, {
1142
- position,
1143
- ...props
1144
- }, /* @__PURE__ */ React4.createElement(ZoomControls, {
1145
- onAction
1146
- }));
1147
- } finally {
1148
- _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
+ }
1149
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
+ })));
1150
1573
  };
1151
- var MapAction = ({ onAction, position = "bottomright", ...props }) => {
1152
- var _effect = _useSignals4();
1153
- try {
1154
- return /* @__PURE__ */ React4.createElement(CustomControl2, {
1155
- position,
1156
- ...props
1157
- }, /* @__PURE__ */ React4.createElement(ActionControls, {
1158
- onAction
1159
- }));
1160
- } finally {
1161
- _effect.f();
1162
- }
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;
1163
1612
  };
1164
- 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 = {
1165
1626
  Root: MapRoot,
1627
+ Viewport: MapViewport,
1166
1628
  Tiles: MapTiles,
1167
1629
  Markers: MapMarkers,
1630
+ Lines: MapLines,
1168
1631
  Zoom: MapZoom,
1169
1632
  Action: MapAction
1170
1633
  };
1171
1634
  export {
1172
1635
  ActionControls,
1636
+ DEFAULT_TILE_URL,
1173
1637
  Globe,
1174
- GlobeContextProvider,
1175
- Map,
1638
+ GlobeContext,
1639
+ Map2 as Map,
1176
1640
  ZoomControls,
1177
1641
  closestPoint,
1178
1642
  controlPositions,
1179
1643
  createLayers,
1644
+ createRotationTween,
1645
+ flyDuration,
1180
1646
  geoCircle,
1181
1647
  geoInertiaDrag,
1182
1648
  geoLine,
1183
1649
  geoPoint,
1184
1650
  geoToPosition,
1185
1651
  getDistance,
1186
- loadTopology,
1652
+ globeStyles,
1187
1653
  positionToRotation,
1188
1654
  renderLayers,
1189
1655
  restrictAxis,
1190
1656
  timer,
1191
- translationKey,
1192
- translations,
1193
1657
  useDrag,
1194
1658
  useGlobeContext,
1195
1659
  useGlobeZoomHandler,
1196
1660
  useMapZoomHandler,
1661
+ useSimplifiedTopology,
1197
1662
  useSpinner,
1198
- useTour
1663
+ useTopology,
1664
+ useTour,
1665
+ useWheel
1199
1666
  };
1200
1667
  //# sourceMappingURL=index.mjs.map