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.
@@ -1,5 +1,5 @@
1
1
  // src/react/ZenitMap.tsx
2
- import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, forwardRef } from "react";
2
+ import React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, forwardRef } from "react";
3
3
  import { GeoJSON, MapContainer, Marker, TileLayer, ZoomControl, useMap } from "react-leaflet";
4
4
  import L from "leaflet";
5
5
 
@@ -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;
@@ -297,6 +315,459 @@ function getFeatureLayerId(feature) {
297
315
  function escapeHtml(value) {
298
316
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
299
317
  }
318
+ var DESCRIPTION_KEYS = /* @__PURE__ */ new Set(["descripcion", "description"]);
319
+ var POPUP_EXCLUDED_KEYS = /* @__PURE__ */ new Set(["geom", "geometry"]);
320
+ var POPUP_HEADER_KEYS = ["nombre", "name", "title", "titulo"];
321
+ var POPUP_STYLE_ID = "zenit-leaflet-popup-styles";
322
+ var DESKTOP_POPUP_DIMENSIONS = { maxWidth: 350, minWidth: 280, maxHeight: 480 };
323
+ var MOBILE_POPUP_DIMENSIONS = { maxWidth: 280, minWidth: 240, maxHeight: 380 };
324
+ var ZENIT_LEAFLET_POPUP_STYLES = `
325
+ /* ===== Zenit Leaflet Popup - Modern Professional Styling ===== */
326
+
327
+ /* Main popup wrapper */
328
+ .zenit-leaflet-popup .leaflet-popup-content-wrapper {
329
+ border-radius: 12px;
330
+ box-shadow:
331
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
332
+ 0 2px 4px -2px rgba(0, 0, 0, 0.1),
333
+ 0 0 0 1px rgba(0, 0, 0, 0.05);
334
+ padding: 0;
335
+ background: #ffffff;
336
+ overflow: hidden;
337
+ }
338
+
339
+ /* Content area with scroll support */
340
+ .zenit-leaflet-popup .leaflet-popup-content {
341
+ margin: 0;
342
+ padding: 0;
343
+ font-size: 13px;
344
+ line-height: 1.5;
345
+ color: #374151;
346
+ min-width: 100%;
347
+ max-height: min(70vh, 480px);
348
+ overflow-y: auto;
349
+ overflow-x: hidden;
350
+ scrollbar-width: thin;
351
+ scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
352
+ }
353
+
354
+ /* Popup tip/arrow shadow */
355
+ .zenit-leaflet-popup .leaflet-popup-tip-container {
356
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
357
+ }
358
+
359
+ .zenit-leaflet-popup .leaflet-popup-tip {
360
+ background: #ffffff;
361
+ box-shadow: none;
362
+ }
363
+
364
+ /* Close button styling */
365
+ .zenit-leaflet-popup .leaflet-popup-close-button {
366
+ color: #9ca3af;
367
+ font-size: 18px;
368
+ font-weight: 400;
369
+ width: 28px;
370
+ height: 28px;
371
+ padding: 0;
372
+ margin: 8px 8px 0 0;
373
+ display: flex;
374
+ align-items: center;
375
+ justify-content: center;
376
+ border-radius: 6px;
377
+ transition: all 0.15s ease;
378
+ z-index: 10;
379
+ }
380
+
381
+ .zenit-leaflet-popup .leaflet-popup-close-button:hover {
382
+ color: #374151;
383
+ background-color: #f3f4f6;
384
+ }
385
+
386
+ .zenit-leaflet-popup .leaflet-popup-close-button:active {
387
+ background-color: #e5e7eb;
388
+ }
389
+
390
+ /* Main card container */
391
+ .zenit-popup-card {
392
+ display: flex;
393
+ flex-direction: column;
394
+ gap: 0;
395
+ padding: 16px;
396
+ }
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
+
412
+ /* Individual row styling with subtle separator */
413
+ .zenit-popup-row {
414
+ display: flex;
415
+ flex-direction: column;
416
+ gap: 2px;
417
+ padding: 10px 0;
418
+ border-bottom: 1px solid #f3f4f6;
419
+ }
420
+
421
+ .zenit-popup-row:first-child {
422
+ padding-top: 0;
423
+ }
424
+
425
+ .zenit-popup-row:last-child {
426
+ border-bottom: none;
427
+ padding-bottom: 0;
428
+ }
429
+
430
+ /* Label styling - small, gray, uppercase */
431
+ .zenit-popup-label {
432
+ font-size: 10px;
433
+ font-weight: 500;
434
+ color: #9ca3af;
435
+ text-transform: uppercase;
436
+ letter-spacing: 0.05em;
437
+ line-height: 1.4;
438
+ }
439
+
440
+ /* Value styling - darker, readable */
441
+ .zenit-popup-value {
442
+ font-size: 13px;
443
+ font-weight: 400;
444
+ color: #1f2937;
445
+ overflow-wrap: break-word;
446
+ word-break: break-word;
447
+ line-height: 1.5;
448
+ }
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
+
460
+ /* Special styling for description field */
461
+ .zenit-popup-row.zenit-popup-description {
462
+ background-color: #f9fafb;
463
+ margin: 0 -16px;
464
+ padding: 12px 16px;
465
+ border-bottom: 1px solid #e5e7eb;
466
+ }
467
+
468
+ .zenit-popup-row.zenit-popup-description:first-child {
469
+ margin-top: 0;
470
+ border-radius: 0;
471
+ }
472
+
473
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value {
474
+ font-size: 13px;
475
+ line-height: 1.6;
476
+ color: #374151;
477
+ max-height: 150px;
478
+ overflow-y: auto;
479
+ padding-right: 4px;
480
+ }
481
+
482
+ /* Preformatted text (JSON objects) */
483
+ .zenit-popup-pre {
484
+ margin: 0;
485
+ font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
486
+ font-size: 11px;
487
+ white-space: pre-wrap;
488
+ word-break: break-word;
489
+ color: #4b5563;
490
+ background-color: #f9fafb;
491
+ padding: 8px;
492
+ border-radius: 6px;
493
+ border: 1px solid #e5e7eb;
494
+ }
495
+
496
+ /* Empty state styling */
497
+ .zenit-popup-empty {
498
+ font-size: 13px;
499
+ color: #9ca3af;
500
+ font-style: italic;
501
+ text-align: center;
502
+ padding: 20px 0;
503
+ }
504
+
505
+ /* Webkit scrollbar styling */
506
+ .zenit-leaflet-popup .leaflet-popup-content::-webkit-scrollbar {
507
+ width: 6px;
508
+ }
509
+
510
+ .zenit-leaflet-popup .leaflet-popup-content::-webkit-scrollbar-track {
511
+ background: transparent;
512
+ }
513
+
514
+ .zenit-leaflet-popup .leaflet-popup-content::-webkit-scrollbar-thumb {
515
+ background-color: rgba(156, 163, 175, 0.4);
516
+ border-radius: 3px;
517
+ }
518
+
519
+ .zenit-leaflet-popup .leaflet-popup-content::-webkit-scrollbar-thumb:hover {
520
+ background-color: rgba(107, 114, 128, 0.6);
521
+ }
522
+
523
+ /* Scrollbar for description field */
524
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value::-webkit-scrollbar {
525
+ width: 4px;
526
+ }
527
+
528
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value::-webkit-scrollbar-track {
529
+ background: transparent;
530
+ }
531
+
532
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value::-webkit-scrollbar-thumb {
533
+ background-color: rgba(156, 163, 175, 0.4);
534
+ border-radius: 2px;
535
+ }
536
+
537
+ /* ===== Responsive: Mobile (<640px) ===== */
538
+ @media (max-width: 640px) {
539
+ .zenit-leaflet-popup .leaflet-popup-content-wrapper {
540
+ border-radius: 10px;
541
+ }
542
+
543
+ .zenit-leaflet-popup .leaflet-popup-close-button {
544
+ width: 26px;
545
+ height: 26px;
546
+ font-size: 16px;
547
+ margin: 6px 6px 0 0;
548
+ }
549
+
550
+ .zenit-popup-card {
551
+ padding: 12px;
552
+ }
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
+
566
+ .zenit-popup-row {
567
+ padding: 8px 0;
568
+ }
569
+
570
+ .zenit-popup-label {
571
+ font-size: 9px;
572
+ }
573
+
574
+ .zenit-popup-value {
575
+ font-size: 12px;
576
+ }
577
+
578
+ .zenit-popup-row.zenit-popup-description {
579
+ margin: 0 -12px;
580
+ padding: 10px 12px;
581
+ }
582
+
583
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value {
584
+ font-size: 12px;
585
+ max-height: 120px;
586
+ }
587
+
588
+ .zenit-popup-pre {
589
+ font-size: 10px;
590
+ padding: 6px;
591
+ }
592
+
593
+ .zenit-popup-empty {
594
+ font-size: 12px;
595
+ padding: 16px 0;
596
+ }
597
+ }
598
+
599
+ /* ===== Map tooltip styling ===== */
600
+ .zenit-map-tooltip {
601
+ background-color: rgba(31, 41, 55, 0.95);
602
+ border: none;
603
+ border-radius: 6px;
604
+ color: #ffffff;
605
+ font-size: 12px;
606
+ font-weight: 500;
607
+ padding: 6px 10px;
608
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
609
+ }
610
+
611
+ .zenit-map-tooltip::before {
612
+ border-top-color: rgba(31, 41, 55, 0.95);
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
+ }
625
+ `;
626
+ function ensurePopupStyles() {
627
+ if (typeof document === "undefined") return;
628
+ if (document.getElementById(POPUP_STYLE_ID)) return;
629
+ const styleTag = document.createElement("style");
630
+ styleTag.id = POPUP_STYLE_ID;
631
+ styleTag.textContent = ZENIT_LEAFLET_POPUP_STYLES;
632
+ document.head.appendChild(styleTag);
633
+ }
634
+ function getPopupDimensions() {
635
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
636
+ return DESKTOP_POPUP_DIMENSIONS;
637
+ }
638
+ return window.matchMedia("(max-width: 640px)").matches ? MOBILE_POPUP_DIMENSIONS : DESKTOP_POPUP_DIMENSIONS;
639
+ }
640
+ function normalizeDescriptionValue(value) {
641
+ if (value === void 0 || value === null) return null;
642
+ if (typeof value === "string") {
643
+ const trimmed = value.trim();
644
+ return trimmed ? trimmed : null;
645
+ }
646
+ if (typeof value === "number" || typeof value === "boolean") {
647
+ return String(value);
648
+ }
649
+ return null;
650
+ }
651
+ function extractDescriptionValue(properties) {
652
+ if (!properties) return null;
653
+ const matches = Object.entries(properties).find(
654
+ ([key]) => DESCRIPTION_KEYS.has(key.toLowerCase())
655
+ );
656
+ if (!matches) return null;
657
+ return normalizeDescriptionValue(matches[1]);
658
+ }
659
+ function safeJsonStringify(value) {
660
+ try {
661
+ const json = JSON.stringify(value, null, 2);
662
+ if (json !== void 0) return json;
663
+ } catch {
664
+ }
665
+ return String(value);
666
+ }
667
+ function renderPopupValue(value) {
668
+ if (value === null || value === void 0) {
669
+ return '<span class="zenit-popup-empty">Sin datos</span>';
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
+ }
696
+ if (typeof value === "object") {
697
+ const json = safeJsonStringify(value);
698
+ return `<pre class="zenit-popup-pre">${escapeHtml(json)}</pre>`;
699
+ }
700
+ return `<span>${escapeHtml(String(value))}</span>`;
701
+ }
702
+ function shouldIncludePopupEntry(key, value) {
703
+ if (!key) return false;
704
+ const normalized = key.trim().toLowerCase();
705
+ if (!normalized) return false;
706
+ if (normalized.startsWith("_")) return false;
707
+ if (POPUP_EXCLUDED_KEYS.has(normalized)) return false;
708
+ if (value === null || value === void 0) return false;
709
+ if (typeof value === "string" && !value.trim()) return false;
710
+ return true;
711
+ }
712
+ function isDescriptionKey(key) {
713
+ const normalized = key.trim().toLowerCase();
714
+ return DESCRIPTION_KEYS.has(normalized);
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
+ }
727
+ function formatLabel(key) {
728
+ return key.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").trim();
729
+ }
730
+ function createPopupContent(properties) {
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
+ });
739
+ if (entries.length === 0) {
740
+ return `<div class="zenit-popup-card"><div class="zenit-popup-empty">Sin datos disponibles</div></div>`;
741
+ }
742
+ const descriptionEntry = entries.find(([key]) => isDescriptionKey(key));
743
+ const otherEntries = entries.filter(([key]) => !isDescriptionKey(key));
744
+ let rowsHtml = "";
745
+ if (descriptionEntry) {
746
+ const [key, value] = descriptionEntry;
747
+ const label = escapeHtml(formatLabel(key));
748
+ const valueHtml = renderPopupValue(value);
749
+ rowsHtml += `
750
+ <div class="zenit-popup-row zenit-popup-description">
751
+ <div class="zenit-popup-label">${label}</div>
752
+ <div class="zenit-popup-value">${valueHtml}</div>
753
+ </div>
754
+ `;
755
+ }
756
+ rowsHtml += otherEntries.map(([key, value]) => {
757
+ const label = escapeHtml(formatLabel(key));
758
+ const valueHtml = renderPopupValue(value);
759
+ return `
760
+ <div class="zenit-popup-row">
761
+ <div class="zenit-popup-label">${label}</div>
762
+ <div class="zenit-popup-value">${valueHtml}</div>
763
+ </div>
764
+ `;
765
+ }).join("");
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>`;
770
+ }
300
771
  function withAlpha(color, alpha) {
301
772
  const trimmed = color.trim();
302
773
  if (trimmed.startsWith("#")) {
@@ -365,40 +836,39 @@ function getFeatureStyleOverrides(feature) {
365
836
  function buildFeaturePopupHtml(feature) {
366
837
  const properties = feature?.properties;
367
838
  if (!properties) return null;
368
- const layerName = properties.layerName ?? properties.layer_name ?? properties.name;
369
- const descripcion = properties.descripcion ?? properties.description;
370
- const reservedKeys = /* @__PURE__ */ new Set([
371
- "_style",
372
- "layerId",
373
- "layer_id",
374
- "__zenit_layerId",
375
- "layerName",
376
- "layer_name",
377
- "name",
378
- "descripcion",
379
- "description"
380
- ]);
381
- const extraEntries = Object.entries(properties).filter(([key, value]) => {
382
- if (reservedKeys.has(key)) return false;
383
- return ["string", "number", "boolean"].includes(typeof value);
384
- }).slice(0, 5);
385
- if (!layerName && !descripcion && extraEntries.length === 0) return null;
386
- const parts = [];
387
- if (layerName) {
388
- parts.push(`<div style="font-weight:600;margin-bottom:4px;">${escapeHtml(layerName)}</div>`);
389
- }
390
- if (descripcion) {
391
- parts.push(`<div style="margin-bottom:6px;">${escapeHtml(descripcion)}</div>`);
392
- }
393
- if (extraEntries.length > 0) {
394
- const rows = extraEntries.map(([key, value]) => {
395
- const label = escapeHtml(key.replace(/_/g, " "));
396
- const val = escapeHtml(String(value));
397
- return `<div><strong>${label}:</strong> ${val}</div>`;
398
- }).join("");
399
- parts.push(`<div style="font-size:12px;line-height:1.4;">${rows}</div>`);
400
- }
401
- return `<div>${parts.join("")}</div>`;
839
+ const rendered = createPopupContent(properties);
840
+ return rendered ? rendered : null;
841
+ }
842
+ var POINT_GEOMETRY_TYPES = /* @__PURE__ */ new Set(["Point", "MultiPoint"]);
843
+ function getGeometryType(feature) {
844
+ const t = feature?.geometry?.type;
845
+ return typeof t === "string" ? t : null;
846
+ }
847
+ function isPointGeometry(feature) {
848
+ const geometryType = getGeometryType(feature);
849
+ return geometryType !== null && POINT_GEOMETRY_TYPES.has(geometryType);
850
+ }
851
+ function isNonPointGeometry(feature) {
852
+ const geometryType = getGeometryType(feature);
853
+ return geometryType !== null && !POINT_GEOMETRY_TYPES.has(geometryType);
854
+ }
855
+ function buildFeatureCollection(features) {
856
+ return {
857
+ type: "FeatureCollection",
858
+ features
859
+ };
860
+ }
861
+ function pickIntersectFeature(baseFeature, candidates) {
862
+ if (!Array.isArray(candidates) || candidates.length === 0) return null;
863
+ const baseId = baseFeature?.id;
864
+ if (baseId !== void 0 && baseId !== null) {
865
+ const matchById = candidates.find((candidate) => candidate?.id === baseId);
866
+ if (matchById) return matchById;
867
+ }
868
+ const matchWithDescription = candidates.find(
869
+ (candidate) => extractDescriptionValue(candidate?.properties)
870
+ );
871
+ return matchWithDescription ?? candidates[0];
402
872
  }
403
873
  function normalizeCenterTuple(center) {
404
874
  if (!center) return null;
@@ -519,7 +989,9 @@ var ZenitMap = forwardRef(({
519
989
  const [loadingMap, setLoadingMap] = useState(false);
520
990
  const [mapError, setMapError] = useState(null);
521
991
  const [mapInstance, setMapInstance] = useState(null);
992
+ const [panesReady, setPanesReady] = useState(false);
522
993
  const [currentZoom, setCurrentZoom] = useState(initialZoom ?? DEFAULT_ZOOM);
994
+ const [isPopupOpen, setIsPopupOpen] = useState(false);
523
995
  const [isMobile, setIsMobile] = useState(() => {
524
996
  if (typeof window === "undefined") return false;
525
997
  return window.matchMedia("(max-width: 768px)").matches;
@@ -543,6 +1015,36 @@ var ZenitMap = forwardRef(({
543
1015
  }
544
1016
  return;
545
1017
  }, []);
1018
+ useEffect(() => {
1019
+ if (featureInfoMode === "popup") {
1020
+ ensurePopupStyles();
1021
+ }
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]);
546
1048
  const layerStyleIndex = useMemo(() => {
547
1049
  const index = /* @__PURE__ */ new Map();
548
1050
  (map?.mapLayers ?? []).forEach((entry) => {
@@ -833,16 +1335,21 @@ var ZenitMap = forwardRef(({
833
1335
  (targetMap, targetLayers) => {
834
1336
  const baseZIndex = 400;
835
1337
  targetLayers.forEach((layer) => {
836
- const paneName = `zenit-layer-${layer.layerId}`;
837
- const pane = targetMap.getPane(paneName) ?? targetMap.createPane(paneName);
838
1338
  const order = Number.isFinite(layer.displayOrder) ? layer.displayOrder : 0;
839
- pane.style.zIndex = String(baseZIndex + order);
1339
+ const orderOffset = Math.max(0, Math.min(order, 150));
1340
+ const fillPaneName = `zenit-layer-${layer.layerId}-fill`;
1341
+ const pointPaneName = `zenit-layer-${layer.layerId}-points`;
1342
+ const fillPane = targetMap.getPane(fillPaneName) ?? targetMap.createPane(fillPaneName);
1343
+ const pointPane = targetMap.getPane(pointPaneName) ?? targetMap.createPane(pointPaneName);
1344
+ fillPane.style.zIndex = String(baseZIndex + orderOffset);
1345
+ pointPane.style.zIndex = String(baseZIndex + orderOffset + 100);
840
1346
  });
841
1347
  },
842
1348
  []
843
1349
  );
844
1350
  const handleMapReady = useCallback(
845
1351
  (instance) => {
1352
+ setPanesReady(false);
846
1353
  setMapInstance(instance);
847
1354
  onMapReady?.(instance);
848
1355
  },
@@ -850,6 +1357,7 @@ var ZenitMap = forwardRef(({
850
1357
  );
851
1358
  useEffect(() => {
852
1359
  if (!mapInstance) {
1360
+ setPanesReady(false);
853
1361
  return;
854
1362
  }
855
1363
  if (orderedLayers.length === 0) {
@@ -860,6 +1368,12 @@ var ZenitMap = forwardRef(({
860
1368
  displayOrder: layer.displayOrder
861
1369
  }));
862
1370
  ensureLayerPanes(mapInstance, layerTargets);
1371
+ const first = layerTargets[0];
1372
+ const testPane = mapInstance.getPane(`zenit-layer-${first.layerId}-fill`);
1373
+ const labelsPane = mapInstance.getPane(LABELS_PANE_NAME);
1374
+ if (testPane && labelsPane) {
1375
+ setPanesReady(true);
1376
+ }
863
1377
  }, [mapInstance, orderedLayers, ensureLayerPanes]);
864
1378
  const overlayOnEachFeature = useMemo(() => {
865
1379
  return (feature, layer) => {
@@ -877,7 +1391,17 @@ var ZenitMap = forwardRef(({
877
1391
  if (featureInfoMode === "popup") {
878
1392
  const content = buildFeaturePopupHtml(feature);
879
1393
  if (content) {
880
- layer.bindPopup(content, { maxWidth: 320 });
1394
+ const { maxWidth, minWidth, maxHeight } = getPopupDimensions();
1395
+ layer.bindPopup(content, {
1396
+ maxWidth,
1397
+ minWidth,
1398
+ maxHeight,
1399
+ className: "zenit-leaflet-popup custom-leaflet-popup",
1400
+ autoPan: true,
1401
+ closeButton: true,
1402
+ keepInView: true,
1403
+ offset: L.point(0, -24)
1404
+ });
881
1405
  }
882
1406
  }
883
1407
  if (isPointFeature && layer.bindTooltip) {
@@ -888,7 +1412,38 @@ var ZenitMap = forwardRef(({
888
1412
  className: "zenit-map-tooltip"
889
1413
  });
890
1414
  }
891
- layer.on("click", () => onFeatureClick?.(feature, layerId));
1415
+ layer.on("click", () => {
1416
+ if (featureInfoMode === "popup" && client && layerId !== void 0 && !extractDescriptionValue(feature?.properties) && feature?.geometry) {
1417
+ const trackedFeature = feature;
1418
+ if (!trackedFeature.__zenit_popup_loaded) {
1419
+ trackedFeature.__zenit_popup_loaded = true;
1420
+ client.layers.getLayerGeoJsonIntersect({
1421
+ id: layerId,
1422
+ geometry: feature.geometry
1423
+ }).then((response) => {
1424
+ const geo = extractGeoJsonFeatureCollection(response);
1425
+ const candidates = geo?.features ?? [];
1426
+ const resolved = pickIntersectFeature(feature, candidates);
1427
+ if (!resolved?.properties) return;
1428
+ const mergedProperties = {
1429
+ ...trackedFeature.properties ?? {},
1430
+ ...resolved.properties
1431
+ };
1432
+ trackedFeature.properties = mergedProperties;
1433
+ const updatedHtml = buildFeaturePopupHtml({
1434
+ ...feature,
1435
+ properties: mergedProperties
1436
+ });
1437
+ if (updatedHtml && layer.setPopupContent) {
1438
+ layer.setPopupContent(updatedHtml);
1439
+ }
1440
+ }).catch(() => {
1441
+ trackedFeature.__zenit_popup_loaded = false;
1442
+ });
1443
+ }
1444
+ }
1445
+ onFeatureClick?.(feature, layerId);
1446
+ });
892
1447
  layer.on("mouseover", () => {
893
1448
  if (layer instanceof L.Path && originalStyle) {
894
1449
  layer.setStyle({
@@ -912,7 +1467,7 @@ var ZenitMap = forwardRef(({
912
1467
  }
913
1468
  });
914
1469
  };
915
- }, [featureInfoMode, onFeatureClick, onFeatureHover]);
1470
+ }, [client, featureInfoMode, onFeatureClick, onFeatureHover]);
916
1471
  const buildLayerStyle = (layerId, baseOpacity, feature, layerType) => {
917
1472
  const style = resolveLayerStyle(layerId);
918
1473
  const featureStyleOverrides = getFeatureStyleOverrides(feature);
@@ -1070,68 +1625,93 @@ var ZenitMap = forwardRef(({
1070
1625
  boxSizing: "border-box"
1071
1626
  },
1072
1627
  children: [
1073
- /* @__PURE__ */ jsx("div", { style: { flex: 1, position: "relative" }, children: /* @__PURE__ */ jsxs(
1074
- MapContainer,
1628
+ /* @__PURE__ */ jsx(
1629
+ "div",
1075
1630
  {
1076
- center,
1077
- zoom,
1078
- style: { height: "100%", width: "100%" },
1079
- scrollWheelZoom: true,
1080
- zoomControl: false,
1081
- children: [
1082
- /* @__PURE__ */ jsx(
1083
- TileLayer,
1084
- {
1085
- url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
1086
- attribution: "\xA9 OpenStreetMap contributors"
1087
- }
1088
- ),
1089
- /* @__PURE__ */ jsx(ZoomControl, { position: "topright" }),
1090
- /* @__PURE__ */ jsx(MapInstanceBridge, { onReady: handleMapReady }),
1091
- /* @__PURE__ */ jsx(FitToBounds, { bbox: explicitZoomBBox ?? void 0 }),
1092
- /* @__PURE__ */ jsx(AutoFitToBounds, { bbox: autoZoomBBox ?? void 0, enabled: !explicitZoomBBox }),
1093
- /* @__PURE__ */ jsx(ZoomBasedOpacityHandler, { onZoomChange: handleZoomChange }),
1094
- orderedLayers.map((layerState) => {
1095
- const baseOpacity = layerState.effective?.baseOpacity ?? layerState.effective?.opacity ?? 1;
1096
- const paneName = `zenit-layer-${layerState.mapLayer.layerId}`;
1097
- const layerType = layerState.layer?.layerType ?? layerState.mapLayer.layerType ?? void 0;
1098
- return /* @__PURE__ */ jsx(
1099
- GeoJSON,
1100
- {
1101
- data: layerState.data,
1102
- pane: mapInstance?.getPane(paneName) ? paneName : void 0,
1103
- style: (feature) => buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType),
1104
- pointToLayer: (feature, latlng) => L.circleMarker(latlng, {
1105
- radius: isMobile ? 8 : 6,
1106
- ...buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType)
1107
- }),
1108
- onEachFeature: overlayOnEachFeature
1109
- },
1110
- layerState.mapLayer.layerId.toString()
1111
- );
1112
- }),
1113
- overlayGeojson && /* @__PURE__ */ jsx(
1114
- GeoJSON,
1115
- {
1116
- data: overlayGeojson,
1117
- style: overlayStyleFunction,
1118
- onEachFeature: overlayOnEachFeature
1119
- },
1120
- "zenit-overlay-geojson"
1121
- ),
1122
- labelMarkers.map((marker) => /* @__PURE__ */ jsx(
1123
- Marker,
1124
- {
1125
- position: marker.position,
1126
- icon: buildLabelIcon(marker.label, marker.opacity, marker.color),
1127
- interactive: false
1128
- },
1129
- marker.key
1130
- ))
1131
- ]
1132
- },
1133
- String(mapId)
1134
- ) }),
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,
1644
+ {
1645
+ url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
1646
+ attribution: "\xA9 OpenStreetMap contributors"
1647
+ }
1648
+ ),
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(
1701
+ GeoJSON,
1702
+ {
1703
+ data: overlayGeojson,
1704
+ style: overlayStyleFunction,
1705
+ onEachFeature: overlayOnEachFeature
1706
+ },
1707
+ "zenit-overlay-geojson"
1708
+ )
1709
+ ]
1710
+ },
1711
+ String(mapId)
1712
+ )
1713
+ }
1714
+ ),
1135
1715
  showLayerPanel && decoratedLayers.length > 0 && /* @__PURE__ */ jsxs(
1136
1716
  "div",
1137
1717
  {
@@ -2543,9 +3123,19 @@ var FloatingChatBox = ({
2543
3123
  getAccessToken,
2544
3124
  onActionClick,
2545
3125
  onOpenChange,
2546
- hideButton
3126
+ hideButton,
3127
+ open: openProp
2547
3128
  }) => {
2548
- const [open, setOpen] = useState4(false);
3129
+ const isControlled = openProp !== void 0;
3130
+ const [internalOpen, setInternalOpen] = useState4(false);
3131
+ const open = isControlled ? openProp : internalOpen;
3132
+ const setOpen = useCallback3((value) => {
3133
+ const newValue = typeof value === "function" ? value(open) : value;
3134
+ if (!isControlled) {
3135
+ setInternalOpen(newValue);
3136
+ }
3137
+ onOpenChange?.(newValue);
3138
+ }, [isControlled, open, onOpenChange]);
2549
3139
  const [expanded, setExpanded] = useState4(false);
2550
3140
  const [messages, setMessages] = useState4([]);
2551
3141
  const [inputValue, setInputValue] = useState4("");
@@ -2562,9 +3152,6 @@ var FloatingChatBox = ({
2562
3152
  }, [accessToken, baseUrl, getAccessToken]);
2563
3153
  const { sendMessage: sendMessage2, isStreaming, streamingText, completeResponse } = useSendMessageStream(chatConfig);
2564
3154
  const canSend = Boolean(mapId) && Boolean(baseUrl) && inputValue.trim().length > 0 && !isStreaming;
2565
- useEffect3(() => {
2566
- onOpenChange?.(open);
2567
- }, [open, onOpenChange]);
2568
3155
  useEffect3(() => {
2569
3156
  if (open && isMobile) {
2570
3157
  setExpanded(true);
@@ -2717,6 +3304,13 @@ var FloatingChatBox = ({
2717
3304
  ] }, index)) })
2718
3305
  ] });
2719
3306
  };
3307
+ const handleActionClick = useCallback3((action) => {
3308
+ if (isStreaming) return;
3309
+ setOpen(false);
3310
+ requestAnimationFrame(() => {
3311
+ onActionClick?.(action);
3312
+ });
3313
+ }, [isStreaming, setOpen, onActionClick]);
2720
3314
  const renderActions = (response) => {
2721
3315
  if (!response?.suggestedActions?.length) return null;
2722
3316
  return /* @__PURE__ */ jsxs4("div", { style: styles.actionsSection, children: [
@@ -2730,7 +3324,7 @@ var FloatingChatBox = ({
2730
3324
  opacity: isStreaming ? 0.5 : 1,
2731
3325
  cursor: isStreaming ? "not-allowed" : "pointer"
2732
3326
  },
2733
- onClick: () => !isStreaming && onActionClick?.(action),
3327
+ onClick: () => handleActionClick(action),
2734
3328
  disabled: isStreaming,
2735
3329
  onMouseEnter: (e) => {
2736
3330
  if (!isStreaming) {
@@ -3057,4 +3651,4 @@ export {
3057
3651
  useSendMessageStream,
3058
3652
  FloatingChatBox
3059
3653
  };
3060
- //# sourceMappingURL=chunk-R73LRYVJ.mjs.map
3654
+ //# sourceMappingURL=chunk-52CLFD4L.mjs.map