@zendir/ui 0.2.2 → 0.2.4

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.
@@ -1,10 +1,10 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
2
- import { useRef, useState, useCallback, useEffect } from "react";
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
+ import { useRef, useState, useEffect, useCallback } from "react";
3
3
  import L from "leaflet";
4
4
  import "leaflet/dist/leaflet.css";
5
5
  /* empty css */
6
6
  import { geodesicCirclePoints, splitRingAtAntimeridian, segmentsWithWorldCopies, splitPolylineAtAntimeridian } from "./groundTrackMapLeafletUtils.js";
7
- import { DEFAULT_TILE, CARTO_ATTRIBUTION, OSM_ATTRIBUTION, FALLBACK_TILE } from "./groundTrackMapLeafletTiles.js";
7
+ import { CARTO_ATTRIBUTION, OSM_ATTRIBUTION, TILE_PRESETS, FALLBACK_TILE } from "./groundTrackMapLeafletTiles.js";
8
8
  import { useTheme } from "../theme/ThemeProvider.js";
9
9
  const STATUS_COLOR_MAP = {
10
10
  normal: "#56f000",
@@ -143,13 +143,14 @@ function GroundTrackMapLeaflet({
143
143
  showLegend = true,
144
144
  showEquator = false,
145
145
  showRecenterButton = true,
146
+ showMapStyleToggle = true,
146
147
  defaultCenter = [20, 0],
147
148
  defaultZoom = 2,
148
149
  height = "100%",
149
150
  width = "100%",
150
151
  minHeight = "400px",
151
152
  emptyMessage = "No orbital data available",
152
- tileUrl = DEFAULT_TILE,
153
+ tileUrl,
153
154
  nightTileUrl,
154
155
  lightSources,
155
156
  className = "",
@@ -159,7 +160,9 @@ function GroundTrackMapLeaflet({
159
160
  pinsEditable = false,
160
161
  onPinAdd,
161
162
  onPinUpdate,
162
- onPinRemove
163
+ onPinRemove,
164
+ customLayers,
165
+ onLayerChange
163
166
  }) {
164
167
  const { tokens } = useTheme();
165
168
  const containerRef = useRef(null);
@@ -169,6 +172,71 @@ function GroundTrackMapLeaflet({
169
172
  const controlsRef = useRef([]);
170
173
  const pinsGroupRef = useRef(null);
171
174
  const [ready, setReady] = useState(false);
175
+ const [layersPanelOpen, setLayersPanelOpen] = useState(false);
176
+ const layersPanelRef = useRef(null);
177
+ useEffect(() => {
178
+ if (!layersPanelOpen) return;
179
+ const handleClickOutside = (e) => {
180
+ if (layersPanelRef.current && !layersPanelRef.current.contains(e.target)) {
181
+ setLayersPanelOpen(false);
182
+ }
183
+ };
184
+ document.addEventListener("mousedown", handleClickOutside);
185
+ return () => document.removeEventListener("mousedown", handleClickOutside);
186
+ }, [layersPanelOpen]);
187
+ const TILE_STORAGE_KEY = "zendir-map-tile-style";
188
+ const isExplicitTileUrl = tileUrl !== void 0;
189
+ const [tileStyle, setTileStyle] = useState(() => {
190
+ if (isExplicitTileUrl) return "dark";
191
+ try {
192
+ const saved = localStorage.getItem(TILE_STORAGE_KEY);
193
+ if (saved === "dark" || saved === "satellite") return saved;
194
+ } catch {
195
+ }
196
+ return "dark";
197
+ });
198
+ const handleTileStyleChange = useCallback((style) => {
199
+ setTileStyle(style);
200
+ try {
201
+ localStorage.setItem(TILE_STORAGE_KEY, style);
202
+ } catch {
203
+ }
204
+ }, []);
205
+ const effectiveTileUrl = isExplicitTileUrl ? tileUrl : TILE_PRESETS[tileStyle];
206
+ const [internalTerminator, setInternalTerminator] = useState(showTerminator);
207
+ const [internalGrid, setInternalGrid] = useState(showGrid);
208
+ useEffect(() => {
209
+ setInternalTerminator(showTerminator);
210
+ }, [showTerminator]);
211
+ useEffect(() => {
212
+ setInternalGrid(showGrid);
213
+ }, [showGrid]);
214
+ const [customLayerState, setCustomLayerState] = useState(() => {
215
+ const state = {};
216
+ for (const l of customLayers ?? []) {
217
+ state[l.id] = l.defaultEnabled ?? true;
218
+ }
219
+ return state;
220
+ });
221
+ useEffect(() => {
222
+ setCustomLayerState((prev) => {
223
+ const next = { ...prev };
224
+ for (const l of customLayers ?? []) {
225
+ if (!(l.id in next)) next[l.id] = l.defaultEnabled ?? true;
226
+ }
227
+ return next;
228
+ });
229
+ }, [customLayers]);
230
+ const handleLayerToggle = useCallback((layerId, enabled) => {
231
+ if (layerId === "terminator") {
232
+ setInternalTerminator(enabled);
233
+ } else if (layerId === "grid") {
234
+ setInternalGrid(enabled);
235
+ } else {
236
+ setCustomLayerState((prev) => ({ ...prev, [layerId]: enabled }));
237
+ }
238
+ onLayerChange == null ? void 0 : onLayerChange(layerId, enabled);
239
+ }, [onLayerChange]);
172
240
  const clearLayers = useCallback(() => {
173
241
  var _a;
174
242
  const map = mapRef.current;
@@ -200,40 +268,7 @@ function GroundTrackMapLeaflet({
200
268
  maxBounds: [[-90, -540], [90, 540]],
201
269
  maxBoundsViscosity: 1
202
270
  });
203
- L.control.zoom({ position: "topright" }).addTo(map);
204
271
  map.attributionControl.setPrefix("");
205
- const isCartoTiles = tileUrl.includes("cartocdn");
206
- if (isCartoTiles) {
207
- map.attributionControl.addAttribution(CARTO_ATTRIBUTION);
208
- }
209
- const tileOptions = {
210
- maxZoom: 19,
211
- subdomains: isCartoTiles ? "abcd" : "abc",
212
- // crossOrigin avoids tainted-canvas errors when Leaflet tries to read tile pixels
213
- crossOrigin: true
214
- };
215
- const tile = L.tileLayer(tileUrl, tileOptions);
216
- tile.addTo(map);
217
- tileLayerRef.current = tile;
218
- let hasSwitchedToFallback = false;
219
- const onTileError = () => {
220
- if (hasSwitchedToFallback) return;
221
- hasSwitchedToFallback = true;
222
- tile.off("tileerror", onTileError);
223
- tile.remove();
224
- if (isCartoTiles) {
225
- map.attributionControl.removeAttribution(CARTO_ATTRIBUTION);
226
- map.attributionControl.addAttribution(OSM_ATTRIBUTION);
227
- }
228
- const fallback = L.tileLayer(FALLBACK_TILE, {
229
- maxZoom: 19,
230
- subdomains: "abc",
231
- crossOrigin: true
232
- });
233
- fallback.addTo(map);
234
- tileLayerRef.current = fallback;
235
- };
236
- tile.on("tileerror", onTileError);
237
272
  const overlayGroup = L.layerGroup();
238
273
  overlayGroup.addTo(map);
239
274
  overlayGroupRef.current = overlayGroup;
@@ -261,12 +296,53 @@ function GroundTrackMapLeaflet({
261
296
  mapRef.current = null;
262
297
  overlayGroupRef.current = null;
263
298
  };
264
- }, [tileUrl, clearLayers]);
299
+ }, [clearLayers]);
300
+ useEffect(() => {
301
+ const map = mapRef.current;
302
+ if (!map || !ready) return;
303
+ if (tileLayerRef.current) {
304
+ tileLayerRef.current.remove();
305
+ tileLayerRef.current = null;
306
+ }
307
+ try {
308
+ map.attributionControl.removeAttribution(CARTO_ATTRIBUTION);
309
+ map.attributionControl.removeAttribution(OSM_ATTRIBUTION);
310
+ } catch {
311
+ }
312
+ const isCartoTiles = effectiveTileUrl.includes("cartocdn");
313
+ if (isCartoTiles) {
314
+ map.attributionControl.addAttribution(CARTO_ATTRIBUTION);
315
+ }
316
+ const tile = L.tileLayer(effectiveTileUrl, {
317
+ maxZoom: 19,
318
+ subdomains: isCartoTiles ? "abcd" : "abc",
319
+ crossOrigin: true
320
+ });
321
+ tile.addTo(map);
322
+ tile.bringToBack();
323
+ tileLayerRef.current = tile;
324
+ let hasSwitchedToFallback = false;
325
+ const onTileError = () => {
326
+ if (hasSwitchedToFallback) return;
327
+ hasSwitchedToFallback = true;
328
+ tile.off("tileerror", onTileError);
329
+ tile.remove();
330
+ const fallback = L.tileLayer(FALLBACK_TILE, {
331
+ maxZoom: 19,
332
+ subdomains: "abc",
333
+ crossOrigin: true
334
+ });
335
+ fallback.addTo(map);
336
+ fallback.bringToBack();
337
+ tileLayerRef.current = fallback;
338
+ };
339
+ tile.on("tileerror", onTileError);
340
+ }, [effectiveTileUrl, ready]);
265
341
  useEffect(() => {
266
342
  if (!ready || !mapRef.current) return;
267
343
  const map = mapRef.current;
268
344
  clearLayers();
269
- if (nightTileUrl && showTerminator) {
345
+ if (nightTileUrl && internalTerminator) {
270
346
  const nightTile = L.tileLayer(nightTileUrl, {
271
347
  maxZoom: 19,
272
348
  crossOrigin: true,
@@ -275,7 +351,7 @@ function GroundTrackMapLeaflet({
275
351
  });
276
352
  addLayer(nightTile);
277
353
  }
278
- if (showTerminator) {
354
+ if (internalTerminator) {
279
355
  const now = terminatorTime ?? /* @__PURE__ */ new Date();
280
356
  const BAND_STEP = 2;
281
357
  const MAX_DEP = 24;
@@ -308,8 +384,31 @@ function GroundTrackMapLeaflet({
308
384
  });
309
385
  prevOpacity = b.opacity;
310
386
  }
387
+ const terminatorEdge = calculateTerminatorContinuous(now, 0);
388
+ if (terminatorEdge.sunset.length > 2) {
389
+ const sunsetLine = terminatorEdge.sunset.map(([lat, lon]) => [lat, lon]);
390
+ const sunriseLine = terminatorEdge.sunrise.map(([lat, lon]) => [lat, lon]);
391
+ [0, 360, -360].forEach((offset) => {
392
+ addLayer(L.polyline(
393
+ sunsetLine.map(([lat, lon]) => [lat, lon + offset]),
394
+ { color: "#5a8ec8", weight: 4, opacity: 0.12, interactive: false, smoothFactor: 1.5 }
395
+ ));
396
+ addLayer(L.polyline(
397
+ sunriseLine.map(([lat, lon]) => [lat, lon + offset]),
398
+ { color: "#5a8ec8", weight: 4, opacity: 0.12, interactive: false, smoothFactor: 1.5 }
399
+ ));
400
+ addLayer(L.polyline(
401
+ sunsetLine.map(([lat, lon]) => [lat, lon + offset]),
402
+ { color: "#7aa4d4", weight: 1, opacity: 0.5, interactive: false, smoothFactor: 1.5 }
403
+ ));
404
+ addLayer(L.polyline(
405
+ sunriseLine.map(([lat, lon]) => [lat, lon + offset]),
406
+ { color: "#7aa4d4", weight: 1, opacity: 0.5, interactive: false, smoothFactor: 1.5 }
407
+ ));
408
+ });
409
+ }
311
410
  }
312
- if (lightSources && lightSources.length > 0 && showTerminator) {
411
+ if (lightSources && lightSources.length > 0 && internalTerminator) {
313
412
  const now = terminatorTime ?? /* @__PURE__ */ new Date();
314
413
  const dayOfYear = Math.floor((now.getTime() - new Date(now.getFullYear(), 0, 0).getTime()) / 864e5);
315
414
  const declination = -23.44 * Math.cos(2 * Math.PI / 365 * (dayOfYear + 10));
@@ -355,7 +454,7 @@ function GroundTrackMapLeaflet({
355
454
  }
356
455
  }
357
456
  }
358
- if (showGrid) {
457
+ if (internalGrid) {
359
458
  const gridStyle = { color: "rgba(157, 112, 255, 0.12)", weight: 0.5, interactive: false };
360
459
  [-360, 0, 360].forEach((offset) => {
361
460
  for (let lon = -180; lon <= 180; lon += 30) {
@@ -547,11 +646,11 @@ function GroundTrackMapLeaflet({
547
646
  ready,
548
647
  allSatellites,
549
648
  groundStations,
550
- showTerminator,
649
+ internalTerminator,
551
650
  terminatorTime,
552
651
  nightTileUrl,
553
652
  lightSources,
554
- showGrid,
653
+ internalGrid,
555
654
  showEquator,
556
655
  showLegend,
557
656
  tokens.colors.text.secondary,
@@ -650,8 +749,42 @@ function GroundTrackMapLeaflet({
650
749
  map.setView(defaultCenter, defaultZoom);
651
750
  }
652
751
  }, [allSatellites, groundStations, defaultCenter, defaultZoom]);
752
+ const handleZoomIn = useCallback(() => {
753
+ var _a;
754
+ (_a = mapRef.current) == null ? void 0 : _a.zoomIn();
755
+ }, []);
756
+ const handleZoomOut = useCallback(() => {
757
+ var _a;
758
+ (_a = mapRef.current) == null ? void 0 : _a.zoomOut();
759
+ }, []);
653
760
  const resolvedMinHeight = minHeight || (typeof height === "number" ? `${height}px` : "400px");
654
761
  const isEmpty = allSatellites.length === 0 && groundStations.length === 0 && (!pins || pins.length === 0);
762
+ const ctrlBtnBase = {
763
+ background: "rgba(20, 24, 38, 0.92)",
764
+ backdropFilter: "blur(8px)",
765
+ WebkitBackdropFilter: "blur(8px)",
766
+ border: "1px solid rgba(120, 100, 180, 0.18)",
767
+ color: "#c8c0d8",
768
+ cursor: "pointer",
769
+ display: "flex",
770
+ alignItems: "center",
771
+ justifyContent: "center",
772
+ transition: "background 0.15s, border-color 0.15s"
773
+ };
774
+ const ctrlHover = (e) => {
775
+ e.currentTarget.style.background = "rgba(30, 34, 52, 0.95)";
776
+ e.currentTarget.style.borderColor = "rgba(157, 112, 255, 0.4)";
777
+ };
778
+ const ctrlLeave = (e) => {
779
+ e.currentTarget.style.background = "rgba(20, 24, 38, 0.92)";
780
+ e.currentTarget.style.borderColor = "rgba(120, 100, 180, 0.18)";
781
+ };
782
+ const overlayItems = [];
783
+ overlayItems.push({ id: "terminator", label: "Day / Night", enabled: internalTerminator });
784
+ overlayItems.push({ id: "grid", label: "Grid Lines", enabled: internalGrid });
785
+ for (const l of customLayers ?? []) {
786
+ overlayItems.push({ id: l.id, label: l.label, enabled: customLayerState[l.id] ?? (l.defaultEnabled ?? true) });
787
+ }
655
788
  return /* @__PURE__ */ jsxs(
656
789
  "div",
657
790
  {
@@ -689,35 +822,232 @@ function GroundTrackMapLeaflet({
689
822
  children: emptyMessage
690
823
  }
691
824
  ),
692
- showRecenterButton && /* @__PURE__ */ jsxs(
693
- "button",
825
+ /* @__PURE__ */ jsxs(
826
+ "div",
694
827
  {
695
- type: "button",
696
- onClick: handleRecenter,
697
- title: "Recenter map",
698
- "aria-label": "Recenter map",
699
828
  style: {
700
829
  position: "absolute",
701
- top: 8,
702
- right: 50,
830
+ top: 10,
831
+ right: 10,
703
832
  zIndex: 1e3,
704
- background: "rgba(24, 29, 46, 0.9)",
705
- border: "1px solid rgba(157, 112, 255, 0.25)",
706
- borderRadius: 6,
707
- color: "#e4e0f0",
708
- cursor: "pointer",
709
- padding: "6px 10px",
710
- fontSize: 12,
711
833
  display: "flex",
712
- alignItems: "center",
713
- gap: 4
834
+ flexDirection: "row",
835
+ gap: 4,
836
+ alignItems: "flex-start",
837
+ pointerEvents: "none"
714
838
  },
715
839
  children: [
716
- /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
717
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
718
- /* @__PURE__ */ jsx("path", { d: "M12 2v4M12 18v4M2 12h4M18 12h4" })
840
+ showRecenterButton && /* @__PURE__ */ jsx(
841
+ "button",
842
+ {
843
+ type: "button",
844
+ onClick: handleRecenter,
845
+ title: "Recenter map to fit all assets and ground stations",
846
+ "aria-label": "Recenter map",
847
+ style: {
848
+ ...ctrlBtnBase,
849
+ borderRadius: 4,
850
+ padding: "6px 8px",
851
+ gap: 4,
852
+ fontSize: 10,
853
+ fontWeight: 500,
854
+ letterSpacing: "0.03em",
855
+ pointerEvents: "auto"
856
+ },
857
+ onMouseEnter: ctrlHover,
858
+ onMouseLeave: ctrlLeave,
859
+ children: /* @__PURE__ */ jsxs("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", children: [
860
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" }),
861
+ /* @__PURE__ */ jsx("path", { d: "M12 2v4M12 18v4M2 12h4M18 12h4" })
862
+ ] })
863
+ }
864
+ ),
865
+ /* @__PURE__ */ jsxs("div", { ref: layersPanelRef, style: { position: "relative", pointerEvents: "auto" }, children: [
866
+ /* @__PURE__ */ jsx(
867
+ "button",
868
+ {
869
+ type: "button",
870
+ onClick: () => setLayersPanelOpen((o) => !o),
871
+ title: "Map layers",
872
+ "aria-label": "Toggle map layers panel",
873
+ "aria-expanded": layersPanelOpen,
874
+ style: {
875
+ ...ctrlBtnBase,
876
+ borderRadius: 4,
877
+ padding: "6px 8px",
878
+ pointerEvents: "auto",
879
+ borderColor: layersPanelOpen ? "rgba(157, 112, 255, 0.4)" : void 0,
880
+ background: layersPanelOpen ? "rgba(30, 34, 52, 0.95)" : ctrlBtnBase.background
881
+ },
882
+ onMouseEnter: ctrlHover,
883
+ onMouseLeave: ctrlLeave,
884
+ children: /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinejoin: "round", children: [
885
+ /* @__PURE__ */ jsx("path", { d: "M12 2L2 7l10 5 10-5-10-5z" }),
886
+ /* @__PURE__ */ jsx("path", { d: "M2 17l10 5 10-5" }),
887
+ /* @__PURE__ */ jsx("path", { d: "M2 12l10 5 10-5" })
888
+ ] })
889
+ }
890
+ ),
891
+ layersPanelOpen && /* @__PURE__ */ jsxs(
892
+ "div",
893
+ {
894
+ style: {
895
+ position: "absolute",
896
+ top: "calc(100% + 6px)",
897
+ right: 0,
898
+ minWidth: 180,
899
+ background: "rgba(16, 18, 30, 0.96)",
900
+ backdropFilter: "blur(16px)",
901
+ WebkitBackdropFilter: "blur(16px)",
902
+ border: "1px solid rgba(120, 100, 180, 0.18)",
903
+ borderRadius: 6,
904
+ padding: "8px 0",
905
+ boxShadow: "0 8px 32px rgba(0,0,0,0.5)"
906
+ },
907
+ children: [
908
+ !isExplicitTileUrl && /* @__PURE__ */ jsxs(Fragment, { children: [
909
+ /* @__PURE__ */ jsx("div", { style: {
910
+ padding: "4px 12px 6px",
911
+ fontSize: 9,
912
+ fontWeight: 600,
913
+ letterSpacing: "0.08em",
914
+ textTransform: "uppercase",
915
+ color: "#7a748e"
916
+ }, children: "Base Map" }),
917
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 2, padding: "0 8px 8px" }, children: ["dark", "satellite"].map((style) => /* @__PURE__ */ jsxs(
918
+ "button",
919
+ {
920
+ type: "button",
921
+ onClick: () => handleTileStyleChange(style),
922
+ "aria-pressed": tileStyle === style,
923
+ style: {
924
+ flex: 1,
925
+ background: tileStyle === style ? "rgba(157, 112, 255, 0.18)" : "rgba(255,255,255,0.03)",
926
+ border: `1px solid ${tileStyle === style ? "rgba(157, 112, 255, 0.35)" : "rgba(120, 100, 180, 0.1)"}`,
927
+ borderRadius: 4,
928
+ color: tileStyle === style ? "#d0c4ee" : "#7a748e",
929
+ cursor: "pointer",
930
+ padding: "5px 6px",
931
+ fontSize: 10,
932
+ fontWeight: tileStyle === style ? 600 : 400,
933
+ display: "flex",
934
+ alignItems: "center",
935
+ justifyContent: "center",
936
+ gap: 4,
937
+ transition: "all 0.15s ease"
938
+ },
939
+ children: [
940
+ style === "dark" ? /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "currentColor", stroke: "none", children: /* @__PURE__ */ jsx("path", { d: "M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" }) }) : /* @__PURE__ */ jsxs("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
941
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
942
+ /* @__PURE__ */ jsx("path", { d: "M2 12h20M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z" })
943
+ ] }),
944
+ style === "dark" ? "Dark" : "Satellite"
945
+ ]
946
+ },
947
+ style
948
+ )) }),
949
+ /* @__PURE__ */ jsx("div", { style: { height: 1, background: "rgba(120, 100, 180, 0.1)", margin: "0 8px" } })
950
+ ] }),
951
+ /* @__PURE__ */ jsx("div", { style: {
952
+ padding: `${isExplicitTileUrl ? "4px" : "8px"} 12px 4px`,
953
+ fontSize: 9,
954
+ fontWeight: 600,
955
+ letterSpacing: "0.08em",
956
+ textTransform: "uppercase",
957
+ color: "#7a748e"
958
+ }, children: "Overlays" }),
959
+ overlayItems.map((item) => /* @__PURE__ */ jsxs(
960
+ "button",
961
+ {
962
+ type: "button",
963
+ onClick: () => handleLayerToggle(item.id, !item.enabled),
964
+ style: {
965
+ display: "flex",
966
+ alignItems: "center",
967
+ gap: 8,
968
+ width: "100%",
969
+ padding: "5px 12px",
970
+ background: "transparent",
971
+ border: "none",
972
+ color: item.enabled ? "#c8c0d8" : "#5a5470",
973
+ cursor: "pointer",
974
+ fontSize: 11,
975
+ textAlign: "left",
976
+ transition: "background 0.1s"
977
+ },
978
+ onMouseEnter: (e) => {
979
+ e.currentTarget.style.background = "rgba(157, 112, 255, 0.08)";
980
+ },
981
+ onMouseLeave: (e) => {
982
+ e.currentTarget.style.background = "transparent";
983
+ },
984
+ children: [
985
+ /* @__PURE__ */ jsx("span", { style: {
986
+ width: 14,
987
+ height: 14,
988
+ borderRadius: 3,
989
+ border: `1.5px solid ${item.enabled ? "rgba(157, 112, 255, 0.6)" : "rgba(120, 100, 180, 0.25)"}`,
990
+ background: item.enabled ? "rgba(157, 112, 255, 0.22)" : "transparent",
991
+ display: "flex",
992
+ alignItems: "center",
993
+ justifyContent: "center",
994
+ flexShrink: 0,
995
+ transition: "all 0.15s ease"
996
+ }, children: item.enabled && /* @__PURE__ */ jsx("svg", { width: "9", height: "9", viewBox: "0 0 12 12", fill: "none", stroke: "#d0c4ee", strokeWidth: "2", children: /* @__PURE__ */ jsx("path", { d: "M2 6l3 3 5-5" }) }) }),
997
+ item.label
998
+ ]
999
+ },
1000
+ item.id
1001
+ ))
1002
+ ]
1003
+ }
1004
+ )
719
1005
  ] }),
720
- "Recenter"
1006
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", pointerEvents: "auto" }, children: [
1007
+ /* @__PURE__ */ jsx(
1008
+ "button",
1009
+ {
1010
+ type: "button",
1011
+ onClick: handleZoomIn,
1012
+ title: "Zoom in",
1013
+ "aria-label": "Zoom in",
1014
+ style: {
1015
+ ...ctrlBtnBase,
1016
+ borderRadius: "4px 4px 0 0",
1017
+ borderBottom: "none",
1018
+ width: 30,
1019
+ height: 28,
1020
+ fontSize: 16,
1021
+ fontWeight: 300,
1022
+ pointerEvents: "auto"
1023
+ },
1024
+ onMouseEnter: ctrlHover,
1025
+ onMouseLeave: ctrlLeave,
1026
+ children: "+"
1027
+ }
1028
+ ),
1029
+ /* @__PURE__ */ jsx(
1030
+ "button",
1031
+ {
1032
+ type: "button",
1033
+ onClick: handleZoomOut,
1034
+ title: "Zoom out",
1035
+ "aria-label": "Zoom out",
1036
+ style: {
1037
+ ...ctrlBtnBase,
1038
+ borderRadius: "0 0 4px 4px",
1039
+ width: 30,
1040
+ height: 28,
1041
+ fontSize: 16,
1042
+ fontWeight: 300,
1043
+ pointerEvents: "auto"
1044
+ },
1045
+ onMouseEnter: ctrlHover,
1046
+ onMouseLeave: ctrlLeave,
1047
+ children: "−"
1048
+ }
1049
+ )
1050
+ ] })
721
1051
  ]
722
1052
  }
723
1053
  )