feat: expose graphic screens in sidebar
This commit is contained in:
@@ -7,22 +7,63 @@
|
||||
const toggle = document.getElementById("workspace-nav-toggle");
|
||||
const toggleLabel = toggle && toggle.querySelector(".workspace-nav-toggle-label");
|
||||
const stockTab = document.getElementById("workspace-stock-tab");
|
||||
const catalogTab = document.getElementById("workspace-catalog-tab");
|
||||
const settingsTab = document.getElementById("workspace-settings-tab");
|
||||
const stockWorkspace = document.getElementById("stock-workspace");
|
||||
const catalogWorkspace = document.getElementById("catalog-workspace");
|
||||
const settingsWorkspace = document.getElementById("settings-workspace");
|
||||
const marketTabs = document.getElementById("market-tabs");
|
||||
const title = document.getElementById("workspace-title");
|
||||
|
||||
if (!region || !navigation || !navigationItems || !toggle || !toggleLabel ||
|
||||
!stockTab || !catalogTab || !stockWorkspace || !catalogWorkspace ||
|
||||
!marketTabs || !title) {
|
||||
!stockTab || !settingsTab || !stockWorkspace || !catalogWorkspace ||
|
||||
!settingsWorkspace || !marketTabs || !title) {
|
||||
return;
|
||||
}
|
||||
|
||||
const views = Object.freeze({
|
||||
stock: Object.freeze({ tab: stockTab, view: stockWorkspace, title: "종목·컷" }),
|
||||
catalog: Object.freeze({ tab: catalogTab, view: catalogWorkspace, title: "기타 그래픽" })
|
||||
stock: Object.freeze({ view: stockWorkspace, title: "종목·컷" }),
|
||||
catalog: Object.freeze({ view: catalogWorkspace, title: "기타 그래픽" }),
|
||||
settings: Object.freeze({ view: settingsWorkspace, title: "설정" })
|
||||
});
|
||||
let activeWorkspace = "stock";
|
||||
let activeMarketTabId = marketTabs.querySelector("button.active[data-tab-id]")
|
||||
?.dataset.tabId || marketTabs.querySelector("button[data-tab-id]")?.dataset.tabId || null;
|
||||
let pendingKeyboardMarketTabId = null;
|
||||
|
||||
function marketButtons() {
|
||||
return Array.from(marketTabs.querySelectorAll("button[data-tab-id]"));
|
||||
}
|
||||
|
||||
function marketButton(tabId) {
|
||||
return marketButtons().find(function (button) {
|
||||
return button.dataset.tabId === tabId;
|
||||
}) || null;
|
||||
}
|
||||
|
||||
function marketLabel(button) {
|
||||
const label = button && button.querySelector(".workspace-nav-label");
|
||||
return (label && label.textContent.trim()) || button?.textContent.trim() || views.catalog.title;
|
||||
}
|
||||
|
||||
function selectedMenuButton(name) {
|
||||
if (name === "stock") return stockTab;
|
||||
if (name === "settings") return settingsTab;
|
||||
if (name === "catalog") {
|
||||
return marketButton(activeMarketTabId) || marketButtons()[0] || null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function refreshCurrentMenu() {
|
||||
const selected = selectedMenuButton(activeWorkspace);
|
||||
Array.from(navigation.querySelectorAll(".workspace-nav-item")).forEach(function (button) {
|
||||
const isCurrent = button === selected;
|
||||
button.classList.toggle("workspace-current", isCurrent);
|
||||
button.setAttribute("aria-current", isCurrent ? "page" : "false");
|
||||
});
|
||||
region.dataset.activeWorkspace = activeWorkspace;
|
||||
if (activeMarketTabId) region.dataset.activeMarketTab = activeMarketTabId;
|
||||
}
|
||||
|
||||
function setNavigationExpanded(expanded) {
|
||||
const isExpanded = expanded === true;
|
||||
@@ -37,31 +78,62 @@
|
||||
const selected = views[name];
|
||||
if (!selected) return false;
|
||||
|
||||
const selectedButton = selectedMenuButton(name);
|
||||
const currentFocus = document.activeElement;
|
||||
const focusWillBeHidden = Object.keys(views).some(function (key) {
|
||||
return key !== name && views[key].view.contains(currentFocus);
|
||||
});
|
||||
if (focusWillBeHidden) selected.tab.focus();
|
||||
if (focusWillBeHidden && selectedButton) selectedButton.focus();
|
||||
|
||||
Object.keys(views).forEach(function (key) {
|
||||
const entry = views[key];
|
||||
const isCurrent = key === name;
|
||||
entry.tab.classList.toggle("workspace-current", isCurrent);
|
||||
entry.tab.setAttribute("aria-current", isCurrent ? "page" : "false");
|
||||
entry.view.hidden = !isCurrent;
|
||||
entry.view.toggleAttribute("inert", !isCurrent);
|
||||
entry.view.setAttribute("aria-hidden", isCurrent ? "false" : "true");
|
||||
});
|
||||
|
||||
region.dataset.activeWorkspace = name;
|
||||
title.textContent = selected.title;
|
||||
activeWorkspace = name;
|
||||
refreshCurrentMenu();
|
||||
title.textContent = name === "catalog"
|
||||
? marketLabel(selectedMenuButton(name))
|
||||
: selected.title;
|
||||
return true;
|
||||
}
|
||||
|
||||
function syncMarket(tabId) {
|
||||
const button = marketButton(tabId);
|
||||
if (!button) return false;
|
||||
activeMarketTabId = tabId;
|
||||
region.dataset.activeMarketTab = tabId;
|
||||
if (activeWorkspace === "catalog") {
|
||||
refreshCurrentMenu();
|
||||
title.textContent = marketLabel(button);
|
||||
}
|
||||
if (pendingKeyboardMarketTabId && !button.disabled) {
|
||||
const pendingButton = marketButton(pendingKeyboardMarketTabId);
|
||||
const focusWasLost = !document.activeElement ||
|
||||
document.activeElement === document.body ||
|
||||
document.activeElement === document.documentElement;
|
||||
if (activeWorkspace === "catalog" && pendingButton &&
|
||||
!pendingButton.disabled && focusWasLost) {
|
||||
pendingButton.focus();
|
||||
}
|
||||
pendingKeyboardMarketTabId = null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function select(name) {
|
||||
if (views[name]) return setWorkspace(name);
|
||||
if (!syncMarket(name)) return false;
|
||||
return setWorkspace("catalog");
|
||||
}
|
||||
|
||||
function focusAdjacentItem(direction) {
|
||||
const items = [stockTab, catalogTab].filter(function (button) {
|
||||
return button.disabled !== true;
|
||||
});
|
||||
const items = Array.from(navigation.querySelectorAll(".workspace-nav-item"))
|
||||
.filter(function (button) { return button.disabled !== true; });
|
||||
if (items.length === 0) return;
|
||||
const currentIndex = items.indexOf(document.activeElement);
|
||||
const nextIndex = currentIndex < 0
|
||||
? 0
|
||||
@@ -73,14 +145,17 @@
|
||||
setNavigationExpanded(toggle.getAttribute("aria-expanded") !== "true");
|
||||
});
|
||||
stockTab.addEventListener("click", function () { setWorkspace("stock"); });
|
||||
catalogTab.addEventListener("click", function () { setWorkspace("catalog"); });
|
||||
settingsTab.addEventListener("click", function () { setWorkspace("settings"); });
|
||||
|
||||
navigationItems.addEventListener("keydown", function (event) {
|
||||
navigation.addEventListener("keydown", function (event) {
|
||||
const menuButton = event.target.closest && event.target.closest(".workspace-nav-item");
|
||||
if ((event.key === "Enter" || event.key === " ") && menuButton) {
|
||||
if (!menuButton) return;
|
||||
if (event.key === "Enter" || event.key === " ") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
setWorkspace(menuButton === stockTab ? "stock" : "catalog");
|
||||
const keyboardMarketTabId = menuButton.dataset.tabId || null;
|
||||
menuButton.click();
|
||||
if (keyboardMarketTabId) pendingKeyboardMarketTabId = keyboardMarketTabId;
|
||||
return;
|
||||
}
|
||||
if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
|
||||
@@ -89,19 +164,25 @@
|
||||
focusAdjacentItem(event.key === "ArrowUp" ? -1 : 1);
|
||||
});
|
||||
|
||||
// A market tab can also be activated by automation or accessibility tools.
|
||||
// Reveal its owning workspace before app.js dispatches the native select-tab intent.
|
||||
// Reveal the selected catalog screen before app.js dispatches its native
|
||||
// select-tab intent. The native state response remains authoritative and
|
||||
// is reconciled through syncMarket after every render.
|
||||
marketTabs.addEventListener("click", function (event) {
|
||||
if (event.target.closest("button[data-tab-id]")) setWorkspace("catalog");
|
||||
const button = event.target.closest("button[data-tab-id]");
|
||||
if (!button || !marketTabs.contains(button)) return;
|
||||
syncMarket(button.dataset.tabId);
|
||||
setWorkspace("catalog");
|
||||
}, true);
|
||||
|
||||
setNavigationExpanded(true);
|
||||
setWorkspace("stock");
|
||||
|
||||
window.LegacyWorkspaceNavigation = Object.freeze({
|
||||
select: setWorkspace,
|
||||
select: select,
|
||||
syncMarket: syncMarket,
|
||||
setExpanded: setNavigationExpanded,
|
||||
active: function () { return region.dataset.activeWorkspace; },
|
||||
active: function () { return activeWorkspace; },
|
||||
activeMarket: function () { return activeMarketTabId; },
|
||||
expanded: function () { return toggle.getAttribute("aria-expanded") === "true"; }
|
||||
});
|
||||
}());
|
||||
|
||||
Reference in New Issue
Block a user