@@ -4,11 +4,25 @@ import observeDom from '../../utils/observe-dom'
4
4
import idMixin from '../../mixins/id'
5
5
6
6
// Private Helper component
7
+ // @vue /component
7
8
const BTabButtonHelper = {
8
9
name : 'BTabButtonHelper' ,
10
+ inject : {
11
+ bvTabs : {
12
+ default ( ) /* istanbul ignore next */ {
13
+ return { }
14
+ }
15
+ }
16
+ } ,
9
17
props : {
10
18
// Reference to the child b-tab instance
11
- tab : { default : null , required : true } ,
19
+ tab : { default : null } ,
20
+ tabs : {
21
+ type : Array ,
22
+ default ( ) {
23
+ return [ ]
24
+ }
25
+ } ,
12
26
id : { type : String , default : null } ,
13
27
controls : { type : String , default : null } ,
14
28
tabIndex : { type : Number , default : null } ,
@@ -28,6 +42,7 @@ const BTabButtonHelper = {
28
42
evt . stopPropagation ( )
29
43
}
30
44
if ( this . tab . disabled ) {
45
+ /* istanbul ignore next */
31
46
return
32
47
}
33
48
const type = evt . type
@@ -186,9 +201,11 @@ export default {
186
201
}
187
202
} ,
188
203
data ( ) {
204
+ let tabIdx = parseInt ( this . value , 10 )
205
+ tabIdx = isNaN ( tabIdx ) ? - 1 : tabIdx
189
206
return {
190
207
// Index of current tab
191
- currentTab : parseInt ( this . value , 10 ) || - 1 ,
208
+ currentTab : tabIdx ,
192
209
// Array of direct child b-tab instances
193
210
tabs : [ ]
194
211
}
@@ -220,6 +237,7 @@ export default {
220
237
value ( val , old ) {
221
238
if ( val !== old ) {
222
239
val = parseInt ( val , 10 )
240
+ val = isNaN ( val ) ? - 1 : val
223
241
old = parseInt ( old , 10 ) || 0
224
242
const tabs = this . tabs
225
243
if ( tabs [ val ] && ! tabs [ val ] . disabled ) {
@@ -236,27 +254,66 @@ export default {
236
254
}
237
255
} ,
238
256
created ( ) {
257
+ let tabIdx = parseInt ( this . value , 10 )
258
+ this . currentTab = isNaN ( tabIdx ) ? - 1 : tabIdx
259
+ // Create private non-reactive prop
260
+ this . _bvObserver = null
239
261
// For SSR and to make sure only a single tab is shown on mount
240
- // We wrap this in a `$nextTick()` to ensure the tabs have been created
262
+ // We wrap this in a `$nextTick()` to ensure the child tabs have been created
241
263
this . $nextTick ( ( ) => {
242
264
this . updateTabs ( )
243
265
} )
244
266
} ,
245
267
mounted ( ) {
246
- // In case tabs have changed before mount
247
- this . updateTabs ( )
248
- // Observe Child changes so we can update list of tabs
249
- observeDom ( this . $refs . tabsContainer , this . updateTabs . bind ( this ) , {
250
- subtree : false
268
+ this . $nextTick ( ( ) => {
269
+ // Call updateTabs jsut in case....
270
+ this . updateTabs ( )
271
+ // Observe Child changes so we can update list of tabs
272
+ this . setObserver ( true )
273
+ } )
274
+ } ,
275
+ deactivated ( ) /* istanbul ignore next */ {
276
+ this . setObserver ( false )
277
+ } ,
278
+ activated ( ) /* istanbul ignore next */ {
279
+ let tabIdx = parseInt ( this . value , 10 )
280
+ this . currentTab = isNaN ( tabIdx ) ? - 1 : tabIdx
281
+ this . $nextTick ( ( ) => {
282
+ this . updateTabs ( )
283
+ this . setObserver ( true )
251
284
} )
252
285
} ,
286
+ beforeDestroy ( ) /* istanbul ignore next */ {
287
+ this . setObserver ( false )
288
+ } ,
253
289
methods : {
290
+ setObserver ( on ) {
291
+ if ( on ) {
292
+ // Make sure no existing observer running
293
+ this . setObserver ( false )
294
+ // Watch for changes to b-tab sub components
295
+ this . _bvObserver = observeDom ( this . $refs . tabsContainer , this . updateTabs . bind ( this ) , {
296
+ childList : true ,
297
+ subtree : false ,
298
+ attributes : true ,
299
+ attributeFilter : [ 'style' , 'class' ]
300
+ } )
301
+ } else {
302
+ if ( this . _bvObserver && this . _bvObserver . disconnect ) {
303
+ this . _bvObserver . disconnect ( )
304
+ }
305
+ this . _bvObserver = null
306
+ }
307
+ } ,
308
+ getTabs ( ) {
309
+ return ( this . $slots . default || [ ] )
310
+ . map ( vnode => vnode . componentInstance )
311
+ . filter ( tab => tab && tab . _isTab )
312
+ } ,
254
313
// Update list of b-tab children
255
314
updateTabs ( ) {
256
315
// Probe tabs
257
- const tabs = ( this . $slots . default || [ ] )
258
- . map ( vnode => vnode . componentInstance )
259
- . filter ( tab => tab && tab . _isTab )
316
+ const tabs = this . getTabs ( )
260
317
261
318
// Find *last* active non-disabled tab in current tabs
262
319
// We trust tab state over currentTab, in case tabs were added/removed/re-ordered
@@ -291,8 +348,12 @@ export default {
291
348
292
349
// Set the current tab state to active
293
350
tabs . forEach ( ( tab , idx ) => {
294
- tab . localActive = idx === tabIndex && ! tab . disabled
351
+ // tab.localActive = idx === tabIndex && !tab.disabled
352
+ tab . localActive = false
295
353
} )
354
+ if ( tabs [ tabIndex ] ) {
355
+ tabs [ tabIndex ] . localActive = true
356
+ }
296
357
297
358
// Update the array of tab children
298
359
this . tabs = tabs
@@ -325,6 +386,7 @@ export default {
325
386
}
326
387
if ( ! result ) {
327
388
// Couldn't set tab, so ensure v-model is set to this.currentTab
389
+ /* istanbul ignore next: should rarely happen */
328
390
this . $emit ( 'input' , this . currentTab )
329
391
}
330
392
return result
@@ -338,6 +400,7 @@ export default {
338
400
return this . activateTab ( this . tabs . filter ( t => t !== tab ) . find ( notDisabled ) )
339
401
} else {
340
402
// No tab specified
403
+ /* istanbull ignore next: should never happen */
341
404
return false
342
405
}
343
406
} ,
@@ -405,14 +468,15 @@ export default {
405
468
} ,
406
469
render ( h ) {
407
470
const tabs = this . tabs
471
+
408
472
// Currently active tab
409
473
let activeTab = tabs . find ( tab => tab . localActive && ! tab . disabled )
474
+
410
475
// Tab button to allow focusing when no active tab found (keynav only)
411
476
const fallbackTab = tabs . find ( tab => ! tab . disabled )
412
477
413
478
// For each <b-tab> found create the tab buttons
414
479
const buttons = tabs . map ( ( tab , index ) => {
415
- const buttonId = tab . controlledBy || this . safeId ( `_BV_tab_${ index + 1 } _` )
416
480
let tabIndex = null
417
481
// Ensure at least one tab button is focusable when keynav enabled (if possible)
418
482
if ( ! this . noKeyNav ) {
@@ -424,14 +488,17 @@ export default {
424
488
}
425
489
}
426
490
return h ( BTabButtonHelper , {
427
- key : tab . _uid || buttonId || index ,
491
+ key : tab . _uid || index ,
428
492
ref : 'buttons' ,
429
493
// Needed to make this.$refs.buttons an array
430
494
refInFor : true ,
431
495
props : {
432
496
tab : tab ,
433
- id : buttonId ,
434
- controls : this . safeId ( '_BV_tab_container_' ) ,
497
+ tabs : tabs ,
498
+ id :
499
+ tab . controlledBy ||
500
+ ( this . tab && this . tab . safeId ? this . tab . safeId ( `_BV_tab_button_` ) : null ) ,
501
+ controls : this . tab && this . tab . safeId ? this . tab . safeId ( ) : null ,
435
502
tabIndex,
436
503
setSize : tabs . length ,
437
504
posInSet : index + 1 ,
@@ -453,6 +520,7 @@ export default {
453
520
let navs = h (
454
521
'ul' ,
455
522
{
523
+ ref : 'navs' ,
456
524
class : [
457
525
'nav' ,
458
526
{
@@ -477,6 +545,7 @@ export default {
477
545
navs = h (
478
546
'div' ,
479
547
{
548
+ key : 'bv-tabs-navs' ,
480
549
class : [
481
550
{
482
551
'card-header' : this . card && ! this . vertical && ! ( this . end || this . bottom ) ,
@@ -499,10 +568,12 @@ export default {
499
568
}
500
569
501
570
// Main content section
571
+ // TODO: This container should be a helper component
502
572
const content = h (
503
573
'div' ,
504
574
{
505
575
ref : 'tabsContainer' ,
576
+ key : 'bv-tabs-container' ,
506
577
staticClass : 'tab-content' ,
507
578
class : [ { col : this . vertical } , this . contentClass ] ,
508
579
attrs : { id : this . safeId ( '_BV_tab_container_' ) }
0 commit comments