zenit-sdk 0.0.2 → 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.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,20 +17,40 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
21
31
  var src_exports = {};
22
32
  __export(src_exports, {
33
+ ChevronLeft: () => import_lucide_react.ChevronLeft,
34
+ ChevronRight: () => import_lucide_react.ChevronRight,
23
35
  DEFAULT_MAX_FEATURES_FULL_GEOJSON: () => DEFAULT_MAX_FEATURES_FULL_GEOJSON,
36
+ Eye: () => import_lucide_react.Eye,
37
+ EyeOff: () => import_lucide_react.EyeOff,
24
38
  FilterEngine: () => FilterEngine,
39
+ FloatingChatBox: () => FloatingChatBox,
25
40
  HttpClient: () => HttpClient,
41
+ Layers: () => import_lucide_react.Layers,
42
+ Upload: () => import_lucide_react.Upload,
43
+ X: () => import_lucide_react.X,
26
44
  ZenitAuthClient: () => ZenitAuthClient,
27
45
  ZenitCatalogNotSupportedError: () => ZenitCatalogNotSupportedError,
28
46
  ZenitClient: () => ZenitClient,
47
+ ZenitFeatureFilterPanel: () => ZenitFeatureFilterPanel,
48
+ ZenitLayerManager: () => ZenitLayerManager,
29
49
  ZenitLayersClient: () => ZenitLayersClient,
50
+ ZenitMap: () => ZenitMap,
30
51
  ZenitMapsClient: () => ZenitMapsClient,
31
52
  ZenitSdkAuthClient: () => ZenitSdkAuthClient,
53
+ ZoomIn: () => import_lucide_react.ZoomIn,
32
54
  applyFilteredGeoJSONStrategy: () => applyFilteredGeoJSONStrategy,
33
55
  applyLayerOverrides: () => applyLayerOverrides,
34
56
  applyPrefiltersToFeatureCollection: () => applyPrefiltersToFeatureCollection,
@@ -37,22 +59,34 @@ __export(src_exports, {
37
59
  buildLayerFilters: () => buildLayerFilters,
38
60
  buildLimitedMessage: () => buildLimitedMessage,
39
61
  centroidFromBBox: () => centroidFromBBox,
62
+ clampNumber: () => clampNumber,
63
+ clampOpacity: () => clampOpacity3,
40
64
  computeBBoxFromFeature: () => computeBBoxFromFeature,
41
65
  createChatService: () => createChatService,
42
66
  decorateFeaturesWithLayerId: () => decorateFeaturesWithLayerId,
43
67
  extractMapDto: () => extractMapDto,
44
68
  filterEngine: () => filterEngine,
69
+ getAccentByLayerId: () => getAccentByLayerId,
45
70
  getCatalogSupport: () => getCatalogSupport,
71
+ getEffectiveLayerOpacity: () => getEffectiveLayerOpacity,
72
+ getLayerColor: () => getLayerColor,
73
+ getLayerZoomOpacityFactor: () => getLayerZoomOpacityFactor,
74
+ getStyleByLayerId: () => getStyleByLayerId,
75
+ getZoomOpacityFactor: () => getZoomOpacityFactor,
46
76
  initLayerStates: () => initLayerStates,
77
+ isPolygonLayer: () => isPolygonLayer,
47
78
  normalizeBbox: () => normalizeBbox,
48
79
  normalizeMapCenter: () => normalizeMapCenter,
49
80
  normalizeMapLayers: () => normalizeMapLayers,
50
81
  resetOverrides: () => resetOverrides,
82
+ resolveLayerAccent: () => resolveLayerAccent,
51
83
  resolveLayerStrategy: () => resolveLayerStrategy,
52
84
  sendMessage: () => sendMessage,
53
85
  sendMessageStream: () => sendMessageStream,
54
86
  shouldSkipGeojsonDownload: () => shouldSkipGeojsonDownload,
55
- shouldUseFilterMultiple: () => shouldUseFilterMultiple
87
+ shouldUseFilterMultiple: () => shouldUseFilterMultiple,
88
+ useSendMessage: () => useSendMessage,
89
+ useSendMessageStream: () => useSendMessageStream
56
90
  });
57
91
  module.exports = __toCommonJS(src_exports);
58
92
 
@@ -1367,17 +1401,2931 @@ function normalizeBbox(input) {
1367
1401
  console.warn("[normalizeBbox] Unsupported bbox format:", typeof input);
1368
1402
  return null;
1369
1403
  }
1404
+
1405
+ // src/react/ZenitMap.tsx
1406
+ var import_react = __toESM(require("react"));
1407
+ var import_react_leaflet = require("react-leaflet");
1408
+ var import_leaflet = __toESM(require("leaflet"));
1409
+
1410
+ // src/react/layerStyleHelpers.ts
1411
+ function resolveLayerAccent(style) {
1412
+ if (!style) return null;
1413
+ return style.fillColor ?? style.color ?? null;
1414
+ }
1415
+ function getLayerColor(style, fallback = "#94a3b8") {
1416
+ return resolveLayerAccent(style) ?? fallback;
1417
+ }
1418
+ function getStyleByLayerId(layerId, mapLayers) {
1419
+ if (!mapLayers) return null;
1420
+ const match = mapLayers.find((ml) => String(ml.layerId) === String(layerId));
1421
+ return match?.style ?? null;
1422
+ }
1423
+ function getAccentByLayerId(layerId, mapLayers) {
1424
+ const style = getStyleByLayerId(layerId, mapLayers);
1425
+ return resolveLayerAccent(style);
1426
+ }
1427
+
1428
+ // src/react/zoomOpacity.ts
1429
+ var DEFAULT_OPTIONS = {
1430
+ minZoom: 10,
1431
+ maxZoom: 17,
1432
+ minFactor: 0.6,
1433
+ maxFactor: 1,
1434
+ minOpacity: 0.1,
1435
+ maxOpacity: 0.92
1436
+ };
1437
+ function clampNumber(value, min, max) {
1438
+ return Math.min(max, Math.max(min, value));
1439
+ }
1440
+ function clampOpacity3(value) {
1441
+ return clampNumber(value, 0, 1);
1442
+ }
1443
+ function isPolygonLayer(layerType, geometryType) {
1444
+ const candidate = (layerType ?? geometryType ?? "").toLowerCase();
1445
+ return candidate === "polygon" || candidate === "multipolygon";
1446
+ }
1447
+ function getZoomOpacityFactor(zoom, options) {
1448
+ const settings = { ...DEFAULT_OPTIONS, ...options };
1449
+ const { minZoom, maxZoom, minFactor, maxFactor } = settings;
1450
+ if (maxZoom <= minZoom) return clampNumber(maxFactor, minFactor, maxFactor);
1451
+ const t = clampNumber((zoom - minZoom) / (maxZoom - minZoom), 0, 1);
1452
+ const factor = maxFactor + (minFactor - maxFactor) * t;
1453
+ return clampNumber(factor, minFactor, maxFactor);
1454
+ }
1455
+ function getLayerZoomOpacityFactor(zoom, layerType, geometryType, options) {
1456
+ if (!isPolygonLayer(layerType, geometryType)) return 1;
1457
+ return getZoomOpacityFactor(zoom, options);
1458
+ }
1459
+ function getEffectiveLayerOpacity(baseOpacity, zoom, layerType, geometryType, options) {
1460
+ if (!isPolygonLayer(layerType, geometryType)) {
1461
+ return clampOpacity3(baseOpacity);
1462
+ }
1463
+ const settings = { ...DEFAULT_OPTIONS, ...options };
1464
+ const factor = getZoomOpacityFactor(zoom, options);
1465
+ const effective = clampOpacity3(baseOpacity) * factor;
1466
+ return clampNumber(effective, settings.minOpacity, settings.maxOpacity);
1467
+ }
1468
+
1469
+ // src/react/ZenitMap.tsx
1470
+ var import_jsx_runtime = require("react/jsx-runtime");
1471
+ var DEFAULT_CENTER = [0, 0];
1472
+ var DEFAULT_ZOOM = 3;
1473
+ function computeBBoxFromGeojson(geojson) {
1474
+ if (!geojson) return null;
1475
+ if (!Array.isArray(geojson.features) || geojson.features.length === 0) return null;
1476
+ const coords = [];
1477
+ const collect = (candidate) => {
1478
+ if (!Array.isArray(candidate)) return;
1479
+ if (candidate.length === 2 && typeof candidate[0] === "number" && typeof candidate[1] === "number") {
1480
+ coords.push([candidate[0], candidate[1]]);
1481
+ return;
1482
+ }
1483
+ candidate.forEach((entry) => collect(entry));
1484
+ };
1485
+ geojson.features.forEach((feature) => {
1486
+ collect(feature.geometry?.coordinates);
1487
+ });
1488
+ if (coords.length === 0) return null;
1489
+ const [firstLon, firstLat] = coords[0];
1490
+ const bbox = { minLon: firstLon, minLat: firstLat, maxLon: firstLon, maxLat: firstLat };
1491
+ coords.forEach(([lon, lat]) => {
1492
+ bbox.minLon = Math.min(bbox.minLon, lon);
1493
+ bbox.minLat = Math.min(bbox.minLat, lat);
1494
+ bbox.maxLon = Math.max(bbox.maxLon, lon);
1495
+ bbox.maxLat = Math.max(bbox.maxLat, lat);
1496
+ });
1497
+ return bbox;
1498
+ }
1499
+ function mergeBBoxes(bboxes) {
1500
+ const valid = bboxes.filter((bbox) => !!bbox);
1501
+ if (valid.length === 0) return null;
1502
+ const first = valid[0];
1503
+ return valid.slice(1).reduce(
1504
+ (acc, bbox) => ({
1505
+ minLon: Math.min(acc.minLon, bbox.minLon),
1506
+ minLat: Math.min(acc.minLat, bbox.minLat),
1507
+ maxLon: Math.max(acc.maxLon, bbox.maxLon),
1508
+ maxLat: Math.max(acc.maxLat, bbox.maxLat)
1509
+ }),
1510
+ { ...first }
1511
+ );
1512
+ }
1513
+ function getFeatureLayerId(feature) {
1514
+ const layerId = feature?.properties?.__zenit_layerId ?? feature?.properties?.layerId ?? feature?.properties?.layer_id;
1515
+ if (layerId === void 0 || layerId === null) return null;
1516
+ return layerId;
1517
+ }
1518
+ function escapeHtml(value) {
1519
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1520
+ }
1521
+ var DESCRIPTION_KEYS = /* @__PURE__ */ new Set(["descripcion", "description"]);
1522
+ function normalizeDescriptionValue(value) {
1523
+ if (value === void 0 || value === null) return null;
1524
+ if (typeof value === "string") {
1525
+ const trimmed = value.trim();
1526
+ return trimmed ? trimmed : null;
1527
+ }
1528
+ if (typeof value === "number" || typeof value === "boolean") {
1529
+ return String(value);
1530
+ }
1531
+ return null;
1532
+ }
1533
+ function extractDescriptionValue(properties) {
1534
+ if (!properties) return null;
1535
+ const matches = Object.entries(properties).find(
1536
+ ([key]) => DESCRIPTION_KEYS.has(key.toLowerCase())
1537
+ );
1538
+ if (!matches) return null;
1539
+ return normalizeDescriptionValue(matches[1]);
1540
+ }
1541
+ function safeJsonStringify(value) {
1542
+ try {
1543
+ const json = JSON.stringify(value, null, 2);
1544
+ if (json !== void 0) return json;
1545
+ } catch {
1546
+ }
1547
+ return String(value);
1548
+ }
1549
+ function renderPropertyValue(value) {
1550
+ if (value === null || value === void 0) {
1551
+ return '<span class="prop-empty">\u2014</span>';
1552
+ }
1553
+ if (typeof value === "object") {
1554
+ const json = safeJsonStringify(value);
1555
+ return `<pre class="prop-json">${escapeHtml(json)}</pre>`;
1556
+ }
1557
+ return `<span class="prop-text">${escapeHtml(String(value))}</span>`;
1558
+ }
1559
+ var POPUP_TITLE_KEYS = ["name", "title", "nombre", "label", "id"];
1560
+ var POPUP_CHIP_KEYS = /* @__PURE__ */ new Set(["churn", "color", "sector"]);
1561
+ function isChipValue(value) {
1562
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
1563
+ }
1564
+ function getSanitizedChipColor(value) {
1565
+ if (typeof value !== "string") return null;
1566
+ const trimmed = value.trim();
1567
+ if (/^#([0-9a-fA-F]{3}){1,2}$/.test(trimmed)) {
1568
+ return trimmed;
1569
+ }
1570
+ if (/^rgb\((\s*\d+\s*,){2}\s*\d+\s*\)$/.test(trimmed)) {
1571
+ return trimmed;
1572
+ }
1573
+ if (/^rgba\((\s*\d+\s*,){3}\s*(0|1|0?\.\d+)\s*\)$/.test(trimmed)) {
1574
+ return trimmed;
1575
+ }
1576
+ return null;
1577
+ }
1578
+ function getPopupTitle(entries) {
1579
+ for (const candidate of POPUP_TITLE_KEYS) {
1580
+ const match = entries.find((entry) => entry.normalized === candidate);
1581
+ if (!match || match.value === null || match.value === void 0) continue;
1582
+ const value = String(match.value).trim();
1583
+ if (!value) continue;
1584
+ return { title: value, key: match.key };
1585
+ }
1586
+ return null;
1587
+ }
1588
+ function renderProperties(properties) {
1589
+ const description = extractDescriptionValue(properties);
1590
+ const entries = Object.entries(properties).filter(([key]) => !DESCRIPTION_KEYS.has(key.toLowerCase())).map(([key, value]) => ({
1591
+ key,
1592
+ value,
1593
+ normalized: key.trim().toLowerCase()
1594
+ }));
1595
+ if (!description && entries.length === 0) return "";
1596
+ const titleEntry = getPopupTitle(entries);
1597
+ const titleText = titleEntry?.title ?? "Detalle del elemento";
1598
+ const chipEntries = entries.filter(
1599
+ (entry) => POPUP_CHIP_KEYS.has(entry.normalized) && isChipValue(entry.value)
1600
+ );
1601
+ const listEntries = entries.filter((entry) => {
1602
+ if (titleEntry && entry.key === titleEntry.key) return false;
1603
+ if (chipEntries.find((chip) => chip.key === entry.key)) return false;
1604
+ return true;
1605
+ });
1606
+ const descriptionHtml = description ? `<div class="popup-description">${escapeHtml(description)}</div>` : "";
1607
+ const chipsHtml = chipEntries.length ? `<div class="popup-chip-row">${chipEntries.map((entry) => {
1608
+ const label = escapeHtml(entry.key.replace(/_/g, " "));
1609
+ const value = escapeHtml(String(entry.value));
1610
+ const color = getSanitizedChipColor(entry.value);
1611
+ const colorStyle = color ? ` style="--chip-color: ${color}"` : "";
1612
+ return `<span class="popup-chip"${colorStyle}><span class="popup-chip-label">${label}</span><span class="popup-chip-value">${value}</span></span>`;
1613
+ }).join("")}</div>` : "";
1614
+ const rowsHtml = listEntries.map((entry) => {
1615
+ const label = escapeHtml(entry.key.replace(/_/g, " "));
1616
+ const valueHtml = renderPropertyValue(entry.value);
1617
+ return `<div class="prop-key">${label}</div><div class="prop-value">${valueHtml}</div>`;
1618
+ }).join("");
1619
+ const listHtml = rowsHtml ? `<div class="prop-list">${rowsHtml}</div>` : "";
1620
+ return `
1621
+ <div class="feature-popup">
1622
+ <div class="feature-popup-card">
1623
+ <div class="feature-popup-header">
1624
+ <p class="popup-eyebrow">Informaci\xF3n</p>
1625
+ <h3 class="popup-title">${escapeHtml(titleText)}</h3>
1626
+ </div>
1627
+ <div class="feature-popup-body">
1628
+ ${descriptionHtml}
1629
+ ${chipsHtml}
1630
+ ${listHtml}
1631
+ </div>
1632
+ </div>
1633
+ </div>
1634
+ `;
1635
+ }
1636
+ function withAlpha(color, alpha) {
1637
+ const trimmed = color.trim();
1638
+ if (trimmed.startsWith("#")) {
1639
+ const hex = trimmed.replace("#", "");
1640
+ const expanded = hex.length === 3 ? hex.split("").map((char) => `${char}${char}`).join("") : hex;
1641
+ if (expanded.length === 6) {
1642
+ const r = parseInt(expanded.slice(0, 2), 16);
1643
+ const g = parseInt(expanded.slice(2, 4), 16);
1644
+ const b = parseInt(expanded.slice(4, 6), 16);
1645
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
1646
+ }
1647
+ }
1648
+ if (trimmed.startsWith("rgb(")) {
1649
+ const inner = trimmed.slice(4, -1);
1650
+ return `rgba(${inner}, ${alpha})`;
1651
+ }
1652
+ if (trimmed.startsWith("rgba(")) {
1653
+ const inner = trimmed.slice(5, -1).split(",").slice(0, 3).map((value) => value.trim());
1654
+ return `rgba(${inner.join(", ")}, ${alpha})`;
1655
+ }
1656
+ return color;
1657
+ }
1658
+ function getRgbFromColor(color) {
1659
+ const trimmed = color.trim();
1660
+ if (trimmed.startsWith("#")) {
1661
+ const hex = trimmed.replace("#", "");
1662
+ const expanded = hex.length === 3 ? hex.split("").map((char) => `${char}${char}`).join("") : hex;
1663
+ if (expanded.length === 6) {
1664
+ const r = parseInt(expanded.slice(0, 2), 16);
1665
+ const g = parseInt(expanded.slice(2, 4), 16);
1666
+ const b = parseInt(expanded.slice(4, 6), 16);
1667
+ return { r, g, b };
1668
+ }
1669
+ }
1670
+ const rgbMatch = trimmed.match(/rgba?\(([^)]+)\)/i);
1671
+ if (rgbMatch) {
1672
+ const [r, g, b] = rgbMatch[1].split(",").map((value) => parseFloat(value.trim())).slice(0, 3);
1673
+ if ([r, g, b].every((value) => Number.isFinite(value))) {
1674
+ return { r, g, b };
1675
+ }
1676
+ }
1677
+ return null;
1678
+ }
1679
+ function getLabelTextStyles(color) {
1680
+ const rgb = getRgbFromColor(color);
1681
+ if (!rgb) {
1682
+ return { color: "#0f172a", shadow: "0 1px 2px rgba(255, 255, 255, 0.6)" };
1683
+ }
1684
+ const luminance = (0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b) / 255;
1685
+ if (luminance > 0.6) {
1686
+ return { color: "#0f172a", shadow: "0 1px 2px rgba(255, 255, 255, 0.7)" };
1687
+ }
1688
+ return { color: "#ffffff", shadow: "0 1px 2px rgba(0, 0, 0, 0.4)" };
1689
+ }
1690
+ function getFeatureStyleOverrides(feature) {
1691
+ const candidate = feature?.properties?._style;
1692
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) return null;
1693
+ const styleCandidate = candidate;
1694
+ return {
1695
+ color: styleCandidate.color,
1696
+ weight: styleCandidate.weight,
1697
+ fillColor: styleCandidate.fillColor,
1698
+ fillOpacity: styleCandidate.fillOpacity
1699
+ };
1700
+ }
1701
+ function buildFeaturePopupHtml(feature) {
1702
+ const properties = feature?.properties;
1703
+ if (!properties) return null;
1704
+ const rendered = renderProperties(properties);
1705
+ return rendered ? rendered : null;
1706
+ }
1707
+ var POINT_GEOMETRY_TYPES = /* @__PURE__ */ new Set(["Point", "MultiPoint"]);
1708
+ function getGeometryType(feature) {
1709
+ const t = feature?.geometry?.type;
1710
+ return typeof t === "string" ? t : null;
1711
+ }
1712
+ function isPointGeometry(feature) {
1713
+ const geometryType = getGeometryType(feature);
1714
+ return geometryType !== null && POINT_GEOMETRY_TYPES.has(geometryType);
1715
+ }
1716
+ function isNonPointGeometry(feature) {
1717
+ const geometryType = getGeometryType(feature);
1718
+ return geometryType !== null && !POINT_GEOMETRY_TYPES.has(geometryType);
1719
+ }
1720
+ function buildFeatureCollection(features) {
1721
+ return {
1722
+ type: "FeatureCollection",
1723
+ features
1724
+ };
1725
+ }
1726
+ function pickIntersectFeature(baseFeature, candidates) {
1727
+ if (!Array.isArray(candidates) || candidates.length === 0) return null;
1728
+ const baseId = baseFeature?.id;
1729
+ if (baseId !== void 0 && baseId !== null) {
1730
+ const matchById = candidates.find((candidate) => candidate?.id === baseId);
1731
+ if (matchById) return matchById;
1732
+ }
1733
+ const matchWithDescription = candidates.find(
1734
+ (candidate) => extractDescriptionValue(candidate?.properties)
1735
+ );
1736
+ return matchWithDescription ?? candidates[0];
1737
+ }
1738
+ function normalizeCenterTuple(center) {
1739
+ if (!center) return null;
1740
+ if (Array.isArray(center) && center.length >= 2) {
1741
+ const lat = center[0];
1742
+ const lon = center[1];
1743
+ if (typeof lat === "number" && typeof lon === "number") return [lat, lon];
1744
+ return null;
1745
+ }
1746
+ const maybeObj = center;
1747
+ if (typeof maybeObj.lat === "number" && typeof maybeObj.lon === "number") {
1748
+ return [maybeObj.lat, maybeObj.lon];
1749
+ }
1750
+ return null;
1751
+ }
1752
+ var FitToBounds = ({ bbox }) => {
1753
+ const mapInstance = (0, import_react_leaflet.useMap)();
1754
+ const lastAppliedBBox = (0, import_react.useRef)(null);
1755
+ (0, import_react.useEffect)(() => {
1756
+ const targetBBox = bbox;
1757
+ if (!targetBBox) return;
1758
+ const serialized = JSON.stringify(targetBBox);
1759
+ if (lastAppliedBBox.current === serialized) return;
1760
+ const bounds = [
1761
+ [targetBBox.minLat, targetBBox.minLon],
1762
+ [targetBBox.maxLat, targetBBox.maxLon]
1763
+ ];
1764
+ mapInstance.fitBounds(bounds, { padding: [12, 12] });
1765
+ lastAppliedBBox.current = serialized;
1766
+ }, [bbox, mapInstance]);
1767
+ return null;
1768
+ };
1769
+ var AutoFitToBounds = ({
1770
+ bbox,
1771
+ enabled = true
1772
+ }) => {
1773
+ const mapInstance = (0, import_react_leaflet.useMap)();
1774
+ const lastAutoBBoxApplied = (0, import_react.useRef)(null);
1775
+ const lastUserInteracted = (0, import_react.useRef)(false);
1776
+ (0, import_react.useEffect)(() => {
1777
+ if (!enabled) return;
1778
+ const handleInteraction = () => {
1779
+ lastUserInteracted.current = true;
1780
+ };
1781
+ mapInstance.on("dragstart", handleInteraction);
1782
+ mapInstance.on("zoomstart", handleInteraction);
1783
+ return () => {
1784
+ mapInstance.off("dragstart", handleInteraction);
1785
+ mapInstance.off("zoomstart", handleInteraction);
1786
+ };
1787
+ }, [enabled, mapInstance]);
1788
+ (0, import_react.useEffect)(() => {
1789
+ if (!enabled) return;
1790
+ if (!bbox) return;
1791
+ const serialized = JSON.stringify(bbox);
1792
+ if (lastAutoBBoxApplied.current === serialized) return;
1793
+ if (lastUserInteracted.current) {
1794
+ lastUserInteracted.current = false;
1795
+ }
1796
+ const bounds = [
1797
+ [bbox.minLat, bbox.minLon],
1798
+ [bbox.maxLat, bbox.maxLon]
1799
+ ];
1800
+ mapInstance.fitBounds(bounds, { padding: [12, 12] });
1801
+ lastAutoBBoxApplied.current = serialized;
1802
+ }, [bbox, enabled, mapInstance]);
1803
+ return null;
1804
+ };
1805
+ var ZoomBasedOpacityHandler = ({ onZoomChange }) => {
1806
+ const mapInstance = (0, import_react_leaflet.useMap)();
1807
+ (0, import_react.useEffect)(() => {
1808
+ const handleZoom = () => {
1809
+ onZoomChange(mapInstance.getZoom());
1810
+ };
1811
+ mapInstance.on("zoomend", handleZoom);
1812
+ handleZoom();
1813
+ return () => {
1814
+ mapInstance.off("zoomend", handleZoom);
1815
+ };
1816
+ }, [mapInstance, onZoomChange]);
1817
+ return null;
1818
+ };
1819
+ var MapInstanceBridge = ({ onReady }) => {
1820
+ const mapInstance = (0, import_react_leaflet.useMap)();
1821
+ (0, import_react.useEffect)(() => {
1822
+ onReady(mapInstance);
1823
+ }, [mapInstance, onReady]);
1824
+ return null;
1825
+ };
1826
+ var ZenitMap = (0, import_react.forwardRef)(({
1827
+ client,
1828
+ mapId,
1829
+ height = "500px",
1830
+ width = "100%",
1831
+ initialZoom,
1832
+ initialCenter,
1833
+ showLayerPanel = true,
1834
+ overlayGeojson,
1835
+ overlayStyle,
1836
+ layerControls,
1837
+ layerStates,
1838
+ layerGeojson,
1839
+ onLayerStateChange,
1840
+ onError,
1841
+ onLoadingChange,
1842
+ onFeatureClick,
1843
+ onFeatureHover,
1844
+ featureInfoMode = "popup",
1845
+ mapLayers,
1846
+ zoomToBbox,
1847
+ zoomToGeojson,
1848
+ onZoomChange,
1849
+ onMapReady
1850
+ }, ref) => {
1851
+ const [map, setMap] = (0, import_react.useState)(null);
1852
+ const [layers, setLayers] = (0, import_react.useState)([]);
1853
+ const [effectiveStates, setEffectiveStates] = (0, import_react.useState)([]);
1854
+ const [loadingMap, setLoadingMap] = (0, import_react.useState)(false);
1855
+ const [mapError, setMapError] = (0, import_react.useState)(null);
1856
+ const [mapInstance, setMapInstance] = (0, import_react.useState)(null);
1857
+ const [panesReady, setPanesReady] = (0, import_react.useState)(false);
1858
+ const [currentZoom, setCurrentZoom] = (0, import_react.useState)(initialZoom ?? DEFAULT_ZOOM);
1859
+ const [isMobile, setIsMobile] = (0, import_react.useState)(() => {
1860
+ if (typeof window === "undefined") return false;
1861
+ return window.matchMedia("(max-width: 768px)").matches;
1862
+ });
1863
+ const normalizedLayers = (0, import_react.useMemo)(() => normalizeMapLayers(map), [map]);
1864
+ (0, import_react.useEffect)(() => {
1865
+ if (typeof window === "undefined") return;
1866
+ const mql = window.matchMedia("(max-width: 768px)");
1867
+ const onChange = (e) => {
1868
+ setIsMobile(e.matches);
1869
+ };
1870
+ setIsMobile(mql.matches);
1871
+ if (typeof mql.addEventListener === "function") {
1872
+ mql.addEventListener("change", onChange);
1873
+ return () => mql.removeEventListener("change", onChange);
1874
+ }
1875
+ const legacy = mql;
1876
+ if (typeof legacy.addListener === "function") {
1877
+ legacy.addListener(onChange);
1878
+ return () => legacy.removeListener(onChange);
1879
+ }
1880
+ return;
1881
+ }, []);
1882
+ const layerStyleIndex = (0, import_react.useMemo)(() => {
1883
+ const index = /* @__PURE__ */ new Map();
1884
+ (map?.mapLayers ?? []).forEach((entry) => {
1885
+ const layerStyle = entry.layer?.style ?? entry.mapLayer?.layer?.style ?? entry.style ?? null;
1886
+ const layerId = entry.layerId ?? entry.mapLayer?.layerId ?? entry.mapLayer?.layer?.id ?? entry.layer?.id;
1887
+ if (layerId !== void 0 && layerId !== null && layerStyle) {
1888
+ index.set(String(layerId), layerStyle);
1889
+ }
1890
+ });
1891
+ return index;
1892
+ }, [map]);
1893
+ const labelKeyIndex = (0, import_react.useMemo)(() => {
1894
+ const index = /* @__PURE__ */ new Map();
1895
+ normalizedLayers.forEach((entry) => {
1896
+ const label = entry.layer?.label ?? entry.mapLayer?.label ?? entry.mapLayer.layerConfig?.label;
1897
+ if (typeof label === "string" && label.trim().length > 0) {
1898
+ index.set(String(entry.layerId), label);
1899
+ }
1900
+ });
1901
+ return index;
1902
+ }, [normalizedLayers]);
1903
+ const layerMetaIndex = (0, import_react.useMemo)(() => {
1904
+ const index = /* @__PURE__ */ new Map();
1905
+ normalizedLayers.forEach((entry) => {
1906
+ index.set(String(entry.layerId), {
1907
+ layerType: typeof entry.layer?.layerType === "string" ? entry.layer.layerType : typeof entry.mapLayer.layerType === "string" ? entry.mapLayer.layerType : void 0,
1908
+ geometryType: typeof entry.layer?.geometryType === "string" ? entry.layer.geometryType : typeof entry.mapLayer.layerConfig?.geometryType === "string" ? entry.mapLayer.layerConfig.geometryType : void 0
1909
+ });
1910
+ });
1911
+ return index;
1912
+ }, [normalizedLayers]);
1913
+ const overlayStyleFunction = (0, import_react.useMemo)(() => {
1914
+ return (feature) => {
1915
+ const featureLayerId = getFeatureLayerId(feature);
1916
+ const featureStyleOverrides = getFeatureStyleOverrides(feature);
1917
+ let style = null;
1918
+ if (featureLayerId !== null) {
1919
+ style = getStyleByLayerId(featureLayerId, mapLayers) ?? layerStyleIndex.get(String(featureLayerId)) ?? null;
1920
+ }
1921
+ const resolvedStyle = featureStyleOverrides ? { ...style ?? {}, ...featureStyleOverrides } : style;
1922
+ const defaultOptions = {
1923
+ color: resolvedStyle?.color ?? resolvedStyle?.fillColor ?? "#2563eb",
1924
+ weight: resolvedStyle?.weight ?? 2,
1925
+ fillColor: resolvedStyle?.fillColor ?? resolvedStyle?.color ?? "#2563eb",
1926
+ fillOpacity: resolvedStyle?.fillOpacity ?? 0.15,
1927
+ ...overlayStyle ?? {}
1928
+ };
1929
+ return defaultOptions;
1930
+ };
1931
+ }, [layerStyleIndex, mapLayers, overlayStyle]);
1932
+ const [baseStates, setBaseStates] = (0, import_react.useState)([]);
1933
+ const [mapOverrides, setMapOverrides] = (0, import_react.useState)([]);
1934
+ const [controlOverrides, setControlOverrides] = (0, import_react.useState)([]);
1935
+ const [uiOverrides, setUiOverrides] = (0, import_react.useState)([]);
1936
+ (0, import_react.useEffect)(() => {
1937
+ let isMounted = true;
1938
+ setLoadingMap(true);
1939
+ setMapError(null);
1940
+ onLoadingChange?.("map", true);
1941
+ client.maps.getMap(mapId, true).then((mapResponse) => {
1942
+ if (!isMounted) return;
1943
+ const extracted = extractMapDto(mapResponse);
1944
+ const resolved = extracted ? normalizeMapCenter(extracted) : null;
1945
+ setMap(resolved);
1946
+ }).catch((err) => {
1947
+ if (!isMounted) return;
1948
+ setMapError(err instanceof Error ? err.message : "No se pudo obtener el mapa");
1949
+ onError?.(err, "map");
1950
+ }).finally(() => {
1951
+ if (!isMounted) return;
1952
+ setLoadingMap(false);
1953
+ onLoadingChange?.("map", false);
1954
+ });
1955
+ return () => {
1956
+ isMounted = false;
1957
+ };
1958
+ }, [client.maps, mapId, onError, onLoadingChange]);
1959
+ (0, import_react.useEffect)(() => {
1960
+ if (normalizedLayers.length === 0) {
1961
+ setLayers([]);
1962
+ setBaseStates([]);
1963
+ setMapOverrides([]);
1964
+ setUiOverrides([]);
1965
+ return;
1966
+ }
1967
+ const nextLayers = normalizedLayers.map((entry) => ({
1968
+ mapLayer: { ...entry.mapLayer, layerId: entry.layerId },
1969
+ layer: entry.layer,
1970
+ displayOrder: entry.displayOrder
1971
+ }));
1972
+ const base = initLayerStates(
1973
+ normalizedLayers.map((entry) => ({
1974
+ ...entry.mapLayer,
1975
+ layerId: entry.layerId,
1976
+ opacity: entry.opacity,
1977
+ isVisible: entry.isVisible
1978
+ }))
1979
+ );
1980
+ const initialOverrides = normalizedLayers.map((entry) => ({
1981
+ layerId: entry.layerId,
1982
+ overrideVisible: entry.isVisible,
1983
+ overrideOpacity: entry.opacity
1984
+ }));
1985
+ setLayers(nextLayers);
1986
+ setBaseStates(base);
1987
+ setMapOverrides(initialOverrides);
1988
+ setUiOverrides([]);
1989
+ }, [normalizedLayers]);
1990
+ (0, import_react.useEffect)(() => {
1991
+ if (!layerControls) {
1992
+ setControlOverrides([]);
1993
+ return;
1994
+ }
1995
+ const overrides = layerControls.map((entry) => ({
1996
+ layerId: entry.layerId,
1997
+ overrideVisible: entry.visible,
1998
+ overrideOpacity: entry.opacity
1999
+ }));
2000
+ setControlOverrides(overrides);
2001
+ }, [layerControls]);
2002
+ (0, import_react.useEffect)(() => {
2003
+ if (layerStates) {
2004
+ return;
2005
+ }
2006
+ if (Array.isArray(layerControls) && layerControls.length === 0 && effectiveStates.length > 0) {
2007
+ const reset = resetOverrides(baseStates);
2008
+ setEffectiveStates(reset);
2009
+ onLayerStateChange?.(reset);
2010
+ }
2011
+ }, [baseStates, effectiveStates.length, layerControls, layerStates, onLayerStateChange]);
2012
+ (0, import_react.useEffect)(() => {
2013
+ if (layerStates) {
2014
+ setEffectiveStates(layerStates);
2015
+ }
2016
+ }, [layerStates]);
2017
+ (0, import_react.useEffect)(() => {
2018
+ if (layerStates) {
2019
+ return;
2020
+ }
2021
+ if (mapOverrides.length === 0 && controlOverrides.length === 0 && uiOverrides.length === 0) {
2022
+ setEffectiveStates(baseStates);
2023
+ return;
2024
+ }
2025
+ const combined = [...mapOverrides, ...controlOverrides, ...uiOverrides];
2026
+ const next = applyLayerOverrides(baseStates, combined);
2027
+ setEffectiveStates(next);
2028
+ onLayerStateChange?.(next);
2029
+ }, [baseStates, controlOverrides, layerStates, mapOverrides, onLayerStateChange, uiOverrides]);
2030
+ (0, import_react.useEffect)(() => {
2031
+ if (!Array.isArray(layerControls) || layerControls.length > 0) return;
2032
+ setUiOverrides([]);
2033
+ }, [layerControls]);
2034
+ (0, import_react.useEffect)(() => {
2035
+ if (layerStates) {
2036
+ return;
2037
+ }
2038
+ if (!Array.isArray(layerControls) && effectiveStates.length > 0 && baseStates.length > 0) {
2039
+ const next = resetOverrides(baseStates);
2040
+ setEffectiveStates(next);
2041
+ onLayerStateChange?.(next);
2042
+ }
2043
+ }, [baseStates, effectiveStates.length, layerControls, layerStates, onLayerStateChange]);
2044
+ const upsertUiOverride = (layerId, patch) => {
2045
+ setUiOverrides((prev) => {
2046
+ const filtered = prev.filter((entry) => entry.layerId !== layerId);
2047
+ const nextEntry = {
2048
+ layerId,
2049
+ ...patch
2050
+ };
2051
+ return [...filtered, nextEntry];
2052
+ });
2053
+ };
2054
+ const updateOpacityFromUi = (0, import_react.useCallback)(
2055
+ (layerId, uiOpacity) => {
2056
+ const meta = layerMetaIndex.get(String(layerId));
2057
+ const zoomFactor = getLayerZoomOpacityFactor(
2058
+ currentZoom,
2059
+ meta?.layerType,
2060
+ meta?.geometryType
2061
+ );
2062
+ const baseOpacity = clampOpacity3(uiOpacity / zoomFactor);
2063
+ const effectiveOpacity = getEffectiveLayerOpacity(
2064
+ baseOpacity,
2065
+ currentZoom,
2066
+ meta?.layerType,
2067
+ meta?.geometryType
2068
+ );
2069
+ if (layerStates && onLayerStateChange) {
2070
+ const next = effectiveStates.map(
2071
+ (state) => state.layerId === layerId ? {
2072
+ ...state,
2073
+ baseOpacity,
2074
+ opacity: effectiveOpacity,
2075
+ overrideOpacity: uiOpacity
2076
+ } : state
2077
+ );
2078
+ onLayerStateChange(next);
2079
+ return;
2080
+ }
2081
+ setBaseStates(
2082
+ (prev) => prev.map((state) => state.layerId === layerId ? { ...state, baseOpacity } : state)
2083
+ );
2084
+ setUiOverrides((prev) => {
2085
+ const existing = prev.find((entry) => entry.layerId === layerId);
2086
+ const filtered = prev.filter((entry) => entry.layerId !== layerId);
2087
+ if (existing && existing.overrideVisible !== void 0) {
2088
+ return [
2089
+ ...filtered,
2090
+ { layerId, overrideVisible: existing.overrideVisible }
2091
+ ];
2092
+ }
2093
+ return filtered;
2094
+ });
2095
+ },
2096
+ [currentZoom, effectiveStates, layerMetaIndex, layerStates, onLayerStateChange]
2097
+ );
2098
+ const center = (0, import_react.useMemo)(() => {
2099
+ if (initialCenter) {
2100
+ return initialCenter;
2101
+ }
2102
+ const normalized = normalizeCenterTuple(map?.settings?.center);
2103
+ if (normalized) {
2104
+ return normalized;
2105
+ }
2106
+ return DEFAULT_CENTER;
2107
+ }, [initialCenter, map?.settings?.center]);
2108
+ const zoom = initialZoom ?? map?.settings?.zoom ?? DEFAULT_ZOOM;
2109
+ (0, import_react.useEffect)(() => {
2110
+ setCurrentZoom(zoom);
2111
+ }, [zoom]);
2112
+ const decoratedLayers = (0, import_react.useMemo)(() => {
2113
+ return layers.map((layer) => ({
2114
+ ...layer,
2115
+ effective: effectiveStates.find((state) => state.layerId === layer.mapLayer.layerId),
2116
+ data: layerGeojson?.[layer.mapLayer.layerId] ?? layerGeojson?.[String(layer.mapLayer.layerId)] ?? null
2117
+ }));
2118
+ }, [effectiveStates, layerGeojson, layers]);
2119
+ const orderedLayers = (0, import_react.useMemo)(() => {
2120
+ return [...decoratedLayers].filter((layer) => layer.effective?.visible && layer.data).sort((a, b) => a.displayOrder - b.displayOrder);
2121
+ }, [decoratedLayers]);
2122
+ const explicitZoomBBox = (0, import_react.useMemo)(() => {
2123
+ if (zoomToBbox) return zoomToBbox;
2124
+ if (zoomToGeojson) return computeBBoxFromGeojson(zoomToGeojson);
2125
+ return null;
2126
+ }, [zoomToBbox, zoomToGeojson]);
2127
+ const autoZoomBBox = (0, import_react.useMemo)(() => {
2128
+ if (explicitZoomBBox) return null;
2129
+ const visibleBBoxes = orderedLayers.map((layer) => computeBBoxFromGeojson(layer.data));
2130
+ return mergeBBoxes(visibleBBoxes);
2131
+ }, [explicitZoomBBox, orderedLayers]);
2132
+ const resolveLayerStyle = (0, import_react.useCallback)(
2133
+ (layerId) => {
2134
+ return getStyleByLayerId(layerId, mapLayers) ?? layerStyleIndex.get(String(layerId)) ?? null;
2135
+ },
2136
+ [layerStyleIndex, mapLayers]
2137
+ );
2138
+ const labelMarkers = (0, import_react.useMemo)(() => {
2139
+ const markers = [];
2140
+ decoratedLayers.forEach((layerState) => {
2141
+ if (!layerState.effective?.visible) return;
2142
+ const labelKey = labelKeyIndex.get(String(layerState.mapLayer.layerId));
2143
+ if (!labelKey) return;
2144
+ const data = layerState.data;
2145
+ if (!data) return;
2146
+ const resolvedStyle = resolveLayerStyle(layerState.mapLayer.layerId);
2147
+ const layerColor = resolvedStyle?.fillColor ?? resolvedStyle?.color ?? "rgba(37, 99, 235, 1)";
2148
+ const opacity = layerState.effective?.opacity ?? 1;
2149
+ data.features.forEach((feature, index) => {
2150
+ const properties = feature.properties;
2151
+ const value = properties?.[labelKey];
2152
+ if (value === void 0 || value === null || value === "") return;
2153
+ const bbox = computeBBoxFromFeature(feature);
2154
+ if (!bbox) return;
2155
+ const [lat, lng] = centroidFromBBox(bbox);
2156
+ markers.push({
2157
+ key: `${layerState.mapLayer.layerId}-label-${index}`,
2158
+ position: [lat, lng],
2159
+ label: String(value),
2160
+ opacity,
2161
+ layerId: layerState.mapLayer.layerId,
2162
+ color: layerColor
2163
+ });
2164
+ });
2165
+ });
2166
+ return markers;
2167
+ }, [decoratedLayers, labelKeyIndex, resolveLayerStyle]);
2168
+ const ensureLayerPanes = (0, import_react.useCallback)(
2169
+ (targetMap, targetLayers) => {
2170
+ const baseZIndex = 400;
2171
+ targetLayers.forEach((layer) => {
2172
+ const order = Number.isFinite(layer.displayOrder) ? layer.displayOrder : 0;
2173
+ const fillPaneName = `zenit-layer-${layer.layerId}-fill`;
2174
+ const pointPaneName = `zenit-layer-${layer.layerId}-points`;
2175
+ const labelPaneName = `zenit-layer-${layer.layerId}-labels`;
2176
+ const fillPane = targetMap.getPane(fillPaneName) ?? targetMap.createPane(fillPaneName);
2177
+ const pointPane = targetMap.getPane(pointPaneName) ?? targetMap.createPane(pointPaneName);
2178
+ const labelPane = targetMap.getPane(labelPaneName) ?? targetMap.createPane(labelPaneName);
2179
+ fillPane.style.zIndex = String(baseZIndex + order);
2180
+ pointPane.style.zIndex = String(baseZIndex + order + 1e3);
2181
+ labelPane.style.zIndex = String(baseZIndex + order + 2e3);
2182
+ });
2183
+ },
2184
+ []
2185
+ );
2186
+ const handleMapReady = (0, import_react.useCallback)(
2187
+ (instance) => {
2188
+ setPanesReady(false);
2189
+ setMapInstance(instance);
2190
+ onMapReady?.(instance);
2191
+ },
2192
+ [onMapReady]
2193
+ );
2194
+ (0, import_react.useEffect)(() => {
2195
+ if (!mapInstance) {
2196
+ setPanesReady(false);
2197
+ return;
2198
+ }
2199
+ if (orderedLayers.length === 0) {
2200
+ return;
2201
+ }
2202
+ const layerTargets = orderedLayers.map((layer) => ({
2203
+ layerId: layer.mapLayer.layerId,
2204
+ displayOrder: layer.displayOrder
2205
+ }));
2206
+ ensureLayerPanes(mapInstance, layerTargets);
2207
+ const first = layerTargets[0];
2208
+ const testPane = mapInstance.getPane(`zenit-layer-${first.layerId}-labels`);
2209
+ if (testPane) {
2210
+ setPanesReady(true);
2211
+ }
2212
+ }, [mapInstance, orderedLayers, ensureLayerPanes]);
2213
+ const overlayOnEachFeature = (0, import_react.useMemo)(() => {
2214
+ return (feature, layer) => {
2215
+ const layerId = getFeatureLayerId(feature) ?? void 0;
2216
+ const geometryType = feature?.geometry?.type;
2217
+ const isPointFeature = geometryType === "Point" || geometryType === "MultiPoint" || layer instanceof import_leaflet.default.CircleMarker;
2218
+ const originalStyle = layer instanceof import_leaflet.default.Path ? {
2219
+ color: layer.options.color,
2220
+ weight: layer.options.weight,
2221
+ fillColor: layer.options.fillColor,
2222
+ opacity: layer.options.opacity,
2223
+ fillOpacity: layer.options.fillOpacity
2224
+ } : null;
2225
+ const originalRadius = layer instanceof import_leaflet.default.CircleMarker ? layer.getRadius() : null;
2226
+ if (featureInfoMode === "popup") {
2227
+ const content = buildFeaturePopupHtml(feature);
2228
+ if (content) {
2229
+ layer.bindPopup(content, {
2230
+ maxWidth: 420,
2231
+ minWidth: 280,
2232
+ className: "zenit-feature-popup-shell",
2233
+ autoPan: true,
2234
+ closeButton: true,
2235
+ offset: import_leaflet.default.point(0, -24)
2236
+ });
2237
+ }
2238
+ }
2239
+ if (isPointFeature && layer.bindTooltip) {
2240
+ layer.bindTooltip("Click para ver detalle", {
2241
+ sticky: true,
2242
+ direction: "top",
2243
+ opacity: 0.9,
2244
+ className: "zenit-map-tooltip"
2245
+ });
2246
+ }
2247
+ layer.on("click", () => {
2248
+ if (featureInfoMode === "popup" && client && layerId !== void 0 && !extractDescriptionValue(feature?.properties) && feature?.geometry) {
2249
+ const trackedFeature = feature;
2250
+ if (!trackedFeature.__zenit_popup_loaded) {
2251
+ trackedFeature.__zenit_popup_loaded = true;
2252
+ client.layers.getLayerGeoJsonIntersect({
2253
+ id: layerId,
2254
+ geometry: feature.geometry
2255
+ }).then((response) => {
2256
+ const candidates = response.data?.features ?? [];
2257
+ const resolved = pickIntersectFeature(feature, candidates);
2258
+ if (!resolved?.properties) return;
2259
+ const mergedProperties = {
2260
+ ...trackedFeature.properties ?? {},
2261
+ ...resolved.properties
2262
+ };
2263
+ trackedFeature.properties = mergedProperties;
2264
+ const updatedHtml = buildFeaturePopupHtml({
2265
+ ...feature,
2266
+ properties: mergedProperties
2267
+ });
2268
+ if (updatedHtml && layer.setPopupContent) {
2269
+ layer.setPopupContent(updatedHtml);
2270
+ }
2271
+ }).catch(() => {
2272
+ trackedFeature.__zenit_popup_loaded = false;
2273
+ });
2274
+ }
2275
+ }
2276
+ onFeatureClick?.(feature, layerId);
2277
+ });
2278
+ layer.on("mouseover", () => {
2279
+ if (layer instanceof import_leaflet.default.Path && originalStyle) {
2280
+ layer.setStyle({
2281
+ ...originalStyle,
2282
+ weight: (originalStyle.weight ?? 2) + 1,
2283
+ opacity: Math.min(1, (originalStyle.opacity ?? 1) + 0.2),
2284
+ fillOpacity: Math.min(1, (originalStyle.fillOpacity ?? 0.8) + 0.1)
2285
+ });
2286
+ }
2287
+ if (layer instanceof import_leaflet.default.CircleMarker && typeof originalRadius === "number") {
2288
+ layer.setRadius(originalRadius + 1);
2289
+ }
2290
+ onFeatureHover?.(feature, layerId);
2291
+ });
2292
+ layer.on("mouseout", () => {
2293
+ if (layer instanceof import_leaflet.default.Path && originalStyle) {
2294
+ layer.setStyle(originalStyle);
2295
+ }
2296
+ if (layer instanceof import_leaflet.default.CircleMarker && typeof originalRadius === "number") {
2297
+ layer.setRadius(originalRadius);
2298
+ }
2299
+ });
2300
+ };
2301
+ }, [client, featureInfoMode, onFeatureClick, onFeatureHover]);
2302
+ const buildLayerStyle = (layerId, baseOpacity, feature, layerType) => {
2303
+ const style = resolveLayerStyle(layerId);
2304
+ const featureStyleOverrides = getFeatureStyleOverrides(feature);
2305
+ const resolvedStyle = featureStyleOverrides ? { ...style ?? {}, ...featureStyleOverrides } : style;
2306
+ const geometryType = feature?.geometry?.type;
2307
+ const resolvedLayerType = layerType ?? geometryType;
2308
+ const sanitizedBaseOpacity = clampOpacity3(baseOpacity);
2309
+ const normalizedStyleFill = typeof resolvedStyle?.fillOpacity === "number" ? clampOpacity3(resolvedStyle.fillOpacity) : 0.8;
2310
+ const effectiveOpacity = getEffectiveLayerOpacity(
2311
+ sanitizedBaseOpacity,
2312
+ currentZoom,
2313
+ resolvedLayerType,
2314
+ geometryType
2315
+ );
2316
+ const fillOpacity = clampOpacity3(effectiveOpacity * normalizedStyleFill);
2317
+ const strokeOpacity = clampOpacity3(Math.max(0.35, effectiveOpacity * 0.9));
2318
+ return {
2319
+ color: resolvedStyle?.color ?? resolvedStyle?.fillColor ?? "#2563eb",
2320
+ weight: resolvedStyle?.weight ?? 2,
2321
+ fillColor: resolvedStyle?.fillColor ?? resolvedStyle?.color ?? "#2563eb",
2322
+ opacity: strokeOpacity,
2323
+ fillOpacity
2324
+ };
2325
+ };
2326
+ const buildLabelIcon = (0, import_react.useCallback)((label, opacity, color) => {
2327
+ const size = 60;
2328
+ const innerSize = 44;
2329
+ const textStyles = getLabelTextStyles(color);
2330
+ const safeLabel = escapeHtml(label);
2331
+ const clampedOpacity = Math.min(1, Math.max(0.92, opacity));
2332
+ const innerBackground = withAlpha(color, 0.9);
2333
+ return import_leaflet.default.divIcon({
2334
+ className: "zenit-label-marker",
2335
+ iconSize: [size, size],
2336
+ iconAnchor: [size / 2, size / 2],
2337
+ html: `
2338
+ <div
2339
+ title="${safeLabel}"
2340
+ style="
2341
+ width:${size}px;
2342
+ height:${size}px;
2343
+ border-radius:9999px;
2344
+ background:rgba(255, 255, 255, 0.95);
2345
+ border:3px solid rgba(255, 255, 255, 1);
2346
+ display:flex;
2347
+ align-items:center;
2348
+ justify-content:center;
2349
+ opacity:${clampedOpacity};
2350
+ box-shadow:0 2px 6px rgba(0, 0, 0, 0.25);
2351
+ pointer-events:none;
2352
+ "
2353
+ >
2354
+ <div
2355
+ style="
2356
+ width:${innerSize}px;
2357
+ height:${innerSize}px;
2358
+ border-radius:9999px;
2359
+ background:${innerBackground};
2360
+ display:flex;
2361
+ align-items:center;
2362
+ justify-content:center;
2363
+ box-shadow:inset 0 0 0 1px rgba(15, 23, 42, 0.12);
2364
+ "
2365
+ >
2366
+ <span
2367
+ style="
2368
+ color:${textStyles.color};
2369
+ font-size:20px;
2370
+ font-weight:800;
2371
+ text-shadow:${textStyles.shadow};
2372
+ "
2373
+ >
2374
+ ${safeLabel}
2375
+ </span>
2376
+ </div>
2377
+ </div>
2378
+ `
2379
+ });
2380
+ }, []);
2381
+ (0, import_react.useImperativeHandle)(ref, () => ({
2382
+ setLayerOpacity: (layerId, opacity) => {
2383
+ upsertUiOverride(layerId, { overrideOpacity: opacity });
2384
+ },
2385
+ setLayerVisibility: (layerId, visible) => {
2386
+ upsertUiOverride(layerId, { overrideVisible: visible });
2387
+ },
2388
+ fitBounds: (bbox, options) => {
2389
+ if (!mapInstance) return;
2390
+ if (typeof bbox.minLat !== "number" || typeof bbox.minLon !== "number" || typeof bbox.maxLat !== "number" || typeof bbox.maxLon !== "number" || !Number.isFinite(bbox.minLat) || !Number.isFinite(bbox.minLon) || !Number.isFinite(bbox.maxLat) || !Number.isFinite(bbox.maxLon)) {
2391
+ console.warn("[ZenitMap.fitBounds] Invalid bbox: missing or non-finite coordinates", bbox);
2392
+ return;
2393
+ }
2394
+ const bounds = [
2395
+ [bbox.minLat, bbox.minLon],
2396
+ [bbox.maxLat, bbox.maxLon]
2397
+ ];
2398
+ const fitOptions = {
2399
+ padding: options?.padding ?? [12, 12],
2400
+ animate: options?.animate ?? true
2401
+ };
2402
+ mapInstance.fitBounds(bounds, fitOptions);
2403
+ },
2404
+ setView: (coordinates, zoom2) => {
2405
+ if (!mapInstance) return;
2406
+ mapInstance.setView([coordinates.lat, coordinates.lon], zoom2 ?? mapInstance.getZoom(), {
2407
+ animate: true
2408
+ });
2409
+ },
2410
+ getLayerSnapshot: () => {
2411
+ return effectiveStates.map((state) => ({
2412
+ layerId: state.layerId,
2413
+ visible: state.visible,
2414
+ opacity: state.opacity
2415
+ }));
2416
+ },
2417
+ restoreLayerSnapshot: (snapshot) => {
2418
+ const overrides = snapshot.map((s) => ({
2419
+ layerId: s.layerId,
2420
+ overrideVisible: s.visible,
2421
+ overrideOpacity: s.opacity
2422
+ }));
2423
+ setUiOverrides(overrides);
2424
+ },
2425
+ highlightFeature: (layerId, featureId) => {
2426
+ upsertUiOverride(layerId, { overrideVisible: true, overrideOpacity: 1 });
2427
+ },
2428
+ getMapInstance: () => mapInstance
2429
+ }), [effectiveStates, mapInstance]);
2430
+ if (loadingMap) {
2431
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: 16, height, width }, children: "Cargando mapa..." });
2432
+ }
2433
+ if (mapError) {
2434
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { padding: 16, height, width, color: "red" }, children: [
2435
+ "Error al cargar mapa: ",
2436
+ mapError
2437
+ ] });
2438
+ }
2439
+ if (!map) {
2440
+ return null;
2441
+ }
2442
+ const handleZoomChange = (zoomValue) => {
2443
+ setCurrentZoom(zoomValue);
2444
+ onZoomChange?.(zoomValue);
2445
+ };
2446
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2447
+ "div",
2448
+ {
2449
+ style: {
2450
+ display: "flex",
2451
+ height,
2452
+ width,
2453
+ border: "1px solid #e2e8f0",
2454
+ borderRadius: 4,
2455
+ overflow: "hidden",
2456
+ boxSizing: "border-box"
2457
+ },
2458
+ children: [
2459
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { flex: 1, position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2460
+ import_react_leaflet.MapContainer,
2461
+ {
2462
+ center,
2463
+ zoom,
2464
+ style: { height: "100%", width: "100%" },
2465
+ scrollWheelZoom: true,
2466
+ zoomControl: false,
2467
+ children: [
2468
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2469
+ import_react_leaflet.TileLayer,
2470
+ {
2471
+ url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
2472
+ attribution: "\xA9 OpenStreetMap contributors"
2473
+ }
2474
+ ),
2475
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_leaflet.ZoomControl, { position: "topright" }),
2476
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MapInstanceBridge, { onReady: handleMapReady }),
2477
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FitToBounds, { bbox: explicitZoomBBox ?? void 0 }),
2478
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AutoFitToBounds, { bbox: autoZoomBBox ?? void 0, enabled: !explicitZoomBBox }),
2479
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ZoomBasedOpacityHandler, { onZoomChange: handleZoomChange }),
2480
+ orderedLayers.map((layerState) => {
2481
+ const baseOpacity = layerState.effective?.baseOpacity ?? layerState.effective?.opacity ?? 1;
2482
+ const fillPaneName = `zenit-layer-${layerState.mapLayer.layerId}-fill`;
2483
+ const pointsPaneName = `zenit-layer-${layerState.mapLayer.layerId}-points`;
2484
+ const labelPaneName = `zenit-layer-${layerState.mapLayer.layerId}-labels`;
2485
+ const layerType = layerState.layer?.layerType ?? layerState.mapLayer.layerType ?? void 0;
2486
+ const data = layerState.data?.features ?? [];
2487
+ const fillFeatures = data.filter(isNonPointGeometry);
2488
+ const pointFeatures = data.filter(isPointGeometry);
2489
+ const fillData = fillFeatures.length > 0 ? buildFeatureCollection(fillFeatures) : null;
2490
+ const pointsData = pointFeatures.length > 0 ? buildFeatureCollection(pointFeatures) : null;
2491
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.default.Fragment, { children: [
2492
+ fillData && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2493
+ import_react_leaflet.GeoJSON,
2494
+ {
2495
+ data: fillData,
2496
+ pane: panesReady && mapInstance?.getPane(fillPaneName) ? fillPaneName : void 0,
2497
+ style: (feature) => buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType),
2498
+ onEachFeature: overlayOnEachFeature
2499
+ }
2500
+ ),
2501
+ pointsData && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2502
+ import_react_leaflet.GeoJSON,
2503
+ {
2504
+ data: pointsData,
2505
+ pane: panesReady && mapInstance?.getPane(pointsPaneName) ? pointsPaneName : void 0,
2506
+ pointToLayer: (feature, latlng) => import_leaflet.default.circleMarker(latlng, {
2507
+ radius: isMobile ? 8 : 6,
2508
+ ...buildLayerStyle(layerState.mapLayer.layerId, baseOpacity, feature, layerType)
2509
+ }),
2510
+ onEachFeature: overlayOnEachFeature
2511
+ }
2512
+ ),
2513
+ panesReady && mapInstance?.getPane(labelPaneName) ? labelMarkers.filter(
2514
+ (marker) => String(marker.layerId) === String(layerState.mapLayer.layerId)
2515
+ ).map((marker) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2516
+ import_react_leaflet.Marker,
2517
+ {
2518
+ position: marker.position,
2519
+ icon: buildLabelIcon(marker.label, marker.opacity, marker.color),
2520
+ interactive: false,
2521
+ pane: labelPaneName
2522
+ },
2523
+ marker.key
2524
+ )) : null
2525
+ ] }, layerState.mapLayer.layerId.toString());
2526
+ }),
2527
+ overlayGeojson && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2528
+ import_react_leaflet.GeoJSON,
2529
+ {
2530
+ data: overlayGeojson,
2531
+ style: overlayStyleFunction,
2532
+ onEachFeature: overlayOnEachFeature
2533
+ },
2534
+ "zenit-overlay-geojson"
2535
+ )
2536
+ ]
2537
+ },
2538
+ String(mapId)
2539
+ ) }),
2540
+ showLayerPanel && decoratedLayers.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2541
+ "div",
2542
+ {
2543
+ style: {
2544
+ width: 260,
2545
+ borderLeft: "1px solid #e2e8f0",
2546
+ padding: "12px 16px",
2547
+ background: "#fafafa",
2548
+ fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
2549
+ fontSize: 14,
2550
+ overflowY: "auto"
2551
+ },
2552
+ children: [
2553
+ overlayGeojson && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2554
+ "div",
2555
+ {
2556
+ style: {
2557
+ border: "1px solid #bfdbfe",
2558
+ background: "#eff6ff",
2559
+ color: "#1e40af",
2560
+ padding: "8px 10px",
2561
+ borderRadius: 8,
2562
+ marginBottom: 12
2563
+ },
2564
+ children: [
2565
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 600, marginBottom: 4 }, children: "Overlay activo" }),
2566
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 13 }, children: [
2567
+ "GeoJSON externo con ",
2568
+ (overlayGeojson.features?.length ?? 0).toLocaleString(),
2569
+ " elementos."
2570
+ ] })
2571
+ ]
2572
+ }
2573
+ ),
2574
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontWeight: 600, marginBottom: 12 }, children: "Capas" }),
2575
+ decoratedLayers.map((layerState) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2576
+ "div",
2577
+ {
2578
+ style: { borderBottom: "1px solid #e5e7eb", paddingBottom: 10, marginBottom: 10 },
2579
+ children: [
2580
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "flex", gap: 8, alignItems: "center" }, children: [
2581
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2582
+ "input",
2583
+ {
2584
+ type: "checkbox",
2585
+ checked: layerState.effective?.visible ?? false,
2586
+ onChange: (e) => {
2587
+ const visible = e.target.checked;
2588
+ upsertUiOverride(layerState.mapLayer.layerId, { overrideVisible: visible });
2589
+ }
2590
+ }
2591
+ ),
2592
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: layerState.layer?.name ?? `Capa ${layerState.mapLayer.layerId}` })
2593
+ ] }),
2594
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: 8 }, children: [
2595
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: 4 }, children: [
2596
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#4a5568" }, children: "Opacidad" }),
2597
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
2598
+ Math.round((layerState.effective?.opacity ?? 1) * 100),
2599
+ "%"
2600
+ ] })
2601
+ ] }),
2602
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2603
+ "input",
2604
+ {
2605
+ type: "range",
2606
+ min: 0,
2607
+ max: 1,
2608
+ step: 0.05,
2609
+ value: layerState.effective?.opacity ?? 1,
2610
+ onChange: (e) => {
2611
+ const value = Number(e.target.value);
2612
+ updateOpacityFromUi(layerState.mapLayer.layerId, value);
2613
+ },
2614
+ style: { width: "100%" }
2615
+ }
2616
+ )
2617
+ ] })
2618
+ ]
2619
+ },
2620
+ layerState.mapLayer.layerId.toString()
2621
+ ))
2622
+ ]
2623
+ }
2624
+ )
2625
+ ]
2626
+ }
2627
+ );
2628
+ });
2629
+ ZenitMap.displayName = "ZenitMap";
2630
+
2631
+ // src/react/ZenitLayerManager.tsx
2632
+ var import_react2 = __toESM(require("react"));
2633
+
2634
+ // src/react/icons.tsx
2635
+ var import_lucide_react = require("lucide-react");
2636
+
2637
+ // src/react/ZenitLayerManager.tsx
2638
+ var import_jsx_runtime2 = require("react/jsx-runtime");
2639
+ var FLOAT_TOLERANCE = 1e-3;
2640
+ function areEffectiveStatesEqual(a, b) {
2641
+ if (a.length !== b.length) return false;
2642
+ return a.every((state, index) => {
2643
+ const other = b[index];
2644
+ if (!other) return false;
2645
+ const opacityDiff = Math.abs((state.opacity ?? 1) - (other.opacity ?? 1));
2646
+ return String(state.layerId) === String(other.layerId) && (state.visible ?? false) === (other.visible ?? false) && opacityDiff <= FLOAT_TOLERANCE;
2647
+ });
2648
+ }
2649
+ function getLayerColor2(style) {
2650
+ if (!style) return "#94a3b8";
2651
+ return style.fillColor ?? style.color ?? "#94a3b8";
2652
+ }
2653
+ function resolveDisplayOrder(value) {
2654
+ if (typeof value === "number" && Number.isFinite(value)) return value;
2655
+ if (typeof value === "string") {
2656
+ const parsed = Number.parseFloat(value);
2657
+ if (Number.isFinite(parsed)) return parsed;
2658
+ }
2659
+ return 0;
2660
+ }
2661
+ var ZenitLayerManager = ({
2662
+ client,
2663
+ mapId,
2664
+ side = "right",
2665
+ className,
2666
+ style,
2667
+ height,
2668
+ layerStates,
2669
+ onLayerStatesChange,
2670
+ mapZoom,
2671
+ autoOpacityOnZoom = false,
2672
+ autoOpacityConfig,
2673
+ showUploadTab = true,
2674
+ showLayerVisibilityIcon = true,
2675
+ layerFeatureCounts,
2676
+ mapLayers
2677
+ }) => {
2678
+ const [map, setMap] = (0, import_react2.useState)(null);
2679
+ const [loadingMap, setLoadingMap] = (0, import_react2.useState)(false);
2680
+ const [mapError, setMapError] = (0, import_react2.useState)(null);
2681
+ const [layers, setLayers] = (0, import_react2.useState)([]);
2682
+ const [activeTab, setActiveTab] = (0, import_react2.useState)("layers");
2683
+ const [panelVisible, setPanelVisible] = (0, import_react2.useState)(true);
2684
+ const lastEmittedStatesRef = (0, import_react2.useRef)(null);
2685
+ const isControlled = Array.isArray(layerStates) && typeof onLayerStatesChange === "function";
2686
+ const baseStates = (0, import_react2.useMemo)(
2687
+ () => initLayerStates(
2688
+ layers.map((entry) => ({
2689
+ ...entry.mapLayer,
2690
+ layerId: entry.mapLayer.layerId,
2691
+ isVisible: entry.visible,
2692
+ opacity: entry.opacity
2693
+ }))
2694
+ ),
2695
+ [layers]
2696
+ );
2697
+ const overrideStates = (0, import_react2.useMemo)(
2698
+ () => layers.map(
2699
+ (entry) => ({
2700
+ layerId: entry.mapLayer.layerId,
2701
+ overrideVisible: entry.visible,
2702
+ overrideOpacity: entry.opacity
2703
+ })
2704
+ ),
2705
+ [layers]
2706
+ );
2707
+ const effectiveStates = (0, import_react2.useMemo)(
2708
+ () => layerStates ?? applyLayerOverrides(baseStates, overrideStates),
2709
+ [baseStates, layerStates, overrideStates]
2710
+ );
2711
+ const layerMetaIndex = (0, import_react2.useMemo)(() => {
2712
+ const index = /* @__PURE__ */ new Map();
2713
+ mapLayers?.forEach((entry) => {
2714
+ const key = String(entry.layerId);
2715
+ index.set(key, { layerType: entry.layerType ?? void 0, geometryType: entry.geometryType ?? void 0 });
2716
+ });
2717
+ map?.mapLayers?.forEach((entry) => {
2718
+ const key = String(entry.layerId);
2719
+ if (!index.has(key)) {
2720
+ index.set(key, { layerType: entry.layerType ?? void 0, geometryType: entry.geometryType ?? void 0 });
2721
+ }
2722
+ });
2723
+ return index;
2724
+ }, [map, mapLayers]);
2725
+ const resolveUserOpacity = import_react2.default.useCallback((state) => {
2726
+ if (typeof state.overrideOpacity === "number") return state.overrideOpacity;
2727
+ if (typeof state.overrideOpacity === "string") {
2728
+ const parsed = Number.parseFloat(state.overrideOpacity);
2729
+ if (Number.isFinite(parsed)) return parsed;
2730
+ }
2731
+ return state.opacity ?? 1;
2732
+ }, []);
2733
+ const resolveEffectiveOpacity = import_react2.default.useCallback(
2734
+ (layerId, userOpacity) => {
2735
+ if (!autoOpacityOnZoom || typeof mapZoom !== "number") {
2736
+ return userOpacity;
2737
+ }
2738
+ const meta = layerMetaIndex.get(String(layerId));
2739
+ return getEffectiveLayerOpacity(
2740
+ userOpacity,
2741
+ mapZoom,
2742
+ meta?.layerType,
2743
+ meta?.geometryType,
2744
+ autoOpacityConfig
2745
+ );
2746
+ },
2747
+ [autoOpacityConfig, autoOpacityOnZoom, layerMetaIndex, mapZoom]
2748
+ );
2749
+ const effectiveStatesWithZoom = (0, import_react2.useMemo)(() => {
2750
+ if (!autoOpacityOnZoom || typeof mapZoom !== "number") {
2751
+ return effectiveStates;
2752
+ }
2753
+ return effectiveStates.map((state) => {
2754
+ const userOpacity = resolveUserOpacity(state);
2755
+ const adjustedOpacity = resolveEffectiveOpacity(state.layerId, userOpacity);
2756
+ return {
2757
+ ...state,
2758
+ opacity: adjustedOpacity,
2759
+ overrideOpacity: userOpacity
2760
+ };
2761
+ });
2762
+ }, [autoOpacityOnZoom, effectiveStates, mapZoom, resolveEffectiveOpacity, resolveUserOpacity]);
2763
+ (0, import_react2.useEffect)(() => {
2764
+ let cancelled = false;
2765
+ setLoadingMap(true);
2766
+ setMapError(null);
2767
+ setLayers([]);
2768
+ client.maps.getMap(mapId, true).then((mapResponse) => {
2769
+ if (cancelled) return;
2770
+ const extractedMap = extractMapDto(mapResponse);
2771
+ const resolvedMap = extractedMap ? normalizeMapCenter(extractedMap) : null;
2772
+ setMap(resolvedMap);
2773
+ const normalizedLayers = normalizeMapLayers(resolvedMap);
2774
+ setLayers(
2775
+ normalizedLayers.map((entry, index) => ({
2776
+ mapLayer: { ...entry.mapLayer, layerId: entry.layerId },
2777
+ layer: entry.layer,
2778
+ visible: entry.isVisible,
2779
+ opacity: entry.opacity,
2780
+ displayOrder: entry.displayOrder,
2781
+ initialIndex: index
2782
+ }))
2783
+ );
2784
+ }).catch((err) => {
2785
+ if (cancelled) return;
2786
+ const message = err instanceof Error ? err.message : "No se pudo cargar el mapa";
2787
+ setMapError(message);
2788
+ }).finally(() => {
2789
+ if (!cancelled) setLoadingMap(false);
2790
+ });
2791
+ return () => {
2792
+ cancelled = true;
2793
+ };
2794
+ }, [client.maps, mapId]);
2795
+ (0, import_react2.useEffect)(() => {
2796
+ if (!showUploadTab && activeTab === "upload") {
2797
+ setActiveTab("layers");
2798
+ }
2799
+ }, [activeTab, showUploadTab]);
2800
+ (0, import_react2.useEffect)(() => {
2801
+ if (isControlled) return;
2802
+ if (!onLayerStatesChange) return;
2803
+ const emitStates = autoOpacityOnZoom && typeof mapZoom === "number" ? effectiveStatesWithZoom : effectiveStates;
2804
+ const previous = lastEmittedStatesRef.current;
2805
+ if (previous && areEffectiveStatesEqual(previous, emitStates)) {
2806
+ return;
2807
+ }
2808
+ lastEmittedStatesRef.current = emitStates;
2809
+ onLayerStatesChange(emitStates);
2810
+ }, [
2811
+ autoOpacityOnZoom,
2812
+ effectiveStates,
2813
+ effectiveStatesWithZoom,
2814
+ isControlled,
2815
+ mapZoom,
2816
+ onLayerStatesChange
2817
+ ]);
2818
+ const updateLayerVisible = import_react2.default.useCallback(
2819
+ (layerId, visible) => {
2820
+ if (!onLayerStatesChange) return;
2821
+ const next = effectiveStates.map(
2822
+ (state) => state.layerId === layerId ? { ...state, visible, overrideVisible: visible } : state
2823
+ );
2824
+ onLayerStatesChange(next);
2825
+ },
2826
+ [effectiveStates, onLayerStatesChange]
2827
+ );
2828
+ const updateLayerOpacity = import_react2.default.useCallback(
2829
+ (layerId, opacity) => {
2830
+ if (!onLayerStatesChange) return;
2831
+ const adjustedOpacity = resolveEffectiveOpacity(layerId, opacity);
2832
+ const next = effectiveStates.map(
2833
+ (state) => state.layerId === layerId ? { ...state, opacity: adjustedOpacity, overrideOpacity: opacity } : state
2834
+ );
2835
+ onLayerStatesChange(next);
2836
+ },
2837
+ [effectiveStates, onLayerStatesChange, resolveEffectiveOpacity]
2838
+ );
2839
+ const resolveFeatureCount = import_react2.default.useCallback(
2840
+ (layerId, layer) => {
2841
+ const resolvedFeatureCount = layerFeatureCounts?.[layerId] ?? layerFeatureCounts?.[String(layerId)];
2842
+ if (typeof resolvedFeatureCount === "number") return resolvedFeatureCount;
2843
+ const featureCount = layer?.featuresCount ?? layer?.featureCount ?? layer?.totalFeatures;
2844
+ return typeof featureCount === "number" ? featureCount : null;
2845
+ },
2846
+ [layerFeatureCounts]
2847
+ );
2848
+ const decoratedLayers = (0, import_react2.useMemo)(() => {
2849
+ return layers.map((entry) => ({
2850
+ ...entry,
2851
+ effective: effectiveStates.find((state) => state.layerId === entry.mapLayer.layerId),
2852
+ featureCount: resolveFeatureCount(entry.mapLayer.layerId, entry.layer),
2853
+ layerName: entry.layer?.name ?? entry.mapLayer.name ?? `Capa ${entry.mapLayer.layerId}`
2854
+ })).sort((a, b) => {
2855
+ const aOrder = resolveDisplayOrder(
2856
+ a.displayOrder ?? a.mapLayer.displayOrder ?? a.mapLayer.order
2857
+ );
2858
+ const bOrder = resolveDisplayOrder(
2859
+ b.displayOrder ?? b.mapLayer.displayOrder ?? b.mapLayer.order
2860
+ );
2861
+ const aHasOrder = a.displayOrder ?? a.mapLayer.displayOrder ?? a.mapLayer.order ?? null;
2862
+ const bHasOrder = b.displayOrder ?? b.mapLayer.displayOrder ?? b.mapLayer.order ?? null;
2863
+ if (aHasOrder !== null && bHasOrder !== null) {
2864
+ const orderCompare = aOrder - bOrder;
2865
+ if (orderCompare !== 0) return orderCompare;
2866
+ }
2867
+ const aInitial = a.initialIndex ?? 0;
2868
+ const bInitial = b.initialIndex ?? 0;
2869
+ if (aInitial !== bInitial) return aInitial - bInitial;
2870
+ const aName = a.layerName ?? String(a.mapLayer.layerId);
2871
+ const bName = b.layerName ?? String(b.mapLayer.layerId);
2872
+ const nameCompare = aName.localeCompare(bName, void 0, { sensitivity: "base" });
2873
+ if (nameCompare !== 0) return nameCompare;
2874
+ return String(a.mapLayer.layerId).localeCompare(String(b.mapLayer.layerId));
2875
+ });
2876
+ }, [effectiveStates, layers, resolveFeatureCount]);
2877
+ const resolveLayerStyle = import_react2.default.useCallback(
2878
+ (layerId) => {
2879
+ const layerKey = String(layerId);
2880
+ const fromProp = mapLayers?.find((entry) => String(entry.layerId) === layerKey)?.style;
2881
+ if (fromProp) return fromProp;
2882
+ const fromMapDto = map?.mapLayers?.find((entry) => String(entry.layerId) === layerKey);
2883
+ if (fromMapDto?.layer) {
2884
+ const layer = fromMapDto.layer;
2885
+ if (layer.style) return layer.style;
2886
+ }
2887
+ return void 0;
2888
+ },
2889
+ [map, mapLayers]
2890
+ );
2891
+ const panelStyle = {
2892
+ width: 360,
2893
+ borderLeft: side === "right" ? "1px solid #e2e8f0" : void 0,
2894
+ borderRight: side === "left" ? "1px solid #e2e8f0" : void 0,
2895
+ background: "#f1f5f9",
2896
+ fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
2897
+ fontSize: 14,
2898
+ boxSizing: "border-box",
2899
+ height: height ?? "100%",
2900
+ display: "flex",
2901
+ flexDirection: "column",
2902
+ boxShadow: "0 12px 28px rgba(15, 23, 42, 0.08)",
2903
+ ...style,
2904
+ ...height ? { height } : {}
2905
+ };
2906
+ if (loadingMap) {
2907
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className, style: panelStyle, children: "Cargando capas\u2026" });
2908
+ }
2909
+ if (mapError) {
2910
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, style: { ...panelStyle, color: "#c53030" }, children: [
2911
+ "Error al cargar mapa: ",
2912
+ mapError
2913
+ ] });
2914
+ }
2915
+ if (!map) {
2916
+ return null;
2917
+ }
2918
+ const headerStyle = {
2919
+ padding: "14px 16px",
2920
+ borderBottom: "1px solid #e2e8f0",
2921
+ background: "#fff",
2922
+ position: "sticky",
2923
+ top: 0,
2924
+ zIndex: 2,
2925
+ boxShadow: "0 1px 0 rgba(148, 163, 184, 0.25)"
2926
+ };
2927
+ const renderLayerCards = () => {
2928
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: 12 }, children: decoratedLayers.map((layerState) => {
2929
+ const layerId = layerState.mapLayer.layerId;
2930
+ const layerName = layerState.layerName ?? `Capa ${layerId}`;
2931
+ const visible = layerState.effective?.visible ?? false;
2932
+ const userOpacity = layerState.effective ? resolveUserOpacity(layerState.effective) : 1;
2933
+ const featureCount = layerState.featureCount;
2934
+ const layerColor = getLayerColor2(resolveLayerStyle(layerId));
2935
+ const muted = !visible;
2936
+ const opacityPercent = Math.round(userOpacity * 100);
2937
+ const sliderBackground = `linear-gradient(to right, ${layerColor} 0%, ${layerColor} ${opacityPercent}%, #e5e7eb ${opacityPercent}%, #e5e7eb 100%)`;
2938
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2939
+ "div",
2940
+ {
2941
+ className: `zlm-card${muted ? " is-muted" : ""}`,
2942
+ style: {
2943
+ border: "1px solid #e2e8f0",
2944
+ borderRadius: 12,
2945
+ padding: 12,
2946
+ background: "#fff",
2947
+ display: "flex",
2948
+ flexDirection: "column",
2949
+ gap: 10,
2950
+ width: "100%"
2951
+ },
2952
+ children: [
2953
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", gap: 12 }, children: [
2954
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 10, alignItems: "flex-start", minWidth: 0, flex: 1 }, children: [
2955
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2956
+ "div",
2957
+ {
2958
+ style: {
2959
+ width: 14,
2960
+ height: 14,
2961
+ borderRadius: "50%",
2962
+ backgroundColor: layerColor,
2963
+ border: "2px solid #e2e8f0",
2964
+ flexShrink: 0,
2965
+ marginTop: 4
2966
+ },
2967
+ title: "Color de la capa"
2968
+ }
2969
+ ),
2970
+ showLayerVisibilityIcon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2971
+ "button",
2972
+ {
2973
+ type: "button",
2974
+ className: `zlm-icon-button${visible ? " is-active" : ""}`,
2975
+ onClick: () => isControlled ? updateLayerVisible(layerId, !visible) : setLayers(
2976
+ (prev) => prev.map(
2977
+ (entry) => entry.mapLayer.layerId === layerId ? { ...entry, visible: !visible } : entry
2978
+ )
2979
+ ),
2980
+ "aria-label": visible ? "Ocultar capa" : "Mostrar capa",
2981
+ children: visible ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Eye, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.EyeOff, { size: 16 })
2982
+ }
2983
+ ),
2984
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { minWidth: 0, flex: 1 }, children: [
2985
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2986
+ "div",
2987
+ {
2988
+ className: "zlm-layer-name",
2989
+ style: {
2990
+ fontWeight: 700,
2991
+ display: "-webkit-box",
2992
+ WebkitLineClamp: 2,
2993
+ WebkitBoxOrient: "vertical",
2994
+ overflow: "hidden",
2995
+ overflowWrap: "anywhere",
2996
+ lineHeight: 1.2,
2997
+ color: muted ? "#64748b" : "#0f172a"
2998
+ },
2999
+ title: layerName,
3000
+ children: layerName
3001
+ }
3002
+ ),
3003
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { color: muted ? "#94a3b8" : "#64748b", fontSize: 12 }, children: [
3004
+ "ID ",
3005
+ layerId
3006
+ ] })
3007
+ ] })
3008
+ ] }),
3009
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", alignItems: "flex-start", gap: 6, flexShrink: 0 }, children: typeof featureCount === "number" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "zlm-badge", children: [
3010
+ featureCount.toLocaleString(),
3011
+ " features"
3012
+ ] }) })
3013
+ ] }),
3014
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", gap: 10, alignItems: "center" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { flex: 1 }, children: [
3015
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", marginBottom: 6, color: "#64748b", fontSize: 12 }, children: [
3016
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Opacidad" }),
3017
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
3018
+ opacityPercent,
3019
+ "%"
3020
+ ] })
3021
+ ] }),
3022
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3023
+ "input",
3024
+ {
3025
+ className: "zlm-range",
3026
+ type: "range",
3027
+ min: 0,
3028
+ max: 1,
3029
+ step: 0.05,
3030
+ value: userOpacity,
3031
+ style: {
3032
+ width: "100%",
3033
+ accentColor: layerColor,
3034
+ background: sliderBackground,
3035
+ height: 6,
3036
+ borderRadius: 999
3037
+ },
3038
+ onChange: (e) => {
3039
+ const value = Number(e.target.value);
3040
+ if (isControlled) {
3041
+ updateLayerOpacity(layerId, value);
3042
+ return;
3043
+ }
3044
+ setLayers(
3045
+ (prev) => prev.map(
3046
+ (entry) => entry.mapLayer.layerId === layerId ? { ...entry, opacity: value } : entry
3047
+ )
3048
+ );
3049
+ },
3050
+ "aria-label": `Opacidad de la capa ${layerName}`
3051
+ }
3052
+ )
3053
+ ] }) })
3054
+ ]
3055
+ },
3056
+ layerId.toString()
3057
+ );
3058
+ }) });
3059
+ };
3060
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: ["zenit-layer-manager", className].filter(Boolean).join(" "), style: panelStyle, children: [
3061
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `
3062
+ .zenit-layer-manager .zlm-card {
3063
+ transition: box-shadow 0.2s ease, transform 0.2s ease, opacity 0.2s ease;
3064
+ box-shadow: 0 6px 16px rgba(15, 23, 42, 0.08);
3065
+ }
3066
+ .zenit-layer-manager .zlm-card.is-muted {
3067
+ opacity: 0.7;
3068
+ }
3069
+ .zenit-layer-manager .zlm-badge {
3070
+ display: inline-flex;
3071
+ align-items: center;
3072
+ gap: 4px;
3073
+ margin-top: 6px;
3074
+ padding: 2px 8px;
3075
+ border-radius: 999px;
3076
+ background: #f1f5f9;
3077
+ color: #475569;
3078
+ font-size: 11px;
3079
+ font-weight: 600;
3080
+ }
3081
+ .zenit-layer-manager .zlm-icon-button {
3082
+ border: 1px solid #e2e8f0;
3083
+ background: #f8fafc;
3084
+ color: #475569;
3085
+ border-radius: 8px;
3086
+ width: 34px;
3087
+ height: 34px;
3088
+ display: inline-flex;
3089
+ align-items: center;
3090
+ justify-content: center;
3091
+ cursor: pointer;
3092
+ transition: all 0.15s ease;
3093
+ }
3094
+ .zenit-layer-manager .zlm-icon-button.is-active {
3095
+ background: #0f172a;
3096
+ color: #fff;
3097
+ border-color: #0f172a;
3098
+ }
3099
+ .zenit-layer-manager .zlm-icon-button:hover {
3100
+ box-shadow: 0 4px 10px rgba(15, 23, 42, 0.12);
3101
+ }
3102
+ .zenit-layer-manager .zlm-icon-button:focus-visible {
3103
+ outline: 2px solid #60a5fa;
3104
+ outline-offset: 2px;
3105
+ }
3106
+ .zenit-layer-manager .zlm-range {
3107
+ width: 100%;
3108
+ accent-color: #0f172a;
3109
+ }
3110
+ .zenit-layer-manager .zlm-tab {
3111
+ flex: 1;
3112
+ padding: 8px 12px;
3113
+ border: none;
3114
+ background: transparent;
3115
+ color: #475569;
3116
+ font-weight: 600;
3117
+ cursor: pointer;
3118
+ display: inline-flex;
3119
+ align-items: center;
3120
+ justify-content: center;
3121
+ gap: 6px;
3122
+ font-size: 13px;
3123
+ }
3124
+ .zenit-layer-manager .zlm-tab.is-active {
3125
+ background: #fff;
3126
+ color: #0f172a;
3127
+ box-shadow: 0 4px 10px rgba(15, 23, 42, 0.08);
3128
+ border-radius: 10px;
3129
+ }
3130
+ .zenit-layer-manager .zlm-tab:focus-visible {
3131
+ outline: 2px solid #60a5fa;
3132
+ outline-offset: 2px;
3133
+ }
3134
+ .zenit-layer-manager .zlm-panel-toggle {
3135
+ border: 1px solid #e2e8f0;
3136
+ background: #fff;
3137
+ color: #0f172a;
3138
+ border-radius: 10px;
3139
+ padding: 6px 10px;
3140
+ display: inline-flex;
3141
+ align-items: center;
3142
+ gap: 6px;
3143
+ font-size: 12px;
3144
+ font-weight: 600;
3145
+ cursor: pointer;
3146
+ transition: all 0.15s ease;
3147
+ }
3148
+ .zenit-layer-manager .zlm-panel-toggle:hover {
3149
+ box-shadow: 0 4px 10px rgba(15, 23, 42, 0.12);
3150
+ }
3151
+ .zenit-layer-manager .zlm-panel-toggle:focus-visible {
3152
+ outline: 2px solid #60a5fa;
3153
+ outline-offset: 2px;
3154
+ }
3155
+ ` }),
3156
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: headerStyle, children: [
3157
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
3158
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
3159
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { fontWeight: 800, fontSize: 16, color: "#0f172a" }, children: "Gesti\xF3n de Capas" }),
3160
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { color: "#64748b", fontSize: 12 }, children: [
3161
+ "Mapa #",
3162
+ map.id
3163
+ ] })
3164
+ ] }),
3165
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
3166
+ "button",
3167
+ {
3168
+ type: "button",
3169
+ onClick: () => setPanelVisible((prev) => !prev),
3170
+ className: "zlm-panel-toggle",
3171
+ "aria-label": panelVisible ? "Ocultar panel de capas" : "Mostrar panel de capas",
3172
+ children: [
3173
+ panelVisible ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Eye, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.EyeOff, { size: 16 }),
3174
+ panelVisible ? "Ocultar" : "Mostrar"
3175
+ ]
3176
+ }
3177
+ )
3178
+ ] }),
3179
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
3180
+ "div",
3181
+ {
3182
+ style: {
3183
+ display: "flex",
3184
+ gap: 6,
3185
+ marginTop: 12,
3186
+ padding: 4,
3187
+ border: "1px solid #e2e8f0",
3188
+ borderRadius: 12,
3189
+ background: "#f1f5f9"
3190
+ },
3191
+ children: [
3192
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
3193
+ "button",
3194
+ {
3195
+ type: "button",
3196
+ className: `zlm-tab${activeTab === "layers" ? " is-active" : ""}`,
3197
+ onClick: () => setActiveTab("layers"),
3198
+ children: [
3199
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Layers, { size: 16 }),
3200
+ "Capas"
3201
+ ]
3202
+ }
3203
+ ),
3204
+ showUploadTab && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
3205
+ "button",
3206
+ {
3207
+ type: "button",
3208
+ className: `zlm-tab${activeTab === "upload" ? " is-active" : ""}`,
3209
+ onClick: () => setActiveTab("upload"),
3210
+ children: [
3211
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.Upload, { size: 16 }),
3212
+ "Subir"
3213
+ ]
3214
+ }
3215
+ )
3216
+ ]
3217
+ }
3218
+ )
3219
+ ] }),
3220
+ panelVisible && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { padding: "12px 10px 18px", overflowY: "auto", flex: 1, minHeight: 0 }, children: [
3221
+ activeTab === "layers" && renderLayerCards(),
3222
+ showUploadTab && activeTab === "upload" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { color: "#475569", fontSize: 13 }, children: "Pr\xF3ximamente podr\xE1s subir capas desde este panel." })
3223
+ ] })
3224
+ ] });
3225
+ };
3226
+
3227
+ // src/react/ZenitFeatureFilterPanel.tsx
3228
+ var import_jsx_runtime3 = require("react/jsx-runtime");
3229
+ var ZenitFeatureFilterPanel = ({
3230
+ title = "Filtros",
3231
+ description,
3232
+ className,
3233
+ style,
3234
+ children
3235
+ }) => {
3236
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3237
+ "section",
3238
+ {
3239
+ className,
3240
+ style: {
3241
+ border: "1px solid #e2e8f0",
3242
+ borderRadius: 12,
3243
+ padding: 16,
3244
+ background: "#fff",
3245
+ fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
3246
+ ...style
3247
+ },
3248
+ children: [
3249
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { style: { marginBottom: 12 }, children: [
3250
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { style: { margin: 0, fontSize: 16 }, children: title }),
3251
+ description && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: { margin: "6px 0 0", color: "#475569", fontSize: 13 }, children: description })
3252
+ ] }),
3253
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children })
3254
+ ]
3255
+ }
3256
+ );
3257
+ };
3258
+
3259
+ // src/react/ai/FloatingChatBox.tsx
3260
+ var import_react4 = require("react");
3261
+ var import_react_dom = require("react-dom");
3262
+
3263
+ // src/react/hooks/use-chat.ts
3264
+ var import_react3 = require("react");
3265
+ var useSendMessage = (config) => {
3266
+ const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
3267
+ const [error, setError] = (0, import_react3.useState)(null);
3268
+ const send = (0, import_react3.useCallback)(
3269
+ async (mapId, request, options) => {
3270
+ setIsLoading(true);
3271
+ setError(null);
3272
+ try {
3273
+ return await sendMessage(mapId, request, options, config);
3274
+ } catch (err) {
3275
+ setError(err);
3276
+ throw err;
3277
+ } finally {
3278
+ setIsLoading(false);
3279
+ }
3280
+ },
3281
+ [config]
3282
+ );
3283
+ return { sendMessage: send, isLoading, error };
3284
+ };
3285
+ var useSendMessageStream = (config) => {
3286
+ const [isStreaming, setIsStreaming] = (0, import_react3.useState)(false);
3287
+ const [streamingText, setStreamingText] = (0, import_react3.useState)("");
3288
+ const [completeResponse, setCompleteResponse] = (0, import_react3.useState)(null);
3289
+ const [error, setError] = (0, import_react3.useState)(null);
3290
+ const requestIdRef = (0, import_react3.useRef)(0);
3291
+ const reset = (0, import_react3.useCallback)(() => {
3292
+ setIsStreaming(false);
3293
+ setStreamingText("");
3294
+ setCompleteResponse(null);
3295
+ setError(null);
3296
+ }, []);
3297
+ const send = (0, import_react3.useCallback)(
3298
+ async (mapId, request, options) => {
3299
+ const requestId = requestIdRef.current + 1;
3300
+ requestIdRef.current = requestId;
3301
+ setIsStreaming(true);
3302
+ setStreamingText("");
3303
+ setCompleteResponse(null);
3304
+ setError(null);
3305
+ try {
3306
+ const response = await sendMessageStream(
3307
+ mapId,
3308
+ request,
3309
+ {
3310
+ onChunk: (_chunk, aggregated) => {
3311
+ if (requestIdRef.current !== requestId) return;
3312
+ setStreamingText(aggregated);
3313
+ },
3314
+ onError: (err) => {
3315
+ if (requestIdRef.current !== requestId) return;
3316
+ setError(err);
3317
+ },
3318
+ onComplete: (finalResponse) => {
3319
+ if (requestIdRef.current !== requestId) return;
3320
+ setCompleteResponse(finalResponse);
3321
+ }
3322
+ },
3323
+ options,
3324
+ config
3325
+ );
3326
+ return response;
3327
+ } catch (err) {
3328
+ setError(err);
3329
+ throw err;
3330
+ } finally {
3331
+ if (requestIdRef.current === requestId) {
3332
+ setIsStreaming(false);
3333
+ }
3334
+ }
3335
+ },
3336
+ [config]
3337
+ );
3338
+ return {
3339
+ sendMessage: send,
3340
+ isStreaming,
3341
+ streamingText,
3342
+ completeResponse,
3343
+ error,
3344
+ reset
3345
+ };
3346
+ };
3347
+
3348
+ // src/react/components/MarkdownRenderer.tsx
3349
+ var import_react_markdown = __toESM(require("react-markdown"));
3350
+ var import_remark_gfm = __toESM(require("remark-gfm"));
3351
+ var import_jsx_runtime4 = require("react/jsx-runtime");
3352
+ function normalizeAssistantMarkdown(text) {
3353
+ if (!text || typeof text !== "string") return "";
3354
+ let normalized = text;
3355
+ normalized = normalized.replace(/\r\n/g, "\n");
3356
+ normalized = normalized.replace(/^\s*#{1,6}\s*$/gm, "");
3357
+ normalized = normalized.replace(/\n{3,}/g, "\n\n");
3358
+ normalized = normalized.split("\n").map((line) => line.trimEnd()).join("\n");
3359
+ normalized = normalized.trim();
3360
+ return normalized;
3361
+ }
3362
+ var MarkdownRenderer = ({ content, className }) => {
3363
+ const normalizedContent = normalizeAssistantMarkdown(content);
3364
+ if (!normalizedContent) {
3365
+ return null;
3366
+ }
3367
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className, style: { wordBreak: "break-word" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3368
+ import_react_markdown.default,
3369
+ {
3370
+ remarkPlugins: [import_remark_gfm.default],
3371
+ components: {
3372
+ // Headings with proper spacing
3373
+ h1: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h1", { style: { fontSize: "1.5em", fontWeight: 700, marginTop: "1em", marginBottom: "0.5em" }, ...props, children }),
3374
+ h2: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { style: { fontSize: "1.3em", fontWeight: 700, marginTop: "0.9em", marginBottom: "0.45em" }, ...props, children }),
3375
+ h3: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { style: { fontSize: "1.15em", fontWeight: 600, marginTop: "0.75em", marginBottom: "0.4em" }, ...props, children }),
3376
+ h4: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { style: { fontSize: "1.05em", fontWeight: 600, marginTop: "0.6em", marginBottom: "0.35em" }, ...props, children }),
3377
+ h5: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h5", { style: { fontSize: "1em", fontWeight: 600, marginTop: "0.5em", marginBottom: "0.3em" }, ...props, children }),
3378
+ h6: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h6", { style: { fontSize: "0.95em", fontWeight: 600, marginTop: "0.5em", marginBottom: "0.3em" }, ...props, children }),
3379
+ // Paragraphs with comfortable line height
3380
+ p: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: { marginTop: "0.5em", marginBottom: "0.5em", lineHeight: 1.6 }, ...props, children }),
3381
+ // Lists with proper indentation
3382
+ ul: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { style: { paddingLeft: "1.5em", marginTop: "0.5em", marginBottom: "0.5em" }, ...props, children }),
3383
+ ol: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ol", { style: { paddingLeft: "1.5em", marginTop: "0.5em", marginBottom: "0.5em" }, ...props, children }),
3384
+ li: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { style: { marginTop: "0.25em", marginBottom: "0.25em" }, ...props, children }),
3385
+ // Code blocks
3386
+ code: ({ inline, children, ...props }) => {
3387
+ if (inline) {
3388
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3389
+ "code",
3390
+ {
3391
+ style: {
3392
+ backgroundColor: "rgba(0, 0, 0, 0.08)",
3393
+ padding: "0.15em 0.4em",
3394
+ borderRadius: "4px",
3395
+ fontSize: "0.9em",
3396
+ fontFamily: "monospace"
3397
+ },
3398
+ ...props,
3399
+ children
3400
+ }
3401
+ );
3402
+ }
3403
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3404
+ "code",
3405
+ {
3406
+ style: {
3407
+ display: "block",
3408
+ backgroundColor: "rgba(0, 0, 0, 0.08)",
3409
+ padding: "0.75em",
3410
+ borderRadius: "6px",
3411
+ fontSize: "0.9em",
3412
+ fontFamily: "monospace",
3413
+ overflowX: "auto",
3414
+ marginTop: "0.5em",
3415
+ marginBottom: "0.5em"
3416
+ },
3417
+ ...props,
3418
+ children
3419
+ }
3420
+ );
3421
+ },
3422
+ // Pre (code block wrapper)
3423
+ pre: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("pre", { style: { margin: 0 }, ...props, children }),
3424
+ // Blockquotes
3425
+ blockquote: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3426
+ "blockquote",
3427
+ {
3428
+ style: {
3429
+ borderLeft: "4px solid rgba(0, 0, 0, 0.2)",
3430
+ paddingLeft: "1em",
3431
+ marginLeft: 0,
3432
+ marginTop: "0.5em",
3433
+ marginBottom: "0.5em",
3434
+ color: "rgba(0, 0, 0, 0.7)"
3435
+ },
3436
+ ...props,
3437
+ children
3438
+ }
3439
+ ),
3440
+ // Strong/bold
3441
+ strong: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { style: { fontWeight: 600 }, ...props, children }),
3442
+ // Emphasis/italic
3443
+ em: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("em", { style: { fontStyle: "italic" }, ...props, children }),
3444
+ // Horizontal rule
3445
+ hr: (props) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3446
+ "hr",
3447
+ {
3448
+ style: {
3449
+ border: "none",
3450
+ borderTop: "1px solid rgba(0, 0, 0, 0.1)",
3451
+ marginTop: "1em",
3452
+ marginBottom: "1em"
3453
+ },
3454
+ ...props
3455
+ }
3456
+ ),
3457
+ // Tables (GFM)
3458
+ table: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { overflowX: "auto", marginTop: "0.5em", marginBottom: "0.5em" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3459
+ "table",
3460
+ {
3461
+ style: {
3462
+ borderCollapse: "collapse",
3463
+ width: "100%",
3464
+ fontSize: "0.9em"
3465
+ },
3466
+ ...props,
3467
+ children
3468
+ }
3469
+ ) }),
3470
+ th: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3471
+ "th",
3472
+ {
3473
+ style: {
3474
+ border: "1px solid rgba(0, 0, 0, 0.2)",
3475
+ padding: "0.5em",
3476
+ backgroundColor: "rgba(0, 0, 0, 0.05)",
3477
+ fontWeight: 600,
3478
+ textAlign: "left"
3479
+ },
3480
+ ...props,
3481
+ children
3482
+ }
3483
+ ),
3484
+ td: ({ children, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3485
+ "td",
3486
+ {
3487
+ style: {
3488
+ border: "1px solid rgba(0, 0, 0, 0.2)",
3489
+ padding: "0.5em"
3490
+ },
3491
+ ...props,
3492
+ children
3493
+ }
3494
+ )
3495
+ },
3496
+ children: normalizedContent
3497
+ }
3498
+ ) });
3499
+ };
3500
+
3501
+ // src/react/ai/FloatingChatBox.tsx
3502
+ var import_jsx_runtime5 = require("react/jsx-runtime");
3503
+ var ChatIcon = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) });
3504
+ var CloseIcon = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3505
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
3506
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
3507
+ ] });
3508
+ var ExpandIcon = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3509
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "15 3 21 3 21 9" }),
3510
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "9 21 3 21 3 15" }),
3511
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "21", y1: "3", x2: "14", y2: "10" }),
3512
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
3513
+ ] });
3514
+ var CollapseIcon = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3515
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "4 14 10 14 10 20" }),
3516
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "20 10 14 10 14 4" }),
3517
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "14", y1: "10", x2: "21", y2: "3" }),
3518
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
3519
+ ] });
3520
+ var SendIcon = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3521
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
3522
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
3523
+ ] });
3524
+ var LayersIcon = () => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3525
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polygon", { points: "12 2 2 7 12 12 22 7 12 2" }),
3526
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "2 17 12 22 22 17" }),
3527
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polyline", { points: "2 12 12 17 22 12" })
3528
+ ] });
3529
+ var styles = {
3530
+ root: {
3531
+ fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
3532
+ },
3533
+ // Floating button (closed state - wide with text, open state - circular with X)
3534
+ floatingButton: {
3535
+ position: "fixed",
3536
+ bottom: 24,
3537
+ right: 24,
3538
+ borderRadius: "999px",
3539
+ border: "none",
3540
+ cursor: "pointer",
3541
+ background: "linear-gradient(135deg, #10b981, #059669)",
3542
+ color: "#fff",
3543
+ boxShadow: "0 12px 28px rgba(16, 185, 129, 0.4)",
3544
+ display: "flex",
3545
+ alignItems: "center",
3546
+ justifyContent: "center",
3547
+ fontSize: 15,
3548
+ fontWeight: 600,
3549
+ zIndex: 99999,
3550
+ transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
3551
+ },
3552
+ floatingButtonClosed: {
3553
+ padding: "14px 24px",
3554
+ gap: 8
3555
+ },
3556
+ floatingButtonOpen: {
3557
+ width: 56,
3558
+ height: 56,
3559
+ padding: 0
3560
+ },
3561
+ floatingButtonMobile: {
3562
+ width: 56,
3563
+ height: 56,
3564
+ padding: 0
3565
+ },
3566
+ // Panel (expandable)
3567
+ panel: {
3568
+ position: "fixed",
3569
+ bottom: 92,
3570
+ right: 24,
3571
+ background: "#fff",
3572
+ borderRadius: 16,
3573
+ boxShadow: "0 20px 60px rgba(15, 23, 42, 0.3), 0 0 0 1px rgba(15, 23, 42, 0.05)",
3574
+ display: "flex",
3575
+ flexDirection: "column",
3576
+ overflow: "hidden",
3577
+ zIndex: 99999,
3578
+ transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
3579
+ },
3580
+ panelNormal: {
3581
+ width: 400,
3582
+ height: 550
3583
+ },
3584
+ panelExpanded: {
3585
+ width: 520,
3586
+ height: 700
3587
+ },
3588
+ // Header with green gradient
3589
+ header: {
3590
+ padding: "16px 18px",
3591
+ background: "linear-gradient(135deg, #10b981, #059669)",
3592
+ color: "#fff",
3593
+ display: "flex",
3594
+ alignItems: "center",
3595
+ justifyContent: "space-between"
3596
+ },
3597
+ title: {
3598
+ margin: 0,
3599
+ fontSize: 16,
3600
+ fontWeight: 600,
3601
+ letterSpacing: "-0.01em"
3602
+ },
3603
+ headerButtons: {
3604
+ display: "flex",
3605
+ alignItems: "center",
3606
+ gap: 8
3607
+ },
3608
+ headerButton: {
3609
+ border: "none",
3610
+ background: "rgba(255, 255, 255, 0.15)",
3611
+ color: "#fff",
3612
+ width: 32,
3613
+ height: 32,
3614
+ borderRadius: 8,
3615
+ cursor: "pointer",
3616
+ display: "flex",
3617
+ alignItems: "center",
3618
+ justifyContent: "center",
3619
+ transition: "background 0.2s"
3620
+ },
3621
+ // Messages area
3622
+ messages: {
3623
+ flex: 1,
3624
+ padding: "20px 18px",
3625
+ overflowY: "auto",
3626
+ background: "#f8fafc",
3627
+ display: "flex",
3628
+ flexDirection: "column",
3629
+ gap: 16
3630
+ },
3631
+ // Message bubbles
3632
+ messageWrapper: {
3633
+ display: "flex",
3634
+ flexDirection: "column",
3635
+ gap: 8
3636
+ },
3637
+ messageBubble: {
3638
+ maxWidth: "85%",
3639
+ padding: "12px 14px",
3640
+ borderRadius: 16,
3641
+ lineHeight: 1.5,
3642
+ fontSize: 14,
3643
+ whiteSpace: "pre-wrap",
3644
+ wordBreak: "break-word"
3645
+ },
3646
+ userMessage: {
3647
+ alignSelf: "flex-end",
3648
+ background: "linear-gradient(135deg, #10b981, #059669)",
3649
+ color: "#fff",
3650
+ borderBottomRightRadius: 4
3651
+ },
3652
+ assistantMessage: {
3653
+ alignSelf: "flex-start",
3654
+ background: "#e2e8f0",
3655
+ color: "#0f172a",
3656
+ borderBottomLeftRadius: 4
3657
+ },
3658
+ // Streaming cursor
3659
+ cursor: {
3660
+ display: "inline-block",
3661
+ width: 2,
3662
+ height: 16,
3663
+ background: "#0f172a",
3664
+ marginLeft: 2,
3665
+ animation: "zenitBlink 1s infinite",
3666
+ verticalAlign: "text-bottom"
3667
+ },
3668
+ thinkingText: {
3669
+ fontStyle: "italic",
3670
+ opacity: 0.7,
3671
+ display: "flex",
3672
+ alignItems: "center",
3673
+ gap: 8
3674
+ },
3675
+ typingIndicator: {
3676
+ display: "flex",
3677
+ gap: 4,
3678
+ alignItems: "center"
3679
+ },
3680
+ typingDot: {
3681
+ width: 8,
3682
+ height: 8,
3683
+ borderRadius: "50%",
3684
+ backgroundColor: "#059669",
3685
+ animation: "typingDotBounce 1.4s infinite ease-in-out"
3686
+ },
3687
+ // Metadata section (Referenced Layers)
3688
+ metadataSection: {
3689
+ marginTop: 12,
3690
+ padding: "10px 12px",
3691
+ background: "rgba(255, 255, 255, 0.5)",
3692
+ borderRadius: 10,
3693
+ fontSize: 12
3694
+ },
3695
+ metadataTitle: {
3696
+ fontWeight: 600,
3697
+ color: "#059669",
3698
+ marginBottom: 6,
3699
+ display: "flex",
3700
+ alignItems: "center",
3701
+ gap: 6
3702
+ },
3703
+ metadataList: {
3704
+ margin: 0,
3705
+ paddingLeft: 18,
3706
+ color: "#475569"
3707
+ },
3708
+ metadataItem: {
3709
+ marginBottom: 2
3710
+ },
3711
+ // Suggested actions
3712
+ actionsSection: {
3713
+ marginTop: 12
3714
+ },
3715
+ sectionLabel: {
3716
+ fontSize: 11,
3717
+ fontWeight: 600,
3718
+ color: "#64748b",
3719
+ marginBottom: 8,
3720
+ textTransform: "uppercase",
3721
+ letterSpacing: "0.05em"
3722
+ },
3723
+ actionsGrid: {
3724
+ display: "flex",
3725
+ flexWrap: "wrap",
3726
+ gap: 6
3727
+ },
3728
+ actionButton: {
3729
+ border: "1px solid #d1fae5",
3730
+ background: "#d1fae5",
3731
+ color: "#065f46",
3732
+ borderRadius: 999,
3733
+ padding: "6px 12px",
3734
+ fontSize: 12,
3735
+ fontWeight: 500,
3736
+ cursor: "pointer",
3737
+ transition: "all 0.2s"
3738
+ },
3739
+ // Follow-up questions
3740
+ followUpButton: {
3741
+ border: "1px solid #cbd5e1",
3742
+ background: "#fff",
3743
+ color: "#475569",
3744
+ borderRadius: 10,
3745
+ padding: "8px 12px",
3746
+ fontSize: 12,
3747
+ fontWeight: 500,
3748
+ cursor: "pointer",
3749
+ textAlign: "left",
3750
+ width: "100%",
3751
+ transition: "all 0.2s"
3752
+ },
3753
+ // Input area
3754
+ inputWrapper: {
3755
+ borderTop: "1px solid #e2e8f0",
3756
+ padding: "14px 16px",
3757
+ display: "flex",
3758
+ gap: 10,
3759
+ alignItems: "flex-end",
3760
+ background: "#fff"
3761
+ },
3762
+ textarea: {
3763
+ flex: 1,
3764
+ resize: "none",
3765
+ borderRadius: 12,
3766
+ border: "1.5px solid #cbd5e1",
3767
+ padding: "10px 12px",
3768
+ fontSize: 14,
3769
+ fontFamily: "inherit",
3770
+ lineHeight: 1.4,
3771
+ transition: "border-color 0.2s"
3772
+ },
3773
+ textareaFocus: {
3774
+ borderColor: "#10b981",
3775
+ outline: "none"
3776
+ },
3777
+ sendButton: {
3778
+ borderRadius: 12,
3779
+ border: "none",
3780
+ padding: "10px 14px",
3781
+ background: "linear-gradient(135deg, #10b981, #059669)",
3782
+ color: "#fff",
3783
+ cursor: "pointer",
3784
+ fontSize: 14,
3785
+ fontWeight: 600,
3786
+ display: "flex",
3787
+ alignItems: "center",
3788
+ justifyContent: "center",
3789
+ transition: "opacity 0.2s, transform 0.2s",
3790
+ minWidth: 44,
3791
+ height: 44
3792
+ },
3793
+ // Status messages
3794
+ statusNote: {
3795
+ padding: "0 16px 14px",
3796
+ fontSize: 12,
3797
+ color: "#64748b",
3798
+ textAlign: "center"
3799
+ },
3800
+ errorText: {
3801
+ padding: "0 16px 14px",
3802
+ fontSize: 12,
3803
+ color: "#dc2626",
3804
+ textAlign: "center"
3805
+ }
3806
+ };
3807
+ var FloatingChatBox = ({
3808
+ mapId,
3809
+ filteredLayerIds,
3810
+ filters,
3811
+ userId,
3812
+ baseUrl,
3813
+ accessToken,
3814
+ getAccessToken,
3815
+ onActionClick,
3816
+ onOpenChange,
3817
+ hideButton,
3818
+ open: openProp
3819
+ }) => {
3820
+ const isControlled = openProp !== void 0;
3821
+ const [internalOpen, setInternalOpen] = (0, import_react4.useState)(false);
3822
+ const open = isControlled ? openProp : internalOpen;
3823
+ const setOpen = (0, import_react4.useCallback)((value) => {
3824
+ const newValue = typeof value === "function" ? value(open) : value;
3825
+ if (!isControlled) {
3826
+ setInternalOpen(newValue);
3827
+ }
3828
+ onOpenChange?.(newValue);
3829
+ }, [isControlled, open, onOpenChange]);
3830
+ const [expanded, setExpanded] = (0, import_react4.useState)(false);
3831
+ const [messages, setMessages] = (0, import_react4.useState)([]);
3832
+ const [inputValue, setInputValue] = (0, import_react4.useState)("");
3833
+ const [conversationId, setConversationId] = (0, import_react4.useState)();
3834
+ const [errorMessage, setErrorMessage] = (0, import_react4.useState)(null);
3835
+ const [isFocused, setIsFocused] = (0, import_react4.useState)(false);
3836
+ const [isMobile, setIsMobile] = (0, import_react4.useState)(false);
3837
+ const messagesEndRef = (0, import_react4.useRef)(null);
3838
+ const messagesContainerRef = (0, import_react4.useRef)(null);
3839
+ const chatBoxRef = (0, import_react4.useRef)(null);
3840
+ const chatConfig = (0, import_react4.useMemo)(() => {
3841
+ if (!baseUrl) return void 0;
3842
+ return { baseUrl, accessToken, getAccessToken };
3843
+ }, [accessToken, baseUrl, getAccessToken]);
3844
+ const { sendMessage: sendMessage2, isStreaming, streamingText, completeResponse } = useSendMessageStream(chatConfig);
3845
+ const canSend = Boolean(mapId) && Boolean(baseUrl) && inputValue.trim().length > 0 && !isStreaming;
3846
+ (0, import_react4.useEffect)(() => {
3847
+ if (open && isMobile) {
3848
+ setExpanded(true);
3849
+ }
3850
+ }, [open, isMobile]);
3851
+ const scrollToBottom = (0, import_react4.useCallback)(() => {
3852
+ if (messagesEndRef.current) {
3853
+ messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
3854
+ }
3855
+ }, []);
3856
+ (0, import_react4.useEffect)(() => {
3857
+ if (open && messages.length === 0) {
3858
+ setMessages([
3859
+ {
3860
+ id: "welcome",
3861
+ role: "assistant",
3862
+ content: "Hola, soy el asistente de IA de ZENIT. Estoy aqu\xED para ayudarte a analizar tu mapa y responder tus preguntas sobre las capas y datos. \xBFEn qu\xE9 puedo ayudarte hoy?"
3863
+ }
3864
+ ]);
3865
+ }
3866
+ }, [open, messages.length]);
3867
+ (0, import_react4.useEffect)(() => {
3868
+ scrollToBottom();
3869
+ }, [messages, streamingText, scrollToBottom]);
3870
+ (0, import_react4.useEffect)(() => {
3871
+ if (!open) return;
3872
+ if (isMobile && expanded) return;
3873
+ const handleClickOutside = (event) => {
3874
+ if (chatBoxRef.current && !chatBoxRef.current.contains(event.target)) {
3875
+ setOpen(false);
3876
+ }
3877
+ };
3878
+ document.addEventListener("mousedown", handleClickOutside);
3879
+ return () => {
3880
+ document.removeEventListener("mousedown", handleClickOutside);
3881
+ };
3882
+ }, [open, isMobile, expanded]);
3883
+ (0, import_react4.useEffect)(() => {
3884
+ if (typeof window === "undefined") return;
3885
+ const mediaQuery = window.matchMedia("(max-width: 768px)");
3886
+ const updateMobile = () => setIsMobile(mediaQuery.matches);
3887
+ updateMobile();
3888
+ if (mediaQuery.addEventListener) {
3889
+ mediaQuery.addEventListener("change", updateMobile);
3890
+ } else {
3891
+ mediaQuery.addListener(updateMobile);
3892
+ }
3893
+ return () => {
3894
+ if (mediaQuery.removeEventListener) {
3895
+ mediaQuery.removeEventListener("change", updateMobile);
3896
+ } else {
3897
+ mediaQuery.removeListener(updateMobile);
3898
+ }
3899
+ };
3900
+ }, []);
3901
+ (0, import_react4.useEffect)(() => {
3902
+ if (typeof document === "undefined") return;
3903
+ if (!open || !isMobile) return;
3904
+ document.body.style.overflow = "hidden";
3905
+ return () => {
3906
+ document.body.style.overflow = "";
3907
+ };
3908
+ }, [open, isMobile]);
3909
+ const addMessage = (0, import_react4.useCallback)((message) => {
3910
+ setMessages((prev) => [...prev, message]);
3911
+ }, []);
3912
+ const handleSend = (0, import_react4.useCallback)(async () => {
3913
+ if (!mapId) {
3914
+ setErrorMessage("Selecciona un mapa para usar el asistente.");
3915
+ return;
3916
+ }
3917
+ if (!baseUrl) {
3918
+ setErrorMessage("Configura la baseUrl del SDK para usar Zenit AI.");
3919
+ return;
3920
+ }
3921
+ if (!inputValue.trim()) return;
3922
+ const messageText = inputValue.trim();
3923
+ setInputValue("");
3924
+ setErrorMessage(null);
3925
+ addMessage({
3926
+ id: `user-${Date.now()}`,
3927
+ role: "user",
3928
+ content: messageText
3929
+ });
3930
+ const request = {
3931
+ message: messageText,
3932
+ conversationId,
3933
+ filteredLayerIds,
3934
+ filters,
3935
+ userId
3936
+ };
3937
+ try {
3938
+ const response = await sendMessage2(mapId, request);
3939
+ setConversationId(response.conversationId ?? conversationId);
3940
+ addMessage({
3941
+ id: `assistant-${Date.now()}`,
3942
+ role: "assistant",
3943
+ content: response.answer,
3944
+ response
3945
+ });
3946
+ } catch (error) {
3947
+ setErrorMessage(error instanceof Error ? error.message : "Ocurri\xF3 un error inesperado.");
3948
+ addMessage({
3949
+ id: `error-${Date.now()}`,
3950
+ role: "assistant",
3951
+ content: `\u274C Error: ${error instanceof Error ? error.message : "Ocurri\xF3 un error inesperado."}`
3952
+ });
3953
+ }
3954
+ }, [
3955
+ addMessage,
3956
+ baseUrl,
3957
+ conversationId,
3958
+ filteredLayerIds,
3959
+ filters,
3960
+ inputValue,
3961
+ mapId,
3962
+ sendMessage2,
3963
+ userId
3964
+ ]);
3965
+ const handleKeyDown = (0, import_react4.useCallback)(
3966
+ (event) => {
3967
+ if (event.key === "Enter" && !event.shiftKey) {
3968
+ event.preventDefault();
3969
+ if (canSend) {
3970
+ void handleSend();
3971
+ }
3972
+ }
3973
+ },
3974
+ [canSend, handleSend]
3975
+ );
3976
+ const handleFollowUpClick = (0, import_react4.useCallback)((question) => {
3977
+ setInputValue(question);
3978
+ }, []);
3979
+ const renderMetadata = (response) => {
3980
+ if (!response?.metadata) return null;
3981
+ const referencedLayers = response.metadata.referencedLayers;
3982
+ if (!referencedLayers || referencedLayers.length === 0) return null;
3983
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.metadataSection, children: [
3984
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.metadataTitle, children: [
3985
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LayersIcon, {}),
3986
+ "Capas Analizadas"
3987
+ ] }),
3988
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { style: styles.metadataList, children: referencedLayers.map((layer, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("li", { style: styles.metadataItem, children: [
3989
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: layer.layerName }),
3990
+ " (",
3991
+ layer.featureCount,
3992
+ " ",
3993
+ layer.featureCount === 1 ? "elemento" : "elementos",
3994
+ ")"
3995
+ ] }, index)) })
3996
+ ] });
3997
+ };
3998
+ const handleActionClick = (0, import_react4.useCallback)((action) => {
3999
+ if (isStreaming) return;
4000
+ setOpen(false);
4001
+ requestAnimationFrame(() => {
4002
+ onActionClick?.(action);
4003
+ });
4004
+ }, [isStreaming, setOpen, onActionClick]);
4005
+ const renderActions = (response) => {
4006
+ if (!response?.suggestedActions?.length) return null;
4007
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.actionsSection, children: [
4008
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: styles.sectionLabel, children: "Acciones Sugeridas" }),
4009
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: styles.actionsGrid, children: response.suggestedActions.map((action, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4010
+ "button",
4011
+ {
4012
+ type: "button",
4013
+ style: {
4014
+ ...styles.actionButton,
4015
+ opacity: isStreaming ? 0.5 : 1,
4016
+ cursor: isStreaming ? "not-allowed" : "pointer"
4017
+ },
4018
+ onClick: () => handleActionClick(action),
4019
+ disabled: isStreaming,
4020
+ onMouseEnter: (e) => {
4021
+ if (!isStreaming) {
4022
+ e.currentTarget.style.background = "#a7f3d0";
4023
+ e.currentTarget.style.borderColor = "#a7f3d0";
4024
+ }
4025
+ },
4026
+ onMouseLeave: (e) => {
4027
+ if (!isStreaming) {
4028
+ e.currentTarget.style.background = "#d1fae5";
4029
+ e.currentTarget.style.borderColor = "#d1fae5";
4030
+ }
4031
+ },
4032
+ children: action.label ?? action.action ?? "Acci\xF3n"
4033
+ },
4034
+ `${action.label ?? action.action ?? "action"}-${index}`
4035
+ )) })
4036
+ ] });
4037
+ };
4038
+ const renderFollowUps = (response) => {
4039
+ if (!response?.followUpQuestions?.length) return null;
4040
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.actionsSection, children: [
4041
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: styles.sectionLabel, children: "Preguntas Relacionadas" }),
4042
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: response.followUpQuestions.map((question, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4043
+ "button",
4044
+ {
4045
+ type: "button",
4046
+ style: {
4047
+ ...styles.followUpButton,
4048
+ opacity: isStreaming ? 0.5 : 1,
4049
+ cursor: isStreaming ? "not-allowed" : "pointer"
4050
+ },
4051
+ onClick: () => !isStreaming && handleFollowUpClick(question),
4052
+ disabled: isStreaming,
4053
+ onMouseEnter: (e) => {
4054
+ if (!isStreaming) {
4055
+ e.currentTarget.style.background = "#f8fafc";
4056
+ e.currentTarget.style.borderColor = "#10b981";
4057
+ }
4058
+ },
4059
+ onMouseLeave: (e) => {
4060
+ if (!isStreaming) {
4061
+ e.currentTarget.style.background = "#fff";
4062
+ e.currentTarget.style.borderColor = "#cbd5e1";
4063
+ }
4064
+ },
4065
+ children: question
4066
+ },
4067
+ `followup-${index}`
4068
+ )) })
4069
+ ] });
4070
+ };
4071
+ const chatContent = /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.root, children: [
4072
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `
4073
+ @keyframes zenitBlink {
4074
+ 0%, 49% { opacity: 1; }
4075
+ 50%, 100% { opacity: 0; }
4076
+ }
4077
+ @keyframes zenitPulse {
4078
+ 0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7); }
4079
+ 70% { box-shadow: 0 0 0 14px rgba(16, 185, 129, 0); }
4080
+ 100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
4081
+ }
4082
+ @keyframes typingDotBounce {
4083
+ 0%, 60%, 100% { transform: translateY(0); }
4084
+ 30% { transform: translateY(-8px); }
4085
+ }
4086
+ .zenit-typing-dot:nth-child(1) { animation-delay: 0s; }
4087
+ .zenit-typing-dot:nth-child(2) { animation-delay: 0.2s; }
4088
+ .zenit-typing-dot:nth-child(3) { animation-delay: 0.4s; }
4089
+ .zenit-ai-button:not(.open) {
4090
+ animation: zenitPulse 2.5s infinite;
4091
+ }
4092
+ .zenit-ai-button:hover {
4093
+ transform: scale(1.05);
4094
+ }
4095
+ .zenit-ai-button:active {
4096
+ transform: scale(0.95);
4097
+ }
4098
+ .zenit-send-button:disabled {
4099
+ opacity: 0.5;
4100
+ cursor: not-allowed;
4101
+ }
4102
+ .zenit-send-button:not(:disabled):hover {
4103
+ opacity: 0.9;
4104
+ transform: translateY(-1px);
4105
+ }
4106
+ .zenit-send-button:not(:disabled):active {
4107
+ transform: translateY(0);
4108
+ }
4109
+ .zenit-chat-panel {
4110
+ box-sizing: border-box;
4111
+ }
4112
+ @media (max-width: 768px) {
4113
+ .zenit-chat-panel.zenit-chat-panel--fullscreen {
4114
+ position: fixed !important;
4115
+ inset: 0 !important;
4116
+ width: 100vw !important;
4117
+ max-width: 100vw !important;
4118
+ height: 100vh !important;
4119
+ height: 100dvh !important;
4120
+ border-radius: 0 !important;
4121
+ display: flex !important;
4122
+ flex-direction: column !important;
4123
+ overflow: hidden !important;
4124
+ z-index: 100000 !important;
4125
+ padding-top: env(safe-area-inset-top);
4126
+ }
4127
+ .zenit-chat-panel.zenit-chat-panel--fullscreen .zenit-ai-body {
4128
+ flex: 1;
4129
+ min-height: 0;
4130
+ overflow-y: auto;
4131
+ -webkit-overflow-scrolling: touch;
4132
+ }
4133
+ .zenit-chat-panel.zenit-chat-panel--fullscreen .zenit-ai-input-area {
4134
+ flex-shrink: 0;
4135
+ padding-bottom: max(14px, env(safe-area-inset-bottom));
4136
+ }
4137
+ .zenit-ai-button.zenit-ai-button--hidden-mobile {
4138
+ display: none !important;
4139
+ }
4140
+ }
4141
+ ` }),
4142
+ open && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4143
+ "div",
4144
+ {
4145
+ ref: chatBoxRef,
4146
+ className: `zenit-chat-panel${expanded ? " zenit-chat-panel--expanded" : ""}${isMobile ? " zenit-chat-panel--fullscreen" : ""}`,
4147
+ style: {
4148
+ ...styles.panel,
4149
+ ...expanded ? styles.panelExpanded : styles.panelNormal
4150
+ },
4151
+ children: [
4152
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("header", { style: styles.header, children: [
4153
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { style: styles.title, children: "Asistente Zenit AI" }),
4154
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.headerButtons, children: [
4155
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4156
+ "button",
4157
+ {
4158
+ type: "button",
4159
+ style: styles.headerButton,
4160
+ onClick: () => setExpanded(!expanded),
4161
+ onMouseEnter: (e) => {
4162
+ e.currentTarget.style.background = "rgba(255, 255, 255, 0.25)";
4163
+ },
4164
+ onMouseLeave: (e) => {
4165
+ e.currentTarget.style.background = "rgba(255, 255, 255, 0.15)";
4166
+ },
4167
+ "aria-label": expanded ? "Contraer" : "Expandir",
4168
+ children: expanded ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CollapseIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ExpandIcon, {})
4169
+ }
4170
+ ),
4171
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4172
+ "button",
4173
+ {
4174
+ type: "button",
4175
+ style: styles.headerButton,
4176
+ onClick: () => setOpen(false),
4177
+ onMouseEnter: (e) => {
4178
+ e.currentTarget.style.background = "rgba(255, 255, 255, 0.25)";
4179
+ },
4180
+ onMouseLeave: (e) => {
4181
+ e.currentTarget.style.background = "rgba(255, 255, 255, 0.15)";
4182
+ },
4183
+ "aria-label": "Cerrar",
4184
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseIcon, {})
4185
+ }
4186
+ )
4187
+ ] })
4188
+ ] }),
4189
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { ref: messagesContainerRef, className: "zenit-ai-body", style: styles.messages, children: [
4190
+ messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4191
+ "div",
4192
+ {
4193
+ style: {
4194
+ ...styles.messageWrapper,
4195
+ alignItems: message.role === "user" ? "flex-end" : "flex-start"
4196
+ },
4197
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
4198
+ "div",
4199
+ {
4200
+ style: {
4201
+ ...styles.messageBubble,
4202
+ ...message.role === "user" ? styles.userMessage : styles.assistantMessage
4203
+ },
4204
+ children: [
4205
+ message.role === "assistant" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MarkdownRenderer, { content: message.content }) : message.content,
4206
+ message.role === "assistant" && renderMetadata(message.response),
4207
+ message.role === "assistant" && renderActions(message.response),
4208
+ message.role === "assistant" && renderFollowUps(message.response)
4209
+ ]
4210
+ }
4211
+ )
4212
+ },
4213
+ message.id
4214
+ )),
4215
+ isStreaming && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4216
+ "div",
4217
+ {
4218
+ style: {
4219
+ ...styles.messageWrapper,
4220
+ alignItems: "flex-start"
4221
+ },
4222
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4223
+ "div",
4224
+ {
4225
+ style: {
4226
+ ...styles.messageBubble,
4227
+ ...styles.assistantMessage
4228
+ },
4229
+ children: streamingText ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
4230
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(MarkdownRenderer, { content: streamingText }),
4231
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: styles.cursor })
4232
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.thinkingText, children: [
4233
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Analizando" }),
4234
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.typingIndicator, children: [
4235
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zenit-typing-dot", style: styles.typingDot }),
4236
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zenit-typing-dot", style: styles.typingDot }),
4237
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "zenit-typing-dot", style: styles.typingDot })
4238
+ ] })
4239
+ ] })
4240
+ }
4241
+ )
4242
+ }
4243
+ ),
4244
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { ref: messagesEndRef })
4245
+ ] }),
4246
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "zenit-ai-input-area", style: styles.inputWrapper, children: [
4247
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4248
+ "textarea",
4249
+ {
4250
+ style: {
4251
+ ...styles.textarea,
4252
+ ...isFocused ? styles.textareaFocus : {}
4253
+ },
4254
+ rows: 2,
4255
+ value: inputValue,
4256
+ onChange: (event) => setInputValue(event.target.value),
4257
+ onKeyDown: handleKeyDown,
4258
+ onFocus: () => setIsFocused(true),
4259
+ onBlur: () => setIsFocused(false),
4260
+ placeholder: !mapId ? "Selecciona un mapa para comenzar" : isStreaming ? "Esperando respuesta..." : "Escribe tu pregunta...",
4261
+ disabled: !mapId || !baseUrl || isStreaming
4262
+ }
4263
+ ),
4264
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4265
+ "button",
4266
+ {
4267
+ type: "button",
4268
+ className: "zenit-send-button",
4269
+ style: styles.sendButton,
4270
+ onClick: () => void handleSend(),
4271
+ disabled: !canSend,
4272
+ "aria-label": "Enviar mensaje",
4273
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(SendIcon, {})
4274
+ }
4275
+ )
4276
+ ] }),
4277
+ errorMessage && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: styles.errorText, children: errorMessage }),
4278
+ !mapId && !errorMessage && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: styles.statusNote, children: "Selecciona un mapa para usar el asistente" }),
4279
+ !baseUrl && !errorMessage && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: styles.statusNote, children: "Configura la baseUrl del SDK" })
4280
+ ]
4281
+ }
4282
+ ),
4283
+ !(hideButton && !open) && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
4284
+ "button",
4285
+ {
4286
+ type: "button",
4287
+ className: `zenit-ai-button ${open ? "open" : ""}${open && isMobile ? " zenit-ai-button--hidden-mobile" : ""}`,
4288
+ style: {
4289
+ ...styles.floatingButton,
4290
+ ...open ? styles.floatingButtonOpen : isMobile ? styles.floatingButtonMobile : styles.floatingButtonClosed
4291
+ },
4292
+ onClick: () => setOpen((prev) => !prev),
4293
+ "aria-label": open ? "Cerrar asistente" : "Abrir asistente Zenit AI",
4294
+ children: open ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CloseIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
4295
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ChatIcon, {}),
4296
+ !isMobile && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: "Asistente IA" })
4297
+ ] })
4298
+ }
4299
+ )
4300
+ ] });
4301
+ if (typeof document !== "undefined") {
4302
+ return (0, import_react_dom.createPortal)(chatContent, document.body);
4303
+ }
4304
+ return chatContent;
4305
+ };
1370
4306
  // Annotate the CommonJS export names for ESM import in node:
1371
4307
  0 && (module.exports = {
4308
+ ChevronLeft,
4309
+ ChevronRight,
1372
4310
  DEFAULT_MAX_FEATURES_FULL_GEOJSON,
4311
+ Eye,
4312
+ EyeOff,
1373
4313
  FilterEngine,
4314
+ FloatingChatBox,
1374
4315
  HttpClient,
4316
+ Layers,
4317
+ Upload,
4318
+ X,
1375
4319
  ZenitAuthClient,
1376
4320
  ZenitCatalogNotSupportedError,
1377
4321
  ZenitClient,
4322
+ ZenitFeatureFilterPanel,
4323
+ ZenitLayerManager,
1378
4324
  ZenitLayersClient,
4325
+ ZenitMap,
1379
4326
  ZenitMapsClient,
1380
4327
  ZenitSdkAuthClient,
4328
+ ZoomIn,
1381
4329
  applyFilteredGeoJSONStrategy,
1382
4330
  applyLayerOverrides,
1383
4331
  applyPrefiltersToFeatureCollection,
@@ -1386,21 +4334,33 @@ function normalizeBbox(input) {
1386
4334
  buildLayerFilters,
1387
4335
  buildLimitedMessage,
1388
4336
  centroidFromBBox,
4337
+ clampNumber,
4338
+ clampOpacity,
1389
4339
  computeBBoxFromFeature,
1390
4340
  createChatService,
1391
4341
  decorateFeaturesWithLayerId,
1392
4342
  extractMapDto,
1393
4343
  filterEngine,
4344
+ getAccentByLayerId,
1394
4345
  getCatalogSupport,
4346
+ getEffectiveLayerOpacity,
4347
+ getLayerColor,
4348
+ getLayerZoomOpacityFactor,
4349
+ getStyleByLayerId,
4350
+ getZoomOpacityFactor,
1395
4351
  initLayerStates,
4352
+ isPolygonLayer,
1396
4353
  normalizeBbox,
1397
4354
  normalizeMapCenter,
1398
4355
  normalizeMapLayers,
1399
4356
  resetOverrides,
4357
+ resolveLayerAccent,
1400
4358
  resolveLayerStrategy,
1401
4359
  sendMessage,
1402
4360
  sendMessageStream,
1403
4361
  shouldSkipGeojsonDownload,
1404
- shouldUseFilterMultiple
4362
+ shouldUseFilterMultiple,
4363
+ useSendMessage,
4364
+ useSendMessageStream
1405
4365
  });
1406
4366
  //# sourceMappingURL=index.js.map