mirror of
https://github.com/rio-labs/rio.git
synced 2026-01-03 11:49:47 -06:00
172 lines
4.6 KiB
TypeScript
172 lines
4.6 KiB
TypeScript
import { ComponentBase } from './components/componentBase';
|
|
|
|
export function markEventAsHandled(event: Event): void {
|
|
event.stopPropagation();
|
|
event.stopImmediatePropagation();
|
|
event.preventDefault();
|
|
}
|
|
|
|
export function stopPropagation(event: Event): void {
|
|
event.stopPropagation();
|
|
}
|
|
|
|
export abstract class EventHandler {
|
|
component: ComponentBase;
|
|
|
|
constructor(component: ComponentBase) {
|
|
this.component = component;
|
|
|
|
component._eventHandlers.add(this);
|
|
}
|
|
|
|
disconnect(): void {
|
|
this.component._eventHandlers.delete(this);
|
|
}
|
|
}
|
|
|
|
function _no_op(): boolean {
|
|
return true;
|
|
}
|
|
|
|
export type ClickHandlerArguments = {
|
|
onClick: (event: MouseEvent) => boolean;
|
|
target?: EventTarget;
|
|
capturing?: boolean;
|
|
};
|
|
|
|
export class ClickHandler extends EventHandler {
|
|
private onClick: (event: MouseEvent) => void;
|
|
private target: EventTarget;
|
|
private capturing: boolean;
|
|
|
|
constructor(component: ComponentBase, args: ClickHandlerArguments) {
|
|
super(component);
|
|
|
|
this.onClick = args.onClick;
|
|
this.target = args.target ?? component.element;
|
|
this.capturing = args.capturing ?? false;
|
|
|
|
// @ts-ignore
|
|
this.target.addEventListener('click', this.onClick, this.capturing);
|
|
}
|
|
|
|
override disconnect(): void {
|
|
super.disconnect();
|
|
|
|
// @ts-ignore
|
|
this.target.removeEventListener('click', this.onClick, this.capturing);
|
|
}
|
|
}
|
|
|
|
export type DragHandlerArguments = {
|
|
element: HTMLElement;
|
|
onStart?: (event: MouseEvent) => boolean;
|
|
onMove?: (event: MouseEvent) => void;
|
|
onEnd?: (event: MouseEvent) => void;
|
|
capturing?: boolean;
|
|
};
|
|
|
|
export class DragHandler extends EventHandler {
|
|
private element: HTMLElement;
|
|
private onStart: (event: MouseEvent) => boolean;
|
|
private onMove: (event: MouseEvent) => void;
|
|
private onEnd: (event: MouseEvent) => void;
|
|
private capturing: boolean;
|
|
|
|
private onMouseDown = this._onMouseDown.bind(this);
|
|
private onMouseMove = this._onMouseMove.bind(this);
|
|
private onMouseUp = this._onMouseUp.bind(this);
|
|
private onClick = this._onClick.bind(this);
|
|
|
|
private hasDragged = false;
|
|
|
|
constructor(component: ComponentBase, args: DragHandlerArguments) {
|
|
super(component);
|
|
|
|
this.element = args.element;
|
|
|
|
this.onStart = args.onStart ?? _no_op;
|
|
this.onMove = args.onMove ?? _no_op;
|
|
this.onEnd = args.onEnd ?? _no_op;
|
|
|
|
this.capturing = args.capturing ?? true;
|
|
this.element.addEventListener(
|
|
'mousedown',
|
|
this.onMouseDown,
|
|
this.capturing
|
|
);
|
|
}
|
|
|
|
private _onMouseDown(event: MouseEvent): void {
|
|
// We only care about the left mouse button
|
|
if (event.button !== 0) {
|
|
return;
|
|
}
|
|
|
|
let onStartResult = this.onStart(event);
|
|
|
|
// It's easy to forget to return a boolean. Make sure to catch this
|
|
// mistake.
|
|
if (onStartResult !== true && onStartResult !== false) {
|
|
throw new Error(
|
|
`Drag event onStart must return a boolean, but it returned ${onStartResult}`
|
|
);
|
|
}
|
|
|
|
// Don't continue if the handler isn't interested in the event.
|
|
if (!onStartResult) {
|
|
return;
|
|
}
|
|
|
|
markEventAsHandled(event);
|
|
|
|
window.addEventListener('mousemove', this.onMouseMove, true);
|
|
window.addEventListener('mouseup', this.onMouseUp, true);
|
|
window.addEventListener('click', this.onClick, true);
|
|
}
|
|
|
|
private _onMouseMove(event: MouseEvent): void {
|
|
this.hasDragged = true;
|
|
|
|
markEventAsHandled(event);
|
|
|
|
this.onMove(event);
|
|
}
|
|
|
|
private _onMouseUp(event: MouseEvent): void {
|
|
if (this.hasDragged) {
|
|
markEventAsHandled(event);
|
|
}
|
|
|
|
this._disconnectDragListeners();
|
|
this.onEnd(event);
|
|
}
|
|
|
|
private _onClick(event: MouseEvent): void {
|
|
// This event isn't using by the drag event handler, but it should
|
|
// nonetheless be stopped to prevent the click from being handled by
|
|
// other handlers.
|
|
|
|
if (this.hasDragged) {
|
|
markEventAsHandled(event);
|
|
}
|
|
}
|
|
|
|
private _disconnectDragListeners(): void {
|
|
window.removeEventListener('mousemove', this.onMouseMove, true);
|
|
window.removeEventListener('mouseup', this.onMouseUp, true);
|
|
window.removeEventListener('click', this.onClick, true);
|
|
}
|
|
|
|
override disconnect(): void {
|
|
super.disconnect();
|
|
|
|
this.element.removeEventListener(
|
|
'mousedown',
|
|
this.onMouseDown,
|
|
this.capturing
|
|
);
|
|
this._disconnectDragListeners();
|
|
}
|
|
}
|