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

Throw/Manage focus when an overlay is open #710

Closed
Westbrook opened this issue May 26, 2020 · 11 comments · Fixed by #728
Closed

Throw/Manage focus when an overlay is open #710

Westbrook opened this issue May 26, 2020 · 11 comments · Fixed by #728

Comments

@Westbrook
Copy link
Contributor

When an overlay opens, the browser focus should be thrown into it. This means either the overlay itself or the first focusable item in the overlay should receive focus immediately by default. When this occurs the focus should flow through the overlay, and then be thrown back to the triggering element when focusable content has been fully stepped through. For instance, the sp-dropdown element should not need to throw focus into its options list when it opens, and once the list is exhausted (a single tab in this case for the floating tab index) the focus should return to the UI from which it came.

related #709

@Westbrook
Copy link
Contributor Author

@cuberoot and @adixon-adobe, et al I'm looking for usage of either the Overlay API, particularly Overlay.open() or the <overlay-trigger><* slot="click-content"></*></overlay-trigger> in the wild to help outline the next steps/API changes that should be made in reference to this and #709.

It is becoming clear that for accessibility's sake that the "click" type of overlay content should likely be split into two types of overlay content; inline and modal.

  • inline content should generally be content with only one tab stop (see the <sp-dropdown> element) that is an extension of a tab stop (in position and interaction) that falls within the native tab order already. This sort of overlay will certainly need to have its focus order managed (how else would you get back into the page from the <active-overlay> element placed at the end of the <body> tag?) by moving to the previous/next element in the native tab order in relation to the element that triggers it, but won't always have focus thrown into it by default. You will see focus throwing in the <sp-dropdown> UX, but if you visit the search UI in the documentation site (which aligns closely with the Combobox pattern) you'll see a similar inline experience that requires a secondary interaction to enter the overlay after it has been opened. It's possible that this means there should be both an inline and an inline-auto "type", or that our types should be more complex than the string they are currently, or that throwFocus should be an addition to the options bag.
  • modal content generally has more than one tab stop (though this line is pretty blurry as native DOM elements go full modal after a single tab stop in many cases (see the <select> element), and the accessibility team sees some cases where "inline" content would have more than one tab stop), when entered the Tab order would be trapped into the overlaid element, and it may or may not be directly related to another element (both position and trigger wise). This sort of overlay would receive thrown focus and would trap the tab order, likely without an option to alter those facts. When this sort of overlay was closed, the focus would return to the previously focused element instead of the next element in the tab order as is experienced above.

A more strict interpretation of the realities above would certainly add focus to the API surface to which we would need to expand. The three main question that could affect that are:

  1. Is inline actually inline-manual and inline-auto?
  2. Do we need to add throwFocus to the options bag?
  3. Can we reliably say that overlays with more than one tab stop should be modal?

Particularly, the possibility of multiple tab stops in an inline overlay provides some difficult questions around we know when you're on the last (or first) tab stop to flow ensuing Tab presses back into the origination page. With a single tab stop, an inline overlay can rely on retargeting any Tab inclusive keydown events to the overlay trigger after giving it focus. Expanding beyond that would require either outlining expectations on the implementing element to resolve/register those tab stops in someway or for the <active-overlay> to walk its tree, piercing shadow boundaries, to establish all of the tab stops within it; neither of which is awesome. In the case we needed to go that way, I'd lean towards the former as it would seem less bug-prone from the outside, but it does place a lot of responsibility of elements/developers leveraging the API.

Internal to the SWC library, we currently only leverage this API as part of the <sp-dropdown> element, the search interface (which may grow to be the basis of the <sp-dynamic-search> interface currently working its way through the Spectrum contributions system), and the <overlay-trigger> element. <sp-dropdown> and <sp-dynamic-search> both make sense as some form of the inline overlay. It's with <overlay-trigger> that we will need to do the most thinking.

<overlay-trigger> at current isn't used internal to the library, but all of its documentation/test that use slot="click-content" deliver what would likely become modal overlays. @charlessuh I know you have some thoughts on this and whether SWC should similarly implement a <modal-trigger> component as seen in related React libraries. Would we be served by assuming all content that is overlaid in this way was modal in nature? Should we add an attribute to manage this <overlay-trigger type="inline|modal">? Should we further facet the slot API and surface <slot name="inline-content"> and <slot name="modal-content"> interfaces? As this element is likely where this API is most often leveraged in the wild, hopefully, current use cases will help to define the path forward here. Our imaging application uses slot="click-content" predominately as part of a two or more tab stop color selection element that is planned for abstraction to SWC at some point. As such, whether it continues to use <overlay-trigger> or moves to an internal implementation of Overlay.open() as seen in <sp-dropdown> is open for conversation. Beyond that, we're primarily leveraging <overlay-trigger> for hover content, which should be unaffected by this conversation.

I look forward to hearing how these APIs are currently being leveraged and how that might shape your thoughts on how we could bring these two issues to a resolution!

@Westbrook Westbrook pinned this issue Jun 8, 2020
@adixon-adobe
Copy link
Collaborator

In terms of how we're using this right now, we're just using sp-dropdown (and only recently). That said I'll also say I'm itching to start using SWC overlays on our project and start moving away from our current implementations.

I'm also gonna answer your questions out of order :)

3. Can we reliably say that overlays with more than one tab stop should be modal?

I don't think so, but I think it'll be rare that there's more than one tab stop (and I think it'd be reasonable to push some of the complexity to developers).
e.g. Here's a screenshot of search auto-complete in Lightroom Desktop. When we get to implementing this on web we'll want this to be inline, and probably need to tab through the different options for accessibility: search-auto-complete

1. Is inline actually inline-manual and inline-auto?

I think so based on my answer above. My assumption is that we'd need to use inline-manual to implement that overlay.

2. Do we need to add throwFocus to the options bag?

I think we'd need that for search auto-complete as well since we'll want to keep focus in the search text input.

I'll also say that I like adding type="inline|modal" to overlay-trigger. I don't see why we'd need different slots though (and if you do, then might I suggest 2 top-level elements: modal-trigger and inline-overlay-trigger instead).

@Westbrook
Copy link
Contributor Author

Clarifications:
type="inline|modal" vs <slot name="inline|modal-content"> is an either/or case where I was attempting to be exhaustive of our options so that we could make the best choice. The one point that the slot approach outlines more clearly is that the existing <slot name="hover-content"> API should likely be unaffected by the type of the parent <overlay-trigger> element.

@adixon-adobe that Search UI seems like a great challenge case to compare to the path we choose here. Would you and the team happen to have the keyboard accessibility requirements that you'd be applying to that in a place to share? It's likely that you're correct and this is something that should be manage manually, but the way that it's very close to this sample in the documentation of accessible modals, while also seeming like non-mainline interactions, makes it a very interesting case to understand in the context of what we're making here.

Proposed Next Steps

TriggerInteractions type
Extend this to include inline and modal (currently click, hover, and custom) to outline the experience for single tab stop overlays (inline) should act as if they were inline with the trigger content from which they arise and for multiple tab stop overlays (modal) that should trap the tab order.

OverlayOpenDetail interface
Add an optional receivesFocus property that would be typed to auto (for now) outlining that the overlay in question should be immediately focused upon opening. Making it not a boolean could be overkill but having the option to extend the type at a later date to include common patterns (e.g. receivesFocus: 'ArrowDown', or receivesFocus: () => testIfSomethingIsTrue()) seems like a good door to keep open for now.

OverlayStack
This class will likely be the best place to manage inline and modal interactions as there is no reference to the stack in individual ActiveOverlays and all other touch points would have a high possibility of duplicate code paths. Here we leverage the pattern available in #709 for modal overlays and use keydown + event.code === 'Tab' to exit inline overlays.

<overlay-trigger>
Add type="inline|modal" to leverage the interaction types outlined above. Making the default here undefined should allow this change to be non-breaking to any applications currently implementing this functionality.

Alignment?

If that makes sense, please respond to or thumbs up this comment and I can start looking at getting that into a place for us to compare notes again next week sometime.


Postlog

All of this should be non-breaking while offering benefits to existing elements like <sp-dropdown> that wanted to leverage these abstracted APIs rather than managing these code paths themselves. Once these APIs were available in main we'd then be able to look into follow-up work where we more clearly codified patterns for elements like <sp-dialog/dialog-wrapper> to be leveraged in conjunction with these APIs. I know that a number of team members have been looking for clarity in this area, and that it would likely be of benefit to the LRWeb project (at least the page that I most often see) as the application always opens with a dialog overlaid across the application surface... 😉

@adixon-adobe
Copy link
Collaborator

Alas we don't have any keyboard accessibility requirements for that UI yet (and it doesn't look like there is any in LrD I can steal). My assumption with this UI is that you'd be able to tab/arrow between the controls. It looks like depending on what's selected the overall either closes, or presents new options (like which camera to filter on). That definitely adds an extra wrinkle. What I will do is add a task to implement a simpler proof of concept.

Overall though, this sounds great. 👍 👍 👍

@cuberoot / @charlessuh I'd love to get your thoughts here as well.

@Westbrook
Copy link
Contributor Author

Westbrook commented Jun 18, 2020

Making some nice progress with the steps outlined above:

To-dos:

Thoughts?

@Westbrook
Copy link
Contributor Author

Westbrook commented Jun 18, 2020

I've updated the links above to be more evergreen. And, you can now check out the "local" modal via: https://westbrook-overlay-focus--spectrum-web-components.netlify.app/storybook/iframe.html?id=overlay--deep-nesting

Also, the more I looked at this, the more I needed to make sure that sp-radio-group performed the way that I expected, so and bunch of edits when into that, too.

@Westbrook
Copy link
Contributor Author

This is coming together really nicely in #728 and might be ready to go.

Upon review of how this might work in a couple of other use cases I do have one possibly additional "mode" to consider. For "inline", we are actually doing something close to replacing the trigger with the overlay element; E0->T->E1 opens an overlay that then amounts to E0->Overlay->E1 and might be better called "replace" than "inline". This workflow support the "Dropdown" interface in sp-dropdown perfectly. In the case of something like the "search results" UI in the docs site, you actually convert the E0->T->E1 tab order to to E0->T->Overlay->E1 with the option to return to the trigger element via the shift+tab interface, something more akin to actually "inline".

I wonder if this should lead toward either or both of renaming inline to replace (or similar) and adding the "new inline" where you are actually inserting the overlay into the tab order? Naming is probably more important than functional coverage in the current PR, as adding is easier than removing/renaming, but let me know what you think.

@Westbrook Westbrook unpinned this issue Jul 14, 2020
@jnurthen
Copy link
Member

jnurthen commented Sep 3, 2020

@Westbrook This doesn't seem to be resolved.

@Westbrook
Copy link
Contributor Author

@jnurthen Do you have an example? It's always possible we've missing something, and we are very happy for the extra eyes. For instance the work outlined herein can be experienced by the following steps:

  1. Visit https://opensource.adobe.com/spectrum-web-components/components/dropdown/examples
  2. Type esc into the search field
  3. Press Tab, see focus enter the "results list"
  4. Press Tab again, see the "results list" close and the focus enter the "SideNav"

Or:

  1. On the same page.
  2. Click on "Scale Medium", see the dropdown open and focus enter it
  3. Press Tab, see focus thrown to "LTR"
  4. Click on "Scale Medium", see the dropdown open and focus enter it
  5. Press Shift + Tab, see focus move to the "Select Button" without closing the drop down
  6. Press Shift + Tab, see focus move to "Dark"

We're still tracking #764 separately as sadly it does seem to apply in more VO situations than just iOS. I'm actually reviewing next steps for our implementation of that right now, and hope to be able to get your eyes on it when it's close to ready! I've also just moved #866 into the repo from a personal list, which will further outline use cases and guidelines for the various API we now surface in this area.

@jnurthen
Copy link
Member

jnurthen commented Sep 3, 2020

I'm looking at https://opensource.adobe.com/spectrum-web-components/components/dialog-wrapper/examples
Activating any of the buttons causes focus issues

@Westbrook
Copy link
Contributor Author

Yes. Those are element demos right now and not experience demos, which is to say they don't use this API.

I'll add reviewing this page to the documentation task in #866. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants