feat: 为ssh标签栏和文件编辑器标签栏添加右键菜单

This commit is contained in:
Baobhan Sith
2025-05-03 19:43:50 +08:00
parent 23a470a6a0
commit 88ad7332a3
11 changed files with 620 additions and 47 deletions
@@ -34,6 +34,10 @@ const emit = defineEmits<{
(e: 'request-save', tabId: string): void; // 发送保存请求,携带 tabId
(e: 'update:content', payload: { tabId: string; content: string }): void; // 用于 v-model 同步
(e: 'change-encoding', payload: { tabId: string; encoding: string }): void; // +++ 新增:编码更改事件 +++
// +++ 新增:传递右键菜单关闭事件 +++
(e: 'close-other-tabs', tabId: string): void;
(e: 'close-tabs-to-right', tabId: string): void;
(e: 'close-tabs-to-left', tabId: string): void;
}>();
@@ -228,8 +232,11 @@ const handleKeyDown = (event: KeyboardEvent) => {
<FileEditorTabs
:tabs="orderedTabs"
:active-tab-id="props.activeTabId"
@activate-tab="(tabId: string) => emit('activate-tab', tabId)"
@close-tab="(tabId: string) => emit('close-tab', tabId)"
@activate-tab="(tabId: string) => emit('activate-tab', tabId)"
@close-tab="(tabId: string) => emit('close-tab', tabId)"
@close-other-tabs="(tabId: string) => emit('close-other-tabs', tabId)"
@close-tabs-to-right="(tabId: string) => emit('close-tabs-to-right', tabId)"
@close-tabs-to-left="(tabId: string) => emit('close-tabs-to-left', tabId)"
/>
<!-- 2. 编辑器头部 (显示当前激活标签信息) -->
@@ -23,7 +23,7 @@ const {
popupTrigger,
popupFileInfo, // 包含 sessionId 和 filePath
activeTabId: globalActiveTabIdRef, // 获取全局 activeTabId
tabs: globalTabsRef, // 获取全局 tabs Map
// tabs: globalTabsRef, // 不再使用 storeToRefs 获取 tabs
} = storeToRefs(fileEditorStore);
// 设置 Store (用于判断模式)
@@ -32,18 +32,26 @@ const { showPopupFileEditorBoolean, shareFileEditorTabsBoolean } = storeToRefs(s
// --- 从 Store 获取方法 ---
// 全局 Store Actions (用于共享模式)
const {
saveFile: saveGlobalFile,
closeTab: closeGlobalTab,
setActiveTab: setGlobalActiveTab,
updateFileContent: updateGlobalFileContent,
saveFile: saveGlobalFile,
closeTab: closeGlobalTab,
setActiveTab: setGlobalActiveTab,
updateFileContent: updateGlobalFileContent,
// + 添加右键菜单操作 actions
closeOtherTabs, // 修正:移除 Global 后缀
closeTabsToTheRight, // 修正:移除 Global 后缀
closeTabsToTheLeft, // 修正:移除 Global 后缀
} = fileEditorStore;
// 会话 Store Actions (用于非共享模式)
const {
saveFileInSession,
closeEditorTabInSession,
setActiveEditorTabInSession,
updateFileContentInSession,
saveFileInSession,
closeEditorTabInSession,
setActiveEditorTabInSession,
updateFileContentInSession,
// + 添加右键菜单操作 actions
closeOtherTabsInSession,
closeTabsToTheRightInSession,
closeTabsToTheLeftInSession,
} = sessionStore;
// --- 移除本地文件状态 ---
@@ -89,10 +97,12 @@ const currentSession = computed(() => {
// 获取当前模式下的标签页列表
const orderedTabs = computed(() => {
// 直接访问 store.tabs
if (shareFileEditorTabsBoolean.value) {
return Array.from(globalTabsRef.value.values()); // 全局 Store
return Array.from(fileEditorStore.tabs.values()); // 直接访问 store
} else {
return currentSession.value?.editorTabs.value ?? []; // 会话 Store
// 非共享模式保持不变,因为它依赖 sessionStore
return currentSession.value?.editorTabs.value ?? [];
}
});
@@ -110,10 +120,11 @@ const activeTab = computed((): FileTab | null => {
const currentId = activeTabId.value;
if (!currentId) return null;
// 直接访问 store.tabs
if (shareFileEditorTabsBoolean.value) {
return globalTabsRef.value.get(currentId) ?? null; // 全局 Store
return fileEditorStore.tabs.get(currentId) ?? null; // 直接访问 store
} else {
// 在会话的 editorTabs 数组中查找
// 非共享模式保持不变
return currentSession.value?.editorTabs.value.find(tab => tab.id === currentId) ?? null;
}
});
@@ -197,6 +208,48 @@ const handleCloseTab = (tabId: string) => {
}
};
// +++ 处理右键菜单事件 +++
const handleCloseOtherTabs = (targetTabId: string) => {
console.log(`[FileEditorOverlay] handleCloseOtherTabs called for target: ${targetTabId}`); // Add log
if (shareFileEditorTabsBoolean.value) {
closeOtherTabs(targetTabId); // 修正:调用正确的 action 名称
} else {
const sessionId = popupFileInfo.value?.sessionId;
if (sessionId) {
closeOtherTabsInSession(sessionId, targetTabId); // 会话 Store
} else {
console.error("[FileEditorOverlay] 无法关闭其他标签页:非共享模式下缺少 sessionId。");
}
}
};
const handleCloseRightTabs = (targetTabId: string) => {
console.log(`[FileEditorOverlay] handleCloseRightTabs called for target: ${targetTabId}`); // Add log
if (shareFileEditorTabsBoolean.value) {
closeTabsToTheRight(targetTabId); // 修正:调用正确的 action 名称
} else {
const sessionId = popupFileInfo.value?.sessionId;
if (sessionId) {
closeTabsToTheRightInSession(sessionId, targetTabId); // 会话 Store
} else {
console.error("[FileEditorOverlay] 无法关闭右侧标签页:非共享模式下缺少 sessionId。");
}
}
};
const handleCloseLeftTabs = (targetTabId: string) => {
console.log(`[FileEditorOverlay] handleCloseLeftTabs called for target: ${targetTabId}`); // Add log
if (shareFileEditorTabsBoolean.value) {
closeTabsToTheLeft(targetTabId); // 修正:调用正确的 action 名称
} else {
const sessionId = popupFileInfo.value?.sessionId;
if (sessionId) {
closeTabsToTheLeftInSession(sessionId, targetTabId); // 会话 Store
} else {
console.error("[FileEditorOverlay] 无法关闭左侧标签页:非共享模式下缺少 sessionId。");
}
}
};
// 关闭弹窗 (保持不变)
const handleCloseContainer = () => {
@@ -291,6 +344,9 @@ onBeforeUnmount(() => {
:active-tab-id="activeTabId"
@activate-tab="handleActivateTab"
@close-tab="handleCloseTab"
@close-other-tabs="handleCloseOtherTabs"
@close-tabs-to-right="handleCloseRightTabs"
@close-tabs-to-left="handleCloseLeftTabs"
/>
<!-- 编辑器头部 (使用动态计算属性) -->
@@ -1,9 +1,10 @@
<script setup lang="ts">
import type { PropType } from 'vue';
import { ref, computed, type PropType, onBeforeUnmount } from 'vue'; // + ref, computed, onBeforeUnmount
import { useI18n } from 'vue-i18n';
import type { FileTab } from '../stores/fileEditor.store';
import TabBarContextMenu from './TabBarContextMenu.vue'; // + Import context menu
defineProps({
const props = defineProps({
tabs: {
type: Array as PropType<FileTab[]>,
required: true,
@@ -17,10 +18,20 @@ defineProps({
const emit = defineEmits<{
(e: 'activate-tab', tabId: string): void;
(e: 'close-tab', tabId: string): void;
// + 新增右键菜单事件
(e: 'close-other-tabs', tabId: string): void;
(e: 'close-tabs-to-right', tabId: string): void;
(e: 'close-tabs-to-left', tabId: string): void;
}>();
const { t } = useI18n();
// +++ 右键菜单状态 +++
const contextMenuVisible = ref(false);
const contextMenuPosition = ref({ x: 0, y: 0 });
const contextTargetTabId = ref<string | null>(null); // Keep for logic inside this component if needed elsewhere
const menuTargetId = ref<string | null>(null); // + Ref specifically for passing to the menu prop
const handleActivate = (tabId: string) => {
emit('activate-tab', tabId);
};
@@ -29,6 +40,94 @@ const handleClose = (event: MouseEvent, tabId: string) => {
event.stopPropagation(); // 防止触发 activateTab
emit('close-tab', tabId);
};
// +++ 右键菜单方法 +++
const showContextMenu = (event: MouseEvent, tabId: string) => {
event.preventDefault();
event.stopPropagation();
console.log(`[FileTabs] showContextMenu called with tabId: ${tabId}`); // ++ Log the received tabId
contextTargetTabId.value = tabId; // Still set the original ref if needed elsewhere
menuTargetId.value = tabId; // + Set the dedicated ref for the prop
contextMenuPosition.value = { x: event.clientX, y: event.clientY };
contextMenuVisible.value = true;
// 添加全局监听器以关闭菜单
document.addEventListener('click', closeContextMenuOnClickOutside, { capture: true, once: true });
};
const closeContextMenu = () => {
contextMenuVisible.value = false;
contextTargetTabId.value = null; // Clear original ref if needed
// menuTargetId.value = null; // -- REMOVE THIS LINE -- Let the value persist until next show
// 移除监听器(如果它仍然存在)
document.removeEventListener('click', closeContextMenuOnClickOutside, { capture: true });
};
// 用于全局点击监听器的函数
const closeContextMenuOnClickOutside = (event: MouseEvent) => {
closeContextMenu();
};
// + Update function signature to receive payload
const handleContextMenuAction = (payload: { action: string; targetId: string | number | null }) => {
const { action, targetId } = payload;
console.log(`[FileTabs] handleContextMenuAction received payload:`, JSON.stringify(payload)); // + Log received payload
// const targetId = contextTargetTabId.value; // No longer needed
if (!targetId || typeof targetId !== 'string') { // Ensure targetId is a string (tab ID)
console.warn('[FileTabs] handleContextMenuAction called but targetId is null or not a string.');
return;
}
console.log(`[FileTabs] Context menu action '${action}' requested for tab ID: ${targetId}`); // Keep original log
switch (action) {
case 'close':
emit('close-tab', targetId);
break;
case 'close-others':
emit('close-other-tabs', targetId);
break;
case 'close-right':
emit('close-tabs-to-right', targetId);
break;
case 'close-left':
emit('close-tabs-to-left', targetId);
break;
default:
console.warn(`[FileTabs] Unknown context menu action: ${action}`);
}
};
// 计算右键菜单项
const contextMenuItems = computed(() => {
const items = [];
const targetId = contextTargetTabId.value;
if (!targetId) return [];
const currentIndex = props.tabs.findIndex(t => t.id === targetId);
const totalTabs = props.tabs.length;
items.push({ label: 'tabs.contextMenu.close', action: 'close' });
if (totalTabs > 1) {
items.push({ label: 'tabs.contextMenu.closeOthers', action: 'close-others' });
}
if (currentIndex < totalTabs - 1) {
items.push({ label: 'tabs.contextMenu.closeRight', action: 'close-right' });
}
if (currentIndex > 0) {
items.push({ label: 'tabs.contextMenu.closeLeft', action: 'close-left' });
}
return items;
});
// +++ 组件卸载前移除全局监听器 +++
onBeforeUnmount(() => {
document.removeEventListener('click', closeContextMenuOnClickOutside, { capture: true });
});
</script>
<template>
@@ -36,9 +135,11 @@ const handleClose = (event: MouseEvent, tabId: string) => {
<div
v-for="tab in tabs"
:key="tab.id"
:data-tab-id-debug="tab.id"
class="tab-item"
:class="{ active: tab.id === activeTabId }"
@click="handleActivate(tab.id)"
@contextmenu.prevent="(event) => { console.log(`[FileTabs Template Debug] Context menu for tab.id: ${tab.id}`); showContextMenu(event, tab.id); }"
:title="tab.filePath"
>
<span class="tab-filename">{{ tab.filename }}</span>
@@ -54,6 +155,15 @@ const handleClose = (event: MouseEvent, tabId: string) => {
<div v-if="tabs.length === 0" class="no-tabs-placeholder">
<!-- 可以留空或添加提示 -->
</div>
<!-- +++ Context Menu Instance +++ -->
<TabBarContextMenu
:visible="contextMenuVisible"
:position="contextMenuPosition"
:items="contextMenuItems"
:target-id="menuTargetId"
@menu-action="handleContextMenuAction"
@close="closeContextMenu"
/>
</div>
</template>
@@ -66,9 +66,13 @@ const emit = defineEmits({
'close-search': null, // ()
'clear-terminal': null, // () +++ 添加 clear-terminal 事件 +++
'change-encoding': null, // +++ 添加 change-encoding 事件 +++
// +++ 添加文件编辑器标签页关闭事件 +++
'close-other-tabs': null, // (tabId: string)
'close-tabs-to-right': null, // (tabId: string)
'close-tabs-to-left': null, // (tabId: string)
// --- 移除 RDP 事件 ---
});
// --- Setup ---
const layoutStore = useLayoutStore();
const sessionStore = useSessionStore();
@@ -206,6 +210,10 @@ const componentProps = computed(() => {
onRequestSave: (tabId: string) => emit('saveEditorTab', tabId),
// +++ 添加:转发 change-encoding 事件 +++
onChangeEncoding: (payload: { tabId: string; encoding: string }) => emit('change-encoding', payload),
// +++ 添加:转发其他关闭事件 +++
onCloseOtherTabs: (tabId: string) => emit('close-other-tabs', tabId),
onCloseTabsToRight: (tabId: string) => emit('close-tabs-to-right', tabId),
onCloseTabsToLeft: (tabId: string) => emit('close-tabs-to-left', tabId),
};
case 'commandBar':
// CommandInputBar 需要转发 send-command 事件
@@ -514,6 +522,9 @@ onMounted(() => {
@close-search="emit('close-search')"
@clear-terminal="() => emit('clear-terminal')"
@change-encoding="emit('change-encoding', $event)"
@close-other-tabs="emit('close-other-tabs', $event)"
@close-tabs-to-right="emit('close-tabs-to-right', $event)"
@close-tabs-to-left="emit('close-tabs-to-left', $event)"
class="flex-grow overflow-auto"
/>
</pane>
@@ -0,0 +1,103 @@
<script setup lang="ts">
import { computed, PropType } from 'vue';
import { useI18n } from 'vue-i18n';
interface MenuItem {
label: string;
action: string;
disabled?: boolean; // 可选:是否禁用
isSeparator?: boolean; // 可选:是否是分隔线
isDanger?: boolean; // 可选:是否是危险操作 (例如红色文本)
}
const props = defineProps({
visible: {
type: Boolean,
required: true,
},
position: {
type: Object as PropType<{ x: number; y: number }>,
required: true,
},
items: {
type: Array as PropType<MenuItem[]>,
required: true,
},
// + Add targetId prop
targetId: {
type: [String, Number, null] as PropType<string | number | null>,
default: null,
}
});
const emit = defineEmits<{
// + Update signature to include targetId
(e: 'menu-action', payload: { action: string; targetId: string | number | null }): void;
(e: 'close'): void; // 请求关闭菜单
}>();
const { t } = useI18n();
const menuStyle = computed(() => ({
top: `${props.position.y}px`,
left: `${props.position.x}px`,
}));
const handleAction = (item: MenuItem) => {
console.log(`[ContextMenu] handleAction called for item:`, JSON.stringify(item)); // + Log item
if (!item.disabled && !item.isSeparator) {
console.log(`[ContextMenu] Inside handleAction, props.targetId is:`, props.targetId); // ++ Log prop value before emit
const payload = { action: item.action, targetId: props.targetId };
console.log(`[ContextMenu] Emitting menu-action with payload:`, JSON.stringify(payload)); // + Log emit payload
emit('menu-action', payload);
emit('close'); // 点击后自动关闭
}
};
// 点击菜单外部时,也应该关闭,这通常在父组件中处理 document click listener
// 但这里也添加一个遮罩层点击关闭
const handleOverlayClick = () => {
emit('close');
};
</script>
<template>
<div
v-if="visible"
class="fixed inset-0 z-40"
@click.self="handleOverlayClick"
@contextmenu.prevent
>
<div
class="fixed bg-background border border-border/50 shadow-xl rounded-lg py-1.5 z-50 min-w-[180px]"
:style="menuStyle"
@click.stop
>
<ul class="list-none p-0 m-0">
<template v-for="(item, index) in items" :key="index">
<li v-if="item.isSeparator" class="border-t border-border/50 my-1 mx-1"></li>
<li
v-else
class="group px-4 py-1.5 flex items-center text-sm transition-colors duration-150 rounded-md mx-1"
:class="[
item.disabled
? 'text-text-secondary opacity-50 cursor-not-allowed'
: item.isDanger
? 'text-error hover:bg-error/10 cursor-pointer'
: 'text-foreground hover:bg-primary/10 hover:text-primary cursor-pointer',
]"
@click="handleAction(item)"
>
<!-- 移除了图标 -->
<span>{{ t(item.label, item.label) }}</span> <!-- 使用 i18n -->
</li>
</template>
</ul>
</div>
</div>
</template>
<style scoped>
/* 可以添加一些额外的样式,如果需要的话 */
</style>
@@ -1,44 +1,49 @@
<script setup lang="ts">
import { ref, computed, PropType, onMounted, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { ref, computed, PropType, onMounted, onBeforeUnmount, watch } from 'vue'; // + onBeforeUnmount
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { storeToRefs } from 'pinia';
import WorkspaceConnectionListComponent from './WorkspaceConnectionList.vue';
import TabBarContextMenu from './TabBarContextMenu.vue'; // + Import context menu
import { useSessionStore } from '../stores/session.store';
import { useConnectionsStore, type ConnectionInfo } from '../stores/connections.store';
import { useLayoutStore, type PaneName } from '../stores/layout.store';
import type { SessionTabInfoWithStatus } from '../stores/session.store';
import type { SessionTabInfoWithStatus } from '../stores/session.store';
const { t } = useI18n(); // 初始化 i18n
const layoutStore = useLayoutStore(); // 初始化布局 store
const connectionsStore = useConnectionsStore();
const connectionsStore = useConnectionsStore();
const { isHeaderVisible } = storeToRefs(layoutStore); // 从 layout store 获取主导航栏可见状态
const route = useRoute(); // 获取路由实例
// 定义 Props
const props = defineProps({
sessions: {
type: Array as PropType<SessionTabInfoWithStatus[]>,
type: Array as PropType<SessionTabInfoWithStatus[]>,
required: true,
},
activeSessionId: {
type: String as PropType<string | null>,
required: false,
default: null,
type: String as PropType<string | null>,
required: false,
default: null,
},
});
// 定义事件
const emit = defineEmits([
'activate-session',
'close-session',
'open-layout-configurator',
'request-add-connection-from-popup',
'request-edit-connection-from-popup'
]);
// 定义事件 (使用对象语法修复类型)
const emit = defineEmits<{
(e: 'activate-session', sessionId: string): void;
(e: 'close-session', sessionId: string): void;
(e: 'open-layout-configurator'): void;
(e: 'request-add-connection-from-popup'): void;
(e: 'request-edit-connection-from-popup', connection: any): void; // 保持 any 或使用 ConnectionInfo
// + 新增右键菜单事件
(e: 'close-other-sessions', sessionId: string): void;
(e: 'close-sessions-to-right', sessionId: string): void;
(e: 'close-sessions-to-left', sessionId: string): void;
}>();
const activateSession = (sessionId: string) => {
if (sessionId !== props.activeSessionId) {
@@ -55,6 +60,12 @@ const closeSession = (event: MouseEvent, sessionId: string) => {
const sessionStore = useSessionStore(); // Session store 保持不变
const showConnectionListPopup = ref(false); // 连接列表弹出状态
// +++ 右键菜单状态 +++
const contextMenuVisible = ref(false);
const contextMenuPosition = ref({ x: 0, y: 0 });
const contextTargetSessionId = ref<string | null>(null); // Keep for logic inside this component if needed elsewhere
const menuTargetId = ref<string | null>(null); // + Ref specifically for passing to the menu prop
const togglePopup = () => {
showConnectionListPopup.value = !showConnectionListPopup.value;
};
@@ -98,6 +109,94 @@ const handleRequestEditFromPopup = (connection: any) => { // 假设 WorkspaceCon
// --- 移除 handleRequestRdpFromPopup 方法 ---
// const handleRequestRdpFromPopup = (connection: ConnectionInfo) => { ... };
// +++ 右键菜单方法 +++
const showContextMenu = (event: MouseEvent, sessionId: string) => {
event.preventDefault();
event.stopPropagation();
contextTargetSessionId.value = sessionId; // Still set the original ref if needed elsewhere
menuTargetId.value = sessionId; // + Set the dedicated ref for the prop
contextMenuPosition.value = { x: event.clientX, y: event.clientY };
contextMenuVisible.value = true;
// 添加全局监听器以关闭菜单
document.addEventListener('click', closeContextMenuOnClickOutside, { capture: true, once: true });
};
const closeContextMenu = () => {
contextMenuVisible.value = false;
contextTargetSessionId.value = null; // Clear original ref if needed
// menuTargetId.value = null; // -- REMOVE THIS LINE -- Let the value persist until next show
// 移除监听器(如果它仍然存在)
document.removeEventListener('click', closeContextMenuOnClickOutside, { capture: true });
};
// 用于全局点击监听器的函数
const closeContextMenuOnClickOutside = (event: MouseEvent) => {
// 检查点击是否发生在菜单内部,如果是,则不关闭
// 这个检查在 TabBarContextMenu 组件内部通过 @click.stop 完成了
// 所以这里可以直接关闭
closeContextMenu();
};
// + Update function signature to receive payload
const handleContextMenuAction = (payload: { action: string; targetId: string | number | null }) => {
const { action, targetId } = payload;
console.log(`[TabBar] handleContextMenuAction received payload:`, JSON.stringify(payload)); // + Log received payload
// const targetId = contextTargetSessionId.value; // No longer needed
if (!targetId || typeof targetId !== 'string') { // Ensure targetId is a string (session ID)
console.warn('[TabBar] handleContextMenuAction called but targetId is null or not a string.');
return;
}
console.log(`[TabBar] Context menu action '${action}' requested for session ID: ${targetId}`); // Keep original log
switch (action) {
case 'close':
emit('close-session', targetId);
break;
case 'close-others':
emit('close-other-sessions', targetId);
break;
case 'close-right':
emit('close-sessions-to-right', targetId);
break;
case 'close-left':
// 注意:关闭左侧通常不包括当前标签本身
emit('close-sessions-to-left', targetId);
break;
default:
console.warn(`[TabBar] Unknown context menu action: ${action}`);
}
// closeContextMenu(); // TabBarContextMenu 内部点击后会触发 close 事件
};
// 计算右键菜单项
const contextMenuItems = computed(() => {
const items = [];
const targetId = contextTargetSessionId.value;
if (!targetId) return [];
const currentIndex = props.sessions.findIndex(s => s.sessionId === targetId);
const totalTabs = props.sessions.length;
items.push({ label: 'tabs.contextMenu.close', action: 'close' }); // 使用 i18n key
if (totalTabs > 1) {
items.push({ label: 'tabs.contextMenu.closeOthers', action: 'close-others' });
}
if (currentIndex < totalTabs - 1) {
items.push({ label: 'tabs.contextMenu.closeRight', action: 'close-right' });
}
if (currentIndex > 0) {
items.push({ label: 'tabs.contextMenu.closeLeft', action: 'close-left' });
}
return items;
});
// 新增:处理打开布局配置器的事件
const openLayoutConfigurator = () => {
console.log('[TabBar] Emitting open-layout-configurator event');
@@ -125,6 +224,13 @@ onMounted(() => {
}
});
// +++ 组件卸载前移除全局监听器 +++
// onBeforeUnmount is imported now
onBeforeUnmount(() => {
document.removeEventListener('click', closeContextMenuOnClickOutside, { capture: true });
});
// 切换主导航栏可见性 (只在 workspace 路由下生效)
const toggleHeader = () => {
if (isWorkspaceRoute.value) {
@@ -153,7 +259,7 @@ const toggleButtonTitle = computed(() => {
</script>
<template>
<div class="flex bg-header border border-border rounded-t-md mx-2 mt-2 overflow-hidden h-10">
<div class="flex bg-header border border-border rounded-t-md mx-2 mt-2 overflow-hidden h-10">
<div class="flex items-center overflow-x-auto flex-shrink min-w-0">
<ul class="flex list-none p-0 m-0 h-full flex-shrink-0">
<li
@@ -162,6 +268,7 @@ const toggleButtonTitle = computed(() => {
:class="['flex items-center px-3 h-full cursor-pointer border-r border-border transition-colors duration-150 relative group',
session.sessionId === activeSessionId ? 'bg-background text-foreground' : 'bg-header text-text-secondary hover:bg-border']"
@click="activateSession(session.sessionId)"
@contextmenu.prevent="showContextMenu($event, session.sessionId)"
:title="session.connectionName"
>
<!-- Status dot -->
@@ -220,7 +327,14 @@ const toggleButtonTitle = computed(() => {
</div>
</div>
</div>
<!-- +++ Context Menu Instance (Ensure it's present) +++ -->
<TabBarContextMenu
:visible="contextMenuVisible"
:position="contextMenuPosition"
:items="contextMenuItems"
:target-id="menuTargetId"
@menu-action="handleContextMenuAction"
@close="closeContextMenu"
/>
</div>
</template>