/* eslint-disable func-style */ export const distinct = Symbol('distinct'); function distinctImpl(this: Array): Array { return [...new Set(this)]; } export const groupBy = Symbol('groupBy'); function groupByImpl(this: Array, keySelector: (item: T) => K): Map { const groups = new Map(); for (const item of this) { const key = keySelector(item); const group = groups.get(key); if (group) { group.push(item); } else { groups.set(key, [item]); } } return groups; } export const remove = Symbol('remove'); function removeImpl(this: Array, ...items: T[]) { for (const item of items) { const itemIndex = this.indexOf(item); if (itemIndex > -1) { this.splice(itemIndex, 1); } } } export const sortBy = Symbol('sortBy'); const temporalComparableTags = [ 'Temporal.PlainDateTime', 'Temporal.PlainDate', 'Temporal.PlainTime', 'Temporal.PlainYearMonth', 'Temporal.ZonedDateTime', 'Temporal.Instant', ]; function resolveSortValue(value: any): any { if (value.constructor && value.call && value.apply) { return value(); } return value; } function getTemporalComparableTag(value: any): string | null { const tag = value?.[Symbol.toStringTag]; if (typeof tag === 'string' && temporalComparableTags.includes(tag)) { return tag; } return null; } function compareTemporalValues(v1: any, v2: any): number | null { const v1Tag = getTemporalComparableTag(v1); if (v1Tag == null || v1Tag !== getTemporalComparableTag(v2)) { return null; } const temporalConstructor = v1.constructor; if (temporalConstructor === v2.constructor && temporalConstructor?.compare != null) { return temporalConstructor.compare(v1, v2); } const temporal = typeof Temporal === 'undefined' ? null : Temporal; const temporalType = temporal?.[v1Tag.replace('Temporal.', '')]; return temporalType?.compare != null ? temporalType.compare(v1, v2) : null; } function compareSortValues(v1: any, v2: any): number { v1 = resolveSortValue(v1); v2 = resolveSortValue(v2); const temporalComparison = compareTemporalValues(v1, v2); if (temporalComparison != null) { return temporalComparison; } return v1 < v2 ? -1 : v1 > v2 ? 1 : 0; } function sortByImpl(this: T[], propName: string | ((item: T) => any)): Array { function dynamicSort(property) { if (propName && {}.toString.call(propName) === '[object Function]') { return function (a, b) { return compareSortValues((propName)(a), (propName)(b)); }; } else { let propArr: string[] = property; if (typeof property === 'string' || property instanceof String) { propArr = [property]; } return function (a, b) { for (let i = 0, len = propArr.length; i < len; i++) { let currProp = propArr[i]; let sortOrder = 1; if (currProp[0] === '-') { sortOrder = -1; currProp = currProp.substr(1); } const result = compareSortValues(a[currProp], b[currProp]); if (result != 0) { return result * sortOrder; } } return 0; }; } } this.sort(dynamicSort(propName)); return this; } declare global { export interface Array { [distinct]: typeof distinctImpl; [groupBy]: typeof groupByImpl; /** * Removes given items from the Array * * @param items Items that should be removed from the Array */ [remove]: typeof removeImpl; /** * Sorts array by given property * * @param propName Property name (or function returning the sort key), the array should be sorted by */ [sortBy]: typeof sortByImpl; } } (Array as any).prototype[distinct] = distinctImpl; (Array as any).prototype[groupBy] = groupByImpl; (Array as any).prototype[remove] = removeImpl; (Array as any).prototype[sortBy] = sortByImpl;