@@ -155,6 +155,7 @@ interface TabsState {
155155 isInitializingAccent : boolean ;
156156 currentLinkAccentLength : string ;
157157 currentLinkAccentStart : string ;
158+ currentUrlHash : string ;
158159}
159160
160161class Tabs extends Component < TabsProps , TabsState > {
@@ -164,20 +165,24 @@ class Tabs extends Component<TabsProps, TabsState> {
164165 private direction = 'ltr' ;
165166 constructor ( props : TabsProps ) {
166167 super ( props ) ;
168+ const currentUrlHash = Tabs . getCurrentUrlHash ( ) ;
169+ const initialActiveKey = Tabs . getActiveKeyFromProps ( props , props . defaultActiveKey , currentUrlHash ) ;
170+
167171 this . state = {
168172 enableScrollButtons : false ,
169173 showScrollButtons : false ,
170174 renderScrollButtons : false ,
171175 disableBackScrollButton : true ,
172176 disableForwardScrollButton : true ,
173- shownKeys : this . props . defaultActiveKey !== undefined ? [ this . props . defaultActiveKey ] : [ this . props . activeKey ] , // only for mountOnEnter case
177+ shownKeys : initialActiveKey !== undefined ? [ initialActiveKey ] : [ ] , // only for mountOnEnter case
174178 uncontrolledActiveKey : this . props . defaultActiveKey ,
175179 uncontrolledIsExpandedLocal : this . props . defaultIsExpanded ,
176180 ouiaStateId : getDefaultOUIAId ( Tabs . displayName ) ,
177181 overflowingTabCount : 0 ,
178182 isInitializingAccent : true ,
179183 currentLinkAccentLength : linkAccentLength . value ,
180- currentLinkAccentStart : linkAccentStart . value
184+ currentLinkAccentStart : linkAccentStart . value ,
185+ currentUrlHash
181186 } ;
182187
183188 if ( this . props . isVertical && this . props . expandable !== undefined ) {
@@ -193,6 +198,36 @@ class Tabs extends Component<TabsProps, TabsState> {
193198
194199 scrollTimeout : NodeJS . Timeout = null ;
195200
201+ static getCurrentUrlHash = ( ) => ( canUseDOM ? window . location . hash : '' ) ;
202+
203+ static getActiveKeyFromCurrentUrl = (
204+ props : Pick < TabsProps , 'children' | 'component' | 'isNav' > ,
205+ currentUrlHash ?: string
206+ ) => {
207+ if ( ( ! props . isNav && props . component !== TabsComponent . nav ) || ! currentUrlHash ) {
208+ return undefined ;
209+ }
210+
211+ return Children . toArray ( props . children )
212+ . filter ( ( child ) : child is TabElement => isValidElement ( child ) )
213+ . filter ( ( { props } ) => ! props . isHidden )
214+ . find ( ( { props } ) => ! props . isDisabled && ! props . isAriaDisabled && props . href === currentUrlHash ) ?. props . eventKey ;
215+ } ;
216+
217+ static getActiveKeyFromProps = (
218+ props : TabsProps ,
219+ uncontrolledActiveKey : TabsState [ 'uncontrolledActiveKey' ] ,
220+ currentUrlHash ?: string
221+ ) => {
222+ const activeKeyFromCurrentUrl = Tabs . getActiveKeyFromCurrentUrl ( props , currentUrlHash ) ;
223+
224+ if ( activeKeyFromCurrentUrl !== undefined ) {
225+ return activeKeyFromCurrentUrl ;
226+ }
227+
228+ return props . defaultActiveKey !== undefined ? uncontrolledActiveKey : props . activeKey ;
229+ } ;
230+
196231 static defaultProps : PickOptional < TabsProps > = {
197232 activeKey : 0 ,
198233 onSelect : ( ) => undefined as any ,
@@ -373,7 +408,23 @@ class Tabs extends Component<TabsProps, TabsState> {
373408 this . setAccentStyles ( ) ;
374409 } ;
375410
411+ handleHashChange = ( ) => {
412+ const currentUrlHash = Tabs . getCurrentUrlHash ( ) ;
413+
414+ if ( currentUrlHash !== this . state . currentUrlHash ) {
415+ this . setState ( { currentUrlHash } ) ;
416+ }
417+ } ;
418+
419+ getLocalActiveKey = ( props = this . props , state = this . state ) =>
420+ Tabs . getActiveKeyFromProps ( props , state . uncontrolledActiveKey , state . currentUrlHash ) ;
421+
376422 componentDidMount ( ) {
423+ if ( canUseDOM ) {
424+ window . addEventListener ( 'hashchange' , this . handleHashChange , false ) ;
425+ this . handleHashChange ( ) ;
426+ }
427+
377428 if ( ! this . props . isVertical ) {
378429 if ( canUseDOM ) {
379430 window . addEventListener ( 'resize' , this . handleResize , false ) ;
@@ -387,6 +438,10 @@ class Tabs extends Component<TabsProps, TabsState> {
387438 }
388439
389440 componentWillUnmount ( ) {
441+ if ( canUseDOM ) {
442+ window . removeEventListener ( 'hashchange' , this . handleHashChange , false ) ;
443+ }
444+
390445 if ( ! this . props . isVertical ) {
391446 if ( canUseDOM ) {
392447 window . removeEventListener ( 'resize' , this . handleResize , false ) ;
@@ -398,20 +453,24 @@ class Tabs extends Component<TabsProps, TabsState> {
398453
399454 componentDidUpdate ( prevProps : TabsProps , prevState : TabsState ) {
400455 this . direction = getLanguageDirection ( this . tabList . current ) ;
401- const { activeKey , mountOnEnter, isOverflowHorizontal, children, defaultActiveKey } = this . props ;
402- const { shownKeys, overflowingTabCount, enableScrollButtons, uncontrolledActiveKey } = this . state ;
456+ const { mountOnEnter, isOverflowHorizontal, children } = this . props ;
457+ const { shownKeys, overflowingTabCount, enableScrollButtons } = this . state ;
403458 const isOnCloseUpdate = ! ! prevProps . onClose !== ! ! this . props . onClose ;
404- if (
405- ( defaultActiveKey !== undefined && prevState . uncontrolledActiveKey !== uncontrolledActiveKey ) ||
406- ( defaultActiveKey === undefined && prevProps . activeKey !== activeKey ) ||
407- isOnCloseUpdate
408- ) {
459+ const previousLocalActiveKey = this . getLocalActiveKey ( prevProps , prevState ) ;
460+ const localActiveKey = this . getLocalActiveKey ( ) ;
461+
462+ if ( previousLocalActiveKey !== localActiveKey || isOnCloseUpdate ) {
409463 this . setAccentStyles ( isOnCloseUpdate ) ;
410464 }
411465
412- if ( prevProps . activeKey !== activeKey && mountOnEnter && shownKeys . indexOf ( activeKey ) < 0 ) {
466+ if (
467+ mountOnEnter &&
468+ previousLocalActiveKey !== localActiveKey &&
469+ localActiveKey !== undefined &&
470+ shownKeys . indexOf ( localActiveKey ) < 0
471+ ) {
413472 this . setState ( {
414- shownKeys : shownKeys . concat ( activeKey )
473+ shownKeys : shownKeys . concat ( localActiveKey )
415474 } ) ;
416475 }
417476
@@ -463,16 +522,19 @@ class Tabs extends Component<TabsProps, TabsState> {
463522 // otherwise update state derived from nextProps.defaultActiveKey
464523 return {
465524 uncontrolledActiveKey : nextProps . defaultActiveKey ,
466- shownKeys : nextProps . defaultActiveKey !== undefined ? [ nextProps . defaultActiveKey ] : [ nextProps . activeKey ] // only for mountOnEnter case
525+ shownKeys : ( ( ) => {
526+ const activeKey = Tabs . getActiveKeyFromProps ( nextProps , nextProps . defaultActiveKey , prevState . currentUrlHash ) ;
527+ return activeKey !== undefined ? [ activeKey ] : [ ] ;
528+ } ) ( ) // only for mountOnEnter case
467529 } ;
468530 }
469531
470532 render ( ) {
471533 const {
472534 className,
473535 children,
474- activeKey,
475- defaultActiveKey,
536+ activeKey : _activeKey ,
537+ defaultActiveKey : _defaultActiveKey ,
476538 id,
477539 isAddButtonDisabled,
478540 isFilled,
@@ -506,13 +568,14 @@ class Tabs extends Component<TabsProps, TabsState> {
506568 isOverflowHorizontal : isOverflowHorizontal ,
507569 ...props
508570 } = this . props ;
571+ void _activeKey ;
572+ void _defaultActiveKey ;
509573 const {
510574 showScrollButtons,
511575 renderScrollButtons,
512576 disableBackScrollButton,
513577 disableForwardScrollButton,
514578 shownKeys,
515- uncontrolledActiveKey,
516579 uncontrolledIsExpandedLocal,
517580 overflowingTabCount,
518581 isInitializingAccent,
@@ -530,7 +593,7 @@ class Tabs extends Component<TabsProps, TabsState> {
530593 const uniqueId = id || getUniqueId ( ) ;
531594 const defaultComponent = isNav && ! component ? 'nav' : 'div' ;
532595 const Component : any = component !== undefined ? component : defaultComponent ;
533- const localActiveKey = defaultActiveKey !== undefined ? uncontrolledActiveKey : activeKey ;
596+ const localActiveKey = this . getLocalActiveKey ( ) ;
534597
535598 const isExpandedLocal = defaultIsExpanded !== undefined ? uncontrolledIsExpandedLocal : isExpanded ;
536599 /* Uncontrolled expandable tabs */
0 commit comments