1- import * as React from 'react' ;
21import classNames from 'classnames' ;
32import Overflow from 'rc-overflow' ;
4- import warning from 'rc-util/lib/warning' ;
53import KeyCode from 'rc-util/lib/KeyCode' ;
64import omit from 'rc-util/lib/omit' ;
7- import type { MenuInfo , MenuItemType } from './interface' ;
5+ import { useComposeRef } from 'rc-util/lib/ref' ;
6+ import warning from 'rc-util/lib/warning' ;
7+ import * as React from 'react' ;
8+ import { useMenuId } from './context/IdContext' ;
89import { MenuContext } from './context/MenuContext' ;
9- import useActive from './hooks/useActive' ;
10- import { warnItemProp } from './utils/warnUtil' ;
11- import Icon from './Icon' ;
12- import useDirectionStyle from './hooks/useDirectionStyle' ;
1310import { useFullPath , useMeasure } from './context/PathContext' ;
14- import { useMenuId } from './context/IdContext' ;
1511import PrivateContext from './context/PrivateContext' ;
12+ import useActive from './hooks/useActive' ;
13+ import useDirectionStyle from './hooks/useDirectionStyle' ;
14+ import Icon from './Icon' ;
15+ import type { MenuInfo , MenuItemType } from './interface' ;
16+ import { warnItemProp } from './utils/warnUtil' ;
1617
1718export interface MenuItemProps
1819 extends Omit < MenuItemType , 'label' | 'key' > ,
@@ -38,12 +39,17 @@ export interface MenuItemProps
3839class LegacyMenuItem extends React . Component < any > {
3940 render ( ) {
4041 const { title, attribute, elementRef, ...restProps } = this . props ;
41-
42+
4243 // Here the props are eventually passed to the DOM element.
4344 // React does not recognize non-standard attributes.
4445 // Therefore, remove the props that is not used here.
4546 // ref: https://github.com/ant-design/ant-design/issues/41395
46- const passedProps = omit ( restProps , [ 'eventKey' , 'popupClassName' , 'popupOffset' , 'onTitleClick' ] ) ;
47+ const passedProps = omit ( restProps , [
48+ 'eventKey' ,
49+ 'popupClassName' ,
50+ 'popupOffset' ,
51+ 'onTitleClick' ,
52+ ] ) ;
4753 warning (
4854 ! attribute ,
4955 '`attribute` of Menu.Item is deprecated. Please pass attribute directly.' ,
@@ -63,184 +69,191 @@ class LegacyMenuItem extends React.Component<any> {
6369/**
6470 * Real Menu Item component
6571 */
66- const InternalMenuItem = ( props : MenuItemProps ) => {
67- const {
68- style,
69- className,
70-
71- eventKey,
72- warnKey,
73- disabled,
74- itemIcon,
75- children,
72+ const InternalMenuItem = React . forwardRef (
73+ ( props : MenuItemProps , ref : React . Ref < HTMLElement > ) => {
74+ const {
75+ style,
76+ className,
7677
77- // Aria
78- role,
78+ eventKey,
79+ warnKey,
80+ disabled,
81+ itemIcon,
82+ children,
7983
80- // Active
81- onMouseEnter,
82- onMouseLeave,
84+ // Aria
85+ role,
8386
84- onClick,
85- onKeyDown,
87+ // Active
88+ onMouseEnter,
89+ onMouseLeave,
8690
87- onFocus,
91+ onClick,
92+ onKeyDown,
8893
89- ...restProps
90- } = props ;
94+ onFocus,
9195
92- const domDataId = useMenuId ( eventKey ) ;
96+ ...restProps
97+ } = props ;
9398
94- const {
95- prefixCls,
96- onItemClick,
99+ const domDataId = useMenuId ( eventKey ) ;
97100
98- disabled : contextDisabled ,
99- overflowDisabled,
101+ const {
102+ prefixCls,
103+ onItemClick,
100104
101- // Icon
102- itemIcon : contextItemIcon ,
105+ disabled : contextDisabled ,
106+ overflowDisabled ,
103107
104- // Select
105- selectedKeys ,
108+ // Icon
109+ itemIcon : contextItemIcon ,
106110
107- // Active
108- onActive,
109- } = React . useContext ( MenuContext ) ;
111+ // Select
112+ selectedKeys,
110113
111- const { _internalRenderMenuItem } = React . useContext ( PrivateContext ) ;
114+ // Active
115+ onActive,
116+ } = React . useContext ( MenuContext ) ;
112117
113- const itemCls = ` ${ prefixCls } -item` ;
118+ const { _internalRenderMenuItem } = React . useContext ( PrivateContext ) ;
114119
115- const legacyMenuItemRef = React . useRef < any > ( ) ;
116- const elementRef = React . useRef < HTMLLIElement > ( ) ;
117- const mergedDisabled = contextDisabled || disabled ;
120+ const itemCls = `${ prefixCls } -item` ;
118121
119- const connectedKeys = useFullPath ( eventKey ) ;
120-
121- // ================================ Warn ================================
122- if ( process . env . NODE_ENV !== 'production' && warnKey ) {
123- warning ( false , 'MenuItem should not leave undefined `key`.' ) ;
124- }
122+ const legacyMenuItemRef = React . useRef < any > ( ) ;
123+ const elementRef = React . useRef < HTMLLIElement > ( ) ;
124+ const mergedDisabled = contextDisabled || disabled ;
125125
126- // ============================= Info =============================
127- const getEventInfo = (
128- e : React . MouseEvent < HTMLElement > | React . KeyboardEvent < HTMLElement > ,
129- ) : MenuInfo => {
130- return {
131- key : eventKey ,
132- // Note: For legacy code is reversed which not like other antd component
133- keyPath : [ ...connectedKeys ] . reverse ( ) ,
134- item : legacyMenuItemRef . current ,
135- domEvent : e ,
136- } ;
137- } ;
126+ const mergedEleRef = useComposeRef ( ref , elementRef ) ;
138127
139- // ============================= Icon =============================
140- const mergedItemIcon = itemIcon || contextItemIcon ;
128+ const connectedKeys = useFullPath ( eventKey ) ;
141129
142- // ============================ Active ============================
143- const { active, ...activeProps } = useActive (
144- eventKey ,
145- mergedDisabled ,
146- onMouseEnter ,
147- onMouseLeave ,
148- ) ;
130+ // ================================ Warn ================================
131+ if ( process . env . NODE_ENV !== 'production' && warnKey ) {
132+ warning ( false , 'MenuItem should not leave undefined `key`.' ) ;
133+ }
149134
150- // ============================ Select ============================
151- const selected = selectedKeys . includes ( eventKey ) ;
135+ // ============================= Info =============================
136+ const getEventInfo = (
137+ e : React . MouseEvent < HTMLElement > | React . KeyboardEvent < HTMLElement > ,
138+ ) : MenuInfo => {
139+ return {
140+ key : eventKey ,
141+ // Note: For legacy code is reversed which not like other antd component
142+ keyPath : [ ...connectedKeys ] . reverse ( ) ,
143+ item : legacyMenuItemRef . current ,
144+ domEvent : e ,
145+ } ;
146+ } ;
152147
153- // ======================== DirectionStyle ========================
154- const directionStyle = useDirectionStyle ( connectedKeys . length ) ;
148+ // ============================= Icon ===== ========================
149+ const mergedItemIcon = itemIcon || contextItemIcon ;
155150
156- // ============================ Events ============================
157- const onInternalClick : React . MouseEventHandler < HTMLLIElement > = e => {
158- if ( mergedDisabled ) {
159- return ;
160- }
151+ // ============================ Active ============================
152+ const { active, ...activeProps } = useActive (
153+ eventKey ,
154+ mergedDisabled ,
155+ onMouseEnter ,
156+ onMouseLeave ,
157+ ) ;
161158
162- const info = getEventInfo ( e ) ;
159+ // ============================ Select ============================
160+ const selected = selectedKeys . includes ( eventKey ) ;
163161
164- onClick ?.( warnItemProp ( info ) ) ;
165- onItemClick ( info ) ;
166- } ;
162+ // ======================== DirectionStyle ========================
163+ const directionStyle = useDirectionStyle ( connectedKeys . length ) ;
167164
168- const onInternalKeyDown : React . KeyboardEventHandler < HTMLLIElement > = e => {
169- onKeyDown ?.( e ) ;
165+ // ============================ Events ============================
166+ const onInternalClick : React . MouseEventHandler < HTMLLIElement > = e => {
167+ if ( mergedDisabled ) {
168+ return ;
169+ }
170170
171- if ( e . which === KeyCode . ENTER ) {
172171 const info = getEventInfo ( e ) ;
173172
174- // Legacy. Key will also trigger click event
175173 onClick ?.( warnItemProp ( info ) ) ;
176174 onItemClick ( info ) ;
175+ } ;
176+
177+ const onInternalKeyDown : React . KeyboardEventHandler < HTMLLIElement > = e => {
178+ onKeyDown ?.( e ) ;
179+
180+ if ( e . which === KeyCode . ENTER ) {
181+ const info = getEventInfo ( e ) ;
182+
183+ // Legacy. Key will also trigger click event
184+ onClick ?.( warnItemProp ( info ) ) ;
185+ onItemClick ( info ) ;
186+ }
187+ } ;
188+
189+ /**
190+ * Used for accessibility. Helper will focus element without key board.
191+ * We should manually trigger an active
192+ */
193+ const onInternalFocus : React . FocusEventHandler < HTMLLIElement > = e => {
194+ onActive ( eventKey ) ;
195+ onFocus ?.( e ) ;
196+ } ;
197+
198+ // ============================ Render ============================
199+ const optionRoleProps : React . HTMLAttributes < HTMLDivElement > = { } ;
200+
201+ if ( props . role === 'option' ) {
202+ optionRoleProps [ 'aria-selected' ] = selected ;
177203 }
178- } ;
179-
180- /**
181- * Used for accessibility. Helper will focus element without key board.
182- * We should manually trigger an active
183- */
184- const onInternalFocus : React . FocusEventHandler < HTMLLIElement > = e => {
185- onActive ( eventKey ) ;
186- onFocus ?.( e ) ;
187- } ;
188-
189- // ============================ Render ============================
190- const optionRoleProps : React . HTMLAttributes < HTMLDivElement > = { } ;
191-
192- if ( props . role === 'option' ) {
193- optionRoleProps [ 'aria-selected' ] = selected ;
194- }
195204
196- let renderNode = (
197- < LegacyMenuItem
198- ref = { legacyMenuItemRef }
199- elementRef = { elementRef }
200- role = { role === null ? 'none' : role || 'menuitem' }
201- tabIndex = { disabled ? null : - 1 }
202- data-menu-id = { overflowDisabled && domDataId ? null : domDataId }
203- { ...restProps }
204- { ...activeProps }
205- { ...optionRoleProps }
206- component = "li"
207- aria-disabled = { disabled }
208- style = { {
209- ...directionStyle ,
210- ...style ,
211- } }
212- className = { classNames (
213- itemCls ,
214- {
215- [ `${ itemCls } -active` ] : active ,
216- [ `${ itemCls } -selected` ] : selected ,
217- [ `${ itemCls } -disabled` ] : mergedDisabled ,
218- } ,
219- className ,
220- ) }
221- onClick = { onInternalClick }
222- onKeyDown = { onInternalKeyDown }
223- onFocus = { onInternalFocus }
224- >
225- { children }
226- < Icon
227- props = { {
228- ...props ,
229- isSelected : selected ,
205+ let renderNode = (
206+ < LegacyMenuItem
207+ ref = { legacyMenuItemRef }
208+ elementRef = { mergedEleRef }
209+ role = { role === null ? 'none' : role || 'menuitem' }
210+ tabIndex = { disabled ? null : - 1 }
211+ data-menu-id = { overflowDisabled && domDataId ? null : domDataId }
212+ { ...restProps }
213+ { ...activeProps }
214+ { ...optionRoleProps }
215+ component = "li"
216+ aria-disabled = { disabled }
217+ style = { {
218+ ...directionStyle ,
219+ ...style ,
230220 } }
231- icon = { mergedItemIcon }
232- />
233- </ LegacyMenuItem >
234- ) ;
221+ className = { classNames (
222+ itemCls ,
223+ {
224+ [ `${ itemCls } -active` ] : active ,
225+ [ `${ itemCls } -selected` ] : selected ,
226+ [ `${ itemCls } -disabled` ] : mergedDisabled ,
227+ } ,
228+ className ,
229+ ) }
230+ onClick = { onInternalClick }
231+ onKeyDown = { onInternalKeyDown }
232+ onFocus = { onInternalFocus }
233+ >
234+ { children }
235+ < Icon
236+ props = { {
237+ ...props ,
238+ isSelected : selected ,
239+ } }
240+ icon = { mergedItemIcon }
241+ />
242+ </ LegacyMenuItem >
243+ ) ;
235244
236- if ( _internalRenderMenuItem ) {
237- renderNode = _internalRenderMenuItem ( renderNode , props , { selected } ) ;
238- }
245+ if ( _internalRenderMenuItem ) {
246+ renderNode = _internalRenderMenuItem ( renderNode , props , { selected } ) ;
247+ }
239248
240- return renderNode ;
241- } ;
249+ return renderNode ;
250+ } ,
251+ ) ;
242252
243- function MenuItem ( props : MenuItemProps ) : React . ReactElement {
253+ function MenuItem (
254+ props : MenuItemProps ,
255+ ref : React . Ref < HTMLElement > ,
256+ ) : React . ReactElement {
244257 const { eventKey } = props ;
245258
246259 // ==================== Record KeyPath ====================
@@ -263,7 +276,7 @@ function MenuItem(props: MenuItemProps): React.ReactElement {
263276 }
264277
265278 // ======================== Render ========================
266- return < InternalMenuItem { ...props } /> ;
279+ return < InternalMenuItem { ...props } ref = { ref } /> ;
267280}
268281
269- export default MenuItem ;
282+ export default React . forwardRef ( MenuItem ) ;
0 commit comments