import type { BaseInputorContext, BaseInputorControllerOptions, BaseInputorSchema, InputorControllerInitializeHooks, } from "./base.ts" import { BaseInputorController } from "./base.ts" import type * as Boolean from "./boolean.ts" import type * as File from "./file.ts" import type * as MultiSelect from "./multi-select.ts" import type * as Number from "./number.ts" import type * as Select from "./select.ts" import type * as Text from "./text.ts" interface InternalInputorControllerDict { [FormInputorController.inputorTypeName]: FormInputorController [Boolean.BooleanInputorController.inputorTypeName]: Boolean.BooleanInputorController [Number.NumberInputorController.inputorTypeName]: Number.NumberInputorController [Text.TextInputorController.inputorTypeName]: Text.TextInputorController [File.FileInputorController.inputorTypeName]: File.FileInputorController [Select.SelectInputorController.inputorTypeName]: Select.SelectInputorController [MultiSelect.MultiSelectInputorController .inputorTypeName]: MultiSelect.MultiSelectInputorController // Year // Month // Day // Hour // Minute // Second // Week // Time // TimeRange // Url // Color } type InternalInputorControllerUnion = InternalInputorControllerDict[keyof InternalInputorControllerDict] // 下面是 Form Inputor Controller 的实现 export const FORM_INPUTOR_TYPE_NAME = "form" as const export type FormInputorTypeName = typeof FORM_INPUTOR_TYPE_NAME export type FormInputorInternalValue = Record | undefined export interface FormInputorSchema< ExternalValue = unknown, > extends BaseInputorSchema { inputorTypeName: FormInputorTypeName } export interface FormInputorContext< ExternalValue = unknown, InternalValue = unknown, > extends BaseInputorContext {} export type FormInputorControllerItems = Record type GetSchema = Item["schemaType"] type GetSchemaDict = { [Key in keyof Items]: GetSchema } // type GetSchemaUnion = GetSchemaDict[keyof GetSchemaDict] type GetValue = GetSchema["value"] type GetValueDict = { [Key in keyof Items]: GetValue } type GetValueUnion = GetValueDict[keyof GetValueDict] type GeneralUpdateValue = ( value: GetValueUnion, ) => Promise export interface FormInputorControllerOptions< // NOTE: use any to avoid contravariance error // oxlint-disable-next-line typescript/no-explicit-any ExternalValue = any, // eslint-disable-next-line @typescript-eslint/no-explicit-any Items extends FormInputorControllerItems = any, > extends BaseInputorControllerOptions> { voidExternalValue?: ExternalValue items: Items encodeValue: (schemaDict: GetSchemaDict) => ExternalValue decodeValue: (value: ExternalValue) => GetValueDict } const VOID_EXTERNAL_VALUE = Symbol("voidExternalValue") export class FormInputorController< // ! Use `any` to avoid contravariance error // oxlint-disable-next-line typescript/no-explicit-any ExternalValue = any, // ! Use `any` to avoid circular reference error // ! Use `any` to avoid contravariance error // oxlint-disable-next-line typescript/no-explicit-any Items extends FormInputorControllerItems = any, > extends BaseInputorController< ExternalValue, FormInputorInternalValue, FormInputorSchema, FormInputorContext > { declare schemaType: FormInputorSchema declare optionsType: FormInputorControllerOptions declare constructorType: typeof FormInputorController static inputorTypeName: FormInputorTypeName = FORM_INPUTOR_TYPE_NAME inputorTypeName: FormInputorTypeName = FORM_INPUTOR_TYPE_NAME voidExternalValue!: ExternalValue voidInternalValue = undefined protected items!: Record protected encodeValue!: FormInputorControllerOptions["encodeValue"] protected decodeValue!: FormInputorControllerOptions["decodeValue"] constructor(options: FormInputorControllerOptions) { super(options) } protected override async initialize( options: FormInputorControllerOptions, hooks?: InputorControllerInitializeHooks, ): Promise { await super.initialize(options, { ...hooks, onSchemaReady: async () => { if ("voidExternalValue" in options) { this.voidExternalValue = options.voidExternalValue } else { this.voidExternalValue = VOID_EXTERNAL_VALUE as unknown as ExternalValue } this.items = options.items this.encodeValue = options.encodeValue this.decodeValue = options.decodeValue this.subscribeItemSchemaChange() await hooks?.onSchemaReady?.() }, }) } /** * @description 订阅所有子 Inputor 的 Schema,当子 Inputor 的 Schema 发生变化时,更新自己。 * 1. 当子项 value 发生变化时,更新自己的 value; * 2. 待遇到实际情况再补充…… */ protected subscribeItemSchemaChange(): void { Object.values(this.items).forEach((item) => { item.subscribeSchemaChange(() => { void this.updateValueFromItems() }) }) } protected async getSchemaDict(): Promise> { const schemaDict = {} as GetSchemaDict await Promise.all( Object.entries(this.items).map(async ([key, item]) => { Object.assign(schemaDict, { [key]: await item.getSchema(), }) }), ) return schemaDict } protected async updateValueFromItems(): Promise { const schemaDict = await this.getSchemaDict() const value = this.encodeValue(schemaDict) await this.updateValue(value) } protected override async isVoidExternalValue(value: ExternalValue): Promise { if (this.voidExternalValue === VOID_EXTERNAL_VALUE && value === VOID_EXTERNAL_VALUE) { return true } else { return await super.isVoidExternalValue(value) } } /** * @description 在更新自己之前会先更新所有子 Inputor。 */ override async updateValue(value: ExternalValue): Promise { await this.updateItemsFromValue(value) await super.updateValue(value) } protected async updateItemsFromValue(value: ExternalValue): Promise { const valueDict = this.decodeValue(value) await Promise.all( Object.entries(this.items).map(async ([key, item]) => { await (item.updateValue as GeneralUpdateValue)(valueDict[key]) }), ) } }