fix(grid): improve column drag scrolling and selection

This commit is contained in:
hanxuanyu authored and GitHub committed 2026-08-13 12:41:57 +08:00
1 parent 7eac5dd610
commit e4b9e55883
9 files changed
+539 -16

No files matched your search

+16 -1
View File
@@ -2123,6 +2123,16 @@ const gridViewportWidth = ref(0);
let gridScrollLeftBeforeTranspose = 0;
let gridScrollTopBeforeKeyboardTranspose: number | null = null;
let restoreGridScrollTopAfterTranspose = false;
function persistDraggedColumnOrder(indexes: number[]) {
const previousVisibleColumnIndexes = [...visibleColumnIndexes.value];
persistColumnOrder(indexes);
selection.remapColumnSelection(
previousVisibleColumnIndexes,
indexes.filter((index) => !hiddenColumnIndexes.value.has(index)),
);
}
const {
renderedColumnOffsets,
horizontalColumnWindow,
@@ -2147,14 +2157,19 @@ const {
viewportWidth: gridViewportWidth,
rowNumberWidth,
headerRef,
getScrollElement: gridScrollerElement,
orderedColumnIndexes: orderedDisplayableColumnIndexes,
hiddenColumnIndexes,
getIsResizing,
onResizeStart,
onCanvasMouseLeave,
onCanvasDrawSchedule: scheduleCanvasDraw,
onHorizontalScroll: (scroller) => {
updateGridHorizontalViewport(scroller);
if (headerRef.value) headerRef.value.scrollLeft = scroller.scrollLeft;
},
onRefreshMetrics: scheduleColumnLayoutRefresh,
onPersistColumnOrder: persistColumnOrder,
onPersistColumnOrder: persistDraggedColumnOrder,
frozenColumnCount,
});
@@ -20,9 +20,11 @@ const props = withDefaults(
defineProps<{
grid?: DataGridColumnLayoutHandle;
triggerClass?: string;
compact?: boolean;
}>(),
{
triggerClass: "",
compact: false,
},
);
@@ -299,14 +301,14 @@ onBeforeUnmount(resetColumnDragState);
variant="ghost"
size="sm"
class="h-5 shrink-0 gap-1 px-1.5 text-xs text-foreground hover:bg-accent"
:class="[triggerClass, { 'bg-accent text-foreground': (grid?.hiddenColumnCount ?? 0) > 0 }]"
:class="[triggerClass, compact ? 'w-6 gap-0 px-0' : '', { 'bg-accent text-foreground': (grid?.hiddenColumnCount ?? 0) > 0 }]"
:disabled="!grid"
:title="t('grid.columnVisibility')"
:aria-label="t('grid.columnVisibility')"
>
<Columns3 class="h-3.5 w-3.5" />
{{ t("grid.columnVisibility") }}
<span v-if="(grid?.hiddenColumnCount ?? 0) > 0" class="tabular-nums"> {{ grid?.visibleColumnCount }}/{{ grid?.displayableColumnCount }} </span>
<span v-if="!compact">{{ t("grid.columnVisibility") }}</span>
<span v-if="!compact && (grid?.hiddenColumnCount ?? 0) > 0" class="tabular-nums"> {{ grid?.visibleColumnCount }}/{{ grid?.displayableColumnCount }} </span>
</Button>
</PopoverTrigger>
<PopoverContent align="end" class="w-72 max-w-[calc(100vw-2rem)] gap-0 overflow-hidden rounded-md border bg-popover p-0 text-popover-foreground shadow-xl" @click.stop @keydown.stop>
@@ -1,5 +1,7 @@
// @vitest-environment happy-dom
import { readFileSync } from "node:fs";
import path from "node:path";
import { createApp, nextTick, type App } from "vue";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import i18n from "@/i18n";
@@ -39,6 +41,8 @@ vi.mock("@/components/ui/button", async () => {
import DataGridColumnLayoutPopover from "../DataGridColumnLayoutPopover.vue";
const contentAreaSource = readFileSync(path.resolve(process.cwd(), "apps/desktop/src/components/layout/ContentArea.vue"), "utf8");
const mountedApps: Array<{ app: App; host: HTMLElement }> = [];
function columnLayoutOptions(itemCount: number): DataGridColumnLayoutOption[] {
@@ -137,6 +141,23 @@ afterEach(() => {
});
describe("data grid column layout popover", () => {
it("is available from the query result toolbar and keeps a labelled compact trigger", async () => {
expect(contentAreaSource).toContain('<DataGridColumnLayoutPopover :grid="dataGridRef" :compact="compact" />');
const gridState = createGrid(4);
const host = document.createElement("div");
document.body.append(host);
const app = createApp(DataGridColumnLayoutPopover, { grid: gridState.grid, compact: true });
app.use(i18n);
app.mount(host);
mountedApps.push({ app, host });
await nextTick();
const trigger = [...host.querySelectorAll<HTMLButtonElement>("button")].find((button) => button.getAttribute("aria-label") === i18n.global.t("grid.columnVisibility"));
expect(trigger?.className).toContain("w-6");
expect(trigger?.textContent?.trim()).toBe("");
});
it("renders only the visible field window plus a bounded buffer", () => {
const window = dataGridColumnLayoutVirtualWindow({
itemCount: 500,
@@ -1622,6 +1622,7 @@ defineExpose({ focusSearch, refreshData, refreshQueryEditorCompletionCache, hand
</template>
</template>
<template #result-toolbar-actions="{ compact }">
<DataGridColumnLayoutPopover :grid="dataGridRef" :compact="compact" />
<QueryResultToolbarActions
:active-view="activeOutputView"
:can-show-explain="canShowExplainOutput"