Skip to content

feat(multiple): add options to autoFocus field for dialogs #22780

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 30, 2021

Conversation

amysorto
Copy link
Contributor

Before this PR, autoFocus was a boolean that allowed users to specify whether the container element or the first tabbable element is focused on dialog open. Now you can also specify focusing the first header element or use a CSS selector and focus the first element that matches that. If these elements can't be focused, then the container element is focused by default.

This applies to other components that are similar to dialog and also have a autoFocus field.

@amysorto amysorto requested review from jelbourn and crisbeto May 24, 2021 20:35
@google-cla google-cla bot added the cla: yes PR author has agreed to Google's Contributor License Agreement label May 24, 2021
@@ -20,7 +20,7 @@ export declare const matBottomSheetAnimations: {

export declare class MatBottomSheetConfig<D = any> {
ariaLabel?: string | null;
autoFocus?: boolean;
autoFocus?: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't just change this to a string since that's a breaking change. Instead, make it boolean | string and map boolean values to the behavior that most closely matches what they did prior to this change.

You can add a comment like:

/** @breaking-change 13.0.0 remove boolean from type */

to remind us that we can remove the boolean support 2 versions from now (in alignment with our policy for making breaking changes)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, the new type wouldn't be string, but instead include the enum type:

autoFocus?: AutoFocusTarget | string | boolean;

I know that AutoFocusTarget is a subtype of string, but adding the enum should improve IDE autocomplete

The removed version would also be v14 at this point

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up adding the comment in these .d.ts files. Although in the code I mainly saw them in .ts files. Is there any convention on where they should go?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment should go in the actual source; these .d.ts files in public_api_guard are automatically generated, so any changes there will be clobbered.

For context: when a TypeScript file is compiled, it generates the .js output file and a .d.ts type definition file. The type definition is produced so that other TypeScript code can type check against it without having to compile everything all together. We take of the type definition file with our public_api_guards to make sure we don't make unintentional changes to the public api. There's a test that compares the existing public api .d.ts file to the one from the PR and fails if it changed, so that any intentional changes have to be explicitly approved.

Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI you would put Fixes #22678 at the end of your commit message to tell GitHub that this PR addressed the corresponding issue (which creates a reference between them)

@@ -20,7 +20,7 @@ export declare const matBottomSheetAnimations: {

export declare class MatBottomSheetConfig<D = any> {
ariaLabel?: string | null;
autoFocus?: boolean;
autoFocus?: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, the new type wouldn't be string, but instead include the enum type:

autoFocus?: AutoFocusTarget | string | boolean;

I know that AutoFocusTarget is a subtype of string, but adding the enum should improve IDE autocomplete

The removed version would also be v14 at this point

@@ -26,6 +26,15 @@ import {take} from 'rxjs/operators';
import {InteractivityChecker} from '../interactivity-checker/interactivity-checker';


/** Options for how to handle focus upon opening the dialog */
export const enum AutoFocusOption {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would name this AutoFocusTarget

if (!this._checker.isFocusable(element)) {
element.tabIndex = -1;
// The tabindex attribute should be removed to avoid navigating to that element again
element.addEventListener('blur', () => element.removeAttribute('tabindex'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This handler should be added outside of the Angular zone. You can look through the codebase for runOutsideAngular for an example.

@amysorto amysorto force-pushed the auto-focus-options branch 2 times, most recently from 14313aa to ac27351 Compare June 2, 2021 16:19
Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good with a couple last comments. This PR definitely reinforces the need for us to finish cdk/dialog. As a follow-up, we'll also want to update the documentation for dialog/sidenav/bottom-sheet with the new stuff.

@amysorto amysorto force-pushed the auto-focus-options branch 5 times, most recently from c12cb53 to 8fcac0d Compare June 4, 2021 17:55
*/
private _autoFocusFirstTabbableElement() {
private _forceFocus(element: HTMLElement, options?: FocusOptions) {
Copy link
Collaborator

@zelliott zelliott Jun 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to move this out into an a11y helper? We have an identical function in our g3 app.

EDIT: Plus I now see there are multiple identical implementations throughout this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that these methods will exist in cdk/dialog once that's created in a future PR

Copy link
Member

@jelbourn jelbourn Jun 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I told Amy to do it this way since cdk/dialog is the correct place for this logic. It just has the unfortunate property of not existing yet.

*/
private _autoFocusFirstTabbableElement() {
private _forceFocus(element: HTMLElement, options?: FocusOptions) {
if (!this._interactivityChecker.isFocusable(element)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of checking if the element is focusable, we could just call element.focus(), then check to see if element === document.activeElement. If not, then apply tabindex = "-1" and focus again. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. Although it seems strange to have different functionality for the same thing. @jelbourn do you have thoughts on this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably stick with using InteractivityChecker mostly for consistency, but also because I vaguely recall that Firefox will sometimes(?) not actually focus an element until the next microtask tick.

@amysorto amysorto force-pushed the auto-focus-options branch from 8fcac0d to 76beab4 Compare June 8, 2021 19:55
Copy link
Member

@jelbourn jelbourn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

We can update the documentation in a follow-up PR

@jelbourn jelbourn added Accessibility This issue is related to accessibility (a11y) action: merge The PR is ready for merge by the caretaker area: material/dialog P2 The issue is important to a large percentage of users, with a workaround labels Jun 18, 2021
@amysorto amysorto added the target: minor This PR is targeted for the next minor release label Jun 25, 2021
@mmalerba
Copy link
Contributor

@amysorto needs rebase

@amysorto amysorto force-pushed the auto-focus-options branch from 76beab4 to 059dbde Compare July 26, 2021 17:11
@amysorto amysorto requested a review from a team as a code owner July 26, 2021 17:11
@mmalerba mmalerba force-pushed the auto-focus-options branch from 059dbde to bce7b34 Compare July 27, 2021 03:27
Before this PR, autoFocus was a boolean that allowed users to specify whether the container element or the first tabbable element is focused on dialog open. Now you can also specify focusing the first header element or use a CSS selector and focus the first element that matches that. If these elements can't be focused, then the container element is focused by default.

This applies to other components that are similar to dialog and also have a autoFocus field.

Fixes angular#22678
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Aug 30, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Accessibility This issue is related to accessibility (a11y) action: merge The PR is ready for merge by the caretaker area: material/dialog cla: yes PR author has agreed to Google's Contributor License Agreement P2 The issue is important to a large percentage of users, with a workaround target: minor This PR is targeted for the next minor release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants