UNPKG

interactjs

Version:

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

245 lines (192 loc) 7.11 kB
const interact = require('../interact'); const Interactable = require('../Interactable'); const Interaction = require('../Interaction'); const actions = require('../actions/base'); const defaultOptions = require('../defaultOptions'); const scope = require('../scope'); const utils = require('../utils'); const signals = require('../utils/Signals').new(); require('./InteractableMethods'); const autoStart = { signals, withinInteractionLimit, // Allow this many interactions to happen simultaneously maxInteractions: Infinity, defaults: { perAction: { manualStart: false, max: Infinity, maxPerElement: 1, allowFrom: null, ignoreFrom: null, // only allow left button by default // see https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons#Return_value mouseButtons: 1, }, }, setActionDefaults: function (action) { utils.extend(action.defaults, autoStart.defaults.perAction); }, validateAction, }; // set cursor style on mousedown Interaction.signals.on('down', function ({ interaction, pointer, event, eventTarget }) { if (interaction.interacting()) { return; } const actionInfo = getActionInfo(interaction, pointer, event, eventTarget); prepare(interaction, actionInfo); }); // set cursor style on mousemove Interaction.signals.on('move', function ({ interaction, pointer, event, eventTarget }) { if (interaction.pointerType !== 'mouse' || interaction.pointerIsDown || interaction.interacting()) { return; } const actionInfo = getActionInfo(interaction, pointer, event, eventTarget); prepare(interaction, actionInfo); }); Interaction.signals.on('move', function (arg) { const { interaction, event } = arg; if (!interaction.pointerIsDown || interaction.interacting() || !interaction.pointerWasMoved || !interaction.prepared.name) { return; } signals.fire('before-start', arg); const target = interaction.target; if (interaction.prepared.name && target) { // check manualStart and interaction limit if (target.options[interaction.prepared.name].manualStart || !withinInteractionLimit(target, interaction.element, interaction.prepared)) { interaction.stop(event); } else { interaction.start(interaction.prepared, target, interaction.element); } } }); // Check if the current target supports the action. // If so, return the validated action. Otherwise, return null function validateAction (action, interactable, element, eventTarget) { if (utils.is.object(action) && interactable.testIgnoreAllow(interactable.options[action.name], element, eventTarget) && interactable.options[action.name].enabled && withinInteractionLimit(interactable, element, action)) { return action; } return null; } function validateSelector (interaction, pointer, event, matches, matchElements, eventTarget) { for (let i = 0, len = matches.length; i < len; i++) { const match = matches[i]; const matchElement = matchElements[i]; const action = validateAction(match.getAction(pointer, event, interaction, matchElement), match, matchElement, eventTarget); if (action) { return { action, target: match, element: matchElement, }; } } return {}; } function getActionInfo (interaction, pointer, event, eventTarget) { let matches = []; let matchElements = []; let element = eventTarget; function pushMatches (interactable) { matches.push(interactable); matchElements.push(element); } while (utils.is.element(element)) { matches = []; matchElements = []; scope.interactables.forEachMatch(element, pushMatches); const actionInfo = validateSelector(interaction, pointer, event, matches, matchElements, eventTarget); if (actionInfo.action && !actionInfo.target.options[actionInfo.action.name].manualStart) { return actionInfo; } element = utils.parentNode(element); } return {}; } function prepare (interaction, { action, target, element }) { action = action || {}; if (interaction.target && interaction.target.options.styleCursor) { interaction.target._doc.documentElement.style.cursor = ''; } interaction.target = target; interaction.element = element; utils.copyAction(interaction.prepared, action); if (target && target.options.styleCursor) { const cursor = action? actions[action.name].getCursor(action) : ''; interaction.target._doc.documentElement.style.cursor = cursor; } signals.fire('prepared', { interaction: interaction }); } Interaction.signals.on('stop', function ({ interaction }) { const target = interaction.target; if (target && target.options.styleCursor) { target._doc.documentElement.style.cursor = ''; } }); function withinInteractionLimit (interactable, element, action) { const options = interactable.options; const maxActions = options[action.name].max; const maxPerElement = options[action.name].maxPerElement; let activeInteractions = 0; let targetCount = 0; let targetElementCount = 0; // no actions if any of these values == 0 if (!(maxActions && maxPerElement && autoStart.maxInteractions)) { return; } for (const interaction of scope.interactions) { const otherAction = interaction.prepared.name; if (!interaction.interacting()) { continue; } activeInteractions++; if (activeInteractions >= autoStart.maxInteractions) { return false; } if (interaction.target !== interactable) { continue; } targetCount += (otherAction === action.name)|0; if (targetCount >= maxActions) { return false; } if (interaction.element === element) { targetElementCount++; if (otherAction !== action.name || targetElementCount >= maxPerElement) { return false; } } } return autoStart.maxInteractions > 0; } /** * Returns or sets the maximum number of concurrent interactions allowed. By * default only 1 interaction is allowed at a time (for backwards * compatibility). To allow multiple interactions on the same Interactables and * elements, you need to enable it in the draggable, resizable and gesturable * `'max'` and `'maxPerElement'` options. * * @alias module:interact.maxInteractions * * @param {number} [newValue] Any number. newValue <= 0 means no interactions. */ interact.maxInteractions = function (newValue) { if (utils.is.number(newValue)) { autoStart.maxInteractions = newValue; return interact; } return autoStart.maxInteractions; }; Interactable.settingsMethods.push('styleCursor'); Interactable.settingsMethods.push('actionChecker'); Interactable.settingsMethods.push('ignoreFrom'); Interactable.settingsMethods.push('allowFrom'); defaultOptions.base.actionChecker = null; defaultOptions.base.styleCursor = true; utils.extend(defaultOptions.perAction, autoStart.defaults.perAction); module.exports = autoStart;