@@ -137,23 +137,49 @@ const ItemWithSubNavContext = React.createContext<{buttonId: string; subNavId: s
137137 isOpen : false ,
138138} )
139139
140+ function hasCurrentNavItem ( node : React . ReactNode ) : boolean {
141+ if (
142+ ! isValidElement < {
143+ children ?: React . ReactNode
144+ 'aria-current' ?: NavListItemProps [ 'aria-current' ]
145+ } > ( node )
146+ ) {
147+ return false
148+ }
149+
150+ const ariaCurrent = node . props [ 'aria-current' ]
151+ if ( Boolean ( ariaCurrent ) && ariaCurrent !== 'false' ) {
152+ return true
153+ }
154+
155+ if ( ! node . props . children ) {
156+ return false
157+ }
158+
159+ return React . Children . toArray ( node . props . children ) . some ( hasCurrentNavItem )
160+ }
161+
140162function ItemWithSubNav ( { children, subNav, depth : _depth , defaultOpen, style} : ItemWithSubNavProps ) {
141163 const buttonId = useId ( )
142164 const subNavId = useId ( )
143- const [ isOpen , setIsOpen ] = React . useState ( ( defaultOpen || null ) ?? false )
144- const subNavRef = React . useRef < HTMLDivElement > ( null )
145- const [ containsCurrentItem , setContainsCurrentItem ] = React . useState ( false )
165+
166+ // We have to use recursion to check if the current nav item is part of the subnav before initial render
167+ // which is why we can't use the querySelector on the ref as it will cause the parent item to blink during first render.
168+ const hasCurrentItem = React . useMemo ( ( ) => hasCurrentNavItem ( subNav ) , [ subNav ] )
169+ const [ isOpen , setIsOpen ] = React . useState ( ( defaultOpen || null ) ?? hasCurrentItem )
170+ const subNavRef = React . useRef < HTMLUListElement > ( null )
171+ const [ containsCurrentItem , setContainsCurrentItem ] = React . useState ( hasCurrentItem )
146172
147173 useIsomorphicLayoutEffect ( ( ) => {
148- if ( subNavRef . current ) {
149- // Check if SubNav contains current item
150- // valid values: page, step, location, date, time, true and false
151- const currentItem = subNavRef . current . querySelector ( '[aria-current]:not([aria-current=false])' )
152-
153- if ( currentItem ) {
154- setContainsCurrentItem ( true )
155- setIsOpen ( true )
156- }
174+ // The React tree check handles the initial render before refs exist. The DOM fallback handles custom link
175+ // components that compute aria- current internally instead of receiving it as a prop.
176+ // valid values: page, step, location, date, time, true and false
177+ const currentItem =
178+ hasCurrentNavItem ( subNav ) || Boolean ( subNavRef . current ?. querySelector ( '[aria-current]:not([aria-current=false])' ) )
179+ setContainsCurrentItem ( currentItem )
180+
181+ if ( currentItem ) {
182+ setIsOpen ( true )
157183 }
158184 } , [ subNav , buttonId ] )
159185
0 commit comments