import * as arr from "../utils/arr.js"; import extend from "../utils/extend.js"; import hypot from "../utils/hypot.js"; import { warnOnce, copyAction } from "../utils/misc.js"; import * as pointerUtils from "../utils/pointerUtils.js"; import * as rectUtils from "../utils/rect.js"; import { InteractEvent } from "./InteractEvent.js"; import { PointerInfo } from "./PointerInfo.js"; export let _ProxyValues; (function (_ProxyValues) { _ProxyValues["interactable"] = ""; _ProxyValues["element"] = ""; _ProxyValues["prepared"] = ""; _ProxyValues["pointerIsDown"] = ""; _ProxyValues["pointerWasMoved"] = ""; _ProxyValues["_proxy"] = ""; })(_ProxyValues || (_ProxyValues = {})); export let _ProxyMethods; (function (_ProxyMethods) { _ProxyMethods["start"] = ""; _ProxyMethods["move"] = ""; _ProxyMethods["end"] = ""; _ProxyMethods["stop"] = ""; _ProxyMethods["interacting"] = ""; })(_ProxyMethods || (_ProxyMethods = {})); let idCounter = 0; export class Interaction { // current interactable being interacted with // the target element of the interactable // action that's ready to be fired on next move event // keep track of added pointers // pointerdown/mousedown/touchstart event // previous action event /** @internal */ get pointerMoveTolerance() { return 1; } /** * @alias Interaction.prototype.move */ /** */ constructor({ pointerType, scopeFire }) { this.interactable = null; this.element = null; this.rect = void 0; this._rects = void 0; this.edges = void 0; this._scopeFire = void 0; this.prepared = { name: null, axis: null, edges: null }; this.pointerType = void 0; this.pointers = []; this.downEvent = null; this.downPointer = {}; this._latestPointer = { pointer: null, event: null, eventTarget: null }; this.prevEvent = null; this.pointerIsDown = false; this.pointerWasMoved = false; this._interacting = false; this._ending = false; this._stopped = true; this._proxy = null; this.simulation = null; this.doMove = warnOnce(function (signalArg) { this.move(signalArg); }, 'The interaction.doMove() method has been renamed to interaction.move()'); this.coords = { // Starting InteractEvent pointer coordinates start: pointerUtils.newCoords(), // Previous native pointer move event coordinates prev: pointerUtils.newCoords(), // current native pointer move event coordinates cur: pointerUtils.newCoords(), // Change in coordinates and time of the pointer delta: pointerUtils.newCoords(), // pointer velocity velocity: pointerUtils.newCoords() }; this._id = idCounter++; this._scopeFire = scopeFire; this.pointerType = pointerType; const that = this; this._proxy = {}; for (const key in _ProxyValues) { Object.defineProperty(this._proxy, key, { get() { return that[key]; } }); } for (const key in _ProxyMethods) { Object.defineProperty(this._proxy, key, { value: (...args) => that[key](...args) }); } this._scopeFire('interactions:new', { interaction: this }); } pointerDown(pointer, event, eventTarget) { const pointerIndex = this.updatePointer(pointer, event, eventTarget, true); const pointerInfo = this.pointers[pointerIndex]; this._scopeFire('interactions:down', { pointer, event, eventTarget, pointerIndex, pointerInfo, type: 'down', 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.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 {Boolean} Whether the interaction was successfully started */ start(action, interactable, element) { if (this.interacting() || !this.pointerIsDown || this.pointers.length < (action.name === 'gesture' ? 2 : 1) || !interactable.options[action.name].enabled) { return false; } copyAction(this.prepared, action); this.interactable = interactable; this.element = element; this.rect = interactable.getRect(element); this.edges = this.prepared.edges ? extend({}, this.prepared.edges) : { left: true, right: true, top: true, bottom: true }; this._stopped = false; this._interacting = this._doPhase({ interaction: this, event: this.downEvent, phase: 'start' }) && !this._stopped; return this._interacting; } pointerMove(pointer, event, eventTarget) { if (!this.simulation && !(this.modification && this.modification.endResult)) { this.updatePointer(pointer, event, eventTarget, false); } 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 = hypot(dx, dy) > this.pointerMoveTolerance; } const pointerIndex = this.getPointerIndex(pointer); const signalArg = { pointer, pointerIndex, pointerInfo: this.pointers[pointerIndex], event, type: 'move', eventTarget, dx, dy, duplicate: duplicateMove, interaction: this }; if (!duplicateMove) { // set pointer coordinate, time changes and velocity pointerUtils.setCoordVelocity(this.coords.velocity, this.coords.delta); } this._scopeFire('interactions:move', signalArg); if (!duplicateMove && !this.simulation) { // if interacting, fire an 'action-move' signal etc if (this.interacting()) { signalArg.type = null; this.move(signalArg); } if (this.pointerWasMoved) { pointerUtils.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) { if (!signalArg || !signalArg.event) { pointerUtils.setZeroCoords(this.coords.delta); } signalArg = extend({ pointer: this._latestPointer.pointer, event: this._latestPointer.event, eventTarget: this._latestPointer.eventTarget, interaction: this }, 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); } const type = /cancel$/i.test(event.type) ? 'cancel' : 'up'; this._scopeFire(`interactions:${type}`, { pointer, pointerIndex, pointerInfo: this.pointers[pointerIndex], event, eventTarget, type: type, curEventTarget, interaction: this }); if (!this.simulation) { this.end(event); } this.removePointer(pointer, event); } documentBlur(event) { this.end(event); this._scopeFire('interactions:blur', { event, type: 'blur', 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' }); } this._ending = false; if (endPhaseResult === true) { this.stop(); } } currentAction() { return this._interacting ? this.prepared.name : null; } interacting() { return this._interacting; } /** */ stop() { this._scopeFire('interactions:stop', { interaction: this }); this.interactable = this.element = null; this._interacting = false; this._stopped = true; this.prepared.name = this.prevEvent = null; } getPointerIndex(pointer) { const pointerId = pointerUtils.getPointerId(pointer); // mouse and pen interactions may have only one pointer return this.pointerType === 'mouse' || this.pointerType === 'pen' ? this.pointers.length - 1 : arr.findIndex(this.pointers, curPointer => curPointer.id === pointerId); } getPointerInfo(pointer) { return this.pointers[this.getPointerIndex(pointer)]; } updatePointer(pointer, event, eventTarget, down) { const id = pointerUtils.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; } pointerUtils.setCoords(this.coords.cur, this.pointers.map(p => p.pointer), this._now()); pointerUtils.setCoordDeltas(this.coords.delta, this.coords.prev, this.coords.cur); if (down) { this.pointerIsDown = true; pointerInfo.downTime = this.coords.cur.timeStamp; pointerInfo.downTarget = eventTarget; pointerUtils.pointerExtend(this.downPointer, pointer); if (!this.interacting()) { pointerUtils.copyCoords(this.coords.start, this.coords.cur); pointerUtils.copyCoords(this.coords.prev, this.coords.cur); this.downEvent = event; this.pointerWasMoved = false; } } this._updateLatestPointer(pointer, event, eventTarget); this._scopeFire('interactions: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._scopeFire('interactions:remove-pointer', { pointer, event, eventTarget: null, pointerIndex, pointerInfo, interaction: this }); this.pointers.splice(pointerIndex, 1); this.pointerIsDown = false; } _updateLatestPointer(pointer, event, eventTarget) { this._latestPointer.pointer = pointer; this._latestPointer.event = event; this._latestPointer.eventTarget = eventTarget; } destroy() { this._latestPointer.pointer = null; this._latestPointer.event = null; this._latestPointer.eventTarget = null; } _createPreparedEvent(event, phase, preEnd, type) { return new InteractEvent(this, event, this.prepared.name, phase, this.element, preEnd, type); } _fireEvent(iEvent) { this.interactable.fire(iEvent); if (!this.prevEvent || iEvent.timeStamp >= this.prevEvent.timeStamp) { this.prevEvent = iEvent; } } _doPhase(signalArg) { const { event, phase, preEnd, type } = signalArg; const { rect } = this; if (rect && phase === 'move') { // update the rect changes due to pointer move rectUtils.addEdges(this.edges, rect, this.coords.delta[this.interactable.options.deltaSource]); rect.width = rect.right - rect.left; rect.height = rect.bottom - rect.top; } const beforeResult = this._scopeFire(`interactions:before-action-${phase}`, signalArg); if (beforeResult === false) { return false; } const iEvent = signalArg.iEvent = this._createPreparedEvent(event, phase, preEnd, type); this._scopeFire(`interactions:action-${phase}`, signalArg); if (phase === 'start') { this.prevEvent = iEvent; } this._fireEvent(iEvent); this._scopeFire(`interactions:after-action-${phase}`, signalArg); return true; } _now() { return Date.now(); } } export default Interaction; export { PointerInfo }; //# sourceMappingURL=Interaction.js.map