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 if (selected.find(el => el === target)) {
37 target = target.parentNode;
45 const handlerOutput = handler(ev, target || ev.target, this.root);
46 if (handlerOutput === false) {
53 eventHolder.addEventListener(event, evListener);
55 this.handlers.get(eventHolder)![event].push(evListener);
57 return function off() {
58 eventHolder.removeEventListener(event, evListener);
66 public adaptedDrag(selector: string,
67 move?: (dx: number, dy: number, event: UIEvent, target?: Element, root?: Element) => any,
68 start?: (event: UIEvent, target?: Element, root?: Element) => any,
69 end?: (event: UIEvent, target?: Element, root?: Element) => any) {
72 let lastMove: MouseEvent | undefined;
73 let draggedEl: Element | undefined;
74 let moveEventCount = 0;
75 let mouseDownEv: MouseEvent;
77 let mouseOverListeners: EventListener[];
79 const onMouseDown = (ev: MouseEvent, el: Element) => {
87 mouseOverListeners = this.detachHandlers("mouseover");
89 document.addEventListener("mousemove", moveHandler);
90 document.addEventListener("mouseup", upHandler);
95 const off = this.on("mousedown", selector, onMouseDown);
97 const moveHandler = (ev: MouseEvent) => {
102 const dx = ev.screenX - lastMove!.screenX;
103 const dy = ev.screenY - lastMove!.screenY;
106 if (moveEventCount === threshold && typeof start === "function") {
107 start(mouseDownEv, draggedEl, this.root);
110 if (moveEventCount >= threshold && typeof move === "function") {
111 move(dx, dy, ev, draggedEl, this.root);
114 const upHandler = (ev: MouseEvent) => {
115 if (moveEventCount >= threshold) {
117 if (typeof end === "function") {
118 end(ev, draggedEl, this.root)
122 const parentNode = draggedEl!.parentNode;
123 const clickCancellation = (ev: MouseEvent) => {
124 ev.stopPropagation();
125 parentNode!.removeEventListener("click", clickCancellation, true);
127 parentNode!.addEventListener("click", clickCancellation, true);
131 draggedEl = undefined;
132 lastMove = undefined;
134 document.removeEventListener("mouseup", upHandler);
135 document.removeEventListener("mousemove", moveHandler);
137 for (let i in mouseOverListeners) {
138 this.root.addEventListener("mouseover", mouseOverListeners[i]);
139 this.handlers.get(this.root)!["mouseover"] = [];
140 this.handlers.get(this.root)!["mouseover"].push(mouseOverListeners[i]);
148 public drag(selector: string,
149 move?: (dx: number, dy: number, event: UIEvent, target?: Element, root?: Element) => any,
150 start?: (event: UIEvent, target?: Element, root?: Element) => any,
151 end?: (event: UIEvent, target?: Element, root?: Element) => any) {
153 let dragging = false;
154 let lastMove: MouseEvent | undefined;
155 let draggedEl: Element | undefined;
156 let moveEventCount = 0;
157 let mouseDownEv: MouseEvent;
159 let mouseOverListeners: EventListener[];
161 const onMouseDown = (ev: MouseEvent, el: Element, root: Element) => {
169 mouseOverListeners = this.detachHandlers("mouseover");
171 document.addEventListener("mousemove", moveHandler);
172 document.addEventListener("mouseup", upHandler);
177 const off = this.on("mousedown", selector, onMouseDown);
179 const moveHandler = (ev: MouseEvent) => {
184 const dx = ev.screenX - lastMove!.screenX;
185 const dy = ev.screenY - lastMove!.screenY;
188 if (moveEventCount === threshold && typeof start === "function") {
189 start(mouseDownEv, draggedEl, this.root);
192 if (moveEventCount >= threshold && typeof move === "function") {
193 move(dx, dy, ev, draggedEl, this.root);
197 const upHandler = (ev: MouseEvent) => {
199 if (moveEventCount >= threshold) {
201 if (typeof end === "function") {
202 end(ev, draggedEl, this.root)
206 // When releasing the mouse button, if it happens over the same element that we initially had
207 // the mouseDown event, it will trigger a click event. We want to stop that, so we intercept
208 // it by capturing click top-down and stopping its propagation.
209 // However, if the mouseUp didn't happen above the starting element, it wouldn't trigger a click,
210 // but it would intercept the next (unrelated) click event unless we prevent interception in the
211 // first place by checking if we released above the starting element.
212 if (draggedEl!.contains(ev.target as Node)) {
213 const parentNode = draggedEl!.parentNode;
215 const clickCancellation = (ev: MouseEvent) => {
216 ev.stopPropagation();
217 parentNode!.removeEventListener("click", clickCancellation, true);
219 parentNode!.addEventListener("click", clickCancellation, true);
225 draggedEl = undefined;
226 lastMove = undefined;
228 document.removeEventListener("mouseup", upHandler);
229 document.removeEventListener("mousemove", moveHandler);
232 for (let i in mouseOverListeners) {
233 this.root.addEventListener("mouseover", mouseOverListeners[i]);
234 this.handlers.get(this.root)!["mouseover"] = [];
235 this.handlers.get(this.root)!["mouseover"].push(mouseOverListeners[i]);
242 public hover(element: HTMLElement,
243 hover: (event: UIEvent, target?: HTMLElement, root?: HTMLElement) => any = () => {},
244 enter: (event: UIEvent, target?: HTMLElement, root?: HTMLElement) => any = () => {},
245 leave: (event: UIEvent, target?: HTMLElement, root?: HTMLElement) => any = () => {}) {
247 let hovering = false;
249 element.addEventListener("mouseenter", (ev: MouseEvent) => {
251 enter(ev, element, this.root);
255 element.addEventListener("mouseleave", (ev) => {
257 leave(ev, element, this.root);
260 element.addEventListener("mousemove", (ev) => {
264 hover(ev, element, this.root);
268 public detachHandlers(evName: string, root?: HTMLElement): EventListener[] {
269 root = root || this.root;
270 let eventListeners: EventListener[] = [];
271 this.handlers.forEach((handlers: { [event: string]: EventListener[] }, listenerRoot: Element) => {
272 if (listenerRoot.id !== root!.id || listenerRoot !== root) {
275 for (let eventName in handlers) {
276 if (eventName !== evName) {
279 handlers[eventName].forEach((handler) => {
280 eventListeners.push(handler);
281 listenerRoot.removeEventListener(eventName, handler);
286 delete this.handlers.get(this.root)![evName];
288 return eventListeners;
292 this.handlers.forEach((handlers: { [event: string]: EventListener[] }, listenerRoot: Element) => {
293 for (let eventName in handlers) {
294 handlers[eventName].forEach(handler => listenerRoot.removeEventListener(eventName, handler));
298 this.handlers.clear();