zenit-sdk 0.0.6 → 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.
package/dist/index.js CHANGED
@@ -1403,7 +1403,7 @@ function normalizeBbox(input) {
1403
1403
  }
1404
1404
 
1405
1405
  // src/react/ZenitMap.tsx
1406
- var import_react = require("react");
1406
+ var import_react = __toESM(require("react"));
1407
1407
  var import_react_leaflet = require("react-leaflet");
1408
1408
  var import_leaflet = __toESM(require("leaflet"));
1409
1409
 
@@ -1470,6 +1470,7 @@ function getEffectiveLayerOpacity(baseOpacity, zoom, layerType, geometryType, op
1470
1470
  var import_jsx_runtime = require("react/jsx-runtime");
1471
1471
  var DEFAULT_CENTER = [0, 0];
1472
1472
  var DEFAULT_ZOOM = 3;
1473
+ var LABELS_PANE_NAME = "zenit-labels-pane";
1473
1474
  function computeBBoxFromGeojson(geojson) {
1474
1475
  if (!geojson) return null;
1475
1476
  if (!Array.isArray(geojson.features) || geojson.features.length === 0) return null;
@@ -1510,6 +1511,23 @@ function mergeBBoxes(bboxes) {
1510
1511
  { ...first }
1511
1512
  );
1512
1513
  }
1514
+ function isRecord(value) {
1515
+ return typeof value === "object" && value !== null;
1516
+ }
1517
+ function isGeoJsonFeatureCollection(value) {
1518
+ if (!isRecord(value)) return false;
1519
+ const features = value.features;
1520
+ if (!Array.isArray(features)) return false;
1521
+ const type = value.type;
1522
+ return type === void 0 || type === "FeatureCollection";
1523
+ }
1524
+ function extractGeoJsonFeatureCollection(value) {
1525
+ if (isRecord(value) && "data" in value) {
1526
+ const data = value.data;
1527
+ return isGeoJsonFeatureCollection(data) ? data : null;
1528
+ }
1529
+ return isGeoJsonFeatureCollection(value) ? value : null;
1530
+ }
1513
1531
  function getFeatureLayerId(feature) {
1514
1532
  const layerId = feature?.properties?.__zenit_layerId ?? feature?.properties?.layerId ?? feature?.properties?.layer_id;
1515
1533
  if (layerId === void 0 || layerId === null) return null;
@@ -1518,6 +1536,459 @@ function getFeatureLayerId(feature) {
1518
1536
  function escapeHtml(value) {
1519
1537
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1520
1538
  }
1539
+ var DESCRIPTION_KEYS = /* @__PURE__ */ new Set(["descripcion", "description"]);
1540
+ var POPUP_EXCLUDED_KEYS = /* @__PURE__ */ new Set(["geom", "geometry"]);
1541
+ var POPUP_HEADER_KEYS = ["nombre", "name", "title", "titulo"];
1542
+ var POPUP_STYLE_ID = "zenit-leaflet-popup-styles";
1543
+ var DESKTOP_POPUP_DIMENSIONS = { maxWidth: 350, minWidth: 280, maxHeight: 480 };
1544
+ var MOBILE_POPUP_DIMENSIONS = { maxWidth: 280, minWidth: 240, maxHeight: 380 };
1545
+ var ZENIT_LEAFLET_POPUP_STYLES = `
1546
+ /* ===== Zenit Leaflet Popup - Modern Professional Styling ===== */
1547
+
1548
+ /* Main popup wrapper */
1549
+ .zenit-leaflet-popup .leaflet-popup-content-wrapper {
1550
+ border-radius: 12px;
1551
+ box-shadow:
1552
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
1553
+ 0 2px 4px -2px rgba(0, 0, 0, 0.1),
1554
+ 0 0 0 1px rgba(0, 0, 0, 0.05);
1555
+ padding: 0;
1556
+ background: #ffffff;
1557
+ overflow: hidden;
1558
+ }
1559
+
1560
+ /* Content area with scroll support */
1561
+ .zenit-leaflet-popup .leaflet-popup-content {
1562
+ margin: 0;
1563
+ padding: 0;
1564
+ font-size: 13px;
1565
+ line-height: 1.5;
1566
+ color: #374151;
1567
+ min-width: 100%;
1568
+ max-height: min(70vh, 480px);
1569
+ overflow-y: auto;
1570
+ overflow-x: hidden;
1571
+ scrollbar-width: thin;
1572
+ scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
1573
+ }
1574
+
1575
+ /* Popup tip/arrow shadow */
1576
+ .zenit-leaflet-popup .leaflet-popup-tip-container {
1577
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
1578
+ }
1579
+
1580
+ .zenit-leaflet-popup .leaflet-popup-tip {
1581
+ background: #ffffff;
1582
+ box-shadow: none;
1583
+ }
1584
+
1585
+ /* Close button styling */
1586
+ .zenit-leaflet-popup .leaflet-popup-close-button {
1587
+ color: #9ca3af;
1588
+ font-size: 18px;
1589
+ font-weight: 400;
1590
+ width: 28px;
1591
+ height: 28px;
1592
+ padding: 0;
1593
+ margin: 8px 8px 0 0;
1594
+ display: flex;
1595
+ align-items: center;
1596
+ justify-content: center;
1597
+ border-radius: 6px;
1598
+ transition: all 0.15s ease;
1599
+ z-index: 10;
1600
+ }
1601
+
1602
+ .zenit-leaflet-popup .leaflet-popup-close-button:hover {
1603
+ color: #374151;
1604
+ background-color: #f3f4f6;
1605
+ }
1606
+
1607
+ .zenit-leaflet-popup .leaflet-popup-close-button:active {
1608
+ background-color: #e5e7eb;
1609
+ }
1610
+
1611
+ /* Main card container */
1612
+ .zenit-popup-card {
1613
+ display: flex;
1614
+ flex-direction: column;
1615
+ gap: 0;
1616
+ padding: 16px;
1617
+ }
1618
+
1619
+ .zenit-popup-header {
1620
+ padding-bottom: 12px;
1621
+ border-bottom: 1px solid #e5e7eb;
1622
+ margin-bottom: 4px;
1623
+ }
1624
+
1625
+ .zenit-popup-title {
1626
+ font-size: 14px;
1627
+ font-weight: 700;
1628
+ color: #111827;
1629
+ letter-spacing: 0.01em;
1630
+ line-height: 1.4;
1631
+ }
1632
+
1633
+ /* Individual row styling with subtle separator */
1634
+ .zenit-popup-row {
1635
+ display: flex;
1636
+ flex-direction: column;
1637
+ gap: 2px;
1638
+ padding: 10px 0;
1639
+ border-bottom: 1px solid #f3f4f6;
1640
+ }
1641
+
1642
+ .zenit-popup-row:first-child {
1643
+ padding-top: 0;
1644
+ }
1645
+
1646
+ .zenit-popup-row:last-child {
1647
+ border-bottom: none;
1648
+ padding-bottom: 0;
1649
+ }
1650
+
1651
+ /* Label styling - small, gray, uppercase */
1652
+ .zenit-popup-label {
1653
+ font-size: 10px;
1654
+ font-weight: 500;
1655
+ color: #9ca3af;
1656
+ text-transform: uppercase;
1657
+ letter-spacing: 0.05em;
1658
+ line-height: 1.4;
1659
+ }
1660
+
1661
+ /* Value styling - darker, readable */
1662
+ .zenit-popup-value {
1663
+ font-size: 13px;
1664
+ font-weight: 400;
1665
+ color: #1f2937;
1666
+ overflow-wrap: break-word;
1667
+ word-break: break-word;
1668
+ line-height: 1.5;
1669
+ }
1670
+
1671
+ .zenit-popup-link {
1672
+ color: #2563eb;
1673
+ text-decoration: underline;
1674
+ font-weight: 500;
1675
+ }
1676
+
1677
+ .zenit-popup-link:hover {
1678
+ color: #1d4ed8;
1679
+ }
1680
+
1681
+ /* Special styling for description field */
1682
+ .zenit-popup-row.zenit-popup-description {
1683
+ background-color: #f9fafb;
1684
+ margin: 0 -16px;
1685
+ padding: 12px 16px;
1686
+ border-bottom: 1px solid #e5e7eb;
1687
+ }
1688
+
1689
+ .zenit-popup-row.zenit-popup-description:first-child {
1690
+ margin-top: 0;
1691
+ border-radius: 0;
1692
+ }
1693
+
1694
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value {
1695
+ font-size: 13px;
1696
+ line-height: 1.6;
1697
+ color: #374151;
1698
+ max-height: 150px;
1699
+ overflow-y: auto;
1700
+ padding-right: 4px;
1701
+ }
1702
+
1703
+ /* Preformatted text (JSON objects) */
1704
+ .zenit-popup-pre {
1705
+ margin: 0;
1706
+ font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
1707
+ font-size: 11px;
1708
+ white-space: pre-wrap;
1709
+ word-break: break-word;
1710
+ color: #4b5563;
1711
+ background-color: #f9fafb;
1712
+ padding: 8px;
1713
+ border-radius: 6px;
1714
+ border: 1px solid #e5e7eb;
1715
+ }
1716
+
1717
+ /* Empty state styling */
1718
+ .zenit-popup-empty {
1719
+ font-size: 13px;
1720
+ color: #9ca3af;
1721
+ font-style: italic;
1722
+ text-align: center;
1723
+ padding: 20px 0;
1724
+ }
1725
+
1726
+ /* Webkit scrollbar styling */
1727
+ .zenit-leaflet-popup .leaflet-popup-content::-webkit-scrollbar {
1728
+ width: 6px;
1729
+ }
1730
+
1731
+ .zenit-leaflet-popup .leaflet-popup-content::-webkit-scrollbar-track {
1732
+ background: transparent;
1733
+ }
1734
+
1735
+ .zenit-leaflet-popup .leaflet-popup-content::-webkit-scrollbar-thumb {
1736
+ background-color: rgba(156, 163, 175, 0.4);
1737
+ border-radius: 3px;
1738
+ }
1739
+
1740
+ .zenit-leaflet-popup .leaflet-popup-content::-webkit-scrollbar-thumb:hover {
1741
+ background-color: rgba(107, 114, 128, 0.6);
1742
+ }
1743
+
1744
+ /* Scrollbar for description field */
1745
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value::-webkit-scrollbar {
1746
+ width: 4px;
1747
+ }
1748
+
1749
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value::-webkit-scrollbar-track {
1750
+ background: transparent;
1751
+ }
1752
+
1753
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value::-webkit-scrollbar-thumb {
1754
+ background-color: rgba(156, 163, 175, 0.4);
1755
+ border-radius: 2px;
1756
+ }
1757
+
1758
+ /* ===== Responsive: Mobile (<640px) ===== */
1759
+ @media (max-width: 640px) {
1760
+ .zenit-leaflet-popup .leaflet-popup-content-wrapper {
1761
+ border-radius: 10px;
1762
+ }
1763
+
1764
+ .zenit-leaflet-popup .leaflet-popup-close-button {
1765
+ width: 26px;
1766
+ height: 26px;
1767
+ font-size: 16px;
1768
+ margin: 6px 6px 0 0;
1769
+ }
1770
+
1771
+ .zenit-popup-card {
1772
+ padding: 12px;
1773
+ }
1774
+
1775
+ .zenit-leaflet-popup .leaflet-popup-content {
1776
+ max-height: min(65vh, 380px);
1777
+ }
1778
+
1779
+ .zenit-popup-header {
1780
+ padding-bottom: 10px;
1781
+ }
1782
+
1783
+ .zenit-popup-title {
1784
+ font-size: 13px;
1785
+ }
1786
+
1787
+ .zenit-popup-row {
1788
+ padding: 8px 0;
1789
+ }
1790
+
1791
+ .zenit-popup-label {
1792
+ font-size: 9px;
1793
+ }
1794
+
1795
+ .zenit-popup-value {
1796
+ font-size: 12px;
1797
+ }
1798
+
1799
+ .zenit-popup-row.zenit-popup-description {
1800
+ margin: 0 -12px;
1801
+ padding: 10px 12px;
1802
+ }
1803
+
1804
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value {
1805
+ font-size: 12px;
1806
+ max-height: 120px;
1807
+ }
1808
+
1809
+ .zenit-popup-pre {
1810
+ font-size: 10px;
1811
+ padding: 6px;
1812
+ }
1813
+
1814
+ .zenit-popup-empty {
1815
+ font-size: 12px;
1816
+ padding: 16px 0;
1817
+ }
1818
+ }
1819
+
1820
+ /* ===== Map tooltip styling ===== */
1821
+ .zenit-map-tooltip {
1822
+ background-color: rgba(31, 41, 55, 0.95);
1823
+ border: none;
1824
+ border-radius: 6px;
1825
+ color: #ffffff;
1826
+ font-size: 12px;
1827
+ font-weight: 500;
1828
+ padding: 6px 10px;
1829
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
1830
+ }
1831
+
1832
+ .zenit-map-tooltip::before {
1833
+ border-top-color: rgba(31, 41, 55, 0.95);
1834
+ }
1835
+
1836
+ .polygon-label-tooltip {
1837
+ z-index: 600 !important;
1838
+ }
1839
+
1840
+ .zenit-map-shell.popup-open .zenit-label-marker,
1841
+ .zenit-map-shell.popup-open .polygon-label-tooltip,
1842
+ .zenit-map-shell.popup-open .click-for-detail-hint,
1843
+ .zenit-map-shell.popup-open .zenit-map-tooltip {
1844
+ display: none !important;
1845
+ }
1846
+ `;
1847
+ function ensurePopupStyles() {
1848
+ if (typeof document === "undefined") return;
1849
+ if (document.getElementById(POPUP_STYLE_ID)) return;
1850
+ const styleTag = document.createElement("style");
1851
+ styleTag.id = POPUP_STYLE_ID;
1852
+ styleTag.textContent = ZENIT_LEAFLET_POPUP_STYLES;
1853
+ document.head.appendChild(styleTag);
1854
+ }
1855
+ function getPopupDimensions() {
1856
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
1857
+ return DESKTOP_POPUP_DIMENSIONS;
1858
+ }
1859
+ return window.matchMedia("(max-width: 640px)").matches ? MOBILE_POPUP_DIMENSIONS : DESKTOP_POPUP_DIMENSIONS;
1860
+ }
1861
+ function normalizeDescriptionValue(value) {
1862
+ if (value === void 0 || value === null) return null;
1863
+ if (typeof value === "string") {
1864
+ const trimmed = value.trim();
1865
+ return trimmed ? trimmed : null;
1866
+ }
1867
+ if (typeof value === "number" || typeof value === "boolean") {
1868
+ return String(value);
1869
+ }
1870
+ return null;
1871
+ }
1872
+ function extractDescriptionValue(properties) {
1873
+ if (!properties) return null;
1874
+ const matches = Object.entries(properties).find(
1875
+ ([key]) => DESCRIPTION_KEYS.has(key.toLowerCase())
1876
+ );
1877
+ if (!matches) return null;
1878
+ return normalizeDescriptionValue(matches[1]);
1879
+ }
1880
+ function safeJsonStringify(value) {
1881
+ try {
1882
+ const json = JSON.stringify(value, null, 2);
1883
+ if (json !== void 0) return json;
1884
+ } catch {
1885
+ }
1886
+ return String(value);
1887
+ }
1888
+ function renderPopupValue(value) {
1889
+ if (value === null || value === void 0) {
1890
+ return '<span class="zenit-popup-empty">Sin datos</span>';
1891
+ }
1892
+ if (value instanceof Date) {
1893
+ return `<span>${escapeHtml(value.toLocaleDateString("es-GT"))}</span>`;
1894
+ }
1895
+ if (typeof value === "number") {
1896
+ return `<span>${escapeHtml(value.toLocaleString("es-GT"))}</span>`;
1897
+ }
1898
+ if (typeof value === "string") {
1899
+ const trimmed = value.trim();
1900
+ const isLikelyDate = /^\d{4}-\d{2}-\d{2}/.test(trimmed) || trimmed.includes("T");
1901
+ if (isLikelyDate) {
1902
+ const parsed = Date.parse(trimmed);
1903
+ if (!Number.isNaN(parsed)) {
1904
+ return `<span>${escapeHtml(new Date(parsed).toLocaleDateString("es-GT"))}</span>`;
1905
+ }
1906
+ }
1907
+ try {
1908
+ const parsedUrl = new URL(trimmed);
1909
+ if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") {
1910
+ const safeHref = escapeHtml(parsedUrl.toString());
1911
+ return `<a class="zenit-popup-link" href="${safeHref}" target="_blank" rel="noopener noreferrer">${safeHref}</a>`;
1912
+ }
1913
+ } catch {
1914
+ }
1915
+ return `<span>${escapeHtml(trimmed || value)}</span>`;
1916
+ }
1917
+ if (typeof value === "object") {
1918
+ const json = safeJsonStringify(value);
1919
+ return `<pre class="zenit-popup-pre">${escapeHtml(json)}</pre>`;
1920
+ }
1921
+ return `<span>${escapeHtml(String(value))}</span>`;
1922
+ }
1923
+ function shouldIncludePopupEntry(key, value) {
1924
+ if (!key) return false;
1925
+ const normalized = key.trim().toLowerCase();
1926
+ if (!normalized) return false;
1927
+ if (normalized.startsWith("_")) return false;
1928
+ if (POPUP_EXCLUDED_KEYS.has(normalized)) return false;
1929
+ if (value === null || value === void 0) return false;
1930
+ if (typeof value === "string" && !value.trim()) return false;
1931
+ return true;
1932
+ }
1933
+ function isDescriptionKey(key) {
1934
+ const normalized = key.trim().toLowerCase();
1935
+ return DESCRIPTION_KEYS.has(normalized);
1936
+ }
1937
+ function extractPopupHeader(properties) {
1938
+ if (!properties) return null;
1939
+ const entry = Object.entries(properties).find(
1940
+ (candidate) => {
1941
+ const [key, value] = candidate;
1942
+ return POPUP_HEADER_KEYS.includes(key.trim().toLowerCase()) && typeof value === "string" && value.trim().length > 0;
1943
+ }
1944
+ );
1945
+ if (!entry) return null;
1946
+ return entry[1].trim();
1947
+ }
1948
+ function formatLabel(key) {
1949
+ return key.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").trim();
1950
+ }
1951
+ function createPopupContent(properties) {
1952
+ const headerText = extractPopupHeader(properties);
1953
+ const entries = Object.entries(properties).filter(([key, value]) => {
1954
+ if (!shouldIncludePopupEntry(key, value)) return false;
1955
+ if (headerText && POPUP_HEADER_KEYS.includes(key.trim().toLowerCase())) {
1956
+ return false;
1957
+ }
1958
+ return true;
1959
+ });
1960
+ if (entries.length === 0) {
1961
+ return `<div class="zenit-popup-card"><div class="zenit-popup-empty">Sin datos disponibles</div></div>`;
1962
+ }
1963
+ const descriptionEntry = entries.find(([key]) => isDescriptionKey(key));
1964
+ const otherEntries = entries.filter(([key]) => !isDescriptionKey(key));
1965
+ let rowsHtml = "";
1966
+ if (descriptionEntry) {
1967
+ const [key, value] = descriptionEntry;
1968
+ const label = escapeHtml(formatLabel(key));
1969
+ const valueHtml = renderPopupValue(value);
1970
+ rowsHtml += `
1971
+ <div class="zenit-popup-row zenit-popup-description">
1972
+ <div class="zenit-popup-label">${label}</div>
1973
+ <div class="zenit-popup-value">${valueHtml}</div>
1974
+ </div>
1975
+ `;
1976
+ }
1977
+ rowsHtml += otherEntries.map(([key, value]) => {
1978
+ const label = escapeHtml(formatLabel(key));
1979
+ const valueHtml = renderPopupValue(value);
1980
+ return `
1981
+ <div class="zenit-popup-row">
1982
+ <div class="zenit-popup-label">${label}</div>
1983
+ <div class="zenit-popup-value">${valueHtml}</div>
1984
+ </div>
1985
+ `;
1986
+ }).join("");
1987
+ const headerHtml = headerText ? `<div class="zenit-popup-header"><div class="zenit-popup-title">${escapeHtml(
1988
+ headerText
1989
+ )}</div></div>` : "";
1990
+ return `<div class="zenit-popup-card">${headerHtml}${rowsHtml}</div>`;
1991
+ }
1521
1992
  function withAlpha(color, alpha) {
1522
1993
  const trimmed = color.trim();
1523
1994
  if (trimmed.startsWith("#")) {
@@ -1586,40 +2057,39 @@ function getFeatureStyleOverrides(feature) {
1586
2057
  function buildFeaturePopupHtml(feature) {
1587
2058
  const properties = feature?.properties;
1588
2059
  if (!properties) return null;
1589
- const layerName = properties.layerName ?? properties.layer_name ?? properties.name;
1590
- const descripcion = properties.descripcion ?? properties.description;
1591
- const reservedKeys = /* @__PURE__ */ new Set([
1592
- "_style",
1593
- "layerId",
1594
- "layer_id",
1595
- "__zenit_layerId",
1596
- "layerName",
1597
- "layer_name",
1598
- "name",
1599
- "descripcion",
1600
- "description"
1601
- ]);
1602
- const extraEntries = Object.entries(properties).filter(([key, value]) => {
1603
- if (reservedKeys.has(key)) return false;
1604
- return ["string", "number", "boolean"].includes(typeof value);
1605
- }).slice(0, 5);
1606
- if (!layerName && !descripcion && extraEntries.length === 0) return null;
1607
- const parts = [];
1608
- if (layerName) {
1609
- parts.push(`<div style="font-weight:600;margin-bottom:4px;">${escapeHtml(layerName)}</div>`);
1610
- }
1611
- if (descripcion) {
1612
- parts.push(`<div style="margin-bottom:6px;">${escapeHtml(descripcion)}</div>`);
1613
- }
1614
- if (extraEntries.length > 0) {
1615
- const rows = extraEntries.map(([key, value]) => {
1616
- const label = escapeHtml(key.replace(/_/g, " "));
1617
- const val = escapeHtml(String(value));
1618
- return `<div><strong>${label}:</strong> ${val}</div>`;
1619
- }).join("");
1620
- parts.push(`<div style="font-size:12px;line-height:1.4;">${rows}</div>`);
1621
- }
1622
- return `<div>${parts.join("")}</div>`;
2060
+ const rendered = createPopupContent(properties);
2061
+ return rendered ? rendered : null;
2062
+ }
2063
+ var POINT_GEOMETRY_TYPES = /* @__PURE__ */ new Set(["Point", "MultiPoint"]);
2064
+ function getGeometryType(feature) {
2065
+ const t = feature?.geometry?.type;
2066
+ return typeof t === "string" ? t : null;
2067
+ }
2068
+ function isPointGeometry(feature) {
2069
+ const geometryType = getGeometryType(feature);
2070
+ return geometryType !== null && POINT_GEOMETRY_TYPES.has(geometryType);
2071
+ }
2072
+ function isNonPointGeometry(feature) {
2073
+ const geometryType = getGeometryType(feature);
2074
+ return geometryType !== null && !POINT_GEOMETRY_TYPES.has(geometryType);
2075
+ }
2076
+ function buildFeatureCollection(features) {
2077
+ return {
2078
+ type: "FeatureCollection",
2079
+ features
2080
+ };
2081
+ }
2082
+ function pickIntersectFeature(baseFeature, candidates) {
2083
+ if (!Array.isArray(candidates) || candidates.length === 0) return null;
2084
+ const baseId = baseFeature?.id;
2085
+ if (baseId !== void 0 && baseId !== null) {
2086
+ const matchById = candidates.find((candidate) => candidate?.id === baseId);
2087
+ if (matchById) return matchById;
2088
+ }
2089
+ const matchWithDescription = candidates.find(
2090
+ (candidate) => extractDescriptionValue(candidate?.properties)
2091
+ );
2092
+ return matchWithDescription ?? candidates[0];
1623
2093
  }
1624
2094
  function normalizeCenterTuple(center) {
1625
2095
  if (!center) return null;
@@ -1740,7 +2210,9 @@ var ZenitMap = (0, import_react.forwardRef)(({
1740
2210
  const [loadingMap, setLoadingMap] = (0, import_react.useState)(false);
1741
2211
  const [mapError, setMapError] = (0, import_react.useState)(null);
1742
2212
  const [mapInstance, setMapInstance] = (0, import_react.useState)(null);
2213
+ const [panesReady, setPanesReady] = (0, import_react.useState)(false);
1743
2214
  const [currentZoom, setCurrentZoom] = (0, import_react.useState)(initialZoom ?? DEFAULT_ZOOM);
2215
+ const [isPopupOpen, setIsPopupOpen] = (0, import_react.useState)(false);
1744
2216
  const [isMobile, setIsMobile] = (0, import_react.useState)(() => {
1745
2217
  if (typeof window === "undefined") return false;
1746
2218
  return window.matchMedia("(max-width: 768px)").matches;
@@ -1764,6 +2236,36 @@ var ZenitMap = (0, import_react.forwardRef)(({
1764
2236
  }
1765
2237
  return;
1766
2238
  }, []);
2239
+ (0, import_react.useEffect)(() => {
2240
+ if (featureInfoMode === "popup") {
2241
+ ensurePopupStyles();
2242
+ }
2243
+ }, [featureInfoMode]);
2244
+ (0, import_react.useEffect)(() => {
2245
+ if (featureInfoMode !== "popup") {
2246
+ setIsPopupOpen(false);
2247
+ }
2248
+ }, [featureInfoMode]);
2249
+ (0, import_react.useEffect)(() => {
2250
+ if (!mapInstance) return;
2251
+ const popupPane = mapInstance.getPane("popupPane");
2252
+ if (popupPane) {
2253
+ popupPane.style.zIndex = "800";
2254
+ }
2255
+ const labelsPane = mapInstance.getPane(LABELS_PANE_NAME) ?? mapInstance.createPane(LABELS_PANE_NAME);
2256
+ labelsPane.style.zIndex = "600";
2257
+ }, [mapInstance]);
2258
+ (0, import_react.useEffect)(() => {
2259
+ if (!mapInstance) return;
2260
+ const handlePopupOpen = () => setIsPopupOpen(true);
2261
+ const handlePopupClose = () => setIsPopupOpen(false);
2262
+ mapInstance.on("popupopen", handlePopupOpen);
2263
+ mapInstance.on("popupclose", handlePopupClose);
2264
+ return () => {
2265
+ mapInstance.off("popupopen", handlePopupOpen);
2266
+ mapInstance.off("popupclose", handlePopupClose);
2267
+ };
2268
+ }, [mapInstance]);
1767
2269
  const layerStyleIndex = (0, import_react.useMemo)(() => {
1768
2270
  const index = /* @__PURE__ */ new Map();
1769
2271
  (map?.mapLayers ?? []).forEach((entry) => {
@@ -2054,16 +2556,21 @@ var ZenitMap = (0, import_react.forwardRef)(({
2054
2556
  (targetMap, targetLayers) => {
2055
2557
  const baseZIndex = 400;
2056
2558
  targetLayers.forEach((layer) => {
2057
- const paneName = `zenit-layer-${layer.layerId}`;
2058
- const pane = targetMap.getPane(paneName) ?? targetMap.createPane(paneName);
2059
2559
  const order = Number.isFinite(layer.displayOrder) ? layer.displayOrder : 0;
2060
- pane.style.zIndex = String(baseZIndex + order);
2560
+ const orderOffset = Math.max(0, Math.min(order, 150));
2561
+ const fillPaneName = `zenit-layer-${layer.layerId}-fill`;
2562
+ const pointPaneName = `zenit-layer-${layer.layerId}-points`;
2563
+ const fillPane = targetMap.getPane(fillPaneName) ?? targetMap.createPane(fillPaneName);
2564
+ const pointPane = targetMap.getPane(pointPaneName) ?? targetMap.createPane(pointPaneName);
2565
+ fillPane.style.zIndex = String(baseZIndex + orderOffset);
2566
+ pointPane.style.zIndex = String(baseZIndex + orderOffset + 100);
2061
2567
  });
2062
2568
  },
2063
2569
  []
2064
2570
  );
2065
2571
  const handleMapReady = (0, import_react.useCallback)(
2066
2572
  (instance) => {
2573
+ setPanesReady(false);
2067
2574
  setMapInstance(instance);
2068
2575
  onMapReady?.(instance);
2069
2576
  },
@@ -2071,6 +2578,7 @@ var ZenitMap = (0, import_react.forwardRef)(({
2071
2578
  );
2072
2579
  (0, import_react.useEffect)(() => {
2073
2580
  if (!mapInstance) {
2581
+ setPanesReady(false);
2074
2582
  return;
2075
2583
  }
2076
2584
  if (orderedLayers.length === 0) {
@@ -2081,6 +2589,12 @@ var ZenitMap = (0, import_react.forwardRef)(({
2081
2589
  displayOrder: layer.displayOrder
2082
2590
  }));
2083
2591
  ensureLayerPanes(mapInstance, layerTargets);
2592
+ const first = layerTargets[0];
2593
+ const testPane = mapInstance.getPane(`zenit-layer-${first.layerId}-fill`);
2594
+ const labelsPane = mapInstance.getPane(LABELS_PANE_NAME);
2595
+ if (testPane && labelsPane) {
2596
+ setPanesReady(true);
2597
+ }
2084
2598
  }, [mapInstance, orderedLayers, ensureLayerPanes]);
2085
2599
  const overlayOnEachFeature = (0, import_react.useMemo)(() => {
2086
2600
  return (feature, layer) => {
@@ -2098,7 +2612,17 @@ var ZenitMap = (0, import_react.forwardRef)(({
2098
2612
  if (featureInfoMode === "popup") {
2099
2613
  const content = buildFeaturePopupHtml(feature);
2100
2614
  if (content) {
2101
- layer.bindPopup(content, { maxWidth: 320 });
2615
+ const { maxWidth, minWidth, maxHeight } = getPopupDimensions();
2616
+ layer.bindPopup(content, {
2617
+ maxWidth,
2618
+ minWidth,
2619
+ maxHeight,
2620
+ className: "zenit-leaflet-popup custom-leaflet-popup",
2621
+ autoPan: true,
2622
+ closeButton: true,
2623
+ keepInView: true,
2624
+ offset: import_leaflet.default.point(0, -24)
2625
+ });
2102
2626
  }
2103
2627
  }
2104
2628
  if (isPointFeature && layer.bindTooltip) {
@@ -2109,7 +2633,38 @@ var ZenitMap = (0, import_react.forwardRef)(({
2109
2633
  className: "zenit-map-tooltip"
2110
2634
  });
2111
2635
  }
2112
- layer.on("click", () => onFeatureClick?.(feature, layerId));
2636
+ layer.on("click", () => {
2637
+ if (featureInfoMode === "popup" && client && layerId !== void 0 && !extractDescriptionValue(feature?.properties) && feature?.geometry) {
2638
+ const trackedFeature = feature;
2639
+ if (!trackedFeature.__zenit_popup_loaded) {
2640
+ trackedFeature.__zenit_popup_loaded = true;
2641
+ client.layers.getLayerGeoJsonIntersect({
2642
+ id: layerId,
2643
+ geometry: feature.geometry
2644
+ }).then((response) => {
2645
+ const geo = extractGeoJsonFeatureCollection(response);
2646
+ const candidates = geo?.features ?? [];
2647
+ const resolved = pickIntersectFeature(feature, candidates);
2648
+ if (!resolved?.properties) return;
2649
+ const mergedProperties = {
2650
+ ...trackedFeature.properties ?? {},
2651
+ ...resolved.properties
2652
+ };
2653
+ trackedFeature.properties = mergedProperties;
2654
+ const updatedHtml = buildFeaturePopupHtml({
2655
+ ...feature,
2656
+ properties: mergedProperties
2657
+ });
2658
+ if (updatedHtml && layer.setPopupContent) {
2659
+ layer.setPopupContent(updatedHtml);
2660
+ }
2661
+ }).catch(() => {
2662
+ trackedFeature.__zenit_popup_loaded = false;
2663
+ });
2664
+ }
2665
+ }
2666
+ onFeatureClick?.(feature, layerId);
2667
+ });
2113
2668
  layer.on("mouseover", () => {
2114
2669
  if (layer instanceof import_leaflet.default.Path && originalStyle) {
2115
2670
  layer.setStyle({
@@ -2133,7 +2688,7 @@ var ZenitMap = (0, import_react.forwardRef)(({
2133
2688
  }
2134
2689
  });
2135
2690
  };
2136
- }, [featureInfoMode, onFeatureClick, onFeatureHover]);
2691
+ }, [client, featureInfoMode, onFeatureClick, onFeatureHover]);
2137
2692
  const buildLayerStyle = (layerId, baseOpacity, feature, layerType) => {
2138
2693
  const style = resolveLayerStyle(layerId);
2139
2694
  const featureStyleOverrides = getFeatureStyleOverrides(feature);
@@ -2291,68 +2846,93 @@ var ZenitMap = (0, import_react.forwardRef)(({
2291
2846
  boxSizing: "border-box"
2292
2847
  },
2293
2848
  children: [
2294
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2295
- import_react_leaflet.MapContainer,
2849
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2850
+ "div",
2296
2851
  {
2297
- center,
2298
- zoom,
2299
- style: { height: "100%", width: "100%" },
2300
- scrollWheelZoom: true,
2301
- zoomControl: false,
2302
- children: [
2303
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2304
- import_react_leaflet.TileLayer,
2305
- {
2306
- url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
2307
- attribution: "\xA9 OpenStreetMap contributors"
2308
- }
2309
- ),
2310
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_leaflet.ZoomControl, { position: "topright" }),
2311
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MapInstanceBridge, { onReady: handleMapReady }),
2312
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FitToBounds, { bbox: explicitZoomBBox ?? void 0 }),
2313
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AutoFitToBounds, { bbox: autoZoomBBox ?? void 0, enabled: !explicitZoomBBox }),
2314
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ZoomBasedOpacityHandler, { onZoomChange: handleZoomChange }),
2315
- orderedLayers.map((layerState) => {
2316
- const baseOpacity = layerState.effective?.baseOpacity ?? layerState.effective?.opacity ?? 1;
2317
- const paneName = `zenit-layer-${layerState.mapLayer.layerId}`;
2318
- const layerType = layerState.layer?.layerType ?? layerState.mapLayer.layerType ?? void 0;
2319
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2320
- import_react_leaflet.GeoJSON,
2321
- {
2322
- data: layerState.data,
2323
- pane: mapInstance?.getPane(paneName) ? paneName : void 0,
2324
- style: (feature) => buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType),
2325
- pointToLayer: (feature, latlng) => import_leaflet.default.circleMarker(latlng, {
2326
- radius: isMobile ? 8 : 6,
2327
- ...buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType)
2328
- }),
2329
- onEachFeature: overlayOnEachFeature
2330
- },
2331
- layerState.mapLayer.layerId.toString()
2332
- );
2333
- }),
2334
- overlayGeojson && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2335
- import_react_leaflet.GeoJSON,
2336
- {
2337
- data: overlayGeojson,
2338
- style: overlayStyleFunction,
2339
- onEachFeature: overlayOnEachFeature
2340
- },
2341
- "zenit-overlay-geojson"
2342
- ),
2343
- labelMarkers.map((marker) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2344
- import_react_leaflet.Marker,
2345
- {
2346
- position: marker.position,
2347
- icon: buildLabelIcon(marker.label, marker.opacity, marker.color),
2348
- interactive: false
2349
- },
2350
- marker.key
2351
- ))
2352
- ]
2353
- },
2354
- String(mapId)
2355
- ) }),
2852
+ className: `zenit-map-shell${isPopupOpen ? " popup-open" : ""}`,
2853
+ style: { flex: 1, position: "relative" },
2854
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2855
+ import_react_leaflet.MapContainer,
2856
+ {
2857
+ center,
2858
+ zoom,
2859
+ style: { height: "100%", width: "100%" },
2860
+ scrollWheelZoom: true,
2861
+ zoomControl: false,
2862
+ children: [
2863
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2864
+ import_react_leaflet.TileLayer,
2865
+ {
2866
+ url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
2867
+ attribution: "\xA9 OpenStreetMap contributors"
2868
+ }
2869
+ ),
2870
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_leaflet.ZoomControl, { position: "topright" }),
2871
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MapInstanceBridge, { onReady: handleMapReady }),
2872
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FitToBounds, { bbox: explicitZoomBBox ?? void 0 }),
2873
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AutoFitToBounds, { bbox: autoZoomBBox ?? void 0, enabled: !explicitZoomBBox }),
2874
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ZoomBasedOpacityHandler, { onZoomChange: handleZoomChange }),
2875
+ orderedLayers.map((layerState) => {
2876
+ const baseOpacity = layerState.effective?.baseOpacity ?? layerState.effective?.opacity ?? 1;
2877
+ const fillPaneName = `zenit-layer-${layerState.mapLayer.layerId}-fill`;
2878
+ const pointsPaneName = `zenit-layer-${layerState.mapLayer.layerId}-points`;
2879
+ const layerType = layerState.layer?.layerType ?? layerState.mapLayer.layerType ?? void 0;
2880
+ const data = layerState.data?.features ?? [];
2881
+ const fillFeatures = data.filter(isNonPointGeometry);
2882
+ const pointFeatures = data.filter(isPointGeometry);
2883
+ const fillData = fillFeatures.length > 0 ? buildFeatureCollection(fillFeatures) : null;
2884
+ const pointsData = pointFeatures.length > 0 ? buildFeatureCollection(pointFeatures) : null;
2885
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.default.Fragment, { children: [
2886
+ fillData && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2887
+ import_react_leaflet.GeoJSON,
2888
+ {
2889
+ data: fillData,
2890
+ pane: panesReady && mapInstance?.getPane(fillPaneName) ? fillPaneName : void 0,
2891
+ style: (feature) => buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType),
2892
+ onEachFeature: overlayOnEachFeature
2893
+ }
2894
+ ),
2895
+ pointsData && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2896
+ import_react_leaflet.GeoJSON,
2897
+ {
2898
+ data: pointsData,
2899
+ pane: panesReady && mapInstance?.getPane(pointsPaneName) ? pointsPaneName : void 0,
2900
+ pointToLayer: (feature, latlng) => import_leaflet.default.circleMarker(latlng, {
2901
+ radius: isMobile ? 8 : 6,
2902
+ ...buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType)
2903
+ }),
2904
+ onEachFeature: overlayOnEachFeature
2905
+ }
2906
+ ),
2907
+ panesReady && mapInstance?.getPane(LABELS_PANE_NAME) ? labelMarkers.filter(
2908
+ (marker) => String(marker.layerId) === String(layerState.mapLayer.layerId)
2909
+ ).map((marker) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2910
+ import_react_leaflet.Marker,
2911
+ {
2912
+ position: marker.position,
2913
+ icon: buildLabelIcon(marker.label, marker.opacity, marker.color),
2914
+ interactive: false,
2915
+ pane: LABELS_PANE_NAME
2916
+ },
2917
+ marker.key
2918
+ )) : null
2919
+ ] }, layerState.mapLayer.layerId.toString());
2920
+ }),
2921
+ overlayGeojson && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2922
+ import_react_leaflet.GeoJSON,
2923
+ {
2924
+ data: overlayGeojson,
2925
+ style: overlayStyleFunction,
2926
+ onEachFeature: overlayOnEachFeature
2927
+ },
2928
+ "zenit-overlay-geojson"
2929
+ )
2930
+ ]
2931
+ },
2932
+ String(mapId)
2933
+ )
2934
+ }
2935
+ ),
2356
2936
  showLayerPanel && decoratedLayers.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2357
2937
  "div",
2358
2938
  {
@@ -3630,9 +4210,19 @@ var FloatingChatBox = ({
3630
4210
  getAccessToken,
3631
4211
  onActionClick,
3632
4212
  onOpenChange,
3633
- hideButton
4213
+ hideButton,
4214
+ open: openProp
3634
4215
  }) => {
3635
- const [open, setOpen] = (0, import_react4.useState)(false);
4216
+ const isControlled = openProp !== void 0;
4217
+ const [internalOpen, setInternalOpen] = (0, import_react4.useState)(false);
4218
+ const open = isControlled ? openProp : internalOpen;
4219
+ const setOpen = (0, import_react4.useCallback)((value) => {
4220
+ const newValue = typeof value === "function" ? value(open) : value;
4221
+ if (!isControlled) {
4222
+ setInternalOpen(newValue);
4223
+ }
4224
+ onOpenChange?.(newValue);
4225
+ }, [isControlled, open, onOpenChange]);
3636
4226
  const [expanded, setExpanded] = (0, import_react4.useState)(false);
3637
4227
  const [messages, setMessages] = (0, import_react4.useState)([]);
3638
4228
  const [inputValue, setInputValue] = (0, import_react4.useState)("");
@@ -3649,9 +4239,6 @@ var FloatingChatBox = ({
3649
4239
  }, [accessToken, baseUrl, getAccessToken]);
3650
4240
  const { sendMessage: sendMessage2, isStreaming, streamingText, completeResponse } = useSendMessageStream(chatConfig);
3651
4241
  const canSend = Boolean(mapId) && Boolean(baseUrl) && inputValue.trim().length > 0 && !isStreaming;
3652
- (0, import_react4.useEffect)(() => {
3653
- onOpenChange?.(open);
3654
- }, [open, onOpenChange]);
3655
4242
  (0, import_react4.useEffect)(() => {
3656
4243
  if (open && isMobile) {
3657
4244
  setExpanded(true);
@@ -3804,6 +4391,13 @@ var FloatingChatBox = ({
3804
4391
  ] }, index)) })
3805
4392
  ] });
3806
4393
  };
4394
+ const handleActionClick = (0, import_react4.useCallback)((action) => {
4395
+ if (isStreaming) return;
4396
+ setOpen(false);
4397
+ requestAnimationFrame(() => {
4398
+ onActionClick?.(action);
4399
+ });
4400
+ }, [isStreaming, setOpen, onActionClick]);
3807
4401
  const renderActions = (response) => {
3808
4402
  if (!response?.suggestedActions?.length) return null;
3809
4403
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.actionsSection, children: [
@@ -3817,7 +4411,7 @@ var FloatingChatBox = ({
3817
4411
  opacity: isStreaming ? 0.5 : 1,
3818
4412
  cursor: isStreaming ? "not-allowed" : "pointer"
3819
4413
  },
3820
- onClick: () => !isStreaming && onActionClick?.(action),
4414
+ onClick: () => handleActionClick(action),
3821
4415
  disabled: isStreaming,
3822
4416
  onMouseEnter: (e) => {
3823
4417
  if (!isStreaming) {