zenit-sdk 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -35,7 +35,7 @@ import {
35
35
  sendMessageStream,
36
36
  useSendMessage,
37
37
  useSendMessageStream
38
- } from "./chunk-R73LRYVJ.mjs";
38
+ } from "./chunk-LX2N2BXV.mjs";
39
39
 
40
40
  // src/http/HttpClient.ts
41
41
  var HttpClient = class {
@@ -1,4 +1,4 @@
1
- export { an as FloatingChatBox, ao as FloatingChatBoxProps, aj as LayerSnapshot, ar as LayerStyle, am as ZenitFeatureFilterPanel, al as ZenitLayerManager, ah as ZenitMap, ak as ZenitMapProps, ai as ZenitMapRef, aw as ZoomOpacityOptions, ax as clampNumber, ay as clampOpacity, av as getAccentByLayerId, aC as getEffectiveLayerOpacity, at as getLayerColor, aB as getLayerZoomOpacityFactor, au as getStyleByLayerId, aA as getZoomOpacityFactor, az as isPolygonLayer, as as resolveLayerAccent, ap as useSendMessage, aq as useSendMessageStream } from '../index-Da0hFqDL.mjs';
1
+ export { an as FloatingChatBox, ao as FloatingChatBoxProps, aj as LayerSnapshot, ar as LayerStyle, am as ZenitFeatureFilterPanel, al as ZenitLayerManager, ah as ZenitMap, ak as ZenitMapProps, ai as ZenitMapRef, aw as ZoomOpacityOptions, ax as clampNumber, ay as clampOpacity, av as getAccentByLayerId, aC as getEffectiveLayerOpacity, at as getLayerColor, aB as getLayerZoomOpacityFactor, au as getStyleByLayerId, aA as getZoomOpacityFactor, az as isPolygonLayer, as as resolveLayerAccent, ap as useSendMessage, aq as useSendMessageStream } from '../index-kGwfqTc_.mjs';
2
2
  export { ChevronLeft, ChevronRight, Eye, EyeOff, Layers, Upload, X, ZoomIn } from 'lucide-react';
3
3
  import 'react';
4
4
  import 'leaflet';
@@ -1,4 +1,4 @@
1
- export { an as FloatingChatBox, ao as FloatingChatBoxProps, aj as LayerSnapshot, ar as LayerStyle, am as ZenitFeatureFilterPanel, al as ZenitLayerManager, ah as ZenitMap, ak as ZenitMapProps, ai as ZenitMapRef, aw as ZoomOpacityOptions, ax as clampNumber, ay as clampOpacity, av as getAccentByLayerId, aC as getEffectiveLayerOpacity, at as getLayerColor, aB as getLayerZoomOpacityFactor, au as getStyleByLayerId, aA as getZoomOpacityFactor, az as isPolygonLayer, as as resolveLayerAccent, ap as useSendMessage, aq as useSendMessageStream } from '../index-Da0hFqDL.js';
1
+ export { an as FloatingChatBox, ao as FloatingChatBoxProps, aj as LayerSnapshot, ar as LayerStyle, am as ZenitFeatureFilterPanel, al as ZenitLayerManager, ah as ZenitMap, ak as ZenitMapProps, ai as ZenitMapRef, aw as ZoomOpacityOptions, ax as clampNumber, ay as clampOpacity, av as getAccentByLayerId, aC as getEffectiveLayerOpacity, at as getLayerColor, aB as getLayerZoomOpacityFactor, au as getStyleByLayerId, aA as getZoomOpacityFactor, az as isPolygonLayer, as as resolveLayerAccent, ap as useSendMessage, aq as useSendMessageStream } from '../index-kGwfqTc_.js';
2
2
  export { ChevronLeft, ChevronRight, Eye, EyeOff, Layers, Upload, X, ZoomIn } from 'lucide-react';
3
3
  import 'react';
4
4
  import 'leaflet';
@@ -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
 
@@ -344,6 +344,121 @@ function getFeatureLayerId(feature) {
344
344
  function escapeHtml(value) {
345
345
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
346
346
  }
347
+ var DESCRIPTION_KEYS = /* @__PURE__ */ new Set(["descripcion", "description"]);
348
+ function normalizeDescriptionValue(value) {
349
+ if (value === void 0 || value === null) return null;
350
+ if (typeof value === "string") {
351
+ const trimmed = value.trim();
352
+ return trimmed ? trimmed : null;
353
+ }
354
+ if (typeof value === "number" || typeof value === "boolean") {
355
+ return String(value);
356
+ }
357
+ return null;
358
+ }
359
+ function extractDescriptionValue(properties) {
360
+ if (!properties) return null;
361
+ const matches = Object.entries(properties).find(
362
+ ([key]) => DESCRIPTION_KEYS.has(key.toLowerCase())
363
+ );
364
+ if (!matches) return null;
365
+ return normalizeDescriptionValue(matches[1]);
366
+ }
367
+ function safeJsonStringify(value) {
368
+ try {
369
+ const json = JSON.stringify(value, null, 2);
370
+ if (json !== void 0) return json;
371
+ } catch {
372
+ }
373
+ return String(value);
374
+ }
375
+ function renderPropertyValue(value) {
376
+ if (value === null || value === void 0) {
377
+ return '<span class="prop-empty">\u2014</span>';
378
+ }
379
+ if (typeof value === "object") {
380
+ const json = safeJsonStringify(value);
381
+ return `<pre class="prop-json">${escapeHtml(json)}</pre>`;
382
+ }
383
+ return `<span class="prop-text">${escapeHtml(String(value))}</span>`;
384
+ }
385
+ var POPUP_TITLE_KEYS = ["name", "title", "nombre", "label", "id"];
386
+ var POPUP_CHIP_KEYS = /* @__PURE__ */ new Set(["churn", "color", "sector"]);
387
+ function isChipValue(value) {
388
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
389
+ }
390
+ function getSanitizedChipColor(value) {
391
+ if (typeof value !== "string") return null;
392
+ const trimmed = value.trim();
393
+ if (/^#([0-9a-fA-F]{3}){1,2}$/.test(trimmed)) {
394
+ return trimmed;
395
+ }
396
+ if (/^rgb\((\s*\d+\s*,){2}\s*\d+\s*\)$/.test(trimmed)) {
397
+ return trimmed;
398
+ }
399
+ if (/^rgba\((\s*\d+\s*,){3}\s*(0|1|0?\.\d+)\s*\)$/.test(trimmed)) {
400
+ return trimmed;
401
+ }
402
+ return null;
403
+ }
404
+ function getPopupTitle(entries) {
405
+ for (const candidate of POPUP_TITLE_KEYS) {
406
+ const match = entries.find((entry) => entry.normalized === candidate);
407
+ if (!match || match.value === null || match.value === void 0) continue;
408
+ const value = String(match.value).trim();
409
+ if (!value) continue;
410
+ return { title: value, key: match.key };
411
+ }
412
+ return null;
413
+ }
414
+ function renderProperties(properties) {
415
+ const description = extractDescriptionValue(properties);
416
+ const entries = Object.entries(properties).filter(([key]) => !DESCRIPTION_KEYS.has(key.toLowerCase())).map(([key, value]) => ({
417
+ key,
418
+ value,
419
+ normalized: key.trim().toLowerCase()
420
+ }));
421
+ if (!description && entries.length === 0) return "";
422
+ const titleEntry = getPopupTitle(entries);
423
+ const titleText = titleEntry?.title ?? "Detalle del elemento";
424
+ const chipEntries = entries.filter(
425
+ (entry) => POPUP_CHIP_KEYS.has(entry.normalized) && isChipValue(entry.value)
426
+ );
427
+ const listEntries = entries.filter((entry) => {
428
+ if (titleEntry && entry.key === titleEntry.key) return false;
429
+ if (chipEntries.find((chip) => chip.key === entry.key)) return false;
430
+ return true;
431
+ });
432
+ const descriptionHtml = description ? `<div class="popup-description">${escapeHtml(description)}</div>` : "";
433
+ const chipsHtml = chipEntries.length ? `<div class="popup-chip-row">${chipEntries.map((entry) => {
434
+ const label = escapeHtml(entry.key.replace(/_/g, " "));
435
+ const value = escapeHtml(String(entry.value));
436
+ const color = getSanitizedChipColor(entry.value);
437
+ const colorStyle = color ? ` style="--chip-color: ${color}"` : "";
438
+ return `<span class="popup-chip"${colorStyle}><span class="popup-chip-label">${label}</span><span class="popup-chip-value">${value}</span></span>`;
439
+ }).join("")}</div>` : "";
440
+ const rowsHtml = listEntries.map((entry) => {
441
+ const label = escapeHtml(entry.key.replace(/_/g, " "));
442
+ const valueHtml = renderPropertyValue(entry.value);
443
+ return `<div class="prop-key">${label}</div><div class="prop-value">${valueHtml}</div>`;
444
+ }).join("");
445
+ const listHtml = rowsHtml ? `<div class="prop-list">${rowsHtml}</div>` : "";
446
+ return `
447
+ <div class="feature-popup">
448
+ <div class="feature-popup-card">
449
+ <div class="feature-popup-header">
450
+ <p class="popup-eyebrow">Informaci\xF3n</p>
451
+ <h3 class="popup-title">${escapeHtml(titleText)}</h3>
452
+ </div>
453
+ <div class="feature-popup-body">
454
+ ${descriptionHtml}
455
+ ${chipsHtml}
456
+ ${listHtml}
457
+ </div>
458
+ </div>
459
+ </div>
460
+ `;
461
+ }
347
462
  function withAlpha(color, alpha) {
348
463
  const trimmed = color.trim();
349
464
  if (trimmed.startsWith("#")) {
@@ -412,40 +527,39 @@ function getFeatureStyleOverrides(feature) {
412
527
  function buildFeaturePopupHtml(feature) {
413
528
  const properties = feature?.properties;
414
529
  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>`);
530
+ const rendered = renderProperties(properties);
531
+ return rendered ? rendered : null;
532
+ }
533
+ var POINT_GEOMETRY_TYPES = /* @__PURE__ */ new Set(["Point", "MultiPoint"]);
534
+ function getGeometryType(feature) {
535
+ const t = feature?.geometry?.type;
536
+ return typeof t === "string" ? t : null;
537
+ }
538
+ function isPointGeometry(feature) {
539
+ const geometryType = getGeometryType(feature);
540
+ return geometryType !== null && POINT_GEOMETRY_TYPES.has(geometryType);
541
+ }
542
+ function isNonPointGeometry(feature) {
543
+ const geometryType = getGeometryType(feature);
544
+ return geometryType !== null && !POINT_GEOMETRY_TYPES.has(geometryType);
545
+ }
546
+ function buildFeatureCollection(features) {
547
+ return {
548
+ type: "FeatureCollection",
549
+ features
550
+ };
551
+ }
552
+ function pickIntersectFeature(baseFeature, candidates) {
553
+ if (!Array.isArray(candidates) || candidates.length === 0) return null;
554
+ const baseId = baseFeature?.id;
555
+ if (baseId !== void 0 && baseId !== null) {
556
+ const matchById = candidates.find((candidate) => candidate?.id === baseId);
557
+ if (matchById) return matchById;
447
558
  }
448
- return `<div>${parts.join("")}</div>`;
559
+ const matchWithDescription = candidates.find(
560
+ (candidate) => extractDescriptionValue(candidate?.properties)
561
+ );
562
+ return matchWithDescription ?? candidates[0];
449
563
  }
450
564
  function normalizeCenterTuple(center) {
451
565
  if (!center) return null;
@@ -566,6 +680,7 @@ var ZenitMap = (0, import_react.forwardRef)(({
566
680
  const [loadingMap, setLoadingMap] = (0, import_react.useState)(false);
567
681
  const [mapError, setMapError] = (0, import_react.useState)(null);
568
682
  const [mapInstance, setMapInstance] = (0, import_react.useState)(null);
683
+ const [panesReady, setPanesReady] = (0, import_react.useState)(false);
569
684
  const [currentZoom, setCurrentZoom] = (0, import_react.useState)(initialZoom ?? DEFAULT_ZOOM);
570
685
  const [isMobile, setIsMobile] = (0, import_react.useState)(() => {
571
686
  if (typeof window === "undefined") return false;
@@ -880,16 +995,23 @@ var ZenitMap = (0, import_react.forwardRef)(({
880
995
  (targetMap, targetLayers) => {
881
996
  const baseZIndex = 400;
882
997
  targetLayers.forEach((layer) => {
883
- const paneName = `zenit-layer-${layer.layerId}`;
884
- const pane = targetMap.getPane(paneName) ?? targetMap.createPane(paneName);
885
998
  const order = Number.isFinite(layer.displayOrder) ? layer.displayOrder : 0;
886
- pane.style.zIndex = String(baseZIndex + order);
999
+ const fillPaneName = `zenit-layer-${layer.layerId}-fill`;
1000
+ const pointPaneName = `zenit-layer-${layer.layerId}-points`;
1001
+ const labelPaneName = `zenit-layer-${layer.layerId}-labels`;
1002
+ const fillPane = targetMap.getPane(fillPaneName) ?? targetMap.createPane(fillPaneName);
1003
+ const pointPane = targetMap.getPane(pointPaneName) ?? targetMap.createPane(pointPaneName);
1004
+ const labelPane = targetMap.getPane(labelPaneName) ?? targetMap.createPane(labelPaneName);
1005
+ fillPane.style.zIndex = String(baseZIndex + order);
1006
+ pointPane.style.zIndex = String(baseZIndex + order + 1e3);
1007
+ labelPane.style.zIndex = String(baseZIndex + order + 2e3);
887
1008
  });
888
1009
  },
889
1010
  []
890
1011
  );
891
1012
  const handleMapReady = (0, import_react.useCallback)(
892
1013
  (instance) => {
1014
+ setPanesReady(false);
893
1015
  setMapInstance(instance);
894
1016
  onMapReady?.(instance);
895
1017
  },
@@ -897,6 +1019,7 @@ var ZenitMap = (0, import_react.forwardRef)(({
897
1019
  );
898
1020
  (0, import_react.useEffect)(() => {
899
1021
  if (!mapInstance) {
1022
+ setPanesReady(false);
900
1023
  return;
901
1024
  }
902
1025
  if (orderedLayers.length === 0) {
@@ -907,6 +1030,11 @@ var ZenitMap = (0, import_react.forwardRef)(({
907
1030
  displayOrder: layer.displayOrder
908
1031
  }));
909
1032
  ensureLayerPanes(mapInstance, layerTargets);
1033
+ const first = layerTargets[0];
1034
+ const testPane = mapInstance.getPane(`zenit-layer-${first.layerId}-labels`);
1035
+ if (testPane) {
1036
+ setPanesReady(true);
1037
+ }
910
1038
  }, [mapInstance, orderedLayers, ensureLayerPanes]);
911
1039
  const overlayOnEachFeature = (0, import_react.useMemo)(() => {
912
1040
  return (feature, layer) => {
@@ -924,7 +1052,14 @@ var ZenitMap = (0, import_react.forwardRef)(({
924
1052
  if (featureInfoMode === "popup") {
925
1053
  const content = buildFeaturePopupHtml(feature);
926
1054
  if (content) {
927
- layer.bindPopup(content, { maxWidth: 320 });
1055
+ layer.bindPopup(content, {
1056
+ maxWidth: 420,
1057
+ minWidth: 280,
1058
+ className: "zenit-feature-popup-shell",
1059
+ autoPan: true,
1060
+ closeButton: true,
1061
+ offset: import_leaflet.default.point(0, -24)
1062
+ });
928
1063
  }
929
1064
  }
930
1065
  if (isPointFeature && layer.bindTooltip) {
@@ -935,7 +1070,37 @@ var ZenitMap = (0, import_react.forwardRef)(({
935
1070
  className: "zenit-map-tooltip"
936
1071
  });
937
1072
  }
938
- layer.on("click", () => onFeatureClick?.(feature, layerId));
1073
+ layer.on("click", () => {
1074
+ if (featureInfoMode === "popup" && client && layerId !== void 0 && !extractDescriptionValue(feature?.properties) && feature?.geometry) {
1075
+ const trackedFeature = feature;
1076
+ if (!trackedFeature.__zenit_popup_loaded) {
1077
+ trackedFeature.__zenit_popup_loaded = true;
1078
+ client.layers.getLayerGeoJsonIntersect({
1079
+ id: layerId,
1080
+ geometry: feature.geometry
1081
+ }).then((response) => {
1082
+ const candidates = response.data?.features ?? [];
1083
+ const resolved = pickIntersectFeature(feature, candidates);
1084
+ if (!resolved?.properties) return;
1085
+ const mergedProperties = {
1086
+ ...trackedFeature.properties ?? {},
1087
+ ...resolved.properties
1088
+ };
1089
+ trackedFeature.properties = mergedProperties;
1090
+ const updatedHtml = buildFeaturePopupHtml({
1091
+ ...feature,
1092
+ properties: mergedProperties
1093
+ });
1094
+ if (updatedHtml && layer.setPopupContent) {
1095
+ layer.setPopupContent(updatedHtml);
1096
+ }
1097
+ }).catch(() => {
1098
+ trackedFeature.__zenit_popup_loaded = false;
1099
+ });
1100
+ }
1101
+ }
1102
+ onFeatureClick?.(feature, layerId);
1103
+ });
939
1104
  layer.on("mouseover", () => {
940
1105
  if (layer instanceof import_leaflet.default.Path && originalStyle) {
941
1106
  layer.setStyle({
@@ -959,7 +1124,7 @@ var ZenitMap = (0, import_react.forwardRef)(({
959
1124
  }
960
1125
  });
961
1126
  };
962
- }, [featureInfoMode, onFeatureClick, onFeatureHover]);
1127
+ }, [client, featureInfoMode, onFeatureClick, onFeatureHover]);
963
1128
  const buildLayerStyle = (layerId, baseOpacity, feature, layerType) => {
964
1129
  const style = resolveLayerStyle(layerId);
965
1130
  const featureStyleOverrides = getFeatureStyleOverrides(feature);
@@ -1140,22 +1305,50 @@ var ZenitMap = (0, import_react.forwardRef)(({
1140
1305
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ZoomBasedOpacityHandler, { onZoomChange: handleZoomChange }),
1141
1306
  orderedLayers.map((layerState) => {
1142
1307
  const baseOpacity = layerState.effective?.baseOpacity ?? layerState.effective?.opacity ?? 1;
1143
- const paneName = `zenit-layer-${layerState.mapLayer.layerId}`;
1308
+ const fillPaneName = `zenit-layer-${layerState.mapLayer.layerId}-fill`;
1309
+ const pointsPaneName = `zenit-layer-${layerState.mapLayer.layerId}-points`;
1310
+ const labelPaneName = `zenit-layer-${layerState.mapLayer.layerId}-labels`;
1144
1311
  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
- );
1312
+ const data = layerState.data?.features ?? [];
1313
+ const fillFeatures = data.filter(isNonPointGeometry);
1314
+ const pointFeatures = data.filter(isPointGeometry);
1315
+ const fillData = fillFeatures.length > 0 ? buildFeatureCollection(fillFeatures) : null;
1316
+ const pointsData = pointFeatures.length > 0 ? buildFeatureCollection(pointFeatures) : null;
1317
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.default.Fragment, { children: [
1318
+ fillData && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1319
+ import_react_leaflet.GeoJSON,
1320
+ {
1321
+ data: fillData,
1322
+ pane: panesReady && mapInstance?.getPane(fillPaneName) ? fillPaneName : void 0,
1323
+ style: (feature) => buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType),
1324
+ onEachFeature: overlayOnEachFeature
1325
+ }
1326
+ ),
1327
+ pointsData && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1328
+ import_react_leaflet.GeoJSON,
1329
+ {
1330
+ data: pointsData,
1331
+ pane: panesReady && mapInstance?.getPane(pointsPaneName) ? pointsPaneName : void 0,
1332
+ pointToLayer: (feature, latlng) => import_leaflet.default.circleMarker(latlng, {
1333
+ radius: isMobile ? 8 : 6,
1334
+ ...buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType)
1335
+ }),
1336
+ onEachFeature: overlayOnEachFeature
1337
+ }
1338
+ ),
1339
+ panesReady && mapInstance?.getPane(labelPaneName) ? labelMarkers.filter(
1340
+ (marker) => String(marker.layerId) === String(layerState.mapLayer.layerId)
1341
+ ).map((marker) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1342
+ import_react_leaflet.Marker,
1343
+ {
1344
+ position: marker.position,
1345
+ icon: buildLabelIcon(marker.label, marker.opacity, marker.color),
1346
+ interactive: false,
1347
+ pane: labelPaneName
1348
+ },
1349
+ marker.key
1350
+ )) : null
1351
+ ] }, layerState.mapLayer.layerId.toString());
1159
1352
  }),
1160
1353
  overlayGeojson && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1161
1354
  import_react_leaflet.GeoJSON,
@@ -1165,16 +1358,7 @@ var ZenitMap = (0, import_react.forwardRef)(({
1165
1358
  onEachFeature: overlayOnEachFeature
1166
1359
  },
1167
1360
  "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
- ))
1361
+ )
1178
1362
  ]
1179
1363
  },
1180
1364
  String(mapId)
@@ -2586,9 +2770,19 @@ var FloatingChatBox = ({
2586
2770
  getAccessToken,
2587
2771
  onActionClick,
2588
2772
  onOpenChange,
2589
- hideButton
2773
+ hideButton,
2774
+ open: openProp
2590
2775
  }) => {
2591
- const [open, setOpen] = (0, import_react4.useState)(false);
2776
+ const isControlled = openProp !== void 0;
2777
+ const [internalOpen, setInternalOpen] = (0, import_react4.useState)(false);
2778
+ const open = isControlled ? openProp : internalOpen;
2779
+ const setOpen = (0, import_react4.useCallback)((value) => {
2780
+ const newValue = typeof value === "function" ? value(open) : value;
2781
+ if (!isControlled) {
2782
+ setInternalOpen(newValue);
2783
+ }
2784
+ onOpenChange?.(newValue);
2785
+ }, [isControlled, open, onOpenChange]);
2592
2786
  const [expanded, setExpanded] = (0, import_react4.useState)(false);
2593
2787
  const [messages, setMessages] = (0, import_react4.useState)([]);
2594
2788
  const [inputValue, setInputValue] = (0, import_react4.useState)("");
@@ -2605,9 +2799,6 @@ var FloatingChatBox = ({
2605
2799
  }, [accessToken, baseUrl, getAccessToken]);
2606
2800
  const { sendMessage: sendMessage2, isStreaming, streamingText, completeResponse } = useSendMessageStream(chatConfig);
2607
2801
  const canSend = Boolean(mapId) && Boolean(baseUrl) && inputValue.trim().length > 0 && !isStreaming;
2608
- (0, import_react4.useEffect)(() => {
2609
- onOpenChange?.(open);
2610
- }, [open, onOpenChange]);
2611
2802
  (0, import_react4.useEffect)(() => {
2612
2803
  if (open && isMobile) {
2613
2804
  setExpanded(true);
@@ -2760,6 +2951,13 @@ var FloatingChatBox = ({
2760
2951
  ] }, index)) })
2761
2952
  ] });
2762
2953
  };
2954
+ const handleActionClick = (0, import_react4.useCallback)((action) => {
2955
+ if (isStreaming) return;
2956
+ setOpen(false);
2957
+ requestAnimationFrame(() => {
2958
+ onActionClick?.(action);
2959
+ });
2960
+ }, [isStreaming, setOpen, onActionClick]);
2763
2961
  const renderActions = (response) => {
2764
2962
  if (!response?.suggestedActions?.length) return null;
2765
2963
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.actionsSection, children: [
@@ -2773,7 +2971,7 @@ var FloatingChatBox = ({
2773
2971
  opacity: isStreaming ? 0.5 : 1,
2774
2972
  cursor: isStreaming ? "not-allowed" : "pointer"
2775
2973
  },
2776
- onClick: () => !isStreaming && onActionClick?.(action),
2974
+ onClick: () => handleActionClick(action),
2777
2975
  disabled: isStreaming,
2778
2976
  onMouseEnter: (e) => {
2779
2977
  if (!isStreaming) {