fix(grid): stop stealing focus after cell edit commit on blur
This commit is contained in:
7 files changed
+74
-4
No files matched your search
@@ -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 |
Reference in new issue
Block a user