tooltip.js 16.69 KiB
/* global Tether */
import Util from './util'
/**
 * --------------------------------------------------------------------------
 * Bootstrap (v4.0.0-alpha.6): tooltip.js
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * --------------------------------------------------------------------------
const Tooltip = (($) => {
  /**
   * Check for Tether dependency
   * Tether - http://tether.io/
  if (typeof Tether === 'undefined') {
    throw new Error('Bootstrap tooltips require Tether (http://tether.io/)')
  /**
   * ------------------------------------------------------------------------
   * Constants
   * ------------------------------------------------------------------------
  const NAME                = 'tooltip'
  const VERSION             = '4.0.0-alpha.6'
  const DATA_KEY            = 'bs.tooltip'
  const EVENT_KEY           = `.${DATA_KEY}`
  const JQUERY_NO_CONFLICT  = $.fn[NAME]
  const TRANSITION_DURATION = 150
  const CLASS_PREFIX        = 'bs-tether'
  const TETHER_PREFIX_REGEX = new RegExp(`(^|\\s)${CLASS_PREFIX}\\S+`, 'g')
  const Default = {
    animation   : true,
    template    : '<div class="tooltip" role="tooltip">'
                + '<div class="tooltip-inner"></div></div>',
    trigger     : 'hover focus',
    title       : '',
    delay       : 0,
    html        : false,
    selector    : false,
    placement   : 'top',
    offset      : '0 0',
    constraints : [],
    container   : false
  const DefaultType = {
    animation   : 'boolean',
    template    : 'string',
    title       : '(string|element|function)',
    trigger     : 'string',
    delay       : '(number|object)',
    html        : 'boolean',
    selector    : '(string|boolean)',
    placement   : '(string|function)',
    offset      : 'string',
    constraints : 'array',
    container   : '(string|element|boolean)'
  const AttachmentMap = {
    TOP    : 'bottom center',
    RIGHT  : 'middle left',
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
BOTTOM : 'top center', LEFT : 'middle right' } 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' } const TetherClass = { element : false, enabled : false } const Trigger = { HOVER : 'hover', FOCUS : 'focus', CLICK : 'click', MANUAL : 'manual' } /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ class Tooltip { constructor(element, config) { // private this._isEnabled = true this._timeout = 0 this._hoverState = '' this._activeTrigger = {} this._tether = null // protected this.element = element this.config = this._getConfig(config) this.tip = null this._setListeners() }
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
// getters static get VERSION() { return VERSION } static get Default() { return Default } static get NAME() { return NAME } static get DATA_KEY() { return DATA_KEY } static get Event() { return Event } static get EVENT_KEY() { return EVENT_KEY } static get DefaultType() { return DefaultType } // public enable() { this._isEnabled = true } disable() { this._isEnabled = false } toggleEnabled() { this._isEnabled = !this._isEnabled } toggle(event) { if (event) { const dataKey = this.constructor.DATA_KEY let context = $(event.currentTarget).data(dataKey) if (!context) { context = new this.constructor( event.currentTarget, this._getDelegateConfig() ) $(event.currentTarget).data(dataKey, context) } context._activeTrigger.click = !context._activeTrigger.click if (context._isWithActiveTrigger()) { context._enter(null, context) } else { context._leave(null, context) } } else {
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
if ($(this.getTipElement()).hasClass(ClassName.SHOW)) { this._leave(null, this) return } this._enter(null, this) } } dispose() { clearTimeout(this._timeout) this.cleanupTether() $.removeData(this.element, this.constructor.DATA_KEY) $(this.element).off(this.constructor.EVENT_KEY) $(this.element).closest('.modal').off('hide.bs.modal') if (this.tip) { $(this.tip).remove() } this._isEnabled = null this._timeout = null this._hoverState = null this._activeTrigger = null this._tether = null this.element = null this.config = null this.tip = null } show() { if ($(this.element).css('display') === 'none') { throw new Error('Please use show on visible elements') } const showEvent = $.Event(this.constructor.Event.SHOW) if (this.isWithContent() && this._isEnabled) { $(this.element).trigger(showEvent) const isInTheDom = $.contains( this.element.ownerDocument.documentElement, this.element ) if (showEvent.isDefaultPrevented() || !isInTheDom) { return } const tip = this.getTipElement() const tipId = Util.getUID(this.constructor.NAME) tip.setAttribute('id', tipId) this.element.setAttribute('aria-describedby', tipId) this.setContent() if (this.config.animation) { $(tip).addClass(ClassName.FADE) } const placement = typeof this.config.placement === 'function' ? this.config.placement.call(this, tip, this.element) : this.config.placement const attachment = this._getAttachment(placement)
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
const container = this.config.container === false ? document.body : $(this.config.container) $(tip).data(this.constructor.DATA_KEY, this) if (!$.contains(this.element.ownerDocument.documentElement, this.tip)) { $(tip).appendTo(container) } $(this.element).trigger(this.constructor.Event.INSERTED) this._tether = new Tether({ attachment, element : tip, target : this.element, classes : TetherClass, classPrefix : CLASS_PREFIX, offset : this.config.offset, constraints : this.config.constraints, addTargetClasses: false }) Util.reflow(tip) this._tether.position() $(tip).addClass(ClassName.SHOW) // 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) { $('body').children().on('mouseover', null, $.noop) } const complete = () => { const prevHoverState = this._hoverState this._hoverState = null $(this.element).trigger(this.constructor.Event.SHOWN) if (prevHoverState === HoverState.OUT) { this._leave(null, this) } } if (Util.supportsTransitionEnd() && $(this.tip).hasClass(ClassName.FADE)) { $(this.tip) .one(Util.TRANSITION_END, complete) .emulateTransitionEnd(Tooltip._TRANSITION_DURATION) return } complete() } } 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) } this._cleanTipClass() this.element.removeAttribute('aria-describedby') $(this.element).trigger(this.constructor.Event.HIDDEN) this.cleanupTether() if (callback) {