-
Notifications
You must be signed in to change notification settings - Fork 487
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
refactor: rewrite the useFocusManagement hook with a new approach #2369
Conversation
9b1f3d7
to
293e74c
Compare
Pull Request Test Coverage Report for Build 3431
💛 - Coveralls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this context is specifically concerned about the focusManger
, what do you think about just making is FocusManagerContext
?
Also, I'm a little worried we are making our focus management overly complex. At the moment I'm not totally sure how to remedy that, but I think its something we should keep an eye on. I wouldn't want consumers of Clay have to worry about it as much as we do or implement anything overly complex on their side just to get it to work.
Hm, I just worry that only
Hmm yeah, I'm worried about that too. I see that it seems easier to expose I'm wondering how I could change the rule of not having to use But dealing with focus is rather complex especially for this case that is a manager, its logic becomes complex because it has many use cases. I will try to work on it to simplify things a bit. Since we are doing something "outside of React" and not in the core, we missed a few things, especially for the components that are Portals, which makes things more complex here. |
Could you expand on this a bit? Not totally sure what you mean. My concern is that by adding additional components or contexts that are outside of FocusManager, our footprint for maintain gets larger and slowly becomes harder to get rid of the focus manager component when React implements it into its API. If we can contain it to just the focus manager package, it will be easier to maintain and upgrade in the long run. |
Sorry, that was not very clear. But I'm just saying that creating a
You mean creating a package just to deal with focus management?
Yes you are right about that, if we do that we will compromise maintenance and will have to deal with the depreciation of this when React implements its API. I think it is better to on hold this decision to expose the context and I will try to eliminate the use of |
Oh sorry I didnt mean package, I mean the FocusManager component. Maybe instead of restricting it to DropDown, we can just create a generic context for focus that would allow for nested structures like dropdown? Im sure this isnt the only place we will run into focus issues. |
Hmm, I'm not sure if it would be necessary to create a Context for this, most of the cases where we want to control focus is when a component uses My view on having a Generic Context is that we would only have one instance of In the end FocusManager needs to create a scope or control area for it to manage focus and look for focusable edge elements to let the browser handle the rest. |
My concern is that if we limit this specifically for DropDown, we will eventually run into again whenever we use a context. For example, any modal or popover. The more generic we can make it and non-specific to dropdown will make it easier to refactor later when we want to utilize React's API rather than our own focus manager. |
Hmm, I think I'm starting to understand using a generic context for |
@matuzalemsteles yeah thats a valid concern, we would have to craft it to be an opt-in type of functionality. Thanks for looking into it! |
3d736a1
to
f1daf3a
Compare
hey @bryceosterhaus I just refactored the So now it goes through Fiber looking for focusable elements within the This approach also allows the low-level Another thing to note is that we are consuming Fiber's direct information this means that this API is a bit risky but I find React difficult to change the approach, if that changes it is likely to be a paradigm shift so I'm sure go with it. The FocusManager API PR in React can be set aside but has a similar new specification being commanded by the React team that can be delivered with the new event engine... facebook/react#16009 |
@matuzalemsteles I was just looking at our storybook and it looks like this doesn't work for dropdown? Also, I really just don't know if this is the best way forward for us, not necessarily because I disagree but more just ignorance of not knowing what the best implementation is for our needs. I really don't have much knowledge of working with React internals fibers but it does worry me at first glance as being overly complex. I would be curious to get @wincent's thoughts and see what he thinks about accessing internals like this. |
Reaching into internals is definitely a liability that may come back to bite us in the future. It would be a shame if we wanted to move to a new version of React but found ourselves blocked because Clay was depending on an internal implementation detail in a previous version. I don't have enough context to know whether there is another way to address #2357 — in this PR it isn't clear which bit is fixing that issue...
... and which bit is just refactoring. @matuzalemsteles: is there a more minimal approach that we could take while waiting for those upstream React changes to go in? I know we don't know when they'll go in, but we need to balance the risk being that they never go in against the risk of reinventing a pretty complex wheel. |
Oh sorry guys, I noticed on Friday that I pushed something that broke the netlify CI to run the builds, I will try to force a new build for this PR, locally the Storybook with these changes work fine.
Yeah I thought about it and it will be a risk we can take, I think if any change in this structure comes up it won't be a big problem because the information I'm consuming is the most crucial thing for React: go back and forth about the tree. And because they're based on Fiber, it's likely that big changes that hurt this aren't a problem for now until the new APIs for React's FocusManager arrive.
Focus control for DropDown was added in commit 20c388f... And actually I rewrite all the
That was the most ideal approach I found and less complex than before so I don't have to touch DOM directly, so it covers just about every use case, a Component in Just to highlight the use of FocusManager here is to solve the problem of focus on elements within |
f1daf3a
to
320255b
Compare
…se of createScope The `useFocusManagement` hook rewrite allows to remove the manual tagging API from items that should be added to the scope. Instead of consuming the DOM directly, now it traverse the Fiber for get focal elements, the Fiber is safer than the DOM since the use of `React.Portal` influences the tree structure and the fiber maintains the tree.
320255b
to
e80199a
Compare
e80199a
to
0cfd7cf
Compare
Just a note about this, we may really have to look better at this approach to consuming Fiber information, it seems that the I will try to look more tomorrow if I can understand this behavior of |
I've been looking a bit at the API that will be implemented in React, it looks like there will be a manual tagging with the So something like this: <Focusable>
<ClayButton tabIndex={-1}></ClayButton>
</Focusable> And we would have to expose This is a pseudocode that React is thinking: const FocusableDiv = ReactDOM.createAccessibleComponent((props, focusable) => {
return <div tabIndex={focusable ? 0 : -1} {...props} />;
});
//...
<FocusScope onMount={() => focusManager.focusFirst()}>
<FocusableDiv focusable={true} />
<FocusableDiv focusable={true} />
<div tabIndex={0}>You can't focus this</div>
</FocusScope> |
So I went back to the heart of the issue and was trying to understand it a bit better since I'm not fully convinced I know what proper focusing even looks like. But i was demoing one of the storybooks and didn't actually see the issue at hand. In this story I am able to focus throughout, even the user defined buttons at the bottom. Am I missing something with that example? |
Oh yeah, in this particular example you won't be able to see the problem because components are rendered within an iframe, so its order is being respected even if DropDown is rendered in the body, you can test this by adding focusable elements (ClayButton) with DropDown's sibling, so you'll see the problems. Other examples like MultiStepNav, Pagination... are more visible and they become more visible in the application. This is the current behavior without a focus manager: With the focus manager: The problem gets bigger when you start having many components like this on the same screen and putting it all together into one Modal... |
Ah I see, that is definitely an issue. Your changes definitely make the accessibility better and more intuitive and I think the usage of the focus manager is a bit cleaner. I am still unsure about the implementation of focus manager itself, but since its inside of "shared" I am more apt to not worry about it a ton. I do like that the usage is more in line with pseudocode that react is thinking as well. So overall, I think I would be fine with merging this and going this direction, particularly because it moves the code footprint into FocusScope and away from our components themselves. What do you think? Do you feel comfortable with this implementation, the code itself doesnt look too concerning, but you know the nature of the react internals a bit better than I do. Are there any particular edge cases we might run into issues with this? |
I'm fine with consuming the information from react internals, of course it's a risk... but I'm safe because the structure is the tree, it's very difficult for them to do some rewriting of this, so it's likely to be slow and if it happens might be a breaking change in a major version of React, so I'm fine until React releases their focus manager. I like this current implementation because we leave all the internal implementation and people don't need to know about it. |
Yeah I agree. As long as we can keep the messy internal part within shared and create a relatively simple API for consuming, it should be okay. I am good with pushing this forward. Any final thoughts on this @wincent? |
Nope. I just hope the upstream focus management gets merged/released soon so that we can reduce the exposure (to breakage) and the complexity that comes with reaching into framework internals. It's nasty, but sometimes you have to do it. |
Yeah, we can expose an API soon and we can see how it will work.
Yes, I'm following the Focus Manager implementation, hope we can see something stable soon about it. I also agree that comes the complexity of working with internals. Hope we can remove this soon. |
Merged, approved offline |
Fixes #2357
This still has a design problem, our
useFocusManagement
is designed to control focusable elements within the Portal and an indicator element to link the structure between the Portal and the DOM. The problem here is that some DropDown APIs let you render elements, if you render a focusable element (e.g. Button)useFocusManagement
will not compute the element in scope.I've been thinking of making
ClayDropDownContext
a public API with restricted access tofocusManager.createScope
so that I can add to focusable elements within the Portal. Maybe we can use this same approach for DropDown's low-level component, so people don't have to create their own implementation.