diff --git a/src/lib/drag-utils.js b/src/lib/drag-utils.js index e3f5c1bd0e65bd6fbade42ad9dda30b260e650a9..096b42fe2924568e210386965fac153dcf6d0d58 100644 --- a/src/lib/drag-utils.js +++ b/src/lib/drag-utils.js @@ -12,9 +12,10 @@ * many rows, or a single row of items. * @param {{x: number, y: number}} position The xy coordinates to retreive the corresponding index of. * @param {Array.<DOMRect>} boxes The rects of the items, returned from `getBoundingClientRect` + * @param {bool} isRtl are the boxes in RTL order. * @return {?number} index of the corresponding box, or null if one could not be found. */ -const indexForPositionOnList = ({x, y}, boxes) => { +const indexForPositionOnList = ({x, y}, boxes, isRtl) => { if (boxes.length === 0) return null; let index = null; const leftEdge = Math.min.apply(null, boxes.map(b => b.left)); @@ -25,16 +26,23 @@ const indexForPositionOnList = ({x, y}, boxes) => { const box = boxes[n]; // Construct an "extended" box for each, extending out to infinity if // the box is along a boundary. - const minX = box.left === leftEdge ? -Infinity : box.left; + let minX = box.left === leftEdge ? -Infinity : box.left; + let maxX = box.right === rightEdge ? Infinity : box.right; const minY = box.top === topEdge ? -Infinity : box.top; const maxY = box.bottom === bottomEdge ? Infinity : box.bottom; // The last item in the wrapped list gets a right edge at infinity, even - // if it isn't the farthest right. Add this as an "or" condition for extension. - const maxX = (n === boxes.length - 1 || box.right === rightEdge) ? - Infinity : box.right; + // if it isn't the farthest right, in RTL mode. In LTR mode, it gets a + // left edge at infinity. + if (n === boxes.length - 1) { + if (isRtl) { + minX = -Infinity; + } else { + maxX = Infinity; + } + } // Check if the point is in the bounds. - if (x > minX && x <= maxX && y > minY && y <= maxY) { + if (x >= minX && x <= maxX && y >= minY && y <= maxY) { index = n; break; // No need to keep looking. } diff --git a/src/lib/sortable-hoc.jsx b/src/lib/sortable-hoc.jsx index eb971d0c090e6a2ef33ff9c9c337181fd8da8db2..652bcaec07a5354bf5b5dabca24c400b540e5a43 100644 --- a/src/lib/sortable-hoc.jsx +++ b/src/lib/sortable-hoc.jsx @@ -24,8 +24,8 @@ const SortableHOC = function (WrappedComponent) { if (newProps.dragInfo.dragging && !this.props.dragInfo.dragging) { // Drag just started, snapshot the sorted bounding boxes for sortables. this.boxes = this.sortableRefs.map(el => el && el.getBoundingClientRect()); - this.boxes.sort((a, b) => { // Sort top-to-bottom, left-to-right. - if (a.top === b.top) return a.left - b.left; + this.boxes.sort((a, b) => { // Sort top-to-bottom, left-to-right (in LTR) / right-to-left (in RTL). + if (a.top === b.top) return (a.left - b.left) * (this.props.isRtl ? -1 : 1); return a.top - b.top; }); if (!this.ref) { @@ -82,7 +82,7 @@ const SortableHOC = function (WrappedComponent) { mouseOverIndex = 0; } else { mouseOverIndex = indexForPositionOnList( - this.props.dragInfo.currentOffset, this.boxes); + this.props.dragInfo.currentOffset, this.boxes, this.props.isRtl); } } } @@ -125,11 +125,13 @@ const SortableHOC = function (WrappedComponent) { name: PropTypes.string.isRequired })), onClose: PropTypes.func, - onDrop: PropTypes.func + onDrop: PropTypes.func, + isRtl: PropTypes.bool }; const mapStateToProps = state => ({ - dragInfo: state.scratchGui.assetDrag + dragInfo: state.scratchGui.assetDrag, + isRtl: state.locales.isRtl }); const mapDispatchToProps = () => ({}); diff --git a/test/unit/util/drag-utils.test.js b/test/unit/util/drag-utils.test.js index 73029564eb584bf87d55745be9f9883fab472dbf..e7fe2797f9ab8a1bc3ab6401591dee56b18bd4aa 100644 --- a/test/unit/util/drag-utils.test.js +++ b/test/unit/util/drag-utils.test.js @@ -7,7 +7,7 @@ describe('indexForPositionOnList', () => { expect(indexForPositionOnList({x: 0, y: 0}, [])).toEqual(null); }); - test('wrapped list with incomplete last row', () => { + test('wrapped list with incomplete last row LTR', () => { const boxes = [ box(0, 100, 100, 0), // index: 0 box(0, 200, 100, 100), // index: 1 @@ -17,25 +17,58 @@ describe('indexForPositionOnList', () => { ]; // Inside the second box. - expect(indexForPositionOnList({x: 150, y: 50}, boxes)).toEqual(1); + expect(indexForPositionOnList({x: 150, y: 50}, boxes, false)).toEqual(1); // On the border edge of the first and second box. Given to the first box. - expect(indexForPositionOnList({x: 100, y: 50}, boxes)).toEqual(0); + expect(indexForPositionOnList({x: 100, y: 50}, boxes, false)).toEqual(0); // Off the top/left edge. - expect(indexForPositionOnList({x: -100, y: -100}, boxes)).toEqual(0); + expect(indexForPositionOnList({x: -100, y: -100}, boxes, false)).toEqual(0); // Off the left edge, in the second row. - expect(indexForPositionOnList({x: -100, y: 175}, boxes)).toEqual(3); + expect(indexForPositionOnList({x: -100, y: 175}, boxes, false)).toEqual(3); // Off the right edge, in the first row. - expect(indexForPositionOnList({x: 400, y: 75}, boxes)).toEqual(2); + expect(indexForPositionOnList({x: 400, y: 75}, boxes, false)).toEqual(2); // Off the top edge, middle of second item. - expect(indexForPositionOnList({x: 150, y: -75}, boxes)).toEqual(1); + expect(indexForPositionOnList({x: 150, y: -75}, boxes, false)).toEqual(1); // Within the right edge bounds, but on the second (incomplete) row. // This tests that wrapped lists with incomplete final rows work correctly. - expect(indexForPositionOnList({x: 375, y: 175}, boxes)).toEqual(4); + expect(indexForPositionOnList({x: 375, y: 175}, boxes, false)).toEqual(4); }); + + test('wrapped list with incomplete last row RTL', () => { + const boxes = [ + box(0, 0, 100, -100), // index: 0 + box(0, -100, 100, -200), // index: 1 + box(0, -200, 100, -300), // index: 2 + box(100, 0, 200, -100), // index: 3 (second row) + box(100, -100, 200, -200) // index: 4 (second row, left incomplete intentionally) + ]; + + // Inside the second box. + expect(indexForPositionOnList({x: -150, y: 50}, boxes, true)).toEqual(1); + + // On the border edge of the first and second box. Given to the first box. + expect(indexForPositionOnList({x: -100, y: 50}, boxes, true)).toEqual(0); + + // Off the top/right edge. + expect(indexForPositionOnList({x: 100, y: -100}, boxes, true)).toEqual(0); + + // Off the right edge, in the second row. + expect(indexForPositionOnList({x: 100, y: 175}, boxes, true)).toEqual(3); + + // Off the left edge, in the first row. + expect(indexForPositionOnList({x: -400, y: 75}, boxes, true)).toEqual(2); + + // Off the top edge, middle of second item. + expect(indexForPositionOnList({x: -150, y: -75}, boxes, true)).toEqual(1); + + // Within the left edge bounds, but on the second (incomplete) row. + // This tests that wrapped lists with incomplete final rows work correctly. + expect(indexForPositionOnList({x: -375, y: 175}, boxes, true)).toEqual(4); + }); + });