import { createInterface, singletonRegistration } from '../utilities-di'; import { getOwnPropertyNames, objectFreeze, baseObjectPrototype } from '../utilities'; import { type Constructable, type IContainer, type AnyFunction, type FunctionPropNames, IRegistry, registrableMetadataKey } from '@aurelia/kernel'; export type LifecycleHook = TViewModel[TKey] extends (AnyFunction | undefined) ? (vm: TViewModel, ...args: Parameters>) => ReturnType> : never; export type ILifecycleHooks = { [K in TKey]-?: LifecycleHook; }; export const ILifecycleHooks = /*@__PURE__*/createInterface>('ILifecycleHooks'); export type LifecycleHooksLookup = { [K in FunctionPropNames]?: readonly LifecycleHooksEntry[]; }; export class LifecycleHooksEntry { public constructor( public readonly definition: LifecycleHooksDefinition, public readonly instance: ILifecycleHooks, ) {} } /** * This definition has no specific properties yet other than the type, but is in place for future extensions. * * See: https://github.com/aurelia/aurelia/issues/1044 */ export class LifecycleHooksDefinition { private constructor( public readonly Type: T, public readonly propertyNames: ReadonlySet, ) {} /** * @param def - Placeholder for future extensions. Currently always an empty object. */ public static create(def: {}, Type: T): LifecycleHooksDefinition { const propertyNames = new Set(); let proto = Type.prototype; while (proto !== baseObjectPrototype) { for (const name of getOwnPropertyNames(proto)) { // This is the only check we will do for now. Filtering on e.g. function types might not always work properly when decorators come into play. This would need more testing first. if (name !== 'constructor' && !name.startsWith('_')) { propertyNames.add(name); } } proto = Object.getPrototypeOf(proto); } return new LifecycleHooksDefinition(Type, propertyNames); } } export const LifecycleHooks = /*@__PURE__*/(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const containerLookup = new WeakMap>(); // const lhBaseName = getAnnotationKeyFor('lifecycle-hooks'); const definitionMap = new WeakMap(); return objectFreeze({ // name: lhBaseName, /** * @param def - Placeholder for future extensions. Currently always an empty object. */ define(def: {}, Type: T): IRegistry { const definition = LifecycleHooksDefinition.create(def, Type); const $Type = definition.Type; definitionMap.set($Type, definition); return { register(container: IContainer): void { singletonRegistration(ILifecycleHooks, $Type).register(container); } }; }, /** * @param ctx - The container where the resolution starts * @param Type - The constructor of the Custom element/ Custom attribute with lifecycle metadata */ resolve(ctx: IContainer): LifecycleHooksLookup { let lookup = containerLookup.get(ctx); if (lookup === void 0) { containerLookup.set(ctx, lookup = new LifecycleHooksLookupImpl()); const root = ctx.root; const instances = root === ctx ? ctx.getAll(ILifecycleHooks) // if it's not root, only resolve it from the current context when it has the resolver // to maintain resources semantic: current -> root : ctx.has(ILifecycleHooks, false) ? root.getAll(ILifecycleHooks).concat(ctx.getAll(ILifecycleHooks)) : root.getAll(ILifecycleHooks); let instance: ILifecycleHooks; let definition: LifecycleHooksDefinition; let entry: LifecycleHooksEntry; let name: string; let entries: LifecycleHooksEntry[]; for (instance of instances) { definition = definitionMap.get(instance.constructor as Constructable)!; entry = new LifecycleHooksEntry(definition, instance); for (name of definition.propertyNames) { entries = lookup[name] as LifecycleHooksEntry[]; if (entries === void 0) { lookup[name] = [entry]; } else { entries.push(entry); } } } } return lookup; }, }); })(); class LifecycleHooksLookupImpl implements LifecycleHooksLookup {} /** * Decorator: Indicates that the decorated class is a custom element. */ export function lifecycleHooks(): (target: T, context: ClassDecoratorContext) => T; export function lifecycleHooks(target: T, context: ClassDecoratorContext): T; export function lifecycleHooks(target?: T, context?: ClassDecoratorContext): T | ((target: T, context: ClassDecoratorContext) => T) { function decorator(target: T, context: ClassDecoratorContext): T { const metadata = context?.metadata ?? (target[Symbol.metadata] ??= Object.create(null)); metadata[registrableMetadataKey] = LifecycleHooks.define({}, target); return target; } return target == null ? decorator : decorator(target, context!); }