tooltip.js 19.44 KiB
/**
 * --------------------------------------------------------------------------
 * Bootstrap (v5.0.0-alpha1): tooltip.js
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
 * --------------------------------------------------------------------------
 */
import {
  getjQuery,
  TRANSITION_END,
  emulateTransitionEnd,
  findShadowRoot,
  getTransitionDurationFromElement,
  getUID,
  isElement,
  noop,
  typeCheckConfig
} from './util/index'
import {
  DefaultAllowlist,
  sanitizeHtml
} from './util/sanitizer'
import Data from './dom/data'
import EventHandler from './dom/event-handler'
import Manipulator from './dom/manipulator'
import Popper from 'popper.js'
import SelectorEngine from './dom/selector-engine'
/**
 * ------------------------------------------------------------------------
 * Constants
 * ------------------------------------------------------------------------
const NAME = 'tooltip'
const VERSION = '5.0.0-alpha1'
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', 'allowList', 'sanitizeFn']
const DefaultType = {
  animation: 'boolean',
  template: 'string',
  title: '(string|element|function)',
  trigger: 'string',
  delay: '(number|object)',
  html: 'boolean',
  selector: '(string|boolean)',
  placement: '(string|function)',
  offset: '(number|string|function)',
  container: '(string|element|boolean)',
  fallbackPlacement: '(string|array)',
  boundary: '(string|element)',
  sanitize: 'boolean',
  sanitizeFn: '(null|function)',
  allowList: 'object',
  popperConfig: '(null|object)'
const AttachmentMap = {
  AUTO: 'auto',
  TOP: 'top',
  RIGHT: 'right',
  BOTTOM: 'bottom',
  LEFT: 'left'
const Default = {
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
animation: true, template: '<div class="tooltip" role="tooltip">' + '<div class="tooltip-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', boundary: 'scrollParent', sanitize: true, sanitizeFn: null, allowList: DefaultAllowlist, popperConfig: null } 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 CLASS_NAME_FADE = 'fade' const CLASS_NAME_MODAL = 'modal' const CLASS_NAME_SHOW = 'show' const HOVER_STATE_SHOW = 'show' const HOVER_STATE_OUT = 'out' const SELECTOR_TOOLTIP_INNER = '.tooltip-inner' const TRIGGER_HOVER = 'hover' const TRIGGER_FOCUS = 'focus' const TRIGGER_CLICK = 'click' const TRIGGER_MANUAL = 'manual' /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ class Tooltip { constructor(element, config) { if (typeof Popper === 'undefined') { throw new TypeError('Bootstrap\'s tooltips require Popper.js (https://popper.js.org)') } // private this._isEnabled = true this._timeout = 0 this._hoverState = '' this._activeTrigger = {} this._popper = null // Protected this.element = element this.config = this._getConfig(config) this.tip = null
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
this._setListeners() Data.setData(element, this.constructor.DATA_KEY, this) } // 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 (!this._isEnabled) { return } if (event) { const dataKey = this.constructor.DATA_KEY let context = Data.getData(event.delegateTarget, dataKey) if (!context) { context = new this.constructor( event.delegateTarget, this._getDelegateConfig() ) Data.setData(event.delegateTarget, dataKey, context) } context._activeTrigger.click = !context._activeTrigger.click if (context._isWithActiveTrigger()) { context._enter(null, context)
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
} else { context._leave(null, context) } } else { if (this.getTipElement().classList.contains(CLASS_NAME_SHOW)) { this._leave(null, this) return } this._enter(null, this) } } dispose() { clearTimeout(this._timeout) Data.removeData(this.element, this.constructor.DATA_KEY) EventHandler.off(this.element, this.constructor.EVENT_KEY) EventHandler.off(this.element.closest(`.${CLASS_NAME_MODAL}`), 'hide.bs.modal', this._hideModalHandler) if (this.tip) { this.tip.parentNode.removeChild(this.tip) } this._isEnabled = null this._timeout = null this._hoverState = null this._activeTrigger = null if (this._popper) { this._popper.destroy() } this._popper = null this.element = null this.config = null this.tip = null } show() { if (this.element.style.display === 'none') { throw new Error('Please use show on visible elements') } if (this.isWithContent() && this._isEnabled) { const showEvent = EventHandler.trigger(this.element, this.constructor.Event.SHOW) const shadowRoot = findShadowRoot(this.element) const isInTheDom = shadowRoot === null ? this.element.ownerDocument.documentElement.contains(this.element) : shadowRoot.contains(this.element) if (showEvent.defaultPrevented || !isInTheDom) { return } const tip = this.getTipElement() const tipId = getUID(this.constructor.NAME) tip.setAttribute('id', tipId) this.element.setAttribute('aria-describedby', tipId) this.setContent() if (this.config.animation) { tip.classList.add(CLASS_NAME_FADE) } const placement = typeof this.config.placement === 'function' ? this.config.placement.call(this, tip, this.element) : this.config.placement