Skip to content

Commit

Permalink
fix(): Changed useMove() API, added operations & transformers
Browse files Browse the repository at this point in the history
  • Loading branch information
ykadosh committed Mar 21, 2021
1 parent 86750e7 commit 83cc8e6
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 235 deletions.
3 changes: 2 additions & 1 deletion .nycrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"lines": 90,
"functions": 90,
"statements": 90,
"reporter": ["lcov", "text-summary", "html"]
"reporter": ["lcov", "text-summary", "html"],
"exclude": ["**/index.js"]
}
105 changes: 8 additions & 97 deletions src/components/Movable/Movable.hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,110 +15,21 @@
*/

import {useRef, useCallback} from 'react';
import {noop} from 'utility/memory';
import {clamp} from 'utility/number';

export const operation = props => ({
dependencies: [],
onBeginMove: noop,
onMove: noop,
onEndMove: noop,
...props,
});

// The base hook
export const useBase = (ops = [], args) => {
export const useMove = ops => {
const shared = useRef({});
const dependencies = ops.reduce((acc, cur) => acc.concat(cur.dependencies), []).concat(Object.values(args));

const onBeginMove = useCallback(e => {
ops.forEach(({onBeginMove}) => onBeginMove(e, args, shared.current));
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
ops.forEach(({onBeginMove}) => onBeginMove(e, shared.current));
}, ops); // eslint-disable-line react-hooks/exhaustive-deps

const onMove = useCallback(e => {
ops.forEach(({onMove}) => onMove(e, args, shared.current));
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps
ops.forEach(({onMove}) => onMove(e, shared.current));
}, ops); // eslint-disable-line react-hooks/exhaustive-deps

const onEndMove = useCallback(e => {
ops.forEach(({onEndMove}) => onEndMove(e, args, shared.current))
}, dependencies); // eslint-disable-line react-hooks/exhaustive-deps

return {onBeginMove, onMove, onEndMove, ref: args.ref};
};

export const useNewMove = (...ops) => {
return useBase(ops, {});
};
/**
* Generate the set of props to be injected to a Movable component,
* applying the given constraints.
*
* @param constraints {array}
* @param args
* @return {{ref, onEndMove: function, onMove: function, onBeginMove: function}}
*/
export const useMove = ({constraints = [], ...args}) => {
const before = operation({
onBeginMove: (e, {ref}, shared) => {
const {top, left} = ref.current.getBoundingClientRect();
shared.next = {top, left};
shared.initial = {top, left};
},
onMove: ({dx, dy}, args, {next, initial})=> {
const {left, top} = initial;
next.left = left + dx;
next.top = top + dy;
},
});
const after = operation({
onMove: (e, {onMove}, {next}) => {
onMove({
top: Math.round(next.top),
left: Math.round(next.left),
});
},
});
const ops = [before, ...constraints, after];
return useBase(ops, args);
};

/**
* Generate the set of props to be injected to a Movable component,
* applying the given constraints. This is similar to useMove(),
* only here the ref is used as a movement 'pad', and the movement
* is usually applied to another element.
*
* @param constraints {array}
* @param args
* @return {{ref, onEndMove: function, onMove: function, onBeginMove: function}}
*/
export const useMoveArea = ({constraints = [], ...args}) => {
const before = operation({
onBeginMove: ({x, y}, {ref, onMove}, shared) => {
shared.initial = ref.current.getBoundingClientRect();
shared.next = {left: x - shared.initial.left, top: y - shared.initial.top};
shared.bounds = {top: 0, left: 0, right: shared.initial.width, bottom: shared.initial.height};
onMove({
top: Math.round(shared.next.top),
left: Math.round(shared.next.left),
});
},
onMove: ({x, y}, args, shared) => {
shared.next.left = Math.round(x - shared.initial.left);
shared.next.top = Math.round(y - shared.initial.top);
},
});
const after = operation({
onMove: (e, {onMove}, {next, bounds}) => {
next.left = clamp(next.left, bounds.left, bounds.right);
next.top = clamp(next.top, bounds.top, bounds.bottom);
ops.forEach(({onEndMove}) => onEndMove(e, shared.current))
}, ops); // eslint-disable-line react-hooks/exhaustive-deps

onMove({
top: Math.round(next.top),
left: Math.round(next.left),
});
},
});
const ops = [before, ...constraints, after];
return useBase(ops, args);
return {onBeginMove, onMove, onEndMove};
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,70 @@
*/

import {clamp} from 'utility/number';
import {operation} from './Movable.hooks';
import {noop} from 'utility/memory';

export const createOperation = handlers => ({
onBeginMove: noop,
onMove: noop,
onEndMove: noop,
...handlers,
});

/**
* Use the Movable component given in ref as a movable object.
*
* @param movable
* @returns {operation}
*/
export const reposition = movable => createOperation({
onBeginMove: (e, shared) => {
const {top, left} = movable.current.getBoundingClientRect();
shared.next = {top, left};
shared.initial = {top, left};
},
onMove: ({dx, dy}, shared)=> {
const {left, top} = shared.initial;
shared.next = {
left: left + dx,
top: top + dy,
};
},
});

/**
* Use the Movable component given in ref as a trackpad, rather than a movable object.
*
* @param ref
* @returns {operation}
*/
export const trackpad = ref => createOperation({
onBeginMove: ({x, y}, shared) => {
shared.initial = ref.current.getBoundingClientRect();
shared.next = {left: x - shared.initial.left, top: y - shared.initial.top};
shared.bounds = {top: 0, left: 0, right: shared.initial.width, bottom: shared.initial.height};
},
onMove: ({x, y}, shared) => {
const {bounds, initial} = shared;
shared.next = {
left: clamp(x - initial.left, bounds.left, bounds.right),
top: clamp(y - initial.top, bounds.top, bounds.bottom),
};
},
});

/**
* Limit the movement to the given container.
*
* @param container {ReactRef} A ref to the container element.
* @return {constraint}
* @return {operation}
*/
export const contain = container => operation({
dependencies: [container],
onBeginMove: (e, {ref}, shared) => {
const {width, height} = ref.current.getBoundingClientRect();
export const contain = (movable, container) => createOperation({
onBeginMove: (e, shared) => {
const {width, height} = movable.current.getBoundingClientRect();
shared.bounds = container.current.getBoundingClientRect();
shared.size = {width, height};
},
onMove: (e, args, shared) => {
onMove: (e, shared) => {
const {bounds, next, size} = shared;
shared.next = {
left: clamp(next.left, bounds.left, bounds.right - size.width),
Expand All @@ -46,11 +94,10 @@ export const contain = container => operation({
* @param horizontal {number} The horizontal grid step size
* @param vertical {number} The vertical grid step size
* @param strength {number} The snap strength, where 1 is
* @return {constraint}
* @return {operation}
*/
export const snap = (horizontal, vertical, strength = 1) => operation({
dependencies: [horizontal, vertical, strength],
onMove: (e, args, shared) => {
export const snap = (horizontal, vertical, strength = 1) => createOperation({
onMove: (e, shared) => {
const {top, left} = shared.next;
const rl = Math.round(left / horizontal) * horizontal; // Rounded left
const rt = Math.round(top / vertical) * vertical; // Rounded top
Expand All @@ -65,44 +112,24 @@ export const snap = (horizontal, vertical, strength = 1) => operation({
});

/**
* Apply padding to the movement boundaries.
* This can either be the bounds specified by the contain() constraints,
* or, if using the useMoveArea() hook, the bounding rect of the given ref.
* Transform the value in shared.next by applying the given transformers
*
* @param top {number}
* @param right {number}
* @param bottom {number}
* @param left {number}
* @return {constraint}
* @param t
* @returns {operation}
*/
export const padding = (top, right, bottom, left) => operation({
dependencies: [top, right, bottom, left],
onBeginMove: (e, args, shared) => {
shared.bounds = {
top: shared.bounds.top + top,
right: shared.bounds.right - right,
bottom: shared.bounds.bottom - bottom,
left: shared.bounds.left + left,
};
},
export const transform = (...t) => createOperation({
onBeginMove: (e, shared) => shared.next = t.reduce((acc, cur) => cur(acc), shared.next),
onMove: (e, shared) => shared.next = t.reduce((acc, cur) => cur(acc), shared.next),
});

export const trackpad = ref => operation({
dependencies: [ref],
onBeginMove: ({x, y}, args, shared) => {
shared.initial = ref.current.getBoundingClientRect();
shared.next = {left: x - shared.initial.left, top: y - shared.initial.top};
shared.bounds = {top: 0, left: 0, right: shared.initial.width, bottom: shared.initial.height};
},
onMove: ({x, y}, args, {next, bounds, initial}) => {
next.left = clamp(x - initial.left, bounds.left, bounds.right);
next.top = clamp(y - initial.top, bounds.top, bounds.bottom);
},
});

export const update = onUpdate => operation({
dependencies: [onUpdate],
onBeginMove: (e, args, {next}) => onUpdate(next),
onMove: (e, args, {next}) => onUpdate(next),
onEndMove: (e, args, {next}) => onUpdate(next),
/**
* Call the function given in callback, passing shared.next as an argument.
*
* @param onUpdate
* @returns {operation}
*/
export const update = onUpdate => createOperation({
onBeginMove: (e, {next}) => onUpdate(next),
onMove: (e, {next}) => onUpdate(next),
onEndMove: (e, {next}) => onUpdate(next),
});
Loading

0 comments on commit 83cc8e6

Please sign in to comment.