@deepkit/desktop-ui
Version:
Library for desktop UI widgets in Angular 10+
162 lines (139 loc) • 5.11 kB
text/typescript
import { Directive, input, output } from '@angular/core';
import { injectElementRef, registerEventListener, RegisterEventListenerRemove } from './utils';
export interface DuiDragEvent extends PointerEvent {
id: number;
deltaX: number;
deltaY: number;
}
export interface DuiDragStartEvent extends DuiDragEvent {
/**
* If this is set to false, the drag will not be accepted.
*/
accept: boolean;
}
/**
* A directive that catches pointer events and emits drag events.
*
* This won't move the element, it just emits events when the user does a drag gesture.
*
* ```html
* <div (duiDrag)="onDrag($event)" [duiDragThreshold]="2"></div>
* ```
*/
export class DragDirective {
protected element = injectElementRef();
protected id = 0;
duiDragThreshold = input(0);
duiDragAbortOnEscape = input(true);
duiDrag = output<DuiDragEvent>();
duiDragStart = output<DuiDragStartEvent>();
duiDragEnd = output<DuiDragEvent>();
duiDragCancel = output<number>();
protected startX = 0;
protected startY = 0;
protected dragging = false;
protected draggingElement?: DuiDragEvent['target'];
protected destroy: RegisterEventListenerRemove;
protected removePointerMove?: RegisterEventListenerRemove;
protected removePointerUp?: RegisterEventListenerRemove;
protected removeKeyUp?: RegisterEventListenerRemove;
constructor() {
this.destroy = registerEventListener(this.element.nativeElement, 'pointerdown', (e) => this.onPointerDown(e));
}
protected onPointerDown(e: PointerEvent) {
if (e.button !== 0) return;
e.stopPropagation();
this.startX = e.clientX;
this.startY = e.clientY;
this.dragging = false;
const el = this.element.nativeElement;
el.setPointerCapture(e.pointerId);
const id = ++this.id;
this.dragging = false;
const threshold = this.duiDragThreshold() * this.duiDragThreshold();
this.draggingElement = e.target;
const onMove = (event: PointerEvent) => {
const dx = event.clientX - this.startX;
const dy = event.clientY - this.startY;
if (!this.dragging) {
const start = threshold ? dx * dx + dy * dy >= threshold : true;
if (start) {
const startEvent: DuiDragStartEvent = Object.assign(event, {
id,
accept: true,
deltaX: dx,
deltaY: dy,
});
this.duiDragStart.emit(startEvent);
this.dragging = startEvent.accept;
if (!startEvent.accept) {
this.abort(id);
return;
}
}
}
if (this.dragging) {
const dragEvent: DuiDragEvent = Object.assign(event, {
id,
deltaX: dx,
deltaY: dy,
});
this.duiDrag.emit(dragEvent);
}
};
const onUp = (event: PointerEvent) => {
el.releasePointerCapture(event.pointerId);
this.release();
if (this.dragging) {
this.dragging = false;
event.stopPropagation();
const dx = event.clientX - this.startX;
const dy = event.clientY - this.startY;
const dragEndEvent: DuiDragEvent = Object.assign(event, {
id,
deltaX: dx,
deltaY: dy,
});
this.duiDragEnd.emit(dragEndEvent);
this.element.nativeElement.addEventListener('click', (up) => up.stopPropagation(), { capture: true, once: true });
}
};
const onKey = (event: KeyboardEvent) => {
if (!this.dragging) return;
event.stopPropagation();
if (event.key === 'Escape') {
el.releasePointerCapture(e.pointerId);
this.dragging = false;
this.release();
this.duiDragCancel.emit(id);
}
};
this.removePointerMove = registerEventListener(window, 'pointermove', onMove);
this.removePointerUp = registerEventListener(window, 'pointerup', onUp, { once: true });
if (this.duiDragAbortOnEscape()) {
this.removeKeyUp = registerEventListener(window, 'keydown', onKey);
}
}
protected release() {
this.removePointerMove?.();
this.removePointerUp?.();
this.removeKeyUp?.();
}
protected abort(id: number) {
this.release();
this.draggingElement = undefined;
if (this.dragging) {
this.duiDragCancel.emit(id);
}
this.dragging = false;
}
ngOnDestroy() {
this.removePointerMove?.();
this.removePointerUp?.();
this.removeKeyUp?.();
this.destroy();
}
}