diff --git a/js/__tests__/snapUtils-test.js b/js/__tests__/snapUtils-test.js index ef3be524..68d791ad 100644 --- a/js/__tests__/snapUtils-test.js +++ b/js/__tests__/snapUtils-test.js @@ -8,7 +8,9 @@ import { near, snap, overlapY, - overlapX + overlapX, + snapWithin, + applySnap } from '../snapUtils'; describe('side functions', () => { @@ -102,28 +104,28 @@ describe('snap function', () => { expect(actual).toEqual(expected); }); it('snaps the left of A to the right of B', () => { - const a = {x: 120, y: 10, width: 100, height: 100}; + const a = {x: 120, y: 30, width: 100, height: 100}; const b = {x: 10, y: 10, width: 100, height: 100}; const actual = snap(a, b); const expected = {x: 110}; expect(actual).toEqual(expected); }); it('snaps the right of A to the left of B', () => { - const a = {x: 10, y: 10, width: 100, height: 100}; + const a = {x: 10, y: 30, width: 100, height: 100}; const b = {x: 120, y: 10, width: 100, height: 100}; const actual = snap(a, b); const expected = {x: 20}; expect(actual).toEqual(expected); }); it('snaps the top of A to the bottom of B', () => { - const a = {x: 10, y: 10, width: 100, height: 100}; + const a = {x: 30, y: 10, width: 100, height: 100}; const b = {x: 10, y: 120, width: 100, height: 100}; const actual = snap(a, b); const expected = {y: 20}; expect(actual).toEqual(expected); }); it('snaps the bottom of A to the top of B', () => { - const a = {x: 10, y: 120, width: 100, height: 100}; + const a = {x: 30, y: 120, width: 100, height: 100}; const b = {x: 10, y: 10, width: 100, height: 100}; const actual = snap(a, b); const expected = {y: 110}; @@ -143,4 +145,108 @@ describe('snap function', () => { const expected = {x: 20, y: 20}; expect(actual).toEqual(expected); }); + it('snaps the left of A to the left of B', () => { + const a = {x: 10, y: 10, width: 100, height: 100}; + const b = {x: 15, y: 110, width: 100, height: 100}; + const actual = snap(a, b); + const expected = {x: 15, y: 10}; + expect(actual).toEqual(expected); + }); + it('snaps the top of A to the bottom of B', () => { + const a = {x: 10, y: 10, width: 100, height: 100}; + const b = {x: 110, y: 15, width: 100, height: 100}; + const actual = snap(a, b); + const expected = {x: 10, y: 15}; + expect(actual).toEqual(expected); + }); + it('snaps the top left corner of A to the bottom right corner of B', () => { + const a = {x: 110, y: 110, width: 100, height: 100}; + const b = {x: 0, y: 0, width: 100, height: 100}; + const actual = snap(a, b); + const expected = {x: 100, y: 100}; + expect(actual).toEqual(expected); + }); +}); + +describe('snapWithin function', () => { + it('snaps the inner box to the top of the outer box', () => { + const inner = {x: 120, y: 10, width: 100, height: 100}; + const outer = {width: 1000, height: 1000}; + const actual = snapWithin(inner, outer); + const expected = {y: 0}; + expect(actual).toEqual(expected); + }); + it('snaps the inner box to the bottom of the outer box', () => { + const inner = {x: 120, y: 910, width: 100, height: 100}; + const outer = {width: 1000, height: 1000}; + const actual = snapWithin(inner, outer); + const expected = {y: 900}; + expect(actual).toEqual(expected); + }); + it('snaps the inner box to the right of the outer box', () => { + const inner = {x: 910, y: 120, width: 100, height: 100}; + const outer = {width: 1000, height: 1000}; + const actual = snapWithin(inner, outer); + const expected = {x: 900}; + expect(actual).toEqual(expected); + }); + it('snaps the inner box to the left of the outer box', () => { + const inner = {x: 10, y: 120, width: 100, height: 100}; + const outer = {width: 1000, height: 1000}; + const actual = snapWithin(inner, outer); + const expected = {x: 0}; + expect(actual).toEqual(expected); + }); + it('snaps the inner box to the top-left of the outer box', () => { + const inner = {x: 10, y: 10, width: 100, height: 100}; + const outer = {width: 1000, height: 1000}; + const actual = snapWithin(inner, outer); + const expected = {x: 0, y: 0}; + expect(actual).toEqual(expected); + }); +}); + +describe('applySnap function', () => { + it('does not apply undefined values', () => { + const original = {x: 10, y: 10}; + const snapped = {x: undefined, y: undefined}; + const actual = applySnap(original, snapped); + const expected = original; + expect(actual).toEqual(expected); + }); + it('preserves other values on original', () => { + const original = {x: 10, y: 10, foo: 'bar'}; + const snapped = {x: 50, y: 50}; + const actual = applySnap(original, snapped); + const expected = {x: 50, y: 50, foo: 'bar'}; + expect(actual).toEqual(expected); + }); + it('will clobber original with falsy "0" values', () => { + const original = {x: 10, y: 10}; + const snapped = {x: 0, y: 0}; + const actual = applySnap(original, snapped); + expect(actual).toEqual(snapped); + }); + it('will clobber original with falsy "0" values', () => { + const original = {x: 10, y: 10}; + const snapped = {x: 0, y: 0}; + const actual = applySnap(original, snapped); + expect(actual).toEqual(snapped); + }); + it('can apply multiple snaps', () => { + const original = {x: 10, y: 10}; + const snapped1 = {x: 0}; + const snapped2 = {y: 0}; + const actual = applySnap(original, snapped1, snapped2); + const expected = {x: 0, y: 0}; + expect(actual).toEqual(expected); + }); + it('the last snap wins', () => { + const original = {x: 10, y: 10}; + const snapped1 = {x: 50, y: 20}; + const snapped2 = {x: 60, y: 60}; + const actual = applySnap(original, snapped1, snapped2); + const expected = {x: 60, y: 60}; + expect(actual).toEqual(expected); + }); }); diff --git a/js/snapUtils.js b/js/snapUtils.js index 764d3697..f88986ca 100644 --- a/js/snapUtils.js +++ b/js/snapUtils.js @@ -22,23 +22,28 @@ export const overlapY = (a, b) => { export const snap = (boxA, boxB) => { let x, y; + // TODO: Refactor/simplify this code if (overlapY(boxA, boxB)) { if (near(left(boxA), right(boxB))) { x = right(boxB); - } - - if (near(right(boxA), left(boxB))) { + } else if (near(right(boxA), left(boxB))) { x = left(boxB) - boxA.width; + } else if (near(left(boxA), left(boxB))) { + x = left(boxB); + } else if (near(right(boxA), right(boxB))) { + x = right(boxB) - boxA.width; } } if (overlapX(boxA, boxB)) { if (near(top(boxA), bottom(boxB))) { y = bottom(boxB); - } - - if (near(bottom(boxA), top(boxB))) { + } else if (near(bottom(boxA), top(boxB))) { y = top(boxB) - boxA.height; + } else if (near(top(boxA), top(boxB))) { + y = top(boxB); + } else if (near(bottom(boxA), bottom(boxB))) { + y = bottom(boxB) - boxA.height; } } return {x, y}; @@ -55,3 +60,29 @@ export const snapToMany = (boxA, otherBoxes) => { return {x, y}; }; + +export const snapWithin = (boxA, boundingBox) => { + let x, y; + + if (boxA.x - SNAP_DISTANCE < 0) { + x = 0; + } else if ((boxA.x + boxA.width + SNAP_DISTANCE) > boundingBox.width) { + x = boundingBox.width - boxA.width; + } + + if (boxA.y - SNAP_DISTANCE < 0) { + y = 0; + } else if ((boxA.y + boxA.height + SNAP_DISTANCE) > boundingBox.height) { + y = boundingBox.height - boxA.height; + } + + return {x, y}; +}; + +export const applySnap = (original, ...snaps) => ( + snaps.reduce((previous, snapped) => ({ + ...previous, + x: typeof snapped.x !== 'undefined' ? snapped.x : previous.x, + y: typeof snapped.y !== 'undefined' ? snapped.y : previous.y + }), original) +);