sass-references/angular-material/material/dialog/dialog.ts

247 lines
8.4 KiB
TypeScript
Raw Normal View History

2024-12-06 10:42:08 +08:00
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {ComponentType, Overlay, ScrollStrategy} from '@angular/cdk/overlay';
import {
ComponentRef,
Injectable,
InjectionToken,
OnDestroy,
TemplateRef,
Type,
inject,
} from '@angular/core';
import {MatDialogConfig} from './dialog-config';
import {MatDialogContainer} from './dialog-container';
import {MatDialogRef} from './dialog-ref';
import {defer, Observable, Subject} from 'rxjs';
import {Dialog, DialogConfig} from '@angular/cdk/dialog';
import {startWith} from 'rxjs/operators';
import {_IdGenerator} from '@angular/cdk/a11y';
/** Injection token that can be used to access the data that was passed in to a dialog. */
export const MAT_DIALOG_DATA = new InjectionToken<any>('MatMdcDialogData');
/** Injection token that can be used to specify default dialog options. */
export const MAT_DIALOG_DEFAULT_OPTIONS = new InjectionToken<MatDialogConfig>(
'mat-mdc-dialog-default-options',
);
/** Injection token that determines the scroll handling while the dialog is open. */
export const MAT_DIALOG_SCROLL_STRATEGY = new InjectionToken<() => ScrollStrategy>(
'mat-mdc-dialog-scroll-strategy',
{
providedIn: 'root',
factory: () => {
const overlay = inject(Overlay);
return () => overlay.scrollStrategies.block();
},
},
);
/**
* @docs-private
* @deprecated No longer used. To be removed.
* @breaking-change 19.0.0
*/
export function MAT_DIALOG_SCROLL_STRATEGY_PROVIDER_FACTORY(
overlay: Overlay,
): () => ScrollStrategy {
return () => overlay.scrollStrategies.block();
}
/**
* @docs-private
* @deprecated No longer used. To be removed.
* @breaking-change 19.0.0
*/
export const MAT_DIALOG_SCROLL_STRATEGY_PROVIDER = {
provide: MAT_DIALOG_SCROLL_STRATEGY,
deps: [Overlay],
useFactory: MAT_DIALOG_SCROLL_STRATEGY_PROVIDER_FACTORY,
};
/**
* Service to open Material Design modal dialogs.
*/
@Injectable({providedIn: 'root'})
export class MatDialog implements OnDestroy {
private _overlay = inject(Overlay);
private _defaultOptions = inject<MatDialogConfig>(MAT_DIALOG_DEFAULT_OPTIONS, {optional: true});
private _scrollStrategy = inject(MAT_DIALOG_SCROLL_STRATEGY);
private _parentDialog = inject(MatDialog, {optional: true, skipSelf: true});
private _idGenerator = inject(_IdGenerator);
protected _dialog = inject(Dialog);
private readonly _openDialogsAtThisLevel: MatDialogRef<any>[] = [];
private readonly _afterAllClosedAtThisLevel = new Subject<void>();
private readonly _afterOpenedAtThisLevel = new Subject<MatDialogRef<any>>();
protected dialogConfigClass = MatDialogConfig;
private readonly _dialogRefConstructor: Type<MatDialogRef<any>>;
private readonly _dialogContainerType: Type<MatDialogContainer>;
private readonly _dialogDataToken: InjectionToken<any>;
/** Keeps track of the currently-open dialogs. */
get openDialogs(): MatDialogRef<any>[] {
return this._parentDialog ? this._parentDialog.openDialogs : this._openDialogsAtThisLevel;
}
/** Stream that emits when a dialog has been opened. */
get afterOpened(): Subject<MatDialogRef<any>> {
return this._parentDialog ? this._parentDialog.afterOpened : this._afterOpenedAtThisLevel;
}
private _getAfterAllClosed(): Subject<void> {
const parent = this._parentDialog;
return parent ? parent._getAfterAllClosed() : this._afterAllClosedAtThisLevel;
}
/**
* Stream that emits when all open dialog have finished closing.
* Will emit on subscribe if there are no open dialogs to begin with.
*/
readonly afterAllClosed: Observable<void> = defer(() =>
this.openDialogs.length
? this._getAfterAllClosed()
: this._getAfterAllClosed().pipe(startWith(undefined)),
) as Observable<any>;
constructor(...args: unknown[]);
constructor() {
this._dialogRefConstructor = MatDialogRef;
this._dialogContainerType = MatDialogContainer;
this._dialogDataToken = MAT_DIALOG_DATA;
}
/**
* Opens a modal dialog containing the given component.
* @param component Type of the component to load into the dialog.
* @param config Extra configuration options.
* @returns Reference to the newly-opened dialog.
*/
open<T, D = any, R = any>(
component: ComponentType<T>,
config?: MatDialogConfig<D>,
): MatDialogRef<T, R>;
/**
* Opens a modal dialog containing the given template.
* @param template TemplateRef to instantiate as the dialog content.
* @param config Extra configuration options.
* @returns Reference to the newly-opened dialog.
*/
open<T, D = any, R = any>(
template: TemplateRef<T>,
config?: MatDialogConfig<D>,
): MatDialogRef<T, R>;
open<T, D = any, R = any>(
template: ComponentType<T> | TemplateRef<T>,
config?: MatDialogConfig<D>,
): MatDialogRef<T, R>;
open<T, D = any, R = any>(
componentOrTemplateRef: ComponentType<T> | TemplateRef<T>,
config?: MatDialogConfig<D>,
): MatDialogRef<T, R> {
let dialogRef: MatDialogRef<T, R>;
config = {...(this._defaultOptions || new MatDialogConfig()), ...config};
config.id = config.id || this._idGenerator.getId('mat-mdc-dialog-');
config.scrollStrategy = config.scrollStrategy || this._scrollStrategy();
const cdkRef = this._dialog.open<R, D, T>(componentOrTemplateRef, {
...config,
positionStrategy: this._overlay.position().global().centerHorizontally().centerVertically(),
// Disable closing since we need to sync it up to the animation ourselves.
disableClose: true,
// Disable closing on destroy, because this service cleans up its open dialogs as well.
// We want to do the cleanup here, rather than the CDK service, because the CDK destroys
// the dialogs immediately whereas we want it to wait for the animations to finish.
closeOnDestroy: false,
// Disable closing on detachments so that we can sync up the animation.
// The Material dialog ref handles this manually.
closeOnOverlayDetachments: false,
container: {
type: this._dialogContainerType,
providers: () => [
// Provide our config as the CDK config as well since it has the same interface as the
// CDK one, but it contains the actual values passed in by the user for things like
// `disableClose` which we disable for the CDK dialog since we handle it ourselves.
{provide: this.dialogConfigClass, useValue: config},
{provide: DialogConfig, useValue: config},
],
},
templateContext: () => ({dialogRef}),
providers: (ref, cdkConfig, dialogContainer) => {
dialogRef = new this._dialogRefConstructor(ref, config, dialogContainer);
dialogRef.updatePosition(config?.position);
return [
{provide: this._dialogContainerType, useValue: dialogContainer},
{provide: this._dialogDataToken, useValue: cdkConfig.data},
{provide: this._dialogRefConstructor, useValue: dialogRef},
];
},
});
// This can't be assigned in the `providers` callback, because
// the instance hasn't been assigned to the CDK ref yet.
(dialogRef! as {componentRef: ComponentRef<T>}).componentRef = cdkRef.componentRef!;
dialogRef!.componentInstance = cdkRef.componentInstance!;
this.openDialogs.push(dialogRef!);
this.afterOpened.next(dialogRef!);
dialogRef!.afterClosed().subscribe(() => {
const index = this.openDialogs.indexOf(dialogRef);
if (index > -1) {
this.openDialogs.splice(index, 1);
if (!this.openDialogs.length) {
this._getAfterAllClosed().next();
}
}
});
return dialogRef!;
}
/**
* Closes all of the currently-open dialogs.
*/
closeAll(): void {
this._closeDialogs(this.openDialogs);
}
/**
* Finds an open dialog by its id.
* @param id ID to use when looking up the dialog.
*/
getDialogById(id: string): MatDialogRef<any> | undefined {
return this.openDialogs.find(dialog => dialog.id === id);
}
ngOnDestroy() {
// Only close the dialogs at this level on destroy
// since the parent service may still be active.
this._closeDialogs(this._openDialogsAtThisLevel);
this._afterAllClosedAtThisLevel.complete();
this._afterOpenedAtThisLevel.complete();
}
private _closeDialogs(dialogs: MatDialogRef<any>[]) {
let i = dialogs.length;
while (i--) {
dialogs[i].close();
}
}
}