ReactJS maps without external dependencies - performance-first React-centric extendable map engine
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
Wrapper component that makes child elements draggable on the map with mouse and touch support.
/**
* Makes child elements draggable on the map with touch and mouse support
* @param props - Draggable configuration and event handlers
* @returns JSX.Element representing the draggable container
*/
function Draggable(props: DraggableProps): JSX.Element;
interface DraggableProps extends PigeonProps {
// Styling
className?: string;
style?: React.CSSProperties;
// Content
children?: React.ReactNode;
// Drag event handlers
onDragStart?: (anchor: Point) => void;
onDragMove?: (anchor: Point) => void;
onDragEnd?: (anchor: Point) => void;
}Usage Examples:
import React, { useState } from "react";
import { Map, Draggable, Marker } from "pigeon-maps";
// Basic draggable marker
function DraggableMarker() {
const [position, setPosition] = useState([50.879, 4.6997]);
return (
<Map height={400} center={position} zoom={11}>
<Draggable
anchor={position}
onDragEnd={(newPosition) => {
setPosition(newPosition);
}}
>
<Marker anchor={position} />
</Draggable>
</Map>
);
}
// Draggable custom content
function DraggableContent() {
const [position, setPosition] = useState([50.879, 4.6997]);
return (
<Map height={400} center={[50.879, 4.6997]} zoom={11}>
<Draggable
anchor={position}
onDragStart={(anchor) => {
console.log('Started dragging at:', anchor);
}}
onDragMove={(anchor) => {
console.log('Dragging to:', anchor);
}}
onDragEnd={(anchor) => {
console.log('Drag ended at:', anchor);
setPosition(anchor);
}}
>
<div style={{
background: 'white',
border: '2px solid #333',
borderRadius: '8px',
padding: '12px',
cursor: 'grab'
}}>
Drag me!
</div>
</Draggable>
</Map>
);
}function MultipleDraggables() {
const [markers, setMarkers] = useState([
{ id: 1, position: [50.879, 4.6997], name: 'Marker 1' },
{ id: 2, position: [50.885, 4.7050], name: 'Marker 2' },
{ id: 3, position: [50.875, 4.6900], name: 'Marker 3' }
]);
const updateMarkerPosition = (id, newPosition) => {
setMarkers(markers.map(marker =>
marker.id === id
? { ...marker, position: newPosition }
: marker
));
};
return (
<Map height={400} center={[50.879, 4.6997]} zoom={11}>
{markers.map(marker => (
<Draggable
key={marker.id}
anchor={marker.position}
onDragEnd={(newPosition) => {
updateMarkerPosition(marker.id, newPosition);
}}
>
<Marker anchor={marker.position} payload={marker} />
</Draggable>
))}
</Map>
);
}The draggable component provides three event handlers for the complete drag lifecycle:
/**
* Called when drag operation begins
* @param anchor - Geographic coordinates where drag started
*/
onDragStart?: (anchor: Point) => void;
/**
* Called continuously during drag operation
* @param anchor - Current geographic coordinates during drag
*/
onDragMove?: (anchor: Point) => void;
/**
* Called when drag operation ends
* @param anchor - Final geographic coordinates where drag ended
*/
onDragEnd?: (anchor: Point) => void;All drag events provide geographic coordinates (Point as [lat, lng]) rather than pixel coordinates, automatically handling the conversion from screen pixels to map coordinates.
function TrackingDraggable() {
const [dragPath, setDragPath] = useState([]);
const [isDragging, setIsDragging] = useState(false);
return (
<Map height={400} center={[50.879, 4.6997]} zoom={11}>
<Draggable
anchor={[50.879, 4.6997]}
onDragStart={(anchor) => {
setIsDragging(true);
setDragPath([anchor]);
}}
onDragMove={(anchor) => {
setDragPath(path => [...path, anchor]);
}}
onDragEnd={(anchor) => {
setIsDragging(false);
console.log('Drag path:', dragPath);
}}
>
<div style={{
background: isDragging ? 'red' : 'blue',
width: 20,
height: 20,
borderRadius: '50%'
}} />
</Draggable>
</Map>
);
}The draggable component automatically:
// Automatic cursor styling
cursor: isDragging ? 'grabbing' : 'grab'The component automatically updates the cursor to provide visual feedback:
grab: When hoverable but not dragginggrabbing: During active drag operationfunction StyledDraggable() {
return (
<Map height={400} center={[50.879, 4.6997]} zoom={11}>
<Draggable
anchor={[50.879, 4.6997]}
style={{
transform: 'scale(1.1)', // Make slightly larger
transition: 'transform 0.2s' // Smooth scaling
}}
className="custom-draggable"
>
<div>Styled draggable content</div>
</Draggable>
</Map>
);
}// Default CSS class
className="pigeon-drag-block"
// Custom additional classes
className="pigeon-drag-block my-custom-class"The component automatically adds the pigeon-drag-block class, which:
function ConstrainedDraggable() {
const [position, setPosition] = useState([50.879, 4.6997]);
const bounds = {
north: 50.89,
south: 50.87,
east: 4.71,
west: 4.69
};
const constrainPosition = (newPosition) => {
const [lat, lng] = newPosition;
return [
Math.max(bounds.south, Math.min(bounds.north, lat)),
Math.max(bounds.west, Math.min(bounds.east, lng))
];
};
return (
<Map height={400} center={[50.879, 4.6997]} zoom={11}>
<Draggable
anchor={position}
onDragEnd={(newPosition) => {
const constrainedPosition = constrainPosition(newPosition);
setPosition(constrainedPosition);
}}
>
<Marker anchor={position} />
</Draggable>
</Map>
);
}function SnappingDraggable() {
const [position, setPosition] = useState([50.879, 4.6997]);
const gridSize = 0.01; // Grid spacing in degrees
const snapToGrid = (position) => {
const [lat, lng] = position;
return [
Math.round(lat / gridSize) * gridSize,
Math.round(lng / gridSize) * gridSize
];
};
return (
<Map height={400} center={[50.879, 4.6997]} zoom={11}>
<Draggable
anchor={position}
onDragEnd={(newPosition) => {
const snappedPosition = snapToGrid(newPosition);
setPosition(snappedPosition);
}}
>
<Marker anchor={position} />
</Draggable>
</Map>
);
}onDragMove sparingly for performance-critical applicationsThe Draggable component integrates seamlessly with map settings:
mouseEvents and touchEvents map propsInstall with Tessl CLI
npx tessl i tessl/npm-pigeon-maps