UNPKG

@byloth/core

Version:

An unopinionated collection of useful functions and classes that I use widely in all my projects. 🔧

214 lines (196 loc) • 6.1 kB
import Publisher from "../callbacks/publisher.js"; import type { Subscribable } from "../types.js"; // eslint-disable-next-line @typescript-eslint/no-unused-vars import type SetView from "./set-view.js"; import type { MapViewEventsMap } from "./types.js"; /** * A wrapper class around the native {@link Map} class that provides additional functionality * for publishing events when entries are added, removed or the collection is cleared. * There's also a complementary class that works with the native `Set` class. * See also {@link SetView}. * * --- * * @example * ```ts * const map = new MapView<string, number>(); * * map.subscribe("entry:add", (key: string, value: number) => console.log(`Added ${key}: ${value}`)); * map.set("answer", 42); // "Added answer: 42" * ``` * * --- * * @template K The type of the keys in the map. * @template V The type of the values in the map. */ export default class MapView<K, V> extends Map<K, V> implements Subscribable<MapViewEventsMap<K, V>> { /** * The internal {@link Publisher} instance used to publish events. */ protected readonly _publisher: Publisher<MapViewEventsMap<K, V>>; /** * Initializes a new instance of the {@link MapView} class. * * --- * * @example * ```ts * const map = new MapView<string, number>([["key1", 2], ["key2", 4], ["key3", 8]]); * ``` * * --- * * @param iterable An optional iterable of key-value pairs to initialize the {@link Map} with. */ public constructor(iterable?: Iterable<[K, V]> | null) { super(); this._publisher = new Publisher(); if (iterable) { for (const [key, value] of iterable) { this.set(key, value); } } } /** * Adds a new entry with a specified key and value to the {@link Map}. * If an entry with the same key already exists, the entry will be overwritten with the new value. * * --- * * @example * ```ts * const map = new MapView<string, number>(); * map.set("key1", 2) * .set("key2", 4) * .set("key3", 8); * * console.log(map); // MapView { "key1" => 2, "key2" => 4, "key3" => 8 } * ``` * * --- * * @param key The key of the entry to add. * @param value The value of the entry to add. * * @returns The current instance of the {@link MapView} class. */ public override set(key: K, value: V): this { super.set(key, value); this._publisher.publish("entry:add", key, value); return this; } /** * Removes an entry with a specified key from the {@link Map}. * * --- * * @example * ```ts * const map = new MapView<string, number>([["key1", 2], ["key2", 4], ["key3", 8]]); * map.delete("key2"); // true * map.delete("key4"); // false * * console.log(map); // MapView { "key1" => 2, "key3" => 8 } * ``` * * --- * * @param key The key of the entry to remove. * * @returns `true` if the entry existed and has been removed; otherwise `false` if the entry doesn't exist. */ public override delete(key: K): boolean { const value = this.get(key); if (value === undefined) { return false; } super.delete(key); this._publisher.publish("entry:remove", key, value); return true; } /** * Removes all entries from the {@link Map}. * * --- * * @example * ```ts * const map = new MapView<string, number>([["key1", 2], ["key2", 4], ["key3", 8]]); * map.clear(); * * console.log(map); // MapView { } * ``` */ public override clear(): void { const size = this.size; super.clear(); if (size > 0) { this._publisher.publish("collection:clear"); } } /** * Subscribes to an event and adds a callback to be executed when the event is published. * * --- * * @example * ```ts * const map = new MapView<string, number>(); * const unsubscribe = map.subscribe("entry:add", (key: string, value: number) => * { * if (key === "answer") { unsubscribe(); } * console.log(`Added ${key}: ${value}`); * }); * * map.set("key1", 2); // "Added key1: 2" * map.set("answer", 42); // "Added answer: 42" * map.set("key2", 4); * map.set("key3", 8); * ``` * * --- * * @template T The key of the map containing the callback signature to subscribe. * * @param event The name of the event to subscribe to. * @param subscriber The callback to execute when the event is published. * * @returns A function that can be used to unsubscribe the callback from the event. */ public subscribe<T extends keyof MapViewEventsMap<K, V>>(event: T, subscriber: MapViewEventsMap<K, V>[T]) : () => void { return this._publisher.subscribe(event, subscriber); } /** * Unsubscribes from an event and removes a callback from being executed when the event is published. * * --- * * @example * ```ts * const callback = (key: string, value: number) => console.log(`Added ${key}: ${value}`); * const map = new MapView<string, number>(); * * map.subscribe("entry:add", callback); * map.set("key1", 2); // "Added key1: 2" * * map.unsubscribe("entry:add", callback); * map.set("key2", 4); * ``` * * --- * * @template T The key of the map containing the callback signature to unsubscribe. * * @param event The name of the event to unsubscribe from. * @param subscriber The callback to remove from the event. */ public unsubscribe<T extends keyof MapViewEventsMap<K, V>>(event: T & string, subscriber: MapViewEventsMap<K, V>[T]) : void { this._publisher.unsubscribe(event, subscriber); } public override readonly [Symbol.toStringTag]: string = "MapView"; }