@interactjs/core
Advanced tools
Comparing version 1.4.0-alpha.17 to 1.4.0-alpha.18
@@ -1,11 +0,12 @@ | ||
export default { | ||
base: { | ||
preventDefault: 'auto', | ||
deltaSource : 'page', | ||
}, | ||
perAction: { | ||
enabled : false, | ||
origin: { x: 0, y: 0 }, | ||
}, | ||
export const defaults = { | ||
base: { | ||
preventDefault: 'auto', | ||
deltaSource: 'page', | ||
}, | ||
perAction: { | ||
enabled: false, | ||
origin: { x: 0, y: 0 }, | ||
}, | ||
}; | ||
export default defaults; | ||
//# sourceMappingURL=defaultOptions.js.map |
@@ -1,63 +0,54 @@ | ||
import * as arr from '@interactjs/utils/arr'; | ||
import extend from '@interactjs/utils/extend'; | ||
import * as arr from '@interactjs/utils/arr'; | ||
import extend from '@interactjs/utils/extend'; | ||
import normalize from '@interactjs/utils/normalizeListeners'; | ||
function fireUntilImmediateStopped (event, listeners) { | ||
for (const listener of listeners) { | ||
if (event.immediatePropagationStopped) { break; } | ||
listener(event); | ||
} | ||
function fireUntilImmediateStopped(event, listeners) { | ||
for (const listener of listeners) { | ||
if (event.immediatePropagationStopped) { | ||
break; | ||
} | ||
listener(event); | ||
} | ||
} | ||
class Eventable { | ||
constructor (options) { | ||
this.options = extend({}, options || {}); | ||
this.types = {}; | ||
this.propagationStopped = this.immediatePropagationStopped = false; | ||
} | ||
fire (event) { | ||
let listeners; | ||
const global = this.global; | ||
// Interactable#on() listeners | ||
if ((listeners = this.types[event.type])) { | ||
fireUntilImmediateStopped(event, listeners); | ||
constructor(options) { | ||
this.types = {}; | ||
this.propagationStopped = false; | ||
this.immediatePropagationStopped = false; | ||
this.options = extend({}, options || {}); | ||
} | ||
// interact.on() listeners | ||
if (!event.propagationStopped && global && (listeners = global[event.type])) { | ||
fireUntilImmediateStopped(event, listeners); | ||
fire(event) { | ||
let listeners; | ||
const global = this.global; | ||
// Interactable#on() listeners | ||
if ((listeners = this.types[event.type])) { | ||
fireUntilImmediateStopped(event, listeners); | ||
} | ||
// interact.on() listeners | ||
if (!event.propagationStopped && global && (listeners = global[event.type])) { | ||
fireUntilImmediateStopped(event, listeners); | ||
} | ||
} | ||
} | ||
on (type, listener) { | ||
const listeners = normalize(type, listener); | ||
for (type in listeners) { | ||
this.types[type] = arr.merge(this.types[type] || [], listeners[type]); | ||
on(type, listener) { | ||
const listeners = normalize(type, listener); | ||
for (type in listeners) { | ||
this.types[type] = arr.merge(this.types[type] || [], listeners[type]); | ||
} | ||
} | ||
} | ||
off (type, listener) { | ||
const listeners = normalize(type, listener); | ||
for (type in listeners) { | ||
const eventList = this.types[type]; | ||
if (!eventList || !eventList.length) { continue; } | ||
for (listener of listeners[type]) { | ||
const index = eventList.indexOf(listener); | ||
if (index !== -1) { | ||
eventList.splice(index, 1); | ||
off(type, listener) { | ||
const listeners = normalize(type, listener); | ||
for (type in listeners) { | ||
const eventList = this.types[type]; | ||
if (!eventList || !eventList.length) { | ||
continue; | ||
} | ||
for (listener of listeners[type]) { | ||
const index = eventList.indexOf(listener); | ||
if (index !== -1) { | ||
eventList.splice(index, 1); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
export default Eventable; | ||
//# sourceMappingURL=Eventable.js.map |
@@ -1,342 +0,297 @@ | ||
import clone from '@interactjs/utils/clone'; | ||
import * as is from '@interactjs/utils/is'; | ||
import events from '@interactjs/utils/events'; | ||
import extend from '@interactjs/utils/extend'; | ||
import * as arr from '@interactjs/utils/arr'; | ||
import * as arr from '@interactjs/utils/arr'; | ||
import browser from '@interactjs/utils/browser'; | ||
import clone from '@interactjs/utils/clone'; | ||
import { getElementRect, nodeContains, trySelector } from '@interactjs/utils/domUtils'; | ||
import events from '@interactjs/utils/events'; | ||
import extend from '@interactjs/utils/extend'; | ||
import * as is from '@interactjs/utils/is'; | ||
import normalizeListeners from '@interactjs/utils/normalizeListeners'; | ||
import { getWindow } from '@interactjs/utils/window'; | ||
import Eventable from './Eventable'; | ||
import { | ||
getElementRect, | ||
nodeContains, | ||
trySelector, | ||
} from '@interactjs/utils/domUtils'; | ||
import { getWindow } from '@interactjs/utils/window'; | ||
import browser from '@interactjs/utils/browser'; | ||
class Interactable { | ||
get _defaults () { | ||
return { | ||
base: {}, | ||
perAction: {}, | ||
}; | ||
} | ||
/** */ | ||
constructor (target, options, defaultContext) { | ||
this._actions = options.actions; | ||
this.target = target; | ||
this.events = new Eventable(); | ||
this._context = options.context || defaultContext; | ||
this._win = getWindow(trySelector(target)? this._context : target); | ||
this._doc = this._win.document; | ||
this.set(options); | ||
} | ||
setOnEvents (actionName, phases) { | ||
if (is.func(phases.onstart) ) { this.on(`${actionName}start` , phases.onstart ); } | ||
if (is.func(phases.onmove) ) { this.on(`${actionName}move` , phases.onmove ); } | ||
if (is.func(phases.onend) ) { this.on(`${actionName}end` , phases.onend ); } | ||
if (is.func(phases.oninertiastart)) { this.on(`${actionName}inertiastart`, phases.oninertiastart); } | ||
return this; | ||
} | ||
updatePerActionListeners (actionName, prev, cur) { | ||
if (is.array(prev)) { | ||
this.off(actionName, prev); | ||
/** */ | ||
export class Interactable { | ||
/** */ | ||
constructor(target, options, defaultContext) { | ||
this.events = new Eventable(); | ||
this._actions = options.actions; | ||
this.target = target; | ||
this._context = options.context || defaultContext; | ||
this._win = getWindow(trySelector(target) ? this._context : target); | ||
this._doc = this._win.document; | ||
this.set(options); | ||
} | ||
if (is.array(cur)) { | ||
this.on(actionName, cur); | ||
get _defaults() { | ||
return { | ||
base: {}, | ||
perAction: {}, | ||
}; | ||
} | ||
} | ||
setPerAction (actionName, options) { | ||
const defaults = this._defaults; | ||
// for all the default per-action options | ||
for (const optionName in options) { | ||
const actionOptions = this.options[actionName]; | ||
const optionValue = options[optionName]; | ||
const isArray = is.array(optionValue); | ||
// remove old event listeners and add new ones | ||
if (optionName === 'listeners') { | ||
this.updatePerActionListeners(actionName, actionOptions.listeners, optionValue); | ||
} | ||
// if the option value is an array | ||
if (isArray) { | ||
actionOptions[optionName] = arr.from(optionValue); | ||
} | ||
// if the option value is an object | ||
else if (!isArray && is.plainObject(optionValue)) { | ||
// copy the object | ||
actionOptions[optionName] = extend( | ||
actionOptions[optionName] || {}, | ||
clone(optionValue)); | ||
// set anabled field to true if it exists in the defaults | ||
if (is.object(defaults.perAction[optionName]) && 'enabled' in defaults.perAction[optionName]) { | ||
actionOptions[optionName].enabled = optionValue.enabled === false? false : true; | ||
setOnEvents(actionName, phases) { | ||
if (is.func(phases.onstart)) { | ||
this.on(`${actionName}start`, phases.onstart); | ||
} | ||
} | ||
// if the option value is a boolean and the default is an object | ||
else if (is.bool(optionValue) && is.object(defaults.perAction[optionName])) { | ||
actionOptions[optionName].enabled = optionValue; | ||
} | ||
// if it's anything else, do a plain assignment | ||
else { | ||
actionOptions[optionName] = optionValue; | ||
} | ||
if (is.func(phases.onmove)) { | ||
this.on(`${actionName}move`, phases.onmove); | ||
} | ||
if (is.func(phases.onend)) { | ||
this.on(`${actionName}end`, phases.onend); | ||
} | ||
if (is.func(phases.oninertiastart)) { | ||
this.on(`${actionName}inertiastart`, phases.oninertiastart); | ||
} | ||
return this; | ||
} | ||
} | ||
/** | ||
* The default function to get an Interactables bounding rect. Can be | ||
* overridden using {@link Interactable.rectChecker}. | ||
* | ||
* @param {Element} [element] The element to measure. | ||
* @return {object} The object's bounding rectangle. | ||
*/ | ||
getRect (element) { | ||
element = element || this.target; | ||
if (is.string(this.target) && !(is.element(element))) { | ||
element = this._context.querySelector(this.target); | ||
updatePerActionListeners(actionName, prev, cur) { | ||
if (is.array(prev)) { | ||
this.off(actionName, prev); | ||
} | ||
if (is.array(cur)) { | ||
this.on(actionName, cur); | ||
} | ||
} | ||
return getElementRect(element); | ||
} | ||
/** | ||
* Returns or sets the function used to calculate the interactable's | ||
* element's rectangle | ||
* | ||
* @param {function} [checker] A function which returns this Interactable's | ||
* bounding rectangle. See {@link Interactable.getRect} | ||
* @return {function | object} The checker function or this Interactable | ||
*/ | ||
rectChecker (checker) { | ||
if (is.func(checker)) { | ||
this.getRect = checker; | ||
return this; | ||
setPerAction(actionName, options) { | ||
const defaults = this._defaults; | ||
// for all the default per-action options | ||
for (const optionName in options) { | ||
const actionOptions = this.options[actionName]; | ||
const optionValue = options[optionName]; | ||
const isArray = is.array(optionValue); | ||
// remove old event listeners and add new ones | ||
if (optionName === 'listeners') { | ||
this.updatePerActionListeners(actionName, actionOptions.listeners, optionValue); | ||
} | ||
// if the option value is an array | ||
if (isArray) { | ||
actionOptions[optionName] = arr.from(optionValue); | ||
} | ||
// if the option value is an object | ||
else if (!isArray && is.plainObject(optionValue)) { | ||
// copy the object | ||
actionOptions[optionName] = extend(actionOptions[optionName] || {}, clone(optionValue)); | ||
// set anabled field to true if it exists in the defaults | ||
if (is.object(defaults.perAction[optionName]) && 'enabled' in defaults.perAction[optionName]) { | ||
actionOptions[optionName].enabled = optionValue.enabled === false ? false : true; | ||
} | ||
} | ||
// if the option value is a boolean and the default is an object | ||
else if (is.bool(optionValue) && is.object(defaults.perAction[optionName])) { | ||
actionOptions[optionName].enabled = optionValue; | ||
} | ||
// if it's anything else, do a plain assignment | ||
else { | ||
actionOptions[optionName] = optionValue; | ||
} | ||
} | ||
} | ||
if (checker === null) { | ||
delete this.options.getRect; | ||
return this; | ||
/** | ||
* The default function to get an Interactables bounding rect. Can be | ||
* overridden using {@link Interactable.rectChecker}. | ||
* | ||
* @param {Element} [element] The element to measure. | ||
* @return {object} The object's bounding rectangle. | ||
*/ | ||
getRect(element) { | ||
element = element | ||
? element | ||
: is.element(this.target) | ||
? this.target | ||
: null; | ||
if (is.string(this.target)) { | ||
element = element || this._context.querySelector(this.target); | ||
} | ||
return getElementRect(element); | ||
} | ||
return this.getRect; | ||
} | ||
_backCompatOption (optionName, newValue) { | ||
if (trySelector(newValue) || is.object(newValue)) { | ||
this.options[optionName] = newValue; | ||
for (const action of this._actions.names) { | ||
this.options[action][optionName] = newValue; | ||
} | ||
return this; | ||
/** | ||
* Returns or sets the function used to calculate the interactable's | ||
* element's rectangle | ||
* | ||
* @param {function} [checker] A function which returns this Interactable's | ||
* bounding rectangle. See {@link Interactable.getRect} | ||
* @return {function | object} The checker function or this Interactable | ||
*/ | ||
rectChecker(checker) { | ||
if (is.func(checker)) { | ||
this.getRect = checker; | ||
return this; | ||
} | ||
if (checker === null) { | ||
delete this.options.getRect; | ||
return this; | ||
} | ||
return this.getRect; | ||
} | ||
return this.options[optionName]; | ||
} | ||
/** | ||
* Gets or sets the origin of the Interactable's element. The x and y | ||
* of the origin will be subtracted from action event coordinates. | ||
* | ||
* @param {Element | object | string} [origin] An HTML or SVG Element whose | ||
* rect will be used, an object eg. { x: 0, y: 0 } or string 'parent', 'self' | ||
* or any CSS selector | ||
* | ||
* @return {object} The current origin or this Interactable | ||
*/ | ||
origin (newValue) { | ||
return this._backCompatOption('origin', newValue); | ||
} | ||
/** | ||
* Returns or sets the mouse coordinate types used to calculate the | ||
* movement of the pointer. | ||
* | ||
* @param {string} [newValue] Use 'client' if you will be scrolling while | ||
* interacting; Use 'page' if you want autoScroll to work | ||
* @return {string | object} The current deltaSource or this Interactable | ||
*/ | ||
deltaSource (newValue) { | ||
if (newValue === 'page' || newValue === 'client') { | ||
this.options.deltaSource = newValue; | ||
return this; | ||
_backCompatOption(optionName, newValue) { | ||
if (trySelector(newValue) || is.object(newValue)) { | ||
this.options[optionName] = newValue; | ||
for (const action of this._actions.names) { | ||
this.options[action][optionName] = newValue; | ||
} | ||
return this; | ||
} | ||
return this.options[optionName]; | ||
} | ||
return this.options.deltaSource; | ||
} | ||
/** | ||
* Gets the selector context Node of the Interactable. The default is | ||
* `window.document`. | ||
* | ||
* @return {Node} The context Node of this Interactable | ||
*/ | ||
context () { | ||
return this._context; | ||
} | ||
inContext (element) { | ||
return (this._context === element.ownerDocument | ||
/** | ||
* Gets or sets the origin of the Interactable's element. The x and y | ||
* of the origin will be subtracted from action event coordinates. | ||
* | ||
* @param {Element | object | string} [origin] An HTML or SVG Element whose | ||
* rect will be used, an object eg. { x: 0, y: 0 } or string 'parent', 'self' | ||
* or any CSS selector | ||
* | ||
* @return {object} The current origin or this Interactable | ||
*/ | ||
origin(newValue) { | ||
return this._backCompatOption('origin', newValue); | ||
} | ||
/** | ||
* Returns or sets the mouse coordinate types used to calculate the | ||
* movement of the pointer. | ||
* | ||
* @param {string} [newValue] Use 'client' if you will be scrolling while | ||
* interacting; Use 'page' if you want autoScroll to work | ||
* @return {string | object} The current deltaSource or this Interactable | ||
*/ | ||
deltaSource(newValue) { | ||
if (newValue === 'page' || newValue === 'client') { | ||
this.options.deltaSource = newValue; | ||
return this; | ||
} | ||
return this.options.deltaSource; | ||
} | ||
/** | ||
* Gets the selector context Node of the Interactable. The default is | ||
* `window.document`. | ||
* | ||
* @return {Node} The context Node of this Interactable | ||
*/ | ||
context() { | ||
return this._context; | ||
} | ||
inContext(element) { | ||
return (this._context === element.ownerDocument | ||
|| nodeContains(this._context, element)); | ||
} | ||
/** | ||
* Calls listeners for the given InteractEvent type bound globally | ||
* and directly to this Interactable | ||
* | ||
* @param {InteractEvent} iEvent The InteractEvent object to be fired on this | ||
* Interactable | ||
* @return {Interactable} this Interactable | ||
*/ | ||
fire (iEvent) { | ||
this.events.fire(iEvent); | ||
return this; | ||
} | ||
_onOff (method, typeArg, listenerArg, options) { | ||
if (is.object(typeArg) && !is.array(typeArg)) { | ||
options = listenerArg; | ||
listenerArg = null; | ||
} | ||
const addRemove = method === 'on' ? 'add' : 'remove'; | ||
const listeners = normalizeListeners(typeArg, listenerArg); | ||
for (let type in listeners) { | ||
if (type === 'wheel') { type = browser.wheelEvent; } | ||
for (const listener of listeners[type]) { | ||
// if it is an action event type | ||
if (arr.contains(this._actions.eventTypes, type)) { | ||
this.events[method](type, listener); | ||
/** | ||
* Calls listeners for the given InteractEvent type bound globally | ||
* and directly to this Interactable | ||
* | ||
* @param {InteractEvent} iEvent The InteractEvent object to be fired on this | ||
* Interactable | ||
* @return {Interactable} this Interactable | ||
*/ | ||
fire(iEvent) { | ||
this.events.fire(iEvent); | ||
return this; | ||
} | ||
_onOff(method, typeArg, listenerArg, options) { | ||
if (is.object(typeArg) && !is.array(typeArg)) { | ||
options = listenerArg; | ||
listenerArg = null; | ||
} | ||
// delegated event | ||
else if (is.string(this.target)) { | ||
events[`${addRemove}Delegate`](this.target, this._context, type, listener, options); | ||
const addRemove = method === 'on' ? 'add' : 'remove'; | ||
const listeners = normalizeListeners(typeArg, listenerArg); | ||
for (let type in listeners) { | ||
if (type === 'wheel') { | ||
type = browser.wheelEvent; | ||
} | ||
for (const listener of listeners[type]) { | ||
// if it is an action event type | ||
if (arr.contains(this._actions.eventTypes, type)) { | ||
this.events[method](type, listener); | ||
} | ||
// delegated event | ||
else if (is.string(this.target)) { | ||
events[`${addRemove}Delegate`](this.target, this._context, type, listener, options); | ||
} | ||
// remove listener from this Interatable's element | ||
else { | ||
events[addRemove](this.target, type, listener, options); | ||
} | ||
} | ||
} | ||
// remove listener from this Interatable's element | ||
else { | ||
events[addRemove](this.target, type, listener, options); | ||
} | ||
} | ||
return this; | ||
} | ||
return this; | ||
} | ||
/** | ||
* Binds a listener for an InteractEvent, pointerEvent or DOM event. | ||
* | ||
* @param {string | array | object} types The types of events to listen | ||
* for | ||
* @param {function | array | object} [listener] The event listener function(s) | ||
* @param {object | boolean} [options] options object or useCapture flag for | ||
* addEventListener | ||
* @return {Interactable} This Interactable | ||
*/ | ||
on (types, listener, options) { | ||
return this._onOff('on', types, listener, options); | ||
} | ||
/** | ||
* Removes an InteractEvent, pointerEvent or DOM event listener. | ||
* | ||
* @param {string | array | object} types The types of events that were | ||
* listened for | ||
* @param {function | array | object} [listener] The event listener function(s) | ||
* @param {object | boolean} [options] options object or useCapture flag for | ||
* removeEventListener | ||
* @return {Interactable} This Interactable | ||
*/ | ||
off (types, listener, options) { | ||
return this._onOff('off', types, listener, options); | ||
} | ||
/** | ||
* Reset the options of this Interactable | ||
* | ||
* @param {object} options The new settings to apply | ||
* @return {object} This Interactable | ||
*/ | ||
set (options) { | ||
const defaults = this._defaults; | ||
if (!is.object(options)) { | ||
options = {}; | ||
/** | ||
* Binds a listener for an InteractEvent, pointerEvent or DOM event. | ||
* | ||
* @param {string | array | object} types The types of events to listen | ||
* for | ||
* @param {function | array | object} [listener] The event listener function(s) | ||
* @param {object | boolean} [options] options object or useCapture flag for | ||
* addEventListener | ||
* @return {Interactable} This Interactable | ||
*/ | ||
on(types, listener, options) { | ||
return this._onOff('on', types, listener, options); | ||
} | ||
this.options = clone(defaults.base); | ||
for (const actionName in this._actions.methodDict) { | ||
const methodName = this._actions.methodDict[actionName]; | ||
this.options[actionName] = {}; | ||
this.setPerAction(actionName, extend(extend({}, defaults.perAction), defaults[actionName])); | ||
this[methodName](options[actionName]); | ||
/** | ||
* Removes an InteractEvent, pointerEvent or DOM event listener. | ||
* | ||
* @param {string | array | object} types The types of events that were | ||
* listened for | ||
* @param {function | array | object} [listener] The event listener function(s) | ||
* @param {object | boolean} [options] options object or useCapture flag for | ||
* removeEventListener | ||
* @return {Interactable} This Interactable | ||
*/ | ||
off(types, listener, options) { | ||
return this._onOff('off', types, listener, options); | ||
} | ||
for (const setting in options) { | ||
if (is.func(this[setting])) { | ||
this[setting](options[setting]); | ||
} | ||
/** | ||
* Reset the options of this Interactable | ||
* | ||
* @param {object} options The new settings to apply | ||
* @return {object} This Interactable | ||
*/ | ||
set(options) { | ||
const defaults = this._defaults; | ||
if (!is.object(options)) { | ||
options = {}; | ||
} | ||
this.options = clone(defaults.base); | ||
for (const actionName in this._actions.methodDict) { | ||
const methodName = this._actions.methodDict[actionName]; | ||
this.options[actionName] = {}; | ||
this.setPerAction(actionName, extend(extend({}, defaults.perAction), defaults[actionName])); | ||
this[methodName](options[actionName]); | ||
} | ||
for (const setting in options) { | ||
if (is.func(this[setting])) { | ||
this[setting](options[setting]); | ||
} | ||
} | ||
return this; | ||
} | ||
return this; | ||
} | ||
/** | ||
* Remove this interactable from the list of interactables and remove it's | ||
* action capabilities and event listeners | ||
* | ||
* @return {interact} | ||
*/ | ||
unset () { | ||
events.remove(this.target, 'all'); | ||
if (is.string(this.target)) { | ||
// remove delegated events | ||
for (const type in events.delegatedEvents) { | ||
const delegated = events.delegatedEvents[type]; | ||
if (delegated.selectors[0] === this.target | ||
&& delegated.contexts[0] === this._context) { | ||
delegated.selectors.splice(0, 1); | ||
delegated.contexts .splice(0, 1); | ||
delegated.listeners.splice(0, 1); | ||
// remove the arrays if they are empty | ||
if (!delegated.selectors.length) { | ||
delegated[type] = null; | ||
} | ||
/** | ||
* Remove this interactable from the list of interactables and remove it's | ||
* action capabilities and event listeners | ||
* | ||
* @return {interact} | ||
*/ | ||
unset() { | ||
events.remove(this.target, 'all'); | ||
if (is.string(this.target)) { | ||
// remove delegated events | ||
for (const type in events.delegatedEvents) { | ||
const delegated = events.delegatedEvents[type]; | ||
if (delegated.selectors[0] === this.target | ||
&& delegated.contexts[0] === this._context) { | ||
delegated.selectors.splice(0, 1); | ||
delegated.contexts.splice(0, 1); | ||
delegated.listeners.splice(0, 1); | ||
// remove the arrays if they are empty | ||
if (!delegated.selectors.length) { | ||
delegated[type] = null; | ||
} | ||
} | ||
events.remove(this._context, type, events.delegateListener); | ||
events.remove(this._context, type, events.delegateUseCapture, true); | ||
} | ||
} | ||
events.remove(this._context, type, events.delegateListener); | ||
events.remove(this._context, type, events.delegateUseCapture, true); | ||
} | ||
else { | ||
events.remove(this.target, 'all'); | ||
} | ||
} | ||
else { | ||
events.remove(this, 'all'); | ||
} | ||
} | ||
} | ||
export default Interactable; | ||
//# sourceMappingURL=Interactable.js.map |
@@ -0,105 +1,87 @@ | ||
import { matchesSelector, nodeContains } from '@interactjs/utils/domUtils'; | ||
import events from '@interactjs/utils/events'; | ||
import * as is from '@interactjs/utils/is'; | ||
import events from '@interactjs/utils/events'; | ||
import { nodeContains, matchesSelector } from '@interactjs/utils/domUtils'; | ||
import { getWindow } from '@interactjs/utils/window'; | ||
function preventDefault (interactable, newValue) { | ||
if (/^(always|never|auto)$/.test(newValue)) { | ||
interactable.options.preventDefault = newValue; | ||
return interactable; | ||
} | ||
if (is.bool(newValue)) { | ||
interactable.options.preventDefault = newValue? 'always' : 'never'; | ||
return interactable; | ||
} | ||
return interactable.options.preventDefault; | ||
function preventDefault(interactable, newValue) { | ||
if (/^(always|never|auto)$/.test(newValue)) { | ||
interactable.options.preventDefault = newValue; | ||
return interactable; | ||
} | ||
if (is.bool(newValue)) { | ||
interactable.options.preventDefault = newValue ? 'always' : 'never'; | ||
return interactable; | ||
} | ||
return interactable.options.preventDefault; | ||
} | ||
function checkAndPreventDefault (interactable, scope, event) { | ||
const setting = interactable.options.preventDefault; | ||
if (setting === 'never') { return; } | ||
if (setting === 'always') { | ||
function checkAndPreventDefault(interactable, scope, event) { | ||
const setting = interactable.options.preventDefault; | ||
if (setting === 'never') { | ||
return; | ||
} | ||
if (setting === 'always') { | ||
event.preventDefault(); | ||
return; | ||
} | ||
// setting === 'auto' | ||
// if the browser supports passive event listeners and isn't running on iOS, | ||
// don't preventDefault of touch{start,move} events. CSS touch-action and | ||
// user-select should be used instead of calling event.preventDefault(). | ||
if (events.supportsPassive && /^touch(start|move)$/.test(event.type)) { | ||
const doc = getWindow(event.target).document; | ||
const docOptions = scope.getDocOptions(doc); | ||
if (!(docOptions && docOptions.events) || docOptions.events.passive !== false) { | ||
return; | ||
} | ||
} | ||
// don't preventDefault of pointerdown events | ||
if (/^(mouse|pointer|touch)*(down|start)/i.test(event.type)) { | ||
return; | ||
} | ||
// don't preventDefault on editable elements | ||
if (is.element(event.target) | ||
&& matchesSelector(event.target, 'input,select,textarea,[contenteditable=true],[contenteditable=true] *')) { | ||
return; | ||
} | ||
event.preventDefault(); | ||
return; | ||
} | ||
// setting === 'auto' | ||
// if the browser supports passive event listeners and isn't running on iOS, | ||
// don't preventDefault of touch{start,move} events. CSS touch-action and | ||
// user-select should be used instead of calling event.preventDefault(). | ||
if (events.supportsPassive && /^touch(start|move)$/.test(event.type)) { | ||
const doc = getWindow(event.target).document; | ||
const docOptions = scope.getDocOptions(doc); | ||
if (!(docOptions && docOptions.events) || docOptions.events.passive !== false) { | ||
return; | ||
} | ||
} | ||
// don't preventDefault of pointerdown events | ||
if (/^(mouse|pointer|touch)*(down|start)/i.test(event.type)) { | ||
return; | ||
} | ||
// don't preventDefault on editable elements | ||
if (is.element(event.target) | ||
&& matchesSelector(event.target, 'input,select,textarea,[contenteditable=true],[contenteditable=true] *')) { | ||
return; | ||
} | ||
event.preventDefault(); | ||
} | ||
function onInteractionEvent ({ interaction, event }) { | ||
if (interaction.target) { | ||
interaction.target.checkAndPreventDefault(event); | ||
} | ||
} | ||
export function install (scope) { | ||
/** @lends Interactable */ | ||
const Interactable = scope.Interactable; | ||
/** | ||
* Returns or sets whether to prevent the browser's default behaviour in | ||
* response to pointer events. Can be set to: | ||
* - `'always'` to always prevent | ||
* - `'never'` to never prevent | ||
* - `'auto'` to let interact.js try to determine what would be best | ||
* | ||
* @param {string} [newValue] `'always'`, `'never'` or `'auto'` | ||
* @return {string | Interactable} The current setting or this Interactable | ||
*/ | ||
Interactable.prototype.preventDefault = function (newValue) { | ||
return preventDefault(this, newValue); | ||
}; | ||
Interactable.prototype.checkAndPreventDefault = function (event) { | ||
return checkAndPreventDefault(this, scope, event); | ||
}; | ||
for (const eventSignal of ['down', 'move', 'up', 'cancel']) { | ||
scope.interactions.signals.on(eventSignal, onInteractionEvent); | ||
} | ||
// prevent native HTML5 drag on interact.js target elements | ||
scope.interactions.eventMap.dragstart = function preventNativeDrag (event) { | ||
for (const interaction of scope.interactions.list) { | ||
if (interaction.element | ||
&& (interaction.element === event.target | ||
|| nodeContains(interaction.element, event.target))) { | ||
function onInteractionEvent({ interaction, event }) { | ||
if (interaction.target) { | ||
interaction.target.checkAndPreventDefault(event); | ||
return; | ||
} | ||
} | ||
}; | ||
} | ||
export function install(scope) { | ||
/** @lends Interactable */ | ||
const Interactable = scope.Interactable; | ||
/** | ||
* Returns or sets whether to prevent the browser's default behaviour in | ||
* response to pointer events. Can be set to: | ||
* - `'always'` to always prevent | ||
* - `'never'` to never prevent | ||
* - `'auto'` to let interact.js try to determine what would be best | ||
* | ||
* @param {string} [newValue] `'always'`, `'never'` or `'auto'` | ||
* @return {string | Interactable} The current setting or this Interactable | ||
*/ | ||
Interactable.prototype.preventDefault = function (newValue) { | ||
return preventDefault(this, newValue); | ||
}; | ||
Interactable.prototype.checkAndPreventDefault = function (event) { | ||
return checkAndPreventDefault(this, scope, event); | ||
}; | ||
for (const eventSignal of ['down', 'move', 'up', 'cancel']) { | ||
scope.interactions.signals.on(eventSignal, onInteractionEvent); | ||
} | ||
// prevent native HTML5 drag on interact.js target elements | ||
scope.interactions.eventMap.dragstart = function preventNativeDrag(event) { | ||
for (const interaction of scope.interactions.list) { | ||
if (interaction.element | ||
&& (interaction.element === event.target | ||
|| nodeContains(interaction.element, event.target))) { | ||
interaction.target.checkAndPreventDefault(event); | ||
return; | ||
} | ||
} | ||
}; | ||
} | ||
export default { install }; | ||
//# sourceMappingURL=interactablePreventDefault.js.map |
@@ -1,146 +0,126 @@ | ||
import extend from '@interactjs/utils/extend'; | ||
import extend from '@interactjs/utils/extend'; | ||
import getOriginXY from '@interactjs/utils/getOriginXY'; | ||
import defaults from './defaultOptions'; | ||
import hypot from '@interactjs/utils/hypot'; | ||
import hypot from '@interactjs/utils/hypot'; | ||
import defaults from './defaultOptions'; | ||
class InteractEvent { | ||
/** */ | ||
constructor (interaction, event, actionName, phase, element, related, preEnd, type) { | ||
element = element || interaction.element; | ||
const target = interaction.target; | ||
const deltaSource = (target && target.options || defaults).deltaSource; | ||
const origin = getOriginXY(target, element, actionName); | ||
const starting = phase === 'start'; | ||
const ending = phase === 'end'; | ||
const prevEvent = starting? this : interaction.prevEvent; | ||
const coords = starting | ||
? interaction.coords.start | ||
: ending | ||
? { page: prevEvent.page, client: prevEvent.client, timeStamp: interaction.coords.cur.timeStamp } | ||
: interaction.coords.cur; | ||
this.page = extend({}, coords.page); | ||
this.client = extend({}, coords.client); | ||
this.timeStamp = coords.timeStamp; | ||
if (!ending) { | ||
this.page.x -= origin.x; | ||
this.page.y -= origin.y; | ||
this.client.x -= origin.x; | ||
this.client.y -= origin.y; | ||
/** */ | ||
constructor(interaction, event, actionName, phase, element, related, preEnd, type) { | ||
this.immediatePropagationStopped = false; | ||
this.propagationStopped = false; | ||
element = element || interaction.element; | ||
const target = interaction.target; | ||
const deltaSource = (target && target.options || defaults).deltaSource; | ||
const origin = getOriginXY(target, element, actionName); | ||
const starting = phase === 'start'; | ||
const ending = phase === 'end'; | ||
const prevEvent = starting ? this : interaction.prevEvent; | ||
const coords = starting | ||
? interaction.coords.start | ||
: ending | ||
? { page: prevEvent.page, client: prevEvent.client, timeStamp: interaction.coords.cur.timeStamp } | ||
: interaction.coords.cur; | ||
this.page = extend({}, coords.page); | ||
this.client = extend({}, coords.client); | ||
this.timeStamp = coords.timeStamp; | ||
if (!ending) { | ||
this.page.x -= origin.x; | ||
this.page.y -= origin.y; | ||
this.client.x -= origin.x; | ||
this.client.y -= origin.y; | ||
} | ||
this.ctrlKey = event.ctrlKey; | ||
this.altKey = event.altKey; | ||
this.shiftKey = event.shiftKey; | ||
this.metaKey = event.metaKey; | ||
this.button = event.button; | ||
this.buttons = event.buttons; | ||
this.target = element; | ||
this.currentTarget = element; | ||
this.relatedTarget = related || null; | ||
this.preEnd = preEnd; | ||
this.type = type || (actionName + (phase || '')); | ||
this.interaction = interaction; | ||
this.interactable = target; | ||
this.t0 = starting | ||
? interaction.pointers[interaction.pointers.length - 1].downTime | ||
: prevEvent.t0; | ||
this.x0 = interaction.coords.start.page.x - origin.x; | ||
this.y0 = interaction.coords.start.page.y - origin.y; | ||
this.clientX0 = interaction.coords.start.client.x - origin.x; | ||
this.clientY0 = interaction.coords.start.client.y - origin.y; | ||
if (starting || ending) { | ||
this.delta = { x: 0, y: 0 }; | ||
} | ||
else { | ||
this.delta = { | ||
x: this[deltaSource].x - prevEvent[deltaSource].x, | ||
y: this[deltaSource].y - prevEvent[deltaSource].y, | ||
}; | ||
} | ||
this.dt = interaction.coords.delta.timeStamp; | ||
this.duration = this.timeStamp - this.t0; | ||
// velocity and speed in pixels per second | ||
this.velocity = extend({}, interaction.coords.velocity[deltaSource]); | ||
this.speed = hypot(this.velocity.x, this.velocity.y); | ||
this.swipe = (ending || phase === 'inertiastart') ? this.getSwipe() : null; | ||
} | ||
this.ctrlKey = event.ctrlKey; | ||
this.altKey = event.altKey; | ||
this.shiftKey = event.shiftKey; | ||
this.metaKey = event.metaKey; | ||
this.button = event.button; | ||
this.buttons = event.buttons; | ||
this.target = element; | ||
this.currentTarget = element; | ||
this.relatedTarget = related || null; | ||
this.preEnd = preEnd; | ||
this.type = type || (actionName + (phase || '')); | ||
this.interaction = interaction; | ||
this.interactable = target; | ||
this.t0 = starting | ||
? interaction.pointers[interaction.pointers.length - 1].downTime | ||
: prevEvent.t0; | ||
this.x0 = interaction.coords.start.page.x - origin.x; | ||
this.y0 = interaction.coords.start.page.y - origin.y; | ||
this.clientX0 = interaction.coords.start.client.x - origin.x; | ||
this.clientY0 = interaction.coords.start.client.y - origin.y; | ||
if (starting || ending) { | ||
this.delta = { x: 0, y: 0 }; | ||
get pageX() { return this.page.x; } | ||
get pageY() { return this.page.y; } | ||
set pageX(value) { this.page.x = value; } | ||
set pageY(value) { this.page.y = value; } | ||
get clientX() { return this.client.x; } | ||
get clientY() { return this.client.y; } | ||
set clientX(value) { this.client.x = value; } | ||
set clientY(value) { this.client.y = value; } | ||
get dx() { return this.delta.x; } | ||
get dy() { return this.delta.y; } | ||
set dx(value) { this.delta.x = value; } | ||
set dy(value) { this.delta.y = value; } | ||
get velocityX() { return this.velocity.x; } | ||
get velocityY() { return this.velocity.y; } | ||
set velocityX(value) { this.velocity.x = value; } | ||
set velocityY(value) { this.velocity.y = value; } | ||
getSwipe() { | ||
const interaction = this.interaction; | ||
if (interaction.prevEvent.speed < 600 | ||
|| this.timeStamp - interaction.prevEvent.timeStamp > 150) { | ||
return null; | ||
} | ||
let angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI; | ||
const overlap = 22.5; | ||
if (angle < 0) { | ||
angle += 360; | ||
} | ||
const left = 135 - overlap <= angle && angle < 225 + overlap; | ||
const up = 225 - overlap <= angle && angle < 315 + overlap; | ||
const right = !left && (315 - overlap <= angle || angle < 45 + overlap); | ||
const down = !up && 45 - overlap <= angle && angle < 135 + overlap; | ||
return { | ||
up, | ||
down, | ||
left, | ||
right, | ||
angle, | ||
speed: interaction.prevEvent.speed, | ||
velocity: { | ||
x: interaction.prevEvent.velocityX, | ||
y: interaction.prevEvent.velocityY, | ||
}, | ||
}; | ||
} | ||
else { | ||
this.delta = { | ||
x: this[deltaSource].x - prevEvent[deltaSource].x, | ||
y: this[deltaSource].y - prevEvent[deltaSource].y, | ||
}; | ||
preventDefault() { } | ||
/** | ||
* Don't call listeners on the remaining targets | ||
*/ | ||
stopImmediatePropagation() { | ||
this.immediatePropagationStopped = this.propagationStopped = true; | ||
} | ||
this.dt = interaction.coords.delta.timeStamp; | ||
this.duration = this.timeStamp - this.t0; | ||
// velocity and speed in pixels per second | ||
this.velocity = extend({}, interaction.coords.velocity[deltaSource]); | ||
this.speed = hypot(this.velocity.x, this.velocity.y); | ||
this.swipe = (ending || phase === 'inertiastart')? this.getSwipe() : null; | ||
} | ||
get pageX () { return this.page.x; } | ||
get pageY () { return this.page.y; } | ||
set pageX (value) { this.page.x = value; } | ||
set pageY (value) { this.page.y = value; } | ||
get clientX () { return this.client.x; } | ||
get clientY () { return this.client.y; } | ||
set clientX (value) { this.client.x = value; } | ||
set clientY (value) { this.client.y = value; } | ||
get dx () { return this.delta.x; } | ||
get dy () { return this.delta.y; } | ||
set dx (value) { this.delta.x = value; } | ||
set dy (value) { this.delta.y = value; } | ||
get velocityX () { return this.velocity.x; } | ||
get velocityY () { return this.velocity.y; } | ||
set velocityX (value) { this.velocity.x = value; } | ||
set velocityY (value) { this.velocity.y = value; } | ||
getSwipe () { | ||
const interaction = this.interaction; | ||
if (interaction.prevEvent.speed < 600 | ||
|| this.timeStamp - interaction.prevEvent.timeStamp > 150) { | ||
return null; | ||
/** | ||
* Don't call any other listeners (even on the current target) | ||
*/ | ||
stopPropagation() { | ||
this.propagationStopped = true; | ||
} | ||
let angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI; | ||
const overlap = 22.5; | ||
if (angle < 0) { | ||
angle += 360; | ||
} | ||
const left = 135 - overlap <= angle && angle < 225 + overlap; | ||
const up = 225 - overlap <= angle && angle < 315 + overlap; | ||
const right = !left && (315 - overlap <= angle || angle < 45 + overlap); | ||
const down = !up && 45 - overlap <= angle && angle < 135 + overlap; | ||
return { | ||
up, | ||
down, | ||
left, | ||
right, | ||
angle, | ||
speed: interaction.prevEvent.speed, | ||
velocity: { | ||
x: interaction.prevEvent.velocityX, | ||
y: interaction.prevEvent.velocityY, | ||
}, | ||
}; | ||
} | ||
preventDefault () {} | ||
/** */ | ||
stopImmediatePropagation () { | ||
this.immediatePropagationStopped = this.propagationStopped = true; | ||
} | ||
/** */ | ||
stopPropagation () { | ||
this.propagationStopped = true; | ||
} | ||
} | ||
export default InteractEvent; | ||
//# sourceMappingURL=InteractEvent.js.map |
@@ -0,429 +1,360 @@ | ||
import * as utils from '@interactjs/utils'; | ||
import InteractEvent from './InteractEvent'; | ||
import * as utils from '@interactjs/utils'; | ||
class Interaction { | ||
get pointerMoveTolerance () { | ||
return 1; | ||
} | ||
/** */ | ||
constructor ({ pointerType, signals }) { | ||
this._signals = signals; | ||
this.target = null; // current interactable being interacted with | ||
this.element = null; // the target element of the interactable | ||
this.prepared = { // action that's ready to be fired on next move event | ||
name : null, | ||
axis : null, | ||
edges: null, | ||
}; | ||
// keep track of added pointers | ||
this.pointers = [/* { id, pointer, event, target, downTime }*/]; | ||
this.coords = { | ||
// Starting InteractEvent pointer coordinates | ||
start: utils.pointer.newCoords(), | ||
// Previous native pointer move event coordinates | ||
prev: utils.pointer.newCoords(), | ||
// current native pointer move event coordinates | ||
cur: utils.pointer.newCoords(), | ||
// Change in coordinates and time of the pointer | ||
delta: utils.pointer.newCoords(), | ||
// pointer velocity | ||
velocity: utils.pointer.newCoords(), | ||
}; | ||
this.downEvent = null; // pointerdown/mousedown/touchstart event | ||
this.downPointer = {}; | ||
this._latestPointer = { | ||
pointer: null, | ||
event: null, | ||
eventTarget : null, | ||
}; | ||
this.prevEvent = null; // previous action event | ||
this.pointerIsDown = false; | ||
this.pointerWasMoved = false; | ||
this._interacting = false; | ||
this._ending = false; | ||
this.pointerType = pointerType; | ||
this._signals.fire('new', this); | ||
} | ||
pointerDown (pointer, event, eventTarget) { | ||
const pointerIndex = this.updatePointer(pointer, event, eventTarget, true); | ||
this._signals.fire('down', { | ||
pointer, | ||
event, | ||
eventTarget, | ||
pointerIndex, | ||
interaction: this, | ||
}); | ||
} | ||
/** | ||
* ```js | ||
* interact(target) | ||
* .draggable({ | ||
* // disable the default drag start by down->move | ||
* manualStart: true | ||
* }) | ||
* // start dragging after the user holds the pointer down | ||
* .on('hold', function (event) { | ||
* var interaction = event.interaction; | ||
* | ||
* if (!interaction.interacting()) { | ||
* interaction.start({ name: 'drag' }, | ||
* event.interactable, | ||
* event.currentTarget); | ||
* } | ||
* }); | ||
* ``` | ||
* | ||
* Start an action with the given Interactable and Element as tartgets. The | ||
* action must be enabled for the target Interactable and an appropriate | ||
* number of pointers must be held down - 1 for drag/resize, 2 for gesture. | ||
* | ||
* Use it with `interactable.<action>able({ manualStart: false })` to always | ||
* [start actions manually](https://github.com/taye/interact.js/issues/114) | ||
* | ||
* @param {object} action The action to be performed - drag, resize, etc. | ||
* @param {Interactable} target The Interactable to target | ||
* @param {Element} element The DOM Element to target | ||
* @return {object} interact | ||
*/ | ||
start (action, target, element) { | ||
if (this.interacting() | ||
|| !this.pointerIsDown | ||
|| this.pointers.length < (action.name === 'gesture'? 2 : 1)) { | ||
return; | ||
export class Interaction { | ||
/** */ | ||
constructor({ pointerType, signals }) { | ||
// current interactable being interacted with | ||
this.target = null; | ||
// the target element of the interactable | ||
this.element = null; | ||
// action that's ready to be fired on next move event | ||
this.prepared = { | ||
name: null, | ||
axis: null, | ||
edges: null, | ||
}; | ||
// keep track of added pointers | ||
this.pointers = []; | ||
// pointerdown/mousedown/touchstart event | ||
this.downEvent = null; | ||
this.downPointer = {}; | ||
this._latestPointer = { | ||
pointer: null, | ||
event: null, | ||
eventTarget: null, | ||
}; | ||
// previous action event | ||
this.prevEvent = null; | ||
this.pointerIsDown = false; | ||
this.pointerWasMoved = false; | ||
this._interacting = false; | ||
this._ending = false; | ||
this.simulation = null; | ||
/** | ||
* @alias Interaction.prototype.move | ||
*/ | ||
this.doMove = utils.warnOnce(function (signalArg) { | ||
this.move(signalArg); | ||
}, 'The interaction.doMove() method has been renamed to interaction.move()'); | ||
this.coords = { | ||
// Starting InteractEvent pointer coordinates | ||
start: utils.pointer.newCoords(), | ||
// Previous native pointer move event coordinates | ||
prev: utils.pointer.newCoords(), | ||
// current native pointer move event coordinates | ||
cur: utils.pointer.newCoords(), | ||
// Change in coordinates and time of the pointer | ||
delta: utils.pointer.newCoords(), | ||
// pointer velocity | ||
velocity: utils.pointer.newCoords(), | ||
}; | ||
this._signals = signals; | ||
this.pointerType = pointerType; | ||
this._signals.fire('new', this); | ||
} | ||
utils.copyAction(this.prepared, action); | ||
this.target = target; | ||
this.element = element; | ||
this._interacting = this._doPhase({ | ||
interaction: this, | ||
event: this.downEvent, | ||
phase: 'start', | ||
}); | ||
} | ||
pointerMove (pointer, event, eventTarget) { | ||
if (!this.simulation) { | ||
this.updatePointer(pointer, event, eventTarget, false); | ||
utils.pointer.setCoords(this.coords.cur, this.pointers.map(p => p.pointer)); | ||
get pointerMoveTolerance() { | ||
return 1; | ||
} | ||
const duplicateMove = (this.coords.cur.page.x === this.coords.prev.page.x | ||
&& this.coords.cur.page.y === this.coords.prev.page.y | ||
&& this.coords.cur.client.x === this.coords.prev.client.x | ||
&& this.coords.cur.client.y === this.coords.prev.client.y); | ||
let dx; | ||
let dy; | ||
// register movement greater than pointerMoveTolerance | ||
if (this.pointerIsDown && !this.pointerWasMoved) { | ||
dx = this.coords.cur.client.x - this.coords.start.client.x; | ||
dy = this.coords.cur.client.y - this.coords.start.client.y; | ||
this.pointerWasMoved = utils.hypot(dx, dy) > this.pointerMoveTolerance; | ||
pointerDown(pointer, event, eventTarget) { | ||
const pointerIndex = this.updatePointer(pointer, event, eventTarget, true); | ||
this._signals.fire('down', { | ||
pointer, | ||
event, | ||
eventTarget, | ||
pointerIndex, | ||
interaction: this, | ||
}); | ||
} | ||
const signalArg = { | ||
pointer, | ||
pointerIndex: this.getPointerIndex(pointer), | ||
event, | ||
eventTarget, | ||
dx, | ||
dy, | ||
duplicate: duplicateMove, | ||
interaction: this, | ||
}; | ||
if (!duplicateMove) { | ||
// set pointer coordinate, time changes and velocity | ||
utils.pointer.setCoordDeltas(this.coords.delta, this.coords.prev, this.coords.cur); | ||
utils.pointer.setCoordVelocity(this.coords.velocity, this.coords.delta); | ||
/** | ||
* ```js | ||
* interact(target) | ||
* .draggable({ | ||
* // disable the default drag start by down->move | ||
* manualStart: true | ||
* }) | ||
* // start dragging after the user holds the pointer down | ||
* .on('hold', function (event) { | ||
* var interaction = event.interaction; | ||
* | ||
* if (!interaction.interacting()) { | ||
* interaction.start({ name: 'drag' }, | ||
* event.interactable, | ||
* event.currentTarget); | ||
* } | ||
* }); | ||
* ``` | ||
* | ||
* Start an action with the given Interactable and Element as tartgets. The | ||
* action must be enabled for the target Interactable and an appropriate | ||
* number of pointers must be held down - 1 for drag/resize, 2 for gesture. | ||
* | ||
* Use it with `interactable.<action>able({ manualStart: false })` to always | ||
* [start actions manually](https://github.com/taye/interact.js/issues/114) | ||
* | ||
* @param {object} action The action to be performed - drag, resize, etc. | ||
* @param {Interactable} target The Interactable to target | ||
* @param {Element} element The DOM Element to target | ||
* @return {object} interact | ||
*/ | ||
start(action, target, element) { | ||
if (this.interacting() | ||
|| !this.pointerIsDown | ||
|| this.pointers.length < (action.name === 'gesture' ? 2 : 1)) { | ||
return; | ||
} | ||
utils.copyAction(this.prepared, action); | ||
this.target = target; | ||
this.element = element; | ||
this._interacting = this._doPhase({ | ||
interaction: this, | ||
event: this.downEvent, | ||
phase: 'start', | ||
}); | ||
} | ||
this._signals.fire('move', signalArg); | ||
if (!duplicateMove) { | ||
// if interacting, fire an 'action-move' signal etc | ||
if (this.interacting()) { | ||
this.move(signalArg); | ||
} | ||
if (this.pointerWasMoved) { | ||
utils.pointer.copyCoords(this.coords.prev, this.coords.cur); | ||
} | ||
pointerMove(pointer, event, eventTarget) { | ||
if (!this.simulation) { | ||
this.updatePointer(pointer, event, eventTarget, false); | ||
utils.pointer.setCoords(this.coords.cur, this.pointers.map(p => p.pointer)); | ||
} | ||
const duplicateMove = (this.coords.cur.page.x === this.coords.prev.page.x | ||
&& this.coords.cur.page.y === this.coords.prev.page.y | ||
&& this.coords.cur.client.x === this.coords.prev.client.x | ||
&& this.coords.cur.client.y === this.coords.prev.client.y); | ||
let dx; | ||
let dy; | ||
// register movement greater than pointerMoveTolerance | ||
if (this.pointerIsDown && !this.pointerWasMoved) { | ||
dx = this.coords.cur.client.x - this.coords.start.client.x; | ||
dy = this.coords.cur.client.y - this.coords.start.client.y; | ||
this.pointerWasMoved = utils.hypot(dx, dy) > this.pointerMoveTolerance; | ||
} | ||
const signalArg = { | ||
pointer, | ||
pointerIndex: this.getPointerIndex(pointer), | ||
event, | ||
eventTarget, | ||
dx, | ||
dy, | ||
duplicate: duplicateMove, | ||
interaction: this, | ||
}; | ||
if (!duplicateMove) { | ||
// set pointer coordinate, time changes and velocity | ||
utils.pointer.setCoordDeltas(this.coords.delta, this.coords.prev, this.coords.cur); | ||
utils.pointer.setCoordVelocity(this.coords.velocity, this.coords.delta); | ||
} | ||
this._signals.fire('move', signalArg); | ||
if (!duplicateMove) { | ||
// if interacting, fire an 'action-move' signal etc | ||
if (this.interacting()) { | ||
this.move(signalArg); | ||
} | ||
if (this.pointerWasMoved) { | ||
utils.pointer.copyCoords(this.coords.prev, this.coords.cur); | ||
} | ||
} | ||
} | ||
} | ||
/** | ||
* ```js | ||
* interact(target) | ||
* .draggable(true) | ||
* .on('dragmove', function (event) { | ||
* if (someCondition) { | ||
* // change the snap settings | ||
* event.interactable.draggable({ snap: { targets: [] }}); | ||
* // fire another move event with re-calculated snap | ||
* event.interaction.move(); | ||
* } | ||
* }); | ||
* ``` | ||
* | ||
* Force a move of the current action at the same coordinates. Useful if | ||
* snap/restrict has been changed and you want a movement with the new | ||
* settings. | ||
*/ | ||
move (signalArg) { | ||
signalArg = utils.extend({ | ||
pointer: this._latestPointer.pointer, | ||
event: this._latestPointer.event, | ||
eventTarget: this._latestPointer.eventTarget, | ||
interaction: this, | ||
noBefore: false, | ||
}, signalArg || {}); | ||
signalArg.phase = 'move'; | ||
this._doPhase(signalArg); | ||
} | ||
// End interact move events and stop auto-scroll unless simulation is running | ||
pointerUp (pointer, event, eventTarget, curEventTarget) { | ||
let pointerIndex = this.getPointerIndex(pointer); | ||
if (pointerIndex === -1) { | ||
pointerIndex = this.updatePointer(pointer, event, eventTarget, false); | ||
/** | ||
* ```js | ||
* interact(target) | ||
* .draggable(true) | ||
* .on('dragmove', function (event) { | ||
* if (someCondition) { | ||
* // change the snap settings | ||
* event.interactable.draggable({ snap: { targets: [] }}); | ||
* // fire another move event with re-calculated snap | ||
* event.interaction.move(); | ||
* } | ||
* }); | ||
* ``` | ||
* | ||
* Force a move of the current action at the same coordinates. Useful if | ||
* snap/restrict has been changed and you want a movement with the new | ||
* settings. | ||
*/ | ||
move(signalArg) { | ||
signalArg = utils.extend({ | ||
pointer: this._latestPointer.pointer, | ||
event: this._latestPointer.event, | ||
eventTarget: this._latestPointer.eventTarget, | ||
interaction: this, | ||
noBefore: false, | ||
}, signalArg || {}); | ||
signalArg.phase = 'move'; | ||
this._doPhase(signalArg); | ||
} | ||
this._signals.fire(/cancel$/i.test(event.type)? 'cancel' : 'up', { | ||
pointer, | ||
pointerIndex, | ||
event, | ||
eventTarget, | ||
curEventTarget, | ||
interaction: this, | ||
}); | ||
if (!this.simulation) { | ||
this.end(event); | ||
// End interact move events and stop auto-scroll unless simulation is running | ||
pointerUp(pointer, event, eventTarget, curEventTarget) { | ||
let pointerIndex = this.getPointerIndex(pointer); | ||
if (pointerIndex === -1) { | ||
pointerIndex = this.updatePointer(pointer, event, eventTarget, false); | ||
} | ||
this._signals.fire(/cancel$/i.test(event.type) ? 'cancel' : 'up', { | ||
pointer, | ||
pointerIndex, | ||
event, | ||
eventTarget, | ||
curEventTarget, | ||
interaction: this, | ||
}); | ||
if (!this.simulation) { | ||
this.end(event); | ||
} | ||
this.pointerIsDown = false; | ||
this.removePointer(pointer, event); | ||
} | ||
this.pointerIsDown = false; | ||
this.removePointer(pointer, event); | ||
} | ||
documentBlur (event) { | ||
this.end(event); | ||
this._signals.fire('blur', { event, interaction: this }); | ||
} | ||
/** | ||
* ```js | ||
* interact(target) | ||
* .draggable(true) | ||
* .on('move', function (event) { | ||
* if (event.pageX > 1000) { | ||
* // end the current action | ||
* event.interaction.end(); | ||
* // stop all further listeners from being called | ||
* event.stopImmediatePropagation(); | ||
* } | ||
* }); | ||
* ``` | ||
* | ||
* @param {PointerEvent} [event] | ||
*/ | ||
end (event) { | ||
this._ending = true; | ||
event = event || this._latestPointer.event; | ||
let endPhaseResult; | ||
if (this.interacting()) { | ||
endPhaseResult = this._doPhase({ | ||
event, | ||
interaction: this, | ||
phase: 'end', | ||
}); | ||
documentBlur(event) { | ||
this.end(event); | ||
this._signals.fire('blur', { event, interaction: this }); | ||
} | ||
this._ending = false; | ||
if (endPhaseResult === true) { | ||
this.stop(); | ||
/** | ||
* ```js | ||
* interact(target) | ||
* .draggable(true) | ||
* .on('move', function (event) { | ||
* if (event.pageX > 1000) { | ||
* // end the current action | ||
* event.interaction.end(); | ||
* // stop all further listeners from being called | ||
* event.stopImmediatePropagation(); | ||
* } | ||
* }); | ||
* ``` | ||
* | ||
* @param {PointerEvent} [event] | ||
*/ | ||
end(event) { | ||
this._ending = true; | ||
event = event || this._latestPointer.event; | ||
let endPhaseResult; | ||
if (this.interacting()) { | ||
endPhaseResult = this._doPhase({ | ||
event, | ||
interaction: this, | ||
phase: 'end', | ||
}); | ||
} | ||
this._ending = false; | ||
if (endPhaseResult === true) { | ||
this.stop(); | ||
} | ||
} | ||
} | ||
currentAction () { | ||
return this._interacting? this.prepared.name: null; | ||
} | ||
interacting () { | ||
return this._interacting; | ||
} | ||
/** */ | ||
stop () { | ||
this._signals.fire('stop', { interaction: this }); | ||
this.target = this.element = null; | ||
this._interacting = false; | ||
this.prepared.name = this.prevEvent = null; | ||
} | ||
getPointerIndex (pointer) { | ||
const pointerId = utils.pointer.getPointerId(pointer); | ||
// mouse and pen interactions may have only one pointer | ||
return (this.pointerType === 'mouse' || this.pointerType === 'pen') | ||
? this.pointers.length - 1 | ||
: utils.arr.findIndex(this.pointers, curPointer => curPointer.id === pointerId); | ||
} | ||
getPointerInfo (pointer) { | ||
return this.pointers[this.getPointerIndex(pointer)]; | ||
} | ||
updatePointer (pointer, event, eventTarget, down) { | ||
const id = utils.pointer.getPointerId(pointer); | ||
let pointerIndex = this.getPointerIndex(pointer); | ||
let pointerInfo = this.pointers[pointerIndex]; | ||
down = down === false | ||
? false | ||
: down || /(down|start)$/i.test(event.type); | ||
if (!pointerInfo) { | ||
pointerInfo = { | ||
id, | ||
pointer, | ||
event, | ||
downTime: null, | ||
downTarget: null, | ||
}; | ||
pointerIndex = this.pointers.length; | ||
this.pointers.push(pointerInfo); | ||
currentAction() { | ||
return this._interacting ? this.prepared.name : null; | ||
} | ||
else { | ||
pointerInfo.pointer = pointer; | ||
interacting() { | ||
return this._interacting; | ||
} | ||
if (down) { | ||
this.pointerIsDown = true; | ||
if (!this.interacting()) { | ||
utils.pointer.setCoords(this.coords.start, this.pointers.map(p => p.pointer)); | ||
utils.pointer.copyCoords(this.coords.cur , this.coords.start); | ||
utils.pointer.copyCoords(this.coords.prev, this.coords.start); | ||
utils.pointer.pointerExtend(this.downPointer, pointer); | ||
this.downEvent = event; | ||
pointerInfo.downTime = this.coords.cur.timeStamp; | ||
pointerInfo.downTarget = eventTarget; | ||
this.pointerWasMoved = false; | ||
} | ||
/** */ | ||
stop() { | ||
this._signals.fire('stop', { interaction: this }); | ||
this.target = this.element = null; | ||
this._interacting = false; | ||
this.prepared.name = this.prevEvent = null; | ||
} | ||
this._updateLatestPointer(pointer, event, eventTarget); | ||
this._signals.fire('update-pointer', { | ||
pointer, | ||
event, | ||
eventTarget, | ||
down, | ||
pointerInfo, | ||
pointerIndex, | ||
interaction: this, | ||
}); | ||
return pointerIndex; | ||
} | ||
removePointer (pointer, event) { | ||
const pointerIndex = this.getPointerIndex(pointer); | ||
if (pointerIndex === -1) { return; } | ||
const pointerInfo = this.pointers[pointerIndex]; | ||
this._signals.fire('remove-pointer', { | ||
pointer, | ||
event, | ||
pointerIndex, | ||
pointerInfo, | ||
interaction: this, | ||
}); | ||
this.pointers.splice(pointerIndex, 1); | ||
} | ||
_updateLatestPointer (pointer, event, eventTarget) { | ||
this._latestPointer.pointer = pointer; | ||
this._latestPointer.event = event; | ||
this._latestPointer.eventTarget = eventTarget; | ||
} | ||
_createPreparedEvent (event, phase, preEnd, type) { | ||
const actionName = this.prepared.name; | ||
return new InteractEvent(this, event, actionName, phase, this.element, null, preEnd, type); | ||
} | ||
_fireEvent (iEvent) { | ||
this.target.fire(iEvent); | ||
if (!this.prevEvent || iEvent.timeStamp >= this.prevEvent.timeStamp) { | ||
this.prevEvent = iEvent; | ||
getPointerIndex(pointer) { | ||
const pointerId = utils.pointer.getPointerId(pointer); | ||
// mouse and pen interactions may have only one pointer | ||
return (this.pointerType === 'mouse' || this.pointerType === 'pen') | ||
? this.pointers.length - 1 | ||
: utils.arr.findIndex(this.pointers, curPointer => curPointer.id === pointerId); | ||
} | ||
} | ||
_doPhase (signalArg) { | ||
const { event, phase, preEnd, type } = signalArg; | ||
if (!signalArg.noBefore) { | ||
const beforeResult = this._signals.fire(`before-action-${phase}`, signalArg); | ||
if (beforeResult === false) { | ||
return false; | ||
} | ||
getPointerInfo(pointer) { | ||
return this.pointers[this.getPointerIndex(pointer)]; | ||
} | ||
const iEvent = signalArg.iEvent = this._createPreparedEvent(event, phase, preEnd, type); | ||
this._signals.fire(`action-${phase}`, signalArg); | ||
this._fireEvent(iEvent); | ||
this._signals.fire(`after-action-${phase}`, signalArg); | ||
return true; | ||
} | ||
updatePointer(pointer, event, eventTarget, down) { | ||
const id = utils.pointer.getPointerId(pointer); | ||
let pointerIndex = this.getPointerIndex(pointer); | ||
let pointerInfo = this.pointers[pointerIndex]; | ||
down = down === false | ||
? false | ||
: down || /(down|start)$/i.test(event.type); | ||
if (!pointerInfo) { | ||
pointerInfo = new PointerInfo(id, pointer, event, null, null); | ||
pointerIndex = this.pointers.length; | ||
this.pointers.push(pointerInfo); | ||
} | ||
else { | ||
pointerInfo.pointer = pointer; | ||
} | ||
if (down) { | ||
this.pointerIsDown = true; | ||
if (!this.interacting()) { | ||
utils.pointer.setCoords(this.coords.start, this.pointers.map(p => p.pointer)); | ||
utils.pointer.copyCoords(this.coords.cur, this.coords.start); | ||
utils.pointer.copyCoords(this.coords.prev, this.coords.start); | ||
utils.pointer.pointerExtend(this.downPointer, pointer); | ||
this.downEvent = event; | ||
pointerInfo.downTime = this.coords.cur.timeStamp; | ||
pointerInfo.downTarget = eventTarget; | ||
this.pointerWasMoved = false; | ||
} | ||
} | ||
this._updateLatestPointer(pointer, event, eventTarget); | ||
this._signals.fire('update-pointer', { | ||
pointer, | ||
event, | ||
eventTarget, | ||
down, | ||
pointerInfo, | ||
pointerIndex, | ||
interaction: this, | ||
}); | ||
return pointerIndex; | ||
} | ||
removePointer(pointer, event) { | ||
const pointerIndex = this.getPointerIndex(pointer); | ||
if (pointerIndex === -1) { | ||
return; | ||
} | ||
const pointerInfo = this.pointers[pointerIndex]; | ||
this._signals.fire('remove-pointer', { | ||
pointer, | ||
event, | ||
pointerIndex, | ||
pointerInfo, | ||
interaction: this, | ||
}); | ||
this.pointers.splice(pointerIndex, 1); | ||
} | ||
_updateLatestPointer(pointer, event, eventTarget) { | ||
this._latestPointer.pointer = pointer; | ||
this._latestPointer.event = event; | ||
this._latestPointer.eventTarget = eventTarget; | ||
} | ||
_createPreparedEvent(event, phase, preEnd, type) { | ||
const actionName = this.prepared.name; | ||
return new InteractEvent(this, event, actionName, phase, this.element, null, preEnd, type); | ||
} | ||
_fireEvent(iEvent) { | ||
this.target.fire(iEvent); | ||
if (!this.prevEvent || iEvent.timeStamp >= this.prevEvent.timeStamp) { | ||
this.prevEvent = iEvent; | ||
} | ||
} | ||
_doPhase(signalArg) { | ||
const { event, phase, preEnd, type } = signalArg; | ||
if (!signalArg.noBefore) { | ||
const beforeResult = this._signals.fire(`before-action-${phase}`, signalArg); | ||
if (beforeResult === false) { | ||
return false; | ||
} | ||
} | ||
const iEvent = signalArg.iEvent = this._createPreparedEvent(event, phase, preEnd, type); | ||
this._signals.fire(`action-${phase}`, signalArg); | ||
this._fireEvent(iEvent); | ||
this._signals.fire(`after-action-${phase}`, signalArg); | ||
return true; | ||
} | ||
} | ||
/** | ||
* @alias Interaction.prototype.move | ||
*/ | ||
Interaction.prototype.doMove = utils.warnOnce( | ||
function (signalArg) { | ||
this.move(signalArg); | ||
}, | ||
'The interaction.doMove() method has been renamed to interaction.move()'); | ||
export class PointerInfo { | ||
constructor(id, pointer, event, downTime, downTarget) { | ||
this.id = id; | ||
this.pointer = pointer; | ||
this.event = event; | ||
this.downTime = downTime; | ||
this.downTarget = downTarget; | ||
} | ||
} | ||
export default Interaction; | ||
//# sourceMappingURL=Interaction.js.map |
@@ -0,210 +1,174 @@ | ||
import browser from '@interactjs/utils/browser'; | ||
import domObjects from '@interactjs/utils/domObjects'; | ||
import events from '@interactjs/utils/events'; | ||
import finder from '@interactjs/utils/interactionFinder'; | ||
import pointerUtils from '@interactjs/utils/pointerUtils'; | ||
import Signals from '@interactjs/utils/Signals'; | ||
import InteractionBase from './Interaction'; | ||
import events from '@interactjs/utils/events'; | ||
import finder from '@interactjs/utils/interactionFinder'; | ||
import browser from '@interactjs/utils/browser'; | ||
import domObjects from '@interactjs/utils/domObjects'; | ||
import pointerUtils from '@interactjs/utils/pointerUtils'; | ||
import Signals from '@interactjs/utils/Signals'; | ||
const methodNames = [ | ||
'pointerDown', 'pointerMove', 'pointerUp', | ||
'updatePointer', 'removePointer', 'windowBlur', | ||
'pointerDown', 'pointerMove', 'pointerUp', | ||
'updatePointer', 'removePointer', 'windowBlur', | ||
]; | ||
function install (scope) { | ||
const signals = new Signals(); | ||
const listeners = {}; | ||
for (const method of methodNames) { | ||
listeners[method] = doOnInteractions(method, scope); | ||
} | ||
const eventMap = { /* 'eventType': listenerFunc */ }; | ||
const pEventTypes = browser.pEventTypes; | ||
if (domObjects.PointerEvent) { | ||
eventMap[pEventTypes.down ] = listeners.pointerDown; | ||
eventMap[pEventTypes.move ] = listeners.pointerMove; | ||
eventMap[pEventTypes.up ] = listeners.pointerUp; | ||
eventMap[pEventTypes.cancel] = listeners.pointerUp; | ||
} | ||
else { | ||
eventMap.mousedown = listeners.pointerDown; | ||
eventMap.mousemove = listeners.pointerMove; | ||
eventMap.mouseup = listeners.pointerUp; | ||
eventMap.touchstart = listeners.pointerDown; | ||
eventMap.touchmove = listeners.pointerMove; | ||
eventMap.touchend = listeners.pointerUp; | ||
eventMap.touchcancel = listeners.pointerUp; | ||
} | ||
eventMap.blur = event => { | ||
for (const interaction of scope.interactions.list) { | ||
interaction.documentBlur(event); | ||
function install(scope) { | ||
const signals = new Signals(); | ||
const listeners = {}; | ||
for (const method of methodNames) { | ||
listeners[method] = doOnInteractions(method, scope); | ||
} | ||
}; | ||
scope.signals.on('add-document' , onDocSignal); | ||
scope.signals.on('remove-document', onDocSignal); | ||
// for ignoring browser's simulated mouse events | ||
scope.prevTouchTime = 0; | ||
scope.Interaction = class Interaction extends InteractionBase { | ||
get pointerMoveTolerance () { | ||
return scope.interactions.pointerMoveTolerance; | ||
const pEventTypes = browser.pEventTypes; | ||
const eventMap = {}; | ||
if (domObjects.PointerEvent) { | ||
eventMap[pEventTypes.down] = listeners.pointerDown; | ||
eventMap[pEventTypes.move] = listeners.pointerMove; | ||
eventMap[pEventTypes.up] = listeners.pointerUp; | ||
eventMap[pEventTypes.cancel] = listeners.pointerUp; | ||
} | ||
set pointerMoveTolerance (value) { | ||
scope.interactions.pointerMoveTolerance = value; | ||
else { | ||
eventMap.mousedown = listeners.pointerDown; | ||
eventMap.mousemove = listeners.pointerMove; | ||
eventMap.mouseup = listeners.pointerUp; | ||
eventMap.touchstart = listeners.pointerDown; | ||
eventMap.touchmove = listeners.pointerMove; | ||
eventMap.touchend = listeners.pointerUp; | ||
eventMap.touchcancel = listeners.pointerUp; | ||
} | ||
}; | ||
scope.interactions = { | ||
signals, | ||
// all active and idle interactions | ||
list: [], | ||
new (options) { | ||
options.signals = signals; | ||
return new scope.Interaction(options); | ||
}, | ||
listeners, | ||
eventMap, | ||
pointerMoveTolerance: 1, | ||
}; | ||
scope.actions = { | ||
names: [], | ||
methodDict: {}, | ||
eventTypes: [], | ||
}; | ||
eventMap.blur = event => { | ||
for (const interaction of scope.interactions.list) { | ||
interaction.documentBlur(event); | ||
} | ||
}; | ||
scope.signals.on('add-document', onDocSignal); | ||
scope.signals.on('remove-document', onDocSignal); | ||
// for ignoring browser's simulated mouse events | ||
scope.prevTouchTime = 0; | ||
scope.Interaction = class Interaction extends InteractionBase { | ||
get pointerMoveTolerance() { | ||
return scope.interactions.pointerMoveTolerance; | ||
} | ||
set pointerMoveTolerance(value) { | ||
scope.interactions.pointerMoveTolerance = value; | ||
} | ||
}; | ||
scope.interactions = { | ||
signals, | ||
// all active and idle interactions | ||
list: [], | ||
new(options) { | ||
options.signals = signals; | ||
return new scope.Interaction(options); | ||
}, | ||
listeners, | ||
eventMap, | ||
pointerMoveTolerance: 1, | ||
}; | ||
scope.actions = { | ||
names: [], | ||
methodDict: {}, | ||
eventTypes: [], | ||
}; | ||
} | ||
function doOnInteractions (method, scope) { | ||
return (function (event) { | ||
const interactions = scope.interactions.list; | ||
const pointerType = pointerUtils.getPointerType(event); | ||
const [eventTarget, curEventTarget] = pointerUtils.getEventTargets(event); | ||
const matches = []; // [ [pointer, interaction], ...] | ||
if (browser.supportsTouch && /touch/.test(event.type)) { | ||
scope.prevTouchTime = new Date().getTime(); | ||
for (const changedTouch of event.changedTouches) { | ||
const pointer = changedTouch; | ||
const pointerId = pointerUtils.getPointerId(pointer); | ||
const searchDetails = { | ||
pointer, | ||
pointerId, | ||
pointerType, | ||
eventType: event.type, | ||
eventTarget, | ||
curEventTarget, | ||
scope, | ||
}; | ||
const interaction = getInteraction(searchDetails); | ||
matches.push([ | ||
searchDetails.pointer, | ||
searchDetails.eventTarget, | ||
searchDetails.curEventTarget, | ||
interaction, | ||
]); | ||
} | ||
} | ||
else { | ||
let invalidPointer = false; | ||
if (!browser.supportsPointerEvent && /mouse/.test(event.type)) { | ||
// ignore mouse events while touch interactions are active | ||
for (let i = 0; i < interactions.length && !invalidPointer; i++) { | ||
invalidPointer = interactions[i].pointerType !== 'mouse' && interactions[i].pointerIsDown; | ||
function doOnInteractions(method, scope) { | ||
return (function (event) { | ||
const interactions = scope.interactions.list; | ||
const pointerType = pointerUtils.getPointerType(event); | ||
const [eventTarget, curEventTarget] = pointerUtils.getEventTargets(event); | ||
const matches = []; // [ [pointer, interaction], ...] | ||
if (browser.supportsTouch && /touch/.test(event.type)) { | ||
scope.prevTouchTime = new Date().getTime(); | ||
for (const changedTouch of event.changedTouches) { | ||
const pointer = changedTouch; | ||
const pointerId = pointerUtils.getPointerId(pointer); | ||
const searchDetails = { | ||
pointer, | ||
pointerId, | ||
pointerType, | ||
eventType: event.type, | ||
eventTarget, | ||
curEventTarget, | ||
scope, | ||
}; | ||
const interaction = getInteraction(searchDetails); | ||
matches.push([ | ||
searchDetails.pointer, | ||
searchDetails.eventTarget, | ||
searchDetails.curEventTarget, | ||
interaction, | ||
]); | ||
} | ||
} | ||
// try to ignore mouse events that are simulated by the browser | ||
// after a touch event | ||
invalidPointer = invalidPointer | ||
|| (new Date().getTime() - scope.prevTouchTime < 500) | ||
// on iOS and Firefox Mobile, MouseEvent.timeStamp is zero if simulated | ||
|| event.timeStamp === 0; | ||
} | ||
if (!invalidPointer) { | ||
const searchDetails = { | ||
pointer: event, | ||
pointerId: pointerUtils.getPointerId(event), | ||
pointerType, | ||
eventType: event.type, | ||
curEventTarget, | ||
eventTarget, | ||
scope, | ||
}; | ||
const interaction = getInteraction(searchDetails); | ||
matches.push([ | ||
searchDetails.pointer, | ||
searchDetails.eventTarget, | ||
searchDetails.curEventTarget, | ||
interaction, | ||
]); | ||
} | ||
} | ||
// eslint-disable-next-line no-shadow | ||
for (const [pointer, eventTarget, curEventTarget, interaction] of matches) { | ||
interaction[method](pointer, event, eventTarget, curEventTarget); | ||
} | ||
}); | ||
else { | ||
let invalidPointer = false; | ||
if (!browser.supportsPointerEvent && /mouse/.test(event.type)) { | ||
// ignore mouse events while touch interactions are active | ||
for (let i = 0; i < interactions.length && !invalidPointer; i++) { | ||
invalidPointer = interactions[i].pointerType !== 'mouse' && interactions[i].pointerIsDown; | ||
} | ||
// try to ignore mouse events that are simulated by the browser | ||
// after a touch event | ||
invalidPointer = invalidPointer | ||
|| (new Date().getTime() - scope.prevTouchTime < 500) | ||
// on iOS and Firefox Mobile, MouseEvent.timeStamp is zero if simulated | ||
|| event.timeStamp === 0; | ||
} | ||
if (!invalidPointer) { | ||
const searchDetails = { | ||
pointer: event, | ||
pointerId: pointerUtils.getPointerId(event), | ||
pointerType, | ||
eventType: event.type, | ||
curEventTarget, | ||
eventTarget, | ||
scope, | ||
}; | ||
const interaction = getInteraction(searchDetails); | ||
matches.push([ | ||
searchDetails.pointer, | ||
searchDetails.eventTarget, | ||
searchDetails.curEventTarget, | ||
interaction, | ||
]); | ||
} | ||
} | ||
// eslint-disable-next-line no-shadow | ||
for (const [pointer, eventTarget, curEventTarget, interaction] of matches) { | ||
interaction[method](pointer, event, eventTarget, curEventTarget); | ||
} | ||
}); | ||
} | ||
function getInteraction (searchDetails) { | ||
const { pointerType, scope } = searchDetails; | ||
const foundInteraction = finder.search(searchDetails); | ||
const signalArg = { interaction: foundInteraction, searchDetails }; | ||
scope.interactions.signals.fire('find', signalArg); | ||
return signalArg.interaction || newInteraction({ pointerType }, scope); | ||
function getInteraction(searchDetails) { | ||
const { pointerType, scope } = searchDetails; | ||
const foundInteraction = finder.search(searchDetails); | ||
const signalArg = { interaction: foundInteraction, searchDetails }; | ||
scope.interactions.signals.fire('find', signalArg); | ||
return signalArg.interaction || newInteraction({ pointerType }, scope); | ||
} | ||
export function newInteraction (options, scope) { | ||
const interaction = scope.interactions.new(options); | ||
scope.interactions.list.push(interaction); | ||
return interaction; | ||
export function newInteraction(options, scope) { | ||
const interaction = scope.interactions.new(options); | ||
scope.interactions.list.push(interaction); | ||
return interaction; | ||
} | ||
function onDocSignal ({ doc, scope, options }, signalName) { | ||
const { eventMap } = scope.interactions; | ||
const eventMethod = signalName.indexOf('add') === 0 | ||
? events.add : events.remove; | ||
if (scope.browser.isIOS && !options.events) { | ||
options.events = { passive: false }; | ||
} | ||
// delegate event listener | ||
for (const eventType in events.delegatedEvents) { | ||
eventMethod(doc, eventType, events.delegateListener); | ||
eventMethod(doc, eventType, events.delegateUseCapture, true); | ||
} | ||
const eventOptions = options && options.events; | ||
for (const eventType in eventMap) { | ||
eventMethod(doc, eventType, eventMap[eventType], eventOptions); | ||
} | ||
function onDocSignal({ doc, scope, options }, signalName) { | ||
const { eventMap } = scope.interactions; | ||
const eventMethod = signalName.indexOf('add') === 0 | ||
? events.add : events.remove; | ||
if (scope.browser.isIOS && !options.events) { | ||
options.events = { passive: false }; | ||
} | ||
// delegate event listener | ||
for (const eventType in events.delegatedEvents) { | ||
eventMethod(doc, eventType, events.delegateListener); | ||
eventMethod(doc, eventType, events.delegateUseCapture, true); | ||
} | ||
const eventOptions = options && options.events; | ||
for (const eventType in eventMap) { | ||
eventMethod(doc, eventType, eventMap[eventType], eventOptions); | ||
} | ||
} | ||
export default { | ||
install, | ||
onDocSignal, | ||
doOnInteractions, | ||
newInteraction, | ||
methodNames, | ||
install, | ||
onDocSignal, | ||
doOnInteractions, | ||
newInteraction, | ||
methodNames, | ||
}; | ||
//# sourceMappingURL=interactions.js.map |
{ | ||
"name": "@interactjs/core", | ||
"version": "1.4.0-alpha.17+sha.793e5fa", | ||
"version": "1.4.0-alpha.18+sha.a8adfbf", | ||
"peerDependencies": { | ||
"@interactjs/utils": "1.4.0-alpha.17+sha.793e5fa" | ||
"@interactjs/utils": "1.4.0-alpha.18+sha.a8adfbf" | ||
}, | ||
"devDependencies": { | ||
"@interactjs/_dev": "1.4.0-alpha.17+sha.793e5fa", | ||
"@interactjs/utils": "1.4.0-alpha.17+sha.793e5fa" | ||
"@interactjs/_dev": "1.4.0-alpha.18+sha.a8adfbf", | ||
"@interactjs/utils": "1.4.0-alpha.18+sha.a8adfbf" | ||
}, | ||
@@ -11,0 +11,0 @@ "publishConfig": { |
318
scope.js
@@ -1,197 +0,153 @@ | ||
import Eventable from './Eventable'; | ||
import defaults from './defaultOptions'; | ||
import * as utils from '@interactjs/utils'; | ||
import domObjects from '@interactjs/utils/domObjects'; | ||
import defaults from './defaultOptions'; | ||
import Eventable from './Eventable'; | ||
import InteractableBase from './Interactable'; | ||
import InteractEvent from './InteractEvent'; | ||
import interactions from './interactions'; | ||
import InteractEvent from './InteractEvent'; | ||
import InteractableBase from './Interactable'; | ||
const { | ||
win, | ||
browser, | ||
raf, | ||
Signals, | ||
events, | ||
} = utils; | ||
export function createScope () { | ||
const scope = { | ||
Signals, | ||
signals: new Signals(), | ||
browser, | ||
events, | ||
utils, | ||
defaults: utils.clone(defaults), | ||
Eventable, | ||
InteractEvent: InteractEvent, | ||
Interactable: class Interactable extends InteractableBase { | ||
get _defaults () { return scope.defaults; } | ||
set (options) { | ||
super.set(options); | ||
scope.interactables.signals.fire('set', { | ||
options, | ||
interactable: this, | ||
}); | ||
return this; | ||
} | ||
unset () { | ||
super.unset(); | ||
scope.interactables.signals.fire('unset', { interactable: this }); | ||
} | ||
}, | ||
interactables: { | ||
// all set interactables | ||
list: [], | ||
new (target, options) { | ||
const { win, browser, raf, Signals, events, } = utils; | ||
export function createScope() { | ||
return new Scope(); | ||
} | ||
export class Scope { | ||
constructor() { | ||
// FIXME Signals | ||
this.signals = new Signals(); | ||
this.browser = browser; | ||
this.events = events; | ||
this.utils = utils; | ||
this.defaults = utils.clone(defaults); | ||
this.Eventable = Eventable; | ||
this.InteractEvent = InteractEvent; | ||
this.interactables = new InteractableSet(this); | ||
// main document | ||
this.document = null; | ||
// all documents being listened to | ||
this.documents = [ /* { doc, options } */]; | ||
const scope = this; | ||
this.Interactable = class Interactable extends InteractableBase { | ||
get _defaults() { return scope.defaults; } | ||
set(options) { | ||
super.set(options); | ||
scope.interactables.signals.fire('set', { | ||
options, | ||
interactable: this, | ||
}); | ||
return this; | ||
} | ||
unset() { | ||
super.unset(); | ||
scope.interactables.signals.fire('unset', { interactable: this }); | ||
} | ||
}; | ||
} | ||
init(window) { | ||
return initScope(this, window); | ||
} | ||
addDocument(doc, options) { | ||
// do nothing if document is already known | ||
if (this.getDocIndex(doc) !== -1) { | ||
return false; | ||
} | ||
const window = win.getWindow(doc); | ||
options = options ? utils.extend({}, options) : {}; | ||
this.documents.push({ doc, options }); | ||
events.documents.push(doc); | ||
// don't add an unload event for the main document | ||
// so that the page may be cached in browser history | ||
if (doc !== this.document) { | ||
events.add(window, 'unload', this.onWindowUnload); | ||
} | ||
this.signals.fire('add-document', { doc, window, scope: this, options }); | ||
} | ||
removeDocument(doc) { | ||
const index = this.getDocIndex(doc); | ||
const window = win.getWindow(doc); | ||
const options = this.documents[index].options; | ||
events.remove(window, 'unload', this.onWindowUnload); | ||
this.documents.splice(index, 1); | ||
events.documents.splice(index, 1); | ||
this.signals.fire('remove-document', { doc, window, scope: this, options }); | ||
} | ||
onWindowUnload(event) { | ||
this.removeDocument(event.currentTarget.document); | ||
} | ||
getDocIndex(doc) { | ||
for (let i = 0; i < this.documents.length; i++) { | ||
if (this.documents[i].doc === doc) { | ||
return i; | ||
} | ||
} | ||
return -1; | ||
} | ||
getDocOptions(doc) { | ||
const docIndex = this.getDocIndex(doc); | ||
return docIndex === -1 ? null : this.documents[docIndex].options; | ||
} | ||
} | ||
class InteractableSet { | ||
constructor(scope) { | ||
this.scope = scope; | ||
this.signals = new utils.Signals(); | ||
// all set interactables | ||
this.list = []; | ||
} | ||
new(target, options) { | ||
options = utils.extend(options || {}, { | ||
actions: scope.actions, | ||
actions: this.scope.actions, | ||
}); | ||
const interactable = new scope.Interactable(target, options, scope.document); | ||
scope.addDocument(interactable._doc); | ||
scope.interactables.list.push(interactable); | ||
scope.interactables.signals.fire('new', { | ||
target, | ||
options, | ||
interactable: interactable, | ||
win: this._win, | ||
const interactable = new this.scope.Interactable(target, options, this.scope.document); | ||
this.scope.addDocument(interactable._doc); | ||
this.scope.interactables.list.push(interactable); | ||
this.scope.interactables.signals.fire('new', { | ||
target, | ||
options, | ||
interactable: interactable, | ||
win: this.scope._win, | ||
}); | ||
return interactable; | ||
}, | ||
indexOfElement (target, context) { | ||
context = context || scope.document; | ||
} | ||
indexOfElement(target, context) { | ||
context = context || this.scope.document; | ||
const list = this.list; | ||
for (let i = 0; i < list.length; i++) { | ||
const interactable = list[i]; | ||
if (interactable.target === target && interactable._context === context) { | ||
return i; | ||
} | ||
const interactable = list[i]; | ||
if (interactable.target === target && interactable._context === context) { | ||
return i; | ||
} | ||
} | ||
return -1; | ||
}, | ||
get (element, options, dontCheckInContext) { | ||
} | ||
get(element, options, dontCheckInContext) { | ||
const ret = this.list[this.indexOfElement(element, options && options.context)]; | ||
return ret && (utils.is.string(element) || dontCheckInContext || ret.inContext(element))? ret : null; | ||
}, | ||
forEachMatch (element, callback) { | ||
return ret && (utils.is.string(element) || dontCheckInContext || ret.inContext(element)) ? ret : null; | ||
} | ||
forEachMatch(element, callback) { | ||
for (const interactable of this.list) { | ||
let ret; | ||
if ((utils.is.string(interactable.target) | ||
// target is a selector and the element matches | ||
? (utils.is.element(element) && utils.dom.matchesSelector(element, interactable.target)) | ||
// target is the element | ||
: element === interactable.target) | ||
// the element is in context | ||
&& (interactable.inContext(element))) { | ||
ret = callback(interactable); | ||
} | ||
if (ret !== undefined) { | ||
return ret; | ||
} | ||
let ret; | ||
if ((utils.is.string(interactable.target) | ||
// target is a selector and the element matches | ||
? (utils.is.element(element) && utils.dom.matchesSelector(element, interactable.target)) | ||
// target is the element | ||
: element === interactable.target) | ||
// the element is in context | ||
&& (interactable.inContext(element))) { | ||
ret = callback(interactable); | ||
} | ||
if (ret !== undefined) { | ||
return ret; | ||
} | ||
} | ||
}, | ||
signals: new utils.Signals(), | ||
}, | ||
// main document | ||
document: null, | ||
// all documents being listened to | ||
documents: [/* { doc, options } */], | ||
init (window) { | ||
return initScope(scope, window); | ||
}, | ||
addDocument (doc, options) { | ||
// do nothing if document is already known | ||
if (scope.getDocIndex(doc) !== -1) { return false; } | ||
const window = win.getWindow(doc); | ||
options = options ? utils.extend({}, options) : {}; | ||
scope.documents.push({ doc, options }); | ||
events.documents.push(doc); | ||
// don't add an unload event for the main document | ||
// so that the page may be cached in browser history | ||
if (doc !== scope.document) { | ||
events.add(window, 'unload', scope.onWindowUnload); | ||
} | ||
scope.signals.fire('add-document', { doc, window, scope, options }); | ||
}, | ||
removeDocument (doc) { | ||
const index = scope.getDocIndex(doc); | ||
const window = win.getWindow(doc); | ||
const options = scope.documents[index].options; | ||
events.remove(window, 'unload', scope.onWindowUnload); | ||
scope.documents.splice(index, 1); | ||
events.documents.splice(index, 1); | ||
scope.signals.fire('remove-document', { doc, window, scope, options }); | ||
}, | ||
onWindowUnload (event) { | ||
scope.removeDocument(event.currentTarget.document); | ||
}, | ||
getDocIndex (doc) { | ||
for (let i = 0; i < scope.documents.length; i++) { | ||
if (scope.documents[i].doc === doc) { | ||
return i; | ||
} | ||
} | ||
return -1; | ||
}, | ||
getDocOptions (doc) { | ||
const docIndex = scope.getDocIndex(doc); | ||
return docIndex === -1 ? null : scope.documents[docIndex].options; | ||
}, | ||
}; | ||
return scope; | ||
} | ||
} | ||
export function initScope (scope, window) { | ||
win.init(window); | ||
domObjects.init(window); | ||
browser.init(window); | ||
raf.init(window); | ||
events.init(window); | ||
interactions.install(scope); | ||
scope.document = window.document; | ||
return scope; | ||
export function initScope(scope, window) { | ||
win.init(window); | ||
domObjects.init(window); | ||
browser.init(window); | ||
raf.init(window); | ||
events.init(window); | ||
interactions.install(scope); | ||
scope.document = window.document; | ||
return scope; | ||
} | ||
//# sourceMappingURL=scope.js.map |
import test from '@interactjs/_dev/test/test'; | ||
import Eventable from '../Eventable'; | ||
import Eventable from '../Eventable.ts'; | ||
@@ -4,0 +4,0 @@ test('Eventable', t => { |
@@ -1,5 +0,5 @@ | ||
import * as utils from '@interactjs/utils'; | ||
import Signals from '@interactjs/utils/Signals'; | ||
import { createScope } from '../scope'; | ||
import Eventable from '../Eventable'; | ||
import * as utils from '@interactjs/utils/index.ts'; | ||
import Signals from '@interactjs/utils/Signals.ts'; | ||
import { createScope } from '../scope.ts'; | ||
import Eventable from '../Eventable.ts'; | ||
import { doc } from '@interactjs/_dev/test/domator'; | ||
@@ -6,0 +6,0 @@ |
@@ -5,3 +5,3 @@ import test from '@interactjs/_dev/test/test'; | ||
import * as helpers from './helpers'; | ||
import Interactable from '../Interactable'; | ||
import Interactable from '../Interactable.ts'; | ||
@@ -8,0 +8,0 @@ test('Interactable copies and extends defaults', t => { |
import test from '@interactjs/_dev/test/test'; | ||
import pointerUtils from '@interactjs/utils/pointerUtils'; | ||
import pointerUtils from '@interactjs/utils/pointerUtils.ts'; | ||
import * as helpers from './helpers'; | ||
import Signals from '@interactjs/utils/Signals'; | ||
import Interaction from '../Interaction'; | ||
import InteractEvent from '../InteractEvent'; | ||
import interactions from '../interactions'; | ||
import Signals from '@interactjs/utils/Signals.ts'; | ||
import Interaction from '../Interaction.ts'; | ||
import InteractEvent from '../InteractEvent.ts'; | ||
import interactions from '../interactions.ts'; | ||
@@ -10,0 +10,0 @@ const makeInteractionAndSignals = () => new Interaction({ signals: new Signals }); |
import test from '@interactjs/_dev/test/test'; | ||
import * as helpers from './helpers'; | ||
import Signals from '@interactjs/utils/Signals'; | ||
import Interaction from '../Interaction'; | ||
import interactions from '../interactions'; | ||
import Signals from '@interactjs/utils/Signals.ts'; | ||
import Interaction from '../Interaction.ts'; | ||
import interactions from '../interactions.ts'; | ||
@@ -8,0 +8,0 @@ test('interactions', t => { |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
173659
40
3792