various ui improvements, including a new sequence nav component and a new kabbalah detail view
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
(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
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user