From 5d7b51e1d0d50e999041149bd0b681fb5bf633b3 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Wed, 27 Jan 2021 19:01:24 +0200 Subject: [PATCH] Tooltip refactoring (#32523) * tooltip: move common code to a reusable function * tooltip: return early in `show()` Co-authored-by: Rohit Sharma Co-authored-by: XhmikosR --- js/src/tooltip.js | 158 ++++++++++++++++++++++------------------------ 1 file changed, 74 insertions(+), 84 deletions(-) diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 63a30cf2fbd2..909cb0f8a7d5 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -191,13 +191,7 @@ class Tooltip extends BaseComponent { } 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) - } + const context = this._initializeOnDelegatedTarget(event) context._activeTrigger.click = !context._activeTrigger.click @@ -245,83 +239,85 @@ class Tooltip extends BaseComponent { 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 (!(this.isWithContent() && this._isEnabled)) { + return + } - if (showEvent.defaultPrevented || !isInTheDom) { - return - } + 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) - const tip = this.getTipElement() - const tipId = getUID(this.constructor.NAME) + if (showEvent.defaultPrevented || !isInTheDom) { + return + } - tip.setAttribute('id', tipId) - this._element.setAttribute('aria-describedby', tipId) + const tip = this.getTipElement() + const tipId = getUID(this.constructor.NAME) - this.setContent() + tip.setAttribute('id', tipId) + this._element.setAttribute('aria-describedby', tipId) - if (this.config.animation) { - tip.classList.add(CLASS_NAME_FADE) - } + this.setContent() - const placement = typeof this.config.placement === 'function' ? - this.config.placement.call(this, tip, this._element) : - this.config.placement + if (this.config.animation) { + tip.classList.add(CLASS_NAME_FADE) + } - const attachment = this._getAttachment(placement) - this._addAttachmentClass(attachment) + const placement = typeof this.config.placement === 'function' ? + this.config.placement.call(this, tip, this._element) : + this.config.placement - const container = this._getContainer() - Data.setData(tip, this.constructor.DATA_KEY, this) + const attachment = this._getAttachment(placement) + this._addAttachmentClass(attachment) - if (!this._element.ownerDocument.documentElement.contains(this.tip)) { - container.appendChild(tip) - } + const container = this._getContainer() + Data.setData(tip, this.constructor.DATA_KEY, this) - EventHandler.trigger(this._element, this.constructor.Event.INSERTED) + if (!this._element.ownerDocument.documentElement.contains(this.tip)) { + container.appendChild(tip) + } - this._popper = Popper.createPopper(this._element, tip, this._getPopperConfig(attachment)) + EventHandler.trigger(this._element, this.constructor.Event.INSERTED) - tip.classList.add(CLASS_NAME_SHOW) + this._popper = Popper.createPopper(this._element, tip, this._getPopperConfig(attachment)) - const customClass = typeof this.config.customClass === 'function' ? this.config.customClass() : this.config.customClass - if (customClass) { - tip.classList.add(...customClass.split(' ')) - } + tip.classList.add(CLASS_NAME_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) { - [].concat(...document.body.children).forEach(element => { - EventHandler.on(element, 'mouseover', noop()) - }) - } + const customClass = typeof this.config.customClass === 'function' ? this.config.customClass() : this.config.customClass + if (customClass) { + tip.classList.add(...customClass.split(' ')) + } - const complete = () => { - const prevHoverState = this._hoverState + // 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) { + [].concat(...document.body.children).forEach(element => { + EventHandler.on(element, 'mouseover', noop()) + }) + } - this._hoverState = null - EventHandler.trigger(this._element, this.constructor.Event.SHOWN) + const complete = () => { + const prevHoverState = this._hoverState - if (prevHoverState === HOVER_STATE_OUT) { - this._leave(null, this) - } - } + this._hoverState = null + EventHandler.trigger(this._element, this.constructor.Event.SHOWN) - if (this.tip.classList.contains(CLASS_NAME_FADE)) { - const transitionDuration = getTransitionDurationFromElement(this.tip) - EventHandler.one(this.tip, 'transitionend', complete) - emulateTransitionEnd(this.tip, transitionDuration) - } else { - complete() + if (prevHoverState === HOVER_STATE_OUT) { + this._leave(null, this) } } + + if (this.tip.classList.contains(CLASS_NAME_FADE)) { + const transitionDuration = getTransitionDurationFromElement(this.tip) + EventHandler.one(this.tip, 'transitionend', complete) + emulateTransitionEnd(this.tip, transitionDuration) + } else { + complete() + } } hide() { @@ -465,6 +461,18 @@ class Tooltip extends BaseComponent { // Private + _initializeOnDelegatedTarget(event, context) { + const dataKey = this.constructor.DATA_KEY + context = context || Data.getData(event.delegateTarget, dataKey) + + if (!context) { + context = new this.constructor(event.delegateTarget, this._getDelegateConfig()) + Data.setData(event.delegateTarget, dataKey, context) + } + + return context + } + _getPopperConfig(attachment) { const defaultBsConfig = { placement: attachment, @@ -582,16 +590,7 @@ class Tooltip extends BaseComponent { } _enter(event, context) { - const dataKey = this.constructor.DATA_KEY - context = context || Data.getData(event.delegateTarget, dataKey) - - if (!context) { - context = new this.constructor( - event.delegateTarget, - this._getDelegateConfig() - ) - Data.setData(event.delegateTarget, dataKey, context) - } + context = this._initializeOnDelegatedTarget(event, context) if (event) { context._activeTrigger[ @@ -621,16 +620,7 @@ class Tooltip extends BaseComponent { } _leave(event, context) { - const dataKey = this.constructor.DATA_KEY - context = context || Data.getData(event.delegateTarget, dataKey) - - if (!context) { - context = new this.constructor( - event.delegateTarget, - this._getDelegateConfig() - ) - Data.setData(event.delegateTarget, dataKey, context) - } + context = this._initializeOnDelegatedTarget(event, context) if (event) { context._activeTrigger[