Last active
September 4, 2024 11:19
-
-
Save pigoz/57636c112be44a6871c0b14dfbe77261 to your computer and use it in GitHub Desktop.
TypeScript Stimulus Controller
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { TController } from "./TController"; | |
import FooExample2 from "./foo_example2_controller"; | |
export default class extends TController({ | |
el: HTMLDivElement, | |
values: { | |
foo: String, | |
bar: Boolean, | |
}, | |
targets: { | |
button: HTMLButtonElement, | |
}, | |
outlets: { | |
"foo-example2": FooExample2, | |
}, | |
}) { | |
connect() { | |
this.element satisfies HTMLDivElement; | |
this.fooValue satisfies string; | |
this.hasFooValue satisfies boolean; | |
this.barValue satisfies boolean; | |
this.hasBarValue satisfies boolean; | |
this.buttonTarget satisfies HTMLButtonElement; | |
this.hasButtonTarget satisfies boolean; | |
this.fooExample2Outlet.customMethod() satisfies "!"; | |
this.fooExample2Outlet.hasBazValue satisfies boolean; | |
this.fooExample2Outlet.bazValue satisfies boolean; | |
console.log("hello from example controller", { | |
el: this.element, | |
button: this.buttonTarget, | |
foo: this.fooValue, | |
bar: this.barValue, | |
}); | |
// {el: div, button: button.btn.btn-primary, foo: 'foo', bar: false} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { TController } from "./TController"; | |
export default class extends TController({ | |
el: HTMLAnchorElement, | |
values: { | |
baz: Boolean, | |
}, | |
}) { | |
connect(): void { | |
this.element.textContent = "Outlet content set by Stimulus"; | |
} | |
customMethod() { | |
return "!" as const; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div | |
data-controller="foo-example1" | |
data-foo-example1-foo-value="foo" | |
data-foo-example1-bar-value="false" | |
data-foo-example1-foo-example2-outlet="#out" | |
> | |
<button type="button" class="btn btn-primary" data-foo-example1-target="button">Button</button> | |
</div> | |
<a data-controller="foo-example2" id="out"> | |
Outlet | |
</a> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Context, Controller } from "@hotwired/stimulus"; | |
interface PClass<T> { | |
prototype: T; | |
} | |
// Camelback | |
type Cbz<T extends string> = T extends `${infer A}_${infer B}` | |
? `${A}${Cbz<Capitalize<B>>}` | |
: T extends `${infer A}-${infer B}` | |
? `${A}${Cbz<Capitalize<B>>}` | |
: T; | |
type Camelize<T extends string> = Capitalize<Cbz<T>>; | |
namespace Values { | |
type ValuesConstructors = | |
| BooleanConstructor | |
| NumberConstructor | |
| StringConstructor; | |
interface TypeWithDefault { | |
type: ValuesConstructors; | |
default: unknown; | |
} | |
export interface ValueType { | |
[name: string]: ValuesConstructors | TypeWithDefault; | |
} | |
type HasValue<V extends ValueType> = { | |
[K in keyof V as `has${Camelize<K & string>}Value`]: boolean; | |
}; | |
type NamedValue<V extends ValueType> = { | |
[K in keyof V as `${Cbz<K & string>}Value`]: V[K] extends TypeWithDefault | |
? ReturnType<V[K]["type"]> | |
: V[K] extends ValuesConstructors | |
? ReturnType<V[K]> | |
: unknown; | |
}; | |
export type Typed<V extends ValueType> = HasValue<V> & NamedValue<V>; | |
} | |
namespace Targets { | |
type TargetConstructors = PClass<HTMLElement>; | |
export interface ValueType { | |
[name: string]: TargetConstructors; | |
} | |
type HasTarget<V extends ValueType> = { | |
[K in keyof V as `has${Camelize<K & string>}Target`]: boolean; | |
}; | |
type NamedTarget<V extends ValueType> = { | |
[K in keyof V as `${Cbz< | |
K & string | |
>}Target`]: V[K] extends TargetConstructors ? V[K]["prototype"] : unknown; | |
}; | |
export type Typed<V extends ValueType> = HasTarget<V> & NamedTarget<V>; | |
} | |
namespace Outlets { | |
type OutletConstructors = PClass<Controller>; | |
export interface ValueType { | |
[name: string]: OutletConstructors; | |
} | |
type HasOutlet<V extends ValueType> = { | |
[K in keyof V as `has${Camelize<K & string>}Outlet`]: boolean; | |
}; | |
type NamedOutlet<V extends ValueType> = { | |
[K in keyof V as `${Cbz< | |
K & string | |
>}Outlet`]: V[K] extends OutletConstructors ? V[K]["prototype"] : unknown; | |
}; | |
export type Typed<V extends ValueType> = HasOutlet<V> & NamedOutlet<V>; | |
} | |
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<any>, | |
V extends Values.ValueType, | |
T extends Targets.ValueType, | |
O extends Outlets.ValueType | |
> { | |
new (context: Context): Controller<P["prototype"]> & | |
Values.Typed<V> & | |
Targets.Typed<T> & | |
Outlets.Typed<O>; | |
} | |
export function TController< | |
E extends HTMLElement, | |
P extends PClass<E>, | |
V extends Values.ValueType = {}, | |
T extends Targets.ValueType = {}, | |
O extends Outlets.ValueType = {} | |
>(options: StaticOptions<V, T, O> & { el: P }) { | |
return class extends Controller<E> { | |
static values = options.values ?? {}; | |
static targets = Object.keys(options.targets ?? {}); | |
static outlets = Object.keys(options.outlets ?? {}); | |
} as unknown as TControllerInstance<P, V, T, O>; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment