Skip to content

Commit

Permalink
refactor(material/datepicker): Remove use of zone onStable for focus …
Browse files Browse the repository at this point in the history
…and dropdown positioning
  • Loading branch information
mmalerba committed Feb 27, 2024
1 parent 09111d0 commit 5e01bae
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 38 deletions.
35 changes: 21 additions & 14 deletions src/material/datepicker/calendar-body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
OnDestroy,
AfterViewChecked,
inject,
afterNextRender,
Injector,
} from '@angular/core';
import {take} from 'rxjs/operators';
import {NgClass} from '@angular/common';
Expand Down Expand Up @@ -189,6 +191,8 @@ export class MatCalendarBody<D = any> implements OnChanges, OnDestroy, AfterView

private _didDragSinceMouseDown = false;

private _injector = inject(Injector);

constructor(
private _elementRef: ElementRef<HTMLElement>,
private _ngZone: NgZone,
Expand Down Expand Up @@ -310,21 +314,24 @@ export class MatCalendarBody<D = any> implements OnChanges, OnDestroy, AfterView
*/
_focusActiveCell(movePreview = true) {
this._ngZone.runOutsideAngular(() => {
this._ngZone.onStable.pipe(take(1)).subscribe(() => {
setTimeout(() => {
const activeCell: HTMLElement | null = this._elementRef.nativeElement.querySelector(
'.mat-calendar-body-active',
);

if (activeCell) {
if (!movePreview) {
this._skipNextFocus = true;
afterNextRender(
() => {
setTimeout(() => {
const activeCell: HTMLElement | null = this._elementRef.nativeElement.querySelector(
'.mat-calendar-body-active',
);

if (activeCell) {
if (!movePreview) {
this._skipNextFocus = true;
}

activeCell.focus();
}

activeCell.focus();
}
});
});
});
},
{injector: this._injector},
);
});
}

Expand Down
31 changes: 8 additions & 23 deletions src/material/datepicker/calendar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,9 @@ import {
dispatchFakeEvent,
dispatchKeyboardEvent,
dispatchMouseEvent,
MockNgZone,
} from '@angular/cdk/testing/private';
import {Component, NgZone} from '@angular/core';
import {
fakeAsync,
waitForAsync,
ComponentFixture,
inject,
TestBed,
tick,
} from '@angular/core/testing';
import {Component} from '@angular/core';
import {waitForAsync, ComponentFixture, inject, TestBed} from '@angular/core/testing';
import {DateAdapter, MatNativeDateModule} from '@angular/material/core';
import {DEC, FEB, JAN, JUL, NOV} from '../testing';
import {By} from '@angular/platform-browser';
Expand All @@ -23,16 +15,10 @@ import {MatDatepickerIntl} from './datepicker-intl';
import {MatDatepickerModule} from './datepicker-module';

describe('MatCalendar', () => {
let zone: MockNgZone;

beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [MatNativeDateModule, MatDatepickerModule],
providers: [
MatDatepickerIntl,
{provide: NgZone, useFactory: () => (zone = new MockNgZone())},
{provide: Directionality, useFactory: () => ({value: 'ltr'})},
],
providers: [MatDatepickerIntl, {provide: Directionality, useFactory: () => ({value: 'ltr'})}],
declarations: [
// Test components.
StandardCalendar,
Expand Down Expand Up @@ -183,19 +169,19 @@ describe('MatCalendar', () => {
expect(calendarBodyEl.getAttribute('tabindex')).toBe('-1');
});

it('should not move focus to the active cell on init', () => {
it('should not move focus to the active cell on init', waitForAsync(async () => {
const activeCell = calendarBodyEl.querySelector(
'.mat-calendar-body-active',
)! as HTMLElement;

spyOn(activeCell, 'focus').and.callThrough();
fixture.detectChanges();
zone.simulateZoneExit();
await new Promise(resolve => setTimeout(resolve));

expect(activeCell.focus).not.toHaveBeenCalled();
});
}));

it('should move focus to the active cell when the view changes', fakeAsync(() => {
it('should move focus to the active cell when the view changes', waitForAsync(async () => {
calendarInstance.currentView = 'multi-year';
fixture.detectChanges();

Expand All @@ -204,8 +190,7 @@ describe('MatCalendar', () => {
)! as HTMLElement;
spyOn(activeCell, 'focus').and.callThrough();

zone.simulateZoneExit();
tick();
await new Promise(resolve => setTimeout(resolve));

expect(activeCell.focus).toHaveBeenCalled();
}));
Expand Down
11 changes: 10 additions & 1 deletion src/material/datepicker/datepicker-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ import {
OnInit,
inject,
booleanAttribute,
afterNextRender,
Injector,
} from '@angular/core';
import {DateAdapter, ThemePalette} from '@angular/material/core';
import {AnimationEvent} from '@angular/animations';
Expand Down Expand Up @@ -509,6 +511,8 @@ export abstract class MatDatepickerBase<
/** Emits when the datepicker's state changes. */
readonly stateChanges = new Subject<void>();

private _injector = inject(Injector);

constructor(
private _overlay: Overlay,
private _ngZone: NgZone,
Expand Down Expand Up @@ -749,7 +753,12 @@ export abstract class MatDatepickerBase<

// Update the position once the calendar has rendered. Only relevant in dropdown mode.
if (!isDialog) {
this._ngZone.onStable.pipe(take(1)).subscribe(() => overlayRef.updatePosition());
afterNextRender(
() => {
overlayRef.updatePosition();
},
{injector: this._injector},
);
}
}

Expand Down

0 comments on commit 5e01bae

Please sign in to comment.