-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
delegate.ts
51 lines (45 loc) · 1.74 KB
/
delegate.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* Copyright (c) 2017, Philip Walton <philip@philipwalton.com>
*/
import { closest } from './closest';
import { matches } from './matches';
/**
* Delegates the handling of events for an element matching a selector to an
* ancestor of the matching element.
* @param {!Node} ancestor The ancestor element to add the listener to.
* @param {string} eventType The event type to listen to.
* @param {string} selector A CSS selector to match against child elements.
* @param {!Function} callback A function to run any time the event happens.
* @param {Object=} opts A configuration options object. The available options:
* - useCapture<boolean>: If true, bind to the event capture phase.
* - deep<boolean>: If true, delegate into shadow trees.
* @return {Object} The delegate object. It contains a destroy method.
*/
export function delegate(ancestor, eventType, selector, callback, opts = {}) {
// Defines the event listener.
const listener = function(event) {
let delegateTarget;
// If opts.composed is true and the event originated from inside a Shadow
// tree, check the composed path nodes.
if (opts['composed'] && typeof event['composedPath'] === 'function') {
const composedPath = event.composedPath();
for (let i = 0, node; (node = composedPath[i]); i++) {
if (node.nodeType === 1 && matches(node, selector)) {
delegateTarget = node;
}
}
} else {
// Otherwise check the parents.
delegateTarget = closest(event.target, selector, true);
}
if (delegateTarget) {
callback.call(delegateTarget, event, delegateTarget);
}
};
ancestor.addEventListener(eventType, listener, opts['useCapture']);
return {
destroy: () => {
ancestor.removeEventListener(eventType, listener, opts['useCapture']);
},
};
}