203 lines
6.1 KiB
TypeScript
203 lines
6.1 KiB
TypeScript
/**
|
|
* @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 {
|
|
Directive,
|
|
ElementRef,
|
|
Input,
|
|
OnChanges,
|
|
OnDestroy,
|
|
OnInit,
|
|
SimpleChanges,
|
|
inject,
|
|
} from '@angular/core';
|
|
import {_IdGenerator} from '@angular/cdk/a11y';
|
|
import {CdkScrollable} from '@angular/cdk/scrolling';
|
|
|
|
import {MatDialog} from './dialog';
|
|
import {_closeDialogVia, MatDialogRef} from './dialog-ref';
|
|
|
|
/**
|
|
* Button that will close the current dialog.
|
|
*/
|
|
@Directive({
|
|
selector: '[mat-dialog-close], [matDialogClose]',
|
|
exportAs: 'matDialogClose',
|
|
host: {
|
|
'(click)': '_onButtonClick($event)',
|
|
'[attr.aria-label]': 'ariaLabel || null',
|
|
'[attr.type]': 'type',
|
|
},
|
|
})
|
|
export class MatDialogClose implements OnInit, OnChanges {
|
|
dialogRef = inject<MatDialogRef<any>>(MatDialogRef, {optional: true})!;
|
|
private _elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
|
|
private _dialog = inject(MatDialog);
|
|
|
|
/** Screen-reader label for the button. */
|
|
@Input('aria-label') ariaLabel: string;
|
|
|
|
/** Default to "button" to prevents accidental form submits. */
|
|
@Input() type: 'submit' | 'button' | 'reset' = 'button';
|
|
|
|
/** Dialog close input. */
|
|
@Input('mat-dialog-close') dialogResult: any;
|
|
|
|
@Input('matDialogClose') _matDialogClose: any;
|
|
|
|
constructor(...args: unknown[]);
|
|
constructor() {}
|
|
|
|
ngOnInit() {
|
|
if (!this.dialogRef) {
|
|
// When this directive is included in a dialog via TemplateRef (rather than being
|
|
// in a Component), the DialogRef isn't available via injection because embedded
|
|
// views cannot be given a custom injector. Instead, we look up the DialogRef by
|
|
// ID. This must occur in `onInit`, as the ID binding for the dialog container won't
|
|
// be resolved at constructor time.
|
|
this.dialogRef = getClosestDialog(this._elementRef, this._dialog.openDialogs)!;
|
|
}
|
|
}
|
|
|
|
ngOnChanges(changes: SimpleChanges) {
|
|
const proxiedChange = changes['_matDialogClose'] || changes['_matDialogCloseResult'];
|
|
|
|
if (proxiedChange) {
|
|
this.dialogResult = proxiedChange.currentValue;
|
|
}
|
|
}
|
|
|
|
_onButtonClick(event: MouseEvent) {
|
|
// Determinate the focus origin using the click event, because using the FocusMonitor will
|
|
// result in incorrect origins. Most of the time, close buttons will be auto focused in the
|
|
// dialog, and therefore clicking the button won't result in a focus change. This means that
|
|
// the FocusMonitor won't detect any origin change, and will always output `program`.
|
|
_closeDialogVia(
|
|
this.dialogRef,
|
|
event.screenX === 0 && event.screenY === 0 ? 'keyboard' : 'mouse',
|
|
this.dialogResult,
|
|
);
|
|
}
|
|
}
|
|
|
|
@Directive()
|
|
export abstract class MatDialogLayoutSection implements OnInit, OnDestroy {
|
|
protected _dialogRef = inject<MatDialogRef<any>>(MatDialogRef, {optional: true})!;
|
|
private _elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
|
|
private _dialog = inject(MatDialog);
|
|
|
|
constructor(...args: unknown[]);
|
|
|
|
constructor() {}
|
|
|
|
protected abstract _onAdd(): void;
|
|
protected abstract _onRemove(): void;
|
|
|
|
ngOnInit() {
|
|
if (!this._dialogRef) {
|
|
this._dialogRef = getClosestDialog(this._elementRef, this._dialog.openDialogs)!;
|
|
}
|
|
|
|
if (this._dialogRef) {
|
|
Promise.resolve().then(() => {
|
|
this._onAdd();
|
|
});
|
|
}
|
|
}
|
|
|
|
ngOnDestroy() {
|
|
// Note: we null check because there are some internal
|
|
// tests that are mocking out `MatDialogRef` incorrectly.
|
|
const instance = this._dialogRef?._containerInstance;
|
|
|
|
if (instance) {
|
|
Promise.resolve().then(() => {
|
|
this._onRemove();
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Title of a dialog element. Stays fixed to the top of the dialog when scrolling.
|
|
*/
|
|
@Directive({
|
|
selector: '[mat-dialog-title], [matDialogTitle]',
|
|
exportAs: 'matDialogTitle',
|
|
host: {
|
|
'class': 'mat-mdc-dialog-title mdc-dialog__title',
|
|
'[id]': 'id',
|
|
},
|
|
})
|
|
export class MatDialogTitle extends MatDialogLayoutSection {
|
|
@Input() id: string = inject(_IdGenerator).getId('mat-mdc-dialog-title-');
|
|
|
|
protected _onAdd() {
|
|
// Note: we null check the queue, because there are some internal
|
|
// tests that are mocking out `MatDialogRef` incorrectly.
|
|
this._dialogRef._containerInstance?._addAriaLabelledBy?.(this.id);
|
|
}
|
|
|
|
protected override _onRemove(): void {
|
|
this._dialogRef?._containerInstance?._removeAriaLabelledBy?.(this.id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scrollable content container of a dialog.
|
|
*/
|
|
@Directive({
|
|
selector: `[mat-dialog-content], mat-dialog-content, [matDialogContent]`,
|
|
host: {'class': 'mat-mdc-dialog-content mdc-dialog__content'},
|
|
hostDirectives: [CdkScrollable],
|
|
})
|
|
export class MatDialogContent {}
|
|
|
|
/**
|
|
* Container for the bottom action buttons in a dialog.
|
|
* Stays fixed to the bottom when scrolling.
|
|
*/
|
|
@Directive({
|
|
selector: `[mat-dialog-actions], mat-dialog-actions, [matDialogActions]`,
|
|
host: {
|
|
'class': 'mat-mdc-dialog-actions mdc-dialog__actions',
|
|
'[class.mat-mdc-dialog-actions-align-start]': 'align === "start"',
|
|
'[class.mat-mdc-dialog-actions-align-center]': 'align === "center"',
|
|
'[class.mat-mdc-dialog-actions-align-end]': 'align === "end"',
|
|
},
|
|
})
|
|
export class MatDialogActions extends MatDialogLayoutSection {
|
|
/**
|
|
* Horizontal alignment of action buttons.
|
|
*/
|
|
@Input() align?: 'start' | 'center' | 'end';
|
|
|
|
protected _onAdd() {
|
|
this._dialogRef._containerInstance?._updateActionSectionCount?.(1);
|
|
}
|
|
|
|
protected override _onRemove(): void {
|
|
this._dialogRef._containerInstance?._updateActionSectionCount?.(-1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds the closest MatDialogRef to an element by looking at the DOM.
|
|
* @param element Element relative to which to look for a dialog.
|
|
* @param openDialogs References to the currently-open dialogs.
|
|
*/
|
|
function getClosestDialog(element: ElementRef<HTMLElement>, openDialogs: MatDialogRef<any>[]) {
|
|
let parent: HTMLElement | null = element.nativeElement.parentElement;
|
|
|
|
while (parent && !parent.classList.contains('mat-mdc-dialog-container')) {
|
|
parent = parent.parentElement;
|
|
}
|
|
|
|
return parent ? openDialogs.find(dialog => dialog.id === parent!.id) : null;
|
|
}
|