[bug][editor] Удаление строки/колонки таблицы из всплывающего меню нельзя отменить через Ctrl+Z (теряется фокус редактора) #269
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Симптом
Удаление строки или колонки таблицы через всплывающие меню (меню «ручек» строки/колонки и меню шеврона ячейки) невозможно отменить через Ctrl+Z — нажатие не даёт никакого эффекта.
Важно: данные на самом деле отменяемы. Если после удаления кликнуть в любую ячейку (вернуть фокус в редактор) и нажать Ctrl+Z — отмена срабатывает. То есть транзакция корректно попадает в историю; ломается именно доставка горячей клавиши до редактора.
Шаги воспроизведения
Корневая причина
Все эти меню — Mantine
<Menu>(v8). По умолчанию уMenuстоитtrapFocus: true, и при закрытии он передаётreturnFocus: true(см.@mantine/core/.../Menu/Menu.mjs). «Ручки» строки/колонки и шеврон ячейки рендерятся в плавающем слое (withinPortal) вне contenteditable редактора.Последовательность при клике на «Delete column»:
editor.chain().focus().deleteColumn().run()— фокусирует редактор и удаляет колонку (транзакция уходит в историю Yjs/UndoManager);closeOnItemClickмгновенно закрывает меню, аreturnFocus: trueвозвращает фокус обратно на ручку — DOM-элемент<div role="button">, который находится вне редактора;Undo (Ctrl+Z) в ProseMirror — это keymap, который срабатывает только когда фокус внутри редактора. Фокус на ручке → событие Ctrl+Z до ProseMirror не доходит → отмены нет.
Это объясняет, почему:
table-menu.tsx(на tippy/floating) — там фокус после клика не перехватывается;Затронутые места
apps/client/src/features/editor/components/table/handle/column-handle.tsx— Mantine Menu колонкиapps/client/src/features/editor/components/table/handle/row-handle.tsx— Mantine Menu строкиapps/client/src/features/editor/components/table/handle/cell-chevron.tsx— Mantine Menu ячейки (свойonClose)apps/client/src/features/editor/components/table/handle/hooks/use-column-row-menu-lifecycle.ts— общийonOpen/onCloseдля строк и колонокmenus/column-handle-menu.tsx,menus/row-handle-menu.tsx,menus/cell-chevron-menu.tsx(все вызываютeditor.chain().focus().deleteColumn()/deleteRow())Предлагаемое исправление
Возвращать фокус в редактор после закрытия меню — в обработчике
onClose(он уже есть и вызываетunfreezeHandles). Делать это отложенно (requestAnimationFrame), чтобы сработать после возврата фокуса Mantine на ручку, и с защитой, чтобы не «красть» фокус, если пользователь намеренно перешёл в другое поле:Точки подключения (общий хук покрывает строки и колонки одновременно):
use-column-row-menu-lifecycle.ts→ вonCloseпослеunfreezeHandles;cell-chevron.tsx→ вonCloseпослеunfreezeHandles.Альтернатива (менее предпочтительна): задать
returnFocus={false}на<Menu>— но из-заtrapFocusсинхронный.focus()в onClick может не «прилипнуть», поэтому отложенный возврат фокуса всё равно нужен.Критерии приёмки (DoD)
Окружение
Collaboration(undo через Yjs UndoManager).@mantine/core8.3.18.develop.