|
1 | 1 | import Publisher from "../callbacks/publisher.js";
|
2 |
| -import type { MapViewEventsMap } from "./types.js"; |
3 | 2 |
|
4 |
| -export interface ReadonlyMapView<K, V> extends ReadonlyMap<K, V> |
5 |
| -{ |
6 |
| - subscribe<T extends keyof MapViewEventsMap<K, V>>(event: T, callback: MapViewEventsMap<K, V>[T]): () => void; |
7 |
| - unsubscribe<T extends keyof MapViewEventsMap<K, V>>(event: T, callback: MapViewEventsMap<K, V>[T]): void; |
8 |
| -} |
| 3 | +// eslint-disable-next-line @typescript-eslint/no-unused-vars |
| 4 | +import type SetView from "./set-view.js"; |
| 5 | +import type { MapViewEventsMap } from "./types.js"; |
9 | 6 |
|
10 |
| -export class MapView<K, V> extends Map<K, V> |
| 7 | +/** |
| 8 | + * A wrapper class around the native {@link Map} class that provides additional functionality |
| 9 | + * for publishing events when entries are added, removed or the collection is cleared. |
| 10 | + * There's also a complementary class that works with the native `Set` class. |
| 11 | + * See also {@link SetView}. |
| 12 | + * |
| 13 | + * --- |
| 14 | + * |
| 15 | + * @example |
| 16 | + * ```ts |
| 17 | + * const map = new MapView<string, number>(); |
| 18 | + * |
| 19 | + * map.subscribe("entry:add", (key: string, value: number) => console.log(`Added ${key}: ${value}`)); |
| 20 | + * map.set("answer", 42); // Added answer: 42 |
| 21 | + * ``` |
| 22 | + * |
| 23 | + * --- |
| 24 | + * |
| 25 | + * @template K The type of the keys in the map. |
| 26 | + * @template V The type of the values in the map. |
| 27 | + */ |
| 28 | +export default class MapView<K, V> extends Map<K, V> |
11 | 29 | {
|
| 30 | + /** |
| 31 | + * The internal {@link Publisher} instance used to publish events. |
| 32 | + */ |
12 | 33 | protected readonly _publisher: Publisher<MapViewEventsMap<K, V>>;
|
13 | 34 |
|
| 35 | + /** |
| 36 | + * Initializes a new instance of the {@link MapView} class. |
| 37 | + * |
| 38 | + * --- |
| 39 | + * |
| 40 | + * @example |
| 41 | + * ```ts |
| 42 | + * const map = new MapView<string, number>([["key1", 2], ["key2", 4], ["key3", 8]]); |
| 43 | + * ``` |
| 44 | + * |
| 45 | + * --- |
| 46 | + * |
| 47 | + * @param iterable An optional iterable of key-value pairs to initialize the {@link Map} with. |
| 48 | + */ |
14 | 49 | public constructor(iterable?: Iterable<[K, V]> | null)
|
15 | 50 | {
|
16 |
| - super(iterable); |
| 51 | + super(); |
17 | 52 |
|
18 | 53 | this._publisher = new Publisher();
|
| 54 | + |
| 55 | + if (iterable) |
| 56 | + { |
| 57 | + for (const [key, value] of iterable) { this.set(key, value); } |
| 58 | + } |
19 | 59 | }
|
20 | 60 |
|
| 61 | + /** |
| 62 | + * Adds a new entry with a specified key and value to the {@link Map}. |
| 63 | + * If an entry with the same key already exists, the entry will be overwritten with the new value. |
| 64 | + * |
| 65 | + * --- |
| 66 | + * |
| 67 | + * @example |
| 68 | + * ```ts |
| 69 | + * const map = new MapView<string, number>(); |
| 70 | + * map.set("key1", 2) |
| 71 | + * .set("key2", 4) |
| 72 | + * .set("key3", 8); |
| 73 | + * |
| 74 | + * console.log(map); // MapView { "key1" => 2, "key2" => 4, "key3" => 8 } |
| 75 | + * ``` |
| 76 | + * |
| 77 | + * --- |
| 78 | + * |
| 79 | + * @param key The key of the entry to add. |
| 80 | + * @param value The value of the entry to add. |
| 81 | + * |
| 82 | + * @returns The current instance of the {@link MapView} class. |
| 83 | + */ |
21 | 84 | public override set(key: K, value: V): this
|
22 | 85 | {
|
23 | 86 | super.set(key, value);
|
24 | 87 |
|
25 |
| - this._publisher.publish("key:set", key, value); |
| 88 | + this._publisher.publish("entry:add", key, value); |
26 | 89 |
|
27 | 90 | return this;
|
28 | 91 | }
|
| 92 | + |
| 93 | + /** |
| 94 | + * Removes an entry with a specified key from the {@link Map}. |
| 95 | + * |
| 96 | + * --- |
| 97 | + * |
| 98 | + * @example |
| 99 | + * ```ts |
| 100 | + * const map = new MapView<string, number>([["key1", 2], ["key2", 4], ["key3", 8]]); |
| 101 | + * map.delete("key2"); // true |
| 102 | + * map.delete("key4"); // false |
| 103 | + * |
| 104 | + * console.log(map); // MapView { "key1" => 2, "key3" => 8 } |
| 105 | + * ``` |
| 106 | + * |
| 107 | + * --- |
| 108 | + * |
| 109 | + * @param key The key of the entry to remove. |
| 110 | + * |
| 111 | + * @returns `true` if the entry existed and has been removed; otherwise `false` if the entry doesn't exist. |
| 112 | + */ |
29 | 113 | public override delete(key: K): boolean
|
30 | 114 | {
|
31 | 115 | const result = super.delete(key);
|
32 |
| - if (result) { this._publisher.publish("key:delete", key); } |
| 116 | + if (result) { this._publisher.publish("entry:remove", key); } |
33 | 117 |
|
34 | 118 | return result;
|
35 | 119 | }
|
36 | 120 |
|
| 121 | + /** |
| 122 | + * Removes all entries from the {@link Map}. |
| 123 | + * |
| 124 | + * --- |
| 125 | + * |
| 126 | + * @example |
| 127 | + * ```ts |
| 128 | + * const map = new MapView<string, number>([["key1", 2], ["key2", 4], ["key3", 8]]); |
| 129 | + * map.clear(); |
| 130 | + * |
| 131 | + * console.log(map); // MapView { } |
| 132 | + * ``` |
| 133 | + */ |
37 | 134 | public override clear(): void
|
38 | 135 | {
|
39 | 136 | const size = this.size;
|
40 | 137 |
|
41 | 138 | super.clear();
|
42 |
| - if (size > 0) { this._publisher.publish("map:clear"); } |
| 139 | + if (size > 0) { this._publisher.publish("collection:clear"); } |
43 | 140 | }
|
44 | 141 |
|
| 142 | + /** |
| 143 | + * Subscribes to an event and adds a callback to be executed when the event is published. |
| 144 | + * |
| 145 | + * --- |
| 146 | + * |
| 147 | + * @example |
| 148 | + * ```ts |
| 149 | + * const map = new MapView<string, number>(); |
| 150 | + * const unsubscribe = map.subscribe("entry:add", (key: string, value: number) => |
| 151 | + * { |
| 152 | + * if (key === "answer") { unsubscribe(); } |
| 153 | + * console.log(`Added ${key}: ${value}`); |
| 154 | + * }); |
| 155 | + * |
| 156 | + * map.set("key1", 2); // Added key1: 2 |
| 157 | + * map.set("answer", 42); // Added answer: 42 |
| 158 | + * map.set("key2", 4); |
| 159 | + * map.set("key3", 8); |
| 160 | + * ``` |
| 161 | + * |
| 162 | + * --- |
| 163 | + * |
| 164 | + * @template T The key of the map containing the callback signature to subscribe. |
| 165 | + * |
| 166 | + * @param event The name of the event to subscribe to. |
| 167 | + * @param callback The callback to execute when the event is published. |
| 168 | + * |
| 169 | + * @returns A function that can be used to unsubscribe the callback from the event. |
| 170 | + */ |
45 | 171 | public subscribe<T extends keyof MapViewEventsMap<K, V>>(event: T, callback: MapViewEventsMap<K, V>[T]): () => void
|
46 | 172 | {
|
47 | 173 | return this._publisher.subscribe(event, callback);
|
48 | 174 | }
|
| 175 | + |
| 176 | + /** |
| 177 | + * Unsubscribes from an event and removes a callback from being executed when the event is published. |
| 178 | + * |
| 179 | + * --- |
| 180 | + * |
| 181 | + * @example |
| 182 | + * ```ts |
| 183 | + * const callback = (key: string, value: number) => console.log(`Added ${key}: ${value}`); |
| 184 | + * const map = new MapView<string, number>(); |
| 185 | + * |
| 186 | + * map.subscribe("entry:add", callback); |
| 187 | + * map.set("key1", 2); // Added key1: 2 |
| 188 | + * |
| 189 | + * map.unsubscribe("entry:add", callback); |
| 190 | + * map.set("key2", 4); |
| 191 | + * ``` |
| 192 | + * |
| 193 | + * --- |
| 194 | + * |
| 195 | + * @template T The key of the map containing the callback signature to unsubscribe. |
| 196 | + * |
| 197 | + * @param event The name of the event to unsubscribe from. |
| 198 | + * @param callback The callback to remove from the event. |
| 199 | + */ |
49 | 200 | public unsubscribe<T extends keyof MapViewEventsMap<K, V>>(event: T, callback: MapViewEventsMap<K, V>[T]): void
|
50 | 201 | {
|
51 | 202 | this._publisher.unsubscribe(event, callback);
|
52 | 203 | }
|
| 204 | + |
| 205 | + public override readonly [Symbol.toStringTag]: string = "MapView"; |
53 | 206 | }
|
0 commit comments