Skip to content

Commit 4eaf5ff

Browse files
Merge pull request #560 from mkszepp/add-grid-drag-drop
Add direction grid
2 parents a606bc1 + c19c34a commit 4eaf5ff

File tree

13 files changed

+552
-37
lines changed

13 files changed

+552
-37
lines changed

addon/src/modifiers/sortable-group.js

Lines changed: 194 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -184,14 +184,32 @@ export default class SortableGroupModifier extends Modifier {
184184
const dimension = direction === 'y' ? 'height' : 'width';
185185
// DOWN or RIGHT
186186
if (toIndex > fromIndex) {
187-
value = item[direction];
188-
set(item, direction, nextItem[direction] + (nextItem[dimension] - item[dimension]));
189-
set(nextItem, direction, value);
187+
if (direction === 'grid') {
188+
const valueX = item.x;
189+
const valueY = item.y;
190+
item.x = nextItem.x + (nextItem.width - item.width);
191+
item.y = nextItem.y + (nextItem.height - item.height);
192+
nextItem.x = valueX;
193+
nextItem.y = valueY;
194+
} else {
195+
value = item[direction];
196+
set(item, direction, nextItem[direction] + (nextItem[dimension] - item[dimension]));
197+
set(nextItem, direction, value);
198+
}
190199
// UP or LEFT
191200
} else {
192-
value = nextItem[direction];
193-
set(nextItem, direction, item[direction] + (item[dimension] - nextItem[dimension]));
194-
set(item, direction, value);
201+
if (direction === 'grid') {
202+
const valueX = nextItem.x;
203+
const valueY = nextItem.y;
204+
nextItem.x = item.x + (item.width - nextItem.width);
205+
nextItem.y = item.y + (item.height - nextItem.height);
206+
item.x = valueX;
207+
item.y = valueY;
208+
} else {
209+
value = nextItem[direction];
210+
set(nextItem, direction, item[direction] + (item[dimension] - nextItem[dimension]));
211+
set(item, direction, value);
212+
}
195213
}
196214
}
197215

@@ -212,9 +230,9 @@ export default class SortableGroupModifier extends Modifier {
212230
this.moveItem(selectedItem, 1);
213231
} else if (direction === 'y' && isUpArrowKey(event)) {
214232
this.moveItem(selectedItem, -1);
215-
} else if (direction === 'x' && isLeftArrowKey(event)) {
233+
} else if ((direction === 'x' || direction === 'grid') && isLeftArrowKey(event)) {
216234
this.moveItem(selectedItem, -1);
217-
} else if (direction === 'x' && isRightArrowKey(event)) {
235+
} else if ((direction === 'x' || direction === 'grid') && isRightArrowKey(event)) {
218236
this.moveItem(selectedItem, 1);
219237
} else if (isEnterKey(event) || isSpaceKey(event)) {
220238
// confirm will reset the selectedItem, so caching it here before we remove it.
@@ -531,10 +549,14 @@ export default class SortableGroupModifier extends Modifier {
531549
*/
532550
@computed('direction', 'sortedItems')
533551
get firstItemPosition() {
534-
const direction = this.direction;
535552
const sortedItems = this.sortedItems;
536553

537-
return sortedItems[0][`${direction}`] - sortedItems[0].spacing;
554+
const item = sortedItems[0];
555+
556+
return {
557+
x: item.x - item.spacing,
558+
y: item.y - item.spacing,
559+
};
538560
}
539561

540562
/**
@@ -544,7 +566,18 @@ export default class SortableGroupModifier extends Modifier {
544566
*/
545567
get sortedItems() {
546568
const direction = this.direction;
547-
return this.items.sort((a, b) => a[direction] - b[direction]);
569+
570+
const groupStyles = getComputedStyle(this.element);
571+
const groupWidth = parseFloat(groupStyles.width);
572+
573+
return this.items.sort((a, b) => {
574+
if (direction === 'grid') {
575+
let { ax, ay, bx, by } = this._calculateGridPosition(a, b, groupWidth);
576+
if (ay == by) return ax - bx;
577+
return ay - by;
578+
}
579+
return a[direction] - b[direction];
580+
});
548581
}
549582

550583
/**
@@ -603,31 +636,68 @@ export default class SortableGroupModifier extends Modifier {
603636
/**
604637
Update item positions (relatively to the first element position).
605638
@method update
639+
@param {SortableItemModifier[]} sortedItems
606640
*/
607641
@action
608-
update() {
609-
let sortedItems = this.sortedItems;
642+
update(sortedItems) {
643+
if (!sortedItems) {
644+
sortedItems = this.sortedItems;
645+
}
646+
610647
// Position of the first element
611-
let position = this._firstItemPosition;
648+
let axis = this._firstItemPosition;
612649

613650
// Just in case we haven’t called prepare first.
614-
if (position === undefined) {
615-
position = this.firstItemPosition;
651+
if (axis === undefined) {
652+
axis = this.firstItemPosition;
653+
}
654+
655+
let direction = this.direction;
656+
657+
let position = 0;
658+
let groupPositionRight = 0;
659+
let lastTopOffset = 0;
660+
let maxPrevHeight = 0;
661+
662+
if (direction === 'grid') {
663+
position = axis.x;
664+
lastTopOffset = axis.y;
665+
const groupStyles = getComputedStyle(this.element);
666+
groupPositionRight = position + parseFloat(groupStyles.width);
667+
} else {
668+
position = axis[direction];
616669
}
617670

618671
sortedItems.forEach((item) => {
619-
let dimension;
620-
let direction = this.direction;
672+
if (direction === 'grid' && position + item.width > groupPositionRight) {
673+
lastTopOffset = lastTopOffset + maxPrevHeight;
674+
position = axis.x;
675+
maxPrevHeight = 0;
676+
}
621677

622678
if (!isDestroyed(item) && !item.isDragging) {
623-
set(item, direction, position);
679+
if (direction === 'grid') {
680+
item.x = position;
681+
item.y = lastTopOffset;
682+
} else {
683+
set(item, direction, position);
684+
}
624685
}
625686

626687
// add additional spacing around active element
627688
if (item.isBusy) {
628689
position += item.spacing * 2;
629690
}
630691

692+
let dimension;
693+
694+
if (direction === 'grid') {
695+
dimension = 'width';
696+
697+
if (item.height > maxPrevHeight) {
698+
maxPrevHeight = item.height;
699+
}
700+
}
631701
if (direction === 'x') {
632702
dimension = 'width';
633703
}
@@ -697,6 +767,111 @@ export default class SortableGroupModifier extends Modifier {
697767
return announcer;
698768
}
699769

770+
_calculateGridPosition(a, b, groupWidth) {
771+
const groupTopPos = a.element.parentNode?.offsetTop ?? 0;
772+
const groupLeftPos = a.element.parentNode?.offsetLeft ?? 0;
773+
774+
const position = {
775+
ax: a.x,
776+
ay: a.y,
777+
bx: b.x,
778+
by: b.y,
779+
};
780+
781+
if (a.isDragging) {
782+
const dragItemPos = this._calculateGridDragItemPos(
783+
position.ax,
784+
position.ay,
785+
position.bx,
786+
position.by,
787+
b.width,
788+
b.height,
789+
a.moveDirection,
790+
groupTopPos,
791+
groupLeftPos,
792+
groupWidth
793+
);
794+
position.ax = dragItemPos.x;
795+
position.ay = dragItemPos.y;
796+
} else if (b.isDragging) {
797+
const dragItemPos = this._calculateGridDragItemPos(
798+
position.bx,
799+
position.by,
800+
position.ax,
801+
position.ay,
802+
a.width,
803+
a.height,
804+
b.moveDirection,
805+
groupTopPos,
806+
groupLeftPos,
807+
groupWidth
808+
);
809+
position.bx = dragItemPos.x;
810+
position.by = dragItemPos.y;
811+
}
812+
813+
// Math.hypot needs always a positive number (-5 = 5 in hypot).
814+
// As a negative number will be positive, we need to fake position from non dragged element
815+
if (a.isDragging && position.ax <= 0) {
816+
position.ax = 0;
817+
position.bx += 1;
818+
}
819+
820+
if (b.isDragging && position.bx <= 0) {
821+
position.bx = 0;
822+
position.ax += 1;
823+
}
824+
825+
return position;
826+
}
827+
828+
_calculateGridDragItemPos(x, y, otherX, otherY, width, height, moveDirection, groupTopPos, groupLeftPos, groupWidth) {
829+
const toleranceWidth = width / 4;
830+
const initialX = x;
831+
832+
if (moveDirection.left) {
833+
x = x - toleranceWidth;
834+
}
835+
836+
if (moveDirection.right) {
837+
x = x + toleranceWidth;
838+
// Calculate the maximum of items in row & the maximal x-position of last item
839+
const itemsPerRow = Math.floor(groupWidth / width);
840+
const possibleLastItemPos = (itemsPerRow - 1) * width + groupLeftPos;
841+
if (otherX > initialX && x + width > possibleLastItemPos - 1) {
842+
// Removing one pixel is necessary to move drag item before other element
843+
x = possibleLastItemPos - 1;
844+
}
845+
}
846+
847+
if (y < groupTopPos) {
848+
y = groupTopPos;
849+
}
850+
851+
const toleranceHeight = height / 4;
852+
853+
// When item is moved a quarter of height to top, user wants to move up
854+
if (moveDirection.top && y - height + toleranceHeight <= otherY && y >= otherY) {
855+
y = otherY;
856+
// tolerance that it doesn't jump directly in previews line
857+
} else if (moveDirection.top && y >= otherY - toleranceHeight && y <= otherY) {
858+
y = otherY;
859+
}
860+
861+
// When item is moved a quarter of height to bottom, user wants to move down
862+
if (moveDirection.bottom && y <= otherY + height - toleranceHeight && y >= otherY) {
863+
y = otherY;
864+
// tolerance that it doesn't jump directly in next line
865+
} else if (moveDirection.bottom && y > otherY - toleranceHeight && y <= otherY) {
866+
y = otherY;
867+
}
868+
869+
return {
870+
x: x,
871+
y: y,
872+
};
873+
}
874+
700875
// end of API
701876

702877
addEventListener() {

0 commit comments

Comments
 (0)