fix(electrobun): rewrite terminal collapse — blur fix, tab bar as bottom divider
This commit is contained in:
parent
9edece0dc7
commit
e8132b7dc6
5 changed files with 108 additions and 160 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -4,8 +4,8 @@
|
|||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Svelte App</title>
|
||||
<script type="module" crossorigin src="/assets/index-B4yMIAeH.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-D6jrqWO6.css">
|
||||
<script type="module" crossorigin src="/assets/index-U683DxRe.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-RZPm-TN9.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
|
|
|||
|
|
@ -13,21 +13,25 @@
|
|||
title: string;
|
||||
}
|
||||
|
||||
// Capture stable initial value — projectId is a mount-time constant, not reactive
|
||||
// svelte-ignore state_referenced_locally
|
||||
const initialId = projectId;
|
||||
const firstTabId = `${initialId}-t1`;
|
||||
|
||||
let tabs = $state<TermTab[]>([{ id: firstTabId, title: 'shell 1' }]);
|
||||
let activeTabId = $state<string>(firstTabId);
|
||||
let collapsed = $state(false);
|
||||
let expanded = $state(true);
|
||||
let counter = $state(2);
|
||||
// Track which tabs have been mounted at least once (for lazy init)
|
||||
let mounted = $state<Set<string>>(new Set([firstTabId]));
|
||||
|
||||
function addTab(e?: MouseEvent) {
|
||||
// Blur terminal canvas so future clicks on tab bar work
|
||||
if (e) (e.target as HTMLElement)?.focus();
|
||||
function blurTerminal() {
|
||||
// Force-blur xterm canvas so UI buttons become clickable
|
||||
if (document.activeElement instanceof HTMLElement) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
}
|
||||
|
||||
function addTab() {
|
||||
blurTerminal();
|
||||
const id = `${projectId}-t${counter}`;
|
||||
tabs = [...tabs, { id, title: `shell ${counter}` }];
|
||||
counter++;
|
||||
|
|
@ -37,6 +41,7 @@
|
|||
|
||||
function closeTab(id: string, e: MouseEvent) {
|
||||
e.stopPropagation();
|
||||
blurTerminal();
|
||||
const idx = tabs.findIndex(t => t.id === id);
|
||||
tabs = tabs.filter(t => t.id !== id);
|
||||
if (activeTabId === id) {
|
||||
|
|
@ -49,43 +54,42 @@
|
|||
}
|
||||
|
||||
function activateTab(id: string) {
|
||||
blurTerminal();
|
||||
activeTabId = id;
|
||||
if (!mounted.has(id)) {
|
||||
mounted = new Set([...mounted, id]);
|
||||
}
|
||||
if (!mounted.has(id)) mounted = new Set([...mounted, id]);
|
||||
if (!expanded) expanded = true;
|
||||
}
|
||||
|
||||
function toggleCollapse() {
|
||||
collapsed = !collapsed;
|
||||
function toggleExpand() {
|
||||
blurTerminal();
|
||||
expanded = !expanded;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="terminal-section" class:collapsed style="--accent: {accent}">
|
||||
<!-- Tab bar always visible — between agent pane and terminal content -->
|
||||
<div class="term-section-header">
|
||||
<!-- Wrapper: uses flex to push tab bar to bottom when terminal is collapsed -->
|
||||
<div class="term-wrapper" style="--accent: {accent}">
|
||||
<!-- Tab bar: always visible, acts as divider -->
|
||||
<div class="term-bar" onmousedown={blurTerminal}>
|
||||
<button
|
||||
class="collapse-btn"
|
||||
onclick={toggleCollapse}
|
||||
aria-label={collapsed ? 'Expand terminals' : 'Collapse terminals'}
|
||||
aria-expanded={!collapsed}
|
||||
title={collapsed ? 'Expand' : 'Collapse'}
|
||||
class="expand-btn"
|
||||
onclick={toggleExpand}
|
||||
title={expanded ? 'Collapse terminal' : 'Expand terminal'}
|
||||
>
|
||||
<svg
|
||||
class="chevron"
|
||||
class:rotated={collapsed}
|
||||
class:open={expanded}
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<polyline points="6 9 12 15 18 9"/>
|
||||
<polyline points="9 6 15 12 9 18"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="term-tabs" role="tablist" aria-label="Terminal tabs">
|
||||
<div class="term-tabs" role="tablist">
|
||||
{#each tabs as tab (tab.id)}
|
||||
<button
|
||||
class="term-tab"
|
||||
|
|
@ -93,42 +97,23 @@
|
|||
role="tab"
|
||||
aria-selected={activeTabId === tab.id}
|
||||
onclick={() => activateTab(tab.id)}
|
||||
title={tab.title}
|
||||
>
|
||||
<span class="term-tab-title">{tab.title}</span>
|
||||
<span class="tab-label">{tab.title}</span>
|
||||
{#if tabs.length > 1}
|
||||
<span
|
||||
class="term-tab-close"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Close {tab.title}"
|
||||
onclick={(e) => closeTab(tab.id, e)}
|
||||
onkeydown={(e) => e.key === 'Enter' && closeTab(tab.id, e as unknown as MouseEvent)}
|
||||
>×</span>
|
||||
<span class="tab-close" onclick={(e) => closeTab(tab.id, e)}>×</span>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
<button
|
||||
class="term-tab-add"
|
||||
onclick={addTab}
|
||||
aria-label="Add terminal tab"
|
||||
title="New terminal"
|
||||
>+</button>
|
||||
<button class="tab-add" onclick={() => addTab()}>+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Terminal panes — hidden when collapsed, agent pane fills freed space -->
|
||||
{#if !collapsed}
|
||||
<!-- Terminal panes: only rendered when expanded -->
|
||||
{#if expanded}
|
||||
<div class="term-panes">
|
||||
{#each tabs as tab (tab.id)}
|
||||
{#if mounted.has(tab.id)}
|
||||
<div
|
||||
class="term-pane"
|
||||
style:display={activeTabId === tab.id ? 'flex' : 'none'}
|
||||
role="tabpanel"
|
||||
aria-label={tab.title}
|
||||
>
|
||||
<div class="term-pane" style:display={activeTabId === tab.id ? 'flex' : 'none'}>
|
||||
<Terminal />
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -138,33 +123,25 @@
|
|||
</div>
|
||||
|
||||
<style>
|
||||
.terminal-section {
|
||||
.term-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-top: 1px solid var(--ctp-surface0);
|
||||
flex-shrink: 0;
|
||||
background: var(--ctp-crust);
|
||||
}
|
||||
|
||||
/* When collapsed: no terminal height, just the tab bar */
|
||||
.terminal-section.collapsed {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
/* Section header: collapse + tabs in one row */
|
||||
.term-section-header {
|
||||
/* Tab bar */
|
||||
.term-bar {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
height: 1.875rem;
|
||||
height: 1.75rem;
|
||||
background: var(--ctp-mantle);
|
||||
border-bottom: 1px solid var(--ctp-surface0);
|
||||
border-top: 1px solid var(--ctp-surface0);
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.collapse-btn {
|
||||
width: 2rem;
|
||||
.expand-btn {
|
||||
width: 1.75rem;
|
||||
flex-shrink: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
|
|
@ -174,23 +151,13 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: color 0.12s;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.collapse-btn:hover { color: var(--ctp-text); }
|
||||
.expand-btn:hover { color: var(--ctp-text); }
|
||||
.expand-btn svg { width: 0.75rem; height: 0.75rem; transition: transform 0.15s; }
|
||||
.chevron.open { transform: rotate(90deg); }
|
||||
|
||||
.collapse-btn svg {
|
||||
width: 0.875rem;
|
||||
height: 0.875rem;
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
|
||||
.chevron.rotated {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
/* Tab pills */
|
||||
.term-tabs {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
|
@ -200,7 +167,6 @@
|
|||
gap: 0.125rem;
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
.term-tabs::-webkit-scrollbar { display: none; }
|
||||
|
||||
.term-tab {
|
||||
|
|
@ -212,68 +178,50 @@
|
|||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
color: var(--ctp-subtext0);
|
||||
font-family: var(--ui-font-family);
|
||||
font-size: 0.75rem;
|
||||
font-size: 0.6875rem;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: color 0.1s, border-color 0.1s;
|
||||
margin-bottom: -1px;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.term-tab:hover { color: var(--ctp-text); }
|
||||
.term-tab.active { color: var(--ctp-text); border-bottom-color: var(--accent); }
|
||||
.tab-label { pointer-events: none; }
|
||||
|
||||
.term-tab.active {
|
||||
color: var(--ctp-text);
|
||||
border-bottom-color: var(--accent, var(--ctp-mauve));
|
||||
}
|
||||
|
||||
.term-tab-title { pointer-events: none; }
|
||||
|
||||
.term-tab-close {
|
||||
.tab-close {
|
||||
width: 0.875rem;
|
||||
height: 0.875rem;
|
||||
border-radius: 0.2rem;
|
||||
font-size: 0.6875rem;
|
||||
color: var(--ctp-overlay0);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-radius: 0.2rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--ctp-overlay0);
|
||||
transition: background 0.1s, color 0.1s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.tab-close:hover { background: var(--ctp-surface1); color: var(--ctp-red); }
|
||||
|
||||
.term-tab-close:hover {
|
||||
background: var(--ctp-surface1);
|
||||
color: var(--ctp-red);
|
||||
}
|
||||
|
||||
.term-tab-add {
|
||||
.tab-add {
|
||||
align-self: center;
|
||||
width: 1.375rem;
|
||||
height: 1.375rem;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
flex-shrink: 0;
|
||||
background: transparent;
|
||||
border: 1px solid var(--ctp-surface1);
|
||||
border-radius: 0.25rem;
|
||||
border-radius: 0.2rem;
|
||||
color: var(--ctp-overlay1);
|
||||
font-size: 0.875rem;
|
||||
font-size: 0.75rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.1s, color 0.1s;
|
||||
margin-left: 0.125rem;
|
||||
}
|
||||
.tab-add:hover { background: var(--ctp-surface0); color: var(--ctp-text); }
|
||||
|
||||
.term-tab-add:hover {
|
||||
background: var(--ctp-surface0);
|
||||
color: var(--ctp-text);
|
||||
}
|
||||
|
||||
/* Terminal pane container */
|
||||
/* Terminal panes */
|
||||
.term-panes {
|
||||
height: 12rem;
|
||||
min-height: 8rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue