UNPKG

ripple

Version:

Ripple is an elegant TypeScript UI framework

206 lines (171 loc) 3.69 kB
/** @import { Block, Tracked } from '#client' */ import { get, increment, safe_scope, set, tracked, with_scope } from './internal/client/runtime.js'; const introspect_methods = ['entries', 'forEach', 'values', Symbol.iterator]; let init = false; /** * @template K, V * @extends {Map<K, V>} * @returns {RippleMap<K, V>} */ export class RippleMap extends Map { /** @type {Tracked} */ #tracked_size; /** @type {Map<K, Tracked>} */ #tracked_items = new Map(); /** @type {Block} */ #block; /** * @param {Iterable<readonly [K, V]>} [iterable] */ constructor(iterable) { super(); var block = (this.#block = safe_scope()); if (iterable) { for (var [key, value] of iterable) { super.set(key, value); this.#tracked_items.set(key, tracked(0, block)); } } this.#tracked_size = tracked(super.size, block); if (!init) { init = true; this.#init(); } } /** * @returns {void} */ #init() { var proto = RippleMap.prototype; var map_proto = Map.prototype; for (const method of introspect_methods) { /** @type {any} */ (proto)[method] = function (/** @type {...any} */ ...v) { this.size; this.#read_all(); return /** @type {any} */ (map_proto)[method].apply(this, v); }; } } /** * @param {K} key * @returns {V | undefined} */ get(key) { var tracked_items = this.#tracked_items; var t = tracked_items.get(key); if (t === undefined) { // same logic as has this.size; } else { get(t); } return super.get(key); } /** * @param {K} key * @returns {boolean} */ has(key) { var has = super.has(key); var tracked_items = this.#tracked_items; var t = tracked_items.get(key); if (t === undefined) { // if no tracked it also means super didn't have it // It's not possible to have a disconnect, we tract each key // If the key doesn't exist, track the size in case it's added later // but don't create tracked entries willy-nilly to track all possible keys this.size; } else { get(t); } return has; } /** * @param {K} key * @param {V} value * @returns {this} */ set(key, value) { var block = this.#block; var tracked_items = this.#tracked_items; var t = tracked_items.get(key); var prev_res = super.get(key); super.set(key, value); if (!t) { tracked_items.set(key, tracked(0, block)); set(this.#tracked_size, super.size); } else if (prev_res !== value) { increment(t); } return this; } /** * @param {K} key * @returns {boolean} */ delete(key) { var block = this.#block; var tracked_items = this.#tracked_items; var t = tracked_items.get(key); var result = super.delete(key); if (t) { increment(t); tracked_items.delete(key); set(this.#tracked_size, super.size); } return result; } /** * @returns {void} */ clear() { var block = this.#block; if (super.size === 0) { return; } for (var [_, t] of this.#tracked_items) { increment(t); } super.clear(); this.#tracked_items.clear(); set(this.#tracked_size, 0); } /** * @returns {MapIterator<K>} */ keys() { this.size; return super.keys(); } /** * @returns {void} */ #read_all() { for (const [, t] of this.#tracked_items) { get(t); } } /** * @returns {number} */ get size() { return get(this.#tracked_size); } /** * @returns {Array<[K, V]>} */ toJSON() { this.size; this.#read_all(); return [...this]; } } /** * @template K, V * @param {Block} block * @param {Iterable<readonly [K, V]>} [iterable] * @returns {RippleMap<K, V>} */ export function ripple_map(block, iterable) { return with_scope(block, () => new RippleMap(iterable)); }