pinafore/src/routes/_components/shortcut/ScrollListShortcuts.html

134 wiersze
4.2 KiB
HTML

<script>
import {
isVisible,
firstVisibleElementIndex,
scrollIntoViewIfNeeded
} from '../../_utils/scrollIntoView'
import {
addShortcutFallback,
removeShortcutFallback,
onKeyDownInShortcutScope
} from '../../_utils/shortcuts'
import { smoothScroll } from '../../_utils/smoothScroll'
import { getScrollContainer } from '../../_utils/scrollContainer'
const VISIBILITY_CHECK_DELAY_MS = 600
const keyToElement = key => document.getElementById(key)
const elementToKey = element => element.getAttribute('id')
const scope = 'global'
const shouldIgnoreEvent = event => {
// For accessibility reasons, do not override the arrowup/arrowdown behavior for radio buttons
// (e.g. in a poll). Up/down is supposed to change the radio value, not the current status.
const { target, key } = event
const isRadio = target &&
target.tagName === 'INPUT' &&
(target.type || '').toLowerCase() === 'radio'
const isArrow = key === 'ArrowUp' || key === 'ArrowDown'
return isRadio && isArrow
}
export default {
data: () => ({
activeItemChangeTime: 0,
elements: document.getElementsByClassName('shortcut-list-item')
}),
oncreate () {
addShortcutFallback(scope, this)
},
ondestroy () {
removeShortcutFallback(scope, this)
},
methods: {
onKeyDown (event) {
if (shouldIgnoreEvent(event)) {
return
}
if (event.key === 'j' || event.key === 'ArrowDown') {
event.stopPropagation()
event.preventDefault()
this.changeActiveItem(1, event.timeStamp)
return
}
if (event.key === 'k' || event.key === 'ArrowUp') {
event.stopPropagation()
event.preventDefault()
this.changeActiveItem(-1, event.timeStamp)
return
}
let activeItemKey = this.checkActiveItem(event.timeStamp)
if (!activeItemKey) {
const { elements } = this.get()
const index = firstVisibleElementIndex(elements).first
if (index >= 0) {
activeItemKey = elementToKey(elements[index])
}
}
if (activeItemKey) {
onKeyDownInShortcutScope(activeItemKey, event)
}
},
changeActiveItem (movement, timeStamp) {
const { elements } = this.get()
let index = -1
let activeItemKey = this.checkActiveItem(timeStamp)
if (activeItemKey) {
const len = elements.length
let i = -1
while (++i < len) {
if (elementToKey(elements[i]) === activeItemKey) {
index = i
break
}
}
}
if (index === 0 && movement === -1) {
activeItemKey = null
this.set({ activeItemKey })
smoothScroll(getScrollContainer(), 0, /* horizontal */ false, /* preferFast */ false)
return
}
if (index === -1) {
const { first, firstComplete } = firstVisibleElementIndex(elements)
index = (movement > 0) ? firstComplete : first
} else {
index += movement
}
if (index >= 0 && index < elements.length) {
activeItemKey = elementToKey(elements[index])
this.setActiveItem(activeItemKey, timeStamp)
scrollIntoViewIfNeeded(keyToElement(activeItemKey))
}
},
checkActiveItem (timeStamp) {
const activeElement = document.activeElement
if (!activeElement) {
return null
}
const activeItem = activeElement.getAttribute('id')
if (!activeItem) {
return null
}
const { activeItemChangeTime } = this.get()
if ((timeStamp - activeItemChangeTime) > VISIBILITY_CHECK_DELAY_MS &&
!isVisible(keyToElement(activeItem))) {
this.setActiveItem(null, 0)
return null
}
return activeItem
},
setActiveItem (key, timeStamp) {
this.set({ activeItemChangeTime: timeStamp })
try {
keyToElement(key).focus({
preventScroll: true
})
} catch (err) {
console.error('Ignored focus error', err)
}
}
}
}
</script>