tooltip.js 19.8 KB
Newer Older
1
2
/**
 * --------------------------------------------------------------------------
XhmikosR's avatar
XhmikosR committed
3
 * Bootstrap (v4.3.1): tooltip.js
4
5
6
7
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * --------------------------------------------------------------------------
 */

8
9
10
11
import {
  DefaultWhitelist,
  sanitizeHtml
} from './tools/sanitizer'
12
13
import Data from './dom/data'
import EventHandler from './dom/eventHandler'
14
import Manipulator from './dom/manipulator'
15
import Popper from 'popper.js'
16
import SelectorEngine from './dom/selectorEngine'
17
18
import Util from './util'

Johann-S's avatar
Johann-S committed
19
20
21
22
23
/**
 * ------------------------------------------------------------------------
 * Constants
 * ------------------------------------------------------------------------
 */
24

25
const NAME                  = 'tooltip'
XhmikosR's avatar
XhmikosR committed
26
const VERSION               = '4.3.1'
27
28
29
30
31
const DATA_KEY              = 'bs.tooltip'
const EVENT_KEY             = `.${DATA_KEY}`
const CLASS_PREFIX          = 'bs-tooltip'
const BSCLS_PREFIX_REGEX    = new RegExp(`(^|\\s)${CLASS_PREFIX}\\S+`, 'g')
const DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn']
Johann-S's avatar
Johann-S committed
32

33

Johann-S's avatar
Johann-S committed
34
const DefaultType = {
35
36
37
38
39
40
41
42
  animation         : 'boolean',
  template          : 'string',
  title             : '(string|element|function)',
  trigger           : 'string',
  delay             : '(number|object)',
  html              : 'boolean',
  selector          : '(string|boolean)',
  placement         : '(string|function)',
43
  offset            : '(number|string|function)',
44
45
  container         : '(string|element|boolean)',
  fallbackPlacement : '(string|array)',
46
47
48
49
  boundary          : '(string|element)',
  sanitize          : 'boolean',
  sanitizeFn        : '(null|function)',
  whiteList         : 'object'
Johann-S's avatar
Johann-S committed
50
51
52
53
54
55
56
57
58
59
60
}

const AttachmentMap = {
  AUTO   : 'auto',
  TOP    : 'top',
  RIGHT  : 'right',
  BOTTOM : 'bottom',
  LEFT   : 'left'
}

const Default = {
61
62
  animation         : true,
  template          : '<div class="tooltip" role="tooltip">' +
63
                    '<div class="tooltip-arrow"></div>' +
64
65
66
67
68
69
70
71
72
73
                    '<div class="tooltip-inner"></div></div>',
  trigger           : 'hover focus',
  title             : '',
  delay             : 0,
  html              : false,
  selector          : false,
  placement         : 'top',
  offset            : 0,
  container         : false,
  fallbackPlacement : 'flip',
74
75
76
77
  boundary          : 'scrollParent',
  sanitize          : true,
  sanitizeFn        : null,
  whiteList         : DefaultWhitelist
Johann-S's avatar
Johann-S committed
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
}

const HoverState = {
  SHOW : 'show',
  OUT  : 'out'
}

const Event = {
  HIDE       : `hide${EVENT_KEY}`,
  HIDDEN     : `hidden${EVENT_KEY}`,
  SHOW       : `show${EVENT_KEY}`,
  SHOWN      : `shown${EVENT_KEY}`,
  INSERTED   : `inserted${EVENT_KEY}`,
  CLICK      : `click${EVENT_KEY}`,
  FOCUSIN    : `focusin${EVENT_KEY}`,
  FOCUSOUT   : `focusout${EVENT_KEY}`,
  MOUSEENTER : `mouseenter${EVENT_KEY}`,
  MOUSELEAVE : `mouseleave${EVENT_KEY}`
}

const ClassName = {
  FADE : 'fade',
  SHOW : 'show'
}

const Selector = {
  TOOLTIP       : '.tooltip',
  TOOLTIP_INNER : '.tooltip-inner',
106
  TOOLTIP_ARROW : '.tooltip-arrow'
Johann-S's avatar
Johann-S committed
107
108
109
110
111
112
113
114
}

const Trigger = {
  HOVER  : 'hover',
  FOCUS  : 'focus',
  CLICK  : 'click',
  MANUAL : 'manual'
}
115
116


Johann-S's avatar
Johann-S committed
117
118
119
120
121
/**
 * ------------------------------------------------------------------------
 * Class Definition
 * ------------------------------------------------------------------------
 */
122

Johann-S's avatar
Johann-S committed
123
124
125
126
127
128
129
class Tooltip {
  constructor(element, config) {
    /**
     * Check for Popper dependency
     * Popper - https://popper.js.org
     */
    if (typeof Popper === 'undefined') {
130
      throw new TypeError('Bootstrap\'s tooltips require Popper.js (https://popper.js.org)')
131
132
    }

Johann-S's avatar
Johann-S committed
133
134
135
136
137
138
    // private
    this._isEnabled     = true
    this._timeout       = 0
    this._hoverState    = ''
    this._activeTrigger = {}
    this._popper        = null
139

Johann-S's avatar
Johann-S committed
140
141
142
143
    // Protected
    this.element = element
    this.config  = this._getConfig(config)
    this.tip     = null
fat's avatar
fat committed
144

Johann-S's avatar
Johann-S committed
145
    this._setListeners()
146
    Data.setData(element, this.constructor.DATA_KEY, this)
Johann-S's avatar
Johann-S committed
147
  }
fat's avatar
fat committed
148

Johann-S's avatar
Johann-S committed
149
  // Getters
fat's avatar
fat committed
150

Johann-S's avatar
Johann-S committed
151
152
153
  static get VERSION() {
    return VERSION
  }
fat's avatar
fat committed
154

Johann-S's avatar
Johann-S committed
155
156
157
  static get Default() {
    return Default
  }
fat's avatar
fat committed
158

Johann-S's avatar
Johann-S committed
159
160
161
  static get NAME() {
    return NAME
  }
162

Johann-S's avatar
Johann-S committed
163
164
165
  static get DATA_KEY() {
    return DATA_KEY
  }
166

Johann-S's avatar
Johann-S committed
167
168
169
  static get Event() {
    return Event
  }
170

Johann-S's avatar
Johann-S committed
171
172
173
  static get EVENT_KEY() {
    return EVENT_KEY
  }
174

Johann-S's avatar
Johann-S committed
175
176
177
  static get DefaultType() {
    return DefaultType
  }
178

Johann-S's avatar
Johann-S committed
179
  // Public
180

Johann-S's avatar
Johann-S committed
181
182
183
  enable() {
    this._isEnabled = true
  }
184

Johann-S's avatar
Johann-S committed
185
186
187
  disable() {
    this._isEnabled = false
  }
188

Johann-S's avatar
Johann-S committed
189
190
191
  toggleEnabled() {
    this._isEnabled = !this._isEnabled
  }
Jacob Thornton's avatar
Jacob Thornton committed
192

Johann-S's avatar
Johann-S committed
193
194
195
  toggle(event) {
    if (!this._isEnabled) {
      return
196
197
    }

Johann-S's avatar
Johann-S committed
198
199
    if (event) {
      const dataKey = this.constructor.DATA_KEY
200
      let context = Data.getData(event.delegateTarget, dataKey)
fat's avatar
fat committed
201

Johann-S's avatar
Johann-S committed
202
203
      if (!context) {
        context = new this.constructor(
204
          event.delegateTarget,
Johann-S's avatar
Johann-S committed
205
206
          this._getDelegateConfig()
        )
207
        Data.setData(event.delegateTarget, dataKey, context)
Johann-S's avatar
Johann-S committed
208
      }
fat's avatar
fat committed
209

Johann-S's avatar
Johann-S committed
210
      context._activeTrigger.click = !context._activeTrigger.click
fat's avatar
fat committed
211

Johann-S's avatar
Johann-S committed
212
213
214
215
      if (context._isWithActiveTrigger()) {
        context._enter(null, context)
      } else {
        context._leave(null, context)
fat's avatar
fat committed
216
      }
Johann-S's avatar
Johann-S committed
217
    } else {
218
      if (this.getTipElement().classList.contains(ClassName.SHOW)) {
Johann-S's avatar
Johann-S committed
219
220
        this._leave(null, this)
        return
221
      }
fat's avatar
fat committed
222

Johann-S's avatar
Johann-S committed
223
      this._enter(null, this)
224
    }
Johann-S's avatar
Johann-S committed
225
  }
226

Johann-S's avatar
Johann-S committed
227
228
  dispose() {
    clearTimeout(this._timeout)
229

230
    Data.removeData(this.element, this.constructor.DATA_KEY)
231

232
233
    EventHandler.off(this.element, this.constructor.EVENT_KEY)
    EventHandler.off(SelectorEngine.closest(this.element, '.modal'), 'hide.bs.modal')
234

Johann-S's avatar
Johann-S committed
235
    if (this.tip) {
236
      this.tip.parentNode.removeChild(this.tip)
Johann-S's avatar
Johann-S committed
237
238
239
240
241
242
243
244
245
    }

    this._isEnabled     = null
    this._timeout       = null
    this._hoverState    = null
    this._activeTrigger = null
    if (this._popper !== null) {
      this._popper.destroy()
    }
246

Johann-S's avatar
Johann-S committed
247
248
249
250
251
    this._popper = null
    this.element = null
    this.config  = null
    this.tip     = null
  }
252

Johann-S's avatar
Johann-S committed
253
  show() {
254
    if (this.element.style.display === 'none') {
Johann-S's avatar
Johann-S committed
255
256
      throw new Error('Please use show on visible elements')
    }
257

Johann-S's avatar
Johann-S committed
258
    if (this.isWithContent() && this._isEnabled) {
259
      const showEvent = EventHandler.trigger(this.element, this.constructor.Event.SHOW)
260
      const shadowRoot = Util.findShadowRoot(this.element)
261
262
263
      const isInTheDom = shadowRoot !== null
        ? shadowRoot.contains(this.element)
        : this.element.ownerDocument.documentElement.contains(this.element)
264

265
      if (showEvent.defaultPrevented || !isInTheDom) {
Johann-S's avatar
Johann-S committed
266
267
        return
      }
268

Johann-S's avatar
Johann-S committed
269
270
      const tip   = this.getTipElement()
      const tipId = Util.getUID(this.constructor.NAME)
271

Johann-S's avatar
Johann-S committed
272
273
      tip.setAttribute('id', tipId)
      this.element.setAttribute('aria-describedby', tipId)
274

Johann-S's avatar
Johann-S committed
275
      this.setContent()
276

Johann-S's avatar
Johann-S committed
277
      if (this.config.animation) {
278
        tip.classList.add(ClassName.FADE)
Johann-S's avatar
Johann-S committed
279
      }
280

Johann-S's avatar
Johann-S committed
281
282
283
      const placement  = typeof this.config.placement === 'function'
        ? this.config.placement.call(this, tip, this.element)
        : this.config.placement
284

Johann-S's avatar
Johann-S committed
285
286
      const attachment = this._getAttachment(placement)
      this.addAttachmentClass(attachment)
287

288
      const container = this._getContainer()
289
      Data.setData(tip, this.constructor.DATA_KEY, this)
Johann-S's avatar
Johann-S committed
290

291
292
      if (!this.element.ownerDocument.documentElement.contains(this.tip)) {
        container.appendChild(tip)
Johann-S's avatar
Johann-S committed
293
      }
294

295
      EventHandler.trigger(this.element, this.constructor.Event.INSERTED)
296

Johann-S's avatar
Johann-S committed
297
298
299
      this._popper = new Popper(this.element, tip, {
        placement: attachment,
        modifiers: {
300
          offset: this._getOffset(),
Johann-S's avatar
Johann-S committed
301
302
303
304
          flip: {
            behavior: this.config.fallbackPlacement
          },
          arrow: {
305
            element: Selector.TOOLTIP_ARROW
Johann-S's avatar
Johann-S committed
306
307
308
309
310
311
312
313
          },
          preventOverflow: {
            boundariesElement: this.config.boundary
          }
        },
        onCreate: (data) => {
          if (data.originalPlacement !== data.placement) {
            this._handlePopperPlacementChange(data)
314
          }
Johann-S's avatar
Johann-S committed
315
        },
316
        onUpdate: (data) => this._handlePopperPlacementChange(data)
Johann-S's avatar
Johann-S committed
317
      })
318

319
      tip.classList.add(ClassName.SHOW)
320

Johann-S's avatar
Johann-S committed
321
322
323
324
325
      // If this is a touch-enabled device we add extra
      // empty mouseover listeners to the body's immediate children;
      // only needed because of broken event delegation on iOS
      // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html
      if ('ontouchstart' in document.documentElement) {
326
        Util.makeArray(document.body.children).forEach((element) => {
327
          EventHandler.on(element, 'mouseover', Util.noop())
328
        })
329
330
      }

XhmikosR's avatar
XhmikosR committed
331
      const complete = () => {
Johann-S's avatar
Johann-S committed
332
333
        if (this.config.animation) {
          this._fixTransition()
334
        }
Johann-S's avatar
Johann-S committed
335
336
        const prevHoverState = this._hoverState
        this._hoverState     = null
337

338
        EventHandler.trigger(this.element, this.constructor.Event.SHOWN)
339

Johann-S's avatar
Johann-S committed
340
341
        if (prevHoverState === HoverState.OUT) {
          this._leave(null, this)
342
343
344
        }
      }

345
      if (this.tip.classList.contains(ClassName.FADE)) {
Johann-S's avatar
Johann-S committed
346
        const transitionDuration = Util.getTransitionDurationFromElement(this.tip)
347
348
        EventHandler.one(this.tip, Util.TRANSITION_END, complete)
        Util.emulateTransitionEnd(this.tip, transitionDuration)
349
350
351
352
      } else {
        complete()
      }
    }
Johann-S's avatar
Johann-S committed
353
  }
354

Johann-S's avatar
Johann-S committed
355
356
  hide(callback) {
    const tip       = this.getTipElement()
357
    const complete  = () => {
Johann-S's avatar
Johann-S committed
358
359
      if (this._hoverState !== HoverState.SHOW && tip.parentNode) {
        tip.parentNode.removeChild(tip)
360
      }
361

Johann-S's avatar
Johann-S committed
362
363
      this._cleanTipClass()
      this.element.removeAttribute('aria-describedby')
364
      EventHandler.trigger(this.element, this.constructor.Event.HIDDEN)
Johann-S's avatar
Johann-S committed
365
366
367
      if (this._popper !== null) {
        this._popper.destroy()
      }
368

Johann-S's avatar
Johann-S committed
369
370
371
      if (callback) {
        callback()
      }
372
373
    }

374
375
    const hideEvent = EventHandler.trigger(this.element, this.constructor.Event.HIDE)
    if (hideEvent.defaultPrevented) {
Johann-S's avatar
Johann-S committed
376
      return
377
378
    }

379
    tip.classList.remove(ClassName.SHOW)
380

Johann-S's avatar
Johann-S committed
381
382
383
    // If this is a touch-enabled device we remove the extra
    // empty mouseover listeners we added for iOS support
    if ('ontouchstart' in document.documentElement) {
384
      Util.makeArray(document.body.children)
385
        .forEach((element) => EventHandler.off(element, 'mouseover', Util.noop))
386
387
    }

Johann-S's avatar
Johann-S committed
388
389
390
    this._activeTrigger[Trigger.CLICK] = false
    this._activeTrigger[Trigger.FOCUS] = false
    this._activeTrigger[Trigger.HOVER] = false
391

392
    if (this.tip.classList.contains(ClassName.FADE)) {
Johann-S's avatar
Johann-S committed
393
      const transitionDuration = Util.getTransitionDurationFromElement(tip)
394
395
      EventHandler.one(tip, Util.TRANSITION_END, complete)
      Util.emulateTransitionEnd(tip, transitionDuration)
Johann-S's avatar
Johann-S committed
396
397
    } else {
      complete()
398
399
    }

Johann-S's avatar
Johann-S committed
400
401
    this._hoverState = ''
  }
402

Johann-S's avatar
Johann-S committed
403
404
405
  update() {
    if (this._popper !== null) {
      this._popper.scheduleUpdate()
fat's avatar
fat committed
406
    }
Johann-S's avatar
Johann-S committed
407
  }
fat's avatar
fat committed
408

Johann-S's avatar
Johann-S committed
409
  // Protected
410

Johann-S's avatar
Johann-S committed
411
412
413
  isWithContent() {
    return Boolean(this.getTitle())
  }
414

Johann-S's avatar
Johann-S committed
415
  addAttachmentClass(attachment) {
416
    this.getTipElement().classList.add(`${CLASS_PREFIX}-${attachment}`)
Johann-S's avatar
Johann-S committed
417
  }
418

Johann-S's avatar
Johann-S committed
419
  getTipElement() {
420
421
422
423
424
425
426
427
    if (this.tip) {
      return this.tip
    }

    const element = document.createElement('div')
    element.innerHTML = this.config.template

    this.tip = element.children[0]
Johann-S's avatar
Johann-S committed
428
429
430
431
432
    return this.tip
  }

  setContent() {
    const tip = this.getTipElement()
433
434
435
    this.setElementContent(SelectorEngine.findOne(Selector.TOOLTIP_INNER, tip), this.getTitle())
    tip.classList.remove(ClassName.FADE)
    tip.classList.remove(ClassName.SHOW)
Johann-S's avatar
Johann-S committed
436
437
  }

438
439
440
441
442
  setElementContent(element, content) {
    if (element === null) {
      return
    }

Johann-S's avatar
Johann-S committed
443
    if (typeof content === 'object' && (content.nodeType || content.jquery)) {
444
445
446
447
448
      if (content.jquery) {
        content = content[0]
      }

      // content is a DOM node or a jQuery
449
      if (this.config.html) {
450
451
452
        if (content.parentNode !== element) {
          element.innerHTML = ''
          element.appendChild(content)
453
        }
454
      } else {
455
        element.innerText = content.textContent
456
      }
457
458
459
460
461
462
463
464
465

      return
    }

    if (this.config.html) {
      if (this.config.sanitize) {
        content = sanitizeHtml(content, this.config.whiteList, this.config.sanitizeFn)
      }

466
      element.innerHTML = content
Johann-S's avatar
Johann-S committed
467
    } else {
468
      element.innerText = content
469
    }
Johann-S's avatar
Johann-S committed
470
  }
471

Johann-S's avatar
Johann-S committed
472
473
474
475
476
477
478
  getTitle() {
    let title = this.element.getAttribute('data-original-title')

    if (!title) {
      title = typeof this.config.title === 'function'
        ? this.config.title.call(this.element)
        : this.config.title
479
480
    }

Johann-S's avatar
Johann-S committed
481
482
    return title
  }
fat's avatar
fat committed
483

Johann-S's avatar
Johann-S committed
484
  // Private
485

486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
  _getOffset() {
    const offset = {}

    if (typeof this.config.offset === 'function') {
      offset.fn = (data) => {
        data.offsets = {
          ...data.offsets,
          ...this.config.offset(data.offsets, this.element) || {}
        }

        return data
      }
    } else {
      offset.offset = this.config.offset
    }

    return offset
  }

505
506
507
508
509
510
  _getContainer() {
    if (this.config.container === false) {
      return document.body
    }

    if (Util.isElement(this.config.container)) {
511
      return this.config.container
512
513
    }

514
    return SelectorEngine.findOne(this.config.container)
515
516
  }

Johann-S's avatar
Johann-S committed
517
518
519
520
521
522
523
524
525
  _getAttachment(placement) {
    return AttachmentMap[placement.toUpperCase()]
  }

  _setListeners() {
    const triggers = this.config.trigger.split(' ')

    triggers.forEach((trigger) => {
      if (trigger === 'click') {
526
        EventHandler.on(this.element,
Johann-S's avatar
Johann-S committed
527
528
529
          this.constructor.Event.CLICK,
          this.config.selector,
          (event) => this.toggle(event)
530
        )
Johann-S's avatar
Johann-S committed
531
532
533
534
535
536
537
538
      } else if (trigger !== Trigger.MANUAL) {
        const eventIn = trigger === Trigger.HOVER
          ? this.constructor.Event.MOUSEENTER
          : this.constructor.Event.FOCUSIN
        const eventOut = trigger === Trigger.HOVER
          ? this.constructor.Event.MOUSELEAVE
          : this.constructor.Event.FOCUSOUT

539
540
541
542
543
544
545
546
547
548
        EventHandler.on(this.element,
          eventIn,
          this.config.selector,
          (event) => this._enter(event)
        )
        EventHandler.on(this.element,
          eventOut,
          this.config.selector,
          (event) => this._leave(event)
        )
549
      }
Johann-S's avatar
Johann-S committed
550
    })
551

552
    EventHandler.on(SelectorEngine.closest(this.element, '.modal'),
553
554
555
556
557
558
559
560
      'hide.bs.modal',
      () => {
        if (this.element) {
          this.hide()
        }
      }
    )

Johann-S's avatar
Johann-S committed
561
562
563
564
565
    if (this.config.selector) {
      this.config = {
        ...this.config,
        trigger: 'manual',
        selector: ''
566
      }
Johann-S's avatar
Johann-S committed
567
568
569
570
    } else {
      this._fixTitle()
    }
  }
571

Johann-S's avatar
Johann-S committed
572
573
  _fixTitle() {
    const titleType = typeof this.element.getAttribute('data-original-title')
574
575

    if (this.element.getAttribute('title') || titleType !== 'string') {
Johann-S's avatar
Johann-S committed
576
577
578
579
      this.element.setAttribute(
        'data-original-title',
        this.element.getAttribute('title') || ''
      )
580

Johann-S's avatar
Johann-S committed
581
582
583
      this.element.setAttribute('title', '')
    }
  }
584

Johann-S's avatar
Johann-S committed
585
586
  _enter(event, context) {
    const dataKey = this.constructor.DATA_KEY
587
    context = context || Data.getData(event.delegateTarget, dataKey)
588

Johann-S's avatar
Johann-S committed
589
590
    if (!context) {
      context = new this.constructor(
591
        event.delegateTarget,
Johann-S's avatar
Johann-S committed
592
593
        this._getDelegateConfig()
      )
594
      Data.setData(event.delegateTarget, dataKey, context)
595
596
    }

Johann-S's avatar
Johann-S committed
597
598
599
600
601
    if (event) {
      context._activeTrigger[
        event.type === 'focusin' ? Trigger.FOCUS : Trigger.HOVER
      ] = true
    }
fat's avatar
fat committed
602

603
604
    if (context.getTipElement().classList.contains(ClassName.SHOW) ||
        context._hoverState === HoverState.SHOW) {
Johann-S's avatar
Johann-S committed
605
606
607
      context._hoverState = HoverState.SHOW
      return
    }
608

Johann-S's avatar
Johann-S committed
609
    clearTimeout(context._timeout)
610

Johann-S's avatar
Johann-S committed
611
    context._hoverState = HoverState.SHOW
612

Johann-S's avatar
Johann-S committed
613
614
615
616
    if (!context.config.delay || !context.config.delay.show) {
      context.show()
      return
    }
617

Johann-S's avatar
Johann-S committed
618
619
620
621
622
623
    context._timeout = setTimeout(() => {
      if (context._hoverState === HoverState.SHOW) {
        context.show()
      }
    }, context.config.delay.show)
  }
624

Johann-S's avatar
Johann-S committed
625
626
  _leave(event, context) {
    const dataKey = this.constructor.DATA_KEY
627
    context = context || Data.getData(event.delegateTarget, dataKey)
628

Johann-S's avatar
Johann-S committed
629
630
    if (!context) {
      context = new this.constructor(
631
        event.delegateTarget,
Johann-S's avatar
Johann-S committed
632
633
        this._getDelegateConfig()
      )
634
      Data.setData(event.delegateTarget, dataKey, context)
635
636
    }

Johann-S's avatar
Johann-S committed
637
638
639
640
641
    if (event) {
      context._activeTrigger[
        event.type === 'focusout' ? Trigger.FOCUS : Trigger.HOVER
      ] = false
    }
642

Johann-S's avatar
Johann-S committed
643
644
    if (context._isWithActiveTrigger()) {
      return
645
646
    }

Johann-S's avatar
Johann-S committed
647
    clearTimeout(context._timeout)
648

Johann-S's avatar
Johann-S committed
649
    context._hoverState = HoverState.OUT
650

Johann-S's avatar
Johann-S committed
651
652
653
654
    if (!context.config.delay || !context.config.delay.hide) {
      context.hide()
      return
    }
655

Johann-S's avatar
Johann-S committed
656
657
658
    context._timeout = setTimeout(() => {
      if (context._hoverState === HoverState.OUT) {
        context.hide()
659
      }
Johann-S's avatar
Johann-S committed
660
661
    }, context.config.delay.hide)
  }
662

Johann-S's avatar
Johann-S committed
663
664
665
666
667
  _isWithActiveTrigger() {
    for (const trigger in this._activeTrigger) {
      if (this._activeTrigger[trigger]) {
        return true
      }
668
669
    }

Johann-S's avatar
Johann-S committed
670
671
    return false
  }
672

Johann-S's avatar
Johann-S committed
673
  _getConfig(config) {
674
    const dataAttributes = Manipulator.getDataAttributes(this.element)
675
676
677
678
679
680
681
682

    Object.keys(dataAttributes)
      .forEach((dataAttr) => {
        if (DISALLOWED_ATTRIBUTES.indexOf(dataAttr) !== -1) {
          delete dataAttributes[dataAttr]
        }
      })

Johann-S's avatar
Johann-S committed
683
    if (config && typeof config.container === 'object' && config.container.jquery) {
684
685
686
      config.container = config.container[0]
    }

Johann-S's avatar
Johann-S committed
687
688
    config = {
      ...this.constructor.Default,
689
      ...dataAttributes,
Johann-S's avatar
Johann-S committed
690
      ...typeof config === 'object' && config ? config : {}
691
692
    }

Johann-S's avatar
Johann-S committed
693
694
695
696
    if (typeof config.delay === 'number') {
      config.delay = {
        show: config.delay,
        hide: config.delay
Johann-S's avatar
Johann-S committed
697
      }
Johann-S's avatar
Johann-S committed
698
699
    }

Johann-S's avatar
Johann-S committed
700
701
    if (typeof config.title === 'number') {
      config.title = config.title.toString()
702
    }
703

Johann-S's avatar
Johann-S committed
704
705
    if (typeof config.content === 'number') {
      config.content = config.content.toString()
706
707
    }

Johann-S's avatar
Johann-S committed
708
709
710
711
712
    Util.typeCheckConfig(
      NAME,
      config,
      this.constructor.DefaultType
    )
713

714
715
716
717
    if (config.sanitize) {
      config.template = sanitizeHtml(config.template, config.whiteList, config.sanitizeFn)
    }

Johann-S's avatar
Johann-S committed
718
719
    return config
  }
720

Johann-S's avatar
Johann-S committed
721
722
  _getDelegateConfig() {
    const config = {}
723

Johann-S's avatar
Johann-S committed
724
725
726
727
    if (this.config) {
      for (const key in this.config) {
        if (this.constructor.Default[key] !== this.config[key]) {
          config[key] = this.config[key]
728
        }
Johann-S's avatar
Johann-S committed
729
730
      }
    }
731

Johann-S's avatar
Johann-S committed
732
733
734
735
    return config
  }

  _cleanTipClass() {
736
737
    const tip = this.getTipElement()
    const tabClass = tip.getAttribute('class').match(BSCLS_PREFIX_REGEX)
Johann-S's avatar
Johann-S committed
738
    if (tabClass !== null && tabClass.length) {
739
740
741
      tabClass
        .map((token) => token.trim())
        .forEach((tClass) => tip.classList.remove(tClass))
742
743
744
    }
  }

Johann-S's avatar
Johann-S committed
745
746
747
748
749
750
751
752
753
754
755
756
757
  _handlePopperPlacementChange(popperData) {
    const popperInstance = popperData.instance
    this.tip = popperInstance.popper
    this._cleanTipClass()
    this.addAttachmentClass(this._getAttachment(popperData.placement))
  }

  _fixTransition() {
    const tip = this.getTipElement()
    const initConfigAnimation = this.config.animation
    if (tip.getAttribute('x-placement') !== null) {
      return
    }
758
    tip.classList.remove(ClassName.FADE)
Johann-S's avatar
Johann-S committed
759
760
761
762
763
764
765
766
767
768
    this.config.animation = false
    this.hide()
    this.show()
    this.config.animation = initConfigAnimation
  }

  // Static

  static _jQueryInterface(config) {
    return this.each(function () {
769
      let data      = Data.getData(this, DATA_KEY)
Johann-S's avatar
Johann-S committed
770
771
772
773
774
775
776
777
778
      const _config = typeof config === 'object' && config

      if (!data && /dispose|hide/.test(config)) {
        return
      }

      if (!data) {
        data = new Tooltip(this, _config)
      }
779

Johann-S's avatar
Johann-S committed
780
781
782
783
784
785
786
      if (typeof config === 'string') {
        if (typeof data[config] === 'undefined') {
          throw new TypeError(`No method named "${config}"`)
        }
        data[config]()
      }
    })
787
  }
788
789
790
791

  static _getInstance(element) {
    return Data.getData(element, DATA_KEY)
  }
Johann-S's avatar
Johann-S committed
792
793
794
795
796
797
798
}

/**
 * ------------------------------------------------------------------------
 * jQuery
 * ------------------------------------------------------------------------
 */
799
800
801
802
803
804
805
806
807
const $ = Util.jQuery
if (typeof $ !== 'undefined') {
  const JQUERY_NO_CONFLICT  = $.fn[NAME]
  $.fn[NAME]                = Tooltip._jQueryInterface
  $.fn[NAME].Constructor    = Tooltip
  $.fn[NAME].noConflict     = () => {
    $.fn[NAME] = JQUERY_NO_CONFLICT
    return Tooltip._jQueryInterface
  }
Johann-S's avatar
Johann-S committed
808
}
809
810

export default Tooltip