40 lines
1.7 KiB
TypeScript
40 lines
1.7 KiB
TypeScript
export interface Removed<T> {
|
|
item: T;
|
|
index: number;
|
|
}
|
|
|
|
/** In place; `undefined` means nothing matched and the list is untouched. */
|
|
export function removeFirst<T>(list: T[], predicate: (item: T) => boolean): Removed<T> | undefined {
|
|
const index = list.findIndex(predicate);
|
|
// A valid index makes `splice` return exactly one element, which is what the `!` rests on.
|
|
return index < 0 ? undefined : { item: list.splice(index, 1)[0]!, index };
|
|
}
|
|
|
|
/** In place; the returned indices are the former ones, ascending. */
|
|
export function removeWhere<T>(list: T[], predicate: (item: T) => boolean): Removed<T>[] {
|
|
const removed: Removed<T>[] = [];
|
|
list.forEach((item, index) => {
|
|
if (predicate(item)) removed.push({ item, index });
|
|
});
|
|
for (let i = removed.length - 1; i >= 0; i--) list.splice(removed[i]!.index, 1);
|
|
return removed;
|
|
}
|
|
|
|
/** Inserting in ascending index order is what restores the list exactly. */
|
|
export function restoreAt<T>(list: T[], removed: readonly Removed<T>[]): void {
|
|
for (const entry of [...removed].sort((a, b) => a.index - b.index)) list.splice(entry.index, 0, entry.item);
|
|
}
|
|
|
|
/** In place; `false` means `from` or `to` is no place in the list, and nothing moved. */
|
|
export function moveItem<T>(list: T[], from: number, to: number): boolean {
|
|
const inside = (index: number) => Number.isInteger(index) && index >= 0 && index < list.length;
|
|
if (!inside(from) || !inside(to)) return false;
|
|
list.splice(to, 0, ...list.splice(from, 1));
|
|
return true;
|
|
}
|
|
|
|
/** Returns a new list; unlike the helpers above this one does not mutate. */
|
|
export function removeById<T extends { id: string }>(list: readonly T[], id: string): T[] {
|
|
return list.filter((item) => item.id !== id);
|
|
}
|