• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

keplergl / kepler.gl / 20487014359

24 Dec 2025 01:17PM UTC coverage: 61.67% (-0.002%) from 61.672%
20487014359

Pull #3269

github

web-flow
Merge 5bdeffc67 into f468e3a51
Pull Request #3269: fix: trigger a redraw from icon layer once the icons are loaded.

6353 of 12231 branches covered (51.94%)

Branch coverage included in aggregate %.

4 of 7 new or added lines in 2 files covered. (57.14%)

19 existing lines in 1 file now uncovered.

13049 of 19230 relevant lines covered (67.86%)

81.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

45.17
/src/components/src/map-container.tsx
1
// SPDX-License-Identifier: MIT
2
// Copyright contributors to the kepler.gl project
3

4
// libraries
5
import React, {Component, createRef, useMemo} from 'react';
6
import styled, {withTheme} from 'styled-components';
7
import {Map, MapRef} from 'react-map-gl';
8
import {PickInfo} from '@deck.gl/core/lib/deck';
9
import DeckGL from '@deck.gl/react';
10
import {createSelector, Selector} from 'reselect';
11
import {useDroppable} from '@dnd-kit/core';
12
import debounce from 'lodash/debounce';
13

14
import {VisStateActions, MapStateActions, UIStateActions} from '@kepler.gl/actions';
15

16
// components
17
import MapPopoverFactory from './map/map-popover';
18
import MapControlFactory from './map/map-control';
19
import {
20
  StyledMapContainer,
21
  StyledAttribution,
22
  EndHorizontalFlexbox
23
} from './common/styled-components';
24

25
import EditorFactory from './editor/editor';
26

27
// utils
28
import {
29
  generateMapboxLayers,
30
  updateMapboxLayers,
31
  LayerBaseConfig,
32
  VisualChannelDomain,
33
  EditorLayerUtils,
34
  AggregatedBin
35
} from '@kepler.gl/layers';
36
import {
37
  DatasetAttribution,
38
  MapState,
39
  MapControls,
40
  Viewport,
41
  SplitMap,
42
  SplitMapLayers
43
} from '@kepler.gl/types';
44
import {
45
  errorNotification,
46
  setLayerBlending,
47
  isStyleUsingMapboxTiles,
48
  isStyleUsingOpenStreetMapTiles,
49
  getBaseMapLibrary,
50
  BaseMapLibraryConfig,
51
  transformRequest,
52
  observeDimensions,
53
  unobserveDimensions,
54
  hasMobileWidth,
55
  getMapLayersFromSplitMaps,
56
  onViewPortChange,
57
  getViewportFromMapState,
58
  normalizeEvent,
59
  rgbToHex,
60
  computeDeckEffects,
61
  getApplicationConfig,
62
  GetMapRef
63
} from '@kepler.gl/utils';
64
import {breakPointValues} from '@kepler.gl/styles';
65

66
// default-settings
67
import {
68
  FILTER_TYPES,
69
  GEOCODER_LAYER_ID,
70
  THROTTLE_NOTIFICATION_TIME,
71
  DEFAULT_PICKING_RADIUS,
72
  NO_MAP_ID,
73
  EMPTY_MAPBOX_STYLE
74
} from '@kepler.gl/constants';
75

76
import {DROPPABLE_MAP_CONTAINER_TYPE} from './common/dnd-layer-items';
77
// Contexts
78
import {MapViewStateContext} from './map-view-state-context';
79

80
import ErrorBoundary from './common/error-boundary';
81
import {LOCALE_CODES} from '@kepler.gl/localization';
82
import {MapView} from '@deck.gl/core';
83
import {
84
  MapStyle,
85
  areAnyDeckLayersLoading,
86
  computeDeckLayers,
87
  getLayerHoverProp,
88
  LayerHoverProp,
89
  prepareLayersForDeck,
90
  prepareLayersToRender,
91
  LayersToRender
92
} from '@kepler.gl/reducers';
93
import {VisState} from '@kepler.gl/schemas';
94

95
import LoadingIndicator from './loading-indicator';
96

97
// Debounce the propagation of viewport change and mouse moves to redux store.
98
// This is to avoid too many renders of other components when the map is
99
// being panned/zoomed (leading to laggy basemap/deck syncing).
100
const DEBOUNCE_VIEWPORT_PROPAGATE = 10;
7✔
101
const DEBOUNCE_MOUSE_MOVE_PROPAGATE = 10;
7✔
102

103
// How long should we wait between layer loading state changes before triggering a UI update
104
const DEBOUNCE_LOADING_STATE_PROPAGATE = 100;
7✔
105

106
const MAP_STYLE: {[key: string]: React.CSSProperties} = {
7✔
107
  container: {
108
    display: 'inline-block',
109
    position: 'relative',
110
    width: '100%',
111
    height: '100%'
112
  },
113
  top: {
114
    position: 'absolute',
115
    top: 0,
116
    width: '100%',
117
    height: '100%',
118
    pointerEvents: 'none'
119
  }
120
};
121

122
const LOCALE_CODES_ARRAY = Object.keys(LOCALE_CODES);
7✔
123

124
interface StyledMapContainerProps {
125
  $mixBlendMode?: string;
126
  $mapLibCssClass: string;
127
}
128

129
const StyledMap = styled(StyledMapContainer)<StyledMapContainerProps>(
7✔
130
  ({$mixBlendMode = 'normal', $mapLibCssClass}) => `
29!
131
  #default-deckgl-overlay {
132
    mix-blend-mode: ${$mixBlendMode};
133
  };
134
  *[${$mapLibCssClass}-children] {
135
    position: absolute;
136
  }
137
`
138
);
139

140
const MAPBOXGL_STYLE_UPDATE = 'style.load';
7✔
141
const MAPBOXGL_RENDER = 'render';
7✔
142
const nop = () => {
7✔
143
  return;
×
144
};
145

146
type MapLibLogoProps = {
147
  baseMapLibraryConfig: BaseMapLibraryConfig;
148
};
149

150
const MapLibLogo = ({baseMapLibraryConfig}: MapLibLogoProps) => (
7✔
151
  <div className="attrition-logo">
25✔
152
    Basemap by:
153
    <a
154
      style={{marginLeft: '5px'}}
155
      className={`${baseMapLibraryConfig.mapLibCssClass}-ctrl-logo`}
156
      target="_blank"
157
      rel="noopener noreferrer"
158
      href={baseMapLibraryConfig.mapLibUrl}
159
      aria-label={`${baseMapLibraryConfig.mapLibName} logo`}
160
    />
161
  </div>
162
);
163

164
interface StyledDroppableProps {
165
  isOver: boolean;
166
}
167

168
const StyledDroppable = styled.div<StyledDroppableProps>`
7✔
169
  background-color: ${props => (props.isOver ? props.theme.dndOverBackgroundColor : 'none')};
9!
170
  width: 100%;
171
  height: 100%;
172
  position: absolute;
173
  pointer-events: none;
174
  z-index: 1;
175
`;
176

177
export const isSplitSelector = props =>
7✔
178
  props.visState.splitMaps && props.visState.splitMaps.length > 1;
29✔
179

180
export const Droppable = ({containerId}) => {
7✔
181
  const {isOver, setNodeRef} = useDroppable({
9✔
182
    id: containerId,
183
    data: {type: DROPPABLE_MAP_CONTAINER_TYPE, index: containerId},
184
    disabled: !containerId
185
  });
186

187
  return <StyledDroppable ref={setNodeRef} isOver={isOver} />;
9✔
188
};
189

190
interface StyledDatasetAttributionsContainerProps {
191
  isPalm: boolean;
192
}
193

194
const StyledDatasetAttributionsContainer = styled.div<StyledDatasetAttributionsContainerProps>`
7✔
195
  max-width: ${props => (props.isPalm ? '200px' : '300px')};
×
196
  text-overflow: ellipsis;
197
  white-space: nowrap;
198
  overflow: hidden;
199
  color: ${props => props.theme.labelColor};
×
200
  margin-right: 2px;
201
  margin-bottom: 1px;
202
  line-height: ${props => (props.isPalm ? '1em' : '1.4em')};
×
203

204
  &:hover {
205
    white-space: inherit;
206
  }
207
`;
208

209
const DatasetAttributions = ({
7✔
210
  datasetAttributions,
211
  isPalm
212
}: {
213
  datasetAttributions: DatasetAttribution[];
214
  isPalm: boolean;
215
}) => (
216
  <>
25✔
217
    {datasetAttributions?.length ? (
25!
218
      <StyledDatasetAttributionsContainer isPalm={isPalm}>
219
        {datasetAttributions.map((ds, idx) => (
220
          <a
×
221
            {...(ds.url ? {href: ds.url} : null)}
×
222
            target="_blank"
223
            rel="noopener noreferrer"
224
            key={`${ds.title}_${idx}`}
225
          >
226
            {ds.title}
227
            {idx !== datasetAttributions.length - 1 ? ', ' : null}
×
228
          </a>
229
        ))}
230
      </StyledDatasetAttributionsContainer>
231
    ) : null}
232
  </>
233
);
234

235
type AttributionProps = {
236
  showBaseMapLibLogo: boolean;
237
  showOsmBasemapAttribution: boolean;
238
  datasetAttributions: DatasetAttribution[];
239
  baseMapLibraryConfig: BaseMapLibraryConfig;
240
};
241

242
export const Attribution: React.FC<AttributionProps> = ({
7✔
243
  showBaseMapLibLogo = true,
×
244
  showOsmBasemapAttribution = false,
×
245
  datasetAttributions,
246
  baseMapLibraryConfig
247
}: AttributionProps) => {
248
  const isPalm = hasMobileWidth(breakPointValues);
28✔
249

250
  const memoizedComponents = useMemo(() => {
28✔
251
    if (!showBaseMapLibLogo) {
25!
252
      return (
×
253
        <StyledAttribution
254
          mapLibCssClass={baseMapLibraryConfig.mapLibCssClass}
255
          mapLibAttributionCssClass={baseMapLibraryConfig.mapLibAttributionCssClass}
256
        >
257
          <EndHorizontalFlexbox>
258
            <DatasetAttributions datasetAttributions={datasetAttributions} isPalm={isPalm} />
259
            {showOsmBasemapAttribution ? (
×
260
              <div className="attrition-link">
261
                {datasetAttributions?.length ? <span className="pipe-separator">|</span> : null}
×
262
                <a
263
                  href="http://www.openstreetmap.org/copyright"
264
                  target="_blank"
265
                  rel="noopener noreferrer"
266
                >
267
                  © OpenStreetMap
268
                </a>
269
              </div>
270
            ) : null}
271
          </EndHorizontalFlexbox>
272
        </StyledAttribution>
273
      );
274
    }
275

276
    return (
25✔
277
      <StyledAttribution
278
        mapLibCssClass={baseMapLibraryConfig.mapLibCssClass}
279
        mapLibAttributionCssClass={baseMapLibraryConfig.mapLibAttributionCssClass}
280
      >
281
        <EndHorizontalFlexbox>
282
          <DatasetAttributions datasetAttributions={datasetAttributions} isPalm={isPalm} />
283
          <div className="attrition-link">
284
            {datasetAttributions?.length ? <span className="pipe-separator">|</span> : null}
25!
285
            {isPalm ? <MapLibLogo baseMapLibraryConfig={baseMapLibraryConfig} /> : null}
25!
286
            <a href="https://kepler.gl/policy/" target="_blank" rel="noopener noreferrer">
287
              © kepler.gl |{' '}
288
            </a>
289
            {!isPalm ? <MapLibLogo baseMapLibraryConfig={baseMapLibraryConfig} /> : null}
25!
290
          </div>
291
        </EndHorizontalFlexbox>
292
      </StyledAttribution>
293
    );
294
  }, [
295
    showBaseMapLibLogo,
296
    showOsmBasemapAttribution,
297
    datasetAttributions,
298
    isPalm,
299
    baseMapLibraryConfig
300
  ]);
301

302
  return memoizedComponents;
28✔
303
};
304

305
MapContainerFactory.deps = [MapPopoverFactory, MapControlFactory, EditorFactory];
7✔
306

307
type MapboxStyle = string | object | undefined;
308
type PropSelector<R> = Selector<MapContainerProps, R>;
309

310
export interface MapContainerProps {
311
  visState: VisState;
312
  mapState: MapState;
313
  mapControls: MapControls;
314
  mapStyle: {bottomMapStyle?: MapboxStyle; topMapStyle?: MapboxStyle} & MapStyle;
315
  mapboxApiAccessToken: string;
316
  mapboxApiUrl: string;
317
  visStateActions: typeof VisStateActions;
318
  mapStateActions: typeof MapStateActions;
319
  uiStateActions: typeof UIStateActions;
320

321
  // optional
322
  primary?: boolean; // primary one will be reporting its size to appState
323
  readOnly?: boolean;
324
  isExport?: boolean;
325
  // onMapStyleLoaded?: (map: maplibregl.Map | ReturnType<MapRef['getMap']> | null) => void;
326
  onMapStyleLoaded?: (map: GetMapRef | null) => void;
327
  onMapRender?: (map: GetMapRef | null) => void;
328
  getMapboxRef?: (mapbox?: MapRef | null, index?: number) => void;
329
  index?: number;
330
  deleteMapLabels?: (containerId: string, layerId: string) => void;
331
  containerId?: number;
332

333
  isLoadingIndicatorVisible?: boolean;
334
  activeSidePanel: string | null;
335
  sidePanelWidth?: number;
336

337
  locale?: any;
338
  theme?: any;
339
  editor?: any;
340
  MapComponent?: typeof Map;
341
  deckGlProps?: any;
342
  onDeckInitialized?: (a: any, b: any) => void;
343
  onViewStateChange?: (viewport: Viewport) => void;
344

345
  topMapContainerProps: any;
346
  bottomMapContainerProps: any;
347
  transformRequest?: (url: string, resourceType?: string) => {url: string};
348

349
  datasetAttributions?: DatasetAttribution[];
350

351
  generateMapboxLayers?: typeof generateMapboxLayers;
352
  generateDeckGLLayers?: typeof computeDeckLayers;
353

354
  onMouseMove?: (event: React.MouseEvent & {lngLat?: [number, number]}) => void;
355

356
  children?: React.ReactNode;
357
  deckRenderCallbacks?: {
358
    onDeckLoad?: () => void;
359
    onDeckRender?: (deckProps: Record<string, unknown>) => Record<string, unknown> | null;
360
    onDeckAfterRender?: (deckProps: Record<string, unknown>) => any;
361
  };
362

363
  // Optional: override legend header logo in map controls (used by image export)
364
  logoComponent?: React.FC | React.ReactNode;
365
}
366

367
export default function MapContainerFactory(
368
  MapPopover: ReturnType<typeof MapPopoverFactory>,
369
  MapControl: ReturnType<typeof MapControlFactory>,
370
  Editor: ReturnType<typeof EditorFactory>
371
): React.ComponentType<MapContainerProps> {
372
  class MapContainer extends Component<MapContainerProps> {
373
    displayName = 'MapContainer';
25✔
374

375
    private anyActiveLayerLoading = false;
25✔
376

377
    static contextType = MapViewStateContext;
14✔
378

379
    declare context: React.ContextType<typeof MapViewStateContext>;
380

381
    static defaultProps = {
14✔
382
      MapComponent: Map,
383
      deckGlProps: {},
384
      index: 0,
385
      primary: true
386
    };
387

388
    constructor(props) {
389
      super(props);
25✔
390
    }
391

392
    state = {
25✔
393
      // Determines whether attribution should be visible based the result of loading the map style
394
      showBaseMapAttribution: true
395
    };
396

397
    componentDidMount() {
398
      if (!this._ref.current) {
25!
399
        return;
×
400
      }
401
      observeDimensions(this._ref.current, this._handleResize);
25✔
402
    }
403

404
    componentWillUnmount() {
405
      // unbind mapboxgl event listener
406
      if (this._map) {
2!
407
        this._map?.off(MAPBOXGL_STYLE_UPDATE, nop);
×
408
        this._map?.off(MAPBOXGL_RENDER, nop);
×
409
      }
410
      if (!this._ref.current) {
2!
411
        return;
×
412
      }
413
      unobserveDimensions(this._ref.current);
2✔
414
    }
415

416
    _deck: any = null;
25✔
417
    _map: GetMapRef | null = null;
25✔
418
    _ref = createRef<HTMLDivElement>();
25✔
419
    _deckGLErrorsElapsed: {[id: string]: number} = {};
25✔
420

421
    previousLayers = {
25✔
422
      // [layers.id]: mapboxLayerConfig
423
    };
424

425
    _handleResize = dimensions => {
25✔
426
      const {primary, index} = this.props;
×
427
      if (primary) {
×
428
        const {mapStateActions} = this.props;
×
429
        if (dimensions && dimensions.width > 0 && dimensions.height > 0) {
×
430
          mapStateActions.updateMap(dimensions, index);
×
431
        }
432
      }
433
    };
434

435
    layersSelector: PropSelector<VisState['layers']> = props => props.visState.layers;
56✔
436
    layerDataSelector: PropSelector<VisState['layers']> = props => props.visState.layerData;
56✔
437
    splitMapSelector: PropSelector<SplitMap[]> = props => props.visState.splitMaps;
28✔
438
    splitMapIndexSelector: PropSelector<number | undefined> = props => props.index;
28✔
439
    mapLayersSelector: PropSelector<SplitMapLayers | null | undefined> = createSelector(
25✔
440
      this.splitMapSelector,
441
      this.splitMapIndexSelector,
442
      getMapLayersFromSplitMaps
443
    );
444
    layerOrderSelector: PropSelector<VisState['layerOrder']> = props => props.visState.layerOrder;
25✔
445
    layersToRenderSelector: PropSelector<LayersToRender> = createSelector(
25✔
446
      this.layersSelector,
447
      this.layerDataSelector,
448
      this.mapLayersSelector,
449
      prepareLayersToRender
450
    );
451
    layersForDeckSelector = createSelector(
25✔
452
      this.layersSelector,
453
      this.layerDataSelector,
454
      prepareLayersForDeck
455
    );
456
    filtersSelector = props => props.visState.filters;
28✔
457
    polygonFiltersSelector = createSelector(this.filtersSelector, filters =>
25✔
458
      filters.filter(f => f.type === FILTER_TYPES.polygon && f.enabled !== false)
26!
459
    );
460
    featuresSelector = props => props.visState.editor.features;
28✔
461
    selectedFeatureSelector = props => props.visState.editor.selectedFeature;
28✔
462
    featureCollectionSelector = createSelector(
25✔
463
      this.polygonFiltersSelector,
464
      this.featuresSelector,
465
      (polygonFilters, features) => ({
26✔
466
        type: 'FeatureCollection',
467
        features: features.concat(polygonFilters.map(f => f.value))
×
468
      })
469
    );
470
    // @ts-ignore - No overload matches this call
471
    selectedPolygonIndexSelector = createSelector(
25✔
472
      this.featureCollectionSelector,
473
      this.selectedFeatureSelector,
474
      (collection, selectedFeature) =>
475
        collection.features.findIndex(f => f.id === selectedFeature?.id)
26✔
476
    );
477
    selectedFeatureIndexArraySelector = createSelector(
25✔
478
      (value: number) => value,
25✔
479
      value => {
480
        return value < 0 ? [] : [value];
25!
481
      }
482
    );
483

484
    generateMapboxLayerMethodSelector = props => props.generateMapboxLayers ?? generateMapboxLayers;
25!
485

486
    mapboxLayersSelector = createSelector(
25✔
487
      this.layersSelector,
488
      this.layerDataSelector,
489
      this.layerOrderSelector,
490
      this.layersToRenderSelector,
491
      this.generateMapboxLayerMethodSelector,
492
      (layer, layerData, layerOrder, layersToRender, generateMapboxLayerMethod) =>
493
        generateMapboxLayerMethod(layer, layerData, layerOrder, layersToRender)
×
494
    );
495

496
    // merge in a background-color style if the basemap choice is NO_MAP_ID
497
    // used by <StyledMap> inline style prop
498
    mapStyleTypeSelector = props => props.mapStyle.styleType;
28✔
499
    mapStyleBackgroundColorSelector = props => props.mapStyle.backgroundColor;
28✔
500
    styleSelector = createSelector(
25✔
501
      this.mapStyleTypeSelector,
502
      this.mapStyleBackgroundColorSelector,
503
      (styleType, backgroundColor) => ({
26✔
504
        ...MAP_STYLE.container,
505
        ...(styleType === NO_MAP_ID ? {backgroundColor: rgbToHex(backgroundColor)} : {})
26!
506
      })
507
    );
508

509
    /* component private functions */
510
    _onCloseMapPopover = () => {
25✔
511
      this.props.visStateActions.onLayerClick(null);
×
512
    };
513

514
    _onLayerHover = (_idx: number, info: PickInfo<any> | null) => {
25✔
515
      this.props.visStateActions.onLayerHover(info, this.props.index);
×
516
    };
517

518
    _onLayerSetDomain = (
25✔
519
      idx: number,
520
      value: {domain: VisualChannelDomain; aggregatedBins: AggregatedBin[]}
521
    ) => {
522
      this.props.visStateActions.layerConfigChange(this.props.visState.layers[idx], {
×
523
        colorDomain: value.domain,
524
        aggregatedBins: value.aggregatedBins
525
      } as Partial<LayerBaseConfig>);
526
    };
527

528
    _onRedrawNeeded = (_idx: number) => {
25✔
529
      // Force a map re-render by triggering a minimal map state update
NEW
530
      const {mapStateActions, mapState, index} = this.props;
×
NEW
531
      mapStateActions.updateMap({width: mapState.width, height: mapState.height}, index);
×
532
    };
533

534
    _onLayerFilteredItemsChange = (idx, event) => {
25✔
535
      this.props.visStateActions.layerFilteredItemsChange(this.props.visState.layers[idx], event);
×
536
    };
537

538
    _onWMSFeatureInfo = (
25✔
539
      idx: number,
540
      data: {
541
        featureInfo: Array<{name: string; value: string}> | string | null;
542
        coordinate?: [number, number] | null;
543
      }
544
    ) => {
545
      this.props.visStateActions.wmsFeatureInfo(
×
546
        this.props.visState.layers[idx],
547
        data.featureInfo,
548
        data.coordinate
549
      );
550
    };
551

552
    _handleMapToggleLayer = layerId => {
25✔
553
      const {index: mapIndex = 0, visStateActions} = this.props;
1!
554
      visStateActions.toggleLayerForMap(mapIndex, layerId);
1✔
555
    };
556

557
    _onMapboxStyleUpdate = update => {
25✔
558
      // force refresh mapboxgl layers
559
      this.previousLayers = {};
×
560
      this._updateMapboxLayers();
×
561

562
      if (update && update.style) {
×
563
        // No attributions are needed if the style doesn't reference Mapbox sources
564
        this.setState({
×
565
          showBaseMapAttribution:
566
            isStyleUsingMapboxTiles(update.style) || !isStyleUsingOpenStreetMapTiles(update.style)
×
567
        });
568
      }
569

570
      if (typeof this.props.onMapStyleLoaded === 'function') {
×
571
        this.props.onMapStyleLoaded(this._map);
×
572
      }
573
    };
574

575
    _setMapRef = mapRef => {
25✔
576
      // Handle change of the map library
577
      if (this._map && mapRef) {
×
578
        const map = mapRef.getMap();
×
579
        if (map && this._map !== map) {
×
580
          this._map?.off(MAPBOXGL_STYLE_UPDATE, nop);
×
581
          this._map?.off(MAPBOXGL_RENDER, nop);
×
582
          this._map = null;
×
583
        }
584
      }
585

586
      if (!this._map && mapRef) {
×
587
        this._map = mapRef.getMap();
×
588
        // i noticed in certain context we don't access the actual map element
589
        if (!this._map) {
×
590
          return;
×
591
        }
592
        // bind mapboxgl event listener
593
        this._map.on(MAPBOXGL_STYLE_UPDATE, this._onMapboxStyleUpdate);
×
594

595
        this._map.on(MAPBOXGL_RENDER, () => {
×
596
          if (typeof this.props.onMapRender === 'function') {
×
597
            this.props.onMapRender(this._map);
×
598
          }
599
        });
600
      }
601

602
      if (this.props.getMapboxRef) {
×
603
        // The parent component can gain access to our MapboxGlMap by
604
        // providing this callback. Note that 'mapbox' will be null when the
605
        // ref is unset (e.g. when a split map is closed).
606
        this.props.getMapboxRef(mapRef, this.props.index);
×
607
      }
608
    };
609

610
    _onDeckInitialized(gl) {
611
      if (this.props.onDeckInitialized) {
×
612
        this.props.onDeckInitialized(this._deck, gl);
×
613
      }
614
    }
615

616
    /**
617
     * 1) Allow effects only for the first view.
618
     * 2) Prevent effect:preRender call without valid generated viewports.
619
     * @param viewIndex View index.
620
     * @returns Returns true if effects can be used.
621
     */
622
    _isOKToRenderEffects(viewIndex?: number): boolean {
623
      return !viewIndex && Boolean(this._deck?.viewManager?._viewports?.length);
29✔
624
    }
625

626
    _onBeforeRender = ({gl}) => {
25✔
627
      setLayerBlending(gl, this.props.visState.layerBlending);
×
628
    };
629

630
    _onDeckError = (error, layer) => {
25✔
631
      const errorMessage = error?.message || 'unknown-error';
23!
632
      const layerMessage = layer?.id ? ` in ${layer.id} layer` : '';
23!
633
      const errorMessageFull =
634
        errorMessage === 'WebGL context is lost'
23!
635
          ? 'Your GPU was disconnected. This can happen if your computer goes to sleep. It can also occur for other reasons, such as if you are running too many GPU applications.'
636
          : `An error in deck.gl: ${errorMessage}${layerMessage}.`;
637

638
      // Throttle error notifications, as React doesn't like too many state changes from here.
639
      const lastShown = this._deckGLErrorsElapsed[errorMessageFull];
23✔
640
      if (!lastShown || lastShown < Date.now() - THROTTLE_NOTIFICATION_TIME) {
23!
641
        this._deckGLErrorsElapsed[errorMessageFull] = Date.now();
23✔
642

643
        // Mark layer as invalid
644
        let extraLayerMessage = '';
23✔
645
        const {visStateActions} = this.props;
23✔
646
        if (layer) {
23!
647
          let topMostLayer = layer;
×
648
          while (topMostLayer.parent) {
×
649
            topMostLayer = topMostLayer.parent;
×
650
          }
651
          if (topMostLayer.props?.id) {
×
652
            visStateActions.layerSetIsValid(topMostLayer, false);
×
653
            extraLayerMessage = 'The layer has been disabled and highlighted.';
×
654
          }
655
        }
656

657
        // Create new error notification or update existing one with same id.
658
        // Update is required to preserve the order of notifications as they probably are going to "jump" based on order of errors.
659
        const {uiStateActions} = this.props;
23✔
660
        uiStateActions.addNotification(
23✔
661
          errorNotification({
662
            message: `${errorMessageFull} ${extraLayerMessage}`,
663
            id: errorMessageFull // treat the error message as id
664
          })
665
        );
666
      }
667
    };
668

669
    /* component render functions */
670

671
    /* eslint-disable complexity */
672
    _renderMapPopover() {
673
      // this check is for limiting the display of the `<MapPopover>` to the `<MapContainer>` the user is interacting with
674
      // the DeckGL onHover event handler adds a `mapIndex` property which is available in the `hoverInfo` object of `visState`
675
      if (this.props.index !== this.props.visState.hoverInfo?.mapIndex) {
29!
676
        return null;
29✔
677
      }
678

679
      // TODO: move this into reducer so it can be tested
680
      const {
681
        mapState,
682
        visState: {
683
          hoverInfo,
684
          clicked,
685
          datasets,
686
          interactionConfig,
687
          animationConfig,
688
          layers,
689
          mousePos: {mousePosition, coordinate, pinned}
690
        }
691
      } = this.props;
×
692
      const layersToRender = this.layersToRenderSelector(this.props);
×
693

694
      if (!mousePosition || !interactionConfig.tooltip) {
×
695
        return null;
×
696
      }
697

698
      const layerHoverProp = getLayerHoverProp({
×
699
        animationConfig,
700
        interactionConfig,
701
        hoverInfo,
702
        layers,
703
        layersToRender,
704
        datasets
705
      });
706

707
      const compareMode = interactionConfig.tooltip.config
×
708
        ? interactionConfig.tooltip.config.compareMode
709
        : false;
710

711
      let pinnedPosition = {x: 0, y: 0};
×
712
      let layerPinnedProp: LayerHoverProp | null = null;
×
713
      if (pinned || clicked) {
×
714
        // project lnglat to screen so that tooltip follows the object on zoom
715
        const viewport = getViewportFromMapState(mapState);
×
716
        const lngLat = clicked ? clicked.coordinate : pinned.coordinate;
×
717
        pinnedPosition = this._getHoverXY(viewport, lngLat);
×
718
        layerPinnedProp = getLayerHoverProp({
×
719
          animationConfig,
720
          interactionConfig,
721
          hoverInfo: clicked,
722
          layers,
723
          layersToRender,
724
          datasets
725
        });
726
        if (layerHoverProp && layerPinnedProp) {
×
727
          layerHoverProp.primaryData = layerPinnedProp.data;
×
728
          layerHoverProp.compareType = interactionConfig.tooltip.config.compareType;
×
729
        }
730
      }
731

732
      const commonProp = {
×
733
        onClose: this._onCloseMapPopover,
734
        zoom: mapState.zoom,
735
        container: this._deck ? this._deck.canvas : undefined
×
736
      };
737

738
      return (
×
739
        <ErrorBoundary>
740
          {layerPinnedProp && (
×
741
            <MapPopover
742
              {...pinnedPosition}
743
              {...commonProp}
744
              layerHoverProp={layerPinnedProp}
745
              coordinate={interactionConfig.coordinate.enabled && (pinned || {}).coordinate}
×
746
              frozen={true}
747
              isBase={compareMode}
748
              onSetFeatures={this.props.visStateActions.setFeatures}
749
              setSelectedFeature={this.props.visStateActions.setSelectedFeature}
750
              // @ts-ignore Argument of type 'Readonly<MapContainerProps>' is not assignable to parameter of type 'never'
751
              featureCollection={this.featureCollectionSelector(this.props)}
752
            />
753
          )}
754
          {layerHoverProp && (!layerPinnedProp || compareMode) && (
×
755
            <MapPopover
756
              x={mousePosition[0]}
757
              y={mousePosition[1]}
758
              {...commonProp}
759
              layerHoverProp={layerHoverProp}
760
              frozen={false}
761
              coordinate={interactionConfig.coordinate.enabled && coordinate}
×
762
              onSetFeatures={this.props.visStateActions.setFeatures}
763
              setSelectedFeature={this.props.visStateActions.setSelectedFeature}
764
              // @ts-ignore Argument of type 'Readonly<MapContainerProps>' is not assignable to parameter of type 'never'
765
              featureCollection={this.featureCollectionSelector(this.props)}
766
            />
767
          )}
768
        </ErrorBoundary>
769
      );
770
    }
771

772
    /* eslint-enable complexity */
773

774
    _getHoverXY(viewport, lngLat) {
775
      const screenCoord = !viewport || !lngLat ? null : viewport.project(lngLat);
×
776
      return screenCoord && {x: screenCoord[0], y: screenCoord[1]};
×
777
    }
778

779
    _renderDeckOverlay(
780
      layersForDeck,
781
      options: {primaryMap: boolean; isInteractive?: boolean; children?: React.ReactNode} = {
×
782
        primaryMap: false
783
      }
784
    ) {
785
      const {
786
        mapStyle,
787
        visState,
788
        mapState,
789
        visStateActions,
790
        mapboxApiAccessToken,
791
        mapboxApiUrl,
792
        deckGlProps,
793
        index,
794
        mapControls,
795
        deckRenderCallbacks,
796
        theme,
797
        generateDeckGLLayers,
798
        onMouseMove
799
      } = this.props;
29✔
800

801
      const {hoverInfo, editor} = visState;
29✔
802
      const {primaryMap, isInteractive, children} = options;
29✔
803

804
      // disable double click zoom when editor is in any draw mode
805
      const {mapDraw} = mapControls;
29✔
806
      const {active: editorMenuActive = false} = mapDraw || {};
29✔
807
      const isEditorDrawingMode = EditorLayerUtils.isDrawingActive(editorMenuActive, editor.mode);
29✔
808

809
      const internalViewState = this.context?.getInternalViewState(index);
29✔
810
      const internalMapState = {...mapState, ...internalViewState};
29✔
811
      const viewport = getViewportFromMapState(internalMapState);
29✔
812

813
      const editorFeatureSelectedIndex = this.selectedPolygonIndexSelector(this.props);
29✔
814

815
      const {setFeatures, onLayerClick, setSelectedFeature} = visStateActions;
29✔
816

817
      const generateDeckGLLayersMethod = generateDeckGLLayers ?? computeDeckLayers;
29✔
818
      const deckGlLayers = generateDeckGLLayersMethod(
29✔
819
        {
820
          visState,
821
          mapState: internalMapState,
822
          mapStyle
823
        },
824
        {
825
          mapIndex: index,
826
          primaryMap,
827
          mapboxApiAccessToken,
828
          mapboxApiUrl,
829
          layersForDeck,
830
          editorInfo: primaryMap
29!
831
            ? {
832
                editor,
833
                editorMenuActive,
834
                onSetFeatures: setFeatures,
835
                setSelectedFeature,
836
                // @ts-ignore Argument of type 'Readonly<MapContainerProps>' is not assignable to parameter of type 'never'
837
                featureCollection: this.featureCollectionSelector(this.props),
838
                selectedFeatureIndexes: this.selectedFeatureIndexArraySelector(
839
                  // @ts-ignore Argument of type 'unknown' is not assignable to parameter of type 'number'.
840
                  editorFeatureSelectedIndex
841
                ),
842
                viewport
843
              }
844
            : undefined
845
        },
846
        {
847
          onLayerHover: this._onLayerHover,
848
          onSetLayerDomain: this._onLayerSetDomain,
849
          onFilteredItemsChange: this._onLayerFilteredItemsChange,
850
          onWMSFeatureInfo: this._onWMSFeatureInfo,
851
          onRedrawNeeded: this._onRedrawNeeded
852
        },
853
        deckGlProps
854
      );
855

856
      const extraDeckParams: {
857
        getTooltip?: (info: any) => object | null;
858
        getCursor?: ({isDragging}: {isDragging: boolean}) => string;
859
      } = {};
29✔
860
      if (primaryMap) {
29!
861
        extraDeckParams.getTooltip = info =>
29✔
862
          EditorLayerUtils.getTooltip(info, {
×
863
            editorMenuActive,
864
            editor,
865
            theme
866
          });
867

868
        extraDeckParams.getCursor = ({isDragging}: {isDragging: boolean}) => {
29✔
869
          const editorCursor = EditorLayerUtils.getCursor({
×
870
            editorMenuActive,
871
            editor,
872
            hoverInfo
873
          });
874
          if (editorCursor) return editorCursor;
×
875

876
          if (isDragging) return 'grabbing';
×
877
          if (hoverInfo?.layer) return 'pointer';
×
878
          return 'grab';
×
879
        };
880
      }
881

882
      const effects = this._isOKToRenderEffects(index)
29!
883
        ? computeDeckEffects({visState, mapState})
884
        : [];
885

886
      const views = deckGlProps?.views
29!
887
        ? deckGlProps?.views()
888
        : new MapView({legacyMeterSizes: true});
889

890
      let allDeckGlProps = {
29✔
891
        ...deckGlProps,
892
        pickingRadius: DEFAULT_PICKING_RADIUS,
893
        views,
894
        layers: deckGlLayers,
895
        effects
896
      };
897

898
      if (typeof deckRenderCallbacks?.onDeckRender === 'function') {
29!
899
        allDeckGlProps = deckRenderCallbacks.onDeckRender(allDeckGlProps);
×
900
        if (!allDeckGlProps) {
×
901
          // if onDeckRender returns null, do not render deck.gl
902
          return null;
×
903
        }
904
      }
905

906
      return (
29✔
907
        <div
908
          {...(isInteractive
29!
909
            ? {
910
                onMouseMove: primaryMap
29!
911
                  ? event => {
912
                      onMouseMove?.(event);
×
913
                      this._onMouseMoveDebounced(event, viewport);
×
914
                    }
915
                  : undefined
916
              }
917
            : {style: {pointerEvents: 'none'}})}
918
        >
919
          <DeckGL
920
            id="default-deckgl-overlay"
921
            onLoad={() => {
922
              if (typeof deckRenderCallbacks?.onDeckLoad === 'function') {
×
923
                deckRenderCallbacks.onDeckLoad();
×
924
              }
925
            }}
926
            {...allDeckGlProps}
927
            controller={
928
              isInteractive
29!
929
                ? {
930
                    doubleClickZoom: !isEditorDrawingMode,
931
                    dragRotate: this.props.mapState.dragRotate
932
                  }
933
                : false
934
            }
935
            initialViewState={internalViewState}
936
            onBeforeRender={this._onBeforeRender}
937
            onViewStateChange={isInteractive ? this._onViewportChange : undefined}
29!
938
            {...extraDeckParams}
939
            onHover={
940
              isInteractive
29!
941
                ? data => {
942
                    const res = EditorLayerUtils.onHover(data, {
×
943
                      editorMenuActive,
944
                      editor,
945
                      hoverInfo
946
                    });
947
                    if (res) return;
×
948

949
                    this._onLayerHoverDebounced(data, index);
×
950
                  }
951
                : null
952
            }
953
            onClick={(data, event) => {
954
              // @ts-ignore
955
              normalizeEvent(event.srcEvent, viewport);
×
956
              const res = EditorLayerUtils.onClick(data, event, {
×
957
                editorMenuActive,
958
                editor,
959
                onLayerClick,
960
                setSelectedFeature,
961
                mapIndex: index
962
              });
963
              if (res) return;
×
964

965
              visStateActions.onLayerClick(data);
×
966
            }}
967
            onError={this._onDeckError}
968
            ref={comp => {
969
              // @ts-ignore
970
              if (comp && comp.deck && !this._deck) {
35✔
971
                // @ts-ignore
972
                this._deck = comp.deck;
1✔
973
              }
974
            }}
975
            onWebGLInitialized={gl => this._onDeckInitialized(gl)}
×
976
            onAfterRender={() => {
977
              if (typeof deckRenderCallbacks?.onDeckAfterRender === 'function') {
×
978
                deckRenderCallbacks.onDeckAfterRender(allDeckGlProps);
×
979
              }
980

981
              const anyActiveLayerLoading = areAnyDeckLayersLoading(allDeckGlProps.layers);
×
982
              if (anyActiveLayerLoading !== this.anyActiveLayerLoading) {
×
983
                this._onLayerLoadingStateChange();
×
984
                this.anyActiveLayerLoading = anyActiveLayerLoading;
×
985
              }
986
            }}
987
          >
988
            {children}
989
          </DeckGL>
990
        </div>
991
      );
992
    }
993

994
    _updateMapboxLayers() {
995
      const mapboxLayers = this.mapboxLayersSelector(this.props);
×
996
      if (!Object.keys(mapboxLayers).length && !Object.keys(this.previousLayers).length) {
×
997
        return;
×
998
      }
999

1000
      updateMapboxLayers(this._map, mapboxLayers, this.previousLayers);
×
1001

1002
      this.previousLayers = mapboxLayers;
×
1003
    }
1004

1005
    _renderMapboxOverlays() {
1006
      if (this._map && this._map.isStyleLoaded()) {
29!
1007
        this._updateMapboxLayers();
×
1008
      }
1009
    }
1010
    _onViewportChangePropagateDebounced = debounce(() => {
25✔
1011
      const viewState = this.context?.getInternalViewState(this.props.index);
×
1012
      onViewPortChange(
×
1013
        viewState,
1014
        this.props.mapStateActions.updateMap,
1015
        this.props.onViewStateChange,
1016
        this.props.primary,
1017
        this.props.index
1018
      );
1019
    }, DEBOUNCE_VIEWPORT_PROPAGATE);
1020

1021
    _onViewportChange = viewport => {
25✔
1022
      const {viewState} = viewport;
×
1023
      if (this.props.isExport) {
×
1024
        // Image export map shouldn't be interactive (otherwise this callback can
1025
        // lead to inadvertent changes to the state of the main map)
1026
        return;
×
1027
      }
1028
      const {setInternalViewState} = this.context;
×
1029
      setInternalViewState(viewState, this.props.index);
×
1030
      this._onViewportChangePropagateDebounced();
×
1031
    };
1032

1033
    _onLayerHoverDebounced = debounce((data, index) => {
25✔
1034
      this.props.visStateActions.onLayerHover(data, index);
×
1035
    }, DEBOUNCE_MOUSE_MOVE_PROPAGATE);
1036

1037
    _onMouseMoveDebounced = debounce((event, viewport) => {
25✔
1038
      this.props.visStateActions.onMouseMove(normalizeEvent(event, viewport));
×
1039
    }, DEBOUNCE_MOUSE_MOVE_PROPAGATE);
1040

1041
    _onLayerLoadingStateChange = debounce(() => {
25✔
1042
      // trigger loading indicator update without any change to update UI
1043
      this.props.visStateActions.setLoadingIndicator({change: 0});
×
1044
    }, DEBOUNCE_LOADING_STATE_PROPAGATE);
1045

1046
    _toggleMapControl = panelId => {
25✔
1047
      const {index, uiStateActions} = this.props;
2✔
1048

1049
      uiStateActions.toggleMapControl(panelId, Number(index));
2✔
1050
    };
1051

1052
    /* eslint-disable complexity */
1053
    _renderMap() {
1054
      const {
1055
        visState,
1056
        mapState,
1057
        mapStyle,
1058
        mapStateActions,
1059
        MapComponent = Map,
×
1060
        mapboxApiAccessToken,
1061
        // mapboxApiUrl,
1062
        mapControls,
1063
        isExport,
1064
        locale,
1065
        uiStateActions,
1066
        visStateActions,
1067
        index,
1068
        primary,
1069
        bottomMapContainerProps,
1070
        topMapContainerProps,
1071
        theme,
1072
        datasetAttributions = [],
×
1073
        containerId = 0,
13✔
1074
        isLoadingIndicatorVisible,
1075
        activeSidePanel,
1076
        sidePanelWidth
1077
      } = this.props;
29✔
1078

1079
      const {layers, datasets, editor, interactionConfig} = visState;
29✔
1080

1081
      const layersToRender = this.layersToRenderSelector(this.props);
29✔
1082
      const layersForDeck = this.layersForDeckSelector(this.props);
29✔
1083

1084
      // Current style can be a custom style, from which we pull the mapbox API acccess token
1085
      const currentStyle = mapStyle.mapStyles?.[mapStyle.styleType];
29✔
1086
      const baseMapLibraryName = getBaseMapLibrary(currentStyle);
29✔
1087
      const baseMapLibraryConfig =
1088
        getApplicationConfig().baseMapLibraryConfig?.[baseMapLibraryName];
29✔
1089

1090
      const internalViewState = this.context?.getInternalViewState(index);
29✔
1091
      const mapProps = {
29✔
1092
        ...internalViewState,
1093
        preserveDrawingBuffer: true,
1094
        mapboxAccessToken: currentStyle?.accessToken || mapboxApiAccessToken,
58✔
1095
        // baseApiUrl: mapboxApiUrl,
1096
        mapLib: baseMapLibraryConfig.getMapLib(),
1097
        transformRequest:
1098
          this.props.transformRequest ||
58✔
1099
          transformRequest(currentStyle?.accessToken || mapboxApiAccessToken)
58✔
1100
      };
1101

1102
      const hasGeocoderLayer = Boolean(layers.find(l => l.id === GEOCODER_LAYER_ID));
29✔
1103
      const isSplit = Boolean(mapState.isSplit);
29✔
1104

1105
      const deck = this._renderDeckOverlay(layersForDeck, {
29✔
1106
        primaryMap: true,
1107
        isInteractive: true,
1108
        children: (
1109
          <MapComponent
1110
            key={`bottom-${baseMapLibraryName}`}
1111
            {...mapProps}
1112
            mapStyle={mapStyle.bottomMapStyle ?? EMPTY_MAPBOX_STYLE}
58✔
1113
            {...bottomMapContainerProps}
1114
            ref={this._setMapRef}
1115
          />
1116
        )
1117
      });
1118
      if (!deck) {
29!
1119
        // deckOverlay can be null if onDeckRender returns null
1120
        // in this case we don't want to render the map
1121
        return null;
×
1122
      }
1123
      return (
29✔
1124
        <>
1125
          <MapControl
1126
            mapState={mapState}
1127
            datasets={datasets}
1128
            availableLocales={LOCALE_CODES_ARRAY}
1129
            dragRotate={mapState.dragRotate}
1130
            isSplit={isSplit}
1131
            primary={Boolean(primary)}
1132
            isExport={isExport}
1133
            layers={layers}
1134
            layersToRender={layersToRender}
1135
            mapIndex={index || 0}
53✔
1136
            mapControls={mapControls}
1137
            readOnly={this.props.readOnly}
1138
            scale={mapState.scale || 1}
58✔
1139
            logoComponent={this.props.logoComponent}
1140
            top={
1141
              interactionConfig.geocoder && interactionConfig.geocoder.enabled
87!
1142
                ? theme.mapControlTop
1143
                : 0
1144
            }
1145
            editor={editor}
1146
            locale={locale}
1147
            onTogglePerspective={mapStateActions.togglePerspective}
1148
            onToggleSplitMap={mapStateActions.toggleSplitMap}
1149
            onMapToggleLayer={this._handleMapToggleLayer}
1150
            onToggleMapControl={this._toggleMapControl}
1151
            onToggleSplitMapViewport={mapStateActions.toggleSplitMapViewport}
1152
            onSetEditorMode={visStateActions.setEditorMode}
1153
            onSetLocale={uiStateActions.setLocale}
1154
            onToggleEditorVisibility={visStateActions.toggleEditorVisibility}
1155
            onLayerVisConfigChange={visStateActions.layerVisConfigChange}
1156
            mapHeight={mapState.height}
1157
            setMapControlSettings={uiStateActions.setMapControlSettings}
1158
            activeSidePanel={activeSidePanel}
1159
          />
1160
          {isSplitSelector(this.props) && <Droppable containerId={containerId} />}
38✔
1161

1162
          {deck}
1163
          {this._renderMapboxOverlays()}
1164
          <Editor
1165
            index={index || 0}
53✔
1166
            datasets={datasets}
1167
            editor={editor}
1168
            filters={this.polygonFiltersSelector(this.props)}
1169
            layers={layers}
1170
            onDeleteFeature={visStateActions.deleteFeature}
1171
            onSelect={visStateActions.setSelectedFeature}
1172
            onTogglePolygonFilter={visStateActions.setPolygonFilterLayer}
1173
            onSetEditorMode={visStateActions.setEditorMode}
1174
            style={{
1175
              pointerEvents: 'all',
1176
              position: 'absolute',
1177
              display: editor.visible ? 'block' : 'none'
29!
1178
            }}
1179
          />
1180
          {this.props.children}
1181
          {mapStyle.topMapStyle ? (
29!
1182
            <MapComponent
1183
              key={`top-${baseMapLibraryName}`}
1184
              viewState={internalViewState}
1185
              mapStyle={mapStyle.topMapStyle}
1186
              style={MAP_STYLE.top}
1187
              mapboxAccessToken={mapProps.mapboxAccessToken}
1188
              transformRequest={mapProps.transformRequest}
1189
              mapLib={baseMapLibraryConfig.getMapLib()}
1190
              {...topMapContainerProps}
1191
            />
1192
          ) : null}
1193

1194
          {hasGeocoderLayer
29!
1195
            ? this._renderDeckOverlay(
1196
                {[GEOCODER_LAYER_ID]: hasGeocoderLayer},
1197
                {primaryMap: false, isInteractive: false}
1198
              )
1199
            : null}
1200
          {this._renderMapPopover()}
1201
          {primary !== isSplit ? (
29✔
1202
            <LoadingIndicator
1203
              isVisible={Boolean(isLoadingIndicatorVisible || this.anyActiveLayerLoading)}
42✔
1204
              activeSidePanel={Boolean(activeSidePanel)}
1205
              sidePanelWidth={sidePanelWidth}
1206
            />
1207
          ) : null}
1208
          {this.props.primary ? (
29✔
1209
            <Attribution
1210
              showBaseMapLibLogo={this.state.showBaseMapAttribution}
1211
              showOsmBasemapAttribution={true}
1212
              datasetAttributions={datasetAttributions}
1213
              baseMapLibraryConfig={baseMapLibraryConfig}
1214
            />
1215
          ) : null}
1216
        </>
1217
      );
1218
    }
1219

1220
    render() {
1221
      const {visState, mapStyle} = this.props;
29✔
1222
      const mapContent = this._renderMap();
29✔
1223
      if (!mapContent) {
29!
1224
        // mapContent can be null if onDeckRender returns null
1225
        // in this case we don't want to render the map
1226
        return null;
×
1227
      }
1228

1229
      const currentStyle = mapStyle.mapStyles?.[mapStyle.styleType];
29✔
1230
      const baseMapLibraryName = getBaseMapLibrary(currentStyle);
29✔
1231
      const baseMapLibraryConfig =
1232
        getApplicationConfig().baseMapLibraryConfig?.[baseMapLibraryName];
29✔
1233

1234
      return (
29✔
1235
        <StyledMap
1236
          ref={this._ref}
1237
          style={this.styleSelector(this.props)}
1238
          onContextMenu={event => event.preventDefault()}
×
1239
          $mixBlendMode={visState.overlayBlending}
1240
          $mapLibCssClass={baseMapLibraryConfig.mapLibCssClass}
1241
        >
1242
          {mapContent}
1243
        </StyledMap>
1244
      );
1245
    }
1246
  }
1247

1248
  return withTheme(MapContainer);
14✔
1249
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc