search-with-your-keyboard/index.js

129 строки
3.7 KiB
JavaScript

const keycode = require('keycode')
module.exports = function searchWithYourKeyboard (inputSelector, hitsSelector) {
if (typeof inputSelector !== 'string') {
throw new TypeError('inputSelector should be a string')
}
if (typeof hitsSelector !== 'string') {
throw new TypeError('hitsSelector should be a string')
}
let activeIndex = 0
const targetEventCodes = ['up', 'down', 'enter', '/', 'esc']
const input = document.querySelector(inputSelector)
// deactivate any active hit when search input is focused by a mouse click
input.addEventListener('focus', () => {
activeIndex = 0
deactivateHits()
})
// deactivate any active hit when typing in search box
input.addEventListener('keydown', event => {
if (!targetEventCodes.includes(event.code)) {
activeIndex = 0
deactivateHits()
}
})
document.addEventListener('keydown', event => {
// bail early if key code is not one that we're explicity expecting
if (!event || !event.code || !targetEventCodes.includes(keycode(event))) return
const hits = getCurrentHits()
const queryExists = Boolean(input && input.value && input.value.length > 0)
// If we on `input` element we skip all events, if not
// we accept all events described bottom.
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(event.target.tagName)) return false
switch (keycode(event)) {
case 'esc':
input.focus()
input.select()
input.value = ''
return
case '/':
// when the input is focused, `/` should have no special behavior
if (event.target !== input) {
input.focus()
input.select()
event.preventDefault() // prevent slash from being typed into input
}
break
case 'up':
if (!queryExists) return
// search input is the zero index (don't go beyond it)
if (activeIndex > 0) {
activeIndex--
event.preventDefault() // prevent window scrolling
}
updateActiveHit()
break
case 'down':
if (!queryExists) return
// last hit is the last index (don't go beyond it)
if (activeIndex < hits.length) {
activeIndex++
event.preventDefault() // prevent window scrolling
}
updateActiveHit()
break
case 'enter':
// look for a link in the given hit, then visit it
if (activeIndex > 0) {
const hit = hits[activeIndex - 1]
if (!hit) return
const link = hit.querySelector('a')
if (!link) return
const href = link.getAttribute('href')
if (!href) return
// If `ctrlKey` is pressed, opens page in new window.
// In all other cases, we open the page in the same window.
if (event.ctrlKey) {
window.open(href, '_blank')
// NOTE: The `window.focus()` method not work correctly
// on some browser or OS. It don't focus window in
// Chrome browser.
window.focus()
} else {
window.location = href
}
}
break
}
})
function deactivateHits () {
Array.from(document.querySelectorAll(hitsSelector)).forEach(hit => {
hit.classList.remove('active')
})
}
function getCurrentHits () {
return Array.from(document.querySelectorAll(hitsSelector)).filter(el => {
return el.style.display !== 'none' && el.offsetParent !== null // element is visible
})
}
function updateActiveHit () {
deactivateHits()
if (activeIndex === 0) {
input.focus()
input.select()
} else {
const hits = getCurrentHits()
hits[activeIndex - 1].classList.add('active')
input.blur()
}
}
}