diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ConversationModelSelect.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ConversationModelSelect.tsx index b4acba71b..6869ede23 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/ConversationModelSelect.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/ConversationModelSelect.tsx @@ -25,6 +25,7 @@ export function ConversationModelSelect({ const [error, setError] = useState(''); const [open, setOpen] = useState(false); const initializedRef = useRef(false); + const containerRef = useRef(null); const refresh = useCallback(async () => { setBusy(true); setError(''); @@ -63,6 +64,27 @@ export function ConversationModelSelect({ void refresh(); }, [refresh, lazy]); + useEffect(() => { + if (!open) return; + function handleOutsidePointerDown(event: MouseEvent) { + const target = event.target as Node | null; + if (containerRef.current && !containerRef.current.contains(target)) { + setOpen(false); + } + } + function handleEscape(event: KeyboardEvent) { + if (event.key === 'Escape') { + setOpen(false); + } + } + document.addEventListener('mousedown', handleOutsidePointerDown); + document.addEventListener('keydown', handleEscape); + return () => { + document.removeEventListener('mousedown', handleOutsidePointerDown); + document.removeEventListener('keydown', handleEscape); + }; + }, [open]); + async function select(id: string) { onReady?.(false); setBusy(true); @@ -87,6 +109,7 @@ export function ConversationModelSelect({ return (
{ await screen.findByText('模型选择保存失败'); expect(onReady).toHaveBeenLastCalledWith(false); }); + +test('closes the menu when clicking outside', async () => { + const onReady = vi.fn(); + render(); + await screen.findByRole('button', { name: '对话模型' }); + fireEvent.click(screen.getByRole('button', { name: '对话模型' })); + expect(screen.getByRole('option', { name: '快速' })).not.toBeNull(); + + fireEvent.mouseDown(document.body); + await waitFor(() => + expect(screen.queryByRole('option', { name: '快速' })).toBeNull(), + ); +}); + +test('closes the menu on Escape', async () => { + const onReady = vi.fn(); + render(); + await screen.findByRole('button', { name: '对话模型' }); + fireEvent.click(screen.getByRole('button', { name: '对话模型' })); + expect(screen.getByRole('option', { name: '快速' })).not.toBeNull(); + + fireEvent.keyDown(document, { key: 'Escape' }); + await waitFor(() => + expect(screen.queryByRole('option', { name: '快速' })).toBeNull(), + ); +});