mirror of https://github.com/docusealco/docuseal
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
81 lines
2.3 KiB
81 lines
2.3 KiB
export default class extends HTMLElement {
|
|
connectedCallback () {
|
|
this._trigger = this.querySelector('[aria-haspopup]')
|
|
this._menu = this.querySelector('ul')
|
|
|
|
if (!this._trigger || !this._menu) return
|
|
|
|
this._menu.setAttribute('role', 'menu')
|
|
this._menu.querySelectorAll('a[href], button').forEach((el) => {
|
|
el.setAttribute('role', 'menuitem')
|
|
})
|
|
|
|
this.addEventListener('focusin', this._onFocusin)
|
|
this.addEventListener('focusout', this._onFocusout)
|
|
this._trigger.addEventListener('keydown', this._onTriggerKeydown)
|
|
this._menu.addEventListener('keydown', this._onMenuKeydown)
|
|
}
|
|
|
|
_onFocusin = () => {
|
|
this._trigger.setAttribute('aria-expanded', 'true')
|
|
}
|
|
|
|
_onFocusout = (e) => {
|
|
if (!this.contains(e.relatedTarget)) {
|
|
this._trigger.setAttribute('aria-expanded', 'false')
|
|
}
|
|
}
|
|
|
|
_onTriggerKeydown = (e) => {
|
|
if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {
|
|
e.preventDefault()
|
|
this._focusItem(0)
|
|
} else if (e.key === 'ArrowUp') {
|
|
e.preventDefault()
|
|
this._focusItem(-1)
|
|
}
|
|
}
|
|
|
|
_onMenuKeydown = (e) => {
|
|
const items = this._menuItems()
|
|
const idx = items.indexOf(document.activeElement)
|
|
|
|
if (e.key === 'ArrowDown') {
|
|
e.preventDefault()
|
|
items[(idx + 1) % items.length]?.focus()
|
|
} else if (e.key === 'ArrowUp') {
|
|
e.preventDefault()
|
|
items[(idx - 1 + items.length) % items.length]?.focus()
|
|
} else if (e.key === 'Home') {
|
|
e.preventDefault()
|
|
items[0]?.focus()
|
|
} else if (e.key === 'End') {
|
|
e.preventDefault()
|
|
items[items.length - 1]?.focus()
|
|
} else if (e.key === 'Escape') {
|
|
e.preventDefault()
|
|
this._closeMenu()
|
|
}
|
|
}
|
|
|
|
_menuItems () {
|
|
return Array.from(this._menu.querySelectorAll('a[href], button:not([disabled])'))
|
|
}
|
|
|
|
_focusItem (idx) {
|
|
const items = this._menuItems()
|
|
const target = idx >= 0 ? items[idx] : items[items.length + idx]
|
|
target?.focus()
|
|
}
|
|
|
|
_closeMenu () {
|
|
// Force-hide while focusing trigger to prevent CSS :focus-within from re-opening it
|
|
this._menu.style.setProperty('display', 'none', 'important')
|
|
this._trigger.setAttribute('aria-expanded', 'false')
|
|
this._trigger.focus()
|
|
this._trigger.addEventListener('blur', () => {
|
|
this._menu.style.removeProperty('display')
|
|
}, { once: true })
|
|
}
|
|
}
|