fix(web): fix selection menu hide

This commit is contained in:
JzoNg 2026-04-20 16:08:58 +08:00
parent b6fbec066d
commit 8e2d507e5c
2 changed files with 63 additions and 52 deletions

View File

@ -25,7 +25,7 @@ vi.mock('@/next/navigation', () => ({
}), }),
})) }))
vi.mock('@/app/components/base/ui/toast', () => ({ vi.mock('@langgenius/dify-ui/toast', () => ({
toast: { toast: {
success: (...args: unknown[]) => mockToastSuccess(...args), success: (...args: unknown[]) => mockToastSuccess(...args),
error: (...args: unknown[]) => mockToastError(...args), error: (...args: unknown[]) => mockToastError(...args),
@ -39,13 +39,19 @@ vi.mock('@/service/use-snippets', () => ({
}), }),
})) }))
vi.mock('@/service/client', () => ({ vi.mock('@/service/client', async (importOriginal) => {
consoleClient: { const actual = await importOriginal<typeof import('@/service/client')>()
snippets: { return {
syncDraftWorkflow: (...args: unknown[]) => mockSyncDraftWorkflow(...args), ...actual,
consoleClient: {
...actual.consoleClient,
snippets: {
...actual.consoleClient.snippets,
syncDraftWorkflow: (...args: unknown[]) => mockSyncDraftWorkflow(...args),
},
}, },
}, }
})) })
vi.mock('../hooks', async () => { vi.mock('../hooks', async () => {
const actual = await vi.importActual<typeof import('../hooks')>('../hooks') const actual = await vi.importActual<typeof import('../hooks')>('../hooks')
@ -301,6 +307,8 @@ describe('SelectionContextmenu', () => {
}) })
fireEvent.click(screen.getByTestId('selection-contextmenu-item-createSnippet')) fireEvent.click(screen.getByTestId('selection-contextmenu-item-createSnippet'))
expect(store.getState().selectionMenu).toBeUndefined()
expect(screen.queryByTestId('selection-contextmenu-item-createSnippet')).not.toBeInTheDocument()
fireEvent.change(screen.getByPlaceholderText('workflow.snippet.namePlaceholder'), { fireEvent.change(screen.getByPlaceholderText('workflow.snippet.namePlaceholder'), {
target: { value: 'My snippet' }, target: { value: 'My snippet' },
}) })

View File

@ -335,6 +335,7 @@ const SelectionContextmenu = () => {
}), }),
} }
}, [selectionMenu]) }, [selectionMenu])
const isMenuOpen = Boolean(selectionMenu && anchor)
useEffect(() => { useEffect(() => {
if (selectionMenu && selectedNodes.length <= 1) if (selectionMenu && selectedNodes.length <= 1)
@ -561,57 +562,59 @@ const SelectionContextmenu = () => {
return ( return (
<div data-testid="selection-contextmenu"> <div data-testid="selection-contextmenu">
<ContextMenu <ContextMenu
open open={isMenuOpen}
onOpenChange={(open) => { onOpenChange={(open) => {
if (!open) if (!open)
handleSelectionContextmenuCancel() handleSelectionContextmenuCancel()
}} }}
> >
<ContextMenuContent {isMenuOpen && (
positionerProps={anchor ? { anchor } : undefined} <ContextMenuContent
popupClassName="w-[240px] py-0" positionerProps={{ anchor }}
> popupClassName="w-[240px] py-0"
<div className="p-1"> >
{menuActions.map(item => ( <div className="p-1">
<ContextMenuItem {menuActions.map(item => (
key={item.action} <ContextMenuItem
data-testid={`selection-contextmenu-item-${item.action}`} key={item.action}
disabled={item.disabled} data-testid={`selection-contextmenu-item-${item.action}`}
className={cn( disabled={item.disabled}
'mx-0 h-8 justify-between gap-3 rounded-lg px-2 text-[14px] leading-5 font-normal text-text-secondary', className={cn(
item.action === 'delete' && 'data-highlighted:bg-state-destructive-hover data-highlighted:text-text-destructive', 'mx-0 h-8 justify-between gap-3 rounded-lg px-2 text-[14px] leading-5 font-normal text-text-secondary',
)} item.action === 'delete' && 'data-highlighted:bg-state-destructive-hover data-highlighted:text-text-destructive',
onClick={() => handleMenuAction(item.action)} )}
> onClick={() => handleMenuAction(item.action)}
<span>{getActionLabel(item.translationKey)}</span> >
{item.shortcutKeys && ( <span>{getActionLabel(item.translationKey)}</span>
<ShortcutsName {item.shortcutKeys && (
keys={item.shortcutKeys} <ShortcutsName
textColor="secondary" keys={item.shortcutKeys}
/> textColor="secondary"
)} />
</ContextMenuItem> )}
))} </ContextMenuItem>
</div> ))}
<ContextMenuSeparator className="my-0" />
<div className="p-1.5">
<div className="flex items-center">
{alignMenuItems.map((item) => {
return (
<ContextMenuItem
key={item.alignType}
aria-label={t(item.translationKey, { defaultValue: item.translationKey, ns: 'workflow' })}
className="mx-0 h-8 w-8 justify-center rounded-md px-0 text-text-tertiary data-highlighted:bg-state-base-hover data-highlighted:text-text-secondary"
data-testid={`selection-contextmenu-item-${item.alignType}`}
onClick={() => handleAlignNodes(item.alignType)}
>
<span aria-hidden className={`${item.icon} h-4 w-4 ${item.iconClassName ?? ''}`.trim()} />
</ContextMenuItem>
)
})}
</div> </div>
</div> <ContextMenuSeparator className="my-0" />
</ContextMenuContent> <div className="p-1.5">
<div className="flex items-center">
{alignMenuItems.map((item) => {
return (
<ContextMenuItem
key={item.alignType}
aria-label={t(item.translationKey, { defaultValue: item.translationKey, ns: 'workflow' })}
className="mx-0 h-8 w-8 justify-center rounded-md px-0 text-text-tertiary data-highlighted:bg-state-base-hover data-highlighted:text-text-secondary"
data-testid={`selection-contextmenu-item-${item.alignType}`}
onClick={() => handleAlignNodes(item.alignType)}
>
<span aria-hidden className={`${item.icon} h-4 w-4 ${item.iconClassName ?? ''}`.trim()} />
</ContextMenuItem>
)
})}
</div>
</div>
</ContextMenuContent>
)}
</ContextMenu> </ContextMenu>
{isCreateSnippetDialogOpen && ( {isCreateSnippetDialogOpen && (
<CreateSnippetDialog <CreateSnippetDialog