Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
98 changes: 65 additions & 33 deletions src/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,40 +34,71 @@ export interface MenuItemProps
itemData?: ItemData;
}

// Since Menu event provide the `info.item` which point to the MenuItem node instance.
// We have to use class component here.
// This should be removed from doc & api in future.
class LegacyMenuItem extends React.Component<any> {
render() {
const { title, attribute, elementRef, ...restProps } = this.props;

// Here the props are eventually passed to the DOM element.
// React does not recognize non-standard attributes.
// Therefore, remove the props that is not used here.
// ref: https://github.com/ant-design/ant-design/issues/41395
const passedProps = omit(restProps, [
'eventKey',
'popupClassName',
'popupOffset',
'onTitleClick',
]);
warning(!attribute, '`attribute` of Menu.Item is deprecated. Please pass attribute directly.');

return (
<Overflow.Item
{...attribute}
title={typeof title === 'string' ? title : undefined}
{...passedProps}
ref={elementRef}
/>
);
}
}
type LegacyMenuItemProps = Omit<React.ComponentProps<typeof Overflow.Item>, 'title' | 'ref'> & {
title?: React.ReactNode;
attribute?: Record<string, string>;
elementRef?: React.Ref<HTMLLIElement>;
eventKey?: string;
popupClassName?: string;
popupOffset?: number[];
onTitleClick?: () => void;
};

type LegacyMenuItemHandle = {
props: LegacyMenuItemProps;
element: HTMLLIElement | null;
};

// Keep exposing a legacy-compatible handle for deprecated `info.item`.
// This should be removed together with the deprecated API in future.
const LegacyMenuItem = React.forwardRef<LegacyMenuItemHandle, LegacyMenuItemProps>((props, ref) => {
const { title, attribute, elementRef, ...restProps } = props;
const propsRef = React.useRef(props);
const domRef = React.useRef<HTMLLIElement>(null);

propsRef.current = props;

React.useImperativeHandle(
ref,
() => ({
get props() {
return propsRef.current;
},
get element() {
return domRef.current;
},
}),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
[],
);

// Here the props are eventually passed to the DOM element.
// React does not recognize non-standard attributes.
// Therefore, remove the props that is not used here.
// ref: https://github.com/ant-design/ant-design/issues/41395
const passedProps = omit(restProps, [
'eventKey',
'popupClassName',
'popupOffset',
'onTitleClick',
]);
const mergedRef = useComposeRef(elementRef, domRef);

warning(!attribute, '`attribute` of Menu.Item is deprecated. Please pass attribute directly.');

return (
<Overflow.Item
{...attribute}
title={typeof title === 'string' ? title : undefined}
{...passedProps}
ref={mergedRef}
/>
);
});

/**
* Real Menu Item component
*/
const InternalMenuItem = React.forwardRef((props: MenuItemProps, ref: React.Ref<HTMLElement>) => {
const InternalMenuItem = React.forwardRef((props: MenuItemProps, ref: React.Ref<HTMLLIElement>) => {
const {
style,
className,
Expand Down Expand Up @@ -117,7 +148,7 @@ const InternalMenuItem = React.forwardRef((props: MenuItemProps, ref: React.Ref<

const itemCls = `${prefixCls}-item`;

const legacyMenuItemRef = React.useRef<any>();
const legacyMenuItemRef = React.useRef<LegacyMenuItemHandle | null>(null);
const elementRef = React.useRef<HTMLLIElement>();
const mergedDisabled = contextDisabled || disabled;

Expand Down Expand Up @@ -147,7 +178,7 @@ const InternalMenuItem = React.forwardRef((props: MenuItemProps, ref: React.Ref<
key: eventKey,
// Note: For legacy code is reversed which not like other antd component
keyPath: [...connectedKeys].reverse(),
item: legacyMenuItemRef.current,
item: legacyMenuItemRef.current as unknown as React.ReactInstance,
domEvent: e,
itemData: propsItemData || itemData,
};
Expand Down Expand Up @@ -214,6 +245,7 @@ const InternalMenuItem = React.forwardRef((props: MenuItemProps, ref: React.Ref<
<LegacyMenuItem
ref={legacyMenuItemRef}
elementRef={mergedEleRef}
eventKey={eventKey}
role={role === null ? 'none' : role || 'menuitem'}
tabIndex={disabled ? null : -1}
data-menu-id={overflowDisabled && domDataId ? null : domDataId}
Expand Down Expand Up @@ -257,7 +289,7 @@ const InternalMenuItem = React.forwardRef((props: MenuItemProps, ref: React.Ref<
return renderNode;
});

function MenuItem(props: MenuItemProps, ref: React.Ref<HTMLElement>): React.ReactElement {
function MenuItem(props: MenuItemProps, ref: React.Ref<HTMLLIElement>): React.ReactElement {
const { eventKey } = props;

// ==================== Record KeyPath ====================
Expand Down
4 changes: 2 additions & 2 deletions src/utils/warnUtil.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { warning } from '@rc-component/util';
Comment thread
ZQDesigned marked this conversation as resolved.

/**
* `onClick` event return `info.item` which point to react node directly.
* We should warning this since it will not work on FC.
* `onClick` still exposes deprecated `info.item` for backward compatibility.
* Keep warning since function components no longer provide a React node instance.
*/
export function warnItemProp<T extends { item: React.ReactInstance }>({ item, ...restInfo }: T): T {
Object.defineProperty(restInfo, 'item', {
Expand Down
4 changes: 3 additions & 1 deletion tests/Menu.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,10 @@ describe('Menu', () => {

fireEvent.click(container.querySelector('.rc-menu-item'));
const info = handleClick.mock.calls[0][0];
const legacyItem = info.item as unknown as { props: { eventKey?: string } };
expect(info.key).toBe('1');
expect(info.item).toBeTruthy();
expect(legacyItem).toBeTruthy();
expect(legacyItem.props.eventKey).toBe('1');
Comment thread
ZQDesigned marked this conversation as resolved.
Outdated

expect(errorSpy).toHaveBeenCalledWith(
'Warning: `info.item` is deprecated since we will move to function component that not provides React Node instance in future.',
Expand Down