UNPKG

@agnos-ui/angular-headless

Version:

Headless component library for Angular.

924 lines (911 loc) 38.6 kB
export * from '@agnos-ui/core/services/siblingsInert'; export * from '@agnos-ui/core/services/resizeObserver'; export * from '@agnos-ui/core/services/portal'; export * from '@agnos-ui/core/services/navManager'; export * from '@agnos-ui/core/services/matchMedia'; export * from '@agnos-ui/core/services/intersection'; export * from '@agnos-ui/core/services/hash'; export * from '@agnos-ui/core/services/focustrack'; export * from '@agnos-ui/core/services/floatingUI'; export * from '@agnos-ui/core/services/extendWidget'; export * from '@agnos-ui/core/services/transitions/simpleClassTransition'; export * from '@agnos-ui/core/services/transitions/cssTransitions'; export * from '@agnos-ui/core/services/transitions/collapse'; export * from '@agnos-ui/core/services/transitions/baseTransitions'; export * from '@agnos-ui/core/utils/writables'; import { writable, computed } from '@amadeus-it-group/tansu'; import * as i0 from '@angular/core'; import { inject, NgZone, Injectable, signal, DestroyRef, Injector, runInInjectionContext, Directive, ElementRef, PLATFORM_ID, afterNextRender, input, computed as computed$1, InjectionToken, SkipSelf, Optional, booleanAttribute, numberAttribute, viewChild, ChangeDetectionStrategy, Component, createComponent, EnvironmentInjector, TemplateRef, reflectComponentType, ViewContainerRef } from '@angular/core'; import { toReadableStore } from '@agnos-ui/core/utils/stores'; export * from '@agnos-ui/core/utils/stores'; export * from '@agnos-ui/core/utils/func'; import { multiDirective } from '@agnos-ui/core/utils/directive'; export * from '@agnos-ui/core/utils/directive'; import { isPlatformServer } from '@angular/common'; import { FACTORY_WIDGET_NAME } from '@agnos-ui/core/types'; export * from '@agnos-ui/core/types'; import { createWidgetsConfig } from '@agnos-ui/core/config'; export * from '@agnos-ui/core/config'; export * from '@agnos-ui/core/components/tree'; import { Toaster } from '@agnos-ui/core/components/toast'; export * from '@agnos-ui/core/components/toast'; export * from '@agnos-ui/core/components/slider'; export * from '@agnos-ui/core/components/select'; export * from '@agnos-ui/core/components/rating'; export * from '@agnos-ui/core/components/progressbar'; export * from '@agnos-ui/core/components/pagination'; export * from '@agnos-ui/core/components/modal'; export * from '@agnos-ui/core/components/carousel'; export * from '@agnos-ui/core/components/alert'; export * from '@agnos-ui/core/components/accordion'; const noop = () => { }; const identity = (a) => a; const createObjectWrapper = (wrap) => (object) => { if (!object || typeof object !== 'object') { return object; } const res = {}; for (const key of Object.keys(object)) { res[key] = wrap(object[key]); } return res; }; const createReturnValueWrapper = (wrapReturnValue, wrapResult) => (fn) => wrapResult(typeof fn === 'function' ? ((...args) => wrapReturnValue(fn(...args))) : fn); /** * A utility class that provides methods to run functions inside or outside of Angular's NgZone. * This can be useful for optimizing performance by avoiding unnecessary change detection cycles. */ class ZoneWrapper { constructor() { this.#zone = inject(NgZone); this.#hasZone = this.#zone.run(() => NgZone.isInAngularZone()); // check if zone is enabled (can be NoopZone, cf https://angular.io/guide/zone#noopzone) this.#runNeeded = false; this.#runPlanned = false; this.planNgZoneRun = this.#hasZone ? () => { if (this.#zone.isStable) { this.#runNeeded = true; if (!this.#runPlanned) { this.#runPlanned = true; void (async () => { await Promise.resolve(); this.#runPlanned = false; if (this.#runNeeded) { this.ngZoneRun(noop); } })(); } } } : noop; this.insideNgZone = this.#hasZone ? (fn) => (typeof fn === 'function' ? ((...args) => this.ngZoneRun(() => fn(...args))) : fn) : identity; this.insideNgZoneWrapFunctionsObject = createObjectWrapper(this.insideNgZone); this.outsideNgZone = this.#hasZone ? (fn) => (typeof fn === 'function' ? ((...args) => this.#zone.runOutsideAngular(() => fn(...args))) : fn) : identity; this.outsideNgZoneWrapFunctionsObject = createObjectWrapper(this.outsideNgZone); this.outsideNgZoneWrapDirective = createReturnValueWrapper(this.outsideNgZoneWrapFunctionsObject, this.outsideNgZone); this.outsideNgZoneWrapDirectivesObject = createObjectWrapper(this.outsideNgZoneWrapDirective); } #zone; #hasZone; // check if zone is enabled (can be NoopZone, cf https://angular.io/guide/zone#noopzone) #runNeeded; #runPlanned; /** * Run the input function synchronously within the Angular zone * * @param fn - a function to run * @returns the value returned by the function */ ngZoneRun(fn) { this.#runNeeded = false; return this.#zone.run(fn); } static { this.ɵfac = function ZoneWrapper_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ZoneWrapper)(); }; } static { this.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: ZoneWrapper, factory: ZoneWrapper.ɵfac, providedIn: 'root' }); } } (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ZoneWrapper, [{ type: Injectable, args: [{ providedIn: 'root', }] }], null, null); })(); /** * Converts a Tansu `ReadableSignal` to an Angular `Signal`. * * This function wraps the provided Tansu signal in an Angular signal, ensuring that updates * are properly handled within Angular's zone. It subscribes to the Tansu signal and updates * the Angular signal with the received values. The equality function for the Angular signal * is set to always return false, ensuring that every new value from the Tansu signal triggers * an update. * * @template T - The type of the value emitted by the signals. * @param tansuSignal - The Tansu signal to convert. * @returns - The resulting Angular signal. */ const toAngularSignal = (tansuSignal) => { const zoneWrapper = inject(ZoneWrapper); // The equality of objects from 2 sequential emissions is already checked in tansu signal. // Here we'll always emit the value received from tansu signal, therefor the equality function const res = signal(undefined, { equal: () => false }); const subscription = zoneWrapper.outsideNgZone(tansuSignal.subscribe)((value) => { res.set(value); zoneWrapper.planNgZoneRun(); }); inject(DestroyRef).onDestroy(zoneWrapper.outsideNgZone(subscription)); return res.asReadonly(); }; /** * Converts a Tansu `WritableSignal` to an Angular `WritableSignal`. * * This function wraps the provided Tansu signal in an Angular signal, ensuring that updates * are properly handled within Angular's zone. It subscribes to the Tansu signal and updates * the Angular signal with the received values. The equality function for the Angular signal * is set to always return false, ensuring that every new value from the Tansu signal triggers * an update. * * @template T - The type of the value emitted by the signals. * @param tansuSignal - The Tansu signal to convert. * @returns - The resulting Angular signal. */ const toAngularWritableSignal = (tansuSignal) => { const zoneWrapper = inject(ZoneWrapper); const res = signal(undefined, { equal: () => false }); const set = res.set.bind(res); const subscription = zoneWrapper.outsideNgZone(tansuSignal.subscribe)((value) => { set(value); zoneWrapper.planNgZoneRun(); }); inject(DestroyRef).onDestroy(zoneWrapper.outsideNgZone(subscription)); res.set = zoneWrapper.outsideNgZone(tansuSignal.set); res.update = zoneWrapper.outsideNgZone(tansuSignal.update); return res; }; const createPatchSlots = (set) => { let lastValue = {}; return (object) => { const newValue = {}; let hasChange = false; for (const key of Object.keys(object)) { const objectKey = object[key]; if (objectKey != null) { // only use defined slots newValue[key] = objectKey; } if (objectKey != lastValue[key]) { hasChange = true; } } if (hasChange) { lastValue = newValue; set(newValue); } }; }; /** * Call a widget factory using provided configs. * * The resulting widget can be easily hooked into the lifecycle of an Angular component through {@link BaseWidgetDirective}. * * @param factory - the widget factory to call * @param options - the options * @param options.defaultConfig - the default config of the widget * @param options.widgetConfig - the config of the widget, overriding the defaultConfig * @param options.events - the events of the widget * @param options.afterInit - a callback to call after successful setup of the widget * @param options.slotTemplates - a function to provide all slot templates using child queries * @param options.slotChildren - a function to provide the default children slot using a view query * @returns the widget */ const callWidgetFactoryWithConfig = (factory, options) => { let { defaultConfig, widgetConfig, events, afterInit, slotTemplates, slotChildren } = options ?? {}; const injector = inject(Injector); const slots$ = writable({}); const props = {}; let initDone; const patchSlots = createPatchSlots(slots$.set); const res = { initialized: new Promise((resolve) => { initDone = resolve; }), updateSlots: () => { if (slotTemplates) { patchSlots(slotTemplates()); } }, patch(newProps) { // temporary function replaced in ngInit Object.assign(props, newProps); }, ngInit() { runInInjectionContext(injector, () => { const zoneWrapper = inject(ZoneWrapper); factory = zoneWrapper.outsideNgZone(factory); const defaultConfig$ = toReadableStore(defaultConfig); events = zoneWrapper.insideNgZoneWrapFunctionsObject(events); const widget = factory({ config: computed(() => ({ ...defaultConfig$(), children: slotChildren?.(), ...widgetConfig?.(), ...slots$(), ...events, })), props, }); Object.assign(res, { patch: zoneWrapper.outsideNgZone(widget.patch), directives: zoneWrapper.outsideNgZoneWrapDirectivesObject(widget.directives), api: zoneWrapper.outsideNgZoneWrapFunctionsObject(widget.api), state: Object.fromEntries(Object.entries(widget.stores).map(([key, val]) => [key.slice(0, -1), toAngularSignal(val)])), }); afterInit?.(res); initDone(); }); }, }; return res; }; function patchSimpleChanges(patchFn, changes) { const obj = {}; for (const [key, simpleChange] of Object.entries(changes)) { if (simpleChange !== undefined) { obj[key] = simpleChange.currentValue; } } patchFn(obj); } /** * An abstract base class for widget directives, providing common functionality * for Angular components that interact with widgets. * * @template W - The type of the widget. */ class BaseWidgetDirective { // eslint-disable-next-line @angular-eslint/prefer-inject constructor(_widget) { this._widget = _widget; } /** * Retrieves the widget api * @returns the widget api */ get api() { return this._widget.api; } /** * Retrieves the widget state. Each property of the state is exposed through an Angular {@link https://angular.dev/api/core/Signal | Signal} * @returns the widget state */ get state() { return this._widget.state; } /** * Retrieves the widget directives * @returns the widget directives */ get directives() { return this._widget.directives; } /** * @inheritdoc * @internal */ ngOnChanges(changes) { patchSimpleChanges(this._widget.patch, changes); } /** @internal */ ngOnInit() { this._widget.ngInit(); } /** @internal */ ngAfterContentChecked() { this._widget.updateSlots(); } static { this.ɵfac = function BaseWidgetDirective_Factory(__ngFactoryType__) { i0.ɵɵinvalidFactory(); }; } static { this.ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: BaseWidgetDirective, features: [i0.ɵɵNgOnChangesFeature] }); } } (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(BaseWidgetDirective, [{ type: Directive }], () => [{ type: undefined }], null); })(); /** * A utility function to manage the lifecycle of a directive for a host element. * * This function handles the creation, updating, and destruction of a directive instance * associated with a host element. It ensures that the directive is called appropriately * based on the platform (server or client) and manages the directive's lifecycle within * the Angular injection context. * * @template T - The type of parameters that the directive accepts. * * @param [directive] - The directive to be applied to the host element. * @param [params] - The parameters to be passed to the directive. * * @returns An object containing an `update` function to update the directive and its parameters. */ const useDirectiveForHost = (directive, params) => { const injector = inject(Injector); const ref = inject(ElementRef); const platform = inject(PLATFORM_ID); let instance; let plannedCallDirective = false; const callDirective = isPlatformServer(platform) ? () => { instance = directive?.(ref.nativeElement, params); } : () => { if (plannedCallDirective || !directive) { return; } plannedCallDirective = true; runInInjectionContext(injector, () => { afterNextRender(() => { plannedCallDirective = false; instance = directive?.(ref.nativeElement, params); }); }); }; function destroyDirectiveInstance() { const oldInstance = instance; instance = undefined; directive = undefined; oldInstance?.destroy?.(); } inject(DestroyRef).onDestroy(destroyDirectiveInstance); function update(newDirective, newParams) { if (newDirective !== directive) { void destroyDirectiveInstance(); directive = newDirective; params = newParams; callDirective(); } else if (newParams != params) { params = newParams; instance?.update?.(params); } } callDirective(); return { update }; }; /** * A directive that allows the use of another directive with optional parameters. * * @template T - The type of the parameter that can be passed to the directive. * * @remarks * This directive uses a private instance of {@link useDirectiveForHost} to manage the directive and its parameter. */ class UseDirective { constructor() { this.use = input.required({ alias: 'auUse' }); this.#useDirective = useDirectiveForHost(); } #useDirective; /** @internal */ ngOnChanges() { const use = this.use(); const [directive, param] = Array.isArray(use) ? use : [use]; this.#useDirective.update(directive, param); } static { this.ɵfac = function UseDirective_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || UseDirective)(); }; } static { this.ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: UseDirective, selectors: [["", "auUse", ""]], inputs: { use: [1, "auUse", "use"] }, features: [i0.ɵɵNgOnChangesFeature] }); } } (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(UseDirective, [{ type: Directive, args: [{ selector: '[auUse]', }] }], null, null); })(); /** * A directive that allows the use of multiple directives on a host element. * * @template T - A tuple type representing the directives and their optional parameters. */ class UseMultiDirective { constructor() { /** * An input property that takes a tuple of directives and their optional parameters. */ this.useMulti = input.required({ alias: 'auUseMulti' }); this.#useDirective = useDirectiveForHost(); } #useDirective; /** @internal */ ngOnChanges() { this.#useDirective.update(multiDirective, this.useMulti()); } static { this.ɵfac = function UseMultiDirective_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || UseMultiDirective)(); }; } static { this.ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: UseMultiDirective, selectors: [["", "auUseMulti", ""]], inputs: { useMulti: [1, "auUseMulti", "useMulti"] }, features: [i0.ɵɵNgOnChangesFeature] }); } } (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(UseMultiDirective, [{ type: Directive, args: [{ selector: '[auUseMulti]', }] }], null, null); })(); /** * Represents a template for a component with specified properties. * * @template Props - The type of properties that the template accepts. * @template K - The key in the template object that maps to the template reference. * @template T - An object type where each key of type K maps to a TemplateRef of Props. * * @param component - The component type that contains the template. * @param templateProp - The key in the component that maps to the template reference. */ class ComponentTemplate { constructor(component, templateProp) { this.component = component; this.templateProp = templateProp; } } /** * A directive representing a slot component that can be used to manage the state and context of a widget. * * @template W - The type of the widget that this slot component manages. */ class SlotComponent { constructor() { /** * The state of the widget. Each property of the state is exposed through an Angular {@link https://angular.dev/api/core/Signal | Signal} */ this.state = input.required(); /** * all the api functions to interact with the widget */ this.api = input.required(); /** * directives to be used on html elements in the template of the slot */ this.directives = input.required(); /** * The slot context, to be used when the slot component uses other slots. */ this.slotContext = computed$1(() => ({ state: this.state(), api: this.api(), directives: this.directives() })); } static { this.ɵfac = function SlotComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || SlotComponent)(); }; } static { this.ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: SlotComponent, inputs: { state: [1, "state"], api: [1, "api"], directives: [1, "directives"] } }); } } (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SlotComponent, [{ type: Directive }], null, null); })(); /** * A factory to create the utilities to allow widgets to be context-aware. * * It can be used when extending the core and creating new widgets. * * @template Config - The type of the widgets configuration object. * @param widgetsConfigInjectionToken - the widgets config injection token * @returns the utilities to create / manage widgets and contexts */ const widgetsConfigFactory = (widgetsConfigInjectionToken = new InjectionToken('widgetsConfig')) => { /** * Creates a provider of widgets default configuration that inherits from any widgets default configuration already defined at an upper level * in the Angular dependency injection system. It contains its own set of widgets configuration properties that override the same properties form * the parent configuration. * * @remarks * The configuration is computed from the parent configuration in two steps: * - first step: the parent configuration is transformed by the adaptParentConfig function (if specified). * If adaptParentConfig is not specified, this step is skipped. * - second step: the configuration from step 1 is merged (2-levels deep) with the own$ store. The own$ store initially contains * an empty object (i.e. no property from the parent is overridden). It can be changed by calling set on the store returned by * {@link injectWidgetsConfig}. * * @param adaptParentConfig - optional function that receives a 2-levels copy of the widgets default configuration * defined at an upper level in the Angular dependency injection system (or an empty object if there is none) and returns the widgets * default configuration to be used. * It is called only if the configuration is needed, and was not yet computed for the current value of the parent configuration. * It is called in a tansu reactive context, so it can use any tansu store and will be called again if those stores change. * It is also called in an Angular injection context, so it can call the Angular inject function to get and use dependencies from the * Angular dependency injection system. * * @returns DI provider to be included a list of `providers` (for example at a component level or * any other level of the Angular dependency injection system) * * @example * ```typescript * @Component({ * // ... * providers: [ * provideWidgetsConfig((parentConfig) => { * // first step configuration: transforms the parent configuration * parentConfig.rating = parentConfig.rating ?? {}; * parentConfig.rating.className = `${parentConfig.rating.className ?? ''} my-rating-extra-class` * return parentConfig; * }) * ] * }) * class MyComponent { * widgetsConfig = injectWidgetsConfig(); * constructor() { * this.widgetsConfig.set({ * // second step configuration: overrides the parent configuration * rating: { * slotStar: MyCustomSlotStar * } * }); * } * // ... * } * ``` */ const provideWidgetsConfig = (adaptParentConfig) => ({ provide: widgetsConfigInjectionToken, useFactory: (parent) => { if (adaptParentConfig) { const injector = inject(Injector); const originalAdaptParentConfig = adaptParentConfig; adaptParentConfig = (value) => runInInjectionContext(injector, () => originalAdaptParentConfig(value)); } return createWidgetsConfig(parent ?? undefined, adaptParentConfig); }, deps: [[new SkipSelf(), new Optional(), widgetsConfigInjectionToken]], }); /** * Returns the widgets default configuration store that was provided in the current injection context. * Throws if the no widgets default configuration store was provided. * * @param defaultConfig - values to set as soon as the config is injected * @remarks * This function must be called from an injection context, such as a constructor, a factory function, a field initializer or * a function used with {@link https://angular.io/api/core/runInInjectionContext | runInInjectionContext}. * * @returns the widgets default configuration store. */ const injectWidgetsConfig = (defaultConfig) => { const widgetsConfig = inject(widgetsConfigInjectionToken); if (defaultConfig) { widgetsConfig.set(defaultConfig); } return widgetsConfig; }; /** * Injects the configuration for a specific widget. * * @template N - The key of the widget configuration in the `Config` type. * @param widgetName - The name of the widget whose configuration is to be injected. * @returns A `ReadableSignal` that provides a partial configuration of the specified widget or `undefined` if the configuration is not available. */ const injectWidgetConfig = (widgetName) => { const widgetsConfig = inject(widgetsConfigInjectionToken, { optional: true }); return computed(() => widgetsConfig?.()[widgetName]); }; /** * Creates and initializes a widget using the provided factory and configuration options. * * The resulting widget can be easily hooked into the lifecycle of an Angular component through {@link BaseWidgetDirective}. * * @template W - The type of the widget. * @param factory - The factory function to create the widget. * @param options - The options for creating the widget. * @param options.defaultConfig - The default configuration for the widget. * @param options.events - The event handlers for the widget. * @param options.slotTemplates - A function that returns the slot templates for the widget. * @param options.slotChildren - A function that returns the slot children for the widget. * @param options.afterInit - A callback function to be called after the widget is initialized. * @returns The initialized widget. */ const callWidgetFactory = (factory, options) => callWidgetFactoryWithConfig(factory, { widgetConfig: factory[FACTORY_WIDGET_NAME] ? injectWidgetConfig(factory[FACTORY_WIDGET_NAME]) : undefined, defaultConfig: options?.defaultConfig, events: options?.events, afterInit: options?.afterInit, slotTemplates: options?.slotTemplates, slotChildren: options?.slotChildren, }); return { /** * Dependency Injection token which can be used to provide or inject the widgets default configuration store. */ widgetsConfigInjectionToken, provideWidgetsConfig, injectWidgetsConfig, injectWidgetConfig, callWidgetFactory, }; }; const { widgetsConfigInjectionToken, provideWidgetsConfig, injectWidgetConfig, injectWidgetsConfig, callWidgetFactory } = widgetsConfigFactory(); /** * Injection token used to provide configuration properties for the toaster service. * * This token is associated with the `ToasterProps` interface, which defines the * structure of the configuration object. It allows dependency injection to supply * custom properties for the toaster service, such as default settings or behavior. */ const ToastPropsToken = new InjectionToken('ToasterProps'); /** * Create a toaster provider with helpers and state. * @param props Options for the toaster. * @template Props Type of the toast properties. */ class ToasterService { constructor() { this.optionsCore = inject(ToastPropsToken, { optional: true }); this.#toaster = new Toaster(this.optionsCore ?? undefined); this.toasts = toAngularSignal(this.#toaster.toasts); this.options = toAngularWritableSignal(this.#toaster.options); this.addToast = this.#toaster.addToast; this.removeToast = this.#toaster.removeToast; this.eventsDirective = this.#toaster.eventsDirective; this.closeAll = this.#toaster.closeAll; } #toaster; static { this.ɵfac = function ToasterService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ToasterService)(); }; } static { this.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: ToasterService, factory: ToasterService.ɵfac, providedIn: 'root' }); } } (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ToasterService, [{ type: Injectable, args: [{ providedIn: 'root', }] }], null, null); })(); /** * Transforms a value (typically a string) to a boolean. * Intended to be used as a transform function of an input. * * @example * ```readonly status = input({ transform: auBooleanAttribute });``` * @param value - Value to be transformed. * @returns the value transformed */ function auBooleanAttribute(value) { if (value === undefined) { return undefined; } return booleanAttribute(value); } /** * Transforms a value (typically a string) to a number. * Intended to be used as a transform function of an input. * @param value - Value to be transformed. * * @example * ```readonly id = input({ transform: auNumberAttribute });``` * @returns the value transformed */ function auNumberAttribute(value) { if (value === undefined) { return undefined; } return numberAttribute(value); } const _c0 = ["text"]; function StringSlotComponent_ng_template_0_Template(rf, ctx) { if (rf & 1) { i0.ɵɵtext(0); } if (rf & 2) { const content_r1 = ctx.content; i0.ɵɵtextInterpolate(content_r1); } } class SlotHandler { constructor(viewContainerRef) { this.viewContainerRef = viewContainerRef; } slotChange(_slot, _props) { } propsChange(_slot, _props) { } destroy() { } } class StringSlotComponent { constructor() { this.text = viewChild.required('text'); } static { this.ɵfac = function StringSlotComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || StringSlotComponent)(); }; } static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: StringSlotComponent, selectors: [["ng-component"]], viewQuery: function StringSlotComponent_Query(rf, ctx) { if (rf & 1) { i0.ɵɵviewQuerySignal(ctx.text, _c0, 5); } if (rf & 2) { i0.ɵɵqueryAdvance(); } }, decls: 2, vars: 0, consts: [["text", ""]], template: function StringSlotComponent_Template(rf, ctx) { if (rf & 1) { i0.ɵɵtemplate(0, StringSlotComponent_ng_template_0_Template, 1, 1, "ng-template", null, 0, i0.ɵɵtemplateRefExtractor); } }, encapsulation: 2, changeDetection: 0 }); } } (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(StringSlotComponent, [{ type: Component, args: [{ changeDetection: ChangeDetectionStrategy.OnPush, template: `<ng-template #text let-content="content">{{ content }}</ng-template>`, }] }], null, null); })(); (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(StringSlotComponent, { className: "StringSlotComponent", filePath: "slot.directive.ts", lineNumber: 29 }); })(); const stringSlotComponentTemplate = new ComponentTemplate(StringSlotComponent, 'text'); class StringSlotHandler extends SlotHandler { #templateRefSlotHandler = new ComponentTemplateSlotHandler(this.viewContainerRef); #initialized = false; slotChange(content) { if (!this.#initialized) { this.#initialized = true; this.#templateRefSlotHandler.slotChange(stringSlotComponentTemplate, { content }); } else { this.#templateRefSlotHandler.propsChange(stringSlotComponentTemplate, { content }); } } destroy() { this.#templateRefSlotHandler.destroy(); } } class FunctionSlotHandler extends SlotHandler { #stringSlotHandler = new StringSlotHandler(this.viewContainerRef); slotChange(slot, props) { this.#stringSlotHandler.slotChange(slot(props)); } propsChange(slot, props) { this.#stringSlotHandler.slotChange(slot(props)); } destroy() { this.#stringSlotHandler.destroy(); } } class ComponentSlotHandler extends SlotHandler { #componentRef; #properties; slotChange(slot, props) { if (this.#componentRef) { this.destroy(); } this.#componentRef = this.viewContainerRef.createComponent(slot); this.#applyProperties(props); } #applyProperties(props, oldProperties) { const properties = Object.keys(props); this.#properties = properties; const componentRef = this.#componentRef; for (const property of properties) { componentRef.setInput(property, props[property]); oldProperties?.delete(property); } } propsChange(_slot, props) { const oldProperties = new Set(this.#properties); this.#applyProperties(props, oldProperties); const componentRef = this.#componentRef; for (const property of oldProperties) { componentRef.setInput(property, undefined); } } destroy() { this.viewContainerRef.clear(); this.#componentRef = undefined; } } class TemplateRefSlotHandler extends SlotHandler { #viewRef; #props; slotChange(slot, props) { if (this.#viewRef) { this.destroy(); } props = { ...props }; this.#props = props; this.#viewRef = this.viewContainerRef.createEmbeddedView(slot, props); } propsChange(_slot, props) { if (this.#viewRef) { const templateProps = this.#props; const oldProperties = new Set(Object.keys(templateProps)); for (const property of Object.keys(props)) { templateProps[property] = props[property]; oldProperties.delete(property); } for (const oldProperty of oldProperties) { delete templateProps[oldProperty]; } this.#viewRef.markForCheck(); } } destroy() { this.viewContainerRef.clear(); } } class ComponentTemplateSlotHandler extends SlotHandler { #componentRef; #templateSlotHandler = new TemplateRefSlotHandler(this.viewContainerRef); #templateRef; slotChange(slot, props) { if (this.#componentRef) { this.destroy(); } this.#componentRef = createComponent(slot.component, { elementInjector: this.viewContainerRef.injector, environmentInjector: this.viewContainerRef.injector.get(EnvironmentInjector), }); this.#templateRef = this.#componentRef.instance[slot.templateProp]; this.#templateSlotHandler.slotChange(this.#templateRef(), props); } propsChange(_slot, props) { this.#templateSlotHandler.propsChange(this.#templateRef(), props); } destroy() { this.#templateSlotHandler.destroy(); this.#componentRef?.destroy(); this.#componentRef = undefined; } } const getSlotType = (value) => { if (!value) return undefined; const type = typeof value; switch (type) { case 'string': return StringSlotHandler; case 'function': if (reflectComponentType(value)) { return ComponentSlotHandler; } return FunctionSlotHandler; case 'object': if (value instanceof TemplateRef) { return TemplateRefSlotHandler; } if (value instanceof ComponentTemplate) { return ComponentTemplateSlotHandler; } break; } return undefined; }; /** * A directive that manages slot content and its properties. * * @template Props - A record type representing the properties for the slot. * * @remarks * This directive handles changes to the slot content and its properties, * and manages the lifecycle of the slot handler. */ class SlotDirective { constructor() { /** * The slot content to be managed. */ this.slot = input.required({ alias: 'auSlot' }); /** * The properties for the slot content. */ this.props = input.required({ alias: 'auSlotProps' }); this._viewContainerRef = inject(ViewContainerRef); } /** * @param changes SimpleChanges from Angular * @internal */ ngOnChanges(changes) { const slotChange = changes['slot']; const propsChange = changes['props']; const slot = this.slot(); if (slotChange) { const newSlotType = getSlotType(slot); if (newSlotType !== this._slotType) { this._slotHandler?.destroy(); this._slotHandler = newSlotType ? new newSlotType(this._viewContainerRef) : undefined; this._slotType = newSlotType; } this._slotHandler?.slotChange(slot, this.props()); } else if (propsChange) { this._slotHandler?.propsChange(slot, this.props()); } } /** @internal */ ngOnDestroy() { this._slotHandler?.destroy(); this._slotHandler = undefined; } static { this.ɵfac = function SlotDirective_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || SlotDirective)(); }; } static { this.ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: SlotDirective, selectors: [["", "auSlot", ""]], inputs: { slot: [1, "auSlot", "slot"], props: [1, "auSlotProps", "props"] }, features: [i0.ɵɵNgOnChangesFeature] }); } } (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SlotDirective, [{ type: Directive, args: [{ selector: '[auSlot]', }] }], null, null); })(); /* * Public API Surface of @agnos-ui/angular-headless */ /** * Generated bundle index. Do not edit. */ export { BaseWidgetDirective, ComponentTemplate, SlotComponent, SlotDirective, ToastPropsToken, ToasterService, UseDirective, UseMultiDirective, ZoneWrapper, auBooleanAttribute, auNumberAttribute, callWidgetFactory, callWidgetFactoryWithConfig, injectWidgetConfig, injectWidgetsConfig, provideWidgetsConfig, toAngularSignal, toAngularWritableSignal, useDirectiveForHost, widgetsConfigFactory, widgetsConfigInjectionToken }; //# sourceMappingURL=agnos-ui-angular-headless.mjs.map