1 | import * as React from 'react';
|
2 | import { Props as FeatureProps } from './feature';
|
3 | import { generateID } from './util/uid';
|
4 | import { LayerCommonProps, Props as LayerProps } from './layer';
|
5 | import { Map } from 'mapbox-gl';
|
6 |
|
7 | export interface EnhancedLayerProps {
|
8 | id?: string;
|
9 | map: Map;
|
10 | }
|
11 |
|
12 | export type OwnProps = EnhancedLayerProps & LayerCommonProps;
|
13 |
|
14 | type LayerChildren = React.ReactElement<FeatureProps> | undefined;
|
15 |
|
16 | export function layerMouseTouchEvents(
|
17 | WrappedComponent: React.ComponentClass<LayerProps>
|
18 | ) {
|
19 | return class EnhancedLayer extends React.Component<OwnProps> {
|
20 | public hover: number[] = [];
|
21 | public draggedChildren:
|
22 | | Array<React.ReactElement<FeatureProps>>
|
23 | | undefined = undefined;
|
24 |
|
25 | public id: string = this.props.id || `layer-${generateID()}`;
|
26 |
|
27 | public getChildren = () =>
|
28 | ([] as LayerChildren[])
|
29 | .concat(this.props.children)
|
30 | .filter(
|
31 | (el): el is React.ReactElement<FeatureProps> =>
|
32 | typeof el !== 'undefined'
|
33 | );
|
34 | public getChildFromId = (
|
35 | children: Array<React.ReactElement<FeatureProps>>,
|
36 | id: number
|
37 | ) => children[id];
|
38 |
|
39 | public areFeaturesDraggable = (
|
40 | children: Array<React.ReactElement<FeatureProps>>,
|
41 | featureIds: number[] = this.hover
|
42 | ) =>
|
43 | !!featureIds
|
44 | .map(
|
45 | id =>
|
46 | this.getChildFromId(children, id)
|
47 | ? this.getChildFromId(children, id)!.props.draggable
|
48 | : false
|
49 | )
|
50 | .filter(Boolean).length;
|
51 |
|
52 |
|
53 | public onClick = (evt: any) => {
|
54 | const features = evt.features as Array<
|
55 | GeoJSON.Feature<GeoJSON.GeometryObject, { id: number }>
|
56 | >;
|
57 | const children = this.getChildren();
|
58 |
|
59 | const { map } = this.props;
|
60 |
|
61 | if (features) {
|
62 | features.forEach(feature => {
|
63 | const { id } = feature.properties;
|
64 | if (children) {
|
65 | const child = this.getChildFromId(children, id);
|
66 |
|
67 | const onClick = child && child.props.onClick;
|
68 | if (onClick) {
|
69 | onClick({ ...evt, feature, map });
|
70 | }
|
71 | }
|
72 | });
|
73 | }
|
74 | };
|
75 |
|
76 |
|
77 | public onMouseEnter = (evt: any) => {
|
78 | const children = this.getChildren();
|
79 |
|
80 | const { map } = this.props;
|
81 | this.hover = [];
|
82 |
|
83 | evt.features.forEach(
|
84 | (feature: GeoJSON.Feature<GeoJSON.GeometryObject, { id: number }>) => {
|
85 | const { id } = feature.properties;
|
86 | const child = this.getChildFromId(children, id);
|
87 | this.hover.push(id);
|
88 |
|
89 | const onMouseEnter = child && child.props.onMouseEnter;
|
90 | if (onMouseEnter) {
|
91 | onMouseEnter({ ...evt, feature, map });
|
92 | }
|
93 | }
|
94 | );
|
95 |
|
96 | if (this.areFeaturesDraggable(children)) {
|
97 | map.dragPan.disable();
|
98 | }
|
99 | };
|
100 |
|
101 |
|
102 | public onMouseLeave = (evt: any) => {
|
103 | const children = this.getChildren();
|
104 | const { map } = this.props;
|
105 | if (this.areFeaturesDraggable(children)) {
|
106 | map.dragPan.enable();
|
107 | }
|
108 |
|
109 | this.hover.forEach(id => {
|
110 | const child = this.getChildFromId(children, id);
|
111 | const onMouseLeave = child && child.props.onMouseLeave;
|
112 | if (onMouseLeave) {
|
113 | onMouseLeave({ ...evt, map });
|
114 | }
|
115 | });
|
116 |
|
117 | if (!this.draggedChildren) {
|
118 | this.hover = [];
|
119 | }
|
120 | };
|
121 |
|
122 | public onMouseDown = () => {
|
123 |
|
124 | if (this.hover.length) {
|
125 | this.onFeatureDown('mousedown');
|
126 | }
|
127 | };
|
128 |
|
129 |
|
130 | public onTouchStart = (evt: any) => {
|
131 |
|
132 | this.hover = evt.features.map((feature: any) => feature.properties.id);
|
133 |
|
134 | if (this.hover.length) {
|
135 | this.onFeatureDown('touchstart');
|
136 | }
|
137 | };
|
138 |
|
139 | public onFeatureDown = (startEvent: string) => {
|
140 | const moveEvent = startEvent === 'mousedown' ? 'mousemove' : 'touchmove';
|
141 | const endEvent = startEvent === 'mousedown' ? 'mouseup' : 'touchend';
|
142 | const { map } = this.props;
|
143 |
|
144 | map.once(moveEvent, this.onFeatureDragStart);
|
145 | map.on(moveEvent, this.onFeatureDrag);
|
146 |
|
147 |
|
148 | map.once(endEvent, (evt: any) => {
|
149 | map.off(moveEvent, this.onFeatureDragStart);
|
150 | map.off(moveEvent, this.onFeatureDrag);
|
151 | this.onFeatureDragEnd(evt);
|
152 | });
|
153 | };
|
154 |
|
155 |
|
156 | public onFeatureDragStart = (evt: any) => {
|
157 | const { map } = this.props;
|
158 | const children = this.getChildren();
|
159 |
|
160 | this.hover.forEach(id => {
|
161 | const child = this.getChildFromId(children, id);
|
162 | if (child && !child.props.draggable) {
|
163 | return;
|
164 | }
|
165 |
|
166 | const onDragStart = child && child.props.onDragStart;
|
167 | if (onDragStart) {
|
168 | onDragStart({ ...evt, map });
|
169 | }
|
170 | });
|
171 | };
|
172 |
|
173 |
|
174 | public onFeatureDrag = (evt: any) => {
|
175 | const children = this.getChildren();
|
176 | const { map } = this.props;
|
177 | const { lngLat: { lng, lat } } = evt;
|
178 | this.draggedChildren = [];
|
179 |
|
180 | this.hover.forEach(id => {
|
181 | const child = this.getChildFromId(children, id);
|
182 | const onDrag = child && child.props.onDrag;
|
183 |
|
184 |
|
185 | if (child && child.props.draggable) {
|
186 | this.draggedChildren!.push(
|
187 | React.cloneElement(child, {
|
188 | coordinates: [lng, lat]
|
189 | })
|
190 | );
|
191 |
|
192 | if (onDrag) {
|
193 | onDrag({ ...evt, map });
|
194 | }
|
195 | }
|
196 | });
|
197 |
|
198 | this.forceUpdate();
|
199 | };
|
200 |
|
201 |
|
202 | public onFeatureDragEnd = (evt: any) => {
|
203 | const { map } = this.props;
|
204 | const children = this.getChildren();
|
205 |
|
206 | this.hover.forEach(id => {
|
207 | const child = this.getChildFromId(children, id);
|
208 | const onDragEnd = child && child.props.onDragEnd;
|
209 |
|
210 | if (onDragEnd && child!.props.draggable && this.draggedChildren) {
|
211 | onDragEnd({ ...evt, map });
|
212 | }
|
213 | });
|
214 |
|
215 | this.draggedChildren = undefined;
|
216 | };
|
217 |
|
218 | public componentDidMount() {
|
219 | const { map } = this.props;
|
220 |
|
221 | map.on('click', this.id, this.onClick);
|
222 | map.on('mouseenter', this.id, this.onMouseEnter);
|
223 | map.on('mouseleave', this.id, this.onMouseLeave);
|
224 | map.on('mousedown', this.id, this.onMouseDown);
|
225 | map.on('touchstart', this.id, this.onTouchStart);
|
226 | }
|
227 |
|
228 | public componentWillUnmount() {
|
229 | const { map } = this.props;
|
230 |
|
231 | map.off('click', this.onClick);
|
232 | map.off('mouseenter', this.onMouseEnter);
|
233 | map.off('mouseleave', this.onMouseLeave);
|
234 | map.off('mousedown', this.onMouseDown);
|
235 | map.off('touchstart', this.onTouchStart);
|
236 | }
|
237 |
|
238 | public render() {
|
239 | return (
|
240 | <WrappedComponent
|
241 | {...this.props}
|
242 | id={this.id}
|
243 | map={this.props.map}
|
244 | draggedChildren={this.draggedChildren}
|
245 | />
|
246 | );
|
247 | }
|
248 | };
|
249 | }
|
250 |
|
251 | export default layerMouseTouchEvents;
|