Description
Feature Description
Today, FocusMonitor
listens for focus changes and attributes them to a particular input modality. It would also be useful to be able to listen for changes in aria-activedescendant
changes and attribute them appropriately as well. To support this, we can extend FocusMonitor.monitor
with an optional checkActiveDescendant
parameter that, when true
, will listen for changes in the focused element's aria-activedescendant
property with a MutationObserver
.
Use Case
In my app, we render strong keyboard focus indicators only on keyboard focus. We determine this by attaching a FocusMonitor
to body
with checkChildren = true
. When the class .cdk-keyboard-focused
is present on the body
, we know that keyboard input modality has been detected.
This works for most cases. However, consider the following scenario with the MatAutocomplete
component.
- User clicks on
MatAutocomplete
input. - Autocomplete options render below the input.
- User presses down arrow to navigate to the first option.
At step 1, we do not want to render a strong focus indicator (as the user is interacting with mouse). At step 3, we do want to render a strong focus indicator on the first option (as the user is now interacting with keyboard). However, FocusMonitor
does not support this scenario today, as no focus change has occurred. Instead, the aria-activedescendant
on the focused <input />
element has updated. We want to be able to detect this change.
Implementation Details
I'm thinking we can support this in FocusMonitor
via the following changes:
- On
FocusMonitor._onFocus
, create a newMutationObserver
that listens foraria-activedescendant
changes on the focused element (ifcheckActiveDescendant
istrue
). Additionally, if a createdMutationObserver
already exists, disconnect the existing observer. This means that there should only ever be oneMutationObserver
at any point in time, as we only need to observe thedocument.activeElement
. This also means that ifaria-activedescendant
changes on an element that is not focused, this will not be detected (I think this is fine). - When the observer callback is fired, update the focus origin accordingly.
- On
FocusMonitor._onBlur
, disconnect the existing observer (again ifcheckActiveDescendant
istrue
).