-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Navigator
: add focus restoration
#38149
Conversation
elementToFocus.focus(); | ||
}, [ isInitialLocation, isMatch ] ); | ||
|
||
const mergedWrapperRef = useMergeRefs( [ forwardedRef, wrapperRef ] ); |
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.
Merging the two refs, so that they are correctly assigned to the wrapper element regardless of the value of prefersReducedMotion
const firstTabbable = ( focus.tabbable.find( | ||
wrapperRef.current | ||
) as HTMLElement[] )[ 0 ]; | ||
|
||
elementToFocus = firstTabbable ?? wrapperRef.current; |
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.
This code effectively replicates the same logic as in the useFocusOnMount
's logic
Size Change: +218 B (0%) Total Size: 1.14 MB
ℹ️ View Unchanged
|
e0a2250
to
67d68df
Compare
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.
Tests out very well! 👍 There was a styling issue (#38414), but that was unrelated to this PR.
To be honest, it isn't immediately clear to me why there is a complicated system to manipulate isBack
and isInitial
flags in the stack. As a person who hasn't worked on this 😄, I would think that we could rely on simple stack mechanics instead of tinkering with these flags in the history array, if the current location were set explicitly in the push/pop functions instead of just peeking the stack.
const [ locationHistory, setLocationHistory ] = useState();
const [ location, setLocation ] = useState();
const push = path => {
setLocation({path, isBack: false}); // any flags are set here, instead of mucking with them in the history stack
setLocationHistory([...locationHistory, {path}]);
};
const pop = () => {
const newLocationHistory = [...locationHistory];
setLocation({...newLocationHistory.pop(), isBack: true});
setLocationHistory(newLocationHistory);
};
^ This kind of way better matches my mental model of how a history stack works, but maybe there are other complications to consider. Non-blocking, just a thought!
Thank you @mirka for the review! I'm glad you took some time to look into the code and post some in-depth feedback with fresh eyes! Looking at your code, these are the major differences that stand out:
To summarise, I see points 1 and 2 more about personal preference (explicit vs derived location state, array "surgery" with mutable/immutable methods). Regarding the complexity of the Here's an example of what code be easily removeddiff --git a/packages/components/src/navigator/navigator-provider/component.tsx b/packages/components/src/navigator/navigator-provider/component.tsx
index c20e7aaa5c..69685e4de0 100644
--- a/packages/components/src/navigator/navigator-provider/component.tsx
+++ b/packages/components/src/navigator/navigator-provider/component.tsx
@@ -42,7 +42,6 @@ function NavigatorProvider(
>( [
{
path: initialPath,
- isBack: false,
isInitial: true,
},
] );
@@ -51,26 +50,18 @@ function NavigatorProvider(
( path, options = {} ) => {
const { focusTargetSelector, ...restOptions } = options;
- // Notes:
- // - the `isBack` flag is set to `false` when navigating forwards on both
- // the previous and the new location.
- // - the `isInitial` flag is set to `false` for the new location, to make
- // sure it doesn't get overridden by mistake.
- // - the `focusTargetSelector` prop is set on the current (soon previous)
- // location, as it is used to restore focus in NavigatorScreen. The
- // remaining options are instead set on the new location being pushed.
+ // `focusTargetSelector` is set on the current (soon to be previous)
+ // location, as it is used to restore focus in NavigatorScreen. The
+ // remaining options are instead set on the new location being pushed.
setLocationHistory( [
...locationHistory.slice( 0, -1 ),
{
...locationHistory[ locationHistory.length - 1 ],
- isBack: false,
focusTargetSelector,
},
{
...restOptions,
path,
- isBack: false,
- isInitial: false,
},
] );
}, What do you think about the proposed changes? Do you still think we should change the code further? |
Thanks for the thoughtful reply! I trust your gut so I defer to your decision either way 🙂 It's just that in my experience, undo functionality like this was usually implemented with standard stack operation equivalents, without having to alter a previous item in the history stack. I don't really mind whether the "stack operation" is achieved by Could the const push = (path, focusTargetSelector) => {
setLocationHistory([...locationHistory, {path: location.path, focusTargetSelector}]); // push *current* path to history
setLocation({path, isBack: false}); // go to *new* path
}; I'm also starting to wonder if API-wise it would be more friendly to name it something like |
This is a fair point. I though about a way to simplify this, and I came up with a potential solution (committed in 4bfccc7):
What do you think?
I personally like |
Much simplified, and I like the use of |
I don't have a strong preference. I guess the argument of push/pop is to make it closer to the browser history API? |
…focusTargetSelector`
4bfccc7
to
b3e5ede
Compare
Awesome! I'll give this PR one final check, rebase on top of
That is also a fair point, although the History API's public functions are Personally, I'm not sure it's a good idea to name |
Description
Following the conversation in #37063 and the exploratory work done in #37223, this PR is the first step towards adding support for advanced focus restoration in
Navigator
. The plan is to have the work split across 3 PRs:Navigator
: add basic location history #37416)NavigationLink
andNavigationBackLink
components (future PR)This PR adds focus restoration to the
Navigator
:focusTargetSelector
to thepush
function's optionsNavigatorProvider
to store thefocusTargetSelector
with the correct locationNavigatorScreen
How has this been tested?
Types of changes
New feature (technically not breaking, since the component is experimental)
Checklist:
*.native.js
files for terms that need renaming or removal).