Skip to content

Commit eebab43

Browse files
authored
feat(tooltip, popover): overall code refactor for better reactivity and performance (fixes: #1990, #2937, #3480, #3717, #3854, closes #3451) (#3908)
1 parent 5c4c89a commit eebab43

26 files changed

+2675
-2132
lines changed

src/components/popover/README.md

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,17 @@
66
77
```html
88
<div class="text-center my-3">
9-
<b-button v-b-popover.hover="'I am popover content!'" title="Popover Title">Hover Me</b-button>
9+
<b-button v-b-popover.hover.top="'I am popover directive content!'" title="Popover Title">
10+
Hover Me
11+
</b-button>
12+
13+
<b-button id="popover-target-1">
14+
Hover Me
15+
</b-button>
16+
<b-popover target="popover-target-1" triggers="hover" placement="top">
17+
<template v-slot:title>Popover Title</template>
18+
I am popover <b>component</b> content!
19+
</b-popover>
1020
</div>
1121

1222
<!-- b-popover.vue -->
@@ -17,7 +27,6 @@
1727
Things to know when using popover component:
1828

1929
- Popovers rely on the 3rd party library [Popper.js](https://popper.js.org/) for positioning.
20-
- Popovers with zero-length title _and_ content are never displayed.
2130
- Specify `container` as `null` (default, appends to `<body>`) to avoid rendering problems in more
2231
complex components (like input groups, button groups, etc). You can use `container` to optionally
2332
specify a different element to append the rendered popover to.
@@ -26,22 +35,10 @@ Things to know when using popover component:
2635
- When triggered from hyperlinks that span multiple lines, popovers will be centered. Use
2736
`white-space: nowrap;` on your `<a>`s, `<b-link>`s and `<router-link>`s to avoid this behavior.
2837

29-
The `<b-popover>` component inserts a hidden (`display: none;`) `<div>` intermediate container
30-
element at the point in the DOM where the `<b-popover>` component is placed. This may affect layout
31-
and/or styling of components such as `<b-button-group>`, `<b-button-toolbar>`, and
32-
`<b-input-group>`. To avoid these possible layout issues, place the `<b-popover>` component
33-
**outside** of these types of components.
34-
3538
The target element **must** exist in the document before `<b-popover>` is mounted. If the target
3639
element is not found during mount, the popover will never open. Always place your `<b-popover>`
3740
component lower in the DOM than your target element.
3841

39-
**Note:** _When using slots for content and/or title, `<b-popover>` transfers the rendered DOM from
40-
those slots into the popover's markup when shown, and returns them back to the `<b-popover>`
41-
component when hidden. This may cause some issues in rare circumstances, so please test your
42-
implementation accordingly! The `title` and `content` props do not have this behavior. For simple
43-
popovers, we recommend using the `v-b-popover` directive and enable the `html` modifier if needed._
44-
4542
## Positioning
4643

4744
Twelve options are available for positioning: `top`, `topleft`, `topright`, `right`, `righttop`,
@@ -155,7 +152,8 @@ Positioning is relative to the trigger element.
155152
## Triggers
156153

157154
Popovers can be triggered (opened/closed) via any combination of `click`, `hover` and `focus`. The
158-
default trigger is `click`.
155+
default trigger is `click`. Or a trigger of `manual` can be specified, where the popover can only be
156+
opened or closed [programmatically](#programmatically-disabling-popover).
159157

160158
If a popover has more than one trigger, then all triggers must be cleared before the popover will
161159
close. I.e. if a popover has the trigger `focus click`, and it was opened by `focus`, and the user
@@ -259,13 +257,14 @@ The special `blur` trigger **must** be used in combination with the `click` trig
259257
| `disabled` | `false` | Programmatic control of the Popover display state. Recommended to use with [sync modifier](https://vuejs.org/v2/guide/components.html#sync-Modifier). | `true`, `false` |
260258
| `triggers` | `'click'` | Space separated list of event(s), which will trigger open/close of popover using built-in handling | `hover`, `focus`, `click`. Note `blur` is a special use case to close popover on next click. |
261259
| `no-fade` | `false` | Disable fade animation when set to `true` | `true` or `false` |
262-
| `delay` | `0` | Delay showing and hiding of popover by specified number of milliseconds. Can also be defined as an object in the form of `{ show: 100, hide: 400 }` allowing different show and hide delays | `0` and up, integers only. |
260+
| `delay` | `50` | Delay showing and hiding of popover by specified number of milliseconds. Can also be defined as an object in the form of `{ show: 100, hide: 400 }` allowing different show and hide delays | `0` and up, integers only. |
263261
| `offset` | `0` | Shift the center of the popover by specified number of pixels. Also affects the position of the popover arrow. | Any negative or positive integer |
264262
| `container` | `null` | Element string ID to append rendered popover into. If `null` or element not found, popover is appended to `<body>` (default) | Any valid in-document unique element ID. |
265263
| `boundary` | `'scrollParent'` | The container that the popover 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. |
266264
| `boundary-padding` | `5` | Amount of pixel used to define a minimum distance between the boundaries and the popover. This makes sure the popover always has a little padding between the edges of its container. | Any positive number |
267265
| `variant` | `null` | Contextual color variant for the popover | Any contextual theme color variant name |
268-
| `customClass` | `null` | A custom classname to apply to the popover outer wrapper element | A string |
266+
| `custom-class` | `null` | A custom classname to apply to the popover outer wrapper element | A string |
267+
| `id` | `null` | An ID to use on the popover 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 |
269268

270269
### Variants and custom class
271270

@@ -300,8 +299,7 @@ A custom class can be applied to the popover outer wrapper `<div>` by using the
300299
</div>
301300
```
302301

303-
**Note:** Custom classes will not work with scoped styles, as the popovers are appended to the
304-
document `<body>` element by default.
302+
`variant` and `custom-class` are reactive and can be changed while the popover is open.
305303

306304
Refer to the [popover directive](/docs/directives/popover) docs on applying variants and custom
307305
class to the directive version.
@@ -582,7 +580,7 @@ Just need quick popovers without too much markup? Use the
582580
</b-col>
583581

584582
<b-col md="3" class="py-3">
585-
<b-button v-b-popover.hover.bottom="'ToolTip!'" variant="primary">Bottom</b-button>
583+
<b-button v-b-popover.hover.bottom="'Tooltip!'" variant="primary">Bottom</b-button>
586584
</b-col>
587585
</b-row>
588586
</b-container>
@@ -596,9 +594,8 @@ information on the directive usage.
596594

597595
## Advanced `<b-popover>` usage with reactive content
598596

599-
You can even make your `<b-popover>` content interactive. Just remember not to use the `focus`,
600-
`hover` or `blur` triggers (use only `click`), otherwise your popover will close automatically as
601-
soon as someone will try to interact with the content.
597+
You can even make your `<b-popover>` content interactive. Just remember not to use the `focus` or
598+
triggers (use only `click`).
602599

603600
If you absolutely must use a trigger other than `click` (or want to disable closing of the popover
604601
when the trigger element is clicked a second time), then you can either:
@@ -617,7 +614,7 @@ to deal with on mobile devices (such as smart-phones).
617614
<div id="my-container">
618615
<div class="my-3">
619616
<!-- Our triggering (target) element -->
620-
<b-button id="popover-reactive-1" :disabled="popoverShow" variant="primary" ref="button">
617+
<b-button id="popover-reactive-1" variant="primary" ref="button">
621618
Reactive Content Using Slots
622619
</b-button>
623620
</div>
@@ -798,14 +795,16 @@ You can close (hide) **all open popovers** by emitting the `bv::hide::popover` e
798795
this.$root.$emit('bv::hide::popover')
799796
```
800797

801-
To close a **specific popover**, pass the trigger element's `id` as the first argument:
798+
To close a **specific popover**, pass the trigger element's `id`, or the `id` of the popover (if one
799+
was provided via the `id` prop), as the first argument:
802800

803801
```js
804802
this.$root.$emit('bv::hide::popover', 'my-trigger-button-id')
805803
```
806804

807-
To open (show) a **specific popover**, pass the trigger element's `id` as the first argument when
808-
emitting the `bv::show::popover` event:
805+
To open (show) a **specific popover**, pass the trigger element's `id`, or the `id` of the popover
806+
(if one was provided via the `id` prop), as the first argument when emitting the `bv::show::popover`
807+
event:
809808

810809
```js
811810
this.$root.$emit('bv::show::popover', 'my-trigger-button-id')
@@ -827,14 +826,16 @@ You can disable **all** popovers by emitting the `bv::disable::popover` event on
827826
this.$root.$emit('bv::disable::popover')
828827
```
829828

830-
To disable a **specific popover**, pass the trigger element's `id` as the first argument:
829+
To disable a **specific popover**, pass the trigger element's `id`, or the `id` of the popover (if
830+
one was provided via the `id` prop), as the first argument:
831831

832832
```js
833833
this.$root.$emit('bv::disable::popover', 'my-trigger-button-id')
834834
```
835835

836-
To enable a **specific popover**, pass the trigger element's `id` as the first argument when
837-
emitting the `bv::enable::popover` event:
836+
To enable a **specific popover**, pass the trigger element's `id`, or the `id` of the popover (if
837+
one was provided via the `id` prop), as the first argument when emitting the `bv::enable::popover`
838+
event:
838839

839840
```js
840841
this.$root.$emit('bv::enable::popover', 'my-trigger-button-id')

src/components/popover/_popover.scss

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
// Some overrides to make popover transitions work with Vue `<transition>`
2+
.popover.b-popover {
3+
display: block;
4+
opacity: 1;
5+
6+
&.fade:not(.show) {
7+
opacity: 0;
8+
}
9+
10+
&.show {
11+
opacity: 1;
12+
}
13+
}
14+
115
@if $bv-enable-popover-variants {
216
@each $variant, $value in $theme-colors {
317
.b-popover-#{$variant} {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import Vue from '../../../utils/vue'
2+
import { isFunction, isUndefinedOrNull } from '../../../utils/inspect'
3+
4+
import { BVTooltipTemplate } from '../../tooltip/helpers/bv-tooltip-template'
5+
6+
const NAME = 'BVPopoverTemplate'
7+
8+
// @vue/component
9+
export const BVPopoverTemplate = /*#__PURE__*/ Vue.extend({
10+
name: NAME,
11+
extends: BVTooltipTemplate,
12+
computed: {
13+
templateType() {
14+
return 'popover'
15+
}
16+
},
17+
methods: {
18+
renderTemplate(h) {
19+
// Title and content could be a scoped slot function
20+
const $title = isFunction(this.title) ? this.title({}) : this.title
21+
const $content = isFunction(this.content) ? this.content({}) : this.content
22+
23+
// Directive usage only
24+
const titleDomProps = this.html && !isFunction(this.title) ? { innerHTML: this.title } : {}
25+
const contentDomProps =
26+
this.html && !isFunction(this.content) ? { innerHTML: this.content } : {}
27+
28+
return h(
29+
'div',
30+
{
31+
staticClass: 'popover b-popover',
32+
class: this.templateClasses,
33+
attrs: this.templateAttributes,
34+
on: this.templateListeners
35+
},
36+
[
37+
h('div', { ref: 'arrow', staticClass: 'arrow' }),
38+
isUndefinedOrNull($title)
39+
? h()
40+
: h('h3', { staticClass: 'popover-header', domProps: titleDomProps }, [$title]),
41+
isUndefinedOrNull($content)
42+
? h()
43+
: h('div', { staticClass: 'popover-body', domProps: contentDomProps }, [$content])
44+
]
45+
)
46+
}
47+
}
48+
})
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Popover "Class" (Built as a renderless Vue instance)
2+
// Inherits from BVTooltip
3+
//
4+
// Handles trigger events, etc.
5+
// Instantiates template on demand
6+
7+
import Vue from '../../../utils/vue'
8+
import { BVTooltip } from '../../tooltip/helpers/bv-tooltip'
9+
import { BVPopoverTemplate } from './bv-popover-template'
10+
11+
const NAME = 'BVPopover'
12+
13+
// @vue/component
14+
export const BVPopover = /*#__PURE__*/ Vue.extend({
15+
name: NAME,
16+
extends: BVTooltip,
17+
computed: {
18+
// Overwrites BVTooltip
19+
templateType() {
20+
return 'popover'
21+
}
22+
},
23+
methods: {
24+
getTemplate() {
25+
// Overwrites BVTooltip
26+
return BVPopoverTemplate
27+
}
28+
}
29+
})

src/components/popover/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,11 @@
165165
"slots": [
166166
{
167167
"name": "title",
168-
"description": "Optional slot for title (html supported)"
168+
"description": "Optional slot for title (HTML supported)"
169+
},
170+
{
171+
"name": "default",
172+
"description": "Slot for content (HTML supported)"
169173
}
170174
]
171175
}

0 commit comments

Comments
 (0)