UNPKG

leaflet

Version:

JavaScript library for mobile-friendly interactive maps

188 lines (149 loc) • 5.72 kB
import {Handler} from '../../core/Handler.js'; import * as DomEvent from '../../dom/DomEvent.js'; import * as DomUtil from '../../dom/DomUtil.js'; import {Draggable} from '../../dom/Draggable.js'; import {Bounds} from '../../geometry/Bounds.js'; import {Point} from '../../geometry/Point.js'; /** @import {DragEndEvent, LeafletEvent, MarkerMoveEvent} from '../../types.js' */ /** @import {Marker} from './Marker.js' */ /** @type {Record<string, [number, number]>} */ const arrows = {ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1]}; // Used internally by Marker to make the markers draggable. export class MarkerDrag extends Handler { static initHooks = true; /** @param {Marker} marker */ constructor(marker) { super(marker._map); this._marker = marker; this.callInitHooks(); } addHooks() { const icon = /** @type {HTMLElement} */ (this._marker._icon); if (!this._draggable) { /** @type {Draggable} */ this._draggable = new Draggable(icon); } this._draggable.on({ dragstart: this._onDragStart, predrag: this._onPreDrag, drag: this._onDrag, dragend: this._onDragEnd }, this).enable(); DomEvent.on(icon, 'keydown keyup blur', this._onKey, this); icon.classList.add('leaflet-marker-draggable'); } removeHooks() { this._keyDrag = false; // disabling the Draggable below ends a keyboard drag in progress, as it does a pointer drag this._draggable.off({ dragstart: this._onDragStart, predrag: this._onPreDrag, drag: this._onDrag, dragend: this._onDragEnd }, this).disable(); DomEvent.off(this._draggable._element, 'keydown keyup blur', this._onKey, this); this._marker._icon?.classList.remove('leaflet-marker-draggable'); } /** Returns `true` if the current or last drag moved the marker, e.g. to ignore the click that ends a drag. */ moved() { return this._draggable?._moved; } /** @param {LeafletEvent & Partial<MarkerMoveEvent>} e */ _adjustPan(e) { const marker = this._marker, map = marker._map, speed = this._marker.options.autoPanSpeed, padding = this._marker.options.autoPanPadding, icon = /** @type {HTMLElement} */ (marker._icon), iconPos = DomUtil.getPosition(icon), bounds = map.getPixelBounds(), origin = map.getPixelOrigin(); const panBounds = new Bounds( bounds.min._subtract(origin).add(padding), bounds.max._subtract(origin).subtract(padding) ); if (!panBounds.contains(iconPos)) { // Compute incremental movement const movement = new Point( (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) - (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x), (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) - (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y) ).multiplyBy(speed); map.panBy(movement, {animate: false}); this._draggable._newPos._add(movement); this._draggable._startPos._add(movement); DomUtil.setPosition(icon, this._draggable._newPos); this._onDrag(e); /** @type {number} */ this._panRequest = requestAnimationFrame(this._adjustPan.bind(this, e)); } } _onDragStart() { this._oldLatLng = this._marker.getLatLng(); // When using ES6 imports it could not be set when `Popup` was not imported as well this._marker.closePopup?.(); this._marker .fire('movestart') .fire('dragstart'); } /** @param {LeafletEvent} e */ _onPreDrag(e) { if (this._marker.options.autoPan) { cancelAnimationFrame(this._panRequest); this._panRequest = requestAnimationFrame(this._adjustPan.bind(this, e)); } } /** @param {LeafletEvent & Partial<MarkerMoveEvent>} e */ _onDrag(e) { const marker = this._marker, iconPos = DomUtil.getPosition(/** @type {HTMLElement} */ (marker._icon)), latlng = marker._map.layerPointToLatLng(iconPos); // Draggable only moved the icon and dropped its rotation marker._positionElements(iconPos); marker._latlng = latlng; e.latlng = latlng; e.oldLatLng = this._oldLatLng; marker .fire('move', e) .fire('drag', e); } /** @param {DragEndEvent} e */ _onDragEnd(e) { cancelAnimationFrame(this._panRequest); delete this._oldLatLng; this._marker .fire('moveend') .fire('dragend', e); } // Arrow keys move a focused marker. Holding a key down is one drag, driven through the Draggable like a pointer // drag (so the two can't overlap), and ends on keyup or blur. /** @param {KeyboardEvent | FocusEvent} e */ _onKey(e) { const k = /** @type {KeyboardEvent} */ (e), // all but blur dir = arrows[k.key], draggable = this._draggable, {options, _map: map} = this._marker; if (e.type !== 'keydown') { if (this._keyDrag) { this._keyDrag = false; draggable.finishDrag(); } } else if (dir && k.target === draggable._element && !(k.altKey || k.ctrlKey || k.metaKey)) { e.preventDefault(); // don't scroll the page if (!this._keyDrag) { if (Draggable._dragging) { return; } Draggable._dragging = draggable; draggable._startPos = draggable._newPos = DomUtil.getPosition(draggable._element); draggable._moved = draggable._moving = this._keyDrag = true; draggable.fire('dragstart'); } // not through _updatePosition, whose predrag would start the pointer autoPan loop draggable._newPos = draggable._newPos.add(new Point(...dir).multiplyBy(k.shiftKey ? 30 : 10)); DomUtil.setPosition(draggable._element, draggable._newPos); draggable.fire('drag', {originalEvent: k}); if (options.autoPan) { map.panInside(this._marker._latlng, {padding: options.autoPanPadding, animate: false}); } } } }