fix-editor-sync-context-menu-selection-snapshot
This commit is contained in:
parent
9d95f34bd9
commit
ab84000919
@ -9,7 +9,7 @@ import {
|
||||
Tabs,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import { useCallback, useEffect, useMemo, useReducer, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';
|
||||
import { bindEditorHotkeys } from './hotkeys';
|
||||
import { Canvas } from './Canvas';
|
||||
import { ContextMenu, type ContextMenuState } from './ContextMenu';
|
||||
@ -48,17 +48,26 @@ export function EditorApp() {
|
||||
const selected = state.doc.screen.nodes.find((n) => n.id === state.selection.ids[0]);
|
||||
const hasSelection = state.selection.ids.length > 0;
|
||||
|
||||
const selection = useMemo(() => {
|
||||
const ids = new Set(state.selection.ids);
|
||||
// Note: selection lock/visibility flags for the context menu are computed from `selectionIdsForMenu` below.
|
||||
|
||||
// Context-menu parity: right-click selection updates happen via reducer dispatch,
|
||||
// but the context menu opens immediately.
|
||||
// Use the captured selectionIds from the context menu state to avoid a one-frame mismatch.
|
||||
const selectionIdsForMenu = ctxMenu?.selectionIds ?? state.selection.ids;
|
||||
|
||||
const selectionForMenu = useMemo(() => {
|
||||
const ids = new Set(selectionIdsForMenu);
|
||||
return state.doc.screen.nodes.filter((n) => ids.has(n.id));
|
||||
}, [state.doc.screen.nodes, state.selection.ids]);
|
||||
}, [selectionIdsForMenu, state.doc.screen.nodes]);
|
||||
|
||||
const selectionAllLocked = selection.length > 0 && selection.every((n) => n.locked);
|
||||
const selectionSomeLocked = selection.length > 0 && selection.some((n) => n.locked) && !selectionAllLocked;
|
||||
const selectionHasUnlocked = selection.length > 0 && selection.some((n) => !n.locked);
|
||||
const menuSelectionAllLocked = selectionForMenu.length > 0 && selectionForMenu.every((n) => n.locked);
|
||||
const menuSelectionSomeLocked =
|
||||
selectionForMenu.length > 0 && selectionForMenu.some((n) => n.locked) && !menuSelectionAllLocked;
|
||||
const menuSelectionHasUnlocked = selectionForMenu.length > 0 && selectionForMenu.some((n) => !n.locked);
|
||||
|
||||
const selectionAllHidden = selection.length > 0 && selection.every((n) => n.hidden);
|
||||
const selectionSomeHidden = selection.length > 0 && selection.some((n) => n.hidden) && !selectionAllHidden;
|
||||
const menuSelectionAllHidden = selectionForMenu.length > 0 && selectionForMenu.every((n) => n.hidden);
|
||||
const menuSelectionSomeHidden =
|
||||
selectionForMenu.length > 0 && selectionForMenu.some((n) => n.hidden) && !menuSelectionAllHidden;
|
||||
|
||||
const bounds = useMemo(
|
||||
() => ({ w: state.doc.screen.width, h: state.doc.screen.height }),
|
||||
@ -77,10 +86,29 @@ export function EditorApp() {
|
||||
|
||||
const closeContextMenu = useCallback(() => setCtxMenu(null), []);
|
||||
|
||||
// Selection parity: if selection changes via hotkeys/toolbar, close any open context menu.
|
||||
const ctxMenuSyncedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Reset the sync gate whenever a new context menu opens/closes.
|
||||
ctxMenuSyncedRef.current = false;
|
||||
}, [ctxMenu?.selectionKey]);
|
||||
|
||||
// Selection parity: if selection changes via hotkeys/toolbar *after* the context menu has opened,
|
||||
// close it. But don't close immediately during the one-frame gap where right-click selection
|
||||
// has not been reduced into state yet.
|
||||
useEffect(() => {
|
||||
if (!ctxMenu) return;
|
||||
|
||||
const currentKey = state.selection.ids.join('|');
|
||||
|
||||
// Wait until reducer state has caught up with the context menu's intended selection.
|
||||
if (!ctxMenuSyncedRef.current) {
|
||||
if (currentKey === ctxMenu.selectionKey) {
|
||||
ctxMenuSyncedRef.current = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentKey !== ctxMenu.selectionKey) {
|
||||
setCtxMenu(null);
|
||||
}
|
||||
@ -522,12 +550,12 @@ export function EditorApp() {
|
||||
|
||||
<ContextMenu
|
||||
state={ctxMenu}
|
||||
selectionIds={state.selection.ids}
|
||||
selectionAllLocked={selectionAllLocked}
|
||||
selectionSomeLocked={selectionSomeLocked}
|
||||
selectionHasUnlocked={selectionHasUnlocked}
|
||||
selectionAllHidden={selectionAllHidden}
|
||||
selectionSomeHidden={selectionSomeHidden}
|
||||
selectionIds={selectionIdsForMenu}
|
||||
selectionAllLocked={menuSelectionAllLocked}
|
||||
selectionSomeLocked={menuSelectionSomeLocked}
|
||||
selectionHasUnlocked={menuSelectionHasUnlocked}
|
||||
selectionAllHidden={menuSelectionAllHidden}
|
||||
selectionSomeHidden={menuSelectionSomeHidden}
|
||||
hasAnyNodes={state.doc.screen.nodes.length > 0}
|
||||
onClose={closeContextMenu}
|
||||
onAddTextAt={(x, y) => dispatch({ type: 'addTextAt', x, y })}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user