Skip to content
Permalink
Browse files

feat(b-tooltip): add `noninteractive` prop (closes #4556) (#4563)

* feat(b-tooltip): add `interactive` prop

* Update package.json

* Update README.md

* Update README.md

* Replace `interactive` prop/modifier by `noninteractive`

* Update _tooltip.scss

* Update README.md

* Update _tooltip.scss

* Update _popover.scss

* Update package.json

Co-authored-by: Troy Morehouse <troymore@nbnet.nb.ca>
  • Loading branch information
jackmu95 and tmorehouse committed Jan 8, 2020
1 parent f5a2fba commit b3ad7264d9b10fb1b8dfba70c62eed11a56519d6
@@ -2,6 +2,8 @@
.popover.b-popover {
display: block;
opacity: 1;
// Needed due to Bootstrap v4.4 reboot.css changes
outline: 0;

&.fade:not(.show) {
opacity: 0;
@@ -170,20 +170,42 @@ override the `pointer-events` on the disabled element.

| Prop | Default | Description | Supported values |
| -------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `target` | `null` | Element String ID, or a reference to an element or component, or a function returning either of them, that you want to trigger the tooltip. **Required** | Any valid, in-document unique element ID, element reference or component reference or a function returning any such ID / reference |
| `target` | `null` | Element String ID, or a reference to an element or component, or a function returning either of them, that you want to trigger the tooltip **Required** | Any valid, in-document unique element ID, element reference or component reference or a function returning any such ID / reference |
| `title` | `null` | Tooltip content (text only, no HTML). if HTML is required, place it in the default slot | Plain text |
| `placement` | `'top'` | Tooltip position, relative to the trigger element. | `top`, `bottom`, `left`, `right`, `auto`, `topleft`, `topright`, `bottomleft`, `bottomright`, `lefttop`, `leftbottom`, `righttop`, `rightbottom` |
| `fallback-placement` | `'flip'` | Auto-flip placement behaviour of the tooltip, relative to the trigger element. | `flip`, `clockwise`, `counterclockwise`, or an array of valid placements evaluated from left to right |
| `placement` | `'top'` | Tooltip position, relative to the trigger element | `top`, `bottom`, `left`, `right`, `auto`, `topleft`, `topright`, `bottomleft`, `bottomright`, `lefttop`, `leftbottom`, `righttop`, `rightbottom` |
| `fallback-placement` | `'flip'` | Auto-flip placement behaviour of the tooltip, relative to the trigger element | `flip`, `clockwise`, `counterclockwise`, or an array of valid placements evaluated from left to right |
| `triggers` | `'hover focus'` | Space separated list of event(s), which will trigger open/close of tooltip | `hover`, `focus`, `click`. Note `blur` is a special use case to close tooltip on next click, usually used in conjunction with `click`. |
| `no-fade` | `false` | Disable fade animation when set to `true` | `true` or `false` |
| `delay` | `50` | Delay showing and hiding of tooltip by specified number of milliseconds. Can also be specified as an object in the form of `{ show: 100, hide: 400 }` allowing different show and hide delays | `0` and up, integers only. |
| `offset` | `0` | Shift the center of the tooltip by specified number of pixels | Any negative or positive integer |
| `container` | `null` | Element string ID to append rendered tooltip into. If `null` or element not found, tooltip is appended to `<body>` (default) | Any valid in-document unique element ID. |
| `boundary` | `'scrollParent'` | The container that the tooltip will be constrained visually. The default should suffice in most cases, but you may need to change this if your target element is in a small container with overflow scroll | `'scrollParent'` (default), `'viewport'`, `'window'`, or a reference to an HTML element. |
| `boundary-padding` | `5` | Amount of pixel used to define a minimum distance between the boundaries and the tooltip. This makes sure the tooltip always has a little padding between the edges of its container. | Any positive number |
| `boundary-padding` | `5` | Amount of pixel used to define a minimum distance between the boundaries and the tooltip. This makes sure the tooltip always has a little padding between the edges of its container | Any positive number |
| `noninteractive` | `false` | Wether the tooltip should not be user-interactive | `true` or `false` |
| `variant` | `null` | Contextual color variant for the tooltip | Any contextual theme color variant name |
| `custom-class` | `null` | A custom classname to apply to the tooltip outer wrapper element | A string |
| `id` | `null` | An ID to use on the tooltip root element. If none is provided, one will automatically be generated. If you do provide an ID, it _must_ be guaranteed to be unique on the rendered page. | A valid unique element ID string |
| `id` | `null` | An ID to use on the tooltip root element. If none is provided, one will automatically be generated. If you do provide an ID, it _must_ be guaranteed to be unique on the rendered page | A valid unique element ID string |

### Noninteractive tooltips

BootstrapVue's tooltips are user-interactive by default for accessability reasons. To restore
Bootstraps default behavior apply the `noninteractive` prop:

```html
<div class="text-center">
<div>
<b-button id="tooltip-button-interactive">My tooltip is interactive</b-button>
<b-tooltip target="tooltip-button-interactive">I will stay open when hovered</b-tooltip>
</div>

<div class="mt-3">
<b-button id="tooltip-button-not-interactive">Mine is not...</b-button>
<b-tooltip target="tooltip-button-not-interactive" noninteractive>Catch me if you can!</b-tooltip>
</div>
</div>

<!-- b-tooltip-interactive.vue -->
```

### Variants and custom class

@@ -2,6 +2,8 @@
.tooltip.b-tooltip {
display: block;
opacity: $tooltip-opacity;
// Needed due to Bootstrap v4.4 reboot.css changes
outline: 0;

&.fade:not(.show) {
opacity: 0;
@@ -10,6 +12,12 @@
&.show {
opacity: $tooltip-opacity;
}

// Disabled pointer events when in 'noninteractive' mode to hide
// the tooltip when the user hovers over its content
&.noninteractive {
pointer-events: none;
}
}

// Create custom variants for tooltips
@@ -29,7 +29,8 @@ export const BVTooltipTemplate = /*#__PURE__*/ Vue.extend({
title: '',
content: '',
variant: null,
customClass: null
customClass: null,
interactive: true
}
},
computed: {
@@ -39,6 +40,9 @@ export const BVTooltipTemplate = /*#__PURE__*/ Vue.extend({
templateClasses() {
return [
{
// Disables pointer events to hide the tooltip when the user
// hovers over its content
noninteractive: !this.interactive,
[`b-${this.templateType}-${this.variant}`]: this.variant,
// `attachment` will come from BVToolpop
[`bs-${this.templateType}-${this.attachment}`]: this.attachment
@@ -88,6 +88,8 @@ const templateData = {
// Arrow of Tooltip/popover will try and stay away from
// the edge of tooltip/popover edge by this many pixels
arrowPadding: 6,
// Interactive state (Boolean)
interactive: true,
// Disabled state (Boolean)
disabled: false,
// ID to use for tooltip/popover
@@ -162,7 +164,8 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({
content: this.content,
variant: this.variant,
customClass: this.customClass,
noFade: this.noFade
noFade: this.noFade,
interactive: this.interactive
}
}
},
@@ -346,7 +349,7 @@ export const BVTooltip = /*#__PURE__*/ Vue.extend({
// So that the template updates accordingly
const $tip = this.$_tip
if ($tip) {
const props = ['title', 'content', 'variant', 'customClass', 'noFade']
const props = ['title', 'content', 'variant', 'customClass', 'noFade', 'interactive']
// Only update the values if they have changed
props.forEach(prop => {
if ($tip[prop] !== this[prop]) {
@@ -58,6 +58,11 @@
{
"prop": "show",
"description": "When set will show the tooltip"
},
{
"prop": "noninteractive",
"version": "2.2.0",
"description": "Wether the tooltip should not be user-interactive"
}
],
"events": [
@@ -88,6 +88,10 @@ export const BTooltip = /*#__PURE__*/ Vue.extend({
type: Boolean,
default: false
},
noninteractive: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
@@ -126,6 +130,7 @@ export const BTooltip = /*#__PURE__*/ Vue.extend({
delay: this.delay,
offset: this.offset,
noFade: this.noFade,
interactive: !this.noninteractive,
disabled: this.disabled,
id: this.id
}
@@ -9,6 +9,7 @@ const appDef = {
props: [
'triggers',
'show',
'noninteractive',
'disabled',
'noFade',
'title',
@@ -23,6 +24,7 @@ const appDef = {
target: 'foo',
triggers: this.triggers,
show: this.show,
noninteractive: this.noninteractive || false,
disabled: this.disabled,
noFade: this.noFade || false,
title: this.title || null,
@@ -173,6 +175,7 @@ describe('b-tooltip', () => {
expect(tip.tagName).toEqual('DIV')
expect(tip.classList.contains('tooltip')).toBe(true)
expect(tip.classList.contains('b-tooltip')).toBe(true)
expect(tip.classList.contains('interactive')).toBe(false)

// Hide the tooltip
wrapper.setProps({
@@ -694,9 +697,9 @@ describe('b-tooltip', () => {
expect(tip.classList.contains('b-tooltip')).toBe(true)

// Hide the tooltip by emitting event on instance
const btooltip = wrapper.find(BTooltip)
expect(btooltip.exists()).toBe(true)
btooltip.vm.$emit('close')
const bTooltip = wrapper.find(BTooltip)
expect(bTooltip.exists()).toBe(true)
bTooltip.vm.$emit('close')
await waitNT(wrapper.vm)
await waitRAF()
await waitNT(wrapper.vm)
@@ -712,7 +715,7 @@ describe('b-tooltip', () => {
expect(document.getElementById(adb)).toBe(null)

// Show the tooltip by emitting event on instance
btooltip.vm.$emit('open')
bTooltip.vm.$emit('open')

await waitNT(wrapper.vm)
await waitRAF()
@@ -1068,7 +1071,66 @@ describe('b-tooltip', () => {
wrapper.destroy()
})

it('Applies variant class', async () => {
it('applies noninteractive class based on noninteractive prop', async () => {
jest.useFakeTimers()
const App = localVue.extend(appDef)
const wrapper = mount(App, {
attachToDocument: true,
localVue: localVue,
propsData: {
show: true
},
slots: {
default: 'title'
}
})

expect(wrapper.isVueInstance()).toBe(true)
await waitNT(wrapper.vm)
await waitRAF()
await waitNT(wrapper.vm)
await waitRAF()
jest.runOnlyPendingTimers()
await waitNT(wrapper.vm)
await waitRAF()

expect(wrapper.is('article')).toBe(true)
expect(wrapper.attributes('id')).toBeDefined()
expect(wrapper.attributes('id')).toEqual('wrapper')

// The trigger button
const $button = wrapper.find('button')
expect($button.exists()).toBe(true)

// ID of the tooltip that will be in the body
const adb = $button.attributes('aria-describedby')
expect(adb).toBeDefined()
expect(adb).not.toBe('')
expect(adb).not.toBe(null)

// Find the tooltip element in the document
const tip = document.getElementById(adb)
expect(tip).not.toBe(null)
expect(tip).toBeInstanceOf(HTMLElement)
expect(tip.tagName).toEqual('DIV')
expect(tip.classList.contains('tooltip')).toBe(true)
expect(tip.classList.contains('b-tooltip')).toBe(true)
expect(tip.classList.contains('noninteractive')).toBe(false)

// Enable 'noninteractive'. Should be reactive
wrapper.setProps({
noninteractive: true
})
await waitNT(wrapper.vm)
await waitRAF()
expect(tip.classList.contains('tooltip')).toBe(true)
expect(tip.classList.contains('b-tooltip')).toBe(true)
expect(tip.classList.contains('noninteractive')).toBe(true)

wrapper.destroy()
})

it('applies variant class', async () => {
jest.useFakeTimers()
const App = localVue.extend(appDef)
const wrapper = mount(App, {
@@ -1125,7 +1187,7 @@ describe('b-tooltip', () => {
wrapper.destroy()
})

it('Applies custom class', async () => {
it('applies custom class', async () => {
jest.useFakeTimers()
const App = localVue.extend(appDef)
const wrapper = mount(App, {

0 comments on commit b3ad726

Please sign in to comment.
You can’t perform that action at this time.