Files

189 lines
5.1 KiB
JavaScript
Raw Permalink Normal View History

(function () {
"use strict";
function normalizeSequenceState(sequence) {
return {
total: Math.max(0, Number(sequence?.total) || 0),
currentIndex: Number.isFinite(Number(sequence?.currentIndex)) ? Number(sequence.currentIndex) : -1,
previousKey: String(sequence?.previousKey ?? sequence?.previousId ?? "").trim(),
nextKey: String(sequence?.nextKey ?? sequence?.nextId ?? "").trim()
};
}
function isEditableKeyTarget(target) {
if (!(target instanceof HTMLElement)) {
return false;
}
return target instanceof HTMLInputElement
|| target instanceof HTMLTextAreaElement
|| target instanceof HTMLSelectElement
|| target.isContentEditable
|| Boolean(target.closest("[contenteditable='true']"));
}
function hasOpenModalDialog() {
return Boolean(document.querySelector("[role='dialog'][aria-modal='true'][aria-hidden='false']"));
}
function createSequenceNavigator(config = {}) {
const getElements = typeof config.getElements === "function"
? config.getElements
: () => ({});
let buttonsBound = false;
let keyboardBound = false;
function getSequenceState() {
return normalizeSequenceState(
typeof config.getSequenceState === "function"
? config.getSequenceState()
: null
);
}
function getPrevButton(elements) {
return typeof config.getPrevButton === "function" ? config.getPrevButton(elements) : null;
}
function getNextButton(elements) {
return typeof config.getNextButton === "function" ? config.getNextButton(elements) : null;
}
function getPositionEl(elements) {
return typeof config.getPositionEl === "function" ? config.getPositionEl(elements) : null;
}
function isActive(elements) {
return typeof config.isActive === "function" ? config.isActive(elements) !== false : true;
}
function getTargetKey(sequence, offset) {
return offset < 0 ? sequence.previousKey : sequence.nextKey;
}
function formatPositionText(sequence, elements) {
return typeof config.formatPositionText === "function"
? String(config.formatPositionText(sequence, elements) || "")
: "";
}
function selectTarget(targetKey, elements, offset) {
if (!targetKey || typeof config.selectTarget !== "function") {
return false;
}
return config.selectTarget(targetKey, elements, offset) !== false;
}
function afterSelect(targetKey, elements, offset) {
if (typeof config.afterSelect === "function") {
config.afterSelect(targetKey, elements, offset);
}
}
function shouldHandleKeyEvent(event, elements) {
if (!isActive(elements)) {
return false;
}
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
return false;
}
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") {
return false;
}
if (hasOpenModalDialog()) {
return false;
}
return !isEditableKeyTarget(event.target);
}
function sync(elements = getElements()) {
const sequence = getSequenceState();
const previousKey = getTargetKey(sequence, -1);
const nextKey = getTargetKey(sequence, 1);
const prevButton = getPrevButton(elements);
const nextButton = getNextButton(elements);
const positionEl = getPositionEl(elements);
if (prevButton) {
prevButton.disabled = !previousKey;
}
if (nextButton) {
nextButton.disabled = !nextKey;
}
if (positionEl) {
positionEl.textContent = formatPositionText(sequence, elements);
}
}
function step(offset, elements = getElements()) {
const sequence = getSequenceState();
const targetKey = getTargetKey(sequence, offset);
if (!targetKey) {
return false;
}
const didSelect = selectTarget(targetKey, elements, offset);
if (didSelect) {
afterSelect(targetKey, elements, offset);
}
return didSelect;
}
function bind(elements = getElements()) {
if (!buttonsBound) {
getPrevButton(elements)?.addEventListener("click", () => {
step(-1, getElements());
});
getNextButton(elements)?.addEventListener("click", () => {
step(1, getElements());
});
buttonsBound = true;
}
if (!keyboardBound) {
document.addEventListener("keydown", (event) => {
const latestElements = getElements();
if (!shouldHandleKeyEvent(event, latestElements)) {
return;
}
const offset = event.key === "ArrowRight" ? 1 : -1;
const sequence = getSequenceState();
if (!getTargetKey(sequence, offset)) {
return;
}
event.preventDefault();
step(offset, latestElements);
});
keyboardBound = true;
}
sync(elements);
}
return {
bind,
step,
sync,
getSequenceState
};
}
window.TarotSequenceNav = {
...(window.TarotSequenceNav || {}),
createSequenceNavigator
};
})();