(function exposeApplicationShell(global) { "use strict"; const SIDEBAR_STORAGE_KEY = "xiaobai-sidebar-collapsed"; const MOBILE_BREAKPOINT = 767; function isMobileViewport() { return global.matchMedia(`(max-width: ${MOBILE_BREAKPOINT}px)`).matches; } function create(options) { const state = options.state; const registry = options.pages; let initialized = false; function syncViewportMode() { document.body.classList.toggle("mobile-shell", isMobileViewport()); } function toggleHeaderCommandMenu(force) { const menu = document.querySelector("#headerCommandGroup"); const button = document.querySelector("#headerMenuButton"); const backdrop = document.querySelector("#mobileCommandBackdrop"); if (!menu || !button) return; const open = typeof force === "boolean" ? force : !menu.classList.contains("is-open"); menu.classList.toggle("is-open", open); button.setAttribute("aria-expanded", String(open)); document.body.classList.toggle("mobile-command-open", open && isMobileViewport()); if (backdrop) backdrop.hidden = !(open && isMobileViewport()); if (open && isMobileViewport()) { global.requestAnimationFrame(() => menu.querySelector("button:not([hidden])")?.focus({ preventScroll: true })); } else if (force === false && document.activeElement && menu.contains(document.activeElement)) { button.focus({ preventScroll: true }); } } function updateSidebarControl() { const button = document.querySelector("#sidebarCollapseButton"); if (!button) return; const automaticallyCollapsed = global.innerWidth <= 1023 && !isMobileViewport(); const collapsed = document.body.classList.contains("sidebar-collapsed") || automaticallyCollapsed; button.setAttribute("aria-expanded", String(!collapsed)); button.setAttribute("aria-label", collapsed ? "展开侧栏" : "收起侧栏"); button.title = collapsed ? "展开侧栏" : "收起侧栏"; const label = button.querySelector("span"); if (label) label.textContent = collapsed ? "展开侧栏" : "收起侧栏"; } function toggleSidebar() { const collapsed = document.body.classList.toggle("sidebar-collapsed"); try { global.localStorage.setItem(SIDEBAR_STORAGE_KEY, collapsed ? "1" : "0"); } catch (_error) { // The shell remains usable when storage is unavailable. } updateSidebarControl(); } function syncNavigation(viewId) { const page = registry.get(viewId); const navigationId = page?.navigation_alias || viewId; const marketView = page?.group === "market"; const previousActiveView = document.body.dataset.activeView; document.body.dataset.activeView = viewId; const mobileGroup = document.querySelector("#mobilePageGroup"); const mobileTitle = document.querySelector("#mobilePageTitle"); if (mobileGroup) { mobileGroup.textContent = page?.group === "market" ? "行情" : page?.group === "personal" ? "复盘" : "工具"; } if (mobileTitle) mobileTitle.textContent = page?.title || "小白复盘"; const pageTitle = document.querySelector("#currentPageTitle"); if (pageTitle) pageTitle.textContent = page?.title || "小白复盘"; const pageSubtitle = document.querySelector("#currentPageSubtitle"); if (pageSubtitle && previousActiveView !== viewId) pageSubtitle.textContent = ""; document.querySelectorAll(".module-tab").forEach((button) => { button.classList.toggle("active", button.dataset.view === navigationId); button.classList.toggle( "mobile-active", isMobileViewport() && marketView && button.dataset.view === "limitPool" && viewId !== "limitPool", ); }); const selector = document.querySelector("#mobileMarketSelector"); const select = document.querySelector("#mobileMarketViewSelect"); if (selector) selector.hidden = !marketView; if (select && marketView) select.value = viewId; toggleHeaderCommandMenu(false); options.onNavigationSync?.(viewId); } function setStatus(text) { const status = document.querySelector("#statusText"); if (status) status.textContent = text; } function setPageStatus(viewId, tradeDate = "") { const page = registry.get(viewId); const label = page?.title || "小白复盘"; setStatus(tradeDate && tradeDate !== "--" ? `${label} · 数据日期 ${tradeDate}` : `${label} · 等待数据`); } function openModalDialog(dialog) { if (!(dialog instanceof HTMLDialogElement)) return; document.querySelectorAll("dialog[open]").forEach((openDialog) => { if (openDialog !== dialog) openDialog.close(); }); if (!dialog.open) dialog.showModal(); } function mount(viewId, mountOptions = {}) { const page = registry.get(viewId); const view = document.getElementById(viewId); if (!page || !view?.classList.contains("workspace-view")) return false; const previousView = state.activeView; options.onBeforeMount?.(viewId, previousView); state.activeView = viewId; document.querySelectorAll(".workspace-view").forEach((candidate) => { const active = candidate.id === viewId; candidate.classList.toggle("active-view", active); candidate.classList.remove("view-entering"); if (active && options.motionEnabled?.()) { void candidate.offsetWidth; candidate.classList.add("view-entering"); candidate.addEventListener( "animationend", () => candidate.classList.remove("view-entering"), { once: true }, ); const body = candidate.querySelector("tbody"); if (body) options.animateRows?.(body); } }); syncNavigation(viewId); setPageStatus(viewId, options.tradeDate?.() || ""); if (mountOptions.updateUrl !== false) { const url = new URL(global.location.href); url.searchParams.set("view", viewId); url.hash = ""; global.history.replaceState(null, "", url); } global.scrollTo({ top: 0, behavior: "auto" }); options.onAfterMount?.(viewId, previousView); return true; } function initialize() { if (initialized) return; initialized = true; let collapsed = false; try { collapsed = global.localStorage.getItem(SIDEBAR_STORAGE_KEY) === "1"; } catch (_error) { collapsed = false; } document.body.classList.toggle("sidebar-collapsed", collapsed); syncViewportMode(); updateSidebarControl(); syncNavigation(state.activeView); document.querySelectorAll(".module-tab").forEach((button) => { button.addEventListener("click", () => options.onNavigate?.(button.dataset.view)); }); document.querySelectorAll("[data-open-view]").forEach((button) => { button.addEventListener("click", () => options.onNavigate?.(button.dataset.openView)); }); document.querySelector("#mobileMarketViewSelect")?.addEventListener("change", (event) => { options.onNavigate?.(event.target.value); }); document.querySelector("#sidebarCollapseButton")?.addEventListener("click", toggleSidebar); document.querySelector("#headerMenuButton")?.addEventListener("click", (event) => { event.stopPropagation(); toggleHeaderCommandMenu(); }); document.querySelector("#mobileCommandBackdrop")?.addEventListener("click", () => { toggleHeaderCommandMenu(false); }); document.querySelectorAll("[data-mobile-command-target]").forEach((button) => { button.addEventListener("click", () => { const target = document.getElementById(button.dataset.mobileCommandTarget || ""); toggleHeaderCommandMenu(false); target?.click(); }); }); document.querySelector("#headerCommandGroup")?.addEventListener("click", (event) => { if (event.target.closest("button") && !event.target.closest(".account-menu-shell")) { toggleHeaderCommandMenu(false); } }); document.querySelector("#overviewToggle")?.addEventListener("click", (event) => { const overview = document.querySelector(".overview-strip"); if (!overview) return; const expanded = overview.dataset.overviewExpanded !== "true"; overview.dataset.overviewExpanded = String(expanded); event.currentTarget.setAttribute("aria-expanded", String(expanded)); event.currentTarget.title = expanded ? "收起市场详情" : "展开市场详情"; const label = event.currentTarget.querySelector("span"); if (label) label.textContent = expanded ? "收起详情" : "展开详情"; event.currentTarget.querySelector("i")?.setAttribute( "data-lucide", expanded ? "chevron-up" : "chevron-down", ); options.refreshIcons?.(); }); document.addEventListener("click", (event) => { if (!event.target.closest(".header-actions")) toggleHeaderCommandMenu(false); }); document.addEventListener("keydown", (event) => { if (event.key === "Escape") toggleHeaderCommandMenu(false); }); global.addEventListener("resize", () => { toggleHeaderCommandMenu(false); syncViewportMode(); updateSidebarControl(); syncNavigation(state.activeView); }); } return Object.freeze({ initialize, mount, openModalDialog, page: (viewId) => registry.get(viewId), setPageStatus, setStatus, syncNavigation, syncViewportMode, toggleHeaderCommandMenu, toggleSidebar, updateSidebarControl, }); } global.XiaobaiShell = Object.freeze({ create }); })(window);