zenit-sdk 0.0.7 → 0.0.8

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.
@@ -249,6 +249,7 @@ function getEffectiveLayerOpacity(baseOpacity, zoom, layerType, geometryType, op
249
249
  import { jsx, jsxs } from "react/jsx-runtime";
250
250
  var DEFAULT_CENTER = [0, 0];
251
251
  var DEFAULT_ZOOM = 3;
252
+ var LABELS_PANE_NAME = "zenit-labels-pane";
252
253
  function computeBBoxFromGeojson(geojson) {
253
254
  if (!geojson) return null;
254
255
  if (!Array.isArray(geojson.features) || geojson.features.length === 0) return null;
@@ -289,6 +290,23 @@ function mergeBBoxes(bboxes) {
289
290
  { ...first }
290
291
  );
291
292
  }
293
+ function isRecord(value) {
294
+ return typeof value === "object" && value !== null;
295
+ }
296
+ function isGeoJsonFeatureCollection(value) {
297
+ if (!isRecord(value)) return false;
298
+ const features = value.features;
299
+ if (!Array.isArray(features)) return false;
300
+ const type = value.type;
301
+ return type === void 0 || type === "FeatureCollection";
302
+ }
303
+ function extractGeoJsonFeatureCollection(value) {
304
+ if (isRecord(value) && "data" in value) {
305
+ const data = value.data;
306
+ return isGeoJsonFeatureCollection(data) ? data : null;
307
+ }
308
+ return isGeoJsonFeatureCollection(value) ? value : null;
309
+ }
292
310
  function getFeatureLayerId(feature) {
293
311
  const layerId = feature?.properties?.__zenit_layerId ?? feature?.properties?.layerId ?? feature?.properties?.layer_id;
294
312
  if (layerId === void 0 || layerId === null) return null;
@@ -299,6 +317,7 @@ function escapeHtml(value) {
299
317
  }
300
318
  var DESCRIPTION_KEYS = /* @__PURE__ */ new Set(["descripcion", "description"]);
301
319
  var POPUP_EXCLUDED_KEYS = /* @__PURE__ */ new Set(["geom", "geometry"]);
320
+ var POPUP_HEADER_KEYS = ["nombre", "name", "title", "titulo"];
302
321
  var POPUP_STYLE_ID = "zenit-leaflet-popup-styles";
303
322
  var DESKTOP_POPUP_DIMENSIONS = { maxWidth: 350, minWidth: 280, maxHeight: 480 };
304
323
  var MOBILE_POPUP_DIMENSIONS = { maxWidth: 280, minWidth: 240, maxHeight: 380 };
@@ -325,6 +344,7 @@ var ZENIT_LEAFLET_POPUP_STYLES = `
325
344
  line-height: 1.5;
326
345
  color: #374151;
327
346
  min-width: 100%;
347
+ max-height: min(70vh, 480px);
328
348
  overflow-y: auto;
329
349
  overflow-x: hidden;
330
350
  scrollbar-width: thin;
@@ -375,6 +395,20 @@ var ZENIT_LEAFLET_POPUP_STYLES = `
375
395
  padding: 16px;
376
396
  }
377
397
 
398
+ .zenit-popup-header {
399
+ padding-bottom: 12px;
400
+ border-bottom: 1px solid #e5e7eb;
401
+ margin-bottom: 4px;
402
+ }
403
+
404
+ .zenit-popup-title {
405
+ font-size: 14px;
406
+ font-weight: 700;
407
+ color: #111827;
408
+ letter-spacing: 0.01em;
409
+ line-height: 1.4;
410
+ }
411
+
378
412
  /* Individual row styling with subtle separator */
379
413
  .zenit-popup-row {
380
414
  display: flex;
@@ -413,6 +447,16 @@ var ZENIT_LEAFLET_POPUP_STYLES = `
413
447
  line-height: 1.5;
414
448
  }
415
449
 
450
+ .zenit-popup-link {
451
+ color: #2563eb;
452
+ text-decoration: underline;
453
+ font-weight: 500;
454
+ }
455
+
456
+ .zenit-popup-link:hover {
457
+ color: #1d4ed8;
458
+ }
459
+
416
460
  /* Special styling for description field */
417
461
  .zenit-popup-row.zenit-popup-description {
418
462
  background-color: #f9fafb;
@@ -507,6 +551,18 @@ var ZENIT_LEAFLET_POPUP_STYLES = `
507
551
  padding: 12px;
508
552
  }
509
553
 
554
+ .zenit-leaflet-popup .leaflet-popup-content {
555
+ max-height: min(65vh, 380px);
556
+ }
557
+
558
+ .zenit-popup-header {
559
+ padding-bottom: 10px;
560
+ }
561
+
562
+ .zenit-popup-title {
563
+ font-size: 13px;
564
+ }
565
+
510
566
  .zenit-popup-row {
511
567
  padding: 8px 0;
512
568
  }
@@ -555,6 +611,17 @@ var ZENIT_LEAFLET_POPUP_STYLES = `
555
611
  .zenit-map-tooltip::before {
556
612
  border-top-color: rgba(31, 41, 55, 0.95);
557
613
  }
614
+
615
+ .polygon-label-tooltip {
616
+ z-index: 600 !important;
617
+ }
618
+
619
+ .zenit-map-shell.popup-open .zenit-label-marker,
620
+ .zenit-map-shell.popup-open .polygon-label-tooltip,
621
+ .zenit-map-shell.popup-open .click-for-detail-hint,
622
+ .zenit-map-shell.popup-open .zenit-map-tooltip {
623
+ display: none !important;
624
+ }
558
625
  `;
559
626
  function ensurePopupStyles() {
560
627
  if (typeof document === "undefined") return;
@@ -601,6 +668,31 @@ function renderPopupValue(value) {
601
668
  if (value === null || value === void 0) {
602
669
  return '<span class="zenit-popup-empty">Sin datos</span>';
603
670
  }
671
+ if (value instanceof Date) {
672
+ return `<span>${escapeHtml(value.toLocaleDateString("es-GT"))}</span>`;
673
+ }
674
+ if (typeof value === "number") {
675
+ return `<span>${escapeHtml(value.toLocaleString("es-GT"))}</span>`;
676
+ }
677
+ if (typeof value === "string") {
678
+ const trimmed = value.trim();
679
+ const isLikelyDate = /^\d{4}-\d{2}-\d{2}/.test(trimmed) || trimmed.includes("T");
680
+ if (isLikelyDate) {
681
+ const parsed = Date.parse(trimmed);
682
+ if (!Number.isNaN(parsed)) {
683
+ return `<span>${escapeHtml(new Date(parsed).toLocaleDateString("es-GT"))}</span>`;
684
+ }
685
+ }
686
+ try {
687
+ const parsedUrl = new URL(trimmed);
688
+ if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") {
689
+ const safeHref = escapeHtml(parsedUrl.toString());
690
+ return `<a class="zenit-popup-link" href="${safeHref}" target="_blank" rel="noopener noreferrer">${safeHref}</a>`;
691
+ }
692
+ } catch {
693
+ }
694
+ return `<span>${escapeHtml(trimmed || value)}</span>`;
695
+ }
604
696
  if (typeof value === "object") {
605
697
  const json = safeJsonStringify(value);
606
698
  return `<pre class="zenit-popup-pre">${escapeHtml(json)}</pre>`;
@@ -621,13 +713,29 @@ function isDescriptionKey(key) {
621
713
  const normalized = key.trim().toLowerCase();
622
714
  return DESCRIPTION_KEYS.has(normalized);
623
715
  }
716
+ function extractPopupHeader(properties) {
717
+ if (!properties) return null;
718
+ const entry = Object.entries(properties).find(
719
+ (candidate) => {
720
+ const [key, value] = candidate;
721
+ return POPUP_HEADER_KEYS.includes(key.trim().toLowerCase()) && typeof value === "string" && value.trim().length > 0;
722
+ }
723
+ );
724
+ if (!entry) return null;
725
+ return entry[1].trim();
726
+ }
624
727
  function formatLabel(key) {
625
728
  return key.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").trim();
626
729
  }
627
730
  function createPopupContent(properties) {
628
- const entries = Object.entries(properties).filter(
629
- ([key, value]) => shouldIncludePopupEntry(key, value)
630
- );
731
+ const headerText = extractPopupHeader(properties);
732
+ const entries = Object.entries(properties).filter(([key, value]) => {
733
+ if (!shouldIncludePopupEntry(key, value)) return false;
734
+ if (headerText && POPUP_HEADER_KEYS.includes(key.trim().toLowerCase())) {
735
+ return false;
736
+ }
737
+ return true;
738
+ });
631
739
  if (entries.length === 0) {
632
740
  return `<div class="zenit-popup-card"><div class="zenit-popup-empty">Sin datos disponibles</div></div>`;
633
741
  }
@@ -655,7 +763,10 @@ function createPopupContent(properties) {
655
763
  </div>
656
764
  `;
657
765
  }).join("");
658
- return `<div class="zenit-popup-card">${rowsHtml}</div>`;
766
+ const headerHtml = headerText ? `<div class="zenit-popup-header"><div class="zenit-popup-title">${escapeHtml(
767
+ headerText
768
+ )}</div></div>` : "";
769
+ return `<div class="zenit-popup-card">${headerHtml}${rowsHtml}</div>`;
659
770
  }
660
771
  function withAlpha(color, alpha) {
661
772
  const trimmed = color.trim();
@@ -880,6 +991,7 @@ var ZenitMap = forwardRef(({
880
991
  const [mapInstance, setMapInstance] = useState(null);
881
992
  const [panesReady, setPanesReady] = useState(false);
882
993
  const [currentZoom, setCurrentZoom] = useState(initialZoom ?? DEFAULT_ZOOM);
994
+ const [isPopupOpen, setIsPopupOpen] = useState(false);
883
995
  const [isMobile, setIsMobile] = useState(() => {
884
996
  if (typeof window === "undefined") return false;
885
997
  return window.matchMedia("(max-width: 768px)").matches;
@@ -908,6 +1020,31 @@ var ZenitMap = forwardRef(({
908
1020
  ensurePopupStyles();
909
1021
  }
910
1022
  }, [featureInfoMode]);
1023
+ useEffect(() => {
1024
+ if (featureInfoMode !== "popup") {
1025
+ setIsPopupOpen(false);
1026
+ }
1027
+ }, [featureInfoMode]);
1028
+ useEffect(() => {
1029
+ if (!mapInstance) return;
1030
+ const popupPane = mapInstance.getPane("popupPane");
1031
+ if (popupPane) {
1032
+ popupPane.style.zIndex = "800";
1033
+ }
1034
+ const labelsPane = mapInstance.getPane(LABELS_PANE_NAME) ?? mapInstance.createPane(LABELS_PANE_NAME);
1035
+ labelsPane.style.zIndex = "600";
1036
+ }, [mapInstance]);
1037
+ useEffect(() => {
1038
+ if (!mapInstance) return;
1039
+ const handlePopupOpen = () => setIsPopupOpen(true);
1040
+ const handlePopupClose = () => setIsPopupOpen(false);
1041
+ mapInstance.on("popupopen", handlePopupOpen);
1042
+ mapInstance.on("popupclose", handlePopupClose);
1043
+ return () => {
1044
+ mapInstance.off("popupopen", handlePopupOpen);
1045
+ mapInstance.off("popupclose", handlePopupClose);
1046
+ };
1047
+ }, [mapInstance]);
911
1048
  const layerStyleIndex = useMemo(() => {
912
1049
  const index = /* @__PURE__ */ new Map();
913
1050
  (map?.mapLayers ?? []).forEach((entry) => {
@@ -1199,15 +1336,13 @@ var ZenitMap = forwardRef(({
1199
1336
  const baseZIndex = 400;
1200
1337
  targetLayers.forEach((layer) => {
1201
1338
  const order = Number.isFinite(layer.displayOrder) ? layer.displayOrder : 0;
1339
+ const orderOffset = Math.max(0, Math.min(order, 150));
1202
1340
  const fillPaneName = `zenit-layer-${layer.layerId}-fill`;
1203
1341
  const pointPaneName = `zenit-layer-${layer.layerId}-points`;
1204
- const labelPaneName = `zenit-layer-${layer.layerId}-labels`;
1205
1342
  const fillPane = targetMap.getPane(fillPaneName) ?? targetMap.createPane(fillPaneName);
1206
1343
  const pointPane = targetMap.getPane(pointPaneName) ?? targetMap.createPane(pointPaneName);
1207
- const labelPane = targetMap.getPane(labelPaneName) ?? targetMap.createPane(labelPaneName);
1208
- fillPane.style.zIndex = String(baseZIndex + order);
1209
- pointPane.style.zIndex = String(baseZIndex + order + 1e3);
1210
- labelPane.style.zIndex = String(baseZIndex + order + 2e3);
1344
+ fillPane.style.zIndex = String(baseZIndex + orderOffset);
1345
+ pointPane.style.zIndex = String(baseZIndex + orderOffset + 100);
1211
1346
  });
1212
1347
  },
1213
1348
  []
@@ -1234,8 +1369,9 @@ var ZenitMap = forwardRef(({
1234
1369
  }));
1235
1370
  ensureLayerPanes(mapInstance, layerTargets);
1236
1371
  const first = layerTargets[0];
1237
- const testPane = mapInstance.getPane(`zenit-layer-${first.layerId}-labels`);
1238
- if (testPane) {
1372
+ const testPane = mapInstance.getPane(`zenit-layer-${first.layerId}-fill`);
1373
+ const labelsPane = mapInstance.getPane(LABELS_PANE_NAME);
1374
+ if (testPane && labelsPane) {
1239
1375
  setPanesReady(true);
1240
1376
  }
1241
1377
  }, [mapInstance, orderedLayers, ensureLayerPanes]);
@@ -1260,7 +1396,7 @@ var ZenitMap = forwardRef(({
1260
1396
  maxWidth,
1261
1397
  minWidth,
1262
1398
  maxHeight,
1263
- className: "zenit-leaflet-popup",
1399
+ className: "zenit-leaflet-popup custom-leaflet-popup",
1264
1400
  autoPan: true,
1265
1401
  closeButton: true,
1266
1402
  keepInView: true,
@@ -1285,7 +1421,8 @@ var ZenitMap = forwardRef(({
1285
1421
  id: layerId,
1286
1422
  geometry: feature.geometry
1287
1423
  }).then((response) => {
1288
- const candidates = response.data?.features ?? [];
1424
+ const geo = extractGeoJsonFeatureCollection(response);
1425
+ const candidates = geo?.features ?? [];
1289
1426
  const resolved = pickIntersectFeature(feature, candidates);
1290
1427
  if (!resolved?.properties) return;
1291
1428
  const mergedProperties = {
@@ -1488,87 +1625,93 @@ var ZenitMap = forwardRef(({
1488
1625
  boxSizing: "border-box"
1489
1626
  },
1490
1627
  children: [
1491
- /* @__PURE__ */ jsx("div", { style: { flex: 1, position: "relative" }, children: /* @__PURE__ */ jsxs(
1492
- MapContainer,
1628
+ /* @__PURE__ */ jsx(
1629
+ "div",
1493
1630
  {
1494
- center,
1495
- zoom,
1496
- style: { height: "100%", width: "100%" },
1497
- scrollWheelZoom: true,
1498
- zoomControl: false,
1499
- children: [
1500
- /* @__PURE__ */ jsx(
1501
- TileLayer,
1502
- {
1503
- url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
1504
- attribution: "\xA9 OpenStreetMap contributors"
1505
- }
1506
- ),
1507
- /* @__PURE__ */ jsx(ZoomControl, { position: "topright" }),
1508
- /* @__PURE__ */ jsx(MapInstanceBridge, { onReady: handleMapReady }),
1509
- /* @__PURE__ */ jsx(FitToBounds, { bbox: explicitZoomBBox ?? void 0 }),
1510
- /* @__PURE__ */ jsx(AutoFitToBounds, { bbox: autoZoomBBox ?? void 0, enabled: !explicitZoomBBox }),
1511
- /* @__PURE__ */ jsx(ZoomBasedOpacityHandler, { onZoomChange: handleZoomChange }),
1512
- orderedLayers.map((layerState) => {
1513
- const baseOpacity = layerState.effective?.baseOpacity ?? layerState.effective?.opacity ?? 1;
1514
- const fillPaneName = `zenit-layer-${layerState.mapLayer.layerId}-fill`;
1515
- const pointsPaneName = `zenit-layer-${layerState.mapLayer.layerId}-points`;
1516
- const labelPaneName = `zenit-layer-${layerState.mapLayer.layerId}-labels`;
1517
- const layerType = layerState.layer?.layerType ?? layerState.mapLayer.layerType ?? void 0;
1518
- const data = layerState.data?.features ?? [];
1519
- const fillFeatures = data.filter(isNonPointGeometry);
1520
- const pointFeatures = data.filter(isPointGeometry);
1521
- const fillData = fillFeatures.length > 0 ? buildFeatureCollection(fillFeatures) : null;
1522
- const pointsData = pointFeatures.length > 0 ? buildFeatureCollection(pointFeatures) : null;
1523
- return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1524
- fillData && /* @__PURE__ */ jsx(
1525
- GeoJSON,
1631
+ className: `zenit-map-shell${isPopupOpen ? " popup-open" : ""}`,
1632
+ style: { flex: 1, position: "relative" },
1633
+ children: /* @__PURE__ */ jsxs(
1634
+ MapContainer,
1635
+ {
1636
+ center,
1637
+ zoom,
1638
+ style: { height: "100%", width: "100%" },
1639
+ scrollWheelZoom: true,
1640
+ zoomControl: false,
1641
+ children: [
1642
+ /* @__PURE__ */ jsx(
1643
+ TileLayer,
1526
1644
  {
1527
- data: fillData,
1528
- pane: panesReady && mapInstance?.getPane(fillPaneName) ? fillPaneName : void 0,
1529
- style: (feature) => buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType),
1530
- onEachFeature: overlayOnEachFeature
1645
+ url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
1646
+ attribution: "\xA9 OpenStreetMap contributors"
1531
1647
  }
1532
1648
  ),
1533
- pointsData && /* @__PURE__ */ jsx(
1649
+ /* @__PURE__ */ jsx(ZoomControl, { position: "topright" }),
1650
+ /* @__PURE__ */ jsx(MapInstanceBridge, { onReady: handleMapReady }),
1651
+ /* @__PURE__ */ jsx(FitToBounds, { bbox: explicitZoomBBox ?? void 0 }),
1652
+ /* @__PURE__ */ jsx(AutoFitToBounds, { bbox: autoZoomBBox ?? void 0, enabled: !explicitZoomBBox }),
1653
+ /* @__PURE__ */ jsx(ZoomBasedOpacityHandler, { onZoomChange: handleZoomChange }),
1654
+ orderedLayers.map((layerState) => {
1655
+ const baseOpacity = layerState.effective?.baseOpacity ?? layerState.effective?.opacity ?? 1;
1656
+ const fillPaneName = `zenit-layer-${layerState.mapLayer.layerId}-fill`;
1657
+ const pointsPaneName = `zenit-layer-${layerState.mapLayer.layerId}-points`;
1658
+ const layerType = layerState.layer?.layerType ?? layerState.mapLayer.layerType ?? void 0;
1659
+ const data = layerState.data?.features ?? [];
1660
+ const fillFeatures = data.filter(isNonPointGeometry);
1661
+ const pointFeatures = data.filter(isPointGeometry);
1662
+ const fillData = fillFeatures.length > 0 ? buildFeatureCollection(fillFeatures) : null;
1663
+ const pointsData = pointFeatures.length > 0 ? buildFeatureCollection(pointFeatures) : null;
1664
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1665
+ fillData && /* @__PURE__ */ jsx(
1666
+ GeoJSON,
1667
+ {
1668
+ data: fillData,
1669
+ pane: panesReady && mapInstance?.getPane(fillPaneName) ? fillPaneName : void 0,
1670
+ style: (feature) => buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType),
1671
+ onEachFeature: overlayOnEachFeature
1672
+ }
1673
+ ),
1674
+ pointsData && /* @__PURE__ */ jsx(
1675
+ GeoJSON,
1676
+ {
1677
+ data: pointsData,
1678
+ pane: panesReady && mapInstance?.getPane(pointsPaneName) ? pointsPaneName : void 0,
1679
+ pointToLayer: (feature, latlng) => L.circleMarker(latlng, {
1680
+ radius: isMobile ? 8 : 6,
1681
+ ...buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType)
1682
+ }),
1683
+ onEachFeature: overlayOnEachFeature
1684
+ }
1685
+ ),
1686
+ panesReady && mapInstance?.getPane(LABELS_PANE_NAME) ? labelMarkers.filter(
1687
+ (marker) => String(marker.layerId) === String(layerState.mapLayer.layerId)
1688
+ ).map((marker) => /* @__PURE__ */ jsx(
1689
+ Marker,
1690
+ {
1691
+ position: marker.position,
1692
+ icon: buildLabelIcon(marker.label, marker.opacity, marker.color),
1693
+ interactive: false,
1694
+ pane: LABELS_PANE_NAME
1695
+ },
1696
+ marker.key
1697
+ )) : null
1698
+ ] }, layerState.mapLayer.layerId.toString());
1699
+ }),
1700
+ overlayGeojson && /* @__PURE__ */ jsx(
1534
1701
  GeoJSON,
1535
1702
  {
1536
- data: pointsData,
1537
- pane: panesReady && mapInstance?.getPane(pointsPaneName) ? pointsPaneName : void 0,
1538
- pointToLayer: (feature, latlng) => L.circleMarker(latlng, {
1539
- radius: isMobile ? 8 : 6,
1540
- ...buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType)
1541
- }),
1703
+ data: overlayGeojson,
1704
+ style: overlayStyleFunction,
1542
1705
  onEachFeature: overlayOnEachFeature
1543
- }
1544
- ),
1545
- panesReady && mapInstance?.getPane(labelPaneName) ? labelMarkers.filter(
1546
- (marker) => String(marker.layerId) === String(layerState.mapLayer.layerId)
1547
- ).map((marker) => /* @__PURE__ */ jsx(
1548
- Marker,
1549
- {
1550
- position: marker.position,
1551
- icon: buildLabelIcon(marker.label, marker.opacity, marker.color),
1552
- interactive: false,
1553
- pane: labelPaneName
1554
1706
  },
1555
- marker.key
1556
- )) : null
1557
- ] }, layerState.mapLayer.layerId.toString());
1558
- }),
1559
- overlayGeojson && /* @__PURE__ */ jsx(
1560
- GeoJSON,
1561
- {
1562
- data: overlayGeojson,
1563
- style: overlayStyleFunction,
1564
- onEachFeature: overlayOnEachFeature
1565
- },
1566
- "zenit-overlay-geojson"
1567
- )
1568
- ]
1569
- },
1570
- String(mapId)
1571
- ) }),
1707
+ "zenit-overlay-geojson"
1708
+ )
1709
+ ]
1710
+ },
1711
+ String(mapId)
1712
+ )
1713
+ }
1714
+ ),
1572
1715
  showLayerPanel && decoratedLayers.length > 0 && /* @__PURE__ */ jsxs(
1573
1716
  "div",
1574
1717
  {
@@ -3508,4 +3651,4 @@ export {
3508
3651
  useSendMessageStream,
3509
3652
  FloatingChatBox
3510
3653
  };
3511
- //# sourceMappingURL=chunk-ITF7QCUZ.mjs.map
3654
+ //# sourceMappingURL=chunk-52CLFD4L.mjs.map