Skip to content

Commit

Permalink
feat(drag-n-drop): fixed drag between grids
Browse files Browse the repository at this point in the history
  • Loading branch information
skutam committed Mar 6, 2024
1 parent 505c326 commit 06d222a
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 103 deletions.
118 changes: 24 additions & 94 deletions projects/angular-grid-layout/src/lib/grid.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ export type KtdResizeEnd = KtdDragResizeEvent;
interface KtdDroppedEvent<T> {
event: PointingDeviceEvent;
currentLayout: KtdGridLayout;
previousLayoutItem: KtdGridLayoutItem<T> | null; // Previous layout is null only when dragging ktdDrag
currentLayoutItem: KtdGridLayoutItem<T>;
}

Expand Down Expand Up @@ -367,7 +366,10 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
private _gridItemsRenderData: KtdDictionary<KtdGridItemRenderData<number>>;
private subscriptions: Subscription[];

private drag: KtdGridDrag | null = null;
public get drag(): KtdGridDrag | null {
return this._drag;
}
private _drag: KtdGridDrag | null = null;

private readonly gridElement: HTMLElement;

Expand Down Expand Up @@ -551,14 +553,6 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
this.dragExited.subscribe(({dragInfo}) => {
this.pauseDragSequence(dragInfo);
}),
this.gridService.pointerBeforeEnd$.subscribe(({dragInfo}) => {
if (this.drag !== null && dragInfo !== null && dragInfo.currentGrid === this) {
console.log('Grid ', this.id);
this.updateLayout(dragInfo);
this.stopDragSequence(dragInfo);
}
this.drag = null;
}),
];
}

Expand All @@ -568,10 +562,16 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
* @param dragInfo The drag info.
*/
private startRestoreDragSequence(event: PointingDeviceEvent, dragInfo: PointerEventInfo): void {
// Drag sequence can be paused, but the resize sequence can't be paused.
if (this.drag !== null && dragInfo.type === 'resize') {
return;
}

// Prevents the resize from starting if the resize already started on another grid.
if (dragInfo.type === 'resize' && this.drag === null && dragInfo.fromGrid !== this) {
return;
}

const scrollableParent = typeof this.scrollableParent === 'string' ? document.getElementById(this.scrollableParent) : this.scrollableParent;

// TODO (enhancement): consider move this 'side effect' observable inside the main drag loop.
Expand All @@ -588,7 +588,7 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
takeUntil(this.gridService.pointerEnd$),
).subscribe());

this.drag = {
this._drag = {
dragSubscription: this.createDragResizeLoop(scrollableParent, dragInfo),
scrollSubscription,
startEvent: event,
Expand Down Expand Up @@ -740,7 +740,7 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
);
}

private stopDragSequence(dragInfo: PointerEventInfo): void {
public stopDragSequence(dragInfo: PointerEventInfo): void {
if (this.drag === null) {
return;
}
Expand All @@ -754,86 +754,18 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
});

this.addGridItemAnimatingClass(dragInfo.dragRef).subscribe();
// Consider destroying the placeholder after the animation has finished.
this.destroyPlaceholder();
this.drag.dragSubscription?.unsubscribe();
this.drag.scrollSubscription?.unsubscribe();
this.drag = null;
}

public updateLayout(dragInfo: PointerEventInfo): void {
if (this.drag != null && this.drag.newLayout) {
const currentLayoutItem = dragInfo.fromGrid === null ? {...dragInfo.newLayoutItem, id: this.getNextId()} : dragInfo.newLayoutItem;
const previousLayoutItem = this.layout.find(item => item.id === dragInfo.dragRef.id);

// Dragging from one grid to another
if (dragInfo.fromGrid !== dragInfo.currentGrid) {
// Add new item to the layout if it is being dragged from outside the grid.
this.ngZone.run(() => {
if (dragInfo.fromGrid !== null) {
dragInfo.fromGrid.layoutUpdated.emit(dragInfo.fromGrid.layout.filter(item => item.id !== dragInfo.dragRef.id));
}

// Do not emit when:
// - Drag is a resize and bounds have not changed.
// - Drag is a normal drag and the item is being dragged inside the same grid.
// - We are dragging from outside the grid and the item was dragged into the grid, but then pointer was released outside the grid.
if (dragInfo.type !== 'resize' && dragInfo.currentGrid === this) {
this.dropped.emit({
event: dragInfo.moveEvent,
currentLayout: this.drag!.newLayout!.map(item => ({
id: item.id,
x: item.x,
y: item.y,
w: item.w,
h: item.h,
minW: item.minW,
minH: item.minH,
maxW: item.maxW,
maxH: item.maxH,
data: item.data,
})) as KtdGridLayout,
previousLayoutItem: previousLayoutItem !== undefined ? previousLayoutItem : null,
currentLayoutItem: currentLayoutItem,
});
return;
}

// Emit when we are not dragging or resizing items already inside the grid.
// this.layoutUpdated.emit(this.drag!.newLayout!);
});
} else {
// Add new item to the layout if it is being dragged from outside the grid.
this.ngZone.run(() => {
// Do not emit when:
// - Drag is a resize and bounds have not changed.
// - Drag is a normal drag and the item is being dragged inside the same grid.
// - We are dragging from outside the grid and the item was dragged into the grid, but then pointer was released outside the grid.
if (dragInfo.type !== 'resize' && dragInfo.fromGrid === null && dragInfo.currentGrid === this) {
this.dropped.emit({
event: dragInfo.moveEvent,
currentLayout: this.drag!.newLayout!.map(item => ({
id: item.id,
x: item.x,
y: item.y,
w: item.w,
h: item.h,
minW: item.minW,
minH: item.minH,
maxW: item.maxW,
maxH: item.maxH,
data: item.data,
})) as KtdGridLayout,
previousLayoutItem: previousLayoutItem !== undefined ? previousLayoutItem : null,
currentLayoutItem: currentLayoutItem,
});
return;
}
}

// Emit when we are not dragging or resizing items already inside the grid.
this.layoutUpdated.emit(this.drag!.newLayout!);
});
}
/**
* Clears the drag sequence.
* This is called from grid-service when drag/resize finishes.
*/
public clearDragSequence(): void {
if (this.drag !== null) {
this.destroyPlaceholder();
this.drag?.dragSubscription?.unsubscribe();
this.drag?.scrollSubscription?.unsubscribe();
this._drag = null;
}
}

Expand All @@ -844,7 +776,7 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
return gridElemClientRect.left < pointerX && pointerX < gridElemClientRect.right && gridElemClientRect.top < pointerY && pointerY < gridElemClientRect.bottom;
}

private getNextId(): string {
public getNextId(): string {
return this._gridItems.toArray().reduce((acc, cur) => acc > parseInt(cur.id) ? acc : parseInt(cur.id), 0) + 1 + '';
}

Expand All @@ -854,9 +786,7 @@ export class KtdGridComponent implements OnChanges, AfterContentInit, AfterConte
* @param dragRef that has been dragged
*/
private addGridItemAnimatingClass(dragRef: DragRef): Observable<undefined> {

return new Observable(observer => {

const duration = getTransformTransitionDurationInMs(dragRef.elementRef.nativeElement);

if (duration === 0) {
Expand Down
68 changes: 59 additions & 9 deletions projects/angular-grid-layout/src/lib/grid.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@ export class KtdGridService {
private pointerEndSubject: Subject<MouseEvent | TouchEvent> = new Subject<MouseEvent | TouchEvent>();
private pointerEndSubscription: Subscription;

pointerBeforeEnd$: Observable<{event: MouseEvent | TouchEvent, dragInfo: PointerEventInfo | null}>;
private pointerBeforeEndSubject: Subject<{event: MouseEvent | TouchEvent, dragInfo: PointerEventInfo | null}> = new Subject<{event: MouseEvent | TouchEvent, dragInfo: PointerEventInfo | null}>();

private drag: PointerEventInfo | null = null;

constructor(
Expand All @@ -43,7 +40,6 @@ export class KtdGridService {
) {
this.pointerMove$ = this.pointerMoveSubject.asObservable();
this.pointerEnd$ = this.pointerEndSubject.asObservable();
this.pointerBeforeEnd$ = this.pointerBeforeEndSubject.asObservable();
this.initSubscriptions();
}

Expand All @@ -56,12 +52,11 @@ export class KtdGridService {
this.pointerEndSubscription = this.ngZone.runOutsideAngular(() =>
ktdPointerUp(document)
.subscribe((mouseEvent: MouseEvent | TouchEvent) => {
this.pointerBeforeEndSubject.next({
event: mouseEvent,
dragInfo: this.drag,
});
this.drag = null;
this.pointerEndSubject.next(mouseEvent);
if (this.drag !== null) {
this.updateGrids(this.drag);
}
this.drag = null;
})
);
}
Expand All @@ -72,6 +67,7 @@ export class KtdGridService {
* @param dragRef The dragRef that started the drag sequence.
* @param type The type of drag sequence.
* @param grid The grid where the drag sequence started. It can be null if the drag sequence started outside a grid.
* @param gridItem The grid item that is being dragged. It can be null if the drag sequence started from outside a grid.
*/
public startDrag(event: MouseEvent | TouchEvent | PointerEvent, dragRef: DragRef, type: DragActionType, grid: KtdGridComponent | null = null, gridItem: {layoutItem: LayoutItem, renderData: KtdGridItemRenderData<number>} | null = null): void {
// Make sure, this function is only being called once
Expand Down Expand Up @@ -163,6 +159,60 @@ export class KtdGridService {
this.drag!.currentGrid = grid;
}

private updateGrids(drag: PointerEventInfo): void {
// If the drag ended outside a grid, we don't need to do anything
if (drag.currentGrid === null) {
/*
* This emit is not required, but when it is not here, it cases a bug where the grid-element,
* does not return to its original position when the drag ends outside the grid.
* The same thing happens when the resize ends outside the grid.
* */
drag.fromGrid?.layoutUpdated.emit(drag.fromGrid!.layout);
drag.fromGrid?.stopDragSequence(drag);
return;
}

if (drag.type === 'resize') {
if (drag.fromGrid === drag.currentGrid) {
drag.currentGrid.layoutUpdated.emit(drag.currentGrid.drag!.newLayout!);
} else {
/*
* This emit is not required, but when it is not here, it cases a bug where the grid-element,
* does not return to its original position when the resize ends on another grid than the one it started.
*/
if (drag.fromGrid !== null && drag.fromGrid.drag !== null) {
drag.fromGrid.layoutUpdated.emit(drag.fromGrid.drag.newLayout!);
}
}
} else {
const currentLayoutItem = drag.fromGrid === null ? {
...drag.newLayoutItem,
id: drag.currentGrid.getNextId()
} : drag.newLayoutItem;

// Dragging between two distinct grids
if (drag.fromGrid !== drag.currentGrid) {
// Notify the previous grid that the item has left it
drag.fromGrid?.layoutUpdated.emit(drag.fromGrid!.layout.filter(item => item.id !== drag.dragRef.id));

// Notify the new grid that we dropped new item that was not in any grid
drag.currentGrid?.dropped.emit({
event: drag.moveEvent,
currentLayout: drag.currentGrid.drag!.newLayout!.map(item => ({...item})),
currentLayoutItem: currentLayoutItem,
});
} else {
// Update the new grid layout
drag.currentGrid.layoutUpdated.emit(drag.currentGrid.drag!.newLayout!);
}
}

// Clean up
drag.fromGrid?.stopDragSequence(drag);
drag.currentGrid?.stopDragSequence(drag);
this.registryService._ktgGrids.forEach(grid => grid.clearDragSequence());
}

dispose() {
this.pointerMoveSubscription.unsubscribe();
this.pointerEndSubscription.unsubscribe();
Expand Down

0 comments on commit 06d222a

Please sign in to comment.