import type * as H from 'hotscript' import {Controller} from '@hotwired/stimulus' import {type Fn} from 'hotscript' type ObjIterator = H.Pipe< T, [H.Objects.Entries, H.Unions.ToTuple, H.Tuples.FlatMap, H.Tuples.ToUnion, H.Objects.FromEntries] > /* * Covering `has` and `Value` for Stimulus values. */ interface ValuesFn extends Fn { return: [ [`${this['arg0'][0]}Value`, ReturnType], [`has${Capitalize}Value`, boolean], ] } /* * Covering `has`, `Target` and `Targets` for Stimulus targets. */ interface TargetsFn extends Fn { return: [ [`has${Capitalize}Target`, boolean], [`${this['arg0'][0]}Target`, this['arg0'][1] extends null ? HTMLElement : InstanceType], [`${this['arg0'][0]}Targets`, this['arg0'][1] extends null ? HTMLElement[] : InstanceType[]], ] } type TargetsConfig = Record HTMLElement) | null> type ValuesConfig = Record type ControllerWithTargets = Controller & ObjIterator & ObjIterator /** * Creates a new Stimulus controller class with automatically defined target properties. * The function takes an array of target identifiers, which can be either strings or tuples. * For each target, it creates three properties on the controller class: * - `Target` for single target elements, * - `Targets` for all target elements, * - `hasTarget` as a boolean indicating the presence of the target. * * The single target elements are typed according to the provided class in the tuple, * or as HTMLElement by default if a string is provided. * * @returns {Function} A new controller class extending the base Stimulus controller with the defined target properties. * * @example * export default class extends controllerFactory()({ * targets: { * one: null, * two: HTMLInputElement, * three: HTMLButtonElement * }, * values: { * key: String, * } *}) { * example() { * this.oneTarget // <- HTMLElement * this.twoTarget // <- HTMLInputElement * this.threeTarget // <- HTMLButtonElement * * this.hasOneTarget // <- boolean * this.oneTargets // <- HTMLElement[] * * this.hasKeyValue // <- boolean * this.keyValue // <- string * * this.element // <- HTMLDivElement * } *} */ export function controllerFactory() { return function createControllerClass< const T extends TargetsConfig = TargetsConfig, const Y extends ValuesConfig = ValuesConfig, >({ targets, values, }: { targets?: T values?: Y } = {}): new () => ControllerWithTargets { class ExtendedController extends Controller { static targets = Object.keys(targets ?? {}) static values = values ?? {} } return ExtendedController as unknown as new () => ControllerWithTargets } }