From 1a6fdfae6be09b09eaced8f0e442ca6f7680a61e Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Sat, 9 Oct 2021 09:33:12 +0300 Subject: Bump version to 5.1.3. --- js/src/tooltip.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src/tooltip.js') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index a26b8ada6..d8bb31a76 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -1,6 +1,6 @@ /** * -------------------------------------------------------------------------- - * Bootstrap (v5.1.2): tooltip.js + * Bootstrap (v5.1.3): tooltip.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ -- cgit v1.2.3 From 24e3ca2474a51f996e9166c065da80b068e8e599 Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Sun, 10 Oct 2021 14:49:41 +0300 Subject: tooltip.js: ignore a LGTM error (#35147) The code on this line is either sanitized or the user chose to not sanitize it. --- js/src/tooltip.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'js/src/tooltip.js') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 423a192c0..7a8986548 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -421,7 +421,7 @@ class Tooltip extends BaseComponent { content = sanitizeHtml(content, this._config.allowList, this._config.sanitizeFn) } - element.innerHTML = content + element.innerHTML = content // lgtm [js/xss-through-dom] } else { element.textContent = content } -- cgit v1.2.3 From e8f702666f285a3e69866ed1f8d29fa6eaaaeabb Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Wed, 13 Oct 2021 15:19:28 +0300 Subject: JS: minor refactoring (#35183) * add missing comments * shorten block comments * reorder constants * reorder public/private methods * sort exports alphabetically in util/index.js * fix a couple of typos --- js/src/tooltip.js | 88 ++++++++++++++++++++++++------------------------------- 1 file changed, 38 insertions(+), 50 deletions(-) (limited to 'js/src/tooltip.js') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index 7a8986548..f069dc751 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -6,7 +6,6 @@ */ import * as Popper from '@popperjs/core' - import { defineJQueryPlugin, findShadowRoot, @@ -25,9 +24,7 @@ import SelectorEngine from './dom/selector-engine' import BaseComponent from './base-component' /** - * ------------------------------------------------------------------------ * Constants - * ------------------------------------------------------------------------ */ const NAME = 'tooltip' @@ -36,25 +33,22 @@ const EVENT_KEY = `.${DATA_KEY}` const CLASS_PREFIX = 'bs-tooltip' const DISALLOWED_ATTRIBUTES = new Set(['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: '(array|string|function)', - container: '(string|element|boolean)', - fallbackPlacements: 'array', - boundary: '(string|element)', - customClass: '(string|function)', - sanitize: 'boolean', - sanitizeFn: '(null|function)', - allowList: 'object', - popperConfig: '(null|object|function)' -} +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 SELECTOR_MODAL = `.${CLASS_NAME_MODAL}` + +const EVENT_MODAL_HIDE = 'hide.bs.modal' + +const TRIGGER_HOVER = 'hover' +const TRIGGER_FOCUS = 'focus' +const TRIGGER_CLICK = 'click' +const TRIGGER_MANUAL = 'manual' const AttachmentMap = { AUTO: 'auto', @@ -87,6 +81,26 @@ const Default = { popperConfig: null } +const DefaultType = { + animation: 'boolean', + template: 'string', + title: '(string|element|function)', + trigger: 'string', + delay: '(number|object)', + html: 'boolean', + selector: '(string|boolean)', + placement: '(string|function)', + offset: '(array|string|function)', + container: '(string|element|boolean)', + fallbackPlacements: 'array', + boundary: '(string|element)', + customClass: '(string|function)', + sanitize: 'boolean', + sanitizeFn: '(null|function)', + allowList: 'object', + popperConfig: '(null|object|function)' +} + const Event = { HIDE: `hide${EVENT_KEY}`, HIDDEN: `hidden${EVENT_KEY}`, @@ -100,27 +114,8 @@ const Event = { 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 SELECTOR_MODAL = `.${CLASS_NAME_MODAL}` - -const EVENT_MODAL_HIDE = 'hide.bs.modal' - -const TRIGGER_HOVER = 'hover' -const TRIGGER_FOCUS = 'focus' -const TRIGGER_CLICK = 'click' -const TRIGGER_MANUAL = 'manual' - /** - * ------------------------------------------------------------------------ - * Class Definition - * ------------------------------------------------------------------------ + * Class definition */ class Tooltip extends BaseComponent { @@ -131,7 +126,7 @@ class Tooltip extends BaseComponent { super(element) - // private + // Private this._isEnabled = true this._timeout = 0 this._hoverState = '' @@ -146,7 +141,6 @@ class Tooltip extends BaseComponent { } // Getters - static get Default() { return Default } @@ -164,7 +158,6 @@ class Tooltip extends BaseComponent { } // Public - enable() { this._isEnabled = true } @@ -358,7 +351,6 @@ class Tooltip extends BaseComponent { } // Protected - isWithContent() { return Boolean(this.getTitle()) } @@ -446,7 +438,6 @@ class Tooltip extends BaseComponent { } // Private - _initializeOnDelegatedTarget(event, context) { return context || this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig()) } @@ -754,10 +745,7 @@ class Tooltip extends BaseComponent { } /** - * ------------------------------------------------------------------------ * jQuery - * ------------------------------------------------------------------------ - * add .Tooltip to jQuery only if jQuery is present */ defineJQueryPlugin(Tooltip) -- cgit v1.2.3 From 94a596fbcb1011ba990da2078ba7e20b39dba2d9 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 25 Nov 2021 19:14:02 +0200 Subject: Add a template factory helper to handle all template cases (#34519) Co-authored-by: XhmikosR --- js/src/tooltip.js | 129 ++++++++++++++++++++---------------------------------- 1 file changed, 47 insertions(+), 82 deletions(-) (limited to 'js/src/tooltip.js') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index f069dc751..c84596101 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -11,17 +11,16 @@ import { findShadowRoot, getElement, getUID, - isElement, isRTL, noop, typeCheckConfig } from './util/index' -import { DefaultAllowlist, sanitizeHtml } from './util/sanitizer' +import { DefaultAllowlist } from './util/sanitizer' import Data from './dom/data' import EventHandler from './dom/event-handler' import Manipulator from './dom/manipulator' -import SelectorEngine from './dom/selector-engine' import BaseComponent from './base-component' +import TemplateFactory from './util/template-factory' /** * Constants @@ -40,6 +39,7 @@ const CLASS_NAME_SHOW = 'show' const HOVER_STATE_SHOW = 'show' const HOVER_STATE_OUT = 'out' +const SELECTOR_TOOLTIP_ARROW = '.tooltip-arrow' const SELECTOR_TOOLTIP_INNER = '.tooltip-inner' const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}` @@ -132,6 +132,7 @@ class Tooltip extends BaseComponent { this._hoverState = '' this._activeTrigger = {} this._popper = null + this._templateFactory = null // Protected this._config = this._getConfig(config) @@ -227,23 +228,9 @@ class Tooltip extends BaseComponent { return } - // A trick to recreate a tooltip in case a new title is given by using the NOT documented `data-bs-original-title` - // This will be removed later in favor of a `setContent` method - if (this.constructor.NAME === 'tooltip' && this.tip && this.getTitle() !== this.tip.querySelector(SELECTOR_TOOLTIP_INNER).innerHTML) { - this._disposePopper() - this.tip.remove() - this.tip = null - } - const tip = this.getTipElement() - const tipId = getUID(this.constructor.NAME) - - tip.setAttribute('id', tipId) - this._element.setAttribute('aria-describedby', tipId) - if (this._config.animation) { - tip.classList.add(CLASS_NAME_FADE) - } + this._element.setAttribute('aria-describedby', tip.getAttribute('id')) const placement = typeof this._config.placement === 'function' ? this._config.placement.call(this, tip, this._element) : @@ -268,11 +255,6 @@ class Tooltip extends BaseComponent { tip.classList.add(CLASS_NAME_SHOW) - const customClass = this._resolvePossibleFunction(this._config.customClass) - if (customClass) { - tip.classList.add(...customClass.split(' ')) - } - // 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 @@ -360,69 +342,63 @@ class Tooltip extends BaseComponent { return this.tip } - const element = document.createElement('div') - element.innerHTML = this._config.template + const templateFactory = this._getTemplateFactory(this._getContentForTemplate()) - const tip = element.children[0] - this.setContent(tip) + const tip = templateFactory.toHtml() tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW) - this.tip = tip - return this.tip - } - - setContent(tip) { - this._sanitizeAndSetContent(tip, this.getTitle(), SELECTOR_TOOLTIP_INNER) - } + const tipId = getUID(this.constructor.NAME).toString() - _sanitizeAndSetContent(template, content, selector) { - const templateElement = SelectorEngine.findOne(selector, template) + tip.setAttribute('id', tipId) - if (!content && templateElement) { - templateElement.remove() - return + if (this._config.animation) { + tip.classList.add(CLASS_NAME_FADE) } - // we use append for html objects to maintain js events - this.setElementContent(templateElement, content) + this.tip = tip + return this.tip } - setElementContent(element, content) { - if (element === null) { - return + setContent(content) { + let isShown = false + if (this.tip) { + isShown = this.tip.classList.contains(CLASS_NAME_SHOW) + this.tip.remove() } - if (isElement(content)) { - content = getElement(content) + this._disposePopper() - // content is a DOM node or a jQuery - if (this._config.html) { - if (content.parentNode !== element) { - element.innerHTML = '' - element.append(content) - } - } else { - element.textContent = content.textContent - } + this.tip = this._getTemplateFactory(content).toHtml() - return + if (isShown) { + this.show() } + } - if (this._config.html) { - if (this._config.sanitize) { - content = sanitizeHtml(content, this._config.allowList, this._config.sanitizeFn) - } - - element.innerHTML = content // lgtm [js/xss-through-dom] + _getTemplateFactory(content) { + if (this._templateFactory) { + this._templateFactory.changeContent(content) } else { - element.textContent = content + this._templateFactory = new TemplateFactory({ + ...this._config, + // the `content` var has to be after `this._config` + // to override config.content in case of popover + content, + extraClass: this._resolvePossibleFunction(this._config.customClass) + }) } + + return this._templateFactory } - getTitle() { - const title = this._element.getAttribute('data-bs-original-title') || this._config.title + _getContentForTemplate() { + return { + [SELECTOR_TOOLTIP_INNER]: this.getTitle() + } + } - return this._resolvePossibleFunction(title) + getTitle() { + return this._resolvePossibleFunction(this._config.title) || this._element.getAttribute('title') } updateAttachment(attachment) { @@ -456,8 +432,8 @@ class Tooltip extends BaseComponent { return offset } - _resolvePossibleFunction(content) { - return typeof content === 'function' ? content.call(this._element) : content + _resolvePossibleFunction(arg) { + return typeof arg === 'function' ? arg.call(this._element) : arg } _getPopperConfig(attachment) { @@ -485,7 +461,7 @@ class Tooltip extends BaseComponent { { name: 'arrow', options: { - element: `.${this.constructor.NAME}-arrow` + element: SELECTOR_TOOLTIP_ARROW } }, { @@ -556,15 +532,9 @@ class Tooltip extends BaseComponent { _fixTitle() { const title = this._element.getAttribute('title') - const originalTitleType = typeof this._element.getAttribute('data-bs-original-title') - if (title || originalTitleType !== 'string') { - this._element.setAttribute('data-bs-original-title', title || '') - if (title && !this._element.getAttribute('aria-label') && !this._element.textContent) { - this._element.setAttribute('aria-label', title) - } - - this._element.setAttribute('title', '') + if (title && !this._element.getAttribute('aria-label') && !this._element.textContent) { + this._element.setAttribute('aria-label', title) } } @@ -670,11 +640,6 @@ class Tooltip extends BaseComponent { } typeCheckConfig(NAME, config, this.constructor.DefaultType) - - if (config.sanitize) { - config.template = sanitizeHtml(config.template, config.allowList, config.sanitizeFn) - } - return config } -- cgit v1.2.3 From 374eeecfbcabae20da08ce00d0f9268bb67b1e5e Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 25 Nov 2021 19:39:13 +0200 Subject: tooltip.js: use array.includes instead of for iteration (#35127) --- js/src/tooltip.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'js/src/tooltip.js') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index c84596101..bc59e6e94 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -598,13 +598,7 @@ class Tooltip extends BaseComponent { } _isWithActiveTrigger() { - for (const trigger in this._activeTrigger) { - if (this._activeTrigger[trigger]) { - return true - } - } - - return false + return Object.values(this._activeTrigger).includes(true) } _getConfig(config) { -- cgit v1.2.3 From 6f077ff7bcf5c3e8a9cd579e26a2325ce32543c9 Mon Sep 17 00:00:00 2001 From: GeoSot Date: Thu, 25 Nov 2021 20:08:11 +0200 Subject: Clean tooltip component unneeded functionality (#32692) --- js/src/tooltip.js | 63 +++++++------------------------------------------------ 1 file changed, 7 insertions(+), 56 deletions(-) (limited to 'js/src/tooltip.js') diff --git a/js/src/tooltip.js b/js/src/tooltip.js index bc59e6e94..29be4d8d2 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -29,7 +29,6 @@ import TemplateFactory from './util/template-factory' const NAME = 'tooltip' const DATA_KEY = 'bs.tooltip' const EVENT_KEY = `.${DATA_KEY}` -const CLASS_PREFIX = 'bs-tooltip' const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']) const CLASS_NAME_FADE = 'fade' @@ -232,13 +231,6 @@ class Tooltip extends BaseComponent { this._element.setAttribute('aria-describedby', tip.getAttribute('id')) - const placement = typeof this._config.placement === 'function' ? - this._config.placement.call(this, tip, this._element) : - this._config.placement - - const attachment = this._getAttachment(placement) - this._addAttachmentClass(attachment) - const { container } = this._config Data.set(tip, this.constructor.DATA_KEY, this) @@ -250,6 +242,10 @@ class Tooltip extends BaseComponent { if (this._popper) { this._popper.update() } else { + const placement = typeof this._config.placement === 'function' ? + this._config.placement.call(this, tip, this._element) : + this._config.placement + const attachment = AttachmentMap[placement.toUpperCase()] this._popper = Popper.createPopper(this._element, tip, this._getPopperConfig(attachment)) } @@ -295,7 +291,6 @@ class Tooltip extends BaseComponent { tip.remove() } - this._cleanTipClass() this._element.removeAttribute('aria-describedby') EventHandler.trigger(this._element, this.constructor.Event.HIDDEN) @@ -346,6 +341,8 @@ class Tooltip extends BaseComponent { const tip = templateFactory.toHtml() tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW) + // todo on v6 the following can be done on css only + tip.classList.add(`bs-${this.constructor.NAME}-auto`) const tipId = getUID(this.constructor.NAME).toString() @@ -463,19 +460,8 @@ class Tooltip extends BaseComponent { options: { element: SELECTOR_TOOLTIP_ARROW } - }, - { - name: 'onChange', - enabled: true, - phase: 'afterWrite', - fn: data => this._handlePopperPlacementChange(data) - } - ], - onFirstUpdate: data => { - if (data.options.placement !== data.placement) { - this._handlePopperPlacementChange(data) } - } + ] } return { @@ -484,14 +470,6 @@ class Tooltip extends BaseComponent { } } - _addAttachmentClass(attachment) { - this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(attachment)}`) - } - - _getAttachment(placement) { - return AttachmentMap[placement.toUpperCase()] - } - _setListeners() { const triggers = this._config.trigger.split(' ') @@ -652,33 +630,6 @@ class Tooltip extends BaseComponent { return config } - _cleanTipClass() { - const tip = this.getTipElement() - const basicClassPrefixRegex = new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`, 'g') - const tabClass = tip.getAttribute('class').match(basicClassPrefixRegex) - if (tabClass !== null && tabClass.length > 0) { - for (const tClass of tabClass.map(token => token.trim())) { - tip.classList.remove(tClass) - } - } - } - - _getBasicClassPrefix() { - return CLASS_PREFIX - } - - _handlePopperPlacementChange(popperData) { - const { state } = popperData - - if (!state) { - return - } - - this.tip = state.elements.popper - this._cleanTipClass() - this._addAttachmentClass(this._getAttachment(state.placement)) - } - _disposePopper() { if (this._popper) { this._popper.destroy() -- cgit v1.2.3