feat(grid): add go-to-column shortcut and keyboard navigation
This commit is contained in:
4 files changed
+156
-17
No files matched your search
@@ -74,6 +74,7 @@ import {
|
|||||||
isExecuteSqlInNewResultTabShortcut,
|
isExecuteSqlInNewResultTabShortcut,
|
||||||
isExecuteSqlShortcut,
|
isExecuteSqlShortcut,
|
||||||
isFocusSearchShortcut,
|
isFocusSearchShortcut,
|
||||||
|
isGoToColumnShortcut,
|
||||||
isModRShortcut,
|
isModRShortcut,
|
||||||
handleTabHistoryNavigationShortcut,
|
handleTabHistoryNavigationShortcut,
|
||||||
isNewQueryShortcut,
|
isNewQueryShortcut,
|
||||||
@@ -2570,8 +2571,17 @@ async function handleKeydown(e: KeyboardEvent) {
|
|||||||
if (e.defaultPrevented) return;
|
if (e.defaultPrevented) return;
|
||||||
|
|
||||||
const shortcuts = settingsStore.editorSettings.shortcuts;
|
const shortcuts = settingsStore.editorSettings.shortcuts;
|
||||||
const tabSwitcherDirection = tabSwitcherDirectionFromShortcut(e, shortcuts);
|
|
||||||
if (showTabSwitcher.value) return;
|
if (showTabSwitcher.value) return;
|
||||||
|
// Grid-scoped shortcuts normally win inside DataGrid. Keep that precedence
|
||||||
|
// for a data tab even when focus is in a sibling input, where the grid's
|
||||||
|
// local listener intentionally leaves native editing untouched.
|
||||||
|
if (isGoToColumnShortcut(e, shortcuts) && contentAreaRef.value?.openGoToColumn()) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabSwitcherDirection = tabSwitcherDirectionFromShortcut(e, shortcuts);
|
||||||
if (tabSwitcherDirection) {
|
if (tabSwitcherDirection) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|||||||
@@ -2547,6 +2547,9 @@ let highlightedColumnTimer = 0;
|
|||||||
|
|
||||||
const goToColumnOpen = ref(false);
|
const goToColumnOpen = ref(false);
|
||||||
const goToColumnSearch = ref("");
|
const goToColumnSearch = ref("");
|
||||||
|
const goToColumnSearchInput = ref<HTMLInputElement>();
|
||||||
|
const goToColumnListRef = ref<HTMLElement>();
|
||||||
|
const goToColumnSelectedIndex = ref(0);
|
||||||
const columnOrderKeys = computed(() => uniqueDataGridColumnOrderKeys(props.result.columns, props.sourceColumns));
|
const columnOrderKeys = computed(() => uniqueDataGridColumnOrderKeys(props.result.columns, props.sourceColumns));
|
||||||
const resolvedColumnLayoutScopeKey = computed(
|
const resolvedColumnLayoutScopeKey = computed(
|
||||||
() =>
|
() =>
|
||||||
@@ -2731,15 +2734,76 @@ function scrollToColumn(columnIndex: number) {
|
|||||||
gridRef.value?.focus();
|
gridRef.value?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function focusGoToColumnSearch() {
|
||||||
|
goToColumnSearchInput.value?.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollGoToColumnSelectionIntoView() {
|
||||||
|
void nextTick(() => {
|
||||||
|
goToColumnListRef.value?.querySelectorAll<HTMLButtonElement>("button")[goToColumnSelectedIndex.value]?.scrollIntoView({ block: "nearest" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openGoToColumn(): boolean {
|
||||||
|
if (!displayableColumnIndexes.value.length) return false;
|
||||||
|
|
||||||
|
const alreadyOpen = goToColumnOpen.value;
|
||||||
|
goToColumnSearch.value = "";
|
||||||
|
goToColumnSelectedIndex.value = 0;
|
||||||
|
goToColumnOpen.value = true;
|
||||||
|
if (alreadyOpen) void nextTick(focusGoToColumnSearch);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveGoToColumnSelection(delta: number) {
|
||||||
|
const count = filteredGoToColumns.value.length;
|
||||||
|
if (!count) return;
|
||||||
|
goToColumnSelectedIndex.value = (goToColumnSelectedIndex.value + delta + count) % count;
|
||||||
|
scrollGoToColumnSelectionIntoView();
|
||||||
|
}
|
||||||
|
|
||||||
function onGoToColumnKeydown(event: KeyboardEvent) {
|
function onGoToColumnKeydown(event: KeyboardEvent) {
|
||||||
if (event.key === "Escape") {
|
if (event.key === "ArrowDown") {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
moveGoToColumnSelection(1);
|
||||||
|
} else if (event.key === "ArrowUp") {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
moveGoToColumnSelection(-1);
|
||||||
|
} else if (event.key === "Enter") {
|
||||||
|
const selected = filteredGoToColumns.value[goToColumnSelectedIndex.value];
|
||||||
|
if (!selected) return;
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
scrollToColumn(selected.index);
|
||||||
|
} else if (event.key === "Escape") {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
goToColumnOpen.value = false;
|
goToColumnOpen.value = false;
|
||||||
goToColumnSearch.value = "";
|
goToColumnSearch.value = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(goToColumnOpen, (open) => {
|
watch(goToColumnOpen, (open) => {
|
||||||
if (!open) goToColumnSearch.value = "";
|
if (!open) {
|
||||||
|
goToColumnSearch.value = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
goToColumnSelectedIndex.value = 0;
|
||||||
|
void nextTick(() => {
|
||||||
|
focusGoToColumnSearch();
|
||||||
|
scrollGoToColumnSelectionIntoView();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(goToColumnSearch, () => {
|
||||||
|
goToColumnSelectedIndex.value = 0;
|
||||||
|
scrollGoToColumnSelectionIntoView();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(filteredGoToColumns, (columns) => {
|
||||||
|
if (goToColumnSelectedIndex.value >= columns.length) goToColumnSelectedIndex.value = Math.max(columns.length - 1, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
function matchesTableInfoColumn(resultColumn: string, sourceColumn: string | undefined, columnName: string): boolean {
|
function matchesTableInfoColumn(resultColumn: string, sourceColumn: string | undefined, columnName: string): boolean {
|
||||||
@@ -9355,14 +9419,13 @@ async function onGridKeydown(event: KeyboardEvent) {
|
|||||||
openTableStructureEditor();
|
openTableStructureEditor();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!targetAllowsNativeClipboard && displayableColumnIndexes.value.length > 0 && isGoToColumnShortcut(event, settingsStore.editorSettings.shortcuts)) {
|
if (!targetAllowsNativeClipboard && isGoToColumnShortcut(event, settingsStore.editorSettings.shortcuts) && openGoToColumn()) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
goToColumnOpen.value = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!targetAllowsNativeClipboard && handleGridPaginationShortcut(event)) return;
|
if (!targetAllowsNativeClipboard && handleGridPaginationShortcut(event)) return;
|
||||||
if (isFocusSearchShortcut(event)) {
|
if (isFocusSearchShortcut(event) && !isGoToColumnShortcut(event, settingsStore.editorSettings.shortcuts)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
focusSearch();
|
focusSearch();
|
||||||
return;
|
return;
|
||||||
@@ -11721,6 +11784,7 @@ defineExpose({
|
|||||||
setMultiRowTranspose,
|
setMultiRowTranspose,
|
||||||
toggleMultiRowTranspose,
|
toggleMultiRowTranspose,
|
||||||
focusSearch,
|
focusSearch,
|
||||||
|
openGoToColumn,
|
||||||
visibleColumnCount,
|
visibleColumnCount,
|
||||||
displayableColumnCount,
|
displayableColumnCount,
|
||||||
hiddenColumnCount,
|
hiddenColumnCount,
|
||||||
@@ -12292,13 +12356,23 @@ function openGridSnapshot() {
|
|||||||
<PopoverContent align="end" class="w-56 p-2" @keydown="onGoToColumnKeydown">
|
<PopoverContent align="end" class="w-56 p-2" @keydown="onGoToColumnKeydown">
|
||||||
<div class="relative mb-1">
|
<div class="relative mb-1">
|
||||||
<Search class="pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
|
<Search class="pointer-events-none absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
|
||||||
<input v-model="goToColumnSearch" :placeholder="t('grid.searchColumn')" class="h-8 w-full rounded-md border bg-transparent pl-7 pr-6 text-xs outline-none focus-visible:border-ring/50 focus-visible:ring-1 focus-visible:ring-ring/25" />
|
<input ref="goToColumnSearchInput" v-model="goToColumnSearch" :placeholder="t('grid.searchColumn')" class="h-8 w-full rounded-md border bg-transparent pl-7 pr-6 text-xs outline-none focus-visible:border-ring/50 focus-visible:ring-1 focus-visible:ring-ring/25" />
|
||||||
<button v-if="goToColumnSearch" type="button" class="absolute right-1.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground" @click="goToColumnSearch = ''">
|
<button v-if="goToColumnSearch" type="button" class="absolute right-1.5 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground" @click="goToColumnSearch = ''">
|
||||||
<X class="h-3.5 w-3.5" />
|
<X class="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="max-h-56 overflow-auto rounded border">
|
<div ref="goToColumnListRef" class="max-h-56 overflow-auto rounded border">
|
||||||
<button v-for="column in filteredGoToColumns" :key="column.index" type="button" class="grid w-full grid-cols-[minmax(0,1fr)_auto] items-center gap-x-2 gap-y-0.5 px-2 py-1.5 text-left text-xs hover:bg-accent hover:text-accent-foreground" @click="scrollToColumn(column.index)">
|
<button
|
||||||
|
v-for="(column, index) in filteredGoToColumns"
|
||||||
|
:key="column.index"
|
||||||
|
type="button"
|
||||||
|
:class="[
|
||||||
|
'grid w-full grid-cols-[minmax(0,1fr)_auto] items-center gap-x-2 gap-y-0.5 px-2 py-1.5 text-left text-xs hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground focus-visible:outline-none',
|
||||||
|
index === goToColumnSelectedIndex ? 'bg-accent text-accent-foreground' : '',
|
||||||
|
]"
|
||||||
|
@pointerenter="goToColumnSelectedIndex = index"
|
||||||
|
@click="scrollToColumn(column.index)"
|
||||||
|
>
|
||||||
<span class="min-w-0 truncate">{{ column.name }}</span>
|
<span class="min-w-0 truncate">{{ column.name }}</span>
|
||||||
<span class="shrink-0 font-mono text-[10px] text-muted-foreground">#{{ column.index + 1 }}</span>
|
<span class="shrink-0 font-mono text-[10px] text-muted-foreground">#{{ column.index + 1 }}</span>
|
||||||
<span v-if="column.comment" class="col-span-2 min-w-0 truncate text-[11px] leading-4 text-muted-foreground" :title="column.comment">{{ column.comment }}</span>
|
<span v-if="column.comment" class="col-span-2 min-w-0 truncate text-[11px] leading-4 text-muted-foreground" :title="column.comment">{{ column.comment }}</span>
|
||||||
|
|||||||
+56
-8
@@ -30,6 +30,7 @@ import DataGrid from "../DataGrid.vue";
|
|||||||
import { useSettingsStore } from "@/stores/settingsStore";
|
import { useSettingsStore } from "@/stores/settingsStore";
|
||||||
|
|
||||||
const dataGridSource = readFileSync(resolve(process.cwd(), "apps/desktop/src/components/grid/DataGrid.vue"), "utf8");
|
const dataGridSource = readFileSync(resolve(process.cwd(), "apps/desktop/src/components/grid/DataGrid.vue"), "utf8");
|
||||||
|
const appSource = readFileSync(resolve(process.cwd(), "apps/desktop/src/App.vue"), "utf8");
|
||||||
const mountedApps: Array<{ app: App; host: HTMLElement }> = [];
|
const mountedApps: Array<{ app: App; host: HTMLElement }> = [];
|
||||||
|
|
||||||
const RecycleScroller = defineComponent({
|
const RecycleScroller = defineComponent({
|
||||||
@@ -49,7 +50,7 @@ const RecycleScroller = defineComponent({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function mountGrid(options: { displayableColumns?: boolean } = {}) {
|
function mountGrid(options: { displayableColumns?: boolean; columns?: string[] } = {}) {
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
setActivePinia(pinia);
|
setActivePinia(pinia);
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
@@ -61,8 +62,8 @@ function mountGrid(options: { displayableColumns?: boolean } = {}) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const result = markRaw<QueryResult>({
|
const result = markRaw<QueryResult>({
|
||||||
columns: ["id"],
|
columns: options.columns ?? ["id"],
|
||||||
rows: [[1]],
|
rows: [Array.from({ length: (options.columns ?? ["id"]).length }, (_, index) => index + 1)],
|
||||||
affected_rows: 0,
|
affected_rows: 0,
|
||||||
execution_time_ms: 0,
|
execution_time_ms: 0,
|
||||||
hidden_column_indexes: options.displayableColumns === false ? [0] : undefined,
|
hidden_column_indexes: options.displayableColumns === false ? [0] : undefined,
|
||||||
@@ -115,6 +116,12 @@ function goToColumnButton(host: HTMLElement): HTMLButtonElement {
|
|||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function goToColumnItem(name: string, position: number): HTMLButtonElement {
|
||||||
|
const button = [...document.querySelectorAll<HTMLButtonElement>("button")].find((candidate) => candidate.textContent?.replaceAll(/\s/g, "") === `${name}#${position}`);
|
||||||
|
if (!button) throw new Error(`Go-to-column item ${name} not found`);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
function goToColumnEvent(target: HTMLElement): KeyboardEvent {
|
function goToColumnEvent(target: HTMLElement): KeyboardEvent {
|
||||||
const event = new KeyboardEvent("keydown", { bubbles: true, cancelable: true, ctrlKey: true, key: "g" });
|
const event = new KeyboardEvent("keydown", { bubbles: true, cancelable: true, ctrlKey: true, key: "g" });
|
||||||
target.dispatchEvent(event);
|
target.dispatchEvent(event);
|
||||||
@@ -153,6 +160,30 @@ describe("DataGrid go-to-column shortcut", () => {
|
|||||||
expect(document.activeElement?.getAttribute("placeholder")).toBe("Search column/comment...");
|
expect(document.activeElement?.getAttribute("placeholder")).toBe("Search column/comment...");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("moves the lookup selection with arrows and chooses it with Enter", async () => {
|
||||||
|
const { host } = mountGrid({ columns: ["id", "name"] });
|
||||||
|
await settle();
|
||||||
|
const root = gridRoot(host);
|
||||||
|
root.focus();
|
||||||
|
goToColumnEvent(root);
|
||||||
|
await settle();
|
||||||
|
|
||||||
|
const input = document.activeElement as HTMLInputElement;
|
||||||
|
const down = new KeyboardEvent("keydown", { bubbles: true, cancelable: true, key: "ArrowDown" });
|
||||||
|
input.dispatchEvent(down);
|
||||||
|
await settle();
|
||||||
|
|
||||||
|
expect(down.defaultPrevented).toBe(true);
|
||||||
|
expect(goToColumnItem("name", 2).classList).toContain("bg-accent");
|
||||||
|
|
||||||
|
const enter = new KeyboardEvent("keydown", { bubbles: true, cancelable: true, key: "Enter" });
|
||||||
|
input.dispatchEvent(enter);
|
||||||
|
await settle();
|
||||||
|
|
||||||
|
expect(enter.defaultPrevented).toBe(true);
|
||||||
|
expect(goToColumnButton(host).getAttribute("aria-expanded")).toBe("false");
|
||||||
|
});
|
||||||
|
|
||||||
it("does not consume the configured shortcut without a displayable column", async () => {
|
it("does not consume the configured shortcut without a displayable column", async () => {
|
||||||
const { host } = mountGrid({ displayableColumns: false });
|
const { host } = mountGrid({ displayableColumns: false });
|
||||||
await settle();
|
await settle();
|
||||||
@@ -205,18 +236,20 @@ describe("DataGrid go-to-column shortcut", () => {
|
|||||||
expect(goToColumnButton(host).getAttribute("aria-expanded")).toBe("false");
|
expect(goToColumnButton(host).getAttribute("aria-expanded")).toBe("false");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("opens the existing popover only for a handled root shortcut", () => {
|
it("opens the existing popover through the shared grid action", () => {
|
||||||
const keydown = functionBody("onGridKeydown", "copyDetailValue");
|
const keydown = functionBody("onGridKeydown", "copyDetailValue");
|
||||||
|
|
||||||
expect(dataGridSource).toMatch(/<div\b(?=[^>]*\bdata-grid-root)(?=[^>]*\btabindex="0")(?=[^>]*@keydown="onGridKeydown")[^>]*>/);
|
expect(dataGridSource).toMatch(/<div\b(?=[^>]*\bdata-grid-root)(?=[^>]*\btabindex="0")(?=[^>]*@keydown="onGridKeydown")[^>]*>/);
|
||||||
expect(keydown).toMatch(
|
expect(keydown).toMatch(
|
||||||
/const targetAllowsNativeClipboard = eventTargetAllowsNativeClipboard\(event\);[\s\S]*?if \(!targetAllowsNativeClipboard && displayableColumnIndexes\.value\.length > 0 && isGoToColumnShortcut\(event, settingsStore\.editorSettings\.shortcuts\)\) \{[\s\S]*?event\.preventDefault\(\);[\s\S]*?event\.stopPropagation\(\);[\s\S]*?goToColumnOpen\.value = true;[\s\S]*?return;[\s\S]*?\}/,
|
/const targetAllowsNativeClipboard = eventTargetAllowsNativeClipboard\(event\);[\s\S]*?if \(!targetAllowsNativeClipboard && isGoToColumnShortcut\(event, settingsStore\.editorSettings\.shortcuts\) && openGoToColumn\(\)\) \{[\s\S]*?event\.preventDefault\(\);[\s\S]*?event\.stopPropagation\(\);[\s\S]*?return;[\s\S]*?\}/,
|
||||||
);
|
);
|
||||||
|
expect(dataGridSource).toContain("function openGoToColumn(): boolean");
|
||||||
|
expect(dataGridSource).toContain("if (!displayableColumnIndexes.value.length) return false;");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("leaves editable targets and unhandled events untouched by the shortcut", () => {
|
it("keeps editable targets available for the application-level fallback", () => {
|
||||||
const keydown = functionBody("onGridKeydown", "copyDetailValue");
|
const keydown = functionBody("onGridKeydown", "copyDetailValue");
|
||||||
const shortcutStart = keydown.indexOf("if (!targetAllowsNativeClipboard && displayableColumnIndexes.value.length > 0 && isGoToColumnShortcut");
|
const shortcutStart = keydown.indexOf("if (!targetAllowsNativeClipboard && isGoToColumnShortcut");
|
||||||
const shortcutEnd = keydown.indexOf("if (isFocusSearchShortcut", shortcutStart);
|
const shortcutEnd = keydown.indexOf("if (isFocusSearchShortcut", shortcutStart);
|
||||||
const shortcutBranch = keydown.slice(shortcutStart, shortcutEnd);
|
const shortcutBranch = keydown.slice(shortcutStart, shortcutEnd);
|
||||||
|
|
||||||
@@ -226,7 +259,16 @@ describe("DataGrid go-to-column shortcut", () => {
|
|||||||
expect(shortcutBranch.match(/stopPropagation/g)).toHaveLength(1);
|
expect(shortcutBranch.match(/stopPropagation/g)).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps toolbar navigation and the existing search/filter/reveal path", () => {
|
it("gives the data-tab fallback priority over conflicting global shortcuts", () => {
|
||||||
|
const keydown = functionBody("onGridKeydown", "copyDetailValue");
|
||||||
|
|
||||||
|
expect(keydown).toMatch(/if \(isFocusSearchShortcut\(event\) && !isGoToColumnShortcut\(event, settingsStore\.editorSettings\.shortcuts\)\)/);
|
||||||
|
expect(appSource).toMatch(
|
||||||
|
/const shortcuts = settingsStore\.editorSettings\.shortcuts;[\s\S]*?if \(showTabSwitcher\.value\) return;[\s\S]*?if \(isGoToColumnShortcut\(e, shortcuts\) && contentAreaRef\.value\?\.openGoToColumn\(\)\) \{[\s\S]*?return;[\s\S]*?const tabSwitcherDirection = tabSwitcherDirectionFromShortcut\(e, shortcuts\);/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps toolbar navigation and adds keyboard selection to the lookup", () => {
|
||||||
const selectColumn = functionBody("scrollToColumn", "onGoToColumnKeydown");
|
const selectColumn = functionBody("scrollToColumn", "onGoToColumnKeydown");
|
||||||
const escape = functionBody("onGoToColumnKeydown", "matchesTableInfoColumn");
|
const escape = functionBody("onGoToColumnKeydown", "matchesTableInfoColumn");
|
||||||
const scroll = functionBody("scrollToColumnIndex", "measureColumnHeaderText");
|
const scroll = functionBody("scrollToColumnIndex", "measureColumnHeaderText");
|
||||||
@@ -234,9 +276,15 @@ describe("DataGrid go-to-column shortcut", () => {
|
|||||||
expect(dataGridSource.match(/<Popover v-model:open="goToColumnOpen">/g)).toHaveLength(1);
|
expect(dataGridSource.match(/<Popover v-model:open="goToColumnOpen">/g)).toHaveLength(1);
|
||||||
expect(dataGridSource).toContain('>{{ t("grid.goToColumn") }}</span');
|
expect(dataGridSource).toContain('>{{ t("grid.goToColumn") }}</span');
|
||||||
expect(dataGridSource).toContain('v-model="goToColumnSearch"');
|
expect(dataGridSource).toContain('v-model="goToColumnSearch"');
|
||||||
|
expect(dataGridSource).toContain('ref="goToColumnSearchInput"');
|
||||||
|
expect(dataGridSource).toContain('ref="goToColumnListRef"');
|
||||||
expect(dataGridSource).toContain("filterDataGridColumnLookupItems(goToColumnItems.value, goToColumnSearch.value)");
|
expect(dataGridSource).toContain("filterDataGridColumnLookupItems(goToColumnItems.value, goToColumnSearch.value)");
|
||||||
|
expect(dataGridSource).toContain("const goToColumnSelectedIndex = ref(0);");
|
||||||
expect(selectColumn).toContain('goToColumnSearch.value = ""');
|
expect(selectColumn).toContain('goToColumnSearch.value = ""');
|
||||||
expect(selectColumn).toContain("scrollToColumnIndex(columnIndex)");
|
expect(selectColumn).toContain("scrollToColumnIndex(columnIndex)");
|
||||||
|
expect(escape).toMatch(/event\.key === "ArrowDown"[\s\S]*?moveGoToColumnSelection\(1\)/);
|
||||||
|
expect(escape).toMatch(/event\.key === "ArrowUp"[\s\S]*?moveGoToColumnSelection\(-1\)/);
|
||||||
|
expect(escape).toMatch(/event\.key === "Enter"[\s\S]*?scrollToColumn\(selected\.index\)/);
|
||||||
expect(escape).toMatch(/event\.key === "Escape"[\s\S]*?goToColumnOpen\.value = false;[\s\S]*?goToColumnSearch\.value = ""/);
|
expect(escape).toMatch(/event\.key === "Escape"[\s\S]*?goToColumnOpen\.value = false;[\s\S]*?goToColumnSearch\.value = ""/);
|
||||||
expect(scroll).toContain("if (hiddenColumnIndexes.value.has(columnIndex))");
|
expect(scroll).toContain("if (hiddenColumnIndexes.value.has(columnIndex))");
|
||||||
expect(scroll).toContain("showColumn(columnIndex)");
|
expect(scroll).toContain("showColumn(columnIndex)");
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ import { connectionIsEffectivelyReadOnly } from "@/lib/database/readOnlyWriteAcc
|
|||||||
type DataGridHandle = DataGridColumnLayoutHandle & {
|
type DataGridHandle = DataGridColumnLayoutHandle & {
|
||||||
onToolbarRefresh: () => Promise<void> | void;
|
onToolbarRefresh: () => Promise<void> | void;
|
||||||
focusSearch: () => boolean;
|
focusSearch: () => boolean;
|
||||||
|
openGoToColumn: () => boolean;
|
||||||
openCellDetailSearch: () => boolean;
|
openCellDetailSearch: () => boolean;
|
||||||
nullColumnsHidden: boolean;
|
nullColumnsHidden: boolean;
|
||||||
allNullColumnCount: number;
|
allNullColumnCount: number;
|
||||||
@@ -902,6 +903,11 @@ function focusSearch(): boolean {
|
|||||||
return dataGridRef.value?.focusSearch() ?? false;
|
return dataGridRef.value?.focusSearch() ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openGoToColumn(): boolean {
|
||||||
|
if (props.activeTab.mode !== "data") return false;
|
||||||
|
return dataGridRef.value?.openGoToColumn() ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
function refreshQueryEditorCompletionCache(): boolean {
|
function refreshQueryEditorCompletionCache(): boolean {
|
||||||
if (props.activeTab.mode !== "query" || !queryEditorRef.value) return false;
|
if (props.activeTab.mode !== "query" || !queryEditorRef.value) return false;
|
||||||
queryEditorRef.value.refreshCompletionCache();
|
queryEditorRef.value.refreshCompletionCache();
|
||||||
@@ -1163,6 +1169,7 @@ async function executeRedisCommand(command: string): Promise<boolean> {
|
|||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
focusSearch,
|
focusSearch,
|
||||||
|
openGoToColumn,
|
||||||
refreshData,
|
refreshData,
|
||||||
toggleResultsPane,
|
toggleResultsPane,
|
||||||
refreshQueryEditorCompletionCache,
|
refreshQueryEditorCompletionCache,
|
||||||
|
|||||||
Reference in new issue
Block a user