/** @module interact */
import { Options } from '@interactjs/core/defaultOptions'
import Interactable from '@interactjs/core/Interactable'
import { Scope } from '@interactjs/core/scope'
import * as utils from '@interactjs/utils'
import browser from '@interactjs/utils/browser'
import events from '@interactjs/utils/events'
declare module '@interactjs/core/scope' {
interface Scope {
interact: InteractStatic
}
}
export interface InteractStatic {
(target: Interact.Target, options?: Options): Interactable
on: typeof on
pointerMoveTolerance: typeof pointerMoveTolerance
stop: typeof stop
supportsPointerEvent: typeof supportsPointerEvent
supportsTouch: typeof supportsTouch
debug: typeof debug
off: typeof off
isSet: typeof isSet
use: typeof use
getPointerAverage: typeof utils.pointer.pointerAverage
getTouchBBox: typeof utils.pointer.touchBBox
getTouchDistance: typeof utils.pointer.touchDistance
getTouchAngle: typeof utils.pointer.touchAngle
getElementRect: typeof utils.dom.getElementRect
getElementClientRect: typeof utils.dom.getElementClientRect
matchesSelector: typeof utils.dom.matchesSelector
closest: typeof utils.dom.closest
addDocument: typeof scope.addDocument
removeDocument: typeof scope.removeDocument
version: string
}
const globalEvents: any = {}
const scope = new Scope()
/**
* ```js
* interact('#draggable').draggable(true)
*
* var rectables = interact('rect')
* rectables
* .gesturable(true)
* .on('gesturemove', function (event) {
* // ...
* })
* ```
*
* The methods of this variable can be used to set elements as interactables
* and also to change various default settings.
*
* Calling it as a function and passing an element or a valid CSS selector
* string returns an Interactable object which has various methods to configure
* it.
*
* @global
*
* @param {Element | string} target The HTML or SVG Element to interact with
* or CSS selector
* @return {Interactable}
*/
export const interact: InteractStatic = function interact (target: Interact.Target, options?: any) {
let interactable = scope.interactables.get(target, options)
if (!interactable) {
interactable = scope.interactables.new(target, options)
interactable.events.global = globalEvents
}
return interactable
} as InteractStatic
/**
* Use a plugin
*
* @alias module:interact.use
*
* @param {Object} plugin
* @param {function} plugin.install
* @return {interact}
*/
interact.use = use
function use (plugin: Interact.Plugin, options?: { [key: string]: any }) {
scope.usePlugin(plugin, options)
return interact
}
/**
* Check if an element or selector has been set with the {@link interact}
* function
*
* @alias module:interact.isSet
*
* @param {Element} element The Element being searched for
* @return {boolean} Indicates if the element or CSS selector was previously
* passed to interact
*/
interact.isSet = isSet
function isSet (target: Element, options?: any) {
return !!scope.interactables.get(target, options && options.context)
}
/**
* Add a global listener for an InteractEvent or adds a DOM event to `document`
*
* @alias module:interact.on
*
* @param {string | array | object} type The types of events to listen for
* @param {function} listener The function event (s)
* @param {object | boolean} [options] object or useCapture flag for
* addEventListener
* @return {object} interact
*/
interact.on = on
function on (type: string | Interact.EventTypes, listener: Interact.ListenersArg, options?) {
if (utils.is.string(type) && type.search(' ') !== -1) {
type = type.trim().split(/ +/)
}
if (utils.is.array(type)) {
for (const eventType of (type as any[])) {
interact.on(eventType, listener, options)
}
return interact
}
if (utils.is.object(type)) {
for (const prop in type) {
interact.on(prop, (type as Interact.EventTypes)[prop], listener)
}
return interact
}
// if it is an InteractEvent type, add listener to globalEvents
if (utils.arr.contains(scope.actions.eventTypes, type)) {
// if this type of event was never bound
if (!globalEvents[type]) {
globalEvents[type] = [listener]
}
else {
globalEvents[type].push(listener)
}
}
// If non InteractEvent type, addEventListener to document
else {
events.add(scope.document, type, listener as Interact.Listener, { options })
}
return interact
}
/**
* Removes a global InteractEvent listener or DOM event from `document`
*
* @alias module:interact.off
*
* @param {string | array | object} type The types of events that were listened
* for
* @param {function} listener The listener function to be removed
* @param {object | boolean} options [options] object or useCapture flag for
* removeEventListener
* @return {object} interact
*/
interact.off = off
function off (type, listener, options) {
if (utils.is.string(type) && type.search(' ') !== -1) {
type = type.trim().split(/ +/)
}
if (utils.is.array(type)) {
for (const eventType of type) {
interact.off(eventType, listener, options)
}
return interact
}
if (utils.is.object(type)) {
for (const prop in type) {
interact.off(prop, type[prop], listener)
}
return interact
}
if (!utils.arr.contains(scope.actions.eventTypes, type)) {
events.remove(scope.document, type, listener, options)
}
else {
let index
if (type in globalEvents &&
(index = globalEvents[type].indexOf(listener)) !== -1) {
globalEvents[type].splice(index, 1)
}
}
return interact
}
/**
* Returns an object which exposes internal data
* @alias module:interact.debug
*
* @return {object} An object with properties that outline the current state
* and expose internal functions and variables
*/
interact.debug = debug
function debug () {
return scope
}
// expose the functions used to calculate multi-touch properties
interact.getPointerAverage = utils.pointer.pointerAverage
interact.getTouchBBox = utils.pointer.touchBBox
interact.getTouchDistance = utils.pointer.touchDistance
interact.getTouchAngle = utils.pointer.touchAngle
interact.getElementRect = utils.dom.getElementRect
interact.getElementClientRect = utils.dom.getElementClientRect
interact.matchesSelector = utils.dom.matchesSelector
interact.closest = utils.dom.closest
/**
* @alias module:interact.supportsTouch
*
* @return {boolean} Whether or not the browser supports touch input
*/
interact.supportsTouch = supportsTouch
function supportsTouch () {
return browser.supportsTouch
}
/**
* @alias module:interact.supportsPointerEvent
*
* @return {boolean} Whether or not the browser supports PointerEvents
*/
interact.supportsPointerEvent = supportsPointerEvent
function supportsPointerEvent () {
return browser.supportsPointerEvent
}
/**
* Cancels all interactions (end events are not fired)
*
* @alias module:interact.stop
*
* @return {object} interact
*/
interact.stop = stop
function stop () {
for (const interaction of scope.interactions.list) {
interaction.stop()
}
return interact
}
/**
* Returns or sets the distance the pointer must be moved before an action
* sequence occurs. This also affects tolerance for tap events.
*
* @alias module:interact.pointerMoveTolerance
*
* @param {number} [newValue] The movement from the start position must be greater than this value
* @return {interact | number}
*/
interact.pointerMoveTolerance = pointerMoveTolerance
function pointerMoveTolerance (newValue) {
if (utils.is.number(newValue)) {
scope.interactions.pointerMoveTolerance = newValue
return interact
}
return scope.interactions.pointerMoveTolerance
}
scope.interactables.signals.on('unset', ({ interactable }) => {
scope.interactables.list.splice(scope.interactables.list.indexOf(interactable), 1)
// Stop related interactions when an Interactable is unset
for (const interaction of scope.interactions.list) {
if (interaction.interactable === interactable && interaction.interacting() && !interaction._ending) {
interaction.stop()
}
}
})
interact.addDocument = (doc, options) => scope.addDocument(doc, options)
interact.removeDocument = (doc) => scope.removeDocument(doc)
scope.interact = interact
export { scope }
export default interact