import { Context, Controller } from "@hotwired/stimulus"; interface PClass { prototype: T; } // Camelback type Cbz = T extends `${infer A}_${infer B}` ? `${A}${Cbz>}` : T extends `${infer A}-${infer B}` ? `${A}${Cbz>}` : T; type Camelize = Capitalize>; namespace Values { type ValuesConstructors = | BooleanConstructor | NumberConstructor | StringConstructor; interface TypeWithDefault { type: ValuesConstructors; default: unknown; } export interface ValueType { [name: string]: ValuesConstructors | TypeWithDefault; } type HasValue = { [K in keyof V as `has${Camelize}Value`]: boolean; }; type NamedValue = { [K in keyof V as `${Cbz}Value`]: V[K] extends TypeWithDefault ? ReturnType : V[K] extends ValuesConstructors ? ReturnType : unknown; }; export type Typed = HasValue & NamedValue; } namespace Targets { type TargetConstructors = PClass; export interface ValueType { [name: string]: TargetConstructors; } type HasTarget = { [K in keyof V as `has${Camelize}Target`]: boolean; }; type NamedTarget = { [K in keyof V as `${Cbz< K & string >}Target`]: V[K] extends TargetConstructors ? V[K]["prototype"] : unknown; }; export type Typed = HasTarget & NamedTarget; } namespace Outlets { type OutletConstructors = PClass; export interface ValueType { [name: string]: OutletConstructors; } type HasOutlet = { [K in keyof V as `has${Camelize}Outlet`]: boolean; }; type NamedOutlet = { [K in keyof V as `${Cbz< K & string >}Outlet`]: V[K] extends OutletConstructors ? V[K]["prototype"] : unknown; }; export type Typed = HasOutlet & NamedOutlet; } interface StaticOptions< V extends Values.ValueType, T extends Targets.ValueType, O extends Outlets.ValueType > { values?: V; targets?: T; outlets?: O; } interface TControllerInstance< P extends PClass, V extends Values.ValueType, T extends Targets.ValueType, O extends Outlets.ValueType > { new (context: Context): Controller & Values.Typed & Targets.Typed & Outlets.Typed; } export function TController< E extends HTMLElement, P extends PClass, V extends Values.ValueType = {}, T extends Targets.ValueType = {}, O extends Outlets.ValueType = {} >(options: StaticOptions & { el: P }) { return class extends Controller { static values = options.values ?? {}; static targets = Object.keys(options.targets ?? {}); static outlets = Object.keys(options.outlets ?? {}); } as unknown as TControllerInstance; }