import { Obj, type Optional } from '@ephox/katamari'; import { SugarBody, type SugarElement } from '@ephox/sugar'; import type { AlloyComponent } from '../api/component/ComponentApi'; import { type ElementAndHandler, EventRegistry, type UidAndHandler } from '../events/EventRegistry'; import * as AlloyLogger from '../log/AlloyLogger'; import * as Tagger from './Tagger'; export interface Registry { readonly find: (isAboveRoot: (elem: SugarElement) => boolean, type: string, target: SugarElement) => Optional; readonly filter: (type: string) => UidAndHandler[]; readonly register: (component: AlloyComponent) => void; readonly unregister: (component: AlloyComponent) => void; readonly getById: (id: string) => Optional; } export const Registry = (): Registry => { const events = EventRegistry(); // An index of uid -> built components const components: Record = { }; const readOrTag = (component: AlloyComponent): string => { const elem = component.element; return Tagger.read(elem).getOrThunk(() => // No existing tag, so add one. Tagger.write('uid-', component.element) ); }; const failOnDuplicate = (component: AlloyComponent, tagId: string): void => { const conflict = components[tagId]; if (conflict === component) { unregister(component); } else { throw new Error( 'The tagId "' + tagId + '" is already used by: ' + AlloyLogger.element(conflict.element) + '\nCannot use it for: ' + AlloyLogger.element(component.element) + '\n' + 'The conflicting element is' + (SugarBody.inBody(conflict.element) ? ' ' : ' not ') + 'already in the DOM' ); } }; const register = (component: AlloyComponent): void => { const tagId = readOrTag(component); if (Obj.hasNonNullableKey(components, tagId)) { failOnDuplicate(component, tagId); } // Component is passed through an an extra argument to all events const extraArgs = [ component ]; events.registerId(extraArgs, tagId, component.events); components[tagId] = component; }; const unregister = (component: AlloyComponent): void => { Tagger.read(component.element).each((tagId) => { delete components[tagId]; events.unregisterId(tagId); }); }; const filter = (type: string): UidAndHandler[] => events.filterByType(type); const find = (isAboveRoot: (elem: SugarElement) => boolean, type: string, target: SugarElement): Optional => events.find(isAboveRoot, type, target); const getById = (id: string): Optional => Obj.get(components, id); return { find, filter, register, unregister, getById }; };