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

Edit Post: Make sidebar header focusable for button focus normalization #21031

Merged
merged 2 commits into from
Mar 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

exports[`Using Plugins API Document Setting Custom Panel Should render a custom panel inside Document Setting sidebar 1`] = `"My Custom Panel"`;

exports[`Using Plugins API Sidebar Medium screen Should open plugins sidebar using More Menu item and render content 1`] = `"<div class=\\"components-panel__header edit-post-sidebar-header__small\\"><span class=\\"edit-post-sidebar-header__title\\">(no title)</span><button type=\\"button\\" class=\\"components-button has-icon\\" aria-label=\\"Close plugin\\"><svg width=\\"24\\" height=\\"24\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 24 24\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><path d=\\"M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z\\"></path></svg></button></div><div class=\\"components-panel__header edit-post-sidebar-header\\"><strong>Sidebar title plugin</strong><button type=\\"button\\" aria-pressed=\\"true\\" aria-expanded=\\"true\\" class=\\"components-button is-pressed has-icon\\" aria-label=\\"Unpin from toolbar\\"><svg width=\\"24\\" height=\\"24\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2 -2 24 24\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><path d=\\"M10 1l3 6 6 .75-4.12 4.62L16 19l-6-3-6 3 1.13-6.63L1 7.75 7 7z\\"></path></svg></button><button type=\\"button\\" class=\\"components-button has-icon\\" aria-label=\\"Close plugin\\"><svg width=\\"24\\" height=\\"24\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 24 24\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><path d=\\"M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z\\"></path></svg></button></div><div class=\\"components-panel\\"><div class=\\"components-panel__body is-opened\\"><div class=\\"components-panel__row\\"><label for=\\"title-plain-text\\">Title:</label><textarea class=\\"block-editor-plain-text\\" id=\\"title-plain-text\\" placeholder=\\"(no title)\\" rows=\\"1\\" style=\\"overflow: hidden; overflow-wrap: break-word; resize: none; height: 18px;\\"></textarea></div><div class=\\"components-panel__row\\"><button type=\\"button\\" class=\\"components-button is-primary\\">Reset</button></div></div></div>"`;
exports[`Using Plugins API Sidebar Medium screen Should open plugins sidebar using More Menu item and render content 1`] = `"<div class=\\"components-panel__header edit-post-sidebar-header__small\\"><span class=\\"edit-post-sidebar-header__title\\">(no title)</span><button type=\\"button\\" class=\\"components-button has-icon\\" aria-label=\\"Close plugin\\"><svg width=\\"24\\" height=\\"24\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 24 24\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><path d=\\"M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z\\"></path></svg></button></div><div class=\\"components-panel__header edit-post-sidebar-header\\" tabindex=\\"-1\\"><strong>Sidebar title plugin</strong><button type=\\"button\\" aria-pressed=\\"true\\" aria-expanded=\\"true\\" class=\\"components-button is-pressed has-icon\\" aria-label=\\"Unpin from toolbar\\"><svg width=\\"24\\" height=\\"24\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2 -2 24 24\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><path d=\\"M10 1l3 6 6 .75-4.12 4.62L16 19l-6-3-6 3 1.13-6.63L1 7.75 7 7z\\"></path></svg></button><button type=\\"button\\" class=\\"components-button has-icon\\" aria-label=\\"Close plugin\\"><svg width=\\"24\\" height=\\"24\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 24 24\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><path d=\\"M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z\\"></path></svg></button></div><div class=\\"components-panel\\"><div class=\\"components-panel__body is-opened\\"><div class=\\"components-panel__row\\"><label for=\\"title-plain-text\\">Title:</label><textarea class=\\"block-editor-plain-text\\" id=\\"title-plain-text\\" placeholder=\\"(no title)\\" rows=\\"1\\" style=\\"overflow: hidden; overflow-wrap: break-word; resize: none; height: 18px;\\"></textarea></div><div class=\\"components-panel__row\\"><button type=\\"button\\" class=\\"components-button is-primary\\">Reset</button></div></div></div>"`;

exports[`Using Plugins API Sidebar Should open plugins sidebar using More Menu item and render content 1`] = `"<div class=\\"components-panel__header edit-post-sidebar-header__small\\"><span class=\\"edit-post-sidebar-header__title\\">(no title)</span><button type=\\"button\\" class=\\"components-button has-icon\\" aria-label=\\"Close plugin\\"><svg width=\\"24\\" height=\\"24\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 24 24\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><path d=\\"M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z\\"></path></svg></button></div><div class=\\"components-panel__header edit-post-sidebar-header\\"><strong>Sidebar title plugin</strong><button type=\\"button\\" aria-pressed=\\"true\\" aria-expanded=\\"true\\" class=\\"components-button is-pressed has-icon\\" aria-label=\\"Unpin from toolbar\\"><svg width=\\"24\\" height=\\"24\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2 -2 24 24\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><path d=\\"M10 1l3 6 6 .75-4.12 4.62L16 19l-6-3-6 3 1.13-6.63L1 7.75 7 7z\\"></path></svg></button><button type=\\"button\\" class=\\"components-button has-icon\\" aria-label=\\"Close plugin\\"><svg width=\\"24\\" height=\\"24\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 24 24\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><path d=\\"M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z\\"></path></svg></button></div><div class=\\"components-panel\\"><div class=\\"components-panel__body is-opened\\"><div class=\\"components-panel__row\\"><label for=\\"title-plain-text\\">Title:</label><textarea class=\\"block-editor-plain-text\\" id=\\"title-plain-text\\" placeholder=\\"(no title)\\" rows=\\"1\\" style=\\"overflow: hidden; overflow-wrap: break-word; resize: none; height: 18px;\\"></textarea></div><div class=\\"components-panel__row\\"><button type=\\"button\\" class=\\"components-button is-primary\\">Reset</button></div></div></div>"`;
exports[`Using Plugins API Sidebar Should open plugins sidebar using More Menu item and render content 1`] = `"<div class=\\"components-panel__header edit-post-sidebar-header__small\\"><span class=\\"edit-post-sidebar-header__title\\">(no title)</span><button type=\\"button\\" class=\\"components-button has-icon\\" aria-label=\\"Close plugin\\"><svg width=\\"24\\" height=\\"24\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 24 24\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><path d=\\"M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z\\"></path></svg></button></div><div class=\\"components-panel__header edit-post-sidebar-header\\" tabindex=\\"-1\\"><strong>Sidebar title plugin</strong><button type=\\"button\\" aria-pressed=\\"true\\" aria-expanded=\\"true\\" class=\\"components-button is-pressed has-icon\\" aria-label=\\"Unpin from toolbar\\"><svg width=\\"24\\" height=\\"24\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"-2 -2 24 24\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><path d=\\"M10 1l3 6 6 .75-4.12 4.62L16 19l-6-3-6 3 1.13-6.63L1 7.75 7 7z\\"></path></svg></button><button type=\\"button\\" class=\\"components-button has-icon\\" aria-label=\\"Close plugin\\"><svg width=\\"24\\" height=\\"24\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 24 24\\" role=\\"img\\" aria-hidden=\\"true\\" focusable=\\"false\\"><path d=\\"M13 11.8l6.1-6.3-1-1-6.1 6.2-6.1-6.2-1 1 6.1 6.3-6.5 6.7 1 1 6.5-6.6 6.5 6.6 1-1z\\"></path></svg></button></div><div class=\\"components-panel\\"><div class=\\"components-panel__body is-opened\\"><div class=\\"components-panel__row\\"><label for=\\"title-plain-text\\">Title:</label><textarea class=\\"block-editor-plain-text\\" id=\\"title-plain-text\\" placeholder=\\"(no title)\\" rows=\\"1\\" style=\\"overflow: hidden; overflow-wrap: break-word; resize: none; height: 18px;\\"></textarea></div><div class=\\"components-panel__row\\"><button type=\\"button\\" class=\\"components-button is-primary\\">Reset</button></div></div></div>"`;
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ const SidebarHeader = ( { children, className, closeLabel } ) => {
);
const { closeGeneralSidebar } = useDispatch( 'core/edit-post' );

// The `tabIndex` serves the purpose of normalizing browser behavior of
// button clicks and focus. Notably, without making the header focusable, a
// Button click would not trigger a focus event in macOS Firefox. Thus, when
// the sidebar is unmounted, the corresponding "focus return" behavior to
// shift focus back to the heading toolbar would not be run.
//
// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus
Copy link
Contributor

Choose a reason for hiding this comment

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

I know we tried this before but forgot the conclusion, why don't we try to normalize it on the Button click handler (or related events).

Copy link
Contributor

Choose a reason for hiding this comment

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

Asking cause it's not the first time we have the button click focus issues and certainly not the last time.

Copy link
Member Author

@aduth aduth Mar 20, 2020

Choose a reason for hiding this comment

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

I've been thinking about this as well.

I think one of the main reluctances I'd have is in breaking expectations about how elements are expected to work, but I'm not sure how well this holds up:

  • The abstraction of a button shouldn't necessarily be known to be tied to expectations around a particular HTML element.
  • The "expected" behavior is clearly not very expected, or at least it's highly inconsistent between browsers and consequently prone to error.

On the technical front, I've been wondering as well just how this would work:

  • If we dispatch a new window.Event, how well does would it interoperate with React's event system, and in particular with portaled rendering which can't rely on DOM event bubbling?

I've also been thinking about a few other options:

  • Is <input type="button"> susceptible to this as well? And if not, could we implement <Button> this way? I expect by virtue of the differences between the two (largely ability to use non-text HTML content in <button>), it may be infeasible.
  • What about something like <span role="button"> ? As I understand it, there are a lot of accessibility challenges in using role="button" effectively, but if it were technically possible, we could absorb that complexity into the implementation of the core component.
  • Wrap every <button> with <span tabindex="-1">, essentially applying the behavior implemented here universally. There are some known issues here (huge proliferation of wrapping elements) and some unknown issues (potential side-effects of so many additional focusable elements).

Copy link
Contributor

Choose a reason for hiding this comment

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

I think @diegohaz might have good thoughts on this problem as something he probably already faced with reakit

Copy link
Member

Choose a reason for hiding this comment

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

On Reakit we opted to ensure consistency across browsers on a Tabbable component (which is used by Button): https://github.com/reakit/reakit/blob/c9f55d7d87f12b58ee409e8a392a9c36c2aadf52/packages/reakit/src/Tabbable/Tabbable.ts#L141-L148

It's important to note that, on Firefox/macOS, calling preventDefault() there also prevents it to get the :active state when clicked (ariakit/ariakit#432). Something like data-active could be used instead. That's the only bad side-effect people noticed so far.

Copy link
Member Author

Choose a reason for hiding this comment

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

@diegohaz Thanks for sharing! It hadn't occurred to me as potentially being as simple as calling .focus() on the button node. About the implementation and your point about preventDefault: Can you explain why preventDefault is necessary at all?

Copy link
Member

Choose a reason for hiding this comment

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

That's because Safari/Firefox on macOS will blur the button if it has focus on mouse down. preventDefault prevents that from happening. But there may be another way to work around that.


return (
<>
<div className="components-panel__header edit-post-sidebar-header__small">
Expand All @@ -40,6 +48,7 @@ const SidebarHeader = ( { children, className, closeLabel } ) => {
'components-panel__header edit-post-sidebar-header',
className
) }
tabIndex={ -1 }
>
{ children }
<Button
Expand Down