Skip to content

Commit 2785a32

Browse files
committed
[v8] react-mapbox module (#2467)
1 parent 8885f5f commit 2785a32

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+3969
-7
lines changed

modules/main/src/mapbox-legacy/components/fullscreen-control.tsx renamed to modules/main/src/mapbox-legacy/components/fullscreen-control.ts

File renamed without changes.

modules/react-mapbox/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"dependencies": {
3333
},
3434
"devDependencies": {
35-
"mapbox-gl": "3.9.0"
35+
"mapbox-gl": "^3.9.0"
3636
},
3737
"peerDependencies": {
3838
"mapbox-gl": ">=3.5.0",
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as React from 'react';
2+
import {useEffect, memo} from 'react';
3+
import {applyReactStyle} from '../utils/apply-react-style';
4+
import {useControl} from './use-control';
5+
6+
import type {ControlPosition, AttributionControlOptions} from '../types/lib';
7+
8+
export type AttributionControlProps = AttributionControlOptions & {
9+
/** Placement of the control relative to the map. */
10+
position?: ControlPosition;
11+
/** CSS style override, applied to the control's container */
12+
style?: React.CSSProperties;
13+
};
14+
15+
function _AttributionControl(props: AttributionControlProps) {
16+
const ctrl = useControl(({mapLib}) => new mapLib.AttributionControl(props), {
17+
position: props.position
18+
});
19+
20+
useEffect(() => {
21+
applyReactStyle(ctrl._container, props.style);
22+
}, [props.style]);
23+
24+
return null;
25+
}
26+
27+
export const AttributionControl = memo(_AttributionControl);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/* global document */
2+
import * as React from 'react';
3+
import {useEffect, memo} from 'react';
4+
import {applyReactStyle} from '../utils/apply-react-style';
5+
import {useControl} from './use-control';
6+
7+
import type {ControlPosition, FullscreenControlOptions} from '../types/lib';
8+
9+
export type FullscreenControlProps = Omit<FullscreenControlOptions, 'container'> & {
10+
/** Id of the DOM element which should be made full screen. By default, the map container
11+
* element will be made full screen. */
12+
containerId?: string;
13+
/** Placement of the control relative to the map. */
14+
position?: ControlPosition;
15+
/** CSS style override, applied to the control's container */
16+
style?: React.CSSProperties;
17+
};
18+
19+
function _FullscreenControl(props: FullscreenControlProps) {
20+
const ctrl = useControl(
21+
({mapLib}) =>
22+
new mapLib.FullscreenControl({
23+
container: props.containerId && document.getElementById(props.containerId)
24+
}),
25+
{position: props.position}
26+
);
27+
28+
useEffect(() => {
29+
applyReactStyle(ctrl._controlContainer, props.style);
30+
}, [props.style]);
31+
32+
return null;
33+
}
34+
35+
export const FullscreenControl = memo(_FullscreenControl);
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import * as React from 'react';
2+
import {useImperativeHandle, useRef, useEffect, forwardRef, memo} from 'react';
3+
import {applyReactStyle} from '../utils/apply-react-style';
4+
import {useControl} from './use-control';
5+
6+
import type {
7+
ControlPosition,
8+
GeolocateControlInstance,
9+
GeolocateControlOptions
10+
} from '../types/lib';
11+
import type {GeolocateEvent, GeolocateResultEvent, GeolocateErrorEvent} from '../types/events';
12+
13+
export type GeolocateControlProps = GeolocateControlOptions & {
14+
/** Placement of the control relative to the map. */
15+
position?: ControlPosition;
16+
/** CSS style override, applied to the control's container */
17+
style?: React.CSSProperties;
18+
19+
/** Called on each Geolocation API position update that returned as success. */
20+
onGeolocate?: (e: GeolocateResultEvent) => void;
21+
/** Called on each Geolocation API position update that returned as an error. */
22+
onError?: (e: GeolocateErrorEvent) => void;
23+
/** Called on each Geolocation API position update that returned as success but user position
24+
* is out of map `maxBounds`. */
25+
onOutOfMaxBounds?: (e: GeolocateResultEvent) => void;
26+
/** Called when the GeolocateControl changes to the active lock state. */
27+
onTrackUserLocationStart?: (e: GeolocateEvent) => void;
28+
/** Called when the GeolocateControl changes to the background state. */
29+
onTrackUserLocationEnd?: (e: GeolocateEvent) => void;
30+
};
31+
32+
function _GeolocateControl(props: GeolocateControlProps, ref: React.Ref<GeolocateControlInstance>) {
33+
const thisRef = useRef({props});
34+
35+
const ctrl = useControl(
36+
({mapLib}) => {
37+
const gc = new mapLib.GeolocateControl(props);
38+
39+
// Hack: fix GeolocateControl reuse
40+
// When using React strict mode, the component is mounted twice.
41+
// GeolocateControl's UI creation is asynchronous. Removing and adding it back causes the UI to be initialized twice.
42+
const setupUI = gc._setupUI.bind(gc);
43+
gc._setupUI = args => {
44+
if (!gc._container.hasChildNodes()) {
45+
setupUI(args);
46+
}
47+
};
48+
49+
gc.on('geolocate', e => {
50+
thisRef.current.props.onGeolocate?.(e as GeolocateResultEvent);
51+
});
52+
gc.on('error', e => {
53+
thisRef.current.props.onError?.(e as GeolocateErrorEvent);
54+
});
55+
gc.on('outofmaxbounds', e => {
56+
thisRef.current.props.onOutOfMaxBounds?.(e as GeolocateResultEvent);
57+
});
58+
gc.on('trackuserlocationstart', e => {
59+
thisRef.current.props.onTrackUserLocationStart?.(e as GeolocateEvent);
60+
});
61+
gc.on('trackuserlocationend', e => {
62+
thisRef.current.props.onTrackUserLocationEnd?.(e as GeolocateEvent);
63+
});
64+
65+
return gc;
66+
},
67+
{position: props.position}
68+
);
69+
70+
thisRef.current.props = props;
71+
72+
useImperativeHandle(ref, () => ctrl, []);
73+
74+
useEffect(() => {
75+
applyReactStyle(ctrl._container, props.style);
76+
}, [props.style]);
77+
78+
return null;
79+
}
80+
81+
export const GeolocateControl = memo(forwardRef(_GeolocateControl));
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import {useContext, useEffect, useMemo, useState, useRef} from 'react';
2+
import {MapContext} from './map';
3+
import assert from '../utils/assert';
4+
import {deepEqual} from '../utils/deep-equal';
5+
6+
import type {MapInstance, CustomLayerInterface} from '../types/lib';
7+
import type {AnyLayer} from '../types/style-spec';
8+
9+
// Omiting property from a union type, see
10+
// https://github.com/microsoft/TypeScript/issues/39556#issuecomment-656925230
11+
type OptionalId<T> = T extends {id: string} ? Omit<T, 'id'> & {id?: string} : T;
12+
type OptionalSource<T> = T extends {source: string} ? Omit<T, 'source'> & {source?: string} : T;
13+
14+
export type LayerProps = (OptionalSource<OptionalId<AnyLayer>> | CustomLayerInterface) & {
15+
/** If set, the layer will be inserted before the specified layer */
16+
beforeId?: string;
17+
};
18+
19+
/* eslint-disable complexity, max-statements */
20+
function updateLayer(map: MapInstance, id: string, props: LayerProps, prevProps: LayerProps) {
21+
assert(props.id === prevProps.id, 'layer id changed');
22+
assert(props.type === prevProps.type, 'layer type changed');
23+
24+
if (props.type === 'custom' || prevProps.type === 'custom') {
25+
return;
26+
}
27+
28+
// @ts-ignore filter does not exist in some Layer types
29+
const {layout = {}, paint = {}, filter, minzoom, maxzoom, beforeId} = props;
30+
31+
if (beforeId !== prevProps.beforeId) {
32+
map.moveLayer(id, beforeId);
33+
}
34+
if (layout !== prevProps.layout) {
35+
const prevLayout = prevProps.layout || {};
36+
for (const key in layout) {
37+
if (!deepEqual(layout[key], prevLayout[key])) {
38+
map.setLayoutProperty(id, key as any, layout[key]);
39+
}
40+
}
41+
for (const key in prevLayout) {
42+
if (!layout.hasOwnProperty(key)) {
43+
map.setLayoutProperty(id, key as any, undefined);
44+
}
45+
}
46+
}
47+
if (paint !== prevProps.paint) {
48+
const prevPaint = prevProps.paint || {};
49+
for (const key in paint) {
50+
if (!deepEqual(paint[key], prevPaint[key])) {
51+
map.setPaintProperty(id, key as any, paint[key]);
52+
}
53+
}
54+
for (const key in prevPaint) {
55+
if (!paint.hasOwnProperty(key)) {
56+
map.setPaintProperty(id, key as any, undefined);
57+
}
58+
}
59+
}
60+
61+
// @ts-ignore filter does not exist in some Layer types
62+
if (!deepEqual(filter, prevProps.filter)) {
63+
map.setFilter(id, filter);
64+
}
65+
if (minzoom !== prevProps.minzoom || maxzoom !== prevProps.maxzoom) {
66+
map.setLayerZoomRange(id, minzoom, maxzoom);
67+
}
68+
}
69+
70+
function createLayer(map: MapInstance, id: string, props: LayerProps) {
71+
// @ts-ignore
72+
if (map.style && map.style._loaded && (!('source' in props) || map.getSource(props.source))) {
73+
const options: LayerProps = {...props, id};
74+
delete options.beforeId;
75+
76+
// @ts-ignore
77+
map.addLayer(options, props.beforeId);
78+
}
79+
}
80+
81+
/* eslint-enable complexity, max-statements */
82+
83+
let layerCounter = 0;
84+
85+
export function Layer(props: LayerProps) {
86+
const map = useContext(MapContext).map.getMap();
87+
const propsRef = useRef(props);
88+
const [, setStyleLoaded] = useState(0);
89+
90+
const id = useMemo(() => props.id || `jsx-layer-${layerCounter++}`, []);
91+
92+
useEffect(() => {
93+
if (map) {
94+
const forceUpdate = () => setStyleLoaded(version => version + 1);
95+
map.on('styledata', forceUpdate);
96+
forceUpdate();
97+
98+
return () => {
99+
map.off('styledata', forceUpdate);
100+
// @ts-ignore
101+
if (map.style && map.style._loaded && map.getLayer(id)) {
102+
map.removeLayer(id);
103+
}
104+
};
105+
}
106+
return undefined;
107+
}, [map]);
108+
109+
// @ts-ignore
110+
const layer = map && map.style && map.getLayer(id);
111+
if (layer) {
112+
try {
113+
updateLayer(map, id, props, propsRef.current);
114+
} catch (error) {
115+
console.warn(error); // eslint-disable-line
116+
}
117+
} else {
118+
createLayer(map, id, props);
119+
}
120+
121+
// Store last rendered props
122+
propsRef.current = props;
123+
124+
return null;
125+
}

0 commit comments

Comments
 (0)