diff --git a/.changeset/early-squids-boil.md b/.changeset/early-squids-boil.md new file mode 100644 index 0000000..765bb4a --- /dev/null +++ b/.changeset/early-squids-boil.md @@ -0,0 +1,5 @@ +--- +'emitten': minor +--- + +Make all members of EmittenProtected protected. diff --git a/.eslintrc b/.eslintrc index c29436c..7883d7e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -13,6 +13,7 @@ "rules": { "prettier/prettier": ["error"], "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/method-signature-style": "off", "@typescript-eslint/no-dynamic-delete": "off" } } diff --git a/docs/examples.md b/docs/examples.md index 00738cf..5095352 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -6,7 +6,7 @@ Below are some common use-cases for `Emitten` and `EmittenProtected`. The easiest way to use `Emitten` is to simply instantiate it and begin wiring up your logic. -For this use case, you most likely want to use `Emitten` instead of `EmittenProtected`. If you instantiate using `EmittenProtected`, you will not be able to call any `protected` members, such as `.emit()` or `.empty()`. +For this use case, you most likely want to use `Emitten` instead of `EmittenProtected`. If you instantiate using `EmittenProtected`, you will not be able to call any `protected` members. ```ts import {Emitten} from 'emitten'; @@ -50,7 +50,7 @@ myEvents.on('count', (value) => console.log('2nd count listener', value)); // Alternatively, you can register a listener using // `.disposable()`, which will return the corresponding // `.off()` method to make removal easier. -const registered = myEvents.on('count', (value) => +const registered = myEvents.disposable('count', (value) => console.log('An anonymous function', value), ); @@ -100,13 +100,24 @@ interface ExtendedEventMap { } class ExtendedEmitten extends EmittenProtected { - constructor() { - // `ExtendedEmitten` now has all of the `properties`, - // `accessors`, and `methods` from `EmittenProtected`. - super(); + // If required, you can selectively expose any `protected` members. + // Otherwise, if you want all members to be `public`, you can + // extend the `Emitten` class instead. + + public on( + eventName: TKey, + listener: EmittenListener, + ) { + super.on(eventName, listener); + } + + public off( + eventName: TKey, + listener: EmittenListener, + ) { + super.off(eventName, listener); } - // If required, you can expose any `protected` members. public emit( eventName: TKey, value?: ExtendedEventMap[TKey], @@ -121,13 +132,14 @@ class ExtendedEmitten extends EmittenProtected { const extended = new ExtendedEmitten(); -extended.on('custom', (value) => console.log('value', value)); -extended.report(); +// Since we converted both `.on()` and `.emit()` to be `public`, +// we can safely call them on the instance. -// Since we converted `.emit()` to a `public` member, -// we can safely call this on the instance. +extended.on('custom', (value) => console.log('value', value)); extended.emit('custom', 'hello'); +extended.report(); + // However, we did not expose `.empty()`, so we will // receive a TypeScript error attempting to call this. extended.empty(); @@ -138,6 +150,10 @@ extended.empty(); We can of course create classes that do not extend `Emitten`, and instead create a `private` instance of `Emitten` to perform event actions on. ```ts +function assertValue(value?: string): value is string { + return Boolean(value?.length); +} + class AnotherExample { #id = 'DefaultId'; #counter = 0; @@ -153,7 +169,7 @@ class AnotherExample { constructor() { this.#handleChange = (value) => { - this.#id = value?.length ? value : this.#id; + this.#id = assertValue(value) ? value : this.#id; console.log('#handleChange', value); }; diff --git a/docs/future.md b/docs/future.md index b89529b..4e86edb 100644 --- a/docs/future.md +++ b/docs/future.md @@ -44,35 +44,6 @@ function emit( This will require some experimentation. -## Private iterator - -It would be nice to have a `every()` method to iterate over all `listeners` for each `event` within a `library`. - -This isn’t so please though, as `.bind(this)` ends up being required when calling `.empty()`. - -```ts -protected empty() { - this.#every(this.#multiLibrary, this.off.bind(this)); - this.#every(this.#singleLibrary, this.off.bind(this)); -} - -#every( - library: EmittenLibrary, - callback: ( - eventName: TKey, - listener: EmittenListener, - ) => void, -) { - for (const eventName in library) { - if (Object.prototype.hasOwnProperty.call(library, eventName)) { - library[eventName]?.forEach((listener) => - callback(eventName, listener), - ); - } - } -} -``` - ## No dynamic delete I got sloppy and used the `delete` keyword... I need to remove the `@typescript-eslint/no-dynamic-delete` override and filter that `object` properly. diff --git a/src/Emitten.ts b/src/Emitten.ts index 47bc4c7..b9390dc 100644 --- a/src/Emitten.ts +++ b/src/Emitten.ts @@ -1,7 +1,44 @@ import {EmittenProtected} from './EmittenProtected'; +import type {EmittenListener} from './types'; -export class Emitten extends EmittenProtected { - public emit(eventName: TKey, value?: T[TKey]) { +export class Emitten extends EmittenProtected { + public get activeEvents() { + return super.activeEvents; + } + + public off( + eventName: TKey, + listener: EmittenListener, + ) { + super.off(eventName, listener); + } + + public on( + eventName: TKey, + listener: EmittenListener, + ) { + super.on(eventName, listener); + } + + public once( + eventName: TKey, + listener: EmittenListener, + ) { + super.once(eventName, listener); + } + + public disposable( + eventName: TKey, + listener: EmittenListener, + ) { + const result = super.disposable(eventName, listener); + return result; + } + + public emit( + eventName: TKey, + value?: TEventMap[TKey], + ) { super.emit(eventName, value); } diff --git a/src/EmittenProtected.ts b/src/EmittenProtected.ts index 2ded75e..2e1d8f5 100644 --- a/src/EmittenProtected.ts +++ b/src/EmittenProtected.ts @@ -1,10 +1,10 @@ import type {EmittenListener, EmittenLibrary} from './types'; -export class EmittenProtected { - #multiLibrary: EmittenLibrary = {}; - #singleLibrary: EmittenLibrary = {}; +export class EmittenProtected { + #multiLibrary: EmittenLibrary = {}; + #singleLibrary: EmittenLibrary = {}; - get activeEvents() { + protected get activeEvents() { const multiKeys = Object.keys(this.#multiLibrary); const singleKeys = Object.keys(this.#singleLibrary); @@ -13,9 +13,9 @@ export class EmittenProtected { return [...dedupedKeys]; } - off( + protected off( eventName: TKey, - listener: EmittenListener, + listener: EmittenListener, ) { const multiSet = this.#multiLibrary[eventName]; const singleSet = this.#singleLibrary[eventName]; @@ -31,9 +31,9 @@ export class EmittenProtected { } } - on( + protected on( eventName: TKey, - listener: EmittenListener, + listener: EmittenListener, ) { if (this.#multiLibrary[eventName] == null) { this.#multiLibrary[eventName] = new Set(); @@ -42,9 +42,9 @@ export class EmittenProtected { this.#multiLibrary[eventName]?.add(listener); } - once( + protected once( eventName: TKey, - listener: EmittenListener, + listener: EmittenListener, ) { if (this.#singleLibrary[eventName] == null) { this.#singleLibrary[eventName] = new Set(); @@ -53,17 +53,21 @@ export class EmittenProtected { this.#singleLibrary[eventName]?.add(listener); } - disposable( + protected disposable( eventName: TKey, - listener: EmittenListener, + listener: EmittenListener, ) { this.on(eventName, listener); + return () => { this.off(eventName, listener); }; } - protected emit(eventName: TKey, value?: T[TKey]) { + protected emit( + eventName: TKey, + value?: TEventMap[TKey], + ) { const multiSet = this.#multiLibrary[eventName]; const singleSet = this.#singleLibrary[eventName]; @@ -74,21 +78,22 @@ export class EmittenProtected { singleSet?.forEach((listener) => { listener(value); }); + delete this.#singleLibrary[eventName]; } protected empty() { - const every = (library: EmittenLibrary) => { - for (const eventName in library) { - if (Object.hasOwn(library, eventName)) { - library[eventName]?.forEach((listener) => { - this.off(eventName, listener); - }); - } - } - }; - - every(this.#multiLibrary); - every(this.#singleLibrary); + this.#every(this.#multiLibrary); + this.#every(this.#singleLibrary); } + + #every = (library: EmittenLibrary) => { + for (const eventName in library) { + if (Object.hasOwn(library, eventName)) { + library[eventName]?.forEach((listener) => { + this.off(eventName, listener); + }); + } + } + }; } diff --git a/src/types.ts b/src/types.ts index d4efbc5..525a399 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,5 @@ -export type EmittenListener = (value?: T) => void; +export type EmittenListener = (value?: TValue) => void; -export type EmittenLibrary = { - [eventName in keyof T]?: Set>; +export type EmittenLibrary = { + [event in keyof TEventMap]?: Set>; };