Skip to content

Conversation

@youknowriad
Copy link
Contributor

closes #895

This PR adds a generic disposableFocus higher order component.
A component wrapped in the "disposableFocus" hoc will have the following behaviour:

  • Saves the active element when the disposable element is mounted
  • Restores the active element when the disposable element is unmounted (unless the focus has moved outside the disposable component)

All disposable elements should be wrapped within this HoC: Thinking of inserter, sidebar...

@youknowriad youknowriad added the [Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). label May 25, 2017
@youknowriad youknowriad self-assigned this May 25, 2017
@jasmussen
Copy link
Contributor

Thanks for working on this. I'm not entirely convinced how to test this exactly, so would appreciate @afercia's eyes as well. The inserter seems to be working well (outside of that focus rectangle which is my fault, and for which I'm opening a separate ticket for soon).

Codewise, I like all the red I see this clearing up!

@afercia
Copy link
Contributor

afercia commented May 25, 2017

Looks very promising to me, as it's a simple way to get an UI control to return focus to later. Tested on both the Inserter and Sidebar, and focus is correctly moved back to the related buttons. About the Sidebar for example, it solves the focus loss pointed out in #895

I'd like to see this as the first tool of a more complete "focus management" set of tools.

Just one thing, I'd consider to don't use a <div> element for the wrapper, since it could happen to use it in places where a <div> is not allowed and would cause invalid HTML. After all this wrapper is not meant for layout purposes and could be, for example, a span.

Not sure about the naming 🙂 maybe disposableFocus is not so clear, but I wouldn't know what other naming to use. Minor point anyways.

@youknowriad
Copy link
Contributor Author

youknowriad commented May 25, 2017

Just one thing, I'd consider to don't use a <div> element for the wrapper

Unfortunately, it's not possible to avoid a wrapper here. The wrapper tagName could be made customizable though (if we need to). We need a wrapper to get a reference to the dom node.

@youknowriad
Copy link
Contributor Author

Actually, I was able to drop the wrapper in 74f0dc8

@afercia
Copy link
Contributor

afercia commented May 25, 2017

Sorry, just noticed a drawback: when clicking using the mouse, document.activeElement returns the body element in Firefox and Safari. Returns the button in Chrome. Haven't tested in IE.

The previous method used for the inserter worked also when clicking with the mouse, because it was using the click event current target.

It would be great to find a way to make this work both with mouse and keyboard!

@youknowriad
Copy link
Contributor Author

youknowriad commented May 25, 2017

@afercia Good catch! I found that this happening because when we click on the "Insert" Button for example, the focus is not being moved to the button in Firefox and Safari while it's done in Chrome. I can't find yet why the focus is not moved to buttons when clicking on them.

@youknowriad
Copy link
Contributor Author

I found this link showing these differences. thinking how can we make this work the Chrome way everywhere https://gist.github.com/cvrebert/68659d0333a578d75372

}

componentWillUnmount() {
const wrapper = findDOMNode( this );
Copy link
Member

@aduth aduth May 25, 2017

Choose a reason for hiding this comment

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

findDOMNode is documented as an escape hatch, though in this case I think a reasonable one. Concerns me to see some conversation in which React maintainers appear inclined to put it on a path toward deprecation.

I noted the earlier point about wrapper components. I think we should keep this as-is, but leave open as options for future consideration:

  • Wrap with span to reduce layout implications (inline, not block)
  • Expect the mounted component to define a node getter (similar to react-click-outside's handleClickOutside)
  • Pass callback as prop to the wrapped component, expected to be called on mount with reference to the DOM node

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was thinking of providing a config object as a parameter to the HoC as an alternative.

disposableFocus({ wrapper: 'span' })(Component)

@youknowriad
Copy link
Contributor Author

@afercia Do you agree that buttons should receive the focus when we click on them all the time (like chrome does)? In that case, we can add a onClick handle to focus the buttons in a generic way in the Button component to mimic the Chrome's behaviour?

Thoughts on this @aduth?

@@ -0,0 +1,32 @@
/**
Copy link
Member

Choose a reason for hiding this comment

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

Re: Folder placement. This is technically not a component, but rather a function which returns a modified component. Dunno if or how we'd want to distinguish this.

}

toggle( event ) {
// When opening the menu, track reference to the current active element
Copy link
Member

Choose a reason for hiding this comment

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

I think some of the inline documentation could be adapted for use in the new higher-order variant.

@afercia
Copy link
Contributor

afercia commented May 25, 2017

Do you agree that buttons should receive the focus when we click on them all the time (like chrome does)?

Just tested Firefox on Windows and document.activeElement correctly returns the buttons also when clicking with the mouse. 😬 That confirms what reported in the link you posted, data actually comes from MDN, see: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Clicking_and_focus

Ideally, I think it would make sense to try to pair this behaviour across platforms/browsers.

@afercia
Copy link
Contributor

afercia commented May 25, 2017

Tested also in:
Edge: works
IE 11: can't test, still getting Object doesn't support property or method 'from', see #664

@youknowriad
Copy link
Contributor Author

I'm thinking we should merge this. And try (or not) to fix the browser issue separately. The fact that this issue breaks the disposableFocus is just a consequence and the HoC is not doing anything wrong.

@aduth
Copy link
Member

aduth commented May 26, 2017

@youknowriad
Copy link
Contributor Author

@aduth Oh sorry, I forgot about the documentation comment.

About the findDomNode I'm thinking we should leave it for now, since it's not deprecated or anything yet but keeping the possibility to add the wrapper back with a config if this changes. What do you think?

@aduth
Copy link
Member

aduth commented May 26, 2017

Misbehaving hashbang behavior I think 😄 Was referring to the folder placement remark, not findDOMNode.

@youknowriad
Copy link
Contributor Author

@aduth 😅 I need a good long weekend :P

For the placement, I don't know, I'm open to suggestions. I put it in "components" to make it reusable across other WordPress modules (outside Editor/Blocks), maybe we can create a subfolder components/hoc

@aduth
Copy link
Member

aduth commented May 26, 2017

Yeah, I don't really have a great answer. It's not a true component, but I don't necessarily want another top-level directory for these functions. I think they're related to components and in that way could belong in the components directory, but I find the hoc acronym to be a fair bit cryptic. It would be nice to have something short for plugin consumers using the global wp.components.hoc. I'm wondering if components/higher-order/ (wp.components.higherOrder) would be an "okay-ish" compromise.

@youknowriad
Copy link
Contributor Author

youknowriad commented May 26, 2017

Let's move it to the higher-order subfolder for now and we'll see how it feels with other HoCs :)

Also, thinking we should add aindex.js to the components and the higher-order folder to allow import { Button, IconButton } from 'components' and import { disposableFocus } from 'components/higher-order'. But let's leave this to a separate PR

@aduth
Copy link
Member

aduth commented May 26, 2017

Also, thinking we should add aindex.js to the components and the higher-order folder to allow import { Button, IconButton } from 'components' and import { disposableFocus } from 'components/higher-order'`. But let's leave this to a separate PR

Yep, this is needed too for exposing components on the global. I was wondering if there might be a way to automate it, so we can avoid this kind of scenario.

@aduth
Copy link
Member

aduth commented May 26, 2017

Not sure about the naming 🙂 maybe disposableFocus is not so clear, but I wouldn't know what other naming to use. Minor point anyways.

Maybe something about focus being remembered or returned? focusMemory, withFocusReturn ?

@youknowriad youknowriad merged commit 4ce764f into master May 29, 2017
@youknowriad youknowriad deleted the add/disposable-focus-component branch May 29, 2017 08:39
@@ -0,0 +1,41 @@
/**
Copy link
Member

Choose a reason for hiding this comment

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

What about utilities instead of higher-order? Alternatively, we can just keep them in the root. cc @aduth @youknowriad

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't have a preference here, happy to update to what we'll settle on.

Copy link
Member

Choose a reason for hiding this comment

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

I find "utilities" to be a meaningless word.

@nylen
Copy link
Member

nylen commented May 30, 2017

Why is this component named withFocusReturn instead of WithFocusReturn? I would think that everything under components should be named in upper-case.

@youknowriad
Copy link
Contributor Author

@nylen to make the distinction that it's not a component, it's a component factory. It can't be used like this <WithReturnFocus />

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

Labels

[Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Focus management: a practical example of focus loss in the Sidebar

7 participants