├── LICENSE
├── README.md
├── index.js
├── package.json
└── test
└── index.html
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright © 2024-today, Andrea Giammarchi, @WebReflection
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the “Software”), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software
10 | is furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included
13 | in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 | IN THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # add-promise-listener
2 |
3 | **Social Media Photo by [Europeana](https://unsplash.com/@europeanaeu) on [Unsplash](https://unsplash.com/)**
4 |
5 | Using promises as generic event listener.
6 |
7 | ```js
8 | import addPromiseListener from 'https://esm.run/add-promise-listener';
9 |
10 | const button = document.getElementById('test-button');
11 | const ac = new AbortController;
12 | addPromiseListener(
13 | button,
14 | 'click',
15 | {
16 | // this is optionally needed to be sure the operation is performed
17 | // when it's needed and not during the next tick:
18 | // stopPropagation: true
19 | // stopImmediatePropagation: true
20 | preventDefault: true,
21 | // optional signal to eventually catch rejections
22 | signal: ac.signal
23 | // other standard options are allowed as well
24 | // capture: true
25 | // passive: true
26 | }
27 | ).then(
28 | event => {
29 | console.log(`${event.type}ed 🥳`);
30 | console.assert(event.currentTarget === button, 'currentTarget');
31 | console.assert(event.defaultPrevented, 'defaultPrevented');
32 | },
33 | event => {
34 | console.assert(event.currentTarget === button, 'currentTarget');
35 | console.error(event.target.reason);
36 | }
37 | );
38 |
39 | // simulate a rejection in 5 seconds
40 | setTimeout(() => ac.abort('timeout!'), 5000);
41 | ```
42 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const { defineProperty } = Object;
2 | const { get } = Reflect;
3 |
4 | const methods = [
5 | 'preventDefault',
6 | 'stopPropagation',
7 | 'stopImmediatePropagation',
8 | ];
9 |
10 | const once = { once: true };
11 |
12 | // avoid event.preventDefault throwing due illegal Proxy invocation
13 | const bound = (e, value) => typeof value === 'function' ? value.bind(e) : value;
14 |
15 | // traps the `event.currentTarget` to be sure it's available later on
16 | class Handler {
17 | #currentTarget;
18 | constructor(currentTarget) {
19 | this.#currentTarget = currentTarget;
20 | }
21 | get(e, name) {
22 | // Did you know? event.currentTarget disappears from events on
23 | // next tick, which is why this proxy handler needs to exist.
24 | return name === 'currentTarget' ? this.#currentTarget : bound(e, get(e, name));
25 | }
26 | }
27 |
28 | /**
29 | * Add a listener that result as a Promise, fulfilled when the event happens once or rejected if the optional provided signal is aborted.
30 | * @param {Element} element
31 | * @param {string} type
32 | * @param {{ signal?:AbortSignal, capture?:boolean, passive?:boolean, preventDefault?:boolean, stopPropagation?:boolean, stopImmediatePropagation?:boolean }?} options
33 | * @returns {Promise}
34 | */
35 | export default (element, type, options = null) => new Promise(
36 | (resolve, reject) => {
37 | const handler = new Handler(element);
38 | if (options.signal) {
39 | const abort = event => reject(new Proxy(event, handler));
40 | options.signal.addEventListener('abort', abort, once);
41 | if (options.signal.aborted)
42 | return options.signal.dispatchEvent(new Event('abort'));
43 | }
44 | element.addEventListener(
45 | type,
46 | (event) => {
47 | for (const method of methods) {
48 | if (options[method]) event[method]();
49 | }
50 | resolve(new Proxy(event, handler));
51 | },
52 | { ...options, ...once }
53 | );
54 | }
55 | );
56 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "add-promise-listener",
3 | "version": "0.1.3",
4 | "main": "index.js",
5 | "directories": {
6 | "test": "test"
7 | },
8 | "scripts": {
9 | "test": "echo -e \"\\x1b[1mhttp://localhost:8080/test/\\x1b[0m ↗️\"; npx static-handler ."
10 | },
11 | "files": [
12 | "index.js",
13 | "LICENSE",
14 | "README.md"
15 | ],
16 | "keywords": [
17 | "promise",
18 | "listener"
19 | ],
20 | "author": "Andrea Giammarchi",
21 | "license": "MIT",
22 | "description": "Using promises as generic event listener",
23 | "repository": {
24 | "type": "git",
25 | "url": "git+https://github.com/WebReflection/add-promise-listener.git"
26 | },
27 | "bugs": {
28 | "url": "https://github.com/WebReflection/add-promise-listener/issues"
29 | },
30 | "homepage": "https://github.com/WebReflection/add-promise-listener#readme"
31 | }
32 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------