Skip to content

Instantly share code, notes, and snippets.

@msereniti
Last active December 23, 2021 22:24
Show Gist options
  • Save msereniti/eb80913bc0f8036749bda3c1e5d5a5d9 to your computer and use it in GitHub Desktop.
Save msereniti/eb80913bc0f8036749bda3c1e5d5a5d9 to your computer and use it in GitHub Desktop.
An attempt to create Typescript typing that adds context modifiers to parent functions when nested context consumers appears
type ArgumentTypes<F extends Function> = F extends (...args: infer A) => any
? A
: never;
type ArrShift<Arr extends any[]> = Arr extends [skip: any, ...use: infer Use]
? Use
: Arr;
type ViewSetup = {
modifiers: { [key: string]: (...args: any[]) => unknown };
};
type CombineTwoSetups<A extends ViewSetup, B extends ViewSetup> = {
modifiers: {
[modifier in keyof A["modifiers"]]: A["modifiers"][modifier];
} &
{
[modifier in keyof B["modifiers"]]: B["modifiers"][modifier];
};
};
type CombineSetups<Setups extends ViewSetup[] = ViewSetup[]> =
Setups extends [ViewSetup, ViewSetup, ...ViewSetup[]] ?
CombineSetups<[CombineTwoSetups<Setups[0], Setups[1]>, ...ArrShift<ArrShift<Setups>>]> :
Setups extends [ViewSetup, ViewSetup] ?
CombineTwoSetups<Setups[0], Setups[1]> :
Setups extends [ViewSetup] ?
Setups[0] :
ViewSetup
type ViewInstance<Setup extends ViewSetup = ViewSetup> = {
[modifier in keyof Setup["modifiers"]]: (
...args: ArgumentTypes<Setup["modifiers"][modifier]>
) => ViewInstance<{ modifiers: Setup["modifiers"] }>;
// looks like ts looses modifier specific key type and omits all modiefers
// ) => ViewInstance<{ modifiers: Omit<Setup["modifiers"], modifier> }>;
};
type ViewSetupOfChild<Child extends ViewInstance> = Child extends ViewInstance<
infer Setup
>
? Setup
: never;
type ViewSetupOfChildren<Children extends ViewInstance[]> =
Children extends [ViewInstance, ViewInstance, ...ViewInstance[]] ?
CombineTwoSetups<ViewSetupOfChild<Children[0]>, ViewSetupOfChildren<ArrShift<Children>>> :
Children extends [ViewInstance, ViewInstance] ?
CombineTwoSetups<ViewSetupOfChild<Children[0]>, ViewSetupOfChild<Children[1]>> :
Children extends [ViewInstance] ?
ViewSetupOfChild<Children[0]> :
ViewSetup
type TextViewSetup = {
modifiers: {
fontSize: (fontSize: "title" | "subtitle") => unknown;
color: (color: "black" | "blue") => unknown;
};
};
type TextView = (content: string) => ViewInstance<TextViewSetup>;
type View<OwnSetup extends ViewSetup = ViewSetup> = <
Children extends ViewInstance<ViewSetup>[] = ViewInstance<ViewSetup>[],
>(
...children: Children
) => ViewInstance<
CombineTwoSetups<ViewSetupOfChildren<Children>, OwnSetup>
>;
type VStack = View<{
modifiers: {
gap: (gap: "small" | "big") => unknown;
};
}>;
const text: TextView = 0 as any;
const view: View<{ modifiers: {} }> = 0 as any;
const vStack: VStack = 0 as any;
vStack(
view(text('Hello world')).color('blue'),
text('Hello world')
).fontSize('title')
// Platform specific declaration
const browserRenderer = 'browser_renderer';
type BrowserViewDeclaration<
TagName extends keyof HTMLElementTagNameMap = keyof HTMLElementTagNameMap
> = {
renderer: typeof browserRenderer;
params: {
tagName: TagName;
attributes: Partial<HTMLElementTagNameMap[TagName]>;
eventHandlers: TagName extends keyof HTMLElementEventMap
? Partial<HTMLElementEventMap[TagName]>
: {};
children: ViewInstance[];
};
};
// Declaration
type ViewDeclaration = BrowserViewDeclaration; // | OtherPlatformViewDeclaration
type ArgumentTypes<F extends Function> = F extends (...args: infer A) => any
? A
: never;
type ArrayLast<Arr extends any[]> = Arr extends [
...skip: unknown[],
last: infer Last
]
? Last
: Arr[0];
type ViewSetup = {
properties?: any[];
modifiers: { [key: string]: (...args: any[]) => unknown };
};
type ArrayPop<Arr extends any[]> = Arr extends [
...use: infer Use,
skip: unknown
]
? Use
: [];
type CombineTwoSetups<
SecondarySetup extends ViewSetup,
PrimarySetup extends ViewSetup
> = {
properties: PrimarySetup['properties'];
modifiers: {
[modifier in keyof SecondarySetup['modifiers']]: SecondarySetup['modifiers'][modifier];
} & {
[modifier in keyof PrimarySetup['modifiers']]: PrimarySetup['modifiers'][modifier];
};
};
type ViewInstance<Setup extends ViewSetup = ViewSetup> = {
[modifier in keyof Setup['modifiers']]: (
...args: ArgumentTypes<Setup['modifiers'][modifier]>
) => ViewInstance<{
properties: Setup['properties'];
modifiers: Setup['modifiers'];
}>;
};
type ViewSetupOfChild<Child extends ViewInstance> = Child extends ViewInstance<
infer Setup
>
? Setup
: never;
type ViewSetupOfChildren<Children extends ViewInstance[]> = Children extends [
ViewInstance,
ViewInstance,
...ViewInstance[]
]
? CombineTwoSetups<
ViewSetupOfChildren<ArrayPop<Children>>,
ViewSetupOfChild<ArrayLast<Children>>
>
: Children extends [ViewInstance, ViewInstance]
? CombineTwoSetups<
ViewSetupOfChild<Children[0]>,
ViewSetupOfChild<Children[1]>
>
: Children extends [ViewInstance]
? ViewSetupOfChild<Children[0]>
: ViewSetup;
type View<Setup extends ViewSetup = ViewSetup> = <
Children extends ViewInstance<ViewSetup>[] = ViewInstance<ViewSetup>[]
>(
...properties: Setup['properties'] extends [any, ...any]
? Children extends [any, ...any]
? [...Setup['properties'], ...Children]
: Setup['properties']
: Children
) => ViewInstance<CombineTwoSetups<ViewSetupOfChildren<Children>, Setup>>;
type ModifiersMap<Setup extends ViewSetup> = {
[modifier in keyof Setup['modifiers']]: ArgumentTypes<
Setup['modifiers'][modifier]
> extends [any, any, ...any]
? ArgumentTypes<Setup['modifiers'][modifier]>
: ArgumentTypes<Setup['modifiers'][modifier]>[0];
};
type ViewFunc<Setup extends ViewSetup = ViewSetup> = <
Children extends ViewInstance<ViewSetup>[] = ViewInstance<ViewSetup>[]
>(
modifiers: ModifiersMap<Setup>,
...properties: Setup['properties'] extends [any, ...any]
? Children extends [any, ...any]
? [...Setup['properties'], ...Children]
: Setup['properties']
: Children
) => ViewDeclaration;
type CreateView = <Setup extends ViewSetup = ViewSetup>(
viewFunc: ViewFunc<Setup>
) => View<Setup>;
// Fake runtime
const createView: CreateView = (() => ({})) as any
const vStack = createView<{
modifiers: {
gap: (gap: 'small' | 'big') => any;
};
}>((...args): any => {
console.log(args)
// some runtime realization
});
const text = createView<{
properties: [content: string];
modifiers: {
fontSize: (fontSize: 'title' | 'subtitle') => any;
color: (color: 'black' | 'blue') => any;
};
}>((...args): any => {
console.log(args)
// some runtime realization
});
// Fun part
vStack(
vStack(text('Hello world')).color('blue'),
text('Hello world')
).color('black').
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment