easepick-plugin-keyboard
Version:
Adds keyboard navigation for [easepick](https://easepick.com/) library.
219 lines (218 loc) • 6.63 kB
JavaScript
// src/index.ts
import { BasePlugin } from "@easepick/base-plugin";
var KeyboardPlugin = class extends BasePlugin {
constructor() {
super(...arguments);
this.binds = {
onKeydown: this.onKeydown.bind(this),
handleShow: this.handleShow.bind(this),
updateTabIndex: this.updateTabIndex.bind(this)
};
this.options = {
unitIndex: 1,
dayIndex: 2,
openHotkeys: ["Enter", "Space"]
};
}
getName() {
return "KeyboardPlugin";
}
onAttach() {
const element = this.picker.options.element;
element.addEventListener("keydown", this.binds.handleShow, {
capture: true
});
this.picker.on("keydown", this.binds.onKeydown);
this.picker.on("show", this.binds.updateTabIndex);
this.picker.on("hide", this.binds.updateTabIndex);
this.picker.on("render", this.binds.updateTabIndex);
}
onDetach() {
const element = this.picker.options.element;
element.removeEventListener("keydown", this.binds.handleShow, {
capture: true
});
this.picker.off("keydown", this.binds.onKeydown);
this.picker.off("show", this.binds.updateTabIndex);
this.picker.off("hide", this.binds.updateTabIndex);
this.picker.off("render", this.binds.updateTabIndex);
}
updateTabIndex() {
const show = this.picker.ui.container.classList.contains("show");
const days = this.picker.ui.container.querySelectorAll(
".unit.day:not(.locked,.not-available)"
);
[...days].forEach(
(el) => el.tabIndex = show ? this.options.dayIndex || 0 : -1
);
const units = this.picker.ui.container.querySelectorAll(".unit:not(.day)");
[...units].forEach(
(el) => el.tabIndex = show ? this.options.unitIndex || 0 : -1
);
}
handleShow(event) {
const { openHotkeys } = this.options;
switch (true) {
case (openHotkeys == null ? void 0 : openHotkeys.includes(event.code)):
event.preventDefault();
this.picker.show({ target: this.picker.options.element });
break;
case ["Escape"].includes(event.code):
this.picker.hide();
break;
}
}
onKeydown(event) {
this.onMouseEnter(event);
switch (event.code) {
case "ArrowUp":
case "ArrowDown":
this.verticalMove(event);
break;
case "ArrowLeft":
case "ArrowRight":
this.horizontalMove(event);
break;
case "Enter":
case "Space":
this.handleEnter(event);
break;
case "Escape":
this.picker.hide();
break;
}
}
findAllowableDaySibling(layout, target, isAllow) {
const elms = Array.from(
layout.querySelectorAll(
`.day[tabindex="${this.options.dayIndex}"]`
)
);
const targetIdx = elms.indexOf(target);
return elms.filter((el, idx) => {
return isAllow(idx, targetIdx) && el.tabIndex === this.options.dayIndex;
})[0];
}
changeMonth(evt) {
var _a;
const arrows = {
ArrowLeft: "previous",
ArrowRight: "next"
};
const button = this.picker.ui.container.querySelector(
`.${arrows[evt.code]}-button[tabindex="${this.options.unitIndex}"]`
);
if (button && !((_a = button.parentElement) == null ? void 0 : _a.classList.contains(`no-${arrows[evt.code]}-month`))) {
button.dispatchEvent(new Event("click", { bubbles: true }));
setTimeout(() => {
let focusEl = null;
switch (evt.code) {
case "ArrowLeft": {
const elms = this.picker.ui.container.querySelectorAll(
`.day[tabindex="${this.options.dayIndex}"]`
);
focusEl = elms[elms.length - 1];
break;
}
case "ArrowRight":
focusEl = this.picker.ui.container.querySelector(
`.day[tabindex="${this.options.dayIndex}"]`
);
break;
}
if (focusEl) {
focusEl.focus();
}
});
}
}
verticalMove(evt) {
const target = evt.target;
if (target.classList.contains("day")) {
evt.preventDefault();
const nextElement = this.findAllowableDaySibling(
this.picker.ui.container,
target,
(idx, targetIdx) => {
targetIdx = evt.code === "ArrowUp" ? targetIdx - 7 : targetIdx + 7;
return idx === targetIdx;
}
);
if (nextElement) {
nextElement.focus();
}
}
}
horizontalMove(evt) {
const target = evt.target;
if (target.classList.contains("day")) {
evt.preventDefault();
const nextElement = this.findAllowableDaySibling(
this.picker.ui.container,
target,
(idx, targetIdx) => {
targetIdx = evt.code === "ArrowLeft" ? targetIdx - 1 : targetIdx + 1;
return idx === targetIdx;
}
);
if (nextElement) {
nextElement.focus();
} else {
this.changeMonth(evt);
}
}
}
handleEnter(evt) {
const target = evt.target;
if (target.classList.contains("day")) {
evt.preventDefault();
target.dispatchEvent(new Event("click", { bubbles: true }));
setTimeout(() => {
const rangePlugin = this.picker.PluginManager.getInstance("RangePlugin");
if (rangePlugin || !this.picker.options.autoApply) {
const { container } = this.picker.ui;
const endSelected = container.querySelector(".day.end");
if (endSelected) {
this.focusTo(".apply-button");
} else {
this.focusTo(".day.selected");
}
}
});
}
if (target.classList.contains("unit")) {
if (target.classList.contains("preset-button")) {
if (!this.picker.options.autoApply) {
this.focusTo(".apply-button");
}
}
if (target.classList.contains("previous-button")) {
this.focusTo(".calendar:first-child > .header .previous-button");
}
if (target.classList.contains("next-button")) {
this.focusTo(".calendar:last-child > .header .next-button");
}
}
}
onMouseEnter(evt) {
const target = evt.target;
if (target.classList.contains("day")) {
setTimeout(() => {
const e = this.picker.ui.shadowRoot.activeElement;
if (e) {
e.dispatchEvent(new Event("mouseenter", { bubbles: true }));
}
});
}
}
focusTo(selectors, timeout = 0) {
setTimeout(() => {
const { container } = this.picker.ui;
const element = container.querySelector(selectors);
element == null ? void 0 : element.focus();
}, timeout);
}
};
export {
KeyboardPlugin
};