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
146
    this._setListeners()
  }
fat's avatar
fat committed
147

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Johann-S's avatar
Johann-S committed
320
321
322
323
324
      // 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) {
325
        Util.makeArray(document.body.children).forEach((element) => {
326
          EventHandler.on(element, 'mouseover', Util.noop())
327
        })
328
329
      }

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

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

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

344
      if (this.tip.classList.contains(ClassName.FADE)) {
Johann-S's avatar
Johann-S committed
345
        const transitionDuration = Util.getTransitionDurationFromElement(this.tip)
346

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
396
      EventHandler.one(tip, Util.TRANSITION_END, complete)
      Util.emulateTransitionEnd(tip, transitionDuration)
Johann-S's avatar
Johann-S committed
397
398
    } else {
      complete()
399
400
    }

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

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

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

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

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

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

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

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

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

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

      return
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

684
685
686
687
688
    if (typeof config !== 'undefined' &&
      typeof config.container === 'object' && config.container.jquery) {
      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
      const _config = typeof config === 'object' && config

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

      if (!data) {
        data = new Tooltip(this, _config)
782
        Data.setData(this, DATA_KEY, data)
Johann-S's avatar
Johann-S committed
783
      }
784

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

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

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

export default Tooltip