forked from angular/components
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathdialog.ts
240 lines (211 loc) · 8.41 KB
/
dialog.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
/**
* @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,
createBlockScrollStrategy,
createGlobalPositionStrategy,
ScrollStrategy,
} from '@angular/cdk/overlay';
import {
ComponentRef,
Injectable,
InjectionToken,
Injector,
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';
import {_animationsDisabled} from '../core';
/** 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 injector = inject(Injector);
return () => createBlockScrollStrategy(injector);
},
},
);
/**
* Service to open Material Design modal dialogs.
*/
@Injectable({providedIn: 'root'})
export class MatDialog implements OnDestroy {
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);
private _injector = inject(Injector);
protected _dialog = inject(Dialog);
private _animationsDisabled = _animationsDisabled();
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: createGlobalPositionStrategy(this._injector)
.centerHorizontally()
.centerVertically(),
// Disable closing since we need to sync it up to the animation ourselves.
disableClose: true,
// Closing is tied to our animation so the close predicate has to be implemented separately.
closePredicate: undefined,
// 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,
disableAnimations:
this._animationsDisabled ||
config.enterAnimationDuration?.toLocaleString() === '0' ||
config.exitAnimationDuration?.toString() === '0',
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();
}
}
}