fix(editor): preserve editor selection during result auto refresh

This commit is contained in:
hanxuanyu authored and GitHub committed 2026-09-05 22:17:01 +08:00
1 parent eab1f6344b
commit 4bef571fc4
2 files changed
+26 -1

No files matched your search

@@ -2,7 +2,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
import { beginDataGridNativeSelectionBlock, DATA_GRID_NATIVE_SELECTION_BLOCK_CLASS, DATA_GRID_NATIVE_SELECTION_RELEASE_DELAY_MS, finishDataGridNativeSelectionBlock } from "@/lib/dataGrid/dataGridNativeSelection";
function selectionEnvironment() {
function selectionEnvironment(options: { editableActiveElement?: boolean } = {}) {
const classes = new Set<string>();
const removeAllRanges = vi.fn();
return {
@@ -16,6 +16,11 @@ function selectionEnvironment() {
remove: (className: string) => classes.delete(className),
},
},
activeElement: options.editableActiveElement
? {
closest: vi.fn(() => ({})),
}
: null,
},
getSelection: () => ({ removeAllRanges }),
setTimeout,
@@ -39,6 +44,16 @@ describe("data grid native selection block", () => {
expect(removeAllRanges).toHaveBeenCalledOnce();
});
it("preserves the native selection while an editable control is focused", () => {
const { classes, removeAllRanges, environment } = selectionEnvironment({ editableActiveElement: true });
const owner = {};
beginDataGridNativeSelectionBlock(owner, environment);
expect(classes.has(DATA_GRID_NATIVE_SELECTION_BLOCK_CLASS)).toBe(true);
expect(removeAllRanges).not.toHaveBeenCalled();
});
it("keeps the application-level block across a fast component replacement", () => {
vi.useFakeTimers();
const { classes, environment } = selectionEnvironment();
@@ -9,6 +9,9 @@ interface NativeSelectionBlockEnvironment {
remove(className: string): void;
};
};
activeElement?: {
closest?(selector: string): unknown;
} | null;
};
getSelection?: () => { removeAllRanges(): void } | null;
setTimeout(callback: () => void, delay: number): ReturnType<typeof setTimeout>;
@@ -40,10 +43,17 @@ function hasActiveBlockForEnvironment(environment: NativeSelectionBlockEnvironme
return [...activeSelectionBlocks.values()].some((activeBlock) => activeBlock.environment.document?.documentElement === documentElement);
}
function activeElementUsesNativeSelection(environment: NativeSelectionBlockEnvironment): boolean {
return !!environment.document?.activeElement?.closest?.("input, textarea, [contenteditable='true'], [role='textbox']");
}
export function beginDataGridNativeSelectionBlock(owner: DataGridNativeSelectionBlockOwner, environment: NativeSelectionBlockEnvironment = defaultEnvironment()) {
clearReleaseTimer(owner);
activeSelectionBlocks.set(owner, { environment });
environment.document?.documentElement?.classList.add(DATA_GRID_NATIVE_SELECTION_BLOCK_CLASS);
// Clearing the document selection while a text editor is focused can leave
// CodeMirror's caret visible but detach the browser's active editing range.
if (activeElementUsesNativeSelection(environment)) return;
environment.getSelection?.()?.removeAllRanges();
}