@opra/common
Version:
Opra common package
196 lines (195 loc) • 6.03 kB
JavaScript
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();
}
}