UNPKG

keyux

Version:

Improve keyboard UI of web apps

150 lines (133 loc) 4.11 kB
function focus(current, next) { if (next) { next.tabIndex = 0 next.focus() current.tabIndex = -1 } } function findGroupNodeByEventTarget(target) { let fg = target.closest('[focusgroup]:not([focusgroup="none"])') if (fg) return fg } function getItems(group) { if (group.hasAttribute('focusgroup')) { let items = [...group.querySelectorAll('*:not([focusgroup="none"])')] return items.filter(item => { return ( item.role === 'button' || item.type === 'button' || item.role === 'checkbox' || item.type === 'checkbox' ) }) } } function isHorizontalOrientation(group) { let fg = group.getAttribute('focusgroup') if (fg !== null) return !fg.split(' ').includes('block') } export function focusGroupPolyfill() { return window => { let inGroup = false function keyDown(event) { let group = findGroupNodeByEventTarget(event.target) if (!group) { stop() return } let items = getItems(group) let index = Array.from(items).indexOf(event.target) let nextKey = 'ArrowDown' let prevKey = 'ArrowUp' if (isHorizontalOrientation(group)) { if (window.document.dir === 'rtl') { nextKey = 'ArrowLeft' prevKey = 'ArrowRight' } else { nextKey = 'ArrowRight' prevKey = 'ArrowLeft' } } if (event.key === nextKey) { event.preventDefault() if (items[index + 1]) { focus(event.target, items[index + 1]) } else if (group.getAttribute('focusgroup').includes('wrap')) { focus(event.target, items[0]) } } else if (event.key === prevKey) { event.preventDefault() if (items[index - 1]) { focus(event.target, items[index - 1]) } else if (group.getAttribute('focusgroup').includes('wrap')) { focus(event.target, items[items.length - 1]) } } else if (event.key === 'Home') { event.preventDefault() focus(event.target, items[0]) } else if (event.key === 'End') { event.preventDefault() focus(event.target, items[items.length - 1]) } } function stop() { inGroup = false window.removeEventListener('keydown', keyDown) } function focusIn(event) { let group = findGroupNodeByEventTarget(event.target) if (group) { if (!inGroup) { inGroup = true window.addEventListener('keydown', keyDown) } let items = getItems(group) if (!items.some(item => item.getAttribute('tabindex') === '0')) { items.forEach((item, index) => item.setAttribute('tabindex', index === 0 ? 0 : -1) ) items[0]?.focus() } else { items.forEach(item => { if (item !== event.target) item.setAttribute('tabindex', -1) }) } } else if (inGroup) { stop() } } function focusOut(event) { let group = findGroupNodeByEventTarget(event.target) if (group?.getAttribute('focusgroup')?.includes('no-memory')) { let items = getItems(group) items.forEach((item, index) => { item.setAttribute('tabindex', index === 0 ? 0 : -1) }) } if (!event.relatedTarget || event.relatedTarget === window.document) { stop() } } function click(event) { let group = findGroupNodeByEventTarget(event.target) if (group) { let items = getItems(group) for (let item of items) { if (item !== event.target) { item.setAttribute('tabindex', -1) } } event.target.setAttribute('tabindex', 0) } } window.addEventListener('click', click) window.addEventListener('focusin', focusIn) window.addEventListener('focusout', focusOut) return () => { stop() window.removeEventListener('click', click) window.removeEventListener('focusin', focusIn) window.removeEventListener('focusout', focusOut) } } }