-
Notifications
You must be signed in to change notification settings - Fork 6.8k
fix(material/dialog): improve screen reader support when opened #23228
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
Conversation
c1f361f
to
92a5dce
Compare
Kicked off an internal presubmit |
src/components-examples/material/dialog/dialog-harness/dialog-harness-example.spec.ts
Show resolved
Hide resolved
src/material/dialog/dialog.ts
Outdated
* @return true if a dialog was opened less than 300ms ago, false otherwise. | ||
*/ | ||
_shouldBlockAnotherDialogOpening(): boolean { | ||
return new Date().getTime() - this._lastDialogOpenTime < 300; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does the 300ms number come from? Currently the dialog animation is 150ms.
Also I think that this is pretty fragile. A better approach would be to have an isAnimating
flag on the dialog container. It should be straightforward to do, because we already have _onAnimationStart
and _onAnimationDone
callbacks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree and I don't prefer this naive approach, but that's what past reviews and presubmits suggested that we go with.
In https://github.com/angular/components/pull/23085/files#diff-fcade7d193ab41c7b9e2b072af496bedcf5b73ee519e237e32f8f5e00328246dR81, I had _dialogAnimatingOpen
in _MatDialogBase
, but you are suggesting using an attribute on the <mat-dialog-container>
DOM element instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once focus has been moved, the keyboard events won't be a problem anymore because they'll fire from inside the dialog. We could have another flag like _hasCapturedFocus
which is flipped inside the focusInitialElementWhenReady
callback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The _hasCapturedFocus
would be on the first dialog's _MatDialogContainerBase
. Then when the 2nd dialog open is called and we see a matching componentOrTemplateRef
, we would find the matching DialogRef
in this.openDialogs
and check it's _containerInstance. _hasCapturedFocus
?
- If it's
false
, then we throw an error or return theDialogRef
from the first dialog. - If it's
true
, then what? Are we thinking that it shouldn't be possible to get a duplicateopen
event in this case and we should go ahead with opening the second dialog (with the samecomponentOrTemplateRef
)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct. If focus made it into the dialog, then it shouldn't be possible to accidentally trigger another dialog from the same trigger.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this introduce a race condition that could lead to inconsistent behavior?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Focus is moved as a result of the animation finishing so I don't think there would be a race condition.
@zarend how did this go? |
187 test failures 😳 |
Doh. 🤦🏼♂️ |
641cb48
to
8f247b0
Compare
@@ -14,6 +14,8 @@ import { | |||
AnimationTriggerMetadata, | |||
} from '@angular/animations'; | |||
|
|||
export const _transitionTime = 150; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@crisbeto I didn't want to copy this value all over the place. Instead I defined this const here, but I'm not confident that this is the right way to do it in this repo. Can you provide some guidance here please?
this._lastDialogOpenTime = new Date().getTime(); | ||
this._lastDialogOpenType = componentOrTemplateRef; | ||
this._lastDialogRef = dialogRef; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if these should get set to null
in ngOnDestroy()
? But I guess that would require changing the types to accept null
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get why we need to deal with time at all. What's stopping from setting an isAnimating
flag on the dialog container and reading it instead? We already have all the event handlers in place and the dialog refs are already tracked in MatDialog.openDialogs
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's what caused the need for all of the flushing in the other PR. But I guess you feel like this specific approach would avoid that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that the flushing was necessary, because we introduced a hard error if you didn't flush. Since now we return a dialog ref, the flushing should be necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed, when I removed the throwing of the error, I was able to remove the flush();
calls from the other PR.
8f247b0
to
6cc3644
Compare
Test run (internal) Resulted in 94 test failures |
6cc3644
to
78ea4b4
Compare
So the recently presubmit failures are due to a case where the |
78ea4b4
to
087ed2c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, although I think that we can iterate on it afterwards so that we don't have to check the time to know if an animation is running.
@@ -290,6 +291,9 @@ export class MatDialogTitle implements OnInit { | |||
// @public | |||
export function throwMatDialogContentAlreadyAttachedError(): void; | |||
|
|||
// @public | |||
export const _transitionTime = 150; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we avoid exposing the publicly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to, that's kind of what I was trying to ask in #23228 (review).
087ed2c
to
a891034
Compare
- notify screen reader users that they have entered a dialog - previously only the focused element would be read i.e. "Close Button Press Search plus Space to activate" - now the screen reader user gets the normal dialog behavior, which is to read the dialog title, role, content, and then tell the user about the focused element - this matches the guidance here: https://www.w3.org/TR/wai-aria-practices-1.1/examples/dialog-modal/dialog.html - Avoid opening multiple of the same dialog before animations complete by returning the previous `MatDialogRef` - update tests to use different dialog components when they need to open multiple dialogs quickly Fixes angular#21840
a891034
to
72bd6c6
Compare
Currently the preferred solution for this is in PR #23085. |
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
i.e. "Close Button Press Search plus Space to activate"
read the dialog title, role, content, and then tell the user about the
focused element
- this matches the guidance here:
https://www.w3.org/TR/wai-aria-practices-1.1/examples/dialog-modal/dialog.html
the previous
MatDialogRef
dialogs quickly
Fixes #21840
This PR takes a different approach than PR #23085 which required adding
flush()
to tests to ensure that the animations had a chance to complete. This PR uses a more naive approach that only gives animations 300ms to complete and won't allow another dialog of the same type to open within that time. The a11y piece of both PRs is the same (only move the focus to the dialog once).