diff --git a/README.md b/README.md index c95740711..e87fff86d 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,7 @@ var sortable = new Sortable(el, { invertSwap: false, // Will always use inverted swap zone if set to true invertedSwapThreshold: 1, // Threshold of the inverted swap zone (will be set to swapThreshold value by default) direction: 'horizontal', // Direction of Sortable (will be detected automatically if not given) + rtl: false, // If Sortable is written right to left (will be detected automatically if not given) forceFallback: false, // ignore the HTML5 DnD behaviour and force the fallback to kick in @@ -339,7 +340,10 @@ Sortable.create(el, { --- +#### `rtl` option +Set to `true` if sorting direction is right to left (including `flex-direction: row-reverse`). Can be set to `true`, `false`, or a function, which will be called whenever a target is dragged over. Must return `true` or `false`. +--- #### `touchStartThreshold` option This option is similar to `fallbackTolerance` option. diff --git a/src/Sortable.js b/src/Sortable.js index 81ff84494..345358e15 100644 --- a/src/Sortable.js +++ b/src/Sortable.js @@ -372,6 +372,9 @@ function Sortable(el, options) { direction: function() { return _detectDirection(el, this.options); }, + rtl: function () { + return (css(el, 'direction') === 'rtl') !== (css(el, 'flex-direction') === 'row-reverse') + }, ghostClass: 'sortable-ghost', chosenClass: 'sortable-chosen', dragClass: 'sortable-drag', @@ -453,8 +456,8 @@ Sortable.prototype = /** @lends Sortable.prototype */ { } }, - _getDirection: function(evt, target) { - return (typeof this.options.direction === 'function') ? this.options.direction.call(this, evt, target, dragEl) : this.options.direction; + _getOptionValue: function(evt, target, optionName) { + return (typeof this.options[optionName] === 'function') ? this.options[optionName].call(this, evt, target, dragEl) : this.options[optionName]; }, _onTapStart: function (/** Event|TouchEvent */evt) { @@ -1004,6 +1007,7 @@ Sortable.prototype = /** @lends Sortable.prototype */ { canSort = options.sort, fromSortable = (putSortable || activeSortable), vertical, + rtl, _this = this, completedFired = false; @@ -1143,7 +1147,8 @@ Sortable.prototype = /** @lends Sortable.prototype */ { ) ) ) { - vertical = this._getDirection(evt, target) === 'vertical'; + vertical = this._getOptionValue(evt, target, 'direction') === 'vertical'; + rtl = this._getOptionValue(evt, target, 'rtl'); dragRect = getRect(dragEl); @@ -1171,7 +1176,7 @@ Sortable.prototype = /** @lends Sortable.prototype */ { let elLastChild = lastChild(el, options.draggable); - if (!elLastChild || _ghostIsLast(evt, vertical, this) && !elLastChild.animated) { + if (!elLastChild || _ghostIsLast(evt, vertical, rtl, this) && !elLastChild.animated) { // Insert to end of list // If already at end of list: Do not insert @@ -1202,7 +1207,7 @@ Sortable.prototype = /** @lends Sortable.prototype */ { return completed(true); } } - else if (elLastChild && _ghostIsFirst(evt, vertical, this)) { + else if (elLastChild && _ghostIsFirst(evt, vertical, rtl, this)) { // Insert to start of list let firstChild = getChild(el, 0, options, true); if (firstChild === dragEl) { @@ -1789,24 +1794,34 @@ function _unsilent() { _silent = false; } -function _ghostIsFirst(evt, vertical, sortable) { +function _ghostIsFirst(evt, vertical, rtl, sortable) { let firstElRect = getRect(getChild(sortable.el, 0, sortable.options, true)); const childContainingRect = getChildContainingRectFromElement(sortable.el, sortable.options, ghostEl); const spacer = 10; + - return vertical ? - (evt.clientX < childContainingRect.left - spacer || evt.clientY < firstElRect.top && evt.clientX < firstElRect.right) : - (evt.clientY < childContainingRect.top - spacer || evt.clientY < firstElRect.bottom && evt.clientX < firstElRect.left) + if (vertical) { + return (evt.clientX < childContainingRect.left - spacer || evt.clientY < firstElRect.top && evt.clientX < firstElRect.right) + } else if (!rtl) { + return (evt.clientY < childContainingRect.top - spacer || evt.clientY < firstElRect.bottom && evt.clientX < firstElRect.left) + } else { + return (evt.clientY < childContainingRect.top - spacer || evt.clientY < firstElRect.bottom && evt.clientX > firstElRect.right) + } } -function _ghostIsLast(evt, vertical, sortable) { +function _ghostIsLast(evt, vertical, rtl, sortable) { const lastElRect = getRect(lastChild(sortable.el, sortable.options.draggable)); const childContainingRect = getChildContainingRectFromElement(sortable.el, sortable.options, ghostEl); const spacer = 10; - return vertical ? - (evt.clientX > childContainingRect.right + spacer || evt.clientY > lastElRect.bottom && evt.clientX > lastElRect.left) : - (evt.clientY > childContainingRect.bottom + spacer || evt.clientX > lastElRect.right && evt.clientY > lastElRect.top); + + if (vertical) { + return (evt.clientX > childContainingRect.right + spacer || evt.clientY > lastElRect.bottom && evt.clientX > lastElRect.left) + } else if (!rtl) { + return (evt.clientY > childContainingRect.bottom + spacer || evt.clientX > lastElRect.right && evt.clientY > lastElRect.top) + } else { + return (evt.clientY > childContainingRect.bottom + spacer || evt.clientX < lastElRect.left && evt.clientY > lastElRect.top) + } } function _getSwapDirection(evt, target, targetRect, vertical, swapThreshold, invertedSwapThreshold, invertSwap, isLastTarget) { diff --git a/tests/Sortable.test.js b/tests/Sortable.test.js index 59ce11f46..47de27a84 100644 --- a/tests/Sortable.test.js +++ b/tests/Sortable.test.js @@ -384,3 +384,39 @@ test('Do not insert into empty list if outside emptyInsertThreshold', async brow }) .expect(dragStartPosition.innerText).eql(dragEl.innerText); }); + + +fixture `Right to left` + .page `./rtl-list.html`; + +test('Sort left list', async browser => { + const dragStartPosition = list1.child(0); + const dragEl = await dragStartPosition(); + const dragEndPosition = list1.child(2); + const targetStartPosition = list1.child(2); + const target = await targetStartPosition(); + const targetEndPosition = list1.child(1); + + await browser + .expect(dragStartPosition.innerText).eql(dragEl.innerText) + .expect(targetStartPosition.innerText).eql(target.innerText) + .dragToElement(dragEl, target) + .expect(dragEndPosition.innerText).eql(dragEl.innerText) + .expect(targetEndPosition.innerText).eql(target.innerText); +}); + +test('Sort right list', async browser => { + const dragStartPosition = list1.child(2); + const dragEl = await dragStartPosition(); + const dragEndPosition = list1.child(0); + const targetStartPosition = list1.child(0); + const target = await targetStartPosition(); + const targetEndPosition = list1.child(1); + + await browser + .expect(dragStartPosition.innerText).eql(dragEl.innerText) + .expect(targetStartPosition.innerText).eql(target.innerText) + .dragToElement(dragEl, target) + .expect(dragEndPosition.innerText).eql(dragEl.innerText) + .expect(targetEndPosition.innerText).eql(target.innerText); +}); \ No newline at end of file diff --git a/tests/rtl-list.html b/tests/rtl-list.html new file mode 100644 index 000000000..4d7bd992a --- /dev/null +++ b/tests/rtl-list.html @@ -0,0 +1,35 @@ + + + + + + + + + + + +
+
Item 1.1
+
Item 1.2
+
Item 1.3
+
Item 1.4
+
Item 1.5
+
+ + + + + + + + + \ No newline at end of file