-
Notifications
You must be signed in to change notification settings - Fork 87
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
Issues with keyboard navigation on KDropdownMenu #818
Issues with keyboard navigation on KDropdownMenu #818
Conversation
lib/KDropdownMenu.vue
Outdated
|
||
//function to check if the sibling is divider | ||
const isDivider = element => { | ||
return element && element.classList.contains('is-divider'); |
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 a good way to determine if the element is a divider, but is a little specific. If anything changes and in the future we get rid of this specific class. This would break this condition.
A more general solution could be to ask if the element has a tabIndex >= 0, so this means it is focusable and with this, we can skip the dividers.
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.
Oh right, that seems more logical to skip any other element [like divider] in a more generalized way. Thanks @AlexVelezLl .
lib/KDropdownMenu.vue
Outdated
|
||
//Chekcing if next sibling is divider and skipping it | ||
let sibling = focusedElement.nextElementSibling; | ||
while (sibling && isDivider(sibling)) { |
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.
We could write two separate methods here, to breakdown a little bit this method. so we can just do something like
const sibling = this.getNextFocusableSibling(focusedElement);
const prevSibling = this.getPrevFocusableSibling(focusedElement);
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.
Hey @AlexVelezLl , I've defined prevSibling Already here
and the variable sibling Is for next focused 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.
Yes, my suggestion was more to avoid writing more logic to query the next/previouss element in the same handleOpenMenuNavigation
method. So lets define two new methods in this component getNextFocusableSibling
and getPrevFocusableSibling
where we should do the focusable check and the while sentence.
e.g.
methods: {
...
getNextFocusableSibling(element) {
let sibling = focusedElement.nextElementSibling;
while (sibling && !isFocusable(sibling)) {
sibling = sibling.nextElementSibling;
}
return sibling;
}
}
and lets refactor the definition of these variables prevSibling
and sibling
to use the result of these methods.
…skipping of elements
Hey @Sahil-Sinha-11, I made the suggested changes. 🤗 |
Hey @AlexVelezLl , the focus works like this, should I return it to the last focused button? Screen.Recording.2024-11-11.at.1.27.50.AM.mov |
Hey @Sahil-Sinha-11. Yes!! It should be working like that, when we close the dropdown, the last focused element is the element that should be focused. Thank you!! Loking forward to see this change pushed in the PR 💯.
No worriees! 😅 There is no rush, please take your time 🤗. |
Hey @AlexVelezLl, I made the changes, please review it, thanks. |
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.
Woo 🎉. This is working soo smothly @Sahil-Sinha-11. Thank you!! I have left a couple of comments just for some code clean up, thanks!
lib/KDropdownMenu.vue
Outdated
@@ -32,6 +32,7 @@ | |||
<script> | |||
|
|||
import { computed } from '@vue/composition-api'; | |||
// import { get } from 'lodash'; |
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.
We can get rid of this line
lib/KDropdownMenu.vue
Outdated
@@ -147,6 +149,7 @@ | |||
handleOpen() { | |||
this.$nextTick(() => this.$nextTick(() => this.setFocus())); | |||
window.addEventListener('keydown', this.handleOpenMenuNavigation, true); | |||
this.lastFocusElement = document.activeElement; |
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 there is no problem with having this line as the last line of the handleOpen
method, we can move it to be the first line of the method, so the intention of storing the last focused element before we focus anything else is clearer :).
lib/KDropdownMenu.vue
Outdated
@@ -162,8 +165,31 @@ | |||
) { | |||
this.focusOnButton(); | |||
} | |||
|
|||
if (this.lastFocusElement) { |
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.
Above this, you can see there is a call to the this.focusOnButton()
method, which apparently was an old behaviour we had in KDropdownMenu, but its no longer useful as now KDropdownMenu doesnt render any button anymore.
But if in the past, we were making this call in those conditions, it was for some reason (perhaps because of an edge case im not aware of), but to play safe, lets just replace that focusOnButton()
call with this lastFocusElement call, for this focus to occur on those conditions too, so we would have something like:
if (
popover.contains(focusedElement) &&
(focusedElement.classList.contains('ui-popover') ||
focusedElement.classList.contains('ui-popover-focus-redirector') ||
focusedElement.classList.contains('ui-menu-option'))
) {
if (this.lastFocusElement) {
this.lastFocusElement.focus();
}
}
And then we can remove the focusOnButton
method :).
lib/KDropdownMenu.vue
Outdated
@@ -175,19 +201,27 @@ | |||
const popoverIsOpen = popover.clientWidth > 0 && popover.clientHeight > 0; | |||
// set current element and its siblings | |||
let focusedElement = document.activeElement; | |||
let sibling = focusedElement.nextElementSibling; | |||
let prevSibling = focusedElement.previousElementSibling; | |||
// let sibling = focusedElement.nextElementSibling; |
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 catch! We dont need to make these calls always. We can remove these lines too.
lib/KDropdownMenu.vue
Outdated
|
||
// manage rotating through the options using arrow keys | ||
// UP arrow: .keyCode is depricated and should used only as a fallback | ||
if ((event.key == 'ArrowUp' || event.keyCode == 38) && popoverIsOpen) { | ||
event.preventDefault(); | ||
|
||
// Checking if previous sibling is divider and if yes then skip it | ||
let prevSibling = this.getPreviousFocusableSibling(focusedElement); |
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.
lets define these prevSibling
and sibling
as const, as we wont need to reassign them anymore.
Hey @AlexVelezLl, I made the suggested changes, please review them, thanks. |
Thank youu @Sahil-Sinha-11!! Code changes look good to me! :) . I have just opened a PR in Kolibri with your changes for QA to test this in Kolibri and confirming that this is the expected behaviour, after that we can merge your PR. Thanks @Sahil-Sinha-11! Good job 🤗 |
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 @Sahil-Sinha-11!
Thanks @Sahil-Sinha-11 and @AlexVelezLl, good chunk of work here :)! |
…p down position
Description
Issue addressed
Addresses #588
After Video
Screen.Recording.2024-11-08.at.12.16.34.AM.mov
Changelog
Steps to test
(optional) Implementation notes
At a high level, how did you implement this?
Does this introduce any tech-debt items?
Testing checklist
Reviewer guidance
Comments