Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions src/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import useAccessibility from './hooks/useAccessibility';
import useUUID from './hooks/useUUID';
import { PathRegisterContext, PathUserContext } from './context/PathContext';
import useKeyRecords, { OVERFLOW_KEY } from './hooks/useKeyRecords';
import { IdContext } from './context/IdContext';
import { getMenuId, IdContext } from './context/IdContext';
import PrivateContext from './context/PrivateContext';
import { useImperativeHandle } from 'react';

Expand Down Expand Up @@ -235,13 +235,6 @@ const Menu = React.forwardRef<MenuRef, MenuProps>((props, ref) => {

const containerRef = React.useRef<HTMLUListElement>();

useImperativeHandle(ref, () => ({
list: containerRef.current,
focus: (options?: FocusOptions) => {
containerRef.current?.focus(options);
},
}));

const uuid = useUUID(id);

const isRtl = direction === 'rtl';
Expand Down Expand Up @@ -360,6 +353,21 @@ const Menu = React.forwardRef<MenuRef, MenuProps>((props, ref) => {
setMergedActiveKey(undefined);
});

useImperativeHandle(ref, () => ({
list: containerRef.current,
focus: options => {
const shouldFocusKey =
mergedActiveKey ?? childList.find(node => !node.props.disabled)?.key;
if (shouldFocusKey) {
containerRef.current
?.querySelector<HTMLLIElement>(
`li[data-menu-id='${getMenuId(uuid, shouldFocusKey as string)}']`,
)
?.focus?.(options);
}
},
}));

// ======================== Select ========================
// >>>>> Select keys
const [mergedSelectKeys, setMergedSelectKeys] = useMergedState(
Expand Down
4 changes: 4 additions & 0 deletions src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ export type SelectEventHandler = (info: SelectInfo) => void;
export type MenuClickEventHandler = (info: MenuInfo) => void;

export type MenuRef = {
/**
* Focus active child if any, or the first child which is not disabled will be focused.
* @param options
*/
focus: (options?: FocusOptions) => void;
list: HTMLUListElement;
};
18 changes: 16 additions & 2 deletions tests/Menu.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -663,14 +663,28 @@ describe('Menu', () => {
const menuRef = React.createRef();
const wrapper = mount(
<Menu ref={menuRef}>
<SubMenu key="bamboo" title="Disabled" disabled>
<MenuItem key="light">Disabled child</MenuItem>
</SubMenu>
<MenuItem key="light">Light</MenuItem>
</Menu>,
);
menuRef.current?.focus();

expect(document.activeElement).toBe(
wrapper.find('ul').first().getDOMNode(),
expect(document.activeElement).toBe(wrapper.find('li').last().getDOMNode());
});

it('should focus active item through ref', () => {
const menuRef = React.createRef();
const wrapper = mount(
<Menu ref={menuRef} activeKey="cat">
<MenuItem key="light">Light</MenuItem>
<MenuItem key="cat">Cat</MenuItem>
</Menu>,
);
menuRef.current?.focus();

expect(document.activeElement).toBe(wrapper.find('li').last().getDOMNode());
});
});
/* eslint-enable */
2 changes: 1 addition & 1 deletion tests/__snapshots__/SubMenu.spec.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Array [
<div>
<div
class="rc-menu-submenu rc-menu-submenu-popup custom-className"
style="opacity: 0; pointer-events: none;"
style="opacity: 0;"
>
<ul
class="rc-menu rc-menu-sub rc-menu-vertical"
Expand Down