diff --git a/src/DropdownMenu.tsx b/src/DropdownMenu.tsx index 49f1ae9..2d53d53 100644 --- a/src/DropdownMenu.tsx +++ b/src/DropdownMenu.tsx @@ -1,8 +1,8 @@ -import Menu, { MenuItem } from '@rc-component/menu'; -import * as React from 'react'; +import Menu, { MenuItem, MenuRef } from '@rc-component/menu'; +import React, { useEffect, useRef } from 'react'; import MentionsContext from './MentionsContext'; import type { DataDrivenOptionProps } from './Mentions'; -interface DropdownMenuProps { +export interface DropdownMenuProps { prefixCls?: string; options: DataDrivenOptionProps[]; } @@ -24,9 +24,26 @@ function DropdownMenu(props: DropdownMenuProps) { const { prefixCls, options } = props; const activeOption = options[activeIndex] || {}; + const menuRef = useRef(null); + + // Monitor the changes in ActiveIndex and scroll to the visible area if there are any changes + useEffect(() => { + if (activeIndex === -1 || !menuRef.current) { + return; + } + + const activeItem = menuRef.current?.findItem?.({ key: activeOption.key }); + if (activeItem) { + activeItem.scrollIntoView({ + block: 'nearest', + inline: 'nearest', + }); + } + }, [activeIndex, activeOption.key]); return ( { diff --git a/tests/DropdownMenu.spec.tsx b/tests/DropdownMenu.spec.tsx new file mode 100644 index 0000000..9f45684 --- /dev/null +++ b/tests/DropdownMenu.spec.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { render, fireEvent } from '@testing-library/react'; +import Mentions, { UnstableContext } from '../src'; +import { expectMeasuring } from './util'; + +describe('DropdownMenu', () => { + // Generate 20 options for testing scrolling behavior + const generateOptions = Array.from({ length: 20 }).map((_, index) => ({ + value: `item-${index}`, + label: `item-${index}`, + })); + + // Setup component with UnstableContext for testing dropdown behavior + const { container } = render( + + + , + ); + + it('should scroll into view when navigating with keyboard', () => { + // Mock scrollIntoView since it's not implemented in JSDOM + const scrollIntoViewMock = jest + .spyOn(HTMLElement.prototype, 'scrollIntoView') + .mockImplementation(jest.fn()); + + // Press ArrowDown multiple times to make options overflow the visible area + for (let i = 0; i < 10; i++) { + fireEvent.keyDown(document.activeElement!, { key: 'ArrowDown' }); + } + + // Verify if scrollIntoView was called + expect(scrollIntoViewMock).toHaveBeenCalledWith({ + block: 'nearest', + inline: 'nearest', + }); + + // Press ArrowUp to verify scrolling up + for (let i = 0; i < 5; i++) { + fireEvent.keyDown(document.activeElement!, { key: 'ArrowUp' }); + } + + // Verify if scrollIntoView was called again + expect(scrollIntoViewMock).toHaveBeenCalledWith({ + block: 'nearest', + inline: 'nearest', + }); + + expectMeasuring(container); + + scrollIntoViewMock.mockRestore(); + }); +}); diff --git a/tests/setup.ts b/tests/setup.ts index d44227c..35235ae 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -4,3 +4,5 @@ global.ResizeObserver = class ResizeObserver { unobserve() {} disconnect() {} }; + +window.HTMLElement.prototype.scrollIntoView = jest.fn();