1
1
import Vue from '../../utils/vue'
2
2
import { mergeData } from 'vue-functional-data-merge'
3
+ import KeyCodes from '../../utils/key-codes'
3
4
import pluckProps from '../../utils/pluck-props'
4
5
import { concat } from '../../utils/array'
5
6
import { getComponentConfig } from '../../utils/config'
@@ -47,8 +48,8 @@ const btnProps = {
47
48
default : false
48
49
} ,
49
50
pressed : {
50
- // tri -state prop: true, false or null
51
- // => on , off, not a toggle
51
+ // Tri -state: ` true`, ` false` or ` null`
52
+ // => On , off, not a toggle
52
53
type : Boolean ,
53
54
default : null
54
55
}
@@ -63,10 +64,11 @@ export const props = { ...linkProps, ...btnProps }
63
64
64
65
// --- Helper methods ---
65
66
66
- // Returns true if a tag's name is name
67
+ // Returns ` true` if a tag's name equals ` name`
67
68
const tagIs = ( tag , name ) => toString ( tag ) . toLowerCase ( ) === toString ( name ) . toLowerCase ( )
68
69
69
- // Focus handler for toggle buttons. Needs class of 'focus' when focused.
70
+ // Focus handler for toggle buttons
71
+ // Needs class of 'focus' when focused
70
72
const handleFocus = evt => {
71
73
if ( evt . type === 'focusin' ) {
72
74
addClass ( evt . target , 'focus' )
@@ -76,7 +78,7 @@ const handleFocus = evt => {
76
78
}
77
79
78
80
// Is the requested button a link?
79
- // If tag prop is set to `a`, we use a b-link to get proper disabled handling
81
+ // If tag prop is set to `a`, we use a < b-link> to get proper disabled handling
80
82
const isLink = props => props . href || props . to || tagIs ( props . tag , 'a' )
81
83
82
84
// Is the button to be a toggle button?
@@ -109,31 +111,33 @@ const computeAttrs = (props, data) => {
109
111
const button = isButton ( props )
110
112
const link = isLink ( props )
111
113
const toggle = isToggle ( props )
112
- const nonStdTag = isNonStandardTag ( props )
114
+ const nonStandardTag = isNonStandardTag ( props )
115
+ const hashLink = link && props . href === '#'
113
116
const role = data . attrs && data . attrs . role ? data . attrs . role : null
114
117
let tabindex = data . attrs ? data . attrs . tabindex : null
115
- if ( nonStdTag ) {
118
+ if ( nonStandardTag || hashLink ) {
116
119
tabindex = '0'
117
120
}
118
121
return {
119
122
// Type only used for "real" buttons
120
123
type : button && ! link ? props . type : null ,
121
124
// Disabled only set on "real" buttons
122
125
disabled : button ? props . disabled : null ,
123
- // We add a role of button when the tag is not a link or button for ARIA.
124
- // Don't bork any role provided in data.attrs when isLink or isButton
125
- role : nonStdTag ? 'button' : role ,
126
- // We set the aria-disabled state for non-standard tags
127
- 'aria-disabled' : nonStdTag ? String ( props . disabled ) : null ,
126
+ // We add a role of button when the tag is not a link or button for ARIA
127
+ // Don't bork any role provided in `data.attrs` when `isLink` or `isButton`
128
+ // Except when link has `href` of `#`
129
+ role : nonStandardTag || hashLink ? 'button' : role ,
130
+ // We set the `aria-disabled` state for non-standard tags
131
+ 'aria-disabled' : nonStandardTag ? String ( props . disabled ) : null ,
128
132
// For toggles, we need to set the pressed state for ARIA
129
133
'aria-pressed' : toggle ? String ( props . pressed ) : null ,
130
- // autocomplete off is needed in toggle mode to prevent some browsers from
131
- // remembering the previous setting when using the back button.
134
+ // ` autocomplete=" off"` is needed in toggle mode to prevent some browsers
135
+ // from remembering the previous setting when using the back button
132
136
autocomplete : toggle ? 'off' : null ,
133
- // Tab index is used when the component is not a button.
137
+ // `tabindex` is used when the component is not a button
134
138
// Links are tabbable, but don't allow disabled, while non buttons or links
135
139
// are not tabbable, so we mimic that functionality by disabling tabbing
136
- // when disabled, and adding a tabindex of '0' to non buttons or non links.
140
+ // when disabled, and adding a ` tabindex="0"` to non buttons or non links
137
141
tabindex : props . disabled && ! button ? '-1' : tabindex
138
142
}
139
143
}
@@ -146,16 +150,33 @@ export const BButton = /*#__PURE__*/ Vue.extend({
146
150
render ( h , { props, data, listeners, children } ) {
147
151
const toggle = isToggle ( props )
148
152
const link = isLink ( props )
153
+ const nonStandardTag = isNonStandardTag ( props )
154
+ const hashLink = link && props . href === '#'
149
155
const on = {
156
+ keydown ( evt ) {
157
+ // When the link is a `href="#"` or a non-standard tag (has `role="button"`),
158
+ // we add a keydown handlers for SPACE/ENTER
159
+ /* istanbul ignore next */
160
+ if ( props . disabled || ! ( nonStandardTag || hashLink ) ) {
161
+ return
162
+ }
163
+ const { keyCode } = evt
164
+ // Add SPACE handler for `href="#"` and ENTER handler for non-standard tags
165
+ if ( keyCode === KeyCodes . SPACE || ( keyCode === KeyCodes . ENTER && nonStandardTag ) ) {
166
+ const target = evt . currentTarget || evt . target
167
+ evt . preventDefault ( )
168
+ target . click ( )
169
+ }
170
+ } ,
150
171
click ( evt ) {
151
172
/* istanbul ignore if: blink/button disabled should handle this */
152
173
if ( props . disabled && isEvent ( evt ) ) {
153
174
evt . stopPropagation ( )
154
175
evt . preventDefault ( )
155
176
} else if ( toggle && listeners && listeners [ 'update:pressed' ] ) {
156
- // Send .sync updates to any "pressed" prop (if .sync listeners)
157
- // Concat will normalize the value to an array
158
- // without double wrapping an array value in an array.
177
+ // Send ` .sync` updates to any "pressed" prop (if ` .sync` listeners)
178
+ // `concat()` will normalize the value to an array without
179
+ // double wrapping an array value in an array
159
180
concat ( listeners [ 'update:pressed' ] ) . forEach ( fn => {
160
181
if ( isFunction ( fn ) ) {
161
182
fn ( ! props . pressed )
0 commit comments