diff --git a/README.md b/README.md index 5e52da93a..fddc4e53a 100644 --- a/README.md +++ b/README.md @@ -46,11 +46,13 @@ var pixelSize = 16; interact('.rainbow-pixel-canvas') .origin('self') .draggable({ - snap: { - targets: [ interact.createSnapGrid({ + modifiers: [ + interact.modifiers.snap({ + targets: [ interact.snappers.grid({ x: pixelSize, y: pixelSize - }) ] - }, + }) ], + }), + ], // allow multiple drags on the same element maxPerElement: Infinity }) diff --git a/packages/_dev/test/helpers.js b/packages/_dev/test/helpers.js index 7a3bf4db4..254a97112 100644 --- a/packages/_dev/test/helpers.js +++ b/packages/_dev/test/helpers.js @@ -115,4 +115,10 @@ export function mockInteractable (props) { props); } +export function getProps (src, props) { + return props.reduce((acc, prop) => { + acc[prop] = src[prop]; + return acc; + }, {}); +} export { _ }; diff --git a/packages/inertia/index.js b/packages/inertia/index.js index 88eb9c178..a3fa4ad31 100644 --- a/packages/inertia/index.js +++ b/packages/inertia/index.js @@ -80,7 +80,6 @@ function resume ({ interaction, event, pointer, eventTarget }, scope) { interaction, event, interaction.prepared.name, 'resume', interaction.element); interaction._fireEvent(resumeEvent); - modifiers.resetStatuses(interaction.modifiers.statuses, scope.modifiers); utils.pointer.copyCoords(interaction.coords.prev, interaction.coords.cur); break; @@ -120,15 +119,15 @@ function release ({ interaction, event }, scope) { const modifierArg = { interaction, pageCoords: utils.extend({}, interaction.coords.cur.page), - statuses: {}, + statuses: inertiaPossible && interaction.modifiers.statuses.map( + modifierStatus => utils.extend({}, modifierStatus) + ), preEnd: true, requireEndOnly: true, }; // smoothEnd if (inertiaPossible && !inertia) { - modifiers.resetStatuses(modifierArg.statuses, scope.modifiers); - modifierResult = modifiers.setAll(modifierArg, scope.modifiers); if (modifierResult.shouldMove && modifierResult.locked) { @@ -163,8 +162,6 @@ function release ({ interaction, event }, scope) { modifierArg.pageCoords.x += status.xe; modifierArg.pageCoords.y += status.ye; - modifiers.resetStatuses(modifierArg.statuses, scope.modifiers); - modifierResult = modifiers.setAll(modifierArg, scope.modifiers); status.modifiedXe += modifierResult.delta.x; diff --git a/packages/interact/index.js b/packages/interact/index.js index 5f9cf86a5..6047b5002 100644 --- a/packages/interact/index.js +++ b/packages/interact/index.js @@ -6,6 +6,7 @@ import inertia from '@interactjs/inertia'; import * as pointerEvents from '@interactjs/pointerEvents'; import * as autoStart from '@interactjs/autoStart'; import * as actions from '@interactjs/actions'; +import modifiersBase from '@interactjs/modifiers/base'; import * as modifiers from '@interactjs/modifiers'; import * as snappers from '@interactjs/utils/snappers'; import autoScroll from '@interactjs/autoScroll'; @@ -29,11 +30,19 @@ export function init (window) { interact.use(actions); // snap, resize, etc. - interact.use(modifiers); - + interact.use(modifiersBase); + interact.modifiers = modifiers; interact.snappers = snappers; interact.createSnapGrid = interact.snappers.grid; + // for backwrads compatibility + for (const type in modifiers) { + const { _defaults, _methods } = modifiers[type]; + + _defaults._methods = _methods; + scope.defaults.perAction[type] = _defaults; + } + // autoScroll interact.use(autoScroll); diff --git a/packages/modifiers/README.md b/packages/modifiers/README.md new file mode 100644 index 000000000..ad187f990 --- /dev/null +++ b/packages/modifiers/README.md @@ -0,0 +1,25 @@ +# modifiers + +Use modifiers to change the coordinates of drag, resize and gesture events. + +The `options` object passed to the action methods can have a `modifiers` array +which will be applied to events of that action type. + +```js +// create a restrict modifier to prevent dragging an element out of its parent +const restrictToParent = interact.modifiers.restrict({ + restriction: 'parent', + elementRect: { left: 0, right: 0, top: 1, bottom: 1 }, +}) + +// create a snap modifier which changes the event coordinates to the closest +// corner of a grid +const snap100x100 = interact.modifiers.snap({ + targets: [interact.snappers.grid({ x: 100, y: 100 })], +}), + +// apply the restrict and then the snap modifiers to drag events +interact(target).draggable({ + modifiers: [restrictToParent, snap100x100], +}) +``` diff --git a/packages/modifiers/base.js b/packages/modifiers/base.js index 828ee36e4..ca33ea352 100644 --- a/packages/modifiers/base.js +++ b/packages/modifiers/base.js @@ -7,154 +7,177 @@ function init (scope) { scope.modifiers = { names: [] }; + scope.defaults.perAction.modifiers = []; + interactions.signals.on('new', function (interaction) { interaction.modifiers = { startOffset: { left: 0, right: 0, top: 0, bottom: 0 }, offsets : {}, - statuses : resetStatuses({}, scope.modifiers), + statuses : null, result : null, }; }); interactions.signals.on('before-action-start' , arg => - start(arg, scope.modifiers, arg.interaction.coords.start.page)); + start(arg, arg.interaction.coords.start.page)); interactions.signals.on('action-resume', arg => { - beforeMove(arg, scope.modifiers); - start(arg, scope.modifiers, arg.interaction.coords.cur.page); + beforeMove(arg); + start(arg, arg.interaction.coords.cur.page); }); - interactions.signals.on('before-action-move', arg => beforeMove(arg, scope.modifiers)); - interactions.signals.on('before-action-end', arg => beforeEnd(arg, scope.modifiers)); + interactions.signals.on('before-action-move', beforeMove); + interactions.signals.on('before-action-end', beforeEnd); + + interactions.signals.on('before-action-start', setCoords); + interactions.signals.on('before-action-move', setCoords); - interactions.signals.on('before-action-start', arg => setCurCoords(arg, scope.modifiers)); - interactions.signals.on('before-action-move', arg => setCurCoords(arg, scope.modifiers)); + interactions.signals.on('after-action-start', restoreCoords); + interactions.signals.on('after-action-move', restoreCoords); + interactions.signals.on('stop', stop); } -function startAll (arg, modifiers) { - const { interaction, pageCoords: page } = arg; - const { target, element, modifiers: { startOffset } } = interaction; - const rect = target.getRect(element); +function startAll (arg) { + for (const status of arg.statuses) { + if (status.methods.start) { + arg.status = status; + status.methods.start(arg); + } + } +} - if (rect) { - startOffset.left = page.x - rect.left; - startOffset.top = page.y - rect.top; +function getRectOffset (rect, coords) { + return rect + ? { + left : coords.x - rect.left, + top : coords.y - rect.top, + right : rect.right - coords.x, + bottom: rect.bottom - coords.y, + } + : { + left : 0, + top : 0, + right : 0, + bottom: 0, + }; +} - startOffset.right = rect.right - page.x; - startOffset.bottom = rect.bottom - page.y; +function start ({ interaction, phase }, pageCoords) { + const { target: interactable, element } = interaction; + const modifierList = getModifierList(interaction); + const statuses = prepareStatuses(modifierList); - if (!('width' in rect)) { rect.width = rect.right - rect.left; } - if (!('height' in rect)) { rect.height = rect.bottom - rect.top ; } - } - else { - startOffset.left = startOffset.top = startOffset.right = startOffset.bottom = 0; - } + const rect = extend({}, interactable.getRect(element)); - arg.rect = rect; - arg.interactable = target; - arg.element = element; + if (!('width' in rect)) { rect.width = rect.right - rect.left; } + if (!('height' in rect)) { rect.height = rect.bottom - rect.top ; } - for (const modifierName of modifiers.names) { - arg.options = target.options[interaction.prepared.name][modifierName]; - arg.status = arg.statuses[modifierName]; + const startOffset = getRectOffset(rect, pageCoords); - if (!arg.options) { - continue; - } + interaction.modifiers.startOffset = startOffset; + interaction.modifiers.startDelta = { x: 0, y: 0 }; - interaction.modifiers.offsets[modifierName] = modifiers[modifierName].start(arg); - } + const arg = { + interaction, + interactable, + element, + pageCoords, + phase, + rect, + startOffset, + statuses, + preEnd: false, + requireEndOnly: false, + }; + + interaction.modifiers.statuses = statuses; + startAll(arg); + + arg.pageCoords = extend({}, interaction.coords.start.page); + + const result = interaction.modifiers.result = setAll(arg); + + return result; } -function setAll (arg, modifiers) { - const { interaction, statuses, preEnd, requireEndOnly } = arg; +function setAll (arg) { + const { interaction, phase, preEnd, requireEndOnly, rect, skipModifiers } = arg; + + const statuses = skipModifiers + ? arg.statuses.slice(interaction.modifiers.skil) + : arg.statuses; - arg.modifiedCoords = extend({}, arg.pageCoords); + arg.coords = extend({}, arg.pageCoords); + arg.rect = extend({}, rect); const result = { delta: { x: 0, y: 0 }, - coords: arg.modifiedCoords, - changed: false, - locked: false, + coords: arg.coords, shouldMove: true, }; - for (const modifierName of modifiers.names) { - const modifier = modifiers[modifierName]; - const options = interaction.target.options[interaction.prepared.name][modifierName]; + for (const status of statuses) { + const { options } = status; - if (!shouldDo(options, preEnd, requireEndOnly)) { continue; } + if (!status.methods.set || + !shouldDo(options, preEnd, requireEndOnly, phase)) { continue; } - arg.status = arg.status = statuses[modifierName]; - arg.options = options; - arg.offset = arg.interaction.modifiers.offsets[modifierName]; + arg.status = status; + status.methods.set(arg); + } - modifier.set(arg); + result.delta.x = arg.coords.x - arg.pageCoords.x; + result.delta.y = arg.coords.y - arg.pageCoords.y; - if (arg.status.locked) { - arg.modifiedCoords.x += arg.status.delta.x; - arg.modifiedCoords.y += arg.status.delta.y; - result.delta.x += arg.status.delta.x; - result.delta.y += arg.status.delta.y; - - result.locked = true; - } - } - - const changed = - interaction.coords.cur.page.x !== arg.modifiedCoords.x || - interaction.coords.cur.page.y !== arg.modifiedCoords.y; + const differsFromPrevCoords = + interaction.coords.prev.page.x !== result.coords.x || + interaction.coords.prev.page.y !== result.coords.y; // a move should be fired if: - // - there are no modifiers enabled, - // - no modifiers are "locked" i.e. have changed the pointer's coordinates, or - // - the locked coords have changed since the last pointer move - result.shouldMove = !arg.status || !result.locked || changed; + // - the modified coords are different to the prev interaction coords + // - there's a non zero result.delta + result.shouldMove = differsFromPrevCoords || + result.delta.x !== 0 || result.delta.y !== 0; return result; } -function resetStatuses (statuses, modifiers) { - for (const modifierName of modifiers.names) { - const status = statuses[modifierName] || {}; +function prepareStatuses (modifierList) { + const statuses = []; - status.delta = { x: 0, y: 0 }; - status.locked = false; + for (let index = 0; index < modifierList.length; index++) { + const { options, methods } = modifierList[index]; - statuses[modifierName] = status; - } + if (options && options.enabled === false) { continue; } - return statuses; -} - -function start ({ interaction, phase }, modifiers, pageCoords) { - const arg = { - interaction, - pageCoords, - phase, - startOffset: interaction.modifiers.startOffset, - statuses: interaction.modifiers.statuses, - preEnd: false, - requireEndOnly: false, - }; + const status = { + options, + methods, + index, + }; - startAll(arg, modifiers); - resetStatuses(arg.statuses, modifiers); + statuses.push(status); + } - arg.pageCoords = extend({}, interaction.coords.start.page); - interaction.modifiers.result = setAll(arg, modifiers); + return statuses; } -function beforeMove ({ interaction, preEnd }, modifiers) { +function beforeMove ({ interaction, phase, preEnd, skipModifiers }) { + const { target: interactable, element } = interaction; const modifierResult = setAll( { interaction, + interactable, + element, preEnd, + phase, pageCoords: interaction.coords.cur.page, + rect: interactable.getRect(element), statuses: interaction.modifiers.statuses, requireEndOnly: false, - }, modifiers); + skipModifiers, + }); interaction.modifiers.result = modifierResult; @@ -165,58 +188,131 @@ function beforeMove ({ interaction, preEnd }, modifiers) { } } -function beforeEnd ({ interaction, event }, modifiers) { - for (const modifierName of modifiers.names) { - const options = interaction.target.options[interaction.prepared.name][modifierName]; +function beforeEnd (arg) { + const { interaction, event } = arg; + const statuses = interaction.modifiers.statuses; + + if (!statuses || !statuses.length) { + return; + } + + let didPreEnd = false; + + for (const status of statuses) { + arg.status = status; + const { options, methods } = status; + + const endResult = methods.beforeEnd && methods.beforeEnd(arg); + + if (endResult === false) { + return false; + } // if the endOnly option is true for any modifier - if (shouldDo(options, true, true)) { + if (!didPreEnd && shouldDo(options, true, true)) { // fire a move event at the modified coordinates interaction.move({ event, preEnd: true }); - break; + didPreEnd = true; } } } -function setCurCoords (arg, modifiers) { +function stop (arg) { const { interaction } = arg; + const statuses = interaction.modifiers.statuses; + + if (!statuses || !statuses.length) { + return; + } + const modifierArg = extend({ - page: interaction.coords.cur.page, - client: interaction.coords.cur.client, + statuses, + interactable: interaction.target, + element: interaction.element, }, arg); - for (let i = 0; i < modifiers.names.length; i++) { - const modifierName = modifiers.names[i]; - modifierArg.options = interaction.target.options[interaction.prepared.name][modifierName]; - if (!modifierArg.options || !modifierArg.options.enabled) { - continue; - } + restoreCoords(arg); - const status = interaction.modifiers.statuses[modifierName]; + for (const status of statuses) { + modifierArg.status = status; - if (status.locked) { - modifierArg.page.x += status.delta.x; - modifierArg.page.y += status.delta.y; - modifierArg.client.x += status.delta.x; - modifierArg.client.y += status.delta.y; - } + if (status.methods.stop) { status.methods.stop(modifierArg); } + } + + arg.interaction.modifiers.statuses = null; +} + +function setCoords (arg) { + const { interaction, phase } = arg; + const curCoords = arg.curCoords || interaction.coords.cur; + const startCoords = arg.startCoords || interaction.coords.start; + const { result, startDelta } = interaction.modifiers; + const curDelta = result.delta; + + if (phase === 'start') { + extend(interaction.modifiers.startDelta, result.delta); + } + + for (const [coordsSet, delta] of [[startCoords, startDelta], [curCoords, curDelta]]) { + coordsSet.page.x += delta.x; + coordsSet.page.y += delta.y; + coordsSet.client.x += delta.x; + coordsSet.client.y += delta.y; } } -function shouldDo (options, preEnd, requireEndOnly) { - return (options && options.enabled - && (preEnd || !options.endOnly) - && (!requireEndOnly || options.endOnly)); +function restoreCoords ({ interaction: { coords, modifiers } }) { + const { startDelta, result: { delta: curDelta } } = modifiers; + + for (const [coordsSet, delta] of [[coords.start, startDelta], [coords.cur, curDelta]]) { + coordsSet.page.x -= delta.x; + coordsSet.page.y -= delta.y; + coordsSet.client.x -= delta.x; + coordsSet.client.y -= delta.y; + } + +} + +function getModifierList (interaction) { + const actionOptions = interaction.target.options[interaction.prepared.name]; + const actionModifiers = actionOptions.modifiers; + + if (actionModifiers && actionModifiers.length) { + return actionModifiers; + } + + return ['snap', 'snapSize', 'snapEdges', 'restrict', 'restrictEdges', 'restrictSize'] + .map(type => { + const options = actionOptions[type]; + + return options && options.enabled && { + options, + methods: options._methods, + }; + }) + .filter(m => !!m); +} + +function shouldDo (options, preEnd, requireEndOnly, phase) { + return options + ? options.enabled !== false && + (preEnd || !options.endOnly) && + (!requireEndOnly || options.endOnly) && + (options.setStart || phase !== 'start') + : !requireEndOnly; } export default { init, startAll, setAll, - resetStatuses, + prepareStatuses, start, beforeMove, beforeEnd, + stop, shouldDo, + getModifierList, + getRectOffset, }; diff --git a/packages/modifiers/index.js b/packages/modifiers/index.js index 4e8064121..ff4d9aa3f 100644 --- a/packages/modifiers/index.js +++ b/packages/modifiers/index.js @@ -1,28 +1,44 @@ -import modifiers from './base'; -import snap from './snap'; -import snapSize from './snapSize'; -import snapEdges from './snapEdges'; -import restrict from './restrict'; -import restrictEdges from './restrictEdges'; -import restrictSize from './restrictSize'; - -function init (scope) { - modifiers.init(scope); - snap.init(scope); - snapSize.init(scope); - snapEdges.init(scope); - restrict.init(scope); - restrictEdges.init(scope); - restrictSize.init(scope); -} +import snapModule from './snap'; +import snapSizeModule from './snapSize'; +import snapEdgesModule from './snapEdges'; +import restrictModule from './restrict'; +import restrictEdgesModule from './restrictEdges'; +import restrictSizeModule from './restrictSize'; + +export const snap = makeModifier('snap', snapModule); +export const snapSize = makeModifier('snapSize', snapSizeModule); +export const snapEdges = makeModifier('snapEdges', snapEdgesModule); +export const restrict = makeModifier('restrict', restrictModule); +export const restrictEdges = makeModifier('restrictEdges', restrictEdgesModule); +export const restrictSize = makeModifier('restrictSize', restrictSizeModule); + +function makeModifier (name, module) { + const methods = { start: module.start, set: module.set }; + const { defaults } = module; + + const modifier = options => { + options = options || {}; + + // add missing defaults to options + options.enabled = options.enabled !== false; -export { - modifiers, - snap, - snapSize, - snapEdges, - restrict, - restrictEdges, - restrictSize, - init, -}; + for (const prop in defaults) { + if (!(prop in options)) { + options[prop] = defaults[prop]; + } + } + + return { options, methods }; + }; + + Object.defineProperty( + modifier, + 'name', + { value: name }); + + // for backwrads compatibility + modifier._defaults = defaults; + modifier._methods = methods; + + return modifier; +} diff --git a/packages/modifiers/restrict.js b/packages/modifiers/restrict.js index 8eee157be..4b85382a0 100644 --- a/packages/modifiers/restrict.js +++ b/packages/modifiers/restrict.js @@ -1,21 +1,9 @@ import * as is from '@interactjs/utils/is'; -import extend from '@interactjs/utils/extend'; import rectUtils from '@interactjs/utils/rect'; -function init (scope) { - const { - modifiers, - defaults, - } = scope; - - modifiers.restrict = restrict; - modifiers.names.push('restrict'); - - defaults.perAction.restrict = restrict.defaults; -} - -function start ({ rect, startOffset, options }) { - const elementRect = options && options.elementRect; +function start ({ rect, startOffset, status }) { + const { options } = status; + const { elementRect } = options; const offset = {}; if (rect && elementRect) { @@ -29,63 +17,44 @@ function start ({ rect, startOffset, options }) { offset.left = offset.top = offset.right = offset.bottom = 0; } - return offset; + status.offset = offset; } -function set ({ modifiedCoords, interaction, status, phase, offset, options }) { - if (phase === 'start' && options.elementRect) { return; } - - const page = extend({}, modifiedCoords); +function set ({ coords, interaction, status }) { + const { options, offset } = status; - const restriction = getRestrictionRect(options.restriction, interaction, page); + const restriction = getRestrictionRect(options.restriction, interaction, coords); if (!restriction) { return status; } - status.delta.x = 0; - status.delta.y = 0; - status.locked = false; - const rect = restriction; - let modifiedX = page.x; - let modifiedY = page.y; // object is assumed to have // x, y, width, height or // left, top, right, bottom if ('x' in restriction && 'y' in restriction) { - modifiedX = Math.max(Math.min(rect.x + rect.width - offset.right , page.x), rect.x + offset.left); - modifiedY = Math.max(Math.min(rect.y + rect.height - offset.bottom, page.y), rect.y + offset.top ); + coords.x = Math.max(Math.min(rect.x + rect.width - offset.right , coords.x), rect.x + offset.left); + coords.y = Math.max(Math.min(rect.y + rect.height - offset.bottom, coords.y), rect.y + offset.top ); } else { - modifiedX = Math.max(Math.min(rect.right - offset.right , page.x), rect.left + offset.left); - modifiedY = Math.max(Math.min(rect.bottom - offset.bottom, page.y), rect.top + offset.top ); + coords.x = Math.max(Math.min(rect.right - offset.right , coords.x), rect.left + offset.left); + coords.y = Math.max(Math.min(rect.bottom - offset.bottom, coords.y), rect.top + offset.top ); } - - status.delta.x = modifiedX - page.x; - status.delta.y = modifiedY - page.y; - - status.locked = !!(status.delta.x || status.delta.y); - - status.modifiedX = modifiedX; - status.modifiedY = modifiedY; } -function getRestrictionRect (value, interaction, page) { +function getRestrictionRect (value, interaction, coords) { if (is.func(value)) { - return rectUtils.resolveRectLike(value, interaction.target, interaction.element, [page.x, page.y, interaction]); + return rectUtils.resolveRectLike(value, interaction.target, interaction.element, [coords.x, coords.y, interaction]); } else { return rectUtils.resolveRectLike(value, interaction.target, interaction.element); } } const restrict = { - init, start, set, getRestrictionRect, defaults: { - enabled : false, - endOnly : false, restriction: null, elementRect: null, }, diff --git a/packages/modifiers/restrictEdges.js b/packages/modifiers/restrictEdges.js index 46378abf0..e7e012754 100644 --- a/packages/modifiers/restrictEdges.js +++ b/packages/modifiers/restrictEdges.js @@ -17,19 +17,8 @@ const { getRestrictionRect } = restrict; const noInner = { top: +Infinity, left: +Infinity, bottom: -Infinity, right: -Infinity }; const noOuter = { top: -Infinity, left: -Infinity, bottom: +Infinity, right: +Infinity }; -function init (scope) { - const { - modifiers, - defaults, - } = scope; - - modifiers.restrictEdges = restrictEdges; - modifiers.names.push('restrictEdges'); - - defaults.perAction.restrictEdges = restrictEdges.defaults; -} - -function start ({ interaction, options }) { +function start ({ interaction, status }) { + const { options } = status; const startOffset = interaction.modifiers.startOffset; let offset; @@ -41,7 +30,7 @@ function start ({ interaction, options }) { offset = offset || { x: 0, y: 0 }; - return { + status.offset = { top: offset.y + startOffset.top, left: offset.x + startOffset.left, bottom: offset.y - startOffset.bottom, @@ -49,44 +38,33 @@ function start ({ interaction, options }) { }; } -function set ({ modifiedCoords, interaction, status, phase, offset, options }) { +function set ({ coords, interaction, status }) { + const { offset, options } = status; const edges = interaction.prepared.linkedEdges || interaction.prepared.edges; - if (!interaction.interacting() || !edges || phase === 'start') { + if (!edges) { return; } - const page = extend({}, modifiedCoords); + const page = extend({}, coords); const inner = getRestrictionRect(options.inner, interaction, page) || {}; const outer = getRestrictionRect(options.outer, interaction, page) || {}; fixRect(inner, noInner); fixRect(outer, noOuter); - let modifiedX = page.x; - let modifiedY = page.y; - - status.delta.x = 0; - status.delta.y = 0; - status.locked = false; - if (edges.top) { - modifiedY = Math.min(Math.max(outer.top + offset.top, page.y), inner.top + offset.top); + coords.y = Math.min(Math.max(outer.top + offset.top, page.y), inner.top + offset.top); } else if (edges.bottom) { - modifiedY = Math.max(Math.min(outer.bottom + offset.bottom, page.y), inner.bottom + offset.bottom); + coords.y = Math.max(Math.min(outer.bottom + offset.bottom, page.y), inner.bottom + offset.bottom); } if (edges.left) { - modifiedX = Math.min(Math.max(outer.left + offset.left, page.x), inner.left + offset.left); + coords.x = Math.min(Math.max(outer.left + offset.left, page.x), inner.left + offset.left); } else if (edges.right) { - modifiedX = Math.max(Math.min(outer.right + offset.right, page.x), inner.right + offset.right); + coords.x = Math.max(Math.min(outer.right + offset.right, page.x), inner.right + offset.right); } - - status.delta.x = modifiedX - page.x; - status.delta.y = modifiedY - page.y; - - status.locked = !!(status.delta.x || status.delta.y); } function fixRect (rect, defaults) { @@ -100,15 +78,12 @@ function fixRect (rect, defaults) { } const restrictEdges = { - init, noInner, noOuter, getRestrictionRect, start, set, defaults: { - enabled: false, - endOnly: false, inner: null, outer: null, offset: null, diff --git a/packages/modifiers/restrictSize.js b/packages/modifiers/restrictSize.js index 209c75446..1d5d6fb52 100644 --- a/packages/modifiers/restrictSize.js +++ b/packages/modifiers/restrictSize.js @@ -16,27 +16,16 @@ import restrictEdges from './restrictEdges'; const noMin = { width: -Infinity, height: -Infinity }; const noMax = { width: +Infinity, height: +Infinity }; -function init (scope) { - const { - modifiers, - defaults, - } = scope; - - modifiers.restrictSize = restrictSize; - modifiers.names.push('restrictSize'); - - defaults.perAction.restrictSize = restrictSize.defaults; -} - -function start ({ interaction }) { - return restrictEdges.start({ interaction }); +function start (arg) { + return restrictEdges.start(arg); } function set (arg) { - const { interaction, options, phase } = arg; + const { interaction, status } = arg; + const { options } = status; const edges = interaction.prepared.linkedEdges || interaction.prepared.edges; - if (!interaction.interacting() || !edges || phase === 'start') { + if (!edges) { return; } @@ -45,7 +34,7 @@ function set (arg) { const minSize = rectUtils.tlbrToXywh(restrictEdges.getRestrictionRect(options.min, interaction)) || noMin; const maxSize = rectUtils.tlbrToXywh(restrictEdges.getRestrictionRect(options.max, interaction)) || noMax; - arg.options = { + status.options = { enabled: options.enabled, endOnly: options.endOnly, inner: extend({}, restrictEdges.noInner), @@ -53,32 +42,31 @@ function set (arg) { }; if (edges.top) { - arg.options.inner.top = rect.bottom - minSize.height; - arg.options.outer.top = rect.bottom - maxSize.height; + status.options.inner.top = rect.bottom - minSize.height; + status.options.outer.top = rect.bottom - maxSize.height; } else if (edges.bottom) { - arg.options.inner.bottom = rect.top + minSize.height; - arg.options.outer.bottom = rect.top + maxSize.height; + status.options.inner.bottom = rect.top + minSize.height; + status.options.outer.bottom = rect.top + maxSize.height; } if (edges.left) { - arg.options.inner.left = rect.right - minSize.width; - arg.options.outer.left = rect.right - maxSize.width; + status.options.inner.left = rect.right - minSize.width; + status.options.outer.left = rect.right - maxSize.width; } else if (edges.right) { - arg.options.inner.right = rect.left + minSize.width; - arg.options.outer.right = rect.left + maxSize.width; + status.options.inner.right = rect.left + minSize.width; + status.options.outer.right = rect.left + maxSize.width; } restrictEdges.set(arg); + + status.options = options; } const restrictSize = { - init, start, set, defaults: { - enabled: false, - endOnly: false, min: null, max: null, }, diff --git a/packages/modifiers/snap.js b/packages/modifiers/snap.js index 28bf8d9ed..50bdfecb6 100644 --- a/packages/modifiers/snap.js +++ b/packages/modifiers/snap.js @@ -1,23 +1,10 @@ import * as utils from '@interactjs/utils'; -function init (scope) { - const { - modifiers, - defaults, - } = scope; - - - modifiers.snap = snap; - modifiers.names.push('snap'); - - defaults.perAction.snap = snap.defaults; -} - -function start ({ interaction, interactable, element, rect, startOffset, options }) { +function start ({ interaction, interactable, element, rect, status, startOffset }) { + const { options } = status; const offsets = []; const optionsOrigin = utils.rect.rectToXY(utils.rect.resolveRectLike(options.origin)); const origin = optionsOrigin || utils.getOriginXY(interactable, element, interaction.prepared.name); - options = options || interactable.options[interaction.prepared.name].snap || {}; let snapOffset; @@ -45,18 +32,14 @@ function start ({ interaction, interactable, element, rect, startOffset, options offsets.push(snapOffset); } - return offsets; + status.offset = offsets; } -function set ({ interaction, modifiedCoords, status, phase, options, offset: offsets }) { - const relativePoints = options && options.relativePoints; - - if (phase === 'start' && relativePoints && relativePoints.length) { - return; - } +function set ({ interaction, coords, status }) { + const { options, offset: offsets } = status; const origin = utils.getOriginXY(interaction.target, interaction.element, interaction.prepared.name); - const page = utils.extend({}, modifiedCoords); + const page = utils.extend({}, coords); const targets = []; let target; let i; @@ -139,22 +122,16 @@ function set ({ interaction, modifiedCoords, status, phase, options, offset: off } } - status.modifiedX = closest.target.x; - status.modifiedY = closest.target.y; - - status.delta.x = closest.dx; - status.delta.y = closest.dy; - - status.locked = closest.inRange; + if (closest.inRange) { + coords.x = closest.target.x; + coords.y = closest.target.y; + } } const snap = { - init, start, set, defaults: { - enabled: false, - endOnly: false, range : Infinity, targets: null, offsets: null, diff --git a/packages/modifiers/snapSize.js b/packages/modifiers/snapSize.js index 00dd050b0..cc75ffdb3 100644 --- a/packages/modifiers/snapSize.js +++ b/packages/modifiers/snapSize.js @@ -5,32 +5,23 @@ import extend from '@interactjs/utils/extend'; import * as is from '@interactjs/utils/is'; import snap from './snap'; -function init (scope) { - const { - modifiers, - defaults, - } = scope; - - modifiers.snapSize = snapSize; - modifiers.names.push('snapSize'); - - defaults.perAction.snapSize = snapSize.defaults; -} - function start (arg) { - const { interaction, status, options } = arg; + const { interaction, status } = arg; + const { options } = status; const edges = interaction.prepared.edges; if (!edges) { return null; } - arg.options = { - relativePoints: [{ - x: edges.left? 0 : 1, - y: edges.top ? 0 : 1, - }], - origin: { x: 0, y: 0 }, - offset: options.offset || 'self', - range: options.range, + arg.status = { + options: { + relativePoints: [{ + x: edges.left? 0 : 1, + y: edges.top ? 0 : 1, + }], + origin: { x: 0, y: 0 }, + offset: options.offset || 'self', + range: options.range, + }, }; status.targetFields = status.targetFields || [ @@ -38,21 +29,22 @@ function start (arg) { ['x', 'y'], ]; - const offsets = snap.start(arg); - arg.options = options; + snap.start(arg); + status.offset = arg.status.offset; - return offsets; + arg.status = status; } function set (arg) { - const { interaction, status, options, offset, modifiedCoords } = arg; + const { interaction, status, coords } = arg; + const { options, offset } = status; const relative = { - x: modifiedCoords.x - offset[0].x, - y: modifiedCoords.y - offset[0].y, + x: coords.x - offset[0].x, + y: coords.y - offset[0].y, }; - arg.options = extend({}, options); - arg.options.targets = []; + status.options = extend({}, options); + status.options.targets = []; for (const snapTarget of (options.targets || [])) { let target; @@ -75,19 +67,18 @@ function set (arg) { } } - arg.options.targets.push(target); + status.options.targets.push(target); } snap.set(arg); + + status.options = options; } const snapSize = { - init, start, set, defaults: { - enabled: false, - endOnly: false, range : Infinity, targets: null, offset: null, diff --git a/packages/modifiers/tests/base.js b/packages/modifiers/tests/base.js new file mode 100644 index 000000000..a0cea807a --- /dev/null +++ b/packages/modifiers/tests/base.js @@ -0,0 +1,158 @@ +import test from '@interactjs/_dev/test/test'; +import * as helpers from '@interactjs/_dev/test/helpers'; +import Interaction from '@interactjs/core/Interaction'; +import * as utils from '@interactjs/utils'; +import modifiersBase from '@interactjs/modifiers/base'; + +test('modifiers/base', t => { + const scope = helpers.mockScope(); + + modifiersBase.init(scope); + + const interaction = new scope.interactions.new({}); + + t.ok(utils.is.object(interaction.modifiers), 'modifiers prop is added new Interaction'); + + const element = utils.win.window.document.documentElement; + const interactable = scope.interactables.new(element); + const startEvent = { + pageX: 100, + pageY: 200, + clientX: 100, + clientY: 200, + target: element, + }; + const moveEvent = { + pageX: 400, + pageY: 500, + clientX: 400, + clientY: 500, + target: element, + }; + const options = { target: { x: 100, y: 100 }, setStart: true }; + + interactable.rectChecker(() => ({ top: 0, left: 0, bottom: 50, right: 50 })); + interaction.pointerDown(startEvent, startEvent, element); + + interactable.options.test = { + modifiers: [ + { + options, + methods: targetModifier, + }, + ], + }; + + interaction.start({ name: 'test' }, interactable, element); + + t.ok( + options.started, + 'modifier methods.start() was called', + ); + + t.ok( + options.setted, + 'modifier methods.set() was called', + ); + + t.deepEqual( + interaction.prevEvent.page, + options.target, + 'start event coords are modified'); + + t.deepEqual( + interaction.coords.start.page, + { x: 100, y: 200}, + 'interaction.coords.start are restored after action start phase'); + + t.deepEqual( + interaction.coords.cur.page, + { x: 100, y: 200}, + 'interaction.coords.cur are restored after action start phase'); + + interaction.pointerMove(moveEvent, moveEvent, element); + + t.deepEqual( + interaction.coords.cur.page, + { x: moveEvent.pageX, y: moveEvent.pageY }, + 'interaction.coords.cur are restored after action move phase'); + + t.deepEqual( + interaction.coords.start.page, + { x: startEvent.pageX, y: startEvent.pageY }, + 'interaction.coords.start are restored after action move phase'); + + t.deepEqual( + { x: interaction.prevEvent.x0, y: interaction.prevEvent.y0 }, + { x: 100, y: 100}, + 'move event start coords are modified'); + + interaction.stop(); + + t.ok( + options.stopped, + 'modifier methods.stop() was called', + ); + + // don't set start + options.setStart = null; + // add second modifier + interactable.options.test.modifiers.push({ + options, + methods: doubleModifier, + }); + + interaction.pointerDown(startEvent, startEvent, element); + interaction.start({ name: 'test' }, interactable, element); + + t.notOk( + options.setted, + 'modifier methods.set() was not called on start phase without options.setStart', + ); + + t.deepEqual( + interaction.prevEvent.page, + { x: 100, y: 200}, + 'start event coords are not modified without options.setStart'); + + t.deepEqual( + interaction.coords.start.page, + { x: 100, y: 200}, + 'interaction.coords.start are not modified without options.setStart'); + + interaction.pointerMove(moveEvent, moveEvent, element); + + t.deepEqual( + interaction.prevEvent.page, + { x: 200, y: 200}, + 'move event coords are modified by all modifiers'); + + t.end(); +}); + +const targetModifier = { + start ({ status }) { + status.options.started = true; + }, + set ({ status, coords }) { + const { target } = status.options; + + coords.x = target.x; + coords.y = target.y; + + status.options.setted = true; + }, + stop ({ status }) { + status.options.stopped = true; + delete status.options.started; + delete status.options.setted; + }, +}; + +const doubleModifier = { + start () {}, + set ({ coords }) { + coords.x *= 2; + coords.y *= 2; + }, +}; diff --git a/packages/modifiers/tests/restrictEdges.js b/packages/modifiers/tests/restrictEdges.js index f6d8868ff..32eb84c7c 100644 --- a/packages/modifiers/tests/restrictEdges.js +++ b/packages/modifiers/tests/restrictEdges.js @@ -13,37 +13,33 @@ test('restrictEdges', t => { interaction._interacting = true; const options = { enabled: true }; - const status = { - delta: { x: 0, y: 0 }, - }; const coords = { x: 40, y: 40 }; const offset = { top: 0, left: 0, bottom: 0, right: 0 }; - const arg = { interaction, options, status, modifiedCoords: coords, offset }; + const status = { options, offset }; + const arg = { interaction, status }; + + arg.coords = { ...coords }; // outer restriction options.outer = { top: 100, left: 100, bottom: 200, right: 200 }; restrictEdges.set(arg); t.deepEqual( - status, - { - delta: { x: 60, y: 60 }, - locked: true, - }, + arg.coords, + { x: coords.y + 60, y: coords.y + 60 }, 'outer restriction is applied correctly' ); + arg.coords = { ...coords }; + // inner restriction options.outer = null; options.inner = { top: 0, left: 0, bottom: 10, right: 10 }; restrictEdges.set(arg); t.deepEqual( - status, - { - delta: { x: -40, y: -40 }, - locked: true, - }, + arg.coords, + { x: coords.x - 40, y: coords.y - 40 }, 'inner restriction is applied correctly' ); @@ -54,17 +50,15 @@ test('restrictEdges', t => { bottom: 200, right: 200, }); + arg.coords = { ...coords }; options.outer = { top: 100, left: 100, bottom: 200, right: 200 }; options.inner = null; restrictEdges.set(arg); t.deepEqual( - status, - { - delta: { x: 160, y: 160 }, - locked: true, - }, + arg.coords, + { x: coords.x + 160, y: coords.x + 160 }, 'outer restriction is applied correctly with offset' ); @@ -78,9 +72,10 @@ test('restrictEdges', t => { }; options.offset = 'self'; + restrictEdges.start(arg); t.deepEqual( - restrictEdges.start(arg), + arg.status.offset, { top: 505, left: 910, bottom: 508, right: 916 }, 'start gets x/y from selector string' ); diff --git a/packages/modifiers/tests/restrictSize.js b/packages/modifiers/tests/restrictSize.js index fb8914d2a..a7d005696 100644 --- a/packages/modifiers/tests/restrictSize.js +++ b/packages/modifiers/tests/restrictSize.js @@ -1,68 +1,63 @@ import test from '@interactjs/_dev/test/test'; import { mockSignals } from '@interactjs/_dev/test/helpers'; -import RestrictSize from '@interactjs/modifiers/restrictSize'; +import rectUtils from '@interactjs/utils/rect'; +import base from '@interactjs/modifiers/base'; +import restrictSize from '@interactjs/modifiers/restrictSize'; import Interaction from '@interactjs/core/Interaction'; test('restrictSize', t => { + const edges = { left: true, top: true }; + const rect = { left: 0, top: 0, right: 200, bottom: 300 }; const interaction = new Interaction({ signals: mockSignals() }); + interaction.prepared = {}; - interaction.prepared.edges = { top: true, bottom: true, left: true, right: true }; + interaction.prepared.edges = edges; interaction.resizeRects = {}; - interaction.resizeRects.inverted = { x: 10, y: 20, width: 300, height: 200 }; + interaction.resizeRects.inverted = rectUtils.xywhToTlbr(rect); + interaction.modifiers = {}; interaction._interacting = true; - t.test('works with min and max options', tt => { - const options = { - min: { width: 60, height: 50 }, - max: { width: 600, height: 500 }, - }; - const status = { - delta: { x: 0, y: 0 }, - }; - const pageCoords = { x: 5, y: 15 }; - const offset = { top: 0, bottom: 0, left: 0, right: 0 }; - const arg = { interaction, options, status, pageCoords, offset }; - - RestrictSize.set(arg); - tt.deepEqual(arg.options.inner, { top: 170, left: 250, bottom: -Infinity, right: -Infinity }); - tt.deepEqual(arg.options.outer, { top: -280, left: -290, bottom: Infinity, right: Infinity }); - tt.end(); - }); - - t.test('works with min option only', tt => { - const options = { - min: { width: 60, height: 50 }, - }; - const status = { - delta: { x: 0, y: 0 }, - }; - const pageCoords = { x: 5, y: 15 }; - const offset = { top: 0, bottom: 0, left: 0, right: 0 }; - const arg = { interaction, options, status, pageCoords, offset }; - - RestrictSize.set(arg); - tt.deepEqual(arg.options.inner, { top: 170, left: 250, bottom: -Infinity, right: -Infinity }); - tt.deepEqual(arg.options.outer, { top: -Infinity, left: -Infinity, bottom: Infinity, right: Infinity }); - tt.end(); - }); - - t.test('works with max option only', tt => { - const options = { - max: { width: 600, height: 500 }, - }; - const status = { - delta: { x: 0, y: 0 }, - }; - const pageCoords = { x: 5, y: 15 }; - const offset = { top: 0, bottom: 0, left: 0, right: 0 }; - const arg = { interaction, options, status, pageCoords, offset }; - - RestrictSize.set(arg); - tt.deepEqual(arg.options.inner, { top: Infinity, left: Infinity, bottom: -Infinity, right: -Infinity }); - tt.deepEqual(arg.options.outer, { top: -280, left: -290, bottom: Infinity, right: Infinity }); - tt.end(); - }); + const options = { + min: { width: 60, height: 50 }, + max: { width: 300, height: 350 }, + }; + const startCoords = Object.freeze({ x: 0, y: 0 }); + const offset = { top: 0, bottom: 0, left: 0, right: 0 }; + const status = { + options, + offset, + methods: restrictSize, + }; + const arg = { + interaction, + statuses: [status], + coords: startCoords, + pageCoords: startCoords, + options, + }; + + interaction.modifiers.startOffset = base.getRectOffset(rect, startCoords); + base.startAll(arg); + arg.status = status; + + const move1 = Object.freeze({ x: -50, y: -40 }); + arg.coords = { ...move1 }; + restrictSize.set(arg); + + t.deepEqual(arg.coords, move1, 'within both min and max'); + + const move2 = Object.freeze({ x: -200, y: -300 }); + arg.coords = { ...move2 }; + restrictSize.set(arg); + + t.deepEqual(arg.coords, { x: -100, y: -50 }, 'outside max'); + + const move3 = Object.freeze({ x: 250, y: 320 }); + arg.coords = { ...move3 }; + restrictSize.set(arg); + + t.deepEqual(arg.coords, { x: 140, y: 250 }, 'outside min'); t.end(); }); diff --git a/packages/modifiers/tests/snap.js b/packages/modifiers/tests/snap.js index 5e07f46dc..c2016972b 100644 --- a/packages/modifiers/tests/snap.js +++ b/packages/modifiers/tests/snap.js @@ -19,34 +19,23 @@ test('modifiers/snap', t => { range: Infinity, }; const status = { + options, delta: { x: 0, y: 0 }, + offset: [{ x: 0, y: 0 }], }; const pageCoords = Object.freeze({ x: 10, y: 20 }); const arg = { interaction, - options, status, pageCoords, - modifiedCoords: { ...pageCoords }, - offset: [{ x: 0, y: 0 }], + coords: { ...pageCoords }, }; snap.set(arg); t.deepEqual( - status, - { - locked: true, - range: Infinity, - realX: pageCoords.x, - realY: pageCoords.y, - delta: { - x: target0.x - pageCoords.x, - y: target0.y - pageCoords.y, - }, - modifiedX: target0.x, - modifiedY: target0.y, - }, + arg.coords, + target0, 'snap.set single target, zereo offset' ); diff --git a/packages/modifiers/tests/snapEdges.js b/packages/modifiers/tests/snapEdges.js index f8508bb27..48cce426e 100644 --- a/packages/modifiers/tests/snapEdges.js +++ b/packages/modifiers/tests/snapEdges.js @@ -26,36 +26,35 @@ test('modifiers/snapEdges', t => { const arg = { interaction, interactable: interaction.target, - options, status: null, pageCoords, - modifiedCoords: { ...pageCoords }, + coords: { ...pageCoords }, offset: [{ x: 0, y: 0 }], }; // resize from top left interaction.prepared.edges = { top: true, left: true }; - arg.status = { delta: { x: 0, y: 0 } }; + arg.status = { options }; snapEdges.start(arg); snapEdges.set(arg); t.deepEqual( - arg.status.delta, - { x: target0.left - pageCoords.x, y: target0.top - pageCoords.y }, - 'modified delta is correct'); + arg.coords, + { x: target0.left, y: target0.top }, + 'modified coords are correct'); // resize from bottom right interaction.prepared.edges = { bottom: true, right: true }; - arg.status = { delta: { x: 0, y: 0 } }; + arg.status = { options }; snapEdges.start(arg); snapEdges.set(arg); t.deepEqual( - arg.status.delta, - { x: target0.right - pageCoords.x, y: target0.bottom - pageCoords.y }, - 'modified coord is correct'); + arg.coords, + { x: target0.right, y: target0.bottom }, + 'modified coord are correct'); t.end(); }); diff --git a/packages/modifiers/tests/snapSize.js b/packages/modifiers/tests/snapSize.js index c224d7e34..4c2a5a261 100644 --- a/packages/modifiers/tests/snapSize.js +++ b/packages/modifiers/tests/snapSize.js @@ -21,37 +21,25 @@ test('modifiers/snapSize', t => { range: Infinity, }; const status = { + options, delta: { x: 0, y: 0 }, + offset: [{ x: 0, y: 0 }], }; const pageCoords = Object.freeze({ x: 10, y: 20 }); const arg = { interaction, interactable: interaction.target, - options, status, pageCoords, - modifiedCoords: { ...pageCoords }, - offset: [{ x: 0, y: 0 }], + coords: { ...pageCoords }, }; snapSize.start(arg); snapSize.set(arg); t.deepEqual( - status, - { - locked: true, - range: Infinity, - realX: pageCoords.x, - realY: pageCoords.y, - delta: { - x: target0.x - pageCoords.x, - y: target0.y - pageCoords.y, - }, - modifiedX: target0.x, - modifiedY: target0.y, - targetFields: [ [ 'width', 'height' ], [ 'x', 'y' ] ], - }, + arg.coords, + target0, 'snapSize.set single target, zereo offset' );