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.
@@ -58,7 +58,7 @@ __export(react_exports, {
58
58
  module.exports = __toCommonJS(react_exports);
59
59
 
60
60
  // src/react/ZenitMap.tsx
61
- var import_react = require("react");
61
+ var import_react = __toESM(require("react"));
62
62
  var import_react_leaflet = require("react-leaflet");
63
63
  var import_leaflet = __toESM(require("leaflet"));
64
64
 
@@ -296,6 +296,7 @@ function getEffectiveLayerOpacity(baseOpacity, zoom, layerType, geometryType, op
296
296
  var import_jsx_runtime = require("react/jsx-runtime");
297
297
  var DEFAULT_CENTER = [0, 0];
298
298
  var DEFAULT_ZOOM = 3;
299
+ var LABELS_PANE_NAME = "zenit-labels-pane";
299
300
  function computeBBoxFromGeojson(geojson) {
300
301
  if (!geojson) return null;
301
302
  if (!Array.isArray(geojson.features) || geojson.features.length === 0) return null;
@@ -336,6 +337,23 @@ function mergeBBoxes(bboxes) {
336
337
  { ...first }
337
338
  );
338
339
  }
340
+ function isRecord(value) {
341
+ return typeof value === "object" && value !== null;
342
+ }
343
+ function isGeoJsonFeatureCollection(value) {
344
+ if (!isRecord(value)) return false;
345
+ const features = value.features;
346
+ if (!Array.isArray(features)) return false;
347
+ const type = value.type;
348
+ return type === void 0 || type === "FeatureCollection";
349
+ }
350
+ function extractGeoJsonFeatureCollection(value) {
351
+ if (isRecord(value) && "data" in value) {
352
+ const data = value.data;
353
+ return isGeoJsonFeatureCollection(data) ? data : null;
354
+ }
355
+ return isGeoJsonFeatureCollection(value) ? value : null;
356
+ }
339
357
  function getFeatureLayerId(feature) {
340
358
  const layerId = feature?.properties?.__zenit_layerId ?? feature?.properties?.layerId ?? feature?.properties?.layer_id;
341
359
  if (layerId === void 0 || layerId === null) return null;
@@ -344,6 +362,459 @@ function getFeatureLayerId(feature) {
344
362
  function escapeHtml(value) {
345
363
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
346
364
  }
365
+ var DESCRIPTION_KEYS = /* @__PURE__ */ new Set(["descripcion", "description"]);
366
+ var POPUP_EXCLUDED_KEYS = /* @__PURE__ */ new Set(["geom", "geometry"]);
367
+ var POPUP_HEADER_KEYS = ["nombre", "name", "title", "titulo"];
368
+ var POPUP_STYLE_ID = "zenit-leaflet-popup-styles";
369
+ var DESKTOP_POPUP_DIMENSIONS = { maxWidth: 350, minWidth: 280, maxHeight: 480 };
370
+ var MOBILE_POPUP_DIMENSIONS = { maxWidth: 280, minWidth: 240, maxHeight: 380 };
371
+ var ZENIT_LEAFLET_POPUP_STYLES = `
372
+ /* ===== Zenit Leaflet Popup - Modern Professional Styling ===== */
373
+
374
+ /* Main popup wrapper */
375
+ .zenit-leaflet-popup .leaflet-popup-content-wrapper {
376
+ border-radius: 12px;
377
+ box-shadow:
378
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
379
+ 0 2px 4px -2px rgba(0, 0, 0, 0.1),
380
+ 0 0 0 1px rgba(0, 0, 0, 0.05);
381
+ padding: 0;
382
+ background: #ffffff;
383
+ overflow: hidden;
384
+ }
385
+
386
+ /* Content area with scroll support */
387
+ .zenit-leaflet-popup .leaflet-popup-content {
388
+ margin: 0;
389
+ padding: 0;
390
+ font-size: 13px;
391
+ line-height: 1.5;
392
+ color: #374151;
393
+ min-width: 100%;
394
+ max-height: min(70vh, 480px);
395
+ overflow-y: auto;
396
+ overflow-x: hidden;
397
+ scrollbar-width: thin;
398
+ scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
399
+ }
400
+
401
+ /* Popup tip/arrow shadow */
402
+ .zenit-leaflet-popup .leaflet-popup-tip-container {
403
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
404
+ }
405
+
406
+ .zenit-leaflet-popup .leaflet-popup-tip {
407
+ background: #ffffff;
408
+ box-shadow: none;
409
+ }
410
+
411
+ /* Close button styling */
412
+ .zenit-leaflet-popup .leaflet-popup-close-button {
413
+ color: #9ca3af;
414
+ font-size: 18px;
415
+ font-weight: 400;
416
+ width: 28px;
417
+ height: 28px;
418
+ padding: 0;
419
+ margin: 8px 8px 0 0;
420
+ display: flex;
421
+ align-items: center;
422
+ justify-content: center;
423
+ border-radius: 6px;
424
+ transition: all 0.15s ease;
425
+ z-index: 10;
426
+ }
427
+
428
+ .zenit-leaflet-popup .leaflet-popup-close-button:hover {
429
+ color: #374151;
430
+ background-color: #f3f4f6;
431
+ }
432
+
433
+ .zenit-leaflet-popup .leaflet-popup-close-button:active {
434
+ background-color: #e5e7eb;
435
+ }
436
+
437
+ /* Main card container */
438
+ .zenit-popup-card {
439
+ display: flex;
440
+ flex-direction: column;
441
+ gap: 0;
442
+ padding: 16px;
443
+ }
444
+
445
+ .zenit-popup-header {
446
+ padding-bottom: 12px;
447
+ border-bottom: 1px solid #e5e7eb;
448
+ margin-bottom: 4px;
449
+ }
450
+
451
+ .zenit-popup-title {
452
+ font-size: 14px;
453
+ font-weight: 700;
454
+ color: #111827;
455
+ letter-spacing: 0.01em;
456
+ line-height: 1.4;
457
+ }
458
+
459
+ /* Individual row styling with subtle separator */
460
+ .zenit-popup-row {
461
+ display: flex;
462
+ flex-direction: column;
463
+ gap: 2px;
464
+ padding: 10px 0;
465
+ border-bottom: 1px solid #f3f4f6;
466
+ }
467
+
468
+ .zenit-popup-row:first-child {
469
+ padding-top: 0;
470
+ }
471
+
472
+ .zenit-popup-row:last-child {
473
+ border-bottom: none;
474
+ padding-bottom: 0;
475
+ }
476
+
477
+ /* Label styling - small, gray, uppercase */
478
+ .zenit-popup-label {
479
+ font-size: 10px;
480
+ font-weight: 500;
481
+ color: #9ca3af;
482
+ text-transform: uppercase;
483
+ letter-spacing: 0.05em;
484
+ line-height: 1.4;
485
+ }
486
+
487
+ /* Value styling - darker, readable */
488
+ .zenit-popup-value {
489
+ font-size: 13px;
490
+ font-weight: 400;
491
+ color: #1f2937;
492
+ overflow-wrap: break-word;
493
+ word-break: break-word;
494
+ line-height: 1.5;
495
+ }
496
+
497
+ .zenit-popup-link {
498
+ color: #2563eb;
499
+ text-decoration: underline;
500
+ font-weight: 500;
501
+ }
502
+
503
+ .zenit-popup-link:hover {
504
+ color: #1d4ed8;
505
+ }
506
+
507
+ /* Special styling for description field */
508
+ .zenit-popup-row.zenit-popup-description {
509
+ background-color: #f9fafb;
510
+ margin: 0 -16px;
511
+ padding: 12px 16px;
512
+ border-bottom: 1px solid #e5e7eb;
513
+ }
514
+
515
+ .zenit-popup-row.zenit-popup-description:first-child {
516
+ margin-top: 0;
517
+ border-radius: 0;
518
+ }
519
+
520
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value {
521
+ font-size: 13px;
522
+ line-height: 1.6;
523
+ color: #374151;
524
+ max-height: 150px;
525
+ overflow-y: auto;
526
+ padding-right: 4px;
527
+ }
528
+
529
+ /* Preformatted text (JSON objects) */
530
+ .zenit-popup-pre {
531
+ margin: 0;
532
+ font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
533
+ font-size: 11px;
534
+ white-space: pre-wrap;
535
+ word-break: break-word;
536
+ color: #4b5563;
537
+ background-color: #f9fafb;
538
+ padding: 8px;
539
+ border-radius: 6px;
540
+ border: 1px solid #e5e7eb;
541
+ }
542
+
543
+ /* Empty state styling */
544
+ .zenit-popup-empty {
545
+ font-size: 13px;
546
+ color: #9ca3af;
547
+ font-style: italic;
548
+ text-align: center;
549
+ padding: 20px 0;
550
+ }
551
+
552
+ /* Webkit scrollbar styling */
553
+ .zenit-leaflet-popup .leaflet-popup-content::-webkit-scrollbar {
554
+ width: 6px;
555
+ }
556
+
557
+ .zenit-leaflet-popup .leaflet-popup-content::-webkit-scrollbar-track {
558
+ background: transparent;
559
+ }
560
+
561
+ .zenit-leaflet-popup .leaflet-popup-content::-webkit-scrollbar-thumb {
562
+ background-color: rgba(156, 163, 175, 0.4);
563
+ border-radius: 3px;
564
+ }
565
+
566
+ .zenit-leaflet-popup .leaflet-popup-content::-webkit-scrollbar-thumb:hover {
567
+ background-color: rgba(107, 114, 128, 0.6);
568
+ }
569
+
570
+ /* Scrollbar for description field */
571
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value::-webkit-scrollbar {
572
+ width: 4px;
573
+ }
574
+
575
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value::-webkit-scrollbar-track {
576
+ background: transparent;
577
+ }
578
+
579
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value::-webkit-scrollbar-thumb {
580
+ background-color: rgba(156, 163, 175, 0.4);
581
+ border-radius: 2px;
582
+ }
583
+
584
+ /* ===== Responsive: Mobile (<640px) ===== */
585
+ @media (max-width: 640px) {
586
+ .zenit-leaflet-popup .leaflet-popup-content-wrapper {
587
+ border-radius: 10px;
588
+ }
589
+
590
+ .zenit-leaflet-popup .leaflet-popup-close-button {
591
+ width: 26px;
592
+ height: 26px;
593
+ font-size: 16px;
594
+ margin: 6px 6px 0 0;
595
+ }
596
+
597
+ .zenit-popup-card {
598
+ padding: 12px;
599
+ }
600
+
601
+ .zenit-leaflet-popup .leaflet-popup-content {
602
+ max-height: min(65vh, 380px);
603
+ }
604
+
605
+ .zenit-popup-header {
606
+ padding-bottom: 10px;
607
+ }
608
+
609
+ .zenit-popup-title {
610
+ font-size: 13px;
611
+ }
612
+
613
+ .zenit-popup-row {
614
+ padding: 8px 0;
615
+ }
616
+
617
+ .zenit-popup-label {
618
+ font-size: 9px;
619
+ }
620
+
621
+ .zenit-popup-value {
622
+ font-size: 12px;
623
+ }
624
+
625
+ .zenit-popup-row.zenit-popup-description {
626
+ margin: 0 -12px;
627
+ padding: 10px 12px;
628
+ }
629
+
630
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value {
631
+ font-size: 12px;
632
+ max-height: 120px;
633
+ }
634
+
635
+ .zenit-popup-pre {
636
+ font-size: 10px;
637
+ padding: 6px;
638
+ }
639
+
640
+ .zenit-popup-empty {
641
+ font-size: 12px;
642
+ padding: 16px 0;
643
+ }
644
+ }
645
+
646
+ /* ===== Map tooltip styling ===== */
647
+ .zenit-map-tooltip {
648
+ background-color: rgba(31, 41, 55, 0.95);
649
+ border: none;
650
+ border-radius: 6px;
651
+ color: #ffffff;
652
+ font-size: 12px;
653
+ font-weight: 500;
654
+ padding: 6px 10px;
655
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
656
+ }
657
+
658
+ .zenit-map-tooltip::before {
659
+ border-top-color: rgba(31, 41, 55, 0.95);
660
+ }
661
+
662
+ .polygon-label-tooltip {
663
+ z-index: 600 !important;
664
+ }
665
+
666
+ .zenit-map-shell.popup-open .zenit-label-marker,
667
+ .zenit-map-shell.popup-open .polygon-label-tooltip,
668
+ .zenit-map-shell.popup-open .click-for-detail-hint,
669
+ .zenit-map-shell.popup-open .zenit-map-tooltip {
670
+ display: none !important;
671
+ }
672
+ `;
673
+ function ensurePopupStyles() {
674
+ if (typeof document === "undefined") return;
675
+ if (document.getElementById(POPUP_STYLE_ID)) return;
676
+ const styleTag = document.createElement("style");
677
+ styleTag.id = POPUP_STYLE_ID;
678
+ styleTag.textContent = ZENIT_LEAFLET_POPUP_STYLES;
679
+ document.head.appendChild(styleTag);
680
+ }
681
+ function getPopupDimensions() {
682
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
683
+ return DESKTOP_POPUP_DIMENSIONS;
684
+ }
685
+ return window.matchMedia("(max-width: 640px)").matches ? MOBILE_POPUP_DIMENSIONS : DESKTOP_POPUP_DIMENSIONS;
686
+ }
687
+ function normalizeDescriptionValue(value) {
688
+ if (value === void 0 || value === null) return null;
689
+ if (typeof value === "string") {
690
+ const trimmed = value.trim();
691
+ return trimmed ? trimmed : null;
692
+ }
693
+ if (typeof value === "number" || typeof value === "boolean") {
694
+ return String(value);
695
+ }
696
+ return null;
697
+ }
698
+ function extractDescriptionValue(properties) {
699
+ if (!properties) return null;
700
+ const matches = Object.entries(properties).find(
701
+ ([key]) => DESCRIPTION_KEYS.has(key.toLowerCase())
702
+ );
703
+ if (!matches) return null;
704
+ return normalizeDescriptionValue(matches[1]);
705
+ }
706
+ function safeJsonStringify(value) {
707
+ try {
708
+ const json = JSON.stringify(value, null, 2);
709
+ if (json !== void 0) return json;
710
+ } catch {
711
+ }
712
+ return String(value);
713
+ }
714
+ function renderPopupValue(value) {
715
+ if (value === null || value === void 0) {
716
+ return '<span class="zenit-popup-empty">Sin datos</span>';
717
+ }
718
+ if (value instanceof Date) {
719
+ return `<span>${escapeHtml(value.toLocaleDateString("es-GT"))}</span>`;
720
+ }
721
+ if (typeof value === "number") {
722
+ return `<span>${escapeHtml(value.toLocaleString("es-GT"))}</span>`;
723
+ }
724
+ if (typeof value === "string") {
725
+ const trimmed = value.trim();
726
+ const isLikelyDate = /^\d{4}-\d{2}-\d{2}/.test(trimmed) || trimmed.includes("T");
727
+ if (isLikelyDate) {
728
+ const parsed = Date.parse(trimmed);
729
+ if (!Number.isNaN(parsed)) {
730
+ return `<span>${escapeHtml(new Date(parsed).toLocaleDateString("es-GT"))}</span>`;
731
+ }
732
+ }
733
+ try {
734
+ const parsedUrl = new URL(trimmed);
735
+ if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:") {
736
+ const safeHref = escapeHtml(parsedUrl.toString());
737
+ return `<a class="zenit-popup-link" href="${safeHref}" target="_blank" rel="noopener noreferrer">${safeHref}</a>`;
738
+ }
739
+ } catch {
740
+ }
741
+ return `<span>${escapeHtml(trimmed || value)}</span>`;
742
+ }
743
+ if (typeof value === "object") {
744
+ const json = safeJsonStringify(value);
745
+ return `<pre class="zenit-popup-pre">${escapeHtml(json)}</pre>`;
746
+ }
747
+ return `<span>${escapeHtml(String(value))}</span>`;
748
+ }
749
+ function shouldIncludePopupEntry(key, value) {
750
+ if (!key) return false;
751
+ const normalized = key.trim().toLowerCase();
752
+ if (!normalized) return false;
753
+ if (normalized.startsWith("_")) return false;
754
+ if (POPUP_EXCLUDED_KEYS.has(normalized)) return false;
755
+ if (value === null || value === void 0) return false;
756
+ if (typeof value === "string" && !value.trim()) return false;
757
+ return true;
758
+ }
759
+ function isDescriptionKey(key) {
760
+ const normalized = key.trim().toLowerCase();
761
+ return DESCRIPTION_KEYS.has(normalized);
762
+ }
763
+ function extractPopupHeader(properties) {
764
+ if (!properties) return null;
765
+ const entry = Object.entries(properties).find(
766
+ (candidate) => {
767
+ const [key, value] = candidate;
768
+ return POPUP_HEADER_KEYS.includes(key.trim().toLowerCase()) && typeof value === "string" && value.trim().length > 0;
769
+ }
770
+ );
771
+ if (!entry) return null;
772
+ return entry[1].trim();
773
+ }
774
+ function formatLabel(key) {
775
+ return key.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").trim();
776
+ }
777
+ function createPopupContent(properties) {
778
+ const headerText = extractPopupHeader(properties);
779
+ const entries = Object.entries(properties).filter(([key, value]) => {
780
+ if (!shouldIncludePopupEntry(key, value)) return false;
781
+ if (headerText && POPUP_HEADER_KEYS.includes(key.trim().toLowerCase())) {
782
+ return false;
783
+ }
784
+ return true;
785
+ });
786
+ if (entries.length === 0) {
787
+ return `<div class="zenit-popup-card"><div class="zenit-popup-empty">Sin datos disponibles</div></div>`;
788
+ }
789
+ const descriptionEntry = entries.find(([key]) => isDescriptionKey(key));
790
+ const otherEntries = entries.filter(([key]) => !isDescriptionKey(key));
791
+ let rowsHtml = "";
792
+ if (descriptionEntry) {
793
+ const [key, value] = descriptionEntry;
794
+ const label = escapeHtml(formatLabel(key));
795
+ const valueHtml = renderPopupValue(value);
796
+ rowsHtml += `
797
+ <div class="zenit-popup-row zenit-popup-description">
798
+ <div class="zenit-popup-label">${label}</div>
799
+ <div class="zenit-popup-value">${valueHtml}</div>
800
+ </div>
801
+ `;
802
+ }
803
+ rowsHtml += otherEntries.map(([key, value]) => {
804
+ const label = escapeHtml(formatLabel(key));
805
+ const valueHtml = renderPopupValue(value);
806
+ return `
807
+ <div class="zenit-popup-row">
808
+ <div class="zenit-popup-label">${label}</div>
809
+ <div class="zenit-popup-value">${valueHtml}</div>
810
+ </div>
811
+ `;
812
+ }).join("");
813
+ const headerHtml = headerText ? `<div class="zenit-popup-header"><div class="zenit-popup-title">${escapeHtml(
814
+ headerText
815
+ )}</div></div>` : "";
816
+ return `<div class="zenit-popup-card">${headerHtml}${rowsHtml}</div>`;
817
+ }
347
818
  function withAlpha(color, alpha) {
348
819
  const trimmed = color.trim();
349
820
  if (trimmed.startsWith("#")) {
@@ -412,40 +883,39 @@ function getFeatureStyleOverrides(feature) {
412
883
  function buildFeaturePopupHtml(feature) {
413
884
  const properties = feature?.properties;
414
885
  if (!properties) return null;
415
- const layerName = properties.layerName ?? properties.layer_name ?? properties.name;
416
- const descripcion = properties.descripcion ?? properties.description;
417
- const reservedKeys = /* @__PURE__ */ new Set([
418
- "_style",
419
- "layerId",
420
- "layer_id",
421
- "__zenit_layerId",
422
- "layerName",
423
- "layer_name",
424
- "name",
425
- "descripcion",
426
- "description"
427
- ]);
428
- const extraEntries = Object.entries(properties).filter(([key, value]) => {
429
- if (reservedKeys.has(key)) return false;
430
- return ["string", "number", "boolean"].includes(typeof value);
431
- }).slice(0, 5);
432
- if (!layerName && !descripcion && extraEntries.length === 0) return null;
433
- const parts = [];
434
- if (layerName) {
435
- parts.push(`<div style="font-weight:600;margin-bottom:4px;">${escapeHtml(layerName)}</div>`);
436
- }
437
- if (descripcion) {
438
- parts.push(`<div style="margin-bottom:6px;">${escapeHtml(descripcion)}</div>`);
439
- }
440
- if (extraEntries.length > 0) {
441
- const rows = extraEntries.map(([key, value]) => {
442
- const label = escapeHtml(key.replace(/_/g, " "));
443
- const val = escapeHtml(String(value));
444
- return `<div><strong>${label}:</strong> ${val}</div>`;
445
- }).join("");
446
- parts.push(`<div style="font-size:12px;line-height:1.4;">${rows}</div>`);
447
- }
448
- return `<div>${parts.join("")}</div>`;
886
+ const rendered = createPopupContent(properties);
887
+ return rendered ? rendered : null;
888
+ }
889
+ var POINT_GEOMETRY_TYPES = /* @__PURE__ */ new Set(["Point", "MultiPoint"]);
890
+ function getGeometryType(feature) {
891
+ const t = feature?.geometry?.type;
892
+ return typeof t === "string" ? t : null;
893
+ }
894
+ function isPointGeometry(feature) {
895
+ const geometryType = getGeometryType(feature);
896
+ return geometryType !== null && POINT_GEOMETRY_TYPES.has(geometryType);
897
+ }
898
+ function isNonPointGeometry(feature) {
899
+ const geometryType = getGeometryType(feature);
900
+ return geometryType !== null && !POINT_GEOMETRY_TYPES.has(geometryType);
901
+ }
902
+ function buildFeatureCollection(features) {
903
+ return {
904
+ type: "FeatureCollection",
905
+ features
906
+ };
907
+ }
908
+ function pickIntersectFeature(baseFeature, candidates) {
909
+ if (!Array.isArray(candidates) || candidates.length === 0) return null;
910
+ const baseId = baseFeature?.id;
911
+ if (baseId !== void 0 && baseId !== null) {
912
+ const matchById = candidates.find((candidate) => candidate?.id === baseId);
913
+ if (matchById) return matchById;
914
+ }
915
+ const matchWithDescription = candidates.find(
916
+ (candidate) => extractDescriptionValue(candidate?.properties)
917
+ );
918
+ return matchWithDescription ?? candidates[0];
449
919
  }
450
920
  function normalizeCenterTuple(center) {
451
921
  if (!center) return null;
@@ -566,7 +1036,9 @@ var ZenitMap = (0, import_react.forwardRef)(({
566
1036
  const [loadingMap, setLoadingMap] = (0, import_react.useState)(false);
567
1037
  const [mapError, setMapError] = (0, import_react.useState)(null);
568
1038
  const [mapInstance, setMapInstance] = (0, import_react.useState)(null);
1039
+ const [panesReady, setPanesReady] = (0, import_react.useState)(false);
569
1040
  const [currentZoom, setCurrentZoom] = (0, import_react.useState)(initialZoom ?? DEFAULT_ZOOM);
1041
+ const [isPopupOpen, setIsPopupOpen] = (0, import_react.useState)(false);
570
1042
  const [isMobile, setIsMobile] = (0, import_react.useState)(() => {
571
1043
  if (typeof window === "undefined") return false;
572
1044
  return window.matchMedia("(max-width: 768px)").matches;
@@ -590,6 +1062,36 @@ var ZenitMap = (0, import_react.forwardRef)(({
590
1062
  }
591
1063
  return;
592
1064
  }, []);
1065
+ (0, import_react.useEffect)(() => {
1066
+ if (featureInfoMode === "popup") {
1067
+ ensurePopupStyles();
1068
+ }
1069
+ }, [featureInfoMode]);
1070
+ (0, import_react.useEffect)(() => {
1071
+ if (featureInfoMode !== "popup") {
1072
+ setIsPopupOpen(false);
1073
+ }
1074
+ }, [featureInfoMode]);
1075
+ (0, import_react.useEffect)(() => {
1076
+ if (!mapInstance) return;
1077
+ const popupPane = mapInstance.getPane("popupPane");
1078
+ if (popupPane) {
1079
+ popupPane.style.zIndex = "800";
1080
+ }
1081
+ const labelsPane = mapInstance.getPane(LABELS_PANE_NAME) ?? mapInstance.createPane(LABELS_PANE_NAME);
1082
+ labelsPane.style.zIndex = "600";
1083
+ }, [mapInstance]);
1084
+ (0, import_react.useEffect)(() => {
1085
+ if (!mapInstance) return;
1086
+ const handlePopupOpen = () => setIsPopupOpen(true);
1087
+ const handlePopupClose = () => setIsPopupOpen(false);
1088
+ mapInstance.on("popupopen", handlePopupOpen);
1089
+ mapInstance.on("popupclose", handlePopupClose);
1090
+ return () => {
1091
+ mapInstance.off("popupopen", handlePopupOpen);
1092
+ mapInstance.off("popupclose", handlePopupClose);
1093
+ };
1094
+ }, [mapInstance]);
593
1095
  const layerStyleIndex = (0, import_react.useMemo)(() => {
594
1096
  const index = /* @__PURE__ */ new Map();
595
1097
  (map?.mapLayers ?? []).forEach((entry) => {
@@ -880,16 +1382,21 @@ var ZenitMap = (0, import_react.forwardRef)(({
880
1382
  (targetMap, targetLayers) => {
881
1383
  const baseZIndex = 400;
882
1384
  targetLayers.forEach((layer) => {
883
- const paneName = `zenit-layer-${layer.layerId}`;
884
- const pane = targetMap.getPane(paneName) ?? targetMap.createPane(paneName);
885
1385
  const order = Number.isFinite(layer.displayOrder) ? layer.displayOrder : 0;
886
- pane.style.zIndex = String(baseZIndex + order);
1386
+ const orderOffset = Math.max(0, Math.min(order, 150));
1387
+ const fillPaneName = `zenit-layer-${layer.layerId}-fill`;
1388
+ const pointPaneName = `zenit-layer-${layer.layerId}-points`;
1389
+ const fillPane = targetMap.getPane(fillPaneName) ?? targetMap.createPane(fillPaneName);
1390
+ const pointPane = targetMap.getPane(pointPaneName) ?? targetMap.createPane(pointPaneName);
1391
+ fillPane.style.zIndex = String(baseZIndex + orderOffset);
1392
+ pointPane.style.zIndex = String(baseZIndex + orderOffset + 100);
887
1393
  });
888
1394
  },
889
1395
  []
890
1396
  );
891
1397
  const handleMapReady = (0, import_react.useCallback)(
892
1398
  (instance) => {
1399
+ setPanesReady(false);
893
1400
  setMapInstance(instance);
894
1401
  onMapReady?.(instance);
895
1402
  },
@@ -897,6 +1404,7 @@ var ZenitMap = (0, import_react.forwardRef)(({
897
1404
  );
898
1405
  (0, import_react.useEffect)(() => {
899
1406
  if (!mapInstance) {
1407
+ setPanesReady(false);
900
1408
  return;
901
1409
  }
902
1410
  if (orderedLayers.length === 0) {
@@ -907,6 +1415,12 @@ var ZenitMap = (0, import_react.forwardRef)(({
907
1415
  displayOrder: layer.displayOrder
908
1416
  }));
909
1417
  ensureLayerPanes(mapInstance, layerTargets);
1418
+ const first = layerTargets[0];
1419
+ const testPane = mapInstance.getPane(`zenit-layer-${first.layerId}-fill`);
1420
+ const labelsPane = mapInstance.getPane(LABELS_PANE_NAME);
1421
+ if (testPane && labelsPane) {
1422
+ setPanesReady(true);
1423
+ }
910
1424
  }, [mapInstance, orderedLayers, ensureLayerPanes]);
911
1425
  const overlayOnEachFeature = (0, import_react.useMemo)(() => {
912
1426
  return (feature, layer) => {
@@ -924,7 +1438,17 @@ var ZenitMap = (0, import_react.forwardRef)(({
924
1438
  if (featureInfoMode === "popup") {
925
1439
  const content = buildFeaturePopupHtml(feature);
926
1440
  if (content) {
927
- layer.bindPopup(content, { maxWidth: 320 });
1441
+ const { maxWidth, minWidth, maxHeight } = getPopupDimensions();
1442
+ layer.bindPopup(content, {
1443
+ maxWidth,
1444
+ minWidth,
1445
+ maxHeight,
1446
+ className: "zenit-leaflet-popup custom-leaflet-popup",
1447
+ autoPan: true,
1448
+ closeButton: true,
1449
+ keepInView: true,
1450
+ offset: import_leaflet.default.point(0, -24)
1451
+ });
928
1452
  }
929
1453
  }
930
1454
  if (isPointFeature && layer.bindTooltip) {
@@ -935,7 +1459,38 @@ var ZenitMap = (0, import_react.forwardRef)(({
935
1459
  className: "zenit-map-tooltip"
936
1460
  });
937
1461
  }
938
- layer.on("click", () => onFeatureClick?.(feature, layerId));
1462
+ layer.on("click", () => {
1463
+ if (featureInfoMode === "popup" && client && layerId !== void 0 && !extractDescriptionValue(feature?.properties) && feature?.geometry) {
1464
+ const trackedFeature = feature;
1465
+ if (!trackedFeature.__zenit_popup_loaded) {
1466
+ trackedFeature.__zenit_popup_loaded = true;
1467
+ client.layers.getLayerGeoJsonIntersect({
1468
+ id: layerId,
1469
+ geometry: feature.geometry
1470
+ }).then((response) => {
1471
+ const geo = extractGeoJsonFeatureCollection(response);
1472
+ const candidates = geo?.features ?? [];
1473
+ const resolved = pickIntersectFeature(feature, candidates);
1474
+ if (!resolved?.properties) return;
1475
+ const mergedProperties = {
1476
+ ...trackedFeature.properties ?? {},
1477
+ ...resolved.properties
1478
+ };
1479
+ trackedFeature.properties = mergedProperties;
1480
+ const updatedHtml = buildFeaturePopupHtml({
1481
+ ...feature,
1482
+ properties: mergedProperties
1483
+ });
1484
+ if (updatedHtml && layer.setPopupContent) {
1485
+ layer.setPopupContent(updatedHtml);
1486
+ }
1487
+ }).catch(() => {
1488
+ trackedFeature.__zenit_popup_loaded = false;
1489
+ });
1490
+ }
1491
+ }
1492
+ onFeatureClick?.(feature, layerId);
1493
+ });
939
1494
  layer.on("mouseover", () => {
940
1495
  if (layer instanceof import_leaflet.default.Path && originalStyle) {
941
1496
  layer.setStyle({
@@ -959,7 +1514,7 @@ var ZenitMap = (0, import_react.forwardRef)(({
959
1514
  }
960
1515
  });
961
1516
  };
962
- }, [featureInfoMode, onFeatureClick, onFeatureHover]);
1517
+ }, [client, featureInfoMode, onFeatureClick, onFeatureHover]);
963
1518
  const buildLayerStyle = (layerId, baseOpacity, feature, layerType) => {
964
1519
  const style = resolveLayerStyle(layerId);
965
1520
  const featureStyleOverrides = getFeatureStyleOverrides(feature);
@@ -1117,68 +1672,93 @@ var ZenitMap = (0, import_react.forwardRef)(({
1117
1672
  boxSizing: "border-box"
1118
1673
  },
1119
1674
  children: [
1120
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1121
- import_react_leaflet.MapContainer,
1675
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1676
+ "div",
1122
1677
  {
1123
- center,
1124
- zoom,
1125
- style: { height: "100%", width: "100%" },
1126
- scrollWheelZoom: true,
1127
- zoomControl: false,
1128
- children: [
1129
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1130
- import_react_leaflet.TileLayer,
1131
- {
1132
- url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
1133
- attribution: "\xA9 OpenStreetMap contributors"
1134
- }
1135
- ),
1136
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_leaflet.ZoomControl, { position: "topright" }),
1137
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MapInstanceBridge, { onReady: handleMapReady }),
1138
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FitToBounds, { bbox: explicitZoomBBox ?? void 0 }),
1139
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AutoFitToBounds, { bbox: autoZoomBBox ?? void 0, enabled: !explicitZoomBBox }),
1140
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ZoomBasedOpacityHandler, { onZoomChange: handleZoomChange }),
1141
- orderedLayers.map((layerState) => {
1142
- const baseOpacity = layerState.effective?.baseOpacity ?? layerState.effective?.opacity ?? 1;
1143
- const paneName = `zenit-layer-${layerState.mapLayer.layerId}`;
1144
- const layerType = layerState.layer?.layerType ?? layerState.mapLayer.layerType ?? void 0;
1145
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1146
- import_react_leaflet.GeoJSON,
1147
- {
1148
- data: layerState.data,
1149
- pane: mapInstance?.getPane(paneName) ? paneName : void 0,
1150
- style: (feature) => buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType),
1151
- pointToLayer: (feature, latlng) => import_leaflet.default.circleMarker(latlng, {
1152
- radius: isMobile ? 8 : 6,
1153
- ...buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType)
1154
- }),
1155
- onEachFeature: overlayOnEachFeature
1156
- },
1157
- layerState.mapLayer.layerId.toString()
1158
- );
1159
- }),
1160
- overlayGeojson && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1161
- import_react_leaflet.GeoJSON,
1162
- {
1163
- data: overlayGeojson,
1164
- style: overlayStyleFunction,
1165
- onEachFeature: overlayOnEachFeature
1166
- },
1167
- "zenit-overlay-geojson"
1168
- ),
1169
- labelMarkers.map((marker) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1170
- import_react_leaflet.Marker,
1171
- {
1172
- position: marker.position,
1173
- icon: buildLabelIcon(marker.label, marker.opacity, marker.color),
1174
- interactive: false
1175
- },
1176
- marker.key
1177
- ))
1178
- ]
1179
- },
1180
- String(mapId)
1181
- ) }),
1678
+ className: `zenit-map-shell${isPopupOpen ? " popup-open" : ""}`,
1679
+ style: { flex: 1, position: "relative" },
1680
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1681
+ import_react_leaflet.MapContainer,
1682
+ {
1683
+ center,
1684
+ zoom,
1685
+ style: { height: "100%", width: "100%" },
1686
+ scrollWheelZoom: true,
1687
+ zoomControl: false,
1688
+ children: [
1689
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1690
+ import_react_leaflet.TileLayer,
1691
+ {
1692
+ url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
1693
+ attribution: "\xA9 OpenStreetMap contributors"
1694
+ }
1695
+ ),
1696
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_leaflet.ZoomControl, { position: "topright" }),
1697
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MapInstanceBridge, { onReady: handleMapReady }),
1698
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FitToBounds, { bbox: explicitZoomBBox ?? void 0 }),
1699
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AutoFitToBounds, { bbox: autoZoomBBox ?? void 0, enabled: !explicitZoomBBox }),
1700
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ZoomBasedOpacityHandler, { onZoomChange: handleZoomChange }),
1701
+ orderedLayers.map((layerState) => {
1702
+ const baseOpacity = layerState.effective?.baseOpacity ?? layerState.effective?.opacity ?? 1;
1703
+ const fillPaneName = `zenit-layer-${layerState.mapLayer.layerId}-fill`;
1704
+ const pointsPaneName = `zenit-layer-${layerState.mapLayer.layerId}-points`;
1705
+ const layerType = layerState.layer?.layerType ?? layerState.mapLayer.layerType ?? void 0;
1706
+ const data = layerState.data?.features ?? [];
1707
+ const fillFeatures = data.filter(isNonPointGeometry);
1708
+ const pointFeatures = data.filter(isPointGeometry);
1709
+ const fillData = fillFeatures.length > 0 ? buildFeatureCollection(fillFeatures) : null;
1710
+ const pointsData = pointFeatures.length > 0 ? buildFeatureCollection(pointFeatures) : null;
1711
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.default.Fragment, { children: [
1712
+ fillData && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1713
+ import_react_leaflet.GeoJSON,
1714
+ {
1715
+ data: fillData,
1716
+ pane: panesReady && mapInstance?.getPane(fillPaneName) ? fillPaneName : void 0,
1717
+ style: (feature) => buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType),
1718
+ onEachFeature: overlayOnEachFeature
1719
+ }
1720
+ ),
1721
+ pointsData && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1722
+ import_react_leaflet.GeoJSON,
1723
+ {
1724
+ data: pointsData,
1725
+ pane: panesReady && mapInstance?.getPane(pointsPaneName) ? pointsPaneName : void 0,
1726
+ pointToLayer: (feature, latlng) => import_leaflet.default.circleMarker(latlng, {
1727
+ radius: isMobile ? 8 : 6,
1728
+ ...buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType)
1729
+ }),
1730
+ onEachFeature: overlayOnEachFeature
1731
+ }
1732
+ ),
1733
+ panesReady && mapInstance?.getPane(LABELS_PANE_NAME) ? labelMarkers.filter(
1734
+ (marker) => String(marker.layerId) === String(layerState.mapLayer.layerId)
1735
+ ).map((marker) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1736
+ import_react_leaflet.Marker,
1737
+ {
1738
+ position: marker.position,
1739
+ icon: buildLabelIcon(marker.label, marker.opacity, marker.color),
1740
+ interactive: false,
1741
+ pane: LABELS_PANE_NAME
1742
+ },
1743
+ marker.key
1744
+ )) : null
1745
+ ] }, layerState.mapLayer.layerId.toString());
1746
+ }),
1747
+ overlayGeojson && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1748
+ import_react_leaflet.GeoJSON,
1749
+ {
1750
+ data: overlayGeojson,
1751
+ style: overlayStyleFunction,
1752
+ onEachFeature: overlayOnEachFeature
1753
+ },
1754
+ "zenit-overlay-geojson"
1755
+ )
1756
+ ]
1757
+ },
1758
+ String(mapId)
1759
+ )
1760
+ }
1761
+ ),
1182
1762
  showLayerPanel && decoratedLayers.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1183
1763
  "div",
1184
1764
  {
@@ -2586,9 +3166,19 @@ var FloatingChatBox = ({
2586
3166
  getAccessToken,
2587
3167
  onActionClick,
2588
3168
  onOpenChange,
2589
- hideButton
3169
+ hideButton,
3170
+ open: openProp
2590
3171
  }) => {
2591
- const [open, setOpen] = (0, import_react4.useState)(false);
3172
+ const isControlled = openProp !== void 0;
3173
+ const [internalOpen, setInternalOpen] = (0, import_react4.useState)(false);
3174
+ const open = isControlled ? openProp : internalOpen;
3175
+ const setOpen = (0, import_react4.useCallback)((value) => {
3176
+ const newValue = typeof value === "function" ? value(open) : value;
3177
+ if (!isControlled) {
3178
+ setInternalOpen(newValue);
3179
+ }
3180
+ onOpenChange?.(newValue);
3181
+ }, [isControlled, open, onOpenChange]);
2592
3182
  const [expanded, setExpanded] = (0, import_react4.useState)(false);
2593
3183
  const [messages, setMessages] = (0, import_react4.useState)([]);
2594
3184
  const [inputValue, setInputValue] = (0, import_react4.useState)("");
@@ -2605,9 +3195,6 @@ var FloatingChatBox = ({
2605
3195
  }, [accessToken, baseUrl, getAccessToken]);
2606
3196
  const { sendMessage: sendMessage2, isStreaming, streamingText, completeResponse } = useSendMessageStream(chatConfig);
2607
3197
  const canSend = Boolean(mapId) && Boolean(baseUrl) && inputValue.trim().length > 0 && !isStreaming;
2608
- (0, import_react4.useEffect)(() => {
2609
- onOpenChange?.(open);
2610
- }, [open, onOpenChange]);
2611
3198
  (0, import_react4.useEffect)(() => {
2612
3199
  if (open && isMobile) {
2613
3200
  setExpanded(true);
@@ -2760,6 +3347,13 @@ var FloatingChatBox = ({
2760
3347
  ] }, index)) })
2761
3348
  ] });
2762
3349
  };
3350
+ const handleActionClick = (0, import_react4.useCallback)((action) => {
3351
+ if (isStreaming) return;
3352
+ setOpen(false);
3353
+ requestAnimationFrame(() => {
3354
+ onActionClick?.(action);
3355
+ });
3356
+ }, [isStreaming, setOpen, onActionClick]);
2763
3357
  const renderActions = (response) => {
2764
3358
  if (!response?.suggestedActions?.length) return null;
2765
3359
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.actionsSection, children: [
@@ -2773,7 +3367,7 @@ var FloatingChatBox = ({
2773
3367
  opacity: isStreaming ? 0.5 : 1,
2774
3368
  cursor: isStreaming ? "not-allowed" : "pointer"
2775
3369
  },
2776
- onClick: () => !isStreaming && onActionClick?.(action),
3370
+ onClick: () => handleActionClick(action),
2777
3371
  disabled: isStreaming,
2778
3372
  onMouseEnter: (e) => {
2779
3373
  if (!isStreaming) {