Skip to content

Commit 83f1f85

Browse files
author
Clauderic Demers
committed
WIP: Initial implementation of collision detection
1 parent 45b48b4 commit 83f1f85

File tree

2 files changed

+112
-118
lines changed

2 files changed

+112
-118
lines changed

src/SortableContainer/index.js

Lines changed: 42 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import omit from 'lodash/omit'
44
import invariant from 'invariant';
55

66
import Manager from '../Manager';
7-
import {closest, events, vendorPrefix, limit, getElementMargin, provideDisplayName} from '../utils';
7+
import {closest, events, vendorPrefix, limit, getElementMargin, provideDisplayName, hitTest} from '../utils';
88

99
// Export Higher Order Sortable Container Component
1010
export default function sortableContainer(WrappedComponent, config = {withRef: false}) {
@@ -436,132 +436,56 @@ export default function sortableContainer(WrappedComponent, config = {withRef: f
436436

437437
animateNodes() {
438438
const {transitionDuration, hideSortableGhost} = this.props;
439-
let nodes = this.manager.getOrderedRefs();
440-
const deltaScroll = {
441-
left: this.scrollContainer.scrollLeft - this.initialScroll.left,
442-
top: this.scrollContainer.scrollTop - this.initialScroll.top
439+
const axis = {
440+
x: this.props.axis.indexOf('x') !== -1,
441+
y: this.props.axis.indexOf('y') !== -1
443442
};
444-
const sortingOffset = {
445-
left: this.offsetEdge.left + this.translate.x + deltaScroll.left,
446-
top: this.offsetEdge.top + this.translate.y + deltaScroll.top
447-
};
448-
this.newIndex = null;
449-
450-
for (let i = 0, len = nodes.length; i < len; i++) {
451-
let {node, edgeOffset} = nodes[i];
452-
const index = node.sortableInfo.index;
453-
const width = node.offsetWidth;
454-
const height = node.offsetHeight;
455-
const offset = {
456-
width: (this.width > width) ? (width / 2) : (this.width / 2),
457-
height: (this.height > height) ? (height / 2) : (this.height / 2)
458-
};
459-
let translate = {
460-
x: 0,
461-
y: 0
462-
};
463-
464-
// If we haven't cached the node's offsetTop / offsetLeft value
465-
if (!edgeOffset) {
466-
nodes[i].edgeOffset = edgeOffset = this.getEdgeOffset(node);
467-
}
468-
469-
// Get a reference to the next and previous node
470-
const nextNode = i < nodes.length - 1 && nodes[i + 1];
471-
const prevNode = i > 0 && nodes[i - 1];
472-
473-
// Also cache the next node's edge offset if needed.
474-
// We need this for calculating the animation in a grid setup
475-
if (nextNode && !nextNode.edgeOffset) {
476-
nextNode.edgeOffset = this.getEdgeOffset(nextNode.node)
477-
}
478-
479-
// If the node is the one we're currently animating, skip it
480-
if (index === this.index) {
481-
if (hideSortableGhost) {
482-
/*
483-
* With windowing libraries such as `react-virtualized`, the sortableGhost
484-
* node may change while scrolling down and then back up (or vice-versa),
485-
* so we need to update the reference to the new node just to be safe.
486-
*/
487-
this.sortableGhost = node;
488-
node.style.visibility = 'hidden';
443+
const nodes = this.manager.getOrderedRefs();
444+
const hovering = nodes.find(({node}) => hitTest(node, this.helper, {
445+
threshold: '50%',
446+
axis
447+
}));
448+
449+
if (hovering) {
450+
const hoveringIndex = this.newIndex = hovering.node.sortableInfo.index;
451+
452+
const offset = axis.x ? this.width : this.height;
453+
454+
for (let i = 0, len = nodes.length; i < len; i++) {
455+
const {node} = nodes[i];
456+
const translate = {x: 0, y: 0};
457+
458+
if (i === this.index) {
459+
if (hideSortableGhost) {
460+
/*
461+
* With windowing libraries such as `react-virtualized`, the sortableGhost
462+
* node may change while scrolling down and then back up (or vice-versa),
463+
* so we need to update the reference to the new node just to be safe.
464+
*/
465+
this.sortableGhost = node;
466+
node.style.visibility = 'hidden';
467+
}
468+
continue;
489469
}
490-
continue;
491-
}
492-
493-
if (transitionDuration) {
494-
node.style[`${vendorPrefix}TransitionDuration`] = `${transitionDuration}ms`;
495-
}
496470

497-
if (this.axis.x) {
498-
if (this.axis.y) {
499-
// Calculations for a grid setup
500-
if (
501-
(index < this.index)
502-
&& (
503-
((sortingOffset.left - offset.width <= edgeOffset.left) && (sortingOffset.top <= edgeOffset.top + offset.height))
504-
|| (sortingOffset.top + offset.height <= edgeOffset.top)
505-
)
506-
) {
507-
// If the current node is to the left on the same row, or above the node that's being dragged
508-
// then move it to the right
509-
translate.x = this.width + this.marginOffset.x;
510-
if (edgeOffset.left + translate.x > this.containerBoundingRect.width - offset.width) {
511-
// If it moves passed the right bounds, then animate it to the first position of the next row.
512-
// We just use the offset of the next node to calculate where to move, because that node's original position
513-
// is exactly where we want to go
514-
translate.x = nextNode.edgeOffset.left - edgeOffset.left;
515-
translate.y = nextNode.edgeOffset.top - edgeOffset.top;
516-
}
517-
if (this.newIndex === null) {
518-
this.newIndex = index;
519-
}
520-
} else if (index > this.index
521-
&& (((sortingOffset.left + offset.width >= edgeOffset.left) && (sortingOffset.top + offset.height >= edgeOffset.top))
522-
|| (sortingOffset.top + offset.height >= edgeOffset.top + height))
523-
) {
524-
// If the current node is to the right on the same row, or below the node that's being dragged
525-
// then move it to the left
526-
translate.x = -(this.width + this.marginOffset.x);
527-
if (edgeOffset.left + translate.x < this.containerBoundingRect.left + offset.width) {
528-
// If it moves passed the left bounds, then animate it to the last position of the previous row.
529-
// We just use the offset of the previous node to calculate where to move, because that node's original position
530-
// is exactly where we want to go
531-
translate.x = prevNode.edgeOffset.left - edgeOffset.left;
532-
translate.y = prevNode.edgeOffset.top - edgeOffset.top;
533-
}
534-
this.newIndex = index;
535-
}
471+
if (axis.x && axis.y) {
472+
// TODO: Grid sorting to be implemented using hit detection
536473
} else {
537-
if (index > this.index && (sortingOffset.left + offset.width >= edgeOffset.left)) {
538-
translate.x = -(this.width + this.marginOffset.x);
539-
this.newIndex = index;
474+
const translateAxis = (axis.x) ? 'x' : 'y';
475+
476+
if (i > this.index && i <= hoveringIndex) {
477+
translate[translateAxis] = -(offset + this.marginOffset[translateAxis]);
540478
}
541-
else if (index < this.index && (sortingOffset.left <= edgeOffset.left + offset.width)) {
542-
translate.x = this.width + this.marginOffset.x;
543-
if (this.newIndex == null) {
544-
this.newIndex = index;
545-
}
479+
else if (i < this.index && i >= hoveringIndex) {
480+
translate[translateAxis] = offset + this.marginOffset[translateAxis];
546481
}
547482
}
548-
} else if (this.axis.y) {
549-
if (index > this.index && (sortingOffset.top + offset.height >= edgeOffset.top)) {
550-
translate.y = -(this.height + this.marginOffset.y);
551-
this.newIndex = index;
552-
}
553-
else if (index < this.index && (sortingOffset.top <= edgeOffset.top + offset.height)) {
554-
translate.y = this.height + this.marginOffset.y;
555-
if (this.newIndex == null) {
556-
this.newIndex = index;
557-
}
483+
484+
if (transitionDuration) {
485+
node.style[`${vendorPrefix}TransitionDuration`] = `${transitionDuration}ms`;
558486
}
487+
node.style[`${vendorPrefix}Transform`] = `translate3d(${translate.x}px,${translate.y}px,0)`;
559488
}
560-
node.style[`${vendorPrefix}Transform`] = `translate3d(${translate.x}px,${translate.y}px,0)`;
561-
}
562-
563-
if (this.newIndex == null) {
564-
this.newIndex = this.index;
565489
}
566490
}
567491

src/utils.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,73 @@ export function provideDisplayName(prefix, Component) {
7777

7878
return componentName ? `${prefix}(${componentName})` : prefix;
7979
}
80+
81+
82+
const _doc = document;
83+
const _docElement = _doc.documentElement || {};
84+
const unwrapElement = function(value) {
85+
if (!value) {
86+
return value;
87+
}
88+
if (value.length && value !== window && value[0] && value[0].style && !value.nodeType) {
89+
value = value[0];
90+
}
91+
return (value === window || (value.nodeType && value.style)) ? value : null;
92+
};
93+
const getDocScrollTop = function() {
94+
return (window.pageYOffset != null) ? window.pageYOffset : (_doc.scrollTop != null) ? _doc.scrollTop : _docElement.scrollTop || _doc.body.scrollTop || 0;
95+
};
96+
97+
const getDocScrollLeft = function() {
98+
return (window.pageXOffset != null) ? window.pageXOffset : (_doc.scrollLeft != null) ? _doc.scrollLeft : _docElement.scrollLeft || _doc.body.scrollLeft || 0;
99+
};
100+
const _tempRect = {}; // reuse to reduce garbage collection tasks
101+
102+
function parseRect(e, undefined) {
103+
//accepts a DOM element, a mouse event, or a rectangle object and returns the corresponding rectangle with left, right, width, height, top, and bottom properties
104+
if (e === window) {
105+
_tempRect.left = _tempRect.top = 0;
106+
_tempRect.width = _tempRect.right = _docElement.clientWidth || e.innerWidth || _doc.body.clientWidth || 0;
107+
_tempRect.height = _tempRect.bottom = ((e.innerHeight || 0) - 20 < _docElement.clientHeight) ? _docElement.clientHeight : e.innerHeight || _doc.body.clientHeight || 0;
108+
return _tempRect;
109+
}
110+
var r = (e.pageX !== undefined) ? {left:e.pageX - getDocScrollLeft(), top:e.pageY - getDocScrollTop(), right:e.pageX - getDocScrollLeft() + 1, bottom:e.pageY - getDocScrollTop() + 1} : (!e.nodeType && e.left !== undefined && e.top !== undefined) ? e : unwrapElement(e).getBoundingClientRect();
111+
if (r.right === undefined && r.width !== undefined) {
112+
r.right = r.left + r.width;
113+
r.bottom = r.top + r.height;
114+
} else if (r.width === undefined) { //some browsers don't include width and height properties. We can't just set them directly on r because some browsers throw errors, so create a new generic object.
115+
r = {width: r.right - r.left, height: r.bottom - r.top, right: r.right, left: r.left, bottom: r.bottom, top: r.top};
116+
}
117+
return r;
118+
}
119+
120+
export function hitTest(obj1, obj2, {threshold, axis}) {
121+
if (obj1 === obj2) {
122+
return false;
123+
}
124+
const r1 = parseRect(obj1);
125+
const r2 = parseRect(obj2);
126+
const isOutside = (axis.x && (r2.left > r1.right || r2.right < r1.left) || axis.y && (r2.top > r1.bottom || r2.bottom < r1.top));
127+
let overlap, area, isRatio;
128+
129+
if (isOutside || !threshold) {
130+
return !isOutside;
131+
}
132+
isRatio = ((threshold + "").indexOf("%") !== -1);
133+
threshold = parseFloat(threshold) || 0;
134+
overlap = {left:Math.max(r1.left, r2.left), top:Math.max(r1.top, r2.top)};
135+
overlap.width = Math.min(r1.right, r2.right) - overlap.left;
136+
overlap.height = Math.min(r1.bottom, r2.bottom) - overlap.top;
137+
138+
if (axis.x && overlap.width < 0 || axis.y && overlap.height < 0) {
139+
return false;
140+
}
141+
142+
if (isRatio) {
143+
threshold *= 0.01;
144+
145+
return (axis.x && overlap.width >= r2.width * threshold || axis.y && overlap.height >= r2.height * threshold);
146+
}
147+
148+
return (overlap.width > threshold && overlap.height > threshold);
149+
}

0 commit comments

Comments
 (0)