UNPKG

7.09 kBTypeScriptView Raw
1import * as React from 'react';
2import { Props as FeatureProps } from './feature';
3import { generateID } from './util/uid';
4import { LayerCommonProps, Props as LayerProps } from './layer';
5import { Map } from 'mapbox-gl';
6
7export interface EnhancedLayerProps {
8 id?: string;
9 map: Map;
10}
11
12export type OwnProps = EnhancedLayerProps & LayerCommonProps;
13
14type LayerChildren = React.ReactElement<FeatureProps> | undefined;
15
16export 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 // tslint:disable-next-line:no-any
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 // tslint:disable-next-line:no-any
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 // tslint:disable-next-line:no-any
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 // User did this on a feature
124 if (this.hover.length) {
125 this.onFeatureDown('mousedown');
126 }
127 };
128
129 // tslint:disable-next-line:no-any
130 public onTouchStart = (evt: any) => {
131 // tslint:disable-next-line:no-any
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 // tslint:disable-next-line:no-any
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 // tslint:disable-next-line:no-any
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 // tslint:disable-next-line:no-any
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 // drag children if draggable
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 // tslint:disable-next-line:no-any
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
251export default layerMouseTouchEvents;