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
T250253: update useNavigation to support stacked popups #210
Conversation
src/hooks/useNavigation.js
Outdated
let selectedElement | ||
allNavSelectedTrue.forEach(element => { | ||
if (element.getAttribute(currentSelector)) { | ||
selectedElement = element |
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.
I'm assuming there's always a nav-selected=true
element with the corresponding currentSelector
, but let's talk if that's not a safe assumption
Haven't looked into the details yet, but when test on search page, navigation breaks. |
What behavior are you seeing? Seems to be working fine on my end |
You can follow
|
Good idea. Updated PR to follow suit of this better approach, thanks for the feedback Huei 👍 |
@@ -14,7 +14,7 @@ export const ArticleToc = ({ items, currentAnchor, onSelectItem, close, closeAll | |||
|
|||
if (item && item.title) { | |||
onSelectItem(item) | |||
close() | |||
closeAll() |
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.
Little (unrelated) bug I found along the way, a bonus fix
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.
I don't advice doing "drive-by" fixes like that. If you do, make sure to provide steps to reproduce the issue that this is fixing.
The preferred way is to file a task describing the issue and link a one-line PR. Yes it takes longer, but it is so much more safer.
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.
Makes sense
to reproduce the issue that this is fixing.
Issue was that when if you openned ArticleToc
through the article menu, selecting a section there would route to correct section but the screen you saw was the article menu. This was because not all popups were closing, so this was introduced recently when we stacked popups from the article menu.
Will make sure to file a task next time.
src/hooks/useNavigation.js
Outdated
|
||
const getSelectedElement = () => { | ||
return document.querySelector('[nav-selected=true]') | ||
const grandparentContainer = containerRef.current.parentNode.parentNode | ||
return grandparentContainer.querySelector('[nav-selected=true]') |
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.
great, it works now!
two questions and comments
- why grandparent, not just parent?
- This fix. Although we will have the problem again if the grandparent or parent component use
useNavigation
, this is not our case as the container usually set to child components. I think we can ignore this now as this seems not a simple fix.
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.
In the case of getSelectedElement
, we can actully get a working behavior if we just return containerRef.current.querySelector('[nav-selected=true]’)
directly instead. The only issue with that is a harmless TypeError
if you click on (invisible) center softkey when input element is active with a ListView/RadioListView below:
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.
Although we will have the problem again if the grandparent or parent component use useNavigation, this is not our case as the container usually set to child components. I think we can ignore this now as this seems not a simple fix
I think the latest commit addresses this too then, if I'm understanding you correctly
src/hooks/useNavigation.js
Outdated
@@ -4,10 +4,18 @@ import { useSoftkey } from 'hooks' | |||
export const useNavigation = (origin, containerRef, axis, elementsSelector = '[data-selectable]') => { | |||
const [current, setCurrent] = useState({ type: null, index: 0, key: null }) | |||
|
|||
const getAllElements = () => document.querySelectorAll(elementsSelector) | |||
const getAllElements = () => { | |||
if (containerRef.current === undefined) { |
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.
I would like to understand when is containerRef.current
undefined. I feel like querying document
is never OK but I might be missing some context here.
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.
containerRef.current
is undefined when the app first loads the Search
home screen. If we comment the if-statement here in line 8, this is stack trace that the console throws:
It’s from the setNavigation
in the component's useEffect
. This error messes up the loading of Search
because softkeys don’t get set, the cursor doesn’t get set, and querying doesn’t work.
I am returning document.querySelectorAll(elementsSelector)
in this case because querying document
is what we were doing previously.
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.
useEffect
runs after the component has rendered and the refs are populated. However, in this case the ref is on the results list which doesn't exist until there are search results. What we are trying to do here with setNavigation(0)
is to focus the input box.
Have 2 refs would fix that too. Or we could be more explicit with something along the lines of inputRef.current.focus()
src/hooks/useNavigation.js
Outdated
return document.querySelectorAll(elementsSelector) | ||
} | ||
|
||
const grandparentContainer = containerRef.current.parentNode.parentNode |
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.
Why do you provide a containerRef that is actually 2 levels down from the node we actually need. Can you explain how this works?
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 is because we need the input
element to also be part of the elements being returned by getAllElements
. The input
is also data-selectable
and there are at least three cases where the input
is found two levels away from where ref={containerRef}
is being set: ArticleLanguage.js#L59, Language.js#L51, Search.js#L64
The idea then is to make the scope of the container big enough to contain all needed elements but small enough to also ignore other elements when popups are stacked. I tried a couple different approaches for this
- For example moving the
ref={containterRef}
placement on level up in RadioListView and ListView, and then querying thecontainerRef
parent node (instead of grandparent). This query does include the neededinput
and makes navigation work, but it messes up the screen scrolling code which also usescontainerRef
(useNavigation.js#L80). Scrolling code would have to be updated too. - Also tried setting
ref={containterRef}
actually two levels out, which means taking it outside of the RadioViewList/ListView component and into Search/Language/ArticleLanguage. But again this meant even further changes needed to get navigation to work.
So ultimately I’m proposing to grab the grandparent right from within getAllElements
because it seems to be the least intrusive change that gets us the working behavior, correctly supporting navigation with stacked popups. Does this make sense? Can we think of a better way?
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.
Thanks for the explanation. I remember now the issue with the input being conceptually part of the list for navigation but not for "scroll into view".
If there's a simple way to capture and use 2 refs, one for each purpose, I think that would be more clear but if not, a prominent comment explaining what's going on could do.
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.
That's a great idea, what about this: 95900ec
Am I missing a component? Seems to be working fine
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.
yeah, it looks good now 👍
found an issue: IMO both buttons should have the same behaviour/call the same method. |
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.
It looks good to me now 👍
the code looks better to have two ref to split the container and list.
Phabricator Link: https://phabricator.wikimedia.org/T250253
Problem Statement
When we changed the article menu so that popups stack, in order to get the desired back key behavior when working on backspace key feature, a bug was introduced because
useNavigation
hook was not prepared to handle a specific case such asArticleLanguage
stacked on top ofArticleMenu
. What was happening is that on everygetAllElements()
call, it was actually getting all elements from both the first and second popup. This was messing up navigation altogether.Solution
At first I was trying to let it grab all elements from both popups and then slicing the array to handle the corresponding elements, but this approach wasn't really working correctly (branch
T250253-article-menu-language-first-approach-idea
for the curious). I opted instead for taking advantage of theelementsSelector
argument to have the hook grab all the correct elements whenArticleLanguage
is theorigin
. This required an update togetSelectedElement()
so that it selects the correct element from the 'active' (latest) popup.Note
Still a mystery to me why the first piece of code below works but the second one doesn't