Skip to content

Commit a082ace

Browse files
authored
Merge branch 'dev' into feat/time-picker
2 parents 6c51355 + 1e02769 commit a082ace

File tree

12 files changed

+222
-29
lines changed

12 files changed

+222
-29
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,27 @@
44
> [standard-version](https://github.com/conventional-changelog/standard-version) for commit
55
> guidelines.
66
7+
<a name="2.4.2"></a>
8+
9+
## [v2.4.2](https://github.com/bootstrap-vue/bootstrap-vue/compare/v2.4.1...v2.4.2)
10+
11+
Released: 2020-02-15
12+
13+
### Bug Fixes v2.4.2
14+
15+
- **b-button:** when `href` is "#" add `role=button` and appropriate keydown handlers for A11Y
16+
([#4768](https://github.com/bootstrap-vue/bootstrap-vue/issues/4768))
17+
([087a128](https://github.com/bootstrap-vue/bootstrap-vue/commit/087a1283977061c44d5b059c203f13d2326dabae))
18+
- **b-modal:** fix transition show enter timing (closes
19+
[#4761](https://github.com/bootstrap-vue/bootstrap-vue/issues/4761))
20+
([#4766](https://github.com/bootstrap-vue/bootstrap-vue/issues/4766))
21+
([968c957](https://github.com/bootstrap-vue/bootstrap-vue/commit/968c95758e45610a8c002507790c79d87d8fe956))
22+
23+
### Other v2.4.2
24+
25+
- documentation updates
26+
- dev dependency updates
27+
728
<a name="2.4.1"></a>
829

930
## [v2.4.1](https://github.com/bootstrap-vue/bootstrap-vue/compare/v2.4.0...v2.4.1)

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bootstrap-vue",
3-
"version": "2.4.1",
3+
"version": "2.4.2",
44
"description": "BootstrapVue, with over 40 plugins and more than 80 custom components, custom directives, and over 300 icons, provides one of the most comprehensive implementations of Bootstrap v4 components and grid system for Vue.js. With extensive and automated WAI-ARIA accessibility markup.",
55
"main": "dist/bootstrap-vue.common.js",
66
"web": "dist/bootstrap-vue.js",
@@ -145,7 +145,7 @@
145145
"postcss-cli": "^7.1.0",
146146
"prettier": "1.14.3",
147147
"require-context": "^1.1.0",
148-
"rollup": "^1.31.0",
148+
"rollup": "^1.31.1",
149149
"rollup-plugin-babel": "^4.3.3",
150150
"rollup-plugin-commonjs": "^10.1.0",
151151
"rollup-plugin-node-resolve": "^5.2.0",

src/components/button/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,16 @@ supported `<router-link>` related props.
257257

258258
Note the `<router-link>` prop `tag` is referred to as `router-tag` in `bootstrap-vue`.
259259

260+
## Accessibility
261+
262+
When the `href` prop is set to `'#'`, `<b-button>` will render a link (`<a>`) element with attribute
263+
`role="button"` set and apropriate keydown listeners (<kbd>Enter</kbd> and <kbd>Space</kbd>) so that
264+
the link acts like a native HTML `<button>` for screen reader and keyboard-only users. When disabled,
265+
the `aria-disabled="true"` attribute will be set on the `<a>` element.
266+
267+
When the `href` is set to any other value (or the `to` prop is used), `role="button"` will not be
268+
added, nor will the keyboad event listeners be enabled.
269+
260270
## See also
261271

262272
- [`<b-button-group>`](/docs/components/button-group)

src/components/button/button.js

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Vue from '../../utils/vue'
22
import { mergeData } from 'vue-functional-data-merge'
3+
import KeyCodes from '../../utils/key-codes'
34
import pluckProps from '../../utils/pluck-props'
45
import { concat } from '../../utils/array'
56
import { getComponentConfig } from '../../utils/config'
@@ -47,8 +48,8 @@ const btnProps = {
4748
default: false
4849
},
4950
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
5253
type: Boolean,
5354
default: null
5455
}
@@ -63,10 +64,11 @@ export const props = { ...linkProps, ...btnProps }
6364

6465
// --- Helper methods ---
6566

66-
// Returns true if a tag's name is name
67+
// Returns `true` if a tag's name equals `name`
6768
const tagIs = (tag, name) => toString(tag).toLowerCase() === toString(name).toLowerCase()
6869

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
7072
const handleFocus = evt => {
7173
if (evt.type === 'focusin') {
7274
addClass(evt.target, 'focus')
@@ -76,7 +78,7 @@ const handleFocus = evt => {
7678
}
7779

7880
// 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
8082
const isLink = props => props.href || props.to || tagIs(props.tag, 'a')
8183

8284
// Is the button to be a toggle button?
@@ -109,31 +111,33 @@ const computeAttrs = (props, data) => {
109111
const button = isButton(props)
110112
const link = isLink(props)
111113
const toggle = isToggle(props)
112-
const nonStdTag = isNonStandardTag(props)
114+
const nonStandardTag = isNonStandardTag(props)
115+
const hashLink = link && props.href === '#'
113116
const role = data.attrs && data.attrs.role ? data.attrs.role : null
114117
let tabindex = data.attrs ? data.attrs.tabindex : null
115-
if (nonStdTag) {
118+
if (nonStandardTag || hashLink) {
116119
tabindex = '0'
117120
}
118121
return {
119122
// Type only used for "real" buttons
120123
type: button && !link ? props.type : null,
121124
// Disabled only set on "real" buttons
122125
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,
128132
// For toggles, we need to set the pressed state for ARIA
129133
'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
132136
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
134138
// Links are tabbable, but don't allow disabled, while non buttons or links
135139
// 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
137141
tabindex: props.disabled && !button ? '-1' : tabindex
138142
}
139143
}
@@ -146,16 +150,33 @@ export const BButton = /*#__PURE__*/ Vue.extend({
146150
render(h, { props, data, listeners, children }) {
147151
const toggle = isToggle(props)
148152
const link = isLink(props)
153+
const nonStandardTag = isNonStandardTag(props)
154+
const hashLink = link && props.href === '#'
149155
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+
},
150171
click(evt) {
151172
/* istanbul ignore if: blink/button disabled should handle this */
152173
if (props.disabled && isEvent(evt)) {
153174
evt.stopPropagation()
154175
evt.preventDefault()
155176
} 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
159180
concat(listeners['update:pressed']).forEach(fn => {
160181
if (isFunction(fn)) {
161182
fn(!props.pressed)

src/components/button/button.spec.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,22 @@ describe('button', () => {
179179
// Actually returns 4, as disabled is there twice
180180
expect(wrapper.attributes('aria-disabled')).toBeDefined()
181181
expect(wrapper.attributes('aria-disabled')).toBe('true')
182+
// Shouldnt have a role with href not `#`
183+
expect(wrapper.attributes('role')).not.toEqual('button')
184+
})
185+
186+
it('link with href="#" should have role="button"', async () => {
187+
const wrapper = mount(BButton, {
188+
propsData: {
189+
href: '#'
190+
}
191+
})
192+
193+
expect(wrapper.is('a')).toBe(true)
194+
expect(wrapper.classes()).toContain('btn')
195+
expect(wrapper.classes()).toContain('btn-secondary')
196+
expect(wrapper.classes()).not.toContain('disabled')
197+
expect(wrapper.attributes('role')).toEqual('button')
182198
})
183199

184200
it('should emit click event when clicked', async () => {
@@ -201,6 +217,38 @@ describe('button', () => {
201217
expect(evt).toBeInstanceOf(MouseEvent)
202218
})
203219

220+
it('link with href="#" should treat keydown.space as click', async () => {
221+
let called = 0
222+
let evt = null
223+
const wrapper = mount(BButton, {
224+
propsData: {
225+
href: '#'
226+
},
227+
listeners: {
228+
click: e => {
229+
evt = e
230+
called++
231+
}
232+
}
233+
})
234+
235+
expect(wrapper.is('a')).toBe(true)
236+
expect(wrapper.classes()).toContain('btn')
237+
expect(wrapper.classes()).toContain('btn-secondary')
238+
expect(wrapper.classes()).not.toContain('disabled')
239+
expect(wrapper.attributes('role')).toEqual('button')
240+
241+
expect(called).toBe(0)
242+
expect(evt).toEqual(null)
243+
244+
// We add keydown.space to make links act like buttons
245+
wrapper.find('.btn').trigger('keydown.space')
246+
expect(called).toBe(1)
247+
expect(evt).toBeInstanceOf(Event)
248+
249+
// Links treat keydown.enter natively as a click
250+
})
251+
204252
it('should not emit click event when clicked and disabled', async () => {
205253
let called = 0
206254
const wrapper = mount(BButton, {

src/components/link/link.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ export const BLink = /*#__PURE__*/ Vue.extend({
168168
},
169169
props: this.computedProps
170170
}
171-
// Add the event handlers. We must use `navtiveOn` for
171+
// Add the event handlers. We must use `nativeOn` for
172172
// `<router-link>`/`<nuxt-link>` instead of `on`
173173
componentData[isRouterLink ? 'nativeOn' : 'on'] = {
174174
// Transfer all listeners (native) to the root element

src/components/modal/modal.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,10 +604,13 @@ export const BModal = /*#__PURE__*/ Vue.extend({
604604
},
605605
onEnter() {
606606
this.isBlock = true
607+
// We add show class 1 frame after
608+
requestAF(() => {
609+
this.isShow = true
610+
})
607611
},
608612
onAfterEnter() {
609613
this.checkModalOverflow()
610-
this.isShow = true
611614
this.isTransitioning = false
612615
// We use `requestAF()` to allow transition hooks to complete
613616
// before passing control over to the other handlers

src/components/popover/README.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,37 @@ If a popover has more than one trigger, then all triggers must be cleared before
160160
close. I.e. if a popover has the trigger `focus click`, and it was opened by `focus`, and the user
161161
then clicks the trigger element, they must click it again **and** move focus to close the popover.
162162

163+
### Caveats with `focus` trigger on `<button>` elements
164+
165+
For proper cross-browser and cross-platform behavior when using only the `focus` trigger, you must
166+
use an element that renders the `<a>` tag, not the `<button>` tag, and you also must include a
167+
`tabindex="0"` attribute.
168+
169+
The following will generate an `<a>` that looks like a button:
170+
171+
```html
172+
<b-button
173+
href="#"
174+
tabindex="0"
175+
v-b-popover.focus="'Popover content'"
176+
title="Popover title"
177+
>
178+
Link button with popover directive
179+
</b-button>
180+
181+
<b-button id="link-button" href="#" tabindex="0">
182+
Link button with popover component
183+
</b-button>
184+
<b-popover target="link-button" title="Popover title" triggers="focus">
185+
Popover content
186+
</b-popover>
187+
```
188+
163189
### Dismiss on next click (self-dismissing)
164190

165191
Use the `focus` trigger by itself to dismiss popovers on the next click that the user makes. `focus`
166192
also makes the popover activate on both `focus` and `click` (as a click makes the element receive
167-
focus, assuming it is in the tab sequence of the page).
193+
focus on most browsers, assuming it is in the tab sequence of the page).
168194

169195
You can, however, specify your trigger as `click blur`, which will make only a click activate the
170196
popover, and either a click on the element, _or_ losing focus to another element or part of the
@@ -274,7 +300,7 @@ prop:
274300

275301
```html
276302
<div class="text-center">
277-
<b-button id="popover-button-variant">Button</b-button>
303+
<b-button id="popover-button-variant" href="#" tabindex="0">Button</b-button>
278304
<b-popover target="popover-button-variant" variant="danger" triggers="focus">
279305
<template v-slot:title>Danger!</template>
280306
Danger variant popover

src/components/tooltip/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,32 @@ If a tooltip has more than one trigger, then all triggers must be cleared before
106106
close. I.e. if a tooltip has the trigger `focus click`, and it was opened by `focus`, and the user
107107
then clicks the trigger element, they must click it again **and** move focus to close the tooltip.
108108

109+
### Caveats with `focus` trigger on `<button>` elements
110+
111+
For proper cross-browser and cross-platform behavior when using only the `focus` trigger, you must
112+
use an element that renders the `<a>` tag, not the `<button>` tag, and you also must include a
113+
`tabindex="0"` attribute.
114+
115+
The following will generate an `<a>` that looks like a button:
116+
117+
```html
118+
<b-button
119+
href="#"
120+
tabindex="0"
121+
v-b-tooltip.focus
122+
title="Tooltip title"
123+
>
124+
Link button with tooltip directive
125+
</b-button>
126+
127+
<b-button id="link-button" href="#" tabindex="0">
128+
Link button with tooltip component
129+
</b-button>
130+
<b-tooltip target="link-button" title="Tooltip title" triggers="focus">
131+
Tooltip title
132+
</b-tooltip>
133+
```
134+
109135
### Making tooltips work for keyboard and assistive technology users
110136

111137
You should only add tooltips to HTML elements that are traditionally keyboard-focusable and

src/directives/popover/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,25 @@ assistive technologies currently do not announce the popover in this situation.
215215
Additionally, do not rely solely on `hover` as the trigger for your popover, as this will make your
216216
popovers _impossible to trigger for keyboard-only users_.
217217

218+
### Caveats with `focus` trigger on `<button>` elements
219+
220+
For proper cross-browser and cross-platform behavior when using only the `focus` trigger, you must
221+
use an element that renders the `<a>` tag, not the `<button>` tag, and you also must include a
222+
`tabindex="0"` attribute.
223+
224+
The following will generate an `<a>` that looks like a button:
225+
226+
```html
227+
<b-button
228+
href="#"
229+
tabindex="0"
230+
v-b-popover.focus="'Popover content'"
231+
title="Popover title"
232+
>
233+
Link button with popover directive
234+
</b-button>
235+
```
236+
218237
### Dismiss on next click (self dismissing)
219238

220239
Use the `focus` trigger by itself to dismiss popovers on the next click that the user makes. `focus`

0 commit comments

Comments
 (0)