@opra/common
Version:
Opra common package
152 lines (151 loc) • 4.57 kB
JavaScript
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 {
constructor(init, options) {
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;
}
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;
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]--;
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;
return this;
}
toObject() {
const out = {};
for (const [storeKey, orgKey] of Object.entries(this[kKeyMap])) {
out[orgKey] = this[kEntries][storeKey];
}
return out;
}
[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();
}
}