react-map-gl/src/map-interactions.react.js
Drew Bollinger 3d034f43c0 Remove ReactDOM.findDOMNode calls from src
Per the react docs, this use case is discouraged and can be
replaced with the ref itself. It also allows the removal of
ReactDOM from the src require statements

https://facebook.github.io/react/docs/top-level-api.html#reactdom.finddomnode
2015-11-24 15:41:33 -05:00

223 lines
7.0 KiB
JavaScript

// Copyright (c) 2015 Uber Technologies, Inc.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
'use strict';
var React = require('react');
var MapboxGL = require('mapbox-gl');
var Point = MapboxGL.Point;
var document = require('global/document');
var window = require('global/window');
var r = require('r-dom');
var noop = require('./noop');
var ua = typeof window.navigator !== 'undefined' ?
window.navigator.userAgent.toLowerCase() : '';
var firefox = ua.indexOf('firefox') !== -1;
function mousePos(el, event) {
var rect = el.getBoundingClientRect();
event = event.touches ? event.touches[0] : event;
return new Point(
event.clientX - rect.left - el.clientLeft,
event.clientY - rect.top - el.clientTop
);
}
/* eslint-disable max-len */
// Portions of the code below originally from:
// https://github.com/mapbox/mapbox-gl-js/blob/master/js/ui/handler/scroll_zoom.js
/* eslint-enable max-len */
var MapInteractions = React.createClass({
displayName: 'MapInteractions',
PropTypes: {
width: React.PropTypes.number.isRequired,
height: React.PropTypes.number.isRequired,
onMouseDown: React.PropTypes.func,
onMouseDrag: React.PropTypes.func,
onMouseUp: React.PropTypes.func,
onZoom: React.PropTypes.func,
onZoomEnd: React.PropTypes.func
},
getDefaultProps: function getDefaultProps() {
return {
onMouseDown: noop,
onMouseDrag: noop,
onMouseUp: noop,
onZoom: noop,
onZoomEnd: noop
};
},
getInitialState: function getInitialState() {
return {
startPos: null,
pos: null,
mouseWheelPos: null
};
},
_getMousePos: function _getMousePos(event) {
var el = this.refs.container;
return mousePos(el, event);
},
_onMouseDown: function _onMouseDown(event) {
var pos = this._getMousePos(event);
this.setState({startPos: pos, pos: pos});
this.props.onMouseDown({pos: pos});
document.addEventListener('mousemove', this._onMouseDrag, false);
document.addEventListener('mouseup', this._onMouseUp, false);
},
_onMouseDrag: function _onMouseDrag(event) {
var pos = this._getMousePos(event);
this.setState({pos: pos});
this.props.onMouseDrag({pos: pos});
},
_onMouseUp: function _onMouseUp(event) {
document.removeEventListener('mousemove', this._onMouseDrag, false);
document.removeEventListener('mouseup', this._onMouseUp, false);
var pos = this._getMousePos(event);
this.setState({pos: pos});
this.props.onMouseUp({pos: pos});
},
_onMouseMove: function _onMouseMove(event) {
var pos = this._getMousePos(event);
this.props.onMouseMove({pos: pos});
},
/* eslint-disable complexity, max-statements */
_onWheel: function _onWheel(event) {
event.stopPropagation();
event.preventDefault();
var value = event.deltaY;
// Firefox doubles the values on retina screens...
if (firefox && event.deltaMode === window.WheelEvent.DOM_DELTA_PIXEL) {
value /= window.devicePixelRatio;
}
if (event.deltaMode === window.WheelEvent.DOM_DELTA_LINE) {
value *= 40;
}
var type = this.state.mouseWheelType;
var timeout = this.state.mouseWheelTimeout;
var lastValue = this.state.mouseWheelLastValue;
var time = this.state.mouseWheelTime;
var now = (window.performance || Date).now();
var timeDelta = now - (time || 0);
var pos = this._getMousePos(event);
time = now;
if (value !== 0 && value % 4.000244140625 === 0) {
// This one is definitely a mouse wheel event.
type = 'wheel';
// Normalize this value to match trackpad.
value = Math.floor(value / 4);
} else if (value !== 0 && Math.abs(value) < 4) {
// This one is definitely a trackpad event because it is so small.
type = 'trackpad';
} else if (timeDelta > 400) {
// This is likely a new scroll action.
type = null;
lastValue = value;
// Start a timeout in case this was a singular event, and delay it by up
// to 40ms.
timeout = window.setTimeout(function setTimeout() {
var _type = 'wheel';
this._zoom(-this.state.mouseWheelLastValue, this.state.mouseWheelPos);
this.setState({mouseWheelType: _type});
}.bind(this), 40);
} else if (!this._type) {
// This is a repeating event, but we don't know the type of event just
// yet.
// If the delta per time is small, we assume it's a fast trackpad;
// otherwise we switch into wheel mode.
type = Math.abs(timeDelta * value) < 200 ? 'trackpad' : 'wheel';
// Make sure our delayed event isn't fired again, because we accumulate
// the previous event (which was less than 40ms ago) into this event.
if (timeout) {
window.clearTimeout(timeout);
timeout = null;
value += lastValue;
}
}
// Slow down zoom if shift key is held for more precise zooming
if (event.shiftKey && value) {
value = value / 4;
}
// Only fire the callback if we actually know what type of scrolling device
// the user uses.
if (type) {
this._zoom(-value, pos);
}
this.setState({
mouseWheelTime: time,
mouseWheelPos: pos,
mouseWheelType: type,
mouseWheelTimeout: timeout,
mouseWheelLastValue: lastValue
});
},
/* eslint-enable complexity, max-statements */
_zoom: function _zoom(delta, pos) {
// Scale by sigmoid of scroll wheel delta.
var scale = 2 / (1 + Math.exp(-Math.abs(delta / 100)));
if (delta < 0 && scale !== 0) {
scale = 1 / scale;
}
this.props.onZoom({pos: pos, scale: scale});
window.clearTimeout(this._zoomEndTimeout);
this._zoomEndTimeout = window.setTimeout(function _setTimeout() {
this.props.onZoomEnd();
}.bind(this), 200);
},
render: function render() {
return r.div({
ref: 'container',
onMouseMove: this._onMouseMove,
onMouseDown: this._onMouseDown,
onWheel: this._onWheel,
style: {
width: this.props.width,
height: this.props.height,
position: 'relative'
}
}, this.props.children);
}
});
module.exports = MapInteractions;