feat(grid): add table info column navigation

This commit is contained in:
t8y2 2026-05-21 22:26:10 +08:00
parent a08d2aaab9
commit e9d58e5aef
2 changed files with 73 additions and 1 deletions

View File

@ -1070,6 +1070,8 @@ const gridRef = ref<HTMLDivElement>();
const headerRef = ref<HTMLDivElement>();
const gridScrollbarGutter = ref(0);
const hiddenColumnIndexes = ref<Set<number>>(new Set());
const highlightedColumnIndex = ref<number | null>(null);
let highlightedColumnTimer = 0;
const displayableColumnIndexes = computed(() =>
props.result.columns
.map((column, index) => ({ column, index }))
@ -1111,6 +1113,40 @@ const firstVisibleColumnIndex = computed(() => visibleColumnIndexes.value[0] ??
function actualColumnIndex(visibleColumnIndex: number): number {
return visibleColumnIndexes.value[visibleColumnIndex] ?? visibleColumnIndex;
}
function matchesTableInfoColumn(resultColumn: string, sourceColumn: string | undefined, columnName: string): boolean {
const target = columnName.toLocaleLowerCase();
return resultColumn.toLocaleLowerCase() === target || sourceColumn?.toLocaleLowerCase() === target;
}
function scrollToTableInfoColumn(columnName: string) {
const columnIndex = props.result.columns.findIndex((column, index) =>
matchesTableInfoColumn(column, props.sourceColumns?.[index], columnName),
);
if (columnIndex < 0 || !displayableColumnIndexes.value.includes(columnIndex)) return;
if (hiddenColumnIndexes.value.has(columnIndex)) {
hiddenColumnIndexes.value.delete(columnIndex);
hiddenColumnIndexes.value = new Set(hiddenColumnIndexes.value);
}
highlightedColumnIndex.value = columnIndex;
clearTimeout(highlightedColumnTimer);
highlightedColumnTimer = window.setTimeout(() => {
highlightedColumnIndex.value = null;
}, 1400);
nextTick(() => {
const visibleColIdx = visibleColumnIndexes.value.indexOf(columnIndex);
const scroller = gridRef.value?.querySelector<HTMLElement>(".data-grid-scroller");
const headerCell = headerRef.value?.querySelector<HTMLElement>(`[data-grid-column-index="${columnIndex}"]`);
if (visibleColIdx < 0 || !scroller || !headerCell) return;
const targetLeft = Math.max(0, headerCell.offsetLeft - scroller.clientWidth / 2 + headerCell.offsetWidth / 2);
scroller.scrollLeft = targetLeft;
if (headerRef.value) {
headerRef.value.scrollLeft = scroller.scrollLeft;
}
});
}
// --- Column resize composable ---
const { initColumnWidths, onResizeStart, autoFitColumn, columnVars, getIsResizing } = useDataGridColumnResize({
@ -2817,6 +2853,7 @@ onUnmounted(() => {
onDdlResizeEnd();
onDetailResizeEnd();
finishCellSelection();
clearTimeout(highlightedColumnTimer);
clearTimeout(_searchTimer);
clearInterval(_loadingTimer);
});
@ -3342,7 +3379,12 @@ defineExpose({
<TooltipTrigger as-child>
<div
class="shrink-0 px-2 py-1.5 border-r border-border whitespace-nowrap hover:bg-accent/60 select-none relative overflow-hidden"
:class="{
'bg-primary/15 ring-1 ring-inset ring-primary/40':
highlightedColumnIndex === actualColumnIndex(colIdx),
}"
:style="{ width: `var(--col-w-${colIdx})` }"
:data-grid-column-index="actualColumnIndex(colIdx)"
>
<span class="flex min-w-0 items-center gap-1 overflow-hidden">
<span
@ -3992,7 +4034,17 @@ defineExpose({
</tr>
</thead>
<tbody>
<tr v-for="column in tableMeta?.columns" :key="column.name" class="border-b hover:bg-muted/30">
<tr
v-for="column in tableMeta?.columns"
:key="column.name"
class="border-b cursor-pointer hover:bg-muted/30"
role="button"
tabindex="0"
:title="column.name"
@click="scrollToTableInfoColumn(column.name)"
@keydown.enter.prevent="scrollToTableInfoColumn(column.name)"
@keydown.space.prevent="scrollToTableInfoColumn(column.name)"
>
<td class="px-3 py-2 font-medium">
<span class="inline-flex items-center gap-1.5">
<KeyRound v-if="column.is_primary_key" class="h-3 w-3 text-amber-500" />

View File

@ -0,0 +1,20 @@
import { readFileSync } from "node:fs";
import { strict as assert } from "node:assert";
import test from "node:test";
const source = readFileSync("apps/desktop/src/components/grid/DataGrid.vue", "utf8");
test("table info column rows navigate the grid header", () => {
assert.match(source, /function scrollToTableInfoColumn\(columnName: string\)/);
assert.match(source, /@click="scrollToTableInfoColumn\(column\.name\)"/);
assert.match(source, /:data-grid-column-index="actualColumnIndex\(colIdx\)"/);
});
test("column navigation reveals hidden columns and keeps header scroll synchronized", () => {
const match = source.match(/function scrollToTableInfoColumn\(columnName: string\) \{([\s\S]*?)\n\}/);
assert.ok(match, "DataGrid should define scrollToTableInfoColumn");
assert.match(match[1], /hiddenColumnIndexes\.value\.delete\(columnIndex\)/);
assert.match(match[1], /\.data-grid-scroller/);
assert.match(match[1], /headerRef\.value\.scrollLeft = scroller\.scrollLeft/);
assert.match(match[1], /highlightedColumnIndex\.value = columnIndex/);
});