feat(branch-panel): improve context menu positioning and styling

This update enhances the context menu for branches and tags by
ensuring it fits within the viewport, preventing overflow and
improving usability. Additionally, the styling of the branch
context menu has been adjusted for better visibility and
interaction.

- Added dynamic positioning for context menus to avoid overflow
- Updated CSS for branch context menu to improve layout and usability
- Simplified context menu opening logic for better performance
This commit is contained in:
2026-08-15 17:15:13 +02:00
parent 2bc74dc4a7
commit 37d2152fcd
2 changed files with 40 additions and 21 deletions
+5 -1
View File
@@ -2212,7 +2212,11 @@
box-shadow: 0 18px 50px rgba(0,0,0,0.5); box-shadow: 0 18px 50px rgba(0,0,0,0.5);
} }
.branch-context-menu, .branch-context-menu {
position: fixed;
max-height: calc(100vh - 16px);
overflow-y: auto;
}
.history-context-menu { position: absolute; } .history-context-menu { position: absolute; }
.explorer-context-menu, .explorer-context-menu,
.repo-tab-context-menu { position: fixed; } .repo-tab-context-menu { position: fixed; }
+35 -20
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { Check, ChevronDown, ChevronRight, Folder, FolderOpen, GitBranch, GitCompare, GitMerge, HardDrive, Pencil, Plus, Tag as TagIcon, Trash2, Upload, X } from "@lucide/svelte"; import { Check, ChevronDown, ChevronRight, Folder, FolderOpen, GitBranch, GitCompare, GitMerge, HardDrive, Pencil, Plus, Tag as TagIcon, Trash2, Upload, X } from "@lucide/svelte";
import { tick } from "svelte";
import type { GitBranch as GitBranchInfo, GitTag } from "../types"; import type { GitBranch as GitBranchInfo, GitTag } from "../types";
type BranchTreeNode = BranchFolderNode | BranchLeafNode; type BranchTreeNode = BranchFolderNode | BranchLeafNode;
@@ -99,11 +100,12 @@
let newTagName = $state(""); let newTagName = $state("");
let newTagMessage = $state(""); let newTagMessage = $state("");
let tagCreateInput = $state<HTMLInputElement | null>(null); let tagCreateInput = $state<HTMLInputElement | null>(null);
let panelElement = $state<HTMLElement | null>(null);
let contextBranch = $state<GitBranchInfo | null>(null); let contextBranch = $state<GitBranchInfo | null>(null);
let branchContextMenuElement = $state<HTMLElement | null>(null);
let contextMenuX = $state(0); let contextMenuX = $state(0);
let contextMenuY = $state(0); let contextMenuY = $state(0);
let contextTag = $state<GitTag | null>(null); let contextTag = $state<GitTag | null>(null);
let tagContextMenuElement = $state<HTMLElement | null>(null);
let tagContextMenuX = $state(0); let tagContextMenuX = $state(0);
let tagContextMenuY = $state(0); let tagContextMenuY = $state(0);
let collapsedBranchFolders = $state<Set<string>>(new Set()); let collapsedBranchFolders = $state<Set<string>>(new Set());
@@ -243,20 +245,31 @@
onCheckout(branch); onCheckout(branch);
} }
function openBranchContextMenu(event: MouseEvent, branch: GitBranchInfo) { function fitContextMenuToViewport(element: HTMLElement | null, x: number, y: number) {
const rect = element?.getBoundingClientRect();
const width = rect?.width ?? 184;
const height = rect?.height ?? 0;
return {
x: Math.max(8, Math.min(x + 2, window.innerWidth - width - 8)),
y: Math.max(8, Math.min(y + 2, window.innerHeight - height - 8)),
};
}
async function openBranchContextMenu(event: MouseEvent, branch: GitBranchInfo) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
if (isBusy) return; if (isBusy) return;
const rect = panelElement?.getBoundingClientRect();
const rawX = rect ? event.clientX - rect.left : event.offsetX;
const rawY = rect ? event.clientY - rect.top : event.offsetY;
const maxX = Math.max(8, (rect?.width ?? window.innerWidth) - 192);
const maxY = Math.max(8, (rect?.height ?? window.innerHeight) - 226);
contextBranch = branch; contextBranch = branch;
contextMenuX = Math.max(8, Math.min(rawX, maxX)); contextMenuX = event.clientX + 2;
contextMenuY = Math.max(8, Math.min(rawY, maxY)); contextMenuY = event.clientY + 2;
await tick();
if (contextBranch !== branch) return;
const position = fitContextMenuToViewport(branchContextMenuElement, event.clientX, event.clientY);
contextMenuX = position.x;
contextMenuY = position.y;
} }
function closeBranchContextMenu() { function closeBranchContextMenu() {
@@ -335,20 +348,20 @@
tagsOpen = true; tagsOpen = true;
} }
function openTagContextMenu(event: MouseEvent, tag: GitTag) { async function openTagContextMenu(event: MouseEvent, tag: GitTag) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
if (isBusy) return; if (isBusy) return;
const rect = panelElement?.getBoundingClientRect();
const rawX = rect ? event.clientX - rect.left : event.offsetX;
const rawY = rect ? event.clientY - rect.top : event.offsetY;
const maxX = Math.max(8, (rect?.width ?? window.innerWidth) - 192);
const maxY = Math.max(8, (rect?.height ?? window.innerHeight) - 130);
contextTag = tag; contextTag = tag;
tagContextMenuX = Math.max(8, Math.min(rawX, maxX)); tagContextMenuX = event.clientX + 2;
tagContextMenuY = Math.max(8, Math.min(rawY, maxY)); tagContextMenuY = event.clientY + 2;
await tick();
if (contextTag !== tag) return;
const position = fitContextMenuToViewport(tagContextMenuElement, event.clientX, event.clientY);
tagContextMenuX = position.x;
tagContextMenuY = position.y;
} }
function closeTagContextMenu() { function closeTagContextMenu() {
@@ -381,7 +394,7 @@
<svelte:window on:click={closeAllContextMenus} on:keydown={handleWindowKeydown} on:contextmenu|capture={closeAllContextMenus} /> <svelte:window on:click={closeAllContextMenus} on:keydown={handleWindowKeydown} on:contextmenu|capture={closeAllContextMenus} />
<section bind:this={panelElement} class="panel branch-panel grid grid-rows-[auto_1fr] overflow-hidden" class:collapsed aria-label="Branches"> <section class="panel branch-panel grid grid-rows-[auto_1fr] overflow-hidden" class:collapsed aria-label="Branches">
<div class="section-head"> <div class="section-head">
<div> <div>
<span class="eyebrow">Branches</span> <span class="eyebrow">Branches</span>
@@ -676,6 +689,7 @@
{#if contextBranch} {#if contextBranch}
<div <div
bind:this={branchContextMenuElement}
class="branch-context-menu" class="branch-context-menu"
style={`left: ${contextMenuX}px; top: ${contextMenuY}px;`} style={`left: ${contextMenuX}px; top: ${contextMenuY}px;`}
role="menu" role="menu"
@@ -723,6 +737,7 @@
{#if contextTag} {#if contextTag}
<div <div
bind:this={tagContextMenuElement}
class="branch-context-menu" class="branch-context-menu"
style={`left: ${tagContextMenuX}px; top: ${tagContextMenuY}px;`} style={`left: ${tagContextMenuX}px; top: ${tagContextMenuY}px;`}
role="menu" role="menu"