UNPKG

@progress/kendo-angular-dialog

Version:
234 lines (233 loc) 10.8 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ // eslint-disable max-line-length import { Subject, merge } from 'rxjs'; import { take, filter, share, map } from 'rxjs/operators'; import { Injectable, ComponentFactoryResolver, Renderer2, TemplateRef, isDevMode, Inject } from '@angular/core'; import { DialogComponent } from './dialog.component'; import { DialogContainerService } from './dialog-container.service'; import { DialogRef } from './models/dialog-ref'; import { DialogContentBase } from './dialog-content-base'; import { PreventableEvent } from '../common/preventable-event'; import { isString } from '../common/util'; import { DialogCloseResult } from './models'; import * as i0 from "@angular/core"; import * as i1 from "./dialog-container.service"; const isNotComponent = (component) => isString(component) || component instanceof TemplateRef; class DialogInjector { getDialogRef; parentInjector; constructor(getDialogRef, parentInjector) { this.getDialogRef = getDialogRef; this.parentInjector = parentInjector; } get(token, notFoundValue) { if (token === DialogRef) { return this.getDialogRef(); } return this.parentInjector.get(token, notFoundValue); } } /** * A service for opening Dialog windows dynamically * ([see example]({% slug service_dialog %})). */ export class DialogService { resolver; containerService; constructor( /** * @hidden */ resolver, containerService) { this.resolver = resolver; this.containerService = containerService; } /** * Opens a Dialog window. Requires an element in the application that uses the * [`kendoDialogContainer`]({% slug api_dialog_dialogcontainerdirective %}) directive. * Created Dialogs will be mounted in the DOM directly after that element. * * @param {DialogAction} options - The options that define the Dialog. * @returns {DialogRef} - A reference to the Dialog object and the convenience properties. * * @example * * ```ts-no-run * _@Component({ * selector: 'my-app', * template: ` * <button kendoButton (click)="open()">Harmless button</button> * <div kendoDialogContainer></div> * ` * }) * export class AppComponent { * constructor( private dialogService: DialogService ) {} * * public open() { * var dialog = this.dialogService.open({ * title: "Please confirm", * content: "Are you sure?", * actions: [ * { text: "No" }, * { text: "Yes", themeColor: 'primary' } * ] * }); * * dialog.result.subscribe((result) => { * if (result instanceof DialogCloseResult) { * console.log("close"); * } else { * console.log("action", result); * } * }); * } * } * ``` * */ open(options) { const factory = this.resolver.resolveComponentFactory(DialogComponent); const container = options.appendTo || this.containerService.container; if (!container) { throw new Error(` Cannot attach dialog to the page. Add an element that uses the kendoDialogContainer directive, or set the 'appendTo' property. See https://www.telerik.com/kendo-angular-ui/components/dialogs/dialog/service/. `); } // create DialogRef to (1) pass as result, (2) provide through injector const dialogRef = { close: () => { /* noop */ }, content: null, dialog: null, result: null }; return this.initializeDialog(options.content, factory, container, dialogRef, options); } initializeDialog(component, factory, container, dialogRef, options) { const content = this.contentFrom(component, container, dialogRef); const dialog = container.createComponent(factory, undefined, undefined, content.nodes); dialogRef.dialog = dialog; dialog.changeDetectorRef.markForCheck(); // copy @Input options to dialog instance this.applyOptions(dialog.instance, options); // create close handler and result stream const apiClose = new Subject(); const close = (e) => { if (e instanceof PreventableEvent) { e = new DialogCloseResult(); } apiClose.next(e || new DialogCloseResult()); if (content.componentRef) { content.componentRef.destroy(); } dialog.destroy(); }; const result = merge(apiClose, // triggered when the titlebar or actions are defined in DialogSettings merge(dialog.instance.close, dialog.instance.action).pipe(map(e => (e instanceof PreventableEvent ? new DialogCloseResult() : e)), filter(e => { if (options.preventAction) { // add dialogRef only when using component const dialogRefParameter = isNotComponent(component) ? undefined : dialogRef; return !options.preventAction(e, dialogRefParameter); } return true; }))).pipe(take(1), // Takes care for multiple subscriptions: // We subscribe internaly and the user may subscribe to get a close result - dialog.result.subscribe(). // This causes multiple subscriptions to the same source and thus multiple emissions. share() solves that. share()); result.subscribe(close); dialogRef.close = close; dialogRef.result = result; if (component && isDevMode()) { const hasContentTitle = content.nodes[0] && content.nodes[0].length > 0; const hasContentActions = content.nodes[2] && content.nodes[2].length > 0; const multipleTitles = options.title && hasContentTitle; const multipleActions = options.actions && hasContentActions; if (component.prototype instanceof DialogContentBase) { // content component extends DialogContentBase if (multipleTitles || multipleActions) { console.warn(` Multiple Title and/or Actions configurations detected. When using a component as content, provide the title and actions either in the component's markup or via the title and actions properties of the DialogSettings object, but not both. See https://www.telerik.com/kendo-angular-ui/components/dialogs/dialog/service/#toc-passing-title-content-and-actions-as-a-single-component'`); } } else { if (hasContentTitle || hasContentActions) { console.warn(` When Title and/or Actions markup is provided in content component's template, the component needs to inherit the DialogContentBase class to ensure that close and result events are properly hooked. See https://www.telerik.com/kendo-angular-ui/components/dialogs/dialog/service/#toc-passing-title-content-and-actions-as-a-single-component'`); } } } return dialogRef; } applyOptions(instance, options) { instance.title = options.title; instance.actions = options.actions; instance.actionsLayout = options.actionsLayout || 'stretched'; instance.width = options.width; instance.minWidth = options.minWidth; instance.maxWidth = options.maxWidth; instance.height = options.height; instance.minHeight = options.minHeight; instance.maxHeight = options.maxHeight; instance.autoFocusedElement = options.autoFocusedElement; instance.themeColor = options.themeColor != undefined ? options.themeColor : null; instance.closeTitle = options.closeTitle; instance.cssClass = options.cssClass; instance.htmlAttributes = options.htmlAttributes; instance.animation = options.animation !== undefined ? options.animation : true; if (options.content instanceof TemplateRef) { instance.contentTemplate = options.content; } } contentFrom(content, container, dialogRef) { const renderer = container.injector.get(Renderer2); let nodes = []; let titleNodes = []; let actionNodes = []; let componentRef = null; if (typeof content === 'string') { nodes = [renderer.createText(content)]; } else if (content && !(content instanceof TemplateRef)) { // Component const injector = new DialogInjector(() => dialogRef, container.injector); const factory = this.resolver.resolveComponentFactory(content); componentRef = container.createComponent(factory, undefined, injector); titleNodes = Array.from(componentRef.location.nativeElement.querySelectorAll('kendo-dialog-titlebar')); nodes = [componentRef.location.nativeElement]; actionNodes = Array.from(componentRef.location.nativeElement.querySelectorAll('kendo-dialog-actions')); dialogRef.content = componentRef; } return { componentRef, nodes: [ titleNodes, nodes, actionNodes // <ng-content select="kendo-dialog-actions"> ] }; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DialogService, deps: [{ token: i0.ComponentFactoryResolver }, { token: DialogContainerService }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DialogService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DialogService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: function () { return [{ type: i0.ComponentFactoryResolver }, { type: i1.DialogContainerService, decorators: [{ type: Inject, args: [DialogContainerService] }] }]; } });