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
63
64
65
66
67
68
69
70
71
72
73
  animation         : true,
  template          : '<div class="tooltip" role="tooltip">' +
                    '<div class="arrow"></div>' +
                    '<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
106
107
108
109
110
111
112
113
114
}

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',
  ARROW         : '.arrow'
}

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
204
205
206
      if (!context) {
        context = new this.constructor(
          event.currentTarget,
          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
305
306
307
308
309
310
311
312
313
          flip: {
            behavior: this.config.fallbackPlacement
          },
          arrow: {
            element: Selector.ARROW
          },
          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
349
        EventHandler.one(this.tip, Util.TRANSITION_END, complete)
        Util.emulateTransitionEnd(this.tip, transitionDuration)
350
351
352
353
      } else {
        complete()
      }
    }
Johann-S's avatar
Johann-S committed
354
  }
355

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

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

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

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

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

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

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

393
    if (this.tip.classList.contains(ClassName.FADE)) {
Johann-S's avatar
Johann-S committed
394
      const transitionDuration = Util.getTransitionDurationFromElement(tip)
395

396
397
      EventHandler.one(tip, Util.TRANSITION_END, complete)
      Util.emulateTransitionEnd(tip, transitionDuration)
Johann-S's avatar
Johann-S committed
398
399
    } else {
      complete()
400
401
    }

Johann-S's avatar
Johann-S committed
402
403
    this._hoverState = ''
  }
404

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

Johann-S's avatar
Johann-S committed
411
  // Protected
412

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

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

Johann-S's avatar
Johann-S committed
421
  getTipElement() {
422
423
424
425
426
427
428
429
    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
430
431
432
433
434
    return this.tip
  }

  setContent() {
    const tip = this.getTipElement()
435
436
437
    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
438
439
  }

440
441
442
443
444
  setElementContent(element, content) {
    if (element === null) {
      return
    }

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

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

      return
    }

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

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

Johann-S's avatar
Johann-S committed
474
475
476
477
478
479
480
  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
481
482
    }

Johann-S's avatar
Johann-S committed
483
484
    return title
  }
fat's avatar
fat committed
485

Johann-S's avatar
Johann-S committed
486
  // Private
487

488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
  _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
  }

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

    if (Util.isElement(this.config.container)) {
513
      return this.config.container
514
515
    }

516
    return SelectorEngine.findOne(this.config.container)
517
518
  }

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

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

    triggers.forEach((trigger) => {
      if (trigger === 'click') {
528
        EventHandler.on(this.element,
Johann-S's avatar
Johann-S committed
529
530
531
          this.constructor.Event.CLICK,
          this.config.selector,
          (event) => this.toggle(event)
532
        )
Johann-S's avatar
Johann-S committed
533
534
535
536
537
538
539
540
      } 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

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

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

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

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

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

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

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

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

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

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

Johann-S's avatar
Johann-S committed
611
    clearTimeout(context._timeout)
612

Johann-S's avatar
Johann-S committed
613
    context._hoverState = HoverState.SHOW
614

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

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

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

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

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

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

Johann-S's avatar
Johann-S committed
649
    clearTimeout(context._timeout)
650

Johann-S's avatar
Johann-S committed
651
    context._hoverState = HoverState.OUT
652

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

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

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

Johann-S's avatar
Johann-S committed
672
673
    return false
  }
674

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

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

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

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

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

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

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

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

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

Johann-S's avatar
Johann-S committed
720
721
    return config
  }
722

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

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

Johann-S's avatar
Johann-S committed
734
735
736
737
    return config
  }

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

Johann-S's avatar
Johann-S committed
747
748
749
750
751
752
753
754
755
756
  _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
757

Johann-S's avatar
Johann-S committed
758
759
760
    if (tip.getAttribute('x-placement') !== null) {
      return
    }
761

762
    tip.classList.remove(ClassName.FADE)
Johann-S's avatar
Johann-S committed
763
764
765
766
767
768
769
770
771
772
    this.config.animation = false
    this.hide()
    this.show()
    this.config.animation = initConfigAnimation
  }

  // Static

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

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

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

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

  static _getInstance(element) {
    return Data.getData(element, DATA_KEY)
  }
Johann-S's avatar
Johann-S committed
796
797
798
799
800
801
802
}

/**
 * ------------------------------------------------------------------------
 * jQuery
 * ------------------------------------------------------------------------
 */
803
804
805
806
807
808
809
810
811
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
812
}
813
814

export default Tooltip