дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
fix(editor): keep the scroll position when the editor gets focus back on macOS (#2201)
On macOS, closing the editor's context menu could scroll the editor by a screenful and put the caret on an unrelated line. A right-click moves focus to the menu during the mouse press, so WebKit keeps the editor's DOM caret; redraws while the menu is open move that caret, and refocusing scrolls to it despite `preventScroll`. Focus tracking now drops the DOM caret left in a CodeMirror editor when focus moves elsewhere in the window. CodeMirror restores its own selection on refocus, and other editable elements are untouched. Refs #1215 Co-authored-by: lleviathe <[email protected]>
Цей коміт міститься в:
1 джерело
1dbebefa94
коміт
ccd1f3aca1
2 files changed
+54
No files matched your search
@@ -38,6 +38,42 @@ describe("focus after popups", () => {
|
||||
dispose();
|
||||
});
|
||||
|
||||
it("drops the editor's caret when focus moves to a menu, so focusing the editor again cannot scroll to it", () => {
|
||||
document.body.innerHTML = `<div class="cm-content" contenteditable="true" tabindex="0">first line</div><button id="other">x</button>`;
|
||||
const editor = document.querySelector<HTMLElement>(".cm-content")!;
|
||||
const dispose = installFocusTracking(() => {});
|
||||
editor.focus();
|
||||
document.getSelection()!.collapse(editor.firstChild, 3);
|
||||
// WebKit keeps the caret during a mouse press; jsdom moves the selection on focus, so check
|
||||
// what is left once the editor has lost focus.
|
||||
let caretLeft: Node | null = null;
|
||||
const probe = () => (caretLeft = document.getSelection()!.anchorNode);
|
||||
document.addEventListener("focusout", probe);
|
||||
document.getElementById("other")!.focus();
|
||||
document.removeEventListener("focusout", probe);
|
||||
expect(caretLeft).toBeNull();
|
||||
dispose();
|
||||
});
|
||||
|
||||
it.each([
|
||||
'<div contenteditable="true" tabindex="0">draft message</div>',
|
||||
'<div class="cm-content" contenteditable="false" tabindex="0">preview text</div>',
|
||||
])("keeps the selection in an unrelated editable or read-only preview: %s", (markup) => {
|
||||
document.body.innerHTML = `${markup}<button id="other">x</button>`;
|
||||
const content = document.body.firstElementChild as HTMLElement;
|
||||
const dispose = installFocusTracking(() => {});
|
||||
content.focus();
|
||||
const selection = document.getSelection()!;
|
||||
selection.setBaseAndExtent(content.firstChild!, 1, content.firstChild!, 5);
|
||||
let selectedOnBlur = "";
|
||||
const probe = () => (selectedOnBlur = selection.toString());
|
||||
document.addEventListener("focusout", probe);
|
||||
document.getElementById("other")!.focus();
|
||||
document.removeEventListener("focusout", probe);
|
||||
dispose();
|
||||
expect(selectedOnBlur).toBe(content.textContent!.slice(1, 5));
|
||||
});
|
||||
|
||||
it("leaves focus alone when the popup handed it to another component", () => {
|
||||
vi.useFakeTimers();
|
||||
setup();
|
||||
|
||||
@@ -26,6 +26,23 @@ function focusEditorNow() {
|
||||
target?.focus({ preventScroll: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* WebKit keeps the DOM caret of an editor that loses focus during a mouse press, as when a
|
||||
* right-click opens the context menu. The editor redraws its lines while unfocused, which moves
|
||||
* that caret to an unrelated line, and focusing the editor again scrolls to it even with
|
||||
* `preventScroll`. Without it, focusing puts the caret at the start of the content without
|
||||
* scrolling and CodeMirror restores its own selection.
|
||||
*/
|
||||
function dropEditorCaret(e: FocusEvent) {
|
||||
const editor = e.target;
|
||||
if (!(editor instanceof HTMLElement) || !editor.matches('.cm-content[contenteditable="true"]')) return;
|
||||
// The window lost focus and keeps the editor focused for its return.
|
||||
if (document.activeElement === editor) return;
|
||||
if (e.relatedTarget instanceof Node && editor.contains(e.relatedTarget)) return;
|
||||
const selection = document.getSelection();
|
||||
if (selection?.anchorNode && editor.contains(selection.anchorNode)) selection.removeAllRanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks which workbench part holds focus and publishes `toolWindowFocus`,
|
||||
* `activeToolWindow` and `editorAreaFocus`. Returns an uninstaller.
|
||||
@@ -38,6 +55,7 @@ export function installFocusTracking(onToolWindowFocus: (id: string | null) => v
|
||||
setContext("editorAreaFocus", !!el?.closest?.(EDITOR_AREA_SELECTOR));
|
||||
};
|
||||
const onFocusOut = (e: FocusEvent) => {
|
||||
dropEditorCaret(e);
|
||||
if (e.relatedTarget) return;
|
||||
// Focus left to nowhere (window blur keeps document.activeElement).
|
||||
queueMicrotask(() => {
|
||||
|
||||
Reference in new issue
Заблокувати користувача