or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

controls.mddraggable.mdgeojson.mdindex.mdmap.mdmarkers-overlays.mdproviders.md

draggable.mddocs/

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