Skip to content

Instantly share code, notes, and snippets.

@dearlordylord
Created August 15, 2024 13:24
Show Gist options
  • Save dearlordylord/e194e11393f4f2f52d2f58440dead258 to your computer and use it in GitHub Desktop.
Save dearlordylord/e194e11393f4f2f52d2f58440dead258 to your computer and use it in GitHub Desktop.
Flatten and intersperse a dynamic menu in Typescript; type-level and runtime code
type Group = 'item'[];
type NEGroup = NonEmptyArray<'item'>;
// whatever our dynamic code produced; can be empty or non-empty groups
type DynamicMenu = Group[];
type FilterEmptyGroups<T> = T extends DynamicMenu ?
(T extends [[], ...infer R] ? FilterEmptyGroups<R> : T extends [] ? T : T extends [infer NG, ...infer R] ? (
NG extends NEGroup ? [NG, ...FilterEmptyGroups<R>] : [NEGroup, ...FilterEmptyGroups<R>]
) : NEGroup[])
: never;
type Joined<T> =
T extends NEGroup[] ?
(
T extends [infer NG1, infer NG2, ...infer R] ? (
NG1 extends NEGroup ? (
NG2 extends NEGroup ? [NG1, 'separator', ...Joined<[NG2, ...R]>] : [NG1, 'separator', ...Joined<[NEGroup, ...R]>]
) : [NEGroup, 'separator', ...Joined<[NEGroup, ...R]>]
) : T extends [] ? T : T extends [infer NG] ? [NG] : (NEGroup | 'separator')[]
)
: never;
type MenuInvariants<T> =
T extends DynamicMenu ? Joined<FilterEmptyGroups<T>> : never;
type FlattenMenu<T> = T extends [infer NG, ...infer R] ? (
NG extends NEGroup ? [...NG, ...FlattenMenu<R>] : NG extends 'separator' ? [NG, ...FlattenMenu<R>] : never
) :
T extends [] ? [] :
T extends ['separator', ...infer R] ? ['separator', ...FlattenMenu<R>] :
T extends (NEGroup | 'separator')[] ? ('item' | 'separator')[] : never;
const menu1: FlattenMenu<MenuInvariants<[['item'], [], ['item', 'item'], ['item', 'item'], [], []]>> = [
'item',
'separator',
'item', 'item',
'separator',
'item', 'item'
] as const;
const ITEM = 'item' as const;
const SEPARATOR = 'separator' as const;
const renderMenu = <M extends DynamicMenu>(dm: M) =>
pipe(dm,
A.filter(A.isNonEmpty),
A.intersperse(SEPARATOR as typeof SEPARATOR | NonEmptyArray<'item'>),
A.flatMap(x => x === SEPARATOR ? [SEPARATOR] : x)
) as FlattenMenu<MenuInvariants<M>>;
const dynamicMenu: DynamicMenu = [[ITEM], [], [ITEM, ITEM], [ITEM, ITEM], [], []];
const menu = renderMenu(dynamicMenu);
console.log(menu);
// [ 'item', 'separator', 'item', 'item', 'separator', 'item', 'item' ]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment