UNPKG

@progress/kendo-angular-popup

Version:

Kendo UI Angular Popup component - an easily customized popup from the most trusted provider of professional Angular components.

242 lines (241 loc) 9.83 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { ApplicationRef, ComponentFactoryResolver, ElementRef, InjectionToken, Injectable, Injector, Inject, Optional, TemplateRef } from '@angular/core'; import { PopupComponent } from './popup.component'; import * as i0 from "@angular/core"; const removeElement = (element) => { if (element && element.parentNode) { element.parentNode.removeChild(element); } }; /** * Injects the Popup container. If not set, uses the first root component of the application. * * > Use `POPUP_CONTAINER` only with the `PopupService` class ([see example](slug:service_popup)). * * In standalone components: * * @example * ```ts * import { Component } from '@angular/core'; * import { KENDO_POPUP, PopupService } from '@progress/kendo-angular-popup'; * * @Component({ * standalone: true, * imports: [KENDO_POPUP], * providers: [PopupService, { * provide: POPUP_CONTAINER, * useFactory: () => { * //return the container ElementRef, where the popup will be injected * return { nativeElement: document.body } as ElementRef; * } * }], * selector: 'app-root', * templateUrl: './app.component.html', * }) * export class AppComponent {} * ``` * * In NgModule-based applications: * * @example * ```ts * import { PopupModule, POPUP_CONTAINER } from '@progress/kendo-angular-popup'; * import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; * import { ElementRef, NgModule } from '@angular/core'; * import { AppComponent } from './app.component'; * * _@NgModule({ * declarations: [AppComponent], * imports: [BrowserModule, PopupModule], * bootstrap: [AppComponent], * providers: [{ * provide: POPUP_CONTAINER, * useFactory: () => { * //return the container ElementRef, where the popup will be injected * return { nativeElement: document.body } as ElementRef; * } * }] * }) * export class AppModule {} * * platformBrowserDynamic().bootstrapModule(AppModule); * ``` */ export const POPUP_CONTAINER = new InjectionToken('Popup Container'); /** * Provides a service for opening Popup components dynamically ([see example]({% slug service_popup %})). * * @export * @class PopupService */ export class PopupService { applicationRef; componentFactoryResolver; injector; container; /** * Gets the root view container for injecting the component. * * @returns {ComponentRef<any>} The root view container reference. */ get rootViewContainer() { // https://github.com/angular/angular/blob/4.0.x/packages/core/src/application_ref.ts#L571 const rootComponents = this.applicationRef.components || []; if (rootComponents[0]) { return rootComponents[0]; } throw new Error(` View Container not found! Inject the POPUP_CONTAINER or define a specific ViewContainerRef via the appendTo option. See https://www.telerik.com/kendo-angular-ui/components/popup/api/POPUP_CONTAINER/ for more details. `); } /** * Gets the HTML element of the root component container. * * @returns {HTMLElement} The root container HTML element. */ get rootViewContainerNode() { return this.container ? this.container.nativeElement : this.getComponentRootNode(this.rootViewContainer); } constructor(applicationRef, componentFactoryResolver, injector, container) { this.applicationRef = applicationRef; this.componentFactoryResolver = componentFactoryResolver; this.injector = injector; this.container = container; } /** * Opens a Popup component. The Popup mounts in the DOM under the root application component. * * @param {PopupSettings} options - The options for the Popup. * @returns {ComponentRef<PopupComponent>} A reference to the Popup object. */ open(options = {}) { const { component, nodes } = this.contentFrom(options.content); const popupComponentRef = this.appendPopup(nodes, options.appendTo); const popupInstance = popupComponentRef.instance; this.projectComponentInputs(popupComponentRef, options); popupComponentRef.changeDetectorRef.detectChanges(); if (component) { component.changeDetectorRef.detectChanges(); } const popupElement = this.getComponentRootNode(popupComponentRef); return { close: () => { if (component) { component.destroy(); } popupComponentRef.destroy(); // Issue in Chrome causes https://github.com/telerik/kendo-angular/issues/4434 // To be fixed in Chrome, remove try..catch afterwards // https://chromestatus.com/feature/5128696823545856 // https://issues.chromium.org/issues/41484175 try { // Angular will not remove the element unless the change detection is triggered removeElement(popupElement); } catch { /* noop */ } }, content: component, popup: popupComponentRef, popupAnchorViewportLeave: popupInstance.anchorViewportLeave, popupClose: popupInstance.close, popupElement: popupElement, popupOpen: popupInstance.open, popupPositionChange: popupInstance.positionChange }; } appendPopup(nodes, container) { const popupComponentRef = this.createComponent(PopupComponent, nodes, container); if (!container) { this.rootViewContainerNode.appendChild(this.getComponentRootNode(popupComponentRef)); } return popupComponentRef; } /** * Gets the HTML element for a component reference. * * @param {ComponentRef<any>} componentRef The component reference. * @returns {HTMLElement} The root HTML element of the component. */ getComponentRootNode(componentRef) { return componentRef.location.nativeElement; } /** * Gets the `ComponentFactory` instance by type. * * @param {*} componentClass The component class. * @returns {ComponentFactory<any>} The component factory instance. */ getComponentFactory(componentClass) { return this.componentFactoryResolver.resolveComponentFactory(componentClass); } /** * Creates a component reference from a `Component` class. * * @param {*} componentClass The component class. * @param {*} nodes The nodes to project. * @param {ViewContainerRef} container The container to use. * @returns {ComponentRef<any>} The created component reference. */ createComponent(componentClass, nodes, container) { const factory = this.getComponentFactory(componentClass); if (container) { return container.createComponent(factory, undefined, this.injector, nodes); } else { const component = factory.create(this.injector, nodes); this.applicationRef.attachView(component.hostView); return component; } } /** * Projects the input options onto the component instance. * * @param {ComponentRef<any>} component The component reference. * @param {*} options The options to project. * @returns {ComponentRef<any>} The updated component reference. */ projectComponentInputs(component, options) { Object.getOwnPropertyNames(options) .filter(prop => prop !== 'content' || options.content instanceof TemplateRef) .map((prop) => { component.instance[prop] = options[prop]; }); return component; } /** * Gets the component and nodes to append from the `content` option. * * @param {*} content The content to use. * @returns {any} The component and nodes for projection. */ contentFrom(content) { if (!content || content instanceof TemplateRef) { return { component: null, nodes: [[]] }; } const component = this.createComponent(content); const nodes = component ? [component.location.nativeElement] : []; return { component: component, nodes: [ nodes // <ng-content> ] }; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PopupService, deps: [{ token: i0.ApplicationRef }, { token: i0.ComponentFactoryResolver }, { token: i0.Injector }, { token: POPUP_CONTAINER, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PopupService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PopupService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: function () { return [{ type: i0.ApplicationRef }, { type: i0.ComponentFactoryResolver }, { type: i0.Injector }, { type: i0.ElementRef, decorators: [{ type: Inject, args: [POPUP_CONTAINER] }, { type: Optional }] }]; } });