Skip to content

Commit

Permalink
fix(focus-trap): focus initial element when zone stabilizes (#4867)
Browse files Browse the repository at this point in the history
* fix(focus-trap): focus initial element when zone stabilizes

* Focuses the first element when the zone stabilizes, instead of when the microtask queue is empty. This avoids issues where the element might be focused before Angular is done doing change detection.
* Only runs the `onStable` callback if the zone is unstable.
* Avoids an extra DOM lookup by combining a couple of selectors.

Fixes #4864.

* chore: switch to for loop
  • Loading branch information
crisbeto authored and andrewseguin committed Jun 5, 2017
1 parent 7e91270 commit ce9d253
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 16 deletions.
6 changes: 5 additions & 1 deletion src/demo-app/dialog/dialog-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ export class DialogDemo {
selector: 'demo-jazz-dialog',
template: `
<p>It's Jazz!</p>
<p><label>How much? <input #howMuch></label></p>
<md-input-container>
<input mdInput placeholder="How much?" #howMuch>
</md-input-container>
<p> {{ data.message }} </p>
<button type="button" (click)="dialogRef.close(howMuch.value)">Close dialog</button>
<button (click)="togglePosition()">Change dimensions</button>`
Expand Down
41 changes: 26 additions & 15 deletions src/lib/core/a11y/focus-trap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,24 +81,28 @@ export class FocusTrap {
});
}

/**
* Waits for the zone to stabilize, then either focuses the first element that the
* user specified, or the first tabbable element..
*/
focusInitialElementWhenReady() {
this._ngZone.onMicrotaskEmpty.first().subscribe(() => this.focusInitialElement());
this._executeOnStable(() => this.focusInitialElement());
}

/**
* Waits for microtask queue to empty, then focuses
* Waits for the zone to stabilize, then focuses
* the first tabbable element within the focus trap region.
*/
focusFirstTabbableElementWhenReady() {
this._ngZone.onMicrotaskEmpty.first().subscribe(() => this.focusFirstTabbableElement());
this._executeOnStable(() => this.focusFirstTabbableElement());
}

/**
* Waits for microtask queue to empty, then focuses
* Waits for the zone to stabilize, then focuses
* the last tabbable element within the focus trap region.
*/
focusLastTabbableElementWhenReady() {
this._ngZone.onMicrotaskEmpty.first().subscribe(() => this.focusLastTabbableElement());
this._executeOnStable(() => this.focusLastTabbableElement());
}

/**
Expand All @@ -107,18 +111,16 @@ export class FocusTrap {
* @returns The boundary element.
*/
private _getRegionBoundary(bound: 'start' | 'end'): HTMLElement | null {
let markers = [
...Array.prototype.slice.call(this._element.querySelectorAll(`[cdk-focus-region-${bound}]`)),
// Deprecated version of selector, for temporary backwards comparability:
...Array.prototype.slice.call(this._element.querySelectorAll(`[cdk-focus-${bound}]`)),
];

markers.forEach((el: HTMLElement) => {
if (el.hasAttribute(`cdk-focus-${bound}`)) {
// Contains the deprecated version of selector, for temporary backwards comparability.
let markers = this._element.querySelectorAll(`[cdk-focus-region-${bound}], ` +
`[cdk-focus-${bound}]`) as NodeListOf<HTMLElement>;

for (let i = 0; i < markers.length; i++) {
if (markers[i].hasAttribute(`cdk-focus-${bound}`)) {
console.warn(`Found use of deprecated attribute 'cdk-focus-${bound}',` +
` use 'cdk-focus-region-${bound}' instead.`, el);
` use 'cdk-focus-region-${bound}' instead.`, markers[i]);
}
});
}

if (bound == 'start') {
return markers.length ? markers[0] : this._getFirstTabbableElement(this._element);
Expand Down Expand Up @@ -206,6 +208,15 @@ export class FocusTrap {
anchor.classList.add('cdk-focus-trap-anchor');
return anchor;
}

/** Executes a function when the zone is stable. */
private _executeOnStable(fn: () => any): void {
if (this._ngZone.isStable) {
fn();
} else {
this._ngZone.onStable.first().subscribe(fn);
}
}
}


Expand Down

0 comments on commit ce9d253

Please sign in to comment.