tooltip.js 18.6 KB
Newer Older
1
2
/**
 * --------------------------------------------------------------------------
Mark Otto's avatar
Mark Otto committed
3
 * Bootstrap (v4.3.0): 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
23
24
25
26
27
28
29
const NAME                  = 'tooltip'
const VERSION               = '4.3.0'
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)
347
          .emulateTransitionEnd(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
356
357
358
359
  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)
360
      }
361

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

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

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

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

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

Johann-S's avatar
Johann-S committed
382
383
384
385
    // 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)
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

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

Johann-S's avatar
Johann-S committed
395
396
397
398
399
      $(tip)
        .one(Util.TRANSITION_END, complete)
        .emulateTransitionEnd(transitionDuration)
    } 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
418
419
  addAttachmentClass(attachment) {
    $(this.getTipElement()).addClass(`${CLASS_PREFIX}-${attachment}`)
  }
420

Johann-S's avatar
Johann-S committed
421
422
423
424
425
426
427
428
429
430
431
432
433
434
  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
435
      if (this.config.html) {
Johann-S's avatar
Johann-S committed
436
437
        if (!$(content).parent().is($element)) {
          $element.empty().append(content)
438
        }
439
      } else {
Johann-S's avatar
Johann-S committed
440
        $element.text($(content).text())
441
      }
442
443
444
445
446
447
448
449
450
451

      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
452
    } else {
453
      $element.text(content)
454
    }
Johann-S's avatar
Johann-S committed
455
  }
456

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

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

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

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

490
491
492
493
494
495
496
497
498
499
500
501
  _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
502
503
504
505
506
507
508
509
510
511
512
513
514
  _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)
515
        )
Johann-S's avatar
Johann-S committed
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
      } 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)
          )
535
      }
Johann-S's avatar
Johann-S committed
536
    })
537

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Johann-S's avatar
Johann-S committed
658
  _getConfig(config) {
659
660
661
662
663
664
665
666
667
    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
668
669
    config = {
      ...this.constructor.Default,
670
      ...dataAttributes,
Johann-S's avatar
Johann-S committed
671
      ...typeof config === 'object' && config ? config : {}
672
673
    }

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

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

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

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

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

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

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

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

Johann-S's avatar
Johann-S committed
713
714
715
716
717
718
719
720
    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(''))
721
722
723
    }
  }

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

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

Johann-S's avatar
Johann-S committed
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
    $(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)
      }
761

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

/**
 * ------------------------------------------------------------------------
 * jQuery
 * ------------------------------------------------------------------------
 */
777

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

export default Tooltip