Skip to content

Commit 7a34f73

Browse files
chore: create BVHoverSwap utility helper component (bootstrap-vue#4759)
* chore: create BVHoverSwap utility helper component * Update bv-hover-swap.js * Update bv-hover-swap.js * Update bv-hover-swap.js * Create bv-hover-swap.spec.js * Update bv-hover-swap.spec.js * Update bv-hover-swap.spec.js * Update bv-hover-swap.js * Update bv-hover-swap.js * Update bv-hover-swap.spec.js * Update bv-hover-swap.spec.js * Update bv-hover-swap.spec.js * Update bv-hover-swap.js * Add missing `@vue/component` comments * Update bv-hover-swap.spec.js * Update bv-hover-swap.js * Merge branch 'hover-swap' of https://github.com/bootstrap-vue/bootstrap-vue into hover-swap * Update bv-hover-swap.spec.js * Update bv-hover-swap.js * Update bv-hover-swap.js * Update bv-hover-swap.js * Update bv-hover-swap.spec.js * Update bv-hover-swap.spec.js Co-authored-by: Jacob Müller <jacob.mueller.elz@gmail.com>
1 parent 47ba834 commit 7a34f73

File tree

3 files changed

+166
-0
lines changed

3 files changed

+166
-0
lines changed

src/utils/bv-hover-swap.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import Vue from './vue'
2+
import { eventOn, eventOff } from './dom'
3+
4+
// --- Constants ---
5+
6+
const EVENT_OPTIONS = { passive: true }
7+
8+
// @vue/component
9+
export const BVHoverSwap = /*#__PURE__*/ Vue.extend({
10+
name: 'BVHoverSwap',
11+
props: {
12+
tag: {
13+
type: String,
14+
default: 'div'
15+
},
16+
parent: {
17+
type: Boolean,
18+
default: false
19+
}
20+
},
21+
data() {
22+
return {
23+
isHovered: false
24+
}
25+
},
26+
watch: {
27+
parent() {
28+
this.listen(true)
29+
}
30+
},
31+
created() {
32+
// Create non-reactive property
33+
this.$_hoverEl = null
34+
},
35+
mounted() {
36+
this.$nextTick(() => this.listen(true))
37+
},
38+
updated() /* istanbul ignore next */ {
39+
this.$nextTick(() => this.listen(true))
40+
},
41+
beforeDestroy() {
42+
this.listen(false)
43+
this.$_hoverEl = null
44+
},
45+
methods: {
46+
listen(on) {
47+
const el = this.parent ? this.$el.parentElement || this.$el : this.$el
48+
if (on && this.$_hoverEl !== el) {
49+
this.listen(false)
50+
this.$_hoverEl = el
51+
}
52+
const method = on ? eventOn : eventOff
53+
method(this.$_hoverEl, 'mouseenter', this.handleHover, EVENT_OPTIONS)
54+
method(this.$_hoverEl, 'mouseleave', this.handleHover, EVENT_OPTIONS)
55+
},
56+
handleHover(evt) {
57+
this.isHovered = evt.type === 'mouseenter'
58+
}
59+
},
60+
render(h) {
61+
const $scoped = this.$scopedSlots
62+
const $default = $scoped.default || (() => h())
63+
const $hovered = $scoped.hovered || $default
64+
return h(this.tag, [this.isHovered ? $hovered() : $default()])
65+
}
66+
})

src/utils/bv-hover-swap.spec.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { mount } from '@vue/test-utils'
2+
import { waitNT } from '../../tests/utils'
3+
import { BVHoverSwap } from './bv-hover-swap'
4+
5+
describe('utils/bv-hoverswap', () => {
6+
it('works', async () => {
7+
const wrapper = mount(BVHoverSwap, {
8+
slots: {
9+
default: '<span>FOO</span>',
10+
hovered: '<span>BAR</span>'
11+
}
12+
})
13+
14+
expect(wrapper.isVueInstance()).toBe(true)
15+
await waitNT(wrapper.vm)
16+
expect(wrapper.is('div')).toBe(true)
17+
expect(wrapper.text()).toBe('FOO')
18+
19+
wrapper.trigger('mouseenter')
20+
await waitNT(wrapper.vm)
21+
22+
expect(wrapper.is('div')).toBe(true)
23+
expect(wrapper.text()).toBe('BAR')
24+
25+
wrapper.trigger('mouseleave')
26+
await waitNT(wrapper.vm)
27+
28+
expect(wrapper.is('div')).toBe(true)
29+
expect(wrapper.text()).toBe('FOO')
30+
31+
wrapper.destroy()
32+
})
33+
34+
it('works when `parent` is true ', async () => {
35+
const app = {
36+
props: {
37+
parent: {
38+
type: Boolean,
39+
defaut: false
40+
}
41+
},
42+
methods: {
43+
foo() {
44+
return this.$createElement('span', {}, 'FOO')
45+
},
46+
bar() {
47+
return this.$createElement('span', {}, 'BAR')
48+
}
49+
},
50+
render(h) {
51+
const $content = h(BVHoverSwap, {
52+
props: { parent: this.parent },
53+
scopedSlots: { default: this.foo, hovered: this.bar }
54+
})
55+
return h('div', {}, [$content])
56+
}
57+
}
58+
const wrapper = mount(app, {
59+
propsData: {
60+
parent: true
61+
}
62+
})
63+
64+
expect(wrapper.isVueInstance()).toBe(true)
65+
await waitNT(wrapper.vm)
66+
expect(wrapper.is('div')).toBe(true)
67+
expect(wrapper.find('div > div').exists()).toBe(true)
68+
expect(wrapper.find('div > div').is(BVHoverSwap)).toBe(true)
69+
expect(wrapper.text()).toBe('FOO')
70+
71+
wrapper.trigger('mouseenter')
72+
await waitNT(wrapper.vm)
73+
74+
expect(wrapper.is('div')).toBe(true)
75+
expect(wrapper.text()).toBe('BAR')
76+
77+
wrapper.trigger('mouseleave')
78+
await waitNT(wrapper.vm)
79+
80+
expect(wrapper.text()).toBe('FOO')
81+
82+
wrapper.setProps({
83+
parent: false
84+
})
85+
await waitNT(wrapper.vm)
86+
87+
wrapper.trigger('mouseenter')
88+
await waitNT(wrapper.vm)
89+
90+
expect(wrapper.text()).toBe('FOO')
91+
92+
wrapper.find('div > div').trigger('mouseenter')
93+
await waitNT(wrapper.vm)
94+
95+
expect(wrapper.text()).toBe('BAR')
96+
97+
wrapper.destroy()
98+
})
99+
})

src/utils/bv-transition.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const FADE_PROPS = {
2424
leaveActiveClass: 'fade'
2525
}
2626

27+
// @vue/component
2728
export const BVTransition = /*#__PURE__*/ Vue.extend({
2829
name: 'BVTransition',
2930
functional: true,

0 commit comments

Comments
 (0)