Skip to content

Commit 2eab55b

Browse files
fix(b-dropdown): focus-in handling for Safari and Firefox on macOS/iOS (closes bootstrap-vue#4328) (bootstrap-vue#4426)
* fix(b-dropdown): focus-in handling for Safari and Firefox on macOS/iOS * Update dropdown.js * Fix dropdown toggle focus-in handling * Handle 'touchstart' * Revert "Handle 'touchstart'" This reverts commit b46ab2b. * Remove outdated stuff * Update dropdown.js * Add temporary logs * Update click-out.js * Update dropdown.js * Improve `inNavbar` detection by using provide/inject * Correct typos * add comment with link to issue * Update dropdown.js Co-authored-by: Troy Morehouse <troymore@nbnet.nb.ca>
1 parent 3a50ad8 commit 2eab55b

File tree

9 files changed

+115
-135
lines changed

9 files changed

+115
-135
lines changed

src/components/dropdown/README.md

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -708,13 +708,6 @@ The `.dropdown-menu` is the `<ul>` element, while dropdown items (items, buttons
708708
headers, and dividers) are wrapped in an `<li>` element. If creating custom items to place inside
709709
the dropdown menu, ensure they are wrapped with a plain `<li>`.
710710

711-
On touch-enabled devices, opening a `<b-dropdown>` adds empty (noop) `mouseover` handlers to the
712-
immediate children of the `<body>` element. This admittedly ugly hack is necessary to work around a
713-
[quirk in iOS' event delegation](https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html),
714-
which would otherwise prevent a tap anywhere outside of the dropdown from triggering the code that
715-
closes the dropdown. Once the dropdown is closed, these additional empty `mouseover` handlers are
716-
removed.
717-
718711
## See also
719712

720713
- [`<b-nav-item-dropdown>`](/docs/components/nav#dropdown-support) for dropdown support inside

src/components/dropdown/dropdown-item-button.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Vue from '../../utils/vue'
2-
import nomalizeSlotMixin from '../../mixins/normalize-slot'
2+
import normalizeSlotMixin from '../../mixins/normalize-slot'
33

44
export const props = {
55
active: {
@@ -23,7 +23,7 @@ export const props = {
2323
// @vue/component
2424
export const BDropdownItemButton = /*#__PURE__*/ Vue.extend({
2525
name: 'BDropdownItemButton',
26-
mixins: [nomalizeSlotMixin],
26+
mixins: [normalizeSlotMixin],
2727
inheritAttrs: false,
2828
inject: {
2929
bvDropdown: {

src/components/dropdown/dropdown-item.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import Vue from '../../utils/vue'
22
import { requestAF } from '../../utils/dom'
3-
import nomalizeSlotMixin from '../../mixins/normalize-slot'
3+
import normalizeSlotMixin from '../../mixins/normalize-slot'
44
import { BLink, propsFactory as linkPropsFactory } from '../link/link'
55

66
export const props = linkPropsFactory()
77

88
// @vue/component
99
export const BDropdownItem = /*#__PURE__*/ Vue.extend({
1010
name: 'BDropdownItem',
11-
mixins: [nomalizeSlotMixin],
11+
mixins: [normalizeSlotMixin],
1212
inheritAttrs: false,
1313
inject: {
1414
bvDropdown: {

src/components/dropdown/dropdown.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ export const BDropdown = /*#__PURE__*/ Vue.extend({
146146
id: this.safeId('_BV_button_')
147147
},
148148
on: {
149-
click: this.click
149+
click: this.onSplitClick
150150
}
151151
},
152152
[buttonContent]
@@ -171,8 +171,9 @@ export const BDropdown = /*#__PURE__*/ Vue.extend({
171171
'aria-expanded': this.visible ? 'true' : 'false'
172172
},
173173
on: {
174-
click: this.toggle, // click
175-
keydown: this.toggle // enter, space, down
174+
mousedown: this.onMousedown,
175+
click: this.toggle,
176+
keydown: this.toggle // Handle ENTER, SPACE and DOWN
176177
}
177178
},
178179
[this.split ? h('span', { class: ['sr-only'] }, [this.toggleText]) : buttonContent]
@@ -189,7 +190,7 @@ export const BDropdown = /*#__PURE__*/ Vue.extend({
189190
'aria-labelledby': this.safeId(this.split ? '_BV_button_' : '_BV_toggle_')
190191
},
191192
on: {
192-
keydown: this.onKeydown // up, down, esc
193+
keydown: this.onKeydown // Handle UP, DOWN and ESC
193194
}
194195
},
195196
!this.lazy || this.visible ? this.normalizeSlot('default', { hide: this.hide }) : [h()]

src/components/nav/nav-item-dropdown.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,9 @@ export const BNavItemDropdown = /*#__PURE__*/ Vue.extend({
5454
'aria-expanded': this.visible ? 'true' : 'false'
5555
},
5656
on: {
57+
mousedown: this.onMousedown,
5758
click: this.toggle,
58-
keydown: this.toggle // space, enter, down
59+
keydown: this.toggle // Handle ENTER, SPACE and DOWN
5960
}
6061
},
6162
[
@@ -75,7 +76,7 @@ export const BNavItemDropdown = /*#__PURE__*/ Vue.extend({
7576
'aria-labelledby': this.safeId('_BV_button_')
7677
},
7778
on: {
78-
keydown: this.onKeydown // up, down, esc
79+
keydown: this.onKeydown // Handle UP, DOWN and ESC
7980
}
8081
},
8182
!this.lazy || this.visible ? this.normalizeSlot('default', { hide: this.hide }) : [h()]

src/components/navbar/navbar.js

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Vue from '../../utils/vue'
2-
import { mergeData } from 'vue-functional-data-merge'
32
import { getComponentConfig, getBreakpoints } from '../../utils/config'
43
import { isString } from '../../utils/inspect'
4+
import normalizeSlotMixin from '../../mixins/normalize-slot'
55

66
const NAME = 'BNavbar'
77

@@ -38,33 +38,45 @@ export const props = {
3838
// @vue/component
3939
export const BNavbar = /*#__PURE__*/ Vue.extend({
4040
name: NAME,
41-
functional: true,
41+
mixins: [normalizeSlotMixin],
4242
props,
43-
render(h, { props, data, children }) {
44-
let breakpoint = ''
45-
const xs = getBreakpoints()[0]
46-
if (props.toggleable && isString(props.toggleable) && props.toggleable !== xs) {
47-
breakpoint = `navbar-expand-${props.toggleable}`
48-
} else if (props.toggleable === false) {
49-
breakpoint = 'navbar-expand'
43+
provide() {
44+
return { bvNavbar: this }
45+
},
46+
computed: {
47+
breakpointClass() {
48+
let breakpoint = null
49+
const xs = getBreakpoints()[0]
50+
const toggleable = this.toggleable
51+
if (toggleable && isString(toggleable) && toggleable !== xs) {
52+
breakpoint = `navbar-expand-${toggleable}`
53+
} else if (toggleable === false) {
54+
breakpoint = 'navbar-expand'
55+
}
56+
57+
return breakpoint
5058
}
59+
},
60+
render(h) {
5161
return h(
52-
props.tag,
53-
mergeData(data, {
62+
this.tag,
63+
{
5464
staticClass: 'navbar',
55-
class: {
56-
'd-print': props.print,
57-
'sticky-top': props.sticky,
58-
[`navbar-${props.type}`]: props.type,
59-
[`bg-${props.variant}`]: props.variant,
60-
[`fixed-${props.fixed}`]: props.fixed,
61-
[`${breakpoint}`]: breakpoint
62-
},
65+
class: [
66+
{
67+
'd-print': this.print,
68+
'sticky-top': this.sticky,
69+
[`navbar-${this.type}`]: this.type,
70+
[`bg-${this.variant}`]: this.variant,
71+
[`fixed-${this.fixed}`]: this.fixed
72+
},
73+
this.breakpointClass
74+
],
6375
attrs: {
64-
role: props.tag === 'nav' ? null : 'navigation'
76+
role: this.tag === 'nav' ? null : 'navigation'
6577
}
66-
}),
67-
children
78+
},
79+
[this.normalizeSlot('default')]
6880
)
6981
}
7082
})

src/components/navbar/navbar.spec.js

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ describe('navbar', () => {
1919

2020
it('accepts custom tag', async () => {
2121
const wrapper = mount(BNavbar, {
22-
context: {
23-
props: { tag: 'div' }
24-
}
22+
propsData: { tag: 'div' }
2523
})
2624
expect(wrapper.is('div')).toBe(true)
2725
expect(wrapper.attributes('role')).toBeDefined()
@@ -30,9 +28,7 @@ describe('navbar', () => {
3028

3129
it('accepts breakpoint via toggleable prop', async () => {
3230
const wrapper = mount(BNavbar, {
33-
context: {
34-
props: { toggleable: 'lg' }
35-
}
31+
propsData: { toggleable: 'lg' }
3632
})
3733
expect(wrapper.classes()).toContain('navbar')
3834
expect(wrapper.classes()).toContain('navbar-expand-lg')
@@ -42,9 +38,7 @@ describe('navbar', () => {
4238

4339
it('toggleable=true has expected classes', async () => {
4440
const wrapper = mount(BNavbar, {
45-
context: {
46-
props: { toggleable: true }
47-
}
41+
propsData: { toggleable: true }
4842
})
4943
expect(wrapper.classes()).toContain('navbar')
5044
expect(wrapper.classes()).toContain('navbar-light')
@@ -53,9 +47,7 @@ describe('navbar', () => {
5347

5448
it('toggleable=xs has expected classes', async () => {
5549
const wrapper = mount(BNavbar, {
56-
context: {
57-
props: { toggleable: 'xs' }
58-
}
50+
propsData: { toggleable: 'xs' }
5951
})
6052
expect(wrapper.classes()).toContain('navbar')
6153
expect(wrapper.classes()).toContain('navbar-light')
@@ -64,9 +56,7 @@ describe('navbar', () => {
6456

6557
it('has class "fixed-top" when fixed="top"', async () => {
6658
const wrapper = mount(BNavbar, {
67-
context: {
68-
props: { fixed: 'top' }
69-
}
59+
propsData: { fixed: 'top' }
7060
})
7161
expect(wrapper.classes()).toContain('fixed-top')
7262
expect(wrapper.classes()).toContain('navbar')
@@ -77,9 +67,7 @@ describe('navbar', () => {
7767

7868
it('has class "fixed-top" when fixed="top"', async () => {
7969
const wrapper = mount(BNavbar, {
80-
context: {
81-
props: { fixed: 'top' }
82-
}
70+
propsData: { fixed: 'top' }
8371
})
8472
expect(wrapper.classes()).toContain('fixed-top')
8573
expect(wrapper.classes()).toContain('navbar')
@@ -90,9 +78,7 @@ describe('navbar', () => {
9078

9179
it('has class "sticky-top" when sticky=true', async () => {
9280
const wrapper = mount(BNavbar, {
93-
context: {
94-
props: { sticky: true }
95-
}
81+
propsData: { sticky: true }
9682
})
9783
expect(wrapper.classes()).toContain('sticky-top')
9884
expect(wrapper.classes()).toContain('navbar')
@@ -103,9 +89,7 @@ describe('navbar', () => {
10389

10490
it('accepts variant prop', async () => {
10591
const wrapper = mount(BNavbar, {
106-
context: {
107-
props: { variant: 'primary' }
108-
}
92+
propsData: { variant: 'primary' }
10993
})
11094
expect(wrapper.classes()).toContain('bg-primary')
11195
expect(wrapper.classes()).toContain('navbar')

src/mixins/click-out.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export default {
2929
this.clickOutElement = document
3030
}
3131
if (!this.clickOutEventName) {
32-
this.clickOutEventName = 'ontouchstart' in document.documentElement ? 'touchstart' : 'click'
32+
this.clickOutEventName = 'click'
3333
}
3434
if (this.listenForClickOut) {
3535
eventOn(this.clickOutElement, this.clickOutEventName, this._clickOutHandler, eventOptions)

0 commit comments

Comments
 (0)