function assertNever(x: never): never { throw new Error("Unexpected object: " + x); } export enum Focus { /** Focus the first non-disabled item. */ First, /** Focus the previous non-disabled item. */ Previous, /** Focus the next non-disabled item. */ Next, /** Focus the last non-disabled item. */ Last, /** Focus a specific item based on the `id` of the item. */ Specific, /** Focus no items at all. */ Nothing, } export function calculateActiveIndex( action: | { focus: Focus.Specific; id: string } | { focus: Exclude }, resolvers: { resolveItems(): TItem[]; resolveActiveIndex(): number | null; resolveId(item: TItem): string; resolveDisabled(item: TItem): boolean; } ) { let items = resolvers.resolveItems(); if (items.length <= 0) return null; let currentActiveIndex = resolvers.resolveActiveIndex(); let activeIndex = currentActiveIndex ?? -1; let nextActiveIndex = (() => { switch (action.focus) { case Focus.First: return items.findIndex((item) => !resolvers.resolveDisabled(item)); case Focus.Previous: { let idx = items .slice() .reverse() .findIndex((item, idx, all) => { if (activeIndex !== -1 && all.length - idx - 1 >= activeIndex) return false; return !resolvers.resolveDisabled(item); }); if (idx === -1) return idx; return items.length - 1 - idx; } case Focus.Next: return items.findIndex((item, idx) => { if (idx <= activeIndex) return false; return !resolvers.resolveDisabled(item); }); case Focus.Last: { let idx = items .slice() .reverse() .findIndex((item) => !resolvers.resolveDisabled(item)); if (idx === -1) return idx; return items.length - 1 - idx; } case Focus.Specific: return items.findIndex( (item) => resolvers.resolveId(item) === action.id ); case Focus.Nothing: return null; default: assertNever(action); } })(); return nextActiveIndex === -1 ? currentActiveIndex : nextActiveIndex; }