@amcharts/amcharts4
Version:
amCharts 4
1,271 lines (1,270 loc) • 107 kB
JavaScript
/**
* Interaction manages all aspects of user interaction - mouse move,
* click, hover, drag events, touch gestures.
*
* [[InteractionObject]] elements that want to use certain events, must attach event
* listeners to Interaction instance.
*
* Interaction itself will not modify [[InteractionObject]] elements, it will be up to
* those elements to handle interaction information received via event triggers.
*/
import { __extends } from "tslib";
/**
* ============================================================================
* IMPORTS
* ============================================================================
* @hidden
*/
import { BaseObjectEvents } from "../Base";
import { List } from "../utils/List";
import { Animation } from "../utils/Animation";
import { MultiDisposer } from "../utils/Disposer";
import { InteractionObject } from "./InteractionObject";
import { InteractionKeyboardObject } from "./InteractionKeyboardObject";
import { Dictionary } from "../utils/Dictionary";
import { Inertia } from "./Inertia";
import { addEventListener } from "../utils/DOM";
import { keyboard } from "../utils/Keyboard";
import { system } from "./../System";
import { options } from "./../Options";
import * as $ease from "../utils/Ease";
import * as $math from "../utils/Math";
import * as $array from "../utils/Array";
import * as $dom from "../utils/DOM";
import * as $iter from "../utils/Iterator";
import * as $type from "../utils/Type";
import * as $time from "../utils/Time";
/**
* ============================================================================
* MAIN CLASS
* ============================================================================
* @hidden
*/
/**
* Interaction manages all aspects of user interaction - mouse move,
* click, hover, drag events, touch gestures.
*
* [[InteractionObject]] elements that want to use certain events, must attach event
* listeners to Interaction instance.
*
* Interaction itself will not modify [[InteractionObject]] elements, it will be up to
* those elements to handle interaction information received via event triggers.
*
* @see {@link IInteractionEvents} for a list of available events
*/
var Interaction = /** @class */ (function (_super) {
__extends(Interaction, _super);
/**
* Constructor. Sets up universal document-wide move events to handle stuff
* outside particular chart container.
*/
function Interaction() {
var _this =
// Call super
_super.call(this) || this;
/**
* An indicator of global events were already initialized.
*/
_this._globalEventsAdded = false;
/**
* Holds which mouse event listeners to use.
*/
_this._pointerEvents = {
"pointerdown": "mousedown",
"pointerup": "mouseup",
"pointermove": "mousemove",
"pointercancel": "mouseup",
"pointerover": "mouseover",
"pointerout": "mouseout",
"wheel": "wheel"
};
/**
* Indicates if Interaction should use only "pointer" type events, like
* "pointermove", available in all modern browsers, ignoring "legacy"
* events, like "touchmove".
*/
_this._usePointerEventsOnly = false;
/**
* Use only touch events (for touch only devices such as tablets and phones)
*/
_this._useTouchEventsOnly = false;
/**
* Add special hover events. Normally, touch device tap will also simulate
* hover event. On some devices (ahem iOS) we want to prevent that so that
* over/out events are not duplicated.
*/
_this._addHoverEvents = true;
/**
* Indicates if passive mode options is supported by this browser.
*/
_this._passiveSupported = false;
/**
* Holds list of delayed events
*/
_this._delayedEvents = { out: [] };
/**
* List of objects that current have a pointer hovered over them.
*/
_this.overObjects = new List();
/**
* List of objects that currently has a pressed pointer.
*/
_this.downObjects = new List();
/**
* List of objects that need mouse position to be reported to them.
*/
_this.trackedObjects = new List();
/**
* List of objects that are currently being dragged.
*/
_this.transformedObjects = new List();
/**
* Holds all known pointers.
*/
_this.pointers = new Dictionary();
/**
* Inertia options that need to be applied to after element drag, if it's
* `inert = true`.
*
* This is just a default, which can and probably will be overridden by
* actual elements.
*/
_this.inertiaOptions = new Dictionary();
/**
* Default options for click events. These can be overridden in
* [[InteractionObject]].
*/
_this.hitOptions = {
"doubleHitTime": 300,
//"delayFirstHit": false,
"hitTolerance": 10,
"noFocus": true
};
/**
* Default options for hover events. These can be overridden in
* [[InteractionObject]].
*/
_this.hoverOptions = {
"touchOutBehavior": "leave",
"touchOutDelay": 1000
};
/**
* Default options for detecting a swipe gesture. These can be overridden in
* [[InteractionObject]].
*/
_this.swipeOptions = {
"time": 500,
"verticalThreshold": 75,
"horizontalThreshold": 30
};
/**
* Default options for keyboard operations. These can be overridden in
* [[InteractionObject]].
*/
_this.keyboardOptions = {
"speed": 0.1,
"accelleration": 1.2,
"accellerationDelay": 2000
};
/**
* Default options for keyboard operations. These can be overridden in
* [[InteractionObject]].
*
* @since 4.5.14
*/
_this.mouseOptions = {
"sensitivity": 1
};
// Set class name
_this.className = "Interaction";
// Create InteractionObject for <body>
_this.body = _this.getInteraction(document.body);
_this._disposers.push(_this.body);
// Detect browser capabilities and determine what event listeners to use
if (window.hasOwnProperty("PointerEvent")) {
// IE10+/Edge without touch controls enabled
_this._pointerEvents.pointerdown = "pointerdown";
_this._pointerEvents.pointerup = "pointerup";
_this._pointerEvents.pointermove = "pointermove";
_this._pointerEvents.pointercancel = "pointercancel";
_this._pointerEvents.pointerover = "pointerover";
_this._pointerEvents.pointerout = "pointerout";
//this._usePointerEventsOnly = true;
}
else if (window.hasOwnProperty("MSPointerEvent")) {
// IE9
_this._pointerEvents.pointerdown = "MSPointerDown";
_this._pointerEvents.pointerup = "MSPointerUp";
_this._pointerEvents.pointermove = "MSPointerMove";
_this._pointerEvents.pointercancel = "MSPointerUp";
_this._pointerEvents.pointerover = "MSPointerOver";
_this._pointerEvents.pointerout = "MSPointerOut";
//this._usePointerEventsOnly = true;
}
else if ((typeof matchMedia !== "undefined") && matchMedia('(pointer:fine)').matches) {
// This is only for Safari as it does not support PointerEvent
// Do nothing and let it use regular `mouse*` events
// Hi Apple ;)
// Additionally disable hover events for iOS devices
if ('ontouchstart' in window) {
_this._addHoverEvents = false;
_this._useTouchEventsOnly = true;
}
}
else if (window.navigator.userAgent.match(/MSIE /)) {
// Oh looky, an MSIE that does not support PointerEvent. Hi granpa IE9!
_this._usePointerEventsOnly = true;
}
else if (_this.fullFF()) {
// Old FF, let's use regular events.
// (Newer FFs would be detected by the PointerEvent availability check)
_this._usePointerEventsOnly = true;
}
else {
// Uses defaults for normal browsers
// We also assume that this must be a touch device that does not have
// any pointer events
_this._useTouchEventsOnly = true;
}
// Detect if device has a mouse
// This is turning out to be not reliable
// @todo remove
/*if (!window.navigator.msPointerEnabled && (typeof matchMedia !== "undefined") && !matchMedia('(pointer:fine)').matches && !this.fullFF()) {
this._useTouchEventsOnly = true;
}*/
// Detect proper mouse wheel events
if ("onwheel" in document.createElement("div")) {
// Modern browsers
_this._pointerEvents.wheel = "wheel";
}
else if ($type.hasValue(document.onmousewheel)) {
// Webkit and IE support at least "mousewheel"
_this._pointerEvents.wheel = "mousewheel";
}
// Set up default inertia options
_this.inertiaOptions.setKey("move", {
"time": 100,
"duration": 500,
"factor": 1,
"easing": $ease.polyOut3
});
_this.inertiaOptions.setKey("resize", {
"time": 100,
"duration": 500,
"factor": 1,
"easing": $ease.polyOut3
});
// Set the passive mode support
_this._passiveSupported = Interaction.passiveSupported;
// Apply theme
_this.applyTheme();
return _this;
}
/**
* This is a nasty detection for Firefox. The reason why we have is that
* Firefox ESR version does not support matchMedia correctly.
*
* On iOS, Firefox uses different userAgent, so we don't have to detect iOS.
*
* @return Full Firefox?
*/
Interaction.prototype.fullFF = function () {
return (window.navigator.userAgent.match(/Firefox/)) && !(window.navigator.userAgent.match(/Android/));
};
Interaction.prototype.debug = function () { };
/**
* ==========================================================================
* Processing
* ==========================================================================
* @hidden
*/
/**
* Sets up global events.
*
* We need this so that we can track drag movement beyond chart's container.
*
* @ignore Exclude from docs
*/
Interaction.prototype.addGlobalEvents = function () {
var _this = this;
if (!this._globalEventsAdded) {
if (!this._useTouchEventsOnly) {
this._disposers.push(addEventListener(document, this._pointerEvents.pointerdown, function (ev) { _this.handleGlobalPointerDown(ev); }));
this._disposers.push(addEventListener(document, this._pointerEvents.pointermove, function (ev) { _this.handleGlobalPointerMove(ev); }));
this._disposers.push(addEventListener(document, this._pointerEvents.pointerup, function (ev) { _this.handleGlobalPointerUp(ev); }));
this._disposers.push(addEventListener(document, this._pointerEvents.pointercancel, function (ev) { _this.handleGlobalPointerUp(ev, true); }));
this._disposers.push(addEventListener(document, "mouseenter", function (ev) {
if (!$type.hasValue(ev.relatedTarget) && (ev.buttons == 0 || ev.which == 0)) {
_this.handleDocumentLeave(ev);
}
}));
}
// No need to duplicate events for hubrid systems that support both
// pointer events and touch events. Touch events are need only for
// some touch-only systems, like Mobile Safari.
if (!this._usePointerEventsOnly) {
this._disposers.push(addEventListener(document, "touchstart", function (ev) { _this.handleGlobalTouchStart(ev); }));
this._disposers.push(addEventListener(document, "touchmove", function (ev) { _this.handleGlobalTouchMove(ev); }));
this._disposers.push(addEventListener(document, "touchend", function (ev) { _this.handleGlobalTouchEnd(ev); }));
}
this._disposers.push(addEventListener(document, "keydown", function (ev) { _this.handleGlobalKeyDown(ev); }));
this._disposers.push(addEventListener(document, "keyup", function (ev) { _this.handleGlobalKeyUp(ev); }));
this._globalEventsAdded = true;
}
};
/**
* Sets if [[InteractionObject]] is clickable.
*
* @ignore Exclude from docs
* @param io [[InteractionObject]] instance
*/
Interaction.prototype.processClickable = function (io) {
// Add or remove touch events
this.processTouchable(io);
};
/**
* Sets if [[InteractionObject]] will display context menu when right-clicked.
*
* @ignore Exclude from docs
* @param io [[InteractionObject]] instance
*/
Interaction.prototype.processContextMenu = function (io) {
if (io.contextMenuDisabled) {
if (!io.eventDisposers.hasKey("contextMenuDisabled")) {
io.eventDisposers.setKey("contextMenuDisabled", addEventListener(io.element, "contextmenu", function (e) {
e.preventDefault();
}));
}
}
else {
if (io.eventDisposers.hasKey("contextMenuDisabled")) {
io.eventDisposers.getKey("contextMenuDisabled").dispose();
}
}
};
/**
* Sets if [[InteractionObject]] is hoverable.
*
* @ignore Exclude from docs
* @param io [[InteractionObject]] instance
*/
Interaction.prototype.processHoverable = function (io) {
var _this = this;
if (io.hoverable || io.trackable) {
// Add global events
this.addGlobalEvents();
// Add hover styles
this.applyCursorOverStyle(io);
// Add local events
if (!io.eventDisposers.hasKey("hoverable") && this._addHoverEvents) {
io.eventDisposers.setKey("hoverable", new MultiDisposer([
addEventListener(io.element, this._pointerEvents.pointerout, function (e) { return _this.handlePointerOut(io, e); }),
addEventListener(io.element, this._pointerEvents.pointerover, function (e) { return _this.handlePointerOver(io, e); })
]));
}
if (io.trackable) {
//sprite.addEventListener("touchmove", this.handleTouchMove, false, this);
}
}
else {
var disposer = io.eventDisposers.getKey("hoverable");
if (disposer != null) {
disposer.dispose();
io.eventDisposers.removeKey("hoverable");
}
}
// Add or remove touch events
this.processTouchable(io);
};
/**
* Sets up [[InteractionObject]] as movable. Movable can be any
* transformation, e.g. drag, swipe, resize, track.
*
* @ignore Exclude from docs
* @param io Element
*/
Interaction.prototype.processMovable = function (io) {
// Add unified events
if (io.draggable || io.swipeable || io.trackable || io.resizable) {
// Prep the element
if (!this.isGlobalElement(io) && !io.isTouchProtected) {
this.prepElement(io);
}
// Add hover styles
this.applyCursorOverStyle(io);
}
// Add or remove touch events
this.processTouchable(io);
};
/**
* Checks if [[InteractionObject]] is trackable and sets relative events.
*
* @ignore Exclude from docs
* @param io Element
*/
Interaction.prototype.processTrackable = function (io) {
this.processHoverable(io);
this.processMovable(io);
if (io.trackable) {
this.trackedObjects.moveValue(io);
}
else {
this.trackedObjects.removeValue(io);
}
};
/**
* Checks if [[InteractionObject]] is draggable.
*
* @ignore Exclude from docs
* @param io Element
*/
Interaction.prototype.processDraggable = function (io) {
this.processMovable(io);
};
/**
* Checks if [[InteractionObject]] is swipeable and sets relative events.
*
* A swipe event is triggered when a horizontal drag of 75px or more (and
* less than 30px vertically) occurs within 700 milliseconds. This can be
* overridden in sprites [[swipeOptions]].
*
* @ignore Exclude from docs
* @param io Element
*/
Interaction.prototype.processSwipeable = function (io) {
this.processMovable(io);
};
/**
* Checks if [[InteractionObject]] is resizable and attaches required events
* to it.
*
* @ignore Exclude from docs
* @param io Element
*/
Interaction.prototype.processResizable = function (io) {
this.processMovable(io);
};
/**
* Checks if [[InteractionObject]] is supposed to capture mouse wheel events
* and prepares it to catch those events.
*
* @ignore Exclude from docs
* @param io Element
*/
Interaction.prototype.processWheelable = function (io) {
var _this = this;
if (io.wheelable) {
//io.hoverable = true;
if (!io.eventDisposers.hasKey("wheelable")) {
io.eventDisposers.setKey("wheelable", new MultiDisposer([
addEventListener(io.element, this._pointerEvents.wheel, function (e) { return _this.handleMouseWheel(io, e); }, this._passiveSupported ? { passive: false } : false),
io.events.on("out", function (e) {
if (io.wheelable) {
_this.unlockWheel();
}
}),
io.events.on("over", function (e) {
//console.log("whelab over")
if (io.wheelable) {
_this.lockWheel();
}
})
]));
}
}
else {
var disposer = io.eventDisposers.getKey("wheelable");
if (disposer != null) {
disposer.dispose();
io.eventDisposers.removeKey("wheelable");
}
}
};
/**
* Checks if [[InteractionObject]] is focusable. A focusable element is an
* element that will be highlighted when users presses TAB key. If the
* element is focusable, this function will attach relative focus/blur
* events to it.
*
* @ignore Exclude from docs
* @param io Element
*/
Interaction.prototype.processFocusable = function (io) {
var _this = this;
if (io.focusable === true && (io.tabindex > -1) && !this._useTouchEventsOnly) {
if (!io.eventDisposers.hasKey("focusable")) {
io.eventDisposers.setKey("focusable", new MultiDisposer([
addEventListener(io.element, "focus", function (e) { return _this.handleFocus(io, e); }),
addEventListener(io.element, "blur", function (e) { return _this.handleBlur(io, e); }),
addEventListener(io.element, this._pointerEvents.pointerdown, function (e) { return _this.handleFocusBlur(io, e); }),
addEventListener(io.element, "touchstart", function (e) { return _this.handleFocusBlur(io, e); }, this._passiveSupported ? { passive: false } : false)
]));
}
}
else {
var disposer = io.eventDisposers.getKey("focusable");
if (disposer != null) {
disposer.dispose();
io.eventDisposers.removeKey("focusable");
}
}
};
/**
* Checks if [[InteractionObject]] is "touchable". It means any interaction
* whatsoever: mouse click, touch screen tap, swipe, drag, resize, etc.
*
* @ignore Exclude from docs
* @param io Element
*/
Interaction.prototype.processTouchable = function (io) {
var _this = this;
// Add unified events
if (io.clickable || io.hoverable || io.trackable || io.draggable || io.swipeable || io.resizable) {
// Add global events
this.addGlobalEvents();
// Add local events
if (!io.eventDisposers.hasKey("touchable")) {
if (!this._useTouchEventsOnly && !this._usePointerEventsOnly) {
io.eventDisposers.setKey("touchable", new MultiDisposer([
addEventListener(io.element, this._pointerEvents.pointerdown, function (e) { return _this.handlePointerDown(io, e); }),
addEventListener(io.element, "touchstart", function (e) { return _this.handleTouchDown(io, e); }, this._passiveSupported ? { passive: false } : false)
]));
}
else if (!this._useTouchEventsOnly) {
io.eventDisposers.setKey("touchable", addEventListener(io.element, this._pointerEvents.pointerdown, function (e) { return _this.handlePointerDown(io, e); }));
}
else if (!this._usePointerEventsOnly) {
io.eventDisposers.setKey("touchable", addEventListener(io.element, "touchstart", function (e) { return _this.handleTouchDown(io, e); }, this._passiveSupported ? { passive: false } : false));
}
}
}
else {
var disposer = io.eventDisposers.getKey("touchable");
if (disposer != null) {
disposer.dispose();
io.eventDisposers.removeKey("touchable");
}
}
};
/**
* ==========================================================================
* Non-pointer events
* ==========================================================================
* @hidden
*/
/**
* Dispatches "focus" event when element gains focus.
*
* @ignore Exclude from docs
* @param io Element
* @param ev Original event
*/
Interaction.prototype.handleFocus = function (io, ev) {
if (!io.focusable) {
ev.preventDefault();
return;
}
io.isFocused = true;
if (io.events.isEnabled("focus") && !system.isPaused) {
var imev = {
type: "focus",
target: io,
event: ev
};
io.events.dispatchImmediately("focus", imev);
}
};
/**
* Used by regular click events to prevent focus if "noFocus" is set.
*
* This should not be called by "focus" handlers.
*
* @param io Element
* @param ev Original event
*/
Interaction.prototype.handleFocusBlur = function (io, ev) {
if (io.focusable !== false && this.getHitOption(io, "noFocus")) {
io.events.once("focus", function () {
io.events.disableType("blur");
$dom.blur();
if (io.sprite) {
io.sprite.handleBlur();
}
io.events.enableType("blur");
});
}
};
/**
* Dispatches "blur" event when element loses focus.
*
* @ignore Exclude from docs
* @param io Element
* @param ev Original event
*/
Interaction.prototype.handleBlur = function (io, ev) {
if (!io.focusable) {
ev.preventDefault();
return;
}
io.isFocused = false;
if (io.events.isEnabled("blur") && !system.isPaused) {
var imev = {
type: "blur",
target: io,
event: ev
};
io.events.dispatchImmediately("blur", imev);
}
};
/**
* ==========================================================================
* Global keyboard-related even handlers
* ==========================================================================
* @hidden
*/
/**
* Checks if there is an item that has currently focus and that they key is
* one of the directional keys. If both of the conditions are true, it
* creates an object to simulate movement of dragable element with keyboard.
*
* @ignore Exclude from docs
* @param ev An original keyboard event
*/
Interaction.prototype.handleGlobalKeyDown = function (ev) {
if (this.focusedObject) {
if (keyboard.isKey(ev, "esc")) {
// ESC removes focus
$dom.blur();
}
else if (this.focusedObject.draggable && keyboard.isKey(ev, ["up", "down", "left", "right"])) {
// Prevent scrolling of the document
ev.preventDefault();
// Get focused object
var io = this.focusedObject;
// Get particular key
var disposerKey = "interactionKeyboardObject";
// If such disposer already exists we know the event is going on so we
// just move on
if (io.eventDisposers.hasKey(disposerKey)) {
return;
}
// Create a keyboard mover
var ko = new InteractionKeyboardObject(io, ev);
io.eventDisposers.setKey(disposerKey, ko);
switch (keyboard.getEventKey(ev)) {
case "up":
ko.directionY = -1;
break;
case "down":
ko.directionY = 1;
break;
case "left":
ko.directionX = -1;
break;
case "right":
ko.directionX = 1;
break;
}
}
}
};
/**
* Dispatches related events when the keyboard key is realeasd.
*
* @ignore Exclude from docs
* @param ev An original keyboard event
*/
Interaction.prototype.handleGlobalKeyUp = function (ev) {
var disposerKey = "interactionKeyboardObject";
if (this.focusedObject) {
var disposer = this.focusedObject.eventDisposers.getKey(disposerKey);
if (disposer != null) {
// Prevent scrolling of the document
ev.preventDefault();
// Dispose stuff
disposer.dispose();
this.focusedObject.eventDisposers.removeKey(disposerKey);
}
// Does focused object have "hit" event?
var sprite = this.focusedObject.sprite;
if (keyboard.isKey(ev, "enter") && sprite) {
if (sprite.events.isEnabled("hit") || sprite.events.isEnabled("toggled")) {
this.focusedObject.dispatchImmediately("hit");
}
else if (sprite.showTooltipOn == "hit") {
this.focusedObject.dispatchImmediately("up");
}
}
}
};
/**
* ==========================================================================
* Global pointer-related even handlers
* ==========================================================================
* @hidden
*/
/**
* Handler for a global "pointermove" event.
*
* @ignore Exclude from docs
* @param ev Event object
*/
Interaction.prototype.handleGlobalPointerMove = function (ev) {
// Get pointer
var pointer = this.getPointer(ev);
// Update current point position
pointer.point = this.getPointerPoint(ev);
// Prepare and fire global event
if (this.events.isEnabled("track") && !system.isPaused) {
var imev = {
type: "track",
target: this,
event: ev,
pointer: pointer,
touch: pointer.touch
};
this.events.dispatchImmediately("track", imev);
}
// Track
this.addBreadCrumb(pointer, pointer.point);
// Process further
this.handleGlobalMove(pointer, ev);
};
/**
* Handler for a global "pointerdown" event.
*
* @ignore Exclude from docs
* @param ev Event object
*/
Interaction.prototype.handleGlobalPointerDown = function (ev) {
// Remove delayed hovers
this.processDelayed();
// Get pointer
var pointer = this.getPointer(ev);
// Prepare and fire global event
if (this.events.isEnabled("down") && !system.isPaused) {
var imev = {
type: "down",
target: this,
event: ev,
pointer: pointer,
touch: pointer.touch
};
this.events.dispatchImmediately("down", imev);
}
};
/**
* Prevents touch action from firing.
*
* @ignore Exclude from docs
* @param ev Event
*/
Interaction.prototype.preventTouchAction = function (ev) {
if (!ev.defaultPrevented) {
ev.preventDefault();
}
};
/**
* Handler for a global "pointerup" event.
*
* @ignore Exclude from docs
* @param ev Event object
*/
Interaction.prototype.handleGlobalPointerUp = function (ev, cancelled) {
if (cancelled === void 0) { cancelled = false; }
// Get pointer
var pointer = this.getPointer(ev);
// Prepare and fire global event
if (this.events.isEnabled("up") && !system.isPaused) {
var imev = {
type: "up",
target: this,
event: ev,
pointer: pointer,
touch: pointer.touch
};
this.events.dispatchImmediately("up", imev);
}
// Process further
this.handleGlobalUp(pointer, ev, cancelled);
};
/**
* ==========================================================================
* Global touch-related even handlers
* ==========================================================================
*/
/**
* Handler for a global "touchmove" event.
*
* @ignore Exclude from docs
* @param ev Event object
*/
Interaction.prototype.handleGlobalTouchMove = function (ev) {
// Process each changed touch point
for (var i = 0; i < ev.changedTouches.length; i++) {
// Get pointer
var pointer = this.getPointer(ev.changedTouches[i]);
// Update current point position
pointer.point = this.getPointerPoint(ev.changedTouches[i]);
// Prepare and fire global event
if (this.events.isEnabled("track") && !system.isPaused) {
var imev = {
type: "track",
target: this,
event: ev,
pointer: pointer,
touch: pointer.touch
};
this.events.dispatchImmediately("track", imev);
}
// Track
this.addBreadCrumb(pointer, pointer.point);
// Process further
this.handleGlobalMove(pointer, ev);
}
};
/**
* Handler for a global "touchstart" event.
*
* @ignore Exclude from docs
* @param ev Event object
*/
Interaction.prototype.handleGlobalTouchStart = function (ev) {
// Remove delayed hovers
this.processDelayed();
// Process each changed touch point
for (var i = 0; i < ev.changedTouches.length; i++) {
// Get pointer
var pointer = this.getPointer(ev.changedTouches[i]);
// Prepare and fire global event
if (!this._usePointerEventsOnly && this.events.isEnabled("down") && !system.isPaused) {
var imev = {
type: "down",
target: this,
event: ev,
pointer: pointer,
touch: pointer.touch
};
this.events.dispatchImmediately("down", imev);
}
}
};
/**
* Handler for a global "touchend" event.
*
* @ignore Exclude from docs
* @param ev Event object
*/
Interaction.prototype.handleGlobalTouchEnd = function (ev) {
// Process each changed touch point
for (var i = 0; i < ev.changedTouches.length; i++) {
// Get pointer
var pointer = this.getPointer(ev.changedTouches[i]);
// Prepare and fire global event
if (this.events.isEnabled("up") && !system.isPaused) {
var imev = {
type: "up",
target: this,
event: ev,
pointer: pointer,
touch: pointer.touch
};
this.events.dispatchImmediately("up", imev);
}
// Handle element-related events
this.handleGlobalUp(pointer, ev);
}
};
/**
* ==========================================================================
* Element-specific pointer-related even handlers
* ==========================================================================
* @hidden
*/
/**
* Handles event when pointer is over [[InteractionObject]] and button is
* pressed.
*
* @ignore Exclude from docs
* @param io Element
* @param ev Original event
*/
Interaction.prototype.handlePointerDown = function (io, ev) {
// Stop further propagation so we don't get multiple triggers on hybrid
// devices (both mouse and touch capabilities)
//ev.preventDefault();
//ev.stopPropagation();
//if (ev.defaultPrevented) {
//}
// Get pointer
var pointer = this.getPointer(ev);
// Ignore if it's anything but mouse's primary button
if (!pointer.touch && ev.which != 1 && ev.which != 3) {
return;
}
// Set mouse button
pointer.button = ev.which;
// Reset pointer
this.resetPointer(pointer, ev);
// Process down
this.handleDown(io, pointer, ev);
};
/**
* Handles event when [[InteractionObject]] is hovered by a mouse pointer.
*
* @ignore Exclude from docs
* @param io Element
* @param ev Original event
*/
Interaction.prototype.handlePointerOver = function (io, ev) {
// Get pointer
var pointer = this.getPointer(ev);
// Process down
this.handleOver(io, pointer, ev);
};
/**
* Handles event when [[InteractionObject]] loses hover from a mouse pointer.
*
* @ignore Exclude from docs
* @param io Element
* @param ev Original event
*/
Interaction.prototype.handlePointerOut = function (io, ev) {
// Get pointer
var pointer = this.getPointer(ev);
// Process down
this.handleOut(io, pointer, ev);
};
/**
* Handles event when mouse wheel is crolled over the [[InteractionObject]].
*
* @ignore Exclude from docs
* @param io Element
* @param ev Original event
* @todo Investigate more-cross browser stuff https://developer.mozilla.org/en-US/docs/Web/Events/wheel
*/
Interaction.prototype.handleMouseWheel = function (io, ev) {
// Get pointer
var pointer = this.getPointer(ev);
// Update current point position
pointer.point = this.getPointerPoint(ev);
// Init delta values
var deltaX = 0, deltaY = 0;
// Set up modifier
// This is needed because FireFox reports wheel deltas in "lines" instead
// of pixels so we have to approximate pixel value
var mod = 1;
if (ev.deltaMode == 1) {
mod = 50;
}
// Adjust configurable sensitivity
mod *= this.getMouseOption(io, "sensitivity");
// Calculate deltas
if (ev instanceof WheelEvent) {
deltaX = Math.round((-1 * ev.wheelDeltaX) || (ev.deltaX * mod));
deltaY = Math.round((-1 * ev.wheelDeltaY) || (ev.deltaY * mod));
}
else {
throw new Error("Invalid event type");
}
// Handle the event
this.handleWheel(io, pointer, deltaX, deltaY, ev);
};
/**
* ==========================================================================
* Element-specific touch-related even handlers
* ==========================================================================
* @hidden
*/
/**
* Handles an event when an [[InteractionObject]] is touched on a touch
* device.
*
* @ignore Exclude from docs
* @param io Element
* @param ev Original event
*/
Interaction.prototype.handleTouchDown = function (io, ev) {
// Stop further propagation so we don't get multiple triggers on hybrid
// devices (both mouse and touch capabilities)
//this.maybePreventDefault(io, ev);
//return;
// Process each changed touch point
for (var i = 0; i < ev.changedTouches.length; i++) {
// Get pointer
var pointer = this.getPointer(ev.changedTouches[i]);
this.maybePreventDefault(io, ev, pointer);
// Reset pointer
this.resetPointer(pointer, ev.changedTouches[i]);
// Process down
this.handleDown(io, pointer, ev);
}
};
/**
* ==========================================================================
* Universal handlers
* ==========================================================================
* @hidden
*/
/**
* Handles click/tap. Checks for doublehit.
*
* @ignore Exclude from docs
* @param io Interaction object
* @param pointer Pointer
* @param ev Original event
*/
Interaction.prototype.handleHit = function (io, pointer, ev) {
// Check if this is a double-hit
var now = $time.getTime();
if (io.lastHit && (io.lastHit >= (now - this.getHitOption(io, "doubleHitTime")))) {
// Yup - it's a double-hit
// Cancel the hit
//clearTimeout(io.lastHitPointer.hitTimeout);
// If it happened too fast it probably means that hybrid device just
// generated two events for the same tap
if ((now - io.lastHit) < 100) {
// Ignore
return;
}
// Clear last hit
io.lastHit = undefined;
io.lastHitPointer = undefined;
// Dispatch event
if (io.events.isEnabled("doublehit") && !system.isPaused) {
var imev = {
type: "doublehit",
target: io,
point: pointer.point,
event: ev,
touch: pointer.touch
};
io.events.dispatchImmediately("doublehit", imev);
}
}
else {
// Log last hit
io.lastHit = now;
io.lastHitPointer = pointer;
if (pointer.button === 3) {
// Execute HIT now
if (io.events.isEnabled("rightclick") && !system.isPaused) {
var imev = {
type: "rightclick",
target: io,
event: ev
};
io.events.dispatchImmediately("rightclick", imev);
}
}
else {
if (io.events.isEnabled("hit") && !system.isPaused) {
var imev = {
type: "hit",
target: io,
event: ev,
point: pointer.point,
touch: pointer.touch
};
io.events.dispatchImmediately("hit", imev);
}
}
}
};
/**
* Handles pointer hovering over [[InteractionObject]].
*
* @ignore Exclude from docs
* @param io Interaction object
* @param pointer Pointer
* @param ev Original event
* @param soft Invoked by helper function
*/
Interaction.prototype.handleOver = function (io, pointer, ev, soft) {
if (soft === void 0) { soft = false; }
if (!io.hoverable) {
return;
}
var hoversPaused = false;
if (this.shouldCancelHovers(pointer) && this.areTransformed() && this.moved(pointer, this.getHitOption(io, "hitTolerance"))) {
hoversPaused = true;
this.cancelAllHovers(ev);
}
// Remove any delayed outs
this.processDelayed();
// Add pointer
io.overPointers.moveValue(pointer);
// Check if object is not yet hovered
if (!io.isRealHover) {
// Set element as hovered
if (!hoversPaused) {
io.isHover = true;
io.isRealHover = true;
this.overObjects.moveValue(io);
}
// Generate body track event. This is needed so that if element loads
// under unmoved mouse cursor, we still need all the actions that are
// required to happen to kick in.
this.handleTrack(this.body, pointer, ev, true);
// Event
if (io.events.isEnabled("over") && !system.isPaused && !hoversPaused) {
var imev = {
type: "over",
target: io,
event: ev,
pointer: pointer,
touch: pointer.touch
};
io.events.dispatchImmediately("over", imev);
}
}
};
/**
* Handles when [[InteractionObject]] is no longer hovered.
*
* If `soft = true`, this means that method is being invoked by some other
* code, not hard "out" function, like `handleUp` which implies we need to
* run additional checks before unhovering the object.
*
* @ignore Exclude from docs
* @param io Interaction object
* @param pointer Pointer
* @param ev Original event
* @param soft Invoked by helper function
* @param force Force imediate out
*/
Interaction.prototype.handleOut = function (io, pointer, ev, soft, force) {
var _this = this;
if (soft === void 0) { soft = false; }
if (force === void 0) { force = false; }
if (!io.hoverable) {
return;
}
// Remove pointer
io.overPointers.removeValue(pointer);
// Check if element is still hovered
if (io.isHover && (!io.hasDelayedOut || force)) {
// Should we run additional checks?
if (soft && io.overPointers.length) {
// There are still pointers hovering - don't do anything else and
// wait until either no over pointers are there or we get a hard out
// event.
return;
}
// Should we delay "out" if this is happening on a touch device?
if (pointer && pointer.touch && !force && !this.old(pointer)) {
// This is a touch pointer, and it hasn't moved, let's pretend
// the object is still hovered, and act as per "behavior" setting
var behavior = this.getHoverOption(io, "touchOutBehavior");
if (behavior == "leave") {
// Set to "leave", so we do not execute any "out" event.
// It will be handled by any other interaction that happens
// afterwards.
this._delayedEvents.out.push({
type: "out",
io: io,
pointer: pointer,
event: ev,
keepUntil: $time.getTime() + 500
});
io.hasDelayedOut = true;
return;
}
else if (behavior == "delay" && this.getHoverOption(io, "touchOutDelay")) {
this._delayedEvents.out.push({
type: "out",
io: io,
pointer: pointer,
event: ev,
keepUntil: $time.getTime() + 500,
timeout: this.setTimeout(function () {
_this.handleOut(io, pointer, ev, true);
}, this.getHoverOption(io, "touchOutDelay"))
});
return;
}
else {
// Nothing for "remove" - that's how it works "out-of-the-box"
}
}
// Set element as not hovered
io.isHover = false;
this.overObjects.removeValue(io);
// Invoke event
if (!io.isDisposed() && io.events.isEnabled("out") && !system.isPaused) {
var imev = {
type: "out",
target: io,
event: ev,
pointer: pointer,
touch: pointer.touch
};
io.events.dispatchImmediately("out", imev);
}
// Reset object from lefover delayed outs, pointers
io.overPointers.clear();
io.hasDelayedOut = false;
// @todo (clean delayed)
}
};
/**
* Processes dalyed events, such as "out" event that was initiated for
* elements by touch.
*/
Interaction.prototype.processDelayed = function () {
var delayedEvent;
while (true) {
delayedEvent = this._delayedEvents.out.pop();
if (!delayedEvent) {
break;
}
if (delayedEvent.timeout) {
delayedEvent.timeout.dispose();
}
this.handleOut(delayedEvent.io, delayedEvent.pointer, delayedEvent.event, false, true);
}
};
/**
* Performs tasks on pointer down.
*
* @ignore Exclude from docs
* @param io Element
* @param pointer Pointer
* @param ev Original event
*/
Interaction.prototype.handleDown = function (io, pointer, ev) {
// Need to prevent default event from happening on transformable objects
this.maybePreventDefault(io, ev, pointer);
// Stop inertia animations if they're currently being played out
if (io.inert) {
this.stopInertia(io);
}
// Trigger hover because some touch devices won't trigger over events
// on their own
this.handleOver(io, pointer, ev, true);
// Add pointer to list
io.downPointers.moveValue(pointer);
// Apply styles if necessary
this.applyCursorDownStyle(io, pointer);
// Check if object is already down
if (!io.isDown) {
// Lose focus if needed
if (io.focusable !== false && this.getHitOption(io, "noFocus") && this.focusedObject) {
$dom.blur();
}
// Set object as hovered
io.isDown = true;
this.downObjects.moveValue(io);
// Prep object for dragging and/or resizing
if (io.draggable) {
this.processDragStart(io, pointer, ev);
}
if (io.resizable) {
this.processResizeStart(io, pointer, ev);
}
}
// Dispatch "down" event
if (io.events.isEnabled("down") && !system.isPaused) {
var imev = {
type: "down",
target: io,