tooltip.js 19.7 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 Popper from 'popper.js'
15
import SelectorEngine from './dom/selectorEngine'
16
17
import Util from './util'

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

24
const NAME                  = 'tooltip'
XhmikosR's avatar
XhmikosR committed
25
const VERSION               = '4.3.1'
26
27
28
29
30
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
31

32

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

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

const Default = {
60
61
62
63
64
65
66
67
68
69
70
71
72
  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',
73
74
75
76
  boundary          : 'scrollParent',
  sanitize          : true,
  sanitizeFn        : null,
  whiteList         : DefaultWhitelist
Johann-S's avatar
Johann-S committed
77
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
}

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'
}
114
115


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

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

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

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

Johann-S's avatar
Johann-S committed
144
145
    this._setListeners()
  }
fat's avatar
fat committed
146

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

346
347
        EventHandler.one(this.tip, Util.TRANSITION_END, complete)
        Util.emulateTransitionEnd(this.tip, transitionDuration)
348
349
350
351
      } else {
        complete()
      }
    }
Johann-S's avatar
Johann-S committed
352
  }
353

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

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

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

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

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

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

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

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

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 = Util.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]
        }
      })

683
684
685
686
687
    if (typeof config !== 'undefined' &&
      typeof config.container === 'object' && config.container.jquery) {
      config.container = config.container[0]
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  // Static

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

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

      if (!data) {
        data = new Tooltip(this, _config)
781
        Data.setData(this, DATA_KEY, data)
Johann-S's avatar
Johann-S committed
782
      }
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
  }
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