aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README.md18
-rw-r--r--package.json9
-rw-r--r--src/index.ts71
-rw-r--r--test/index_test.ts25
-rw-r--r--test/test-types-compilation.ts75
-rw-r--r--tsconfig.json6
7 files changed, 139 insertions, 66 deletions
diff --git a/.gitignore b/.gitignore
index eb570d3..bf29d4f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@
package-lock.json
.DS_Store
.idea
+.vscode
diff --git a/README.md b/README.md
index 08a21bf..0fe8dc4 100644
--- a/README.md
+++ b/README.md
@@ -80,9 +80,17 @@ emitter.off('foo', onFoo) // unlisten
### Typescript
+Set `"strict": true` in your tsconfig.json to get improved type inference for `mitt` instance methods.
+
```ts
import mitt from 'mitt';
-const emitter: mitt.Emitter = mitt();
+
+type Events = {
+ foo: string
+ bar?: number
+}
+
+const emitter: mitt.Emitter<Events> = mitt<Events>();
```
## Examples & Demos
@@ -126,7 +134,7 @@ Register an event handler for the given type.
#### Parameters
-- `type` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [symbol](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol))** Type of event to listen for, or `"*"` for all events
+- `type` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [symbol](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol))** Type of event to listen for, or `'*'` for all events
- `handler` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** Function to call in response to given event
### off
@@ -135,15 +143,15 @@ Remove an event handler for the given type.
#### Parameters
-- `type` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [symbol](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol))** Type of event to unregister `handler` from, or `"*"`
+- `type` **([string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [symbol](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol))** Type of event to unregister `handler` from, or `'*'`
- `handler` **[Function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function)** Handler function to remove
### emit
Invoke all handlers for the given type.
-If present, `"*"` handlers are invoked after type-matched handlers.
+If present, `'*'` handlers are invoked after type-matched handlers.
-Note: Manually firing "\*" handlers is not supported.
+Note: Manually firing '\*' handlers is not supported.
#### Parameters
diff --git a/package.json b/package.json
index 51b5cf8..b34f8e9 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,7 @@
"scripts": {
"test": "npm-run-all --silent typecheck lint mocha test-types",
"mocha": "mocha test",
- "test-types": "tsc test/test-types-compilation.ts --noEmit",
+ "test-types": "tsc test/test-types-compilation.ts --noEmit --strict",
"lint": "eslint src test --ext ts --ext js",
"typecheck": "tsc --noEmit",
"bundle": "microbundle",
@@ -78,7 +78,8 @@
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
- "@typescript-eslint/no-empty-function": 0
+ "@typescript-eslint/no-empty-function": 0,
+ "@typescript-eslint/no-non-null-assertion": 0
}
},
"eslintIgnore": [
@@ -104,6 +105,6 @@
"sinon": "^9.0.2",
"sinon-chai": "^3.5.0",
"ts-node": "^8.10.2",
- "typescript": "^3.9.3"
+ "typescript": "^3.9.7"
}
-} \ No newline at end of file
+}
diff --git a/src/index.ts b/src/index.ts
index ae85607..00286bc 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -2,27 +2,33 @@ export type EventType = string | symbol;
// An event handler can take an optional event argument
// and should not return a value
-export type Handler<T = any> = (event?: T) => void;
-export type WildcardHandler = (type: EventType, event?: any) => void;
+export type Handler<T = unknown> = (event: T) => void;
+export type WildcardHandler<T = Record<string, unknown>> = (
+ type: keyof T,
+ event: T[keyof T]
+) => void;
// An array of all currently registered event handlers for a type
-export type EventHandlerList = Array<Handler>;
-export type WildCardEventHandlerList = Array<WildcardHandler>;
+export type EventHandlerList<T = unknown> = Array<Handler<T>>;
+export type WildCardEventHandlerList<T = Record<string, unknown>> = Array<WildcardHandler<T>>;
// A map of event types and their corresponding event handlers.
-export type EventHandlerMap = Map<EventType, EventHandlerList | WildCardEventHandlerList>;
+export type EventHandlerMap<Events extends Record<EventType, unknown>> = Map<
+ keyof Events | '*',
+ EventHandlerList<Events[keyof Events]> | WildCardEventHandlerList<Events>
+>;
-export interface Emitter {
- all: EventHandlerMap;
+export interface Emitter<Events extends Record<EventType, unknown>> {
+ all: EventHandlerMap<Events>;
- on<T = any>(type: EventType, handler: Handler<T>): void;
- on(type: '*', handler: WildcardHandler): void;
+ on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): void;
+ on(type: '*', handler: WildcardHandler<Events>): void;
- off<T = any>(type: EventType, handler: Handler<T>): void;
- off(type: '*', handler: WildcardHandler): void;
+ off<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): void;
+ off(type: '*', handler: WildcardHandler<Events>): void;
- emit<T = any>(type: EventType, event?: T): void;
- emit(type: '*', event?: any): void;
+ emit<Key extends keyof Events>(type: Key, event: Events[Key]): void;
+ emit<Key extends keyof Events>(type: undefined extends Events[Key] ? Key : never): void;
}
/**
@@ -30,7 +36,12 @@ export interface Emitter {
* @name mitt
* @returns {Mitt}
*/
-export default function mitt(all?: EventHandlerMap): Emitter {
+export default function mitt<Events extends Record<EventType, unknown>>(
+ all?: EventHandlerMap<Events>
+): Emitter<Events> {
+ type GenericEventHandler =
+ | Handler<Events[keyof Events]>
+ | WildcardHandler<Events>;
all = all || new Map();
return {
@@ -42,26 +53,26 @@ export default function mitt(all?: EventHandlerMap): Emitter {
/**
* Register an event handler for the given type.
- * @param {string|symbol} type Type of event to listen for, or `"*"` for all events
+ * @param {string|symbol} type Type of event to listen for, or `'*'` for all events
* @param {Function} handler Function to call in response to given event
* @memberOf mitt
*/
- on<T = any>(type: EventType, handler: Handler<T>) {
- const handlers = all.get(type);
+ on<Key extends keyof Events>(type: Key, handler: GenericEventHandler) {
+ const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
const added = handlers && handlers.push(handler);
if (!added) {
- all.set(type, [handler]);
+ all!.set(type, [handler] as EventHandlerList<Events[keyof Events]>);
}
},
/**
* Remove an event handler for the given type.
- * @param {string|symbol} type Type of event to unregister `handler` from, or `"*"`
+ * @param {string|symbol} type Type of event to unregister `handler` from, or `'*'`
* @param {Function} handler Handler function to remove
* @memberOf mitt
*/
- off<T = any>(type: EventType, handler: Handler<T>) {
- const handlers = all.get(type);
+ off<Key extends keyof Events>(type: Key, handler: GenericEventHandler) {
+ const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
if (handlers) {
handlers.splice(handlers.indexOf(handler) >>> 0, 1);
}
@@ -69,17 +80,25 @@ export default function mitt(all?: EventHandlerMap): Emitter {
/**
* Invoke all handlers for the given type.
- * If present, `"*"` handlers are invoked after type-matched handlers.
+ * If present, `'*'` handlers are invoked after type-matched handlers.
*
- * Note: Manually firing "*" handlers is not supported.
+ * Note: Manually firing '*' handlers is not supported.
*
* @param {string|symbol} type The event type to invoke
* @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler
* @memberOf mitt
*/
- emit<T = any>(type: EventType, evt: T) {
- ((all.get(type) || []) as EventHandlerList).slice().map((handler) => { handler(evt); });
- ((all.get('*') || []) as WildCardEventHandlerList).slice().map((handler) => { handler(type, evt); });
+ emit<Key extends keyof Events>(type: Key, evt?: Events[Key]) {
+ ((all!.get(type) || []) as EventHandlerList<Events[keyof Events]>)
+ .slice()
+ .map((handler) => {
+ handler(evt!);
+ });
+ ((all!.get('*') || []) as WildCardEventHandlerList<Events>)
+ .slice()
+ .map((handler) => {
+ handler(type, evt!);
+ });
}
};
}
diff --git a/test/index_test.ts b/test/index_test.ts
index 5c25b55..ba1dbff 100644
--- a/test/index_test.ts
+++ b/test/index_test.ts
@@ -1,4 +1,4 @@
-import mitt, { Emitter } from '..';
+import mitt, { Emitter, EventHandlerMap } from '..';
import chai, { expect } from 'chai';
import { spy } from 'sinon';
import sinonChai from 'sinon-chai';
@@ -15,7 +15,7 @@ describe('mitt', () => {
const a = spy();
const b = spy();
map.set('foo', [a, b]);
- const events = mitt(map);
+ const events = mitt<{ foo: undefined }>(map);
events.emit('foo');
expect(a).to.have.been.calledOnce;
expect(b).to.have.been.calledOnce;
@@ -23,9 +23,21 @@ describe('mitt', () => {
});
describe('mitt#', () => {
- let events, inst: Emitter;
-
- beforeEach( () => {
+ const eventType = Symbol('eventType');
+ type Events = {
+ foo: unknown;
+ constructor: unknown;
+ FOO: unknown;
+ bar: unknown;
+ Bar: unknown;
+ 'baz:bat!': unknown;
+ 'baz:baT!': unknown;
+ Foo: unknown;
+ [eventType]: unknown;
+ };
+ let events: EventHandlerMap<Events>, inst: Emitter<Events>;
+
+ beforeEach(() => {
events = new Map();
inst = mitt(events);
});
@@ -83,7 +95,6 @@ describe('mitt#', () => {
it('can take symbols for event types', () => {
const foo = () => {};
- const eventType = Symbol('eventType');
inst.on(eventType, foo);
expect(events.get(eventType)).to.deep.equal([foo]);
});
@@ -151,7 +162,7 @@ describe('mitt#', () => {
it('should invoke handler for type', () => {
const event = { a: 'b' };
- inst.on('foo', (one, two?) => {
+ inst.on('foo', (one, two?: unknown) => {
expect(one).to.deep.equal(event);
expect(two).to.be.an('undefined');
});
diff --git a/test/test-types-compilation.ts b/test/test-types-compilation.ts
index 00510da..476e631 100644
--- a/test/test-types-compilation.ts
+++ b/test/test-types-compilation.ts
@@ -2,42 +2,77 @@
import mitt from '..';
-const emitter = mitt();
+interface SomeEventData {
+ name: string;
+}
+
+const emitter = mitt<{
+ foo: string;
+ someEvent: SomeEventData;
+ bar?: number;
+}>();
+
+const barHandler = (x?: number) => {};
+const fooHandler = (x: string) => {};
+const wildcardHandler = (
+ _type: 'foo' | 'bar' | 'someEvent',
+ _event: string | SomeEventData | number | undefined
+) => {};
/*
- * Check that if on is provided a generic, it only accepts handlers of that type
+ * Check that 'on' args are inferred correctly
*/
{
- const badHandler = (x: number) => {};
- const goodHandler = (x: string) => {};
+ // @ts-expect-error
+ emitter.on('foo', barHandler);
+ emitter.on('foo', fooHandler);
+ emitter.on('bar', barHandler);
// @ts-expect-error
- emitter.on<string>('foo', badHandler);
- emitter.on<string>('foo', goodHandler);
+ emitter.on('bar', fooHandler);
+
+ emitter.on('*', wildcardHandler);
+ // fooHandler is ok, because ('foo' | 'bar' | 'someEvent') extends string
+ emitter.on('*', fooHandler);
+ // @ts-expect-error
+ emitter.on('*', barHandler);
}
/*
- * Check that if off is provided a generic, it only accepts handlers of that type
+ * Check that 'off' args are inferred correctly
*/
{
- const badHandler = (x: number) => {};
- const goodHandler = (x: string) => {};
+ // @ts-expect-error
+ emitter.off('foo', barHandler);
+ emitter.off('foo', fooHandler);
+ emitter.off('bar', barHandler);
// @ts-expect-error
- emitter.off<string>('foo', badHandler);
- emitter.off<string>('foo', goodHandler);
-}
+ emitter.off('bar', fooHandler);
+ emitter.off('*', wildcardHandler);
+ // fooHandler is ok, because ('foo' | 'bar' | 'someEvent') extends string
+ emitter.off('*', fooHandler);
+ // @ts-expect-error
+ emitter.off('*', barHandler);
+}
/*
- * Check that if emitt is provided a generic, it only accepts event data of that type
+ * Check that 'emit' args are inferred correctly
*/
{
- interface SomeEventData {
- name: string;
- }
- // @ts-expect-error
- emitter.emit<SomeEventData>('foo', 'NOT VALID');
- emitter.emit<SomeEventData>('foo', { name: 'jack' });
-}
+ // @ts-expect-error
+ emitter.emit('someEvent', 'NOT VALID');
+ emitter.emit('someEvent', { name: 'jack' });
+
+ // @ts-expect-error
+ emitter.emit('foo');
+ // @ts-expect-error
+ emitter.emit('foo', 1);
+ emitter.emit('foo', 'string');
+ emitter.emit('bar');
+ emitter.emit('bar', 1);
+ // @ts-expect-error
+ emitter.emit('bar', 'string');
+}
diff --git a/tsconfig.json b/tsconfig.json
index acab4f5..563d58e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,13 +1,11 @@
{
"compileOnSave": false,
"compilerOptions": {
+ "strict": true,
"noEmit": true,
"declaration": true,
"moduleResolution": "node",
"esModuleInterop": true
},
- "include": [
- "src/*.ts",
- "test/*.ts",
- ]
+ "include": ["src/*.ts", "test/*.ts"]
}