diff --git a/frontend/index.html b/frontend/index.html index 0ee690b..07b51c5 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -293,8 +293,9 @@ class="dock-tab" type="button" role="tab" - aria-selected="true" + aria-selected="false" aria-controls="file-manager-panel" + aria-expanded="false" tabindex="0" data-i18n-zh="文件管理" data-i18n-en="Files" @@ -314,7 +315,7 @@ - + 当前路径 diff --git a/frontend/src/main.ts b/frontend/src/main.ts index e181f58..e044422 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -1211,21 +1211,30 @@ function sendHostKeyDecision(accept: boolean): void { } type WorkspaceTab = 'files' | 'log'; +let activeWorkspaceTab: WorkspaceTab | null = null; -function setWorkspaceTab(tab: WorkspaceTab, focus = false): void { +function setWorkspaceTab(tab: WorkspaceTab | null, focus = false, rovingTab = tab ?? activeWorkspaceTab ?? 'files'): void { const filesActive = tab === 'files'; + const logActive = tab === 'log'; + activeWorkspaceTab = tab; + ui.terminalCard.classList.toggle('workspace-panel-open', tab !== null); ui.fileManagerPanel.hidden = !filesActive; - ui.eventLog.hidden = filesActive; + ui.eventLog.hidden = !logActive; ui.fileManagerTab.setAttribute('aria-selected', String(filesActive)); - ui.eventToggle.setAttribute('aria-selected', String(!filesActive)); - ui.eventToggle.setAttribute('aria-expanded', String(!filesActive)); - ui.fileManagerTab.tabIndex = filesActive ? 0 : -1; - ui.eventToggle.tabIndex = filesActive ? -1 : 0; - if (!filesActive) requestAnimationFrame(() => { ui.eventLog.scrollTop = ui.eventLog.scrollHeight; }); - if (focus) (filesActive ? ui.fileManagerTab : ui.eventToggle).focus(); + ui.eventToggle.setAttribute('aria-selected', String(logActive)); + ui.fileManagerTab.setAttribute('aria-expanded', String(filesActive)); + ui.eventToggle.setAttribute('aria-expanded', String(logActive)); + ui.fileManagerTab.tabIndex = rovingTab === 'files' ? 0 : -1; + ui.eventToggle.tabIndex = rovingTab === 'log' ? 0 : -1; + if (logActive) requestAnimationFrame(() => { ui.eventLog.scrollTop = ui.eventLog.scrollHeight; }); + if (focus) (rovingTab === 'files' ? ui.fileManagerTab : ui.eventToggle).focus(); requestAnimationFrame(() => fitTerminal(true)); } +function toggleWorkspaceTab(tab: WorkspaceTab): void { + setWorkspaceTab(activeWorkspaceTab === tab ? null : tab, false, tab); +} + function handleWorkspaceTabKey(event: KeyboardEvent): void { if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return; event.preventDefault(); @@ -1847,8 +1856,8 @@ ui.fullscreenTerminal.addEventListener('click', async () => { else await ui.terminalCard.requestFullscreen(); }); document.addEventListener('fullscreenchange', () => fitTerminal(true)); -ui.fileManagerTab.addEventListener('click', () => setWorkspaceTab('files')); -ui.eventToggle.addEventListener('click', () => setWorkspaceTab('log')); +ui.fileManagerTab.addEventListener('click', () => toggleWorkspaceTab('files')); +ui.eventToggle.addEventListener('click', () => toggleWorkspaceTab('log')); ui.fileManagerTab.addEventListener('keydown', handleWorkspaceTabKey); ui.eventToggle.addEventListener('keydown', handleWorkspaceTabKey); ui.languageToggle.addEventListener('click', () => { @@ -1895,7 +1904,7 @@ async function initialize(): Promise { setPanelOpen(panelOpen); setAuthMethod('password'); setState('idle'); - setWorkspaceTab('files'); + setWorkspaceTab(null); ui.sessionTitle.textContent = bilingual('无活动会话', 'No active session'); ui.sessionSubtitle.textContent = bilingual('选择目标并连接', 'Choose a target and connect'); ui.eventMessage.textContent = bilingual('Worker 运行时待命', 'Worker runtime standing by'); @@ -1916,6 +1925,6 @@ void initialize().catch(() => { setPanelOpen(panelOpen); setAuthMethod('password'); setState('idle'); - setWorkspaceTab('files'); + setWorkspaceTab(null); initializeCompatibilityAPI(); }); diff --git a/frontend/src/style.css b/frontend/src/style.css index 35f71c3..df8b42f 100644 --- a/frontend/src/style.css +++ b/frontend/src/style.css @@ -953,12 +953,17 @@ button:disabled { display: grid; min-width: 0; min-height: 0; - grid-template-rows: minmax(0, 2fr) minmax(0, 1fr); + grid-template-rows: minmax(0, 1fr) minmax(34px, 0fr); overflow: hidden; border: 1px solid var(--border); border-radius: 12px; background: var(--terminal); box-shadow: var(--shadow); + transition: grid-template-rows 220ms cubic-bezier(0.2, 0.75, 0.2, 1); +} + +.terminal-card.workspace-panel-open { + grid-template-rows: minmax(0, 2fr) minmax(34px, 1fr); } .terminal-pane {