NoriginMedia / react-spatial-navigation

DEPRECATED. HOC-based Spatial Navigation. NEW Hooks version is available here: https://github.com/NoriginMedia/norigin-spatial-navigation
MIT License
226 stars 64 forks source link

Allow focus / onClick of native browser elements #32

Closed stuartflanagan closed 5 years ago

stuartflanagan commented 5 years ago

Is your feature request related to a problem? Please describe. Have currently been working with focusing elements and using voice over on Tizen TV. Voice over works well and as expected when focusing of aria labelled elements.

Describe the solution you'd like Allow actual focus of an element composed withFocusable to get native focus state from browser engine.

stuartflanagan commented 5 years ago

Do you require any further information to discuss this? Just wondering if it is feasible or not? Integrating with voice over on tizen would be great for the project I am currently working on.

Kind regards,

Stuart

asgvard commented 5 years ago

Hi,

This should be already possible if you just bind that event from the browser element that reports the native focus, and call stealFocus method inside wrapped component. This should "steal" the focus to this component and mark it as "focused" in the spatial navigation system. I don't think this should be implemented in this package itself, since it's agnostic of which components you use with the withFocusable wrapper and it's also not aware of the way you trigger this from your specific browser elements.

stuartflanagan commented 5 years ago

Thank you for the explanation @asgvard.

I am still not sure how to get focus to a native browser element. It is probably not required it just seems to be an easy win for accessibility as Tizen will read out a focused elements text or aria label.

It is just getting that element to be natively focused in parallel to your spatial nav system.

Which is working very well thank you!

Are there still plans to move from the current HOC approach to use Hooks?

Kind regards, Stuart

asgvard commented 5 years ago

Hi,

Yes, definitely, this will be quite major refactoring though, but we want to migrate it to Hooks eventually.

Regarding getting native focus to the element, if the goal is to "sync" two focused systems, this can be achieved either by using "stealFocus" as I described before, or in another way - by using "onBecameFocused" callback from this lib and calling some ".focus()" method on the native element. But this library should be agnostic of what are the native elements used underneath, so we cannot rely on specific methods or events from native elements.

Closing this for now. If you would have any questions, please feel free to reopen, or submit a PR if you manage to solve this problem and feel it can be a part of this lib :)

Thanks, Dmitriy

chwagssd commented 4 years ago

Adding some color for future viewers - native "focus" at least in DOM has unexpected side effects when implementing. As I look at this library for an alternative to react-js-spatial-navigation, aria compatibility is a problem here because screen readers rely on tabindex/focus of page elements to read the content to the user. BUT as soon as you really call a domElement.focus(), the browser will try to scroll it into view, which breaks any animation you want to do on focus. Basically, something needs to be visible in the viewport once it is focused, and if it is not, browsers will ultimately adjust the scrollTop/scrollLeft of parent element that contains it to bring it into view.

So native domElement.focus() is more than just about synchronizing focus, it has side effects that must be considered. Some other libraries have written up about this complexity (https://allyjs.io/tutorials/focusing-in-animated-ui.html) but in the end using faux focus as done here in react-spatial-navigation keeps those concerns out of the picture.

benkingcode commented 4 years ago

@chwagssd I'm currently overcoming this with this, seems to work nicely;

const onBecameFocused = (layout) => {
  layout.node.focus({ preventScroll: true });
  layout.node.scrollIntoView({ behavior: 'smooth', block: 'end' });
};