UNPKG

baseframe-js

Version:

Baseframe JS is a comprehensive suite of modular plugins and utilities designed for front-end development

118 lines (117 loc) 4.92 kB
import $be from "base-elem-js"; import { KEYS } from "./core/constants"; import Store from "./core/Store"; import { getDataOptions, setParams } from "./util/helpers"; const VERSION = "1.3.0", DATA_NAME = 'AccessibleMenu', EVENT_NAME = 'accessibleMenu', DEFAULTS = { keyDirections: ['horizontal', 'vertical', 'vertical'], focusCss: 'focus', focusInElems: 'a, [tabindex]', focusLeaveElems: 'a, [tabindex], select, button' }; export default class AccessibleMenu { static defaults = DEFAULTS; static version = VERSION; static pluginName = DATA_NAME; element; $element; params; $aeLiParents = null; activeElem = null; focusInElems = ''; constructor(element, options) { const s = this; const dataOptions = getDataOptions(element, EVENT_NAME); s.element = element; s.$element = $be(element); s.params = setParams(AccessibleMenu.defaults, options, dataOptions); s.#handleEvents(); return s; } static remove(element, plugin) { $be(element).each((elem) => { const s = plugin || Store(elem, DATA_NAME); s.$element.off([ `focusin.${EVENT_NAME}`, `mouseleave.${EVENT_NAME}`, `blur.${EVENT_NAME}`, `keydown.${EVENT_NAME}` ]); Store(elem, DATA_NAME, null); }); } #prev(e) { const s = this, p = s.params, l = s.$aeLiParents.elem.length - 1, key = e.key, dirs = p.keyDirections; if (key === KEYS.left && dirs[l] === "horizontal" || key === KEYS.up && dirs[l] === "vertical" || key === KEYS.left && dirs[l] === "vertical" && (l > 1 && dirs[l - 1] === "vertical" && s.$aeLiParents.hasEls)) { s.#focusListItem(true); e.preventDefault(); } } #next(e) { const s = this, p = s.params, l = s.$aeLiParents.size - 1, atRootUl = s.$aeLiParents.size === 1, key = e.key, keyIsRight = key === KEYS.right && p.keyDirections[l], keyIsDown = key === KEYS.down && p.keyDirections[l], activeLi = s.activeElem.closest('li'), $sliblingLis = $be(activeLi).siblings('li', true), isLastLi = $sliblingLis.elem.at(-1) === activeLi, isLastAtRoolLevel = atRootUl && isLastLi; //go to sibling <li> if (keyIsRight === "horizontal" || keyIsDown === "vertical") { if (!isLastAtRoolLevel) { s.#focusListItem(false); e.preventDefault(); } } if (key === KEYS.esc && isLastAtRoolLevel) { $be(s.activeElem.closest('li')).rmClass(p.focusCss); } //go to the nestled <li> if (keyIsRight === "vertical" || keyIsDown === "horizontal") { const $nestledUl = $be(s.activeElem.closest('li')).findOne('ul'); s.#focusFirstElem($nestledUl); e.preventDefault(); } } #handleEvents() { const s = this, p = s.params, $lis = s.$element.findBy('tag', 'li'), lis = $lis.toArray(); let focusOutDelay = null; s.$element.on(`focusin.${EVENT_NAME}`, () => { s.activeElem = document.activeElement; focusOutDelay && clearTimeout(focusOutDelay); $be(s.activeElem.closest('li')) .addClass(p.focusCss) .siblings('li').rmClass(p.focusCss); }, lis) .on(`focusout.${EVENT_NAME}`, () => { focusOutDelay = setTimeout(() => $lis.rmClass(p.focusCss), 200); }) .on(`mouseleave.${EVENT_NAME}`, () => { $lis.rmClass(p.focusCss); }) .on(`keydown.${EVENT_NAME}`, (e) => { s.$aeLiParents = $be(s.activeElem).parents('li', s.element); if (e.key == KEYS.esc) s.#focusFirstElem(s.$aeLiParents); s.#prev(e); s.#next(e); }); } #focusFirstElem($focusWrapEl, prev = false, index = 0) { if (!$focusWrapEl.hasEls) return; const s = this, p = s.params, $focusEls = $focusWrapEl.find(p.focusInElems, $be.isVisible); if ($focusEls.hasEls) { const focusEl = $focusEls.elem[index]; $be(focusEl.closest('li')).tgClass(p.focusCss, !prev); focusEl.focus(); } } #focusListItem(prev) { const s = this, p = s.params, currLi = s.activeElem.closest('li'), siblingLi = (prev ? currLi.previousElementSibling : currLi.nextElementSibling), $currLi = $be(currLi), $siblingLi = $be(siblingLi); if ($siblingLi.hasEls) { s.#focusFirstElem($siblingLi, prev); } else { // go to the parent <li> if there is no sibling <li> const nth = s.$aeLiParents.size > 2 ? -2 : 0; $currLi.rmClass(p.focusCss); s.#focusFirstElem($be(s.$aeLiParents.elem.at(nth)), prev); } } }