|
2 | 2 | <div>
|
3 | 3 | <span ref="trigger"><slot></slot></span>
|
4 | 4 |
|
5 |
| - <div tabindex="-1" :class="['popover',popoverAlignment]" ref="popover" @focus="$emit('focus')" |
| 5 | + <div tabindex="-1" class="popover fade" :class="[classState ? 'show' : '', popoverAlignment]" ref="popover" @focus="$emit('focus')" |
6 | 6 | @blur="$emit('blur')" :style="popoverStyle">
|
7 | 7 | <div class="popover-arrow"></div>
|
8 | 8 | <h3 class="popover-title" v-if="title" v-html="title"></h3>
|
|
20 | 20 | import Tether from 'tether';
|
21 | 21 |
|
22 | 22 | // Controls which events are mapped for each named trigger, and the expected popover behavior for each.
|
23 |
| - const triggerListeners = { |
| 23 | + const TRIGGER_LISTENERS = { |
24 | 24 | click: {click: 'toggle'},
|
25 | 25 | hover: {mouseenter: 'show', mouseleave: 'hide'},
|
26 | 26 | focus: {focus: 'show', blur: 'hide'}
|
27 | 27 | };
|
28 | 28 |
|
29 |
| - const placementParams = { |
| 29 | + const PLACEMENT_PARAMS = { |
30 | 30 | top: {
|
31 | 31 | attachment: 'bottom center',
|
32 | 32 | targetAttachment: 'top center'
|
|
46 | 46 | }
|
47 | 47 | };
|
48 | 48 |
|
| 49 | + const TRANSITION_DURATION = 150; |
| 50 | +
|
49 | 51 | export default {
|
50 | 52 | props: {
|
51 | 53 | constraints: {
|
|
92 | 94 | type: String,
|
93 | 95 | default: 'top',
|
94 | 96 | validator(value) {
|
95 |
| - return ['top', 'bottom', 'left', 'right'].indexOf(value) !== -1; |
| 97 | + return Object.keys(PLACEMENT_PARAMS).indexOf(value) !== -1; |
96 | 98 | }
|
97 | 99 | },
|
98 | 100 | popoverStyle: {
|
|
115 | 117 | if (value === false || value === '') {
|
116 | 118 | return true;
|
117 | 119 | } else if (typeof value === 'string') {
|
118 |
| - return Object.keys(triggerListeners).indexOf(value) !== -1; |
| 120 | + return Object.keys(TRIGGER_LISTENERS).indexOf(value) !== -1; |
119 | 121 | } else if (Array.isArray(value)) {
|
120 |
| - const keys = Object.keys(triggerListeners); |
| 122 | + const keys = Object.keys(TRIGGER_LISTENERS); |
121 | 123 | value.forEach(item => {
|
122 | 124 | if (keys.indexOf(item) === -1) {
|
123 | 125 | return false;
|
|
133 | 135 | data() {
|
134 | 136 | return {
|
135 | 137 | triggerState: this.show,
|
| 138 | + classState: this.show, |
136 | 139 | lastEvent: null
|
137 | 140 | };
|
138 | 141 | },
|
|
226 | 229 | */
|
227 | 230 | addListener(trigger) {
|
228 | 231 | // eslint-disable-next-line guard-for-in
|
229 |
| - for (const item in triggerListeners[trigger]) { |
| 232 | + for (const item in TRIGGER_LISTENERS[trigger]) { |
230 | 233 | this._trigger.addEventListener(item, e => this.eventHandler(e));
|
231 | 234 | }
|
232 | 235 | },
|
|
235 | 238 | * Tidy removal of Tether object from the DOM
|
236 | 239 | */
|
237 | 240 | destroyTether() {
|
238 |
| - if (this._tether) { |
| 241 | + if (this._tether && !this.showState) { |
239 | 242 | this._tether.destroy();
|
240 | 243 | this._tether = null;
|
241 | 244 | }
|
|
253 | 256 |
|
254 | 257 | // Look up the expected popover action for the event
|
255 | 258 | // eslint-disable-next-line guard-for-in
|
256 |
| - for (const trigger in triggerListeners) { |
257 |
| - for (const event in triggerListeners[trigger]) { |
| 259 | + for (const trigger in TRIGGER_LISTENERS) { |
| 260 | + for (const event in TRIGGER_LISTENERS[trigger]) { |
258 | 261 | if (event === e.type) {
|
259 |
| - const action = triggerListeners[trigger][event]; |
| 262 | + const action = TRIGGER_LISTENERS[trigger][event]; |
260 | 263 |
|
261 | 264 | // If the expected event action is the opposite of the current state, allow it
|
262 | 265 | if (action === 'toggle' || (this.triggerState && action === 'hide') || (!this.triggerState && action === 'show')) {
|
|
293 | 296 | target: this._trigger,
|
294 | 297 | offset: this.offset,
|
295 | 298 | constraints: this.constraints,
|
296 |
| - attachment: placementParams[this.placement].attachment, |
297 |
| - targetAttachment: placementParams[this.placement].targetAttachment |
| 299 | + attachment: PLACEMENT_PARAMS[this.placement].attachment, |
| 300 | + targetAttachment: PLACEMENT_PARAMS[this.placement].targetAttachment |
298 | 301 | };
|
299 | 302 | },
|
300 | 303 |
|
301 | 304 | /**
|
302 | 305 | * Hide popover and fire event
|
303 | 306 | */
|
304 | 307 | hidePopover() {
|
305 |
| - this._popover.style.display = 'none'; |
306 |
| - this.destroyTether(); |
| 308 | + this.classState = false; |
| 309 | + clearTimeout(this._timeout); |
| 310 | + this._timeout = setTimeout(() => { |
| 311 | + this._popover.style.display = 'none'; |
| 312 | + this.destroyTether(); |
| 313 | + }, TRANSITION_DURATION); |
307 | 314 | },
|
308 | 315 |
|
309 | 316 | /**
|
|
323 | 330 | */
|
324 | 331 | removeListener(trigger) {
|
325 | 332 | // eslint-disable-next-line guard-for-in
|
326 |
| - for (const item in triggerListeners[trigger]) { |
| 333 | + for (const item in TRIGGER_LISTENERS[trigger]) { |
327 | 334 | this._trigger.removeEventListener(item, e => this.eventHandler(e));
|
328 | 335 | }
|
329 | 336 | },
|
|
341 | 348 | * Display popover and fire event
|
342 | 349 | */
|
343 | 350 | showPopover() {
|
344 |
| - // Just in case |
345 |
| - this.destroyTether(); |
| 351 | + clearTimeout(this._timeout); |
346 | 352 |
|
347 |
| - // Let tether do the magic, after element is shown |
| 353 | + if (!this._tether) { |
| 354 | + this._tether = new Tether(this.getTetherOptions()); |
| 355 | + } |
348 | 356 | this._popover.style.display = 'block';
|
349 |
| - this._tether = new Tether(this.getTetherOptions()); |
350 | 357 |
|
351 | 358 | // Make sure the popup is rendered in the correct location
|
352 | 359 | this.refreshPosition();
|
| 360 | +
|
| 361 | + this.$nextTick(() => { |
| 362 | + this.classState = true; |
| 363 | + }); |
353 | 364 | },
|
354 | 365 |
|
355 | 366 | /**
|
|
405 | 416 | // Configure tether
|
406 | 417 | this._trigger = this.$refs.trigger.children[0];
|
407 | 418 | this._popover = this.$refs.popover;
|
408 |
| - this._popover.style.display = 'none'; |
409 | 419 | this._timeout = 0;
|
410 | 420 |
|
411 | 421 | // Add listeners for specified triggers and complementary click event
|
|
0 commit comments