@@ -33,7 +33,7 @@ import {
3333} from '../../constants/slots'
3434import { arrayIncludes } from '../../utils/array'
3535import { BvEvent } from '../../utils/bv-event.class'
36- import { attemptFocus , selectAll } from '../../utils/dom'
36+ import { attemptFocus , selectAll , requestAF } from '../../utils/dom'
3737import { stopEvent } from '../../utils/events'
3838import { identity } from '../../utils/identity'
3939import { isEvent } from '../../utils/inspect'
@@ -280,14 +280,14 @@ export const BTabs = /*#__PURE__*/ Vue.extend({
280280 // Update the v-model
281281 this . $emit ( MODEL_EVENT_NAME , index )
282282 } ,
283+ // If tabs added, removed, or re-ordered, we emit a `changed` event
283284 tabs ( newValue , oldValue ) {
284- // If tabs added, removed, or re-ordered, we emit a `changed` event
285- // We use `tab._uid` instead of `tab.safeId()`, as the later is changed
286- // in a `$nextTick()` if no explicit ID is provided, causing duplicate emits
285+ // We use `_uid` instead of `safeId()`, as the later is changed in a `$nextTick()`
286+ // if no explicit ID is provided, causing duplicate emits
287287 if (
288288 ! looseEqual (
289- newValue . map ( t => t [ COMPONENT_UID_KEY ] ) ,
290- oldValue . map ( t => t [ COMPONENT_UID_KEY ] )
289+ newValue . map ( $tab => $tab [ COMPONENT_UID_KEY ] ) ,
290+ oldValue . map ( $tab => $tab [ COMPONENT_UID_KEY ] )
291291 )
292292 ) {
293293 // In a `$nextTick()` to ensure `currentTab` has been set first
@@ -298,6 +298,9 @@ export const BTabs = /*#__PURE__*/ Vue.extend({
298298 } )
299299 }
300300 } ,
301+ // Each `<b-tab>` will register/unregister itself
302+ // We use this to detect when tabs are added/removed
303+ // to trigger the update of the tabs
301304 registeredTabs ( ) {
302305 this . updateTabs ( )
303306 }
@@ -332,7 +335,9 @@ export const BTabs = /*#__PURE__*/ Vue.extend({
332335 /* istanbul ignore next: difficult to test mutation observer in JSDOM */
333336 const handler = ( ) => {
334337 this . $nextTick ( ( ) => {
335- this . updateTabs ( )
338+ requestAF ( ( ) => {
339+ this . updateTabs ( )
340+ } )
336341 } )
337342 }
338343
@@ -352,8 +357,9 @@ export const BTabs = /*#__PURE__*/ Vue.extend({
352357
353358 // DOM Order of Tabs
354359 let order = [ ]
360+ /* istanbul ignore next: too difficult to test */
355361 if ( IS_BROWSER && $tabs . length > 0 ) {
356- // We rely on the DOM when mounted to get the ' true' order of the `<b-tab>` children
362+ // We rely on the DOM when mounted to get the " true" order of the `<b-tab>` children
357363 // `querySelectorAll()` always returns elements in document order, regardless of
358364 // order specified in the selector
359365 const selector = $tabs . map ( $tab => `#${ $tab . safeId ( ) } ` ) . join ( ', ' )
@@ -369,29 +375,44 @@ export const BTabs = /*#__PURE__*/ Vue.extend({
369375 updateTabs ( ) {
370376 const $tabs = this . getTabs ( )
371377
372- // Normalize `currentTab`
373- let { currentTab } = this
374- const $tab = $tabs [ currentTab ]
375- if ( ! $tab || $tab . disabled ) {
376- currentTab = $tabs . indexOf (
377- $tabs
378- . slice ( )
379- . reverse ( )
380- . find ( $tab => $tab . localActive && ! $tab . disabled )
381- )
378+ // Find last active non-disabled tab in current tabs
379+ // We trust tab state over `currentTab`, in case tabs were added/removed/re-ordered
380+ let tabIndex = $tabs . indexOf (
381+ $tabs
382+ . slice ( )
383+ . reverse ( )
384+ . find ( $tab => $tab . localActive && ! $tab . disabled )
385+ )
382386
383- if ( currentTab === - 1 ) {
384- currentTab = $tabs . indexOf ( $tabs . find ( notDisabled ) )
387+ // Else try setting to `currentTab`
388+ if ( tabIndex < 0 ) {
389+ const { currentTab } = this
390+ if ( currentTab >= $tabs . length ) {
391+ // Handle last tab being removed, so find the last non-disabled tab
392+ tabIndex = $tabs . indexOf (
393+ $tabs
394+ . slice ( )
395+ . reverse ( )
396+ . find ( notDisabled )
397+ )
398+ } else if ( $tabs [ currentTab ] && ! $tabs [ currentTab ] . disabled ) {
399+ // Current tab is not disabled
400+ tabIndex = currentTab
385401 }
386402 }
387403
404+ // Else find first non-disabled tab in current tabs
405+ if ( tabIndex < 0 ) {
406+ tabIndex = $tabs . indexOf ( $tabs . find ( notDisabled ) )
407+ }
408+
388409 // Ensure only one tab is active at a time
389410 $tabs . forEach ( ( $tab , index ) => {
390- $tab . localActive = index === currentTab
411+ $tab . localActive = index === tabIndex
391412 } )
392413
393414 this . tabs = $tabs
394- this . currentTab = currentTab
415+ this . currentTab = tabIndex
395416 } ,
396417 // Find a button that controls a tab, given the tab reference
397418 // Returns the button vm instance
0 commit comments