update
This commit is contained in:
+259
-1
@@ -26,6 +26,261 @@
|
||||
return Boolean(document.querySelector("[role='dialog'][aria-modal='true'][aria-hidden='false']"));
|
||||
}
|
||||
|
||||
function isHiddenElement(element) {
|
||||
if (!(element instanceof HTMLElement)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (element.hidden || element.getAttribute("aria-hidden") === "true") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getAutoSequenceOptions(listEl) {
|
||||
if (!(listEl instanceof HTMLElement)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return Array.from(listEl.querySelectorAll("[role='option']"))
|
||||
.filter((option) => option instanceof HTMLElement)
|
||||
.filter((option) => !isHiddenElement(option));
|
||||
}
|
||||
|
||||
function getAutoSelectedIndex(options) {
|
||||
const selectedIndex = options.findIndex((option) => {
|
||||
if (!(option instanceof HTMLElement)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return option.getAttribute("aria-selected") === "true"
|
||||
|| option.classList.contains("is-selected");
|
||||
});
|
||||
|
||||
return selectedIndex;
|
||||
}
|
||||
|
||||
function createAutomaticSequenceControls(sectionEl, headingEl) {
|
||||
if (!(headingEl instanceof HTMLElement)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (headingEl.querySelector(".detail-sequence-nav")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const titleText = String(headingEl.querySelector("h2")?.textContent || "items").trim() || "items";
|
||||
const normalizedTitle = titleText
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/(^-|-$)/g, "");
|
||||
|
||||
const sectionId = String(sectionEl?.id || "section").trim() || "section";
|
||||
const navPrefix = `${sectionId}-${normalizedTitle || "items"}`;
|
||||
|
||||
const navEl = document.createElement("div");
|
||||
navEl.className = "detail-sequence-nav";
|
||||
navEl.dataset.autoSequenceNav = "true";
|
||||
navEl.setAttribute("aria-label", `Browse ${titleText}`);
|
||||
|
||||
const prevButton = document.createElement("button");
|
||||
prevButton.type = "button";
|
||||
prevButton.id = `${navPrefix}-detail-prev-auto`;
|
||||
prevButton.className = "detail-sequence-btn";
|
||||
prevButton.textContent = "Back";
|
||||
prevButton.setAttribute("aria-label", `Previous ${titleText}`);
|
||||
|
||||
const positionEl = document.createElement("div");
|
||||
positionEl.id = `${navPrefix}-detail-position-auto`;
|
||||
positionEl.className = "detail-sequence-position";
|
||||
positionEl.setAttribute("aria-live", "polite");
|
||||
positionEl.textContent = "--";
|
||||
|
||||
const nextButton = document.createElement("button");
|
||||
nextButton.type = "button";
|
||||
nextButton.id = `${navPrefix}-detail-next-auto`;
|
||||
nextButton.className = "detail-sequence-btn";
|
||||
nextButton.textContent = "Next";
|
||||
nextButton.setAttribute("aria-label", `Next ${titleText}`);
|
||||
|
||||
navEl.append(prevButton, positionEl, nextButton);
|
||||
|
||||
const summaryEl = headingEl.querySelector(".planet-detail-summary");
|
||||
if (summaryEl && summaryEl.parentElement === headingEl) {
|
||||
headingEl.insertBefore(navEl, summaryEl);
|
||||
} else {
|
||||
headingEl.appendChild(navEl);
|
||||
}
|
||||
|
||||
return {
|
||||
sectionEl,
|
||||
listEl: sectionEl.querySelector(".planet-list-panel [role='listbox']"),
|
||||
prevButton,
|
||||
nextButton,
|
||||
positionEl
|
||||
};
|
||||
}
|
||||
|
||||
const automaticNavigators = new WeakMap();
|
||||
let autoScanObserver = null;
|
||||
let autoScanQueued = false;
|
||||
let autoNavigationEnabled = false;
|
||||
|
||||
function createAutomaticNavigatorForSection(sectionEl) {
|
||||
if (!(sectionEl instanceof HTMLElement) || automaticNavigators.has(sectionEl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const listEl = sectionEl.querySelector(".planet-list-panel [role='listbox']");
|
||||
const headingEl = sectionEl.querySelector(".planet-detail-panel .planet-detail-heading");
|
||||
if (!(listEl instanceof HTMLElement) || !(headingEl instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (headingEl.querySelector(".detail-sequence-nav")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const controls = createAutomaticSequenceControls(sectionEl, headingEl);
|
||||
if (!controls || !(controls.listEl instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const navigator = createSequenceNavigator({
|
||||
getElements: () => controls,
|
||||
isActive: (elements) => Boolean(elements?.sectionEl && elements.sectionEl.hidden === false),
|
||||
getSequenceState: () => {
|
||||
const options = getAutoSequenceOptions(controls.listEl);
|
||||
const total = options.length;
|
||||
const currentIndex = getAutoSelectedIndex(options);
|
||||
|
||||
if (!total) {
|
||||
return {
|
||||
total: 0,
|
||||
currentIndex: -1,
|
||||
previousKey: "",
|
||||
nextKey: ""
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
total,
|
||||
currentIndex,
|
||||
previousKey: currentIndex > 0 ? String(currentIndex - 1) : "",
|
||||
nextKey: currentIndex >= 0 && currentIndex < total - 1
|
||||
? String(currentIndex + 1)
|
||||
: currentIndex < 0
|
||||
? "0"
|
||||
: ""
|
||||
};
|
||||
},
|
||||
getPrevButton: (elements) => elements?.prevButton,
|
||||
getNextButton: (elements) => elements?.nextButton,
|
||||
getPositionEl: (elements) => elements?.positionEl,
|
||||
formatPositionText: ({ total, currentIndex }) => {
|
||||
if (total > 0 && currentIndex >= 0) {
|
||||
return `${currentIndex + 1} of ${total}`;
|
||||
}
|
||||
|
||||
return total > 0 ? `${total} items` : "No items";
|
||||
},
|
||||
selectTarget: (targetKey) => {
|
||||
const options = getAutoSequenceOptions(controls.listEl);
|
||||
const targetIndex = Number(targetKey);
|
||||
if (!Number.isFinite(targetIndex) || targetIndex < 0 || targetIndex >= options.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const targetOption = options[targetIndex];
|
||||
if (!(targetOption instanceof HTMLElement)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetOption instanceof HTMLButtonElement && targetOption.disabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
targetOption.dispatchEvent(new MouseEvent("click", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
view: window
|
||||
}));
|
||||
|
||||
return true;
|
||||
},
|
||||
afterSelect: (targetKey) => {
|
||||
const options = getAutoSequenceOptions(controls.listEl);
|
||||
const targetIndex = Number(targetKey);
|
||||
if (!Number.isFinite(targetIndex) || targetIndex < 0 || targetIndex >= options.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetOption = options[targetIndex];
|
||||
if (targetOption instanceof HTMLElement) {
|
||||
targetOption.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
navigator.bind(controls);
|
||||
|
||||
const listObserver = new MutationObserver(() => {
|
||||
navigator.sync(controls);
|
||||
});
|
||||
|
||||
listObserver.observe(controls.listEl, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
attributes: true,
|
||||
attributeFilter: ["aria-selected", "class", "hidden", "aria-hidden"]
|
||||
});
|
||||
|
||||
automaticNavigators.set(sectionEl, {
|
||||
controls,
|
||||
navigator,
|
||||
listObserver
|
||||
});
|
||||
}
|
||||
|
||||
function scanForAutomaticNavigators() {
|
||||
document.querySelectorAll("section").forEach((sectionEl) => {
|
||||
createAutomaticNavigatorForSection(sectionEl);
|
||||
});
|
||||
}
|
||||
|
||||
function queueAutomaticNavigationScan() {
|
||||
if (autoScanQueued) {
|
||||
return;
|
||||
}
|
||||
|
||||
autoScanQueued = true;
|
||||
window.requestAnimationFrame(() => {
|
||||
autoScanQueued = false;
|
||||
scanForAutomaticNavigators();
|
||||
});
|
||||
}
|
||||
|
||||
function enableAutomaticSequenceNavigation() {
|
||||
if (autoNavigationEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
autoNavigationEnabled = true;
|
||||
scanForAutomaticNavigators();
|
||||
|
||||
if (document.body && !autoScanObserver) {
|
||||
autoScanObserver = new MutationObserver(() => {
|
||||
queueAutomaticNavigationScan();
|
||||
});
|
||||
|
||||
autoScanObserver.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function createSequenceNavigator(config = {}) {
|
||||
const getElements = typeof config.getElements === "function"
|
||||
? config.getElements
|
||||
@@ -184,6 +439,9 @@
|
||||
|
||||
window.TarotSequenceNav = {
|
||||
...(window.TarotSequenceNav || {}),
|
||||
createSequenceNavigator
|
||||
createSequenceNavigator,
|
||||
enableAutomaticSequenceNavigation
|
||||
};
|
||||
|
||||
enableAutomaticSequenceNavigation();
|
||||
})();
|
||||
Reference in New Issue
Block a user