const DOM = require("mapbox-gl/src/util/dom");
const util = require("mapbox-gl/src/util/util");
const window = require("mapbox-gl/src/util/window");
/**
* Feature drag and drop handler class.
*
* The following events will be triggered on the map:
* - `feature-is-draggable`
* - `feature-dragstart`
* - `feature-drag`
* - `feature-dragend`
*
* Each event is composed of the following fields:
* - `originalEvent` the browser event
* - `startPoint` the point where the drag starts
* - `point` the current point
* - `feature` the feature on which the drag starts
* - `handler` this handler instance
*
* @param {Map} map The Mapbox GL JS map to add the handler to.
*/
class FeatureDragNDropHandler {
constructor(map) {
this._map = map;
this._el = map.getCanvasContainer();
this._enabled = false;
this._active = false;
this._startPos = null;
this._pos = null;
this._feature = null;
util.bindAll([
"_onDown",
"_onMove",
"_onUp",
"_onTouchEnd",
"_onMouseUp"
], this);
}
/**
* Returns a boolean indicating whether this handler is enabled or not.
*
* @returns {boolean} `true` if the drag and drop enabled.
*/
isEnabled() {
return !!this._enabled;
}
/**
* Returns a boolean indicating whether this handler is in a dragging state or not.
*
* @returns {boolean} `true` if dragging.
*/
isActive() {
return !!this._active;
}
/**
* Enables this handler
*/
enable() {
if (!this.isEnabled()) {
this._el.addEventListener("mousedown", this._onDown);
this._el.addEventListener("touchstart", this._onDown);
this._enabled = true;
}
}
/**
* Disables this handler
*/
disable() {
if (this.isEnabled()) {
this._el.removeEventListener("mousedown", this._onDown);
this._el.removeEventListener("touchstart", this._onDown);
this._enabled = false;
}
}
/**
* Call by external code when a `feature-is-draggable` event is triggered and the feature should not be draggable
*/
featureNotDraggable() {
this._startPos = null;
this._pos = null;
this._feature = null;
}
/**
* Returns the feature on which the drag starts.
* @returns {Object} the feature on which the drag starts.
*/
getFeature() {
return this._feature;
}
/**
* Tells if the event is processed by this handler or not.
* @private
* @param {Event} e any browser event but probably on MouseEvent or TouchEvent.
* @returns {boolean} `true` is event ignored
*/
_ignoreEvent(e) {
const map = this._map;
if (map.boxZoom && map.boxZoom.isActive()) {
return true;
} else if (map.dragPan && map.dragPan.isActive()) {
return true;
} else if (map.dragRotate && map.dragRotate.isActive()) {
return true;
} else if (e.touches) {
return !!(e.touches.length > 1);
} else if (e.ctrlKey) {
return true;
} else {
return e.type !== "mousemove" && e.button && e.button !== 0; // left button
}
}
/**
* Convert a local `Point` instance into a `[x, y]` `Array`.
* @param {Point} point local `Point` instance
* @returns {Array}
*/
_pointToArray(point) {
return point ? [point.x, point.y] : null;
}
/**
* Triggered when a mouse button is pressed on the canvas.
* @private
* @param {MouseEvent|TouchEvent} e mouse or touch browser event
*/
_onDown(e) {
if (!this._ignoreEvent(e) && !this.isActive()) {
// because this plugin Point class prototype will be different than the one defined in mapboxgl
// this._pos could no be considered a Point for the map
// https://github.com/mapbox/mapbox-gl-js/blob/46ec4e2fb57f0db772ec928ac8d945efde5d2082/src/ui/map.js#L802
// https://github.com/mapbox/mapbox-gl-js/blob/46ec4e2fb57f0db772ec928ac8d945efde5d2082/src/ui/map.js#L784
// So the Point instance is converted to a Array.
this._startPos = this._pos = this._pointToArray(DOM.mousePos(this._el, e));
this._feature = this._map.queryRenderedFeatures(this._pos).shift();
this._fireEvent("feature-is-draggable", e);
if (this._feature) {
if (e.touches) {
window.document.addEventListener("touchmove", this._onMove);
window.document.addEventListener("touchend", this._onTouchEnd);
} else {
window.document.addEventListener("mousemove", this._onMove);
window.document.addEventListener("mouseup", this._onMouseUp);
window.addEventListener("blur", this._onMouseUp);
}
}
}
}
/**
* Triggered when the mouse move on the document.
* @private
* @param {MouseEvent|TouchEvent} e mouse or touch browser event
*/
_onMove(e) {
if (this._ignoreEvent(e) || !this._feature) {
return;
}
this._pos = this._pointToArray(DOM.mousePos(this._el, e));
if (!this.isActive()) {
this._active = true;
this._fireEvent("feature-dragstart", e);
}
this._fireEvent("feature-drag", e);
e.preventDefault();
}
/**
* Triggered when a mouse button is released or the touch event ends.
* @private
* @param {MouseEvent|TouchEvent|FocusEvent} e mouse or touch browser event
*/
_onUp(e) {
if (!this.isActive()) {
return;
}
this._active = false;
this._pos = this._pointToArray(DOM.mousePos(this._el, e));
this._fireEvent("feature-dragend", e);
}
/**
* Triggered when a mouse button is released or the mouse leave the window.
* @private
* @param {MouseEvent|FocusEvent} e mouse browser event
*/
_onMouseUp(e) {
if (!this._ignoreEvent(e)) {
this._onUp(e);
window.document.removeEventListener("mousemove", this._onMove);
window.document.removeEventListener("mouseup", this._onMouseUp);
window.removeEventListener("blur", this._onMouseUp);
}
}
/**
* Triggered when the touch event ends.
* @private
* @param {FocusEvent} e touch browser event
*/
_onTouchEnd(e) {
if (!this._ignoreEvent(e)) {
this._onUp(e);
window.document.removeEventListener("touchmove", this._onMove);
window.document.removeEventListener("touchend", this._onTouchEnd);
}
}
/**
* Fire the event on the map.
*
* The event is wrapped in a object providing the following fields:
* - `originalEvent` the browser event
* - `startPoint` the point where the drag starts
* - `point` the current point
* - `feature` the feature on which the drag starts
* - `handler` this handler instance
*
* @private
* @param {string} type event type
* @param {Event} e browser event
* @returns {Map} the map instance
*/
_fireEvent(type, e) {
return this._map.fire(type, {
originalEvent: e,
startPoint: this._startPos,
point: this._pos,
feature: this._feature,
handler: this
});
}
}
module.exports = FeatureDragNDropHandler;