aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Miller <[email protected]>2021-06-22 17:56:54 -0400
committerGitHub <[email protected]>2021-06-22 17:56:54 -0400
commit53eb689c83bca78d9275f724995406495262c333 (patch)
tree7d2272705a45b75a7171c61f9233fe1e69482d18
parentec9122bba89372f521da8c2ae4febb40ded3c570 (diff)
parentf0f36b09df3eaac16792b6840bdb594301ec3d8f (diff)
Merge branch 'master' into patch_123
-rw-r--r--.gitignore1
-rw-r--r--LICENSE21
-rw-r--r--README.md18
-rw-r--r--package.json10
-rw-r--r--src/index.ts82
-rw-r--r--test/index_test.ts25
-rw-r--r--test/test-types-compilation.ts75
-rw-r--r--tsconfig.json6
8 files changed, 169 insertions, 69 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/LICENSE b/LICENSE
new file mode 100644
index 0000000..84c52fe
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Jason Miller
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 9bc0436..dff9914 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
@@ -136,15 +144,15 @@ If omit the `handler`, all event handlers of the given type are deleted.
#### 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..666ffe7 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",
@@ -34,7 +34,6 @@
],
"license": "MIT",
"files": [
- "src",
"dist",
"index.d.ts"
],
@@ -78,7 +77,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 +104,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 946802a..4648156 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,27 +53,27 @@ 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.
- * If omit the `handler`, all event handlers of the given type are deleted.
- * @param {string|symbol} type Type of event to unregister `handler` from, or `"*"`
- * @param {Function} handler Handler function to remove
+ * If `handler` is omitted, all handlers of the given type are removed.
+ * @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) {
if (handler) {
handlers.splice(handlers.indexOf(handler) >>> 0, 1);
@@ -75,17 +86,32 @@ 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]) {
+ let handlers = all!.get(type);
+ if (handlers) {
+ (handlers as EventHandlerList<Events[keyof Events]>)
+ .slice()
+ .map((handler) => {
+ handler(evt!);
+ });
+ }
+
+ handlers = all!.get('*');
+ if (handlers) {
+ (handlers 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"]
}