import { Objects } from '@ephox/boulder'; import { Fun } from '@ephox/katamari'; import { TransformFind } from '@ephox/sugar'; import * as EventRoot from '../../alien/EventRoot'; import * as EventHandler from '../../construct/EventHandler'; import type { EventFormat, SimulatedEvent } from '../../events/SimulatedEvent'; import type { AlloyComponent } from '../component/ComponentApi'; import type { CompositeSketchDetail } from '../ui/Sketcher'; import * as AlloyTriggers from './AlloyTriggers'; import * as SystemEvents from './SystemEvents'; export type AlloyEventRecord = Record>; export interface AlloyEventHandler { can: EventCanHandler; abort: EventAbortHandler; run: EventRunHandler; } export interface AlloyEventKeyAndHandler { key: string; value: AlloyEventHandler; } type RunOnName = (handler: EventRunHandler) => AlloyEventKeyAndHandler; type RunOnSourceName = (handler: EventRunHandler) => AlloyEventKeyAndHandler; export type EventRunHandler = (component: AlloyComponent, se: SimulatedEvent, ...others: any[]) => void; export type EventAbortHandler = (comp: AlloyComponent, se: SimulatedEvent) => boolean; export type EventCanHandler = (comp: AlloyComponent, se: SimulatedEvent) => boolean; const derive = (configs: Array>): AlloyEventRecord => Objects.wrapAll(configs) as AlloyEventRecord; // const combine = (configs...); const abort = (name: string, predicate: EventAbortHandler): AlloyEventKeyAndHandler => { return { key: name, value: EventHandler.nu({ abort: predicate }) }; }; const can = (name: string, predicate: EventCanHandler): AlloyEventKeyAndHandler => { return { key: name, value: EventHandler.nu({ can: predicate }) }; }; const preventDefault = (name: string): AlloyEventKeyAndHandler => { return { key: name, value: EventHandler.nu({ run: (component: AlloyComponent, simulatedEvent: SimulatedEvent) => { simulatedEvent.event.prevent(); } }) }; }; const run = (name: string, handler: EventRunHandler): AlloyEventKeyAndHandler => { return { key: name, value: EventHandler.nu({ run: handler }) }; }; // Extra can be used when your handler needs more context, and is declared in one spot // It's really just convenient partial application. const runActionExtra = (name: string, action: (t: AlloyComponent, se: SimulatedEvent, u: any) => void, extra: any[]): AlloyEventKeyAndHandler => { return { key: name, value: EventHandler.nu({ run: (component: AlloyComponent, simulatedEvent: SimulatedEvent) => { action.apply(undefined, ([ component, simulatedEvent ] as any).concat(extra)); } }) }; }; const runOnName = (name: string): RunOnName => { return (handler) => run(name, handler); }; const runOnSourceName = (name: string): RunOnSourceName => { return (handler: (component: AlloyComponent, simulatedEvent: SimulatedEvent) => void): AlloyEventKeyAndHandler => ({ key: name, value: EventHandler.nu({ run: (component: AlloyComponent, simulatedEvent: SimulatedEvent) => { if (EventRoot.isSource(component, simulatedEvent)) { handler(component, simulatedEvent); } } }) }); }; const redirectToUid = (name: string, uid: string): AlloyEventKeyAndHandler => { return run(name, (component: AlloyComponent, simulatedEvent: SimulatedEvent) => { component.getSystem().getByUid(uid).each((redirectee) => { AlloyTriggers.dispatchEvent(redirectee, redirectee.element, name, simulatedEvent); }); }); }; const redirectToPart = (name: string, detail: CompositeSketchDetail, partName: string): AlloyEventKeyAndHandler => { const uid = detail.partUids[partName]; return redirectToUid(name, uid); }; const runWithTarget = (name: string, f: (component: AlloyComponent, target: AlloyComponent, se: SimulatedEvent) => void): AlloyEventKeyAndHandler => { return run(name, (component, simulatedEvent) => { const ev: T = simulatedEvent.event; const target = component.getSystem().getByDom(ev.target).getOrThunk( // If we don't find an alloy component for the target, I guess we go up the tree // until we find an alloy component? Performance concern? // TODO: Write tests for this. () => { const closest = TransformFind.closest(ev.target, (el) => component.getSystem().getByDom(el).toOptional(), Fun.never); // If we still found nothing ... fire on component itself; return closest.getOr(component); } ); f(component, target, simulatedEvent); }); }; const cutter = (name: string): AlloyEventKeyAndHandler => { return run(name, (component, simulatedEvent) => { simulatedEvent.cut(); }); }; const stopper = (name: string): AlloyEventKeyAndHandler => { return run(name, (component, simulatedEvent) => { simulatedEvent.stop(); }); }; const runOnSource = (name: string, f: EventRunHandler): AlloyEventKeyAndHandler => { return runOnSourceName(name)(f); }; const runOnAttached = runOnSourceName(SystemEvents.attachedToDom()); const runOnDetached = runOnSourceName(SystemEvents.detachedFromDom()); const runOnInit = runOnSourceName(SystemEvents.systemInit()); const runOnExecute = runOnName(SystemEvents.execute()); export { derive, run, preventDefault, runActionExtra, runOnAttached, runOnDetached, runOnSource, runOnInit, runOnExecute, redirectToUid, redirectToPart, runWithTarget, abort, can, cutter, stopper };