zenit-sdk 0.0.5 → 0.0.6

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 React, { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, forwardRef } from "react";
2
+ import { 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,121 +297,6 @@ 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
- function normalizeDescriptionValue(value) {
302
- if (value === void 0 || value === null) return null;
303
- if (typeof value === "string") {
304
- const trimmed = value.trim();
305
- return trimmed ? trimmed : null;
306
- }
307
- if (typeof value === "number" || typeof value === "boolean") {
308
- return String(value);
309
- }
310
- return null;
311
- }
312
- function extractDescriptionValue(properties) {
313
- if (!properties) return null;
314
- const matches = Object.entries(properties).find(
315
- ([key]) => DESCRIPTION_KEYS.has(key.toLowerCase())
316
- );
317
- if (!matches) return null;
318
- return normalizeDescriptionValue(matches[1]);
319
- }
320
- function safeJsonStringify(value) {
321
- try {
322
- const json = JSON.stringify(value, null, 2);
323
- if (json !== void 0) return json;
324
- } catch {
325
- }
326
- return String(value);
327
- }
328
- function renderPropertyValue(value) {
329
- if (value === null || value === void 0) {
330
- return '<span class="prop-empty">\u2014</span>';
331
- }
332
- if (typeof value === "object") {
333
- const json = safeJsonStringify(value);
334
- return `<pre class="prop-json">${escapeHtml(json)}</pre>`;
335
- }
336
- return `<span class="prop-text">${escapeHtml(String(value))}</span>`;
337
- }
338
- var POPUP_TITLE_KEYS = ["name", "title", "nombre", "label", "id"];
339
- var POPUP_CHIP_KEYS = /* @__PURE__ */ new Set(["churn", "color", "sector"]);
340
- function isChipValue(value) {
341
- return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
342
- }
343
- function getSanitizedChipColor(value) {
344
- if (typeof value !== "string") return null;
345
- const trimmed = value.trim();
346
- if (/^#([0-9a-fA-F]{3}){1,2}$/.test(trimmed)) {
347
- return trimmed;
348
- }
349
- if (/^rgb\((\s*\d+\s*,){2}\s*\d+\s*\)$/.test(trimmed)) {
350
- return trimmed;
351
- }
352
- if (/^rgba\((\s*\d+\s*,){3}\s*(0|1|0?\.\d+)\s*\)$/.test(trimmed)) {
353
- return trimmed;
354
- }
355
- return null;
356
- }
357
- function getPopupTitle(entries) {
358
- for (const candidate of POPUP_TITLE_KEYS) {
359
- const match = entries.find((entry) => entry.normalized === candidate);
360
- if (!match || match.value === null || match.value === void 0) continue;
361
- const value = String(match.value).trim();
362
- if (!value) continue;
363
- return { title: value, key: match.key };
364
- }
365
- return null;
366
- }
367
- function renderProperties(properties) {
368
- const description = extractDescriptionValue(properties);
369
- const entries = Object.entries(properties).filter(([key]) => !DESCRIPTION_KEYS.has(key.toLowerCase())).map(([key, value]) => ({
370
- key,
371
- value,
372
- normalized: key.trim().toLowerCase()
373
- }));
374
- if (!description && entries.length === 0) return "";
375
- const titleEntry = getPopupTitle(entries);
376
- const titleText = titleEntry?.title ?? "Detalle del elemento";
377
- const chipEntries = entries.filter(
378
- (entry) => POPUP_CHIP_KEYS.has(entry.normalized) && isChipValue(entry.value)
379
- );
380
- const listEntries = entries.filter((entry) => {
381
- if (titleEntry && entry.key === titleEntry.key) return false;
382
- if (chipEntries.find((chip) => chip.key === entry.key)) return false;
383
- return true;
384
- });
385
- const descriptionHtml = description ? `<div class="popup-description">${escapeHtml(description)}</div>` : "";
386
- const chipsHtml = chipEntries.length ? `<div class="popup-chip-row">${chipEntries.map((entry) => {
387
- const label = escapeHtml(entry.key.replace(/_/g, " "));
388
- const value = escapeHtml(String(entry.value));
389
- const color = getSanitizedChipColor(entry.value);
390
- const colorStyle = color ? ` style="--chip-color: ${color}"` : "";
391
- return `<span class="popup-chip"${colorStyle}><span class="popup-chip-label">${label}</span><span class="popup-chip-value">${value}</span></span>`;
392
- }).join("")}</div>` : "";
393
- const rowsHtml = listEntries.map((entry) => {
394
- const label = escapeHtml(entry.key.replace(/_/g, " "));
395
- const valueHtml = renderPropertyValue(entry.value);
396
- return `<div class="prop-key">${label}</div><div class="prop-value">${valueHtml}</div>`;
397
- }).join("");
398
- const listHtml = rowsHtml ? `<div class="prop-list">${rowsHtml}</div>` : "";
399
- return `
400
- <div class="feature-popup">
401
- <div class="feature-popup-card">
402
- <div class="feature-popup-header">
403
- <p class="popup-eyebrow">Informaci\xF3n</p>
404
- <h3 class="popup-title">${escapeHtml(titleText)}</h3>
405
- </div>
406
- <div class="feature-popup-body">
407
- ${descriptionHtml}
408
- ${chipsHtml}
409
- ${listHtml}
410
- </div>
411
- </div>
412
- </div>
413
- `;
414
- }
415
300
  function withAlpha(color, alpha) {
416
301
  const trimmed = color.trim();
417
302
  if (trimmed.startsWith("#")) {
@@ -480,39 +365,40 @@ function getFeatureStyleOverrides(feature) {
480
365
  function buildFeaturePopupHtml(feature) {
481
366
  const properties = feature?.properties;
482
367
  if (!properties) return null;
483
- const rendered = renderProperties(properties);
484
- return rendered ? rendered : null;
485
- }
486
- var POINT_GEOMETRY_TYPES = /* @__PURE__ */ new Set(["Point", "MultiPoint"]);
487
- function getGeometryType(feature) {
488
- const t = feature?.geometry?.type;
489
- return typeof t === "string" ? t : null;
490
- }
491
- function isPointGeometry(feature) {
492
- const geometryType = getGeometryType(feature);
493
- return geometryType !== null && POINT_GEOMETRY_TYPES.has(geometryType);
494
- }
495
- function isNonPointGeometry(feature) {
496
- const geometryType = getGeometryType(feature);
497
- return geometryType !== null && !POINT_GEOMETRY_TYPES.has(geometryType);
498
- }
499
- function buildFeatureCollection(features) {
500
- return {
501
- type: "FeatureCollection",
502
- features
503
- };
504
- }
505
- function pickIntersectFeature(baseFeature, candidates) {
506
- if (!Array.isArray(candidates) || candidates.length === 0) return null;
507
- const baseId = baseFeature?.id;
508
- if (baseId !== void 0 && baseId !== null) {
509
- const matchById = candidates.find((candidate) => candidate?.id === baseId);
510
- if (matchById) return matchById;
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>`);
511
389
  }
512
- const matchWithDescription = candidates.find(
513
- (candidate) => extractDescriptionValue(candidate?.properties)
514
- );
515
- return matchWithDescription ?? candidates[0];
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>`;
516
402
  }
517
403
  function normalizeCenterTuple(center) {
518
404
  if (!center) return null;
@@ -633,7 +519,6 @@ var ZenitMap = forwardRef(({
633
519
  const [loadingMap, setLoadingMap] = useState(false);
634
520
  const [mapError, setMapError] = useState(null);
635
521
  const [mapInstance, setMapInstance] = useState(null);
636
- const [panesReady, setPanesReady] = useState(false);
637
522
  const [currentZoom, setCurrentZoom] = useState(initialZoom ?? DEFAULT_ZOOM);
638
523
  const [isMobile, setIsMobile] = useState(() => {
639
524
  if (typeof window === "undefined") return false;
@@ -948,23 +833,16 @@ var ZenitMap = forwardRef(({
948
833
  (targetMap, targetLayers) => {
949
834
  const baseZIndex = 400;
950
835
  targetLayers.forEach((layer) => {
836
+ const paneName = `zenit-layer-${layer.layerId}`;
837
+ const pane = targetMap.getPane(paneName) ?? targetMap.createPane(paneName);
951
838
  const order = Number.isFinite(layer.displayOrder) ? layer.displayOrder : 0;
952
- const fillPaneName = `zenit-layer-${layer.layerId}-fill`;
953
- const pointPaneName = `zenit-layer-${layer.layerId}-points`;
954
- const labelPaneName = `zenit-layer-${layer.layerId}-labels`;
955
- const fillPane = targetMap.getPane(fillPaneName) ?? targetMap.createPane(fillPaneName);
956
- const pointPane = targetMap.getPane(pointPaneName) ?? targetMap.createPane(pointPaneName);
957
- const labelPane = targetMap.getPane(labelPaneName) ?? targetMap.createPane(labelPaneName);
958
- fillPane.style.zIndex = String(baseZIndex + order);
959
- pointPane.style.zIndex = String(baseZIndex + order + 1e3);
960
- labelPane.style.zIndex = String(baseZIndex + order + 2e3);
839
+ pane.style.zIndex = String(baseZIndex + order);
961
840
  });
962
841
  },
963
842
  []
964
843
  );
965
844
  const handleMapReady = useCallback(
966
845
  (instance) => {
967
- setPanesReady(false);
968
846
  setMapInstance(instance);
969
847
  onMapReady?.(instance);
970
848
  },
@@ -972,7 +850,6 @@ var ZenitMap = forwardRef(({
972
850
  );
973
851
  useEffect(() => {
974
852
  if (!mapInstance) {
975
- setPanesReady(false);
976
853
  return;
977
854
  }
978
855
  if (orderedLayers.length === 0) {
@@ -983,11 +860,6 @@ var ZenitMap = forwardRef(({
983
860
  displayOrder: layer.displayOrder
984
861
  }));
985
862
  ensureLayerPanes(mapInstance, layerTargets);
986
- const first = layerTargets[0];
987
- const testPane = mapInstance.getPane(`zenit-layer-${first.layerId}-labels`);
988
- if (testPane) {
989
- setPanesReady(true);
990
- }
991
863
  }, [mapInstance, orderedLayers, ensureLayerPanes]);
992
864
  const overlayOnEachFeature = useMemo(() => {
993
865
  return (feature, layer) => {
@@ -1005,14 +877,7 @@ var ZenitMap = forwardRef(({
1005
877
  if (featureInfoMode === "popup") {
1006
878
  const content = buildFeaturePopupHtml(feature);
1007
879
  if (content) {
1008
- layer.bindPopup(content, {
1009
- maxWidth: 420,
1010
- minWidth: 280,
1011
- className: "zenit-feature-popup-shell",
1012
- autoPan: true,
1013
- closeButton: true,
1014
- offset: L.point(0, -24)
1015
- });
880
+ layer.bindPopup(content, { maxWidth: 320 });
1016
881
  }
1017
882
  }
1018
883
  if (isPointFeature && layer.bindTooltip) {
@@ -1023,37 +888,7 @@ var ZenitMap = forwardRef(({
1023
888
  className: "zenit-map-tooltip"
1024
889
  });
1025
890
  }
1026
- layer.on("click", () => {
1027
- if (featureInfoMode === "popup" && client && layerId !== void 0 && !extractDescriptionValue(feature?.properties) && feature?.geometry) {
1028
- const trackedFeature = feature;
1029
- if (!trackedFeature.__zenit_popup_loaded) {
1030
- trackedFeature.__zenit_popup_loaded = true;
1031
- client.layers.getLayerGeoJsonIntersect({
1032
- id: layerId,
1033
- geometry: feature.geometry
1034
- }).then((response) => {
1035
- const candidates = response.data?.features ?? [];
1036
- const resolved = pickIntersectFeature(feature, candidates);
1037
- if (!resolved?.properties) return;
1038
- const mergedProperties = {
1039
- ...trackedFeature.properties ?? {},
1040
- ...resolved.properties
1041
- };
1042
- trackedFeature.properties = mergedProperties;
1043
- const updatedHtml = buildFeaturePopupHtml({
1044
- ...feature,
1045
- properties: mergedProperties
1046
- });
1047
- if (updatedHtml && layer.setPopupContent) {
1048
- layer.setPopupContent(updatedHtml);
1049
- }
1050
- }).catch(() => {
1051
- trackedFeature.__zenit_popup_loaded = false;
1052
- });
1053
- }
1054
- }
1055
- onFeatureClick?.(feature, layerId);
1056
- });
891
+ layer.on("click", () => onFeatureClick?.(feature, layerId));
1057
892
  layer.on("mouseover", () => {
1058
893
  if (layer instanceof L.Path && originalStyle) {
1059
894
  layer.setStyle({
@@ -1077,7 +912,7 @@ var ZenitMap = forwardRef(({
1077
912
  }
1078
913
  });
1079
914
  };
1080
- }, [client, featureInfoMode, onFeatureClick, onFeatureHover]);
915
+ }, [featureInfoMode, onFeatureClick, onFeatureHover]);
1081
916
  const buildLayerStyle = (layerId, baseOpacity, feature, layerType) => {
1082
917
  const style = resolveLayerStyle(layerId);
1083
918
  const featureStyleOverrides = getFeatureStyleOverrides(feature);
@@ -1258,50 +1093,22 @@ var ZenitMap = forwardRef(({
1258
1093
  /* @__PURE__ */ jsx(ZoomBasedOpacityHandler, { onZoomChange: handleZoomChange }),
1259
1094
  orderedLayers.map((layerState) => {
1260
1095
  const baseOpacity = layerState.effective?.baseOpacity ?? layerState.effective?.opacity ?? 1;
1261
- const fillPaneName = `zenit-layer-${layerState.mapLayer.layerId}-fill`;
1262
- const pointsPaneName = `zenit-layer-${layerState.mapLayer.layerId}-points`;
1263
- const labelPaneName = `zenit-layer-${layerState.mapLayer.layerId}-labels`;
1096
+ const paneName = `zenit-layer-${layerState.mapLayer.layerId}`;
1264
1097
  const layerType = layerState.layer?.layerType ?? layerState.mapLayer.layerType ?? void 0;
1265
- const data = layerState.data?.features ?? [];
1266
- const fillFeatures = data.filter(isNonPointGeometry);
1267
- const pointFeatures = data.filter(isPointGeometry);
1268
- const fillData = fillFeatures.length > 0 ? buildFeatureCollection(fillFeatures) : null;
1269
- const pointsData = pointFeatures.length > 0 ? buildFeatureCollection(pointFeatures) : null;
1270
- return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1271
- fillData && /* @__PURE__ */ jsx(
1272
- GeoJSON,
1273
- {
1274
- data: fillData,
1275
- pane: panesReady && mapInstance?.getPane(fillPaneName) ? fillPaneName : void 0,
1276
- style: (feature) => buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType),
1277
- onEachFeature: overlayOnEachFeature
1278
- }
1279
- ),
1280
- pointsData && /* @__PURE__ */ jsx(
1281
- GeoJSON,
1282
- {
1283
- data: pointsData,
1284
- pane: panesReady && mapInstance?.getPane(pointsPaneName) ? pointsPaneName : void 0,
1285
- pointToLayer: (feature, latlng) => L.circleMarker(latlng, {
1286
- radius: isMobile ? 8 : 6,
1287
- ...buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType)
1288
- }),
1289
- onEachFeature: overlayOnEachFeature
1290
- }
1291
- ),
1292
- panesReady && mapInstance?.getPane(labelPaneName) ? labelMarkers.filter(
1293
- (marker) => String(marker.layerId) === String(layerState.mapLayer.layerId)
1294
- ).map((marker) => /* @__PURE__ */ jsx(
1295
- Marker,
1296
- {
1297
- position: marker.position,
1298
- icon: buildLabelIcon(marker.label, marker.opacity, marker.color),
1299
- interactive: false,
1300
- pane: labelPaneName
1301
- },
1302
- marker.key
1303
- )) : null
1304
- ] }, layerState.mapLayer.layerId.toString());
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
+ );
1305
1112
  }),
1306
1113
  overlayGeojson && /* @__PURE__ */ jsx(
1307
1114
  GeoJSON,
@@ -1311,7 +1118,16 @@ var ZenitMap = forwardRef(({
1311
1118
  onEachFeature: overlayOnEachFeature
1312
1119
  },
1313
1120
  "zenit-overlay-geojson"
1314
- )
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
+ ))
1315
1131
  ]
1316
1132
  },
1317
1133
  String(mapId)
@@ -2727,19 +2543,9 @@ var FloatingChatBox = ({
2727
2543
  getAccessToken,
2728
2544
  onActionClick,
2729
2545
  onOpenChange,
2730
- hideButton,
2731
- open: openProp
2546
+ hideButton
2732
2547
  }) => {
2733
- const isControlled = openProp !== void 0;
2734
- const [internalOpen, setInternalOpen] = useState4(false);
2735
- const open = isControlled ? openProp : internalOpen;
2736
- const setOpen = useCallback3((value) => {
2737
- const newValue = typeof value === "function" ? value(open) : value;
2738
- if (!isControlled) {
2739
- setInternalOpen(newValue);
2740
- }
2741
- onOpenChange?.(newValue);
2742
- }, [isControlled, open, onOpenChange]);
2548
+ const [open, setOpen] = useState4(false);
2743
2549
  const [expanded, setExpanded] = useState4(false);
2744
2550
  const [messages, setMessages] = useState4([]);
2745
2551
  const [inputValue, setInputValue] = useState4("");
@@ -2756,6 +2562,9 @@ var FloatingChatBox = ({
2756
2562
  }, [accessToken, baseUrl, getAccessToken]);
2757
2563
  const { sendMessage: sendMessage2, isStreaming, streamingText, completeResponse } = useSendMessageStream(chatConfig);
2758
2564
  const canSend = Boolean(mapId) && Boolean(baseUrl) && inputValue.trim().length > 0 && !isStreaming;
2565
+ useEffect3(() => {
2566
+ onOpenChange?.(open);
2567
+ }, [open, onOpenChange]);
2759
2568
  useEffect3(() => {
2760
2569
  if (open && isMobile) {
2761
2570
  setExpanded(true);
@@ -2908,13 +2717,6 @@ var FloatingChatBox = ({
2908
2717
  ] }, index)) })
2909
2718
  ] });
2910
2719
  };
2911
- const handleActionClick = useCallback3((action) => {
2912
- if (isStreaming) return;
2913
- setOpen(false);
2914
- requestAnimationFrame(() => {
2915
- onActionClick?.(action);
2916
- });
2917
- }, [isStreaming, setOpen, onActionClick]);
2918
2720
  const renderActions = (response) => {
2919
2721
  if (!response?.suggestedActions?.length) return null;
2920
2722
  return /* @__PURE__ */ jsxs4("div", { style: styles.actionsSection, children: [
@@ -2928,7 +2730,7 @@ var FloatingChatBox = ({
2928
2730
  opacity: isStreaming ? 0.5 : 1,
2929
2731
  cursor: isStreaming ? "not-allowed" : "pointer"
2930
2732
  },
2931
- onClick: () => handleActionClick(action),
2733
+ onClick: () => !isStreaming && onActionClick?.(action),
2932
2734
  disabled: isStreaming,
2933
2735
  onMouseEnter: (e) => {
2934
2736
  if (!isStreaming) {
@@ -3255,4 +3057,4 @@ export {
3255
3057
  useSendMessageStream,
3256
3058
  FloatingChatBox
3257
3059
  };
3258
- //# sourceMappingURL=chunk-LX2N2BXV.mjs.map
3060
+ //# sourceMappingURL=chunk-R73LRYVJ.mjs.map