diff --git a/src/components/sidebar/TreeItem.vue b/src/components/sidebar/TreeItem.vue index 16a83df4ee..fe72721c48 100644 --- a/src/components/sidebar/TreeItem.vue +++ b/src/components/sidebar/TreeItem.vue @@ -86,7 +86,7 @@ import { usesTreeSchemaMode, usesFetchFirst, } from "@/lib/databaseCapabilities"; -import { treeNodeRowAction, treeNodeRowDoubleClickAction } from "@/lib/treeNodeClick"; +import { sidebarSelectionCopyAction, treeNodeRowAction, treeNodeRowDoubleClickAction } from "@/lib/treeNodeClick"; import { formatCsv, formatJson, formatSqlInsert } from "@/lib/exportFormats"; import { hexToRgba } from "@/lib/color"; import DangerConfirmDialog from "@/components/editor/DangerConfirmDialog.vue"; @@ -102,6 +102,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip const { t } = useI18n(); const labelRef = ref(); +const rowRef = ref(); const isTruncated = computed(() => { const el = labelRef.value; return !!el && el.scrollWidth > el.clientWidth; @@ -338,11 +339,31 @@ function runRowClickAction() { function onClick(event: MouseEvent) { connectionStore.selectedTreeNodeId = props.node.id; + rowRef.value?.focus({ preventScroll: true }); if (settingsStore.editorSettings.sidebarActivation === "double") return; if (event.detail > 1) return; runRowClickAction(); } +function isEditableShortcutTarget(target: EventTarget | null): boolean { + if (!(target instanceof HTMLElement)) return false; + return ( + target instanceof HTMLInputElement || + target instanceof HTMLTextAreaElement || + target.isContentEditable || + !!target.closest("[contenteditable='true']") + ); +} + +function onKeydown(event: KeyboardEvent) { + if (!isSelected.value || isEditableShortcutTarget(event.target)) return; + const action = sidebarSelectionCopyAction(event, settingsStore.editorSettings.sidebarActivation); + if (action !== "copy-name") return; + event.preventDefault(); + event.stopPropagation(); + copyName(); +} + function onDoubleClick() { const action = treeNodeRowDoubleClickAction( props.node.type, @@ -1448,6 +1469,7 @@ const isDragging = computed(() => dragState.active && dragState.draggedId === pr
(["table", "view"]); @@ -43,3 +45,11 @@ export function treeNodeRowDoubleClickAction( if (canOpenObjectBrowser && objectBrowserNodeTypes.has(type)) return "open-object-browser"; return "none"; } + +export function sidebarSelectionCopyAction( + event: ShortcutLikeEvent, + activation: SidebarActivation, +): SidebarSelectionCopyAction { + if (activation !== "double") return "none"; + return matchesShortcut(event, "Mod+C") ? "copy-name" : "none"; +} diff --git a/tests/treeNodeClick.test.ts b/tests/treeNodeClick.test.ts index ca9624e997..f45fc65cf7 100644 --- a/tests/treeNodeClick.test.ts +++ b/tests/treeNodeClick.test.ts @@ -1,6 +1,6 @@ import test from "node:test"; import assert from "node:assert/strict"; -import { treeNodeRowAction, treeNodeRowDoubleClickAction } from "../src/lib/treeNodeClick.ts"; +import { sidebarSelectionCopyAction, treeNodeRowAction, treeNodeRowDoubleClickAction } from "../src/lib/treeNodeClick.ts"; test("table and view rows open data without toggling structure groups", () => { assert.equal(treeNodeRowAction("table", true), "open-data"); @@ -58,3 +58,12 @@ test("double click does not open object browser for non-browsable rows", () => { assert.equal(treeNodeRowDoubleClickAction("table", true), "none"); assert.equal(treeNodeRowDoubleClickAction("column", true), "none"); }); + +test("double click navigation mode copies the selected sidebar row name", () => { + assert.equal(sidebarSelectionCopyAction({ key: "c", metaKey: true }, "double"), "copy-name"); + assert.equal(sidebarSelectionCopyAction({ key: "C", ctrlKey: true }, "double"), "copy-name"); +}); + +test("single click navigation mode leaves copy shortcuts alone", () => { + assert.equal(sidebarSelectionCopyAction({ key: "c", metaKey: true }, "single"), "none"); +});