模型选择下拉支持点击外部与 Esc 自动收起
为 ConversationModelSelect 增加点击外部关闭和 Esc 关闭,首页与项目右侧对话共用生效 补充点击外部与 Esc 收起的回归测试
This commit is contained in:
@@ -25,6 +25,7 @@ export function ConversationModelSelect({
|
||||
const [error, setError] = useState('');
|
||||
const [open, setOpen] = useState(false);
|
||||
const initializedRef = useRef(false);
|
||||
const containerRef = useRef<HTMLDivElement | null>(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 (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={
|
||||
className
|
||||
? `conversation-model-select ${className}`
|
||||
|
||||
@@ -88,3 +88,29 @@ test('a failed save keeps submission unavailable', async () => {
|
||||
await screen.findByText('模型选择保存失败');
|
||||
expect(onReady).toHaveBeenLastCalledWith(false);
|
||||
});
|
||||
|
||||
test('closes the menu when clicking outside', async () => {
|
||||
const onReady = vi.fn();
|
||||
render(<ConversationModelSelect disabled={false} onReady={onReady} />);
|
||||
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(<ConversationModelSelect disabled={false} onReady={onReady} />);
|
||||
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(),
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user