zenit-sdk 0.0.3 → 0.0.7

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
 
@@ -297,6 +297,366 @@ function getFeatureLayerId(feature) {
297
297
  function escapeHtml(value) {
298
298
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
299
299
  }
300
+ var DESCRIPTION_KEYS = /* @__PURE__ */ new Set(["descripcion", "description"]);
301
+ var POPUP_EXCLUDED_KEYS = /* @__PURE__ */ new Set(["geom", "geometry"]);
302
+ var POPUP_STYLE_ID = "zenit-leaflet-popup-styles";
303
+ var DESKTOP_POPUP_DIMENSIONS = { maxWidth: 350, minWidth: 280, maxHeight: 480 };
304
+ var MOBILE_POPUP_DIMENSIONS = { maxWidth: 280, minWidth: 240, maxHeight: 380 };
305
+ var ZENIT_LEAFLET_POPUP_STYLES = `
306
+ /* ===== Zenit Leaflet Popup - Modern Professional Styling ===== */
307
+
308
+ /* Main popup wrapper */
309
+ .zenit-leaflet-popup .leaflet-popup-content-wrapper {
310
+ border-radius: 12px;
311
+ box-shadow:
312
+ 0 4px 6px -1px rgba(0, 0, 0, 0.1),
313
+ 0 2px 4px -2px rgba(0, 0, 0, 0.1),
314
+ 0 0 0 1px rgba(0, 0, 0, 0.05);
315
+ padding: 0;
316
+ background: #ffffff;
317
+ overflow: hidden;
318
+ }
319
+
320
+ /* Content area with scroll support */
321
+ .zenit-leaflet-popup .leaflet-popup-content {
322
+ margin: 0;
323
+ padding: 0;
324
+ font-size: 13px;
325
+ line-height: 1.5;
326
+ color: #374151;
327
+ min-width: 100%;
328
+ overflow-y: auto;
329
+ overflow-x: hidden;
330
+ scrollbar-width: thin;
331
+ scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
332
+ }
333
+
334
+ /* Popup tip/arrow shadow */
335
+ .zenit-leaflet-popup .leaflet-popup-tip-container {
336
+ filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
337
+ }
338
+
339
+ .zenit-leaflet-popup .leaflet-popup-tip {
340
+ background: #ffffff;
341
+ box-shadow: none;
342
+ }
343
+
344
+ /* Close button styling */
345
+ .zenit-leaflet-popup .leaflet-popup-close-button {
346
+ color: #9ca3af;
347
+ font-size: 18px;
348
+ font-weight: 400;
349
+ width: 28px;
350
+ height: 28px;
351
+ padding: 0;
352
+ margin: 8px 8px 0 0;
353
+ display: flex;
354
+ align-items: center;
355
+ justify-content: center;
356
+ border-radius: 6px;
357
+ transition: all 0.15s ease;
358
+ z-index: 10;
359
+ }
360
+
361
+ .zenit-leaflet-popup .leaflet-popup-close-button:hover {
362
+ color: #374151;
363
+ background-color: #f3f4f6;
364
+ }
365
+
366
+ .zenit-leaflet-popup .leaflet-popup-close-button:active {
367
+ background-color: #e5e7eb;
368
+ }
369
+
370
+ /* Main card container */
371
+ .zenit-popup-card {
372
+ display: flex;
373
+ flex-direction: column;
374
+ gap: 0;
375
+ padding: 16px;
376
+ }
377
+
378
+ /* Individual row styling with subtle separator */
379
+ .zenit-popup-row {
380
+ display: flex;
381
+ flex-direction: column;
382
+ gap: 2px;
383
+ padding: 10px 0;
384
+ border-bottom: 1px solid #f3f4f6;
385
+ }
386
+
387
+ .zenit-popup-row:first-child {
388
+ padding-top: 0;
389
+ }
390
+
391
+ .zenit-popup-row:last-child {
392
+ border-bottom: none;
393
+ padding-bottom: 0;
394
+ }
395
+
396
+ /* Label styling - small, gray, uppercase */
397
+ .zenit-popup-label {
398
+ font-size: 10px;
399
+ font-weight: 500;
400
+ color: #9ca3af;
401
+ text-transform: uppercase;
402
+ letter-spacing: 0.05em;
403
+ line-height: 1.4;
404
+ }
405
+
406
+ /* Value styling - darker, readable */
407
+ .zenit-popup-value {
408
+ font-size: 13px;
409
+ font-weight: 400;
410
+ color: #1f2937;
411
+ overflow-wrap: break-word;
412
+ word-break: break-word;
413
+ line-height: 1.5;
414
+ }
415
+
416
+ /* Special styling for description field */
417
+ .zenit-popup-row.zenit-popup-description {
418
+ background-color: #f9fafb;
419
+ margin: 0 -16px;
420
+ padding: 12px 16px;
421
+ border-bottom: 1px solid #e5e7eb;
422
+ }
423
+
424
+ .zenit-popup-row.zenit-popup-description:first-child {
425
+ margin-top: 0;
426
+ border-radius: 0;
427
+ }
428
+
429
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value {
430
+ font-size: 13px;
431
+ line-height: 1.6;
432
+ color: #374151;
433
+ max-height: 150px;
434
+ overflow-y: auto;
435
+ padding-right: 4px;
436
+ }
437
+
438
+ /* Preformatted text (JSON objects) */
439
+ .zenit-popup-pre {
440
+ margin: 0;
441
+ font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
442
+ font-size: 11px;
443
+ white-space: pre-wrap;
444
+ word-break: break-word;
445
+ color: #4b5563;
446
+ background-color: #f9fafb;
447
+ padding: 8px;
448
+ border-radius: 6px;
449
+ border: 1px solid #e5e7eb;
450
+ }
451
+
452
+ /* Empty state styling */
453
+ .zenit-popup-empty {
454
+ font-size: 13px;
455
+ color: #9ca3af;
456
+ font-style: italic;
457
+ text-align: center;
458
+ padding: 20px 0;
459
+ }
460
+
461
+ /* Webkit scrollbar styling */
462
+ .zenit-leaflet-popup .leaflet-popup-content::-webkit-scrollbar {
463
+ width: 6px;
464
+ }
465
+
466
+ .zenit-leaflet-popup .leaflet-popup-content::-webkit-scrollbar-track {
467
+ background: transparent;
468
+ }
469
+
470
+ .zenit-leaflet-popup .leaflet-popup-content::-webkit-scrollbar-thumb {
471
+ background-color: rgba(156, 163, 175, 0.4);
472
+ border-radius: 3px;
473
+ }
474
+
475
+ .zenit-leaflet-popup .leaflet-popup-content::-webkit-scrollbar-thumb:hover {
476
+ background-color: rgba(107, 114, 128, 0.6);
477
+ }
478
+
479
+ /* Scrollbar for description field */
480
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value::-webkit-scrollbar {
481
+ width: 4px;
482
+ }
483
+
484
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value::-webkit-scrollbar-track {
485
+ background: transparent;
486
+ }
487
+
488
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value::-webkit-scrollbar-thumb {
489
+ background-color: rgba(156, 163, 175, 0.4);
490
+ border-radius: 2px;
491
+ }
492
+
493
+ /* ===== Responsive: Mobile (<640px) ===== */
494
+ @media (max-width: 640px) {
495
+ .zenit-leaflet-popup .leaflet-popup-content-wrapper {
496
+ border-radius: 10px;
497
+ }
498
+
499
+ .zenit-leaflet-popup .leaflet-popup-close-button {
500
+ width: 26px;
501
+ height: 26px;
502
+ font-size: 16px;
503
+ margin: 6px 6px 0 0;
504
+ }
505
+
506
+ .zenit-popup-card {
507
+ padding: 12px;
508
+ }
509
+
510
+ .zenit-popup-row {
511
+ padding: 8px 0;
512
+ }
513
+
514
+ .zenit-popup-label {
515
+ font-size: 9px;
516
+ }
517
+
518
+ .zenit-popup-value {
519
+ font-size: 12px;
520
+ }
521
+
522
+ .zenit-popup-row.zenit-popup-description {
523
+ margin: 0 -12px;
524
+ padding: 10px 12px;
525
+ }
526
+
527
+ .zenit-popup-row.zenit-popup-description .zenit-popup-value {
528
+ font-size: 12px;
529
+ max-height: 120px;
530
+ }
531
+
532
+ .zenit-popup-pre {
533
+ font-size: 10px;
534
+ padding: 6px;
535
+ }
536
+
537
+ .zenit-popup-empty {
538
+ font-size: 12px;
539
+ padding: 16px 0;
540
+ }
541
+ }
542
+
543
+ /* ===== Map tooltip styling ===== */
544
+ .zenit-map-tooltip {
545
+ background-color: rgba(31, 41, 55, 0.95);
546
+ border: none;
547
+ border-radius: 6px;
548
+ color: #ffffff;
549
+ font-size: 12px;
550
+ font-weight: 500;
551
+ padding: 6px 10px;
552
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
553
+ }
554
+
555
+ .zenit-map-tooltip::before {
556
+ border-top-color: rgba(31, 41, 55, 0.95);
557
+ }
558
+ `;
559
+ function ensurePopupStyles() {
560
+ if (typeof document === "undefined") return;
561
+ if (document.getElementById(POPUP_STYLE_ID)) return;
562
+ const styleTag = document.createElement("style");
563
+ styleTag.id = POPUP_STYLE_ID;
564
+ styleTag.textContent = ZENIT_LEAFLET_POPUP_STYLES;
565
+ document.head.appendChild(styleTag);
566
+ }
567
+ function getPopupDimensions() {
568
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
569
+ return DESKTOP_POPUP_DIMENSIONS;
570
+ }
571
+ return window.matchMedia("(max-width: 640px)").matches ? MOBILE_POPUP_DIMENSIONS : DESKTOP_POPUP_DIMENSIONS;
572
+ }
573
+ function normalizeDescriptionValue(value) {
574
+ if (value === void 0 || value === null) return null;
575
+ if (typeof value === "string") {
576
+ const trimmed = value.trim();
577
+ return trimmed ? trimmed : null;
578
+ }
579
+ if (typeof value === "number" || typeof value === "boolean") {
580
+ return String(value);
581
+ }
582
+ return null;
583
+ }
584
+ function extractDescriptionValue(properties) {
585
+ if (!properties) return null;
586
+ const matches = Object.entries(properties).find(
587
+ ([key]) => DESCRIPTION_KEYS.has(key.toLowerCase())
588
+ );
589
+ if (!matches) return null;
590
+ return normalizeDescriptionValue(matches[1]);
591
+ }
592
+ function safeJsonStringify(value) {
593
+ try {
594
+ const json = JSON.stringify(value, null, 2);
595
+ if (json !== void 0) return json;
596
+ } catch {
597
+ }
598
+ return String(value);
599
+ }
600
+ function renderPopupValue(value) {
601
+ if (value === null || value === void 0) {
602
+ return '<span class="zenit-popup-empty">Sin datos</span>';
603
+ }
604
+ if (typeof value === "object") {
605
+ const json = safeJsonStringify(value);
606
+ return `<pre class="zenit-popup-pre">${escapeHtml(json)}</pre>`;
607
+ }
608
+ return `<span>${escapeHtml(String(value))}</span>`;
609
+ }
610
+ function shouldIncludePopupEntry(key, value) {
611
+ if (!key) return false;
612
+ const normalized = key.trim().toLowerCase();
613
+ if (!normalized) return false;
614
+ if (normalized.startsWith("_")) return false;
615
+ if (POPUP_EXCLUDED_KEYS.has(normalized)) return false;
616
+ if (value === null || value === void 0) return false;
617
+ if (typeof value === "string" && !value.trim()) return false;
618
+ return true;
619
+ }
620
+ function isDescriptionKey(key) {
621
+ const normalized = key.trim().toLowerCase();
622
+ return DESCRIPTION_KEYS.has(normalized);
623
+ }
624
+ function formatLabel(key) {
625
+ return key.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").trim();
626
+ }
627
+ function createPopupContent(properties) {
628
+ const entries = Object.entries(properties).filter(
629
+ ([key, value]) => shouldIncludePopupEntry(key, value)
630
+ );
631
+ if (entries.length === 0) {
632
+ return `<div class="zenit-popup-card"><div class="zenit-popup-empty">Sin datos disponibles</div></div>`;
633
+ }
634
+ const descriptionEntry = entries.find(([key]) => isDescriptionKey(key));
635
+ const otherEntries = entries.filter(([key]) => !isDescriptionKey(key));
636
+ let rowsHtml = "";
637
+ if (descriptionEntry) {
638
+ const [key, value] = descriptionEntry;
639
+ const label = escapeHtml(formatLabel(key));
640
+ const valueHtml = renderPopupValue(value);
641
+ rowsHtml += `
642
+ <div class="zenit-popup-row zenit-popup-description">
643
+ <div class="zenit-popup-label">${label}</div>
644
+ <div class="zenit-popup-value">${valueHtml}</div>
645
+ </div>
646
+ `;
647
+ }
648
+ rowsHtml += otherEntries.map(([key, value]) => {
649
+ const label = escapeHtml(formatLabel(key));
650
+ const valueHtml = renderPopupValue(value);
651
+ return `
652
+ <div class="zenit-popup-row">
653
+ <div class="zenit-popup-label">${label}</div>
654
+ <div class="zenit-popup-value">${valueHtml}</div>
655
+ </div>
656
+ `;
657
+ }).join("");
658
+ return `<div class="zenit-popup-card">${rowsHtml}</div>`;
659
+ }
300
660
  function withAlpha(color, alpha) {
301
661
  const trimmed = color.trim();
302
662
  if (trimmed.startsWith("#")) {
@@ -365,40 +725,39 @@ function getFeatureStyleOverrides(feature) {
365
725
  function buildFeaturePopupHtml(feature) {
366
726
  const properties = feature?.properties;
367
727
  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>`);
728
+ const rendered = createPopupContent(properties);
729
+ return rendered ? rendered : null;
730
+ }
731
+ var POINT_GEOMETRY_TYPES = /* @__PURE__ */ new Set(["Point", "MultiPoint"]);
732
+ function getGeometryType(feature) {
733
+ const t = feature?.geometry?.type;
734
+ return typeof t === "string" ? t : null;
735
+ }
736
+ function isPointGeometry(feature) {
737
+ const geometryType = getGeometryType(feature);
738
+ return geometryType !== null && POINT_GEOMETRY_TYPES.has(geometryType);
739
+ }
740
+ function isNonPointGeometry(feature) {
741
+ const geometryType = getGeometryType(feature);
742
+ return geometryType !== null && !POINT_GEOMETRY_TYPES.has(geometryType);
743
+ }
744
+ function buildFeatureCollection(features) {
745
+ return {
746
+ type: "FeatureCollection",
747
+ features
748
+ };
749
+ }
750
+ function pickIntersectFeature(baseFeature, candidates) {
751
+ if (!Array.isArray(candidates) || candidates.length === 0) return null;
752
+ const baseId = baseFeature?.id;
753
+ if (baseId !== void 0 && baseId !== null) {
754
+ const matchById = candidates.find((candidate) => candidate?.id === baseId);
755
+ if (matchById) return matchById;
400
756
  }
401
- return `<div>${parts.join("")}</div>`;
757
+ const matchWithDescription = candidates.find(
758
+ (candidate) => extractDescriptionValue(candidate?.properties)
759
+ );
760
+ return matchWithDescription ?? candidates[0];
402
761
  }
403
762
  function normalizeCenterTuple(center) {
404
763
  if (!center) return null;
@@ -519,6 +878,7 @@ var ZenitMap = forwardRef(({
519
878
  const [loadingMap, setLoadingMap] = useState(false);
520
879
  const [mapError, setMapError] = useState(null);
521
880
  const [mapInstance, setMapInstance] = useState(null);
881
+ const [panesReady, setPanesReady] = useState(false);
522
882
  const [currentZoom, setCurrentZoom] = useState(initialZoom ?? DEFAULT_ZOOM);
523
883
  const [isMobile, setIsMobile] = useState(() => {
524
884
  if (typeof window === "undefined") return false;
@@ -543,6 +903,11 @@ var ZenitMap = forwardRef(({
543
903
  }
544
904
  return;
545
905
  }, []);
906
+ useEffect(() => {
907
+ if (featureInfoMode === "popup") {
908
+ ensurePopupStyles();
909
+ }
910
+ }, [featureInfoMode]);
546
911
  const layerStyleIndex = useMemo(() => {
547
912
  const index = /* @__PURE__ */ new Map();
548
913
  (map?.mapLayers ?? []).forEach((entry) => {
@@ -833,16 +1198,23 @@ var ZenitMap = forwardRef(({
833
1198
  (targetMap, targetLayers) => {
834
1199
  const baseZIndex = 400;
835
1200
  targetLayers.forEach((layer) => {
836
- const paneName = `zenit-layer-${layer.layerId}`;
837
- const pane = targetMap.getPane(paneName) ?? targetMap.createPane(paneName);
838
1201
  const order = Number.isFinite(layer.displayOrder) ? layer.displayOrder : 0;
839
- pane.style.zIndex = String(baseZIndex + order);
1202
+ const fillPaneName = `zenit-layer-${layer.layerId}-fill`;
1203
+ const pointPaneName = `zenit-layer-${layer.layerId}-points`;
1204
+ const labelPaneName = `zenit-layer-${layer.layerId}-labels`;
1205
+ const fillPane = targetMap.getPane(fillPaneName) ?? targetMap.createPane(fillPaneName);
1206
+ const pointPane = targetMap.getPane(pointPaneName) ?? targetMap.createPane(pointPaneName);
1207
+ const labelPane = targetMap.getPane(labelPaneName) ?? targetMap.createPane(labelPaneName);
1208
+ fillPane.style.zIndex = String(baseZIndex + order);
1209
+ pointPane.style.zIndex = String(baseZIndex + order + 1e3);
1210
+ labelPane.style.zIndex = String(baseZIndex + order + 2e3);
840
1211
  });
841
1212
  },
842
1213
  []
843
1214
  );
844
1215
  const handleMapReady = useCallback(
845
1216
  (instance) => {
1217
+ setPanesReady(false);
846
1218
  setMapInstance(instance);
847
1219
  onMapReady?.(instance);
848
1220
  },
@@ -850,6 +1222,7 @@ var ZenitMap = forwardRef(({
850
1222
  );
851
1223
  useEffect(() => {
852
1224
  if (!mapInstance) {
1225
+ setPanesReady(false);
853
1226
  return;
854
1227
  }
855
1228
  if (orderedLayers.length === 0) {
@@ -860,6 +1233,11 @@ var ZenitMap = forwardRef(({
860
1233
  displayOrder: layer.displayOrder
861
1234
  }));
862
1235
  ensureLayerPanes(mapInstance, layerTargets);
1236
+ const first = layerTargets[0];
1237
+ const testPane = mapInstance.getPane(`zenit-layer-${first.layerId}-labels`);
1238
+ if (testPane) {
1239
+ setPanesReady(true);
1240
+ }
863
1241
  }, [mapInstance, orderedLayers, ensureLayerPanes]);
864
1242
  const overlayOnEachFeature = useMemo(() => {
865
1243
  return (feature, layer) => {
@@ -877,7 +1255,17 @@ var ZenitMap = forwardRef(({
877
1255
  if (featureInfoMode === "popup") {
878
1256
  const content = buildFeaturePopupHtml(feature);
879
1257
  if (content) {
880
- layer.bindPopup(content, { maxWidth: 320 });
1258
+ const { maxWidth, minWidth, maxHeight } = getPopupDimensions();
1259
+ layer.bindPopup(content, {
1260
+ maxWidth,
1261
+ minWidth,
1262
+ maxHeight,
1263
+ className: "zenit-leaflet-popup",
1264
+ autoPan: true,
1265
+ closeButton: true,
1266
+ keepInView: true,
1267
+ offset: L.point(0, -24)
1268
+ });
881
1269
  }
882
1270
  }
883
1271
  if (isPointFeature && layer.bindTooltip) {
@@ -888,7 +1276,37 @@ var ZenitMap = forwardRef(({
888
1276
  className: "zenit-map-tooltip"
889
1277
  });
890
1278
  }
891
- layer.on("click", () => onFeatureClick?.(feature, layerId));
1279
+ layer.on("click", () => {
1280
+ if (featureInfoMode === "popup" && client && layerId !== void 0 && !extractDescriptionValue(feature?.properties) && feature?.geometry) {
1281
+ const trackedFeature = feature;
1282
+ if (!trackedFeature.__zenit_popup_loaded) {
1283
+ trackedFeature.__zenit_popup_loaded = true;
1284
+ client.layers.getLayerGeoJsonIntersect({
1285
+ id: layerId,
1286
+ geometry: feature.geometry
1287
+ }).then((response) => {
1288
+ const candidates = response.data?.features ?? [];
1289
+ const resolved = pickIntersectFeature(feature, candidates);
1290
+ if (!resolved?.properties) return;
1291
+ const mergedProperties = {
1292
+ ...trackedFeature.properties ?? {},
1293
+ ...resolved.properties
1294
+ };
1295
+ trackedFeature.properties = mergedProperties;
1296
+ const updatedHtml = buildFeaturePopupHtml({
1297
+ ...feature,
1298
+ properties: mergedProperties
1299
+ });
1300
+ if (updatedHtml && layer.setPopupContent) {
1301
+ layer.setPopupContent(updatedHtml);
1302
+ }
1303
+ }).catch(() => {
1304
+ trackedFeature.__zenit_popup_loaded = false;
1305
+ });
1306
+ }
1307
+ }
1308
+ onFeatureClick?.(feature, layerId);
1309
+ });
892
1310
  layer.on("mouseover", () => {
893
1311
  if (layer instanceof L.Path && originalStyle) {
894
1312
  layer.setStyle({
@@ -912,7 +1330,7 @@ var ZenitMap = forwardRef(({
912
1330
  }
913
1331
  });
914
1332
  };
915
- }, [featureInfoMode, onFeatureClick, onFeatureHover]);
1333
+ }, [client, featureInfoMode, onFeatureClick, onFeatureHover]);
916
1334
  const buildLayerStyle = (layerId, baseOpacity, feature, layerType) => {
917
1335
  const style = resolveLayerStyle(layerId);
918
1336
  const featureStyleOverrides = getFeatureStyleOverrides(feature);
@@ -1093,22 +1511,50 @@ var ZenitMap = forwardRef(({
1093
1511
  /* @__PURE__ */ jsx(ZoomBasedOpacityHandler, { onZoomChange: handleZoomChange }),
1094
1512
  orderedLayers.map((layerState) => {
1095
1513
  const baseOpacity = layerState.effective?.baseOpacity ?? layerState.effective?.opacity ?? 1;
1096
- const paneName = `zenit-layer-${layerState.mapLayer.layerId}`;
1514
+ const fillPaneName = `zenit-layer-${layerState.mapLayer.layerId}-fill`;
1515
+ const pointsPaneName = `zenit-layer-${layerState.mapLayer.layerId}-points`;
1516
+ const labelPaneName = `zenit-layer-${layerState.mapLayer.layerId}-labels`;
1097
1517
  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
- );
1518
+ const data = layerState.data?.features ?? [];
1519
+ const fillFeatures = data.filter(isNonPointGeometry);
1520
+ const pointFeatures = data.filter(isPointGeometry);
1521
+ const fillData = fillFeatures.length > 0 ? buildFeatureCollection(fillFeatures) : null;
1522
+ const pointsData = pointFeatures.length > 0 ? buildFeatureCollection(pointFeatures) : null;
1523
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1524
+ fillData && /* @__PURE__ */ jsx(
1525
+ GeoJSON,
1526
+ {
1527
+ data: fillData,
1528
+ pane: panesReady && mapInstance?.getPane(fillPaneName) ? fillPaneName : void 0,
1529
+ style: (feature) => buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType),
1530
+ onEachFeature: overlayOnEachFeature
1531
+ }
1532
+ ),
1533
+ pointsData && /* @__PURE__ */ jsx(
1534
+ GeoJSON,
1535
+ {
1536
+ data: pointsData,
1537
+ pane: panesReady && mapInstance?.getPane(pointsPaneName) ? pointsPaneName : void 0,
1538
+ pointToLayer: (feature, latlng) => L.circleMarker(latlng, {
1539
+ radius: isMobile ? 8 : 6,
1540
+ ...buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType)
1541
+ }),
1542
+ onEachFeature: overlayOnEachFeature
1543
+ }
1544
+ ),
1545
+ panesReady && mapInstance?.getPane(labelPaneName) ? labelMarkers.filter(
1546
+ (marker) => String(marker.layerId) === String(layerState.mapLayer.layerId)
1547
+ ).map((marker) => /* @__PURE__ */ jsx(
1548
+ Marker,
1549
+ {
1550
+ position: marker.position,
1551
+ icon: buildLabelIcon(marker.label, marker.opacity, marker.color),
1552
+ interactive: false,
1553
+ pane: labelPaneName
1554
+ },
1555
+ marker.key
1556
+ )) : null
1557
+ ] }, layerState.mapLayer.layerId.toString());
1112
1558
  }),
1113
1559
  overlayGeojson && /* @__PURE__ */ jsx(
1114
1560
  GeoJSON,
@@ -1118,16 +1564,7 @@ var ZenitMap = forwardRef(({
1118
1564
  onEachFeature: overlayOnEachFeature
1119
1565
  },
1120
1566
  "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
- ))
1567
+ )
1131
1568
  ]
1132
1569
  },
1133
1570
  String(mapId)
@@ -2543,9 +2980,19 @@ var FloatingChatBox = ({
2543
2980
  getAccessToken,
2544
2981
  onActionClick,
2545
2982
  onOpenChange,
2546
- hideButton
2983
+ hideButton,
2984
+ open: openProp
2547
2985
  }) => {
2548
- const [open, setOpen] = useState4(false);
2986
+ const isControlled = openProp !== void 0;
2987
+ const [internalOpen, setInternalOpen] = useState4(false);
2988
+ const open = isControlled ? openProp : internalOpen;
2989
+ const setOpen = useCallback3((value) => {
2990
+ const newValue = typeof value === "function" ? value(open) : value;
2991
+ if (!isControlled) {
2992
+ setInternalOpen(newValue);
2993
+ }
2994
+ onOpenChange?.(newValue);
2995
+ }, [isControlled, open, onOpenChange]);
2549
2996
  const [expanded, setExpanded] = useState4(false);
2550
2997
  const [messages, setMessages] = useState4([]);
2551
2998
  const [inputValue, setInputValue] = useState4("");
@@ -2562,9 +3009,6 @@ var FloatingChatBox = ({
2562
3009
  }, [accessToken, baseUrl, getAccessToken]);
2563
3010
  const { sendMessage: sendMessage2, isStreaming, streamingText, completeResponse } = useSendMessageStream(chatConfig);
2564
3011
  const canSend = Boolean(mapId) && Boolean(baseUrl) && inputValue.trim().length > 0 && !isStreaming;
2565
- useEffect3(() => {
2566
- onOpenChange?.(open);
2567
- }, [open, onOpenChange]);
2568
3012
  useEffect3(() => {
2569
3013
  if (open && isMobile) {
2570
3014
  setExpanded(true);
@@ -2717,6 +3161,13 @@ var FloatingChatBox = ({
2717
3161
  ] }, index)) })
2718
3162
  ] });
2719
3163
  };
3164
+ const handleActionClick = useCallback3((action) => {
3165
+ if (isStreaming) return;
3166
+ setOpen(false);
3167
+ requestAnimationFrame(() => {
3168
+ onActionClick?.(action);
3169
+ });
3170
+ }, [isStreaming, setOpen, onActionClick]);
2720
3171
  const renderActions = (response) => {
2721
3172
  if (!response?.suggestedActions?.length) return null;
2722
3173
  return /* @__PURE__ */ jsxs4("div", { style: styles.actionsSection, children: [
@@ -2730,7 +3181,7 @@ var FloatingChatBox = ({
2730
3181
  opacity: isStreaming ? 0.5 : 1,
2731
3182
  cursor: isStreaming ? "not-allowed" : "pointer"
2732
3183
  },
2733
- onClick: () => !isStreaming && onActionClick?.(action),
3184
+ onClick: () => handleActionClick(action),
2734
3185
  disabled: isStreaming,
2735
3186
  onMouseEnter: (e) => {
2736
3187
  if (!isStreaming) {
@@ -3057,4 +3508,4 @@ export {
3057
3508
  useSendMessageStream,
3058
3509
  FloatingChatBox
3059
3510
  };
3060
- //# sourceMappingURL=chunk-R73LRYVJ.mjs.map
3511
+ //# sourceMappingURL=chunk-ITF7QCUZ.mjs.map