tooltip.js 18.6 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
14
15
import $ from 'jquery'
import Popper from 'popper.js'
import Util from './util'

Johann-S's avatar
Johann-S committed
16
17
18
19
20
/**
 * ------------------------------------------------------------------------
 * Constants
 * ------------------------------------------------------------------------
 */
21

22
const NAME                  = 'tooltip'
XhmikosR's avatar
XhmikosR committed
23
const VERSION               = '4.3.1'
24
25
26
27
28
29
const DATA_KEY              = 'bs.tooltip'
const EVENT_KEY             = `.${DATA_KEY}`
const JQUERY_NO_CONFLICT    = $.fn[NAME]
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
30
31

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

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

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

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'
}
112
113


Johann-S's avatar
Johann-S committed
114
115
116
117
118
/**
 * ------------------------------------------------------------------------
 * Class Definition
 * ------------------------------------------------------------------------
 */
119

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

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

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

Johann-S's avatar
Johann-S committed
142
143
    this._setListeners()
  }
fat's avatar
fat committed
144

Johann-S's avatar
Johann-S committed
145
  // Getters
fat's avatar
fat committed
146

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

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

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

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

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

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

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

Johann-S's avatar
Johann-S committed
175
  // Public
176

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

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

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

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

Johann-S's avatar
Johann-S committed
194
195
196
    if (event) {
      const dataKey = this.constructor.DATA_KEY
      let context = $(event.currentTarget).data(dataKey)
fat's avatar
fat committed
197

Johann-S's avatar
Johann-S committed
198
199
200
201
202
203
204
      if (!context) {
        context = new this.constructor(
          event.currentTarget,
          this._getDelegateConfig()
        )
        $(event.currentTarget).data(dataKey, context)
      }
fat's avatar
fat committed
205

Johann-S's avatar
Johann-S committed
206
      context._activeTrigger.click = !context._activeTrigger.click
fat's avatar
fat committed
207

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

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

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

Johann-S's avatar
Johann-S committed
226
    $.removeData(this.element, this.constructor.DATA_KEY)
227

Johann-S's avatar
Johann-S committed
228
229
    $(this.element).off(this.constructor.EVENT_KEY)
    $(this.element).closest('.modal').off('hide.bs.modal')
230

Johann-S's avatar
Johann-S committed
231
232
233
234
235
236
237
238
239
240
241
    if (this.tip) {
      $(this.tip).remove()
    }

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

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

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

Johann-S's avatar
Johann-S committed
254
255
256
    const showEvent = $.Event(this.constructor.Event.SHOW)
    if (this.isWithContent() && this._isEnabled) {
      $(this.element).trigger(showEvent)
257

258
      const shadowRoot = Util.findShadowRoot(this.element)
Johann-S's avatar
Johann-S committed
259
      const isInTheDom = $.contains(
260
        shadowRoot !== null ? shadowRoot : this.element.ownerDocument.documentElement,
Johann-S's avatar
Johann-S committed
261
262
        this.element
      )
263

Johann-S's avatar
Johann-S committed
264
265
266
      if (showEvent.isDefaultPrevented() || !isInTheDom) {
        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
277
278
      if (this.config.animation) {
        $(tip).addClass(ClassName.FADE)
      }
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()
Johann-S's avatar
Johann-S committed
288
289
290
291
292
      $(tip).data(this.constructor.DATA_KEY, this)

      if (!$.contains(this.element.ownerDocument.documentElement, this.tip)) {
        $(tip).appendTo(container)
      }
293

Johann-S's avatar
Johann-S committed
294
      $(this.element).trigger(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

Johann-S's avatar
Johann-S committed
318
      $(tip).addClass(ClassName.SHOW)
319

Johann-S's avatar
Johann-S committed
320
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) {
        $(document.body).children().on('mouseover', null, $.noop)
326
327
      }

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

Johann-S's avatar
Johann-S committed
335
        $(this.element).trigger(this.constructor.Event.SHOWN)
336

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

342
      if ($(this.tip).hasClass(ClassName.FADE)) {
Johann-S's avatar
Johann-S committed
343
        const transitionDuration = Util.getTransitionDurationFromElement(this.tip)
344

Johann-S's avatar
Johann-S committed
345
        $(this.tip)
346
          .one(Util.TRANSITION_END, complete)
Johann-S's avatar
Johann-S committed
347
348

        Util.emulateTransitionEnd(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
357
358
359
360
  hide(callback) {
    const tip       = this.getTipElement()
    const hideEvent = $.Event(this.constructor.Event.HIDE)
    const complete = () => {
      if (this._hoverState !== HoverState.SHOW && tip.parentNode) {
        tip.parentNode.removeChild(tip)
361
      }
362

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

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

Johann-S's avatar
Johann-S committed
375
    $(this.element).trigger(hideEvent)
Johann-S's avatar
Johann-S committed
376

Johann-S's avatar
Johann-S committed
377
378
    if (hideEvent.isDefaultPrevented()) {
      return
379
380
    }

Johann-S's avatar
Johann-S committed
381
    $(tip).removeClass(ClassName.SHOW)
382

Johann-S's avatar
Johann-S committed
383
384
385
386
    // If this is a touch-enabled device we remove the extra
    // empty mouseover listeners we added for iOS support
    if ('ontouchstart' in document.documentElement) {
      $(document.body).children().off('mouseover', null, $.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

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

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

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

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

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

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

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

Johann-S's avatar
Johann-S committed
422
423
424
425
426
427
428
429
430
431
432
433
434
435
  getTipElement() {
    this.tip = this.tip || $(this.config.template)[0]
    return this.tip
  }

  setContent() {
    const tip = this.getTipElement()
    this.setElementContent($(tip.querySelectorAll(Selector.TOOLTIP_INNER)), this.getTitle())
    $(tip).removeClass(`${ClassName.FADE} ${ClassName.SHOW}`)
  }

  setElementContent($element, content) {
    if (typeof content === 'object' && (content.nodeType || content.jquery)) {
      // Content is a DOM node or a jQuery
436
      if (this.config.html) {
Johann-S's avatar
Johann-S committed
437
438
        if (!$(content).parent().is($element)) {
          $element.empty().append(content)
439
        }
440
      } else {
Johann-S's avatar
Johann-S committed
441
        $element.text($(content).text())
442
      }
443
444
445
446
447
448
449
450
451
452

      return
    }

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

      $element.html(content)
Johann-S's avatar
Johann-S committed
453
    } else {
454
      $element.text(content)
455
    }
Johann-S's avatar
Johann-S committed
456
  }
457

Johann-S's avatar
Johann-S committed
458
459
460
461
462
463
464
  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
465
466
    }

Johann-S's avatar
Johann-S committed
467
468
    return title
  }
fat's avatar
fat committed
469

Johann-S's avatar
Johann-S committed
470
  // Private
471

472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
  _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
  }

491
492
493
494
495
496
497
498
499
500
501
502
  _getContainer() {
    if (this.config.container === false) {
      return document.body
    }

    if (Util.isElement(this.config.container)) {
      return $(this.config.container)
    }

    return $(document).find(this.config.container)
  }

Johann-S's avatar
Johann-S committed
503
504
505
506
507
508
509
510
511
512
513
514
515
  _getAttachment(placement) {
    return AttachmentMap[placement.toUpperCase()]
  }

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

    triggers.forEach((trigger) => {
      if (trigger === 'click') {
        $(this.element).on(
          this.constructor.Event.CLICK,
          this.config.selector,
          (event) => this.toggle(event)
516
        )
Johann-S's avatar
Johann-S committed
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
      } 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

        $(this.element)
          .on(
            eventIn,
            this.config.selector,
            (event) => this._enter(event)
          )
          .on(
            eventOut,
            this.config.selector,
            (event) => this._leave(event)
          )
536
      }
Johann-S's avatar
Johann-S committed
537
    })
538

539
540
541
542
543
544
545
546
547
    $(this.element).closest('.modal').on(
      'hide.bs.modal',
      () => {
        if (this.element) {
          this.hide()
        }
      }
    )

Johann-S's avatar
Johann-S committed
548
549
550
551
552
    if (this.config.selector) {
      this.config = {
        ...this.config,
        trigger: 'manual',
        selector: ''
553
      }
Johann-S's avatar
Johann-S committed
554
555
556
557
    } else {
      this._fixTitle()
    }
  }
558

Johann-S's avatar
Johann-S committed
559
560
  _fixTitle() {
    const titleType = typeof this.element.getAttribute('data-original-title')
561
562

    if (this.element.getAttribute('title') || titleType !== 'string') {
Johann-S's avatar
Johann-S committed
563
564
565
566
      this.element.setAttribute(
        'data-original-title',
        this.element.getAttribute('title') || ''
      )
567

Johann-S's avatar
Johann-S committed
568
569
570
      this.element.setAttribute('title', '')
    }
  }
571

Johann-S's avatar
Johann-S committed
572
573
574
  _enter(event, context) {
    const dataKey = this.constructor.DATA_KEY
    context = context || $(event.currentTarget).data(dataKey)
575

Johann-S's avatar
Johann-S committed
576
577
578
579
580
581
    if (!context) {
      context = new this.constructor(
        event.currentTarget,
        this._getDelegateConfig()
      )
      $(event.currentTarget).data(dataKey, context)
582
583
    }

Johann-S's avatar
Johann-S committed
584
585
586
587
588
    if (event) {
      context._activeTrigger[
        event.type === 'focusin' ? Trigger.FOCUS : Trigger.HOVER
      ] = true
    }
fat's avatar
fat committed
589

590
    if ($(context.getTipElement()).hasClass(ClassName.SHOW) || context._hoverState === HoverState.SHOW) {
Johann-S's avatar
Johann-S committed
591
592
593
      context._hoverState = HoverState.SHOW
      return
    }
594

Johann-S's avatar
Johann-S committed
595
    clearTimeout(context._timeout)
596

Johann-S's avatar
Johann-S committed
597
    context._hoverState = HoverState.SHOW
598

Johann-S's avatar
Johann-S committed
599
600
601
602
    if (!context.config.delay || !context.config.delay.show) {
      context.show()
      return
    }
603

Johann-S's avatar
Johann-S committed
604
605
606
607
608
609
    context._timeout = setTimeout(() => {
      if (context._hoverState === HoverState.SHOW) {
        context.show()
      }
    }, context.config.delay.show)
  }
610

Johann-S's avatar
Johann-S committed
611
612
613
  _leave(event, context) {
    const dataKey = this.constructor.DATA_KEY
    context = context || $(event.currentTarget).data(dataKey)
614

Johann-S's avatar
Johann-S committed
615
616
617
618
619
620
    if (!context) {
      context = new this.constructor(
        event.currentTarget,
        this._getDelegateConfig()
      )
      $(event.currentTarget).data(dataKey, context)
621
622
    }

Johann-S's avatar
Johann-S committed
623
624
625
626
627
    if (event) {
      context._activeTrigger[
        event.type === 'focusout' ? Trigger.FOCUS : Trigger.HOVER
      ] = false
    }
628

Johann-S's avatar
Johann-S committed
629
630
    if (context._isWithActiveTrigger()) {
      return
631
632
    }

Johann-S's avatar
Johann-S committed
633
    clearTimeout(context._timeout)
634

Johann-S's avatar
Johann-S committed
635
    context._hoverState = HoverState.OUT
636

Johann-S's avatar
Johann-S committed
637
638
639
640
    if (!context.config.delay || !context.config.delay.hide) {
      context.hide()
      return
    }
641

Johann-S's avatar
Johann-S committed
642
643
644
    context._timeout = setTimeout(() => {
      if (context._hoverState === HoverState.OUT) {
        context.hide()
645
      }
Johann-S's avatar
Johann-S committed
646
647
    }, context.config.delay.hide)
  }
648

Johann-S's avatar
Johann-S committed
649
650
651
652
653
  _isWithActiveTrigger() {
    for (const trigger in this._activeTrigger) {
      if (this._activeTrigger[trigger]) {
        return true
      }
654
655
    }

Johann-S's avatar
Johann-S committed
656
657
    return false
  }
658

Johann-S's avatar
Johann-S committed
659
  _getConfig(config) {
660
661
662
663
664
665
666
667
668
    const dataAttributes = $(this.element).data()

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

Johann-S's avatar
Johann-S committed
669
670
    config = {
      ...this.constructor.Default,
671
      ...dataAttributes,
Johann-S's avatar
Johann-S committed
672
      ...typeof config === 'object' && config ? config : {}
673
674
    }

Johann-S's avatar
Johann-S committed
675
676
677
678
    if (typeof config.delay === 'number') {
      config.delay = {
        show: config.delay,
        hide: config.delay
Johann-S's avatar
Johann-S committed
679
      }
Johann-S's avatar
Johann-S committed
680
681
    }

Johann-S's avatar
Johann-S committed
682
683
    if (typeof config.title === 'number') {
      config.title = config.title.toString()
684
    }
685

Johann-S's avatar
Johann-S committed
686
687
    if (typeof config.content === 'number') {
      config.content = config.content.toString()
688
689
    }

Johann-S's avatar
Johann-S committed
690
691
692
693
694
    Util.typeCheckConfig(
      NAME,
      config,
      this.constructor.DefaultType
    )
695

696
697
698
699
    if (config.sanitize) {
      config.template = sanitizeHtml(config.template, config.whiteList, config.sanitizeFn)
    }

Johann-S's avatar
Johann-S committed
700
701
    return config
  }
702

Johann-S's avatar
Johann-S committed
703
704
  _getDelegateConfig() {
    const config = {}
705

Johann-S's avatar
Johann-S committed
706
707
708
709
    if (this.config) {
      for (const key in this.config) {
        if (this.constructor.Default[key] !== this.config[key]) {
          config[key] = this.config[key]
710
        }
Johann-S's avatar
Johann-S committed
711
712
      }
    }
713

Johann-S's avatar
Johann-S committed
714
715
716
717
718
719
720
721
    return config
  }

  _cleanTipClass() {
    const $tip = $(this.getTipElement())
    const tabClass = $tip.attr('class').match(BSCLS_PREFIX_REGEX)
    if (tabClass !== null && tabClass.length) {
      $tip.removeClass(tabClass.join(''))
722
723
724
    }
  }

Johann-S's avatar
Johann-S committed
725
726
727
728
729
730
731
732
733
734
  _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
735

Johann-S's avatar
Johann-S committed
736
737
738
    if (tip.getAttribute('x-placement') !== null) {
      return
    }
739

Johann-S's avatar
Johann-S committed
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
    $(tip).removeClass(ClassName.FADE)
    this.config.animation = false
    this.hide()
    this.show()
    this.config.animation = initConfigAnimation
  }

  // Static

  static _jQueryInterface(config) {
    return this.each(function () {
      let data = $(this).data(DATA_KEY)
      const _config = typeof config === 'object' && config

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

      if (!data) {
        data = new Tooltip(this, _config)
        $(this).data(DATA_KEY, data)
      }
762

Johann-S's avatar
Johann-S committed
763
764
765
766
767
768
769
      if (typeof config === 'string') {
        if (typeof data[config] === 'undefined') {
          throw new TypeError(`No method named "${config}"`)
        }
        data[config]()
      }
    })
770
  }
Johann-S's avatar
Johann-S committed
771
772
773
774
775
776
777
}

/**
 * ------------------------------------------------------------------------
 * jQuery
 * ------------------------------------------------------------------------
 */
778

Johann-S's avatar
Johann-S committed
779
780
781
782
783
784
$.fn[NAME] = Tooltip._jQueryInterface
$.fn[NAME].Constructor = Tooltip
$.fn[NAME].noConflict = () => {
  $.fn[NAME] = JQUERY_NO_CONFLICT
  return Tooltip._jQueryInterface
}
785
786

export default Tooltip