UNPKG

leaflet

Version:

JavaScript library for mobile-friendly interactive maps

127 lines (98 loc) • 3.64 kB
import {Handler} from '../../core/Handler.js'; import * as DomUtil from '../../dom/DomUtil.js'; import * as DomEvent from '../../dom/DomEvent.js'; import {LatLngBounds} from '../../geo/LatLngBounds.js'; import {Bounds} from '../../geometry/Bounds.js'; /** * @typedef {Object} BoxZoomEventProps * @property {LatLngBounds} boxZoomBounds The geographical bounds of the box the map zooms to. */ /** @import {Point} from '../../geometry/Point.js' */ // Adds shift-drag zoom interaction to the map (zoom to a selected bounding box), enabled by default. export class BoxZoomHandler extends Handler { addHooks() { DomEvent.on(this._map._container, 'pointerdown', this._onPointerDown, this); } removeHooks() { DomEvent.off(this._map._container, 'pointerdown', this._onPointerDown, this); } /** Returns `true` if a box is being drawn or was just drawn, e.g. to ignore the click that ends it. */ moved() { return this._moved; } _resetState() { clearTimeout(this._resetStateTimeout); this._moved = false; } /** @param {typeof DomEvent.on} onOff */ _toggleListeners(onOff) { onOff(this._map._container, { contextmenu: DomEvent.stop, pointermove: this._onPointerMove, pointerup: this._onPointerUp }, this); onOff(document, 'keydown', this._onKeyDown, this); } /** @param {PointerEvent} e */ _onPointerDown(e) { if (!e.shiftKey || (e.button !== 0)) { return false; } // also cancels a deferred reset that hasn't run yet, which would interrupt the interaction and orphan a box this._resetState(); // stops both text selection and native image drag in every engine without restyling the page; it also // suppresses compatibility mouse events, which is fine for a press that is already a box zoom gesture DomEvent.preventDefault(e); /** @type {Point} */ this._startPoint = this._map.pointerEventToContainerPoint(e); this._toggleListeners(DomEvent.on); } /** @param {PointerEvent} e */ _onPointerMove(e) { const map = this._map; if (!this._moved) { this._moved = true; /** @type {HTMLDivElement} */ this._box = DomUtil.create('div', 'leaflet-zoom-box', map._container); map._container.classList.add('leaflet-crosshair'); map.fire('boxzoomstart'); } /** @type {Point} */ this._point = map.pointerEventToContainerPoint(e); const bounds = new Bounds(this._point, this._startPoint), size = bounds.getSize(); DomUtil.setPosition(this._box, bounds.min); this._box.style.width = `${size.x}px`; this._box.style.height = `${size.y}px`; } _finish() { if (this._moved) { this._box.remove(); this._map._container.classList.remove('leaflet-crosshair'); } this._toggleListeners(DomEvent.off); } /** @param {PointerEvent} e */ _onPointerUp(e) { if (e.button !== 0) { return; } this._finish(); if (!this._moved) { return; } // Postpone to next JS tick so internal click event handling // still see it as "moved". this._resetStateTimeout = setTimeout(() => this._resetState(), 0); const bounds = new LatLngBounds( this._map.containerPointToLatLng(this._startPoint), this._map.containerPointToLatLng(this._point)); // a tiny box can collapse to a single LatLng at very high zoom, which fits at an Infinity zoom when no layer // limits it; unlike fitBounds before a tile layer is added, no layer will come along to clamp it (#6709) if (this._map.getBoundsZoom(bounds) !== Infinity) { this._map.fitBounds(bounds); } this._map.fire('boxzoomend', {boxZoomBounds: bounds}); } /** @param {KeyboardEvent} e */ _onKeyDown(e) { if (e.code === 'Escape') { this._finish(); this._resetState(); } } }