├── .eslintrc.yml
├── .github
└── workflows
│ └── CI.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.yml
├── .vscode
├── extensions.json
└── settings.json
├── LICENSE
├── README.md
├── docs
├── migrating-to-v6.md
└── reference.md
├── package-lock.json
├── package.json
├── scripts
├── empty.js
├── rollup.config.js
└── test.ts
├── src
├── index.ts
└── lib
│ ├── dom-exception.ts
│ ├── error-handler.ts
│ ├── event-attribute-handler.ts
│ ├── event-target.ts
│ ├── event-wrapper.ts
│ ├── event.ts
│ ├── global.ts
│ ├── legacy.ts
│ ├── listener-list-map.ts
│ ├── listener-list.ts
│ ├── listener.ts
│ ├── misc.ts
│ ├── warning-handler.ts
│ └── warnings.ts
├── test
├── default-error-handler.ts
├── default-warning-handler.ts
├── define-custom-event-target.ts
├── event-attribute.ts
├── event-target.ts
├── event.ts
├── fixtures
│ ├── entrypoint.ts
│ └── types.ts
└── lib
│ ├── abort-signal-stub.ts
│ ├── count-event-listeners.ts
│ └── setup-error-check.ts
├── tsconfig.json
└── tsconfig
├── base.json
├── build.json
├── dts.json
└── test.json
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | root: true
2 |
3 | ignorePatterns:
4 | - "!.*"
5 | - /coverage
6 | - /dist
7 | - /node_modules
8 | - /test/fixtures/types.ts
9 | # parser raised unexpected error.
10 | - /src/lib/warnings.ts
11 |
12 | extends:
13 | - plugin:@mysticatea/es2020
14 |
15 | globals:
16 | console: "off"
17 |
18 | rules:
19 | no-console: error
20 | "@mysticatea/ts/explicit-member-accessibility": "off"
21 | "@mysticatea/prettier": "off"
22 |
23 | settings:
24 | node:
25 | tryExtensions:
26 | - .tsx
27 | - .ts
28 | - .mjs
29 | - .cjs
30 | - .js
31 | - .json
32 | - .node
33 |
34 | overrides:
35 | - files: scripts/**
36 | rules:
37 | no-console: "off"
38 |
39 | - files: src/**
40 | rules:
41 | # Avoid iteration because transpiled code will inflate much.
42 | no-restricted-syntax: [error, ForOfStatement]
43 | "@mysticatea/prefer-for-of": "off"
44 |
--------------------------------------------------------------------------------
/.github/workflows/CI.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | push:
4 | branches: [master]
5 | pull_request:
6 | branches: [master]
7 | schedule:
8 | - cron: 0 0 * * 0
9 |
10 | jobs:
11 | static-analysis:
12 | name: Static Analysis
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v2
17 | - name: Install Node.js 14.x
18 | uses: actions/setup-node@v1
19 | with:
20 | node-version: 14.x
21 | - name: Install Packages
22 | run: npm ci
23 | - name: Test
24 | run: npx run-s "test:{tsc,lint,format}"
25 |
26 | test-on-node:
27 | name: Test on Node.js
28 | runs-on: ubuntu-latest
29 | strategy:
30 | matrix:
31 | node: [14.x, 12.x, 10.x, "10.13.0"]
32 | steps:
33 | - name: Checkout
34 | uses: actions/checkout@v2
35 | - name: Install Node.js ${{ matrix.node }}
36 | uses: actions/setup-node@v1
37 | with:
38 | node-version: ${{ matrix.node }}
39 | - name: Install Packages
40 | run: npm ci
41 | - name: Test
42 | run: npm run test:mocha -- --only-node
43 | - name: Send Coverage
44 | run: npx codecov
45 | env:
46 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
47 |
48 | test-on-browser:
49 | name: Test on Browsers
50 | runs-on: ubuntu-latest
51 | steps:
52 | - name: Checkout
53 | uses: actions/checkout@v2
54 | - name: Install Node.js 14.x
55 | uses: actions/setup-node@v1
56 | with:
57 | node-version: 14.x
58 | - name: Install Packages
59 | run: |
60 | sudo apt-get install libbrotli1 libegl1 libopus0 libwoff1 \
61 | libgstreamer-plugins-base1.0-0 libgstreamer1.0-0 \
62 | libgstreamer-gl1.0-0 libgstreamer-plugins-bad1.0-0 libopenjp2-7 \
63 | libwebpdemux2 libhyphen0 libgles2 gstreamer1.0-libav
64 | npm ci
65 | - name: Test
66 | run: npm run test:mocha -- --only-browsers
67 | - name: Send Coverage
68 | run: npx codecov
69 | env:
70 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
71 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /coverage
2 | /dist
3 | /node_modules
4 | /npm-debug.log
5 | /test.*
6 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .nyc_output
2 | coverage
3 | dist
4 | node_modules
5 | LICENSE
6 | package-lock.json
7 |
--------------------------------------------------------------------------------
/.prettierrc.yml:
--------------------------------------------------------------------------------
1 | overrides:
2 | - files: ["*.js", "*.ts"]
3 | options:
4 | tabWidth: 4
5 | semi: false
6 | trailingComma: all
7 | arrowParens: avoid
8 | endOfLine: lf
9 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "typescript.format.semicolons": "remove",
3 |
4 | "[javascript]": {
5 | "editor.codeActionsOnSave": ["source.fixAll.eslint"],
6 | "editor.defaultFormatter": "esbenp.prettier-vscode",
7 | "editor.formatOnSave": true
8 | },
9 | "[json]": {
10 | "editor.codeActionsOnSave": [],
11 | "editor.defaultFormatter": "esbenp.prettier-vscode",
12 | "editor.formatOnSave": true
13 | },
14 | "[jsonc]": {
15 | "editor.codeActionsOnSave": [],
16 | "editor.defaultFormatter": "esbenp.prettier-vscode",
17 | "editor.formatOnSave": true
18 | },
19 | "[markdown]": {
20 | "editor.codeActionsOnSave": [],
21 | "editor.defaultFormatter": "esbenp.prettier-vscode",
22 | "editor.formatOnSave": true
23 | },
24 | "[typescript]": {
25 | "editor.codeActionsOnSave": [
26 | "source.fixAll.eslint",
27 | "source.organizeImports"
28 | ],
29 | "editor.defaultFormatter": "esbenp.prettier-vscode",
30 | "editor.formatOnSave": true
31 | },
32 | "[yaml]": {
33 | "editor.codeActionsOnSave": [],
34 | "editor.defaultFormatter": "esbenp.prettier-vscode",
35 | "editor.formatOnSave": true
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Toru Nagashima
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 is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | 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 IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # event-target-shim
2 |
3 | [](https://www.npmjs.com/package/event-target-shim)
4 | [](http://www.npmtrends.com/event-target-shim)
5 | [](https://github.com/mysticatea/event-target-shim/actions)
6 | [](https://codecov.io/gh/mysticatea/event-target-shim)
7 | [](https://david-dm.org/mysticatea/event-target-shim)
8 |
9 | An implementation of [WHATWG `EventTarget` interface](https://dom.spec.whatwg.org/#interface-eventtarget) and [WHATWG `Event` interface](https://dom.spec.whatwg.org/#interface-event). This implementation supports constructor, `passive`, `once`, and `signal`.
10 |
11 | This implementation is designed ...
12 |
13 | - Working fine on both browsers and Node.js.
14 | - TypeScript friendly.
15 |
16 | **Native Support Information:**
17 |
18 | | Feature | IE | Edge | Firefox | Chrome | Safari | Node.js |
19 | | :------------------------ | :-- | :--- | :------ | :----- | :----- | :------ |
20 | | `Event` constructor | ❌ | 12 | 11 | 15 | 6 | 15.4.0 |
21 | | `EventTarget` constructor | ❌ | 87 | 84 | 87 | 14 | 15.4.0 |
22 | | `passive` option | ❌ | 16 | 49 | 51 | 10 | 15.4.0 |
23 | | `once` option | ❌ | 16 | 50 | 55 | 10 | 15.4.0 |
24 | | `signal` option | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
25 |
26 | ---
27 |
28 | ## 💿 Installation
29 |
30 | Use [npm](https://www.npmjs.com/) or a compatible tool.
31 |
32 | ```
33 | npm install event-target-shim
34 | ```
35 |
36 | ## 📖 Getting started
37 |
38 | ```js
39 | import { EventTarget, Event } from "event-target-shim";
40 |
41 | // constructor (was added to the standard on 8 Jul 2017)
42 | const myNode = new EventTarget();
43 |
44 | // passive flag (was added to the standard on 6 Jan 2016)
45 | myNode.addEventListener(
46 | "hello",
47 | (e) => {
48 | e.preventDefault(); // ignored and print warning on console.
49 | },
50 | { passive: true }
51 | );
52 |
53 | // once flag (was added to the standard on 15 Apr 2016)
54 | myNode.addEventListener("hello", listener, { once: true });
55 | myNode.dispatchEvent(new Event("hello")); // remove the listener after call.
56 |
57 | // signal (was added to the standard on 4 Dec 2020)
58 | const ac = new AbortController();
59 | myNode.addEventListener("hello", listener, { signal: ac.signal });
60 | ac.abort(); // remove the listener.
61 | ```
62 |
63 | - For browsers, there are two ways:
64 | - use a bundler such as [Webpack](https://webpack.js.org/) to bundle. If you want to support IE11, use `import {} from "event-target-shim/es5"` instead. It's a transpiled code by babel. It depends on `@baebl/runtime` (`^7.12.0`) package.
65 | - use CDN such as `unpkg.com`. For example, `` will define `EventTargetShim` global variable.
66 | - The `AbortController` class was added to the standard on 14 Jul 2017. If you want the shim of that, use [abort-controller](https://www.npmjs.com/package/abort-controller) package.
67 |
68 | ### Runnable Examples
69 |
70 | - [Basic Example](https://jsbin.com/dapuwomamo/1/edit?html,console)
71 | - [Basic Example (IE11)](https://jsbin.com/xigeyetipe/1/edit?html,console)
72 |
73 | ## 📚 API Reference
74 |
75 | See [docs/reference.md](docs/reference.md).
76 |
77 | ## 💥 Migrating to v6
78 |
79 | See [docs/migrating-to-v6.md](docs/migrating-to-v6.md).
80 |
81 | ## 📰 Changelog
82 |
83 | See [GitHub releases](https://github.com/mysticatea/event-target-shim/releases).
84 |
85 | ## 🍻 Contributing
86 |
87 | Contributing is welcome ❤️
88 |
89 | Please use GitHub issues/PRs.
90 |
91 | ### Development tools
92 |
93 | - `npm install` installs dependencies for development.
94 | - `npm test` runs tests and measures code coverage.
95 | - `npm run watch:mocha` runs tests on each file change.
96 |
--------------------------------------------------------------------------------
/docs/migrating-to-v6.md:
--------------------------------------------------------------------------------
1 | # 💥 Migrating to v6
2 |
3 | `event-target-shim` v6.0.0 contains large changes of API.
4 |
5 | - [CommonJS's default export is no longer `EventTarget`.](#-commonjss-default-export-is-no-longer-eventtarget)
6 | - [The function call support of `EventTarget` constructor was removed.](#-the-function-call-support-of-eventtarget-constructor-was-removed)
7 | - [Internal file structure was changed.](#-internal-file-structure-was-changed)
8 | - [\[Node.js\] Node.js version requirement was updated.](#-nodejs-nodejs-version-requirement-was-updated)
9 | - [\[Node.js\] Throwing errors from listeners now causes `uncaughtExcption` event.](#-nodejs-throwing-errors-from-listeners-now-causes-uncaughtexcption-event)
10 | - [\[Browsers\] ECMAScript version requirement was updated.](#-browsers-ecmascript-version-requirement-was-updated)
11 | - [\[Browsers\] Throwing errors from listeners now causes `error` event on `window`.](#-browsers-throwing-errors-from-listeners-now-causes-error-event-on-window)
12 | - [\[TypeScript\] TypeScript version requirement was updated.](#-typescript-typescript-version-requirement-was-updated)
13 | - [\[TypeScript\] The second type parameter of `EventTarget` was removed.](#-typescript-the-second-type-parameter-of-eventtarget-was-removed)
14 |
15 | ## ■ CommonJS's default export is no longer `EventTarget`.
16 |
17 | ```js
18 | // ❌ Before
19 | const EventTarget = require("event-target-shim");
20 |
21 | // ✅ After
22 | const { EventTarget } = require("event-target-shim");
23 | // or
24 | const { default: EventTarget } = require("event-target-shim");
25 | ```
26 |
27 | Because this package has multiple exports (`EventTarget`, `Event`, ...), so we have to modify the static members of `EventTarget` in order to support CommonJS's default export. I don't want to modify `EventTarget` for this purpose.
28 |
29 | This change doesn't affect to ESM syntax.
30 | I.e., `import EventTarget from "event-target-shim"` is OK.
31 |
32 | ## ■ The function call support of `EventTarget` constructor was removed.
33 |
34 | ```js
35 | // ❌ Before
36 | import { EventTarget } from "event-target-shim";
37 | class DerivedClass extends EventTarget("foo", "bar") {}
38 |
39 | // ✅ After
40 | import { defineCustomEventTarget } from "event-target-shim";
41 | class DerivedClass extends defineCustomEventTarget("foo", "bar") {}
42 |
43 | // Or define getters/setters of `onfoo`/`onbar` manually in your derived class.
44 | import {
45 | EventTarget,
46 | getEventAttributeValue,
47 | setEventAttributeValue,
48 | } from "event-target-shim";
49 | class DerivedClass extends EventTarget {
50 | get onfoo() {
51 | return getEventAttributeValue(this, "foo");
52 | }
53 | set onfoo(value) {
54 | setEventAttributeValue(this, "foo", value);
55 | }
56 | get onbar() {
57 | return getEventAttributeValue(this, "bar");
58 | }
59 | set onbar(value) {
60 | setEventAttributeValue(this, "bar", value);
61 | }
62 | }
63 | ```
64 |
65 | Because that was non-standard behavior and ES2015 class syntax cannot support it.
66 |
67 | ## ■ Internal file structure was changed.
68 |
69 | - (previous) → (now)
70 | - `dist/event-target-shim.mjs` → `index.mjs`
71 | - `dist/event-target-shim.js` → `index.js`
72 | - `dist/event-target-shim.umd.js` → `umd.js`
73 |
74 | And now the internal file structure is private by the `exports` field of `package.json`.
75 |
76 | ## ■ \[Node.js] Node.js version requirement was updated.
77 |
78 | Now this package requires Node.js **10.13.0** or later.
79 |
80 | Because Node.js v9 and older have been End-of-Life already.
81 | 10.13.0 is the first LTS version of v10 series.
82 |
83 | ## ■ \[Node.js] Throwing errors from listeners now causes `uncaughtExcption` event.
84 |
85 | If a registered listener threw an exception while event dispatching, it now emits an `uncaughtExcption` event by default.
86 |
87 | You can customize this behavior by `setErrorHandler` API.
88 |
89 | ```js
90 | import { setErrorHandler } from "event-target-shim";
91 |
92 | // Only print errors.
93 | setErrorHandler((error) => {
94 | console.error(error);
95 | });
96 | ```
97 |
98 | ## ■ \[Browsers] ECMAScript version requirement was updated.
99 |
100 | Now this package requires **ES2018** or later.
101 |
102 | Because modern browsers have supported ES2018 widely.
103 | If you want to support IE11, use `event-target-shim/es5` that is a transpiled version.
104 |
105 | ```js
106 | import { EventTarget } from "event-target-shim/es5";
107 | ```
108 |
109 | ## ■ \[Browsers] Throwing errors from listeners now causes `error` event on `window`.
110 |
111 | If a registered listener threw an exception while event dispatching, it now dispatches an `error` event on `window` by default.
112 |
113 | You can customize this behavior by `setErrorHandler` API.
114 |
115 | ```js
116 | import { setErrorHandler } from "event-target-shim";
117 |
118 | // Only print errors.
119 | setErrorHandler((error) => {
120 | console.error(error);
121 | });
122 | ```
123 |
124 | ## ■ \[TypeScript] TypeScript version requirement was updated.
125 |
126 | Now this package requires TypeScript **4.1** or later if you are using this package on TypeScript.
127 |
128 | Because this is using [Template Literal Types](https://devblogs.microsoft.com/typescript/announcing-typescript-4-1/#template-literal-types).
129 |
130 | ## ■ \[TypeScript] The second type parameter of `EventTarget` was removed.
131 |
132 | ```js
133 | // ❌ Before
134 | import { EventTarget } from "event-target-shim";
135 | interface MyEventTarget
136 | extends EventTarget<{ myevent: Event }, { onmyevent: Event }> {}
137 |
138 | // ✅ After
139 | import { EventTarget } from "event-target-shim";
140 | interface MyEventTarget extends EventTarget<{ myevent: Event }> {
141 | onmyevent: EventTarget.CallbackFunction | null;
142 | }
143 | ```
144 |
145 | Because the `EventTarget` object never have event attributes. Derived classes can define event attributes. Therefore, it was odd that the `EventTarget` interface had event attributes.
146 |
--------------------------------------------------------------------------------
/docs/reference.md:
--------------------------------------------------------------------------------
1 | # 📚 API Reference
2 |
3 | ## ■ `EventTarget`
4 |
5 | ```js
6 | import { EventTarget } from "event-target-shim";
7 | // or
8 | const { EventTarget } = require("event-target-shim");
9 | ```
10 |
11 | > The HTML Standard: [EventTarget interface](https://dom.spec.whatwg.org/#interface-eventtarget)
12 |
13 | ### ▶ constructor
14 |
15 | Create a new instance of the `EventTarget` class.
16 |
17 | There are no arguments.
18 |
19 | ### ▶ `eventTarget.addEventListener(type, callback, options)`
20 |
21 | Register an event listener.
22 |
23 | - `type` is a string. This is the event name to register.
24 | - `callback` is a function. This is the event listener to register.
25 | - `options` is an object `{ capture?: boolean; passive?: boolean; once?: boolean; signal?: AbortSignal }`. This is optional.
26 | - `capture` is the flag to register the event listener for capture phase.
27 | - `passive` is the flag to ignore `event.preventDefault()` method in the event listener.
28 | - `once` is the flag to remove this callback automatically after the first call.
29 | - `signal` is an `AbortSignal` object to remove this callback. You can use this option as alternative to the `eventTarget.removeEventListener(...)` method.
30 |
31 | ### ▶ `eventTarget.removeEventListener(type, callback, options)`
32 |
33 | Unregister an event listener.
34 |
35 | - `type` is a string. This is the event name to unregister.
36 | - `callback` is a function. This is the event listener to unregister.
37 | - `options` is an object `{ capture?: boolean }`. This is optional.
38 | - `capture` is the flag to register the event listener for capture phase.
39 |
40 | ### ▶ `eventTarget.dispatchEvent(event)`
41 |
42 | Dispatch an event.
43 |
44 | - `event` is a [Event](https://dom.spec.whatwg.org/#event) object to dispatch.
45 |
46 | ## ■ `Event`
47 |
48 | ```js
49 | import { Event } from "event-target-shim";
50 | // or
51 | const { Event } = require("event-target-shim");
52 | ```
53 |
54 | > The HTML Standard: [Event interface](https://dom.spec.whatwg.org/#interface-event)
55 |
56 | See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Event#Properties) for details.
57 |
58 | ## ■ Event attribute handler
59 |
60 | ```js
61 | import {
62 | getEventAttributeValue,
63 | setEventAttributeValue,
64 | } from "event-target-shim";
65 | // or
66 | const {
67 | getEventAttributeValue,
68 | setEventAttributeValue,
69 | } = require("event-target-shim");
70 | ```
71 |
72 | > Non-standard.
73 |
74 | You can define event attributes (e.g. `onclick`) on your derived classes of `EventTarget`.
75 |
76 | #### Example
77 |
78 | ```js
79 | import {
80 | EventTarget,
81 | getEventAttributeValue,
82 | setEventAttributeValue,
83 | } from "event-target-shim";
84 |
85 | class AbortSignal extends EventTarget {
86 | constructor() {
87 | this.aborted = false;
88 | }
89 |
90 | // Define `onabort` property
91 | get onabort() {
92 | return getEventAttributeValue(this, "abort");
93 | }
94 | set onabort(value) {
95 | setEventAttributeValue(this, "abort", value);
96 | }
97 | }
98 | ```
99 |
100 | ## ■ Error handling
101 |
102 | ```js
103 | import { setErrorHandler, setWarningHandler } from "event-target-shim";
104 | ```
105 |
106 | > Non-standard.
107 |
108 | You can customize error/wanring behavior of `EventTarget`-shim.
109 |
110 | ### ▶ `setErrorHandler(handler)`
111 |
112 | Set your error handler. The error means exceptions that event listeners threw.
113 |
114 | The default handler is `undefined`. It dispatches an [ErrorEvent](https://developer.mozilla.org/ja/docs/Web/API/ErrorEvent) on `window` on browsers, or emits an [`uncaughtException` event](https://nodejs.org/api/process.html#process_event_uncaughtexception) on `process` on Node.js.
115 |
116 | The first argument of the error handler is a thrown error.
117 |
118 | #### Example
119 |
120 | ```js
121 | import { setErrorHandler } from "event-target-shim";
122 |
123 | // Print log only.
124 | setErrorHandler((error) => {
125 | console.error(error);
126 | });
127 | ```
128 |
129 | ### ▶ `setWarningHandler(handler)`
130 |
131 | Set your warning handler. The warning is reported when `EventTarget` or `Event` doesn't throw any errors but ignores operations silently.
132 |
133 | The default handler is `undefined`. It prints warnings with the `console.warn` method.
134 |
135 | The first argument of the warning handler is a reported warning information. It has three properties:
136 |
137 | - `code` ... A warning code. Use it for i18n.
138 | - `message` ... The warning message in English.
139 | - `args` ... The array of arguments for replacing placeholders in the message.
140 |
141 | The warning handler will be called when...
142 |
143 | | Code | Description |
144 | | :------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
145 | | `"W01"` | `Event.prototype.initEvent` method was called while dispatching. In this case, the method does nothing. |
146 | | `"W02"` | `Event.prototype.cancelBubble` setter received a falsy value. In this case, the setter does nothing. |
147 | | `"W03"` | `Event.prototype.returnValue` setter received a truthy value. In this case, the setter does nothing. |
148 | | `"W04"` | `Event.prototype.preventDefault` method was called or `Event.prototype.returnValue` setter received a falsy value, but the event was not cancelable. In this case, the method or setter does nothing. |
149 | | `"W05"` | `Event.prototype.preventDefault` method was called or `Event.prototype.returnValue` setter received a falsy value, but that's in a passive listener. In this case, the method or setter does nothing. |
150 | | `"W06"` | `EventTarget.prototype.addEventListener` method received a listener that has been added already. In this case, the method does nothing. |
151 | | `"W07"` | `EventTarget.prototype.addEventListener` method received a listener that has been added already, and any of `passive`, `once`, and `signal` options are different between the existing listener and the ignored listener. In this case, the new options are abandoned. |
152 | | `"W08"` | `EventTarget.prototype.{addEventListener,removeEventListener}` methods received an invalid event listener. In this case, the methods ignore the listener. |
153 | | `"W09"` | `setEventAttributeValue` function received an invalid event attribute handler. If that was a primitive value, the function removes the current event attribute handler. Otherwise, the function adopts the listener, but the listener will never be called. |
154 |
155 | #### Example
156 |
157 | ```js
158 | import { setWarningHandler } from "event-target-shim";
159 |
160 | // Print log only.
161 | setWarningHandler((warning) => {
162 | console.warn(warning.message, ...warning.args);
163 | });
164 | ```
165 |
166 | ## ■ \[TypeScript] Types
167 |
168 | The `EventTarget` and `Event` classes this package provides are compatible with the `EventTarget` and `Event` interfaces in the built-in `DOM` library of TypeScript. We can assign those each other.
169 |
170 | Additionally, the `EventTarget` and `Event` classes this package provides have some type parameters.
171 |
172 | ### ▶ `EventTarget`
173 |
174 | The `EventTarget` class has two type parameters.
175 |
176 | - `TEventMap` ... Optional. The event map. Keys are event types and each value is the type of `Event` class. Default is `Record`.
177 | This event map provides known event types. It's useful to infer the event types on `addEventListener` method.
178 | - `TMode` ... Optional. The mode of `EventTarget` type. This is `"standard"` or `"strict"`. Default is `"standard"`.
179 | If this is `"standard"`, the `EventTarget` type accepts unknown event types as well. It follows the standard.
180 | If this is `"strict"`, the `EventTarget` type accepts only known event types. It will protect the mistakes of giving wrong `Event` objects. On the other hand, the `EventTarget` type is not compatible to the standard.
181 |
182 | #### Example
183 |
184 | ```ts
185 | type AbortSignalEventMap = {
186 | abort: Event<"abort">;
187 | };
188 |
189 | class AbortSignal extends EventTarget {
190 | // ....
191 | }
192 |
193 | type EventSourceEventMap = {
194 | close: Event<"close">;
195 | error: Event<"error">;
196 | message: MessageEvent;
197 | };
198 |
199 | class EventSource extends EventTarget {
200 | // ....
201 | }
202 |
203 | type MyEventMap = {
204 | // ....
205 | };
206 |
207 | class MyStuff extends EventTarget {
208 | // ....
209 | }
210 | ```
211 |
212 | ### ▶ `Event`
213 |
214 | The `Event` class has a type parameter.
215 |
216 | - `T` ... Optional. The type of the `type` property. Default is `string`.
217 |
218 | #### Example
219 |
220 | ```ts
221 | const e = new Event("myevent");
222 | const t: "myevent" = e.type; // the type of `type` property is `"myevent"`.
223 | ```
224 |
225 | ### ▶ `getEventAttributeValue(target, type)`, `setEventAttributeValue(target, type, value)`
226 |
227 | The `getEventAttributeValue` and `setEventAttributeValue` functions have a type parameter.
228 |
229 | - `T` ... The type of the `Event` class.
230 |
231 | #### Example
232 |
233 | ```ts
234 | type AbortSignalEventMap = {
235 | abort: Event<"abort">;
236 | };
237 |
238 | class AbortSignal extends EventTarget {
239 | // ....
240 |
241 | get onabort() {
242 | return getEventAttributeValue(this, "abort");
243 | }
244 | set onabort(value) {
245 | setEventAttributeValue(this, "abort", value);
246 | }
247 | }
248 | ```
249 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "event-target-shim",
3 | "version": "6.0.2",
4 | "description": "An implementation of WHATWG EventTarget interface.",
5 | "main": "index.js",
6 | "exports": {
7 | ".": {
8 | "import": "./index.mjs",
9 | "require": "./index.js"
10 | },
11 | "./es5": {
12 | "import": "./es5.mjs",
13 | "require": "./es5.js"
14 | },
15 | "./umd": "./umd.js",
16 | "./package.json": "./package.json"
17 | },
18 | "files": [
19 | "index.*",
20 | "es5.*",
21 | "umd.*"
22 | ],
23 | "engines": {
24 | "node": ">=10.13.0"
25 | },
26 | "scripts": {
27 | "build": "run-s \"build:{clean,rollup,dts,meta}\"",
28 | "build:clean": "rimraf \"dist/*\"",
29 | "build:rollup": "rollup --config scripts/rollup.config.js",
30 | "build:dts": "dts-bundle-generator --project tsconfig/dts.json --out-file dist/index.d.ts src/index.ts && dts-bundle-generator --project tsconfig/dts.json --out-file dist/es5.d.ts src/index.ts",
31 | "build:meta": "cpx \"{LICENSE,package.json,README.md}\" dist/",
32 | "preversion": "npm test",
33 | "version": "npm run build",
34 | "postversion": "release",
35 | "test": "run-s \"test:{clean,tsc,lint,format,mocha}\"",
36 | "test:clean": "rimraf \"coverage/*\"",
37 | "test:tsc": "tsc -p tsconfig/build.json --noEmit",
38 | "test:lint": "eslint .",
39 | "test:format": "prettier --check .",
40 | "test:mocha": "ts-node scripts/test",
41 | "watch:mocha": "mocha --require ts-node/register/transpile-only --extensions ts --watch-files src,test --watch \"test/*.ts\""
42 | },
43 | "dependencies": {},
44 | "devDependencies": {
45 | "@babel/core": "^7.12.10",
46 | "@babel/plugin-transform-runtime": "^7.12.10",
47 | "@babel/preset-env": "^7.12.11",
48 | "@mysticatea/eslint-plugin": "^13.0.0",
49 | "@mysticatea/spy": "^0.1.2",
50 | "@mysticatea/tools": "^0.1.1",
51 | "@rollup/plugin-babel": "^5.2.2",
52 | "@rollup/plugin-typescript": "^8.1.0",
53 | "@types/istanbul-lib-coverage": "^2.0.3",
54 | "@types/istanbul-lib-report": "^3.0.0",
55 | "@types/istanbul-lib-source-maps": "^4.0.1",
56 | "@types/istanbul-reports": "^3.0.0",
57 | "@types/mocha": "^8.2.0",
58 | "@types/rimraf": "^3.0.0",
59 | "assert": "^2.0.0",
60 | "babel-loader": "^8.2.2",
61 | "babel-plugin-istanbul": "^6.0.0",
62 | "buffer": "^6.0.3",
63 | "chalk": "^4.1.0",
64 | "codecov": "^3.8.1",
65 | "cpx": "^1.5.0",
66 | "dts-bundle-generator": "^5.5.0",
67 | "eslint": "^7.15.0",
68 | "istanbul-lib-coverage": "^3.0.0",
69 | "istanbul-lib-report": "^3.0.0",
70 | "istanbul-lib-source-maps": "^4.0.0",
71 | "istanbul-reports": "^3.0.2",
72 | "mocha": "^7.2.0",
73 | "npm-run-all": "^4.1.5",
74 | "path-browserify": "^1.0.1",
75 | "playwright": "^1.7.0",
76 | "prettier": "~2.2.1",
77 | "process": "^0.11.10",
78 | "rimraf": "^3.0.2",
79 | "rollup": "^2.35.1",
80 | "rollup-plugin-terser": "^7.0.2",
81 | "rollup-watch": "^4.3.1",
82 | "stream-browserify": "^3.0.0",
83 | "ts-loader": "^8.0.12",
84 | "ts-node": "^9.1.1",
85 | "tslib": "^2.0.3",
86 | "typescript": "~4.1.3",
87 | "url": "^0.11.0",
88 | "util": "^0.12.3",
89 | "webpack": "^5.11.0"
90 | },
91 | "repository": {
92 | "type": "git",
93 | "url": "https://github.com/mysticatea/event-target-shim.git"
94 | },
95 | "keywords": [
96 | "w3c",
97 | "whatwg",
98 | "eventtarget",
99 | "event",
100 | "events",
101 | "shim"
102 | ],
103 | "author": "Toru Nagashima",
104 | "license": "MIT",
105 | "bugs": {
106 | "url": "https://github.com/mysticatea/event-target-shim/issues"
107 | },
108 | "homepage": "https://github.com/mysticatea/event-target-shim",
109 | "funding": "https://github.com/sponsors/mysticatea",
110 | "sideEffects": false,
111 | "unpkg": "umd.js"
112 | }
113 |
--------------------------------------------------------------------------------
/scripts/empty.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mysticatea/event-target-shim/a37993c95ed9cc1d7cb89358ea126ed4f95372e8/scripts/empty.js
--------------------------------------------------------------------------------
/scripts/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from "@rollup/plugin-babel"
2 | import typescript from "@rollup/plugin-typescript"
3 | import { terser } from "rollup-plugin-terser"
4 |
5 | const babelBaseConfig = {
6 | babelrc: false,
7 | presets: [
8 | [
9 | "@babel/env",
10 | {
11 | modules: false,
12 | targets: "IE 11",
13 | useBuiltIns: false,
14 | },
15 | ],
16 | ],
17 | }
18 |
19 | function sourcemapPathTransform(path) {
20 | return path.startsWith("../") ? path.slice("../".length) : path
21 | }
22 |
23 | export default [
24 | {
25 | input: "src/index.ts",
26 | output: {
27 | file: "dist/index.mjs",
28 | format: "es",
29 | sourcemap: true,
30 | sourcemapPathTransform,
31 | },
32 | plugins: [typescript({ tsconfig: "tsconfig/build.json" })],
33 | },
34 | {
35 | input: "src/index.ts",
36 | output: {
37 | exports: "named",
38 | file: "dist/index.js",
39 | format: "cjs",
40 | sourcemap: true,
41 | sourcemapPathTransform,
42 | },
43 | plugins: [typescript({ tsconfig: "tsconfig/build.json" })],
44 | },
45 | {
46 | external: id => id.startsWith("@babel/runtime/"),
47 | input: "dist/index.mjs",
48 | output: {
49 | file: "dist/es5.mjs",
50 | format: "es",
51 | },
52 | plugins: [
53 | babel({
54 | ...babelBaseConfig,
55 | babelHelpers: "runtime",
56 | plugins: [["@babel/transform-runtime", { useESModules: true }]],
57 | }),
58 | ],
59 | },
60 | {
61 | external: id => id.startsWith("@babel/runtime/"),
62 | input: "dist/index.mjs",
63 | output: {
64 | exports: "named",
65 | file: "dist/es5.js",
66 | format: "cjs",
67 | },
68 | plugins: [
69 | babel({
70 | ...babelBaseConfig,
71 | babelHelpers: "runtime",
72 | plugins: ["@babel/transform-runtime"],
73 | }),
74 | ],
75 | },
76 | {
77 | input: "dist/index.mjs",
78 | output: {
79 | exports: "named",
80 | file: "dist/umd.js",
81 | format: "umd",
82 | name: "EventTargetShim",
83 | },
84 | plugins: [
85 | terser(),
86 | babel({
87 | ...babelBaseConfig,
88 | babelHelpers: "bundled",
89 | }),
90 | ],
91 | },
92 | ]
93 |
--------------------------------------------------------------------------------
/scripts/test.ts:
--------------------------------------------------------------------------------
1 | import chalk from "chalk"
2 | import crypto from "crypto"
3 | import fs from "fs"
4 | import { CoverageMap, createCoverageMap } from "istanbul-lib-coverage"
5 | import { createContext as createCoverageContext } from "istanbul-lib-report"
6 | import { createSourceMapStore } from "istanbul-lib-source-maps"
7 | import { create as createCoverageReporter } from "istanbul-reports"
8 | import os from "os"
9 | import path from "path"
10 | import playwright from "playwright"
11 | import rimraf from "rimraf"
12 | import url from "url"
13 | import util from "util"
14 | import webpackCallback, { Configuration, ProvidePlugin, Stats } from "webpack"
15 |
16 | const writeFile = util.promisify(fs.writeFile)
17 | const mkdir = util.promisify(fs.mkdir)
18 | const rmdir = util.promisify(rimraf)
19 | const webpack = util.promisify(
20 | webpackCallback,
21 | )
22 |
23 | main(process.argv.slice(2)).catch(error => {
24 | process.exitCode = 1
25 | console.error(error)
26 | })
27 |
28 | async function main(argv: readonly string[]) {
29 | const testOnNode = !argv.includes("--only-browsers")
30 | const testOnBrowsers = !argv.includes("--only-node")
31 | const workspacePath = path.join(
32 | os.tmpdir(),
33 | `event-target-shim-${crypto.randomBytes(4).toString("hex")}`,
34 | )
35 | const coverageMap = createCoverageMap()
36 |
37 | await mkdir(workspacePath)
38 | try {
39 | await buildTests(workspacePath, testOnNode, testOnBrowsers)
40 | await runTests(workspacePath, coverageMap, testOnNode, testOnBrowsers)
41 | reportCoverage(coverageMap)
42 | } finally {
43 | await rmdir(workspacePath)
44 | }
45 | }
46 |
47 | async function buildTests(
48 | workspacePath: string,
49 | testOnNode: boolean,
50 | testOnBrowsers: boolean,
51 | ): Promise {
52 | console.log("======== Build Tests ".padEnd(80, "="))
53 |
54 | if (testOnNode) {
55 | await build(workspacePath, false)
56 | }
57 | if (testOnBrowsers) {
58 | await writeFile(
59 | path.join(workspacePath, "playwright.html"),
60 | '\n',
61 | )
62 | await build(workspacePath, true)
63 | }
64 |
65 | console.log("Done!")
66 | }
67 |
68 | async function runTests(
69 | workspacePath: string,
70 | coverageMap: CoverageMap,
71 | testOnNode: boolean,
72 | testOnBrowsers: boolean,
73 | ): Promise {
74 | console.log("======== Run Tests ".padEnd(80, "="))
75 |
76 | let failures = 0
77 |
78 | if (testOnNode) {
79 | failures += await runTestsOnNode(workspacePath, coverageMap)
80 | }
81 | if (testOnBrowsers) {
82 | failures += await runTestsOnBrowsers(workspacePath, coverageMap)
83 | }
84 |
85 | console.log("-------- Result ".padEnd(80, "-"))
86 | if (failures) {
87 | console.log(chalk.bold.redBright("%d test cases failed."), failures)
88 | process.exitCode = 1
89 | } else {
90 | console.log(chalk.greenBright("All test cases succeeded ❤️"))
91 | }
92 | }
93 |
94 | async function runTestsOnNode(
95 | workspacePath: string,
96 | coverageMap: CoverageMap,
97 | ): Promise {
98 | console.log(chalk.magentaBright("-------- node ".padEnd(80, "-")))
99 |
100 | await import(path.join(workspacePath, "node.js"))
101 | const { coverage, failures } = await (global as any).result
102 |
103 | await mergeCoverageMap(coverageMap, coverage)
104 |
105 | console.log()
106 | return failures
107 | }
108 |
109 | async function runTestsOnBrowsers(
110 | workspacePath: string,
111 | coverageMap: CoverageMap,
112 | ): Promise {
113 | let failures = 0
114 | for (const browserType of ["chromium", "firefox", "webkit"] as const) {
115 | console.log(
116 | chalk.magentaBright(`-------- ${browserType} `.padEnd(80, "-")),
117 | )
118 |
119 | const browser = await playwright[browserType].launch()
120 | try {
121 | const context = await browser.newContext()
122 | const page = await context.newPage()
123 |
124 | // Redirect console logs.
125 | let consolePromise = Promise.resolve()
126 | page.on("console", msg => {
127 | consolePromise = consolePromise
128 | .then(() => Promise.all(msg.args().map(h => h.jsonValue())))
129 | .then(args => console.log(...args))
130 | })
131 |
132 | // Run tests.
133 | await page.goto(
134 | url
135 | .pathToFileURL(path.join(workspacePath, "playwright.html"))
136 | .toString(),
137 | )
138 |
139 | // Get result.
140 | const result = await page.evaluate("result")
141 | failures += result.failures
142 | await consolePromise
143 |
144 | // Merge coverage data.
145 | await mergeCoverageMap(coverageMap, result.coverage)
146 | } finally {
147 | await browser.close()
148 | }
149 | console.log()
150 | }
151 |
152 | return failures
153 | }
154 |
155 | async function build(
156 | workspacePath: string,
157 | forBrowsers: boolean,
158 | ): Promise {
159 | const conf: Configuration = {
160 | devtool: "inline-source-map",
161 | entry: path.resolve("test/fixtures/entrypoint.ts"),
162 | mode: "development",
163 | module: {
164 | rules: [
165 | {
166 | test: /\.ts$/u,
167 | include: [path.resolve(__dirname, "../src")],
168 | loader: "babel-loader",
169 | options: {
170 | babelrc: false,
171 | plugins: ["istanbul"],
172 | sourceMaps: "inline",
173 | },
174 | },
175 | {
176 | test: /\.ts$/u,
177 | loader: "ts-loader",
178 | options: {
179 | configFile: path.resolve("tsconfig/test.json"),
180 | transpileOnly: true,
181 | },
182 | },
183 | ],
184 | },
185 | output: {
186 | devtoolModuleFilenameTemplate: path.resolve("[resource-path]"),
187 | path: workspacePath,
188 | filename: "node.js",
189 | },
190 | resolve: {
191 | extensions: [".ts", ".mjs", ".cjs", ".js", ".json"],
192 | },
193 | target: "node",
194 | }
195 |
196 | if (forBrowsers) {
197 | conf.output!.filename = "playwright.js"
198 | conf.plugins = [
199 | new ProvidePlugin({
200 | Buffer: ["buffer/", "Buffer"],
201 | process: "process/browser",
202 | }),
203 | ]
204 | conf.resolve!.fallback = {
205 | assert: require.resolve("assert/"),
206 | buffer: require.resolve("buffer/"),
207 | fs: require.resolve("./empty.js"),
208 | path: require.resolve("path-browserify"),
209 | stream: require.resolve("stream-browserify"),
210 | url: require.resolve("url/"),
211 | util: require.resolve("util/"),
212 | }
213 | conf.target = "web"
214 | }
215 |
216 | const stats = await webpack(conf)
217 | if (stats?.hasErrors()) {
218 | throw new Error(stats.toString())
219 | }
220 | }
221 |
222 | async function mergeCoverageMap(
223 | coverageMap: CoverageMap,
224 | rawData: any,
225 | ): Promise {
226 | const sourceMapStore = createSourceMapStore()
227 | const mappedData = toJSON(
228 | await sourceMapStore.transformCoverage(createCoverageMap(rawData)),
229 | )
230 |
231 | const normalizedData = Object.entries(mappedData)
232 | .map(([k, v]) => [
233 | path.normalize(k),
234 | { ...toJSON(v), path: path.normalize(k) },
235 | ])
236 | // eslint-disable-next-line no-sequences
237 | .reduce((obj, [k, v]) => ((obj[k] = v), obj), {})
238 |
239 | try {
240 | coverageMap.merge(normalizedData)
241 | } catch (err) {
242 | console.log(normalizedData)
243 | throw err
244 | }
245 | }
246 |
247 | function reportCoverage(coverageMap: CoverageMap): void {
248 | const context = createCoverageContext({ coverageMap, dir: "coverage" })
249 |
250 | // 出力する
251 | ;(createCoverageReporter("text-summary") as any).execute(context)
252 | ;(createCoverageReporter("lcov") as any).execute(context)
253 | console.log('See "coverage/lcov-report/index.html" for details.')
254 | console.log()
255 | }
256 |
257 | function toJSON(x: any): any {
258 | return typeof x.toJSON === "function" ? toJSON(x.toJSON()) : x
259 | }
260 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { setErrorHandler } from "./lib/error-handler"
2 | import { Event } from "./lib/event"
3 | import {
4 | getEventAttributeValue,
5 | setEventAttributeValue,
6 | } from "./lib/event-attribute-handler"
7 | import { EventTarget } from "./lib/event-target"
8 | import { defineCustomEventTarget, defineEventAttribute } from "./lib/legacy"
9 | import { setWarningHandler } from "./lib/warning-handler"
10 |
11 | export default EventTarget
12 | export {
13 | defineCustomEventTarget,
14 | defineEventAttribute,
15 | Event,
16 | EventTarget,
17 | getEventAttributeValue,
18 | setErrorHandler,
19 | setEventAttributeValue,
20 | setWarningHandler,
21 | }
22 |
--------------------------------------------------------------------------------
/src/lib/dom-exception.ts:
--------------------------------------------------------------------------------
1 | import { Global } from "./global"
2 |
3 | /**
4 | * Create a new InvalidStateError instance.
5 | * @param message The error message.
6 | */
7 | export function createInvalidStateError(message: string): Error {
8 | if (Global.DOMException) {
9 | return new Global.DOMException(message, "InvalidStateError")
10 | }
11 |
12 | if (DOMException == null) {
13 | DOMException = class DOMException extends Error {
14 | constructor(msg: string) {
15 | super(msg)
16 | if ((Error as any).captureStackTrace) {
17 | ;(Error as any).captureStackTrace(this, DOMException)
18 | }
19 | }
20 | // eslint-disable-next-line class-methods-use-this
21 | get code() {
22 | return 11
23 | }
24 | // eslint-disable-next-line class-methods-use-this
25 | get name() {
26 | return "InvalidStateError"
27 | }
28 | }
29 | Object.defineProperties(DOMException.prototype, {
30 | code: { enumerable: true },
31 | name: { enumerable: true },
32 | })
33 | defineErrorCodeProperties(DOMException)
34 | defineErrorCodeProperties(DOMException.prototype)
35 | }
36 | return new DOMException(message)
37 | }
38 |
39 | //------------------------------------------------------------------------------
40 | // Helpers
41 | //------------------------------------------------------------------------------
42 |
43 | let DOMException: { new (message: string): Error } | undefined
44 |
45 | const ErrorCodeMap = {
46 | INDEX_SIZE_ERR: 1,
47 | DOMSTRING_SIZE_ERR: 2,
48 | HIERARCHY_REQUEST_ERR: 3,
49 | WRONG_DOCUMENT_ERR: 4,
50 | INVALID_CHARACTER_ERR: 5,
51 | NO_DATA_ALLOWED_ERR: 6,
52 | NO_MODIFICATION_ALLOWED_ERR: 7,
53 | NOT_FOUND_ERR: 8,
54 | NOT_SUPPORTED_ERR: 9,
55 | INUSE_ATTRIBUTE_ERR: 10,
56 | INVALID_STATE_ERR: 11,
57 | SYNTAX_ERR: 12,
58 | INVALID_MODIFICATION_ERR: 13,
59 | NAMESPACE_ERR: 14,
60 | INVALID_ACCESS_ERR: 15,
61 | VALIDATION_ERR: 16,
62 | TYPE_MISMATCH_ERR: 17,
63 | SECURITY_ERR: 18,
64 | NETWORK_ERR: 19,
65 | ABORT_ERR: 20,
66 | URL_MISMATCH_ERR: 21,
67 | QUOTA_EXCEEDED_ERR: 22,
68 | TIMEOUT_ERR: 23,
69 | INVALID_NODE_TYPE_ERR: 24,
70 | DATA_CLONE_ERR: 25,
71 | }
72 | type ErrorCodeMap = typeof ErrorCodeMap
73 |
74 | function defineErrorCodeProperties(obj: any): void {
75 | const keys = Object.keys(ErrorCodeMap) as (keyof ErrorCodeMap)[]
76 | for (let i = 0; i < keys.length; ++i) {
77 | const key = keys[i]
78 | const value = ErrorCodeMap[key]
79 | Object.defineProperty(obj, key, {
80 | get() {
81 | return value
82 | },
83 | configurable: true,
84 | enumerable: true,
85 | })
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/lib/error-handler.ts:
--------------------------------------------------------------------------------
1 | import { anyToString, assertType } from "./misc"
2 |
3 | declare const console: any
4 | declare const dispatchEvent: any
5 | declare const ErrorEvent: any
6 | declare const process: any
7 |
8 | let currentErrorHandler: setErrorHandler.ErrorHandler | undefined
9 |
10 | /**
11 | * Set the error handler.
12 | * @param value The error handler to set.
13 | */
14 | export function setErrorHandler(
15 | value: setErrorHandler.ErrorHandler | undefined,
16 | ): void {
17 | assertType(
18 | typeof value === "function" || value === undefined,
19 | "The error handler must be a function or undefined, but got %o.",
20 | value,
21 | )
22 | currentErrorHandler = value
23 | }
24 | export namespace setErrorHandler {
25 | /**
26 | * The error handler.
27 | * @param error The thrown error object.
28 | */
29 | export type ErrorHandler = (error: Error) => void
30 | }
31 |
32 | /**
33 | * Print a error message.
34 | * @param maybeError The error object.
35 | */
36 | export function reportError(maybeError: unknown): void {
37 | try {
38 | const error =
39 | maybeError instanceof Error
40 | ? maybeError
41 | : new Error(anyToString(maybeError))
42 |
43 | // Call the user-defined error handler if exists.
44 | if (currentErrorHandler) {
45 | currentErrorHandler(error)
46 | return
47 | }
48 |
49 | // Dispatch an `error` event if this is on a browser.
50 | if (
51 | typeof dispatchEvent === "function" &&
52 | typeof ErrorEvent === "function"
53 | ) {
54 | dispatchEvent(
55 | new ErrorEvent("error", { error, message: error.message }),
56 | )
57 | }
58 |
59 | // Emit an `uncaughtException` event if this is on Node.js.
60 | //istanbul ignore else
61 | else if (
62 | typeof process !== "undefined" &&
63 | typeof process.emit === "function"
64 | ) {
65 | process.emit("uncaughtException", error)
66 | return
67 | }
68 |
69 | // Otherwise, print the error.
70 | console.error(error)
71 | } catch {
72 | // ignore.
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/lib/event-attribute-handler.ts:
--------------------------------------------------------------------------------
1 | import { Event } from "./event"
2 | import { EventTarget, getEventTargetInternalData } from "./event-target"
3 | import { addListener, ListenerList, removeListener } from "./listener-list"
4 | import { ensureListenerList } from "./listener-list-map"
5 | import { InvalidAttributeHandler } from "./warnings"
6 |
7 | /**
8 | * Get the current value of a given event attribute.
9 | * @param target The `EventTarget` object to get.
10 | * @param type The event type.
11 | */
12 | export function getEventAttributeValue<
13 | TEventTarget extends EventTarget,
14 | TEvent extends Event
15 | >(
16 | target: TEventTarget,
17 | type: string,
18 | ): EventTarget.CallbackFunction | null {
19 | const listMap = getEventTargetInternalData(target, "target")
20 | return listMap[type]?.attrCallback ?? null
21 | }
22 |
23 | /**
24 | * Set an event listener to a given event attribute.
25 | * @param target The `EventTarget` object to set.
26 | * @param type The event type.
27 | * @param callback The event listener.
28 | */
29 | export function setEventAttributeValue(
30 | target: EventTarget,
31 | type: string,
32 | callback: EventTarget.CallbackFunction | null,
33 | ): void {
34 | if (callback != null && typeof callback !== "function") {
35 | InvalidAttributeHandler.warn(callback)
36 | }
37 |
38 | if (
39 | typeof callback === "function" ||
40 | (typeof callback === "object" && callback !== null)
41 | ) {
42 | upsertEventAttributeListener(target, type, callback)
43 | } else {
44 | removeEventAttributeListener(target, type)
45 | }
46 | }
47 |
48 | //------------------------------------------------------------------------------
49 | // Helpers
50 | //------------------------------------------------------------------------------
51 |
52 | /**
53 | * Update or insert the given event attribute handler.
54 | * @param target The `EventTarget` object to set.
55 | * @param type The event type.
56 | * @param callback The event listener.
57 | */
58 | function upsertEventAttributeListener<
59 | TEventTarget extends EventTarget
60 | >(
61 | target: TEventTarget,
62 | type: string,
63 | callback: EventTarget.CallbackFunction,
64 | ): void {
65 | const list = ensureListenerList(
66 | getEventTargetInternalData(target, "target"),
67 | String(type),
68 | )
69 | list.attrCallback = callback
70 |
71 | if (list.attrListener == null) {
72 | list.attrListener = addListener(
73 | list,
74 | defineEventAttributeCallback(list),
75 | false,
76 | false,
77 | false,
78 | undefined,
79 | )
80 | }
81 | }
82 |
83 | /**
84 | * Remove the given event attribute handler.
85 | * @param target The `EventTarget` object to remove.
86 | * @param type The event type.
87 | * @param callback The event listener.
88 | */
89 | function removeEventAttributeListener(
90 | target: EventTarget,
91 | type: string,
92 | ): void {
93 | const listMap = getEventTargetInternalData(target, "target")
94 | const list = listMap[String(type)]
95 | if (list && list.attrListener) {
96 | removeListener(list, list.attrListener.callback, false)
97 | list.attrCallback = list.attrListener = undefined
98 | }
99 | }
100 |
101 | /**
102 | * Define the callback function for the given listener list object.
103 | * It calls `attrCallback` property if the property value is a function.
104 | * @param list The `ListenerList` object.
105 | */
106 | function defineEventAttributeCallback(
107 | list: ListenerList,
108 | ): EventTarget.CallbackFunction {
109 | return function (event) {
110 | const callback = list.attrCallback
111 | if (typeof callback === "function") {
112 | callback.call(this, event)
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/lib/event-target.ts:
--------------------------------------------------------------------------------
1 | import { createInvalidStateError } from "./dom-exception"
2 | import { Event, getEventInternalData } from "./event"
3 | import { EventWrapper } from "./event-wrapper"
4 | import { Global } from "./global"
5 | import {
6 | invokeCallback,
7 | isCapture,
8 | isOnce,
9 | isPassive,
10 | isRemoved,
11 | Listener,
12 | } from "./listener"
13 | import {
14 | addListener,
15 | findIndexOfListener,
16 | removeListener,
17 | removeListenerAt,
18 | } from "./listener-list"
19 | import {
20 | createListenerListMap,
21 | ensureListenerList,
22 | ListenerListMap,
23 | } from "./listener-list-map"
24 | import { assertType, format } from "./misc"
25 | import {
26 | EventListenerWasDuplicated,
27 | InvalidEventListener,
28 | OptionWasIgnored,
29 | } from "./warnings"
30 |
31 | /**
32 | * An implementation of the `EventTarget` interface.
33 | * @see https://dom.spec.whatwg.org/#eventtarget
34 | */
35 | export class EventTarget<
36 | TEventMap extends Record = Record,
37 | TMode extends "standard" | "strict" = "standard"
38 | > {
39 | /**
40 | * Initialize this instance.
41 | */
42 | constructor() {
43 | internalDataMap.set(this, createListenerListMap())
44 | }
45 |
46 | /**
47 | * Add an event listener.
48 | * @param type The event type.
49 | * @param callback The event listener.
50 | * @param options Options.
51 | */
52 | addEventListener(
53 | type: T,
54 | callback?: EventTarget.EventListener | null,
55 | options?: EventTarget.AddOptions,
56 | ): void
57 |
58 | /**
59 | * Add an event listener.
60 | * @param type The event type.
61 | * @param callback The event listener.
62 | * @param options Options.
63 | */
64 | addEventListener(
65 | type: string,
66 | callback?: EventTarget.FallbackEventListener,
67 | options?: EventTarget.AddOptions,
68 | ): void
69 |
70 | /**
71 | * Add an event listener.
72 | * @param type The event type.
73 | * @param callback The event listener.
74 | * @param capture The capture flag.
75 | * @deprecated Use `{capture: boolean}` object instead of a boolean value.
76 | */
77 | addEventListener(
78 | type: T,
79 | callback:
80 | | EventTarget.EventListener
81 | | null
82 | | undefined,
83 | capture: boolean,
84 | ): void
85 |
86 | /**
87 | * Add an event listener.
88 | * @param type The event type.
89 | * @param callback The event listener.
90 | * @param capture The capture flag.
91 | * @deprecated Use `{capture: boolean}` object instead of a boolean value.
92 | */
93 | addEventListener(
94 | type: string,
95 | callback: EventTarget.FallbackEventListener,
96 | capture: boolean,
97 | ): void
98 |
99 | // Implementation
100 | addEventListener(
101 | type0: T,
102 | callback0?: EventTarget.EventListener | null,
103 | options0?: boolean | EventTarget.AddOptions,
104 | ): void {
105 | const listenerMap = $(this)
106 | const {
107 | callback,
108 | capture,
109 | once,
110 | passive,
111 | signal,
112 | type,
113 | } = normalizeAddOptions(type0, callback0, options0)
114 | if (callback == null || signal?.aborted) {
115 | return
116 | }
117 | const list = ensureListenerList(listenerMap, type)
118 |
119 | // Find existing listener.
120 | const i = findIndexOfListener(list, callback, capture)
121 | if (i !== -1) {
122 | warnDuplicate(list.listeners[i], passive, once, signal)
123 | return
124 | }
125 |
126 | // Add the new listener.
127 | addListener(list, callback, capture, passive, once, signal)
128 | }
129 |
130 | /**
131 | * Remove an added event listener.
132 | * @param type The event type.
133 | * @param callback The event listener.
134 | * @param options Options.
135 | */
136 | removeEventListener(
137 | type: T,
138 | callback?: EventTarget.EventListener | null,
139 | options?: EventTarget.Options,
140 | ): void
141 |
142 | /**
143 | * Remove an added event listener.
144 | * @param type The event type.
145 | * @param callback The event listener.
146 | * @param options Options.
147 | */
148 | removeEventListener(
149 | type: string,
150 | callback?: EventTarget.FallbackEventListener,
151 | options?: EventTarget.Options,
152 | ): void
153 |
154 | /**
155 | * Remove an added event listener.
156 | * @param type The event type.
157 | * @param callback The event listener.
158 | * @param capture The capture flag.
159 | * @deprecated Use `{capture: boolean}` object instead of a boolean value.
160 | */
161 | removeEventListener(
162 | type: T,
163 | callback:
164 | | EventTarget.EventListener
165 | | null
166 | | undefined,
167 | capture: boolean,
168 | ): void
169 |
170 | /**
171 | * Remove an added event listener.
172 | * @param type The event type.
173 | * @param callback The event listener.
174 | * @param capture The capture flag.
175 | * @deprecated Use `{capture: boolean}` object instead of a boolean value.
176 | */
177 | removeEventListener(
178 | type: string,
179 | callback: EventTarget.FallbackEventListener,
180 | capture: boolean,
181 | ): void
182 |
183 | // Implementation
184 | removeEventListener(
185 | type0: T,
186 | callback0?: EventTarget.EventListener | null,
187 | options0?: boolean | EventTarget.Options,
188 | ): void {
189 | const listenerMap = $(this)
190 | const { callback, capture, type } = normalizeOptions(
191 | type0,
192 | callback0,
193 | options0,
194 | )
195 | const list = listenerMap[type]
196 |
197 | if (callback != null && list) {
198 | removeListener(list, callback, capture)
199 | }
200 | }
201 |
202 | /**
203 | * Dispatch an event.
204 | * @param event The `Event` object to dispatch.
205 | */
206 | dispatchEvent(
207 | event: EventTarget.EventData,
208 | ): boolean
209 |
210 | /**
211 | * Dispatch an event.
212 | * @param event The `Event` object to dispatch.
213 | */
214 | dispatchEvent(event: EventTarget.FallbackEvent): boolean
215 |
216 | // Implementation
217 | dispatchEvent(
218 | e:
219 | | EventTarget.EventData
220 | | EventTarget.FallbackEvent,
221 | ): boolean {
222 | const list = $(this)[String(e.type)]
223 | if (list == null) {
224 | return true
225 | }
226 |
227 | const event = e instanceof Event ? e : EventWrapper.wrap(e)
228 | const eventData = getEventInternalData(event, "event")
229 | if (eventData.dispatchFlag) {
230 | throw createInvalidStateError("This event has been in dispatching.")
231 | }
232 |
233 | eventData.dispatchFlag = true
234 | eventData.target = eventData.currentTarget = this
235 |
236 | if (!eventData.stopPropagationFlag) {
237 | const { cow, listeners } = list
238 |
239 | // Set copy-on-write flag.
240 | list.cow = true
241 |
242 | // Call listeners.
243 | for (let i = 0; i < listeners.length; ++i) {
244 | const listener = listeners[i]
245 |
246 | // Skip if removed.
247 | if (isRemoved(listener)) {
248 | continue
249 | }
250 |
251 | // Remove this listener if has the `once` flag.
252 | if (isOnce(listener) && removeListenerAt(list, i, !cow)) {
253 | // Because this listener was removed, the next index is the
254 | // same as the current value.
255 | i -= 1
256 | }
257 |
258 | // Call this listener with the `passive` flag.
259 | eventData.inPassiveListenerFlag = isPassive(listener)
260 | invokeCallback(listener, this, event)
261 | eventData.inPassiveListenerFlag = false
262 |
263 | // Stop if the `event.stopImmediatePropagation()` method was called.
264 | if (eventData.stopImmediatePropagationFlag) {
265 | break
266 | }
267 | }
268 |
269 | // Restore copy-on-write flag.
270 | if (!cow) {
271 | list.cow = false
272 | }
273 | }
274 |
275 | eventData.target = null
276 | eventData.currentTarget = null
277 | eventData.stopImmediatePropagationFlag = false
278 | eventData.stopPropagationFlag = false
279 | eventData.dispatchFlag = false
280 |
281 | return !eventData.canceledFlag
282 | }
283 | }
284 |
285 | export namespace EventTarget {
286 | /**
287 | * The event listener.
288 | */
289 | export type EventListener<
290 | TEventTarget extends EventTarget,
291 | TEvent extends Event
292 | > = CallbackFunction | CallbackObject
293 |
294 | /**
295 | * The event listener function.
296 | */
297 | export interface CallbackFunction<
298 | TEventTarget extends EventTarget,
299 | TEvent extends Event
300 | > {
301 | (this: TEventTarget, event: TEvent): void
302 | }
303 |
304 | /**
305 | * The event listener object.
306 | * @see https://dom.spec.whatwg.org/#callbackdef-eventlistener
307 | */
308 | export interface CallbackObject {
309 | handleEvent(event: TEvent): void
310 | }
311 |
312 | /**
313 | * The common options for both `addEventListener` and `removeEventListener` methods.
314 | * @see https://dom.spec.whatwg.org/#dictdef-eventlisteneroptions
315 | */
316 | export interface Options {
317 | capture?: boolean
318 | }
319 |
320 | /**
321 | * The options for the `addEventListener` methods.
322 | * @see https://dom.spec.whatwg.org/#dictdef-addeventlisteneroptions
323 | */
324 | export interface AddOptions extends Options {
325 | passive?: boolean
326 | once?: boolean
327 | signal?: AbortSignal | null | undefined
328 | }
329 |
330 | /**
331 | * The abort signal.
332 | * @see https://dom.spec.whatwg.org/#abortsignal
333 | */
334 | export interface AbortSignal extends EventTarget<{ abort: Event }> {
335 | readonly aborted: boolean
336 | onabort: CallbackFunction | null
337 | }
338 |
339 | /**
340 | * The event data to dispatch in strict mode.
341 | */
342 | export type EventData<
343 | TEventMap extends Record,
344 | TMode extends "standard" | "strict",
345 | TEventType extends string
346 | > = TMode extends "strict"
347 | ? IsValidEventMap extends true
348 | ? ExplicitType &
349 | Omit &
350 | Partial>
351 | : never
352 | : never
353 |
354 | /**
355 | * Define explicit `type` property if `T` is a string literal.
356 | * Otherwise, never.
357 | */
358 | export type ExplicitType = string extends T
359 | ? never
360 | : { readonly type: T }
361 |
362 | /**
363 | * The event listener type in standard mode.
364 | * Otherwise, never.
365 | */
366 | export type FallbackEventListener<
367 | TEventTarget extends EventTarget,
368 | TMode extends "standard" | "strict"
369 | > = TMode extends "standard"
370 | ? EventListener | null | undefined
371 | : never
372 |
373 | /**
374 | * The event type in standard mode.
375 | * Otherwise, never.
376 | */
377 | export type FallbackEvent<
378 | TMode extends "standard" | "strict"
379 | > = TMode extends "standard" ? Event : never
380 |
381 | /**
382 | * Check if given event map is valid.
383 | * It's valid if the keys of the event map are narrower than `string`.
384 | */
385 | export type IsValidEventMap = string extends keyof T ? false : true
386 | }
387 |
388 | export { $ as getEventTargetInternalData }
389 |
390 | //------------------------------------------------------------------------------
391 | // Helpers
392 | //------------------------------------------------------------------------------
393 |
394 | /**
395 | * Internal data for EventTarget
396 | */
397 | type EventTargetInternalData = ListenerListMap
398 |
399 | /**
400 | * Internal data.
401 | */
402 | const internalDataMap = new WeakMap()
403 |
404 | /**
405 | * Get private data.
406 | * @param target The event target object to get private data.
407 | * @param name The variable name to report.
408 | * @returns The private data of the event.
409 | */
410 | function $(target: any, name = "this"): EventTargetInternalData {
411 | const retv = internalDataMap.get(target)
412 | assertType(
413 | retv != null,
414 | "'%s' must be an object that EventTarget constructor created, but got another one: %o",
415 | name,
416 | target,
417 | )
418 | return retv
419 | }
420 |
421 | /**
422 | * Normalize options.
423 | * @param options The options to normalize.
424 | */
425 | function normalizeAddOptions(
426 | type: string,
427 | callback: EventTarget.EventListener | null | undefined,
428 | options: boolean | EventTarget.AddOptions | undefined,
429 | ): {
430 | type: string
431 | callback: EventTarget.EventListener | undefined
432 | capture: boolean
433 | passive: boolean
434 | once: boolean
435 | signal: EventTarget.AbortSignal | undefined
436 | } {
437 | assertCallback(callback)
438 |
439 | if (typeof options === "object" && options !== null) {
440 | return {
441 | type: String(type),
442 | callback: callback ?? undefined,
443 | capture: Boolean(options.capture),
444 | passive: Boolean(options.passive),
445 | once: Boolean(options.once),
446 | signal: options.signal ?? undefined,
447 | }
448 | }
449 |
450 | return {
451 | type: String(type),
452 | callback: callback ?? undefined,
453 | capture: Boolean(options),
454 | passive: false,
455 | once: false,
456 | signal: undefined,
457 | }
458 | }
459 |
460 | /**
461 | * Normalize options.
462 | * @param options The options to normalize.
463 | */
464 | function normalizeOptions(
465 | type: string,
466 | callback: EventTarget.EventListener | null | undefined,
467 | options: boolean | EventTarget.Options | undefined,
468 | ): {
469 | type: string
470 | callback: EventTarget.EventListener | undefined
471 | capture: boolean
472 | } {
473 | assertCallback(callback)
474 |
475 | if (typeof options === "object" && options !== null) {
476 | return {
477 | type: String(type),
478 | callback: callback ?? undefined,
479 | capture: Boolean(options.capture),
480 | }
481 | }
482 |
483 | return {
484 | type: String(type),
485 | callback: callback ?? undefined,
486 | capture: Boolean(options),
487 | }
488 | }
489 |
490 | /**
491 | * Assert the type of 'callback' argument.
492 | * @param callback The callback to check.
493 | */
494 | function assertCallback(callback: any): void {
495 | if (
496 | typeof callback === "function" ||
497 | (typeof callback === "object" &&
498 | callback !== null &&
499 | typeof callback.handleEvent === "function")
500 | ) {
501 | return
502 | }
503 | if (callback == null || typeof callback === "object") {
504 | InvalidEventListener.warn(callback)
505 | return
506 | }
507 |
508 | throw new TypeError(format(InvalidEventListener.message, [callback]))
509 | }
510 |
511 | /**
512 | * Print warning for duplicated.
513 | * @param listener The current listener that is duplicated.
514 | * @param passive The passive flag of the new duplicated listener.
515 | * @param once The once flag of the new duplicated listener.
516 | * @param signal The signal object of the new duplicated listener.
517 | */
518 | function warnDuplicate(
519 | listener: Listener,
520 | passive: boolean,
521 | once: boolean,
522 | signal: EventTarget.AbortSignal | undefined,
523 | ): void {
524 | EventListenerWasDuplicated.warn(
525 | isCapture(listener) ? "capture" : "bubble",
526 | listener.callback,
527 | )
528 |
529 | if (isPassive(listener) !== passive) {
530 | OptionWasIgnored.warn("passive")
531 | }
532 | if (isOnce(listener) !== once) {
533 | OptionWasIgnored.warn("once")
534 | }
535 | if (listener.signal !== signal) {
536 | OptionWasIgnored.warn("signal")
537 | }
538 | }
539 |
540 | // Set enumerable
541 | const keys = Object.getOwnPropertyNames(EventTarget.prototype)
542 | for (let i = 0; i < keys.length; ++i) {
543 | if (keys[i] === "constructor") {
544 | continue
545 | }
546 | Object.defineProperty(EventTarget.prototype, keys[i], { enumerable: true })
547 | }
548 |
549 | // Ensure `eventTarget instanceof window.EventTarget` is `true`.
550 | if (
551 | typeof Global !== "undefined" &&
552 | typeof Global.EventTarget !== "undefined"
553 | ) {
554 | Object.setPrototypeOf(EventTarget.prototype, Global.EventTarget.prototype)
555 | }
556 |
--------------------------------------------------------------------------------
/src/lib/event-wrapper.ts:
--------------------------------------------------------------------------------
1 | import { Event } from "./event"
2 | import { Global } from "./global"
3 | import { assertType } from "./misc"
4 |
5 | /**
6 | * An implementation of `Event` interface, that wraps a given event object.
7 | * This class controls the internal state of `Event`.
8 | * @see https://dom.spec.whatwg.org/#interface-event
9 | */
10 | export class EventWrapper extends Event {
11 | /**
12 | * Wrap a given event object to control states.
13 | * @param event The event-like object to wrap.
14 | */
15 | static wrap(event: T): EventWrapperOf {
16 | return new (getWrapperClassOf(event))(event)
17 | }
18 |
19 | protected constructor(event: Event) {
20 | super(event.type, {
21 | bubbles: event.bubbles,
22 | cancelable: event.cancelable,
23 | composed: event.composed,
24 | })
25 |
26 | if (event.cancelBubble) {
27 | super.stopPropagation()
28 | }
29 | if (event.defaultPrevented) {
30 | super.preventDefault()
31 | }
32 |
33 | internalDataMap.set(this, { original: event })
34 |
35 | // Define accessors
36 | const keys = Object.keys(event)
37 | for (let i = 0; i < keys.length; ++i) {
38 | const key = keys[i]
39 | if (!(key in this)) {
40 | Object.defineProperty(
41 | this,
42 | key,
43 | defineRedirectDescriptor(event, key),
44 | )
45 | }
46 | }
47 | }
48 |
49 | stopPropagation(): void {
50 | super.stopPropagation()
51 |
52 | const { original } = $(this)
53 | if ("stopPropagation" in original) {
54 | original.stopPropagation!()
55 | }
56 | }
57 |
58 | get cancelBubble(): boolean {
59 | return super.cancelBubble
60 | }
61 | set cancelBubble(value: boolean) {
62 | super.cancelBubble = value
63 |
64 | const { original } = $(this)
65 | if ("cancelBubble" in original) {
66 | original.cancelBubble = value
67 | }
68 | }
69 |
70 | stopImmediatePropagation(): void {
71 | super.stopImmediatePropagation()
72 |
73 | const { original } = $(this)
74 | if ("stopImmediatePropagation" in original) {
75 | original.stopImmediatePropagation!()
76 | }
77 | }
78 |
79 | get returnValue(): boolean {
80 | return super.returnValue
81 | }
82 | set returnValue(value: boolean) {
83 | super.returnValue = value
84 |
85 | const { original } = $(this)
86 | if ("returnValue" in original) {
87 | original.returnValue = value
88 | }
89 | }
90 |
91 | preventDefault(): void {
92 | super.preventDefault()
93 |
94 | const { original } = $(this)
95 | if ("preventDefault" in original) {
96 | original.preventDefault!()
97 | }
98 | }
99 |
100 | get timeStamp(): number {
101 | const { original } = $(this)
102 | if ("timeStamp" in original) {
103 | return original.timeStamp!
104 | }
105 | return super.timeStamp
106 | }
107 | }
108 |
109 | //------------------------------------------------------------------------------
110 | // Helpers
111 | //------------------------------------------------------------------------------
112 |
113 | type EventLike = { readonly type: string } & Partial
114 | type EventWrapperOf = Event &
115 | Omit
116 |
117 | interface EventWrapperInternalData {
118 | readonly original: EventLike
119 | }
120 |
121 | /**
122 | * Private data for event wrappers.
123 | */
124 | const internalDataMap = new WeakMap()
125 |
126 | /**
127 | * Get private data.
128 | * @param event The event object to get private data.
129 | * @returns The private data of the event.
130 | */
131 | function $(event: unknown): EventWrapperInternalData {
132 | const retv = internalDataMap.get(event)
133 | assertType(
134 | retv != null,
135 | "'this' is expected an Event object, but got",
136 | event,
137 | )
138 | return retv
139 | }
140 |
141 | /**
142 | * Cache for wrapper classes.
143 | * @type {WeakMap