UNPKG

interactjs

Version:

Drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE8+)

232 lines (189 loc) 6.31 kB
const PointerEvent = require('./PointerEvent'); const Interaction = require('../Interaction'); const utils = require('../utils'); const browser = require('../utils/browser'); const defaults = require('../defaultOptions'); const signals = require('../utils/Signals').new(); const { filter } = require('../utils/arr'); const simpleSignals = [ 'down', 'up', 'cancel' ]; const simpleEvents = [ 'down', 'up', 'cancel' ]; const pointerEvents = { PointerEvent, fire, collectEventTargets, signals, defaults: { holdDuration: 600, ignoreFrom : null, allowFrom : null, origin : { x: 0, y: 0 }, }, types: [ 'down', 'move', 'up', 'cancel', 'tap', 'doubletap', 'hold', ], }; function fire (arg) { const { interaction, pointer, event, eventTarget, type = arg.pointerEvent.type, targets = collectEventTargets(arg), pointerEvent = new PointerEvent(type, pointer, event, eventTarget, interaction), } = arg; const signalArg = { interaction, pointer, event, eventTarget, targets, type, pointerEvent, }; for (let i = 0; i < targets.length; i++) { const target = targets[i]; for (const prop in target.props || {}) { pointerEvent[prop] = target.props[prop]; } const origin = utils.getOriginXY(target.eventable, target.element); pointerEvent.subtractOrigin(origin); pointerEvent.eventable = target.eventable; pointerEvent.currentTarget = target.element; target.eventable.fire(pointerEvent); pointerEvent.addOrigin(origin); if (pointerEvent.immediatePropagationStopped || (pointerEvent.propagationStopped && (i + 1) < targets.length && targets[i + 1].element !== pointerEvent.currentTarget)) { break; } } signals.fire('fired', signalArg); if (type === 'tap') { // if pointerEvent should make a double tap, create and fire a doubletap // PointerEvent and use that as the prevTap const prevTap = pointerEvent.double ? fire({ interaction, pointer, event, eventTarget, type: 'doubletap', }) : pointerEvent; interaction.prevTap = prevTap; interaction.tapTime = prevTap.timeStamp; } return pointerEvent; } function collectEventTargets ({ interaction, pointer, event, eventTarget, type }) { const pointerIndex = interaction.getPointerIndex(pointer); // do not fire a tap event if the pointer was moved before being lifted if (type === 'tap' && (interaction.pointerWasMoved // or if the pointerup target is different to the pointerdown target || !(interaction.downTargets[pointerIndex] && interaction.downTargets[pointerIndex] === eventTarget))) { return []; } const path = utils.getPath(eventTarget); const signalArg = { interaction, pointer, event, eventTarget, type, path, targets: [], element: null, }; for (const element of path) { signalArg.element = element; signals.fire('collect-targets', signalArg); } if (type === 'hold') { signalArg.targets = filter(signalArg.targets, target => target.eventable.options.holdDuration === interaction.holdTimers[pointerIndex].duration); } return signalArg.targets; } Interaction.signals.on('update-pointer-down', function ({ interaction, pointerIndex }) { interaction.holdTimers[pointerIndex] = { duration: Infinity, timeout: null }; }); Interaction.signals.on('remove-pointer', function ({ interaction, pointerIndex }) { interaction.holdTimers.splice(pointerIndex, 1); }); Interaction.signals.on('move', function ({ interaction, pointer, event, eventTarget, duplicateMove }) { const pointerIndex = interaction.getPointerIndex(pointer); if (!duplicateMove && (!interaction.pointerIsDown || interaction.pointerWasMoved)) { if (interaction.pointerIsDown) { clearTimeout(interaction.holdTimers[pointerIndex].timeout); } fire({ interaction, pointer, event, eventTarget, type: 'move', }); } }); Interaction.signals.on('down', function ({ interaction, pointer, event, eventTarget, pointerIndex }) { // copy event to be used in timeout for IE8 const eventCopy = browser.isIE8? utils.extend({}, event) : event; const timer = interaction.holdTimers[pointerIndex]; const path = utils.getPath(eventTarget); const signalArg = { interaction, pointer, event, eventTarget, type: 'hold', targets: [], path, element: null, }; for (const element of path) { signalArg.element = element; signals.fire('collect-targets', signalArg); } if (!signalArg.targets.length) { return; } let minDuration = Infinity; for (let i = 0; i < signalArg.targets.length; i++) { const target = signalArg.targets[i]; const holdDuration = target.eventable.options.holdDuration; if (holdDuration < minDuration) { minDuration = holdDuration; } } timer.duration = minDuration; timer.timeout = setTimeout(function () { fire({ interaction, eventCopy, eventTarget, pointer: browser.isIE8? eventCopy : pointer, type: 'hold', }); }, minDuration); }); Interaction.signals.on('up', ({ interaction, pointer, event, eventTarget }) => { if (!interaction.pointerWasMoved) { fire({ interaction, eventTarget, pointer, event, type: 'tap' }); } }); ['up', 'cancel'].forEach(function (signalName) { Interaction.signals.on(signalName, function ({ interaction, pointerIndex }) { if (interaction.holdTimers[pointerIndex]) { clearTimeout(interaction.holdTimers[pointerIndex].timeout); } }); }); function createSignalListener (type) { return function ({ interaction, pointer, event, eventTarget }) { fire({ interaction, eventTarget, pointer, event, type }); }; } for (let i = 0; i < simpleSignals.length; i++) { Interaction.signals.on(simpleSignals[i], createSignalListener(simpleEvents[i])); } Interaction.signals.on('new', function (interaction) { interaction.prevTap = null; // the most recent tap event on this interaction interaction.tapTime = 0; // time of the most recent tap event interaction.holdTimers = []; // [{ duration, timeout }] }); defaults.pointerEvents = pointerEvents.defaults; module.exports = pointerEvents;