1 export class DomEvents {
3 private handlers = new Map<{ removeEventListener: Function }, { [key: string]: Function[] }>();
5 constructor(private root: HTMLElement) {
9 public on(event: string, selector: string, handler: (event: UIEvent, target?: Element, root?: Element) => any, root?: Element): Function;
10 public on(event: string, handler: (event: UIEvent, target?: Element, root?: Element) => any, root?: Element): Function;
11 public on(...args: any[]) {
13 const event = args.shift();
14 const selector = typeof args[0] === "string" ? args.shift() : undefined;
15 const handler = typeof args[0] === "function" ? args.shift() : () => {
17 const root = args.shift();
19 const eventHolder = root || this.root;
21 if (!this.handlers.has(eventHolder)) {
22 this.handlers.set(eventHolder, {});
24 if (!this.handlers.get(eventHolder)![event]) {
25 this.handlers.get(eventHolder)![event] = [];
28 const evListener = (ev: UIEvent) => {
31 const selected = Array.from(this.root.querySelectorAll(selector));
32 target = ev.target as HTMLElement;
34 // eslint-disable-next-line
35 if (selected.find(el => el === target)) {
38 target = target.parentNode;
46 const handlerOutput = handler(ev, target || ev.target, this.root);
47 if (handlerOutput === false) {
54 eventHolder.addEventListener(event, evListener);
56 this.handlers.get(eventHolder)![event].push(evListener);
58 return function off() {
59 eventHolder.removeEventListener(event, evListener);
67 public adaptedDrag(selector: string,
68 move?: (dx: number, dy: number, event: UIEvent, target?: Element, root?: Element) => any,
69 start?: (event: UIEvent, target?: Element, root?: Element) => any,
70 end?: (event: UIEvent, target?: Element, root?: Element) => any) {
73 let lastMove: MouseEvent | undefined;
74 let draggedEl: Element | undefined;
75 let moveEventCount = 0;
76 let mouseDownEv: MouseEvent;
78 let mouseOverListeners: EventListener[];
80 const onMouseDown = (ev: MouseEvent, el: Element) => {
88 mouseOverListeners = this.detachHandlers("mouseover");
90 document.addEventListener("mousemove", moveHandler);
91 document.addEventListener("mouseup", upHandler);
96 const off = this.on("mousedown", selector, onMouseDown);
98 const moveHandler = (ev: MouseEvent) => {
103 const dx = ev.screenX - lastMove!.screenX;
104 const dy = ev.screenY - lastMove!.screenY;
107 if (moveEventCount === threshold && typeof start === "function") {
108 start(mouseDownEv, draggedEl, this.root);
111 if (moveEventCount >= threshold && typeof move === "function") {
112 move(dx, dy, ev, draggedEl, this.root);
115 const upHandler = (ev: MouseEvent) => {
116 if (moveEventCount >= threshold) {
118 if (typeof end === "function") {
119 end(ev, draggedEl, this.root)
123 const parentNode = draggedEl!.parentNode;
124 const clickCancellation = (ev: MouseEvent) => {
125 ev.stopPropagation();
126 parentNode!.removeEventListener("click", clickCancellation, true);
128 parentNode!.addEventListener("click", clickCancellation, true);
132 draggedEl = undefined;
133 lastMove = undefined;
135 document.removeEventListener("mouseup", upHandler);
136 document.removeEventListener("mousemove", moveHandler);
138 for (let i in mouseOverListeners) {
139 this.root.addEventListener("mouseover", mouseOverListeners[i]);
140 this.handlers.get(this.root)!["mouseover"] = [];
141 this.handlers.get(this.root)!["mouseover"].push(mouseOverListeners[i]);
149 public drag(selector: string,
150 move?: (dx: number, dy: number, event: UIEvent, target?: Element, root?: Element) => any,
151 start?: (event: UIEvent, target?: Element, root?: Element) => any,
152 end?: (event: UIEvent, target?: Element, root?: Element) => any) {
154 let dragging = false;
155 let lastMove: MouseEvent | undefined;
156 let draggedEl: Element | undefined;
157 let moveEventCount = 0;
158 let mouseDownEv: MouseEvent;
160 let mouseOverListeners: EventListener[];
162 const onMouseDown = (ev: MouseEvent, el: Element, root: Element) => {
170 mouseOverListeners = this.detachHandlers("mouseover");
172 document.addEventListener("mousemove", moveHandler);
173 document.addEventListener("mouseup", upHandler);
178 const off = this.on("mousedown", selector, onMouseDown);
180 const moveHandler = (ev: MouseEvent) => {
185 const dx = ev.screenX - lastMove!.screenX;
186 const dy = ev.screenY - lastMove!.screenY;
189 if (moveEventCount === threshold && typeof start === "function") {
190 start(mouseDownEv, draggedEl, this.root);
193 if (moveEventCount >= threshold && typeof move === "function") {
194 move(dx, dy, ev, draggedEl, this.root);
198 const upHandler = (ev: MouseEvent) => {
200 if (moveEventCount >= threshold) {
202 if (typeof end === "function") {
203 end(ev, draggedEl, this.root)
207 // When releasing the mouse button, if it happens over the same element that we initially had
208 // the mouseDown event, it will trigger a click event. We want to stop that, so we intercept
209 // it by capturing click top-down and stopping its propagation.
210 // However, if the mouseUp didn't happen above the starting element, it wouldn't trigger a click,
211 // but it would intercept the next (unrelated) click event unless we prevent interception in the
212 // first place by checking if we released above the starting element.
213 if (draggedEl!.contains(ev.target as Node)) {
214 const parentNode = draggedEl!.parentNode;
216 const clickCancellation = (ev: MouseEvent) => {
217 ev.stopPropagation();
218 parentNode!.removeEventListener("click", clickCancellation, true);
220 parentNode!.addEventListener("click", clickCancellation, true);
226 draggedEl = undefined;
227 lastMove = undefined;
229 document.removeEventListener("mouseup", upHandler);
230 document.removeEventListener("mousemove", moveHandler);
233 for (let i in mouseOverListeners) {
234 this.root.addEventListener("mouseover", mouseOverListeners[i]);
235 this.handlers.get(this.root)!["mouseover"] = [];
236 this.handlers.get(this.root)!["mouseover"].push(mouseOverListeners[i]);
243 public hover(element: HTMLElement,
244 hover: (event: UIEvent, target?: HTMLElement, root?: HTMLElement) => any = () => {},
245 enter: (event: UIEvent, target?: HTMLElement, root?: HTMLElement) => any = () => {},
246 leave: (event: UIEvent, target?: HTMLElement, root?: HTMLElement) => any = () => {}) {
248 let hovering = false;
250 element.addEventListener("mouseenter", (ev: MouseEvent) => {
252 enter(ev, element, this.root);
256 element.addEventListener("mouseleave", (ev) => {
258 leave(ev, element, this.root);
261 element.addEventListener("mousemove", (ev) => {
265 hover(ev, element, this.root);
269 public detachHandlers(evName: string, root?: HTMLElement): EventListener[] {
270 root = root || this.root;
271 let eventListeners: EventListener[] = [];
272 this.handlers.forEach((handlers: { [event: string]: EventListener[] }, listenerRoot: Element) => {
273 if (listenerRoot.id !== root!.id || listenerRoot !== root) {
276 for (let eventName in handlers) {
277 if (eventName !== evName) {
280 handlers[eventName].forEach((handler) => {
281 eventListeners.push(handler);
282 listenerRoot.removeEventListener(eventName, handler);
287 delete this.handlers.get(this.root)![evName];
289 return eventListeners;
293 this.handlers.forEach((handlers: { [event: string]: EventListener[] }, listenerRoot: Element) => {
294 for (let eventName in handlers) {
295 handlers[eventName].forEach(handler => listenerRoot.removeEventListener(eventName, handler));
299 this.handlers.clear();