@@ -33,7 +33,7 @@ import {
33
33
} from '../../constants/slots'
34
34
import { arrayIncludes } from '../../utils/array'
35
35
import { BvEvent } from '../../utils/bv-event.class'
36
- import { attemptFocus , selectAll } from '../../utils/dom'
36
+ import { attemptFocus , selectAll , requestAF } from '../../utils/dom'
37
37
import { stopEvent } from '../../utils/events'
38
38
import { identity } from '../../utils/identity'
39
39
import { isEvent } from '../../utils/inspect'
@@ -280,14 +280,14 @@ export const BTabs = /*#__PURE__*/ Vue.extend({
280
280
// Update the v-model
281
281
this . $emit ( MODEL_EVENT_NAME , index )
282
282
} ,
283
+ // If tabs added, removed, or re-ordered, we emit a `changed` event
283
284
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
287
287
if (
288
288
! 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 ] )
291
291
)
292
292
) {
293
293
// In a `$nextTick()` to ensure `currentTab` has been set first
@@ -298,6 +298,9 @@ export const BTabs = /*#__PURE__*/ Vue.extend({
298
298
} )
299
299
}
300
300
} ,
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
301
304
registeredTabs ( ) {
302
305
this . updateTabs ( )
303
306
}
@@ -332,7 +335,9 @@ export const BTabs = /*#__PURE__*/ Vue.extend({
332
335
/* istanbul ignore next: difficult to test mutation observer in JSDOM */
333
336
const handler = ( ) => {
334
337
this . $nextTick ( ( ) => {
335
- this . updateTabs ( )
338
+ requestAF ( ( ) => {
339
+ this . updateTabs ( )
340
+ } )
336
341
} )
337
342
}
338
343
@@ -352,8 +357,9 @@ export const BTabs = /*#__PURE__*/ Vue.extend({
352
357
353
358
// DOM Order of Tabs
354
359
let order = [ ]
360
+ /* istanbul ignore next: too difficult to test */
355
361
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
357
363
// `querySelectorAll()` always returns elements in document order, regardless of
358
364
// order specified in the selector
359
365
const selector = $tabs . map ( $tab => `#${ $tab . safeId ( ) } ` ) . join ( ', ' )
@@ -369,29 +375,44 @@ export const BTabs = /*#__PURE__*/ Vue.extend({
369
375
updateTabs ( ) {
370
376
const $tabs = this . getTabs ( )
371
377
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
+ )
382
386
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
385
401
}
386
402
}
387
403
404
+ // Else find first non-disabled tab in current tabs
405
+ if ( tabIndex < 0 ) {
406
+ tabIndex = $tabs . indexOf ( $tabs . find ( notDisabled ) )
407
+ }
408
+
388
409
// Ensure only one tab is active at a time
389
410
$tabs . forEach ( ( $tab , index ) => {
390
- $tab . localActive = index === currentTab
411
+ $tab . localActive = index === tabIndex
391
412
} )
392
413
393
414
this . tabs = $tabs
394
- this . currentTab = currentTab
415
+ this . currentTab = tabIndex
395
416
} ,
396
417
// Find a button that controls a tab, given the tab reference
397
418
// Returns the button vm instance
0 commit comments