fix(grid): select cell editor text only once on focus

This commit is contained in:
zipg
2026-09-05 00:42:07 +08:00
committed by GitHub
parent c258fe86be
commit a9ce18aef1
2 changed files with 40 additions and 4 deletions
@@ -1,3 +1,4 @@
// @vitest-environment happy-dom
import { computed, nextTick, ref, type Ref } from "vue";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { clearDataGridPendingSnapshot, DATA_GRID_QUICK_ENTRY_DRAFT_ROW_ID, useDataGridEditor } from "@/composables/useDataGridEditor";
@@ -874,3 +875,31 @@ describe("useDataGridEditor saveChanges reload", () => {
expect(emit).not.toHaveBeenCalledWith("reload", expect.anything());
});
});
describe("useDataGridEditor cell edit focus", () => {
it("selects the editor value only on the first frame so fast typing is not clobbered (#7336)", async () => {
const editor = createEditor(undefined, true, undefined, undefined, [["long json value", "hidden", "last"]]);
const select = vi.fn();
const setSelectionRange = vi.fn();
const focus = vi.fn();
const input = { focus, select, setSelectionRange, dataset: {}, value: "long json value" };
const rafCallbacks: Array<(time: number) => void> = [];
vi.stubGlobal("document", { querySelector: () => input });
vi.stubGlobal("requestAnimationFrame", (callback: (time: number) => void) => {
rafCallbacks.push(callback);
return rafCallbacks.length;
});
editor.startEdit(0, 0);
await nextTick();
for (let frame = 0; frame < 3; frame += 1) {
const callbacks = rafCallbacks.splice(0);
callbacks.forEach((callback) => callback(0));
await nextTick();
}
expect(select).toHaveBeenCalledTimes(1);
expect(setSelectionRange).toHaveBeenCalledTimes(1);
vi.unstubAllGlobals();
});
});
@@ -361,13 +361,20 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
}
function focusEditInput(select = true) {
const focusInput = () => {
// `selectText` is tri-state: true selects the full value once when the
// editor opens, false places the caret at the end, and undefined only
// refocuses without touching the selection. The requestAnimationFrame
// retry below uses the undefined mode so a user who starts typing or
// moving the caret immediately after the editor opens is not clobbered by
// a second select-all on a later frame.
const focusInput = (selectText?: boolean) => {
if (typeof document === "undefined") return;
const scroller = getScrollerElement();
const root = scroller?.closest("[data-grid-root]");
const input = (root ?? document).querySelector(".cell-edit-input") as HTMLInputElement | HTMLTextAreaElement | null;
if (input) focusDataGridEditorWithoutScrolling(input, scroller);
if (select && input) {
if (selectText === undefined) return;
if (selectText && input) {
if (input instanceof HTMLTextAreaElement && input.dataset.expandedCellEditor === "true") {
// Expanded editors must match single-line editors: a double-click selects the whole value.
input.select();
@@ -382,11 +389,11 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
}
};
nextTick(() => {
focusInput();
focusInput(select);
if (typeof requestAnimationFrame === "undefined") return;
let attempts = 0;
const focusNextFrame = () => {
focusInput();
focusInput(undefined);
attempts += 1;
if (attempts < 3) requestAnimationFrame(focusNextFrame);
};