Skip to content

Commit c92c282

Browse files
author
Matúš Škuta
committed
feat(drag-n-drop): add item dragging into grid
1 parent 9f527e4 commit c92c282

File tree

12 files changed

+795
-521
lines changed

12 files changed

+795
-521
lines changed

projects/angular-grid-layout/src/lib/directives/ktd-drag.ts

Lines changed: 67 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import {
44
InjectionToken, Input, OnDestroy, Output, QueryList
55
} from '@angular/core';
66
import {coerceBooleanProperty} from "../coercion/boolean-property";
7-
import {BehaviorSubject, Observable, Observer, Subscription} from "rxjs";
7+
import {Observable, Observer, Subscription} from "rxjs";
88
import {coerceNumberProperty} from "../coercion/number-property";
99
import {KtdRegistryService} from "../ktd-registry.service";
1010
import {KTD_GRID_DRAG_HANDLE, KtdGridDragHandle} from "./drag-handle";
1111
import {DragRef} from "../utils/drag-ref";
1212
import {KTD_GRID_ITEM_PLACEHOLDER, KtdGridItemPlaceholder} from "./placeholder";
13-
import {ktdPointerClientX, ktdPointerClientY} from "../utils/pointer.utils";
14-
import {takeUntil} from "rxjs/operators";
13+
import {KtdGridComponent, PointingDeviceEvent} from "../grid.component";
14+
import {KtdGridService} from "../grid.service";
1515

1616

1717
export const KTD_DRAG = new InjectionToken<KtdDrag<any>>('KtdDrag');
@@ -45,25 +45,62 @@ export class KtdDrag<T> implements AfterContentInit, OnDestroy {
4545
/** Minimum amount of pixels that the user should move before it starts the drag sequence. */
4646
@Input()
4747
get dragStartThreshold(): number {
48-
return this._dragStartThreshold;
48+
return this._dragRef.dragStartThreshold;
4949
}
5050
set dragStartThreshold(val: number) {
51-
this._dragStartThreshold = coerceNumberProperty(val);
51+
this._dragRef.dragStartThreshold = coerceNumberProperty(val);
52+
}
53+
54+
/** Number of CSS pixels that would be scrolled on each 'tick' when auto scroll is performed. */
55+
@Input()
56+
get scrollSpeed(): number { return this._dragRef.scrollSpeed; }
57+
set scrollSpeed(value: number) {
58+
this._dragRef.scrollSpeed = coerceNumberProperty(value, 2);
59+
}
60+
61+
/**
62+
* Parent element that contains the scroll. If an string is provided it would search that element by id on the dom.
63+
* If no data provided or null autoscroll is not performed.
64+
*/
65+
@Input()
66+
get scrollableParent(): HTMLElement | Document | string | null { return this._dragRef.scrollableParent; }
67+
set scrollableParent(value: HTMLElement | Document | string | null) {
68+
this._dragRef.scrollableParent = value;
5269
}
53-
private _dragStartThreshold: number = 0;
5470

5571
/** Whether the item is draggable or not. Defaults to true. Does not affect manual dragging using the startDragManually method. */
5672
@Input()
5773
get draggable(): boolean {
58-
return this._draggable;
74+
return this._dragRef.draggable;
5975
}
6076
set draggable(val: boolean) {
61-
this._draggable = coerceBooleanProperty(val);
62-
this._dragRef.draggable = this._draggable;
63-
this._draggable$.next(this._draggable);
77+
this._dragRef.draggable = coerceBooleanProperty(val);
6478
}
65-
private _draggable: boolean = true;
66-
private _draggable$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(this._draggable);
79+
80+
/**
81+
* List of ids of grids or grid components that the item is connected to.
82+
*/
83+
@Input()
84+
get connectedTo(): KtdGridComponent[] {
85+
return this._connectedTo;
86+
}
87+
set connectedTo(val: (string|KtdGridComponent|any)[]) {
88+
this._connectedTo = val.map((item: string|KtdGridComponent|any) => {
89+
if (typeof item === 'string') {
90+
const grid = this.registryService._ktgGrids.find(grid => grid.id === item);
91+
if (grid === undefined) {
92+
throw new Error(`KtdDrag connectedTo: could not find grid with id ${item}`);
93+
}
94+
return grid;
95+
}
96+
if (item instanceof KtdGridComponent) {
97+
return item;
98+
}
99+
throw new Error(`KtdDrag connectedTo: connectedTo must be an array of KtdGridComponent or string`);
100+
});
101+
this.registryService.updateConnectedTo(this._dragRef, this._connectedTo);
102+
}
103+
private _connectedTo: KtdGridComponent[] = [];
67104

68105
@Input()
69106
get id(): string {
@@ -95,14 +132,14 @@ export class KtdDrag<T> implements AfterContentInit, OnDestroy {
95132
this._dragRef.height = coerceNumberProperty(val);
96133
}
97134

98-
/**
99-
* TODO: Add support for custom drag data.
100-
*/
101-
@Input('ktdDragData') data: T;
135+
@Input('ktdDragData')
136+
set data(val: T) {
137+
this._dragRef.data = val;
138+
}
102139

103140
@Output('dragStart')
104-
readonly dragStart: Observable<{source: DragRef<T>, event: MouseEvent | TouchEvent}> = new Observable(
105-
(observer: Observer<{source: DragRef<T>, event: MouseEvent | TouchEvent}>) => {
141+
readonly dragStart: Observable<{source: DragRef<T>, event: PointingDeviceEvent}> = new Observable(
142+
(observer: Observer<{source: DragRef<T>, event: PointingDeviceEvent}>) => {
106143
const subscription = this._dragRef.dragStart$
107144
.subscribe(observer);
108145

@@ -113,8 +150,8 @@ export class KtdDrag<T> implements AfterContentInit, OnDestroy {
113150
);
114151

115152
@Output('dragMove')
116-
readonly dragMove: Observable<{source: DragRef<T>, event: MouseEvent | TouchEvent}> = new Observable(
117-
(observer: Observer<{source: DragRef<T>, event: MouseEvent | TouchEvent}>) => {
153+
readonly dragMove: Observable<{source: DragRef<T>, event: PointingDeviceEvent}> = new Observable(
154+
(observer: Observer<{source: DragRef<T>, event: PointingDeviceEvent}>) => {
118155
const subscription = this._dragRef.dragMove$
119156
.subscribe(observer);
120157

@@ -125,8 +162,8 @@ export class KtdDrag<T> implements AfterContentInit, OnDestroy {
125162
);
126163

127164
@Output('dragEnd')
128-
readonly dragEnd: Observable<{source: DragRef<T>, event: MouseEvent | TouchEvent}> = new Observable(
129-
(observer: Observer<{source: DragRef<T>, event: MouseEvent | TouchEvent}>) => {
165+
readonly dragEnd: Observable<{source: DragRef<T>, event: PointingDeviceEvent}> = new Observable(
166+
(observer: Observer<{source: DragRef<T>, event: PointingDeviceEvent}>) => {
130167
const subscription = this._dragRef.dragEnd$
131168
.subscribe(observer);
132169

@@ -137,76 +174,30 @@ export class KtdDrag<T> implements AfterContentInit, OnDestroy {
137174
);
138175

139176
public _dragRef: DragRef<T>;
177+
140178
private subscriptions: Subscription[] = [];
141-
private element: HTMLElement;
142179

143180
constructor(
144181
/** Element that the draggable is attached to. */
145182
public elementRef: ElementRef,
183+
private gridService: KtdGridService,
146184
private registryService: KtdRegistryService,
147185
) {
148-
this._dragRef = this.registryService.createKtgDrag(this.elementRef, this, this.data);
186+
this._dragRef = this.registryService.createKtgDrag(this.elementRef, this.gridService, this);
149187
}
150188

151189
ngAfterContentInit(): void {
152-
this.element = this.elementRef.nativeElement as HTMLElement;
153190
this.registryService.registerKtgDragItem(this);
154-
this.initDrag();
191+
this.subscriptions.push(
192+
this._dragHandles.changes.subscribe(() => {
193+
this._dragRef.dragHandles = this._dragHandles.toArray();
194+
})
195+
);
155196
}
156197

157198
ngOnDestroy(): void {
158199
this.registryService.unregisterKtgDragItem(this);
159200
this.registryService.destroyKtgDrag(this._dragRef);
160201
this.subscriptions.forEach(subscription => subscription.unsubscribe());
161202
}
162-
163-
/**
164-
* Initialize the drag of ktd-drag element, placeholder dragging is handled by ktd-grid.
165-
* The element will be freely draggable, when drag ends it will snap back to its initial place.
166-
*/
167-
private initDrag() {
168-
this._dragRef.dragStartThreshold = this.dragStartThreshold;
169-
this._dragRef.placeholder = this.placeholder;
170-
this._dragRef.draggable = this.draggable;
171-
this._dragRef.dragHandles = this._dragHandles.toArray();
172-
173-
const handlesSub$ = this._dragHandles.changes.subscribe(() => {
174-
console.log(this._dragHandles.toArray());
175-
this._dragRef.dragHandles = this._dragHandles.toArray();
176-
});
177-
178-
const dragStart$ = this.dragStart.subscribe(({event}) => {
179-
let currentX = 0,
180-
currentY = 0;
181-
182-
const initialX = ktdPointerClientX(event) - currentX;
183-
const initialY = ktdPointerClientY(event) - currentY;
184-
185-
this.dragMove.pipe(
186-
takeUntil(this.dragEnd),
187-
).subscribe(({event}) => {
188-
event.preventDefault();
189-
190-
// Calculate the new cursor position:
191-
currentX = ktdPointerClientX(event) - initialX;
192-
currentY = ktdPointerClientY(event) - initialY;
193-
194-
/**
195-
* TODO: Add support handling scroll offset
196-
*
197-
* Possible solution would be to add for each scrollParent element observable that emits scroll offset,
198-
* and use the offset when we are on top of the scrollParent.
199-
* Still dont know how we would handle nested scrollParents, generated by nested grids.
200-
*/
201-
202-
this.element.style.transform = `translate3d(${currentX}px, ${currentY}px, 0)`;
203-
});
204-
});
205-
206-
const dragEnd$ = this.dragEnd.subscribe(() => {
207-
this.element.style.transform = 'none';
208-
});
209-
210-
this.subscriptions.push(handlesSub$, dragStart$, dragEnd$);
211-
}
212203
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
<ng-content></ng-content>
2-
<div #resizeElem class="grid-item-resize-icon"></div>
2+
<div #resizeElem class="grid-item-resize-icon"></div>

projects/angular-grid-layout/src/lib/grid-item/grid-item.component.ts

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ import {
1414
Renderer2,
1515
ViewChild
1616
} from '@angular/core';
17-
import { BehaviorSubject, iif, merge, NEVER, Observable, Observer, Subject, Subscription } from 'rxjs';
18-
import { exhaustMap, filter, map, startWith, switchMap, take, takeUntil } from 'rxjs/operators';
19-
import { ktdPointerDown, ktdPointerUp, ktdPointerClient } from '../utils/pointer.utils';
17+
import { BehaviorSubject, merge, NEVER, Observable, Observer, Subject, Subscription} from 'rxjs';
18+
import { startWith, switchMap} from 'rxjs/operators';
19+
import { ktdPointerDown} from '../utils/pointer.utils';
2020
import { GRID_ITEM_GET_RENDER_DATA_TOKEN, KtdGridItemRenderDataTokenType } from '../grid.definitions';
2121
import { KTD_GRID_DRAG_HANDLE, KtdGridDragHandle } from '../directives/drag-handle';
2222
import { KTD_GRID_RESIZE_HANDLE, KtdGridResizeHandle } from '../directives/resize-handle';
@@ -25,6 +25,7 @@ import { coerceNumberProperty, NumberInput } from '../coercion/number-property';
2525
import { KTD_GRID_ITEM_PLACEHOLDER, KtdGridItemPlaceholder } from '../directives/placeholder';
2626
import {DragRef} from "../utils/drag-ref";
2727
import {KtdRegistryService} from "../ktd-registry.service";
28+
import {KtdGridService} from "../grid.service";
2829

2930
@Component({
3031
selector: 'ktd-grid-item',
@@ -55,35 +56,27 @@ export class KtdGridItemComponent<T = any> implements OnInit, OnDestroy, AfterCo
5556
/** Id of the grid item. This property is strictly compulsory. */
5657
@Input()
5758
get id(): string {
58-
return this._id;
59+
return this._dragRef.id;
5960
}
6061
set id(val: string) {
61-
this._id = val;
6262
this._dragRef.id = val;
6363
}
64-
private _id: string;
6564

6665
/** Minimum amount of pixels that the user should move before it starts the drag sequence. */
6766
@Input()
68-
get dragStartThreshold(): number { return this._dragStartThreshold; }
67+
get dragStartThreshold(): number { return this._dragRef.dragStartThreshold; }
6968
set dragStartThreshold(val: number) {
70-
this._dragStartThreshold = coerceNumberProperty(val);
71-
this._dragRef.dragStartThreshold = this._dragStartThreshold;
69+
this._dragRef.dragStartThreshold = coerceNumberProperty(val);
7270
}
73-
private _dragStartThreshold: number = 0;
74-
7571

7672
/** Whether the item is draggable or not. Defaults to true. Does not affect manual dragging using the startDragManually method. */
7773
@Input()
7874
get draggable(): boolean {
79-
return this._draggable;
75+
return this._dragRef.draggable;
8076
}
8177
set draggable(val: boolean) {
82-
this._draggable = coerceBooleanProperty(val);
83-
this._draggable$.next(this._draggable);
78+
this._dragRef.draggable = coerceBooleanProperty(val);
8479
}
85-
private _draggable: boolean = true;
86-
private _draggable$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(this._draggable);
8780

8881
/** Whether the item is resizable or not. Defaults to true. */
8982
@Input()
@@ -143,12 +136,13 @@ export class KtdGridItemComponent<T = any> implements OnInit, OnDestroy, AfterCo
143136
);
144137

145138
constructor(public elementRef: ElementRef,
139+
private gridService: KtdGridService,
146140
private registryService: KtdRegistryService,
147141
private renderer: Renderer2,
148142
@Inject(GRID_ITEM_GET_RENDER_DATA_TOKEN) private getItemRenderData: KtdGridItemRenderDataTokenType) {
149143
this.resizeStart$ = this.resizeStartSubject.asObservable();
150144

151-
this._dragRef = this.registryService.createKtgDrag(this.elementRef, this);
145+
this._dragRef = this.registryService.createKtgDrag(this.elementRef, this.gridService, this);
152146
}
153147

154148
ngOnInit() {
@@ -157,12 +151,14 @@ export class KtdGridItemComponent<T = any> implements OnInit, OnDestroy, AfterCo
157151
}
158152

159153
ngAfterContentInit() {
160-
this._dragRef.placeholder = this.placeholder;
161154
this.subscriptions.push(
162155
this._resizeStart$().subscribe(this.resizeStartSubject),
163156
this._dragHandles.changes.subscribe(() => {
164157
this._dragRef.dragHandles = this._dragHandles.toArray();
165158
}),
159+
this._resizeHandles.changes.subscribe(() => {
160+
this._dragRef.resizeHandles = this._resizeHandles.toArray();
161+
}),
166162
);
167163
}
168164

@@ -179,17 +175,19 @@ export class KtdGridItemComponent<T = any> implements OnInit, OnDestroy, AfterCo
179175
* @param startEvent The pointer event that should initiate the drag.
180176
*/
181177
startDragManually(startEvent: MouseEvent | TouchEvent) {
182-
// this._manualDragEvents$.next(startEvent);
183178
this._dragRef.startDragManual(startEvent);
184179
}
185180

186-
setStyles({top, left, width, height}: { top: string, left: string, width?: string, height?: string }) {
181+
setStyles({top, left, width, height}: { top: number, left: number, width?: number, height?: number }) {
182+
this._dragRef.transformX = left;
183+
this._dragRef.transformY = top;
184+
187185
// transform is 6x times faster than top/left
188-
this.renderer.setStyle(this.elementRef.nativeElement, 'transform', `translateX(${left}) translateY(${top})`);
186+
this.renderer.setStyle(this.elementRef.nativeElement, 'transform', `translateX(${left}px) translateY(${top}px)`);
189187
this.renderer.setStyle(this.elementRef.nativeElement, 'display', `block`);
190188
this.renderer.setStyle(this.elementRef.nativeElement, 'transition', this.transition);
191-
if (width != null) { this.renderer.setStyle(this.elementRef.nativeElement, 'width', width); }
192-
if (height != null) {this.renderer.setStyle(this.elementRef.nativeElement, 'height', height); }
189+
if (width != null) { this.renderer.setStyle(this.elementRef.nativeElement, 'width', `${width}px`); }
190+
if (height != null) {this.renderer.setStyle(this.elementRef.nativeElement, 'height', `${height}px`); }
193191
}
194192

195193
private _resizeStart$(): Observable<MouseEvent | TouchEvent> {

0 commit comments

Comments
 (0)