mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(focus-trap): add the ability to specify a focus target (#1752)
Fixes #1468.
This commit is contained in:
committed by
Kara
parent
bbc5f6a954
commit
72ac7a0982
@@ -6,24 +6,21 @@ import {InteractivityChecker} from './interactivity-checker';
|
||||
|
||||
|
||||
describe('FocusTrap', () => {
|
||||
let checker: InteractivityChecker;
|
||||
let fixture: ComponentFixture<FocusTrapTestApp>;
|
||||
|
||||
describe('with default element', () => {
|
||||
let fixture: ComponentFixture<FocusTrapTestApp>;
|
||||
let focusTrapInstance: FocusTrap;
|
||||
|
||||
beforeEach(() => TestBed.configureTestingModule({
|
||||
declarations: [FocusTrap, FocusTrapTestApp],
|
||||
providers: [InteractivityChecker]
|
||||
}));
|
||||
|
||||
beforeEach(inject([InteractivityChecker], (c: InteractivityChecker) => {
|
||||
checker = c;
|
||||
fixture = TestBed.createComponent(FocusTrapTestApp);
|
||||
focusTrapInstance = fixture.debugElement.query(By.directive(FocusTrap)).componentInstance;
|
||||
}));
|
||||
|
||||
it('wrap focus from end to start', () => {
|
||||
let focusTrap = fixture.debugElement.query(By.directive(FocusTrap));
|
||||
let focusTrapInstance = focusTrap.componentInstance as FocusTrap;
|
||||
|
||||
// Because we can't mimic a real tab press focus change in a unit test, just call the
|
||||
// focus event handler directly.
|
||||
focusTrapInstance.focusFirstTabbableElement();
|
||||
@@ -33,9 +30,6 @@ describe('FocusTrap', () => {
|
||||
});
|
||||
|
||||
it('should wrap focus from start to end', () => {
|
||||
let focusTrap = fixture.debugElement.query(By.directive(FocusTrap));
|
||||
let focusTrapInstance = focusTrap.componentInstance as FocusTrap;
|
||||
|
||||
// Because we can't mimic a real tab press focus change in a unit test, just call the
|
||||
// focus event handler directly.
|
||||
focusTrapInstance.focusLastTabbableElement();
|
||||
@@ -44,6 +38,35 @@ describe('FocusTrap', () => {
|
||||
.toBe('button', 'Expected button element to be focused');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with focus targets', () => {
|
||||
let fixture: ComponentFixture<FocusTrapTargetTestApp>;
|
||||
let focusTrapInstance: FocusTrap;
|
||||
|
||||
beforeEach(() => TestBed.configureTestingModule({
|
||||
declarations: [FocusTrap, FocusTrapTargetTestApp],
|
||||
providers: [InteractivityChecker]
|
||||
}));
|
||||
|
||||
beforeEach(inject([InteractivityChecker], (c: InteractivityChecker) => {
|
||||
fixture = TestBed.createComponent(FocusTrapTargetTestApp);
|
||||
focusTrapInstance = fixture.debugElement.query(By.directive(FocusTrap)).componentInstance;
|
||||
}));
|
||||
|
||||
it('should be able to prioritize the first focus target', () => {
|
||||
// Because we can't mimic a real tab press focus change in a unit test, just call the
|
||||
// focus event handler directly.
|
||||
focusTrapInstance.focusFirstTabbableElement();
|
||||
expect(document.activeElement.id).toBe('first');
|
||||
});
|
||||
|
||||
it('should be able to prioritize the last focus target', () => {
|
||||
// Because we can't mimic a real tab press focus change in a unit test, just call the
|
||||
// focus event handler directly.
|
||||
focusTrapInstance.focusLastTabbableElement();
|
||||
expect(document.activeElement.id).toBe('last');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -56,3 +79,16 @@ describe('FocusTrap', () => {
|
||||
`
|
||||
})
|
||||
class FocusTrapTestApp { }
|
||||
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<focus-trap>
|
||||
<input>
|
||||
<button id="last" md-focus-end></button>
|
||||
<button id="first" md-focus-start>SAVE</button>
|
||||
<input>
|
||||
</focus-trap>
|
||||
`
|
||||
})
|
||||
class FocusTrapTargetTestApp { }
|
||||
|
||||
@@ -27,7 +27,10 @@ export class FocusTrap {
|
||||
|
||||
/** Focuses the first tabbable element within the focus trap region. */
|
||||
focusFirstTabbableElement() {
|
||||
let redirectToElement = this._getFirstTabbableElement(this.trappedContent.nativeElement);
|
||||
let rootElement = this.trappedContent.nativeElement;
|
||||
let redirectToElement = rootElement.querySelector('[md-focus-start]') as HTMLElement ||
|
||||
this._getFirstTabbableElement(rootElement);
|
||||
|
||||
if (redirectToElement) {
|
||||
redirectToElement.focus();
|
||||
}
|
||||
@@ -35,7 +38,16 @@ export class FocusTrap {
|
||||
|
||||
/** Focuses the last tabbable element within the focus trap region. */
|
||||
focusLastTabbableElement() {
|
||||
let redirectToElement = this._getLastTabbableElement(this.trappedContent.nativeElement);
|
||||
let rootElement = this.trappedContent.nativeElement;
|
||||
let focusTargets = rootElement.querySelectorAll('[md-focus-end]');
|
||||
let redirectToElement: HTMLElement = null;
|
||||
|
||||
if (focusTargets.length) {
|
||||
redirectToElement = focusTargets[focusTargets.length - 1] as HTMLElement;
|
||||
} else {
|
||||
redirectToElement = this._getLastTabbableElement(rootElement);
|
||||
}
|
||||
|
||||
if (redirectToElement) {
|
||||
redirectToElement.focus();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user