diff --git a/src/components/Settings/SearchBar.vue b/src/components/Settings/SearchBar.vue index 01e7173d..0ec1beca 100644 --- a/src/components/Settings/SearchBar.vue +++ b/src/components/Settings/SearchBar.vue @@ -17,98 +17,46 @@ diff --git a/src/utils/ArrowKeyNavigation.js b/src/utils/ArrowKeyNavigation.js new file mode 100644 index 00000000..5756b519 --- /dev/null +++ b/src/utils/ArrowKeyNavigation.js @@ -0,0 +1,97 @@ +/** + * Class encapsulating the functionality that enables the user to + * navigate through tiles/ search result grid using the arrow keys + * This code is very hacky, it's best not to look at it for too long + */ +export default class ArrowKeyNavigation { + constructor(index) { + this.index = index; + } + + resetIndex() { + this.index = 0; + } + + /* Figures out which element is next, based on the key pressed * + * current index and total number of items. Then calls focus function */ + arrowNavigation(key) { + if (this.index === undefined) this.index = 0; // Start at beginning + else if (key === 37) { // Left --> Previous + this.index -= 1; + } else if (key === 38) { // Up --> Previous + this.index = ArrowKeyNavigation.goToPrevious(this.index); + } else if (key === 39) { // Right --> Next + this.index += 1; + } else if (key === 40) { // Down --> Next + this.index = ArrowKeyNavigation.goToNext(this.index); + } + /* Ensure the index is within bounds, then focus element */ + this.index = ArrowKeyNavigation.getSafeElementIndex(this.index); + ArrowKeyNavigation.selectItemByIndex(this.index).focus(); + } + + /* Returns the number of visible items / results */ + static getNumResults() { + return document.getElementsByClassName('item').length; + } + + /* Returns the index for an element, ensuring that it's within bounds */ + static getSafeElementIndex(index) { + const numResults = ArrowKeyNavigation.getNumResults(); + if (index < 0) return numResults - 1; + else if (index >= numResults) return 0; + return index; + } + + /* Selects a given element, by it's ID. If out of bounds, returns element 0 */ + static selectItemByIndex(index) { + return (index >= 0 && index <= ArrowKeyNavigation.getNumResults()) + ? document.getElementsByClassName('item')[index] : [document.getElementsByClassName('item')]; + } + + /* Returns the index of the first cell in the previous/ above row */ + static findPreviousRow(startingIndex) { + const isSameRow = (indx, pos) => ArrowKeyNavigation.selectItemByIndex(indx).offsetTop === pos; + const checkPreviousIndex = (currentIndex, yPos) => { + if (currentIndex >= ArrowKeyNavigation.getNumResults()) return checkPreviousIndex(0, yPos); + else if (isSameRow(currentIndex, yPos)) return checkPreviousIndex(currentIndex - 1, yPos); + return currentIndex; + }; + const position = ArrowKeyNavigation.selectItemByIndex(startingIndex).offsetTop; + return checkPreviousIndex(startingIndex, position); + } + + /* Moves to the cell directly above the current */ + static goToPrevious(startingIndex) { + const isBelow = (start, end) => (ArrowKeyNavigation.selectItemByIndex(start).offsetTop + < ArrowKeyNavigation.selectItemByIndex(end).offsetTop); + const nextIndex = ArrowKeyNavigation.findPreviousRow(startingIndex); + const count = nextIndex - startingIndex; + const rowLen = nextIndex - ArrowKeyNavigation.findNextRow(startingIndex) + 1; + const adjustment = isBelow(startingIndex, nextIndex) ? 0 : rowLen - count; + return nextIndex + adjustment; + } + + /* Returns the index of the first cell in the next/ below row */ + static findNextRow(startingIndex) { + const isSameRow = (indx, pos) => ArrowKeyNavigation.selectItemByIndex(indx).offsetTop === pos; + const checkNextIndex = (currentIndex, yPos) => { + if (currentIndex >= ArrowKeyNavigation.getNumResults()) return checkNextIndex(0, yPos); + else if (isSameRow(currentIndex, yPos)) return checkNextIndex(currentIndex + 1, yPos); + return currentIndex; + }; + const position = ArrowKeyNavigation.selectItemByIndex(startingIndex).offsetTop; + return checkNextIndex(startingIndex, position); + } + + /* Moves to the cell directly below the current */ + static goToNext(startingIndex) { + const isAbove = (start, end) => (ArrowKeyNavigation.selectItemByIndex(start).offsetTop + > ArrowKeyNavigation.selectItemByIndex(end).offsetTop); + const nextIndex = ArrowKeyNavigation.findNextRow(startingIndex); + const count = nextIndex - startingIndex; + const rowLen = nextIndex - ArrowKeyNavigation.findPreviousRow(startingIndex) - 1; + const adjustment = isAbove(startingIndex, nextIndex) ? 0 : rowLen - count; + return nextIndex + adjustment; + } +}