11import React from 'react' ;
22import { Provider , create } from 'mini-store' ;
3+ import omit from 'omit.js' ;
34import SubPopupMenu , { getActiveKey } from './SubPopupMenu' ;
45import { noop } from './util' ;
56import {
@@ -47,6 +48,8 @@ export interface MenuProps
4748 overflowedIndicator ?: React . ReactNode ;
4849 /** Menu motion define */
4950 motion ?: MotionType ;
51+ /** Default menu motion */
52+ defaultMotion ?: MotionType ;
5053
5154 /** @deprecated Please use `motion` instead */
5255 openTransitionName ?: string ;
@@ -55,9 +58,19 @@ export interface MenuProps
5558
5659 /** direction of menu */
5760 direction ?: 'ltr' | 'rtl' ;
61+
62+ inlineCollapsed ?: boolean ;
63+
64+ /** SiderContextProps of layout in ant design */
65+ siderCollapsed ?: boolean ;
66+ collapsedWidth ?: string | number ;
67+ }
68+
69+ export interface MenuState {
70+ switchingModeFromInline : boolean ;
5871}
5972
60- class Menu extends React . Component < MenuProps > {
73+ class Menu extends React . Component < MenuProps , MenuState > {
6174 static defaultProps = {
6275 selectable : true ,
6376 onClick : noop ,
@@ -96,6 +109,10 @@ class Menu extends React.Component<MenuProps> {
96109 openKeys,
97110 activeKey : { '0-menu-' : getActiveKey ( props , props . activeKey ) } ,
98111 } ) ;
112+
113+ this . state = {
114+ switchingModeFromInline : false ,
115+ } ;
99116 }
100117
101118 isRootMenu : boolean ;
@@ -104,12 +121,89 @@ class Menu extends React.Component<MenuProps> {
104121
105122 innerMenu : typeof SubPopupMenu ;
106123
124+ inlineOpenKeys : string [ ] = [ ] ;
125+
126+ prevOpenKeys : string [ ] ;
127+
107128 componentDidMount ( ) {
108129 this . updateMiniStore ( ) ;
130+ this . updateMenuDisplay ( ) ;
109131 }
110132
111- componentDidUpdate ( ) {
133+ componentDidUpdate ( prevProps : MenuProps ) {
134+ this . updateOpentKeysWhenSwitchMode ( prevProps ) ;
112135 this . updateMiniStore ( ) ;
136+ const { siderCollapsed, inlineCollapsed, onOpenChange } = this . props ;
137+ if (
138+ ( ! prevProps . inlineCollapsed && inlineCollapsed ) ||
139+ ( ! prevProps . siderCollapsed && siderCollapsed )
140+ ) {
141+ onOpenChange ( [ ] ) ;
142+ }
143+ this . updateMenuDisplay ( ) ;
144+ }
145+
146+ updateOpentKeysWhenSwitchMode ( prevProps : MenuProps ) {
147+ const { props : nextProps , store, inlineOpenKeys } = this ;
148+ const prevState = store . getState ( ) ;
149+ const newState : any = { } ;
150+ if ( prevProps . mode === 'inline' && nextProps . mode !== 'inline' ) {
151+ this . setState ( {
152+ switchingModeFromInline : true ,
153+ } ) ;
154+ }
155+
156+ if ( ! ( 'openKeys' in nextProps ) ) {
157+ // [Legacy] Old code will return after `openKeys` changed.
158+ // Not sure the reason, we should keep this logic still.
159+ if (
160+ ( nextProps . inlineCollapsed && ! prevProps . inlineCollapsed ) ||
161+ ( nextProps . siderCollapsed && ! prevProps . siderCollapsed )
162+ ) {
163+ this . setState ( {
164+ switchingModeFromInline : true ,
165+ } ) ;
166+ this . inlineOpenKeys = prevState . openKeys . concat ( ) ;
167+ newState . openKeys = [ ] ;
168+ }
169+
170+ if (
171+ ( ! nextProps . inlineCollapsed && prevProps . inlineCollapsed ) ||
172+ ( ! nextProps . siderCollapsed && prevProps . siderCollapsed )
173+ ) {
174+ newState . openKeys = inlineOpenKeys ;
175+ this . inlineOpenKeys = [ ] ;
176+ }
177+ }
178+
179+ if ( Object . keys ( newState ) . length ) {
180+ store . setState ( newState ) ;
181+ }
182+ }
183+
184+ updateMenuDisplay ( ) {
185+ const {
186+ props : { collapsedWidth } ,
187+ store,
188+ prevOpenKeys,
189+ } = this ;
190+ // https://github.com/ant-design/ant-design/issues/8587
191+ const hideMenu =
192+ this . getInlineCollapsed ( ) &&
193+ ( collapsedWidth === 0 ||
194+ collapsedWidth === '0' ||
195+ collapsedWidth === '0px' ) ;
196+ if ( hideMenu ) {
197+ this . prevOpenKeys = store . getState ( ) . openKeys . concat ( ) ;
198+ this . store . setState ( {
199+ openKeys : [ ] ,
200+ } ) ;
201+ } else if ( prevOpenKeys ) {
202+ this . store . setState ( {
203+ openKeys : prevOpenKeys ,
204+ } ) ;
205+ this . prevOpenKeys = null ;
206+ }
113207 }
114208
115209 onSelect = ( selectInfo : SelectInfo ) => {
@@ -136,6 +230,19 @@ class Menu extends React.Component<MenuProps> {
136230 } ;
137231
138232 onClick : MenuClickEventHandler = e => {
233+ const mode = this . getRealMenuMode ( ) ;
234+ const {
235+ store,
236+ props : { onOpenChange } ,
237+ } = this ;
238+
239+ if ( mode !== 'inline' && ! ( 'openKeys' in this . props ) ) {
240+ // closing vertical popup submenu after click it
241+ store . setState ( {
242+ openKeys : [ ] ,
243+ } ) ;
244+ onOpenChange ( [ ] ) ;
245+ }
139246 this . props . onClick ( e ) ;
140247 } ;
141248
@@ -201,16 +308,71 @@ class Menu extends React.Component<MenuProps> {
201308 }
202309 } ;
203310
204- getOpenTransitionName = ( ) => {
205- const { props } = this ;
206- let transitionName = props . openTransitionName ;
207- const animationName = props . openAnimation ;
208- if ( ! transitionName && typeof animationName === 'string' ) {
209- transitionName = `${ props . prefixCls } -open-${ animationName } ` ;
311+ getRealMenuMode ( ) {
312+ const { mode } = this . props ;
313+ const { switchingModeFromInline } = this . state ;
314+ const inlineCollapsed = this . getInlineCollapsed ( ) ;
315+ if ( switchingModeFromInline && inlineCollapsed ) {
316+ return 'inline' ;
317+ }
318+ return inlineCollapsed ? 'vertical' : mode ;
319+ }
320+
321+ getInlineCollapsed ( ) {
322+ const { inlineCollapsed, siderCollapsed } = this . props ;
323+ if ( siderCollapsed !== undefined ) {
324+ return siderCollapsed ;
325+ }
326+ return inlineCollapsed ;
327+ }
328+
329+ // Restore vertical mode when menu is collapsed responsively when mounted
330+ // https://github.com/ant-design/ant-design/issues/13104
331+ // TODO: not a perfect solution,
332+ // looking a new way to avoid setting switchingModeFromInline in this situation
333+ onMouseEnter = ( e : React . MouseEvent < HTMLDivElement , MouseEvent > ) => {
334+ this . restoreModeVerticalFromInline ( ) ;
335+ const { onMouseEnter } = this . props ;
336+ if ( onMouseEnter ) {
337+ onMouseEnter ( e ) ;
338+ }
339+ } ;
340+
341+ onTransitionEnd = ( e : React . TransitionEvent < HTMLDivElement > ) => {
342+ // when inlineCollapsed menu width animation finished
343+ // https://github.com/ant-design/ant-design/issues/12864
344+ const widthCollapsed =
345+ e . propertyName === 'width' && e . target === e . currentTarget ;
346+
347+ // Fix SVGElement e.target.className.indexOf is not a function
348+ // https://github.com/ant-design/ant-design/issues/15699
349+ const { className } = e . target as HTMLElement | SVGElement ;
350+ // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal,
351+ // unless during an animation.
352+ const classNameValue =
353+ Object . prototype . toString . call ( className ) === '[object SVGAnimatedString]'
354+ ? className . animVal
355+ : className ;
356+
357+ // Fix for <Menu style={{ width: '100%' }} />,
358+ // the width transition won't trigger when menu is collapsed
359+ // https://github.com/ant-design/ant-design-pro/issues/2783
360+ const iconScaled =
361+ e . propertyName === 'font-size' && classNameValue . indexOf ( 'anticon' ) >= 0 ;
362+ if ( widthCollapsed || iconScaled ) {
363+ this . restoreModeVerticalFromInline ( ) ;
210364 }
211- return transitionName ;
212365 } ;
213366
367+ restoreModeVerticalFromInline ( ) {
368+ const { switchingModeFromInline } = this . state ;
369+ if ( switchingModeFromInline ) {
370+ this . setState ( {
371+ switchingModeFromInline : false ,
372+ } ) ;
373+ }
374+ }
375+
214376 setInnerMenu = node => {
215377 this . innerMenu = node ;
216378 } ;
@@ -229,19 +391,29 @@ class Menu extends React.Component<MenuProps> {
229391 }
230392
231393 render ( ) {
232- let props : MenuProps & { parentMenu ?: Menu } = { ...this . props } ;
394+ let props : MenuProps & { parentMenu ?: Menu } = {
395+ ...omit ( this . props , [
396+ 'collapsedWidth' ,
397+ 'siderCollapsed' ,
398+ 'defaultMotion' ,
399+ ] ) ,
400+ } ;
401+ const mode = this . getRealMenuMode ( ) ;
233402 props . className += ` ${ props . prefixCls } -root` ;
234403 if ( props . direction === 'rtl' ) {
235404 props . className += ` ${ props . prefixCls } -rtl` ;
236405 }
237406 props = {
238407 ...props ,
408+ mode,
239409 onClick : this . onClick ,
240410 onOpenChange : this . onOpenChange ,
241411 onDeselect : this . onDeselect ,
242412 onSelect : this . onSelect ,
413+ onMouseEnter : this . onMouseEnter ,
414+ onTransitionEnd : this . onTransitionEnd ,
243415 parentMenu : this ,
244- motion : getMotion ( this . props ) ,
416+ motion : getMotion ( this . props , this . state ) ,
245417 } ;
246418
247419 delete props . openAnimation ;
0 commit comments