Skip to content
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

TeachingTip: Pressing F6 now sets focus on custom content of light-dismissable tip #3545

Conversation

Felix-Dev
Copy link
Contributor

Description

This PR fixes a bug where pressing F6 while a light-dismissable teaching tip is displayed would not set focus to the tip's custom content (if it is focusable). Pressing F6 again moves focus away from the content back to the tip again so users can always close it using the Esc key, for example.

More generally speaking, if a tip does not have a close button, pressing F6 now sets focus on the custom content if possible. Typically, only light-dismissable tips don't have a close button.

Motivation and Context

Fixes #3257.

How Has This Been Tested?

Tested manually and added interaction test.

GIF:

teachingtip-lightdismiss-focus-custom-content

@ghost ghost added the needs-triage Issue needs to be triaged by the area owners label Nov 2, 2020
@StephenLPeters StephenLPeters added area-TeachingTip team-Controls Issue for the Controls team and removed needs-triage Issue needs to be triaged by the area owners labels Nov 3, 2020
Copy link
Contributor

@StephenLPeters StephenLPeters left a comment

Choose a reason for hiding this comment

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

:shipit:

@StephenLPeters
Copy link
Contributor

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

if (const auto focusableElement = winrt::FocusManager::FindFirstFocusableElement(mainContentPresenter))
{
// A focusable element can be a Hyperlink which does not expose a GettingFocus event. As such, we use the FocusManager API here.
auto const scopedRevoker = winrt::FocusManager::GettingFocus(winrt::auto_revoke, [this, focusableElement](auto const&, const winrt::GettingFocusEventArgs& args) {
Copy link
Contributor Author

@Felix-Dev Felix-Dev Nov 20, 2020

Choose a reason for hiding this comment

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

Looks like I missed the fact that the FocusManager::GettingFocus API is only available starting with RS5 (hence the test failures on RS4 and below).

A possible alternative could be using the FocusManager::GetFocusedEelement API as that is available since day 1 Windows 10. The updated code could then look like this:

// If there is no close button, try setting focus on the tip's custom content.
else if (const auto mainContentPresenter = m_mainContentPresenter.get())
{
    if (const auto focusableElement = winrt::FocusManager::FindFirstFocusableElement(mainContentPresenter))
    {
        const auto previouslyFocusedElement = winrt::FocusManager::GetFocusedElement();

        const bool setFocus = SetFocus(focusableElement, winrt::FocusState::Keyboard);
        if (setFocus)
        {
            if (const auto previouslyFocusedElementAsDO = previouslyFocusedElement.try_as<winrt::DependencyObject>())
            {
                m_previouslyFocusedElement = winrt::make_weak(previouslyFocusedElementAsDO);
            }
        }
        args.Handled(setFocus);
    }
}

@StephenLPeters @ranjeshj Your thoughts?

Copy link
Contributor Author

@Felix-Dev Felix-Dev Nov 21, 2020

Choose a reason for hiding this comment

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

On another note, I have also noticed that pressing F6 to put focus on a tip in a Xaml Island scenario does not work (no matter if focus is already in the island or outside of it at the time of F6). Is that a known issue?

One implemention issue I see with Xaml island support is that we expect our focusable elements to be of type Windows.UI.Xaml.DependencyObject, yet, for example, a WPF button is of type System.Windows.DependencyObject.

So... is the current guidance that developers using a tip in a Xaml island need to take care of keyboard accessibility (F6) on their own? And if so, we should update the teaching tip documentation to reflect that.

Copy link
Contributor

Choose a reason for hiding this comment

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

@JesseCol @Austin-Lamb for the Xaml Island behavior. I would like to avoid controls having to do special behavior for Islands as much as possible.

@Felix-Dev
Copy link
Contributor Author

See my comment about the test failures here.

@ranjeshj
Copy link
Contributor

@Felix-Dev Keith made a recent fix for the build pipelines. can you merge with master for your PRs ?

@Felix-Dev
Copy link
Contributor Author

@ranjeshj Merged with master and updated implementation to use FocusManager::GetFocusedElement() to resolve test failures.

@ranjeshj
Copy link
Contributor

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@Felix-Dev
Copy link
Contributor Author

Felix-Dev commented Nov 23, 2020

Hmmm...while moving to FocusManager::GetFocusedElement() fixed the test failure on RS4, we are still failing on RS3 and RS2. I checked the used FocusManager APIs again and both APIs are available since TS1 (first public Windows 10 version):

Could there be a different popup (focus/ESC keyboard) behavior on RS3 and RS2? The test assumes that we can
a) set focus to the teaching tip's internal light dismiss popup and
b) the light-dismissable popup can be closed by pressing the ESC key

Other than perhaps some popup changes I am not immediately sure where else the cause of the RS3 and below test failure could lie, especially if the FocusManager APIs do indeed work as expected on these versions.

@ranjeshj @StephenLPeters Any ideas?

@StephenLPeters
Copy link
Contributor

I'll take a look when this new build finishes, sorry the old one aged out.

@StephenLPeters
Copy link
Contributor

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@Felix-Dev
Copy link
Contributor Author

@StephenLPeters Need to merge with master to resolve the merge conflicts...you should still have access to the recent test pipeline run then, correct?

@StephenLPeters
Copy link
Contributor

yes, I will

@Felix-Dev
Copy link
Contributor Author

Merged with master to resolve merge conflicts.

m_previouslyFocusedElement = winrt::make_weak(args.OldFocusedElement());
});
const bool setFocus = f6Button.Focus(winrt::FocusState::Keyboard);
args.Handled(setFocus);
}
// If there is no close button, try setting focus on the tip's custom content.
else if (const auto mainContentPresenter = m_mainContentPresenter.get())
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of making a seperate else block for this case, can we just expand the lambda that sets the f6Button a bit? something like:

            const winrt::Button f6Button = [this]() -> winrt::Button
            {
                auto firstButton = m_closeButton.get();
                auto secondButton = m_alternateCloseButton.get();
                //Prefer the close button to the alternate, except when there is no content.
                if (!CloseButtonContent())
                {
                    std::swap(firstButton, secondButton);
                }
                if (firstButton && firstButton.Visibility() == winrt::Visibility::Visible)
                {
                    return firstButton;
                }
                else if (secondButton && secondButton.Visibility() == winrt::Visibility::Visible)
                {
                    return secondButton;
                }
else if (const auto mainContentPresenter = m_mainContentPresenter.get())
{
    return const auto focusableElement = winrt::FocusManager::FindFirstFocusableElement(mainContentPresenter);
}
                return nullptr;
            }();

Then we shouldn't need this block at all

Copy link
Contributor Author

@Felix-Dev Felix-Dev Dec 7, 2020

Choose a reason for hiding this comment

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

@StephenLPeters Moved code into lambda as suggested. Note that I currently am solely using the FocusManager::GetFocusedElement() API to get the previously focused element as as the GettingFocus event is not available to all possibly focusably elements (such as a Hyperlink).

That said, if you prefer we use the UIElement.GettingFocus API wherever possible (and the documentation suggests this), I can add a check for the f6FocusableElement to see if it is a UIElement and if it is, we use the GettingFocus event instead.

@StephenLPeters
Copy link
Contributor

@Felix-Dev the down level issue is pretty weird. The issue is that pressing F6 the second time isn't returning focus to the previously focused element. This is because previously focused element pointer we have is not a Control, Hyperlink, ContentLink or Webview, the acceptable types down level which you can see here:

inline bool SetFocus(winrt::DependencyObject const& object, winrt::FocusState focusState)
. So the question is, what had focus when we hit F6. Well... I'm not sure... Even on the latest OS versions, when a light dismiss popup is opened, focus goes to... something, presumably because you don't want the users keyboard interaction to close the recently opened light dismiss popup. However this object that is getting focus isn't one of the types we support. However on RS4+ we use the TryFocusAsync which is able to set focus to this object, which explains why the test passes up level.

I'm hoping @Austin-Lamb or @ocalvo can shed light on where focus goes when a light dismiss popup is opened. However I suspect there wont be a way for us to work around this, but maybe pressing f6 a second time should close a light dismiss teaching tip so that focus can return to the app?

@Felix-Dev
Copy link
Contributor Author

Felix-Dev commented Dec 7, 2020

@StephenLPeters Yes, I am seeing the reason for the test failure now, thanks for investigating! In the light-dismissable teaching tip case, opening such a tip sets focus to the tip's internal light-dismissable popup. As such, users can press, for example, the Esc key to close the tip. However, as the popup is not one of those UI elements which has an explicit Focus() method, the test fails on RS3 and below. As you have outlined above, we do not have FocusManager::TryFocusAsync() available to us on RS3 and below, thus we currently cannot set back focus on the internal light-dismissable popup again on the second F6 key press -- hence the test failure because pressing Esc after the second F6 now no longer closes the tip.

Important here is that we make sure that setting focus on a light-dismissable tip's content still gives users the ability to close the tip via the keyboard. For example, the custom content could be a button which handles, for whatever reason, the Esc key pressed event and thus prevents the tip from being closed with the Esc key when focus is on that button. So in that case there has to be a guranteed way users can still close the tip via the keyboad.

In light of this, your suggestion about closing the light-dismissable tip instead when pressing F6 a second time sounds like a possible solution for now. Presumably, users would only invoke F6 again on a tip to close it anyway (i.e. via the Esc key in a follow-up keyboard interaction) so we might as well shortcut that operation here. If we do opt for this approach, we should make sure to document this behavior.

@StephenLPeters
Copy link
Contributor

I wonder if we should rethink the f6 behavior for light dismiss ones. Maybe if the teaching tip is light dismiss we should always immediately put focus in the popup. I think that is better than focus being "lost" on the light dismiss popup. If we did this, we could have the first F6 close the popup? We would also need to change the accessibility announcement from "Press F6 to go to new notification from [App name]"

@StephenLPeters
Copy link
Contributor

@SavoySchuler fyi

@Felix-Dev
Copy link
Contributor Author

@StephenLPeters

Maybe if the teaching tip is light dismiss we should always immediately put focus in the popup. I think that is better than focus being "lost" on the light dismiss popup.

A light-dismissable tip does not need to have focusable content though so in that case, we would essentially have the same visual behavior as today, right?

In the framework, we seem to use the Esc key to dismiss transient UI in a few places, like a Flyout/MenuFlyout:

Unlike a dialog, a flyout can be quickly dismissed by tapping or clicking somewhere outside the flyout, pressing the Escape key or Back button, resizing the app window, or changing the device's orientation.

Then there is also the case of light-dimissable popups which are currently dismissable by pressing the Esc key.

In that sense, using F6 to close a transient UI like a light-dismissabe tip seems a bit...unexpected to me as a user when other transient controls use the Esc key among other user input events.

@StephenLPeters
Copy link
Contributor

I agree with your assessment. In that case can we make is so F6 doesn't do anything when the tip is in light dismiss mode? Assuming we move focus to its focusable content when it opens by default (if it has some).

@SavoySchuler
Copy link
Member

SavoySchuler commented Dec 7, 2020

Hey all, thank you for great work here! I'm just joining the thread, so allow me to get caught up.

I am to understand that currently F6 will not take you to a light-dismiss tip unless there is a close button? If that is the case, that is a major accessibility bug. The heading landmark navigation (Narrator + F6) was added as an accessibility requirement to give keyboard users a way to access TeachingTips.

I think there are two questions:

  1. Where should F6 navigation into a TeachingTip land? My understanding is that Narrator will read out loud the focused content once focus lands in TeachingTip, so my expectation would be that focus should land on whatever non-close/button content is first in the tabstop order (Title, Subtitle, or Content) when Narrator is active, so that a sight-limited user may naturally navigate downward across the content of a TeachingTip with the same reading pattern of a sighted-user (link to TeachingTip accessibility patterns). @YuliKl does this align with your expectation?
  2. Should pressing F6 again (once already focused in the TeachingTip) move focus elsewhere in a Teaching? My perspective is no, according to the Landmark documentation linked above, F6 is reserved for "Header navigation" and is intended for larger navigation leaps (effectively switching between more major content regions where navigation to/from might be more larger or otherwise more ambiguous to a sight-limited person; e.g.: think Microsoft word, F6 should cycle between the Ribbon, editing area, bottom toolbar, right-hand tools pane, tips, etc. - not within them). So ultimately, F6 should move to the top of a TeachingTip and then (once pressed again) out of it to the next Landmark Header stop.

I'm definitely catching up here so please let me know if I missed something or details we should discuss further! 🙂

Thanks again for catching this bug, it certainly sounds like there is something here worth fixing.

@SavoySchuler
Copy link
Member

I chatted with @YuliKl who added some context.

  1. The take away was that Narrator will land on the first focusable element within the TeachingTip and we're not sure if there's much we can do about that. Assuming focus appropriately lands within the TeachingTip (which I believe is the bug this PR is fixing), the suggestion was to let Narrator select its focus as it may, but support non-sighted users by injecting some narrated contextual info into the initial F6 navigation event (E.g., "Teaching tip close button" or "Teaching tip content").
  2. @YuliKl agreed with my statement here.

@Felix-Dev
Copy link
Contributor Author

Felix-Dev commented Dec 8, 2020

@SavoySchuler

To 1:

The take away was that Narrator will land on the first focusable element within the TeachingTip and we're not sure if there's much we can do about that. Assuming focus appropriately lands within the TeachingTip (which I believe is the bug this PR is fixing), the suggestion was to let Narrator select its focus as it may, but support non-sighted users by injecting some narrated contextual info into the initial F6 navigation event (E.g., "Teaching tip close button" or "Teaching tip content").

Yes, this PR aims to fix a bug where users are currently unable to set focus into the tip via the keyboard when there is no close button for the tip. This is by default the case for light-dismissable tips so pressing F6 does not yet set focus inside the tip for them (which is the bug in question).

Current Narrator output when navigating focus inside a tip

I tested the narrator input and for tips with a close button (aka not light-dimissable), pressing F6 puts the focus on the close button and narrator reads: "tip...close...button" where the "..." are symbolizing an "audial pause". From the focused close button, users can then use the Tab key to move focus to focusable content of the tip (if any). Narrator only reads the content of that custom content out for now (i.e. "[button_content]...button"), so no additional notice of "Teaching tip content" or any of that sort. That is also the case when the tip does not have a close button, so F6 moves focus directly to the focusable content and narrator reads "tip...[button_content]...button".

To 2:

So ultimately, F6 should move to the top of a TeachingTip and then (once pressed again) out of it to the next Landmark Header stop.

@SavoySchuler @StephenLPeters To my understanding this means that for light-dismissable tips with focusable content we should implement the suggestion made by @StephenLPeters: Pressing F6 a second time closes the tip and sets focus back to the previously focused element (before the tip was opened - as is currently the case when closing a tip). Is that correct?

How, then, about light-dismissable tips then which do not have any focusable content?
Should we allow

  • both Esc key tip closing (this feature comes with the Popup class we host the teaching tip in) and
  • closing the tip by pressing F6 (that would be on the first time the user presses the key then since no focusable content exists)?

Keyboard support for closing a light-dismissable tip without any focusable content

Supporting both the Esc key and F6 to close a light-dimissable tip without any focusable content might not be a bad idea because it shares a keyboard closing mechanism (F6) with light-dismissable tips which do have custom content. As such, closing a light-dismissabe tip with F6 would always be an option, no matter if custom focusable content exists or not.

Narrator output improvements about how to close a tip via keyboard

When opening a tip, Narrator for now says users can press F6 to put focus inside the tip. If we allow F6 to close a light-dismissable tip, we should update the narrator output. So for light-dimissable tips with focusable content: "Press F6 to set focus inside the tip. Press F6 a second time to close the tip" -> something like that. As for light-dimissable tips without any focusable content: There is no need for narrator to announce to users to press F6 to focus content (as there is no such content). Instead, we should probably directly announce how users can close a light-dismissable tip (i.e. via the Esc key, or, if approved, pressing the F6 key). Thoughts?

"Focus lost" visual effect for light-dismissable tip

I also read the comments by @SavoySchuler and @YuliKl that when we open a light-dismissable tip, we do not yet put focus on the focusable content (if any). Setting focus via the keyboard can be done by pressing F6 for the first time (and users can then use the Tab key to navigate through all the focusable tip elements). As such, we would still have the appearance of "focus being lost" when opening a tip (as focus would be on the internal popup hosting the tip and enabling the user to close the tip via the Esc key). @SavoySchuler @StephenLPeters Am I reading this correctly here?
Narrator output could help here by announcing to the user that the opened light-dismissable tip can be closed via keyboard keys (Esc and/or F6) so keyboard focus is currently captured by the tip).

Summary of my understanding about the current questions/suggestion we are facing as part of this PR

We should:

  • perhaps improve the narrator output when setting focus inside the tip by pressing F6 (differentiate between close button and custom content)
  • If we set focus on custom content of a light-dimissable tip, users can press F6 again to close the tip and set focus back to the focused UI element before the tip was opened
  • for light-dismissable tips which do not have any focusable content: Think about whether Esc key and F6 can be used to close the tip
  • improve the narrator output for light-dismissable tips how users can close such a tip via the keyboard

Additional Narrator output suggestion when opening a tip

Additionally, would it make sense to have Narrator announce the type of tip being opened? When opening a tip today (either light-dismissable or not), Narrator tells users that they can press F6 to set focus inside the tip. It does net tell the user whether that tip is a light-dimissable tip (so it can be closed via Esc key, clicking elsewhere on the app UI surface...) or not. The distinction between a tip and a light-dismissable tip for now is just a) the not-existing default close button for a tip and b) a different default tip background. However, that design can be changed by developers so it might not be a visual characteristic users can uniformly rely on. Having Narrator announce an opened tip as being light-dismissable could be of help here. @SavoySchuler @StephenLPeters Thoughts?

@SavoySchuler
Copy link
Member

+@YuliKl for a double check on this accessibility scenario.

@Felix-Dev what a write-up! Love your energy here! Thank you a TON for your passion in making sure we have a great accessibility experience. 😄

I think I have a few different takeaways:

  • I believe light-dismiss vs manual dismiss is the point of distinction in the experience (not the content). I believe it is appropriate to close a light-dismiss tip when either F6 or escape is used to navigate away (regardless of focusable content) - as light-dismiss tips are meant to be light, transient, and require limited engagement. I believe manual dismiss tip should not close on F6 but should instead remain open until manually dismissed by the close button or by the escape button as manual dismiss tips are intended to be used for heavier, slightly more persistent information than their light-dismiss counterpart.

  • I love the spirit of ensuring use is clear to narrator users, however, I have also received guidance to be very mindful about being over-verbose as a narrator user would have to hear this every time there is a new tip. I believe we should focus on saying concisely only what's necessary to inform a narrator user that 1) a new tip as appeared, 2) what needs to happen to navigate to the tip and 3) when the tip overall has received focus for the first time. Past that, I don't currently recommend that we add any extra narrator guidance. The default interactions for leaving a teaching tip (Escape, navigation to close) should already be apparent for a narrator user. A light-dismiss tip closing on F6 navigation away would be the one exception to precedent, but the intended nature of TeachingTips is that they be non-critical, transient, and non-invasive, so I believe it would be expected for the tip to do its best to minimize impact on the user experience (especially for light-dismiss tips) and would not expect a light-dismiss tip dismissing on any "navigating away" event to be a surprise (rather, I would expect it to be welcome). Fortunately, this is an area we can always iterate on further if there are any complaints here. 🙂

To restate what I think need to happen:

  1. Initial appearance of teaching tip: When a teaching tip appears, narrator announces that there is a new tip and how to access it (F6)
  2. Navigating to a teaching tip: F6 can be used to navigate to any kind of teaching tip (no need to control where inside the tip focus lands - this is handled automatically) + Narrator announces when focus lands inside a teaching tip
  3. Closing a teaching tip: Light dismiss tips can be closed by escape or by pressing F6 a second time. Manual dismiss tips can be closed by pressing escape or a close button.

Does that make sense? I hope my actually making it easier to implement this change by reducing the number of changes we're considering. 🙂 I know teaching tip accessibility is complex, so please let me know if you have any questions here - I am happy to try and make sure we're all super clear. I'll be out of the office until the New Year but would be happy to set up time to talk directly once I'm back if that helps? 🙂

@bkudiess
Copy link
Contributor

@Felix-Dev This issue seems to be resolved in main. Please feel free to reopen is the issue persists 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-TeachingTip team-Controls Issue for the Controls team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Focus is completely lost with Teaching tip
5 participants