角度材质 - 叠加 X 日期选择器

问题描述 投票:0回答:1

我有一个关于 Angular Material 的叠加和日期选择器的问题。每当我在覆盖层外部单击时,我都会使用 overlayOutsideClick 事件来关闭覆盖层。

它工作正常,但如果我有一个日期选择器并且我选择一个日期,它被视为覆盖层之外的点击,我怎样才能防止这种行为?

谢谢

这里是一个 stackblitz 来重现这种情况: https://stackblitz.com/edit/ghjvsh-zy2lf2?file=src%2Fexample%2Fcdk-overlay-basic-example.html

angular datepicker overlay
1个回答
0
投票

所以我想出了一个可能比需要的更复杂的解决方案。

为了修复点击日期选择器切换按钮时覆盖关闭的问题,我没有使用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>;
© www.soinside.com 2019 - 2024. All rights reserved.