Skip to content

Commit 51137b6

Browse files
feat(cdk/interactivity-checker) add config parameter to isFocusable (#19594)
Adds config object to isFocusable so that it can optionally return true even when an element is invisible. Example use case: call isFocusable with ignoreVisibility=true to find a focusable element within a collapsed element. If isFocusable returns true, expand the collapsed element and focus the focusable element. Fixes #6468.
1 parent c225b28 commit 51137b6

File tree

3 files changed

+30
-4
lines changed

3 files changed

+30
-4
lines changed

src/cdk/a11y/interactivity-checker/interactivity-checker.spec.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {Platform} from '@angular/cdk/platform';
22
import {inject} from '@angular/core/testing';
3-
import {InteractivityChecker} from './interactivity-checker';
3+
import {InteractivityChecker, IsFocusableConfig} from './interactivity-checker';
44

55
describe('InteractivityChecker', () => {
66
let platform: Platform;
@@ -153,6 +153,17 @@ describe('InteractivityChecker', () => {
153153
.toBe(false, 'Expected element with `display: none` to not be visible');
154154
});
155155

156+
it('should return true for a `display: none` element with ignoreVisibility', () => {
157+
testContainerElement.innerHTML =
158+
`<input style="display: none;">`;
159+
const input = testContainerElement.querySelector('input') as HTMLElement;
160+
let config = new IsFocusableConfig();
161+
config.ignoreVisibility = true;
162+
163+
expect(checker.isFocusable(input, config))
164+
.toBe(true, 'Expected element with `display: none` to be focusable');
165+
});
166+
156167
it('should return false for the child of a `display: none` element', () => {
157168
testContainerElement.innerHTML =
158169
`<div style="display: none;">

src/cdk/a11y/interactivity-checker/interactivity-checker.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@
99
import {Platform} from '@angular/cdk/platform';
1010
import {Injectable} from '@angular/core';
1111

12+
/**
13+
* Configuration for the isFocusable method.
14+
*/
15+
export class IsFocusableConfig {
16+
/**
17+
* Whether to count an element as focusable even if it is not currently visible.
18+
*/
19+
ignoreVisibility: boolean = false;
20+
}
1221

1322
// The InteractivityChecker leans heavily on the ally.js accessibility utilities.
1423
// Methods like `isTabbable` are only covering specific edge-cases for the browsers which are
@@ -132,12 +141,14 @@ export class InteractivityChecker {
132141
* Gets whether an element can be focused by the user.
133142
*
134143
* @param element Element to be checked.
144+
* @param config The config object with options to customize this method's behavior
135145
* @returns Whether the element is focusable.
136146
*/
137-
isFocusable(element: HTMLElement): boolean {
147+
isFocusable(element: HTMLElement, config?: IsFocusableConfig): boolean {
138148
// Perform checks in order of left to most expensive.
139149
// Again, naive approach that does not capture many edge cases and browser quirks.
140-
return isPotentiallyFocusable(element) && !this.isDisabled(element) && this.isVisible(element);
150+
return isPotentiallyFocusable(element) && !this.isDisabled(element) &&
151+
(config?.ignoreVisibility || this.isVisible(element));
141152
}
142153

143154
}

tools/public_api_guard/cdk/a11y.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ export interface Highlightable extends ListKeyManagerOption {
181181
export declare class InteractivityChecker {
182182
constructor(_platform: Platform);
183183
isDisabled(element: HTMLElement): boolean;
184-
isFocusable(element: HTMLElement): boolean;
184+
isFocusable(element: HTMLElement, config?: IsFocusableConfig): boolean;
185185
isTabbable(element: HTMLElement): boolean;
186186
isVisible(element: HTMLElement): boolean;
187187
static ɵfac: i0.ɵɵFactoryDef<InteractivityChecker, never>;
@@ -190,6 +190,10 @@ export declare class InteractivityChecker {
190190

191191
export declare function isFakeMousedownFromScreenReader(event: MouseEvent): boolean;
192192

193+
export declare class IsFocusableConfig {
194+
ignoreVisibility: boolean;
195+
}
196+
193197
export declare class ListKeyManager<T extends ListKeyManagerOption> {
194198
get activeItem(): T | null;
195199
get activeItemIndex(): number | null;

0 commit comments

Comments
 (0)