Skip to content

Commit 5f0ce7e

Browse files
feat(cdk/interactivity-checker) isFocusable optionally includes invisible/disabled elements
Fixes #6468.
1 parent df981ee commit 5f0ce7e

File tree

3 files changed

+26
-3
lines changed

3 files changed

+26
-3
lines changed

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,17 @@ describe('InteractivityChecker', () => {
144144
});
145145
});
146146

147+
it('should return true for disabled form controls with includeDisabled', () => {
148+
const elements = createElements('input', 'textarea', 'select', 'button');
149+
elements.forEach(el => el.setAttribute('disabled', ''));
150+
appendElements(elements);
151+
152+
elements.forEach(el => {
153+
expect(checker.isFocusable(el, true, false))
154+
.toBe(true, `Expected <${el.nodeName} disabled> to to be focusable`);
155+
});
156+
});
157+
147158
it('should return false for a `display: none` element', () => {
148159
testContainerElement.innerHTML =
149160
`<input style="display: none;">`;
@@ -153,6 +164,15 @@ describe('InteractivityChecker', () => {
153164
.toBe(false, 'Expected element with `display: none` to not be visible');
154165
});
155166

167+
it('should return true for a `display: none` element with includeInvisible', () => {
168+
testContainerElement.innerHTML =
169+
`<input style="display: none;">`;
170+
const input = testContainerElement.querySelector('input') as HTMLElement;
171+
172+
expect(checker.isFocusable(input, false, true))
173+
.toBe(true, 'Expected element with `display: none` to be focusable');
174+
});
175+
156176
it('should return false for the child of a `display: none` element', () => {
157177
testContainerElement.innerHTML =
158178
`<div style="display: none;">

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,15 @@ export class InteractivityChecker {
132132
* Gets whether an element can be focused by the user.
133133
*
134134
* @param element Element to be checked.
135+
* @param includeDisabled When true, considers element to be focusable even if it is disabled.
136+
* @param includeInvisible When true, considers element to be focusable even if it is not visible.
135137
* @returns Whether the element is focusable.
136138
*/
137-
isFocusable(element: HTMLElement): boolean {
139+
isFocusable(element: HTMLElement, includeDisabled = false, includeInvisible = false): boolean {
138140
// Perform checks in order of left to most expensive.
139141
// Again, naive approach that does not capture many edge cases and browser quirks.
140-
return isPotentiallyFocusable(element) && !this.isDisabled(element) && this.isVisible(element);
142+
return isPotentiallyFocusable(element) && (includeDisabled || !this.isDisabled(element)) &&
143+
(includeInvisible || this.isVisible(element));
141144
}
142145

143146
}

tools/public_api_guard/cdk/a11y.d.ts

Lines changed: 1 addition & 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, includeDisabled?: boolean, includeInvisible?: boolean): boolean;
185185
isTabbable(element: HTMLElement): boolean;
186186
isVisible(element: HTMLElement): boolean;
187187
static ɵfac: i0.ɵɵFactoryDef<InteractivityChecker, never>;

0 commit comments

Comments
 (0)