/* eslint-disable @typescript-eslint/no-unsafe-return */ import * as glassEasel from 'glass-easel' import { Behavior, ComponentType, TraitBehavior } from '../behavior' import type { BehaviorDefinition, utils as typeUtils } from '../types' import type { GeneralBehavior } from '../behavior' import type { AllData, Component, GeneralComponent } from '../component' import type { CodeSpace } from '../space' import type { ResolveBehaviorBuilder, Lifetimes, RelationParams, TraitRelationParams, BuilderContext, } from './type_utils' type Empty = typeUtils.Empty type DataList = typeUtils.DataList type PropertyList = typeUtils.PropertyList type PropertyType = typeUtils.PropertyType type PropertyTypeToValueType = typeUtils.PropertyTypeToValueType type MethodList = typeUtils.MethodList type ChainingFilterType = typeUtils.ChainingFilterType type ComponentMethod = typeUtils.ComponentMethod type TaggedMethod = typeUtils.TaggedMethod type UnTaggedMethod> = typeUtils.UnTaggedMethod export class BaseBehaviorBuilder< TPrevData extends DataList, TData extends DataList, TProperty extends PropertyList, TMethod extends MethodList, TChainingFilter extends ChainingFilterType, TPendingChainingFilter extends ChainingFilterType, TComponentExport, TExtraThisFields extends DataList, > { protected _$codeSpace!: CodeSpace protected _$!: glassEasel.BehaviorBuilder< TPrevData, TData, TProperty, TMethod, TChainingFilter, TPendingChainingFilter, TExtraThisFields > protected _$parents: GeneralBehavior[] = [] protected _$export?: (source: GeneralComponent | null) => TComponentExport /** Implement a trait behavior */ implement( traitBehavior: TraitBehavior, impl: TIn, ): ResolveBehaviorBuilder { this._$.implement(traitBehavior._$, impl) return this as any } /** Add external classes */ externalClasses(list: string[]): this { this._$.externalClasses(list) return this } export( f: (this: GeneralComponent, source: GeneralComponent | null) => TNewComponentExport, ): ResolveBehaviorBuilder< BaseBehaviorBuilder< TPrevData, TData, TProperty, TMethod, TChainingFilter, TPendingChainingFilter, TNewComponentExport, TExtraThisFields >, TChainingFilter > { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this._$export = f as any return this as any } /** Use another behavior */ behavior< UData extends DataList, UProperty extends PropertyList, UMethod extends MethodList, UChainingFilter extends ChainingFilterType, UComponentExport, UExtraThisFields extends DataList, >( behavior: Behavior< UData, UProperty, UMethod, UChainingFilter, UComponentExport, UExtraThisFields >, ): ResolveBehaviorBuilder< BaseBehaviorBuilder< TPrevData, TData & UData, TProperty & UProperty, TMethod & UMethod, UChainingFilter, TPendingChainingFilter, UComponentExport, TExtraThisFields & UExtraThisFields >, UChainingFilter > { this._$parents.push(behavior) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this._$ = this._$.behavior(behavior._$) if (behavior._$chainingFilter) { return behavior._$chainingFilter(this as any) } return this as any } data( gen: () => typeUtils.NewFieldList, T>, ): ResolveBehaviorBuilder< BaseBehaviorBuilder< T, TData & T, TProperty, TMethod, TChainingFilter, TPendingChainingFilter, TComponentExport, TExtraThisFields >, TChainingFilter > { this._$.data(gen) return this as any } staticData( data: typeUtils.NewFieldList, T>, ): ResolveBehaviorBuilder< BaseBehaviorBuilder< T, TData & T, TProperty, TMethod, TChainingFilter, TPendingChainingFilter, TComponentExport, TExtraThisFields >, TChainingFilter > { this._$.staticData(data) return this as any } property>( name: N, def: N extends keyof (TData & TProperty) ? never : typeUtils.PropertyListItem, ): ResolveBehaviorBuilder< BaseBehaviorBuilder< TPrevData, TData, TProperty & Record>, TMethod, TChainingFilter, TPendingChainingFilter, TComponentExport, TExtraThisFields >, TChainingFilter > { this._$.property(name, def) return this as any } /** * Add some public methods * * The public method can be used as an event handler, and can be visited in component instance. */ methods( funcs: T & ThisType>, ): ResolveBehaviorBuilder< BaseBehaviorBuilder< TPrevData, TData, TProperty, TMethod & T, TChainingFilter, TPendingChainingFilter, TComponentExport, TExtraThisFields >, TChainingFilter > { this._$.methods(funcs) return this as any } /** * Add a data observer */ observer< P extends typeUtils.ObserverDataPathStrings< typeUtils.DataWithPropertyValues >, V = typeUtils.GetFromObserverPathString< typeUtils.DataWithPropertyValues, P >, >( paths: P, func: (this: Component, newValue: V) => void, once?: boolean, ): ResolveBehaviorBuilder observer< P extends typeUtils.ObserverDataPathStrings< typeUtils.DataWithPropertyValues >[], V = { [K in keyof P]: typeUtils.GetFromObserverPathString< typeUtils.DataWithPropertyValues, P[K] > }, >( paths: readonly [...P], func: ( this: Component, ...newValues: V extends any[] ? V : never ) => void, once?: boolean, ): ResolveBehaviorBuilder observer( paths: string | readonly string[], func: ( this: Component, ...args: any[] ) => any, once = false, ): ResolveBehaviorBuilder { this._$.observer(paths as any, func as any, once) return this as any } /** * Add a lifetime callback */ lifetime( name: L, func: ( this: Component, ...args: Parameters ) => ReturnType, once = false, ): ResolveBehaviorBuilder { this._$.lifetime(name, func as any, once) return this as any } /** * Add a page-lifetime callback */ pageLifetime( name: string, func: ( this: Component, ...args: any[] ) => any, once = false, ): ResolveBehaviorBuilder { this._$.pageLifetime(name, func as any, once) return this as any } /** * Add a relation */ relation( name: string, rel: RelationParams & ThisType>, ): ResolveBehaviorBuilder { const target = rel.target instanceof Behavior || rel.target instanceof ComponentType || rel.target instanceof TraitBehavior ? rel.target._$ : rel.target this._$.relation(name, { target, type: rel.type, linked: rel.linked, linkChanged: rel.linkChanged, unlinked: rel.unlinked, linkFailed: rel.linkFailed, } as any) return this as any } init any>> | void>( func: ( this: Component, builderContext: BuilderContext< TPrevData, TProperty, Component >, ) => TExport, // eslint-disable-next-line function-paren-newline ): ResolveBehaviorBuilder< BaseBehaviorBuilder< TPrevData, TData, TProperty, TMethod & (TExport extends void ? Empty : { [K in keyof TExport]: UnTaggedMethod }), TChainingFilter, TPendingChainingFilter, TComponentExport, TExtraThisFields >, TChainingFilter > { this._$.init(function ({ data, setData, implement, relation, observer, lifetime, pageLifetime, method, listener, }) { const relationInit = ((rel: RelationParams | TraitRelationParams) => { const target = rel.target instanceof Behavior || rel.target instanceof ComponentType || rel.target instanceof TraitBehavior ? rel.target._$ : rel.target return relation({ target, type: rel.type, linked: rel.linked, linkChanged: rel.linkChanged, unlinked: rel.unlinked, linkFailed: rel.linkFailed, } as any) }) as BuilderContext['relation'] const implementInit = ((traitBehavior: TraitBehavior, impl: Record) => { implement(traitBehavior._$, impl) }) as BuilderContext['implement'] const methodCaller = this as unknown as Component< TData, TProperty, TMethod, TComponentExport, TExtraThisFields > const exported = func.call(methodCaller, { self: methodCaller, data, setData: (newData, callback) => { setData(newData) if (callback) { glassEasel.triggerRender(methodCaller._$, () => { callback() }) } }, implement: implementInit, relation: relationInit, observer, lifetime, pageLifetime, method, listener, }) if (exported) { const exportedKeys = Object.keys(exported) for (let j = 0; j < exportedKeys.length; j += 1) { const exportedKey = exportedKeys[j]! const exportItem: unknown = exported[exportedKey] if (glassEasel.Component.isTaggedMethod(exportItem)) { ;(methodCaller as { [key: string]: unknown })[exportedKey] = exportItem } } } return exported }) return this as any } definition< TNewData extends DataList = Empty, TNewProperty extends PropertyList = Empty, TNewMethod extends MethodList = Empty, TNewComponentExport = never, >( def: BehaviorDefinition & ThisType< Component< TData & TNewData, TProperty & TNewProperty, TMethod & TNewMethod, TNewComponentExport, TExtraThisFields > >, ): ResolveBehaviorBuilder< BaseBehaviorBuilder< TPrevData, TData & TNewData, TProperty & TNewProperty, TMethod & TNewMethod, TChainingFilter, TPendingChainingFilter, TNewComponentExport, TExtraThisFields >, TChainingFilter > { def.behaviors?.forEach((beh) => beh._$boundDefinitionFilter?.(def)) const inner = this._$ const { behaviors, properties: rawProperties, data: rawData, observers: rawObservers, methods, created, attached, ready, moved, detached, lifetimes: rawLifetimes, pageLifetimes: rawPageLifetimes, relations: rawRelations, externalClasses, export: exports, } = def behaviors?.forEach((beh) => { this._$parents.push(beh) inner.behavior(beh._$) }) if (rawData !== undefined) { if (typeof rawData === 'function') { inner.data(rawData as any) } else { inner.staticData(rawData as any) } } if (rawProperties !== undefined) { const keys = Object.keys(rawProperties) for (let i = 0; i < keys.length; i += 1) { const name = keys[i]! // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion inner.property(name, rawProperties[name] as any) } } if (rawObservers !== undefined) { if (Array.isArray(rawObservers)) { for (let i = 0; i < rawObservers.length; i += 1) { const { fields, observer } = rawObservers[i]! inner.observer(fields || ('**' as any), observer) } } else { const keys = Object.keys(rawObservers) for (let i = 0; i < keys.length; i += 1) { const fields = keys[i]! const observer = rawObservers[fields]! inner.observer(fields as any, observer) } } } if (methods) inner.methods(methods) if (created && rawLifetimes?.created === undefined) inner.lifetime('created', created, true) if (attached && rawLifetimes?.attached === undefined) inner.lifetime('attached', attached, true) if (ready && rawLifetimes?.ready === undefined) inner.lifetime('ready', ready, true) if (moved && rawLifetimes?.moved === undefined) inner.lifetime('moved', moved, true) if (detached && rawLifetimes?.detached === undefined) inner.lifetime('detached', detached, true) if (rawLifetimes) { const keys = Object.keys(rawLifetimes) for (let i = 0; i < keys.length; i += 1) { const name = keys[i]! const func = rawLifetimes[name]! inner.lifetime(name as any, func, true) } } if (rawPageLifetimes) { const keys = Object.keys(rawPageLifetimes) for (let i = 0; i < keys.length; i += 1) { const name = keys[i]! const func = rawPageLifetimes[name]! inner.pageLifetime(name, func, true) } } if (rawRelations) { const keys = Object.keys(rawRelations) for (let i = 0; i < keys.length; i += 1) { const name = keys[i]! const rel = rawRelations[name]! const target = rel.target instanceof Behavior || rel.target instanceof ComponentType || rel.target instanceof TraitBehavior ? rel.target._$ : rel.target inner.relation(name, { target, type: rel.type, linked: rel.linked, linkChanged: rel.linkChanged, unlinked: rel.unlinked, linkFailed: rel.linkFailed, } as any) } } if (externalClasses) inner.externalClasses(externalClasses) // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment if (exports) this._$export = exports as any return this as any } }