import { type StructureProcessor, FieldSchema, StructureSchema, type FieldProcessor } from '@ephox/boulder'; import { Fun, Obj, Optional, Optionals, Thunk } from '@ephox/katamari'; import type { AlloyComponent } from '../../api/component/ComponentApi'; import * as AlloyEvents from '../../api/events/AlloyEvents'; import * as FunctionAnnotator from '../../debugging/FunctionAnnotator'; import type { DomDefinitionDetail } from '../../dom/DomDefinition'; import * as DomModification from '../../dom/DomModification'; import type { CustomEvent } from '../../events/SimulatedEvent'; import type { BehaviourConfigAndState } from './BehaviourBlob'; import type { BehaviourState, BehaviourStateInitialiser } from './BehaviourState'; import type { AlloyBehaviour, BehaviourActiveSpec, BehaviourApiFunc, BehaviourApisRecord, BehaviourConfigDetail, BehaviourConfigSpec, BehaviourExtraRecord, BehaviourInfo, NamedConfiguredBehaviour } from './BehaviourTypes'; export type WrappedApiFunc any> = T extends (comp: AlloyComponent, config: any, state: any, ...args: infer P) => infer R ? (comp: AlloyComponent, ...args: P) => R : never; type Executor = (component: AlloyComponent, bconfig: D, bState: S) => void; export type AlloyBehaviourWithApis< C extends BehaviourConfigSpec, D extends BehaviourConfigDetail, S extends BehaviourState, A extends BehaviourApisRecord, E extends BehaviourExtraRecord > = AlloyBehaviour & { [K in keyof A]: WrappedApiFunc } & E; const executeEvent = (bConfig: C, bState: S, executor: Executor): AlloyEvents.AlloyEventKeyAndHandler => AlloyEvents.runOnExecute((component) => { executor(component, bConfig, bState); }); const loadEvent = (bConfig: C, bState: S, f: Executor): AlloyEvents.AlloyEventKeyAndHandler => AlloyEvents.runOnInit((component, _simulatedEvent) => { f(component, bConfig, bState); }); const create = < C extends BehaviourConfigSpec, D extends BehaviourConfigDetail, S extends BehaviourState, A extends BehaviourApisRecord, E extends BehaviourExtraRecord >(schema: FieldProcessor[], name: string, active: BehaviourActiveSpec, apis: A, extra: E, state: BehaviourStateInitialiser): AlloyBehaviourWithApis => { const configSchema = StructureSchema.objOfOnly(schema); const schemaSchema = FieldSchema.optionObjOf(name, [ FieldSchema.optionObjOfOnly('config', schema) ]); return doCreate(configSchema, schemaSchema, name, active, apis, extra, state); }; const createModes = < C extends BehaviourConfigSpec, D extends BehaviourConfigDetail, S extends BehaviourState, A extends BehaviourApisRecord, E extends BehaviourExtraRecord >(modes: StructureProcessor, name: string, active: BehaviourActiveSpec, apis: A, extra: E, state: BehaviourStateInitialiser): AlloyBehaviourWithApis => { const configSchema = modes; const schemaSchema = FieldSchema.optionObjOf(name, [ FieldSchema.optionOf('config', modes) ]); return doCreate(configSchema, schemaSchema, name, active, apis, extra, state); }; const wrapApi = (bName: string, apiFunction: BehaviourApiFunc, apiName: string) => { const f = (component: AlloyComponent, ...rest: any[]) => { const args = [ component ].concat(rest); return component.config({ name: Fun.constant(bName) } as AlloyBehaviour).fold( () => { throw new Error('We could not find any behaviour configuration for: ' + bName + '. Using API: ' + apiName); }, (info) => { const rest = Array.prototype.slice.call(args, 1); return apiFunction.apply(undefined, ([ component, info.config, info.state ] as any).concat(rest)); } ); }; return FunctionAnnotator.markAsBehaviourApi(f, apiName, apiFunction); }; // I think the "revoke" idea is fragile at best. const revokeBehaviour = (name: string): NamedConfiguredBehaviour => ({ key: name, value: undefined }); const doCreate = < C extends BehaviourConfigSpec, D extends BehaviourConfigDetail, S extends BehaviourState, A extends BehaviourApisRecord, E extends BehaviourExtraRecord >(configSchema: StructureProcessor, schemaSchema: FieldProcessor, name: string, active: BehaviourActiveSpec, apis: A, extra: E, state: BehaviourStateInitialiser): AlloyBehaviourWithApis => { const getConfig = (info: BehaviourInfo) => Obj.hasNonNullableKey(info, name) ? info[name]() : Optional.none>(); const wrappedApis = Obj.map(apis, (apiF, apiName) => wrapApi(name, apiF, apiName)) as { [K in keyof A]: WrappedApiFunc }; const wrappedExtra = Obj.map(extra, (extraF, extraName) => FunctionAnnotator.markAsExtraApi(extraF, extraName)) as E; const me: AlloyBehaviour & typeof wrappedApis & typeof extra = { ...wrappedExtra, ...wrappedApis, revoke: Fun.curry(revokeBehaviour, name), config: (spec: C) => { const prepared = StructureSchema.asRawOrDie(name + '-config', configSchema, spec); return { key: name, value: { config: prepared, me, configAsRaw: Thunk.cached(() => StructureSchema.asRawOrDie(name + '-config', configSchema, spec)), initialConfig: spec, state } }; }, schema: Fun.constant(schemaSchema), exhibit: (info: BehaviourInfo, base: DomDefinitionDetail) => { return Optionals.lift2(getConfig(info), Obj.get(active, 'exhibit'), (behaviourInfo, exhibitor) => { return exhibitor(base, behaviourInfo.config, behaviourInfo.state); }).getOrThunk(() => DomModification.nu({ })); }, name: Fun.constant(name), handlers: (info: BehaviourInfo) => { return getConfig(info).map((behaviourInfo) => { const getEvents = Obj.get(active, 'events').getOr(() => ({ })); return getEvents(behaviourInfo.config, behaviourInfo.state); }).getOr({ }); } }; return me; }; export { executeEvent, loadEvent, create, createModes };