Skip to content

Commit 322eb3d

Browse files
committed
feat: introduce AbstractEvent and upgrade to node 20.x
1 parent 1c8f31e commit 322eb3d

10 files changed

+112
-12
lines changed

.eslintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"message": "Use `globalThis` instead"
4040
}
4141
],
42+
"prefer-rest-params": 0,
4243
"require-yield": 0,
4344
"eqeqeq": ["error", "smart"],
4445
"spaced-comment": [

package-lock.json

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,8 @@
4747
"tsconfig-paths": "^3.9.0",
4848
"typedoc": "^0.23.21",
4949
"typescript": "^4.9.3"
50+
},
51+
"engines": {
52+
"node": ">=19.0.0"
5053
}
5154
}

pkgs.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import (
2-
let rev = "f294325aed382b66c7a188482101b0f336d1d7db"; in
2+
let rev = "ea5234e7073d5f44728c499192544a84244bf35a"; in
33
builtins.fetchTarball "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz"
44
)

shell.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
with pkgs;
44
mkShell {
55
nativeBuildInputs = [
6-
nodejs
6+
nodejs_20
77
shellcheck
88
gitAndTools.gh
99
];

src/AbstractEvent.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* By default the `detail` property of `CustomEvent` is `null`.
3+
* So the default type has to be `null` too.
4+
*/
5+
class AbstractEvent<T = null> extends CustomEvent<T> {
6+
protected constructorParams: IArguments;
7+
8+
public constructor(
9+
type?: string,
10+
options?: CustomEventInit<T>,
11+
constructorParams?: IArguments,
12+
);
13+
public constructor(
14+
options: CustomEventInit<T>,
15+
constructorParams?: IArguments,
16+
);
17+
public constructor(
18+
type: string | CustomEventInit<T> = new.target.name,
19+
options?: CustomEventInit<T> | IArguments,
20+
constructorParams?: IArguments,
21+
) {
22+
if (typeof type === 'string') {
23+
super(type, options as CustomEventInit<T> | undefined);
24+
} else {
25+
super(new.target.name, type);
26+
constructorParams = options as IArguments | undefined;
27+
}
28+
this.constructorParams = constructorParams ?? arguments;
29+
}
30+
31+
/**
32+
* Events cannot be re-dispatched. This was probably to prevent infinite
33+
* loops. So instead of re-dispatching the same instance, we re-dispatch
34+
* a clone of the instance.
35+
*/
36+
public clone(): this {
37+
try {
38+
return new (this.constructor as any)(...this.constructorParams);
39+
} catch (e) {
40+
// Normally we would check `instanceof TypeError`
41+
// However jest appears to choke on that
42+
if (e.name === 'TypeError') {
43+
throw new TypeError(
44+
`Cloning ${this.constructor.name} requires the original constructor arguments to be passed into super`,
45+
);
46+
}
47+
throw e;
48+
}
49+
}
50+
}
51+
52+
export default AbstractEvent;

src/EventAny.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
// @ts-ignore package.json is outside rootDir
1+
import AbstractEvent from './AbstractEvent';
2+
// @ts-ignore package.json is outside root dir
23
import { name } from '../package.json';
34

4-
class EventAny extends Event {
5+
class EventAny extends AbstractEvent<Event> {
56
public static type = `${name}/${this.name}`;
6-
public detail: Event;
7-
constructor(options: EventInit & { detail: Event }) {
8-
super(EventAny.type, options);
9-
this.detail = options.detail;
7+
public constructor(options: EventInit & { detail: Event }) {
8+
super(EventAny.type, options, arguments);
109
}
1110
}
1211

src/Evented.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ interface Evented {
1111
callback: EventListenerOrEventListenerObject | null,
1212
options?: AddEventListenerOptions | boolean,
1313
): void;
14-
dispatchEvent(event: Event): boolean;
1514
removeEventListener(
1615
callback: EventListenerOrEventListenerObject | null,
1716
options?: EventListenerOptions | boolean,
@@ -21,6 +20,7 @@ interface Evented {
2120
callback: EventListenerOrEventListenerObject | null,
2221
options?: EventListenerOptions | boolean,
2322
): void;
23+
dispatchEvent(event: Event): boolean;
2424
}
2525

2626
function Evented() {

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
export { Evented } from './Evented';
1+
export { default as AbstractEvent } from './AbstractEvent';
22
export { default as EventAny } from './EventAny';
3+
export { Evented } from './Evented';
34
export * as utils from './utils';

tests/index.test.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,47 @@
1-
import { Evented, EventAny, utils } from '@';
1+
import { AbstractEvent, EventAny, Evented, utils } from '@';
22

3-
describe('Evented', () => {
3+
describe('index', () => {
4+
test('abstract event subclasses', () => {
5+
class Event0 extends AbstractEvent {}
6+
class Event1<T> extends AbstractEvent<T> {}
7+
class Event2 extends AbstractEvent<string> {}
8+
class Event3 extends AbstractEvent<object> {}
9+
const e0 = new Event0();
10+
expect(e0.detail).toBeNull();
11+
const e1 = new Event1();
12+
expect(e1.detail).toBeNull();
13+
const e1_ = new Event1({ detail: 'string' });
14+
expect(e1_.detail).toBe('string');
15+
// This is not caught by the type system
16+
const e2 = new Event2();
17+
expect(e2.detail).toBeNull();
18+
const e2_ = new Event2({ detail: 'string' });
19+
expect(e2_.detail).toBe('string');
20+
const e11 = e1.clone();
21+
expect(e11.type).toBe(e1.type);
22+
expect(e11.detail).toBe(e1.detail);
23+
const e22 = e2.clone();
24+
expect(e22.type).toBe(e2.type);
25+
expect(e22.detail).toBe(e2.detail);
26+
const e3 = new Event3({ detail: { a: 1 } });
27+
expect(e3.detail).toBe(e3.clone().detail);
28+
});
29+
test('abstract event subclasses with different constructors', () => {
30+
class EventCustom1 extends AbstractEvent<string> {
31+
constructor(options: CustomEventInit<string>) {
32+
super(EventCustom1.name, options);
33+
}
34+
}
35+
class EventCustom2 extends AbstractEvent<string> {
36+
constructor(options: CustomEventInit<string>) {
37+
super(EventCustom2.name, options, arguments);
38+
}
39+
}
40+
const e1 = new EventCustom1({ detail: 'string' });
41+
expect(() => e1.clone()).toThrow(TypeError);
42+
const e2 = new EventCustom2({ detail: 'string' });
43+
expect(() => e2.clone()).not.toThrowError();
44+
});
445
test('can access event target via symbol', () => {
546
interface X extends Evented {}
647
@Evented()

0 commit comments

Comments
 (0)