fix(grid): stop stealing focus after cell edit commit on blur

This commit is contained in:
zipg authored and GitHub committed 2026-09-21 11:37:38 +08:00
1 parent e364e4a70f
commit 566bfc3039
7 files changed
+74 -4

No files matched your search

+12 -3
View File
@@ -327,7 +327,7 @@ import { useConnectionStore } from "@/stores/connectionStore";
import { useQueryStore } from "@/stores/queryStore"; import { useQueryStore } from "@/stores/queryStore";
import { useSettingsStore } from "@/stores/settingsStore"; import { useSettingsStore } from "@/stores/settingsStore";
import { databaseSortSupportedForDatabase, simpleDataGridOrderByMatchesSort, simpleDataGridOrderByReferencesMissingColumn, type DataGridSortDirection, type DataGridSortMode } from "@/lib/dataGrid/dataGridSort"; import { databaseSortSupportedForDatabase, simpleDataGridOrderByMatchesSort, simpleDataGridOrderByReferencesMissingColumn, type DataGridSortDirection, type DataGridSortMode } from "@/lib/dataGrid/dataGridSort";
import { resolveGridFocusRestoreTarget } from "@/lib/dataGrid/dataGridFocusRestore"; import { resolveGridFocusRestoreTarget, shouldRestoreDataGridFocusAfterEditCommit } from "@/lib/dataGrid/dataGridFocusRestore";
import { buildOrderedGridRows, type GridInsertRowPosition, type GridNewRowPlacement } from "@/lib/dataGrid/gridNewRowPlacement"; import { buildOrderedGridRows, type GridInsertRowPosition, type GridNewRowPlacement } from "@/lib/dataGrid/gridNewRowPlacement";
import { import {
DATA_GRID_CONDITION_TOOLBAR_MIN_WIDTH, DATA_GRID_CONDITION_TOOLBAR_MIN_WIDTH,
@@ -8936,7 +8936,14 @@ function currentIoTDBTimestampEditValue(): CellValue | undefined {
function commitGridEdit(value?: CellValue) { function commitGridEdit(value?: CellValue) {
if (value === undefined) value = currentIoTDBTimestampEditValue(); if (value === undefined) value = currentIoTDBTimestampEditValue();
if (commitInlineBulkEdit(value)) return; if (commitInlineBulkEdit(value)) return;
void commitEditAndMaybeAutoSave(value === undefined ? undefined : { explicitValue: value }).finally(() => nextTick(() => gridRef.value?.focus({ preventScroll: true }))); void commitEditAndMaybeAutoSave(value === undefined ? undefined : { explicitValue: value }).finally(() =>
nextTick(() => {
// A click outside the grid (SQL editor, WHERE bar, toolbar) already moved focus on
// purpose. Only pull it back when the grid still owns it, otherwise the element the
// user just clicked loses the caret and swallows the following keystroke (#9383).
if (shouldRestoreDataGridFocusAfterEditCommit(gridRef.value, document.activeElement)) gridRef.value?.focus({ preventScroll: true });
}),
);
} }
function commitInlineBulkEdit(value?: CellValue): boolean { function commitInlineBulkEdit(value?: CellValue): boolean {
@@ -8945,7 +8952,9 @@ function commitInlineBulkEdit(value?: CellValue): boolean {
const nextValue = value === undefined ? editValue.value : value === null ? null : String(value); const nextValue = value === undefined ? editValue.value : value === null ? null : String(value);
cancelEdit(); cancelEdit();
fillSelectionWithValue(nextValue, { emptyStringAsNull: true }); fillSelectionWithValue(nextValue, { emptyStringAsNull: true });
nextTick(() => gridRef.value?.focus({ preventScroll: true })); nextTick(() => {
if (shouldRestoreDataGridFocusAfterEditCommit(gridRef.value, document.activeElement)) gridRef.value?.focus({ preventScroll: true });
});
return true; return true;
} }
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest"; import { describe, expect, it } from "vitest";
import { resolveGridFocusRestoreTarget } from "@/lib/dataGrid/dataGridFocusRestore"; import { resolveGridFocusRestoreTarget, shouldRestoreDataGridFocusAfterEditCommit } from "@/lib/dataGrid/dataGridFocusRestore";
function element(overrides: { connected?: boolean; children?: HTMLElement[]; closest?: Record<string, HTMLElement | null>; ownerDocument?: { body: HTMLElement; documentElement: HTMLElement }; activeGrid?: boolean } = {}) { function element(overrides: { connected?: boolean; children?: HTMLElement[]; closest?: Record<string, HTMLElement | null>; ownerDocument?: { body: HTMLElement; documentElement: HTMLElement }; activeGrid?: boolean } = {}) {
const children = overrides.children ?? []; const children = overrides.children ?? [];
@@ -66,3 +66,48 @@ describe("resolveGridFocusRestoreTarget", () => {
expect(resolveGridFocusRestoreTarget(root, searchInput, activeGridInput)).toBeNull(); expect(resolveGridFocusRestoreTarget(root, searchInput, activeGridInput)).toBeNull();
}); });
}); });
describe("shouldRestoreDataGridFocusAfterEditCommit", () => {
it("restores focus when the grid root is not mounted yet", () => {
expect(shouldRestoreDataGridFocusAfterEditCommit(null, null)).toBe(true);
expect(shouldRestoreDataGridFocusAfterEditCommit(undefined, null)).toBe(true);
});
it("keeps focus in the grid after a keyboard commit", () => {
const input = element();
const root = element({ children: [input] });
expect(shouldRestoreDataGridFocusAfterEditCommit(root, input)).toBe(true);
});
it("restores focus when the blur landed on body or the document element", () => {
const body = element();
const documentElement = element();
const root = element({ ownerDocument: { body, documentElement } });
expect(shouldRestoreDataGridFocusAfterEditCommit(root, body)).toBe(true);
expect(shouldRestoreDataGridFocusAfterEditCommit(root, documentElement)).toBe(true);
});
it("restores focus from a stale removed element", () => {
const root = element();
expect(shouldRestoreDataGridFocusAfterEditCommit(root, element({ connected: false }))).toBe(true);
});
it("restores focus when the tab strip took it", () => {
const root = element();
const tabButton = element({ closest: { ".app-tab-bar, [role='tab'], [role='tablist']": element() } });
expect(shouldRestoreDataGridFocusAfterEditCommit(root, tabButton)).toBe(true);
});
it("does not steal focus back from the SQL editor after an outside click", () => {
const root = element();
const sqlEditor = element();
expect(shouldRestoreDataGridFocusAfterEditCommit(root, sqlEditor)).toBe(false);
});
it("does not steal focus back from another active grid", () => {
const root = element();
const grid = element({ activeGrid: true });
const otherGridInput = element({ closest: { "[data-grid-root]": grid } });
expect(shouldRestoreDataGridFocusAfterEditCommit(root, otherGridInput)).toBe(false);
});
});
@@ -32,3 +32,19 @@ function gridFocusCanTransferFrom(root: HTMLElement, activeElement: Element | nu
if (activeGrid && activeGrid !== root) return activeGrid.dataset.gridActive !== "true"; if (activeGrid && activeGrid !== root) return activeGrid.dataset.gridActive !== "true";
return !!closest(".app-tab-bar, [role='tab'], [role='tablist']"); return !!closest(".app-tab-bar, [role='tab'], [role='tablist']");
} }
/**
* Decide whether a committed cell edit should hand focus back to the grid.
*
* Keyboard commits (Enter, inline bulk edit) leave focus inside the grid, so
* the grid must take it back for arrow-key navigation to keep working. A commit
* that ended because an outside pointer interaction moved focus elsewhere — the
* SQL editor, the WHERE condition bar, a toolbar button — must not, otherwise
* the element the user just clicked never keeps the caret, and the next
* keystroke or paste is claimed by the grid instead (#9383).
*/
export function shouldRestoreDataGridFocusAfterEditCommit(root: HTMLElement | null | undefined, activeElement: Element | null): boolean {
if (!root) return true;
if (root.contains(activeElement)) return true;
return gridFocusCanTransferFrom(root, activeElement);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB