@progress/kendo-charts
Version:
Kendo UI platform-independent Charts library
622 lines (507 loc) • 17.5 kB
JavaScript
import { Class } from '../drawing-utils';
import { applyEventMap, eventMap } from './event-map';
import { on, off } from './event-utils';
import getSupportedFeatures from './get-supported-features';
import noop from './noop';
import now from './now';
import grep from './grep';
import Observable from './observable';
var extend = Object.assign;
var preventDefault = function (e) {
e.preventDefault();
};
var
DEFAULT_MIN_HOLD = 800,
CLICK_DELAY = 300,
DEFAULT_THRESHOLD = 0,
PRESS = 'press',
HOLD = 'hold',
SELECT = 'select',
START = 'start',
MOVE = 'move',
END = 'end',
CANCEL = 'cancel',
TAP = 'tap',
DOUBLETAP = 'doubleTap',
RELEASE = 'release',
GESTURESTART = 'gesturestart',
GESTURECHANGE = 'gesturechange',
GESTUREEND = 'gestureend',
GESTURETAP = 'gesturetap';
var THRESHOLD = {
'api': 0,
'touch': 0,
'mouse': 9,
'pointer': 9
};
function touchDelta(touch1, touch2) {
var x1 = touch1.x.location,
y1 = touch1.y.location,
x2 = touch2.x.location,
y2 = touch2.y.location,
dx = x1 - x2,
dy = y1 - y2;
return {
center: {
x: (x1 + x2) / 2,
y: (y1 + y2) / 2
},
distance: Math.sqrt(dx * dx + dy * dy)
};
}
function getTouches(e) {
var touches = [],
originalEvent = e.originalEvent || e,
currentTarget = e.currentTarget;
if (e.api) {
touches.push({
id: 2, // hardcoded ID for API call
event: e,
target: e.target,
currentTarget: e.target,
location: e,
type: 'api'
});
} else {
touches.push({
location: originalEvent,
event: e,
target: e.target,
currentTarget: currentTarget,
id: originalEvent.pointerId,
type: 'pointer'
});
}
return touches;
}
var TouchAxis = (function (Class) {
function TouchAxis(axis, location) {
Class.call(this);
var that = this;
that.support = getSupportedFeatures();
that.invalidZeroEvents = this.support.mobileOS && this.support.mobileOS.android;
that.axis = axis;
that._updateLocationData(location);
that.startLocation = that.location;
that.velocity = that.delta = 0;
that.timeStamp = now();
}
if ( Class ) TouchAxis.__proto__ = Class;
TouchAxis.prototype = Object.create( Class && Class.prototype );
TouchAxis.prototype.constructor = TouchAxis;
TouchAxis.prototype.move = function move (location) {
var that = this,
offset = location['page' + that.axis],
timeStamp = now(),
timeDelta = timeStamp - that.timeStamp || 1;
if (!offset && this.invalidZeroEvents) {
return;
}
that.delta = offset - that.location;
that._updateLocationData(location);
that.initialDelta = offset - that.startLocation;
that.velocity = that.delta / timeDelta;
that.timeStamp = timeStamp;
};
TouchAxis.prototype._updateLocationData = function _updateLocationData (location) {
var that = this,
axis = that.axis;
that.location = location['page' + axis];
that.client = location['client' + axis];
that.screen = location['screen' + axis];
};
return TouchAxis;
}(Class));
var Touch = (function (Class) {
function Touch(userEvents, target, touchInfo) {
Class.call(this);
extend(this, {
x: new TouchAxis('X', touchInfo.location),
y: new TouchAxis('Y', touchInfo.location),
type: touchInfo.type,
threshold: userEvents.threshold || THRESHOLD[touchInfo.type],
userEvents: userEvents,
target: target,
currentTarget: touchInfo.currentTarget,
initialTouch: touchInfo.target,
id: touchInfo.id,
pressEvent: touchInfo,
_clicks: userEvents._clicks,
supportDoubleTap: userEvents.supportDoubleTap,
_moved: false,
_finished: false
});
}
if ( Class ) Touch.__proto__ = Class;
Touch.prototype = Object.create( Class && Class.prototype );
Touch.prototype.constructor = Touch;
Touch.prototype.press = function press () {
var this$1 = this;
this._holdTimeout = setTimeout(function () { return this$1._hold(); }, this.userEvents.minHold);
this._trigger(PRESS, this.pressEvent);
};
Touch.prototype._tap = function _tap (touchInfo) {
var that = this;
that.userEvents._clicks++;
if (that.userEvents._clicks === 1) {
that._clickTimeout = setTimeout(function() {
if (that.userEvents._clicks === 1) {
that._trigger(TAP, touchInfo);
} else {
that._trigger(DOUBLETAP, touchInfo);
}
that.userEvents._clicks = 0;
}, CLICK_DELAY);
}
};
Touch.prototype._hold = function _hold () {
this._trigger(HOLD, this.pressEvent);
};
/* eslint-disable consistent-return */
Touch.prototype.move = function move (touchInfo) {
var that = this;
var preventMove = touchInfo.type !== 'api' && that.userEvents._shouldNotMove;
if (that._finished || preventMove) {
return;
}
that.x.move(touchInfo.location);
that.y.move(touchInfo.location);
if (!that._moved) {
if (that._withinIgnoreThreshold()) {
return;
}
if (!UserEvents.current || UserEvents.current === that.userEvents) {
that._start(touchInfo);
} else {
return that.dispose();
}
}
if (!that._finished) {
that._trigger(MOVE, touchInfo);
}
};
/* eslint-enable consistent-return */
Touch.prototype.end = function end (touchInfo) {
this.endTime = now();
if (this._finished) {
return;
}
this._finished = true;
this._trigger(RELEASE, touchInfo);
if (this._moved) {
this._trigger(END, touchInfo);
} else {
if (this.supportDoubleTap) {
this._tap(touchInfo);
} else {
this._trigger(TAP, touchInfo);
}
}
clearTimeout(this._holdTimeout);
this.dispose();
};
Touch.prototype.dispose = function dispose () {
var userEvents = this.userEvents,
activeTouches = userEvents.touches || [];
this._finished = true;
this.pressEvent = null;
clearTimeout(this._holdTimeout);
// activeTouches.splice($.inArray(this, activeTouches), 1);
var activeTouchIndex = activeTouches.indexOf(this);
activeTouches.splice(activeTouchIndex, 1);
};
Touch.prototype.skip = function skip () {
this.dispose();
};
Touch.prototype.cancel = function cancel () {
this.dispose();
};
Touch.prototype.isMoved = function isMoved () {
return this._moved;
};
Touch.prototype._start = function _start (touchInfo) {
clearTimeout(this._holdTimeout);
this.startTime = now();
this._moved = true;
this._trigger(START, touchInfo);
};
Touch.prototype._trigger = function _trigger (name, touchInfo) {
var e = touchInfo.event;
var data = {
touch: this,
x: this.x,
y: this.y,
target: this.target,
event: e
};
if (this.userEvents.notify(name, data)) {
e.preventDefault();
}
};
Touch.prototype._withinIgnoreThreshold = function _withinIgnoreThreshold () {
var xDelta = this.x.initialDelta,
yDelta = this.y.initialDelta;
return Math.sqrt(xDelta * xDelta + yDelta * yDelta) <= this.threshold;
};
return Touch;
}(Class));
function withEachUpEvent(callback) {
var downEvents = eventMap.up.split(' '),
idx = 0,
length = downEvents.length;
for (; idx < length; idx++) {
callback(downEvents[idx]);
}
}
var UserEvents = (function (Observable) {
function UserEvents(element, options) {
Observable.call(this);
var that = this;
var filter;
var support = getSupportedFeatures();
this.support = support;
/* eslint-disable no-param-reassign */
options = options || {};
/* eslint-enable no-param-reassign */
this.options = options;
filter = that.filter = options.filter;
that.threshold = options.threshold || DEFAULT_THRESHOLD;
that.minHold = options.minHold || DEFAULT_MIN_HOLD;
that.touches = [];
that._maxTouches = options.multiTouch ? 2 : 1;
that.allowSelection = options.allowSelection;
that.captureUpIfMoved = options.captureUpIfMoved;
that._clicks = 0;
that.supportDoubleTap = options.supportDoubleTap;
extend(that, {
element: element,
surface: options.surface || element,
stopPropagation: options.stopPropagation,
pressed: false
});
this._surfaceMoveHandler = this._move.bind(this);
on(that.surface, applyEventMap('move'), this._surfaceMoveHandler);
this._surfaceEndHandler = this._end.bind(this);
on(that.surface, applyEventMap('up cancel'), this._surfaceEndHandler);
this._elementStartHandler = this._start.bind(this);
on(element, applyEventMap('down'), filter, this._elementStartHandler);
element.style['touch-action'] = options.touchAction || 'none';
if (options.preventDragEvent) {
this._elementDragStartHandler = preventDefault;
on(element, applyEventMap('dragstart'), this._elementDragStartHandler);
}
// element.on(kendo.applyEventMap('mousedown'), filter, {
// root: element
// } '_select');
// todo: use root
this._elementSelectHandler = this._select.bind(this);
on(element, applyEventMap('mousedown'), filter, this._elementSelectHandler);
if (that.captureUpIfMoved) {
var surfaceElement = that.surface,
preventIfMovingProxy = that.preventIfMoving.bind(that);
withEachUpEvent(function(eventName) {
surfaceElement.addEventListener(eventName, preventIfMovingProxy, true);
});
}
that.bind([
PRESS,
HOLD,
TAP,
DOUBLETAP,
START,
MOVE,
END,
RELEASE,
CANCEL,
GESTURESTART,
GESTURECHANGE,
GESTUREEND,
GESTURETAP,
SELECT
], options);
}
if ( Observable ) UserEvents.__proto__ = Observable;
UserEvents.prototype = Object.create( Observable && Observable.prototype );
UserEvents.prototype.constructor = UserEvents;
UserEvents.prototype.preventIfMoving = function preventIfMoving (e) {
if (this._isMoved()) {
e.preventDefault();
}
};
UserEvents.prototype.destroy = function destroy () {
var that = this;
var options = this.options;
var element = this.element;
if (that._destroyed) {
return;
}
that._destroyed = true;
if (that.captureUpIfMoved) {
var surfaceElement = that.surface;
withEachUpEvent(function(eventName) {
surfaceElement.removeEventListener(eventName, that.preventIfMoving);
});
}
off(that.surface, applyEventMap('move'), this._surfaceMoveHandler);
off(that.surface, applyEventMap('up cancel'), this._surfaceEndHandler);
off(element, applyEventMap('down'), this._elementStartHandler);
if (options.preventDragEvent) {
off(element, applyEventMap('dragstart'), this._elementDragStartHandler);
}
off(element, applyEventMap('mousedown'), this._elementSelectHandler);
that._disposeAll();
that.unbind();
delete that.surface;
delete that.element;
delete that.currentTarget;
};
UserEvents.prototype.capture = function capture () {
UserEvents.current = this;
};
UserEvents.prototype.cancel = function cancel () {
this._disposeAll();
this.trigger(CANCEL);
};
UserEvents.prototype.notify = function notify (event, data) {
var that = this,
touches = that.touches;
var eventName = event;
if (this._isMultiTouch()) {
switch (eventName) {
case MOVE:
eventName = GESTURECHANGE;
break;
case END:
eventName = GESTUREEND;
break;
case TAP:
eventName = GESTURETAP;
break;
default:
break;
}
extend(data, {
touches: touches
}, touchDelta(touches[0], touches[1]));
}
return this.trigger(eventName, extend(data, {
type: eventName
}));
};
UserEvents.prototype.press = function press (x, y, target) {
this._apiCall('_start', x, y, target);
};
UserEvents.prototype.move = function move (x, y) {
this._apiCall('_move', x, y);
};
UserEvents.prototype.end = function end (x, y) {
this._apiCall('_end', x, y);
};
UserEvents.prototype._isMultiTouch = function _isMultiTouch () {
return this.touches.length > 1;
};
UserEvents.prototype._maxTouchesReached = function _maxTouchesReached () {
return this.touches.length >= this._maxTouches;
};
UserEvents.prototype._disposeAll = function _disposeAll () {
var touches = this.touches;
while (touches.length > 0) {
touches.pop().dispose();
}
};
UserEvents.prototype._isMoved = function _isMoved () {
return grep(this.touches, function(touch) {
return touch.isMoved();
}).length;
};
UserEvents.prototype._select = function _select (e) {
if (!this.allowSelection || this.trigger(SELECT, { event: e })) {
e.preventDefault();
}
};
UserEvents.prototype._start = function _start (e) {
var this$1 = this;
if (e.which && e.which > 1 || this._maxTouchesReached()) {
return;
}
UserEvents.current = null;
this.currentTarget = e.currentTarget;
if (this.stopPropagation) {
e.stopPropagation();
}
var target;
var eventTouches = getTouches(e);
for (var idx = 0; idx < eventTouches.length; idx++) {
if (this$1._maxTouchesReached()) {
break;
}
var eventTouch = eventTouches[idx];
if (this$1.filter) {
target = eventTouch.currentTarget;
} else {
target = this$1.element;
}
if (target && target.length === 0) {
continue;
}
var touch = new Touch(this$1, target, eventTouch);
this$1.touches.push(touch);
touch.press();
if (this$1._isMultiTouch()) {
this$1.notify('gesturestart', {});
}
}
};
UserEvents.prototype._move = function _move (e) {
this._eachTouch('move', e);
};
UserEvents.prototype._end = function _end (e) {
this._eachTouch('end', e);
};
UserEvents.prototype._eachTouch = function _eachTouch (methodName, e) {
var this$1 = this;
var that = this,
dict = {},
touches = getTouches(e),
activeTouches = that.touches,
idx,
touch,
touchInfo,
matchingTouch;
for (idx = 0; idx < activeTouches.length; idx++) {
touch = activeTouches[idx];
dict[touch.id] = touch;
}
for (idx = 0; idx < touches.length; idx++) {
touchInfo = touches[idx];
matchingTouch = dict[touchInfo.id];
if (matchingTouch) {
var shouldCapture = methodName === 'move' && touchInfo.type === 'pointer' && !this$1.surface.hasPointerCapture(touchInfo.id);
if (shouldCapture) {
this$1.surface.setPointerCapture(touchInfo.id);
}
matchingTouch[methodName](touchInfo);
}
}
};
UserEvents.prototype._apiCall = function _apiCall (type, x, y, target) {
this[type]({
api: true,
pageX: x,
pageY: y,
clientX: x,
clientY: y,
target: target || this.element,
stopPropagation: noop,
preventDefault: noop
});
};
UserEvents.defaultThreshold = function defaultThreshold (value) {
DEFAULT_THRESHOLD = value;
};
UserEvents.minHold = function minHold (value) {
DEFAULT_MIN_HOLD = value;
};
return UserEvents;
}(Observable));
export default UserEvents;