Carry over what only the win7 tree had: middle-click close, ARIA, menu fix
The deleted win7 tree held a few behaviours the modern tree never got: - Context menu: the first ArrowUp in a fresh menu skipped the last item (Delete in the row menu, Disconnect in the tab menu) — stepping from -1 landed one short. The index math moves to core/menu-nav.ts stepIndex(), checked by menu-nav.spec.ts (bun test; verified to fail on the old logic). Items get ids and the menu exposes aria-activedescendant, so screen readers follow the arrow keys while focus stays on the menu. - Middle-click closes a tab, as in browsers. auxclick also fires for the right button, so only button 1 counts; closeWorkspace already refuses the last tab. A middle press is swallowed so WebView2 doesn't start autoscroll over the scrollable strip. The hover ✕ stays. - ARIA: the tab strip is a tablist of role=tab buttons with aria-selected (the in-tab ✕ is taken out of the a11y tree and tab order — Ctrl/Cmd+W and the tab menu remain the keyboard paths); transfer bars are progressbars with value and a name. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,7 @@ const CONCURRENCY_CHOICES = [2, 3, 4, 6, 8] as const;
|
||||
>
|
||||
<div
|
||||
#tabScroll
|
||||
role="tablist"
|
||||
class="flex min-w-0 items-center gap-0.5 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
||||
>
|
||||
@for (ws of panels.workspaces(); track ws.id; let i = $index) {
|
||||
@@ -59,6 +60,8 @@ const CONCURRENCY_CHOICES = [2, 3, 4, 6, 8] as const;
|
||||
[class.opacity-50]="draggedId() === ws.id"
|
||||
[style.box-shadow]="dropIndicatorShadow(i)"
|
||||
(contextmenu)="onTabContextMenu($event, ws, i)"
|
||||
(mousedown)="onTabMouseDown($event)"
|
||||
(auxclick)="onAuxClick($event, ws.id)"
|
||||
draggable="true"
|
||||
(dragstart)="onDragStart($event, ws.id)"
|
||||
(dragover)="onDragOver($event, i)"
|
||||
@@ -66,6 +69,8 @@ const CONCURRENCY_CHOICES = [2, 3, 4, 6, 8] as const;
|
||||
(dragend)="onDragEnd()"
|
||||
>
|
||||
<button
|
||||
role="tab"
|
||||
[attr.aria-selected]="ws.id === panels.activeWorkspaceId()"
|
||||
(click)="panels.setActiveWorkspace(ws.id)"
|
||||
class="flex min-w-0 items-center gap-1.5 py-1.5 pr-1 pl-2.5 text-xs"
|
||||
[class.text-foreground]="ws.id === panels.activeWorkspaceId()"
|
||||
@@ -83,6 +88,8 @@ const CONCURRENCY_CHOICES = [2, 3, 4, 6, 8] as const;
|
||||
@if (panels.workspaces().length > 1) {
|
||||
<button
|
||||
(click)="close($event, ws.id)"
|
||||
tabindex="-1"
|
||||
aria-hidden="true"
|
||||
[title]="i18n.t('tabBar.closeTab', { mod: modLabel })"
|
||||
class="mr-1 grid size-4 shrink-0 place-items-center rounded text-muted-foreground opacity-0 group-hover:opacity-100 hover:bg-border hover:text-foreground"
|
||||
>
|
||||
@@ -484,6 +491,22 @@ export class TabBar {
|
||||
this.panels.closeWorkspace(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Middle-click closes the tab, as in browsers. `auxclick` also fires for the
|
||||
* right button, hence the button check; the last tab is refused by
|
||||
* closeWorkspace itself.
|
||||
*/
|
||||
onAuxClick(event: MouseEvent, id: number): void {
|
||||
if (event.button !== 1) return;
|
||||
event.preventDefault();
|
||||
this.close(event, id);
|
||||
}
|
||||
|
||||
/** A middle press over the scrollable strip would start WebView2's autoscroll. */
|
||||
onTabMouseDown(event: MouseEvent): void {
|
||||
if (event.button === 1) event.preventDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* Right-click a tab. The menu targets THIS tab, not the active one — its
|
||||
* actions all carry the id — so right-clicking a background tab does not
|
||||
|
||||
Reference in New Issue
Block a user