我有一个关于 Angular Material 的叠加和日期选择器的问题。每当我在覆盖层外部单击时,我都会使用 overlayOutsideClick 事件来关闭覆盖层。
它工作正常,但如果我有一个日期选择器并且我选择一个日期,它被视为覆盖层之外的点击,我怎样才能防止这种行为?
谢谢
这里是一个 stackblitz 来重现这种情况: https://stackblitz.com/edit/ghjvsh-zy2lf2?file=src%2Fexample%2Fcdk-overlay-basic-example.html
所以我想出了一个可能比需要的更复杂的解决方案。
为了修复点击日期选择器切换按钮时覆盖关闭的问题,我没有使用overlayOutsideClick,而是依靠模糊事件在日期输入失去焦点时运行逻辑。
通过将 MatFormField 封装在 div 中,我将在关闭覆盖层之前检查新的焦点元素是否位于该 div 内部,如果我们单击日期选择器切换按钮,则将其保持打开状态。
export class CdkOverlayBasicExample {
protected isOpen = signal<boolean>(false);
@ViewChild('container', { static: false })
datepickerContainer!: ElementRef<HTMLDivElement>;
protected onBlur($event: FocusEvent) {
const relatedTarget = $event.relatedTarget;
if (
!relatedTarget || // If the related target is null, we didn't click on any HTML element
!(relatedTarget instanceof HTMLElement) ||
!this.datepickerContainer.nativeElement.contains(relatedTarget)
) {
// If the relatedTarget is not contained inside our container div, close the overlay
this.isOpen.set(false);
}
}
}
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayOpen]="isOpen()"
>
<div #container>
<mat-form-field>
<mat-label>Choose a date</mat-label>
<input
#input
matInput
[matDatepicker]="picker"
(blur)="onBlur($event)"
[cdkTrapFocusAutoCapture]="true"
cdkTrapFocus
/>
<mat-hint>MM/DD/YYYY</mat-hint>
<mat-datepicker-toggle
matIconSuffix
[for]="picker"
></mat-datepicker-toggle>
<mat-datepicker
#picker
[restoreFocus]="false"
(closed)="input.focus()"
></mat-datepicker>
</mat-form-field>
</div>
</ng-template>
虽然按预期工作,但此解决方案有一些注意事项,特别是默认情况下日期选择器输入不具有焦点,并且关闭日期选择器不会将焦点设置在输入上。
为了获得更好的结果,我将 A11yModule 中的 cdkFocusTrap 添加到了日期选择器输入,我禁用了日期选择器恢复焦点功能,并在日期选择器关闭时手动将焦点设置在日期选择器输入上。
import {
Component,
ElementRef,
TemplateRef,
ViewChild,
signal,
} from '@angular/core';
import { OverlayModule } from '@angular/cdk/overlay';
import {
MatDatepicker,
MatDatepickerModule,
} from '@angular/material/datepicker';
import { MatFormField, MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { A11yModule } from '@angular/cdk/a11y';
/**
* @title Overlay basic example
*/
@Component({
selector: 'cdk-overlay-basic-example',
template: `
<!-- This button triggers the overlay and is it's origin -->
<button
(click)="isOpen.set(!isOpen());"
type="button"
cdkOverlayOrigin
#trigger="cdkOverlayOrigin"
>
{{isOpen() ? "Close" : "Open"}}
</button>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayOpen]="isOpen()"
>
<div #container>
<mat-form-field>
<mat-label>Choose a date</mat-label>
<input
#input
matInput
[matDatepicker]="picker"
(blur)="onBlur($event)"
[cdkTrapFocusAutoCapture]="true"
cdkTrapFocus
/>
<mat-hint>MM/DD/YYYY</mat-hint>
<mat-datepicker-toggle
matIconSuffix
[for]="picker"
></mat-datepicker-toggle>
<mat-datepicker
#picker
[restoreFocus]="false"
(closed)="input.focus()"
></mat-datepicker>
</mat-form-field>
</div>
</ng-template>
`,
standalone: true,
imports: [
OverlayModule,
MatFormFieldModule,
MatInputModule,
MatDatepickerModule,
A11yModule,
],
})
export class CdkOverlayBasicExample {
protected isOpen = signal<boolean>(false);
@ViewChild('container', { static: false })
datepickerContainer!: ElementRef<HTMLDivElement>;
protected onBlur($event: FocusEvent) {
const relatedTarget = $event.relatedTarget;
if (
!relatedTarget ||
!(relatedTarget instanceof HTMLElement) ||
!this.datepickerContainer.nativeElement.contains(relatedTarget)
) {
this.isOpen.set(false);
}
}
}
这是一个从你的 stackblitz 中分叉出来的工作 :
https://stackblitz.com/edit/ghjvsh-bmd6my?file=src%2Fexample%2Fcdk-overlay-basic-example.html
我希望这有帮助!
我想补充一点,将 mat-form-field 封装在 div 元素内并不是强制性的,因为也可以只使用 mat-form-field 的 HTMLElement :
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayOpen]="isOpen()"
>
<mat-form-field #container>
...
</mat-form-field>
</ng-template>
@ViewChild('container', { static: false })
datepickerContainer!: ElementRef<HTMLElement>;