Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/twbs/bootstrap.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/tooltip.js')
-rw-r--r--js/src/tooltip.js632
1 files changed, 632 insertions, 0 deletions
diff --git a/js/src/tooltip.js b/js/src/tooltip.js
new file mode 100644
index 0000000000..5d62e154ad
--- /dev/null
+++ b/js/src/tooltip.js
@@ -0,0 +1,632 @@
+import Util from './util'
+
+
+/**
+ * --------------------------------------------------------------------------
+ * Bootstrap (v4.0.0): tooltip.js
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
+ * --------------------------------------------------------------------------
+ */
+
+const Tooltip = (($) => {
+
+
+ /**
+ * ------------------------------------------------------------------------
+ * Constants
+ * ------------------------------------------------------------------------
+ */
+
+ const NAME = 'tooltip'
+ const VERSION = '4.0.0'
+ 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 Default = {
+ 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 0',
+ constraints : []
+ }
+
+ const DefaultType = {
+ animation : 'boolean',
+ template : 'string',
+ title : '(string|function)',
+ trigger : 'string',
+ delay : '(number|object)',
+ html : 'boolean',
+ selector : '(string|boolean)',
+ placement : '(string|function)',
+ offset : 'string',
+ constraints : 'array'
+ }
+
+ const AttachmentMap = {
+ TOP : 'bottom center',
+ RIGHT : 'middle left',
+ BOTTOM : 'top center',
+ LEFT : 'middle right'
+ }
+
+ const HoverState = {
+ IN : 'in',
+ 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',
+ IN : 'in'
+ }
+
+ 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()
+
+ }
+
+
+ // 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) {
+ let context = this
+ let dataKey = this.constructor.DATA_KEY
+
+ if (event) {
+ 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 {
+ $(context.getTipElement()).hasClass(ClassName.IN) ?
+ context._leave(null, context) :
+ context._enter(null, context)
+ }
+ }
+
+ dispose() {
+ clearTimeout(this._timeout)
+
+ this.cleanupTether()
+
+ $.removeData(this.element, this.constructor.DATA_KEY)
+
+ $(this.element).off(this.constructor.EVENT_KEY)
+
+ 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() {
+ let showEvent = $.Event(this.constructor.Event.SHOW)
+
+ if (this.isWithContent() && this._isEnabled) {
+ $(this.element).trigger(showEvent)
+
+ let isInTheDom = $.contains(
+ this.element.ownerDocument.documentElement,
+ this.element
+ )
+
+ if (showEvent.isDefaultPrevented() || !isInTheDom) {
+ return
+ }
+
+ let tip = this.getTipElement()
+ let 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)
+ }
+
+ let placement = typeof this.config.placement === 'function' ?
+ this.config.placement.call(this, tip, this.element) :
+ this.config.placement
+
+ let attachment = this._getAttachment(placement)
+
+ $(tip)
+ .data(this.constructor.DATA_KEY, this)
+ .appendTo(document.body)
+
+ $(this.element).trigger(this.constructor.Event.INSERTED)
+
+ this._tether = new Tether({
+ element : tip,
+ target : this.element,
+ attachment : attachment,
+ classes : TetherClass,
+ classPrefix : CLASS_PREFIX,
+ offset : this.config.offset,
+ constraints : this.config.constraints
+ })
+
+ Util.reflow(tip)
+ this._tether.position()
+
+ $(tip).addClass(ClassName.IN)
+
+ let complete = () => {
+ let prevHoverState = this._hoverState
+ this._hoverState = null
+
+ $(this.element).trigger(this.constructor.Event.SHOWN)
+
+ if (prevHoverState === HoverState.OUT) {
+ this._leave(null, this)
+ }
+ }
+
+ Util.supportsTransitionEnd() && $(this.tip).hasClass(ClassName.FADE) ?
+ $(this.tip)
+ .one(Util.TRANSITION_END, complete)
+ .emulateTransitionEnd(Tooltip._TRANSITION_DURATION) :
+ complete()
+ }
+ }
+
+ hide(callback) {
+ let tip = this.getTipElement()
+ let hideEvent = $.Event(this.constructor.Event.HIDE)
+ let complete = () => {
+ if (this._hoverState !== HoverState.IN && tip.parentNode) {
+ tip.parentNode.removeChild(tip)
+ }
+
+ this.element.removeAttribute('aria-describedby')
+ $(this.element).trigger(this.constructor.Event.HIDDEN)
+ this.cleanupTether()
+
+ if (callback) {
+ callback()
+ }
+ }
+
+ $(this.element).trigger(hideEvent)
+
+ if (hideEvent.isDefaultPrevented()) {
+ return
+ }
+
+ $(tip).removeClass(ClassName.IN)
+
+ if (Util.supportsTransitionEnd() &&
+ ($(this.tip).hasClass(ClassName.FADE))) {
+
+ $(tip)
+ .one(Util.TRANSITION_END, complete)
+ .emulateTransitionEnd(TRANSITION_DURATION)
+
+ } else {
+ complete()
+ }
+
+ this._hoverState = ''
+ }
+
+
+ // protected
+
+ isWithContent() {
+ return !!this.getTitle()
+ }
+
+ getTipElement() {
+ return (this.tip = this.tip || $(this.config.template)[0])
+ }
+
+ setContent() {
+ let tip = this.getTipElement()
+ let title = this.getTitle()
+ let method = this.config.html ? 'innerHTML' : 'innerText'
+
+ $(tip).find(Selector.TOOLTIP_INNER)[0][method] = title
+
+ $(tip)
+ .removeClass(ClassName.FADE)
+ .removeClass(ClassName.IN)
+
+ this.cleanupTether()
+ }
+
+ 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
+ }
+
+ return title
+ }
+
+ cleanupTether() {
+ if (this._tether) {
+ this._tether.destroy()
+
+ // clean up after tether's junk classes
+ // remove after they fix issue
+ // (https://github.com/HubSpot/tether/issues/36)
+ $(this.element).removeClass(this._removeTetherClasses)
+ $(this.tip).removeClass(this._removeTetherClasses)
+ }
+ }
+
+
+ // private
+
+ _getAttachment(placement) {
+ return AttachmentMap[placement.toUpperCase()]
+ }
+
+ _setListeners() {
+ let triggers = this.config.trigger.split(' ')
+
+ triggers.forEach((trigger) => {
+ if (trigger === 'click') {
+ $(this.element).on(
+ this.constructor.Event.CLICK,
+ this.config.selector,
+ $.proxy(this.toggle, this)
+ )
+
+ } else if (trigger !== Trigger.MANUAL) {
+ let eventIn = trigger == Trigger.HOVER ?
+ this.constructor.Event.MOUSEENTER :
+ this.constructor.Event.FOCUSIN
+ let eventOut = trigger == Trigger.HOVER ?
+ this.constructor.Event.MOUSELEAVE :
+ this.constructor.Event.FOCUSOUT
+
+ $(this.element)
+ .on(
+ eventIn,
+ this.config.selector,
+ $.proxy(this._enter, this)
+ )
+ .on(
+ eventOut,
+ this.config.selector,
+ $.proxy(this._leave, this)
+ )
+ }
+ })
+
+ if (this.config.selector) {
+ this.config = $.extend({}, this.config, {
+ trigger : 'manual',
+ selector : ''
+ })
+ } else {
+ this._fixTitle()
+ }
+ }
+
+ _removeTetherClasses(i, css) {
+ return ((css.baseVal || css).match(
+ new RegExp(`(^|\\s)${CLASS_PREFIX}-\\S+`, 'g')) || []
+ ).join(' ')
+ }
+
+ _fixTitle() {
+ let titleType = typeof this.element.getAttribute('data-original-title')
+ if (this.element.getAttribute('title') ||
+ (titleType !== 'string')) {
+ this.element.setAttribute(
+ 'data-original-title',
+ this.element.getAttribute('title') || ''
+ )
+ this.element.setAttribute('title', '')
+ }
+ }
+
+ _enter(event, context) {
+ let dataKey = this.constructor.DATA_KEY
+
+ context = context || $(event.currentTarget).data(dataKey)
+
+ if (!context) {
+ context = new this.constructor(
+ event.currentTarget,
+ this._getDelegateConfig()
+ )
+ $(event.currentTarget).data(dataKey, context)
+ }
+
+ if (event) {
+ context._activeTrigger[
+ event.type == 'focusin' ? Trigger.FOCUS : Trigger.HOVER
+ ] = true
+ }
+
+ if ($(context.getTipElement()).hasClass(ClassName.IN) ||
+ (context._hoverState === HoverState.IN)) {
+ context._hoverState = HoverState.IN
+ return
+ }
+
+ clearTimeout(context._timeout)
+
+ context._hoverState = HoverState.IN
+
+ if (!context.config.delay || !context.config.delay.show) {
+ context.show()
+ return
+ }
+
+ context._timeout = setTimeout(() => {
+ if (context._hoverState === HoverState.IN) {
+ context.show()
+ }
+ }, context.config.delay.show)
+ }
+
+ _leave(event, context) {
+ let dataKey = this.constructor.DATA_KEY
+
+ context = context || $(event.currentTarget).data(dataKey)
+
+ if (!context) {
+ context = new this.constructor(
+ event.currentTarget,
+ this._getDelegateConfig()
+ )
+ $(event.currentTarget).data(dataKey, context)
+ }
+
+ if (event) {
+ context._activeTrigger[
+ event.type == 'focusout' ? Trigger.FOCUS : Trigger.HOVER
+ ] = false
+ }
+
+ if (context._isWithActiveTrigger()) {
+ return
+ }
+
+ clearTimeout(context._timeout)
+
+ context._hoverState = HoverState.OUT
+
+ if (!context.config.delay || !context.config.delay.hide) {
+ context.hide()
+ return
+ }
+
+ context._timeout = setTimeout(() => {
+ if (context._hoverState === HoverState.OUT) {
+ context.hide()
+ }
+ }, context.config.delay.hide)
+ }
+
+ _isWithActiveTrigger() {
+ for (let trigger in this._activeTrigger) {
+ if (this._activeTrigger[trigger]) {
+ return true
+ }
+ }
+
+ return false
+ }
+
+ _getConfig(config) {
+ config = $.extend(
+ {},
+ this.constructor.Default,
+ $(this.element).data(),
+ config
+ )
+
+ if (config.delay && typeof config.delay === 'number') {
+ config.delay = {
+ show : config.delay,
+ hide : config.delay
+ }
+ }
+
+ Util.typeCheckConfig(
+ NAME,
+ config,
+ this.constructor.DefaultType
+ )
+
+ return config
+ }
+
+ _getDelegateConfig() {
+ let config = {}
+
+ if (this.config) {
+ for (let key in this.config) {
+ let value = this.config[key]
+ if (this.constructor.Default[key] !== value) {
+ config[key] = value
+ }
+ }
+ }
+
+ return config
+ }
+
+
+ // static
+
+ static _jQueryInterface(config) {
+ return this.each(function () {
+ let data = $(this).data(DATA_KEY)
+ let _config = typeof config === 'object' ?
+ config : null
+
+ if (!data && /destroy|hide/.test(config)) {
+ return
+ }
+
+ if (!data) {
+ data = new Tooltip(this, _config)
+ $(this).data(DATA_KEY, data)
+ }
+
+ if (typeof config === 'string') {
+ data[config]()
+ }
+ })
+ }
+
+ }
+
+
+ /**
+ * ------------------------------------------------------------------------
+ * jQuery
+ * ------------------------------------------------------------------------
+ */
+
+ $.fn[NAME] = Tooltip._jQueryInterface
+ $.fn[NAME].Constructor = Tooltip
+ $.fn[NAME].noConflict = function () {
+ $.fn[NAME] = JQUERY_NO_CONFLICT
+ return Tooltip._jQueryInterface
+ }
+
+ return Tooltip
+
+})(jQuery)
+
+export default Tooltip