0
# Draggable Elements
1
2
System for making map elements draggable with comprehensive touch and mouse support. The Draggable component wraps other elements and provides smooth drag interactions with geographic coordinate updates.
3
4
## Capabilities
5
6
### Draggable Component
7
8
Wrapper component that makes child elements draggable on the map with mouse and touch support.
9
10
```typescript { .api }
11
/**
12
* Makes child elements draggable on the map with touch and mouse support
13
* @param props - Draggable configuration and event handlers
14
* @returns JSX.Element representing the draggable container
15
*/
16
function Draggable(props: DraggableProps): JSX.Element;
17
18
interface DraggableProps extends PigeonProps {
19
// Styling
20
className?: string;
21
style?: React.CSSProperties;
22
23
// Content
24
children?: React.ReactNode;
25
26
// Drag event handlers
27
onDragStart?: (anchor: Point) => void;
28
onDragMove?: (anchor: Point) => void;
29
onDragEnd?: (anchor: Point) => void;
30
}
31
```
32
33
**Usage Examples:**
34
35
```tsx
36
import React, { useState } from "react";
37
import { Map, Draggable, Marker } from "pigeon-maps";
38
39
// Basic draggable marker
40
function DraggableMarker() {
41
const [position, setPosition] = useState([50.879, 4.6997]);
42
43
return (
44
<Map height={400} center={position} zoom={11}>
45
<Draggable
46
anchor={position}
47
onDragEnd={(newPosition) => {
48
setPosition(newPosition);
49
}}
50
>
51
<Marker anchor={position} />
52
</Draggable>
53
</Map>
54
);
55
}
56
57
// Draggable custom content
58
function DraggableContent() {
59
const [position, setPosition] = useState([50.879, 4.6997]);
60
61
return (
62
<Map height={400} center={[50.879, 4.6997]} zoom={11}>
63
<Draggable
64
anchor={position}
65
onDragStart={(anchor) => {
66
console.log('Started dragging at:', anchor);
67
}}
68
onDragMove={(anchor) => {
69
console.log('Dragging to:', anchor);
70
}}
71
onDragEnd={(anchor) => {
72
console.log('Drag ended at:', anchor);
73
setPosition(anchor);
74
}}
75
>
76
<div style={{
77
background: 'white',
78
border: '2px solid #333',
79
borderRadius: '8px',
80
padding: '12px',
81
cursor: 'grab'
82
}}>
83
Drag me!
84
</div>
85
</Draggable>
86
</Map>
87
);
88
}
89
```
90
91
### Multiple Draggable Elements
92
93
```tsx
94
function MultipleDraggables() {
95
const [markers, setMarkers] = useState([
96
{ id: 1, position: [50.879, 4.6997], name: 'Marker 1' },
97
{ id: 2, position: [50.885, 4.7050], name: 'Marker 2' },
98
{ id: 3, position: [50.875, 4.6900], name: 'Marker 3' }
99
]);
100
101
const updateMarkerPosition = (id, newPosition) => {
102
setMarkers(markers.map(marker =>
103
marker.id === id
104
? { ...marker, position: newPosition }
105
: marker
106
));
107
};
108
109
return (
110
<Map height={400} center={[50.879, 4.6997]} zoom={11}>
111
{markers.map(marker => (
112
<Draggable
113
key={marker.id}
114
anchor={marker.position}
115
onDragEnd={(newPosition) => {
116
updateMarkerPosition(marker.id, newPosition);
117
}}
118
>
119
<Marker anchor={marker.position} payload={marker} />
120
</Draggable>
121
))}
122
</Map>
123
);
124
}
125
```
126
127
## Drag Event Handling
128
129
### Event Lifecycle
130
131
The draggable component provides three event handlers for the complete drag lifecycle:
132
133
```typescript { .api }
134
/**
135
* Called when drag operation begins
136
* @param anchor - Geographic coordinates where drag started
137
*/
138
onDragStart?: (anchor: Point) => void;
139
140
/**
141
* Called continuously during drag operation
142
* @param anchor - Current geographic coordinates during drag
143
*/
144
onDragMove?: (anchor: Point) => void;
145
146
/**
147
* Called when drag operation ends
148
* @param anchor - Final geographic coordinates where drag ended
149
*/
150
onDragEnd?: (anchor: Point) => void;
151
```
152
153
### Coordinate Updates
154
155
All drag events provide geographic coordinates (`Point` as `[lat, lng]`) rather than pixel coordinates, automatically handling the conversion from screen pixels to map coordinates.
156
157
```tsx
158
function TrackingDraggable() {
159
const [dragPath, setDragPath] = useState([]);
160
const [isDragging, setIsDragging] = useState(false);
161
162
return (
163
<Map height={400} center={[50.879, 4.6997]} zoom={11}>
164
<Draggable
165
anchor={[50.879, 4.6997]}
166
onDragStart={(anchor) => {
167
setIsDragging(true);
168
setDragPath([anchor]);
169
}}
170
onDragMove={(anchor) => {
171
setDragPath(path => [...path, anchor]);
172
}}
173
onDragEnd={(anchor) => {
174
setIsDragging(false);
175
console.log('Drag path:', dragPath);
176
}}
177
>
178
<div style={{
179
background: isDragging ? 'red' : 'blue',
180
width: 20,
181
height: 20,
182
borderRadius: '50%'
183
}} />
184
</Draggable>
185
</Map>
186
);
187
}
188
```
189
190
## Input Handling
191
192
### Mouse Support
193
194
- **Mouse Down**: Initiates drag when clicking on the draggable element
195
- **Mouse Move**: Updates position during drag
196
- **Mouse Up**: Completes drag operation
197
198
### Touch Support
199
200
- **Touch Start**: Initiates drag when touching the draggable element
201
- **Touch Move**: Updates position during drag
202
- **Touch End**: Completes drag operation
203
204
### Event Prevention
205
206
The draggable component automatically:
207
- Prevents default browser behaviors during drag
208
- Stops event propagation to prevent map interactions
209
- Handles both mouse and touch events simultaneously
210
211
## Styling and Visual Feedback
212
213
### Cursor States
214
215
```typescript { .api }
216
// Automatic cursor styling
217
cursor: isDragging ? 'grabbing' : 'grab'
218
```
219
220
The component automatically updates the cursor to provide visual feedback:
221
- `grab`: When hoverable but not dragging
222
- `grabbing`: During active drag operation
223
224
### Custom Styling
225
226
```tsx
227
function StyledDraggable() {
228
return (
229
<Map height={400} center={[50.879, 4.6997]} zoom={11}>
230
<Draggable
231
anchor={[50.879, 4.6997]}
232
style={{
233
transform: 'scale(1.1)', // Make slightly larger
234
transition: 'transform 0.2s' // Smooth scaling
235
}}
236
className="custom-draggable"
237
>
238
<div>Styled draggable content</div>
239
</Draggable>
240
</Map>
241
);
242
}
243
```
244
245
### CSS Classes
246
247
```typescript { .api }
248
// Default CSS class
249
className="pigeon-drag-block"
250
251
// Custom additional classes
252
className="pigeon-drag-block my-custom-class"
253
```
254
255
The component automatically adds the `pigeon-drag-block` class, which:
256
- Prevents map drag interactions when interacting with the draggable element
257
- Provides a hook for custom CSS styling
258
259
## Advanced Usage
260
261
### Constrained Dragging
262
263
```tsx
264
function ConstrainedDraggable() {
265
const [position, setPosition] = useState([50.879, 4.6997]);
266
const bounds = {
267
north: 50.89,
268
south: 50.87,
269
east: 4.71,
270
west: 4.69
271
};
272
273
const constrainPosition = (newPosition) => {
274
const [lat, lng] = newPosition;
275
return [
276
Math.max(bounds.south, Math.min(bounds.north, lat)),
277
Math.max(bounds.west, Math.min(bounds.east, lng))
278
];
279
};
280
281
return (
282
<Map height={400} center={[50.879, 4.6997]} zoom={11}>
283
<Draggable
284
anchor={position}
285
onDragEnd={(newPosition) => {
286
const constrainedPosition = constrainPosition(newPosition);
287
setPosition(constrainedPosition);
288
}}
289
>
290
<Marker anchor={position} />
291
</Draggable>
292
</Map>
293
);
294
}
295
```
296
297
### Snapping to Grid
298
299
```tsx
300
function SnappingDraggable() {
301
const [position, setPosition] = useState([50.879, 4.6997]);
302
const gridSize = 0.01; // Grid spacing in degrees
303
304
const snapToGrid = (position) => {
305
const [lat, lng] = position;
306
return [
307
Math.round(lat / gridSize) * gridSize,
308
Math.round(lng / gridSize) * gridSize
309
];
310
};
311
312
return (
313
<Map height={400} center={[50.879, 4.6997]} zoom={11}>
314
<Draggable
315
anchor={position}
316
onDragEnd={(newPosition) => {
317
const snappedPosition = snapToGrid(newPosition);
318
setPosition(snappedPosition);
319
}}
320
>
321
<Marker anchor={position} />
322
</Draggable>
323
</Map>
324
);
325
}
326
```
327
328
## Performance Considerations
329
330
- Drag events are throttled to prevent excessive updates
331
- Only the final position is typically persisted to state
332
- Use `onDragMove` sparingly for performance-critical applications
333
- Consider debouncing external API calls triggered by drag events
334
- The component uses React refs to minimize re-renders during drag operations
335
336
## Integration with Map Controls
337
338
The Draggable component integrates seamlessly with map settings:
339
- Respects `mouseEvents` and `touchEvents` map props
340
- Works with all zoom levels and map transformations
341
- Automatically handles coordinate system conversions
342
- Compatible with all other overlay types