diff --git a/examples/index.html b/examples/index.html index 0bbc7d6..96e78e6 100644 --- a/examples/index.html +++ b/examples/index.html @@ -7,12 +7,15 @@
- Robots + human: none
diff --git a/index.js b/index.js index 0fbabf6..364dfe6 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,8 @@ /* @flow */ +let typing = null +let typingTimeoutID = null + class DetailsMenuElement extends HTMLElement { constructor() { super() @@ -63,10 +66,46 @@ function focusInput(details: Element) { } } +function handleTyping(event: KeyboardEvent) { + const summary = event.currentTarget + if (!(summary instanceof HTMLElement)) return + const details = summary.closest('details') + if (!details) return + if (!event.key.match(/^[A-z0-9]{1}$/)) { + typing = null + return + } + + if (!typing) typing = '^' + if (!event.metaKey && !event.ctrlKey && !event.shiftKey) typing += event.key + + if (typingTimeoutID) { + clearTimeout(typingTimeoutID) + typingTimeoutID = null + } + + typingTimeoutID = setTimeout(() => { + typing = null + }, 500) + const target = findElementByString(details, new RegExp(typing, 'i')) + if (target) { + details.setAttribute('open', 'open') + target.focus() + } +} + +function menuitems(details: Element): NodeList { + return details.querySelectorAll('[role^="menuitem"]:not([hidden]):not([disabled]):not([aria-disabled="true"])') +} + +function findElementByString(details: Element, regex: RegExp): ?HTMLElement { + for (const item of menuitems(details)) { + if (item.textContent.match(regex)) return item + } +} + function sibling(details: Element, next: boolean): ?HTMLElement { - const options = Array.from( - details.querySelectorAll('[role^="menuitem"]:not([hidden]):not([disabled]):not([aria-disabled="true"])') - ) + const options = Array.from(menuitems(details)) const selected = document.activeElement const index = options.indexOf(selected) const sibling = next ? options[index + 1] : options[index - 1] @@ -172,6 +211,8 @@ function keydown(event: KeyboardEvent) { } } break + default: + handleTyping(event) } }