zenit-sdk 0.1.1 → 0.1.2

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.
@@ -619,6 +619,8 @@ interface ZenitMapRef {
619
619
  padding?: [number, number];
620
620
  animate?: boolean;
621
621
  }) => void;
622
+ fitToBbox: (bbox: Bbox, padding?: number) => void;
623
+ fitToGeoJson: (fc: GeoJsonFeatureCollection, padding?: number) => void;
622
624
  setView: (coordinates: {
623
625
  lat: number;
624
626
  lon: number;
@@ -626,6 +628,8 @@ interface ZenitMapRef {
626
628
  getLayerSnapshot: () => LayerSnapshot[];
627
629
  restoreLayerSnapshot: (snapshot: LayerSnapshot[]) => void;
628
630
  highlightFeature: (layerId: number | string, featureId?: number | string) => void;
631
+ updateLayerGeoJson: (layerId: number | string, featureCollection: GeoJsonFeatureCollection) => void;
632
+ restoreLayerGeoJson: (layerId: number | string) => void;
629
633
  getMapInstance: () => L.Map | null;
630
634
  }
631
635
  interface ZenitMapProps {
@@ -698,6 +702,15 @@ interface ZenitLayerManagerProps {
698
702
  displayOrder?: number | null;
699
703
  }>;
700
704
  onZoomToLayer?: (layerId: number | string, bboxGeometry?: GeoJsonPolygon | null) => void;
705
+ onApplyLayerFilter?: (params: {
706
+ layerId: number | string;
707
+ field: string;
708
+ value: string;
709
+ }) => Promise<void> | void;
710
+ onClearLayerFilter?: (params: {
711
+ layerId: number | string;
712
+ field?: string;
713
+ }) => Promise<void> | void;
701
714
  }
702
715
  declare const ZenitLayerManager: React.FC<ZenitLayerManagerProps>;
703
716
 
@@ -619,6 +619,8 @@ interface ZenitMapRef {
619
619
  padding?: [number, number];
620
620
  animate?: boolean;
621
621
  }) => void;
622
+ fitToBbox: (bbox: Bbox, padding?: number) => void;
623
+ fitToGeoJson: (fc: GeoJsonFeatureCollection, padding?: number) => void;
622
624
  setView: (coordinates: {
623
625
  lat: number;
624
626
  lon: number;
@@ -626,6 +628,8 @@ interface ZenitMapRef {
626
628
  getLayerSnapshot: () => LayerSnapshot[];
627
629
  restoreLayerSnapshot: (snapshot: LayerSnapshot[]) => void;
628
630
  highlightFeature: (layerId: number | string, featureId?: number | string) => void;
631
+ updateLayerGeoJson: (layerId: number | string, featureCollection: GeoJsonFeatureCollection) => void;
632
+ restoreLayerGeoJson: (layerId: number | string) => void;
629
633
  getMapInstance: () => L.Map | null;
630
634
  }
631
635
  interface ZenitMapProps {
@@ -698,6 +702,15 @@ interface ZenitLayerManagerProps {
698
702
  displayOrder?: number | null;
699
703
  }>;
700
704
  onZoomToLayer?: (layerId: number | string, bboxGeometry?: GeoJsonPolygon | null) => void;
705
+ onApplyLayerFilter?: (params: {
706
+ layerId: number | string;
707
+ field: string;
708
+ value: string;
709
+ }) => Promise<void> | void;
710
+ onClearLayerFilter?: (params: {
711
+ layerId: number | string;
712
+ field?: string;
713
+ }) => Promise<void> | void;
701
714
  }
702
715
  declare const ZenitLayerManager: React.FC<ZenitLayerManagerProps>;
703
716
 
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { M as MapDto, N as NormalizedMapLayer, G as GeoJsonFeature, B as Bbox, a as GeoJsonFeatureCollection, F as FilterMultipleMetadata, L as LayerFilters, b as GeoJsonRequestOptions, c as GeoJsonPolygon } from './index-DvcYGhqj.mjs';
2
- export { O as ApiEnvelope, P as ApiResponseData, ai as ChatRequestDto, ad as ChatRequestOptions, ak as ChatResponseDto, ac as ChatServiceConfig, ae as ChatStreamCallbacks, D as DEFAULT_MAX_FEATURES_FULL_GEOJSON, a8 as EffectiveLayerState, a4 as FilterMultipleLayerMeta, a5 as FilterMultipleWithFallbackResult, a3 as FilterValue, ar as FloatingChatBox, as as FloatingChatBoxProps, T as GeoJsonBBoxRequest, J as GeoJsonGeometryCollection, W as GeoJsonIntersectRequest, I as GeoJsonPoint, o as HttpClient, H as HttpClientOptions, X as LayerAoi, a6 as LayerBaseState, K as LayerDataStrategy, C as LayerDto, a1 as LayerFeatureCatalogItem, a2 as LayerFeaturesCatalogDto, Q as LayerMetadata, a7 as LayerOverrideState, an as LayerSnapshot, av as LayerStyle, E as LayerSummary, $ as LoadLayerDataParams, a0 as LoadLayerDataResult, f as LoginRequest, g as LoginResponse, s as MapLayerConfig, q as MapSettings, h as MeResponse, R as RefreshResponse, Y as ResolveLayerStrategyParams, _ as ResolveLayerStrategyResult, j as SdkTokenExchangeResponse, S as SdkTokenValidateResponse, aj as SuggestedAction, U as UserSummary, V as ValidateResponse, i as ZenitAuthClient, d as ZenitClient, aq as ZenitFeatureFilterPanel, ap as ZenitLayerManager, A as ZenitLayersClient, al as ZenitMap, ao as ZenitMapProps, am as ZenitMapRef, p as ZenitMapsClient, e as ZenitRuntimeConfig, k as ZenitSdkAuthClient, Z as ZenitSdkConfig, l as ZenitSdkError, aA as ZoomOpacityOptions, aa as applyLayerOverrides, x as buildAoiFromFeatureCollection, u as buildFilterMultipleParams, t as buildLayerFilters, z as buildLimitedMessage, aB as clampNumber, aC as clampOpacity, ah as createChatService, az as getAccentByLayerId, aG as getEffectiveLayerOpacity, ax as getLayerColor, aF as getLayerZoomOpacityFactor, ay as getStyleByLayerId, aE as getZoomOpacityFactor, a9 as initLayerStates, aD as isPolygonLayer, m as mergeFilters, n as normalizeFilters, ab as resetOverrides, aw as resolveLayerAccent, y as resolveLayerStrategy, r as resolveRuntimeConfig, af as sendMessage, ag as sendMessageStream, w as shouldSkipGeojsonDownload, v as shouldUseFilterMultiple, at as useSendMessage, au as useSendMessageStream } from './index-DvcYGhqj.mjs';
1
+ import { M as MapDto, N as NormalizedMapLayer, G as GeoJsonFeature, B as Bbox, a as GeoJsonFeatureCollection, F as FilterMultipleMetadata, L as LayerFilters, b as GeoJsonRequestOptions, c as GeoJsonPolygon } from './index-BSljZaYk.mjs';
2
+ export { O as ApiEnvelope, P as ApiResponseData, ai as ChatRequestDto, ad as ChatRequestOptions, ak as ChatResponseDto, ac as ChatServiceConfig, ae as ChatStreamCallbacks, D as DEFAULT_MAX_FEATURES_FULL_GEOJSON, a8 as EffectiveLayerState, a4 as FilterMultipleLayerMeta, a5 as FilterMultipleWithFallbackResult, a3 as FilterValue, ar as FloatingChatBox, as as FloatingChatBoxProps, T as GeoJsonBBoxRequest, J as GeoJsonGeometryCollection, W as GeoJsonIntersectRequest, I as GeoJsonPoint, o as HttpClient, H as HttpClientOptions, X as LayerAoi, a6 as LayerBaseState, K as LayerDataStrategy, C as LayerDto, a1 as LayerFeatureCatalogItem, a2 as LayerFeaturesCatalogDto, Q as LayerMetadata, a7 as LayerOverrideState, an as LayerSnapshot, av as LayerStyle, E as LayerSummary, $ as LoadLayerDataParams, a0 as LoadLayerDataResult, f as LoginRequest, g as LoginResponse, s as MapLayerConfig, q as MapSettings, h as MeResponse, R as RefreshResponse, Y as ResolveLayerStrategyParams, _ as ResolveLayerStrategyResult, j as SdkTokenExchangeResponse, S as SdkTokenValidateResponse, aj as SuggestedAction, U as UserSummary, V as ValidateResponse, i as ZenitAuthClient, d as ZenitClient, aq as ZenitFeatureFilterPanel, ap as ZenitLayerManager, A as ZenitLayersClient, al as ZenitMap, ao as ZenitMapProps, am as ZenitMapRef, p as ZenitMapsClient, e as ZenitRuntimeConfig, k as ZenitSdkAuthClient, Z as ZenitSdkConfig, l as ZenitSdkError, aA as ZoomOpacityOptions, aa as applyLayerOverrides, x as buildAoiFromFeatureCollection, u as buildFilterMultipleParams, t as buildLayerFilters, z as buildLimitedMessage, aB as clampNumber, aC as clampOpacity, ah as createChatService, az as getAccentByLayerId, aG as getEffectiveLayerOpacity, ax as getLayerColor, aF as getLayerZoomOpacityFactor, ay as getStyleByLayerId, aE as getZoomOpacityFactor, a9 as initLayerStates, aD as isPolygonLayer, m as mergeFilters, n as normalizeFilters, ab as resetOverrides, aw as resolveLayerAccent, y as resolveLayerStrategy, r as resolveRuntimeConfig, af as sendMessage, ag as sendMessageStream, w as shouldSkipGeojsonDownload, v as shouldUseFilterMultiple, at as useSendMessage, au as useSendMessageStream } from './index-BSljZaYk.mjs';
3
3
  export { ChevronLeft, ChevronRight, Eye, EyeOff, Layers, Upload, X, ZoomIn } from 'lucide-react';
4
4
  import 'react';
5
5
  import 'leaflet';
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { M as MapDto, N as NormalizedMapLayer, G as GeoJsonFeature, B as Bbox, a as GeoJsonFeatureCollection, F as FilterMultipleMetadata, L as LayerFilters, b as GeoJsonRequestOptions, c as GeoJsonPolygon } from './index-DvcYGhqj.js';
2
- export { O as ApiEnvelope, P as ApiResponseData, ai as ChatRequestDto, ad as ChatRequestOptions, ak as ChatResponseDto, ac as ChatServiceConfig, ae as ChatStreamCallbacks, D as DEFAULT_MAX_FEATURES_FULL_GEOJSON, a8 as EffectiveLayerState, a4 as FilterMultipleLayerMeta, a5 as FilterMultipleWithFallbackResult, a3 as FilterValue, ar as FloatingChatBox, as as FloatingChatBoxProps, T as GeoJsonBBoxRequest, J as GeoJsonGeometryCollection, W as GeoJsonIntersectRequest, I as GeoJsonPoint, o as HttpClient, H as HttpClientOptions, X as LayerAoi, a6 as LayerBaseState, K as LayerDataStrategy, C as LayerDto, a1 as LayerFeatureCatalogItem, a2 as LayerFeaturesCatalogDto, Q as LayerMetadata, a7 as LayerOverrideState, an as LayerSnapshot, av as LayerStyle, E as LayerSummary, $ as LoadLayerDataParams, a0 as LoadLayerDataResult, f as LoginRequest, g as LoginResponse, s as MapLayerConfig, q as MapSettings, h as MeResponse, R as RefreshResponse, Y as ResolveLayerStrategyParams, _ as ResolveLayerStrategyResult, j as SdkTokenExchangeResponse, S as SdkTokenValidateResponse, aj as SuggestedAction, U as UserSummary, V as ValidateResponse, i as ZenitAuthClient, d as ZenitClient, aq as ZenitFeatureFilterPanel, ap as ZenitLayerManager, A as ZenitLayersClient, al as ZenitMap, ao as ZenitMapProps, am as ZenitMapRef, p as ZenitMapsClient, e as ZenitRuntimeConfig, k as ZenitSdkAuthClient, Z as ZenitSdkConfig, l as ZenitSdkError, aA as ZoomOpacityOptions, aa as applyLayerOverrides, x as buildAoiFromFeatureCollection, u as buildFilterMultipleParams, t as buildLayerFilters, z as buildLimitedMessage, aB as clampNumber, aC as clampOpacity, ah as createChatService, az as getAccentByLayerId, aG as getEffectiveLayerOpacity, ax as getLayerColor, aF as getLayerZoomOpacityFactor, ay as getStyleByLayerId, aE as getZoomOpacityFactor, a9 as initLayerStates, aD as isPolygonLayer, m as mergeFilters, n as normalizeFilters, ab as resetOverrides, aw as resolveLayerAccent, y as resolveLayerStrategy, r as resolveRuntimeConfig, af as sendMessage, ag as sendMessageStream, w as shouldSkipGeojsonDownload, v as shouldUseFilterMultiple, at as useSendMessage, au as useSendMessageStream } from './index-DvcYGhqj.js';
1
+ import { M as MapDto, N as NormalizedMapLayer, G as GeoJsonFeature, B as Bbox, a as GeoJsonFeatureCollection, F as FilterMultipleMetadata, L as LayerFilters, b as GeoJsonRequestOptions, c as GeoJsonPolygon } from './index-BSljZaYk.js';
2
+ export { O as ApiEnvelope, P as ApiResponseData, ai as ChatRequestDto, ad as ChatRequestOptions, ak as ChatResponseDto, ac as ChatServiceConfig, ae as ChatStreamCallbacks, D as DEFAULT_MAX_FEATURES_FULL_GEOJSON, a8 as EffectiveLayerState, a4 as FilterMultipleLayerMeta, a5 as FilterMultipleWithFallbackResult, a3 as FilterValue, ar as FloatingChatBox, as as FloatingChatBoxProps, T as GeoJsonBBoxRequest, J as GeoJsonGeometryCollection, W as GeoJsonIntersectRequest, I as GeoJsonPoint, o as HttpClient, H as HttpClientOptions, X as LayerAoi, a6 as LayerBaseState, K as LayerDataStrategy, C as LayerDto, a1 as LayerFeatureCatalogItem, a2 as LayerFeaturesCatalogDto, Q as LayerMetadata, a7 as LayerOverrideState, an as LayerSnapshot, av as LayerStyle, E as LayerSummary, $ as LoadLayerDataParams, a0 as LoadLayerDataResult, f as LoginRequest, g as LoginResponse, s as MapLayerConfig, q as MapSettings, h as MeResponse, R as RefreshResponse, Y as ResolveLayerStrategyParams, _ as ResolveLayerStrategyResult, j as SdkTokenExchangeResponse, S as SdkTokenValidateResponse, aj as SuggestedAction, U as UserSummary, V as ValidateResponse, i as ZenitAuthClient, d as ZenitClient, aq as ZenitFeatureFilterPanel, ap as ZenitLayerManager, A as ZenitLayersClient, al as ZenitMap, ao as ZenitMapProps, am as ZenitMapRef, p as ZenitMapsClient, e as ZenitRuntimeConfig, k as ZenitSdkAuthClient, Z as ZenitSdkConfig, l as ZenitSdkError, aA as ZoomOpacityOptions, aa as applyLayerOverrides, x as buildAoiFromFeatureCollection, u as buildFilterMultipleParams, t as buildLayerFilters, z as buildLimitedMessage, aB as clampNumber, aC as clampOpacity, ah as createChatService, az as getAccentByLayerId, aG as getEffectiveLayerOpacity, ax as getLayerColor, aF as getLayerZoomOpacityFactor, ay as getStyleByLayerId, aE as getZoomOpacityFactor, a9 as initLayerStates, aD as isPolygonLayer, m as mergeFilters, n as normalizeFilters, ab as resetOverrides, aw as resolveLayerAccent, y as resolveLayerStrategy, r as resolveRuntimeConfig, af as sendMessage, ag as sendMessageStream, w as shouldSkipGeojsonDownload, v as shouldUseFilterMultiple, at as useSendMessage, au as useSendMessageStream } from './index-BSljZaYk.js';
3
3
  export { ChevronLeft, ChevronRight, Eye, EyeOff, Layers, Upload, X, ZoomIn } from 'lucide-react';
4
4
  import 'react';
5
5
  import 'leaflet';
package/dist/index.js CHANGED
@@ -371,10 +371,6 @@ function parseQueryParams(options = {}) {
371
371
  return params;
372
372
  }
373
373
  function serializeFilterValue(value) {
374
- if (Array.isArray(value)) {
375
- const serialized2 = value.map(String).filter((item) => item.trim().length > 0).join(",");
376
- return serialized2.length > 0 ? serialized2 : void 0;
377
- }
378
374
  if (value === void 0 || value === null) {
379
375
  return void 0;
380
376
  }
@@ -387,6 +383,15 @@ function buildLayerFilters(filters) {
387
383
  return query;
388
384
  }
389
385
  Object.entries(filters).forEach(([key, value]) => {
386
+ if (Array.isArray(value)) {
387
+ value.forEach((item) => {
388
+ const serialized2 = serializeFilterValue(item);
389
+ if (serialized2 !== void 0) {
390
+ query.append(key, serialized2);
391
+ }
392
+ });
393
+ return;
394
+ }
390
395
  const serialized = serializeFilterValue(value);
391
396
  if (serialized !== void 0) {
392
397
  query.set(key, serialized);
@@ -1590,6 +1595,26 @@ var import_react_leaflet = require("react-leaflet");
1590
1595
  var import_leaflet = __toESM(require("leaflet"));
1591
1596
  var import_jsx_runtime = require("react/jsx-runtime");
1592
1597
  var POINT_GEOMETRY_TYPES = /* @__PURE__ */ new Set(["Point", "MultiPoint"]);
1598
+ function normalizeBboxFromData(data) {
1599
+ const bboxCandidate = data.bbox;
1600
+ if (!Array.isArray(bboxCandidate) || bboxCandidate.length < 4) return null;
1601
+ const [minLon, minLat, maxLon, maxLat] = bboxCandidate;
1602
+ const values = [minLon, minLat, maxLon, maxLat].map((value) => Number(value));
1603
+ if (values.every((value) => Number.isFinite(value))) {
1604
+ return values;
1605
+ }
1606
+ return null;
1607
+ }
1608
+ function buildIdsSample(features) {
1609
+ return features.slice(0, 10).map((feature) => {
1610
+ const typedFeature = feature;
1611
+ if (typedFeature.id !== void 0 && typedFeature.id !== null) {
1612
+ return String(typedFeature.id);
1613
+ }
1614
+ const featureId = typedFeature.properties?.featureId;
1615
+ return featureId !== void 0 && featureId !== null ? String(featureId) : "";
1616
+ }).join(",");
1617
+ }
1593
1618
  function getGeometryType(feature) {
1594
1619
  const t = feature?.geometry?.type;
1595
1620
  return typeof t === "string" ? t : null;
@@ -1625,6 +1650,18 @@ var LayerGeoJson = ({
1625
1650
  const features = data.features ?? [];
1626
1651
  const fillFeatures = features.filter(isNonPointGeometry);
1627
1652
  const pointFeatures = features.filter(isPointGeometry);
1653
+ const dataVersionRef = (0, import_react.useRef)(0);
1654
+ const prevSignatureRef = (0, import_react.useRef)("");
1655
+ const firstId = features.length > 0 ? String(features[0]?.id ?? "") : "";
1656
+ const lastId = features.length > 0 ? String(features[features.length - 1]?.id ?? "") : "";
1657
+ const bbox = normalizeBboxFromData(data);
1658
+ const idsSample = buildIdsSample(features);
1659
+ const signature = bbox ? `${layerId}|${features.length}|${firstId}|${lastId}|${idsSample}|${bbox[0]}|${bbox[1]}|${bbox[2]}|${bbox[3]}` : `${layerId}|${features.length}|${firstId}|${lastId}|${idsSample}`;
1660
+ const signatureToken = signature.replace(/[^a-zA-Z0-9_-]/g, "_");
1661
+ if (prevSignatureRef.current !== signature) {
1662
+ dataVersionRef.current += 1;
1663
+ prevSignatureRef.current = signature;
1664
+ }
1628
1665
  const fillData = fillFeatures.length > 0 ? buildFeatureCollection(fillFeatures) : null;
1629
1666
  const pointsData = pointFeatures.length > 0 ? buildFeatureCollection(pointFeatures) : null;
1630
1667
  const clusterLayerRef = (0, import_react.useRef)(null);
@@ -1677,7 +1714,7 @@ var LayerGeoJson = ({
1677
1714
  onPolygonLabel?.(feature, layer);
1678
1715
  }
1679
1716
  },
1680
- `fill-${layerId}`
1717
+ `fill-${layerId}-${signatureToken}-v${dataVersionRef.current}`
1681
1718
  ),
1682
1719
  pointsData && !canCluster && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1683
1720
  import_react_leaflet.GeoJSON,
@@ -1690,7 +1727,7 @@ var LayerGeoJson = ({
1690
1727
  }),
1691
1728
  onEachFeature
1692
1729
  },
1693
- `points-${layerId}`
1730
+ `points-${layerId}-${signatureToken}-v${dataVersionRef.current}`
1694
1731
  )
1695
1732
  ] });
1696
1733
  };
@@ -2250,6 +2287,18 @@ var LocationControl = ({ position = "bottomleft", zoom = 16 }) => {
2250
2287
  // src/react/map/map-handlers.tsx
2251
2288
  var import_react4 = require("react");
2252
2289
  var import_react_leaflet3 = require("react-leaflet");
2290
+ var MapInvalidator = ({ trigger }) => {
2291
+ const map = (0, import_react_leaflet3.useMap)();
2292
+ const lastTrigger = (0, import_react4.useRef)(void 0);
2293
+ (0, import_react4.useEffect)(() => {
2294
+ if (lastTrigger.current === trigger) return;
2295
+ lastTrigger.current = trigger;
2296
+ requestAnimationFrame(() => {
2297
+ map.invalidateSize();
2298
+ });
2299
+ }, [map, trigger]);
2300
+ return null;
2301
+ };
2253
2302
  function computeBBoxFromGeojson(geojson) {
2254
2303
  if (!geojson || !Array.isArray(geojson.features)) return null;
2255
2304
  const coords = [];
@@ -2478,6 +2527,8 @@ var ZenitMap = (0, import_react5.forwardRef)(({
2478
2527
  const [loadingMap, setLoadingMap] = (0, import_react5.useState)(false);
2479
2528
  const [mapError, setMapError] = (0, import_react5.useState)(null);
2480
2529
  const [mapInstance, setMapInstance] = (0, import_react5.useState)(null);
2530
+ const [layerGeojsonOverrides, setLayerGeojsonOverrides] = (0, import_react5.useState)({});
2531
+ const originalGeojsonByLayerIdRef = (0, import_react5.useRef)({});
2481
2532
  const [panesReady, setPanesReady] = (0, import_react5.useState)(false);
2482
2533
  const [currentZoom, setCurrentZoom] = (0, import_react5.useState)(initialZoom ?? DEFAULT_ZOOM);
2483
2534
  const [isPopupOpen, setIsPopupOpen] = (0, import_react5.useState)(false);
@@ -2763,13 +2814,27 @@ var ZenitMap = (0, import_react5.forwardRef)(({
2763
2814
  (0, import_react5.useEffect)(() => {
2764
2815
  setCurrentZoom(zoom);
2765
2816
  }, [zoom]);
2817
+ (0, import_react5.useEffect)(() => {
2818
+ if (!layerGeojson) return;
2819
+ layers.forEach((layer) => {
2820
+ const layerKey = String(layer.mapLayer.layerId);
2821
+ const incoming = layerGeojson[layer.mapLayer.layerId] ?? layerGeojson[layerKey] ?? null;
2822
+ if (incoming && !originalGeojsonByLayerIdRef.current[layerKey]) {
2823
+ originalGeojsonByLayerIdRef.current[layerKey] = incoming;
2824
+ }
2825
+ });
2826
+ }, [layerGeojson, layers]);
2766
2827
  const decoratedLayers = (0, import_react5.useMemo)(() => {
2767
- return layers.map((layer) => ({
2768
- ...layer,
2769
- effective: effectiveStates.find((state) => state.layerId === layer.mapLayer.layerId),
2770
- data: layerGeojson?.[layer.mapLayer.layerId] ?? layerGeojson?.[String(layer.mapLayer.layerId)] ?? null
2771
- }));
2772
- }, [effectiveStates, layerGeojson, layers]);
2828
+ return layers.map((layer) => {
2829
+ const layerKey = String(layer.mapLayer.layerId);
2830
+ const override = layerGeojsonOverrides[layerKey];
2831
+ return {
2832
+ ...layer,
2833
+ effective: effectiveStates.find((state) => state.layerId === layer.mapLayer.layerId),
2834
+ data: override ?? layerGeojson?.[layer.mapLayer.layerId] ?? layerGeojson?.[layerKey] ?? null
2835
+ };
2836
+ });
2837
+ }, [effectiveStates, layerGeojson, layerGeojsonOverrides, layers]);
2773
2838
  const orderedLayers = (0, import_react5.useMemo)(() => {
2774
2839
  return [...decoratedLayers].filter((layer) => layer.effective?.visible && layer.data).sort((a, b) => a.displayOrder - b.displayOrder);
2775
2840
  }, [decoratedLayers]);
@@ -2998,6 +3063,24 @@ var ZenitMap = (0, import_react5.forwardRef)(({
2998
3063
  };
2999
3064
  mapInstance.fitBounds(bounds, fitOptions);
3000
3065
  },
3066
+ fitToBbox: (bbox, padding) => {
3067
+ if (!mapInstance) return;
3068
+ 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)) {
3069
+ console.warn("[ZenitMap.fitToBbox] Invalid bbox", bbox);
3070
+ return;
3071
+ }
3072
+ const resolvedPadding = padding ?? (isMobile ? 40 : 24);
3073
+ const bounds = import_leaflet4.default.latLngBounds([bbox.minLat, bbox.minLon], [bbox.maxLat, bbox.maxLon]);
3074
+ mapInstance.fitBounds(bounds, { padding: [resolvedPadding, resolvedPadding], animate: true });
3075
+ },
3076
+ fitToGeoJson: (fc, padding) => {
3077
+ if (!mapInstance) return;
3078
+ if (!fc?.features?.length) return;
3079
+ const bounds = import_leaflet4.default.geoJSON(fc).getBounds();
3080
+ if (!bounds.isValid()) return;
3081
+ const resolvedPadding = padding ?? (isMobile ? 40 : 24);
3082
+ mapInstance.fitBounds(bounds, { padding: [resolvedPadding, resolvedPadding], animate: true });
3083
+ },
3001
3084
  setView: (coordinates, zoom2) => {
3002
3085
  if (!mapInstance) return;
3003
3086
  mapInstance.setView([coordinates.lat, coordinates.lon], zoom2 ?? mapInstance.getZoom(), {
@@ -3022,8 +3105,25 @@ var ZenitMap = (0, import_react5.forwardRef)(({
3022
3105
  highlightFeature: (layerId, featureId) => {
3023
3106
  upsertUiOverride(layerId, { overrideVisible: true, overrideOpacity: 1 });
3024
3107
  },
3108
+ updateLayerGeoJson: (layerId, featureCollection) => {
3109
+ const layerKey = String(layerId);
3110
+ setLayerGeojsonOverrides((prev) => ({ ...prev, [layerKey]: featureCollection }));
3111
+ },
3112
+ restoreLayerGeoJson: (layerId) => {
3113
+ const layerKey = String(layerId);
3114
+ const original = originalGeojsonByLayerIdRef.current[layerKey];
3115
+ setLayerGeojsonOverrides((prev) => {
3116
+ const next = { ...prev };
3117
+ if (original) {
3118
+ next[layerKey] = original;
3119
+ } else {
3120
+ delete next[layerKey];
3121
+ }
3122
+ return next;
3123
+ });
3124
+ },
3025
3125
  getMapInstance: () => mapInstance
3026
- }), [effectiveStates, mapInstance]);
3126
+ }), [effectiveStates, isMobile, mapInstance]);
3027
3127
  if (loadingMap) {
3028
3128
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { padding: 16, height, width }, children: "Cargando mapa..." });
3029
3129
  }
@@ -3076,6 +3176,7 @@ var ZenitMap = (0, import_react5.forwardRef)(({
3076
3176
  ),
3077
3177
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_leaflet4.ZoomControl, { position: "topright" }),
3078
3178
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MapInstanceBridge, { onReady: handleMapReady }),
3179
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MapInvalidator, { trigger: mapId }),
3079
3180
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3080
3181
  BBoxZoomHandler,
3081
3182
  {
@@ -3293,7 +3394,9 @@ var ZenitLayerManager = ({
3293
3394
  showUploadTab = true,
3294
3395
  showLayerVisibilityIcon = true,
3295
3396
  layerFeatureCounts,
3296
- mapLayers
3397
+ mapLayers,
3398
+ onApplyLayerFilter,
3399
+ onClearLayerFilter
3297
3400
  }) => {
3298
3401
  const [map, setMap] = (0, import_react6.useState)(null);
3299
3402
  const [loadingMap, setLoadingMap] = (0, import_react6.useState)(false);
@@ -3301,6 +3404,15 @@ var ZenitLayerManager = ({
3301
3404
  const [layers, setLayers] = (0, import_react6.useState)([]);
3302
3405
  const [activeTab, setActiveTab] = (0, import_react6.useState)("layers");
3303
3406
  const [panelVisible, setPanelVisible] = (0, import_react6.useState)(true);
3407
+ const [selectedFilterLayerId, setSelectedFilterLayerId] = (0, import_react6.useState)("");
3408
+ const [selectedFilterField, setSelectedFilterField] = (0, import_react6.useState)("");
3409
+ const [selectedFilterValue, setSelectedFilterValue] = (0, import_react6.useState)("");
3410
+ const [catalogByLayerField, setCatalogByLayerField] = (0, import_react6.useState)({});
3411
+ const [loadingCatalog, setLoadingCatalog] = (0, import_react6.useState)(false);
3412
+ const [applyingFilter, setApplyingFilter] = (0, import_react6.useState)(false);
3413
+ const [filterError, setFilterError] = (0, import_react6.useState)(null);
3414
+ const [appliedFilter, setAppliedFilter] = (0, import_react6.useState)(null);
3415
+ const catalogAbortRef = (0, import_react6.useRef)(null);
3304
3416
  const lastEmittedStatesRef = (0, import_react6.useRef)(null);
3305
3417
  const isControlled = Array.isArray(layerStates) && typeof onLayerStatesChange === "function";
3306
3418
  const baseStates = (0, import_react6.useMemo)(
@@ -3494,6 +3606,139 @@ var ZenitLayerManager = ({
3494
3606
  return String(a.mapLayer.layerId).localeCompare(String(b.mapLayer.layerId));
3495
3607
  });
3496
3608
  }, [effectiveStates, layers, resolveFeatureCount]);
3609
+ const filterableLayers = (0, import_react6.useMemo)(() => {
3610
+ return decoratedLayers.filter((entry) => {
3611
+ const prefilters = entry.mapLayer.layerConfig?.prefilters;
3612
+ return !!prefilters && Object.keys(prefilters).length > 0;
3613
+ });
3614
+ }, [decoratedLayers]);
3615
+ const selectedFilterLayer = (0, import_react6.useMemo)(
3616
+ () => filterableLayers.find((layer) => String(layer.mapLayer.layerId) === selectedFilterLayerId) ?? null,
3617
+ [filterableLayers, selectedFilterLayerId]
3618
+ );
3619
+ const filterFields = (0, import_react6.useMemo)(() => {
3620
+ const prefilters = selectedFilterLayer?.mapLayer.layerConfig?.prefilters;
3621
+ return prefilters ? Object.keys(prefilters) : [];
3622
+ }, [selectedFilterLayer]);
3623
+ const activeCatalogKey = selectedFilterLayer ? `${selectedFilterLayer.mapLayer.layerId}:${selectedFilterField}` : null;
3624
+ const activeCatalogValues = activeCatalogKey ? catalogByLayerField[activeCatalogKey] ?? [] : [];
3625
+ const extractCatalogValues = import_react6.default.useCallback((catalogData, field) => {
3626
+ const values = /* @__PURE__ */ new Set();
3627
+ const pushValue = (value) => {
3628
+ if (value === null || value === void 0) return;
3629
+ const normalized = String(value).trim();
3630
+ if (normalized) values.add(normalized);
3631
+ };
3632
+ if (catalogData && typeof catalogData === "object") {
3633
+ const maybeRecord = catalogData;
3634
+ const directField = maybeRecord[field];
3635
+ if (Array.isArray(directField)) {
3636
+ directField.forEach(pushValue);
3637
+ }
3638
+ const items = maybeRecord.items;
3639
+ if (Array.isArray(items)) {
3640
+ items.forEach((item) => {
3641
+ if (!item || typeof item !== "object") return;
3642
+ const row = item;
3643
+ const rowField = row.field;
3644
+ if (String(rowField ?? "").toUpperCase() === field.toUpperCase() && Array.isArray(row.values)) {
3645
+ row.values.forEach(pushValue);
3646
+ }
3647
+ });
3648
+ }
3649
+ const features = maybeRecord.features;
3650
+ if (Array.isArray(features)) {
3651
+ features.forEach((feature) => {
3652
+ if (!feature || typeof feature !== "object") return;
3653
+ const properties = feature.properties;
3654
+ if (properties && field in properties) {
3655
+ pushValue(properties[field]);
3656
+ }
3657
+ });
3658
+ }
3659
+ }
3660
+ return [...values].sort((a, b) => a.localeCompare(b, void 0, { sensitivity: "base" }));
3661
+ }, []);
3662
+ (0, import_react6.useEffect)(() => {
3663
+ if (!filterableLayers.length) {
3664
+ setSelectedFilterLayerId("");
3665
+ return;
3666
+ }
3667
+ if (!selectedFilterLayerId || !filterableLayers.some((layer) => String(layer.mapLayer.layerId) === selectedFilterLayerId)) {
3668
+ setSelectedFilterLayerId(String(filterableLayers[0].mapLayer.layerId));
3669
+ }
3670
+ }, [filterableLayers, selectedFilterLayerId]);
3671
+ (0, import_react6.useEffect)(() => {
3672
+ if (!filterFields.length) {
3673
+ setSelectedFilterField("");
3674
+ return;
3675
+ }
3676
+ if (!selectedFilterField || !filterFields.includes(selectedFilterField)) {
3677
+ setSelectedFilterField(filterFields[0]);
3678
+ setSelectedFilterValue("");
3679
+ }
3680
+ }, [filterFields, selectedFilterField]);
3681
+ (0, import_react6.useEffect)(() => {
3682
+ if (activeTab !== "filters") return;
3683
+ if (!selectedFilterLayer || !selectedFilterField || !activeCatalogKey) return;
3684
+ if (catalogByLayerField[activeCatalogKey]) return;
3685
+ catalogAbortRef.current?.abort();
3686
+ const controller = new AbortController();
3687
+ catalogAbortRef.current = controller;
3688
+ setLoadingCatalog(true);
3689
+ setFilterError(null);
3690
+ client.layers.getLayerFeaturesCatalog(selectedFilterLayer.mapLayer.layerId).then((response) => {
3691
+ if (controller.signal.aborted) return;
3692
+ const values = extractCatalogValues(response.data, selectedFilterField);
3693
+ setCatalogByLayerField((prev) => ({ ...prev, [activeCatalogKey]: values }));
3694
+ }).catch((error) => {
3695
+ if (controller.signal.aborted) return;
3696
+ const message = error instanceof Error ? error.message : "No se pudo cargar el cat\xE1logo";
3697
+ setFilterError(message);
3698
+ }).finally(() => {
3699
+ if (!controller.signal.aborted) setLoadingCatalog(false);
3700
+ });
3701
+ return () => {
3702
+ controller.abort();
3703
+ };
3704
+ }, [activeCatalogKey, activeTab, catalogByLayerField, client.layers, extractCatalogValues, selectedFilterField, selectedFilterLayer]);
3705
+ const handleApplyFilter = import_react6.default.useCallback(async () => {
3706
+ if (!selectedFilterLayer || !selectedFilterField || !selectedFilterValue || !onApplyLayerFilter) return;
3707
+ setApplyingFilter(true);
3708
+ setFilterError(null);
3709
+ try {
3710
+ await onApplyLayerFilter({
3711
+ layerId: selectedFilterLayer.mapLayer.layerId,
3712
+ field: selectedFilterField,
3713
+ value: selectedFilterValue
3714
+ });
3715
+ setAppliedFilter({
3716
+ layerId: selectedFilterLayer.mapLayer.layerId,
3717
+ field: selectedFilterField,
3718
+ value: selectedFilterValue
3719
+ });
3720
+ } catch (error) {
3721
+ const message = error instanceof Error ? error.message : "No se pudo aplicar el filtro";
3722
+ setFilterError(message);
3723
+ } finally {
3724
+ setApplyingFilter(false);
3725
+ }
3726
+ }, [onApplyLayerFilter, selectedFilterField, selectedFilterLayer, selectedFilterValue]);
3727
+ const handleClearFilter = import_react6.default.useCallback(async () => {
3728
+ if (!selectedFilterLayer) return;
3729
+ setApplyingFilter(true);
3730
+ setFilterError(null);
3731
+ try {
3732
+ await onClearLayerFilter?.({ layerId: selectedFilterLayer.mapLayer.layerId, field: selectedFilterField || void 0 });
3733
+ setSelectedFilterValue("");
3734
+ setAppliedFilter(null);
3735
+ } catch (error) {
3736
+ const message = error instanceof Error ? error.message : "No se pudo limpiar el filtro";
3737
+ setFilterError(message);
3738
+ } finally {
3739
+ setApplyingFilter(false);
3740
+ }
3741
+ }, [onClearLayerFilter, selectedFilterField, selectedFilterLayer]);
3497
3742
  const resolveLayerStyle = import_react6.default.useCallback(
3498
3743
  (layerId) => {
3499
3744
  const layerKey = String(layerId);
@@ -3832,6 +4077,18 @@ var ZenitLayerManager = ({
3832
4077
  "Subir"
3833
4078
  ]
3834
4079
  }
4080
+ ),
4081
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
4082
+ "button",
4083
+ {
4084
+ type: "button",
4085
+ className: `zlm-tab${activeTab === "filters" ? " is-active" : ""}`,
4086
+ onClick: () => setActiveTab("filters"),
4087
+ children: [
4088
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react.Layers, { size: 16 }),
4089
+ "Filtros"
4090
+ ]
4091
+ }
3835
4092
  )
3836
4093
  ]
3837
4094
  }
@@ -3839,6 +4096,68 @@ var ZenitLayerManager = ({
3839
4096
  ] }),
3840
4097
  panelVisible && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { padding: "12px 10px 18px", overflowY: "auto", flex: 1, minHeight: 0 }, children: [
3841
4098
  activeTab === "layers" && renderLayerCards(),
4099
+ activeTab === "filters" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "zlm-filter-panel", style: { display: "flex", flexDirection: "column", gap: 10, background: "#fff", border: "1px solid #e2e8f0", borderRadius: 12, padding: 12 }, children: !filterableLayers.length ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { color: "#64748b", fontSize: 13 }, children: "No hay filtros disponibles para las capas de este mapa." }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
4100
+ filterableLayers.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { style: { display: "flex", flexDirection: "column", gap: 6, fontSize: 12, color: "#475569" }, children: [
4101
+ "Capa",
4102
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { className: "zlm-filter-select", value: selectedFilterLayerId, onChange: (e) => setSelectedFilterLayerId(e.target.value), children: filterableLayers.map((layer) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: String(layer.mapLayer.layerId), children: layer.layerName ?? `Capa ${layer.mapLayer.layerId}` }, String(layer.mapLayer.layerId))) })
4103
+ ] }),
4104
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { style: { display: "flex", flexDirection: "column", gap: 6, fontSize: 12, color: "#475569" }, children: [
4105
+ "Campo",
4106
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { className: "zlm-filter-select", value: selectedFilterField, onChange: (e) => {
4107
+ setSelectedFilterField(e.target.value);
4108
+ setSelectedFilterValue("");
4109
+ }, children: filterFields.map((field) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: field, children: field }, field)) })
4110
+ ] }),
4111
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { style: { display: "flex", flexDirection: "column", gap: 6, fontSize: 12, color: "#475569" }, children: [
4112
+ "Valor",
4113
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
4114
+ "select",
4115
+ {
4116
+ className: "zlm-filter-select",
4117
+ value: selectedFilterValue,
4118
+ onChange: (e) => {
4119
+ const value = e.target.value;
4120
+ setSelectedFilterValue(value);
4121
+ },
4122
+ children: [
4123
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "", children: "Seleccionar\u2026" }),
4124
+ activeCatalogValues.map((value) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value, children: value }, value))
4125
+ ]
4126
+ }
4127
+ )
4128
+ ] }),
4129
+ loadingCatalog && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { color: "#64748b", fontSize: 12 }, children: "Cargando cat\xE1logo\u2026" }),
4130
+ !loadingCatalog && selectedFilterField && activeCatalogValues.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { color: "#64748b", fontSize: 12 }, children: "No hay cat\xE1logo disponible para este filtro." }),
4131
+ filterError && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { color: "#b91c1c", fontSize: 12 }, children: filterError }),
4132
+ appliedFilter && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "zlm-badge", style: { alignSelf: "flex-start" }, children: [
4133
+ "Activo: ",
4134
+ appliedFilter.field,
4135
+ " = ",
4136
+ appliedFilter.value
4137
+ ] }),
4138
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "zlm-filter-actions", style: { display: "flex", gap: 8 }, children: [
4139
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4140
+ "button",
4141
+ {
4142
+ type: "button",
4143
+ className: "zlm-panel-toggle",
4144
+ disabled: !selectedFilterValue || applyingFilter || !onApplyLayerFilter,
4145
+ onClick: handleApplyFilter,
4146
+ children: applyingFilter ? "Aplicando\u2026" : "Aplicar"
4147
+ }
4148
+ ),
4149
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4150
+ "button",
4151
+ {
4152
+ type: "button",
4153
+ className: "zlm-panel-toggle",
4154
+ disabled: applyingFilter,
4155
+ onClick: handleClearFilter,
4156
+ children: "Limpiar"
4157
+ }
4158
+ )
4159
+ ] })
4160
+ ] }) }),
3842
4161
  showUploadTab && activeTab === "upload" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { color: "#475569", fontSize: 13 }, children: "Pr\xF3ximamente podr\xE1s subir capas desde este panel." })
3843
4162
  ] })
3844
4163
  ] });