@@ -9,6 +9,7 @@ import warn from '../../utils/warn'
9
9
import { select , selectAll , isVisible , setAttr , removeAttr , getAttr } from '../../utils/dom'
10
10
import { arrayIncludes } from '../../utils/array'
11
11
import { keys , create } from '../../utils/object'
12
+ import { inBrowser } from '../../utils/env'
12
13
// Sub components
13
14
import BFormRow from '../layout/form-row'
14
15
import BCol from '../layout/col'
@@ -19,16 +20,17 @@ import BFormValidFeedback from '../form/form-valid-feedback'
19
20
// Selector for finding first input in the form-group
20
21
const SELECTOR = 'input:not(:disabled),textarea:not(:disabled),select:not(:disabled)'
21
22
22
- // Memoize this function to return cached values to save time in computed functions
23
+ // Memoize this function to return cached values to
24
+ // save time in computed functions
23
25
const makePropName = memoize ( ( breakpoint = '' , prefix ) => {
24
26
return `${ prefix } ${ upperFirst ( breakpoint ) } `
25
27
} )
26
28
27
29
const DEPRECATED_MSG =
28
30
'Props "horizontal" and "breakpoint" are deprecated. Use "label-cols(-{breakpoint})" props instead.'
29
31
30
- // render helper functions (here rather than polluting the instance with more methods)
31
- function renderInvalidFeedback ( h , ctx ) {
32
+ // Render helper functions (here rather than polluting the instance with more methods)
33
+ const renderInvalidFeedback = ( h , ctx ) => {
32
34
let content = ctx . $slots [ 'invalid-feedback' ] || ctx . invalidFeedback
33
35
let invalidFeedback = h ( false )
34
36
if ( content ) {
@@ -54,7 +56,7 @@ function renderInvalidFeedback(h, ctx) {
54
56
return invalidFeedback
55
57
}
56
58
57
- function renderValidFeedback ( h , ctx ) {
59
+ const renderValidFeedback = ( h , ctx ) => {
58
60
const content = ctx . $slots [ 'valid-feedback' ] || ctx . validFeedback
59
61
let validFeedback = h ( false )
60
62
if ( content ) {
@@ -80,7 +82,7 @@ function renderValidFeedback(h, ctx) {
80
82
return validFeedback
81
83
}
82
84
83
- function renderHelpText ( h , ctx ) {
85
+ const renderHelpText = ( h , ctx ) => {
84
86
// Form help text (description)
85
87
const content = ctx . $slots [ 'description' ] || ctx . description
86
88
let description = h ( false )
@@ -99,8 +101,8 @@ function renderHelpText(h, ctx) {
99
101
return description
100
102
}
101
103
102
- function renderLabel ( h , ctx ) {
103
- // render label/legend inside b-col if necessary
104
+ const renderLabel = ( h , ctx ) => {
105
+ // Render label/legend inside b-col if necessary
104
106
const content = ctx . $slots [ 'label' ] || ctx . label
105
107
const labelFor = ctx . labelFor
106
108
const isLegend = ! labelFor
@@ -132,17 +134,20 @@ function renderLabel(h, ctx) {
132
134
attrs : {
133
135
id : ctx . labelId ,
134
136
for : labelFor || null ,
135
- // We add a tab index to legend so that screen readers will properly read the aria-labelledby in IE.
137
+ // We add a tab index to legend so that screen readers
138
+ // will properly read the aria-labelledby in IE.
136
139
tabindex : isLegend ? '-1' : null
137
140
} ,
138
141
class : [
139
- // When horizontal or if a legend is rendered, add col-form-label for correct sizing
140
- // as Bootstrap has inconsitent font styling for legend in non-horiontal form-groups.
142
+ // When horizontal or if a legend is rendered, add col-form-label
143
+ // for correct sizing as Bootstrap has inconsistent font styling
144
+ // for legend in non-horizontal form-groups.
141
145
// See: https://github.com/twbs/bootstrap/issues/27805
142
146
isHorizontal || isLegend ? 'col-form-label' : '' ,
143
147
// Emulate label padding top of 0 on legend when not horizontal
144
148
! isHorizontal && isLegend ? 'pt-0' : '' ,
145
- // If not horizontal and not a legend, we add d-block to label so that label-align works
149
+ // If not horizontal and not a legend, we add d-block to label
150
+ // so that label-align works
146
151
! isHorizontal && ! isLegend ? 'd-block' : '' ,
147
152
ctx . labelSize ? `col-form-label-${ ctx . labelSize } ` : '' ,
148
153
ctx . labelAlignClasses ,
@@ -154,10 +159,9 @@ function renderLabel(h, ctx) {
154
159
}
155
160
}
156
161
157
- //
158
162
// Async (lazy) component for BFormGroup
159
- // Needed so that the breakpoint specific props can be computed once hte config is created
160
- //
163
+ // Needed so that the breakpoint specific props can be computed
164
+ // once the config is created
161
165
export default ( resolve , reject ) => {
162
166
// Grab the current config for breakpoints
163
167
const BREAKPOINTS = getBreakpointsUp ( )
@@ -268,13 +272,14 @@ export default (resolve, reject) => {
268
272
const bp = this . breakpoint || BREAKPOINTS [ 1 ] // 'sm'
269
273
const cols = parseInt ( this . labelCols , 10 ) || 3
270
274
props [ bp ] = cols > 0 ? cols : 3
271
- // We then return the single breakpoint prop for legacy compatability
275
+ // We then return the single breakpoint prop for legacy compatibility
272
276
return props
273
277
}
274
278
BREAKPOINTS . forEach ( breakpoint => {
275
279
// Grab the value if the label column breakpoint prop
276
280
let propVal = this [ makePropName ( breakpoint , 'labelCols' ) ]
277
- // Handle case where the prop's value is an empty string, which represents true
281
+ // Handle case where the prop's value is an empty string,
282
+ // which represents true
278
283
propVal = propVal === '' ? true : propVal || false
279
284
if ( typeof propVal !== 'boolean' ) {
280
285
// Convert to column size to number
@@ -283,8 +288,9 @@ export default (resolve, reject) => {
283
288
propVal = propVal > 0 ? propVal : false
284
289
}
285
290
if ( propVal ) {
286
- // Add the prop to the list of props to give to b-col.
287
- // if breakpoint is '' (labelCols=true), then we use the col prop to make equal width at xs
291
+ // Add the prop to the list of props to give to b-col
292
+ // If breakpoint is '' (labelCols=true), then we use the
293
+ // col prop to make equal width at xs
288
294
const bColPropName = breakpoint || ( typeof propVal === 'boolean' ? 'col' : 'cols' )
289
295
// Add it to the props
290
296
props [ bColPropName ] = propVal
@@ -295,7 +301,7 @@ export default (resolve, reject) => {
295
301
labelAlignClasses ( ) {
296
302
const classes = [ ]
297
303
BREAKPOINTS . forEach ( breakpoint => {
298
- // assemble the label column breakpoint align classes
304
+ // Assemble the label column breakpoint align classes
299
305
const propVal = this [ makePropName ( breakpoint , 'labelAlign' ) ] || null
300
306
if ( propVal ) {
301
307
const className = breakpoint ? `text-${ breakpoint } -${ propVal } ` : `text-${ propVal } `
@@ -318,23 +324,23 @@ export default (resolve, reject) => {
318
324
: null
319
325
} ,
320
326
hasInvalidFeedback ( ) {
321
- // used for computing aria-describedby
327
+ // Used for computing aria-describedby
322
328
const $slots = this . $slots
323
329
return this . computedState === false && ( $slots [ 'invalid-feedback' ] || this . invalidFeedback )
324
330
} ,
325
331
invalidFeedbackId ( ) {
326
332
return this . hasInvalidFeedback ? this . safeId ( '_BV_feedback_invalid_' ) : null
327
333
} ,
328
334
hasValidFeedback ( ) {
329
- // used for computing aria-describedby
335
+ // Used for computing aria-describedby
330
336
return this . computedState === true && ( this . $slots [ 'valid-feedback' ] || this . validFeedback )
331
337
} ,
332
338
validFeedbackId ( ) {
333
339
return this . hasValidFeedback ? this . safeId ( '_BV_feedback_valid_' ) : null
334
340
} ,
335
341
describedByIds ( ) {
336
342
// Screen readers will read out any content linked to by aria-describedby
337
- // even if the content is hidden with ' display: none' , hence we only include
343
+ // even if the content is hidden with ` display: none;` , hence we only include
338
344
// feedback IDs if the form-group's state is explicitly valid or invalid.
339
345
return (
340
346
[ this . descriptionId , this . invalidFeedbackId , this . validFeedbackId ]
@@ -352,21 +358,22 @@ export default (resolve, reject) => {
352
358
} ,
353
359
mounted ( ) {
354
360
this . $nextTick ( ( ) => {
355
- // Set the adia -describedby IDs on the input specified by label-for
361
+ // Set the aria -describedby IDs on the input specified by label-for
356
362
// We do this in a nextTick to ensure the children have finished rendering
357
363
this . setInputDescribedBy ( this . describedByIds )
358
364
} )
359
365
} ,
360
366
methods : {
361
367
legendClick ( evt ) {
362
368
if ( this . labelFor ) {
363
- // don 't do anything if labelFor is set
369
+ // Don 't do anything if labelFor is set
364
370
/* istanbul ignore next: clicking a label will focus the input, so no need to test */
365
371
return
366
372
}
367
373
const tagName = evt . target ? evt . target . tagName : ''
368
374
if ( / ^ ( i n p u t | s e l e c t | t e x t a r e a | l a b e l | b u t t o n | a ) $ / i. test ( tagName ) ) {
369
- // If clicked an interactive element inside legend, we just let the default happen
375
+ // If clicked an interactive element inside legend,
376
+ // we just let the default happen
370
377
/* istanbul ignore next */
371
378
return
372
379
}
@@ -378,8 +385,9 @@ export default (resolve, reject) => {
378
385
} ,
379
386
setInputDescribedBy ( add , remove ) {
380
387
// Sets the `aria-describedby` attribute on the input if label-for is set.
381
- // Optionally accepts a string of IDs to remove as the second parameter
382
- if ( this . labelFor && typeof document !== 'undefined' ) {
388
+ // Optionally accepts a string of IDs to remove as the second parameter.
389
+ // Preserves any aria-describedby value(s) user may have on input.
390
+ if ( this . labelFor && inBrowser ) {
383
391
const input = select ( `#${ this . labelFor } ` , this . $refs . content )
384
392
if ( input ) {
385
393
const adb = 'aria-describedby'
@@ -413,9 +421,7 @@ export default (resolve, reject) => {
413
421
ref : 'content' ,
414
422
attrs : {
415
423
tabindex : isFieldset ? '-1' : null ,
416
- role : isFieldset ? 'group' : null ,
417
- 'aria-labelledby' : isFieldset ? this . labelId : null ,
418
- 'aria-describedby' : isFieldset ? this . ariaDescribedBy : null
424
+ role : isFieldset ? 'group' : null
419
425
}
420
426
} ,
421
427
[
@@ -434,14 +440,18 @@ export default (resolve, reject) => {
434
440
disabled : isFieldset ? this . disabled : null ,
435
441
role : isFieldset ? null : 'group' ,
436
442
'aria-invalid' : this . computedState === false ? 'true' : null ,
437
- 'aria-labelledby' : this . labelId || null ,
438
- 'aria-describedby' : this . describedByIds || null
443
+ // Only apply aria-labelledby if we are a horizontal fieldset
444
+ // as the legend is no longer a direct child of fieldset
445
+ 'aria-labelledby' : isFieldset && isHorizontal ? this . labelId : null ,
446
+ // Only apply aria-describedby IDs if we are a fieldset
447
+ // as the input will have the IDs when not a fieldset
448
+ 'aria-describedby' : isFieldset ? this . describedByIds : null
439
449
}
440
450
}
441
- // Return it wrapped in a form-group.
442
- // Note: fieldsets do not support adding `row` or `form-row` directly to them
443
- // due to browser specific render issues, so we move the form-row to an
444
- // inner wrapper div when horizontal and using a fieldset
451
+ // Return it wrapped in a form-group
452
+ // Note: Fieldsets do not support adding `row` or `form-row` directly
453
+ // to them due to browser specific render issues, so we move the ` form-row`
454
+ // to an inner wrapper div when horizontal and using a fieldset
445
455
return h (
446
456
isFieldset ? 'fieldset' : isHorizontal ? 'b-form-row' : 'div' ,
447
457
data ,
@@ -450,6 +460,6 @@ export default (resolve, reject) => {
450
460
}
451
461
}
452
462
453
- // Return the componwent options reference
463
+ // Return the component options reference
454
464
resolve ( BFormGroup )
455
465
}
0 commit comments