forked from advplyr/audiobookshelf
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor menu keyoboard navigation into mixin
- Loading branch information
Showing
5 changed files
with
110 additions
and
107 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
/** | ||
* Mixin for keyboard navigation in dropdown menus. | ||
* This can be used in any component that has a dropdown menu with <li> items. | ||
* The following example shows how to use this mixin in your component: | ||
* <template> | ||
* <div> | ||
* <input type="text" @keydown="menuNavigationHandler"> | ||
* <ul ref="menu"> | ||
* <li v-for="(item, index) in itemsToShow" :key="index" :class="isMenuItemSelected(item) ? ... : ''" @click="clickedOption($event, item)"> | ||
* {{ item }} | ||
* </li> | ||
* </ul> | ||
* </div> | ||
* </template> | ||
* | ||
* This mixin assumes the following are defined in your component: | ||
* itemsToShow: Array of items to show in the dropdown | ||
* clickedOption: Event handler for when an item is clicked | ||
* submitForm: Event handler for when the form is submitted | ||
* | ||
* It also assumes you have a ref="menu" on the menu element. | ||
*/ | ||
export default { | ||
data() { | ||
return { | ||
selectedMenuItemIndex: null | ||
} | ||
}, | ||
methods: { | ||
menuNavigationHandler(event) { | ||
let items = this.itemsToShow | ||
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { | ||
event.preventDefault() | ||
if (!items.length) return | ||
if (event.key === 'ArrowDown') { | ||
if (this.selectedMenuItemIndex === null) { | ||
this.selectedMenuItemIndex = 0 | ||
} else { | ||
this.selectedMenuItemIndex = Math.min(this.selectedMenuItemIndex + 1, items.length - 1) | ||
} | ||
} else if (event.key === 'ArrowUp') { | ||
if (this.selectedMenuItemIndex === null) { | ||
this.selectedMenuItemIndex = items.length - 1 | ||
} else { | ||
this.selectedMenuItemIndex = Math.max(this.selectedMenuItemIndex - 1, 0) | ||
} | ||
} | ||
this.recalcScroll() | ||
} else if (event.key === 'Enter') { | ||
event.preventDefault() | ||
if (this.selectedMenuItemIndex !== null) { | ||
this.clickedOption(event, items[this.selectedMenuItemIndex]) | ||
} else { | ||
this.submitForm() | ||
} | ||
} else { | ||
this.selectedMenuItemIndex = null | ||
} | ||
}, | ||
recalcScroll() { | ||
const menu = this.$refs.menu | ||
if (!menu) return | ||
var menuItems = menu.querySelectorAll('li') | ||
if (!menuItems.length) return | ||
var selectedItem = menuItems[this.selectedMenuItemIndex] | ||
if (!selectedItem) return | ||
var menuHeight = menu.offsetHeight | ||
var itemHeight = selectedItem.offsetHeight | ||
var itemTop = selectedItem.offsetTop | ||
var itemBottom = itemTop + itemHeight | ||
if (itemBottom > menu.scrollTop + menuHeight) { | ||
let menuPaddingBottom = parseFloat(window.getComputedStyle(menu).paddingBottom) | ||
menu.scrollTop = itemBottom - menuHeight + menuPaddingBottom | ||
} else if (itemTop < menu.scrollTop) { | ||
let menuPaddingTop = parseFloat(window.getComputedStyle(menu).paddingTop) | ||
menu.scrollTop = itemTop - menuPaddingTop | ||
} | ||
}, | ||
isMenuItemSelected(item) { | ||
return this.selectedMenuItemIndex !== null && this.itemsToShow[this.selectedMenuItemIndex] === item | ||
} | ||
} | ||
} |