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
JavaScript
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);
}
}
}