fix(editor): restore editor focus after table menu closes so Ctrl+Z works (closes #269)
The row/column grip and cell-chevron menus are Mantine <Menu>s with returnFocus:true whose targets live outside the editor's contenteditable. After a menu action focus returns to that outside target, so ProseMirror's undo keymap never sees Ctrl+Z until the user clicks back into a cell. Add refocusEditorAfterMenuClose(editor): on the next frame (after Mantine's returnFocus) restore editor focus via view.focus(), unless the user intentionally moved to another input/editable. Wired into both onClose paths (the shared row/column lifecycle hook + cell-chevron). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import clsx from "clsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { isCellSelection } from "@docmost/editor-ext";
|
||||
import { CellChevronMenu } from "./menus/cell-chevron-menu";
|
||||
import { refocusEditorAfterMenuClose } from "./hooks/use-column-row-menu-lifecycle";
|
||||
import classes from "./handle.module.css";
|
||||
|
||||
interface CellChevronProps {
|
||||
@@ -87,6 +88,7 @@ export const CellChevron = React.memo(function CellChevron({
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
editor.commands.unfreezeHandles();
|
||||
refocusEditorAfterMenuClose(editor);
|
||||
}, [editor]);
|
||||
|
||||
if (!cellDom) return null;
|
||||
|
||||
+34
@@ -11,6 +11,39 @@ interface Args {
|
||||
tablePos: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore focus to the editor after a table handle/cell menu closes.
|
||||
*
|
||||
* The grip/chevron menus are Mantine `<Menu>`s with `returnFocus: true`, and
|
||||
* their targets live in a floating/portaled layer OUTSIDE the editor's
|
||||
* contenteditable. After an action (delete row/column, insert, etc.) the menu
|
||||
* closes and Mantine returns focus to that outside target, so ProseMirror's
|
||||
* undo keymap never sees Ctrl+Z until the user clicks back into a cell.
|
||||
*
|
||||
* We defer with `requestAnimationFrame` so this runs AFTER Mantine's
|
||||
* returnFocus, and guard against stealing focus if the user intentionally
|
||||
* moved to another input/editable (e.g. the page title).
|
||||
*/
|
||||
export function refocusEditorAfterMenuClose(editor: Editor) {
|
||||
requestAnimationFrame(() => {
|
||||
if (editor.isDestroyed) return;
|
||||
const active = document.activeElement as HTMLElement | null;
|
||||
// Already inside the editor — nothing to do.
|
||||
if (active && editor.view.dom.contains(active)) return;
|
||||
// Respect a deliberate move to another field/editable.
|
||||
const tag = active?.tagName;
|
||||
if (
|
||||
tag === "INPUT" ||
|
||||
tag === "TEXTAREA" ||
|
||||
tag === "SELECT" ||
|
||||
active?.isContentEditable
|
||||
) {
|
||||
return;
|
||||
}
|
||||
editor.view.focus(); // pure DOM focus, no extra transaction
|
||||
});
|
||||
}
|
||||
|
||||
export function useColumnRowMenuLifecycle({
|
||||
editor,
|
||||
orientation,
|
||||
@@ -34,6 +67,7 @@ export function useColumnRowMenuLifecycle({
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
editor.commands.unfreezeHandles();
|
||||
refocusEditorAfterMenuClose(editor);
|
||||
}, [editor]);
|
||||
|
||||
return { onOpen, onClose };
|
||||
|
||||
Reference in New Issue
Block a user