feat(ui): route Ctrl+F search by focused surface

This commit is contained in:
zipg authored and GitHub committed 2026-09-15 23:20:05 +08:00
1 parent 4c80d16e26
commit 3c8ae159fb
23 files changed
+233 -56

No files matched your search

@@ -1547,7 +1547,7 @@ watch(driverStoreTab, (tab) => {
<!-- Driver List -->
<div class="relative">
<Search class="absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
<Input v-model="agentDriverSearch" class="h-8 pl-8 text-xs" :placeholder="t('driverStore.searchDrivers')" />
<Input data-driver-store-agent-search v-model="agentDriverSearch" class="h-8 pl-8 text-xs" :placeholder="t('driverStore.searchDrivers')" />
</div>
<!-- Global update section — always above category navigation -->
<div v-if="globalUpdatableDrivers.length > 0" class="rounded-lg border divide-y">
@@ -1972,7 +1972,7 @@ watch(driverStoreTab, (tab) => {
</div>
<div class="relative">
<Search class="absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
<Input v-model="jdbcDriverSearch" class="h-8 pl-8 text-xs" :placeholder="t('driverStore.searchJdbcDrivers')" />
<Input data-driver-store-jdbc-search v-model="jdbcDriverSearch" class="h-8 pl-8 text-xs" :placeholder="t('driverStore.searchJdbcDrivers')" />
</div>
<div class="flex items-center gap-2">
<Input v-model="jdbcDriverPathInput" class="flex-1" :placeholder="t('settings.jdbcDriverPathPlaceholder')" @keydown.enter.prevent="importJdbcDriverPathInput" />
@@ -6274,7 +6274,7 @@ function openExternalUrl(url: string) {
</div>
<div class="connection-db-picker-search relative w-full sm:w-64">
<Search class="absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input v-model="dbSearchQuery" v-connection-dialog-auto-focus class="h-9 pl-8" :placeholder="t('connection.searchDatabasePlaceholder')" />
<Input data-connection-db-search v-model="dbSearchQuery" v-connection-dialog-auto-focus class="h-9 pl-8" :placeholder="t('connection.searchDatabasePlaceholder')" />
</div>
</div>
<Button data-jdbc-connection-entry type="button" variant="outline" class="h-9 shrink-0 gap-2" @click="goToConnectionStep('jdbc')">
@@ -4484,7 +4484,12 @@ function clearContextReferences() {
mentionError.value = "";
}
defineExpose({ triggerAction, setPrompt, addTableMention, clearContextReferences, selectConversationById });
function focusSearch(): boolean {
void setConversationListOpen(true);
return true;
}
defineExpose({ triggerAction, setPrompt, addTableMention, clearContextReferences, selectConversationById, focusSearch });
const messageRenderer = computed(() => {
const appearance = aiCodeAppearance.value;
@@ -4555,6 +4560,7 @@ async function openExternalUrl(url: string) {
<Search class="pointer-events-none absolute left-3 h-3 w-3 text-muted-foreground" />
<input
ref="conversationSearchInput"
data-ai-conversation-search
v-model="conversationSearchQuery"
type="search"
:aria-label="t('history.conversationSearch')"
@@ -5237,6 +5237,7 @@ onUnmounted(() => {
<div ref="settingsSearchInputContainerRef" class="relative">
<Search class="pointer-events-none absolute top-1/2 left-4 z-10 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
data-settings-global-search
v-model="settingsSearchQuery"
type="text"
autocomplete="off"
@@ -463,7 +463,7 @@ onBeforeUnmount(() => {
</div>
<div class="relative flex items-center px-2 py-1">
<Search class="absolute left-3 w-3 h-3 text-muted-foreground pointer-events-none" />
<input v-model="searchText" autocapitalize="off" autocorrect="off" spellcheck="false" class="flex-1 h-5 text-xs bg-transparent border rounded pl-5 pr-1 outline-none placeholder:text-muted-foreground" :placeholder="t('history.search')" />
<input data-history-search v-model="searchText" autocapitalize="off" autocorrect="off" spellcheck="false" class="flex-1 h-5 text-xs bg-transparent border rounded pl-5 pr-1 outline-none placeholder:text-muted-foreground" :placeholder="t('history.search')" />
<Popover :open="dateRangeOpen" @update:open="setDateRangeOpen">
<PopoverTrigger as-child>
<button
@@ -80,7 +80,7 @@ describe("QueryEditor search shortcuts", () => {
expect(queryEditorSource).toMatch(/\.\.\.binding\(shortcuts\.sendSelectionToAi[\s\S]*\.\.\.createQueryEditorSearchKeymap/);
expect(queryEditorSource).not.toMatch(/Prec\.highest\(\s*keymap\.of\(\[\s*\.\.\.createQueryEditorSearchKeymap/);
expect(queryEditorSource).toMatch(/function openReplace\(\): boolean \{\s*if \(props\.readOnly\) return false;/);
expect(contentAreaSource).toContain("if (props.resultOnly) return dataGridRef.value?.focusSearch() ?? false;");
expect(contentAreaSource).toContain("if (props.resultOnly) return dataGridRef.value?.focusSearch(target) ?? false;");
expect(contentAreaSource).toContain("return queryEditorRef.value?.openSearch() ?? false;");
expect(contentAreaSource).toContain("return queryEditorRef.value?.openReplace() ?? false;");
expect(queryEditorSource).toMatch(/key:\s*"Escape"/);
+13 -3
View File
@@ -1707,7 +1707,16 @@ function navigateSuggestion(delta: number) {
dataGridSearch.navigateSuggestion(delta);
}
function focusSearch(): boolean {
function focusSearch(target: Element | null = null): boolean {
const tableInfoDrawer = target?.closest<HTMLElement>("[data-table-info-drawer]");
if (tableInfoDrawer) {
const input = tableInfoDrawer.querySelector<HTMLInputElement>("[data-table-info-search]");
if (input) {
input.focus();
input.select();
return true;
}
}
searchOverlayVisible.value = true;
nextTick(() => {
searchBarRef.value?.focus(true);
@@ -8845,7 +8854,7 @@ async function onGridKeydown(event: KeyboardEvent) {
if (!targetAllowsNativeClipboard && handleGridPaginationShortcut(event)) return;
if (isFocusSearchShortcut(event) && !isGoToColumnShortcut(event, settingsStore.editorSettings.shortcuts)) {
event.preventDefault();
focusSearch();
focusSearch(event.target instanceof Element ? event.target : document.activeElement instanceof Element ? document.activeElement : null);
return;
}
if (isModRShortcut(event)) {
@@ -13079,6 +13088,7 @@ useUpdateBlocker(() => (hasPendingChanges.value || hasPendingDataEditorDraft.val
<div
v-if="showTableInfo"
data-native-clipboard
data-table-info-drawer
class="table-info-drawer relative col-start-2 row-start-1 border-l flex flex-col bg-background min-w-0 max-w-full"
:class="[{ 'row-span-2': cellDetailPanelIsBottom }, { 'ddl-drawer-resizing': isResizingDdl }]"
:style="ddlDrawerStyle"
@@ -13151,7 +13161,7 @@ useUpdateBlocker(() => (hasPendingChanges.value || hasPendingDataEditorDraft.val
<div class="flex min-w-0 items-center gap-1">
<div class="relative min-w-0 flex-1">
<Search class="absolute left-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground" />
<input v-model="searchQuery" :placeholder="t('grid.tableInfoSearch')" class="w-full h-7 pl-7 pr-6 text-xs bg-muted/50 rounded border border-border focus:outline-none focus:border-primary/50" @keydown="onTableInfoSearchKeydown" />
<input v-model="searchQuery" data-table-info-search :placeholder="t('grid.tableInfoSearch')" class="w-full h-7 pl-7 pr-6 text-xs bg-muted/50 rounded border border-border focus:outline-none focus:border-primary/50" @keydown="onTableInfoSearchKeydown" />
<button v-if="searchQuery" type="button" class="absolute right-1.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground" @click="searchQuery = ''">
<X class="w-3 h-3" />
</button>
@@ -110,8 +110,8 @@ function collapseAllTreeNodes() {
connectionTreeRef.value?.collapseAllTreeNodes();
}
function focusSearch(): boolean {
return connectionTreeRef.value?.focusSearch() ?? false;
function focusSearch(target: Element | null = null): boolean {
return connectionTreeRef.value?.focusSearch(target) ?? false;
}
function locateTabInSidebar(tab: QueryTab) {
@@ -197,7 +197,7 @@ defineExpose({ focusSearch, locateTabInSidebar });
</script>
<template>
<div class="app-sidebar-panel h-full shrink-0 relative select-none" :class="classicLayout ? '' : 'rounded-md border border-border/80 bg-background'" :style="{ width: sidebarWidth + 'px' }" @keydown="onSidebarKeydown">
<div data-app-sidebar class="app-sidebar-panel h-full shrink-0 relative select-none" :class="classicLayout ? '' : 'rounded-md border border-border/80 bg-background'" :style="{ width: sidebarWidth + 'px' }" @keydown="onSidebarKeydown">
<div class="h-full flex flex-col overflow-hidden">
<div class="app-sidebar-toolbar flex items-center gap-px px-3 text-xs font-medium text-muted-foreground border-b bg-muted/20" :class="classicLayout ? 'h-9' : 'h-10'">
<span v-if="showConnectionMultiSelectToolbar" class="flex min-w-0 self-stretch items-center" data-tauri-drag-region>
@@ -170,7 +170,6 @@ import type { DataGridSortMode } from "@/lib/dataGrid/dataGridSort";
import { isDataGridToolbarCompact, type DataGridReloadIntent } from "@/lib/dataGrid/dataGridToolbar";
import { useTabScroll } from "@/composables/useTabScroll";
import { useToolbarOverflow } from "@/composables/useToolbarOverflow";
import ToolbarOverflowMenu from "@/components/ui/ToolbarOverflowMenu.vue";
import { formatElapsedSeconds } from "@/lib/common/elapsedTime";
import { copyToClipboard } from "@/lib/common/clipboard";
import type { CustomSaveHandler } from "@/composables/useDataGridEditor";
@@ -183,7 +182,7 @@ import { connectionIsEffectivelyReadOnly } from "@/lib/database/readOnlyWriteAcc
type DataGridHandle = DataGridColumnLayoutHandle & {
onToolbarRefresh: () => Promise<void> | void;
focusSearch: () => boolean;
focusSearch: (target?: Element | null) => boolean;
openGoToColumn: () => boolean;
openCellDetailSearch: () => boolean;
nullColumnsHidden: boolean;
@@ -206,7 +205,7 @@ type DataGridHandle = DataGridColumnLayoutHandle & {
};
type SearchableBrowserHandle = {
focusSearch: () => boolean;
focusSearch: (target?: Element | null) => boolean;
refresh?: () => boolean;
insertCommand?: (command: string) => Promise<boolean>;
executeCommand?: (command: string) => Promise<boolean>;
@@ -304,8 +303,6 @@ const dataGridViewOptionsOpen = ref(false);
const dataToolbarRef = ref<HTMLElement | null>(null);
const { tier: dataToolbarTier } = useToolbarOverflow(dataToolbarRef, [() => props.activeTab.id, () => !!props.activeTab.result]);
const dataToolbarCompact = computed(() => dataToolbarTier.value >= 1);
const showDataToolbarOverflow = computed(() => dataToolbarTier.value >= 2);
const showDataTableInfoButton = computed(() => dataToolbarTier.value < 2);
const showDataColumnsChip = computed(() => dataToolbarTier.value < 2);
const dataGridRenderMode = computed(() => settingsStore.editorSettings.dataGridRenderMode);
const dataGridSearchMode = computed(() => settingsStore.editorSettings.dataGridSearchMode);
@@ -953,7 +950,7 @@ function onHandleCloseColumnPanel() {
columnInfoError.value = undefined;
}
function focusSearch(): boolean {
function focusSearch(target: Element | null = null): boolean {
if (elasticsearchJsonResponsePanelRef.value?.focusSearch()) return true;
if (props.activeTab.mode === "mongo") return documentBrowserRef.value?.focusSearch() ?? false;
if (props.activeTab.mode === "redis") return redisKeyBrowserRef.value?.focusSearch() ?? false;
@@ -961,15 +958,15 @@ function focusSearch(): boolean {
if (props.activeTab.mode === "zookeeper") return zookeeperKeyBrowserRef.value?.focusSearch() ?? false;
if (props.activeTab.mode === "consul") return consulWorkspaceRef.value?.focusSearch() ?? false;
if (props.activeTab.mode === "databases") return databaseBrowserRef.value?.focusSearch() ?? false;
if (props.activeTab.mode === "objects") return objectBrowserRef.value?.focusSearch() ?? false;
if (props.activeTab.mode === "objects") return objectBrowserRef.value?.focusSearch(target) ?? false;
if (props.activeTab.mode === "structure") return tableStructureEditorRef.value?.focusSearch() ?? false;
if (props.activeTab.mode === "query") {
// The shared result surface (resultOnly) owns the grid, not the editor;
// route its search to the DataGrid instead of the missing QueryEditor.
if (props.resultOnly) return dataGridRef.value?.focusSearch() ?? false;
if (props.resultOnly) return dataGridRef.value?.focusSearch(target) ?? false;
return queryEditorRef.value?.openSearch() ?? false;
}
return dataGridRef.value?.focusSearch() ?? false;
return dataGridRef.value?.focusSearch(target) ?? false;
}
function openGoToColumn(): boolean {
@@ -2174,14 +2171,7 @@ defineExpose({
<span v-if="showDataColumnsChip && activeDataTabTableMeta" class="inline-flex shrink-0 items-center rounded border border-border bg-muted/30 px-2 py-0.5 font-medium text-muted-foreground tabular-nums"> {{ activeDataTabTableMeta.columns.length }} {{ t("tree.columns") }} </span>
<span class="ml-auto" />
<DataGridColumnLayoutPopover v-if="activeTab.result?.columns.length" :grid="dataGridRef" trigger-class="px-1.5" />
<Button
v-if="showDataTableInfoButton && activeTab.result && activeDataTabTableMeta && activeTab.connectionId"
variant="ghost"
size="sm"
class="h-5 text-xs px-1.5 shrink-0"
:class="{ 'bg-accent': dataGridRef?.showDdl }"
:title="dataToolbarCompact ? t('grid.tableInfo') : undefined"
@click="dataGridRef?.toggleDdl()"
<Button v-if="activeTab.result && activeDataTabTableMeta && activeTab.connectionId" variant="ghost" size="sm" class="h-5 text-xs px-1.5 shrink-0" :class="{ 'bg-accent': dataGridRef?.showDdl }" :title="dataToolbarCompact ? t('grid.tableInfo') : undefined" @click="dataGridRef?.toggleDdl()"
><TableProperties class="h-3.5 w-3.5" /><span v-if="!dataToolbarCompact">{{ t("grid.tableInfo") }}</span></Button
>
<DropdownMenu v-if="activeTab.result && activeDataTabTableMeta && activeTab.connectionId">
@@ -2426,12 +2416,6 @@ defineExpose({
/>
</PopoverContent>
</Popover>
<ToolbarOverflowMenu v-if="showDataToolbarOverflow" :label="t('toolbar.moreActions')">
<DropdownMenuItem v-if="activeTab.result && activeDataTabTableMeta && activeTab.connectionId" @select="dataGridRef?.toggleDdl()">
<TableProperties class="h-3.5 w-3.5" />
{{ t("grid.tableInfo") }}
</DropdownMenuItem>
</ToolbarOverflowMenu>
</div>
<DataGrid
v-if="activeTab.result"
@@ -70,7 +70,7 @@ const surfaceBindings = computed(() => ({ ...surfaceProps.value, ...contentEmits
const activeSurfaceRef = ref<QueryEditorSurfaceHandle | null>(null);
defineExpose({
focusSearch: () => activeSurfaceRef.value?.focusSearch() ?? false,
focusSearch: (target: Element | null = null) => activeSurfaceRef.value?.focusSearch(target) ?? false,
openGoToColumn: () => activeSurfaceRef.value?.openGoToColumn() ?? false,
refreshData: () => activeSurfaceRef.value?.refreshData() ?? false,
toggleResultsPane: () => activeSurfaceRef.value?.toggleResultsPane() ?? false,
@@ -22,7 +22,7 @@ const bindings = computed(() => ({ ...surfaceProps.value, ...contentEmits }));
const contentAreaRef = ref<InstanceType<typeof ContentArea> | null>(null);
defineExpose<QueryEditorSurfaceHandle>({
focusSearch: () => contentAreaRef.value?.focusSearch() ?? false,
focusSearch: (target: Element | null = null) => contentAreaRef.value?.focusSearch(target) ?? false,
openGoToColumn: () => contentAreaRef.value?.openGoToColumn() ?? false,
refreshData: () => contentAreaRef.value?.refreshData() ?? false,
toggleResultsPane: () => contentAreaRef.value?.toggleResultsPane() ?? false,
@@ -27,7 +27,7 @@ const bindings = computed(() => ({ ...surfaceProps.value, ...contentEmits }));
const contentAreaRef = ref<InstanceType<typeof ContentArea> | null>(null);
defineExpose<QueryResultSurfaceHandle>({
focusSearch: () => contentAreaRef.value?.focusSearch() ?? false,
focusSearch: (target: Element | null = null) => contentAreaRef.value?.focusSearch(target) ?? false,
refreshData: () => contentAreaRef.value?.refreshData() ?? false,
toggleResultsPane: () => contentAreaRef.value?.toggleResultsPane() ?? false,
handleModRTarget: (target: Element) => contentAreaRef.value?.handleModRTarget(target) ?? false,
@@ -66,10 +66,10 @@ defineExpose({
// A data-mode cell-detail dialog is portaled to body too but belongs to
// the group's grid — indistinguishable in the DOM, hence the gate.
if (element?.closest("[data-shared-result-surface]") || (showSharedResult.value && element?.closest("[data-cell-detail-editor-root]"))) {
return resultSurfaceRef.value?.focusSearch() ?? false;
return resultSurfaceRef.value?.focusSearch(element) ?? false;
}
const group = groupForElement(element) ?? activeEditorGroup();
return group?.focusSearch() ?? false;
return group?.focusSearch(element) ?? false;
},
openGoToColumn: () => activeEditorGroup()?.openGoToColumn() ?? false,
refreshData: (target: Element | null = null) => {
@@ -1199,7 +1199,7 @@ function showDropInside(targetId: string) {
<div class="border-b shrink-0 px-2 py-1">
<div class="relative">
<Search class="absolute left-2 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground" />
<input v-model="searchText" autocapitalize="off" autocorrect="off" spellcheck="false" class="w-full h-6 pl-7 pr-6 text-[13px] rounded border border-border bg-background focus:outline-none focus:ring-1 focus:ring-ring" :placeholder="t('grid.search')" />
<input data-sql-library-search v-model="searchText" autocapitalize="off" autocorrect="off" spellcheck="false" class="w-full h-6 pl-7 pr-6 text-[13px] rounded border border-border bg-background focus:outline-none focus:ring-1 focus:ring-ring" :placeholder="t('grid.search')" />
<button v-if="searchText" type="button" class="absolute right-1.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground" @click="searchText = ''">
<X class="h-3 w-3" />
</button>
@@ -0,0 +1,12 @@
import { readFileSync } from "node:fs";
import { describe, expect, it } from "vitest";
const contentAreaSource = readFileSync(new URL("../ContentArea.vue", import.meta.url), "utf8");
describe("ContentArea data toolbar", () => {
it("keeps table properties in the primary toolbar at every overflow tier", () => {
expect(contentAreaSource).toContain('v-if="activeTab.result && activeDataTabTableMeta && activeTab.connectionId"');
expect(contentAreaSource).not.toContain("showDataTableInfoButton");
expect(contentAreaSource).not.toContain("ToolbarOverflowMenu");
});
});
@@ -3047,7 +3047,16 @@ function getSearchInput(): HTMLInputElement | null {
return rootRef.value?.querySelector<HTMLInputElement>("[data-object-search-input]") ?? null;
}
function focusSearch(): boolean {
function focusSearch(target: Element | null = null): boolean {
const tableInfoPanel = target?.closest<HTMLElement>("[data-object-table-info-panel]");
if (tableInfoPanel) {
const input = tableInfoPanel.querySelector<HTMLInputElement>("[data-table-info-search]");
if (input) {
input.focus();
input.select();
return true;
}
}
const input = getSearchInput();
if (!input) return false;
input.focus();
@@ -3694,7 +3703,13 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
</div>
</div>
<!-- Right-side panel: table info or source -->
<div v-if="sidePanelRow || isEventEditor" class="object-browser-side-panel relative flex min-h-0 min-w-0 shrink-0 flex-col border-l bg-background" :class="{ 'side-panel-resizing': isResizingSidePanel }" :style="{ width: `min(${sidePanelWidth}px, 100%)` }">
<div
v-if="sidePanelRow || isEventEditor"
:data-object-table-info-panel="sidePanelMode === 'table-info' ? '' : undefined"
class="object-browser-side-panel relative flex min-h-0 min-w-0 shrink-0 flex-col border-l bg-background"
:class="{ 'side-panel-resizing': isResizingSidePanel }"
:style="{ width: `min(${sidePanelWidth}px, 100%)` }"
>
<div class="absolute left-0 top-0 bottom-0 z-20 w-1.5 -translate-x-1/2 cursor-col-resize hover:bg-primary/30" @mousedown.prevent="onSidePanelResizeStart" />
<!-- Table info mode -->
<template v-if="sidePanelMode === 'table-info'">
@@ -3735,7 +3750,7 @@ function getObjectBrowserMenuItems(item: ObjectBrowserRow): ContextMenuItem[] {
<div class="flex items-center gap-1 px-2 py-1.5 border-b shrink-0 bg-background">
<div class="relative min-w-0 flex-1">
<Search class="absolute left-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground" />
<input v-model="tableInfoSearchQuery" :placeholder="t('grid.tableInfoSearch')" class="w-full h-7 pl-7 pr-6 text-xs bg-muted/50 rounded border border-border focus:outline-none focus:border-primary/50" @keydown.escape="tableInfoSearchQuery = ''" />
<input v-model="tableInfoSearchQuery" data-table-info-search :placeholder="t('grid.tableInfoSearch')" class="w-full h-7 pl-7 pr-6 text-xs bg-muted/50 rounded border border-border focus:outline-none focus:border-primary/50" @keydown.escape="tableInfoSearchQuery = ''" />
<button v-if="tableInfoSearchQuery" class="absolute right-1.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground" @click="tableInfoSearchQuery = ''">
<X class="w-3 h-3" />
</button>
@@ -617,7 +617,7 @@ onBeforeUnmount(() => {
<div class="flex w-full flex-col gap-2 rounded-xl border bg-card/70 p-3 sm:flex-row sm:items-center">
<div class="relative">
<Search class="pointer-events-none absolute left-2.5 top-2.5 size-3.5 text-muted-foreground" />
<Input v-model="marketplaceQuery" class="h-8 min-w-0 pl-8 text-xs sm:w-[min(100%,28rem)]" :placeholder="t('pluginPlatform.searchMarketplace')" />
<Input data-plugin-marketplace-search v-model="marketplaceQuery" class="h-8 min-w-0 pl-8 text-xs sm:w-[min(100%,28rem)]" :placeholder="t('pluginPlatform.searchMarketplace')" />
</div>
<div class="flex min-w-0 items-center gap-2 sm:ml-auto">
<Select v-model="marketplaceRepositoryId">
@@ -2292,7 +2292,16 @@ watch(
{ flush: "post" },
);
function focusSearch(): boolean {
function focusSearch(target: Element | null = null): boolean {
const tableSearchControl = target?.closest<HTMLElement>("[data-sidebar-table-search-control]");
if (tableSearchControl) {
const input = tableSearchControl.querySelector<HTMLInputElement>("[data-sidebar-table-search-parent-id]");
if (input) {
input.focus();
input.select();
return true;
}
}
const input = searchInputRef.value;
if (!input) return false;
input.focus();
@@ -73,6 +73,7 @@ import { formatSidebarObjectStorage } from "@/lib/sidebar/sidebarDatabaseStorage
import { dataTabOpenModeFromTreeClick } from "@/lib/sidebar/dataTabOpenPolicy";
import { effectiveDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
import { connectionDisplayUrlScheme } from "@/lib/connection/connectionPresentation";
import { isFocusSearchShortcut } from "@/lib/editor/keyboardShortcuts";
import { encodeSpannerResourcePath } from "@/lib/connection/spannerResourcePath";
import { hexToRgba } from "@/lib/common/color";
import { sidebarDisplayTableName } from "@/lib/sidebar/sidebarTableNameDisplay";
@@ -1041,6 +1042,17 @@ function clearTableSearchQuery() {
updateTableSearchQuery("");
}
function onTableSearchControlKeydown(event: KeyboardEvent) {
if (isFocusSearchShortcut(event, settingsStore.editorSettings.shortcuts)) {
event.preventDefault();
const control = event.currentTarget instanceof HTMLElement ? event.currentTarget : null;
const input = control?.querySelector<HTMLInputElement>("[data-sidebar-table-search-parent-id]");
input?.focus();
input?.select();
}
event.stopPropagation();
}
// --- Connection Group Management ---
const isRenamingGroup = ref(false);
@@ -1504,7 +1516,7 @@ function onKeydown(event: KeyboardEvent) {
</script>
<template>
<div v-if="node.type === 'table-search-control'" class="tree-table-search-control flex h-7 items-center gap-1.5 py-0.5 pr-2" :style="tableSearchStyle" @click.stop @dblclick.stop @mousedown.stop @keydown.stop>
<div v-if="node.type === 'table-search-control'" data-sidebar-table-search-control class="tree-table-search-control flex h-7 items-center gap-1.5 py-0.5 pr-2" :style="tableSearchStyle" @click.stop @dblclick.stop @mousedown.stop @keydown="onTableSearchControlKeydown">
<div class="relative min-w-0 flex-1">
<Search class="pointer-events-none absolute left-2 top-1/2 h-3 w-3 -translate-y-1/2 text-muted-foreground" />
<Input