import {
  loadTopology
} from "./chunk-SC2FBYFU.mjs";

// src/components/Globe/Globe.tsx
import { selection as d3Selection, easeLinear, easeSinOut, geoMercator, geoOrthographic, geoPath as geoPath2, geoTransverseMercator, interpolateNumber, transition } from "d3";
import React2, { forwardRef, useCallback as useCallback3, useEffect as useEffect6, useId, useImperativeHandle, useMemo as useMemo2, useRef, useState as useState4 } from "react";
import { useResizeDetector } from "react-resize-detector";
import { useComposedRefs, useControlledState, useDynamicRef, useThemeContext } from "@dxos/react-ui";
import { composable, composableProps } from "@dxos/react-ui";
import { mx } from "@dxos/ui-theme";

// src/hooks/context.tsx
import { createContext, useContext } from "react";
import { raise } from "@dxos/debug";
var GlobeContext = /* @__PURE__ */ createContext(void 0);
var useGlobeContext = () => {
  return useContext(GlobeContext) ?? raise(new Error("Missing GlobeContext"));
};

// src/hooks/useDrag.ts
import { select as select2 } from "d3";
import { useEffect } from "react";

// src/util/animation.ts
import { geoDistance } from "d3";
import versor from "versor";
var flyDuration = (p1, p2, base, scale) => Math.max(base, geoDistance(p1, p2) * scale);
var createRotationTween = (projection, setRotation, r1, r2) => {
  const iv = versor.interpolate(r1, r2);
  return (t) => {
    projection.rotate(iv(t));
    setRotation(projection.rotate());
  };
};

// src/util/debug.ts
var debug = false;
var timer = (cb) => {
  const start = Date.now();
  const data = cb();
  const t = Date.now() - start / 1e3;
  if (debug) {
    console.log({
      t,
      data
    });
  }
  return data;
};

// src/util/inertia.ts
import { drag, select, timer as timer2 } from "d3";
import versor2 from "versor";
var restrictAxis = (axis) => (original, current) => current.map((d, i) => axis[i] ? d : original[i]);
var geoInertiaDrag = (target, render, projection, options) => {
  if (!options) {
    options = {};
  }
  if (target.node) {
    target = target.node();
  }
  target = select(target);
  const linear = (options.mode ?? "linear") === "linear";
  const axis = restrictAxis(options.lockTilt ? [
    true,
    false,
    false
  ] : [
    true,
    true,
    true
  ]);
  const sharedHandlers = {
    projection,
    render: (rotation) => {
      projection.rotate(rotation);
      render && render();
    },
    axis,
    start: options.start,
    move: options.move,
    end: options.end,
    stop: options.stop,
    finish: options.finish,
    time: options.time,
    hold: options.hold
  };
  const inertia = linear ? geoInertiaDragLinearHelper({
    ...sharedHandlers,
    sensitivity: options.sensitivity,
    getZoom: options.getZoom
  }) : geoInertiaDragHelper(sharedHandlers);
  target.call(drag().on("start", inertia.start).on("drag", inertia.move).on("end", inertia.end));
  return inertia;
};
var geoInertiaDragHelper = (opt) => {
  const projection = opt.projection;
  let v0;
  let r0;
  let q0;
  let v10;
  let v11;
  let q10;
  const inertia = inertiaHelper({
    axis: opt.axis,
    start: () => {
      v0 = versor2.cartesian(projection.invert(inertia.position));
      r0 = projection.rotate();
      q0 = versor2(r0);
      opt.start && opt.start();
    },
    move: () => {
      const inv = projection.rotate(r0).invert(inertia.position);
      if (isNaN(inv[0])) {
        return;
      }
      const v1 = versor2.cartesian(inv);
      const q1 = versor2.multiply(q0, versor2.delta(v0, v1));
      const r1 = versor2.rotation(q1);
      const r2 = opt.axis(r0, r1);
      opt.render(r2);
      opt.move && opt.move();
    },
    end: () => {
      v10 = versor2.cartesian(projection.invert(inertia.position.map((d, i) => d - inertia.velocity[i] / 1e3)));
      q10 = versor2(projection.rotate());
      v11 = versor2.cartesian(projection.invert(inertia.position));
      opt.end && opt.end();
    },
    stop: opt.stop,
    finish: opt.finish,
    render: (t) => {
      const r1 = versor2.rotation(versor2.multiply(q10, versor2.delta(v10, v11, t * 1e3)));
      const r2 = opt.axis(r0, r1);
      opt.render && opt.render(r2);
    },
    time: opt.time
  });
  return inertia;
};
var DEFAULT_LINEAR_SENSITIVITY = 0.25;
var geoInertiaDragLinearHelper = (opt) => {
  const projection = opt.projection;
  const sensitivity = opt.sensitivity ?? DEFAULT_LINEAR_SENSITIVITY;
  const gain = () => sensitivity / Math.max(opt.getZoom?.() ?? 1, 0.1);
  let r0;
  let p0;
  let kStart;
  let rEnd;
  let vEnd;
  const inertia = inertiaHelper({
    axis: opt.axis,
    start: () => {
      r0 = projection.rotate();
      p0 = [
        inertia.position[0],
        inertia.position[1]
      ];
      kStart = gain();
      opt.start && opt.start();
    },
    move: () => {
      const dx = inertia.position[0] - p0[0];
      const dy = inertia.position[1] - p0[1];
      const r1 = [
        r0[0] + dx * kStart,
        r0[1] - dy * kStart,
        0
      ];
      const r2 = opt.axis(r0, r1);
      opt.render(r2);
      opt.move && opt.move();
    },
    end: () => {
      rEnd = projection.rotate();
      vEnd = [
        inertia.velocity[0],
        inertia.velocity[1]
      ];
      opt.end && opt.end();
    },
    stop: opt.stop,
    finish: opt.finish,
    render: (t) => {
      const r1 = [
        rEnd[0] + vEnd[0] * kStart * t,
        rEnd[1] - vEnd[1] * kStart * t,
        0
      ];
      const r2 = opt.axis(rEnd, r1);
      opt.render && opt.render(r2);
    },
    time: opt.time
  });
  return inertia;
};
function inertiaHelper(opt) {
  const A = opt.time || 5e3;
  const limit = 1.0001;
  const B = -Math.log(1 - 1 / limit);
  const inertia = {
    position: [
      0,
      0
    ],
    velocity: [
      0,
      0
    ],
    timer: timer2(() => {
    }),
    time: 0,
    t: 0,
    start: function(ev) {
      const position = [
        ev.x,
        ev.y
      ];
      inertia.position = position;
      inertia.velocity = [
        0,
        0
      ];
      inertia.timer.stop();
      this.classList.remove("inertia");
      this.classList.add("dragging");
      opt.start && opt.start.call(this, position);
    },
    move: function(ev) {
      const position = [
        ev.x,
        ev.y
      ];
      const time = performance.now();
      const deltaTime = time - inertia.time;
      const decay = 1 - Math.exp(-deltaTime / 1e3);
      inertia.velocity = inertia.velocity.map((d, i) => {
        const deltaPos = position[i] - inertia.position[i];
        const deltaTime2 = time - inertia.time;
        return 1e3 * (1 - decay) * deltaPos / deltaTime2 + d * decay;
      });
      inertia.velocity = opt.axis([
        0,
        0
      ], inertia.velocity);
      inertia.time = time;
      inertia.position = position;
      opt.move && opt.move.call(this, position);
    },
    end: function(ev) {
      this.classList.remove("dragging", "inertia");
      const v = inertia.velocity;
      if (v[0] * v[0] + v[1] * v[1] < 100) {
        inertia.timer.stop();
        return opt.stop && opt.stop();
      }
      const time = performance.now();
      const deltaTime = time - inertia.time;
      if (opt.hold === void 0) {
        opt.hold = 100;
      }
      if (deltaTime >= opt.hold) {
        inertia.timer.stop();
        return opt.stop && opt.stop();
      }
      this.classList.add("inertia");
      opt.end && opt.end();
      const self = this;
      inertia.timer.restart((e) => {
        inertia.t = limit * (1 - Math.exp(-B * e / A));
        opt.render && opt.render(inertia.t);
        if (inertia.t > 1) {
          inertia.timer.stop();
          self.classList.remove("inertia");
          inertia.velocity = [
            0,
            0
          ];
          inertia.t = 1;
          opt.finish && opt.finish();
        }
      });
    }
  };
  inertia.timer.stop();
  return inertia;
}

// src/util/path.ts
import { geoCircle as d3GeoCircle } from "d3";
var positionToRotation = ([lng, lat], tilt = 0) => [
  -lng,
  tilt - lat,
  0
];
var geoToPosition = ({ lat, lng }) => [
  lng,
  lat
];
var geoPoint = (point2) => ({
  type: "Point",
  coordinates: geoToPosition(point2)
});
var geoCircle = ({ lat, lng }, radius) => d3GeoCircle().radius(radius).center([
  lng,
  lat
])();
var geoLine = (p1, p2) => ({
  type: "LineString",
  coordinates: [
    [
      p1.lng,
      p1.lat
    ],
    [
      p2.lng,
      p2.lat
    ]
  ]
});
var closestPoint = (points, target) => {
  if (points.length === 0) {
    return target;
  }
  let closestPoint2 = points[0];
  let minDistance = getDistance(points[0], target);
  for (const point2 of points) {
    const distance = getDistance(point2, target);
    if (distance < minDistance) {
      minDistance = distance;
      closestPoint2 = point2;
    }
  }
  return closestPoint2;
};
var getDistance = (point1, point2) => {
  const dx = point1[0] - point2[0];
  const dy = point1[1] - point2[1];
  return Math.sqrt(dx * dx + dy * dy);
};

// src/util/render.ts
import { geoBounds, geoCentroid, geoDistance as geoDistance2, geoGraticule } from "d3";
import { feature, mesh } from "topojson-client";
var RAD_TO_DEG = 180 / Math.PI;
var computeBounds = (geometry) => {
  const feat = {
    type: "Feature",
    geometry,
    properties: {}
  };
  const centroid = geoCentroid(feat);
  const [[w, s], [e, n]] = geoBounds(feat);
  const corners = [
    [
      w,
      s
    ],
    [
      w,
      n
    ],
    [
      e,
      s
    ],
    [
      e,
      n
    ]
  ];
  let radius = 0;
  for (const corner of corners) {
    const d = geoDistance2(centroid, corner) * RAD_TO_DEG;
    if (d > radius) {
      radius = d;
    }
  }
  return {
    geometry,
    centroid,
    radius
  };
};
var createLayers = (topology, features, styles) => {
  const layers = [];
  if (styles.water) {
    layers.push({
      styles: styles.water,
      path: {
        type: "Sphere"
      }
    });
  }
  if (styles.graticule) {
    layers.push({
      styles: styles.graticule,
      path: geoGraticule().step([
        6,
        6
      ])()
    });
  }
  if (topology) {
    if (styles.land) {
      if (topology.objects.countries) {
        const fc = feature(topology, topology.objects.countries);
        const memberGeoms = fc.features.map((f) => f.geometry);
        const bounds = memberGeoms.map(computeBounds);
        layers.push({
          styles: styles.land,
          path: {
            type: "GeometryCollection",
            geometries: memberGeoms
          },
          cullable: bounds
        });
      } else if (topology.objects.land) {
        layers.push({
          styles: styles.land,
          path: feature(topology, topology.objects.land)
        });
      }
    }
    if (topology.objects.countries && styles.border) {
      layers.push({
        styles: styles.border,
        path: mesh(topology, topology.objects.countries, (a, b) => a !== b)
      });
    }
    if (topology.objects.dots && styles.dots) {
      layers.push({
        styles: styles.dots,
        path: topology.objects.dots
      });
    }
  }
  if (features) {
    const { points, lines } = features;
    if (lines && styles.line) {
      layers.push({
        styles: styles.line,
        path: {
          type: "GeometryCollection",
          geometries: lines.map(({ source, target }) => geoLine(source, target))
        }
      });
    }
    if (points && styles.point) {
      layers.push({
        styles: styles.point,
        path: {
          type: "GeometryCollection",
          geometries: points.map((point2) => geoPoint(point2))
        }
      });
    }
  }
  return layers;
};
var renderLayers = (generator, layers = [], scale, styles, viewCenter) => {
  const context = generator.context();
  const { canvas: { width, height } } = context;
  context.reset();
  if (styles.background) {
    context.fillStyle = styles.background.fillStyle;
    context.fillRect(0, 0, width, height);
  } else {
    context.clearRect(0, 0, width, height);
  }
  layers.forEach((layer) => {
    const { path, styles: styles2, cullable } = layer;
    context.save();
    let fill = false;
    let stroke = false;
    if (styles2) {
      Object.entries(styles2).forEach(([key, value]) => {
        if (key === "pointRadius") {
          generator.pointRadius(value * scale);
        } else {
          context[key] = value;
          fill ||= key === "fillStyle";
          stroke ||= key === "strokeStyle";
        }
      });
    }
    let renderPath = path;
    if (cullable && viewCenter) {
      const geometries = [];
      for (let index = 0; index < cullable.length; index++) {
        const bounds = cullable[index];
        const angularDistance = geoDistance2(viewCenter, bounds.centroid) * RAD_TO_DEG;
        if (angularDistance < 90 + bounds.radius) {
          geometries.push(bounds.geometry);
        }
      }
      renderPath = {
        type: "GeometryCollection",
        geometries
      };
    }
    context.beginPath();
    generator(renderPath);
    fill && context.fill();
    stroke && context.stroke();
    context.restore();
  });
  return context;
};

// src/util/styles.ts
var POINT_COLOR = "rgb(220, 38, 38)";
var LINE_COLOR = "rgba(220, 38, 38, 0.5)";
var globeStyles = (themeMode) => themeMode === "dark" ? {
  water: {
    fillStyle: "#191919"
  },
  land: {
    fillStyle: "#444",
    strokeStyle: "#222"
  },
  border: {
    strokeStyle: "#111"
  },
  graticule: {
    strokeStyle: "#111"
  },
  line: {
    lineWidth: 1.5,
    lineDash: [
      4,
      16
    ],
    strokeStyle: LINE_COLOR
  },
  point: {
    radius: 0.2,
    fillStyle: POINT_COLOR
  }
} : {
  water: {
    fillStyle: "#C0DAE4"
  },
  land: {
    fillStyle: "#C2D8B4",
    strokeStyle: "#A6C291"
  },
  line: {
    lineWidth: 1.5,
    lineDash: [
      4,
      16
    ],
    strokeStyle: LINE_COLOR
  },
  point: {
    radius: 0.2,
    fillStyle: POINT_COLOR
  }
};

// src/hooks/useDrag.ts
var useDrag = (controller, options = {}) => {
  useEffect(() => {
    const canvas = controller?.canvas;
    if (!canvas || options.disabled) {
      return;
    }
    const inertia = geoInertiaDrag(select2(canvas), () => {
      controller.setRotation(controller.projection.rotate());
      options.onUpdate?.({
        type: "move",
        controller
      });
    }, controller.projection, {
      lockTilt: options.lockTilt,
      mode: options.mode,
      sensitivity: options.sensitivity,
      // Zoom-driven gain: matches useWheel — degrees-per-pixel shrinks as the
      // globe gets larger on screen so the drag feel is consistent across zoom.
      getZoom: () => controller.zoom,
      time: 3e3,
      start: () => options.onUpdate?.({
        type: "start",
        controller
      }),
      finish: () => options.onUpdate?.({
        type: "end",
        controller
      })
    });
    return () => {
      cancelDrag(select2(canvas));
      inertia?.timer?.stop();
    };
  }, [
    controller,
    JSON.stringify(options)
  ]);
};
var cancelDrag = (node) => node.on(".drag", null);

// src/hooks/useGlobeZoomHandler.ts
import { useCallback } from "react";
var ZOOM_FACTOR = 0.1;
var useGlobeZoomHandler = (controller) => {
  return useCallback((event) => {
    if (!controller) {
      return;
    }
    switch (event) {
      case "zoom-in": {
        controller.setZoom((zoom) => {
          return zoom * (1 + ZOOM_FACTOR);
        });
        break;
      }
      case "zoom-out": {
        controller.setZoom((zoom) => {
          return zoom * (1 - ZOOM_FACTOR);
        });
        break;
      }
    }
  }, [
    controller
  ]);
};

// src/hooks/useMapZoomHandler.ts
import { useCallback as useCallback2 } from "react";
var useMapZoomHandler = (controller) => {
  return useCallback2((event) => {
    if (!controller) {
      return;
    }
    switch (event) {
      case "zoom-in": {
        controller.setZoom((scale) => scale + 1);
        break;
      }
      case "zoom-out": {
        controller.setZoom((scale) => scale - 1);
        break;
      }
    }
  }, [
    controller
  ]);
};

// src/hooks/useSimplifiedTopology.ts
import { useMemo } from "react";
import { presimplify, quantile, simplify } from "topojson-simplify";
var DEFAULT_TIERS = [
  {
    minZoom: 0,
    percentile: 0.95
  },
  {
    minZoom: 2,
    percentile: 0.85
  },
  {
    minZoom: 4,
    percentile: 0.6
  },
  {
    minZoom: 7,
    percentile: 0.3
  },
  {
    minZoom: 12,
    percentile: 0
  }
];
var pickTier = (zoom, tiers) => {
  let match = tiers[0];
  for (const tier of tiers) {
    if (zoom >= tier.minZoom) {
      match = tier;
    }
  }
  return match;
};
var useSimplifiedTopology = (topology, zoom, options = {}) => {
  const { tiers = DEFAULT_TIERS } = options;
  const presimplified = useMemo(() => topology ? presimplify(topology) : void 0, [
    topology
  ]);
  const tier = pickTier(zoom, tiers);
  return useMemo(() => {
    if (!presimplified) {
      return void 0;
    }
    if (tier.percentile <= 0) {
      return presimplified;
    }
    const minWeight = quantile(presimplified, tier.percentile);
    return simplify(presimplified, minWeight);
  }, [
    presimplified,
    tier
  ]);
};

// src/hooks/useSpinner.ts
import { timer as d3Timer } from "d3";
import { useEffect as useEffect2, useState } from "react";
var useSpinner = (controller, options = {}) => {
  const [running, setRunning] = useState(false);
  useEffect2(() => {
    let timer3;
    const start = () => {
      const delta = options.delta ?? [
        1e-3,
        0,
        0
      ];
      let t = 0;
      let lastRotation = controller.projection.rotate();
      timer3 = d3Timer((elapsed) => {
        const dt = elapsed - t;
        t = elapsed;
        const rotation = [
          lastRotation[0] + delta[0] * dt,
          lastRotation[1] + delta[1] * dt,
          lastRotation[2] + delta[2] * dt
        ];
        lastRotation = rotation;
        controller.setRotation(rotation);
      });
    };
    const stop = () => {
      if (timer3) {
        timer3.stop();
        timer3 = void 0;
      }
    };
    if (controller && running) {
      start();
    } else {
      stop();
    }
    return () => stop();
  }, [
    controller,
    running
  ]);
  return [
    () => {
      if (!options.disabled) {
        setRunning(true);
      }
    },
    () => setRunning(false)
  ];
};

// src/hooks/useTopology.ts
import { useEffect as useEffect3, useState as useState2 } from "react";
import { log } from "@dxos/log";
var __dxlog_file = "/__w/dxos/dxos/packages/ui/react-ui-geo/src/hooks/useTopology.ts";
var DEFAULT_TIERS2 = [
  {
    minZoom: 0,
    level: "110m"
  }
];
var pickTier2 = (zoom, tiers) => {
  let match = tiers[0];
  for (const tier of tiers) {
    if (zoom >= tier.minZoom) {
      match = tier;
    }
  }
  return match;
};
var topologyCache = /* @__PURE__ */ new Map();
var useTopology = (zoom, options = {}) => {
  const { tiers = DEFAULT_TIERS2 } = options;
  const level = zoom === void 0 ? "110m" : pickTier2(zoom, tiers).level;
  const [topology, setTopology] = useState2(() => topologyCache.get(level));
  useEffect3(() => {
    const cached = topologyCache.get(level);
    if (cached) {
      setTopology(cached);
      return;
    }
    let disposed = false;
    void loadTopology(level).then((loaded) => {
      topologyCache.set(level, loaded);
      if (!disposed) {
        setTopology(loaded);
      }
    }).catch((err) => {
      if (!disposed) {
        log.warn("failed to load topology", {
          level,
          err
        }, { "~LogMeta": "~LogMeta", F: __dxlog_file, L: 52, S: void 0 });
      }
    });
    return () => {
      disposed = true;
    };
  }, [
    level
  ]);
  return topology;
};

// src/hooks/useTour.ts
import { geoInterpolate, geoPath } from "d3";
import { useEffect as useEffect4, useState as useState3 } from "react";
var defaultDuration = 1500;
var useTour = (controller, points, options = {}) => {
  const [running, setRunning] = useState3(options.running ?? false);
  useEffect4(() => {
    if (!controller || !running) {
      return;
    }
    let cancelled = false;
    const t = setTimeout(async () => {
      const { canvas, projection } = controller;
      const context = canvas.getContext("2d", {
        alpha: false
      });
      const path = geoPath(projection, context).pointRadius(2);
      try {
        const tourPoints = [
          ...points
        ];
        if (options.loop) {
          tourPoints.push(tourPoints[0]);
        }
        let last;
        for (const next of tourPoints) {
          if (cancelled) {
            break;
          }
          const p1 = last ? geoToPosition(last) : void 0;
          const p2 = geoToPosition(next);
          const ip = geoInterpolate(p1 ?? p2, p2);
          const onTick = (t2) => {
            const t1 = Math.max(0, Math.min(1, t2 * 2 - 1));
            const t22 = Math.min(1, t2 * 2);
            context.save();
            try {
              context.beginPath();
              context.strokeStyle = options.styles?.arc?.strokeStyle ?? "yellow";
              context.lineWidth = (options.styles?.arc?.lineWidth ?? 1.5) * (controller.zoom ?? 1);
              context.setLineDash(options.styles?.arc?.lineDash ?? []);
              path({
                type: "LineString",
                coordinates: [
                  ip(t1),
                  ip(t22)
                ]
              });
              context.stroke();
              context.beginPath();
              context.fillStyle = options.styles?.cursor?.fillStyle ?? "orange";
              path.pointRadius((options.styles?.cursor?.pointRadius ?? 2) * (controller.zoom ?? 1));
              path({
                type: "Point",
                coordinates: ip(t22)
              });
              context.fill();
            } finally {
              context.restore();
            }
          };
          await controller.flyTo(next, {
            duration: options.duration ?? defaultDuration,
            tilt: options.tilt ?? 0,
            onTick
          });
          last = next;
        }
      } catch {
      } finally {
        if (!cancelled) {
          setRunning(false);
        }
      }
    });
    return () => {
      cancelled = true;
      clearTimeout(t);
      controller.cancelFlyTo();
    };
  }, [
    controller,
    running,
    JSON.stringify(options)
  ]);
  return [
    running,
    setRunning
  ];
};

// src/hooks/useWheel.ts
import { useEffect as useEffect5 } from "react";
var DEFAULT_SENSITIVITY = 0.25;
var DEFAULT_ZOOM_SENSITIVITY = 0.01;
var useWheel = (controller, options = {}) => {
  useEffect5(() => {
    const canvas = controller?.canvas;
    if (!canvas || options.disabled) {
      return;
    }
    const sensitivity = options.sensitivity ?? DEFAULT_SENSITIVITY;
    const zoomSensitivity = options.zoomSensitivity ?? DEFAULT_ZOOM_SENSITIVITY;
    const handleWheel = (event) => {
      event.preventDefault();
      if (event.ctrlKey) {
        const factor = Math.exp(-event.deltaY * zoomSensitivity);
        controller.setZoom(controller.zoom * factor);
      } else {
        const [lambda, phi, gamma] = controller.projection.rotate();
        const k = sensitivity / Math.max(controller.zoom, 0.1);
        const next = [
          lambda - event.deltaX * k,
          phi + event.deltaY * k,
          gamma
        ];
        controller.projection.rotate(next);
        controller.setRotation(controller.projection.rotate());
      }
      options.onUpdate?.(controller);
    };
    canvas.addEventListener("wheel", handleWheel, {
      passive: false
    });
    return () => {
      canvas.removeEventListener("wheel", handleWheel);
    };
  }, [
    controller,
    options.disabled,
    options.sensitivity,
    options.zoomSensitivity,
    options.onUpdate
  ]);
};

// src/components/Toolbar/Controls.tsx
import React from "react";
import { IconButton, Toolbar, useTranslation } from "@dxos/react-ui";
import { translationKey } from "#translations";
var controlPositions = {
  topleft: "top-2 left-2",
  topright: "top-2 right-2",
  bottomleft: "bottom-2 left-2",
  bottomright: "bottom-2 right-2"
};
var ZoomControls = ({ classNames, onAction }) => {
  const { t } = useTranslation(translationKey);
  return /* @__PURE__ */ React.createElement(Toolbar.Root, {
    classNames: [
      "gap-2",
      classNames
    ]
  }, /* @__PURE__ */ React.createElement(IconButton, {
    icon: "ph--plus--regular",
    iconOnly: true,
    label: t("zoom-in-icon.button"),
    onClick: () => onAction?.("zoom-in")
  }), /* @__PURE__ */ React.createElement(IconButton, {
    icon: "ph--minus--regular",
    iconOnly: true,
    label: t("zoom-out-icon.button"),
    onClick: () => onAction?.("zoom-out")
  }));
};
var ActionControls = ({ classNames, onAction }) => {
  const { t } = useTranslation(translationKey);
  return /* @__PURE__ */ React.createElement(Toolbar.Root, {
    classNames: [
      "gap-2",
      classNames
    ]
  }, /* @__PURE__ */ React.createElement(IconButton, {
    icon: "ph--path--regular",
    iconOnly: true,
    label: t("start-icon.button"),
    onClick: () => onAction?.("start")
  }), /* @__PURE__ */ React.createElement(IconButton, {
    icon: "ph--globe-hemisphere-west--regular",
    iconOnly: true,
    label: t("toggle-icon.button"),
    onClick: () => onAction?.("toggle")
  }));
};

// src/components/Globe/Globe.tsx
var defaultStyles = {
  light: {
    background: {
      fillStyle: "#EEE"
    },
    water: {
      fillStyle: "#555"
    },
    land: {
      fillStyle: "#999"
    },
    line: {
      strokeStyle: "darkred"
    },
    point: {
      fillStyle: "#111111",
      strokeStyle: "#111111",
      strokeWidth: 1,
      pointRadius: 0.5
    }
  },
  dark: {
    background: {
      fillStyle: "#111111"
    },
    water: {
      fillStyle: "#123E6A"
    },
    land: {
      fillStyle: "#032153"
    },
    line: {
      strokeStyle: "#111111"
    },
    point: {
      fillStyle: "#111111",
      strokeStyle: "#111111",
      strokeWidth: 1,
      pointRadius: 0.5
    }
  }
};
var projectionMap = {
  orthographic: geoOrthographic,
  mercator: geoMercator,
  "transverse-mercator": geoTransverseMercator
};
var getProjection = (type = "orthographic") => {
  if (typeof type === "string") {
    const constructor = projectionMap[type] ?? geoOrthographic;
    return constructor();
  }
  return type ?? geoOrthographic();
};
var DEFAULT_ZOOM = 1.5;
var GlobeRoot = /* @__PURE__ */ forwardRef(({ children, center: centerProp, zoom: zoomProp = DEFAULT_ZOOM, translation: translationProp, rotation: rotationProp }, forwardedRef) => {
  const [size, setSize] = useState4({
    width: 0,
    height: 0
  });
  const [center, setCenter] = useControlledState(centerProp);
  const [zoom, setZoom] = useControlledState(zoomProp);
  const [translation, setTranslation] = useControlledState(translationProp);
  const [rotation, setRotation] = useControlledState(rotationProp);
  const [controller, setController] = useState4(null);
  const registerController = useCallback3((next) => setController(next), []);
  useImperativeHandle(forwardedRef, () => controller, [
    controller
  ]);
  return /* @__PURE__ */ React2.createElement(GlobeContext.Provider, {
    value: {
      size,
      center,
      zoom,
      translation,
      rotation,
      setSize,
      setCenter,
      setZoom,
      setTranslation,
      setRotation,
      registerController
    }
  }, children);
});
GlobeRoot.displayName = "Globe.Root";
var GlobeViewport = composable(({ children, ...props }, forwardedRef) => {
  const { setSize } = useGlobeContext();
  const localRef = useRef(null);
  const composedRef = useComposedRefs(localRef, forwardedRef);
  const { width, height } = useResizeDetector({
    targetRef: localRef
  });
  useEffect6(() => {
    setSize({
      width: width ?? 0,
      height: height ?? 0
    });
  }, [
    width,
    height,
    setSize
  ]);
  return /* @__PURE__ */ React2.createElement("div", {
    ...composableProps(props, {
      classNames: "relative dx-container"
    }),
    ref: composedRef
  }, children);
});
GlobeViewport.displayName = "Globe.Viewport";
var GlobeCanvas = ({ projection: projectionProp, topology, features, styles: stylesProp }) => {
  const { themeMode } = useThemeContext();
  const styles = useMemo2(() => stylesProp ?? defaultStyles[themeMode], [
    stylesProp,
    themeMode
  ]);
  const { size, center, zoom, translation, rotation, setZoom, setTranslation, setRotation, registerController } = useGlobeContext();
  const zoomRef = useDynamicRef(zoom);
  const [canvas, setCanvas] = useState4(null);
  const canvasRef = (canvas2) => setCanvas(canvas2);
  const projection = useMemo2(() => getProjection(projectionProp), [
    projectionProp
  ]);
  const layers = useMemo2(() => {
    return timer(() => createLayers(topology, features, styles));
  }, [
    topology,
    features,
    styles
  ]);
  useEffect6(() => {
    if (center) {
      setRotation(positionToRotation(geoToPosition(center)));
    }
  }, [
    center
  ]);
  const flyToSelection = useMemo2(() => d3Selection(), []);
  const flyToTransitionName = `globe-fly-to-${useId()}`;
  useEffect6(() => () => {
    flyToSelection.interrupt(flyToTransitionName);
  }, [
    flyToSelection,
    flyToTransitionName
  ]);
  const zooming = useRef(false);
  const controller = useMemo2(() => {
    return {
      canvas,
      projection,
      get zoom() {
        return zoomRef.current;
      },
      translation,
      rotation,
      setZoom: (state) => {
        if (typeof state === "function") {
          const is = interpolateNumber(zoomRef.current, state(zoomRef.current));
          transition().ease(zooming.current ? easeLinear : easeSinOut).duration(200).tween("scale", () => (t) => setZoom(is(t))).on("end", () => {
            zooming.current = false;
          });
        } else {
          setZoom(state);
        }
      },
      setTranslation,
      setRotation,
      flyTo: (target, options = {}) => {
        const { duration = 1200, tilt = 0, onTick } = options;
        const p2 = geoToPosition(target);
        const r1 = projection.rotate();
        const r2 = positionToRotation(p2, tilt);
        const p1 = [
          -r1[0],
          -r1[1]
        ];
        const rotationTween = createRotationTween(projection, setRotation, r1, r2);
        const iz = target.zoom !== void 0 ? interpolateNumber(zoomRef.current, target.zoom) : void 0;
        flyToSelection.interrupt(flyToTransitionName);
        const tx = flyToSelection.transition(flyToTransitionName).duration(flyDuration(p1, p2, duration, 1500));
        if (onTick) {
          tx.tween("flyToOnTick", () => onTick);
        }
        tx.tween("flyToRotation", () => rotationTween);
        if (iz) {
          tx.tween("flyToZoom", () => (t) => setZoom(iz(t)));
        }
        return tx.end();
      },
      cancelFlyTo: () => {
        flyToSelection.interrupt(flyToTransitionName);
      }
    };
  }, [
    canvas,
    projection,
    flyToSelection,
    flyToTransitionName
  ]);
  useEffect6(() => {
    registerController(controller);
    return () => registerController(null);
  }, [
    registerController,
    controller
  ]);
  const generator = useMemo2(() => canvas && projection && geoPath2(projection, canvas.getContext("2d")), [
    canvas,
    projection
  ]);
  useEffect6(() => {
    if (canvas && projection) {
      timer(() => {
        projection.scale(Math.min(size.width, size.height) / 2 * zoom).translate([
          size.width / 2 + (translation?.x ?? 0),
          size.height / 2 + (translation?.y ?? 0)
        ]).rotate(rotation ?? [
          0,
          0,
          0
        ]);
        const isOrthographic = !projectionProp || projectionProp === "orthographic";
        const [lambda, phi] = rotation ?? [
          0,
          0,
          0
        ];
        const viewCenter = isOrthographic ? [
          -lambda,
          -phi
        ] : void 0;
        renderLayers(generator, layers, zoom, styles, viewCenter);
      });
    }
  }, [
    generator,
    size,
    zoom,
    translation,
    rotation,
    layers,
    projectionProp
  ]);
  if (!size.width || !size.height) {
    return null;
  }
  return /* @__PURE__ */ React2.createElement("canvas", {
    ref: canvasRef,
    className: "bg-base-surface",
    width: size.width,
    height: size.height
  });
};
GlobeCanvas.displayName = "Globe.Canvas";
var GlobeDebug = ({ position = "topleft" }) => {
  const { size, zoom, translation, rotation } = useGlobeContext();
  return /* @__PURE__ */ React2.createElement("div", {
    className: mx("z-10 absolute w-96 p-2 overflow-hidden border border-green-700 rounded-sm", controlPositions[position])
  }, /* @__PURE__ */ React2.createElement("pre", {
    className: "font-mono text-xs text-green-700"
  }, JSON.stringify({
    size,
    zoom,
    translation,
    rotation
  }, null, 2)));
};
var GlobePanel = ({ position, classNames, children }) => {
  return /* @__PURE__ */ React2.createElement("div", {
    className: mx("z-10 absolute overflow-hidden", controlPositions[position], classNames)
  }, children);
};
var CustomControl = ({ position, children }) => {
  return /* @__PURE__ */ React2.createElement("div", {
    className: mx("z-10 absolute overflow-hidden", controlPositions[position])
  }, children);
};
var GlobeZoom = ({ onAction, position = "bottomleft", ...props }) => /* @__PURE__ */ React2.createElement(CustomControl, {
  position,
  ...props
}, /* @__PURE__ */ React2.createElement(ZoomControls, {
  onAction
}));
var GlobeAction = ({ onAction, position = "bottomright", ...props }) => /* @__PURE__ */ React2.createElement(CustomControl, {
  position,
  ...props
}, /* @__PURE__ */ React2.createElement(ActionControls, {
  onAction
}));
var Globe = {
  Root: GlobeRoot,
  Viewport: GlobeViewport,
  Canvas: GlobeCanvas,
  Zoom: GlobeZoom,
  Action: GlobeAction,
  Debug: GlobeDebug,
  Panel: GlobePanel
};

// src/components/Map/Map.tsx
import "leaflet/dist/leaflet.css";
import { createContext as createContext2 } from "@radix-ui/react-context";
import L, { Control, DomEvent, DomUtil, point, latLngBounds } from "leaflet";
import React3, { forwardRef as forwardRef2, useCallback as useCallback4, useEffect as useEffect7, useImperativeHandle as useImperativeHandle2, useRef as useRef2, useState as useState5 } from "react";
import { createRoot } from "react-dom/client";
import { MapContainer, Marker, Polyline, Popup, TileLayer, useMap, useMapEvents } from "react-leaflet";
import { ThemeProvider, Tooltip } from "@dxos/react-ui";
import { composable as composable2, composableProps as composableProps2, defaultTx } from "@dxos/react-ui";
import { mx as mx2 } from "@dxos/ui-theme";
var defaults = {
  center: {
    lat: 51,
    lng: 0
  },
  zoom: 4
};
var [MapContextProvider, useMapContext] = createContext2("Map");
var MapRoot = /* @__PURE__ */ forwardRef2(({ children, onChange }, forwardedRef) => {
  const mapRef = useRef2(null);
  const registerMap = useCallback4((map) => {
    mapRef.current = map;
  }, []);
  useImperativeHandle2(forwardedRef, () => ({
    getCenter: () => {
      const center = mapRef.current?.getCenter();
      return center ? {
        lat: center.lat,
        lng: center.lng
      } : void 0;
    },
    getZoom: () => mapRef.current?.getZoom(),
    setCenter: (center, zoom) => {
      mapRef.current?.setView(center, zoom);
    },
    setZoom: (cb) => {
      mapRef.current?.setZoom(cb(mapRef.current?.getZoom() ?? 0));
    }
  }), []);
  const attention = false;
  return /* @__PURE__ */ React3.createElement(MapContextProvider, {
    attention,
    onChange,
    registerMap
  }, children);
});
MapRoot.displayName = "Map.Root";
var MAP_VIEWPORT_NAME = "Map.Viewport";
var MapResize = () => {
  const map = useMap();
  useEffect7(() => {
    const container = map.getContainer();
    let frame = 0;
    const observer = new ResizeObserver(() => {
      cancelAnimationFrame(frame);
      frame = requestAnimationFrame(() => map.invalidateSize());
    });
    observer.observe(container);
    return () => {
      cancelAnimationFrame(frame);
      observer.disconnect();
    };
  }, [
    map
  ]);
  return null;
};
var PINCH_ZOOM_SENSITIVITY = 0.03;
var MapPinchZoom = () => {
  const map = useMap();
  useEffect7(() => {
    const container = map.getContainer();
    let frame = 0;
    let point2;
    let target;
    const onWheel = (event) => {
      if (!event.ctrlKey) {
        return;
      }
      event.preventDefault();
      const rect = container.getBoundingClientRect();
      point2 = L.point(event.clientX - rect.left, event.clientY - rect.top);
      target = (target ?? map.getZoom()) - event.deltaY * PINCH_ZOOM_SENSITIVITY;
      if (!frame) {
        frame = requestAnimationFrame(() => {
          frame = 0;
          if (target !== void 0 && point2) {
            map.setZoomAround(point2, target, {
              animate: false
            });
            target = void 0;
          }
        });
      }
    };
    container.addEventListener("wheel", onWheel, {
      passive: false
    });
    return () => {
      container.removeEventListener("wheel", onWheel);
      cancelAnimationFrame(frame);
    };
  }, [
    map
  ]);
  return null;
};
var MapViewport = composable2((props, _forwardedRef) => {
  const { scrollWheelZoom = true, doubleClickZoom = true, touchZoom = true, center, zoom, whenReady, children, ...rest } = props;
  const { attention, registerMap } = useMapContext(MAP_VIEWPORT_NAME);
  const [map, setMap] = useState5(null);
  const setMapRef = useCallback4((next) => {
    setMap(next);
    registerMap(next);
  }, [
    registerMap
  ]);
  useEffect7(() => {
    if (!map) {
      return;
    }
    if (attention) {
      map.scrollWheelZoom.enable();
    } else {
      map.scrollWheelZoom.disable();
    }
  }, [
    map,
    attention
  ]);
  return /* @__PURE__ */ React3.createElement(MapContainer, {
    ...composableProps2(rest, {
      // Frame classes (formerly on Map.Root): focusable grid container.
      classNames: "dx-container group relative grid dx-focus-ring-inset bg-base-surface!"
    }),
    attributionControl: false,
    zoomControl: false,
    scrollWheelZoom,
    doubleClickZoom,
    touchZoom,
    // Allow fractional zoom so trackpad pinch (small ctrl+wheel deltas) isn't rounded away.
    zoomSnap: 0,
    center: center ?? defaults.center,
    zoom: zoom ?? defaults.zoom,
    whenReady,
    ref: setMapRef
  }, /* @__PURE__ */ React3.createElement(MapResize, null), /* @__PURE__ */ React3.createElement(MapPinchZoom, null), children);
});
MapViewport.displayName = "Map.Viewport";
var MAP_TILES_NAME = "Map.Tiles";
var DEFAULT_TILE_URL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
var MapTiles = ({ url = DEFAULT_TILE_URL }) => {
  const ref = useRef2(null);
  const { onChange } = useMapContext(MAP_TILES_NAME);
  useMapEvents({
    moveend: (ev) => {
      onChange?.({
        center: ev.target.getCenter(),
        zoom: ev.target.getZoom()
      });
    }
  });
  const { attention } = useMapContext(MAP_TILES_NAME);
  useEffect7(() => {
    if (ref.current) {
      ref.current.getContainer().dataset.attention = attention ? "1" : "0";
    }
  }, [
    attention
  ]);
  return /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(TileLayer, {
    ref,
    "data-attention": attention,
    detectRetina: true,
    className: 'dark:grayscale dark:invert data-[attention="0"]:!opacity-80',
    url,
    keepBuffer: 4
  }));
};
MapTiles.displayName = MAP_TILES_NAME;
var MapMarkers = ({ selected, markers, lines, onSelect }) => {
  const map = useMap();
  useEffect7(() => {
    const points = [
      ...markers?.map((marker) => marker.location) ?? [],
      ...lines?.flatMap((line) => [
        line.source,
        line.target
      ]) ?? []
    ];
    if (points.length > 0) {
      const bounds = latLngBounds(points);
      const size = map.getSize();
      const padding = Math.max(48, Math.min(size.x, size.y) / 6);
      map.fitBounds(bounds, {
        padding: point(padding, padding),
        animate: false
      });
    }
  }, [
    markers,
    lines,
    map
  ]);
  return /* @__PURE__ */ React3.createElement(React3.Fragment, null, markers?.map(({ id, title, location: { lat, lng } }) => {
    return /* @__PURE__ */ React3.createElement(Marker, {
      key: id,
      position: {
        lat,
        lng
      },
      eventHandlers: onSelect ? {
        click: () => onSelect(id)
      } : void 0,
      icon: (
        // TODO(burdon): Create custom icon from bundled assets.
        // TODO(burdon): Selection state.
        new L.Icon({
          iconUrl: "https://dxos.network/marker-icon.png",
          iconRetinaUrl: "https://dxos.network/marker-icon-2x.png",
          shadowUrl: "https://dxos.network/marker-shadow.png",
          iconSize: [
            25,
            41
          ],
          iconAnchor: [
            12,
            41
          ],
          popupAnchor: [
            1,
            -34
          ],
          shadowSize: [
            41,
            41
          ]
        })
      )
    }, title && /* @__PURE__ */ React3.createElement(Popup, null, title));
  }));
};
MapMarkers.displayName = "Map.Markers";
var MapLines = ({ lines }) => {
  if (!lines || lines.length === 0) {
    return null;
  }
  const polylines = [];
  for (const { source, target, color } of lines) {
    const last = polylines[polylines.length - 1];
    const lastPos = last?.positions[last.positions.length - 1];
    if (last && last.color === color && lastPos?.lat === source.lat && lastPos?.lng === source.lng) {
      last.positions.push(target);
    } else {
      polylines.push({
        positions: [
          source,
          target
        ],
        color
      });
    }
  }
  return /* @__PURE__ */ React3.createElement(React3.Fragment, null, polylines.map(({ positions, color }, index) => /* @__PURE__ */ React3.createElement(Polyline, {
    key: index,
    positions,
    pathOptions: {
      color,
      weight: 4,
      opacity: 0.8
    }
  })));
};
MapLines.displayName = "Map.Lines";
var CustomControl2 = ({ position, children }) => {
  const map = useMap();
  const rootRef = useRef2(void 0);
  useEffect7(() => {
    const control = new Control({
      position
    });
    control.onAdd = () => {
      const container = DomUtil.create("div", mx2("m-0!", controlPositions[position]));
      DomEvent.disableClickPropagation(container);
      DomEvent.disableScrollPropagation(container);
      const root = createRoot(container);
      rootRef.current = root;
      root.render(/* @__PURE__ */ React3.createElement(ThemeProvider, {
        tx: defaultTx
      }, /* @__PURE__ */ React3.createElement(Tooltip.Provider, null, children)));
      return container;
    };
    control.addTo(map);
    return () => {
      control.remove();
      const root = rootRef.current;
      rootRef.current = void 0;
      queueMicrotask(() => root?.unmount());
    };
  }, [
    map,
    position
  ]);
  useEffect7(() => {
    rootRef.current?.render(/* @__PURE__ */ React3.createElement(ThemeProvider, {
      tx: defaultTx
    }, /* @__PURE__ */ React3.createElement(Tooltip.Provider, null, children)));
  }, [
    children
  ]);
  return null;
};
var MapZoom = ({ onAction, position = "bottomleft", ...props }) => /* @__PURE__ */ React3.createElement(CustomControl2, {
  position,
  ...props
}, /* @__PURE__ */ React3.createElement(ZoomControls, {
  onAction
}));
var MapAction = ({ onAction, position = "bottomright", ...props }) => /* @__PURE__ */ React3.createElement(CustomControl2, {
  position,
  ...props
}, /* @__PURE__ */ React3.createElement(ActionControls, {
  onAction
}));
var Map2 = {
  Root: MapRoot,
  Viewport: MapViewport,
  Tiles: MapTiles,
  Markers: MapMarkers,
  Lines: MapLines,
  Zoom: MapZoom,
  Action: MapAction
};
export {
  ActionControls,
  DEFAULT_TILE_URL,
  Globe,
  GlobeContext,
  Map2 as Map,
  ZoomControls,
  closestPoint,
  controlPositions,
  createLayers,
  createRotationTween,
  flyDuration,
  geoCircle,
  geoInertiaDrag,
  geoLine,
  geoPoint,
  geoToPosition,
  getDistance,
  globeStyles,
  positionToRotation,
  renderLayers,
  restrictAxis,
  timer,
  useDrag,
  useGlobeContext,
  useGlobeZoomHandler,
  useMapZoomHandler,
  useSimplifiedTopology,
  useSpinner,
  useTopology,
  useTour,
  useWheel
};
//# sourceMappingURL=index.mjs.map
