123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882 |
- import { DOCUMENT } from '@angular/common';
- import * as i0 from '@angular/core';
- import { inject, ElementRef, NgZone, Renderer2, ChangeDetectorRef, Injector, afterNextRender, Component, ViewEncapsulation, ChangeDetectionStrategy, ViewChild, InjectionToken, TemplateRef, Injectable, NgModule } from '@angular/core';
- import { Subject, defer, of } from 'rxjs';
- import { B as BasePortalOutlet, f as CdkPortalOutlet, C as ComponentPortal, T as TemplatePortal, h as PortalModule } from './portal-directives-Bw5woq8I.mjs';
- export { d as ɵɵCdkPortal, g as ɵɵPortalHostDirective, e as ɵɵTemplatePortalDirective } from './portal-directives-Bw5woq8I.mjs';
- import { F as FocusTrapFactory, I as InteractivityChecker, A as A11yModule } from './a11y-module-BYox5gpI.mjs';
- import { c as OverlayRef, a as Overlay, O as OverlayContainer, f as OverlayConfig, m as OverlayModule } from './overlay-module-BUj0D19H.mjs';
- import { F as FocusMonitor } from './focus-monitor-e2l_RpN3.mjs';
- import { P as Platform } from './platform-DmdVEw_C.mjs';
- import { c as _getFocusedElementPierceShadowDom } from './shadow-dom-B0oHn41l.mjs';
- import { g as ESCAPE } from './keycodes-CpHkExLC.mjs';
- import { hasModifierKey } from './keycodes.mjs';
- import { startWith, take } from 'rxjs/operators';
- import { _ as _IdGenerator } from './id-generator-Dw_9dSDu.mjs';
- import { D as Directionality } from './directionality-CBXD4hga.mjs';
- import './style-loader-Cu9AvjH9.mjs';
- import './private.mjs';
- import './breakpoints-observer-CljOfYGy.mjs';
- import './array-I1yfCXUO.mjs';
- import './observers.mjs';
- import './element-x4z00URv.mjs';
- import './backwards-compatibility-DHR38MsD.mjs';
- import './test-environment-CT0XxPyp.mjs';
- import './css-pixel-value-C_HEqLhI.mjs';
- import './scrolling.mjs';
- import './scrolling-BkvA05C8.mjs';
- import './bidi.mjs';
- import './recycle-view-repeater-strategy-DoWdPqVw.mjs';
- import './data-source-D34wiQZj.mjs';
- import './fake-event-detection-DWOdFTFz.mjs';
- import './passive-listeners-esHZRgIN.mjs';
- /** Configuration for opening a modal dialog. */
- class DialogConfig {
- /**
- * Where the attached component should live in Angular's *logical* component tree.
- * This affects what is available for injection and the change detection order for the
- * component instantiated inside of the dialog. This does not affect where the dialog
- * content will be rendered.
- */
- viewContainerRef;
- /**
- * Injector used for the instantiation of the component to be attached. If provided,
- * takes precedence over the injector indirectly provided by `ViewContainerRef`.
- */
- injector;
- /** ID for the dialog. If omitted, a unique one will be generated. */
- id;
- /** The ARIA role of the dialog element. */
- role = 'dialog';
- /** Optional CSS class or classes applied to the overlay panel. */
- panelClass = '';
- /** Whether the dialog has a backdrop. */
- hasBackdrop = true;
- /** Optional CSS class or classes applied to the overlay backdrop. */
- backdropClass = '';
- /** Whether the dialog closes with the escape key or pointer events outside the panel element. */
- disableClose = false;
- /** Width of the dialog. */
- width = '';
- /** Height of the dialog. */
- height = '';
- /** Min-width of the dialog. If a number is provided, assumes pixel units. */
- minWidth;
- /** Min-height of the dialog. If a number is provided, assumes pixel units. */
- minHeight;
- /** Max-width of the dialog. If a number is provided, assumes pixel units. */
- maxWidth;
- /** Max-height of the dialog. If a number is provided, assumes pixel units. */
- maxHeight;
- /** Strategy to use when positioning the dialog. Defaults to centering it on the page. */
- positionStrategy;
- /** Data being injected into the child component. */
- data = null;
- /** Layout direction for the dialog's content. */
- direction;
- /** ID of the element that describes the dialog. */
- ariaDescribedBy = null;
- /** ID of the element that labels the dialog. */
- ariaLabelledBy = null;
- /** Dialog label applied via `aria-label` */
- ariaLabel = null;
- /**
- * Whether this is a modal dialog. Used to set the `aria-modal` attribute. Off by default,
- * because it can interfere with other overlay-based components (e.g. `mat-select`) and because
- * it is redundant since the dialog marks all outside content as `aria-hidden` anyway.
- */
- ariaModal = false;
- /**
- * Where the dialog should focus on open.
- * @breaking-change 14.0.0 Remove boolean option from autoFocus. Use string or
- * AutoFocusTarget instead.
- */
- autoFocus = 'first-tabbable';
- /**
- * Whether the dialog should restore focus to the previously-focused element upon closing.
- * Has the following behavior based on the type that is passed in:
- * - `boolean` - when true, will return focus to the element that was focused before the dialog
- * was opened, otherwise won't restore focus at all.
- * - `string` - focus will be restored to the first element that matches the CSS selector.
- * - `HTMLElement` - focus will be restored to the specific element.
- */
- restoreFocus = true;
- /**
- * Scroll strategy to be used for the dialog. This determines how
- * the dialog responds to scrolling underneath the panel element.
- */
- scrollStrategy;
- /**
- * Whether the dialog should close when the user navigates backwards or forwards through browser
- * history. This does not apply to navigation via anchor element unless using URL-hash based
- * routing (`HashLocationStrategy` in the Angular router).
- */
- closeOnNavigation = true;
- /**
- * Whether the dialog should close when the dialog service is destroyed. This is useful if
- * another service is wrapping the dialog and is managing the destruction instead.
- */
- closeOnDestroy = true;
- /**
- * Whether the dialog should close when the underlying overlay is detached. This is useful if
- * another service is wrapping the dialog and is managing the destruction instead. E.g. an
- * external detachment can happen as a result of a scroll strategy triggering it or when the
- * browser location changes.
- */
- closeOnOverlayDetachments = true;
- /**
- * Alternate `ComponentFactoryResolver` to use when resolving the associated component.
- * @deprecated No longer used. Will be removed.
- * @breaking-change 20.0.0
- */
- componentFactoryResolver;
- /**
- * Providers that will be exposed to the contents of the dialog. Can also
- * be provided as a function in order to generate the providers lazily.
- */
- providers;
- /**
- * Component into which the dialog content will be rendered. Defaults to `CdkDialogContainer`.
- * A configuration object can be passed in to customize the providers that will be exposed
- * to the dialog container.
- */
- container;
- /**
- * Context that will be passed to template-based dialogs.
- * A function can be passed in to resolve the context lazily.
- */
- templateContext;
- }
- function throwDialogContentAlreadyAttachedError() {
- throw Error('Attempting to attach dialog content after content is already attached');
- }
- /**
- * Internal component that wraps user-provided dialog content.
- * @docs-private
- */
- class CdkDialogContainer extends BasePortalOutlet {
- _elementRef = inject(ElementRef);
- _focusTrapFactory = inject(FocusTrapFactory);
- _config;
- _interactivityChecker = inject(InteractivityChecker);
- _ngZone = inject(NgZone);
- _overlayRef = inject(OverlayRef);
- _focusMonitor = inject(FocusMonitor);
- _renderer = inject(Renderer2);
- _changeDetectorRef = inject(ChangeDetectorRef);
- _injector = inject(Injector);
- _platform = inject(Platform);
- _document = inject(DOCUMENT, { optional: true });
- /** The portal outlet inside of this container into which the dialog content will be loaded. */
- _portalOutlet;
- _focusTrapped = new Subject();
- /** The class that traps and manages focus within the dialog. */
- _focusTrap = null;
- /** Element that was focused before the dialog was opened. Save this to restore upon close. */
- _elementFocusedBeforeDialogWasOpened = null;
- /**
- * Type of interaction that led to the dialog being closed. This is used to determine
- * whether the focus style will be applied when returning focus to its original location
- * after the dialog is closed.
- */
- _closeInteractionType = null;
- /**
- * Queue of the IDs of the dialog's label element, based on their definition order. The first
- * ID will be used as the `aria-labelledby` value. We use a queue here to handle the case
- * where there are two or more titles in the DOM at a time and the first one is destroyed while
- * the rest are present.
- */
- _ariaLabelledByQueue = [];
- _isDestroyed = false;
- constructor() {
- super();
- // Callback is primarily for some internal tests
- // that were instantiating the dialog container manually.
- this._config = (inject(DialogConfig, { optional: true }) || new DialogConfig());
- if (this._config.ariaLabelledBy) {
- this._ariaLabelledByQueue.push(this._config.ariaLabelledBy);
- }
- }
- _addAriaLabelledBy(id) {
- this._ariaLabelledByQueue.push(id);
- this._changeDetectorRef.markForCheck();
- }
- _removeAriaLabelledBy(id) {
- const index = this._ariaLabelledByQueue.indexOf(id);
- if (index > -1) {
- this._ariaLabelledByQueue.splice(index, 1);
- this._changeDetectorRef.markForCheck();
- }
- }
- _contentAttached() {
- this._initializeFocusTrap();
- this._handleBackdropClicks();
- this._captureInitialFocus();
- }
- /**
- * Can be used by child classes to customize the initial focus
- * capturing behavior (e.g. if it's tied to an animation).
- */
- _captureInitialFocus() {
- this._trapFocus();
- }
- ngOnDestroy() {
- this._focusTrapped.complete();
- this._isDestroyed = true;
- this._restoreFocus();
- }
- /**
- * Attach a ComponentPortal as content to this dialog container.
- * @param portal Portal to be attached as the dialog content.
- */
- attachComponentPortal(portal) {
- if (this._portalOutlet.hasAttached() && (typeof ngDevMode === 'undefined' || ngDevMode)) {
- throwDialogContentAlreadyAttachedError();
- }
- const result = this._portalOutlet.attachComponentPortal(portal);
- this._contentAttached();
- return result;
- }
- /**
- * Attach a TemplatePortal as content to this dialog container.
- * @param portal Portal to be attached as the dialog content.
- */
- attachTemplatePortal(portal) {
- if (this._portalOutlet.hasAttached() && (typeof ngDevMode === 'undefined' || ngDevMode)) {
- throwDialogContentAlreadyAttachedError();
- }
- const result = this._portalOutlet.attachTemplatePortal(portal);
- this._contentAttached();
- return result;
- }
- /**
- * Attaches a DOM portal to the dialog container.
- * @param portal Portal to be attached.
- * @deprecated To be turned into a method.
- * @breaking-change 10.0.0
- */
- attachDomPortal = (portal) => {
- if (this._portalOutlet.hasAttached() && (typeof ngDevMode === 'undefined' || ngDevMode)) {
- throwDialogContentAlreadyAttachedError();
- }
- const result = this._portalOutlet.attachDomPortal(portal);
- this._contentAttached();
- return result;
- };
- // TODO(crisbeto): this shouldn't be exposed, but there are internal references to it.
- /** Captures focus if it isn't already inside the dialog. */
- _recaptureFocus() {
- if (!this._containsFocus()) {
- this._trapFocus();
- }
- }
- /**
- * Focuses the provided element. If the element is not focusable, it will add a tabIndex
- * attribute to forcefully focus it. The attribute is removed after focus is moved.
- * @param element The element to focus.
- */
- _forceFocus(element, options) {
- if (!this._interactivityChecker.isFocusable(element)) {
- element.tabIndex = -1;
- // The tabindex attribute should be removed to avoid navigating to that element again
- this._ngZone.runOutsideAngular(() => {
- const callback = () => {
- deregisterBlur();
- deregisterMousedown();
- element.removeAttribute('tabindex');
- };
- const deregisterBlur = this._renderer.listen(element, 'blur', callback);
- const deregisterMousedown = this._renderer.listen(element, 'mousedown', callback);
- });
- }
- element.focus(options);
- }
- /**
- * Focuses the first element that matches the given selector within the focus trap.
- * @param selector The CSS selector for the element to set focus to.
- */
- _focusByCssSelector(selector, options) {
- let elementToFocus = this._elementRef.nativeElement.querySelector(selector);
- if (elementToFocus) {
- this._forceFocus(elementToFocus, options);
- }
- }
- /**
- * Moves the focus inside the focus trap. When autoFocus is not set to 'dialog', if focus
- * cannot be moved then focus will go to the dialog container.
- */
- _trapFocus(options) {
- if (this._isDestroyed) {
- return;
- }
- // If were to attempt to focus immediately, then the content of the dialog would not yet be
- // ready in instances where change detection has to run first. To deal with this, we simply
- // wait until after the next render.
- afterNextRender(() => {
- const element = this._elementRef.nativeElement;
- switch (this._config.autoFocus) {
- case false:
- case 'dialog':
- // Ensure that focus is on the dialog container. It's possible that a different
- // component tried to move focus while the open animation was running. See:
- // https://github.com/angular/components/issues/16215. Note that we only want to do this
- // if the focus isn't inside the dialog already, because it's possible that the consumer
- // turned off `autoFocus` in order to move focus themselves.
- if (!this._containsFocus()) {
- element.focus(options);
- }
- break;
- case true:
- case 'first-tabbable':
- const focusedSuccessfully = this._focusTrap?.focusInitialElement(options);
- // If we weren't able to find a focusable element in the dialog, then focus the dialog
- // container instead.
- if (!focusedSuccessfully) {
- this._focusDialogContainer(options);
- }
- break;
- case 'first-heading':
- this._focusByCssSelector('h1, h2, h3, h4, h5, h6, [role="heading"]', options);
- break;
- default:
- this._focusByCssSelector(this._config.autoFocus, options);
- break;
- }
- this._focusTrapped.next();
- }, { injector: this._injector });
- }
- /** Restores focus to the element that was focused before the dialog opened. */
- _restoreFocus() {
- const focusConfig = this._config.restoreFocus;
- let focusTargetElement = null;
- if (typeof focusConfig === 'string') {
- focusTargetElement = this._document.querySelector(focusConfig);
- }
- else if (typeof focusConfig === 'boolean') {
- focusTargetElement = focusConfig ? this._elementFocusedBeforeDialogWasOpened : null;
- }
- else if (focusConfig) {
- focusTargetElement = focusConfig;
- }
- // We need the extra check, because IE can set the `activeElement` to null in some cases.
- if (this._config.restoreFocus &&
- focusTargetElement &&
- typeof focusTargetElement.focus === 'function') {
- const activeElement = _getFocusedElementPierceShadowDom();
- const element = this._elementRef.nativeElement;
- // Make sure that focus is still inside the dialog or is on the body (usually because a
- // non-focusable element like the backdrop was clicked) before moving it. It's possible that
- // the consumer moved it themselves before the animation was done, in which case we shouldn't
- // do anything.
- if (!activeElement ||
- activeElement === this._document.body ||
- activeElement === element ||
- element.contains(activeElement)) {
- if (this._focusMonitor) {
- this._focusMonitor.focusVia(focusTargetElement, this._closeInteractionType);
- this._closeInteractionType = null;
- }
- else {
- focusTargetElement.focus();
- }
- }
- }
- if (this._focusTrap) {
- this._focusTrap.destroy();
- }
- }
- /** Focuses the dialog container. */
- _focusDialogContainer(options) {
- // Note that there is no focus method when rendering on the server.
- if (this._elementRef.nativeElement.focus) {
- this._elementRef.nativeElement.focus(options);
- }
- }
- /** Returns whether focus is inside the dialog. */
- _containsFocus() {
- const element = this._elementRef.nativeElement;
- const activeElement = _getFocusedElementPierceShadowDom();
- return element === activeElement || element.contains(activeElement);
- }
- /** Sets up the focus trap. */
- _initializeFocusTrap() {
- if (this._platform.isBrowser) {
- this._focusTrap = this._focusTrapFactory.create(this._elementRef.nativeElement);
- // Save the previously focused element. This element will be re-focused
- // when the dialog closes.
- if (this._document) {
- this._elementFocusedBeforeDialogWasOpened = _getFocusedElementPierceShadowDom();
- }
- }
- }
- /** Sets up the listener that handles clicks on the dialog backdrop. */
- _handleBackdropClicks() {
- // Clicking on the backdrop will move focus out of dialog.
- // Recapture it if closing via the backdrop is disabled.
- this._overlayRef.backdropClick().subscribe(() => {
- if (this._config.disableClose) {
- this._recaptureFocus();
- }
- });
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkDialogContainer, deps: [], target: i0.ɵɵFactoryTarget.Component });
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.6", type: CdkDialogContainer, isStandalone: true, selector: "cdk-dialog-container", host: { attributes: { "tabindex": "-1" }, properties: { "attr.id": "_config.id || null", "attr.role": "_config.role", "attr.aria-modal": "_config.ariaModal", "attr.aria-labelledby": "_config.ariaLabel ? null : _ariaLabelledByQueue[0]", "attr.aria-label": "_config.ariaLabel", "attr.aria-describedby": "_config.ariaDescribedBy || null" }, classAttribute: "cdk-dialog-container" }, viewQueries: [{ propertyName: "_portalOutlet", first: true, predicate: CdkPortalOutlet, descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<ng-template cdkPortalOutlet />\n", styles: [".cdk-dialog-container{display:block;width:100%;height:100%;min-height:inherit;max-height:inherit}\n"], dependencies: [{ kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.Default, encapsulation: i0.ViewEncapsulation.None });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: CdkDialogContainer, decorators: [{
- type: Component,
- args: [{ selector: 'cdk-dialog-container', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.Default, imports: [CdkPortalOutlet], host: {
- 'class': 'cdk-dialog-container',
- 'tabindex': '-1',
- '[attr.id]': '_config.id || null',
- '[attr.role]': '_config.role',
- '[attr.aria-modal]': '_config.ariaModal',
- '[attr.aria-labelledby]': '_config.ariaLabel ? null : _ariaLabelledByQueue[0]',
- '[attr.aria-label]': '_config.ariaLabel',
- '[attr.aria-describedby]': '_config.ariaDescribedBy || null',
- }, template: "<ng-template cdkPortalOutlet />\n", styles: [".cdk-dialog-container{display:block;width:100%;height:100%;min-height:inherit;max-height:inherit}\n"] }]
- }], ctorParameters: () => [], propDecorators: { _portalOutlet: [{
- type: ViewChild,
- args: [CdkPortalOutlet, { static: true }]
- }] } });
- /**
- * Reference to a dialog opened via the Dialog service.
- */
- class DialogRef {
- overlayRef;
- config;
- /**
- * Instance of component opened into the dialog. Will be
- * null when the dialog is opened using a `TemplateRef`.
- */
- componentInstance;
- /**
- * `ComponentRef` of the component opened into the dialog. Will be
- * null when the dialog is opened using a `TemplateRef`.
- */
- componentRef;
- /** Instance of the container that is rendering out the dialog content. */
- containerInstance;
- /** Whether the user is allowed to close the dialog. */
- disableClose;
- /** Emits when the dialog has been closed. */
- closed = new Subject();
- /** Emits when the backdrop of the dialog is clicked. */
- backdropClick;
- /** Emits when on keyboard events within the dialog. */
- keydownEvents;
- /** Emits on pointer events that happen outside of the dialog. */
- outsidePointerEvents;
- /** Unique ID for the dialog. */
- id;
- /** Subscription to external detachments of the dialog. */
- _detachSubscription;
- constructor(overlayRef, config) {
- this.overlayRef = overlayRef;
- this.config = config;
- this.disableClose = config.disableClose;
- this.backdropClick = overlayRef.backdropClick();
- this.keydownEvents = overlayRef.keydownEvents();
- this.outsidePointerEvents = overlayRef.outsidePointerEvents();
- this.id = config.id; // By the time the dialog is created we are guaranteed to have an ID.
- this.keydownEvents.subscribe(event => {
- if (event.keyCode === ESCAPE && !this.disableClose && !hasModifierKey(event)) {
- event.preventDefault();
- this.close(undefined, { focusOrigin: 'keyboard' });
- }
- });
- this.backdropClick.subscribe(() => {
- if (!this.disableClose) {
- this.close(undefined, { focusOrigin: 'mouse' });
- }
- });
- this._detachSubscription = overlayRef.detachments().subscribe(() => {
- // Check specifically for `false`, because we want `undefined` to be treated like `true`.
- if (config.closeOnOverlayDetachments !== false) {
- this.close();
- }
- });
- }
- /**
- * Close the dialog.
- * @param result Optional result to return to the dialog opener.
- * @param options Additional options to customize the closing behavior.
- */
- close(result, options) {
- if (this.containerInstance) {
- const closedSubject = this.closed;
- this.containerInstance._closeInteractionType = options?.focusOrigin || 'program';
- // Drop the detach subscription first since it can be triggered by the
- // `dispose` call and override the result of this closing sequence.
- this._detachSubscription.unsubscribe();
- this.overlayRef.dispose();
- closedSubject.next(result);
- closedSubject.complete();
- this.componentInstance = this.containerInstance = null;
- }
- }
- /** Updates the position of the dialog based on the current position strategy. */
- updatePosition() {
- this.overlayRef.updatePosition();
- return this;
- }
- /**
- * Updates the dialog's width and height.
- * @param width New width of the dialog.
- * @param height New height of the dialog.
- */
- updateSize(width = '', height = '') {
- this.overlayRef.updateSize({ width, height });
- return this;
- }
- /** Add a CSS class or an array of classes to the overlay pane. */
- addPanelClass(classes) {
- this.overlayRef.addPanelClass(classes);
- return this;
- }
- /** Remove a CSS class or an array of classes from the overlay pane. */
- removePanelClass(classes) {
- this.overlayRef.removePanelClass(classes);
- return this;
- }
- }
- /** Injection token for the Dialog's ScrollStrategy. */
- const DIALOG_SCROLL_STRATEGY = new InjectionToken('DialogScrollStrategy', {
- providedIn: 'root',
- factory: () => {
- const overlay = inject(Overlay);
- return () => overlay.scrollStrategies.block();
- },
- });
- /** Injection token for the Dialog's Data. */
- const DIALOG_DATA = new InjectionToken('DialogData');
- /** Injection token that can be used to provide default options for the dialog module. */
- const DEFAULT_DIALOG_CONFIG = new InjectionToken('DefaultDialogConfig');
- /**
- * @docs-private
- * @deprecated No longer used. To be removed.
- * @breaking-change 19.0.0
- */
- function DIALOG_SCROLL_STRATEGY_PROVIDER_FACTORY(overlay) {
- return () => overlay.scrollStrategies.block();
- }
- /**
- * @docs-private
- * @deprecated No longer used. To be removed.
- * @breaking-change 19.0.0
- */
- const DIALOG_SCROLL_STRATEGY_PROVIDER = {
- provide: DIALOG_SCROLL_STRATEGY,
- deps: [Overlay],
- useFactory: DIALOG_SCROLL_STRATEGY_PROVIDER_FACTORY,
- };
- class Dialog {
- _overlay = inject(Overlay);
- _injector = inject(Injector);
- _defaultOptions = inject(DEFAULT_DIALOG_CONFIG, { optional: true });
- _parentDialog = inject(Dialog, { optional: true, skipSelf: true });
- _overlayContainer = inject(OverlayContainer);
- _idGenerator = inject(_IdGenerator);
- _openDialogsAtThisLevel = [];
- _afterAllClosedAtThisLevel = new Subject();
- _afterOpenedAtThisLevel = new Subject();
- _ariaHiddenElements = new Map();
- _scrollStrategy = inject(DIALOG_SCROLL_STRATEGY);
- /** Keeps track of the currently-open dialogs. */
- get openDialogs() {
- return this._parentDialog ? this._parentDialog.openDialogs : this._openDialogsAtThisLevel;
- }
- /** Stream that emits when a dialog has been opened. */
- get afterOpened() {
- return this._parentDialog ? this._parentDialog.afterOpened : this._afterOpenedAtThisLevel;
- }
- /**
- * Stream that emits when all open dialog have finished closing.
- * Will emit on subscribe if there are no open dialogs to begin with.
- */
- afterAllClosed = defer(() => this.openDialogs.length
- ? this._getAfterAllClosed()
- : this._getAfterAllClosed().pipe(startWith(undefined)));
- constructor() { }
- open(componentOrTemplateRef, config) {
- const defaults = (this._defaultOptions || new DialogConfig());
- config = { ...defaults, ...config };
- config.id = config.id || this._idGenerator.getId('cdk-dialog-');
- if (config.id &&
- this.getDialogById(config.id) &&
- (typeof ngDevMode === 'undefined' || ngDevMode)) {
- throw Error(`Dialog with id "${config.id}" exists already. The dialog id must be unique.`);
- }
- const overlayConfig = this._getOverlayConfig(config);
- const overlayRef = this._overlay.create(overlayConfig);
- const dialogRef = new DialogRef(overlayRef, config);
- const dialogContainer = this._attachContainer(overlayRef, dialogRef, config);
- dialogRef.containerInstance = dialogContainer;
- // If this is the first dialog that we're opening, hide all the non-overlay content.
- if (!this.openDialogs.length) {
- // Resolve this ahead of time, because some internal apps
- // mock it out and depend on it being synchronous.
- const overlayContainer = this._overlayContainer.getContainerElement();
- if (dialogContainer._focusTrapped) {
- dialogContainer._focusTrapped.pipe(take(1)).subscribe(() => {
- this._hideNonDialogContentFromAssistiveTechnology(overlayContainer);
- });
- }
- else {
- this._hideNonDialogContentFromAssistiveTechnology(overlayContainer);
- }
- }
- this._attachDialogContent(componentOrTemplateRef, dialogRef, dialogContainer, config);
- this.openDialogs.push(dialogRef);
- dialogRef.closed.subscribe(() => this._removeOpenDialog(dialogRef, true));
- this.afterOpened.next(dialogRef);
- return dialogRef;
- }
- /**
- * Closes all of the currently-open dialogs.
- */
- closeAll() {
- reverseForEach(this.openDialogs, dialog => dialog.close());
- }
- /**
- * Finds an open dialog by its id.
- * @param id ID to use when looking up the dialog.
- */
- getDialogById(id) {
- return this.openDialogs.find(dialog => dialog.id === id);
- }
- ngOnDestroy() {
- // Make one pass over all the dialogs that need to be untracked, but should not be closed. We
- // want to stop tracking the open dialog even if it hasn't been closed, because the tracking
- // determines when `aria-hidden` is removed from elements outside the dialog.
- reverseForEach(this._openDialogsAtThisLevel, dialog => {
- // Check for `false` specifically since we want `undefined` to be interpreted as `true`.
- if (dialog.config.closeOnDestroy === false) {
- this._removeOpenDialog(dialog, false);
- }
- });
- // Make a second pass and close the remaining dialogs. We do this second pass in order to
- // correctly dispatch the `afterAllClosed` event in case we have a mixed array of dialogs
- // that should be closed and dialogs that should not.
- reverseForEach(this._openDialogsAtThisLevel, dialog => dialog.close());
- this._afterAllClosedAtThisLevel.complete();
- this._afterOpenedAtThisLevel.complete();
- this._openDialogsAtThisLevel = [];
- }
- /**
- * Creates an overlay config from a dialog config.
- * @param config The dialog configuration.
- * @returns The overlay configuration.
- */
- _getOverlayConfig(config) {
- const state = new OverlayConfig({
- positionStrategy: config.positionStrategy ||
- this._overlay.position().global().centerHorizontally().centerVertically(),
- scrollStrategy: config.scrollStrategy || this._scrollStrategy(),
- panelClass: config.panelClass,
- hasBackdrop: config.hasBackdrop,
- direction: config.direction,
- minWidth: config.minWidth,
- minHeight: config.minHeight,
- maxWidth: config.maxWidth,
- maxHeight: config.maxHeight,
- width: config.width,
- height: config.height,
- disposeOnNavigation: config.closeOnNavigation,
- });
- if (config.backdropClass) {
- state.backdropClass = config.backdropClass;
- }
- return state;
- }
- /**
- * Attaches a dialog container to a dialog's already-created overlay.
- * @param overlay Reference to the dialog's underlying overlay.
- * @param config The dialog configuration.
- * @returns A promise resolving to a ComponentRef for the attached container.
- */
- _attachContainer(overlay, dialogRef, config) {
- const userInjector = config.injector || config.viewContainerRef?.injector;
- const providers = [
- { provide: DialogConfig, useValue: config },
- { provide: DialogRef, useValue: dialogRef },
- { provide: OverlayRef, useValue: overlay },
- ];
- let containerType;
- if (config.container) {
- if (typeof config.container === 'function') {
- containerType = config.container;
- }
- else {
- containerType = config.container.type;
- providers.push(...config.container.providers(config));
- }
- }
- else {
- containerType = CdkDialogContainer;
- }
- const containerPortal = new ComponentPortal(containerType, config.viewContainerRef, Injector.create({ parent: userInjector || this._injector, providers }));
- const containerRef = overlay.attach(containerPortal);
- return containerRef.instance;
- }
- /**
- * Attaches the user-provided component to the already-created dialog container.
- * @param componentOrTemplateRef The type of component being loaded into the dialog,
- * or a TemplateRef to instantiate as the content.
- * @param dialogRef Reference to the dialog being opened.
- * @param dialogContainer Component that is going to wrap the dialog content.
- * @param config Configuration used to open the dialog.
- */
- _attachDialogContent(componentOrTemplateRef, dialogRef, dialogContainer, config) {
- if (componentOrTemplateRef instanceof TemplateRef) {
- const injector = this._createInjector(config, dialogRef, dialogContainer, undefined);
- let context = { $implicit: config.data, dialogRef };
- if (config.templateContext) {
- context = {
- ...context,
- ...(typeof config.templateContext === 'function'
- ? config.templateContext()
- : config.templateContext),
- };
- }
- dialogContainer.attachTemplatePortal(new TemplatePortal(componentOrTemplateRef, null, context, injector));
- }
- else {
- const injector = this._createInjector(config, dialogRef, dialogContainer, this._injector);
- const contentRef = dialogContainer.attachComponentPortal(new ComponentPortal(componentOrTemplateRef, config.viewContainerRef, injector));
- dialogRef.componentRef = contentRef;
- dialogRef.componentInstance = contentRef.instance;
- }
- }
- /**
- * Creates a custom injector to be used inside the dialog. This allows a component loaded inside
- * of a dialog to close itself and, optionally, to return a value.
- * @param config Config object that is used to construct the dialog.
- * @param dialogRef Reference to the dialog being opened.
- * @param dialogContainer Component that is going to wrap the dialog content.
- * @param fallbackInjector Injector to use as a fallback when a lookup fails in the custom
- * dialog injector, if the user didn't provide a custom one.
- * @returns The custom injector that can be used inside the dialog.
- */
- _createInjector(config, dialogRef, dialogContainer, fallbackInjector) {
- const userInjector = config.injector || config.viewContainerRef?.injector;
- const providers = [
- { provide: DIALOG_DATA, useValue: config.data },
- { provide: DialogRef, useValue: dialogRef },
- ];
- if (config.providers) {
- if (typeof config.providers === 'function') {
- providers.push(...config.providers(dialogRef, config, dialogContainer));
- }
- else {
- providers.push(...config.providers);
- }
- }
- if (config.direction &&
- (!userInjector ||
- !userInjector.get(Directionality, null, { optional: true }))) {
- providers.push({
- provide: Directionality,
- useValue: { value: config.direction, change: of() },
- });
- }
- return Injector.create({ parent: userInjector || fallbackInjector, providers });
- }
- /**
- * Removes a dialog from the array of open dialogs.
- * @param dialogRef Dialog to be removed.
- * @param emitEvent Whether to emit an event if this is the last dialog.
- */
- _removeOpenDialog(dialogRef, emitEvent) {
- const index = this.openDialogs.indexOf(dialogRef);
- if (index > -1) {
- this.openDialogs.splice(index, 1);
- // If all the dialogs were closed, remove/restore the `aria-hidden`
- // to a the siblings and emit to the `afterAllClosed` stream.
- if (!this.openDialogs.length) {
- this._ariaHiddenElements.forEach((previousValue, element) => {
- if (previousValue) {
- element.setAttribute('aria-hidden', previousValue);
- }
- else {
- element.removeAttribute('aria-hidden');
- }
- });
- this._ariaHiddenElements.clear();
- if (emitEvent) {
- this._getAfterAllClosed().next();
- }
- }
- }
- }
- /** Hides all of the content that isn't an overlay from assistive technology. */
- _hideNonDialogContentFromAssistiveTechnology(overlayContainer) {
- // Ensure that the overlay container is attached to the DOM.
- if (overlayContainer.parentElement) {
- const siblings = overlayContainer.parentElement.children;
- for (let i = siblings.length - 1; i > -1; i--) {
- const sibling = siblings[i];
- if (sibling !== overlayContainer &&
- sibling.nodeName !== 'SCRIPT' &&
- sibling.nodeName !== 'STYLE' &&
- !sibling.hasAttribute('aria-live')) {
- this._ariaHiddenElements.set(sibling, sibling.getAttribute('aria-hidden'));
- sibling.setAttribute('aria-hidden', 'true');
- }
- }
- }
- }
- _getAfterAllClosed() {
- const parent = this._parentDialog;
- return parent ? parent._getAfterAllClosed() : this._afterAllClosedAtThisLevel;
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: Dialog, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: Dialog, providedIn: 'root' });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: Dialog, decorators: [{
- type: Injectable,
- args: [{ providedIn: 'root' }]
- }], ctorParameters: () => [] });
- /**
- * Executes a callback against all elements in an array while iterating in reverse.
- * Useful if the array is being modified as it is being iterated.
- */
- function reverseForEach(items, callback) {
- let i = items.length;
- while (i--) {
- callback(items[i]);
- }
- }
- class DialogModule {
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: DialogModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: DialogModule, imports: [OverlayModule, PortalModule, A11yModule, CdkDialogContainer], exports: [
- // Re-export the PortalModule so that people extending the `CdkDialogContainer`
- // don't have to remember to import it or be faced with an unhelpful error.
- PortalModule,
- CdkDialogContainer] });
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: DialogModule, providers: [Dialog], imports: [OverlayModule, PortalModule, A11yModule,
- // Re-export the PortalModule so that people extending the `CdkDialogContainer`
- // don't have to remember to import it or be faced with an unhelpful error.
- PortalModule] });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: DialogModule, decorators: [{
- type: NgModule,
- args: [{
- imports: [OverlayModule, PortalModule, A11yModule, CdkDialogContainer],
- exports: [
- // Re-export the PortalModule so that people extending the `CdkDialogContainer`
- // don't have to remember to import it or be faced with an unhelpful error.
- PortalModule,
- CdkDialogContainer,
- ],
- providers: [Dialog],
- }]
- }] });
- export { CdkDialogContainer, DEFAULT_DIALOG_CONFIG, DIALOG_DATA, DIALOG_SCROLL_STRATEGY, DIALOG_SCROLL_STRATEGY_PROVIDER, DIALOG_SCROLL_STRATEGY_PROVIDER_FACTORY, Dialog, DialogConfig, DialogModule, DialogRef, throwDialogContentAlreadyAttachedError, CdkPortalOutlet as ɵɵCdkPortalOutlet };
- //# sourceMappingURL=dialog.mjs.map
|