UNPKG

@opra/common

Version:

Opra common package

196 lines (195 loc) 6.03 kB
import EventEmitter from 'events'; function isMap(v) { return v && typeof v.forEach === 'function'; } const kEntries = Symbol.for('kEntries'); const kKeyMap = Symbol.for('kKeyMap'); const kWellKnownKeys = Symbol.for('kWellKnownKeys'); const kOptions = Symbol.for('kOptions'); const kSize = Symbol.for('kSize'); /** * A Map implementation that supports case-insensitivity and ordered keys */ export class ResponsiveMap extends EventEmitter { constructor(init, options) { super(); Object.defineProperty(this, kSize, { value: 0, enumerable: false, writable: true, }); Object.defineProperty(this, kEntries, { value: {}, enumerable: false, writable: true, }); Object.defineProperty(this, kKeyMap, { value: {}, enumerable: false, writable: true, }); Object.defineProperty(this, kWellKnownKeys, { value: {}, enumerable: false, writable: true, }); const caseSensitive = !!options?.caseSensitive; Object.defineProperty(this, kOptions, { value: { caseSensitive, }, enumerable: false, }); if (options?.wellKnownKeys) { options.wellKnownKeys.forEach(k => { if (caseSensitive) this[kWellKnownKeys][k] = k; else this[kWellKnownKeys][k.toLowerCase()] = k; }); } this.clear(); if (init) this.setAll(init); } get size() { return this[kSize]; } clear() { Object.keys(this[kEntries]).forEach(k => delete this[kEntries][k]); Object.keys(this[kKeyMap]).forEach(k => delete this[kKeyMap][k]); this[kSize] = 0; this.emit('change'); this.emit('clear'); } forEach(callbackfn, thisArg) { for (const [k, v] of this.entries()) { callbackfn.call(thisArg, v, k, this); } } get(key) { if (!key) return; return this[kEntries][this._getStoringKey(key)]; } has(key) { if (!key) return false; return Object.prototype.hasOwnProperty.call(this[kEntries], this._getStoringKey(key)); } set(key, value) { const storeKey = this._getStoringKey(key); key = this._getOriginalKey(key); const exists = Object.prototype.hasOwnProperty.call(this[kEntries], storeKey); this[kEntries][storeKey] = value; if (!exists) this[kSize]++; this[kKeyMap][storeKey] = key; this.emit('change'); this.emit('set', key, value); return this; } setAll(source) { if (isMap(source)) source.forEach((v, k) => this.set(k, v)); else Object.keys(source).forEach(k => this.set(k, source[k])); return this; } keys() { return Object.values(this[kKeyMap])[Symbol.iterator](); } values() { return Object.values(this[kEntries])[Symbol.iterator](); } entries() { return Object.entries(this[kEntries])[Symbol.iterator](); } delete(key) { const storeKey = this._getStoringKey(key); const exists = Object.prototype.hasOwnProperty.call(this[kEntries], storeKey); delete this[kEntries][storeKey]; delete this[kKeyMap][storeKey]; if (exists) { this[kSize]--; this.emit('change'); } return exists; } sort(compareFn) { const oldValues = { ...this[kEntries] }; const oldKeymap = { ...this[kKeyMap] }; const keys = Array.from(this.keys()); if (compareFn) keys.sort(compareFn); else keys.sort(); this.clear(); for (const k of keys) { this[kEntries][k] = oldValues[k]; this[kKeyMap][k] = oldKeymap[k]; } this[kSize] = keys.length; this.emit('change'); return this; } toObject() { const out = {}; for (const [storeKey, orgKey] of Object.entries(this[kKeyMap])) { out[orgKey] = this[kEntries][storeKey]; } return out; } toProxy() { const _this = this; return new Proxy(_this[kEntries], { get(target, prop) { return typeof prop === 'string' ? _this.get(prop) : undefined; }, set(target, prop, value) { if (typeof prop === 'string') { _this.set(prop, value); return true; } return false; }, has(target, prop) { return typeof prop === 'string' ? _this.has(prop) : false; }, deleteProperty(target, prop) { if (typeof prop === 'string') { _this.delete(prop); return true; } return false; }, defineProperty(target, prop, attributes) { if (typeof prop === 'string') { if (attributes.value !== undefined) _this.set(prop, attributes.value); else _this.delete(prop); return true; } return false; }, }); } [Symbol.iterator]() { return this.entries(); } get [Symbol.toStringTag]() { return '[Object ResponsiveMap]'; } _getOriginalKey(key) { if (!key || this[kOptions].caseSensitive) return key; const storeKey = this._getStoringKey(key); return this[kKeyMap][storeKey] ?? this[kWellKnownKeys][storeKey] ?? key; } _getStoringKey(key) { if (this[kOptions].caseSensitive) return key; return key.toLowerCase(); } }