Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rtl support #2368

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down
41 changes: 28 additions & 13 deletions src/Sortable.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -1004,6 +1007,7 @@ Sortable.prototype = /** @lends Sortable.prototype */ {
canSort = options.sort,
fromSortable = (putSortable || activeSortable),
vertical,
rtl,
_this = this,
completedFired = false;

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
36 changes: 36 additions & 0 deletions tests/Sortable.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
35 changes: 35 additions & 0 deletions tests/rtl-list.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html>

<head>
<title></title>
<link rel="stylesheet" type="text/css" href="./style.css">
<style>
.list {
display: flex;
direction: rtl;
gap: 10px;
}
</style>
</head>

<body>

<div class="list" id="list1">
<div>Item 1.1</div>
<div>Item 1.2</div>
<div>Item 1.3</div>
<div>Item 1.4</div>
<div>Item 1.5</div>
</div>


<script src="../Sortable.js"></script>

<script type="text/javascript">
new Sortable(document.getElementById('list1'));
</script>

</body>

</html>