UNPKG

enum-plus

Version:

A drop-in replacement for native enum. Like native enum but much better!

298 lines (285 loc) 9.22 kB
import { EnumItemClass } from "./enum-item.js"; import { IS_ENUM_ITEMS, KEYS, VALUES } from "./utils.js"; /** * Enum items array, mostly are simple wrappers for EnumCollectionClass * * @template T Type of the initialization data of the enum collection * * @class EnumItemsArray * * @extends {EnumItemClass<T, K, V>[]} * * @implements {IEnumItems<T, K, V>} */ export class EnumItemsArray extends Array { __raw__; /** * - **EN:** A boolean value indicates that this is an enum items array. * - **CN:** 布尔值,表示这是一个枚举项数组 */ // Do not use readonly field here, because don't want print this field in Node.js // eslint-disable-next-line @typescript-eslint/class-literal-property-style get [IS_ENUM_ITEMS]() { return true; } [KEYS]; [VALUES]; labels; named; meta; _runtimeError; /** * Instantiate an enum items array * * @memberof EnumItemsArray * * @param {T} raw Original initialization data object * @param {EnumItemOptions<T[K], K, V, P> | undefined} options Enum item options */ constructor(raw, options) { super(); // Do not use class field here, because don't want print this field in Node.js Object.defineProperty(this, '__raw__', { value: raw, enumerable: false, writable: false, configurable: false }); // Generate keys array // exclude number keys with a "reverse mapping" value, it means those "reverse mapping" keys of number enums const keys = parseKeys(raw); const parsed = keys.map(key => parseEnumItem(raw[key], key)); this[KEYS] = keys; Object.freeze(keys); const items = []; const meta = {}; this.meta = meta; const named = {}; this.named = named; keys.forEach((key, index) => { const { value, label } = parsed[index]; const item = new EnumItemClass(key, value, label, raw[key], options); items.push(item); this.push(item); named[key] = item; // Collect custom meta fields const itemRaw = raw[key]; if (itemRaw && typeof itemRaw === 'object') { Object.keys(itemRaw).forEach(k => { const metaKey = k; if (metaKey !== 'key' && metaKey !== 'value' && metaKey !== 'label') { if (meta[metaKey] == null) { meta[metaKey] = []; } // eslint-disable-next-line @typescript-eslint/no-explicit-any const metaValue = itemRaw[metaKey]; if (metaValue != null) { meta[metaKey].push(metaValue); } } }); } }); // Freeze meta arrays Object.keys(meta).forEach(k => { Object.freeze(meta[k]); }); // Generate values array const values = parsed.map(item => item.value); this[VALUES] = values; Object.freeze(values); // Generate labels array Object.defineProperty(this, 'labels', { get: function () { // Cannot save to static array because labels may be localized contents // Should not use `items` in the closure because the getter function cannot be fully serialized return Array.from(this).map(item => item.label); }, enumerable: true, configurable: false }); this._runtimeError = undefined; Object.defineProperty(this, '_runtimeError', { value: function (name) { return `The ${name} property of the enumeration is only allowed to be used to declare the ts type, and cannot be accessed at runtime! Please use the typeof operator in the ts type, for example: typeof Week.${name}`; }, writable: false, enumerable: false, configurable: false }); } [Symbol.hasInstance](instance) { // intentionally use == to support both number and string format value return this.some( // eslint-disable-next-line eqeqeq i => instance == i.value || instance === i.key); } label(keyOrValue) { // Find by value, then try key // eslint-disable-next-line @typescript-eslint/no-explicit-any return (this.find(i => i.value === keyOrValue) ?? this.find(i => i.key === keyOrValue))?.label; } key(value) { // eslint-disable-next-line @typescript-eslint/no-explicit-any return this.find(i => i.value === value)?.key; } raw(keyOrValue) { if (keyOrValue == null) { // Return the original initialization object return this.__raw__; } else { // Find by key if (Object.keys(this.__raw__).some(k => k === keyOrValue)) { return this.__raw__[keyOrValue]; } // Find by value const itemByValue = this.find(i => i.value === keyOrValue); if (itemByValue) { return itemByValue.raw; } return undefined; } } has(keyOrValue) { return this.some(i => i.value === keyOrValue || i.key === keyOrValue); } findBy(field, value) { return this.find(item => { if (field === 'key' || field === 'value') { return item[field] === value; } else if (field === 'label') { // eslint-disable-next-line @typescript-eslint/no-explicit-any return item.raw?.label === value || item.label === value; } else { // For other fields, use the raw object to find // eslint-disable-next-line @typescript-eslint/no-explicit-any return item.raw?.[field] === value; } // eslint-disable-next-line @typescript-eslint/no-explicit-any }); } toList(config) { const { valueField = 'value', labelField = 'label', extra } = config ?? {}; return Array.from(this).map(item => { const valueFieldName = typeof valueField === 'function' ? valueField(item) : valueField; const labelFieldName = typeof labelField === 'function' ? labelField(item) : labelField; const extraData = extra ? extra(item) : {}; const listItem = { [valueFieldName]: item.value, [labelFieldName]: item.label, ...extraData }; return listItem; }); } toMap(config) { if (!config) { return this.reduce((prev, cur) => { prev[cur.value] = cur.label; return prev; }, // eslint-disable-next-line @typescript-eslint/no-explicit-any {}); } const { keySelector = 'value', valueSelector = 'label' } = config; return this.reduce((prev, cur) => { let key; if (typeof keySelector === 'function') { key = keySelector(cur); } else { key = cur[keySelector]; } // eslint-disable-next-line @typescript-eslint/no-explicit-any let value; if (typeof valueSelector === 'function') { value = valueSelector(cur); } else { value = cur[valueSelector]; } prev[key] = value; return prev; }, {}); } /** Stub method, only for typing usages, not for runtime calling */ get valueType() { throw new Error(this._runtimeError('valueType')); } /** Stub method, only for typing usages, not for runtime calling */ get keyType() { throw new Error(this._runtimeError('keyType')); } /** Stub method, only for typing usages, not for runtime calling */ get rawType() { throw new Error(this._runtimeError('rawType')); } } /** * - **EN:** Enum item collection interface, excluding members inherited from the array * - **CN:** 枚举项集合接口,不包含从数组集成的成员 * * @template T The type of enum initialization | 枚举初始化的类型 * @template K The type of enum keys | 枚举键的类型 * @template V The type of enum values | 枚举值的类型 * * @interface IEnumItems */ // typeof IS_ENUM_ITEMS | typeof ITEMS | typeof KEYS | typeof VALUES | 'labels' | 'meta' | 'named' /** More options for the options method */ export function parseKeys(raw) { return Object.keys(raw).filter(k => !(/^-?\d+$/.test(k) && k === `${raw[raw[k]] ?? ''}`)); } function parseEnumItem(init, key) { let value; let label; if (init != null) { if (typeof init === 'number' || typeof init === 'string' || typeof init === 'symbol') { value = init; label = key; } else if (typeof init === 'object') { // Initialize using object if (Object.prototype.toString.call(init) === '[object Object]') { if ('value' in init && Object.keys(init).some(k => k === 'value')) { // type of {value, label} value = init.value ?? key; if ('label' in init && Object.keys(init).some(k => k === 'label')) { label = init.label; } else { label = key; } } else if ('label' in init && Object.keys(init).some(k => k === 'label')) { // typeof {label} value = key; label = init.label ?? key; } else { // {} empty object value = key; label = key; } } else { // Probably Date, RegExp and other primitive types value = init; label = key; } } else { throw new Error(`Invalid enum item: ${JSON.stringify(init)}`); } } else { value = key; label = key; } return { value, label }; } //# sourceMappingURL=enum-items.js.map